diff options
78 files changed, 1320 insertions, 673 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/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/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/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/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/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/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/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/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..fa3479cca5e7 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); 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/RetailDemoModeService.java b/services/retaildemo/java/com/android/server/retaildemo/RetailDemoModeService.java index a4d401318793..2529564107b2 100644 --- a/services/retaildemo/java/com/android/server/retaildemo/RetailDemoModeService.java +++ b/services/retaildemo/java/com/android/server/retaildemo/RetailDemoModeService.java @@ -16,6 +16,7 @@ package com.android.server.retaildemo; +import android.Manifest; import android.app.ActivityManagerInternal; import android.app.ActivityManagerNative; import android.app.AppGlobals; @@ -32,6 +33,7 @@ import android.content.Intent; import android.content.IntentFilter; import android.content.pm.IPackageManager; import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; import android.content.pm.UserInfo; import android.content.res.Configuration; import android.database.ContentObserver; @@ -51,6 +53,7 @@ import android.os.RemoteException; import android.os.SystemClock; import android.os.UserHandle; import android.os.UserManager; +import android.provider.MediaStore; import android.provider.Settings; import android.util.Slog; import com.android.internal.os.BackgroundThread; @@ -250,6 +253,26 @@ 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); + + grantRuntimePermissionToCamera(userInfo.getUserHandle()); + } + + private void grantRuntimePermissionToCamera(UserHandle user) { + final Intent cameraIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); + final PackageManager pm = getContext().getPackageManager(); + final ResolveInfo handler = pm.resolveActivityAsUser(cameraIntent, + PackageManager.MATCH_DIRECT_BOOT_AWARE | PackageManager.MATCH_DIRECT_BOOT_UNAWARE, + user.getIdentifier()); + if (handler == null || handler.activityInfo == null) { + return; + } + try { + pm.grantRuntimePermission(handler.activityInfo.packageName, + Manifest.permission.ACCESS_FINE_LOCATION, user); + } catch (Exception e) { + // Ignore + } + } void logSessionDuration() { @@ -257,7 +280,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() { 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..794a73e0fe35 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; @@ -1038,6 +1040,13 @@ public class VoiceInteractionManagerService extends SystemService { } @Override + public void registerVoiceInteractionSessionListener( + IVoiceInteractionSessionListener listener) { + enforceCallingPermission(Manifest.permission.ACCESS_VOICE_INTERACTION_SERVICE); + mImpl.registerVoiceInteractionSessionListener(listener); + } + + @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..52e1a9b51c35 100644 --- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java +++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java @@ -30,6 +30,7 @@ import android.content.pm.PackageManager; import android.os.Bundle; import android.os.Handler; import android.os.IBinder; +import android.os.RemoteCallbackList; import android.os.RemoteException; import android.os.ServiceManager; import android.os.UserHandle; @@ -41,6 +42,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; @@ -71,6 +73,9 @@ class VoiceInteractionManagerServiceImpl implements VoiceInteractionSessionConne VoiceInteractionSessionConnection mActiveSession; int mDisabledShowContext; + private final RemoteCallbackList<IVoiceInteractionSessionListener> + mVoiceInteractionSessionListeners = new RemoteCallbackList<>(); + final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { @@ -353,10 +358,52 @@ class VoiceInteractionManagerServiceImpl implements VoiceInteractionSessionConne } } + public void registerVoiceInteractionSessionListener( + IVoiceInteractionSessionListener listener) { + synchronized (mLock) { + mVoiceInteractionSessionListeners.register(listener); + } + } + @Override public void sessionConnectionGone(VoiceInteractionSessionConnection connection) { synchronized (mLock) { finishLocked(connection.mToken, false); } } + + @Override + public void onSessionShown(VoiceInteractionSessionConnection connection) { + synchronized (mLock) { + 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(); + } + } + + @Override + public void onSessionHidden(VoiceInteractionSessionConnection connection) { + synchronized (mLock) { + 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(); + } + } } 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); |