Merge "Add carrier config for WFC promo mode." into nyc-mr1-dev
diff --git a/Android.mk b/Android.mk
index 5766e46..8c39d19 100644
--- a/Android.mk
+++ b/Android.mk
@@ -308,6 +308,7 @@
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 \
@@ -342,6 +343,7 @@
core/java/com/android/internal/view/IInputMethodManager.aidl \
core/java/com/android/internal/view/IInputMethodSession.aidl \
core/java/com/android/internal/view/IInputSessionCallback.aidl \
+ core/java/com/android/internal/widget/ICheckCredentialProgressCallback.aidl \
core/java/com/android/internal/widget/ILockSettings.aidl \
core/java/com/android/internal/widget/IRemoteViewsFactory.aidl \
core/java/com/android/internal/widget/IRemoteViewsAdapterConnection.aidl \
@@ -1060,6 +1062,42 @@
-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 +1139,7 @@
-hdf android.hasSamples true \
-samplesdir $(samples_dir)
-LOCAL_DROIDDOC_CUSTOM_TEMPLATE_DIR:=build/tools/droiddoc/templates-sdk
+LOCAL_DROIDDOC_CUSTOM_TEMPLATE_DIR:=build/tools/droiddoc/templates-sdk-dev
include $(BUILD_DROIDDOC)
diff --git a/api/current.txt b/api/current.txt
index b9c0269..d5f36bd 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -10063,17 +10063,14 @@
method public android.content.ComponentName getActivity();
method public java.util.Set<java.lang.String> getCategories();
method public java.lang.CharSequence getDisabledMessage();
- method public int getDisabledMessageResourceId();
method public android.os.PersistableBundle getExtras();
method public java.lang.String getId();
method public android.content.Intent getIntent();
method public long getLastChangedTimestamp();
method public java.lang.CharSequence getLongLabel();
- method public int getLongLabelResourceId();
method public java.lang.String getPackage();
method public int getRank();
method public java.lang.CharSequence getShortLabel();
- method public int getShortLabelResourceId();
method public android.os.UserHandle getUserHandle();
method public boolean hasKeyFieldsOnly();
method public boolean isDeclaredInManifest();
@@ -10087,7 +10084,6 @@
}
public static class ShortcutInfo.Builder {
- ctor public deprecated ShortcutInfo.Builder(android.content.Context);
ctor public ShortcutInfo.Builder(android.content.Context, java.lang.String);
method public android.content.pm.ShortcutInfo build();
method public android.content.pm.ShortcutInfo.Builder setActivity(android.content.ComponentName);
@@ -10095,7 +10091,6 @@
method public android.content.pm.ShortcutInfo.Builder setDisabledMessage(java.lang.CharSequence);
method public android.content.pm.ShortcutInfo.Builder setExtras(android.os.PersistableBundle);
method public android.content.pm.ShortcutInfo.Builder setIcon(android.graphics.drawable.Icon);
- method public deprecated android.content.pm.ShortcutInfo.Builder setId(java.lang.String);
method public android.content.pm.ShortcutInfo.Builder setIntent(android.content.Intent);
method public android.content.pm.ShortcutInfo.Builder setLongLabel(java.lang.CharSequence);
method public android.content.pm.ShortcutInfo.Builder setRank(int);
@@ -10113,8 +10108,6 @@
method public java.util.List<android.content.pm.ShortcutInfo> getManifestShortcuts();
method public int getMaxShortcutCountPerActivity();
method public java.util.List<android.content.pm.ShortcutInfo> getPinnedShortcuts();
- method public long getRateLimitResetTime();
- method public int getRemainingCallCount();
method public void removeAllDynamicShortcuts();
method public void removeDynamicShortcuts(java.util.List<java.lang.String>);
method public void reportShortcutUsed(java.lang.String);
diff --git a/api/system-current.txt b/api/system-current.txt
index b0fb9f3..16c75cc 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -10488,17 +10488,14 @@
method public android.content.ComponentName getActivity();
method public java.util.Set<java.lang.String> getCategories();
method public java.lang.CharSequence getDisabledMessage();
- method public int getDisabledMessageResourceId();
method public android.os.PersistableBundle getExtras();
method public java.lang.String getId();
method public android.content.Intent getIntent();
method public long getLastChangedTimestamp();
method public java.lang.CharSequence getLongLabel();
- method public int getLongLabelResourceId();
method public java.lang.String getPackage();
method public int getRank();
method public java.lang.CharSequence getShortLabel();
- method public int getShortLabelResourceId();
method public android.os.UserHandle getUserHandle();
method public boolean hasKeyFieldsOnly();
method public boolean isDeclaredInManifest();
@@ -10512,7 +10509,6 @@
}
public static class ShortcutInfo.Builder {
- ctor public deprecated ShortcutInfo.Builder(android.content.Context);
ctor public ShortcutInfo.Builder(android.content.Context, java.lang.String);
method public android.content.pm.ShortcutInfo build();
method public android.content.pm.ShortcutInfo.Builder setActivity(android.content.ComponentName);
@@ -10520,7 +10516,6 @@
method public android.content.pm.ShortcutInfo.Builder setDisabledMessage(java.lang.CharSequence);
method public android.content.pm.ShortcutInfo.Builder setExtras(android.os.PersistableBundle);
method public android.content.pm.ShortcutInfo.Builder setIcon(android.graphics.drawable.Icon);
- method public deprecated android.content.pm.ShortcutInfo.Builder setId(java.lang.String);
method public android.content.pm.ShortcutInfo.Builder setIntent(android.content.Intent);
method public android.content.pm.ShortcutInfo.Builder setLongLabel(java.lang.CharSequence);
method public android.content.pm.ShortcutInfo.Builder setRank(int);
@@ -10538,8 +10533,6 @@
method public java.util.List<android.content.pm.ShortcutInfo> getManifestShortcuts();
method public int getMaxShortcutCountPerActivity();
method public java.util.List<android.content.pm.ShortcutInfo> getPinnedShortcuts();
- method public long getRateLimitResetTime();
- method public int getRemainingCallCount();
method public void removeAllDynamicShortcuts();
method public void removeDynamicShortcuts(java.util.List<java.lang.String>);
method public void reportShortcutUsed(java.lang.String);
diff --git a/api/test-current.txt b/api/test-current.txt
index 1906c5e..7fdbb64 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -10076,17 +10076,14 @@
method public android.content.ComponentName getActivity();
method public java.util.Set<java.lang.String> getCategories();
method public java.lang.CharSequence getDisabledMessage();
- method public int getDisabledMessageResourceId();
method public android.os.PersistableBundle getExtras();
method public java.lang.String getId();
method public android.content.Intent getIntent();
method public long getLastChangedTimestamp();
method public java.lang.CharSequence getLongLabel();
- method public int getLongLabelResourceId();
method public java.lang.String getPackage();
method public int getRank();
method public java.lang.CharSequence getShortLabel();
- method public int getShortLabelResourceId();
method public android.os.UserHandle getUserHandle();
method public boolean hasKeyFieldsOnly();
method public boolean isDeclaredInManifest();
@@ -10100,7 +10097,6 @@
}
public static class ShortcutInfo.Builder {
- ctor public deprecated ShortcutInfo.Builder(android.content.Context);
ctor public ShortcutInfo.Builder(android.content.Context, java.lang.String);
method public android.content.pm.ShortcutInfo build();
method public android.content.pm.ShortcutInfo.Builder setActivity(android.content.ComponentName);
@@ -10108,7 +10104,6 @@
method public android.content.pm.ShortcutInfo.Builder setDisabledMessage(java.lang.CharSequence);
method public android.content.pm.ShortcutInfo.Builder setExtras(android.os.PersistableBundle);
method public android.content.pm.ShortcutInfo.Builder setIcon(android.graphics.drawable.Icon);
- method public deprecated android.content.pm.ShortcutInfo.Builder setId(java.lang.String);
method public android.content.pm.ShortcutInfo.Builder setIntent(android.content.Intent);
method public android.content.pm.ShortcutInfo.Builder setLongLabel(java.lang.CharSequence);
method public android.content.pm.ShortcutInfo.Builder setRank(int);
@@ -10127,8 +10122,6 @@
method public java.util.List<android.content.pm.ShortcutInfo> getManifestShortcuts();
method public int getMaxShortcutCountPerActivity();
method public java.util.List<android.content.pm.ShortcutInfo> getPinnedShortcuts();
- method public long getRateLimitResetTime();
- method public int getRemainingCallCount();
method public void removeAllDynamicShortcuts();
method public void removeDynamicShortcuts(java.util.List<java.lang.String>);
method public void reportShortcutUsed(java.lang.String);
diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java
index 14f9db7..277348a 100644
--- a/core/java/android/app/ActivityManagerNative.java
+++ b/core/java/android/app/ActivityManagerNative.java
@@ -3003,6 +3003,13 @@
reply.writeNoException();
return true;
}
+ case SET_RENDER_THREAD_TRANSACTION: {
+ data.enforceInterface(IActivityManager.descriptor);
+ final int tid = data.readInt();
+ setRenderThread(tid);
+ reply.writeNoException();
+ return true;
+ }
}
return super.onTransact(code, data, reply, flags);
@@ -7040,7 +7047,7 @@
@Override
public void setVrThread(int tid)
- throws RemoteException {
+ throws RemoteException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
data.writeInterfaceToken(IActivityManager.descriptor);
@@ -7052,5 +7059,18 @@
return;
}
+ public void setRenderThread(int tid)
+ throws RemoteException {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ data.writeInterfaceToken(IActivityManager.descriptor);
+ data.writeInt(tid);
+ mRemote.transact(SET_RENDER_THREAD_TRANSACTION, data, reply, 0);
+ reply.readException();
+ data.recycle();
+ reply.recycle();
+ return;
+ }
+
private IBinder mRemote;
}
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index e6ca520..8f42467 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -185,15 +185,6 @@
@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 @@
@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 @@
@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 @@
@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/app/IActivityManager.java b/core/java/android/app/IActivityManager.java
index d38fb941..4a4202a 100644
--- a/core/java/android/app/IActivityManager.java
+++ b/core/java/android/app/IActivityManager.java
@@ -659,6 +659,7 @@
throws RemoteException;
public void setVrThread(int tid) throws RemoteException;
+ public void setRenderThread(int tid) throws RemoteException;
/*
* Private non-Binder interfaces
@@ -1046,5 +1047,8 @@
int START_CONFIRM_DEVICE_CREDENTIAL_INTENT = IBinder.FIRST_CALL_TRANSACTION + 374;
int SEND_IDLE_JOB_TRIGGER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 375;
int SEND_INTENT_SENDER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 376;
+
+ // Start of N MR1 transactions
int SET_VR_THREAD_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 377;
+ int SET_RENDER_THREAD_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 378;
}
diff --git a/core/java/android/app/IWallpaperManager.aidl b/core/java/android/app/IWallpaperManager.aidl
index a0762b9..9cd70e6 100644
--- a/core/java/android/app/IWallpaperManager.aidl
+++ b/core/java/android/app/IWallpaperManager.aidl
@@ -72,7 +72,7 @@
* information about that wallpaper. Otherwise, if it is a static image,
* simply return null.
*/
- WallpaperInfo getWallpaperInfo();
+ WallpaperInfo getWallpaperInfo(int userId);
/**
* Clear the system wallpaper.
diff --git a/core/java/android/app/WallpaperManager.java b/core/java/android/app/WallpaperManager.java
index 7f467f0..79f2a1e9 100644
--- a/core/java/android/app/WallpaperManager.java
+++ b/core/java/android/app/WallpaperManager.java
@@ -51,6 +51,7 @@
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SystemProperties;
+import android.os.UserHandle;
import android.text.TextUtils;
import android.util.Log;
import android.view.WindowManagerGlobal;
@@ -783,7 +784,7 @@
Log.w(TAG, "WallpaperService not running");
throw new RuntimeException(new DeadSystemException());
} else {
- return sGlobals.mService.getWallpaperInfo();
+ return sGlobals.mService.getWallpaperInfo(UserHandle.myUserId());
}
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
diff --git a/core/java/android/content/ContentProviderClient.java b/core/java/android/content/ContentProviderClient.java
index 9221fbb..e49eb34 100644
--- a/core/java/android/content/ContentProviderClient.java
+++ b/core/java/android/content/ContentProviderClient.java
@@ -147,13 +147,7 @@
if (cursor == null) {
return null;
}
-
- if ("com.google.android.gms".equals(mPackageName)) {
- // They're casting to a concrete subclass, sigh
- return cursor;
- } else {
- return new CursorWrapperInner(cursor);
- }
+ return new CursorWrapperInner(cursor);
} catch (DeadObjectException e) {
if (!mStable) {
mContentResolver.unstableProviderDied(mContentProvider);
diff --git a/core/java/android/content/ContentResolver.java b/core/java/android/content/ContentResolver.java
index c8d8920..b3320d6 100644
--- a/core/java/android/content/ContentResolver.java
+++ b/core/java/android/content/ContentResolver.java
@@ -34,6 +34,7 @@
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.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 int resolveUserId(Uri uri) {
return ContentProvider.getUserIdFromUri(uri, mContext.getUserId());
}
+
+ /** @hide */
+ public Drawable getTypeDrawable(String mimeType) {
+ return MimeIconUtils.loadMimeIcon(mContext, mimeType);
+ }
}
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index fbe16c5..281d6f6 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -146,6 +146,8 @@
MATCH_UNINSTALLED_PACKAGES,
MATCH_SYSTEM_ONLY,
MATCH_DEBUG_TRIAGED_MISSING,
+ MATCH_DISABLED_UNTIL_USED_COMPONENTS,
+ GET_DISABLED_UNTIL_USED_COMPONENTS,
GET_UNINSTALLED_PACKAGES,
})
@Retention(RetentionPolicy.SOURCE)
@@ -2879,6 +2881,7 @@
*
* @see #GET_META_DATA
* @see #GET_SHARED_LIBRARY_FILES
+ * @see #MATCH_DISABLED_UNTIL_USED_COMPONENTS
* @see #MATCH_SYSTEM_ONLY
* @see #MATCH_UNINSTALLED_PACKAGES
*/
@@ -3507,6 +3510,7 @@
*
* @see #GET_META_DATA
* @see #GET_SHARED_LIBRARY_FILES
+ * @see #MATCH_DISABLED_UNTIL_USED_COMPONENTS
* @see #MATCH_SYSTEM_ONLY
* @see #MATCH_UNINSTALLED_PACKAGES
*/
diff --git a/core/java/android/content/pm/ShortcutInfo.java b/core/java/android/content/pm/ShortcutInfo.java
index 35370f0..39e15e0 100644
--- a/core/java/android/content/pm/ShortcutInfo.java
+++ b/core/java/android/content/pm/ShortcutInfo.java
@@ -696,7 +696,8 @@
private PersistableBundle mExtras;
/**
- * Old style constructor. STOPSHIP hide it before launch.
+ * Old style constructor.
+ * @hide
*/
@Deprecated
public Builder(Context context) {
@@ -704,7 +705,8 @@
}
/**
- * Used with the old style constructor, kept for unit tests. STOPSHIP hide it before launch.
+ * Used with the old style constructor, kept for unit tests.
+ * @hide
*/
@NonNull
@Deprecated
@@ -1004,7 +1006,7 @@
return mTitle;
}
- /** TODO Javadoc */
+ /** @hide */
public int getShortLabelResourceId() {
return mTitleResId;
}
@@ -1017,7 +1019,7 @@
return mText;
}
- /** TODO Javadoc */
+ /** @hide */
public int getLongLabelResourceId() {
return mTextResId;
}
@@ -1030,7 +1032,7 @@
return mDisabledMessage;
}
- /** TODO Javadoc */
+ /** @hide */
public int getDisabledMessageResourceId() {
return mDisabledMessageResId;
}
diff --git a/core/java/android/content/pm/ShortcutManager.java b/core/java/android/content/pm/ShortcutManager.java
index 7b3c487..1af63a0 100644
--- a/core/java/android/content/pm/ShortcutManager.java
+++ b/core/java/android/content/pm/ShortcutManager.java
@@ -63,24 +63,6 @@
* published, existing shortcuts with the same ID will be updated. Note this may include a
* pinned shortcut.
*
- * <h3>Rate limiting</h3>
- *
- * Calls to {@link #setDynamicShortcuts(List)}, {@link #addDynamicShortcuts(List)},
- * and {@link #updateShortcuts(List)} from <b>background applications</b> will be
- * rate-limited. An application can call these methods at most
- * {@link #getRemainingCallCount()} times until the rate-limiting counter is reset,
- * which happens at a certain time every day.
- *
- * <p>An application can use {@link #getRateLimitResetTime()} to get the next reset time.
- *
- * <p>Foreground applications (i.e. ones with a foreground activity or a foreground services)
- * will not be throttled. Also, when an application comes to foreground,
- * {@link #getRemainingCallCount()} will be reset to the initial value.
- *
- * <p>For testing purposes, use "Developer Options" (found in the Settings menu) to reset the
- * internal rate-limiting counter. Automated tests can use the following ADB shell command to
- * achieve the same effect:</p>
- * <pre>adb shell cmd shortcut reset-throttling</pre>
*
* <h3>Backup and Restore</h3>
*
@@ -328,6 +310,8 @@
* before the rate limit counter is reset.
*
* @see #getRateLimitResetTime()
+ *
+ * @hide
*/
public int getRemainingCallCount() {
try {
@@ -342,6 +326,8 @@
*
* @see #getRemainingCallCount()
* @see System#currentTimeMillis()
+ *
+ * @hide
*/
public long getRateLimitResetTime() {
try {
diff --git a/core/java/android/hardware/camera2/CameraManager.java b/core/java/android/hardware/camera2/CameraManager.java
index 3917bfa..145b1d0 100644
--- a/core/java/android/hardware/camera2/CameraManager.java
+++ b/core/java/android/hardware/camera2/CameraManager.java
@@ -267,6 +267,10 @@
* @param cameraId The unique identifier of the camera device to open
* @param callback The callback for the camera. Must not be null.
* @param handler The handler to invoke the callback on. Must not be null.
+ * @param uid The UID of the application actually opening the camera.
+ * Must be USE_CALLING_UID unless the caller is a service
+ * that is trusted to open the device on behalf of an
+ * application and to forward the real UID.
*
* @throws CameraAccessException if the camera is disabled by device policy,
* too many camera devices are already open, or the cameraId does not match
@@ -281,7 +285,7 @@
* @see android.app.admin.DevicePolicyManager#setCameraDisabled
*/
private CameraDevice openCameraDeviceUserAsync(String cameraId,
- CameraDevice.StateCallback callback, Handler handler)
+ CameraDevice.StateCallback callback, Handler handler, final int uid)
throws CameraAccessException {
CameraCharacteristics characteristics = getCameraCharacteristics(cameraId);
CameraDevice device = null;
@@ -317,7 +321,7 @@
"Camera service is currently unavailable");
}
cameraUser = cameraService.connectDevice(callbacks, id,
- mContext.getOpPackageName(), USE_CALLING_UID);
+ mContext.getOpPackageName(), uid);
} else {
// Use legacy camera implementation for HAL1 devices
Log.i(TAG, "Using legacy camera HAL.");
@@ -434,6 +438,29 @@
@NonNull final CameraDevice.StateCallback callback, @Nullable Handler handler)
throws CameraAccessException {
+ openCameraForUid(cameraId, callback, handler, USE_CALLING_UID);
+ }
+
+ /**
+ * Open a connection to a camera with the given ID, on behalf of another application
+ * specified by clientUid.
+ *
+ * <p>The behavior of this method matches that of {@link #openCamera}, except that it allows
+ * the caller to specify the UID to use for permission/etc verification. This can only be
+ * done by services trusted by the camera subsystem to act on behalf of applications and
+ * to forward the real UID.</p>
+ *
+ * @param clientUid
+ * The UID of the application on whose behalf the camera is being opened.
+ * Must be USE_CALLING_UID unless the caller is a trusted service.
+ *
+ * @hide
+ */
+ public void openCameraForUid(@NonNull String cameraId,
+ @NonNull final CameraDevice.StateCallback callback, @Nullable Handler handler,
+ int clientUid)
+ throws CameraAccessException {
+
if (cameraId == null) {
throw new IllegalArgumentException("cameraId was null");
} else if (callback == null) {
@@ -447,7 +474,7 @@
}
}
- openCameraDeviceUserAsync(cameraId, callback, handler);
+ openCameraDeviceUserAsync(cameraId, callback, handler, clientUid);
}
/**
diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java
index a45e6f5..3c2ac67 100644
--- a/core/java/android/net/ConnectivityManager.java
+++ b/core/java/android/net/ConnectivityManager.java
@@ -1035,6 +1035,26 @@
}
/**
+ * Request that this callback be invoked at ConnectivityService's earliest
+ * convenience with the current satisfying network's LinkProperties.
+ * If no such network exists no callback invocation is performed.
+ *
+ * The callback must have been registered with #requestNetwork() or
+ * #registerDefaultNetworkCallback(); callbacks registered with
+ * registerNetworkCallback() are not specific to any particular Network so
+ * do not cause any updates.
+ *
+ * @hide
+ */
+ public void requestLinkProperties(NetworkCallback networkCallback) {
+ try {
+ mService.requestLinkProperties(networkCallback.networkRequest);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Get the {@link android.net.NetworkCapabilities} for the given {@link Network}. This
* will return {@code null} if the network is unknown.
* <p>This method requires the caller to hold the permission
@@ -1052,6 +1072,26 @@
}
/**
+ * Request that this callback be invoked at ConnectivityService's earliest
+ * convenience with the current satisfying network's NetworkCapabilities.
+ * If no such network exists no callback invocation is performed.
+ *
+ * The callback must have been registered with #requestNetwork() or
+ * #registerDefaultNetworkCallback(); callbacks registered with
+ * registerNetworkCallback() are not specific to any particular Network so
+ * do not cause any updates.
+ *
+ * @hide
+ */
+ public void requestNetworkCapabilities(NetworkCallback networkCallback) {
+ try {
+ mService.requestNetworkCapabilities(networkCallback.networkRequest);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Gets the URL that should be used for resolving whether a captive portal is present.
* 1. This URL should respond with a 204 response to a GET request to indicate no captive
* portal is present.
@@ -3103,14 +3143,11 @@
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/IConnectivityManager.aidl b/core/java/android/net/IConnectivityManager.aidl
index 0d518cc..d48c155 100644
--- a/core/java/android/net/IConnectivityManager.aidl
+++ b/core/java/android/net/IConnectivityManager.aidl
@@ -156,6 +156,8 @@
void pendingListenForNetwork(in NetworkCapabilities networkCapabilities,
in PendingIntent operation);
+ void requestLinkProperties(in NetworkRequest networkRequest);
+ void requestNetworkCapabilities(in NetworkRequest networkRequest);
void releaseNetworkRequest(in NetworkRequest networkRequest);
void setAcceptUnvalidated(in Network network, boolean accept, boolean always);
diff --git a/core/java/android/net/NetworkPolicyManager.java b/core/java/android/net/NetworkPolicyManager.java
index 51c45e0..11b861a 100644
--- a/core/java/android/net/NetworkPolicyManager.java
+++ b/core/java/android/net/NetworkPolicyManager.java
@@ -18,7 +18,6 @@
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.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 @@
throw new IllegalArgumentException("Unable to compute boundary without cycleDay");
}
- final Time now = new Time(policy.cycleTimezone);
- now.set(currentTime);
+ final Calendar cal = Calendar.getInstance(TimeZone.getTimeZone(policy.cycleTimezone));
+ cal.setTimeInMillis(currentTime);
+ snapToCycleDay(cal, policy.cycleDay);
- // 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);
+ 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 @@
throw new IllegalArgumentException("Unable to compute boundary without cycleDay");
}
- final Time now = new Time(policy.cycleTimezone);
- now.set(currentTime);
+ final Calendar cal = Calendar.getInstance(TimeZone.getTimeZone(policy.cycleTimezone));
+ cal.setTimeInMillis(currentTime);
+ snapToCycleDay(cal, policy.cycleDay);
- // 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);
+ 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 @@
*
* @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/NetworkUtils.java b/core/java/android/net/NetworkUtils.java
index 141af3d..35e3065 100644
--- a/core/java/android/net/NetworkUtils.java
+++ b/core/java/android/net/NetworkUtils.java
@@ -45,13 +45,20 @@
public native static void attachDhcpFilter(FileDescriptor fd) throws SocketException;
/**
- * Attaches a socket filter that accepts ICMP6 router advertisement packets to the given socket.
+ * Attaches a socket filter that accepts ICMPv6 router advertisements to the given socket.
* @param fd the socket's {@link FileDescriptor}.
* @param packetType the hardware address type, one of ARPHRD_*.
*/
public native static void attachRaFilter(FileDescriptor fd, int packetType) throws SocketException;
/**
+ * Configures a socket for receiving ICMPv6 router solicitations and sending advertisements.
+ * @param fd the socket's {@link FileDescriptor}.
+ * @param ifIndex the interface index.
+ */
+ public native static void setupRaSocket(FileDescriptor fd, int ifIndex) throws SocketException;
+
+ /**
* Binds the current process to the network designated by {@code netId}. All sockets created
* in the future (and not explicitly bound via a bound {@link SocketFactory} (see
* {@link Network#getSocketFactory}) will be bound to this network. Note that if this
diff --git a/core/java/android/net/metrics/ApfProgramEvent.java b/core/java/android/net/metrics/ApfProgramEvent.java
index e322dc1..258d8e1 100644
--- a/core/java/android/net/metrics/ApfProgramEvent.java
+++ b/core/java/android/net/metrics/ApfProgramEvent.java
@@ -124,7 +124,7 @@
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 169f571..4a9ff05 100644
--- a/core/java/android/net/metrics/DhcpClientEvent.java
+++ b/core/java/android/net/metrics/DhcpClientEvent.java
@@ -21,11 +21,18 @@
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 7d02291..ee09e22 100644
--- a/core/java/android/net/metrics/IpReachabilityEvent.java
+++ b/core/java/android/net/metrics/IpReachabilityEvent.java
@@ -24,21 +24,31 @@
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 @@
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 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/Process.java b/core/java/android/os/Process.java
index b9e46a5..c26d974 100644
--- a/core/java/android/os/Process.java
+++ b/core/java/android/os/Process.java
@@ -19,6 +19,7 @@
import android.net.LocalSocket;
import android.net.LocalSocketAddress;
import android.system.Os;
+import android.system.OsConstants;
import android.util.Log;
import com.android.internal.os.Zygote;
import dalvik.system.VMRuntime;
@@ -328,7 +329,6 @@
*/
public static final int SCHED_RESET_ON_FORK = 0x40000000;
-
// Keep in sync with SP_* constants of enum type SchedPolicy
// declared in system/core/include/cutils/sched_policy.h,
// except THREAD_GROUP_DEFAULT does not correspond to any SP_* value.
@@ -1250,4 +1250,22 @@
* @hide
*/
public static final native void removeAllProcessGroups();
+
+ /**
+ * Check to see if a thread belongs to a given process. This may require
+ * more permissions than apps generally have.
+ * @return true if this thread belongs to a process
+ * @hide
+ */
+ public static final boolean isThreadInProcess(int tid, int pid) {
+ try {
+ if (Os.access("/proc/" + tid + "/task/" + pid, OsConstants.F_OK)) {
+ return true;
+ } else {
+ return false;
+ }
+ } catch (Exception e) {
+ return false;
+ }
+ }
}
diff --git a/core/java/android/preference/PreferenceFragment.java b/core/java/android/preference/PreferenceFragment.java
index 3e496b6..d4a3582 100644
--- a/core/java/android/preference/PreferenceFragment.java
+++ b/core/java/android/preference/PreferenceFragment.java
@@ -189,12 +189,10 @@
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 dae243b..56610ed 100755
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -1548,7 +1548,7 @@
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 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 @@
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 @@
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 @@
}
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 @@
}
String value = c.moveToNext() ? c.getString(0) : null;
- synchronized (this) {
+ synchronized (NameValueCache.this) {
mValues.put(name, value);
}
if (LOCAL_LOGV) {
@@ -6147,6 +6147,39 @@
"camera_double_tap_power_gesture_disabled";
/**
+ * Whether the camera double twist gesture to flip between front and back mode should be
+ * enabled.
+ *
+ * @hide
+ */
+ public static final String CAMERA_DOUBLE_TWIST_TO_FLIP_ENABLED =
+ "camera_double_twist_to_flip_enabled";
+
+ /**
+ * Control whether Night display is currently activated.
+ * @hide
+ */
+ public static final String NIGHT_DISPLAY_ACTIVATED = "night_display_activated";
+
+ /**
+ * Control whether Night display will automatically activate/deactivate.
+ * @hide
+ */
+ public static final String NIGHT_DISPLAY_AUTO_MODE = "night_display_auto_mode";
+
+ /**
+ * Custom time when Night display is scheduled to activate.
+ * Represented as milliseconds from midnight (e.g. 79200000 == 10pm).
+ * @hide
+ */
+ public static final String NIGHT_DISPLAY_CUSTOM_START_TIME = "night_display_custom_start_time";
+
+ /**
+ * Custom time when Night display is scheduled to deactivate.
+ * Represented as milliseconds from midnight (e.g. 21600000 == 6am).
+ * @hide
+ */
+ public static final String NIGHT_DISPLAY_CUSTOM_END_TIME = "night_display_custom_end_time";
/**
* Behavior of twilight on the device.
@@ -6277,6 +6310,14 @@
/**
+ * Whether SystemUI navigation keys is enabled.
+ * @hide
+ */
+ public static final String SYSTEM_NAVIGATION_KEYS_ENABLED =
+ "system_navigation_keys_enabled";
+
+
+ /**
* This are the settings to be backed up.
*
* NOTE: Settings are backed up and restored in the order they appear
@@ -8659,6 +8700,22 @@
public static final String DEVICE_DEMO_MODE = "device_demo_mode";
/**
+ * Retail mode specific settings. This is encoded as a key=value list, separated by commas.
+ * Ex: "user_inactivity_timeout_ms=30000,warning_dialog_timeout_ms=10000". The following
+ * keys are supported:
+ *
+ * <pre>
+ * user_inactivity_timeout_ms (long)
+ * warning_dialog_timeout_ms (long)
+ * </pre>
+ * <p>
+ * Type: string
+ *
+ * @hide
+ */
+ public static final String RETAIL_DEMO_MODE_CONSTANTS = "retail_demo_mode_constants";
+
+ /**
* Settings to backup. This is here so that it's in the same place as the settings
* keys and easy to update.
*
@@ -9074,13 +9131,6 @@
public static final String MAX_NOTIFICATION_ENQUEUE_RATE = "max_notification_enqueue_rate";
/**
- * Whether SystemUI navigation keys is enabled.
- * @hide
- */
- public static final String SYSTEM_NAVIGATION_KEYS_ENABLED =
- "system_navigation_keys_enabled";
-
- /**
* Whether cell is enabled/disabled
* @hide
*/
diff --git a/core/java/android/view/ThreadedRenderer.java b/core/java/android/view/ThreadedRenderer.java
index fcca739..e129a06 100644
--- a/core/java/android/view/ThreadedRenderer.java
+++ b/core/java/android/view/ThreadedRenderer.java
@@ -16,6 +16,7 @@
package android.view;
+import android.app.ActivityManagerNative;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.content.Context;
@@ -917,10 +918,20 @@
synchronized void init(Context context, long renderProxy) {
if (mInitialized) return;
mInitialized = true;
+ initSched(context, renderProxy);
initGraphicsStats(context, renderProxy);
initAssetAtlas(context, renderProxy);
}
+ private static void initSched(Context context, long renderProxy) {
+ try {
+ int tid = nGetRenderThreadTid(renderProxy);
+ ActivityManagerNative.getDefault().setRenderThread(tid);
+ } catch (Throwable t) {
+ Log.w(LOG_TAG, "Failed to set scheduler for RenderThread", t);
+ }
+ }
+
private static void initGraphicsStats(Context context, long renderProxy) {
try {
IBinder binder = ServiceManager.getService("graphicsstats");
@@ -979,6 +990,7 @@
private static native void nSetAtlas(long nativeProxy, GraphicBuffer buffer, long[] map);
private static native void nSetProcessStatsBuffer(long nativeProxy, int fd);
+ private static native int nGetRenderThreadTid(long nativeProxy);
private static native long nCreateRootRenderNode();
private static native long nCreateProxy(boolean translucent, long rootRenderNode);
diff --git a/core/java/com/android/internal/app/AssistUtils.java b/core/java/com/android/internal/app/AssistUtils.java
index d552e54..56c5cc9 100644
--- a/core/java/com/android/internal/app/AssistUtils.java
+++ b/core/java/com/android/internal/app/AssistUtils.java
@@ -132,6 +132,16 @@
}
}
+ 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 1a963f3..033dd13 100644
--- a/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl
+++ b/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl
@@ -22,6 +22,7 @@
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 @@
* 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 0000000..87749d2
--- /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/app/NightDisplayController.java b/core/java/com/android/internal/app/NightDisplayController.java
new file mode 100644
index 0000000..03cd729
--- /dev/null
+++ b/core/java/com/android/internal/app/NightDisplayController.java
@@ -0,0 +1,421 @@
+/*
+ * 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;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.database.ContentObserver;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.UserHandle;
+import android.provider.Settings.Secure;
+import android.util.Slog;
+
+import com.android.internal.R;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Calendar;
+import java.util.Locale;
+
+/**
+ * Controller for managing Night display settings.
+ * <p/>
+ * Night display tints your screen red at night. This makes it easier to look at your screen in
+ * dim light and may help you fall asleep more easily.
+ */
+public final class NightDisplayController {
+
+ private static final String TAG = "NightDisplayController";
+ private static final boolean DEBUG = false;
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({ AUTO_MODE_DISABLED, AUTO_MODE_CUSTOM, AUTO_MODE_TWILIGHT })
+ public @interface AutoMode {}
+
+ /**
+ * Auto mode value to prevent Night display from being automatically activated. It can still
+ * be activated manually via {@link #setActivated(boolean)}.
+ *
+ * @see #setAutoMode(int)
+ */
+ public static final int AUTO_MODE_DISABLED = 0;
+ /**
+ * Auto mode value to automatically activate Night display at a specific start and end time.
+ *
+ * @see #setAutoMode(int)
+ * @see #setCustomStartTime(LocalTime)
+ * @see #setCustomEndTime(LocalTime)
+ */
+ public static final int AUTO_MODE_CUSTOM = 1;
+ /**
+ * Auto mode value to automatically activate Night display from sunset to sunrise.
+ *
+ * @see #setAutoMode(int)
+ */
+ public static final int AUTO_MODE_TWILIGHT = 2;
+
+ private final Context mContext;
+ private final int mUserId;
+
+ private final ContentObserver mContentObserver;
+
+ private Callback mCallback;
+
+ public NightDisplayController(@NonNull Context context) {
+ this(context, UserHandle.myUserId());
+ }
+
+ public NightDisplayController(@NonNull Context context, int userId) {
+ mContext = context.getApplicationContext();
+ mUserId = userId;
+
+ mContentObserver = new ContentObserver(new Handler(Looper.getMainLooper())) {
+ @Override
+ public void onChange(boolean selfChange, Uri uri) {
+ super.onChange(selfChange, uri);
+
+ final String setting = uri == null ? null : uri.getLastPathSegment();
+ if (setting != null) {
+ onSettingChanged(setting);
+ }
+ }
+ };
+ }
+
+ /**
+ * Returns {@code true} when Night display is activated (the display is tinted red).
+ */
+ public boolean isActivated() {
+ return Secure.getIntForUser(mContext.getContentResolver(),
+ Secure.NIGHT_DISPLAY_ACTIVATED, 0, mUserId) == 1;
+ }
+
+ /**
+ * Sets whether Night display should be activated.
+ *
+ * @param activated {@code true} if Night display should be activated
+ * @return {@code true} if the activated value was set successfully
+ */
+ public boolean setActivated(boolean activated) {
+ return Secure.putIntForUser(mContext.getContentResolver(),
+ Secure.NIGHT_DISPLAY_ACTIVATED, activated ? 1 : 0, mUserId);
+ }
+
+ /**
+ * Returns the current auto mode value controlling when Night display will be automatically
+ * activated. One of {@link #AUTO_MODE_DISABLED}, {@link #AUTO_MODE_CUSTOM}, or
+ * {@link #AUTO_MODE_TWILIGHT}.
+ */
+ public @AutoMode int getAutoMode() {
+ int autoMode = Secure.getIntForUser(mContext.getContentResolver(),
+ Secure.NIGHT_DISPLAY_AUTO_MODE, -1, mUserId);
+ if (autoMode == -1) {
+ if (DEBUG) {
+ Slog.d(TAG, "Using default value for setting: " + Secure.NIGHT_DISPLAY_AUTO_MODE);
+ }
+ autoMode = mContext.getResources().getInteger(
+ R.integer.config_defaultNightDisplayAutoMode);
+ }
+
+ if (autoMode != AUTO_MODE_DISABLED
+ && autoMode != AUTO_MODE_CUSTOM
+ && autoMode != AUTO_MODE_TWILIGHT) {
+ Slog.e(TAG, "Invalid autoMode: " + autoMode);
+ autoMode = AUTO_MODE_DISABLED;
+ }
+
+ return autoMode;
+ }
+
+ /**
+ * Sets the current auto mode value controlling when Night display will be automatically
+ * activated. One of {@link #AUTO_MODE_DISABLED}, {@link #AUTO_MODE_CUSTOM}, or
+ * {@link #AUTO_MODE_TWILIGHT}.
+ *
+ * @param autoMode the new auto mode to use
+ * @return {@code true} if new auto mode was set successfully
+ */
+ public boolean setAutoMode(@AutoMode int autoMode) {
+ if (autoMode != AUTO_MODE_DISABLED
+ && autoMode != AUTO_MODE_CUSTOM
+ && autoMode != AUTO_MODE_TWILIGHT) {
+ throw new IllegalArgumentException("Invalid autoMode: " + autoMode);
+ }
+
+ return Secure.putIntForUser(mContext.getContentResolver(),
+ Secure.NIGHT_DISPLAY_AUTO_MODE, autoMode, mUserId);
+ }
+
+ /**
+ * Returns the local time when Night display will be automatically activated when using
+ * {@link #AUTO_MODE_CUSTOM}.
+ */
+ public @NonNull LocalTime getCustomStartTime() {
+ int startTimeValue = Secure.getIntForUser(mContext.getContentResolver(),
+ Secure.NIGHT_DISPLAY_CUSTOM_START_TIME, -1, mUserId);
+ if (startTimeValue == -1) {
+ if (DEBUG) {
+ Slog.d(TAG, "Using default value for setting: "
+ + Secure.NIGHT_DISPLAY_CUSTOM_START_TIME);
+ }
+ startTimeValue = mContext.getResources().getInteger(
+ R.integer.config_defaultNightDisplayCustomStartTime);
+ }
+
+ return LocalTime.valueOf(startTimeValue);
+ }
+
+ /**
+ * Sets the local time when Night display will be automatically activated when using
+ * {@link #AUTO_MODE_CUSTOM}.
+ *
+ * @param startTime the local time to automatically activate Night display
+ * @return {@code true} if the new custom start time was set successfully
+ */
+ public boolean setCustomStartTime(@NonNull LocalTime startTime) {
+ if (startTime == null) {
+ throw new IllegalArgumentException("startTime cannot be null");
+ }
+ return Secure.putIntForUser(mContext.getContentResolver(),
+ Secure.NIGHT_DISPLAY_CUSTOM_START_TIME, startTime.toMillis(), mUserId);
+ }
+
+ /**
+ * Returns the local time when Night display will be automatically deactivated when using
+ * {@link #AUTO_MODE_CUSTOM}.
+ */
+ public @NonNull LocalTime getCustomEndTime() {
+ int endTimeValue = Secure.getIntForUser(mContext.getContentResolver(),
+ Secure.NIGHT_DISPLAY_CUSTOM_END_TIME, -1, mUserId);
+ if (endTimeValue == -1) {
+ if (DEBUG) {
+ Slog.d(TAG, "Using default value for setting: "
+ + Secure.NIGHT_DISPLAY_CUSTOM_END_TIME);
+ }
+ endTimeValue = mContext.getResources().getInteger(
+ R.integer.config_defaultNightDisplayCustomEndTime);
+ }
+
+ return LocalTime.valueOf(endTimeValue);
+ }
+
+ /**
+ * Sets the local time when Night display will be automatically deactivated when using
+ * {@link #AUTO_MODE_CUSTOM}.
+ *
+ * @param endTime the local time to automatically deactivate Night display
+ * @return {@code true} if the new custom end time was set successfully
+ */
+ public boolean setCustomEndTime(@NonNull LocalTime endTime) {
+ if (endTime == null) {
+ throw new IllegalArgumentException("endTime cannot be null");
+ }
+ return Secure.putIntForUser(mContext.getContentResolver(),
+ Secure.NIGHT_DISPLAY_CUSTOM_END_TIME, endTime.toMillis(), mUserId);
+ }
+
+ private void onSettingChanged(@NonNull String setting) {
+ if (DEBUG) {
+ Slog.d(TAG, "onSettingChanged: " + setting);
+ }
+
+ if (mCallback != null) {
+ switch (setting) {
+ case Secure.NIGHT_DISPLAY_ACTIVATED:
+ mCallback.onActivated(isActivated());
+ break;
+ case Secure.NIGHT_DISPLAY_AUTO_MODE:
+ mCallback.onAutoModeChanged(getAutoMode());
+ break;
+ case Secure.NIGHT_DISPLAY_CUSTOM_START_TIME:
+ mCallback.onCustomStartTimeChanged(getCustomStartTime());
+ break;
+ case Secure.NIGHT_DISPLAY_CUSTOM_END_TIME:
+ mCallback.onCustomEndTimeChanged(getCustomEndTime());
+ break;
+ }
+ }
+ }
+
+ /**
+ * Register a callback to be invoked whenever the Night display settings are changed.
+ */
+ public void setListener(Callback callback) {
+ final Callback oldCallback = mCallback;
+ if (oldCallback != callback) {
+ mCallback = callback;
+
+ if (callback == null) {
+ // Stop listening for changes now that there IS NOT a listener.
+ mContext.getContentResolver().unregisterContentObserver(mContentObserver);
+ } else if (oldCallback == null) {
+ // Start listening for changes now that there IS a listener.
+ final ContentResolver cr = mContext.getContentResolver();
+ cr.registerContentObserver(Secure.getUriFor(Secure.NIGHT_DISPLAY_ACTIVATED),
+ false /* notifyForDescendants */, mContentObserver, mUserId);
+ cr.registerContentObserver(Secure.getUriFor(Secure.NIGHT_DISPLAY_AUTO_MODE),
+ false /* notifyForDescendants */, mContentObserver, mUserId);
+ cr.registerContentObserver(Secure.getUriFor(Secure.NIGHT_DISPLAY_CUSTOM_START_TIME),
+ false /* notifyForDescendants */, mContentObserver, mUserId);
+ cr.registerContentObserver(Secure.getUriFor(Secure.NIGHT_DISPLAY_CUSTOM_END_TIME),
+ false /* notifyForDescendants */, mContentObserver, mUserId);
+ }
+ }
+ }
+
+ /**
+ * Returns {@code true} if Night display is supported by the device.
+ */
+ public static boolean isAvailable(Context context) {
+ return context.getResources().getBoolean(R.bool.config_nightDisplayAvailable);
+ }
+
+ /**
+ * A time without a time-zone or date.
+ */
+ public static class LocalTime {
+
+ /**
+ * The hour of the day from 0 - 23.
+ */
+ public final int hourOfDay;
+ /**
+ * The minute within the hour from 0 - 59.
+ */
+ public final int minute;
+
+ public LocalTime(int hourOfDay, int minute) {
+ if (hourOfDay < 0 || hourOfDay > 23) {
+ throw new IllegalArgumentException("Invalid hourOfDay: " + hourOfDay);
+ } else if (minute < 0 || minute > 59) {
+ throw new IllegalArgumentException("Invalid minute: " + minute);
+ }
+
+ this.hourOfDay = hourOfDay;
+ this.minute = minute;
+ }
+
+ /**
+ * Returns the first date time corresponding to this local time that occurs before the
+ * provided date time.
+ *
+ * @param time the date time to compare against
+ * @return the prior date time corresponding to this local time
+ */
+ public Calendar getDateTimeBefore(Calendar time) {
+ final Calendar c = Calendar.getInstance();
+ c.set(Calendar.YEAR, time.get(Calendar.YEAR));
+ c.set(Calendar.DAY_OF_YEAR, time.get(Calendar.DAY_OF_YEAR));
+
+ c.set(Calendar.HOUR_OF_DAY, hourOfDay);
+ c.set(Calendar.MINUTE, minute);
+ c.set(Calendar.SECOND, 0);
+ c.set(Calendar.MILLISECOND, 0);
+
+ // Check if the local time has past, if so return the same time tomorrow.
+ if (c.after(time)) {
+ c.add(Calendar.DATE, -1);
+ }
+
+ return c;
+ }
+
+ /**
+ * Returns the first date time corresponding to this local time that occurs after the
+ * provided date time.
+ *
+ * @param time the date time to compare against
+ * @return the next date time corresponding to this local time
+ */
+ public Calendar getDateTimeAfter(Calendar time) {
+ final Calendar c = Calendar.getInstance();
+ c.set(Calendar.YEAR, time.get(Calendar.YEAR));
+ c.set(Calendar.DAY_OF_YEAR, time.get(Calendar.DAY_OF_YEAR));
+
+ c.set(Calendar.HOUR_OF_DAY, hourOfDay);
+ c.set(Calendar.MINUTE, minute);
+ c.set(Calendar.SECOND, 0);
+ c.set(Calendar.MILLISECOND, 0);
+
+ // Check if the local time has past, if so return the same time tomorrow.
+ if (c.before(time)) {
+ c.add(Calendar.DATE, 1);
+ }
+
+ return c;
+ }
+
+ /**
+ * Returns a local time corresponding the given number of milliseconds from midnight.
+ *
+ * @param millis the number of milliseconds from midnight
+ * @return the corresponding local time
+ */
+ private static LocalTime valueOf(int millis) {
+ final int hourOfDay = (millis / 3600000) % 24;
+ final int minutes = (millis / 60000) % 60;
+ return new LocalTime(hourOfDay, minutes);
+ }
+
+ /**
+ * Returns the local time represented as milliseconds from midnight.
+ */
+ private int toMillis() {
+ return hourOfDay * 3600000 + minute * 60000;
+ }
+
+ @Override
+ public String toString() {
+ return String.format(Locale.US, "%02d:%02d", hourOfDay, minute);
+ }
+ }
+
+ /**
+ * Callback invoked whenever the Night display settings are changed.
+ */
+ public interface Callback {
+ /**
+ * Callback invoked when the activated state changes.
+ *
+ * @param activated {@code true} if Night display is activated
+ */
+ default void onActivated(boolean activated) {}
+ /**
+ * Callback invoked when the auto mode changes.
+ *
+ * @param autoMode the auto mode to use
+ */
+ default void onAutoModeChanged(int autoMode) {}
+ /**
+ * Callback invoked when the time to automatically activate Night display changes.
+ *
+ * @param startTime the local time to automatically activate Night display
+ */
+ default void onCustomStartTimeChanged(LocalTime startTime) {}
+ /**
+ * Callback invoked when the time to automatically deactivate Night display changes.
+ *
+ * @param endTime the local time to automatically deactivate Night display
+ */
+ default void onCustomEndTimeChanged(LocalTime endTime) {}
+ }
+}
diff --git a/core/java/com/android/internal/net/VpnProfile.java b/core/java/com/android/internal/net/VpnProfile.java
index 5c92f3c..9a5543a 100644
--- a/core/java/com/android/internal/net/VpnProfile.java
+++ b/core/java/com/android/internal/net/VpnProfile.java
@@ -176,6 +176,11 @@
* connection.
*/
public boolean isValidLockdownProfile() {
+ // b/7064069: lockdown firewall blocks ports that would be used for PPTP
+ if (type == TYPE_PPTP) {
+ return false;
+ }
+
try {
InetAddress.parseNumericAddress(server);
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 0000000..841ec7c
--- /dev/null
+++ b/core/java/com/android/internal/util/MimeIconUtils.java
@@ -0,0 +1,230 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.util;
+
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.provider.DocumentsContract;
+
+import com.android.internal.R;
+
+import java.util.HashMap;
+
+public class MimeIconUtils {
+
+ private static HashMap<String, Integer> sMimeIcons = new HashMap<>();
+
+ private static void add(String mimeType, int resId) {
+ if (sMimeIcons.put(mimeType, resId) != null) {
+ throw new RuntimeException(mimeType + " already registered!");
+ }
+ }
+
+ static {
+ int icon;
+
+ // Package
+ icon = R.drawable.ic_doc_apk;
+ add("application/vnd.android.package-archive", icon);
+
+ // Audio
+ icon = R.drawable.ic_doc_audio;
+ add("application/ogg", icon);
+ add("application/x-flac", icon);
+
+ // Certificate
+ icon = R.drawable.ic_doc_certificate;
+ add("application/pgp-keys", icon);
+ add("application/pgp-signature", icon);
+ add("application/x-pkcs12", icon);
+ add("application/x-pkcs7-certreqresp", icon);
+ add("application/x-pkcs7-crl", icon);
+ add("application/x-x509-ca-cert", icon);
+ add("application/x-x509-user-cert", icon);
+ add("application/x-pkcs7-certificates", icon);
+ add("application/x-pkcs7-mime", icon);
+ add("application/x-pkcs7-signature", icon);
+
+ // Source code
+ icon = R.drawable.ic_doc_codes;
+ add("application/rdf+xml", icon);
+ add("application/rss+xml", icon);
+ add("application/x-object", icon);
+ add("application/xhtml+xml", icon);
+ add("text/css", icon);
+ add("text/html", icon);
+ add("text/xml", icon);
+ add("text/x-c++hdr", icon);
+ add("text/x-c++src", icon);
+ add("text/x-chdr", icon);
+ add("text/x-csrc", icon);
+ add("text/x-dsrc", icon);
+ add("text/x-csh", icon);
+ add("text/x-haskell", icon);
+ add("text/x-java", icon);
+ add("text/x-literate-haskell", icon);
+ add("text/x-pascal", icon);
+ add("text/x-tcl", icon);
+ add("text/x-tex", icon);
+ add("application/x-latex", icon);
+ add("application/x-texinfo", icon);
+ add("application/atom+xml", icon);
+ add("application/ecmascript", icon);
+ add("application/json", icon);
+ add("application/javascript", icon);
+ add("application/xml", icon);
+ add("text/javascript", icon);
+ add("application/x-javascript", icon);
+
+ // Compressed
+ icon = R.drawable.ic_doc_compressed;
+ add("application/mac-binhex40", icon);
+ add("application/rar", icon);
+ add("application/zip", icon);
+ add("application/x-apple-diskimage", icon);
+ add("application/x-debian-package", icon);
+ add("application/x-gtar", icon);
+ add("application/x-iso9660-image", icon);
+ add("application/x-lha", icon);
+ add("application/x-lzh", icon);
+ add("application/x-lzx", icon);
+ add("application/x-stuffit", icon);
+ add("application/x-tar", icon);
+ add("application/x-webarchive", icon);
+ add("application/x-webarchive-xml", icon);
+ add("application/gzip", icon);
+ add("application/x-7z-compressed", icon);
+ add("application/x-deb", icon);
+ add("application/x-rar-compressed", icon);
+
+ // Contact
+ icon = R.drawable.ic_doc_contact;
+ add("text/x-vcard", icon);
+ add("text/vcard", icon);
+
+ // Event
+ icon = R.drawable.ic_doc_event;
+ add("text/calendar", icon);
+ add("text/x-vcalendar", icon);
+
+ // Font
+ icon = R.drawable.ic_doc_font;
+ add("application/x-font", icon);
+ add("application/font-woff", icon);
+ add("application/x-font-woff", icon);
+ add("application/x-font-ttf", icon);
+
+ // Image
+ icon = R.drawable.ic_doc_image;
+ add("application/vnd.oasis.opendocument.graphics", icon);
+ add("application/vnd.oasis.opendocument.graphics-template", icon);
+ add("application/vnd.oasis.opendocument.image", icon);
+ add("application/vnd.stardivision.draw", icon);
+ add("application/vnd.sun.xml.draw", icon);
+ add("application/vnd.sun.xml.draw.template", icon);
+
+ // PDF
+ icon = R.drawable.ic_doc_pdf;
+ add("application/pdf", icon);
+
+ // Presentation
+ icon = R.drawable.ic_doc_presentation;
+ add("application/vnd.stardivision.impress", icon);
+ add("application/vnd.sun.xml.impress", icon);
+ add("application/vnd.sun.xml.impress.template", icon);
+ add("application/x-kpresenter", icon);
+ add("application/vnd.oasis.opendocument.presentation", icon);
+
+ // Spreadsheet
+ icon = R.drawable.ic_doc_spreadsheet;
+ add("application/vnd.oasis.opendocument.spreadsheet", icon);
+ add("application/vnd.oasis.opendocument.spreadsheet-template", icon);
+ add("application/vnd.stardivision.calc", icon);
+ add("application/vnd.sun.xml.calc", icon);
+ add("application/vnd.sun.xml.calc.template", icon);
+ add("application/x-kspread", icon);
+
+ // Document
+ icon = R.drawable.ic_doc_document;
+ add("application/vnd.oasis.opendocument.text", icon);
+ add("application/vnd.oasis.opendocument.text-master", icon);
+ add("application/vnd.oasis.opendocument.text-template", icon);
+ add("application/vnd.oasis.opendocument.text-web", icon);
+ add("application/vnd.stardivision.writer", icon);
+ add("application/vnd.stardivision.writer-global", icon);
+ add("application/vnd.sun.xml.writer", icon);
+ add("application/vnd.sun.xml.writer.global", icon);
+ add("application/vnd.sun.xml.writer.template", icon);
+ add("application/x-abiword", icon);
+ add("application/x-kword", icon);
+
+ // Video
+ icon = R.drawable.ic_doc_video;
+ add("application/x-quicktimeplayer", icon);
+ add("application/x-shockwave-flash", icon);
+
+ // Word
+ icon = R.drawable.ic_doc_word;
+ add("application/msword", icon);
+ add("application/vnd.openxmlformats-officedocument.wordprocessingml.document", icon);
+ add("application/vnd.openxmlformats-officedocument.wordprocessingml.template", icon);
+
+ // Excel
+ icon = R.drawable.ic_doc_excel;
+ add("application/vnd.ms-excel", icon);
+ add("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", icon);
+ add("application/vnd.openxmlformats-officedocument.spreadsheetml.template", icon);
+
+ // Powerpoint
+ icon = R.drawable.ic_doc_powerpoint;
+ add("application/vnd.ms-powerpoint", icon);
+ add("application/vnd.openxmlformats-officedocument.presentationml.presentation", icon);
+ add("application/vnd.openxmlformats-officedocument.presentationml.template", icon);
+ add("application/vnd.openxmlformats-officedocument.presentationml.slideshow", icon);
+ }
+
+ public static Drawable loadMimeIcon(Context context, String mimeType) {
+ if (DocumentsContract.Document.MIME_TYPE_DIR.equals(mimeType)) {
+ return context.getDrawable(R.drawable.ic_doc_folder);
+ }
+
+ // Look for exact match first
+ Integer resId = sMimeIcons.get(mimeType);
+ if (resId != null) {
+ return context.getDrawable(resId);
+ }
+
+ if (mimeType == null) {
+ // TODO: generic icon?
+ return null;
+ }
+
+ // Otherwise look for partial match
+ final String typeOnly = mimeType.split("/")[0];
+ if ("audio".equals(typeOnly)) {
+ return context.getDrawable(R.drawable.ic_doc_audio);
+ } else if ("image".equals(typeOnly)) {
+ return context.getDrawable(R.drawable.ic_doc_image);
+ } else if ("text".equals(typeOnly)) {
+ return context.getDrawable(R.drawable.ic_doc_text);
+ } else if ("video".equals(typeOnly)) {
+ return context.getDrawable(R.drawable.ic_doc_video);
+ } else {
+ return context.getDrawable(R.drawable.ic_doc_generic);
+ }
+ }
+}
diff --git a/core/java/com/android/internal/widget/ICheckCredentialProgressCallback.aidl b/core/java/com/android/internal/widget/ICheckCredentialProgressCallback.aidl
new file mode 100644
index 0000000..79717cf
--- /dev/null
+++ b/core/java/com/android/internal/widget/ICheckCredentialProgressCallback.aidl
@@ -0,0 +1,22 @@
+/*
+ * 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.widget;
+
+/** {@hide} */
+oneway interface ICheckCredentialProgressCallback {
+ void onCredentialVerified();
+}
diff --git a/core/java/com/android/internal/widget/ILockSettings.aidl b/core/java/com/android/internal/widget/ILockSettings.aidl
index 05b839d..9fa558e 100644
--- a/core/java/com/android/internal/widget/ILockSettings.aidl
+++ b/core/java/com/android/internal/widget/ILockSettings.aidl
@@ -17,6 +17,7 @@
package com.android.internal.widget;
import android.app.trust.IStrongAuthTracker;
+import com.android.internal.widget.ICheckCredentialProgressCallback;
import com.android.internal.widget.VerifyCredentialResponse;
/** {@hide} */
@@ -29,10 +30,12 @@
String getString(in String key, in String defaultValue, in int userId);
void setLockPattern(in String pattern, in String savedPattern, int userId);
void resetKeyStore(int userId);
- VerifyCredentialResponse checkPattern(in String pattern, int userId);
+ VerifyCredentialResponse checkPattern(in String pattern, int userId,
+ in ICheckCredentialProgressCallback progressCallback);
VerifyCredentialResponse verifyPattern(in String pattern, long challenge, int userId);
void setLockPassword(in String password, in String savedPassword, int userId);
- VerifyCredentialResponse checkPassword(in String password, int userId);
+ VerifyCredentialResponse checkPassword(in String password, int userId,
+ in ICheckCredentialProgressCallback progressCallback);
VerifyCredentialResponse verifyPassword(in String password, long challenge, int userId);
VerifyCredentialResponse verifyTiedProfileChallenge(String password, boolean isPattern, long challenge, int userId);
boolean checkVoldPassword(int userId);
diff --git a/core/java/com/android/internal/widget/LockPatternChecker.java b/core/java/com/android/internal/widget/LockPatternChecker.java
index 713f56f..df9b0dd 100644
--- a/core/java/com/android/internal/widget/LockPatternChecker.java
+++ b/core/java/com/android/internal/widget/LockPatternChecker.java
@@ -14,6 +14,13 @@
* Interface for a callback to be invoked after security check.
*/
public interface OnCheckCallback {
+
+ /**
+ * Invoked as soon as possible we know that the credentials match. This will be called
+ * earlier than {@link #onChecked} but only if the credentials match.
+ */
+ default void onEarlyMatched() {}
+
/**
* Invoked when a security check is finished.
*
@@ -92,7 +99,7 @@
@Override
protected Boolean doInBackground(Void... args) {
try {
- return utils.checkPattern(pattern, userId);
+ return utils.checkPattern(pattern, userId, callback::onEarlyMatched);
} catch (RequestThrottledException ex) {
mThrottleTimeout = ex.getTimeoutMs();
return false;
@@ -199,7 +206,7 @@
@Override
protected Boolean doInBackground(Void... args) {
try {
- return utils.checkPassword(password, userId);
+ return utils.checkPassword(password, userId, callback::onEarlyMatched);
} catch (RequestThrottledException ex) {
mThrottleTimeout = ex.getTimeoutMs();
return false;
diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java
index 2e0dfa5..eff116b 100644
--- a/core/java/com/android/internal/widget/LockPatternUtils.java
+++ b/core/java/com/android/internal/widget/LockPatternUtils.java
@@ -17,6 +17,7 @@
package com.android.internal.widget;
import android.annotation.IntDef;
+import android.annotation.Nullable;
import android.app.admin.DevicePolicyManager;
import android.app.trust.IStrongAuthTracker;
import android.app.trust.TrustManager;
@@ -32,7 +33,6 @@
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SystemClock;
-import android.os.SystemProperties;
import android.os.UserHandle;
import android.os.UserManager;
import android.os.storage.IMountService;
@@ -149,6 +149,7 @@
private DevicePolicyManager mDevicePolicyManager;
private ILockSettings mLockSettingsService;
private UserManager mUserManager;
+ private final Handler mHandler = new Handler();
/**
* Use {@link TrustManager#isTrustUsuallyManaged(int)}.
@@ -341,10 +342,23 @@
*/
public boolean checkPattern(List<LockPatternView.Cell> pattern, int userId)
throws RequestThrottledException {
+ return checkPattern(pattern, userId, null /* progressCallback */);
+ }
+
+ /**
+ * Check to see if a pattern matches the saved pattern. If no pattern exists,
+ * always returns true.
+ * @param pattern The pattern to check.
+ * @return Whether the pattern matches the stored one.
+ */
+ public boolean checkPattern(List<LockPatternView.Cell> pattern, int userId,
+ @Nullable CheckCredentialProgressCallback progressCallback)
+ throws RequestThrottledException {
throwIfCalledOnMainThread();
try {
VerifyCredentialResponse response =
- getLockSettings().checkPattern(patternToString(pattern), userId);
+ getLockSettings().checkPattern(patternToString(pattern), userId,
+ wrapCallback(progressCallback));
if (response.getResponseCode() == VerifyCredentialResponse.RESPONSE_OK) {
return true;
@@ -423,10 +437,22 @@
* @return Whether the password matches the stored one.
*/
public boolean checkPassword(String password, int userId) throws RequestThrottledException {
+ return checkPassword(password, userId, null /* progressCallback */);
+ }
+
+ /**
+ * Check to see if a password matches the saved password. If no password exists,
+ * always returns true.
+ * @param password The password to check.
+ * @return Whether the password matches the stored one.
+ */
+ public boolean checkPassword(String password, int userId,
+ @Nullable CheckCredentialProgressCallback progressCallback)
+ throws RequestThrottledException {
throwIfCalledOnMainThread();
try {
VerifyCredentialResponse response =
- getLockSettings().checkPassword(password, userId);
+ getLockSettings().checkPassword(password, userId, wrapCallback(progressCallback));
if (response.getResponseCode() == VerifyCredentialResponse.RESPONSE_OK) {
return true;
} else if (response.getResponseCode() == VerifyCredentialResponse.RESPONSE_RETRY) {
@@ -1475,6 +1501,33 @@
return (getStrongAuthForUser(userId) & ~StrongAuthTracker.ALLOWING_FINGERPRINT) == 0;
}
+ private ICheckCredentialProgressCallback wrapCallback(
+ final CheckCredentialProgressCallback callback) {
+ if (callback == null) {
+ return null;
+ } else {
+ return new ICheckCredentialProgressCallback.Stub() {
+
+ @Override
+ public void onCredentialVerified() throws RemoteException {
+ mHandler.post(callback::onEarlyMatched);
+ }
+ };
+ }
+ }
+
+ /**
+ * Callback to be notified about progress when checking credentials.
+ */
+ public interface CheckCredentialProgressCallback {
+
+ /**
+ * Called as soon as possible when we know that the credentials match but the user hasn't
+ * been fully unlocked.
+ */
+ void onEarlyMatched();
+ }
+
/**
* Tracks the global strong authentication state.
*/
diff --git a/core/jni/android_hardware_location_ContextHubService.cpp b/core/jni/android_hardware_location_ContextHubService.cpp
index 3881d6d..a56eba6 100644
--- a/core/jni/android_hardware_location_ContextHubService.cpp
+++ b/core/jni/android_hardware_location_ContextHubService.cpp
@@ -186,9 +186,13 @@
static void send_query_for_apps() {
hub_message_t msg;
+ query_apps_request_t queryMsg;
+
+ queryMsg.app_name.id = NANOAPP_VENDOR_ALL_APPS;
msg.message_type = CONTEXT_HUB_QUERY_APPS;
- msg.message_len = 0;
+ msg.message_len = sizeof(queryMsg);
+ msg.message = &queryMsg;
for (int i = 0; i < db.hubInfo.numHubs; i++ ) {
ALOGD("Sending query for apps to hub %d", i);
diff --git a/core/jni/android_media_AudioFormat.h b/core/jni/android_media_AudioFormat.h
index 6513304..092aaf6 100644
--- a/core/jni/android_media_AudioFormat.h
+++ b/core/jni/android_media_AudioFormat.h
@@ -20,18 +20,20 @@
#include <system/audio.h>
// keep these values in sync with AudioFormat.java
-#define ENCODING_PCM_16BIT 2
-#define ENCODING_PCM_8BIT 3
-#define ENCODING_PCM_FLOAT 4
-#define ENCODING_AC3 5
-#define ENCODING_E_AC3 6
-#define ENCODING_DTS 7
-#define ENCODING_DTS_HD 8
-#define ENCODING_MP3 9
-#define ENCODING_AAC_LC 10
-#define ENCODING_AAC_HE_V1 11
-#define ENCODING_AAC_HE_V2 12
-#define ENCODING_IEC61937 13
+#define ENCODING_PCM_16BIT 2
+#define ENCODING_PCM_8BIT 3
+#define ENCODING_PCM_FLOAT 4
+#define ENCODING_AC3 5
+#define ENCODING_E_AC3 6
+#define ENCODING_DTS 7
+#define ENCODING_DTS_HD 8
+#define ENCODING_MP3 9
+#define ENCODING_AAC_LC 10
+#define ENCODING_AAC_HE_V1 11
+#define ENCODING_AAC_HE_V2 12
+#define ENCODING_IEC61937 13
+#define ENCODING_DOLBY_TRUEHD 14
+
#define ENCODING_INVALID 0
#define ENCODING_DEFAULT 1
@@ -65,6 +67,8 @@
return AUDIO_FORMAT_AAC_HE_V1;
case ENCODING_AAC_HE_V2:
return AUDIO_FORMAT_AAC_HE_V2;
+ case ENCODING_DOLBY_TRUEHD:
+ return AUDIO_FORMAT_DOLBY_TRUEHD;
case ENCODING_IEC61937:
return AUDIO_FORMAT_IEC61937;
case ENCODING_DEFAULT:
@@ -108,6 +112,8 @@
return ENCODING_AAC_HE_V2;
case AUDIO_FORMAT_IEC61937:
return ENCODING_IEC61937;
+ case AUDIO_FORMAT_DOLBY_TRUEHD:
+ return ENCODING_DOLBY_TRUEHD;
case AUDIO_FORMAT_DEFAULT:
return ENCODING_DEFAULT;
default:
diff --git a/core/jni/android_net_NetUtils.cpp b/core/jni/android_net_NetUtils.cpp
index 2364787..679e882 100644
--- a/core/jni/android_net_NetUtils.cpp
+++ b/core/jni/android_net_NetUtils.cpp
@@ -125,6 +125,99 @@
}
}
+static void android_net_utils_setupRaSocket(JNIEnv *env, jobject clazz, jobject javaFd,
+ jint ifIndex)
+{
+ static const int kLinkLocalHopLimit = 255;
+
+ int fd = jniGetFDFromFileDescriptor(env, javaFd);
+
+ // Set an ICMPv6 filter that only passes Router Solicitations.
+ struct icmp6_filter rs_only;
+ ICMP6_FILTER_SETBLOCKALL(&rs_only);
+ ICMP6_FILTER_SETPASS(ND_ROUTER_SOLICIT, &rs_only);
+ socklen_t len = sizeof(rs_only);
+ if (setsockopt(fd, IPPROTO_ICMPV6, ICMP6_FILTER, &rs_only, len) != 0) {
+ jniThrowExceptionFmt(env, "java/net/SocketException",
+ "setsockopt(ICMP6_FILTER): %s", strerror(errno));
+ return;
+ }
+
+ // Most/all of the rest of these options can be set via Java code, but
+ // because we're here on account of setting an icmp6_filter go ahead
+ // and do it all natively for now.
+ //
+ // TODO: Consider moving these out to Java.
+
+ // Set the multicast hoplimit to 255 (link-local only).
+ int hops = kLinkLocalHopLimit;
+ len = sizeof(hops);
+ if (setsockopt(fd, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, &hops, len) != 0) {
+ jniThrowExceptionFmt(env, "java/net/SocketException",
+ "setsockopt(IPV6_MULTICAST_HOPS): %s", strerror(errno));
+ return;
+ }
+
+ // Set the unicast hoplimit to 255 (link-local only).
+ hops = kLinkLocalHopLimit;
+ len = sizeof(hops);
+ if (setsockopt(fd, IPPROTO_IPV6, IPV6_UNICAST_HOPS, &hops, len) != 0) {
+ jniThrowExceptionFmt(env, "java/net/SocketException",
+ "setsockopt(IPV6_UNICAST_HOPS): %s", strerror(errno));
+ return;
+ }
+
+ // Explicitly disable multicast loopback.
+ int off = 0;
+ len = sizeof(off);
+ if (setsockopt(fd, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, &off, len) != 0) {
+ jniThrowExceptionFmt(env, "java/net/SocketException",
+ "setsockopt(IPV6_MULTICAST_LOOP): %s", strerror(errno));
+ return;
+ }
+
+ // Specify the IPv6 interface to use for outbound multicast.
+ len = sizeof(ifIndex);
+ if (setsockopt(fd, IPPROTO_IPV6, IPV6_MULTICAST_IF, &ifIndex, len) != 0) {
+ jniThrowExceptionFmt(env, "java/net/SocketException",
+ "setsockopt(IPV6_MULTICAST_IF): %s", strerror(errno));
+ return;
+ }
+
+ // Additional options to be considered:
+ // - IPV6_TCLASS
+ // - IPV6_RECVPKTINFO
+ // - IPV6_RECVHOPLIMIT
+
+ // Bind to [::].
+ const struct sockaddr_in6 sin6 = {
+ .sin6_family = AF_INET6,
+ .sin6_port = 0,
+ .sin6_flowinfo = 0,
+ .sin6_addr = IN6ADDR_ANY_INIT,
+ .sin6_scope_id = 0,
+ };
+ auto sa = reinterpret_cast<const struct sockaddr *>(&sin6);
+ len = sizeof(sin6);
+ if (bind(fd, sa, len) != 0) {
+ jniThrowExceptionFmt(env, "java/net/SocketException",
+ "bind(IN6ADDR_ANY): %s", strerror(errno));
+ return;
+ }
+
+ // Join the all-routers multicast group, ff02::2%index.
+ struct ipv6_mreq all_rtrs = {
+ .ipv6mr_multiaddr = {{{0xff,2,0,0,0,0,0,0,0,0,0,0,0,0,0,2}}},
+ .ipv6mr_interface = ifIndex,
+ };
+ len = sizeof(all_rtrs);
+ if (setsockopt(fd, IPPROTO_IPV6, IPV6_JOIN_GROUP, &all_rtrs, len) != 0) {
+ jniThrowExceptionFmt(env, "java/net/SocketException",
+ "setsockopt(IPV6_JOIN_GROUP): %s", strerror(errno));
+ return;
+ }
+}
+
static jboolean android_net_utils_bindProcessToNetwork(JNIEnv *env, jobject thiz, jint netId)
{
return (jboolean) !setNetworkForProcess(netId);
@@ -173,6 +266,7 @@
{ "queryUserAccess", "(II)Z", (void*)android_net_utils_queryUserAccess },
{ "attachDhcpFilter", "(Ljava/io/FileDescriptor;)V", (void*) android_net_utils_attachDhcpFilter },
{ "attachRaFilter", "(Ljava/io/FileDescriptor;I)V", (void*) android_net_utils_attachRaFilter },
+ { "setupRaSocket", "(Ljava/io/FileDescriptor;I)V", (void*) android_net_utils_setupRaSocket },
};
int register_android_net_NetworkUtils(JNIEnv* env)
diff --git a/core/jni/android_view_ThreadedRenderer.cpp b/core/jni/android_view_ThreadedRenderer.cpp
index 61a0bda..595d5c6 100644
--- a/core/jni/android_view_ThreadedRenderer.cpp
+++ b/core/jni/android_view_ThreadedRenderer.cpp
@@ -543,6 +543,12 @@
proxy->setProcessStatsBuffer(fd);
}
+static jint android_view_ThreadedRenderer_getRenderThreadTid(JNIEnv* env, jobject clazz,
+ jlong proxyPtr) {
+ RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr);
+ return proxy->getRenderThreadTid();
+}
+
static jlong android_view_ThreadedRenderer_createRootRenderNode(JNIEnv* env, jobject clazz) {
RootRenderNode* node = new RootRenderNode(env);
node->incStrong(0);
@@ -858,6 +864,7 @@
static const JNINativeMethod gMethods[] = {
{ "nSetAtlas", "(JLandroid/view/GraphicBuffer;[J)V", (void*) android_view_ThreadedRenderer_setAtlas },
{ "nSetProcessStatsBuffer", "(JI)V", (void*) android_view_ThreadedRenderer_setProcessStatsBuffer },
+ { "nGetRenderThreadTid", "(J)I", (void*) android_view_ThreadedRenderer_getRenderThreadTid },
{ "nCreateRootRenderNode", "()J", (void*) android_view_ThreadedRenderer_createRootRenderNode },
{ "nCreateProxy", "(ZJ)J", (void*) android_view_ThreadedRenderer_createProxy },
{ "nDeleteProxy", "(J)V", (void*) android_view_ThreadedRenderer_deleteProxy },
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 3c71dd9..8388f05 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -478,7 +478,7 @@
<protected-broadcast android:name="android.intent.action.MANAGED_PROFILE_UNAVAILABLE" />
<protected-broadcast android:name="com.android.server.pm.DISABLE_QUIET_MODE_AFTER_UNLOCK" />
- <protected-broadcast android:name="com.android.server.am.ACTION_RESET_DEMO" />
+ <protected-broadcast android:name="com.android.server.retaildemo.ACTION_RESET_DEMO" />
<!-- ====================================================================== -->
<!-- RUNTIME PERMISSIONS -->
diff --git a/packages/DocumentsUI/res/drawable/ic_doc_apk.xml b/core/res/res/drawable/ic_doc_apk.xml
similarity index 100%
rename from packages/DocumentsUI/res/drawable/ic_doc_apk.xml
rename to 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
similarity index 100%
rename from packages/DocumentsUI/res/drawable/ic_doc_audio.xml
rename to 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
similarity index 100%
rename from packages/DocumentsUI/res/drawable/ic_doc_certificate.xml
rename to 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
similarity index 100%
rename from packages/DocumentsUI/res/drawable/ic_doc_codes.xml
rename to 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
similarity index 100%
rename from packages/DocumentsUI/res/drawable/ic_doc_compressed.xml
rename to 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
similarity index 100%
rename from packages/DocumentsUI/res/drawable/ic_doc_contact.xml
rename to 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
similarity index 100%
rename from packages/DocumentsUI/res/drawable/ic_doc_document.xml
rename to 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
similarity index 100%
rename from packages/DocumentsUI/res/drawable/ic_doc_event.xml
rename to 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
similarity index 100%
rename from packages/DocumentsUI/res/drawable/ic_doc_excel.xml
rename to 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 0000000..dcbce01
--- /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
similarity index 100%
rename from packages/DocumentsUI/res/drawable/ic_doc_font.xml
rename to 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
similarity index 100%
rename from packages/DocumentsUI/res/drawable/ic_doc_generic.xml
rename to 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
similarity index 100%
rename from packages/DocumentsUI/res/drawable/ic_doc_image.xml
rename to 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
similarity index 100%
rename from packages/DocumentsUI/res/drawable/ic_doc_pdf.xml
rename to 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
similarity index 100%
rename from packages/DocumentsUI/res/drawable/ic_doc_powerpoint.xml
rename to 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
similarity index 100%
rename from packages/DocumentsUI/res/drawable/ic_doc_presentation.xml
rename to 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
similarity index 100%
rename from packages/DocumentsUI/res/drawable/ic_doc_spreadsheet.xml
rename to 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 0000000..7fc04e8
--- /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
similarity index 100%
rename from packages/DocumentsUI/res/drawable/ic_doc_video.xml
rename to 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
similarity index 100%
rename from packages/DocumentsUI/res/drawable/ic_doc_word.xml
rename to core/res/res/drawable/ic_doc_word.xml
diff --git a/core/res/res/layout-watch/preference_material.xml b/core/res/res/layout-watch/preference_material.xml
index 5da64fc..ad217db 100644
--- a/core/res/res/layout-watch/preference_material.xml
+++ b/core/res/res/layout-watch/preference_material.xml
@@ -29,34 +29,42 @@
<LinearLayout
android:id="@+id/icon_frame"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_marginStart="-4dp"
- android:minWidth="32dp"
- android:gravity="start|center_vertical"
+ android:layout_width="40dp"
+ android:layout_height="40dp"
+ android:gravity="center"
android:orientation="horizontal"
- android:paddingEnd="8dp"
- android:paddingTop="4dp"
- android:paddingBottom="4dp">
+ android:layout_marginEnd="8dp"
+ android:layout_marginTop="8dp"
+ android:layout_marginBottom="8dp">
<com.android.internal.widget.PreferenceImageView
android:id="@+id/icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:maxWidth="24dp"
- android:maxHeight="24dp" />
+ android:maxWidth="40dp"
+ android:maxHeight="40dp" />
</LinearLayout>
+ <!-- Preference should place its actual preference widget here. -->
+ <LinearLayout android:id="@+id/widget_frame"
+ android:layout_width="40dp"
+ android:layout_height="40dp"
+ android:gravity="center"
+ android:orientation="horizontal"
+ android:layout_marginEnd="8dp"
+ android:layout_marginTop="8dp"
+ android:layout_marginBottom="8dp" />
+
<RelativeLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
- android:paddingTop="16dp"
- android:paddingBottom="16dp">
+ android:layout_marginTop="8dp"
+ android:layout_marginBottom="8dp">
<TextView android:id="@+id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:maxLines="2"
+ android:maxLines="3"
android:textAppearance="?attr/textAppearanceListItem"
android:ellipsize="end" />
@@ -70,13 +78,4 @@
android:maxLines="10" />
</RelativeLayout>
-
- <!-- Preference should place its actual preference widget here. -->
- <LinearLayout android:id="@+id/widget_frame"
- android:layout_width="wrap_content"
- android:layout_height="match_parent"
- android:gravity="end|center_vertical"
- android:paddingStart="4dp"
- android:orientation="vertical" />
-
</LinearLayout>
diff --git a/core/res/res/layout/date_picker_header_material.xml b/core/res/res/layout/date_picker_header_material.xml
index a0e2b1d..755317e 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 020fd23..a578989 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-round-watch/dimens_material.xml b/core/res/res/values-round-watch/dimens_material.xml
index a417d19..db1f1f3 100644
--- a/core/res/res/values-round-watch/dimens_material.xml
+++ b/core/res/res/values-round-watch/dimens_material.xml
@@ -14,10 +14,10 @@
limitations under the License.
-->
<resources>
- <dimen name="dialog_padding_material">32dp</dimen>
- <dimen name="preference_fragment_padding_vertical_material">22dp</dimen>
+ <dimen name="dialog_padding_material">@dimen/screen_percentage_15</dimen>
+ <dimen name="preference_fragment_padding_vertical_material">@dimen/screen_percentage_15</dimen>
- <dimen name="list_item_padding_horizontal_material">32dp</dimen>
- <dimen name="list_item_padding_start_material">40dp</dimen>
- <dimen name="list_item_padding_end_material">24dp</dimen>
+ <dimen name="list_item_padding_horizontal_material">@dimen/screen_percentage_15</dimen>
+ <dimen name="list_item_padding_start_material">@dimen/screen_percentage_15</dimen>
+ <dimen name="list_item_padding_end_material">@dimen/screen_percentage_10</dimen>
</resources>
diff --git a/packages/SystemUI/res/layout/night_mode_settings.xml b/core/res/res/values-w194dp/dimens_material.xml
similarity index 71%
copy from packages/SystemUI/res/layout/night_mode_settings.xml
copy to core/res/res/values-w194dp/dimens_material.xml
index 3725e78..220c4b0 100644
--- a/packages/SystemUI/res/layout/night_mode_settings.xml
+++ b/core/res/res/values-w194dp/dimens_material.xml
@@ -13,12 +13,8 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:orientation="vertical">
-
- <include layout="@layout/switch_bar" />
-
-</LinearLayout>
+<resources>
+ <dimen name="screen_percentage_05">9.7dp</dimen>
+ <dimen name="screen_percentage_10">19.4dp</dimen>
+ <dimen name="screen_percentage_15">29.1dp</dimen>
+</resources>
diff --git a/packages/SystemUI/res/layout/night_mode_settings.xml b/core/res/res/values-w205dp/dimens_material.xml
similarity index 71%
copy from packages/SystemUI/res/layout/night_mode_settings.xml
copy to core/res/res/values-w205dp/dimens_material.xml
index 3725e78..94907ee 100644
--- a/packages/SystemUI/res/layout/night_mode_settings.xml
+++ b/core/res/res/values-w205dp/dimens_material.xml
@@ -13,12 +13,8 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:orientation="vertical">
-
- <include layout="@layout/switch_bar" />
-
-</LinearLayout>
+<resources>
+ <dimen name="screen_percentage_05">10.25dp</dimen>
+ <dimen name="screen_percentage_10">20.5dp</dimen>
+ <dimen name="screen_percentage_15">30.75dp</dimen>
+</resources>
diff --git a/packages/SystemUI/res/layout/night_mode_settings.xml b/core/res/res/values-w213dp/dimens_material.xml
similarity index 71%
rename from packages/SystemUI/res/layout/night_mode_settings.xml
rename to core/res/res/values-w213dp/dimens_material.xml
index 3725e78..8a4e3a0 100644
--- a/packages/SystemUI/res/layout/night_mode_settings.xml
+++ b/core/res/res/values-w213dp/dimens_material.xml
@@ -13,12 +13,8 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:orientation="vertical">
-
- <include layout="@layout/switch_bar" />
-
-</LinearLayout>
+<resources>
+ <dimen name="screen_percentage_05">10.65dp</dimen>
+ <dimen name="screen_percentage_10">21.3dp</dimen>
+ <dimen name="screen_percentage_15">31.95dp</dimen>
+</resources>
diff --git a/packages/SystemUI/res/layout/night_mode_settings.xml b/core/res/res/values-w228dp/dimens_material.xml
similarity index 71%
copy from packages/SystemUI/res/layout/night_mode_settings.xml
copy to core/res/res/values-w228dp/dimens_material.xml
index 3725e78..a200975 100644
--- a/packages/SystemUI/res/layout/night_mode_settings.xml
+++ b/core/res/res/values-w228dp/dimens_material.xml
@@ -13,12 +13,8 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:orientation="vertical">
-
- <include layout="@layout/switch_bar" />
-
-</LinearLayout>
+<resources>
+ <dimen name="screen_percentage_05">11.4dp</dimen>
+ <dimen name="screen_percentage_10">22.8dp</dimen>
+ <dimen name="screen_percentage_15">34.2dp</dimen>
+</resources>
diff --git a/packages/SystemUI/res/layout/night_mode_settings.xml b/core/res/res/values-w240dp/dimens_material.xml
similarity index 71%
copy from packages/SystemUI/res/layout/night_mode_settings.xml
copy to core/res/res/values-w240dp/dimens_material.xml
index 3725e78..a4b58fa9 100644
--- a/packages/SystemUI/res/layout/night_mode_settings.xml
+++ b/core/res/res/values-w240dp/dimens_material.xml
@@ -13,12 +13,8 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:orientation="vertical">
-
- <include layout="@layout/switch_bar" />
-
-</LinearLayout>
+<resources>
+ <dimen name="screen_percentage_05">12dp</dimen>
+ <dimen name="screen_percentage_10">24dp</dimen>
+ <dimen name="screen_percentage_15">36dp</dimen>
+</resources>
diff --git a/core/res/res/values-watch/styles_material.xml b/core/res/res/values-watch/styles_material.xml
index c19cc72..daeeca2 100644
--- a/core/res/res/values-watch/styles_material.xml
+++ b/core/res/res/values-watch/styles_material.xml
@@ -53,6 +53,10 @@
<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/colors.xml b/core/res/res/values/colors.xml
index bddd225..10e2a4a 100644
--- a/core/res/res/values/colors.xml
+++ b/core/res/res/values/colors.xml
@@ -151,7 +151,7 @@
<color name="battery_saver_mode_color">#fff4511e</color><!-- deep orange 600 -->
<!-- Default user icon colors -->
- <color name="user_icon_1">#ff00bcd4</color><!-- teal 500 -->
+ <color name="user_icon_1">#ff00bcd4</color><!-- cyan 500 -->
<color name="user_icon_2">#ff3f51b5</color><!-- indigo 500 -->
<color name="user_icon_3">#ff4285f4</color><!-- blue 500 -->
<color name="user_icon_4">#ffe91e63</color><!-- pink 500 -->
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 7d8d811..7d19537 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -754,6 +754,26 @@
-->
<integer name="config_defaultNightMode">1</integer>
+ <!-- Control whether Night display is available. This should only be enabled on devices
+ with HWC 2.0 or higher. -->
+ <bool name="config_nightDisplayAvailable">false</bool>
+
+ <!-- Default mode to control how Night display is automatically activated.
+ One of the following values (see NightDisplayController.java):
+ 0 - AUTO_MODE_DISABLED
+ 1 - AUTO_MODE_CUSTOM
+ 2 - AUTO_MODE_TWILIGHT
+ -->
+ <integer name="config_defaultNightDisplayAutoMode">1</integer>
+
+ <!-- Default time when Night display is automatically activated.
+ Represented as milliseconds from midnight (e.g. 79200000 == 10pm). -->
+ <integer name="config_defaultNightDisplayCustomStartTime">79200000</integer>
+
+ <!-- Default time when Night display is automatically deactivated.
+ Represented as milliseconds from midnight (e.g. 21600000 == 6am). -->
+ <integer name="config_defaultNightDisplayCustomEndTime">21600000</integer>
+
<!-- Indicate whether to allow the device to suspend when the screen is off
due to the proximity sensor. This resource should only be set to true
if the sensor HAL correctly handles the proximity sensor as a wake-up source.
diff --git a/core/res/res/values/integers.xml b/core/res/res/values/integers.xml
index 8f8d59e..71ac2f4 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 6e0ad36..fad3488 100644
--- a/core/res/res/values/styles_material.xml
+++ b/core/res/res/values/styles_material.xml
@@ -36,7 +36,7 @@
<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 @@
<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 d426d1a..ff5f7d9 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -2638,4 +2638,34 @@
<!-- 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" />
+
+ <java-symbol type="bool" name="config_nightDisplayAvailable" />
+ <java-symbol type="integer" name="config_defaultNightDisplayAutoMode" />
+ <java-symbol type="integer" name="config_defaultNightDisplayCustomStartTime" />
+ <java-symbol type="integer" name="config_defaultNightDisplayCustomEndTime" />
</resources>
diff --git a/docs/html/_redirects.yaml b/docs/html/_redirects.yaml
index cf56f2b..11e06f1 100644
--- a/docs/html/_redirects.yaml
+++ b/docs/html/_redirects.yaml
@@ -149,9 +149,11 @@
to: /google/play/licensing/index.html
- from: /google/play/billing/billing_about.html
to: /google/play/billing/index.html
-- from: /guide/developing/tools/
+- from: /guide/developing/tools/proguard.html
+ to: /studio/build/shrink-code.html
+- from: /guide/developing/tools/...
to: /studio/command-line/
-- from: /guide/developing/
+- from: /guide/developing/...
to: /studio/
- from: /tools/aidl.html
to: /guide/components/aidl.html
diff --git a/docs/html/distribute/engage/_book.yaml b/docs/html/distribute/engage/_book.yaml
index 87e819a..c371268 100644
--- a/docs/html/distribute/engage/_book.yaml
+++ b/docs/html/distribute/engage/_book.yaml
@@ -31,3 +31,6 @@
- title: Get Feedback with Beta Tests
path: /distribute/engage/beta.html
+
+- title: Interact with Nearby Users
+ path: /distribute/engage/nearby.html
diff --git a/docs/html/distribute/engage/engage_toc.cs b/docs/html/distribute/engage/engage_toc.cs
index 4f3e0af..cc6e2844 100644
--- a/docs/html/distribute/engage/engage_toc.cs
+++ b/docs/html/distribute/engage/engage_toc.cs
@@ -66,6 +66,12 @@
<span class="en">Get Feedback with Beta Tests</span></a>
</div>
</li>
+ <li class="nav-section">
+ <div class="nav-section-header empty" style="font-weight:normal"><a href="<?cs
+ var:toroot?>distribute/engage/nearby.html">
+ <span class="en">Interact with Nearby Users</span></a>
+ </div>
+ </li>
</ul>
<script type="text/javascript">
diff --git a/docs/html/distribute/engage/nearby.jd b/docs/html/distribute/engage/nearby.jd
new file mode 100644
index 0000000..b1571f67
--- /dev/null
+++ b/docs/html/distribute/engage/nearby.jd
@@ -0,0 +1,93 @@
+page.title=Interact with Nearby Users
+page.metaDescription=Use the Nearby feature to interact with nearby people, devices, and beacons.
+page.image=images/distribute/nearby_connections.png
+page.tags="users, nearby, engage"
+@jd:body
+
+<p>Create experiences that seem magical for users who are in close proximity by using the unique
+close-range and cross-platform capabilities of Nearby. Set up multiplayer games, ad-hoc groups,
+sharing, or collaborative sessions so that your users can work or play together more easily when
+they're close.</p>
+
+<p>Learn more about <a href="https://developers.google.com/nearby/">how to add nearby interactions
+to your app or game</a>.</p>
+
+<div class="wrap">
+ <div class="cols" style="margin-top:1em;">
+ <div class="col-4of12">
+ <h3>
+ Messaging
+ </h3>
+ <img src="{@docRoot}images/distribute/nearby_messaging.png">
+ <p class="figure-caption">
+ Find nearby devices and share messages to enable rich interactions and collaboration
+ among users.
+ </p>
+ </div>
+
+ <div class="col-4of12">
+ <h3>
+ Connections
+ </h3>
+ <img src="{@docRoot}images/distribute/nearby_connections.png">
+ <p class="figure-caption">
+ Discover other local devices and create connections that enable real-time, cross-device
+ experiences.
+ </p>
+ </div>
+
+ <div class="col-4of12">
+ <h3>
+ Beacons
+ </h3>
+ <img src="{@docRoot}images/distribute/nearby_beacons.png">
+ <p class="figure-caption">
+ Receive messages from beacons using
+ <a href="https://developers.google.com/beacons/eddystone">Eddystone</a> and add context to
+ location-based apps and games.
+ </p>
+ </div>
+ </div>
+</div>
+
+<p class="note"><strong>Note:</strong> Nearby uses Bluetooth 2.0, Bluetooth 4.0, Wi-Fi, and an
+ultrasonic modem to function over distances of up to 100 feet.</p>
+
+<h2 id="best-practices">Best practices</h2>
+
+<p>The following list contains some helpful tips and best practices that will help you set up
+and use Nearby effectively:
+
+<ul>
+ <li>Use Nearby features sparingly and only when they're needed because they can consume battery
+ life quickly (up to 3.5 times faster than normal).
+ </li>
+
+ <li>Invoke Nearby explicitly with a button, switch, or special screen, and provide a visual
+ indication when the features are actively sending or receiving content.
+ </li>
+
+ <li>Ensure that users are aware of the data that is made visible by Nearby before
+ they start using the features.
+ </li>
+
+ <li>Stop any publish or subscribe operations when the user exits the app or stops the
+ activity that requires Nearby.
+ </li>
+
+ <li>Use the <em>earshot</em> option, which uses only the ultrasonic modem to send and receive
+ messages, to limit the range of Nearby messages to about five feet when privacy is important.
+ </li>
+
+ <li>Accelerate the exchange of messages (when appropriate) by making one device the publisher
+ only, and all other devices subscribers.
+ </li>
+</ul>
+
+<h2 id="related-resources">Related resources</h2>
+
+<div class="resource-widget resource-flow-layout col-13"
+ data-query="collection:distribute/users/nearby"
+ data-sortOrder="-timestamp"
+ data-cardSizes="9x3"
+ data-maxResults="6"></div>
diff --git a/docs/html/distribute/monetize/_book.yaml b/docs/html/distribute/monetize/_book.yaml
index 2ebc695..974e9ed 100644
--- a/docs/html/distribute/monetize/_book.yaml
+++ b/docs/html/distribute/monetize/_book.yaml
@@ -16,3 +16,6 @@
- title: Purchasing
path: /distribute/monetize/payments.html
+
+- title: Drive Conversions
+ path: /distribute/monetize/conversions.html
diff --git a/docs/html/distribute/monetize/conversions.jd b/docs/html/distribute/monetize/conversions.jd
new file mode 100644
index 0000000..20b2333
--- /dev/null
+++ b/docs/html/distribute/monetize/conversions.jd
@@ -0,0 +1,100 @@
+page.title=Drive Conversions
+page.image=images/cards/card-drive-conversions_16-9_2x.png
+page.metaDescription=Discover where your users are coming from, drive engagement, and surface your in-app products to maximize your conversions.
+page.tags="conversions"
+
+@jd:body
+
+<div class="figure">
+ <img src="{@docRoot}images/cards/card-drive-conversions_16-9_2x.png">
+</div>
+
+<p>
+ Users who've made in-app purchases or converted in other ways are more likely
+ to do so again.
+ You can now easily discover where those users are coming from, drive engagement,
+ and surface your in-app products to maximize your conversions.
+</p>
+
+<div class="headerLine">
+ <h2 id="dicover">
+ Discover your most valuable channels
+ </h2>
+
+</div>
+
+<p>
+ From the <strong>User Acquisition</strong> page in the Google Play Developer Console, explore
+ how users convert into spenders across your acquisition channels and find the best prospects
+ for app install campaigns.
+</p>
+
+<p>For more information, view the guide on how to <a class="external-link"
+ href="https://support.google.com/googleplay/android-developer/answer/6263332">
+ measure your app's user acquisition</a> in the Google Play Developer Console Help Center.</p>
+
+</p>
+
+<div class="headerLine">
+ <h2 id="adwords">
+ Re-engage users with AdWords ads
+ </h2>
+
+</div>
+
+<p>
+ Bring users back to your app by creating an AdWords re-engagement campaign.
+ Use display and search ads that appear only to users who have installed your app.
+ Use deep links to bring them to where they'll find the content or actions they're
+ searching for.
+</p>
+
+<p>For more information, view the guide on <a class="external-link"
+ href="https://support.google.com/adwords/topic/3119078?hl=en">campaign settings</a> in the
+ AdWords Help Center.</p>
+
+<div class="headerLine">
+ <h2 id="gift-cards">
+ Drive spending with AdMob in-app purchase ads
+ </h2>
+</div>
+
+<p>Use your Google Analytics data to create Remarketing Audience lists for the high-value
+ users most likely to purchase products within your app. Then create an AdMob campaign that
+ targets these users to increase their awareness of your products.</p>
+
+<p>For more information, view the guide on how to <a class="external-link"
+ href="https://www.google.com/admob/promote.html">drive more in-app purchases and installs</a>
+ in the AdMob platform.</p>
+
+<div class="headerLine">
+ <h2 id="tips">
+ Tips
+ </h2>
+</div>
+<ul>
+<li>Add <a class="external-link"
+ href="https://developers.google.com/app-indexing/webmasters/app">deep links</a>
+ to your app so ads bring users directly to
+ conversion activities.</li>
+<li>Track what users do in your app by installing the
+ AdWords <a class="external-link"
+ href="https://developers.google.com/app-conversion-tracking">
+ Conversion Tracking SDK</a>.</li>
+<li>Link your Google Analytics and AdMob accounts to share audience lists.</li>
+<li>Make conversion ads compelling, such as promoting a booking search or
+ in-app product special offer.</li>
+<li>Use the AdMob Conversion Optimizer with existing campaigns. Predictions
+ are more accurate when there is more data to work with.</li>
+<li>Re-engage with your app's users across the Display Network with remarketing
+ lists in AdMob and with search keywords in AdWords.</li>
+</ul>
+
+
+<div class="headerLine"><h2 id="related-resources">Related resources</h2></div>
+
+<div class="resource-widget resource-flow-layout col-13"
+ data-query="collection:distribute/monetize/conversions"
+ data-sortOrder="-timestamp"
+ data-cardSizes="9x3"
+ data-maxResults="8"></div>
diff --git a/docs/html/distribute/monetize/monetize_toc.cs b/docs/html/distribute/monetize/monetize_toc.cs
index a3aa50fe..b586633 100644
--- a/docs/html/distribute/monetize/monetize_toc.cs
+++ b/docs/html/distribute/monetize/monetize_toc.cs
@@ -34,6 +34,12 @@
</a>
</div>
</li>
+ <li class="nav-section">
+ <div class="nav-section-header empty" style="font-weight:normal"><a href="<?cs var:toroot?>distribute/monetize/conversions.html">
+ <span class="en">Drive Conversions</span>
+ </a>
+ </div>
+ </li>
</ul>
@@ -44,4 +50,3 @@
changeNavLang(getLangPref());
//-->
</script>
-
diff --git a/docs/html/distribute/monetize/payments.jd b/docs/html/distribute/monetize/payments.jd
index 7d972bb..004e47e 100644
--- a/docs/html/distribute/monetize/payments.jd
+++ b/docs/html/distribute/monetize/payments.jd
@@ -15,36 +15,43 @@
instantly with a streamlined, consistent purchasing process and convenient
payment methods.
</p>
-
+<p><strong>Key facts</strong></p>
+<ul>
+<li>Direct carrier billing in over 35 countries.</li>
+<li>Google Play gift cards in over 25 countries.</li>
+<li>PayPal in over 20 countries.</li>
+<li>Developers can sell their apps from over 75 countries.</li>
+<li>Users can buy apps in over 135 countries.</li>
+</ul>
<div class="headerLine">
<h2 id="dcb">
- Direct Carrier Billing
+ Direct carrier billing
</h2>
</div>
<p>
- Users pay by charging their monthly carrier bills . The benefit of Direct
- Carrier Billing is that it opens up markets where credit cards are less
- common, as purchases are charged to your customers’ monthly mobile phone
+ Users pay by charging their monthly carrier bills. The benefit of direct
+ carrier billing is that it opens up markets where credit cards are less
+ common, as purchases are charged to your customers' monthly mobile phone
bills. This option is available to users in key markets
around the world. Many more will get the option in the months ahead.
</p>
<div class="headerLine">
<h2 id="credit">
- Credit Cards
+ Credit cards
</h2>
</div>
<p>
- Users can pay using any credit card that they’ve registered in Google Play.
- Credit Cards added to Google Play are stored in the user’s Google wallet and
- available for in-app purchases and instant buys too. To make it easy for
- users to get started, registration is offered as a part of the initial device
- setup process.
+ Users can pay using any credit card that they've registered in Google Play.
+ The credit cards that a user adds to Google Play are stored in the user's Google wallet.
+ They are available for in-app purchases and instant buys. It's easy for
+ users to get started, as the initial device setup process allows users to register a credit
+ card that they can use in Google Play.
</p>
<div class="headerLine">
@@ -62,16 +69,40 @@
<p>
Gift cards enable users to add value to their Google Play balance by entering
a unique code printed on a card purchased online or from major retailers.
- More information gift cards can be found <a href=
+ More information on gift cards can be found <a href=
"http://play.google.com/intl/en-US_us/about/giftcards/" target=
"_android">here</a>.
</p>
<p style="clear:both">
</p>
+
+<div class="headerLine">
+ <h2 id="paypal">
+ PayPal
+ </h2>
+
+
+</div>
+
+<div class="figure">
+ <img src="{@docRoot}images/paypal-logo.png">
+</div>
+
+<p>
+ Users with PayPal accounts can buy apps and digital content on Google Play using any
+ of their available payment methods. They sign into their PayPal account and
+ complete the purchase. The popularity of PayPal for payments on the web gives
+ you more customers.
+</p>
+
+<p style="clear:both">
+</p>
+
+
<div class="headerLine">
<h2 id="balance">
- Google Play Balance
+ Google Play balance
</h2>
@@ -95,14 +126,13 @@
The payment methods available to users may vary based on location, carrier
network, and other factors.
</p>
-
+
<p style="clear:both">
</p>
-<div class="headerLine"><h2 id="related-resources">Related Resources</h2></div>
+<div class="headerLine"><h2 id="related-resources">Related resources</h2></div>
<div class="resource-widget resource-flow-layout col-13"
data-query="collection:distribute/monetize/paymentmethods"
data-sortOrder="-timestamp"
data-cardSizes="9x3"
data-maxResults="8"></div>
-
diff --git a/docs/html/distribute/users/promote-with-ads.jd b/docs/html/distribute/users/promote-with-ads.jd
index 2db4ca3..d99f449 100644
--- a/docs/html/distribute/users/promote-with-ads.jd
+++ b/docs/html/distribute/users/promote-with-ads.jd
@@ -6,20 +6,27 @@
<p>Users have a huge amount of choice when it comes to which apps they install and
use, so it’s important to actively find new ways to promote your app and drive
-ongoing engagement. AdWords is a powerful and effective way to do both.</p>
+ongoing engagement. AdWords campaigns, which you create in the
+<a href="http://play.google.com/apps/publish">Google Play Developer Console</a>,
+are a powerful and effective way to do both.</p>
-<h2 id=drive_installs>Drive installs</h2>
+<h2 id="drive_installs">Drive installs with universal app campaigns</h2>
-<p><a href="http://adwords.google.com">AdWords</a> promotes your app to interested
-users where they spend time on phones and
-tablets – with app install ads on Google Search, YouTube, Gmail, and within
-apps and across the web on the Google Display Network. AdWords is a powerful
-way to scale app promotion across Google networks and find customers that are
-most likely to install your app. </p>
+<p><a href="http://adwords.google.com">AdWords</a> is a powerful way to scale
+app promotion across Google networks and find customers who are most likely to
+install your app. AdWords promotes your app to interested users where they spend
+time on phones and tablets – with app install ads on Google Play, Google Search,
+YouTube, Gmail, and within apps and across the web.</p>
-<p><a href="https://support.google.com/adwords/answer/6032059">Get started with AdWords
-app install ads</a>.</p>
+<p>By creating a <em>universal app camapign</em>, you can reach all of these
+networks. This type of campaign allocates ads, bids, and budgets automatically,
+making it easier to improve install volume for your app.</p>
+
+<p>To learn more about creating universal ad campaigns, read the article about
+<a class="external-link" href="https://support.google.com/googleplay/android-developer/answer/6262700">creating
+an AdWords campaign for your app</a> in the Google Play Developer Console Help
+Center.</p>
<div class="wrap">
<div class="cols" style="margin-top:1em;">
@@ -27,18 +34,16 @@
<h3>
From Google Play
</h3>
- <img src="/images/distribute/promote_ads_play.png">
+ <img src="{@docRoot}images/distribute/promote_ads_play.png">
<p class="figure-caption">
- Promote your app on Google Play when users are searching and browsing
- for apps.
+ Reach users as they search for apps and games on Google Play.
</p>
</div>
-
<div class="col-4of12">
<h3>
- From search
+ From Google Search
</h3>
- <img src="/images/distribute/promote_ads_search.png">
+ <img src="{@docRoot}images/distribute/promote_ads_search.png">
<p class="figure-caption">
Connect with users as they search for content and services provided by
your app.
@@ -49,47 +54,32 @@
<h3>
From YouTube
</h3>
- <img src="/images/distribute/promote_ads_youtube.png">
+ <img src="{@docRoot}images/distribute/promote_ads_youtube.png">
<p class="figure-caption">
Promote your app when users are watching related videos.
</p>
</div>
- </div>
-</div>
-<div class="wrap">
- <div class="cols" style="margin-top:1em;">
<div class="col-4of12">
<h3>
From apps
</h3>
- <img src="/images/distribute/promote_ads_apps.png">
+ <img src="{@docRoot}images/distribute/promote_ads_apps.png">
<p class="figure-caption">
Reach users while they’re engaged with apps and games across the AdMob
network.
</p>
</div>
-
<div class="col-4of12">
<h3>
From the web
</h3>
- <img src="/images/distribute/promote_ads_web.png">
+ <img src="{@docRoot}images/distribute/promote_ads_web.png">
<p class="figure-caption">
Reach users while they’re engaged with websites across the Google
Display Network.
</p>
</div>
-
- <div class="col-4of12">
- <h3>
- From Gmail
- </h3>
- <img src="/images/distribute/promote_ads_gmail.png">
- <p class="figure-caption">
- Promote your app while users communicate and get things done in Gmail.
- </p>
- </div>
</div>
</div>
@@ -130,77 +120,7 @@
</li>
</ul>
-<h2 id="engage_with_users">
- Engage with users
-</h2>
-
-<p>
- Getting a user to install an app is one thing, but you'll also want them to
- open it regularly. AdWords offers app re-engagement tools to help your app
- stay in mind with users who’ve already installed it on their phone. AdWords
- can remind them of key features and encourage them to try your app again, or
- help them complete an activity they didn't know your app could handle.
-</p>
-
-<div class="wrap">
- <div class="cols" style="margin-top:1em;">
- <div class="col-4of12">
- <h3>
- From search
- </h3>
- <img src="/images/distribute/promote_ads.png">
- <p class="figure-caption">
- Add deep links to your app, then bring users straight to relevant app
- content when they’re searching.
- </p>
- </div>
-
- <div class="col-4of12">
- <h3>
- From apps
- </h3>
- <img src="/images/distribute/promote_ads_inapp.png">
- <p class="figure-caption">
- Use remarketing and deep links to bring users to just the right place
- in your app to re-engage and convert, from other apps and games they
- love.
- </p>
- </div>
- </div>
-</div>
-
-<h3>
- Tips
-</h3>
-
-<ul>
- <li>Track what users do in your app after they’ve clicked an ad, by
- installing the AdWords <a href=
- "https://developers.google.com/app-conversion-tracking/">conversion tracking
- SDK</a>.
- </li>
-
- <li>Advertise a compelling reason for users to re-engage with your app (such
- as a reminder or a special offer).
- </li>
-
- <li>
- <a href="https://developers.google.com/app-indexing/webmasters/app">Add
- deep links</a> to your app that’ll take users directly to the parts of your
- app that will be most relevant and interesting to them, where they can
- easily take action.
- </li>
-
- <li>Re-engage your app users across the display network with remarketing
- lists and search with keywords.
- </li>
-
- <li>Use remarketing lists to target high value users so that you can drive
- more conversions in your app.
- </li>
-</ul>
-
-<h2 id="related-resources">Related Resources</h2>
+<h2 id="related-resources">Related resources</h2>
<div class="resource-widget resource-flow-layout col-13"
data-query="collection:distribute/users/promotewithads"
diff --git a/docs/html/guide/topics/security/permissions.jd b/docs/html/guide/topics/security/permissions.jd
index e7bf760..39a1f814 100644
--- a/docs/html/guide/topics/security/permissions.jd
+++ b/docs/html/guide/topics/security/permissions.jd
@@ -675,7 +675,7 @@
<a name="manifest"></a>
<h3>Enforcing Permissions in AndroidManifest.xml</h3>
-<p>TYou can apply high-level permissions restricting access to entire components
+<p>You can apply high-level permissions restricting access to entire components
of the system or application through your
<code>AndroidManifest.xml</code>. To do this, include an {@link
android.R.attr#permission android:permission} attribute on the desired
diff --git a/docs/html/guide/topics/ui/accessibility/apps.jd b/docs/html/guide/topics/ui/accessibility/apps.jd
index 90781f7..eb639e3 100644
--- a/docs/html/guide/topics/ui/accessibility/apps.jd
+++ b/docs/html/guide/topics/ui/accessibility/apps.jd
@@ -454,18 +454,20 @@
<pre>
@Override
-public void dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
- super.dispatchPopulateAccessibilityEvent(event);
+public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
// Call the super implementation to populate its text to the event, which
// calls onPopulateAccessibilityEvent() on API Level 14 and up.
-
+ boolean completed = super.dispatchPopulateAccessibilityEvent(event);
+
// In case this is running on a API revision earlier that 14, check
// the text content of the event and add an appropriate text
// description for this custom view:
CharSequence text = getText();
if (!TextUtils.isEmpty(text)) {
event.getText().add(text);
+ return true;
}
+ return completed;
}
</pre>
diff --git a/docs/html/images/cards/card-drive-conversions_16-9_2x.png b/docs/html/images/cards/card-drive-conversions_16-9_2x.png
new file mode 100644
index 0000000..3448012
--- /dev/null
+++ b/docs/html/images/cards/card-drive-conversions_16-9_2x.png
Binary files differ
diff --git a/docs/html/images/distribute/nearby_beacons.png b/docs/html/images/distribute/nearby_beacons.png
new file mode 100644
index 0000000..aba2f39
--- /dev/null
+++ b/docs/html/images/distribute/nearby_beacons.png
Binary files differ
diff --git a/docs/html/images/distribute/nearby_connections.png b/docs/html/images/distribute/nearby_connections.png
new file mode 100644
index 0000000..52e6daa
--- /dev/null
+++ b/docs/html/images/distribute/nearby_connections.png
Binary files differ
diff --git a/docs/html/images/distribute/nearby_messaging.png b/docs/html/images/distribute/nearby_messaging.png
new file mode 100644
index 0000000..6ae2d00
--- /dev/null
+++ b/docs/html/images/distribute/nearby_messaging.png
Binary files differ
diff --git a/docs/html/images/distribute/promote_ads_apps.png b/docs/html/images/distribute/promote_ads_apps.png
index 2f57865..1c25be3 100644
--- a/docs/html/images/distribute/promote_ads_apps.png
+++ b/docs/html/images/distribute/promote_ads_apps.png
Binary files differ
diff --git a/docs/html/images/distribute/promote_ads_gmail.png b/docs/html/images/distribute/promote_ads_gmail.png
index 1d21b4a..c1013fc 100644
--- a/docs/html/images/distribute/promote_ads_gmail.png
+++ b/docs/html/images/distribute/promote_ads_gmail.png
Binary files differ
diff --git a/docs/html/images/distribute/promote_ads_play.png b/docs/html/images/distribute/promote_ads_play.png
index 1cf51b2..ae0f84b 100644
--- a/docs/html/images/distribute/promote_ads_play.png
+++ b/docs/html/images/distribute/promote_ads_play.png
Binary files differ
diff --git a/docs/html/images/distribute/promote_ads_search.png b/docs/html/images/distribute/promote_ads_search.png
index 27c0b38..adcede1 100644
--- a/docs/html/images/distribute/promote_ads_search.png
+++ b/docs/html/images/distribute/promote_ads_search.png
Binary files differ
diff --git a/docs/html/images/distribute/promote_ads_web.png b/docs/html/images/distribute/promote_ads_web.png
index 588a3d4..8fefed1 100644
--- a/docs/html/images/distribute/promote_ads_web.png
+++ b/docs/html/images/distribute/promote_ads_web.png
Binary files differ
diff --git a/docs/html/images/distribute/promote_ads_youtube.png b/docs/html/images/distribute/promote_ads_youtube.png
index e88a796..ad44e51 100644
--- a/docs/html/images/distribute/promote_ads_youtube.png
+++ b/docs/html/images/distribute/promote_ads_youtube.png
Binary files differ
diff --git a/docs/html/images/paypal-logo.png b/docs/html/images/paypal-logo.png
new file mode 100644
index 0000000..3e08b95
--- /dev/null
+++ b/docs/html/images/paypal-logo.png
Binary files differ
diff --git a/docs/html/jd_collections.js b/docs/html/jd_collections.js
index aa0620a..03efa86 100644
--- a/docs/html/jd_collections.js
+++ b/docs/html/jd_collections.js
@@ -419,7 +419,8 @@
"distribute/engage/easy-signin.html",
"distribute/analyze/build-better-apps.html",
"distribute/engage/gcm.html",
- "distribute/engage/beta.html"
+ "distribute/engage/beta.html",
+ "distribute/engage/nearby.html"
]
},
"distribute/monetize": {
@@ -430,6 +431,7 @@
"distribute/monetize/ads.html",
"distribute/monetize/ecommerce.html",
"distribute/monetize/payments.html",
+ "distribute/monetize/conversions.html",
"distribute/analyze/understand-user-value.html",
]
},
@@ -761,6 +763,14 @@
"https://support.google.com/adwords/answer/6167162"
]
},
+ "distribute/users/nearby": {
+ "title": "",
+ "resources": [
+ "https://developers.google.com/nearby/",
+ "https://www.youtube.com/watch?v=hultDpBS22s",
+ "https://developers.google.com/beacons"
+ ]
+ },
"distribute/users/buildbuzz": {
"title": "",
"resources": [
@@ -1556,6 +1566,15 @@
"https://support.google.com/googleplay/answer/2651410"
]
},
+ "distribute/monetize/conversions": {
+ "title": "",
+ "resources": [
+ "https://support.google.com/adwords/answer/2471188",
+ "https://developers.google.com/app-conversion-tracking/",
+ "https://support.google.com/analytics/answer/2611404",
+ "https://support.google.com/adwords/answer/1704341"
+ ]
+ },
"autolanding": {
"title": "",
"resources": [
diff --git a/docs/html/jd_extras.js b/docs/html/jd_extras.js
index e5347d9..44ccafa 100644
--- a/docs/html/jd_extras.js
+++ b/docs/html/jd_extras.js
@@ -1942,7 +1942,7 @@
"url": "https://support.google.com/googleplay/answer/2651410",
"timestamp": null,
"image": "images/play_dev.jpg",
- "title": "Google Play Accepted Payment Methods",
+ "title": "Google Play accepted payment methods",
"summary": "Support details on the payment methods supported in Google Play.",
"keywords": ["gift card"],
"type": "distribute",
@@ -1951,6 +1951,59 @@
{
"lang": "en",
"group": "",
+ "tags": [],
+ "url": "https://support.google.com/adwords/answer/2471188",
+ "timestamp": null,
+ "image": "images/play_dev.jpg",
+ "title": "AdWords Conversion Optimizer",
+ "summary": "Learn how Conversion Optimizer works to find the users who are most likely to convert and to serve them your conversion ads.",
+ "keywords": [],
+ "type": "distribute",
+ "titleFriendly": ""
+ },
+ {
+ "lang": "en",
+ "group": "",
+ "tags": [],
+ "url": "https://developers.google.com/app-conversion-tracking/",
+ "timestamp": null,
+ "image": "images/play_dev.jpg",
+ "title": "Track conversions with the AdWords SDK or server API",
+ "summary": "Use the lightweight AdWords app SDK or server-to-server API to track remarketing conversions.",
+ "keywords": [],
+ "type": "distribute",
+ "titleFriendly": ""
+ },
+ {
+ "lang": "en",
+ "group": "",
+ "tags": [],
+ "url": "https://support.google.com/analytics/answer/2611404",
+ "timestamp": null,
+ "image": "images/play_dev.jpg",
+ "title": "Create Remarketing Audiences in Google Analytics",
+ "summary": "Learn how to use preconfigured audiences created by the Analytics team or create your own to use in your conversion campaigns.",
+ "keywords": [],
+ "type": "distribute",
+ "titleFriendly": ""
+ },
+ {
+ "lang": "en",
+ "group": "",
+ "tags": [],
+ "url": "https://support.google.com/adwords/answer/1704341",
+ "timestamp": null,
+ "image": "images/play_dev.jpg",
+ "title": "Link your Google Analytics and AdWords accounts",
+ "summary": "Gain greater insight into how AdWords is driving app engagement and conversions, and use this insight to improve your ads and app.",
+ "keywords": [],
+ "type": "distribute",
+ "titleFriendly": ""
+ },
+
+ {
+ "lang": "en",
+ "group": "",
"tags": ["plus", "social"],
"url": "https://plus.google.com/+AndroidDevelopers/",
"timestamp": null,
@@ -2719,7 +2772,6 @@
"type": "material design",
"titleFriendly": ""
},
-
{
"lang": "en",
"group": "",
@@ -2737,6 +2789,45 @@
"lang": "en",
"group": "",
"tags": [],
+ "url": "https://developers.google.com/nearby/",
+ "timestamp": null,
+ "image": "images/play_dev.jpg",
+ "title": "Create features based on proximity",
+ "summary": "Build simple interactions between nearby devices and people.",
+ "keywords": ["nearby", "engage"],
+ "type": "distribute",
+ "titleFriendly": ""
+ },
+ {
+ "lang": "en",
+ "group": "",
+ "tags": [],
+ "url": "https://www.youtube.com/watch?v=hultDpBS22s",
+ "timestamp": null,
+ "image": "images/play_dev.jpg",
+ "title": "Use Nearby Messages to collaborate",
+ "summary": "Nearby Messages is perfect for setting up ad-hoc groups, collaborative sessions, or sharing resources with people in a co-located space.",
+ "keywords": ["nearby", "engage"],
+ "type": "distribute",
+ "titleFriendly": ""
+ },
+ {
+ "lang": "en",
+ "group": "",
+ "tags": [],
+ "url": "https://developers.google.com/beacons",
+ "timestamp": null,
+ "image": "images/play_dev.jpg",
+ "title": "Mark up the world using beacons",
+ "summary": "Give your users better location and proximity experiences by providing a strong context signal for their devices in the form of Bluetooth low energy (BLE) beacons with Eddystone.",
+ "keywords": ["nearby", "engage"],
+ "type": "distribute",
+ "titleFriendly": ""
+ },
+ {
+ "lang": "en",
+ "group": "",
+ "tags": [],
"url": "https://support.google.com/adwords/answer/6167164",
"timestamp": null,
"image": "distribute/images/advertising.jpg",
diff --git a/docs/html/jd_extras_en.js b/docs/html/jd_extras_en.js
index 9cc110d..434f211 100644
--- a/docs/html/jd_extras_en.js
+++ b/docs/html/jd_extras_en.js
@@ -1943,13 +1943,65 @@
"url": "https://support.google.com/googleplay/answer/2651410",
"timestamp": null,
"image": "images/cards/google-play_2x.png",
- "title": "Google Play Accepted Payment Methods",
+ "title": "Google Play accepted payment methods",
"summary": "Support details on the payment methods supported in Google Play.",
"keywords": ["gift card"],
"type": "distribute",
"category": "google play"
},
{
+ "lang": "en",
+ "group": "",
+ "tags": [],
+ "url": "https://support.google.com/adwords/answer/2471188",
+ "timestamp": null,
+ "image": "images/play_dev.jpg",
+ "title": "AdWords Conversion Optimizer",
+ "summary": "Learn how Conversion Optimizer works to find the users who are most likely to convert and to serve them your conversion ads.",
+ "keywords": [],
+ "type": "distribute",
+ "titleFriendly": ""
+ },
+ {
+ "lang": "en",
+ "group": "",
+ "tags": [],
+ "url": "https://developers.google.com/app-conversion-tracking/",
+ "timestamp": null,
+ "image": "images/play_dev.jpg",
+ "title": "Track conversions with the AdWords SDK or server API",
+ "summary": "Use the lightweight AdWords app SDK or server-to-server API to track remarketing conversions.",
+ "keywords": [],
+ "type": "distribute",
+ "titleFriendly": ""
+ },
+ {
+ "lang": "en",
+ "group": "",
+ "tags": [],
+ "url": "https://support.google.com/analytics/answer/2611404",
+ "timestamp": null,
+ "image": "images/play_dev.jpg",
+ "title": "Create Remarketing Audiences in Google Analytics",
+ "summary": "Learn how to use preconfigured audiences created by the Analytics team or create your own to use in your conversion campaigns.",
+ "keywords": [],
+ "type": "distribute",
+ "titleFriendly": ""
+ },
+ {
+ "lang": "en",
+ "group": "",
+ "tags": [],
+ "url": "https://support.google.com/adwords/answer/1704341",
+ "timestamp": null,
+ "image": "images/play_dev.jpg",
+ "title": "Link your Google Analytics and AdWords accounts",
+ "summary": "Gain greater insight into how AdWords is driving app engagement and conversions, and use this insight to improve your ads and app.",
+ "keywords": [],
+ "type": "distribute",
+ "titleFriendly": ""
+ },
+ {
"lang": "en",
"group": "",
"tags": ["plus", "social"],
@@ -2699,6 +2751,45 @@
"lang": "en",
"group": "",
"tags": [],
+ "url": "https://developers.google.com/nearby/",
+ "timestamp": null,
+ "image": "images/play_dev.jpg",
+ "title": "Create features based on proximity",
+ "summary": "Build simple interactions between nearby devices and people.",
+ "keywords": ["nearby", "engage"],
+ "type": "distribute",
+ "titleFriendly": ""
+ },
+ {
+ "lang": "en",
+ "group": "",
+ "tags": [],
+ "url": "https://www.youtube.com/watch?v=hultDpBS22s",
+ "timestamp": null,
+ "image": "images/play_dev.jpg",
+ "title": "Use Nearby Messages to collaborate",
+ "summary": "Nearby Messages is perfect for setting up ad-hoc groups, collaborative sessions, or sharing resources with people in a co-located space.",
+ "keywords": ["nearby", "engage"],
+ "type": "distribute",
+ "titleFriendly": ""
+ },
+ {
+ "lang": "en",
+ "group": "",
+ "tags": [],
+ "url": "https://developers.google.com/beacons",
+ "timestamp": null,
+ "image": "images/play_dev.jpg",
+ "title": "Mark up the world using beacons",
+ "summary": "Give your users better location and proximity experiences by providing a strong context signal for their devices in the form of Bluetooth low energy (BLE) beacons with Eddystone.",
+ "keywords": ["nearby", "engage"],
+ "type": "distribute",
+ "titleFriendly": ""
+ },
+ {
+ "lang": "en",
+ "group": "",
+ "tags": [],
"url": "https://support.google.com/adwords/answer/6167164",
"timestamp": null,
"image": "distribute/images/advertising.jpg",
@@ -4160,7 +4251,8 @@
"distribute/engage/easy-signin.html",
"distribute/analyze/build-better-apps.html",
"distribute/engage/gcm.html",
- "distribute/engage/beta.html"
+ "distribute/engage/beta.html",
+ "distribute/engage/nearby.html"
]
},
"distribute/monetize": {
@@ -4171,6 +4263,7 @@
"distribute/monetize/ads.html",
"distribute/monetize/ecommerce.html",
"distribute/monetize/payments.html",
+ "distribute/monetize/conversions.html",
"distribute/analyze/understand-user-value.html",
]
},
@@ -4480,6 +4573,14 @@
"https://support.google.com/adwords/answer/6167162"
]
},
+ "distribute/users/nearby": {
+ "title": "",
+ "resources": [
+ "https://developers.google.com/nearby/",
+ "https://www.youtube.com/watch?v=hultDpBS22s",
+ "https://developers.google.com/beacons"
+ ]
+ },
"distribute/users/buildbuzz": {
"title": "",
"resources": [
@@ -5117,6 +5218,15 @@
"https://support.google.com/googleplay/answer/2651410"
]
},
+ "distribute/monetize/conversions": {
+ "title": "",
+ "resources": [
+ "https://support.google.com/adwords/answer/2471188",
+ "https://developers.google.com/app-conversion-tracking/",
+ "https://support.google.com/analytics/answer/2611404",
+ "https://support.google.com/adwords/answer/1704341"
+ ]
+ },
"topic/libraries": {
"title": "",
"resources": [
diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp
index ceef9c7..5003c6a 100644
--- a/libs/hwui/renderthread/CanvasContext.cpp
+++ b/libs/hwui/renderthread/CanvasContext.cpp
@@ -198,6 +198,45 @@
return info && ((*info)[FrameInfoIndex::Flags] & FrameInfoFlags::SkippedFrame);
}
+bool CanvasContext::isSwapChainStuffed() {
+ if (mSwapHistory.size() != mSwapHistory.capacity()) {
+ // We want at least 3 frames of history before attempting to
+ // guess if the queue is stuffed
+ return false;
+ }
+ nsecs_t frameInterval = mRenderThread.timeLord().frameIntervalNanos();
+ auto& swapA = mSwapHistory[0];
+
+ // Was there a happy queue & dequeue time? If so, don't
+ // consider it stuffed
+ if (swapA.dequeueDuration < 3_ms
+ && swapA.queueDuration < 3_ms) {
+ return false;
+ }
+
+ for (size_t i = 1; i < mSwapHistory.size(); i++) {
+ auto& swapB = mSwapHistory[i];
+
+ // If there's a frameInterval gap we effectively already dropped a frame,
+ // so consider the queue healthy.
+ if (swapA.swapCompletedTime - swapB.swapCompletedTime > frameInterval) {
+ return false;
+ }
+
+ // Was there a happy queue & dequeue time? If so, don't
+ // consider it stuffed
+ if (swapB.dequeueDuration < 3_ms
+ && swapB.queueDuration < 3_ms) {
+ return false;
+ }
+
+ swapA = swapB;
+ }
+
+ // All signs point to a stuffed swap chain
+ return true;
+}
+
void CanvasContext::prepareTree(TreeInfo& info, int64_t* uiFrameInfo,
int64_t syncQueued, RenderNode* target) {
mRenderThread.removeFrameCallback(this);
@@ -243,7 +282,12 @@
if (CC_LIKELY(mSwapHistory.size())) {
nsecs_t latestVsync = mRenderThread.timeLord().latestVsync();
- const SwapHistory& lastSwap = mSwapHistory.back();
+ SwapHistory& lastSwap = mSwapHistory.back();
+ int durationUs;
+ mNativeSurface->query(NATIVE_WINDOW_LAST_DEQUEUE_DURATION, &durationUs);
+ lastSwap.dequeueDuration = us2ns(durationUs);
+ mNativeSurface->query(NATIVE_WINDOW_LAST_QUEUE_DURATION, &durationUs);
+ lastSwap.queueDuration = us2ns(durationUs);
nsecs_t vsyncDelta = std::abs(lastSwap.vsyncTime - latestVsync);
// The slight fudge-factor is to deal with cases where
// the vsync was estimated due to being slow handling the signal.
@@ -253,15 +297,12 @@
// Already drew for this vsync pulse, UI draw request missed
// the deadline for RT animations
info.out.canDrawThisFrame = false;
- } else if (lastSwap.swapTime < latestVsync) {
+ } else if (vsyncDelta >= mRenderThread.timeLord().frameIntervalNanos()) {
+ // It's been at least an entire frame interval, assume
+ // the buffer queue is fine
info.out.canDrawThisFrame = true;
} else {
- // We're maybe behind? Find out for sure
- int runningBehind = 0;
- // TODO: Have this method be on Surface, too, not just ANativeWindow...
- ANativeWindow* window = mNativeSurface.get();
- window->query(window, NATIVE_WINDOW_CONSUMER_RUNNING_BEHIND, &runningBehind);
- info.out.canDrawThisFrame = !runningBehind;
+ info.out.canDrawThisFrame = !isSwapChainStuffed();
}
} else {
info.out.canDrawThisFrame = true;
@@ -516,7 +557,7 @@
}
SwapHistory& swap = mSwapHistory.next();
swap.damage = screenDirty;
- swap.swapTime = systemTime(CLOCK_MONOTONIC);
+ swap.swapCompletedTime = systemTime(CLOCK_MONOTONIC);
swap.vsyncTime = mRenderThread.timeLord().latestVsync();
mHaveNewSurface = false;
mFrameNumber = -1;
diff --git a/libs/hwui/renderthread/CanvasContext.h b/libs/hwui/renderthread/CanvasContext.h
index a6eb7ad..b0d980b 100644
--- a/libs/hwui/renderthread/CanvasContext.h
+++ b/libs/hwui/renderthread/CanvasContext.h
@@ -180,6 +180,8 @@
void waitOnFences();
+ bool isSwapChainStuffed();
+
EGLint mLastFrameWidth = 0;
EGLint mLastFrameHeight = 0;
@@ -198,7 +200,9 @@
struct SwapHistory {
SkRect damage;
nsecs_t vsyncTime;
- nsecs_t swapTime;
+ nsecs_t swapCompletedTime;
+ nsecs_t dequeueDuration;
+ nsecs_t queueDuration;
};
RingBuffer<SwapHistory, 3> mSwapHistory;
diff --git a/libs/hwui/renderthread/RenderProxy.cpp b/libs/hwui/renderthread/RenderProxy.cpp
index 54af282..06a24b2 100644
--- a/libs/hwui/renderthread/RenderProxy.cpp
+++ b/libs/hwui/renderthread/RenderProxy.cpp
@@ -514,6 +514,10 @@
post(task);
}
+int RenderProxy::getRenderThreadTid() {
+ return mRenderThread.getTid();
+}
+
CREATE_BRIDGE3(addRenderNode, CanvasContext* context, RenderNode* node, bool placeFront) {
args->context->addRenderNode(args->node, args->placeFront);
return nullptr;
diff --git a/libs/hwui/renderthread/RenderProxy.h b/libs/hwui/renderthread/RenderProxy.h
index 898b314..e31062c8 100644
--- a/libs/hwui/renderthread/RenderProxy.h
+++ b/libs/hwui/renderthread/RenderProxy.h
@@ -115,6 +115,7 @@
ANDROID_API void setTextureAtlas(const sp<GraphicBuffer>& buffer, int64_t* map, size_t size);
ANDROID_API void setProcessStatsBuffer(int fd);
+ ANDROID_API int getRenderThreadTid();
ANDROID_API void serializeDisplayListTree();
diff --git a/media/java/android/media/AudioFormat.java b/media/java/android/media/AudioFormat.java
index a4484e7..7d5939c 100644
--- a/media/java/android/media/AudioFormat.java
+++ b/media/java/android/media/AudioFormat.java
@@ -263,6 +263,10 @@
* on some platforms when converting to short internally.
*/
public static final int ENCODING_IEC61937 = 13;
+ /** Audio data format: DOLBY TRUEHD compressed
+ * @hide
+ **/
+ public static final int ENCODING_DOLBY_TRUEHD = 14;
/** Invalid audio channel configuration */
/** @deprecated Use {@link #CHANNEL_INVALID} instead. */
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index 4bdc70e..f19a262 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -2744,6 +2744,7 @@
* to be notified.
* Use {@link AudioManager#getActiveRecordingConfigurations()} to query the current
* configuration.
+ * @see AudioRecordingConfiguration
*/
public static abstract class AudioRecordingCallback {
/**
@@ -2850,6 +2851,7 @@
* Returns the current active audio recording configurations of the device.
* @return a non-null list of recording configurations. An empty list indicates there is
* no recording active when queried.
+ * @see AudioRecordingConfiguration
*/
public @NonNull List<AudioRecordingConfiguration> getActiveRecordingConfigurations() {
final IAudioService service = getService();
diff --git a/media/java/android/media/AudioRecordingConfiguration.java b/media/java/android/media/AudioRecordingConfiguration.java
index 5935166..354339c 100644
--- a/media/java/android/media/AudioRecordingConfiguration.java
+++ b/media/java/android/media/AudioRecordingConfiguration.java
@@ -28,8 +28,18 @@
/**
* The AudioRecordingConfiguration class collects the information describing an audio recording
- * session. This information is returned through the
- * {@link AudioManager#getActiveRecordingConfigurations()} method.
+ * session.
+ * <p>Direct polling (see {@link AudioManager#getActiveRecordingConfigurations()}) or callback
+ * (see {@link AudioManager#registerAudioRecordingCallback(android.media.AudioManager.AudioRecordingCallback, android.os.Handler)}
+ * methods are ways to receive information about the current recording configuration of the device.
+ * <p>An audio recording configuration contains information about the recording format as used by
+ * the application ({@link #getClientFormat()}, as well as the recording format actually used by
+ * the device ({@link #getFormat()}). The two recording formats may, for instance, be at different
+ * sampling rates due to hardware limitations (e.g. application recording at 44.1kHz whereas the
+ * device always records at 48kHz, and the Android framework resamples for the application).
+ * <p>The configuration also contains the use case for which audio is recorded
+ * ({@link #getClientAudioSource()}), enabling the ability to distinguish between different
+ * activities such as ongoing voice recognition or camcorder recording.
*
*/
public final class AudioRecordingConfiguration implements Parcelable {
@@ -198,4 +208,4 @@
&& (mClientFormat.equals(that.mClientFormat))
&& (mDeviceFormat.equals(that.mDeviceFormat)));
}
-}
\ No newline at end of file
+}
diff --git a/media/java/android/media/MediaScanner.java b/media/java/android/media/MediaScanner.java
index 5fd85d1..c8ab5f9 100644
--- a/media/java/android/media/MediaScanner.java
+++ b/media/java/android/media/MediaScanner.java
@@ -60,11 +60,14 @@
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 @@
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 @@
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 @@
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 @@
mYear = 0;
mDuration = 0;
mPath = path;
+ mDate = 0;
mLastModified = lastModified;
mWriter = null;
mCompilation = 0;
@@ -627,6 +639,14 @@
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 @@
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 @@
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/media/java/android/media/SoundPool.java b/media/java/android/media/SoundPool.java
index 3164930..5ede1d5 100644
--- a/media/java/android/media/SoundPool.java
+++ b/media/java/android/media/SoundPool.java
@@ -133,6 +133,8 @@
private final IAppOpsService mAppOps;
private final IAppOpsCallback mAppOpsCallback;
+ private static IAudioService sService;
+
/**
* Constructor. Constructs a SoundPool object with the following
* characteristics:
@@ -492,7 +494,34 @@
}
}
+ private static IAudioService getService()
+ {
+ if (sService != null) {
+ return sService;
+ }
+ IBinder b = ServiceManager.getService(Context.AUDIO_SERVICE);
+ sService = IAudioService.Stub.asInterface(b);
+ return sService;
+ }
+
private boolean isRestricted() {
+ IAudioService service = getService();
+ boolean cameraSoundForced = false;
+
+ try {
+ cameraSoundForced = service.isCameraSoundForced();
+ } catch (RemoteException e) {
+ Log.e(TAG, "Cannot access AudioService in isRestricted()");
+ }
+
+ if (cameraSoundForced &&
+ ((mAttributes.getAllFlags() & AudioAttributes.FLAG_AUDIBILITY_ENFORCED) != 0)
+// FIXME: should also check usage when set properly by camera app
+// && (mAttributes.getUsage() == AudioAttributes.USAGE_ASSISTANCE_SONIFICATION)
+ ) {
+ return false;
+ }
+
if ((mAttributes.getAllFlags() & AudioAttributes.FLAG_BYPASS_INTERRUPTION_POLICY) != 0) {
return false;
}
diff --git a/packages/DocumentsUI/src/com/android/documentsui/IconUtils.java b/packages/DocumentsUI/src/com/android/documentsui/IconUtils.java
index c28fae8..177ba0d 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.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 @@
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 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 870c343..47df940 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 @@
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 d54bdfd..649dde0 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 @@
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/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java b/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java
index 62f33bf49..a3070bd 100644
--- a/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java
+++ b/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java
@@ -492,23 +492,26 @@
throw new IllegalStateException("Failed to delete " + file);
}
- final ContentResolver resolver = getContext().getContentResolver();
- final Uri externalUri = MediaStore.Files.getContentUri("external");
+ final File visibleFile = getFileForDocId(docId, true);
+ if (visibleFile != null) {
+ final ContentResolver resolver = getContext().getContentResolver();
+ final Uri externalUri = MediaStore.Files.getContentUri("external");
- // Remove media store entries for any files inside this directory, using
- // path prefix match. Logic borrowed from MtpDatabase.
- if (isDirectory) {
- final String path = file.getAbsolutePath() + "/";
+ // Remove media store entries for any files inside this directory, using
+ // path prefix match. Logic borrowed from MtpDatabase.
+ if (isDirectory) {
+ final String path = visibleFile.getAbsolutePath() + "/";
+ resolver.delete(externalUri,
+ "_data LIKE ?1 AND lower(substr(_data,1,?2))=lower(?3)",
+ new String[] { path + "%", Integer.toString(path.length()), path });
+ }
+
+ // Remove media store entry for this exact file.
+ final String path = visibleFile.getAbsolutePath();
resolver.delete(externalUri,
- "_data LIKE ?1 AND lower(substr(_data,1,?2))=lower(?3)",
- new String[] { path + "%", Integer.toString(path.length()), path });
+ "_data LIKE ?1 AND lower(_data)=lower(?2)",
+ new String[] { path, path });
}
-
- // Remove media store entry for this exact file.
- final String path = file.getAbsolutePath();
- resolver.delete(externalUri,
- "_data LIKE ?1 AND lower(_data)=lower(?2)",
- new String[] { path, path });
}
@Override
diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardAbsKeyInputView.java b/packages/Keyguard/src/com/android/keyguard/KeyguardAbsKeyInputView.java
index 60eaad2..038e08d 100644
--- a/packages/Keyguard/src/com/android/keyguard/KeyguardAbsKeyInputView.java
+++ b/packages/Keyguard/src/com/android/keyguard/KeyguardAbsKeyInputView.java
@@ -137,12 +137,21 @@
entry,
userId,
new LockPatternChecker.OnCheckCallback() {
+
+ @Override
+ public void onEarlyMatched() {
+ onPasswordChecked(userId, true /* matched */, 0 /* timeoutMs */,
+ true /* isValidPassword */);
+ }
+
@Override
public void onChecked(boolean matched, int timeoutMs) {
setPasswordEntryInputEnabled(true);
mPendingLockCheck = null;
- onPasswordChecked(userId, matched, timeoutMs,
- true /* isValidPassword */);
+ if (!matched) {
+ onPasswordChecked(userId, false /* matched */, timeoutMs,
+ true /* isValidPassword */);
+ }
}
});
}
diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardPINView.java b/packages/Keyguard/src/com/android/keyguard/KeyguardPINView.java
index 7ea767c..4f5152a 100644
--- a/packages/Keyguard/src/com/android/keyguard/KeyguardPINView.java
+++ b/packages/Keyguard/src/com/android/keyguard/KeyguardPINView.java
@@ -36,6 +36,7 @@
private final AppearAnimationUtils mAppearAnimationUtils;
private final DisappearAnimationUtils mDisappearAnimationUtils;
+ private final DisappearAnimationUtils mDisappearAnimationUtilsLocked;
private ViewGroup mContainer;
private ViewGroup mRow0;
private ViewGroup mRow1;
@@ -44,6 +45,7 @@
private View mDivider;
private int mDisappearYTranslation;
private View[][] mViews;
+ private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
public KeyguardPINView(Context context) {
this(context, null);
@@ -56,8 +58,14 @@
125, 0.6f /* translationScale */,
0.45f /* delayScale */, AnimationUtils.loadInterpolator(
mContext, android.R.interpolator.fast_out_linear_in));
+ mDisappearAnimationUtilsLocked = new DisappearAnimationUtils(context,
+ (long) (125 * KeyguardPatternView.DISAPPEAR_MULTIPLIER_LOCKED),
+ 0.6f /* translationScale */,
+ 0.45f /* delayScale */, AnimationUtils.loadInterpolator(
+ mContext, android.R.interpolator.fast_out_linear_in));
mDisappearYTranslation = getResources().getDimensionPixelSize(
R.dimen.disappear_y_translation);
+ mKeyguardUpdateMonitor = KeyguardUpdateMonitor.getInstance(context);
}
@Override
@@ -136,7 +144,10 @@
setTranslationY(0);
AppearAnimationUtils.startTranslationYAnimation(this, 0 /* delay */, 280 /* duration */,
mDisappearYTranslation, mDisappearAnimationUtils.getInterpolator());
- mDisappearAnimationUtils.startAnimation2d(mViews,
+ DisappearAnimationUtils disappearAnimationUtils = mKeyguardUpdateMonitor.isUserUnlocked()
+ ? mDisappearAnimationUtils
+ : mDisappearAnimationUtilsLocked;
+ disappearAnimationUtils.startAnimation2d(mViews,
new Runnable() {
@Override
public void run() {
diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardPatternView.java b/packages/Keyguard/src/com/android/keyguard/KeyguardPatternView.java
index e070492..84b90c4 100644
--- a/packages/Keyguard/src/com/android/keyguard/KeyguardPatternView.java
+++ b/packages/Keyguard/src/com/android/keyguard/KeyguardPatternView.java
@@ -55,9 +55,13 @@
// how many cells the user has to cross before we poke the wakelock
private static final int MIN_PATTERN_BEFORE_POKE_WAKELOCK = 2;
+ // How much we scale up the duration of the disappear animation when the current user is locked
+ public static final float DISAPPEAR_MULTIPLIER_LOCKED = 1.5f;
+
private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
private final AppearAnimationUtils mAppearAnimationUtils;
private final DisappearAnimationUtils mDisappearAnimationUtils;
+ private final DisappearAnimationUtils mDisappearAnimationUtilsLocked;
private CountDownTimer mCountdownTimer = null;
private LockPatternUtils mLockPatternUtils;
@@ -109,6 +113,10 @@
125, 1.2f /* translationScale */,
0.6f /* delayScale */, AnimationUtils.loadInterpolator(
mContext, android.R.interpolator.fast_out_linear_in));
+ mDisappearAnimationUtilsLocked = new DisappearAnimationUtils(context,
+ (long) (125 * DISAPPEAR_MULTIPLIER_LOCKED), 1.2f /* translationScale */,
+ 0.6f /* delayScale */, AnimationUtils.loadInterpolator(
+ mContext, android.R.interpolator.fast_out_linear_in));
mDisappearYTranslation = getResources().getDimensionPixelSize(
R.dimen.disappear_y_translation);
}
@@ -239,11 +247,21 @@
pattern,
userId,
new LockPatternChecker.OnCheckCallback() {
+
+ @Override
+ public void onEarlyMatched() {
+ onPatternChecked(userId, true /* matched */, 0 /* timeoutMs */,
+ true /* isValidPattern */);
+ }
+
@Override
public void onChecked(boolean matched, int timeoutMs) {
mLockPatternView.enableInput();
mPendingLockCheck = null;
- onPatternChecked(userId, matched, timeoutMs, true);
+ if (!matched) {
+ onPatternChecked(userId, false /* matched */, timeoutMs,
+ true /* isValidPattern */);
+ }
}
});
if (pattern.size() > MIN_PATTERN_BEFORE_POKE_WAKELOCK) {
@@ -390,25 +408,30 @@
@Override
public boolean startDisappearAnimation(final Runnable finishRunnable) {
+ float durationMultiplier = mKeyguardUpdateMonitor.isUserUnlocked()
+ ? 1f
+ : DISAPPEAR_MULTIPLIER_LOCKED;
mLockPatternView.clearPattern();
enableClipping(false);
setTranslationY(0);
- AppearAnimationUtils.startTranslationYAnimation(this, 0 /* delay */, 300 /* duration */,
+ AppearAnimationUtils.startTranslationYAnimation(this, 0 /* delay */,
+ (long) (300 * durationMultiplier),
-mDisappearAnimationUtils.getStartTranslation(),
mDisappearAnimationUtils.getInterpolator());
- mDisappearAnimationUtils.startAnimation2d(mLockPatternView.getCellStates(),
- new Runnable() {
- @Override
- public void run() {
- enableClipping(true);
- if (finishRunnable != null) {
- finishRunnable.run();
- }
+
+ DisappearAnimationUtils disappearAnimationUtils = mKeyguardUpdateMonitor.isUserUnlocked()
+ ? mDisappearAnimationUtils
+ : mDisappearAnimationUtilsLocked;
+ disappearAnimationUtils.startAnimation2d(mLockPatternView.getCellStates(),
+ () -> {
+ enableClipping(true);
+ if (finishRunnable != null) {
+ finishRunnable.run();
}
}, KeyguardPatternView.this);
if (!TextUtils.isEmpty(mSecurityMessageDisplay.getText())) {
mDisappearAnimationUtils.createAnimation(mSecurityMessageDisplay, 0,
- 200,
+ (long) (200 * durationMultiplier),
- mDisappearAnimationUtils.getStartTranslation() * 3,
false /* appearing */,
mDisappearAnimationUtils.getInterpolator(),
diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/Keyguard/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 94d9550..dec1fd2 100644
--- a/packages/Keyguard/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/Keyguard/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -16,6 +16,16 @@
package com.android.keyguard;
+import static android.os.BatteryManager.BATTERY_HEALTH_UNKNOWN;
+import static android.os.BatteryManager.BATTERY_STATUS_FULL;
+import static android.os.BatteryManager.BATTERY_STATUS_UNKNOWN;
+import static android.os.BatteryManager.EXTRA_HEALTH;
+import static android.os.BatteryManager.EXTRA_LEVEL;
+import static android.os.BatteryManager.EXTRA_MAX_CHARGING_CURRENT;
+import static android.os.BatteryManager.EXTRA_MAX_CHARGING_VOLTAGE;
+import static android.os.BatteryManager.EXTRA_PLUGGED;
+import static android.os.BatteryManager.EXTRA_STATUS;
+
import android.app.ActivityManager;
import android.app.ActivityManagerNative;
import android.app.AlarmManager;
@@ -29,7 +39,6 @@
import android.content.IntentFilter;
import android.database.ContentObserver;
import android.graphics.Bitmap;
-import android.hardware.fingerprint.Fingerprint;
import android.hardware.fingerprint.FingerprintManager;
import android.hardware.fingerprint.FingerprintManager.AuthenticationCallback;
import android.hardware.fingerprint.FingerprintManager.AuthenticationResult;
@@ -42,6 +51,7 @@
import android.os.RemoteException;
import android.os.SystemClock;
import android.os.UserHandle;
+import android.os.UserManager;
import android.provider.Settings;
import android.telephony.ServiceState;
import android.telephony.SubscriptionInfo;
@@ -69,16 +79,6 @@
import java.util.List;
import java.util.Map.Entry;
-import static android.os.BatteryManager.BATTERY_HEALTH_UNKNOWN;
-import static android.os.BatteryManager.BATTERY_STATUS_FULL;
-import static android.os.BatteryManager.BATTERY_STATUS_UNKNOWN;
-import static android.os.BatteryManager.EXTRA_HEALTH;
-import static android.os.BatteryManager.EXTRA_LEVEL;
-import static android.os.BatteryManager.EXTRA_MAX_CHARGING_CURRENT;
-import static android.os.BatteryManager.EXTRA_MAX_CHARGING_VOLTAGE;
-import static android.os.BatteryManager.EXTRA_PLUGGED;
-import static android.os.BatteryManager.EXTRA_STATUS;
-
/**
* Watches for updates that may be interesting to the keyguard, and provides
* the up to date information as well as a registration for callbacks that care
@@ -176,6 +176,7 @@
private boolean mGoingToSleep;
private boolean mBouncer;
private boolean mBootCompleted;
+ private boolean mUserUnlocked;
// Device provisioning state
private boolean mDeviceProvisioned;
@@ -202,6 +203,7 @@
private AlarmManager mAlarmManager;
private List<SubscriptionInfo> mSubscriptionInfo;
private TrustManager mTrustManager;
+ private UserManager mUserManager;
private int mFingerprintRunningState = FINGERPRINT_STATE_STOPPED;
private final Handler mHandler = new Handler() {
@@ -554,6 +556,10 @@
&& !hasFingerprintUnlockTimedOut(sCurrentUser);
}
+ public boolean isUserUnlocked() {
+ return mUserUnlocked;
+ }
+
public StrongAuthTracker getStrongAuthTracker() {
return mStrongAuthTracker;
}
@@ -1058,6 +1064,8 @@
if (mFpm != null) {
mFpm.addLockoutResetCallback(mLockoutResetCallback);
}
+
+ mUserManager = context.getSystemService(UserManager.class);
}
private void updateFingerprintListeningState() {
@@ -1390,6 +1398,7 @@
private void handleKeyguardReset() {
if (DEBUG) Log.d(TAG, "handleKeyguardReset");
updateFingerprintListeningState();
+ mUserUnlocked = mUserManager.isUserUnlocked(getCurrentUser());
}
/**
diff --git a/packages/SettingsLib/src/com/android/settingslib/DeviceInfoUtils.java b/packages/SettingsLib/src/com/android/settingslib/DeviceInfoUtils.java
index ff1c866..b04948b 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.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.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 static String getFormattedPhoneNumber(Context context, SubscriptionInfo subscriptionInfo) {
+ String formattedNumber = null;
+ if (subscriptionInfo != null) {
+ final TelephonyManager telephonyManager =
+ (TelephonyManager) context.getSystemService(TELEPHONY_SERVICE);
+ final String rawNumber =
+ telephonyManager.getLine1Number(subscriptionInfo.getSubscriptionId());
+ if (!TextUtils.isEmpty(rawNumber)) {
+ formattedNumber = PhoneNumberUtils.formatNumber(rawNumber);
+ }
+
+ }
+ return formattedNumber;
+ }
+
+ public static String getFormattedPhoneNumbers(Context context,
+ List<SubscriptionInfo> subscriptionInfo) {
+ StringBuilder sb = new StringBuilder();
+ if (subscriptionInfo != null) {
+ final TelephonyManager telephonyManager =
+ (TelephonyManager) context.getSystemService(TELEPHONY_SERVICE);
+ final int count = subscriptionInfo.size();
+ for (int i = 0; i < count; i++) {
+ final String rawNumber = telephonyManager.getLine1Number(
+ subscriptionInfo.get(i).getSubscriptionId());
+ if (!TextUtils.isEmpty(rawNumber)) {
+ sb.append(PhoneNumberUtils.formatNumber(rawNumber));
+ if (i < count - 1) {
+ sb.append("\n");
+ }
+ }
+ }
+ }
+ return sb.toString();
+ }
+
}
diff --git a/packages/SystemUI/res/drawable/ic_night_mode.xml b/packages/SystemUI/res/drawable/ic_night_mode.xml
deleted file mode 100644
index caa7a47..0000000
--- a/packages/SystemUI/res/drawable/ic_night_mode.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
- Copyright (C) 2016 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24.0dp"
- android:height="24.0dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FFFFFFFF"
- android:pathData="M20.71,5.63l-2.34,-2.34c-0.39,-0.39 -1.02,-0.39 -1.41,0.0l-3.12,3.12 -1.93,-1.91 -1.41,1.41 1.42,1.42L3.0,16.25L3.0,21.0l4.75,0.0l8.92,-8.92 1.42,1.42 1.41,-1.41 -1.92,-1.92 3.12,-3.12c0.4,0.0 0.4,-1.0 0.01,-1.42zM6.92,19.0L5.0,17.08l8.06,-8.06 1.92,1.92L6.92,19.0z"/>
-</vector>
diff --git a/packages/SystemUI/res/drawable/ic_night_mode_disabled.xml b/packages/SystemUI/res/drawable/ic_night_mode_disabled.xml
deleted file mode 100644
index 010815a..0000000
--- a/packages/SystemUI/res/drawable/ic_night_mode_disabled.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<!--
- Copyright (C) 2016 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24.0dp"
- android:height="24.0dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#4DFFFFFF"
- android:pathData="M20.71,5.63l-2.34,-2.34c-0.39,-0.39 -1.02,-0.39 -1.41,0.0l-3.12,3.12 -1.93,-1.91 -1.41,1.41 1.42,1.42L3.0,16.25L3.0,21.0l4.75,0.0l8.92,-8.92 1.42,1.42 1.41,-1.41 -1.92,-1.92 3.12,-3.12c0.4,0.0 0.4,-1.0 0.01,-1.42zM6.92,19.0L5.0,17.08l8.06,-8.06 1.92,1.92L6.92,19.0z"/>
-</vector>
diff --git a/packages/SystemUI/res/layout/calibrate_sliders.xml b/packages/SystemUI/res/layout/calibrate_sliders.xml
deleted file mode 100644
index 0dec8a1..0000000
--- a/packages/SystemUI/res/layout/calibrate_sliders.xml
+++ /dev/null
@@ -1,97 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- 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.
--->
-
-<LinearLayout
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:paddingStart="?android:attr/listPreferredItemPaddingStart"
- android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
- android:paddingTop="8dp"
- android:paddingBottom="8dp"
- android:orientation="vertical">
-
- <LinearLayout
- android:id="@+id/r_group"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:paddingTop="4dp"
- android:paddingBottom="4dp"
- android:orientation="horizontal">
-
- <TextView
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="@string/color_modification_r"
- android:gravity="center"
- android:textAppearance="?android:attr/textAppearanceMedium" />
-
- <SeekBar android:id="@*android:id/seekbar"
- android:layout_marginStart="16dp"
- android:layout_gravity="center_vertical"
- android:layout_width="0dp"
- android:layout_height="wrap_content"
- android:layout_weight="1" />
- </LinearLayout>
-
- <LinearLayout
- android:id="@+id/g_group"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:paddingTop="4dp"
- android:paddingBottom="4dp"
- android:orientation="horizontal">
-
- <TextView
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="@string/color_modification_g"
- android:gravity="center"
- android:textAppearance="?android:attr/textAppearanceMedium" />
-
- <SeekBar android:id="@*android:id/seekbar"
- android:layout_marginStart="16dp"
- android:layout_gravity="center_vertical"
- android:layout_width="0dp"
- android:layout_height="wrap_content"
- android:layout_weight="1" />
- </LinearLayout>
-
- <LinearLayout
- android:id="@+id/b_group"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:paddingTop="4dp"
- android:paddingBottom="4dp"
- android:orientation="horizontal">
-
- <TextView
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="@string/color_modification_b"
- android:gravity="center"
- android:textAppearance="?android:attr/textAppearanceMedium" />
-
- <SeekBar android:id="@*android:id/seekbar"
- android:layout_marginStart="16dp"
- android:layout_gravity="center_vertical"
- android:layout_width="0dp"
- android:layout_height="wrap_content"
- android:layout_weight="1" />
- </LinearLayout>
-
-</LinearLayout>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index cd861e14..e1cbbc5 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -141,6 +141,9 @@
<!-- Margin start of the system icons super container -->
<dimen name="system_icons_super_container_margin_start">16dp</dimen>
+ <!-- Margin end of the system icons super container when the avatar is missing. -->
+ <dimen name="system_icons_super_container_avatarless_margin_end">6dp</dimen>
+
<!-- Width for the notification panel and related windows -->
<dimen name="match_parent">-1px</dimen>
<dimen name="standard_notification_panel_width">416dp</dimen>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 4f523f3..3ed0711 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -1336,59 +1336,6 @@
<!-- Notification: Gear: Content description for the gear. [CHAR LIMIT=NONE] -->
<string name="notification_gear_accessibility"><xliff:g id="app_name" example="YouTube">%1$s</xliff:g> notification controls</string>
- <!-- SysUI Tuner: Color and appearance screen title [CHAR LIMIT=50] -->
- <string name="color_and_appearance">Color and appearance</string>
-
- <!-- SysUI Tuner: Name of the night mode feature [CHAR LIMIT=30] -->
- <string name="night_mode">Night mode</string>
-
- <!-- SysUI Tuner: Name of calibrate display dialog [CHAR LIMIT=30] -->
- <string name="calibrate_display">Calibrate display</string>
-
- <!-- SysUI Tuner: Summary of night mode when its on [CHAR LIMIT=NONE] -->
- <string name="night_mode_on">On</string>
-
- <!-- SysUI Tuner: Summary of night mode when its off [CHAR LIMIT=NONE] -->
- <string name="night_mode_off">Off</string>
-
- <!-- SysUI Tuner: Label for switch to turn on night mode automatically [CHAR LIMIT=50] -->
- <string name="turn_on_automatically">Turn on automatically</string>
-
- <!-- SysUI Tuner: Summary for switch to turn on night mode automatically [CHAR LIMIT=NONE] -->
- <string name="turn_on_auto_summary">Switch into Night Mode as appropriate for location and time of day</string>
-
- <!-- SysUI Tuner: Label for section controlling what night mode does [CHAR LIMIT=60] -->
- <string name="when_night_mode_on">When Night Mode is on</string>
-
- <!-- SysUI Tuner: Switch controlling whether dark theme is turned on with night mode [CHAR LIMIT=45] -->
- <string name="use_dark_theme">Use dark theme for Android OS</string>
-
- <!-- SysUI Tuner: Switch controlling whether tint is changed with night mode [CHAR LIMIT=45] -->
- <string name="adjust_tint">Adjust tint</string>
-
- <!-- SysUI Tuner: Switch controlling whether brightness is changed with night mode [CHAR LIMIT=45] -->
- <string name="adjust_brightness">Adjust brightness</string>
-
- <!-- SysUI Tuner: Disclaimer about using dark theme with night mode [CHAR LIMIT=NONE] -->
- <string name="night_mode_disclaimer">The dark theme is applied to
- core areas of Android OS that are normally displayed in a light theme,
- such as Settings.</string>
-
- <!-- Button to apply settings [CHAR LIMIT=30] -->
- <string name="color_apply">Apply</string>
-
- <!-- Title of warning dialog about bad color settings. [CHAR LIMIT=30] -->
- <string name="color_revert_title">Confirm settings</string>
-
- <!-- Message warning user about custom color settings [CHAR LIMIT=NONE] -->
- <string name="color_revert_message">Some color settings can make this
- device unusable. Click OK to confirm these color settings,
- otherwise these settings will reset after 10 seconds.</string>
-
- <string name="color_modification_r" translatable="false">R</string>
- <string name="color_modification_g" translatable="false">G</string>
- <string name="color_modification_b" translatable="false">B</string>
-
<!-- Title of the battery settings detail panel [CHAR LIMIT=20] -->
<string name="battery_panel_title">Battery usage</string>
diff --git a/packages/SystemUI/res/xml/color_and_appearance.xml b/packages/SystemUI/res/xml/color_and_appearance.xml
deleted file mode 100644
index 21f890e..0000000
--- a/packages/SystemUI/res/xml/color_and_appearance.xml
+++ /dev/null
@@ -1,30 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- 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.
--->
-
-<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:sysui="http://schemas.android.com/apk/res-auto"
- android:title="@string/color_and_appearance">
-
- <Preference
- android:key="night_mode"
- android:title="@string/night_mode"
- android:fragment="com.android.systemui.tuner.NightModeFragment" />
-
- <com.android.systemui.tuner.CalibratePreference
- android:key="calibrate"
- android:title="@string/calibrate_display" />
-
-</PreferenceScreen>
diff --git a/packages/SystemUI/res/xml/night_mode.xml b/packages/SystemUI/res/xml/night_mode.xml
deleted file mode 100644
index 34af820..0000000
--- a/packages/SystemUI/res/xml/night_mode.xml
+++ /dev/null
@@ -1,39 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- 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.
--->
-
-<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:sysui="http://schemas.android.com/apk/res-auto"
- android:title="@string/night_mode">
-
- <SwitchPreference
- android:key="auto"
- android:title="@string/turn_on_automatically"
- android:summary="@string/turn_on_auto_summary" />
-
- <PreferenceCategory
- android:title="@string/when_night_mode_on">
-
- <SwitchPreference
- android:key="adjust_tint"
- android:title="@string/adjust_tint" />
-
- <SwitchPreference
- android:key="adjust_brightness"
- android:title="@string/adjust_brightness" />
-
- </PreferenceCategory>
-
-</PreferenceScreen>
diff --git a/packages/SystemUI/res/xml/tuner_prefs.xml b/packages/SystemUI/res/xml/tuner_prefs.xml
index 116bc69..b46e862 100644
--- a/packages/SystemUI/res/xml/tuner_prefs.xml
+++ b/packages/SystemUI/res/xml/tuner_prefs.xml
@@ -100,13 +100,6 @@
</PreferenceScreen>
- <!--
- <Preference
- android:key="color_transform"
- android:title="@string/color_and_appearance"
- android:fragment="com.android.systemui.tuner.ColorAndAppearanceFragment" />
- -->
-
<PreferenceScreen
android:key="volume_and_do_not_disturb"
android:title="@string/volume_and_do_not_disturb">
diff --git a/packages/SystemUI/src/com/android/systemui/Prefs.java b/packages/SystemUI/src/com/android/systemui/Prefs.java
index 907616c..19ae295 100644
--- a/packages/SystemUI/src/com/android/systemui/Prefs.java
+++ b/packages/SystemUI/src/com/android/systemui/Prefs.java
@@ -47,7 +47,6 @@
Key.QS_DATA_SAVER_DIALOG_SHOWN,
Key.QS_INVERT_COLORS_ADDED,
Key.QS_WORK_ADDED,
- Key.QS_NIGHT_ADDED,
})
public @interface Key {
String OVERVIEW_LAST_STACK_TASK_ACTIVE_TIME = "OverviewLastStackTaskActiveTime";
@@ -67,7 +66,6 @@
String QS_DATA_SAVER_DIALOG_SHOWN = "QsDataSaverDialogShown";
String QS_INVERT_COLORS_ADDED = "QsInvertColorsAdded";
String QS_WORK_ADDED = "QsWorkAdded";
- String QS_NIGHT_ADDED = "QsNightAdded";
}
public static boolean getBoolean(Context context, @Key String key, boolean defaultValue) {
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
index 9a36aca..06ce88cc 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
@@ -28,6 +28,7 @@
import com.android.systemui.statusbar.BaseStatusBar;
import com.android.systemui.statusbar.ScrimView;
import com.android.systemui.statusbar.phone.KeyguardBouncer;
+import com.android.systemui.statusbar.phone.LockscreenWallpaper;
import com.android.systemui.statusbar.phone.NotificationIconAreaController;
import com.android.systemui.statusbar.phone.PhoneStatusBar;
import com.android.systemui.statusbar.phone.QSTileHost;
@@ -92,7 +93,7 @@
}
public ScrimController createScrimController(ScrimView scrimBehind, ScrimView scrimInFront,
- View headsUpScrim) {
+ View headsUpScrim, LockscreenWallpaper lockscreenWallpaper) {
return new ScrimController(scrimBehind, scrimInFront, headsUpScrim);
}
diff --git a/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java b/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java
index a5e771f..af2a286 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.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.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 @@
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 @@
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/qs/QSTile.java b/packages/SystemUI/src/com/android/systemui/qs/QSTile.java
index 27b079a..ca853fe 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSTile.java
@@ -39,7 +39,6 @@
import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.statusbar.policy.BluetoothController;
import com.android.systemui.statusbar.policy.CastController;
-import com.android.systemui.statusbar.policy.NightModeController;
import com.android.systemui.statusbar.policy.FlashlightController;
import com.android.systemui.statusbar.policy.HotspotController;
import com.android.systemui.statusbar.policy.KeyguardMonitor;
@@ -447,7 +446,6 @@
UserInfoController getUserInfoController();
BatteryController getBatteryController();
TileServices getTileServices();
- NightModeController getNightModeController();
void removeTile(String tileSpec);
ManagedProfileController getManagedProfileController();
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
index 0f356e0..7bdb1c4 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
@@ -801,9 +801,13 @@
Recents.getTaskLoader().dump(prefix, writer);
String id = Integer.toHexString(System.identityHashCode(this));
+ long lastStackActiveTime = Prefs.getLong(this,
+ Prefs.Key.OVERVIEW_LAST_STACK_TASK_ACTIVE_TIME, -1);
writer.print(prefix); writer.print(TAG);
writer.print(" visible="); writer.print(mIsVisible ? "Y" : "N");
+ writer.print(" lastStackTaskActiveTime="); writer.print(lastStackActiveTime);
+ writer.print(" currentTime="); writer.print(System.currentTimeMillis());
writer.print(" [0x"); writer.print(id); writer.print("]");
writer.println();
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
index 7e1deec..f9e59e7 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 @@
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 26200d0..b5753ba 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 @@
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 e3fe1ab..89789bce 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 @@
* 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 586a8bc..21780a6 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 @@
// 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 4badc42..f3bae20 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.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 @@
}
}
};
+
+ // 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/AutoTileManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java
index 58fbd4c..b742479 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java
@@ -24,7 +24,6 @@
import com.android.systemui.statusbar.policy.DataSaverController.Listener;
import com.android.systemui.statusbar.policy.HotspotController;
import com.android.systemui.statusbar.policy.HotspotController.Callback;
-import com.android.systemui.statusbar.policy.NightModeController;
/**
* Manages which tiles should be automatically added to QS.
@@ -67,35 +66,12 @@
if (!Prefs.getBoolean(context, Key.QS_WORK_ADDED, false)) {
host.getManagedProfileController().addCallback(mProfileCallback);
}
- if (!Prefs.getBoolean(context, Key.QS_NIGHT_ADDED, false)) {
- host.getNightModeController().addListener(mNightModeListener);
- }
}
public void destroy() {
// TODO: Remove any registered listeners.
}
- private final NightModeController.Listener mNightModeListener =
- new NightModeController.Listener() {
- @Override
- public void onNightModeChanged() {
- if (mHost.getNightModeController().isEnabled()) {
- mHost.addTile("night");
- Prefs.putBoolean(mContext, Key.QS_NIGHT_ADDED, true);
- mHandler.post(new Runnable() {
- @Override
- public void run() {
- mHost.getNightModeController().removeListener(mNightModeListener);
- }
- });
- }
- }
-
- @Override
- public void onTwilightAutoChanged() { }
- };
-
private final ManagedProfileController.Callback mProfileCallback =
new ManagedProfileController.Callback() {
@Override
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 b5a48a3..b53a999 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 ArrayList<View> getViews() {
+ return mViews;
+ }
+
public View getCurrentView() {
return mCurrentView;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
index ee88b00..b3e86b5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
@@ -234,7 +234,6 @@
mKeyguardView.setViewMediatorCallback(mCallback);
mContainer.addView(mRoot, mContainer.getChildCount());
mRoot.setVisibility(View.INVISIBLE);
- mRoot.setSystemUiVisibility(View.STATUS_BAR_DISABLE_HOME);
}
protected void removeView() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
index 42f398d..93ed139 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
@@ -18,6 +18,7 @@
import android.content.Context;
import android.content.res.Configuration;
+import android.content.res.Resources;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.util.TypedValue;
@@ -59,6 +60,7 @@
private UserSwitcherController mUserSwitcherController;
private int mSystemIconsSwitcherHiddenExpandedMargin;
+ private int mSystemIconsBaseMargin;
private View mSystemIconsContainer;
public KeyguardStatusBarView(Context context, AttributeSet attrs) {
@@ -137,8 +139,11 @@
}
private void loadDimens() {
- mSystemIconsSwitcherHiddenExpandedMargin = getResources().getDimensionPixelSize(
+ Resources res = getResources();
+ mSystemIconsSwitcherHiddenExpandedMargin = res.getDimensionPixelSize(
R.dimen.system_icons_switcher_hidden_expanded_margin);
+ mSystemIconsBaseMargin = res.getDimensionPixelSize(
+ R.dimen.system_icons_super_container_avatarless_margin_end);
}
private void updateVisibilities() {
@@ -166,7 +171,13 @@
private void updateSystemIconsLayoutParams() {
RelativeLayout.LayoutParams lp =
(LayoutParams) mSystemIconsSuperContainer.getLayoutParams();
- int marginEnd = mKeyguardUserSwitcherShowing ? mSystemIconsSwitcherHiddenExpandedMargin : 0;
+ // If the avatar icon is gone, we need to have some end margin to display the system icons
+ // correctly.
+ int baseMarginEnd = mMultiUserSwitch.getVisibility() == View.GONE
+ ? mSystemIconsBaseMargin
+ : 0;
+ int marginEnd = mKeyguardUserSwitcherShowing ? mSystemIconsSwitcherHiddenExpandedMargin :
+ baseMarginEnd;
if (marginEnd != lp.getMarginEnd()) {
lp.setMarginEnd(marginEnd);
mSystemIconsSuperContainer.setLayoutParams(lp);
@@ -301,6 +312,7 @@
mMultiUserSwitch.setAlpha(1f);
} else {
updateVisibilities();
+ updateSystemIconsLayoutParams();
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarInflaterView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarInflaterView.java
index b7faf15..dd46b08 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.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 @@
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 @@
false);
mRot90.setId(R.id.rot90);
addView(mRot90);
- updateAlternativeOrder();
if (getParent() instanceof NavigationBarView) {
((NavigationBarView) getParent()).updateRotatedViews();
}
@@ -154,20 +152,6 @@
}
}
- 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 23aeae8..53fe6ce 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 @@
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 @@
@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 @@
}
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 fcb1da0..34aaae4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
@@ -788,11 +788,15 @@
mBackdropFront = (ImageView) mBackdrop.findViewById(R.id.backdrop_front);
mBackdropBack = (ImageView) mBackdrop.findViewById(R.id.backdrop_back);
+ if (ENABLE_LOCKSCREEN_WALLPAPER) {
+ mLockscreenWallpaper = new LockscreenWallpaper(mContext, this, mHandler);
+ }
+
ScrimView scrimBehind = (ScrimView) mStatusBarWindow.findViewById(R.id.scrim_behind);
ScrimView scrimInFront = (ScrimView) mStatusBarWindow.findViewById(R.id.scrim_in_front);
View headsUpScrim = mStatusBarWindow.findViewById(R.id.heads_up_scrim);
mScrimController = SystemUIFactory.getInstance().createScrimController(
- scrimBehind, scrimInFront, headsUpScrim);
+ scrimBehind, scrimInFront, headsUpScrim, mLockscreenWallpaper);
if (mScrimSrcModeEnabled) {
Runnable runnable = new Runnable() {
@Override
@@ -822,10 +826,6 @@
mKeyguardBottomArea.getLockIcon());
mKeyguardBottomArea.setKeyguardIndicationController(mKeyguardIndicationController);
- if (ENABLE_LOCKSCREEN_WALLPAPER) {
- mLockscreenWallpaper = new LockscreenWallpaper(mContext, this, mHandler);
- }
-
// set the initial view visibility
setAreThereNotifications();
@@ -3513,6 +3513,7 @@
setControllerUsers();
clearCurrentMediaNotification();
mLockscreenWallpaper.setCurrentUser(newUserId);
+ mScrimController.setCurrentUser(newUserId);
updateMediaMetaData(true, false);
}
@@ -4307,6 +4308,10 @@
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/QSTileHost.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QSTileHost.java
index 011ec22..ca7f905 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QSTileHost.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QSTileHost.java
@@ -60,7 +60,6 @@
import com.android.systemui.statusbar.policy.BluetoothController;
import com.android.systemui.statusbar.policy.CastController;
import com.android.systemui.statusbar.policy.NextAlarmController;
-import com.android.systemui.statusbar.policy.NightModeController;
import com.android.systemui.statusbar.policy.FlashlightController;
import com.android.systemui.statusbar.policy.HotspotController;
import com.android.systemui.statusbar.policy.KeyguardMonitor;
@@ -71,7 +70,6 @@
import com.android.systemui.statusbar.policy.UserInfoController;
import com.android.systemui.statusbar.policy.UserSwitcherController;
import com.android.systemui.statusbar.policy.ZenModeController;
-import com.android.systemui.tuner.NightModeTile;
import com.android.systemui.tuner.TunerService;
import com.android.systemui.tuner.TunerService.Tunable;
@@ -111,7 +109,6 @@
private final TileServices mServices;
private final List<Callback> mCallbacks = new ArrayList<>();
- private final NightModeController mNightModeController;
private final AutoTileManager mAutoTiles;
private final ManagedProfileController mProfileController;
private final NextAlarmController mNextAlarmController;
@@ -144,7 +141,6 @@
mBattery = battery;
mIconController = iconController;
mNextAlarmController = nextAlarmController;
- mNightModeController = new NightModeController(mContext, true);
mProfileController = new ManagedProfileController(this);
final HandlerThread ht = new HandlerThread(QSTileHost.class.getSimpleName(),
@@ -308,10 +304,6 @@
return mIconController;
}
- public NightModeController getNightModeController() {
- return mNightModeController;
- }
-
public ManagedProfileController getManagedProfileController() {
return mProfileController;
}
@@ -448,8 +440,6 @@
else if (tileSpec.equals("user")) return new UserTile(this);
else if (tileSpec.equals("battery")) return new BatteryTile(this);
else if (tileSpec.equals("saver")) return new DataSaverTile(this);
- else if (tileSpec.equals(NightModeTile.NIGHT_MODE_SPEC))
- return new NightModeTile(this);
// Intent tiles.
else if (tileSpec.startsWith(IntentTile.PREFIX)) return IntentTile.create(this,tileSpec);
else if (tileSpec.startsWith(CustomTile.PREFIX)) return CustomTile.create(this,tileSpec);
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 f45967a..3682aa1 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 @@
*/
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 @@
@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 @@
for (int i = childCount - 1; i >= 0; i--) {
super.addView(childList.get(i));
}
- mIsLayoutReverse = isLayoutReverse;
+ mIsLayoutRtl = isLayoutRtl;
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
index 135c294..8b87a7f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
@@ -30,6 +30,7 @@
import android.view.animation.Interpolator;
import android.view.animation.PathInterpolator;
+import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.systemui.R;
import com.android.systemui.statusbar.ExpandableNotificationRow;
import com.android.systemui.statusbar.NotificationData;
@@ -46,10 +47,13 @@
public static final long ANIMATION_DURATION = 220;
public static final Interpolator KEYGUARD_FADE_OUT_INTERPOLATOR
= new PathInterpolator(0f, 0, 0.7f, 1f);
+ public static final Interpolator KEYGUARD_FADE_OUT_INTERPOLATOR_LOCKED
+ = new PathInterpolator(0.3f, 0f, 0.8f, 1f);
private static final float SCRIM_BEHIND_ALPHA = 0.62f;
- private static final float SCRIM_BEHIND_ALPHA_KEYGUARD = 0.45f;
- private static final float SCRIM_BEHIND_ALPHA_UNLOCKING = 0.2f;
+ protected static final float SCRIM_BEHIND_ALPHA_KEYGUARD = 0.45f;
+ protected static final float SCRIM_BEHIND_ALPHA_UNLOCKING = 0.2f;
private static final float SCRIM_IN_FRONT_ALPHA = 0.75f;
+ private static final float SCRIM_IN_FRONT_ALPHA_LOCKED = 0.85f;
private static final int TAG_KEY_ANIM = R.id.scrim;
private static final int TAG_KEY_ANIM_TARGET = R.id.scrim_target;
private static final int TAG_START_ALPHA = R.id.scrim_alpha_start;
@@ -59,6 +63,7 @@
private final ScrimView mScrimInFront;
private final UnlockMethodCache mUnlockMethodCache;
private final View mHeadsUpScrim;
+ private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
private float mScrimBehindAlpha = SCRIM_BEHIND_ALPHA;
private float mScrimBehindAlphaKeyguard = SCRIM_BEHIND_ALPHA_KEYGUARD;
@@ -99,6 +104,7 @@
mHeadsUpScrim = headsUpScrim;
final Context context = scrimBehind.getContext();
mUnlockMethodCache = UnlockMethodCache.getInstance(context);
+ mKeyguardUpdateMonitor = KeyguardUpdateMonitor.getInstance(context);
updateHeadsUpScrim(false);
}
@@ -120,6 +126,13 @@
scheduleUpdate();
}
+ protected void setScrimBehindValues(float scrimBehindAlphaKeyguard,
+ float scrimBehindAlphaUnlocking) {
+ mScrimBehindAlphaKeyguard = scrimBehindAlphaKeyguard;
+ mScrimBehindAlphaUnlocking = scrimBehindAlphaUnlocking;
+ scheduleUpdate();
+ }
+
public void onTrackingStarted() {
mExpanding = true;
mDarkenWhileDragging = !mUnlockMethodCache.canSkipBouncer();
@@ -162,11 +175,19 @@
mAnimateChange = true;
mSkipFirstFrame = skipFirstFrame;
mOnAnimationFinished = onAnimationFinished;
- scheduleUpdate();
- // No need to wait for the next frame to be drawn for this case - onPreDraw will execute
- // the changes we just scheduled.
- onPreDraw();
+ if (mKeyguardUpdateMonitor.isUserUnlocked()) {
+ scheduleUpdate();
+
+ // No need to wait for the next frame to be drawn for this case - onPreDraw will execute
+ // the changes we just scheduled.
+ onPreDraw();
+ } else {
+
+ // In case the user isn't unlocked, make sure to delay a bit because the system is hosed
+ // with too many things in this case, in order to not skip the initial frames.
+ mScrimInFront.postOnAnimationDelayed(this::scheduleUpdate, 16);
+ }
}
public void abortKeyguardFadingOut() {
@@ -211,6 +232,11 @@
return mDozeInFrontAlpha;
}
+ private float getScrimInFrontAlpha() {
+ return mKeyguardUpdateMonitor.isUserUnlocked()
+ ? SCRIM_IN_FRONT_ALPHA
+ : SCRIM_IN_FRONT_ALPHA_LOCKED;
+ }
private void scheduleUpdate() {
if (mUpdatePending) return;
@@ -250,10 +276,10 @@
float fraction = 1 - behindFraction;
fraction = (float) Math.pow(fraction, 0.8f);
behindFraction = (float) Math.pow(behindFraction, 0.8f);
- setScrimInFrontColor(fraction * SCRIM_IN_FRONT_ALPHA);
+ setScrimInFrontColor(fraction * getScrimInFrontAlpha());
setScrimBehindColor(behindFraction * mScrimBehindAlphaKeyguard);
} else if (mBouncerShowing) {
- setScrimInFrontColor(SCRIM_IN_FRONT_ALPHA);
+ setScrimInFrontColor(getScrimInFrontAlpha());
setScrimBehindColor(0f);
} else {
float fraction = Math.max(0, Math.min(mFraction, 1));
@@ -371,7 +397,13 @@
}
private Interpolator getInterpolator() {
- return mAnimateKeyguardFadingOut ? KEYGUARD_FADE_OUT_INTERPOLATOR : mInterpolator;
+ if (mAnimateKeyguardFadingOut && !mKeyguardUpdateMonitor.isUserUnlocked()) {
+ return KEYGUARD_FADE_OUT_INTERPOLATOR_LOCKED;
+ } else if (mAnimateKeyguardFadingOut) {
+ return KEYGUARD_FADE_OUT_INTERPOLATOR;
+ } else {
+ return mInterpolator;
+ }
}
@Override
@@ -542,4 +574,8 @@
R.dimen.heads_up_scrim_height);
mHeadsUpScrim.setLayoutParams(layoutParams);
}
+
+ public void setCurrentUser(int currentUser) {
+ // Don't care in the base class.
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index 8d0d9cb..3691a42 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -53,6 +53,11 @@
private static final long WAKE_AND_UNLOCK_SCRIM_FADEOUT_DURATION_MS = 200;
+ // Duration of the Keyguard dismissal animation in case the user is currently locked. This is to
+ // make everything a bit slower to bridge a gap until the user is unlocked and home screen has
+ // dranw its first frame.
+ private static final long KEYGUARD_DISMISS_DURATION_LOCKED = 2000;
+
private static String TAG = "StatusBarKeyguardViewManager";
protected final Context mContext;
@@ -274,9 +279,12 @@
/**
* Hides the keyguard view
*/
- public void hide(long startTime, final long fadeoutDuration) {
+ public void hide(long startTime, long fadeoutDuration) {
mShowing = false;
+ if (!KeyguardUpdateMonitor.getInstance(mContext).isUserUnlocked()) {
+ fadeoutDuration = KEYGUARD_DISMISS_DURATION_LOCKED;
+ }
long uptimeMillis = SystemClock.uptimeMillis();
long delay = Math.max(0, startTime + HIDE_TIMING_CORRECTION_MS - uptimeMillis);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NightModeController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NightModeController.java
deleted file mode 100644
index 4611ef9..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NightModeController.java
+++ /dev/null
@@ -1,246 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
- * except in compliance with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software distributed under the
- * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the specific language governing
- * permissions and limitations under the License.
- */
-
-package com.android.systemui.statusbar.policy;
-
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.opengl.Matrix;
-import android.provider.Settings.Secure;
-import android.util.MathUtils;
-import com.android.systemui.tuner.TunerService;
-
-import java.util.ArrayList;
-
-/**
- * Listens for changes to twilight from the TwilightService.
- *
- * Also pushes the current matrix to accessibility based on the current twilight
- * and various tuner settings.
- */
-public class NightModeController implements TunerService.Tunable {
-
- public static final String NIGHT_MODE_ADJUST_TINT = "tuner_night_mode_adjust_tint";
- private static final String COLOR_MATRIX_CUSTOM_VALUES = "tuner_color_custom_values";
-
- private static final String ACTION_TWILIGHT_CHANGED = "android.intent.action.TWILIGHT_CHANGED";
-
- private static final String EXTRA_IS_NIGHT = "isNight";
- private static final String EXTRA_AMOUNT = "amount";
-
- // Night mode ~= 3400 K
- private static final float[] NIGHT_VALUES = new float[] {
- 1, 0, 0, 0,
- 0, .754f, 0, 0,
- 0, 0, .516f, 0,
- 0, 0, 0, 1,
- };
- public static final float[] IDENTITY_MATRIX = new float[] {
- 1, 0, 0, 0,
- 0, 1, 0, 0,
- 0, 0, 1, 0,
- 0, 0, 0, 1,
- };
-
- private final ArrayList<Listener> mListeners = new ArrayList<>();
-
- private final Context mContext;
-
- // This is whether or not this is the main NightMode controller in SysUI that should be
- // updating relevant color matrixes or if its in the tuner process getting current state
- // for UI.
- private final boolean mUpdateMatrix;
-
- private float[] mCustomMatrix;
- private boolean mListening;
- private boolean mAdjustTint;
-
- private boolean mIsNight;
- private float mAmount;
- private boolean mIsAuto;
-
- public NightModeController(Context context) {
- this(context, false);
- }
-
- public NightModeController(Context context, boolean updateMatrix) {
- mContext = context;
- mUpdateMatrix = updateMatrix;
- TunerService.get(mContext).addTunable(this, NIGHT_MODE_ADJUST_TINT,
- COLOR_MATRIX_CUSTOM_VALUES, Secure.TWILIGHT_MODE);
- }
-
- public void setNightMode(boolean isNight) {
- if (mIsAuto) {
- if (mIsNight != isNight) {
- TunerService.get(mContext).setValue(Secure.TWILIGHT_MODE, isNight
- ? Secure.TWILIGHT_MODE_AUTO_OVERRIDE_ON
- : Secure.TWILIGHT_MODE_AUTO_OVERRIDE_OFF);
- } else {
- TunerService.get(mContext).setValue(Secure.TWILIGHT_MODE,
- Secure.TWILIGHT_MODE_AUTO);
- }
- } else {
- TunerService.get(mContext).setValue(Secure.TWILIGHT_MODE, isNight
- ? Secure.TWILIGHT_MODE_LOCKED_ON : Secure.TWILIGHT_MODE_LOCKED_OFF);
- }
- }
-
- public void setAuto(boolean auto) {
- mIsAuto = auto;
- if (auto) {
- TunerService.get(mContext).setValue(Secure.TWILIGHT_MODE, Secure.TWILIGHT_MODE_AUTO);
- } else {
- // Lock into the current state
- TunerService.get(mContext).setValue(Secure.TWILIGHT_MODE, mIsNight
- ? Secure.TWILIGHT_MODE_LOCKED_ON : Secure.TWILIGHT_MODE_LOCKED_OFF);
- }
- }
-
- public boolean isAuto() {
- return mIsAuto;
- }
-
- public void setAdjustTint(Boolean newValue) {
- TunerService.get(mContext).setValue(NIGHT_MODE_ADJUST_TINT, ((Boolean) newValue) ? 1 : 0);
- }
-
- public void addListener(Listener listener) {
- mListeners.add(listener);
- listener.onNightModeChanged();
- updateListening();
- }
-
- public void removeListener(Listener listener) {
- mListeners.remove(listener);
- updateListening();
- }
-
- private void updateListening() {
- boolean shouldListen = mListeners.size() != 0 || (mUpdateMatrix && mAdjustTint);
- if (shouldListen == mListening) return;
- mListening = shouldListen;
- if (mListening) {
- mContext.registerReceiver(mReceiver, new IntentFilter(ACTION_TWILIGHT_CHANGED));
- } else {
- mContext.unregisterReceiver(mReceiver);
- }
- }
-
- public boolean isEnabled() {
- if (!mListening) {
- updateNightMode(mContext.registerReceiver(null,
- new IntentFilter(ACTION_TWILIGHT_CHANGED)));
- }
- return mIsNight;
- }
-
- public String getCustomValues() {
- return TunerService.get(mContext).getValue(COLOR_MATRIX_CUSTOM_VALUES);
- }
-
- public void setCustomValues(String values) {
- TunerService.get(mContext).setValue(COLOR_MATRIX_CUSTOM_VALUES, values);
- }
-
- @Override
- public void onTuningChanged(String key, String newValue) {
- if (COLOR_MATRIX_CUSTOM_VALUES.equals(key)) {
- mCustomMatrix = newValue != null ? toValues(newValue) : null;
- updateCurrentMatrix();
- } else if (NIGHT_MODE_ADJUST_TINT.equals(key)) {
- mAdjustTint = newValue == null || Integer.parseInt(newValue) != 0;
- updateListening();
- updateCurrentMatrix();
- } else if (Secure.TWILIGHT_MODE.equals(key)) {
- mIsAuto = newValue != null && Integer.parseInt(newValue) >= Secure.TWILIGHT_MODE_AUTO;
- }
- }
-
- private void updateCurrentMatrix() {
- if (!mUpdateMatrix) return;
- if ((!mAdjustTint || mAmount == 0) && mCustomMatrix == null) {
- TunerService.get(mContext).setValue(Secure.ACCESSIBILITY_DISPLAY_COLOR_MATRIX, null);
- return;
- }
- float[] values = scaleValues(IDENTITY_MATRIX, NIGHT_VALUES, mAdjustTint ? mAmount : 0);
- if (mCustomMatrix != null) {
- values = multiply(values, mCustomMatrix);
- }
- TunerService.get(mContext).setValue(Secure.ACCESSIBILITY_DISPLAY_COLOR_MATRIX,
- toString(values));
- }
-
- private void updateNightMode(Intent intent) {
- mIsNight = intent != null && intent.getBooleanExtra(EXTRA_IS_NIGHT, false);
- mAmount = intent != null ? intent.getFloatExtra(EXTRA_AMOUNT, 0) : 0;
- }
-
- private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- if (ACTION_TWILIGHT_CHANGED.equals(intent.getAction())) {
- updateNightMode(intent);
- updateCurrentMatrix();
- for (int i = 0; i < mListeners.size(); i++) {
- mListeners.get(i).onNightModeChanged();
- }
- }
- }
- };
-
- public interface Listener {
- void onNightModeChanged();
- void onTwilightAutoChanged();
- }
-
- private static float[] multiply(float[] matrix, float[] other) {
- if (matrix == null) {
- return other;
- }
- float[] result = new float[16];
- Matrix.multiplyMM(result, 0, matrix, 0, other, 0);
- return result;
- }
-
- private float[] scaleValues(float[] identityMatrix, float[] nightValues, float amount) {
- float[] values = new float[identityMatrix.length];
- for (int i = 0; i < values.length; i++) {
- values[i] = MathUtils.lerp(identityMatrix[i], nightValues[i], amount);
- }
- return values;
- }
-
- public static String toString(float[] values) {
- StringBuilder builder = new StringBuilder();
- for (int i = 0; i < values.length; i++) {
- if (builder.length() != 0) {
- builder.append(',');
- }
- builder.append(values[i]);
- }
- return builder.toString();
- }
-
- public static float[] toValues(String customValues) {
- String[] strValues = customValues.split(",");
- float[] values = new float[strValues.length];
- for (int i = 0; i < values.length; i++) {
- values[i] = Float.parseFloat(strValues[i]);
- }
- return values;
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/CalibratePreference.java b/packages/SystemUI/src/com/android/systemui/tuner/CalibratePreference.java
deleted file mode 100644
index ff7be13..0000000
--- a/packages/SystemUI/src/com/android/systemui/tuner/CalibratePreference.java
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
- * except in compliance with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software distributed under the
- * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the specific language governing
- * permissions and limitations under the License.
- */
-
-package com.android.systemui.tuner;
-
-import android.content.Context;
-import android.support.v7.preference.DialogPreference;
-import android.util.AttributeSet;
-
-public class CalibratePreference extends DialogPreference {
- public CalibratePreference(Context context, AttributeSet attrs) {
- super(context, attrs);
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/ColorAndAppearanceFragment.java b/packages/SystemUI/src/com/android/systemui/tuner/ColorAndAppearanceFragment.java
deleted file mode 100644
index af95cf9..0000000
--- a/packages/SystemUI/src/com/android/systemui/tuner/ColorAndAppearanceFragment.java
+++ /dev/null
@@ -1,219 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
- * except in compliance with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software distributed under the
- * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the specific language governing
- * permissions and limitations under the License.
- */
-
-package com.android.systemui.tuner;
-
-
-import android.app.AlertDialog;
-import android.app.Dialog;
-import android.app.DialogFragment;
-import android.content.DialogInterface;
-import android.os.Bundle;
-import android.provider.Settings;
-import android.provider.Settings.Secure;
-import android.support.v14.preference.PreferenceFragment;
-import android.support.v7.preference.Preference;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.widget.SeekBar;
-
-import com.android.internal.logging.MetricsLogger;
-import com.android.internal.logging.MetricsProto.MetricsEvent;
-import com.android.systemui.R;
-import com.android.systemui.statusbar.policy.NightModeController;
-
-public class ColorAndAppearanceFragment extends PreferenceFragment {
-
- private static final String KEY_CALIBRATE = "calibrate";
-
- private static final long RESET_DELAY = 10000;
- private static final CharSequence KEY_NIGHT_MODE = "night_mode";
-
- private NightModeController mNightModeController;
-
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- mNightModeController = new NightModeController(getContext());
- }
-
- @Override
- public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
- addPreferencesFromResource(R.xml.color_and_appearance);
- }
-
- @Override
- public void onResume() {
- super.onResume();
- MetricsLogger.visibility(getContext(), MetricsEvent.TUNER_COLOR_AND_APPEARANCE, true);
- // TODO: Figure out better title model for Tuner, to avoid any more of this.
- getActivity().setTitle(R.string.color_and_appearance);
-
- Preference nightMode = findPreference(KEY_NIGHT_MODE);
- nightMode.setSummary(mNightModeController.isEnabled()
- ? R.string.night_mode_on : R.string.night_mode_off);
- }
-
- @Override
- public void onPause() {
- super.onPause();
- MetricsLogger.visibility(getContext(), MetricsEvent.TUNER_COLOR_AND_APPEARANCE, false);
- }
-
- @Override
- public void onDisplayPreferenceDialog(Preference preference) {
- if (preference instanceof CalibratePreference) {
- CalibrateDialog.show(this);
- } else {
- super.onDisplayPreferenceDialog(preference);
- }
- }
-
- private void startRevertTimer() {
- getView().postDelayed(mResetColorMatrix, RESET_DELAY);
- }
-
- private void onApply() {
- MetricsLogger.action(getContext(), MetricsEvent.ACTION_TUNER_CALIBRATE_DISPLAY_CHANGED);
- mNightModeController.setCustomValues(Settings.Secure.getString(
- getContext().getContentResolver(), Secure.ACCESSIBILITY_DISPLAY_COLOR_MATRIX));
- getView().removeCallbacks(mResetColorMatrix);
- }
-
- private void onRevert() {
- getView().removeCallbacks(mResetColorMatrix);
- mResetColorMatrix.run();
- }
-
- private final Runnable mResetColorMatrix = new Runnable() {
- @Override
- public void run() {
- ((DialogFragment) getFragmentManager().findFragmentByTag("RevertWarning")).dismiss();
- Settings.Secure.putString(getContext().getContentResolver(),
- Settings.Secure.ACCESSIBILITY_DISPLAY_COLOR_MATRIX, null);
- }
- };
-
- public static class CalibrateDialog extends DialogFragment implements
- DialogInterface.OnClickListener {
- private float[] mValues;
- private NightModeController mNightModeController;
-
- public static void show(ColorAndAppearanceFragment fragment) {
- CalibrateDialog dialog = new CalibrateDialog();
- dialog.setTargetFragment(fragment, 0);
- dialog.show(fragment.getFragmentManager(), "Calibrate");
- }
-
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- mNightModeController = new NightModeController(getContext());
- String customValues = mNightModeController.getCustomValues();
- if (customValues == null) {
- // Generate this as a string because its the easiest way to generate a copy of the
- // identity.
- customValues = NightModeController.toString(NightModeController.IDENTITY_MATRIX);
- }
- mValues = NightModeController.toValues(customValues);
- }
-
- @Override
- public Dialog onCreateDialog(Bundle savedInstanceState) {
- View v = LayoutInflater.from(getContext()).inflate(R.layout.calibrate_sliders, null);
- bindView(v.findViewById(R.id.r_group), 0);
- bindView(v.findViewById(R.id.g_group), 5);
- bindView(v.findViewById(R.id.b_group), 10);
- MetricsLogger.visible(getContext(), MetricsEvent.TUNER_CALIBRATE_DISPLAY);
- return new AlertDialog.Builder(getContext())
- .setTitle(R.string.calibrate_display)
- .setView(v)
- .setPositiveButton(R.string.color_apply, this)
- .setNegativeButton(android.R.string.cancel, null)
- .create();
- }
-
- @Override
- public void onDismiss(DialogInterface dialog) {
- super.onDismiss(dialog);
- MetricsLogger.hidden(getContext(), MetricsEvent.TUNER_CALIBRATE_DISPLAY);
- }
-
- private void bindView(View view, final int index) {
- SeekBar seekBar = (SeekBar) view.findViewById(com.android.internal.R.id.seekbar);
- seekBar.setMax(1000);
- seekBar.setProgress((int) (1000 * mValues[index]));
- seekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
- @Override
- public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
- mValues[index] = progress / 1000f;
- }
-
- @Override
- public void onStartTrackingTouch(SeekBar seekBar) {
- }
-
- @Override
- public void onStopTrackingTouch(SeekBar seekBar) {
- }
- });
- }
-
- @Override
- public void onClick(DialogInterface dialog, int which) {
- if (mValues[0] == 1 && mValues[5] == 1 && mValues[10] == 1) {
- // Allow removal of matrix by all values set to highest.
- mNightModeController.setCustomValues(null);
- return;
- }
- ((ColorAndAppearanceFragment) getTargetFragment()).startRevertTimer();
- Settings.Secure.putString(getContext().getContentResolver(),
- Settings.Secure.ACCESSIBILITY_DISPLAY_COLOR_MATRIX,
- NightModeController.toString(mValues));
- RevertWarning.show((ColorAndAppearanceFragment) getTargetFragment());
- }
- }
-
- public static class RevertWarning extends DialogFragment
- implements DialogInterface.OnClickListener {
-
- public static void show(ColorAndAppearanceFragment fragment) {
- RevertWarning warning = new RevertWarning();
- warning.setTargetFragment(fragment, 0);
- warning.show(fragment.getFragmentManager(), "RevertWarning");
- }
-
- @Override
- public Dialog onCreateDialog(Bundle savedInstanceState) {
- AlertDialog alertDialog = new AlertDialog.Builder(getContext())
- .setTitle(R.string.color_revert_title)
- .setMessage(R.string.color_revert_message)
- .setPositiveButton(R.string.ok, this)
- .create();
- alertDialog.setCanceledOnTouchOutside(true);
- return alertDialog;
- }
-
- @Override
- public void onCancel(DialogInterface dialog) {
- super.onCancel(dialog);
- ((ColorAndAppearanceFragment) getTargetFragment()).onRevert();
- }
-
- @Override
- public void onClick(DialogInterface dialog, int which) {
- ((ColorAndAppearanceFragment) getTargetFragment()).onApply();
- }
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/NightModeFragment.java b/packages/SystemUI/src/com/android/systemui/tuner/NightModeFragment.java
deleted file mode 100644
index ae2856c..0000000
--- a/packages/SystemUI/src/com/android/systemui/tuner/NightModeFragment.java
+++ /dev/null
@@ -1,188 +0,0 @@
-/**
- * Copyright (c) 2016, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.systemui.tuner;
-
-import android.annotation.Nullable;
-import android.app.UiModeManager;
-import android.content.Context;
-import android.os.Bundle;
-import android.provider.Settings.Secure;
-import android.support.v14.preference.PreferenceFragment;
-import android.support.v14.preference.SwitchPreference;
-import android.support.v7.preference.Preference;
-import android.support.v7.preference.Preference.OnPreferenceChangeListener;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.Switch;
-import com.android.internal.logging.MetricsLogger;
-import com.android.internal.logging.MetricsProto.MetricsEvent;
-import com.android.systemui.R;
-import com.android.systemui.statusbar.policy.NightModeController;
-import com.android.systemui.statusbar.policy.NightModeController.Listener;
-import com.android.systemui.tuner.TunerService.Tunable;
-
-public class NightModeFragment extends PreferenceFragment implements Tunable,
- Listener, OnPreferenceChangeListener {
-
- private static final String TAG = "NightModeFragment";
-
- public static final String EXTRA_SHOW_NIGHT_MODE = "show_night_mode";
-
- private static final CharSequence KEY_AUTO = "auto";
- private static final CharSequence KEY_ADJUST_TINT = "adjust_tint";
- private static final CharSequence KEY_ADJUST_BRIGHTNESS = "adjust_brightness";
-
- private Switch mSwitch;
-
- private NightModeController mNightModeController;
- private SwitchPreference mAutoSwitch;
- private SwitchPreference mAdjustTint;
- private SwitchPreference mAdjustBrightness;
- private UiModeManager mUiModeManager;
-
- @Override
- public void onCreate(@Nullable Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- mNightModeController = new NightModeController(getContext());
- mUiModeManager = getContext().getSystemService(UiModeManager.class);
- }
-
- @Override
- public View onCreateView(LayoutInflater inflater, ViewGroup container,
- Bundle savedInstanceState) {
- final View view = LayoutInflater.from(getContext()).inflate(
- R.layout.night_mode_settings, container, false);
- ((ViewGroup) view).addView(super.onCreateView(inflater, container, savedInstanceState));
- return view;
- }
-
- @Override
- public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
- final Context context = getPreferenceManager().getContext();
-
- addPreferencesFromResource(R.xml.night_mode);
- mAutoSwitch = (SwitchPreference) findPreference(KEY_AUTO);
- mAutoSwitch.setOnPreferenceChangeListener(this);
- mAdjustTint = (SwitchPreference) findPreference(KEY_ADJUST_TINT);
- mAdjustTint.setOnPreferenceChangeListener(this);
- mAdjustBrightness = (SwitchPreference) findPreference(KEY_ADJUST_BRIGHTNESS);
- mAdjustBrightness.setOnPreferenceChangeListener(this);
- }
-
- @Override
- public void onViewCreated(View view, Bundle savedInstanceState) {
- super.onViewCreated(view, savedInstanceState);
- View switchBar = view.findViewById(R.id.switch_bar);
- mSwitch = (Switch) switchBar.findViewById(android.R.id.switch_widget);
- mSwitch.setChecked(mNightModeController.isEnabled());
- switchBar.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- boolean newState = !mNightModeController.isEnabled();
- MetricsLogger.action(getContext(), MetricsEvent.ACTION_TUNER_NIGHT_MODE, newState);
- mNightModeController.setNightMode(newState);
- mSwitch.setChecked(newState);
- }
- });
- }
-
- @Override
- public void onResume() {
- super.onResume();
- MetricsLogger.visibility(getContext(), MetricsEvent.TUNER_NIGHT_MODE, true);
- mNightModeController.addListener(this);
- TunerService.get(getContext()).addTunable(this, Secure.BRIGHTNESS_USE_TWILIGHT,
- NightModeController.NIGHT_MODE_ADJUST_TINT);
- calculateDisabled();
- }
-
- @Override
- public void onPause() {
- super.onPause();
- MetricsLogger.visibility(getContext(), MetricsEvent.TUNER_NIGHT_MODE, false);
- mNightModeController.removeListener(this);
- TunerService.get(getContext()).removeTunable(this);
- }
-
- @Override
- public boolean onPreferenceChange(Preference preference, Object newValue) {
- final Boolean value = (Boolean) newValue;
- if (mAutoSwitch == preference) {
- MetricsLogger.action(getContext(), MetricsEvent.ACTION_TUNER_NIGHT_MODE_AUTO, value);
- mNightModeController.setAuto(value);
- } else if (mAdjustTint == preference) {
- MetricsLogger.action(getContext(),
- MetricsEvent.ACTION_TUNER_NIGHT_MODE_ADJUST_TINT, value);
- mNightModeController.setAdjustTint(value);
- postCalculateDisabled();
- } else if (mAdjustBrightness == preference) {
- MetricsLogger.action(getContext(),
- MetricsEvent.ACTION_TUNER_NIGHT_MODE_ADJUST_BRIGHTNESS, value);
- TunerService.get(getContext()).setValue(Secure.BRIGHTNESS_USE_TWILIGHT,
- value ? 1 : 0);
- postCalculateDisabled();
- } else {
- return false;
- }
- return true;
- }
-
- private void postCalculateDisabled() {
- // Post this because its the easiest way to wait for all state to be calculated.
- getView().post(new Runnable() {
- @Override
- public void run() {
- calculateDisabled();
- }
- });
- }
-
- private void calculateDisabled() {
- int enabledCount = (mAdjustTint.isChecked() ? 1 : 0)
- + (mAdjustBrightness.isChecked() ? 1 : 0);
- if (enabledCount == 1) {
- if (mAdjustTint.isChecked()) {
- mAdjustTint.setEnabled(false);
- } else {
- mAdjustBrightness.setEnabled(false);
- }
- } else {
- mAdjustTint.setEnabled(true);
- mAdjustBrightness.setEnabled(true);
- }
- }
-
- @Override
- public void onTuningChanged(String key, String newValue) {
- if (Secure.BRIGHTNESS_USE_TWILIGHT.equals(key)) {
- mAdjustBrightness.setChecked(newValue != null && Integer.parseInt(newValue) != 0);
- } else if (NightModeController.NIGHT_MODE_ADJUST_TINT.equals(key)) {
- // Default on.
- mAdjustTint.setChecked(newValue == null || Integer.parseInt(newValue) != 0);
- }
- }
-
- @Override
- public void onNightModeChanged() {
- mSwitch.setChecked(mNightModeController.isEnabled());
- }
-
- @Override
- public void onTwilightAutoChanged() {
- mAutoSwitch.setChecked(mNightModeController.isAuto());
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/NightModeTile.java b/packages/SystemUI/src/com/android/systemui/tuner/NightModeTile.java
deleted file mode 100644
index fe44502..0000000
--- a/packages/SystemUI/src/com/android/systemui/tuner/NightModeTile.java
+++ /dev/null
@@ -1,107 +0,0 @@
-/**
- * Copyright (c) 2016, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.systemui.tuner;
-
-import android.content.Intent;
-import com.android.internal.logging.MetricsProto.MetricsEvent;
-import com.android.systemui.Prefs;
-import com.android.systemui.Prefs.Key;
-import com.android.systemui.R;
-import com.android.systemui.qs.QSTile;
-import com.android.systemui.statusbar.policy.NightModeController;
-
-
-public class NightModeTile extends QSTile<QSTile.State> implements NightModeController.Listener {
-
- public static final String NIGHT_MODE_SPEC = "night";
-
- private final NightModeController mNightModeController;
-
- private int mIndex;
- private String mCurrentValue;
-
- private boolean mCustomEnabled;
- private String[] mValues;
- private CharSequence[] mValueTitles;
-
- public NightModeTile(Host host) {
- super(host);
- mNightModeController = host.getNightModeController();
- }
-
- @Override
- public boolean isAvailable() {
- return Prefs.getBoolean(mContext, Key.QS_NIGHT_ADDED, false)
- && TunerService.isTunerEnabled(mContext);
- }
-
- @Override
- public void setListening(boolean listening) {
- if (listening) {
- mNightModeController.addListener(this);
- refreshState();
- } else {
- mNightModeController.removeListener(this);
- }
- }
-
- @Override
- public State newTileState() {
- return new State();
- }
-
- @Override
- public Intent getLongClickIntent() {
- return new Intent(mContext, TunerActivity.class)
- .putExtra(NightModeFragment.EXTRA_SHOW_NIGHT_MODE, true);
- }
-
- @Override
- protected void handleClick() {
- mNightModeController.setNightMode(!mNightModeController.isEnabled());
- refreshState();
- }
-
- @Override
- public CharSequence getTileLabel() {
- return mContext.getString(R.string.night_mode);
- }
-
- @Override
- protected void handleUpdateState(State state, Object arg) {
- // TODO: Right now this is just a dropper, needs an actual night icon.
- boolean enabled = mNightModeController.isEnabled();
- state.icon = ResourceIcon.get(enabled ? R.drawable.ic_night_mode
- : R.drawable.ic_night_mode_disabled);
- state.label = mContext.getString(R.string.night_mode);
- state.contentDescription = mContext.getString(R.string.night_mode);
- }
-
- @Override
- public void onNightModeChanged() {
- refreshState();
- }
-
- @Override
- public void onTwilightAutoChanged() {
- // Don't care.
- }
-
- @Override
- public int getMetricsCategory() {
- return MetricsEvent.QS_COLOR_MATRIX;
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/TunerActivity.java b/packages/SystemUI/src/com/android/systemui/tuner/TunerActivity.java
index 5e5da74..5fe9296 100644
--- a/packages/SystemUI/src/com/android/systemui/tuner/TunerActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/tuner/TunerActivity.java
@@ -39,10 +39,7 @@
final String action = getIntent().getAction();
boolean showDemoMode = action != null && action.equals(
"com.android.settings.action.DEMO_MODE");
- boolean showNightMode = getIntent().getBooleanExtra(
- NightModeFragment.EXTRA_SHOW_NIGHT_MODE, false);
- final PreferenceFragment fragment = showNightMode ? new NightModeFragment()
- : showDemoMode ? new DemoModeFragment()
+ final PreferenceFragment fragment = showDemoMode ? new DemoModeFragment()
: new TunerFragment();
getFragmentManager().beginTransaction().replace(R.id.content_frame,
fragment, TAG_TUNER).commit();
diff --git a/proto/src/metrics_constants.proto b/proto/src/metrics_constants.proto
index 1fb9642..45e3e7e 100644
--- a/proto/src/metrics_constants.proto
+++ b/proto/src/metrics_constants.proto
@@ -2367,6 +2367,10 @@
// ACTION: Settings -> Support -> "Travel Abroad" Button -> Tolled Phone
ACTION_SUPPORT_DIAL_TOLLED = 487;
+ // OPEN: Settings > Display > Night display
+ // CATEGORY: SETTINGS
+ NIGHT_DISPLAY_SETTINGS = 488;
+
// ---- End N-MR1 Constants, all N-MR1 constants go above this line ----
// Add new aosp constants above this line.
// END OF AOSP CONSTANTS
diff --git a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
index feceb14..6defd0f 100644
--- a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
+++ b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
@@ -1905,7 +1905,7 @@
SomeArgs args = SomeArgs.obtain();
args.arg1 = widget.host;
args.arg2 = widget.host.callbacks;
- args.arg3 = updateViews;
+ args.arg3 = updateViews.clone();
args.arg4 = requestTime;
args.argi1 = widget.appWidgetId;
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index fb5b3f8..985d4a8 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.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.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 @@
*/
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 @@
// 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 @@
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
@@ -294,7 +303,7 @@
/**
* indicates a timeout period is over - check if we had a network yet or not
- * and if not, call the timeout calback (but leave the request live until they
+ * and if not, call the timeout callback (but leave the request live until they
* cancel it.
* includes a NetworkRequestInfo
*/
@@ -371,6 +380,16 @@
*/
private static final int EVENT_REGISTER_NETWORK_LISTENER_WITH_INTENT = 31;
+ /**
+ * Indicates a caller has requested to have its callback invoked with
+ * the latest LinkProperties or NetworkCapabilities.
+ *
+ * arg1 = UID of caller
+ * obj = NetworkRequest
+ */
+ private static final int EVENT_REQUEST_LINKPROPERTIES = 32;
+ private static final int EVENT_REQUEST_NETCAPABILITIES = 33;
+
/** Handler thread used for both of the handlers below. */
@VisibleForTesting
protected final HandlerThread mHandlerThread;
@@ -681,6 +700,8 @@
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 +1926,8 @@
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 +1935,7 @@
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 +2180,6 @@
}
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 +2212,50 @@
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 +2345,7 @@
sendUpdatedScoreToFactories(request, 0);
}
}
+ nai.clearLingerState();
if (nai.isSatisfyingRequest(mDefaultRequest.requestId)) {
removeDataActivityTracking(nai);
notifyLockdownVpn(nai);
@@ -2400,7 +2433,10 @@
// 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.
@@ -2421,99 +2457,146 @@
return true;
}
- private void handleReleaseNetworkRequest(NetworkRequest request, int callingUid) {
- NetworkRequestInfo nri = mNetworkRequests.get(request);
+ private NetworkRequestInfo getNriForAppRequest(
+ NetworkRequest request, int callingUid, String requestedOperation) {
+ final NetworkRequestInfo nri = mNetworkRequests.get(request);
+
if (nri != null) {
if (Process.SYSTEM_UID != callingUid && nri.mUid != callingUid) {
- if (DBG) log("Attempt to release unowned NetworkRequest " + request);
- return;
+ log(String.format("UID %d attempted to %s for unowned request %s",
+ callingUid, requestedOperation, nri));
+ return null;
}
- if (VDBG || (DBG && nri.request.isRequest())) log("releasing " + request);
- nri.unlinkDeathRecipient();
- mNetworkRequests.remove(request);
- synchronized (mUidToNetworkRequestCount) {
- int requests = mUidToNetworkRequestCount.get(nri.mUid, 0);
- if (requests < 1) {
- Slog.wtf(TAG, "BUG: too small request count " + requests + " for UID " +
- nri.mUid);
- } else if (requests == 1) {
- mUidToNetworkRequestCount.removeAt(
- mUidToNetworkRequestCount.indexOfKey(nri.mUid));
- } else {
- mUidToNetworkRequestCount.put(nri.mUid, requests - 1);
- }
- }
- mNetworkRequestInfoLogs.log("RELEASE " + nri);
- if (nri.request.isRequest()) {
- // 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;
- }
- }
- }
-
- 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
- // that the network disconnected. LegacyTypeTracker will generate that
- // phantom disconnect for this type.
- if (nri.request.legacyType != TYPE_NONE && nai != null) {
- boolean doRemove = true;
- if (wasKept) {
- // check if any of the remaining requests for this network are for the
- // same legacy type - if so, don't remove the nai
- for (int i = 0; i < nai.numNetworkRequests(); i++) {
- NetworkRequest otherRequest = nai.requestAt(i);
- if (otherRequest.legacyType == nri.request.legacyType &&
- otherRequest.isRequest()) {
- if (DBG) log(" still have other legacy request - leaving");
- doRemove = false;
- }
- }
- }
-
- if (doRemove) {
- mLegacyTypeTracker.remove(nri.request.legacyType, nai, false);
- }
- }
-
- for (NetworkFactoryInfo nfi : mNetworkFactoryInfos.values()) {
- nfi.asyncChannel.sendMessage(android.net.NetworkFactory.CMD_CANCEL_REQUEST,
- nri.request);
- }
- } else {
- // listens don't have a singular affectedNetwork. Check all networks to see
- // if this listen request applies and remove it.
- for (NetworkAgentInfo nai : mNetworkAgentInfos.values()) {
- nai.removeRequest(nri.request.requestId);
- if (nri.request.networkCapabilities.hasSignalStrength() &&
- nai.satisfiesImmutableCapabilitiesOf(nri.request)) {
- updateSignalStrengthThresholds(nai, "RELEASE", nri.request);
- }
- }
- }
- callCallbackForRequest(nri, null, ConnectivityManager.CALLBACK_RELEASED);
}
+
+ return nri;
+ }
+
+ private void handleRequestCallbackUpdate(NetworkRequest request, int callingUid,
+ String description, int callbackType) {
+ final NetworkRequestInfo nri = getNriForAppRequest(request, callingUid, description);
+ if (nri == null) return;
+
+ final NetworkAgentInfo nai = mNetworkForRequestId.get(nri.request.requestId);
+ // The network that is satisfying this request may have changed since
+ // the application requested the update.
+ //
+ // - If the request is no longer satisfied, don't send any updates.
+ // - If the request is satisfied by a different network, it is the
+ // caller's responsibility to check that the Network object in the
+ // callback matches the network that was returned in the last
+ // onAvailable() callback for this request.
+ if (nai == null) return;
+ callCallbackForRequest(nri, nai, callbackType, 0);
+ }
+
+ private void handleRequestLinkProperties(NetworkRequest request, int callingUid) {
+ handleRequestCallbackUpdate(request, callingUid,
+ "request LinkProperties", ConnectivityManager.CALLBACK_IP_CHANGED);
+ }
+
+ private void handleRequestNetworkCapabilities(NetworkRequest request, int callingUid) {
+ handleRequestCallbackUpdate(request, callingUid,
+ "request NetworkCapabilities", ConnectivityManager.CALLBACK_CAP_CHANGED);
+ }
+
+ private void handleReleaseNetworkRequest(NetworkRequest request, int callingUid) {
+ final NetworkRequestInfo nri = getNriForAppRequest(
+ request, callingUid, "release NetworkRequest");
+ if (nri == null) return;
+
+ if (VDBG || (DBG && nri.request.isRequest())) log("releasing " + request);
+ nri.unlinkDeathRecipient();
+ mNetworkRequests.remove(request);
+ synchronized (mUidToNetworkRequestCount) {
+ int requests = mUidToNetworkRequestCount.get(nri.mUid, 0);
+ if (requests < 1) {
+ Slog.wtf(TAG, "BUG: too small request count " + requests + " for UID " +
+ nri.mUid);
+ } else if (requests == 1) {
+ mUidToNetworkRequestCount.removeAt(
+ mUidToNetworkRequestCount.indexOfKey(nri.mUid));
+ } else {
+ mUidToNetworkRequestCount.put(nri.mUid, requests - 1);
+ }
+ }
+ 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
+ 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"));
+ }
+ }
+
+ // 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
+ // that the network disconnected. LegacyTypeTracker will generate that
+ // phantom disconnect for this type.
+ if (nri.request.legacyType != TYPE_NONE && nai != null) {
+ boolean doRemove = true;
+ if (wasKept) {
+ // check if any of the remaining requests for this network are for the
+ // same legacy type - if so, don't remove the nai
+ for (int i = 0; i < nai.numNetworkRequests(); i++) {
+ NetworkRequest otherRequest = nai.requestAt(i);
+ if (otherRequest.legacyType == nri.request.legacyType &&
+ otherRequest.isRequest()) {
+ if (DBG) log(" still have other legacy request - leaving");
+ doRemove = false;
+ }
+ }
+ }
+
+ if (doRemove) {
+ mLegacyTypeTracker.remove(nri.request.legacyType, nai, false);
+ }
+ }
+
+ for (NetworkFactoryInfo nfi : mNetworkFactoryInfos.values()) {
+ nfi.asyncChannel.sendMessage(android.net.NetworkFactory.CMD_CANCEL_REQUEST,
+ nri.request);
+ }
+ } else {
+ // listens don't have a singular affectedNetwork. Check all networks to see
+ // if this listen request applies and remove it.
+ for (NetworkAgentInfo nai : mNetworkAgentInfos.values()) {
+ nai.removeRequest(nri.request.requestId);
+ if (nri.request.networkCapabilities.hasSignalStrength() &&
+ nai.satisfiesImmutableCapabilitiesOf(nri.request)) {
+ updateSignalStrengthThresholds(nai, "RELEASE", nri.request);
+ }
+ }
+ }
+ callCallbackForRequest(nri, null, ConnectivityManager.CALLBACK_RELEASED, 0);
}
@Override
@@ -2676,6 +2759,12 @@
handleMobileDataAlwaysOn();
break;
}
+ case EVENT_REQUEST_LINKPROPERTIES:
+ handleRequestLinkProperties((NetworkRequest) msg.obj, msg.arg1);
+ break;
+ case EVENT_REQUEST_NETCAPABILITIES:
+ handleRequestNetworkCapabilities((NetworkRequest) msg.obj, msg.arg1);
+ break;
// Sent by KeepaliveTracker to process an app request on the state machine thread.
case NetworkAgent.CMD_START_PACKET_KEEPALIVE: {
mKeepaliveTracker.handleStartKeepalive(msg);
@@ -4137,10 +4226,26 @@
}
@Override
+ public void requestLinkProperties(NetworkRequest networkRequest) {
+ ensureNetworkRequestHasType(networkRequest);
+ if (networkRequest.type == NetworkRequest.Type.LISTEN) return;
+ mHandler.sendMessage(mHandler.obtainMessage(
+ EVENT_REQUEST_LINKPROPERTIES, getCallingUid(), 0, networkRequest));
+ }
+
+ @Override
+ public void requestNetworkCapabilities(NetworkRequest networkRequest) {
+ ensureNetworkRequestHasType(networkRequest);
+ if (networkRequest.type == NetworkRequest.Type.LISTEN) return;
+ mHandler.sendMessage(mHandler.obtainMessage(
+ EVENT_REQUEST_NETCAPABILITIES, getCallingUid(), 0, networkRequest));
+ }
+
+ @Override
public void releaseNetworkRequest(NetworkRequest networkRequest) {
ensureNetworkRequestHasType(networkRequest);
- mHandler.sendMessage(mHandler.obtainMessage(EVENT_RELEASE_NETWORK_REQUEST, getCallingUid(),
- 0, networkRequest));
+ mHandler.sendMessage(mHandler.obtainMessage(
+ EVENT_RELEASE_NETWORK_REQUEST, getCallingUid(), 0, networkRequest));
}
@Override
@@ -4499,7 +4604,7 @@
}
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 +4616,7 @@
}
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 +4663,14 @@
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 +4715,7 @@
// 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 +4761,12 @@
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 +4814,7 @@
// 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 +4839,15 @@
// 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 +4913,19 @@
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 +4952,9 @@
// 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 +4968,8 @@
// 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 +5079,8 @@
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 +5128,8 @@
// 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 +5181,24 @@
}
}
- 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 +5329,11 @@
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 d64fe32..e28fa73 100644
--- a/services/core/java/com/android/server/LockSettingsService.java
+++ b/services/core/java/com/android/server/LockSettingsService.java
@@ -69,6 +69,7 @@
import android.util.Slog;
import com.android.internal.util.ArrayUtils;
+import com.android.internal.widget.ICheckCredentialProgressCallback;
import com.android.internal.widget.ILockSettings;
import com.android.internal.widget.LockPatternUtils;
import com.android.internal.widget.VerifyCredentialResponse;
@@ -300,7 +301,8 @@
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 {
@@ -347,7 +349,7 @@
CharSequence detail = r.getText(
com.android.internal.R.string.user_encrypted_detail);
- PendingIntent intent = PendingIntent.getBroadcast(mContext, 0, ACTION_NULL,
+ PendingIntent intent = PendingIntent.getActivity(mContext, 0, ACTION_NULL,
PendingIntent.FLAG_UPDATE_CURRENT);
showEncryptionNotification(user, title, message, detail, intent);
@@ -407,7 +409,9 @@
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)) {
@@ -757,7 +761,7 @@
private void unlockChildProfile(int profileHandle) throws RemoteException {
try {
doVerifyPassword(getDecryptedPasswordForTiedProfile(profileHandle), false,
- 0 /* no challenge */, profileHandle);
+ 0 /* no challenge */, profileHandle, null /* progressCallback */);
} catch (UnrecoverableKeyException | InvalidKeyException | KeyStoreException
| NoSuchAlgorithmException | NoSuchPaddingException
| InvalidAlgorithmParameterException | IllegalBlockSizeException
@@ -944,7 +948,7 @@
CredentialHash willStore
= new CredentialHash(enrolledHandle, CredentialHash.VERSION_GATEKEEPER);
setUserKeyProtection(userId, pattern,
- doVerifyPattern(pattern, willStore, true, 0, userId));
+ doVerifyPattern(pattern, willStore, true, 0, userId, null /* progressCallback */));
mStorage.writePatternHash(enrolledHandle, userId);
fixateNewestUserKeyAuth(userId);
onUserLockChanged(userId);
@@ -1004,7 +1008,8 @@
CredentialHash willStore
= new CredentialHash(enrolledHandle, CredentialHash.VERSION_GATEKEEPER);
setUserKeyProtection(userId, password,
- doVerifyPassword(password, willStore, true, 0, userId));
+ doVerifyPassword(password, willStore, true, 0, userId,
+ null /* progressCallback */));
mStorage.writePasswordHash(enrolledHandle, userId);
fixateNewestUserKeyAuth(userId);
onUserLockChanged(userId);
@@ -1202,25 +1207,29 @@
}
@Override
- public VerifyCredentialResponse checkPattern(String pattern, int userId) throws RemoteException {
- return doVerifyPattern(pattern, false, 0, userId);
+ public VerifyCredentialResponse checkPattern(String pattern, int userId,
+ ICheckCredentialProgressCallback progressCallback) throws RemoteException {
+ return doVerifyPattern(pattern, false, 0, userId, progressCallback);
}
@Override
public VerifyCredentialResponse verifyPattern(String pattern, long challenge, int userId)
throws RemoteException {
- return doVerifyPattern(pattern, true, challenge, userId);
+ return doVerifyPattern(pattern, true, challenge, userId, null /* progressCallback */);
}
private VerifyCredentialResponse doVerifyPattern(String pattern, boolean hasChallenge,
- long challenge, int userId) throws RemoteException {
+ long challenge, int userId, ICheckCredentialProgressCallback progressCallback)
+ throws RemoteException {
checkPasswordReadPermission(userId);
CredentialHash storedHash = mStorage.readPatternHash(userId);
- return doVerifyPattern(pattern, storedHash, hasChallenge, challenge, userId);
+ return doVerifyPattern(pattern, storedHash, hasChallenge, challenge, userId,
+ progressCallback);
}
private VerifyCredentialResponse doVerifyPattern(String pattern, CredentialHash storedHash,
- boolean hasChallenge, long challenge, int userId) throws RemoteException {
+ boolean hasChallenge, long challenge, int userId,
+ ICheckCredentialProgressCallback progressCallback) throws RemoteException {
boolean shouldReEnrollBaseZero = storedHash != null && storedHash.isBaseZeroPattern;
String patternToVerify;
@@ -1249,7 +1258,8 @@
public String adjustForKeystore(String pattern) {
return LockPatternUtils.patternStringToBaseZero(pattern);
}
- }
+ },
+ progressCallback
);
if (response.getResponseCode() == VerifyCredentialResponse.RESPONSE_OK
@@ -1261,15 +1271,15 @@
}
@Override
- public VerifyCredentialResponse checkPassword(String password, int userId)
- throws RemoteException {
- return doVerifyPassword(password, false, 0, userId);
+ public VerifyCredentialResponse checkPassword(String password, int userId,
+ ICheckCredentialProgressCallback progressCallback) throws RemoteException {
+ return doVerifyPassword(password, false, 0, userId, progressCallback);
}
@Override
public VerifyCredentialResponse verifyPassword(String password, long challenge, int userId)
throws RemoteException {
- return doVerifyPassword(password, true, challenge, userId);
+ return doVerifyPassword(password, true, challenge, userId, null /* progressCallback */);
}
@Override
@@ -1282,8 +1292,10 @@
final int parentProfileId = mUserManager.getProfileParent(userId).id;
// Unlock parent by using parent's challenge
final VerifyCredentialResponse parentResponse = isPattern
- ? doVerifyPattern(password, true, challenge, parentProfileId)
- : doVerifyPassword(password, true, challenge, parentProfileId);
+ ? doVerifyPattern(password, true, challenge, parentProfileId,
+ null /* progressCallback */)
+ : doVerifyPassword(password, true, challenge, parentProfileId,
+ null /* progressCallback */);
if (parentResponse.getResponseCode() != VerifyCredentialResponse.RESPONSE_OK) {
// Failed, just return parent's response
return parentResponse;
@@ -1293,7 +1305,7 @@
// Unlock work profile, and work profile with unified lock must use password only
return doVerifyPassword(getDecryptedPasswordForTiedProfile(userId), true,
challenge,
- userId);
+ userId, null /* progressCallback */);
} catch (UnrecoverableKeyException | InvalidKeyException | KeyStoreException
| NoSuchAlgorithmException | NoSuchPaddingException
| InvalidAlgorithmParameterException | IllegalBlockSizeException
@@ -1304,14 +1316,17 @@
}
private VerifyCredentialResponse doVerifyPassword(String password, boolean hasChallenge,
- long challenge, int userId) throws RemoteException {
+ long challenge, int userId, ICheckCredentialProgressCallback progressCallback)
+ throws RemoteException {
checkPasswordReadPermission(userId);
CredentialHash storedHash = mStorage.readPasswordHash(userId);
- return doVerifyPassword(password, storedHash, hasChallenge, challenge, userId);
+ return doVerifyPassword(password, storedHash, hasChallenge, challenge, userId,
+ progressCallback);
}
private VerifyCredentialResponse doVerifyPassword(String password, CredentialHash storedHash,
- boolean hasChallenge, long challenge, int userId) throws RemoteException {
+ boolean hasChallenge, long challenge, int userId,
+ ICheckCredentialProgressCallback progressCallback) throws RemoteException {
return verifyCredential(userId, storedHash, password, hasChallenge, challenge,
new CredentialUtil() {
@Override
@@ -1329,12 +1344,12 @@
public String adjustForKeystore(String password) {
return password;
}
- }
- );
+ }, progressCallback);
}
private VerifyCredentialResponse verifyCredential(int userId, CredentialHash storedHash,
- String credential, boolean hasChallenge, long challenge, CredentialUtil credentialUtil)
+ String credential, boolean hasChallenge, long challenge, CredentialUtil credentialUtil,
+ ICheckCredentialProgressCallback progressCallback)
throws RemoteException {
if ((storedHash == null || storedHash.hash.length == 0) && TextUtils.isEmpty(credential)) {
// don't need to pass empty credentials to GateKeeper
@@ -1391,7 +1406,13 @@
}
if (response.getResponseCode() == VerifyCredentialResponse.RESPONSE_OK) {
+
+
// credential has matched
+
+ if (progressCallback != null) {
+ progressCallback.onCredentialVerified();
+ }
unlockKeystore(credential, userId);
Slog.i(TAG, "Unlocking user " + userId +
@@ -1447,7 +1468,7 @@
try {
if (mLockPatternUtils.isLockPatternEnabled(userId)) {
- if (checkPattern(password, userId).getResponseCode()
+ if (checkPattern(password, userId, null /* progressCallback */).getResponseCode()
== GateKeeperResponse.RESPONSE_OK) {
return true;
}
@@ -1457,7 +1478,7 @@
try {
if (mLockPatternUtils.isLockPasswordEnabled(userId)) {
- if (checkPassword(password, userId).getResponseCode()
+ if (checkPassword(password, userId, null /* progressCallback */).getResponseCode()
== GateKeeperResponse.RESPONSE_OK) {
return true;
}
diff --git a/services/core/java/com/android/server/PinnerService.java b/services/core/java/com/android/server/PinnerService.java
index e63f536..eaf317a 100644
--- a/services/core/java/com/android/server/PinnerService.java
+++ b/services/core/java/com/android/server/PinnerService.java
@@ -28,6 +28,10 @@
import android.util.Slog;
import android.os.Binder;
import android.os.Build;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.Process;
import android.provider.MediaStore;
import android.system.ErrnoException;
import android.system.Os;
@@ -35,6 +39,7 @@
import android.system.StructStat;
import com.android.internal.app.ResolverActivity;
+import com.android.internal.os.BackgroundThread;
import dalvik.system.VMRuntime;
@@ -64,6 +69,8 @@
private final long MAX_CAMERA_PIN_SIZE = 50 * (1 << 20); //50MB max
+ private PinnerHandler mPinnerHandler = null;
+
public PinnerService(Context context) {
super(context);
@@ -71,6 +78,7 @@
mContext = context;
mShouldPinCamera = context.getResources().getBoolean(
com.android.internal.R.bool.config_pinnerCameraApp);
+ mPinnerHandler = new PinnerHandler(BackgroundThread.get().getLooper());
}
@Override
@@ -80,22 +88,8 @@
}
mBinderService = new BinderService();
publishBinderService("pinner", mBinderService);
-
- // Files to pin come from the overlay and can be specified per-device config
- String[] filesToPin = mContext.getResources().getStringArray(
- com.android.internal.R.array.config_defaultPinnerServiceFiles);
- // Continue trying to pin remaining files even if there is a failure
- for (int i = 0; i < filesToPin.length; i++){
- PinnedFile pf = pinFile(filesToPin[i], 0, 0, 0);
- if (pf != null) {
- mPinnedFiles.add(pf);
- if (DEBUG) {
- Slog.i(TAG, "Pinned file = " + pf.mFilename);
- }
- } else {
- Slog.e(TAG, "Failed to pin file = " + filesToPin[i]);
- }
- }
+ mPinnerHandler.sendMessage(
+ mPinnerHandler.obtainMessage(PinnerHandler.PIN_ONSTART_MSG));
}
/**
@@ -106,27 +100,57 @@
*/
@Override
public void onUnlockUser(int userHandle) {
- handlePin(userHandle);
+ mPinnerHandler.sendMessage(
+ mPinnerHandler.obtainMessage(PinnerHandler.PIN_CAMERA_MSG, userHandle, 0));
}
/**
- * Pin camera on user switch.
- * If more than one user is using the device
- * each user may set a different preference for the camera app.
- * Make sure that user's preference is pinned into memory.
- */
+ * Pin camera on user switch.
+ * If more than one user is using the device
+ * each user may set a different preference for the camera app.
+ * Make sure that user's preference is pinned into memory.
+ */
@Override
public void onSwitchUser(int userHandle) {
- handlePin(userHandle);
+ mPinnerHandler.sendMessage(
+ mPinnerHandler.obtainMessage(PinnerHandler.PIN_CAMERA_MSG, userHandle, 0));
}
- private void handlePin(int userHandle) {
+ /**
+ * Handler for on start pinning message
+ */
+ private void handlePinOnStart() {
+ // Files to pin come from the overlay and can be specified per-device config
+ String[] filesToPin = mContext.getResources().getStringArray(
+ com.android.internal.R.array.config_defaultPinnerServiceFiles);
+ synchronized(this) {
+ // Continue trying to pin remaining files even if there is a failure
+ for (int i = 0; i < filesToPin.length; i++){
+ PinnedFile pf = pinFile(filesToPin[i], 0, 0, 0);
+ if (pf != null) {
+ mPinnedFiles.add(pf);
+ if (DEBUG) {
+ Slog.i(TAG, "Pinned file = " + pf.mFilename);
+ }
+ } else {
+ Slog.e(TAG, "Failed to pin file = " + filesToPin[i]);
+ }
+ }
+ }
+ }
+
+ /**
+ * Handler for camera pinning message
+ */
+ private void handlePinCamera(int userHandle) {
if (mShouldPinCamera) {
- boolean success = pinCamera(userHandle);
- if (!success) {
- //this is not necessarily an error
- if (DEBUG) {
- Slog.v(TAG, "Failed to pin camera.");
+ synchronized(this) {
+ boolean success = pinCamera(userHandle);
+ if (!success) {
+ //this is not necessarily an error
+ if (DEBUG) {
+ Slog.v(TAG, "Failed to pin camera.");
+ }
}
}
}
@@ -316,11 +340,13 @@
protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DUMP, TAG);
pw.println("Pinned Files:");
- for (int i = 0; i < mPinnedFiles.size(); i++) {
- pw.println(mPinnedFiles.get(i).mFilename);
- }
- for (int i = 0; i < mPinnedCameraFiles.size(); i++) {
- pw.println(mPinnedCameraFiles.get(i).mFilename);
+ synchronized(this) {
+ for (int i = 0; i < mPinnedFiles.size(); i++) {
+ pw.println(mPinnedFiles.get(i).mFilename);
+ }
+ for (int i = 0; i < mPinnedCameraFiles.size(); i++) {
+ pw.println(mPinnedCameraFiles.get(i).mFilename);
+ }
}
}
}
@@ -336,4 +362,35 @@
mFilename = filename;
}
}
+
+ final class PinnerHandler extends Handler {
+ static final int PIN_CAMERA_MSG = 4000;
+ static final int PIN_ONSTART_MSG = 4001;
+
+ public PinnerHandler(Looper looper) {
+ super(looper, null, true);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+
+ case PIN_CAMERA_MSG:
+ {
+ handlePinCamera(msg.arg1);
+ }
+ break;
+
+ case PIN_ONSTART_MSG:
+ {
+ handlePinOnStart();
+ }
+ break;
+
+ default:
+ super.handleMessage(msg);
+ }
+ }
+ }
+
}
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index e5579e2..5bfb90f 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -569,6 +569,9 @@
private boolean mShowDialogs = true;
private boolean mInVrMode = false;
+ // Whether we should use SCHED_FIFO for UI and RenderThreads.
+ private boolean mUseFifoUiScheduling = false;
+
BroadcastQueue mFgBroadcastQueue;
BroadcastQueue mBgBroadcastQueue;
// Convenient for easy iteration over the queues. Foreground is first
@@ -2657,6 +2660,10 @@
GL_ES_VERSION = SystemProperties.getInt("ro.opengles.version",
ConfigurationInfo.GL_ES_VERSION_UNDEFINED);
+ if (SystemProperties.getInt("sys.use_fifo_ui", 0) != 0) {
+ mUseFifoUiScheduling = true;
+ }
+
mTrackingAssociations = "1".equals(SystemProperties.get("debug.track-associations"));
mConfiguration.setToDefaults();
@@ -6661,8 +6668,7 @@
@Override
public void showBootMessage(final CharSequence msg, final boolean always) {
if (Binder.getCallingUid() != Process.myUid()) {
- // These days only the core system can call this, so apps can't get in
- // the way of what we show about running them.
+ throw new SecurityException();
}
mWindowManager.showBootMessage(msg, always);
}
@@ -12533,6 +12539,10 @@
final int pid = Binder.getCallingPid();
proc = mPidsSelfLocked.get(pid);
if (proc != null && mInVrMode && tid >= 0) {
+ // ensure the tid belongs to the process
+ if (!Process.isThreadInProcess(pid, tid)) {
+ throw new IllegalArgumentException("VR thread does not belong to process");
+ }
// reset existing VR thread to CFS
if (proc.vrThreadTid != 0) {
Process.setThreadScheduler(proc.vrThreadTid, Process.SCHED_OTHER, 0);
@@ -12551,6 +12561,40 @@
}
@Override
+ public void setRenderThread(int tid) {
+ synchronized (this) {
+ ProcessRecord proc;
+ synchronized (mPidsSelfLocked) {
+ int pid = Binder.getCallingPid();
+ proc = mPidsSelfLocked.get(pid);
+ if (mUseFifoUiScheduling && proc != null && proc.renderThreadTid == 0 && tid > 0) {
+ // ensure the tid belongs to the process
+ if (!Process.isThreadInProcess(pid, tid)) {
+ throw new IllegalArgumentException(
+ "Render thread does not belong to process");
+ }
+ proc.renderThreadTid = tid;
+ if (DEBUG_OOM_ADJ) {
+ Slog.d("UI_FIFO", "Set RenderThread tid " + tid + " for pid " + pid);
+ }
+ // promote to FIFO now
+ if (proc.curSchedGroup == ProcessList.SCHED_GROUP_TOP_APP) {
+ if (DEBUG_OOM_ADJ) Slog.d("UI_FIFO", "Promoting " + tid + "out of band");
+ Process.setThreadScheduler(proc.renderThreadTid,
+ Process.SCHED_FIFO | Process.SCHED_RESET_ON_FORK, 1);
+ }
+ } else {
+ if (DEBUG_OOM_ADJ) {
+ Slog.d("UI_FIFO", "Didn't set thread from setRenderThread? " +
+ "PID: " + pid + ", TID: " + tid + " FIFO: " +
+ mUseFifoUiScheduling);
+ }
+ }
+ }
+ }
+ }
+
+ @Override
public int setVrMode(IBinder token, boolean enabled, ComponentName packageName) {
if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_VR_MODE)) {
throw new UnsupportedOperationException("VR mode not supported on this device!");
@@ -19548,7 +19592,7 @@
adj = ProcessList.FOREGROUND_APP_ADJ;
if ((cr.flags&Context.BIND_NOT_FOREGROUND) == 0) {
if ((cr.flags&Context.BIND_IMPORTANT) != 0) {
- schedGroup = ProcessList.SCHED_GROUP_TOP_APP;
+ schedGroup = ProcessList.SCHED_GROUP_TOP_APP_BOUND;
} else {
schedGroup = ProcessList.SCHED_GROUP_DEFAULT;
}
@@ -20165,47 +20209,65 @@
processGroup = Process.THREAD_GROUP_BG_NONINTERACTIVE;
break;
case ProcessList.SCHED_GROUP_TOP_APP:
+ case ProcessList.SCHED_GROUP_TOP_APP_BOUND:
processGroup = Process.THREAD_GROUP_TOP_APP;
break;
default:
processGroup = Process.THREAD_GROUP_DEFAULT;
break;
}
- if (true) {
- long oldId = Binder.clearCallingIdentity();
- try {
- Process.setProcessGroup(app.pid, processGroup);
- if (app.curSchedGroup == ProcessList.SCHED_GROUP_TOP_APP) {
- // do nothing if we already switched to RT
- if (oldSchedGroup != ProcessList.SCHED_GROUP_TOP_APP) {
- // Switch VR thread for app to SCHED_FIFO
- if (mInVrMode && app.vrThreadTid != 0) {
- Process.setThreadScheduler(app.vrThreadTid,
+ long oldId = Binder.clearCallingIdentity();
+ try {
+ Process.setProcessGroup(app.pid, processGroup);
+ if (app.curSchedGroup == ProcessList.SCHED_GROUP_TOP_APP) {
+ // do nothing if we already switched to RT
+ if (oldSchedGroup != ProcessList.SCHED_GROUP_TOP_APP) {
+ // Switch VR thread for app to SCHED_FIFO
+ if (mInVrMode && app.vrThreadTid != 0) {
+ Process.setThreadScheduler(app.vrThreadTid,
+ Process.SCHED_FIFO | Process.SCHED_RESET_ON_FORK, 1);
+ }
+ if (mUseFifoUiScheduling) {
+ // Switch UI pipeline for app to SCHED_FIFO
+ app.savedPriority = Process.getThreadPriority(app.pid);
+ Process.setThreadScheduler(app.pid,
+ Process.SCHED_FIFO | Process.SCHED_RESET_ON_FORK, 1);
+ if (app.renderThreadTid != 0) {
+ Process.setThreadScheduler(app.renderThreadTid,
Process.SCHED_FIFO | Process.SCHED_RESET_ON_FORK, 1);
+ if (DEBUG_OOM_ADJ) {
+ Slog.d("UI_FIFO", "Set RenderThread (TID " +
+ app.renderThreadTid + ") to FIFO");
+ }
+ } else {
+ if (DEBUG_OOM_ADJ) {
+ Slog.d("UI_FIFO", "Not setting RenderThread TID");
+ }
}
}
- } else if (oldSchedGroup == ProcessList.SCHED_GROUP_TOP_APP &&
- app.curSchedGroup != ProcessList.SCHED_GROUP_TOP_APP) {
- // Reset VR thread to SCHED_OTHER
- // Safe to do even if we're not in VR mode
- if (app.vrThreadTid != 0) {
- Process.setThreadScheduler(app.vrThreadTid, Process.SCHED_OTHER, 0);
- }
}
- } catch (Exception e) {
- Slog.w(TAG, "Failed setting process group of " + app.pid
- + " to " + app.curSchedGroup);
- e.printStackTrace();
- } finally {
- Binder.restoreCallingIdentity(oldId);
- }
- } else {
- if (app.thread != null) {
- try {
- app.thread.setSchedulingGroup(processGroup);
- } catch (RemoteException e) {
+ } else if (oldSchedGroup == ProcessList.SCHED_GROUP_TOP_APP &&
+ app.curSchedGroup != ProcessList.SCHED_GROUP_TOP_APP) {
+ // Reset VR thread to SCHED_OTHER
+ // Safe to do even if we're not in VR mode
+ if (app.vrThreadTid != 0) {
+ Process.setThreadScheduler(app.vrThreadTid, Process.SCHED_OTHER, 0);
+ }
+ if (mUseFifoUiScheduling) {
+ // Reset UI pipeline to SCHED_OTHER
+ Process.setThreadScheduler(app.pid, Process.SCHED_OTHER, 0);
+ Process.setThreadScheduler(app.renderThreadTid,
+ Process.SCHED_OTHER, 0);
+ Process.setThreadPriority(app.pid, app.savedPriority);
+ Process.setThreadPriority(app.renderThreadTid, -4);
}
}
+ } catch (Exception e) {
+ Slog.w(TAG, "Failed setting process group of " + app.pid
+ + " to " + app.curSchedGroup);
+ e.printStackTrace();
+ } finally {
+ Binder.restoreCallingIdentity(oldId);
}
}
}
diff --git a/services/core/java/com/android/server/am/PreBootBroadcaster.java b/services/core/java/com/android/server/am/PreBootBroadcaster.java
index 3ed3d9a..a7d09d0 100644
--- a/services/core/java/com/android/server/am/PreBootBroadcaster.java
+++ b/services/core/java/com/android/server/am/PreBootBroadcaster.java
@@ -57,7 +57,7 @@
private int mIndex = 0;
public PreBootBroadcaster(ActivityManagerService service, int userId,
- ProgressReporter progress) {
+ ProgressReporter progress, boolean quiet) {
mService = service;
mUserId = userId;
mProgress = progress;
@@ -68,7 +68,9 @@
mTargets = mService.mContext.getPackageManager().queryBroadcastReceiversAsUser(mIntent,
MATCH_SYSTEM_ONLY, UserHandle.of(userId));
- mHandler.obtainMessage(MSG_SHOW).sendToTarget();
+ if (!quiet) {
+ mHandler.obtainMessage(MSG_SHOW).sendToTarget();
+ }
}
public void sendNext() {
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index f073e5c..475b155 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -130,6 +130,9 @@
static final int SCHED_GROUP_DEFAULT = 1;
// Activity manager's version of Process.THREAD_GROUP_TOP_APP
static final int SCHED_GROUP_TOP_APP = 2;
+ // Activity manager's version of Process.THREAD_GROUP_TOP_APP
+ // Disambiguate between actual top app and processes bound to the top app
+ static final int SCHED_GROUP_TOP_APP_BOUND = 3;
// The minimum number of cached apps we want to be able to keep around,
// without empty apps being able to push them out of memory.
diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java
index 0f7c89d..dad383d 100644
--- a/services/core/java/com/android/server/am/ProcessRecord.java
+++ b/services/core/java/com/android/server/am/ProcessRecord.java
@@ -103,6 +103,8 @@
int repProcState = PROCESS_STATE_NONEXISTENT; // Last reported process state
int setProcState = PROCESS_STATE_NONEXISTENT; // Last set process state in process tracker
int pssProcState = PROCESS_STATE_NONEXISTENT; // Currently requesting pss for
+ int savedPriority; // Previous priority value if we're switching to non-SCHED_OTHER
+ int renderThreadTid; // TID for RenderThread
boolean serviceb; // Process currently is on the service B list
boolean serviceHighRam; // We are forcing to service B list due to its RAM use
boolean setIsForeground; // Running foreground UI when last set?
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index d25f2cb..4de09bd 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -351,7 +351,11 @@
// PRE_BOOT receivers are finished to avoid ANR'ing apps
final UserInfo info = getUserInfo(userId);
if (!Objects.equals(info.lastLoggedInFingerprint, Build.FINGERPRINT)) {
- new PreBootBroadcaster(mService, userId, null) {
+ // Suppress double notifications for managed profiles that
+ // were unlocked automatically (no challenge token required)
+ // as part of their parent user being unlocked.
+ final boolean quiet = info.isManagedProfile() && !uss.tokenProvided;
+ new PreBootBroadcaster(mService, userId, null, quiet) {
@Override
public void onFinished() {
finishUserUnlockedCompleted(uss);
@@ -972,6 +976,7 @@
return false;
} else {
uss.mUnlockProgress.addListener(listener);
+ uss.tokenProvided = (token != null);
}
finishUserUnlocking(uss);
diff --git a/services/core/java/com/android/server/am/UserState.java b/services/core/java/com/android/server/am/UserState.java
index 952283e..ff8014c 100644
--- a/services/core/java/com/android/server/am/UserState.java
+++ b/services/core/java/com/android/server/am/UserState.java
@@ -54,6 +54,7 @@
public int state = STATE_BOOTING;
public int lastState = STATE_BOOTING;
public boolean switching;
+ public boolean tokenProvided;
/**
* The last time that a provider was reported to usage stats as being brought to important
diff --git a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
index 15b872d..7a25df6 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.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 @@
// 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 @@
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 @@
// 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 @@
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 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 @@
}
}
+ /**
+ * 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 eeddff5..92c4577 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 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 @@
*/
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 @@
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 @@
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 @@
@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 @@
}
}
- // 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 @@
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/display/NightDisplayService.java b/services/core/java/com/android/server/display/NightDisplayService.java
new file mode 100644
index 0000000..27abd1e
--- /dev/null
+++ b/services/core/java/com/android/server/display/NightDisplayService.java
@@ -0,0 +1,362 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.display;
+
+import android.app.AlarmManager;
+import android.content.BroadcastReceiver;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.UserHandle;
+import android.provider.Settings.Secure;
+import android.util.Slog;
+
+import com.android.internal.app.NightDisplayController;
+import com.android.server.SystemService;
+import com.android.server.twilight.TwilightListener;
+import com.android.server.twilight.TwilightManager;
+import com.android.server.twilight.TwilightState;
+
+import java.util.Calendar;
+import java.util.TimeZone;
+
+/**
+ * Tints the display at night.
+ */
+public final class NightDisplayService extends SystemService
+ implements NightDisplayController.Callback {
+
+ private static final String TAG = "NightDisplayService";
+ private static final boolean DEBUG = false;
+
+ /**
+ * Night mode ~= 3400 K.
+ */
+ private static final String MATRIX_NIGHT = "1,0,0,0,0,.754,0,0,0,0,.516,0,0,0,0,1";
+
+ private int mCurrentUser = UserHandle.USER_NULL;
+ private boolean mBootCompleted;
+
+ private NightDisplayController mController;
+ private Boolean mIsActivated;
+ private AutoMode mAutoMode;
+
+ public NightDisplayService(Context context) {
+ super(context);
+ }
+
+ @Override
+ public void onStart() {
+ // Nothing to publish.
+ }
+
+ @Override
+ public void onStartUser(int userHandle) {
+ super.onStartUser(userHandle);
+
+ // Register listeners for the new user.
+ if (mCurrentUser == UserHandle.USER_NULL) {
+ mCurrentUser = userHandle;
+ if (mBootCompleted) {
+ setUpNightMode();
+ }
+ }
+ }
+
+ @Override
+ public void onSwitchUser(int userHandle) {
+ super.onSwitchUser(userHandle);
+
+ // Unregister listeners for the old user.
+ if (mBootCompleted && mCurrentUser != UserHandle.USER_NULL) {
+ tearDownNightMode();
+ }
+
+ // Register listeners for the new user.
+ mCurrentUser = userHandle;
+ if (mBootCompleted) {
+ setUpNightMode();
+ }
+ }
+
+ @Override
+ public void onStopUser(int userHandle) {
+ super.onStopUser(userHandle);
+
+ // Unregister listeners for the old user.
+ if (mCurrentUser == userHandle) {
+ if (mBootCompleted) {
+ tearDownNightMode();
+ }
+ mCurrentUser = UserHandle.USER_NULL;
+ }
+ }
+
+ @Override
+ public void onBootPhase(int phase) {
+ if (phase == PHASE_BOOT_COMPLETED) {
+ mBootCompleted = true;
+
+ // Register listeners now that boot is complete.
+ if (mCurrentUser != UserHandle.USER_NULL) {
+ setUpNightMode();
+ }
+ }
+ }
+
+ private void setUpNightMode() {
+ // Create a new controller for the current user and start listening for changes.
+ mController = new NightDisplayController(getContext(), mCurrentUser);
+ mController.setListener(this);
+
+ // Initialize the current auto mode.
+ onAutoModeChanged(mController.getAutoMode());
+
+ // Force the initialization current activated state.
+ if (mIsActivated == null) {
+ onActivated(mController.isActivated());
+ }
+ }
+
+ private void tearDownNightMode() {
+ mController.setListener(null);
+
+ if (mAutoMode != null) {
+ mAutoMode.onStop();
+ mAutoMode = null;
+ }
+
+ mIsActivated = null;
+ mController = null;
+ }
+
+ @Override
+ public void onActivated(boolean activated) {
+ if (mIsActivated == null || mIsActivated != activated) {
+ Slog.i(TAG, activated ? "Turning on night display" : "Turning off night display");
+
+ mIsActivated = activated;
+
+ if (mAutoMode != null) {
+ mAutoMode.onActivated(activated);
+ }
+
+ // Update the current color matrix.
+ final ContentResolver cr = getContext().getContentResolver();
+ Secure.putStringForUser(cr, Secure.ACCESSIBILITY_DISPLAY_COLOR_MATRIX,
+ activated ? MATRIX_NIGHT : null, mCurrentUser);
+ }
+ }
+
+ @Override
+ public void onAutoModeChanged(int autoMode) {
+ if (mAutoMode != null) {
+ mAutoMode.onStop();
+ mAutoMode = null;
+ }
+
+ if (autoMode == NightDisplayController.AUTO_MODE_CUSTOM) {
+ mAutoMode = new CustomAutoMode();
+ } else if (autoMode == NightDisplayController.AUTO_MODE_TWILIGHT) {
+ mAutoMode = new TwilightAutoMode();
+ }
+
+ if (mAutoMode != null) {
+ mAutoMode.onStart();
+ }
+ }
+
+ @Override
+ public void onCustomStartTimeChanged(NightDisplayController.LocalTime startTime) {
+ if (mAutoMode != null) {
+ mAutoMode.onCustomStartTimeChanged(startTime);
+ }
+ }
+
+ @Override
+ public void onCustomEndTimeChanged(NightDisplayController.LocalTime endTime) {
+ if (mAutoMode != null) {
+ mAutoMode.onCustomEndTimeChanged(endTime);
+ }
+ }
+
+ private abstract class AutoMode implements NightDisplayController.Callback {
+ public abstract void onStart();
+ public abstract void onStop();
+ }
+
+ private class CustomAutoMode extends AutoMode implements AlarmManager.OnAlarmListener {
+
+ private final AlarmManager mAlarmManager;
+ private final BroadcastReceiver mTimeChangedReceiver;
+
+ private NightDisplayController.LocalTime mStartTime;
+ private NightDisplayController.LocalTime mEndTime;
+
+ private Calendar mLastActivatedTime;
+
+ public CustomAutoMode() {
+ mAlarmManager = (AlarmManager) getContext().getSystemService(Context.ALARM_SERVICE);
+ mTimeChangedReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ updateActivated();
+ }
+ };
+ }
+
+ private void updateActivated() {
+ final Calendar now = Calendar.getInstance();
+ final Calendar startTime = mStartTime.getDateTimeBefore(now);
+ final Calendar endTime = mEndTime.getDateTimeAfter(startTime);
+ final boolean activated = now.before(endTime);
+
+ boolean setActivated = mIsActivated == null || mLastActivatedTime == null;
+ if (!setActivated && mIsActivated != activated) {
+ final TimeZone currentTimeZone = now.getTimeZone();
+ if (!currentTimeZone.equals(mLastActivatedTime.getTimeZone())) {
+ final int year = mLastActivatedTime.get(Calendar.YEAR);
+ final int dayOfYear = mLastActivatedTime.get(Calendar.DAY_OF_YEAR);
+ final int hourOfDay = mLastActivatedTime.get(Calendar.HOUR_OF_DAY);
+ final int minute = mLastActivatedTime.get(Calendar.MINUTE);
+
+ mLastActivatedTime.setTimeZone(currentTimeZone);
+ mLastActivatedTime.set(Calendar.YEAR, year);
+ mLastActivatedTime.set(Calendar.DAY_OF_YEAR, dayOfYear);
+ mLastActivatedTime.set(Calendar.HOUR_OF_DAY, hourOfDay);
+ mLastActivatedTime.set(Calendar.MINUTE, minute);
+ }
+
+ if (mIsActivated) {
+ setActivated = now.before(mStartTime.getDateTimeBefore(mLastActivatedTime))
+ || now.after(mEndTime.getDateTimeAfter(mLastActivatedTime));
+ } else {
+ setActivated = now.before(mEndTime.getDateTimeBefore(mLastActivatedTime))
+ || now.after(mStartTime.getDateTimeAfter(mLastActivatedTime));
+ }
+ }
+
+ if (setActivated) {
+ mController.setActivated(activated);
+ }
+ updateNextAlarm();
+ }
+
+ private void updateNextAlarm() {
+ if (mIsActivated != null) {
+ final Calendar now = Calendar.getInstance();
+ final Calendar next = mIsActivated ? mEndTime.getDateTimeAfter(now)
+ : mStartTime.getDateTimeAfter(now);
+ mAlarmManager.setExact(AlarmManager.RTC, next.getTimeInMillis(), TAG, this, null);
+ }
+ }
+
+ @Override
+ public void onStart() {
+ final IntentFilter intentFilter = new IntentFilter(Intent.ACTION_TIME_CHANGED);
+ intentFilter.addAction(Intent.ACTION_TIMEZONE_CHANGED);
+ getContext().registerReceiver(mTimeChangedReceiver, intentFilter);
+
+ mStartTime = mController.getCustomStartTime();
+ mEndTime = mController.getCustomEndTime();
+
+ // Force an update to initialize state.
+ updateActivated();
+ }
+
+ @Override
+ public void onStop() {
+ getContext().unregisterReceiver(mTimeChangedReceiver);
+
+ mAlarmManager.cancel(this);
+ mLastActivatedTime = null;
+ }
+
+ @Override
+ public void onActivated(boolean activated) {
+ mLastActivatedTime = Calendar.getInstance();
+ updateNextAlarm();
+ }
+
+ @Override
+ public void onCustomStartTimeChanged(NightDisplayController.LocalTime startTime) {
+ mStartTime = startTime;
+ mLastActivatedTime = null;
+ updateActivated();
+ }
+
+ @Override
+ public void onCustomEndTimeChanged(NightDisplayController.LocalTime endTime) {
+ mEndTime = endTime;
+ mLastActivatedTime = null;
+ updateActivated();
+ }
+
+ @Override
+ public void onAlarm() {
+ if (DEBUG) Slog.d(TAG, "onAlarm");
+ updateActivated();
+ }
+ }
+
+ private class TwilightAutoMode extends AutoMode implements TwilightListener {
+
+ private final TwilightManager mTwilightManager;
+ private final Handler mHandler;
+
+ private boolean mIsNight;
+
+ public TwilightAutoMode() {
+ mTwilightManager = getLocalService(TwilightManager.class);
+ mHandler = new Handler(Looper.getMainLooper());
+ }
+
+ private void updateActivated() {
+ final TwilightState state = mTwilightManager.getCurrentState();
+ final boolean isNight = state != null && state.isNight();
+ if (mIsNight != isNight) {
+ mIsNight = isNight;
+
+ if (mIsActivated == null || mIsActivated != isNight) {
+ mController.setActivated(isNight);
+ }
+ }
+ }
+
+ @Override
+ public void onStart() {
+ mTwilightManager.registerListener(this, mHandler);
+
+ // Force an update to initialize state.
+ updateActivated();
+ }
+
+ @Override
+ public void onStop() {
+ mTwilightManager.unregisterListener(this);
+ }
+
+ @Override
+ public void onTwilightStateChanged() {
+ if (DEBUG) Slog.d(TAG, "onTwilightStateChanged");
+ updateActivated();
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/fingerprint/FingerprintService.java b/services/core/java/com/android/server/fingerprint/FingerprintService.java
index be8e300..1066434 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.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
+import java.util.HashMap;
import java.util.List;
/**
@@ -90,10 +91,17 @@
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 @@
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 @@
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 @@
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 @@
@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 @@
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 @@
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 @@
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 1206263..ec77daf 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 @@
.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 acd8055..84ebdd1 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 @@
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 bbffd32..d750cbf 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.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 @@
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 @@
* 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/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 45322b3..446c75c 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -7280,8 +7280,8 @@
}
private boolean areSystemNavigationKeysEnabled() {
- return Settings.Global.getInt(mContext.getContentResolver(),
- Settings.Global.SYSTEM_NAVIGATION_KEYS_ENABLED, 1) == 1;
+ return Settings.Secure.getInt(mContext.getContentResolver(),
+ Settings.Secure.SYSTEM_NAVIGATION_KEYS_ENABLED, 1) == 1;
}
@Override
diff --git a/services/core/java/com/android/server/twilight/TwilightService.java b/services/core/java/com/android/server/twilight/TwilightService.java
index 6158c92..89e5e58 100644
--- a/services/core/java/com/android/server/twilight/TwilightService.java
+++ b/services/core/java/com/android/server/twilight/TwilightService.java
@@ -556,7 +556,7 @@
public void onChange(boolean selfChange) {
super.onChange(selfChange);
int value = Secure.getIntForUser(getContext().getContentResolver(),
- Secure.TWILIGHT_MODE, Secure.TWILIGHT_MODE_LOCKED_OFF, mCurrentUser);
+ Secure.TWILIGHT_MODE, Secure.TWILIGHT_MODE_AUTO, mCurrentUser);
if (value == Secure.TWILIGHT_MODE_LOCKED_OFF) {
setLockedState(new TwilightState(false, 0));
} else if (value == Secure.TWILIGHT_MODE_LOCKED_ON) {
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index 79fe18b..06e5e73 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -1282,8 +1282,9 @@
}
@Override
- public WallpaperInfo getWallpaperInfo() {
- int userId = UserHandle.getCallingUserId();
+ public WallpaperInfo getWallpaperInfo(int userId) {
+ userId = ActivityManager.handleIncomingUser(Binder.getCallingPid(),
+ Binder.getCallingUid(), userId, false, true, "getWallpaperIdForUser", null);
synchronized (mLock) {
WallpaperData wallpaper = mWallpaperMap.get(userId);
if (wallpaper != null && wallpaper.connection != null) {
diff --git a/services/core/java/com/android/server/wm/AppTransition.java b/services/core/java/com/android/server/wm/AppTransition.java
index 4553f8e..2b58156 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 @@
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 c42d461..c9123fd 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 @@
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 76424d8..1002b0d 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -51,6 +51,7 @@
import android.view.WindowManager;
import com.android.internal.R;
+import com.android.internal.app.NightDisplayController;
import com.android.internal.os.BinderInternal;
import com.android.internal.os.SamplingProfilerIntegration;
import com.android.internal.os.ZygoteInit;
@@ -63,6 +64,7 @@
import com.android.server.connectivity.MetricsLoggerService;
import com.android.server.devicepolicy.DevicePolicyManagerService;
import com.android.server.display.DisplayManagerService;
+import com.android.server.display.NightDisplayService;
import com.android.server.dreams.DreamManagerService;
import com.android.server.fingerprint.FingerprintService;
import com.android.server.hdmi.HdmiControlService;
@@ -724,14 +726,6 @@
}
Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER);
- try {
- ActivityManagerNative.getDefault().showBootMessage(
- context.getResources().getText(
- com.android.internal.R.string.android_upgrading_starting_apps),
- false);
- } catch (RemoteException e) {
- }
-
if (mFactoryTestMode != FactoryTest.FACTORY_TEST_LOW_LEVEL) {
if (!disableNonCoreServices) {
traceBeginAndSlog("StartLockSettingsService");
@@ -1003,6 +997,10 @@
mSystemServiceManager.startService(TwilightService.class);
+ if (NightDisplayController.isAvailable(context)) {
+ mSystemServiceManager.startService(NightDisplayService.class);
+ }
+
mSystemServiceManager.startService(JobSchedulerService.class);
mSystemServiceManager.startService(SoundTriggerService.class);
diff --git a/services/net/java/android/net/dhcp/DhcpClient.java b/services/net/java/android/net/dhcp/DhcpClient.java
index 83cfc01..ffbea9f 100644
--- a/services/net/java/android/net/dhcp/DhcpClient.java
+++ b/services/net/java/android/net/dhcp/DhcpClient.java
@@ -194,6 +194,10 @@
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 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 void enter() {
super.enter();
startNewTransaction();
+ mLastInitEnterTime = SystemClock.elapsedRealtime();
}
protected boolean sendPacket() {
@@ -866,6 +870,13 @@
}
scheduleLeaseTimers();
+ logTimeToBoundState();
+ }
+
+ @Override
+ public void exit() {
+ super.exit();
+ mLastBoundExitTime = SystemClock.elapsedRealtime();
}
@Override
@@ -883,6 +894,15 @@
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 @@
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 afb644f..c6da3c3 100644
--- a/services/net/java/android/net/ip/IpReachabilityMonitor.java
+++ b/services/net/java/android/net/ip/IpReachabilityMonitor.java
@@ -129,7 +129,6 @@
* state it may be best for the link to disconnect completely and
* reconnect afresh.
*
- *
* @hide
*/
public class IpReachabilityMonitor {
@@ -163,6 +162,8 @@
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 @@
private void handleNeighborLost(String msg) {
InetAddress ip = null;
- ProvisioningChange delta;
+ final ProvisioningChange delta;
synchronized (mLock) {
LinkProperties whatIfLp = new LinkProperties(mLinkProperties);
@@ -368,10 +369,8 @@
// 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 @@
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 @@
}
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/net/java/android/net/ip/RouterAdvertisementDaemon.java b/services/net/java/android/net/ip/RouterAdvertisementDaemon.java
new file mode 100644
index 0000000..53c2fd7
--- /dev/null
+++ b/services/net/java/android/net/ip/RouterAdvertisementDaemon.java
@@ -0,0 +1,581 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.ip;
+
+import static android.system.OsConstants.*;
+
+import android.net.IpPrefix;
+import android.net.LinkAddress;
+import android.net.LinkProperties;
+import android.net.NetworkUtils;
+import android.system.ErrnoException;
+import android.system.Os;
+import android.system.StructGroupReq;
+import android.system.StructTimeval;
+import android.util.Log;
+
+import com.android.internal.annotations.GuardedBy;
+
+import libcore.io.IoUtils;
+import libcore.util.HexEncoding;
+
+import java.io.FileDescriptor;
+import java.io.InterruptedIOException;
+import java.io.IOException;
+import java.net.Inet6Address;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.SocketException;
+import java.net.UnknownHostException;
+import java.nio.BufferOverflowException;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.Random;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicInteger;
+
+
+/**
+ * Basic IPv6 Router Advertisement Daemon.
+ *
+ * TODO:
+ *
+ * - Rewrite using Handler (and friends) so that AlarmManager can deliver
+ * "kick" messages when it's time to send a multicast RA.
+ *
+ * - Support transmitting MAX_URGENT_RTR_ADVERTISEMENTS number of empty
+ * RAs with zero default router lifetime when transitioning from an
+ * advertising state to a non-advertising state.
+ *
+ * @hide
+ */
+public class RouterAdvertisementDaemon {
+ private static final String TAG = RouterAdvertisementDaemon.class.getSimpleName();
+ private static final byte ICMPV6_ND_ROUTER_SOLICIT = asByte(133);
+ private static final byte ICMPV6_ND_ROUTER_ADVERT = asByte(134);
+ private static final int IPV6_MIN_MTU = 1280;
+ private static final int MIN_RA_HEADER_SIZE = 16;
+
+ // Summary of various timers and lifetimes.
+ private static final int MIN_RTR_ADV_INTERVAL_SEC = 300;
+ private static final int MAX_RTR_ADV_INTERVAL_SEC = 600;
+ // In general, router, prefix, and DNS lifetimes are all advised to be
+ // greater than or equal to 3 * MAX_RTR_ADV_INTERVAL. Here, we double
+ // that to allow for multicast packet loss.
+ //
+ // This MAX_RTR_ADV_INTERVAL_SEC and DEFAULT_LIFETIME are also consistent
+ // with the https://tools.ietf.org/html/rfc7772#section-4 discussion of
+ // "approximately 7 RAs per hour".
+ private static final int DEFAULT_LIFETIME = 6 * MAX_RTR_ADV_INTERVAL_SEC;
+ // From https://tools.ietf.org/html/rfc4861#section-10 .
+ private static final int MIN_DELAY_BETWEEN_RAS_SEC = 3;
+ // Both initial and final RAs, but also for changes in RA contents.
+ // From https://tools.ietf.org/html/rfc4861#section-10 .
+ private static final int MAX_URGENT_RTR_ADVERTISEMENTS = 5;
+
+ private static final int DAY_IN_SECONDS = 86_400;
+
+ private static final byte[] ALL_NODES = new byte[] {
+ (byte) 0xff, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1
+ };
+
+ private final String mIfName;
+ private final int mIfIndex;
+ private final byte[] mHwAddr;
+ private final InetSocketAddress mAllNodes;
+
+ // This lock is to protect the RA from being updated while being
+ // transmitted on another thread (multicast or unicast).
+ //
+ // TODO: This should be handled with a more RCU-like approach.
+ private final Object mLock = new Object();
+ @GuardedBy("mLock")
+ private final byte[] mRA = new byte[IPV6_MIN_MTU];
+ @GuardedBy("mLock")
+ private int mRaLength;
+
+ private volatile FileDescriptor mSocket;
+ private volatile MulticastTransmitter mMulticastTransmitter;
+ private volatile UnicastResponder mUnicastResponder;
+
+ public static class RaParams {
+ public boolean hasDefaultRoute;
+ public int mtu;
+ public HashSet<IpPrefix> prefixes;
+ public HashSet<Inet6Address> dnses;
+
+ public RaParams() {
+ hasDefaultRoute = false;
+ mtu = IPV6_MIN_MTU;
+ prefixes = new HashSet<IpPrefix>();
+ dnses = new HashSet<Inet6Address>();
+ }
+
+ public RaParams(RaParams other) {
+ hasDefaultRoute = other.hasDefaultRoute;
+ mtu = other.mtu;
+ prefixes = (HashSet) other.prefixes.clone();
+ dnses = (HashSet) other.dnses.clone();
+ }
+ }
+
+
+ public RouterAdvertisementDaemon(String ifname, int ifindex, byte[] hwaddr) {
+ mIfName = ifname;
+ mIfIndex = ifindex;
+ mHwAddr = hwaddr;
+ mAllNodes = new InetSocketAddress(getAllNodesForScopeId(mIfIndex), 0);
+ }
+
+ public void buildNewRa(RaParams params) {
+ if (params == null || params.prefixes.isEmpty()) {
+ // No RA to be served at this time.
+ clearRa();
+ return;
+ }
+
+ if (params.mtu < IPV6_MIN_MTU) {
+ params.mtu = IPV6_MIN_MTU;
+ }
+
+ final ByteBuffer ra = ByteBuffer.wrap(mRA);
+ ra.order(ByteOrder.BIG_ENDIAN);
+
+ synchronized (mLock) {
+ try {
+ putHeader(ra, params.hasDefaultRoute);
+ putSlla(ra, mHwAddr);
+ // https://tools.ietf.org/html/rfc5175#section-4 says:
+ //
+ // "MUST NOT be added to a Router Advertisement message
+ // if no flags in the option are set."
+ //
+ // putExpandedFlagsOption(ra);
+ putMtu(ra, params.mtu);
+ for (IpPrefix ipp : params.prefixes) {
+ putPio(ra, ipp);
+ }
+ if (params.dnses.size() > 0) {
+ putRdnss(ra, params.dnses);
+ }
+ mRaLength = ra.position();
+ } catch (BufferOverflowException e) {
+ Log.e(TAG, "Could not construct new RA: " + e);
+ mRaLength = 0;
+ return;
+ }
+ }
+
+ maybeNotifyMulticastTransmitter();
+ }
+
+ public boolean start() {
+ if (!createSocket()) {
+ return false;
+ }
+
+ mMulticastTransmitter = new MulticastTransmitter();
+ mMulticastTransmitter.start();
+
+ mUnicastResponder = new UnicastResponder();
+ mUnicastResponder.start();
+
+ return true;
+ }
+
+ public void stop() {
+ closeSocket();
+ mMulticastTransmitter = null;
+ mUnicastResponder = null;
+ }
+
+ private void clearRa() {
+ boolean notifySocket;
+ synchronized (mLock) {
+ notifySocket = (mRaLength != 0);
+ mRaLength = 0;
+ }
+ if (notifySocket) {
+ maybeNotifyMulticastTransmitter();
+ }
+ }
+
+ private void maybeNotifyMulticastTransmitter() {
+ final MulticastTransmitter m = mMulticastTransmitter;
+ if (m != null) {
+ m.hup();
+ }
+ }
+
+ private static Inet6Address getAllNodesForScopeId(int scopeId) {
+ try {
+ return Inet6Address.getByAddress("ff02::1", ALL_NODES, scopeId);
+ } catch (UnknownHostException uhe) {
+ Log.wtf(TAG, "Failed to construct ff02::1 InetAddress: " + uhe);
+ return null;
+ }
+ }
+
+ private static byte asByte(int value) { return (byte) value; }
+ private static short asShort(int value) { return (short) value; }
+
+ private static void putHeader(ByteBuffer ra, boolean hasDefaultRoute) {
+ /**
+ Router Advertisement Message Format
+
+ 0 1 2 3
+ 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | Type | Code | Checksum |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | Cur Hop Limit |M|O|H|Prf|P|R|R| Router Lifetime |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | Reachable Time |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | Retrans Timer |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | Options ...
+ +-+-+-+-+-+-+-+-+-+-+-+-
+ */
+ final byte DEFAULT_HOPLIMIT = 64;
+ ra.put(ICMPV6_ND_ROUTER_ADVERT)
+ .put(asByte(0))
+ .putShort(asShort(0))
+ .put(DEFAULT_HOPLIMIT)
+ // RFC 4191 "high" preference, iff. advertising a default route.
+ .put(hasDefaultRoute ? asByte(0x08) : asByte(0))
+ .putShort(hasDefaultRoute ? asShort(DEFAULT_LIFETIME) : asShort(0))
+ .putInt(0)
+ .putInt(0);
+ }
+
+ private static void putSlla(ByteBuffer ra, byte[] slla) {
+ /**
+ Source/Target Link-layer Address
+
+ 0 1 2 3
+ 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | Type | Length | Link-Layer Address ...
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ */
+ if (slla == null || slla.length != 6) {
+ // Only IEEE 802.3 6-byte addresses are supported.
+ return;
+ }
+ final byte ND_OPTION_SLLA = 1;
+ final byte SLLA_NUM_8OCTETS = 1;
+ ra.put(ND_OPTION_SLLA)
+ .put(SLLA_NUM_8OCTETS)
+ .put(slla);
+ }
+
+ private static void putExpandedFlagsOption(ByteBuffer ra) {
+ /**
+ Router Advertisement Expanded Flags Option
+
+ 0 1 2 3
+ 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | Type | Length | Bit fields available ..
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ ... for assignment |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ */
+
+ final byte ND_OPTION_EFO = 26;
+ final byte EFO_NUM_8OCTETS = 1;
+
+ ra.put(ND_OPTION_EFO)
+ .put(EFO_NUM_8OCTETS)
+ .putShort(asShort(0))
+ .putInt(0);
+ }
+
+ private static void putMtu(ByteBuffer ra, int mtu) {
+ /**
+ MTU
+
+ 0 1 2 3
+ 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | Type | Length | Reserved |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | MTU |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ */
+ final byte ND_OPTION_MTU = 5;
+ final byte MTU_NUM_8OCTETS = 1;
+ ra.put(ND_OPTION_MTU)
+ .put(MTU_NUM_8OCTETS)
+ .putShort(asShort(0))
+ .putInt(mtu);
+ }
+
+ private static void putPio(ByteBuffer ra, IpPrefix ipp) {
+ /**
+ Prefix Information
+
+ 0 1 2 3
+ 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | Type | Length | Prefix Length |L|A| Reserved1 |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | Valid Lifetime |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | Preferred Lifetime |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | Reserved2 |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | |
+ + +
+ | |
+ + Prefix +
+ | |
+ + +
+ | |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ */
+ final int prefixLength = ipp.getPrefixLength();
+ if (prefixLength != 64) {
+ return;
+ }
+ final byte ND_OPTION_PIO = 3;
+ final byte PIO_NUM_8OCTETS = 4;
+
+ final byte[] addr = ipp.getAddress().getAddress();
+ ra.put(ND_OPTION_PIO)
+ .put(PIO_NUM_8OCTETS)
+ .put(asByte(prefixLength))
+ .put(asByte(0xc0)) // L&A set
+ .putInt(DEFAULT_LIFETIME)
+ .putInt(DEFAULT_LIFETIME)
+ .putInt(0)
+ .put(addr);
+ }
+
+ private static void putRio(ByteBuffer ra, IpPrefix ipp) {
+ /**
+ Route Information Option
+
+ 0 1 2 3
+ 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | Type | Length | Prefix Length |Resvd|Prf|Resvd|
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | Route Lifetime |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | Prefix (Variable Length) |
+ . .
+ . .
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ */
+ final int prefixLength = ipp.getPrefixLength();
+ if (prefixLength > 64) {
+ return;
+ }
+ final byte ND_OPTION_RIO = 24;
+ final byte RIO_NUM_8OCTETS = asByte(
+ (prefixLength == 0) ? 1 : (prefixLength <= 8) ? 2 : 3);
+
+ final byte[] addr = ipp.getAddress().getAddress();
+ ra.put(ND_OPTION_RIO)
+ .put(RIO_NUM_8OCTETS)
+ .put(asByte(prefixLength))
+ .put(asByte(0x18))
+ .putInt(DEFAULT_LIFETIME);
+
+ // Rely upon an IpPrefix's address being properly zeroed.
+ if (prefixLength > 0) {
+ ra.put(addr, 0, (prefixLength <= 64) ? 8 : 16);
+ }
+ }
+
+ private static void putRdnss(ByteBuffer ra, Set<Inet6Address> dnses) {
+ /**
+ Recursive DNS Server (RDNSS) Option
+
+ 0 1 2 3
+ 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | Type | Length | Reserved |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | Lifetime |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | |
+ : Addresses of IPv6 Recursive DNS Servers :
+ | |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ */
+
+ final byte ND_OPTION_RDNSS = 25;
+ final byte RDNSS_NUM_8OCTETS = asByte(dnses.size() * 2 + 1);
+ ra.put(ND_OPTION_RDNSS)
+ .put(RDNSS_NUM_8OCTETS)
+ .putShort(asShort(0))
+ .putInt(DEFAULT_LIFETIME);
+
+ for (Inet6Address dns : dnses) {
+ ra.put(dns.getAddress());
+ }
+ }
+
+ private boolean createSocket() {
+ final int SEND_TIMEOUT_MS = 300;
+
+ try {
+ mSocket = Os.socket(AF_INET6, SOCK_RAW, IPPROTO_ICMPV6);
+ // Setting SNDTIMEO is purely for defensive purposes.
+ Os.setsockoptTimeval(
+ mSocket, SOL_SOCKET, SO_SNDTIMEO, StructTimeval.fromMillis(SEND_TIMEOUT_MS));
+ Os.setsockoptIfreq(mSocket, SOL_SOCKET, SO_BINDTODEVICE, mIfName);
+ NetworkUtils.protectFromVpn(mSocket);
+ NetworkUtils.setupRaSocket(mSocket, mIfIndex);
+ } catch (ErrnoException | IOException e) {
+ Log.e(TAG, "Failed to create RA daemon socket: " + e);
+ return false;
+ }
+
+ return true;
+ }
+
+ private void closeSocket() {
+ if (mSocket != null) {
+ IoUtils.closeQuietly(mSocket);
+ }
+ mSocket = null;
+ }
+
+ private boolean isSocketValid() {
+ final FileDescriptor s = mSocket;
+ return (s != null) && s.valid();
+ }
+
+ private boolean isSuitableDestination(InetSocketAddress dest) {
+ if (mAllNodes.equals(dest)) {
+ return true;
+ }
+
+ final InetAddress destip = dest.getAddress();
+ return (destip instanceof Inet6Address) &&
+ destip.isLinkLocalAddress() &&
+ (((Inet6Address) destip).getScopeId() == mIfIndex);
+ }
+
+ private void maybeSendRA(InetSocketAddress dest) {
+ if (dest == null || !isSuitableDestination(dest)) {
+ dest = mAllNodes;
+ }
+
+ try {
+ synchronized (mLock) {
+ if (mRaLength < MIN_RA_HEADER_SIZE) {
+ // No actual RA to send.
+ return;
+ }
+ Os.sendto(mSocket, mRA, 0, mRaLength, 0, dest);
+ }
+ Log.d(TAG, "RA sendto " + dest.getAddress().getHostAddress());
+ } catch (ErrnoException | SocketException e) {
+ if (isSocketValid()) {
+ Log.e(TAG, "sendto error: " + e);
+ }
+ }
+ }
+
+ private final class UnicastResponder extends Thread {
+ private final InetSocketAddress solicitor = new InetSocketAddress();
+ // The recycled buffer for receiving Router Solicitations from clients.
+ // If the RS is larger than IPV6_MIN_MTU the packets are truncated.
+ // This is fine since currently only byte 0 is examined anyway.
+ private final byte mSolication[] = new byte[IPV6_MIN_MTU];
+
+ @Override
+ public void run() {
+ while (isSocketValid()) {
+ try {
+ // Blocking receive.
+ final int rval = Os.recvfrom(
+ mSocket, mSolication, 0, mSolication.length, 0, solicitor);
+ // Do the least possible amount of validation.
+ if (rval < 1 || mSolication[0] != ICMPV6_ND_ROUTER_SOLICIT) {
+ continue;
+ }
+ } catch (ErrnoException | SocketException e) {
+ if (isSocketValid()) {
+ Log.e(TAG, "recvfrom error: " + e);
+ }
+ continue;
+ }
+
+ maybeSendRA(solicitor);
+ }
+ }
+ }
+
+ // TODO: Consider moving this to run on a provided Looper as a Handler,
+ // with WakeupMessage-style messages providing the timer driven input.
+ private final class MulticastTransmitter extends Thread {
+ private final Random mRandom = new Random();
+ private final AtomicInteger mUrgentAnnouncements = new AtomicInteger(0);
+
+ @Override
+ public void run() {
+ while (isSocketValid()) {
+ try {
+ Thread.sleep(getNextMulticastTransmitDelayMs());
+ } catch (InterruptedException ignored) {
+ // Stop sleeping, immediately send an RA, and continue.
+ }
+
+ maybeSendRA(mAllNodes);
+ }
+ }
+
+ public void hup() {
+ // Set to one fewer that the desired number, because as soon as
+ // the thread interrupt is processed we immediately send an RA
+ // and mUrgentAnnouncements is not examined until the subsequent
+ // sleep interval computation (i.e. this way we send 3 and not 4).
+ mUrgentAnnouncements.set(MAX_URGENT_RTR_ADVERTISEMENTS - 1);
+ interrupt();
+ }
+
+ private int getNextMulticastTransmitDelaySec() {
+ synchronized (mLock) {
+ if (mRaLength < MIN_RA_HEADER_SIZE) {
+ // No actual RA to send; just sleep for 1 day.
+ return DAY_IN_SECONDS;
+ }
+ }
+
+ final int urgentPending = mUrgentAnnouncements.getAndDecrement();
+ if (urgentPending > 0) {
+ return MIN_DELAY_BETWEEN_RAS_SEC;
+ }
+
+ return MIN_RTR_ADV_INTERVAL_SEC + mRandom.nextInt(
+ MAX_RTR_ADV_INTERVAL_SEC - MIN_RTR_ADV_INTERVAL_SEC);
+ }
+
+ private long getNextMulticastTransmitDelayMs() {
+ return 1000 * (long) getNextMulticastTransmitDelaySec();
+ }
+ }
+}
diff --git a/services/retaildemo/java/com/android/server/retaildemo/PreloadAppsInstaller.java b/services/retaildemo/java/com/android/server/retaildemo/PreloadAppsInstaller.java
new file mode 100644
index 0000000..8480170
--- /dev/null
+++ b/services/retaildemo/java/com/android/server/retaildemo/PreloadAppsInstaller.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.retaildemo;
+
+import android.app.AppGlobals;
+import android.app.PackageInstallObserver;
+import android.content.pm.IPackageManager;
+import android.content.pm.PackageManager;
+import android.os.Bundle;
+import android.os.Environment;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.util.ArrayMap;
+import android.util.Log;
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.ArrayUtils;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Collections;
+import java.util.Map;
+
+/**
+ * Helper class for installing preloaded APKs
+ */
+class PreloadAppsInstaller {
+ private static final String SYSTEM_SERVER_PACKAGE_NAME = "android";
+ private static String TAG = PreloadAppsInstaller.class.getSimpleName();
+ private static final String PRELOAD_APK_EXT = ".apk.preload";
+ private static boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+
+ private final IPackageManager mPackageManager;
+ private final File preloadsAppsDirectory;
+
+ private final Map<String, String> mApkToPackageMap;
+
+ PreloadAppsInstaller() {
+ this(AppGlobals.getPackageManager(), Environment.getDataPreloadsAppsDirectory());
+ }
+
+ @VisibleForTesting
+ PreloadAppsInstaller(IPackageManager packageManager, File preloadsAppsDirectory) {
+ mPackageManager = packageManager;
+ mApkToPackageMap = Collections.synchronizedMap(new ArrayMap<>());
+ this.preloadsAppsDirectory = preloadsAppsDirectory;
+ }
+
+ void installApps(int userId) {
+ File[] files = preloadsAppsDirectory.listFiles();
+ if (ArrayUtils.isEmpty(files)) {
+ return;
+ }
+ for (File file : files) {
+ String apkName = file.getName();
+ if (apkName.endsWith(PRELOAD_APK_EXT) && file.isFile()) {
+ String packageName = mApkToPackageMap.get(apkName);
+ if (packageName != null) {
+ try {
+ installExistingPackage(packageName, userId);
+ } catch (Exception e) {
+ Slog.e(TAG, "Failed to install existing package " + packageName, e);
+ }
+ } else {
+ try {
+ installPackage(file, userId);
+ } catch (Exception e) {
+ Slog.e(TAG, "Failed to install package from " + file, e);
+ }
+ }
+ }
+ }
+ }
+
+ private void installExistingPackage(String packageName, int userId) {
+ if (DEBUG) {
+ Log.d(TAG, "installExistingPackage " + packageName + " u" + userId);
+ }
+ try {
+ mPackageManager.installExistingPackageAsUser(packageName, userId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ private void installPackage(File file, final int userId) throws IOException, RemoteException {
+ final String apkName = file.getName();
+ if (DEBUG) {
+ Log.d(TAG, "installPackage " + apkName + " u" + userId);
+ }
+ mPackageManager.installPackageAsUser(file.getPath(), new PackageInstallObserver() {
+ @Override
+ public void onPackageInstalled(String basePackageName, int returnCode, String msg,
+ Bundle extras) {
+ if (DEBUG) {
+ Log.d(TAG, "Package " + basePackageName + " installed u" + userId
+ + " returnCode: " + returnCode + " msg: " + msg);
+ }
+ if (returnCode == PackageManager.INSTALL_SUCCEEDED) {
+ mApkToPackageMap.put(apkName, basePackageName);
+ // Install on user 0 so that the package is cached when demo user is re-created
+ installExistingPackage(basePackageName, UserHandle.USER_SYSTEM);
+ } else if (returnCode == PackageManager.INSTALL_FAILED_ALREADY_EXISTS) {
+ installExistingPackage(basePackageName, userId);
+ }
+ }
+ }.getBinder(), 0, SYSTEM_SERVER_PACKAGE_NAME, userId);
+ }
+
+}
\ No newline at end of file
diff --git a/services/retaildemo/java/com/android/server/retaildemo/RetailDemoModeService.java b/services/retaildemo/java/com/android/server/retaildemo/RetailDemoModeService.java
index a4d4013..a92498b 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.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,7 +53,9 @@
import android.os.SystemClock;
import android.os.UserHandle;
import android.os.UserManager;
+import android.provider.MediaStore;
import android.provider.Settings;
+import android.util.KeyValueListParser;
import android.util.Slog;
import com.android.internal.os.BackgroundThread;
import com.android.internal.R;
@@ -72,15 +76,17 @@
private static final String TAG = RetailDemoModeService.class.getSimpleName();
private static final String DEMO_USER_NAME = "Demo";
- private static final String ACTION_RESET_DEMO = "com.android.server.am.ACTION_RESET_DEMO";
+ private static final String ACTION_RESET_DEMO =
+ "com.android.server.retaildemo.ACTION_RESET_DEMO";
private static final int MSG_TURN_SCREEN_ON = 0;
private static final int MSG_INACTIVITY_TIME_OUT = 1;
private static final int MSG_START_NEW_SESSION = 2;
private static final long SCREEN_WAKEUP_DELAY = 2500;
- private static final long USER_INACTIVITY_TIMEOUT = 30000;
- private static final long WARNING_DIALOG_TIMEOUT = 6000;
+ private static final long USER_INACTIVITY_TIMEOUT_MIN = 10000;
+ private static final long USER_INACTIVITY_TIMEOUT_DEFAULT = 30000;
+ private static final long WARNING_DIALOG_TIMEOUT_DEFAULT = 6000;
private static final long MILLIS_PER_SECOND = 1000;
private static final int[] VOLUME_STREAMS_TO_MUTE = {
@@ -94,6 +100,8 @@
boolean mDeviceInDemoMode = false;
int mCurrentUserId = UserHandle.USER_SYSTEM;
+ long mUserInactivityTimeout;
+ long mWarningDialogTimeout;
private ActivityManagerService mAms;
private ActivityManagerInternal mAmi;
private AudioManager mAudioManager;
@@ -107,6 +115,7 @@
private CameraManager mCameraManager;
private String[] mCameraIdsWithFlash;
private Configuration mSystemUserConfiguration;
+ private PreloadAppsInstaller mPreloadAppsInstaller;
final Object mActivityLock = new Object();
// Whether the newly created demo user has interacted with the screen yet
@@ -176,9 +185,79 @@
}
}
+ private class SettingsObserver extends ContentObserver {
+
+ private final static String KEY_USER_INACTIVITY_TIMEOUT = "user_inactivity_timeout_ms";
+ private final static String KEY_WARNING_DIALOG_TIMEOUT = "warning_dialog_timeout_ms";
+
+ private final Uri mDeviceDemoModeUri = Settings.Global
+ .getUriFor(Settings.Global.DEVICE_DEMO_MODE);
+ private final Uri mDeviceProvisionedUri = Settings.Global
+ .getUriFor(Settings.Global.DEVICE_PROVISIONED);
+ private final Uri mRetailDemoConstantsUri = Settings.Global
+ .getUriFor(Settings.Global.RETAIL_DEMO_MODE_CONSTANTS);
+
+ private final KeyValueListParser mParser = new KeyValueListParser(',');
+
+ public SettingsObserver(Handler handler) {
+ super(handler);
+ }
+
+ public void register() {
+ ContentResolver cr = getContext().getContentResolver();
+ cr.registerContentObserver(mDeviceDemoModeUri, false, this, UserHandle.USER_SYSTEM);
+ cr.registerContentObserver(mDeviceProvisionedUri, false, this, UserHandle.USER_SYSTEM);
+ cr.registerContentObserver(mRetailDemoConstantsUri, false, this,
+ UserHandle.USER_SYSTEM);
+ }
+
+ @Override
+ public void onChange(boolean selfChange, Uri uri) {
+ if (mRetailDemoConstantsUri.equals(uri)) {
+ refreshTimeoutConstants();
+ return;
+ }
+ if (mDeviceDemoModeUri.equals(uri)) {
+ mDeviceInDemoMode = UserManager.isDeviceInDemoMode(getContext());
+ if (mDeviceInDemoMode) {
+ mHandler.sendEmptyMessage(MSG_START_NEW_SESSION);
+ } else if (mWakeLock.isHeld()) {
+ mWakeLock.release();
+ }
+ }
+ // If device is provisioned and left demo mode - run the cleanup in demo folder
+ if (!mDeviceInDemoMode && isDeviceProvisioned()) {
+ // Run on the bg thread to not block the fg thread
+ BackgroundThread.getHandler().post(new Runnable() {
+ @Override
+ public void run() {
+ if (!deleteDemoFolderContents()) {
+ Slog.w(TAG, "Failed to delete demo folder contents");
+ }
+ }
+ });
+ }
+ }
+
+ private void refreshTimeoutConstants() {
+ try {
+ mParser.setString(Settings.Global.getString(getContext().getContentResolver(),
+ Settings.Global.RETAIL_DEMO_MODE_CONSTANTS));
+ } catch (IllegalArgumentException exc) {
+ Slog.e(TAG, "Invalid string passed to KeyValueListParser");
+ // Consuming the exception to fall back to default values.
+ }
+ mWarningDialogTimeout = mParser.getLong(KEY_WARNING_DIALOG_TIMEOUT,
+ WARNING_DIALOG_TIMEOUT_DEFAULT);
+ mUserInactivityTimeout = mParser.getLong(KEY_USER_INACTIVITY_TIMEOUT,
+ USER_INACTIVITY_TIMEOUT_DEFAULT);
+ mUserInactivityTimeout = Math.max(mUserInactivityTimeout, USER_INACTIVITY_TIMEOUT_MIN);
+ }
+ }
+
private void showInactivityCountdownDialog() {
UserInactivityCountdownDialog dialog = new UserInactivityCountdownDialog(getContext(),
- WARNING_DIALOG_TIMEOUT, MILLIS_PER_SECOND);
+ mWarningDialogTimeout, MILLIS_PER_SECOND);
dialog.setNegativeButtonClickListener(null);
dialog.setPositiveButtonClickListener(new DialogInterface.OnClickListener() {
@Override
@@ -200,6 +279,7 @@
synchronized (mActivityLock) {
mFirstUserActivityTime = mLastUserActivityTime = SystemClock.uptimeMillis();
}
+ mPreloadAppsInstaller = new PreloadAppsInstaller();
}
private Notification createResetNotification() {
@@ -250,6 +330,28 @@
um.setUserRestriction(UserManager.DISALLOW_MODIFY_ACCOUNTS, true, user);
Settings.Secure.putIntForUser(getContext().getContentResolver(),
Settings.Secure.SKIP_FIRST_USE_HINTS, 1, userInfo.id);
+ Settings.Secure.putIntForUser(getContext().getContentResolver(),
+ Settings.Global.PACKAGE_VERIFIER_ENABLE, 0, userInfo.id);
+
+ grantRuntimePermissionToCamera(userInfo.getUserHandle());
+ }
+
+ 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 +359,7 @@
synchronized (mActivityLock) {
sessionDuration = (int) ((mLastUserActivityTime - mFirstUserActivityTime) / 1000);
}
- MetricsLogger.count(getContext(), DEMO_SESSION_DURATION, sessionDuration);
+ MetricsLogger.histogram(getContext(), DEMO_SESSION_DURATION, sessionDuration);
}
private ActivityManagerService getActivityManager() {
@@ -281,42 +383,6 @@
return mAudioManager;
}
- private void registerSettingsChangeObserver() {
- final Uri deviceDemoModeUri = Settings.Global.getUriFor(Settings.Global.DEVICE_DEMO_MODE);
- final Uri deviceProvisionedUri = Settings.Global.getUriFor(
- Settings.Global.DEVICE_PROVISIONED);
- final ContentResolver cr = getContext().getContentResolver();
- final ContentObserver deviceDemoModeSettingObserver = new ContentObserver(mHandler) {
- @Override
- public void onChange(boolean selfChange, Uri uri, int userId) {
- if (deviceDemoModeUri.equals(uri)) {
- mDeviceInDemoMode = UserManager.isDeviceInDemoMode(getContext());
- if (mDeviceInDemoMode) {
- mHandler.sendEmptyMessage(MSG_START_NEW_SESSION);
- } else if (mWakeLock.isHeld()) {
- mWakeLock.release();
- }
- }
- // If device is provisioned and left demo mode - run the cleanup in demo folder
- if (!mDeviceInDemoMode && isDeviceProvisioned()) {
- // Run on the bg thread to not block the fg thread
- BackgroundThread.getHandler().post(new Runnable() {
- @Override
- public void run() {
- if (!deleteDemoFolderContents()) {
- Slog.w(TAG, "Failed to delete demo folder contents");
- }
- }
- });
- }
- }
- };
- cr.registerContentObserver(deviceDemoModeUri, false, deviceDemoModeSettingObserver,
- UserHandle.USER_SYSTEM);
- cr.registerContentObserver(deviceProvisionedUri, false, deviceDemoModeSettingObserver,
- UserHandle.USER_SYSTEM);
- }
-
private boolean isDeviceProvisioned() {
return Settings.Global.getInt(
getContext().getContentResolver(), Settings.Global.DEVICE_PROVISIONED, 0) != 0;
@@ -404,7 +470,9 @@
mDeviceInDemoMode = true;
mHandler.sendEmptyMessage(MSG_START_NEW_SESSION);
}
- registerSettingsChangeObserver();
+ SettingsObserver settingsObserver = new SettingsObserver(mHandler);
+ settingsObserver.register();
+ settingsObserver.refreshTimeoutConstants();
registerBroadcastReceiver();
}
@@ -435,6 +503,12 @@
}
MetricsLogger.count(getContext(), DEMO_SESSION_COUNT, 1);
mHandler.removeMessages(MSG_INACTIVITY_TIME_OUT);
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ mPreloadAppsInstaller.installApps(userId);
+ }
+ });
}
private RetailDemoModeServiceInternal mLocalService = new RetailDemoModeServiceInternal() {
@@ -458,7 +532,7 @@
}
}
mHandler.removeMessages(MSG_INACTIVITY_TIME_OUT);
- mHandler.sendEmptyMessageDelayed(MSG_INACTIVITY_TIME_OUT, USER_INACTIVITY_TIMEOUT);
+ mHandler.sendEmptyMessageDelayed(MSG_INACTIVITY_TIME_OUT, mUserInactivityTimeout);
}
};
}
diff --git a/services/tests/servicestests/src/com/android/server/ConnectivityServiceTest.java b/services/tests/servicestests/src/com/android/server/ConnectivityServiceTest.java
index d424717..f2a9315 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 @@
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 @@
* @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 @@
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 @@
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 @@
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 WrappedConnectivityService(Context context, INetworkManagementService netManager,
INetworkStatsService statsService, INetworkPolicyManager policyManager) {
super(context, netManager, statsService, policyManager);
+ mLingerDelayMs = TEST_LINGER_DELAY_MS;
}
@Override
@@ -642,6 +645,12 @@
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 void setUp() throws Exception {
super.setUp();
- NetworkMonitor.SetDefaultLingerTime(120);
-
// InstrumentationTestRunner prepares a looper, but AndroidJUnitRunner does not.
// http://b/25897652 .
if (Looper.myLooper() == null) {
@@ -1033,6 +1040,8 @@
enum CallbackState {
NONE,
AVAILABLE,
+ NETWORK_CAPABILITIES,
+ LINK_PROPERTIES,
LOSING,
LOST
}
@@ -1051,42 +1060,61 @@
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));
+ protected void setLastCallback(CallbackState state, Network network, Object o) {
+ mCallbacks.offer(new CallbackInfo(state, network, o));
}
+ @Override
public void onAvailable(Network network) {
- setLastCallback(CallbackState.AVAILABLE, network);
+ setLastCallback(CallbackState.AVAILABLE, network, null);
}
+ @Override
public void onLosing(Network network, int maxMsToLive) {
- setLastCallback(CallbackState.LOSING, network);
+ setLastCallback(CallbackState.LOSING, network, maxMsToLive /* autoboxed int */);
}
+ @Override
public void onLost(Network network) {
- setLastCallback(CallbackState.LOST, network);
+ setLastCallback(CallbackState.LOST, network, null);
+ }
+
+ void expectCallback(CallbackState state, MockNetworkAgent mockAgent, int timeoutMs) {
+ CallbackInfo expected = new CallbackInfo(
+ state, (mockAgent != null) ? mockAgent.getNetwork() : null, 0);
+ CallbackInfo actual;
+ try {
+ 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) {
- CallbackInfo expected = new CallbackInfo(
- state,
- (mockAgent != null) ? mockAgent.getNetwork() : null);
- try {
- assertEquals("Unexpected callback:",
- expected, mCallbacks.poll(TIMEOUT_MS, TimeUnit.MILLISECONDS));
- } catch (InterruptedException e) {
- fail("Did not receive expected " + expected + " after " + TIMEOUT_MS + "ms");
- }
+ expectCallback(state, mockAgent, TIMEOUT_MS);
}
void assertNoCallback() {
@@ -1249,6 +1277,8 @@
}
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 +1336,8 @@
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 +1348,24 @@
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 +1380,43 @@
.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);
@@ -1687,6 +1749,67 @@
defaultNetworkCallback.expectCallback(CallbackState.LOST, mCellNetworkAgent);
}
+ private class TestRequestUpdateCallback extends TestNetworkCallback {
+ @Override
+ public void onCapabilitiesChanged(Network network, NetworkCapabilities netCap) {
+ setLastCallback(CallbackState.NETWORK_CAPABILITIES, network, netCap);
+ }
+
+ @Override
+ public void onLinkPropertiesChanged(Network network, LinkProperties linkProp) {
+ setLastCallback(CallbackState.LINK_PROPERTIES, network, linkProp);
+ }
+ }
+
+ @LargeTest
+ public void testRequestCallbackUpdates() throws Exception {
+ // File a network request for mobile.
+ final TestNetworkCallback cellNetworkCallback = new TestRequestUpdateCallback();
+ final NetworkRequest cellRequest = new NetworkRequest.Builder()
+ .addTransportType(TRANSPORT_CELLULAR).build();
+ mCm.requestNetwork(cellRequest, cellNetworkCallback);
+
+ // Bring up the mobile network.
+ mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
+ mCellNetworkAgent.connect(true);
+
+ // We should get onAvailable().
+ cellNetworkCallback.expectCallback(CallbackState.AVAILABLE, mCellNetworkAgent);
+ // We should get onCapabilitiesChanged(), when the mobile network successfully validates.
+ cellNetworkCallback.expectCallback(CallbackState.NETWORK_CAPABILITIES, mCellNetworkAgent);
+ cellNetworkCallback.assertNoCallback();
+
+ // Update LinkProperties.
+ final LinkProperties lp = new LinkProperties();
+ lp.setInterfaceName("foonet_data0");
+ mCellNetworkAgent.sendLinkProperties(lp);
+ // We should get onLinkPropertiesChanged().
+ cellNetworkCallback.expectCallback(CallbackState.LINK_PROPERTIES, mCellNetworkAgent);
+ cellNetworkCallback.assertNoCallback();
+
+ // Register a garden variety default network request.
+ final TestNetworkCallback dfltNetworkCallback = new TestRequestUpdateCallback();
+ mCm.registerDefaultNetworkCallback(dfltNetworkCallback);
+ // Only onAvailable() is called; no other information is delivered.
+ dfltNetworkCallback.expectCallback(CallbackState.AVAILABLE, mCellNetworkAgent);
+ dfltNetworkCallback.assertNoCallback();
+
+ // Request a NetworkCapabilities update; only the requesting callback is notified.
+ mCm.requestNetworkCapabilities(dfltNetworkCallback);
+ dfltNetworkCallback.expectCallback(CallbackState.NETWORK_CAPABILITIES, mCellNetworkAgent);
+ cellNetworkCallback.assertNoCallback();
+ dfltNetworkCallback.assertNoCallback();
+
+ // Request a LinkProperties update; only the requesting callback is notified.
+ mCm.requestLinkProperties(dfltNetworkCallback);
+ dfltNetworkCallback.expectCallback(CallbackState.LINK_PROPERTIES, mCellNetworkAgent);
+ cellNetworkCallback.assertNoCallback();
+ dfltNetworkCallback.assertNoCallback();
+
+ mCm.unregisterNetworkCallback(dfltNetworkCallback);
+ mCm.unregisterNetworkCallback(cellNetworkCallback);
+ }
+
@SmallTest
public void testRequestBenchmark() throws Exception {
// Benchmarks connecting and switching performance in the presence of a large number of
diff --git a/services/tests/servicestests/src/com/android/server/NetworkPolicyManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/NetworkPolicyManagerServiceTest.java
index 622e46e..979f160 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.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.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 @@
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 @@
}
- @Override
- public void tearDown() throws Exception {
+ public void _tearDown() throws Exception {
for (File file : mPolicyDir.listFiles()) {
file.delete();
}
@@ -263,6 +264,7 @@
backgroundChanged.get();
}
+ @Suppress
public void testPidForegroundCombined() throws Exception {
IdleFuture idle;
@@ -310,6 +312,7 @@
assertFalse(mService.isUidForeground(UID_A));
}
+ @Suppress
public void testScreenChangesRules() throws Exception {
Future<Void> future;
@@ -351,6 +354,7 @@
verifyAndReset();
}
+ @Suppress
public void testPolicyNone() throws Exception {
Future<Void> future;
@@ -381,6 +385,7 @@
verifyAndReset();
}
+ @Suppress
public void testPolicyReject() throws Exception {
Future<Void> future;
@@ -412,6 +417,7 @@
verifyAndReset();
}
+ @Suppress
public void testPolicyRejectAddRemove() throws Exception {
Future<Void> future;
@@ -576,6 +582,17 @@
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 @@
verifyAndReset();
}
+ @Suppress
public void testUidRemovedPolicyCleared() throws Exception {
Future<Void> future;
@@ -652,6 +670,7 @@
verifyAndReset();
}
+ @Suppress
public void testOverWarningLimitNotification() throws Exception {
NetworkState[] state = null;
NetworkStats stats = null;
@@ -783,6 +802,7 @@
}
}
+ @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 23c58fe..43d2a1f 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.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.util.Log;
import android.util.Slog;
+import com.android.internal.app.IVoiceInteractionSessionListener;
import com.android.internal.app.IVoiceInteractionManagerService;
import com.android.internal.app.IVoiceInteractionSessionShowCallback;
import com.android.internal.app.IVoiceInteractor;
import com.android.internal.content.PackageMonitor;
import com.android.internal.os.BackgroundThread;
import com.android.server.LocalServices;
-import com.android.server.soundtrigger.SoundTriggerInternal;
import com.android.server.SystemService;
import com.android.server.UiThread;
+import com.android.server.soundtrigger.SoundTriggerInternal;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -84,6 +86,9 @@
final TreeSet<Integer> mLoadedKeyphraseIds;
SoundTriggerInternal mSoundTriggerInternal;
+ private final RemoteCallbackList<IVoiceInteractionSessionListener>
+ mVoiceInteractionSessionListeners = new RemoteCallbackList<>();
+
public VoiceInteractionManagerService(Context context) {
super(context);
mContext = context;
@@ -1038,6 +1043,48 @@
}
@Override
+ public void registerVoiceInteractionSessionListener(
+ IVoiceInteractionSessionListener listener) {
+ enforceCallingPermission(Manifest.permission.ACCESS_VOICE_INTERACTION_SERVICE);
+ synchronized (this) {
+ mVoiceInteractionSessionListeners.register(listener);
+ }
+ }
+
+ public void onSessionShown() {
+ synchronized (this) {
+ final int size = mVoiceInteractionSessionListeners.beginBroadcast();
+ for (int i = 0; i < size; ++i) {
+ final IVoiceInteractionSessionListener listener =
+ mVoiceInteractionSessionListeners.getBroadcastItem(i);
+ try {
+ listener.onVoiceSessionShown();
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Error delivering voice interaction open event.", e);
+ }
+ }
+ mVoiceInteractionSessionListeners.finishBroadcast();
+ }
+ }
+
+ public void onSessionHidden() {
+ synchronized (this) {
+ final int size = mVoiceInteractionSessionListeners.beginBroadcast();
+ for (int i = 0; i < size; ++i) {
+ final IVoiceInteractionSessionListener listener =
+ mVoiceInteractionSessionListeners.getBroadcastItem(i);
+ try {
+ listener.onVoiceSessionHidden();
+
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Error delivering voice interaction closed event.", e);
+ }
+ }
+ mVoiceInteractionSessionListeners.finishBroadcast();
+ }
+ }
+
+ @Override
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
if (mContext.checkCallingOrSelfPermission(Manifest.permission.DUMP)
!= PackageManager.PERMISSION_GRANTED) {
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
index 3f9da4c..a46ccee 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
@@ -41,6 +41,7 @@
import android.util.Slog;
import android.view.IWindowManager;
+import com.android.internal.app.IVoiceInteractionSessionListener;
import com.android.internal.app.IVoiceInteractionSessionShowCallback;
import com.android.internal.app.IVoiceInteractor;
import com.android.server.LocalServices;
@@ -58,7 +59,7 @@
final Context mContext;
final Handler mHandler;
- final Object mLock;
+ final VoiceInteractionManagerService.VoiceInteractionManagerServiceStub mServiceStub;
final int mUser;
final ComponentName mComponent;
final IActivityManager mAm;
@@ -77,7 +78,7 @@
if (Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(intent.getAction())) {
String reason = intent.getStringExtra("reason");
if (!CLOSE_REASON_VOICE_INTERACTION.equals(reason) && !"dream".equals(reason)) {
- synchronized (mLock) {
+ synchronized (mServiceStub) {
if (mActiveSession != null && mActiveSession.mSession != null) {
try {
mActiveSession.mSession.closeSystemDialogs();
@@ -93,7 +94,7 @@
final ServiceConnection mConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
- synchronized (mLock) {
+ synchronized (mServiceStub) {
mService = IVoiceInteractionService.Stub.asInterface(service);
try {
mService.ready();
@@ -108,11 +109,12 @@
}
};
- VoiceInteractionManagerServiceImpl(Context context, Handler handler, Object lock,
+ VoiceInteractionManagerServiceImpl(Context context, Handler handler,
+ VoiceInteractionManagerService.VoiceInteractionManagerServiceStub stub,
int userHandle, ComponentName service) {
mContext = context;
mHandler = handler;
- mLock = lock;
+ mServiceStub = stub;
mUser = userHandle;
mComponent = service;
mAm = ActivityManagerNative.getDefault();
@@ -148,8 +150,9 @@
public boolean showSessionLocked(Bundle args, int flags,
IVoiceInteractionSessionShowCallback showCallback, IBinder activityToken) {
if (mActiveSession == null) {
- mActiveSession = new VoiceInteractionSessionConnection(mLock, mSessionComponentName,
- mUser, mContext, this, mInfo.getServiceInfo().applicationInfo.uid, mHandler);
+ mActiveSession = new VoiceInteractionSessionConnection(mServiceStub,
+ mSessionComponentName, mUser, mContext, this,
+ mInfo.getServiceInfo().applicationInfo.uid, mHandler);
}
List<IBinder> activityTokens = null;
if (activityToken == null) {
@@ -355,8 +358,18 @@
@Override
public void sessionConnectionGone(VoiceInteractionSessionConnection connection) {
- synchronized (mLock) {
+ synchronized (mServiceStub) {
finishLocked(connection.mToken, false);
}
}
+
+ @Override
+ public void onSessionShown(VoiceInteractionSessionConnection connection) {
+ mServiceStub.onSessionShown();
+ }
+
+ @Override
+ public void onSessionHidden(VoiceInteractionSessionConnection connection) {
+ mServiceStub.onSessionHidden();
+ }
}
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionSessionConnection.java
index 0694911..b0cc2ac 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 @@
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 @@
} else if (showCallback != null) {
mPendingShowCallbacks.add(showCallback);
}
+ mCallback.onSessionShown(this);
return true;
}
if (showCallback != null) {
@@ -468,6 +471,7 @@
} catch (RemoteException e) {
}
}
+ mCallback.onSessionHidden(this);
}
if (mFullyBound) {
mContext.unbindService(mFullConnection);
diff --git a/telecomm/java/android/telecom/Connection.java b/telecomm/java/android/telecom/Connection.java
index 766e930..441efb7 100644
--- a/telecomm/java/android/telecom/Connection.java
+++ b/telecomm/java/android/telecom/Connection.java
@@ -407,11 +407,11 @@
/**
* Boolean connection extra key on a {@link Connection} which indicates that adding an
- * additional call is disallowed when there is a video call in progress.
+ * additional call is disallowed.
* @hide
*/
- public static final String EXTRA_DISABLE_ADD_CALL_DURING_VIDEO_CALL =
- "android.telecom.extra.DISABLE_ADD_CALL_DURING_VIDEO_CALL";
+ public static final String EXTRA_DISABLE_ADD_CALL =
+ "android.telecom.extra.DISABLE_ADD_CALL";
/**
* Connection event used to inform Telecom that it should play the on hold tone. This is used
diff --git a/telecomm/java/android/telecom/ParcelableCallAnalytics.java b/telecomm/java/android/telecom/ParcelableCallAnalytics.java
index 0ee9bab..383d10b 100644
--- a/telecomm/java/android/telecom/ParcelableCallAnalytics.java
+++ b/telecomm/java/android/telecom/ParcelableCallAnalytics.java
@@ -21,6 +21,7 @@
import android.os.Parcelable;
import java.util.ArrayList;
+import java.util.LinkedList;
import java.util.List;
/**
@@ -28,6 +29,68 @@
*/
@SystemApi
public class ParcelableCallAnalytics implements Parcelable {
+ /** {@hide} */
+ public static final class VideoEvent implements Parcelable {
+ public static final int SEND_LOCAL_SESSION_MODIFY_REQUEST = 0;
+ public static final int SEND_LOCAL_SESSION_MODIFY_RESPONSE = 1;
+ public static final int RECEIVE_REMOTE_SESSION_MODIFY_REQUEST = 2;
+ public static final int RECEIVE_REMOTE_SESSION_MODIFY_RESPONSE = 3;
+
+ public static final Parcelable.Creator<VideoEvent> CREATOR =
+ new Parcelable.Creator<VideoEvent> () {
+
+ @Override
+ public VideoEvent createFromParcel(Parcel in) {
+ return new VideoEvent(in);
+ }
+
+ @Override
+ public VideoEvent[] newArray(int size) {
+ return new VideoEvent[size];
+ }
+ };
+
+ private int mEventName;
+ private long mTimeSinceLastEvent;
+ private int mVideoState;
+
+ public VideoEvent(int eventName, long timeSinceLastEvent, int videoState) {
+ mEventName = eventName;
+ mTimeSinceLastEvent = timeSinceLastEvent;
+ mVideoState = videoState;
+ }
+
+ VideoEvent(Parcel in) {
+ mEventName = in.readInt();
+ mTimeSinceLastEvent = in.readLong();
+ mVideoState = in.readInt();
+ }
+
+ public int getEventName() {
+ return mEventName;
+ }
+
+ public long getTimeSinceLastEvent() {
+ return mTimeSinceLastEvent;
+ }
+
+ public int getVideoState() {
+ return mVideoState;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeInt(mEventName);
+ out.writeLong(mTimeSinceLastEvent);
+ out.writeInt(mVideoState);
+ }
+ }
+
public static final class AnalyticsEvent implements Parcelable {
public static final int SET_SELECT_PHONE_ACCOUNT = 0;
public static final int SET_ACTIVE = 1;
@@ -250,6 +313,12 @@
// A map from event-pair names to their durations.
private final List<EventTiming> eventTimings;
+ // Whether the call has ever been a video call.
+ private boolean isVideoCall = false;
+
+ // A list of video events that have occurred.
+ private List<VideoEvent> videoEvents;
+
public ParcelableCallAnalytics(long startTimeMillis, long callDurationMillis, int callType,
boolean isAdditionalCall, boolean isInterrupted, int callTechnologies,
int callTerminationCode, boolean isEmergencyCall, String connectionService,
@@ -284,6 +353,9 @@
in.readTypedList(analyticsEvents, AnalyticsEvent.CREATOR);
eventTimings = new ArrayList<>();
in.readTypedList(eventTimings, EventTiming.CREATOR);
+ isVideoCall = readByteAsBoolean(in);
+ videoEvents = new LinkedList<>();
+ in.readTypedList(videoEvents, VideoEvent.CREATOR);
}
public void writeToParcel(Parcel out, int flags) {
@@ -299,6 +371,18 @@
writeBooleanAsByte(out, isCreatedFromExistingConnection);
out.writeTypedList(analyticsEvents);
out.writeTypedList(eventTimings);
+ writeBooleanAsByte(out, isVideoCall);
+ out.writeTypedList(videoEvents);
+ }
+
+ /** {@hide} */
+ public void setIsVideoCall(boolean isVideoCall) {
+ this.isVideoCall = isVideoCall;
+ }
+
+ /** {@hide} */
+ public void setVideoEvents(List<VideoEvent> videoEvents) {
+ this.videoEvents = videoEvents;
}
public long getStartTimeMillis() {
@@ -349,6 +433,16 @@
return eventTimings;
}
+ /** {@hide} */
+ public boolean isVideoCall() {
+ return isVideoCall;
+ }
+
+ /** {@hide} */
+ public List<VideoEvent> getVideoEvents() {
+ return videoEvents;
+ }
+
@Override
public int describeContents() {
return 0;
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 900a157..a84f13e 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -829,7 +829,9 @@
"allow_merge_wifi_calls_when_vowifi_off_bool";
/**
- * When true, indicates that adding a call is disabled when there is an ongoing video call.
+ * When true, indicates that adding a call is disabled when there is an ongoing video call
+ * or when there is an ongoing call on wifi which was downgraded from video and VoWifi is
+ * turned off.
*/
public static final String KEY_ALLOW_ADD_CALL_DURING_VIDEO_CALL_BOOL =
"allow_add_call_during_video_call";
diff --git a/tests/SoundTriggerTestApp/AndroidManifest.xml b/tests/SoundTriggerTestApp/AndroidManifest.xml
index 71d6001..87f3e92 100644
--- a/tests/SoundTriggerTestApp/AndroidManifest.xml
+++ b/tests/SoundTriggerTestApp/AndroidManifest.xml
@@ -1,25 +1,28 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.test.soundtrigger">
-
+ <uses-permission android:name="android.permission.CAPTURE_AUDIO_HOTWORD" />
<uses-permission android:name="android.permission.MANAGE_SOUND_TRIGGER" />
+ <uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<application>
<activity
- android:name="TestSoundTriggerActivity"
+ android:name=".SoundTriggerTestActivity"
android:label="SoundTrigger Test Application"
android:screenOrientation="portrait"
android:theme="@android:style/Theme.Material">
- <!--
- <intent-filter>
- <action android:name="com.android.intent.action.MANAGE_SOUND_TRIGGER" />
- <category android:name="android.intent.category.DEFAULT" />
- </intent-filter>
- -->
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
+ <service
+ android:name=".SoundTriggerTestService"
+ android:stopWithTask="false"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="com.android.intent.action.MANAGE_SOUND_TRIGGER" />
+ </intent-filter>
+ </service>
</application>
</manifest>
diff --git a/tests/SoundTriggerTestApp/res/layout/main.xml b/tests/SoundTriggerTestApp/res/layout/main.xml
index 06949a0..0fd8b12 100644
--- a/tests/SoundTriggerTestApp/res/layout/main.xml
+++ b/tests/SoundTriggerTestApp/res/layout/main.xml
@@ -18,81 +18,107 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:orientation="vertical"
- >
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- >
+ android:orientation="vertical">
- <Button
- android:layout_width="wrap_content"
+ <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content">
+
+ <Button
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/load"
+ android:onClick="onLoadButtonClicked"
+ android:padding="20dp" />
+
+ <Button
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/start_recog"
+ android:onClick="onStartRecognitionButtonClicked"
+ android:padding="20dp" />
+
+ <Button
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/stop_recog"
+ android:onClick="onStopRecognitionButtonClicked"
+ android:padding="20dp" />
+
+ <Button
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/unload"
+ android:onClick="onUnloadButtonClicked"
+ android:padding="20dp" />
+
+ </LinearLayout>
+
+ <LinearLayout
+ android:orientation="horizontal"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content">
+
+ <Button
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/reload"
+ android:onClick="onReloadButtonClicked"
+ android:padding="20dp" />
+
+ <Button
+ android:id="@+id/play_trigger_id"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/play_trigger"
+ android:onClick="onPlayTriggerButtonClicked"
+ android:padding="20dp" />
+ </LinearLayout>
+
+ <LinearLayout
+ android:orientation="horizontal"
+ android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:text="@string/enroll"
- android:onClick="onEnrollButtonClicked"
- android:padding="20dp" />
+ android:layout_gravity="right">
- <Button
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="@string/reenroll"
- android:onClick="onReEnrollButtonClicked"
- android:padding="20dp" />
+ <CheckBox
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/capture"
+ android:id="@+id/caputre_check_box"
+ android:layout_gravity="center_horizontal"
+ android:padding="20dp" />
- <Button
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="@string/start_recog"
- android:onClick="onStartRecognitionButtonClicked"
- android:padding="20dp" />
+ <Button
+ android:id="@+id/play_captured_id"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/play_capture"
+ android:padding="20dp"
+ android:enabled="false" />
+ </LinearLayout>
- <Button
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="@string/stop_recog"
- android:onClick="onStopRecognitionButtonClicked"
- android:padding="20dp" />
-
- <Button
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="@string/unenroll"
- android:onClick="onUnEnrollButtonClicked"
- android:padding="20dp" />
-
- <Button
- android:id="@+id/play_trigger_id"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="@string/play_trigger"
- android:onClick="onPlayTriggerButtonClicked"
- android:padding="20dp" />
-
-</LinearLayout>
-
-<RadioGroup xmlns:android="http://schemas.android.com/apk/res/android"
+ <RadioGroup xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/model_group_id"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:padding="20dp"
- android:orientation="vertical">
-</RadioGroup>
+ android:orientation="vertical" />
-<ScrollView
+ <ScrollView
android:id="@+id/scroller_id"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:scrollbars="vertical"
android:fillViewport="true">
- <TextView
- android:id="@+id/console"
- android:paddingTop="20pt"
- android:layout_height="fill_parent"
- android:layout_width="fill_parent"
- android:textSize="14dp"
- android:layout_weight="1.0"
- android:text="@string/none">
- </TextView>
-</ScrollView>
+ <TextView
+ android:id="@+id/console"
+ android:paddingTop="20pt"
+ android:layout_height="fill_parent"
+ android:layout_width="fill_parent"
+ android:textSize="14dp"
+ android:layout_weight="1.0"
+ android:text="@string/none" />
+ </ScrollView>
</LinearLayout>
diff --git a/tests/SoundTriggerTestApp/res/values/strings.xml b/tests/SoundTriggerTestApp/res/values/strings.xml
index 7c1f649..c48b648 100644
--- a/tests/SoundTriggerTestApp/res/values/strings.xml
+++ b/tests/SoundTriggerTestApp/res/values/strings.xml
@@ -16,11 +16,14 @@
-->
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="enroll">Load</string>
- <string name="reenroll">Re-load</string>
- <string name="unenroll">Un-load</string>
+ <string name="load">Load</string>
+ <string name="reload">Reload</string>
+ <string name="unload">Unload</string>
<string name="start_recog">Start</string>
<string name="stop_recog">Stop</string>
<string name="play_trigger">Play Trigger Audio</string>
+ <string name="capture">Capture Audio</string>
+ <string name="stop_capture">Stop Capturing Audio</string>
+ <string name="play_capture">Play Captured Audio</string>
<string name="none">Debug messages appear here:\n</string>
</resources>
diff --git a/tests/SoundTriggerTestApp/src/com/android/test/soundtrigger/SoundTriggerTestActivity.java b/tests/SoundTriggerTestApp/src/com/android/test/soundtrigger/SoundTriggerTestActivity.java
new file mode 100644
index 0000000..4841bc5
--- /dev/null
+++ b/tests/SoundTriggerTestApp/src/com/android/test/soundtrigger/SoundTriggerTestActivity.java
@@ -0,0 +1,329 @@
+/*
+ * Copyright (C) 2014 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.test.soundtrigger;
+
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+
+import android.Manifest;
+import android.app.Activity;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.content.pm.PackageManager;
+import android.media.AudioManager;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.PowerManager;
+import android.text.Editable;
+import android.text.method.ScrollingMovementMethod;
+import android.util.Log;
+import android.view.View;
+import android.view.WindowManager;
+import android.widget.Button;
+import android.widget.CheckBox;
+import android.widget.RadioButton;
+import android.widget.RadioGroup;
+import android.widget.ScrollView;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import com.android.test.soundtrigger.SoundTriggerTestService.SoundTriggerTestBinder;
+
+public class SoundTriggerTestActivity extends Activity implements SoundTriggerTestService.UserActivity {
+ private static final String TAG = "SoundTriggerTest";
+ private static final int AUDIO_PERMISSIONS_REQUEST = 1;
+
+ private SoundTriggerTestService mService = null;
+
+ private static UUID mSelectedModelUuid = null;
+
+ private Map<RadioButton, UUID> mButtonModelUuidMap;
+ private Map<UUID, RadioButton> mModelButtons;
+ private Map<UUID, String> mModelNames;
+ private List<RadioButton> mModelRadioButtons;
+
+ private TextView mDebugView = null;
+ private ScrollView mScrollView = null;
+ private Button mPlayTriggerButton = null;
+ private PowerManager.WakeLock mScreenWakelock;
+ private Handler mHandler;
+ private RadioGroup mRadioGroup;
+ private CheckBox mCaptureAudioCheckBox;
+ private Button mPlayCapturedAudioButton = null;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ // Make sure that this activity can punch through the lockscreen if needed.
+ getWindow().addFlags(WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD |
+ WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED);
+
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.main);
+ mDebugView = (TextView) findViewById(R.id.console);
+ mScrollView = (ScrollView) findViewById(R.id.scroller_id);
+ mRadioGroup = (RadioGroup) findViewById(R.id.model_group_id);
+ mPlayTriggerButton = (Button) findViewById(R.id.play_trigger_id);
+ mDebugView.setText(mDebugView.getText(), TextView.BufferType.EDITABLE);
+ mDebugView.setMovementMethod(new ScrollingMovementMethod());
+ mCaptureAudioCheckBox = (CheckBox) findViewById(R.id.caputre_check_box);
+ mPlayCapturedAudioButton = (Button) findViewById(R.id.play_captured_id);
+ mHandler = new Handler();
+ mButtonModelUuidMap = new HashMap();
+ mModelButtons = new HashMap();
+ mModelNames = new HashMap();
+ mModelRadioButtons = new LinkedList();
+
+ setVolumeControlStream(AudioManager.STREAM_MUSIC);
+
+ // Make sure that the service is started, so even if our activity goes down, we'll still
+ // have a request for it to run.
+ startService(new Intent(getBaseContext(), SoundTriggerTestService.class));
+
+ // Bind to SoundTriggerTestService.
+ Intent intent = new Intent(this, SoundTriggerTestService.class);
+ bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
+ }
+
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+
+ // Unbind from the service.
+ if (mService != null) {
+ mService.setUserActivity(null);
+ unbindService(mConnection);
+ }
+ }
+
+ @Override
+ public void addModel(UUID modelUuid, String name) {
+ // Create a new widget for this model, and insert everything we'd need into the map.
+ RadioButton button = new RadioButton(this);
+ mModelRadioButtons.add(button);
+ button.setText(name);
+ button.setOnClickListener(new View.OnClickListener() {
+ public void onClick(View v) {
+ onRadioButtonClicked(v);
+ }
+ });
+ mButtonModelUuidMap.put(button, modelUuid);
+ mModelButtons.put(modelUuid, button);
+ mModelNames.put(modelUuid, name);
+
+ // Sort all the radio buttons by name, then push them into the group in order.
+ Collections.sort(mModelRadioButtons, new Comparator<RadioButton>(){
+ @Override
+ public int compare(RadioButton button0, RadioButton button1) {
+ return button0.getText().toString().compareTo(button1.getText().toString());
+ }
+ });
+ mRadioGroup.removeAllViews();
+ for (View v : mModelRadioButtons) {
+ mRadioGroup.addView(v);
+ }
+
+ // If we don't have something selected, select this first thing.
+ if (mSelectedModelUuid == null || mSelectedModelUuid.equals(modelUuid)) {
+ button.setChecked(true);
+ onRadioButtonClicked(button);
+ }
+ }
+
+ @Override
+ public void setModelState(UUID modelUuid, String state) {
+ runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ String newButtonText = mModelNames.get(modelUuid);
+ if (state != null) {
+ newButtonText += ": " + state;
+ }
+ mModelButtons.get(modelUuid).setText(newButtonText);
+ updateSelectModelSpecificUiElements();
+ }
+ });
+ }
+
+ @Override
+ public void showMessage(String msg, boolean showToast) {
+ // Append the message to the text field, then show the toast if requested.
+ this.runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ ((Editable) mDebugView.getText()).append(msg + "\n");
+ mScrollView.post(new Runnable() {
+ public void run() {
+ mScrollView.smoothScrollTo(0, mDebugView.getBottom());
+ }
+ });
+ if (showToast) {
+ Toast.makeText(SoundTriggerTestActivity.this, msg, Toast.LENGTH_SHORT).show();
+ }
+ }
+ });
+ }
+
+ @Override
+ public void handleDetection(UUID modelUuid) {
+ screenWakeup();
+ mHandler.postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ screenRelease();
+ }
+ }, 1000L);
+ }
+
+ private void screenWakeup() {
+ if (mScreenWakelock == null) {
+ PowerManager pm = ((PowerManager)getSystemService(POWER_SERVICE));
+ mScreenWakelock = pm.newWakeLock(
+ PowerManager.SCREEN_DIM_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP, TAG);
+ }
+ mScreenWakelock.acquire();
+ }
+
+ private void screenRelease() {
+ mScreenWakelock.release();
+ }
+
+ public void onLoadButtonClicked(View v) {
+ if (mService == null) {
+ Log.e(TAG, "Could not load sound model: not bound to SoundTriggerTestService");
+ } else {
+ mService.loadModel(mSelectedModelUuid);
+ }
+ }
+
+ public void onUnloadButtonClicked(View v) {
+ if (mService == null) {
+ Log.e(TAG, "Can't unload model: not bound to SoundTriggerTestService");
+ } else {
+ mService.unloadModel(mSelectedModelUuid);
+ }
+ }
+
+ public void onReloadButtonClicked(View v) {
+ if (mService == null) {
+ Log.e(TAG, "Can't reload model: not bound to SoundTriggerTestService");
+ } else {
+ mService.reloadModel(mSelectedModelUuid);
+ }
+ }
+
+ public void onStartRecognitionButtonClicked(View v) {
+ if (mService == null) {
+ Log.e(TAG, "Can't start recognition: not bound to SoundTriggerTestService");
+ } else {
+ mService.startRecognition(mSelectedModelUuid);
+ }
+ }
+
+ public void onStopRecognitionButtonClicked(View v) {
+ if (mService == null) {
+ Log.e(TAG, "Can't stop recognition: not bound to SoundTriggerTestService");
+ } else {
+ mService.stopRecognition(mSelectedModelUuid);
+ }
+ }
+
+ public synchronized void onPlayTriggerButtonClicked(View v) {
+ if (mService == null) {
+ Log.e(TAG, "Can't play trigger audio: not bound to SoundTriggerTestService");
+ } else {
+ mService.playTriggerAudio(mSelectedModelUuid);
+ }
+ }
+
+ public synchronized void onCaptureAudioCheckboxClicked(View v) {
+ // See if we have the right permissions
+ if (!mService.hasMicrophonePermission()) {
+ requestPermissions(new String[]{Manifest.permission.RECORD_AUDIO},
+ AUDIO_PERMISSIONS_REQUEST);
+ return;
+ } else {
+ mService.setCaptureAudio(mSelectedModelUuid, mCaptureAudioCheckBox.isChecked());
+ }
+ }
+
+ @Override
+ public synchronized void onRequestPermissionsResult(int requestCode, String permissions[],
+ int[] grantResults) {
+ if (requestCode == AUDIO_PERMISSIONS_REQUEST) {
+ if (grantResults[0] != PackageManager.PERMISSION_GRANTED) {
+ // Make sure that the check box is set to false.
+ mCaptureAudioCheckBox.setChecked(false);
+ }
+ mService.setCaptureAudio(mSelectedModelUuid, mCaptureAudioCheckBox.isChecked());
+ }
+ }
+
+ public synchronized void onPlayCapturedAudioButtonClicked(View v) {
+ if (mService == null) {
+ Log.e(TAG, "Can't play captured audio: not bound to SoundTriggerTestService");
+ } else {
+ mService.playCapturedAudio(mSelectedModelUuid);
+ }
+ }
+
+ public synchronized void onRadioButtonClicked(View view) {
+ // Is the button now checked?
+ boolean checked = ((RadioButton) view).isChecked();
+ if (checked) {
+ mSelectedModelUuid = mButtonModelUuidMap.get(view);
+ showMessage("Selected " + mModelNames.get(mSelectedModelUuid), false);
+ updateSelectModelSpecificUiElements();
+ }
+ }
+
+ private synchronized void updateSelectModelSpecificUiElements() {
+ // Set the play trigger button to be enabled only if we actually have some audio.
+ mPlayTriggerButton.setEnabled(mService.modelHasTriggerAudio((mSelectedModelUuid)));
+ // Similar logic for the captured audio.
+ mCaptureAudioCheckBox.setChecked(
+ mService.modelWillCaptureTriggerAudio(mSelectedModelUuid));
+ mPlayCapturedAudioButton.setEnabled(mService.modelHasCapturedAudio((mSelectedModelUuid)));
+ }
+
+ private ServiceConnection mConnection = new ServiceConnection() {
+ @Override
+ public void onServiceConnected(ComponentName className, IBinder service) {
+ synchronized (SoundTriggerTestActivity.this) {
+ // We've bound to LocalService, cast the IBinder and get LocalService instance
+ SoundTriggerTestBinder binder = (SoundTriggerTestBinder) service;
+ mService = binder.getService();
+ mService.setUserActivity(SoundTriggerTestActivity.this);
+ }
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName arg0) {
+ synchronized (SoundTriggerTestActivity.this) {
+ mService.setUserActivity(null);
+ mService = null;
+ }
+ }
+ };
+}
diff --git a/tests/SoundTriggerTestApp/src/com/android/test/soundtrigger/SoundTriggerTestService.java b/tests/SoundTriggerTestApp/src/com/android/test/soundtrigger/SoundTriggerTestService.java
new file mode 100644
index 0000000..0ff95c4
--- /dev/null
+++ b/tests/SoundTriggerTestApp/src/com/android/test/soundtrigger/SoundTriggerTestService.java
@@ -0,0 +1,720 @@
+/*
+ * Copyright (C) 2014 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.test.soundtrigger;
+
+import android.Manifest;
+import android.app.Service;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+import android.hardware.soundtrigger.SoundTrigger.GenericSoundModel;
+import android.media.AudioAttributes;
+import android.media.AudioFormat;
+import android.media.AudioManager;
+import android.media.AudioRecord;
+import android.media.AudioTrack;
+import android.media.MediaPlayer;
+import android.media.soundtrigger.SoundTriggerDetector;
+import android.net.Uri;
+import android.os.Binder;
+import android.os.IBinder;
+import android.util.Log;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Properties;
+import java.util.Random;
+import java.util.UUID;
+
+public class SoundTriggerTestService extends Service {
+ private static final String TAG = "SoundTriggerTestSrv";
+ private static final String INTENT_ACTION = "com.android.intent.action.MANAGE_SOUND_TRIGGER";
+
+ // Binder given to clients.
+ private final IBinder mBinder;
+ private final Map<UUID, ModelInfo> mModelInfoMap;
+ private SoundTriggerUtil mSoundTriggerUtil;
+ private Random mRandom;
+ private UserActivity mUserActivity;
+
+ public interface UserActivity {
+ void addModel(UUID modelUuid, String state);
+ void setModelState(UUID modelUuid, String state);
+ void showMessage(String msg, boolean showToast);
+ void handleDetection(UUID modelUuid);
+ }
+
+ public SoundTriggerTestService() {
+ super();
+ mRandom = new Random();
+ mModelInfoMap = new HashMap();
+ mBinder = new SoundTriggerTestBinder();
+ }
+
+ @Override
+ public synchronized int onStartCommand(Intent intent, int flags, int startId) {
+ if (mModelInfoMap.isEmpty()) {
+ mSoundTriggerUtil = new SoundTriggerUtil(this);
+ loadModelsInDataDir();
+ }
+
+ // If we get killed, after returning from here, restart
+ return START_STICKY;
+ }
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(INTENT_ACTION);
+ registerReceiver(mBroadcastReceiver, filter);
+
+ // Make sure the data directory exists, and we're the owner of it.
+ try {
+ getFilesDir().mkdir();
+ } catch (Exception e) {
+ // Don't care - we either made it, or it already exists.
+ }
+ }
+
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+ stopAllRecognitions();
+ unregisterReceiver(mBroadcastReceiver);
+ }
+
+ private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (intent != null && INTENT_ACTION.equals(intent.getAction())) {
+ String command = intent.getStringExtra("command");
+ if (command == null) {
+ Log.e(TAG, "No 'command' specified in " + INTENT_ACTION);
+ } else {
+ try {
+ if (command.equals("load")) {
+ loadModel(getModelUuidFromIntent(intent));
+ } else if (command.equals("unload")) {
+ unloadModel(getModelUuidFromIntent(intent));
+ } else if (command.equals("start")) {
+ startRecognition(getModelUuidFromIntent(intent));
+ } else if (command.equals("stop")) {
+ stopRecognition(getModelUuidFromIntent(intent));
+ } else if (command.equals("play_trigger")) {
+ playTriggerAudio(getModelUuidFromIntent(intent));
+ } else if (command.equals("play_captured")) {
+ playCapturedAudio(getModelUuidFromIntent(intent));
+ } else if (command.equals("set_capture")) {
+ setCaptureAudio(getModelUuidFromIntent(intent),
+ intent.getBooleanExtra("enabled", true));
+ } else if (command.equals("set_capture_timeout")) {
+ setCaptureAudioTimeout(getModelUuidFromIntent(intent),
+ intent.getIntExtra("timeout", 5000));
+ } else {
+ Log.e(TAG, "Unknown command '" + command + "'");
+ }
+ } catch (Exception e) {
+ Log.e(TAG, "Failed to process " + command, e);
+ }
+ }
+ }
+ }
+ };
+
+ private UUID getModelUuidFromIntent(Intent intent) {
+ // First, see if the specified the UUID straight up.
+ String value = intent.getStringExtra("modelUuid");
+ if (value != null) {
+ return UUID.fromString(value);
+ }
+
+ // If they specified a name, use that to iterate through the map of models and find it.
+ value = intent.getStringExtra("name");
+ if (value != null) {
+ for (ModelInfo modelInfo : mModelInfoMap.values()) {
+ if (value.equals(modelInfo.name)) {
+ return modelInfo.modelUuid;
+ }
+ }
+ Log.e(TAG, "Failed to find a matching model with name '" + value + "'");
+ }
+
+ // We couldn't figure out what they were asking for.
+ throw new RuntimeException("Failed to get model from intent - specify either " +
+ "'modelUuid' or 'name'");
+ }
+
+ /**
+ * Will be called when the service is killed (through swipe aways, not if we're force killed).
+ */
+ @Override
+ public void onTaskRemoved(Intent rootIntent) {
+ super.onTaskRemoved(rootIntent);
+ stopAllRecognitions();
+ stopSelf();
+ }
+
+ @Override
+ public synchronized IBinder onBind(Intent intent) {
+ return mBinder;
+ }
+
+ public class SoundTriggerTestBinder extends Binder {
+ SoundTriggerTestService getService() {
+ // Return instance of our parent so clients can call public methods.
+ return SoundTriggerTestService.this;
+ }
+ }
+
+ public synchronized void setUserActivity(UserActivity activity) {
+ mUserActivity = activity;
+ if (mUserActivity != null) {
+ for (Map.Entry<UUID, ModelInfo> entry : mModelInfoMap.entrySet()) {
+ mUserActivity.addModel(entry.getKey(), entry.getValue().name);
+ mUserActivity.setModelState(entry.getKey(), entry.getValue().state);
+ }
+ }
+ }
+
+ private synchronized void stopAllRecognitions() {
+ for (ModelInfo modelInfo : mModelInfoMap.values()) {
+ if (modelInfo.detector != null) {
+ Log.i(TAG, "Stopping recognition for " + modelInfo.name);
+ try {
+ modelInfo.detector.stopRecognition();
+ } catch (Exception e) {
+ Log.e(TAG, "Failed to stop recognition", e);
+ }
+ }
+ }
+ }
+
+ // Helper struct for holding information about a model.
+ public static class ModelInfo {
+ public String name;
+ public String state;
+ public UUID modelUuid;
+ public UUID vendorUuid;
+ public MediaPlayer triggerAudioPlayer;
+ public SoundTriggerDetector detector;
+ public byte modelData[];
+ public boolean captureAudio;
+ public int captureAudioMs;
+ public AudioTrack captureAudioTrack;
+ }
+
+ private GenericSoundModel createNewSoundModel(ModelInfo modelInfo) {
+ return new GenericSoundModel(modelInfo.modelUuid, modelInfo.vendorUuid,
+ modelInfo.modelData);
+ }
+
+ public synchronized void loadModel(UUID modelUuid) {
+ ModelInfo modelInfo = mModelInfoMap.get(modelUuid);
+ if (modelInfo == null) {
+ postError("Could not find model for: " + modelUuid.toString());
+ return;
+ }
+
+ postMessage("Loading model: " + modelInfo.name);
+
+ GenericSoundModel soundModel = createNewSoundModel(modelInfo);
+
+ boolean status = mSoundTriggerUtil.addOrUpdateSoundModel(soundModel);
+ if (status) {
+ postToast("Successfully loaded " + modelInfo.name + ", UUID=" + soundModel.uuid);
+ setModelState(modelInfo, "Loaded");
+ } else {
+ postErrorToast("Failed to load " + modelInfo.name + ", UUID=" + soundModel.uuid + "!");
+ setModelState(modelInfo, "Failed to load");
+ }
+ }
+
+ public synchronized void unloadModel(UUID modelUuid) {
+ ModelInfo modelInfo = mModelInfoMap.get(modelUuid);
+ if (modelInfo == null) {
+ postError("Could not find model for: " + modelUuid.toString());
+ return;
+ }
+
+ postMessage("Unloading model: " + modelInfo.name);
+
+ GenericSoundModel soundModel = mSoundTriggerUtil.getSoundModel(modelUuid);
+ if (soundModel == null) {
+ postErrorToast("Sound model not found for " + modelInfo.name + "!");
+ return;
+ }
+ modelInfo.detector = null;
+ boolean status = mSoundTriggerUtil.deleteSoundModel(modelUuid);
+ if (status) {
+ postToast("Successfully unloaded " + modelInfo.name + ", UUID=" + soundModel.uuid);
+ setModelState(modelInfo, "Unloaded");
+ } else {
+ postErrorToast("Failed to unload " +
+ modelInfo.name + ", UUID=" + soundModel.uuid + "!");
+ setModelState(modelInfo, "Failed to unload");
+ }
+ }
+
+ public synchronized void reloadModel(UUID modelUuid) {
+ ModelInfo modelInfo = mModelInfoMap.get(modelUuid);
+ if (modelInfo == null) {
+ postError("Could not find model for: " + modelUuid.toString());
+ return;
+ }
+ postMessage("Reloading model: " + modelInfo.name);
+ GenericSoundModel soundModel = mSoundTriggerUtil.getSoundModel(modelUuid);
+ if (soundModel == null) {
+ postErrorToast("Sound model not found for " + modelInfo.name + "!");
+ return;
+ }
+ GenericSoundModel updated = createNewSoundModel(modelInfo);
+ boolean status = mSoundTriggerUtil.addOrUpdateSoundModel(updated);
+ if (status) {
+ postToast("Successfully reloaded " + modelInfo.name + ", UUID=" + modelInfo.modelUuid);
+ setModelState(modelInfo, "Reloaded");
+ } else {
+ postErrorToast("Failed to reload "
+ + modelInfo.name + ", UUID=" + modelInfo.modelUuid + "!");
+ setModelState(modelInfo, "Failed to reload");
+ }
+ }
+
+ public synchronized void startRecognition(UUID modelUuid) {
+ ModelInfo modelInfo = mModelInfoMap.get(modelUuid);
+ if (modelInfo == null) {
+ postError("Could not find model for: " + modelUuid.toString());
+ return;
+ }
+
+ if (modelInfo.detector == null) {
+ postMessage("Creating SoundTriggerDetector for " + modelInfo.name);
+ modelInfo.detector = mSoundTriggerUtil.createSoundTriggerDetector(
+ modelUuid, new DetectorCallback(modelInfo));
+ }
+
+ postMessage("Starting recognition for " + modelInfo.name + ", UUID=" + modelInfo.modelUuid);
+ if (modelInfo.detector.startRecognition(modelInfo.captureAudio ?
+ SoundTriggerDetector.RECOGNITION_FLAG_CAPTURE_TRIGGER_AUDIO :
+ SoundTriggerDetector.RECOGNITION_FLAG_ALLOW_MULTIPLE_TRIGGERS)) {
+ setModelState(modelInfo, "Started");
+ } else {
+ postErrorToast("Fast failure attempting to start recognition for " +
+ modelInfo.name + ", UUID=" + modelInfo.modelUuid);
+ setModelState(modelInfo, "Failed to start");
+ }
+ }
+
+ public synchronized void stopRecognition(UUID modelUuid) {
+ ModelInfo modelInfo = mModelInfoMap.get(modelUuid);
+ if (modelInfo == null) {
+ postError("Could not find model for: " + modelUuid.toString());
+ return;
+ }
+
+ if (modelInfo.detector == null) {
+ postErrorToast("Stop called on null detector for " +
+ modelInfo.name + ", UUID=" + modelInfo.modelUuid);
+ return;
+ }
+ postMessage("Triggering stop recognition for " +
+ modelInfo.name + ", UUID=" + modelInfo.modelUuid);
+ if (modelInfo.detector.stopRecognition()) {
+ setModelState(modelInfo, "Stopped");
+ } else {
+ postErrorToast("Fast failure attempting to stop recognition for " +
+ modelInfo.name + ", UUID=" + modelInfo.modelUuid);
+ setModelState(modelInfo, "Failed to stop");
+ }
+ }
+
+ public synchronized void playTriggerAudio(UUID modelUuid) {
+ ModelInfo modelInfo = mModelInfoMap.get(modelUuid);
+ if (modelInfo == null) {
+ postError("Could not find model for: " + modelUuid.toString());
+ return;
+ }
+ if (modelInfo.triggerAudioPlayer != null) {
+ postMessage("Playing trigger audio for " + modelInfo.name);
+ modelInfo.triggerAudioPlayer.start();
+ } else {
+ postMessage("No trigger audio for " + modelInfo.name);
+ }
+ }
+
+ public synchronized void playCapturedAudio(UUID modelUuid) {
+ ModelInfo modelInfo = mModelInfoMap.get(modelUuid);
+ if (modelInfo == null) {
+ postError("Could not find model for: " + modelUuid.toString());
+ return;
+ }
+ if (modelInfo.captureAudioTrack != null) {
+ postMessage("Playing captured audio for " + modelInfo.name);
+ modelInfo.captureAudioTrack.stop();
+ modelInfo.captureAudioTrack.reloadStaticData();
+ modelInfo.captureAudioTrack.play();
+ } else {
+ postMessage("No captured audio for " + modelInfo.name);
+ }
+ }
+
+ public synchronized void setCaptureAudioTimeout(UUID modelUuid, int captureTimeoutMs) {
+ ModelInfo modelInfo = mModelInfoMap.get(modelUuid);
+ if (modelInfo == null) {
+ postError("Could not find model for: " + modelUuid.toString());
+ return;
+ }
+ modelInfo.captureAudioMs = captureTimeoutMs;
+ Log.i(TAG, "Set " + modelInfo.name + " capture audio timeout to " +
+ captureTimeoutMs + "ms");
+ }
+
+ public synchronized void setCaptureAudio(UUID modelUuid, boolean captureAudio) {
+ ModelInfo modelInfo = mModelInfoMap.get(modelUuid);
+ if (modelInfo == null) {
+ postError("Could not find model for: " + modelUuid.toString());
+ return;
+ }
+ modelInfo.captureAudio = captureAudio;
+ Log.i(TAG, "Set " + modelInfo.name + " capture audio to " + captureAudio);
+ }
+
+ public synchronized boolean hasMicrophonePermission() {
+ return getBaseContext().checkSelfPermission(Manifest.permission.RECORD_AUDIO)
+ == PackageManager.PERMISSION_GRANTED;
+ }
+
+ public synchronized boolean modelHasTriggerAudio(UUID modelUuid) {
+ ModelInfo modelInfo = mModelInfoMap.get(modelUuid);
+ return modelInfo != null && modelInfo.triggerAudioPlayer != null;
+ }
+
+ public synchronized boolean modelWillCaptureTriggerAudio(UUID modelUuid) {
+ ModelInfo modelInfo = mModelInfoMap.get(modelUuid);
+ return modelInfo != null && modelInfo.captureAudio;
+ }
+
+ public synchronized boolean modelHasCapturedAudio(UUID modelUuid) {
+ ModelInfo modelInfo = mModelInfoMap.get(modelUuid);
+ return modelInfo != null && modelInfo.captureAudioTrack != null;
+ }
+
+ private void loadModelsInDataDir() {
+ // Load all the models in the data dir.
+ boolean loadedModel = false;
+ for (File file : getFilesDir().listFiles()) {
+ // Find meta-data in .properties files, ignore everything else.
+ if (!file.getName().endsWith(".properties")) {
+ continue;
+ }
+ try {
+ Properties properties = new Properties();
+ properties.load(new FileInputStream(file));
+ createModelInfo(properties);
+ loadedModel = true;
+ } catch (Exception e) {
+ Log.e(TAG, "Failed to load properties file " + file.getName());
+ }
+ }
+
+ // Create a few dummy models if we didn't load anything.
+ if (!loadedModel) {
+ Properties dummyModelProperties = new Properties();
+ for (String name : new String[]{"1", "2", "3"}) {
+ dummyModelProperties.setProperty("name", "Model " + name);
+ createModelInfo(dummyModelProperties);
+ }
+ }
+ }
+
+ /** Parses a Properties collection to generate a sound model.
+ *
+ * Missing keys are filled in with default/random values.
+ * @param properties Has the required 'name' property, but the remaining 'modelUuid',
+ * 'vendorUuid', 'triggerAudio', and 'dataFile' optional properties.
+ *
+ */
+ private synchronized void createModelInfo(Properties properties) {
+ try {
+ ModelInfo modelInfo = new ModelInfo();
+
+ if (!properties.containsKey("name")) {
+ throw new RuntimeException("must have a 'name' property");
+ }
+ modelInfo.name = properties.getProperty("name");
+
+ if (properties.containsKey("modelUuid")) {
+ modelInfo.modelUuid = UUID.fromString(properties.getProperty("modelUuid"));
+ } else {
+ modelInfo.modelUuid = UUID.randomUUID();
+ }
+
+ if (properties.containsKey("vendorUuid")) {
+ modelInfo.vendorUuid = UUID.fromString(properties.getProperty("vendorUuid"));
+ } else {
+ modelInfo.vendorUuid = UUID.randomUUID();
+ }
+
+ if (properties.containsKey("triggerAudio")) {
+ modelInfo.triggerAudioPlayer = MediaPlayer.create(this, Uri.parse(
+ getFilesDir().getPath() + "/" + properties.getProperty("triggerAudio")));
+ if (modelInfo.triggerAudioPlayer.getDuration() == 0) {
+ modelInfo.triggerAudioPlayer.release();
+ modelInfo.triggerAudioPlayer = null;
+ }
+ }
+
+ if (properties.containsKey("dataFile")) {
+ File modelDataFile = new File(
+ getFilesDir().getPath() + "/" + properties.getProperty("dataFile"));
+ modelInfo.modelData = new byte[(int) modelDataFile.length()];
+ FileInputStream input = new FileInputStream(modelDataFile);
+ input.read(modelInfo.modelData, 0, modelInfo.modelData.length);
+ } else {
+ modelInfo.modelData = new byte[1024];
+ mRandom.nextBytes(modelInfo.modelData);
+ }
+
+ modelInfo.captureAudioMs = Integer.parseInt((String) properties.getOrDefault(
+ "captureAudioDurationMs", "5000"));
+
+ // TODO: Add property support for keyphrase models when they're exposed by the
+ // service.
+
+ // Update our maps containing the button -> id and id -> modelInfo.
+ mModelInfoMap.put(modelInfo.modelUuid, modelInfo);
+ if (mUserActivity != null) {
+ mUserActivity.addModel(modelInfo.modelUuid, modelInfo.name);
+ mUserActivity.setModelState(modelInfo.modelUuid, modelInfo.state);
+ }
+ } catch (IOException e) {
+ Log.e(TAG, "Error parsing properties for " + properties.getProperty("name"), e);
+ }
+ }
+
+ private class CaptureAudioRecorder implements Runnable {
+ private final ModelInfo mModelInfo;
+ private final SoundTriggerDetector.EventPayload mEvent;
+
+ public CaptureAudioRecorder(ModelInfo modelInfo, SoundTriggerDetector.EventPayload event) {
+ mModelInfo = modelInfo;
+ mEvent = event;
+ }
+
+ @Override
+ public void run() {
+ AudioFormat format = mEvent.getCaptureAudioFormat();
+ if (format == null) {
+ postErrorToast("No audio format in recognition event.");
+ return;
+ }
+
+ AudioRecord audioRecord = null;
+ AudioTrack playbackTrack = null;
+ try {
+ // Inform the audio flinger that we really do want the stream from the soundtrigger.
+ AudioAttributes.Builder attributesBuilder = new AudioAttributes.Builder();
+ attributesBuilder.setInternalCapturePreset(1999);
+ AudioAttributes attributes = attributesBuilder.build();
+
+ // Make sure we understand this kind of playback so we know how many bytes to read.
+ String encoding;
+ int bytesPerSample;
+ switch (format.getEncoding()) {
+ case AudioFormat.ENCODING_PCM_8BIT:
+ encoding = "8bit";
+ bytesPerSample = 1;
+ break;
+ case AudioFormat.ENCODING_PCM_16BIT:
+ encoding = "16bit";
+ bytesPerSample = 2;
+ break;
+ case AudioFormat.ENCODING_PCM_FLOAT:
+ encoding = "float";
+ bytesPerSample = 4;
+ break;
+ default:
+ throw new RuntimeException("Unhandled audio format in event");
+ }
+
+ int bytesRequired = format.getSampleRate() * format.getChannelCount() *
+ bytesPerSample * mModelInfo.captureAudioMs / 1000;
+ int minBufferSize = AudioRecord.getMinBufferSize(
+ format.getSampleRate(), format.getChannelMask(), format.getEncoding());
+ if (minBufferSize > bytesRequired) {
+ bytesRequired = minBufferSize;
+ }
+
+ // Make an AudioTrack so we can play the data back out after it's finished
+ // recording.
+ try {
+ int channelConfig = AudioFormat.CHANNEL_OUT_MONO;
+ if (format.getChannelCount() == 2) {
+ channelConfig = AudioFormat.CHANNEL_OUT_STEREO;
+ } else if (format.getChannelCount() >= 3) {
+ throw new RuntimeException(
+ "Too many channels in captured audio for playback");
+ }
+
+ playbackTrack = new AudioTrack(AudioManager.STREAM_MUSIC,
+ format.getSampleRate(), channelConfig, format.getEncoding(),
+ bytesRequired, AudioTrack.MODE_STATIC);
+ } catch (Exception e) {
+ Log.e(TAG, "Exception creating playback track", e);
+ postErrorToast("Failed to create playback track: " + e.getMessage());
+ }
+
+ audioRecord = new AudioRecord(attributes, format, bytesRequired,
+ mEvent.getCaptureSession());
+
+ byte[] buffer = new byte[bytesRequired];
+
+ // Create a file so we can save the output data there for analysis later.
+ FileOutputStream fos = null;
+ try {
+ fos = new FileOutputStream( new File(
+ getFilesDir() + File.separator + mModelInfo.name.replace(' ', '_') +
+ "_capture_" + format.getChannelCount() + "ch_" +
+ format.getSampleRate() + "hz_" + encoding + ".pcm"));
+ } catch (IOException e) {
+ Log.e(TAG, "Failed to open output for saving PCM data", e);
+ postErrorToast("Failed to open output for saving PCM data: " + e.getMessage());
+ }
+
+ // Inform the user we're recording.
+ setModelState(mModelInfo, "Recording");
+ audioRecord.startRecording();
+ while (bytesRequired > 0) {
+ int bytesRead = audioRecord.read(buffer, 0, buffer.length);
+ if (bytesRead == -1) {
+ break;
+ }
+ if (fos != null) {
+ fos.write(buffer, 0, bytesRead);
+ }
+ if (playbackTrack != null) {
+ playbackTrack.write(buffer, 0, bytesRead);
+ }
+ bytesRequired -= bytesRead;
+ }
+ audioRecord.stop();
+ } catch (Exception e) {
+ Log.e(TAG, "Error recording trigger audio", e);
+ postErrorToast("Error recording trigger audio: " + e.getMessage());
+ } finally {
+ if (audioRecord != null) {
+ audioRecord.release();
+ }
+ synchronized (SoundTriggerTestService.this) {
+ if (mModelInfo.captureAudioTrack != null) {
+ mModelInfo.captureAudioTrack.release();
+ }
+ mModelInfo.captureAudioTrack = playbackTrack;
+ }
+ setModelState(mModelInfo, "Recording finished");
+ }
+ }
+ }
+
+ // Implementation of SoundTriggerDetector.Callback.
+ private class DetectorCallback extends SoundTriggerDetector.Callback {
+ private final ModelInfo mModelInfo;
+
+ public DetectorCallback(ModelInfo modelInfo) {
+ mModelInfo = modelInfo;
+ }
+
+ public void onAvailabilityChanged(int status) {
+ postMessage(mModelInfo.name + "Availability changed to: " + status);
+ }
+
+ public void onDetected(SoundTriggerDetector.EventPayload event) {
+ postMessage(mModelInfo.name + "onDetected(): " + eventPayloadToString(event));
+ synchronized (SoundTriggerTestService.this) {
+ if (mUserActivity != null) {
+ mUserActivity.handleDetection(mModelInfo.modelUuid);
+ }
+ if (mModelInfo.captureAudio) {
+ new Thread(new CaptureAudioRecorder(mModelInfo, event)).start();
+ }
+ }
+ }
+
+ public void onError() {
+ postMessage(mModelInfo.name + "onError()");
+ setModelState(mModelInfo, "Error");
+ }
+
+ public void onRecognitionPaused() {
+ postMessage(mModelInfo.name + " onRecognitionPaused()");
+ setModelState(mModelInfo, "Paused");
+ }
+
+ public void onRecognitionResumed() {
+ postMessage(mModelInfo.name + "onRecognitionResumed()");
+ setModelState(mModelInfo, "Resumed");
+ }
+ }
+
+ private String eventPayloadToString(SoundTriggerDetector.EventPayload event) {
+ String result = "EventPayload(";
+ AudioFormat format = event.getCaptureAudioFormat();
+ result = result + "AudioFormat: " + ((format == null) ? "null" : format.toString());
+ byte[] triggerAudio = event.getTriggerAudio();
+ result = result + "TriggerAudio: " + (triggerAudio == null ? "null" : triggerAudio.length);
+ result = result + "CaptureSession: " + event.getCaptureSession();
+ result += " )";
+ return result;
+ }
+
+ private void postMessage(String msg) {
+ showMessage(msg, Log.INFO, false);
+ }
+
+ private void postError(String msg) {
+ showMessage(msg, Log.ERROR, false);
+ }
+
+ private void postToast(String msg) {
+ showMessage(msg, Log.INFO, true);
+ }
+
+ private void postErrorToast(String msg) {
+ showMessage(msg, Log.ERROR, true);
+ }
+
+ /** Logs the message at the specified level, then forwards it to the activity if present. */
+ private synchronized void showMessage(String msg, int logLevel, boolean showToast) {
+ Log.println(logLevel, TAG, msg);
+ if (mUserActivity != null) {
+ mUserActivity.showMessage(msg, showToast);
+ }
+ }
+
+ private synchronized void setModelState(ModelInfo modelInfo, String state) {
+ modelInfo.state = state;
+ if (mUserActivity != null) {
+ mUserActivity.setModelState(modelInfo.modelUuid, modelInfo.state);
+ }
+ }
+}
diff --git a/tests/SoundTriggerTestApp/src/com/android/test/soundtrigger/SoundTriggerUtil.java b/tests/SoundTriggerTestApp/src/com/android/test/soundtrigger/SoundTriggerUtil.java
index 1c95c25..8e5ed32 100644
--- a/tests/SoundTriggerTestApp/src/com/android/test/soundtrigger/SoundTriggerUtil.java
+++ b/tests/SoundTriggerTestApp/src/com/android/test/soundtrigger/SoundTriggerUtil.java
@@ -18,7 +18,6 @@
import android.annotation.Nullable;
import android.content.Context;
-import android.hardware.soundtrigger.SoundTrigger;
import android.hardware.soundtrigger.SoundTrigger.GenericSoundModel;
import android.media.soundtrigger.SoundTriggerDetector;
import android.media.soundtrigger.SoundTriggerManager;
@@ -36,7 +35,7 @@
* Utility class for the managing sound trigger sound models.
*/
public class SoundTriggerUtil {
- private static final String TAG = "TestSoundTriggerUtil:Hotsound";
+ private static final String TAG = "SoundTriggerTestUtil";
private final ISoundTriggerService mSoundTriggerService;
private final SoundTriggerManager mSoundTriggerManager;
@@ -68,10 +67,6 @@
return true;
}
- public void addOrUpdateSoundModel(SoundTriggerManager.Model soundModel) {
- mSoundTriggerManager.updateModel(soundModel);
- }
-
/**
* Gets the sound model for the given keyphrase, null if none exists.
* If a sound model for a given keyphrase exists, and it needs to be updated,
@@ -91,7 +86,7 @@
}
if (model == null) {
- Log.w(TAG, "No models present for the gien keyphrase ID");
+ Log.w(TAG, "No models present for the given keyphrase ID");
return null;
} else {
return model;
@@ -109,18 +104,14 @@
try {
mSoundTriggerService.deleteSoundModel(new ParcelUuid(modelId));
} catch (RemoteException e) {
- Log.e(TAG, "RemoteException in updateSoundModel");
+ Log.e(TAG, "RemoteException in deleteSoundModel");
+ return false;
}
return true;
}
- public void deleteSoundModelUsingManager(UUID modelId) {
- mSoundTriggerManager.deleteModel(modelId);
- }
-
public SoundTriggerDetector createSoundTriggerDetector(UUID modelId,
SoundTriggerDetector.Callback callback) {
return mSoundTriggerManager.createSoundTriggerDetector(modelId, callback, null);
}
-
}
diff --git a/tests/SoundTriggerTestApp/src/com/android/test/soundtrigger/TestSoundTriggerActivity.java b/tests/SoundTriggerTestApp/src/com/android/test/soundtrigger/TestSoundTriggerActivity.java
deleted file mode 100644
index 5fd38e9..0000000
--- a/tests/SoundTriggerTestApp/src/com/android/test/soundtrigger/TestSoundTriggerActivity.java
+++ /dev/null
@@ -1,400 +0,0 @@
-/*
- * Copyright (C) 2014 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.test.soundtrigger;
-
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.IOException;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Properties;
-import java.util.Random;
-import java.util.UUID;
-
-import android.app.Activity;
-import android.hardware.soundtrigger.SoundTrigger.GenericSoundModel;
-import android.hardware.soundtrigger.SoundTrigger;
-import android.media.AudioFormat;
-import android.media.AudioManager;
-import android.media.MediaPlayer;
-import android.media.soundtrigger.SoundTriggerDetector;
-import android.media.soundtrigger.SoundTriggerManager;
-import android.net.Uri;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.PowerManager;
-import android.os.UserManager;
-import android.text.Editable;
-import android.text.method.ScrollingMovementMethod;
-import android.util.Log;
-import android.view.View;
-import android.widget.Button;
-import android.widget.RadioButton;
-import android.widget.RadioButton;
-import android.widget.RadioGroup;
-import android.widget.ScrollView;
-import android.widget.TextView;
-import android.widget.Toast;
-
-public class TestSoundTriggerActivity extends Activity {
- private static final String TAG = "TestSoundTriggerActivity";
- private static final boolean DBG = false;
-
- private SoundTriggerUtil mSoundTriggerUtil;
- private Random mRandom;
-
- private Map<Integer, ModelInfo> mModelInfoMap;
- private Map<View, Integer> mModelIdMap;
-
- private TextView mDebugView = null;
- private int mSelectedModelId = -1;
- private ScrollView mScrollView = null;
- private Button mPlayTriggerButton = null;
- private PowerManager.WakeLock mScreenWakelock;
- private Handler mHandler;
- private RadioGroup mRadioGroup;
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- if (DBG) Log.d(TAG, "onCreate");
- super.onCreate(savedInstanceState);
- setContentView(R.layout.main);
- mDebugView = (TextView) findViewById(R.id.console);
- mScrollView = (ScrollView) findViewById(R.id.scroller_id);
- mRadioGroup = (RadioGroup) findViewById(R.id.model_group_id);
- mPlayTriggerButton = (Button) findViewById(R.id.play_trigger_id);
- mDebugView.setText(mDebugView.getText(), TextView.BufferType.EDITABLE);
- mDebugView.setMovementMethod(new ScrollingMovementMethod());
- mSoundTriggerUtil = new SoundTriggerUtil(this);
- mRandom = new Random();
- mHandler = new Handler();
-
- mModelInfoMap = new HashMap();
- mModelIdMap = new HashMap();
-
- setVolumeControlStream(AudioManager.STREAM_MUSIC);
-
- // Load all the models in the data dir.
- for (File file : getFilesDir().listFiles()) {
- // Find meta-data in .properties files, ignore everything else.
- if (!file.getName().endsWith(".properties")) {
- continue;
- }
- try {
- Properties properties = new Properties();
- properties.load(new FileInputStream(file));
- createModelInfoAndWidget(properties);
- } catch (Exception e) {
- Log.e(TAG, "Failed to load properties file " + file.getName());
- }
- }
-
- // Create a few dummy models if we didn't load anything.
- if (mModelIdMap.isEmpty()) {
- Properties dummyModelProperties = new Properties();
- for (String name : new String[]{"One", "Two", "Three"}) {
- dummyModelProperties.setProperty("name", "Model " + name);
- createModelInfoAndWidget(dummyModelProperties);
- }
- }
- }
-
- private void createModelInfoAndWidget(Properties properties) {
- try {
- ModelInfo modelInfo = new ModelInfo();
-
- if (!properties.containsKey("name")) {
- throw new RuntimeException("must have a 'name' property");
- }
- modelInfo.name = properties.getProperty("name");
-
- if (properties.containsKey("modelUuid")) {
- modelInfo.modelUuid = UUID.fromString(properties.getProperty("modelUuid"));
- } else {
- modelInfo.modelUuid = UUID.randomUUID();
- }
-
- if (properties.containsKey("vendorUuid")) {
- modelInfo.vendorUuid = UUID.fromString(properties.getProperty("vendorUuid"));
- } else {
- modelInfo.vendorUuid = UUID.randomUUID();
- }
-
- if (properties.containsKey("triggerAudio")) {
- modelInfo.triggerAudioPlayer = MediaPlayer.create(this, Uri.parse(
- getFilesDir().getPath() + "/" + properties.getProperty("triggerAudio")));
- }
-
- if (properties.containsKey("dataFile")) {
- File modelDataFile = new File(
- getFilesDir().getPath() + "/" + properties.getProperty("dataFile"));
- modelInfo.modelData = new byte[(int) modelDataFile.length()];
- FileInputStream input = new FileInputStream(modelDataFile);
- input.read(modelInfo.modelData, 0, modelInfo.modelData.length);
- } else {
- modelInfo.modelData = new byte[1024];
- mRandom.nextBytes(modelInfo.modelData);
- }
-
- // TODO: Add property support for keyphrase models when they're exposed by the
- // service. Also things like how much audio they should record with the capture session
- // provided in the callback.
-
- // Add a widget into the radio group.
- RadioButton button = new RadioButton(this);
- mRadioGroup.addView(button);
- button.setText(modelInfo.name);
- button.setOnClickListener(new View.OnClickListener() {
- public void onClick(View v) {
- onRadioButtonClicked(v);
- }
- });
-
- // Update our maps containing the button -> id and id -> modelInfo.
- int newModelId = mModelIdMap.size() + 1;
- mModelIdMap.put(button, newModelId);
- mModelInfoMap.put(newModelId, modelInfo);
-
- // If we don't have something selected, select this first thing.
- if (mSelectedModelId < 0) {
- button.setChecked(true);
- onRadioButtonClicked(button);
- }
- } catch (IOException e) {
- Log.e(TAG, "Error parsing properties for " + properties.getProperty("name"), e);
- }
- }
-
- private void postMessage(String msg) {
- Log.i(TAG, "Posted: " + msg);
- ((Editable) mDebugView.getText()).append(msg + "\n");
- if ((mDebugView.getMeasuredHeight() - mScrollView.getScrollY()) <=
- (mScrollView.getHeight() + mDebugView.getLineHeight())) {
- scrollToBottom();
- }
- }
-
- private void scrollToBottom() {
- mScrollView.post(new Runnable() {
- public void run() {
- mScrollView.smoothScrollTo(0, mDebugView.getBottom());
- }
- });
- }
-
- private synchronized UUID getSelectedUuid() {
- return mModelInfoMap.get(mSelectedModelId).modelUuid;
- }
-
- private synchronized void setDetector(SoundTriggerDetector detector) {
- mModelInfoMap.get(mSelectedModelId).detector = detector;
- }
-
- private synchronized SoundTriggerDetector getDetector() {
- return mModelInfoMap.get(mSelectedModelId).detector;
- }
-
- private void screenWakeup() {
- PowerManager pm = ((PowerManager)getSystemService(POWER_SERVICE));
- if (mScreenWakelock == null) {
- mScreenWakelock = pm.newWakeLock(PowerManager.SCREEN_DIM_WAKE_LOCK, "TAG");
- }
- mScreenWakelock.acquire();
- }
-
- private void screenRelease() {
- PowerManager pm = ((PowerManager)getSystemService(POWER_SERVICE));
- mScreenWakelock.release();
- }
-
- /** TODO: Should return the abstract sound model that can be then sent to the service. */
- private GenericSoundModel createNewSoundModel() {
- ModelInfo modelInfo = mModelInfoMap.get(mSelectedModelId);
- return new GenericSoundModel(modelInfo.modelUuid, modelInfo.vendorUuid,
- modelInfo.modelData);
- }
-
- /**
- * Called when the user clicks the enroll button.
- * Performs a fresh enrollment.
- */
- public void onEnrollButtonClicked(View v) {
- postMessage("Loading model: " + mSelectedModelId);
-
- GenericSoundModel model = createNewSoundModel();
-
- boolean status = mSoundTriggerUtil.addOrUpdateSoundModel(model);
- if (status) {
- Toast.makeText(
- this, "Successfully created sound trigger model UUID=" + model.uuid,
- Toast.LENGTH_SHORT).show();
- } else {
- Toast.makeText(this, "Failed to enroll!!!" + model.uuid, Toast.LENGTH_SHORT).show();
- }
-
- // Test the SoundManager API.
- }
-
- /**
- * Called when the user clicks the un-enroll button.
- * Clears the enrollment information for the user.
- */
- public void onUnEnrollButtonClicked(View v) {
- postMessage("Unloading model: " + mSelectedModelId);
- UUID modelUuid = getSelectedUuid();
- GenericSoundModel soundModel = mSoundTriggerUtil.getSoundModel(modelUuid);
- if (soundModel == null) {
- Toast.makeText(this, "Sound model not found!!!", Toast.LENGTH_SHORT).show();
- return;
- }
- boolean status = mSoundTriggerUtil.deleteSoundModel(modelUuid);
- if (status) {
- Toast.makeText(this, "Successfully deleted model UUID=" + soundModel.uuid,
- Toast.LENGTH_SHORT)
- .show();
- } else {
- Toast.makeText(this, "Failed to delete sound model!!!", Toast.LENGTH_SHORT).show();
- }
- }
-
- /**
- * Called when the user clicks the re-enroll button.
- * Uses the previously enrolled sound model and makes changes to it before pushing it back.
- */
- public void onReEnrollButtonClicked(View v) {
- postMessage("Re-loading model: " + mSelectedModelId);
- UUID modelUuid = getSelectedUuid();
- GenericSoundModel soundModel = mSoundTriggerUtil.getSoundModel(modelUuid);
- if (soundModel == null) {
- Toast.makeText(this, "Sound model not found!!!", Toast.LENGTH_SHORT).show();
- return;
- }
- GenericSoundModel updated = createNewSoundModel();
- boolean status = mSoundTriggerUtil.addOrUpdateSoundModel(updated);
- if (status) {
- Toast.makeText(this, "Successfully re-enrolled, model UUID=" + updated.uuid,
- Toast.LENGTH_SHORT)
- .show();
- } else {
- Toast.makeText(this, "Failed to re-enroll!!!", Toast.LENGTH_SHORT).show();
- }
- }
-
- public void onStartRecognitionButtonClicked(View v) {
- UUID modelUuid = getSelectedUuid();
- SoundTriggerDetector detector = getDetector();
- if (detector == null) {
- Log.i(TAG, "Created an instance of the SoundTriggerDetector for model #" +
- mSelectedModelId);
- postMessage("Created an instance of the SoundTriggerDetector for model #" +
- mSelectedModelId);
- detector = mSoundTriggerUtil.createSoundTriggerDetector(modelUuid,
- new DetectorCallback());
- setDetector(detector);
- }
- postMessage("Triggering start recognition for model: " + mSelectedModelId);
- if (!detector.startRecognition(
- SoundTriggerDetector.RECOGNITION_FLAG_ALLOW_MULTIPLE_TRIGGERS)) {
- Log.e(TAG, "Fast failure attempting to start recognition.");
- postMessage("Fast failure attempting to start recognition:" + mSelectedModelId);
- }
- }
-
- public void onStopRecognitionButtonClicked(View v) {
- SoundTriggerDetector detector = getDetector();
- if (detector == null) {
- Log.e(TAG, "Stop called on null detector.");
- postMessage("Error: Stop called on null detector.");
- return;
- }
- postMessage("Triggering stop recognition for model: " + mSelectedModelId);
- if (!detector.stopRecognition()) {
- Log.e(TAG, "Fast failure attempting to stop recognition.");
- postMessage("Fast failure attempting to stop recognition: " + mSelectedModelId);
- }
- }
-
- public synchronized void onRadioButtonClicked(View view) {
- // Is the button now checked?
- boolean checked = ((RadioButton) view).isChecked();
- if (checked) {
- mSelectedModelId = mModelIdMap.get(view);
- ModelInfo modelInfo = mModelInfoMap.get(mSelectedModelId);
- postMessage("Selected " + modelInfo.name);
-
- // Set the play trigger button to be enabled only if we actually have some audio.
- mPlayTriggerButton.setEnabled(modelInfo.triggerAudioPlayer != null);
- }
- }
-
- public synchronized void onPlayTriggerButtonClicked(View v) {
- ModelInfo modelInfo = mModelInfoMap.get(mSelectedModelId);
- modelInfo.triggerAudioPlayer.start();
- postMessage("Playing trigger audio for " + modelInfo.name);
- }
-
- // Helper struct for holding information about a model.
- private static class ModelInfo {
- public String name;
- public UUID modelUuid;
- public UUID vendorUuid;
- public MediaPlayer triggerAudioPlayer;
- public SoundTriggerDetector detector;
- public byte modelData[];
- };
-
- // Implementation of SoundTriggerDetector.Callback.
- public class DetectorCallback extends SoundTriggerDetector.Callback {
- public void onAvailabilityChanged(int status) {
- postMessage("Availability changed to: " + status);
- }
-
- public void onDetected(SoundTriggerDetector.EventPayload event) {
- postMessage("onDetected(): " + eventPayloadToString(event));
- screenWakeup();
- mHandler.postDelayed(new Runnable() {
- @Override
- public void run() {
- screenRelease();
- }
- }, 1000L);
- }
-
- public void onError() {
- postMessage("onError()");
- }
-
- public void onRecognitionPaused() {
- postMessage("onRecognitionPaused()");
- }
-
- public void onRecognitionResumed() {
- postMessage("onRecognitionResumed()");
- }
- }
-
- private String eventPayloadToString(SoundTriggerDetector.EventPayload event) {
- String result = "EventPayload(";
- AudioFormat format = event.getCaptureAudioFormat();
- result = result + "AudioFormat: " + ((format == null) ? "null" : format.toString());
- byte[] triggerAudio = event.getTriggerAudio();
- result = result + "TriggerAudio: " + (triggerAudio == null ? "null" : triggerAudio.length);
- result = result + "CaptureSession: " + event.getCaptureSession();
- result += " )";
- return result;
- }
-}
diff --git a/tests/UiBench/src/com/android/test/uibench/MainActivity.java b/tests/UiBench/src/com/android/test/uibench/MainActivity.java
index 2111274..79837b6 100644
--- a/tests/UiBench/src/com/android/test/uibench/MainActivity.java
+++ b/tests/UiBench/src/com/android/test/uibench/MainActivity.java
@@ -38,6 +38,23 @@
private static final String EXTRA_PATH = "activity_path";
private static final String CATEGORY_HWUI_TEST = "com.android.test.uibench.TEST";
+ public static class TestListFragment extends ListFragment {
+ @Override
+ @SuppressWarnings("unchecked")
+ public void onListItemClick(ListView l, View v, int position, long id) {
+ Map<String, Object> map = (Map<String, Object>)l.getItemAtPosition(position);
+
+ Intent intent = (Intent) map.get("intent");
+ startActivity(intent);
+ }
+
+ @Override
+ public void onViewCreated(View view, Bundle savedInstanceState) {
+ super.onViewCreated(view, savedInstanceState);
+ getListView().setTextFilterEnabled(true);
+ }
+ }
+
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -54,22 +71,7 @@
FragmentManager fm = getSupportFragmentManager();
if (fm.findFragmentById(android.R.id.content) == null) {
- ListFragment listFragment = new ListFragment() {
- @Override
- @SuppressWarnings("unchecked")
- public void onListItemClick(ListView l, View v, int position, long id) {
- Map<String, Object> map = (Map<String, Object>)l.getItemAtPosition(position);
-
- Intent intent = (Intent) map.get("intent");
- startActivity(intent);
- }
-
- @Override
- public void onViewCreated(View view, Bundle savedInstanceState) {
- super.onViewCreated(view, savedInstanceState);
- getListView().setTextFilterEnabled(true);
- }
- };
+ ListFragment listFragment = new TestListFragment();
listFragment.setListAdapter(new SimpleAdapter(this, getData(path),
android.R.layout.simple_list_item_1, new String[] { "title" },
new int[] { android.R.id.text1 }));
diff --git a/tools/aapt/Android.mk b/tools/aapt/Android.mk
index b701445..2a490d1 100644
--- a/tools/aapt/Android.mk
+++ b/tools/aapt/Android.mk
@@ -57,8 +57,8 @@
aaptHostStaticLibs := \
libandroidfw \
libpng \
- liblog \
libutils \
+ liblog \
libcutils \
libexpat \
libziparchive-host \
diff --git a/tools/aapt2/util/Util.cpp b/tools/aapt2/util/Util.cpp
index 7b0c71d..5a87c33 100644
--- a/tools/aapt2/util/Util.cpp
+++ b/tools/aapt2/util/Util.cpp
@@ -441,8 +441,10 @@
}
std::string utf8;
+ // Make room for '\0' explicitly.
+ utf8.resize(utf8Length + 1);
+ utf16_to_utf8(utf16.data(), utf16.length(), &*utf8.begin(), utf8Length + 1);
utf8.resize(utf8Length);
- utf16_to_utf8(utf16.data(), utf16.length(), &*utf8.begin());
return utf8;
}
diff --git a/tools/split-select/Android.mk b/tools/split-select/Android.mk
index 239bed5..863abae 100644
--- a/tools/split-select/Android.mk
+++ b/tools/split-select/Android.mk
@@ -47,8 +47,8 @@
libaapt \
libandroidfw \
libpng \
- liblog \
libutils \
+ liblog \
libcutils \
libexpat \
libziparchive-host \