diff options
116 files changed, 2243 insertions, 891 deletions
diff --git a/Android.mk b/Android.mk index 5766e46658c3..ba4e173ed0c7 100644 --- a/Android.mk +++ b/Android.mk @@ -308,6 +308,7 @@ LOCAL_SRC_FILES += \ core/java/com/android/internal/app/IEphemeralResolver.aidl \ core/java/com/android/internal/app/ISoundTriggerService.aidl \ core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl \ + core/java/com/android/internal/app/IVoiceInteractionSessionListener.aidl \ core/java/com/android/internal/app/IVoiceInteractionSessionShowCallback.aidl \ core/java/com/android/internal/app/IVoiceInteractor.aidl \ core/java/com/android/internal/app/IVoiceInteractorCallback.aidl \ @@ -1060,6 +1061,42 @@ LOCAL_DROIDDOC_OPTIONS:=\ -title "Android SDK" \ -proofread $(OUT_DOCS)/$(LOCAL_MODULE)-proofread.txt \ -sdkvalues $(OUT_DOCS) \ + -hdf android.whichdoc offline + +LOCAL_DROIDDOC_CUSTOM_TEMPLATE_DIR:=build/tools/droiddoc/templates-sdk-dev + +include $(BUILD_DROIDDOC) + +static_doc_index_redirect := $(out_dir)/index.html +$(static_doc_index_redirect): \ + $(LOCAL_PATH)/docs/docs-preview-index.html | $(ACP) + $(hide) mkdir -p $(dir $@) + $(hide) $(ACP) $< $@ + +$(full_target): $(static_doc_index_redirect) +$(full_target): $(framework_built) + + +# ==== static html in the sdk ================================== +include $(CLEAR_VARS) + +LOCAL_SRC_FILES:=$(framework_docs_LOCAL_SRC_FILES) +LOCAL_INTERMEDIATE_SOURCES:=$(framework_docs_LOCAL_INTERMEDIATE_SOURCES) +LOCAL_JAVA_LIBRARIES:=$(framework_docs_LOCAL_JAVA_LIBRARIES) +LOCAL_MODULE_CLASS:=$(framework_docs_LOCAL_MODULE_CLASS) +LOCAL_DROIDDOC_SOURCE_PATH:=$(framework_docs_LOCAL_DROIDDOC_SOURCE_PATH) +LOCAL_DROIDDOC_HTML_DIR:=$(framework_docs_LOCAL_DROIDDOC_HTML_DIR) +LOCAL_ADDITIONAL_JAVA_DIR:=$(framework_docs_LOCAL_ADDITIONAL_JAVA_DIR) +LOCAL_ADDITIONAL_DEPENDENCIES:=$(framework_docs_LOCAL_ADDITIONAL_DEPENDENCIES) + +LOCAL_MODULE := offline-sdk-referenceonly + +LOCAL_DROIDDOC_OPTIONS:=\ + $(framework_docs_LOCAL_DROIDDOC_OPTIONS) \ + -offlinemode \ + -title "Android SDK" \ + -proofread $(OUT_DOCS)/$(LOCAL_MODULE)-proofread.txt \ + -sdkvalues $(OUT_DOCS) \ -hdf android.whichdoc offline \ -referenceonly @@ -1101,7 +1138,7 @@ LOCAL_DROIDDOC_OPTIONS:= \ -hdf android.hasSamples true \ -samplesdir $(samples_dir) -LOCAL_DROIDDOC_CUSTOM_TEMPLATE_DIR:=build/tools/droiddoc/templates-sdk +LOCAL_DROIDDOC_CUSTOM_TEMPLATE_DIR:=build/tools/droiddoc/templates-sdk-dev include $(BUILD_DROIDDOC) diff --git a/api/current.txt b/api/current.txt index b9c026988607..b675136153d5 100644 --- a/api/current.txt +++ b/api/current.txt @@ -10063,17 +10063,14 @@ package android.content.pm { method public android.content.ComponentName getActivity(); method public java.util.Set<java.lang.String> getCategories(); method public java.lang.CharSequence getDisabledMessage(); - method public int getDisabledMessageResourceId(); method public android.os.PersistableBundle getExtras(); method public java.lang.String getId(); method public android.content.Intent getIntent(); method public long getLastChangedTimestamp(); method public java.lang.CharSequence getLongLabel(); - method public int getLongLabelResourceId(); method public java.lang.String getPackage(); method public int getRank(); method public java.lang.CharSequence getShortLabel(); - method public int getShortLabelResourceId(); method public android.os.UserHandle getUserHandle(); method public boolean hasKeyFieldsOnly(); method public boolean isDeclaredInManifest(); @@ -10087,7 +10084,6 @@ package android.content.pm { } public static class ShortcutInfo.Builder { - ctor public deprecated ShortcutInfo.Builder(android.content.Context); ctor public ShortcutInfo.Builder(android.content.Context, java.lang.String); method public android.content.pm.ShortcutInfo build(); method public android.content.pm.ShortcutInfo.Builder setActivity(android.content.ComponentName); @@ -10095,7 +10091,6 @@ package android.content.pm { method public android.content.pm.ShortcutInfo.Builder setDisabledMessage(java.lang.CharSequence); method public android.content.pm.ShortcutInfo.Builder setExtras(android.os.PersistableBundle); method public android.content.pm.ShortcutInfo.Builder setIcon(android.graphics.drawable.Icon); - method public deprecated android.content.pm.ShortcutInfo.Builder setId(java.lang.String); method public android.content.pm.ShortcutInfo.Builder setIntent(android.content.Intent); method public android.content.pm.ShortcutInfo.Builder setLongLabel(java.lang.CharSequence); method public android.content.pm.ShortcutInfo.Builder setRank(int); diff --git a/api/system-current.txt b/api/system-current.txt index b0fb9f3b1759..d69cb648917f 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -10488,17 +10488,14 @@ package android.content.pm { method public android.content.ComponentName getActivity(); method public java.util.Set<java.lang.String> getCategories(); method public java.lang.CharSequence getDisabledMessage(); - method public int getDisabledMessageResourceId(); method public android.os.PersistableBundle getExtras(); method public java.lang.String getId(); method public android.content.Intent getIntent(); method public long getLastChangedTimestamp(); method public java.lang.CharSequence getLongLabel(); - method public int getLongLabelResourceId(); method public java.lang.String getPackage(); method public int getRank(); method public java.lang.CharSequence getShortLabel(); - method public int getShortLabelResourceId(); method public android.os.UserHandle getUserHandle(); method public boolean hasKeyFieldsOnly(); method public boolean isDeclaredInManifest(); @@ -10512,7 +10509,6 @@ package android.content.pm { } public static class ShortcutInfo.Builder { - ctor public deprecated ShortcutInfo.Builder(android.content.Context); ctor public ShortcutInfo.Builder(android.content.Context, java.lang.String); method public android.content.pm.ShortcutInfo build(); method public android.content.pm.ShortcutInfo.Builder setActivity(android.content.ComponentName); @@ -10520,7 +10516,6 @@ package android.content.pm { method public android.content.pm.ShortcutInfo.Builder setDisabledMessage(java.lang.CharSequence); method public android.content.pm.ShortcutInfo.Builder setExtras(android.os.PersistableBundle); method public android.content.pm.ShortcutInfo.Builder setIcon(android.graphics.drawable.Icon); - method public deprecated android.content.pm.ShortcutInfo.Builder setId(java.lang.String); method public android.content.pm.ShortcutInfo.Builder setIntent(android.content.Intent); method public android.content.pm.ShortcutInfo.Builder setLongLabel(java.lang.CharSequence); method public android.content.pm.ShortcutInfo.Builder setRank(int); @@ -39387,10 +39382,14 @@ package android.telecom { method public java.lang.String getConnectionService(); method public java.util.List<android.telecom.ParcelableCallAnalytics.EventTiming> getEventTimings(); method public long getStartTimeMillis(); + method public java.util.List<android.telecom.ParcelableCallAnalytics.VideoEvent> getVideoEvents(); method public boolean isAdditionalCall(); method public boolean isCreatedFromExistingConnection(); method public boolean isEmergencyCall(); method public boolean isInterrupted(); + method public boolean isVideoCall(); + method public void setIsVideoCall(boolean); + method public void setVideoEvents(java.util.List<android.telecom.ParcelableCallAnalytics.VideoEvent>); method public void writeToParcel(android.os.Parcel, int); field public static final int CALLTYPE_INCOMING = 1; // 0x1 field public static final int CALLTYPE_OUTGOING = 2; // 0x2 @@ -39473,6 +39472,20 @@ package android.telecom { field public static final int UNHOLD_TIMING = 4; // 0x4 } + public static final class ParcelableCallAnalytics.VideoEvent implements android.os.Parcelable { + ctor public ParcelableCallAnalytics.VideoEvent(int, long, int); + method public int describeContents(); + method public int getEventName(); + method public long getTimeSinceLastEvent(); + method public int getVideoState(); + method public void writeToParcel(android.os.Parcel, int); + field public static final android.os.Parcelable.Creator<android.telecom.ParcelableCallAnalytics.VideoEvent> CREATOR; + field public static final int RECEIVE_REMOTE_SESSION_MODIFY_REQUEST = 2; // 0x2 + field public static final int RECEIVE_REMOTE_SESSION_MODIFY_RESPONSE = 3; // 0x3 + field public static final int SEND_LOCAL_SESSION_MODIFY_REQUEST = 0; // 0x0 + field public static final int SEND_LOCAL_SESSION_MODIFY_RESPONSE = 1; // 0x1 + } + public final deprecated class Phone { method public final void addListener(android.telecom.Phone.Listener); method public final boolean canAddCall(); diff --git a/api/test-current.txt b/api/test-current.txt index 1906c5efe79f..95d2408db4b5 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -10076,17 +10076,14 @@ package android.content.pm { method public android.content.ComponentName getActivity(); method public java.util.Set<java.lang.String> getCategories(); method public java.lang.CharSequence getDisabledMessage(); - method public int getDisabledMessageResourceId(); method public android.os.PersistableBundle getExtras(); method public java.lang.String getId(); method public android.content.Intent getIntent(); method public long getLastChangedTimestamp(); method public java.lang.CharSequence getLongLabel(); - method public int getLongLabelResourceId(); method public java.lang.String getPackage(); method public int getRank(); method public java.lang.CharSequence getShortLabel(); - method public int getShortLabelResourceId(); method public android.os.UserHandle getUserHandle(); method public boolean hasKeyFieldsOnly(); method public boolean isDeclaredInManifest(); @@ -10100,7 +10097,6 @@ package android.content.pm { } public static class ShortcutInfo.Builder { - ctor public deprecated ShortcutInfo.Builder(android.content.Context); ctor public ShortcutInfo.Builder(android.content.Context, java.lang.String); method public android.content.pm.ShortcutInfo build(); method public android.content.pm.ShortcutInfo.Builder setActivity(android.content.ComponentName); @@ -10108,7 +10104,6 @@ package android.content.pm { method public android.content.pm.ShortcutInfo.Builder setDisabledMessage(java.lang.CharSequence); method public android.content.pm.ShortcutInfo.Builder setExtras(android.os.PersistableBundle); method public android.content.pm.ShortcutInfo.Builder setIcon(android.graphics.drawable.Icon); - method public deprecated android.content.pm.ShortcutInfo.Builder setId(java.lang.String); method public android.content.pm.ShortcutInfo.Builder setIntent(android.content.Intent); method public android.content.pm.ShortcutInfo.Builder setLongLabel(java.lang.CharSequence); method public android.content.pm.ShortcutInfo.Builder setRank(int); diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index 3f15a755bd79..fef1e2c63e46 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -6057,7 +6057,7 @@ public final class ActivityThread { // StrictMode) on debug builds, but using DropBox, not logs. CloseGuard.setEnabled(false); - Environment.initForCurrentUser(); + Environment.init(); // Set the reporter for event logging in libcore EventLogger.setReporter(new EventLoggingReporter()); diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java index e6ca52072079..8f424676dbe5 100644 --- a/core/java/android/app/ContextImpl.java +++ b/core/java/android/app/ContextImpl.java @@ -185,15 +185,6 @@ class ContextImpl extends Context { @GuardedBy("mSync") private File mCodeCacheDir; - @GuardedBy("mSync") - private File[] mExternalObbDirs; - @GuardedBy("mSync") - private File[] mExternalFilesDirs; - @GuardedBy("mSync") - private File[] mExternalCacheDirs; - @GuardedBy("mSync") - private File[] mExternalMediaDirs; - // The system service cache for the system services that are cached per-ContextImpl. final Object[] mServiceCache = SystemServiceRegistry.createServiceCache(); @@ -562,17 +553,10 @@ class ContextImpl extends Context { @Override public File[] getExternalFilesDirs(String type) { synchronized (mSync) { - if (mExternalFilesDirs == null) { - mExternalFilesDirs = Environment.buildExternalStorageAppFilesDirs(getPackageName()); - } - - // Splice in requested type, if any - File[] dirs = mExternalFilesDirs; + File[] dirs = Environment.buildExternalStorageAppFilesDirs(getPackageName()); if (type != null) { dirs = Environment.buildPaths(dirs, type); } - - // Create dirs if needed return ensureExternalDirsExistOrFilter(dirs); } } @@ -586,12 +570,8 @@ class ContextImpl extends Context { @Override public File[] getObbDirs() { synchronized (mSync) { - if (mExternalObbDirs == null) { - mExternalObbDirs = Environment.buildExternalStorageAppObbDirs(getPackageName()); - } - - // Create dirs if needed - return ensureExternalDirsExistOrFilter(mExternalObbDirs); + File[] dirs = Environment.buildExternalStorageAppObbDirs(getPackageName()); + return ensureExternalDirsExistOrFilter(dirs); } } @@ -624,24 +604,16 @@ class ContextImpl extends Context { @Override public File[] getExternalCacheDirs() { synchronized (mSync) { - if (mExternalCacheDirs == null) { - mExternalCacheDirs = Environment.buildExternalStorageAppCacheDirs(getPackageName()); - } - - // Create dirs if needed - return ensureExternalDirsExistOrFilter(mExternalCacheDirs); + File[] dirs = Environment.buildExternalStorageAppCacheDirs(getPackageName()); + return ensureExternalDirsExistOrFilter(dirs); } } @Override public File[] getExternalMediaDirs() { synchronized (mSync) { - if (mExternalMediaDirs == null) { - mExternalMediaDirs = Environment.buildExternalStorageAppMediaDirs(getPackageName()); - } - - // Create dirs if needed - return ensureExternalDirsExistOrFilter(mExternalMediaDirs); + File[] dirs = Environment.buildExternalStorageAppMediaDirs(getPackageName()); + return ensureExternalDirsExistOrFilter(dirs); } } diff --git a/core/java/android/content/ContentResolver.java b/core/java/android/content/ContentResolver.java index c8d89204d8d4..b3320d6f6976 100644 --- a/core/java/android/content/ContentResolver.java +++ b/core/java/android/content/ContentResolver.java @@ -34,6 +34,7 @@ import android.database.CrossProcessCursorWrapper; import android.database.Cursor; import android.database.IContentObserver; import android.graphics.Point; +import android.graphics.drawable.Drawable; import android.net.Uri; import android.os.Bundle; import android.os.CancellationSignal; @@ -51,6 +52,7 @@ import android.util.EventLog; import android.util.Log; import com.android.internal.util.ArrayUtils; +import com.android.internal.util.MimeIconUtils; import com.android.internal.util.Preconditions; import dalvik.system.CloseGuard; @@ -2693,4 +2695,9 @@ public abstract class ContentResolver { public int resolveUserId(Uri uri) { return ContentProvider.getUserIdFromUri(uri, mContext.getUserId()); } + + /** @hide */ + public Drawable getTypeDrawable(String mimeType) { + return MimeIconUtils.loadMimeIcon(mContext, mimeType); + } } diff --git a/core/java/android/content/pm/ShortcutInfo.java b/core/java/android/content/pm/ShortcutInfo.java index 35370f0998fd..39e15e01bab4 100644 --- a/core/java/android/content/pm/ShortcutInfo.java +++ b/core/java/android/content/pm/ShortcutInfo.java @@ -696,7 +696,8 @@ public final class ShortcutInfo implements Parcelable { private PersistableBundle mExtras; /** - * Old style constructor. STOPSHIP hide it before launch. + * Old style constructor. + * @hide */ @Deprecated public Builder(Context context) { @@ -704,7 +705,8 @@ public final class ShortcutInfo implements Parcelable { } /** - * Used with the old style constructor, kept for unit tests. STOPSHIP hide it before launch. + * Used with the old style constructor, kept for unit tests. + * @hide */ @NonNull @Deprecated @@ -1004,7 +1006,7 @@ public final class ShortcutInfo implements Parcelable { return mTitle; } - /** TODO Javadoc */ + /** @hide */ public int getShortLabelResourceId() { return mTitleResId; } @@ -1017,7 +1019,7 @@ public final class ShortcutInfo implements Parcelable { return mText; } - /** TODO Javadoc */ + /** @hide */ public int getLongLabelResourceId() { return mTextResId; } @@ -1030,7 +1032,7 @@ public final class ShortcutInfo implements Parcelable { return mDisabledMessage; } - /** TODO Javadoc */ + /** @hide */ public int getDisabledMessageResourceId() { return mDisabledMessageResId; } diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java index a45e6f51d803..8d4137957b3f 100644 --- a/core/java/android/net/ConnectivityManager.java +++ b/core/java/android/net/ConnectivityManager.java @@ -3103,14 +3103,11 @@ public class ConnectivityManager { throw new IllegalArgumentException("Invalid NetworkCallback"); } try { + // CallbackHandler will release callback when receiving CALLBACK_RELEASED. mService.releaseNetworkRequest(networkCallback.networkRequest); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } - - synchronized (sNetworkCallback) { - sNetworkCallback.remove(networkCallback.networkRequest); - } } /** diff --git a/core/java/android/net/NetworkPolicyManager.java b/core/java/android/net/NetworkPolicyManager.java index 51c45e056092..11b861aef5aa 100644 --- a/core/java/android/net/NetworkPolicyManager.java +++ b/core/java/android/net/NetworkPolicyManager.java @@ -18,7 +18,6 @@ package android.net; import static android.content.pm.PackageManager.GET_SIGNATURES; import static android.net.NetworkPolicy.CYCLE_NONE; -import static android.text.format.Time.MONTH_DAY; import android.content.Context; import android.content.Intent; @@ -27,12 +26,13 @@ import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.Signature; import android.os.RemoteException; import android.os.UserHandle; -import android.text.format.Time; import android.util.DebugUtils; import com.google.android.collect.Sets; +import java.util.Calendar; import java.util.HashSet; +import java.util.TimeZone; /** * Manager for creating and modifying network policy rules. @@ -253,28 +253,18 @@ public class NetworkPolicyManager { throw new IllegalArgumentException("Unable to compute boundary without cycleDay"); } - final Time now = new Time(policy.cycleTimezone); - now.set(currentTime); - - // first, find cycle boundary for current month - final Time cycle = new Time(now); - cycle.hour = cycle.minute = cycle.second = 0; - snapToCycleDay(cycle, policy.cycleDay); - - if (Time.compare(cycle, now) >= 0) { - // cycle boundary is beyond now, use last cycle boundary; start by - // pushing ourselves squarely into last month. - final Time lastMonth = new Time(now); - lastMonth.hour = lastMonth.minute = lastMonth.second = 0; - lastMonth.monthDay = 1; - lastMonth.month -= 1; - lastMonth.normalize(true); - - cycle.set(lastMonth); - snapToCycleDay(cycle, policy.cycleDay); + final Calendar cal = Calendar.getInstance(TimeZone.getTimeZone(policy.cycleTimezone)); + cal.setTimeInMillis(currentTime); + snapToCycleDay(cal, policy.cycleDay); + + if (cal.getTimeInMillis() >= currentTime) { + // Cycle boundary is beyond now, use last cycle boundary + cal.set(Calendar.DAY_OF_MONTH, 1); + cal.add(Calendar.MONTH, -1); + snapToCycleDay(cal, policy.cycleDay); } - return cycle.toMillis(true); + return cal.getTimeInMillis(); } /** {@hide} */ @@ -283,28 +273,18 @@ public class NetworkPolicyManager { throw new IllegalArgumentException("Unable to compute boundary without cycleDay"); } - final Time now = new Time(policy.cycleTimezone); - now.set(currentTime); - - // first, find cycle boundary for current month - final Time cycle = new Time(now); - cycle.hour = cycle.minute = cycle.second = 0; - snapToCycleDay(cycle, policy.cycleDay); - - if (Time.compare(cycle, now) <= 0) { - // cycle boundary is before now, use next cycle boundary; start by - // pushing ourselves squarely into next month. - final Time nextMonth = new Time(now); - nextMonth.hour = nextMonth.minute = nextMonth.second = 0; - nextMonth.monthDay = 1; - nextMonth.month += 1; - nextMonth.normalize(true); - - cycle.set(nextMonth); - snapToCycleDay(cycle, policy.cycleDay); + final Calendar cal = Calendar.getInstance(TimeZone.getTimeZone(policy.cycleTimezone)); + cal.setTimeInMillis(currentTime); + snapToCycleDay(cal, policy.cycleDay); + + if (cal.getTimeInMillis() <= currentTime) { + // Cycle boundary is before now, use next cycle boundary + cal.set(Calendar.DAY_OF_MONTH, 1); + cal.add(Calendar.MONTH, 1); + snapToCycleDay(cal, policy.cycleDay); } - return cycle.toMillis(true); + return cal.getTimeInMillis(); } /** @@ -313,16 +293,17 @@ public class NetworkPolicyManager { * * @hide */ - public static void snapToCycleDay(Time time, int cycleDay) { - if (cycleDay > time.getActualMaximum(MONTH_DAY)) { - // cycle day isn't valid this month; snap to last second of month - time.month += 1; - time.monthDay = 1; - time.second = -1; + public static void snapToCycleDay(Calendar cal, int cycleDay) { + cal.set(Calendar.HOUR_OF_DAY, 0); + cal.set(Calendar.MINUTE, 0); + cal.set(Calendar.SECOND, 0); + if (cycleDay > cal.getActualMaximum(Calendar.DAY_OF_MONTH)) { + cal.set(Calendar.DAY_OF_MONTH, 1); + cal.add(Calendar.MONTH, 1); + cal.add(Calendar.SECOND, -1); } else { - time.monthDay = cycleDay; + cal.set(Calendar.DAY_OF_MONTH, cycleDay); } - time.normalize(true); } /** diff --git a/core/java/android/net/metrics/ApfProgramEvent.java b/core/java/android/net/metrics/ApfProgramEvent.java index e322dc1e1732..258d8e13951f 100644 --- a/core/java/android/net/metrics/ApfProgramEvent.java +++ b/core/java/android/net/metrics/ApfProgramEvent.java @@ -124,7 +124,7 @@ public final class ApfProgramEvent implements Parcelable { for (int bit = set.nextSetBit(0); bit >= 0; bit = set.nextSetBit(bit+1)) { names.add(Decoder.constants.get(bit)); } - return TextUtils.join(", ", names); + return TextUtils.join("|", names); } final static class Decoder { diff --git a/core/java/android/net/metrics/DhcpClientEvent.java b/core/java/android/net/metrics/DhcpClientEvent.java index 169f571d8317..4a9ff0511246 100644 --- a/core/java/android/net/metrics/DhcpClientEvent.java +++ b/core/java/android/net/metrics/DhcpClientEvent.java @@ -21,11 +21,18 @@ import android.os.Parcel; import android.os.Parcelable; /** - * An event recorded when a DhcpClient state machine transitions to a new state. + * An event recorded when a DhcpClient state machine transitions to a new state. * {@hide} */ @SystemApi public final class DhcpClientEvent implements Parcelable { + + // Names for recording DhcpClient pseudo-state transitions. + /** {@hide} Represents transitions from DhcpInitState to DhcpBoundState */ + public static final String INITIAL_BOUND = "InitialBoundState"; + /** {@hide} Represents transitions from and to DhcpBoundState via DhcpRenewingState */ + public static final String RENEWING_BOUND = "RenewingBoundState"; + public final String ifName; public final String msg; public final int durationMs; diff --git a/core/java/android/net/metrics/IpReachabilityEvent.java b/core/java/android/net/metrics/IpReachabilityEvent.java index 7d0229191563..ee09e2292661 100644 --- a/core/java/android/net/metrics/IpReachabilityEvent.java +++ b/core/java/android/net/metrics/IpReachabilityEvent.java @@ -24,21 +24,31 @@ import android.util.SparseArray; import com.android.internal.util.MessageUtils; /** + * An event recorded when IpReachabilityMonitor sends a neighbor probe or receives + * a neighbor probe result. * {@hide} */ @SystemApi public final class IpReachabilityEvent implements Parcelable { - public static final int PROBE = 1 << 8; - public static final int NUD_FAILED = 2 << 8; - public static final int PROVISIONING_LOST = 3 << 8; + // Event types. + /** A probe forced by IpReachabilityMonitor. */ + public static final int PROBE = 1 << 8; + /** Neighbor unreachable after a forced probe. */ + public static final int NUD_FAILED = 2 << 8; + /** Neighbor unreachable after a forced probe, IP provisioning is also lost. */ + public static final int PROVISIONING_LOST = 3 << 8; + /** {@hide} Neighbor unreachable notification from kernel. */ + public static final int NUD_FAILED_ORGANIC = 4 << 8; + /** {@hide} Neighbor unreachable notification from kernel, IP provisioning is also lost. */ + public static final int PROVISIONING_LOST_ORGANIC = 5 << 8; public final String ifName; // eventType byte format (MSB to LSB): // byte 0: unused // byte 1: unused // byte 2: type of event: PROBE, NUD_FAILED, PROVISIONING_LOST - // byte 3: kernel errno from RTNetlink or IpReachabilityMonitor + // byte 3: when byte 2 == PROBE, errno code from RTNetlink or IpReachabilityMonitor. public final int eventType; /** {@hide} */ @@ -52,11 +62,13 @@ public final class IpReachabilityEvent implements Parcelable { this.eventType = in.readInt(); } + @Override public void writeToParcel(Parcel out, int flags) { out.writeString(ifName); out.writeInt(eventType); } + @Override public int describeContents() { return 0; } @@ -81,10 +93,24 @@ public final class IpReachabilityEvent implements Parcelable { public static void logProvisioningLost(String ifName) { } + /** + * Returns the NUD failure event type code corresponding to the given conditions. + * {@hide} + */ + public static int nudFailureEventType(boolean isFromProbe, boolean isProvisioningLost) { + if (isFromProbe) { + return isProvisioningLost ? PROVISIONING_LOST : NUD_FAILED; + } else { + return isProvisioningLost ? PROVISIONING_LOST_ORGANIC : NUD_FAILED_ORGANIC; + } + } + @Override public String toString() { - return String.format("IpReachabilityEvent(%s, %s)", ifName, - Decoder.constants.get(eventType)); + int hi = eventType & 0xff00; + int lo = eventType & 0x00ff; + String eventName = Decoder.constants.get(hi); + return String.format("IpReachabilityEvent(%s, %s:%02x)", ifName, eventName, lo); } final static class Decoder { diff --git a/core/java/android/os/Environment.java b/core/java/android/os/Environment.java index 80927f368e0c..fa328090e90a 100644 --- a/core/java/android/os/Environment.java +++ b/core/java/android/os/Environment.java @@ -62,17 +62,34 @@ public class Environment { private static final File DIR_ODM_ROOT = getDirectory(ENV_ODM_ROOT, "/odm"); private static final File DIR_VENDOR_ROOT = getDirectory(ENV_VENDOR_ROOT, "/vendor"); - private static UserEnvironment sCurrentUser; - private static boolean sUserRequired; + // NoPreloadHolder to separate shared data from user-specific data, and to be able to initialize + // Environment without side effect (allowing a lazy init of the data where possible). + private static class NoPreloadHolder { + public final static UserEnvironment sCurrentUser; + public static boolean sUserRequired; + + static { + sCurrentUser = new UserEnvironment(UserHandle.myUserId()); + } + + // Empty function to be able to trigger static initialization. + public static void init() { + } - static { - initForCurrentUser(); + // Disallow allocation. + private NoPreloadHolder() { + } } /** {@hide} */ - public static void initForCurrentUser() { - final int userId = UserHandle.myUserId(); - sCurrentUser = new UserEnvironment(userId); + public static void init() { + NoPreloadHolder.init(); + + // Check for expected outcome. We only allow one initialization, this will trigger if + // somebody tried to re-initialize. + if (NoPreloadHolder.sCurrentUser.mUserId != UserHandle.myUserId()) { + throw new IllegalStateException(); + } } /** {@hide} */ @@ -428,7 +445,7 @@ public class Environment { */ public static File getExternalStorageDirectory() { throwIfUserRequired(); - return sCurrentUser.getExternalDirs()[0]; + return NoPreloadHolder.sCurrentUser.getExternalDirs()[0]; } /** {@hide} */ @@ -612,7 +629,7 @@ public class Environment { */ public static File getExternalStoragePublicDirectory(String type) { throwIfUserRequired(); - return sCurrentUser.buildExternalStoragePublicDirs(type)[0]; + return NoPreloadHolder.sCurrentUser.buildExternalStoragePublicDirs(type)[0]; } /** @@ -621,7 +638,7 @@ public class Environment { */ public static File[] buildExternalStorageAndroidDataDirs() { throwIfUserRequired(); - return sCurrentUser.buildExternalStorageAndroidDataDirs(); + return NoPreloadHolder.sCurrentUser.buildExternalStorageAndroidDataDirs(); } /** @@ -630,7 +647,7 @@ public class Environment { */ public static File[] buildExternalStorageAppDataDirs(String packageName) { throwIfUserRequired(); - return sCurrentUser.buildExternalStorageAppDataDirs(packageName); + return NoPreloadHolder.sCurrentUser.buildExternalStorageAppDataDirs(packageName); } /** @@ -639,7 +656,7 @@ public class Environment { */ public static File[] buildExternalStorageAppMediaDirs(String packageName) { throwIfUserRequired(); - return sCurrentUser.buildExternalStorageAppMediaDirs(packageName); + return NoPreloadHolder.sCurrentUser.buildExternalStorageAppMediaDirs(packageName); } /** @@ -648,7 +665,7 @@ public class Environment { */ public static File[] buildExternalStorageAppObbDirs(String packageName) { throwIfUserRequired(); - return sCurrentUser.buildExternalStorageAppObbDirs(packageName); + return NoPreloadHolder.sCurrentUser.buildExternalStorageAppObbDirs(packageName); } /** @@ -657,7 +674,7 @@ public class Environment { */ public static File[] buildExternalStorageAppFilesDirs(String packageName) { throwIfUserRequired(); - return sCurrentUser.buildExternalStorageAppFilesDirs(packageName); + return NoPreloadHolder.sCurrentUser.buildExternalStorageAppFilesDirs(packageName); } /** @@ -666,7 +683,7 @@ public class Environment { */ public static File[] buildExternalStorageAppCacheDirs(String packageName) { throwIfUserRequired(); - return sCurrentUser.buildExternalStorageAppCacheDirs(packageName); + return NoPreloadHolder.sCurrentUser.buildExternalStorageAppCacheDirs(packageName); } /** @@ -770,7 +787,7 @@ public class Environment { * {@link #MEDIA_BAD_REMOVAL}, or {@link #MEDIA_UNMOUNTABLE}. */ public static String getExternalStorageState() { - final File externalDir = sCurrentUser.getExternalDirs()[0]; + final File externalDir = NoPreloadHolder.sCurrentUser.getExternalDirs()[0]; return getExternalStorageState(externalDir); } @@ -811,7 +828,7 @@ public class Environment { */ public static boolean isExternalStorageRemovable() { if (isStorageDisabled()) return false; - final File externalDir = sCurrentUser.getExternalDirs()[0]; + final File externalDir = NoPreloadHolder.sCurrentUser.getExternalDirs()[0]; return isExternalStorageRemovable(externalDir); } @@ -850,7 +867,7 @@ public class Environment { */ public static boolean isExternalStorageEmulated() { if (isStorageDisabled()) return false; - final File externalDir = sCurrentUser.getExternalDirs()[0]; + final File externalDir = NoPreloadHolder.sCurrentUser.getExternalDirs()[0]; return isExternalStorageEmulated(externalDir); } @@ -885,11 +902,11 @@ public class Environment { /** {@hide} */ public static void setUserRequired(boolean userRequired) { - sUserRequired = userRequired; + NoPreloadHolder.sUserRequired = userRequired; } private static void throwIfUserRequired() { - if (sUserRequired) { + if (NoPreloadHolder.sUserRequired) { Log.wtf(TAG, "Path requests must specify a user by using UserEnvironment", new Throwable()); } diff --git a/core/java/android/os/UserHandle.java b/core/java/android/os/UserHandle.java index b3f44536214b..10e3718601ea 100644 --- a/core/java/android/os/UserHandle.java +++ b/core/java/android/os/UserHandle.java @@ -297,7 +297,11 @@ public final class UserHandle implements Parcelable { */ @SystemApi public static @UserIdInt int myUserId() { - return getUserId(Process.myUid()); + int myUid = Process.myUid(); + if (myUid == 0) { + throw new IllegalStateException("myUserId unsupported in zygote."); + } + return getUserId(myUid); } /** diff --git a/core/java/android/preference/PreferenceFragment.java b/core/java/android/preference/PreferenceFragment.java index 3e496b6eb550..d4a3582cfbf6 100644 --- a/core/java/android/preference/PreferenceFragment.java +++ b/core/java/android/preference/PreferenceFragment.java @@ -189,12 +189,10 @@ public abstract class PreferenceFragment extends Fragment implements 0); ListView lv = (ListView) view.findViewById(android.R.id.list); - if (lv != null) { - Drawable divider = - a.getDrawable(com.android.internal.R.styleable.PreferenceFragment_divider); - if (divider != null) { - lv.setDivider(divider); - } + if (lv != null + && a.hasValueOrEmpty(com.android.internal.R.styleable.PreferenceFragment_divider)) { + lv.setDivider( + a.getDrawable(com.android.internal.R.styleable.PreferenceFragment_divider)); } a.recycle(); diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index dae243b10762..bb8011080b44 100755 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -1548,7 +1548,7 @@ public final class Settings { private IContentProvider lazyGetProvider(ContentResolver cr) { IContentProvider cp = null; - synchronized (this) { + synchronized (NameValueCache.this) { cp = mContentProvider; if (cp == null) { cp = mContentProvider = cr.acquireProvider(mUri.getAuthority()); @@ -1575,7 +1575,7 @@ public final class Settings { public String getStringForUser(ContentResolver cr, String name, final int userHandle) { final boolean isSelf = (userHandle == UserHandle.myUserId()); if (isSelf) { - synchronized (this) { + synchronized (NameValueCache.this) { if (mGenerationTracker != null) { if (mGenerationTracker.isGenerationChanged()) { if (DEBUG) { @@ -1608,7 +1608,7 @@ public final class Settings { args.putInt(CALL_METHOD_USER_KEY, userHandle); } boolean needsGenerationTracker = false; - synchronized (this) { + synchronized (NameValueCache.this) { if (isSelf && mGenerationTracker == null) { needsGenerationTracker = true; if (args == null) { @@ -1627,7 +1627,7 @@ public final class Settings { String value = b.getString(Settings.NameValueTable.VALUE); // Don't update our cache for reads of other users' data if (isSelf) { - synchronized (this) { + synchronized (NameValueCache.this) { if (needsGenerationTracker) { MemoryIntArray array = b.getParcelable( CALL_METHOD_TRACK_GENERATION_KEY); @@ -1644,7 +1644,7 @@ public final class Settings { } mGenerationTracker = new GenerationTracker(array, index, generation, () -> { - synchronized (this) { + synchronized (NameValueCache.this) { Log.e(TAG, "Error accessing generation" + " tracker - removing"); if (mGenerationTracker != null) { @@ -1685,7 +1685,7 @@ public final class Settings { } String value = c.moveToNext() ? c.getString(0) : null; - synchronized (this) { + synchronized (NameValueCache.this) { mValues.put(name, value); } if (LOCAL_LOGV) { diff --git a/core/java/com/android/internal/app/AssistUtils.java b/core/java/com/android/internal/app/AssistUtils.java index d552e542e527..56c5cc9b69ec 100644 --- a/core/java/com/android/internal/app/AssistUtils.java +++ b/core/java/com/android/internal/app/AssistUtils.java @@ -132,6 +132,16 @@ public class AssistUtils { } } + public void registerVoiceInteractionSessionListener(IVoiceInteractionSessionListener listener) { + try { + if (mVoiceInteractionManagerService != null) { + mVoiceInteractionManagerService.registerVoiceInteractionSessionListener(listener); + } + } catch (RemoteException e) { + Log.w(TAG, "Failed to register voice interaction listener", e); + } + } + public ComponentName getAssistComponentForUser(int userId) { final String setting = Settings.Secure.getStringForUser(mContext.getContentResolver(), Settings.Secure.ASSISTANT, userId); diff --git a/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl b/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl index 1a963f30ca07..033dd13046ea 100644 --- a/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl +++ b/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl @@ -22,6 +22,7 @@ import android.os.Bundle; import com.android.internal.app.IVoiceInteractionSessionShowCallback; import com.android.internal.app.IVoiceInteractor; +import com.android.internal.app.IVoiceInteractionSessionListener; import android.hardware.soundtrigger.IRecognitionStatusCallback; import android.hardware.soundtrigger.SoundTrigger; import android.service.voice.IVoiceInteractionService; @@ -136,4 +137,9 @@ interface IVoiceInteractionManagerService { * Called when the lockscreen got shown. */ void onLockscreenShown(); + + /** + * Register a voice interaction listener. + */ + void registerVoiceInteractionSessionListener(IVoiceInteractionSessionListener listener); } diff --git a/core/java/com/android/internal/app/IVoiceInteractionSessionListener.aidl b/core/java/com/android/internal/app/IVoiceInteractionSessionListener.aidl new file mode 100644 index 000000000000..87749d26e4a0 --- /dev/null +++ b/core/java/com/android/internal/app/IVoiceInteractionSessionListener.aidl @@ -0,0 +1,29 @@ +/* + * 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 com.android.internal.app; + + oneway interface IVoiceInteractionSessionListener { + /** + * Called when a voice session is shown. + */ + void onVoiceSessionShown(); + + /** + * Called when a voice session is hidden. + */ + void onVoiceSessionHidden(); + }
\ No newline at end of file diff --git a/core/java/com/android/internal/util/MimeIconUtils.java b/core/java/com/android/internal/util/MimeIconUtils.java new file mode 100644 index 000000000000..841ec7ca994c --- /dev/null +++ b/core/java/com/android/internal/util/MimeIconUtils.java @@ -0,0 +1,230 @@ +/* + * 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 com.android.internal.util; + +import android.content.Context; +import android.graphics.drawable.Drawable; +import android.provider.DocumentsContract; + +import com.android.internal.R; + +import java.util.HashMap; + +public class MimeIconUtils { + + private static HashMap<String, Integer> sMimeIcons = new HashMap<>(); + + private static void add(String mimeType, int resId) { + if (sMimeIcons.put(mimeType, resId) != null) { + throw new RuntimeException(mimeType + " already registered!"); + } + } + + static { + int icon; + + // Package + icon = R.drawable.ic_doc_apk; + add("application/vnd.android.package-archive", icon); + + // Audio + icon = R.drawable.ic_doc_audio; + add("application/ogg", icon); + add("application/x-flac", icon); + + // Certificate + icon = R.drawable.ic_doc_certificate; + add("application/pgp-keys", icon); + add("application/pgp-signature", icon); + add("application/x-pkcs12", icon); + add("application/x-pkcs7-certreqresp", icon); + add("application/x-pkcs7-crl", icon); + add("application/x-x509-ca-cert", icon); + add("application/x-x509-user-cert", icon); + add("application/x-pkcs7-certificates", icon); + add("application/x-pkcs7-mime", icon); + add("application/x-pkcs7-signature", icon); + + // Source code + icon = R.drawable.ic_doc_codes; + add("application/rdf+xml", icon); + add("application/rss+xml", icon); + add("application/x-object", icon); + add("application/xhtml+xml", icon); + add("text/css", icon); + add("text/html", icon); + add("text/xml", icon); + add("text/x-c++hdr", icon); + add("text/x-c++src", icon); + add("text/x-chdr", icon); + add("text/x-csrc", icon); + add("text/x-dsrc", icon); + add("text/x-csh", icon); + add("text/x-haskell", icon); + add("text/x-java", icon); + add("text/x-literate-haskell", icon); + add("text/x-pascal", icon); + add("text/x-tcl", icon); + add("text/x-tex", icon); + add("application/x-latex", icon); + add("application/x-texinfo", icon); + add("application/atom+xml", icon); + add("application/ecmascript", icon); + add("application/json", icon); + add("application/javascript", icon); + add("application/xml", icon); + add("text/javascript", icon); + add("application/x-javascript", icon); + + // Compressed + icon = R.drawable.ic_doc_compressed; + add("application/mac-binhex40", icon); + add("application/rar", icon); + add("application/zip", icon); + add("application/x-apple-diskimage", icon); + add("application/x-debian-package", icon); + add("application/x-gtar", icon); + add("application/x-iso9660-image", icon); + add("application/x-lha", icon); + add("application/x-lzh", icon); + add("application/x-lzx", icon); + add("application/x-stuffit", icon); + add("application/x-tar", icon); + add("application/x-webarchive", icon); + add("application/x-webarchive-xml", icon); + add("application/gzip", icon); + add("application/x-7z-compressed", icon); + add("application/x-deb", icon); + add("application/x-rar-compressed", icon); + + // Contact + icon = R.drawable.ic_doc_contact; + add("text/x-vcard", icon); + add("text/vcard", icon); + + // Event + icon = R.drawable.ic_doc_event; + add("text/calendar", icon); + add("text/x-vcalendar", icon); + + // Font + icon = R.drawable.ic_doc_font; + add("application/x-font", icon); + add("application/font-woff", icon); + add("application/x-font-woff", icon); + add("application/x-font-ttf", icon); + + // Image + icon = R.drawable.ic_doc_image; + add("application/vnd.oasis.opendocument.graphics", icon); + add("application/vnd.oasis.opendocument.graphics-template", icon); + add("application/vnd.oasis.opendocument.image", icon); + add("application/vnd.stardivision.draw", icon); + add("application/vnd.sun.xml.draw", icon); + add("application/vnd.sun.xml.draw.template", icon); + + // PDF + icon = R.drawable.ic_doc_pdf; + add("application/pdf", icon); + + // Presentation + icon = R.drawable.ic_doc_presentation; + add("application/vnd.stardivision.impress", icon); + add("application/vnd.sun.xml.impress", icon); + add("application/vnd.sun.xml.impress.template", icon); + add("application/x-kpresenter", icon); + add("application/vnd.oasis.opendocument.presentation", icon); + + // Spreadsheet + icon = R.drawable.ic_doc_spreadsheet; + add("application/vnd.oasis.opendocument.spreadsheet", icon); + add("application/vnd.oasis.opendocument.spreadsheet-template", icon); + add("application/vnd.stardivision.calc", icon); + add("application/vnd.sun.xml.calc", icon); + add("application/vnd.sun.xml.calc.template", icon); + add("application/x-kspread", icon); + + // Document + icon = R.drawable.ic_doc_document; + add("application/vnd.oasis.opendocument.text", icon); + add("application/vnd.oasis.opendocument.text-master", icon); + add("application/vnd.oasis.opendocument.text-template", icon); + add("application/vnd.oasis.opendocument.text-web", icon); + add("application/vnd.stardivision.writer", icon); + add("application/vnd.stardivision.writer-global", icon); + add("application/vnd.sun.xml.writer", icon); + add("application/vnd.sun.xml.writer.global", icon); + add("application/vnd.sun.xml.writer.template", icon); + add("application/x-abiword", icon); + add("application/x-kword", icon); + + // Video + icon = R.drawable.ic_doc_video; + add("application/x-quicktimeplayer", icon); + add("application/x-shockwave-flash", icon); + + // Word + icon = R.drawable.ic_doc_word; + add("application/msword", icon); + add("application/vnd.openxmlformats-officedocument.wordprocessingml.document", icon); + add("application/vnd.openxmlformats-officedocument.wordprocessingml.template", icon); + + // Excel + icon = R.drawable.ic_doc_excel; + add("application/vnd.ms-excel", icon); + add("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", icon); + add("application/vnd.openxmlformats-officedocument.spreadsheetml.template", icon); + + // Powerpoint + icon = R.drawable.ic_doc_powerpoint; + add("application/vnd.ms-powerpoint", icon); + add("application/vnd.openxmlformats-officedocument.presentationml.presentation", icon); + add("application/vnd.openxmlformats-officedocument.presentationml.template", icon); + add("application/vnd.openxmlformats-officedocument.presentationml.slideshow", icon); + } + + public static Drawable loadMimeIcon(Context context, String mimeType) { + if (DocumentsContract.Document.MIME_TYPE_DIR.equals(mimeType)) { + return context.getDrawable(R.drawable.ic_doc_folder); + } + + // Look for exact match first + Integer resId = sMimeIcons.get(mimeType); + if (resId != null) { + return context.getDrawable(resId); + } + + if (mimeType == null) { + // TODO: generic icon? + return null; + } + + // Otherwise look for partial match + final String typeOnly = mimeType.split("/")[0]; + if ("audio".equals(typeOnly)) { + return context.getDrawable(R.drawable.ic_doc_audio); + } else if ("image".equals(typeOnly)) { + return context.getDrawable(R.drawable.ic_doc_image); + } else if ("text".equals(typeOnly)) { + return context.getDrawable(R.drawable.ic_doc_text); + } else if ("video".equals(typeOnly)) { + return context.getDrawable(R.drawable.ic_doc_video); + } else { + return context.getDrawable(R.drawable.ic_doc_generic); + } + } +} diff --git a/core/jni/android_hardware_location_ContextHubService.cpp b/core/jni/android_hardware_location_ContextHubService.cpp index 3881d6d9e99f..a56eba6c133f 100644 --- a/core/jni/android_hardware_location_ContextHubService.cpp +++ b/core/jni/android_hardware_location_ContextHubService.cpp @@ -186,9 +186,13 @@ static int set_dest_app(hub_message_t *msg, int id) { static void send_query_for_apps() { hub_message_t msg; + query_apps_request_t queryMsg; + + queryMsg.app_name.id = NANOAPP_VENDOR_ALL_APPS; msg.message_type = CONTEXT_HUB_QUERY_APPS; - msg.message_len = 0; + msg.message_len = sizeof(queryMsg); + msg.message = &queryMsg; for (int i = 0; i < db.hubInfo.numHubs; i++ ) { ALOGD("Sending query for apps to hub %d", i); diff --git a/packages/DocumentsUI/res/drawable/ic_doc_apk.xml b/core/res/res/drawable/ic_doc_apk.xml index 197445ef598d..197445ef598d 100644 --- a/packages/DocumentsUI/res/drawable/ic_doc_apk.xml +++ b/core/res/res/drawable/ic_doc_apk.xml diff --git a/packages/DocumentsUI/res/drawable/ic_doc_audio.xml b/core/res/res/drawable/ic_doc_audio.xml index 454eea3bbad9..454eea3bbad9 100644 --- a/packages/DocumentsUI/res/drawable/ic_doc_audio.xml +++ b/core/res/res/drawable/ic_doc_audio.xml diff --git a/packages/DocumentsUI/res/drawable/ic_doc_certificate.xml b/core/res/res/drawable/ic_doc_certificate.xml index b99baf6f4cd3..b99baf6f4cd3 100644 --- a/packages/DocumentsUI/res/drawable/ic_doc_certificate.xml +++ b/core/res/res/drawable/ic_doc_certificate.xml diff --git a/packages/DocumentsUI/res/drawable/ic_doc_codes.xml b/core/res/res/drawable/ic_doc_codes.xml index ea1c464890e7..ea1c464890e7 100644 --- a/packages/DocumentsUI/res/drawable/ic_doc_codes.xml +++ b/core/res/res/drawable/ic_doc_codes.xml diff --git a/packages/DocumentsUI/res/drawable/ic_doc_compressed.xml b/core/res/res/drawable/ic_doc_compressed.xml index e0eb669af33a..e0eb669af33a 100644 --- a/packages/DocumentsUI/res/drawable/ic_doc_compressed.xml +++ b/core/res/res/drawable/ic_doc_compressed.xml diff --git a/packages/DocumentsUI/res/drawable/ic_doc_contact.xml b/core/res/res/drawable/ic_doc_contact.xml index a77cb6ba1152..a77cb6ba1152 100644 --- a/packages/DocumentsUI/res/drawable/ic_doc_contact.xml +++ b/core/res/res/drawable/ic_doc_contact.xml diff --git a/packages/DocumentsUI/res/drawable/ic_doc_document.xml b/core/res/res/drawable/ic_doc_document.xml index 29251ad799f0..29251ad799f0 100644 --- a/packages/DocumentsUI/res/drawable/ic_doc_document.xml +++ b/core/res/res/drawable/ic_doc_document.xml diff --git a/packages/DocumentsUI/res/drawable/ic_doc_event.xml b/core/res/res/drawable/ic_doc_event.xml index 113f079c8670..113f079c8670 100644 --- a/packages/DocumentsUI/res/drawable/ic_doc_event.xml +++ b/core/res/res/drawable/ic_doc_event.xml diff --git a/packages/DocumentsUI/res/drawable/ic_doc_excel.xml b/core/res/res/drawable/ic_doc_excel.xml index 3ed27e1f7f62..3ed27e1f7f62 100644 --- a/packages/DocumentsUI/res/drawable/ic_doc_excel.xml +++ b/core/res/res/drawable/ic_doc_excel.xml diff --git a/core/res/res/drawable/ic_doc_folder.xml b/core/res/res/drawable/ic_doc_folder.xml new file mode 100644 index 000000000000..dcbce010810e --- /dev/null +++ b/core/res/res/drawable/ic_doc_folder.xml @@ -0,0 +1,24 @@ +<!-- +Copyright (C) 2015 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. +--> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="24.0" + android:viewportHeight="24.0"> + <path + android:fillColor="#FF737373" + android:pathData="M10 4H4c-1.1 0,-1.99.9,-1.99 2L2 18c0 1.1.9 2 2 2h16c1.1 0 2,-.9 2,-2V8c0,-1.1,-.9,-2,-2,-2h-8l-2,-2z"/> +</vector> diff --git a/packages/DocumentsUI/res/drawable/ic_doc_font.xml b/core/res/res/drawable/ic_doc_font.xml index 4c13d71e47d0..4c13d71e47d0 100644 --- a/packages/DocumentsUI/res/drawable/ic_doc_font.xml +++ b/core/res/res/drawable/ic_doc_font.xml diff --git a/packages/DocumentsUI/res/drawable/ic_doc_generic.xml b/core/res/res/drawable/ic_doc_generic.xml index 006dfba4a3bb..006dfba4a3bb 100644 --- a/packages/DocumentsUI/res/drawable/ic_doc_generic.xml +++ b/core/res/res/drawable/ic_doc_generic.xml diff --git a/packages/DocumentsUI/res/drawable/ic_doc_image.xml b/core/res/res/drawable/ic_doc_image.xml index 23953f71fd5e..23953f71fd5e 100644 --- a/packages/DocumentsUI/res/drawable/ic_doc_image.xml +++ b/core/res/res/drawable/ic_doc_image.xml diff --git a/packages/DocumentsUI/res/drawable/ic_doc_pdf.xml b/core/res/res/drawable/ic_doc_pdf.xml index b2d01938c480..b2d01938c480 100644 --- a/packages/DocumentsUI/res/drawable/ic_doc_pdf.xml +++ b/core/res/res/drawable/ic_doc_pdf.xml diff --git a/packages/DocumentsUI/res/drawable/ic_doc_powerpoint.xml b/core/res/res/drawable/ic_doc_powerpoint.xml index aa5bfc818692..aa5bfc818692 100644 --- a/packages/DocumentsUI/res/drawable/ic_doc_powerpoint.xml +++ b/core/res/res/drawable/ic_doc_powerpoint.xml diff --git a/packages/DocumentsUI/res/drawable/ic_doc_presentation.xml b/core/res/res/drawable/ic_doc_presentation.xml index 7937bc12aa3b..7937bc12aa3b 100644 --- a/packages/DocumentsUI/res/drawable/ic_doc_presentation.xml +++ b/core/res/res/drawable/ic_doc_presentation.xml diff --git a/packages/DocumentsUI/res/drawable/ic_doc_spreadsheet.xml b/core/res/res/drawable/ic_doc_spreadsheet.xml index 1663c0a1ae1b..1663c0a1ae1b 100644 --- a/packages/DocumentsUI/res/drawable/ic_doc_spreadsheet.xml +++ b/core/res/res/drawable/ic_doc_spreadsheet.xml diff --git a/core/res/res/drawable/ic_doc_text.xml b/core/res/res/drawable/ic_doc_text.xml new file mode 100644 index 000000000000..7fc04e811b1c --- /dev/null +++ b/core/res/res/drawable/ic_doc_text.xml @@ -0,0 +1,24 @@ +<!-- +Copyright (C) 2015 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. +--> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="24.0" + android:viewportHeight="24.0"> + <path + android:fillColor="#FF737373" + android:pathData="M14 2H6c-1.1 0,-1.99.9,-1.99 2L4 20c0 1.1.89 2 1.99 2H18c1.1 0 2,-.9 2,-2V8l-6,-6zm2 16H8v-2h8v2zm0,-4H8v-2h8v2zm-3,-5V3.5L18.5 9H13z"/> +</vector> diff --git a/packages/DocumentsUI/res/drawable/ic_doc_video.xml b/core/res/res/drawable/ic_doc_video.xml index ad4dae89ccdc..ad4dae89ccdc 100644 --- a/packages/DocumentsUI/res/drawable/ic_doc_video.xml +++ b/core/res/res/drawable/ic_doc_video.xml diff --git a/packages/DocumentsUI/res/drawable/ic_doc_word.xml b/core/res/res/drawable/ic_doc_word.xml index 7a3a0ec9b5e0..7a3a0ec9b5e0 100644 --- a/packages/DocumentsUI/res/drawable/ic_doc_word.xml +++ b/core/res/res/drawable/ic_doc_word.xml diff --git a/core/res/res/layout/date_picker_header_material.xml b/core/res/res/layout/date_picker_header_material.xml index a0e2b1de60d4..755317ee28ce 100644 --- a/core/res/res/layout/date_picker_header_material.xml +++ b/core/res/res/layout/date_picker_header_material.xml @@ -54,7 +54,7 @@ android:textAppearance="@style/TextAppearance.Material.DatePicker.DateLabel" android:includeFontPadding="false" android:gravity="start" - android:maxLines="2" + android:maxLines="@integer/date_picker_header_max_lines_material" android:ellipsize="none" /> </LinearLayout> </LinearLayout> diff --git a/core/res/res/values-land/integers.xml b/core/res/res/values-land/integers.xml index 020fd23cec81..a5789895c970 100644 --- a/core/res/res/values-land/integers.xml +++ b/core/res/res/values-land/integers.xml @@ -23,4 +23,6 @@ <integer name="kg_widget_region_weight">45</integer> <integer name="kg_security_flipper_weight">55</integer> <integer name="kg_glowpad_rotation_offset">-90</integer> + + <integer name="date_picker_header_max_lines_material">4</integer> </resources> diff --git a/core/res/res/values-watch/styles_material.xml b/core/res/res/values-watch/styles_material.xml index c19cc72a8bff..daeeca2b050d 100644 --- a/core/res/res/values-watch/styles_material.xml +++ b/core/res/res/values-watch/styles_material.xml @@ -53,6 +53,10 @@ please see styles_device_defaults.xml. <item name="wallpaperIntraCloseExitAnimation">@anim/slide_in_exit_micro</item> </style> + <style name="PreferenceFragment.Material" parent="BasePreferenceFragment"> + <item name="divider">@empty</item> + </style> + <style name="Widget.Material.TextView" parent="Widget.TextView"> <item name="breakStrategy">balanced</item> </style> diff --git a/core/res/res/values/integers.xml b/core/res/res/values/integers.xml index 8f8d59e95782..71ac2f49ff71 100644 --- a/core/res/res/values/integers.xml +++ b/core/res/res/values/integers.xml @@ -26,4 +26,5 @@ <integer name="date_picker_mode">1</integer> <integer name="time_picker_mode">1</integer> + <integer name="date_picker_header_max_lines_material">2</integer> </resources> diff --git a/core/res/res/values/styles_material.xml b/core/res/res/values/styles_material.xml index 6e0ad3681e81..fad3488d2915 100644 --- a/core/res/res/values/styles_material.xml +++ b/core/res/res/values/styles_material.xml @@ -36,7 +36,7 @@ please see styles_device_defaults.xml. <item name="layout">@layout/preference_material</item> </style> - <style name="PreferenceFragment.Material"> + <style name="BasePreferenceFragment"> <item name="layout">@layout/preference_list_fragment_material</item> <item name="paddingStart">@dimen/preference_fragment_padding_side_material</item> <item name="paddingEnd">@dimen/preference_fragment_padding_side_material</item> @@ -46,6 +46,8 @@ please see styles_device_defaults.xml. <item name="clipToPadding">@bool/config_preferenceFragmentClipToPadding</item> </style> + <style name="PreferenceFragment.Material" parent="BasePreferenceFragment"/> + <style name="PreferenceActivity.Material"> <item name="layout">@layout/preference_list_content_material</item> <item name="headerLayout">@layout/preference_header_item_material</item> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index d426d1aef072..a781f9a13b59 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -2638,4 +2638,30 @@ <!-- Package name for the device provisioning package --> <java-symbol type="string" name="config_deviceProvisioningPackage" /> + + <!-- Used for MimeIconUtils. --> + <java-symbol type="drawable" name="ic_doc_apk" /> + <java-symbol type="drawable" name="ic_doc_audio" /> + <java-symbol type="drawable" name="ic_doc_certificate" /> + <java-symbol type="drawable" name="ic_doc_codes" /> + <java-symbol type="drawable" name="ic_doc_compressed" /> + <java-symbol type="drawable" name="ic_doc_contact" /> + <java-symbol type="drawable" name="ic_doc_event" /> + <java-symbol type="drawable" name="ic_doc_font" /> + <java-symbol type="drawable" name="ic_doc_image" /> + <java-symbol type="drawable" name="ic_doc_pdf" /> + <java-symbol type="drawable" name="ic_doc_presentation" /> + <java-symbol type="drawable" name="ic_doc_spreadsheet" /> + <java-symbol type="drawable" name="ic_doc_document" /> + <java-symbol type="drawable" name="ic_doc_video" /> + <java-symbol type="drawable" name="ic_doc_word" /> + <java-symbol type="drawable" name="ic_doc_excel" /> + <java-symbol type="drawable" name="ic_doc_powerpoint" /> + <java-symbol type="drawable" name="ic_doc_folder" /> + <java-symbol type="drawable" name="ic_doc_audio" /> + <java-symbol type="drawable" name="ic_doc_image" /> + <java-symbol type="drawable" name="ic_doc_text" /> + <java-symbol type="drawable" name="ic_doc_video" /> + <java-symbol type="drawable" name="ic_doc_generic" /> + </resources> diff --git a/docs/html/_redirects.yaml b/docs/html/_redirects.yaml index cf56f2bb1268..11e06f110bec 100644 --- a/docs/html/_redirects.yaml +++ b/docs/html/_redirects.yaml @@ -149,9 +149,11 @@ redirects: to: /google/play/licensing/index.html - from: /google/play/billing/billing_about.html to: /google/play/billing/index.html -- from: /guide/developing/tools/ +- from: /guide/developing/tools/proguard.html + to: /studio/build/shrink-code.html +- from: /guide/developing/tools/... to: /studio/command-line/ -- from: /guide/developing/ +- from: /guide/developing/... to: /studio/ - from: /tools/aidl.html to: /guide/components/aidl.html diff --git a/docs/html/distribute/engage/_book.yaml b/docs/html/distribute/engage/_book.yaml index 87e819a8d84d..c371268677c7 100644 --- a/docs/html/distribute/engage/_book.yaml +++ b/docs/html/distribute/engage/_book.yaml @@ -31,3 +31,6 @@ toc: - title: Get Feedback with Beta Tests path: /distribute/engage/beta.html + +- title: Interact with Nearby Users + path: /distribute/engage/nearby.html diff --git a/docs/html/distribute/engage/engage_toc.cs b/docs/html/distribute/engage/engage_toc.cs index 4f3e0af03734..cc6e28442103 100644 --- a/docs/html/distribute/engage/engage_toc.cs +++ b/docs/html/distribute/engage/engage_toc.cs @@ -66,6 +66,12 @@ <span class="en">Get Feedback with Beta Tests</span></a> </div> </li> + <li class="nav-section"> + <div class="nav-section-header empty" style="font-weight:normal"><a href="<?cs + var:toroot?>distribute/engage/nearby.html"> + <span class="en">Interact with Nearby Users</span></a> + </div> + </li> </ul> <script type="text/javascript"> diff --git a/docs/html/distribute/engage/nearby.jd b/docs/html/distribute/engage/nearby.jd new file mode 100644 index 000000000000..b1571f67e308 --- /dev/null +++ b/docs/html/distribute/engage/nearby.jd @@ -0,0 +1,93 @@ +page.title=Interact with Nearby Users +page.metaDescription=Use the Nearby feature to interact with nearby people, devices, and beacons. +page.image=images/distribute/nearby_connections.png +page.tags="users, nearby, engage" +@jd:body + +<p>Create experiences that seem magical for users who are in close proximity by using the unique +close-range and cross-platform capabilities of Nearby. Set up multiplayer games, ad-hoc groups, +sharing, or collaborative sessions so that your users can work or play together more easily when +they're close.</p> + +<p>Learn more about <a href="https://developers.google.com/nearby/">how to add nearby interactions +to your app or game</a>.</p> + +<div class="wrap"> + <div class="cols" style="margin-top:1em;"> + <div class="col-4of12"> + <h3> + Messaging + </h3> + <img src="{@docRoot}images/distribute/nearby_messaging.png"> + <p class="figure-caption"> + Find nearby devices and share messages to enable rich interactions and collaboration + among users. + </p> + </div> + + <div class="col-4of12"> + <h3> + Connections + </h3> + <img src="{@docRoot}images/distribute/nearby_connections.png"> + <p class="figure-caption"> + Discover other local devices and create connections that enable real-time, cross-device + experiences. + </p> + </div> + + <div class="col-4of12"> + <h3> + Beacons + </h3> + <img src="{@docRoot}images/distribute/nearby_beacons.png"> + <p class="figure-caption"> + Receive messages from beacons using + <a href="https://developers.google.com/beacons/eddystone">Eddystone</a> and add context to + location-based apps and games. + </p> + </div> + </div> +</div> + +<p class="note"><strong>Note:</strong> Nearby uses Bluetooth 2.0, Bluetooth 4.0, Wi-Fi, and an +ultrasonic modem to function over distances of up to 100 feet.</p> + +<h2 id="best-practices">Best practices</h2> + +<p>The following list contains some helpful tips and best practices that will help you set up +and use Nearby effectively: + +<ul> + <li>Use Nearby features sparingly and only when they're needed because they can consume battery + life quickly (up to 3.5 times faster than normal). + </li> + + <li>Invoke Nearby explicitly with a button, switch, or special screen, and provide a visual + indication when the features are actively sending or receiving content. + </li> + + <li>Ensure that users are aware of the data that is made visible by Nearby before + they start using the features. + </li> + + <li>Stop any publish or subscribe operations when the user exits the app or stops the + activity that requires Nearby. + </li> + + <li>Use the <em>earshot</em> option, which uses only the ultrasonic modem to send and receive + messages, to limit the range of Nearby messages to about five feet when privacy is important. + </li> + + <li>Accelerate the exchange of messages (when appropriate) by making one device the publisher + only, and all other devices subscribers. + </li> +</ul> + +<h2 id="related-resources">Related resources</h2> + +<div class="resource-widget resource-flow-layout col-13" + data-query="collection:distribute/users/nearby" + data-sortOrder="-timestamp" + data-cardSizes="9x3" + data-maxResults="6"></div> diff --git a/docs/html/distribute/monetize/_book.yaml b/docs/html/distribute/monetize/_book.yaml index 2ebc6956f31f..974e9edb530d 100644 --- a/docs/html/distribute/monetize/_book.yaml +++ b/docs/html/distribute/monetize/_book.yaml @@ -16,3 +16,6 @@ toc: - title: Purchasing path: /distribute/monetize/payments.html + +- title: Drive Conversions + path: /distribute/monetize/conversions.html diff --git a/docs/html/distribute/monetize/conversions.jd b/docs/html/distribute/monetize/conversions.jd new file mode 100644 index 000000000000..20b2333f33d6 --- /dev/null +++ b/docs/html/distribute/monetize/conversions.jd @@ -0,0 +1,100 @@ +page.title=Drive Conversions +page.image=images/cards/card-drive-conversions_16-9_2x.png +page.metaDescription=Discover where your users are coming from, drive engagement, and surface your in-app products to maximize your conversions. +page.tags="conversions" + +@jd:body + +<div class="figure"> + <img src="{@docRoot}images/cards/card-drive-conversions_16-9_2x.png"> +</div> + +<p> + Users who've made in-app purchases or converted in other ways are more likely + to do so again. + You can now easily discover where those users are coming from, drive engagement, + and surface your in-app products to maximize your conversions. +</p> + +<div class="headerLine"> + <h2 id="dicover"> + Discover your most valuable channels + </h2> + +</div> + +<p> + From the <strong>User Acquisition</strong> page in the Google Play Developer Console, explore + how users convert into spenders across your acquisition channels and find the best prospects + for app install campaigns. +</p> + +<p>For more information, view the guide on how to <a class="external-link" + href="https://support.google.com/googleplay/android-developer/answer/6263332"> + measure your app's user acquisition</a> in the Google Play Developer Console Help Center.</p> + +</p> + +<div class="headerLine"> + <h2 id="adwords"> + Re-engage users with AdWords ads + </h2> + +</div> + +<p> + Bring users back to your app by creating an AdWords re-engagement campaign. + Use display and search ads that appear only to users who have installed your app. + Use deep links to bring them to where they'll find the content or actions they're + searching for. +</p> + +<p>For more information, view the guide on <a class="external-link" + href="https://support.google.com/adwords/topic/3119078?hl=en">campaign settings</a> in the + AdWords Help Center.</p> + +<div class="headerLine"> + <h2 id="gift-cards"> + Drive spending with AdMob in-app purchase ads + </h2> +</div> + +<p>Use your Google Analytics data to create Remarketing Audience lists for the high-value + users most likely to purchase products within your app. Then create an AdMob campaign that + targets these users to increase their awareness of your products.</p> + +<p>For more information, view the guide on how to <a class="external-link" + href="https://www.google.com/admob/promote.html">drive more in-app purchases and installs</a> + in the AdMob platform.</p> + +<div class="headerLine"> + <h2 id="tips"> + Tips + </h2> +</div> +<ul> +<li>Add <a class="external-link" + href="https://developers.google.com/app-indexing/webmasters/app">deep links</a> + to your app so ads bring users directly to + conversion activities.</li> +<li>Track what users do in your app by installing the + AdWords <a class="external-link" + href="https://developers.google.com/app-conversion-tracking"> + Conversion Tracking SDK</a>.</li> +<li>Link your Google Analytics and AdMob accounts to share audience lists.</li> +<li>Make conversion ads compelling, such as promoting a booking search or + in-app product special offer.</li> +<li>Use the AdMob Conversion Optimizer with existing campaigns. Predictions + are more accurate when there is more data to work with.</li> +<li>Re-engage with your app's users across the Display Network with remarketing + lists in AdMob and with search keywords in AdWords.</li> +</ul> + + +<div class="headerLine"><h2 id="related-resources">Related resources</h2></div> + +<div class="resource-widget resource-flow-layout col-13" + data-query="collection:distribute/monetize/conversions" + data-sortOrder="-timestamp" + data-cardSizes="9x3" + data-maxResults="8"></div> diff --git a/docs/html/distribute/monetize/monetize_toc.cs b/docs/html/distribute/monetize/monetize_toc.cs index a3aa50fe1688..b58663328f21 100644 --- a/docs/html/distribute/monetize/monetize_toc.cs +++ b/docs/html/distribute/monetize/monetize_toc.cs @@ -34,6 +34,12 @@ </a> </div> </li> + <li class="nav-section"> + <div class="nav-section-header empty" style="font-weight:normal"><a href="<?cs var:toroot?>distribute/monetize/conversions.html"> + <span class="en">Drive Conversions</span> + </a> + </div> + </li> </ul> @@ -44,4 +50,3 @@ changeNavLang(getLangPref()); //--> </script> - diff --git a/docs/html/distribute/monetize/payments.jd b/docs/html/distribute/monetize/payments.jd index 7d972bb02216..004e47eda93b 100644 --- a/docs/html/distribute/monetize/payments.jd +++ b/docs/html/distribute/monetize/payments.jd @@ -15,36 +15,43 @@ page.tags="google play", "payments", "gift card" instantly with a streamlined, consistent purchasing process and convenient payment methods. </p> - +<p><strong>Key facts</strong></p> +<ul> +<li>Direct carrier billing in over 35 countries.</li> +<li>Google Play gift cards in over 25 countries.</li> +<li>PayPal in over 20 countries.</li> +<li>Developers can sell their apps from over 75 countries.</li> +<li>Users can buy apps in over 135 countries.</li> +</ul> <div class="headerLine"> <h2 id="dcb"> - Direct Carrier Billing + Direct carrier billing </h2> </div> <p> - Users pay by charging their monthly carrier bills . The benefit of Direct - Carrier Billing is that it opens up markets where credit cards are less - common, as purchases are charged to your customers’ monthly mobile phone + Users pay by charging their monthly carrier bills. The benefit of direct + carrier billing is that it opens up markets where credit cards are less + common, as purchases are charged to your customers' monthly mobile phone bills. This option is available to users in key markets around the world. Many more will get the option in the months ahead. </p> <div class="headerLine"> <h2 id="credit"> - Credit Cards + Credit cards </h2> </div> <p> - Users can pay using any credit card that they’ve registered in Google Play. - Credit Cards added to Google Play are stored in the user’s Google wallet and - available for in-app purchases and instant buys too. To make it easy for - users to get started, registration is offered as a part of the initial device - setup process. + Users can pay using any credit card that they've registered in Google Play. + The credit cards that a user adds to Google Play are stored in the user's Google wallet. + They are available for in-app purchases and instant buys. It's easy for + users to get started, as the initial device setup process allows users to register a credit + card that they can use in Google Play. </p> <div class="headerLine"> @@ -62,16 +69,40 @@ page.tags="google play", "payments", "gift card" <p> Gift cards enable users to add value to their Google Play balance by entering a unique code printed on a card purchased online or from major retailers. - More information gift cards can be found <a href= + More information on gift cards can be found <a href= "http://play.google.com/intl/en-US_us/about/giftcards/" target= "_android">here</a>. </p> <p style="clear:both"> </p> + +<div class="headerLine"> + <h2 id="paypal"> + PayPal + </h2> + + +</div> + +<div class="figure"> + <img src="{@docRoot}images/paypal-logo.png"> +</div> + +<p> + Users with PayPal accounts can buy apps and digital content on Google Play using any + of their available payment methods. They sign into their PayPal account and + complete the purchase. The popularity of PayPal for payments on the web gives + you more customers. +</p> + +<p style="clear:both"> +</p> + + <div class="headerLine"> <h2 id="balance"> - Google Play Balance + Google Play balance </h2> @@ -95,14 +126,13 @@ page.tags="google play", "payments", "gift card" The payment methods available to users may vary based on location, carrier network, and other factors. </p> - + <p style="clear:both"> </p> -<div class="headerLine"><h2 id="related-resources">Related Resources</h2></div> +<div class="headerLine"><h2 id="related-resources">Related resources</h2></div> <div class="resource-widget resource-flow-layout col-13" data-query="collection:distribute/monetize/paymentmethods" data-sortOrder="-timestamp" data-cardSizes="9x3" data-maxResults="8"></div> - diff --git a/docs/html/distribute/users/promote-with-ads.jd b/docs/html/distribute/users/promote-with-ads.jd index 2db4ca306107..d99f449b31a5 100644 --- a/docs/html/distribute/users/promote-with-ads.jd +++ b/docs/html/distribute/users/promote-with-ads.jd @@ -6,20 +6,27 @@ page.tags="users, ads, adwords" <p>Users have a huge amount of choice when it comes to which apps they install and use, so it’s important to actively find new ways to promote your app and drive -ongoing engagement. AdWords is a powerful and effective way to do both.</p> +ongoing engagement. AdWords campaigns, which you create in the +<a href="http://play.google.com/apps/publish">Google Play Developer Console</a>, +are a powerful and effective way to do both.</p> -<h2 id=drive_installs>Drive installs</h2> +<h2 id="drive_installs">Drive installs with universal app campaigns</h2> -<p><a href="http://adwords.google.com">AdWords</a> promotes your app to interested -users where they spend time on phones and -tablets – with app install ads on Google Search, YouTube, Gmail, and within -apps and across the web on the Google Display Network. AdWords is a powerful -way to scale app promotion across Google networks and find customers that are -most likely to install your app. </p> +<p><a href="http://adwords.google.com">AdWords</a> is a powerful way to scale +app promotion across Google networks and find customers who are most likely to +install your app. AdWords promotes your app to interested users where they spend +time on phones and tablets – with app install ads on Google Play, Google Search, +YouTube, Gmail, and within apps and across the web.</p> -<p><a href="https://support.google.com/adwords/answer/6032059">Get started with AdWords -app install ads</a>.</p> +<p>By creating a <em>universal app camapign</em>, you can reach all of these +networks. This type of campaign allocates ads, bids, and budgets automatically, +making it easier to improve install volume for your app.</p> + +<p>To learn more about creating universal ad campaigns, read the article about +<a class="external-link" href="https://support.google.com/googleplay/android-developer/answer/6262700">creating +an AdWords campaign for your app</a> in the Google Play Developer Console Help +Center.</p> <div class="wrap"> <div class="cols" style="margin-top:1em;"> @@ -27,18 +34,16 @@ app install ads</a>.</p> <h3> From Google Play </h3> - <img src="/images/distribute/promote_ads_play.png"> + <img src="{@docRoot}images/distribute/promote_ads_play.png"> <p class="figure-caption"> - Promote your app on Google Play when users are searching and browsing - for apps. + Reach users as they search for apps and games on Google Play. </p> </div> - <div class="col-4of12"> <h3> - From search + From Google Search </h3> - <img src="/images/distribute/promote_ads_search.png"> + <img src="{@docRoot}images/distribute/promote_ads_search.png"> <p class="figure-caption"> Connect with users as they search for content and services provided by your app. @@ -49,47 +54,32 @@ app install ads</a>.</p> <h3> From YouTube </h3> - <img src="/images/distribute/promote_ads_youtube.png"> + <img src="{@docRoot}images/distribute/promote_ads_youtube.png"> <p class="figure-caption"> Promote your app when users are watching related videos. </p> </div> - </div> -</div> -<div class="wrap"> - <div class="cols" style="margin-top:1em;"> <div class="col-4of12"> <h3> From apps </h3> - <img src="/images/distribute/promote_ads_apps.png"> + <img src="{@docRoot}images/distribute/promote_ads_apps.png"> <p class="figure-caption"> Reach users while they’re engaged with apps and games across the AdMob network. </p> </div> - <div class="col-4of12"> <h3> From the web </h3> - <img src="/images/distribute/promote_ads_web.png"> + <img src="{@docRoot}images/distribute/promote_ads_web.png"> <p class="figure-caption"> Reach users while they’re engaged with websites across the Google Display Network. </p> </div> - - <div class="col-4of12"> - <h3> - From Gmail - </h3> - <img src="/images/distribute/promote_ads_gmail.png"> - <p class="figure-caption"> - Promote your app while users communicate and get things done in Gmail. - </p> - </div> </div> </div> @@ -130,77 +120,7 @@ app install ads</a>.</p> </li> </ul> -<h2 id="engage_with_users"> - Engage with users -</h2> - -<p> - Getting a user to install an app is one thing, but you'll also want them to - open it regularly. AdWords offers app re-engagement tools to help your app - stay in mind with users who’ve already installed it on their phone. AdWords - can remind them of key features and encourage them to try your app again, or - help them complete an activity they didn't know your app could handle. -</p> - -<div class="wrap"> - <div class="cols" style="margin-top:1em;"> - <div class="col-4of12"> - <h3> - From search - </h3> - <img src="/images/distribute/promote_ads.png"> - <p class="figure-caption"> - Add deep links to your app, then bring users straight to relevant app - content when they’re searching. - </p> - </div> - - <div class="col-4of12"> - <h3> - From apps - </h3> - <img src="/images/distribute/promote_ads_inapp.png"> - <p class="figure-caption"> - Use remarketing and deep links to bring users to just the right place - in your app to re-engage and convert, from other apps and games they - love. - </p> - </div> - </div> -</div> - -<h3> - Tips -</h3> - -<ul> - <li>Track what users do in your app after they’ve clicked an ad, by - installing the AdWords <a href= - "https://developers.google.com/app-conversion-tracking/">conversion tracking - SDK</a>. - </li> - - <li>Advertise a compelling reason for users to re-engage with your app (such - as a reminder or a special offer). - </li> - - <li> - <a href="https://developers.google.com/app-indexing/webmasters/app">Add - deep links</a> to your app that’ll take users directly to the parts of your - app that will be most relevant and interesting to them, where they can - easily take action. - </li> - - <li>Re-engage your app users across the display network with remarketing - lists and search with keywords. - </li> - - <li>Use remarketing lists to target high value users so that you can drive - more conversions in your app. - </li> -</ul> - -<h2 id="related-resources">Related Resources</h2> +<h2 id="related-resources">Related resources</h2> <div class="resource-widget resource-flow-layout col-13" data-query="collection:distribute/users/promotewithads" diff --git a/docs/html/guide/topics/ui/accessibility/apps.jd b/docs/html/guide/topics/ui/accessibility/apps.jd index 90781f7e81ae..eb639e37027d 100644 --- a/docs/html/guide/topics/ui/accessibility/apps.jd +++ b/docs/html/guide/topics/ui/accessibility/apps.jd @@ -454,18 +454,20 @@ following example code demonstrates a basic implementation of this method.</p> <pre> @Override -public void dispatchPopulateAccessibilityEvent(AccessibilityEvent event) { - super.dispatchPopulateAccessibilityEvent(event); +public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) { // Call the super implementation to populate its text to the event, which // calls onPopulateAccessibilityEvent() on API Level 14 and up. - + boolean completed = super.dispatchPopulateAccessibilityEvent(event); + // In case this is running on a API revision earlier that 14, check // the text content of the event and add an appropriate text // description for this custom view: CharSequence text = getText(); if (!TextUtils.isEmpty(text)) { event.getText().add(text); + return true; } + return completed; } </pre> diff --git a/docs/html/images/cards/card-drive-conversions_16-9_2x.png b/docs/html/images/cards/card-drive-conversions_16-9_2x.png Binary files differnew file mode 100644 index 000000000000..34480124567b --- /dev/null +++ b/docs/html/images/cards/card-drive-conversions_16-9_2x.png diff --git a/docs/html/images/distribute/nearby_beacons.png b/docs/html/images/distribute/nearby_beacons.png Binary files differnew file mode 100644 index 000000000000..aba2f39e7b1a --- /dev/null +++ b/docs/html/images/distribute/nearby_beacons.png diff --git a/docs/html/images/distribute/nearby_connections.png b/docs/html/images/distribute/nearby_connections.png Binary files differnew file mode 100644 index 000000000000..52e6daa7028e --- /dev/null +++ b/docs/html/images/distribute/nearby_connections.png diff --git a/docs/html/images/distribute/nearby_messaging.png b/docs/html/images/distribute/nearby_messaging.png Binary files differnew file mode 100644 index 000000000000..6ae2d00505fd --- /dev/null +++ b/docs/html/images/distribute/nearby_messaging.png diff --git a/docs/html/images/distribute/promote_ads_apps.png b/docs/html/images/distribute/promote_ads_apps.png Binary files differindex 2f578651e7f4..1c25be34e80c 100644 --- a/docs/html/images/distribute/promote_ads_apps.png +++ b/docs/html/images/distribute/promote_ads_apps.png diff --git a/docs/html/images/distribute/promote_ads_gmail.png b/docs/html/images/distribute/promote_ads_gmail.png Binary files differindex 1d21b4a3cdf9..c1013fc76034 100644 --- a/docs/html/images/distribute/promote_ads_gmail.png +++ b/docs/html/images/distribute/promote_ads_gmail.png diff --git a/docs/html/images/distribute/promote_ads_play.png b/docs/html/images/distribute/promote_ads_play.png Binary files differindex 1cf51b273f9c..ae0f84bf87f9 100644 --- a/docs/html/images/distribute/promote_ads_play.png +++ b/docs/html/images/distribute/promote_ads_play.png diff --git a/docs/html/images/distribute/promote_ads_search.png b/docs/html/images/distribute/promote_ads_search.png Binary files differindex 27c0b38e2f04..adcede154e3f 100644 --- a/docs/html/images/distribute/promote_ads_search.png +++ b/docs/html/images/distribute/promote_ads_search.png diff --git a/docs/html/images/distribute/promote_ads_web.png b/docs/html/images/distribute/promote_ads_web.png Binary files differindex 588a3d4487a1..8fefed167848 100644 --- a/docs/html/images/distribute/promote_ads_web.png +++ b/docs/html/images/distribute/promote_ads_web.png diff --git a/docs/html/images/distribute/promote_ads_youtube.png b/docs/html/images/distribute/promote_ads_youtube.png Binary files differindex e88a7965f6de..ad44e512f9d6 100644 --- a/docs/html/images/distribute/promote_ads_youtube.png +++ b/docs/html/images/distribute/promote_ads_youtube.png diff --git a/docs/html/images/paypal-logo.png b/docs/html/images/paypal-logo.png Binary files differnew file mode 100644 index 000000000000..3e08b95cb52e --- /dev/null +++ b/docs/html/images/paypal-logo.png diff --git a/docs/html/jd_collections.js b/docs/html/jd_collections.js index aa0620aefd0c..03efa86ffaaa 100644 --- a/docs/html/jd_collections.js +++ b/docs/html/jd_collections.js @@ -419,7 +419,8 @@ var RESOURCE_COLLECTIONS = { "distribute/engage/easy-signin.html", "distribute/analyze/build-better-apps.html", "distribute/engage/gcm.html", - "distribute/engage/beta.html" + "distribute/engage/beta.html", + "distribute/engage/nearby.html" ] }, "distribute/monetize": { @@ -430,6 +431,7 @@ var RESOURCE_COLLECTIONS = { "distribute/monetize/ads.html", "distribute/monetize/ecommerce.html", "distribute/monetize/payments.html", + "distribute/monetize/conversions.html", "distribute/analyze/understand-user-value.html", ] }, @@ -761,6 +763,14 @@ var RESOURCE_COLLECTIONS = { "https://support.google.com/adwords/answer/6167162" ] }, + "distribute/users/nearby": { + "title": "", + "resources": [ + "https://developers.google.com/nearby/", + "https://www.youtube.com/watch?v=hultDpBS22s", + "https://developers.google.com/beacons" + ] + }, "distribute/users/buildbuzz": { "title": "", "resources": [ @@ -1556,6 +1566,15 @@ var RESOURCE_COLLECTIONS = { "https://support.google.com/googleplay/answer/2651410" ] }, + "distribute/monetize/conversions": { + "title": "", + "resources": [ + "https://support.google.com/adwords/answer/2471188", + "https://developers.google.com/app-conversion-tracking/", + "https://support.google.com/analytics/answer/2611404", + "https://support.google.com/adwords/answer/1704341" + ] + }, "autolanding": { "title": "", "resources": [ diff --git a/docs/html/jd_extras.js b/docs/html/jd_extras.js index e5347d970492..44ccafafb45f 100644 --- a/docs/html/jd_extras.js +++ b/docs/html/jd_extras.js @@ -1942,7 +1942,7 @@ DISTRIBUTE_RESOURCES = DISTRIBUTE_RESOURCES.concat([ "url": "https://support.google.com/googleplay/answer/2651410", "timestamp": null, "image": "images/play_dev.jpg", - "title": "Google Play Accepted Payment Methods", + "title": "Google Play accepted payment methods", "summary": "Support details on the payment methods supported in Google Play.", "keywords": ["gift card"], "type": "distribute", @@ -1951,6 +1951,59 @@ DISTRIBUTE_RESOURCES = DISTRIBUTE_RESOURCES.concat([ { "lang": "en", "group": "", + "tags": [], + "url": "https://support.google.com/adwords/answer/2471188", + "timestamp": null, + "image": "images/play_dev.jpg", + "title": "AdWords Conversion Optimizer", + "summary": "Learn how Conversion Optimizer works to find the users who are most likely to convert and to serve them your conversion ads.", + "keywords": [], + "type": "distribute", + "titleFriendly": "" + }, + { + "lang": "en", + "group": "", + "tags": [], + "url": "https://developers.google.com/app-conversion-tracking/", + "timestamp": null, + "image": "images/play_dev.jpg", + "title": "Track conversions with the AdWords SDK or server API", + "summary": "Use the lightweight AdWords app SDK or server-to-server API to track remarketing conversions.", + "keywords": [], + "type": "distribute", + "titleFriendly": "" + }, + { + "lang": "en", + "group": "", + "tags": [], + "url": "https://support.google.com/analytics/answer/2611404", + "timestamp": null, + "image": "images/play_dev.jpg", + "title": "Create Remarketing Audiences in Google Analytics", + "summary": "Learn how to use preconfigured audiences created by the Analytics team or create your own to use in your conversion campaigns.", + "keywords": [], + "type": "distribute", + "titleFriendly": "" + }, + { + "lang": "en", + "group": "", + "tags": [], + "url": "https://support.google.com/adwords/answer/1704341", + "timestamp": null, + "image": "images/play_dev.jpg", + "title": "Link your Google Analytics and AdWords accounts", + "summary": "Gain greater insight into how AdWords is driving app engagement and conversions, and use this insight to improve your ads and app.", + "keywords": [], + "type": "distribute", + "titleFriendly": "" + }, + + { + "lang": "en", + "group": "", "tags": ["plus", "social"], "url": "https://plus.google.com/+AndroidDevelopers/", "timestamp": null, @@ -2719,7 +2772,6 @@ DISTRIBUTE_RESOURCES = DISTRIBUTE_RESOURCES.concat([ "type": "material design", "titleFriendly": "" }, - { "lang": "en", "group": "", @@ -2737,6 +2789,45 @@ DISTRIBUTE_RESOURCES = DISTRIBUTE_RESOURCES.concat([ "lang": "en", "group": "", "tags": [], + "url": "https://developers.google.com/nearby/", + "timestamp": null, + "image": "images/play_dev.jpg", + "title": "Create features based on proximity", + "summary": "Build simple interactions between nearby devices and people.", + "keywords": ["nearby", "engage"], + "type": "distribute", + "titleFriendly": "" + }, + { + "lang": "en", + "group": "", + "tags": [], + "url": "https://www.youtube.com/watch?v=hultDpBS22s", + "timestamp": null, + "image": "images/play_dev.jpg", + "title": "Use Nearby Messages to collaborate", + "summary": "Nearby Messages is perfect for setting up ad-hoc groups, collaborative sessions, or sharing resources with people in a co-located space.", + "keywords": ["nearby", "engage"], + "type": "distribute", + "titleFriendly": "" + }, + { + "lang": "en", + "group": "", + "tags": [], + "url": "https://developers.google.com/beacons", + "timestamp": null, + "image": "images/play_dev.jpg", + "title": "Mark up the world using beacons", + "summary": "Give your users better location and proximity experiences by providing a strong context signal for their devices in the form of Bluetooth low energy (BLE) beacons with Eddystone.", + "keywords": ["nearby", "engage"], + "type": "distribute", + "titleFriendly": "" + }, + { + "lang": "en", + "group": "", + "tags": [], "url": "https://support.google.com/adwords/answer/6167164", "timestamp": null, "image": "distribute/images/advertising.jpg", diff --git a/docs/html/jd_extras_en.js b/docs/html/jd_extras_en.js index 9cc110ddfc7b..434f2116a0b4 100644 --- a/docs/html/jd_extras_en.js +++ b/docs/html/jd_extras_en.js @@ -1943,13 +1943,65 @@ METADATA['en'].extras = METADATA['en'].extras.concat([ "url": "https://support.google.com/googleplay/answer/2651410", "timestamp": null, "image": "images/cards/google-play_2x.png", - "title": "Google Play Accepted Payment Methods", + "title": "Google Play accepted payment methods", "summary": "Support details on the payment methods supported in Google Play.", "keywords": ["gift card"], "type": "distribute", "category": "google play" }, { + "lang": "en", + "group": "", + "tags": [], + "url": "https://support.google.com/adwords/answer/2471188", + "timestamp": null, + "image": "images/play_dev.jpg", + "title": "AdWords Conversion Optimizer", + "summary": "Learn how Conversion Optimizer works to find the users who are most likely to convert and to serve them your conversion ads.", + "keywords": [], + "type": "distribute", + "titleFriendly": "" + }, + { + "lang": "en", + "group": "", + "tags": [], + "url": "https://developers.google.com/app-conversion-tracking/", + "timestamp": null, + "image": "images/play_dev.jpg", + "title": "Track conversions with the AdWords SDK or server API", + "summary": "Use the lightweight AdWords app SDK or server-to-server API to track remarketing conversions.", + "keywords": [], + "type": "distribute", + "titleFriendly": "" + }, + { + "lang": "en", + "group": "", + "tags": [], + "url": "https://support.google.com/analytics/answer/2611404", + "timestamp": null, + "image": "images/play_dev.jpg", + "title": "Create Remarketing Audiences in Google Analytics", + "summary": "Learn how to use preconfigured audiences created by the Analytics team or create your own to use in your conversion campaigns.", + "keywords": [], + "type": "distribute", + "titleFriendly": "" + }, + { + "lang": "en", + "group": "", + "tags": [], + "url": "https://support.google.com/adwords/answer/1704341", + "timestamp": null, + "image": "images/play_dev.jpg", + "title": "Link your Google Analytics and AdWords accounts", + "summary": "Gain greater insight into how AdWords is driving app engagement and conversions, and use this insight to improve your ads and app.", + "keywords": [], + "type": "distribute", + "titleFriendly": "" + }, + { "lang": "en", "group": "", "tags": ["plus", "social"], @@ -2699,6 +2751,45 @@ METADATA['en'].extras = METADATA['en'].extras.concat([ "lang": "en", "group": "", "tags": [], + "url": "https://developers.google.com/nearby/", + "timestamp": null, + "image": "images/play_dev.jpg", + "title": "Create features based on proximity", + "summary": "Build simple interactions between nearby devices and people.", + "keywords": ["nearby", "engage"], + "type": "distribute", + "titleFriendly": "" + }, + { + "lang": "en", + "group": "", + "tags": [], + "url": "https://www.youtube.com/watch?v=hultDpBS22s", + "timestamp": null, + "image": "images/play_dev.jpg", + "title": "Use Nearby Messages to collaborate", + "summary": "Nearby Messages is perfect for setting up ad-hoc groups, collaborative sessions, or sharing resources with people in a co-located space.", + "keywords": ["nearby", "engage"], + "type": "distribute", + "titleFriendly": "" + }, + { + "lang": "en", + "group": "", + "tags": [], + "url": "https://developers.google.com/beacons", + "timestamp": null, + "image": "images/play_dev.jpg", + "title": "Mark up the world using beacons", + "summary": "Give your users better location and proximity experiences by providing a strong context signal for their devices in the form of Bluetooth low energy (BLE) beacons with Eddystone.", + "keywords": ["nearby", "engage"], + "type": "distribute", + "titleFriendly": "" + }, + { + "lang": "en", + "group": "", + "tags": [], "url": "https://support.google.com/adwords/answer/6167164", "timestamp": null, "image": "distribute/images/advertising.jpg", @@ -4160,7 +4251,8 @@ METADATA['en'].collections = { "distribute/engage/easy-signin.html", "distribute/analyze/build-better-apps.html", "distribute/engage/gcm.html", - "distribute/engage/beta.html" + "distribute/engage/beta.html", + "distribute/engage/nearby.html" ] }, "distribute/monetize": { @@ -4171,6 +4263,7 @@ METADATA['en'].collections = { "distribute/monetize/ads.html", "distribute/monetize/ecommerce.html", "distribute/monetize/payments.html", + "distribute/monetize/conversions.html", "distribute/analyze/understand-user-value.html", ] }, @@ -4480,6 +4573,14 @@ METADATA['en'].collections = { "https://support.google.com/adwords/answer/6167162" ] }, + "distribute/users/nearby": { + "title": "", + "resources": [ + "https://developers.google.com/nearby/", + "https://www.youtube.com/watch?v=hultDpBS22s", + "https://developers.google.com/beacons" + ] + }, "distribute/users/buildbuzz": { "title": "", "resources": [ @@ -5117,6 +5218,15 @@ METADATA['en'].collections = { "https://support.google.com/googleplay/answer/2651410" ] }, + "distribute/monetize/conversions": { + "title": "", + "resources": [ + "https://support.google.com/adwords/answer/2471188", + "https://developers.google.com/app-conversion-tracking/", + "https://support.google.com/analytics/answer/2611404", + "https://support.google.com/adwords/answer/1704341" + ] + }, "topic/libraries": { "title": "", "resources": [ diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp index ceef9c788fc1..5003c6aefb8b 100644 --- a/libs/hwui/renderthread/CanvasContext.cpp +++ b/libs/hwui/renderthread/CanvasContext.cpp @@ -198,6 +198,45 @@ static bool wasSkipped(FrameInfo* info) { return info && ((*info)[FrameInfoIndex::Flags] & FrameInfoFlags::SkippedFrame); } +bool CanvasContext::isSwapChainStuffed() { + if (mSwapHistory.size() != mSwapHistory.capacity()) { + // We want at least 3 frames of history before attempting to + // guess if the queue is stuffed + return false; + } + nsecs_t frameInterval = mRenderThread.timeLord().frameIntervalNanos(); + auto& swapA = mSwapHistory[0]; + + // Was there a happy queue & dequeue time? If so, don't + // consider it stuffed + if (swapA.dequeueDuration < 3_ms + && swapA.queueDuration < 3_ms) { + return false; + } + + for (size_t i = 1; i < mSwapHistory.size(); i++) { + auto& swapB = mSwapHistory[i]; + + // If there's a frameInterval gap we effectively already dropped a frame, + // so consider the queue healthy. + if (swapA.swapCompletedTime - swapB.swapCompletedTime > frameInterval) { + return false; + } + + // Was there a happy queue & dequeue time? If so, don't + // consider it stuffed + if (swapB.dequeueDuration < 3_ms + && swapB.queueDuration < 3_ms) { + return false; + } + + swapA = swapB; + } + + // All signs point to a stuffed swap chain + return true; +} + void CanvasContext::prepareTree(TreeInfo& info, int64_t* uiFrameInfo, int64_t syncQueued, RenderNode* target) { mRenderThread.removeFrameCallback(this); @@ -243,7 +282,12 @@ void CanvasContext::prepareTree(TreeInfo& info, int64_t* uiFrameInfo, if (CC_LIKELY(mSwapHistory.size())) { nsecs_t latestVsync = mRenderThread.timeLord().latestVsync(); - const SwapHistory& lastSwap = mSwapHistory.back(); + SwapHistory& lastSwap = mSwapHistory.back(); + int durationUs; + mNativeSurface->query(NATIVE_WINDOW_LAST_DEQUEUE_DURATION, &durationUs); + lastSwap.dequeueDuration = us2ns(durationUs); + mNativeSurface->query(NATIVE_WINDOW_LAST_QUEUE_DURATION, &durationUs); + lastSwap.queueDuration = us2ns(durationUs); nsecs_t vsyncDelta = std::abs(lastSwap.vsyncTime - latestVsync); // The slight fudge-factor is to deal with cases where // the vsync was estimated due to being slow handling the signal. @@ -253,15 +297,12 @@ void CanvasContext::prepareTree(TreeInfo& info, int64_t* uiFrameInfo, // Already drew for this vsync pulse, UI draw request missed // the deadline for RT animations info.out.canDrawThisFrame = false; - } else if (lastSwap.swapTime < latestVsync) { + } else if (vsyncDelta >= mRenderThread.timeLord().frameIntervalNanos()) { + // It's been at least an entire frame interval, assume + // the buffer queue is fine info.out.canDrawThisFrame = true; } else { - // We're maybe behind? Find out for sure - int runningBehind = 0; - // TODO: Have this method be on Surface, too, not just ANativeWindow... - ANativeWindow* window = mNativeSurface.get(); - window->query(window, NATIVE_WINDOW_CONSUMER_RUNNING_BEHIND, &runningBehind); - info.out.canDrawThisFrame = !runningBehind; + info.out.canDrawThisFrame = !isSwapChainStuffed(); } } else { info.out.canDrawThisFrame = true; @@ -516,7 +557,7 @@ void CanvasContext::draw() { } SwapHistory& swap = mSwapHistory.next(); swap.damage = screenDirty; - swap.swapTime = systemTime(CLOCK_MONOTONIC); + swap.swapCompletedTime = systemTime(CLOCK_MONOTONIC); swap.vsyncTime = mRenderThread.timeLord().latestVsync(); mHaveNewSurface = false; mFrameNumber = -1; diff --git a/libs/hwui/renderthread/CanvasContext.h b/libs/hwui/renderthread/CanvasContext.h index a6eb7adc3568..b0d980b94308 100644 --- a/libs/hwui/renderthread/CanvasContext.h +++ b/libs/hwui/renderthread/CanvasContext.h @@ -180,6 +180,8 @@ private: void waitOnFences(); + bool isSwapChainStuffed(); + EGLint mLastFrameWidth = 0; EGLint mLastFrameHeight = 0; @@ -198,7 +200,9 @@ private: struct SwapHistory { SkRect damage; nsecs_t vsyncTime; - nsecs_t swapTime; + nsecs_t swapCompletedTime; + nsecs_t dequeueDuration; + nsecs_t queueDuration; }; RingBuffer<SwapHistory, 3> mSwapHistory; diff --git a/media/java/android/media/MediaScanner.java b/media/java/android/media/MediaScanner.java index 5fd85d19b9b9..c8ab5f9bc429 100644 --- a/media/java/android/media/MediaScanner.java +++ b/media/java/android/media/MediaScanner.java @@ -60,11 +60,14 @@ import java.io.FileDescriptor; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStreamReader; +import java.text.SimpleDateFormat; +import java.text.ParseException; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.Locale; +import java.util.TimeZone; import java.util.concurrent.atomic.AtomicBoolean; /** @@ -451,6 +454,8 @@ public class MediaScanner implements AutoCloseable { private class MyMediaScannerClient implements MediaScannerClient { + private final SimpleDateFormat mDateFormatter; + private String mArtist; private String mAlbumArtist; // use this if mArtist is missing private String mAlbum; @@ -463,6 +468,7 @@ public class MediaScanner implements AutoCloseable { private int mYear; private int mDuration; private String mPath; + private long mDate; private long mLastModified; private long mFileSize; private String mWriter; @@ -472,6 +478,11 @@ public class MediaScanner implements AutoCloseable { private int mWidth; private int mHeight; + public MyMediaScannerClient() { + mDateFormatter = new SimpleDateFormat("yyyyMMdd'T'HHmmss"); + mDateFormatter.setTimeZone(TimeZone.getTimeZone("UTC")); + } + public FileEntry beginFile(String path, String mimeType, long lastModified, long fileSize, boolean isDirectory, boolean noMedia) { mMimeType = mimeType; @@ -537,6 +548,7 @@ public class MediaScanner implements AutoCloseable { mYear = 0; mDuration = 0; mPath = path; + mDate = 0; mLastModified = lastModified; mWriter = null; mCompilation = 0; @@ -627,6 +639,14 @@ public class MediaScanner implements AutoCloseable { return result; } + private long parseDate(String date) { + try { + return mDateFormatter.parse(date).getTime(); + } catch (ParseException e) { + return 0; + } + } + private int parseSubstring(String s, int start, int defaultValue) { int length = s.length(); if (start == length) return defaultValue; @@ -684,6 +704,8 @@ public class MediaScanner implements AutoCloseable { mCompilation = parseSubstring(value, 0, 0); } else if (name.equalsIgnoreCase("isdrm")) { mIsDrm = (parseSubstring(value, 0, 0) == 1); + } else if (name.equalsIgnoreCase("date")) { + mDate = parseDate(value); } else if (name.equalsIgnoreCase("width")) { mWidth = parseSubstring(value, 0, 0); } else if (name.equalsIgnoreCase("height")) { @@ -830,6 +852,9 @@ public class MediaScanner implements AutoCloseable { if (resolution != null) { map.put(Video.Media.RESOLUTION, resolution); } + if (mDate > 0) { + map.put(Video.Media.DATE_TAKEN, mDate); + } } else if (MediaFile.isImageFileType(mFileType)) { // FIXME - add DESCRIPTION } else if (MediaFile.isAudioFileType(mFileType)) { diff --git a/packages/DocumentsUI/src/com/android/documentsui/IconUtils.java b/packages/DocumentsUI/src/com/android/documentsui/IconUtils.java index c28fae85cb8b..177ba0d20154 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/IconUtils.java +++ b/packages/DocumentsUI/src/com/android/documentsui/IconUtils.java @@ -23,181 +23,7 @@ import android.graphics.drawable.Drawable; import android.provider.DocumentsContract.Document; import android.util.TypedValue; -import java.util.HashMap; - public class IconUtils { - - private static HashMap<String, Integer> sMimeIcons = new HashMap<>(); - - private static void add(String mimeType, int resId) { - if (sMimeIcons.put(mimeType, resId) != null) { - throw new RuntimeException(mimeType + " already registered!"); - } - } - - static { - int icon; - - // Package - icon = R.drawable.ic_doc_apk; - add("application/vnd.android.package-archive", icon); - - // Audio - icon = R.drawable.ic_doc_audio; - add("application/ogg", icon); - add("application/x-flac", icon); - - // Certificate - icon = R.drawable.ic_doc_certificate; - add("application/pgp-keys", icon); - add("application/pgp-signature", icon); - add("application/x-pkcs12", icon); - add("application/x-pkcs7-certreqresp", icon); - add("application/x-pkcs7-crl", icon); - add("application/x-x509-ca-cert", icon); - add("application/x-x509-user-cert", icon); - add("application/x-pkcs7-certificates", icon); - add("application/x-pkcs7-mime", icon); - add("application/x-pkcs7-signature", icon); - - // Source code - icon = R.drawable.ic_doc_codes; - add("application/rdf+xml", icon); - add("application/rss+xml", icon); - add("application/x-object", icon); - add("application/xhtml+xml", icon); - add("text/css", icon); - add("text/html", icon); - add("text/xml", icon); - add("text/x-c++hdr", icon); - add("text/x-c++src", icon); - add("text/x-chdr", icon); - add("text/x-csrc", icon); - add("text/x-dsrc", icon); - add("text/x-csh", icon); - add("text/x-haskell", icon); - add("text/x-java", icon); - add("text/x-literate-haskell", icon); - add("text/x-pascal", icon); - add("text/x-tcl", icon); - add("text/x-tex", icon); - add("application/x-latex", icon); - add("application/x-texinfo", icon); - add("application/atom+xml", icon); - add("application/ecmascript", icon); - add("application/json", icon); - add("application/javascript", icon); - add("application/xml", icon); - add("text/javascript", icon); - add("application/x-javascript", icon); - - // Compressed - icon = R.drawable.ic_doc_compressed; - add("application/mac-binhex40", icon); - add("application/rar", icon); - add("application/zip", icon); - add("application/x-apple-diskimage", icon); - add("application/x-debian-package", icon); - add("application/x-gtar", icon); - add("application/x-iso9660-image", icon); - add("application/x-lha", icon); - add("application/x-lzh", icon); - add("application/x-lzx", icon); - add("application/x-stuffit", icon); - add("application/x-tar", icon); - add("application/x-webarchive", icon); - add("application/x-webarchive-xml", icon); - add("application/gzip", icon); - add("application/x-7z-compressed", icon); - add("application/x-deb", icon); - add("application/x-rar-compressed", icon); - - // Contact - icon = R.drawable.ic_doc_contact; - add("text/x-vcard", icon); - add("text/vcard", icon); - - // Event - icon = R.drawable.ic_doc_event; - add("text/calendar", icon); - add("text/x-vcalendar", icon); - - // Font - icon = R.drawable.ic_doc_font; - add("application/x-font", icon); - add("application/font-woff", icon); - add("application/x-font-woff", icon); - add("application/x-font-ttf", icon); - - // Image - icon = R.drawable.ic_doc_image; - add("application/vnd.oasis.opendocument.graphics", icon); - add("application/vnd.oasis.opendocument.graphics-template", icon); - add("application/vnd.oasis.opendocument.image", icon); - add("application/vnd.stardivision.draw", icon); - add("application/vnd.sun.xml.draw", icon); - add("application/vnd.sun.xml.draw.template", icon); - - // PDF - icon = R.drawable.ic_doc_pdf; - add("application/pdf", icon); - - // Presentation - icon = R.drawable.ic_doc_presentation; - add("application/vnd.stardivision.impress", icon); - add("application/vnd.sun.xml.impress", icon); - add("application/vnd.sun.xml.impress.template", icon); - add("application/x-kpresenter", icon); - add("application/vnd.oasis.opendocument.presentation", icon); - - // Spreadsheet - icon = R.drawable.ic_doc_spreadsheet; - add("application/vnd.oasis.opendocument.spreadsheet", icon); - add("application/vnd.oasis.opendocument.spreadsheet-template", icon); - add("application/vnd.stardivision.calc", icon); - add("application/vnd.sun.xml.calc", icon); - add("application/vnd.sun.xml.calc.template", icon); - add("application/x-kspread", icon); - - // Document - icon = R.drawable.ic_doc_document; - add("application/vnd.oasis.opendocument.text", icon); - add("application/vnd.oasis.opendocument.text-master", icon); - add("application/vnd.oasis.opendocument.text-template", icon); - add("application/vnd.oasis.opendocument.text-web", icon); - add("application/vnd.stardivision.writer", icon); - add("application/vnd.stardivision.writer-global", icon); - add("application/vnd.sun.xml.writer", icon); - add("application/vnd.sun.xml.writer.global", icon); - add("application/vnd.sun.xml.writer.template", icon); - add("application/x-abiword", icon); - add("application/x-kword", icon); - - // Video - icon = R.drawable.ic_doc_video; - add("application/x-quicktimeplayer", icon); - add("application/x-shockwave-flash", icon); - - // Word - icon = R.drawable.ic_doc_word; - add("application/msword", icon); - add("application/vnd.openxmlformats-officedocument.wordprocessingml.document", icon); - add("application/vnd.openxmlformats-officedocument.wordprocessingml.template", icon); - - // Excel - icon = R.drawable.ic_doc_excel; - add("application/vnd.ms-excel", icon); - add("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", icon); - add("application/vnd.openxmlformats-officedocument.spreadsheetml.template", icon); - - // Powerpoint - icon = R.drawable.ic_doc_powerpoint; - add("application/vnd.ms-powerpoint", icon); - add("application/vnd.openxmlformats-officedocument.presentationml.presentation", icon); - add("application/vnd.openxmlformats-officedocument.presentationml.template", icon); - add("application/vnd.openxmlformats-officedocument.presentationml.slideshow", icon); - } - public static Drawable loadPackageIcon(Context context, String authority, int icon) { if (icon != 0) { if (authority != null) { @@ -225,7 +51,7 @@ public class IconUtils { if (mode == State.MODE_GRID) { return context.getDrawable(R.drawable.ic_grid_folder); } else { - return context.getDrawable(R.drawable.ic_doc_folder); + return context.getDrawable(com.android.internal.R.drawable.ic_doc_folder); } } @@ -233,34 +59,7 @@ public class IconUtils { } public static Drawable loadMimeIcon(Context context, String mimeType) { - if (Document.MIME_TYPE_DIR.equals(mimeType)) { - return context.getDrawable(R.drawable.ic_doc_folder); - } - - // Look for exact match first - Integer resId = sMimeIcons.get(mimeType); - if (resId != null) { - return context.getDrawable(resId); - } - - if (mimeType == null) { - // TODO: generic icon? - return null; - } - - // Otherwise look for partial match - final String typeOnly = mimeType.split("/")[0]; - if ("audio".equals(typeOnly)) { - return context.getDrawable(R.drawable.ic_doc_audio); - } else if ("image".equals(typeOnly)) { - return context.getDrawable(R.drawable.ic_doc_image); - } else if ("text".equals(typeOnly)) { - return context.getDrawable(R.drawable.ic_doc_text); - } else if ("video".equals(typeOnly)) { - return context.getDrawable(R.drawable.ic_doc_video); - } else { - return context.getDrawable(R.drawable.ic_doc_generic); - } + return context.getContentResolver().getTypeDrawable(mimeType); } public static Drawable applyTintColor(Context context, int drawableId, int tintColorId) { diff --git a/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java b/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java index 870c3439b8a9..47df940bb1b5 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java +++ b/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java @@ -1391,7 +1391,7 @@ public class DirectoryFragment extends Fragment return mIconHelper.getDocumentIcon(mContext, doc.authority, doc.documentId, doc.mimeType, doc.icon); } - return mContext.getDrawable(R.drawable.ic_doc_generic); + return mContext.getDrawable(com.android.internal.R.drawable.ic_doc_generic); } private String getTitle(List<DocumentInfo> docs) { diff --git a/packages/DocumentsUI/src/com/android/documentsui/model/RootInfo.java b/packages/DocumentsUI/src/com/android/documentsui/model/RootInfo.java index d54bdfd580eb..649dde049fc4 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/model/RootInfo.java +++ b/packages/DocumentsUI/src/com/android/documentsui/model/RootInfo.java @@ -213,13 +213,13 @@ public class RootInfo implements Durable, Parcelable, Comparable<RootInfo> { derivedIcon = R.drawable.ic_root_download; } else if (isImages()) { derivedType = TYPE_IMAGES; - derivedIcon = R.drawable.ic_doc_image; + derivedIcon = com.android.internal.R.drawable.ic_doc_image; } else if (isVideos()) { derivedType = TYPE_VIDEO; - derivedIcon = R.drawable.ic_doc_video; + derivedIcon = com.android.internal.R.drawable.ic_doc_video; } else if (isAudio()) { derivedType = TYPE_AUDIO; - derivedIcon = R.drawable.ic_doc_audio; + derivedIcon = com.android.internal.R.drawable.ic_doc_audio; } else if (isRecents()) { derivedType = TYPE_RECENTS; } else { diff --git a/packages/SettingsLib/src/com/android/settingslib/DeviceInfoUtils.java b/packages/SettingsLib/src/com/android/settingslib/DeviceInfoUtils.java index ff1c8665b3d2..b04948b7de5f 100644 --- a/packages/SettingsLib/src/com/android/settingslib/DeviceInfoUtils.java +++ b/packages/SettingsLib/src/com/android/settingslib/DeviceInfoUtils.java @@ -22,6 +22,9 @@ import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.os.Build; +import android.telephony.PhoneNumberUtils; +import android.telephony.SubscriptionInfo; +import android.telephony.TelephonyManager; import android.text.TextUtils; import android.text.format.DateFormat; import android.util.Log; @@ -37,6 +40,8 @@ import java.util.Locale; import java.util.regex.Matcher; import java.util.regex.Pattern; +import static android.content.Context.TELEPHONY_SERVICE; + public class DeviceInfoUtils { private static final String TAG = "DeviceInfoUtils"; @@ -169,4 +174,40 @@ public class DeviceInfoUtils { } } + public static String getFormattedPhoneNumber(Context context, SubscriptionInfo subscriptionInfo) { + String formattedNumber = null; + if (subscriptionInfo != null) { + final TelephonyManager telephonyManager = + (TelephonyManager) context.getSystemService(TELEPHONY_SERVICE); + final String rawNumber = + telephonyManager.getLine1Number(subscriptionInfo.getSubscriptionId()); + if (!TextUtils.isEmpty(rawNumber)) { + formattedNumber = PhoneNumberUtils.formatNumber(rawNumber); + } + + } + return formattedNumber; + } + + public static String getFormattedPhoneNumbers(Context context, + List<SubscriptionInfo> subscriptionInfo) { + StringBuilder sb = new StringBuilder(); + if (subscriptionInfo != null) { + final TelephonyManager telephonyManager = + (TelephonyManager) context.getSystemService(TELEPHONY_SERVICE); + final int count = subscriptionInfo.size(); + for (int i = 0; i < count; i++) { + final String rawNumber = telephonyManager.getLine1Number( + subscriptionInfo.get(i).getSubscriptionId()); + if (!TextUtils.isEmpty(rawNumber)) { + sb.append(PhoneNumberUtils.formatNumber(rawNumber)); + if (i < count - 1) { + sb.append("\n"); + } + } + } + } + return sb.toString(); + } + } diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index cd861e14b18b..e1cbbc5c9c54 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -141,6 +141,9 @@ <!-- Margin start of the system icons super container --> <dimen name="system_icons_super_container_margin_start">16dp</dimen> + <!-- Margin end of the system icons super container when the avatar is missing. --> + <dimen name="system_icons_super_container_avatarless_margin_end">6dp</dimen> + <!-- Width for the notification panel and related windows --> <dimen name="match_parent">-1px</dimen> <dimen name="standard_notification_panel_width">416dp</dimen> diff --git a/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java b/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java index a5e771fa53a4..af2a2869bc30 100644 --- a/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java +++ b/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java @@ -15,6 +15,7 @@ import android.graphics.PixelFormat; import android.os.AsyncTask; import android.os.Bundle; import android.os.Handler; +import android.os.IBinder; import android.os.RemoteException; import android.os.UserHandle; import android.provider.Settings; @@ -28,9 +29,9 @@ import android.view.WindowManager; import android.widget.ImageView; import com.android.internal.app.AssistUtils; +import com.android.internal.app.IVoiceInteractionSessionListener; import com.android.internal.app.IVoiceInteractionSessionShowCallback; import com.android.systemui.R; -import com.android.systemui.SystemUIFactory; import com.android.systemui.statusbar.BaseStatusBar; import com.android.systemui.statusbar.CommandQueue; @@ -52,7 +53,7 @@ public class AssistManager { private AssistOrbContainer mView; private final BaseStatusBar mBar; - private final AssistUtils mAssistUtils; + protected final AssistUtils mAssistUtils; private IVoiceInteractionSessionShowCallback mShowCallback = new IVoiceInteractionSessionShowCallback.Stub() { @@ -82,6 +83,23 @@ public class AssistManager { mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE); mAssistUtils = new AssistUtils(context); mAssistDisclosure = new AssistDisclosure(context, new Handler()); + + registerVoiceInteractionSessionListener(); + } + + protected void registerVoiceInteractionSessionListener() { + mAssistUtils.registerVoiceInteractionSessionListener( + new IVoiceInteractionSessionListener.Stub() { + @Override + public void onVoiceSessionShown() throws RemoteException { + Log.v(TAG, "Voice open"); + } + + @Override + public void onVoiceSessionHidden() throws RemoteException { + Log.v(TAG, "Voice closed"); + } + }); } public void onConfigurationChanged() { diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java index 7e1deec3429a..f9e59e7ccf99 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java +++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java @@ -609,7 +609,7 @@ public class RecentsImpl implements ActivityOptions.OnAnimationFinishedListener stackLayout.setSystemInsets(systemInsets); if (stack != null) { stackLayout.getTaskStackBounds(displayRect, windowRect, systemInsets.top, - systemInsets.right, mTaskStackBounds); + systemInsets.left, systemInsets.right, mTaskStackBounds); stackLayout.reset(); stackLayout.initialize(displayRect, windowRect, mTaskStackBounds, TaskStackLayoutAlgorithm.StackState.getStackStateForStack(stack)); diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java b/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java index 26200d0fac47..b5753ba39a43 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java +++ b/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java @@ -511,8 +511,8 @@ public class TaskStack { int top = dockArea.bottom < 1f ? 0 : insets.top; - layoutAlgorithm.getTaskStackBounds(displayRect, windowRectOut, top, insets.right, - taskStackBounds); + layoutAlgorithm.getTaskStackBounds(displayRect, windowRectOut, top, insets.left, + insets.right, taskStackBounds); return taskStackBounds; } } diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java index e3fe1abf8b20..89789bce6cf4 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java @@ -1058,9 +1058,9 @@ public class TaskStackLayoutAlgorithm { * top and right system insets (but not the bottom inset) and left/right paddings, but _not_ * the top/bottom padding or insets. */ - public void getTaskStackBounds(Rect displayRect, Rect windowRect, int topInset, int rightInset, - Rect taskStackBounds) { - taskStackBounds.set(windowRect.left, windowRect.top + topInset, + public void getTaskStackBounds(Rect displayRect, Rect windowRect, int topInset, int leftInset, + int rightInset, Rect taskStackBounds) { + taskStackBounds.set(windowRect.left + leftInset, windowRect.top + topInset, windowRect.right - rightInset, windowRect.bottom); // Ensure that the new width is at most the smaller display edge size diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java index 586a8bcd8920..21780a6e8864 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java @@ -1191,7 +1191,8 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal // bounds have changed. This is because we may get spurious measures while dragging where // our current stack bounds reflect the target drop region. mLayoutAlgorithm.getTaskStackBounds(mDisplayRect, new Rect(0, 0, width, height), - mLayoutAlgorithm.mSystemInsets.top, mLayoutAlgorithm.mSystemInsets.right, mTmpRect); + mLayoutAlgorithm.mSystemInsets.top, mLayoutAlgorithm.mSystemInsets.left, + mLayoutAlgorithm.mSystemInsets.right, mTmpRect); if (!mTmpRect.equals(mStableStackBounds)) { mStableStackBounds.set(mTmpRect); mStackBounds.set(mTmpRect); diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java index 4badc420d74f..f3bae20e544a 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java @@ -23,6 +23,8 @@ import android.os.IBinder; import android.os.Message; import android.os.Messenger; import android.os.RemoteException; +import android.os.UserManager; +import android.util.Log; import android.view.WindowManager; public class TakeScreenshotService extends Service { @@ -44,6 +46,16 @@ public class TakeScreenshotService extends Service { } } }; + + // If the storage for this user is locked, we have no place to store + // the screenshot, so skip taking it instead of showing a misleading + // animation and error notification. + if (!getSystemService(UserManager.class).isUserUnlocked()) { + Log.w(TAG, "Skipping screenshot because storage is locked!"); + post(finisher); + return; + } + if (mScreenshot == null) { mScreenshot = new GlobalScreenshot(TakeScreenshotService.this); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ButtonDispatcher.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ButtonDispatcher.java index b5a48a3c67e9..b53a99907146 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ButtonDispatcher.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ButtonDispatcher.java @@ -157,6 +157,10 @@ public class ButtonDispatcher { } } + public ArrayList<View> getViews() { + return mViews; + } + public View getCurrentView() { return mCurrentView; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java index 42f398d8d83f..93ed1398c2a6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java @@ -18,6 +18,7 @@ package com.android.systemui.statusbar.phone; import android.content.Context; import android.content.res.Configuration; +import android.content.res.Resources; import android.graphics.drawable.Drawable; import android.util.AttributeSet; import android.util.TypedValue; @@ -59,6 +60,7 @@ public class KeyguardStatusBarView extends RelativeLayout private UserSwitcherController mUserSwitcherController; private int mSystemIconsSwitcherHiddenExpandedMargin; + private int mSystemIconsBaseMargin; private View mSystemIconsContainer; public KeyguardStatusBarView(Context context, AttributeSet attrs) { @@ -137,8 +139,11 @@ public class KeyguardStatusBarView extends RelativeLayout } private void loadDimens() { - mSystemIconsSwitcherHiddenExpandedMargin = getResources().getDimensionPixelSize( + Resources res = getResources(); + mSystemIconsSwitcherHiddenExpandedMargin = res.getDimensionPixelSize( R.dimen.system_icons_switcher_hidden_expanded_margin); + mSystemIconsBaseMargin = res.getDimensionPixelSize( + R.dimen.system_icons_super_container_avatarless_margin_end); } private void updateVisibilities() { @@ -166,7 +171,13 @@ public class KeyguardStatusBarView extends RelativeLayout private void updateSystemIconsLayoutParams() { RelativeLayout.LayoutParams lp = (LayoutParams) mSystemIconsSuperContainer.getLayoutParams(); - int marginEnd = mKeyguardUserSwitcherShowing ? mSystemIconsSwitcherHiddenExpandedMargin : 0; + // If the avatar icon is gone, we need to have some end margin to display the system icons + // correctly. + int baseMarginEnd = mMultiUserSwitch.getVisibility() == View.GONE + ? mSystemIconsBaseMargin + : 0; + int marginEnd = mKeyguardUserSwitcherShowing ? mSystemIconsSwitcherHiddenExpandedMargin : + baseMarginEnd; if (marginEnd != lp.getMarginEnd()) { lp.setMarginEnd(marginEnd); mSystemIconsSuperContainer.setLayoutParams(lp); @@ -301,6 +312,7 @@ public class KeyguardStatusBarView extends RelativeLayout mMultiUserSwitch.setAlpha(1f); } else { updateVisibilities(); + updateSystemIconsLayoutParams(); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarInflaterView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarInflaterView.java index b7faf15250d1..dd46b085e005 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarInflaterView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarInflaterView.java @@ -28,6 +28,7 @@ import android.widget.LinearLayout; import android.widget.Space; import com.android.systemui.R; +import com.android.systemui.SystemUIFactory; import com.android.systemui.statusbar.policy.KeyButtonView; import com.android.systemui.tuner.TunerService; @@ -70,8 +71,6 @@ public class NavigationBarInflaterView extends FrameLayout implements TunerServi private View mLastRot0; private View mLastRot90; - private boolean mAlternativeOrder; - public NavigationBarInflaterView(Context context, AttributeSet attrs) { super(context, attrs); mDensity = context.getResources().getConfiguration().densityDpi; @@ -115,7 +114,6 @@ public class NavigationBarInflaterView extends FrameLayout implements TunerServi false); mRot90.setId(R.id.rot90); addView(mRot90); - updateAlternativeOrder(); if (getParent() instanceof NavigationBarView) { ((NavigationBarView) getParent()).updateRotatedViews(); } @@ -154,20 +152,6 @@ public class NavigationBarInflaterView extends FrameLayout implements TunerServi } } - public void setAlternativeOrder(boolean alternativeOrder) { - if (alternativeOrder != mAlternativeOrder) { - mAlternativeOrder = alternativeOrder; - updateAlternativeOrder(); - } - } - - private void updateAlternativeOrder() { - ((ReverseLinearLayout) mRot90.findViewById(R.id.ends_group)).setAlternativeOrder( - mAlternativeOrder); - ((ReverseLinearLayout) mRot90.findViewById(R.id.center_group)).setAlternativeOrder( - mAlternativeOrder); - } - private void initiallyFill(ButtonDispatcher buttonDispatcher) { addAll(buttonDispatcher, (ViewGroup) mRot0.findViewById(R.id.ends_group)); addAll(buttonDispatcher, (ViewGroup) mRot0.findViewById(R.id.center_group)); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java index 23aeae8c5b33..53fe6ce3efa9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java @@ -99,8 +99,6 @@ public class NavigationBarView extends LinearLayout { private final SparseArray<ButtonDispatcher> mButtonDisatchers = new SparseArray<>(); private Configuration mConfiguration; - private NavigationBarInflaterView mNavigationInflaterView; - private class NavTransitionListener implements TransitionListener { private boolean mBackTransitioning; private boolean mHomeAppearing; @@ -474,10 +472,9 @@ public class NavigationBarView extends LinearLayout { @Override public void onFinishInflate() { - mNavigationInflaterView = (NavigationBarInflaterView) findViewById( - R.id.navigation_inflater); updateRotatedViews(); - mNavigationInflaterView.setButtonDispatchers(mButtonDisatchers); + ((NavigationBarInflaterView) findViewById(R.id.navigation_inflater)).setButtonDispatchers( + mButtonDisatchers); getImeSwitchButton().setOnClickListener(mImeSwitcherClickListener); @@ -533,7 +530,6 @@ public class NavigationBarView extends LinearLayout { } mCurrentView = mRotatedViews[rot]; mCurrentView.setVisibility(View.VISIBLE); - mNavigationInflaterView.setAlternativeOrder(rot == Surface.ROTATION_90); for (int i = 0; i < mButtonDisatchers.size(); i++) { mButtonDisatchers.valueAt(i).setCurrentView(mCurrentView); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java index fcb1da0033bb..cb3089a4b1df 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java @@ -4307,6 +4307,10 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, mStackScroller.setActivatedChild(view); } + public ButtonDispatcher getHomeButton() { + return mNavigationBarView.getHomeButton(); + } + /** * @param state The {@link StatusBarState} to set. */ diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ReverseLinearLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ReverseLinearLayout.java index f45967a0a0a6..3682aa1b06f8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ReverseLinearLayout.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ReverseLinearLayout.java @@ -30,11 +30,7 @@ import java.util.ArrayList; */ public class ReverseLinearLayout extends LinearLayout { - /** If true, the layout is reversed vs. a regular linear layout */ - private boolean mIsLayoutReverse; - - /** If true, the layout is opposite to it's natural reversity from the layout direction */ - private boolean mIsAlternativeOrder; + private boolean mIsLayoutRtl; public ReverseLinearLayout(Context context, @Nullable AttributeSet attrs) { super(context, attrs); @@ -43,50 +39,45 @@ public class ReverseLinearLayout extends LinearLayout { @Override protected void onFinishInflate() { super.onFinishInflate(); - updateOrder(); + mIsLayoutRtl = getResources().getConfiguration() + .getLayoutDirection() == LAYOUT_DIRECTION_RTL; } @Override public void addView(View child) { reversParams(child.getLayoutParams()); - if (mIsLayoutReverse) { - super.addView(child, 0); - } else { + if (mIsLayoutRtl) { super.addView(child); + } else { + super.addView(child, 0); } } @Override public void addView(View child, ViewGroup.LayoutParams params) { reversParams(params); - if (mIsLayoutReverse) { - super.addView(child, 0, params); - } else { + if (mIsLayoutRtl) { super.addView(child, params); + } else { + super.addView(child, 0, params); } } @Override - public void onRtlPropertiesChanged(int layoutDirection) { - super.onRtlPropertiesChanged(layoutDirection); - updateOrder(); - } - - public void setAlternativeOrder(boolean alternative) { - mIsAlternativeOrder = alternative; - updateOrder(); + protected void onConfigurationChanged(Configuration newConfig) { + super.onConfigurationChanged(newConfig); + updateRTLOrder(); } /** * In landscape, the LinearLayout is not auto mirrored since it is vertical. Therefore we * have to do it manually */ - private void updateOrder() { - boolean isLayoutRtl = getLayoutDirection() == LAYOUT_DIRECTION_RTL; - boolean isLayoutReverse = isLayoutRtl ^ mIsAlternativeOrder; - - if (mIsLayoutReverse != isLayoutReverse) { - // reversity changed, swap the order of all views. + private void updateRTLOrder() { + boolean isLayoutRtl = getResources().getConfiguration() + .getLayoutDirection() == LAYOUT_DIRECTION_RTL; + if (mIsLayoutRtl != isLayoutRtl) { + // RTL changed, swap the order of all views. int childCount = getChildCount(); ArrayList<View> childList = new ArrayList<>(childCount); for (int i = 0; i < childCount; i++) { @@ -96,7 +87,7 @@ public class ReverseLinearLayout extends LinearLayout { for (int i = childCount - 1; i >= 0; i--) { super.addView(childList.get(i)); } - mIsLayoutReverse = isLayoutReverse; + mIsLayoutRtl = isLayoutRtl; } } diff --git a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java index feceb149ae3f..6defd0f5baef 100644 --- a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java +++ b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java @@ -1905,7 +1905,7 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku SomeArgs args = SomeArgs.obtain(); args.arg1 = widget.host; args.arg2 = widget.host.callbacks; - args.arg3 = updateViews; + args.arg3 = updateViews.clone(); args.arg4 = requestTime; args.argi1 = widget.appWidgetId; diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java index fb5b3f882efc..b12a9619e506 100644 --- a/services/core/java/com/android/server/ConnectivityService.java +++ b/services/core/java/com/android/server/ConnectivityService.java @@ -94,6 +94,7 @@ import android.os.PowerManager; import android.os.Process; import android.os.RemoteException; import android.os.ResultReceiver; +import android.os.SystemClock; import android.os.SystemProperties; import android.os.UserHandle; import android.os.UserManager; @@ -125,6 +126,7 @@ import com.android.internal.net.VpnProfile; import com.android.internal.util.AsyncChannel; import com.android.internal.util.IndentingPrintWriter; import com.android.internal.util.MessageUtils; +import com.android.internal.util.WakeupMessage; import com.android.internal.util.XmlUtils; import com.android.server.am.BatteryStatsService; import com.android.server.connectivity.DataConnectionStats; @@ -171,7 +173,7 @@ import java.util.TreeSet; */ public class ConnectivityService extends IConnectivityManager.Stub implements PendingIntent.OnFinished { - private static final String TAG = "ConnectivityService"; + private static final String TAG = ConnectivityService.class.getSimpleName(); private static final boolean DBG = true; private static final boolean VDBG = false; @@ -191,6 +193,12 @@ public class ConnectivityService extends IConnectivityManager.Stub // connect anyway?" dialog after the user selects a network that doesn't validate. private static final int PROMPT_UNVALIDATED_DELAY_MS = 8 * 1000; + // Default to 30s linger time-out. Modifiable only for testing. + private static final String LINGER_DELAY_PROPERTY = "persist.netmon.linger"; + private static final int DEFAULT_LINGER_DELAY_MS = 30_000; + @VisibleForTesting + protected int mLingerDelayMs; // Can't be final, or test subclass constructors can't change it. + // How long to delay to removal of a pending intent based request. // See Settings.Secure.CONNECTIVITY_RELEASE_PENDING_INTENT_DELAY_MS private final int mReleasePendingIntentDelayMs; @@ -239,7 +247,8 @@ public class ConnectivityService extends IConnectivityManager.Stub private static final int DISABLED = 0; private static final SparseArray<String> sMagicDecoderRing = MessageUtils.findMessageNames( - new Class[] { AsyncChannel.class, ConnectivityService.class, NetworkAgent.class }); + new Class[] { AsyncChannel.class, ConnectivityService.class, NetworkAgent.class, + NetworkAgentInfo.class }); private enum ReapUnvalidatedNetworks { // Tear down networks that have no chance (e.g. even if validated) of becoming @@ -681,6 +690,8 @@ public class ConnectivityService extends IConnectivityManager.Stub mReleasePendingIntentDelayMs = Settings.Secure.getInt(context.getContentResolver(), Settings.Secure.CONNECTIVITY_RELEASE_PENDING_INTENT_DELAY_MS, 5_000); + mLingerDelayMs = SystemProperties.getInt(LINGER_DELAY_PROPERTY, DEFAULT_LINGER_DELAY_MS); + mContext = checkNotNull(context, "missing Context"); mNetd = checkNotNull(netManager, "missing INetworkManagementService"); mStatsService = checkNotNull(statsService, "missing INetworkStatsService"); @@ -1905,7 +1916,8 @@ public class ConnectivityService extends IConnectivityManager.Stub for (NetworkAgentInfo nai : mNetworkAgentInfos.values()) { pw.println(nai.toString()); pw.increaseIndent(); - pw.println("Requests:"); + pw.println(String.format("Requests: %d request/%d total", + nai.numRequestNetworkRequests(), nai.numNetworkRequests())); pw.increaseIndent(); for (int i = 0; i < nai.numNetworkRequests(); i++) { pw.println(nai.requestAt(i).toString()); @@ -1913,7 +1925,7 @@ public class ConnectivityService extends IConnectivityManager.Stub pw.decreaseIndent(); pw.println("Lingered:"); pw.increaseIndent(); - for (NetworkRequest nr : nai.networkLingered) pw.println(nr.toString()); + nai.dumpLingerTimers(pw); pw.decreaseIndent(); pw.decreaseIndent(); } @@ -2158,13 +2170,6 @@ public class ConnectivityService extends IConnectivityManager.Stub } break; } - case NetworkMonitor.EVENT_NETWORK_LINGER_COMPLETE: { - NetworkAgentInfo nai = (NetworkAgentInfo)msg.obj; - if (isLiveNetworkAgent(nai, msg.what)) { - handleLingerComplete(nai); - } - break; - } case NetworkMonitor.EVENT_PROVISIONING_NOTIFICATION: { final int netId = msg.arg2; final boolean visible = (msg.arg1 != 0); @@ -2197,33 +2202,50 @@ public class ConnectivityService extends IConnectivityManager.Stub return true; } + private boolean maybeHandleNetworkAgentInfoMessage(Message msg) { + switch (msg.what) { + default: + return false; + case NetworkAgentInfo.EVENT_NETWORK_LINGER_COMPLETE: { + NetworkAgentInfo nai = (NetworkAgentInfo) msg.obj; + if (nai != null && isLiveNetworkAgent(nai, msg.what)) { + handleLingerComplete(nai); + } + break; + } + } + return true; + } + @Override public void handleMessage(Message msg) { - if (!maybeHandleAsyncChannelMessage(msg) && !maybeHandleNetworkMonitorMessage(msg)) { + if (!maybeHandleAsyncChannelMessage(msg) && + !maybeHandleNetworkMonitorMessage(msg) && + !maybeHandleNetworkAgentInfoMessage(msg)) { maybeHandleNetworkAgentMessage(msg); } } } - private void linger(NetworkAgentInfo nai) { - nai.lingering = true; - logNetworkEvent(nai, NetworkEvent.NETWORK_LINGER); - nai.networkMonitor.sendMessage(NetworkMonitor.CMD_NETWORK_LINGER); - notifyNetworkCallbacks(nai, ConnectivityManager.CALLBACK_LOSING); - } - - // Cancel any lingering so the linger timeout doesn't teardown a network. - // This should be called when a network begins satisfying a NetworkRequest. - // Note: depending on what state the NetworkMonitor is in (e.g., - // if it's awaiting captive portal login, or if validation failed), this - // may trigger a re-evaluation of the network. - private void unlinger(NetworkAgentInfo nai) { - nai.networkLingered.clear(); - if (!nai.lingering) return; - nai.lingering = false; - logNetworkEvent(nai, NetworkEvent.NETWORK_UNLINGER); - if (VDBG) log("Canceling linger of " + nai.name()); - nai.networkMonitor.sendMessage(NetworkMonitor.CMD_NETWORK_CONNECTED); + private void updateLingerState(NetworkAgentInfo nai, long now) { + // 1. Update the linger timer. If it's changed, reschedule or cancel the alarm. + // 2. If the network was lingering and there are now requests, unlinger it. + // 3. If this network is unneeded (which implies it is not lingering), and there is at least + // one lingered request, start lingering. + nai.updateLingerTimer(); + if (nai.isLingering() && nai.numRequestNetworkRequests() > 0) { + if (DBG) log("Unlingering " + nai.name()); + nai.unlinger(); + logNetworkEvent(nai, NetworkEvent.NETWORK_UNLINGER); + } else if (unneeded(nai) && nai.getLingerExpiry() > 0) { // unneeded() calls isLingering() + int lingerTime = (int) (nai.getLingerExpiry() - now); + if (DBG) { + Log.d(TAG, "Lingering " + nai.name() + " for " + lingerTime + "ms"); + } + nai.linger(); + logNetworkEvent(nai, NetworkEvent.NETWORK_LINGER); + notifyNetworkCallbacks(nai, ConnectivityManager.CALLBACK_LOSING, lingerTime); + } } private void handleAsyncChannelHalfConnect(Message msg) { @@ -2313,6 +2335,7 @@ public class ConnectivityService extends IConnectivityManager.Stub sendUpdatedScoreToFactories(request, 0); } } + nai.clearLingerState(); if (nai.isSatisfyingRequest(mDefaultRequest.requestId)) { removeDataActivityTracking(nai); notifyLockdownVpn(nai); @@ -2400,7 +2423,10 @@ public class ConnectivityService extends IConnectivityManager.Stub // This is whether it is satisfying any NetworkRequests or were it to become validated, // would it have a chance of satisfying any NetworkRequests. private boolean unneeded(NetworkAgentInfo nai) { - if (!nai.everConnected || nai.isVPN() || nai.lingering) return false; + if (!nai.everConnected || nai.isVPN() || + nai.isLingering() || nai.numRequestNetworkRequests() > 0) { + return false; + } for (NetworkRequestInfo nri : mNetworkRequests.values()) { // If this Network is already the highest scoring Network for a request, or if // there is hope for it to become one if it validated, then it is needed. @@ -2445,33 +2471,40 @@ public class ConnectivityService extends IConnectivityManager.Stub } mNetworkRequestInfoLogs.log("RELEASE " + nri); if (nri.request.isRequest()) { + boolean wasKept = false; + NetworkAgentInfo nai = mNetworkForRequestId.get(nri.request.requestId); + if (nai != null) { + nai.removeRequest(nri.request.requestId); + if (VDBG) { + log(" Removing from current network " + nai.name() + + ", leaving " + nai.numNetworkRequests() + " requests."); + } + // If there are still lingered requests on this network, don't tear it down, + // but resume lingering instead. + updateLingerState(nai, SystemClock.elapsedRealtime()); + if (unneeded(nai)) { + if (DBG) log("no live requests for " + nai.name() + "; disconnecting"); + teardownUnneededNetwork(nai); + } else { + wasKept = true; + } + mNetworkForRequestId.remove(nri.request.requestId); + } + + // TODO: remove this code once we know that the Slog.wtf is never hit. + // // Find all networks that are satisfying this request and remove the request // from their request lists. // TODO - it's my understanding that for a request there is only a single // network satisfying it, so this loop is wasteful - boolean wasKept = false; - for (NetworkAgentInfo nai : mNetworkAgentInfos.values()) { - if (nai.isSatisfyingRequest(nri.request.requestId)) { - nai.removeRequest(nri.request.requestId); - if (VDBG) { - log(" Removing from current network " + nai.name() + - ", leaving " + nai.numNetworkRequests() + " requests."); - } - if (unneeded(nai)) { - if (DBG) log("no live requests for " + nai.name() + "; disconnecting"); - teardownUnneededNetwork(nai); - } else { - // suspect there should only be one pass through here - // but if any were kept do the check below - wasKept |= true; - } + for (NetworkAgentInfo otherNai : mNetworkAgentInfos.values()) { + if (otherNai.isSatisfyingRequest(nri.request.requestId) && otherNai != nai) { + Slog.wtf(TAG, "Request " + nri.request + " satisfied by " + + otherNai.name() + ", but mNetworkAgentInfos says " + + (nai != null ? nai.name() : "null")); } } - NetworkAgentInfo nai = mNetworkForRequestId.get(nri.request.requestId); - if (nai != null) { - mNetworkForRequestId.remove(nri.request.requestId); - } // Maintain the illusion. When this request arrived, we might have pretended // that a network connected to serve it, even though the network was already // connected. Now that this request has gone away, we might have to pretend @@ -2512,7 +2545,7 @@ public class ConnectivityService extends IConnectivityManager.Stub } } } - callCallbackForRequest(nri, null, ConnectivityManager.CALLBACK_RELEASED); + callCallbackForRequest(nri, null, ConnectivityManager.CALLBACK_RELEASED, 0); } } @@ -4499,7 +4532,7 @@ public class ConnectivityService extends IConnectivityManager.Stub } private void callCallbackForRequest(NetworkRequestInfo nri, - NetworkAgentInfo networkAgent, int notificationType) { + NetworkAgentInfo networkAgent, int notificationType, int arg1) { if (nri.messenger == null) return; // Default request has no msgr Bundle bundle = new Bundle(); bundle.putParcelable(NetworkRequest.class.getSimpleName(), @@ -4511,7 +4544,7 @@ public class ConnectivityService extends IConnectivityManager.Stub } switch (notificationType) { case ConnectivityManager.CALLBACK_LOSING: { - msg.arg1 = 30 * 1000; // TODO - read this from NetworkMonitor + msg.arg1 = arg1; break; } case ConnectivityManager.CALLBACK_CAP_CHANGED: { @@ -4558,7 +4591,14 @@ public class ConnectivityService extends IConnectivityManager.Stub return; } if (DBG) log("handleLingerComplete for " + oldNetwork.name()); - teardownUnneededNetwork(oldNetwork); + + // If we get here it means that the last linger timeout for this network expired. So there + // must be no other active linger timers, and we must stop lingering. + oldNetwork.clearLingerState(); + + if (unneeded(oldNetwork)) { + teardownUnneededNetwork(oldNetwork); + } } private void makeDefault(NetworkAgentInfo newNetwork) { @@ -4603,7 +4643,7 @@ public class ConnectivityService extends IConnectivityManager.Stub // performed to tear down unvalidated networks that have no chance (i.e. even if // validated) of becoming the highest scoring network. private void rematchNetworkAndRequests(NetworkAgentInfo newNetwork, - ReapUnvalidatedNetworks reapUnvalidatedNetworks) { + ReapUnvalidatedNetworks reapUnvalidatedNetworks, long now) { if (!newNetwork.everConnected) return; boolean keep = newNetwork.isVPN(); boolean isNewDefault = false; @@ -4649,12 +4689,12 @@ public class ConnectivityService extends IConnectivityManager.Stub if (currentNetwork != null) { if (VDBG) log(" accepting network in place of " + currentNetwork.name()); currentNetwork.removeRequest(nri.request.requestId); - currentNetwork.networkLingered.add(nri.request); + currentNetwork.lingerRequest(nri.request, now, mLingerDelayMs); affectedNetworks.add(currentNetwork); } else { if (VDBG) log(" accepting network in place of null"); } - unlinger(newNetwork); + newNetwork.unlingerRequest(nri.request); mNetworkForRequestId.put(nri.request.requestId, newNetwork); if (!newNetwork.addRequest(nri.request)) { Slog.wtf(TAG, "BUG: " + newNetwork.name() + " already has " + nri.request); @@ -4702,23 +4742,7 @@ public class ConnectivityService extends IConnectivityManager.Stub // a) be requested and b) change is NET_CAPABILITY_TRUSTED, // so this code is only incorrect for a network that loses // the TRUSTED capability, which is a rare case. - callCallbackForRequest(nri, newNetwork, ConnectivityManager.CALLBACK_LOST); - } - } - // Linger any networks that are no longer needed. - for (NetworkAgentInfo nai : affectedNetworks) { - if (nai.lingering) { - // Already lingered. Nothing to do. This can only happen if "nai" is in - // "affectedNetworks" twice. The reasoning being that to get added to - // "affectedNetworks", "nai" must have been satisfying a NetworkRequest - // (i.e. not lingered) so it could have only been lingered by this loop. - // unneeded(nai) will be false and we'll call unlinger() below which would - // be bad, so handle it here. - } else if (unneeded(nai)) { - linger(nai); - } else { - // Clear nai.networkLingered we might have added above. - unlinger(nai); + callCallbackForRequest(nri, newNetwork, ConnectivityManager.CALLBACK_LOST, 0); } } if (isNewDefault) { @@ -4743,6 +4767,15 @@ public class ConnectivityService extends IConnectivityManager.Stub // before LegacyTypeTracker sends legacy broadcasts for (NetworkRequestInfo nri : addedRequests) notifyNetworkCallback(newNetwork, nri); + // Linger any networks that are no longer needed. This should be done after sending the + // available callback for newNetwork. + for (NetworkAgentInfo nai : affectedNetworks) { + updateLingerState(nai, now); + } + // Possibly unlinger newNetwork. Unlingering a network does not send any callbacks so it + // does not need to be done in any particular order. + updateLingerState(newNetwork, now); + if (isNewDefault) { // Maintain the illusion: since the legacy API only // understands one network at a time, we must pretend @@ -4808,8 +4841,19 @@ public class ConnectivityService extends IConnectivityManager.Stub if (reapUnvalidatedNetworks == ReapUnvalidatedNetworks.REAP) { for (NetworkAgentInfo nai : mNetworkAgentInfos.values()) { if (unneeded(nai)) { - if (DBG) log("Reaping " + nai.name()); - teardownUnneededNetwork(nai); + if (nai.getLingerExpiry() > 0) { + // This network has active linger timers and no requests, but is not + // lingering. Linger it. + // + // One way (the only way?) this can happen if this network is unvalidated + // and became unneeded due to another network improving its score to the + // point where this network will no longer be able to satisfy any requests + // even if it validates. + updateLingerState(nai, now); + } else { + if (DBG) log("Reaping " + nai.name()); + teardownUnneededNetwork(nai); + } } } } @@ -4836,8 +4880,9 @@ public class ConnectivityService extends IConnectivityManager.Stub // Optimization: Only reprocess "changed" if its score improved. This is safe because it // can only add more NetworkRequests satisfied by "changed", and this is exactly what // rematchNetworkAndRequests() handles. + final long now = SystemClock.elapsedRealtime(); if (changed != null && oldScore < changed.getCurrentScore()) { - rematchNetworkAndRequests(changed, ReapUnvalidatedNetworks.REAP); + rematchNetworkAndRequests(changed, ReapUnvalidatedNetworks.REAP, now); } else { final NetworkAgentInfo[] nais = mNetworkAgentInfos.values().toArray( new NetworkAgentInfo[mNetworkAgentInfos.size()]); @@ -4851,7 +4896,8 @@ public class ConnectivityService extends IConnectivityManager.Stub // is complete could incorrectly teardown a network that hasn't yet been // rematched. (nai != nais[nais.length-1]) ? ReapUnvalidatedNetworks.DONT_REAP - : ReapUnvalidatedNetworks.REAP); + : ReapUnvalidatedNetworks.REAP, + now); } } } @@ -4961,7 +5007,8 @@ public class ConnectivityService extends IConnectivityManager.Stub updateSignalStrengthThresholds(networkAgent, "CONNECT", null); // Consider network even though it is not yet validated. - rematchNetworkAndRequests(networkAgent, ReapUnvalidatedNetworks.REAP); + final long now = SystemClock.elapsedRealtime(); + rematchNetworkAndRequests(networkAgent, ReapUnvalidatedNetworks.REAP, now); // This has to happen after matching the requests, because callbacks are just requests. notifyNetworkCallbacks(networkAgent, ConnectivityManager.CALLBACK_PRECHECK); @@ -5009,14 +5056,8 @@ public class ConnectivityService extends IConnectivityManager.Stub // notify only this one new request of the current state protected void notifyNetworkCallback(NetworkAgentInfo nai, NetworkRequestInfo nri) { int notifyType = ConnectivityManager.CALLBACK_AVAILABLE; - // TODO - read state from monitor to decide what to send. -// if (nai.networkMonitor.isLingering()) { -// notifyType = NetworkCallbacks.LOSING; -// } else if (nai.networkMonitor.isEvaluating()) { -// notifyType = NetworkCallbacks.callCallbackForRequest(request, nai, notifyType); -// } if (nri.mPendingIntent == null) { - callCallbackForRequest(nri, nai, notifyType); + callCallbackForRequest(nri, nai, notifyType, 0); } else { sendPendingIntentForRequest(nri, nai, notifyType); } @@ -5068,20 +5109,24 @@ public class ConnectivityService extends IConnectivityManager.Stub } } - protected void notifyNetworkCallbacks(NetworkAgentInfo networkAgent, int notifyType) { + protected void notifyNetworkCallbacks(NetworkAgentInfo networkAgent, int notifyType, int arg1) { if (VDBG) log("notifyType " + notifyTypeToName(notifyType) + " for " + networkAgent.name()); for (int i = 0; i < networkAgent.numNetworkRequests(); i++) { NetworkRequest nr = networkAgent.requestAt(i); NetworkRequestInfo nri = mNetworkRequests.get(nr); if (VDBG) log(" sending notification for " + nr); if (nri.mPendingIntent == null) { - callCallbackForRequest(nri, networkAgent, notifyType); + callCallbackForRequest(nri, networkAgent, notifyType, arg1); } else { sendPendingIntentForRequest(nri, networkAgent, notifyType); } } } + protected void notifyNetworkCallbacks(NetworkAgentInfo networkAgent, int notifyType) { + notifyNetworkCallbacks(networkAgent, notifyType, 0); + } + private String notifyTypeToName(int notifyType) { switch (notifyType) { case ConnectivityManager.CALLBACK_PRECHECK: return "PRECHECK"; @@ -5212,6 +5257,11 @@ public class ConnectivityService extends IConnectivityManager.Stub return new NetworkMonitor(context, handler, nai, defaultRequest); } + @VisibleForTesting + public WakeupMessage makeWakeupMessage(Context c, Handler h, String s, int cmd, Object obj) { + return new WakeupMessage(c, h, s, cmd, 0, 0, obj); + } + private void logDefaultNetworkEvent(NetworkAgentInfo newNai, NetworkAgentInfo prevNai) { int newNetid = NETID_UNSET; int prevNetid = NETID_UNSET; diff --git a/services/core/java/com/android/server/LockSettingsService.java b/services/core/java/com/android/server/LockSettingsService.java index d64fe32cca55..85eee2bb27e2 100644 --- a/services/core/java/com/android/server/LockSettingsService.java +++ b/services/core/java/com/android/server/LockSettingsService.java @@ -300,7 +300,8 @@ public class LockSettingsService extends ILockSettings.Stub { for (int i = 0; i < users.size(); i++) { UserInfo user = users.get(i); UserHandle userHandle = user.getUserHandle(); - if (!mUserManager.isUserUnlockingOrUnlocked(userHandle)) { + final boolean isSecure = mStorage.hasPassword(user.id) || mStorage.hasPattern(user.id); + if (isSecure && !mUserManager.isUserUnlockingOrUnlocked(userHandle)) { if (!user.isManagedProfile()) { showEncryptionNotification(userHandle); } else { @@ -407,7 +408,9 @@ public class LockSettingsService extends ILockSettings.Stub { List<UserInfo> profiles = mUserManager.getProfiles(userId); for (int i = 0; i < profiles.size(); i++) { UserInfo profile = profiles.get(i); - if (profile.isManagedProfile()) { + final boolean isSecure = + mStorage.hasPassword(profile.id) || mStorage.hasPattern(profile.id); + if (isSecure && profile.isManagedProfile()) { UserHandle userHandle = profile.getUserHandle(); if (!mUserManager.isUserUnlockingOrUnlocked(userHandle) && !mUserManager.isQuietModeEnabled(userHandle)) { diff --git a/services/core/java/com/android/server/PinnerService.java b/services/core/java/com/android/server/PinnerService.java index e63f5365bff8..eaf317a46bc1 100644 --- a/services/core/java/com/android/server/PinnerService.java +++ b/services/core/java/com/android/server/PinnerService.java @@ -28,6 +28,10 @@ import android.util.EventLog; import android.util.Slog; import android.os.Binder; import android.os.Build; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import android.os.Process; import android.provider.MediaStore; import android.system.ErrnoException; import android.system.Os; @@ -35,6 +39,7 @@ import android.system.OsConstants; import android.system.StructStat; import com.android.internal.app.ResolverActivity; +import com.android.internal.os.BackgroundThread; import dalvik.system.VMRuntime; @@ -64,6 +69,8 @@ public final class PinnerService extends SystemService { private final long MAX_CAMERA_PIN_SIZE = 50 * (1 << 20); //50MB max + private PinnerHandler mPinnerHandler = null; + public PinnerService(Context context) { super(context); @@ -71,6 +78,7 @@ public final class PinnerService extends SystemService { mContext = context; mShouldPinCamera = context.getResources().getBoolean( com.android.internal.R.bool.config_pinnerCameraApp); + mPinnerHandler = new PinnerHandler(BackgroundThread.get().getLooper()); } @Override @@ -80,22 +88,8 @@ public final class PinnerService extends SystemService { } mBinderService = new BinderService(); publishBinderService("pinner", mBinderService); - - // Files to pin come from the overlay and can be specified per-device config - String[] filesToPin = mContext.getResources().getStringArray( - com.android.internal.R.array.config_defaultPinnerServiceFiles); - // Continue trying to pin remaining files even if there is a failure - for (int i = 0; i < filesToPin.length; i++){ - PinnedFile pf = pinFile(filesToPin[i], 0, 0, 0); - if (pf != null) { - mPinnedFiles.add(pf); - if (DEBUG) { - Slog.i(TAG, "Pinned file = " + pf.mFilename); - } - } else { - Slog.e(TAG, "Failed to pin file = " + filesToPin[i]); - } - } + mPinnerHandler.sendMessage( + mPinnerHandler.obtainMessage(PinnerHandler.PIN_ONSTART_MSG)); } /** @@ -106,27 +100,57 @@ public final class PinnerService extends SystemService { */ @Override public void onUnlockUser(int userHandle) { - handlePin(userHandle); + mPinnerHandler.sendMessage( + mPinnerHandler.obtainMessage(PinnerHandler.PIN_CAMERA_MSG, userHandle, 0)); } /** - * Pin camera on user switch. - * If more than one user is using the device - * each user may set a different preference for the camera app. - * Make sure that user's preference is pinned into memory. - */ + * Pin camera on user switch. + * If more than one user is using the device + * each user may set a different preference for the camera app. + * Make sure that user's preference is pinned into memory. + */ @Override public void onSwitchUser(int userHandle) { - handlePin(userHandle); + mPinnerHandler.sendMessage( + mPinnerHandler.obtainMessage(PinnerHandler.PIN_CAMERA_MSG, userHandle, 0)); } - private void handlePin(int userHandle) { + /** + * Handler for on start pinning message + */ + private void handlePinOnStart() { + // Files to pin come from the overlay and can be specified per-device config + String[] filesToPin = mContext.getResources().getStringArray( + com.android.internal.R.array.config_defaultPinnerServiceFiles); + synchronized(this) { + // Continue trying to pin remaining files even if there is a failure + for (int i = 0; i < filesToPin.length; i++){ + PinnedFile pf = pinFile(filesToPin[i], 0, 0, 0); + if (pf != null) { + mPinnedFiles.add(pf); + if (DEBUG) { + Slog.i(TAG, "Pinned file = " + pf.mFilename); + } + } else { + Slog.e(TAG, "Failed to pin file = " + filesToPin[i]); + } + } + } + } + + /** + * Handler for camera pinning message + */ + private void handlePinCamera(int userHandle) { if (mShouldPinCamera) { - boolean success = pinCamera(userHandle); - if (!success) { - //this is not necessarily an error - if (DEBUG) { - Slog.v(TAG, "Failed to pin camera."); + synchronized(this) { + boolean success = pinCamera(userHandle); + if (!success) { + //this is not necessarily an error + if (DEBUG) { + Slog.v(TAG, "Failed to pin camera."); + } } } } @@ -316,11 +340,13 @@ public final class PinnerService extends SystemService { protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DUMP, TAG); pw.println("Pinned Files:"); - for (int i = 0; i < mPinnedFiles.size(); i++) { - pw.println(mPinnedFiles.get(i).mFilename); - } - for (int i = 0; i < mPinnedCameraFiles.size(); i++) { - pw.println(mPinnedCameraFiles.get(i).mFilename); + synchronized(this) { + for (int i = 0; i < mPinnedFiles.size(); i++) { + pw.println(mPinnedFiles.get(i).mFilename); + } + for (int i = 0; i < mPinnedCameraFiles.size(); i++) { + pw.println(mPinnedCameraFiles.get(i).mFilename); + } } } } @@ -336,4 +362,35 @@ public final class PinnerService extends SystemService { mFilename = filename; } } + + final class PinnerHandler extends Handler { + static final int PIN_CAMERA_MSG = 4000; + static final int PIN_ONSTART_MSG = 4001; + + public PinnerHandler(Looper looper) { + super(looper, null, true); + } + + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + + case PIN_CAMERA_MSG: + { + handlePinCamera(msg.arg1); + } + break; + + case PIN_ONSTART_MSG: + { + handlePinOnStart(); + } + break; + + default: + super.handleMessage(msg); + } + } + } + } diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index e5579e29c208..9fcbf53e0080 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -6661,8 +6661,7 @@ public final class ActivityManagerService extends ActivityManagerNative @Override public void showBootMessage(final CharSequence msg, final boolean always) { if (Binder.getCallingUid() != Process.myUid()) { - // These days only the core system can call this, so apps can't get in - // the way of what we show about running them. + throw new SecurityException(); } mWindowManager.showBootMessage(msg, always); } diff --git a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java index 15b872d1b332..7a25df68599f 100644 --- a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java +++ b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java @@ -28,14 +28,21 @@ import android.net.NetworkRequest; import android.net.NetworkState; import android.os.Handler; import android.os.Messenger; +import android.os.SystemClock; +import android.util.Log; import android.util.SparseArray; import com.android.internal.util.AsyncChannel; +import com.android.internal.util.WakeupMessage; import com.android.server.ConnectivityService; import com.android.server.connectivity.NetworkMonitor; +import java.io.PrintWriter; import java.util.ArrayList; import java.util.Comparator; +import java.util.Objects; +import java.util.SortedSet; +import java.util.TreeSet; /** * A bag class used by ConnectivityService for holding a collection of most recent @@ -143,12 +150,69 @@ public class NetworkAgentInfo implements Comparable<NetworkAgentInfo> { // Whether a captive portal was found during the last network validation attempt. public boolean lastCaptivePortalDetected; - // Indicates whether the network is lingering. Networks are lingered when they become unneeded - // as a result of their NetworkRequests being satisfied by a different network, so as to allow - // communication to wrap up before the network is taken down. This usually only happens to the - // default network. Lingering ends with either the linger timeout expiring and the network - // being taken down, or the network satisfying a request again. - public boolean lingering; + // Networks are lingered when they become unneeded as a result of their NetworkRequests being + // satisfied by a higher-scoring network. so as to allow communication to wrap up before the + // network is taken down. This usually only happens to the default network. Lingering ends with + // either the linger timeout expiring and the network being taken down, or the network + // satisfying a request again. + public static class LingerTimer implements Comparable<LingerTimer> { + public final NetworkRequest request; + public final long expiryMs; + + public LingerTimer(NetworkRequest request, long expiryMs) { + this.request = request; + this.expiryMs = expiryMs; + } + public boolean equals(Object o) { + if (!(o instanceof LingerTimer)) return false; + LingerTimer other = (LingerTimer) o; + return (request.requestId == other.request.requestId) && (expiryMs == other.expiryMs); + } + public int hashCode() { + return Objects.hash(request.requestId, expiryMs); + } + public int compareTo(LingerTimer other) { + return (expiryMs != other.expiryMs) ? + Long.compare(expiryMs, other.expiryMs) : + Integer.compare(request.requestId, other.request.requestId); + } + public String toString() { + return String.format("%s, expires %dms", request.toString(), + expiryMs - SystemClock.elapsedRealtime()); + } + } + + /** + * Inform ConnectivityService that the network LINGER period has + * expired. + * obj = this NetworkAgentInfo + */ + public static final int EVENT_NETWORK_LINGER_COMPLETE = 1001; + + // All linger timers for this network, sorted by expiry time. A linger timer is added whenever + // a request is moved to a network with a better score, regardless of whether the network is or + // was lingering or not. + // TODO: determine if we can replace this with a smaller or unsorted data structure. (e.g., + // SparseLongArray) combined with the timestamp of when the last timer is scheduled to fire. + private final SortedSet<LingerTimer> mLingerTimers = new TreeSet<>(); + + // For fast lookups. Indexes into mLingerTimers by request ID. + private final SparseArray<LingerTimer> mLingerTimerForRequest = new SparseArray<>(); + + // Linger expiry timer. Armed whenever mLingerTimers is non-empty, regardless of whether the + // network is lingering or not. Always set to the expiry of the LingerTimer that expires last. + // When the timer fires, all linger state is cleared, and if the network has no requests, it is + // torn down. + private WakeupMessage mLingerMessage; + + // Linger expiry. Holds the expiry time of the linger timer, or 0 if the timer is not armed. + private long mLingerExpiryMs; + + // Whether the network is lingering or not. Must be maintained separately from the above because + // it depends on the state of other networks and requests, which only ConnectivityService knows. + // (Example: we don't linger a network if it would become the best for a NetworkRequest if it + // validated). + private boolean mLingering; // This represents the last score received from the NetworkAgent. private int currentScore; @@ -165,8 +229,6 @@ public class NetworkAgentInfo implements Comparable<NetworkAgentInfo> { private final SparseArray<NetworkRequest> mNetworkRequests = new SparseArray<>(); // The list of NetworkRequests that this Network previously satisfied with the highest // score. A non-empty list indicates that if this Network was validated it is lingered. - // NOTE: This list is only used for debugging. - public final ArrayList<NetworkRequest> networkLingered = new ArrayList<NetworkRequest>(); // How many of the satisfied requests are actual requests and not listens. private int mNumRequestNetworkRequests = 0; @@ -176,6 +238,12 @@ public class NetworkAgentInfo implements Comparable<NetworkAgentInfo> { // Used by ConnectivityService to keep track of 464xlat. public Nat464Xlat clatd; + private static final String TAG = ConnectivityService.class.getSimpleName(); + private static final boolean VDBG = false; + private final ConnectivityService mConnService; + private final Context mContext; + private final Handler mHandler; + public NetworkAgentInfo(Messenger messenger, AsyncChannel ac, Network net, NetworkInfo info, LinkProperties lp, NetworkCapabilities nc, int score, Context context, Handler handler, NetworkMisc misc, NetworkRequest defaultRequest, ConnectivityService connService) { @@ -186,7 +254,10 @@ public class NetworkAgentInfo implements Comparable<NetworkAgentInfo> { linkProperties = lp; networkCapabilities = nc; currentScore = score; - networkMonitor = connService.createNetworkMonitor(context, handler, this, defaultRequest); + mConnService = connService; + mContext = context; + mHandler = handler; + networkMonitor = mConnService.createNetworkMonitor(context, handler, this, defaultRequest); networkMisc = misc; } @@ -213,8 +284,12 @@ public class NetworkAgentInfo implements Comparable<NetworkAgentInfo> { */ public void removeRequest(int requestId) { NetworkRequest existing = mNetworkRequests.get(requestId); - if (existing != null && existing.isRequest()) mNumRequestNetworkRequests--; + if (existing == null) return; mNetworkRequests.remove(requestId); + if (existing.isRequest()) { + mNumRequestNetworkRequests--; + unlingerRequest(existing); + } } /** @@ -316,13 +391,100 @@ public class NetworkAgentInfo implements Comparable<NetworkAgentInfo> { } } + /** + * Sets the specified request to linger on this network for the specified time. Called by + * ConnectivityService when the request is moved to another network with a higher score. + */ + public void lingerRequest(NetworkRequest request, long now, long duration) { + if (mLingerTimerForRequest.get(request.requestId) != null) { + // Cannot happen. Once a request is lingering on a particular network, we cannot + // re-linger it unless that network becomes the best for that request again, in which + // case we should have unlingered it. + Log.wtf(TAG, this.name() + ": request " + request.requestId + " already lingered"); + } + final long expiryMs = now + duration; + LingerTimer timer = new LingerTimer(request, expiryMs); + if (VDBG) Log.d(TAG, "Adding LingerTimer " + timer + " to " + this.name()); + mLingerTimers.add(timer); + mLingerTimerForRequest.put(request.requestId, timer); + } + + /** + * Cancel lingering. Called by ConnectivityService when a request is added to this network. + */ + public void unlingerRequest(NetworkRequest request) { + LingerTimer timer = mLingerTimerForRequest.get(request.requestId); + if (timer != null) { + if (VDBG) Log.d(TAG, "Removing LingerTimer " + timer + " from " + this.name()); + mLingerTimers.remove(timer); + mLingerTimerForRequest.remove(request.requestId); + } + } + + public long getLingerExpiry() { + return mLingerExpiryMs; + } + + public void updateLingerTimer() { + long newExpiry = mLingerTimers.isEmpty() ? 0 : mLingerTimers.last().expiryMs; + if (newExpiry == mLingerExpiryMs) return; + + // Even if we're going to reschedule the timer, cancel it first. This is because the + // semantics of WakeupMessage guarantee that if cancel is called then the alarm will + // never call its callback (handleLingerComplete), even if it has already fired. + // WakeupMessage makes no such guarantees about rescheduling a message, so if mLingerMessage + // has already been dispatched, rescheduling to some time in the future it won't stop it + // from calling its callback immediately. + if (mLingerMessage != null) { + mLingerMessage.cancel(); + mLingerMessage = null; + } + + if (newExpiry > 0) { + mLingerMessage = mConnService.makeWakeupMessage( + mContext, mHandler, + "NETWORK_LINGER_COMPLETE." + network.netId, + EVENT_NETWORK_LINGER_COMPLETE, this); + mLingerMessage.schedule(newExpiry); + } + + mLingerExpiryMs = newExpiry; + } + + public void linger() { + mLingering = true; + } + + public void unlinger() { + mLingering = false; + } + + public boolean isLingering() { + return mLingering; + } + + public void clearLingerState() { + if (mLingerMessage != null) { + mLingerMessage.cancel(); + mLingerMessage = null; + } + mLingerTimers.clear(); + mLingerTimerForRequest.clear(); + updateLingerTimer(); // Sets mLingerExpiryMs, cancels and nulls out mLingerMessage. + mLingering = false; + } + + public void dumpLingerTimers(PrintWriter pw) { + for (LingerTimer timer : mLingerTimers) { pw.println(timer); } + } + public String toString() { return "NetworkAgentInfo{ ni{" + networkInfo + "} " + "network{" + network + "} nethandle{" + network.getNetworkHandle() + "} " + "lp{" + linkProperties + "} " + "nc{" + networkCapabilities + "} Score{" + getCurrentScore() + "} " + "everValidated{" + everValidated + "} lastValidated{" + lastValidated + "} " + - "created{" + created + "} lingering{" + lingering + "} " + + "created{" + created + "} lingering{" + isLingering() + "} " + "explicitlySelected{" + networkMisc.explicitlySelected + "} " + "acceptUnvalidated{" + networkMisc.acceptUnvalidated + "} " + "everCaptivePortalDetected{" + everCaptivePortalDetected + "} " + diff --git a/services/core/java/com/android/server/connectivity/NetworkMonitor.java b/services/core/java/com/android/server/connectivity/NetworkMonitor.java index eeddff53e8ce..92c4577f3e0c 100644 --- a/services/core/java/com/android/server/connectivity/NetworkMonitor.java +++ b/services/core/java/com/android/server/connectivity/NetworkMonitor.java @@ -132,31 +132,6 @@ public class NetworkMonitor extends StateMachine { public static final int EVENT_NETWORK_TESTED = BASE + 2; /** - * Inform NetworkMonitor to linger a network. The Monitor should - * start a timer and/or start watching for zero live connections while - * moving towards LINGER_COMPLETE. After the Linger period expires - * (or other events mark the end of the linger state) the LINGER_COMPLETE - * event should be sent and the network will be shut down. If a - * CMD_NETWORK_CONNECTED happens before the LINGER completes - * it indicates further desire to keep the network alive and so - * the LINGER is aborted. - */ - public static final int CMD_NETWORK_LINGER = BASE + 3; - - /** - * Message to self indicating linger delay has expired. - * arg1 = Token to ignore old messages. - */ - private static final int CMD_LINGER_EXPIRED = BASE + 4; - - /** - * Inform ConnectivityService that the network LINGER period has - * expired. - * obj = NetworkAgentInfo - */ - public static final int EVENT_NETWORK_LINGER_COMPLETE = BASE + 5; - - /** * Message to self indicating it's time to evaluate a network's connectivity. * arg1 = Token to ignore old messages. */ @@ -204,12 +179,6 @@ public class NetworkMonitor extends StateMachine { */ private static final int CMD_CAPTIVE_PORTAL_RECHECK = BASE + 12; - private static final String LINGER_DELAY_PROPERTY = "persist.netmon.linger"; - // Default to 30s linger time-out. Modifyable only for testing. - private static int DEFAULT_LINGER_DELAY_MS = 30000; - private final int mLingerDelayMs; - private int mLingerToken = 0; - // Start mReevaluateDelayMs at this value and double. private static final int INITIAL_REEVALUATE_DELAY_MS = 1000; private static final int MAX_REEVALUATE_DELAY_MS = 10*60*1000; @@ -248,7 +217,6 @@ public class NetworkMonitor extends StateMachine { private final State mMaybeNotifyState = new MaybeNotifyState(); private final State mEvaluatingState = new EvaluatingState(); private final State mCaptivePortalState = new CaptivePortalState(); - private final State mLingeringState = new LingeringState(); private CustomIntentReceiver mLaunchCaptivePortalAppBroadcastReceiver = null; @@ -275,11 +243,8 @@ public class NetworkMonitor extends StateMachine { addState(mMaybeNotifyState, mDefaultState); addState(mEvaluatingState, mMaybeNotifyState); addState(mCaptivePortalState, mMaybeNotifyState); - addState(mLingeringState, mDefaultState); setInitialState(mDefaultState); - mLingerDelayMs = SystemProperties.getInt(LINGER_DELAY_PROPERTY, DEFAULT_LINGER_DELAY_MS); - mIsCaptivePortalCheckEnabled = Settings.Global.getInt(mContext.getContentResolver(), Settings.Global.CAPTIVE_PORTAL_DETECTION_ENABLED, 1) == 1; mUseHttps = Settings.Global.getInt(mContext.getContentResolver(), @@ -308,10 +273,6 @@ public class NetworkMonitor extends StateMachine { @Override public boolean processMessage(Message message) { switch (message.what) { - case CMD_NETWORK_LINGER: - log("Lingering"); - transitionTo(mLingeringState); - return HANDLED; case CMD_NETWORK_CONNECTED: logNetworkEvent(NetworkEvent.NETWORK_CONNECTED); transitionTo(mEvaluatingState); @@ -617,72 +578,6 @@ public class NetworkMonitor extends StateMachine { } } - // Being in the LingeringState State indicates a Network's validated bit is true and it once - // was the highest scoring Network satisfying a particular NetworkRequest, but since then - // another Network satisfied the NetworkRequest with a higher score and hence this Network - // is "lingered" for a fixed period of time before it is disconnected. This period of time - // allows apps to wrap up communication and allows for seamless reactivation if the other - // higher scoring Network happens to disconnect. - private class LingeringState extends State { - private static final String ACTION_LINGER_EXPIRED = "android.net.netmon.lingerExpired"; - - private WakeupMessage mWakeupMessage; - - @Override - public void enter() { - mEvaluationTimer.reset(); - final String cmdName = ACTION_LINGER_EXPIRED + "." + mNetId; - mWakeupMessage = makeWakeupMessage(mContext, getHandler(), cmdName, CMD_LINGER_EXPIRED); - long wakeupTime = SystemClock.elapsedRealtime() + mLingerDelayMs; - mWakeupMessage.schedule(wakeupTime); - } - - @Override - public boolean processMessage(Message message) { - switch (message.what) { - case CMD_NETWORK_CONNECTED: - log("Unlingered"); - // If already validated, go straight to validated state. - if (mNetworkAgentInfo.lastValidated) { - transitionTo(mValidatedState); - return HANDLED; - } - return NOT_HANDLED; - case CMD_LINGER_EXPIRED: - mConnectivityServiceHandler.sendMessage( - obtainMessage(EVENT_NETWORK_LINGER_COMPLETE, mNetworkAgentInfo)); - return HANDLED; - case CMD_FORCE_REEVALUATION: - // Ignore reevaluation attempts when lingering. A reevaluation could result - // in a transition to the validated state which would abort the linger - // timeout. Lingering is the result of score assessment; validity is - // irrelevant. - return HANDLED; - case CMD_CAPTIVE_PORTAL_APP_FINISHED: - // Ignore user network determination as this could abort linger timeout. - // Networks are only lingered once validated because: - // - Unvalidated networks are never lingered (see rematchNetworkAndRequests). - // - Once validated, a Network's validated bit is never cleared. - // Since networks are only lingered after being validated a user's - // determination will not change the death sentence that lingering entails: - // - If the user wants to use the network or bypasses the captive portal, - // the network's score will not be increased beyond its current value - // because it is already validated. Without a score increase there is no - // chance of reactivation (i.e. aborting linger timeout). - // - If the user does not want the network, lingering will disconnect the - // network anyhow. - return HANDLED; - default: - return NOT_HANDLED; - } - } - - @Override - public void exit() { - mWakeupMessage.cancel(); - } - } - private static String getCaptivePortalServerUrl(Context context, boolean isHttps) { String server = Settings.Global.getString(context.getContentResolver(), Settings.Global.CAPTIVE_PORTAL_SERVER); @@ -996,20 +891,6 @@ public class NetworkMonitor extends StateMachine { PERMISSION_ACCESS_NETWORK_CONDITIONS); } - // Allow tests to override linger time. - @VisibleForTesting - public static void SetDefaultLingerTime(int time_ms) { - if (Process.myUid() == Process.SYSTEM_UID) { - throw new SecurityException("SetDefaultLingerTime only for internal testing."); - } - DEFAULT_LINGER_DELAY_MS = time_ms; - } - - @VisibleForTesting - protected WakeupMessage makeWakeupMessage(Context c, Handler h, String s, int i) { - return new WakeupMessage(c, h, s, i); - } - private void logNetworkEvent(int evtype) { mMetricsLog.log(new NetworkEvent(mNetId, evtype)); } diff --git a/services/core/java/com/android/server/fingerprint/FingerprintService.java b/services/core/java/com/android/server/fingerprint/FingerprintService.java index be8e300c9352..1066434f10f1 100644 --- a/services/core/java/com/android/server/fingerprint/FingerprintService.java +++ b/services/core/java/com/android/server/fingerprint/FingerprintService.java @@ -72,6 +72,7 @@ import java.io.PrintWriter; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; +import java.util.HashMap; import java.util.List; /** @@ -90,10 +91,17 @@ public class FingerprintService extends SystemService implements IBinder.DeathRe private static final String ACTION_LOCKOUT_RESET = "com.android.server.fingerprint.ACTION_LOCKOUT_RESET"; + private class PerformanceStats { + int accept; // number of accepted fingerprints + int reject; // number of rejected fingerprints + int acquire; // total number of acquisitions. Should be >= accept+reject due to poor image + // acquisition in some cases (too fast, too slow, dirty sensor, etc.) + int lockout; // total number of lockouts + } + private final ArrayList<FingerprintServiceLockoutResetMonitor> mLockoutMonitors = new ArrayList<>(); private final AppOpsManager mAppOps; - private static final long FAIL_LOCKOUT_TIMEOUT_MS = 30*1000; private static final int MAX_FAILED_ATTEMPTS = 5; private static final long CANCEL_TIMEOUT_LIMIT = 3000; // max wait for onCancel() from HAL,in ms @@ -110,6 +118,15 @@ public class FingerprintService extends SystemService implements IBinder.DeathRe private ClientMonitor mCurrentClient; private ClientMonitor mPendingClient; private long mCurrentAuthenticatorId; + private PerformanceStats mPerformanceStats; + + // Normal fingerprint authentications are tracked by mPerformanceMap. + private HashMap<Integer, PerformanceStats> mPerformanceMap + = new HashMap<Integer, PerformanceStats>(); + + // Transactions that make use of CryptoObjects are tracked by mCryptoPerformaceMap. + private HashMap<Integer, PerformanceStats> mCryptoPerformanceMap + = new HashMap<Integer, PerformanceStats>(); private Handler mHandler = new Handler() { @Override @@ -246,6 +263,11 @@ public class FingerprintService extends SystemService implements IBinder.DeathRe if (client != null && client.onAuthenticated(fingerId, groupId)) { removeClient(client); } + if (fingerId != 0) { + mPerformanceStats.accept++; + } else { + mPerformanceStats.reject++; + } } protected void handleAcquired(long deviceId, int acquiredInfo) { @@ -253,6 +275,11 @@ public class FingerprintService extends SystemService implements IBinder.DeathRe if (client != null && client.onAcquired(acquiredInfo)) { removeClient(client); } + if (mPerformanceStats != null && !inLockoutMode() + && client instanceof AuthenticationClient) { + // ignore enrollment acquisitions or acquisitions when we're locked out + mPerformanceStats.acquire++; + } } protected void handleEnrollResult(long deviceId, int fingerId, int groupId, int remaining) { @@ -505,6 +532,9 @@ public class FingerprintService extends SystemService implements IBinder.DeathRe @Override public boolean handleFailedAttempt() { mFailedAttempts++; + if (mFailedAttempts == MAX_FAILED_ATTEMPTS) { + mPerformanceStats.lockout++; + } if (inLockoutMode()) { // Failing multiple times will continue to push out the lockout time. scheduleLockoutReset(); @@ -742,12 +772,24 @@ public class FingerprintService extends SystemService implements IBinder.DeathRe mHandler.post(new Runnable() { @Override public void run() { - MetricsLogger.histogram(mContext, "fingerprint_token", opId != 0L ? 1 : 0); if (!canUseFingerprint(opPackageName, true /* foregroundOnly */, callingUid, pid)) { if (DEBUG) Slog.v(TAG, "authenticate(): reject " + opPackageName); return; } + + MetricsLogger.histogram(mContext, "fingerprint_token", opId != 0L ? 1 : 0); + + // Get performance stats object for this user. + HashMap<Integer, PerformanceStats> pmap + = (opId == 0) ? mPerformanceMap : mCryptoPerformanceMap; + PerformanceStats stats = pmap.get(mCurrentUserId); + if (stats == null) { + stats = new PerformanceStats(); + pmap.put(mCurrentUserId, stats); + } + mPerformanceStats = stats; + startAuthentication(token, opId, callingUserId, groupId, receiver, flags, restricted, opPackageName); } @@ -924,9 +966,21 @@ public class FingerprintService extends SystemService implements IBinder.DeathRe for (UserInfo user : UserManager.get(getContext()).getUsers()) { final int userId = user.getUserHandle().getIdentifier(); final int N = mFingerprintUtils.getFingerprintsForUser(mContext, userId).size(); + PerformanceStats stats = mPerformanceMap.get(userId); + PerformanceStats cryptoStats = mCryptoPerformanceMap.get(userId); JSONObject set = new JSONObject(); set.put("id", userId); set.put("count", N); + set.put("accept", (stats != null) ? stats.accept : 0); + set.put("reject", (stats != null) ? stats.reject : 0); + set.put("acquire", (stats != null) ? stats.acquire : 0); + set.put("lockout", (stats != null) ? stats.lockout : 0); + // cryptoStats measures statistics about secure fingerprint transactions + // (e.g. to unlock password storage, make secure purchases, etc.) + set.put("acceptCrypto", (cryptoStats != null) ? cryptoStats.accept : 0); + set.put("rejectCrypto", (cryptoStats != null) ? cryptoStats.reject : 0); + set.put("acquireCrypto", (cryptoStats != null) ? cryptoStats.acquire : 0); + set.put("lockoutCrypto", (cryptoStats != null) ? cryptoStats.lockout : 0); sets.put(set); } @@ -947,6 +1001,7 @@ public class FingerprintService extends SystemService implements IBinder.DeathRe private void updateActiveGroup(int userId, String clientPackage) { IFingerprintDaemon daemon = getFingerprintDaemon(); + if (daemon != null) { try { userId = getUserOrWorkProfileId(clientPackage, userId); diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index 1206263fb281..ec77dafb2914 100644 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -2286,6 +2286,7 @@ public class NotificationManagerService extends SystemService { .setFlag(Notification.FLAG_AUTOGROUP_SUMMARY, true) .setFlag(Notification.FLAG_GROUP_SUMMARY, true) .setColor(adjustedSbn.getNotification().color) + .setLocalOnly(true) .build(); summaryNotification.extras.putAll(extras); Intent appIntent = getContext().getPackageManager() diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index acd805591c41..84ebdd15947d 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -11947,6 +11947,12 @@ public class PackageManagerService extends IPackageManager.Stub { return false; } + if (mProtectedPackages.isPackageStateProtected(userId, packageName)) { + Slog.w(TAG, "Cannot suspend/un-suspend package \"" + packageName + + "\": protected package"); + return false; + } + return true; } diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java index bbffd328eecd..d750cbf17f66 100644 --- a/services/core/java/com/android/server/pm/UserManagerService.java +++ b/services/core/java/com/android/server/pm/UserManagerService.java @@ -38,6 +38,7 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.IntentSender; +import android.content.pm.IPackageManager; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.UserInfo; @@ -2919,10 +2920,15 @@ public class UserManagerService extends IUserManager.Stub { com.android.internal.R.string.config_demoModeLauncherComponent); if (!TextUtils.isEmpty(demoLauncher)) { ComponentName componentToEnable = ComponentName.unflattenFromString(demoLauncher); + String demoLauncherPkg = componentToEnable.getPackageName(); try { - AppGlobals.getPackageManager().setComponentEnabledSetting(componentToEnable, + final IPackageManager iPm = AppGlobals.getPackageManager(); + iPm.setComponentEnabledSetting(componentToEnable, PackageManager.COMPONENT_ENABLED_STATE_ENABLED, /* flags= */ 0, /* userId= */ userId); + iPm.setApplicationEnabledSetting(demoLauncherPkg, + PackageManager.COMPONENT_ENABLED_STATE_ENABLED, /* flags= */ 0, + /* userId= */ userId, null); } catch (RemoteException re) { // Internal, shouldn't happen } @@ -2961,6 +2967,14 @@ public class UserManagerService extends IUserManager.Stub { * number is mismatched. */ public static void enforceSerialNumber(File file, int serialNumber) throws IOException { + if (StorageManager.isFileEncryptedEmulatedOnly()) { + // When we're emulating FBE, the directory may have been chmod + // 000'ed, meaning we can't read the serial number to enforce it; + // instead of destroying the user, just log a warning. + Slog.w(LOG_TAG, "Device is emulating FBE; assuming current serial number is valid"); + return; + } + final int foundSerial = getSerialNumber(file); Slog.v(LOG_TAG, "Found " + file + " with serial number " + foundSerial); diff --git a/services/core/java/com/android/server/wm/AppTransition.java b/services/core/java/com/android/server/wm/AppTransition.java index 4553f8e07cda..2b581562d680 100644 --- a/services/core/java/com/android/server/wm/AppTransition.java +++ b/services/core/java/com/android/server/wm/AppTransition.java @@ -1057,7 +1057,7 @@ public class AppTransition implements Dump { final float thumbWidth = thumbWidthI > 0 ? thumbWidthI : 1; final int thumbHeightI = mTmpRect.height(); final float thumbHeight = thumbHeightI > 0 ? thumbHeightI : 1; - final int thumbStartX = mTmpRect.left - containingFrame.left; + final int thumbStartX = mTmpRect.left - containingFrame.left - contentInsets.left; final int thumbStartY = mTmpRect.top - containingFrame.top; switch (thumbTransitState) { diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index c42d4617b179..c9123fd2227f 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -4427,6 +4427,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { intent.putExtra(DeviceAdminReceiver.EXTRA_CHOOSE_PRIVATE_KEY_URI, uri); intent.putExtra(DeviceAdminReceiver.EXTRA_CHOOSE_PRIVATE_KEY_ALIAS, alias); intent.putExtra(DeviceAdminReceiver.EXTRA_CHOOSE_PRIVATE_KEY_RESPONSE, response); + intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND); final long id = mInjector.binderClearCallingIdentity(); try { diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index 76424d82a2b0..2236caebdc7c 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -284,6 +284,9 @@ public final class SystemServer { // we've defined it before booting further. Build.ensureFingerprintProperty(); + // Initialize Environment for the system user. + Environment.init(); + // Within the system server, it is an error to access Environment paths without // explicitly specifying a user. Environment.setUserRequired(true); @@ -724,14 +727,6 @@ public final class SystemServer { } Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER); - try { - ActivityManagerNative.getDefault().showBootMessage( - context.getResources().getText( - com.android.internal.R.string.android_upgrading_starting_apps), - false); - } catch (RemoteException e) { - } - if (mFactoryTestMode != FactoryTest.FACTORY_TEST_LOW_LEVEL) { if (!disableNonCoreServices) { traceBeginAndSlog("StartLockSettingsService"); diff --git a/services/net/java/android/net/dhcp/DhcpClient.java b/services/net/java/android/net/dhcp/DhcpClient.java index 83cfc010b4e2..ffbea9fce279 100644 --- a/services/net/java/android/net/dhcp/DhcpClient.java +++ b/services/net/java/android/net/dhcp/DhcpClient.java @@ -194,6 +194,10 @@ public class DhcpClient extends StateMachine { private long mDhcpLeaseExpiry; private DhcpResults mOffer; + // Milliseconds SystemClock timestamps used to record transition times to DhcpBoundState. + private long mLastInitEnterTime; + private long mLastBoundExitTime; + // States. private State mStoppedState = new StoppedState(); private State mDhcpState = new DhcpState(); @@ -498,13 +502,12 @@ public class DhcpClient extends StateMachine { public void enter() { if (STATE_DBG) Log.d(TAG, "Entering state " + getName()); mEnterTimeMs = SystemClock.elapsedRealtime(); - // TODO: record time for Init -> Bound and Bound -> Renewing -> Bound } @Override public void exit() { long durationMs = SystemClock.elapsedRealtime() - mEnterTimeMs; - mMetricsLog.log(new DhcpClientEvent(mIfaceName, getName(), (int) durationMs)); + logState(getName(), (int) durationMs); } private String messageName(int what) { @@ -742,6 +745,7 @@ public class DhcpClient extends StateMachine { public void enter() { super.enter(); startNewTransaction(); + mLastInitEnterTime = SystemClock.elapsedRealtime(); } protected boolean sendPacket() { @@ -866,6 +870,13 @@ public class DhcpClient extends StateMachine { } scheduleLeaseTimers(); + logTimeToBoundState(); + } + + @Override + public void exit() { + super.exit(); + mLastBoundExitTime = SystemClock.elapsedRealtime(); } @Override @@ -883,6 +894,15 @@ public class DhcpClient extends StateMachine { return NOT_HANDLED; } } + + private void logTimeToBoundState() { + long now = SystemClock.elapsedRealtime(); + if (mLastBoundExitTime > mLastInitEnterTime) { + logState(DhcpClientEvent.RENEWING_BOUND, (int)(now - mLastBoundExitTime)); + } else { + logState(DhcpClientEvent.INITIAL_BOUND, (int)(now - mLastInitEnterTime)); + } + } } abstract class DhcpReacquiringState extends PacketRetransmittingState { @@ -993,4 +1013,8 @@ public class DhcpClient extends StateMachine { private void logError(int errorCode) { mMetricsLog.log(new DhcpErrorEvent(mIfaceName, errorCode)); } + + private void logState(String name, int durationMs) { + mMetricsLog.log(new DhcpClientEvent(mIfaceName, name, durationMs)); + } } diff --git a/services/net/java/android/net/ip/IpReachabilityMonitor.java b/services/net/java/android/net/ip/IpReachabilityMonitor.java index afb644f3546b..c6da3c31fd61 100644 --- a/services/net/java/android/net/ip/IpReachabilityMonitor.java +++ b/services/net/java/android/net/ip/IpReachabilityMonitor.java @@ -129,7 +129,6 @@ import java.util.Set; * state it may be best for the link to disconnect completely and * reconnect afresh. * - * * @hide */ public class IpReachabilityMonitor { @@ -163,6 +162,8 @@ public class IpReachabilityMonitor { private int mIpWatchListVersion; @GuardedBy("mLock") private boolean mRunning; + // Time in milliseconds of the last forced probe request. + private volatile long mLastProbeTimeMs; /** * Make the kernel perform neighbor reachability detection (IPv4 ARP or IPv6 ND) @@ -339,7 +340,7 @@ public class IpReachabilityMonitor { private void handleNeighborLost(String msg) { InetAddress ip = null; - ProvisioningChange delta; + final ProvisioningChange delta; synchronized (mLock) { LinkProperties whatIfLp = new LinkProperties(mLinkProperties); @@ -368,10 +369,8 @@ public class IpReachabilityMonitor { // an InetAddress argument. mCallback.notifyLost(ip, logMsg); } - logEvent(IpReachabilityEvent.PROVISIONING_LOST, 0); - } else { - logEvent(IpReachabilityEvent.NUD_FAILED, 0); } + logNudFailed(delta); } public void probeAll() { @@ -397,9 +396,10 @@ public class IpReachabilityMonitor { final int returnValue = probeNeighbor(mInterfaceIndex, target); logEvent(IpReachabilityEvent.PROBE, returnValue); } + mLastProbeTimeMs = SystemClock.elapsedRealtime(); } - private long getProbeWakeLockDuration() { + private static long getProbeWakeLockDuration() { // Ideally, this would be computed by examining the values of: // // /proc/sys/net/ipv[46]/neigh/<ifname>/ucast_solicit @@ -416,7 +416,15 @@ public class IpReachabilityMonitor { } private void logEvent(int probeType, int errorCode) { - int eventType = probeType | (errorCode & 0xff ); + int eventType = probeType | (errorCode & 0xff); + mMetricsLog.log(new IpReachabilityEvent(mInterfaceName, eventType)); + } + + private void logNudFailed(ProvisioningChange delta) { + long duration = SystemClock.elapsedRealtime() - mLastProbeTimeMs; + boolean isFromProbe = (duration < getProbeWakeLockDuration()); + boolean isProvisioningLost = (delta == ProvisioningChange.LOST_PROVISIONING); + int eventType = IpReachabilityEvent.nudFailureEventType(isFromProbe, isProvisioningLost); mMetricsLog.log(new IpReachabilityEvent(mInterfaceName, eventType)); } diff --git a/services/retaildemo/java/com/android/server/retaildemo/PreloadAppsInstaller.java b/services/retaildemo/java/com/android/server/retaildemo/PreloadAppsInstaller.java new file mode 100644 index 000000000000..848017063bff --- /dev/null +++ b/services/retaildemo/java/com/android/server/retaildemo/PreloadAppsInstaller.java @@ -0,0 +1,125 @@ +/* + * 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 com.android.server.retaildemo; + +import android.app.AppGlobals; +import android.app.PackageInstallObserver; +import android.content.pm.IPackageManager; +import android.content.pm.PackageManager; +import android.os.Bundle; +import android.os.Environment; +import android.os.RemoteException; +import android.os.UserHandle; +import android.util.ArrayMap; +import android.util.Log; +import android.util.Slog; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.util.ArrayUtils; + +import java.io.File; +import java.io.IOException; +import java.util.Collections; +import java.util.Map; + +/** + * Helper class for installing preloaded APKs + */ +class PreloadAppsInstaller { + private static final String SYSTEM_SERVER_PACKAGE_NAME = "android"; + private static String TAG = PreloadAppsInstaller.class.getSimpleName(); + private static final String PRELOAD_APK_EXT = ".apk.preload"; + private static boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); + + private final IPackageManager mPackageManager; + private final File preloadsAppsDirectory; + + private final Map<String, String> mApkToPackageMap; + + PreloadAppsInstaller() { + this(AppGlobals.getPackageManager(), Environment.getDataPreloadsAppsDirectory()); + } + + @VisibleForTesting + PreloadAppsInstaller(IPackageManager packageManager, File preloadsAppsDirectory) { + mPackageManager = packageManager; + mApkToPackageMap = Collections.synchronizedMap(new ArrayMap<>()); + this.preloadsAppsDirectory = preloadsAppsDirectory; + } + + void installApps(int userId) { + File[] files = preloadsAppsDirectory.listFiles(); + if (ArrayUtils.isEmpty(files)) { + return; + } + for (File file : files) { + String apkName = file.getName(); + if (apkName.endsWith(PRELOAD_APK_EXT) && file.isFile()) { + String packageName = mApkToPackageMap.get(apkName); + if (packageName != null) { + try { + installExistingPackage(packageName, userId); + } catch (Exception e) { + Slog.e(TAG, "Failed to install existing package " + packageName, e); + } + } else { + try { + installPackage(file, userId); + } catch (Exception e) { + Slog.e(TAG, "Failed to install package from " + file, e); + } + } + } + } + } + + private void installExistingPackage(String packageName, int userId) { + if (DEBUG) { + Log.d(TAG, "installExistingPackage " + packageName + " u" + userId); + } + try { + mPackageManager.installExistingPackageAsUser(packageName, userId); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + private void installPackage(File file, final int userId) throws IOException, RemoteException { + final String apkName = file.getName(); + if (DEBUG) { + Log.d(TAG, "installPackage " + apkName + " u" + userId); + } + mPackageManager.installPackageAsUser(file.getPath(), new PackageInstallObserver() { + @Override + public void onPackageInstalled(String basePackageName, int returnCode, String msg, + Bundle extras) { + if (DEBUG) { + Log.d(TAG, "Package " + basePackageName + " installed u" + userId + + " returnCode: " + returnCode + " msg: " + msg); + } + if (returnCode == PackageManager.INSTALL_SUCCEEDED) { + mApkToPackageMap.put(apkName, basePackageName); + // Install on user 0 so that the package is cached when demo user is re-created + installExistingPackage(basePackageName, UserHandle.USER_SYSTEM); + } else if (returnCode == PackageManager.INSTALL_FAILED_ALREADY_EXISTS) { + installExistingPackage(basePackageName, userId); + } + } + }.getBinder(), 0, SYSTEM_SERVER_PACKAGE_NAME, userId); + } + +}
\ No newline at end of file diff --git a/services/retaildemo/java/com/android/server/retaildemo/RetailDemoModeService.java b/services/retaildemo/java/com/android/server/retaildemo/RetailDemoModeService.java index a812cee746f0..04049a17e6ce 100644 --- a/services/retaildemo/java/com/android/server/retaildemo/RetailDemoModeService.java +++ b/services/retaildemo/java/com/android/server/retaildemo/RetailDemoModeService.java @@ -110,6 +110,7 @@ public class RetailDemoModeService extends SystemService { private CameraManager mCameraManager; private String[] mCameraIdsWithFlash; private Configuration mSystemUserConfiguration; + private PreloadAppsInstaller mPreloadAppsInstaller; final Object mActivityLock = new Object(); // Whether the newly created demo user has interacted with the screen yet @@ -203,6 +204,7 @@ public class RetailDemoModeService extends SystemService { synchronized (mActivityLock) { mFirstUserActivityTime = mLastUserActivityTime = SystemClock.uptimeMillis(); } + mPreloadAppsInstaller = new PreloadAppsInstaller(); } private Notification createResetNotification() { @@ -253,6 +255,8 @@ public class RetailDemoModeService extends SystemService { um.setUserRestriction(UserManager.DISALLOW_MODIFY_ACCOUNTS, true, user); Settings.Secure.putIntForUser(getContext().getContentResolver(), Settings.Secure.SKIP_FIRST_USE_HINTS, 1, userInfo.id); + Settings.Secure.putIntForUser(getContext().getContentResolver(), + Settings.Global.PACKAGE_VERIFIER_ENABLE, 0, userInfo.id); grantRuntimePermissionToCamera(userInfo.getUserHandle()); } @@ -280,7 +284,7 @@ public class RetailDemoModeService extends SystemService { synchronized (mActivityLock) { sessionDuration = (int) ((mLastUserActivityTime - mFirstUserActivityTime) / 1000); } - MetricsLogger.count(getContext(), DEMO_SESSION_DURATION, sessionDuration); + MetricsLogger.histogram(getContext(), DEMO_SESSION_DURATION, sessionDuration); } private ActivityManagerService getActivityManager() { @@ -458,6 +462,12 @@ public class RetailDemoModeService extends SystemService { } MetricsLogger.count(getContext(), DEMO_SESSION_COUNT, 1); mHandler.removeMessages(MSG_INACTIVITY_TIME_OUT); + mHandler.post(new Runnable() { + @Override + public void run() { + mPreloadAppsInstaller.installApps(userId); + } + }); } private RetailDemoModeServiceInternal mLocalService = new RetailDemoModeServiceInternal() { diff --git a/services/tests/servicestests/src/com/android/server/ConnectivityServiceTest.java b/services/tests/servicestests/src/com/android/server/ConnectivityServiceTest.java index d424717fec68..ba77b03c5e73 100644 --- a/services/tests/servicestests/src/com/android/server/ConnectivityServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/ConnectivityServiceTest.java @@ -96,6 +96,7 @@ public class ConnectivityServiceTest extends AndroidTestCase { private static final String TAG = "ConnectivityServiceTest"; private static final int TIMEOUT_MS = 500; + private static final int TEST_LINGER_DELAY_MS = 120; private BroadcastInterceptingContext mServiceContext; private WrappedConnectivityService mService; @@ -330,7 +331,8 @@ public class ConnectivityServiceTest extends AndroidTestCase { * @param validated Indicate if network should pretend to be validated. */ public void connect(boolean validated) { - assertEquals(mNetworkInfo.getDetailedState(), DetailedState.IDLE); + assertEquals("MockNetworkAgents can only be connected once", + mNetworkInfo.getDetailedState(), DetailedState.IDLE); assertFalse(mNetworkCapabilities.hasCapability(NET_CAPABILITY_INTERNET)); NetworkCallback callback = null; @@ -548,6 +550,11 @@ public class ConnectivityServiceTest extends AndroidTestCase { super(context, handler, cmdName, cmd); } + public FakeWakeupMessage(Context context, Handler handler, String cmdName, int cmd, + int arg1, int arg2, Object obj) { + super(context, handler, cmdName, cmd, arg1, arg2, obj); + } + @Override public void schedule(long when) { long delayMs = when - SystemClock.elapsedRealtime(); @@ -556,12 +563,13 @@ public class ConnectivityServiceTest extends AndroidTestCase { fail("Attempting to send msg more than " + UNREASONABLY_LONG_WAIT + "ms into the future: " + delayMs); } - mHandler.sendEmptyMessageDelayed(mCmd, delayMs); + Message msg = mHandler.obtainMessage(mCmd, mArg1, mArg2, mObj); + mHandler.sendMessageDelayed(msg, delayMs); } @Override public void cancel() { - mHandler.removeMessages(mCmd); + mHandler.removeMessages(mCmd, mObj); } @Override @@ -585,12 +593,6 @@ public class ConnectivityServiceTest extends AndroidTestCase { protected CaptivePortalProbeResult isCaptivePortal() { return new CaptivePortalProbeResult(gen204ProbeResult, gen204ProbeRedirectUrl); } - - @Override - protected WakeupMessage makeWakeupMessage( - Context context, Handler handler, String cmdName, int cmd) { - return new FakeWakeupMessage(context, handler, cmdName, cmd); - } } private class WrappedConnectivityService extends ConnectivityService { @@ -599,6 +601,7 @@ public class ConnectivityServiceTest extends AndroidTestCase { public WrappedConnectivityService(Context context, INetworkManagementService netManager, INetworkStatsService statsService, INetworkPolicyManager policyManager) { super(context, netManager, statsService, policyManager); + mLingerDelayMs = TEST_LINGER_DELAY_MS; } @Override @@ -642,6 +645,12 @@ public class ConnectivityServiceTest extends AndroidTestCase { return monitor; } + @Override + public WakeupMessage makeWakeupMessage( + Context context, Handler handler, String cmdName, int cmd, Object obj) { + return new FakeWakeupMessage(context, handler, cmdName, cmd, 0, 0, obj); + } + public WrappedNetworkMonitor getLastCreatedWrappedNetworkMonitor() { return mLastCreatedNetworkMonitor; } @@ -686,8 +695,6 @@ public class ConnectivityServiceTest extends AndroidTestCase { public void setUp() throws Exception { super.setUp(); - NetworkMonitor.SetDefaultLingerTime(120); - // InstrumentationTestRunner prepares a looper, but AndroidJUnitRunner does not. // http://b/25897652 . if (Looper.myLooper() == null) { @@ -1051,44 +1058,60 @@ public class ConnectivityServiceTest extends AndroidTestCase { private class CallbackInfo { public final CallbackState state; public final Network network; - public CallbackInfo(CallbackState s, Network n) { state = s; network = n; } + public Object arg; + public CallbackInfo(CallbackState s, Network n, Object o) { + state = s; network = n; arg = o; + } public String toString() { return String.format("%s (%s)", state, network); } public boolean equals(Object o) { if (!(o instanceof CallbackInfo)) return false; + // Ignore timeMs, since it's unpredictable. CallbackInfo other = (CallbackInfo) o; return state == other.state && Objects.equals(network, other.network); } } private final LinkedBlockingQueue<CallbackInfo> mCallbacks = new LinkedBlockingQueue<>(); - private void setLastCallback(CallbackState state, Network network) { - mCallbacks.offer(new CallbackInfo(state, network)); + private void setLastCallback(CallbackState state, Network network, Object o) { + mCallbacks.offer(new CallbackInfo(state, network, o)); } public void onAvailable(Network network) { - setLastCallback(CallbackState.AVAILABLE, network); + setLastCallback(CallbackState.AVAILABLE, network, null); } public void onLosing(Network network, int maxMsToLive) { - setLastCallback(CallbackState.LOSING, network); + setLastCallback(CallbackState.LOSING, network, maxMsToLive /* autoboxed int */); } public void onLost(Network network) { - setLastCallback(CallbackState.LOST, network); + setLastCallback(CallbackState.LOST, network, null); } - void expectCallback(CallbackState state, MockNetworkAgent mockAgent) { + void expectCallback(CallbackState state, MockNetworkAgent mockAgent, int timeoutMs) { CallbackInfo expected = new CallbackInfo( - state, - (mockAgent != null) ? mockAgent.getNetwork() : null); + state, (mockAgent != null) ? mockAgent.getNetwork() : null, 0); + CallbackInfo actual; try { - assertEquals("Unexpected callback:", - expected, mCallbacks.poll(TIMEOUT_MS, TimeUnit.MILLISECONDS)); + actual = mCallbacks.poll(timeoutMs, TimeUnit.MILLISECONDS); + assertEquals("Unexpected callback:", expected, actual); } catch (InterruptedException e) { fail("Did not receive expected " + expected + " after " + TIMEOUT_MS + "ms"); + actual = null; // Or the compiler can't tell it's never used uninitialized. + } + if (state == CallbackState.LOSING) { + String msg = String.format( + "Invalid linger time value %d, must be between %d and %d", + actual.arg, 0, TEST_LINGER_DELAY_MS); + int maxMsToLive = (Integer) actual.arg; + assertTrue(msg, 0 <= maxMsToLive && maxMsToLive <= TEST_LINGER_DELAY_MS); } } + void expectCallback(CallbackState state, MockNetworkAgent mockAgent) { + expectCallback(state, mockAgent, TIMEOUT_MS); + } + void assertNoCallback() { mService.waitForIdle(); CallbackInfo c = mCallbacks.peek(); @@ -1249,6 +1272,8 @@ public class ConnectivityServiceTest extends AndroidTestCase { } callback.expectCallback(CallbackState.LOSING, oldNetwork); + // TODO: should we send an AVAILABLE callback to newNetwork, to indicate that it is no + // longer lingering? defaultCallback.expectCallback(CallbackState.AVAILABLE, newNetwork); assertEquals(newNetwork.getNetwork(), mCm.getActiveNetwork()); } @@ -1306,8 +1331,8 @@ public class ConnectivityServiceTest extends AndroidTestCase { mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI); mWiFiNetworkAgent.adjustScore(50); mWiFiNetworkAgent.connect(false); // Score: 70 - callback.expectCallback(CallbackState.LOSING, mCellNetworkAgent); callback.expectCallback(CallbackState.AVAILABLE, mWiFiNetworkAgent); + callback.expectCallback(CallbackState.LOSING, mCellNetworkAgent); defaultCallback.expectCallback(CallbackState.AVAILABLE, mWiFiNetworkAgent); assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork()); @@ -1318,24 +1343,24 @@ public class ConnectivityServiceTest extends AndroidTestCase { defaultCallback.expectCallback(CallbackState.AVAILABLE, mCellNetworkAgent); assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork()); - // Bring up wifi, then validate it. In this case we do not linger cell. What happens is that - // when wifi connects, we don't linger because cell could potentially become the default - // network if it validated. Then, when wifi validates, we re-evaluate cell, see it has no - // requests, and tear it down because it's unneeded. - // TODO: can we linger in this case? + // Bring up wifi, then validate it. Previous versions would immediately tear down cell, but + // it's arguably correct to linger it, since it was the default network before it validated. mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI); mWiFiNetworkAgent.connect(true); callback.expectCallback(CallbackState.AVAILABLE, mWiFiNetworkAgent); - callback.expectCallback(CallbackState.LOST, mCellNetworkAgent); + callback.expectCallback(CallbackState.LOSING, mCellNetworkAgent); defaultCallback.expectCallback(CallbackState.AVAILABLE, mWiFiNetworkAgent); assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork()); mWiFiNetworkAgent.disconnect(); callback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent); defaultCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent); + defaultCallback.expectCallback(CallbackState.AVAILABLE, mCellNetworkAgent); + mCellNetworkAgent.disconnect(); + callback.expectCallback(CallbackState.LOST, mCellNetworkAgent); + defaultCallback.expectCallback(CallbackState.LOST, mCellNetworkAgent); - // The current code has a bug: if a network is lingering, and we add and then remove a - // request from it, we forget that the network was lingering and tear it down immediately. + // If a network is lingering, and we add and remove a request from it, resume lingering. mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR); mCellNetworkAgent.connect(true); callback.expectCallback(CallbackState.AVAILABLE, mCellNetworkAgent); @@ -1350,11 +1375,43 @@ public class ConnectivityServiceTest extends AndroidTestCase { .addTransportType(TRANSPORT_CELLULAR).build(); NetworkCallback noopCallback = new NetworkCallback(); mCm.requestNetwork(cellRequest, noopCallback); + // TODO: should this cause an AVAILABLE callback, to indicate that the network is no longer + // lingering? mCm.unregisterNetworkCallback(noopCallback); - callback.expectCallback(CallbackState.LOST, mCellNetworkAgent); + callback.expectCallback(CallbackState.LOSING, mCellNetworkAgent); + + // Similar to the above: lingering can start even after the lingered request is removed. + // Disconnect wifi and switch to cell. + mWiFiNetworkAgent.disconnect(); + callback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent); + defaultCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent); + defaultCallback.expectCallback(CallbackState.AVAILABLE, mCellNetworkAgent); + + // Cell is now the default network. Pin it with a cell-specific request. + noopCallback = new NetworkCallback(); // Can't reuse NetworkCallbacks. http://b/20701525 + mCm.requestNetwork(cellRequest, noopCallback); + + // Now connect wifi, and expect it to become the default network. + mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI); + mWiFiNetworkAgent.connect(true); + callback.expectCallback(CallbackState.AVAILABLE, mWiFiNetworkAgent); + defaultCallback.expectCallback(CallbackState.AVAILABLE, mWiFiNetworkAgent); + // The default request is lingering on cell, but nothing happens to cell, and we send no + // callbacks for it, because it's kept up by cellRequest. + callback.assertNoCallback(); + // Now unregister cellRequest and expect cell to start lingering. + mCm.unregisterNetworkCallback(noopCallback); + callback.expectCallback(CallbackState.LOSING, mCellNetworkAgent); + // Let linger run its course. + callback.assertNoCallback(); + callback.expectCallback(CallbackState.LOST, mCellNetworkAgent, + TEST_LINGER_DELAY_MS /* timeoutMs */); + + // Clean up. mWiFiNetworkAgent.disconnect(); callback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent); + defaultCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent); mCm.unregisterNetworkCallback(callback); mCm.unregisterNetworkCallback(defaultCallback); diff --git a/services/tests/servicestests/src/com/android/server/NetworkPolicyManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/NetworkPolicyManagerServiceTest.java index 622e46efdf2c..979f160d3c7b 100644 --- a/services/tests/servicestests/src/com/android/server/NetworkPolicyManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/NetworkPolicyManagerServiceTest.java @@ -20,6 +20,7 @@ import static android.content.Intent.ACTION_UID_REMOVED; import static android.content.Intent.EXTRA_UID; import static android.net.ConnectivityManager.CONNECTIVITY_ACTION; import static android.net.ConnectivityManager.TYPE_WIFI; +import static android.net.NetworkPolicy.CYCLE_NONE; import static android.net.NetworkPolicy.LIMIT_DISABLED; import static android.net.NetworkPolicy.WARNING_DISABLED; import static android.net.NetworkPolicyManager.POLICY_NONE; @@ -86,7 +87,9 @@ import org.easymock.EasyMock; import org.easymock.IAnswer; import java.io.File; +import java.util.Calendar; import java.util.LinkedHashSet; +import java.util.TimeZone; import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; @@ -141,8 +144,7 @@ public class NetworkPolicyManagerServiceTest extends AndroidTestCase { private static final int PID_2 = 401; private static final int PID_3 = 402; - @Override - public void setUp() throws Exception { + public void _setUp() throws Exception { super.setUp(); setCurrentTimeMillis(TEST_START); @@ -229,8 +231,7 @@ public class NetworkPolicyManagerServiceTest extends AndroidTestCase { } - @Override - public void tearDown() throws Exception { + public void _tearDown() throws Exception { for (File file : mPolicyDir.listFiles()) { file.delete(); } @@ -263,6 +264,7 @@ public class NetworkPolicyManagerServiceTest extends AndroidTestCase { backgroundChanged.get(); } + @Suppress public void testPidForegroundCombined() throws Exception { IdleFuture idle; @@ -310,6 +312,7 @@ public class NetworkPolicyManagerServiceTest extends AndroidTestCase { assertFalse(mService.isUidForeground(UID_A)); } + @Suppress public void testScreenChangesRules() throws Exception { Future<Void> future; @@ -351,6 +354,7 @@ public class NetworkPolicyManagerServiceTest extends AndroidTestCase { verifyAndReset(); } + @Suppress public void testPolicyNone() throws Exception { Future<Void> future; @@ -381,6 +385,7 @@ public class NetworkPolicyManagerServiceTest extends AndroidTestCase { verifyAndReset(); } + @Suppress public void testPolicyReject() throws Exception { Future<Void> future; @@ -412,6 +417,7 @@ public class NetworkPolicyManagerServiceTest extends AndroidTestCase { verifyAndReset(); } + @Suppress public void testPolicyRejectAddRemove() throws Exception { Future<Void> future; @@ -576,6 +582,17 @@ public class NetworkPolicyManagerServiceTest extends AndroidTestCase { computeLastCycleBoundary(parseTime("2013-01-14T15:11:00.000-08:00"), policy)); } + public void testLastCycleBoundaryDST() throws Exception { + final long currentTime = parseTime("1989-01-02T07:30:00.000"); + final long expectedCycle = parseTime("1988-12-03T02:00:00.000Z"); + + final NetworkPolicy policy = new NetworkPolicy( + sTemplateWifi, 3, "America/Argentina/Buenos_Aires", 1024L, 1024L, false); + final long actualCycle = computeLastCycleBoundary(currentTime, policy); + assertTimeEquals(expectedCycle, actualCycle); + } + + @Suppress public void testNetworkPolicyAppliedCycleLastMonth() throws Exception { NetworkState[] state = null; NetworkStats stats = null; @@ -628,6 +645,7 @@ public class NetworkPolicyManagerServiceTest extends AndroidTestCase { verifyAndReset(); } + @Suppress public void testUidRemovedPolicyCleared() throws Exception { Future<Void> future; @@ -652,6 +670,7 @@ public class NetworkPolicyManagerServiceTest extends AndroidTestCase { verifyAndReset(); } + @Suppress public void testOverWarningLimitNotification() throws Exception { NetworkState[] state = null; NetworkStats stats = null; @@ -783,6 +802,7 @@ public class NetworkPolicyManagerServiceTest extends AndroidTestCase { } } + @Suppress public void testMeteredNetworkWithoutLimit() throws Exception { NetworkState[] state = null; NetworkStats stats = null; diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java index 23c58fe4ea6c..43d2a1f1156c 100644 --- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java +++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java @@ -41,6 +41,7 @@ import android.os.Bundle; import android.os.Handler; import android.os.IBinder; import android.os.Parcel; +import android.os.RemoteCallbackList; import android.os.RemoteException; import android.os.UserHandle; import android.provider.Settings; @@ -55,15 +56,16 @@ import android.text.TextUtils; import android.util.Log; import android.util.Slog; +import com.android.internal.app.IVoiceInteractionSessionListener; import com.android.internal.app.IVoiceInteractionManagerService; import com.android.internal.app.IVoiceInteractionSessionShowCallback; import com.android.internal.app.IVoiceInteractor; import com.android.internal.content.PackageMonitor; import com.android.internal.os.BackgroundThread; import com.android.server.LocalServices; -import com.android.server.soundtrigger.SoundTriggerInternal; import com.android.server.SystemService; import com.android.server.UiThread; +import com.android.server.soundtrigger.SoundTriggerInternal; import java.io.FileDescriptor; import java.io.PrintWriter; @@ -84,6 +86,9 @@ public class VoiceInteractionManagerService extends SystemService { final TreeSet<Integer> mLoadedKeyphraseIds; SoundTriggerInternal mSoundTriggerInternal; + private final RemoteCallbackList<IVoiceInteractionSessionListener> + mVoiceInteractionSessionListeners = new RemoteCallbackList<>(); + public VoiceInteractionManagerService(Context context) { super(context); mContext = context; @@ -1038,6 +1043,48 @@ public class VoiceInteractionManagerService extends SystemService { } @Override + public void registerVoiceInteractionSessionListener( + IVoiceInteractionSessionListener listener) { + enforceCallingPermission(Manifest.permission.ACCESS_VOICE_INTERACTION_SERVICE); + synchronized (this) { + mVoiceInteractionSessionListeners.register(listener); + } + } + + public void onSessionShown() { + synchronized (this) { + final int size = mVoiceInteractionSessionListeners.beginBroadcast(); + for (int i = 0; i < size; ++i) { + final IVoiceInteractionSessionListener listener = + mVoiceInteractionSessionListeners.getBroadcastItem(i); + try { + listener.onVoiceSessionShown(); + } catch (RemoteException e) { + Slog.e(TAG, "Error delivering voice interaction open event.", e); + } + } + mVoiceInteractionSessionListeners.finishBroadcast(); + } + } + + public void onSessionHidden() { + synchronized (this) { + final int size = mVoiceInteractionSessionListeners.beginBroadcast(); + for (int i = 0; i < size; ++i) { + final IVoiceInteractionSessionListener listener = + mVoiceInteractionSessionListeners.getBroadcastItem(i); + try { + listener.onVoiceSessionHidden(); + + } catch (RemoteException e) { + Slog.e(TAG, "Error delivering voice interaction closed event.", e); + } + } + mVoiceInteractionSessionListeners.finishBroadcast(); + } + } + + @Override public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { if (mContext.checkCallingOrSelfPermission(Manifest.permission.DUMP) != PackageManager.PERMISSION_GRANTED) { diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java index 3f9da4c154ea..a46ccee0b96d 100644 --- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java +++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java @@ -41,6 +41,7 @@ import android.util.PrintWriterPrinter; import android.util.Slog; import android.view.IWindowManager; +import com.android.internal.app.IVoiceInteractionSessionListener; import com.android.internal.app.IVoiceInteractionSessionShowCallback; import com.android.internal.app.IVoiceInteractor; import com.android.server.LocalServices; @@ -58,7 +59,7 @@ class VoiceInteractionManagerServiceImpl implements VoiceInteractionSessionConne final Context mContext; final Handler mHandler; - final Object mLock; + final VoiceInteractionManagerService.VoiceInteractionManagerServiceStub mServiceStub; final int mUser; final ComponentName mComponent; final IActivityManager mAm; @@ -77,7 +78,7 @@ class VoiceInteractionManagerServiceImpl implements VoiceInteractionSessionConne if (Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(intent.getAction())) { String reason = intent.getStringExtra("reason"); if (!CLOSE_REASON_VOICE_INTERACTION.equals(reason) && !"dream".equals(reason)) { - synchronized (mLock) { + synchronized (mServiceStub) { if (mActiveSession != null && mActiveSession.mSession != null) { try { mActiveSession.mSession.closeSystemDialogs(); @@ -93,7 +94,7 @@ class VoiceInteractionManagerServiceImpl implements VoiceInteractionSessionConne final ServiceConnection mConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { - synchronized (mLock) { + synchronized (mServiceStub) { mService = IVoiceInteractionService.Stub.asInterface(service); try { mService.ready(); @@ -108,11 +109,12 @@ class VoiceInteractionManagerServiceImpl implements VoiceInteractionSessionConne } }; - VoiceInteractionManagerServiceImpl(Context context, Handler handler, Object lock, + VoiceInteractionManagerServiceImpl(Context context, Handler handler, + VoiceInteractionManagerService.VoiceInteractionManagerServiceStub stub, int userHandle, ComponentName service) { mContext = context; mHandler = handler; - mLock = lock; + mServiceStub = stub; mUser = userHandle; mComponent = service; mAm = ActivityManagerNative.getDefault(); @@ -148,8 +150,9 @@ class VoiceInteractionManagerServiceImpl implements VoiceInteractionSessionConne public boolean showSessionLocked(Bundle args, int flags, IVoiceInteractionSessionShowCallback showCallback, IBinder activityToken) { if (mActiveSession == null) { - mActiveSession = new VoiceInteractionSessionConnection(mLock, mSessionComponentName, - mUser, mContext, this, mInfo.getServiceInfo().applicationInfo.uid, mHandler); + mActiveSession = new VoiceInteractionSessionConnection(mServiceStub, + mSessionComponentName, mUser, mContext, this, + mInfo.getServiceInfo().applicationInfo.uid, mHandler); } List<IBinder> activityTokens = null; if (activityToken == null) { @@ -355,8 +358,18 @@ class VoiceInteractionManagerServiceImpl implements VoiceInteractionSessionConne @Override public void sessionConnectionGone(VoiceInteractionSessionConnection connection) { - synchronized (mLock) { + synchronized (mServiceStub) { finishLocked(connection.mToken, false); } } + + @Override + public void onSessionShown(VoiceInteractionSessionConnection connection) { + mServiceStub.onSessionShown(); + } + + @Override + public void onSessionHidden(VoiceInteractionSessionConnection connection) { + mServiceStub.onSessionHidden(); + } } diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java index 0694911461e0..b0cc2aca8981 100644 --- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java +++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java @@ -130,6 +130,8 @@ final class VoiceInteractionSessionConnection implements ServiceConnection { public interface Callback { public void sessionConnectionGone(VoiceInteractionSessionConnection connection); + public void onSessionShown(VoiceInteractionSessionConnection connection); + public void onSessionHidden(VoiceInteractionSessionConnection connection); } final ServiceConnection mFullConnection = new ServiceConnection() { @@ -313,6 +315,7 @@ final class VoiceInteractionSessionConnection implements ServiceConnection { } else if (showCallback != null) { mPendingShowCallbacks.add(showCallback); } + mCallback.onSessionShown(this); return true; } if (showCallback != null) { @@ -468,6 +471,7 @@ final class VoiceInteractionSessionConnection implements ServiceConnection { } catch (RemoteException e) { } } + mCallback.onSessionHidden(this); } if (mFullyBound) { mContext.unbindService(mFullConnection); diff --git a/telecomm/java/android/telecom/ParcelableCallAnalytics.java b/telecomm/java/android/telecom/ParcelableCallAnalytics.java index 0ee9babe6659..318d8411931b 100644 --- a/telecomm/java/android/telecom/ParcelableCallAnalytics.java +++ b/telecomm/java/android/telecom/ParcelableCallAnalytics.java @@ -21,6 +21,7 @@ import android.os.Parcel; import android.os.Parcelable; import java.util.ArrayList; +import java.util.LinkedList; import java.util.List; /** @@ -28,6 +29,67 @@ import java.util.List; */ @SystemApi public class ParcelableCallAnalytics implements Parcelable { + public static final class VideoEvent implements Parcelable { + public static final int SEND_LOCAL_SESSION_MODIFY_REQUEST = 0; + public static final int SEND_LOCAL_SESSION_MODIFY_RESPONSE = 1; + public static final int RECEIVE_REMOTE_SESSION_MODIFY_REQUEST = 2; + public static final int RECEIVE_REMOTE_SESSION_MODIFY_RESPONSE = 3; + + public static final Parcelable.Creator<VideoEvent> CREATOR = + new Parcelable.Creator<VideoEvent> () { + + @Override + public VideoEvent createFromParcel(Parcel in) { + return new VideoEvent(in); + } + + @Override + public VideoEvent[] newArray(int size) { + return new VideoEvent[size]; + } + }; + + private int mEventName; + private long mTimeSinceLastEvent; + private int mVideoState; + + public VideoEvent(int eventName, long timeSinceLastEvent, int videoState) { + mEventName = eventName; + mTimeSinceLastEvent = timeSinceLastEvent; + mVideoState = videoState; + } + + VideoEvent(Parcel in) { + mEventName = in.readInt(); + mTimeSinceLastEvent = in.readLong(); + mVideoState = in.readInt(); + } + + public int getEventName() { + return mEventName; + } + + public long getTimeSinceLastEvent() { + return mTimeSinceLastEvent; + } + + public int getVideoState() { + return mVideoState; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel out, int flags) { + out.writeInt(mEventName); + out.writeLong(mTimeSinceLastEvent); + out.writeInt(mVideoState); + } + } + public static final class AnalyticsEvent implements Parcelable { public static final int SET_SELECT_PHONE_ACCOUNT = 0; public static final int SET_ACTIVE = 1; @@ -250,6 +312,12 @@ public class ParcelableCallAnalytics implements Parcelable { // A map from event-pair names to their durations. private final List<EventTiming> eventTimings; + // Whether the call has ever been a video call. + private boolean isVideoCall = false; + + // A list of video events that have occurred. + private List<VideoEvent> videoEvents; + public ParcelableCallAnalytics(long startTimeMillis, long callDurationMillis, int callType, boolean isAdditionalCall, boolean isInterrupted, int callTechnologies, int callTerminationCode, boolean isEmergencyCall, String connectionService, @@ -284,6 +352,9 @@ public class ParcelableCallAnalytics implements Parcelable { in.readTypedList(analyticsEvents, AnalyticsEvent.CREATOR); eventTimings = new ArrayList<>(); in.readTypedList(eventTimings, EventTiming.CREATOR); + isVideoCall = readByteAsBoolean(in); + videoEvents = new LinkedList<>(); + in.readTypedList(videoEvents, VideoEvent.CREATOR); } public void writeToParcel(Parcel out, int flags) { @@ -299,6 +370,15 @@ public class ParcelableCallAnalytics implements Parcelable { writeBooleanAsByte(out, isCreatedFromExistingConnection); out.writeTypedList(analyticsEvents); out.writeTypedList(eventTimings); + writeBooleanAsByte(out, isVideoCall); + out.writeTypedList(videoEvents); + } + public void setIsVideoCall(boolean isVideoCall) { + this.isVideoCall = isVideoCall; + } + + public void setVideoEvents(List<VideoEvent> videoEvents) { + this.videoEvents = videoEvents; } public long getStartTimeMillis() { @@ -349,6 +429,14 @@ public class ParcelableCallAnalytics implements Parcelable { return eventTimings; } + public boolean isVideoCall() { + return isVideoCall; + } + + public List<VideoEvent> getVideoEvents() { + return videoEvents; + } + @Override public int describeContents() { return 0; |