Merge "Move palette constants to XML" into oc-dr1-dev
diff --git a/Android.mk b/Android.mk
index 55ea69a..cc34767 100644
--- a/Android.mk
+++ b/Android.mk
@@ -238,6 +238,7 @@
core/java/android/net/INetworkScoreService.aidl \
core/java/android/net/INetworkStatsService.aidl \
core/java/android/net/INetworkStatsSession.aidl \
+ core/java/android/net/ITetheringStatsProvider.aidl \
core/java/android/net/nsd/INsdManager.aidl \
core/java/android/nfc/IAppCallback.aidl \
core/java/android/nfc/INfcAdapter.aidl \
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 204df63..abf48a8 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -6480,9 +6480,9 @@
private <T> T instantiate(ClassLoader cl, String className, Context c,
Instantiator<T> instantiator)
throws ClassNotFoundException, IllegalAccessException, InstantiationException {
- if (c.getApplicationContext() instanceof Application) {
- T a = instantiator.instantiate((Application) c.getApplicationContext(),
- cl, className);
+ Application app = getApp(c);
+ if (app != null) {
+ T a = instantiator.instantiate(app, cl, className);
if (a != null) return a;
}
return (T) cl.loadClass(className).newInstance();
@@ -6491,14 +6491,25 @@
private <T> T instantiate(ClassLoader cl, String className, Intent intent, Context c,
IntentInstantiator<T> instantiator)
throws ClassNotFoundException, IllegalAccessException, InstantiationException {
- if (c.getApplicationContext() instanceof Application) {
- T a = instantiator.instantiate((Application) c.getApplicationContext(),
- cl, className, intent);
+ Application app = getApp(c);
+ if (app != null) {
+ T a = instantiator.instantiate(app, cl, className, intent);
if (a != null) return a;
}
return (T) cl.loadClass(className).newInstance();
}
+ private Application getApp(Context c) {
+ // We need this shortcut to avoid actually calling getApplicationContext() on an Application
+ // because the Application may not return itself for getApplicationContext() because the
+ // API doesn't enforce it.
+ if (c instanceof Application) return (Application) c;
+ if (c.getApplicationContext() instanceof Application) {
+ return (Application) c.getApplicationContext();
+ }
+ return null;
+ }
+
private interface Instantiator<T> {
T instantiate(Application app, ClassLoader cl, String className)
throws ClassNotFoundException, IllegalAccessException, InstantiationException;
diff --git a/core/java/android/app/NotificationChannel.java b/core/java/android/app/NotificationChannel.java
index 143d147..d6e3691 100644
--- a/core/java/android/app/NotificationChannel.java
+++ b/core/java/android/app/NotificationChannel.java
@@ -15,11 +15,6 @@
*/
package android.app;
-import org.json.JSONException;
-import org.json.JSONObject;
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlSerializer;
-
import android.annotation.SystemApi;
import android.app.NotificationManager.Importance;
import android.content.Intent;
@@ -31,6 +26,11 @@
import android.service.notification.NotificationListenerService;
import android.text.TextUtils;
+import org.json.JSONException;
+import org.json.JSONObject;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlSerializer;
+
import java.io.IOException;
import java.util.Arrays;
@@ -743,7 +743,7 @@
private static String longArrayToString(long[] values) {
StringBuffer sb = new StringBuffer();
- if (values != null) {
+ if (values != null && values.length > 0) {
for (int i = 0; i < values.length - 1; i++) {
sb.append(values[i]).append(DELIMITER);
}
diff --git a/core/java/android/hardware/camera2/CameraManager.java b/core/java/android/hardware/camera2/CameraManager.java
index 1b150bf..90bf896 100644
--- a/core/java/android/hardware/camera2/CameraManager.java
+++ b/core/java/android/hardware/camera2/CameraManager.java
@@ -16,28 +16,29 @@
package android.hardware.camera2;
-import android.annotation.RequiresPermission;
-import android.annotation.SystemService;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemService;
import android.content.Context;
-import android.hardware.ICameraService;
-import android.hardware.ICameraServiceListener;
import android.hardware.CameraInfo;
import android.hardware.CameraStatus;
+import android.hardware.ICameraService;
+import android.hardware.ICameraServiceListener;
import android.hardware.camera2.impl.CameraMetadataNative;
import android.hardware.camera2.legacy.CameraDeviceUserShim;
import android.hardware.camera2.legacy.LegacyMetadataMapper;
-import android.os.IBinder;
import android.os.Binder;
import android.os.DeadObjectException;
import android.os.Handler;
+import android.os.IBinder;
import android.os.Looper;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.ServiceSpecificException;
-import android.util.Log;
+import android.os.SystemProperties;
import android.util.ArrayMap;
+import android.util.Log;
import java.util.ArrayList;
@@ -210,7 +211,9 @@
public CameraCharacteristics getCameraCharacteristics(@NonNull String cameraId)
throws CameraAccessException {
CameraCharacteristics characteristics = null;
-
+ if (CameraManagerGlobal.sCameraServiceDisabled) {
+ throw new IllegalArgumentException("No cameras available on device");
+ }
synchronized (mLock) {
/*
* Get the camera characteristics from the camera service directly if it supports it,
@@ -462,6 +465,9 @@
"Handler argument is null, but no looper exists in the calling thread");
}
}
+ if (CameraManagerGlobal.sCameraServiceDisabled) {
+ throw new IllegalArgumentException("No cameras available on device");
+ }
openCameraDeviceUserAsync(cameraId, callback, handler, clientUid);
}
@@ -507,6 +513,9 @@
*/
public void setTorchMode(@NonNull String cameraId, boolean enabled)
throws CameraAccessException {
+ if (CameraManagerGlobal.sCameraServiceDisabled) {
+ throw new IllegalArgumentException("No cameras available on device");
+ }
CameraManagerGlobal.get().setTorchMode(cameraId, enabled);
}
@@ -745,6 +754,9 @@
private CameraManagerGlobal() {
}
+ public static final boolean sCameraServiceDisabled =
+ SystemProperties.getBoolean("config.disable_cameraservice", false);
+
public static CameraManagerGlobal get() {
return gCameraManager;
}
@@ -764,7 +776,7 @@
public ICameraService getCameraService() {
synchronized(mLock) {
connectCameraServiceLocked();
- if (mCameraService == null) {
+ if (mCameraService == null && !sCameraServiceDisabled) {
Log.e(TAG, "Camera service is unavailable");
}
return mCameraService;
@@ -779,7 +791,7 @@
*/
private void connectCameraServiceLocked() {
// Only reconnect if necessary
- if (mCameraService != null) return;
+ if (mCameraService != null || sCameraServiceDisabled) return;
Log.i(TAG, "Connecting to camera service");
diff --git a/core/java/android/net/ITetheringStatsProvider.aidl b/core/java/android/net/ITetheringStatsProvider.aidl
new file mode 100644
index 0000000..769086d
--- /dev/null
+++ b/core/java/android/net/ITetheringStatsProvider.aidl
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+import android.net.NetworkStats;
+
+/**
+ * Interface that allows NetworkManagementService to query for tethering statistics.
+ *
+ * TODO: this does not really need to be an interface since Tethering runs in the same process
+ * as NetworkManagementService. Consider refactoring Tethering to use direct access to
+ * NetworkManagementService instead of using INetworkManagementService, and then deleting this
+ * interface.
+ *
+ * @hide
+ */
+interface ITetheringStatsProvider {
+ NetworkStats getTetherStats();
+}
diff --git a/core/java/android/os/INetworkManagementService.aidl b/core/java/android/os/INetworkManagementService.aidl
index 92e78bc..3de2174 100644
--- a/core/java/android/os/INetworkManagementService.aidl
+++ b/core/java/android/os/INetworkManagementService.aidl
@@ -20,6 +20,7 @@
import android.net.InterfaceConfiguration;
import android.net.INetd;
import android.net.INetworkManagementEventObserver;
+import android.net.ITetheringStatsProvider;
import android.net.Network;
import android.net.NetworkStats;
import android.net.RouteInfo;
@@ -207,6 +208,18 @@
void disableNat(String internalInterface, String externalInterface);
/**
+ * Registers a {@code ITetheringStatsProvider} to provide tethering statistics.
+ * All registered providers will be called in order, and their results will be added together.
+ * Netd is always registered as a tethering stats provider.
+ */
+ void registerTetheringStatsProvider(ITetheringStatsProvider provider, String name);
+
+ /**
+ * Unregisters a previously-registered {@code ITetheringStatsProvider}.
+ */
+ void unregisterTetheringStatsProvider(ITetheringStatsProvider provider);
+
+ /**
** PPPD
**/
diff --git a/core/java/android/os/RecoverySystem.java b/core/java/android/os/RecoverySystem.java
index 1f8de04..7fa1c5a 100644
--- a/core/java/android/os/RecoverySystem.java
+++ b/core/java/android/os/RecoverySystem.java
@@ -751,7 +751,9 @@
// Block until the ordered broadcast has completed.
condition.block();
- wipeEuiccData(context, wipeEuicc);
+ // TODO(b/63693573): Uncomment this once the pSIM slot is restored as needed
+ // after the ensuing boot. Currently you end up stuck on the eSIM.
+ // wipeEuiccData(context, wipeEuicc);
String shutdownArg = null;
if (shutdown) {
diff --git a/core/java/android/widget/TextInputTimePickerView.java b/core/java/android/widget/TextInputTimePickerView.java
index 11b7514d..0cf8faa 100644
--- a/core/java/android/widget/TextInputTimePickerView.java
+++ b/core/java/android/widget/TextInputTimePickerView.java
@@ -17,6 +17,7 @@
package android.widget;
import android.content.Context;
+import android.os.LocaleList;
import android.text.Editable;
import android.text.InputFilter;
import android.text.TextWatcher;
@@ -141,6 +142,9 @@
new InputFilter.LengthFilter(maxCharLength)});
mMinuteEditText.setFilters(new InputFilter[] {
new InputFilter.LengthFilter(maxCharLength)});
+ final LocaleList locales = mContext.getResources().getConfiguration().getLocales();
+ mHourEditText.setImeHintLocales(locales);
+ mMinuteEditText.setImeHintLocales(locales);
}
boolean validateInput() {
diff --git a/core/java/com/android/internal/os/ZygoteInit.java b/core/java/com/android/internal/os/ZygoteInit.java
index 9e61a99..21cf95f 100644
--- a/core/java/com/android/internal/os/ZygoteInit.java
+++ b/core/java/com/android/internal/os/ZygoteInit.java
@@ -132,6 +132,9 @@
bootTimingsTraceLog.traceBegin("PreloadResources");
preloadResources();
bootTimingsTraceLog.traceEnd(); // PreloadResources
+ Trace.traceBegin(Trace.TRACE_TAG_DALVIK, "PreloadAppProcessHALs");
+ nativePreloadAppProcessHALs();
+ Trace.traceEnd(Trace.TRACE_TAG_DALVIK);
Trace.traceBegin(Trace.TRACE_TAG_DALVIK, "PreloadOpenGL");
preloadOpenGL();
Trace.traceEnd(Trace.TRACE_TAG_DALVIK);
@@ -184,6 +187,8 @@
System.loadLibrary("jnigraphics");
}
+ native private static void nativePreloadAppProcessHALs();
+
private static void preloadOpenGL() {
String driverPackageName = SystemProperties.get(PROPERTY_GFX_DRIVER);
if (!SystemProperties.getBoolean(PROPERTY_DISABLE_OPENGL_PRELOADING, false) &&
diff --git a/core/java/com/android/server/BootReceiver.java b/core/java/com/android/server/BootReceiver.java
index 2cf58d7..4f9e8a5 100644
--- a/core/java/com/android/server/BootReceiver.java
+++ b/core/java/com/android/server/BootReceiver.java
@@ -143,7 +143,6 @@
try {
return FileUtils.readTextFile(lastHeaderFile, 0, null);
} catch (IOException e) {
- Slog.e(TAG, "Error reading " + lastHeaderFile, e);
return null;
}
}
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
index 7e71ce9..a228d34 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -188,6 +188,7 @@
"com_android_internal_os_FuseAppLoop.cpp",
"com_android_internal_os_PathClassLoaderFactory.cpp",
"com_android_internal_os_Zygote.cpp",
+ "com_android_internal_os_ZygoteInit.cpp",
"com_android_internal_util_VirtualRefBasePtr.cpp",
"com_android_internal_view_animation_NativeInterpolatorFactoryHelper.cpp",
"hwbinder/EphemeralStorage.cpp",
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index 1adc6dd..daf815a 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -206,6 +206,7 @@
extern int register_com_android_internal_os_FuseAppLoop(JNIEnv* env);
extern int register_com_android_internal_os_PathClassLoaderFactory(JNIEnv* env);
extern int register_com_android_internal_os_Zygote(JNIEnv *env);
+extern int register_com_android_internal_os_ZygoteInit(JNIEnv *env);
extern int register_com_android_internal_util_VirtualRefBasePtr(JNIEnv *env);
static AndroidRuntime* gCurRuntime = NULL;
@@ -245,7 +246,7 @@
methods, NELEM(methods));
}
-int register_com_android_internal_os_ZygoteInit(JNIEnv* env)
+int register_com_android_internal_os_ZygoteInit_nativeZygoteInit(JNIEnv* env)
{
const JNINativeMethod methods[] = {
{ "nativeZygoteInit", "()V",
@@ -1286,7 +1287,7 @@
static const RegJNIRec gRegJNI[] = {
REG_JNI(register_com_android_internal_os_RuntimeInit),
- REG_JNI(register_com_android_internal_os_ZygoteInit),
+ REG_JNI(register_com_android_internal_os_ZygoteInit_nativeZygoteInit),
REG_JNI(register_android_os_SystemClock),
REG_JNI(register_android_util_EventLog),
REG_JNI(register_android_util_Log),
@@ -1388,6 +1389,7 @@
REG_JNI(register_android_os_MemoryFile),
REG_JNI(register_com_android_internal_os_PathClassLoaderFactory),
REG_JNI(register_com_android_internal_os_Zygote),
+ REG_JNI(register_com_android_internal_os_ZygoteInit),
REG_JNI(register_com_android_internal_util_VirtualRefBasePtr),
REG_JNI(register_android_hardware_Camera),
REG_JNI(register_android_hardware_camera2_CameraMetadata),
diff --git a/core/jni/com_android_internal_os_ZygoteInit.cpp b/core/jni/com_android_internal_os_ZygoteInit.cpp
new file mode 100644
index 0000000..258a55c
--- /dev/null
+++ b/core/jni/com_android_internal_os_ZygoteInit.cpp
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "Zygote"
+
+#include <ui/GraphicBufferMapper.h>
+
+#include "core_jni_helpers.h"
+
+namespace {
+
+void android_internal_os_ZygoteInit_nativePreloadAppProcessHALs(JNIEnv* env, jclass) {
+ android::GraphicBufferMapper::preloadHal();
+ // Add preloading here for other HALs that are (a) always passthrough, and
+ // (b) loaded by most app processes.
+}
+
+const JNINativeMethod gMethods[] = {
+ { "nativePreloadAppProcessHALs", "()V",
+ (void*)android_internal_os_ZygoteInit_nativePreloadAppProcessHALs },
+};
+
+} // anonymous namespace
+
+namespace android {
+
+int register_com_android_internal_os_ZygoteInit(JNIEnv* env) {
+ return RegisterMethodsOrDie(env, "com/android/internal/os/ZygoteInit",
+ gMethods, NELEM(gMethods));
+}
+
+} // namespace android
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 4059c30..ae115d3 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -3817,6 +3817,11 @@
<service android:name="com.android.server.PreloadsFileCacheExpirationJobService"
android:permission="android.permission.BIND_JOB_SERVICE" >
</service>
+
+ <service android:name="com.android.server.camera.CameraStatsJobService"
+ android:permission="android.permission.BIND_JOB_SERVICE" >
+ </service>
+
</application>
</manifest>
diff --git a/packages/SettingsLib/res/layout/preference_two_target.xml b/packages/SettingsLib/res/layout/preference_two_target.xml
index 7000940d..2309ec6 100644
--- a/packages/SettingsLib/res/layout/preference_two_target.xml
+++ b/packages/SettingsLib/res/layout/preference_two_target.xml
@@ -18,6 +18,7 @@
<!-- Based off preference_material_settings.xml except that ripple on only on the left side. -->
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:settings="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="?android:attr/listPreferredItemHeightSmall"
@@ -50,8 +51,8 @@
android:id="@android:id/icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:maxWidth="48dp"
- android:maxHeight="48dp" />
+ settings:maxWidth="48dp"
+ settings:maxHeight="48dp" />
</LinearLayout>
<RelativeLayout
diff --git a/packages/SettingsLib/res/values/attrs.xml b/packages/SettingsLib/res/values/attrs.xml
index a8a1793..6d852df 100644
--- a/packages/SettingsLib/res/values/attrs.xml
+++ b/packages/SettingsLib/res/values/attrs.xml
@@ -48,4 +48,9 @@
<attr name="footerPreferenceStyle" format="reference" />
+ <declare-styleable name="PreferenceImageView">
+ <attr name="maxWidth" format="dimension" />
+ <attr name="maxHeight" format="dimension" />
+ </declare-styleable>
+
</resources>
diff --git a/packages/SettingsLib/src/com/android/settingslib/drawer/ProfileSelectDialog.java b/packages/SettingsLib/src/com/android/settingslib/drawer/ProfileSelectDialog.java
index 512049f..c79b1466d 100644
--- a/packages/SettingsLib/src/com/android/settingslib/drawer/ProfileSelectDialog.java
+++ b/packages/SettingsLib/src/com/android/settingslib/drawer/ProfileSelectDialog.java
@@ -68,10 +68,8 @@
public void onClick(DialogInterface dialog, int which) {
UserHandle user = mSelectedTile.userHandle.get(which);
// Show menu on top level items.
- mSelectedTile.intent.putExtra(SettingsDrawerActivity.EXTRA_SHOW_MENU, true);
mSelectedTile.intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
getActivity().startActivityAsUser(mSelectedTile.intent, user);
- ((SettingsDrawerActivity) getActivity()).onProfileTileOpen();
}
public static void updateUserHandlesIfNeeded(Context context, Tile tile) {
diff --git a/packages/SystemUI/res-keyguard/drawable/ic_access_alarms_big.xml b/packages/SystemUI/res-keyguard/drawable/ic_access_alarms_big.xml
index fd385fc..fffa0bd 100644
--- a/packages/SystemUI/res-keyguard/drawable/ic_access_alarms_big.xml
+++ b/packages/SystemUI/res-keyguard/drawable/ic_access_alarms_big.xml
@@ -19,6 +19,6 @@
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
- android:fillColor="?attr/bgProtectSecondaryTextColor"
+ android:fillColor="@android:color/white"
android:pathData="M21.35,6.49c-0.35,0.42 -0.98,0.47 -1.4,0.12l-3.07,-2.57a1,1 0,1 1,1.29 -1.53l3.07,2.57c0.42,0.35 0.47,0.98 0.11,1.41zM7.24,2.63a1,1 0,0 0,-1.41 -0.13L2.77,5.07A0.996,0.996 0,1 0,4.05 6.6l3.06,-2.57c0.43,-0.35 0.48,-0.98 0.13,-1.4zM11.75,8c-0.41,0 -0.75,0.34 -0.75,0.75v4.68c0,0.35 0.18,0.68 0.49,0.86l3.65,2.19c0.34,0.2 0.78,0.1 0.98,-0.24a0.71,0.71 0,0 0,-0.25 -0.99l-3.37,-2v-4.5c0,-0.41 -0.34,-0.75 -0.75,-0.75zM12,5.9c-3.91,0 -7.1,3.18 -7.1,7.1s3.19,7.1 7.1,7.1 7.1,-3.18 7.1,-7.1 -3.19,-7.1 -7.1,-7.1M12,4a9,9 0,1 1,-0.001 18.001A9,9 0,0 1,12 4z" />
</vector>
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_status_area.xml b/packages/SystemUI/res-keyguard/layout/keyguard_status_area.xml
index a0850f4..7d125046 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_status_area.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_status_area.xml
@@ -39,6 +39,8 @@
android:layout_height="wrap_content"
android:drawablePadding="6dp"
android:drawableStart="@drawable/ic_access_alarms_big"
+ android:drawableTint="?attr/bgProtectSecondaryTextColor"
+ android:drawableTintMode="src_in"
android:textColor="?attr/bgProtectSecondaryTextColor"
android:letterSpacing="0.15"
style="@style/widget_label"
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 91d6b24..1705f79 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -267,6 +267,12 @@
<!-- Doze: alpha to apply to small icons when dozing -->
<integer name="doze_small_icon_alpha">222</integer><!-- 87% of 0xff -->
+ <!-- Doze: the brightness value to use for the lower brightness AOD mode -->
+ <integer name="config_doze_aod_brightness_low">6</integer>
+
+ <!-- Doze: the brightness value to use for the higher brightness AOD mode -->
+ <integer name="config_doze_aod_brightness_high">27</integer>
+
<!-- Doze: whether the double tap sensor reports 2D touch coordinates -->
<bool name="doze_double_tap_reports_touch_coordinates">false</bool>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 8807393..1ff7ae4 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -814,6 +814,9 @@
burn-in on AOD -->
<dimen name="burn_in_prevention_offset_y">50dp</dimen>
+ <!-- padding between the notification stack and the keyguard status view when dozing -->
+ <dimen name="dozing_stack_padding">10dp</dimen>
+
<dimen name="corner_size">16dp</dimen>
<dimen name="top_padding">0dp</dimen>
<dimen name="bottom_padding">48dp</dimen>
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
index 2262869..5005f9d 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
@@ -19,9 +19,11 @@
import android.app.ActivityManager;
import android.app.AlarmManager;
import android.content.Context;
+import android.content.res.ColorStateList;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.Color;
+import android.graphics.PorterDuff;
import android.os.UserHandle;
import android.support.v4.graphics.ColorUtils;
import android.text.TextUtils;
@@ -56,11 +58,14 @@
private TextView mOwnerInfo;
private ViewGroup mClockContainer;
private ChargingView mBatteryDoze;
+ private View mKeyguardStatusArea;
private View[] mVisibleInDoze;
private boolean mPulsing;
- private float mDarkAmount;
+ private float mDarkAmount = 0;
private int mTextColor;
+ private int mDateTextColor;
+ private int mAlarmTextColor;
private KeyguardUpdateMonitorCallback mInfoCallback = new KeyguardUpdateMonitorCallback() {
@@ -126,8 +131,11 @@
mClockView.setAccessibilityDelegate(new KeyguardClockAccessibilityDelegate(mContext));
mOwnerInfo = findViewById(R.id.owner_info);
mBatteryDoze = findViewById(R.id.battery_doze);
- mVisibleInDoze = new View[]{mBatteryDoze, mClockView};
+ mKeyguardStatusArea = findViewById(R.id.keyguard_status_area);
+ mVisibleInDoze = new View[]{mBatteryDoze, mClockView, mKeyguardStatusArea};
mTextColor = mClockView.getCurrentTextColor();
+ mDateTextColor = mDateView.getCurrentTextColor();
+ mAlarmTextColor = mAlarmStatusView.getCurrentTextColor();
boolean shouldMarquee = KeyguardUpdateMonitor.getInstance(mContext).isDeviceInteractive();
setEnableMarquee(shouldMarquee);
@@ -186,8 +194,7 @@
}
public int getClockBottom() {
- return mClockView.getBottom() +
- ((MarginLayoutParams) mClockView.getLayoutParams()).bottomMargin;
+ return mKeyguardStatusArea.getBottom();
}
public float getClockTextSize() {
@@ -304,6 +311,10 @@
updateDozeVisibleViews();
mBatteryDoze.setDark(dark);
mClockView.setTextColor(ColorUtils.blendARGB(mTextColor, Color.WHITE, darkAmount));
+ mDateView.setTextColor(ColorUtils.blendARGB(mDateTextColor, Color.WHITE, darkAmount));
+ int blendedAlarmColor = ColorUtils.blendARGB(mAlarmTextColor, Color.WHITE, darkAmount);
+ mAlarmStatusView.setTextColor(blendedAlarmColor);
+ mAlarmStatusView.setCompoundDrawableTintList(ColorStateList.valueOf(blendedAlarmColor));
}
public void setPulsing(boolean pulsing) {
diff --git a/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java b/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java
index 6571294..907a79e 100644
--- a/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java
+++ b/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java
@@ -199,7 +199,7 @@
// Load background image dimensions, if we haven't saved them yet
if (mBackgroundWidth <= 0 || mBackgroundHeight <= 0) {
// Need to load the image to get dimensions
- loadWallpaper(forDraw, true /* needsReset */);
+ loadWallpaper(forDraw, false /* needsReset */);
if (DEBUG) {
Log.d(TAG, "Reloading, redoing updateSurfaceSize later.");
}
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java
index e461986..28a45aa 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java
@@ -23,6 +23,9 @@
import android.hardware.SensorManager;
import android.os.Handler;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.systemui.R;
+
/**
* Controls the screen brightness when dozing.
*/
@@ -34,6 +37,9 @@
private final Sensor mLightSensor;
private boolean mRegistered;
+ private final int mHighBrightness;
+ private final int mLowBrightness;
+
public DozeScreenBrightness(Context context, DozeMachine.Service service,
SensorManager sensorManager, Sensor lightSensor, Handler handler) {
mContext = context;
@@ -41,6 +47,11 @@
mSensorManager = sensorManager;
mLightSensor = lightSensor;
mHandler = handler;
+
+ mLowBrightness = context.getResources().getInteger(
+ R.integer.config_doze_aod_brightness_low);
+ mHighBrightness = context.getResources().getInteger(
+ R.integer.config_doze_aod_brightness_high);
}
@Override
@@ -67,7 +78,17 @@
@Override
public void onSensorChanged(SensorEvent event) {
if (mRegistered) {
- mDozeService.setDozeScreenBrightness(Math.max(1, (int) event.values[0]));
+ mDozeService.setDozeScreenBrightness(computeBrightness((int) event.values[0]));
+ }
+ }
+
+ private int computeBrightness(int sensorValue) {
+ // The sensor reports 0 for off, 1 for low brightness and 2 for high brightness.
+ // We currently use DozeScreenState for screen off, so we treat off as low brightness.
+ if (sensorValue >= 2) {
+ return mHighBrightness;
+ } else {
+ return mLowBrightness;
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/SlashDrawable.java b/packages/SystemUI/src/com/android/systemui/qs/SlashDrawable.java
index b8535a3..c356148 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/SlashDrawable.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/SlashDrawable.java
@@ -33,11 +33,12 @@
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.drawable.Drawable;
-import android.util.Log;
import android.util.FloatProperty;
public class SlashDrawable extends Drawable {
+ public static final float CORNER_RADIUS = 1f;
+
private final Path mPath = new Path();
private final Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
@@ -141,8 +142,8 @@
Matrix m = new Matrix();
final int width = getBounds().width();
final int height = getBounds().height();
- final float radiusX = scale(1f, width);
- final float radiusY = scale(1f, height);
+ final float radiusX = scale(CORNER_RADIUS, width);
+ final float radiusY = scale(CORNER_RADIUS, height);
updateRect(
scale(LEFT, width),
scale(TOP, height),
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
index 652288d..e65bab6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
@@ -78,6 +78,7 @@
private AccelerateInterpolator mAccelerateInterpolator = new AccelerateInterpolator();
private int mClockBottom;
private float mDarkAmount;
+ private int mDozingStackPadding;
/**
* Refreshes the dimension values.
@@ -97,6 +98,7 @@
R.dimen.burn_in_prevention_offset_x);
mBurnInPreventionOffsetY = res.getDimensionPixelSize(
R.dimen.burn_in_prevention_offset_y);
+ mDozingStackPadding = res.getDimensionPixelSize(R.dimen.dozing_stack_padding);
}
public void setup(int maxKeyguardNotifications, int maxPanelHeight, float expandedHeight,
@@ -135,7 +137,7 @@
result.stackScrollerPadding = (int) interpolate(
result.stackScrollerPadding,
- mClockBottom + y,
+ mClockBottom + y + mDozingStackPadding,
mDarkAmount);
result.clockX = (int) interpolate(0, burnInPreventionOffsetX(), mDarkAmount);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SignalDrawable.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SignalDrawable.java
index d537cda..f619d812 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SignalDrawable.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SignalDrawable.java
@@ -36,6 +36,7 @@
import com.android.settingslib.R;
import com.android.settingslib.Utils;
+import com.android.systemui.qs.SlashDrawable;
public class SignalDrawable extends Drawable {
@@ -333,10 +334,9 @@
mForegroundPath.reset();
mFullPath.op(mCutPath, Path.Op.DIFFERENCE);
} else if (mState == STATE_AIRPLANE) {
- // Airplane mode is slashed, full-signal
- mForegroundPath.set(mFullPath);
- mFullPath.reset();
- mSlash.draw((int) height, (int) width, canvas, mForegroundPaint);
+ // Airplane mode is slashed, fully drawn background
+ mForegroundPath.reset();
+ mSlash.draw((int) height, (int) width, canvas, mPaint);
} else if (mState != STATE_CARRIER_CHANGE) {
mForegroundPath.reset();
int sigWidth = Math.round(calcFit(mLevel / (mNumLevels - 1)) * (width - 2 * padding));
@@ -473,6 +473,7 @@
void draw(int height, int width, @NonNull Canvas canvas, Paint paint) {
Matrix m = new Matrix();
+ final float radius = scale(SlashDrawable.CORNER_RADIUS, width);
updateRect(
scale(LEFT, width),
scale(TOP, height),
@@ -481,7 +482,7 @@
mPath.reset();
// Draw the slash vertically
- mPath.addRect(mSlashRect, Direction.CW);
+ mPath.addRoundRect(mSlashRect, radius, radius, Direction.CW);
m.setRotate(ROTATION, width / 2, height / 2);
mPath.transform(m);
canvas.drawPath(mPath, paint);
@@ -491,7 +492,7 @@
mPath.transform(m);
m.setTranslate(mSlashRect.width(), 0);
mPath.transform(m);
- mPath.addRect(mSlashRect, Direction.CW);
+ mPath.addRoundRect(mSlashRect, radius, radius, Direction.CW);
m.setRotate(ROTATION, width / 2, height / 2);
mPath.transform(m);
canvas.clipOutPath(mPath);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenBrightnessTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenBrightnessTest.java
index 514dc8b..fe3221a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenBrightnessTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenBrightnessTest.java
@@ -38,11 +38,13 @@
import com.android.systemui.utils.hardware.FakeSensorManager;
import org.junit.Before;
+import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
@RunWith(AndroidJUnit4.class)
@SmallTest
+@Ignore
public class DozeScreenBrightnessTest extends SysuiTestCase {
DozeServiceFake mServiceFake;
diff --git a/proto/src/metrics_constants.proto b/proto/src/metrics_constants.proto
index 78d593e..d6ca23f 100644
--- a/proto/src/metrics_constants.proto
+++ b/proto/src/metrics_constants.proto
@@ -138,6 +138,18 @@
REASON_TIMEOUT = 19;
}
+ // Subtypes of camera events for ACTION_CAMERA_EVENT
+ enum CameraEvent {
+ // A back-facing camera was used
+ CAMERA_BACK_USED = 0;
+
+ // A front-facing camera was used
+ CAMERA_FRONT_USED = 1;
+
+ // An external camera was used
+ CAMERA_EXTERNAL_USED = 2;
+ }
+
// Known visual elements: views or controls.
enum View {
// Unknown view
@@ -4196,6 +4208,12 @@
// OS: O DR
DIALOG_BLUETOOTH_PAIRED_DEVICE_FORGET = 1031;
+ // An event from the camera service
+ // CATEGORY: OTHER
+ // SUBTYPE: CameraEvent
+ // OS: O DR
+ ACTION_CAMERA_EVENT = 1032;
+
// ---- End O-DR1 Constants, all O-DR1 constants go above this line ----
// Add new aosp constants above this line.
diff --git a/services/backup/java/com/android/server/backup/BackupManagerService.java b/services/backup/java/com/android/server/backup/BackupManagerService.java
index 4a7eef9..abfc31e 100644
--- a/services/backup/java/com/android/server/backup/BackupManagerService.java
+++ b/services/backup/java/com/android/server/backup/BackupManagerService.java
@@ -4871,6 +4871,7 @@
final int N = mPackages.size();
final byte[] buffer = new byte[8192];
for (int i = 0; i < N; i++) {
+ mBackupRunner = null;
PackageInfo currentPackage = mPackages.get(i);
String packageName = currentPackage.packageName;
if (DEBUG) {
@@ -5054,7 +5055,13 @@
}
EventLog.writeEvent(EventLogTags.FULL_BACKUP_AGENT_FAILURE, packageName,
"transport rejected");
- // Do nothing, clean up, and continue looping.
+ // This failure state can come either a-priori from the transport, or
+ // from the preflight pass. If we got as far as preflight, we now need
+ // to tear down the target process.
+ if (mBackupRunner != null) {
+ tearDownAgentAndKill(currentPackage.applicationInfo);
+ }
+ // ... and continue looping.
} else if (backupPackageStatus == BackupTransport.TRANSPORT_QUOTA_EXCEEDED) {
sendBackupOnPackageResult(mBackupObserver, packageName,
BackupManager.ERROR_TRANSPORT_QUOTA_EXCEEDED);
@@ -5063,6 +5070,7 @@
EventLog.writeEvent(EventLogTags.FULL_BACKUP_QUOTA_EXCEEDED,
packageName);
}
+ tearDownAgentAndKill(currentPackage.applicationInfo);
// Do nothing, clean up, and continue looping.
} else if (backupPackageStatus == BackupTransport.AGENT_ERROR) {
sendBackupOnPackageResult(mBackupObserver, packageName,
@@ -5086,6 +5094,7 @@
EventLog.writeEvent(EventLogTags.FULL_BACKUP_TRANSPORT_FAILURE);
// Abort entire backup pass.
backupRunStatus = BackupManager.ERROR_TRANSPORT_ABORTED;
+ tearDownAgentAndKill(currentPackage.applicationInfo);
return;
} else {
// Success!
diff --git a/services/core/java/com/android/server/NetworkManagementService.java b/services/core/java/com/android/server/NetworkManagementService.java
index 8ea334d..7959e39 100644
--- a/services/core/java/com/android/server/NetworkManagementService.java
+++ b/services/core/java/com/android/server/NetworkManagementService.java
@@ -18,6 +18,7 @@
import static android.Manifest.permission.CONNECTIVITY_INTERNAL;
import static android.Manifest.permission.DUMP;
+import static android.Manifest.permission.NETWORK_STACK;
import static android.Manifest.permission.SHUTDOWN;
import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_DOZABLE;
import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_DOZABLE;
@@ -55,6 +56,7 @@
import android.net.ConnectivityManager;
import android.net.INetd;
import android.net.INetworkManagementEventObserver;
+import android.net.ITetheringStatsProvider;
import android.net.InterfaceConfiguration;
import android.net.IpPrefix;
import android.net.LinkAddress;
@@ -225,6 +227,10 @@
private final NetworkStatsFactory mStatsFactory = new NetworkStatsFactory();
+ @GuardedBy("mTetheringStatsProviders")
+ private final HashMap<ITetheringStatsProvider, String>
+ mTetheringStatsProviders = Maps.newHashMap();
+
/**
* If both locks need to be held, then they should be obtained in the order:
* first {@link #mQuotaLock} and then {@link #mRulesLock}.
@@ -331,6 +337,10 @@
Watchdog.getInstance().addMonitor(this);
LocalServices.addService(NetworkManagementInternal.class, new LocalService());
+
+ synchronized (mTetheringStatsProviders) {
+ mTetheringStatsProviders.put(new NetdTetheringStatsProvider(), "netd");
+ }
}
@VisibleForTesting
@@ -520,6 +530,23 @@
}
}
+ @Override
+ public void registerTetheringStatsProvider(ITetheringStatsProvider provider, String name) {
+ mContext.enforceCallingOrSelfPermission(NETWORK_STACK, TAG);
+ Preconditions.checkNotNull(provider);
+ synchronized(mTetheringStatsProviders) {
+ mTetheringStatsProviders.put(provider, name);
+ }
+ }
+
+ @Override
+ public void unregisterTetheringStatsProvider(ITetheringStatsProvider provider) {
+ mContext.enforceCallingOrSelfPermission(NETWORK_STACK, TAG);
+ synchronized(mTetheringStatsProviders) {
+ mTetheringStatsProviders.remove(provider);
+ }
+ }
+
// Sync the state of the given chain with the native daemon.
private void syncFirewallChainLocked(int chain, String name) {
SparseIntArray rules;
@@ -1789,14 +1816,16 @@
}
}
- @Override
- public NetworkStats getNetworkStatsTethering() {
- mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
-
- final NetworkStats stats = new NetworkStats(SystemClock.elapsedRealtime(), 1);
- try {
- final NativeDaemonEvent[] events = mConnector.executeForList(
- "bandwidth", "gettetherstats");
+ private class NetdTetheringStatsProvider extends ITetheringStatsProvider.Stub {
+ @Override
+ public NetworkStats getTetherStats() {
+ final NativeDaemonEvent[] events;
+ try {
+ events = mConnector.executeForList("bandwidth", "gettetherstats");
+ } catch (NativeDaemonConnectorException e) {
+ throw e.rethrowAsParcelableException();
+ }
+ final NetworkStats stats = new NetworkStats(SystemClock.elapsedRealtime(), 1);
for (NativeDaemonEvent event : events) {
if (event.getCode() != TetheringStatsListResult) continue;
@@ -1822,8 +1851,24 @@
throw new IllegalStateException("problem parsing tethering stats: " + event);
}
}
- } catch (NativeDaemonConnectorException e) {
- throw e.rethrowAsParcelableException();
+ return stats;
+ }
+ }
+
+ @Override
+ public NetworkStats getNetworkStatsTethering() {
+ mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
+
+ final NetworkStats stats = new NetworkStats(SystemClock.elapsedRealtime(), 1);
+ synchronized (mTetheringStatsProviders) {
+ for (ITetheringStatsProvider provider: mTetheringStatsProviders.keySet()) {
+ try {
+ stats.combineAllValues(provider.getTetherStats());
+ } catch (RemoteException e) {
+ Log.e(TAG, "Problem reading tethering stats from " +
+ mTetheringStatsProviders.get(provider) + ": " + e);
+ }
+ }
}
return stats;
}
diff --git a/services/core/java/com/android/server/Watchdog.java b/services/core/java/com/android/server/Watchdog.java
index b18fa32..aceedf1 100644
--- a/services/core/java/com/android/server/Watchdog.java
+++ b/services/core/java/com/android/server/Watchdog.java
@@ -85,8 +85,9 @@
"android.hardware.bluetooth@1.0::IBluetoothHci",
"android.hardware.camera.provider@2.4::ICameraProvider",
"android.hardware.graphics.composer@2.1::IComposer",
- "android.hardware.vr@1.0::IVr",
- "android.hardware.media.omx@1.0::IOmx"
+ "android.hardware.media.omx@1.0::IOmx",
+ "android.hardware.sensors@1.0::ISensors",
+ "android.hardware.vr@1.0::IVr"
);
static Watchdog sWatchdog;
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index f106ca0..75467f5 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -12460,10 +12460,10 @@
switch (mWakefulness) {
case PowerManagerInternal.WAKEFULNESS_AWAKE:
case PowerManagerInternal.WAKEFULNESS_DREAMING:
- case PowerManagerInternal.WAKEFULNESS_DOZING:
// Pause applications whenever the lock screen is shown or any sleep
// tokens have been acquired.
return mKeyguardController.isKeyguardShowing() || !mSleepTokens.isEmpty();
+ case PowerManagerInternal.WAKEFULNESS_DOZING:
case PowerManagerInternal.WAKEFULNESS_ASLEEP:
default:
// If we're asleep then pause applications unconditionally.
diff --git a/services/core/java/com/android/server/camera/CameraServiceProxy.java b/services/core/java/com/android/server/camera/CameraServiceProxy.java
index 82b2566..3133a51 100644
--- a/services/core/java/com/android/server/camera/CameraServiceProxy.java
+++ b/services/core/java/com/android/server/camera/CameraServiceProxy.java
@@ -21,6 +21,7 @@
import android.content.IntentFilter;
import android.hardware.ICameraService;
import android.hardware.ICameraServiceProxy;
+import android.metrics.LogMaker;
import android.nfc.INfcAdapter;
import android.os.Binder;
import android.os.Handler;
@@ -28,15 +29,23 @@
import android.os.Message;
import android.os.Process;
import android.os.RemoteException;
+import android.os.SystemClock;
import android.os.SystemProperties;
import android.os.UserManager;
+import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Slog;
+import com.android.internal.logging.MetricsLogger;
+import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.server.LocalServices;
import com.android.server.ServiceThread;
import com.android.server.SystemService;
+import java.util.ArrayList;
import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
import java.util.Set;
/**
@@ -65,6 +74,9 @@
private static final int RETRY_DELAY_TIME = 20; //ms
+ // Maximum entries to keep in usage history before dumping out
+ private static final int MAX_USAGE_HISTORY = 100;
+
private final Context mContext;
private final ServiceThread mHandlerThread;
private final Handler mHandler;
@@ -76,14 +88,52 @@
private ICameraService mCameraServiceRaw;
- private final ArraySet<String> mActiveCameraIds = new ArraySet<>();
-
+ private final ArrayMap<String, CameraUsageEvent> mActiveCameraUsage = new ArrayMap<>();
+ private final List<CameraUsageEvent> mCameraUsageHistory = new ArrayList<>();
+ private final MetricsLogger mLogger = new MetricsLogger();
private static final String NFC_NOTIFICATION_PROP = "ro.camera.notify_nfc";
private static final String NFC_SERVICE_BINDER_NAME = "nfc";
private static final IBinder nfcInterfaceToken = new Binder();
private final boolean mNotifyNfc;
- private int mActiveCameraCount = 0;
+
+ /**
+ * Structure to track camera usage
+ */
+ private static class CameraUsageEvent {
+ public final int mCameraFacing;
+ public final String mClientName;
+
+ private boolean mCompleted;
+ private long mDurationOrStartTimeMs; // Either start time, or duration once completed
+
+ public CameraUsageEvent(int facing, String clientName) {
+ mCameraFacing = facing;
+ mClientName = clientName;
+ mDurationOrStartTimeMs = SystemClock.elapsedRealtime();
+ mCompleted = false;
+ }
+
+ public void markCompleted() {
+ if (mCompleted) {
+ return;
+ }
+ mCompleted = true;
+ mDurationOrStartTimeMs = SystemClock.elapsedRealtime() - mDurationOrStartTimeMs;
+ if (CameraServiceProxy.DEBUG) {
+ Slog.v(TAG, "A camera facing " + cameraFacingToString(mCameraFacing) +
+ " was in use by " + mClientName + " for " +
+ mDurationOrStartTimeMs + " ms");
+ }
+ }
+
+ /**
+ * Return duration of camera usage event, or 0 if the event is not done
+ */
+ public long getDuration() {
+ return mCompleted ? mDurationOrStartTimeMs : 0;
+ }
+ }
private final BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
@Override
@@ -120,10 +170,11 @@
public void notifyCameraState(String cameraId, int newCameraState, int facing,
String clientName) {
String state = cameraStateToString(newCameraState);
- if (DEBUG) Slog.v(TAG, "Camera " + cameraId + " facing " + facing + " state now " +
+ String facingStr = cameraFacingToString(facing);
+ if (DEBUG) Slog.v(TAG, "Camera " + cameraId + " facing " + facingStr + " state now " +
state + " for client " + clientName);
- updateActivityCount(cameraId, newCameraState);
+ updateActivityCount(cameraId, newCameraState, facing, clientName);
}
};
@@ -169,6 +220,9 @@
mContext.registerReceiver(mIntentReceiver, filter);
publishBinderService(CAMERA_SERVICE_PROXY_BINDER_NAME, mCameraServiceProxy);
+ publishLocalService(CameraServiceProxy.class, this);
+
+ CameraStatsJobService.schedule(mContext);
}
@Override
@@ -198,8 +252,8 @@
mCameraServiceRaw = null;
// All cameras reset to idle on camera service death
- boolean wasEmpty = mActiveCameraIds.isEmpty();
- mActiveCameraIds.clear();
+ boolean wasEmpty = mActiveCameraUsage.isEmpty();
+ mActiveCameraUsage.clear();
if ( mNotifyNfc && !wasEmpty ) {
notifyNfcService(/*enablePolling*/ true);
@@ -207,6 +261,46 @@
}
}
+ /**
+ * Dump camera usage events to log.
+ * Package-private
+ */
+ void dumpUsageEvents() {
+ synchronized(mLock) {
+ // Randomize order of events so that it's not meaningful
+ Collections.shuffle(mCameraUsageHistory);
+ for (CameraUsageEvent e : mCameraUsageHistory) {
+ if (DEBUG) {
+ Slog.v(TAG, "Camera: " + e.mClientName + " used a camera facing " +
+ cameraFacingToString(e.mCameraFacing) + " for " +
+ e.getDuration() + " ms");
+ }
+ int subtype = 0;
+ switch(e.mCameraFacing) {
+ case ICameraServiceProxy.CAMERA_FACING_BACK:
+ subtype = MetricsEvent.CAMERA_BACK_USED;
+ break;
+ case ICameraServiceProxy.CAMERA_FACING_FRONT:
+ subtype = MetricsEvent.CAMERA_FRONT_USED;
+ break;
+ case ICameraServiceProxy.CAMERA_FACING_EXTERNAL:
+ subtype = MetricsEvent.CAMERA_EXTERNAL_USED;
+ break;
+ default:
+ continue;
+ }
+ LogMaker l = new LogMaker(MetricsEvent.ACTION_CAMERA_EVENT)
+ .setType(MetricsEvent.TYPE_ACTION)
+ .setSubtype(subtype)
+ .setLatency(e.getDuration())
+ .setPackageName(e.mClientName);
+ mLogger.write(l);
+ }
+ mCameraUsageHistory.clear();
+ }
+ CameraStatsJobService.schedule(mContext);
+ }
+
private void switchUserLocked(int userHandle) {
Set<Integer> currentUserHandles = getEnabledUserHandles(userHandle);
mLastUser = userHandle;
@@ -274,21 +368,35 @@
return true;
}
- private void updateActivityCount(String cameraId, int newCameraState) {
+ private void updateActivityCount(String cameraId, int newCameraState, int facing, String clientName) {
synchronized(mLock) {
- boolean wasEmpty = mActiveCameraIds.isEmpty();
+ // Update active camera list and notify NFC if necessary
+ boolean wasEmpty = mActiveCameraUsage.isEmpty();
switch (newCameraState) {
case ICameraServiceProxy.CAMERA_STATE_OPEN:
break;
case ICameraServiceProxy.CAMERA_STATE_ACTIVE:
- mActiveCameraIds.add(cameraId);
+ CameraUsageEvent newEvent = new CameraUsageEvent(facing, clientName);
+ CameraUsageEvent oldEvent = mActiveCameraUsage.put(cameraId, newEvent);
+ if (oldEvent != null) {
+ Slog.w(TAG, "Camera " + cameraId + " was already marked as active");
+ oldEvent.markCompleted();
+ mCameraUsageHistory.add(oldEvent);
+ }
break;
case ICameraServiceProxy.CAMERA_STATE_IDLE:
case ICameraServiceProxy.CAMERA_STATE_CLOSED:
- mActiveCameraIds.remove(cameraId);
+ CameraUsageEvent doneEvent = mActiveCameraUsage.remove(cameraId);
+ if (doneEvent != null) {
+ doneEvent.markCompleted();
+ mCameraUsageHistory.add(doneEvent);
+ if (mCameraUsageHistory.size() > MAX_USAGE_HISTORY) {
+ dumpUsageEvents();
+ }
+ }
break;
}
- boolean isEmpty = mActiveCameraIds.isEmpty();
+ boolean isEmpty = mActiveCameraUsage.isEmpty();
if ( mNotifyNfc && (wasEmpty != isEmpty) ) {
notifyNfcService(isEmpty);
}
@@ -332,4 +440,15 @@
}
return "CAMERA_STATE_UNKNOWN";
}
+
+ private static String cameraFacingToString(int cameraFacing) {
+ switch (cameraFacing) {
+ case ICameraServiceProxy.CAMERA_FACING_BACK: return "CAMERA_FACING_BACK";
+ case ICameraServiceProxy.CAMERA_FACING_FRONT: return "CAMERA_FACING_FRONT";
+ case ICameraServiceProxy.CAMERA_FACING_EXTERNAL: return "CAMERA_FACING_EXTERNAL";
+ default: break;
+ }
+ return "CAMERA_FACING_UNKNOWN";
+ }
+
}
diff --git a/services/core/java/com/android/server/camera/CameraStatsJobService.java b/services/core/java/com/android/server/camera/CameraStatsJobService.java
new file mode 100644
index 0000000..b8a6846
--- /dev/null
+++ b/services/core/java/com/android/server/camera/CameraStatsJobService.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE2.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.camera;
+
+import android.app.job.JobInfo;
+import android.app.job.JobParameters;
+import android.app.job.JobScheduler;
+import android.app.job.JobService;
+import android.content.ComponentName;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.util.Slog;
+
+import java.util.concurrent.TimeUnit;
+
+import com.android.server.LocalServices;
+
+/**
+ * A JobService to periodically collect camera usage stats.
+ */
+public class CameraStatsJobService extends JobService {
+ private static final String TAG = "CameraStatsJobService";
+
+ // Must be unique within UID (system service)
+ private static final int CAMERA_REPORTING_JOB_ID = 0xCA3E7A;
+
+ private static ComponentName sCameraStatsJobServiceName = new ComponentName(
+ "android",
+ CameraStatsJobService.class.getName());
+
+ @Override
+ public boolean onStartJob(JobParameters params) {
+ CameraServiceProxy serviceProxy = LocalServices.getService(CameraServiceProxy.class);
+ if (serviceProxy == null) {
+ Slog.w(TAG, "Can't collect camera usage stats - no camera service proxy found");
+ return false;
+ }
+
+ serviceProxy.dumpUsageEvents();
+ return false;
+ }
+
+ @Override
+ public boolean onStopJob(JobParameters params) {
+ // All work is done in onStartJob, so nothing to stop here
+ return false;
+ }
+
+ public static void schedule(Context context) {
+
+ JobScheduler js = (JobScheduler) context.getSystemService(Context.JOB_SCHEDULER_SERVICE);
+ if (js == null) {
+ Slog.e(TAG, "Can't collect camera usage stats - no Job Scheduler");
+ return;
+ }
+ js.schedule(new JobInfo.Builder(CAMERA_REPORTING_JOB_ID, sCameraStatsJobServiceName)
+ .setMinimumLatency(TimeUnit.DAYS.toMillis(1))
+ .setRequiresDeviceIdle(true)
+ .build());
+
+ }
+
+}
diff --git a/services/core/java/com/android/server/connectivity/Tethering.java b/services/core/java/com/android/server/connectivity/Tethering.java
index 1fb944f..b0be8f7 100644
--- a/services/core/java/com/android/server/connectivity/Tethering.java
+++ b/services/core/java/com/android/server/connectivity/Tethering.java
@@ -214,7 +214,7 @@
final Handler smHandler = mTetherMasterSM.getHandler();
mOffloadController = new OffloadController(smHandler,
deps.getOffloadHardwareInterface(smHandler, mLog),
- mContext.getContentResolver(),
+ mContext.getContentResolver(), mNMService,
mLog);
mUpstreamNetworkMonitor = new UpstreamNetworkMonitor(
mContext, mTetherMasterSM, mLog, TetherMasterSM.EVENT_UPSTREAM_CALLBACK);
@@ -1759,6 +1759,11 @@
pw.decreaseIndent();
}
+ pw.println("Hardware offload:");
+ pw.increaseIndent();
+ mOffloadController.dump(pw);
+ pw.decreaseIndent();
+
pw.println("Log:");
pw.increaseIndent();
if (argsContain(args, SHORT_ARG)) {
diff --git a/services/core/java/com/android/server/connectivity/tethering/OffloadController.java b/services/core/java/com/android/server/connectivity/tethering/OffloadController.java
index b473867..1a5ff77 100644
--- a/services/core/java/com/android/server/connectivity/tethering/OffloadController.java
+++ b/services/core/java/com/android/server/connectivity/tethering/OffloadController.java
@@ -16,24 +16,38 @@
package com.android.server.connectivity.tethering;
+import static android.net.NetworkStats.SET_DEFAULT;
+import static android.net.NetworkStats.TAG_NONE;
+import static android.net.TrafficStats.UID_TETHERING;
import static android.provider.Settings.Global.TETHER_OFFLOAD_DISABLED;
import android.content.ContentResolver;
+import android.net.ITetheringStatsProvider;
import android.net.IpPrefix;
import android.net.LinkAddress;
import android.net.LinkProperties;
+import android.net.NetworkStats;
import android.net.RouteInfo;
import android.net.util.SharedLog;
import android.os.Handler;
+import android.os.INetworkManagementService;
+import android.os.RemoteException;
+import android.os.SystemClock;
import android.provider.Settings;
+import android.text.TextUtils;
+
+import com.android.internal.util.IndentingPrintWriter;
import java.net.Inet4Address;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.util.ArrayList;
+import java.util.HashMap;
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
/**
* A class to encapsulate the business logic of programming the tethering
@@ -44,6 +58,8 @@
public class OffloadController {
private static final String TAG = OffloadController.class.getSimpleName();
+ private static final int STATS_FETCH_TIMEOUT_MS = 1000;
+
private final Handler mHandler;
private final OffloadHardwareInterface mHwInterface;
private final ContentResolver mContentResolver;
@@ -59,14 +75,25 @@
// prefixes representing only the locally-assigned IP addresses.
private Set<String> mLastLocalPrefixStrs;
+ // Maps upstream interface names to offloaded traffic statistics.
+ private HashMap<String, OffloadHardwareInterface.ForwardedStats>
+ mForwardedStats = new HashMap<>();
+
public OffloadController(Handler h, OffloadHardwareInterface hwi,
- ContentResolver contentResolver, SharedLog log) {
+ ContentResolver contentResolver, INetworkManagementService nms, SharedLog log) {
mHandler = h;
mHwInterface = hwi;
mContentResolver = contentResolver;
mLog = log.forSubComponent(TAG);
mExemptPrefixes = new HashSet<>();
mLastLocalPrefixStrs = new HashSet<>();
+
+ try {
+ nms.registerTetheringStatsProvider(
+ new OffloadTetheringStatsProvider(), getClass().getSimpleName());
+ } catch (RemoteException e) {
+ mLog.e("Cannot register offload stats provider: " + e);
+ }
}
public void start() {
@@ -138,6 +165,7 @@
public void stop() {
final boolean wasStarted = started();
+ updateStatsForCurrentUpstream();
mUpstreamLinkProperties = null;
mHwInterface.stopOffloadControl();
mControlInitialized = false;
@@ -145,16 +173,76 @@
if (wasStarted) mLog.log("tethering offload stopped");
}
+ private class OffloadTetheringStatsProvider extends ITetheringStatsProvider.Stub {
+ @Override
+ public NetworkStats getTetherStats() {
+ NetworkStats stats = new NetworkStats(SystemClock.elapsedRealtime(), 0);
+ CountDownLatch latch = new CountDownLatch(1);
+
+ mHandler.post(() -> {
+ try {
+ NetworkStats.Entry entry = new NetworkStats.Entry();
+ entry.set = SET_DEFAULT;
+ entry.tag = TAG_NONE;
+ entry.uid = UID_TETHERING;
+
+ updateStatsForCurrentUpstream();
+
+ for (String iface : mForwardedStats.keySet()) {
+ entry.iface = iface;
+ entry.rxBytes = mForwardedStats.get(iface).rxBytes;
+ entry.txBytes = mForwardedStats.get(iface).txBytes;
+ stats.addValues(entry);
+ }
+ } finally {
+ latch.countDown();
+ }
+ });
+
+ try {
+ latch.await(STATS_FETCH_TIMEOUT_MS, TimeUnit.MILLISECONDS);
+ } catch (InterruptedException e) {
+ mLog.e("Tethering stats fetch timed out after " + STATS_FETCH_TIMEOUT_MS + "ms");
+ }
+
+ return stats;
+ }
+ }
+
+ private void maybeUpdateStats(String iface) {
+ if (TextUtils.isEmpty(iface)) {
+ return;
+ }
+
+ if (!mForwardedStats.containsKey(iface)) {
+ mForwardedStats.put(iface, new OffloadHardwareInterface.ForwardedStats());
+ }
+ mForwardedStats.get(iface).add(mHwInterface.getForwardedStats(iface));
+ }
+
+ private void updateStatsForCurrentUpstream() {
+ if (mUpstreamLinkProperties != null) {
+ maybeUpdateStats(mUpstreamLinkProperties.getInterfaceName());
+ }
+ }
+
public void setUpstreamLinkProperties(LinkProperties lp) {
if (!started() || Objects.equals(mUpstreamLinkProperties, lp)) return;
+ String prevUpstream = (mUpstreamLinkProperties != null) ?
+ mUpstreamLinkProperties.getInterfaceName() : null;
+
mUpstreamLinkProperties = (lp != null) ? new LinkProperties(lp) : null;
+
// TODO: examine return code and decide what to do if programming
// upstream parameters fails (probably just wait for a subsequent
// onOffloadEvent() callback to tell us offload is available again and
// then reapply all state).
computeAndPushLocalPrefixes();
pushUpstreamParameters();
+
+ // Update stats after we've told the hardware to change routing so we don't miss packets.
+ maybeUpdateStats(prevUpstream);
}
public void setLocalPrefixes(Set<IpPrefix> localPrefixes) {
@@ -262,4 +350,16 @@
for (IpPrefix pfx : prefixSet) localPrefixStrs.add(pfx.toString());
return localPrefixStrs;
}
+
+ public void dump(IndentingPrintWriter pw) {
+ if (isOffloadDisabled()) {
+ pw.println("Offload disabled");
+ return;
+ }
+ pw.println("Offload HALs " + (started() ? "started" : "not started"));
+ LinkProperties lp = mUpstreamLinkProperties;
+ String upstream = (lp != null) ? lp.getInterfaceName() : null;
+ pw.println("Current upstream: " + upstream);
+ pw.println("Exempt prefixes: " + mLastLocalPrefixStrs);
+ }
}
diff --git a/services/core/jni/com_android_server_VibratorService.cpp b/services/core/jni/com_android_server_VibratorService.cpp
index cb8416b..45e9cc7 100644
--- a/services/core/jni/com_android_server_VibratorService.cpp
+++ b/services/core/jni/com_android_server_VibratorService.cpp
@@ -73,11 +73,13 @@
ret = (sHal == nullptr) ? NullptrStatus<R>()
: (*sHal.*fn)(std::forward<Args1>(args1)...);
- if (!ret.isOk()) {
- ALOGE("Failed to issue command to vibrator HAL. Retrying.");
- // Restoring connection to the HAL.
- sHal = I::tryGetService();
+ if (ret.isOk()) {
+ break;
}
+
+ ALOGE("Failed to issue command to vibrator HAL. Retrying.");
+ // Restoring connection to the HAL.
+ sHal = I::tryGetService();
}
return ret;
}
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index eca285a..c9be3a2 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -775,13 +775,6 @@
mContentResolver = context.getContentResolver();
- if (!disableCameraService) {
- Slog.i(TAG, "Camera Service Proxy");
- traceBeginAndSlog("StartCameraServiceProxy");
- mSystemServiceManager.startService(CameraServiceProxy.class);
- traceEnd();
- }
-
// The AccountManager must come before the ContentService
traceBeginAndSlog("StartAccountManagerService");
mSystemServiceManager.startService(ACCOUNT_SERVICE_CLASS);
@@ -1531,6 +1524,12 @@
}
}
+ if (!disableCameraService) {
+ traceBeginAndSlog("StartCameraServiceProxy");
+ mSystemServiceManager.startService(CameraServiceProxy.class);
+ traceEnd();
+ }
+
// Before things start rolling, be sure we have decided whether
// we are in safe mode.
final boolean safeMode = wm.detectSafeMode();
diff --git a/services/tests/notification/src/com/android/server/notification/NotificationChannelTest.java b/services/tests/notification/src/com/android/server/notification/NotificationChannelTest.java
index 3007cb1..f457f6a 100644
--- a/services/tests/notification/src/com/android/server/notification/NotificationChannelTest.java
+++ b/services/tests/notification/src/com/android/server/notification/NotificationChannelTest.java
@@ -25,8 +25,14 @@
import android.support.test.runner.AndroidJUnit4;
import android.test.suitebuilder.annotation.SmallTest;
+import com.android.internal.util.FastXmlSerializer;
+
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.BufferedOutputStream;
+import java.io.ByteArrayOutputStream;
@SmallTest
@RunWith(AndroidJUnit4.class)
@@ -50,4 +56,15 @@
channel.setBlockableSystem(true);
assertEquals(true, channel.isBlockableSystem());
}
+
+ @Test
+ public void testEmptyVibration_noException() throws Exception {
+ NotificationChannel channel = new NotificationChannel("a", "ab", IMPORTANCE_DEFAULT);
+ channel.setVibrationPattern(new long[0]);
+
+ XmlSerializer serializer = new FastXmlSerializer();
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ serializer.setOutput(new BufferedOutputStream(baos), "utf-8");
+ channel.writeXml(serializer);
+ }
}
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 9b3786e..f6deb0a 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -1471,6 +1471,17 @@
public static final String IMSI_KEY_EXPIRATION_DAYS_TIME_INT =
"imsi_key_expiration_days_time_int";
+ /**
+ * Key identifying if the CDMA Caller ID presentation and suppression MMI codes
+ * should be converted to 3GPP CLIR codes when a multimode (CDMA+UMTS+LTE) device is roaming
+ * on a 3GPP network. Specifically *67<number> will be converted to #31#<number> and
+ * *82<number> will be converted to *31#<number> before dialing a call when this key is
+ * set TRUE and device is roaming on a 3GPP network.
+ * @hide
+ */
+ public static final String KEY_CONVERT_CDMA_CALLER_ID_MMI_CODES_WHILE_ROAMING_ON_3GPP_BOOL =
+ "convert_cdma_caller_id_mmi_codes_while_roaming_on_3gpp_bool";
+
/** The default value for every variable. */
private final static PersistableBundle sDefaults;
@@ -1721,6 +1732,8 @@
sDefaults.putBoolean(KEY_DISABLE_VOICE_BARRING_NOTIFICATION_BOOL, false);
sDefaults.putInt(IMSI_KEY_EXPIRATION_DAYS_TIME_INT, IMSI_ENCRYPTION_DAYS_TIME_DISABLED);
sDefaults.putString(IMSI_KEY_DOWNLOAD_URL_STRING, null);
+ sDefaults.putBoolean(KEY_CONVERT_CDMA_CALLER_ID_MMI_CODES_WHILE_ROAMING_ON_3GPP_BOOL,
+ false);
}
/**
diff --git a/tests/net/java/com/android/server/connectivity/TetheringTest.java b/tests/net/java/com/android/server/connectivity/TetheringTest.java
index acf9e8f7..a115146 100644
--- a/tests/net/java/com/android/server/connectivity/TetheringTest.java
+++ b/tests/net/java/com/android/server/connectivity/TetheringTest.java
@@ -178,6 +178,7 @@
mTethering = new Tethering(mServiceContext, mNMService, mStatsService, mPolicyManager,
mLooper.getLooper(), mSystemProperties,
mTetheringDependencies);
+ verify(mNMService).registerTetheringStatsProvider(any(), anyString());
}
@After
diff --git a/tests/net/java/com/android/server/connectivity/tethering/OffloadControllerTest.java b/tests/net/java/com/android/server/connectivity/tethering/OffloadControllerTest.java
index 0e4a36c..dcb9723 100644
--- a/tests/net/java/com/android/server/connectivity/tethering/OffloadControllerTest.java
+++ b/tests/net/java/com/android/server/connectivity/tethering/OffloadControllerTest.java
@@ -16,26 +16,38 @@
package com.android.server.connectivity.tethering;
+import static android.net.NetworkStats.SET_DEFAULT;
+import static android.net.NetworkStats.TAG_NONE;
+import static android.net.TrafficStats.UID_TETHERING;
import static android.provider.Settings.Global.TETHER_OFFLOAD_DISABLED;
+import static com.android.server.connectivity.tethering.OffloadHardwareInterface.ForwardedStats;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyObject;
+import static org.mockito.Matchers.anyString;
import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.content.Context;
import android.content.pm.ApplicationInfo;
+import android.net.ITetheringStatsProvider;
import android.net.IpPrefix;
import android.net.LinkAddress;
import android.net.LinkProperties;
+import android.net.NetworkStats;
import android.net.RouteInfo;
import android.net.util.SharedLog;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.INetworkManagementService;
import android.provider.Settings;
import android.provider.Settings.SettingNotFoundException;
@@ -66,11 +78,14 @@
@Mock private OffloadHardwareInterface mHardware;
@Mock private ApplicationInfo mApplicationInfo;
@Mock private Context mContext;
+ @Mock private INetworkManagementService mNMService;
private final ArgumentCaptor<ArrayList> mStringArrayCaptor =
ArgumentCaptor.forClass(ArrayList.class);
+ private final ArgumentCaptor<ITetheringStatsProvider.Stub> mTetherStatsProviderCaptor =
+ ArgumentCaptor.forClass(ITetheringStatsProvider.Stub.class);
private MockContentResolver mContentResolver;
- @Before public void setUp() throws Exception {
+ @Before public void setUp() {
MockitoAnnotations.initMocks(this);
when(mContext.getApplicationInfo()).thenReturn(mApplicationInfo);
when(mContext.getPackageName()).thenReturn("OffloadControllerTest");
@@ -88,14 +103,23 @@
when(mHardware.initOffloadConfig()).thenReturn(true);
when(mHardware.initOffloadControl(any(OffloadHardwareInterface.ControlCallback.class)))
.thenReturn(true);
+ when(mHardware.getForwardedStats(any())).thenReturn(new ForwardedStats());
}
private void enableOffload() {
Settings.Global.putInt(mContentResolver, TETHER_OFFLOAD_DISABLED, 0);
}
+ private OffloadController makeOffloadController() throws Exception {
+ OffloadController offload = new OffloadController(new Handler(Looper.getMainLooper()),
+ mHardware, mContentResolver, mNMService, new SharedLog("test"));
+ verify(mNMService).registerTetheringStatsProvider(
+ mTetherStatsProviderCaptor.capture(), anyString());
+ return offload;
+ }
+
@Test
- public void testNoSettingsValueDefaultDisabledDoesNotStart() {
+ public void testNoSettingsValueDefaultDisabledDoesNotStart() throws Exception {
setupFunctioningHardwareInterface();
when(mHardware.getDefaultTetherOffloadDisabled()).thenReturn(1);
try {
@@ -103,8 +127,7 @@
fail();
} catch (SettingNotFoundException expected) {}
- final OffloadController offload =
- new OffloadController(null, mHardware, mContentResolver, new SharedLog("test"));
+ final OffloadController offload = makeOffloadController();
offload.start();
final InOrder inOrder = inOrder(mHardware);
@@ -116,7 +139,7 @@
}
@Test
- public void testNoSettingsValueDefaultEnabledDoesStart() {
+ public void testNoSettingsValueDefaultEnabledDoesStart() throws Exception {
setupFunctioningHardwareInterface();
when(mHardware.getDefaultTetherOffloadDisabled()).thenReturn(0);
try {
@@ -124,8 +147,7 @@
fail();
} catch (SettingNotFoundException expected) {}
- final OffloadController offload =
- new OffloadController(null, mHardware, mContentResolver, new SharedLog("test"));
+ final OffloadController offload = makeOffloadController();
offload.start();
final InOrder inOrder = inOrder(mHardware);
@@ -137,12 +159,11 @@
}
@Test
- public void testSettingsAllowsStart() {
+ public void testSettingsAllowsStart() throws Exception {
setupFunctioningHardwareInterface();
Settings.Global.putInt(mContentResolver, TETHER_OFFLOAD_DISABLED, 0);
- final OffloadController offload =
- new OffloadController(null, mHardware, mContentResolver, new SharedLog("test"));
+ final OffloadController offload = makeOffloadController();
offload.start();
final InOrder inOrder = inOrder(mHardware);
@@ -154,12 +175,11 @@
}
@Test
- public void testSettingsDisablesStart() {
+ public void testSettingsDisablesStart() throws Exception {
setupFunctioningHardwareInterface();
Settings.Global.putInt(mContentResolver, TETHER_OFFLOAD_DISABLED, 1);
- final OffloadController offload =
- new OffloadController(null, mHardware, mContentResolver, new SharedLog("test"));
+ final OffloadController offload = makeOffloadController();
offload.start();
final InOrder inOrder = inOrder(mHardware);
@@ -174,8 +194,7 @@
setupFunctioningHardwareInterface();
enableOffload();
- final OffloadController offload =
- new OffloadController(null, mHardware, mContentResolver, new SharedLog("test"));
+ final OffloadController offload = makeOffloadController();
offload.start();
final InOrder inOrder = inOrder(mHardware);
@@ -240,6 +259,7 @@
inOrder.verify(mHardware, never()).setLocalPrefixes(mStringArrayCaptor.capture());
inOrder.verify(mHardware, times(1)).setUpstreamParameters(
eq(testIfName), eq(ipv4Addr), eq(null), eq(null));
+ inOrder.verify(mHardware, times(1)).getForwardedStats(eq(testIfName));
inOrder.verifyNoMoreInteractions();
final String ipv4Gateway = "192.0.2.1";
@@ -249,6 +269,7 @@
inOrder.verify(mHardware, never()).setLocalPrefixes(mStringArrayCaptor.capture());
inOrder.verify(mHardware, times(1)).setUpstreamParameters(
eq(testIfName), eq(ipv4Addr), eq(ipv4Gateway), eq(null));
+ inOrder.verify(mHardware, times(1)).getForwardedStats(eq(testIfName));
inOrder.verifyNoMoreInteractions();
final String ipv6Gw1 = "fe80::cafe";
@@ -258,6 +279,7 @@
inOrder.verify(mHardware, never()).setLocalPrefixes(mStringArrayCaptor.capture());
inOrder.verify(mHardware, times(1)).setUpstreamParameters(
eq(testIfName), eq(ipv4Addr), eq(ipv4Gateway), mStringArrayCaptor.capture());
+ inOrder.verify(mHardware, times(1)).getForwardedStats(eq(testIfName));
ArrayList<String> v6gws = mStringArrayCaptor.getValue();
assertEquals(1, v6gws.size());
assertTrue(v6gws.contains(ipv6Gw1));
@@ -270,6 +292,7 @@
inOrder.verify(mHardware, never()).setLocalPrefixes(mStringArrayCaptor.capture());
inOrder.verify(mHardware, times(1)).setUpstreamParameters(
eq(testIfName), eq(ipv4Addr), eq(ipv4Gateway), mStringArrayCaptor.capture());
+ inOrder.verify(mHardware, times(1)).getForwardedStats(eq(testIfName));
v6gws = mStringArrayCaptor.getValue();
assertEquals(2, v6gws.size());
assertTrue(v6gws.contains(ipv6Gw1));
@@ -287,6 +310,7 @@
inOrder.verify(mHardware, never()).setLocalPrefixes(mStringArrayCaptor.capture());
inOrder.verify(mHardware, times(1)).setUpstreamParameters(
eq(testIfName), eq(ipv4Addr), eq(ipv4Gateway), mStringArrayCaptor.capture());
+ inOrder.verify(mHardware, times(1)).getForwardedStats(eq(testIfName));
v6gws = mStringArrayCaptor.getValue();
assertEquals(2, v6gws.size());
assertTrue(v6gws.contains(ipv6Gw1));
@@ -321,6 +345,7 @@
assertEquals(2, v6gws.size());
assertTrue(v6gws.contains(ipv6Gw1));
assertTrue(v6gws.contains(ipv6Gw2));
+ inOrder.verify(mHardware, times(1)).getForwardedStats(eq(testIfName));
inOrder.verifyNoMoreInteractions();
// Completely identical LinkProperties updates are de-duped.
@@ -331,4 +356,65 @@
anyObject(), anyObject(), anyObject(), anyObject());
inOrder.verifyNoMoreInteractions();
}
+
+ private void assertNetworkStats(String iface, ForwardedStats stats, NetworkStats.Entry entry) {
+ assertEquals(iface, entry.iface);
+ assertEquals(stats.rxBytes, entry.rxBytes);
+ assertEquals(stats.txBytes, entry.txBytes);
+ assertEquals(SET_DEFAULT, entry.set);
+ assertEquals(TAG_NONE, entry.tag);
+ assertEquals(UID_TETHERING, entry.uid);
+ }
+
+ @Test
+ public void testGetForwardedStats() throws Exception {
+ setupFunctioningHardwareInterface();
+ enableOffload();
+
+ final OffloadController offload = makeOffloadController();
+ offload.start();
+
+ final String ethernetIface = "eth1";
+ final String mobileIface = "rmnet_data0";
+
+ ForwardedStats ethernetStats = new ForwardedStats();
+ ethernetStats.rxBytes = 12345;
+ ethernetStats.txBytes = 54321;
+
+ ForwardedStats mobileStats = new ForwardedStats();
+ mobileStats.rxBytes = 999;
+ mobileStats.txBytes = 99999;
+
+ when(mHardware.getForwardedStats(eq(ethernetIface))).thenReturn(ethernetStats);
+ when(mHardware.getForwardedStats(eq(mobileIface))).thenReturn(mobileStats);
+
+ final LinkProperties lp = new LinkProperties();
+ lp.setInterfaceName(ethernetIface);
+ offload.setUpstreamLinkProperties(lp);
+
+ lp.setInterfaceName(mobileIface);
+ offload.setUpstreamLinkProperties(lp);
+
+ lp.setInterfaceName(ethernetIface);
+ offload.setUpstreamLinkProperties(lp);
+
+ ethernetStats.rxBytes = 100000;
+ ethernetStats.txBytes = 100000;
+ offload.setUpstreamLinkProperties(null);
+
+ NetworkStats stats = mTetherStatsProviderCaptor.getValue().getTetherStats();
+ assertEquals(2, stats.size());
+
+ NetworkStats.Entry entry = null;
+ int ethernetPosition = ethernetIface.equals(stats.getValues(0, entry).iface) ? 0 : 1;
+ int mobilePosition = 1 - ethernetPosition;
+
+ entry = stats.getValues(mobilePosition, entry);
+ assertNetworkStats(mobileIface, mobileStats, entry);
+
+ ethernetStats.rxBytes = 12345 + 100000;
+ ethernetStats.txBytes = 54321 + 100000;
+ entry = stats.getValues(ethernetPosition, entry);
+ assertNetworkStats(ethernetIface, ethernetStats, entry);
+ }
}