Merge "Additional System APIs for restricted profiles"
diff --git a/Android.bp b/Android.bp
index 5c1ccb7..2c4963c 100644
--- a/Android.bp
+++ b/Android.bp
@@ -58,6 +58,7 @@
// runtime, as well as the only protos that are actually
// needed by the device.
srcs: [
+ "core/proto/android/os/cpuinfo.proto",
"core/proto/android/os/kernelwake.proto",
"core/proto/android/os/pagetypeinfo.proto",
"core/proto/android/os/procrank.proto",
@@ -81,6 +82,7 @@
],
srcs: [
+ "core/proto/android/os/cpuinfo.proto",
"core/proto/android/os/kernelwake.proto",
"core/proto/android/os/pagetypeinfo.proto",
"core/proto/android/os/procrank.proto",
diff --git a/Android.mk b/Android.mk
index a7cb362..b847425 100644
--- a/Android.mk
+++ b/Android.mk
@@ -137,6 +137,7 @@
../../system/bt/binder/android/bluetooth/IBluetoothPbap.aidl \
../../system/bt/binder/android/bluetooth/IBluetoothPbapClient.aidl \
../../system/bt/binder/android/bluetooth/IBluetoothSap.aidl \
+ ../../system/bt/binder/android/bluetooth/IBluetoothSocketManager.aidl \
../../system/bt/binder/android/bluetooth/IBluetoothStateChangeCallback.aidl \
../../system/bt/binder/android/bluetooth/IBluetoothHeadsetClient.aidl \
../../system/bt/binder/android/bluetooth/IBluetoothHidDevice.aidl \
@@ -158,6 +159,7 @@
core/java/android/content/ISyncServiceAdapter.aidl \
core/java/android/content/ISyncStatusObserver.aidl \
core/java/android/content/om/IOverlayManager.aidl \
+ core/java/android/content/pm/crossprofile/ICrossProfileApps.aidl \
core/java/android/content/pm/IDexModuleRegisterCallback.aidl \
core/java/android/content/pm/ILauncherApps.aidl \
core/java/android/content/pm/IOnAppsChangedListener.aidl \
@@ -398,6 +400,7 @@
core/java/com/android/internal/backup/IObbBackupService.aidl \
core/java/com/android/internal/car/ICarServiceHelper.aidl \
core/java/com/android/internal/inputmethod/IInputContentUriToken.aidl \
+ core/java/com/android/internal/net/INetworkWatchlistManager.aidl \
core/java/com/android/internal/policy/IKeyguardDrawnCallback.aidl \
core/java/com/android/internal/policy/IKeyguardDismissCallback.aidl \
core/java/com/android/internal/policy/IKeyguardExitCallback.aidl \
@@ -1555,6 +1558,7 @@
LOCAL_SOURCE_FILES_ALL_GENERATED := true
LOCAL_SRC_FILES := \
tools/streaming_proto/stream.proto \
+ cmds/am/proto/instrumentation_data.proto \
$(call all-proto-files-under, core/proto) \
$(call all-proto-files-under, libs/incident/proto)
include $(BUILD_HOST_JAVA_LIBRARY)
diff --git a/PREUPLOAD.cfg b/PREUPLOAD.cfg
index 50a5974..711c12d 100644
--- a/PREUPLOAD.cfg
+++ b/PREUPLOAD.cfg
@@ -7,3 +7,4 @@
services/print/
services/usb/
+api_lint_hook = ${REPO_ROOT}/frameworks/base/tools/apilint/apilint_sha.sh ${PREUPLOAD_COMMIT}
diff --git a/api/current.txt b/api/current.txt
index 97b065e..099a46f 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -3609,11 +3609,11 @@
method public android.transition.Scene getContentScene();
method public android.transition.TransitionManager getContentTransitionManager();
method public android.view.View getCurrentFocus();
- method public android.app.FragmentManager getFragmentManager();
+ method public deprecated android.app.FragmentManager getFragmentManager();
method public android.content.Intent getIntent();
method public java.lang.Object getLastNonConfigurationInstance();
method public android.view.LayoutInflater getLayoutInflater();
- method public android.app.LoaderManager getLoaderManager();
+ method public deprecated android.app.LoaderManager getLoaderManager();
method public java.lang.String getLocalClassName();
method public int getMaxNumPictureInPictureActions();
method public final android.media.session.MediaController getMediaController();
@@ -3653,7 +3653,7 @@
method public void onActionModeStarted(android.view.ActionMode);
method public void onActivityReenter(int, android.content.Intent);
method protected void onActivityResult(int, int, android.content.Intent);
- method public void onAttachFragment(android.app.Fragment);
+ method public deprecated void onAttachFragment(android.app.Fragment);
method public void onAttachedToWindow();
method public void onBackPressed();
method protected void onChildTitleChanged(android.app.Activity, java.lang.CharSequence);
@@ -3795,8 +3795,8 @@
method public void startActivityForResult(android.content.Intent, int, android.os.Bundle);
method public void startActivityFromChild(android.app.Activity, android.content.Intent, int);
method public void startActivityFromChild(android.app.Activity, android.content.Intent, int, android.os.Bundle);
- method public void startActivityFromFragment(android.app.Fragment, android.content.Intent, int);
- method public void startActivityFromFragment(android.app.Fragment, android.content.Intent, int, android.os.Bundle);
+ method public deprecated void startActivityFromFragment(android.app.Fragment, android.content.Intent, int);
+ method public deprecated void startActivityFromFragment(android.app.Fragment, android.content.Intent, int, android.os.Bundle);
method public boolean startActivityIfNeeded(android.content.Intent, int);
method public boolean startActivityIfNeeded(android.content.Intent, int, android.os.Bundle);
method public void startIntentSenderForResult(android.content.IntentSender, int, android.content.Intent, int, int, int) throws android.content.IntentSender.SendIntentException;
@@ -4455,7 +4455,7 @@
method public void unregisterForContextMenu(android.view.View);
}
- public class DialogFragment extends android.app.Fragment implements android.content.DialogInterface.OnCancelListener android.content.DialogInterface.OnDismissListener {
+ public deprecated class DialogFragment extends android.app.Fragment implements android.content.DialogInterface.OnCancelListener android.content.DialogInterface.OnDismissListener {
ctor public DialogFragment();
method public void dismiss();
method public void dismissAllowingStateLoss();
@@ -4573,7 +4573,7 @@
method public void setSelectedGroup(int);
}
- public class Fragment implements android.content.ComponentCallbacks2 android.view.View.OnCreateContextMenuListener {
+ public deprecated class Fragment implements android.content.ComponentCallbacks2 android.view.View.OnCreateContextMenuListener {
ctor public Fragment();
method public void dump(java.lang.String, java.io.FileDescriptor, java.io.PrintWriter, java.lang.String[]);
method public final boolean equals(java.lang.Object);
@@ -4589,7 +4589,7 @@
method public final java.lang.Object getHost();
method public final int getId();
method public final android.view.LayoutInflater getLayoutInflater();
- method public android.app.LoaderManager getLoaderManager();
+ method public deprecated android.app.LoaderManager getLoaderManager();
method public final android.app.Fragment getParentFragment();
method public android.transition.Transition getReenterTransition();
method public final android.content.res.Resources getResources();
@@ -4684,11 +4684,11 @@
method public void unregisterForContextMenu(android.view.View);
}
- public static class Fragment.InstantiationException extends android.util.AndroidRuntimeException {
+ public static deprecated class Fragment.InstantiationException extends android.util.AndroidRuntimeException {
ctor public Fragment.InstantiationException(java.lang.String, java.lang.Exception);
}
- public static class Fragment.SavedState implements android.os.Parcelable {
+ public static deprecated class Fragment.SavedState implements android.os.Parcelable {
method public int describeContents();
method public void writeToParcel(android.os.Parcel, int);
field public static final android.os.Parcelable.ClassLoaderCreator<android.app.Fragment.SavedState> CREATOR;
@@ -4707,17 +4707,17 @@
method public void setTitle(java.lang.CharSequence, java.lang.CharSequence);
}
- public static abstract interface FragmentBreadCrumbs.OnBreadCrumbClickListener {
+ public static abstract deprecated interface FragmentBreadCrumbs.OnBreadCrumbClickListener {
method public abstract boolean onBreadCrumbClick(android.app.FragmentManager.BackStackEntry, int);
}
- public abstract class FragmentContainer {
+ public abstract deprecated class FragmentContainer {
ctor public FragmentContainer();
method public abstract <T extends android.view.View> T onFindViewById(int);
method public abstract boolean onHasView();
}
- public class FragmentController {
+ public deprecated class FragmentController {
method public void attachHost(android.app.Fragment);
method public static final android.app.FragmentController createController(android.app.FragmentHostCallback<?>);
method public void dispatchActivityCreated();
@@ -4760,7 +4760,7 @@
method public android.os.Parcelable saveAllState();
}
- public abstract class FragmentHostCallback<E> extends android.app.FragmentContainer {
+ public abstract deprecated class FragmentHostCallback<E> extends android.app.FragmentContainer {
ctor public FragmentHostCallback(android.content.Context, android.os.Handler, int);
method public void onAttachFragment(android.app.Fragment);
method public void onDump(java.lang.String, java.io.FileDescriptor, java.io.PrintWriter, java.lang.String[]);
@@ -4778,7 +4778,7 @@
method public boolean onUseFragmentManagerInflaterFactory();
}
- public abstract class FragmentManager {
+ public abstract deprecated class FragmentManager {
ctor public FragmentManager();
method public abstract void addOnBackStackChangedListener(android.app.FragmentManager.OnBackStackChangedListener);
method public abstract android.app.FragmentTransaction beginTransaction();
@@ -4809,7 +4809,7 @@
field public static final int POP_BACK_STACK_INCLUSIVE = 1; // 0x1
}
- public static abstract interface FragmentManager.BackStackEntry {
+ public static abstract deprecated interface FragmentManager.BackStackEntry {
method public abstract java.lang.CharSequence getBreadCrumbShortTitle();
method public abstract int getBreadCrumbShortTitleRes();
method public abstract java.lang.CharSequence getBreadCrumbTitle();
@@ -4818,7 +4818,7 @@
method public abstract java.lang.String getName();
}
- public static abstract class FragmentManager.FragmentLifecycleCallbacks {
+ public static abstract deprecated class FragmentManager.FragmentLifecycleCallbacks {
ctor public FragmentManager.FragmentLifecycleCallbacks();
method public void onFragmentActivityCreated(android.app.FragmentManager, android.app.Fragment, android.os.Bundle);
method public void onFragmentAttached(android.app.FragmentManager, android.app.Fragment, android.content.Context);
@@ -4836,14 +4836,14 @@
method public void onFragmentViewDestroyed(android.app.FragmentManager, android.app.Fragment);
}
- public static abstract interface FragmentManager.OnBackStackChangedListener {
+ public static abstract deprecated interface FragmentManager.OnBackStackChangedListener {
method public abstract void onBackStackChanged();
}
- public class FragmentManagerNonConfig {
+ public deprecated class FragmentManagerNonConfig {
}
- public abstract class FragmentTransaction {
+ public abstract deprecated class FragmentTransaction {
ctor public FragmentTransaction();
method public abstract android.app.FragmentTransaction add(android.app.Fragment, java.lang.String);
method public abstract android.app.FragmentTransaction add(int, android.app.Fragment);
@@ -5048,7 +5048,7 @@
method public void setSelection(int);
}
- public class ListFragment extends android.app.Fragment {
+ public deprecated class ListFragment extends android.app.Fragment {
ctor public ListFragment();
method public android.widget.ListAdapter getListAdapter();
method public android.widget.ListView getListView();
@@ -5062,7 +5062,7 @@
method public void setSelection(int);
}
- public abstract class LoaderManager {
+ public abstract deprecated class LoaderManager {
ctor public LoaderManager();
method public abstract void destroyLoader(int);
method public abstract void dump(java.lang.String, java.io.FileDescriptor, java.io.PrintWriter, java.lang.String[]);
@@ -5072,7 +5072,7 @@
method public abstract <D> android.content.Loader<D> restartLoader(int, android.os.Bundle, android.app.LoaderManager.LoaderCallbacks<D>);
}
- public static abstract interface LoaderManager.LoaderCallbacks<D> {
+ public static abstract deprecated interface LoaderManager.LoaderCallbacks<D> {
method public abstract android.content.Loader<D> onCreateLoader(int, android.os.Bundle);
method public abstract void onLoadFinished(android.content.Loader<D>, D);
method public abstract void onLoaderReset(android.content.Loader<D>);
@@ -6847,7 +6847,8 @@
method public static final long getMinFlexMillis();
method public long getMinLatencyMillis();
method public static final long getMinPeriodMillis();
- method public int getNetworkType();
+ method public deprecated int getNetworkType();
+ method public android.net.NetworkRequest getRequiredNetwork();
method public android.content.ComponentName getService();
method public android.os.Bundle getTransientExtras();
method public long getTriggerContentMaxDelay();
@@ -6867,7 +6868,8 @@
field public static final long MAX_BACKOFF_DELAY_MILLIS = 18000000L; // 0x112a880L
field public static final int NETWORK_BYTES_UNKNOWN = -1; // 0xffffffff
field public static final int NETWORK_TYPE_ANY = 1; // 0x1
- field public static final int NETWORK_TYPE_METERED = 4; // 0x4
+ field public static final int NETWORK_TYPE_CELLULAR = 4; // 0x4
+ field public static final deprecated int NETWORK_TYPE_METERED = 4; // 0x4
field public static final int NETWORK_TYPE_NONE = 0; // 0x0
field public static final int NETWORK_TYPE_NOT_ROAMING = 3; // 0x3
field public static final int NETWORK_TYPE_UNMETERED = 2; // 0x2
@@ -6886,6 +6888,7 @@
method public android.app.job.JobInfo.Builder setPeriodic(long);
method public android.app.job.JobInfo.Builder setPeriodic(long, long);
method public android.app.job.JobInfo.Builder setPersisted(boolean);
+ method public android.app.job.JobInfo.Builder setRequiredNetwork(android.net.NetworkRequest);
method public android.app.job.JobInfo.Builder setRequiredNetworkType(int);
method public android.app.job.JobInfo.Builder setRequiresBatteryNotLow(boolean);
method public android.app.job.JobInfo.Builder setRequiresCharging(boolean);
@@ -8529,7 +8532,7 @@
ctor public AsyncQueryHandler.WorkerHandler(android.os.Looper);
}
- public abstract class AsyncTaskLoader<D> extends android.content.Loader {
+ public abstract deprecated class AsyncTaskLoader<D> extends android.content.Loader {
ctor public AsyncTaskLoader(android.content.Context);
method public void cancelLoadInBackground();
method public boolean isLoadInBackgroundCanceled();
@@ -9102,6 +9105,7 @@
field public static final int CONTEXT_IGNORE_SECURITY = 2; // 0x2
field public static final int CONTEXT_INCLUDE_CODE = 1; // 0x1
field public static final int CONTEXT_RESTRICTED = 4; // 0x4
+ field public static final java.lang.String CROSS_PROFILE_APPS_SERVICE = "crossprofileapps";
field public static final java.lang.String DEVICE_POLICY_SERVICE = "device_policy";
field public static final java.lang.String DISPLAY_SERVICE = "display";
field public static final java.lang.String DOWNLOAD_SERVICE = "download";
@@ -9268,7 +9272,7 @@
method public void unregisterReceiver(android.content.BroadcastReceiver);
}
- public class CursorLoader extends android.content.AsyncTaskLoader {
+ public deprecated class CursorLoader extends android.content.AsyncTaskLoader {
ctor public CursorLoader(android.content.Context);
ctor public CursorLoader(android.content.Context, android.net.Uri, java.lang.String[], java.lang.String, java.lang.String[], java.lang.String);
method public void deliverResult(android.database.Cursor);
@@ -9874,7 +9878,7 @@
ctor public IntentSender.SendIntentException(java.lang.Exception);
}
- public class Loader<D> {
+ public deprecated class Loader<D> {
ctor public Loader(android.content.Context);
method public void abandon();
method public boolean cancelLoad();
@@ -9907,15 +9911,15 @@
method public void unregisterOnLoadCanceledListener(android.content.Loader.OnLoadCanceledListener<D>);
}
- public final class Loader.ForceLoadContentObserver extends android.database.ContentObserver {
+ public final deprecated class Loader.ForceLoadContentObserver extends android.database.ContentObserver {
ctor public Loader.ForceLoadContentObserver();
}
- public static abstract interface Loader.OnLoadCanceledListener<D> {
+ public static abstract deprecated interface Loader.OnLoadCanceledListener<D> {
method public abstract void onLoadCanceled(android.content.Loader<D>);
}
- public static abstract interface Loader.OnLoadCompleteListener<D> {
+ public static abstract deprecated interface Loader.OnLoadCompleteListener<D> {
method public abstract void onLoadComplete(android.content.Loader<D>, D);
}
@@ -11223,6 +11227,15 @@
}
+package android.content.pm.crossprofile {
+
+ public class CrossProfileApps {
+ method public java.util.List<android.os.UserHandle> getTargetUserProfiles();
+ method public void startMainActivity(android.content.ComponentName, android.os.UserHandle, android.graphics.Rect, android.os.Bundle);
+ }
+
+}
+
package android.content.res {
public class AssetFileDescriptor implements java.io.Closeable android.os.Parcelable {
@@ -18273,11 +18286,13 @@
method public synchronized void applyLocalizedPattern(java.lang.String);
method public synchronized void applyPattern(java.lang.String);
method public synchronized boolean areSignificantDigitsUsed();
+ method public synchronized boolean equals(java.lang.Object);
method public java.lang.StringBuffer format(double, java.lang.StringBuffer, java.text.FieldPosition);
method public java.lang.StringBuffer format(long, java.lang.StringBuffer, java.text.FieldPosition);
method public java.lang.StringBuffer format(java.math.BigInteger, java.lang.StringBuffer, java.text.FieldPosition);
method public java.lang.StringBuffer format(java.math.BigDecimal, java.lang.StringBuffer, java.text.FieldPosition);
method public java.lang.StringBuffer format(android.icu.math.BigDecimal, java.lang.StringBuffer, java.text.FieldPosition);
+ method public synchronized android.icu.util.Currency getCurrency();
method public synchronized android.icu.text.CurrencyPluralInfo getCurrencyPluralInfo();
method public synchronized android.icu.util.Currency.CurrencyUsage getCurrencyUsage();
method public synchronized android.icu.text.DecimalFormatSymbols getDecimalFormatSymbols();
@@ -18285,8 +18300,12 @@
method public synchronized int getGroupingSize();
method public synchronized java.math.MathContext getMathContext();
method public synchronized android.icu.math.MathContext getMathContextICU();
+ method public synchronized int getMaximumFractionDigits();
+ method public synchronized int getMaximumIntegerDigits();
method public synchronized int getMaximumSignificantDigits();
method public synchronized byte getMinimumExponentDigits();
+ method public synchronized int getMinimumFractionDigits();
+ method public synchronized int getMinimumIntegerDigits();
method public synchronized int getMinimumSignificantDigits();
method public synchronized int getMultiplier();
method public synchronized java.lang.String getNegativePrefix();
@@ -18297,13 +18316,19 @@
method public synchronized java.lang.String getPositivePrefix();
method public synchronized java.lang.String getPositiveSuffix();
method public synchronized java.math.BigDecimal getRoundingIncrement();
+ method public synchronized int getRoundingMode();
method public synchronized int getSecondaryGroupingSize();
+ method public synchronized int hashCode();
method public synchronized boolean isDecimalPatternMatchRequired();
method public synchronized boolean isDecimalSeparatorAlwaysShown();
method public synchronized boolean isExponentSignAlwaysShown();
+ method public synchronized boolean isGroupingUsed();
method public synchronized boolean isParseBigDecimal();
+ method public synchronized boolean isParseIntegerOnly();
+ method public synchronized boolean isParseStrict();
method public synchronized boolean isScientificNotation();
method public java.lang.Number parse(java.lang.String, java.text.ParsePosition);
+ method public synchronized void setCurrency(android.icu.util.Currency);
method public synchronized void setCurrencyPluralInfo(android.icu.text.CurrencyPluralInfo);
method public synchronized void setCurrencyUsage(android.icu.util.Currency.CurrencyUsage);
method public synchronized void setDecimalFormatSymbols(android.icu.text.DecimalFormatSymbols);
@@ -18312,10 +18337,15 @@
method public synchronized void setExponentSignAlwaysShown(boolean);
method public synchronized void setFormatWidth(int);
method public synchronized void setGroupingSize(int);
+ method public synchronized void setGroupingUsed(boolean);
method public synchronized void setMathContext(java.math.MathContext);
method public synchronized void setMathContextICU(android.icu.math.MathContext);
+ method public synchronized void setMaximumFractionDigits(int);
+ method public synchronized void setMaximumIntegerDigits(int);
method public synchronized void setMaximumSignificantDigits(int);
method public synchronized void setMinimumExponentDigits(byte);
+ method public synchronized void setMinimumFractionDigits(int);
+ method public synchronized void setMinimumIntegerDigits(int);
method public synchronized void setMinimumSignificantDigits(int);
method public synchronized void setMultiplier(int);
method public synchronized void setNegativePrefix(java.lang.String);
@@ -18323,12 +18353,15 @@
method public synchronized void setPadCharacter(char);
method public synchronized void setPadPosition(int);
method public synchronized void setParseBigDecimal(boolean);
+ method public synchronized void setParseIntegerOnly(boolean);
method public deprecated void setParseMaxDigits(int);
+ method public synchronized void setParseStrict(boolean);
method public synchronized void setPositivePrefix(java.lang.String);
method public synchronized void setPositiveSuffix(java.lang.String);
method public synchronized void setRoundingIncrement(java.math.BigDecimal);
method public synchronized void setRoundingIncrement(android.icu.math.BigDecimal);
method public synchronized void setRoundingIncrement(double);
+ method public synchronized void setRoundingMode(int);
method public synchronized void setScientificNotation(boolean);
method public synchronized void setSecondaryGroupingSize(int);
method public synchronized void setSignificantDigitsUsed(boolean);
@@ -31817,6 +31850,14 @@
method public static void setVmPolicy(android.os.StrictMode.VmPolicy);
}
+ public static abstract interface StrictMode.OnThreadViolationListener {
+ method public abstract void onThreadViolation(android.os.strictmode.Violation);
+ }
+
+ public static abstract interface StrictMode.OnVmViolationListener {
+ method public abstract void onVmViolation(android.os.strictmode.Violation);
+ }
+
public static final class StrictMode.ThreadPolicy {
field public static final android.os.StrictMode.ThreadPolicy LAX;
}
@@ -31837,6 +31878,7 @@
method public android.os.StrictMode.ThreadPolicy.Builder penaltyDialog();
method public android.os.StrictMode.ThreadPolicy.Builder penaltyDropBox();
method public android.os.StrictMode.ThreadPolicy.Builder penaltyFlashScreen();
+ method public android.os.StrictMode.ThreadPolicy.Builder penaltyListener(android.os.StrictMode.OnThreadViolationListener, java.util.concurrent.Executor);
method public android.os.StrictMode.ThreadPolicy.Builder penaltyLog();
method public android.os.StrictMode.ThreadPolicy.Builder permitAll();
method public android.os.StrictMode.ThreadPolicy.Builder permitCustomSlowCalls();
@@ -31868,6 +31910,7 @@
method public android.os.StrictMode.VmPolicy.Builder penaltyDeathOnCleartextNetwork();
method public android.os.StrictMode.VmPolicy.Builder penaltyDeathOnFileUriExposure();
method public android.os.StrictMode.VmPolicy.Builder penaltyDropBox();
+ method public android.os.StrictMode.VmPolicy.Builder penaltyListener(android.os.StrictMode.OnVmViolationListener, java.util.concurrent.Executor);
method public android.os.StrictMode.VmPolicy.Builder penaltyLog();
method public android.os.StrictMode.VmPolicy.Builder setClassInstanceLimit(java.lang.Class, int);
}
@@ -31875,10 +31918,12 @@
public final class SystemClock {
method public static long currentThreadTimeMillis();
method public static long elapsedRealtime();
+ method public static java.time.Clock elapsedRealtimeClock();
method public static long elapsedRealtimeNanos();
method public static boolean setCurrentTimeMillis(long);
method public static void sleep(long);
method public static long uptimeMillis();
+ method public static java.time.Clock uptimeMillisClock();
}
public class TestLooperManager {
@@ -31960,6 +32005,7 @@
field public static final java.lang.String DISALLOW_CONFIG_CELL_BROADCASTS = "no_config_cell_broadcasts";
field public static final java.lang.String DISALLOW_CONFIG_CREDENTIALS = "no_config_credentials";
field public static final java.lang.String DISALLOW_CONFIG_DATE_TIME = "no_config_date_time";
+ field public static final java.lang.String DISALLOW_CONFIG_LOCALE = "no_config_locale";
field public static final java.lang.String DISALLOW_CONFIG_MOBILE_NETWORKS = "no_config_mobile_networks";
field public static final java.lang.String DISALLOW_CONFIG_TETHERING = "no_config_tethering";
field public static final java.lang.String DISALLOW_CONFIG_VPN = "no_config_vpn";
@@ -32229,6 +32275,62 @@
}
+package android.os.strictmode {
+
+ public final class CleartextNetworkViolation extends android.os.strictmode.Violation {
+ }
+
+ public final class ContentUriWithoutPermissionViolation extends android.os.strictmode.Violation {
+ }
+
+ public final class CustomViolation extends android.os.strictmode.Violation {
+ }
+
+ public final class DiskReadViolation extends android.os.strictmode.Violation {
+ }
+
+ public final class DiskWriteViolation extends android.os.strictmode.Violation {
+ }
+
+ public final class FileUriExposedViolation extends android.os.strictmode.Violation {
+ }
+
+ public class InstanceCountViolation extends android.os.strictmode.Violation {
+ method public long getNumberOfInstances();
+ }
+
+ public final class IntentReceiverLeakedViolation extends android.os.strictmode.Violation {
+ }
+
+ public final class LeakedClosableViolation extends android.os.strictmode.Violation {
+ }
+
+ public final class NetworkViolation extends android.os.strictmode.Violation {
+ }
+
+ public final class ResourceMismatchViolation extends android.os.strictmode.Violation {
+ }
+
+ public final class ServiceConnectionLeakedViolation extends android.os.strictmode.Violation {
+ }
+
+ public final class SqliteObjectLeakedViolation extends android.os.strictmode.Violation {
+ }
+
+ public final class UnbufferedIoViolation extends android.os.strictmode.Violation {
+ }
+
+ public final class UntaggedSocketViolation extends android.os.strictmode.Violation {
+ }
+
+ public abstract class Violation extends java.lang.Throwable {
+ }
+
+ public final class WebViewMethodCalledOnWrongThreadViolation extends android.os.strictmode.Violation {
+ }
+
+}
+
package android.preference {
public class CheckBoxPreference extends android.preference.TwoStatePreference {
@@ -32515,7 +32617,7 @@
method public default void putStringSet(java.lang.String, java.util.Set<java.lang.String>);
}
- public abstract class PreferenceFragment extends android.app.Fragment {
+ public abstract deprecated class PreferenceFragment extends android.app.Fragment {
ctor public PreferenceFragment();
method public void addPreferencesFromIntent(android.content.Intent);
method public void addPreferencesFromResource(int);
@@ -32526,7 +32628,7 @@
method public void setPreferenceScreen(android.preference.PreferenceScreen);
}
- public static abstract interface PreferenceFragment.OnPreferenceStartFragmentCallback {
+ public static abstract deprecated interface PreferenceFragment.OnPreferenceStartFragmentCallback {
method public abstract boolean onPreferenceStartFragment(android.preference.PreferenceFragment, android.preference.Preference);
}
@@ -39233,7 +39335,9 @@
ctor public CallAudioState(boolean, int, int);
method public static java.lang.String audioRouteToString(int);
method public int describeContents();
+ method public android.bluetooth.BluetoothDevice getActiveBluetoothDevice();
method public int getRoute();
+ method public java.util.Collection<android.bluetooth.BluetoothDevice> getSupportedBluetoothDevices();
method public int getSupportedRouteMask();
method public boolean isMuted();
method public void writeToParcel(android.os.Parcel, int);
@@ -39365,6 +39469,7 @@
method public final void putExtras(android.os.Bundle);
method public final void removeExtras(java.util.List<java.lang.String>);
method public final void removeExtras(java.lang.String...);
+ method public void requestBluetoothAudio(java.lang.String);
method public void sendConnectionEvent(java.lang.String, android.os.Bundle);
method public final void setActive();
method public final void setAddress(android.net.Uri, int);
@@ -39555,6 +39660,7 @@
method public void onCanAddCallChanged(boolean);
method public void onConnectionEvent(android.telecom.Call, java.lang.String, android.os.Bundle);
method public void onSilenceRinger();
+ method public final void requestBluetoothAudio(java.lang.String);
method public final void setAudioRoute(int);
method public final void setMuted(boolean);
field public static final java.lang.String SERVICE_INTERFACE = "android.telecom.InCallService";
@@ -49877,7 +49983,7 @@
method public abstract void setHttpAuthUsernamePassword(java.lang.String, java.lang.String, java.lang.String, java.lang.String);
}
- public class WebViewFragment extends android.app.Fragment {
+ public deprecated class WebViewFragment extends android.app.Fragment {
ctor public WebViewFragment();
method public android.webkit.WebView getWebView();
}
diff --git a/api/system-current.txt b/api/system-current.txt
index dec3726..3a5abd1 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -3754,11 +3754,11 @@
method public android.transition.Scene getContentScene();
method public android.transition.TransitionManager getContentTransitionManager();
method public android.view.View getCurrentFocus();
- method public android.app.FragmentManager getFragmentManager();
+ method public deprecated android.app.FragmentManager getFragmentManager();
method public android.content.Intent getIntent();
method public java.lang.Object getLastNonConfigurationInstance();
method public android.view.LayoutInflater getLayoutInflater();
- method public android.app.LoaderManager getLoaderManager();
+ method public deprecated android.app.LoaderManager getLoaderManager();
method public java.lang.String getLocalClassName();
method public int getMaxNumPictureInPictureActions();
method public final android.media.session.MediaController getMediaController();
@@ -3799,7 +3799,7 @@
method public void onActionModeStarted(android.view.ActionMode);
method public void onActivityReenter(int, android.content.Intent);
method protected void onActivityResult(int, int, android.content.Intent);
- method public void onAttachFragment(android.app.Fragment);
+ method public deprecated void onAttachFragment(android.app.Fragment);
method public void onAttachedToWindow();
method public void onBackPressed();
method public deprecated void onBackgroundVisibleBehindChanged(boolean);
@@ -3943,8 +3943,8 @@
method public void startActivityForResult(android.content.Intent, int, android.os.Bundle);
method public void startActivityFromChild(android.app.Activity, android.content.Intent, int);
method public void startActivityFromChild(android.app.Activity, android.content.Intent, int, android.os.Bundle);
- method public void startActivityFromFragment(android.app.Fragment, android.content.Intent, int);
- method public void startActivityFromFragment(android.app.Fragment, android.content.Intent, int, android.os.Bundle);
+ method public deprecated void startActivityFromFragment(android.app.Fragment, android.content.Intent, int);
+ method public deprecated void startActivityFromFragment(android.app.Fragment, android.content.Intent, int, android.os.Bundle);
method public boolean startActivityIfNeeded(android.content.Intent, int);
method public boolean startActivityIfNeeded(android.content.Intent, int, android.os.Bundle);
method public void startIntentSenderForResult(android.content.IntentSender, int, android.content.Intent, int, int, int) throws android.content.IntentSender.SendIntentException;
@@ -4628,7 +4628,7 @@
method public void unregisterForContextMenu(android.view.View);
}
- public class DialogFragment extends android.app.Fragment implements android.content.DialogInterface.OnCancelListener android.content.DialogInterface.OnDismissListener {
+ public deprecated class DialogFragment extends android.app.Fragment implements android.content.DialogInterface.OnCancelListener android.content.DialogInterface.OnDismissListener {
ctor public DialogFragment();
method public void dismiss();
method public void dismissAllowingStateLoss();
@@ -4747,7 +4747,7 @@
method public void setSelectedGroup(int);
}
- public class Fragment implements android.content.ComponentCallbacks2 android.view.View.OnCreateContextMenuListener {
+ public deprecated class Fragment implements android.content.ComponentCallbacks2 android.view.View.OnCreateContextMenuListener {
ctor public Fragment();
method public void dump(java.lang.String, java.io.FileDescriptor, java.io.PrintWriter, java.lang.String[]);
method public final boolean equals(java.lang.Object);
@@ -4763,7 +4763,7 @@
method public final java.lang.Object getHost();
method public final int getId();
method public final android.view.LayoutInflater getLayoutInflater();
- method public android.app.LoaderManager getLoaderManager();
+ method public deprecated android.app.LoaderManager getLoaderManager();
method public final android.app.Fragment getParentFragment();
method public android.transition.Transition getReenterTransition();
method public final android.content.res.Resources getResources();
@@ -4858,11 +4858,11 @@
method public void unregisterForContextMenu(android.view.View);
}
- public static class Fragment.InstantiationException extends android.util.AndroidRuntimeException {
+ public static deprecated class Fragment.InstantiationException extends android.util.AndroidRuntimeException {
ctor public Fragment.InstantiationException(java.lang.String, java.lang.Exception);
}
- public static class Fragment.SavedState implements android.os.Parcelable {
+ public static deprecated class Fragment.SavedState implements android.os.Parcelable {
method public int describeContents();
method public void writeToParcel(android.os.Parcel, int);
field public static final android.os.Parcelable.ClassLoaderCreator<android.app.Fragment.SavedState> CREATOR;
@@ -4881,17 +4881,17 @@
method public void setTitle(java.lang.CharSequence, java.lang.CharSequence);
}
- public static abstract interface FragmentBreadCrumbs.OnBreadCrumbClickListener {
+ public static abstract deprecated interface FragmentBreadCrumbs.OnBreadCrumbClickListener {
method public abstract boolean onBreadCrumbClick(android.app.FragmentManager.BackStackEntry, int);
}
- public abstract class FragmentContainer {
+ public abstract deprecated class FragmentContainer {
ctor public FragmentContainer();
method public abstract <T extends android.view.View> T onFindViewById(int);
method public abstract boolean onHasView();
}
- public class FragmentController {
+ public deprecated class FragmentController {
method public void attachHost(android.app.Fragment);
method public static final android.app.FragmentController createController(android.app.FragmentHostCallback<?>);
method public void dispatchActivityCreated();
@@ -4934,7 +4934,7 @@
method public android.os.Parcelable saveAllState();
}
- public abstract class FragmentHostCallback<E> extends android.app.FragmentContainer {
+ public abstract deprecated class FragmentHostCallback<E> extends android.app.FragmentContainer {
ctor public FragmentHostCallback(android.content.Context, android.os.Handler, int);
method public void onAttachFragment(android.app.Fragment);
method public void onDump(java.lang.String, java.io.FileDescriptor, java.io.PrintWriter, java.lang.String[]);
@@ -4952,7 +4952,7 @@
method public boolean onUseFragmentManagerInflaterFactory();
}
- public abstract class FragmentManager {
+ public abstract deprecated class FragmentManager {
ctor public FragmentManager();
method public abstract void addOnBackStackChangedListener(android.app.FragmentManager.OnBackStackChangedListener);
method public abstract android.app.FragmentTransaction beginTransaction();
@@ -4983,7 +4983,7 @@
field public static final int POP_BACK_STACK_INCLUSIVE = 1; // 0x1
}
- public static abstract interface FragmentManager.BackStackEntry {
+ public static abstract deprecated interface FragmentManager.BackStackEntry {
method public abstract java.lang.CharSequence getBreadCrumbShortTitle();
method public abstract int getBreadCrumbShortTitleRes();
method public abstract java.lang.CharSequence getBreadCrumbTitle();
@@ -4992,7 +4992,7 @@
method public abstract java.lang.String getName();
}
- public static abstract class FragmentManager.FragmentLifecycleCallbacks {
+ public static abstract deprecated class FragmentManager.FragmentLifecycleCallbacks {
ctor public FragmentManager.FragmentLifecycleCallbacks();
method public void onFragmentActivityCreated(android.app.FragmentManager, android.app.Fragment, android.os.Bundle);
method public void onFragmentAttached(android.app.FragmentManager, android.app.Fragment, android.content.Context);
@@ -5010,14 +5010,14 @@
method public void onFragmentViewDestroyed(android.app.FragmentManager, android.app.Fragment);
}
- public static abstract interface FragmentManager.OnBackStackChangedListener {
+ public static abstract deprecated interface FragmentManager.OnBackStackChangedListener {
method public abstract void onBackStackChanged();
}
- public class FragmentManagerNonConfig {
+ public deprecated class FragmentManagerNonConfig {
}
- public abstract class FragmentTransaction {
+ public abstract deprecated class FragmentTransaction {
ctor public FragmentTransaction();
method public abstract android.app.FragmentTransaction add(android.app.Fragment, java.lang.String);
method public abstract android.app.FragmentTransaction add(int, android.app.Fragment);
@@ -5234,7 +5234,7 @@
method public void setSelection(int);
}
- public class ListFragment extends android.app.Fragment {
+ public deprecated class ListFragment extends android.app.Fragment {
ctor public ListFragment();
method public android.widget.ListAdapter getListAdapter();
method public android.widget.ListView getListView();
@@ -5248,7 +5248,7 @@
method public void setSelection(int);
}
- public abstract class LoaderManager {
+ public abstract deprecated class LoaderManager {
ctor public LoaderManager();
method public abstract void destroyLoader(int);
method public abstract void dump(java.lang.String, java.io.FileDescriptor, java.io.PrintWriter, java.lang.String[]);
@@ -5258,7 +5258,7 @@
method public abstract <D> android.content.Loader<D> restartLoader(int, android.os.Bundle, android.app.LoaderManager.LoaderCallbacks<D>);
}
- public static abstract interface LoaderManager.LoaderCallbacks<D> {
+ public static abstract deprecated interface LoaderManager.LoaderCallbacks<D> {
method public abstract android.content.Loader<D> onCreateLoader(int, android.os.Bundle);
method public abstract void onLoadFinished(android.content.Loader<D>, D);
method public abstract void onLoaderReset(android.content.Loader<D>);
@@ -7290,7 +7290,8 @@
method public static final long getMinFlexMillis();
method public long getMinLatencyMillis();
method public static final long getMinPeriodMillis();
- method public int getNetworkType();
+ method public deprecated int getNetworkType();
+ method public android.net.NetworkRequest getRequiredNetwork();
method public android.content.ComponentName getService();
method public android.os.Bundle getTransientExtras();
method public long getTriggerContentMaxDelay();
@@ -7310,7 +7311,8 @@
field public static final long MAX_BACKOFF_DELAY_MILLIS = 18000000L; // 0x112a880L
field public static final int NETWORK_BYTES_UNKNOWN = -1; // 0xffffffff
field public static final int NETWORK_TYPE_ANY = 1; // 0x1
- field public static final int NETWORK_TYPE_METERED = 4; // 0x4
+ field public static final int NETWORK_TYPE_CELLULAR = 4; // 0x4
+ field public static final deprecated int NETWORK_TYPE_METERED = 4; // 0x4
field public static final int NETWORK_TYPE_NONE = 0; // 0x0
field public static final int NETWORK_TYPE_NOT_ROAMING = 3; // 0x3
field public static final int NETWORK_TYPE_UNMETERED = 2; // 0x2
@@ -7329,6 +7331,7 @@
method public android.app.job.JobInfo.Builder setPeriodic(long);
method public android.app.job.JobInfo.Builder setPeriodic(long, long);
method public android.app.job.JobInfo.Builder setPersisted(boolean);
+ method public android.app.job.JobInfo.Builder setRequiredNetwork(android.net.NetworkRequest);
method public android.app.job.JobInfo.Builder setRequiredNetworkType(int);
method public android.app.job.JobInfo.Builder setRequiresBatteryNotLow(boolean);
method public android.app.job.JobInfo.Builder setRequiresCharging(boolean);
@@ -9041,7 +9044,7 @@
ctor public AsyncQueryHandler.WorkerHandler(android.os.Looper);
}
- public abstract class AsyncTaskLoader<D> extends android.content.Loader {
+ public abstract deprecated class AsyncTaskLoader<D> extends android.content.Loader {
ctor public AsyncTaskLoader(android.content.Context);
method public void cancelLoadInBackground();
method public boolean isLoadInBackgroundCanceled();
@@ -9623,6 +9626,7 @@
field public static final int CONTEXT_IGNORE_SECURITY = 2; // 0x2
field public static final int CONTEXT_INCLUDE_CODE = 1; // 0x1
field public static final int CONTEXT_RESTRICTED = 4; // 0x4
+ field public static final java.lang.String CROSS_PROFILE_APPS_SERVICE = "crossprofileapps";
field public static final java.lang.String DEVICE_POLICY_SERVICE = "device_policy";
field public static final java.lang.String DISPLAY_SERVICE = "display";
field public static final java.lang.String DOWNLOAD_SERVICE = "download";
@@ -9802,7 +9806,7 @@
method public void unregisterReceiver(android.content.BroadcastReceiver);
}
- public class CursorLoader extends android.content.AsyncTaskLoader {
+ public deprecated class CursorLoader extends android.content.AsyncTaskLoader {
ctor public CursorLoader(android.content.Context);
ctor public CursorLoader(android.content.Context, android.net.Uri, java.lang.String[], java.lang.String, java.lang.String[], java.lang.String);
method public void deliverResult(android.database.Cursor);
@@ -10437,7 +10441,7 @@
ctor public IntentSender.SendIntentException(java.lang.Exception);
}
- public class Loader<D> {
+ public deprecated class Loader<D> {
ctor public Loader(android.content.Context);
method public void abandon();
method public boolean cancelLoad();
@@ -10470,15 +10474,15 @@
method public void unregisterOnLoadCanceledListener(android.content.Loader.OnLoadCanceledListener<D>);
}
- public final class Loader.ForceLoadContentObserver extends android.database.ContentObserver {
+ public final deprecated class Loader.ForceLoadContentObserver extends android.database.ContentObserver {
ctor public Loader.ForceLoadContentObserver();
}
- public static abstract interface Loader.OnLoadCanceledListener<D> {
+ public static abstract deprecated interface Loader.OnLoadCanceledListener<D> {
method public abstract void onLoadCanceled(android.content.Loader<D>);
}
- public static abstract interface Loader.OnLoadCompleteListener<D> {
+ public static abstract deprecated interface Loader.OnLoadCompleteListener<D> {
method public abstract void onLoadComplete(android.content.Loader<D>, D);
}
@@ -11956,6 +11960,15 @@
}
+package android.content.pm.crossprofile {
+
+ public class CrossProfileApps {
+ method public java.util.List<android.os.UserHandle> getTargetUserProfiles();
+ method public void startMainActivity(android.content.ComponentName, android.os.UserHandle, android.graphics.Rect, android.os.Bundle);
+ }
+
+}
+
package android.content.pm.permission {
public final class RuntimePermissionPresentationInfo implements android.os.Parcelable {
@@ -19832,11 +19845,13 @@
method public synchronized void applyLocalizedPattern(java.lang.String);
method public synchronized void applyPattern(java.lang.String);
method public synchronized boolean areSignificantDigitsUsed();
+ method public synchronized boolean equals(java.lang.Object);
method public java.lang.StringBuffer format(double, java.lang.StringBuffer, java.text.FieldPosition);
method public java.lang.StringBuffer format(long, java.lang.StringBuffer, java.text.FieldPosition);
method public java.lang.StringBuffer format(java.math.BigInteger, java.lang.StringBuffer, java.text.FieldPosition);
method public java.lang.StringBuffer format(java.math.BigDecimal, java.lang.StringBuffer, java.text.FieldPosition);
method public java.lang.StringBuffer format(android.icu.math.BigDecimal, java.lang.StringBuffer, java.text.FieldPosition);
+ method public synchronized android.icu.util.Currency getCurrency();
method public synchronized android.icu.text.CurrencyPluralInfo getCurrencyPluralInfo();
method public synchronized android.icu.util.Currency.CurrencyUsage getCurrencyUsage();
method public synchronized android.icu.text.DecimalFormatSymbols getDecimalFormatSymbols();
@@ -19844,8 +19859,12 @@
method public synchronized int getGroupingSize();
method public synchronized java.math.MathContext getMathContext();
method public synchronized android.icu.math.MathContext getMathContextICU();
+ method public synchronized int getMaximumFractionDigits();
+ method public synchronized int getMaximumIntegerDigits();
method public synchronized int getMaximumSignificantDigits();
method public synchronized byte getMinimumExponentDigits();
+ method public synchronized int getMinimumFractionDigits();
+ method public synchronized int getMinimumIntegerDigits();
method public synchronized int getMinimumSignificantDigits();
method public synchronized int getMultiplier();
method public synchronized java.lang.String getNegativePrefix();
@@ -19856,13 +19875,19 @@
method public synchronized java.lang.String getPositivePrefix();
method public synchronized java.lang.String getPositiveSuffix();
method public synchronized java.math.BigDecimal getRoundingIncrement();
+ method public synchronized int getRoundingMode();
method public synchronized int getSecondaryGroupingSize();
+ method public synchronized int hashCode();
method public synchronized boolean isDecimalPatternMatchRequired();
method public synchronized boolean isDecimalSeparatorAlwaysShown();
method public synchronized boolean isExponentSignAlwaysShown();
+ method public synchronized boolean isGroupingUsed();
method public synchronized boolean isParseBigDecimal();
+ method public synchronized boolean isParseIntegerOnly();
+ method public synchronized boolean isParseStrict();
method public synchronized boolean isScientificNotation();
method public java.lang.Number parse(java.lang.String, java.text.ParsePosition);
+ method public synchronized void setCurrency(android.icu.util.Currency);
method public synchronized void setCurrencyPluralInfo(android.icu.text.CurrencyPluralInfo);
method public synchronized void setCurrencyUsage(android.icu.util.Currency.CurrencyUsage);
method public synchronized void setDecimalFormatSymbols(android.icu.text.DecimalFormatSymbols);
@@ -19871,10 +19896,15 @@
method public synchronized void setExponentSignAlwaysShown(boolean);
method public synchronized void setFormatWidth(int);
method public synchronized void setGroupingSize(int);
+ method public synchronized void setGroupingUsed(boolean);
method public synchronized void setMathContext(java.math.MathContext);
method public synchronized void setMathContextICU(android.icu.math.MathContext);
+ method public synchronized void setMaximumFractionDigits(int);
+ method public synchronized void setMaximumIntegerDigits(int);
method public synchronized void setMaximumSignificantDigits(int);
method public synchronized void setMinimumExponentDigits(byte);
+ method public synchronized void setMinimumFractionDigits(int);
+ method public synchronized void setMinimumIntegerDigits(int);
method public synchronized void setMinimumSignificantDigits(int);
method public synchronized void setMultiplier(int);
method public synchronized void setNegativePrefix(java.lang.String);
@@ -19882,12 +19912,15 @@
method public synchronized void setPadCharacter(char);
method public synchronized void setPadPosition(int);
method public synchronized void setParseBigDecimal(boolean);
+ method public synchronized void setParseIntegerOnly(boolean);
method public deprecated void setParseMaxDigits(int);
+ method public synchronized void setParseStrict(boolean);
method public synchronized void setPositivePrefix(java.lang.String);
method public synchronized void setPositiveSuffix(java.lang.String);
method public synchronized void setRoundingIncrement(java.math.BigDecimal);
method public synchronized void setRoundingIncrement(android.icu.math.BigDecimal);
method public synchronized void setRoundingIncrement(double);
+ method public synchronized void setRoundingMode(int);
method public synchronized void setScientificNotation(boolean);
method public synchronized void setSecondaryGroupingSize(int);
method public synchronized void setSignificantDigitsUsed(boolean);
@@ -34606,6 +34639,14 @@
method public static void setVmPolicy(android.os.StrictMode.VmPolicy);
}
+ public static abstract interface StrictMode.OnThreadViolationListener {
+ method public abstract void onThreadViolation(android.os.strictmode.Violation);
+ }
+
+ public static abstract interface StrictMode.OnVmViolationListener {
+ method public abstract void onVmViolation(android.os.strictmode.Violation);
+ }
+
public static final class StrictMode.ThreadPolicy {
field public static final android.os.StrictMode.ThreadPolicy LAX;
}
@@ -34626,6 +34667,7 @@
method public android.os.StrictMode.ThreadPolicy.Builder penaltyDialog();
method public android.os.StrictMode.ThreadPolicy.Builder penaltyDropBox();
method public android.os.StrictMode.ThreadPolicy.Builder penaltyFlashScreen();
+ method public android.os.StrictMode.ThreadPolicy.Builder penaltyListener(android.os.StrictMode.OnThreadViolationListener, java.util.concurrent.Executor);
method public android.os.StrictMode.ThreadPolicy.Builder penaltyLog();
method public android.os.StrictMode.ThreadPolicy.Builder permitAll();
method public android.os.StrictMode.ThreadPolicy.Builder permitCustomSlowCalls();
@@ -34657,6 +34699,7 @@
method public android.os.StrictMode.VmPolicy.Builder penaltyDeathOnCleartextNetwork();
method public android.os.StrictMode.VmPolicy.Builder penaltyDeathOnFileUriExposure();
method public android.os.StrictMode.VmPolicy.Builder penaltyDropBox();
+ method public android.os.StrictMode.VmPolicy.Builder penaltyListener(android.os.StrictMode.OnVmViolationListener, java.util.concurrent.Executor);
method public android.os.StrictMode.VmPolicy.Builder penaltyLog();
method public android.os.StrictMode.VmPolicy.Builder setClassInstanceLimit(java.lang.Class, int);
}
@@ -34664,10 +34707,12 @@
public final class SystemClock {
method public static long currentThreadTimeMillis();
method public static long elapsedRealtime();
+ method public static java.time.Clock elapsedRealtimeClock();
method public static long elapsedRealtimeNanos();
method public static boolean setCurrentTimeMillis(long);
method public static void sleep(long);
method public static long uptimeMillis();
+ method public static java.time.Clock uptimeMillisClock();
}
public class TestLooperManager {
@@ -34814,6 +34859,7 @@
field public static final java.lang.String DISALLOW_CONFIG_CELL_BROADCASTS = "no_config_cell_broadcasts";
field public static final java.lang.String DISALLOW_CONFIG_CREDENTIALS = "no_config_credentials";
field public static final java.lang.String DISALLOW_CONFIG_DATE_TIME = "no_config_date_time";
+ field public static final java.lang.String DISALLOW_CONFIG_LOCALE = "no_config_locale";
field public static final java.lang.String DISALLOW_CONFIG_MOBILE_NETWORKS = "no_config_mobile_networks";
field public static final java.lang.String DISALLOW_CONFIG_TETHERING = "no_config_tethering";
field public static final java.lang.String DISALLOW_CONFIG_VPN = "no_config_vpn";
@@ -35103,6 +35149,62 @@
}
+package android.os.strictmode {
+
+ public final class CleartextNetworkViolation extends android.os.strictmode.Violation {
+ }
+
+ public final class ContentUriWithoutPermissionViolation extends android.os.strictmode.Violation {
+ }
+
+ public final class CustomViolation extends android.os.strictmode.Violation {
+ }
+
+ public final class DiskReadViolation extends android.os.strictmode.Violation {
+ }
+
+ public final class DiskWriteViolation extends android.os.strictmode.Violation {
+ }
+
+ public final class FileUriExposedViolation extends android.os.strictmode.Violation {
+ }
+
+ public class InstanceCountViolation extends android.os.strictmode.Violation {
+ method public long getNumberOfInstances();
+ }
+
+ public final class IntentReceiverLeakedViolation extends android.os.strictmode.Violation {
+ }
+
+ public final class LeakedClosableViolation extends android.os.strictmode.Violation {
+ }
+
+ public final class NetworkViolation extends android.os.strictmode.Violation {
+ }
+
+ public final class ResourceMismatchViolation extends android.os.strictmode.Violation {
+ }
+
+ public final class ServiceConnectionLeakedViolation extends android.os.strictmode.Violation {
+ }
+
+ public final class SqliteObjectLeakedViolation extends android.os.strictmode.Violation {
+ }
+
+ public final class UnbufferedIoViolation extends android.os.strictmode.Violation {
+ }
+
+ public final class UntaggedSocketViolation extends android.os.strictmode.Violation {
+ }
+
+ public abstract class Violation extends java.lang.Throwable {
+ }
+
+ public final class WebViewMethodCalledOnWrongThreadViolation extends android.os.strictmode.Violation {
+ }
+
+}
+
package android.permissionpresenterservice {
public abstract class RuntimePermissionPresenterService extends android.app.Service {
@@ -35401,7 +35503,7 @@
method public default void putStringSet(java.lang.String, java.util.Set<java.lang.String>);
}
- public abstract class PreferenceFragment extends android.app.Fragment {
+ public abstract deprecated class PreferenceFragment extends android.app.Fragment {
ctor public PreferenceFragment();
method public void addPreferencesFromIntent(android.content.Intent);
method public void addPreferencesFromResource(int);
@@ -35412,7 +35514,7 @@
method public void setPreferenceScreen(android.preference.PreferenceScreen);
}
- public static abstract interface PreferenceFragment.OnPreferenceStartFragmentCallback {
+ public static abstract deprecated interface PreferenceFragment.OnPreferenceStartFragmentCallback {
method public abstract boolean onPreferenceStartFragment(android.preference.PreferenceFragment, android.preference.Preference);
}
@@ -42561,7 +42663,9 @@
ctor public CallAudioState(boolean, int, int);
method public static java.lang.String audioRouteToString(int);
method public int describeContents();
+ method public android.bluetooth.BluetoothDevice getActiveBluetoothDevice();
method public int getRoute();
+ method public java.util.Collection<android.bluetooth.BluetoothDevice> getSupportedBluetoothDevices();
method public int getSupportedRouteMask();
method public boolean isMuted();
method public void writeToParcel(android.os.Parcel, int);
@@ -42700,6 +42804,7 @@
method public final void putExtras(android.os.Bundle);
method public final void removeExtras(java.util.List<java.lang.String>);
method public final void removeExtras(java.lang.String...);
+ method public void requestBluetoothAudio(java.lang.String);
method public void sendConnectionEvent(java.lang.String, android.os.Bundle);
method public final void setActive();
method public final void setAddress(android.net.Uri, int);
@@ -42893,6 +42998,7 @@
method public deprecated void onPhoneCreated(android.telecom.Phone);
method public deprecated void onPhoneDestroyed(android.telecom.Phone);
method public void onSilenceRinger();
+ method public final void requestBluetoothAudio(java.lang.String);
method public final void setAudioRoute(int);
method public final void setMuted(boolean);
field public static final java.lang.String SERVICE_INTERFACE = "android.telecom.InCallService";
@@ -43031,6 +43137,7 @@
method public final android.telecom.CallAudioState getCallAudioState();
method public final java.util.List<android.telecom.Call> getCalls();
method public final void removeListener(android.telecom.Phone.Listener);
+ method public void requestBluetoothAudio(java.lang.String);
method public final void setAudioRoute(int);
method public final void setMuted(boolean);
}
@@ -53804,7 +53911,7 @@
method public abstract void setWebContentsDebuggingEnabled(boolean);
}
- public class WebViewFragment extends android.app.Fragment {
+ public deprecated class WebViewFragment extends android.app.Fragment {
ctor public WebViewFragment();
method public android.webkit.WebView getWebView();
}
diff --git a/api/test-current.txt b/api/test-current.txt
index 2a2967e..261f173 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -3611,11 +3611,11 @@
method public android.transition.Scene getContentScene();
method public android.transition.TransitionManager getContentTransitionManager();
method public android.view.View getCurrentFocus();
- method public android.app.FragmentManager getFragmentManager();
+ method public deprecated android.app.FragmentManager getFragmentManager();
method public android.content.Intent getIntent();
method public java.lang.Object getLastNonConfigurationInstance();
method public android.view.LayoutInflater getLayoutInflater();
- method public android.app.LoaderManager getLoaderManager();
+ method public deprecated android.app.LoaderManager getLoaderManager();
method public java.lang.String getLocalClassName();
method public int getMaxNumPictureInPictureActions();
method public final android.media.session.MediaController getMediaController();
@@ -3655,7 +3655,7 @@
method public void onActionModeStarted(android.view.ActionMode);
method public void onActivityReenter(int, android.content.Intent);
method protected void onActivityResult(int, int, android.content.Intent);
- method public void onAttachFragment(android.app.Fragment);
+ method public deprecated void onAttachFragment(android.app.Fragment);
method public void onAttachedToWindow();
method public void onBackPressed();
method protected void onChildTitleChanged(android.app.Activity, java.lang.CharSequence);
@@ -3797,8 +3797,8 @@
method public void startActivityForResult(android.content.Intent, int, android.os.Bundle);
method public void startActivityFromChild(android.app.Activity, android.content.Intent, int);
method public void startActivityFromChild(android.app.Activity, android.content.Intent, int, android.os.Bundle);
- method public void startActivityFromFragment(android.app.Fragment, android.content.Intent, int);
- method public void startActivityFromFragment(android.app.Fragment, android.content.Intent, int, android.os.Bundle);
+ method public deprecated void startActivityFromFragment(android.app.Fragment, android.content.Intent, int);
+ method public deprecated void startActivityFromFragment(android.app.Fragment, android.content.Intent, int, android.os.Bundle);
method public boolean startActivityIfNeeded(android.content.Intent, int);
method public boolean startActivityIfNeeded(android.content.Intent, int, android.os.Bundle);
method public void startIntentSenderForResult(android.content.IntentSender, int, android.content.Intent, int, int, int) throws android.content.IntentSender.SendIntentException;
@@ -4484,7 +4484,7 @@
method public void unregisterForContextMenu(android.view.View);
}
- public class DialogFragment extends android.app.Fragment implements android.content.DialogInterface.OnCancelListener android.content.DialogInterface.OnDismissListener {
+ public deprecated class DialogFragment extends android.app.Fragment implements android.content.DialogInterface.OnCancelListener android.content.DialogInterface.OnDismissListener {
ctor public DialogFragment();
method public void dismiss();
method public void dismissAllowingStateLoss();
@@ -4602,7 +4602,7 @@
method public void setSelectedGroup(int);
}
- public class Fragment implements android.content.ComponentCallbacks2 android.view.View.OnCreateContextMenuListener {
+ public deprecated class Fragment implements android.content.ComponentCallbacks2 android.view.View.OnCreateContextMenuListener {
ctor public Fragment();
method public void dump(java.lang.String, java.io.FileDescriptor, java.io.PrintWriter, java.lang.String[]);
method public final boolean equals(java.lang.Object);
@@ -4618,7 +4618,7 @@
method public final java.lang.Object getHost();
method public final int getId();
method public final android.view.LayoutInflater getLayoutInflater();
- method public android.app.LoaderManager getLoaderManager();
+ method public deprecated android.app.LoaderManager getLoaderManager();
method public final android.app.Fragment getParentFragment();
method public android.transition.Transition getReenterTransition();
method public final android.content.res.Resources getResources();
@@ -4713,11 +4713,11 @@
method public void unregisterForContextMenu(android.view.View);
}
- public static class Fragment.InstantiationException extends android.util.AndroidRuntimeException {
+ public static deprecated class Fragment.InstantiationException extends android.util.AndroidRuntimeException {
ctor public Fragment.InstantiationException(java.lang.String, java.lang.Exception);
}
- public static class Fragment.SavedState implements android.os.Parcelable {
+ public static deprecated class Fragment.SavedState implements android.os.Parcelable {
method public int describeContents();
method public void writeToParcel(android.os.Parcel, int);
field public static final android.os.Parcelable.ClassLoaderCreator<android.app.Fragment.SavedState> CREATOR;
@@ -4736,17 +4736,17 @@
method public void setTitle(java.lang.CharSequence, java.lang.CharSequence);
}
- public static abstract interface FragmentBreadCrumbs.OnBreadCrumbClickListener {
+ public static abstract deprecated interface FragmentBreadCrumbs.OnBreadCrumbClickListener {
method public abstract boolean onBreadCrumbClick(android.app.FragmentManager.BackStackEntry, int);
}
- public abstract class FragmentContainer {
+ public abstract deprecated class FragmentContainer {
ctor public FragmentContainer();
method public abstract <T extends android.view.View> T onFindViewById(int);
method public abstract boolean onHasView();
}
- public class FragmentController {
+ public deprecated class FragmentController {
method public void attachHost(android.app.Fragment);
method public static final android.app.FragmentController createController(android.app.FragmentHostCallback<?>);
method public void dispatchActivityCreated();
@@ -4789,7 +4789,7 @@
method public android.os.Parcelable saveAllState();
}
- public abstract class FragmentHostCallback<E> extends android.app.FragmentContainer {
+ public abstract deprecated class FragmentHostCallback<E> extends android.app.FragmentContainer {
ctor public FragmentHostCallback(android.content.Context, android.os.Handler, int);
method public void onAttachFragment(android.app.Fragment);
method public void onDump(java.lang.String, java.io.FileDescriptor, java.io.PrintWriter, java.lang.String[]);
@@ -4807,7 +4807,7 @@
method public boolean onUseFragmentManagerInflaterFactory();
}
- public abstract class FragmentManager {
+ public abstract deprecated class FragmentManager {
ctor public FragmentManager();
method public abstract void addOnBackStackChangedListener(android.app.FragmentManager.OnBackStackChangedListener);
method public abstract android.app.FragmentTransaction beginTransaction();
@@ -4838,7 +4838,7 @@
field public static final int POP_BACK_STACK_INCLUSIVE = 1; // 0x1
}
- public static abstract interface FragmentManager.BackStackEntry {
+ public static abstract deprecated interface FragmentManager.BackStackEntry {
method public abstract java.lang.CharSequence getBreadCrumbShortTitle();
method public abstract int getBreadCrumbShortTitleRes();
method public abstract java.lang.CharSequence getBreadCrumbTitle();
@@ -4847,7 +4847,7 @@
method public abstract java.lang.String getName();
}
- public static abstract class FragmentManager.FragmentLifecycleCallbacks {
+ public static abstract deprecated class FragmentManager.FragmentLifecycleCallbacks {
ctor public FragmentManager.FragmentLifecycleCallbacks();
method public void onFragmentActivityCreated(android.app.FragmentManager, android.app.Fragment, android.os.Bundle);
method public void onFragmentAttached(android.app.FragmentManager, android.app.Fragment, android.content.Context);
@@ -4865,14 +4865,14 @@
method public void onFragmentViewDestroyed(android.app.FragmentManager, android.app.Fragment);
}
- public static abstract interface FragmentManager.OnBackStackChangedListener {
+ public static abstract deprecated interface FragmentManager.OnBackStackChangedListener {
method public abstract void onBackStackChanged();
}
- public class FragmentManagerNonConfig {
+ public deprecated class FragmentManagerNonConfig {
}
- public abstract class FragmentTransaction {
+ public abstract deprecated class FragmentTransaction {
ctor public FragmentTransaction();
method public abstract android.app.FragmentTransaction add(android.app.Fragment, java.lang.String);
method public abstract android.app.FragmentTransaction add(int, android.app.Fragment);
@@ -5077,7 +5077,7 @@
method public void setSelection(int);
}
- public class ListFragment extends android.app.Fragment {
+ public deprecated class ListFragment extends android.app.Fragment {
ctor public ListFragment();
method public android.widget.ListAdapter getListAdapter();
method public android.widget.ListView getListView();
@@ -5091,7 +5091,7 @@
method public void setSelection(int);
}
- public abstract class LoaderManager {
+ public abstract deprecated class LoaderManager {
ctor public LoaderManager();
method public abstract void destroyLoader(int);
method public abstract void dump(java.lang.String, java.io.FileDescriptor, java.io.PrintWriter, java.lang.String[]);
@@ -5101,7 +5101,7 @@
method public abstract <D> android.content.Loader<D> restartLoader(int, android.os.Bundle, android.app.LoaderManager.LoaderCallbacks<D>);
}
- public static abstract interface LoaderManager.LoaderCallbacks<D> {
+ public static abstract deprecated interface LoaderManager.LoaderCallbacks<D> {
method public abstract android.content.Loader<D> onCreateLoader(int, android.os.Bundle);
method public abstract void onLoadFinished(android.content.Loader<D>, D);
method public abstract void onLoaderReset(android.content.Loader<D>);
@@ -6921,7 +6921,8 @@
method public static final long getMinFlexMillis();
method public long getMinLatencyMillis();
method public static final long getMinPeriodMillis();
- method public int getNetworkType();
+ method public deprecated int getNetworkType();
+ method public android.net.NetworkRequest getRequiredNetwork();
method public android.content.ComponentName getService();
method public android.os.Bundle getTransientExtras();
method public long getTriggerContentMaxDelay();
@@ -6941,7 +6942,8 @@
field public static final long MAX_BACKOFF_DELAY_MILLIS = 18000000L; // 0x112a880L
field public static final int NETWORK_BYTES_UNKNOWN = -1; // 0xffffffff
field public static final int NETWORK_TYPE_ANY = 1; // 0x1
- field public static final int NETWORK_TYPE_METERED = 4; // 0x4
+ field public static final int NETWORK_TYPE_CELLULAR = 4; // 0x4
+ field public static final deprecated int NETWORK_TYPE_METERED = 4; // 0x4
field public static final int NETWORK_TYPE_NONE = 0; // 0x0
field public static final int NETWORK_TYPE_NOT_ROAMING = 3; // 0x3
field public static final int NETWORK_TYPE_UNMETERED = 2; // 0x2
@@ -6960,6 +6962,7 @@
method public android.app.job.JobInfo.Builder setPeriodic(long);
method public android.app.job.JobInfo.Builder setPeriodic(long, long);
method public android.app.job.JobInfo.Builder setPersisted(boolean);
+ method public android.app.job.JobInfo.Builder setRequiredNetwork(android.net.NetworkRequest);
method public android.app.job.JobInfo.Builder setRequiredNetworkType(int);
method public android.app.job.JobInfo.Builder setRequiresBatteryNotLow(boolean);
method public android.app.job.JobInfo.Builder setRequiresCharging(boolean);
@@ -8604,7 +8607,7 @@
ctor public AsyncQueryHandler.WorkerHandler(android.os.Looper);
}
- public abstract class AsyncTaskLoader<D> extends android.content.Loader {
+ public abstract deprecated class AsyncTaskLoader<D> extends android.content.Loader {
ctor public AsyncTaskLoader(android.content.Context);
method public void cancelLoadInBackground();
method public boolean isLoadInBackgroundCanceled();
@@ -9179,6 +9182,7 @@
field public static final int CONTEXT_IGNORE_SECURITY = 2; // 0x2
field public static final int CONTEXT_INCLUDE_CODE = 1; // 0x1
field public static final int CONTEXT_RESTRICTED = 4; // 0x4
+ field public static final java.lang.String CROSS_PROFILE_APPS_SERVICE = "crossprofileapps";
field public static final java.lang.String DEVICE_POLICY_SERVICE = "device_policy";
field public static final java.lang.String DISPLAY_SERVICE = "display";
field public static final java.lang.String DOWNLOAD_SERVICE = "download";
@@ -9346,7 +9350,7 @@
method public void unregisterReceiver(android.content.BroadcastReceiver);
}
- public class CursorLoader extends android.content.AsyncTaskLoader {
+ public deprecated class CursorLoader extends android.content.AsyncTaskLoader {
ctor public CursorLoader(android.content.Context);
ctor public CursorLoader(android.content.Context, android.net.Uri, java.lang.String[], java.lang.String, java.lang.String[], java.lang.String);
method public void deliverResult(android.database.Cursor);
@@ -9952,7 +9956,7 @@
ctor public IntentSender.SendIntentException(java.lang.Exception);
}
- public class Loader<D> {
+ public deprecated class Loader<D> {
ctor public Loader(android.content.Context);
method public void abandon();
method public boolean cancelLoad();
@@ -9985,15 +9989,15 @@
method public void unregisterOnLoadCanceledListener(android.content.Loader.OnLoadCanceledListener<D>);
}
- public final class Loader.ForceLoadContentObserver extends android.database.ContentObserver {
+ public final deprecated class Loader.ForceLoadContentObserver extends android.database.ContentObserver {
ctor public Loader.ForceLoadContentObserver();
}
- public static abstract interface Loader.OnLoadCanceledListener<D> {
+ public static abstract deprecated interface Loader.OnLoadCanceledListener<D> {
method public abstract void onLoadCanceled(android.content.Loader<D>);
}
- public static abstract interface Loader.OnLoadCompleteListener<D> {
+ public static abstract deprecated interface Loader.OnLoadCompleteListener<D> {
method public abstract void onLoadComplete(android.content.Loader<D>, D);
}
@@ -11312,6 +11316,15 @@
}
+package android.content.pm.crossprofile {
+
+ public class CrossProfileApps {
+ method public java.util.List<android.os.UserHandle> getTargetUserProfiles();
+ method public void startMainActivity(android.content.ComponentName, android.os.UserHandle, android.graphics.Rect, android.os.Bundle);
+ }
+
+}
+
package android.content.res {
public class AssetFileDescriptor implements java.io.Closeable android.os.Parcelable {
@@ -18419,11 +18432,13 @@
method public synchronized void applyLocalizedPattern(java.lang.String);
method public synchronized void applyPattern(java.lang.String);
method public synchronized boolean areSignificantDigitsUsed();
+ method public synchronized boolean equals(java.lang.Object);
method public java.lang.StringBuffer format(double, java.lang.StringBuffer, java.text.FieldPosition);
method public java.lang.StringBuffer format(long, java.lang.StringBuffer, java.text.FieldPosition);
method public java.lang.StringBuffer format(java.math.BigInteger, java.lang.StringBuffer, java.text.FieldPosition);
method public java.lang.StringBuffer format(java.math.BigDecimal, java.lang.StringBuffer, java.text.FieldPosition);
method public java.lang.StringBuffer format(android.icu.math.BigDecimal, java.lang.StringBuffer, java.text.FieldPosition);
+ method public synchronized android.icu.util.Currency getCurrency();
method public synchronized android.icu.text.CurrencyPluralInfo getCurrencyPluralInfo();
method public synchronized android.icu.util.Currency.CurrencyUsage getCurrencyUsage();
method public synchronized android.icu.text.DecimalFormatSymbols getDecimalFormatSymbols();
@@ -18431,8 +18446,12 @@
method public synchronized int getGroupingSize();
method public synchronized java.math.MathContext getMathContext();
method public synchronized android.icu.math.MathContext getMathContextICU();
+ method public synchronized int getMaximumFractionDigits();
+ method public synchronized int getMaximumIntegerDigits();
method public synchronized int getMaximumSignificantDigits();
method public synchronized byte getMinimumExponentDigits();
+ method public synchronized int getMinimumFractionDigits();
+ method public synchronized int getMinimumIntegerDigits();
method public synchronized int getMinimumSignificantDigits();
method public synchronized int getMultiplier();
method public synchronized java.lang.String getNegativePrefix();
@@ -18443,13 +18462,19 @@
method public synchronized java.lang.String getPositivePrefix();
method public synchronized java.lang.String getPositiveSuffix();
method public synchronized java.math.BigDecimal getRoundingIncrement();
+ method public synchronized int getRoundingMode();
method public synchronized int getSecondaryGroupingSize();
+ method public synchronized int hashCode();
method public synchronized boolean isDecimalPatternMatchRequired();
method public synchronized boolean isDecimalSeparatorAlwaysShown();
method public synchronized boolean isExponentSignAlwaysShown();
+ method public synchronized boolean isGroupingUsed();
method public synchronized boolean isParseBigDecimal();
+ method public synchronized boolean isParseIntegerOnly();
+ method public synchronized boolean isParseStrict();
method public synchronized boolean isScientificNotation();
method public java.lang.Number parse(java.lang.String, java.text.ParsePosition);
+ method public synchronized void setCurrency(android.icu.util.Currency);
method public synchronized void setCurrencyPluralInfo(android.icu.text.CurrencyPluralInfo);
method public synchronized void setCurrencyUsage(android.icu.util.Currency.CurrencyUsage);
method public synchronized void setDecimalFormatSymbols(android.icu.text.DecimalFormatSymbols);
@@ -18458,10 +18483,15 @@
method public synchronized void setExponentSignAlwaysShown(boolean);
method public synchronized void setFormatWidth(int);
method public synchronized void setGroupingSize(int);
+ method public synchronized void setGroupingUsed(boolean);
method public synchronized void setMathContext(java.math.MathContext);
method public synchronized void setMathContextICU(android.icu.math.MathContext);
+ method public synchronized void setMaximumFractionDigits(int);
+ method public synchronized void setMaximumIntegerDigits(int);
method public synchronized void setMaximumSignificantDigits(int);
method public synchronized void setMinimumExponentDigits(byte);
+ method public synchronized void setMinimumFractionDigits(int);
+ method public synchronized void setMinimumIntegerDigits(int);
method public synchronized void setMinimumSignificantDigits(int);
method public synchronized void setMultiplier(int);
method public synchronized void setNegativePrefix(java.lang.String);
@@ -18469,12 +18499,15 @@
method public synchronized void setPadCharacter(char);
method public synchronized void setPadPosition(int);
method public synchronized void setParseBigDecimal(boolean);
+ method public synchronized void setParseIntegerOnly(boolean);
method public deprecated void setParseMaxDigits(int);
+ method public synchronized void setParseStrict(boolean);
method public synchronized void setPositivePrefix(java.lang.String);
method public synchronized void setPositiveSuffix(java.lang.String);
method public synchronized void setRoundingIncrement(java.math.BigDecimal);
method public synchronized void setRoundingIncrement(android.icu.math.BigDecimal);
method public synchronized void setRoundingIncrement(double);
+ method public synchronized void setRoundingMode(int);
method public synchronized void setScientificNotation(boolean);
method public synchronized void setSecondaryGroupingSize(int);
method public synchronized void setSignificantDigitsUsed(boolean);
@@ -32059,6 +32092,14 @@
field public static final int DETECT_VM_UNTAGGED_SOCKET = -2147483648; // 0x80000000
}
+ public static abstract interface StrictMode.OnThreadViolationListener {
+ method public abstract void onThreadViolation(android.os.strictmode.Violation);
+ }
+
+ public static abstract interface StrictMode.OnVmViolationListener {
+ method public abstract void onVmViolation(android.os.strictmode.Violation);
+ }
+
public static final class StrictMode.ThreadPolicy {
field public static final android.os.StrictMode.ThreadPolicy LAX;
}
@@ -32079,6 +32120,7 @@
method public android.os.StrictMode.ThreadPolicy.Builder penaltyDialog();
method public android.os.StrictMode.ThreadPolicy.Builder penaltyDropBox();
method public android.os.StrictMode.ThreadPolicy.Builder penaltyFlashScreen();
+ method public android.os.StrictMode.ThreadPolicy.Builder penaltyListener(android.os.StrictMode.OnThreadViolationListener, java.util.concurrent.Executor);
method public android.os.StrictMode.ThreadPolicy.Builder penaltyLog();
method public android.os.StrictMode.ThreadPolicy.Builder permitAll();
method public android.os.StrictMode.ThreadPolicy.Builder permitCustomSlowCalls();
@@ -32134,6 +32176,7 @@
method public android.os.StrictMode.VmPolicy.Builder penaltyDeathOnCleartextNetwork();
method public android.os.StrictMode.VmPolicy.Builder penaltyDeathOnFileUriExposure();
method public android.os.StrictMode.VmPolicy.Builder penaltyDropBox();
+ method public android.os.StrictMode.VmPolicy.Builder penaltyListener(android.os.StrictMode.OnVmViolationListener, java.util.concurrent.Executor);
method public android.os.StrictMode.VmPolicy.Builder penaltyLog();
method public android.os.StrictMode.VmPolicy.Builder setClassInstanceLimit(java.lang.Class, int);
}
@@ -32141,10 +32184,12 @@
public final class SystemClock {
method public static long currentThreadTimeMillis();
method public static long elapsedRealtime();
+ method public static java.time.Clock elapsedRealtimeClock();
method public static long elapsedRealtimeNanos();
method public static boolean setCurrentTimeMillis(long);
method public static void sleep(long);
method public static long uptimeMillis();
+ method public static java.time.Clock uptimeMillisClock();
}
public class TestLooperManager {
@@ -32229,6 +32274,7 @@
field public static final java.lang.String DISALLOW_CONFIG_CELL_BROADCASTS = "no_config_cell_broadcasts";
field public static final java.lang.String DISALLOW_CONFIG_CREDENTIALS = "no_config_credentials";
field public static final java.lang.String DISALLOW_CONFIG_DATE_TIME = "no_config_date_time";
+ field public static final java.lang.String DISALLOW_CONFIG_LOCALE = "no_config_locale";
field public static final java.lang.String DISALLOW_CONFIG_MOBILE_NETWORKS = "no_config_mobile_networks";
field public static final java.lang.String DISALLOW_CONFIG_TETHERING = "no_config_tethering";
field public static final java.lang.String DISALLOW_CONFIG_VPN = "no_config_vpn";
@@ -32498,6 +32544,62 @@
}
+package android.os.strictmode {
+
+ public final class CleartextNetworkViolation extends android.os.strictmode.Violation {
+ }
+
+ public final class ContentUriWithoutPermissionViolation extends android.os.strictmode.Violation {
+ }
+
+ public final class CustomViolation extends android.os.strictmode.Violation {
+ }
+
+ public final class DiskReadViolation extends android.os.strictmode.Violation {
+ }
+
+ public final class DiskWriteViolation extends android.os.strictmode.Violation {
+ }
+
+ public final class FileUriExposedViolation extends android.os.strictmode.Violation {
+ }
+
+ public class InstanceCountViolation extends android.os.strictmode.Violation {
+ method public long getNumberOfInstances();
+ }
+
+ public final class IntentReceiverLeakedViolation extends android.os.strictmode.Violation {
+ }
+
+ public final class LeakedClosableViolation extends android.os.strictmode.Violation {
+ }
+
+ public final class NetworkViolation extends android.os.strictmode.Violation {
+ }
+
+ public final class ResourceMismatchViolation extends android.os.strictmode.Violation {
+ }
+
+ public final class ServiceConnectionLeakedViolation extends android.os.strictmode.Violation {
+ }
+
+ public final class SqliteObjectLeakedViolation extends android.os.strictmode.Violation {
+ }
+
+ public final class UnbufferedIoViolation extends android.os.strictmode.Violation {
+ }
+
+ public final class UntaggedSocketViolation extends android.os.strictmode.Violation {
+ }
+
+ public abstract class Violation extends java.lang.Throwable {
+ }
+
+ public final class WebViewMethodCalledOnWrongThreadViolation extends android.os.strictmode.Violation {
+ }
+
+}
+
package android.preference {
public class CheckBoxPreference extends android.preference.TwoStatePreference {
@@ -32784,7 +32886,7 @@
method public default void putStringSet(java.lang.String, java.util.Set<java.lang.String>);
}
- public abstract class PreferenceFragment extends android.app.Fragment {
+ public abstract deprecated class PreferenceFragment extends android.app.Fragment {
ctor public PreferenceFragment();
method public void addPreferencesFromIntent(android.content.Intent);
method public void addPreferencesFromResource(int);
@@ -32795,7 +32897,7 @@
method public void setPreferenceScreen(android.preference.PreferenceScreen);
}
- public static abstract interface PreferenceFragment.OnPreferenceStartFragmentCallback {
+ public static abstract deprecated interface PreferenceFragment.OnPreferenceStartFragmentCallback {
method public abstract boolean onPreferenceStartFragment(android.preference.PreferenceFragment, android.preference.Preference);
}
@@ -39624,7 +39726,9 @@
ctor public CallAudioState(boolean, int, int);
method public static java.lang.String audioRouteToString(int);
method public int describeContents();
+ method public android.bluetooth.BluetoothDevice getActiveBluetoothDevice();
method public int getRoute();
+ method public java.util.Collection<android.bluetooth.BluetoothDevice> getSupportedBluetoothDevices();
method public int getSupportedRouteMask();
method public boolean isMuted();
method public void writeToParcel(android.os.Parcel, int);
@@ -39759,6 +39863,7 @@
method public final void putExtras(android.os.Bundle);
method public final void removeExtras(java.util.List<java.lang.String>);
method public final void removeExtras(java.lang.String...);
+ method public void requestBluetoothAudio(java.lang.String);
method public void sendConnectionEvent(java.lang.String, android.os.Bundle);
method public final void sendRemoteRttRequest();
method public final void sendRttInitiationFailure(int);
@@ -39962,6 +40067,7 @@
method public void onCanAddCallChanged(boolean);
method public void onConnectionEvent(android.telecom.Call, java.lang.String, android.os.Bundle);
method public void onSilenceRinger();
+ method public final void requestBluetoothAudio(java.lang.String);
method public final void setAudioRoute(int);
method public final void setMuted(boolean);
field public static final java.lang.String SERVICE_INTERFACE = "android.telecom.InCallService";
@@ -50525,7 +50631,7 @@
method public abstract void setHttpAuthUsernamePassword(java.lang.String, java.lang.String, java.lang.String, java.lang.String);
}
- public class WebViewFragment extends android.app.Fragment {
+ public deprecated class WebViewFragment extends android.app.Fragment {
ctor public WebViewFragment();
method public android.webkit.WebView getWebView();
}
diff --git a/cmds/am/src/com/android/commands/am/Instrument.java b/cmds/am/src/com/android/commands/am/Instrument.java
index 93b9f58..d79b1a6 100644
--- a/cmds/am/src/com/android/commands/am/Instrument.java
+++ b/cmds/am/src/com/android/commands/am/Instrument.java
@@ -47,6 +47,16 @@
/**
* Runs the am instrument command
+ *
+ * Test Result Code:
+ * 1 - Test running
+ * 0 - Test passed
+ * -2 - assertion failure
+ * -1 - other exceptions
+ *
+ * Session Result Code:
+ * -1: Success
+ * other: Failure
*/
public class Instrument {
public static final String DEFAULT_LOG_DIR = "instrument-logs";
diff --git a/cmds/incident_helper/src/ih_util.cpp b/cmds/incident_helper/src/ih_util.cpp
index c7d1ca2..0b51e66 100644
--- a/cmds/incident_helper/src/ih_util.cpp
+++ b/cmds/incident_helper/src/ih_util.cpp
@@ -22,19 +22,28 @@
#include <sstream>
#include <unistd.h>
-const ssize_t BUFFER_SIZE = 16 * 1024; // 4KB
+bool isValidChar(char c) {
+ uint8_t v = (uint8_t)c;
+ return (v >= (uint8_t)'a' && v <= (uint8_t)'z')
+ || (v >= (uint8_t)'A' && v <= (uint8_t)'Z')
+ || (v >= (uint8_t)'0' && v <= (uint8_t)'9')
+ || (v == (uint8_t)'_');
+}
-
-static std::string trim(const std::string& s) {
- const auto head = s.find_first_not_of(DEFAULT_WHITESPACE);
+static std::string trim(const std::string& s, const std::string& chars) {
+ const auto head = s.find_first_not_of(chars);
if (head == std::string::npos) return "";
- const auto tail = s.find_last_not_of(DEFAULT_WHITESPACE);
+ const auto tail = s.find_last_not_of(chars);
return s.substr(head, tail - head + 1);
}
+static std::string trimDefault(const std::string& s) {
+ return trim(s, DEFAULT_WHITESPACE);
+}
+
static std::string trimHeader(const std::string& s) {
- std::string res = trim(s);
+ std::string res = trimDefault(s);
std::transform(res.begin(), res.end(), res.begin(), ::tolower);
return res;
}
@@ -68,22 +77,68 @@
record_t parseRecord(const std::string& line, const std::string& delimiters) {
record_t record;
- trans_func f = &trim;
+ trans_func f = &trimDefault;
split(line, record, f, delimiters);
return record;
}
-bool hasPrefix(std::string* line, const char* key) {
+record_t parseRecordByColumns(const std::string& line, const std::vector<int>& indices, const std::string& delimiters) {
+ record_t record;
+ int lastIndex = 0;
+ int lineSize = (int)line.size();
+ for (std::vector<int>::const_iterator it = indices.begin(); it != indices.end(); ++it) {
+ int idx = *it;
+ if (lastIndex > idx || idx > lineSize) {
+ record.clear(); // The indices is wrong, return empty;
+ return record;
+ }
+ while (idx < lineSize && delimiters.find(line[idx++]) == std::string::npos);
+ record.push_back(trimDefault(line.substr(lastIndex, idx - lastIndex)));
+ lastIndex = idx;
+ }
+ record.push_back(trimDefault(line.substr(lastIndex, lineSize - lastIndex)));
+ return record;
+}
+
+bool stripPrefix(std::string* line, const char* key, bool endAtDelimiter) {
const auto head = line->find_first_not_of(DEFAULT_WHITESPACE);
if (head == std::string::npos) return false;
- auto i = 0;
- auto j = head;
+ int len = (int)line->length();
+ int i = 0;
+ int j = head;
while (key[i] != '\0') {
- if (j >= line->size() || key[i++] != line->at(j++)) {
+ if (j >= len || key[i++] != line->at(j++)) {
return false;
}
}
- line->assign(trim(line->substr(j)));
+
+ if (endAtDelimiter) {
+ // this means if the line only have prefix or no delimiter, we still return false.
+ if (j == len || isValidChar(line->at(j))) return false;
+ }
+
+ line->assign(trimDefault(line->substr(j)));
+ return true;
+}
+
+bool stripSuffix(std::string* line, const char* key, bool endAtDelimiter) {
+ const auto tail = line->find_last_not_of(DEFAULT_WHITESPACE);
+ if (tail == std::string::npos) return false;
+ int i = 0;
+ while (key[++i] != '\0'); // compute the size of the key
+ int j = tail;
+ while (i > 0) {
+ if (j < 0 || key[--i] != line->at(j--)) {
+ return false;
+ }
+ }
+
+ if (endAtDelimiter) {
+ // this means if the line only have suffix or no delimiter, we still return false.
+ if (j < 0 || isValidChar(line->at(j))) return false;
+ }
+
+ line->assign(trimDefault(line->substr(0, j+1)));
return true;
}
@@ -95,65 +150,36 @@
return atoll(s.c_str());
}
-// ==============================================================================
-Reader::Reader(const int fd) : Reader(fd, BUFFER_SIZE) {};
+double toDouble(const std::string& s) {
+ return atof(s.c_str());
+}
-Reader::Reader(const int fd, const size_t capacity)
- : mFd(fd), mMaxSize(capacity), mBufSize(0), mRead(0), mFlushed(0)
+// ==============================================================================
+Reader::Reader(const int fd)
{
- mBuf = capacity > 0 ? (char*)malloc(capacity * sizeof(char)) : NULL;
- mStatus = mFd < 0 ? "Negative fd" : (capacity == 0 ? "Zero buffer capacity" : "");
+ mFile = fdopen(fd, "r");
+ mStatus = mFile == NULL ? "Invalid fd " + std::to_string(fd) : "";
}
Reader::~Reader()
{
- free(mBuf);
+ if (mFile != NULL) fclose(mFile);
}
-bool Reader::readLine(std::string* line, const char newline) {
- if (!ok(line)) return false; // bad status
- line->clear();
- std::stringstream ss;
- while (!EOR()) {
- // read if available
- if (mFd != -1 && mBufSize != mMaxSize) {
- ssize_t amt = 0;
- if (mRead >= mFlushed) {
- amt = ::read(mFd, mBuf + mRead, mMaxSize - mRead);
- } else {
- amt = ::read(mFd, mBuf + mRead, mFlushed - mRead);
- }
- if (amt < 0) {
- mStatus = "Fail to read from fd";
- return false;
- } else if (amt == 0) {
- close(mFd);
- mFd = -1;
- }
- mRead += amt;
- mBufSize += amt;
- }
+bool Reader::readLine(std::string* line) {
+ if (mFile == NULL) return false;
- bool meetsNewLine = false;
- if (mBufSize > 0) {
- int start = mFlushed;
- int end = mFlushed < mRead ? mRead : mMaxSize;
- while (mFlushed < end && mBuf[mFlushed++] != newline && mBufSize > 0) mBufSize--;
- meetsNewLine = (mBuf[mFlushed-1] == newline);
- if (meetsNewLine) mBufSize--; // deduct the new line character
- size_t len = meetsNewLine ? mFlushed - start - 1 : mFlushed - start;
- ss.write(mBuf + start, len);
- }
-
- if (mRead >= (int) mMaxSize) mRead = 0;
- if (mFlushed >= (int) mMaxSize) mFlushed = 0;
-
- if (EOR() || meetsNewLine) {
- line->assign(ss.str());
- return true;
- }
+ char* buf = NULL;
+ size_t len = 0;
+ ssize_t read = getline(&buf, &len, mFile);
+ if (read != -1) {
+ std::string s(buf);
+ line->assign(trim(s, DEFAULT_NEWLINE));
+ } else if (errno == EINVAL) {
+ mStatus = "Bad Argument";
}
- return false;
+ free(buf);
+ return read != -1;
}
bool Reader::ok(std::string* error) {
@@ -162,10 +188,41 @@
}
// ==============================================================================
+static int
+lookupName(const char** names, const int size, const char* name)
+{
+ for (int i=0; i<size; i++) {
+ if (strcmp(name, names[i]) == 0) {
+ return i;
+ }
+ }
+ return -1;
+}
+
+EnumTypeMap::EnumTypeMap(const char* enumNames[], const uint32_t enumValues[], const int enumCount)
+ :mEnumNames(enumNames),
+ mEnumValues(enumValues),
+ mEnumCount(enumCount)
+{
+}
+
+EnumTypeMap::~EnumTypeMap()
+{
+}
+
+int
+EnumTypeMap::parseValue(const std::string& value)
+{
+ int index = lookupName(mEnumNames, mEnumCount, value.c_str());
+ if (index < 0) return mEnumValues[0]; // Assume value 0 is default
+ return mEnumValues[index];
+}
+
Table::Table(const char* names[], const uint64_t ids[], const int count)
:mFieldNames(names),
mFieldIds(ids),
- mFieldCount(count)
+ mFieldCount(count),
+ mEnums()
{
}
@@ -173,41 +230,54 @@
{
}
-bool
-Table::insertField(ProtoOutputStream& proto, const std::string& name, const std::string& value)
+void
+Table::addEnumTypeMap(const char* field, const char* enumNames[], const uint32_t enumValues[], const int enumSize)
{
- uint64_t found = 0;
- for (int i=0; i<mFieldCount; i++) {
- if (strcmp(name.c_str(), mFieldNames[i]) == 0) {
- found = mFieldIds[i];
- break;
- }
- }
+ int index = lookupName(mFieldNames, mFieldCount, field);
+ if (index < 0) return;
+ EnumTypeMap enu(enumNames, enumValues, enumSize);
+ mEnums[index] = enu;
+}
+
+bool
+Table::insertField(ProtoOutputStream* proto, const std::string& name, const std::string& value)
+{
+ int index = lookupName(mFieldNames, mFieldCount, name.c_str());
+ if (index < 0) return false;
+
+ uint64_t found = mFieldIds[index];
switch (found & FIELD_TYPE_MASK) {
case FIELD_TYPE_DOUBLE:
case FIELD_TYPE_FLOAT:
- // TODO: support parse string to float/double
- return false;
+ proto->write(found, toDouble(value));
+ break;
case FIELD_TYPE_STRING:
case FIELD_TYPE_BYTES:
- proto.write(found, value);
+ proto->write(found, value);
break;
case FIELD_TYPE_INT64:
case FIELD_TYPE_SINT64:
case FIELD_TYPE_UINT64:
case FIELD_TYPE_FIXED64:
case FIELD_TYPE_SFIXED64:
- proto.write(found, toLongLong(value));
+ proto->write(found, toLongLong(value));
break;
case FIELD_TYPE_BOOL:
+ return false;
case FIELD_TYPE_ENUM:
+ if (mEnums.find(index) == mEnums.end()) {
+ // forget to add enum type mapping
+ return false;
+ }
+ proto->write(found, mEnums[index].parseValue(value));
+ break;
case FIELD_TYPE_INT32:
case FIELD_TYPE_SINT32:
case FIELD_TYPE_UINT32:
case FIELD_TYPE_FIXED32:
case FIELD_TYPE_SFIXED32:
- proto.write(found, toInt(value));
+ proto->write(found, toInt(value));
break;
default:
return false;
diff --git a/cmds/incident_helper/src/ih_util.h b/cmds/incident_helper/src/ih_util.h
index 86761e9..e8366fa 100644
--- a/cmds/incident_helper/src/ih_util.h
+++ b/cmds/incident_helper/src/ih_util.h
@@ -17,9 +17,9 @@
#ifndef INCIDENT_HELPER_UTIL_H
#define INCIDENT_HELPER_UTIL_H
+#include <map>
#include <string>
#include <vector>
-#include <sstream>
#include <android/util/ProtoOutputStream.h>
@@ -29,8 +29,13 @@
typedef std::vector<std::string> record_t;
typedef std::string (*trans_func) (const std::string&);
-const char DEFAULT_NEWLINE = '\n';
const std::string DEFAULT_WHITESPACE = " \t";
+const std::string DEFAULT_NEWLINE = "\r\n";
+const std::string TAB_DELIMITER = "\t";
+const std::string COMMA_DELIMITER = ",";
+
+// returns true if c is a-zA-Z0-9 or underscore _
+bool isValidChar(char c);
/**
* When a text has a table format like this
@@ -47,19 +52,33 @@
record_t parseRecord(const std::string& line, const std::string& delimiters = DEFAULT_WHITESPACE);
/**
- * When the line starts with the given key, the function returns true
- * as well as the line argument is changed to the rest part of the original.
+ * When a text-format table aligns by its vertical position, it is not possible to split them by purely delimiters.
+ * This function allows to parse record by its header's column position' indices, must in ascending order.
+ * At the same time, it still looks at the char at index, if it doesn't belong to delimiters, moves forward to find the delimiters.
+ */
+record_t parseRecordByColumns(const std::string& line, const std::vector<int>& indices, const std::string& delimiters = DEFAULT_WHITESPACE);
+
+/**
+ * When the line starts/ends with the given key, the function returns true
+ * as well as the line argument is changed to the rest trimmed part of the original.
* e.g. "ZRAM: 6828K physical used for 31076K in swap (524284K total swap)" becomes
* "6828K physical used for 31076K in swap (524284K total swap)" when given key "ZRAM:",
* otherwise the line is not changed.
+ *
+ * In order to prevent two values have same prefix which cause entering to incorrect conditions,
+ * stripPrefix and stripSuffix can turn on a flag that requires the ending char in the line must not be a valid
+ * character or digits, this feature is off by default.
+ * i.e. ABC%some value, ABCD%other value
*/
-bool hasPrefix(std::string* line, const char* key);
+bool stripPrefix(std::string* line, const char* key, bool endAtDelimiter = false);
+bool stripSuffix(std::string* line, const char* key, bool endAtDelimiter = false);
/**
* Converts string to the desired type
*/
int toInt(const std::string& s);
long long toLongLong(const std::string& s);
+double toDouble(const std::string& s);
/**
* Reader class reads data from given fd in streaming fashion.
@@ -69,23 +88,29 @@
{
public:
Reader(const int fd);
- Reader(const int fd, const size_t capacity);
~Reader();
- bool readLine(std::string* line, const char newline = DEFAULT_NEWLINE);
+ bool readLine(std::string* line);
bool ok(std::string* error);
private:
- int mFd; // set mFd to -1 when read EOF()
- const size_t mMaxSize;
- size_t mBufSize;
- char* mBuf; // implements a circular buffer
-
- int mRead;
- int mFlushed;
+ FILE* mFile;
std::string mStatus;
- // end of read
- inline bool EOR() { return mFd == -1 && mBufSize == 0; };
+};
+
+class EnumTypeMap
+{
+public:
+ EnumTypeMap() {};
+ EnumTypeMap(const char* enumNames[], const uint32_t enumValues[], const int enumCount);
+ ~EnumTypeMap();
+
+ int parseValue(const std::string& value);
+
+private:
+ const char** mEnumNames;
+ const uint32_t* mEnumValues;
+ int mEnumCount;
};
/**
@@ -98,12 +123,15 @@
Table(const char* names[], const uint64_t ids[], const int count);
~Table();
- bool insertField(ProtoOutputStream& proto, const std::string& name, const std::string& value);
+ // Add enum names to values for parsing purpose.
+ void addEnumTypeMap(const char* field, const char* enumNames[], const uint32_t enumValues[], const int enumSize);
+ bool insertField(ProtoOutputStream* proto, const std::string& name, const std::string& value);
private:
const char** mFieldNames;
const uint64_t* mFieldIds;
const int mFieldCount;
+ map<int, EnumTypeMap> mEnums;
};
#endif // INCIDENT_HELPER_UTIL_H
diff --git a/cmds/incident_helper/src/main.cpp b/cmds/incident_helper/src/main.cpp
index 3da87b9c..5ebe9bd 100644
--- a/cmds/incident_helper/src/main.cpp
+++ b/cmds/incident_helper/src/main.cpp
@@ -16,6 +16,7 @@
#define LOG_TAG "incident_helper"
+#include "parsers/CpuInfoParser.h"
#include "parsers/KernelWakesParser.h"
#include "parsers/PageTypeInfoParser.h"
#include "parsers/ProcrankParser.h"
@@ -54,6 +55,8 @@
return new PageTypeInfoParser();
case 2002:
return new KernelWakesParser();
+ case 2003:
+ return new CpuInfoParser();
default:
return NULL;
}
diff --git a/cmds/incident_helper/src/parsers/CpuInfoParser.cpp b/cmds/incident_helper/src/parsers/CpuInfoParser.cpp
new file mode 100644
index 0000000..3faca00
--- /dev/null
+++ b/cmds/incident_helper/src/parsers/CpuInfoParser.cpp
@@ -0,0 +1,161 @@
+/*
+ * 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 "incident_helper"
+
+#include <android/util/ProtoOutputStream.h>
+
+#include "frameworks/base/core/proto/android/os/cpuinfo.proto.h"
+#include "ih_util.h"
+#include "CpuInfoParser.h"
+
+using namespace android::os;
+
+static void writeSuffixLine(ProtoOutputStream* proto, uint64_t fieldId,
+ const string& line, const string& delimiter,
+ const int count, const char* names[], const uint64_t ids[])
+{
+ record_t record = parseRecord(line, delimiter);
+ long long token = proto->start(fieldId);
+ for (int i=0; i<(int)record.size(); i++) {
+ for (int j=0; j<count; j++) {
+ if (stripSuffix(&record[i], names[j], true)) {
+ proto->write(ids[j], toInt(record[i]));
+ break;
+ }
+ }
+ }
+ proto->end(token);
+}
+
+status_t
+CpuInfoParser::Parse(const int in, const int out) const
+{
+ Reader reader(in);
+ string line;
+ header_t header;
+ vector<int> columnIndices; // task table can't be split by purely delimiter, needs column positions.
+ record_t record;
+ int nline = 0;
+ bool nextToSwap = false;
+ bool nextToUsage = false;
+
+ ProtoOutputStream proto;
+ Table table(CpuInfo::Task::_FIELD_NAMES, CpuInfo::Task::_FIELD_IDS, CpuInfo::Task::_FIELD_COUNT);
+ table.addEnumTypeMap("s", CpuInfo::Task::_ENUM_STATUS_NAMES,
+ CpuInfo::Task::_ENUM_STATUS_VALUES, CpuInfo::Task::_ENUM_STATUS_COUNT);
+ table.addEnumTypeMap("pcy", CpuInfo::Task::_ENUM_POLICY_NAMES,
+ CpuInfo::Task::_ENUM_POLICY_VALUES, CpuInfo::Task::_ENUM_POLICY_COUNT);
+
+ // parse line by line
+ while (reader.readLine(&line)) {
+ if (line.empty()) continue;
+
+ nline++;
+
+ if (stripPrefix(&line, "Tasks:")) {
+ writeSuffixLine(&proto, CpuInfo::TASK_STATS, line, COMMA_DELIMITER,
+ CpuInfo::TaskStats::_FIELD_COUNT,
+ CpuInfo::TaskStats::_FIELD_NAMES,
+ CpuInfo::TaskStats::_FIELD_IDS);
+ continue;
+ }
+ if (stripPrefix(&line, "Mem:")) {
+ writeSuffixLine(&proto, CpuInfo::MEM, line, COMMA_DELIMITER,
+ CpuInfo::MemStats::_FIELD_COUNT,
+ CpuInfo::MemStats::_FIELD_NAMES,
+ CpuInfo::MemStats::_FIELD_IDS);
+ continue;
+ }
+ if (stripPrefix(&line, "Swap:")) {
+ writeSuffixLine(&proto, CpuInfo::SWAP, line, COMMA_DELIMITER,
+ CpuInfo::MemStats::_FIELD_COUNT,
+ CpuInfo::MemStats::_FIELD_NAMES,
+ CpuInfo::MemStats::_FIELD_IDS);
+ nextToSwap = true;
+ continue;
+ }
+
+ if (nextToSwap) {
+ writeSuffixLine(&proto, CpuInfo::CPU_USAGE, line, DEFAULT_WHITESPACE,
+ CpuInfo::CpuUsage::_FIELD_COUNT,
+ CpuInfo::CpuUsage::_FIELD_NAMES,
+ CpuInfo::CpuUsage::_FIELD_IDS);
+ nextToUsage = true;
+ nextToSwap = false;
+ continue;
+ }
+
+ // Header of tasks must be next to usage line
+ if (nextToUsage) {
+ // How to parse Header of Tasks:
+ // PID TID USER PR NI[%CPU]S VIRT RES PCY CMD NAME
+ // After parsing, header = { PID, TID, USER, PR, NI, CPU, S, VIRT, RES, PCY, CMD, NAME }
+ // And columnIndices will contain end index of each word.
+ header = parseHeader(line, "[ %]");
+ nextToUsage = false;
+
+ // NAME is not in the list since the last split index is default to the end of line.
+ const char* headerNames[11] = { "PID", "TID", "USER", "PR", "NI", "CPU", "S", "VIRT", "RES", "PCY", "CMD" };
+ size_t lastIndex = 0;
+ for (int i = 0; i < 11; i++) {
+ string s = headerNames[i];
+ lastIndex = line.find(s, lastIndex);
+ if (lastIndex == string::npos) {
+ fprintf(stderr, "Bad Task Header: %s\n", line.c_str());
+ return -1;
+ }
+ lastIndex += s.length();
+ columnIndices.push_back(lastIndex);
+ }
+ // Need to remove the end index of CMD and use the start index of NAME because CMD values contain spaces.
+ // for example: ... CMD NAME
+ // ... Jit thread pool com.google.android.gms.feedback
+ // If use end index of CMD, parsed result = { "Jit", "thread pool com.google.android.gms.feedback" }
+ // If use start index of NAME, parsed result = { "Jit thread pool", "com.google.android.gms.feedback" }
+ int endCMD = columnIndices.back();
+ columnIndices.pop_back();
+ columnIndices.push_back(line.find("NAME", endCMD) - 1);
+ continue;
+ }
+
+ record = parseRecordByColumns(line, columnIndices);
+ if (record.size() != header.size()) {
+ fprintf(stderr, "[%s]Line %d has missing fields:\n%s\n", this->name.string(), nline, line.c_str());
+ continue;
+ }
+
+ long long token = proto.start(CpuInfo::TASKS);
+ for (int i=0; i<(int)record.size(); i++) {
+ if (!table.insertField(&proto, header[i], record[i])) {
+ fprintf(stderr, "[%s]Line %d fails to insert field %s with value %s\n",
+ this->name.string(), nline, header[i].c_str(), record[i].c_str());
+ }
+ }
+ proto.end(token);
+ }
+
+ if (!reader.ok(&line)) {
+ fprintf(stderr, "Bad read from fd %d: %s\n", in, line.c_str());
+ return -1;
+ }
+
+ if (!proto.flush(out)) {
+ fprintf(stderr, "[%s]Error writing proto back\n", this->name.string());
+ return -1;
+ }
+ fprintf(stderr, "[%s]Proto size: %zu bytes\n", this->name.string(), proto.size());
+ return NO_ERROR;
+}
diff --git a/cmds/incident_helper/src/parsers/CpuInfoParser.h b/cmds/incident_helper/src/parsers/CpuInfoParser.h
new file mode 100644
index 0000000..f57bb4e
--- /dev/null
+++ b/cmds/incident_helper/src/parsers/CpuInfoParser.h
@@ -0,0 +1,36 @@
+/*
+ * 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.
+ */
+
+#ifndef CPU_INFO_PARSER_H
+#define CPU_INFO_PARSER_H
+
+#include "TextParserBase.h"
+
+using namespace android;
+
+/**
+ * Cpu info parser, parses text produced by command
+ * 'top -b -n 1 -H -s 6 -o pid,tid,user,pr,ni,%cpu,s,virt,res,pcy,cmd,name'
+ */
+class CpuInfoParser : public TextParserBase {
+public:
+ CpuInfoParser() : TextParserBase(String8("CpuInfoParser")) {};
+ ~CpuInfoParser() {};
+
+ virtual status_t Parse(const int in, const int out) const;
+};
+
+#endif // CPU_INFO_PARSER_H
diff --git a/cmds/incident_helper/src/parsers/KernelWakesParser.cpp b/cmds/incident_helper/src/parsers/KernelWakesParser.cpp
index cc4a1e1..ada4a5d 100644
--- a/cmds/incident_helper/src/parsers/KernelWakesParser.cpp
+++ b/cmds/incident_helper/src/parsers/KernelWakesParser.cpp
@@ -23,8 +23,6 @@
using namespace android::os;
-const std::string LINE_DELIMITER = "\t";
-
status_t
KernelWakesParser::Parse(const int in, const int out) const
{
@@ -42,12 +40,12 @@
if (line.empty()) continue;
// parse head line
if (nline++ == 0) {
- header = parseHeader(line, LINE_DELIMITER);
+ header = parseHeader(line, TAB_DELIMITER);
continue;
}
// parse for each record, the line delimiter is \t only!
- record = parseRecord(line, LINE_DELIMITER);
+ record = parseRecord(line, TAB_DELIMITER);
if (record.size() != header.size()) {
// TODO: log this to incident report!
@@ -57,7 +55,7 @@
long long token = proto.start(KernelWakeSources::WAKEUP_SOURCES);
for (int i=0; i<(int)record.size(); i++) {
- if (!table.insertField(proto, header[i], record[i])) {
+ if (!table.insertField(&proto, header[i], record[i])) {
fprintf(stderr, "[%s]Line %d has bad value %s of %s\n",
this->name.string(), nline, header[i].c_str(), record[i].c_str());
}
diff --git a/cmds/incident_helper/src/parsers/PageTypeInfoParser.cpp b/cmds/incident_helper/src/parsers/PageTypeInfoParser.cpp
index 6047bd1..f1b93ff 100644
--- a/cmds/incident_helper/src/parsers/PageTypeInfoParser.cpp
+++ b/cmds/incident_helper/src/parsers/PageTypeInfoParser.cpp
@@ -23,8 +23,6 @@
using namespace android::os;
-const std::string LINE_DELIMITER = ",";
-
status_t
PageTypeInfoParser::Parse(const int in, const int out) const
{
@@ -44,37 +42,37 @@
continue;
}
- if (hasPrefix(&line, "Page block order:")) {
+ if (stripPrefix(&line, "Page block order:")) {
pageBlockOrder = toInt(line);
proto.write(PageTypeInfo::PAGE_BLOCK_ORDER, pageBlockOrder);
continue;
}
- if (hasPrefix(&line, "Pages per block:")) {
+ if (stripPrefix(&line, "Pages per block:")) {
proto.write(PageTypeInfo::PAGES_PER_BLOCK, toInt(line));
continue;
}
- if (hasPrefix(&line, "Free pages count per migrate type at order")) {
+ if (stripPrefix(&line, "Free pages count per migrate type at order")) {
migrateTypeSession = true;
continue;
}
- if (hasPrefix(&line, "Number of blocks type")) {
+ if (stripPrefix(&line, "Number of blocks type")) {
blockHeader = parseHeader(line);
continue;
}
- record_t record = parseRecord(line, LINE_DELIMITER);
+ record_t record = parseRecord(line, COMMA_DELIMITER);
if (migrateTypeSession && record.size() == 3) {
long long token = proto.start(PageTypeInfo::MIGRATE_TYPES);
// expect part 0 starts with "Node"
- if (hasPrefix(&record[0], "Node")) {
+ if (stripPrefix(&record[0], "Node")) {
proto.write(MigrateTypeProto::NODE, toInt(record[0]));
} else return BAD_VALUE;
// expect part 1 starts with "zone"
- if (hasPrefix(&record[1], "zone")) {
+ if (stripPrefix(&record[1], "zone")) {
proto.write(MigrateTypeProto::ZONE, record[1]);
} else return BAD_VALUE;
// expect part 2 starts with "type"
- if (hasPrefix(&record[2], "type")) {
+ if (stripPrefix(&record[2], "type")) {
// expect the rest of part 2 has number of (pageBlockOrder + 2) parts
// An example looks like:
// header line: type 0 1 2 3 4 5 6 7 8 9 10
@@ -94,16 +92,16 @@
proto.end(token);
} else if (!blockHeader.empty() && record.size() == 2) {
long long token = proto.start(PageTypeInfo::BLOCKS);
- if (hasPrefix(&record[0], "Node")) {
+ if (stripPrefix(&record[0], "Node")) {
proto.write(BlockProto::NODE, toInt(record[0]));
} else return BAD_VALUE;
- if (hasPrefix(&record[1], "zone")) {
+ if (stripPrefix(&record[1], "zone")) {
record_t blockCounts = parseRecord(record[1]);
proto.write(BlockProto::ZONE, blockCounts[0]);
for (size_t i=0; i<blockHeader.size(); i++) {
- if (!table.insertField(proto, blockHeader[i], blockCounts[i+1])) {
+ if (!table.insertField(&proto, blockHeader[i], blockCounts[i+1])) {
return BAD_VALUE;
}
}
diff --git a/cmds/incident_helper/src/parsers/ProcrankParser.cpp b/cmds/incident_helper/src/parsers/ProcrankParser.cpp
index 93f970f..a4eb0fd 100644
--- a/cmds/incident_helper/src/parsers/ProcrankParser.cpp
+++ b/cmds/incident_helper/src/parsers/ProcrankParser.cpp
@@ -46,11 +46,11 @@
continue;
}
- if (hasPrefix(&line, "ZRAM:")) {
+ if (stripPrefix(&line, "ZRAM:")) {
zram = line;
continue;
}
- if (hasPrefix(&line, "RAM:")) {
+ if (stripPrefix(&line, "RAM:")) {
ram = line;
continue;
}
@@ -68,7 +68,7 @@
long long token = proto.start(Procrank::PROCESSES);
for (int i=0; i<(int)record.size(); i++) {
- if (!table.insertField(proto, header[i], record[i])) {
+ if (!table.insertField(&proto, header[i], record[i])) {
fprintf(stderr, "[%s]Line %d has bad value %s of %s\n",
this->name.string(), nline, header[i].c_str(), record[i].c_str());
}
@@ -82,7 +82,7 @@
record = parseRecord(total);
long long token = proto.start(SummaryProto::TOTAL);
for (int i=(int)record.size(); i>0; i--) {
- table.insertField(proto, header[header.size() - i].c_str(), record[record.size() - i].c_str());
+ table.insertField(&proto, header[header.size() - i].c_str(), record[record.size() - i].c_str());
}
proto.end(token);
}
diff --git a/cmds/incident_helper/testdata/cpuinfo.txt b/cmds/incident_helper/testdata/cpuinfo.txt
new file mode 100644
index 0000000..ec4a839
--- /dev/null
+++ b/cmds/incident_helper/testdata/cpuinfo.txt
@@ -0,0 +1,15 @@
+Tasks: 2038 total, 1 running,2033 sleeping, 0 stopped, 0 zombie
+
+Mem: 3842668k total, 3761936k used, 80732k free, 220188k buffers
+
+Swap: 524284k total, 25892k used, 498392k free, 1316952k cached
+
+400%cpu 17%user 0%nice 43%sys 338%idle 0%iow 0%irq 1%sirq 0%host
+
+ PID TID USER PR NI[%CPU]S VIRT RES PCY CMD NAME
+
+
+29438 29438 rootabcdefghij 20 0 57.9 R 14M 3.8M top test top
+ 916 916 system 18 -2 1.4 S 4.6G 404M fg system_server system_server
+ 28 28 root -2 0 1.4 S 0 0 bg rcuc/3 [rcuc/3]
+ 27 27 root RT 0 1.4 S 0 0 ta migration/3 [migration/3]
\ No newline at end of file
diff --git a/cmds/incident_helper/tests/CpuInfoParser_test.cpp b/cmds/incident_helper/tests/CpuInfoParser_test.cpp
new file mode 100644
index 0000000..57ad15c
--- /dev/null
+++ b/cmds/incident_helper/tests/CpuInfoParser_test.cpp
@@ -0,0 +1,158 @@
+/*
+ * 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.
+ */
+
+#include "CpuInfoParser.h"
+
+#include "frameworks/base/core/proto/android/os/cpuinfo.pb.h"
+
+#include <android-base/file.h>
+#include <android-base/test_utils.h>
+#include <gmock/gmock.h>
+#include <google/protobuf/message.h>
+#include <gtest/gtest.h>
+#include <string.h>
+#include <fcntl.h>
+
+using namespace android::base;
+using namespace android::os;
+using namespace std;
+using ::testing::StrEq;
+using ::testing::Test;
+using ::testing::internal::CaptureStderr;
+using ::testing::internal::CaptureStdout;
+using ::testing::internal::GetCapturedStderr;
+using ::testing::internal::GetCapturedStdout;
+
+class CpuInfoParserTest : public Test {
+public:
+ virtual void SetUp() override {
+ ASSERT_TRUE(tf.fd != -1);
+ }
+
+ string getSerializedString(::google::protobuf::Message& message) {
+ string expectedStr;
+ message.SerializeToFileDescriptor(tf.fd);
+ ReadFileToString(tf.path, &expectedStr);
+ return expectedStr;
+ }
+
+protected:
+ TemporaryFile tf;
+
+ const string kTestPath = GetExecutableDirectory();
+ const string kTestDataPath = kTestPath + "/testdata/";
+};
+
+TEST_F(CpuInfoParserTest, HasSwapInfo) {
+ const string testFile = kTestDataPath + "cpuinfo.txt";
+ CpuInfoParser parser;
+ CpuInfo expected;
+
+ CpuInfo::TaskStats* taskStats = expected.mutable_task_stats();
+ taskStats->set_total(2038);
+ taskStats->set_running(1);
+ taskStats->set_sleeping(2033);
+ taskStats->set_stopped(0);
+ taskStats->set_zombie(0);
+
+ CpuInfo::MemStats* mem = expected.mutable_mem();
+ mem->set_total(3842668);
+ mem->set_used(3761936);
+ mem->set_free(80732);
+ mem->set_buffers(220188);
+
+ CpuInfo::MemStats* swap = expected.mutable_swap();
+ swap->set_total(524284);
+ swap->set_used(25892);
+ swap->set_free(498392);
+ swap->set_cached(1316952);
+
+ CpuInfo::CpuUsage* usage = expected.mutable_cpu_usage();
+ usage->set_cpu(400);
+ usage->set_user(17);
+ usage->set_nice(0);
+ usage->set_sys(43);
+ usage->set_idle(338);
+ usage->set_iow(0);
+ usage->set_irq(0);
+ usage->set_sirq(1);
+ usage->set_host(0);
+
+ // This is a special line which is able to be parsed by the CpuInfoParser
+ CpuInfo::Task* task1 = expected.add_tasks();
+ task1->set_pid(29438);
+ task1->set_tid(29438);
+ task1->set_user("rootabcdefghij");
+ task1->set_pr("20");
+ task1->set_ni(0);
+ task1->set_cpu(57.9);
+ task1->set_s(CpuInfo::Task::STATUS_R);
+ task1->set_virt("14M");
+ task1->set_res("3.8M");
+ task1->set_pcy(CpuInfo::Task::POLICY_UNKNOWN);
+ task1->set_cmd("top test");
+ task1->set_name("top");
+
+ CpuInfo::Task* task2 = expected.add_tasks();
+ task2->set_pid(916);
+ task2->set_tid(916);
+ task2->set_user("system");
+ task2->set_pr("18");
+ task2->set_ni(-2);
+ task2->set_cpu(1.4);
+ task2->set_s(CpuInfo::Task::STATUS_S);
+ task2->set_virt("4.6G");
+ task2->set_res("404M");
+ task2->set_pcy(CpuInfo::Task::POLICY_fg);
+ task2->set_cmd("system_server");
+ task2->set_name("system_server");
+
+ CpuInfo::Task* task3 = expected.add_tasks();
+ task3->set_pid(28);
+ task3->set_tid(28);
+ task3->set_user("root");
+ task3->set_pr("-2");
+ task3->set_ni(0);
+ task3->set_cpu(1.4);
+ task3->set_s(CpuInfo::Task::STATUS_S);
+ task3->set_virt("0");
+ task3->set_res("0");
+ task3->set_pcy(CpuInfo::Task::POLICY_bg);
+ task3->set_cmd("rcuc/3");
+ task3->set_name("[rcuc/3]");
+
+ CpuInfo::Task* task4 = expected.add_tasks();
+ task4->set_pid(27);
+ task4->set_tid(27);
+ task4->set_user("root");
+ task4->set_pr("RT");
+ task4->set_ni(0);
+ task4->set_cpu(1.4);
+ task4->set_s(CpuInfo::Task::STATUS_S);
+ task4->set_virt("0");
+ task4->set_res("0");
+ task4->set_pcy(CpuInfo::Task::POLICY_ta);
+ task4->set_cmd("migration/3");
+ task4->set_name("[migration/3]");
+
+ int fd = open(testFile.c_str(), O_RDONLY);
+ ASSERT_TRUE(fd != -1);
+
+ CaptureStdout();
+ ASSERT_EQ(NO_ERROR, parser.Parse(fd, STDOUT_FILENO));
+ EXPECT_EQ(GetCapturedStdout(), getSerializedString(expected));
+ close(fd);
+}
diff --git a/cmds/incident_helper/tests/ih_util_test.cpp b/cmds/incident_helper/tests/ih_util_test.cpp
index da88ee3..5740b33 100644
--- a/cmds/incident_helper/tests/ih_util_test.cpp
+++ b/cmds/incident_helper/tests/ih_util_test.cpp
@@ -62,10 +62,63 @@
EXPECT_EQ(expected, result);
}
+TEST(IhUtilTest, ParseRecordByColumns) {
+ record_t result, expected;
+ std::vector<int> indices = { 3, 10 };
+
+ result = parseRecordByColumns("12345", indices);
+ expected = {};
+ EXPECT_EQ(expected, result);
+
+ result = parseRecordByColumns("abc \t2345 6789 ", indices);
+ expected = { "abc", "2345", "6789" };
+ EXPECT_EQ(expected, result);
+
+ result = parseRecordByColumns("abc \t23456789 bob", indices);
+ expected = { "abc", "23456789", "bob" };
+ EXPECT_EQ(expected, result);
+}
+
+TEST(IhUtilTest, stripPrefix) {
+ string data1 = "Swap: abc ";
+ EXPECT_TRUE(stripPrefix(&data1, "Swap:"));
+ EXPECT_THAT(data1, StrEq("abc"));
+
+ string data2 = "Swap: abc ";
+ EXPECT_FALSE(stripPrefix(&data2, "Total:"));
+ EXPECT_THAT(data2, StrEq("Swap: abc "));
+
+ string data3 = "Swap: abc ";
+ EXPECT_TRUE(stripPrefix(&data3, "Swa"));
+ EXPECT_THAT(data3, StrEq("p: abc"));
+
+ string data4 = "Swap: abc ";
+ EXPECT_FALSE(stripPrefix(&data4, "Swa", true));
+ EXPECT_THAT(data4, StrEq("Swap: abc "));
+}
+
+TEST(IhUtilTest, stripSuffix) {
+ string data1 = " 243%abc";
+ EXPECT_TRUE(stripSuffix(&data1, "abc"));
+ EXPECT_THAT(data1, StrEq("243%"));
+
+ string data2 = " 243%abc";
+ EXPECT_FALSE(stripSuffix(&data2, "Not right"));
+ EXPECT_THAT(data2, StrEq(" 243%abc"));
+
+ string data3 = " 243%abc";
+ EXPECT_TRUE(stripSuffix(&data3, "bc"));
+ EXPECT_THAT(data3, StrEq("243%a"));
+
+ string data4 = " 243%abc";
+ EXPECT_FALSE(stripSuffix(&data4, "bc", true));
+ EXPECT_THAT(data4, StrEq(" 243%abc"));
+}
+
TEST(IhUtilTest, Reader) {
TemporaryFile tf;
ASSERT_NE(tf.fd, -1);
- ASSERT_TRUE(WriteStringToFile("test string\nsecond\nooo\n", tf.path, false));
+ ASSERT_TRUE(WriteStringToFile("test string\nsecond\nooo\n", tf.path));
Reader r(tf.fd);
string line;
@@ -79,40 +132,22 @@
ASSERT_TRUE(r.ok(&line));
}
-TEST(IhUtilTest, ReaderSmallBufSize) {
- TemporaryFile tf;
- ASSERT_NE(tf.fd, -1);
- ASSERT_TRUE(WriteStringToFile("test string\nsecond\nooiecccojreo", tf.path, false));
-
- Reader r(tf.fd, 5);
- string line;
- ASSERT_TRUE(r.readLine(&line));
- EXPECT_THAT(line, StrEq("test string"));
- ASSERT_TRUE(r.readLine(&line));
- EXPECT_THAT(line, StrEq("second"));
- ASSERT_TRUE(r.readLine(&line));
- EXPECT_THAT(line, StrEq("ooiecccojreo"));
- ASSERT_FALSE(r.readLine(&line));
- ASSERT_TRUE(r.ok(&line));
-}
-
TEST(IhUtilTest, ReaderEmpty) {
TemporaryFile tf;
ASSERT_NE(tf.fd, -1);
- ASSERT_TRUE(WriteStringToFile("", tf.path, false));
+ ASSERT_TRUE(WriteStringToFile("", tf.path));
Reader r(tf.fd);
string line;
- ASSERT_TRUE(r.readLine(&line));
- EXPECT_THAT(line, StrEq(""));
ASSERT_FALSE(r.readLine(&line));
+ EXPECT_THAT(line, StrEq(""));
ASSERT_TRUE(r.ok(&line));
}
TEST(IhUtilTest, ReaderMultipleEmptyLines) {
TemporaryFile tf;
ASSERT_NE(tf.fd, -1);
- ASSERT_TRUE(WriteStringToFile("\n\n", tf.path, false));
+ ASSERT_TRUE(WriteStringToFile("\n\n", tf.path));
Reader r(tf.fd);
string line;
@@ -130,15 +165,7 @@
string line;
EXPECT_FALSE(r.readLine(&line));
EXPECT_FALSE(r.ok(&line));
- EXPECT_THAT(line, StrEq("Negative fd"));
-}
-
-TEST(IhUtilTest, ReaderFailedZeroBufferSize) {
- Reader r(23, 0);
- string line;
- EXPECT_FALSE(r.readLine(&line));
- EXPECT_FALSE(r.ok(&line));
- EXPECT_THAT(line, StrEq("Zero buffer capacity"));
+ EXPECT_THAT(line, StrEq("Invalid fd -123"));
}
TEST(IhUtilTest, ReaderFailedBadFd) {
@@ -146,5 +173,5 @@
string line;
EXPECT_FALSE(r.readLine(&line));
EXPECT_FALSE(r.ok(&line));
- EXPECT_THAT(line, StrEq("Fail to read from fd"));
+ EXPECT_THAT(line, StrEq("Invalid fd 1231432"));
}
diff --git a/cmds/incidentd/tests/FdBuffer_test.cpp b/cmds/incidentd/tests/FdBuffer_test.cpp
index 2afa778..3fd2ed8 100644
--- a/cmds/incidentd/tests/FdBuffer_test.cpp
+++ b/cmds/incidentd/tests/FdBuffer_test.cpp
@@ -84,7 +84,7 @@
TEST_F(FdBufferTest, ReadAndWrite) {
std::string testdata = "FdBuffer test string";
- ASSERT_TRUE(WriteStringToFile(testdata, tf.path, false));
+ ASSERT_TRUE(WriteStringToFile(testdata, tf.path));
ASSERT_EQ(NO_ERROR, buffer.read(tf.fd, READ_TIMEOUT));
AssertBufferReadSuccessful(testdata.size());
AssertBufferContent(testdata.c_str());
@@ -97,7 +97,7 @@
TEST_F(FdBufferTest, ReadAndIterate) {
std::string testdata = "FdBuffer test string";
- ASSERT_TRUE(WriteStringToFile(testdata, tf.path, false));
+ ASSERT_TRUE(WriteStringToFile(testdata, tf.path));
ASSERT_EQ(NO_ERROR, buffer.read(tf.fd, READ_TIMEOUT));
int i=0;
@@ -137,7 +137,7 @@
TEST_F(FdBufferTest, ReadInStreamAndWrite) {
std::string testdata = "simply test read in stream";
std::string expected = HEAD + testdata;
- ASSERT_TRUE(WriteStringToFile(testdata, tf.path, false));
+ ASSERT_TRUE(WriteStringToFile(testdata, tf.path));
int pid = fork();
ASSERT_TRUE(pid != -1);
@@ -166,7 +166,7 @@
TEST_F(FdBufferTest, ReadInStreamAndWriteAllAtOnce) {
std::string testdata = "child process flushes only after all data are read.";
std::string expected = HEAD + testdata;
- ASSERT_TRUE(WriteStringToFile(testdata, tf.path, false));
+ ASSERT_TRUE(WriteStringToFile(testdata, tf.path));
int pid = fork();
ASSERT_TRUE(pid != -1);
@@ -196,7 +196,7 @@
}
TEST_F(FdBufferTest, ReadInStreamEmpty) {
- ASSERT_TRUE(WriteStringToFile("", tf.path, false));
+ ASSERT_TRUE(WriteStringToFile("", tf.path));
int pid = fork();
ASSERT_TRUE(pid != -1);
@@ -260,7 +260,7 @@
TEST_F(FdBufferTest, ReadInStreamTimeOut) {
std::string testdata = "timeout test";
- ASSERT_TRUE(WriteStringToFile(testdata, tf.path, false));
+ ASSERT_TRUE(WriteStringToFile(testdata, tf.path));
int pid = fork();
ASSERT_TRUE(pid != -1);
diff --git a/cmds/incidentd/tests/PrivacyBuffer_test.cpp b/cmds/incidentd/tests/PrivacyBuffer_test.cpp
index 84a2a82..ca94623 100644
--- a/cmds/incidentd/tests/PrivacyBuffer_test.cpp
+++ b/cmds/incidentd/tests/PrivacyBuffer_test.cpp
@@ -60,7 +60,7 @@
}
void writeToFdBuffer(string str) {
- ASSERT_TRUE(WriteStringToFile(str, tf.path, false));
+ ASSERT_TRUE(WriteStringToFile(str, tf.path));
ASSERT_EQ(NO_ERROR, buffer.read(tf.fd, 10000));
ASSERT_EQ(str.size(), buffer.size());
}
diff --git a/cmds/incidentd/tests/Section_test.cpp b/cmds/incidentd/tests/Section_test.cpp
index 25b05b2..649e908 100644
--- a/cmds/incidentd/tests/Section_test.cpp
+++ b/cmds/incidentd/tests/Section_test.cpp
@@ -99,7 +99,7 @@
ReportRequestSet requests;
ASSERT_TRUE(tf.fd != -1);
- ASSERT_TRUE(WriteStringToFile("iamtestdata", tf.path, false));
+ ASSERT_TRUE(WriteStringToFile("iamtestdata", tf.path));
requests.setMainFd(STDOUT_FILENO);
@@ -173,7 +173,7 @@
ReportRequestSet requests;
ASSERT_TRUE(tf.fd != -1);
- ASSERT_TRUE(WriteStringToFile(VARINT_FIELD_1 + STRING_FIELD_2 + FIX64_FIELD_3, tf.path, false));
+ ASSERT_TRUE(WriteStringToFile(VARINT_FIELD_1 + STRING_FIELD_2 + FIX64_FIELD_3, tf.path));
requests.setMainFd(STDOUT_FILENO);
@@ -186,7 +186,7 @@
TemporaryFile input;
FileSection fs(NOOP_PARSER, input.path);
ReportRequestSet requests;
- ASSERT_TRUE(WriteStringToFile(VARINT_FIELD_1 + STRING_FIELD_2 + FIX64_FIELD_3, input.path, false));
+ ASSERT_TRUE(WriteStringToFile(VARINT_FIELD_1 + STRING_FIELD_2 + FIX64_FIELD_3, input.path));
IncidentReportArgs args;
args.setAll(true);
@@ -205,7 +205,7 @@
TemporaryFile input;
FileSection fs(NOOP_PARSER, input.path);
ReportRequestSet requests;
- ASSERT_TRUE(WriteStringToFile(VARINT_FIELD_1 + STRING_FIELD_2 + FIX64_FIELD_3, input.path, false));
+ ASSERT_TRUE(WriteStringToFile(VARINT_FIELD_1 + STRING_FIELD_2 + FIX64_FIELD_3, input.path));
IncidentReportArgs args;
args.setAll(true);
@@ -223,7 +223,7 @@
ASSERT_TRUE(output1.fd != -1);
ASSERT_TRUE(output2.fd != -1);
ASSERT_TRUE(output3.fd != -1);
- ASSERT_TRUE(WriteStringToFile(VARINT_FIELD_1 + STRING_FIELD_2 + FIX64_FIELD_3, input.path, false));
+ ASSERT_TRUE(WriteStringToFile(VARINT_FIELD_1 + STRING_FIELD_2 + FIX64_FIELD_3, input.path));
IncidentReportArgs args1, args2, args3;
args1.setAll(true);
@@ -265,7 +265,7 @@
ASSERT_TRUE(output2.fd != -1);
ASSERT_TRUE(output3.fd != -1);
- ASSERT_TRUE(WriteStringToFile(VARINT_FIELD_1 + STRING_FIELD_2 + FIX64_FIELD_3, input.path, false));
+ ASSERT_TRUE(WriteStringToFile(VARINT_FIELD_1 + STRING_FIELD_2 + FIX64_FIELD_3, input.path));
IncidentReportArgs args1, args2, args3, args4;
args1.setAll(true);
diff --git a/cmds/statsd/Android.mk b/cmds/statsd/Android.mk
index 87d318b..0f6d868 100644
--- a/cmds/statsd/Android.mk
+++ b/cmds/statsd/Android.mk
@@ -31,14 +31,16 @@
src/config/ConfigManager.cpp \
src/external/StatsCompanionServicePuller.cpp \
src/external/ResourcePowerManagerPuller.cpp \
- src/external/StatsPullerManager.cpp \
+ src/external/CpuTimePerUidPuller.cpp \
+ src/external/CpuTimePerUidFreqPuller.cpp \
+ src/external/StatsPullerManagerImpl.cpp \
src/logd/LogEvent.cpp \
src/logd/LogListener.cpp \
src/logd/LogReader.cpp \
src/matchers/CombinationLogMatchingTracker.cpp \
src/matchers/matcher_util.cpp \
src/matchers/SimpleLogMatchingTracker.cpp \
- src/metrics/CountAnomalyTracker.cpp \
+ src/anomaly/DiscreteAnomalyTracker.cpp \
src/metrics/MetricProducer.cpp \
src/metrics/EventMetricProducer.cpp \
src/metrics/CountMetricProducer.cpp \
@@ -150,18 +152,20 @@
LOCAL_SRC_FILES := \
$(statsd_common_src) \
tests/AnomalyMonitor_test.cpp \
- tests/ConditionTracker_test.cpp \
+ tests/anomaly/AnomalyTracker_test.cpp \
tests/ConfigManager_test.cpp \
tests/indexed_priority_queue_test.cpp \
tests/LogEntryMatcher_test.cpp \
tests/LogReader_test.cpp \
tests/MetricsManager_test.cpp \
tests/UidMap_test.cpp \
+ tests/condition/CombinationConditionTracker_test.cpp \
+ tests/condition/SimpleConditionTracker_test.cpp \
tests/metrics/OringDurationTracker_test.cpp \
tests/metrics/MaxDurationTracker_test.cpp \
tests/metrics/CountMetricProducer_test.cpp \
- tests/metrics/EventMetricProducer_test.cpp
-
+ tests/metrics/EventMetricProducer_test.cpp \
+ tests/metrics/ValueMetricProducer_test.cpp
LOCAL_STATIC_LIBRARIES := \
libgmock
diff --git a/cmds/statsd/src/StatsLogProcessor.cpp b/cmds/statsd/src/StatsLogProcessor.cpp
index 8910523..8c70bb5 100644
--- a/cmds/statsd/src/StatsLogProcessor.cpp
+++ b/cmds/statsd/src/StatsLogProcessor.cpp
@@ -15,9 +15,9 @@
*/
#include "Log.h"
+#include "statslog.h"
#include "StatsLogProcessor.h"
-#include "frameworks/base/cmds/statsd/src/stats_log.pb.h"
#include "metrics/CountMetricProducer.h"
#include "stats_util.h"
@@ -25,6 +25,14 @@
#include <utils/Errors.h>
using namespace android;
+using android::util::FIELD_COUNT_REPEATED;
+using android::util::FIELD_TYPE_BOOL;
+using android::util::FIELD_TYPE_FLOAT;
+using android::util::FIELD_TYPE_INT32;
+using android::util::FIELD_TYPE_INT64;
+using android::util::FIELD_TYPE_MESSAGE;
+using android::util::FIELD_TYPE_STRING;
+using android::util::ProtoOutputStream;
using std::make_unique;
using std::unique_ptr;
using std::vector;
@@ -33,6 +41,14 @@
namespace os {
namespace statsd {
+// for ConfigMetricsReport
+const int FIELD_ID_CONFIG_KEY = 1;
+const int FIELD_ID_METRICS = 2;
+const int FIELD_ID_UID_MAP = 3;
+// for ConfigKey
+const int FIELD_ID_UID = 1;
+const int FIELD_ID_NAME = 2;
+
StatsLogProcessor::StatsLogProcessor(const sp<UidMap>& uidMap,
const std::function<void(const vector<uint8_t>&)>& pushLog)
: mUidMap(uidMap), mPushLog(pushLog) {
@@ -48,6 +64,22 @@
pair.second->onLogEvent(msg);
flushIfNecessary(msg.GetTimestampNs(), pair.first, pair.second);
}
+
+ // Hard-coded logic to update the isolated uid's in the uid-map.
+ // The field numbers need to be currently updated by hand with stats_events.proto
+ if (msg.GetTagId() == android::util::ISOLATED_UID_CHANGED) {
+ status_t err = NO_ERROR, err2 = NO_ERROR, err3 = NO_ERROR;
+ bool is_create = msg.GetBool(3, &err);
+ auto parent_uid = int(msg.GetLong(1, &err2));
+ auto isolated_uid = int(msg.GetLong(2, &err3));
+ if (err == NO_ERROR && err2 == NO_ERROR && err3 == NO_ERROR) {
+ if (is_create) {
+ mUidMap->assignIsolatedUid(isolated_uid, parent_uid);
+ } else {
+ mUidMap->removeIsolatedUid(isolated_uid, parent_uid);
+ }
+ }
+ }
}
void StatsLogProcessor::OnConfigUpdated(const ConfigKey& key, const StatsdConfig& config) {
@@ -70,27 +102,46 @@
}
}
-ConfigMetricsReport StatsLogProcessor::onDumpReport(const ConfigKey& key) {
- ConfigMetricsReport report;
-
+vector<uint8_t> StatsLogProcessor::onDumpReport(const ConfigKey& key) {
auto it = mMetricsManagers.find(key);
if (it == mMetricsManagers.end()) {
ALOGW("Config source %s does not exist", key.ToString().c_str());
- return report;
+ return vector<uint8_t>();
}
- auto set_key = report.mutable_config_key();
- set_key->set_uid(key.GetUid());
- set_key->set_name(key.GetName());
- for (auto m : it->second->onDumpReport()) {
- // Transfer the vector of StatsLogReport into a field
- // TODO: perhaps we just have bytes being returned from onDumpReport and transfer bytes
- auto dest = report.add_metrics();
- *dest = m;
+ ProtoOutputStream proto;
+
+ // Fill in ConfigKey.
+ long long configKeyToken = proto.start(FIELD_TYPE_MESSAGE | FIELD_ID_CONFIG_KEY);
+ proto.write(FIELD_TYPE_INT32 | FIELD_ID_UID, key.GetUid());
+ proto.write(FIELD_TYPE_STRING | FIELD_ID_NAME, key.GetName());
+ proto.end(configKeyToken);
+
+ // Fill in StatsLogReport's.
+ for (auto& m : it->second->onDumpReport()) {
+ // Add each vector of StatsLogReport into a repeated field.
+ proto.write(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_METRICS,
+ reinterpret_cast<char*>(m.get()->data()), m.get()->size());
}
- auto temp = mUidMap->getOutput(key);
- report.mutable_uid_map()->Swap(&temp);
- return report;
+
+ // Fill in UidMap.
+ auto uidMap = mUidMap->getOutput(key);
+ const int uidMapSize = uidMap.ByteSize();
+ char uidMapBuffer[uidMapSize];
+ uidMap.SerializeToArray(&uidMapBuffer[0], uidMapSize);
+ proto.write(FIELD_TYPE_MESSAGE | FIELD_ID_UID_MAP, uidMapBuffer, uidMapSize);
+
+ vector<uint8_t> buffer(proto.size());
+ size_t pos = 0;
+ auto iter = proto.data();
+ while (iter.readBuffer() != NULL) {
+ size_t toRead = iter.currentToRead();
+ std::memcpy(&buffer[pos], iter.readBuffer(), toRead);
+ pos += toRead;
+ iter.rp()->move(toRead);
+ }
+
+ return buffer;
}
void StatsLogProcessor::OnConfigRemoved(const ConfigKey& key) {
diff --git a/cmds/statsd/src/StatsLogProcessor.h b/cmds/statsd/src/StatsLogProcessor.h
index 0083827..f38d715 100644
--- a/cmds/statsd/src/StatsLogProcessor.h
+++ b/cmds/statsd/src/StatsLogProcessor.h
@@ -41,8 +41,7 @@
void OnConfigUpdated(const ConfigKey& key, const StatsdConfig& config);
void OnConfigRemoved(const ConfigKey& key);
- // TODO: Once we have the ProtoOutputStream in c++, we can just return byte array.
- ConfigMetricsReport onDumpReport(const ConfigKey& key);
+ vector<uint8_t> onDumpReport(const ConfigKey& key);
/* Request a flush through a binder call. */
void flush();
diff --git a/cmds/statsd/src/StatsService.h b/cmds/statsd/src/StatsService.h
index 1d7e5a61..fa92f65 100644
--- a/cmds/statsd/src/StatsService.h
+++ b/cmds/statsd/src/StatsService.h
@@ -157,7 +157,7 @@
/**
* Fetches external metrics.
*/
- StatsPullerManager& mStatsPullerManager = StatsPullerManager::GetInstance();
+ StatsPullerManager mStatsPullerManager;
/**
* Tracks the configurations that have been passed to statsd.
diff --git a/cmds/statsd/src/anomaly/AnomalyMonitor.h b/cmds/statsd/src/anomaly/AnomalyMonitor.h
index e2ac623..d9207e9 100644
--- a/cmds/statsd/src/anomaly/AnomalyMonitor.h
+++ b/cmds/statsd/src/anomaly/AnomalyMonitor.h
@@ -55,6 +55,7 @@
};
};
+// TODO: Rename this file to AnomalyAlarmMonitor.
/**
* Manages alarms for Anomaly Detection.
*/
@@ -95,6 +96,15 @@
unordered_set<sp<const AnomalyAlarm>, SpHash<AnomalyAlarm>> popSoonerThan(
uint32_t timestampSec);
+ // TODO: Function that uses popSoonerThan to get all alarms that have fired, and then
+ // iterates over all DurationAnomalyTracker, looking for those alarms. When they're found,
+ // have them declareAnomaly on those alarms. This means that DurationAnomalyTracker
+ // must be thread-safe (since this is being called on a different thread). There is no
+ // worry about missing the alarms (due to them being cancelled after this function being called)
+ // because DurationAnomalyTracker guarantees that it checks for anaomlies when it cancels
+ // alarms anyway.
+ // void declareAnomalies(uint32_t timestampSec);
+
/**
* Returns the projected alarm timestamp that is registered with
* StatsCompanionService. This may not be equal to the soonest alarm,
diff --git a/cmds/statsd/src/anomaly/DiscreteAnomalyTracker.cpp b/cmds/statsd/src/anomaly/DiscreteAnomalyTracker.cpp
new file mode 100644
index 0000000..9c9bde9
--- /dev/null
+++ b/cmds/statsd/src/anomaly/DiscreteAnomalyTracker.cpp
@@ -0,0 +1,159 @@
+/*
+ * 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 DEBUG true // STOPSHIP if true
+#include "Log.h"
+
+#include "DiscreteAnomalyTracker.h"
+
+#include <time.h>
+
+namespace android {
+namespace os {
+namespace statsd {
+
+DiscreteAnomalyTracker::DiscreteAnomalyTracker(const Alert& alert) : mAlert(alert) {
+ VLOG("DiscreteAnomalyTracker() called");
+ if (mAlert.number_of_buckets() <= 0) {
+ ALOGE("Cannot create DiscreteAnomalyTracker with %lld buckets",
+ (long long)mAlert.number_of_buckets());
+ return;
+ }
+ mPastBuckets.resize(mAlert.number_of_buckets());
+ reset(); // initialization
+}
+
+DiscreteAnomalyTracker::~DiscreteAnomalyTracker() {
+ VLOG("~DiscreteAnomalyTracker() called");
+}
+
+void DiscreteAnomalyTracker::reset() {
+ VLOG("reset() called.");
+ mPastBuckets.clear();
+ mPastBuckets.resize(mAlert.number_of_buckets());
+ mSumOverPastBuckets.clear();
+ mCurrentBucketIndex = -1;
+ mLastAlarmAtBucketIndex = -1;
+ mAnomalyDeclared = 0;
+}
+
+size_t DiscreteAnomalyTracker::index(int64_t bucketNum) {
+ return bucketNum % mAlert.number_of_buckets();
+}
+
+void DiscreteAnomalyTracker::addOrUpdateBucket(std::shared_ptr<const DimToValMap> BucketValues,
+ int64_t bucketIndex) {
+ VLOG("addPastBucket() called.");
+ if (bucketIndex <= mCurrentBucketIndex - mAlert.number_of_buckets()) {
+ ALOGE("Cannot add a past bucket %lld units in past", (long long)bucketIndex);
+ return;
+ }
+
+ // Empty out old mPastBuckets[i] values and update mSumOverPastBuckets.
+ if (bucketIndex - mCurrentBucketIndex >= mAlert.number_of_buckets()) {
+ mPastBuckets.clear();
+ mPastBuckets.resize(mAlert.number_of_buckets());
+ mSumOverPastBuckets.clear();
+ } else {
+ for (int64_t i = std::max(
+ 0LL, (long long)(mCurrentBucketIndex - mAlert.number_of_buckets() + 1));
+ i < bucketIndex - mAlert.number_of_buckets(); i++) {
+ const int idx = index(i);
+ subtractBucketFromSum(mPastBuckets[idx]);
+ mPastBuckets[idx] = nullptr; // release (but not clear) the old bucket.
+ }
+ }
+ subtractBucketFromSum(mPastBuckets[index(bucketIndex)]);
+ mPastBuckets[index(bucketIndex)] = nullptr; // release (but not clear) the old bucket.
+
+ // Replace the oldest bucket with the new bucket we are adding.
+ mPastBuckets[index(bucketIndex)] = BucketValues;
+ addBucketToSum(BucketValues);
+
+ mCurrentBucketIndex = std::max(mCurrentBucketIndex, bucketIndex);
+}
+
+void DiscreteAnomalyTracker::subtractBucketFromSum(const shared_ptr<const DimToValMap>& bucket) {
+ if (bucket == nullptr) {
+ return;
+ }
+ // For each dimension present in the bucket, subtract its value from its corresponding sum.
+ for (const auto& keyValuePair : *bucket) {
+ auto itr = mSumOverPastBuckets.find(keyValuePair.first);
+ if (itr == mSumOverPastBuckets.end()) {
+ continue;
+ }
+ itr->second -= keyValuePair.second;
+ // TODO: No need to look up the object twice like this. Use a var.
+ if (itr->second == 0) {
+ mSumOverPastBuckets.erase(itr);
+ }
+ }
+}
+
+void DiscreteAnomalyTracker::addBucketToSum(const shared_ptr<const DimToValMap>& bucket) {
+ if (bucket == nullptr) {
+ return;
+ }
+ // For each dimension present in the bucket, add its value to its corresponding sum.
+ for (const auto& keyValuePair : *bucket) {
+ mSumOverPastBuckets[keyValuePair.first] += keyValuePair.second;
+ }
+}
+
+bool DiscreteAnomalyTracker::detectAnomaly() {
+ for (auto itr = mSumOverPastBuckets.begin(); itr != mSumOverPastBuckets.end(); itr++) {
+ if (mAlert.has_trigger_if_sum_gt() && itr->second > mAlert.trigger_if_sum_gt()) {
+ return true;
+ }
+ }
+ return false;
+}
+
+void DiscreteAnomalyTracker::declareAndDeclareAnomaly() {
+ if (detectAnomaly()) {
+ declareAnomaly();
+ }
+}
+
+void DiscreteAnomalyTracker::declareAnomaly() {
+ if (mLastAlarmAtBucketIndex >= 0 && mCurrentBucketIndex - mLastAlarmAtBucketIndex <=
+ (long long)mAlert.refractory_period_in_buckets()) {
+ VLOG("Skipping anomaly check since within refractory period");
+ return;
+ }
+ mAnomalyDeclared++;
+ // TODO(guardrail): Consider guarding against too short refractory periods.
+ mLastAlarmAtBucketIndex = mCurrentBucketIndex;
+
+ if (mAlert.has_incidentd_details()) {
+ const Alert_IncidentdDetails& incident = mAlert.incidentd_details();
+ if (incident.has_alert_name()) {
+ ALOGW("An anomaly (%s) has occurred! Informing incidentd.",
+ incident.alert_name().c_str());
+ } else {
+ // TODO: Can construct a name based on the criteria (and/or relay the criteria).
+ ALOGW("An anomaly (nameless) has occurred! Informing incidentd.");
+ }
+ // TODO: Send incidentd_details.name and incidentd_details.incidentd_sections to incidentd
+ } else {
+ ALOGW("An anomaly has occurred! (But informing incidentd not requested.)");
+ }
+}
+
+} // namespace statsd
+} // namespace os
+} // namespace android
diff --git a/cmds/statsd/src/anomaly/DiscreteAnomalyTracker.h b/cmds/statsd/src/anomaly/DiscreteAnomalyTracker.h
new file mode 100644
index 0000000..ed7d5d7
--- /dev/null
+++ b/cmds/statsd/src/anomaly/DiscreteAnomalyTracker.h
@@ -0,0 +1,91 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include <gtest/gtest_prod.h>
+#include "frameworks/base/cmds/statsd/src/statsd_config.pb.h" // Alert
+#include "stats_util.h" // HashableDimensionKey and DimToValMap
+
+#include <memory> // unique_ptr
+#include <stdlib.h>
+
+namespace android {
+namespace os {
+namespace statsd {
+
+using std::unordered_map;
+using std::shared_ptr;
+
+// This anomaly track assmues that all values are non-negative.
+class DiscreteAnomalyTracker {
+ public:
+ DiscreteAnomalyTracker(const Alert& alert);
+
+ virtual ~DiscreteAnomalyTracker();
+
+ // Adds a new bucket or updates an existing bucket.
+ // Bucket index starts from 0.
+ void addOrUpdateBucket(std::shared_ptr<const DimToValMap> BucketValues, int64_t bucketIndex);
+
+ // Returns true if detected anomaly for the existing buckets on one or more dimension keys.
+ bool detectAnomaly();
+
+ // Informs incidentd about the detected alert.
+ void declareAnomaly();
+
+ // Detects the alert and informs the incidentd when applicable.
+ void declareAndDeclareAnomaly();
+
+private:
+ // statsd_config.proto Alert message that defines this tracker.
+ const Alert mAlert;
+
+ // The exisiting bucket list.
+ std::vector<shared_ptr<const DimToValMap>> mPastBuckets;
+
+ // Sum over all existing buckets cached in mPastBuckets.
+ DimToValMap mSumOverPastBuckets;
+
+ // Current bucket index of the current anomaly detection window. Bucket index starts from 0.
+ int64_t mCurrentBucketIndex = -1;
+
+ // The bucket index when the last anomaly was declared.
+ int64_t mLastAlarmAtBucketIndex = -1;
+
+ // The total number of declared anomalies.
+ int64_t mAnomalyDeclared = 0;
+
+ // Add the information in the given bucket to mSumOverPastBuckets.
+ void addBucketToSum(const shared_ptr<const DimToValMap>& bucket);
+
+ // Subtract the information in the given bucket from mSumOverPastBuckets
+ // and remove any items with value 0.
+ void subtractBucketFromSum(const shared_ptr<const DimToValMap>& bucket);
+
+ // Calculates the corresponding bucket index within the circular array.
+ size_t index(int64_t bucketNum);
+
+ // Resets all data. For use when all the data gets stale.
+ void reset();
+
+ FRIEND_TEST(AnomalyTrackerTest, TestConsecutiveBuckets);
+ FRIEND_TEST(AnomalyTrackerTest, TestSparseBuckets);
+};
+
+} // namespace statsd
+} // namespace os
+} // namespace android
diff --git a/cmds/statsd/src/condition/CombinationConditionTracker.cpp b/cmds/statsd/src/condition/CombinationConditionTracker.cpp
index 953bcb3..41f5fca 100644
--- a/cmds/statsd/src/condition/CombinationConditionTracker.cpp
+++ b/cmds/statsd/src/condition/CombinationConditionTracker.cpp
@@ -115,49 +115,47 @@
evaluateCombinationCondition(mChildren, mLogicalOperation, conditionCache);
}
-bool CombinationConditionTracker::evaluateCondition(
+void CombinationConditionTracker::evaluateCondition(
const LogEvent& event, const std::vector<MatchingState>& eventMatcherValues,
const std::vector<sp<ConditionTracker>>& mAllConditions,
std::vector<ConditionState>& nonSlicedConditionCache,
- std::vector<bool>& nonSlicedChangedCache, vector<bool>& slicedConditionChanged) {
+ std::vector<bool>& conditionChangedCache) {
// value is up to date.
if (nonSlicedConditionCache[mIndex] != ConditionState::kNotEvaluated) {
- return false;
+ return;
}
for (const int childIndex : mChildren) {
if (nonSlicedConditionCache[childIndex] == ConditionState::kNotEvaluated) {
const sp<ConditionTracker>& child = mAllConditions[childIndex];
child->evaluateCondition(event, eventMatcherValues, mAllConditions,
- nonSlicedConditionCache, nonSlicedChangedCache,
- slicedConditionChanged);
+ nonSlicedConditionCache, conditionChangedCache);
}
}
- ConditionState newCondition =
- evaluateCombinationCondition(mChildren, mLogicalOperation, nonSlicedConditionCache);
+ if (!mSliced) {
+ ConditionState newCondition =
+ evaluateCombinationCondition(mChildren, mLogicalOperation, nonSlicedConditionCache);
- bool nonSlicedChanged = (mNonSlicedConditionState != newCondition);
- mNonSlicedConditionState = newCondition;
+ bool nonSlicedChanged = (mNonSlicedConditionState != newCondition);
+ mNonSlicedConditionState = newCondition;
- nonSlicedConditionCache[mIndex] = mNonSlicedConditionState;
+ nonSlicedConditionCache[mIndex] = mNonSlicedConditionState;
- nonSlicedChangedCache[mIndex] = nonSlicedChanged;
-
- if (mSliced) {
+ conditionChangedCache[mIndex] = nonSlicedChanged;
+ } else {
for (const int childIndex : mChildren) {
// If any of the sliced condition in children condition changes, the combination
// condition may be changed too.
- if (slicedConditionChanged[childIndex]) {
- slicedConditionChanged[mIndex] = true;
+ if (conditionChangedCache[childIndex]) {
+ conditionChangedCache[mIndex] = true;
break;
}
}
+ nonSlicedConditionCache[mIndex] = ConditionState::kUnknown;
ALOGD("CombinationCondition %s sliced may changed? %d", mName.c_str(),
- slicedConditionChanged[mIndex] == true);
+ conditionChangedCache[mIndex] == true);
}
-
- return nonSlicedChanged;
}
} // namespace statsd
diff --git a/cmds/statsd/src/condition/CombinationConditionTracker.h b/cmds/statsd/src/condition/CombinationConditionTracker.h
index dbdb3b7..3d2c6bb 100644
--- a/cmds/statsd/src/condition/CombinationConditionTracker.h
+++ b/cmds/statsd/src/condition/CombinationConditionTracker.h
@@ -35,12 +35,11 @@
const std::unordered_map<std::string, int>& conditionNameIndexMap,
std::vector<bool>& stack) override;
- bool evaluateCondition(const LogEvent& event,
+ void evaluateCondition(const LogEvent& event,
const std::vector<MatchingState>& eventMatcherValues,
const std::vector<sp<ConditionTracker>>& mAllConditions,
std::vector<ConditionState>& conditionCache,
- std::vector<bool>& changedCache,
- std::vector<bool>& slicedConditionMayChanged) override;
+ std::vector<bool>& changedCache) override;
void isConditionMet(const std::map<std::string, HashableDimensionKey>& conditionParameters,
const std::vector<sp<ConditionTracker>>& allConditions,
diff --git a/cmds/statsd/src/condition/ConditionTracker.h b/cmds/statsd/src/condition/ConditionTracker.h
index bb5ddeb..0ac7ef3 100644
--- a/cmds/statsd/src/condition/ConditionTracker.h
+++ b/cmds/statsd/src/condition/ConditionTracker.h
@@ -56,25 +56,20 @@
std::vector<bool>& stack) = 0;
// evaluate current condition given the new event.
- // return true if the condition state changed, false if the condition state is not changed.
// event: the new log event
// eventMatcherValues: the results of the LogMatcherTrackers. LogMatcherTrackers always process
// event before ConditionTrackers, because ConditionTracker depends on
// LogMatchingTrackers.
// mAllConditions: the list of all ConditionTracker
// conditionCache: the cached non-sliced condition of the ConditionTrackers for this new event.
- // nonSlicedConditionChanged: the bit map to record whether non-sliced condition has changed.
- // slicedConditionMayChanged: the bit map to record whether sliced condition may have changed.
- // Because sliced condition needs parameters to determine the value. So the sliced
- // condition is not pushed to metrics. We only inform the relevant metrics that the sliced
- // condition may have changed, and metrics should pull the conditions that they are
- // interested in.
- virtual bool evaluateCondition(const LogEvent& event,
+ // conditionChanged: the bit map to record whether the condition has changed.
+ // If the condition has dimension, then any sub condition changes will report
+ // conditionChanged.
+ virtual void evaluateCondition(const LogEvent& event,
const std::vector<MatchingState>& eventMatcherValues,
const std::vector<sp<ConditionTracker>>& mAllConditions,
std::vector<ConditionState>& conditionCache,
- std::vector<bool>& nonSlicedConditionChanged,
- std::vector<bool>& slicedConditionMayChanged) = 0;
+ std::vector<bool>& conditionChanged) = 0;
// Return the current condition state.
virtual ConditionState isConditionMet() {
diff --git a/cmds/statsd/src/condition/SimpleConditionTracker.cpp b/cmds/statsd/src/condition/SimpleConditionTracker.cpp
index b691faea..a694dbf 100644
--- a/cmds/statsd/src/condition/SimpleConditionTracker.cpp
+++ b/cmds/statsd/src/condition/SimpleConditionTracker.cpp
@@ -74,13 +74,21 @@
mStopAllLogMatcherIndex = -1;
}
- mDimension.insert(mDimension.begin(), simpleCondition.dimension().begin(),
- simpleCondition.dimension().end());
+ mOutputDimension.insert(mOutputDimension.begin(), simpleCondition.dimension().begin(),
+ simpleCondition.dimension().end());
- if (mDimension.size() > 0) {
+ if (mOutputDimension.size() > 0) {
mSliced = true;
}
+ if (simpleCondition.initial_value() == SimpleCondition_InitialValue_FALSE) {
+ mInitialValue = ConditionState::kFalse;
+ } else {
+ mInitialValue = ConditionState::kUnknown;
+ }
+
+ mNonSlicedConditionState = mInitialValue;
+
mInitialized = true;
}
@@ -97,127 +105,166 @@
return mInitialized;
}
-void print(unordered_map<HashableDimensionKey, ConditionState>& conditions, const string& name) {
+void print(map<HashableDimensionKey, int>& conditions, const string& name) {
VLOG("%s DUMP:", name.c_str());
-
for (const auto& pair : conditions) {
- VLOG("\t%s %d", pair.first.c_str(), pair.second);
+ VLOG("\t%s : %d", pair.first.c_str(), pair.second);
}
}
-bool SimpleConditionTracker::evaluateCondition(const LogEvent& event,
+void SimpleConditionTracker::handleStopAll(std::vector<ConditionState>& conditionCache,
+ std::vector<bool>& conditionChangedCache) {
+ // Unless the default condition is false, and there was nothing started, otherwise we have
+ // triggered a condition change.
+ conditionChangedCache[mIndex] =
+ (mInitialValue == ConditionState::kFalse && mSlicedConditionState.empty()) ? false
+ : true;
+
+ // After StopAll, we know everything has stopped. From now on, default condition is false.
+ mInitialValue = ConditionState::kFalse;
+ mSlicedConditionState.clear();
+ conditionCache[mIndex] = ConditionState::kFalse;
+}
+
+void SimpleConditionTracker::handleConditionEvent(const HashableDimensionKey& outputKey,
+ bool matchStart,
+ std::vector<ConditionState>& conditionCache,
+ std::vector<bool>& conditionChangedCache) {
+ bool changed = false;
+ auto outputIt = mSlicedConditionState.find(outputKey);
+ ConditionState newCondition;
+ if (outputIt == mSlicedConditionState.end()) {
+ // We get a new output key.
+ newCondition = matchStart ? ConditionState::kTrue : ConditionState::kFalse;
+ if (matchStart && mInitialValue != ConditionState::kTrue) {
+ mSlicedConditionState[outputKey] = 1;
+ changed = true;
+ } else if (mInitialValue != ConditionState::kFalse) {
+ // it's a stop and we don't have history about it.
+ // If the default condition is not false, it means this stop is valuable to us.
+ mSlicedConditionState[outputKey] = 0;
+ changed = true;
+ }
+ } else {
+ // we have history about this output key.
+ auto& startedCount = outputIt->second;
+ // assign the old value first.
+ newCondition = startedCount > 0 ? ConditionState::kTrue : ConditionState::kFalse;
+ if (matchStart) {
+ if (startedCount == 0) {
+ // This condition for this output key will change from false -> true
+ changed = true;
+ }
+
+ // it's ok to do ++ here, even if we don't count nesting. The >1 counts will be treated
+ // as 1 if not counting nesting.
+ startedCount++;
+ newCondition = ConditionState::kTrue;
+ } else {
+ // This is a stop event.
+ if (startedCount > 0) {
+ if (mCountNesting) {
+ startedCount--;
+ if (startedCount == 0) {
+ newCondition = ConditionState::kFalse;
+ }
+ } else {
+ // not counting nesting, so ignore the number of starts, stop now.
+ startedCount = 0;
+ newCondition = ConditionState::kFalse;
+ }
+ // if everything has stopped for this output key, condition true -> false;
+ if (startedCount == 0) {
+ changed = true;
+ }
+ }
+
+ // if default condition is false, it means we don't need to keep the false values.
+ if (mInitialValue == ConditionState::kFalse && startedCount == 0) {
+ mSlicedConditionState.erase(outputIt);
+ VLOG("erase key %s", outputKey.c_str());
+ }
+ }
+ }
+
+ // dump all dimensions for debugging
+ if (DEBUG) {
+ print(mSlicedConditionState, mName);
+ }
+
+ conditionChangedCache[mIndex] = changed;
+ conditionCache[mIndex] = newCondition;
+
+ VLOG("SimpleCondition %s nonSlicedChange? %d", mName.c_str(),
+ conditionChangedCache[mIndex] == true);
+}
+
+void SimpleConditionTracker::evaluateCondition(const LogEvent& event,
const vector<MatchingState>& eventMatcherValues,
const vector<sp<ConditionTracker>>& mAllConditions,
vector<ConditionState>& conditionCache,
- vector<bool>& nonSlicedConditionChanged,
- std::vector<bool>& slicedConditionChanged) {
+ vector<bool>& conditionChangedCache) {
if (conditionCache[mIndex] != ConditionState::kNotEvaluated) {
// it has been evaluated.
VLOG("Yes, already evaluated, %s %d", mName.c_str(), mNonSlicedConditionState);
- return false;
+ return;
}
- // Ignore nesting, because we know we cannot trust ourselves on tracking nesting conditions.
+ if (mStopAllLogMatcherIndex >= 0 &&
+ eventMatcherValues[mStopAllLogMatcherIndex] == MatchingState::kMatched) {
+ handleStopAll(conditionCache, conditionChangedCache);
+ return;
+ }
- ConditionState newCondition = mNonSlicedConditionState;
- bool matched = false;
+ int matchedState = -1;
// Note: The order to evaluate the following start, stop, stop_all matters.
// The priority of overwrite is stop_all > stop > start.
if (mStartLogMatcherIndex >= 0 &&
eventMatcherValues[mStartLogMatcherIndex] == MatchingState::kMatched) {
- matched = true;
- newCondition = ConditionState::kTrue;
+ matchedState = 1;
}
if (mStopLogMatcherIndex >= 0 &&
eventMatcherValues[mStopLogMatcherIndex] == MatchingState::kMatched) {
- matched = true;
- newCondition = ConditionState::kFalse;
+ matchedState = 0;
}
- bool stopAll = false;
- if (mStopAllLogMatcherIndex >= 0 &&
- eventMatcherValues[mStopAllLogMatcherIndex] == MatchingState::kMatched) {
- matched = true;
- newCondition = ConditionState::kFalse;
- stopAll = true;
- }
-
- if (matched == false) {
- slicedConditionChanged[mIndex] = false;
- nonSlicedConditionChanged[mIndex] = false;
+ if (matchedState < 0) {
+ conditionChangedCache[mIndex] = false;
conditionCache[mIndex] = mNonSlicedConditionState;
- return false;
+ return;
}
- bool nonSlicedChanged = mNonSlicedConditionState != newCondition;
-
- bool slicedChanged = false;
-
- if (stopAll) {
- // TODO: handle stop all; all dimension should be cleared.
- }
-
-
- if (mDimension.size() > 0) {
- HashableDimensionKey hashableKey = getHashableKey(getDimensionKey(event, mDimension));
- if (mSlicedConditionState.find(hashableKey) == mSlicedConditionState.end() ||
- mSlicedConditionState[hashableKey] != newCondition) {
- slicedChanged = true;
- mSlicedConditionState[hashableKey] = newCondition;
- }
- VLOG("key: %s %d", hashableKey.c_str(), newCondition);
- // dump all dimensions for debugging
- if (DEBUG) {
- print(mSlicedConditionState, mName);
- }
- }
-
- // even if this SimpleCondition is not sliced, it may be part of a sliced CombinationCondition
- // if the nonSliced condition changed, it may affect the sliced condition in the parent node.
- // so mark the slicedConditionChanged to be true.
- // For example: APP_IN_BACKGROUND_OR_SCREEN_OFF
- // APP_IN_BACKGROUND is sliced [App_A->True, App_B->False].
- // SCREEN_OFF is not sliced, and it changes from False -> True;
- // We need to populate this change to parent condition. Because for App_B,
- // the APP_IN_BACKGROUND_OR_SCREEN_OFF condition would change from False->True.
- slicedConditionChanged[mIndex] = mSliced ? slicedChanged : nonSlicedChanged;
- nonSlicedConditionChanged[mIndex] = nonSlicedChanged;
-
- VLOG("SimpleCondition %s nonSlicedChange? %d SlicedChanged? %d", mName.c_str(),
- nonSlicedConditionChanged[mIndex] == true, slicedConditionChanged[mIndex] == true);
- mNonSlicedConditionState = newCondition;
- conditionCache[mIndex] = mNonSlicedConditionState;
-
- return nonSlicedConditionChanged[mIndex];
+ // outputKey is the output key values. e.g, uid:1234
+ const HashableDimensionKey outputKey = getHashableKey(getDimensionKey(event, mOutputDimension));
+ handleConditionEvent(outputKey, matchedState == 1, conditionCache, conditionChangedCache);
}
void SimpleConditionTracker::isConditionMet(
const map<string, HashableDimensionKey>& conditionParameters,
const vector<sp<ConditionTracker>>& allConditions, vector<ConditionState>& conditionCache) {
const auto pair = conditionParameters.find(mName);
- if (pair == conditionParameters.end()) {
- // the query does not need my sliced condition. just return the non sliced condition.
- conditionCache[mIndex] = mNonSlicedConditionState;
- VLOG("Condition %s return %d", mName.c_str(), mNonSlicedConditionState);
+ HashableDimensionKey key =
+ (pair == conditionParameters.end()) ? DEFAULT_DIMENSION_KEY : pair->second;
+
+ if (pair == conditionParameters.end() && mOutputDimension.size() > 0) {
+ ALOGE("Condition %s output has dimension, but it's not specified in the query!",
+ mName.c_str());
+ conditionCache[mIndex] = mInitialValue;
return;
}
- const HashableDimensionKey& key = pair->second;
VLOG("simpleCondition %s query key: %s", mName.c_str(), key.c_str());
- if (mSlicedConditionState.find(key) == mSlicedConditionState.end()) {
- // never seen this key before. the condition is unknown to us.
- conditionCache[mIndex] = ConditionState::kUnknown;
+ auto startedCountIt = mSlicedConditionState.find(key);
+ if (startedCountIt == mSlicedConditionState.end()) {
+ conditionCache[mIndex] = mInitialValue;
} else {
- conditionCache[mIndex] = mSlicedConditionState[key];
+ conditionCache[mIndex] =
+ startedCountIt->second > 0 ? ConditionState::kTrue : ConditionState::kFalse;
}
VLOG("Condition %s return %d", mName.c_str(), conditionCache[mIndex]);
-
- if (DEBUG) {
- print(mSlicedConditionState, mName);
- }
}
} // namespace statsd
diff --git a/cmds/statsd/src/condition/SimpleConditionTracker.h b/cmds/statsd/src/condition/SimpleConditionTracker.h
index b72157b..2eda0b1 100644
--- a/cmds/statsd/src/condition/SimpleConditionTracker.h
+++ b/cmds/statsd/src/condition/SimpleConditionTracker.h
@@ -17,6 +17,7 @@
#ifndef SIMPLE_CONDITION_TRACKER_H
#define SIMPLE_CONDITION_TRACKER_H
+#include <gtest/gtest_prod.h>
#include "ConditionTracker.h"
#include "frameworks/base/cmds/statsd/src/statsd_config.pb.h"
#include "stats_util.h"
@@ -38,12 +39,11 @@
const std::unordered_map<std::string, int>& conditionNameIndexMap,
std::vector<bool>& stack) override;
- bool evaluateCondition(const LogEvent& event,
+ void evaluateCondition(const LogEvent& event,
const std::vector<MatchingState>& eventMatcherValues,
const std::vector<sp<ConditionTracker>>& mAllConditions,
std::vector<ConditionState>& conditionCache,
- std::vector<bool>& changedCache,
- std::vector<bool>& slicedChangedCache) override;
+ std::vector<bool>& changedCache) override;
void isConditionMet(const std::map<std::string, HashableDimensionKey>& conditionParameters,
const std::vector<sp<ConditionTracker>>& allConditions,
@@ -62,15 +62,22 @@
// The index of the LogEventMatcher which defines the stop all.
int mStopAllLogMatcherIndex;
- // The dimension defines at the atom level, how start and stop should match.
- // e.g., APP_IN_FOREGROUND, the dimension should be the uid field. Each "start" and
- // "stop" tells you the state change of a particular app. Without this dimension, this
- // condition does not make sense.
- std::vector<KeyMatcher> mDimension;
+ ConditionState mInitialValue;
- // Keep the map from the internal HashableDimensionKey to std::vector<KeyValuePair>
- // that StatsLogReport wants.
- std::unordered_map<HashableDimensionKey, ConditionState> mSlicedConditionState;
+ std::vector<KeyMatcher> mOutputDimension;
+
+ std::map<HashableDimensionKey, int> mSlicedConditionState;
+
+ void handleStopAll(std::vector<ConditionState>& conditionCache,
+ std::vector<bool>& changedCache);
+
+ void handleConditionEvent(const HashableDimensionKey& outputKey, bool matchStart,
+ std::vector<ConditionState>& conditionCache,
+ std::vector<bool>& changedCache);
+
+ FRIEND_TEST(SimpleConditionTrackerTest, TestSlicedCondition);
+ FRIEND_TEST(SimpleConditionTrackerTest, TestSlicedWithNoOutputDim);
+ FRIEND_TEST(SimpleConditionTrackerTest, TestStopAll);
};
} // namespace statsd
diff --git a/cmds/statsd/src/condition/condition_util.cpp b/cmds/statsd/src/condition/condition_util.cpp
index 40d41be..2618a21 100644
--- a/cmds/statsd/src/condition/condition_util.cpp
+++ b/cmds/statsd/src/condition/condition_util.cpp
@@ -25,7 +25,6 @@
#include <unordered_map>
#include "../matchers/matcher_util.h"
#include "ConditionTracker.h"
-#include "frameworks/base/cmds/statsd/src/stats_log.pb.h"
#include "frameworks/base/cmds/statsd/src/statsd_config.pb.h"
#include "stats_util.h"
diff --git a/cmds/statsd/src/condition/condition_util.h b/cmds/statsd/src/condition/condition_util.h
index 47e245e..4167bf9 100644
--- a/cmds/statsd/src/condition/condition_util.h
+++ b/cmds/statsd/src/condition/condition_util.h
@@ -19,7 +19,6 @@
#include <vector>
#include "../matchers/matcher_util.h"
-#include "frameworks/base/cmds/statsd/src/stats_log.pb.h"
#include "frameworks/base/cmds/statsd/src/statsd_config.pb.h"
namespace android {
diff --git a/cmds/statsd/src/config/ConfigListener.h b/cmds/statsd/src/config/ConfigListener.h
index a58766d..19ccfcf 100644
--- a/cmds/statsd/src/config/ConfigListener.h
+++ b/cmds/statsd/src/config/ConfigListener.h
@@ -16,7 +16,6 @@
#pragma once
-#include <frameworks/base/cmds/statsd/src/stats_log.pb.h>
#include "config/ConfigKey.h"
#include <utils/RefBase.h>
diff --git a/cmds/statsd/src/config/ConfigManager.cpp b/cmds/statsd/src/config/ConfigManager.cpp
index 02e6903..d86ab57 100644
--- a/cmds/statsd/src/config/ConfigManager.cpp
+++ b/cmds/statsd/src/config/ConfigManager.cpp
@@ -165,6 +165,12 @@
KeyMatcher* keyMatcher = metric->add_dimension();
keyMatcher->set_key(UID_PROCESS_STATE_UID_KEY);
+ // Anomaly threshold for background count.
+ alert = metric->add_alerts();
+ alert->set_number_of_buckets(4);
+ alert->set_trigger_if_sum_gt(30);
+ alert->set_refractory_period_secs(20);
+
// Count process state changes, slice by uid, while SCREEN_IS_OFF
metric = config.add_count_metric();
metric->set_metric_id(3);
@@ -194,7 +200,7 @@
durationMetric->set_type(DurationMetric_AggregationType_DURATION_SUM);
keyMatcher = durationMetric->add_dimension();
keyMatcher->set_key(WAKE_LOCK_UID_KEY_ID);
- durationMetric->set_what("WL_STATE_PER_APP_PER_NAME");
+ durationMetric->set_what("WL_HELD_PER_APP_PER_NAME");
durationMetric->set_predicate("APP_IS_BACKGROUND_AND_SCREEN_ON");
link = durationMetric->add_links();
link->set_condition("APP_IS_BACKGROUND");
@@ -208,7 +214,7 @@
durationMetric->set_type(DurationMetric_AggregationType_DURATION_MAX_SPARSE);
keyMatcher = durationMetric->add_dimension();
keyMatcher->set_key(WAKE_LOCK_UID_KEY_ID);
- durationMetric->set_what("WL_STATE_PER_APP_PER_NAME");
+ durationMetric->set_what("WL_HELD_PER_APP_PER_NAME");
durationMetric->set_predicate("APP_IS_BACKGROUND_AND_SCREEN_ON");
link = durationMetric->add_links();
link->set_condition("APP_IS_BACKGROUND");
@@ -220,7 +226,7 @@
durationMetric->set_metric_id(7);
durationMetric->mutable_bucket()->set_bucket_size_millis(30 * 1000L);
durationMetric->set_type(DurationMetric_AggregationType_DURATION_MAX_SPARSE);
- durationMetric->set_what("WL_STATE_PER_APP_PER_NAME");
+ durationMetric->set_what("WL_HELD_PER_APP_PER_NAME");
durationMetric->set_predicate("APP_IS_BACKGROUND_AND_SCREEN_ON");
link = durationMetric->add_links();
link->set_condition("APP_IS_BACKGROUND");
@@ -328,12 +334,14 @@
SimpleCondition* simpleCondition = condition->mutable_simple_condition();
simpleCondition->set_start("SCREEN_TURNED_ON");
simpleCondition->set_stop("SCREEN_TURNED_OFF");
+ simpleCondition->set_count_nesting(false);
condition = config.add_condition();
condition->set_name("SCREEN_IS_OFF");
simpleCondition = condition->mutable_simple_condition();
simpleCondition->set_start("SCREEN_TURNED_OFF");
simpleCondition->set_stop("SCREEN_TURNED_ON");
+ simpleCondition->set_count_nesting(false);
condition = config.add_condition();
condition->set_name("APP_IS_BACKGROUND");
@@ -342,6 +350,7 @@
simpleCondition->set_stop("APP_GOES_FOREGROUND");
KeyMatcher* condition_dimension1 = simpleCondition->add_dimension();
condition_dimension1->set_key(APP_USAGE_UID_KEY_ID);
+ simpleCondition->set_count_nesting(false);
condition = config.add_condition();
condition->set_name("APP_IS_BACKGROUND_AND_SCREEN_ON");
@@ -351,7 +360,7 @@
combination_condition->add_condition("SCREEN_IS_ON");
condition = config.add_condition();
- condition->set_name("WL_STATE_PER_APP_PER_NAME");
+ condition->set_name("WL_HELD_PER_APP_PER_NAME");
simpleCondition = condition->mutable_simple_condition();
simpleCondition->set_start("APP_GET_WL");
simpleCondition->set_stop("APP_RELEASE_WL");
@@ -359,6 +368,17 @@
condition_dimension->set_key(WAKE_LOCK_UID_KEY_ID);
condition_dimension = simpleCondition->add_dimension();
condition_dimension->set_key(WAKE_LOCK_NAME_KEY);
+ simpleCondition->set_count_nesting(true);
+
+ condition = config.add_condition();
+ condition->set_name("WL_HELD_PER_APP");
+ simpleCondition = condition->mutable_simple_condition();
+ simpleCondition->set_start("APP_GET_WL");
+ simpleCondition->set_stop("APP_RELEASE_WL");
+ simpleCondition->set_initial_value(SimpleCondition_InitialValue_FALSE);
+ condition_dimension = simpleCondition->add_dimension();
+ condition_dimension->set_key(WAKE_LOCK_UID_KEY_ID);
+ simpleCondition->set_count_nesting(true);
return config;
}
diff --git a/cmds/statsd/src/external/CpuTimePerUidFreqPuller.cpp b/cmds/statsd/src/external/CpuTimePerUidFreqPuller.cpp
new file mode 100644
index 0000000..e004d21
--- /dev/null
+++ b/cmds/statsd/src/external/CpuTimePerUidFreqPuller.cpp
@@ -0,0 +1,90 @@
+/*
+ * 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 DEBUG true // STOPSHIP if true
+#include "Log.h"
+
+#include <fstream>
+#include "external/CpuTimePerUidFreqPuller.h"
+
+#include "logd/LogEvent.h"
+#include "statslog.h"
+
+using std::make_shared;
+using std::shared_ptr;
+using std::ifstream;
+
+namespace android {
+namespace os {
+namespace statsd {
+
+static const string sProcFile = "/proc/uid_cputime/show_uid_stat";
+static const int kLineBufferSize = 1024;
+
+/**
+ * Reads /proc/uid_time_in_state which has the format:
+ *
+ * uid: [freq1] [freq2] [freq3] ...
+ * [uid1]: [time in freq1] [time in freq2] [time in freq3] ...
+ * [uid2]: [time in freq1] [time in freq2] [time in freq3] ...
+ * ...
+ *
+ * This provides the times a UID's processes spent executing at each different cpu frequency.
+ * The file contains a monotonically increasing count of time for a single boot.
+ */
+bool CpuTimePerUidFreqPuller::Pull(const int tagId, vector<shared_ptr<LogEvent>>* data) {
+ data->clear();
+
+ ifstream fin;
+ fin.open(sProcFile);
+ if (!fin.good()) {
+ VLOG("Failed to read pseudo file %s", sProcFile.c_str());
+ return false;
+ }
+
+ uint64_t timestamp = time(nullptr) * NS_PER_SEC;
+ char buf[kLineBufferSize];
+ // first line prints the format and frequencies
+ fin.getline(buf, kLineBufferSize);
+ char * pch;
+ while(!fin.eof()){
+ fin.getline(buf, kLineBufferSize);
+ pch = strtok (buf, " :");
+ if (pch == NULL) break;
+ uint64_t uid = std::stoull(pch);
+ pch = strtok(NULL, " ");
+ uint64_t timeMs;
+ int idx = 0;
+ do {
+ timeMs = std::stoull(pch);
+ auto ptr = make_shared<LogEvent>(android::util::CPU_TIME_PER_UID_FREQ_PULLED, timestamp);
+ auto elemList = ptr->GetAndroidLogEventList();
+ *elemList << uid;
+ *elemList << idx;
+ *elemList << timeMs;
+ ptr->init();
+ data->push_back(ptr);
+ VLOG("uid %lld, freq idx %d, sys time %lld", (long long)uid, idx, (long long)timeMs);
+ idx ++;
+ pch = strtok(NULL, " ");
+ } while (pch != NULL);
+ }
+ return true;
+}
+
+} // namespace statsd
+} // namespace os
+} // namespace android
diff --git a/cmds/statsd/src/external/CpuTimePerUidFreqPuller.h b/cmds/statsd/src/external/CpuTimePerUidFreqPuller.h
new file mode 100644
index 0000000..839e5aa
--- /dev/null
+++ b/cmds/statsd/src/external/CpuTimePerUidFreqPuller.h
@@ -0,0 +1,41 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include <utils/String16.h>
+#include "StatsPuller.h"
+
+namespace android {
+namespace os {
+namespace statsd {
+
+/**
+ * Reads /proc/uid_cputime/show_uid_stat which has the line format:
+ *
+ * uid: user_time_micro_seconds system_time_micro_seconds
+ *
+ * This provides the time a UID's processes spent executing in user-space and kernel-space.
+ * The file contains a monotonically increasing count of time for a single boot.
+ */
+class CpuTimePerUidFreqPuller : public StatsPuller {
+ public:
+ bool Pull(const int tagId, vector<std::shared_ptr<LogEvent>>* data) override;
+};
+
+} // namespace statsd
+} // namespace os
+} // namespace android
diff --git a/cmds/statsd/src/external/CpuTimePerUidPuller.cpp b/cmds/statsd/src/external/CpuTimePerUidPuller.cpp
new file mode 100644
index 0000000..b84b877
--- /dev/null
+++ b/cmds/statsd/src/external/CpuTimePerUidPuller.cpp
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define DEBUG true // STOPSHIP if true
+#include "Log.h"
+
+#include <fstream>
+#include "external/CpuTimePerUidPuller.h"
+
+#include "logd/LogEvent.h"
+#include "statslog.h"
+
+using std::make_shared;
+using std::shared_ptr;
+using std::ifstream;
+
+namespace android {
+namespace os {
+namespace statsd {
+
+static const string sProcFile = "/proc/uid_cputime/show_uid_stat";
+static const int kLineBufferSize = 1024;
+
+/**
+ * Reads /proc/uid_cputime/show_uid_stat which has the line format:
+ *
+ * uid: user_time_micro_seconds system_time_micro_seconds power_in_milli-amp-micro_seconds
+ *
+ * This provides the time a UID's processes spent executing in user-space and kernel-space.
+ * The file contains a monotonically increasing count of time for a single boot.
+ */
+bool CpuTimePerUidPuller::Pull(const int tagId, vector<shared_ptr<LogEvent>>* data) {
+ data->clear();
+
+ ifstream fin;
+ fin.open(sProcFile);
+ if (!fin.good()) {
+ VLOG("Failed to read pseudo file %s", sProcFile.c_str());
+ return false;
+ }
+
+ uint64_t timestamp = time(nullptr) * NS_PER_SEC;
+ char buf[kLineBufferSize];
+ char * pch;
+ while(!fin.eof()){
+ fin.getline(buf, kLineBufferSize);
+ pch = strtok(buf, " :");
+ if (pch == NULL) break;
+ uint64_t uid = std::stoull(pch);
+ pch = strtok(buf, " ");
+ uint64_t userTimeMs = std::stoull(pch);
+ pch = strtok(buf, " ");
+ uint64_t sysTimeMs = std::stoull(pch);
+
+ auto ptr = make_shared<LogEvent>(android::util::CPU_TIME_PER_UID_PULLED, timestamp);
+ auto elemList = ptr->GetAndroidLogEventList();
+ *elemList << uid;
+ *elemList << userTimeMs;
+ *elemList << sysTimeMs;
+ ptr->init();
+ data->push_back(ptr);
+ VLOG("uid %lld, user time %lld, sys time %lld", (long long)uid, (long long)userTimeMs, (long long)sysTimeMs);
+ }
+ return true;
+}
+
+} // namespace statsd
+} // namespace os
+} // namespace android
diff --git a/cmds/statsd/src/external/CpuTimePerUidPuller.h b/cmds/statsd/src/external/CpuTimePerUidPuller.h
new file mode 100644
index 0000000..9bb8946
--- /dev/null
+++ b/cmds/statsd/src/external/CpuTimePerUidPuller.h
@@ -0,0 +1,41 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include <utils/String16.h>
+#include "StatsPuller.h"
+
+namespace android {
+namespace os {
+namespace statsd {
+
+/**
+ * Reads /proc/uid_cputime/show_uid_stat which has the line format:
+ *
+ * uid: user_time_micro_seconds system_time_micro_seconds
+ *
+ * This provides the time a UID's processes spent executing in user-space and kernel-space.
+ * The file contains a monotonically increasing count of time for a single boot.
+ */
+class CpuTimePerUidPuller : public StatsPuller {
+ public:
+ bool Pull(const int tagId, vector<std::shared_ptr<LogEvent>>* data) override;
+};
+
+} // namespace statsd
+} // namespace os
+} // namespace android
diff --git a/cmds/statsd/src/external/ResourcePowerManagerPuller.cpp b/cmds/statsd/src/external/ResourcePowerManagerPuller.cpp
index 3608ee4..319feef4 100644
--- a/cmds/statsd/src/external/ResourcePowerManagerPuller.cpp
+++ b/cmds/statsd/src/external/ResourcePowerManagerPuller.cpp
@@ -34,6 +34,7 @@
#include "external/StatsPuller.h"
#include "logd/LogEvent.h"
+#include "statslog.h"
using android::hardware::hidl_vec;
using android::hardware::power::V1_0::IPower;
@@ -57,10 +58,6 @@
std::mutex gPowerHalMutex;
bool gPowerHalExists = true;
-static const int power_state_platform_sleep_state_tag = 1011;
-static const int power_state_voter_tag = 1012;
-static const int power_state_subsystem_state_tag = 1013;
-
bool getPowerHal() {
if (gPowerHalExists && gPowerHalV1_0 == nullptr) {
gPowerHalV1_0 = android::hardware::power::V1_0::IPower::getService();
@@ -94,8 +91,8 @@
for (size_t i = 0; i < states.size(); i++) {
const PowerStatePlatformSleepState& state = states[i];
- auto statePtr =
- make_shared<LogEvent>(power_state_platform_sleep_state_tag, timestamp);
+ auto statePtr = make_shared<LogEvent>(
+ android::util::POWER_STATE_PLATFORM_SLEEP_STATE_PULLED, timestamp);
auto elemList = statePtr->GetAndroidLogEventList();
*elemList << state.name;
*elemList << state.residencyInMsecSinceBoot;
@@ -107,12 +104,14 @@
(long long)state.residencyInMsecSinceBoot,
(long long)state.totalTransitions, state.supportedOnlyInSuspend ? 1 : 0);
for (auto voter : state.voters) {
- auto voterPtr = make_shared<LogEvent>(power_state_voter_tag, timestamp);
+ auto voterPtr =
+ make_shared<LogEvent>(android::util::POWER_STATE_VOTER_PULLED, timestamp);
auto elemList = voterPtr->GetAndroidLogEventList();
*elemList << state.name;
*elemList << voter.name;
*elemList << voter.totalTimeInMsecVotedForSinceBoot;
*elemList << voter.totalNumberOfTimesVotedSinceBoot;
+ voterPtr->init();
data->push_back(voterPtr);
VLOG("powerstatevoter: %s, %s, %lld, %lld", state.name.c_str(),
voter.name.c_str(), (long long)voter.totalTimeInMsecVotedForSinceBoot,
@@ -141,7 +140,7 @@
for (size_t j = 0; j < subsystem.states.size(); j++) {
const PowerStateSubsystemSleepState& state = subsystem.states[j];
auto subsystemStatePtr = make_shared<LogEvent>(
- power_state_subsystem_state_tag, timestamp);
+ android::util::POWER_STATE_SUBSYSTEM_SLEEP_STATE_PULLED, timestamp);
auto elemList = subsystemStatePtr->GetAndroidLogEventList();
*elemList << subsystem.name;
*elemList << state.name;
diff --git a/cmds/statsd/src/external/StatsPullerManager.h b/cmds/statsd/src/external/StatsPullerManager.h
index 67580d6..2e803c9 100644
--- a/cmds/statsd/src/external/StatsPullerManager.h
+++ b/cmds/statsd/src/external/StatsPullerManager.h
@@ -16,71 +16,39 @@
#pragma once
-#include <android/os/IStatsCompanionService.h>
-#include <binder/IServiceManager.h>
-#include <utils/RefBase.h>
-#include <utils/String16.h>
-#include <utils/String8.h>
-#include <utils/threads.h>
-#include <string>
-#include <unordered_map>
-#include <vector>
-#include "PullDataReceiver.h"
-#include "StatsPuller.h"
-#include "logd/LogEvent.h"
+#include "StatsPullerManagerImpl.h"
namespace android {
namespace os {
namespace statsd {
-class StatsPullerManager : public virtual RefBase {
-public:
- static StatsPullerManager& GetInstance();
+class StatsPullerManager{
+ public:
+ virtual ~StatsPullerManager() {}
- void RegisterReceiver(int tagId, sp<PullDataReceiver> receiver, long intervalMs);
+ virtual void RegisterReceiver(int tagId, wp<PullDataReceiver> receiver, long intervalMs) {
+ mPullerManager.RegisterReceiver(tagId, receiver, intervalMs);
+ };
- void UnRegisterReceiver(int tagId, sp<PullDataReceiver> receiver);
+ virtual void UnRegisterReceiver(int tagId, wp<PullDataReceiver> receiver) {
+ mPullerManager.UnRegisterReceiver(tagId, receiver);
+ };
- // Verify if we know how to pull for this matcher
- bool PullerForMatcherExists(int tagId);
+ // Verify if we know how to pull for this matcher
+ bool PullerForMatcherExists(int tagId) {
+ return mPullerManager.PullerForMatcherExists(tagId);
+ }
- void OnAlarmFired();
+ void OnAlarmFired() {
+ mPullerManager.OnAlarmFired();
+ }
- bool Pull(const int pullCode, vector<std::shared_ptr<LogEvent>>* data);
+ virtual bool Pull(const int tagId, vector<std::shared_ptr<LogEvent>>* data) {
+ return mPullerManager.Pull(tagId, data);
+ }
-private:
- StatsPullerManager();
-
- // use this to update alarm
- sp<IStatsCompanionService> mStatsCompanionService = nullptr;
-
- sp<IStatsCompanionService> get_stats_companion_service();
-
- // mapping from simple matcher tagId to puller
- std::map<int, std::shared_ptr<StatsPuller>> mPullers;
-
- typedef struct {
- // pull_interval_sec : last_pull_time_sec
- std::pair<uint64_t, uint64_t> timeInfo;
- sp<PullDataReceiver> receiver;
- } ReceiverInfo;
-
- // mapping from simple matcher tagId to receivers
- std::map<int, std::vector<ReceiverInfo>> mReceivers;
-
- Mutex mReceiversLock;
-
- long mCurrentPullingInterval;
-
- // for pulled metrics, it is important for the buckets to be aligned to multiple of smallest
- // bucket size. All pulled metrics start pulling based on this time, so that they can be
- // correctly attributed to the correct buckets. Pulled data attach a timestamp which is the
- // request time.
- const long mPullStartTimeMs;
-
- long get_pull_start_time_ms();
-
- LogEvent parse_pulled_data(String16 data);
+ private:
+ StatsPullerManagerImpl& mPullerManager = StatsPullerManagerImpl::GetInstance();
};
} // namespace statsd
diff --git a/cmds/statsd/src/external/StatsPullerManager.cpp b/cmds/statsd/src/external/StatsPullerManagerImpl.cpp
similarity index 62%
rename from cmds/statsd/src/external/StatsPullerManager.cpp
rename to cmds/statsd/src/external/StatsPullerManagerImpl.cpp
index 43543cc..07d0b3e 100644
--- a/cmds/statsd/src/external/StatsPullerManager.cpp
+++ b/cmds/statsd/src/external/StatsPullerManagerImpl.cpp
@@ -21,9 +21,11 @@
#include <cutils/log.h>
#include <algorithm>
#include <climits>
+#include "CpuTimePerUidFreqPuller.h"
+#include "CpuTimePerUidPuller.h"
#include "ResourcePowerManagerPuller.h"
#include "StatsCompanionServicePuller.h"
-#include "StatsPullerManager.h"
+#include "StatsPullerManagerImpl.h"
#include "StatsService.h"
#include "logd/LogEvent.h"
#include "statslog.h"
@@ -35,33 +37,42 @@
using std::shared_ptr;
using std::string;
using std::vector;
+using std::list;
namespace android {
namespace os {
namespace statsd {
-StatsPullerManager::StatsPullerManager()
+StatsPullerManagerImpl::StatsPullerManagerImpl()
: mCurrentPullingInterval(LONG_MAX), mPullStartTimeMs(get_pull_start_time_ms()) {
- shared_ptr<StatsPuller> statsCompanionServicePuller =
- make_shared<StatsCompanionServicePuller>();
+ shared_ptr<StatsPuller> statsCompanionServicePuller = make_shared<StatsCompanionServicePuller>();
shared_ptr<StatsPuller> resourcePowerManagerPuller = make_shared<ResourcePowerManagerPuller>();
+ shared_ptr<StatsPuller> cpuTimePerUidPuller = make_shared<CpuTimePerUidPuller>();
+ shared_ptr<StatsPuller> cpuTimePerUidFreqPuller = make_shared<CpuTimePerUidFreqPuller>();
- mPullers.insert({android::util::KERNEL_WAKELOCK_PULLED, statsCompanionServicePuller});
- mPullers.insert({android::util::WIFI_BYTES_TRANSFERRED, statsCompanionServicePuller});
- mPullers.insert({android::util::MOBILE_BYTES_TRANSFERRED, statsCompanionServicePuller});
- mPullers.insert({android::util::WIFI_BYTES_TRANSFERRED_BY_FG_BG, statsCompanionServicePuller});
- mPullers.insert(
- {android::util::MOBILE_BYTES_TRANSFERRED_BY_FG_BG, statsCompanionServicePuller});
- mPullers.insert(
- {android::util::POWER_STATE_PLATFORM_SLEEP_STATE_PULLED, resourcePowerManagerPuller});
- mPullers.insert({android::util::POWER_STATE_VOTER_PULLED, resourcePowerManagerPuller});
- mPullers.insert(
- {android::util::POWER_STATE_SUBSYSTEM_SLEEP_STATE_PULLED, resourcePowerManagerPuller});
+ mPullers.insert({android::util::KERNEL_WAKELOCK_PULLED,
+ statsCompanionServicePuller});
+ mPullers.insert({android::util::WIFI_BYTES_TRANSFERRED,
+ statsCompanionServicePuller});
+ mPullers.insert({android::util::MOBILE_BYTES_TRANSFERRED,
+ statsCompanionServicePuller});
+ mPullers.insert({android::util::WIFI_BYTES_TRANSFERRED_BY_FG_BG,
+ statsCompanionServicePuller});
+ mPullers.insert({android::util::MOBILE_BYTES_TRANSFERRED_BY_FG_BG,
+ statsCompanionServicePuller});
+ mPullers.insert({android::util::POWER_STATE_PLATFORM_SLEEP_STATE_PULLED,
+ resourcePowerManagerPuller});
+ mPullers.insert({android::util::POWER_STATE_VOTER_PULLED,
+ resourcePowerManagerPuller});
+ mPullers.insert({android::util::POWER_STATE_SUBSYSTEM_SLEEP_STATE_PULLED,
+ resourcePowerManagerPuller});
+ mPullers.insert({android::util::CPU_TIME_PER_UID_PULLED, cpuTimePerUidPuller});
+ mPullers.insert({android::util::CPU_TIME_PER_UID_FREQ_PULLED, cpuTimePerUidFreqPuller});
mStatsCompanionService = StatsService::getStatsCompanionService();
}
-bool StatsPullerManager::Pull(int tagId, vector<shared_ptr<LogEvent>>* data) {
+bool StatsPullerManagerImpl::Pull(int tagId, vector<shared_ptr<LogEvent>>* data) {
if (DEBUG) ALOGD("Initiating pulling %d", tagId);
if (mPullers.find(tagId) != mPullers.end()) {
@@ -72,26 +83,26 @@
}
}
-StatsPullerManager& StatsPullerManager::GetInstance() {
- static StatsPullerManager instance;
+StatsPullerManagerImpl& StatsPullerManagerImpl::GetInstance() {
+ static StatsPullerManagerImpl instance;
return instance;
}
-bool StatsPullerManager::PullerForMatcherExists(int tagId) {
+bool StatsPullerManagerImpl::PullerForMatcherExists(int tagId) {
return mPullers.find(tagId) != mPullers.end();
}
-long StatsPullerManager::get_pull_start_time_ms() {
+long StatsPullerManagerImpl::get_pull_start_time_ms() {
// TODO: limit and align pull intervals to 10min boundaries if this turns out to be a problem
return time(nullptr) * 1000;
}
-void StatsPullerManager::RegisterReceiver(int tagId, sp<PullDataReceiver> receiver,
- long intervalMs) {
+void StatsPullerManagerImpl::RegisterReceiver(int tagId, wp<PullDataReceiver> receiver,
+ long intervalMs) {
AutoMutex _l(mReceiversLock);
- vector<ReceiverInfo>& receivers = mReceivers[tagId];
+ auto& receivers = mReceivers[tagId];
for (auto it = receivers.begin(); it != receivers.end(); it++) {
- if (it->receiver.get() == receiver.get()) {
+ if (it->receiver == receiver) {
VLOG("Receiver already registered of %d", (int)receivers.size());
return;
}
@@ -114,7 +125,7 @@
VLOG("Puller for tagId %d registered of %d", tagId, (int)receivers.size());
}
-void StatsPullerManager::UnRegisterReceiver(int tagId, sp<PullDataReceiver> receiver) {
+void StatsPullerManagerImpl::UnRegisterReceiver(int tagId, wp<PullDataReceiver> receiver) {
AutoMutex _l(mReceiversLock);
if (mReceivers.find(tagId) == mReceivers.end()) {
VLOG("Unknown pull code or no receivers: %d", tagId);
@@ -122,7 +133,7 @@
}
auto& receivers = mReceivers.find(tagId)->second;
for (auto it = receivers.begin(); it != receivers.end(); it++) {
- if (receiver.get() == it->receiver.get()) {
+ if (receiver == it->receiver) {
receivers.erase(it);
VLOG("Puller for tagId %d unregistered of %d", tagId, (int)receivers.size());
return;
@@ -130,7 +141,7 @@
}
}
-void StatsPullerManager::OnAlarmFired() {
+void StatsPullerManagerImpl::OnAlarmFired() {
AutoMutex _l(mReceiversLock);
uint64_t currentTimeMs = time(nullptr) * 1000;
@@ -155,8 +166,13 @@
vector<shared_ptr<LogEvent>> data;
if (Pull(pullInfo.first, &data)) {
for (const auto& receiverInfo : pullInfo.second) {
- receiverInfo->receiver->onDataPulled(data);
- receiverInfo->timeInfo.second = currentTimeMs;
+ sp<PullDataReceiver> receiverPtr = receiverInfo->receiver.promote();
+ if (receiverPtr != nullptr) {
+ receiverPtr->onDataPulled(data);
+ receiverInfo->timeInfo.second = currentTimeMs;
+ } else {
+ VLOG("receiver already gone.");
+ }
}
}
}
diff --git a/cmds/statsd/src/external/StatsPullerManagerImpl.h b/cmds/statsd/src/external/StatsPullerManagerImpl.h
new file mode 100644
index 0000000..0b9f21e
--- /dev/null
+++ b/cmds/statsd/src/external/StatsPullerManagerImpl.h
@@ -0,0 +1,87 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include <android/os/IStatsCompanionService.h>
+#include <binder/IServiceManager.h>
+#include <utils/RefBase.h>
+#include <utils/threads.h>
+#include <string>
+#include <unordered_map>
+#include <vector>
+#include <list>
+#include "PullDataReceiver.h"
+#include "StatsPuller.h"
+#include "logd/LogEvent.h"
+
+namespace android {
+namespace os {
+namespace statsd {
+
+class StatsPullerManagerImpl : public virtual RefBase {
+public:
+ static StatsPullerManagerImpl& GetInstance();
+
+ void RegisterReceiver(int tagId, wp<PullDataReceiver> receiver, long intervalMs);
+
+ void UnRegisterReceiver(int tagId, wp<PullDataReceiver> receiver);
+
+ // Verify if we know how to pull for this matcher
+ bool PullerForMatcherExists(int tagId);
+
+ void OnAlarmFired();
+
+ bool Pull(const int tagId, vector<std::shared_ptr<LogEvent>>* data);
+
+private:
+ StatsPullerManagerImpl();
+
+ // use this to update alarm
+ sp<IStatsCompanionService> mStatsCompanionService = nullptr;
+
+ sp<IStatsCompanionService> get_stats_companion_service();
+
+ // mapping from simple matcher tagId to puller
+ std::map<int, std::shared_ptr<StatsPuller>> mPullers;
+
+ typedef struct {
+ // pull_interval_sec : last_pull_time_sec
+ std::pair<uint64_t, uint64_t> timeInfo;
+ wp<PullDataReceiver> receiver;
+ } ReceiverInfo;
+
+ // mapping from simple matcher tagId to receivers
+ std::map<int, std::list<ReceiverInfo>> mReceivers;
+
+ Mutex mReceiversLock;
+
+ long mCurrentPullingInterval;
+
+ // for pulled metrics, it is important for the buckets to be aligned to multiple of smallest
+ // bucket size. All pulled metrics start pulling based on this time, so that they can be
+ // correctly attributed to the correct buckets. Pulled data attach a timestamp which is the
+ // request time.
+ const long mPullStartTimeMs;
+
+ long get_pull_start_time_ms();
+
+ LogEvent parse_pulled_data(String16 data);
+};
+
+} // namespace statsd
+} // namespace os
+} // namespace android
diff --git a/cmds/statsd/src/matchers/CombinationLogMatchingTracker.h b/cmds/statsd/src/matchers/CombinationLogMatchingTracker.h
index 006d74c..adb691e 100644
--- a/cmds/statsd/src/matchers/CombinationLogMatchingTracker.h
+++ b/cmds/statsd/src/matchers/CombinationLogMatchingTracker.h
@@ -22,7 +22,6 @@
#include <unordered_map>
#include <vector>
#include "LogMatchingTracker.h"
-#include "frameworks/base/cmds/statsd/src/stats_log.pb.h"
#include "frameworks/base/cmds/statsd/src/statsd_config.pb.h"
namespace android {
diff --git a/cmds/statsd/src/matchers/LogMatchingTracker.h b/cmds/statsd/src/matchers/LogMatchingTracker.h
index d82da3b..ffbf248 100644
--- a/cmds/statsd/src/matchers/LogMatchingTracker.h
+++ b/cmds/statsd/src/matchers/LogMatchingTracker.h
@@ -17,7 +17,6 @@
#ifndef LOG_MATCHING_TRACKER_H
#define LOG_MATCHING_TRACKER_H
-#include "frameworks/base/cmds/statsd/src/stats_log.pb.h"
#include "frameworks/base/cmds/statsd/src/statsd_config.pb.h"
#include "logd/LogEvent.h"
#include "matchers/matcher_util.h"
diff --git a/cmds/statsd/src/matchers/SimpleLogMatchingTracker.h b/cmds/statsd/src/matchers/SimpleLogMatchingTracker.h
index e110ec8..5dca55e 100644
--- a/cmds/statsd/src/matchers/SimpleLogMatchingTracker.h
+++ b/cmds/statsd/src/matchers/SimpleLogMatchingTracker.h
@@ -23,7 +23,6 @@
#include <unordered_map>
#include <vector>
#include "LogMatchingTracker.h"
-#include "frameworks/base/cmds/statsd/src/stats_log.pb.h"
#include "frameworks/base/cmds/statsd/src/statsd_config.pb.h"
namespace android {
diff --git a/cmds/statsd/src/matchers/matcher_util.cpp b/cmds/statsd/src/matchers/matcher_util.cpp
index 6aa2211..cccc9b3 100644
--- a/cmds/statsd/src/matchers/matcher_util.cpp
+++ b/cmds/statsd/src/matchers/matcher_util.cpp
@@ -16,7 +16,6 @@
#include "Log.h"
-#include "frameworks/base/cmds/statsd/src/stats_log.pb.h"
#include "frameworks/base/cmds/statsd/src/statsd_config.pb.h"
#include "matchers/LogMatchingTracker.h"
#include "matchers/matcher_util.h"
diff --git a/cmds/statsd/src/metrics/CountAnomalyTracker.cpp b/cmds/statsd/src/metrics/CountAnomalyTracker.cpp
deleted file mode 100644
index 7aa748f..0000000
--- a/cmds/statsd/src/metrics/CountAnomalyTracker.cpp
+++ /dev/null
@@ -1,129 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#define DEBUG true // STOPSHIP if true
-#include "Log.h"
-
-#include "CountAnomalyTracker.h"
-
-#include <time.h>
-
-namespace android {
-namespace os {
-namespace statsd {
-
-CountAnomalyTracker::CountAnomalyTracker(const Alert& alert)
- : mAlert(alert),
- mNumPastBuckets(alert.number_of_buckets() > 0 ? alert.number_of_buckets() - 1 : 0),
- mPastBuckets(mNumPastBuckets > 0 ? (new int[mNumPastBuckets]) : nullptr) {
-
- VLOG("CountAnomalyTracker() called");
- if (alert.number_of_buckets() < 1) {
- ALOGE("Cannot create CountAnomalyTracker with %d buckets", alert.number_of_buckets());
- }
- reset(); // initialization
-}
-
-CountAnomalyTracker::~CountAnomalyTracker() {
- VLOG("~CountAnomalyTracker() called");
-}
-
-void CountAnomalyTracker::addPastBucket(int pastBucketCount,
- time_t numberOfBucketsAgo) {
- VLOG("addPastBucket() called.");
- if (numberOfBucketsAgo < 1) {
- ALOGE("Cannot add a past bucket %ld units in past", numberOfBucketsAgo);
- return;
- }
- // If past bucket was ancient, just empty out all past info.
- // This always applies if mNumPastBuckets == 0 (i.e. store no past buckets).
- if (numberOfBucketsAgo > (time_t) mNumPastBuckets) {
- reset();
- return;
- }
-
- // Empty out old mPastBuckets[i] values and update mSumPastCounters.
- for (size_t i = mOldestBucketIndex;
- i < mOldestBucketIndex + numberOfBucketsAgo; i++) {
- mSumPastCounters -= mPastBuckets[index(i)];
- mPastBuckets[index(i)] = 0;
- }
-
- // Replace the oldest bucket with the new bucket we are adding.
- mPastBuckets[mOldestBucketIndex] = pastBucketCount;
- mSumPastCounters += pastBucketCount;
-
- // Advance the oldest bucket index by numberOfBucketsAgo units.
- mOldestBucketIndex = index(mOldestBucketIndex + numberOfBucketsAgo);
-
- // TODO: Once dimensions are added to mSumPastCounters:
- // iterate through mSumPastCounters and remove any entries that are 0.
-}
-
-void CountAnomalyTracker::reset() {
- VLOG("reset() called.");
- for (size_t i = 0; i < mNumPastBuckets; i++) {
- mPastBuckets[i] = 0;
- }
- mSumPastCounters = 0;
- mOldestBucketIndex = 0;
-}
-
-void CountAnomalyTracker::checkAnomaly(int currentCount) {
- // Skip the check if in refractory period.
- if (time(nullptr) < mRefractoryPeriodEndsSec) {
- VLOG("Skipping anomaly check since within refractory period");
- return;
- }
-
- // TODO: Remove these extremely verbose debugging log.
- VLOG("Checking whether %d + %d > %lld",
- mSumPastCounters, currentCount, mAlert.trigger_if_sum_gt());
-
- // Note that this works even if mNumPastBuckets < 1 (since then
- // mSumPastCounters = 0 so the comparison is based only on currentCount).
- if (mAlert.has_trigger_if_sum_gt() &&
- mSumPastCounters + currentCount > mAlert.trigger_if_sum_gt()) {
- declareAnomaly();
- }
-}
-
-void CountAnomalyTracker::declareAnomaly() {
- // TODO(guardrail): Consider guarding against too short refractory periods.
- time_t currTime = time(nullptr);
- mRefractoryPeriodEndsSec = currTime + mAlert.refractory_period_secs();
-
- // TODO: If we had access to the bucket_size_millis, consider calling reset()
- // if (mAlert.refractory_period_secs() > mNumPastBuckets * bucket_size_millis * 1000).
-
- if (mAlert.has_incidentd_details()) {
- const Alert_IncidentdDetails& incident = mAlert.incidentd_details();
- if (incident.has_alert_name()) {
- ALOGW("An anomaly (%s) has occurred! Informing incidentd.",
- incident.alert_name().c_str());
- } else {
- // TODO: Can construct a name based on the criteria (and/or relay the criteria).
- ALOGW("An anomaly (nameless) has occurred! Informing incidentd.");
- }
- // TODO: Send incidentd_details.name and incidentd_details.incidentd_sections to incidentd
- } else {
- ALOGW("An anomaly has occurred! (But informing incidentd not requested.)");
- }
-}
-
-} // namespace statsd
-} // namespace os
-} // namespace android
diff --git a/cmds/statsd/src/metrics/CountAnomalyTracker.h b/cmds/statsd/src/metrics/CountAnomalyTracker.h
deleted file mode 100644
index 79c47d2a..0000000
--- a/cmds/statsd/src/metrics/CountAnomalyTracker.h
+++ /dev/null
@@ -1,87 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef COUNT_ANOMALY_TRACKER_H
-#define COUNT_ANOMALY_TRACKER_H
-
-#include "frameworks/base/cmds/statsd/src/statsd_config.pb.h" // Alert
-
-#include <memory> // unique_ptr
-#include <stdlib.h>
-
-namespace android {
-namespace os {
-namespace statsd {
-
-// TODO: Can probably be used for Count, Value, and Gauge. If so, rename to ValueAnomalyTracker.
-// (caveat: currently, the value cannot be negative. Probably fine for P.)
-class CountAnomalyTracker {
-public:
- CountAnomalyTracker(const Alert& alert);
-
- virtual ~CountAnomalyTracker();
-
-
- // Adds a new past bucket, holding pastBucketCount, and then advances the
- // present by numberOfBucketsAgo buckets (filling any intervening buckets
- // with 0s).
- // Thus, the newly added bucket (which holds pastBucketCount) is stored
- // numberOfBucketsAgo buckets ago.
- void addPastBucket(int pastBucketCount, time_t numberOfBucketsAgo);
-
- // Informs the anomaly tracker of the current bucket's count, so that it can
- // determine whether an anomaly has occurred. This value is not stored.
- void checkAnomaly(int currentCount);
-
-private:
- // statsd_config.proto Alert message that defines this tracker.
- const Alert mAlert;
-
- // Number of past buckets. One less than the total number of buckets needed
- // for the anomaly detection (since the current bucket is not in the past).
- const size_t mNumPastBuckets;
-
- // Count values for each of the past mNumPastBuckets buckets.
- // TODO: Add dimensions. This parallels the type of CountMetricProducer.mCounter.
- std::unique_ptr<int[]> mPastBuckets;
-
- // Sum over all of mPastBuckets (cached).
- // TODO: Add dimensions. This parallels the type of CountMetricProducer.mCounter.
- // At that point, mSumPastCounters must never contain entries of 0.
- int mSumPastCounters;
-
- // Index of the oldest bucket (i.e. the next bucket to be overwritten).
- size_t mOldestBucketIndex = 0;
-
- // Timestamp that the refractory period (if this anomaly was declared) ends, in seconds.
- // If an anomaly was never declared, set to 0.
- time_t mRefractoryPeriodEndsSec = 0;
-
- void declareAnomaly();
-
- // Calculates the corresponding index within the circular array.
- size_t index(size_t unsafeIndex) {
- return unsafeIndex % mNumPastBuckets;
- }
-
- // Resets all data. For use when all the data gets stale.
- void reset();
-};
-
-} // namespace statsd
-} // namespace os
-} // namespace android
-#endif // COUNT_ANOMALY_TRACKER_H
diff --git a/cmds/statsd/src/metrics/CountMetricProducer.cpp b/cmds/statsd/src/metrics/CountMetricProducer.cpp
index 94f4adf..2252201 100644
--- a/cmds/statsd/src/metrics/CountMetricProducer.cpp
+++ b/cmds/statsd/src/metrics/CountMetricProducer.cpp
@@ -17,13 +17,14 @@
#define DEBUG true // STOPSHIP if true
#include "Log.h"
-#include "CountAnomalyTracker.h"
+#include "../anomaly/DiscreteAnomalyTracker.h"
#include "CountMetricProducer.h"
#include "stats_util.h"
#include <limits.h>
#include <stdlib.h>
+using android::util::FIELD_COUNT_REPEATED;
using android::util::FIELD_TYPE_BOOL;
using android::util::FIELD_TYPE_FLOAT;
using android::util::FIELD_TYPE_INT32;
@@ -61,6 +62,7 @@
const int FIELD_ID_COUNT = 3;
// TODO: add back AnomalyTracker.
+
CountMetricProducer::CountMetricProducer(const CountMetric& metric, const int conditionIndex,
const sp<ConditionWizard>& wizard,
const uint64_t startTimeNs)
@@ -76,7 +78,7 @@
for (int i = 0; i < metric.alerts_size(); i++) {
const Alert& alert = metric.alerts(i);
if (alert.trigger_if_sum_gt() > 0 && alert.number_of_buckets() > 0) {
- mAnomalyTrackers.push_back(std::make_unique<CountAnomalyTracker>(alert));
+ mAnomalyTrackers.push_back(std::make_unique<DiscreteAnomalyTracker>(alert));
} else {
ALOGW("Ignoring invalid count metric alert: threshold=%lld num_buckets= %d",
alert.trigger_if_sum_gt(), alert.number_of_buckets());
@@ -116,7 +118,7 @@
VLOG("Metric %lld onSlicedConditionMayChange", mMetric.metric_id());
}
-StatsLogReport CountMetricProducer::onDumpReport() {
+std::unique_ptr<std::vector<uint8_t>> CountMetricProducer::onDumpReport() {
long long endTime = time(nullptr) * NS_PER_SEC;
// Dump current bucket if it's stale.
@@ -133,11 +135,13 @@
ALOGE("Dimension key %s not found?!?! skip...", hashableKey.c_str());
continue;
}
- long long wrapperToken = mProto->start(FIELD_TYPE_MESSAGE | FIELD_ID_DATA);
+ long long wrapperToken =
+ mProto->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_DATA);
// First fill dimension (KeyValuePairs).
for (const auto& kv : it->second) {
- long long dimensionToken = mProto->start(FIELD_TYPE_MESSAGE | FIELD_ID_DIMENSION);
+ long long dimensionToken =
+ mProto->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_DIMENSION);
mProto->write(FIELD_TYPE_INT32 | FIELD_ID_KEY, kv.key());
if (kv.has_value_str()) {
mProto->write(FIELD_TYPE_INT32 | FIELD_ID_VALUE_STR, kv.value_str());
@@ -153,7 +157,8 @@
// Then fill bucket_info (CountBucketInfo).
for (const auto& bucket : counter.second) {
- long long bucketInfoToken = mProto->start(FIELD_TYPE_MESSAGE | FIELD_ID_BUCKET_INFO);
+ long long bucketInfoToken =
+ mProto->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_BUCKET_INFO);
mProto->write(FIELD_TYPE_INT64 | FIELD_ID_START_BUCKET_NANOS,
(long long)bucket.mBucketStartNs);
mProto->write(FIELD_TYPE_INT64 | FIELD_ID_END_BUCKET_NANOS,
@@ -171,15 +176,13 @@
(long long)mCurrentBucketStartTimeNs);
VLOG("metric %lld dump report now...", mMetric.metric_id());
- std::unique_ptr<uint8_t[]> buffer = serializeProto();
+ std::unique_ptr<std::vector<uint8_t>> buffer = serializeProto();
startNewProtoOutputStream(endTime);
mPastBuckets.clear();
mByteSize = 0;
- // TODO: Once we migrate all MetricProducers to use ProtoOutputStream, we should return this:
- // return std::move(buffer);
- return StatsLogReport();
+ return buffer;
// TODO: Clear mDimensionKeyMap once the report is dumped.
}
@@ -201,25 +204,19 @@
return;
}
- auto it = mCurrentSlicedCounter.find(eventKey);
+ auto it = mCurrentSlicedCounter->find(eventKey);
- if (it == mCurrentSlicedCounter.end()) {
+ if (it == mCurrentSlicedCounter->end()) {
// create a counter for the new key
- mCurrentSlicedCounter[eventKey] = 1;
-
+ (*mCurrentSlicedCounter)[eventKey] = 1;
} else {
// increment the existing value
auto& count = it->second;
count++;
}
- // TODO: Re-add anomaly detection (similar to):
- // for (auto& tracker : mAnomalyTrackers) {
- // tracker->checkAnomaly(mCounter);
- // }
-
VLOG("metric %lld %s->%d", mMetric.metric_id(), eventKey.c_str(),
- mCurrentSlicedCounter[eventKey]);
+ (*mCurrentSlicedCounter)[eventKey]);
}
// When a new matched event comes in, we check if event falls into the current
@@ -230,12 +227,13 @@
}
// adjust the bucket start time
- int64_t numBucketsForward = (eventTimeNs - mCurrentBucketStartTimeNs) / mBucketSizeNs;
+ // TODO: This (and addPastBucket to which it goes) doesn't really need to be an int64.
+ uint64_t numBucketsForward = (eventTimeNs - mCurrentBucketStartTimeNs) / mBucketSizeNs;
CountBucket info;
info.mBucketStartNs = mCurrentBucketStartTimeNs;
info.mBucketEndNs = mCurrentBucketStartTimeNs + mBucketSizeNs;
- for (const auto& counter : mCurrentSlicedCounter) {
+ for (const auto& counter : *mCurrentSlicedCounter) {
info.mCount = counter.second;
auto& bucketList = mPastBuckets[counter.first];
bucketList.push_back(info);
@@ -244,15 +242,16 @@
mByteSize += sizeof(info);
}
- // TODO: Re-add anomaly detection (similar to):
- // for (auto& tracker : mAnomalyTrackers) {
- // tracker->addPastBucket(mCounter, numBucketsForward);
- //}
+ for (auto& tracker : mAnomalyTrackers) {
+ tracker->addOrUpdateBucket(mCurrentSlicedCounter, mCurrentBucketNum);
+ tracker->declareAndDeclareAnomaly();
+ }
- // Reset counters
- mCurrentSlicedCounter.clear();
+ // Reset counters (do not clear, since the old one is still referenced in mAnomalyTrackers).
+ mCurrentSlicedCounter = std::make_shared<DimToValMap>();
mCurrentBucketStartTimeNs = mCurrentBucketStartTimeNs + numBucketsForward * mBucketSizeNs;
+ mCurrentBucketNum += numBucketsForward;
VLOG("metric %lld: new bucket start time: %lld", mMetric.metric_id(),
(long long)mCurrentBucketStartTimeNs);
}
diff --git a/cmds/statsd/src/metrics/CountMetricProducer.h b/cmds/statsd/src/metrics/CountMetricProducer.h
index c3af006..b7e480c 100644
--- a/cmds/statsd/src/metrics/CountMetricProducer.h
+++ b/cmds/statsd/src/metrics/CountMetricProducer.h
@@ -23,14 +23,11 @@
#include <gtest/gtest_prod.h>
#include "../condition/ConditionTracker.h"
#include "../matchers/matcher_util.h"
-#include "CountAnomalyTracker.h"
+#include "../anomaly/DiscreteAnomalyTracker.h"
#include "MetricProducer.h"
-#include "frameworks/base/cmds/statsd/src/stats_log.pb.h"
#include "frameworks/base/cmds/statsd/src/statsd_config.pb.h"
#include "stats_util.h"
-using namespace std;
-
namespace android {
namespace os {
namespace statsd {
@@ -54,7 +51,7 @@
void finish() override;
// TODO: Pass a timestamp as a parameter in onDumpReport.
- StatsLogReport onDumpReport() override;
+ std::unique_ptr<std::vector<uint8_t>> onDumpReport() override;
void onSlicedConditionMayChange(const uint64_t eventTime) override;
@@ -82,9 +79,9 @@
size_t mByteSize;
// The current bucket.
- std::unordered_map<HashableDimensionKey, int> mCurrentSlicedCounter;
+ std::shared_ptr<DimToValMap> mCurrentSlicedCounter = std::make_shared<DimToValMap>();
- vector<unique_ptr<CountAnomalyTracker>> mAnomalyTrackers;
+ vector<std::unique_ptr<DiscreteAnomalyTracker>> mAnomalyTrackers;
void flushCounterIfNeeded(const uint64_t newEventTime);
diff --git a/cmds/statsd/src/metrics/DurationMetricProducer.cpp b/cmds/statsd/src/metrics/DurationMetricProducer.cpp
index c0a0d98..74ce9cd 100644
--- a/cmds/statsd/src/metrics/DurationMetricProducer.cpp
+++ b/cmds/statsd/src/metrics/DurationMetricProducer.cpp
@@ -23,6 +23,7 @@
#include <limits.h>
#include <stdlib.h>
+using android::util::FIELD_COUNT_REPEATED;
using android::util::FIELD_TYPE_BOOL;
using android::util::FIELD_TYPE_FLOAT;
using android::util::FIELD_TYPE_INT32;
@@ -159,7 +160,7 @@
}
}
-StatsLogReport DurationMetricProducer::onDumpReport() {
+std::unique_ptr<std::vector<uint8_t>> DurationMetricProducer::onDumpReport() {
long long endTime = time(nullptr) * NS_PER_SEC;
// Dump current bucket if it's stale.
@@ -176,11 +177,13 @@
ALOGW("Dimension key %s not found?!?! skip...", hashableKey.c_str());
continue;
}
- long long wrapperToken = mProto->start(FIELD_TYPE_MESSAGE | FIELD_ID_DATA);
+ long long wrapperToken =
+ mProto->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_DATA);
// First fill dimension (KeyValuePairs).
for (const auto& kv : it->second) {
- long long dimensionToken = mProto->start(FIELD_TYPE_MESSAGE | FIELD_ID_DIMENSION);
+ long long dimensionToken =
+ mProto->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_DIMENSION);
mProto->write(FIELD_TYPE_INT32 | FIELD_ID_KEY, kv.key());
if (kv.has_value_str()) {
mProto->write(FIELD_TYPE_INT32 | FIELD_ID_VALUE_STR, kv.value_str());
@@ -196,7 +199,8 @@
// Then fill bucket_info (DurationBucketInfo).
for (const auto& bucket : pair.second) {
- long long bucketInfoToken = mProto->start(FIELD_TYPE_MESSAGE | FIELD_ID_BUCKET_INFO);
+ long long bucketInfoToken =
+ mProto->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_BUCKET_INFO);
mProto->write(FIELD_TYPE_INT64 | FIELD_ID_START_BUCKET_NANOS,
(long long)bucket.mBucketStartNs);
mProto->write(FIELD_TYPE_INT64 | FIELD_ID_END_BUCKET_NANOS,
@@ -214,14 +218,12 @@
mProto->write(FIELD_TYPE_INT64 | FIELD_ID_END_REPORT_NANOS,
(long long)mCurrentBucketStartTimeNs);
- std::unique_ptr<uint8_t[]> buffer = serializeProto();
+ std::unique_ptr<std::vector<uint8_t>> buffer = serializeProto();
startNewProtoOutputStream(endTime);
mPastBuckets.clear();
- // TODO: Once we migrate all MetricProducers to use ProtoOutputStream, we should return this:
- // return std::move(buffer);
- return StatsLogReport();
+ return buffer;
}
void DurationMetricProducer::flushIfNeeded(uint64_t eventTime) {
diff --git a/cmds/statsd/src/metrics/DurationMetricProducer.h b/cmds/statsd/src/metrics/DurationMetricProducer.h
index 8fdd0d4..eea00454 100644
--- a/cmds/statsd/src/metrics/DurationMetricProducer.h
+++ b/cmds/statsd/src/metrics/DurationMetricProducer.h
@@ -26,7 +26,6 @@
#include "duration_helper/DurationTracker.h"
#include "duration_helper/MaxDurationTracker.h"
#include "duration_helper/OringDurationTracker.h"
-#include "frameworks/base/cmds/statsd/src/stats_log.pb.h"
#include "frameworks/base/cmds/statsd/src/statsd_config.pb.h"
#include "stats_util.h"
@@ -50,7 +49,7 @@
void finish() override;
// TODO: Pass a timestamp as a parameter in onDumpReport.
- StatsLogReport onDumpReport() override;
+ std::unique_ptr<std::vector<uint8_t>> onDumpReport() override;
void onSlicedConditionMayChange(const uint64_t eventTime) override;
diff --git a/cmds/statsd/src/metrics/EventMetricProducer.cpp b/cmds/statsd/src/metrics/EventMetricProducer.cpp
index ee0bfde..a65092f 100644
--- a/cmds/statsd/src/metrics/EventMetricProducer.cpp
+++ b/cmds/statsd/src/metrics/EventMetricProducer.cpp
@@ -23,6 +23,7 @@
#include <limits.h>
#include <stdlib.h>
+using android::util::FIELD_COUNT_REPEATED;
using android::util::FIELD_TYPE_BOOL;
using android::util::FIELD_TYPE_FLOAT;
using android::util::FIELD_TYPE_INT32;
@@ -84,20 +85,19 @@
void EventMetricProducer::onSlicedConditionMayChange(const uint64_t eventTime) {
}
-StatsLogReport EventMetricProducer::onDumpReport() {
+std::unique_ptr<std::vector<uint8_t>> EventMetricProducer::onDumpReport() {
long long endTime = time(nullptr) * NS_PER_SEC;
mProto->end(mProtoToken);
mProto->write(FIELD_TYPE_INT64 | FIELD_ID_END_REPORT_NANOS, endTime);
size_t bufferSize = mProto->size();
VLOG("metric %lld dump report now... proto size: %zu ", mMetric.metric_id(), bufferSize);
- std::unique_ptr<uint8_t[]> buffer = serializeProto();
+ std::unique_ptr<std::vector<uint8_t>> buffer = serializeProto();
startNewProtoOutputStream(endTime);
+ mByteSize = 0;
- // TODO: Once we migrate all MetricProducers to use ProtoOutputStream, we should return this:
- // return std::move(buffer);
- return StatsLogReport();
+ return buffer;
}
void EventMetricProducer::onConditionChanged(const bool conditionMet, const uint64_t eventTime) {
@@ -113,16 +113,18 @@
return;
}
- long long wrapperToken = mProto->start(FIELD_TYPE_MESSAGE | FIELD_ID_DATA);
+ long long wrapperToken =
+ mProto->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_DATA);
mProto->write(FIELD_TYPE_INT64 | FIELD_ID_TIMESTAMP_NANOS, (long long)event.GetTimestampNs());
long long eventToken = mProto->start(FIELD_TYPE_MESSAGE | FIELD_ID_STATS_EVENTS);
event.ToProto(*mProto);
mProto->end(eventToken);
mProto->end(wrapperToken);
+ // TODO: Find a proper way to derive the size of incoming LogEvent.
}
size_t EventMetricProducer::byteSize() {
- return mProto->size();
+ return mByteSize;
}
} // namespace statsd
diff --git a/cmds/statsd/src/metrics/EventMetricProducer.h b/cmds/statsd/src/metrics/EventMetricProducer.h
index 2ca8181..7740621 100644
--- a/cmds/statsd/src/metrics/EventMetricProducer.h
+++ b/cmds/statsd/src/metrics/EventMetricProducer.h
@@ -24,7 +24,6 @@
#include "../condition/ConditionTracker.h"
#include "../matchers/matcher_util.h"
#include "MetricProducer.h"
-#include "frameworks/base/cmds/statsd/src/stats_log.pb.h"
#include "frameworks/base/cmds/statsd/src/statsd_config.pb.h"
#include "stats_util.h"
@@ -50,7 +49,7 @@
void finish() override;
// TODO: Pass a timestamp as a parameter in onDumpReport.
- StatsLogReport onDumpReport() override;
+ std::unique_ptr<std::vector<uint8_t>> onDumpReport() override;
void onSlicedConditionMayChange(const uint64_t eventTime) override;
@@ -66,6 +65,8 @@
private:
const EventMetric mMetric;
+
+ size_t mByteSize;
};
} // namespace statsd
diff --git a/cmds/statsd/src/metrics/GaugeMetricProducer.cpp b/cmds/statsd/src/metrics/GaugeMetricProducer.cpp
index dcfcc19..fafeee4 100644
--- a/cmds/statsd/src/metrics/GaugeMetricProducer.cpp
+++ b/cmds/statsd/src/metrics/GaugeMetricProducer.cpp
@@ -24,6 +24,7 @@
#include <limits.h>
#include <stdlib.h>
+using android::util::FIELD_COUNT_REPEATED;
using android::util::FIELD_TYPE_BOOL;
using android::util::FIELD_TYPE_FLOAT;
using android::util::FIELD_TYPE_INT32;
@@ -106,7 +107,7 @@
void GaugeMetricProducer::finish() {
}
-StatsLogReport GaugeMetricProducer::onDumpReport() {
+std::unique_ptr<std::vector<uint8_t>> GaugeMetricProducer::onDumpReport() {
VLOG("gauge metric %lld dump report now...", mMetric.metric_id());
// Dump current bucket if it's stale.
@@ -123,11 +124,13 @@
}
VLOG(" dimension key %s", hashableKey.c_str());
- long long wrapperToken = mProto->start(FIELD_TYPE_MESSAGE | FIELD_ID_DATA);
+ long long wrapperToken =
+ mProto->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_DATA);
// First fill dimension (KeyValuePairs).
for (const auto& kv : it->second) {
- long long dimensionToken = mProto->start(FIELD_TYPE_MESSAGE | FIELD_ID_DIMENSION);
+ long long dimensionToken =
+ mProto->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_DIMENSION);
mProto->write(FIELD_TYPE_INT32 | FIELD_ID_KEY, kv.key());
if (kv.has_value_str()) {
mProto->write(FIELD_TYPE_INT32 | FIELD_ID_VALUE_STR, kv.value_str());
@@ -143,7 +146,8 @@
// Then fill bucket_info (GaugeBucketInfo).
for (const auto& bucket : pair.second) {
- long long bucketInfoToken = mProto->start(FIELD_TYPE_MESSAGE | FIELD_ID_BUCKET_INFO);
+ long long bucketInfoToken =
+ mProto->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_BUCKET_INFO);
mProto->write(FIELD_TYPE_INT64 | FIELD_ID_START_BUCKET_NANOS,
(long long)bucket.mBucketStartNs);
mProto->write(FIELD_TYPE_INT64 | FIELD_ID_END_BUCKET_NANOS,
@@ -159,15 +163,13 @@
mProto->write(FIELD_TYPE_INT64 | FIELD_ID_END_REPORT_NANOS,
(long long)mCurrentBucketStartTimeNs);
- std::unique_ptr<uint8_t[]> buffer = serializeProto();
+ std::unique_ptr<std::vector<uint8_t>> buffer = serializeProto();
startNewProtoOutputStream(time(nullptr) * NS_PER_SEC);
mPastBuckets.clear();
mByteSize = 0;
- // TODO: Once we migrate all MetricProducers to use ProtoOutputStream, we should return this:
- // return std::move(buffer);
- return StatsLogReport();
+ return buffer;
// TODO: Clear mDimensionKeyMap once the report is dumped.
}
diff --git a/cmds/statsd/src/metrics/GaugeMetricProducer.h b/cmds/statsd/src/metrics/GaugeMetricProducer.h
index 3757174..f9e4deb 100644
--- a/cmds/statsd/src/metrics/GaugeMetricProducer.h
+++ b/cmds/statsd/src/metrics/GaugeMetricProducer.h
@@ -24,7 +24,6 @@
#include "../external/StatsPullerManager.h"
#include "../matchers/matcher_util.h"
#include "MetricProducer.h"
-#include "frameworks/base/cmds/statsd/src/stats_log.pb.h"
#include "frameworks/base/cmds/statsd/src/statsd_config.pb.h"
#include "stats_util.h"
@@ -60,7 +59,7 @@
void finish() override;
// TODO: Pass a timestamp as a parameter in onDumpReport.
- StatsLogReport onDumpReport() override;
+ std::unique_ptr<std::vector<uint8_t>> onDumpReport() override;
size_t byteSize() override;
@@ -82,7 +81,7 @@
static const uint64_t kDefaultGaugemBucketSizeNs = 1000 * 1000 * 1000;
const GaugeMetric mMetric;
- StatsPullerManager& mStatsPullerManager = StatsPullerManager::GetInstance();
+ StatsPullerManager mStatsPullerManager;
// tagId for pulled data. -1 if this is not pulled
const int mPullTagId;
diff --git a/cmds/statsd/src/metrics/MetricProducer.cpp b/cmds/statsd/src/metrics/MetricProducer.cpp
index 5ca83b2..62fb632 100644
--- a/cmds/statsd/src/metrics/MetricProducer.cpp
+++ b/cmds/statsd/src/metrics/MetricProducer.cpp
@@ -64,16 +64,16 @@
scheduledPull);
}
-std::unique_ptr<uint8_t[]> MetricProducer::serializeProto() {
+std::unique_ptr<std::vector<uint8_t>> MetricProducer::serializeProto() {
size_t bufferSize = mProto->size();
- std::unique_ptr<uint8_t[]> buffer(new uint8_t[bufferSize]);
+ std::unique_ptr<std::vector<uint8_t>> buffer(new std::vector<uint8_t>(bufferSize));
size_t pos = 0;
auto it = mProto->data();
while (it.readBuffer() != NULL) {
size_t toRead = it.currentToRead();
- std::memcpy(&buffer[pos], it.readBuffer(), toRead);
+ std::memcpy(&((*buffer)[pos]), it.readBuffer(), toRead);
pos += toRead;
it.rp()->move(toRead);
}
diff --git a/cmds/statsd/src/metrics/MetricProducer.h b/cmds/statsd/src/metrics/MetricProducer.h
index 7ac97a8..c0930e3 100644
--- a/cmds/statsd/src/metrics/MetricProducer.h
+++ b/cmds/statsd/src/metrics/MetricProducer.h
@@ -23,7 +23,6 @@
#include <log/logprint.h>
#include <utils/RefBase.h>
-#include "frameworks/base/cmds/statsd/src/stats_log.pb.h"
namespace android {
namespace os {
@@ -39,6 +38,7 @@
const sp<ConditionWizard>& wizard)
: mStartTimeNs(startTimeNs),
mCurrentBucketStartTimeNs(startTimeNs),
+ mCurrentBucketNum(0),
mCondition(conditionIndex >= 0 ? false : true),
mConditionSliced(false),
mWizard(wizard),
@@ -61,7 +61,7 @@
// TODO: Pass a timestamp as a parameter in onDumpReport and update all its
// implementations.
- virtual StatsLogReport onDumpReport() = 0;
+ virtual std::unique_ptr<std::vector<uint8_t>> onDumpReport() = 0;
virtual bool isConditionSliced() const {
return mConditionSliced;
@@ -74,6 +74,8 @@
uint64_t mCurrentBucketStartTimeNs;
+ uint64_t mCurrentBucketNum;
+
int64_t mBucketSizeNs;
bool mCondition;
@@ -118,7 +120,7 @@
virtual void startNewProtoOutputStream(long long timestamp) = 0;
- std::unique_ptr<uint8_t[]> serializeProto();
+ std::unique_ptr<std::vector<uint8_t>> serializeProto();
};
} // namespace statsd
diff --git a/cmds/statsd/src/metrics/MetricsManager.cpp b/cmds/statsd/src/metrics/MetricsManager.cpp
index 80b325f..e8a862f 100644
--- a/cmds/statsd/src/metrics/MetricsManager.cpp
+++ b/cmds/statsd/src/metrics/MetricsManager.cpp
@@ -56,10 +56,10 @@
}
}
-vector<StatsLogReport> MetricsManager::onDumpReport() {
+vector<std::unique_ptr<vector<uint8_t>>> MetricsManager::onDumpReport() {
VLOG("=========================Metric Reports Start==========================");
// one StatsLogReport per MetricProduer
- vector<StatsLogReport> reportList;
+ vector<std::unique_ptr<vector<uint8_t>>> reportList;
for (auto& metric : mAllMetricProducers) {
reportList.push_back(metric->onDumpReport());
}
@@ -80,8 +80,6 @@
return;
}
- // Since at least one of the metrics is interested in this event, we parse it now.
- ALOGD("%s", event.ToString().c_str());
vector<MatchingState> matcherCache(mAllLogEntryMatchers.size(), MatchingState::kNotComputed);
for (auto& matcher : mAllLogEntryMatchers) {
@@ -104,18 +102,17 @@
ConditionState::kNotEvaluated);
// A bitmap to track if a condition has changed value.
vector<bool> changedCache(mAllConditionTrackers.size(), false);
- vector<bool> slicedChangedCache(mAllConditionTrackers.size(), false);
for (size_t i = 0; i < mAllConditionTrackers.size(); i++) {
if (conditionToBeEvaluated[i] == false) {
continue;
}
sp<ConditionTracker>& condition = mAllConditionTrackers[i];
condition->evaluateCondition(event, matcherCache, mAllConditionTrackers, conditionCache,
- changedCache, slicedChangedCache);
+ changedCache);
}
for (size_t i = 0; i < mAllConditionTrackers.size(); i++) {
- if (changedCache[i] == false && slicedChangedCache[i] == false) {
+ if (changedCache[i] == false) {
continue;
}
auto pair = mConditionToMetricMap.find(i);
@@ -124,14 +121,13 @@
for (auto metricIndex : metricList) {
// metric cares about non sliced condition, and it's changed.
// Push the new condition to it directly.
- if (!mAllMetricProducers[metricIndex]->isConditionSliced() && changedCache[i]) {
+ if (!mAllMetricProducers[metricIndex]->isConditionSliced()) {
mAllMetricProducers[metricIndex]->onConditionChanged(conditionCache[i],
eventTime);
// metric cares about sliced conditions, and it may have changed. Send
// notification, and the metric can query the sliced conditions that are
// interesting to it.
- } else if (mAllMetricProducers[metricIndex]->isConditionSliced() &&
- slicedChangedCache[i]) {
+ } else if (mAllMetricProducers[metricIndex]->isConditionSliced()) {
mAllMetricProducers[metricIndex]->onSlicedConditionMayChange(eventTime);
}
}
diff --git a/cmds/statsd/src/metrics/MetricsManager.h b/cmds/statsd/src/metrics/MetricsManager.h
index 63e2c33..39c79f9 100644
--- a/cmds/statsd/src/metrics/MetricsManager.h
+++ b/cmds/statsd/src/metrics/MetricsManager.h
@@ -44,7 +44,7 @@
void finish();
// Config source owner can call onDumpReport() to get all the metrics collected.
- std::vector<StatsLogReport> onDumpReport();
+ std::vector<std::unique_ptr<std::vector<uint8_t>>> onDumpReport();
size_t byteSize();
diff --git a/cmds/statsd/src/metrics/ValueMetricProducer.cpp b/cmds/statsd/src/metrics/ValueMetricProducer.cpp
index 41b7a5d..5cffec1 100644
--- a/cmds/statsd/src/metrics/ValueMetricProducer.cpp
+++ b/cmds/statsd/src/metrics/ValueMetricProducer.cpp
@@ -23,6 +23,7 @@
#include <limits.h>
#include <stdlib.h>
+using android::util::FIELD_COUNT_REPEATED;
using android::util::FIELD_TYPE_BOOL;
using android::util::FIELD_TYPE_FLOAT;
using android::util::FIELD_TYPE_INT32;
@@ -30,6 +31,7 @@
using android::util::FIELD_TYPE_MESSAGE;
using android::util::ProtoOutputStream;
using std::list;
+using std::make_pair;
using std::make_shared;
using std::map;
using std::shared_ptr;
@@ -61,13 +63,23 @@
const int FIELD_ID_END_BUCKET_NANOS = 2;
const int FIELD_ID_VALUE = 3;
+static const uint64_t kDefaultBucketSizeMillis = 60 * 60 * 1000L;
+
// ValueMetric has a minimum bucket size of 10min so that we don't pull too frequently
ValueMetricProducer::ValueMetricProducer(const ValueMetric& metric, const int conditionIndex,
const sp<ConditionWizard>& wizard, const int pullTagId,
- const uint64_t startTimeNs)
- : MetricProducer(startTimeNs, conditionIndex, wizard), mMetric(metric), mPullTagId(pullTagId) {
+ const uint64_t startTimeNs,
+ shared_ptr<StatsPullerManager> statsPullerManager)
+ : MetricProducer(startTimeNs, conditionIndex, wizard),
+ mMetric(metric),
+ mStatsPullerManager(statsPullerManager),
+ mPullTagId(pullTagId) {
// TODO: valuemetric for pushed events may need unlimited bucket length
- mBucketSizeNs = mMetric.bucket().bucket_size_millis() * 1000 * 1000;
+ if (metric.has_bucket() && metric.bucket().has_bucket_size_millis()) {
+ mBucketSizeNs = mMetric.bucket().bucket_size_millis() * 1000 * 1000;
+ } else {
+ mBucketSizeNs = kDefaultBucketSizeMillis * 1000 * 1000;
+ }
mDimension.insert(mDimension.begin(), metric.dimension().begin(), metric.dimension().end());
@@ -78,8 +90,9 @@
}
if (!metric.has_condition() && mPullTagId != -1) {
- mStatsPullerManager.RegisterReceiver(mPullTagId, this,
- metric.bucket().bucket_size_millis());
+ VLOG("Setting up periodic pulling for %d", mPullTagId);
+ mStatsPullerManager->RegisterReceiver(mPullTagId, this,
+ metric.bucket().bucket_size_millis());
}
startNewProtoOutputStream(mStartTimeNs);
@@ -88,8 +101,19 @@
(long long)mBucketSizeNs, (long long)mStartTimeNs);
}
+// for testing
+ValueMetricProducer::ValueMetricProducer(const ValueMetric& metric, const int conditionIndex,
+ const sp<ConditionWizard>& wizard, const int pullTagId,
+ const uint64_t startTimeNs)
+ : ValueMetricProducer(metric, conditionIndex, wizard, pullTagId, startTimeNs,
+ make_shared<StatsPullerManager>()) {
+}
+
ValueMetricProducer::~ValueMetricProducer() {
VLOG("~ValueMetricProducer() called");
+ if (mPullTagId != -1) {
+ mStatsPullerManager->UnRegisterReceiver(mPullTagId, this);
+ }
}
void ValueMetricProducer::startNewProtoOutputStream(long long startTime) {
@@ -108,7 +132,7 @@
VLOG("Metric %lld onSlicedConditionMayChange", mMetric.metric_id());
}
-StatsLogReport ValueMetricProducer::onDumpReport() {
+std::unique_ptr<std::vector<uint8_t>> ValueMetricProducer::onDumpReport() {
VLOG("metric %lld dump report now...", mMetric.metric_id());
for (const auto& pair : mPastBuckets) {
@@ -119,11 +143,13 @@
ALOGE("Dimension key %s not found?!?! skip...", hashableKey.c_str());
continue;
}
- long long wrapperToken = mProto->start(FIELD_TYPE_MESSAGE | FIELD_ID_DATA);
+ long long wrapperToken =
+ mProto->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_DATA);
// First fill dimension (KeyValuePairs).
for (const auto& kv : it->second) {
- long long dimensionToken = mProto->start(FIELD_TYPE_MESSAGE | FIELD_ID_DIMENSION);
+ long long dimensionToken =
+ mProto->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_DIMENSION);
mProto->write(FIELD_TYPE_INT32 | FIELD_ID_KEY, kv.key());
if (kv.has_value_str()) {
mProto->write(FIELD_TYPE_INT32 | FIELD_ID_VALUE_STR, kv.value_str());
@@ -139,7 +165,8 @@
// Then fill bucket_info (ValueBucketInfo).
for (const auto& bucket : pair.second) {
- long long bucketInfoToken = mProto->start(FIELD_TYPE_MESSAGE | FIELD_ID_BUCKET_INFO);
+ long long bucketInfoToken =
+ mProto->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_BUCKET_INFO);
mProto->write(FIELD_TYPE_INT64 | FIELD_ID_START_BUCKET_NANOS,
(long long)bucket.mBucketStartNs);
mProto->write(FIELD_TYPE_INT64 | FIELD_ID_END_BUCKET_NANOS,
@@ -156,36 +183,34 @@
(long long)mCurrentBucketStartTimeNs);
VLOG("metric %lld dump report now...", mMetric.metric_id());
- std::unique_ptr<uint8_t[]> buffer = serializeProto();
+ std::unique_ptr<std::vector<uint8_t>> buffer = serializeProto();
startNewProtoOutputStream(time(nullptr) * NS_PER_SEC);
mPastBuckets.clear();
mByteSize = 0;
- // TODO: Once we migrate all MetricProducers to use ProtoOutputStream, we should return this:
- // return std::move(buffer);
- return StatsLogReport();
+ return buffer;
// TODO: Clear mDimensionKeyMap once the report is dumped.
}
void ValueMetricProducer::onConditionChanged(const bool condition, const uint64_t eventTime) {
+ AutoMutex _l(mLock);
mCondition = condition;
if (mPullTagId != -1) {
if (mCondition == true) {
- mStatsPullerManager.RegisterReceiver(mPullTagId, this,
- mMetric.bucket().bucket_size_millis());
- } else if (mCondition == ConditionState::kFalse) {
- mStatsPullerManager.UnRegisterReceiver(mPullTagId, this);
+ mStatsPullerManager->RegisterReceiver(mPullTagId, this,
+ mMetric.bucket().bucket_size_millis());
+ } else if (mCondition == false) {
+ mStatsPullerManager->UnRegisterReceiver(mPullTagId, this);
}
vector<shared_ptr<LogEvent>> allData;
- if (mStatsPullerManager.Pull(mPullTagId, &allData)) {
+ if (mStatsPullerManager->Pull(mPullTagId, &allData)) {
if (allData.size() == 0) {
return;
}
- AutoMutex _l(mLock);
for (const auto& data : allData) {
onMatchedLogEvent(0, *data, false);
}
@@ -196,12 +221,16 @@
}
void ValueMetricProducer::onDataPulled(const std::vector<std::shared_ptr<LogEvent>>& allData) {
- if (mCondition == ConditionState::kTrue || !mMetric.has_condition()) {
- AutoMutex _l(mLock);
+ AutoMutex _l(mLock);
+ if (mCondition == true || !mMetric.has_condition()) {
if (allData.size() == 0) {
return;
}
uint64_t eventTime = allData.at(0)->GetTimestampNs();
+ // alarm is not accurate and might drift.
+ if (eventTime > mCurrentBucketStartTimeNs + mBucketSizeNs * 3 / 2) {
+ flush_if_needed(eventTime);
+ }
for (const auto& data : allData) {
onMatchedLogEvent(0, *data, true);
}
@@ -224,24 +253,36 @@
long value = get_value(event);
- if (scheduledPull) {
- if (interval.raw.size() > 0) {
- interval.raw.back().second = value;
- } else {
- interval.raw.push_back(std::make_pair(value, value));
- }
- mNextSlicedBucket[eventKey].raw[0].first = value;
- } else {
- if (mCondition == ConditionState::kTrue) {
- interval.raw.push_back(std::make_pair(value, 0));
- } else {
- if (interval.raw.size() != 0) {
+ if (mPullTagId != -1) {
+ if (scheduledPull) {
+ // scheduled pull always sets beginning of current bucket and end
+ // of next bucket
+ if (interval.raw.size() > 0) {
interval.raw.back().second = value;
+ } else {
+ interval.raw.push_back(make_pair(value, value));
+ }
+ Interval& nextInterval = mNextSlicedBucket[eventKey];
+ if (nextInterval.raw.size() == 0) {
+ nextInterval.raw.push_back(make_pair(value, 0));
+ } else {
+ nextInterval.raw.front().first = value;
+ }
+ } else {
+ if (mCondition == true) {
+ interval.raw.push_back(make_pair(value, 0));
+ } else {
+ if (interval.raw.size() != 0) {
+ interval.raw.back().second = value;
+ } else {
+ interval.tainted = true;
+ VLOG("Data on condition true missing!");
+ }
}
}
- }
- if (mPullTagId == -1) {
+ } else {
flush_if_needed(eventTimeNs);
+ interval.raw.push_back(make_pair(value, 0));
}
}
@@ -251,7 +292,7 @@
if (err == NO_ERROR) {
return val;
} else {
- VLOG("Can't find value in message.");
+ VLOG("Can't find value in message. %s", event.ToString().c_str());
return 0;
}
}
@@ -269,13 +310,21 @@
info.mBucketStartNs = mCurrentBucketStartTimeNs;
info.mBucketEndNs = mCurrentBucketStartTimeNs + mBucketSizeNs;
+ int tainted = 0;
for (const auto& slice : mCurrentSlicedBucket) {
long value = 0;
- for (const auto& pair : slice.second.raw) {
- value += pair.second - pair.first;
+ if (mPullTagId != -1) {
+ for (const auto& pair : slice.second.raw) {
+ value += (pair.second - pair.first);
+ }
+ } else {
+ for (const auto& pair : slice.second.raw) {
+ value += pair.first;
+ }
}
+ tainted += slice.second.tainted;
info.mValue = value;
- VLOG(" %s, %ld", slice.first.c_str(), value);
+ VLOG(" %s, %ld, %d", slice.first.c_str(), value, tainted);
// it will auto create new vector of ValuebucketInfo if the key is not found.
auto& bucketList = mPastBuckets[slice.first];
bucketList.push_back(info);
diff --git a/cmds/statsd/src/metrics/ValueMetricProducer.h b/cmds/statsd/src/metrics/ValueMetricProducer.h
index 8437665..c6c87f5 100644
--- a/cmds/statsd/src/metrics/ValueMetricProducer.h
+++ b/cmds/statsd/src/metrics/ValueMetricProducer.h
@@ -16,14 +16,13 @@
#pragma once
+#include <gtest/gtest_prod.h>
#include <utils/threads.h>
#include <list>
#include "../condition/ConditionTracker.h"
#include "../external/PullDataReceiver.h"
#include "../external/StatsPullerManager.h"
-#include "CountAnomalyTracker.h"
#include "MetricProducer.h"
-#include "frameworks/base/cmds/statsd/src/stats_log.pb.h"
#include "frameworks/base/cmds/statsd/src/statsd_config.pb.h"
namespace android {
@@ -49,7 +48,7 @@
void finish() override;
// TODO: Pass a timestamp as a parameter in onDumpReport.
- StatsLogReport onDumpReport() override;
+ std::unique_ptr<std::vector<uint8_t>> onDumpReport() override;
void onSlicedConditionMayChange(const uint64_t eventTime);
@@ -73,7 +72,13 @@
private:
const ValueMetric mMetric;
- StatsPullerManager& mStatsPullerManager = StatsPullerManager::GetInstance();
+ std::shared_ptr<StatsPullerManager> mStatsPullerManager;
+
+ // for testing
+ ValueMetricProducer(const ValueMetric& valueMetric, const int conditionIndex,
+ const sp<ConditionWizard>& wizard, const int pullTagId,
+ const uint64_t startTimeNs,
+ std::shared_ptr<StatsPullerManager> statsPullerManager);
Mutex mLock;
@@ -83,6 +88,7 @@
// internal state of a bucket.
typedef struct {
std::vector<std::pair<long, long>> raw;
+ bool tainted;
} Interval;
std::unordered_map<HashableDimensionKey, Interval> mCurrentSlicedBucket;
@@ -99,6 +105,10 @@
void flush_if_needed(const uint64_t eventTimeNs);
size_t mByteSize;
+
+ FRIEND_TEST(ValueMetricProducerTest, TestNonDimensionalEvents);
+ FRIEND_TEST(ValueMetricProducerTest, TestEventsWithNonSlicedCondition);
+ FRIEND_TEST(ValueMetricProducerTest, TestPushedEventsWithoutCondition);
};
} // namespace statsd
diff --git a/cmds/statsd/src/metrics/duration_helper/DurationTracker.h b/cmds/statsd/src/metrics/duration_helper/DurationTracker.h
index aeb234d..5c76d0e 100644
--- a/cmds/statsd/src/metrics/duration_helper/DurationTracker.h
+++ b/cmds/statsd/src/metrics/duration_helper/DurationTracker.h
@@ -20,8 +20,6 @@
#include "condition/ConditionWizard.h"
#include "stats_util.h"
-#include "frameworks/base/cmds/statsd/src/stats_log.pb.h"
-
namespace android {
namespace os {
namespace statsd {
diff --git a/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.h b/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.h
index de53d62..b095884 100644
--- a/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.h
+++ b/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.h
@@ -19,8 +19,6 @@
#include "DurationTracker.h"
-#include "frameworks/base/cmds/statsd/src/stats_log.pb.h"
-
namespace android {
namespace os {
namespace statsd {
diff --git a/cmds/statsd/src/metrics/metrics_manager_util.cpp b/cmds/statsd/src/metrics/metrics_manager_util.cpp
index ca9cdfb..226e4d1 100644
--- a/cmds/statsd/src/metrics/metrics_manager_util.cpp
+++ b/cmds/statsd/src/metrics/metrics_manager_util.cpp
@@ -195,7 +195,7 @@
const int allMetricsCount = config.count_metric_size() + config.duration_metric_size() +
config.event_metric_size() + config.value_metric_size();
allMetricProducers.reserve(allMetricsCount);
- StatsPullerManager& statsPullerManager = StatsPullerManager::GetInstance();
+ StatsPullerManager statsPullerManager;
uint64_t startTimeNs = time(nullptr) * NS_PER_SEC;
// Build MetricProducers for each metric defined in config.
diff --git a/cmds/statsd/src/metrics/metrics_manager_util.h b/cmds/statsd/src/metrics/metrics_manager_util.h
index e089d065..edf3af0 100644
--- a/cmds/statsd/src/metrics/metrics_manager_util.h
+++ b/cmds/statsd/src/metrics/metrics_manager_util.h
@@ -21,7 +21,7 @@
#include <vector>
#include "../condition/ConditionTracker.h"
-#include "../external/StatsPullerManager.h"
+#include "../external/StatsPullerManagerImpl.h"
#include "../matchers/LogMatchingTracker.h"
namespace android {
diff --git a/cmds/statsd/src/packages/UidMap.cpp b/cmds/statsd/src/packages/UidMap.cpp
index d83c3a4..2398814 100644
--- a/cmds/statsd/src/packages/UidMap.cpp
+++ b/cmds/statsd/src/packages/UidMap.cpp
@@ -52,7 +52,7 @@
void UidMap::updateMap(const vector<int32_t>& uid, const vector<int32_t>& versionCode,
const vector<String16>& packageName) {
- updateMap(time(nullptr) * 1000000000, uid, versionCode, packageName);
+ updateMap(time(nullptr) * NS_PER_SEC, uid, versionCode, packageName);
}
void UidMap::updateMap(const int64_t& timestamp, const vector<int32_t>& uid,
@@ -76,7 +76,7 @@
}
void UidMap::updateApp(const String16& app_16, const int32_t& uid, const int32_t& versionCode) {
- updateApp(time(nullptr) * 1000000000, app_16, uid, versionCode);
+ updateApp(time(nullptr) * NS_PER_SEC, app_16, uid, versionCode);
}
void UidMap::updateApp(const int64_t& timestamp, const String16& app_16, const int32_t& uid,
@@ -112,7 +112,7 @@
}
void UidMap::removeApp(const String16& app_16, const int32_t& uid) {
- removeApp(time(nullptr) * 1000000000, app_16, uid);
+ removeApp(time(nullptr) * NS_PER_SEC, app_16, uid);
}
void UidMap::removeApp(const int64_t& timestamp, const String16& app_16, const int32_t& uid) {
lock_guard<mutex> lock(mMutex);
@@ -150,6 +150,31 @@
mSubscribers.erase(producer);
}
+void UidMap::assignIsolatedUid(int isolatedUid, int parentUid) {
+ lock_guard<mutex> lock(mIsolatedMutex);
+
+ mIsolatedUidMap[isolatedUid] = parentUid;
+}
+
+void UidMap::removeIsolatedUid(int isolatedUid, int parentUid) {
+ lock_guard<mutex> lock(mIsolatedMutex);
+
+ auto it = mIsolatedUidMap.find(isolatedUid);
+ if (it != mIsolatedUidMap.end()) {
+ mIsolatedUidMap.erase(it);
+ }
+}
+
+int UidMap::getParentUidOrSelf(int uid) {
+ lock_guard<mutex> lock(mIsolatedMutex);
+
+ auto it = mIsolatedUidMap.find(uid);
+ if (it != mIsolatedUidMap.end()) {
+ return it->second;
+ }
+ return uid;
+}
+
void UidMap::clearOutput() {
mOutput.Clear();
@@ -177,7 +202,7 @@
}
UidMapping UidMap::getOutput(const ConfigKey& key) {
- return getOutput(time(nullptr) * 1000000000, key);
+ return getOutput(time(nullptr) * NS_PER_SEC, key);
}
UidMapping UidMap::getOutput(const int64_t& timestamp, const ConfigKey& key) {
diff --git a/cmds/statsd/src/packages/UidMap.h b/cmds/statsd/src/packages/UidMap.h
index bf120e0..de68fbc 100644
--- a/cmds/statsd/src/packages/UidMap.h
+++ b/cmds/statsd/src/packages/UidMap.h
@@ -84,6 +84,12 @@
// Informs uid map that a config is removed. Used for keeping mConfigKeys up to date.
void OnConfigRemoved(const ConfigKey& key);
+ void assignIsolatedUid(int isolatedUid, int parentUid);
+ void removeIsolatedUid(int isolatedUid, int parentUid);
+
+ // Returns the parent uid if it exists. Otherwise, returns the same uid that was passed-in.
+ int getParentUidOrSelf(int uid);
+
// Gets the output. If every config key has received the output, then the output is cleared.
UidMapping getOutput(const ConfigKey& key);
@@ -105,11 +111,16 @@
// TODO: Use shared_mutex for improved read-locking if a library can be found in Android.
mutable mutex mMutex;
+ mutable mutex mIsolatedMutex;
// Maps uid to application data. This must be multimap since there is a feature in Android for
// multiple apps to share the same uid.
std::unordered_multimap<int, AppData> mMap;
+ // Maps isolated uid to the parent uid. Any metrics for an isolated uid will instead contribute
+ // to the parent uid.
+ std::unordered_map<int, int> mIsolatedUidMap;
+
// We prepare the output proto as apps are updated, so that we can grab the current output.
UidMapping mOutput;
diff --git a/cmds/statsd/src/stats_events.proto b/cmds/statsd/src/stats_events.proto
index 82d9759..9ab07de 100644
--- a/cmds/statsd/src/stats_events.proto
+++ b/cmds/statsd/src/stats_events.proto
@@ -72,7 +72,7 @@
PhoneSignalStrengthChanged phone_signal_strength_changed = 40;
SettingChanged setting_changed = 41;
ActivityForegroundStateChanged activity_foreground_state_changed = 42;
-
+ IsolatedUidChanged isolated_uid_changed = 43;
// TODO: Reorder the numbering so that the most frequent occur events occur in the first 15.
}
@@ -86,6 +86,9 @@
PowerStatePlatformSleepStatePulled power_state_platform_sleep_state_pulled = 1005;
PowerStateVoterPulled power_state_voter_pulled = 1006;
PowerStateSubsystemSleepStatePulled power_state_subsystem_sleep_state_pulled = 1007;
+ CpuTimePerFreqPulled cpu_time_per_freq_pulled = 1008;
+ CpuTimePerUidPulled cpu_time_per_uid_pulled = 1009;
+ CpuTimePerUidFreqPulled cpu_time_per_uid_freq_pulled = 1010;
}
}
@@ -695,7 +698,6 @@
optional int32 user = 7;
}
-
/*
* Logs activity going to foreground or background
*
@@ -848,3 +850,62 @@
optional uint64 last_entry_timestamp_ms = 5;
optional bool supported_only_in_suspend = 6;
}
+
+/**
+<<<<<<< HEAD
+ * Logs creation or removal of an isolated uid. Isolated uid's are temporary uid's to sandbox risky
+ * behavior in its own uid. However, the metrics of these isolated uid's almost always should be
+ * attributed back to the parent (host) uid. One example is Chrome.
+ *
+ * Logged from:
+ * frameworks/base/core/java/com/android/internal/os/BatteryStatsImpl.java
+ */
+message IsolatedUidChanged {
+ // The host UID. Generally, we should attribute metrics from the isolated uid to the host uid.
+ optional int32 parent_uid = 1;
+
+ optional int32 isolated_uid = 2;
+
+ // 1 denotes we're creating an isolated uid and 0 denotes removal. We expect an isolated uid to
+ // be removed before if it's used for another parent uid.
+ optional int32 is_create = 3;
+}
+
+/*
+<<<<<<< HEAD
+ * Pulls Cpu time per frequency.
+ * Note: this should be pulled for gauge metric only, without condition.
+ * The puller keeps internal state of last values. It should not be pulled by
+ * different metrics.
+ * The pulled data is delta of cpu time from last pull, calculated as
+ * following:
+ * if current time is larger than last value, take delta between the two.
+ * if current time is smaller than last value, there must be a cpu
+ * hotplug event, and the current time is taken as delta.
+ */
+message CpuTimePerFreqPulled {
+ optional uint32 cluster = 1;
+ optional uint32 freq_index = 2;
+ optional uint64 time = 3;
+}
+
+/*
+ * Pulls Cpu Time Per Uid.
+ * Note that isolated process uid time should be attributed to host uids.
+ */
+message CpuTimePerUidPulled {
+ optional uint64 uid = 1;
+ optional uint64 user_time_ms = 2;
+ optional uint64 sys_time_ms = 3;
+}
+
+/**
+ * Pulls Cpu Time Per Uid per frequency.
+ * Note that isolated process uid time should be attributed to host uids.
+ * For each uid, we order the time by descending frequencies.
+ */
+message CpuTimePerUidFreqPulled {
+ optional uint64 uid = 1;
+ optional uint64 freq_idx = 2;
+ optional uint64 time_ms = 3;
+}
diff --git a/cmds/statsd/src/stats_util.h b/cmds/statsd/src/stats_util.h
index a9507bf..e1d0aceb 100644
--- a/cmds/statsd/src/stats_util.h
+++ b/cmds/statsd/src/stats_util.h
@@ -13,8 +13,8 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-#ifndef STATS_UTIL_H
-#define STATS_UTIL_H
+
+#pragma once
#include "logd/LogReader.h"
#include "storage/DropboxWriter.h"
@@ -22,6 +22,8 @@
#include <log/logprint.h>
#include "frameworks/base/cmds/statsd/src/statsd_config.pb.h"
+#include <unordered_map>
+
namespace android {
namespace os {
namespace statsd {
@@ -33,6 +35,10 @@
typedef std::map<std::string, HashableDimensionKey> ConditionKey;
+// TODO: For P, change int to int64_t.
+// TODO: Should HashableDimensionKey be marked here as const?
+typedef std::unordered_map<HashableDimensionKey, int> DimToValMap;
+
EventMetricData parse(log_msg msg);
int getTagId(log_msg msg);
@@ -41,5 +47,3 @@
} // namespace statsd
} // namespace os
} // namespace android
-
-#endif // STATS_UTIL_H
diff --git a/cmds/statsd/src/statsd_config.proto b/cmds/statsd/src/statsd_config.proto
index f3e6894..a07f76a 100644
--- a/cmds/statsd/src/statsd_config.proto
+++ b/cmds/statsd/src/statsd_config.proto
@@ -83,7 +83,13 @@
optional string stop_all = 4;
- repeated KeyMatcher dimension = 5;
+ enum InitialValue {
+ UNKNOWN = 0;
+ FALSE = 1;
+ }
+ optional InitialValue initial_value = 5 [default = UNKNOWN];
+
+ repeated KeyMatcher dimension = 6;
}
message Condition {
@@ -117,6 +123,8 @@
optional int32 refractory_period_secs = 4;
optional int64 trigger_if_sum_gt = 5;
+
+ optional int32 refractory_period_in_buckets = 6;
}
message EventMetric {
@@ -168,7 +176,6 @@
repeated Alert alerts = 7;
repeated EventConditionLink links = 8;
-
}
message GaugeMetric {
@@ -235,4 +242,6 @@
repeated LogEntryMatcher log_entry_matcher = 7;
repeated Condition condition = 8;
+
+ repeated Alert alerts = 9;
}
diff --git a/cmds/statsd/tests/UidMap_test.cpp b/cmds/statsd/tests/UidMap_test.cpp
index 671f6d4..c64719e 100644
--- a/cmds/statsd/tests/UidMap_test.cpp
+++ b/cmds/statsd/tests/UidMap_test.cpp
@@ -13,7 +13,10 @@
// limitations under the License.
#include "packages/UidMap.h"
+#include "StatsLogProcessor.h"
#include "config/ConfigKey.h"
+#include "logd/LogEvent.h"
+#include "statslog.h"
#include <gtest/gtest.h>
@@ -29,6 +32,31 @@
const string kApp1 = "app1.sharing.1";
const string kApp2 = "app2.sharing.1";
+TEST(UidMapTest, TestIsolatedUID) {
+ sp<UidMap> m = new UidMap();
+ StatsLogProcessor p(m, nullptr);
+ LogEvent addEvent(android::util::ISOLATED_UID_CHANGED, 1);
+ android_log_event_list* list = addEvent.GetAndroidLogEventList();
+ *list << 100; // parent UID
+ *list << 101; // isolated UID
+ *list << 1; // Indicates creation.
+ addEvent.init();
+
+ EXPECT_EQ(101, m->getParentUidOrSelf(101));
+
+ p.OnLogEvent(addEvent);
+ EXPECT_EQ(100, m->getParentUidOrSelf(101));
+
+ LogEvent removeEvent(android::util::ISOLATED_UID_CHANGED, 1);
+ list = removeEvent.GetAndroidLogEventList();
+ *list << 100; // parent UID
+ *list << 101; // isolated UID
+ *list << 0; // Indicates removal.
+ removeEvent.init();
+ p.OnLogEvent(removeEvent);
+ EXPECT_EQ(101, m->getParentUidOrSelf(101));
+}
+
TEST(UidMapTest, TestMatching) {
UidMap m;
vector<int32_t> uids;
diff --git a/cmds/statsd/tests/anomaly/AnomalyTracker_test.cpp b/cmds/statsd/tests/anomaly/AnomalyTracker_test.cpp
new file mode 100644
index 0000000..b8150d0
--- /dev/null
+++ b/cmds/statsd/tests/anomaly/AnomalyTracker_test.cpp
@@ -0,0 +1,239 @@
+// 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.
+
+#include "src/anomaly/DiscreteAnomalyTracker.h"
+
+#include <gtest/gtest.h>
+#include <stdio.h>
+#include <vector>
+
+using namespace testing;
+using android::sp;
+using std::set;
+using std::unordered_map;
+using std::vector;
+
+#ifdef __ANDROID__
+
+namespace android {
+namespace os {
+namespace statsd {
+
+void AddValueToBucket(const std::vector<std::pair<string, long>>& key_value_pair_list,
+ std::shared_ptr<DimToValMap> bucket) {
+ for (auto itr = key_value_pair_list.begin(); itr != key_value_pair_list.end(); itr++) {
+ (*bucket)[itr->first] += itr->second;
+ }
+}
+
+std::shared_ptr<DimToValMap> MockeBucket(
+ const std::vector<std::pair<string, long>>& key_value_pair_list) {
+ std::shared_ptr<DimToValMap> bucket = std::make_shared<DimToValMap>();
+ AddValueToBucket(key_value_pair_list, bucket);
+ return bucket;
+}
+
+TEST(AnomalyTrackerTest, TestConsecutiveBuckets) {
+ Alert alert;
+ alert.set_number_of_buckets(3);
+ alert.set_refractory_period_in_buckets(3);
+ alert.set_trigger_if_sum_gt(2);
+
+ DiscreteAnomalyTracker anomaly_tracker(alert);
+
+ std::shared_ptr<DimToValMap> bucket0 = MockeBucket({{"a", 1}, {"b", 2}, {"c", 1}});
+ // Adds bucket #0
+ anomaly_tracker.addOrUpdateBucket(bucket0, 0);
+ EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.size(), 3UL);
+ EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.find("a")->second, 1);
+ EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.find("b")->second, 2);
+ EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.find("c")->second, 1);
+ EXPECT_FALSE(anomaly_tracker.detectAnomaly());
+ anomaly_tracker.declareAndDeclareAnomaly();
+ EXPECT_EQ(anomaly_tracker.mAnomalyDeclared, 0L);
+
+ // Adds bucket #0 again. The sum does not change.
+ anomaly_tracker.addOrUpdateBucket(bucket0, 0);
+ EXPECT_EQ(anomaly_tracker.mCurrentBucketIndex, 0L);
+ EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.size(), 3UL);
+ EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.find("a")->second, 1);
+ EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.find("b")->second, 2);
+ EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.find("c")->second, 1);
+ EXPECT_FALSE(anomaly_tracker.detectAnomaly());
+ anomaly_tracker.declareAndDeclareAnomaly();
+ EXPECT_EQ(anomaly_tracker.mAnomalyDeclared, 0L);
+ EXPECT_EQ(anomaly_tracker.mLastAlarmAtBucketIndex, -1L);
+
+ // Adds bucket #1.
+ std::shared_ptr<DimToValMap> bucket1 = MockeBucket({{"b", 2}});
+ anomaly_tracker.addOrUpdateBucket(bucket1, 1);
+ EXPECT_EQ(anomaly_tracker.mCurrentBucketIndex, 1L);
+ EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.size(), 3UL);
+ EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.find("a")->second, 1);
+ EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.find("b")->second, 4);
+ EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.find("c")->second, 1);
+ // Alarm.
+ EXPECT_TRUE(anomaly_tracker.detectAnomaly());
+ anomaly_tracker.declareAndDeclareAnomaly();
+ EXPECT_EQ(anomaly_tracker.mAnomalyDeclared, 1L);
+ EXPECT_EQ(anomaly_tracker.mLastAlarmAtBucketIndex, 1L);
+
+ // Adds bucket #1 again. The sum does not change.
+ anomaly_tracker.addOrUpdateBucket(bucket1, 1);
+ EXPECT_EQ(anomaly_tracker.mCurrentBucketIndex, 1L);
+ EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.size(), 3UL);
+ EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.find("a")->second, 1);
+ EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.find("b")->second, 4);
+ EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.find("c")->second, 1);
+ // Alarm.
+ EXPECT_TRUE(anomaly_tracker.detectAnomaly());
+ anomaly_tracker.declareAndDeclareAnomaly();
+ EXPECT_EQ(anomaly_tracker.mAnomalyDeclared, 1L);
+ EXPECT_EQ(anomaly_tracker.mLastAlarmAtBucketIndex, 1L);
+
+ // Adds bucket #2.
+ anomaly_tracker.addOrUpdateBucket(MockeBucket({{"a", 1}}), 2);
+ EXPECT_EQ(anomaly_tracker.mCurrentBucketIndex, 2L);
+ EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.size(), 3UL);
+ EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.find("a")->second, 2);
+ EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.find("b")->second, 4);
+ EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.find("c")->second, 1);
+ EXPECT_TRUE(anomaly_tracker.detectAnomaly());
+ // Within refractory period.
+ anomaly_tracker.declareAndDeclareAnomaly();
+ EXPECT_EQ(anomaly_tracker.mAnomalyDeclared, 1L);
+ EXPECT_EQ(anomaly_tracker.mLastAlarmAtBucketIndex, 1L);
+
+ // Adds bucket #3.
+ anomaly_tracker.addOrUpdateBucket(MockeBucket({{"a", 1}}), 3);
+ EXPECT_EQ(anomaly_tracker.mCurrentBucketIndex, 3L);
+ EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.size(), 2UL);
+ EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.find("a")->second, 2);
+ EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.find("b")->second, 2);
+ EXPECT_FALSE(anomaly_tracker.detectAnomaly());
+
+ // Adds bucket #3.
+ anomaly_tracker.addOrUpdateBucket(MockeBucket({{"a", 2}}), 4);
+ EXPECT_EQ(anomaly_tracker.mCurrentBucketIndex, 4L);
+ EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.size(), 1UL);
+ EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.find("a")->second, 4);
+ EXPECT_TRUE(anomaly_tracker.detectAnomaly());
+ // Within refractory period.
+ anomaly_tracker.declareAndDeclareAnomaly();
+ EXPECT_EQ(anomaly_tracker.mAnomalyDeclared, 1L);
+ EXPECT_EQ(anomaly_tracker.mLastAlarmAtBucketIndex, 1L);
+
+ anomaly_tracker.addOrUpdateBucket(MockeBucket({{"a", 1}}), 5);
+ EXPECT_EQ(anomaly_tracker.mCurrentBucketIndex, 5L);
+ EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.size(), 1UL);
+ EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.find("a")->second, 4);
+ EXPECT_TRUE(anomaly_tracker.detectAnomaly());
+ // Within refractory period.
+ anomaly_tracker.declareAndDeclareAnomaly();
+ EXPECT_EQ(anomaly_tracker.mAnomalyDeclared, 2L);
+ EXPECT_EQ(anomaly_tracker.mLastAlarmAtBucketIndex, 5L);
+}
+
+TEST(AnomalyTrackerTest, TestSparseBuckets) {
+ Alert alert;
+ alert.set_number_of_buckets(3);
+ alert.set_refractory_period_in_buckets(3);
+ alert.set_trigger_if_sum_gt(2);
+
+ DiscreteAnomalyTracker anomaly_tracker(alert);
+
+ // Add bucket #9
+ anomaly_tracker.addOrUpdateBucket(MockeBucket({{"a", 1}, {"b", 2}, {"c", 1}}), 9);
+ EXPECT_EQ(anomaly_tracker.mCurrentBucketIndex, 9L);
+ EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.size(), 3UL);
+ EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.find("a")->second, 1);
+ EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.find("b")->second, 2);
+ EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.find("c")->second, 1);
+ EXPECT_FALSE(anomaly_tracker.detectAnomaly());
+ anomaly_tracker.declareAndDeclareAnomaly();
+ EXPECT_EQ(anomaly_tracker.mAnomalyDeclared, 0L);
+ EXPECT_EQ(anomaly_tracker.mLastAlarmAtBucketIndex, -1L);
+
+ // Add bucket #16
+ anomaly_tracker.addOrUpdateBucket(MockeBucket({{"b", 4}}), 16);
+ EXPECT_EQ(anomaly_tracker.mCurrentBucketIndex, 16L);
+ EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.size(), 1UL);
+ EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.find("b")->second, 4);
+ EXPECT_TRUE(anomaly_tracker.detectAnomaly());
+ anomaly_tracker.declareAndDeclareAnomaly();
+ EXPECT_EQ(anomaly_tracker.mAnomalyDeclared, 1L);
+ EXPECT_EQ(anomaly_tracker.mLastAlarmAtBucketIndex, 16L);
+
+ // Add bucket #18
+ anomaly_tracker.addOrUpdateBucket(MockeBucket({{"b", 1}, {"c", 1}}), 18);
+ EXPECT_EQ(anomaly_tracker.mCurrentBucketIndex, 18L);
+ EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.size(), 2UL);
+ EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.find("b")->second, 5);
+ EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.find("c")->second, 1);
+ EXPECT_TRUE(anomaly_tracker.detectAnomaly());
+ anomaly_tracker.declareAndDeclareAnomaly();
+ // Within refractory period.
+ EXPECT_EQ(anomaly_tracker.mAnomalyDeclared, 1L);
+ EXPECT_EQ(anomaly_tracker.mLastAlarmAtBucketIndex, 16L);
+
+ // Add bucket #18 again.
+ anomaly_tracker.addOrUpdateBucket(MockeBucket({{"b", 1}, {"c", 1}}), 18);
+ EXPECT_EQ(anomaly_tracker.mCurrentBucketIndex, 18L);
+ EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.size(), 2UL);
+ EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.find("b")->second, 5);
+ EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.find("c")->second, 1);
+ EXPECT_TRUE(anomaly_tracker.detectAnomaly());
+ anomaly_tracker.declareAndDeclareAnomaly();
+ EXPECT_EQ(anomaly_tracker.mAnomalyDeclared, 1L);
+ EXPECT_EQ(anomaly_tracker.mLastAlarmAtBucketIndex, 16L);
+
+ // Add bucket #20
+ anomaly_tracker.addOrUpdateBucket(MockeBucket({{"b", 3}, {"d", 1}}), 20);
+ EXPECT_EQ(anomaly_tracker.mCurrentBucketIndex, 20L);
+ EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.size(), 3UL);
+ EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.find("b")->second, 4);
+ EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.find("c")->second, 1);
+ EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.find("d")->second, 1);
+ EXPECT_TRUE(anomaly_tracker.detectAnomaly());
+ anomaly_tracker.declareAndDeclareAnomaly();
+ EXPECT_EQ(anomaly_tracker.mAnomalyDeclared, 2L);
+ EXPECT_EQ(anomaly_tracker.mLastAlarmAtBucketIndex, 20L);
+
+ // Add bucket #25
+ anomaly_tracker.addOrUpdateBucket(MockeBucket({{"d", 1}}), 25);
+ EXPECT_EQ(anomaly_tracker.mCurrentBucketIndex, 25L);
+ EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.size(), 1UL);
+ EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.find("d")->second, 1L);
+ EXPECT_FALSE(anomaly_tracker.detectAnomaly());
+ anomaly_tracker.declareAndDeclareAnomaly();
+ EXPECT_EQ(anomaly_tracker.mAnomalyDeclared, 2L);
+ EXPECT_EQ(anomaly_tracker.mLastAlarmAtBucketIndex, 20L);
+
+ // Add bucket #28
+ anomaly_tracker.addOrUpdateBucket(MockeBucket({{"e", 5}}), 28);
+ EXPECT_EQ(anomaly_tracker.mCurrentBucketIndex, 28L);
+ EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.size(), 1UL);
+ EXPECT_EQ(anomaly_tracker.mSumOverPastBuckets.find("e")->second, 5L);
+ EXPECT_TRUE(anomaly_tracker.detectAnomaly());
+ anomaly_tracker.declareAndDeclareAnomaly();
+ EXPECT_EQ(anomaly_tracker.mAnomalyDeclared, 3L);
+ EXPECT_EQ(anomaly_tracker.mLastAlarmAtBucketIndex, 28L);
+}
+
+} // namespace statsd
+} // namespace os
+} // namespace android
+#else
+GTEST_LOG_(INFO) << "This test does nothing.\n";
+#endif
diff --git a/cmds/statsd/tests/ConditionTracker_test.cpp b/cmds/statsd/tests/condition/CombinationConditionTracker_test.cpp
similarity index 98%
rename from cmds/statsd/tests/ConditionTracker_test.cpp
rename to cmds/statsd/tests/condition/CombinationConditionTracker_test.cpp
index 2935ac71..23d6926 100644
--- a/cmds/statsd/tests/ConditionTracker_test.cpp
+++ b/cmds/statsd/tests/condition/CombinationConditionTracker_test.cpp
@@ -23,7 +23,6 @@
using namespace android::os::statsd;
using std::vector;
-
#ifdef __ANDROID__
TEST(ConditionTrackerTest, TestUnknownCondition) {
LogicalOperation operation = LogicalOperation::AND;
@@ -39,7 +38,7 @@
conditionResults.push_back(ConditionState::kTrue);
EXPECT_EQ(evaluateCombinationCondition(children, operation, conditionResults),
- ConditionState::kUnknown);
+ ConditionState::kUnknown);
}
TEST(ConditionTrackerTest, TestAndCondition) {
// Set up the matcher
diff --git a/cmds/statsd/tests/condition/SimpleConditionTracker_test.cpp b/cmds/statsd/tests/condition/SimpleConditionTracker_test.cpp
new file mode 100644
index 0000000..05aad29
--- /dev/null
+++ b/cmds/statsd/tests/condition/SimpleConditionTracker_test.cpp
@@ -0,0 +1,469 @@
+// 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.
+#include "src/condition/SimpleConditionTracker.h"
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <stdio.h>
+#include <vector>
+
+using std::map;
+using std::unordered_map;
+using std::vector;
+
+#ifdef __ANDROID__
+
+namespace android {
+namespace os {
+namespace statsd {
+
+SimpleCondition getWakeLockHeldCondition(bool countNesting, bool defaultFalse,
+ bool outputSlicedUid) {
+ SimpleCondition simpleCondition;
+ simpleCondition.set_start("WAKE_LOCK_ACQUIRE");
+ simpleCondition.set_stop("WAKE_LOCK_RELEASE");
+ simpleCondition.set_stop_all("RELEASE_ALL");
+ if (outputSlicedUid) {
+ KeyMatcher* keyMatcher = simpleCondition.add_dimension();
+ keyMatcher->set_key(1);
+ }
+
+ simpleCondition.set_count_nesting(countNesting);
+ simpleCondition.set_initial_value(defaultFalse ? SimpleCondition_InitialValue_FALSE
+ : SimpleCondition_InitialValue_UNKNOWN);
+ return simpleCondition;
+}
+
+void makeWakeLockEvent(LogEvent* event, int uid, const string& wl, int acquire) {
+ auto list = event->GetAndroidLogEventList();
+ *list << uid; // uid
+ *list << wl;
+ *list << acquire;
+ event->init();
+}
+
+map<string, HashableDimensionKey> getWakeLockQueryKey(int key, int uid,
+ const string& conditionName) {
+ // test query
+ KeyValuePair kv1;
+ kv1.set_key(key);
+ kv1.set_value_int(uid);
+ vector<KeyValuePair> kv_list;
+ kv_list.push_back(kv1);
+ map<string, HashableDimensionKey> queryKey;
+ queryKey[conditionName] = getHashableKey(kv_list);
+ return queryKey;
+}
+
+TEST(SimpleConditionTrackerTest, TestNonSlicedCondition) {
+ SimpleCondition simpleCondition;
+ simpleCondition.set_start("SCREEN_TURNED_ON");
+ simpleCondition.set_stop("SCREEN_TURNED_OFF");
+ simpleCondition.set_count_nesting(false);
+
+ unordered_map<string, int> trackerNameIndexMap;
+ trackerNameIndexMap["SCREEN_TURNED_ON"] = 0;
+ trackerNameIndexMap["SCREEN_TURNED_OFF"] = 1;
+
+ SimpleConditionTracker conditionTracker("SCREEN_IS_ON", 0 /*tracker index*/, simpleCondition,
+ trackerNameIndexMap);
+
+ LogEvent event(1 /*tagId*/, 0 /*timestamp*/);
+
+ vector<MatchingState> matcherState;
+ matcherState.push_back(MatchingState::kNotMatched);
+ matcherState.push_back(MatchingState::kNotMatched);
+
+ vector<sp<ConditionTracker>> allConditions;
+ vector<ConditionState> conditionCache(1, ConditionState::kNotEvaluated);
+ vector<bool> changedCache(1, false);
+
+ conditionTracker.evaluateCondition(event, matcherState, allConditions, conditionCache,
+ changedCache);
+ // not matched start or stop. condition doesn't change
+ EXPECT_EQ(ConditionState::kUnknown, conditionCache[0]);
+ EXPECT_FALSE(changedCache[0]);
+
+ // prepare a case for match start.
+ matcherState.clear();
+ matcherState.push_back(MatchingState::kMatched);
+ matcherState.push_back(MatchingState::kNotMatched);
+ conditionCache[0] = ConditionState::kNotEvaluated;
+ changedCache[0] = false;
+
+ conditionTracker.evaluateCondition(event, matcherState, allConditions, conditionCache,
+ changedCache);
+ // now condition should change to true.
+ EXPECT_EQ(ConditionState::kTrue, conditionCache[0]);
+ EXPECT_TRUE(changedCache[0]);
+
+ // the case for match stop.
+ matcherState.clear();
+ matcherState.push_back(MatchingState::kNotMatched);
+ matcherState.push_back(MatchingState::kMatched);
+ conditionCache[0] = ConditionState::kNotEvaluated;
+ changedCache[0] = false;
+
+ conditionTracker.evaluateCondition(event, matcherState, allConditions, conditionCache,
+ changedCache);
+
+ // condition changes to false.
+ EXPECT_EQ(ConditionState::kFalse, conditionCache[0]);
+ EXPECT_TRUE(changedCache[0]);
+
+ // match stop again.
+ matcherState.clear();
+ matcherState.push_back(MatchingState::kNotMatched);
+ matcherState.push_back(MatchingState::kMatched);
+ conditionCache[0] = ConditionState::kNotEvaluated;
+ changedCache[0] = false;
+
+ conditionTracker.evaluateCondition(event, matcherState, allConditions, conditionCache,
+ changedCache);
+ // condition should still be false. not changed.
+ EXPECT_EQ(ConditionState::kFalse, conditionCache[0]);
+ EXPECT_FALSE(changedCache[0]);
+}
+
+TEST(SimpleConditionTrackerTest, TestNonSlicedConditionNestCounting) {
+ SimpleCondition simpleCondition;
+ simpleCondition.set_start("SCREEN_TURNED_ON");
+ simpleCondition.set_stop("SCREEN_TURNED_OFF");
+ simpleCondition.set_count_nesting(true);
+
+ unordered_map<string, int> trackerNameIndexMap;
+ trackerNameIndexMap["SCREEN_TURNED_ON"] = 0;
+ trackerNameIndexMap["SCREEN_TURNED_OFF"] = 1;
+
+ SimpleConditionTracker conditionTracker("SCREEN_IS_ON", 0 /*condition tracker index*/,
+ simpleCondition, trackerNameIndexMap);
+
+ LogEvent event(1 /*tagId*/, 0 /*timestamp*/);
+
+ // one matched start
+ vector<MatchingState> matcherState;
+ matcherState.push_back(MatchingState::kMatched);
+ matcherState.push_back(MatchingState::kNotMatched);
+ vector<sp<ConditionTracker>> allConditions;
+ vector<ConditionState> conditionCache(1, ConditionState::kNotEvaluated);
+ vector<bool> changedCache(1, false);
+
+ conditionTracker.evaluateCondition(event, matcherState, allConditions, conditionCache,
+ changedCache);
+
+ EXPECT_EQ(ConditionState::kTrue, conditionCache[0]);
+ EXPECT_TRUE(changedCache[0]);
+
+ // prepare for another matched start.
+ matcherState.clear();
+ matcherState.push_back(MatchingState::kMatched);
+ matcherState.push_back(MatchingState::kNotMatched);
+ conditionCache[0] = ConditionState::kNotEvaluated;
+ changedCache[0] = false;
+
+ conditionTracker.evaluateCondition(event, matcherState, allConditions, conditionCache,
+ changedCache);
+
+ EXPECT_EQ(ConditionState::kTrue, conditionCache[0]);
+ EXPECT_FALSE(changedCache[0]);
+
+ // ONE MATCHED STOP
+ matcherState.clear();
+ matcherState.push_back(MatchingState::kNotMatched);
+ matcherState.push_back(MatchingState::kMatched);
+ conditionCache[0] = ConditionState::kNotEvaluated;
+ changedCache[0] = false;
+
+ conditionTracker.evaluateCondition(event, matcherState, allConditions, conditionCache,
+ changedCache);
+ // result should still be true
+ EXPECT_EQ(ConditionState::kTrue, conditionCache[0]);
+ EXPECT_FALSE(changedCache[0]);
+
+ // ANOTHER MATCHED STOP
+ matcherState.clear();
+ matcherState.push_back(MatchingState::kNotMatched);
+ matcherState.push_back(MatchingState::kMatched);
+ conditionCache[0] = ConditionState::kNotEvaluated;
+ changedCache[0] = false;
+
+ conditionTracker.evaluateCondition(event, matcherState, allConditions, conditionCache,
+ changedCache);
+ // result should still be true
+ EXPECT_EQ(ConditionState::kFalse, conditionCache[0]);
+ EXPECT_TRUE(changedCache[0]);
+}
+
+TEST(SimpleConditionTrackerTest, TestSlicedCondition) {
+ SimpleCondition simpleCondition = getWakeLockHeldCondition(
+ true /*nesting*/, true /*default to false*/, true /*output slice by uid*/);
+ string conditionName = "WL_HELD_BY_UID2";
+
+ unordered_map<string, int> trackerNameIndexMap;
+ trackerNameIndexMap["WAKE_LOCK_ACQUIRE"] = 0;
+ trackerNameIndexMap["WAKE_LOCK_RELEASE"] = 1;
+ trackerNameIndexMap["RELEASE_ALL"] = 2;
+
+ SimpleConditionTracker conditionTracker(conditionName, 0 /*condition tracker index*/,
+ simpleCondition, trackerNameIndexMap);
+ int uid = 111;
+
+ LogEvent event(1 /*tagId*/, 0 /*timestamp*/);
+ makeWakeLockEvent(&event, uid, "wl1", 1);
+
+ // one matched start
+ vector<MatchingState> matcherState;
+ matcherState.push_back(MatchingState::kMatched);
+ matcherState.push_back(MatchingState::kNotMatched);
+ vector<sp<ConditionTracker>> allConditions;
+ vector<ConditionState> conditionCache(1, ConditionState::kNotEvaluated);
+ vector<bool> changedCache(1, false);
+
+ conditionTracker.evaluateCondition(event, matcherState, allConditions, conditionCache,
+ changedCache);
+
+ EXPECT_EQ(1UL, conditionTracker.mSlicedConditionState.size());
+ EXPECT_TRUE(changedCache[0]);
+
+ // Now test query
+ const auto queryKey = getWakeLockQueryKey(1, uid, conditionName);
+ conditionCache[0] = ConditionState::kNotEvaluated;
+
+ conditionTracker.isConditionMet(queryKey, allConditions, conditionCache);
+ EXPECT_EQ(ConditionState::kTrue, conditionCache[0]);
+
+ // another wake lock acquired by this uid
+ LogEvent event2(1 /*tagId*/, 0 /*timestamp*/);
+ makeWakeLockEvent(&event2, uid, "wl2", 1);
+ matcherState.clear();
+ matcherState.push_back(MatchingState::kMatched);
+ matcherState.push_back(MatchingState::kNotMatched);
+ conditionCache[0] = ConditionState::kNotEvaluated;
+ changedCache[0] = false;
+ conditionTracker.evaluateCondition(event2, matcherState, allConditions, conditionCache,
+ changedCache);
+ EXPECT_FALSE(changedCache[0]);
+
+ // wake lock 1 release
+ LogEvent event3(1 /*tagId*/, 0 /*timestamp*/);
+ makeWakeLockEvent(&event3, uid, "wl1", 0); // now release it.
+ matcherState.clear();
+ matcherState.push_back(MatchingState::kNotMatched);
+ matcherState.push_back(MatchingState::kMatched);
+ conditionCache[0] = ConditionState::kNotEvaluated;
+ changedCache[0] = false;
+ conditionTracker.evaluateCondition(event3, matcherState, allConditions, conditionCache,
+ changedCache);
+ // nothing changes, because wake lock 2 is still held for this uid
+ EXPECT_FALSE(changedCache[0]);
+
+ LogEvent event4(1 /*tagId*/, 0 /*timestamp*/);
+ makeWakeLockEvent(&event4, uid, "wl2", 0); // now release it.
+ matcherState.clear();
+ matcherState.push_back(MatchingState::kNotMatched);
+ matcherState.push_back(MatchingState::kMatched);
+ conditionCache[0] = ConditionState::kNotEvaluated;
+ changedCache[0] = false;
+ conditionTracker.evaluateCondition(event4, matcherState, allConditions, conditionCache,
+ changedCache);
+ EXPECT_EQ(0UL, conditionTracker.mSlicedConditionState.size());
+ EXPECT_TRUE(changedCache[0]);
+
+ // query again
+ conditionCache[0] = ConditionState::kNotEvaluated;
+ conditionTracker.isConditionMet(queryKey, allConditions, conditionCache);
+ EXPECT_EQ(ConditionState::kFalse, conditionCache[0]);
+}
+
+TEST(SimpleConditionTrackerTest, TestSlicedWithNoOutputDim) {
+ SimpleCondition simpleCondition = getWakeLockHeldCondition(
+ true /*nesting*/, true /*default to false*/, false /*slice output by uid*/);
+ string conditionName = "WL_HELD";
+
+ unordered_map<string, int> trackerNameIndexMap;
+ trackerNameIndexMap["WAKE_LOCK_ACQUIRE"] = 0;
+ trackerNameIndexMap["WAKE_LOCK_RELEASE"] = 1;
+ trackerNameIndexMap["RELEASE_ALL"] = 2;
+
+ SimpleConditionTracker conditionTracker(conditionName, 0 /*condition tracker index*/,
+ simpleCondition, trackerNameIndexMap);
+ int uid1 = 111;
+ string uid1_wl1 = "wl1_1";
+ int uid2 = 222;
+ string uid2_wl1 = "wl2_1";
+
+ LogEvent event(1 /*tagId*/, 0 /*timestamp*/);
+ makeWakeLockEvent(&event, uid1, uid1_wl1, 1);
+
+ // one matched start for uid1
+ vector<MatchingState> matcherState;
+ matcherState.push_back(MatchingState::kMatched);
+ matcherState.push_back(MatchingState::kNotMatched);
+ vector<sp<ConditionTracker>> allConditions;
+ vector<ConditionState> conditionCache(1, ConditionState::kNotEvaluated);
+ vector<bool> changedCache(1, false);
+
+ conditionTracker.evaluateCondition(event, matcherState, allConditions, conditionCache,
+ changedCache);
+
+ EXPECT_EQ(1UL, conditionTracker.mSlicedConditionState.size());
+ EXPECT_TRUE(changedCache[0]);
+
+ // Now test query
+ map<string, HashableDimensionKey> queryKey;
+ conditionCache[0] = ConditionState::kNotEvaluated;
+
+ conditionTracker.isConditionMet(queryKey, allConditions, conditionCache);
+ EXPECT_EQ(ConditionState::kTrue, conditionCache[0]);
+
+ // another wake lock acquired by this uid
+ LogEvent event2(1 /*tagId*/, 0 /*timestamp*/);
+ makeWakeLockEvent(&event2, uid2, uid2_wl1, 1);
+ matcherState.clear();
+ matcherState.push_back(MatchingState::kMatched);
+ matcherState.push_back(MatchingState::kNotMatched);
+ conditionCache[0] = ConditionState::kNotEvaluated;
+ changedCache[0] = false;
+ conditionTracker.evaluateCondition(event2, matcherState, allConditions, conditionCache,
+ changedCache);
+ EXPECT_FALSE(changedCache[0]);
+
+ // uid1 wake lock 1 release
+ LogEvent event3(1 /*tagId*/, 0 /*timestamp*/);
+ makeWakeLockEvent(&event3, uid1, uid1_wl1, 0); // now release it.
+ matcherState.clear();
+ matcherState.push_back(MatchingState::kNotMatched);
+ matcherState.push_back(MatchingState::kMatched);
+ conditionCache[0] = ConditionState::kNotEvaluated;
+ changedCache[0] = false;
+ conditionTracker.evaluateCondition(event3, matcherState, allConditions, conditionCache,
+ changedCache);
+ // nothing changes, because uid2 is still holding wl.
+ EXPECT_FALSE(changedCache[0]);
+
+ LogEvent event4(1 /*tagId*/, 0 /*timestamp*/);
+ makeWakeLockEvent(&event4, uid2, uid2_wl1, 0); // now release it.
+ matcherState.clear();
+ matcherState.push_back(MatchingState::kNotMatched);
+ matcherState.push_back(MatchingState::kMatched);
+ conditionCache[0] = ConditionState::kNotEvaluated;
+ changedCache[0] = false;
+ conditionTracker.evaluateCondition(event4, matcherState, allConditions, conditionCache,
+ changedCache);
+ EXPECT_EQ(0UL, conditionTracker.mSlicedConditionState.size());
+ EXPECT_TRUE(changedCache[0]);
+
+ // query again
+ conditionCache[0] = ConditionState::kNotEvaluated;
+ conditionTracker.isConditionMet(queryKey, allConditions, conditionCache);
+ EXPECT_EQ(ConditionState::kFalse, conditionCache[0]);
+}
+
+TEST(SimpleConditionTrackerTest, TestStopAll) {
+ SimpleCondition simpleCondition = getWakeLockHeldCondition(
+ true /*nesting*/, true /*default to false*/, true /*output slice by uid*/);
+ string conditionName = "WL_HELD_BY_UID3";
+
+ unordered_map<string, int> trackerNameIndexMap;
+ trackerNameIndexMap["WAKE_LOCK_ACQUIRE"] = 0;
+ trackerNameIndexMap["WAKE_LOCK_RELEASE"] = 1;
+ trackerNameIndexMap["RELEASE_ALL"] = 2;
+
+ SimpleConditionTracker conditionTracker(conditionName, 0 /*condition tracker index*/,
+ simpleCondition, trackerNameIndexMap);
+ int uid1 = 111;
+ int uid2 = 222;
+
+ LogEvent event(1 /*tagId*/, 0 /*timestamp*/);
+ makeWakeLockEvent(&event, uid1, "wl1", 1);
+
+ // one matched start
+ vector<MatchingState> matcherState;
+ matcherState.push_back(MatchingState::kMatched);
+ matcherState.push_back(MatchingState::kNotMatched);
+ matcherState.push_back(MatchingState::kNotMatched);
+ vector<sp<ConditionTracker>> allConditions;
+ vector<ConditionState> conditionCache(1, ConditionState::kNotEvaluated);
+ vector<bool> changedCache(1, false);
+
+ conditionTracker.evaluateCondition(event, matcherState, allConditions, conditionCache,
+ changedCache);
+
+ EXPECT_EQ(1UL, conditionTracker.mSlicedConditionState.size());
+ EXPECT_TRUE(changedCache[0]);
+
+ // Now test query
+ const auto queryKey = getWakeLockQueryKey(1, uid1, conditionName);
+ conditionCache[0] = ConditionState::kNotEvaluated;
+
+ conditionTracker.isConditionMet(queryKey, allConditions, conditionCache);
+ EXPECT_EQ(ConditionState::kTrue, conditionCache[0]);
+
+ // another wake lock acquired by uid2
+ LogEvent event2(1 /*tagId*/, 0 /*timestamp*/);
+ makeWakeLockEvent(&event2, uid2, "wl2", 1);
+ matcherState.clear();
+ matcherState.push_back(MatchingState::kMatched);
+ matcherState.push_back(MatchingState::kNotMatched);
+ matcherState.push_back(MatchingState::kNotMatched);
+ conditionCache[0] = ConditionState::kNotEvaluated;
+ changedCache[0] = false;
+ conditionTracker.evaluateCondition(event2, matcherState, allConditions, conditionCache,
+ changedCache);
+ EXPECT_EQ(2UL, conditionTracker.mSlicedConditionState.size());
+ EXPECT_TRUE(changedCache[0]);
+
+ // TEST QUERY
+ const auto queryKey2 = getWakeLockQueryKey(1, uid2, conditionName);
+ conditionCache[0] = ConditionState::kNotEvaluated;
+
+ conditionTracker.isConditionMet(queryKey, allConditions, conditionCache);
+ EXPECT_EQ(ConditionState::kTrue, conditionCache[0]);
+
+
+ // stop all event
+ LogEvent event3(2 /*tagId*/, 0 /*timestamp*/);
+ matcherState.clear();
+ matcherState.push_back(MatchingState::kNotMatched);
+ matcherState.push_back(MatchingState::kNotMatched);
+ matcherState.push_back(MatchingState::kMatched);
+
+ conditionCache[0] = ConditionState::kNotEvaluated;
+ changedCache[0] = false;
+ conditionTracker.evaluateCondition(event3, matcherState, allConditions, conditionCache,
+ changedCache);
+ EXPECT_TRUE(changedCache[0]);
+ EXPECT_EQ(0UL, conditionTracker.mSlicedConditionState.size());
+
+ // TEST QUERY
+ const auto queryKey3 = getWakeLockQueryKey(1, uid1, conditionName);
+ conditionCache[0] = ConditionState::kNotEvaluated;
+
+ conditionTracker.isConditionMet(queryKey, allConditions, conditionCache);
+ EXPECT_EQ(ConditionState::kFalse, conditionCache[0]);
+
+ // TEST QUERY
+ const auto queryKey4 = getWakeLockQueryKey(1, uid2, conditionName);
+ conditionCache[0] = ConditionState::kNotEvaluated;
+
+ conditionTracker.isConditionMet(queryKey, allConditions, conditionCache);
+ EXPECT_EQ(ConditionState::kFalse, conditionCache[0]);
+}
+
+} // namespace statsd
+} // namespace os
+} // namespace android
+#else
+GTEST_LOG_(INFO) << "This test does nothing.\n";
+#endif
diff --git a/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp b/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp
new file mode 100644
index 0000000..2a26388
--- /dev/null
+++ b/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp
@@ -0,0 +1,299 @@
+// 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.
+
+#include "metrics_test_helper.h"
+#include "src/metrics/ValueMetricProducer.h"
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <stdio.h>
+#include <vector>
+
+using namespace testing;
+using android::sp;
+using std::set;
+using std::unordered_map;
+using std::vector;
+using std::shared_ptr;
+using std::make_shared;
+
+#ifdef __ANDROID__
+
+namespace android {
+namespace os {
+namespace statsd {
+
+/*
+ * Tests pulled atoms with no conditions
+ */
+TEST(ValueMetricProducerTest, TestNonDimensionalEvents) {
+ int64_t bucketStartTimeNs = 10000000000;
+ int64_t bucketSizeNs = 60 * 1000 * 1000 * 1000LL;
+
+ int64_t bucket2StartTimeNs = bucketStartTimeNs + bucketSizeNs;
+ int64_t bucket3StartTimeNs = bucketStartTimeNs + 2*bucketSizeNs;
+
+ ValueMetric metric;
+ metric.set_metric_id(1);
+ metric.mutable_bucket()->set_bucket_size_millis(bucketSizeNs / 1000000);
+ metric.set_value_field(2);
+
+ int tagId = 1;
+
+ sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
+ // TODO: pending refactor of StatsPullerManager
+ // For now we still need this so that it doesn't do real pulling.
+ shared_ptr<MockStatsPullerManager> pullerManager = make_shared<StrictMock<MockStatsPullerManager>>();
+ EXPECT_CALL(*pullerManager, RegisterReceiver(tagId, _, _)).WillOnce(Return());
+ EXPECT_CALL(*pullerManager, UnRegisterReceiver(tagId, _)).WillOnce(Return());
+
+ ValueMetricProducer valueProducer(metric, -1 /*-1 meaning no condition*/, wizard,tagId,
+ bucketStartTimeNs, pullerManager);
+
+ vector<shared_ptr<LogEvent>> allData;
+ allData.clear();
+ shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucketStartTimeNs + 1);
+ auto list = event->GetAndroidLogEventList();
+ *list << 1;
+ *list << 11;
+ event->init();
+ allData.push_back(event);
+
+ valueProducer.onDataPulled(allData);
+ // has one slice
+ EXPECT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size());
+ ValueMetricProducer::Interval curInterval = valueProducer.mCurrentSlicedBucket.begin()->second;
+ // has one raw pair
+ EXPECT_EQ(1UL, curInterval.raw.size());
+ // value is 11, 11
+ EXPECT_EQ(11, curInterval.raw.front().first);
+ EXPECT_EQ(11, curInterval.raw.front().second);
+ ValueMetricProducer::Interval nextInterval = valueProducer.mNextSlicedBucket.begin()->second;
+ // has one raw pair
+ EXPECT_EQ(1UL, nextInterval.raw.size());
+ // value is 11, 0
+ EXPECT_EQ(11, nextInterval.raw.front().first);
+ EXPECT_EQ(0, nextInterval.raw.front().second);
+ EXPECT_EQ(0UL, valueProducer.mPastBuckets.size());
+
+ allData.clear();
+ event = make_shared<LogEvent>(tagId, bucket2StartTimeNs + 1);
+ list = event->GetAndroidLogEventList();
+ *list << 1;
+ *list << 22;
+ event->init();
+ allData.push_back(event);
+ valueProducer.onDataPulled(allData);
+ // has one slice
+ EXPECT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size());
+ curInterval = valueProducer.mCurrentSlicedBucket.begin()->second;
+ // has one raw pair
+ EXPECT_EQ(1UL, curInterval.raw.size());
+ // value is 22, 0
+ EXPECT_EQ(22, curInterval.raw.front().first);
+ EXPECT_EQ(0, curInterval.raw.front().second);
+ EXPECT_EQ(0UL, valueProducer.mNextSlicedBucket.size());
+ EXPECT_EQ(1UL, valueProducer.mPastBuckets.size());
+ EXPECT_EQ(1UL, valueProducer.mPastBuckets.begin()->second.size());
+ EXPECT_EQ(11, valueProducer.mPastBuckets.begin()->second.back().mValue);
+
+ allData.clear();
+ event = make_shared<LogEvent>(tagId, bucket3StartTimeNs + 1);
+ list = event->GetAndroidLogEventList();
+ *list << 1;
+ *list << 33;
+ event->init();
+ allData.push_back(event);
+ valueProducer.onDataPulled(allData);
+ EXPECT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size());
+ curInterval = valueProducer.mCurrentSlicedBucket.begin()->second;
+ EXPECT_EQ(1UL, curInterval.raw.size());
+ // value is 33, 0
+ EXPECT_EQ(33, curInterval.raw.front().first);
+ EXPECT_EQ(0, curInterval.raw.front().second);
+ EXPECT_EQ(0UL, valueProducer.mNextSlicedBucket.size());
+ EXPECT_EQ(1UL, valueProducer.mPastBuckets.size());
+ EXPECT_EQ(2UL, valueProducer.mPastBuckets.begin()->second.size());
+ EXPECT_EQ(11, valueProducer.mPastBuckets.begin()->second.back().mValue);
+}
+
+/*
+ * Test pulled event with non sliced condition.
+ */
+TEST(ValueMetricProducerTest, TestEventsWithNonSlicedCondition) {
+ int64_t bucketStartTimeNs = 10000000000;
+ int64_t bucketSizeNs = 60 * 1000 * 1000 * 1000LL;
+
+ int64_t bucket2StartTimeNs = bucketStartTimeNs + bucketSizeNs;
+ int64_t bucket3StartTimeNs = bucketStartTimeNs + 2 * bucketSizeNs;
+
+ ValueMetric metric;
+ metric.set_metric_id(1);
+ metric.mutable_bucket()->set_bucket_size_millis(bucketSizeNs / 1000000);
+ metric.set_value_field(2);
+ metric.set_condition("SCREEN_ON");
+
+ int tagId = 1;
+
+ sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
+ shared_ptr<MockStatsPullerManager> pullerManager = make_shared<StrictMock<MockStatsPullerManager>>();
+ EXPECT_CALL(*pullerManager, RegisterReceiver(tagId, _, _)).WillOnce(Return());
+ EXPECT_CALL(*pullerManager, UnRegisterReceiver(tagId, _)).WillRepeatedly(Return());
+
+ EXPECT_CALL(*pullerManager, Pull(tagId, _)).WillOnce(Invoke([] (int tagId, vector<std::shared_ptr<LogEvent>>* data) {
+ int64_t bucketStartTimeNs = 10000000000;
+ int64_t bucketSizeNs = 60 * 1000 * 1000 * 1000LL;
+
+ int64_t bucket2StartTimeNs = bucketStartTimeNs + bucketSizeNs;
+ int64_t bucket3StartTimeNs = bucketStartTimeNs + 2 * bucketSizeNs;
+ data->clear();
+ shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucketStartTimeNs + 10);
+ auto list = event->GetAndroidLogEventList();
+ *list << 1;
+ *list << 100;
+ event->init();
+ data->push_back(event);
+ return true;
+ }))
+ .WillOnce(Invoke([] (int tagId, vector<std::shared_ptr<LogEvent>>* data) {
+ int64_t bucketStartTimeNs = 10000000000;
+ int64_t bucketSizeNs = 60 * 1000 * 1000 * 1000LL;
+
+ int64_t bucket2StartTimeNs = bucketStartTimeNs + bucketSizeNs;
+ int64_t bucket3StartTimeNs = bucketStartTimeNs + 2 * bucketSizeNs;
+ data->clear();
+ shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucket2StartTimeNs + 10);
+ auto list = event->GetAndroidLogEventList();
+ *list << 1;
+ *list << 120;
+ event->init();
+ data->push_back(event);
+ return true;
+ }));
+
+ ValueMetricProducer valueProducer(metric, 1, wizard,tagId,
+ bucketStartTimeNs, pullerManager);
+
+ valueProducer.onConditionChanged(true, bucketStartTimeNs + 10);
+
+ // has one slice
+ EXPECT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size());
+ ValueMetricProducer::Interval curInterval = valueProducer.mCurrentSlicedBucket.begin()->second;
+ // has one raw pair
+ EXPECT_EQ(1UL, curInterval.raw.size());
+ // value is 100, 0
+ EXPECT_EQ(100, curInterval.raw.front().first);
+ EXPECT_EQ(0, curInterval.raw.front().second);
+ EXPECT_EQ(0UL, valueProducer.mNextSlicedBucket.size());
+ EXPECT_EQ(0UL, valueProducer.mPastBuckets.size());
+
+ vector<shared_ptr<LogEvent>> allData;
+ allData.clear();
+ shared_ptr<LogEvent> event = make_shared<LogEvent>(tagId, bucket2StartTimeNs + 1);
+ auto list = event->GetAndroidLogEventList();
+ *list << 1;
+ *list << 110;
+ event->init();
+ allData.push_back(event);
+ valueProducer.onDataPulled(allData);
+
+ // has one slice
+ EXPECT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size());
+ curInterval = valueProducer.mCurrentSlicedBucket.begin()->second;
+ // has one raw pair
+ EXPECT_EQ(1UL, curInterval.raw.size());
+ // value is 110, 0
+ EXPECT_EQ(110, curInterval.raw.front().first);
+ EXPECT_EQ(0, curInterval.raw.front().second);
+ EXPECT_EQ(1UL, valueProducer.mPastBuckets.size());
+ EXPECT_EQ(1UL, valueProducer.mPastBuckets.begin()->second.size());
+ EXPECT_EQ(10, valueProducer.mPastBuckets.begin()->second.back().mValue);
+
+ valueProducer.onConditionChanged(false, bucket2StartTimeNs + 1);
+
+ // has one slice
+ EXPECT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size());
+ curInterval = valueProducer.mCurrentSlicedBucket.begin()->second;
+ // has one raw pair
+ EXPECT_EQ(1UL, curInterval.raw.size());
+ // value is 110, 120
+ EXPECT_EQ(110, curInterval.raw.front().first);
+ EXPECT_EQ(120, curInterval.raw.front().second);
+}
+
+TEST(ValueMetricProducerTest, TestPushedEventsWithoutCondition) {
+ int64_t bucketStartTimeNs = 10000000000;
+ int64_t bucketSizeNs = 60 * 1000 * 1000 * 1000LL;
+
+ int64_t bucket2StartTimeNs = bucketStartTimeNs + bucketSizeNs;
+ int64_t bucket3StartTimeNs = bucketStartTimeNs + 2 * bucketSizeNs;
+
+ ValueMetric metric;
+ metric.set_metric_id(1);
+ metric.mutable_bucket()->set_bucket_size_millis(bucketSizeNs / 1000000);
+ metric.set_value_field(2);
+
+ int tagId = 1;
+
+ sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
+ shared_ptr<MockStatsPullerManager> pullerManager = make_shared<StrictMock<MockStatsPullerManager>>();
+
+ ValueMetricProducer valueProducer(metric, -1, wizard,-1,
+ bucketStartTimeNs, pullerManager);
+
+ shared_ptr<LogEvent> event1 = make_shared<LogEvent>(tagId, bucketStartTimeNs + 10);
+ auto list = event1->GetAndroidLogEventList();
+ *list << 1;
+ *list << 10;
+ event1->init();
+ shared_ptr<LogEvent> event2 = make_shared<LogEvent>(tagId, bucketStartTimeNs + 10);
+ auto list2 = event2->GetAndroidLogEventList();
+ *list2 << 1;
+ *list2 << 20;
+ event2->init();
+ valueProducer.onMatchedLogEvent(1 /*log matcher index*/, *event1, false);
+ // has one slice
+ EXPECT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size());
+ ValueMetricProducer::Interval curInterval = valueProducer.mCurrentSlicedBucket.begin()->second;
+ // has one raw pair
+ EXPECT_EQ(1UL, curInterval.raw.size());
+ // value is 10, 0
+ EXPECT_EQ(10, curInterval.raw.front().first);
+ EXPECT_EQ(0, curInterval.raw.front().second);
+ EXPECT_EQ(0UL, valueProducer.mNextSlicedBucket.size());
+
+ valueProducer.onMatchedLogEvent(1 /*log matcher index*/, *event2, false);
+
+ // has one slice
+ EXPECT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size());
+ curInterval = valueProducer.mCurrentSlicedBucket.begin()->second;
+ // has one raw pair
+ EXPECT_EQ(2UL, curInterval.raw.size());
+ // value is 10, 20
+ EXPECT_EQ(10, curInterval.raw.front().first);
+ EXPECT_EQ(20, curInterval.raw.back().first);
+ EXPECT_EQ(0UL, valueProducer.mNextSlicedBucket.size());
+
+ valueProducer.flush_if_needed(bucket3StartTimeNs);
+ EXPECT_EQ(1UL, valueProducer.mPastBuckets.size());
+ EXPECT_EQ(1UL, valueProducer.mPastBuckets.begin()->second.size());
+ EXPECT_EQ(30, valueProducer.mPastBuckets.begin()->second.back().mValue);
+}
+
+} // namespace statsd
+} // namespace os
+} // namespace android
+#else
+GTEST_LOG_(INFO) << "This test does nothing.\n";
+#endif
diff --git a/cmds/statsd/tests/metrics/metrics_test_helper.h b/cmds/statsd/tests/metrics/metrics_test_helper.h
index 5fd7d62..fa221aa 100644
--- a/cmds/statsd/tests/metrics/metrics_test_helper.h
+++ b/cmds/statsd/tests/metrics/metrics_test_helper.h
@@ -14,6 +14,7 @@
#pragma once
#include "src/condition/ConditionWizard.h"
+#include "src/external/StatsPullerManager.h"
#include <gmock/gmock.h>
#include <gtest/gtest.h>
@@ -30,6 +31,13 @@
const std::map<std::string, HashableDimensionKey>& conditionParameters));
};
+class MockStatsPullerManager : public StatsPullerManager {
+public:
+ MOCK_METHOD3(RegisterReceiver, void(int tagId, wp<PullDataReceiver> receiver, long intervalMs));
+ MOCK_METHOD2(UnRegisterReceiver, void(int tagId, wp<PullDataReceiver> receiver));
+ MOCK_METHOD2(Pull, bool(const int pullCode, vector<std::shared_ptr<LogEvent>>* data));
+};
+
} // namespace statsd
} // namespace os
} // namespace android
diff --git a/config/compiled-classes-phone b/config/compiled-classes-phone
index bc344d7..fb201ef 100644
--- a/config/compiled-classes-phone
+++ b/config/compiled-classes-phone
@@ -5205,6 +5205,8 @@
com.android.internal.app.AlertController$ButtonHandler
com.android.internal.app.AlertController$RecycleListView
com.android.internal.app.AssistUtils
+com.android.internal.app.ColorDisplayController
+com.android.internal.app.ColorDisplayController$Callback
com.android.internal.app.IAppOpsCallback
com.android.internal.app.IAppOpsCallback$Stub
com.android.internal.app.IAppOpsCallback$Stub$Proxy
@@ -5230,8 +5232,6 @@
com.android.internal.app.IVoiceInteractionSessionShowCallback$Stub
com.android.internal.app.IVoiceInteractor
com.android.internal.app.IVoiceInteractor$Stub
-com.android.internal.app.NightDisplayController
-com.android.internal.app.NightDisplayController$Callback
com.android.internal.app.ProcessMap
com.android.internal.app.ResolverActivity
com.android.internal.app.ToolbarActionBar
diff --git a/config/preloaded-classes b/config/preloaded-classes
index a9e90de..784c3f8 100644
--- a/config/preloaded-classes
+++ b/config/preloaded-classes
@@ -2768,6 +2768,7 @@
com.android.ims.ImsException
com.android.internal.R$styleable
com.android.internal.app.AlertController$AlertParams
+com.android.internal.app.ColorDisplayController
com.android.internal.app.IAppOpsCallback
com.android.internal.app.IAppOpsCallback$Stub
com.android.internal.app.IAppOpsService
@@ -2780,7 +2781,6 @@
com.android.internal.app.IVoiceInteractionManagerService$Stub
com.android.internal.app.IVoiceInteractor
com.android.internal.app.IVoiceInteractor$Stub
-com.android.internal.app.NightDisplayController
com.android.internal.appwidget.IAppWidgetService
com.android.internal.appwidget.IAppWidgetService$Stub
com.android.internal.appwidget.IAppWidgetService$Stub$Proxy
diff --git a/core/java/Android.bp b/core/java/Android.bp
index 42b0f6b..1503445 100644
--- a/core/java/Android.bp
+++ b/core/java/Android.bp
@@ -2,3 +2,8 @@
name: "IKeyAttestationApplicationIdProvider.aidl",
srcs: ["android/security/keymaster/IKeyAttestationApplicationIdProvider.aidl"],
}
+
+filegroup {
+ name: "IKeystoreService.aidl",
+ srcs: ["android/security/IKeystoreService.aidl"],
+}
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index a8863bf..99f3dee 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -193,10 +193,13 @@
* <a name="Fragments"></a>
* <h3>Fragments</h3>
*
- * <p>Starting with {@link android.os.Build.VERSION_CODES#HONEYCOMB}, Activity
- * implementations can make use of the {@link Fragment} class to better
+ * <p>The {@link android.support.v4.app.FragmentActivity} subclass
+ * can make use of the {@link android.support.v4.app.Fragment} class to better
* modularize their code, build more sophisticated user interfaces for larger
- * screens, and help scale their application between small and large screens.
+ * screens, and help scale their application between small and large screens.</p>
+ *
+ * <p>For more information about using fragments, read the
+ * <a href="{@docRoot}guide/components/fragments.html">Fragments</a> developer guide.</p>
*
* <a name="ActivityLifecycle"></a>
* <h3>Activity Lifecycle</h3>
@@ -915,7 +918,10 @@
/**
* Return the LoaderManager for this activity, creating it if needed.
+ *
+ * @deprecated Use {@link android.support.v4.app.FragmentActivity#getSupportLoaderManager()}
*/
+ @Deprecated
public LoaderManager getLoaderManager() {
return mFragments.getLoaderManager();
}
@@ -2395,7 +2401,10 @@
/**
* Return the FragmentManager for interacting with fragments associated
* with this activity.
+ *
+ * @deprecated Use {@link android.support.v4.app.FragmentActivity#getSupportFragmentManager()}
*/
+ @Deprecated
public FragmentManager getFragmentManager() {
return mFragments.getFragmentManager();
}
@@ -2404,7 +2413,11 @@
* Called when a Fragment is being attached to this activity, immediately
* after the call to its {@link Fragment#onAttach Fragment.onAttach()}
* method and before {@link Fragment#onCreate Fragment.onCreate()}.
+ *
+ * @deprecated Use {@link
+ * android.support.v4.app.FragmentActivity#onAttachFragment(android.support.v4.app.Fragment)}
*/
+ @Deprecated
public void onAttachFragment(Fragment fragment) {
}
@@ -5106,7 +5119,11 @@
*
* @see Fragment#startActivity
* @see Fragment#startActivityForResult
+ *
+ * @deprecated Use {@link android.support.v4.app.FragmentActivity#startActivityFromFragment(
+ * android.support.v4.app.Fragment,Intent,int)}
*/
+ @Deprecated
public void startActivityFromFragment(@NonNull Fragment fragment,
@RequiresPermission Intent intent, int requestCode) {
startActivityFromFragment(fragment, intent, requestCode, null);
@@ -5131,7 +5148,11 @@
*
* @see Fragment#startActivity
* @see Fragment#startActivityForResult
+ *
+ * @deprecated Use {@link android.support.v4.app.FragmentActivity#startActivityFromFragment(
+ * android.support.v4.app.Fragment,Intent,int,Bundle)}
*/
+ @Deprecated
public void startActivityFromFragment(@NonNull Fragment fragment,
@RequiresPermission Intent intent, int requestCode, @Nullable Bundle options) {
startActivityForResult(fragment.mWho, intent, requestCode, options);
diff --git a/core/java/android/app/DexLoadReporter.java b/core/java/android/app/DexLoadReporter.java
index f99d1a8..0643414 100644
--- a/core/java/android/app/DexLoadReporter.java
+++ b/core/java/android/app/DexLoadReporter.java
@@ -19,7 +19,6 @@
import android.os.FileUtils;
import android.os.RemoteException;
import android.os.SystemProperties;
-import android.system.ErrnoException;
import android.util.Slog;
import com.android.internal.annotations.GuardedBy;
@@ -27,8 +26,6 @@
import dalvik.system.BaseDexClassLoader;
import dalvik.system.VMRuntime;
-import libcore.io.Libcore;
-
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
@@ -155,23 +152,12 @@
return;
}
- File realDexPath;
- try {
- // Secondary dex profiles are stored in the oat directory, next to the real dex file
- // and have the same name with 'cur.prof' appended. We use the realpath because that
- // is what installd is using when processing the dex file.
- // NOTE: Keep in sync with installd.
- realDexPath = new File(Libcore.os.realpath(dexPath));
- } catch (ErrnoException ex) {
- Slog.e(TAG, "Failed to get the real path of secondary dex " + dexPath
- + ":" + ex.getMessage());
- // Do not continue with registration if we could not retrieve the real path.
- return;
- }
-
+ // Secondary dex profiles are stored in the oat directory, next to dex file
+ // and have the same name with 'cur.prof' appended.
// NOTE: Keep this in sync with installd expectations.
- File secondaryProfileDir = new File(realDexPath.getParent(), "oat");
- File secondaryProfile = new File(secondaryProfileDir, realDexPath.getName() + ".cur.prof");
+ File dexPathFile = new File(dexPath);
+ File secondaryProfileDir = new File(dexPathFile.getParent(), "oat");
+ File secondaryProfile = new File(secondaryProfileDir, dexPathFile.getName() + ".cur.prof");
// Create the profile if not already there.
// Returns true if the file was created, false if the file already exists.
diff --git a/core/java/android/app/DialogFragment.java b/core/java/android/app/DialogFragment.java
index 7e0e4d8..a0fb6ee 100644
--- a/core/java/android/app/DialogFragment.java
+++ b/core/java/android/app/DialogFragment.java
@@ -136,7 +136,10 @@
*
* {@sample development/samples/ApiDemos/src/com/example/android/apis/app/FragmentDialogOrActivity.java
* embed}
+ *
+ * @deprecated Use {@link android.support.v4.app.DialogFragment}
*/
+@Deprecated
public class DialogFragment extends Fragment
implements DialogInterface.OnCancelListener, DialogInterface.OnDismissListener {
diff --git a/core/java/android/app/Fragment.java b/core/java/android/app/Fragment.java
index 9377345..a92684b 100644
--- a/core/java/android/app/Fragment.java
+++ b/core/java/android/app/Fragment.java
@@ -256,7 +256,10 @@
* <p>After each call to this function, a new entry is on the stack, and
* pressing back will pop it to return the user to whatever previous state
* the activity UI was in.
+ *
+ * @deprecated Use {@link android.support.v4.app.Fragment}
*/
+@Deprecated
public class Fragment implements ComponentCallbacks2, OnCreateContextMenuListener {
private static final ArrayMap<String, Class<?>> sClassMap =
new ArrayMap<String, Class<?>>();
@@ -414,7 +417,10 @@
* State information that has been retrieved from a fragment instance
* through {@link FragmentManager#saveFragmentInstanceState(Fragment)
* FragmentManager.saveFragmentInstanceState}.
+ *
+ * @deprecated Use {@link android.support.v4.app.Fragment.SavedState}
*/
+ @Deprecated
public static class SavedState implements Parcelable {
final Bundle mState;
@@ -458,7 +464,10 @@
/**
* Thrown by {@link Fragment#instantiate(Context, String, Bundle)} when
* there is an instantiation failure.
+ *
+ * @deprecated Use {@link android.support.v4.app.Fragment.InstantiationException}
*/
+ @Deprecated
static public class InstantiationException extends AndroidRuntimeException {
public InstantiationException(String msg, Exception cause) {
super(msg, cause);
@@ -1031,7 +1040,10 @@
/**
* Return the LoaderManager for this fragment, creating it if needed.
+ *
+ * @deprecated Use {@link android.support.v4.app.Fragment#getLoaderManager()}
*/
+ @Deprecated
public LoaderManager getLoaderManager() {
if (mLoaderManager != null) {
return mLoaderManager;
diff --git a/core/java/android/app/FragmentBreadCrumbs.java b/core/java/android/app/FragmentBreadCrumbs.java
index d0aa0fd..e3e47ae 100644
--- a/core/java/android/app/FragmentBreadCrumbs.java
+++ b/core/java/android/app/FragmentBreadCrumbs.java
@@ -65,7 +65,10 @@
/**
* Interface to intercept clicks on the bread crumbs.
+ *
+ * @deprecated This widget is no longer supported.
*/
+ @Deprecated
public interface OnBreadCrumbClickListener {
/**
* Called when a bread crumb is clicked.
diff --git a/core/java/android/app/FragmentContainer.java b/core/java/android/app/FragmentContainer.java
index f8836bc8..a1dd32f 100644
--- a/core/java/android/app/FragmentContainer.java
+++ b/core/java/android/app/FragmentContainer.java
@@ -24,7 +24,10 @@
/**
* Callbacks to a {@link Fragment}'s container.
+ *
+ * @deprecated Use {@link android.support.v4.app.FragmentContainer}
*/
+@Deprecated
public abstract class FragmentContainer {
/**
* Return the view with the given resource ID. May return {@code null} if the
diff --git a/core/java/android/app/FragmentController.java b/core/java/android/app/FragmentController.java
index cff94d8..cbb58d4 100644
--- a/core/java/android/app/FragmentController.java
+++ b/core/java/android/app/FragmentController.java
@@ -37,7 +37,10 @@
* <p>
* It is the responsibility of the host to take care of the Fragment's lifecycle.
* The methods provided by {@link FragmentController} are for that purpose.
+ *
+ * @deprecated Use {@link android.support.v4.app.FragmentController}
*/
+@Deprecated
public class FragmentController {
private final FragmentHostCallback<?> mHost;
diff --git a/core/java/android/app/FragmentHostCallback.java b/core/java/android/app/FragmentHostCallback.java
index 5ef23e6..1edc68e 100644
--- a/core/java/android/app/FragmentHostCallback.java
+++ b/core/java/android/app/FragmentHostCallback.java
@@ -37,7 +37,10 @@
* Fragments may be hosted by any object; such as an {@link Activity}. In order to
* host fragments, implement {@link FragmentHostCallback}, overriding the methods
* applicable to the host.
+ *
+ * @deprecated Use {@link android.support.v4.app.FragmentHostCallback}
*/
+@Deprecated
public abstract class FragmentHostCallback<E> extends FragmentContainer {
private final Activity mActivity;
final Context mContext;
diff --git a/core/java/android/app/FragmentManager.java b/core/java/android/app/FragmentManager.java
index 0d5cd02..12e60b8 100644
--- a/core/java/android/app/FragmentManager.java
+++ b/core/java/android/app/FragmentManager.java
@@ -74,7 +74,10 @@
* {@link android.support.v4.app.FragmentActivity}. See the blog post
* <a href="http://android-developers.blogspot.com/2011/03/fragments-for-all.html">
* Fragments For All</a> for more details.
+ *
+ * @deprecated Use {@link android.support.v4.app.FragmentManager}
*/
+@Deprecated
public abstract class FragmentManager {
/**
* Representation of an entry on the fragment back stack, as created
@@ -86,7 +89,10 @@
* <p>Note that you should never hold on to a BackStackEntry object;
* the identifier as returned by {@link #getId} is the only thing that
* will be persisted across activity instances.
+ *
+ * @deprecated Use {@link android.support.v4.app.FragmentManager.BackStackEntry}
*/
+ @Deprecated
public interface BackStackEntry {
/**
* Return the unique identifier for the entry. This is the only
@@ -129,7 +135,10 @@
/**
* Interface to watch for changes to the back stack.
+ *
+ * @deprecated Use {@link android.support.v4.app.FragmentManager.OnBackStackChangedListener}
*/
+ @Deprecated
public interface OnBackStackChangedListener {
/**
* Called whenever the contents of the back stack change.
@@ -428,7 +437,10 @@
/**
* Callback interface for listening to fragment state changes that happen
* within a given FragmentManager.
+ *
+ * @deprecated Use {@link android.support.v4.app.FragmentManager.FragmentLifecycleCallbacks}
*/
+ @Deprecated
public abstract static class FragmentLifecycleCallbacks {
/**
* Called right before the fragment's {@link Fragment#onAttach(Context)} method is called.
diff --git a/core/java/android/app/FragmentManagerNonConfig.java b/core/java/android/app/FragmentManagerNonConfig.java
index 50d3797..beb1a15 100644
--- a/core/java/android/app/FragmentManagerNonConfig.java
+++ b/core/java/android/app/FragmentManagerNonConfig.java
@@ -27,7 +27,10 @@
* and passed to the state save and restore process for fragments in
* {@link FragmentController#retainNonConfig()} and
* {@link FragmentController#restoreAllState(Parcelable, FragmentManagerNonConfig)}.</p>
+ *
+ * @deprecated Use {@link android.support.v4.app.FragmentManagerNonConfig}
*/
+@Deprecated
public class FragmentManagerNonConfig {
private final List<Fragment> mFragments;
private final List<FragmentManagerNonConfig> mChildNonConfigs;
diff --git a/core/java/android/app/FragmentTransaction.java b/core/java/android/app/FragmentTransaction.java
index c910e90..0f4a7fb 100644
--- a/core/java/android/app/FragmentTransaction.java
+++ b/core/java/android/app/FragmentTransaction.java
@@ -21,7 +21,10 @@
* <a href="{@docRoot}guide/components/fragments.html">Fragments</a> developer
* guide.</p>
* </div>
+ *
+ * @deprecated Use {@link android.support.v4.app.FragmentTransaction}
*/
+@Deprecated
public abstract class FragmentTransaction {
/**
* Calls {@link #add(int, Fragment, String)} with a 0 containerViewId.
diff --git a/core/java/android/app/ListFragment.java b/core/java/android/app/ListFragment.java
index 0b96d84..90b77b3 100644
--- a/core/java/android/app/ListFragment.java
+++ b/core/java/android/app/ListFragment.java
@@ -144,7 +144,10 @@
*
* @see #setListAdapter
* @see android.widget.ListView
+ *
+ * @deprecated Use {@link android.support.v4.app.ListFragment}
*/
+@Deprecated
public class ListFragment extends Fragment {
final private Handler mHandler = new Handler();
diff --git a/core/java/android/app/LoaderManager.java b/core/java/android/app/LoaderManager.java
index 56dfc58..7969684 100644
--- a/core/java/android/app/LoaderManager.java
+++ b/core/java/android/app/LoaderManager.java
@@ -54,11 +54,17 @@
* <p>For more information about using loaders, read the
* <a href="{@docRoot}guide/topics/fundamentals/loaders.html">Loaders</a> developer guide.</p>
* </div>
+ *
+ * @deprecated Use {@link android.support.v4.app.LoaderManager}
*/
+@Deprecated
public abstract class LoaderManager {
/**
* Callback interface for a client to interact with the manager.
+ *
+ * @deprecated Use {@link android.support.v4.app.LoaderManager.LoaderCallbacks}
*/
+ @Deprecated
public interface LoaderCallbacks<D> {
/**
* Instantiate and return a new Loader for the given ID.
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index 6989db6..d813d66 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -41,6 +41,8 @@
import android.content.pm.LauncherApps;
import android.content.pm.PackageManager;
import android.content.pm.ShortcutManager;
+import android.content.pm.crossprofile.CrossProfileApps;
+import android.content.pm.crossprofile.ICrossProfileApps;
import android.content.res.Resources;
import android.hardware.ConsumerIrManager;
import android.hardware.ISerialManager;
@@ -81,6 +83,7 @@
import android.net.IpSecManager;
import android.net.NetworkPolicyManager;
import android.net.NetworkScoreManager;
+import android.net.NetworkWatchlistManager;
import android.net.lowpan.ILowpanManager;
import android.net.lowpan.LowpanManager;
import android.net.nsd.INsdManager;
@@ -150,6 +153,7 @@
import com.android.internal.app.IBatteryStats;
import com.android.internal.app.ISoundTriggerService;
import com.android.internal.appwidget.IAppWidgetService;
+import com.android.internal.net.INetworkWatchlistManager;
import com.android.internal.os.IDropBoxManagerService;
import com.android.internal.policy.PhoneLayoutInflater;
@@ -862,6 +866,17 @@
return new ShortcutManager(ctx, IShortcutService.Stub.asInterface(b));
}});
+ registerService(Context.NETWORK_WATCHLIST_SERVICE, NetworkWatchlistManager.class,
+ new CachedServiceFetcher<NetworkWatchlistManager>() {
+ @Override
+ public NetworkWatchlistManager createService(ContextImpl ctx)
+ throws ServiceNotFoundException {
+ IBinder b =
+ ServiceManager.getServiceOrThrow(Context.NETWORK_WATCHLIST_SERVICE);
+ return new NetworkWatchlistManager(ctx,
+ INetworkWatchlistManager.Stub.asInterface(b));
+ }});
+
registerService(Context.SYSTEM_HEALTH_SERVICE, SystemHealthManager.class,
new CachedServiceFetcher<SystemHealthManager>() {
@Override
@@ -909,6 +924,18 @@
public RulesManager createService(ContextImpl ctx) {
return new RulesManager(ctx.getOuterContext());
}});
+
+ registerService(Context.CROSS_PROFILE_APPS_SERVICE, CrossProfileApps.class,
+ new CachedServiceFetcher<CrossProfileApps>() {
+ @Override
+ public CrossProfileApps createService(ContextImpl ctx)
+ throws ServiceNotFoundException {
+ IBinder b = ServiceManager.getServiceOrThrow(
+ Context.CROSS_PROFILE_APPS_SERVICE);
+ return new CrossProfileApps(ctx.getOuterContext(),
+ ICrossProfileApps.Stub.asInterface(b));
+ }
+ });
}
/**
diff --git a/core/java/android/app/job/JobInfo.java b/core/java/android/app/job/JobInfo.java
index b640bd5..530d84b 100644
--- a/core/java/android/app/job/JobInfo.java
+++ b/core/java/android/app/job/JobInfo.java
@@ -16,6 +16,12 @@
package android.app.job;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VPN;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED;
+import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
import static android.util.TimeUtils.formatDuration;
import android.annotation.BytesLong;
@@ -25,6 +31,8 @@
import android.annotation.RequiresPermission;
import android.content.ClipData;
import android.content.ComponentName;
+import android.net.NetworkRequest;
+import android.net.NetworkSpecifier;
import android.net.Uri;
import android.os.BaseBundle;
import android.os.Bundle;
@@ -56,6 +64,7 @@
NETWORK_TYPE_ANY,
NETWORK_TYPE_UNMETERED,
NETWORK_TYPE_NOT_ROAMING,
+ NETWORK_TYPE_CELLULAR,
NETWORK_TYPE_METERED,
})
@Retention(RetentionPolicy.SOURCE)
@@ -69,8 +78,21 @@
public static final int NETWORK_TYPE_UNMETERED = 2;
/** This job requires network connectivity that is not roaming. */
public static final int NETWORK_TYPE_NOT_ROAMING = 3;
- /** This job requires metered connectivity such as most cellular data networks. */
- public static final int NETWORK_TYPE_METERED = 4;
+ /** This job requires network connectivity that is a cellular network. */
+ public static final int NETWORK_TYPE_CELLULAR = 4;
+
+ /**
+ * This job requires metered connectivity such as most cellular data
+ * networks.
+ *
+ * @deprecated Cellular networks may be unmetered, or Wi-Fi networks may be
+ * metered, so this isn't a good way of selecting a specific
+ * transport. Instead, use {@link #NETWORK_TYPE_CELLULAR} or
+ * {@link android.net.NetworkRequest.Builder#addTransportType(int)}
+ * if your job requires a specific network transport.
+ */
+ @Deprecated
+ public static final int NETWORK_TYPE_METERED = NETWORK_TYPE_CELLULAR;
/** Sentinel value indicating that bytes are unknown. */
public static final int NETWORK_BYTES_UNKNOWN = -1;
@@ -253,7 +275,7 @@
private final long triggerContentMaxDelay;
private final boolean hasEarlyConstraint;
private final boolean hasLateConstraint;
- private final int networkType;
+ private final NetworkRequest networkRequest;
private final long networkBytes;
private final long minLatencyMillis;
private final long maxExecutionDelayMillis;
@@ -385,10 +407,37 @@
}
/**
- * The kind of connectivity requirements that the job has.
+ * Return the basic description of the kind of network this job requires.
+ *
+ * @deprecated This method attempts to map {@link #getRequiredNetwork()}
+ * into the set of simple constants, which results in a loss of
+ * fidelity. Callers should move to using
+ * {@link #getRequiredNetwork()} directly.
+ * @see Builder#setRequiredNetworkType(int)
*/
+ @Deprecated
public @NetworkType int getNetworkType() {
- return networkType;
+ if (networkRequest == null) {
+ return NETWORK_TYPE_NONE;
+ } else if (networkRequest.networkCapabilities.hasCapability(NET_CAPABILITY_NOT_METERED)) {
+ return NETWORK_TYPE_UNMETERED;
+ } else if (networkRequest.networkCapabilities.hasCapability(NET_CAPABILITY_NOT_ROAMING)) {
+ return NETWORK_TYPE_NOT_ROAMING;
+ } else if (networkRequest.networkCapabilities.hasTransport(TRANSPORT_CELLULAR)) {
+ return NETWORK_TYPE_CELLULAR;
+ } else {
+ return NETWORK_TYPE_ANY;
+ }
+ }
+
+ /**
+ * Return the detailed description of the kind of network this job requires,
+ * or {@code null} if no specific kind of network is required.
+ *
+ * @see Builder#setRequiredNetwork(NetworkRequest)
+ */
+ public @Nullable NetworkRequest getRequiredNetwork() {
+ return networkRequest;
}
/**
@@ -438,8 +487,7 @@
* job does not recur periodically.
*/
public long getIntervalMillis() {
- final long minInterval = getMinPeriodMillis();
- return intervalMillis >= minInterval ? intervalMillis : minInterval;
+ return intervalMillis;
}
/**
@@ -447,10 +495,7 @@
* execute at any time in a window of flex length at the end of the period.
*/
public long getFlexMillis() {
- long interval = getIntervalMillis();
- long percentClamp = 5 * interval / 100;
- long clampedFlex = Math.max(flexMillis, Math.max(percentClamp, getMinFlexMillis()));
- return clampedFlex <= interval ? clampedFlex : interval;
+ return flexMillis;
}
/**
@@ -459,8 +504,7 @@
* to 30 seconds, minimum is currently 10 seconds.
*/
public long getInitialBackoffMillis() {
- final long minBackoff = getMinBackoffMillis();
- return initialBackoffMillis >= minBackoff ? initialBackoffMillis : minBackoff;
+ return initialBackoffMillis;
}
/**
@@ -538,7 +582,7 @@
if (hasLateConstraint != j.hasLateConstraint) {
return false;
}
- if (networkType != j.networkType) {
+ if (!Objects.equals(networkRequest, j.networkRequest)) {
return false;
}
if (networkBytes != j.networkBytes) {
@@ -601,7 +645,9 @@
hashCode = 31 * hashCode + Long.hashCode(triggerContentMaxDelay);
hashCode = 31 * hashCode + Boolean.hashCode(hasEarlyConstraint);
hashCode = 31 * hashCode + Boolean.hashCode(hasLateConstraint);
- hashCode = 31 * hashCode + networkType;
+ if (networkRequest != null) {
+ hashCode = 31 * hashCode + networkRequest.hashCode();
+ }
hashCode = 31 * hashCode + Long.hashCode(networkBytes);
hashCode = 31 * hashCode + Long.hashCode(minLatencyMillis);
hashCode = 31 * hashCode + Long.hashCode(maxExecutionDelayMillis);
@@ -632,7 +678,11 @@
triggerContentUris = in.createTypedArray(TriggerContentUri.CREATOR);
triggerContentUpdateDelay = in.readLong();
triggerContentMaxDelay = in.readLong();
- networkType = in.readInt();
+ if (in.readInt() != 0) {
+ networkRequest = NetworkRequest.CREATOR.createFromParcel(in);
+ } else {
+ networkRequest = null;
+ }
networkBytes = in.readLong();
minLatencyMillis = in.readLong();
maxExecutionDelayMillis = in.readLong();
@@ -661,7 +711,7 @@
: null;
triggerContentUpdateDelay = b.mTriggerContentUpdateDelay;
triggerContentMaxDelay = b.mTriggerContentMaxDelay;
- networkType = b.mNetworkType;
+ networkRequest = b.mNetworkRequest;
networkBytes = b.mNetworkBytes;
minLatencyMillis = b.mMinLatencyMillis;
maxExecutionDelayMillis = b.mMaxExecutionDelayMillis;
@@ -699,7 +749,12 @@
out.writeTypedArray(triggerContentUris, flags);
out.writeLong(triggerContentUpdateDelay);
out.writeLong(triggerContentMaxDelay);
- out.writeInt(networkType);
+ if (networkRequest != null) {
+ out.writeInt(1);
+ networkRequest.writeToParcel(out, flags);
+ } else {
+ out.writeInt(0);
+ }
out.writeLong(networkBytes);
out.writeLong(minLatencyMillis);
out.writeLong(maxExecutionDelayMillis);
@@ -833,7 +888,7 @@
private int mFlags;
// Requirements.
private int mConstraintFlags;
- private int mNetworkType;
+ private NetworkRequest mNetworkRequest;
private long mNetworkBytes = NETWORK_BYTES_UNKNOWN;
private ArrayList<TriggerContentUri> mTriggerContentUris;
private long mTriggerContentUpdateDelay = -1;
@@ -934,24 +989,84 @@
}
/**
- * Set some description of the kind of network type your job needs to
- * have. Not calling this function means the network is not necessary,
- * as the default is {@link #NETWORK_TYPE_NONE}. Bear in mind that
- * calling this function defines network as a strict requirement for
- * your job. If the network requested is not available your job will
- * never run. See {@link #setOverrideDeadline(long)} to change this
- * behaviour.
+ * Set basic description of the kind of network your job requires. If
+ * you need more precise control over network capabilities, see
+ * {@link #setRequiredNetwork(NetworkRequest)}.
+ * <p>
+ * If your job doesn't need a network connection, you don't need to call
+ * this method, as the default value is {@link #NETWORK_TYPE_NONE}.
+ * <p>
+ * Calling this method defines network as a strict requirement for your
+ * job. If the network requested is not available your job will never
+ * run. See {@link #setOverrideDeadline(long)} to change this behavior.
+ * Calling this method will override any requirements previously defined
+ * by {@link #setRequiredNetwork(NetworkRequest)}; you typically only
+ * want to call one of these methods.
* <p class="note">
- * Note: When your job executes in
+ * When your job executes in
* {@link JobService#onStartJob(JobParameters)}, be sure to use the
* specific network returned by {@link JobParameters#getNetwork()},
* otherwise you'll use the default network which may not meet this
* constraint.
*
+ * @see #setRequiredNetwork(NetworkRequest)
+ * @see JobInfo#getNetworkType()
* @see JobParameters#getNetwork()
*/
public Builder setRequiredNetworkType(@NetworkType int networkType) {
- mNetworkType = networkType;
+ if (networkType == NETWORK_TYPE_NONE) {
+ return setRequiredNetwork(null);
+ } else {
+ final NetworkRequest.Builder builder = new NetworkRequest.Builder();
+
+ // All types require validated Internet
+ builder.addCapability(NET_CAPABILITY_INTERNET);
+ builder.addCapability(NET_CAPABILITY_VALIDATED);
+ builder.removeCapability(NET_CAPABILITY_NOT_VPN);
+
+ if (networkType == NETWORK_TYPE_ANY) {
+ // No other capabilities
+ } else if (networkType == NETWORK_TYPE_UNMETERED) {
+ builder.addCapability(NET_CAPABILITY_NOT_METERED);
+ } else if (networkType == NETWORK_TYPE_NOT_ROAMING) {
+ builder.addCapability(NET_CAPABILITY_NOT_ROAMING);
+ } else if (networkType == NETWORK_TYPE_CELLULAR) {
+ builder.addTransportType(TRANSPORT_CELLULAR);
+ }
+
+ return setRequiredNetwork(builder.build());
+ }
+ }
+
+ /**
+ * Set detailed description of the kind of network your job requires.
+ * <p>
+ * If your job doesn't need a network connection, you don't need to call
+ * this method, as the default is {@code null}.
+ * <p>
+ * Calling this method defines network as a strict requirement for your
+ * job. If the network requested is not available your job will never
+ * run. See {@link #setOverrideDeadline(long)} to change this behavior.
+ * Calling this method will override any requirements previously defined
+ * by {@link #setRequiredNetworkType(int)}; you typically only want to
+ * call one of these methods.
+ * <p class="note">
+ * When your job executes in
+ * {@link JobService#onStartJob(JobParameters)}, be sure to use the
+ * specific network returned by {@link JobParameters#getNetwork()},
+ * otherwise you'll use the default network which may not meet this
+ * constraint.
+ *
+ * @param networkRequest The detailed description of the kind of network
+ * this job requires, or {@code null} if no specific kind of
+ * network is required. Defining a {@link NetworkSpecifier}
+ * is only supported for jobs that aren't persisted.
+ * @see #setRequiredNetworkType(int)
+ * @see JobInfo#getRequiredNetwork()
+ * @see JobParameters#getNetwork()
+ */
+ public Builder setRequiredNetwork(@Nullable NetworkRequest networkRequest) {
+ mNetworkRequest = networkRequest;
return this;
}
@@ -1140,6 +1255,21 @@
* higher.
*/
public Builder setPeriodic(long intervalMillis, long flexMillis) {
+ final long minPeriod = getMinPeriodMillis();
+ if (intervalMillis < minPeriod) {
+ Log.w(TAG, "Requested interval " + formatDuration(intervalMillis) + " for job "
+ + mJobId + " is too small; raising to " + formatDuration(minPeriod));
+ intervalMillis = minPeriod;
+ }
+
+ final long percentClamp = 5 * intervalMillis / 100;
+ final long minFlex = Math.max(percentClamp, getMinFlexMillis());
+ if (flexMillis < minFlex) {
+ Log.w(TAG, "Requested flex " + formatDuration(flexMillis) + " for job " + mJobId
+ + " is too small; raising to " + formatDuration(minFlex));
+ flexMillis = minFlex;
+ }
+
mIsPeriodic = true;
mIntervalMillis = intervalMillis;
mFlexMillis = flexMillis;
@@ -1189,6 +1319,13 @@
*/
public Builder setBackoffCriteria(long initialBackoffMillis,
@BackoffPolicy int backoffPolicy) {
+ final long minBackoff = getMinBackoffMillis();
+ if (initialBackoffMillis < minBackoff) {
+ Log.w(TAG, "Requested backoff " + formatDuration(initialBackoffMillis) + " for job "
+ + mJobId + " is too small; raising to " + formatDuration(minBackoff));
+ initialBackoffMillis = minBackoff;
+ }
+
mBackoffPolicySet = true;
mInitialBackoffMillis = initialBackoffMillis;
mBackoffPolicy = backoffPolicy;
@@ -1213,16 +1350,22 @@
public JobInfo build() {
// Allow jobs with no constraints - What am I, a database?
if (!mHasEarlyConstraint && !mHasLateConstraint && mConstraintFlags == 0 &&
- mNetworkType == NETWORK_TYPE_NONE &&
+ mNetworkRequest == null &&
mTriggerContentUris == null) {
throw new IllegalArgumentException("You're trying to build a job with no " +
"constraints, this is not allowed.");
}
// Check that network estimates require network type
- if (mNetworkBytes > 0 && mNetworkType == NETWORK_TYPE_NONE) {
+ if (mNetworkBytes > 0 && mNetworkRequest == null) {
throw new IllegalArgumentException(
"Can't provide estimated network usage without requiring a network");
}
+ // We can't serialize network specifiers
+ if (mIsPersisted && mNetworkRequest != null
+ && mNetworkRequest.networkCapabilities.getNetworkSpecifier() != null) {
+ throw new IllegalArgumentException(
+ "Network specifiers aren't supported for persistent jobs");
+ }
// Check that a deadline was not set on a periodic job.
if (mIsPeriodic) {
if (mMaxExecutionDelayMillis != 0L) {
@@ -1257,31 +1400,7 @@
" back-off policy, so calling setBackoffCriteria with" +
" setRequiresDeviceIdle is an error.");
}
- JobInfo job = new JobInfo(this);
- if (job.isPeriodic()) {
- if (job.intervalMillis != job.getIntervalMillis()) {
- StringBuilder builder = new StringBuilder();
- builder.append("Specified interval for ")
- .append(String.valueOf(mJobId))
- .append(" is ");
- formatDuration(mIntervalMillis, builder);
- builder.append(". Clamped to ");
- formatDuration(job.getIntervalMillis(), builder);
- Log.w(TAG, builder.toString());
- }
- if (job.flexMillis != job.getFlexMillis()) {
- StringBuilder builder = new StringBuilder();
- builder.append("Specified flex for ")
- .append(String.valueOf(mJobId))
- .append(" is ");
- formatDuration(mFlexMillis, builder);
- builder.append(". Clamped to ");
- formatDuration(job.getFlexMillis(), builder);
- Log.w(TAG, builder.toString());
- }
- }
- return job;
+ return new JobInfo(this);
}
}
-
}
diff --git a/core/java/android/bluetooth/BluetoothSocket.java b/core/java/android/bluetooth/BluetoothSocket.java
index 4035ee1..0569913 100644
--- a/core/java/android/bluetooth/BluetoothSocket.java
+++ b/core/java/android/bluetooth/BluetoothSocket.java
@@ -375,7 +375,7 @@
IBluetooth bluetoothProxy =
BluetoothAdapter.getDefaultAdapter().getBluetoothService(null);
if (bluetoothProxy == null) throw new IOException("Bluetooth is off");
- mPfd = bluetoothProxy.connectSocket(mDevice, mType,
+ mPfd = bluetoothProxy.getSocketManager().connectSocket(mDevice, mType,
mUuid, mPort, getSecurityFlags());
synchronized (this) {
if (DBG) Log.d(TAG, "connect(), SocketState: " + mSocketState + ", mPfd: " + mPfd);
@@ -417,7 +417,7 @@
return -1;
}
try {
- mPfd = bluetoothProxy.createSocketChannel(mType, mServiceName,
+ mPfd = bluetoothProxy.getSocketManager().createSocketChannel(mType, mServiceName,
mUuid, mPort, getSecurityFlags());
} catch (RemoteException e) {
Log.e(TAG, Log.getStackTraceString(new Throwable()));
diff --git a/core/java/android/content/AsyncTaskLoader.java b/core/java/android/content/AsyncTaskLoader.java
index b7545bf..6e9f09c 100644
--- a/core/java/android/content/AsyncTaskLoader.java
+++ b/core/java/android/content/AsyncTaskLoader.java
@@ -49,7 +49,10 @@
* fragment}
*
* @param <D> the data type to be loaded.
+ *
+ * @deprecated Use {@link android.support.v4.content.AsyncTaskLoader}
*/
+@Deprecated
public abstract class AsyncTaskLoader<D> extends Loader<D> {
static final String TAG = "AsyncTaskLoader";
static final boolean DEBUG = false;
diff --git a/core/java/android/content/ContentProviderClient.java b/core/java/android/content/ContentProviderClient.java
index f8c139f..2d490a0 100644
--- a/core/java/android/content/ContentProviderClient.java
+++ b/core/java/android/content/ContentProviderClient.java
@@ -22,6 +22,7 @@
import android.database.CrossProcessCursorWrapper;
import android.database.Cursor;
import android.net.Uri;
+import android.os.Binder;
import android.os.Bundle;
import android.os.CancellationSignal;
import android.os.DeadObjectException;
@@ -102,8 +103,16 @@
if (sAnrHandler == null) {
sAnrHandler = new Handler(Looper.getMainLooper(), null, true /* async */);
}
+
+ // If the remote process hangs, we're going to kill it, so we're
+ // technically okay doing blocking calls.
+ Binder.allowBlocking(mContentProvider.asBinder());
} else {
mAnrRunnable = null;
+
+ // If we're no longer watching for hangs, revert back to default
+ // blocking behavior.
+ Binder.defaultBlocking(mContentProvider.asBinder());
}
}
}
@@ -511,6 +520,10 @@
private boolean closeInternal() {
mCloseGuard.close();
if (mClosed.compareAndSet(false, true)) {
+ // We can't do ANR checks after we cease to exist! Reset any
+ // blocking behavior changes we might have made.
+ setDetectNotResponding(0);
+
if (mStable) {
return mContentResolver.releaseProvider(mContentProvider);
} else {
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index c165fb3..72f75112 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -3413,6 +3413,8 @@
public static final String NETWORK_STATS_SERVICE = "netstats";
/** {@hide} */
public static final String NETWORK_POLICY_SERVICE = "netpolicy";
+ /** {@hide} */
+ public static final String NETWORK_WATCHLIST_SERVICE = "network_watchlist";
/**
* Use with {@link #getSystemService} to retrieve a {@link
@@ -4071,6 +4073,14 @@
public static final String TIME_ZONE_RULES_MANAGER_SERVICE = "timezone";
/**
+ * Use with {@link #getSystemService} to retrieve a
+ * {@link android.content.pm.crossprofile.CrossProfileApps} for cross profile operations.
+ *
+ * @see #getSystemService
+ */
+ public static final String CROSS_PROFILE_APPS_SERVICE = "crossprofileapps";
+
+ /**
* Determine whether the given permission is allowed for a particular
* process and user ID running in the system.
*
diff --git a/core/java/android/content/CursorLoader.java b/core/java/android/content/CursorLoader.java
index c78871c..33386e5 100644
--- a/core/java/android/content/CursorLoader.java
+++ b/core/java/android/content/CursorLoader.java
@@ -38,7 +38,10 @@
* in the desired paramters with {@link #setUri(Uri)}, {@link #setSelection(String)},
* {@link #setSelectionArgs(String[])}, {@link #setSortOrder(String)},
* and {@link #setProjection(String[])}.
+ *
+ * @deprecated Use {@link android.support.v4.content.CursorLoader}
*/
+@Deprecated
public class CursorLoader extends AsyncTaskLoader<Cursor> {
final ForceLoadContentObserver mObserver;
diff --git a/core/java/android/content/Loader.java b/core/java/android/content/Loader.java
index 3faf13b..80f9a14 100644
--- a/core/java/android/content/Loader.java
+++ b/core/java/android/content/Loader.java
@@ -48,7 +48,10 @@
* </div>
*
* @param <D> The result returned when the load is complete
+ *
+ * @deprecated Use {@link android.support.v4.content.Loader}
*/
+@Deprecated
public class Loader<D> {
int mId;
OnLoadCompleteListener<D> mListener;
@@ -66,7 +69,10 @@
* is told it has changed. You do not normally need to use this yourself;
* it is used for you by {@link CursorLoader} to take care of executing
* an update when the cursor's backing data changes.
+ *
+ * @deprecated Use {@link android.support.v4.content.Loader.ForceLoadContentObserver}
*/
+ @Deprecated
public final class ForceLoadContentObserver extends ContentObserver {
public ForceLoadContentObserver() {
super(new Handler());
@@ -90,7 +96,10 @@
* to find out when a Loader it is managing has completed so that this can
* be reported to its client. This interface should only be used if a
* Loader is not being used in conjunction with LoaderManager.
+ *
+ * @deprecated Use {@link android.support.v4.content.Loader.OnLoadCompleteListener}
*/
+ @Deprecated
public interface OnLoadCompleteListener<D> {
/**
* Called on the thread that created the Loader when the load is complete.
@@ -108,7 +117,10 @@
* to find out when a Loader it is managing has been canceled so that it
* can schedule the next Loader. This interface should only be used if a
* Loader is not being used in conjunction with LoaderManager.
+ *
+ * @deprecated Use {@link android.support.v4.content.Loader.OnLoadCanceledListener}
*/
+ @Deprecated
public interface OnLoadCanceledListener<D> {
/**
* Called on the thread that created the Loader when the load is canceled.
diff --git a/core/java/android/content/pm/crossprofile/CrossProfileApps.java b/core/java/android/content/pm/crossprofile/CrossProfileApps.java
new file mode 100644
index 0000000..c441b5f
--- /dev/null
+++ b/core/java/android/content/pm/crossprofile/CrossProfileApps.java
@@ -0,0 +1,90 @@
+/*
+ * 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.content.pm.crossprofile;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.ComponentName;
+import android.content.Context;
+import android.graphics.Rect;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.os.UserManager;
+
+import java.util.List;
+
+/**
+ * Class for handling cross profile operations. Apps can use this class to interact with its
+ * instance in any profile that is in {@link #getTargetUserProfiles()}. For example, app can
+ * use this class to start its main activity in managed profile.
+ */
+public class CrossProfileApps {
+ private final Context mContext;
+ private final ICrossProfileApps mService;
+
+ /** @hide */
+ public CrossProfileApps(Context context, ICrossProfileApps service) {
+ mContext = context;
+ mService = service;
+ }
+
+ /**
+ * Starts the specified main activity of the caller package in the specified profile.
+ *
+ * @param component The ComponentName of the activity to launch, it must be exported and has
+ * action {@link android.content.Intent#ACTION_MAIN}, category
+ * {@link android.content.Intent#CATEGORY_LAUNCHER}. Otherwise, SecurityException will
+ * be thrown.
+ * @param user The UserHandle of the profile, must be one of the users returned by
+ * {@link #getTargetUserProfiles()}, otherwise a {@link SecurityException} will
+ * be thrown.
+ * @param sourceBounds The Rect containing the source bounds of the clicked icon, see
+ * {@link android.content.Intent#setSourceBounds(Rect)}.
+ * @param startActivityOptions Options to pass to startActivity
+ */
+ public void startMainActivity(@NonNull ComponentName component, @NonNull UserHandle user,
+ @Nullable Rect sourceBounds, @Nullable Bundle startActivityOptions) {
+ try {
+ mService.startActivityAsUser(mContext.getPackageName(),
+ component, sourceBounds, startActivityOptions, user);
+ } catch (RemoteException ex) {
+ throw ex.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Return a list of user profiles that that the caller can use when calling other APIs in this
+ * class.
+ * <p>
+ * A user profile would be considered as a valid target user profile, provided that:
+ * <ul>
+ * <li>It gets caller app installed</li>
+ * <li>It is not equal to the calling user</li>
+ * <li>It is in the same profile group of calling user profile</li>
+ * <li>It is enabled</li>
+ * </ul>
+ *
+ * @see UserManager#getUserProfiles()
+ */
+ public @NonNull List<UserHandle> getTargetUserProfiles() {
+ try {
+ return mService.getTargetUserProfiles(mContext.getPackageName());
+ } catch (RemoteException ex) {
+ throw ex.rethrowFromSystemServer();
+ }
+ }
+}
diff --git a/core/java/android/content/pm/crossprofile/ICrossProfileApps.aidl b/core/java/android/content/pm/crossprofile/ICrossProfileApps.aidl
new file mode 100644
index 0000000..dd8d04f
--- /dev/null
+++ b/core/java/android/content/pm/crossprofile/ICrossProfileApps.aidl
@@ -0,0 +1,31 @@
+/*
+ * 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.content.pm.crossprofile;
+
+import android.content.ComponentName;
+import android.content.Intent;
+import android.graphics.Rect;
+import android.os.Bundle;
+import android.os.UserHandle;
+
+/**
+ * @hide
+ */
+interface ICrossProfileApps {
+ void startActivityAsUser(in String callingPackage, in ComponentName component, in Rect sourceBounds, in Bundle startActivityOptions, in UserHandle user);
+ List<UserHandle> getTargetUserProfiles(in String callingPackage);
+}
\ No newline at end of file
diff --git a/core/java/android/content/res/Configuration.java b/core/java/android/content/res/Configuration.java
index dfd3bbf..26efda1 100644
--- a/core/java/android/content/res/Configuration.java
+++ b/core/java/android/content/res/Configuration.java
@@ -2105,6 +2105,7 @@
break;
case DENSITY_DPI_NONE:
parts.add("nodpi");
+ break;
default:
parts.add(config.densityDpi + "dpi");
break;
diff --git a/core/java/android/database/sqlite/package.html b/core/java/android/database/sqlite/package.html
index 864a9bb..4d6ba28 100644
--- a/core/java/android/database/sqlite/package.html
+++ b/core/java/android/database/sqlite/package.html
@@ -20,6 +20,8 @@
<p>The version of SQLite depends on the version of Android. See the following table:
<table style="width:auto;">
<tr><th>Android API</th><th>SQLite Version</th></tr>
+ <tr><td>API 27</td><td>3.19</td></tr>
+ <tr><td>API 26</td><td>3.18</td></tr>
<tr><td>API 24</td><td>3.9</td></tr>
<tr><td>API 21</td><td>3.8</td></tr>
<tr><td>API 11</td><td>3.7</td></tr>
diff --git a/core/java/android/hardware/usb/AccessoryFilter.java b/core/java/android/hardware/usb/AccessoryFilter.java
new file mode 100644
index 0000000..d9b7c5b
--- /dev/null
+++ b/core/java/android/hardware/usb/AccessoryFilter.java
@@ -0,0 +1,145 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.usb;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.IOException;
+import java.util.Objects;
+
+/**
+ * This class is used to describe a USB accessory.
+ * When used in HashMaps all values must be specified,
+ * but wildcards can be used for any of the fields in
+ * the package meta-data.
+ *
+ * @hide
+ */
+public class AccessoryFilter {
+ // USB accessory manufacturer (or null for unspecified)
+ public final String mManufacturer;
+ // USB accessory model (or null for unspecified)
+ public final String mModel;
+ // USB accessory version (or null for unspecified)
+ public final String mVersion;
+
+ public AccessoryFilter(String manufacturer, String model, String version) {
+ mManufacturer = manufacturer;
+ mModel = model;
+ mVersion = version;
+ }
+
+ public AccessoryFilter(UsbAccessory accessory) {
+ mManufacturer = accessory.getManufacturer();
+ mModel = accessory.getModel();
+ mVersion = accessory.getVersion();
+ }
+
+ public static AccessoryFilter read(XmlPullParser parser)
+ throws XmlPullParserException, IOException {
+ String manufacturer = null;
+ String model = null;
+ String version = null;
+
+ int count = parser.getAttributeCount();
+ for (int i = 0; i < count; i++) {
+ String name = parser.getAttributeName(i);
+ String value = parser.getAttributeValue(i);
+
+ if ("manufacturer".equals(name)) {
+ manufacturer = value;
+ } else if ("model".equals(name)) {
+ model = value;
+ } else if ("version".equals(name)) {
+ version = value;
+ }
+ }
+ return new AccessoryFilter(manufacturer, model, version);
+ }
+
+ public void write(XmlSerializer serializer)throws IOException {
+ serializer.startTag(null, "usb-accessory");
+ if (mManufacturer != null) {
+ serializer.attribute(null, "manufacturer", mManufacturer);
+ }
+ if (mModel != null) {
+ serializer.attribute(null, "model", mModel);
+ }
+ if (mVersion != null) {
+ serializer.attribute(null, "version", mVersion);
+ }
+ serializer.endTag(null, "usb-accessory");
+ }
+
+ public boolean matches(UsbAccessory acc) {
+ if (mManufacturer != null && !acc.getManufacturer().equals(mManufacturer)) return false;
+ if (mModel != null && !acc.getModel().equals(mModel)) return false;
+ return !(mVersion != null && !acc.getVersion().equals(mVersion));
+ }
+
+ /**
+ * Is the accessories described {@code accessory} covered by this filter?
+ *
+ * @param accessory A filter describing the accessory
+ *
+ * @return {@code true} iff this the filter covers the accessory
+ */
+ public boolean contains(AccessoryFilter accessory) {
+ if (mManufacturer != null && !Objects.equals(accessory.mManufacturer, mManufacturer)) {
+ return false;
+ }
+ if (mModel != null && !Objects.equals(accessory.mModel, mModel)) return false;
+ return !(mVersion != null && !Objects.equals(accessory.mVersion, mVersion));
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ // can't compare if we have wildcard strings
+ if (mManufacturer == null || mModel == null || mVersion == null) {
+ return false;
+ }
+ if (obj instanceof AccessoryFilter) {
+ AccessoryFilter filter = (AccessoryFilter)obj;
+ return (mManufacturer.equals(filter.mManufacturer) &&
+ mModel.equals(filter.mModel) &&
+ mVersion.equals(filter.mVersion));
+ }
+ if (obj instanceof UsbAccessory) {
+ UsbAccessory accessory = (UsbAccessory)obj;
+ return (mManufacturer.equals(accessory.getManufacturer()) &&
+ mModel.equals(accessory.getModel()) &&
+ mVersion.equals(accessory.getVersion()));
+ }
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ return ((mManufacturer == null ? 0 : mManufacturer.hashCode()) ^
+ (mModel == null ? 0 : mModel.hashCode()) ^
+ (mVersion == null ? 0 : mVersion.hashCode()));
+ }
+
+ @Override
+ public String toString() {
+ return "AccessoryFilter[mManufacturer=\"" + mManufacturer +
+ "\", mModel=\"" + mModel +
+ "\", mVersion=\"" + mVersion + "\"]";
+ }
+}
diff --git a/core/java/android/hardware/usb/DeviceFilter.java b/core/java/android/hardware/usb/DeviceFilter.java
new file mode 100644
index 0000000..439c629
--- /dev/null
+++ b/core/java/android/hardware/usb/DeviceFilter.java
@@ -0,0 +1,313 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.usb;
+
+import android.util.Slog;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.IOException;
+import java.util.Objects;
+
+/**
+ * This class is used to describe a USB device.
+ * When used in HashMaps all values must be specified,
+ * but wildcards can be used for any of the fields in
+ * the package meta-data.
+ *
+ * @hide
+ */
+public class DeviceFilter {
+ private static final String TAG = DeviceFilter.class.getSimpleName();
+
+ // USB Vendor ID (or -1 for unspecified)
+ public final int mVendorId;
+ // USB Product ID (or -1 for unspecified)
+ public final int mProductId;
+ // USB device or interface class (or -1 for unspecified)
+ public final int mClass;
+ // USB device subclass (or -1 for unspecified)
+ public final int mSubclass;
+ // USB device protocol (or -1 for unspecified)
+ public final int mProtocol;
+ // USB device manufacturer name string (or null for unspecified)
+ public final String mManufacturerName;
+ // USB device product name string (or null for unspecified)
+ public final String mProductName;
+ // USB device serial number string (or null for unspecified)
+ public final String mSerialNumber;
+
+ public DeviceFilter(int vid, int pid, int clasz, int subclass, int protocol,
+ String manufacturer, String product, String serialnum) {
+ mVendorId = vid;
+ mProductId = pid;
+ mClass = clasz;
+ mSubclass = subclass;
+ mProtocol = protocol;
+ mManufacturerName = manufacturer;
+ mProductName = product;
+ mSerialNumber = serialnum;
+ }
+
+ public DeviceFilter(UsbDevice device) {
+ mVendorId = device.getVendorId();
+ mProductId = device.getProductId();
+ mClass = device.getDeviceClass();
+ mSubclass = device.getDeviceSubclass();
+ mProtocol = device.getDeviceProtocol();
+ mManufacturerName = device.getManufacturerName();
+ mProductName = device.getProductName();
+ mSerialNumber = device.getSerialNumber();
+ }
+
+ public static DeviceFilter read(XmlPullParser parser)
+ throws XmlPullParserException, IOException {
+ int vendorId = -1;
+ int productId = -1;
+ int deviceClass = -1;
+ int deviceSubclass = -1;
+ int deviceProtocol = -1;
+ String manufacturerName = null;
+ String productName = null;
+ String serialNumber = null;
+
+ int count = parser.getAttributeCount();
+ for (int i = 0; i < count; i++) {
+ String name = parser.getAttributeName(i);
+ String value = parser.getAttributeValue(i);
+ // Attribute values are ints or strings
+ if ("manufacturer-name".equals(name)) {
+ manufacturerName = value;
+ } else if ("product-name".equals(name)) {
+ productName = value;
+ } else if ("serial-number".equals(name)) {
+ serialNumber = value;
+ } else {
+ int intValue;
+ int radix = 10;
+ if (value != null && value.length() > 2 && value.charAt(0) == '0' &&
+ (value.charAt(1) == 'x' || value.charAt(1) == 'X')) {
+ // allow hex values starting with 0x or 0X
+ radix = 16;
+ value = value.substring(2);
+ }
+ try {
+ intValue = Integer.parseInt(value, radix);
+ } catch (NumberFormatException e) {
+ Slog.e(TAG, "invalid number for field " + name, e);
+ continue;
+ }
+ if ("vendor-id".equals(name)) {
+ vendorId = intValue;
+ } else if ("product-id".equals(name)) {
+ productId = intValue;
+ } else if ("class".equals(name)) {
+ deviceClass = intValue;
+ } else if ("subclass".equals(name)) {
+ deviceSubclass = intValue;
+ } else if ("protocol".equals(name)) {
+ deviceProtocol = intValue;
+ }
+ }
+ }
+ return new DeviceFilter(vendorId, productId,
+ deviceClass, deviceSubclass, deviceProtocol,
+ manufacturerName, productName, serialNumber);
+ }
+
+ public void write(XmlSerializer serializer) throws IOException {
+ serializer.startTag(null, "usb-device");
+ if (mVendorId != -1) {
+ serializer.attribute(null, "vendor-id", Integer.toString(mVendorId));
+ }
+ if (mProductId != -1) {
+ serializer.attribute(null, "product-id", Integer.toString(mProductId));
+ }
+ if (mClass != -1) {
+ serializer.attribute(null, "class", Integer.toString(mClass));
+ }
+ if (mSubclass != -1) {
+ serializer.attribute(null, "subclass", Integer.toString(mSubclass));
+ }
+ if (mProtocol != -1) {
+ serializer.attribute(null, "protocol", Integer.toString(mProtocol));
+ }
+ if (mManufacturerName != null) {
+ serializer.attribute(null, "manufacturer-name", mManufacturerName);
+ }
+ if (mProductName != null) {
+ serializer.attribute(null, "product-name", mProductName);
+ }
+ if (mSerialNumber != null) {
+ serializer.attribute(null, "serial-number", mSerialNumber);
+ }
+ serializer.endTag(null, "usb-device");
+ }
+
+ private boolean matches(int clasz, int subclass, int protocol) {
+ return ((mClass == -1 || clasz == mClass) &&
+ (mSubclass == -1 || subclass == mSubclass) &&
+ (mProtocol == -1 || protocol == mProtocol));
+ }
+
+ public boolean matches(UsbDevice device) {
+ if (mVendorId != -1 && device.getVendorId() != mVendorId) return false;
+ if (mProductId != -1 && device.getProductId() != mProductId) return false;
+ if (mManufacturerName != null && device.getManufacturerName() == null) return false;
+ if (mProductName != null && device.getProductName() == null) return false;
+ if (mSerialNumber != null && device.getSerialNumber() == null) return false;
+ if (mManufacturerName != null && device.getManufacturerName() != null &&
+ !mManufacturerName.equals(device.getManufacturerName())) return false;
+ if (mProductName != null && device.getProductName() != null &&
+ !mProductName.equals(device.getProductName())) return false;
+ if (mSerialNumber != null && device.getSerialNumber() != null &&
+ !mSerialNumber.equals(device.getSerialNumber())) return false;
+
+ // check device class/subclass/protocol
+ if (matches(device.getDeviceClass(), device.getDeviceSubclass(),
+ device.getDeviceProtocol())) return true;
+
+ // if device doesn't match, check the interfaces
+ int count = device.getInterfaceCount();
+ for (int i = 0; i < count; i++) {
+ UsbInterface intf = device.getInterface(i);
+ if (matches(intf.getInterfaceClass(), intf.getInterfaceSubclass(),
+ intf.getInterfaceProtocol())) return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * If the device described by {@code device} covered by this filter?
+ *
+ * @param device The device
+ *
+ * @return {@code true} iff this filter covers the {@code device}
+ */
+ public boolean contains(DeviceFilter device) {
+ // -1 and null means "match anything"
+
+ if (mVendorId != -1 && device.mVendorId != mVendorId) return false;
+ if (mProductId != -1 && device.mProductId != mProductId) return false;
+ if (mManufacturerName != null && !Objects.equals(mManufacturerName,
+ device.mManufacturerName)) {
+ return false;
+ }
+ if (mProductName != null && !Objects.equals(mProductName, device.mProductName)) {
+ return false;
+ }
+ if (mSerialNumber != null
+ && !Objects.equals(mSerialNumber, device.mSerialNumber)) {
+ return false;
+ }
+
+ // check device class/subclass/protocol
+ return matches(device.mClass, device.mSubclass, device.mProtocol);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ // can't compare if we have wildcard strings
+ if (mVendorId == -1 || mProductId == -1 ||
+ mClass == -1 || mSubclass == -1 || mProtocol == -1) {
+ return false;
+ }
+ if (obj instanceof DeviceFilter) {
+ DeviceFilter filter = (DeviceFilter)obj;
+
+ if (filter.mVendorId != mVendorId ||
+ filter.mProductId != mProductId ||
+ filter.mClass != mClass ||
+ filter.mSubclass != mSubclass ||
+ filter.mProtocol != mProtocol) {
+ return(false);
+ }
+ if ((filter.mManufacturerName != null &&
+ mManufacturerName == null) ||
+ (filter.mManufacturerName == null &&
+ mManufacturerName != null) ||
+ (filter.mProductName != null &&
+ mProductName == null) ||
+ (filter.mProductName == null &&
+ mProductName != null) ||
+ (filter.mSerialNumber != null &&
+ mSerialNumber == null) ||
+ (filter.mSerialNumber == null &&
+ mSerialNumber != null)) {
+ return(false);
+ }
+ if ((filter.mManufacturerName != null &&
+ mManufacturerName != null &&
+ !mManufacturerName.equals(filter.mManufacturerName)) ||
+ (filter.mProductName != null &&
+ mProductName != null &&
+ !mProductName.equals(filter.mProductName)) ||
+ (filter.mSerialNumber != null &&
+ mSerialNumber != null &&
+ !mSerialNumber.equals(filter.mSerialNumber))) {
+ return false;
+ }
+ return true;
+ }
+ if (obj instanceof UsbDevice) {
+ UsbDevice device = (UsbDevice)obj;
+ if (device.getVendorId() != mVendorId ||
+ device.getProductId() != mProductId ||
+ device.getDeviceClass() != mClass ||
+ device.getDeviceSubclass() != mSubclass ||
+ device.getDeviceProtocol() != mProtocol) {
+ return(false);
+ }
+ if ((mManufacturerName != null && device.getManufacturerName() == null) ||
+ (mManufacturerName == null && device.getManufacturerName() != null) ||
+ (mProductName != null && device.getProductName() == null) ||
+ (mProductName == null && device.getProductName() != null) ||
+ (mSerialNumber != null && device.getSerialNumber() == null) ||
+ (mSerialNumber == null && device.getSerialNumber() != null)) {
+ return(false);
+ }
+ if ((device.getManufacturerName() != null &&
+ !mManufacturerName.equals(device.getManufacturerName())) ||
+ (device.getProductName() != null &&
+ !mProductName.equals(device.getProductName())) ||
+ (device.getSerialNumber() != null &&
+ !mSerialNumber.equals(device.getSerialNumber()))) {
+ return false;
+ }
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ return (((mVendorId << 16) | mProductId) ^
+ ((mClass << 16) | (mSubclass << 8) | mProtocol));
+ }
+
+ @Override
+ public String toString() {
+ return "DeviceFilter[mVendorId=" + mVendorId + ",mProductId=" + mProductId +
+ ",mClass=" + mClass + ",mSubclass=" + mSubclass +
+ ",mProtocol=" + mProtocol + ",mManufacturerName=" + mManufacturerName +
+ ",mProductName=" + mProductName + ",mSerialNumber=" + mSerialNumber +
+ "]";
+ }
+}
diff --git a/core/java/android/net/IIpConnectivityMetrics.aidl b/core/java/android/net/IIpConnectivityMetrics.aidl
index 6f07b31..aeaf09d 100644
--- a/core/java/android/net/IIpConnectivityMetrics.aidl
+++ b/core/java/android/net/IIpConnectivityMetrics.aidl
@@ -30,11 +30,11 @@
int logEvent(in ConnectivityMetricsEvent event);
/**
- * At most one callback can be registered (by DevicePolicyManager).
+ * Callback can be registered by DevicePolicyManager or NetworkWatchlistService only.
* @return status {@code true} if registering/unregistering of the callback was successful,
* {@code false} otherwise (might happen if IIpConnectivityMetrics is not available,
* if it happens make sure you call it when the service is up in the caller)
*/
- boolean registerNetdEventCallback(in INetdEventCallback callback);
- boolean unregisterNetdEventCallback();
+ boolean addNetdEventCallback(in int callerType, in INetdEventCallback callback);
+ boolean removeNetdEventCallback(in int callerType);
}
diff --git a/core/java/android/net/INetdEventCallback.aidl b/core/java/android/net/INetdEventCallback.aidl
index 49436be..1fd9423 100644
--- a/core/java/android/net/INetdEventCallback.aidl
+++ b/core/java/android/net/INetdEventCallback.aidl
@@ -19,6 +19,10 @@
/** {@hide} */
oneway interface INetdEventCallback {
+ // Possible addNetdEventCallback callers.
+ const int CALLBACK_CALLER_DEVICE_POLICY = 0;
+ const int CALLBACK_CALLER_NETWORK_WATCHLIST = 1;
+
/**
* Reports a single DNS lookup function call.
* This method must not block or perform long-running operations.
diff --git a/core/java/android/net/MacAddress.java b/core/java/android/net/MacAddress.java
index e76d17d..f6a69ba 100644
--- a/core/java/android/net/MacAddress.java
+++ b/core/java/android/net/MacAddress.java
@@ -16,29 +16,128 @@
package android.net;
-import com.android.internal.annotations.VisibleForTesting;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.util.BitUtils;
import java.util.Arrays;
+import java.util.Random;
+import java.util.StringJoiner;
/**
+ * Represents a mac address.
+ *
* @hide
*/
-public final class MacAddress {
-
- // TODO: add isLocallyAssigned().
- // TODO: add getRandomAddress() factory method.
+public final class MacAddress implements Parcelable {
private static final int ETHER_ADDR_LEN = 6;
- private static final byte FF = (byte) 0xff;
- @VisibleForTesting
- static final byte[] ETHER_ADDR_BROADCAST = { FF, FF, FF, FF, FF, FF };
+ private static final byte[] ETHER_ADDR_BROADCAST = addr(0xff, 0xff, 0xff, 0xff, 0xff, 0xff);
+ /** The broadcast mac address. */
+ public static final MacAddress BROADCAST_ADDRESS = new MacAddress(ETHER_ADDR_BROADCAST);
+
+ /** The zero mac address. */
+ public static final MacAddress ALL_ZEROS_ADDRESS = new MacAddress(0);
+
+ /** Represents categories of mac addresses. */
public enum MacAddressType {
UNICAST,
MULTICAST,
BROADCAST;
}
+ private static final long VALID_LONG_MASK = BROADCAST_ADDRESS.mAddr;
+ private static final long LOCALLY_ASSIGNED_MASK = new MacAddress("2:0:0:0:0:0").mAddr;
+ private static final long MULTICAST_MASK = new MacAddress("1:0:0:0:0:0").mAddr;
+ private static final long OUI_MASK = new MacAddress("ff:ff:ff:0:0:0").mAddr;
+ private static final long NIC_MASK = new MacAddress("0:0:0:ff:ff:ff").mAddr;
+ private static final MacAddress BASE_ANDROID_MAC = new MacAddress("da:a1:19:0:0:0");
+
+ // Internal representation of the mac address as a single 8 byte long.
+ // The encoding scheme sets the two most significant bytes to 0. The 6 bytes of the
+ // mac address are encoded in the 6 least significant bytes of the long, where the first
+ // byte of the array is mapped to the 3rd highest logical byte of the long, the second
+ // byte of the array is mapped to the 4th highest logical byte of the long, and so on.
+ private final long mAddr;
+
+ private MacAddress(long addr) {
+ mAddr = addr;
+ }
+
+ /** Creates a MacAddress for the given byte representation. */
+ public MacAddress(byte[] addr) {
+ this(longAddrFromByteAddr(addr));
+ }
+
+ /** Creates a MacAddress for the given string representation. */
+ public MacAddress(String addr) {
+ this(longAddrFromByteAddr(byteAddrFromStringAddr(addr)));
+ }
+
+ /** Returns the MacAddressType of this MacAddress. */
+ public MacAddressType addressType() {
+ if (equals(BROADCAST_ADDRESS)) {
+ return MacAddressType.BROADCAST;
+ }
+ if (isMulticastAddress()) {
+ return MacAddressType.MULTICAST;
+ }
+ return MacAddressType.UNICAST;
+ }
+
+ /** Returns true if this MacAddress corresponds to a multicast address. */
+ public boolean isMulticastAddress() {
+ return (mAddr & MULTICAST_MASK) != 0;
+ }
+
+ /** Returns true if this MacAddress corresponds to a locally assigned address. */
+ public boolean isLocallyAssigned() {
+ return (mAddr & LOCALLY_ASSIGNED_MASK) != 0;
+ }
+
+ /** Returns a byte array representation of this MacAddress. */
+ public byte[] toByteArray() {
+ return byteAddrFromLongAddr(mAddr);
+ }
+
+ @Override
+ public String toString() {
+ return stringAddrFromByteAddr(byteAddrFromLongAddr(mAddr));
+ }
+
+ @Override
+ public int hashCode() {
+ return (int) ((mAddr >> 32) ^ mAddr);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ return (o instanceof MacAddress) && ((MacAddress) o).mAddr == mAddr;
+ }
+
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeLong(mAddr);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ public static final Parcelable.Creator<MacAddress> CREATOR =
+ new Parcelable.Creator<MacAddress>() {
+ public MacAddress createFromParcel(Parcel in) {
+ return new MacAddress(in.readLong());
+ }
+
+ public MacAddress[] newArray(int size) {
+ return new MacAddress[size];
+ }
+ };
+
/** Return true if the given byte array is not null and has the length of a mac address. */
public static boolean isMacAddress(byte[] addr) {
return addr != null && addr.length == ETHER_ADDR_LEN;
@@ -46,17 +145,130 @@
/**
* Return the MacAddressType of the mac address represented by the given byte array,
- * or null if the given byte array does not represent an mac address. */
+ * or null if the given byte array does not represent an mac address.
+ */
public static MacAddressType macAddressType(byte[] addr) {
if (!isMacAddress(addr)) {
return null;
}
- if (Arrays.equals(addr, ETHER_ADDR_BROADCAST)) {
- return MacAddressType.BROADCAST;
+ return new MacAddress(addr).addressType();
+ }
+
+ /** DOCME */
+ public static byte[] byteAddrFromStringAddr(String addr) {
+ if (addr == null) {
+ throw new IllegalArgumentException("cannot convert the null String");
}
- if ((addr[0] & 0x01) == 1) {
- return MacAddressType.MULTICAST;
+ String[] parts = addr.split(":");
+ if (parts.length != ETHER_ADDR_LEN) {
+ throw new IllegalArgumentException(addr + " was not a valid MAC address");
}
- return MacAddressType.UNICAST;
+ byte[] bytes = new byte[ETHER_ADDR_LEN];
+ for (int i = 0; i < ETHER_ADDR_LEN; i++) {
+ int x = Integer.valueOf(parts[i], 16);
+ if (x < 0 || 0xff < x) {
+ throw new IllegalArgumentException(addr + "was not a valid MAC address");
+ }
+ bytes[i] = (byte) x;
+ }
+ return bytes;
+ }
+
+ /** DOCME */
+ public static String stringAddrFromByteAddr(byte[] addr) {
+ if (!isMacAddress(addr)) {
+ return null;
+ }
+ StringJoiner j = new StringJoiner(":");
+ for (byte b : addr) {
+ j.add(Integer.toHexString(BitUtils.uint8(b)));
+ }
+ return j.toString();
+ }
+
+ /** @hide */
+ public static byte[] byteAddrFromLongAddr(long addr) {
+ byte[] bytes = new byte[ETHER_ADDR_LEN];
+ int index = ETHER_ADDR_LEN;
+ while (index-- > 0) {
+ bytes[index] = (byte) addr;
+ addr = addr >> 8;
+ }
+ return bytes;
+ }
+
+ /** @hide */
+ public static long longAddrFromByteAddr(byte[] addr) {
+ if (!isMacAddress(addr)) {
+ throw new IllegalArgumentException(
+ Arrays.toString(addr) + " was not a valid MAC address");
+ }
+ long longAddr = 0;
+ for (byte b : addr) {
+ longAddr = (longAddr << 8) + BitUtils.uint8(b);
+ }
+ return longAddr;
+ }
+
+ /** @hide */
+ public static long longAddrFromStringAddr(String addr) {
+ if (addr == null) {
+ throw new IllegalArgumentException("cannot convert the null String");
+ }
+ String[] parts = addr.split(":");
+ if (parts.length != ETHER_ADDR_LEN) {
+ throw new IllegalArgumentException(addr + " was not a valid MAC address");
+ }
+ long longAddr = 0;
+ int index = ETHER_ADDR_LEN;
+ while (index-- > 0) {
+ int x = Integer.valueOf(parts[index], 16);
+ if (x < 0 || 0xff < x) {
+ throw new IllegalArgumentException(addr + "was not a valid MAC address");
+ }
+ longAddr = x + (longAddr << 8);
+ }
+ return longAddr;
+ }
+
+ /** @hide */
+ public static String stringAddrFromLongAddr(long addr) {
+ addr = Long.reverseBytes(addr) >> 16;
+ StringJoiner j = new StringJoiner(":");
+ for (int i = 0; i < ETHER_ADDR_LEN; i++) {
+ j.add(Integer.toHexString((byte) addr));
+ addr = addr >> 8;
+ }
+ return j.toString();
+ }
+
+ /**
+ * Returns a randomely generated mac address with the Android OUI value "DA-A1-19".
+ * The locally assigned bit is always set to 1.
+ */
+ public static MacAddress getRandomAddress() {
+ return getRandomAddress(BASE_ANDROID_MAC, new Random());
+ }
+
+ /**
+ * Returns a randomely generated mac address using the given Random object and the same
+ * OUI values as the given MacAddress. The locally assigned bit is always set to 1.
+ */
+ public static MacAddress getRandomAddress(MacAddress base, Random r) {
+ long longAddr = (base.mAddr & OUI_MASK) | (NIC_MASK & r.nextLong()) | LOCALLY_ASSIGNED_MASK;
+ return new MacAddress(longAddr);
+ }
+
+ // Convenience function for working around the lack of byte literals.
+ private static byte[] addr(int... in) {
+ if (in.length != ETHER_ADDR_LEN) {
+ throw new IllegalArgumentException(Arrays.toString(in)
+ + " was not an array with length equal to " + ETHER_ADDR_LEN);
+ }
+ byte[] out = new byte[ETHER_ADDR_LEN];
+ for (int i = 0; i < ETHER_ADDR_LEN; i++) {
+ out[i] = (byte) in[i];
+ }
+ return out;
}
}
diff --git a/core/java/android/net/NetworkWatchlistManager.java b/core/java/android/net/NetworkWatchlistManager.java
new file mode 100644
index 0000000..42e43c8
--- /dev/null
+++ b/core/java/android/net/NetworkWatchlistManager.java
@@ -0,0 +1,75 @@
+/*
+ * 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.annotation.SystemService;
+import android.content.Context;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.util.Log;
+
+import com.android.internal.net.INetworkWatchlistManager;
+import com.android.internal.util.Preconditions;
+
+/**
+ * Class that manage network watchlist in system.
+ * @hide
+ */
+@SystemService(Context.NETWORK_WATCHLIST_SERVICE)
+public class NetworkWatchlistManager {
+
+ private static final String TAG = "NetworkWatchlistManager";
+ private static final String SHARED_MEMORY_TAG = "NETWORK_WATCHLIST_SHARED_MEMORY";
+
+ private final Context mContext;
+ private final INetworkWatchlistManager mNetworkWatchlistManager;
+
+ /**
+ * @hide
+ */
+ public NetworkWatchlistManager(Context context, INetworkWatchlistManager manager) {
+ mContext = context;
+ mNetworkWatchlistManager = manager;
+ }
+
+ /**
+ * @hide
+ */
+ public NetworkWatchlistManager(Context context) {
+ mContext = Preconditions.checkNotNull(context, "missing context");
+ mNetworkWatchlistManager = (INetworkWatchlistManager)
+ INetworkWatchlistManager.Stub.asInterface(
+ ServiceManager.getService(Context.NETWORK_WATCHLIST_SERVICE));
+ }
+
+ /**
+ * Report network watchlist records if necessary.
+ *
+ * Watchlist report process will run summarize records into a single report, then the
+ * report will be processed by differential privacy framework and store it on disk.
+ *
+ * @hide
+ */
+ public void reportWatchlistIfNecessary() {
+ try {
+ mNetworkWatchlistManager.reportWatchlistIfNecessary();
+ } catch (RemoteException e) {
+ Log.e(TAG, "Cannot report records", e);
+ e.rethrowFromSystemServer();
+ }
+ }
+}
diff --git a/core/java/android/net/SSLCertificateSocketFactory.java b/core/java/android/net/SSLCertificateSocketFactory.java
index 0b1569c..4817813 100644
--- a/core/java/android/net/SSLCertificateSocketFactory.java
+++ b/core/java/android/net/SSLCertificateSocketFactory.java
@@ -63,7 +63,12 @@
* This implementation does check the server's certificate hostname, but only
* for createSocket variants that specify a hostname. When using methods that
* use {@link InetAddress} or which return an unconnected socket, you MUST
- * verify the server's identity yourself to ensure a secure connection.</p>
+ * verify the server's identity yourself to ensure a secure connection.
+ *
+ * Refer to
+ * <a href="https://developer.android.com/training/articles/security-gms-provider.html">
+ * Updating Your Security Provider to Protect Against SSL Exploits</a>
+ * for further information.</p>
*
* <p>One way to verify the server's identity is to use
* {@link HttpsURLConnection#getDefaultHostnameVerifier()} to get a
diff --git a/core/java/android/os/Binder.java b/core/java/android/os/Binder.java
index 2bfb013..b5bcd02 100644
--- a/core/java/android/os/Binder.java
+++ b/core/java/android/os/Binder.java
@@ -193,6 +193,19 @@
}
/**
+ * Reset the given interface back to the default blocking behavior,
+ * reverting any changes made by {@link #allowBlocking(IBinder)}.
+ *
+ * @hide
+ */
+ public static IBinder defaultBlocking(IBinder binder) {
+ if (binder instanceof BinderProxy) {
+ ((BinderProxy) binder).mWarnOnBlocking = sWarnOnBlocking;
+ }
+ return binder;
+ }
+
+ /**
* Inherit the current {@link #allowBlocking(IBinder)} value from one given
* interface to another.
*
diff --git a/core/java/android/os/ParcelFileDescriptor.aidl b/core/java/android/os/ParcelFileDescriptor.aidl
index 5857aae..6bbd99e 100644
--- a/core/java/android/os/ParcelFileDescriptor.aidl
+++ b/core/java/android/os/ParcelFileDescriptor.aidl
@@ -17,4 +17,4 @@
package android.os;
-parcelable ParcelFileDescriptor;
+parcelable ParcelFileDescriptor cpp_header "android/os/parcel_file_descriptor.h";
diff --git a/core/java/android/os/ParcelUuid.aidl b/core/java/android/os/ParcelUuid.aidl
index f7e080a..6f36297 100644
--- a/core/java/android/os/ParcelUuid.aidl
+++ b/core/java/android/os/ParcelUuid.aidl
@@ -16,4 +16,4 @@
package android.os;
-parcelable ParcelUuid;
+parcelable ParcelUuid cpp_header "android/os/parcel_uuid.h";
diff --git a/core/java/android/os/StrictMode.java b/core/java/android/os/StrictMode.java
index d5820b6..f90604a 100644
--- a/core/java/android/os/StrictMode.java
+++ b/core/java/android/os/StrictMode.java
@@ -16,11 +16,11 @@
package android.os;
import android.animation.ValueAnimator;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.TestApi;
import android.app.ActivityManager;
import android.app.ActivityThread;
-import android.app.ApplicationErrorReport;
import android.app.IActivityManager;
import android.content.BroadcastReceiver;
import android.content.Context;
@@ -29,8 +29,6 @@
import android.content.pm.ApplicationInfo;
import android.net.TrafficStats;
import android.net.Uri;
-import android.os.StrictMode.ThreadPolicy;
-import android.os.StrictMode.VmPolicy;
import android.os.strictmode.CleartextNetworkViolation;
import android.os.strictmode.ContentUriWithoutPermissionViolation;
import android.os.strictmode.CustomViolation;
@@ -44,7 +42,7 @@
import android.os.strictmode.ResourceMismatchViolation;
import android.os.strictmode.ServiceConnectionLeakedViolation;
import android.os.strictmode.SqliteObjectLeakedViolation;
-import android.os.strictmode.UnbufferedIOViolation;
+import android.os.strictmode.UnbufferedIoViolation;
import android.os.strictmode.UntaggedSocketViolation;
import android.os.strictmode.Violation;
import android.os.strictmode.WebViewMethodCalledOnWrongThreadViolation;
@@ -75,6 +73,8 @@
import java.util.Arrays;
import java.util.Deque;
import java.util.HashMap;
+import java.util.concurrent.Executor;
+import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.atomic.AtomicInteger;
/**
@@ -163,12 +163,14 @@
private static final String CLEARTEXT_PROPERTY = "persist.sys.strictmode.clear";
/**
- * Quick feature-flag that can be used to disable the defaults provided by
- * {@link #initThreadDefaults(ApplicationInfo)} and
- * {@link #initVmDefaults(ApplicationInfo)}.
+ * Quick feature-flag that can be used to disable the defaults provided by {@link
+ * #initThreadDefaults(ApplicationInfo)} and {@link #initVmDefaults(ApplicationInfo)}.
*/
private static final boolean DISABLE = false;
+ // Only apply VM penalties for the same violation at this interval.
+ private static final long MIN_VM_INTERVAL_MS = 1000;
+
// Only log a duplicate stack trace to the logs every second.
private static final long MIN_LOG_INTERVAL_MS = 1000;
@@ -374,6 +376,28 @@
private static volatile ViolationLogger sLogger = LOGCAT_LOGGER;
+ private static final ThreadLocal<OnThreadViolationListener> sThreadViolationListener =
+ new ThreadLocal<>();
+ private static final ThreadLocal<Executor> sThreadViolationExecutor = new ThreadLocal<>();
+
+ /**
+ * When #{@link ThreadPolicy.Builder#penaltyListener} is enabled, the listener is called on the
+ * provided executor when a Thread violation occurs.
+ */
+ public interface OnThreadViolationListener {
+ /** Called on a thread policy violation. */
+ void onThreadViolation(Violation v);
+ }
+
+ /**
+ * When #{@link VmPolicy.Builder#penaltyListener} is enabled, the listener is called on the
+ * provided executor when a VM violation occurs.
+ */
+ public interface OnVmViolationListener {
+ /** Called on a VM policy violation. */
+ void onVmViolation(Violation v);
+ }
+
/** {@hide} */
@TestApi
public static void setViolationLogger(ViolationLogger listener) {
@@ -403,12 +427,16 @@
*/
public static final class ThreadPolicy {
/** The default, lax policy which doesn't catch anything. */
- public static final ThreadPolicy LAX = new ThreadPolicy(0);
+ public static final ThreadPolicy LAX = new ThreadPolicy(0, null, null);
final int mask;
+ final OnThreadViolationListener mListener;
+ final Executor mCallbackExecutor;
- private ThreadPolicy(int mask) {
+ private ThreadPolicy(int mask, OnThreadViolationListener listener, Executor executor) {
this.mask = mask;
+ mListener = listener;
+ mCallbackExecutor = executor;
}
@Override
@@ -436,6 +464,8 @@
*/
public static final class Builder {
private int mMask = 0;
+ private OnThreadViolationListener mListener;
+ private Executor mExecutor;
/**
* Create a Builder that detects nothing and has no violations. (but note that {@link
@@ -601,6 +631,20 @@
return enable(PENALTY_DROPBOX);
}
+ /**
+ * Call #{@link OnThreadViolationListener#onThreadViolation(Violation)} on specified
+ * executor every violation.
+ */
+ public Builder penaltyListener(
+ @NonNull OnThreadViolationListener listener, @NonNull Executor executor) {
+ if (executor == null) {
+ throw new NullPointerException("executor must not be null");
+ }
+ mListener = listener;
+ mExecutor = executor;
+ return this;
+ }
+
private Builder enable(int bit) {
mMask |= bit;
return this;
@@ -620,7 +664,8 @@
public ThreadPolicy build() {
// If there are detection bits set but no violation bits
// set, enable simple logging.
- if (mMask != 0
+ if (mListener == null
+ && mMask != 0
&& (mMask
& (PENALTY_DEATH
| PENALTY_LOG
@@ -629,7 +674,7 @@
== 0) {
penaltyLog();
}
- return new ThreadPolicy(mMask);
+ return new ThreadPolicy(mMask, mListener, mExecutor);
}
}
}
@@ -641,19 +686,27 @@
*/
public static final class VmPolicy {
/** The default, lax policy which doesn't catch anything. */
- public static final VmPolicy LAX = new VmPolicy(0, EMPTY_CLASS_LIMIT_MAP);
+ public static final VmPolicy LAX = new VmPolicy(0, EMPTY_CLASS_LIMIT_MAP, null, null);
final int mask;
+ final OnVmViolationListener mListener;
+ final Executor mCallbackExecutor;
// Map from class to max number of allowed instances in memory.
final HashMap<Class, Integer> classInstanceLimit;
- private VmPolicy(int mask, HashMap<Class, Integer> classInstanceLimit) {
+ private VmPolicy(
+ int mask,
+ HashMap<Class, Integer> classInstanceLimit,
+ OnVmViolationListener listener,
+ Executor executor) {
if (classInstanceLimit == null) {
throw new NullPointerException("classInstanceLimit == null");
}
this.mask = mask;
this.classInstanceLimit = classInstanceLimit;
+ mListener = listener;
+ mCallbackExecutor = executor;
}
@Override
@@ -681,6 +734,8 @@
*/
public static final class Builder {
private int mMask;
+ private OnVmViolationListener mListener;
+ private Executor mExecutor;
private HashMap<Class, Integer> mClassInstanceLimit; // null until needed
private boolean mClassInstanceLimitNeedCow = false; // need copy-on-write
@@ -694,6 +749,8 @@
mMask = base.mask;
mClassInstanceLimitNeedCow = true;
mClassInstanceLimit = base.classInstanceLimit;
+ mListener = base.mListener;
+ mExecutor = base.mCallbackExecutor;
}
/**
@@ -910,6 +967,19 @@
return enable(PENALTY_DROPBOX);
}
+ /**
+ * Call #{@link OnVmViolationListener#onVmViolation(Violation)} on every violation.
+ */
+ public Builder penaltyListener(
+ @NonNull OnVmViolationListener listener, @NonNull Executor executor) {
+ if (executor == null) {
+ throw new NullPointerException("executor must not be null");
+ }
+ mListener = listener;
+ mExecutor = executor;
+ return this;
+ }
+
private Builder enable(int bit) {
mMask |= bit;
return this;
@@ -929,7 +999,8 @@
public VmPolicy build() {
// If there are detection bits set but no violation bits
// set, enable simple logging.
- if (mMask != 0
+ if (mListener == null
+ && mMask != 0
&& (mMask
& (PENALTY_DEATH
| PENALTY_LOG
@@ -940,7 +1011,9 @@
}
return new VmPolicy(
mMask,
- mClassInstanceLimit != null ? mClassInstanceLimit : EMPTY_CLASS_LIMIT_MAP);
+ mClassInstanceLimit != null ? mClassInstanceLimit : EMPTY_CLASS_LIMIT_MAP,
+ mListener,
+ mExecutor);
}
}
}
@@ -973,6 +1046,8 @@
*/
public static void setThreadPolicy(final ThreadPolicy policy) {
setThreadPolicyMask(policy.mask);
+ sThreadViolationListener.set(policy.mListener);
+ sThreadViolationExecutor.set(policy.mCallbackExecutor);
}
/** @hide */
@@ -1029,7 +1104,10 @@
// introduce VmPolicy cleanly) but this isn't particularly
// optimal for users who might call this method often. This
// should be in a thread-local and not allocate on each call.
- return new ThreadPolicy(getThreadPolicyMask());
+ return new ThreadPolicy(
+ getThreadPolicyMask(),
+ sThreadViolationListener.get(),
+ sThreadViolationExecutor.get());
}
/**
@@ -1042,7 +1120,10 @@
* end of a block
*/
public static ThreadPolicy allowThreadDiskWrites() {
- return new ThreadPolicy(allowThreadDiskWritesMask());
+ return new ThreadPolicy(
+ allowThreadDiskWritesMask(),
+ sThreadViolationListener.get(),
+ sThreadViolationExecutor.get());
}
/** @hide */
@@ -1063,7 +1144,10 @@
* @return the old policy, to be passed to setThreadPolicy to restore the policy.
*/
public static ThreadPolicy allowThreadDiskReads() {
- return new ThreadPolicy(allowThreadDiskReadsMask());
+ return new ThreadPolicy(
+ allowThreadDiskReadsMask(),
+ sThreadViolationListener.get(),
+ sThreadViolationExecutor.get());
}
/** @hide */
@@ -1076,16 +1160,27 @@
return oldPolicyMask;
}
+ private static ThreadPolicy allowThreadViolations() {
+ ThreadPolicy oldPolicy = getThreadPolicy();
+ setThreadPolicyMask(0);
+ return oldPolicy;
+ }
+
+ private static VmPolicy allowVmViolations() {
+ VmPolicy oldPolicy = getVmPolicy();
+ sVmPolicy = VmPolicy.LAX;
+ return oldPolicy;
+ }
+
/**
- * Determine if the given app is "bundled" as part of the system image.
- * These bundled apps are developed in lock-step with the OS, and they
- * aren't updated outside of an OTA, so we want to chase any
- * {@link StrictMode} regressions by enabling detection when running on
- * {@link Build#IS_USERDEBUG} or {@link Build#IS_ENG} builds.
- * <p>
- * Unbundled apps included in the system image are expected to detect and
- * triage their own {@link StrictMode} issues separate from the OS release
- * process, which is why we don't enable them here.
+ * Determine if the given app is "bundled" as part of the system image. These bundled apps are
+ * developed in lock-step with the OS, and they aren't updated outside of an OTA, so we want to
+ * chase any {@link StrictMode} regressions by enabling detection when running on {@link
+ * Build#IS_USERDEBUG} or {@link Build#IS_ENG} builds.
+ *
+ * <p>Unbundled apps included in the system image are expected to detect and triage their own
+ * {@link StrictMode} issues separate from the OS release process, which is why we don't enable
+ * them here.
*
* @hide
*/
@@ -1122,8 +1217,8 @@
*/
public static void initThreadDefaults(ApplicationInfo ai) {
final ThreadPolicy.Builder builder = new ThreadPolicy.Builder();
- final int targetSdkVersion = (ai != null) ? ai.targetSdkVersion
- : Build.VERSION_CODES.CUR_DEVELOPMENT;
+ final int targetSdkVersion =
+ (ai != null) ? ai.targetSdkVersion : Build.VERSION_CODES.CUR_DEVELOPMENT;
// Starting in HC, we don't allow network usage on the main thread
if (targetSdkVersion >= Build.VERSION_CODES.HONEYCOMB) {
@@ -1162,8 +1257,8 @@
*/
public static void initVmDefaults(ApplicationInfo ai) {
final VmPolicy.Builder builder = new VmPolicy.Builder();
- final int targetSdkVersion = (ai != null) ? ai.targetSdkVersion
- : Build.VERSION_CODES.CUR_DEVELOPMENT;
+ final int targetSdkVersion =
+ (ai != null) ? ai.targetSdkVersion : Build.VERSION_CODES.CUR_DEVELOPMENT;
// Starting in N, we don't allow file:// Uri exposure
if (targetSdkVersion >= Build.VERSION_CODES.N) {
@@ -1204,7 +1299,9 @@
sVmPolicy.mask
| DETECT_VM_FILE_URI_EXPOSURE
| PENALTY_DEATH_ON_FILE_URI_EXPOSURE,
- sVmPolicy.classInstanceLimit);
+ sVmPolicy.classInstanceLimit,
+ sVmPolicy.mListener,
+ sVmPolicy.mCallbackExecutor);
}
/**
@@ -1219,7 +1316,9 @@
sVmPolicy.mask
& ~(DETECT_VM_FILE_URI_EXPOSURE
| PENALTY_DEATH_ON_FILE_URI_EXPOSURE),
- sVmPolicy.classInstanceLimit);
+ sVmPolicy.classInstanceLimit,
+ sVmPolicy.mListener,
+ sVmPolicy.mCallbackExecutor);
}
/**
@@ -1340,7 +1439,7 @@
if (tooManyViolationsThisLoop()) {
return;
}
- startHandlingViolationException(new UnbufferedIOViolation());
+ startHandlingViolationException(new UnbufferedIoViolation());
}
// Part of BlockGuard.Policy interface:
@@ -1409,7 +1508,7 @@
// go into this immediate mode?
if (looper == null || (info.mPolicy & THREAD_PENALTY_MASK) == PENALTY_DEATH) {
info.durationMillis = -1; // unknown (redundant, already set)
- handleViolation(info);
+ onThreadPolicyViolation(info);
return;
}
@@ -1447,30 +1546,28 @@
THREAD_HANDLER
.get()
.postAtFrontOfQueue(
- new Runnable() {
- public void run() {
- long loopFinishTime = SystemClock.uptimeMillis();
+ () -> {
+ long loopFinishTime = SystemClock.uptimeMillis();
- // Note: we do this early, before handling the
- // violation below, as handling the violation
- // may include PENALTY_DEATH and we don't want
- // to keep the red border on.
- if (windowManager != null) {
- try {
- windowManager.showStrictModeViolation(false);
- } catch (RemoteException unused) {
- }
+ // Note: we do this early, before handling the
+ // violation below, as handling the violation
+ // may include PENALTY_DEATH and we don't want
+ // to keep the red border on.
+ if (windowManager != null) {
+ try {
+ windowManager.showStrictModeViolation(false);
+ } catch (RemoteException unused) {
}
-
- for (int n = 0; n < records.size(); ++n) {
- ViolationInfo v = records.get(n);
- v.violationNumThisLoop = n + 1;
- v.durationMillis =
- (int) (loopFinishTime - v.violationUptimeMillis);
- handleViolation(v);
- }
- records.clear();
}
+
+ for (int n = 0; n < records.size(); ++n) {
+ ViolationInfo v = records.get(n);
+ v.violationNumThisLoop = n + 1;
+ v.durationMillis =
+ (int) (loopFinishTime - v.violationUptimeMillis);
+ onThreadPolicyViolation(v);
+ }
+ records.clear();
});
}
@@ -1479,13 +1576,13 @@
// violation fired and now (after the violating code ran) due
// to people who push/pop temporary policy in regions of code,
// hence the policy being passed around.
- void handleViolation(final ViolationInfo info) {
- if (LOG_V) Log.d(TAG, "handleViolation; policy=" + info.mPolicy);
+ void onThreadPolicyViolation(final ViolationInfo info) {
+ if (LOG_V) Log.d(TAG, "onThreadPolicyViolation; policy=" + info.mPolicy);
if (info.penaltyEnabled(PENALTY_GATHER)) {
ArrayList<ViolationInfo> violations = gatheredViolations.get();
if (violations == null) {
- violations = new ArrayList<ViolationInfo>(1);
+ violations = new ArrayList<>(1);
gatheredViolations.set(violations);
}
for (ViolationInfo previous : violations) {
@@ -1519,6 +1616,8 @@
sLogger.log(info);
}
+ final Violation violation = info.mViolation;
+
// The violationMaskSubset, passed to ActivityManager, is a
// subset of the original StrictMode policy bitmask, with
// only the bit violated and penalty bits to be executed
@@ -1552,15 +1651,32 @@
}
if ((info.getPolicyMask() & PENALTY_DEATH) != 0) {
- executeDeathPenalty(info);
+ throw new RuntimeException("StrictMode ThreadPolicy violation", violation);
+ }
+
+ // penaltyDeath will cause penaltyCallback to no-op since we cannot guarantee the
+ // executor finishes before crashing.
+ final OnThreadViolationListener listener = sThreadViolationListener.get();
+ final Executor executor = sThreadViolationExecutor.get();
+ if (listener != null && executor != null) {
+ try {
+ executor.execute(
+ () -> {
+ // Lift violated policy to prevent infinite recursion.
+ ThreadPolicy oldPolicy = allowThreadViolations();
+ try {
+ listener.onThreadViolation(violation);
+ } finally {
+ setThreadPolicy(oldPolicy);
+ }
+ });
+ } catch (RejectedExecutionException e) {
+ Log.e(TAG, "ThreadPolicy penaltyCallback failed", e);
+ }
}
}
}
- private static void executeDeathPenalty(ViolationInfo info) {
- throw new RuntimeException("StrictMode death penalty", info.mViolation);
- }
-
/**
* In the common case, as set by conditionallyEnableDebugLogging, we're just dropboxing any
* violations but not showing a dialog, not loggging, and not killing the process. In these
@@ -1736,9 +1852,8 @@
* #setThreadPolicy}.
*/
public static void enableDefaults() {
- StrictMode.setThreadPolicy(
- new StrictMode.ThreadPolicy.Builder().detectAll().penaltyLog().build());
- StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder().detectAll().penaltyLog().build());
+ setThreadPolicy(new StrictMode.ThreadPolicy.Builder().detectAll().penaltyLog().build());
+ setVmPolicy(new StrictMode.VmPolicy.Builder().detectAll().penaltyLog().build());
}
/** @hide */
@@ -1857,11 +1972,11 @@
}
/** @hide */
- public static void onVmPolicyViolation(Violation originStack, boolean forceDeath) {
+ public static void onVmPolicyViolation(Violation violation, boolean forceDeath) {
final boolean penaltyDropbox = (sVmPolicy.mask & PENALTY_DROPBOX) != 0;
final boolean penaltyDeath = ((sVmPolicy.mask & PENALTY_DEATH) != 0) || forceDeath;
final boolean penaltyLog = (sVmPolicy.mask & PENALTY_LOG) != 0;
- final ViolationInfo info = new ViolationInfo(originStack, sVmPolicy.mask);
+ final ViolationInfo info = new ViolationInfo(violation, sVmPolicy.mask);
// Erase stuff not relevant for process-wide violations
info.numAnimationsRunning = 0;
@@ -1870,37 +1985,37 @@
final Integer fingerprint = info.hashCode();
final long now = SystemClock.uptimeMillis();
- long lastViolationTime = 0;
+ long lastViolationTime;
long timeSinceLastViolationMillis = Long.MAX_VALUE;
synchronized (sLastVmViolationTime) {
if (sLastVmViolationTime.containsKey(fingerprint)) {
lastViolationTime = sLastVmViolationTime.get(fingerprint);
timeSinceLastViolationMillis = now - lastViolationTime;
}
- if (timeSinceLastViolationMillis > MIN_LOG_INTERVAL_MS) {
+ if (timeSinceLastViolationMillis > MIN_VM_INTERVAL_MS) {
sLastVmViolationTime.put(fingerprint, now);
}
}
-
- if (penaltyLog && sLogger != null) {
- sLogger.log(info);
+ if (timeSinceLastViolationMillis <= MIN_VM_INTERVAL_MS) {
+ // Rate limit all penalties.
+ return;
}
- if (penaltyLog && timeSinceLastViolationMillis > MIN_LOG_INTERVAL_MS) {
+
+ if (penaltyLog && sLogger != null && timeSinceLastViolationMillis > MIN_LOG_INTERVAL_MS) {
sLogger.log(info);
}
int violationMaskSubset = PENALTY_DROPBOX | (ALL_VM_DETECT_BITS & sVmPolicy.mask);
- if (penaltyDropbox && !penaltyDeath) {
- // Common case for userdebug/eng builds. If no death and
- // just dropboxing, we can do the ActivityManager call
- // asynchronously.
- dropboxViolationAsync(violationMaskSubset, info);
- return;
- }
-
- if (penaltyDropbox && lastViolationTime == 0) {
- handleApplicationStrictModeViolation(violationMaskSubset, info);
+ if (penaltyDropbox) {
+ if (penaltyDeath) {
+ handleApplicationStrictModeViolation(violationMaskSubset, info);
+ } else {
+ // Common case for userdebug/eng builds. If no death and
+ // just dropboxing, we can do the ActivityManager call
+ // asynchronously.
+ dropboxViolationAsync(violationMaskSubset, info);
+ }
}
if (penaltyDeath) {
@@ -1908,6 +2023,26 @@
Process.killProcess(Process.myPid());
System.exit(10);
}
+
+ // If penaltyDeath, we can't guarantee this callback finishes before the process dies for
+ // all executors. penaltyDeath supersedes penaltyCallback.
+ if (sVmPolicy.mListener != null && sVmPolicy.mCallbackExecutor != null) {
+ final OnVmViolationListener listener = sVmPolicy.mListener;
+ try {
+ sVmPolicy.mCallbackExecutor.execute(
+ () -> {
+ // Lift violated policy to prevent infinite recursion.
+ VmPolicy oldPolicy = allowVmViolations();
+ try {
+ listener.onVmViolation(violation);
+ } finally {
+ setVmPolicy(oldPolicy);
+ }
+ });
+ } catch (RejectedExecutionException e) {
+ Log.e(TAG, "VmPolicy penaltyCallback failed", e);
+ }
+ }
}
/** Called from Parcel.writeNoException() */
@@ -2407,7 +2542,7 @@
return DETECT_CUSTOM;
} else if (mViolation instanceof ResourceMismatchViolation) {
return DETECT_RESOURCE_MISMATCH;
- } else if (mViolation instanceof UnbufferedIOViolation) {
+ } else if (mViolation instanceof UnbufferedIoViolation) {
return DETECT_UNBUFFERED_IO;
} else if (mViolation instanceof SqliteObjectLeakedViolation) {
return DETECT_VM_CURSOR_LEAKS;
diff --git a/core/java/android/os/SystemClock.java b/core/java/android/os/SystemClock.java
index b3d76d7..c52c22d 100644
--- a/core/java/android/os/SystemClock.java
+++ b/core/java/android/os/SystemClock.java
@@ -16,12 +16,18 @@
package android.os;
+import android.annotation.NonNull;
import android.app.IAlarmManager;
import android.content.Context;
import android.util.Slog;
import dalvik.annotation.optimization.CriticalNative;
+import java.time.Clock;
+import java.time.Instant;
+import java.time.ZoneId;
+import java.time.ZoneOffset;
+
/**
* Core timekeeping facilities.
*
@@ -168,6 +174,31 @@
native public static long uptimeMillis();
/**
+ * Return {@link Clock} that starts at system boot, not counting time spent
+ * in deep sleep.
+ */
+ public static @NonNull Clock uptimeMillisClock() {
+ return new Clock() {
+ @Override
+ public ZoneId getZone() {
+ return ZoneOffset.UTC;
+ }
+ @Override
+ public Clock withZone(ZoneId zone) {
+ throw new UnsupportedOperationException();
+ }
+ @Override
+ public long millis() {
+ return SystemClock.uptimeMillis();
+ }
+ @Override
+ public Instant instant() {
+ return Instant.ofEpochMilli(millis());
+ }
+ };
+ }
+
+ /**
* Returns milliseconds since boot, including time spent in sleep.
*
* @return elapsed milliseconds since boot.
@@ -176,6 +207,31 @@
native public static long elapsedRealtime();
/**
+ * Return {@link Clock} that starts at system boot, including time spent in
+ * sleep.
+ */
+ public static @NonNull Clock elapsedRealtimeClock() {
+ return new Clock() {
+ @Override
+ public ZoneId getZone() {
+ return ZoneOffset.UTC;
+ }
+ @Override
+ public Clock withZone(ZoneId zone) {
+ throw new UnsupportedOperationException();
+ }
+ @Override
+ public long millis() {
+ return SystemClock.elapsedRealtime();
+ }
+ @Override
+ public Instant instant() {
+ return Instant.ofEpochMilli(millis());
+ }
+ };
+ }
+
+ /**
* Returns nanoseconds since boot, including time spent in sleep.
*
* @return elapsed nanoseconds since boot.
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index 95d22ba..22967af 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -140,6 +140,18 @@
public static final String DISALLOW_CONFIG_WIFI = "no_config_wifi";
/**
+ * Specifies if a user is disallowed from changing the device
+ * language. The default value is <code>false</code>.
+ *
+ * <p>Key for user restrictions.
+ * <p>Type: Boolean
+ * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
+ * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
+ * @see #getUserRestrictions()
+ */
+ public static final String DISALLOW_CONFIG_LOCALE = "no_config_locale";
+
+ /**
* Specifies if a user is disallowed from installing applications.
* The default value is <code>false</code>.
*
diff --git a/core/java/android/os/strictmode/CleartextNetworkViolation.java b/core/java/android/os/strictmode/CleartextNetworkViolation.java
index 893780d..6a0d381 100644
--- a/core/java/android/os/strictmode/CleartextNetworkViolation.java
+++ b/core/java/android/os/strictmode/CleartextNetworkViolation.java
@@ -15,8 +15,8 @@
*/
package android.os.strictmode;
-/** @hide */
public final class CleartextNetworkViolation extends Violation {
+ /** @hide */
public CleartextNetworkViolation(String msg) {
super(msg);
}
diff --git a/core/java/android/os/strictmode/ContentUriWithoutPermissionViolation.java b/core/java/android/os/strictmode/ContentUriWithoutPermissionViolation.java
index 017c4f9..e78dc79 100644
--- a/core/java/android/os/strictmode/ContentUriWithoutPermissionViolation.java
+++ b/core/java/android/os/strictmode/ContentUriWithoutPermissionViolation.java
@@ -17,8 +17,8 @@
import android.net.Uri;
-/** @hide */
public final class ContentUriWithoutPermissionViolation extends Violation {
+ /** @hide */
public ContentUriWithoutPermissionViolation(Uri uri, String location) {
super(
uri
diff --git a/core/java/android/os/strictmode/CustomViolation.java b/core/java/android/os/strictmode/CustomViolation.java
index bc1656d..d4ad067 100644
--- a/core/java/android/os/strictmode/CustomViolation.java
+++ b/core/java/android/os/strictmode/CustomViolation.java
@@ -15,8 +15,8 @@
*/
package android.os.strictmode;
-/** @hide */
public final class CustomViolation extends Violation {
+ /** @hide */
public CustomViolation(String name) {
super(name);
}
diff --git a/core/java/android/os/strictmode/DiskReadViolation.java b/core/java/android/os/strictmode/DiskReadViolation.java
index 2edd33e..fad32db 100644
--- a/core/java/android/os/strictmode/DiskReadViolation.java
+++ b/core/java/android/os/strictmode/DiskReadViolation.java
@@ -15,8 +15,8 @@
*/
package android.os.strictmode;
-/** @hide */
public final class DiskReadViolation extends Violation {
+ /** @hide */
public DiskReadViolation() {
super(null);
}
diff --git a/core/java/android/os/strictmode/DiskWriteViolation.java b/core/java/android/os/strictmode/DiskWriteViolation.java
index 6465718..cb9ca38 100644
--- a/core/java/android/os/strictmode/DiskWriteViolation.java
+++ b/core/java/android/os/strictmode/DiskWriteViolation.java
@@ -15,8 +15,8 @@
*/
package android.os.strictmode;
-/** @hide */
public final class DiskWriteViolation extends Violation {
+ /** @hide */
public DiskWriteViolation() {
super(null);
}
diff --git a/core/java/android/os/strictmode/FileUriExposedViolation.java b/core/java/android/os/strictmode/FileUriExposedViolation.java
index 5f71ee5..e3e6f83 100644
--- a/core/java/android/os/strictmode/FileUriExposedViolation.java
+++ b/core/java/android/os/strictmode/FileUriExposedViolation.java
@@ -15,8 +15,8 @@
*/
package android.os.strictmode;
-/** @hide */
public final class FileUriExposedViolation extends Violation {
+ /** @hide */
public FileUriExposedViolation(String msg) {
super(msg);
}
diff --git a/core/java/android/os/strictmode/InstanceCountViolation.java b/core/java/android/os/strictmode/InstanceCountViolation.java
index d3da800..9ee2c8e 100644
--- a/core/java/android/os/strictmode/InstanceCountViolation.java
+++ b/core/java/android/os/strictmode/InstanceCountViolation.java
@@ -15,7 +15,6 @@
*/
package android.os.strictmode;
-/** @hide */
public class InstanceCountViolation extends Violation {
private final long mInstances;
@@ -24,6 +23,7 @@
"android.os.StrictMode", "setClassInstanceLimit", "StrictMode.java", 1)
};
+ /** @hide */
public InstanceCountViolation(Class klass, long instances, int limit) {
super(klass.toString() + "; instances=" + instances + "; limit=" + limit);
setStackTrace(FAKE_STACK);
diff --git a/core/java/android/os/strictmode/IntentReceiverLeakedViolation.java b/core/java/android/os/strictmode/IntentReceiverLeakedViolation.java
index 1d1dfc0..f416c94 100644
--- a/core/java/android/os/strictmode/IntentReceiverLeakedViolation.java
+++ b/core/java/android/os/strictmode/IntentReceiverLeakedViolation.java
@@ -15,8 +15,8 @@
*/
package android.os.strictmode;
-/** @hide */
public final class IntentReceiverLeakedViolation extends Violation {
+ /** @hide */
public IntentReceiverLeakedViolation(Throwable originStack) {
super(null);
setStackTrace(originStack.getStackTrace());
diff --git a/core/java/android/os/strictmode/LeakedClosableViolation.java b/core/java/android/os/strictmode/LeakedClosableViolation.java
index de12533..c795a6b 100644
--- a/core/java/android/os/strictmode/LeakedClosableViolation.java
+++ b/core/java/android/os/strictmode/LeakedClosableViolation.java
@@ -15,8 +15,8 @@
*/
package android.os.strictmode;
-/** @hide */
public final class LeakedClosableViolation extends Violation {
+ /** @hide */
public LeakedClosableViolation(String message, Throwable allocationSite) {
super(message);
initCause(allocationSite);
diff --git a/core/java/android/os/strictmode/NetworkViolation.java b/core/java/android/os/strictmode/NetworkViolation.java
index 637d0b6..abcf009 100644
--- a/core/java/android/os/strictmode/NetworkViolation.java
+++ b/core/java/android/os/strictmode/NetworkViolation.java
@@ -15,8 +15,8 @@
*/
package android.os.strictmode;
-/** @hide */
public final class NetworkViolation extends Violation {
+ /** @hide */
public NetworkViolation() {
super(null);
}
diff --git a/core/java/android/os/strictmode/ResourceMismatchViolation.java b/core/java/android/os/strictmode/ResourceMismatchViolation.java
index 01ae7f8..97c4499 100644
--- a/core/java/android/os/strictmode/ResourceMismatchViolation.java
+++ b/core/java/android/os/strictmode/ResourceMismatchViolation.java
@@ -15,8 +15,8 @@
*/
package android.os.strictmode;
-/** @hide */
public final class ResourceMismatchViolation extends Violation {
+ /** @hide */
public ResourceMismatchViolation(Object tag) {
super(tag.toString());
}
diff --git a/core/java/android/os/strictmode/ServiceConnectionLeakedViolation.java b/core/java/android/os/strictmode/ServiceConnectionLeakedViolation.java
index 5bab3bd..2d6b58f 100644
--- a/core/java/android/os/strictmode/ServiceConnectionLeakedViolation.java
+++ b/core/java/android/os/strictmode/ServiceConnectionLeakedViolation.java
@@ -15,8 +15,8 @@
*/
package android.os.strictmode;
-/** @hide */
public final class ServiceConnectionLeakedViolation extends Violation {
+ /** @hide */
public ServiceConnectionLeakedViolation(Throwable originStack) {
super(null);
setStackTrace(originStack.getStackTrace());
diff --git a/core/java/android/os/strictmode/SqliteObjectLeakedViolation.java b/core/java/android/os/strictmode/SqliteObjectLeakedViolation.java
index 8360088..0200220 100644
--- a/core/java/android/os/strictmode/SqliteObjectLeakedViolation.java
+++ b/core/java/android/os/strictmode/SqliteObjectLeakedViolation.java
@@ -15,9 +15,9 @@
*/
package android.os.strictmode;
-/** @hide */
public final class SqliteObjectLeakedViolation extends Violation {
+ /** @hide */
public SqliteObjectLeakedViolation(String message, Throwable originStack) {
super(message);
initCause(originStack);
diff --git a/core/java/android/os/strictmode/UnbufferedIOViolation.java b/core/java/android/os/strictmode/UnbufferedIoViolation.java
similarity index 69%
rename from core/java/android/os/strictmode/UnbufferedIOViolation.java
rename to core/java/android/os/strictmode/UnbufferedIoViolation.java
index 571ba50..a5c326d 100644
--- a/core/java/android/os/strictmode/UnbufferedIOViolation.java
+++ b/core/java/android/os/strictmode/UnbufferedIoViolation.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2017 The Android Open Source Project
+ * Copyright 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -15,9 +15,14 @@
*/
package android.os.strictmode;
-/** @hide */
-public final class UnbufferedIOViolation extends Violation {
- public UnbufferedIOViolation() {
+import android.os.StrictMode.ThreadPolicy.Builder;
+
+/**
+ * See #{@link Builder#detectUnbufferedIo()}
+ */
+public final class UnbufferedIoViolation extends Violation {
+ /** @hide */
+ public UnbufferedIoViolation() {
super(null);
}
}
diff --git a/core/java/android/os/strictmode/UntaggedSocketViolation.java b/core/java/android/os/strictmode/UntaggedSocketViolation.java
index 9752753..836a8b9 100644
--- a/core/java/android/os/strictmode/UntaggedSocketViolation.java
+++ b/core/java/android/os/strictmode/UntaggedSocketViolation.java
@@ -15,13 +15,13 @@
*/
package android.os.strictmode;
-/** @hide */
public final class UntaggedSocketViolation extends Violation {
/** @hide */
public static final String MESSAGE =
"Untagged socket detected; use"
+ " TrafficStats.setThreadSocketTag() to track all network usage";
+ /** @hide */
public UntaggedSocketViolation() {
super(MESSAGE);
}
diff --git a/core/java/android/os/strictmode/Violation.java b/core/java/android/os/strictmode/Violation.java
index ebae7fc..31c7d58 100644
--- a/core/java/android/os/strictmode/Violation.java
+++ b/core/java/android/os/strictmode/Violation.java
@@ -16,13 +16,9 @@
package android.os.strictmode;
-/**
- * Root class for all StrictMode violations.
- *
- * @hide
- */
+/** Root class for all StrictMode violations. */
public abstract class Violation extends Throwable {
- protected Violation(String message) {
+ Violation(String message) {
super(message);
}
}
diff --git a/core/java/android/os/strictmode/WebViewMethodCalledOnWrongThreadViolation.java b/core/java/android/os/strictmode/WebViewMethodCalledOnWrongThreadViolation.java
index d4c557a..c328d14 100644
--- a/core/java/android/os/strictmode/WebViewMethodCalledOnWrongThreadViolation.java
+++ b/core/java/android/os/strictmode/WebViewMethodCalledOnWrongThreadViolation.java
@@ -15,8 +15,8 @@
*/
package android.os.strictmode;
-/** @hide */
public final class WebViewMethodCalledOnWrongThreadViolation extends Violation {
+ /** @hide */
public WebViewMethodCalledOnWrongThreadViolation(Throwable originStack) {
super(null);
setStackTrace(originStack.getStackTrace());
diff --git a/core/java/android/preference/PreferenceFragment.java b/core/java/android/preference/PreferenceFragment.java
index 73fa01e..4c556ef 100644
--- a/core/java/android/preference/PreferenceFragment.java
+++ b/core/java/android/preference/PreferenceFragment.java
@@ -23,7 +23,6 @@
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.res.TypedArray;
-import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
@@ -105,7 +104,10 @@
*
* @see Preference
* @see PreferenceScreen
+ *
+ * @deprecated Use {@link android.support.v7.preference.PreferenceFragmentCompat}
*/
+@Deprecated
public abstract class PreferenceFragment extends Fragment implements
PreferenceManager.OnPreferenceTreeClickListener {
@@ -146,7 +148,11 @@
* Interface that PreferenceFragment's containing activity should
* implement to be able to process preference items that wish to
* switch to a new fragment.
+ *
+ * @deprecated Use {@link
+ * android.support.v7.preference.PreferenceFragmentCompat.OnPreferenceStartFragmentCallback}
*/
+ @Deprecated
public interface OnPreferenceStartFragmentCallback {
/**
* Called when the user has clicked on a Preference that has
diff --git a/core/java/android/provider/MediaStore.java b/core/java/android/provider/MediaStore.java
index 13e1e26..32d68cd 100644
--- a/core/java/android/provider/MediaStore.java
+++ b/core/java/android/provider/MediaStore.java
@@ -81,6 +81,13 @@
public static final String UNHIDE_CALL = "unhide";
/**
+ * The method name used by the media scanner service to reload all localized ringtone titles due
+ * to a locale change.
+ * @hide
+ */
+ public static final String RETRANSLATE_CALL = "update_titles";
+
+ /**
* This is for internal use by the media scanner only.
* Name of the (optional) Uri parameter that determines whether to skip deleting
* the file pointed to by the _data column, when deleting the database entry.
@@ -1358,6 +1365,18 @@
* @hide
*/
public static final String GENRE = "genre";
+
+ /**
+ * The resource URI of a localized title, if any
+ * <P>Type: TEXT</P>
+ * Conforms to this pattern:
+ * Scheme: {@link ContentResolver.SCHEME_ANDROID_RESOURCE}
+ * Authority: Package Name of ringtone title provider
+ * First Path Segment: Type of resource (must be "string")
+ * Second Path Segment: Resource ID of title
+ * @hide
+ */
+ public static final String TITLE_RESOURCE_URI = "title_resource_uri";
}
/**
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 1716305..1bef2b3 100755
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -8522,6 +8522,13 @@
public static final String NETWORK_METERED_MULTIPATH_PREFERENCE =
"network_metered_multipath_preference";
+ /**
+ * Network watchlist last report time.
+ * @hide
+ */
+ public static final String NETWORK_WATCHLIST_LAST_REPORT_TIME =
+ "network_watchlist_last_report_time";
+
/**
* The thresholds of the wifi throughput badging (SD, HD etc.) as a comma-delimited list of
* colon-delimited key-value pairs. The key is the badging enum value defined in
diff --git a/core/java/android/security/KeystoreArguments.aidl b/core/java/android/security/KeystoreArguments.aidl
index d636414..dc8ed50 100644
--- a/core/java/android/security/KeystoreArguments.aidl
+++ b/core/java/android/security/KeystoreArguments.aidl
@@ -17,4 +17,4 @@
package android.security;
/* @hide */
-parcelable KeystoreArguments;
+parcelable KeystoreArguments cpp_header "keystore/KeystoreArguments.h";
diff --git a/core/java/android/security/keymaster/ExportResult.aidl b/core/java/android/security/keymaster/ExportResult.aidl
index 4d9b2de..1748653 100644
--- a/core/java/android/security/keymaster/ExportResult.aidl
+++ b/core/java/android/security/keymaster/ExportResult.aidl
@@ -17,4 +17,4 @@
package android.security.keymaster;
/* @hide */
-parcelable ExportResult;
+parcelable ExportResult cpp_header "keystore/ExportResult.h";
diff --git a/core/java/android/security/keymaster/KeyCharacteristics.aidl b/core/java/android/security/keymaster/KeyCharacteristics.aidl
index be739d3..32e75ad 100644
--- a/core/java/android/security/keymaster/KeyCharacteristics.aidl
+++ b/core/java/android/security/keymaster/KeyCharacteristics.aidl
@@ -17,4 +17,4 @@
package android.security.keymaster;
/* @hide */
-parcelable KeyCharacteristics;
+parcelable KeyCharacteristics cpp_header "keystore/KeyCharacteristics.h";
diff --git a/core/java/android/security/keymaster/KeymasterArguments.aidl b/core/java/android/security/keymaster/KeymasterArguments.aidl
index 1a73206..44d9f09 100644
--- a/core/java/android/security/keymaster/KeymasterArguments.aidl
+++ b/core/java/android/security/keymaster/KeymasterArguments.aidl
@@ -17,4 +17,4 @@
package android.security.keymaster;
/* @hide */
-parcelable KeymasterArguments;
+parcelable KeymasterArguments cpp_header "keystore/KeymasterArguments.h";
diff --git a/core/java/android/security/keymaster/KeymasterBlob.aidl b/core/java/android/security/keymaster/KeymasterBlob.aidl
index b7cd1c9..5c5db9e 100644
--- a/core/java/android/security/keymaster/KeymasterBlob.aidl
+++ b/core/java/android/security/keymaster/KeymasterBlob.aidl
@@ -17,4 +17,4 @@
package android.security.keymaster;
/* @hide */
-parcelable KeymasterBlob;
+parcelable KeymasterBlob cpp_header "keystore/KeymasterBlob.h";
diff --git a/core/java/android/security/keymaster/KeymasterCertificateChain.aidl b/core/java/android/security/keymaster/KeymasterCertificateChain.aidl
index dc1876a..ddb5cae 100644
--- a/core/java/android/security/keymaster/KeymasterCertificateChain.aidl
+++ b/core/java/android/security/keymaster/KeymasterCertificateChain.aidl
@@ -17,4 +17,4 @@
package android.security.keymaster;
/* @hide */
-parcelable KeymasterCertificateChain;
+parcelable KeymasterCertificateChain cpp_header "keystore/KeymasterCertificateChain.h";
diff --git a/core/java/android/security/keymaster/OperationResult.aidl b/core/java/android/security/keymaster/OperationResult.aidl
index ed26c8d..db689d4 100644
--- a/core/java/android/security/keymaster/OperationResult.aidl
+++ b/core/java/android/security/keymaster/OperationResult.aidl
@@ -17,4 +17,4 @@
package android.security.keymaster;
/* @hide */
-parcelable OperationResult;
+parcelable OperationResult cpp_header "keystore/OperationResult.h";
diff --git a/core/java/android/service/euicc/EuiccService.java b/core/java/android/service/euicc/EuiccService.java
index 0c2e4b7..cd233b8 100644
--- a/core/java/android/service/euicc/EuiccService.java
+++ b/core/java/android/service/euicc/EuiccService.java
@@ -97,6 +97,10 @@
public static final String ACTION_RESOLVE_NO_PRIVILEGES =
"android.service.euicc.action.RESOLVE_NO_PRIVILEGES";
+ /** Ask the user to input carrier confirmation code. */
+ public static final String ACTION_RESOLVE_CONFIRMATION_CODE =
+ "android.service.euicc.action.RESOLVE_CONFIRMATION_CODE";
+
/** Intent extra set for resolution requests containing the package name of the calling app. */
public static final String EXTRA_RESOLUTION_CALLING_PACKAGE =
"android.service.euicc.extra.RESOLUTION_CALLING_PACKAGE";
@@ -105,6 +109,8 @@
public static final int RESULT_OK = 0;
/** Result code indicating that an active SIM must be deactivated to perform the operation. */
public static final int RESULT_MUST_DEACTIVATE_SIM = -1;
+ /** Result code indicating that the user must input a carrier confirmation code. */
+ public static final int RESULT_NEED_CONFIRMATION_CODE = -2;
// New predefined codes should have negative values.
/** Start of implementation-specific error results. */
@@ -119,10 +125,13 @@
RESOLUTION_ACTIONS = new ArraySet<>();
RESOLUTION_ACTIONS.add(EuiccService.ACTION_RESOLVE_DEACTIVATE_SIM);
RESOLUTION_ACTIONS.add(EuiccService.ACTION_RESOLVE_NO_PRIVILEGES);
+ RESOLUTION_ACTIONS.add(EuiccService.ACTION_RESOLVE_CONFIRMATION_CODE);
}
/** Boolean extra for resolution actions indicating whether the user granted consent. */
public static final String RESOLUTION_EXTRA_CONSENT = "consent";
+ /** String extra for resolution actions indicating the carrier confirmation code. */
+ public static final String RESOLUTION_EXTRA_CONFIRMATION_CODE = "confirmation_code";
private final IEuiccService.Stub mStubWrapper;
diff --git a/core/java/android/service/notification/ConditionProviderService.java b/core/java/android/service/notification/ConditionProviderService.java
index 3e992ec..080482b 100644
--- a/core/java/android/service/notification/ConditionProviderService.java
+++ b/core/java/android/service/notification/ConditionProviderService.java
@@ -18,6 +18,7 @@
import android.annotation.SdkConstant;
import android.annotation.SystemApi;
+import android.app.ActivityManager;
import android.app.INotificationManager;
import android.app.Service;
import android.content.ComponentName;
@@ -56,6 +57,8 @@
* </meta-data>
* </service></pre>
*
+ * <p> Condition providers cannot be bound by the system on
+ * {@link ActivityManager#isLowRamDevice() low ram} devices</p>
*/
public abstract class ConditionProviderService extends Service {
private final String TAG = ConditionProviderService.class.getSimpleName()
diff --git a/core/java/android/service/notification/NotificationListenerService.java b/core/java/android/service/notification/NotificationListenerService.java
index 08d3118..dac663e7 100644
--- a/core/java/android/service/notification/NotificationListenerService.java
+++ b/core/java/android/service/notification/NotificationListenerService.java
@@ -21,6 +21,7 @@
import android.annotation.SdkConstant;
import android.annotation.SystemApi;
import android.annotation.TestApi;
+import android.app.ActivityManager;
import android.app.INotificationManager;
import android.app.Notification;
import android.app.Notification.Builder;
@@ -82,6 +83,8 @@
* method is the <i>only</i> one that is safe to call before {@link #onListenerConnected()}
* or after {@link #onListenerDisconnected()}.
* </p>
+ * <p> Notification listeners cannot get notification access or be bound by the system on
+ * {@link ActivityManager#isLowRamDevice() low ram} devices</p>
*/
public abstract class NotificationListenerService extends Service {
diff --git a/core/java/android/util/TimeUtils.java b/core/java/android/util/TimeUtils.java
index 2b03ed6..cc4a0b6 100644
--- a/core/java/android/util/TimeUtils.java
+++ b/core/java/android/util/TimeUtils.java
@@ -340,6 +340,14 @@
}
/** @hide Just for debugging; not internationalized. */
+ public static String formatDuration(long duration) {
+ synchronized (sFormatSync) {
+ int len = formatDurationLocked(duration, 0);
+ return new String(sFormatStr, 0, len);
+ }
+ }
+
+ /** @hide Just for debugging; not internationalized. */
public static void formatDuration(long duration, PrintWriter pw) {
formatDuration(duration, pw, 0);
}
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index c043dca..e12c0b0 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -14218,6 +14218,7 @@
*/
public void setScaleX(float scaleX) {
if (scaleX != getScaleX()) {
+ requireIsFinite(scaleX, "scaleX");
invalidateViewProperty(true, false);
mRenderNode.setScaleX(scaleX);
invalidateViewProperty(false, true);
@@ -14254,6 +14255,7 @@
*/
public void setScaleY(float scaleY) {
if (scaleY != getScaleY()) {
+ requireIsFinite(scaleY, "scaleY");
invalidateViewProperty(true, false);
mRenderNode.setScaleY(scaleY);
invalidateViewProperty(false, true);
@@ -14803,6 +14805,15 @@
}
}
+ private static void requireIsFinite(float transform, String propertyName) {
+ if (Float.isNaN(transform)) {
+ throw new IllegalArgumentException("Cannot set '" + propertyName + "' to Float.NaN");
+ }
+ if (Float.isInfinite(transform)) {
+ throw new IllegalArgumentException("Cannot set '" + propertyName + "' to infinity");
+ }
+ }
+
/**
* The visual x position of this view, in pixels. This is equivalent to the
* {@link #setTranslationX(float) translationX} property plus the current
@@ -14889,6 +14900,7 @@
*/
public void setElevation(float elevation) {
if (elevation != getElevation()) {
+ requireIsFinite(elevation, "elevation");
invalidateViewProperty(true, false);
mRenderNode.setElevation(elevation);
invalidateViewProperty(false, true);
@@ -14981,6 +14993,7 @@
*/
public void setTranslationZ(float translationZ) {
if (translationZ != getTranslationZ()) {
+ requireIsFinite(translationZ, "translationZ");
invalidateViewProperty(true, false);
mRenderNode.setTranslationZ(translationZ);
invalidateViewProperty(false, true);
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 37829f0..e30496f 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -513,7 +513,7 @@
mDisplayManager = (DisplayManager)context.getSystemService(Context.DISPLAY_SERVICE);
if (!sCompatibilityDone) {
- sAlwaysAssignFocus = mTargetSdkVersion < Build.VERSION_CODES.P;
+ sAlwaysAssignFocus = true;
sCompatibilityDone = true;
}
diff --git a/core/java/android/view/WindowManagerGlobal.java b/core/java/android/view/WindowManagerGlobal.java
index c7e8dee..cca66d6 100644
--- a/core/java/android/view/WindowManagerGlobal.java
+++ b/core/java/android/view/WindowManagerGlobal.java
@@ -605,9 +605,10 @@
public void setStoppedState(IBinder token, boolean stopped) {
synchronized (mLock) {
int count = mViews.size();
- for (int i = 0; i < count; i++) {
+ for (int i = count - 1; i >= 0; i--) {
if (token == null || mParams.get(i).token == token) {
ViewRootImpl root = mRoots.get(i);
+ // Client might remove the view by "stopped" event.
root.setWindowStopped(stopped);
}
}
diff --git a/core/java/android/view/WindowManagerInternal.java b/core/java/android/view/WindowManagerInternal.java
index 7e1846a..cd1b190 100644
--- a/core/java/android/view/WindowManagerInternal.java
+++ b/core/java/android/view/WindowManagerInternal.java
@@ -18,6 +18,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.content.ClipData;
import android.graphics.Rect;
import android.graphics.Region;
import android.hardware.display.DisplayManagerInternal;
@@ -140,6 +141,30 @@
}
/**
+ * An interface to customize drag and drop behaviors.
+ */
+ public interface IDragDropCallback {
+ /**
+ * Called when drag operation is started.
+ */
+ default boolean performDrag(IWindow window, IBinder dragToken,
+ int touchSource, float touchX, float touchY, float thumbCenterX, float thumbCenterY,
+ ClipData data) {
+ return true;
+ }
+
+ /**
+ * Called when drop result is reported.
+ */
+ default void reportDropResult(IWindow window, boolean consumed) {}
+
+ /**
+ * Called when drag operation is cancelled.
+ */
+ default void cancelDragAndDrop(IBinder dragToken) {}
+ }
+
+ /**
* Request that the window manager call
* {@link DisplayManagerInternal#performTraversalInTransactionFromWindowManager}
* within a surface transaction at a later time.
@@ -351,4 +376,9 @@
* {@param vr2dDisplayId}.
*/
public abstract void setVr2dDisplayId(int vr2dDisplayId);
+
+ /**
+ * Sets callback to DragDropController.
+ */
+ public abstract void registerDragDropControllerCallback(IDragDropCallback callback);
}
diff --git a/core/java/android/view/textclassifier/TextClassifier.java b/core/java/android/view/textclassifier/TextClassifier.java
index e35bb92..aeb8489 100644
--- a/core/java/android/view/textclassifier/TextClassifier.java
+++ b/core/java/android/view/textclassifier/TextClassifier.java
@@ -133,27 +133,6 @@
}
/**
- * Returns a {@link LinksInfo} that may be applied to the text to annotate it with links
- * information.
- *
- * @param text the text to generate annotations for
- * @param linkMask See {@link android.text.util.Linkify} for a list of linkMasks that may be
- * specified. Subclasses of this interface may specify additional linkMasks
- * @param defaultLocales ordered list of locale preferences that can be used to disambiguate
- * the provided text. If no locale preferences exist, set this to null or an empty locale
- * list in which case the classifier will decide whether to use no locale information, use
- * a default locale, or use the system default.
- *
- * @throws IllegalArgumentException if text is null
- * @hide
- */
- @WorkerThread
- default LinksInfo getLinks(
- @NonNull CharSequence text, int linkMask, @Nullable LocaleList defaultLocales) {
- return LinksInfo.NO_OP;
- }
-
- /**
* Returns a {@link TextLinks} that may be applied to the text to annotate it with links
* information.
*
diff --git a/core/java/android/view/textclassifier/TextClassifierImpl.java b/core/java/android/view/textclassifier/TextClassifierImpl.java
index 9c4fd57..2ad6e02 100644
--- a/core/java/android/view/textclassifier/TextClassifierImpl.java
+++ b/core/java/android/view/textclassifier/TextClassifierImpl.java
@@ -30,13 +30,8 @@
import android.provider.Browser;
import android.provider.ContactsContract;
import android.provider.Settings;
-import android.text.Spannable;
-import android.text.TextUtils;
-import android.text.method.WordIterator;
-import android.text.style.ClickableSpan;
import android.text.util.Linkify;
import android.util.Patterns;
-import android.view.View;
import android.widget.TextViewMetrics;
import com.android.internal.annotations.GuardedBy;
@@ -46,13 +41,8 @@
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
-import java.text.BreakIterator;
import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Comparator;
import java.util.HashMap;
-import java.util.LinkedHashMap;
-import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
@@ -194,21 +184,6 @@
}
@Override
- public LinksInfo getLinks(
- @NonNull CharSequence text, int linkMask, @Nullable LocaleList defaultLocales) {
- Preconditions.checkArgument(text != null);
- try {
- return LinksInfoFactory.create(
- mContext, getSmartSelection(defaultLocales), text.toString(), linkMask);
- } catch (Throwable t) {
- // Avoid throwing from this method. Log the error.
- Log.e(LOG_TAG, "Error getting links info.", t);
- }
- // Getting here means something went wrong, return a NO_OP result.
- return TextClassifier.NO_OP.getLinks(text, linkMask, defaultLocales);
- }
-
- @Override
public TextLinks generateLinks(
@NonNull CharSequence text, @Nullable TextLinks.Options options) {
Preconditions.checkNotNull(text);
@@ -260,7 +235,9 @@
if (mSmartSelection == null || !Objects.equals(mLocale, locale)) {
destroySmartSelectionIfExistsLocked();
final ParcelFileDescriptor fd = getFdLocked(locale);
- mSmartSelection = new SmartSelection(fd.getFd());
+ final int modelFd = fd.getFd();
+ mVersion = SmartSelection.getVersion(modelFd);
+ mSmartSelection = new SmartSelection(modelFd);
closeAndLogError(fd);
mLocale = locale;
}
@@ -281,18 +258,26 @@
@GuardedBy("mSmartSelectionLock") // Do not call outside this lock.
private ParcelFileDescriptor getFdLocked(Locale locale) throws FileNotFoundException {
ParcelFileDescriptor updateFd;
+ int updateVersion = -1;
try {
updateFd = ParcelFileDescriptor.open(
new File(UPDATED_MODEL_FILE_PATH), ParcelFileDescriptor.MODE_READ_ONLY);
+ if (updateFd != null) {
+ updateVersion = SmartSelection.getVersion(updateFd.getFd());
+ }
} catch (FileNotFoundException e) {
updateFd = null;
}
ParcelFileDescriptor factoryFd;
+ int factoryVersion = -1;
try {
final String factoryModelFilePath = getFactoryModelFilePathsLocked().get(locale);
if (factoryModelFilePath != null) {
factoryFd = ParcelFileDescriptor.open(
new File(factoryModelFilePath), ParcelFileDescriptor.MODE_READ_ONLY);
+ if (factoryFd != null) {
+ factoryVersion = SmartSelection.getVersion(factoryFd.getFd());
+ }
} else {
factoryFd = null;
}
@@ -328,15 +313,11 @@
return factoryFd;
}
- final int updateVersion = SmartSelection.getVersion(updateFdInt);
- final int factoryVersion = SmartSelection.getVersion(factoryFd.getFd());
if (updateVersion > factoryVersion) {
closeAndLogError(factoryFd);
- mVersion = updateVersion;
return updateFd;
} else {
closeAndLogError(updateFd);
- mVersion = factoryVersion;
return factoryFd;
}
}
@@ -516,180 +497,6 @@
}
/**
- * Detects and creates links for specified text.
- */
- private static final class LinksInfoFactory {
-
- private LinksInfoFactory() {}
-
- public static LinksInfo create(
- Context context, SmartSelection smartSelection, String text, int linkMask) {
- final WordIterator wordIterator = new WordIterator();
- wordIterator.setCharSequence(text, 0, text.length());
- final List<SpanSpec> spans = new ArrayList<>();
- int start = 0;
- int end;
- while ((end = wordIterator.nextBoundary(start)) != BreakIterator.DONE) {
- final String token = text.substring(start, end);
- if (TextUtils.isEmpty(token)) {
- continue;
- }
-
- final int[] selection = smartSelection.suggest(text, start, end);
- final int selectionStart = selection[0];
- final int selectionEnd = selection[1];
- if (selectionStart >= 0 && selectionEnd <= text.length()
- && selectionStart <= selectionEnd) {
- final SmartSelection.ClassificationResult[] results =
- smartSelection.classifyText(
- text, selectionStart, selectionEnd,
- getHintFlags(text, selectionStart, selectionEnd));
- if (results.length > 0) {
- final String type = getHighestScoringType(results);
- if (matches(type, linkMask)) {
- // For links without disambiguation, we simply use the default intent.
- final List<Intent> intents = IntentFactory.create(
- context, type, text.substring(selectionStart, selectionEnd));
- if (!intents.isEmpty() && hasActivityHandler(context, intents.get(0))) {
- final ClickableSpan span = createSpan(context, intents.get(0));
- spans.add(new SpanSpec(selectionStart, selectionEnd, span));
- }
- }
- }
- }
- start = end;
- }
- return new LinksInfoImpl(text, avoidOverlaps(spans, text));
- }
-
- /**
- * Returns true if the classification type matches the specified linkMask.
- */
- private static boolean matches(String type, int linkMask) {
- type = type.trim().toLowerCase(Locale.ENGLISH);
- if ((linkMask & Linkify.PHONE_NUMBERS) != 0
- && TextClassifier.TYPE_PHONE.equals(type)) {
- return true;
- }
- if ((linkMask & Linkify.EMAIL_ADDRESSES) != 0
- && TextClassifier.TYPE_EMAIL.equals(type)) {
- return true;
- }
- if ((linkMask & Linkify.MAP_ADDRESSES) != 0
- && TextClassifier.TYPE_ADDRESS.equals(type)) {
- return true;
- }
- if ((linkMask & Linkify.WEB_URLS) != 0
- && TextClassifier.TYPE_URL.equals(type)) {
- return true;
- }
- return false;
- }
-
- /**
- * Trim the number of spans so that no two spans overlap.
- *
- * This algorithm first ensures that there is only one span per start index, then it
- * makes sure that no two spans overlap.
- */
- private static List<SpanSpec> avoidOverlaps(List<SpanSpec> spans, String text) {
- Collections.sort(spans, Comparator.comparingInt(span -> span.mStart));
- // Group spans by start index. Take the longest span.
- final Map<Integer, SpanSpec> reps = new LinkedHashMap<>(); // order matters.
- final int size = spans.size();
- for (int i = 0; i < size; i++) {
- final SpanSpec span = spans.get(i);
- final LinksInfoFactory.SpanSpec rep = reps.get(span.mStart);
- if (rep == null || rep.mEnd < span.mEnd) {
- reps.put(span.mStart, span);
- }
- }
- // Avoid span intersections. Take the longer span.
- final LinkedList<SpanSpec> result = new LinkedList<>();
- for (SpanSpec rep : reps.values()) {
- if (result.isEmpty()) {
- result.add(rep);
- continue;
- }
-
- final SpanSpec last = result.getLast();
- if (rep.mStart < last.mEnd) {
- // Spans intersect. Use the one with characters.
- if ((rep.mEnd - rep.mStart) > (last.mEnd - last.mStart)) {
- result.set(result.size() - 1, rep);
- }
- } else {
- result.add(rep);
- }
- }
- return result;
- }
-
- private static ClickableSpan createSpan(final Context context, final Intent intent) {
- return new ClickableSpan() {
- // TODO: Style this span.
- @Override
- public void onClick(View widget) {
- context.startActivity(intent);
- }
- };
- }
-
- private static boolean hasActivityHandler(Context context, Intent intent) {
- if (intent == null) {
- return false;
- }
- final ResolveInfo resolveInfo = context.getPackageManager().resolveActivity(intent, 0);
- return resolveInfo != null && resolveInfo.activityInfo != null;
- }
-
- /**
- * Implementation of LinksInfo that adds ClickableSpans to the specified text.
- */
- private static final class LinksInfoImpl implements LinksInfo {
-
- private final CharSequence mOriginalText;
- private final List<SpanSpec> mSpans;
-
- LinksInfoImpl(CharSequence originalText, List<SpanSpec> spans) {
- mOriginalText = originalText;
- mSpans = spans;
- }
-
- @Override
- public boolean apply(@NonNull CharSequence text) {
- Preconditions.checkArgument(text != null);
- if (text instanceof Spannable && mOriginalText.toString().equals(text.toString())) {
- Spannable spannable = (Spannable) text;
- final int size = mSpans.size();
- for (int i = 0; i < size; i++) {
- final SpanSpec span = mSpans.get(i);
- spannable.setSpan(span.mSpan, span.mStart, span.mEnd, 0);
- }
- return true;
- }
- return false;
- }
- }
-
- /**
- * Span plus its start and end index.
- */
- private static final class SpanSpec {
-
- private final int mStart;
- private final int mEnd;
- private final ClickableSpan mSpan;
-
- SpanSpec(int start, int end, ClickableSpan span) {
- mStart = start;
- mEnd = end;
- mSpan = span;
- }
- }
- }
-
- /**
* Creates intents based on the classification type.
*/
private static final class IntentFactory {
diff --git a/core/java/android/view/textclassifier/logging/SmartSelectionEventTracker.java b/core/java/android/view/textclassifier/logging/SmartSelectionEventTracker.java
index 83af19b..fbf6535 100644
--- a/core/java/android/view/textclassifier/logging/SmartSelectionEventTracker.java
+++ b/core/java/android/view/textclassifier/logging/SmartSelectionEventTracker.java
@@ -48,9 +48,13 @@
private static final int START_EVENT_DELTA = MetricsEvent.FIELD_SELECTION_SINCE_START;
private static final int PREV_EVENT_DELTA = MetricsEvent.FIELD_SELECTION_SINCE_PREVIOUS;
private static final int INDEX = MetricsEvent.FIELD_SELECTION_SESSION_INDEX;
- private static final int VERSION_TAG = MetricsEvent.FIELD_SELECTION_VERSION_TAG;
- private static final int SMART_INDICES = MetricsEvent.FIELD_SELECTION_SMART_RANGE;
- private static final int EVENT_INDICES = MetricsEvent.FIELD_SELECTION_RANGE;
+ private static final int WIDGET_TYPE = MetricsEvent.FIELD_SELECTION_WIDGET_TYPE;
+ private static final int MODEL_NAME = MetricsEvent.FIELD_TEXTCLASSIFIER_MODEL;
+ private static final int ENTITY_TYPE = MetricsEvent.FIELD_SELECTION_ENTITY_TYPE;
+ private static final int SMART_START = MetricsEvent.FIELD_SELECTION_SMART_RANGE_START;
+ private static final int SMART_END = MetricsEvent.FIELD_SELECTION_SMART_RANGE_END;
+ private static final int EVENT_START = MetricsEvent.FIELD_SELECTION_RANGE_START;
+ private static final int EVENT_END = MetricsEvent.FIELD_SELECTION_RANGE_END;
private static final int SESSION_ID = MetricsEvent.FIELD_SELECTION_SESSION_ID;
private static final String ZERO = "0";
@@ -83,7 +87,7 @@
private long mSessionStartTime;
private long mLastEventTime;
private boolean mSmartSelectionTriggered;
- private String mVersionTag;
+ private String mModelName;
public SmartSelectionEventTracker(@NonNull Context context, @WidgetType int widgetType) {
mWidgetType = widgetType;
@@ -115,7 +119,7 @@
case SelectionEvent.EventType.SMART_SELECTION_SINGLE: // fall through
case SelectionEvent.EventType.SMART_SELECTION_MULTI:
mSmartSelectionTriggered = true;
- mVersionTag = getVersionTag(event);
+ mModelName = getModelName(event);
mSmartIndices[0] = event.mStart;
mSmartIndices[1] = event.mEnd;
break;
@@ -137,14 +141,18 @@
final long prevEventDelta = mLastEventTime == 0 ? 0 : now - mLastEventTime;
final LogMaker log = new LogMaker(MetricsEvent.TEXT_SELECTION_SESSION)
.setType(getLogType(event))
- .setSubtype(getLogSubType(event))
+ .setSubtype(MetricsEvent.TEXT_SELECTION_INVOCATION_MANUAL)
.setPackageName(mContext.getPackageName())
.addTaggedData(START_EVENT_DELTA, now - mSessionStartTime)
.addTaggedData(PREV_EVENT_DELTA, prevEventDelta)
.addTaggedData(INDEX, mIndex)
- .addTaggedData(VERSION_TAG, mVersionTag)
- .addTaggedData(SMART_INDICES, getSmartDelta())
- .addTaggedData(EVENT_INDICES, getEventDelta(event))
+ .addTaggedData(WIDGET_TYPE, getWidgetTypeName())
+ .addTaggedData(MODEL_NAME, mModelName)
+ .addTaggedData(ENTITY_TYPE, event.mEntityType)
+ .addTaggedData(SMART_START, getSmartRangeDelta(mSmartIndices[0]))
+ .addTaggedData(SMART_END, getSmartRangeDelta(mSmartIndices[1]))
+ .addTaggedData(EVENT_START, getRangeDelta(event.mStart))
+ .addTaggedData(EVENT_END, getRangeDelta(event.mEnd))
.addTaggedData(SESSION_ID, mSessionId);
mMetricsLogger.write(log);
debugLog(log);
@@ -169,7 +177,7 @@
mSessionStartTime = 0;
mLastEventTime = 0;
mSmartSelectionTriggered = false;
- mVersionTag = getVersionTag(null);
+ mModelName = getModelName(null);
mSessionId = null;
}
@@ -251,113 +259,64 @@
}
}
- private static int getLogSubType(SelectionEvent event) {
- switch (event.mEntityType) {
- case TextClassifier.TYPE_OTHER:
- return MetricsEvent.TEXT_CLASSIFIER_TYPE_OTHER;
- case TextClassifier.TYPE_EMAIL:
- return MetricsEvent.TEXT_CLASSIFIER_TYPE_EMAIL;
- case TextClassifier.TYPE_PHONE:
- return MetricsEvent.TEXT_CLASSIFIER_TYPE_PHONE;
- case TextClassifier.TYPE_ADDRESS:
- return MetricsEvent.TEXT_CLASSIFIER_TYPE_ADDRESS;
- case TextClassifier.TYPE_URL:
- return MetricsEvent.TEXT_CLASSIFIER_TYPE_URL;
- default:
- return MetricsEvent.TEXT_CLASSIFIER_TYPE_UNKNOWN;
- }
+ private int getRangeDelta(int offset) {
+ return offset - mOrigStart;
}
- private static String getLogSubTypeString(int logSubType) {
- switch (logSubType) {
- case MetricsEvent.TEXT_CLASSIFIER_TYPE_OTHER:
- return TextClassifier.TYPE_OTHER;
- case MetricsEvent.TEXT_CLASSIFIER_TYPE_EMAIL:
- return TextClassifier.TYPE_EMAIL;
- case MetricsEvent.TEXT_CLASSIFIER_TYPE_PHONE:
- return TextClassifier.TYPE_PHONE;
- case MetricsEvent.TEXT_CLASSIFIER_TYPE_ADDRESS:
- return TextClassifier.TYPE_ADDRESS;
- case MetricsEvent.TEXT_CLASSIFIER_TYPE_URL:
- return TextClassifier.TYPE_URL;
- default:
- return TextClassifier.TYPE_UNKNOWN;
- }
+ private int getSmartRangeDelta(int offset) {
+ return mSmartSelectionTriggered ? getRangeDelta(offset) : 0;
}
- private int getSmartDelta() {
- if (mSmartSelectionTriggered) {
- return (clamp(mSmartIndices[0] - mOrigStart) << 16)
- | (clamp(mSmartIndices[1] - mOrigStart) & 0xffff);
- }
- // If the smart selection model was not run, return invalid selection indices [0,0]. This
- // allows us to tell from the terminal event alone whether the model was run.
- return 0;
- }
-
- private int getEventDelta(SelectionEvent event) {
- return (clamp(event.mStart - mOrigStart) << 16)
- | (clamp(event.mEnd - mOrigStart) & 0xffff);
- }
-
- private String getVersionTag(@Nullable SelectionEvent event) {
- final String widgetType;
+ private String getWidgetTypeName() {
switch (mWidgetType) {
case WidgetType.TEXTVIEW:
- widgetType = TEXTVIEW;
- break;
+ return TEXTVIEW;
case WidgetType.WEBVIEW:
- widgetType = WEBVIEW;
- break;
+ return WEBVIEW;
case WidgetType.EDITTEXT:
- widgetType = EDITTEXT;
- break;
+ return EDITTEXT;
case WidgetType.EDIT_WEBVIEW:
- widgetType = EDIT_WEBVIEW;
- break;
+ return EDIT_WEBVIEW;
default:
- widgetType = UNKNOWN;
+ return UNKNOWN;
}
- final String version = event == null
+ }
+
+ private String getModelName(@Nullable SelectionEvent event) {
+ return event == null
? SelectionEvent.NO_VERSION_TAG
: Objects.toString(event.mVersionTag, SelectionEvent.NO_VERSION_TAG);
- return String.format("%s/%s", widgetType, version);
}
private static String createSessionId() {
return UUID.randomUUID().toString();
}
- private static int clamp(int val) {
- return Math.max(Math.min(val, Short.MAX_VALUE), Short.MIN_VALUE);
- }
-
private static void debugLog(LogMaker log) {
if (!DEBUG_LOG_ENABLED) return;
- final String tag = Objects.toString(log.getTaggedData(VERSION_TAG), "tag");
+ final String widget = Objects.toString(log.getTaggedData(WIDGET_TYPE), UNKNOWN);
final int index = Integer.parseInt(Objects.toString(log.getTaggedData(INDEX), ZERO));
if (log.getType() == MetricsEvent.ACTION_TEXT_SELECTION_START) {
String sessionId = Objects.toString(log.getTaggedData(SESSION_ID), "");
sessionId = sessionId.substring(sessionId.lastIndexOf("-") + 1);
- Log.d(LOG_TAG, String.format("New selection session: %s(%s)", tag, sessionId));
+ Log.d(LOG_TAG, String.format("New selection session: %s (%s)", widget, sessionId));
}
+ final String model = Objects.toString(log.getTaggedData(MODEL_NAME), UNKNOWN);
+ final String entity = Objects.toString(log.getTaggedData(ENTITY_TYPE), UNKNOWN);
final String type = getLogTypeString(log.getType());
- final String subType = getLogSubTypeString(log.getSubtype());
+ final int smartStart = Integer.parseInt(
+ Objects.toString(log.getTaggedData(SMART_START), ZERO));
+ final int smartEnd = Integer.parseInt(
+ Objects.toString(log.getTaggedData(SMART_END), ZERO));
+ final int eventStart = Integer.parseInt(
+ Objects.toString(log.getTaggedData(EVENT_START), ZERO));
+ final int eventEnd = Integer.parseInt(
+ Objects.toString(log.getTaggedData(EVENT_END), ZERO));
- final int smartIndices = Integer.parseInt(
- Objects.toString(log.getTaggedData(SMART_INDICES), ZERO));
- final int smartStart = (short) ((smartIndices & 0xffff0000) >> 16);
- final int smartEnd = (short) (smartIndices & 0xffff);
-
- final int eventIndices = Integer.parseInt(
- Objects.toString(log.getTaggedData(EVENT_INDICES), ZERO));
- final int eventStart = (short) ((eventIndices & 0xffff0000) >> 16);
- final int eventEnd = (short) (eventIndices & 0xffff);
-
- Log.d(LOG_TAG, String.format("%2d: %s/%s, context=%d,%d - old=%d,%d (%s)",
- index, type, subType, eventStart, eventEnd, smartStart, smartEnd, tag));
+ Log.d(LOG_TAG, String.format("%2d: %s/%s, range=%d,%d - smart_range=%d,%d (%s/%s)",
+ index, type, entity, eventStart, eventEnd, smartStart, smartEnd, widget, model));
}
/**
@@ -369,12 +328,12 @@
/**
* Use this to specify an indeterminate positive index.
*/
- public static final int OUT_OF_BOUNDS = Short.MAX_VALUE;
+ public static final int OUT_OF_BOUNDS = Integer.MAX_VALUE;
/**
* Use this to specify an indeterminate negative index.
*/
- public static final int OUT_OF_BOUNDS_NEGATIVE = Short.MIN_VALUE;
+ public static final int OUT_OF_BOUNDS_NEGATIVE = Integer.MIN_VALUE;
private static final String NO_VERSION_TAG = "";
diff --git a/core/java/android/webkit/WebViewFragment.java b/core/java/android/webkit/WebViewFragment.java
index d803f62d..e5b7c8d 100644
--- a/core/java/android/webkit/WebViewFragment.java
+++ b/core/java/android/webkit/WebViewFragment.java
@@ -27,7 +27,10 @@
* A fragment that displays a WebView.
* <p>
* The WebView is automically paused or resumed when the Fragment is paused or resumed.
+ *
+ * @deprecated Manually call {@link WebView#onPause()} and {@link WebView#onResume()}
*/
+@Deprecated
public class WebViewFragment extends Fragment {
private WebView mWebView;
private boolean mIsWebViewAvailable;
diff --git a/core/java/com/android/internal/app/NightDisplayController.java b/core/java/com/android/internal/app/ColorDisplayController.java
similarity index 98%
rename from core/java/com/android/internal/app/NightDisplayController.java
rename to core/java/com/android/internal/app/ColorDisplayController.java
index b8bfc64..b8682a8 100644
--- a/core/java/com/android/internal/app/NightDisplayController.java
+++ b/core/java/com/android/internal/app/ColorDisplayController.java
@@ -42,14 +42,14 @@
import java.time.format.DateTimeParseException;
/**
- * Controller for managing Night display settings.
+ * Controller for managing night display and color mode 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 {
+public final class ColorDisplayController {
- private static final String TAG = "NightDisplayController";
+ private static final String TAG = "ColorDisplayController";
private static final boolean DEBUG = false;
@Retention(RetentionPolicy.SOURCE)
@@ -114,11 +114,11 @@
private Callback mCallback;
- public NightDisplayController(@NonNull Context context) {
+ public ColorDisplayController(@NonNull Context context) {
this(context, ActivityManager.getCurrentUser());
}
- public NightDisplayController(@NonNull Context context, int userId) {
+ public ColorDisplayController(@NonNull Context context, int userId) {
mContext = context.getApplicationContext();
mUserId = userId;
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/IOverviewProxy.aidl b/core/java/com/android/internal/net/INetworkWatchlistManager.aidl
similarity index 72%
copy from packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/IOverviewProxy.aidl
copy to core/java/com/android/internal/net/INetworkWatchlistManager.aidl
index 8cf3be8..7e88369 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/IOverviewProxy.aidl
+++ b/core/java/com/android/internal/net/INetworkWatchlistManager.aidl
@@ -14,10 +14,13 @@
* limitations under the License.
*/
-package com.android.systemui.shared.recents.model;
+package com.android.internal.net;
-import android.view.MotionEvent;
+import android.os.SharedMemory;
-oneway interface IOverviewProxy {
- void onMotionEvent(in MotionEvent event);
+/** {@hide} */
+interface INetworkWatchlistManager {
+ boolean startWatchlistLogging();
+ boolean stopWatchlistLogging();
+ void reportWatchlistIfNecessary();
}
diff --git a/core/java/com/android/internal/net/NetworkStatsFactory.java b/core/java/com/android/internal/net/NetworkStatsFactory.java
index 3d3e148..5eda81b 100644
--- a/core/java/com/android/internal/net/NetworkStatsFactory.java
+++ b/core/java/com/android/internal/net/NetworkStatsFactory.java
@@ -43,6 +43,8 @@
/**
* Creates {@link NetworkStats} instances by parsing various {@code /proc/}
* files as needed.
+ *
+ * @hide
*/
public class NetworkStatsFactory {
private static final String TAG = "NetworkStatsFactory";
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index 2ec64a5..f2483c0 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -3639,6 +3639,7 @@
public void addIsolatedUidLocked(int isolatedUid, int appUid) {
mIsolatedUids.put(isolatedUid, appUid);
+ StatsLog.write(StatsLog.ISOLATED_UID_CHANGED, appUid, isolatedUid, 1);
}
/**
@@ -3659,9 +3660,11 @@
* @see #scheduleRemoveIsolatedUidLocked(int, int)
*/
public void removeIsolatedUidLocked(int isolatedUid) {
- mIsolatedUids.delete(isolatedUid);
- mKernelUidCpuTimeReader.removeUid(isolatedUid);
- mKernelUidCpuFreqTimeReader.removeUid(isolatedUid);
+ StatsLog.write(
+ StatsLog.ISOLATED_UID_CHANGED, mIsolatedUids.get(isolatedUid, -1), isolatedUid, 0);
+ mIsolatedUids.delete(isolatedUid);
+ mKernelUidCpuTimeReader.removeUid(isolatedUid);
+ mKernelUidCpuFreqTimeReader.removeUid(isolatedUid);
}
public int mapUid(int uid) {
diff --git a/core/proto/android/os/cpuinfo.proto b/core/proto/android/os/cpuinfo.proto
new file mode 100644
index 0000000..a95fa57
--- /dev/null
+++ b/core/proto/android/os/cpuinfo.proto
@@ -0,0 +1,110 @@
+/*
+ * 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.
+ */
+syntax = "proto2";
+
+option java_multiple_files = true;
+option java_outer_classname = "CpuInfoProto";
+
+import "frameworks/base/tools/streaming_proto/stream.proto";
+
+package android.os;
+
+/**
+ * Data structure of the linux command
+ * 'top -b -n 1 -H -s 6 -o pid,tid,user,pr,ni,%cpu,s,virt,res,pcy,cmd,name'
+ *
+ * Next Tag: 6
+ */
+message CpuInfo {
+
+ message TaskStats {
+ option (stream_proto.stream_msg).enable_fields_mapping = true;
+
+ optional int32 total = 1; // total number of cpu tasks
+ optional int32 running = 2; // number of running tasks
+ optional int32 sleeping = 3; // number of sleeping tasks
+ optional int32 stopped = 4; // number of stopped tasks
+ optional int32 zombie = 5; // number of zombie tasks
+ }
+ optional TaskStats task_stats = 1;
+
+ message MemStats { // unit in kB
+ option (stream_proto.stream_msg).enable_fields_mapping = true;
+
+ optional int32 total = 1;
+ optional int32 used = 2;
+ optional int32 free = 3;
+ optional int32 buffers = 4;
+ optional int32 cached = 5;
+ }
+ optional MemStats mem = 2;
+ optional MemStats swap = 3;
+
+ message CpuUsage { // unit is percentage %
+ option (stream_proto.stream_msg).enable_fields_mapping = true;
+
+ optional int32 cpu = 1; // 400% cpu indicates 4 cores
+ optional int32 user = 2;
+ optional int32 nice = 3;
+ optional int32 sys = 4;
+ optional int32 idle = 5;
+ optional int32 iow = 6;
+ optional int32 irq = 7;
+ optional int32 sirq = 8;
+ optional int32 host = 9;
+ }
+ optional CpuUsage cpu_usage = 4;
+
+ // Next Tag: 13
+ message Task {
+ option (stream_proto.stream_msg).enable_fields_mapping = true;
+
+ optional int32 pid = 1;
+ optional int32 tid = 2;
+ optional string user = 3;
+ optional string pr = 4; // priority of each task, using string type is because special value RT (real time)
+ optional sint32 ni = 5; // niceness value
+ optional float cpu = 6; // precentage of cpu usage of the task
+
+ enum Status {
+ option (stream_proto.stream_enum).enable_enums_mapping = true;
+
+ STATUS_UNKNOWN = 0;
+ STATUS_D = 1; // uninterruptible sleep
+ STATUS_R = 2; // running
+ STATUS_S = 3; // sleeping
+ STATUS_T = 4; // traced or stopped
+ STATUS_Z = 5; // zombie
+ }
+ optional Status s = 7; // process status
+ optional string virt = 8; // virtual memory size, i.e. 14.0G, 13.5M
+ optional string res = 9; // Resident size, i.e. 0, 3.1G
+
+ // How Android memory manager will treat the task
+ enum Policy {
+ option (stream_proto.stream_enum).enable_enums_mapping = true;
+
+ POLICY_UNKNOWN = 0;
+ POLICY_fg = 1; // foreground, the name is lower case for parsing the value
+ POLICY_bg = 2; // background, the name is lower case for parsing the value
+ POLICY_ta = 3; // TODO: figure out what is this value
+ }
+ optional Policy pcy = 10; // Policy of the task
+ optional string cmd = 11; // thread name
+ optional string name = 12; // program name
+ }
+ repeated Task tasks = 5;
+}
diff --git a/core/proto/android/os/incident.proto b/core/proto/android/os/incident.proto
index e998b09..55ea285 100644
--- a/core/proto/android/os/incident.proto
+++ b/core/proto/android/os/incident.proto
@@ -21,6 +21,7 @@
import "frameworks/base/libs/incident/proto/android/privacy.proto";
import "frameworks/base/libs/incident/proto/android/section.proto";
import "frameworks/base/core/proto/android/providers/settings.proto";
+import "frameworks/base/core/proto/android/os/cpuinfo.proto";
import "frameworks/base/core/proto/android/os/incidentheader.proto";
import "frameworks/base/core/proto/android/os/kernelwake.proto";
import "frameworks/base/core/proto/android/os/pagetypeinfo.proto";
@@ -68,6 +69,11 @@
(section).args = "/d/wakeup_sources"
];
+ optional CpuInfo cpu_info = 2003 [
+ (section).type = SECTION_COMMAND,
+ (section).args = "/system/bin/top -b -n 1 -H -s 6 -o pid,tid,user,pr,ni,%cpu,s,virt,res,pcy,cmd,name"
+ ];
+
// System Services
optional com.android.server.fingerprint.FingerprintServiceDumpProto fingerprint = 3000 [
diff --git a/core/proto/android/os/kernelwake.proto b/core/proto/android/os/kernelwake.proto
index d032a45..eaad37a 100644
--- a/core/proto/android/os/kernelwake.proto
+++ b/core/proto/android/os/kernelwake.proto
@@ -29,7 +29,7 @@
// Next Tag: 11
message WakeupSourceProto {
- option (stream_proto.stream).enable_fields_mapping = true;
+ option (stream_proto.stream_msg).enable_fields_mapping = true;
// Name of the event which triggers application processor
optional string name = 1;
diff --git a/core/proto/android/os/pagetypeinfo.proto b/core/proto/android/os/pagetypeinfo.proto
index 22b3d73..b86ee01 100644
--- a/core/proto/android/os/pagetypeinfo.proto
+++ b/core/proto/android/os/pagetypeinfo.proto
@@ -63,7 +63,7 @@
// Next tag: 9
message BlockProto {
- option (stream_proto.stream).enable_fields_mapping = true;
+ option (stream_proto.stream_msg).enable_fields_mapping = true;
optional int32 node = 1;
diff --git a/core/proto/android/os/procrank.proto b/core/proto/android/os/procrank.proto
index 4d62a60..9945f2e 100644
--- a/core/proto/android/os/procrank.proto
+++ b/core/proto/android/os/procrank.proto
@@ -33,7 +33,7 @@
// Next Tag: 11
message ProcessProto {
- option (stream_proto.stream).enable_fields_mapping = true;
+ option (stream_proto.stream_msg).enable_fields_mapping = true;
// ID of the process
optional int32 pid = 1;
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 49de135..7af1b46 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -3657,7 +3657,7 @@
</activity-alias>
<activity-alias android:name="com.android.internal.app.ForwardIntentToManagedProfile"
android:targetActivity="com.android.internal.app.IntentForwarderActivity"
- android:icon="@drawable/ic_corp_icon"
+ android:icon="@drawable/ic_corp_badge"
android:exported="true"
android:label="@string/managed_profile_label">
</activity-alias>
@@ -3944,6 +3944,10 @@
<service android:name="com.android.server.timezone.TimeZoneUpdateIdler"
android:permission="android.permission.BIND_JOB_SERVICE" >
</service>
+
+ <service android:name="com.android.server.net.watchlist.ReportWatchlistJobService"
+ android:permission="android.permission.BIND_JOB_SERVICE" >
+ </service>
</application>
</manifest>
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 1c2e5a4..1879ab7 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -123,6 +123,9 @@
be sent during a change to the audio output device. -->
<bool name="config_sendAudioBecomingNoisy">true</bool>
+ <!-- Flag to disable all transition animations -->
+ <bool name="config_disableTransitionAnimation">false</bool>
+
<!-- The duration (in milliseconds) of a short animation. -->
<integer name="config_shortAnimTime">200</integer>
@@ -876,7 +879,7 @@
<bool name="config_nightDisplayAvailable">@bool/config_setColorTransformAccelerated</bool>
<!-- Default mode to control how Night display is automatically activated.
- One of the following values (see NightDisplayController.java):
+ One of the following values (see ColorDisplayController.java):
0 - AUTO_MODE_DISABLED
1 - AUTO_MODE_CUSTOM
2 - AUTO_MODE_TWILIGHT
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index a833579..e476d89 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -252,6 +252,7 @@
<java-symbol type="bool" name="config_bluetooth_hfp_inband_ringing_support" />
<java-symbol type="bool" name="config_cellBroadcastAppLinks" />
<java-symbol type="bool" name="config_duplicate_port_omadm_wappush" />
+ <java-symbol type="bool" name="config_disableTransitionAnimation" />
<java-symbol type="bool" name="config_enableAutoPowerModes" />
<java-symbol type="integer" name="config_autoPowerModeThresholdAngle" />
<java-symbol type="integer" name="config_autoPowerModeAnyMotionSensor" />
@@ -1324,6 +1325,7 @@
<java-symbol type="drawable" name="cling_button" />
<java-symbol type="drawable" name="cling_arrow_up" />
<java-symbol type="drawable" name="cling_bg" />
+ <java-symbol type="drawable" name="ic_corp_badge" />
<java-symbol type="drawable" name="ic_corp_badge_color" />
<java-symbol type="drawable" name="ic_corp_badge_case" />
<java-symbol type="drawable" name="ic_corp_icon" />
diff --git a/core/tests/coretests/res/layout/add_column_in_table.xml b/core/tests/coretests/res/layout/add_column_in_table.xml
index 05f55a8..d929b02 100644
--- a/core/tests/coretests/res/layout/add_column_in_table.xml
+++ b/core/tests/coretests/res/layout/add_column_in_table.xml
@@ -18,6 +18,7 @@
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
+ <requestFocus />
<TableLayout android:id="@+id/table"
android:layout_width="match_parent"
diff --git a/core/tests/coretests/res/layout/baseline_0width_and_weight.xml b/core/tests/coretests/res/layout/baseline_0width_and_weight.xml
index acbb10b..eac9b9d 100644
--- a/core/tests/coretests/res/layout/baseline_0width_and_weight.xml
+++ b/core/tests/coretests/res/layout/baseline_0width_and_weight.xml
@@ -21,6 +21,7 @@
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
+ <requestFocus />
<LinearLayout android:id="@+id/layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
diff --git a/core/tests/coretests/res/layout/focus_after_removal.xml b/core/tests/coretests/res/layout/focus_after_removal.xml
index f4e388d..84449d1 100644
--- a/core/tests/coretests/res/layout/focus_after_removal.xml
+++ b/core/tests/coretests/res/layout/focus_after_removal.xml
@@ -22,6 +22,7 @@
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content">
+ <requestFocus />
<LinearLayout android:id="@+id/leftLayout"
android:orientation="vertical"
diff --git a/core/tests/coretests/res/layout/linear_layout_edittext_then_button.xml b/core/tests/coretests/res/layout/linear_layout_edittext_then_button.xml
index ab76e29..6b3b5a7 100644
--- a/core/tests/coretests/res/layout/linear_layout_edittext_then_button.xml
+++ b/core/tests/coretests/res/layout/linear_layout_edittext_then_button.xml
@@ -22,6 +22,7 @@
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
+ <requestFocus />
<EditText
android:id="@+id/editText"
diff --git a/core/tests/coretests/res/layout/visibility_callback.xml b/core/tests/coretests/res/layout/visibility_callback.xml
index 9034b3f..ff918f5 100644
--- a/core/tests/coretests/res/layout/visibility_callback.xml
+++ b/core/tests/coretests/res/layout/visibility_callback.xml
@@ -24,6 +24,7 @@
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
+ <requestFocus />
<LinearLayout
android:orientation="vertical"
diff --git a/core/tests/coretests/src/android/provider/SettingsBackupTest.java b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
index 1002939..d36ed63 100644
--- a/core/tests/coretests/src/android/provider/SettingsBackupTest.java
+++ b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
@@ -26,7 +26,6 @@
import static java.lang.reflect.Modifier.isPublic;
import static java.lang.reflect.Modifier.isStatic;
-import android.platform.test.annotations.Presubmit;
import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;
@@ -264,6 +263,7 @@
Settings.Global.NETSTATS_UID_TAG_ROTATE_AGE,
Settings.Global.NETWORK_AVOID_BAD_WIFI,
Settings.Global.NETWORK_METERED_MULTIPATH_PREFERENCE,
+ Settings.Global.NETWORK_WATCHLIST_LAST_REPORT_TIME,
Settings.Global.NETWORK_PREFERENCE,
Settings.Global.NETWORK_RECOMMENDATIONS_PACKAGE,
Settings.Global.NETWORK_RECOMMENDATION_REQUEST_TIMEOUT_MS,
diff --git a/core/tests/coretests/src/android/util/ListScenario.java b/core/tests/coretests/src/android/util/ListScenario.java
index fa088a3..129484a 100644
--- a/core/tests/coretests/src/android/util/ListScenario.java
+++ b/core/tests/coretests/src/android/util/ListScenario.java
@@ -28,6 +28,7 @@
import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.TextView;
+
import com.google.android.collect.Maps;
import java.util.ArrayList;
@@ -60,18 +61,18 @@
// separators
private Set<Integer> mUnselectableItems = new HashSet<Integer>();
-
+
private boolean mStackFromBottom;
private int mClickedPosition = -1;
-
+
private int mLongClickedPosition = -1;
-
+
private int mConvertMisses = 0;
-
+
private int mHeaderViewCount;
private boolean mHeadersFocusable;
-
+
private int mFooterViewCount;
private LinearLayout mLinearLayout;
@@ -193,7 +194,7 @@
mIncludeHeader = includeHeader;
return this;
}
-
+
/**
* Sets the stacking direction
* @param stackFromBottom
@@ -203,7 +204,7 @@
mStackFromBottom = stackFromBottom;
return this;
}
-
+
/**
* Sets whether the sum of the height of the list items must be at least the
* height of the list view.
@@ -220,7 +221,7 @@
mFadingEdgeScreenSizeFactor = fadingEdgeScreenSizeFactor;
return this;
}
-
+
/**
* Set the number of header views to appear within the list
*/
@@ -246,7 +247,7 @@
mFooterViewCount = footerViewCount;
return this;
}
-
+
/**
* Sets whether the {@link ListScenario} will automatically set the
* adapter on the list view. If this is false, the client MUST set it
@@ -278,7 +279,7 @@
*/
protected void nothingSelected() {
}
-
+
/**
* Override this if you want to know when something has been clicked (perhaps
* more importantly, that {@link android.widget.AdapterView.OnItemClickListener} has
@@ -287,7 +288,7 @@
protected void positionClicked(int position) {
setClickedPosition(position);
}
-
+
/**
* Override this if you want to know when something has been long clicked (perhaps
* more importantly, that {@link android.widget.AdapterView.OnItemLongClickListener} has
@@ -303,7 +304,7 @@
// for test stability, turn off title bar
requestWindowFeature(Window.FEATURE_NO_TITLE);
-
+
mScreenHeight = getWindowManager().getDefaultDisplay().getHeight();
@@ -326,7 +327,7 @@
header.setText("Header: " + i);
mListView.addHeaderView(header);
}
-
+
for (int i=0; i<mFooterViewCount; i++) {
TextView header = new TextView(this);
header.setText("Footer: " + i);
@@ -336,7 +337,7 @@
if (params.mConnectAdapter) {
setAdapter(mListView);
}
-
+
mListView.setItemsCanFocus(mItemsFocusable);
if (mStartingSelectionPosition >= 0) {
mListView.setSelection(mStartingSelectionPosition);
@@ -360,11 +361,12 @@
positionClicked(position);
}
});
-
+
// set the fading edge length porportionally to the screen
// height for test stability
if (params.mFadingEdgeScreenSizeFactor != null) {
- mListView.setFadingEdgeLength((int) (params.mFadingEdgeScreenSizeFactor * mScreenHeight));
+ mListView.setFadingEdgeLength(
+ (int) (params.mFadingEdgeScreenSizeFactor * mScreenHeight));
} else {
mListView.setFadingEdgeLength((int) ((64.0 / 480) * mScreenHeight));
}
@@ -403,6 +405,7 @@
mLinearLayout.addView(mListView);
setContentView(mLinearLayout);
}
+ mLinearLayout.restoreDefaultFocus();
}
/**
@@ -426,7 +429,7 @@
}
});
}
-
+
/**
* @return The newly created ListView widget.
*/
@@ -440,16 +443,16 @@
protected Params createParams() {
return new Params();
}
-
+
/**
* Sets an adapter on a ListView.
- *
+ *
* @param listView The ListView to set the adapter on.
*/
protected void setAdapter(ListView listView) {
listView.setAdapter(new MyAdapter());
}
-
+
/**
* Read in and validate all of the params passed in by the scenario.
* @param params
@@ -525,7 +528,7 @@
if (!mIncludeHeader) {
throw new IllegalArgumentException("no header above list");
}
- mHeaderTextView.setText(value);
+ mHeaderTextView.setText(value);
}
/**
@@ -543,12 +546,12 @@
}
/**
- * Convert a non-null view.
+ * Convert a non-null view.
*/
public View convertView(int position, View convertView, ViewGroup parent) {
return ListItemFactory.convertText(convertView, getValueAtPosition(position), position);
}
-
+
public void setClickedPosition(int clickedPosition) {
mClickedPosition = clickedPosition;
}
@@ -580,7 +583,7 @@
}
});
}
-
+
/**
* Return an item type for the specified position in the adapter. Override if your
* adapter creates more than one type.
@@ -596,7 +599,7 @@
public int getViewTypeCount() {
return 1;
}
-
+
/**
* @return The number of times convertView failed
*/
@@ -647,7 +650,7 @@
}
return result;
}
-
+
@Override
public int getItemViewType(int position) {
return ListScenario.this.getItemViewType(position);
diff --git a/core/tests/coretests/src/android/widget/layout/linear/LLOfTwoFocusableInTouchMode.java b/core/tests/coretests/src/android/widget/layout/linear/LLOfTwoFocusableInTouchMode.java
index 1ba56ba..0d8d834 100644
--- a/core/tests/coretests/src/android/widget/layout/linear/LLOfTwoFocusableInTouchMode.java
+++ b/core/tests/coretests/src/android/widget/layout/linear/LLOfTwoFocusableInTouchMode.java
@@ -16,12 +16,12 @@
package android.widget.layout.linear;
-import com.android.frameworks.coretests.R;
-
import android.app.Activity;
import android.os.Bundle;
import android.view.View;
+import com.android.frameworks.coretests.R;
+
public class LLOfTwoFocusableInTouchMode extends Activity {
private View mButton1;
@@ -63,6 +63,7 @@
mB3Fired = true;
}
});
+ getWindow().getDecorView().restoreDefaultFocus();
}
public View getButton1() {
diff --git a/core/tests/coretests/src/android/widget/listview/AdjacentListsWithAdjacentISVsInside.java b/core/tests/coretests/src/android/widget/listview/AdjacentListsWithAdjacentISVsInside.java
index 98fbed3..9e49719 100644
--- a/core/tests/coretests/src/android/widget/listview/AdjacentListsWithAdjacentISVsInside.java
+++ b/core/tests/coretests/src/android/widget/listview/AdjacentListsWithAdjacentISVsInside.java
@@ -16,10 +16,9 @@
package android.widget.listview;
-import android.util.InternalSelectionView;
-
import android.app.Activity;
import android.os.Bundle;
+import android.util.InternalSelectionView;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
@@ -80,6 +79,7 @@
setContentView(combineAdjacent(mLeftListView, mRightListView));
+ getWindow().getDecorView().restoreDefaultFocus();
}
private static View combineAdjacent(View... views) {
diff --git a/core/tests/coretests/src/android/widget/listview/focus/ListHorizontalFocusWithinItemWinsTest.java b/core/tests/coretests/src/android/widget/listview/focus/ListHorizontalFocusWithinItemWinsTest.java
index 8f971bb..edc60b5 100644
--- a/core/tests/coretests/src/android/widget/listview/focus/ListHorizontalFocusWithinItemWinsTest.java
+++ b/core/tests/coretests/src/android/widget/listview/focus/ListHorizontalFocusWithinItemWinsTest.java
@@ -16,15 +16,12 @@
package android.widget.listview.focus;
-import android.widget.listview.ListHorizontalFocusWithinItemWins;
-
import android.test.ActivityInstrumentationTestCase;
import android.test.suitebuilder.annotation.MediumTest;
-import android.view.FocusFinder;
import android.view.KeyEvent;
-import android.view.View;
import android.widget.Button;
import android.widget.ListView;
+import android.widget.listview.ListHorizontalFocusWithinItemWins;
public class ListHorizontalFocusWithinItemWinsTest extends ActivityInstrumentationTestCase<ListHorizontalFocusWithinItemWins> {
@@ -51,12 +48,6 @@
public void testPreconditions() {
assertEquals("list position", 0, mListView.getSelectedItemPosition());
assertTrue("mTopLeftButton.isFocused()", mTopLeftButton.isFocused());
- assertEquals("global focus search to right from top left is bottom middle",
- mBottomMiddleButton,
- FocusFinder.getInstance().findNextFocus(mListView, mTopLeftButton, View.FOCUS_RIGHT));
- assertEquals("global focus search to left from top right is bottom middle",
- mBottomMiddleButton,
- FocusFinder.getInstance().findNextFocus(mListView, mTopRightButton, View.FOCUS_LEFT));
}
@MediumTest
diff --git a/core/tests/coretests/src/android/widget/touchmode/TouchModeFocusChangeTest.java b/core/tests/coretests/src/android/widget/touchmode/TouchModeFocusChangeTest.java
index bd6977e..5a6110c 100644
--- a/core/tests/coretests/src/android/widget/touchmode/TouchModeFocusChangeTest.java
+++ b/core/tests/coretests/src/android/widget/touchmode/TouchModeFocusChangeTest.java
@@ -16,7 +16,6 @@
package android.widget.touchmode;
-import android.widget.layout.linear.LLOfButtons1;
import static android.util.TouchModeFlexibleAsserts.assertInTouchModeAfterClick;
import static android.util.TouchModeFlexibleAsserts.assertInTouchModeAfterTap;
import static android.util.TouchModeFlexibleAsserts.assertNotInTouchModeAfterKey;
@@ -25,6 +24,8 @@
import android.test.suitebuilder.annotation.MediumTest;
import android.view.KeyEvent;
import android.widget.Button;
+import android.widget.layout.linear.LLOfButtons1;
+
/**
* Make sure focus isn't kept by buttons when entering touch mode.
@@ -52,7 +53,6 @@
@MediumTest
public void testPreconditions() {
assertFalse("we should not be in touch mode", mActivity.isInTouchMode());
- assertTrue("top button should have focus", mFirstButton.isFocused());
}
@MediumTest
diff --git a/core/tests/webkit/apk_with_native_libs/Android.mk b/core/tests/webkit/apk_with_native_libs/Android.mk
index 7c6c36e..c51de6a 100644
--- a/core/tests/webkit/apk_with_native_libs/Android.mk
+++ b/core/tests/webkit/apk_with_native_libs/Android.mk
@@ -20,6 +20,7 @@
MY_JNI_SHARED_LIBRARIES := libwebviewtest_jni
MY_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
MY_SRC_FILES := $(call all-java-files-under, src)
+MY_CFLAGS := -Wall -Werror
MY_SDK_VERSION := system_current
MY_PROGUARD_ENABLED := disabled
MY_MULTILIB := both
@@ -40,6 +41,7 @@
LOCAL_JNI_SHARED_LIBRARIES := $(MY_JNI_SHARED_LIBRARIES)
LOCAL_MODULE_PATH := $(MY_MODULE_PATH)
LOCAL_SRC_FILES := $(MY_SRC_FILES)
+LOCAL_CFLAGS := $(MY_CFLAGS)
LOCAL_SDK_VERSION := $(MY_SDK_VERSION)
LOCAL_PROGUARD_ENABLED := $(MY_PROGUARD_ENABLED)
LOCAL_MULTILIB := $(MY_MULTILIB)
@@ -59,6 +61,7 @@
LOCAL_JNI_SHARED_LIBRARIES := $(MY_JNI_SHARED_LIBRARIES)
LOCAL_MODULE_PATH := $(MY_MODULE_PATH)
LOCAL_SRC_FILES := $(MY_SRC_FILES)
+LOCAL_CFLAGS := $(MY_CFLAGS)
LOCAL_SDK_VERSION := $(MY_SDK_VERSION)
LOCAL_PROGUARD_ENABLED := $(MY_PROGUARD_ENABLED)
LOCAL_MULTILIB := $(MY_MULTILIB)
diff --git a/core/tests/webkit/apk_with_native_libs/jni/WebViewTestJniOnLoad.cpp b/core/tests/webkit/apk_with_native_libs/jni/WebViewTestJniOnLoad.cpp
index 9b0502f..0ced4ee 100644
--- a/core/tests/webkit/apk_with_native_libs/jni/WebViewTestJniOnLoad.cpp
+++ b/core/tests/webkit/apk_with_native_libs/jni/WebViewTestJniOnLoad.cpp
@@ -16,6 +16,6 @@
#include <jni.h>
-jint JNI_OnLoad(JavaVM *vm, void *reserved) {
+jint JNI_OnLoad(JavaVM * /*vm*/, void * /*reserved*/) {
return JNI_VERSION_1_4;
}
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index f7e0b46..161a0c2 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -258,6 +258,11 @@
<permission name="android.permission.WRITE_SECURE_SETTINGS"/>
</privapp-permissions>
+ <privapp-permissions package="com.android.settings.intelligence">
+ <permission name="android.permission.READ_SEARCH_INDEXABLES"/>
+ <permission name="android.permission.MODIFY_PHONE_STATE"/>
+ </privapp-permissions>
+
<privapp-permissions package="com.android.sharedstoragebackup">
<permission name="android.permission.WRITE_MEDIA_STORAGE"/>
</privapp-permissions>
diff --git a/keystore/java/android/security/KeyStore.java b/keystore/java/android/security/KeyStore.java
index a7469cd..399dddd 100644
--- a/keystore/java/android/security/KeyStore.java
+++ b/keystore/java/android/security/KeyStore.java
@@ -54,7 +54,7 @@
public class KeyStore {
private static final String TAG = "KeyStore";
- // ResponseCodes
+ // ResponseCodes - see system/security/keystore/include/keystore/keystore.h
public static final int NO_ERROR = 1;
public static final int LOCKED = 2;
public static final int UNINITIALIZED = 3;
@@ -168,10 +168,14 @@
public byte[] get(String key, int uid) {
try {
+ key = key != null ? key : "";
return mBinder.get(key, uid);
} catch (RemoteException e) {
Log.w(TAG, "Cannot connect to keystore", e);
return null;
+ } catch (android.os.ServiceSpecificException e) {
+ Log.w(TAG, "KeyStore exception", e);
+ return null;
}
}
@@ -185,6 +189,9 @@
public int insert(String key, byte[] value, int uid, int flags) {
try {
+ if (value == null) {
+ value = new byte[0];
+ }
return mBinder.insert(key, value, uid, flags);
} catch (RemoteException e) {
Log.w(TAG, "Cannot connect to keystore", e);
@@ -228,6 +235,9 @@
} catch (RemoteException e) {
Log.w(TAG, "Cannot connect to keystore", e);
return null;
+ } catch (android.os.ServiceSpecificException e) {
+ Log.w(TAG, "KeyStore exception", e);
+ return null;
}
}
@@ -276,6 +286,7 @@
*/
public boolean unlock(int userId, String password) {
try {
+ password = password != null ? password : "";
mError = mBinder.unlock(userId, password);
return mError == NO_ERROR;
} catch (RemoteException e) {
@@ -330,16 +341,25 @@
} catch (RemoteException e) {
Log.w(TAG, "Cannot connect to keystore", e);
return null;
+ } catch (android.os.ServiceSpecificException e) {
+ Log.w(TAG, "KeyStore exception", e);
+ return null;
}
+
}
public boolean verify(String key, byte[] data, byte[] signature) {
try {
+ signature = signature != null ? signature : new byte[0];
return mBinder.verify(key, data, signature) == NO_ERROR;
} catch (RemoteException e) {
Log.w(TAG, "Cannot connect to keystore", e);
return false;
+ } catch (android.os.ServiceSpecificException e) {
+ Log.w(TAG, "KeyStore exception", e);
+ return false;
}
+
}
public String grant(String key, int uid) {
@@ -432,6 +452,8 @@
public int generateKey(String alias, KeymasterArguments args, byte[] entropy, int uid,
int flags, KeyCharacteristics outCharacteristics) {
try {
+ entropy = entropy != null ? entropy : new byte[0];
+ args = args != null ? args : new KeymasterArguments();
return mBinder.generateKey(alias, args, entropy, uid, flags, outCharacteristics);
} catch (RemoteException e) {
Log.w(TAG, "Cannot connect to keystore", e);
@@ -447,6 +469,8 @@
public int getKeyCharacteristics(String alias, KeymasterBlob clientId, KeymasterBlob appId,
int uid, KeyCharacteristics outCharacteristics) {
try {
+ clientId = clientId != null ? clientId : new KeymasterBlob(new byte[0]);
+ appId = appId != null ? appId : new KeymasterBlob(new byte[0]);
return mBinder.getKeyCharacteristics(alias, clientId, appId, uid, outCharacteristics);
} catch (RemoteException e) {
Log.w(TAG, "Cannot connect to keystore", e);
@@ -478,6 +502,8 @@
public ExportResult exportKey(String alias, int format, KeymasterBlob clientId,
KeymasterBlob appId, int uid) {
try {
+ clientId = clientId != null ? clientId : new KeymasterBlob(new byte[0]);
+ appId = appId != null ? appId : new KeymasterBlob(new byte[0]);
return mBinder.exportKey(alias, format, clientId, appId, uid);
} catch (RemoteException e) {
Log.w(TAG, "Cannot connect to keystore", e);
@@ -492,6 +518,8 @@
public OperationResult begin(String alias, int purpose, boolean pruneable,
KeymasterArguments args, byte[] entropy, int uid) {
try {
+ args = args != null ? args : new KeymasterArguments();
+ entropy = entropy != null ? entropy : new byte[0];
return mBinder.begin(getToken(), alias, purpose, pruneable, args, entropy, uid);
} catch (RemoteException e) {
Log.w(TAG, "Cannot connect to keystore", e);
@@ -501,11 +529,15 @@
public OperationResult begin(String alias, int purpose, boolean pruneable,
KeymasterArguments args, byte[] entropy) {
+ entropy = entropy != null ? entropy : new byte[0];
+ args = args != null ? args : new KeymasterArguments();
return begin(alias, purpose, pruneable, args, entropy, UID_SELF);
}
public OperationResult update(IBinder token, KeymasterArguments arguments, byte[] input) {
try {
+ arguments = arguments != null ? arguments : new KeymasterArguments();
+ input = input != null ? input : new byte[0];
return mBinder.update(token, arguments, input);
} catch (RemoteException e) {
Log.w(TAG, "Cannot connect to keystore", e);
@@ -516,6 +548,9 @@
public OperationResult finish(IBinder token, KeymasterArguments arguments, byte[] signature,
byte[] entropy) {
try {
+ arguments = arguments != null ? arguments : new KeymasterArguments();
+ entropy = entropy != null ? entropy : new byte[0];
+ signature = signature != null ? signature : new byte[0];
return mBinder.finish(token, arguments, signature, entropy);
} catch (RemoteException e) {
Log.w(TAG, "Cannot connect to keystore", e);
@@ -632,6 +667,12 @@
public int attestKey(
String alias, KeymasterArguments params, KeymasterCertificateChain outChain) {
try {
+ if (params == null) {
+ params = new KeymasterArguments();
+ }
+ if (outChain == null) {
+ outChain = new KeymasterCertificateChain();
+ }
return mBinder.attestKey(alias, params, outChain);
} catch (RemoteException e) {
Log.w(TAG, "Cannot connect to keystore", e);
@@ -641,6 +682,12 @@
public int attestDeviceIds(KeymasterArguments params, KeymasterCertificateChain outChain) {
try {
+ if (params == null) {
+ params = new KeymasterArguments();
+ }
+ if (outChain == null) {
+ outChain = new KeymasterCertificateChain();
+ }
return mBinder.attestDeviceIds(params, outChain);
} catch (RemoteException e) {
Log.w(TAG, "Cannot connect to keystore", e);
diff --git a/libs/androidfw/Android.bp b/libs/androidfw/Android.bp
index 5484cf0..251b2e7 100644
--- a/libs/androidfw/Android.bp
+++ b/libs/androidfw/Android.bp
@@ -98,6 +98,9 @@
enabled: true,
},
},
+ sanitize: {
+ blacklist: "libandroidfw_blacklist.txt",
+ },
}
common_test_libs = [
diff --git a/libs/androidfw/ResourceTypes.cpp b/libs/androidfw/ResourceTypes.cpp
index 5d248c2..3fe75cf 100644
--- a/libs/androidfw/ResourceTypes.cpp
+++ b/libs/androidfw/ResourceTypes.cpp
@@ -812,7 +812,13 @@
*outLen = encLen;
if ((uint32_t)(str+encLen-strings) < mStringPoolSize) {
- return (const char*)str;
+ // Reject malformed (non null-terminated) strings
+ if (str[encLen] != 0x00) {
+ ALOGW("Bad string block: string #%d is not null-terminated",
+ (int)idx);
+ return NULL;
+ }
+ return (const char*)str;
} else {
ALOGW("Bad string block: string #%d extends to %d, past end at %d\n",
(int)idx, (int)(str+encLen-strings), (int)mStringPoolSize);
diff --git a/libs/androidfw/libandroidfw_blacklist.txt b/libs/androidfw/libandroidfw_blacklist.txt
new file mode 100644
index 0000000..dd17e4d
--- /dev/null
+++ b/libs/androidfw/libandroidfw_blacklist.txt
@@ -0,0 +1 @@
+src:*/ResourceTypes.cpp
diff --git a/libs/hwui/Extensions.cpp b/libs/hwui/Extensions.cpp
index b7f1fb2..530e82e 100644
--- a/libs/hwui/Extensions.cpp
+++ b/libs/hwui/Extensions.cpp
@@ -31,11 +31,7 @@
namespace uirenderer {
Extensions::Extensions() {
- if (Properties::getRenderPipelineType() == RenderPipelineType::SkiaVulkan) {
- // Extensions class is used only by OpenGL and SkiaGL pipelines
- // The code below will crash for SkiaVulkan, because OpenGL is not initialized
- // TODO: instantiate Extensions class only for OpenGL pipeline
- // TODO: remove the only usage of Extensions by SkiaGL in SkiaOpenGLReadback::copyImageInto
+ if (Properties::isSkiaEnabled()) {
return;
}
const char* version = (const char*)glGetString(GL_VERSION);
diff --git a/libs/hwui/FrameInfoVisualizer.cpp b/libs/hwui/FrameInfoVisualizer.cpp
index 91756e7..5aea04d 100644
--- a/libs/hwui/FrameInfoVisualizer.cpp
+++ b/libs/hwui/FrameInfoVisualizer.cpp
@@ -245,22 +245,19 @@
// last call to dumpData(). In other words if there's a dumpData(), draw frame,
// dumpData(), the last dumpData() should only log 1 frame.
- FILE* file = fdopen(fd, "a");
- fprintf(file, "\n\tDraw\tPrepare\tProcess\tExecute\n");
+ dprintf(fd, "\n\tDraw\tPrepare\tProcess\tExecute\n");
for (size_t i = 0; i < mFrameSource.size(); i++) {
if (mFrameSource[i][FrameInfoIndex::IntendedVsync] <= mLastFrameLogged) {
continue;
}
mLastFrameLogged = mFrameSource[i][FrameInfoIndex::IntendedVsync];
- fprintf(file, "\t%3.2f\t%3.2f\t%3.2f\t%3.2f\n",
+ dprintf(fd, "\t%3.2f\t%3.2f\t%3.2f\t%3.2f\n",
durationMS(i, FrameInfoIndex::IntendedVsync, FrameInfoIndex::SyncStart),
durationMS(i, FrameInfoIndex::SyncStart, FrameInfoIndex::IssueDrawCommandsStart),
durationMS(i, FrameInfoIndex::IssueDrawCommandsStart, FrameInfoIndex::SwapBuffers),
durationMS(i, FrameInfoIndex::SwapBuffers, FrameInfoIndex::FrameCompleted));
}
-
- fflush(file);
}
} /* namespace uirenderer */
diff --git a/libs/hwui/JankTracker.cpp b/libs/hwui/JankTracker.cpp
index 5b67693..afdd339 100644
--- a/libs/hwui/JankTracker.cpp
+++ b/libs/hwui/JankTracker.cpp
@@ -174,24 +174,22 @@
}
void JankTracker::dumpFrames(int fd) {
- FILE* file = fdopen(fd, "a");
- fprintf(file, "\n\n---PROFILEDATA---\n");
+ dprintf(fd, "\n\n---PROFILEDATA---\n");
for (size_t i = 0; i < static_cast<size_t>(FrameInfoIndex::NumIndexes); i++) {
- fprintf(file, "%s", FrameInfoNames[i].c_str());
- fprintf(file, ",");
+ dprintf(fd, "%s", FrameInfoNames[i].c_str());
+ dprintf(fd, ",");
}
for (size_t i = 0; i < mFrames.size(); i++) {
FrameInfo& frame = mFrames[i];
if (frame[FrameInfoIndex::SyncStart] == 0) {
continue;
}
- fprintf(file, "\n");
+ dprintf(fd, "\n");
for (int i = 0; i < static_cast<int>(FrameInfoIndex::NumIndexes); i++) {
- fprintf(file, "%" PRId64 ",", frame[i]);
+ dprintf(fd, "%" PRId64 ",", frame[i]);
}
}
- fprintf(file, "\n---PROFILEDATA---\n\n");
- fflush(file);
+ dprintf(fd, "\n---PROFILEDATA---\n\n");
}
void JankTracker::reset() {
diff --git a/libs/hwui/Properties.cpp b/libs/hwui/Properties.cpp
index d90caf9..d41db63 100644
--- a/libs/hwui/Properties.cpp
+++ b/libs/hwui/Properties.cpp
@@ -178,7 +178,7 @@
}
RenderPipelineType Properties::getRenderPipelineType() {
- if (RenderPipelineType::NotInitialized != sRenderPipelineType) {
+ if (sRenderPipelineType != RenderPipelineType::NotInitialized) {
return sRenderPipelineType;
}
char prop[PROPERTY_VALUE_MAX];
@@ -196,11 +196,17 @@
return sRenderPipelineType;
}
-#ifdef HWUI_GLES_WRAP_ENABLED
void Properties::overrideRenderPipelineType(RenderPipelineType type) {
+#if !defined(HWUI_GLES_WRAP_ENABLED)
+ // If we're doing actual rendering then we can't change the renderer after it's been set.
+ // Unit tests can freely change this as often as it wants, though, as there's no actual
+ // GL rendering happening
+ if (sRenderPipelineType != RenderPipelineType::NotInitialized) {
+ return;
+ }
+#endif
sRenderPipelineType = type;
}
-#endif
bool Properties::isSkiaEnabled() {
auto renderType = getRenderPipelineType();
diff --git a/libs/hwui/Properties.h b/libs/hwui/Properties.h
index fbd9b7a..9c30e4a 100644
--- a/libs/hwui/Properties.h
+++ b/libs/hwui/Properties.h
@@ -255,10 +255,8 @@
static bool skpCaptureEnabled;
-// Used for testing only to change the render pipeline.
-#ifdef HWUI_GLES_WRAP_ENABLED
+ // Used for testing only to change the render pipeline.
static void overrideRenderPipelineType(RenderPipelineType);
-#endif
private:
static ProfileType sProfileType;
diff --git a/libs/hwui/pipeline/skia/SkiaOpenGLReadback.cpp b/libs/hwui/pipeline/skia/SkiaOpenGLReadback.cpp
index 6db57ca..288d039 100644
--- a/libs/hwui/pipeline/skia/SkiaOpenGLReadback.cpp
+++ b/libs/hwui/pipeline/skia/SkiaOpenGLReadback.cpp
@@ -66,14 +66,8 @@
break;
}
- /* Ideally, we would call grContext->caps()->isConfigRenderable(...). We
- * currently can't do that since some devices (i.e. SwiftShader) supports all
- * the appropriate half float extensions, but only allow the buffer to be read
- * back as full floats. We can relax this extension if Skia implements support
- * for reading back float buffers (skbug.com/6945).
- */
if (pixelConfig == kRGBA_half_GrPixelConfig &&
- !DeviceInfo::get()->extensions().hasRenderableFloatTextures()) {
+ !grContext->caps()->isConfigRenderable(kRGBA_half_GrPixelConfig, false)) {
ALOGW("Can't copy surface into bitmap, RGBA_F16 config is not supported");
return CopyResult::DestinationInvalid;
}
diff --git a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp
index 6657fe8..91b35c2 100644
--- a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp
+++ b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp
@@ -144,22 +144,25 @@
// ----------------------------------------------------------------------------
inline static const SkPaint* bitmapPaint(const SkPaint* origPaint, SkPaint* tmpPaint,
- sk_sp<SkColorFilter> colorFilter) {
- if ((origPaint && origPaint->isAntiAlias()) || colorFilter) {
+ sk_sp<SkColorFilter> colorSpaceFilter) {
+ if ((origPaint && origPaint->isAntiAlias()) || colorSpaceFilter) {
if (origPaint) {
*tmpPaint = *origPaint;
}
- sk_sp<SkColorFilter> filter;
- if (colorFilter && tmpPaint->getColorFilter()) {
- filter = SkColorFilter::MakeComposeFilter(tmpPaint->refColorFilter(), colorFilter);
- LOG_ALWAYS_FATAL_IF(!filter);
- } else {
- filter = colorFilter;
+ if (colorSpaceFilter) {
+ if (tmpPaint->getColorFilter()) {
+ tmpPaint->setColorFilter(
+ SkColorFilter::MakeComposeFilter(tmpPaint->refColorFilter(), colorSpaceFilter));
+ } else {
+ tmpPaint->setColorFilter(colorSpaceFilter);
+ }
+ LOG_ALWAYS_FATAL_IF(!tmpPaint->getColorFilter());
}
+
+ // disabling AA on bitmap draws matches legacy HWUI behavior
tmpPaint->setAntiAlias(false);
- tmpPaint->setColorFilter(filter);
return tmpPaint;
} else {
return origPaint;
diff --git a/libs/hwui/renderthread/RenderThread.cpp b/libs/hwui/renderthread/RenderThread.cpp
index 574bb02..3e2eeee 100644
--- a/libs/hwui/renderthread/RenderThread.cpp
+++ b/libs/hwui/renderthread/RenderThread.cpp
@@ -141,10 +141,8 @@
break;
}
- FILE* file = fdopen(fd, "a");
- fprintf(file, "\n%s\n", cachesOutput.string());
- fprintf(file, "\nPipeline=%s\n", pipeline.string());
- fflush(file);
+ dprintf(fd, "\n%s\n", cachesOutput.string());
+ dprintf(fd, "\nPipeline=%s\n", pipeline.string());
}
Readback& RenderThread::readback() {
diff --git a/libs/hwui/renderthread/VulkanManager.cpp b/libs/hwui/renderthread/VulkanManager.cpp
index 1d8cdd6..a693e68 100644
--- a/libs/hwui/renderthread/VulkanManager.cpp
+++ b/libs/hwui/renderthread/VulkanManager.cpp
@@ -602,6 +602,7 @@
mDeviceWaitIdle(mBackendContext->fDevice);
}
+ SkASSERT(surface->mBackbuffers);
VulkanSurface::BackbufferInfo* backbuffer =
surface->mBackbuffers + surface->mCurrentBackbufferIndex;
GrVkImageInfo* imageInfo;
@@ -683,6 +684,7 @@
}
int VulkanManager::getAge(VulkanSurface* surface) {
+ SkASSERT(surface->mBackbuffers);
VulkanSurface::BackbufferInfo* backbuffer =
surface->mBackbuffers + surface->mCurrentBackbufferIndex;
if (mSwapBehavior == SwapBehavior::Discard ||
diff --git a/libs/hwui/renderthread/VulkanManager.h b/libs/hwui/renderthread/VulkanManager.h
index b9898a7..c319c9e 100644
--- a/libs/hwui/renderthread/VulkanManager.h
+++ b/libs/hwui/renderthread/VulkanManager.h
@@ -59,11 +59,11 @@
VkSurfaceKHR mVkSurface = VK_NULL_HANDLE;
VkSwapchainKHR mSwapchain = VK_NULL_HANDLE;
- BackbufferInfo* mBackbuffers;
+ BackbufferInfo* mBackbuffers = nullptr;
uint32_t mCurrentBackbufferIndex;
uint32_t mImageCount;
- VkImage* mImages;
+ VkImage* mImages = nullptr;
ImageInfo* mImageInfos;
uint16_t mCurrentTime = 0;
};
diff --git a/libs/hwui/tests/common/TestScene.h b/libs/hwui/tests/common/TestScene.h
index 5cd90c5..91022cf 100644
--- a/libs/hwui/tests/common/TestScene.h
+++ b/libs/hwui/tests/common/TestScene.h
@@ -16,6 +16,9 @@
#pragma once
+#include <gui/Surface.h>
+#include <utils/StrongPointer.h>
+
#include <string>
#include <unordered_map>
@@ -66,6 +69,8 @@
static std::unordered_map<std::string, Info>& testMap();
static void registerScene(const Info& info);
+
+ sp<Surface> renderTarget;
};
} // namespace test
diff --git a/libs/hwui/tests/common/scenes/MagnifierAnimation.cpp b/libs/hwui/tests/common/scenes/MagnifierAnimation.cpp
new file mode 100644
index 0000000..9eddc20
--- /dev/null
+++ b/libs/hwui/tests/common/scenes/MagnifierAnimation.cpp
@@ -0,0 +1,79 @@
+/*
+ * 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.
+ */
+
+#include "TestSceneBase.h"
+#include "renderthread/RenderProxy.h"
+#include "utils/Color.h"
+
+class MagnifierAnimation;
+
+static TestScene::Registrar _Magnifier(TestScene::Info{
+ "magnifier", "A sample magnifier using Readback",
+ TestScene::simpleCreateScene<MagnifierAnimation>});
+
+class MagnifierAnimation : public TestScene {
+public:
+ sp<RenderNode> card;
+ sp<RenderNode> zoomImageView;
+
+ void createContent(int width, int height, Canvas& canvas) override {
+ magnifier = TestUtils::createBitmap(200, 100);
+ SkBitmap temp;
+ magnifier->getSkBitmap(&temp);
+ temp.eraseColor(Color::White);
+ canvas.drawColor(Color::White, SkBlendMode::kSrcOver);
+ card = TestUtils::createNode(
+ 0, 0, width, height, [&](RenderProperties& props, Canvas& canvas) {
+ SkPaint paint;
+ paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
+ paint.setAntiAlias(true);
+ paint.setTextSize(50);
+
+ paint.setColor(Color::Black);
+ TestUtils::drawUtf8ToCanvas(&canvas, "Test string", paint, 10, 400);
+ });
+ canvas.drawRenderNode(card.get());
+ zoomImageView = TestUtils::createNode(
+ 100, 100, 500, 300, [&](RenderProperties& props, Canvas& canvas) {
+ props.setElevation(dp(16));
+ props.mutableOutline().setRoundRect(0, 0, props.getWidth(), props.getHeight(),
+ dp(6), 1);
+ props.mutableOutline().setShouldClip(true);
+ canvas.drawBitmap(*magnifier, 0.0f, 0.0f, (float)magnifier->width(),
+ (float)magnifier->height(), 0, 0, (float)props.getWidth(),
+ (float)props.getHeight(), nullptr);
+ });
+ canvas.insertReorderBarrier(true);
+ canvas.drawRenderNode(zoomImageView.get());
+ canvas.insertReorderBarrier(false);
+ }
+
+ void doFrame(int frameNr) override {
+ int curFrame = frameNr % 150;
+ card->mutateStagingProperties().setTranslationX(curFrame);
+ card->setPropertyFieldsDirty(RenderNode::X | RenderNode::Y);
+ if (renderTarget) {
+ SkBitmap temp;
+ magnifier->getSkBitmap(&temp);
+ constexpr int x = 90;
+ constexpr int y = 325;
+ RenderProxy::copySurfaceInto(renderTarget, x, y, x + magnifier->width(),
+ y + magnifier->height(), &temp);
+ }
+ }
+
+ sk_sp<Bitmap> magnifier;
+};
diff --git a/libs/hwui/tests/common/scenes/TextAnimation.cpp b/libs/hwui/tests/common/scenes/TextAnimation.cpp
index b9ce24b..a502116 100644
--- a/libs/hwui/tests/common/scenes/TextAnimation.cpp
+++ b/libs/hwui/tests/common/scenes/TextAnimation.cpp
@@ -15,7 +15,6 @@
*/
#include "TestSceneBase.h"
-#include "utils/Color.h"
class TextAnimation;
diff --git a/libs/hwui/tests/macrobench/TestSceneRunner.cpp b/libs/hwui/tests/macrobench/TestSceneRunner.cpp
index 3eb58a9..9428f53 100644
--- a/libs/hwui/tests/macrobench/TestSceneRunner.cpp
+++ b/libs/hwui/tests/macrobench/TestSceneRunner.cpp
@@ -111,8 +111,6 @@
// Switch to the real display
gDisplay = getBuiltInDisplay();
- std::unique_ptr<TestScene> scene(info.createScene(opts));
-
Properties::forceDrawFrame = true;
TestContext testContext;
testContext.setRenderOffscreen(opts.renderOffscreen);
@@ -122,6 +120,9 @@
const int height = gDisplay.h;
sp<Surface> surface = testContext.surface();
+ std::unique_ptr<TestScene> scene(info.createScene(opts));
+ scene->renderTarget = surface;
+
sp<RenderNode> rootNode = TestUtils::createNode(
0, 0, width, height, [&scene, width, height](RenderProperties& props, Canvas& canvas) {
props.setClipToBounds(false);
diff --git a/libs/hwui/tests/macrobench/main.cpp b/libs/hwui/tests/macrobench/main.cpp
index f1ff939..8c0ca5c 100644
--- a/libs/hwui/tests/macrobench/main.cpp
+++ b/libs/hwui/tests/macrobench/main.cpp
@@ -68,6 +68,7 @@
--onscreen Render tests on device screen. By default tests
are offscreen rendered
--benchmark_format Set output format. Possible values are tabular, json, csv
+ --renderer=TYPE Sets the render pipeline to use. May be opengl, skiagl, or skiavk
)");
}
@@ -146,6 +147,20 @@
return true;
}
+static bool setRenderer(const char* renderer) {
+ if (!strcmp(renderer, "opengl")) {
+ Properties::overrideRenderPipelineType(RenderPipelineType::OpenGL);
+ } else if (!strcmp(renderer, "skiagl")) {
+ Properties::overrideRenderPipelineType(RenderPipelineType::SkiaGL);
+ } else if (!strcmp(renderer, "skiavk")) {
+ Properties::overrideRenderPipelineType(RenderPipelineType::SkiaVulkan);
+ } else {
+ fprintf(stderr, "Unknown format '%s'", renderer);
+ return false;
+ }
+ return true;
+}
+
// For options that only exist in long-form. Anything in the
// 0-255 range is reserved for short options (which just use their ASCII value)
namespace LongOpts {
@@ -158,6 +173,7 @@
BenchmarkFormat,
Onscreen,
Offscreen,
+ Renderer,
};
}
@@ -172,6 +188,7 @@
{"benchmark_format", required_argument, nullptr, LongOpts::BenchmarkFormat},
{"onscreen", no_argument, nullptr, LongOpts::Onscreen},
{"offscreen", no_argument, nullptr, LongOpts::Offscreen},
+ {"renderer", required_argument, nullptr, LongOpts::Renderer},
{0, 0, 0, 0}};
static const char* SHORT_OPTIONS = "c:r:h";
@@ -252,6 +269,16 @@
}
break;
+ case LongOpts::Renderer:
+ if (!optarg) {
+ error = true;
+ break;
+ }
+ if (!setRenderer(optarg)) {
+ error = true;
+ }
+ break;
+
case LongOpts::Onscreen:
gOpts.renderOffscreen = false;
break;
diff --git a/media/java/android/media/AudioAttributes.java b/media/java/android/media/AudioAttributes.java
index 20405d3..7afe267 100644
--- a/media/java/android/media/AudioAttributes.java
+++ b/media/java/android/media/AudioAttributes.java
@@ -244,6 +244,7 @@
SUPPRESSIBLE_USAGES.put(USAGE_GAME, SUPPRESSIBLE_MEDIA_SYSTEM_OTHER);
SUPPRESSIBLE_USAGES.put(USAGE_VOICE_COMMUNICATION_SIGNALLING, SUPPRESSIBLE_MEDIA_SYSTEM_OTHER);
SUPPRESSIBLE_USAGES.put(USAGE_ASSISTANT, SUPPRESSIBLE_MEDIA_SYSTEM_OTHER);
+ SUPPRESSIBLE_USAGES.put(USAGE_UNKNOWN, SUPPRESSIBLE_MEDIA_SYSTEM_OTHER);
}
/**
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index dab7632a..58976ca 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -43,17 +43,16 @@
import android.os.Message;
import android.os.Process;
import android.os.RemoteException;
-import android.os.SystemClock;
import android.os.ServiceManager;
+import android.os.SystemClock;
import android.os.UserHandle;
import android.provider.Settings;
import android.util.ArrayMap;
import android.util.Log;
-import android.util.Pair;
+import android.util.Slog;
import android.view.KeyEvent;
import java.util.ArrayList;
-import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
@@ -1966,9 +1965,28 @@
*/
private boolean querySoundEffectsEnabled(int user) {
return Settings.System.getIntForUser(getContext().getContentResolver(),
- Settings.System.SOUND_EFFECTS_ENABLED, 0, user) != 0;
+ Settings.System.SOUND_EFFECTS_ENABLED, 0, user) != 0
+ && !areSystemSoundsZenModeBlocked(getContext());
}
+ private boolean areSystemSoundsZenModeBlocked(Context context) {
+ int zenMode = Settings.Global.getInt(context.getContentResolver(),
+ Settings.Global.ZEN_MODE, 0);
+
+ switch (zenMode) {
+ case Settings.Global.ZEN_MODE_NO_INTERRUPTIONS:
+ case Settings.Global.ZEN_MODE_ALARMS:
+ return true;
+ case Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS:
+ final NotificationManager noMan = (NotificationManager) context
+ .getSystemService(Context.NOTIFICATION_SERVICE);
+ return (noMan.getNotificationPolicy().priorityCategories
+ & NotificationManager.Policy.PRIORITY_CATEGORY_MEDIA_SYSTEM_OTHER) == 0;
+ case Settings.Global.ZEN_MODE_OFF:
+ default:
+ return false;
+ }
+ }
/**
* Load Sound effects.
diff --git a/media/java/android/media/BufferingParams.java b/media/java/android/media/BufferingParams.java
index 681271b..521e897 100644
--- a/media/java/android/media/BufferingParams.java
+++ b/media/java/android/media/BufferingParams.java
@@ -26,170 +26,68 @@
/**
* Structure for source buffering management params.
*
- * Used by {@link MediaPlayer#getDefaultBufferingParams()},
- * {@link MediaPlayer#getBufferingParams()} and
+ * Used by {@link MediaPlayer#getBufferingParams()} and
* {@link MediaPlayer#setBufferingParams(BufferingParams)}
* to control source buffering behavior.
*
* <p>There are two stages of source buffering in {@link MediaPlayer}: initial buffering
* (when {@link MediaPlayer} is being prepared) and rebuffering (when {@link MediaPlayer}
- * is playing back source). {@link BufferingParams} includes mode and corresponding
- * watermarks for each stage of source buffering. The watermarks could be either size
- * based (in milliseconds), or time based (in kilobytes) or both, depending on the mode.
+ * is playing back source). {@link BufferingParams} includes corresponding marks for each
+ * stage of source buffering. The marks are time based (in milliseconds).
*
- * <p>There are 4 buffering modes: {@link #BUFFERING_MODE_NONE},
- * {@link #BUFFERING_MODE_TIME_ONLY}, {@link #BUFFERING_MODE_SIZE_ONLY} and
- * {@link #BUFFERING_MODE_TIME_THEN_SIZE}.
- * {@link MediaPlayer} source component has default buffering modes which can be queried
- * by calling {@link MediaPlayer#getDefaultBufferingParams()}.
- * Users should always use those default modes or their downsized version when trying to
- * change buffering params. For example, {@link #BUFFERING_MODE_TIME_THEN_SIZE} can be
- * downsized to {@link #BUFFERING_MODE_NONE}, {@link #BUFFERING_MODE_TIME_ONLY} or
- * {@link #BUFFERING_MODE_SIZE_ONLY}. But {@link #BUFFERING_MODE_TIME_ONLY} can not be
- * downsized to {@link #BUFFERING_MODE_SIZE_ONLY}.
+ * <p>{@link MediaPlayer} source component has default marks which can be queried by
+ * calling {@link MediaPlayer#getBufferingParams()} before any change is made by
+ * {@link MediaPlayer#setBufferingParams()}.
* <ul>
- * <li><strong>initial buffering stage:</strong> has one watermark which is used when
- * {@link MediaPlayer} is being prepared. When cached data amount exceeds this watermark,
- * {@link MediaPlayer} is prepared.</li>
- * <li><strong>rebuffering stage:</strong> has two watermarks, low and high, which are
- * used when {@link MediaPlayer} is playing back content.
+ * <li><strong>initial buffering:</strong> initialMarkMs is used when
+ * {@link MediaPlayer} is being prepared. When cached data amount exceeds this mark
+ * {@link MediaPlayer} is prepared. </li>
+ * <li><strong>rebuffering during playback:</strong> resumePlaybackMarkMs is used when
+ * {@link MediaPlayer} is playing back content.
* <ul>
- * <li> When cached data amount exceeds high watermark, {@link MediaPlayer} will pause
- * buffering. Buffering will resume when cache runs below some limit which could be low
- * watermark or some intermediate value decided by the source component.</li>
- * <li> When cached data amount runs below low watermark, {@link MediaPlayer} will paused
- * playback. Playback will resume when cached data amount exceeds high watermark
- * or reaches end of stream.</li>
- * </ul>
+ * <li> {@link MediaPlayer} has internal mark, namely pausePlaybackMarkMs, to decide when
+ * to pause playback if cached data amount runs low. This internal mark varies based on
+ * type of data source. </li>
+ * <li> When cached data amount exceeds resumePlaybackMarkMs, {@link MediaPlayer} will
+ * resume playback if it has been paused due to low cached data amount. The internal mark
+ * pausePlaybackMarkMs shall be less than resumePlaybackMarkMs. </li>
+ * <li> {@link MediaPlayer} has internal mark, namely pauseRebufferingMarkMs, to decide
+ * when to pause rebuffering. Apparently, this internal mark shall be no less than
+ * resumePlaybackMarkMs. </li>
+ * <li> {@link MediaPlayer} has internal mark, namely resumeRebufferingMarkMs, to decide
+ * when to resume buffering. This internal mark varies based on type of data source. This
+ * mark shall be larger than pausePlaybackMarkMs, and less than pauseRebufferingMarkMs.
+ * </li>
+ * </ul> </li>
* </ul>
* <p>Users should use {@link Builder} to change {@link BufferingParams}.
* @hide
*/
public final class BufferingParams implements Parcelable {
- /**
- * This mode indicates that source buffering is not supported.
- */
- public static final int BUFFERING_MODE_NONE = 0;
- /**
- * This mode indicates that only time based source buffering is supported. This means
- * the watermark(s) are time based.
- */
- public static final int BUFFERING_MODE_TIME_ONLY = 1;
- /**
- * This mode indicates that only size based source buffering is supported. This means
- * the watermark(s) are size based.
- */
- public static final int BUFFERING_MODE_SIZE_ONLY = 2;
- /**
- * This mode indicates that both time and size based source buffering are supported,
- * and time based calculation precedes size based. Size based calculation will be used
- * only when time information is not available from the source.
- */
- public static final int BUFFERING_MODE_TIME_THEN_SIZE = 3;
-
- /** @hide */
- @IntDef(
- value = {
- BUFFERING_MODE_NONE,
- BUFFERING_MODE_TIME_ONLY,
- BUFFERING_MODE_SIZE_ONLY,
- BUFFERING_MODE_TIME_THEN_SIZE,
- }
- )
- @Retention(RetentionPolicy.SOURCE)
- public @interface BufferingMode {}
-
- private static final int BUFFERING_NO_WATERMARK = -1;
+ private static final int BUFFERING_NO_MARK = -1;
// params
- private int mInitialBufferingMode = BUFFERING_MODE_NONE;
- private int mRebufferingMode = BUFFERING_MODE_NONE;
+ private int mInitialMarkMs = BUFFERING_NO_MARK;
- private int mInitialWatermarkMs = BUFFERING_NO_WATERMARK;
- private int mInitialWatermarkKB = BUFFERING_NO_WATERMARK;
-
- private int mRebufferingWatermarkLowMs = BUFFERING_NO_WATERMARK;
- private int mRebufferingWatermarkHighMs = BUFFERING_NO_WATERMARK;
- private int mRebufferingWatermarkLowKB = BUFFERING_NO_WATERMARK;
- private int mRebufferingWatermarkHighKB = BUFFERING_NO_WATERMARK;
+ private int mResumePlaybackMarkMs = BUFFERING_NO_MARK;
private BufferingParams() {
}
/**
- * Return the initial buffering mode used when {@link MediaPlayer} is being prepared.
- * @return one of the values that can be set in {@link Builder#setInitialBufferingMode(int)}
+ * Return initial buffering mark in milliseconds.
+ * @return initial buffering mark in milliseconds
*/
- public int getInitialBufferingMode() {
- return mInitialBufferingMode;
+ public int getInitialMarkMs() {
+ return mInitialMarkMs;
}
/**
- * Return the rebuffering mode used when {@link MediaPlayer} is playing back source.
- * @return one of the values that can be set in {@link Builder#setRebufferingMode(int)}
+ * Return the mark in milliseconds for resuming playback.
+ * @return the mark for resuming playback in milliseconds
*/
- public int getRebufferingMode() {
- return mRebufferingMode;
- }
-
- /**
- * Return the time based initial buffering watermark in milliseconds.
- * It is meaningful only when initial buffering mode obatined from
- * {@link #getInitialBufferingMode()} is time based.
- * @return time based initial buffering watermark in milliseconds
- */
- public int getInitialBufferingWatermarkMs() {
- return mInitialWatermarkMs;
- }
-
- /**
- * Return the size based initial buffering watermark in kilobytes.
- * It is meaningful only when initial buffering mode obatined from
- * {@link #getInitialBufferingMode()} is size based.
- * @return size based initial buffering watermark in kilobytes
- */
- public int getInitialBufferingWatermarkKB() {
- return mInitialWatermarkKB;
- }
-
- /**
- * Return the time based low watermark in milliseconds for rebuffering.
- * It is meaningful only when rebuffering mode obatined from
- * {@link #getRebufferingMode()} is time based.
- * @return time based low watermark for rebuffering in milliseconds
- */
- public int getRebufferingWatermarkLowMs() {
- return mRebufferingWatermarkLowMs;
- }
-
- /**
- * Return the time based high watermark in milliseconds for rebuffering.
- * It is meaningful only when rebuffering mode obatined from
- * {@link #getRebufferingMode()} is time based.
- * @return time based high watermark for rebuffering in milliseconds
- */
- public int getRebufferingWatermarkHighMs() {
- return mRebufferingWatermarkHighMs;
- }
-
- /**
- * Return the size based low watermark in kilobytes for rebuffering.
- * It is meaningful only when rebuffering mode obatined from
- * {@link #getRebufferingMode()} is size based.
- * @return size based low watermark for rebuffering in kilobytes
- */
- public int getRebufferingWatermarkLowKB() {
- return mRebufferingWatermarkLowKB;
- }
-
- /**
- * Return the size based high watermark in kilobytes for rebuffering.
- * It is meaningful only when rebuffering mode obatined from
- * {@link #getRebufferingMode()} is size based.
- * @return size based high watermark for rebuffering in kilobytes
- */
- public int getRebufferingWatermarkHighKB() {
- return mRebufferingWatermarkHighKB;
+ public int getResumePlaybackMarkMs() {
+ return mResumePlaybackMarkMs;
}
/**
@@ -200,27 +98,19 @@
* <pre class="prettyprint">
* BufferingParams myParams = mediaplayer.getDefaultBufferingParams();
* myParams = new BufferingParams.Builder(myParams)
- * .setInitialBufferingWatermarkMs(10000)
- * .build();
+ * .setInitialMarkMs(10000)
+ * .setResumePlaybackMarkMs(15000)
+ * .build();
* mediaplayer.setBufferingParams(myParams);
* </pre>
*/
public static class Builder {
- private int mInitialBufferingMode = BUFFERING_MODE_NONE;
- private int mRebufferingMode = BUFFERING_MODE_NONE;
-
- private int mInitialWatermarkMs = BUFFERING_NO_WATERMARK;
- private int mInitialWatermarkKB = BUFFERING_NO_WATERMARK;
-
- private int mRebufferingWatermarkLowMs = BUFFERING_NO_WATERMARK;
- private int mRebufferingWatermarkHighMs = BUFFERING_NO_WATERMARK;
- private int mRebufferingWatermarkLowKB = BUFFERING_NO_WATERMARK;
- private int mRebufferingWatermarkHighKB = BUFFERING_NO_WATERMARK;
+ private int mInitialMarkMs = BUFFERING_NO_MARK;
+ private int mResumePlaybackMarkMs = BUFFERING_NO_MARK;
/**
* Constructs a new Builder with the defaults.
- * By default, both initial buffering mode and rebuffering mode are
- * {@link BufferingParams#BUFFERING_MODE_NONE}, and all watermarks are -1.
+ * By default, all marks are -1.
*/
public Builder() {
}
@@ -231,16 +121,8 @@
* in the new Builder.
*/
public Builder(BufferingParams bp) {
- mInitialBufferingMode = bp.mInitialBufferingMode;
- mRebufferingMode = bp.mRebufferingMode;
-
- mInitialWatermarkMs = bp.mInitialWatermarkMs;
- mInitialWatermarkKB = bp.mInitialWatermarkKB;
-
- mRebufferingWatermarkLowMs = bp.mRebufferingWatermarkLowMs;
- mRebufferingWatermarkHighMs = bp.mRebufferingWatermarkHighMs;
- mRebufferingWatermarkLowKB = bp.mRebufferingWatermarkLowKB;
- mRebufferingWatermarkHighKB = bp.mRebufferingWatermarkHighKB;
+ mInitialMarkMs = bp.mInitialMarkMs;
+ mResumePlaybackMarkMs = bp.mResumePlaybackMarkMs;
}
/**
@@ -250,179 +132,37 @@
* @return a new {@link BufferingParams} object
*/
public BufferingParams build() {
- if (isTimeBasedMode(mRebufferingMode)
- && mRebufferingWatermarkLowMs > mRebufferingWatermarkHighMs) {
- throw new IllegalStateException("Illegal watermark:"
- + mRebufferingWatermarkLowMs + " : " + mRebufferingWatermarkHighMs);
- }
- if (isSizeBasedMode(mRebufferingMode)
- && mRebufferingWatermarkLowKB > mRebufferingWatermarkHighKB) {
- throw new IllegalStateException("Illegal watermark:"
- + mRebufferingWatermarkLowKB + " : " + mRebufferingWatermarkHighKB);
- }
-
BufferingParams bp = new BufferingParams();
- bp.mInitialBufferingMode = mInitialBufferingMode;
- bp.mRebufferingMode = mRebufferingMode;
+ bp.mInitialMarkMs = mInitialMarkMs;
+ bp.mResumePlaybackMarkMs = mResumePlaybackMarkMs;
- bp.mInitialWatermarkMs = mInitialWatermarkMs;
- bp.mInitialWatermarkKB = mInitialWatermarkKB;
-
- bp.mRebufferingWatermarkLowMs = mRebufferingWatermarkLowMs;
- bp.mRebufferingWatermarkHighMs = mRebufferingWatermarkHighMs;
- bp.mRebufferingWatermarkLowKB = mRebufferingWatermarkLowKB;
- bp.mRebufferingWatermarkHighKB = mRebufferingWatermarkHighKB;
return bp;
}
- private boolean isTimeBasedMode(int mode) {
- return (mode == BUFFERING_MODE_TIME_ONLY || mode == BUFFERING_MODE_TIME_THEN_SIZE);
- }
-
- private boolean isSizeBasedMode(int mode) {
- return (mode == BUFFERING_MODE_SIZE_ONLY || mode == BUFFERING_MODE_TIME_THEN_SIZE);
- }
-
/**
- * Sets the initial buffering mode.
- * @param mode one of {@link BufferingParams#BUFFERING_MODE_NONE},
- * {@link BufferingParams#BUFFERING_MODE_TIME_ONLY},
- * {@link BufferingParams#BUFFERING_MODE_SIZE_ONLY},
- * {@link BufferingParams#BUFFERING_MODE_TIME_THEN_SIZE},
+ * Sets the time based mark in milliseconds for initial buffering.
+ * @param markMs time based mark in milliseconds
* @return the same Builder instance.
*/
- public Builder setInitialBufferingMode(@BufferingMode int mode) {
- switch (mode) {
- case BUFFERING_MODE_NONE:
- case BUFFERING_MODE_TIME_ONLY:
- case BUFFERING_MODE_SIZE_ONLY:
- case BUFFERING_MODE_TIME_THEN_SIZE:
- mInitialBufferingMode = mode;
- break;
- default:
- throw new IllegalArgumentException("Illegal buffering mode " + mode);
- }
+ public Builder setInitialMarkMs(int markMs) {
+ mInitialMarkMs = markMs;
return this;
}
/**
- * Sets the rebuffering mode.
- * @param mode one of {@link BufferingParams#BUFFERING_MODE_NONE},
- * {@link BufferingParams#BUFFERING_MODE_TIME_ONLY},
- * {@link BufferingParams#BUFFERING_MODE_SIZE_ONLY},
- * {@link BufferingParams#BUFFERING_MODE_TIME_THEN_SIZE},
+ * Sets the time based mark in milliseconds for resuming playback.
+ * @param markMs time based mark in milliseconds for resuming playback
* @return the same Builder instance.
*/
- public Builder setRebufferingMode(@BufferingMode int mode) {
- switch (mode) {
- case BUFFERING_MODE_NONE:
- case BUFFERING_MODE_TIME_ONLY:
- case BUFFERING_MODE_SIZE_ONLY:
- case BUFFERING_MODE_TIME_THEN_SIZE:
- mRebufferingMode = mode;
- break;
- default:
- throw new IllegalArgumentException("Illegal buffering mode " + mode);
- }
- return this;
- }
-
- /**
- * Sets the time based watermark in milliseconds for initial buffering.
- * @param watermarkMs time based watermark in milliseconds
- * @return the same Builder instance.
- */
- public Builder setInitialBufferingWatermarkMs(int watermarkMs) {
- mInitialWatermarkMs = watermarkMs;
- return this;
- }
-
- /**
- * Sets the size based watermark in kilobytes for initial buffering.
- * @param watermarkKB size based watermark in kilobytes
- * @return the same Builder instance.
- */
- public Builder setInitialBufferingWatermarkKB(int watermarkKB) {
- mInitialWatermarkKB = watermarkKB;
- return this;
- }
-
- /**
- * Sets the time based low watermark in milliseconds for rebuffering.
- * @param watermarkMs time based low watermark in milliseconds
- * @return the same Builder instance.
- */
- public Builder setRebufferingWatermarkLowMs(int watermarkMs) {
- mRebufferingWatermarkLowMs = watermarkMs;
- return this;
- }
-
- /**
- * Sets the time based high watermark in milliseconds for rebuffering.
- * @param watermarkMs time based high watermark in milliseconds
- * @return the same Builder instance.
- */
- public Builder setRebufferingWatermarkHighMs(int watermarkMs) {
- mRebufferingWatermarkHighMs = watermarkMs;
- return this;
- }
-
- /**
- * Sets the size based low watermark in milliseconds for rebuffering.
- * @param watermarkKB size based low watermark in milliseconds
- * @return the same Builder instance.
- */
- public Builder setRebufferingWatermarkLowKB(int watermarkKB) {
- mRebufferingWatermarkLowKB = watermarkKB;
- return this;
- }
-
- /**
- * Sets the size based high watermark in milliseconds for rebuffering.
- * @param watermarkKB size based high watermark in milliseconds
- * @return the same Builder instance.
- */
- public Builder setRebufferingWatermarkHighKB(int watermarkKB) {
- mRebufferingWatermarkHighKB = watermarkKB;
- return this;
- }
-
- /**
- * Sets the time based low and high watermarks in milliseconds for rebuffering.
- * @param lowWatermarkMs time based low watermark in milliseconds
- * @param highWatermarkMs time based high watermark in milliseconds
- * @return the same Builder instance.
- */
- public Builder setRebufferingWatermarksMs(int lowWatermarkMs, int highWatermarkMs) {
- mRebufferingWatermarkLowMs = lowWatermarkMs;
- mRebufferingWatermarkHighMs = highWatermarkMs;
- return this;
- }
-
- /**
- * Sets the size based low and high watermarks in kilobytes for rebuffering.
- * @param lowWatermarkKB size based low watermark in kilobytes
- * @param highWatermarkKB size based high watermark in kilobytes
- * @return the same Builder instance.
- */
- public Builder setRebufferingWatermarksKB(int lowWatermarkKB, int highWatermarkKB) {
- mRebufferingWatermarkLowKB = lowWatermarkKB;
- mRebufferingWatermarkHighKB = highWatermarkKB;
+ public Builder setResumePlaybackMarkMs(int markMs) {
+ mResumePlaybackMarkMs = markMs;
return this;
}
}
private BufferingParams(Parcel in) {
- mInitialBufferingMode = in.readInt();
- mRebufferingMode = in.readInt();
-
- mInitialWatermarkMs = in.readInt();
- mInitialWatermarkKB = in.readInt();
-
- mRebufferingWatermarkLowMs = in.readInt();
- mRebufferingWatermarkHighMs = in.readInt();
- mRebufferingWatermarkLowKB = in.readInt();
- mRebufferingWatermarkHighKB = in.readInt();
+ mInitialMarkMs = in.readInt();
+ mResumePlaybackMarkMs = in.readInt();
}
public static final Parcelable.Creator<BufferingParams> CREATOR =
@@ -446,15 +186,7 @@
@Override
public void writeToParcel(Parcel dest, int flags) {
- dest.writeInt(mInitialBufferingMode);
- dest.writeInt(mRebufferingMode);
-
- dest.writeInt(mInitialWatermarkMs);
- dest.writeInt(mInitialWatermarkKB);
-
- dest.writeInt(mRebufferingWatermarkLowMs);
- dest.writeInt(mRebufferingWatermarkHighMs);
- dest.writeInt(mRebufferingWatermarkLowKB);
- dest.writeInt(mRebufferingWatermarkHighKB);
+ dest.writeInt(mInitialMarkMs);
+ dest.writeInt(mResumePlaybackMarkMs);
}
}
diff --git a/media/java/android/media/MediaPlayer.java b/media/java/android/media/MediaPlayer.java
index 17af44b..649c091 100644
--- a/media/java/android/media/MediaPlayer.java
+++ b/media/java/android/media/MediaPlayer.java
@@ -1698,21 +1698,9 @@
public native boolean isPlaying();
/**
- * Gets the default buffering management params.
- * Calling it only after {@code setDataSource} has been called.
- * Each type of data source might have different set of default params.
- *
- * @return the default buffering management params supported by the source component.
- * @throws IllegalStateException if the internal player engine has not been
- * initialized, or {@code setDataSource} has not been called.
- * @hide
- */
- @NonNull
- public native BufferingParams getDefaultBufferingParams();
-
- /**
* Gets the current buffering management params used by the source component.
* Calling it only after {@code setDataSource} has been called.
+ * Each type of data source might have different set of default params.
*
* @return the current buffering management params used by the source component.
* @throws IllegalStateException if the internal player engine has not been
@@ -1727,8 +1715,7 @@
* The object sets its internal BufferingParams to the input, except that the input is
* invalid or not supported.
* Call it only after {@code setDataSource} has been called.
- * Users should only use supported mode returned by {@link #getDefaultBufferingParams()}
- * or its downsized version as described in {@link BufferingParams}.
+ * The input is a hint to MediaPlayer.
*
* @param params the buffering management params.
*
diff --git a/media/jni/android_media_BufferingParams.h b/media/jni/android_media_BufferingParams.h
index 24c51f5..b004672 100644
--- a/media/jni/android_media_BufferingParams.h
+++ b/media/jni/android_media_BufferingParams.h
@@ -29,14 +29,8 @@
jclass clazz;
jmethodID constructID;
- jfieldID initial_buffering_mode;
- jfieldID rebuffering_mode;
- jfieldID initial_watermark_ms;
- jfieldID initial_watermark_kb;
- jfieldID rebuffering_watermark_low_ms;
- jfieldID rebuffering_watermark_high_ms;
- jfieldID rebuffering_watermark_low_kb;
- jfieldID rebuffering_watermark_high_kb;
+ jfieldID initial_mark_ms;
+ jfieldID resume_playback_mark_ms;
void init(JNIEnv *env) {
jclass lclazz = env->FindClass("android/media/BufferingParams");
@@ -51,14 +45,8 @@
constructID = env->GetMethodID(clazz, "<init>", "()V");
- initial_buffering_mode = env->GetFieldID(clazz, "mInitialBufferingMode", "I");
- rebuffering_mode = env->GetFieldID(clazz, "mRebufferingMode", "I");
- initial_watermark_ms = env->GetFieldID(clazz, "mInitialWatermarkMs", "I");
- initial_watermark_kb = env->GetFieldID(clazz, "mInitialWatermarkKB", "I");
- rebuffering_watermark_low_ms = env->GetFieldID(clazz, "mRebufferingWatermarkLowMs", "I");
- rebuffering_watermark_high_ms = env->GetFieldID(clazz, "mRebufferingWatermarkHighMs", "I");
- rebuffering_watermark_low_kb = env->GetFieldID(clazz, "mRebufferingWatermarkLowKB", "I");
- rebuffering_watermark_high_kb = env->GetFieldID(clazz, "mRebufferingWatermarkHighKB", "I");
+ initial_mark_ms = env->GetFieldID(clazz, "mInitialMarkMs", "I");
+ resume_playback_mark_ms = env->GetFieldID(clazz, "mResumePlaybackMarkMs", "I");
env->DeleteLocalRef(lclazz);
}
@@ -70,22 +58,10 @@
};
void fillFromJobject(JNIEnv *env, const fields_t& fields, jobject params) {
- settings.mInitialBufferingMode =
- (BufferingMode)env->GetIntField(params, fields.initial_buffering_mode);
- settings.mRebufferingMode =
- (BufferingMode)env->GetIntField(params, fields.rebuffering_mode);
- settings.mInitialWatermarkMs =
- env->GetIntField(params, fields.initial_watermark_ms);
- settings.mInitialWatermarkKB =
- env->GetIntField(params, fields.initial_watermark_kb);
- settings.mRebufferingWatermarkLowMs =
- env->GetIntField(params, fields.rebuffering_watermark_low_ms);
- settings.mRebufferingWatermarkHighMs =
- env->GetIntField(params, fields.rebuffering_watermark_high_ms);
- settings.mRebufferingWatermarkLowKB =
- env->GetIntField(params, fields.rebuffering_watermark_low_kb);
- settings.mRebufferingWatermarkHighKB =
- env->GetIntField(params, fields.rebuffering_watermark_high_kb);
+ settings.mInitialMarkMs =
+ env->GetIntField(params, fields.initial_mark_ms);
+ settings.mResumePlaybackMarkMs =
+ env->GetIntField(params, fields.resume_playback_mark_ms);
}
jobject asJobject(JNIEnv *env, const fields_t& fields) {
@@ -93,14 +69,8 @@
if (params == NULL) {
return NULL;
}
- env->SetIntField(params, fields.initial_buffering_mode, (jint)settings.mInitialBufferingMode);
- env->SetIntField(params, fields.rebuffering_mode, (jint)settings.mRebufferingMode);
- env->SetIntField(params, fields.initial_watermark_ms, (jint)settings.mInitialWatermarkMs);
- env->SetIntField(params, fields.initial_watermark_kb, (jint)settings.mInitialWatermarkKB);
- env->SetIntField(params, fields.rebuffering_watermark_low_ms, (jint)settings.mRebufferingWatermarkLowMs);
- env->SetIntField(params, fields.rebuffering_watermark_high_ms, (jint)settings.mRebufferingWatermarkHighMs);
- env->SetIntField(params, fields.rebuffering_watermark_low_kb, (jint)settings.mRebufferingWatermarkLowKB);
- env->SetIntField(params, fields.rebuffering_watermark_high_kb, (jint)settings.mRebufferingWatermarkHighKB);
+ env->SetIntField(params, fields.initial_mark_ms, (jint)settings.mInitialMarkMs);
+ env->SetIntField(params, fields.resume_playback_mark_ms, (jint)settings.mResumePlaybackMarkMs);
return params;
}
diff --git a/media/jni/android_media_MediaPlayer.cpp b/media/jni/android_media_MediaPlayer.cpp
index cfa3cc36..eda22d5 100644
--- a/media/jni/android_media_MediaPlayer.cpp
+++ b/media/jni/android_media_MediaPlayer.cpp
@@ -371,25 +371,6 @@
}
static jobject
-android_media_MediaPlayer_getDefaultBufferingParams(JNIEnv *env, jobject thiz)
-{
- sp<MediaPlayer> mp = getMediaPlayer(env, thiz);
- if (mp == NULL) {
- jniThrowException(env, "java/lang/IllegalStateException", NULL);
- return NULL;
- }
-
- BufferingParams bp;
- BufferingSettings &settings = bp.settings;
- process_media_player_call(
- env, thiz, mp->getDefaultBufferingSettings(&settings),
- "java/lang/IllegalStateException", "unexpected error");
- ALOGV("getDefaultBufferingSettings:{%s}", settings.toString().string());
-
- return bp.asJobject(env, gBufferingParamsFields);
-}
-
-static jobject
android_media_MediaPlayer_getBufferingParams(JNIEnv *env, jobject thiz)
{
sp<MediaPlayer> mp = getMediaPlayer(env, thiz);
@@ -1436,7 +1417,6 @@
{"_setDataSource", "(Ljava/io/FileDescriptor;JJ)V", (void *)android_media_MediaPlayer_setDataSourceFD},
{"_setDataSource", "(Landroid/media/MediaDataSource;)V",(void *)android_media_MediaPlayer_setDataSourceCallback },
{"_setVideoSurface", "(Landroid/view/Surface;)V", (void *)android_media_MediaPlayer_setVideoSurface},
- {"getDefaultBufferingParams", "()Landroid/media/BufferingParams;", (void *)android_media_MediaPlayer_getDefaultBufferingParams},
{"getBufferingParams", "()Landroid/media/BufferingParams;", (void *)android_media_MediaPlayer_getBufferingParams},
{"setBufferingParams", "(Landroid/media/BufferingParams;)V", (void *)android_media_MediaPlayer_setBufferingParams},
{"_prepare", "()V", (void *)android_media_MediaPlayer_prepare},
diff --git a/obex/javax/obex/ObexHelper.java b/obex/javax/obex/ObexHelper.java
index fa50943..478297f 100644
--- a/obex/javax/obex/ObexHelper.java
+++ b/obex/javax/obex/ObexHelper.java
@@ -80,6 +80,9 @@
// The minimum allowed max packet size is 255 according to the OBEX specification
public static final int LOWER_LIMIT_MAX_PACKET_SIZE = 255;
+ // The length of OBEX Byte Sequency Header Id according to the OBEX specification
+ public static final int OBEX_BYTE_SEQ_HEADER_LEN = 0x03;
+
/**
* Temporary workaround to be able to push files to Windows 7.
* TODO: Should be removed as soon as Microsoft updates their driver.
@@ -205,12 +208,15 @@
case 0x40:
boolean trimTail = true;
index++;
- length = 0xFF & headerArray[index];
- length = length << 8;
- index++;
- length += 0xFF & headerArray[index];
- length -= 3;
- index++;
+ length = ((0xFF & headerArray[index]) << 8) +
+ (0xFF & headerArray[index + 1]);
+ index += 2;
+ if (length <= OBEX_BYTE_SEQ_HEADER_LEN) {
+ Log.e(TAG, "Remote sent an OBEX packet with " +
+ "incorrect header length = " + length);
+ break;
+ }
+ length -= OBEX_BYTE_SEQ_HEADER_LEN;
value = new byte[length];
System.arraycopy(headerArray, index, value, 0, length);
if (length == 0 || (length > 0 && (value[length - 1] != 0))) {
diff --git a/packages/SettingsLib/src/com/android/settingslib/Utils.java b/packages/SettingsLib/src/com/android/settingslib/Utils.java
index d90386f..21861692 100644
--- a/packages/SettingsLib/src/com/android/settingslib/Utils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/Utils.java
@@ -94,7 +94,7 @@
public static Drawable getUserIcon(Context context, UserManager um, UserInfo user) {
final int iconSize = UserIconDrawable.getSizeForList(context);
if (user.isManagedProfile()) {
- Drawable drawable = context.getDrawable(com.android.internal.R.drawable.ic_corp_icon);
+ Drawable drawable = context.getDrawable(com.android.internal.R.drawable.ic_corp_badge);
drawable.setBounds(0, 0, iconSize, iconSize);
return drawable;
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/core/AbstractPreferenceController.java b/packages/SettingsLib/src/com/android/settingslib/core/AbstractPreferenceController.java
index 386c4e0..88f0d2b 100644
--- a/packages/SettingsLib/src/com/android/settingslib/core/AbstractPreferenceController.java
+++ b/packages/SettingsLib/src/com/android/settingslib/core/AbstractPreferenceController.java
@@ -20,14 +20,16 @@
* Displays preference in this controller.
*/
public void displayPreference(PreferenceScreen screen) {
+ final String prefKey = getPreferenceKey();
if (isAvailable()) {
+ setVisible(screen, prefKey, true /* visible */);
if (this instanceof Preference.OnPreferenceChangeListener) {
- final Preference preference = screen.findPreference(getPreferenceKey());
+ final Preference preference = screen.findPreference(prefKey);
preference.setOnPreferenceChangeListener(
(Preference.OnPreferenceChangeListener) this);
}
} else {
- removePreference(screen, getPreferenceKey());
+ setVisible(screen, prefKey, false /* visible */);
}
}
@@ -59,13 +61,6 @@
public abstract String getPreferenceKey();
/**
- * Removes preference from screen.
- */
- protected final void removePreference(PreferenceScreen screen, String key) {
- findAndRemovePreference(screen, key);
- }
-
- /**
* Show/hide a preference.
*/
protected final void setVisible(PreferenceGroup group, String key, boolean isVisible) {
@@ -74,25 +69,4 @@
pref.setVisible(isVisible);
}
}
-
- // finds the preference recursively and removes it from its parent
- private boolean findAndRemovePreference(PreferenceGroup prefGroup, String key) {
- final int preferenceCount = prefGroup.getPreferenceCount();
- for (int i = 0; i < preferenceCount; i++) {
- final Preference preference = prefGroup.getPreference(i);
- final String curKey = preference.getKey();
-
- if (curKey != null && curKey.equals(key)) {
- return prefGroup.removePreference(preference);
- }
-
- if (preference instanceof PreferenceGroup) {
- if (findAndRemovePreference((PreferenceGroup) preference, key)) {
- return true;
- }
- }
- }
- return false;
- }
-
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/deviceinfo/AbstractSerialNumberPreferenceController.java b/packages/SettingsLib/src/com/android/settingslib/deviceinfo/AbstractSerialNumberPreferenceController.java
index ff7536a..90f14ef 100644
--- a/packages/SettingsLib/src/com/android/settingslib/deviceinfo/AbstractSerialNumberPreferenceController.java
+++ b/packages/SettingsLib/src/com/android/settingslib/deviceinfo/AbstractSerialNumberPreferenceController.java
@@ -18,18 +18,20 @@
import android.content.Context;
import android.os.Build;
+import android.support.annotation.VisibleForTesting;
import android.support.v7.preference.Preference;
import android.support.v7.preference.PreferenceScreen;
import android.text.TextUtils;
-import com.android.internal.annotations.VisibleForTesting;
import com.android.settingslib.core.AbstractPreferenceController;
/**
* Preference controller for displaying device serial number. Wraps {@link Build#getSerial()}.
*/
public class AbstractSerialNumberPreferenceController extends AbstractPreferenceController {
- private static final String KEY_SERIAL_NUMBER = "serial_number";
+
+ @VisibleForTesting
+ static final String KEY_SERIAL_NUMBER = "serial_number";
private final String mSerialNumber;
diff --git a/packages/SettingsLib/src/com/android/settingslib/drawer/UserAdapter.java b/packages/SettingsLib/src/com/android/settingslib/drawer/UserAdapter.java
index 750601d..b27d823 100644
--- a/packages/SettingsLib/src/com/android/settingslib/drawer/UserAdapter.java
+++ b/packages/SettingsLib/src/com/android/settingslib/drawer/UserAdapter.java
@@ -57,7 +57,7 @@
if (userInfo.isManagedProfile()) {
mName = context.getString(R.string.managed_user_title);
icon = context.getDrawable(
- com.android.internal.R.drawable.ic_corp_icon);
+ com.android.internal.R.drawable.ic_corp_badge);
} else {
mName = userInfo.name;
final int userId = userInfo.id;
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/AbstractPreferenceControllerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/AbstractPreferenceControllerTest.java
index c08bc2a..26970e1 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/AbstractPreferenceControllerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/AbstractPreferenceControllerTest.java
@@ -16,17 +16,10 @@
package com.android.settingslib.core;
import static com.google.common.truth.Truth.assertThat;
-import static org.mockito.Matchers.any;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.content.Context;
import android.support.v7.preference.Preference;
-import android.support.v7.preference.PreferenceGroup;
-import android.support.v7.preference.PreferenceManager;
import android.support.v7.preference.PreferenceScreen;
import com.android.settingslib.TestConfig;
@@ -39,134 +32,58 @@
import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Config;
-import org.robolectric.shadows.ShadowApplication;
@RunWith(RobolectricTestRunner.class)
@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
public class AbstractPreferenceControllerTest {
@Mock
- private Context mContext;
- @Mock
private PreferenceScreen mScreen;
+ private Context mContext;
private Preference mPreference;
private TestPrefController mTestPrefController;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
- mPreference = new Preference(RuntimeEnvironment.application);
+ mContext = RuntimeEnvironment.application;
+ mPreference = new Preference(mContext);
mPreference.setKey(TestPrefController.KEY_PREF);
+ when(mScreen.findPreference(TestPrefController.KEY_PREF)).thenReturn(mPreference);
mTestPrefController = new TestPrefController(mContext);
}
@Test
- public void removeExistingPref_shouldBeRemoved() {
- when(mScreen.getPreferenceCount()).thenReturn(1);
- when(mScreen.getPreference(0)).thenReturn(mPreference);
-
- mTestPrefController.removePreference(mScreen, TestPrefController.KEY_PREF);
-
- verify(mScreen).removePreference(mPreference);
- }
-
- @Test
- public void removeNonExistingPref_shouldNotRemoveAnything() {
- mTestPrefController.removePreference(mScreen, TestPrefController.KEY_PREF);
-
- verify(mScreen, never()).removePreference(any(Preference.class));
- }
-
- @Test
public void displayPref_ifAvailable() {
mTestPrefController.isAvailable = true;
mTestPrefController.displayPreference(mScreen);
- verify(mScreen, never()).removePreference(any(Preference.class));
+ assertThat(mPreference.isVisible()).isTrue();
}
@Test
public void setVisible_prefIsVisible_shouldSetToVisible() {
- when(mScreen.findPreference(TestPrefController.KEY_PREF)).thenReturn(mPreference);
-
mTestPrefController.setVisible(mScreen, TestPrefController.KEY_PREF, true /* visible */);
+
assertThat(mPreference.isVisible()).isTrue();
}
@Test
public void setVisible_prefNotVisible_shouldSetToInvisible() {
- when(mScreen.findPreference(TestPrefController.KEY_PREF)).thenReturn(mPreference);
-
mTestPrefController.setVisible(mScreen, TestPrefController.KEY_PREF, false /* visible */);
+
assertThat(mPreference.isVisible()).isFalse();
}
@Test
public void doNotDisplayPref_ifNotAvailable() {
- when(mScreen.getPreferenceCount()).thenReturn(1);
- when(mScreen.getPreference(0)).thenReturn(mPreference);
mTestPrefController.isAvailable = false;
mTestPrefController.displayPreference(mScreen);
- verify(mScreen).removePreference(any(Preference.class));
- }
-
- @Test
- public void removePreference_shouldRemoveRecursively() {
- final Context context = ShadowApplication.getInstance().getApplicationContext();
- final PreferenceManager preferenceManager = mock(PreferenceManager.class);
- // Top level
- PreferenceScreen prefRoot = spy(new PreferenceScreen(context, null));
- when(prefRoot.getPreferenceManager()).thenReturn(preferenceManager);
- Preference pref1 = mock(Preference.class);
- when(pref1.getKey()).thenReturn("key1");
- PreferenceGroup prefGroup2 = spy(new PreferenceScreen(context, null));
- when(prefGroup2.getPreferenceManager()).thenReturn(preferenceManager);
- when(prefGroup2.getKey()).thenReturn("group2");
- Preference pref3 = mock(Preference.class);
- when(pref3.getKey()).thenReturn("key3");
- PreferenceGroup prefGroup4 = spy(new PreferenceScreen(context, null));
- when(prefGroup4.getPreferenceManager()).thenReturn(preferenceManager);
- when(prefGroup4.getKey()).thenReturn("group4");
- prefRoot.addPreference(pref1);
- prefRoot.addPreference(prefGroup2);
- prefRoot.addPreference(pref3);
- prefRoot.addPreference(prefGroup4);
-
- // 2nd level
- Preference pref21 = mock(Preference.class);
- when(pref21.getKey()).thenReturn("key21");
- Preference pref22 = mock(Preference.class);
- when(pref22.getKey()).thenReturn("key22");
- prefGroup2.addPreference(pref21);
- prefGroup2.addPreference(pref22);
- PreferenceGroup prefGroup41 = spy(new PreferenceScreen(context, null));
- when(prefGroup41.getKey()).thenReturn("group41");
- when(prefGroup41.getPreferenceManager()).thenReturn(preferenceManager);
- Preference pref42 = mock(Preference.class);
- when(pref42.getKey()).thenReturn("key42");
- prefGroup4.addPreference(prefGroup41);
- prefGroup4.addPreference(pref42);
-
- // 3rd level
- Preference pref411 = mock(Preference.class);
- when(pref411.getKey()).thenReturn("key411");
- Preference pref412 = mock(Preference.class);
- when(pref412.getKey()).thenReturn("key412");
- prefGroup41.addPreference(pref411);
- prefGroup41.addPreference(pref412);
-
- mTestPrefController.removePreference(prefRoot, "key1");
- verify(prefRoot).removePreference(pref1);
-
- mTestPrefController.removePreference(prefRoot, "key411");
- verify(prefGroup41).removePreference(pref411);
-
- mTestPrefController.removePreference(prefRoot, "group41");
- verify(prefGroup4).removePreference(prefGroup41);
+ assertThat(mPreference.isVisible()).isFalse();
}
private class TestPrefController extends AbstractPreferenceController {
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/development/EnableAdbPreferenceControllerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/development/EnableAdbPreferenceControllerTest.java
index 32fa01c..aac736a 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/development/EnableAdbPreferenceControllerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/development/EnableAdbPreferenceControllerTest.java
@@ -17,14 +17,9 @@
package com.android.settingslib.development;
import static com.google.common.truth.Truth.assertThat;
-
import static org.mockito.Answers.RETURNS_DEEP_STUBS;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.content.Context;
@@ -67,21 +62,19 @@
shadowContext.setSystemService(Context.USER_SERVICE, mUserManager);
mContext = spy(shadowContext.getApplicationContext());
when(mContext.getPackageManager()).thenReturn(mPackageManager);
- mPreference = new SwitchPreference(mContext);
- when(mScreen.findPreference(anyString())).thenReturn(mPreference);
mController = new ConcreteEnableAdbPreferenceController(mContext);
+ mPreference = new SwitchPreference(mContext);
mPreference.setKey(mController.getPreferenceKey());
+ when(mScreen.findPreference(mPreference.getKey())).thenReturn(mPreference);
}
@Test
public void displayPreference_isNotAdmin_shouldRemovePreference() {
when(mUserManager.isAdminUser()).thenReturn(false);
- when(mScreen.getPreferenceCount()).thenReturn(1);
- when(mScreen.getPreference(0)).thenReturn(mPreference);
mController.displayPreference(mScreen);
- verify(mScreen).removePreference(any(Preference.class));
+ assertThat(mPreference.isVisible()).isFalse();
}
@Test
@@ -90,7 +83,7 @@
mController.displayPreference(mScreen);
- verify(mScreen, never()).removePreference(any(Preference.class));
+ assertThat(mPreference.isVisible()).isTrue();
}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/deviceinfo/SerialNumberPreferenceControllerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/deviceinfo/SerialNumberPreferenceControllerTest.java
index 4dbb957..34bbf4f 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/deviceinfo/SerialNumberPreferenceControllerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/deviceinfo/SerialNumberPreferenceControllerTest.java
@@ -17,13 +17,6 @@
package com.android.settingslib.deviceinfo;
import static com.google.common.truth.Truth.assertThat;
-
-import static org.mockito.Answers.RETURNS_DEEP_STUBS;
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.anyString;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.content.Context;
@@ -38,68 +31,67 @@
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Config;
@RunWith(SettingsLibRobolectricTestRunner.class)
@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
public class SerialNumberPreferenceControllerTest {
- @Mock(answer = RETURNS_DEEP_STUBS)
- private Context mContext;
- @Mock(answer = RETURNS_DEEP_STUBS)
+ @Mock
private PreferenceScreen mScreen;
+ private Context mContext;
+ private Preference mPreference;
private AbstractSerialNumberPreferenceController mController;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
+ mContext = RuntimeEnvironment.application;
+ mPreference = new Preference(mContext);
+ mPreference.setKey(AbstractSerialNumberPreferenceController.KEY_SERIAL_NUMBER);
+ when(mScreen.findPreference(mPreference.getKey())).thenReturn(mPreference);
}
@Test
public void testIsAvaiable_noSerial_shouldReturnFalse() {
- mController = new ConcreteSerialNumberPreferenceController(mContext, null);
+ mController = new TestPreferenceController(mContext, null);
assertThat(mController.isAvailable()).isFalse();
}
@Test
- public void testIsAvaiable_hasSerial_shouldReturnTrue() {
- mController = new ConcreteSerialNumberPreferenceController(mContext, "123");
+ public void testIsAvailable_hasSerial_shouldReturnTrue() {
+ mController = new TestPreferenceController(mContext, "123");
assertThat(mController.isAvailable()).isTrue();
}
@Test
public void testDisplay_noSerial_shouldHidePreference() {
- final Preference preference = mock(Preference.class);
- when(mScreen.getPreferenceCount()).thenReturn(1);
- when(mScreen.getPreference(0)).thenReturn(preference);
- mController = new ConcreteSerialNumberPreferenceController(mContext, null);
- when(preference.getKey()).thenReturn(mController.getPreferenceKey());
+ mController = new TestPreferenceController(mContext, null);
mController.displayPreference(mScreen);
- verify(mScreen).removePreference(any(Preference.class));
+ assertThat(mPreference.isVisible()).isFalse();
}
@Test
public void testDisplay_hasSerial_shouldSetSummary() {
final String serial = "123";
- final Preference preference = mock(Preference.class);
- when(mScreen.findPreference(anyString())).thenReturn(preference);
- mController = new ConcreteSerialNumberPreferenceController(mContext, serial);
+ mController = new TestPreferenceController(mContext, serial);
mController.displayPreference(mScreen);
- verify(mScreen, never()).removePreference(any(Preference.class));
- verify(preference).setSummary(serial);
+ assertThat(mPreference.isVisible()).isTrue();
+ assertThat(mPreference.getSummary()).isEqualTo(serial);
}
- private static class ConcreteSerialNumberPreferenceController
+ private static class TestPreferenceController
extends AbstractSerialNumberPreferenceController {
- ConcreteSerialNumberPreferenceController(Context context, String serialNumber) {
+ TestPreferenceController(Context context, String serialNumber) {
super(context, serialNumber);
}
}
diff --git a/packages/SystemUI/res/layout/volume_dialog.xml b/packages/SystemUI/res/layout/volume_dialog.xml
index 3d9a2dc..fac254a 100644
--- a/packages/SystemUI/res/layout/volume_dialog.xml
+++ b/packages/SystemUI/res/layout/volume_dialog.xml
@@ -13,30 +13,36 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<RelativeLayout
+<com.android.systemui.HardwareUiLayout
xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/volume_dialog"
android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_marginBottom="@dimen/volume_dialog_margin_bottom"
- android:background="@drawable/volume_dialog_background"
- android:paddingTop="@dimen/volume_dialog_padding_top"
+ android:layout_height="match_parent"
android:theme="@style/qs_theme"
- android:translationZ="4dp" >
-
- <LinearLayout
- android:id="@+id/volume_dialog_content"
- android:layout_width="match_parent"
+ android:clipChildren="false" >
+ <RelativeLayout
+ android:id="@+id/volume_dialog"
+ android:layout_width="@dimen/volume_dialog_panel_width"
android:layout_height="wrap_content"
- android:orientation="vertical" >
+ android:layout_gravity="center_vertical|end"
+ android:paddingTop="@dimen/volume_row_padding_bottom"
+ android:layout_margin="12dp"
+ android:background="?android:attr/actionBarItemBackground"
+ android:translationZ="8dp" >
<LinearLayout
- android:id="@+id/volume_dialog_rows"
+ android:id="@+id/volume_dialog_content"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical" >
- <!-- volume rows added and removed here! :-) -->
- </LinearLayout>
- </LinearLayout>
-</RelativeLayout>
+ <LinearLayout
+ android:id="@+id/volume_dialog_rows"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical" >
+ <!-- volume rows added and removed here! :-) -->
+ </LinearLayout>
+ </LinearLayout>
+
+ </RelativeLayout>
+</com.android.systemui.HardwareUiLayout>
diff --git a/packages/SystemUI/res/layout/volume_dialog_row.xml b/packages/SystemUI/res/layout/volume_dialog_row.xml
index 7328d05..bf76e78 100644
--- a/packages/SystemUI/res/layout/volume_dialog_row.xml
+++ b/packages/SystemUI/res/layout/volume_dialog_row.xml
@@ -19,8 +19,8 @@
android:layout_height="@dimen/volume_row_height"
android:clipChildren="false"
android:clipToPadding="false"
- android:orientation="vertical"
- android:paddingBottom="@dimen/volume_row_padding_bottom" >
+ android:theme="@style/qs_theme"
+ android:orientation="vertical" >
<TextView
android:id="@+id/volume_row_header"
@@ -28,7 +28,8 @@
android:layout_height="wrap_content"
android:ellipsize="end"
android:maxLines="1"
- android:textAppearance="@style/TextAppearance.Volume.Header"
+ android:textColor="?android:attr/colorControlNormal"
+ android:textAppearance="?android:attr/textAppearanceSmall"
android:paddingStart="@dimen/volume_row_header_padding_start" />
<LinearLayout
@@ -53,4 +54,9 @@
android:paddingStart="@dimen/volume_row_slider_padding_start"/>
</LinearLayout>
+ <Space
+ android:id="@+id/spacer"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/volume_row_padding_bottom"/>
+
</LinearLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index c678111f..f0bad2a 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -240,7 +240,7 @@
<!-- The width of the panel that holds the quick settings. -->
<dimen name="qs_panel_width">@dimen/notification_panel_width</dimen>
- <dimen name="volume_dialog_panel_width">@dimen/standard_notification_panel_width</dimen>
+ <dimen name="volume_dialog_panel_width">315dp</dimen>
<!-- Gravity for the notification panel -->
<integer name="notification_panel_layout_gravity">0x31</integer><!-- center_horizontal|top -->
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 1536b64..b132160 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -124,16 +124,16 @@
<string name="status_bar_use_physical_keyboard">Physical keyboard</string>
<!-- Prompt for the USB device permission dialog [CHAR LIMIT=80] -->
- <string name="usb_device_permission_prompt">Allow the app <xliff:g id="application">%1$s</xliff:g> to access the USB device?</string>
+ <string name="usb_device_permission_prompt">Allow <xliff:g id="application">%1$s</xliff:g> to access <xliff:g id="usb_device">%2$s</xliff:g>?</string>
<!-- Prompt for the USB accessory permission dialog [CHAR LIMIT=80] -->
- <string name="usb_accessory_permission_prompt">Allow the app <xliff:g id="application">%1$s</xliff:g> to access the USB accessory?</string>
+ <string name="usb_accessory_permission_prompt">Allow <xliff:g id="application">%1$s</xliff:g> to access <xliff:g id="usb_accessory">%2$s</xliff:g>?</string>
<!-- Prompt for the USB device confirm dialog [CHAR LIMIT=80] -->
- <string name="usb_device_confirm_prompt">Open <xliff:g id="activity">%1$s</xliff:g> when this USB device is connected?</string>
+ <string name="usb_device_confirm_prompt">Open <xliff:g id="application">%1$s</xliff:g> to handle <xliff:g id="usb_device">%2$s</xliff:g>?</string>
<!-- Prompt for the USB accessory confirm dialog [CHAR LIMIT=80] -->
- <string name="usb_accessory_confirm_prompt">Open <xliff:g id="activity">%1$s</xliff:g> when this USB accessory is connected?</string>
+ <string name="usb_accessory_confirm_prompt">Open <xliff:g id="application">%1$s</xliff:g> to handle <xliff:g id="usb_accessory">%2$s</xliff:g>?</string>
<!-- Prompt for the USB accessory URI dialog [CHAR LIMIT=80] -->
<string name="usb_accessory_uri_prompt">No installed apps work with this USB accessory. Learn more about this accessory at <xliff:g id="url">%1$s</xliff:g></string>
@@ -145,10 +145,10 @@
<string name="label_view">View</string>
<!-- Checkbox label for USB device dialogs. [CHAR LIMIT=50] -->
- <string name="always_use_device">Use by default for this USB device</string>
+ <string name="always_use_device">Always open <xliff:g id="application">%1$s</xliff:g> when <xliff:g id="usb_device">%2$s</xliff:g> is connected</string>
- <!-- Checkbox label for USB accessory dialogs. [CHAR LIMIT=50] -->
- <string name="always_use_accessory">Use by default for this USB accessory</string>
+ <!-- Checkbox label for USB accessory dialogs. [CHAR LIMIT=50]-->
+ <string name="always_use_accessory">Always open <xliff:g id="application">%1$s</xliff:g> when <xliff:g id="usb_accessory">%2$s</xliff:g> is connected</string>
<!-- Title of confirmation dialog for USB debugging -->
<string name="usb_debugging_title">Allow USB debugging?</string>
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/IOverviewProxy.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl
similarity index 82%
rename from packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/IOverviewProxy.aidl
rename to packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl
index 8cf3be8..173a90a 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/IOverviewProxy.aidl
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl
@@ -14,10 +14,12 @@
* limitations under the License.
*/
-package com.android.systemui.shared.recents.model;
+package com.android.systemui.shared.recents;
import android.view.MotionEvent;
+import com.android.systemui.shared.recents.ISystemUiProxy;
oneway interface IOverviewProxy {
+ void onBind(in ISystemUiProxy sysUiProxy);
void onMotionEvent(in MotionEvent event);
}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl
new file mode 100644
index 0000000..d82b010
--- /dev/null
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.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 com.android.systemui.shared.recents;
+
+import android.graphics.Bitmap;
+import android.graphics.Rect;
+
+/**
+ * Temporary callbacks into SystemUI.
+ */
+interface ISystemUiProxy {
+
+ /**
+ * Proxies SurfaceControl.screenshot().
+ */
+ Bitmap screenshot(in Rect sourceCrop, int width, int height,
+ int minLayer, int maxLayer, boolean useIdentityTransform,
+ int rotation);
+}
diff --git a/packages/SystemUI/src/com/android/systemui/Dependency.java b/packages/SystemUI/src/com/android/systemui/Dependency.java
index 62a9319..e7e70af 100644
--- a/packages/SystemUI/src/com/android/systemui/Dependency.java
+++ b/packages/SystemUI/src/com/android/systemui/Dependency.java
@@ -26,7 +26,7 @@
import android.view.WindowManagerGlobal;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.app.NightDisplayController;
+import com.android.internal.app.ColorDisplayController;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.util.Preconditions;
import com.android.settingslib.bluetooth.LocalBluetoothManager;
@@ -205,8 +205,8 @@
mProviders.put(BatteryController.class, () ->
new BatteryControllerImpl(mContext));
- mProviders.put(NightDisplayController.class, () ->
- new NightDisplayController(mContext));
+ mProviders.put(ColorDisplayController.class, () ->
+ new ColorDisplayController(mContext));
mProviders.put(ManagedProfileController.class, () ->
new ManagedProfileControllerImpl(mContext));
diff --git a/packages/SystemUI/src/com/android/systemui/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/OverviewProxyService.java
index 528e242..039e13c 100644
--- a/packages/SystemUI/src/com/android/systemui/OverviewProxyService.java
+++ b/packages/SystemUI/src/com/android/systemui/OverviewProxyService.java
@@ -20,32 +20,57 @@
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
+import android.graphics.Bitmap;
+import android.graphics.Rect;
+import android.os.Binder;
import android.os.Handler;
import android.os.IBinder;
+import android.os.Looper;
import android.os.RemoteException;
import android.os.UserHandle;
import android.util.Log;
-import com.android.systemui.shared.recents.model.IOverviewProxy;
+import android.view.SurfaceControl;
+
+import com.android.systemui.shared.recents.IOverviewProxy;
+import com.android.systemui.shared.recents.ISystemUiProxy;
+import com.android.systemui.OverviewProxyService.OverviewProxyListener;
+import com.android.systemui.statusbar.policy.CallbackController;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
import com.android.systemui.statusbar.policy.DeviceProvisionedController.DeviceProvisionedListener;
+import java.util.ArrayList;
+import java.util.List;
/**
* Class to send information from overview to launcher with a binder.
*/
-public class OverviewProxyService {
+public class OverviewProxyService implements CallbackController<OverviewProxyListener> {
private static final String TAG = "OverviewProxyService";
private static final long BACKOFF_MILLIS = 5000;
private final Context mContext;
private final Handler mHandler;
- private final Runnable mConnectionRunnable = this::startConnectionToCurrentUser;
+ private final Runnable mConnectionRunnable = this::internalConnectToCurrentUser;
private final DeviceProvisionedController mDeviceProvisionedController
= Dependency.get(DeviceProvisionedController.class);
+ private final List<OverviewProxyListener> mConnectionCallbacks = new ArrayList<>();
private IOverviewProxy mOverviewProxy;
private int mConnectionBackoffAttempts;
+ private ISystemUiProxy mSysUiProxy = new ISystemUiProxy.Stub() {
+ public Bitmap screenshot(Rect sourceCrop, int width, int height, int minLayer, int maxLayer,
+ boolean useIdentityTransform, int rotation) {
+ long token = Binder.clearCallingIdentity();
+ try {
+ return SurfaceControl.screenshot(sourceCrop, width, height, minLayer, maxLayer,
+ useIdentityTransform, rotation);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+ };
+
private final ServiceConnection mOverviewServiceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
@@ -58,6 +83,12 @@
} catch (RemoteException e) {
Log.e(TAG, "Lost connection to launcher service", e);
}
+ try {
+ mOverviewProxy.onBind(mSysUiProxy);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to call onBind()", e);
+ }
+ notifyConnectionChanged();
}
}
@@ -72,14 +103,14 @@
@Override
public void onUserSetupChanged() {
if (mDeviceProvisionedController.isCurrentUserSetup()) {
- startConnectionToCurrentUser();
+ internalConnectToCurrentUser();
}
}
@Override
public void onUserSwitched() {
mConnectionBackoffAttempts = 0;
- startConnectionToCurrentUser();
+ internalConnectToCurrentUser();
}
};
@@ -87,7 +118,7 @@
private final IBinder.DeathRecipient mOverviewServiceDeathRcpt = new IBinder.DeathRecipient() {
@Override
public void binderDied() {
- mOverviewProxy = null;
+ startConnectionToCurrentUser();
}
};
@@ -99,6 +130,14 @@
}
public void startConnectionToCurrentUser() {
+ if (mHandler.getLooper() != Looper.myLooper()) {
+ mHandler.post(mConnectionRunnable);
+ } else {
+ internalConnectToCurrentUser();
+ }
+ }
+
+ private void internalConnectToCurrentUser() {
disconnectFromLauncherService();
// If user has not setup yet or already connected, do not try to connect
@@ -120,14 +159,37 @@
}
}
+ @Override
+ public void addCallback(OverviewProxyListener listener) {
+ mConnectionCallbacks.add(listener);
+ listener.onConnectionChanged(mOverviewProxy != null);
+ }
+
+ @Override
+ public void removeCallback(OverviewProxyListener listener) {
+ mConnectionCallbacks.remove(listener);
+ }
+
public IOverviewProxy getProxy() {
return mOverviewProxy;
}
private void disconnectFromLauncherService() {
if (mOverviewProxy != null) {
+ mOverviewProxy.asBinder().unlinkToDeath(mOverviewServiceDeathRcpt, 0);
mContext.unbindService(mOverviewServiceConnection);
mOverviewProxy = null;
+ notifyConnectionChanged();
}
}
+
+ private void notifyConnectionChanged() {
+ for (int i = mConnectionCallbacks.size() - 1; i >= 0; --i) {
+ mConnectionCallbacks.get(i).onConnectionChanged(mOverviewProxy != null);
+ }
+ }
+
+ public interface OverviewProxyListener {
+ void onConnectionChanged(boolean isConnected);
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
index d82f9cd..00e8b1a 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
@@ -80,7 +80,7 @@
import com.android.systemui.colorextraction.SysuiColorExtractor;
import com.android.systemui.plugins.GlobalActions.GlobalActionsManager;
import com.android.systemui.statusbar.phone.ScrimController;
-import com.android.systemui.volume.VolumeDialogMotion.LogAccelerateInterpolator;
+import com.android.systemui.volume.SystemUIInterpolators.LogAccelerateInterpolator;
import java.util.ArrayList;
import java.util.List;
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 28adca9..a35ba9f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -28,6 +28,7 @@
import android.app.Activity;
import android.app.ActivityManager;
import android.app.AlarmManager;
+import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.StatusBarManager;
import android.app.trust.TrustManager;
@@ -1691,6 +1692,8 @@
mUiOffloadThread.submit(() -> {
// If the stream is muted, don't play the sound
if (mAudioManager.isStreamMute(mUiSoundsStreamType)) return;
+ // If DND blocks the sound, don't play the sound
+ if (areSystemSoundsZenModeBlocked(mContext)) return;
int id = mLockSounds.play(soundId,
mLockSoundVolume, mLockSoundVolume, 1/*priortiy*/, 0/*loop*/, 1.0f/*rate*/);
@@ -1702,6 +1705,25 @@
}
}
+ private boolean areSystemSoundsZenModeBlocked(Context context) {
+ int zenMode = Settings.Global.getInt(context.getContentResolver(),
+ Settings.Global.ZEN_MODE, 0);
+
+ switch (zenMode) {
+ case Settings.Global.ZEN_MODE_NO_INTERRUPTIONS:
+ case Settings.Global.ZEN_MODE_ALARMS:
+ return true;
+ case Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS:
+ final NotificationManager noMan = (NotificationManager) context
+ .getSystemService(Context.NOTIFICATION_SERVICE);
+ return (noMan.getNotificationPolicy().priorityCategories
+ & NotificationManager.Policy.PRIORITY_CATEGORY_MEDIA_SYSTEM_OTHER) == 0;
+ case Settings.Global.ZEN_MODE_OFF:
+ default:
+ return false;
+ }
+ }
+
private void playTrustedSound() {
playSound(mTrustedSoundId);
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/NightDisplayTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/NightDisplayTile.java
index 4c20361..763ffc6 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/NightDisplayTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/NightDisplayTile.java
@@ -22,8 +22,7 @@
import android.service.quicksettings.Tile;
import android.widget.Switch;
-import com.android.internal.app.NightDisplayController;
-import com.android.internal.logging.MetricsLogger;
+import com.android.internal.app.ColorDisplayController;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.systemui.R;
import com.android.systemui.qs.QSHost;
@@ -31,19 +30,19 @@
import com.android.systemui.qs.tileimpl.QSTileImpl;
public class NightDisplayTile extends QSTileImpl<BooleanState>
- implements NightDisplayController.Callback {
+ implements ColorDisplayController.Callback {
- private NightDisplayController mController;
+ private ColorDisplayController mController;
private boolean mIsListening;
public NightDisplayTile(QSHost host) {
super(host);
- mController = new NightDisplayController(mContext, ActivityManager.getCurrentUser());
+ mController = new ColorDisplayController(mContext, ActivityManager.getCurrentUser());
}
@Override
public boolean isAvailable() {
- return NightDisplayController.isAvailable(mContext);
+ return ColorDisplayController.isAvailable(mContext);
}
@Override
@@ -65,7 +64,7 @@
}
// Make a new controller for the new user.
- mController = new NightDisplayController(mContext, newUserId);
+ mController = new ColorDisplayController(mContext, newUserId);
if (mIsListening) {
mController.setListener(this);
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java b/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
index a436e17..5eb315e 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
@@ -208,6 +208,8 @@
*
* @param isHomeStackVisible if provided, will return whether the home stack is visible
* regardless of the recents visibility
+ *
+ * TODO(winsonc): Refactor this check to just use the recents activity lifecycle
*/
public boolean isRecentsActivityVisible(MutableBoolean isHomeStackVisible) {
if (mIam == null) return false;
@@ -222,13 +224,13 @@
final WindowConfiguration winConfig = stackInfo.configuration.windowConfiguration;
final int activityType = winConfig.getActivityType();
final int windowingMode = winConfig.getWindowingMode();
- if (activityType == ACTIVITY_TYPE_HOME) {
+ if (homeStackInfo == null && activityType == ACTIVITY_TYPE_HOME) {
homeStackInfo = stackInfo;
- } else if (activityType == ACTIVITY_TYPE_STANDARD
+ } else if (fullscreenStackInfo == null && activityType == ACTIVITY_TYPE_STANDARD
&& (windowingMode == WINDOWING_MODE_FULLSCREEN
|| windowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY)) {
fullscreenStackInfo = stackInfo;
- } else if (activityType == ACTIVITY_TYPE_RECENTS) {
+ } else if (recentsStackInfo == null && activityType == ACTIVITY_TYPE_RECENTS) {
recentsStackInfo = stackInfo;
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGuts.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGuts.java
index 54d622b..c4024a5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGuts.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGuts.java
@@ -18,46 +18,21 @@
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
-import android.app.INotificationManager;
-import android.app.NotificationChannel;
-import android.app.NotificationManager;
import android.content.Context;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageInfo;
-import android.content.pm.PackageManager;
-import android.content.res.ColorStateList;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.drawable.Drawable;
import android.os.Handler;
-import android.os.RemoteException;
-import android.os.ServiceManager;
-import android.service.notification.NotificationListenerService;
-import android.service.notification.StatusBarNotification;
import android.util.AttributeSet;
-import android.util.Log;
import android.view.View;
import android.view.ViewAnimationUtils;
-import android.view.ViewGroup;
import android.view.accessibility.AccessibilityEvent;
import android.widget.FrameLayout;
-import android.widget.ImageView;
-import android.widget.LinearLayout;
-import android.widget.SeekBar;
-import android.widget.Switch;
-import android.widget.TextView;
-import com.android.internal.logging.MetricsLogger;
-import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
-import com.android.settingslib.Utils;
import com.android.systemui.Interpolators;
import com.android.systemui.R;
-import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
-import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin.MenuItem;
import com.android.systemui.statusbar.stack.StackStateAnimator;
-import java.util.Set;
-
/**
* The guts of a notification revealed when performing a long press.
*/
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGutsManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGutsManager.java
new file mode 100644
index 0000000..b585bdf
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGutsManager.java
@@ -0,0 +1,355 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+package com.android.systemui.statusbar;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.app.INotificationManager;
+import android.app.NotificationChannel;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.res.Resources;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.service.notification.StatusBarNotification;
+import android.util.ArraySet;
+import android.util.Log;
+import android.view.HapticFeedbackConstants;
+import android.view.View;
+import android.view.ViewAnimationUtils;
+import android.view.accessibility.AccessibilityManager;
+
+import com.android.internal.logging.MetricsLogger;
+import com.android.internal.logging.nano.MetricsProto;
+import com.android.systemui.Dependency;
+import com.android.systemui.Dumpable;
+import com.android.systemui.Interpolators;
+import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
+import com.android.systemui.statusbar.phone.StatusBar;
+import com.android.systemui.statusbar.stack.NotificationStackScrollLayout;
+import com.android.systemui.statusbar.stack.StackStateAnimator;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Handles various NotificationGuts related tasks, such as binding guts to a row, opening and
+ * closing guts, and keeping track of the currently exposed notification guts.
+ */
+public class NotificationGutsManager implements Dumpable {
+ private static final String TAG = "NotificationGutsManager";
+
+ // Must match constant in Settings. Used to highlight preferences when linking to Settings.
+ private static final String EXTRA_FRAGMENT_ARG_KEY = ":settings:fragment_args_key";
+
+ private final MetricsLogger mMetricsLogger = Dependency.get(MetricsLogger.class);
+ private final Set<String> mNonBlockablePkgs;
+ private final NotificationPresenter mPresenter;
+ // TODO: Create NotificationListContainer interface and use it instead of
+ // NotificationStackScrollLayout here
+ private final NotificationStackScrollLayout mStackScroller;
+ private final Context mContext;
+ private final AccessibilityManager mAccessibilityManager;
+ // which notification is currently being longpress-examined by the user
+ private NotificationGuts mNotificationGutsExposed;
+ private NotificationMenuRowPlugin.MenuItem mGutsMenuItem;
+ private final NotificationInfo.CheckSaveListener mCheckSaveListener;
+ private String mKeyToRemoveOnGutsClosed;
+
+ public NotificationGutsManager(
+ NotificationPresenter presenter,
+ NotificationStackScrollLayout stackScroller,
+ NotificationInfo.CheckSaveListener checkSaveListener,
+ Context context) {
+ mPresenter = presenter;
+ mStackScroller = stackScroller;
+ mCheckSaveListener = checkSaveListener;
+ mContext = context;
+ Resources res = context.getResources();
+
+ mNonBlockablePkgs = new HashSet<>();
+ Collections.addAll(mNonBlockablePkgs, res.getStringArray(
+ com.android.internal.R.array.config_nonBlockableNotificationPackages));
+
+ mAccessibilityManager = (AccessibilityManager)
+ mContext.getSystemService(Context.ACCESSIBILITY_SERVICE);
+ }
+
+ public String getKeyToRemoveOnGutsClosed() {
+ return mKeyToRemoveOnGutsClosed;
+ }
+
+ public void setKeyToRemoveOnGutsClosed(String keyToRemoveOnGutsClosed) {
+ mKeyToRemoveOnGutsClosed = keyToRemoveOnGutsClosed;
+ }
+
+ private void saveAndCloseNotificationMenu(
+ ExpandableNotificationRow row, NotificationGuts guts, View done) {
+ guts.resetFalsingCheck();
+ int[] rowLocation = new int[2];
+ int[] doneLocation = new int[2];
+ row.getLocationOnScreen(rowLocation);
+ done.getLocationOnScreen(doneLocation);
+
+ final int centerX = done.getWidth() / 2;
+ final int centerY = done.getHeight() / 2;
+ final int x = doneLocation[0] - rowLocation[0] + centerX;
+ final int y = doneLocation[1] - rowLocation[1] + centerY;
+ closeAndSaveGuts(false /* removeLeavebehind */, false /* force */,
+ true /* removeControls */, x, y, true /* resetMenu */);
+ }
+
+ /**
+ * Sends an intent to open the notification settings for a particular package and optional
+ * channel.
+ */
+ private void startAppNotificationSettingsActivity(String packageName, final int appUid,
+ final NotificationChannel channel) {
+ final Intent intent = new Intent(Settings.ACTION_APP_NOTIFICATION_SETTINGS);
+ intent.putExtra(Settings.EXTRA_APP_PACKAGE, packageName);
+ intent.putExtra(Settings.EXTRA_APP_UID, appUid);
+ if (channel != null) {
+ intent.putExtra(EXTRA_FRAGMENT_ARG_KEY, channel.getId());
+ }
+ mPresenter.startNotificationGutsIntent(intent, appUid);
+ }
+
+ public void bindGuts(final ExpandableNotificationRow row) {
+ bindGuts(row, mGutsMenuItem);
+ }
+
+ private void bindGuts(final ExpandableNotificationRow row,
+ NotificationMenuRowPlugin.MenuItem item) {
+ row.inflateGuts();
+ row.setGutsView(item);
+ final StatusBarNotification sbn = row.getStatusBarNotification();
+ row.setTag(sbn.getPackageName());
+ final NotificationGuts guts = row.getGuts();
+ guts.setClosedListener((NotificationGuts g) -> {
+ if (!g.willBeRemoved() && !row.isRemoved()) {
+ mStackScroller.onHeightChanged(
+ row, !mPresenter.isPresenterFullyCollapsed() /* needsAnimation */);
+ }
+ if (mNotificationGutsExposed == g) {
+ mNotificationGutsExposed = null;
+ mGutsMenuItem = null;
+ }
+ String key = sbn.getKey();
+ if (key.equals(mKeyToRemoveOnGutsClosed)) {
+ mKeyToRemoveOnGutsClosed = null;
+ mPresenter.removeNotification(key, mPresenter.getLatestRankingMap());
+ }
+ });
+
+ View gutsView = item.getGutsView();
+ if (gutsView instanceof NotificationSnooze) {
+ NotificationSnooze snoozeGuts = (NotificationSnooze) gutsView;
+ snoozeGuts.setSnoozeListener(mStackScroller.getSwipeActionHelper());
+ snoozeGuts.setStatusBarNotification(sbn);
+ snoozeGuts.setSnoozeOptions(row.getEntry().snoozeCriteria);
+ guts.setHeightChangedListener((NotificationGuts g) -> {
+ mStackScroller.onHeightChanged(row, row.isShown() /* needsAnimation */);
+ });
+ }
+
+ if (gutsView instanceof NotificationInfo) {
+ final UserHandle userHandle = sbn.getUser();
+ PackageManager pmUser = StatusBar.getPackageManagerForUser(mContext,
+ userHandle.getIdentifier());
+ final INotificationManager iNotificationManager = INotificationManager.Stub.asInterface(
+ ServiceManager.getService(Context.NOTIFICATION_SERVICE));
+ final String pkg = sbn.getPackageName();
+ NotificationInfo info = (NotificationInfo) gutsView;
+ // Settings link is only valid for notifications that specify a user, unless this is the
+ // system user.
+ NotificationInfo.OnSettingsClickListener onSettingsClick = null;
+ if (!userHandle.equals(UserHandle.ALL)
+ || mPresenter.getCurrentUserId() == UserHandle.USER_SYSTEM) {
+ onSettingsClick = (View v, NotificationChannel channel, int appUid) -> {
+ mMetricsLogger.action(MetricsProto.MetricsEvent.ACTION_NOTE_INFO);
+ guts.resetFalsingCheck();
+ startAppNotificationSettingsActivity(pkg, appUid, channel);
+ };
+ }
+ final NotificationInfo.OnAppSettingsClickListener onAppSettingsClick = (View v,
+ Intent intent) -> {
+ mMetricsLogger.action(MetricsProto.MetricsEvent.ACTION_APP_NOTE_SETTINGS);
+ guts.resetFalsingCheck();
+ mPresenter.startNotificationGutsIntent(intent, sbn.getUid());
+ };
+ final View.OnClickListener onDoneClick = (View v) -> {
+ saveAndCloseNotificationMenu(row, guts, v);
+ };
+
+ ArraySet<NotificationChannel> channels = new ArraySet<>();
+ channels.add(row.getEntry().channel);
+ if (row.isSummaryWithChildren()) {
+ // If this is a summary, then add in the children notification channels for the
+ // same user and pkg.
+ final List<ExpandableNotificationRow> childrenRows = row.getNotificationChildren();
+ final int numChildren = childrenRows.size();
+ for (int i = 0; i < numChildren; i++) {
+ final ExpandableNotificationRow childRow = childrenRows.get(i);
+ final NotificationChannel childChannel = childRow.getEntry().channel;
+ final StatusBarNotification childSbn = childRow.getStatusBarNotification();
+ if (childSbn.getUser().equals(userHandle) &&
+ childSbn.getPackageName().equals(pkg)) {
+ channels.add(childChannel);
+ }
+ }
+ }
+ try {
+ info.bindNotification(pmUser, iNotificationManager, pkg, new ArrayList(channels),
+ row.getEntry().channel.getImportance(), sbn, onSettingsClick,
+ onAppSettingsClick, onDoneClick, mCheckSaveListener,
+ mNonBlockablePkgs);
+ } catch (RemoteException e) {
+ Log.e(TAG, e.toString());
+ }
+ }
+ }
+
+ /**
+ * Closes guts or notification menus that might be visible and saves any changes.
+ *
+ * @param removeLeavebehinds true if leavebehinds (e.g. snooze) should be closed.
+ * @param force true if guts should be closed regardless of state (used for snooze only).
+ * @param removeControls true if controls (e.g. info) should be closed.
+ * @param x if closed based on touch location, this is the x touch location.
+ * @param y if closed based on touch location, this is the y touch location.
+ * @param resetMenu if any notification menus that might be revealed should be closed.
+ */
+ public void closeAndSaveGuts(boolean removeLeavebehinds, boolean force, boolean removeControls,
+ int x, int y, boolean resetMenu) {
+ if (mNotificationGutsExposed != null) {
+ mNotificationGutsExposed.closeControls(removeLeavebehinds, removeControls, x, y, force);
+ }
+ if (resetMenu) {
+ mStackScroller.resetExposedMenuView(false /* animate */, true /* force */);
+ }
+ }
+
+ /**
+ * Returns the exposed NotificationGuts or null if none are exposed.
+ */
+ public NotificationGuts getExposedGuts() {
+ return mNotificationGutsExposed;
+ }
+
+ public void setExposedGuts(NotificationGuts guts) {
+ mNotificationGutsExposed = guts;
+ }
+
+ /**
+ * Opens guts on the given ExpandableNotificationRow |v|.
+ *
+ * @param v ExpandableNotificationRow to open guts on
+ * @param x x coordinate of origin of circular reveal
+ * @param y y coordinate of origin of circular reveal
+ * @param item MenuItem the guts should display
+ * @return true if guts was opened
+ */
+ public boolean openGuts(View v, int x, int y,
+ NotificationMenuRowPlugin.MenuItem item) {
+ if (!(v instanceof ExpandableNotificationRow)) {
+ return false;
+ }
+
+ if (v.getWindowToken() == null) {
+ Log.e(TAG, "Trying to show notification guts, but not attached to window");
+ return false;
+ }
+
+ final ExpandableNotificationRow row = (ExpandableNotificationRow) v;
+ if (row.isDark()) {
+ return false;
+ }
+ v.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
+ if (row.areGutsExposed()) {
+ closeAndSaveGuts(false /* removeLeavebehind */, false /* force */,
+ true /* removeControls */, -1 /* x */, -1 /* y */,
+ true /* resetMenu */);
+ return false;
+ }
+ bindGuts(row, item);
+ NotificationGuts guts = row.getGuts();
+
+ // Assume we are a status_bar_notification_row
+ if (guts == null) {
+ // This view has no guts. Examples are the more card or the dismiss all view
+ return false;
+ }
+
+ mMetricsLogger.action(MetricsProto.MetricsEvent.ACTION_NOTE_CONTROLS);
+
+ // ensure that it's laid but not visible until actually laid out
+ guts.setVisibility(View.INVISIBLE);
+ // Post to ensure the the guts are properly laid out.
+ guts.post(new Runnable() {
+ @Override
+ public void run() {
+ if (row.getWindowToken() == null) {
+ Log.e(TAG, "Trying to show notification guts, but not attached to "
+ + "window");
+ return;
+ }
+ closeAndSaveGuts(true /* removeLeavebehind */, true /* force */,
+ true /* removeControls */, -1 /* x */, -1 /* y */,
+ false /* resetMenu */);
+ guts.setVisibility(View.VISIBLE);
+ final double horz = Math.max(guts.getWidth() - x, x);
+ final double vert = Math.max(guts.getHeight() - y, y);
+ final float r = (float) Math.hypot(horz, vert);
+ final Animator a
+ = ViewAnimationUtils.createCircularReveal(guts, x, y, 0, r);
+ a.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
+ a.setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN);
+ a.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ super.onAnimationEnd(animation);
+ // Move the notification view back over the menu
+ row.resetTranslation();
+ }
+ });
+ a.start();
+ final boolean needsFalsingProtection =
+ (mPresenter.isPresenterLocked() &&
+ !mAccessibilityManager.isTouchExplorationEnabled());
+ guts.setExposed(true /* exposed */, needsFalsingProtection);
+ row.closeRemoteInput();
+ mStackScroller.onHeightChanged(row, true /* needsAnimation */);
+ mNotificationGutsExposed = guts;
+ mGutsMenuItem = item;
+ }
+ });
+ return true;
+ }
+
+ @Override
+ public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ pw.print("mKeyToRemoveOnGutsClosed: ");
+ pw.println(mKeyToRemoveOnGutsClosed);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationInfo.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationInfo.java
index 3b23a0c..8d1bb5f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationInfo.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationInfo.java
@@ -84,7 +84,7 @@
public interface CheckSaveListener {
// Invoked when importance has changed and the NotificationInfo wants to try to save it.
// Listener should run saveImportance unless the change should be canceled.
- void checkSave(Runnable saveImportance);
+ void checkSave(Runnable saveImportance, StatusBarNotification sbn);
}
public interface OnSettingsClickListener {
@@ -409,7 +409,7 @@
public boolean handleCloseControls(boolean save, boolean force) {
if (save && hasImportanceChanged()) {
if (mCheckSaveListener != null) {
- mCheckSaveListener.checkSave(() -> { saveImportance(); });
+ mCheckSaveListener.checkSave(this::saveImportance, mSbn);
} else {
saveImportance();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
new file mode 100644
index 0000000..e65bab2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
@@ -0,0 +1,245 @@
+package com.android.systemui.statusbar;
+
+import android.app.Notification;
+import android.content.Context;
+import android.media.MediaMetadata;
+import android.media.session.MediaController;
+import android.media.session.MediaSession;
+import android.media.session.MediaSessionManager;
+import android.media.session.PlaybackState;
+import android.os.UserHandle;
+import android.util.Log;
+
+import com.android.systemui.Dumpable;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Handles tasks and state related to media notifications. For example, there is a 'current' media
+ * notification, which this class keeps track of.
+ */
+public class NotificationMediaManager implements Dumpable {
+ private static final String TAG = "NotificationMediaManager";
+ public static final boolean DEBUG_MEDIA = false;
+
+ private final NotificationPresenter mPresenter;
+ private final Context mContext;
+ private final MediaSessionManager mMediaSessionManager;
+ private MediaController mMediaController;
+ private String mMediaNotificationKey;
+ private MediaMetadata mMediaMetadata;
+
+ private final MediaController.Callback mMediaListener = new MediaController.Callback() {
+ @Override
+ public void onPlaybackStateChanged(PlaybackState state) {
+ super.onPlaybackStateChanged(state);
+ if (DEBUG_MEDIA) {
+ Log.v(TAG, "DEBUG_MEDIA: onPlaybackStateChanged: " + state);
+ }
+ if (state != null) {
+ if (!isPlaybackActive(state.getState())) {
+ clearCurrentMediaNotification();
+ mPresenter.updateMediaMetaData(true, true);
+ }
+ }
+ }
+
+ @Override
+ public void onMetadataChanged(MediaMetadata metadata) {
+ super.onMetadataChanged(metadata);
+ if (DEBUG_MEDIA) {
+ Log.v(TAG, "DEBUG_MEDIA: onMetadataChanged: " + metadata);
+ }
+ mMediaMetadata = metadata;
+ mPresenter.updateMediaMetaData(true, true);
+ }
+ };
+
+ public NotificationMediaManager(NotificationPresenter presenter, Context context) {
+ mPresenter = presenter;
+ mContext = context;
+ mMediaSessionManager
+ = (MediaSessionManager) mContext.getSystemService(Context.MEDIA_SESSION_SERVICE);
+ // TODO: use MediaSessionManager.SessionListener to hook us up to future updates
+ // in session state
+ }
+
+ public void onNotificationRemoved(String key) {
+ if (key.equals(mMediaNotificationKey)) {
+ clearCurrentMediaNotification();
+ mPresenter.updateMediaMetaData(true, true);
+ }
+ }
+
+ public String getMediaNotificationKey() {
+ return mMediaNotificationKey;
+ }
+
+ public MediaMetadata getMediaMetadata() {
+ return mMediaMetadata;
+ }
+
+ public void findAndUpdateMediaNotifications() {
+ boolean metaDataChanged = false;
+
+ synchronized (mPresenter.getNotificationData()) {
+ ArrayList<NotificationData.Entry> activeNotifications = mPresenter
+ .getNotificationData().getActiveNotifications();
+ final int N = activeNotifications.size();
+
+ // Promote the media notification with a controller in 'playing' state, if any.
+ NotificationData.Entry mediaNotification = null;
+ MediaController controller = null;
+ for (int i = 0; i < N; i++) {
+ final NotificationData.Entry entry = activeNotifications.get(i);
+
+ if (isMediaNotification(entry)) {
+ final MediaSession.Token token =
+ entry.notification.getNotification().extras.getParcelable(
+ Notification.EXTRA_MEDIA_SESSION);
+ if (token != null) {
+ MediaController aController = new MediaController(mContext, token);
+ if (PlaybackState.STATE_PLAYING ==
+ getMediaControllerPlaybackState(aController)) {
+ if (DEBUG_MEDIA) {
+ Log.v(TAG, "DEBUG_MEDIA: found mediastyle controller matching "
+ + entry.notification.getKey());
+ }
+ mediaNotification = entry;
+ controller = aController;
+ break;
+ }
+ }
+ }
+ }
+ if (mediaNotification == null) {
+ // Still nothing? OK, let's just look for live media sessions and see if they match
+ // one of our notifications. This will catch apps that aren't (yet!) using media
+ // notifications.
+
+ if (mMediaSessionManager != null) {
+ // TODO: Should this really be for all users?
+ final List<MediaController> sessions
+ = mMediaSessionManager.getActiveSessionsForUser(
+ null,
+ UserHandle.USER_ALL);
+
+ for (MediaController aController : sessions) {
+ if (PlaybackState.STATE_PLAYING ==
+ getMediaControllerPlaybackState(aController)) {
+ // now to see if we have one like this
+ final String pkg = aController.getPackageName();
+
+ for (int i = 0; i < N; i++) {
+ final NotificationData.Entry entry = activeNotifications.get(i);
+ if (entry.notification.getPackageName().equals(pkg)) {
+ if (DEBUG_MEDIA) {
+ Log.v(TAG, "DEBUG_MEDIA: found controller matching "
+ + entry.notification.getKey());
+ }
+ controller = aController;
+ mediaNotification = entry;
+ break;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ if (controller != null && !sameSessions(mMediaController, controller)) {
+ // We have a new media session
+ clearCurrentMediaNotification();
+ mMediaController = controller;
+ mMediaController.registerCallback(mMediaListener);
+ mMediaMetadata = mMediaController.getMetadata();
+ if (DEBUG_MEDIA) {
+ Log.v(TAG, "DEBUG_MEDIA: insert listener, receive metadata: "
+ + mMediaMetadata);
+ }
+
+ if (mediaNotification != null) {
+ mMediaNotificationKey = mediaNotification.notification.getKey();
+ if (DEBUG_MEDIA) {
+ Log.v(TAG, "DEBUG_MEDIA: Found new media notification: key="
+ + mMediaNotificationKey + " controller=" + mMediaController);
+ }
+ }
+ metaDataChanged = true;
+ }
+ }
+
+ if (metaDataChanged) {
+ mPresenter.updateNotifications();
+ }
+ mPresenter.updateMediaMetaData(metaDataChanged, true);
+ }
+
+ public void clearCurrentMediaNotification() {
+ mMediaNotificationKey = null;
+ mMediaMetadata = null;
+ if (mMediaController != null) {
+ if (DEBUG_MEDIA) {
+ Log.v(TAG, "DEBUG_MEDIA: Disconnecting from old controller: "
+ + mMediaController.getPackageName());
+ }
+ mMediaController.unregisterCallback(mMediaListener);
+ }
+ mMediaController = null;
+ }
+
+ @Override
+ public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ pw.print(" mMediaSessionManager=");
+ pw.println(mMediaSessionManager);
+ pw.print(" mMediaNotificationKey=");
+ pw.println(mMediaNotificationKey);
+ pw.print(" mMediaController=");
+ pw.print(mMediaController);
+ if (mMediaController != null) {
+ pw.print(" state=" + mMediaController.getPlaybackState());
+ }
+ pw.println();
+ pw.print(" mMediaMetadata=");
+ pw.print(mMediaMetadata);
+ if (mMediaMetadata != null) {
+ pw.print(" title=" + mMediaMetadata.getText(MediaMetadata.METADATA_KEY_TITLE));
+ }
+ pw.println();
+ }
+
+ private boolean isPlaybackActive(int state) {
+ return state != PlaybackState.STATE_STOPPED && state != PlaybackState.STATE_ERROR
+ && state != PlaybackState.STATE_NONE;
+ }
+
+ private boolean sameSessions(MediaController a, MediaController b) {
+ if (a == b) {
+ return true;
+ }
+ if (a == null) {
+ return false;
+ }
+ return a.controlsSameSession(b);
+ }
+
+ private int getMediaControllerPlaybackState(MediaController controller) {
+ if (controller != null) {
+ final PlaybackState playbackState = controller.getPlaybackState();
+ if (playbackState != null) {
+ return playbackState.getState();
+ }
+ }
+ return PlaybackState.STATE_NONE;
+ }
+
+ private boolean isMediaNotification(NotificationData.Entry entry) {
+ // TODO: confirm that there's a valid media key
+ return entry.getExpandedContentView() != null &&
+ entry.getExpandedContentView()
+ .findViewById(com.android.internal.R.id.media_actions) != null;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationPresenter.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationPresenter.java
new file mode 100644
index 0000000..1aca60c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationPresenter.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+package com.android.systemui.statusbar;
+
+import android.content.Intent;
+import android.service.notification.NotificationListenerService;
+
+/**
+ * An abstraction of something that presents notifications, e.g. StatusBar. Contains methods
+ * for both querying the state of the system (some modularised piece of functionality may
+ * want to act differently based on e.g. whether the presenter is visible to the user or not) and
+ * for affecting the state of the system (e.g. starting an intent, given that the presenter may
+ * want to perform some action before doing so).
+ */
+public interface NotificationPresenter {
+
+ /**
+ * Returns true if the presenter is not visible. For example, it may not be necessary to do
+ * animations if this returns true.
+ */
+ boolean isPresenterFullyCollapsed();
+
+ /**
+ * Returns true if the presenter is locked. For example, if the keyguard is active.
+ */
+ boolean isPresenterLocked();
+
+ /**
+ * Returns the current user id. This can change if the user is switched.
+ */
+ int getCurrentUserId();
+
+ /**
+ * Runs the given intent. The presenter may want to run some animations or close itself when
+ * this happens.
+ */
+ void startNotificationGutsIntent(Intent intent, int appUid);
+
+ /**
+ * Returns NotificationData.
+ */
+ NotificationData getNotificationData();
+
+ // TODO: Create NotificationEntryManager and move this method to there.
+ /**
+ * Signals that some notifications have changed, and NotificationPresenter should update itself.
+ */
+ void updateNotifications();
+
+ /**
+ * Refresh or remove lockscreen artwork from media metadata or the lockscreen wallpaper.
+ */
+ void updateMediaMetaData(boolean metaDataChanged, boolean allowEnterAnimation);
+
+ // TODO: Create NotificationUpdateHandler and move this method to there.
+ /**
+ * Removes a notification.
+ */
+ void removeNotification(String key, NotificationListenerService.RankingMap ranking);
+
+ // TODO: Create NotificationEntryManager and move this method to there.
+ /**
+ * Gets the latest ranking map.
+ */
+ NotificationListenerService.RankingMap getLatestRankingMap();
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/MessagingLayoutTransformState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/MessagingLayoutTransformState.java
index 4c2a2f5..fc420eb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/MessagingLayoutTransformState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/MessagingLayoutTransformState.java
@@ -383,6 +383,12 @@
}
}
+ @Override
+ public void prepareFadeIn() {
+ super.prepareFadeIn();
+ setVisible(true /* visible */, false /* force */);
+ }
+
private void resetTransformedView(View child) {
TransformState ownState = TransformState.createFrom(child, mTransformInfo);
ownState.resetTransformedView();
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 1bd90fa..149ec0b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java
@@ -20,7 +20,7 @@
import android.provider.Settings.Secure;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.app.NightDisplayController;
+import com.android.internal.app.ColorDisplayController;
import com.android.systemui.Dependency;
import com.android.systemui.Prefs;
import com.android.systemui.Prefs.Key;
@@ -78,8 +78,8 @@
}
if (!mAutoTracker.isAdded(NIGHT)
- && NightDisplayController.isAvailable(mContext)) {
- Dependency.get(NightDisplayController.class).setListener(mNightDisplayCallback);
+ && ColorDisplayController.isAvailable(mContext)) {
+ Dependency.get(ColorDisplayController.class).setListener(mColorDisplayCallback);
}
}
@@ -89,7 +89,7 @@
Dependency.get(HotspotController.class).removeCallback(mHotspotCallback);
Dependency.get(DataSaverController.class).removeCallback(mDataSaverListener);
Dependency.get(ManagedProfileController.class).removeCallback(mProfileCallback);
- Dependency.get(NightDisplayController.class).setListener(null);
+ Dependency.get(ColorDisplayController.class).setListener(null);
}
private final ManagedProfileController.Callback mProfileCallback =
@@ -139,8 +139,8 @@
};
@VisibleForTesting
- final NightDisplayController.Callback mNightDisplayCallback =
- new NightDisplayController.Callback() {
+ final ColorDisplayController.Callback mColorDisplayCallback =
+ new ColorDisplayController.Callback() {
@Override
public void onActivated(boolean activated) {
if (activated) {
@@ -150,8 +150,8 @@
@Override
public void onAutoModeChanged(int autoMode) {
- if (autoMode == NightDisplayController.AUTO_MODE_CUSTOM
- || autoMode == NightDisplayController.AUTO_MODE_TWILIGHT) {
+ if (autoMode == ColorDisplayController.AUTO_MODE_CUSTOM
+ || autoMode == ColorDisplayController.AUTO_MODE_TWILIGHT) {
addNightTile();
}
}
@@ -160,7 +160,7 @@
if (mAutoTracker.isAdded(NIGHT)) return;
mHost.addTile(NIGHT);
mAutoTracker.setTileAdded(NIGHT);
- mHandler.post(() -> Dependency.get(NightDisplayController.class)
+ mHandler.post(() -> Dependency.get(ColorDisplayController.class)
.setListener(null));
}
};
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarGestureHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarGestureHelper.java
index 3ba9ac6..4180884 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarGestureHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarGestureHelper.java
@@ -35,7 +35,7 @@
import com.android.systemui.R;
import com.android.systemui.RecentsComponent;
import com.android.systemui.plugins.statusbar.phone.NavGesture.GestureHelper;
-import com.android.systemui.shared.recents.model.IOverviewProxy;
+import com.android.systemui.shared.recents.IOverviewProxy;
import com.android.systemui.stackdivider.Divider;
import com.android.systemui.tuner.TunerService;
@@ -112,20 +112,26 @@
mIsRTL = isRTL;
}
- public boolean onInterceptTouchEvent(MotionEvent event) {
+ private boolean proxyMotionEvents(MotionEvent event) {
final IOverviewProxy overviewProxy = mOverviewEventSender.getProxy();
if (overviewProxy != null) {
mNavigationBarView.requestUnbufferedDispatch(event);
try {
overviewProxy.onMotionEvent(event);
+ return true;
} catch (RemoteException e) {
Log.e(TAG, "Callback failed", e);
}
}
+ return false;
+ }
- // If we move more than a fixed amount, then start capturing for the
- // task switcher detector
- mTaskSwitcherDetector.onTouchEvent(event);
+ public boolean onInterceptTouchEvent(MotionEvent event) {
+ if (!proxyMotionEvents(event)) {
+ // If we move more than a fixed amount, then start capturing for the
+ // task switcher detector, disabled when proxying motion events to launcher service
+ mTaskSwitcherDetector.onTouchEvent(event);
+ }
int action = event.getAction();
switch (action & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN: {
@@ -289,7 +295,7 @@
}
public boolean onTouchEvent(MotionEvent event) {
- boolean result = mTaskSwitcherDetector.onTouchEvent(event);
+ boolean result = proxyMotionEvents(event) || mTaskSwitcherDetector.onTouchEvent(event);
if (mDockWindowEnabled) {
result |= handleDockWindowEvent(event);
}
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 094129c..4e7f205 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
@@ -47,6 +47,8 @@
import com.android.settingslib.Utils;
import com.android.systemui.Dependency;
import com.android.systemui.DockedStackExistsListener;
+import com.android.systemui.OverviewProxyService;
+import com.android.systemui.OverviewProxyService.OverviewProxyListener;
import com.android.systemui.R;
import com.android.systemui.RecentsComponent;
import com.android.systemui.plugins.PluginListener;
@@ -196,6 +198,9 @@
}
}
+ private final OverviewProxyListener mOverviewProxyListener =
+ isConnected -> setSlippery(!isConnected);
+
public NavigationBarView(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -531,6 +536,24 @@
}
}
+ private void setSlippery(boolean slippery) {
+ boolean changed = false;
+ final ViewGroup navbarView = ((ViewGroup) getParent());
+ final WindowManager.LayoutParams lp = (WindowManager.LayoutParams) navbarView
+ .getLayoutParams();
+ if (slippery && (lp.flags & WindowManager.LayoutParams.FLAG_SLIPPERY) == 0) {
+ lp.flags |= WindowManager.LayoutParams.FLAG_SLIPPERY;
+ changed = true;
+ } else if (!slippery && (lp.flags & WindowManager.LayoutParams.FLAG_SLIPPERY) != 0) {
+ lp.flags &= ~WindowManager.LayoutParams.FLAG_SLIPPERY;
+ changed = true;
+ }
+ if (changed) {
+ WindowManager wm = (WindowManager)getContext().getSystemService(Context.WINDOW_SERVICE);
+ wm.updateViewLayout(navbarView, lp);
+ }
+ }
+
public void setMenuVisibility(final boolean show) {
setMenuVisibility(show, false);
}
@@ -756,6 +779,7 @@
onPluginDisconnected(null); // Create default gesture helper
Dependency.get(PluginManager.class).addPluginListener(this,
NavGesture.class, false /* Only one */);
+ Dependency.get(OverviewProxyService.class).addCallback(mOverviewProxyListener);
}
@Override
@@ -765,6 +789,7 @@
if (mGestureHelper != null) {
mGestureHelper.destroy();
}
+ Dependency.get(OverviewProxyService.class).removeCallback(mOverviewProxyListener);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
index af03440..86a8f41 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
@@ -596,7 +596,7 @@
mLastCameraLaunchSource = KeyguardBottomAreaView.CAMERA_LAUNCH_SOURCE_AFFORDANCE;
}
closeQs();
- mStatusBar.closeAndSaveGuts(true /* leavebehind */, true /* force */,
+ mStatusBar.getGutsManager().closeAndSaveGuts(true /* leavebehind */, true /* force */,
true /* controls */, -1 /* x */, -1 /* y */, true /* resetMenu */);
mNotificationStackScroller.setOverScrollAmount(0f, true /* onTop */, false /* animate */,
true /* cancelAnimators */);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
index 46e3aa1..e3ed581 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -20,11 +20,12 @@
import static android.app.StatusBarManager.WINDOW_STATE_HIDDEN;
import static android.app.StatusBarManager.WINDOW_STATE_SHOWING;
import static android.app.StatusBarManager.windowStateToString;
-
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY;
+
import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_ASLEEP;
import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_AWAKE;
import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_WAKING;
+import static com.android.systemui.statusbar.NotificationMediaManager.DEBUG_MEDIA;
import static com.android.systemui.statusbar.notification.NotificationInflater.InflationCallback;
import static com.android.systemui.statusbar.phone.BarTransitions.MODE_LIGHTS_OUT;
import static com.android.systemui.statusbar.phone.BarTransitions.MODE_LIGHTS_OUT_TRANSPARENT;
@@ -40,10 +41,8 @@
import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.ActivityOptions;
-import android.app.INotificationManager;
import android.app.KeyguardManager;
import android.app.Notification;
-import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.RemoteInput;
@@ -80,10 +79,7 @@
import android.graphics.drawable.Drawable;
import android.media.AudioAttributes;
import android.media.MediaMetadata;
-import android.media.session.MediaController;
-import android.media.session.MediaSession;
import android.media.session.MediaSessionManager;
-import android.media.session.PlaybackState;
import android.metrics.LogMaker;
import android.net.Uri;
import android.os.AsyncTask;
@@ -115,14 +111,12 @@
import android.util.SparseArray;
import android.util.SparseBooleanArray;
import android.view.Display;
-import android.view.HapticFeedbackConstants;
import android.view.IWindowManager;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.ThreadedRenderer;
import android.view.View;
-import android.view.ViewAnimationUtils;
import android.view.ViewGroup;
import android.view.ViewParent;
import android.view.ViewTreeObserver;
@@ -181,7 +175,6 @@
import com.android.systemui.keyguard.WakefulnessLifecycle;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.qs.QS;
-import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin.MenuItem;
import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper.SnoozeOption;
import com.android.systemui.qs.QSFragment;
import com.android.systemui.qs.QSPanel;
@@ -207,10 +200,11 @@
import com.android.systemui.statusbar.KeyguardIndicationController;
import com.android.systemui.statusbar.NotificationData;
import com.android.systemui.statusbar.NotificationData.Entry;
-import com.android.systemui.statusbar.NotificationGuts;
+import com.android.systemui.statusbar.NotificationGutsManager;
import com.android.systemui.statusbar.NotificationInfo;
+import com.android.systemui.statusbar.NotificationMediaManager;
+import com.android.systemui.statusbar.NotificationPresenter;
import com.android.systemui.statusbar.NotificationShelf;
-import com.android.systemui.statusbar.NotificationSnooze;
import com.android.systemui.statusbar.RemoteInputController;
import com.android.systemui.statusbar.ScrimView;
import com.android.systemui.statusbar.SignalClusterView;
@@ -243,7 +237,6 @@
import com.android.systemui.statusbar.stack.NotificationStackScrollLayout;
import com.android.systemui.statusbar.stack.NotificationStackScrollLayout
.OnChildLocationsChangedListener;
-import com.android.systemui.statusbar.stack.StackStateAnimator;
import com.android.systemui.util.NotificationChannels;
import com.android.systemui.util.leak.LeakDetector;
import com.android.systemui.volume.VolumeComponent;
@@ -255,10 +248,8 @@
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
-import java.util.HashSet;
import java.util.List;
import java.util.Map;
-import java.util.Set;
import java.util.Stack;
public class StatusBar extends SystemUI implements DemoMode,
@@ -267,7 +258,7 @@
ActivatableNotificationView.OnActivatedListener,
ExpandableNotificationRow.ExpansionLogger, NotificationData.Environment,
ExpandableNotificationRow.OnExpandClickListener, InflationCallback,
- ColorExtractor.OnColorsChangedListener, ConfigurationListener {
+ ColorExtractor.OnColorsChangedListener, ConfigurationListener, NotificationPresenter {
public static final boolean MULTIUSER_DEBUG = false;
public static final boolean ENABLE_REMOTE_INPUT =
@@ -287,9 +278,6 @@
protected static final boolean ENABLE_HEADS_UP = true;
protected static final String SETTING_HEADS_UP_TICKER = "ticker_gets_heads_up";
- // Must match constant in Settings. Used to highlight preferences when linking to Settings.
- private static final String EXTRA_FRAGMENT_ARG_KEY = ":settings:fragment_args_key";
-
private static final String PERMISSION_SELF = "com.android.systemui.permission.SELF";
// Should match the values in PhoneWindowManager
@@ -308,7 +296,6 @@
public static final boolean SPEW = false;
public static final boolean DUMPTRUCK = true; // extra dumpsys info
public static final boolean DEBUG_GESTURES = false;
- public static final boolean DEBUG_MEDIA = false;
public static final boolean DEBUG_MEDIA_FAKE_ARTWORK = false;
public static final boolean DEBUG_CAMERA_LIFT = false;
@@ -458,6 +445,8 @@
private final int[] mAbsPos = new int[2];
private final ArrayList<Runnable> mPostCollapseRunnables = new ArrayList<>();
+ private NotificationGutsManager mGutsManager;
+
// for disabling the status bar
private int mDisabled1 = 0;
private int mDisabled2 = 0;
@@ -553,31 +542,7 @@
protected final PorterDuffXfermode mSrcOverXferMode =
new PorterDuffXfermode(PorterDuff.Mode.SRC_OVER);
- private MediaSessionManager mMediaSessionManager;
- private MediaController mMediaController;
- private String mMediaNotificationKey;
- private MediaMetadata mMediaMetadata;
- private final MediaController.Callback mMediaListener = new MediaController.Callback() {
- @Override
- public void onPlaybackStateChanged(PlaybackState state) {
- super.onPlaybackStateChanged(state);
- if (DEBUG_MEDIA) Log.v(TAG, "DEBUG_MEDIA: onPlaybackStateChanged: " + state);
- if (state != null) {
- if (!isPlaybackActive(state.getState())) {
- clearCurrentMediaNotification();
- updateMediaMetaData(true, true);
- }
- }
- }
-
- @Override
- public void onMetadataChanged(MediaMetadata metadata) {
- super.onMetadataChanged(metadata);
- if (DEBUG_MEDIA) Log.v(TAG, "DEBUG_MEDIA: onMetadataChanged: " + metadata);
- mMediaMetadata = metadata;
- updateMediaMetaData(true, true);
- }
- };
+ private NotificationMediaManager mMediaManager;
/** Keys of notifications currently visible to the user. */
private final ArraySet<NotificationVisibility> mCurrentlyVisibleNotifications =
@@ -700,7 +665,6 @@
private final HashMap<String, Entry> mPendingNotifications = new HashMap<>();
private boolean mClearAllEnabled;
@Nullable private View mAmbientIndicationContainer;
- private String mKeyToRemoveOnGutsClosed;
private SysuiColorExtractor mColorExtractor;
private ForegroundServiceController mForegroundServiceController;
private ScreenLifecycle mScreenLifecycle;
@@ -817,6 +781,8 @@
mKeyguardManager = (KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE);
mLockPatternUtils = new LockPatternUtils(mContext);
+ mMediaManager = new NotificationMediaManager(this, mContext);
+
// Connect in to the status bar manager service
mCommandQueue = getComponent(CommandQueue.class);
mCommandQueue.addCallbacks(this);
@@ -903,16 +869,8 @@
Slog.e(TAG, "Failed to register VR mode state listener: " + e);
}
- mNonBlockablePkgs = new HashSet<>();
- Collections.addAll(mNonBlockablePkgs, res.getStringArray(
- com.android.internal.R.array.config_nonBlockableNotificationPackages));
// end old BaseStatusBar.start().
- mMediaSessionManager
- = (MediaSessionManager) mContext.getSystemService(Context.MEDIA_SESSION_SERVICE);
- // TODO: use MediaSessionManager.SessionListener to hook us up to future updates
- // in session state
-
// Lastly, call to the icon policy to install/update all the icons.
mIconPolicy = new PhoneStatusBarPolicy(mContext, mIconController);
mSettingsObserver.onChange(false); // set up
@@ -960,6 +918,8 @@
// into fragments, but the rest here, it leaves some awkward lifecycle and whatnot.
mNotificationPanel = mStatusBarWindow.findViewById(R.id.notification_panel);
mStackScroller = mStatusBarWindow.findViewById(R.id.notification_stack_scroller);
+ mGutsManager = new NotificationGutsManager(this, mStackScroller,
+ mCheckSaveListener, mContext);
mNotificationPanel.setStatusBar(this);
mNotificationPanel.setGroupManager(mGroupManager);
mAboveShelfObserver = new AboveShelfObserver(mStackScroller);
@@ -1302,12 +1262,12 @@
ArrayList<Entry> activeNotifications = mNotificationData.getActiveNotifications();
for (int i = 0; i < activeNotifications.size(); i++) {
Entry entry = activeNotifications.get(i);
- boolean exposedGuts = mNotificationGutsExposed != null
- && entry.row.getGuts() == mNotificationGutsExposed;
+ boolean exposedGuts = mGutsManager.getExposedGuts() != null
+ && entry.row.getGuts() == mGutsManager.getExposedGuts();
entry.row.onDensityOrFontScaleChanged();
if (exposedGuts) {
- mNotificationGutsExposed = entry.row.getGuts();
- bindGuts(entry.row, mGutsMenuItem);
+ mGutsManager.setExposedGuts(entry.row.getGuts());
+ mGutsManager.bindGuts(entry.row);
}
}
}
@@ -1672,6 +1632,7 @@
updateNotifications();
}
+ @Override
public void removeNotification(String key, RankingMap ranking) {
boolean deferRemoval = false;
abortExistingInflation(key);
@@ -1685,10 +1646,8 @@
|| !mVisualStabilityManager.isReorderingAllowed();
deferRemoval = !mHeadsUpManager.removeNotification(key, ignoreEarliestRemovalTime);
}
- if (key.equals(mMediaNotificationKey)) {
- clearCurrentMediaNotification();
- updateMediaMetaData(true, true);
- }
+ mMediaManager.onNotificationRemoved(key);
+
Entry entry = mNotificationData.get(key);
if (FORCE_REMOTE_INPUT_HISTORY && mRemoteInputController.isSpinning(key)
&& entry.row != null && !entry.row.isDismissed()) {
@@ -1744,12 +1703,12 @@
mRemoteInputEntriesToRemoveOnCollapse.add(entry);
return;
}
- if (entry != null && mNotificationGutsExposed != null
- && mNotificationGutsExposed == entry.row.getGuts() && entry.row.getGuts() != null
- && !entry.row.getGuts().isLeavebehind()) {
+ if (entry != null && mGutsManager.getExposedGuts() != null
+ && mGutsManager.getExposedGuts() == entry.row.getGuts()
+ && entry.row.getGuts() != null && !entry.row.getGuts().isLeavebehind()) {
Log.w(TAG, "Keeping notification because it's showing guts. " + key);
mLatestRankingMap = ranking;
- mKeyToRemoveOnGutsClosed = key;
+ mGutsManager.setKeyToRemoveOnGutsClosed(key);
return;
}
@@ -2142,7 +2101,8 @@
return entry.row.getParent() instanceof NotificationStackScrollLayout;
}
- protected void updateNotifications() {
+ @Override
+ public void updateNotifications() {
mNotificationData.filterAndSort();
updateNotificationShade();
@@ -2184,136 +2144,9 @@
}
}
- findAndUpdateMediaNotifications();
+ mMediaManager.findAndUpdateMediaNotifications();
}
- public void findAndUpdateMediaNotifications() {
- boolean metaDataChanged = false;
-
- synchronized (mNotificationData) {
- ArrayList<Entry> activeNotifications = mNotificationData.getActiveNotifications();
- final int N = activeNotifications.size();
-
- // Promote the media notification with a controller in 'playing' state, if any.
- Entry mediaNotification = null;
- MediaController controller = null;
- for (int i = 0; i < N; i++) {
- final Entry entry = activeNotifications.get(i);
-
- if (isMediaNotification(entry)) {
- final MediaSession.Token token =
- entry.notification.getNotification().extras.getParcelable(
- Notification.EXTRA_MEDIA_SESSION);
- if (token != null) {
- MediaController aController = new MediaController(mContext, token);
- if (PlaybackState.STATE_PLAYING ==
- getMediaControllerPlaybackState(aController)) {
- if (DEBUG_MEDIA) {
- Log.v(TAG, "DEBUG_MEDIA: found mediastyle controller matching "
- + entry.notification.getKey());
- }
- mediaNotification = entry;
- controller = aController;
- break;
- }
- }
- }
- }
- if (mediaNotification == null) {
- // Still nothing? OK, let's just look for live media sessions and see if they match
- // one of our notifications. This will catch apps that aren't (yet!) using media
- // notifications.
-
- if (mMediaSessionManager != null) {
- final List<MediaController> sessions
- = mMediaSessionManager.getActiveSessionsForUser(
- null,
- UserHandle.USER_ALL);
-
- for (MediaController aController : sessions) {
- if (PlaybackState.STATE_PLAYING ==
- getMediaControllerPlaybackState(aController)) {
- // now to see if we have one like this
- final String pkg = aController.getPackageName();
-
- for (int i = 0; i < N; i++) {
- final Entry entry = activeNotifications.get(i);
- if (entry.notification.getPackageName().equals(pkg)) {
- if (DEBUG_MEDIA) {
- Log.v(TAG, "DEBUG_MEDIA: found controller matching "
- + entry.notification.getKey());
- }
- controller = aController;
- mediaNotification = entry;
- break;
- }
- }
- }
- }
- }
- }
-
- if (controller != null && !sameSessions(mMediaController, controller)) {
- // We have a new media session
- clearCurrentMediaNotification();
- mMediaController = controller;
- mMediaController.registerCallback(mMediaListener);
- mMediaMetadata = mMediaController.getMetadata();
- if (DEBUG_MEDIA) {
- Log.v(TAG, "DEBUG_MEDIA: insert listener, receive metadata: "
- + mMediaMetadata);
- }
-
- if (mediaNotification != null) {
- mMediaNotificationKey = mediaNotification.notification.getKey();
- if (DEBUG_MEDIA) {
- Log.v(TAG, "DEBUG_MEDIA: Found new media notification: key="
- + mMediaNotificationKey + " controller=" + mMediaController);
- }
- }
- metaDataChanged = true;
- }
- }
-
- if (metaDataChanged) {
- updateNotifications();
- }
- updateMediaMetaData(metaDataChanged, true);
- }
-
- private int getMediaControllerPlaybackState(MediaController controller) {
- if (controller != null) {
- final PlaybackState playbackState = controller.getPlaybackState();
- if (playbackState != null) {
- return playbackState.getState();
- }
- }
- return PlaybackState.STATE_NONE;
- }
-
- private boolean isPlaybackActive(int state) {
- return state != PlaybackState.STATE_STOPPED && state != PlaybackState.STATE_ERROR
- && state != PlaybackState.STATE_NONE;
- }
-
- private void clearCurrentMediaNotification() {
- mMediaNotificationKey = null;
- mMediaMetadata = null;
- if (mMediaController != null) {
- if (DEBUG_MEDIA) {
- Log.v(TAG, "DEBUG_MEDIA: Disconnecting from old controller: "
- + mMediaController.getPackageName());
- }
- mMediaController.unregisterCallback(mMediaListener);
- }
- mMediaController = null;
- }
-
- private boolean sameSessions(MediaController a, MediaController b) {
- if (a == b) return true;
- if (a == null) return false;
- return a.controlsSameSession(b);
- }
/**
* Hide the album artwork that is fading out and release its bitmap.
@@ -2330,9 +2163,11 @@
}
};
+ // TODO: Move this to NotificationMediaManager.
/**
* Refresh or remove lockscreen artwork from media metadata or the lockscreen wallpaper.
*/
+ @Override
public void updateMediaMetaData(boolean metaDataChanged, boolean allowEnterAnimation) {
Trace.beginSection("StatusBar#updateMediaMetaData");
if (!SHOW_LOCKSCREEN_MEDIA_ARTWORK) {
@@ -2351,19 +2186,22 @@
return;
}
+ MediaMetadata mediaMetadata = mMediaManager.getMediaMetadata();
+
if (DEBUG_MEDIA) {
- Log.v(TAG, "DEBUG_MEDIA: updating album art for notification " + mMediaNotificationKey
- + " metadata=" + mMediaMetadata
+ Log.v(TAG, "DEBUG_MEDIA: updating album art for notification "
+ + mMediaManager.getMediaNotificationKey()
+ + " metadata=" + mediaMetadata
+ " metaDataChanged=" + metaDataChanged
+ " state=" + mState);
}
Drawable artworkDrawable = null;
- if (mMediaMetadata != null) {
+ if (mediaMetadata != null) {
Bitmap artworkBitmap = null;
- artworkBitmap = mMediaMetadata.getBitmap(MediaMetadata.METADATA_KEY_ART);
+ artworkBitmap = mediaMetadata.getBitmap(MediaMetadata.METADATA_KEY_ART);
if (artworkBitmap == null) {
- artworkBitmap = mMediaMetadata.getBitmap(MediaMetadata.METADATA_KEY_ALBUM_ART);
+ artworkBitmap = mediaMetadata.getBitmap(MediaMetadata.METADATA_KEY_ALBUM_ART);
// might still be null
}
if (artworkBitmap != null) {
@@ -2636,7 +2474,7 @@
@Override // NotificationData.Environment
public String getCurrentMediaNotificationKey() {
- return mMediaNotificationKey;
+ return mMediaManager.getMediaNotificationKey();
}
public boolean isScrimSrcModeEnabled() {
@@ -3101,8 +2939,8 @@
mStatusBarWindowManager.setForceStatusBarVisible(false);
// Close any guts that might be visible
- closeAndSaveGuts(true /* removeLeavebehind */, true /* force */, true /* removeControls */,
- -1 /* x */, -1 /* y */, true /* resetMenu */);
+ mGutsManager.closeAndSaveGuts(true /* removeLeavebehind */, true /* force */,
+ true /* removeControls */, -1 /* x */, -1 /* y */, true /* resetMenu */);
runPostCollapseRunnables();
setInteracting(StatusBarManager.WINDOW_STATUS_BAR, false);
@@ -3449,28 +3287,18 @@
pw.println(Settings.Global.zenModeToString(mZenMode));
pw.print(" mUseHeadsUp=");
pw.println(mUseHeadsUp);
- pw.print(" mKeyToRemoveOnGutsClosed=");
- pw.println(mKeyToRemoveOnGutsClosed);
+ pw.print(" mGutsManager: ");
+ if (mGutsManager != null) {
+ mGutsManager.dump(fd, pw, args);
+ }
if (mStatusBarView != null) {
dumpBarTransitions(pw, "mStatusBarView", mStatusBarView.getBarTransitions());
}
- pw.print(" mMediaSessionManager=");
- pw.println(mMediaSessionManager);
- pw.print(" mMediaNotificationKey=");
- pw.println(mMediaNotificationKey);
- pw.print(" mMediaController=");
- pw.print(mMediaController);
- if (mMediaController != null) {
- pw.print(" state=" + mMediaController.getPlaybackState());
+ pw.println(" mMediaManager: ");
+ if (mMediaManager != null) {
+ mMediaManager.dump(fd, pw, args);
}
- pw.println();
- pw.print(" mMediaMetadata=");
- pw.print(mMediaMetadata);
- if (mMediaMetadata != null) {
- pw.print(" title=" + mMediaMetadata.getText(MediaMetadata.METADATA_KEY_TITLE));
- }
- pw.println();
pw.println(" Panels: ");
if (mNotificationPanel != null) {
@@ -3792,7 +3620,7 @@
mReinflateNotificationsOnUserSwitched = false;
}
updateNotificationShade();
- clearCurrentMediaNotification();
+ mMediaManager.clearCurrentMediaNotification();
setLockscreenUser(newUserId);
}
@@ -3872,10 +3700,10 @@
if (visibleToUser) {
boolean pinnedHeadsUp = mHeadsUpManager.hasPinnedHeadsUp();
boolean clearNotificationEffects =
- !isPanelFullyCollapsed() &&
+ !isPresenterFullyCollapsed() &&
(mState == StatusBarState.SHADE || mState == StatusBarState.SHADE_LOCKED);
int notificationLoad = mNotificationData.getActiveNotifications().size();
- if (pinnedHeadsUp && isPanelFullyCollapsed()) {
+ if (pinnedHeadsUp && isPresenterFullyCollapsed()) {
notificationLoad = 1;
}
mBarService.onPanelRevealed(clearNotificationEffects, notificationLoad);
@@ -4152,7 +3980,8 @@
return mState;
}
- public boolean isPanelFullyCollapsed() {
+ @Override
+ public boolean isPresenterFullyCollapsed() {
return mNotificationPanel.isFullyCollapsed();
}
@@ -4735,7 +4564,7 @@
public void onClosingFinished() {
runPostCollapseRunnables();
- if (!isPanelFullyCollapsed()) {
+ if (!isPresenterFullyCollapsed()) {
// if we set it not to be focusable when collapsing, we have to undo it when we aborted
// the closing
mStatusBarWindowManager.setStatusBarFocusable(true);
@@ -5600,10 +5429,6 @@
protected int mZenMode;
- // which notification is currently being longpress-examined by the user
- private NotificationGuts mNotificationGutsExposed;
- private MenuItem mGutsMenuItem;
-
protected NotificationShelf mNotificationShelf;
protected DismissView mDismissView;
protected EmptyShadeView mEmptyShadeView;
@@ -5614,8 +5439,6 @@
protected boolean mVrMode;
- private Set<String> mNonBlockablePkgs;
-
public boolean isDeviceInteractive() {
return mDeviceInteractive;
}
@@ -6147,26 +5970,8 @@
return mGroupManager;
}
- public boolean isMediaNotification(NotificationData.Entry entry) {
- // TODO: confirm that there's a valid media key
- return entry.getExpandedContentView() != null &&
- entry.getExpandedContentView()
- .findViewById(com.android.internal.R.id.media_actions) != null;
- }
-
- // The button in the guts that links to the system notification settings for that app
- private void startAppNotificationSettingsActivity(String packageName, final int appUid,
- final NotificationChannel channel) {
- final Intent intent = new Intent(Settings.ACTION_APP_NOTIFICATION_SETTINGS);
- intent.putExtra(Settings.EXTRA_APP_PACKAGE, packageName);
- intent.putExtra(Settings.EXTRA_APP_UID, appUid);
- if (channel != null) {
- intent.putExtra(EXTRA_FRAGMENT_ARG_KEY, channel.getId());
- }
- startNotificationGutsIntent(intent, appUid);
- }
-
- private void startNotificationGutsIntent(final Intent intent, final int appUid) {
+ @Override
+ public void startNotificationGutsIntent(final Intent intent, final int appUid) {
dismissKeyguardThenExecute(() -> {
AsyncTask.execute(() -> {
TaskStackBuilder.create(mContext)
@@ -6189,231 +5994,8 @@
}
}
- private void bindGuts(final ExpandableNotificationRow row, MenuItem item) {
- row.inflateGuts();
- row.setGutsView(item);
- final StatusBarNotification sbn = row.getStatusBarNotification();
- row.setTag(sbn.getPackageName());
- final NotificationGuts guts = row.getGuts();
- guts.setClosedListener((NotificationGuts g) -> {
- if (!g.willBeRemoved() && !row.isRemoved()) {
- mStackScroller.onHeightChanged(row, !isPanelFullyCollapsed() /* needsAnimation */);
- }
- if (mNotificationGutsExposed == g) {
- mNotificationGutsExposed = null;
- mGutsMenuItem = null;
- }
- String key = sbn.getKey();
- if (key.equals(mKeyToRemoveOnGutsClosed)) {
- mKeyToRemoveOnGutsClosed = null;
- removeNotification(key, mLatestRankingMap);
- }
- });
-
- View gutsView = item.getGutsView();
- if (gutsView instanceof NotificationSnooze) {
- NotificationSnooze snoozeGuts = (NotificationSnooze) gutsView;
- snoozeGuts.setSnoozeListener(mStackScroller.getSwipeActionHelper());
- snoozeGuts.setStatusBarNotification(sbn);
- snoozeGuts.setSnoozeOptions(row.getEntry().snoozeCriteria);
- guts.setHeightChangedListener((NotificationGuts g) -> {
- mStackScroller.onHeightChanged(row, row.isShown() /* needsAnimation */);
- });
- }
-
- if (gutsView instanceof NotificationInfo) {
- final UserHandle userHandle = sbn.getUser();
- PackageManager pmUser = getPackageManagerForUser(mContext,
- userHandle.getIdentifier());
- final INotificationManager iNotificationManager = INotificationManager.Stub.asInterface(
- ServiceManager.getService(Context.NOTIFICATION_SERVICE));
- final String pkg = sbn.getPackageName();
- NotificationInfo info = (NotificationInfo) gutsView;
- // Settings link is only valid for notifications that specify a user, unless this is the
- // system user.
- NotificationInfo.OnSettingsClickListener onSettingsClick = null;
- if (!userHandle.equals(UserHandle.ALL) || mCurrentUserId == UserHandle.USER_SYSTEM) {
- onSettingsClick = (View v, NotificationChannel channel, int appUid) -> {
- mMetricsLogger.action(MetricsEvent.ACTION_NOTE_INFO);
- guts.resetFalsingCheck();
- startAppNotificationSettingsActivity(pkg, appUid, channel);
- };
- }
- final NotificationInfo.OnAppSettingsClickListener onAppSettingsClick = (View v,
- Intent intent) -> {
- mMetricsLogger.action(MetricsEvent.ACTION_APP_NOTE_SETTINGS);
- guts.resetFalsingCheck();
- startNotificationGutsIntent(intent, sbn.getUid());
- };
- final View.OnClickListener onDoneClick = (View v) -> {
- saveAndCloseNotificationMenu(row, guts, v);
- };
- final NotificationInfo.CheckSaveListener checkSaveListener =
- (Runnable saveImportance) -> {
- // If the user has security enabled, show challenge if the setting is changed.
- if (isLockscreenPublicMode(userHandle.getIdentifier())
- && (mState == StatusBarState.KEYGUARD
- || mState == StatusBarState.SHADE_LOCKED)) {
- onLockedNotificationImportanceChange(() -> {
- saveImportance.run();
- return true;
- });
- } else {
- saveImportance.run();
- }
- };
-
- ArraySet<NotificationChannel> channels = new ArraySet<>();
- channels.add(row.getEntry().channel);
- if (row.isSummaryWithChildren()) {
- // If this is a summary, then add in the children notification channels for the
- // same user and pkg.
- final List<ExpandableNotificationRow> childrenRows = row.getNotificationChildren();
- final int numChildren = childrenRows.size();
- for (int i = 0; i < numChildren; i++) {
- final ExpandableNotificationRow childRow = childrenRows.get(i);
- final NotificationChannel childChannel = childRow.getEntry().channel;
- final StatusBarNotification childSbn = childRow.getStatusBarNotification();
- if (childSbn.getUser().equals(userHandle) &&
- childSbn.getPackageName().equals(pkg)) {
- channels.add(childChannel);
- }
- }
- }
- try {
- info.bindNotification(pmUser, iNotificationManager, pkg, new ArrayList(channels),
- row.getEntry().channel.getImportance(), sbn, onSettingsClick,
- onAppSettingsClick, onDoneClick, checkSaveListener,
- mNonBlockablePkgs);
- } catch (RemoteException e) {
- Log.e(TAG, e.toString());
- }
- }
- }
-
- private void saveAndCloseNotificationMenu(
- ExpandableNotificationRow row, NotificationGuts guts, View done) {
- guts.resetFalsingCheck();
- int[] rowLocation = new int[2];
- int[] doneLocation = new int[2];
- row.getLocationOnScreen(rowLocation);
- done.getLocationOnScreen(doneLocation);
-
- final int centerX = done.getWidth() / 2;
- final int centerY = done.getHeight() / 2;
- final int x = doneLocation[0] - rowLocation[0] + centerX;
- final int y = doneLocation[1] - rowLocation[1] + centerY;
- closeAndSaveGuts(false /* removeLeavebehind */, false /* force */,
- true /* removeControls */, x, y, true /* resetMenu */);
- }
-
protected ExpandableNotificationRow.LongPressListener getNotificationLongClicker() {
- return new ExpandableNotificationRow.LongPressListener() {
- @Override
- public boolean onLongPress(View v, final int x, final int y,
- MenuItem item) {
- if (!(v instanceof ExpandableNotificationRow)) {
- return false;
- }
-
- if (v.getWindowToken() == null) {
- Log.e(TAG, "Trying to show notification guts, but not attached to window");
- return false;
- }
-
- final ExpandableNotificationRow row = (ExpandableNotificationRow) v;
- if (row.isDark()) {
- return false;
- }
- v.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
- if (row.areGutsExposed()) {
- closeAndSaveGuts(false /* removeLeavebehind */, false /* force */,
- true /* removeControls */, -1 /* x */, -1 /* y */,
- true /* resetMenu */);
- return true;
- }
- bindGuts(row, item);
- NotificationGuts guts = row.getGuts();
-
- // Assume we are a status_bar_notification_row
- if (guts == null) {
- // This view has no guts. Examples are the more card or the dismiss all view
- return false;
- }
-
- mMetricsLogger.action(MetricsEvent.ACTION_NOTE_CONTROLS);
-
- // ensure that it's laid but not visible until actually laid out
- guts.setVisibility(View.INVISIBLE);
- // Post to ensure the the guts are properly laid out.
- guts.post(new Runnable() {
- @Override
- public void run() {
- if (row.getWindowToken() == null) {
- Log.e(TAG, "Trying to show notification guts, but not attached to "
- + "window");
- return;
- }
- closeAndSaveGuts(true /* removeLeavebehind */, true /* force */,
- true /* removeControls */, -1 /* x */, -1 /* y */,
- false /* resetMenu */);
- guts.setVisibility(View.VISIBLE);
- final double horz = Math.max(guts.getWidth() - x, x);
- final double vert = Math.max(guts.getHeight() - y, y);
- final float r = (float) Math.hypot(horz, vert);
- final Animator a
- = ViewAnimationUtils.createCircularReveal(guts, x, y, 0, r);
- a.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
- a.setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN);
- a.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- super.onAnimationEnd(animation);
- // Move the notification view back over the menu
- row.resetTranslation();
- }
- });
- a.start();
- final boolean needsFalsingProtection =
- (mState == StatusBarState.KEYGUARD &&
- !mAccessibilityManager.isTouchExplorationEnabled());
- guts.setExposed(true /* exposed */, needsFalsingProtection);
- row.closeRemoteInput();
- mStackScroller.onHeightChanged(row, true /* needsAnimation */);
- mNotificationGutsExposed = guts;
- mGutsMenuItem = item;
- }
- });
- return true;
- }
- };
- }
-
- /**
- * Returns the exposed NotificationGuts or null if none are exposed.
- */
- public NotificationGuts getExposedGuts() {
- return mNotificationGutsExposed;
- }
-
- /**
- * Closes guts or notification menus that might be visible and saves any changes.
- *
- * @param removeLeavebehinds true if leavebehinds (e.g. snooze) should be closed.
- * @param force true if guts should be closed regardless of state (used for snooze only).
- * @param removeControls true if controls (e.g. info) should be closed.
- * @param x if closed based on touch location, this is the x touch location.
- * @param y if closed based on touch location, this is the y touch location.
- * @param resetMenu if any notification menus that might be revealed should be closed.
- */
- public void closeAndSaveGuts(boolean removeLeavebehinds, boolean force, boolean removeControls,
- int x, int y, boolean resetMenu) {
- if (mNotificationGutsExposed != null) {
- mNotificationGutsExposed.closeControls(removeLeavebehinds, removeControls, x, y, force);
- }
- if (resetMenu) {
- mStackScroller.resetExposedMenuView(false /* animate */, true /* force */);
- }
+ return (v, x, y, item) -> mGutsManager.openGuts(v, x, y, item);
}
@Override
@@ -6802,7 +6384,7 @@
if (mHeadsUpManager != null && mHeadsUpManager.isHeadsUp(notificationKey)) {
// Release the HUN notification to the shade.
- if (isPanelFullyCollapsed()) {
+ if (isPresenterFullyCollapsed()) {
HeadsUpManager.setIsClickedNotification(row, true);
}
//
@@ -6934,7 +6516,7 @@
if (mVisible != visible) {
mVisible = visible;
if (!visible) {
- closeAndSaveGuts(true /* removeLeavebehind */, true /* force */,
+ mGutsManager.closeAndSaveGuts(true /* removeLeavebehind */, true /* force */,
true /* removeControls */, -1 /* x */, -1 /* y */, true /* resetMenu */);
}
}
@@ -7156,8 +6738,8 @@
}
mHeadsUpEntriesToRemoveOnSwitch.remove(entry);
mRemoteInputEntriesToRemoveOnCollapse.remove(entry);
- if (key.equals(mKeyToRemoveOnGutsClosed)) {
- mKeyToRemoveOnGutsClosed = null;
+ if (key.equals(mGutsManager.getKeyToRemoveOnGutsClosed())) {
+ mGutsManager.setKeyToRemoveOnGutsClosed(null);
Log.w(TAG, "Notification that was kept for guts was updated. " + key);
}
@@ -7355,4 +6937,43 @@
mNavigationBar.getBarTransitions().setAutoDim(true);
}
};
+
+ public NotificationGutsManager getGutsManager() {
+ return mGutsManager;
+ }
+
+ @Override
+ public boolean isPresenterLocked() {
+ return mState == StatusBarState.KEYGUARD;
+ }
+
+ @Override
+ public int getCurrentUserId() {
+ return mCurrentUserId;
+ }
+
+ @Override
+ public NotificationData getNotificationData() {
+ return mNotificationData;
+ }
+
+ @Override
+ public RankingMap getLatestRankingMap() {
+ return mLatestRankingMap;
+ }
+
+ final NotificationInfo.CheckSaveListener mCheckSaveListener =
+ (Runnable saveImportance, StatusBarNotification sbn) -> {
+ // If the user has security enabled, show challenge if the setting is changed.
+ if (isLockscreenPublicMode(sbn.getUser().getIdentifier()) && (
+ mState == StatusBarState.KEYGUARD
+ || mState == StatusBarState.SHADE_LOCKED)) {
+ onLockedNotificationImportanceChange(() -> {
+ saveImportance.run();
+ return true;
+ });
+ } else {
+ saveImportance.run();
+ }
+ };
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
index 1e14626..efe049a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
@@ -1343,7 +1343,7 @@
}
// Check if we need to clear any snooze leavebehinds
- NotificationGuts guts = mStatusBar.getExposedGuts();
+ NotificationGuts guts = mStatusBar.getGutsManager().getExposedGuts();
if (guts != null && !isTouchInView(ev, guts)
&& guts.getGutsContent() instanceof NotificationSnooze) {
NotificationSnooze ns = (NotificationSnooze) guts.getGutsContent();
@@ -2508,12 +2508,13 @@
}
// Check if we need to clear any snooze leavebehinds
boolean isUp = ev.getActionMasked() == MotionEvent.ACTION_UP;
- NotificationGuts guts = mStatusBar.getExposedGuts();
+ NotificationGuts guts = mStatusBar.getGutsManager().getExposedGuts();
if (!isTouchInView(ev, guts) && isUp && !swipeWantsIt && !expandWantsIt
&& !scrollWantsIt) {
mCheckForLeavebehind = false;
- mStatusBar.closeAndSaveGuts(true /* removeLeavebehind */, false /* force */,
- false /* removeControls */, -1 /* x */, -1 /* y */, false /* resetMenu */);
+ mStatusBar.getGutsManager().closeAndSaveGuts(true /* removeLeavebehind */,
+ false /* force */, false /* removeControls */, -1 /* x */, -1 /* y */,
+ false /* resetMenu */);
}
if (ev.getActionMasked() == MotionEvent.ACTION_UP) {
mCheckForLeavebehind = true;
@@ -3065,7 +3066,7 @@
}
if (!childWasSwipedOut) {
Rect clipBounds = child.getClipBounds();
- childWasSwipedOut = clipBounds.height() == 0;
+ childWasSwipedOut = clipBounds != null && clipBounds.height() == 0;
}
int animationType = childWasSwipedOut
? AnimationEvent.ANIMATION_TYPE_REMOVE_SWIPED_OUT
@@ -3355,8 +3356,9 @@
public void checkSnoozeLeavebehind() {
if (mCheckForLeavebehind) {
- mStatusBar.closeAndSaveGuts(true /* removeLeavebehind */, false /* force */,
- false /* removeControls */, -1 /* x */, -1 /* y */, false /* resetMenu */);
+ mStatusBar.getGutsManager().closeAndSaveGuts(true /* removeLeavebehind */,
+ false /* force */, false /* removeControls */, -1 /* x */, -1 /* y */,
+ false /* resetMenu */);
mCheckForLeavebehind = false;
}
}
@@ -4438,8 +4440,9 @@
// of the panel early.
handleChildDismissed(view);
}
- mStatusBar.closeAndSaveGuts(true /* removeLeavebehind */, false /* force */,
- false /* removeControls */, -1 /* x */, -1 /* y */, false /* resetMenu */);
+ mStatusBar.getGutsManager().closeAndSaveGuts(true /* removeLeavebehind */,
+ false /* force */, false /* removeControls */, -1 /* x */, -1 /* y */,
+ false /* resetMenu */);
handleMenuCoveredOrDismissed();
}
@@ -4524,7 +4527,7 @@
}
public void closeControlsIfOutsideTouch(MotionEvent ev) {
- NotificationGuts guts = mStatusBar.getExposedGuts();
+ NotificationGuts guts = mStatusBar.getGutsManager().getExposedGuts();
View view = null;
if (guts != null && !guts.getGutsContent().isLeavebehind()) {
// Only close visible guts if they're not a leavebehind.
@@ -4536,8 +4539,9 @@
}
if (view != null && !isTouchInView(ev, view)) {
// Touch was outside visible guts / menu notification, close what's visible
- mStatusBar.closeAndSaveGuts(false /* removeLeavebehind */, false /* force */,
- true /* removeControls */, -1 /* x */, -1 /* y */, false /* resetMenu */);
+ mStatusBar.getGutsManager().closeAndSaveGuts(false /* removeLeavebehind */,
+ false /* force */, true /* removeControls */, -1 /* x */, -1 /* y */,
+ false /* resetMenu */);
resetExposedMenuView(true /* animate */, true /* force */);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/PluginFragment.java b/packages/SystemUI/src/com/android/systemui/tuner/PluginFragment.java
index f91e45d..27bf534 100644
--- a/packages/SystemUI/src/com/android/systemui/tuner/PluginFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/tuner/PluginFragment.java
@@ -169,16 +169,23 @@
protected boolean persistBoolean(boolean value) {
final int desiredState = value ? PackageManager.COMPONENT_ENABLED_STATE_ENABLED
: PackageManager.COMPONENT_ENABLED_STATE_DISABLED;
+ boolean shouldSendBroadcast = false;
for (int i = 0; i < mInfo.services.length; i++) {
ComponentName componentName = new ComponentName(mInfo.packageName,
mInfo.services[i].name);
- mPm.setComponentEnabledSetting(componentName, desiredState,
- PackageManager.DONT_KILL_APP);
+
+ if (mPm.getComponentEnabledSetting(componentName) != desiredState) {
+ mPm.setComponentEnabledSetting(componentName, desiredState,
+ PackageManager.DONT_KILL_APP);
+ shouldSendBroadcast = true;
+ }
}
- final String pkg = mInfo.packageName;
- final Intent intent = new Intent(PluginManager.PLUGIN_CHANGED,
- pkg != null ? Uri.fromParts("package", pkg, null) : null);
- getContext().sendBroadcast(intent);
+ if (shouldSendBroadcast) {
+ final String pkg = mInfo.packageName;
+ final Intent intent = new Intent(PluginManager.PLUGIN_CHANGED,
+ pkg != null ? Uri.fromParts("package", pkg, null) : null);
+ getContext().sendBroadcast(intent);
+ }
return true;
}
diff --git a/packages/SystemUI/src/com/android/systemui/usb/UsbConfirmActivity.java b/packages/SystemUI/src/com/android/systemui/usb/UsbConfirmActivity.java
index 3eccccd..e117969 100644
--- a/packages/SystemUI/src/com/android/systemui/usb/UsbConfirmActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/usb/UsbConfirmActivity.java
@@ -71,10 +71,12 @@
ap.mIcon = mResolveInfo.loadIcon(packageManager);
ap.mTitle = appName;
if (mDevice == null) {
- ap.mMessage = getString(R.string.usb_accessory_confirm_prompt, appName);
+ ap.mMessage = getString(R.string.usb_accessory_confirm_prompt, appName,
+ mAccessory.getDescription());
mDisconnectedReceiver = new UsbDisconnectedReceiver(this, mAccessory);
} else {
- ap.mMessage = getString(R.string.usb_device_confirm_prompt, appName);
+ ap.mMessage = getString(R.string.usb_device_confirm_prompt, appName,
+ mDevice.getProductName());
mDisconnectedReceiver = new UsbDisconnectedReceiver(this, mDevice);
}
ap.mPositiveButtonText = getString(android.R.string.ok);
@@ -88,9 +90,11 @@
ap.mView = inflater.inflate(com.android.internal.R.layout.always_use_checkbox, null);
mAlwaysUse = (CheckBox)ap.mView.findViewById(com.android.internal.R.id.alwaysUse);
if (mDevice == null) {
- mAlwaysUse.setText(R.string.always_use_accessory);
+ mAlwaysUse.setText(getString(R.string.always_use_accessory, appName,
+ mAccessory.getDescription()));
} else {
- mAlwaysUse.setText(R.string.always_use_device);
+ mAlwaysUse.setText(getString(R.string.always_use_device, appName,
+ mDevice.getProductName()));
}
mAlwaysUse.setOnCheckedChangeListener(this);
mClearDefaultHint = (TextView)ap.mView.findViewById(
diff --git a/packages/SystemUI/src/com/android/systemui/usb/UsbPermissionActivity.java b/packages/SystemUI/src/com/android/systemui/usb/UsbPermissionActivity.java
index 1e69fc5..87d11b2 100644
--- a/packages/SystemUI/src/com/android/systemui/usb/UsbPermissionActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/usb/UsbPermissionActivity.java
@@ -16,13 +16,17 @@
package com.android.systemui.usb;
+import android.annotation.NonNull;
import android.app.AlertDialog;
import android.app.PendingIntent;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
+import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
+import android.content.res.XmlResourceParser;
import android.hardware.usb.IUsbManager;
import android.hardware.usb.UsbAccessory;
import android.hardware.usb.UsbDevice;
@@ -41,8 +45,13 @@
import com.android.internal.app.AlertActivity;
import com.android.internal.app.AlertController;
+import com.android.internal.util.XmlUtils;
+import android.hardware.usb.AccessoryFilter;
+import android.hardware.usb.DeviceFilter;
import com.android.systemui.R;
+import org.xmlpull.v1.XmlPullParser;
+
public class UsbPermissionActivity extends AlertActivity
implements DialogInterface.OnClickListener, CheckBox.OnCheckedChangeListener {
@@ -84,10 +93,12 @@
ap.mIcon = aInfo.loadIcon(packageManager);
ap.mTitle = appName;
if (mDevice == null) {
- ap.mMessage = getString(R.string.usb_accessory_permission_prompt, appName);
+ ap.mMessage = getString(R.string.usb_accessory_permission_prompt, appName,
+ mAccessory.getDescription());
mDisconnectedReceiver = new UsbDisconnectedReceiver(this, mAccessory);
} else {
- ap.mMessage = getString(R.string.usb_device_permission_prompt, appName);
+ ap.mMessage = getString(R.string.usb_device_permission_prompt, appName,
+ mDevice.getProductName());
mDisconnectedReceiver = new UsbDisconnectedReceiver(this, mDevice);
}
ap.mPositiveButtonText = getString(android.R.string.ok);
@@ -95,25 +106,123 @@
ap.mPositiveButtonListener = this;
ap.mNegativeButtonListener = this;
- // add "always use" checkbox
- LayoutInflater inflater = (LayoutInflater)getSystemService(
- Context.LAYOUT_INFLATER_SERVICE);
- ap.mView = inflater.inflate(com.android.internal.R.layout.always_use_checkbox, null);
- mAlwaysUse = (CheckBox)ap.mView.findViewById(com.android.internal.R.id.alwaysUse);
- if (mDevice == null) {
- mAlwaysUse.setText(R.string.always_use_accessory);
- } else {
- mAlwaysUse.setText(R.string.always_use_device);
+ try {
+ PackageInfo packageInfo = packageManager.getPackageInfo(mPackageName,
+ PackageManager.GET_ACTIVITIES | PackageManager.GET_META_DATA);
+
+ if ((mDevice != null && canBeDefault(mDevice, packageInfo))
+ || (mAccessory != null && canBeDefault(mAccessory, packageInfo))) {
+ // add "open when" checkbox
+ LayoutInflater inflater = (LayoutInflater) getSystemService(
+ Context.LAYOUT_INFLATER_SERVICE);
+ ap.mView = inflater.inflate(com.android.internal.R.layout.always_use_checkbox, null);
+ mAlwaysUse = (CheckBox) ap.mView.findViewById(com.android.internal.R.id.alwaysUse);
+ if (mDevice == null) {
+ mAlwaysUse.setText(getString(R.string.always_use_accessory, appName,
+ mAccessory.getDescription()));
+ } else {
+ mAlwaysUse.setText(getString(R.string.always_use_device, appName,
+ mDevice.getProductName()));
+ }
+ mAlwaysUse.setOnCheckedChangeListener(this);
+
+ mClearDefaultHint = (TextView)ap.mView.findViewById(
+ com.android.internal.R.id.clearDefaultHint);
+ mClearDefaultHint.setVisibility(View.GONE);
+ }
+ } catch (PackageManager.NameNotFoundException e) {
+ // ignore
}
- mAlwaysUse.setOnCheckedChangeListener(this);
- mClearDefaultHint = (TextView)ap.mView.findViewById(
- com.android.internal.R.id.clearDefaultHint);
- mClearDefaultHint.setVisibility(View.GONE);
setupAlert();
}
+ /**
+ * Can the app be the default for the USB device. I.e. can the app be launched by default if
+ * the device is plugged in.
+ *
+ * @param device The device the app would be default for
+ * @param packageInfo The package info of the app
+ *
+ * @return {@code true} iff the app can be default
+ */
+ private boolean canBeDefault(@NonNull UsbDevice device, @NonNull PackageInfo packageInfo) {
+ ActivityInfo[] activities = packageInfo.activities;
+ if (activities != null) {
+ int numActivities = activities.length;
+ for (int i = 0; i < numActivities; i++) {
+ ActivityInfo activityInfo = activities[i];
+
+ try (XmlResourceParser parser = activityInfo.loadXmlMetaData(getPackageManager(),
+ UsbManager.ACTION_USB_DEVICE_ATTACHED)) {
+ if (parser == null) {
+ continue;
+ }
+
+ XmlUtils.nextElement(parser);
+ while (parser.getEventType() != XmlPullParser.END_DOCUMENT) {
+ if ("usb-device".equals(parser.getName())) {
+ DeviceFilter filter = DeviceFilter.read(parser);
+ if (filter.matches(device)) {
+ return true;
+ }
+ }
+
+ XmlUtils.nextElement(parser);
+ }
+ } catch (Exception e) {
+ Log.w(TAG, "Unable to load component info " + activityInfo.toString(), e);
+ }
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Can the app be the default for the USB accessory. I.e. can the app be launched by default if
+ * the accessory is plugged in.
+ *
+ * @param accessory The accessory the app would be default for
+ * @param packageInfo The package info of the app
+ *
+ * @return {@code true} iff the app can be default
+ */
+ private boolean canBeDefault(@NonNull UsbAccessory accessory,
+ @NonNull PackageInfo packageInfo) {
+ ActivityInfo[] activities = packageInfo.activities;
+ if (activities != null) {
+ int numActivities = activities.length;
+ for (int i = 0; i < numActivities; i++) {
+ ActivityInfo activityInfo = activities[i];
+
+ try (XmlResourceParser parser = activityInfo.loadXmlMetaData(getPackageManager(),
+ UsbManager.ACTION_USB_ACCESSORY_ATTACHED)) {
+ if (parser == null) {
+ continue;
+ }
+
+ XmlUtils.nextElement(parser);
+ while (parser.getEventType() != XmlPullParser.END_DOCUMENT) {
+ if ("usb-accessory".equals(parser.getName())) {
+ AccessoryFilter filter = AccessoryFilter.read(parser);
+ if (filter.matches(accessory)) {
+ return true;
+ }
+ }
+
+ XmlUtils.nextElement(parser);
+ }
+ } catch (Exception e) {
+ Log.w(TAG, "Unable to load component info " + activityInfo.toString(), e);
+ }
+ }
+ }
+
+ return false;
+ }
+
@Override
public void onDestroy() {
IBinder b = ServiceManager.getService(USB_SERVICE);
diff --git a/packages/SystemUI/src/com/android/systemui/volume/SystemUIInterpolators.java b/packages/SystemUI/src/com/android/systemui/volume/SystemUIInterpolators.java
new file mode 100644
index 0000000..5ad8840
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/SystemUIInterpolators.java
@@ -0,0 +1,78 @@
+/*
+ * 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.
+ */
+
+package com.android.systemui.volume;
+
+import android.animation.TimeInterpolator;
+
+public class SystemUIInterpolators {
+ public static final class LogDecelerateInterpolator implements TimeInterpolator {
+ private final float mBase;
+ private final float mDrift;
+ private final float mTimeScale;
+ private final float mOutputScale;
+
+ public LogDecelerateInterpolator() {
+ this(400f, 1.4f, 0);
+ }
+
+ private LogDecelerateInterpolator(float base, float timeScale, float drift) {
+ mBase = base;
+ mDrift = drift;
+ mTimeScale = 1f / timeScale;
+
+ mOutputScale = 1f / computeLog(1f);
+ }
+
+ private float computeLog(float t) {
+ return 1f - (float) Math.pow(mBase, -t * mTimeScale) + (mDrift * t);
+ }
+
+ @Override
+ public float getInterpolation(float t) {
+ return computeLog(t) * mOutputScale;
+ }
+ }
+
+ public static final class LogAccelerateInterpolator implements TimeInterpolator {
+ private final int mBase;
+ private final int mDrift;
+ private final float mLogScale;
+
+ public LogAccelerateInterpolator() {
+ this(100, 0);
+ }
+
+ private LogAccelerateInterpolator(int base, int drift) {
+ mBase = base;
+ mDrift = drift;
+ mLogScale = 1f / computeLog(1, mBase, mDrift);
+ }
+
+ private static float computeLog(float t, int base, int drift) {
+ return (float) -Math.pow(base, -t) + 1 + (drift * t);
+ }
+
+ @Override
+ public float getInterpolation(float t) {
+ return 1 - computeLog(1 - t, mBase, mDrift) * mLogScale;
+ }
+ }
+
+ public interface Callback {
+ void onAnimatingChanged(boolean animating);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogComponent.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogComponent.java
index ee8f18e..4dff9bd 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogComponent.java
@@ -62,7 +62,6 @@
private final InterestingConfigChanges mConfigChanges = new InterestingConfigChanges(
ActivityInfo.CONFIG_FONT_SCALE | ActivityInfo.CONFIG_LOCALE
| ActivityInfo.CONFIG_ASSETS_PATHS);
- private final Extension mExtension;
private VolumeDialog mDialog;
private VolumePolicy mVolumePolicy = new VolumePolicy(
DEFAULT_VOLUME_DOWN_TO_ENTER_SILENT, // volumeDownToEnterSilent
@@ -79,7 +78,7 @@
// Allow plugins to reference the VolumeDialogController.
Dependency.get(PluginDependencyProvider.class)
.allowPluginDependency(VolumeDialogController.class);
- mExtension = Dependency.get(ExtensionController.class).newExtension(VolumeDialog.class)
+ Dependency.get(ExtensionController.class).newExtension(VolumeDialog.class)
.withPlugin(VolumeDialog.class)
.withDefault(this::createDefault)
.withCallback(dialog -> {
@@ -151,7 +150,7 @@
@Override
public void onConfigurationChanged(Configuration newConfig) {
if (mConfigChanges.applyNewConfig(mContext.getResources())) {
- mExtension.reload();
+ mController.mCallbacks.onConfigurationChanged();
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
index 1ecaa13..4b8f581 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
@@ -19,6 +19,8 @@
import static android.accessibilityservice.AccessibilityServiceInfo.FEEDBACK_ALL_MASK;
import static android.accessibilityservice.AccessibilityServiceInfo.FEEDBACK_GENERIC;
+import static com.android.systemui.volume.Events.DISMISS_REASON_TOUCH_OUTSIDE;
+
import android.accessibilityservice.AccessibilityServiceInfo;
import android.animation.ObjectAnimator;
import android.annotation.NonNull;
@@ -26,11 +28,11 @@
import android.app.Dialog;
import android.app.KeyguardManager;
import android.content.Context;
+import android.content.DialogInterface;
import android.content.res.ColorStateList;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.Color;
-import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.graphics.drawable.ColorDrawable;
import android.media.AudioManager;
@@ -41,12 +43,10 @@
import android.os.Message;
import android.os.SystemClock;
import android.provider.Settings.Global;
-import android.util.DisplayMetrics;
import android.util.Log;
import android.util.Slog;
import android.util.SparseBooleanArray;
import android.view.ContextThemeWrapper;
-import android.view.Gravity;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.AccessibilityDelegate;
@@ -54,7 +54,6 @@
import android.view.View.OnClickListener;
import android.view.View.OnTouchListener;
import android.view.ViewGroup;
-import android.view.ViewGroup.MarginLayoutParams;
import android.view.Window;
import android.view.WindowManager;
import android.view.accessibility.AccessibilityEvent;
@@ -68,12 +67,13 @@
import com.android.settingslib.Utils;
import com.android.systemui.Dependency;
+import com.android.systemui.HardwareUiLayout;
+import com.android.systemui.Interpolators;
import com.android.systemui.R;
import com.android.systemui.plugins.VolumeDialog;
import com.android.systemui.plugins.VolumeDialogController;
import com.android.systemui.plugins.VolumeDialogController.State;
import com.android.systemui.plugins.VolumeDialogController.StreamState;
-import com.android.systemui.statusbar.policy.ZenModeController;
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -89,8 +89,6 @@
public class VolumeDialogImpl implements VolumeDialog {
private static final String TAG = Util.logTag(VolumeDialogImpl.class);
- public static final String SHOW_FULL_ZEN = "sysui_show_full_zen";
-
private static final long USER_ATTEMPT_GRACE_PERIOD = 1000;
private static final int UPDATE_ANIMATION_DURATION = 80;
@@ -99,6 +97,7 @@
private final VolumeDialogController mController;
private Window mWindow;
+ private HardwareUiLayout mHardwareLayout;
private CustomDialog mDialog;
private ViewGroup mDialogView;
private ViewGroup mDialogRowsView;
@@ -107,16 +106,11 @@
private ConfigurableTexts mConfigurableTexts;
private final SparseBooleanArray mDynamic = new SparseBooleanArray();
private final KeyguardManager mKeyguard;
- private final AudioManager mAudioManager;
private final AccessibilityManager mAccessibilityMgr;
- private int mExpandButtonAnimationDuration;
private final Object mSafetyWarningLock = new Object();
private final Accessibility mAccessibility = new Accessibility();
private final ColorStateList mActiveSliderTint;
private final ColorStateList mInactiveSliderTint;
- private VolumeDialogMotion mMotion;
- private int mWindowType;
- private final ZenModeController mZenModeController;
private boolean mShowing;
private boolean mShowA11yStream;
@@ -127,19 +121,12 @@
private boolean mSilentMode = VolumePrefs.DEFAULT_ENABLE_SILENT_MODE;
private State mState;
private SafetyWarningDialog mSafetyWarning;
- private Callback mCallback;
- private boolean mPendingStateChanged;
- private boolean mPendingRecheckAll;
- private long mCollapseTime;
private boolean mHovering = false;
- private int mDensity;
public VolumeDialogImpl(Context context) {
mContext = new ContextThemeWrapper(context, com.android.systemui.R.style.qs_theme);
- mZenModeController = Dependency.get(ZenModeController.class);
mController = Dependency.get(VolumeDialogController.class);
mKeyguard = (KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE);
- mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
mAccessibilityMgr =
(AccessibilityManager) mContext.getSystemService(Context.ACCESSIBILITY_SERVICE);
mActiveSliderTint = ColorStateList.valueOf(Utils.getColorAccent(mContext));
@@ -147,18 +134,12 @@
}
public void init(int windowType, Callback callback) {
- mCallback = callback;
- mWindowType = windowType;
-
initDialog();
mAccessibility.init();
mController.addCallback(mControllerCallbackH, mHandler);
mController.getState();
-
- final Configuration currentConfig = mContext.getResources().getConfiguration();
- mDensity = currentConfig.densityDpi;
}
@Override
@@ -177,25 +158,16 @@
mWindow = mDialog.getWindow();
mWindow.requestFeature(Window.FEATURE_NO_TITLE);
mWindow.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
- mWindow.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND);
- mWindow.addFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
- | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
- | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
- | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
- | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
- | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED);
- mDialog.setCanceledOnTouchOutside(true);
- final Resources res = mContext.getResources();
- final WindowManager.LayoutParams lp = mWindow.getAttributes();
- lp.type = mWindowType;
- lp.format = PixelFormat.TRANSLUCENT;
- lp.setTitle(VolumeDialogImpl.class.getSimpleName());
- lp.gravity = Gravity.TOP | Gravity.CENTER_HORIZONTAL;
- lp.y = res.getDimensionPixelSize(R.dimen.volume_offset_top);
- lp.gravity = Gravity.TOP;
- lp.windowAnimations = -1;
- mWindow.setAttributes(lp);
- mWindow.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING);
+ mWindow.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND
+ | WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR);
+ mWindow.addFlags(
+ WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
+ | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
+ | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
+ | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
+ | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED);
+ mWindow.setType(WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY);
+ mWindow.setWindowAnimations(com.android.internal.R.style.Animation_Toast);
mDialog.setContentView(R.layout.volume_dialog);
mDialogView = (ViewGroup) mDialog.findViewById(R.id.volume_dialog);
@@ -209,26 +181,11 @@
return true;
}
});
+ mHardwareLayout = HardwareUiLayout.get(mDialogView);
+ mHardwareLayout.setOutsideTouchListener(view -> dismiss(DISMISS_REASON_TOUCH_OUTSIDE));
mDialogContentView = mDialog.findViewById(R.id.volume_dialog_content);
mDialogRowsView = mDialogContentView.findViewById(R.id.volume_dialog_rows);
- updateWindowWidthH();
-
- mMotion = new VolumeDialogMotion(mDialog, mDialogView, mDialogContentView,
- new VolumeDialogMotion.Callback() {
- @Override
- public void onAnimatingChanged(boolean animating) {
- if (animating) return;
- if (mPendingStateChanged) {
- mHandler.sendEmptyMessage(H.STATE_CHANGED);
- mPendingStateChanged = false;
- }
- if (mPendingRecheckAll) {
- mHandler.sendEmptyMessage(H.RECHECK_ALL);
- mPendingRecheckAll = false;
- }
- }
- });
if (mRows.isEmpty()) {
addRow(AudioManager.STREAM_MUSIC,
@@ -250,28 +207,12 @@
} else {
addExistingRows();
}
- mExpandButtonAnimationDuration = res.getInteger(R.integer.volume_expand_animation_duration);
}
private ColorStateList loadColorStateList(int colorResId) {
return ColorStateList.valueOf(mContext.getColor(colorResId));
}
- private void updateWindowWidthH() {
- final ViewGroup.MarginLayoutParams lp =
- (ViewGroup.MarginLayoutParams) mDialogView.getLayoutParams();
- final DisplayMetrics dm = mContext.getResources().getDisplayMetrics();
- if (D.BUG) Log.d(TAG, "updateWindowWidth dm.w=" + dm.widthPixels);
- int w = dm.widthPixels;
- final int max = mContext.getResources()
- .getDimensionPixelSize(R.dimen.volume_dialog_panel_width);
- if (w > max) {
- w = max;
- }
- lp.width = w - lp.getMarginEnd() - lp.getMarginStart();
- mDialogView.setLayoutParams(lp);
- }
-
public void setStreamImportant(int stream, boolean important) {
mHandler.obtainMessage(H.SET_STREAM_IMPORTANT, stream, important ? 1 : 0).sendToTarget();
}
@@ -315,6 +256,7 @@
final VolumeRow row = mRows.get(i);
initRow(row, row.stream, row.iconRes, row.iconMuteRes, row.important);
mDialogRowsView.addView(row.view);
+ updateVolumeRowH(row);
}
}
@@ -341,7 +283,6 @@
writer.print(" mDynamic: "); writer.println(mDynamic);
writer.print(" mAutomute: "); writer.println(mAutomute);
writer.print(" mSilentMode: "); writer.println(mSilentMode);
- writer.print(" mCollapseTime: "); writer.println(mCollapseTime);
writer.print(" mAccessibility.mFeedbackEnabled: ");
writer.println(mAccessibility.mFeedbackEnabled);
}
@@ -364,9 +305,9 @@
row.view = mDialog.getLayoutInflater().inflate(R.layout.volume_dialog_row, null);
row.view.setId(row.stream);
row.view.setTag(row);
- row.header = (TextView) row.view.findViewById(R.id.volume_row_header);
+ row.header = row.view.findViewById(R.id.volume_row_header);
row.header.setId(20 * row.stream);
- row.slider = (SeekBar) row.view.findViewById(R.id.volume_row_slider);
+ row.slider = row.view.findViewById(R.id.volume_row_slider);
row.slider.setOnSeekBarChangeListener(new VolumeSeekBarChangeListener(row));
row.anim = null;
@@ -447,11 +388,27 @@
rescheduleTimeoutH();
if (mShowing) return;
mShowing = true;
- mMotion.startShow();
+ mHardwareLayout.setTranslationX(getAnimTranslation());
+ mHardwareLayout.setAlpha(0);
+ mHardwareLayout.animate()
+ .alpha(1)
+ .translationX(0)
+ .setDuration(300)
+ .setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
+ .withEndAction(() -> {
+ mDialog.show();
+ mWindow.getDecorView().requestAccessibilityFocus();
+ })
+ .start();
Events.writeEvent(mContext, Events.EVENT_SHOW_DIALOG, reason, mKeyguard.isKeyguardLocked());
mController.notifyVisible(true);
}
+ private float getAnimTranslation() {
+ return mContext.getResources().getDimension(
+ R.dimen.volume_dialog_panel_width) / 2;
+ }
+
protected void rescheduleTimeoutH() {
mHandler.removeMessages(H.DISMISS);
final int timeout = computeTimeoutH();
@@ -470,14 +427,19 @@
}
protected void dismissH(int reason) {
- if (mMotion.isAnimating()) {
- return;
- }
mHandler.removeMessages(H.DISMISS);
mHandler.removeMessages(H.SHOW);
if (!mShowing) return;
mShowing = false;
- mMotion.startDismiss();
+ mHardwareLayout.setTranslationX(0);
+ mHardwareLayout.setAlpha(1);
+ mHardwareLayout.animate()
+ .alpha(0)
+ .translationX(getAnimTranslation())
+ .setDuration(300)
+ .withEndAction(() -> mDialog.dismiss())
+ .setInterpolator(new SystemUIInterpolators.LogAccelerateInterpolator())
+ .start();
if (mAccessibilityMgr.isEnabled()) {
AccessibilityEvent event =
AccessibilityEvent.obtain(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
@@ -497,30 +459,6 @@
}
}
- private void updateDialogBottomMarginH() {
- final long diff = System.currentTimeMillis() - mCollapseTime;
- final boolean collapsing = mCollapseTime != 0 && diff < getConservativeCollapseDuration();
- final ViewGroup.MarginLayoutParams mlp = (MarginLayoutParams) mDialogView.getLayoutParams();
- final int bottomMargin = collapsing ? mDialogContentView.getHeight() :
- mContext.getResources().getDimensionPixelSize(R.dimen.volume_dialog_margin_bottom);
- if (bottomMargin != mlp.bottomMargin) {
- if (D.BUG) Log.d(TAG, "bottomMargin " + mlp.bottomMargin + " -> " + bottomMargin);
- mlp.bottomMargin = bottomMargin;
- mDialogView.setLayoutParams(mlp);
- }
- }
-
- private long getConservativeCollapseDuration() {
- return mExpandButtonAnimationDuration * 3;
- }
-
- private void prepareForCollapse() {
- mHandler.removeMessages(H.UPDATE_BOTTOM_MARGIN);
- mCollapseTime = System.currentTimeMillis();
- updateDialogBottomMarginH();
- mHandler.sendEmptyMessageDelayed(H.UPDATE_BOTTOM_MARGIN, getConservativeCollapseDuration());
- }
-
private boolean shouldBeVisibleH(VolumeRow row, VolumeRow activeRow) {
boolean isActive = row == activeRow;
if (row.stream == AudioSystem.STREAM_ACCESSIBILITY) {
@@ -567,13 +505,7 @@
}
private void onStateChangedH(State state) {
- final boolean animating = mMotion.isAnimating();
- if (D.BUG) Log.d(TAG, "onStateChangedH animating=" + animating);
mState = state;
- if (animating) {
- mPendingStateChanged = true;
- return;
- }
mDynamic.clear();
// add any new dynamic rows
for (int i = 0; i < state.states.size(); i++) {
@@ -642,19 +574,13 @@
row.icon.setAlpha(iconEnabled ? 1 : 0.5f);
final int iconRes =
isRingVibrate ? R.drawable.ic_volume_ringer_vibrate
- : isRingSilent || zenMuted ? row.cachedIconRes
+ : isRingSilent || zenMuted ? row.iconMuteRes
: ss.routedToBluetooth ?
(ss.muted ? R.drawable.ic_volume_media_bt_mute
: R.drawable.ic_volume_media_bt)
: mAutomute && ss.level == 0 ? row.iconMuteRes
: (ss.muted ? row.iconMuteRes : row.iconRes);
- if (iconRes != row.cachedIconRes) {
- if (row.cachedIconRes != 0 && isRingVibrate) {
- mController.vibrate();
- }
- row.cachedIconRes = iconRes;
- row.icon.setImageResource(iconRes);
- }
+ row.icon.setImageResource(iconRes);
row.iconState =
iconRes == R.drawable.ic_volume_ringer_vibrate ? Events.ICON_STATE_VIBRATE
: (iconRes == R.drawable.ic_volume_media_bt_mute || iconRes == row.iconMuteRes)
@@ -864,14 +790,8 @@
@Override
public void onConfigurationChanged() {
- Configuration newConfig = mContext.getResources().getConfiguration();
- final int density = newConfig.densityDpi;
- if (density != mDensity) {
- mDialog.dismiss();
- initDialog();
- mDensity = density;
- }
- updateWindowWidthH();
+ mDialog.dismiss();
+ initDialog();
mConfigurableTexts.update();
}
@@ -908,23 +828,6 @@
}
};
- private final ZenModePanel.Callback mZenPanelCallback = new ZenModePanel.Callback() {
- @Override
- public void onPrioritySettings() {
- mCallback.onZenPrioritySettingsClicked();
- }
-
- @Override
- public void onInteraction() {
- mHandler.sendEmptyMessage(H.RESCHEDULE_TIMEOUT);
- }
-
- @Override
- public void onExpanded(boolean expanded) {
- // noop.
- }
- };
-
private final class H extends Handler {
private static final int SHOW = 1;
private static final int DISMISS = 2;
@@ -933,7 +836,6 @@
private static final int SET_STREAM_IMPORTANT = 5;
private static final int RESCHEDULE_TIMEOUT = 6;
private static final int STATE_CHANGED = 7;
- private static final int UPDATE_BOTTOM_MARGIN = 8;
public H() {
super(Looper.getMainLooper());
@@ -949,14 +851,13 @@
case SET_STREAM_IMPORTANT: setStreamImportantH(msg.arg1, msg.arg2 != 0); break;
case RESCHEDULE_TIMEOUT: rescheduleTimeoutH(); break;
case STATE_CHANGED: onStateChangedH(mState); break;
- case UPDATE_BOTTOM_MARGIN: updateDialogBottomMarginH(); break;
}
}
}
- private final class CustomDialog extends Dialog {
+ private final class CustomDialog extends Dialog implements DialogInterface {
public CustomDialog(Context context) {
- super(context);
+ super(context, com.android.systemui.R.style.qs_theme);
}
@Override
@@ -966,26 +867,15 @@
}
@Override
- protected void onStop() {
- super.onStop();
- final boolean animating = mMotion.isAnimating();
- if (D.BUG) Log.d(TAG, "onStop animating=" + animating);
- if (animating) {
- mPendingRecheckAll = true;
- return;
- }
- mHandler.sendEmptyMessage(H.RECHECK_ALL);
+ protected void onStart() {
+ super.setCanceledOnTouchOutside(true);
+ super.onStart();
}
@Override
- public boolean onTouchEvent(MotionEvent event) {
- if (isShowing()) {
- if (event.getAction() == MotionEvent.ACTION_OUTSIDE) {
- dismissH(Events.DISMISS_REASON_TOUCH_OUTSIDE);
- return true;
- }
- }
- return false;
+ protected void onStop() {
+ super.onStop();
+ mHandler.sendEmptyMessage(H.RECHECK_ALL);
}
@Override
@@ -1128,7 +1018,6 @@
private int iconRes;
private int iconMuteRes;
private boolean important;
- private int cachedIconRes;
private ColorStateList cachedSliderTint;
private int iconState; // from Events
private ObjectAnimator anim; // slider progress animation for non-touch-related updates
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogMotion.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogMotion.java
deleted file mode 100644
index 2b65fbd..0000000
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogMotion.java
+++ /dev/null
@@ -1,265 +0,0 @@
-/*
- * 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.
- */
-
-package com.android.systemui.volume;
-
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.TimeInterpolator;
-import android.animation.ValueAnimator;
-import android.animation.ValueAnimator.AnimatorUpdateListener;
-import android.app.Dialog;
-import android.content.DialogInterface;
-import android.content.DialogInterface.OnDismissListener;
-import android.content.DialogInterface.OnShowListener;
-import android.os.Handler;
-import android.util.Log;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.animation.PathInterpolator;
-
-public class VolumeDialogMotion {
- private static final String TAG = Util.logTag(VolumeDialogMotion.class);
-
- private static final float ANIMATION_SCALE = 1.0f;
- private static final int PRE_DISMISS_DELAY = 50;
-
- private final Dialog mDialog;
- private final View mDialogView;
- private final ViewGroup mContents; // volume rows + zen footer
- private final Handler mHandler = new Handler();
- private final Callback mCallback;
-
- private boolean mAnimating; // show or dismiss animation is running
- private boolean mShowing; // show animation is running
- private boolean mDismissing; // dismiss animation is running
- private ValueAnimator mContentsPositionAnimator;
-
- public VolumeDialogMotion(Dialog dialog, View dialogView, ViewGroup contents,
- Callback callback) {
- mDialog = dialog;
- mDialogView = dialogView;
- mContents = contents;
- mCallback = callback;
- mDialog.setOnDismissListener(new OnDismissListener() {
- @Override
- public void onDismiss(DialogInterface dialog) {
- if (D.BUG) Log.d(TAG, "mDialog.onDismiss");
- }
- });
- mDialog.setOnShowListener(new OnShowListener() {
- @Override
- public void onShow(DialogInterface dialog) {
- if (D.BUG) Log.d(TAG, "mDialog.onShow");
- final int h = mDialogView.getHeight();
- mDialogView.setTranslationY(-h);
- startShowAnimation();
- }
- });
- }
-
- public boolean isAnimating() {
- return mAnimating;
- }
-
- private void setShowing(boolean showing) {
- if (showing == mShowing) return;
- mShowing = showing;
- if (D.BUG) Log.d(TAG, "mShowing = " + mShowing);
- updateAnimating();
- }
-
- private void setDismissing(boolean dismissing) {
- if (dismissing == mDismissing) return;
- mDismissing = dismissing;
- if (D.BUG) Log.d(TAG, "mDismissing = " + mDismissing);
- updateAnimating();
- }
-
- private void updateAnimating() {
- final boolean animating = mShowing || mDismissing;
- if (animating == mAnimating) return;
- mAnimating = animating;
- if (D.BUG) Log.d(TAG, "mAnimating = " + mAnimating);
- if (mCallback != null) {
- mCallback.onAnimatingChanged(mAnimating);
- }
- }
-
- public void startShow() {
- if (D.BUG) Log.d(TAG, "startShow");
- if (mShowing) return;
- setShowing(true);
- if (mDismissing) {
- mDialogView.animate().cancel();
- setDismissing(false);
- startShowAnimation();
- return;
- }
- if (D.BUG) Log.d(TAG, "mDialog.show()");
- mDialog.show();
- }
-
- private void startShowAnimation() {
- if (D.BUG) Log.d(TAG, "startShowAnimation");
- mDialogView.animate()
- .translationY(0)
- .setDuration(scaledDuration(300))
- .setInterpolator(new LogDecelerateInterpolator())
- .setListener(null)
- .start();
-
- mContentsPositionAnimator = ValueAnimator.ofFloat(0).setDuration(scaledDuration(400));
- mContentsPositionAnimator.addListener(new AnimatorListenerAdapter() {
- private boolean mCancelled;
-
- @Override
- public void onAnimationEnd(Animator animation) {
- if (mCancelled) return;
- if (D.BUG) Log.d(TAG, "show.onAnimationEnd");
- setShowing(false);
- }
- @Override
- public void onAnimationCancel(Animator animation) {
- if (D.BUG) Log.d(TAG, "show.onAnimationCancel");
- mCancelled = true;
- }
- });
- mContentsPositionAnimator.addUpdateListener(new AnimatorUpdateListener() {
- @Override
- public void onAnimationUpdate(ValueAnimator animation) {
- float v = (Float) animation.getAnimatedValue();
- mContents.setTranslationY(v + -mDialogView.getTranslationY());
- }
- });
- mContentsPositionAnimator.setInterpolator(new LogDecelerateInterpolator());
- mContentsPositionAnimator.start();
-
- mContents.setAlpha(0);
- mContents.animate()
- .alpha(1)
- .setDuration(scaledDuration(150))
- .setInterpolator(new PathInterpolator(0f, 0f, .2f, 1f))
- .start();
- }
-
- public void startDismiss() {
- if (D.BUG) Log.d(TAG, "startDismiss");
- if (mDismissing) return;
- setDismissing(true);
- if (mShowing) {
- mDialogView.animate().cancel();
- if (mContentsPositionAnimator != null) {
- mContentsPositionAnimator.cancel();
- }
- mContents.animate().cancel();
- setShowing(false);
- }
- mDialogView.animate()
- .translationY(-mDialogView.getHeight())
- .setDuration(scaledDuration(250))
- .setInterpolator(new LogAccelerateInterpolator())
- .setUpdateListener(new AnimatorUpdateListener() {
- @Override
- public void onAnimationUpdate(ValueAnimator animation) {
- mContents.setTranslationY(-mDialogView.getTranslationY());
- }
- })
- .setListener(new AnimatorListenerAdapter() {
- private boolean mCancelled;
- @Override
- public void onAnimationEnd(Animator animation) {
- if (mCancelled) return;
- if (D.BUG) Log.d(TAG, "dismiss.onAnimationEnd");
- mHandler.postDelayed(new Runnable() {
- @Override
- public void run() {
- if (D.BUG) Log.d(TAG, "mDialog.dismiss()");
- mDialog.dismiss();
- setDismissing(false);
- }
- }, PRE_DISMISS_DELAY);
-
- }
- @Override
- public void onAnimationCancel(Animator animation) {
- if (D.BUG) Log.d(TAG, "dismiss.onAnimationCancel");
- mCancelled = true;
- }
- }).start();
- }
-
- private static int scaledDuration(int base) {
- return (int) (base * ANIMATION_SCALE);
- }
-
- public static final class LogDecelerateInterpolator implements TimeInterpolator {
- private final float mBase;
- private final float mDrift;
- private final float mTimeScale;
- private final float mOutputScale;
-
- public LogDecelerateInterpolator() {
- this(400f, 1.4f, 0);
- }
-
- private LogDecelerateInterpolator(float base, float timeScale, float drift) {
- mBase = base;
- mDrift = drift;
- mTimeScale = 1f / timeScale;
-
- mOutputScale = 1f / computeLog(1f);
- }
-
- private float computeLog(float t) {
- return 1f - (float) Math.pow(mBase, -t * mTimeScale) + (mDrift * t);
- }
-
- @Override
- public float getInterpolation(float t) {
- return computeLog(t) * mOutputScale;
- }
- }
-
- public static final class LogAccelerateInterpolator implements TimeInterpolator {
- private final int mBase;
- private final int mDrift;
- private final float mLogScale;
-
- public LogAccelerateInterpolator() {
- this(100, 0);
- }
-
- private LogAccelerateInterpolator(int base, int drift) {
- mBase = base;
- mDrift = drift;
- mLogScale = 1f / computeLog(1, mBase, mDrift);
- }
-
- private static float computeLog(float t, int base, int drift) {
- return (float) -Math.pow(base, -t) + 1 + (drift * t);
- }
-
- @Override
- public float getInterpolation(float t) {
- return 1 - computeLog(1 - t, mBase, mDrift) * mLogScale;
- }
- }
-
- public interface Callback {
- void onAnimatingChanged(boolean animating);
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationInfoTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationInfoTest.java
index 88fa659..6721938 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationInfoTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationInfoTest.java
@@ -745,7 +745,7 @@
mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
TEST_PACKAGE_NAME, Arrays.asList(mNotificationChannel),
mNotificationChannel.getImportance(), mSbn, null, null, null,
- (Runnable saveImportance) -> {
+ (Runnable saveImportance, StatusBarNotification sbn) -> {
},
Collections.singleton(TEST_PACKAGE_NAME));
@@ -762,7 +762,7 @@
mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
TEST_PACKAGE_NAME, Arrays.asList(mNotificationChannel),
mNotificationChannel.getImportance(), mSbn, null, null, null,
- (Runnable saveImportance) -> {
+ (Runnable saveImportance, StatusBarNotification sbn) -> {
saveImportance.run();
},
Collections.singleton(TEST_PACKAGE_NAME));
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java
index 3b401a5..a95e3db 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java
@@ -24,7 +24,7 @@
import android.testing.TestableLooper;
import android.testing.TestableLooper.RunWithLooper;
-import com.android.internal.app.NightDisplayController;
+import com.android.internal.app.ColorDisplayController;
import com.android.systemui.Dependency;
import com.android.systemui.Prefs;
import com.android.systemui.Prefs.Key;
@@ -61,49 +61,49 @@
@Test
public void nightTileAdded_whenActivated() {
- if (!NightDisplayController.isAvailable(mContext)) {
+ if (!ColorDisplayController.isAvailable(mContext)) {
return;
}
- mAutoTileManager.mNightDisplayCallback.onActivated(true);
+ mAutoTileManager.mColorDisplayCallback.onActivated(true);
verify(mQsTileHost).addTile("night");
}
@Test
public void nightTileNotAdded_whenDeactivated() {
- if (!NightDisplayController.isAvailable(mContext)) {
+ if (!ColorDisplayController.isAvailable(mContext)) {
return;
}
- mAutoTileManager.mNightDisplayCallback.onActivated(false);
+ mAutoTileManager.mColorDisplayCallback.onActivated(false);
verify(mQsTileHost, never()).addTile("night");
}
@Test
public void nightTileAdded_whenNightModeTwilight() {
- if (!NightDisplayController.isAvailable(mContext)) {
+ if (!ColorDisplayController.isAvailable(mContext)) {
return;
}
- mAutoTileManager.mNightDisplayCallback.onAutoModeChanged(
- NightDisplayController.AUTO_MODE_TWILIGHT);
+ mAutoTileManager.mColorDisplayCallback.onAutoModeChanged(
+ ColorDisplayController.AUTO_MODE_TWILIGHT);
verify(mQsTileHost).addTile("night");
}
@Test
public void nightTileAdded_whenNightModeCustom() {
- if (!NightDisplayController.isAvailable(mContext)) {
+ if (!ColorDisplayController.isAvailable(mContext)) {
return;
}
- mAutoTileManager.mNightDisplayCallback.onAutoModeChanged(
- NightDisplayController.AUTO_MODE_CUSTOM);
+ mAutoTileManager.mColorDisplayCallback.onAutoModeChanged(
+ ColorDisplayController.AUTO_MODE_CUSTOM);
verify(mQsTileHost).addTile("night");
}
@Test
public void nightTileNotAdded_whenNightModeDisabled() {
- if (!NightDisplayController.isAvailable(mContext)) {
+ if (!ColorDisplayController.isAvailable(mContext)) {
return;
}
- mAutoTileManager.mNightDisplayCallback.onAutoModeChanged(
- NightDisplayController.AUTO_MODE_DISABLED);
+ mAutoTileManager.mColorDisplayCallback.onAutoModeChanged(
+ ColorDisplayController.AUTO_MODE_DISABLED);
verify(mQsTileHost, never()).addTile("night");
}
}
diff --git a/proto/src/metrics_constants.proto b/proto/src/metrics_constants.proto
index d3dcf5a..73a6d7c 100644
--- a/proto/src/metrics_constants.proto
+++ b/proto/src/metrics_constants.proto
@@ -171,6 +171,12 @@
TEXT_CLASSIFIER_TYPE_URL = 6;
}
+ // Selection invocation methods. Used as sub-type for TEXT_SELECTION_SESSION events.
+ enum TextSelectionInvocationMethod {
+ TEXT_SELECTION_INVOCATION_MANUAL = 1;
+ TEXT_SELECTION_INVOCATION_LINK = 2;
+ }
+
// Known visual elements: views or controls.
enum View {
// Unknown view
@@ -4680,6 +4686,78 @@
// OS: P
DIALOG_FIRMWARE_VERSION = 1247;
+ // OPEN: Settings > Network & internet > Menu > Private DNS
+ // CATEGORY: SETTINGS
+ // OS: P
+ DIALOG_PRIVATE_DNS = 1248;
+
+ // ACTION: A private dns mode been selected by user
+ // CATEGORY: SETTINGS
+ // OS: P
+ ACTION_PRIVATE_DNS_MODE = 1249;
+
+ // FIELD: text select start offset in words (as defined by the ICU BreakIterator).
+ // CATEGORY: TEXT_SELECTION_SESSION
+ // OS: P
+ FIELD_SELECTION_RANGE_START = 1250;
+
+ // FIELD: text select end offset in words (as defined by the ICU BreakIterator).
+ // CATEGORY: TEXT_SELECTION_SESSION
+ // OS: P
+ FIELD_SELECTION_RANGE_END = 1251;
+
+ // FIELD: smart text selection start offset in words (as defined by the ICU BreakIterator),
+ // stored as two packed 16bit integers. (start in MSBs, end in LSBs)
+ // CATEGORY: TEXT_SELECTION_SESSION
+ // OS: P
+ FIELD_SELECTION_SMART_RANGE_START = 1252;
+
+ // FIELD: smart text selection end offset in words (as defined by the ICU BreakIterator),
+ // stored as two packed 16bit integers. (start in MSBs, end in LSBs)
+ // CATEGORY: TEXT_SELECTION_SESSION
+ // OS: P
+ FIELD_SELECTION_SMART_RANGE_END = 1253;
+
+ // FIELD: the entity type of the text currently selected.
+ // CATEGORY: TEXT_SELECTION_SESSION
+ // OS: P
+ FIELD_SELECTION_ENTITY_TYPE = 1254;
+
+ // FIELD: the type of widget the selection was made in.
+ // CATEGORY: TEXT_SELECTION_SESSION
+ // OS: P
+ FIELD_SELECTION_WIDGET_TYPE = 1255;
+
+ // FIELD: the name of the text classifier model used.
+ // CATEGORY: TEXT_SELECTION_SESSION
+ // OS: P
+ FIELD_TEXTCLASSIFIER_MODEL = 1256;
+
+ // OPEN: Settings > Sound & notification > Do Not Disturb > Behavior > Messages
+ // CATEGORY: SETTINGS
+ // OS: P
+ NOTIFICATION_ZEN_MODE_MESSAGES = 1257;
+
+ // OPEN: Settings > Sound & notification > Do Not Disturb > Behavior > Calls
+ // CATEGORY: SETTINGS
+ // OS: P
+ NOTIFICATION_ZEN_MODE_CALLS = 1258;
+
+ // OPEN: Settings > Sound & notification > Do Not Disturb > TURN ON -> Until you turn off
+ // CATEGORY: SETTINGS
+ // OS: P
+ NOTIFICATION_ZEN_MODE_TOGGLE_ON_FOREVER = 1259;
+
+ // OPEN: Settings > Sound & notification > Do Not Disturb > TURN ON -> Time countdown manual rule (ie: for one hour)
+ // CATEGORY: SETTINGS
+ // OS: P
+ NOTIFICATION_ZEN_MODE_TOGGLE_ON_COUNTDOWN = 1260;
+
+ // OPEN: Settings > Sound & notification > Do Not Disturb > TURN ON -> Next Alarm (ie: Until Tue 7:20 AM)
+ // CATEGORY: SETTINGS
+ // OS: P
+ NOTIFICATION_ZEN_MODE_TOGGLE_ON_ALARM = 1261;
+
// Add new aosp constants above this line.
// END OF AOSP CONSTANTS
}
diff --git a/proto/src/wifi.proto b/proto/src/wifi.proto
index 9d25055..8f58a38 100644
--- a/proto/src/wifi.proto
+++ b/proto/src/wifi.proto
@@ -364,6 +364,12 @@
// (one value added per unique ESS - potentially multiple counts per single
// scan!)
repeated NumConnectableNetworksBucket observed_hotspot_r2_aps_per_ess_in_scan_histogram = 88;
+
+ // SoftAP event list tracking sessions and client counts in tethered mode
+ repeated SoftApConnectedClientsEvent soft_ap_connected_clients_events_tethered = 89;
+
+ // SoftAP event list tracking sessions and client counts in local only mode
+ repeated SoftApConnectedClientsEvent soft_ap_connected_clients_events_local_only = 90;
}
// Information that gets logged for every WiFi connection.
@@ -1071,3 +1077,29 @@
// Occurrences of this action.
optional int32 count = 4;
}
+
+// SoftAP event tracking sessions and client counts
+message SoftApConnectedClientsEvent {
+
+ // Soft AP event Types
+ enum SoftApEventType {
+
+ // Soft AP is Up and ready for use
+ SOFT_AP_UP = 0;
+
+ // Soft AP is Down
+ SOFT_AP_DOWN = 1;
+
+ // Number of connected soft AP clients has changed
+ NUM_CLIENTS_CHANGED = 2;
+ }
+
+ // Type of event being recorded
+ optional SoftApEventType event_type = 1;
+
+ // Absolute time when event happened
+ optional int64 time_stamp_millis = 2;
+
+ // Number of connected clients if event_type is NUM_CLIENTS_CHANGED, otherwise zero.
+ optional int32 num_connected_clients = 3;
+}
\ No newline at end of file
diff --git a/services/art-profile b/services/art-profile
index ac5ecaf..301918f 100644
--- a/services/art-profile
+++ b/services/art-profile
@@ -2374,6 +2374,9 @@
HPLcom/android/server/display/AutomaticBrightnessController;->updateAmbientLux()V
HPLcom/android/server/display/AutomaticBrightnessController;->updateAmbientLux(J)V
HPLcom/android/server/display/AutomaticBrightnessController;->weightIntegral(J)F
+HPLcom/android/server/display/ColorDisplayService$3;->onAnimationUpdate(Landroid/animation/ValueAnimator;)V
+HPLcom/android/server/display/ColorDisplayService$ColorMatrixEvaluator;->evaluate(FLjava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
+HPLcom/android/server/display/ColorDisplayService$ColorMatrixEvaluator;->evaluate(F[F[F)[F
HPLcom/android/server/display/DisplayBlanker;->requestDisplayState(II)V
HPLcom/android/server/display/DisplayManagerService$BinderService;->getDisplayIds()[I
HPLcom/android/server/display/DisplayManagerService$CallbackRecord;->binderDied()V
@@ -2430,9 +2433,6 @@
HPLcom/android/server/display/LogicalDisplay;->getRequestedModeIdLocked()I
HPLcom/android/server/display/LogicalDisplay;->hasContentLocked()Z
HPLcom/android/server/display/LogicalDisplay;->setDisplayInfoOverrideFromWindowManagerLocked(Landroid/view/DisplayInfo;)Z
-HPLcom/android/server/display/NightDisplayService$3;->onAnimationUpdate(Landroid/animation/ValueAnimator;)V
-HPLcom/android/server/display/NightDisplayService$ColorMatrixEvaluator;->evaluate(FLjava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
-HPLcom/android/server/display/NightDisplayService$ColorMatrixEvaluator;->evaluate(F[F[F)[F
HPLcom/android/server/display/RampAnimator$1;->run()V
HPLcom/android/server/display/RampAnimator$Listener;->onAnimationEnd()V
HPLcom/android/server/display/RampAnimator;->-get0(Lcom/android/server/display/RampAnimator;)F
@@ -10133,6 +10133,25 @@
PLcom/android/server/display/AutomaticBrightnessController;->setLightSensorEnabled(Z)Z
PLcom/android/server/display/AutomaticBrightnessController;->setScreenAutoBrightnessAdjustment(F)Z
PLcom/android/server/display/AutomaticBrightnessController;->updateAutoBrightness(Z)V
+PLcom/android/server/display/ColorDisplayService$1;-><init>(Lcom/android/server/display/ColorDisplayService;)V
+PLcom/android/server/display/ColorDisplayService$3;-><init>(Lcom/android/server/display/ColorDisplayService;Lcom/android/server/display/DisplayTransformManager;)V
+PLcom/android/server/display/ColorDisplayService$4;-><init>(Lcom/android/server/display/ColorDisplayService;Lcom/android/server/display/DisplayTransformManager;[F)V
+PLcom/android/server/display/ColorDisplayService$4;->onAnimationCancel(Landroid/animation/Animator;)V
+PLcom/android/server/display/ColorDisplayService$4;->onAnimationEnd(Landroid/animation/Animator;)V
+PLcom/android/server/display/ColorDisplayService$ColorMatrixEvaluator;-><init>()V
+PLcom/android/server/display/ColorDisplayService$ColorMatrixEvaluator;-><init>(Lcom/android/server/display/ColorDisplayService$ColorMatrixEvaluator;)V
+PLcom/android/server/display/ColorDisplayService;->-set0(Lcom/android/server/display/ColorDisplayService;Landroid/animation/ValueAnimator;)Landroid/animation/ValueAnimator;
+PLcom/android/server/display/ColorDisplayService;-><init>(Landroid/content/Context;)V
+PLcom/android/server/display/ColorDisplayService;->applyTint(Z)V
+PLcom/android/server/display/ColorDisplayService;->isUserSetupCompleted(Landroid/content/ContentResolver;I)Z
+PLcom/android/server/display/ColorDisplayService;->onActivated(Z)V
+PLcom/android/server/display/ColorDisplayService;->onAutoModeChanged(I)V
+PLcom/android/server/display/ColorDisplayService;->onBootPhase(I)V
+PLcom/android/server/display/ColorDisplayService;->onStart()V
+PLcom/android/server/display/ColorDisplayService;->onStartUser(I)V
+PLcom/android/server/display/ColorDisplayService;->onUserChanged(I)V
+PLcom/android/server/display/ColorDisplayService;->setMatrix(I[F)V
+PLcom/android/server/display/ColorDisplayService;->setUp()V
PLcom/android/server/display/ColorFade;-><init>(I)V
PLcom/android/server/display/ColorFade;->createNativeFloatBuffer(I)Ljava/nio/FloatBuffer;
PLcom/android/server/display/ColorFade;->dismiss()V
@@ -10230,25 +10249,6 @@
PLcom/android/server/display/LogicalDisplay;->getDisplayIdLocked()I
PLcom/android/server/display/LogicalDisplay;->getNonOverrideDisplayInfoLocked(Landroid/view/DisplayInfo;)V
PLcom/android/server/display/LogicalDisplay;->setHasContentLocked(Z)V
-PLcom/android/server/display/NightDisplayService$1;-><init>(Lcom/android/server/display/NightDisplayService;)V
-PLcom/android/server/display/NightDisplayService$3;-><init>(Lcom/android/server/display/NightDisplayService;Lcom/android/server/display/DisplayTransformManager;)V
-PLcom/android/server/display/NightDisplayService$4;-><init>(Lcom/android/server/display/NightDisplayService;Lcom/android/server/display/DisplayTransformManager;[F)V
-PLcom/android/server/display/NightDisplayService$4;->onAnimationCancel(Landroid/animation/Animator;)V
-PLcom/android/server/display/NightDisplayService$4;->onAnimationEnd(Landroid/animation/Animator;)V
-PLcom/android/server/display/NightDisplayService$ColorMatrixEvaluator;-><init>()V
-PLcom/android/server/display/NightDisplayService$ColorMatrixEvaluator;-><init>(Lcom/android/server/display/NightDisplayService$ColorMatrixEvaluator;)V
-PLcom/android/server/display/NightDisplayService;->-set0(Lcom/android/server/display/NightDisplayService;Landroid/animation/ValueAnimator;)Landroid/animation/ValueAnimator;
-PLcom/android/server/display/NightDisplayService;-><init>(Landroid/content/Context;)V
-PLcom/android/server/display/NightDisplayService;->applyTint(Z)V
-PLcom/android/server/display/NightDisplayService;->isUserSetupCompleted(Landroid/content/ContentResolver;I)Z
-PLcom/android/server/display/NightDisplayService;->onActivated(Z)V
-PLcom/android/server/display/NightDisplayService;->onAutoModeChanged(I)V
-PLcom/android/server/display/NightDisplayService;->onBootPhase(I)V
-PLcom/android/server/display/NightDisplayService;->onStart()V
-PLcom/android/server/display/NightDisplayService;->onStartUser(I)V
-PLcom/android/server/display/NightDisplayService;->onUserChanged(I)V
-PLcom/android/server/display/NightDisplayService;->setMatrix(I[F)V
-PLcom/android/server/display/NightDisplayService;->setUp()V
PLcom/android/server/display/OverlayDisplayAdapter$1$1;-><init>(Lcom/android/server/display/OverlayDisplayAdapter$1;Landroid/os/Handler;)V
PLcom/android/server/display/OverlayDisplayAdapter$1;-><init>(Lcom/android/server/display/OverlayDisplayAdapter;)V
PLcom/android/server/display/OverlayDisplayAdapter$1;->run()V
diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java
index e609bd7..831c9cb 100644
--- a/services/core/java/com/android/server/TelephonyRegistry.java
+++ b/services/core/java/com/android/server/TelephonyRegistry.java
@@ -1356,31 +1356,6 @@
}
}
- public void notifyOemHookRawEventForSubscriber(int subId, byte[] rawData) {
- if (!checkNotifyPermission("notifyOemHookRawEventForSubscriber")) {
- return;
- }
-
- synchronized (mRecords) {
- for (Record r : mRecords) {
- if (VDBG) {
- log("notifyOemHookRawEventForSubscriber: r=" + r + " subId=" + subId);
- }
- if ((r.matchPhoneStateListenerEvent(
- PhoneStateListener.LISTEN_OEM_HOOK_RAW_EVENT)) &&
- ((r.subId == subId) ||
- (r.subId == SubscriptionManager.DEFAULT_SUBSCRIPTION_ID))) {
- try {
- r.callback.onOemHookRawEvent(rawData);
- } catch (RemoteException ex) {
- mRemoveList.add(r.binder);
- }
- }
- }
- handleRemoveListLocked();
- }
- }
-
@Override
public void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
final IndentingPrintWriter pw = new IndentingPrintWriter(writer, " ");
@@ -1673,11 +1648,6 @@
android.Manifest.permission.READ_PRECISE_PHONE_STATE, null);
}
-
- if ((events & PhoneStateListener.LISTEN_OEM_HOOK_RAW_EVENT) != 0) {
- mContext.enforceCallingOrSelfPermission(
- android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, null);
- }
}
private void handleRemoveListLocked() {
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 842a5b8..02ba6a0 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -3889,7 +3889,7 @@
}
if (app.info.isPrivilegedApp() &&
- !SystemProperties.getBoolean("pm.dexopt.priv-apps", true)) {
+ SystemProperties.getBoolean("pm.dexopt.priv-apps-oob", false)) {
runtimeFlags |= Zygote.DISABLE_VERIFIER;
runtimeFlags |= Zygote.ONLY_USE_SYSTEM_OAT_FILES;
}
@@ -7075,10 +7075,12 @@
}
// We deprecated Build.SERIAL and it is not accessible to
- // apps that target the v2 security sandbox. Since access to
- // the serial is now behind a permission we push down the value.
- String buildSerial = appInfo.targetSandboxVersion < 2
- ? sTheRealBuildSerial : Build.UNKNOWN;
+ // apps that target the v2 security sandbox and to apps that
+ // target APIs higher than O MR1. Since access to the serial
+ // is now behind a permission we push down the value.
+ final String buildSerial = (appInfo.targetSandboxVersion < 2
+ && appInfo.targetSdkVersion <= Build.VERSION_CODES.O_MR1)
+ ? sTheRealBuildSerial : Build.UNKNOWN;
// Check if this is a secondary process that should be incorporated into some
// currently active instrumentation. (Note we do this AFTER all of the profiling
diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java
index 1fefbd1..8cc584e 100644
--- a/services/core/java/com/android/server/am/ActivityStack.java
+++ b/services/core/java/com/android/server/am/ActivityStack.java
@@ -871,6 +871,29 @@
}
}
+ /**
+ * @param reason The reason for moving the stack to the back.
+ * @param task If non-null, the task will be moved to the bottom of the stack.
+ **/
+ void moveToBack(String reason, TaskRecord task) {
+ if (!isAttached()) {
+ return;
+ }
+
+ getDisplay().positionChildAtBottom(this);
+ mStackSupervisor.setFocusStackUnchecked(reason, getDisplay().getTopStack());
+ if (task != null) {
+ insertTaskAtBottom(task);
+ return;
+ } else {
+ task = bottomTask();
+ if (task != null) {
+ mWindowContainerController.positionChildAtBottom(
+ task.getWindowContainerController(), true /* includingParents */);
+ }
+ }
+ }
+
boolean isFocusable() {
if (getWindowConfiguration().canReceiveKeys()) {
return true;
@@ -2591,6 +2614,9 @@
if (position >= mTaskHistory.size()) {
insertTaskAtTop(task, null);
return;
+ } else if (position <= 0) {
+ insertTaskAtBottom(task);
+ return;
}
position = getAdjustedPositionForTask(task, position, null /* starting */);
mTaskHistory.remove(task);
@@ -2611,6 +2637,16 @@
true /* includingParents */);
}
+ private void insertTaskAtBottom(TaskRecord task) {
+ // Unlike insertTaskAtPosition, this will also position parents of the windowcontroller.
+ mTaskHistory.remove(task);
+ final int position = getAdjustedPositionForTask(task, 0, null);
+ mTaskHistory.add(position, task);
+ updateTaskMovement(task, true);
+ mWindowContainerController.positionChildAtBottom(task.getWindowContainerController(),
+ true /* includingParents */);
+ }
+
final void startActivityLocked(ActivityRecord r, ActivityRecord focusedTopActivity,
boolean newTask, boolean keepCurTransition, ActivityOptions options) {
TaskRecord rTask = r.getTask();
@@ -4380,8 +4416,7 @@
updateTaskMovement(tr, false);
mWindowManager.prepareAppTransition(TRANSIT_TASK_TO_BACK, false);
- mWindowContainerController.positionChildAtBottom(tr.getWindowContainerController(),
- true /* includingParents */);
+ moveToBack("moveTaskToBackLocked", tr);
if (inPinnedWindowingMode()) {
mStackSupervisor.removeStack(this);
diff --git a/services/core/java/com/android/server/am/KeyguardController.java b/services/core/java/com/android/server/am/KeyguardController.java
index c3fed17..76b4679 100644
--- a/services/core/java/com/android/server/am/KeyguardController.java
+++ b/services/core/java/com/android/server/am/KeyguardController.java
@@ -41,10 +41,8 @@
import android.os.Trace;
import android.util.Slog;
import android.util.proto.ProtoOutputStream;
-
import com.android.internal.policy.IKeyguardDismissCallback;
import com.android.server.wm.WindowManagerService;
-
import java.io.PrintWriter;
/**
@@ -236,24 +234,33 @@
final ActivityRecord lastDismissingKeyguardActivity = mDismissingKeyguardActivity;
mOccluded = false;
mDismissingKeyguardActivity = null;
- final ActivityDisplay display = mStackSupervisor.getDefaultDisplay();
- for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) {
- final ActivityStack stack = display.getChildAt(stackNdx);
- // Only the focused stack top activity may control occluded state
- if (mStackSupervisor.isFocusedStack(stack)) {
+ for (int displayNdx = mStackSupervisor.getChildCount() - 1; displayNdx >= 0; displayNdx--) {
+ final ActivityDisplay display = mStackSupervisor.getChildAt(displayNdx);
+ for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) {
+ final ActivityStack stack = display.getChildAt(stackNdx);
- // A dismissing activity occludes Keyguard in the insecure case for legacy reasons.
- final ActivityRecord topDismissing = stack.getTopDismissingKeyguardActivity();
- mOccluded = stack.topActivityOccludesKeyguard()
- || (topDismissing != null
- && stack.topRunningActivityLocked() == topDismissing
- && canShowWhileOccluded(true /* dismissKeyguard */,
- false /* showWhenLocked */));
- }
- if (mDismissingKeyguardActivity == null
- && stack.getTopDismissingKeyguardActivity() != null) {
- mDismissingKeyguardActivity = stack.getTopDismissingKeyguardActivity();
+ // Only the top activity of the focused stack on the default display may control
+ // occluded state.
+ if (display.mDisplayId == DEFAULT_DISPLAY
+ && mStackSupervisor.isFocusedStack(stack)) {
+
+ // A dismissing activity occludes Keyguard in the insecure case for legacy
+ // reasons.
+ final ActivityRecord topDismissing = stack.getTopDismissingKeyguardActivity();
+ mOccluded =
+ stack.topActivityOccludesKeyguard()
+ || (topDismissing != null
+ && stack.topRunningActivityLocked() == topDismissing
+ && canShowWhileOccluded(
+ true /* dismissKeyguard */,
+ false /* showWhenLocked */));
+ }
+
+ if (mDismissingKeyguardActivity == null
+ && stack.getTopDismissingKeyguardActivity() != null) {
+ mDismissingKeyguardActivity = stack.getTopDismissingKeyguardActivity();
+ }
}
}
mOccluded |= mWindowManager.isShowingDream();
diff --git a/services/core/java/com/android/server/am/TaskRecord.java b/services/core/java/com/android/server/am/TaskRecord.java
index c5514fb..40b1cac 100644
--- a/services/core/java/com/android/server/am/TaskRecord.java
+++ b/services/core/java/com/android/server/am/TaskRecord.java
@@ -1139,6 +1139,9 @@
mActivities.remove(newTop);
mActivities.add(newTop);
+
+ // Make sure window manager is aware of the position change.
+ mWindowContainerController.positionChildAtTop(newTop.mWindowContainerController);
updateEffectiveIntent();
setFrontOfTask();
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 3f23737..15a418dc 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -1345,8 +1345,9 @@
} else {
final int maybeActiveStreamType = getActiveStreamType(suggestedStreamType);
final boolean activeForReal;
- if (maybeActiveStreamType == AudioSystem.STREAM_MUSIC) {
- activeForReal = isAfMusicActiveRecently(0);
+ if (maybeActiveStreamType == AudioSystem.STREAM_RING
+ || maybeActiveStreamType == AudioSystem.STREAM_NOTIFICATION) {
+ activeForReal = wasStreamActiveRecently(maybeActiveStreamType, 0);
} else {
activeForReal = AudioSystem.isStreamActive(maybeActiveStreamType, 0);
}
@@ -3883,13 +3884,13 @@
/**
* For code clarity for getActiveStreamType(int)
- * @param delay_ms max time since last STREAM_MUSIC activity to consider
- * @return true if STREAM_MUSIC is active in streams handled by AudioFlinger now or
+ * @param delay_ms max time since last stream activity to consider
+ * @return true if stream is active in streams handled by AudioFlinger now or
* in the last "delay_ms" ms.
*/
- private boolean isAfMusicActiveRecently(int delay_ms) {
- return AudioSystem.isStreamActive(AudioSystem.STREAM_MUSIC, delay_ms)
- || AudioSystem.isStreamActiveRemotely(AudioSystem.STREAM_MUSIC, delay_ms);
+ private boolean wasStreamActiveRecently(int stream, int delay_ms) {
+ return AudioSystem.isStreamActive(stream, delay_ms)
+ || AudioSystem.isStreamActiveRemotely(stream, delay_ms);
}
private int getActiveStreamType(int suggestedStreamType) {
@@ -3910,21 +3911,30 @@
return AudioSystem.STREAM_VOICE_CALL;
}
} else if (suggestedStreamType == AudioManager.USE_DEFAULT_STREAM_TYPE) {
- if (isAfMusicActiveRecently(sStreamOverrideDelayMs)) {
+ if (wasStreamActiveRecently(AudioSystem.STREAM_RING, sStreamOverrideDelayMs)) {
if (DEBUG_VOL)
- Log.v(TAG, "getActiveStreamType: Forcing STREAM_MUSIC stream active");
+ Log.v(TAG, "getActiveStreamType: Forcing STREAM_RING stream active");
+ return AudioSystem.STREAM_RING;
+ } else if (wasStreamActiveRecently(
+ AudioSystem.STREAM_NOTIFICATION, sStreamOverrideDelayMs)) {
+ if (DEBUG_VOL)
+ Log.v(TAG, "getActiveStreamType: Forcing STREAM_NOTIFICATION stream active");
+ return AudioSystem.STREAM_NOTIFICATION;
+ } else {
+ if (DEBUG_VOL)
+ Log.v(TAG, "getActiveStreamType: Forcing STREAM_MUSIC b/c default");
return AudioSystem.STREAM_MUSIC;
- } else {
- if (DEBUG_VOL)
- Log.v(TAG, "getActiveStreamType: Forcing STREAM_RING b/c default");
- return AudioSystem.STREAM_RING;
}
- } else if (isAfMusicActiveRecently(0)) {
+ } else if (
+ wasStreamActiveRecently(AudioSystem.STREAM_NOTIFICATION, sStreamOverrideDelayMs)) {
if (DEBUG_VOL)
- Log.v(TAG, "getActiveStreamType: Forcing STREAM_MUSIC stream active");
- return AudioSystem.STREAM_MUSIC;
+ Log.v(TAG, "getActiveStreamType: Forcing STREAM_NOTIFICATION stream active");
+ return AudioSystem.STREAM_NOTIFICATION;
+ } else if (wasStreamActiveRecently(AudioSystem.STREAM_RING, sStreamOverrideDelayMs)) {
+ if (DEBUG_VOL)
+ Log.v(TAG, "getActiveStreamType: Forcing STREAM_RING stream active");
+ return AudioSystem.STREAM_RING;
}
- break;
default:
if (isInCommunication()) {
if (AudioSystem.getForceUse(AudioSystem.FOR_COMMUNICATION)
@@ -3935,20 +3945,26 @@
if (DEBUG_VOL) Log.v(TAG, "getActiveStreamType: Forcing STREAM_VOICE_CALL");
return AudioSystem.STREAM_VOICE_CALL;
}
- } else if (AudioSystem.isStreamActive(AudioSystem.STREAM_NOTIFICATION,
- sStreamOverrideDelayMs) ||
- AudioSystem.isStreamActive(AudioSystem.STREAM_RING,
- sStreamOverrideDelayMs)) {
+ } else if (AudioSystem.isStreamActive(
+ AudioSystem.STREAM_NOTIFICATION, sStreamOverrideDelayMs)) {
if (DEBUG_VOL) Log.v(TAG, "getActiveStreamType: Forcing STREAM_NOTIFICATION");
return AudioSystem.STREAM_NOTIFICATION;
+ } else if (AudioSystem.isStreamActive(
+ AudioSystem.STREAM_RING, sStreamOverrideDelayMs)) {
+ if (DEBUG_VOL) Log.v(TAG, "getActiveStreamType: Forcing STREAM_RING");
+ return AudioSystem.STREAM_RING;
} else if (suggestedStreamType == AudioManager.USE_DEFAULT_STREAM_TYPE) {
- if (isAfMusicActiveRecently(sStreamOverrideDelayMs)) {
- if (DEBUG_VOL) Log.v(TAG, "getActiveStreamType: forcing STREAM_MUSIC");
- return AudioSystem.STREAM_MUSIC;
- } else {
- if (DEBUG_VOL) Log.v(TAG,
- "getActiveStreamType: using STREAM_NOTIFICATION as default");
+ if (AudioSystem.isStreamActive(
+ AudioSystem.STREAM_NOTIFICATION, sStreamOverrideDelayMs)) {
+ if (DEBUG_VOL) Log.v(TAG, "getActiveStreamType: Forcing STREAM_NOTIFICATION");
return AudioSystem.STREAM_NOTIFICATION;
+ } else if (AudioSystem.isStreamActive(
+ AudioSystem.STREAM_RING, sStreamOverrideDelayMs)) {
+ if (DEBUG_VOL) Log.v(TAG, "getActiveStreamType: Forcing STREAM_RING");
+ return AudioSystem.STREAM_RING;
+ } else {
+ if (DEBUG_VOL) Log.v(TAG, "getActiveStreamType: using STREAM_MUSIC as default");
+ return AudioSystem.STREAM_MUSIC;
}
}
break;
@@ -6349,10 +6365,10 @@
// stream override timeout when adjusting volume
//---------------------------------------------------------------------------------
- // AudioService.getActiveStreamType() will return:
// - STREAM_NOTIFICATION on tablets during this period after a notification stopped
- // - STREAM_MUSIC on phones during this period after music or talkback/voice search prompt
- // stopped
+ // - STREAM_RING on phones during this period after a notification stopped
+ // - STREAM_MUSIC otherwise
+
private static final int DEFAULT_STREAM_TYPE_OVERRIDE_DELAY_MS = 0;
private static final int TOUCH_EXPLORE_STREAM_TYPE_OVERRIDE_DELAY_MS = 1000;
diff --git a/services/core/java/com/android/server/connectivity/IpConnectivityMetrics.java b/services/core/java/com/android/server/connectivity/IpConnectivityMetrics.java
index 5cc390a..f427819 100644
--- a/services/core/java/com/android/server/connectivity/IpConnectivityMetrics.java
+++ b/services/core/java/com/android/server/connectivity/IpConnectivityMetrics.java
@@ -23,8 +23,6 @@
import android.net.metrics.ApfProgramEvent;
import android.net.metrics.IpConnectivityLog;
import android.os.Binder;
-import android.os.IBinder;
-import android.os.Parcelable;
import android.os.Process;
import android.provider.Settings;
import android.text.TextUtils;
@@ -322,22 +320,22 @@
}
@Override
- public boolean registerNetdEventCallback(INetdEventCallback callback) {
+ public boolean addNetdEventCallback(int callerType, INetdEventCallback callback) {
enforceNetdEventListeningPermission();
if (mNetdListener == null) {
return false;
}
- return mNetdListener.registerNetdEventCallback(callback);
+ return mNetdListener.addNetdEventCallback(callerType, callback);
}
@Override
- public boolean unregisterNetdEventCallback() {
+ public boolean removeNetdEventCallback(int callerType) {
enforceNetdEventListeningPermission();
if (mNetdListener == null) {
// if the service is null, we aren't registered anyway
return true;
}
- return mNetdListener.unregisterNetdEventCallback();
+ return mNetdListener.removeNetdEventCallback(callerType);
}
};
diff --git a/services/core/java/com/android/server/connectivity/NetdEventListenerService.java b/services/core/java/com/android/server/connectivity/NetdEventListenerService.java
index 61b11e1..af138b93 100644
--- a/services/core/java/com/android/server/connectivity/NetdEventListenerService.java
+++ b/services/core/java/com/android/server/connectivity/NetdEventListenerService.java
@@ -98,21 +98,55 @@
@GuardedBy("this")
private final TokenBucket mConnectTb =
new TokenBucket(CONNECT_LATENCY_FILL_RATE, CONNECT_LATENCY_BURST_LIMIT);
- // Callback should only be registered/unregistered when logging is being enabled/disabled in DPM
- // by the device owner. It's DevicePolicyManager's responsibility to ensure that.
- @GuardedBy("this")
- private INetdEventCallback mNetdEventCallback;
- public synchronized boolean registerNetdEventCallback(INetdEventCallback callback) {
- mNetdEventCallback = callback;
+
+ /**
+ * There are only 2 possible callbacks.
+ *
+ * mNetdEventCallbackList[CALLBACK_CALLER_DEVICE_POLICY].
+ * Callback registered/unregistered when logging is being enabled/disabled in DPM
+ * by the device owner. It's DevicePolicyManager's responsibility to ensure that.
+ *
+ * mNetdEventCallbackList[CALLBACK_CALLER_NETWORK_WATCHLIST]
+ * Callback registered/unregistered by NetworkWatchlistService.
+ */
+ @GuardedBy("this")
+ private static final int[] ALLOWED_CALLBACK_TYPES = {
+ INetdEventCallback.CALLBACK_CALLER_DEVICE_POLICY,
+ INetdEventCallback.CALLBACK_CALLER_NETWORK_WATCHLIST
+ };
+
+ @GuardedBy("this")
+ private INetdEventCallback[] mNetdEventCallbackList =
+ new INetdEventCallback[ALLOWED_CALLBACK_TYPES.length];
+
+ public synchronized boolean addNetdEventCallback(int callerType, INetdEventCallback callback) {
+ if (!isValidCallerType(callerType)) {
+ Log.e(TAG, "Invalid caller type: " + callerType);
+ return false;
+ }
+ mNetdEventCallbackList[callerType] = callback;
return true;
}
- public synchronized boolean unregisterNetdEventCallback() {
- mNetdEventCallback = null;
+ public synchronized boolean removeNetdEventCallback(int callerType) {
+ if (!isValidCallerType(callerType)) {
+ Log.e(TAG, "Invalid caller type: " + callerType);
+ return false;
+ }
+ mNetdEventCallbackList[callerType] = null;
return true;
}
+ private static boolean isValidCallerType(int callerType) {
+ for (int i = 0; i < ALLOWED_CALLBACK_TYPES.length; i++) {
+ if (callerType == ALLOWED_CALLBACK_TYPES[i]) {
+ return true;
+ }
+ }
+ return false;
+ }
+
public NetdEventListenerService(Context context) {
this(context.getSystemService(ConnectivityManager.class));
}
@@ -169,8 +203,10 @@
long timestamp = System.currentTimeMillis();
getMetricsForNetwork(timestamp, netId).addDnsResult(eventType, returnCode, latencyMs);
- if (mNetdEventCallback != null) {
- mNetdEventCallback.onDnsEvent(hostname, ipAddresses, ipAddressesCount, timestamp, uid);
+ for (INetdEventCallback callback : mNetdEventCallbackList) {
+ if (callback != null) {
+ callback.onDnsEvent(hostname, ipAddresses, ipAddressesCount, timestamp, uid);
+ }
}
}
@@ -184,8 +220,14 @@
long timestamp = System.currentTimeMillis();
getMetricsForNetwork(timestamp, netId).addConnectResult(error, latencyMs, ipAddr);
- if (mNetdEventCallback != null) {
- mNetdEventCallback.onConnectEvent(ipAddr, port, timestamp, uid);
+ for (INetdEventCallback callback : mNetdEventCallbackList) {
+ if (callback != null) {
+ // TODO(rickywai): Remove this checking to collect ip in watchlist.
+ if (callback ==
+ mNetdEventCallbackList[INetdEventCallback.CALLBACK_CALLER_DEVICE_POLICY]) {
+ callback.onConnectEvent(ipAddr, port, timestamp, uid);
+ }
+ }
}
}
diff --git a/services/core/java/com/android/server/display/NightDisplayService.java b/services/core/java/com/android/server/display/ColorDisplayService.java
similarity index 97%
rename from services/core/java/com/android/server/display/NightDisplayService.java
rename to services/core/java/com/android/server/display/ColorDisplayService.java
index a7c3ff9..af8ecad 100644
--- a/services/core/java/com/android/server/display/NightDisplayService.java
+++ b/services/core/java/com/android/server/display/ColorDisplayService.java
@@ -42,7 +42,7 @@
import android.util.Slog;
import android.view.animation.AnimationUtils;
-import com.android.internal.app.NightDisplayController;
+import com.android.internal.app.ColorDisplayController;
import com.android.server.SystemService;
import com.android.server.twilight.TwilightListener;
import com.android.server.twilight.TwilightManager;
@@ -60,10 +60,10 @@
/**
* Tints the display at night.
*/
-public final class NightDisplayService extends SystemService
- implements NightDisplayController.Callback {
+public final class ColorDisplayService extends SystemService
+ implements ColorDisplayController.Callback {
- private static final String TAG = "NightDisplayService";
+ private static final String TAG = "ColorDisplayService";
/**
* The transition time, in milliseconds, for Night Display to turn on/off.
@@ -119,12 +119,12 @@
private ContentObserver mUserSetupObserver;
private boolean mBootCompleted;
- private NightDisplayController mController;
+ private ColorDisplayController mController;
private ValueAnimator mColorMatrixAnimator;
private Boolean mIsActivated;
private AutoMode mAutoMode;
- public NightDisplayService(Context context) {
+ public ColorDisplayService(Context context) {
super(context);
mHandler = new Handler(Looper.getMainLooper());
}
@@ -228,7 +228,7 @@
Slog.d(TAG, "setUp: currentUser=" + mCurrentUser);
// Create a new controller for the current user and start listening for changes.
- mController = new NightDisplayController(getContext(), mCurrentUser);
+ mController = new ColorDisplayController(getContext(), mCurrentUser);
mController.setListener(this);
setCoefficientMatrix(getContext());
@@ -293,9 +293,9 @@
mAutoMode = null;
}
- if (autoMode == NightDisplayController.AUTO_MODE_CUSTOM) {
+ if (autoMode == ColorDisplayController.AUTO_MODE_CUSTOM) {
mAutoMode = new CustomAutoMode();
- } else if (autoMode == NightDisplayController.AUTO_MODE_TWILIGHT) {
+ } else if (autoMode == ColorDisplayController.AUTO_MODE_TWILIGHT) {
mAutoMode = new TwilightAutoMode();
}
@@ -463,7 +463,7 @@
return ldt.isBefore(compareTime) ? ldt.plusDays(1) : ldt;
}
- private abstract class AutoMode implements NightDisplayController.Callback {
+ private abstract class AutoMode implements ColorDisplayController.Callback {
public abstract void onStart();
public abstract void onStop();
diff --git a/services/core/java/com/android/server/display/DisplayTransformManager.java b/services/core/java/com/android/server/display/DisplayTransformManager.java
index bef6898..338e331 100644
--- a/services/core/java/com/android/server/display/DisplayTransformManager.java
+++ b/services/core/java/com/android/server/display/DisplayTransformManager.java
@@ -29,7 +29,7 @@
import android.util.SparseArray;
import com.android.internal.annotations.GuardedBy;
-import com.android.internal.app.NightDisplayController;
+import com.android.internal.app.ColorDisplayController;
import java.util.Arrays;
/**
@@ -223,13 +223,13 @@
}
public boolean setColorMode(int colorMode) {
- if (colorMode == NightDisplayController.COLOR_MODE_NATURAL) {
+ if (colorMode == ColorDisplayController.COLOR_MODE_NATURAL) {
applySaturation(COLOR_SATURATION_NATURAL);
setNativeMode(false);
- } else if (colorMode == NightDisplayController.COLOR_MODE_BOOSTED) {
+ } else if (colorMode == ColorDisplayController.COLOR_MODE_BOOSTED) {
applySaturation(COLOR_SATURATION_BOOSTED);
setNativeMode(false);
- } else if (colorMode == NightDisplayController.COLOR_MODE_SATURATED) {
+ } else if (colorMode == ColorDisplayController.COLOR_MODE_SATURATED) {
applySaturation(COLOR_SATURATION_NATURAL);
setNativeMode(true);
}
diff --git a/services/core/java/com/android/server/job/JobPackageTracker.java b/services/core/java/com/android/server/job/JobPackageTracker.java
index 025ff0b..296743b 100644
--- a/services/core/java/com/android/server/job/JobPackageTracker.java
+++ b/services/core/java/com/android/server/job/JobPackageTracker.java
@@ -16,15 +16,19 @@
package com.android.server.job;
+import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock;
+import static com.android.server.job.JobSchedulerService.sSystemClock;
+import static com.android.server.job.JobSchedulerService.sUptimeMillisClock;
+
import android.app.job.JobInfo;
import android.app.job.JobParameters;
-import android.os.SystemClock;
import android.os.UserHandle;
import android.text.format.DateFormat;
import android.util.ArrayMap;
import android.util.SparseArray;
import android.util.SparseIntArray;
import android.util.TimeUtils;
+
import com.android.internal.util.RingBufferIndices;
import com.android.server.job.controllers.JobStatus;
@@ -57,7 +61,7 @@
public void addEvent(int cmd, int uid, String tag, int jobId, int stopReason) {
int index = mEventIndices.add();
mEventCmds[index] = cmd | ((stopReason<<EVENT_STOP_REASON_SHIFT) & EVENT_STOP_REASON_MASK);
- mEventTimes[index] = SystemClock.elapsedRealtime();
+ mEventTimes[index] = sElapsedRealtimeClock.millis();
mEventUids[index] = uid;
mEventTags[index] = tag;
mEventJobIds[index] = jobId;
@@ -125,9 +129,9 @@
}
public DataSet() {
- mStartUptimeTime = SystemClock.uptimeMillis();
- mStartElapsedTime = SystemClock.elapsedRealtime();
- mStartClockTime = System.currentTimeMillis();
+ mStartUptimeTime = sUptimeMillisClock.millis();
+ mStartElapsedTime = sElapsedRealtimeClock.millis();
+ mStartClockTime = sSystemClock.millis();
}
private PackageEntry getOrCreateEntry(int uid, String pkg) {
@@ -376,20 +380,20 @@
}
public void notePending(JobStatus job) {
- final long now = SystemClock.uptimeMillis();
+ final long now = sUptimeMillisClock.millis();
job.madePending = now;
rebatchIfNeeded(now);
mCurDataSet.incPending(job.getSourceUid(), job.getSourcePackageName(), now);
}
public void noteNonpending(JobStatus job) {
- final long now = SystemClock.uptimeMillis();
+ final long now = sUptimeMillisClock.millis();
mCurDataSet.decPending(job.getSourceUid(), job.getSourcePackageName(), now);
rebatchIfNeeded(now);
}
public void noteActive(JobStatus job) {
- final long now = SystemClock.uptimeMillis();
+ final long now = sUptimeMillisClock.millis();
job.madeActive = now;
rebatchIfNeeded(now);
if (job.lastEvaluatedPriority >= JobInfo.PRIORITY_TOP_APP) {
@@ -402,7 +406,7 @@
}
public void noteInactive(JobStatus job, int stopReason) {
- final long now = SystemClock.uptimeMillis();
+ final long now = sUptimeMillisClock.millis();
if (job.lastEvaluatedPriority >= JobInfo.PRIORITY_TOP_APP) {
mCurDataSet.decActiveTop(job.getSourceUid(), job.getSourcePackageName(), now,
stopReason);
@@ -431,7 +435,7 @@
if (cur == null && last == null) {
return 0;
}
- final long now = SystemClock.uptimeMillis();
+ final long now = sUptimeMillisClock.millis();
long time = 0;
if (cur != null) {
time += cur.getActiveTime(now) + cur.getPendingTime(now);
@@ -445,8 +449,8 @@
}
public void dump(PrintWriter pw, String prefix, int filterUid) {
- final long now = SystemClock.uptimeMillis();
- final long nowEllapsed = SystemClock.elapsedRealtime();
+ final long now = sUptimeMillisClock.millis();
+ final long nowEllapsed = sElapsedRealtimeClock.millis();
final DataSet total;
if (mLastDataSets[0] != null) {
total = new DataSet(mLastDataSets[0]);
@@ -470,7 +474,7 @@
return false;
}
pw.println(" Job history:");
- final long now = SystemClock.elapsedRealtime();
+ final long now = sElapsedRealtimeClock.millis();
for (int i=0; i<size; i++) {
final int index = mEventIndices.indexOf(i);
final int uid = mEventUids[index];
diff --git a/services/core/java/com/android/server/job/JobSchedulerService.java b/services/core/java/com/android/server/job/JobSchedulerService.java
index 78aa2f9..b549768 100644
--- a/services/core/java/com/android/server/job/JobSchedulerService.java
+++ b/services/core/java/com/android/server/job/JobSchedulerService.java
@@ -19,24 +19,15 @@
import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED;
import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER;
-import java.io.FileDescriptor;
-import java.io.PrintWriter;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.Iterator;
-import java.util.List;
-
import android.app.Activity;
import android.app.ActivityManager;
import android.app.AppGlobals;
import android.app.IUidObserver;
+import android.app.job.IJobScheduler;
import android.app.job.JobInfo;
import android.app.job.JobParameters;
import android.app.job.JobScheduler;
import android.app.job.JobService;
-import android.app.job.IJobScheduler;
import android.app.job.JobWorkItem;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
@@ -46,8 +37,8 @@
import android.content.IntentFilter;
import android.content.pm.IPackageManager;
import android.content.pm.PackageManager;
-import android.content.pm.ServiceInfo;
import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.ServiceInfo;
import android.database.ContentObserver;
import android.net.Uri;
import android.os.BatteryStats;
@@ -55,8 +46,8 @@
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
-import android.os.Process;
import android.os.PowerManager;
+import android.os.Process;
import android.os.RemoteException;
import android.os.ResultReceiver;
import android.os.ServiceManager;
@@ -71,6 +62,7 @@
import android.util.SparseIntArray;
import android.util.TimeUtils;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.app.IBatteryStats;
import com.android.internal.app.procstats.ProcessStats;
import com.android.internal.util.ArrayUtils;
@@ -93,6 +85,16 @@
import libcore.util.EmptyArray;
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.time.Clock;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Iterator;
+import java.util.List;
+
/**
* Responsible for taking jobs representing work to be performed by a client app, and determining
* based on the criteria specified when that job should be run against the client application's
@@ -117,6 +119,12 @@
/** The maximum number of jobs that we allow an unprivileged app to schedule */
private static final int MAX_JOBS_PER_APP = 100;
+ @VisibleForTesting
+ public static Clock sSystemClock = Clock.systemUTC();
+ @VisibleForTesting
+ public static Clock sUptimeMillisClock = SystemClock.uptimeMillisClock();
+ @VisibleForTesting
+ public static Clock sElapsedRealtimeClock = SystemClock.elapsedRealtimeClock();
/** Global local for all job scheduler state. */
final Object mLock = new Object();
@@ -960,7 +968,7 @@
if (Intent.ACTION_TIME_CHANGED.equals(intent.getAction())) {
// When we reach clock sanity, recalculate the temporal windows
// of all affected jobs.
- if (mJobs.clockNowValidToInflate(System.currentTimeMillis())) {
+ if (mJobs.clockNowValidToInflate(sSystemClock.millis())) {
Slog.i(TAG, "RTC now valid; recalculating persisted job windows");
// We've done our job now, so stop watching the time.
@@ -1068,7 +1076,7 @@
if (!jobStatus.isPreparedLocked()) {
Slog.wtf(TAG, "Not yet prepared when started tracking: " + jobStatus);
}
- jobStatus.enqueueTime = SystemClock.elapsedRealtime();
+ jobStatus.enqueueTime = sElapsedRealtimeClock.millis();
final boolean update = mJobs.add(jobStatus);
if (mReadyToRock) {
for (int i = 0; i < mControllers.size(); i++) {
@@ -1156,7 +1164,7 @@
* @see #maybeQueueReadyJobsForExecutionLocked
*/
private JobStatus getRescheduleJobForFailureLocked(JobStatus failureToReschedule) {
- final long elapsedNowMillis = SystemClock.elapsedRealtime();
+ final long elapsedNowMillis = sElapsedRealtimeClock.millis();
final JobInfo job = failureToReschedule.getJob();
final long initialBackoffMillis = job.getInitialBackoffMillis();
@@ -1200,7 +1208,7 @@
Math.min(delayMillis, JobInfo.MAX_BACKOFF_DELAY_MILLIS);
JobStatus newJob = new JobStatus(failureToReschedule, elapsedNowMillis + delayMillis,
JobStatus.NO_LATEST_RUNTIME, backoffAttempts,
- failureToReschedule.getLastSuccessfulRunTime(), System.currentTimeMillis());
+ failureToReschedule.getLastSuccessfulRunTime(), sSystemClock.millis());
for (int ic=0; ic<mControllers.size(); ic++) {
StateController controller = mControllers.get(ic);
controller.rescheduleForFailureLocked(newJob, failureToReschedule);
@@ -1220,7 +1228,7 @@
* recurring job.
*/
private JobStatus getRescheduleJobForPeriodic(JobStatus periodicToReschedule) {
- final long elapsedNow = SystemClock.elapsedRealtime();
+ final long elapsedNow = sElapsedRealtimeClock.millis();
// Compute how much of the period is remaining.
long runEarly = 0L;
@@ -1239,7 +1247,7 @@
}
return new JobStatus(periodicToReschedule, newEarliestRunTimeElapsed,
newLatestRuntimeElapsed, 0 /* backoffAttempt */,
- System.currentTimeMillis() /* lastSuccessfulRunTime */,
+ sSystemClock.millis() /* lastSuccessfulRunTime */,
periodicToReschedule.getLastFailedRunTime());
}
@@ -2367,8 +2375,8 @@
}
final int filterUidFinal = UserHandle.getAppId(filterUid);
- final long nowElapsed = SystemClock.elapsedRealtime();
- final long nowUptime = SystemClock.uptimeMillis();
+ final long nowElapsed = sElapsedRealtimeClock.millis();
+ final long nowUptime = sUptimeMillisClock.millis();
synchronized (mLock) {
mConstants.dump(pw);
pw.println();
diff --git a/services/core/java/com/android/server/job/JobServiceContext.java b/services/core/java/com/android/server/job/JobServiceContext.java
index a7e674b..ac7297e 100644
--- a/services/core/java/com/android/server/job/JobServiceContext.java
+++ b/services/core/java/com/android/server/job/JobServiceContext.java
@@ -16,11 +16,13 @@
package com.android.server.job;
+import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock;
+
import android.app.ActivityManager;
-import android.app.job.JobInfo;
-import android.app.job.JobParameters;
import android.app.job.IJobCallback;
import android.app.job.IJobService;
+import android.app.job.JobInfo;
+import android.app.job.JobParameters;
import android.app.job.JobWorkItem;
import android.content.ComponentName;
import android.content.Context;
@@ -34,7 +36,6 @@
import android.os.Message;
import android.os.PowerManager;
import android.os.RemoteException;
-import android.os.SystemClock;
import android.os.UserHandle;
import android.os.WorkSource;
import android.util.Slog;
@@ -202,7 +203,7 @@
mRunningCallback = new JobCallback();
final boolean isDeadlineExpired =
job.hasDeadlineConstraint() &&
- (job.getLatestRunTimeElapsed() < SystemClock.elapsedRealtime());
+ (job.getLatestRunTimeElapsed() < sElapsedRealtimeClock.millis());
Uri[] triggeredUris = null;
if (job.changedUris != null) {
triggeredUris = new Uri[job.changedUris.size()];
@@ -217,7 +218,7 @@
mParams = new JobParameters(mRunningCallback, job.getJobId(), ji.getExtras(),
ji.getTransientExtras(), ji.getClipData(), ji.getClipGrantFlags(),
isDeadlineExpired, triggeredUris, triggeredAuthorities, job.network);
- mExecutionStartTimeElapsed = SystemClock.elapsedRealtime();
+ mExecutionStartTimeElapsed = sElapsedRealtimeClock.millis();
// Once we'e begun executing a job, we by definition no longer care whether
// it was inflated from disk with not-yet-coherent delay/deadline bounds.
@@ -428,7 +429,7 @@
sb.append("Caller no longer running");
if (cb.mStoppedReason != null) {
sb.append(", last stopped ");
- TimeUtils.formatDuration(SystemClock.elapsedRealtime() - cb.mStoppedTime, sb);
+ TimeUtils.formatDuration(sElapsedRealtimeClock.millis() - cb.mStoppedTime, sb);
sb.append(" because: ");
sb.append(cb.mStoppedReason);
}
@@ -457,7 +458,7 @@
sb.append("Ignoring timeout of no longer active job");
if (jc.mStoppedReason != null) {
sb.append(", stopped ");
- TimeUtils.formatDuration(SystemClock.elapsedRealtime()
+ TimeUtils.formatDuration(sElapsedRealtimeClock.millis()
- jc.mStoppedTime, sb);
sb.append(" because: ");
sb.append(jc.mStoppedReason);
@@ -740,7 +741,7 @@
private void applyStoppedReasonLocked(String reason) {
if (reason != null && mStoppedReason == null) {
mStoppedReason = reason;
- mStoppedTime = SystemClock.elapsedRealtime();
+ mStoppedTime = sElapsedRealtimeClock.millis();
if (mRunningCallback != null) {
mRunningCallback.mStoppedReason = mStoppedReason;
mRunningCallback.mStoppedTime = mStoppedTime;
@@ -777,7 +778,7 @@
}
Message m = mCallbackHandler.obtainMessage(MSG_TIMEOUT, mRunningCallback);
mCallbackHandler.sendMessageDelayed(m, timeoutMillis);
- mTimeoutElapsed = SystemClock.elapsedRealtime() + timeoutMillis;
+ mTimeoutElapsed = sElapsedRealtimeClock.millis() + timeoutMillis;
}
diff --git a/services/core/java/com/android/server/job/JobStore.java b/services/core/java/com/android/server/job/JobStore.java
index aa9f77c..1af3b39 100644
--- a/services/core/java/com/android/server/job/JobStore.java
+++ b/services/core/java/com/android/server/job/JobStore.java
@@ -16,20 +16,23 @@
package com.android.server.job;
+import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock;
+import static com.android.server.job.JobSchedulerService.sSystemClock;
+
import android.app.ActivityManager;
import android.app.IActivityManager;
-import android.content.ComponentName;
import android.app.job.JobInfo;
+import android.content.ComponentName;
import android.content.Context;
+import android.net.NetworkRequest;
import android.os.Environment;
import android.os.Handler;
import android.os.PersistableBundle;
import android.os.Process;
-import android.os.SystemClock;
import android.os.UserHandle;
import android.text.format.DateUtils;
-import android.util.AtomicFile;
import android.util.ArraySet;
+import android.util.AtomicFile;
import android.util.Pair;
import android.util.Slog;
import android.util.SparseArray;
@@ -37,11 +40,16 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.BitUtils;
import com.android.internal.util.FastXmlSerializer;
import com.android.server.IoThread;
import com.android.server.job.JobSchedulerInternal.JobStorePersistStats;
import com.android.server.job.controllers.JobStatus;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
@@ -53,10 +61,6 @@
import java.util.List;
import java.util.Set;
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
-import org.xmlpull.v1.XmlSerializer;
-
/**
* Maintains the master list of jobs that the job scheduler is tracking. These jobs are compared by
* reference, so none of the functions in this class should make a copy.
@@ -141,7 +145,7 @@
// a *correct* timestamp, see a bunch of overdue jobs, and run them; then
// settle into normal operation.
mXmlTimestamp = mJobsFile.getLastModifiedTime();
- mRtcGood = (System.currentTimeMillis() > mXmlTimestamp);
+ mRtcGood = (sSystemClock.millis() > mXmlTimestamp);
readJobMapFromDisk(mJobSet, mRtcGood);
}
@@ -161,7 +165,7 @@
*/
public void getRtcCorrectedJobsLocked(final ArrayList<JobStatus> toAdd,
final ArrayList<JobStatus> toRemove) {
- final long elapsedNow = SystemClock.elapsedRealtime();
+ final long elapsedNow = sElapsedRealtimeClock.millis();
// Find the jobs that need to be fixed up, collecting them for post-iteration
// replacement with their new versions
@@ -323,7 +327,7 @@
private final Runnable mWriteRunnable = new Runnable() {
@Override
public void run() {
- final long startElapsed = SystemClock.elapsedRealtime();
+ final long startElapsed = sElapsedRealtimeClock.millis();
final List<JobStatus> storeCopy = new ArrayList<JobStatus>();
synchronized (mLock) {
// Clone the jobs so we can release the lock before writing.
@@ -338,7 +342,7 @@
}
writeJobsMapImpl(storeCopy);
if (DEBUG) {
- Slog.v(TAG, "Finished writing, took " + (SystemClock.elapsedRealtime()
+ Slog.v(TAG, "Finished writing, took " + (sElapsedRealtimeClock.millis()
- startElapsed) + "ms");
}
}
@@ -454,17 +458,12 @@
*/
private void writeConstraintsToXml(XmlSerializer out, JobStatus jobStatus) throws IOException {
out.startTag(null, XML_TAG_PARAMS_CONSTRAINTS);
- if (jobStatus.needsAnyConnectivity()) {
- out.attribute(null, "connectivity", Boolean.toString(true));
- }
- if (jobStatus.needsMeteredConnectivity()) {
- out.attribute(null, "metered", Boolean.toString(true));
- }
- if (jobStatus.needsUnmeteredConnectivity()) {
- out.attribute(null, "unmetered", Boolean.toString(true));
- }
- if (jobStatus.needsNonRoamingConnectivity()) {
- out.attribute(null, "not-roaming", Boolean.toString(true));
+ if (jobStatus.hasConnectivityConstraint()) {
+ final NetworkRequest network = jobStatus.getJob().getRequiredNetwork();
+ out.attribute(null, "net-capabilities", Long.toString(
+ BitUtils.packBits(network.networkCapabilities.getCapabilities())));
+ out.attribute(null, "net-transport-types", Long.toString(
+ BitUtils.packBits(network.networkCapabilities.getTransportTypes())));
}
if (jobStatus.hasIdleConstraint()) {
out.attribute(null, "idle", Boolean.toString(true));
@@ -497,8 +496,8 @@
Slog.i(TAG, "storing original UTC timestamps for " + jobStatus);
}
- final long nowRTC = System.currentTimeMillis();
- final long nowElapsed = SystemClock.elapsedRealtime();
+ final long nowRTC = sSystemClock.millis();
+ final long nowElapsed = sElapsedRealtimeClock.millis();
if (jobStatus.hasDeadlineConstraint()) {
// Wall clock deadline.
final long deadlineWallclock = (utcJobTimes == null)
@@ -538,7 +537,7 @@
*/
private static Pair<Long, Long> convertRtcBoundsToElapsed(Pair<Long, Long> rtcTimes,
long nowElapsed) {
- final long nowWallclock = System.currentTimeMillis();
+ final long nowWallclock = sSystemClock.millis();
final long earliest = (rtcTimes.first > JobStatus.NO_EARLIEST_RUNTIME)
? nowElapsed + Math.max(rtcTimes.first - nowWallclock, 0)
: JobStatus.NO_EARLIEST_RUNTIME;
@@ -581,7 +580,7 @@
synchronized (mLock) {
jobs = readJobMapImpl(fis, rtcGood);
if (jobs != null) {
- long now = SystemClock.elapsedRealtime();
+ long now = sElapsedRealtimeClock.millis();
IActivityManager am = ActivityManager.getService();
for (int i=0; i<jobs.size(); i++) {
JobStatus js = jobs.get(i);
@@ -754,7 +753,7 @@
return null;
}
- final long elapsedNow = SystemClock.elapsedRealtime();
+ final long elapsedNow = sElapsedRealtimeClock.millis();
Pair<Long, Long> elapsedRuntimes = convertRtcBoundsToElapsed(rtcRuntimes, elapsedNow);
if (XML_TAG_PERIODIC.equals(parser.getName())) {
@@ -862,22 +861,38 @@
}
private void buildConstraintsFromXml(JobInfo.Builder jobBuilder, XmlPullParser parser) {
- String val = parser.getAttributeValue(null, "connectivity");
- if (val != null) {
- jobBuilder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY);
+ String val;
+
+ final String netCapabilities = parser.getAttributeValue(null, "net-capabilities");
+ final String netTransportTypes = parser.getAttributeValue(null, "net-transport-types");
+ if (netCapabilities != null && netTransportTypes != null) {
+ final NetworkRequest request = new NetworkRequest.Builder().build();
+ // We're okay throwing NFE here; caught by caller
+ request.networkCapabilities.setCapabilities(
+ BitUtils.unpackBits(Long.parseLong(netCapabilities)));
+ request.networkCapabilities.setTransportTypes(
+ BitUtils.unpackBits(Long.parseLong(netTransportTypes)));
+ jobBuilder.setRequiredNetwork(request);
+ } else {
+ // Read legacy values
+ val = parser.getAttributeValue(null, "connectivity");
+ if (val != null) {
+ jobBuilder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY);
+ }
+ val = parser.getAttributeValue(null, "metered");
+ if (val != null) {
+ jobBuilder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_METERED);
+ }
+ val = parser.getAttributeValue(null, "unmetered");
+ if (val != null) {
+ jobBuilder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED);
+ }
+ val = parser.getAttributeValue(null, "not-roaming");
+ if (val != null) {
+ jobBuilder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_NOT_ROAMING);
+ }
}
- val = parser.getAttributeValue(null, "metered");
- if (val != null) {
- jobBuilder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_METERED);
- }
- val = parser.getAttributeValue(null, "unmetered");
- if (val != null) {
- jobBuilder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED);
- }
- val = parser.getAttributeValue(null, "not-roaming");
- if (val != null) {
- jobBuilder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_NOT_ROAMING);
- }
+
val = parser.getAttributeValue(null, "idle");
if (val != null) {
jobBuilder.setRequiresDeviceIdle(true);
@@ -937,8 +952,8 @@
private Pair<Long, Long> buildExecutionTimesFromXml(XmlPullParser parser)
throws NumberFormatException {
// Pull out execution time data.
- final long nowWallclock = System.currentTimeMillis();
- final long nowElapsed = SystemClock.elapsedRealtime();
+ final long nowWallclock = sSystemClock.millis();
+ final long nowElapsed = sElapsedRealtimeClock.millis();
long earliestRunTimeElapsed = JobStatus.NO_EARLIEST_RUNTIME;
long latestRunTimeElapsed = JobStatus.NO_LATEST_RUNTIME;
diff --git a/services/core/java/com/android/server/job/controllers/BatteryController.java b/services/core/java/com/android/server/job/controllers/BatteryController.java
index 9111969..76ff834 100644
--- a/services/core/java/com/android/server/job/controllers/BatteryController.java
+++ b/services/core/java/com/android/server/job/controllers/BatteryController.java
@@ -16,13 +16,14 @@
package com.android.server.job.controllers;
+import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock;
+
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.BatteryManager;
import android.os.BatteryManagerInternal;
-import android.os.SystemClock;
import android.os.UserHandle;
import android.util.ArraySet;
import android.util.Slog;
@@ -203,7 +204,7 @@
if (Intent.ACTION_BATTERY_LOW.equals(action)) {
if (DEBUG) {
Slog.d(TAG, "Battery life too low to do work. @ "
- + SystemClock.elapsedRealtime());
+ + sElapsedRealtimeClock.millis());
}
// If we get this action, the battery is discharging => it isn't plugged in so
// there's no work to cancel. We track this variable for the case where it is
@@ -213,14 +214,14 @@
} else if (Intent.ACTION_BATTERY_OKAY.equals(action)) {
if (DEBUG) {
Slog.d(TAG, "Battery life healthy enough to do work. @ "
- + SystemClock.elapsedRealtime());
+ + sElapsedRealtimeClock.millis());
}
mBatteryHealthy = true;
maybeReportNewChargingStateLocked();
} else if (BatteryManager.ACTION_CHARGING.equals(action)) {
if (DEBUG) {
Slog.d(TAG, "Received charging intent, fired @ "
- + SystemClock.elapsedRealtime());
+ + sElapsedRealtimeClock.millis());
}
mCharging = true;
maybeReportNewChargingStateLocked();
diff --git a/services/core/java/com/android/server/job/controllers/ConnectivityController.java b/services/core/java/com/android/server/job/controllers/ConnectivityController.java
index ddee345..da28769 100644
--- a/services/core/java/com/android/server/job/controllers/ConnectivityController.java
+++ b/services/core/java/com/android/server/job/controllers/ConnectivityController.java
@@ -54,7 +54,6 @@
private final ConnectivityManager mConnManager;
private final NetworkPolicyManager mNetPolicyManager;
private boolean mConnected;
- private boolean mValidated;
@GuardedBy("mLock")
private final ArraySet<JobStatus> mTrackedJobs = new ArraySet<>();
@@ -79,7 +78,7 @@
mConnManager = mContext.getSystemService(ConnectivityManager.class);
mNetPolicyManager = mContext.getSystemService(NetworkPolicyManager.class);
- mConnected = mValidated = false;
+ mConnected = false;
mConnManager.registerDefaultNetworkCallback(mNetworkCallback);
mNetPolicyManager.registerListener(mNetPolicyListener);
@@ -149,32 +148,21 @@
}
private boolean updateConstraintsSatisfied(JobStatus jobStatus) {
+ // TODO: consider matching against non-active networks
+
final int jobUid = jobStatus.getSourceUid();
final boolean ignoreBlocked = (jobStatus.getFlags() & JobInfo.FLAG_WILL_BE_FOREGROUND) != 0;
final Network network = mConnManager.getActiveNetworkForUid(jobUid, ignoreBlocked);
final NetworkInfo info = mConnManager.getNetworkInfoForUid(network, jobUid, ignoreBlocked);
-
- final NetworkCapabilities capabilities = (network != null)
- ? mConnManager.getNetworkCapabilities(network) : null;
+ final NetworkCapabilities capabilities = mConnManager.getNetworkCapabilities(network);
final boolean connected = (info != null) && info.isConnected();
- final boolean validated = (capabilities != null)
- && capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED);
+ final boolean satisfied = jobStatus.getJob().getRequiredNetwork().networkCapabilities
+ .satisfiedByNetworkCapabilities(capabilities);
final boolean sane = isSane(jobStatus, capabilities);
- final boolean connectionUsable = connected && validated && sane;
- final boolean metered = connected && (capabilities != null)
- && !capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED);
- final boolean unmetered = connected && (capabilities != null)
- && capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED);
- final boolean notRoaming = connected && (info != null)
- && !info.isRoaming();
-
- boolean changed = false;
- changed |= jobStatus.setConnectivityConstraintSatisfied(connectionUsable);
- changed |= jobStatus.setMeteredConstraintSatisfied(metered);
- changed |= jobStatus.setUnmeteredConstraintSatisfied(unmetered);
- changed |= jobStatus.setNotRoamingConstraintSatisfied(notRoaming);
+ final boolean changed = jobStatus
+ .setConnectivityConstraintSatisfied(connected && satisfied && sane);
// Pass along the evaluated network for job to use; prevents race
// conditions as default routes change over time, and opens the door to
@@ -185,17 +173,13 @@
// overall state of connectivity constraint satisfiability.
if (jobUid == Process.SYSTEM_UID) {
mConnected = connected;
- mValidated = validated;
}
if (DEBUG) {
Slog.i(TAG, "Connectivity " + (changed ? "CHANGED" : "unchanged")
- + " for " + jobStatus + ": usable=" + connectionUsable
- + " connected=" + connected
- + " validated=" + validated
- + " metered=" + metered
- + " unmetered=" + unmetered
- + " notRoaming=" + notRoaming);
+ + " for " + jobStatus + ": connected=" + connected
+ + " satisfied=" + satisfied
+ + " sane=" + sane);
}
return changed;
}
@@ -292,8 +276,6 @@
public void dumpControllerStateLocked(PrintWriter pw, int filterUid) {
pw.print("Connectivity: connected=");
pw.print(mConnected);
- pw.print(" validated=");
- pw.println(mValidated);
pw.print("Tracking ");
pw.print(mTrackedJobs.size());
pw.println(":");
@@ -304,10 +286,7 @@
js.printUniqueId(pw);
pw.print(" from ");
UserHandle.formatUid(pw, js.getSourceUid());
- pw.print(": C="); pw.print(js.needsAnyConnectivity());
- pw.print(": M="); pw.print(js.needsMeteredConnectivity());
- pw.print(": UM="); pw.print(js.needsUnmeteredConnectivity());
- pw.print(": NR="); pw.println(js.needsNonRoamingConnectivity());
+ pw.print(": "); pw.print(js.getJob().getRequiredNetwork());
}
}
}
diff --git a/services/core/java/com/android/server/job/controllers/IdleController.java b/services/core/java/com/android/server/job/controllers/IdleController.java
index 9eda046..7bde174 100644
--- a/services/core/java/com/android/server/job/controllers/IdleController.java
+++ b/services/core/java/com/android/server/job/controllers/IdleController.java
@@ -16,7 +16,7 @@
package com.android.server.job.controllers;
-import java.io.PrintWriter;
+import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock;
import android.app.AlarmManager;
import android.app.PendingIntent;
@@ -24,7 +24,6 @@
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
-import android.os.SystemClock;
import android.os.UserHandle;
import android.util.ArraySet;
import android.util.Slog;
@@ -33,6 +32,8 @@
import com.android.server.job.JobSchedulerService;
import com.android.server.job.StateChangedListener;
+import java.io.PrintWriter;
+
public final class IdleController extends StateController {
private static final String TAG = "IdleController";
@@ -169,7 +170,7 @@
// when the screen goes off or dreaming starts, we schedule the
// alarm that will tell us when we have decided the device is
// truly idle.
- final long nowElapsed = SystemClock.elapsedRealtime();
+ final long nowElapsed = sElapsedRealtimeClock.millis();
final long when = nowElapsed + mInactivityIdleThreshold;
if (DEBUG) {
Slog.v(TAG, "Scheduling idle : " + action + " now:" + nowElapsed + " when="
@@ -182,7 +183,7 @@
// idle time starts now. Do not set mIdle if screen is on.
if (!mIdle && !mScreenOn) {
if (DEBUG) {
- Slog.v(TAG, "Idle trigger fired @ " + SystemClock.elapsedRealtime());
+ Slog.v(TAG, "Idle trigger fired @ " + sElapsedRealtimeClock.millis());
}
mIdle = true;
reportNewIdleState(mIdle);
diff --git a/services/core/java/com/android/server/job/controllers/JobStatus.java b/services/core/java/com/android/server/job/controllers/JobStatus.java
index 46ed84e..a5906cb 100644
--- a/services/core/java/com/android/server/job/controllers/JobStatus.java
+++ b/services/core/java/com/android/server/job/controllers/JobStatus.java
@@ -16,6 +16,8 @@
package com.android.server.job.controllers;
+import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock;
+
import android.app.AppGlobals;
import android.app.IActivityManager;
import android.app.job.JobInfo;
@@ -25,7 +27,6 @@
import android.net.Network;
import android.net.Uri;
import android.os.RemoteException;
-import android.os.SystemClock;
import android.os.UserHandle;
import android.text.format.Time;
import android.util.ArraySet;
@@ -64,19 +65,12 @@
static final int CONSTRAINT_STORAGE_NOT_LOW = JobInfo.CONSTRAINT_FLAG_STORAGE_NOT_LOW;
static final int CONSTRAINT_TIMING_DELAY = 1<<31;
static final int CONSTRAINT_DEADLINE = 1<<30;
- static final int CONSTRAINT_UNMETERED = 1<<29;
static final int CONSTRAINT_CONNECTIVITY = 1<<28;
static final int CONSTRAINT_APP_NOT_IDLE = 1<<27;
static final int CONSTRAINT_CONTENT_TRIGGER = 1<<26;
static final int CONSTRAINT_DEVICE_NOT_DOZING = 1<<25;
- static final int CONSTRAINT_NOT_ROAMING = 1<<24;
- static final int CONSTRAINT_METERED = 1<<23;
static final int CONSTRAINT_BACKGROUND_NOT_RESTRICTED = 1<<22;
- static final int CONNECTIVITY_MASK =
- CONSTRAINT_UNMETERED | CONSTRAINT_CONNECTIVITY |
- CONSTRAINT_NOT_ROAMING | CONSTRAINT_METERED;
-
// Soft override: ignore constraints like time that don't affect API availability
public static final int OVERRIDE_SOFT = 1;
// Full override: ignore all constraints including API-affecting like connectivity
@@ -264,27 +258,9 @@
int requiredConstraints = job.getConstraintFlags();
- switch (job.getNetworkType()) {
- case JobInfo.NETWORK_TYPE_NONE:
- // No constraint.
- break;
- case JobInfo.NETWORK_TYPE_ANY:
- requiredConstraints |= CONSTRAINT_CONNECTIVITY;
- break;
- case JobInfo.NETWORK_TYPE_UNMETERED:
- requiredConstraints |= CONSTRAINT_UNMETERED;
- break;
- case JobInfo.NETWORK_TYPE_NOT_ROAMING:
- requiredConstraints |= CONSTRAINT_NOT_ROAMING;
- break;
- case JobInfo.NETWORK_TYPE_METERED:
- requiredConstraints |= CONSTRAINT_METERED;
- break;
- default:
- Slog.w(TAG, "Unrecognized networking constraint " + job.getNetworkType());
- break;
+ if (job.getRequiredNetwork() != null) {
+ requiredConstraints |= CONSTRAINT_CONNECTIVITY;
}
-
if (earliestRunTimeElapsedMillis != NO_EARLIEST_RUNTIME) {
requiredConstraints |= CONSTRAINT_TIMING_DELAY;
}
@@ -365,7 +341,7 @@
*/
public static JobStatus createFromJobInfo(JobInfo job, int callingUid, String sourcePackageName,
int sourceUserId, String tag) {
- final long elapsedNow = SystemClock.elapsedRealtime();
+ final long elapsedNow = sElapsedRealtimeClock.millis();
final long earliestRunTimeElapsedMillis, latestRunTimeElapsedMillis;
if (job.isPeriodic()) {
latestRunTimeElapsedMillis = elapsedNow + job.getIntervalMillis();
@@ -610,25 +586,9 @@
/** Does this job have any sort of networking constraint? */
public boolean hasConnectivityConstraint() {
- return (requiredConstraints&CONNECTIVITY_MASK) != 0;
- }
-
- public boolean needsAnyConnectivity() {
return (requiredConstraints&CONSTRAINT_CONNECTIVITY) != 0;
}
- public boolean needsUnmeteredConnectivity() {
- return (requiredConstraints&CONSTRAINT_UNMETERED) != 0;
- }
-
- public boolean needsMeteredConnectivity() {
- return (requiredConstraints&CONSTRAINT_METERED) != 0;
- }
-
- public boolean needsNonRoamingConnectivity() {
- return (requiredConstraints&CONSTRAINT_NOT_ROAMING) != 0;
- }
-
public boolean hasChargingConstraint() {
return (requiredConstraints&CONSTRAINT_CHARGING) != 0;
}
@@ -725,18 +685,6 @@
return setConstraintSatisfied(CONSTRAINT_CONNECTIVITY, state);
}
- boolean setUnmeteredConstraintSatisfied(boolean state) {
- return setConstraintSatisfied(CONSTRAINT_UNMETERED, state);
- }
-
- boolean setMeteredConstraintSatisfied(boolean state) {
- return setConstraintSatisfied(CONSTRAINT_METERED, state);
- }
-
- boolean setNotRoamingConstraintSatisfied(boolean state) {
- return setConstraintSatisfied(CONSTRAINT_NOT_ROAMING, state);
- }
-
boolean setAppNotIdleConstraintSatisfied(boolean state) {
return setConstraintSatisfied(CONSTRAINT_APP_NOT_IDLE, state);
}
@@ -817,12 +765,9 @@
&& notRestrictedInBg;
}
- static final int CONSTRAINTS_OF_INTEREST =
- CONSTRAINT_CHARGING | CONSTRAINT_BATTERY_NOT_LOW | CONSTRAINT_STORAGE_NOT_LOW |
- CONSTRAINT_TIMING_DELAY |
- CONSTRAINT_CONNECTIVITY | CONSTRAINT_UNMETERED |
- CONSTRAINT_NOT_ROAMING | CONSTRAINT_METERED |
- CONSTRAINT_IDLE | CONSTRAINT_CONTENT_TRIGGER;
+ static final int CONSTRAINTS_OF_INTEREST = CONSTRAINT_CHARGING | CONSTRAINT_BATTERY_NOT_LOW
+ | CONSTRAINT_STORAGE_NOT_LOW | CONSTRAINT_TIMING_DELAY | CONSTRAINT_CONNECTIVITY
+ | CONSTRAINT_IDLE | CONSTRAINT_CONTENT_TRIGGER;
// Soft override covers all non-"functional" constraints
static final int SOFT_OVERRIDE_CONSTRAINTS =
@@ -870,15 +815,14 @@
sb.append(getSourceUid());
if (earliestRunTimeElapsedMillis != NO_EARLIEST_RUNTIME
|| latestRunTimeElapsedMillis != NO_LATEST_RUNTIME) {
- long now = SystemClock.elapsedRealtime();
+ long now = sElapsedRealtimeClock.millis();
sb.append(" TIME=");
formatRunTime(sb, earliestRunTimeElapsedMillis, NO_EARLIEST_RUNTIME, now);
sb.append(":");
formatRunTime(sb, latestRunTimeElapsedMillis, NO_LATEST_RUNTIME, now);
}
- if (job.getNetworkType() != JobInfo.NETWORK_TYPE_NONE) {
- sb.append(" NET=");
- sb.append(job.getNetworkType());
+ if (job.getRequiredNetwork() != null) {
+ sb.append(" NET");
}
if (job.isRequireCharging()) {
sb.append(" CHARGING");
@@ -985,15 +929,6 @@
if ((constraints&CONSTRAINT_CONNECTIVITY) != 0) {
pw.print(" CONNECTIVITY");
}
- if ((constraints&CONSTRAINT_UNMETERED) != 0) {
- pw.print(" UNMETERED");
- }
- if ((constraints&CONSTRAINT_NOT_ROAMING) != 0) {
- pw.print(" NOT_ROAMING");
- }
- if ((constraints&CONSTRAINT_METERED) != 0) {
- pw.print(" METERED");
- }
if ((constraints&CONSTRAINT_APP_NOT_IDLE) != 0) {
pw.print(" APP_NOT_IDLE");
}
@@ -1084,11 +1019,13 @@
pw.print(prefix); pw.println(" Granted URI permissions:");
uriPerms.dump(pw, prefix + " ");
}
- if (job.getNetworkType() != JobInfo.NETWORK_TYPE_NONE) {
- pw.print(prefix); pw.print(" Network type: "); pw.println(job.getNetworkType());
+ if (job.getRequiredNetwork() != null) {
+ pw.print(prefix); pw.print(" Network type: ");
+ pw.println(job.getRequiredNetwork());
}
if (totalNetworkBytes != JobInfo.NETWORK_BYTES_UNKNOWN) {
- pw.print(prefix); pw.print(" Network bytes: "); pw.println(totalNetworkBytes);
+ pw.print(prefix); pw.print(" Network bytes: ");
+ pw.println(totalNetworkBytes);
}
if (job.getMinLatencyMillis() != 0) {
pw.print(prefix); pw.print(" Minimum latency: ");
diff --git a/services/core/java/com/android/server/job/controllers/StorageController.java b/services/core/java/com/android/server/job/controllers/StorageController.java
index c24e563..84782f5 100644
--- a/services/core/java/com/android/server/job/controllers/StorageController.java
+++ b/services/core/java/com/android/server/job/controllers/StorageController.java
@@ -16,11 +16,12 @@
package com.android.server.job.controllers;
+import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock;
+
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
-import android.os.SystemClock;
import android.os.UserHandle;
import android.util.ArraySet;
import android.util.Slog;
@@ -154,13 +155,13 @@
if (Intent.ACTION_DEVICE_STORAGE_LOW.equals(action)) {
if (DEBUG) {
Slog.d(TAG, "Available storage too low to do work. @ "
- + SystemClock.elapsedRealtime());
+ + sElapsedRealtimeClock.millis());
}
mStorageLow = true;
} else if (Intent.ACTION_DEVICE_STORAGE_OK.equals(action)) {
if (DEBUG) {
Slog.d(TAG, "Available stoage high enough to do work. @ "
- + SystemClock.elapsedRealtime());
+ + sElapsedRealtimeClock.millis());
}
mStorageLow = false;
maybeReportNewStorageState();
diff --git a/services/core/java/com/android/server/job/controllers/TimeController.java b/services/core/java/com/android/server/job/controllers/TimeController.java
index ee4c606..cb9e43a 100644
--- a/services/core/java/com/android/server/job/controllers/TimeController.java
+++ b/services/core/java/com/android/server/job/controllers/TimeController.java
@@ -16,10 +16,11 @@
package com.android.server.job.controllers;
+import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock;
+
import android.app.AlarmManager;
import android.app.AlarmManager.OnAlarmListener;
import android.content.Context;
-import android.os.SystemClock;
import android.os.UserHandle;
import android.os.WorkSource;
import android.util.Slog;
@@ -84,7 +85,7 @@
// pattern of having a job with a 0 deadline constraint ("run immediately").
// Unlike most controllers, once one of our constraints has been satisfied, it
// will never be unsatisfied (our time base can not go backwards).
- final long nowElapsedMillis = SystemClock.elapsedRealtime();
+ final long nowElapsedMillis = sElapsedRealtimeClock.millis();
if (job.hasDeadlineConstraint() && evaluateDeadlineConstraint(job, nowElapsedMillis)) {
return;
} else if (job.hasTimingDelayConstraint() && evaluateTimingDelayConstraint(job,
@@ -157,7 +158,7 @@
long nextExpiryTime = Long.MAX_VALUE;
int nextExpiryUid = 0;
String nextExpiryPackageName = null;
- final long nowElapsedMillis = SystemClock.elapsedRealtime();
+ final long nowElapsedMillis = sElapsedRealtimeClock.millis();
Iterator<JobStatus> it = mTrackedJobs.iterator();
while (it.hasNext()) {
@@ -201,7 +202,7 @@
*/
private void checkExpiredDelaysAndResetAlarm() {
synchronized (mLock) {
- final long nowElapsedMillis = SystemClock.elapsedRealtime();
+ final long nowElapsedMillis = sElapsedRealtimeClock.millis();
long nextDelayTime = Long.MAX_VALUE;
int nextDelayUid = 0;
String nextDelayPackageName = null;
@@ -283,7 +284,7 @@
}
private long maybeAdjustAlarmTime(long proposedAlarmTimeElapsedMillis) {
- final long earliestWakeupTimeElapsed = SystemClock.elapsedRealtime();
+ final long earliestWakeupTimeElapsed = sElapsedRealtimeClock.millis();
if (proposedAlarmTimeElapsedMillis < earliestWakeupTimeElapsed) {
return earliestWakeupTimeElapsed;
}
@@ -328,9 +329,9 @@
@Override
public void dumpControllerStateLocked(PrintWriter pw, int filterUid) {
- final long nowElapsed = SystemClock.elapsedRealtime();
+ final long nowElapsed = sElapsedRealtimeClock.millis();
pw.print("Alarms: now=");
- pw.print(SystemClock.elapsedRealtime());
+ pw.print(sElapsedRealtimeClock.millis());
pw.println();
pw.print("Next delay alarm in ");
TimeUtils.formatDuration(mNextDelayExpiredElapsedMillis, nowElapsed, pw);
diff --git a/services/core/java/com/android/server/net/watchlist/DigestUtils.java b/services/core/java/com/android/server/net/watchlist/DigestUtils.java
new file mode 100644
index 0000000..57becb0
--- /dev/null
+++ b/services/core/java/com/android/server/net/watchlist/DigestUtils.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.net.watchlist;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+
+/**
+ * Utils for calculating digests.
+ */
+public class DigestUtils {
+
+ private static final int FILE_READ_BUFFER_SIZE = 16 * 1024;
+
+ private DigestUtils() {}
+
+ /** @return SHA256 hash of the provided file */
+ public static byte[] getSha256Hash(File apkFile) throws IOException, NoSuchAlgorithmException {
+ try (InputStream stream = new FileInputStream(apkFile)) {
+ return getSha256Hash(stream);
+ }
+ }
+
+ /** @return SHA256 hash of data read from the provided input stream */
+ public static byte[] getSha256Hash(InputStream stream)
+ throws IOException, NoSuchAlgorithmException {
+ MessageDigest digester = MessageDigest.getInstance("SHA256");
+
+ int bytesRead;
+ byte[] buf = new byte[FILE_READ_BUFFER_SIZE];
+ while ((bytesRead = stream.read(buf)) >= 0) {
+ digester.update(buf, 0, bytesRead);
+ }
+ return digester.digest();
+ }
+}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/net/watchlist/HarmfulDigests.java b/services/core/java/com/android/server/net/watchlist/HarmfulDigests.java
new file mode 100644
index 0000000..27c22ce
--- /dev/null
+++ b/services/core/java/com/android/server/net/watchlist/HarmfulDigests.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.net.watchlist;
+
+import com.android.internal.util.HexDump;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Helper class to store all harmful digests in memory.
+ * TODO: Optimize memory usage using byte array with binary search.
+ */
+class HarmfulDigests {
+
+ private final Set<String> mDigestSet;
+
+ HarmfulDigests(List<byte[]> digests) {
+ final HashSet<String> tmpDigestSet = new HashSet<>();
+ final int size = digests.size();
+ for (int i = 0; i < size; i++) {
+ tmpDigestSet.add(HexDump.toHexString(digests.get(i)));
+ }
+ mDigestSet = Collections.unmodifiableSet(tmpDigestSet);
+ }
+
+ public boolean contains(byte[] digest) {
+ return mDigestSet.contains(HexDump.toHexString(digest));
+ }
+
+ public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ for (String digest : mDigestSet) {
+ pw.println(digest);
+ }
+ pw.println("");
+ }
+}
diff --git a/services/core/java/com/android/server/net/watchlist/NetworkWatchlistService.java b/services/core/java/com/android/server/net/watchlist/NetworkWatchlistService.java
new file mode 100644
index 0000000..171703a
--- /dev/null
+++ b/services/core/java/com/android/server/net/watchlist/NetworkWatchlistService.java
@@ -0,0 +1,267 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.net.watchlist;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.net.IIpConnectivityMetrics;
+import android.net.INetdEventCallback;
+import android.net.NetworkWatchlistManager;
+import android.net.metrics.IpConnectivityLog;
+import android.os.Binder;
+import android.os.Process;
+import android.os.SharedMemory;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.SystemProperties;
+import android.text.TextUtils;
+import android.util.Slog;
+
+import com.android.internal.R;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.DumpUtils;
+import com.android.internal.net.INetworkWatchlistManager;
+import com.android.server.ServiceThread;
+import com.android.server.SystemService;
+
+import java.io.FileDescriptor;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.List;
+
+/**
+ * Implementation of network watchlist service.
+ */
+public class NetworkWatchlistService extends INetworkWatchlistManager.Stub {
+
+ private static final String TAG = NetworkWatchlistService.class.getSimpleName();
+ static final boolean DEBUG = false;
+
+ private static final String PROPERTY_NETWORK_WATCHLIST_ENABLED =
+ "ro.network_watchlist_enabled";
+
+ private static final int MAX_NUM_OF_WATCHLIST_DIGESTS = 10000;
+
+ public static class Lifecycle extends SystemService {
+ private NetworkWatchlistService mService;
+
+ public Lifecycle(Context context) {
+ super(context);
+ }
+
+ @Override
+ public void onStart() {
+ if (!SystemProperties.getBoolean(PROPERTY_NETWORK_WATCHLIST_ENABLED, false)) {
+ // Watchlist service is disabled
+ return;
+ }
+ mService = new NetworkWatchlistService(getContext());
+ publishBinderService(Context.NETWORK_WATCHLIST_SERVICE, mService);
+ }
+
+ @Override
+ public void onBootPhase(int phase) {
+ if (!SystemProperties.getBoolean(PROPERTY_NETWORK_WATCHLIST_ENABLED, false)) {
+ // Watchlist service is disabled
+ return;
+ }
+ if (phase == SystemService.PHASE_ACTIVITY_MANAGER_READY) {
+ try {
+ mService.initIpConnectivityMetrics();
+ mService.startWatchlistLogging();
+ } catch (RemoteException e) {
+ // Should not happen
+ }
+ ReportWatchlistJobService.schedule(getContext());
+ }
+ }
+ }
+
+ private volatile boolean mIsLoggingEnabled = false;
+ private final Object mLoggingSwitchLock = new Object();
+
+ private final WatchlistSettings mSettings;
+ private final Context mContext;
+
+ // Separate thread to handle expensive watchlist logging work.
+ private final ServiceThread mHandlerThread;
+
+ @VisibleForTesting
+ IIpConnectivityMetrics mIpConnectivityMetrics;
+ @VisibleForTesting
+ WatchlistLoggingHandler mNetworkWatchlistHandler;
+
+ public NetworkWatchlistService(Context context) {
+ mContext = context;
+ mSettings = WatchlistSettings.getInstance();
+ mHandlerThread = new ServiceThread(TAG, Process.THREAD_PRIORITY_BACKGROUND,
+ /* allowIo */ false);
+ mHandlerThread.start();
+ mNetworkWatchlistHandler = new WatchlistLoggingHandler(mContext,
+ mHandlerThread.getLooper());
+ mNetworkWatchlistHandler.reportWatchlistIfNecessary();
+ }
+
+ // For testing only
+ @VisibleForTesting
+ NetworkWatchlistService(Context context, ServiceThread handlerThread,
+ WatchlistLoggingHandler handler, IIpConnectivityMetrics ipConnectivityMetrics) {
+ mContext = context;
+ mSettings = WatchlistSettings.getInstance();
+ mHandlerThread = handlerThread;
+ mNetworkWatchlistHandler = handler;
+ mIpConnectivityMetrics = ipConnectivityMetrics;
+ }
+
+ private void initIpConnectivityMetrics() {
+ mIpConnectivityMetrics = (IIpConnectivityMetrics) IIpConnectivityMetrics.Stub.asInterface(
+ ServiceManager.getService(IpConnectivityLog.SERVICE_NAME));
+ }
+
+ private final INetdEventCallback mNetdEventCallback = new INetdEventCallback.Stub() {
+ @Override
+ public void onDnsEvent(String hostname, String[] ipAddresses, int ipAddressesCount,
+ long timestamp, int uid) {
+ if (!mIsLoggingEnabled) {
+ return;
+ }
+ mNetworkWatchlistHandler.asyncNetworkEvent(hostname, ipAddresses, uid);
+ }
+
+ @Override
+ public void onConnectEvent(String ipAddr, int port, long timestamp, int uid) {
+ if (!mIsLoggingEnabled) {
+ return;
+ }
+ mNetworkWatchlistHandler.asyncNetworkEvent(null, new String[]{ipAddr}, uid);
+ }
+ };
+
+ @VisibleForTesting
+ protected boolean startWatchlistLoggingImpl() throws RemoteException {
+ if (DEBUG) {
+ Slog.i(TAG, "Starting watchlist logging.");
+ }
+ synchronized (mLoggingSwitchLock) {
+ if (mIsLoggingEnabled) {
+ Slog.w(TAG, "Watchlist logging is already running");
+ return true;
+ }
+ try {
+ if (mIpConnectivityMetrics.addNetdEventCallback(
+ INetdEventCallback.CALLBACK_CALLER_NETWORK_WATCHLIST, mNetdEventCallback)) {
+ mIsLoggingEnabled = true;
+ return true;
+ } else {
+ return false;
+ }
+ } catch (RemoteException re) {
+ // Should not happen
+ return false;
+ }
+ }
+ }
+
+ @Override
+ public boolean startWatchlistLogging() throws RemoteException {
+ enforceWatchlistLoggingPermission();
+ return startWatchlistLoggingImpl();
+ }
+
+ @VisibleForTesting
+ protected boolean stopWatchlistLoggingImpl() {
+ if (DEBUG) {
+ Slog.i(TAG, "Stopping watchlist logging");
+ }
+ synchronized (mLoggingSwitchLock) {
+ if (!mIsLoggingEnabled) {
+ Slog.w(TAG, "Watchlist logging is not running");
+ return true;
+ }
+ // stop the logging regardless of whether we fail to unregister listener
+ mIsLoggingEnabled = false;
+
+ try {
+ return mIpConnectivityMetrics.removeNetdEventCallback(
+ INetdEventCallback.CALLBACK_CALLER_NETWORK_WATCHLIST);
+ } catch (RemoteException re) {
+ // Should not happen
+ return false;
+ }
+ }
+ }
+
+ @Override
+ public boolean stopWatchlistLogging() throws RemoteException {
+ enforceWatchlistLoggingPermission();
+ return stopWatchlistLoggingImpl();
+ }
+
+ private void enforceWatchlistLoggingPermission() {
+ final int uid = Binder.getCallingUid();
+ if (uid != Process.SYSTEM_UID) {
+ throw new SecurityException(String.format("Uid %d has no permission to change watchlist"
+ + " setting.", uid));
+ }
+ }
+
+ /**
+ * Set a new network watchlist.
+ * This method should be called by ConfigUpdater only.
+ *
+ * @return True if network watchlist is updated.
+ */
+ public boolean setNetworkSecurityWatchlist(List<byte[]> domainsCrc32Digests,
+ List<byte[]> domainsSha256Digests,
+ List<byte[]> ipAddressesCrc32Digests,
+ List<byte[]> ipAddressesSha256Digests) {
+ Slog.i(TAG, "Setting network watchlist");
+ if (domainsCrc32Digests == null || domainsSha256Digests == null
+ || ipAddressesCrc32Digests == null || ipAddressesSha256Digests == null) {
+ Slog.e(TAG, "Parameters cannot be null");
+ return false;
+ }
+ if (domainsCrc32Digests.size() != domainsSha256Digests.size()
+ || ipAddressesCrc32Digests.size() != ipAddressesSha256Digests.size()) {
+ Slog.e(TAG, "Must need to have the same number of CRC32 and SHA256 digests");
+ return false;
+ }
+ if (domainsSha256Digests.size() + ipAddressesSha256Digests.size()
+ > MAX_NUM_OF_WATCHLIST_DIGESTS) {
+ Slog.e(TAG, "Total watchlist size cannot exceed " + MAX_NUM_OF_WATCHLIST_DIGESTS);
+ return false;
+ }
+ mSettings.writeSettingsToDisk(domainsCrc32Digests, domainsSha256Digests,
+ ipAddressesCrc32Digests, ipAddressesSha256Digests);
+ Slog.i(TAG, "Set network watchlist: Success");
+ return true;
+ }
+
+ @Override
+ public void reportWatchlistIfNecessary() {
+ // Allow any apps to trigger report event, as we won't run it if it's too early.
+ mNetworkWatchlistHandler.reportWatchlistIfNecessary();
+ }
+
+ @Override
+ protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
+ mSettings.dump(fd, pw, args);
+ }
+
+}
diff --git a/services/core/java/com/android/server/net/watchlist/ReportWatchlistJobService.java b/services/core/java/com/android/server/net/watchlist/ReportWatchlistJobService.java
new file mode 100644
index 0000000..dfeb1b2
--- /dev/null
+++ b/services/core/java/com/android/server/net/watchlist/ReportWatchlistJobService.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.net.watchlist;
+
+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.Context;
+import android.net.NetworkWatchlistManager;
+import android.util.Slog;
+
+import java.util.concurrent.TimeUnit;
+
+/**
+ * A job that periodically report watchlist records.
+ */
+public class ReportWatchlistJobService extends JobService {
+
+ private static final boolean DEBUG = NetworkWatchlistService.DEBUG;
+ private static final String TAG = "WatchlistJobService";
+
+ // Unique job id used in system service, other jobs should not use the same value.
+ public static final int REPORT_WATCHLIST_RECORDS_JOB_ID = 0xd7689;
+ public static final long REPORT_WATCHLIST_RECORDS_PERIOD_MILLIS =
+ TimeUnit.HOURS.toMillis(12);
+
+ @Override
+ public boolean onStartJob(final JobParameters jobParameters) {
+ if (jobParameters.getJobId() != REPORT_WATCHLIST_RECORDS_JOB_ID) {
+ return false;
+ }
+ if (DEBUG) Slog.d(TAG, "Start scheduled job.");
+ new NetworkWatchlistManager(this).reportWatchlistIfNecessary();
+ jobFinished(jobParameters, false);
+ return true;
+ }
+
+ @Override
+ public boolean onStopJob(JobParameters jobParameters) {
+ return true; // Reschedule when possible.
+ }
+
+ /**
+ * Schedule the {@link ReportWatchlistJobService} to run periodically.
+ */
+ public static void schedule(Context context) {
+ if (DEBUG) Slog.d(TAG, "Scheduling records aggregator task");
+ final JobScheduler scheduler =
+ (JobScheduler) context.getSystemService(Context.JOB_SCHEDULER_SERVICE);
+ scheduler.schedule(new JobInfo.Builder(REPORT_WATCHLIST_RECORDS_JOB_ID,
+ new ComponentName(context, ReportWatchlistJobService.class))
+ //.setOverrideDeadline(45 * 1000) // Schedule job soon, for testing.
+ .setPeriodic(REPORT_WATCHLIST_RECORDS_PERIOD_MILLIS)
+ .setRequiresDeviceIdle(true)
+ .setRequiresBatteryNotLow(true)
+ .setPersisted(false)
+ .build());
+ }
+
+}
diff --git a/services/core/java/com/android/server/net/watchlist/WatchlistLoggingHandler.java b/services/core/java/com/android/server/net/watchlist/WatchlistLoggingHandler.java
new file mode 100644
index 0000000..2247558
--- /dev/null
+++ b/services/core/java/com/android/server/net/watchlist/WatchlistLoggingHandler.java
@@ -0,0 +1,298 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.net.watchlist;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.os.Bundle;
+import android.os.DropBoxManager;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.text.TextUtils;
+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.security.NoSuchAlgorithmException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * A Handler class for network watchlist logging on a background thread.
+ */
+class WatchlistLoggingHandler extends Handler {
+
+ private static final String TAG = WatchlistLoggingHandler.class.getSimpleName();
+ private static final boolean DEBUG = NetworkWatchlistService.DEBUG;
+
+ @VisibleForTesting
+ static final int LOG_WATCHLIST_EVENT_MSG = 1;
+ @VisibleForTesting
+ static final int REPORT_RECORDS_IF_NECESSARY_MSG = 2;
+
+ private static final long ONE_DAY_MS = TimeUnit.DAYS.toMillis(1);
+ private static final String DROPBOX_TAG = "network_watchlist_report";
+
+ private final Context mContext;
+ private final ContentResolver mResolver;
+ private final PackageManager mPm;
+ private final WatchlistReportDbHelper mDbHelper;
+ private final WatchlistSettings mSettings;
+ // A cache for uid and apk digest mapping.
+ // As uid won't be reused until reboot, it's safe to assume uid is unique per signature and app.
+ // TODO: Use more efficient data structure.
+ private final HashMap<Integer, byte[]> mCachedUidDigestMap = new HashMap<>();
+
+ private interface WatchlistEventKeys {
+ String HOST = "host";
+ String IP_ADDRESSES = "ipAddresses";
+ String UID = "uid";
+ String TIMESTAMP = "timestamp";
+ }
+
+ WatchlistLoggingHandler(Context context, Looper looper) {
+ super(looper);
+ mContext = context;
+ mPm = mContext.getPackageManager();
+ mResolver = mContext.getContentResolver();
+ mDbHelper = WatchlistReportDbHelper.getInstance(context);
+ mSettings = WatchlistSettings.getInstance();
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case LOG_WATCHLIST_EVENT_MSG: {
+ final Bundle data = msg.getData();
+ handleNetworkEvent(
+ data.getString(WatchlistEventKeys.HOST),
+ data.getStringArray(WatchlistEventKeys.IP_ADDRESSES),
+ data.getInt(WatchlistEventKeys.UID),
+ data.getLong(WatchlistEventKeys.TIMESTAMP)
+ );
+ break;
+ }
+ case REPORT_RECORDS_IF_NECESSARY_MSG:
+ tryAggregateRecords();
+ break;
+ default: {
+ Slog.d(TAG, "WatchlistLoggingHandler received an unknown of message.");
+ break;
+ }
+ }
+ }
+
+ /**
+ * Report network watchlist records if we collected enough data.
+ */
+ public void reportWatchlistIfNecessary() {
+ final Message msg = obtainMessage(REPORT_RECORDS_IF_NECESSARY_MSG);
+ sendMessage(msg);
+ }
+
+ /**
+ * Insert network traffic event to watchlist async queue processor.
+ */
+ public void asyncNetworkEvent(String host, String[] ipAddresses, int uid) {
+ final Message msg = obtainMessage(LOG_WATCHLIST_EVENT_MSG);
+ final Bundle bundle = new Bundle();
+ bundle.putString(WatchlistEventKeys.HOST, host);
+ bundle.putStringArray(WatchlistEventKeys.IP_ADDRESSES, ipAddresses);
+ bundle.putInt(WatchlistEventKeys.UID, uid);
+ bundle.putLong(WatchlistEventKeys.TIMESTAMP, System.currentTimeMillis());
+ msg.setData(bundle);
+ sendMessage(msg);
+ }
+
+ private void handleNetworkEvent(String hostname, String[] ipAddresses,
+ int uid, long timestamp) {
+ if (DEBUG) {
+ Slog.i(TAG, "handleNetworkEvent with host: " + hostname + ", uid: " + uid);
+ }
+ final String cncDomain = searchAllSubDomainsInWatchlist(hostname);
+ if (cncDomain != null) {
+ insertRecord(getDigestFromUid(uid), cncDomain, timestamp);
+ } else {
+ final String cncIp = searchIpInWatchlist(ipAddresses);
+ if (cncIp != null) {
+ insertRecord(getDigestFromUid(uid), cncIp, timestamp);
+ }
+ }
+ }
+
+ private boolean insertRecord(byte[] digest, String cncHost, long timestamp) {
+ final boolean result = mDbHelper.insertNewRecord(digest, cncHost, timestamp);
+ tryAggregateRecords();
+ return result;
+ }
+
+ private boolean shouldReportNetworkWatchlist() {
+ final long lastReportTime = Settings.Global.getLong(mResolver,
+ Settings.Global.NETWORK_WATCHLIST_LAST_REPORT_TIME, 0L);
+ final long currentTimestamp = System.currentTimeMillis();
+ if (currentTimestamp < lastReportTime) {
+ Slog.i(TAG, "Last report time is larger than current time, reset report");
+ mDbHelper.cleanup();
+ return false;
+ }
+ return currentTimestamp >= lastReportTime + ONE_DAY_MS;
+ }
+
+ private void tryAggregateRecords() {
+ if (shouldReportNetworkWatchlist()) {
+ Slog.i(TAG, "Start aggregating watchlist records.");
+ final DropBoxManager dbox = mContext.getSystemService(DropBoxManager.class);
+ if (dbox != null && !dbox.isTagEnabled(DROPBOX_TAG)) {
+ final WatchlistReportDbHelper.AggregatedResult aggregatedResult =
+ mDbHelper.getAggregatedRecords();
+ final byte[] encodedResult = encodeAggregatedResult(aggregatedResult);
+ if (encodedResult != null) {
+ addEncodedReportToDropBox(encodedResult);
+ }
+ }
+ mDbHelper.cleanup();
+ Settings.Global.putLong(mResolver, Settings.Global.NETWORK_WATCHLIST_LAST_REPORT_TIME,
+ System.currentTimeMillis());
+ } else {
+ Slog.i(TAG, "No need to aggregate record yet.");
+ }
+ }
+
+ private byte[] encodeAggregatedResult(
+ WatchlistReportDbHelper.AggregatedResult aggregatedResult) {
+ // TODO: Encode results using differential privacy.
+ return null;
+ }
+
+ private void addEncodedReportToDropBox(byte[] encodedReport) {
+ final DropBoxManager dbox = mContext.getSystemService(DropBoxManager.class);
+ dbox.addData(DROPBOX_TAG, encodedReport, 0);
+ }
+
+ /**
+ * Get app digest from app uid.
+ */
+ private byte[] getDigestFromUid(int uid) {
+ final byte[] cachedDigest = mCachedUidDigestMap.get(uid);
+ if (cachedDigest != null) {
+ return cachedDigest;
+ }
+ final String[] packageNames = mPm.getPackagesForUid(uid);
+ final int userId = UserHandle.getUserId(uid);
+ if (!ArrayUtils.isEmpty(packageNames)) {
+ for (String packageName : packageNames) {
+ try {
+ final String apkPath = mPm.getPackageInfoAsUser(packageName,
+ PackageManager.MATCH_DIRECT_BOOT_AWARE
+ | PackageManager.MATCH_DIRECT_BOOT_UNAWARE, userId)
+ .applicationInfo.publicSourceDir;
+ if (TextUtils.isEmpty(apkPath)) {
+ Slog.w(TAG, "Cannot find apkPath for " + packageName);
+ continue;
+ }
+ final byte[] digest = DigestUtils.getSha256Hash(new File(apkPath));
+ mCachedUidDigestMap.put(uid, digest);
+ return digest;
+ } catch (NameNotFoundException | NoSuchAlgorithmException | IOException e) {
+ Slog.e(TAG, "Should not happen", e);
+ return null;
+ }
+ }
+ } else {
+ Slog.e(TAG, "Should not happen");
+ }
+ return null;
+ }
+
+ /**
+ * Search if any ip addresses are in watchlist.
+ *
+ * @param ipAddresses Ip address that you want to search in watchlist.
+ * @return Ip address that exists in watchlist, null if it does not match anything.
+ */
+ private String searchIpInWatchlist(String[] ipAddresses) {
+ for (String ipAddress : ipAddresses) {
+ if (isIpInWatchlist(ipAddress)) {
+ return ipAddress;
+ }
+ }
+ return null;
+ }
+
+ /** Search if the ip is in watchlist */
+ private boolean isIpInWatchlist(String ipAddr) {
+ if (ipAddr == null) {
+ return false;
+ }
+ return mSettings.containsIp(ipAddr);
+ }
+
+ /** Search if the host is in watchlist */
+ private boolean isHostInWatchlist(String host) {
+ if (host == null) {
+ return false;
+ }
+ return mSettings.containsDomain(host);
+ }
+
+ /**
+ * Search if any sub-domain in host is in watchlist.
+ *
+ * @param host Host that we want to search.
+ * @return Domain that exists in watchlist, null if it does not match anything.
+ */
+ private String searchAllSubDomainsInWatchlist(String host) {
+ if (host == null) {
+ return null;
+ }
+ final String[] subDomains = getAllSubDomains(host);
+ for (String subDomain : subDomains) {
+ if (isHostInWatchlist(subDomain)) {
+ return subDomain;
+ }
+ }
+ return null;
+ }
+
+ /** Get all sub-domains in a host */
+ @VisibleForTesting
+ static String[] getAllSubDomains(String host) {
+ if (host == null) {
+ return null;
+ }
+ final ArrayList<String> subDomainList = new ArrayList<>();
+ subDomainList.add(host);
+ int index = host.indexOf(".");
+ while (index != -1) {
+ host = host.substring(index + 1);
+ if (!TextUtils.isEmpty(host)) {
+ subDomainList.add(host);
+ }
+ index = host.indexOf(".");
+ }
+ return subDomainList.toArray(new String[0]);
+ }
+}
diff --git a/services/core/java/com/android/server/net/watchlist/WatchlistReportDbHelper.java b/services/core/java/com/android/server/net/watchlist/WatchlistReportDbHelper.java
new file mode 100644
index 0000000..f48463f
--- /dev/null
+++ b/services/core/java/com/android/server/net/watchlist/WatchlistReportDbHelper.java
@@ -0,0 +1,203 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.net.watchlist;
+
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteOpenHelper;
+import android.util.Pair;
+
+import com.android.internal.util.HexDump;
+
+import java.util.ArrayList;
+import java.util.GregorianCalendar;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Helper class to process watchlist read / save watchlist reports.
+ */
+class WatchlistReportDbHelper extends SQLiteOpenHelper {
+
+ private static final String TAG = "WatchlistReportDbHelper";
+
+ private static final String NAME = "watchlist_report.db";
+ private static final int VERSION = 2;
+
+ private static final int IDLE_CONNECTION_TIMEOUT_MS = 30000;
+
+ private static class WhiteListReportContract {
+ private static final String TABLE = "records";
+ private static final String APP_DIGEST = "app_digest";
+ private static final String CNC_DOMAIN = "cnc_domain";
+ private static final String TIMESTAMP = "timestamp";
+ }
+
+ private static final String CREATE_TABLE_MODEL = "CREATE TABLE "
+ + WhiteListReportContract.TABLE + "("
+ + WhiteListReportContract.APP_DIGEST + " BLOB,"
+ + WhiteListReportContract.CNC_DOMAIN + " TEXT,"
+ + WhiteListReportContract.TIMESTAMP + " INTEGER DEFAULT 0" + " )";
+
+ private static final int INDEX_DIGEST = 0;
+ private static final int INDEX_CNC_DOMAIN = 1;
+ private static final int INDEX_TIMESTAMP = 2;
+
+ private static final String[] DIGEST_DOMAIN_PROJECTION =
+ new String[] {
+ WhiteListReportContract.APP_DIGEST,
+ WhiteListReportContract.CNC_DOMAIN
+ };
+
+ private static WatchlistReportDbHelper sInstance;
+
+ /**
+ * Aggregated watchlist records.
+ */
+ public static class AggregatedResult {
+ // A list of digests that visited c&c domain or ip before.
+ Set<String> appDigestList;
+
+ // The c&c domain or ip visited before.
+ String cncDomainVisited;
+
+ // A list of app digests and c&c domain visited.
+ HashMap<String, String> appDigestCNCList;
+ }
+
+ private WatchlistReportDbHelper(Context context) {
+ super(context, WatchlistSettings.getSystemWatchlistFile(NAME).getAbsolutePath(),
+ null, VERSION);
+ // Memory optimization - close idle connections after 30s of inactivity
+ setIdleConnectionTimeout(IDLE_CONNECTION_TIMEOUT_MS);
+ }
+
+ public static synchronized WatchlistReportDbHelper getInstance(Context context) {
+ if (sInstance != null) {
+ return sInstance;
+ }
+ sInstance = new WatchlistReportDbHelper(context);
+ return sInstance;
+ }
+
+ @Override
+ public void onCreate(SQLiteDatabase db) {
+ db.execSQL(CREATE_TABLE_MODEL);
+ }
+
+ @Override
+ public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
+ // TODO: For now, drop older tables and recreate new ones.
+ db.execSQL("DROP TABLE IF EXISTS " + WhiteListReportContract.TABLE);
+ onCreate(db);
+ }
+
+ /**
+ * Insert new watchlist record.
+ *
+ * @param appDigest The digest of an app.
+ * @param cncDomain C&C domain that app visited.
+ * @return True if success.
+ */
+ public boolean insertNewRecord(byte[] appDigest, String cncDomain,
+ long timestamp) {
+ final SQLiteDatabase db = getWritableDatabase();
+ final ContentValues values = new ContentValues();
+ values.put(WhiteListReportContract.APP_DIGEST, appDigest);
+ values.put(WhiteListReportContract.CNC_DOMAIN, cncDomain);
+ values.put(WhiteListReportContract.TIMESTAMP, timestamp);
+ return db.insert(WhiteListReportContract.TABLE, null, values) != -1;
+ }
+
+ /**
+ * Aggregate the records in database, and return a rappor encoded result.
+ */
+ public AggregatedResult getAggregatedRecords() {
+ final long twoDaysBefore = getTwoDaysBeforeTimestamp();
+ final long yesterday = getYesterdayTimestamp();
+ final String selectStatement = WhiteListReportContract.TIMESTAMP + " >= ? AND " +
+ WhiteListReportContract.TIMESTAMP + " <= ?";
+
+ final SQLiteDatabase db = getReadableDatabase();
+ Cursor c = null;
+ try {
+ c = db.query(true /* distinct */,
+ WhiteListReportContract.TABLE, DIGEST_DOMAIN_PROJECTION, selectStatement,
+ new String[]{"" + twoDaysBefore, "" + yesterday}, null, null,
+ null, null);
+ if (c == null || c.getCount() == 0) {
+ return null;
+ }
+ final AggregatedResult result = new AggregatedResult();
+ result.cncDomainVisited = null;
+ // After aggregation, each digest maximum will have only 1 record.
+ result.appDigestList = new HashSet<>();
+ result.appDigestCNCList = new HashMap<>();
+ while (c.moveToNext()) {
+ // We use hex string here as byte[] cannot be a key in HashMap.
+ String digestHexStr = HexDump.toHexString(c.getBlob(INDEX_DIGEST));
+ String cncDomain = c.getString(INDEX_CNC_DOMAIN);
+
+ result.appDigestList.add(digestHexStr);
+ if (result.cncDomainVisited != null) {
+ result.cncDomainVisited = cncDomain;
+ }
+ result.appDigestCNCList.put(digestHexStr, cncDomain);
+ }
+ return result;
+ } finally {
+ if (c != null) {
+ c.close();
+ }
+ }
+ }
+
+ /**
+ * Remove all the records before yesterday.
+ *
+ * @return True if success.
+ */
+ public boolean cleanup() {
+ final SQLiteDatabase db = getWritableDatabase();
+ final long twoDaysBefore = getTwoDaysBeforeTimestamp();
+ final String clause = WhiteListReportContract.TIMESTAMP + "< " + twoDaysBefore;
+ return db.delete(WhiteListReportContract.TABLE, clause, null) != 0;
+ }
+
+ static long getTwoDaysBeforeTimestamp() {
+ return getMidnightTimestamp(2);
+ }
+
+ static long getYesterdayTimestamp() {
+ return getMidnightTimestamp(1);
+ }
+
+ static long getMidnightTimestamp(int daysBefore) {
+ java.util.Calendar date = new GregorianCalendar();
+ // reset hour, minutes, seconds and millis
+ date.set(java.util.Calendar.HOUR_OF_DAY, 0);
+ date.set(java.util.Calendar.MINUTE, 0);
+ date.set(java.util.Calendar.SECOND, 0);
+ date.set(java.util.Calendar.MILLISECOND, 0);
+ date.add(java.util.Calendar.DAY_OF_MONTH, -daysBefore);
+ return date.getTimeInMillis();
+ }
+}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/net/watchlist/WatchlistSettings.java b/services/core/java/com/android/server/net/watchlist/WatchlistSettings.java
new file mode 100644
index 0000000..c50f0d5
--- /dev/null
+++ b/services/core/java/com/android/server/net/watchlist/WatchlistSettings.java
@@ -0,0 +1,284 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.net.watchlist;
+
+import android.os.Environment;
+import android.util.AtomicFile;
+import android.util.Log;
+import android.util.Xml;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.FastXmlSerializer;
+import com.android.internal.util.HexDump;
+import com.android.internal.util.XmlUtils;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.File;
+import java.io.FileDescriptor;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.nio.charset.StandardCharsets;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.zip.CRC32;
+
+/**
+ * A util class to do watchlist settings operations, like setting watchlist, query if a domain
+ * exists in watchlist.
+ */
+class WatchlistSettings {
+ private static final String TAG = "WatchlistSettings";
+
+ // Settings xml will be stored in /data/system/network_watchlist/watchlist_settings.xml
+ static final String SYSTEM_WATCHLIST_DIR = "network_watchlist";
+
+ private static final String WATCHLIST_XML_FILE = "watchlist_settings.xml";
+
+ private static class XmlTags {
+ private static final String WATCHLIST_SETTINGS = "watchlist-settings";
+ private static final String SHA256_DOMAIN = "sha256-domain";
+ private static final String CRC32_DOMAIN = "crc32-domain";
+ private static final String SHA256_IP = "sha256-ip";
+ private static final String CRC32_IP = "crc32-ip";
+ private static final String HASH = "hash";
+ }
+
+ private static WatchlistSettings sInstance = new WatchlistSettings();
+ private final AtomicFile mXmlFile;
+ private final Object mLock = new Object();
+ private HarmfulDigests mCrc32DomainDigests = new HarmfulDigests(new ArrayList<>());
+ private HarmfulDigests mSha256DomainDigests = new HarmfulDigests(new ArrayList<>());
+ private HarmfulDigests mCrc32IpDigests = new HarmfulDigests(new ArrayList<>());
+ private HarmfulDigests mSha256IpDigests = new HarmfulDigests(new ArrayList<>());
+
+ public static synchronized WatchlistSettings getInstance() {
+ return sInstance;
+ }
+
+ private WatchlistSettings() {
+ this(getSystemWatchlistFile(WATCHLIST_XML_FILE));
+ }
+
+ @VisibleForTesting
+ protected WatchlistSettings(File xmlFile) {
+ mXmlFile = new AtomicFile(xmlFile);
+ readSettingsLocked();
+ }
+
+ static File getSystemWatchlistFile(String filename) {
+ final File dataSystemDir = Environment.getDataSystemDirectory();
+ final File systemWatchlistDir = new File(dataSystemDir, SYSTEM_WATCHLIST_DIR);
+ systemWatchlistDir.mkdirs();
+ return new File(systemWatchlistDir, filename);
+ }
+
+ private void readSettingsLocked() {
+ synchronized (mLock) {
+ FileInputStream stream;
+ try {
+ stream = mXmlFile.openRead();
+ } catch (FileNotFoundException e) {
+ Log.i(TAG, "No watchlist settings: " + mXmlFile.getBaseFile().getAbsolutePath());
+ return;
+ }
+
+ final List<byte[]> crc32DomainList = new ArrayList<>();
+ final List<byte[]> sha256DomainList = new ArrayList<>();
+ final List<byte[]> crc32IpList = new ArrayList<>();
+ final List<byte[]> sha256IpList = new ArrayList<>();
+
+ try {
+ XmlPullParser parser = Xml.newPullParser();
+ parser.setInput(stream, StandardCharsets.UTF_8.name());
+ parser.nextTag();
+ parser.require(XmlPullParser.START_TAG, null, XmlTags.WATCHLIST_SETTINGS);
+ while (parser.nextTag() == XmlPullParser.START_TAG) {
+ String tagName = parser.getName();
+ switch (tagName) {
+ case XmlTags.CRC32_DOMAIN:
+ parseHash(parser, tagName, crc32DomainList);
+ break;
+ case XmlTags.CRC32_IP:
+ parseHash(parser, tagName, crc32IpList);
+ break;
+ case XmlTags.SHA256_DOMAIN:
+ parseHash(parser, tagName, sha256DomainList);
+ break;
+ case XmlTags.SHA256_IP:
+ parseHash(parser, tagName, sha256IpList);
+ break;
+ default:
+ Log.w(TAG, "Unknown element: " + parser.getName());
+ XmlUtils.skipCurrentTag(parser);
+ }
+ }
+ parser.require(XmlPullParser.END_TAG, null, XmlTags.WATCHLIST_SETTINGS);
+ writeSettingsToMemory(crc32DomainList, sha256DomainList, crc32IpList, sha256IpList);
+ } catch (IllegalStateException | NullPointerException | NumberFormatException |
+ XmlPullParserException | IOException | IndexOutOfBoundsException e) {
+ Log.w(TAG, "Failed parsing " + e);
+ } finally {
+ try {
+ stream.close();
+ } catch (IOException e) {
+ }
+ }
+ }
+ }
+
+ private void parseHash(XmlPullParser parser, String tagName, List<byte[]> hashSet)
+ throws IOException, XmlPullParserException {
+ parser.require(XmlPullParser.START_TAG, null, tagName);
+ while (parser.nextTag() == XmlPullParser.START_TAG) {
+ parser.require(XmlPullParser.START_TAG, null, XmlTags.HASH);
+ byte[] hash = HexDump.hexStringToByteArray(parser.nextText());
+ parser.require(XmlPullParser.END_TAG, null, XmlTags.HASH);
+ hashSet.add(hash);
+ }
+ parser.require(XmlPullParser.END_TAG, null, tagName);
+ }
+
+ /**
+ * Write network watchlist settings to disk.
+ * Adb should not use it, should use writeSettingsToMemory directly instead.
+ */
+ public void writeSettingsToDisk(List<byte[]> newCrc32DomainList,
+ List<byte[]> newSha256DomainList,
+ List<byte[]> newCrc32IpList,
+ List<byte[]> newSha256IpList) {
+ synchronized (mLock) {
+ FileOutputStream stream;
+ try {
+ stream = mXmlFile.startWrite();
+ } catch (IOException e) {
+ Log.w(TAG, "Failed to write display settings: " + e);
+ return;
+ }
+
+ try {
+ XmlSerializer out = new FastXmlSerializer();
+ out.setOutput(stream, StandardCharsets.UTF_8.name());
+ out.startDocument(null, true);
+ out.startTag(null, XmlTags.WATCHLIST_SETTINGS);
+
+ writeHashSetToXml(out, XmlTags.SHA256_DOMAIN, newSha256DomainList);
+ writeHashSetToXml(out, XmlTags.SHA256_IP, newSha256IpList);
+ writeHashSetToXml(out, XmlTags.CRC32_DOMAIN, newCrc32DomainList);
+ writeHashSetToXml(out, XmlTags.CRC32_IP, newCrc32IpList);
+
+ out.endTag(null, XmlTags.WATCHLIST_SETTINGS);
+ out.endDocument();
+ mXmlFile.finishWrite(stream);
+ writeSettingsToMemory(newCrc32DomainList, newSha256DomainList, newCrc32IpList,
+ newSha256IpList);
+ } catch (IOException e) {
+ Log.w(TAG, "Failed to write display settings, restoring backup.", e);
+ mXmlFile.failWrite(stream);
+ }
+ }
+ }
+
+ /**
+ * Write network watchlist settings to memory.
+ */
+ public void writeSettingsToMemory(List<byte[]> newCrc32DomainList,
+ List<byte[]> newSha256DomainList,
+ List<byte[]> newCrc32IpList,
+ List<byte[]> newSha256IpList) {
+ synchronized (mLock) {
+ mCrc32DomainDigests = new HarmfulDigests(newCrc32DomainList);
+ mCrc32IpDigests = new HarmfulDigests(newCrc32IpList);
+ mSha256DomainDigests = new HarmfulDigests(newSha256DomainList);
+ mSha256IpDigests = new HarmfulDigests(newSha256IpList);
+ }
+ }
+
+ private static void writeHashSetToXml(XmlSerializer out, String tagName, List<byte[]> hashSet)
+ throws IOException {
+ out.startTag(null, tagName);
+ for (byte[] hash : hashSet) {
+ out.startTag(null, XmlTags.HASH);
+ out.text(HexDump.toHexString(hash));
+ out.endTag(null, XmlTags.HASH);
+ }
+ out.endTag(null, tagName);
+ }
+
+ public boolean containsDomain(String domain) {
+ // First it does a quick CRC32 check.
+ final byte[] crc32 = getCrc32(domain);
+ if (!mCrc32DomainDigests.contains(crc32)) {
+ return false;
+ }
+ // Now we do a slow SHA256 check.
+ final byte[] sha256 = getSha256(domain);
+ return mSha256DomainDigests.contains(sha256);
+ }
+
+ public boolean containsIp(String ip) {
+ // First it does a quick CRC32 check.
+ final byte[] crc32 = getCrc32(ip);
+ if (!mCrc32IpDigests.contains(crc32)) {
+ return false;
+ }
+ // Now we do a slow SHA256 check.
+ final byte[] sha256 = getSha256(ip);
+ return mSha256IpDigests.contains(sha256);
+ }
+
+
+ /** Get CRC32 of a string */
+ private byte[] getCrc32(String str) {
+ final CRC32 crc = new CRC32();
+ crc.update(str.getBytes());
+ final long tmp = crc.getValue();
+ return new byte[]{(byte)(tmp >> 24 & 255), (byte)(tmp >> 16 & 255),
+ (byte)(tmp >> 8 & 255), (byte)(tmp & 255)};
+ }
+
+ /** Get SHA256 of a string */
+ private byte[] getSha256(String str) {
+ MessageDigest messageDigest;
+ try {
+ messageDigest = MessageDigest.getInstance("SHA256");
+ } catch (NoSuchAlgorithmException e) {
+ /* can't happen */
+ return null;
+ }
+ messageDigest.update(str.getBytes());
+ return messageDigest.digest();
+ }
+
+ public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ pw.println("Domain CRC32 digest list:");
+ mCrc32DomainDigests.dump(fd, pw, args);
+ pw.println("Domain SHA256 digest list:");
+ mSha256DomainDigests.dump(fd, pw, args);
+ pw.println("Ip CRC32 digest list:");
+ mCrc32IpDigests.dump(fd, pw, args);
+ pw.println("Ip SHA256 digest list:");
+ mSha256IpDigests.dump(fd, pw, args);
+ }
+}
diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java
index f61cec9..1e9fab5 100644
--- a/services/core/java/com/android/server/notification/ZenModeHelper.java
+++ b/services/core/java/com/android/server/notification/ZenModeHelper.java
@@ -835,7 +835,8 @@
// alarm restrictions
final boolean muteMediaAndSystemSounds = zen && !mConfig.allowMediaSystemOther;
// total silence restrictions
- final boolean muteEverything = mZenMode == Global.ZEN_MODE_NO_INTERRUPTIONS;
+ final boolean muteEverything = mZenMode == Global.ZEN_MODE_NO_INTERRUPTIONS
+ || areAllBehaviorSoundsMuted();
for (int usage : AudioAttributes.SDK_USAGES) {
final int suppressionBehavior = AudioAttributes.SUPPRESSIBLE_USAGES.get(usage);
@@ -855,9 +856,11 @@
}
}
+
@VisibleForTesting
protected void applyRestrictions(boolean mute, int usage) {
final String[] exceptionPackages = null; // none (for now)
+
mAppOps.setRestriction(AppOpsManager.OP_VIBRATE, usage,
mute ? AppOpsManager.MODE_IGNORED : AppOpsManager.MODE_ALLOWED,
exceptionPackages);
@@ -866,6 +869,12 @@
exceptionPackages);
}
+ private boolean areAllBehaviorSoundsMuted() {
+ return !mConfig.allowAlarms && !mConfig.allowMediaSystemOther && !mConfig.allowReminders
+ && !mConfig.allowCalls && !mConfig.allowMessages && !mConfig.allowEvents
+ && !mConfig.allowRepeatCallers;
+ }
+
private void applyZenToRingerMode() {
if (mAudioManager == null) return;
// force the ringer mode into compliance
diff --git a/services/core/java/com/android/server/pm/PackageDexOptimizer.java b/services/core/java/com/android/server/pm/PackageDexOptimizer.java
index 50ac409..29f48ee 100644
--- a/services/core/java/com/android/server/pm/PackageDexOptimizer.java
+++ b/services/core/java/com/android/server/pm/PackageDexOptimizer.java
@@ -110,9 +110,9 @@
return false;
}
- // We do not dexopt a priv-app package when pm.dexopt.priv-apps is false.
+ // We do not dexopt a priv-app package when pm.dexopt.priv-apps-oob is true.
if (pkg.isPrivileged()) {
- return SystemProperties.getBoolean("pm.dexopt.priv-apps", true);
+ return !SystemProperties.getBoolean("pm.dexopt.priv-apps-oob", false);
}
return true;
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 6a0ea4ee..83cffe5 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -551,6 +551,8 @@
private static final String VENDOR_OVERLAY_DIR = "/vendor/overlay";
+ private static final String PROPERTY_NAME_PM_DEXOPT_PRIV_APPS_OOB = "pm.dexopt.priv-apps-oob";
+
/** Canonical intent used to identify what counts as a "web browser" app */
private static final Intent sBrowserIntent;
static {
@@ -9598,7 +9600,7 @@
if (Build.IS_DEBUGGABLE &&
pkg.isPrivileged() &&
- !SystemProperties.getBoolean("pm.dexopt.priv-apps", true)) {
+ SystemProperties.getBoolean(PROPERTY_NAME_PM_DEXOPT_PRIV_APPS_OOB, false)) {
PackageManagerServiceUtils.logPackageHasUncompressedCode(pkg);
}
@@ -19900,6 +19902,23 @@
.getUriFor(Secure.INSTANT_APPS_ENABLED), false, co, UserHandle.USER_SYSTEM);
co.onChange(true);
+ // This observer provides an one directional mapping from Global.PRIV_APP_OOB_ENABLED to
+ // pm.dexopt.priv-apps-oob property. This is only for experiment and should be removed once
+ // it is done.
+ ContentObserver privAppOobObserver = new ContentObserver(mHandler) {
+ @Override
+ public void onChange(boolean selfChange) {
+ int oobEnabled = Global.getInt(resolver, Global.PRIV_APP_OOB_ENABLED, 0);
+ SystemProperties.set(PROPERTY_NAME_PM_DEXOPT_PRIV_APPS_OOB,
+ oobEnabled == 1 ? "true" : "false");
+ }
+ };
+ mContext.getContentResolver().registerContentObserver(
+ Global.getUriFor(Global.PRIV_APP_OOB_ENABLED), false, privAppOobObserver,
+ UserHandle.USER_SYSTEM);
+ // At boot, restore the value from the setting, which persists across reboot.
+ privAppOobObserver.onChange(true);
+
// Disable any carrier apps. We do this very early in boot to prevent the apps from being
// disabled after already being started.
CarrierAppUtils.disableCarrierAppsUntilPrivileged(mContext.getOpPackageName(), this,
diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceCompilerMapping.java b/services/core/java/com/android/server/pm/PackageManagerServiceCompilerMapping.java
index cf9e6f3..19b0d9b 100644
--- a/services/core/java/com/android/server/pm/PackageManagerServiceCompilerMapping.java
+++ b/services/core/java/com/android/server/pm/PackageManagerServiceCompilerMapping.java
@@ -52,16 +52,7 @@
// Load the property for the given reason and check for validity. This will throw an
// exception in case the reason or value are invalid.
private static String getAndCheckValidity(int reason) {
- String sysPropName = getSystemPropertyName(reason);
- String sysPropValue;
- // TODO: This is a temporary hack to keep marlin booting on aosp/master while we
- // figure out how to deal with these system properties that currently appear on
- // vendor.
- if ("pm.dexopt.inactive".equals(sysPropName)) {
- sysPropValue = "verify";
- } else {
- sysPropValue = SystemProperties.get(sysPropName);
- }
+ String sysPropValue = SystemProperties.get(getSystemPropertyName(reason));
if (sysPropValue == null || sysPropValue.isEmpty() ||
!DexFile.isValidCompilerFilter(sysPropValue)) {
throw new IllegalStateException("Value \"" + sysPropValue +"\" not valid "
diff --git a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
index 011e1ac..4b13404 100644
--- a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
+++ b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
@@ -66,6 +66,7 @@
public static final Set<String> USER_RESTRICTIONS = newSetWithUniqueCheck(new String[] {
UserManager.DISALLOW_CONFIG_WIFI,
+ UserManager.DISALLOW_CONFIG_LOCALE,
UserManager.DISALLOW_MODIFY_ACCOUNTS,
UserManager.DISALLOW_INSTALL_APPS,
UserManager.DISALLOW_UNINSTALL_APPS,
diff --git a/services/core/java/com/android/server/pm/crossprofile/CrossProfileAppsService.java b/services/core/java/com/android/server/pm/crossprofile/CrossProfileAppsService.java
new file mode 100644
index 0000000..0913269
--- /dev/null
+++ b/services/core/java/com/android/server/pm/crossprofile/CrossProfileAppsService.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.pm.crossprofile;
+
+import android.content.Context;
+
+import com.android.server.SystemService;
+
+public class CrossProfileAppsService extends SystemService {
+ private CrossProfileAppsServiceImpl mServiceImpl;
+
+ public CrossProfileAppsService(Context context) {
+ super(context);
+ mServiceImpl = new CrossProfileAppsServiceImpl(context);
+ }
+
+ @Override
+ public void onStart() {
+ publishBinderService(Context.CROSS_PROFILE_APPS_SERVICE, mServiceImpl);
+ }
+}
diff --git a/services/core/java/com/android/server/pm/crossprofile/CrossProfileAppsServiceImpl.java b/services/core/java/com/android/server/pm/crossprofile/CrossProfileAppsServiceImpl.java
new file mode 100644
index 0000000..854b704
--- /dev/null
+++ b/services/core/java/com/android/server/pm/crossprofile/CrossProfileAppsServiceImpl.java
@@ -0,0 +1,264 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.pm.crossprofile;
+
+import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AWARE;
+import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_UNAWARE;
+
+import android.annotation.UserIdInt;
+import android.app.AppOpsManager;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManagerInternal;
+import android.content.pm.ResolveInfo;
+import android.content.pm.crossprofile.ICrossProfileApps;
+import android.graphics.Rect;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.text.TextUtils;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.Preconditions;
+import com.android.server.LocalServices;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class CrossProfileAppsServiceImpl extends ICrossProfileApps.Stub {
+ private static final String TAG = "CrossProfileAppsService";
+
+ private Context mContext;
+ private Injector mInjector;
+
+ public CrossProfileAppsServiceImpl(Context context) {
+ this(context, new InjectorImpl(context));
+ }
+
+ @VisibleForTesting
+ CrossProfileAppsServiceImpl(Context context, Injector injector) {
+ mContext = context;
+ mInjector = injector;
+ }
+
+ @Override
+ public List<UserHandle> getTargetUserProfiles(String callingPackage) {
+ Preconditions.checkNotNull(callingPackage);
+
+ verifyCallingPackage(callingPackage);
+
+ return getTargetUserProfilesUnchecked(
+ callingPackage, mInjector.getCallingUserId());
+ }
+
+ @Override
+ public void startActivityAsUser(
+ String callingPackage,
+ ComponentName component,
+ Rect sourceBounds,
+ Bundle startActivityOptions,
+ UserHandle user) throws RemoteException {
+ Preconditions.checkNotNull(callingPackage);
+ Preconditions.checkNotNull(component);
+ Preconditions.checkNotNull(user);
+
+ verifyCallingPackage(callingPackage);
+
+ List<UserHandle> allowedTargetUsers = getTargetUserProfilesUnchecked(
+ callingPackage, mInjector.getCallingUserId());
+ if (!allowedTargetUsers.contains(user)) {
+ throw new SecurityException(
+ callingPackage + " cannot access unrelated user " + user.getIdentifier());
+ }
+
+ // Verify that caller package is starting activity in its own package.
+ if (!callingPackage.equals(component.getPackageName())) {
+ throw new SecurityException(
+ callingPackage + " attempts to start an activity in other package - "
+ + component.getPackageName());
+ }
+
+ final int callingUid = mInjector.getCallingUid();
+
+ // Verify that target activity does handle the intent with ACTION_MAIN and
+ // CATEGORY_LAUNCHER as calling startActivityAsUser ignore them if component is present.
+ final Intent launchIntent = new Intent(Intent.ACTION_MAIN);
+ launchIntent.addCategory(Intent.CATEGORY_LAUNCHER);
+ launchIntent.setSourceBounds(sourceBounds);
+ launchIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK
+ | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
+ // Only package name is set here, as opposed to component name, because intent action and
+ // category are ignored if component name is present while we are resolving intent.
+ launchIntent.setPackage(component.getPackageName());
+ verifyActivityCanHandleIntentAndExported(launchIntent, component, callingUid, user);
+
+ final long ident = mInjector.clearCallingIdentity();
+ try {
+ launchIntent.setComponent(component);
+ mContext.startActivityAsUser(launchIntent, startActivityOptions, user);
+ } finally {
+ mInjector.restoreCallingIdentity(ident);
+ }
+ }
+
+ private List<UserHandle> getTargetUserProfilesUnchecked(
+ String callingPackage, @UserIdInt int callingUserId) {
+ final long ident = mInjector.clearCallingIdentity();
+ try {
+ final int[] enabledProfileIds =
+ mInjector.getUserManager().getEnabledProfileIds(callingUserId);
+
+ List<UserHandle> targetProfiles = new ArrayList<>();
+ for (final int userId : enabledProfileIds) {
+ if (userId == callingUserId) {
+ continue;
+ }
+ if (!isPackageEnabled(callingPackage, userId)) {
+ continue;
+ }
+ targetProfiles.add(UserHandle.of(userId));
+ }
+ return targetProfiles;
+ } finally {
+ mInjector.restoreCallingIdentity(ident);
+ }
+ }
+
+ private boolean isPackageEnabled(String packageName, @UserIdInt int userId) {
+ final int callingUid = mInjector.getCallingUid();
+ final long ident = mInjector.clearCallingIdentity();
+ try {
+ final PackageInfo info = mInjector.getPackageManagerInternal()
+ .getPackageInfo(
+ packageName,
+ MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE,
+ callingUid,
+ userId);
+ return info != null && info.applicationInfo.enabled;
+ } finally {
+ mInjector.restoreCallingIdentity(ident);
+ }
+ }
+
+ /**
+ * Verify that the specified intent does resolved to the specified component and the resolved
+ * activity is exported.
+ */
+ private void verifyActivityCanHandleIntentAndExported(
+ Intent launchIntent, ComponentName component, int callingUid, UserHandle user) {
+ final long ident = mInjector.clearCallingIdentity();
+ try {
+ final List<ResolveInfo> apps =
+ mInjector.getPackageManagerInternal().queryIntentActivities(
+ launchIntent,
+ MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE,
+ callingUid,
+ user.getIdentifier());
+ final int size = apps.size();
+ for (int i = 0; i < size; ++i) {
+ final ActivityInfo activityInfo = apps.get(i).activityInfo;
+ if (TextUtils.equals(activityInfo.packageName, component.getPackageName())
+ && TextUtils.equals(activityInfo.name, component.getClassName())
+ && activityInfo.exported) {
+ return;
+ }
+ }
+ throw new SecurityException("Attempt to launch activity without "
+ + " category Intent.CATEGORY_LAUNCHER or activity is not exported" + component);
+ } finally {
+ mInjector.restoreCallingIdentity(ident);
+ }
+ }
+
+ /**
+ * Verify that the given calling package is belong to the calling UID.
+ */
+ private void verifyCallingPackage(String callingPackage) {
+ mInjector.getAppOpsManager().checkPackage(mInjector.getCallingUid(), callingPackage);
+ }
+
+ private static class InjectorImpl implements Injector {
+ private Context mContext;
+
+ public InjectorImpl(Context context) {
+ mContext = context;
+ }
+
+ public int getCallingUid() {
+ return Binder.getCallingUid();
+ }
+
+ public int getCallingUserId() {
+ return UserHandle.getCallingUserId();
+ }
+
+ public UserHandle getCallingUserHandle() {
+ return Binder.getCallingUserHandle();
+ }
+
+ public long clearCallingIdentity() {
+ return Binder.clearCallingIdentity();
+ }
+
+ public void restoreCallingIdentity(long token) {
+ Binder.restoreCallingIdentity(token);
+ }
+
+ public UserManager getUserManager() {
+ return mContext.getSystemService(UserManager.class);
+ }
+
+ public PackageManagerInternal getPackageManagerInternal() {
+ return LocalServices.getService(PackageManagerInternal.class);
+ }
+
+ public PackageManager getPackageManager() {
+ return mContext.getPackageManager();
+ }
+
+ public AppOpsManager getAppOpsManager() {
+ return mContext.getSystemService(AppOpsManager.class);
+ }
+ }
+
+ @VisibleForTesting
+ public interface Injector {
+ int getCallingUid();
+
+ int getCallingUserId();
+
+ UserHandle getCallingUserHandle();
+
+ long clearCallingIdentity();
+
+ void restoreCallingIdentity(long token);
+
+ UserManager getUserManager();
+
+ PackageManagerInternal getPackageManagerInternal();
+
+ PackageManager getPackageManager();
+
+ AppOpsManager getAppOpsManager();
+
+ }
+}
diff --git a/services/core/java/com/android/server/policy/GlobalActions.java b/services/core/java/com/android/server/policy/GlobalActions.java
index 7a2e630..3707a5e 100644
--- a/services/core/java/com/android/server/policy/GlobalActions.java
+++ b/services/core/java/com/android/server/policy/GlobalActions.java
@@ -58,7 +58,7 @@
public void showDialog(boolean keyguardShowing, boolean deviceProvisioned) {
if (DEBUG) Slog.d(TAG, "showDialog " + keyguardShowing + " " + deviceProvisioned);
- if (mStatusBarInternal.isGlobalActionsDisabled()) {
+ if (mStatusBarInternal != null && mStatusBarInternal.isGlobalActionsDisabled()) {
return;
}
mKeyguardShowing = keyguardShowing;
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index caf85b0..7837940 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -8084,28 +8084,22 @@
private int updateLightNavigationBarLw(int vis, WindowState opaque,
WindowState opaqueOrDimming) {
final WindowState imeWin = mWindowManagerFuncs.getInputMethodWindowLw();
- final boolean isImeShownWithBottomNavBar =
- imeWin != null && imeWin.isVisibleLw() && mNavigationBarPosition == NAV_BAR_BOTTOM;
- if (isImeShownWithBottomNavBar) {
- final int winFlags = PolicyControl.getWindowFlags(imeWin, null);
- if ((winFlags & WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) != 0) {
- // If the IME window is visible and explicitly requesting custom nav bar background
- // rendering, respect its light flag.
- vis &= ~View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR;
- vis |= PolicyControl.getSystemUiVisibility(imeWin, null)
- & View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR;
- return vis;
- }
+
+ final WindowState navColorWin;
+ if (imeWin != null && imeWin.isVisibleLw() && mNavigationBarPosition == NAV_BAR_BOTTOM) {
+ navColorWin = imeWin;
+ } else {
+ navColorWin = opaqueOrDimming;
}
- if (opaqueOrDimming != null) {
- if (opaqueOrDimming == opaque) {
- // If the top fullscreen-or-dimming window is also the top fullscreen window,
- // respect its light flag.
+ if (navColorWin != null) {
+ if (navColorWin == opaque) {
+ // If the top fullscreen-or-dimming window is also the top fullscreen, respect
+ // its light flag.
vis &= ~View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR;
- vis |= PolicyControl.getSystemUiVisibility(opaqueOrDimming, null)
+ vis |= PolicyControl.getSystemUiVisibility(navColorWin, null)
& View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR;
- } else if (opaqueOrDimming.isDimming() || isImeShownWithBottomNavBar) {
+ } else if (navColorWin.isDimming() || navColorWin == imeWin) {
// Otherwise if it's dimming or it's the IME window, clear the light flag.
vis &= ~View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR;
}
diff --git a/services/core/java/com/android/server/stats/StatsCompanionService.java b/services/core/java/com/android/server/stats/StatsCompanionService.java
index 2488723..f41ff2c 100644
--- a/services/core/java/com/android/server/stats/StatsCompanionService.java
+++ b/services/core/java/com/android/server/stats/StatsCompanionService.java
@@ -44,6 +44,8 @@
import com.android.internal.net.NetworkStatsFactory;
import com.android.internal.os.KernelWakelockReader;
import com.android.internal.os.KernelWakelockStats;
+import com.android.internal.os.KernelCpuSpeedReader;
+import com.android.internal.os.PowerProfile;
import com.android.server.LocalServices;
import com.android.server.SystemService;
@@ -71,6 +73,9 @@
private final PendingIntent mPullingAlarmIntent;
private final BroadcastReceiver mAppUpdateReceiver;
private final BroadcastReceiver mUserUpdateReceiver;
+ private final KernelWakelockReader mKernelWakelockReader = new KernelWakelockReader();
+ private final KernelWakelockStats mTmpWakelockStats = new KernelWakelockStats();
+ private final KernelCpuSpeedReader[] mKernelCpuSpeedReaders;
public StatsCompanionService(Context context) {
super();
@@ -103,6 +108,16 @@
}
};
Slog.w(TAG, "Registered receiver for ACTION_PACKAGE_REPLACE AND ADDED.");
+ PowerProfile powerProfile = new PowerProfile(context);
+ final int numClusters = powerProfile.getNumCpuClusters();
+ mKernelCpuSpeedReaders = new KernelCpuSpeedReader[numClusters];
+ int firstCpuOfCluster = 0;
+ for (int i = 0; i < numClusters; i++) {
+ final int numSpeedSteps = powerProfile.getNumSpeedStepsInCpuCluster(i);
+ mKernelCpuSpeedReaders[i] = new KernelCpuSpeedReader(firstCpuOfCluster,
+ numSpeedSteps);
+ firstCpuOfCluster += powerProfile.getNumCoresInCpuCluster(i);
+ }
}
private final static int[] toIntArray(List<Integer> list) {
@@ -286,9 +301,6 @@
}
}
- private final KernelWakelockReader mKernelWakelockReader = new KernelWakelockReader();
- private final KernelWakelockStats mTmpWakelockStats = new KernelWakelockStats();
-
private StatsLogEventWrapper[] addNetworkStats(int tag, NetworkStats stats, boolean withFGBG) {
List<StatsLogEventWrapper> ret = new ArrayList<>();
int size = stats.size();
@@ -446,6 +458,22 @@
}
return ret.toArray(new StatsLogEventWrapper[ret.size()]);
}
+ case StatsLog.CPU_TIME_PER_FREQ_PULLED: {
+ List<StatsLogEventWrapper> ret = new ArrayList();
+ for (int cluster = 0; cluster < mKernelCpuSpeedReaders.length; cluster++) {
+ long[] clusterTimeMs = mKernelCpuSpeedReaders[cluster].readDelta();
+ if (clusterTimeMs != null) {
+ for (int speed = clusterTimeMs.length - 1; speed >= 0; --speed) {
+ StatsLogEventWrapper e = new StatsLogEventWrapper(tagId, 3);
+ e.writeInt(tagId);
+ e.writeInt(speed);
+ e.writeLong(clusterTimeMs[speed]);
+ ret.add(e);
+ }
+ }
+ }
+ return ret.toArray(new StatsLogEventWrapper[ret.size()]);
+ }
default:
Slog.w(TAG, "No such tagId data as " + tagId);
return null;
diff --git a/services/core/java/com/android/server/wm/DockedStackDividerController.java b/services/core/java/com/android/server/wm/DockedStackDividerController.java
index 5ea0e1d..e693e5a 100644
--- a/services/core/java/com/android/server/wm/DockedStackDividerController.java
+++ b/services/core/java/com/android/server/wm/DockedStackDividerController.java
@@ -23,6 +23,7 @@
import static android.view.Surface.ROTATION_270;
import static android.view.Surface.ROTATION_90;
import static android.view.WindowManager.DOCKED_BOTTOM;
+import static android.view.WindowManager.DOCKED_INVALID;
import static android.view.WindowManager.DOCKED_LEFT;
import static android.view.WindowManager.DOCKED_RIGHT;
import static android.view.WindowManager.DOCKED_TOP;
@@ -116,6 +117,7 @@
private final DimLayer mDimLayer;
private boolean mMinimizedDock;
+ private int mOriginalDockedSide = DOCKED_INVALID;
private boolean mAnimatingForMinimizedDockedStack;
private boolean mAnimationStarted;
private long mAnimationStartTime;
@@ -408,6 +410,31 @@
mDockedStackListeners.finishBroadcast();
}
+ /**
+ * Checks if the primary stack is allowed to dock to a specific side based on its original dock
+ * side.
+ *
+ * @param dockSide the side to see if it is valid
+ * @return true if the side provided is valid
+ */
+ boolean canPrimaryStackDockTo(int dockSide) {
+ if (mService.mPolicy.isDockSideAllowed(dockSide)) {
+ // Side is the same as original side
+ if (dockSide == mOriginalDockedSide) {
+ return true;
+ }
+ // Special rule that the top in portrait is always valid
+ if (dockSide == DOCKED_TOP) {
+ return true;
+ }
+ // Only if original docked side was top in portrait will allow left side for landscape
+ if (dockSide == DOCKED_LEFT && mOriginalDockedSide == DOCKED_TOP) {
+ return true;
+ }
+ }
+ return false;
+ }
+
void notifyDockedStackExistsChanged(boolean exists) {
// TODO(multi-display): Perform all actions only for current display.
final int size = mDockedStackListeners.beginBroadcast();
@@ -430,8 +457,11 @@
inputMethodManagerInternal.hideCurrentInputMethod();
mImeHideRequested = true;
}
+ final TaskStack stack = mDisplayContent.getSplitScreenPrimaryStackIgnoringVisibility();
+ mOriginalDockedSide = stack.getDockSide();
return;
}
+ mOriginalDockedSide = DOCKED_INVALID;
setMinimizedDockedStack(false /* minimizedDock */, false /* animate */);
}
diff --git a/services/core/java/com/android/server/wm/DragDropController.java b/services/core/java/com/android/server/wm/DragDropController.java
index 76e25ba..4567e10 100644
--- a/services/core/java/com/android/server/wm/DragDropController.java
+++ b/services/core/java/com/android/server/wm/DragDropController.java
@@ -21,6 +21,7 @@
import static com.android.server.wm.WindowManagerDebugConfig.SHOW_TRANSACTIONS;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
+import android.annotation.NonNull;
import android.content.ClipData;
import android.graphics.PixelFormat;
import android.os.Binder;
@@ -35,6 +36,8 @@
import android.view.SurfaceControl;
import android.view.SurfaceSession;
import android.view.View;
+import android.view.WindowManagerInternal.IDragDropCallback;
+import com.android.internal.util.Preconditions;
import com.android.server.input.InputWindowHandle;
/**
@@ -61,6 +64,7 @@
private WindowManagerService mService;
private final Handler mHandler;
+
/**
* Lock to preserve the order of state updates.
* The lock is used to process drag and drop state updates in order without having the window
@@ -94,6 +98,11 @@
*/
private final Object mWriteLock = new Object();
+ /**
+ * Callback which is used to sync drag state with the vendor-specific code.
+ */
+ @NonNull private IDragDropCallback mCallback = new IDragDropCallback() {};
+
boolean dragDropActiveLocked() {
return mDragState != null;
}
@@ -102,6 +111,13 @@
return mDragState.getInputWindowHandle();
}
+ void registerCallback(IDragDropCallback callback) {
+ Preconditions.checkNotNull(callback);
+ synchronized (mWriteLock) {
+ mCallback = callback;
+ }
+ }
+
DragDropController(WindowManagerService service, Looper looper) {
mService = service;
mHandler = new DragHandler(service, looper);
@@ -169,6 +185,10 @@
}
synchronized (mWriteLock) {
+ if (!mCallback.performDrag(window, dragToken, touchSource, touchX, touchY, thumbCenterX,
+ thumbCenterY, data)) {
+ return false;
+ }
synchronized (mService.mWindowMap) {
if (mDragState == null) {
Slog.w(TAG_WM, "No drag prepared");
@@ -251,6 +271,7 @@
}
synchronized (mWriteLock) {
+ mCallback.reportDropResult(window, consumed);
synchronized (mService.mWindowMap) {
if (mDragState == null) {
// Most likely the drop recipient ANRed and we ended the drag
@@ -288,6 +309,7 @@
}
synchronized (mWriteLock) {
+ mCallback.cancelDragAndDrop(dragToken);
synchronized (mService.mWindowMap) {
if (mDragState == null) {
Slog.w(TAG_WM, "cancelDragAndDrop() without prepareDrag()");
diff --git a/services/core/java/com/android/server/wm/TaskStack.java b/services/core/java/com/android/server/wm/TaskStack.java
index 6e89e3e..dde7946 100644
--- a/services/core/java/com/android/server/wm/TaskStack.java
+++ b/services/core/java/com/android/server/wm/TaskStack.java
@@ -442,7 +442,7 @@
mTmpRect2.set(mBounds);
mDisplayContent.rotateBounds(mRotation, newRotation, mTmpRect2);
if (inSplitScreenPrimaryWindowingMode()) {
- repositionDockedStackAfterRotation(mTmpRect2);
+ repositionPrimarySplitScreenStackAfterRotation(mTmpRect2);
snapDockedStackAfterRotation(mTmpRect2);
final int newDockSide = getDockSide(mTmpRect2);
@@ -466,14 +466,14 @@
}
/**
- * Some dock sides are not allowed by the policy. This method queries the policy and moves
- * the docked stack around if needed.
+ * Some primary split screen sides are not allowed by the policy. This method queries the policy
+ * and moves the primary stack around if needed.
*
- * @param inOutBounds the bounds of the docked stack to adjust
+ * @param inOutBounds the bounds of the primary stack to adjust
*/
- private void repositionDockedStackAfterRotation(Rect inOutBounds) {
+ private void repositionPrimarySplitScreenStackAfterRotation(Rect inOutBounds) {
int dockSide = getDockSide(inOutBounds);
- if (mService.mPolicy.isDockSideAllowed(dockSide)) {
+ if (mDisplayContent.getDockedDividerController().canPrimaryStackDockTo(dockSide)) {
return;
}
mDisplayContent.getLogicalDisplayRect(mTmpRect);
@@ -603,6 +603,14 @@
} else {
maxPosition = computeMaxPosition(maxPosition);
}
+
+ // preserve POSITION_BOTTOM/POSITION_TOP positions if they are still valid.
+ if (targetPosition == POSITION_BOTTOM && minPosition == 0) {
+ return POSITION_BOTTOM;
+ } else if (targetPosition == POSITION_TOP
+ && maxPosition == (addingNew ? stackSize : stackSize - 1)) {
+ return POSITION_TOP;
+ }
// Reset position based on minimum/maximum possible positions.
return Math.min(Math.max(targetPosition, minPosition), maxPosition);
}
diff --git a/services/core/java/com/android/server/wm/TaskWindowContainerController.java b/services/core/java/com/android/server/wm/TaskWindowContainerController.java
index 65f8cdf..b3bb0b7 100644
--- a/services/core/java/com/android/server/wm/TaskWindowContainerController.java
+++ b/services/core/java/com/android/server/wm/TaskWindowContainerController.java
@@ -103,6 +103,10 @@
}
}
+ public void positionChildAtTop(AppWindowContainerController childController) {
+ positionChildAt(childController, POSITION_TOP);
+ }
+
public void positionChildAt(AppWindowContainerController childController, int position) {
synchronized(mService.mWindowMap) {
final AppWindowToken aToken = childController.mContainer;
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index d9ba7d5..0652767 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -125,6 +125,7 @@
import android.app.AppOpsManager;
import android.app.IActivityManager;
import android.content.BroadcastReceiver;
+import android.content.ClipData;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
@@ -568,6 +569,8 @@
boolean mForceResizableTasks = false;
boolean mSupportsPictureInPicture = false;
+ private boolean mDisableTransitionAnimation = false;
+
int getDragLayerLocked() {
return mPolicy.getWindowLayerFromTypeLw(TYPE_DRAG) * TYPE_LAYER_MULTIPLIER + TYPE_LAYER_OFFSET;
}
@@ -965,6 +968,8 @@
com.android.internal.R.bool.config_allowAnimationsInLowPowerMode);
mMaxUiWidth = context.getResources().getInteger(
com.android.internal.R.integer.config_maxUiWidth);
+ mDisableTransitionAnimation = context.getResources().getBoolean(
+ com.android.internal.R.bool.config_disableTransitionAnimation);
mInputManager = inputManager; // Must be before createDisplayContentLocked.
mDisplayManagerInternal = LocalServices.getService(DisplayManagerInternal.class);
mDisplaySettings = new DisplaySettings();
@@ -2278,6 +2283,14 @@
boolean applyAnimationLocked(AppWindowToken atoken, WindowManager.LayoutParams lp,
int transit, boolean enter, boolean isVoiceInteraction) {
+ if (mDisableTransitionAnimation) {
+ if (DEBUG_APP_TRANSITIONS || DEBUG_ANIM) {
+ Slog.v(TAG_WM,
+ "applyAnimation: transition animation is disabled. atoken=" + atoken);
+ }
+ atoken.mAppAnimator.clearAnimation();
+ return false;
+ }
// Only apply an animation if the display isn't frozen. If it is
// frozen, there is no reason to animate and it can cause strange
// artifacts when we unfreeze the display if some different animation
@@ -7441,6 +7454,11 @@
mVr2dDisplayId = vr2dDisplayId;
}
}
+
+ @Override
+ public void registerDragDropControllerCallback(IDragDropCallback callback) {
+ mDragDropController.registerCallback(callback);
+ }
}
void registerAppFreezeListener(AppFreezeListener listener) {
diff --git a/services/core/jni/com_android_server_UsbDescriptorParser.cpp b/services/core/jni/com_android_server_UsbDescriptorParser.cpp
index 35e65bc..df85e31 100644
--- a/services/core/jni/com_android_server_UsbDescriptorParser.cpp
+++ b/services/core/jni/com_android_server_UsbDescriptorParser.cpp
@@ -17,18 +17,16 @@
#define LOG_TAG "UsbHostManagerJNI"
#include "utils/Log.h"
-#include <stdlib.h>
-
#include "jni.h"
#include <nativehelper/JNIHelp.h>
#include <usbhost/usbhost.h>
-#define MAX_DESCRIPTORS_LENGTH 4096
+#define MAX_DESCRIPTORS_LENGTH 16384
// com.android.server.usb.descriptors
extern "C" {
-jbyteArray JNICALL Java_com_android_server_usb_descriptors_UsbDescriptorParser_getRawDescriptors_1native(
+jbyteArray JNICALL Java_com_android_server_usb_descriptors_UsbDescriptorParser_getRawDescriptors(
JNIEnv* env, jobject thiz, jstring deviceAddr) {
const char *deviceAddrStr = env->GetStringUTFChars(deviceAddr, NULL);
struct usb_device* device = usb_device_open(deviceAddrStr);
@@ -41,7 +39,6 @@
int fd = usb_device_get_fd(device);
if (fd < 0) {
- usb_device_close(device);
return NULL;
}
@@ -49,48 +46,17 @@
jbyte buffer[MAX_DESCRIPTORS_LENGTH];
lseek(fd, 0, SEEK_SET);
int numBytes = read(fd, buffer, sizeof(buffer));
- jbyteArray ret = NULL;
+
usb_device_close(device);
- if (numBytes > 0) {
+ jbyteArray ret = NULL;
+ if (numBytes != 0) {
ret = env->NewByteArray(numBytes);
env->SetByteArrayRegion(ret, 0, numBytes, buffer);
- } else {
- ALOGE("error reading descriptors\n");
}
-
return ret;
}
-jstring JNICALL Java_com_android_server_usb_descriptors_UsbDescriptorParser_getDescriptorString_1native(
- JNIEnv* env, jobject thiz, jstring deviceAddr, jint stringId) {
-
- const char *deviceAddrStr = env->GetStringUTFChars(deviceAddr, NULL);
- struct usb_device* device = usb_device_open(deviceAddrStr);
- env->ReleaseStringUTFChars(deviceAddr, deviceAddrStr);
-
- if (!device) {
- ALOGE("usb_device_open failed");
- return NULL;
- }
-
- int fd = usb_device_get_fd(device);
- if (fd < 0) {
- ALOGE("usb_device_get_fd failed");
- usb_device_close(device);
- return NULL;
- }
-
- char* c_str = usb_device_get_string(device, stringId, 0 /*timeout*/);
-
- jstring j_str = env->NewStringUTF(c_str);
-
- free(c_str);
- usb_device_close(device);
-
- return j_str;
-}
-
} // extern "C"
diff --git a/services/core/jni/com_android_server_UsbHostManager.cpp b/services/core/jni/com_android_server_UsbHostManager.cpp
index 24f2014..88ae824 100644
--- a/services/core/jni/com_android_server_UsbHostManager.cpp
+++ b/services/core/jni/com_android_server_UsbHostManager.cpp
@@ -22,6 +22,8 @@
#include "android_runtime/AndroidRuntime.h"
#include "android_runtime/Log.h"
+#include <usbhost/usbhost.h>
+
#include <stdio.h>
#include <asm/byteorder.h>
#include <sys/types.h>
@@ -29,20 +31,22 @@
#include <fcntl.h>
#include <sys/ioctl.h>
-#include <usbhost/usbhost.h>
-
-#define MAX_DESCRIPTORS_LENGTH 4096
-
namespace android
{
+static const int USB_CONTROL_TRANSFER_TIMEOUT_MS = 200;
+
static struct parcel_file_descriptor_offsets_t
{
jclass mClass;
jmethodID mConstructor;
} gParcelFileDescriptorOffsets;
-static jmethodID method_usbDeviceAdded;
+static jmethodID method_beginUsbDeviceAdded;
+static jmethodID method_addUsbConfiguration;
+static jmethodID method_addUsbInterface;
+static jmethodID method_addUsbEndpoint;
+static jmethodID method_endUsbDeviceAdded;
static jmethodID method_usbDeviceRemoved;
static void checkAndClearExceptionFromCallback(JNIEnv* env, const char* methodName) {
@@ -53,52 +57,101 @@
}
}
-static int usb_device_added(const char *devAddress, void* clientData) {
- struct usb_device *device = usb_device_open(devAddress);
+static int usb_device_added(const char *devname, void* client_data) {
+ struct usb_descriptor_header* desc;
+ struct usb_descriptor_iter iter;
+
+ struct usb_device *device = usb_device_open(devname);
if (!device) {
ALOGE("usb_device_open failed\n");
return 0;
}
+ JNIEnv* env = AndroidRuntime::getJNIEnv();
+ jobject thiz = (jobject)client_data;
const usb_device_descriptor* deviceDesc = usb_device_get_device_descriptor(device);
- int classID = deviceDesc->bDeviceClass;
- int subClassID = deviceDesc->bDeviceSubClass;
- // get the raw descriptors
- int numBytes = usb_device_get_descriptors_length(device);
- if (numBytes > 0) {
- JNIEnv* env = AndroidRuntime::getJNIEnv();
- jobject thiz = (jobject)clientData;
- jstring deviceAddress = env->NewStringUTF(devAddress);
+ char *manufacturer = usb_device_get_manufacturer_name(device,
+ USB_CONTROL_TRANSFER_TIMEOUT_MS);
+ char *product = usb_device_get_product_name(device,
+ USB_CONTROL_TRANSFER_TIMEOUT_MS);
+ int version = usb_device_get_version(device);
+ char *serial = usb_device_get_serial(device,
+ USB_CONTROL_TRANSFER_TIMEOUT_MS);
- jbyteArray descriptorsArray = env->NewByteArray(numBytes);
- const jbyte* rawDescriptors = (const jbyte*)usb_device_get_raw_descriptors(device);
- env->SetByteArrayRegion(descriptorsArray, 0, numBytes, rawDescriptors);
+ jstring deviceName = env->NewStringUTF(devname);
+ jstring manufacturerName = AndroidRuntime::NewStringLatin1(env, manufacturer);
+ jstring productName = AndroidRuntime::NewStringLatin1(env, product);
+ jstring serialNumber = AndroidRuntime::NewStringLatin1(env, serial);
- env->CallBooleanMethod(thiz, method_usbDeviceAdded,
- deviceAddress, classID, subClassID, descriptorsArray);
+ jboolean result = env->CallBooleanMethod(thiz, method_beginUsbDeviceAdded,
+ deviceName, usb_device_get_vendor_id(device), usb_device_get_product_id(device),
+ deviceDesc->bDeviceClass, deviceDesc->bDeviceSubClass, deviceDesc->bDeviceProtocol,
+ manufacturerName, productName, version, serialNumber);
- env->DeleteLocalRef(descriptorsArray);
- env->DeleteLocalRef(deviceAddress);
+ env->DeleteLocalRef(serialNumber);
+ env->DeleteLocalRef(productName);
+ env->DeleteLocalRef(manufacturerName);
+ env->DeleteLocalRef(deviceName);
+ free(manufacturer);
+ free(product);
+ free(serial);
- checkAndClearExceptionFromCallback(env, __FUNCTION__);
- } else {
- // TODO return an error code here?
- ALOGE("error reading descriptors\n");
+ if (!result) goto fail;
+
+ usb_descriptor_iter_init(device, &iter);
+
+ while ((desc = usb_descriptor_iter_next(&iter)) != NULL) {
+ if (desc->bDescriptorType == USB_DT_CONFIG) {
+ struct usb_config_descriptor *config = (struct usb_config_descriptor *)desc;
+ char *name = usb_device_get_string(device, config->iConfiguration,
+ USB_CONTROL_TRANSFER_TIMEOUT_MS);
+ jstring configName = AndroidRuntime::NewStringLatin1(env, name);
+
+ env->CallVoidMethod(thiz, method_addUsbConfiguration,
+ config->bConfigurationValue, configName, config->bmAttributes,
+ config->bMaxPower);
+
+ env->DeleteLocalRef(configName);
+ free(name);
+ } else if (desc->bDescriptorType == USB_DT_INTERFACE) {
+ struct usb_interface_descriptor *interface = (struct usb_interface_descriptor *)desc;
+ char *name = usb_device_get_string(device, interface->iInterface,
+ USB_CONTROL_TRANSFER_TIMEOUT_MS);
+ jstring interfaceName = AndroidRuntime::NewStringLatin1(env, name);
+
+ env->CallVoidMethod(thiz, method_addUsbInterface,
+ interface->bInterfaceNumber, interfaceName, interface->bAlternateSetting,
+ interface->bInterfaceClass, interface->bInterfaceSubClass,
+ interface->bInterfaceProtocol);
+
+ env->DeleteLocalRef(interfaceName);
+ free(name);
+ } else if (desc->bDescriptorType == USB_DT_ENDPOINT) {
+ struct usb_endpoint_descriptor *endpoint = (struct usb_endpoint_descriptor *)desc;
+
+ env->CallVoidMethod(thiz, method_addUsbEndpoint,
+ endpoint->bEndpointAddress, endpoint->bmAttributes,
+ __le16_to_cpu(endpoint->wMaxPacketSize), endpoint->bInterval);
+ }
}
+ env->CallVoidMethod(thiz, method_endUsbDeviceAdded);
+
+fail:
usb_device_close(device);
+ checkAndClearExceptionFromCallback(env, __FUNCTION__);
return 0;
}
-static int usb_device_removed(const char *devAddress, void* clientData) {
+static int usb_device_removed(const char *devname, void* client_data) {
JNIEnv* env = AndroidRuntime::getJNIEnv();
- jobject thiz = (jobject)clientData;
+ jobject thiz = (jobject)client_data;
- jstring deviceAddress = env->NewStringUTF(devAddress);
- env->CallVoidMethod(thiz, method_usbDeviceRemoved, deviceAddress);
- env->DeleteLocalRef(deviceAddress);
+ jstring deviceName = env->NewStringUTF(devname);
+ env->CallVoidMethod(thiz, method_usbDeviceRemoved, deviceName);
+ env->DeleteLocalRef(deviceName);
checkAndClearExceptionFromCallback(env, __FUNCTION__);
return 0;
}
@@ -115,11 +168,11 @@
}
static jobject android_server_UsbHostManager_openDevice(JNIEnv *env, jobject /* thiz */,
- jstring deviceAddress)
+ jstring deviceName)
{
- const char *deviceAddressStr = env->GetStringUTFChars(deviceAddress, NULL);
- struct usb_device* device = usb_device_open(deviceAddressStr);
- env->ReleaseStringUTFChars(deviceAddress, deviceAddressStr);
+ const char *deviceNameStr = env->GetStringUTFChars(deviceName, NULL);
+ struct usb_device* device = usb_device_open(deviceNameStr);
+ env->ReleaseStringUTFChars(deviceName, deviceNameStr);
if (!device)
return NULL;
@@ -153,12 +206,34 @@
ALOGE("Can't find com/android/server/usb/UsbHostManager");
return -1;
}
- method_usbDeviceAdded =
- env->GetMethodID(clazz, "usbDeviceAdded", "(Ljava/lang/String;II[B)Z");
- if (method_usbDeviceAdded == NULL) {
+ method_beginUsbDeviceAdded = env->GetMethodID(clazz, "beginUsbDeviceAdded",
+ "(Ljava/lang/String;IIIIILjava/lang/String;Ljava/lang/String;ILjava/lang/String;)Z");
+ if (method_beginUsbDeviceAdded == NULL) {
ALOGE("Can't find beginUsbDeviceAdded");
return -1;
}
+ method_addUsbConfiguration = env->GetMethodID(clazz, "addUsbConfiguration",
+ "(ILjava/lang/String;II)V");
+ if (method_addUsbConfiguration == NULL) {
+ ALOGE("Can't find addUsbConfiguration");
+ return -1;
+ }
+ method_addUsbInterface = env->GetMethodID(clazz, "addUsbInterface",
+ "(ILjava/lang/String;IIII)V");
+ if (method_addUsbInterface == NULL) {
+ ALOGE("Can't find addUsbInterface");
+ return -1;
+ }
+ method_addUsbEndpoint = env->GetMethodID(clazz, "addUsbEndpoint", "(IIII)V");
+ if (method_addUsbEndpoint == NULL) {
+ ALOGE("Can't find addUsbEndpoint");
+ return -1;
+ }
+ method_endUsbDeviceAdded = env->GetMethodID(clazz, "endUsbDeviceAdded", "()V");
+ if (method_endUsbDeviceAdded == NULL) {
+ ALOGE("Can't find endUsbDeviceAdded");
+ return -1;
+ }
method_usbDeviceRemoved = env->GetMethodID(clazz, "usbDeviceRemoved",
"(Ljava/lang/String;)V");
if (method_usbDeviceRemoved == NULL) {
diff --git a/services/core/jni/com_android_server_location_GnssLocationProvider.cpp b/services/core/jni/com_android_server_location_GnssLocationProvider.cpp
index 1f1324a..daf3f2f 100644
--- a/services/core/jni/com_android_server_location_GnssLocationProvider.cpp
+++ b/services/core/jni/com_android_server_location_GnssLocationProvider.cpp
@@ -1207,6 +1207,13 @@
ALOGE("Unable to initialize GNSS NI interface\n");
}
+ sp<IAGnssRilCallback> aGnssRilCbIface = new AGnssRilCallback();
+ if (agnssRilIface != nullptr) {
+ agnssRilIface->setCallback(aGnssRilCbIface);
+ } else {
+ ALOGI("Unable to Initialize AGnss Ril interface\n");
+ }
+
return JNI_TRUE;
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/NetworkLogger.java b/services/devicepolicy/java/com/android/server/devicepolicy/NetworkLogger.java
index 0085931..0aaf32c 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/NetworkLogger.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/NetworkLogger.java
@@ -107,7 +107,8 @@
return false;
}
try {
- if (mIpConnectivityMetrics.registerNetdEventCallback(mNetdEventCallback)) {
+ if (mIpConnectivityMetrics.addNetdEventCallback(
+ INetdEventCallback.CALLBACK_CALLER_DEVICE_POLICY, mNetdEventCallback)) {
mHandlerThread = new ServiceThread(TAG, Process.THREAD_PRIORITY_BACKGROUND,
/* allowIo */ false);
mHandlerThread.start();
@@ -138,7 +139,8 @@
// logging is forcefully disabled even if unregistering fails
return true;
}
- return mIpConnectivityMetrics.unregisterNetdEventCallback();
+ return mIpConnectivityMetrics.removeNetdEventCallback(
+ INetdEventCallback.CALLBACK_CALLER_DEVICE_POLICY);
} catch (RemoteException re) {
Slog.wtf(TAG, "Failed to make remote calls to unregister the callback", re);
return true;
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index f8bcb73..74a7bd4a 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -37,7 +37,6 @@
import android.os.Message;
import android.os.PowerManager;
import android.os.Process;
-import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.StrictMode;
import android.os.SystemClock;
@@ -52,7 +51,7 @@
import android.view.WindowManager;
import com.android.internal.R;
-import com.android.internal.app.NightDisplayController;
+import com.android.internal.app.ColorDisplayController;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.notification.SystemNotificationChannels;
import com.android.internal.os.BinderInternal;
@@ -69,7 +68,7 @@
import com.android.server.coverage.CoverageService;
import com.android.server.devicepolicy.DevicePolicyManagerService;
import com.android.server.display.DisplayManagerService;
-import com.android.server.display.NightDisplayService;
+import com.android.server.display.ColorDisplayService;
import com.android.server.dreams.DreamManagerService;
import com.android.server.emergency.EmergencyAffordanceService;
import com.android.server.fingerprint.FingerprintService;
@@ -83,6 +82,7 @@
import com.android.server.media.projection.MediaProjectionManagerService;
import com.android.server.net.NetworkPolicyManagerService;
import com.android.server.net.NetworkStatsService;
+import com.android.server.net.watchlist.NetworkWatchlistService;
import com.android.server.notification.NotificationManagerService;
import com.android.server.oemlock.OemLockService;
import com.android.server.om.OverlayManagerService;
@@ -95,6 +95,7 @@
import com.android.server.pm.PackageManagerService;
import com.android.server.pm.ShortcutService;
import com.android.server.pm.UserManagerService;
+import com.android.server.pm.crossprofile.CrossProfileAppsService;
import com.android.server.policy.PhoneWindowManager;
import com.android.server.power.PowerManagerService;
import com.android.server.power.ShutdownThread;
@@ -884,6 +885,10 @@
mSystemServiceManager.startService(IpConnectivityMetrics.class);
traceEnd();
+ traceBeginAndSlog("NetworkWatchlistService");
+ mSystemServiceManager.startService(NetworkWatchlistService.Lifecycle.class);
+ traceEnd();
+
traceBeginAndSlog("PinnerService");
mSystemServiceManager.startService(PinnerService.class);
traceEnd();
@@ -1277,9 +1282,9 @@
mSystemServiceManager.startService(TwilightService.class);
traceEnd();
- if (NightDisplayController.isAvailable(context)) {
+ if (ColorDisplayController.isAvailable(context)) {
traceBeginAndSlog("StartNightDisplay");
- mSystemServiceManager.startService(NightDisplayService.class);
+ mSystemServiceManager.startService(ColorDisplayService.class);
traceEnd();
}
@@ -1485,6 +1490,10 @@
traceBeginAndSlog("StartLauncherAppsService");
mSystemServiceManager.startService(LauncherAppsService.class);
traceEnd();
+
+ traceBeginAndSlog("StartCrossProfileAppsService");
+ mSystemServiceManager.startService(CrossProfileAppsService.class);
+ traceEnd();
}
if (!disableMediaProjection) {
diff --git a/services/robotests/Android.mk b/services/robotests/Android.mk
index 322b891..59d2a24 100644
--- a/services/robotests/Android.mk
+++ b/services/robotests/Android.mk
@@ -26,13 +26,8 @@
LOCAL_PRIVILEGED_MODULE := true
LOCAL_STATIC_JAVA_LIBRARIES := \
- frameworks-base-testutils \
services.backup \
- services.core \
- android-support-test \
- mockito-target-minus-junit4 \
- platform-test-annotations \
- truth-prebuilt
+ services.core
include $(BUILD_PACKAGE)
@@ -45,6 +40,9 @@
# Include the testing libraries (JUnit4 + Robolectric libs).
LOCAL_STATIC_JAVA_LIBRARIES := \
+ android-support-test \
+ mockito-robolectric-prebuilt \
+ platform-test-annotations \
truth-prebuilt
LOCAL_JAVA_LIBRARIES := \
diff --git a/services/tests/notification/src/com/android/server/notification/ZenModeHelperTest.java b/services/tests/notification/src/com/android/server/notification/ZenModeHelperTest.java
index cbe9650..8ac6481 100644
--- a/services/tests/notification/src/com/android/server/notification/ZenModeHelperTest.java
+++ b/services/tests/notification/src/com/android/server/notification/ZenModeHelperTest.java
@@ -17,7 +17,7 @@
package com.android.server.notification;
import static junit.framework.Assert.assertFalse;
-import static junit.framework.Assert.assertTrue;
+import static junit.framework.TestCase.assertTrue;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
@@ -100,5 +100,34 @@
AudioAttributes.USAGE_GAME);
verify(mZenModeHelperSpy, atLeastOnce()).applyRestrictions(true,
AudioAttributes.USAGE_ASSISTANCE_SONIFICATION);
+ verify(mZenModeHelperSpy, atLeastOnce()).applyRestrictions(true,
+ AudioAttributes.USAGE_UNKNOWN);
+ }
+
+ @Test
+ public void testZenAllCannotBypass() {
+ // Only audio attributes with SUPPRESIBLE_NEVER can bypass
+ mZenModeHelperSpy.mZenMode = Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS;
+ mZenModeHelperSpy.mConfig.allowAlarms = false;
+ mZenModeHelperSpy.mConfig.allowMediaSystemOther = false;
+ mZenModeHelperSpy.mConfig.allowReminders = false;
+ mZenModeHelperSpy.mConfig.allowCalls = false;
+ mZenModeHelperSpy.mConfig.allowMessages = false;
+ mZenModeHelperSpy.mConfig.allowEvents = false;
+ mZenModeHelperSpy.mConfig.allowRepeatCallers= false;
+ assertFalse(mZenModeHelperSpy.mConfig.allowAlarms);
+ assertFalse(mZenModeHelperSpy.mConfig.allowMediaSystemOther);
+ assertFalse(mZenModeHelperSpy.mConfig.allowReminders);
+ assertFalse(mZenModeHelperSpy.mConfig.allowCalls);
+ assertFalse(mZenModeHelperSpy.mConfig.allowMessages);
+ assertFalse(mZenModeHelperSpy.mConfig.allowEvents);
+ assertFalse(mZenModeHelperSpy.mConfig.allowRepeatCallers);
+ mZenModeHelperSpy.applyRestrictions();
+
+ for (int usage : AudioAttributes.SDK_USAGES) {
+ boolean shouldMute = AudioAttributes.SUPPRESSIBLE_USAGES.get(usage)
+ != AudioAttributes.SUPPRESSIBLE_NEVER;
+ verify(mZenModeHelperSpy, atLeastOnce()).applyRestrictions(shouldMute, usage);
+ }
}
}
diff --git a/services/tests/servicestests/Android.mk b/services/tests/servicestests/Android.mk
index 8e41a55..a2ec234 100644
--- a/services/tests/servicestests/Android.mk
+++ b/services/tests/servicestests/Android.mk
@@ -25,7 +25,8 @@
mockito-target-minus-junit4 \
platform-test-annotations \
ShortcutManagerTestUtils \
- truth-prebuilt
+ truth-prebuilt \
+ testng
LOCAL_AIDL_INCLUDES := $(LOCAL_PATH)/aidl
diff --git a/services/tests/servicestests/assets/NetworkWatchlistTest/watchlist_settings_test1.xml b/services/tests/servicestests/assets/NetworkWatchlistTest/watchlist_settings_test1.xml
new file mode 100644
index 0000000..bb97e94
--- /dev/null
+++ b/services/tests/servicestests/assets/NetworkWatchlistTest/watchlist_settings_test1.xml
@@ -0,0 +1,27 @@
+<?xml version='1.0'?>
+<watchlist-settings>
+ <sha256-domain>
+ <!-- test-cc-domain.com -->
+ <hash>8E7DCD2AEB4F364358242BB3F403263E61E3B4AECE4E2500FF28BF32E52FF0F1</hash>
+ <!-- test-cc-match-sha256-only.com -->
+ <hash>F0905DA7549614957B449034C281EF7BDEFDBC2B6E050AD1E78D6DE18FBD0D5F</hash>
+ </sha256-domain>
+ <sha256-ip>
+ <!-- 127.0.0.2 -->
+ <hash>1EDD62868F2767A1FFF68DF0A4CB3C23448E45100715768DB9310B5E719536A1</hash>
+ <!-- 127.0.0.3, match in sha256 only -->
+ <hash>18DD41C9F2E8E4879A1575FB780514EF33CF6E1F66578C4AE7CCA31F49B9F2ED</hash>
+ </sha256-ip>
+ <crc32-domain>
+ <!-- test-cc-domain.com -->
+ <hash>6C67059D</hash>
+ <!-- test-cc-match-crc32-only.com -->
+ <hash>3DC775F8</hash>
+ </crc32-domain>
+ <crc32-ip>
+ <!-- 127.0.0.2 -->
+ <hash>4EBEB612</hash>
+ <!-- 127.0.0.4, match in crc32 only -->
+ <hash>A7DD1327</hash>
+ </crc32-ip>
+</watchlist-settings>
diff --git a/services/tests/servicestests/src/com/android/server/NightDisplayServiceTest.java b/services/tests/servicestests/src/com/android/server/ColorDisplayServiceTest.java
similarity index 96%
rename from services/tests/servicestests/src/com/android/server/NightDisplayServiceTest.java
rename to services/tests/servicestests/src/com/android/server/ColorDisplayServiceTest.java
index 3a92d63..46b364c 100644
--- a/services/tests/servicestests/src/com/android/server/NightDisplayServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/ColorDisplayServiceTest.java
@@ -29,10 +29,10 @@
import android.support.test.runner.AndroidJUnit4;
import android.test.mock.MockContentResolver;
-import com.android.internal.app.NightDisplayController;
+import com.android.internal.app.ColorDisplayController;
import com.android.internal.util.test.FakeSettingsProvider;
import com.android.server.display.DisplayTransformManager;
-import com.android.server.display.NightDisplayService;
+import com.android.server.display.ColorDisplayService;
import com.android.server.twilight.TwilightListener;
import com.android.server.twilight.TwilightManager;
import com.android.server.twilight.TwilightState;
@@ -55,15 +55,15 @@
import static org.mockito.Mockito.doReturn;
@RunWith(AndroidJUnit4.class)
-public class NightDisplayServiceTest {
+public class ColorDisplayServiceTest {
private Context mContext;
private int mUserId;
private MockTwilightManager mTwilightManager;
- private NightDisplayController mNightDisplayController;
- private NightDisplayService mNightDisplayService;
+ private ColorDisplayController mColorDisplayController;
+ private ColorDisplayService mColorDisplayService;
@Before
public void setUp() {
@@ -85,8 +85,8 @@
mTwilightManager = new MockTwilightManager();
LocalServices.addService(TwilightManager.class, mTwilightManager);
- mNightDisplayController = new NightDisplayController(mContext, mUserId);
- mNightDisplayService = new NightDisplayService(mContext);
+ mColorDisplayController = new ColorDisplayController(mContext, mUserId);
+ mColorDisplayService = new ColorDisplayService(mContext);
}
@After
@@ -94,8 +94,8 @@
LocalServices.removeServiceForTest(DisplayTransformManager.class);
LocalServices.removeServiceForTest(TwilightManager.class);
- mNightDisplayService = null;
- mNightDisplayController = null;
+ mColorDisplayService = null;
+ mColorDisplayController = null;
mTwilightManager = null;
@@ -902,9 +902,9 @@
* @param endTimeOffset the offset relative to now to deactivate Night display (in minutes)
*/
private void setAutoModeCustom(int startTimeOffset, int endTimeOffset) {
- mNightDisplayController.setAutoMode(NightDisplayController.AUTO_MODE_CUSTOM);
- mNightDisplayController.setCustomStartTime(getLocalTimeRelativeToNow(startTimeOffset));
- mNightDisplayController.setCustomEndTime(getLocalTimeRelativeToNow(endTimeOffset));
+ mColorDisplayController.setAutoMode(ColorDisplayController.AUTO_MODE_CUSTOM);
+ mColorDisplayController.setCustomStartTime(getLocalTimeRelativeToNow(startTimeOffset));
+ mColorDisplayController.setCustomEndTime(getLocalTimeRelativeToNow(endTimeOffset));
}
/**
@@ -914,7 +914,7 @@
* @param sunriseOffset the offset relative to now for sunrise (in minutes)
*/
private void setAutoModeTwilight(int sunsetOffset, int sunriseOffset) {
- mNightDisplayController.setAutoMode(NightDisplayController.AUTO_MODE_TWILIGHT);
+ mColorDisplayController.setAutoMode(ColorDisplayController.AUTO_MODE_TWILIGHT);
mTwilightManager.setTwilightState(
getTwilightStateRelativeToNow(sunsetOffset, sunriseOffset));
}
@@ -927,7 +927,7 @@
* activated (in minutes)
*/
private void setActivated(boolean activated, int lastActivatedTimeOffset) {
- mNightDisplayController.setActivated(activated);
+ mColorDisplayController.setActivated(activated);
Secure.putStringForUser(mContext.getContentResolver(),
Secure.NIGHT_DISPLAY_LAST_ACTIVATED_TIME,
LocalDateTime.now().plusMinutes(lastActivatedTimeOffset).toString(),
@@ -935,7 +935,7 @@
}
/**
- * Convenience method to start {@link #mNightDisplayService}.
+ * Convenience method to start {@link #mColorDisplayService}.
*/
private void startService() {
Secure.putIntForUser(mContext.getContentResolver(), Secure.USER_SETUP_COMPLETE, 1, mUserId);
@@ -943,9 +943,9 @@
InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
@Override
public void run() {
- mNightDisplayService.onStart();
- mNightDisplayService.onBootPhase(SystemService.PHASE_BOOT_COMPLETED);
- mNightDisplayService.onStartUser(mUserId);
+ mColorDisplayService.onStart();
+ mColorDisplayService.onBootPhase(SystemService.PHASE_BOOT_COMPLETED);
+ mColorDisplayService.onStartUser(mUserId);
}
});
}
@@ -957,7 +957,7 @@
*/
private void assertActivated(boolean activated) {
assertWithMessage("Invalid Night display activated state")
- .that(mNightDisplayController.isActivated())
+ .that(mColorDisplayController.isActivated())
.isEqualTo(activated);
}
@@ -988,21 +988,21 @@
final LocalDateTime now = LocalDateTime.now();
final ZoneId zoneId = ZoneId.systemDefault();
- long sunsetMillis = NightDisplayService.getDateTimeBefore(sunset, now)
+ long sunsetMillis = ColorDisplayService.getDateTimeBefore(sunset, now)
.atZone(zoneId)
.toInstant()
.toEpochMilli();
- long sunriseMillis = NightDisplayService.getDateTimeBefore(sunrise, now)
+ long sunriseMillis = ColorDisplayService.getDateTimeBefore(sunrise, now)
.atZone(zoneId)
.toInstant()
.toEpochMilli();
if (sunsetMillis < sunriseMillis) {
- sunsetMillis = NightDisplayService.getDateTimeAfter(sunset, now)
+ sunsetMillis = ColorDisplayService.getDateTimeAfter(sunset, now)
.atZone(zoneId)
.toInstant()
.toEpochMilli();
} else {
- sunriseMillis = NightDisplayService.getDateTimeAfter(sunrise, now)
+ sunriseMillis = ColorDisplayService.getDateTimeAfter(sunrise, now)
.atZone(zoneId)
.toInstant()
.toEpochMilli();
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
index 9d23fe9..6de3395 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -3193,7 +3193,7 @@
// setUp() adds a secondary user for CALLER_USER_HANDLE. Remove it as otherwise the
// feature is disabled because there are non-affiliated secondary users.
getServices().removeUser(DpmMockContext.CALLER_USER_HANDLE);
- when(getServices().iipConnectivityMetrics.registerNetdEventCallback(anyObject()))
+ when(getServices().iipConnectivityMetrics.addNetdEventCallback(anyInt(), anyObject()))
.thenReturn(true);
// No logs were retrieved so far.
diff --git a/services/tests/servicestests/src/com/android/server/job/JobStoreTest.java b/services/tests/servicestests/src/com/android/server/job/JobStoreTest.java
index 31ed8ba..bf912dd 100644
--- a/services/tests/servicestests/src/com/android/server/job/JobStoreTest.java
+++ b/services/tests/servicestests/src/com/android/server/job/JobStoreTest.java
@@ -1,50 +1,93 @@
package com.android.server.job;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_IMS;
+import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
-import android.content.ComponentName;
-import android.content.Context;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
import android.app.job.JobInfo;
import android.app.job.JobInfo.Builder;
+import android.content.ComponentName;
+import android.content.Context;
+import android.net.NetworkRequest;
+import android.os.Parcel;
+import android.os.Parcelable;
import android.os.PersistableBundle;
import android.os.SystemClock;
-import android.test.AndroidTestCase;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.runner.AndroidJUnit4;
import android.test.RenamingDelegatingContext;
import android.util.Log;
import android.util.Pair;
+import com.android.internal.util.HexDump;
+import com.android.server.IoThread;
import com.android.server.job.JobStore.JobSet;
import com.android.server.job.controllers.JobStatus;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.time.Clock;
+import java.time.ZoneOffset;
+import java.util.Arrays;
import java.util.Iterator;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
/**
* Test reading and writing correctly from file.
*/
-public class JobStoreTest extends AndroidTestCase {
+@RunWith(AndroidJUnit4.class)
+public class JobStoreTest {
private static final String TAG = "TaskStoreTest";
private static final String TEST_PREFIX = "_test_";
- private static final int SOME_UID = 34234;
+ private static final int SOME_UID = android.os.Process.FIRST_APPLICATION_UID;
private ComponentName mComponent;
- private static final long IO_WAIT = 1000L;
JobStore mTaskStoreUnderTest;
Context mTestContext;
- @Override
+ private Context getContext() {
+ return InstrumentationRegistry.getContext();
+ }
+
+ @Before
public void setUp() throws Exception {
mTestContext = new RenamingDelegatingContext(getContext(), TEST_PREFIX);
Log.d(TAG, "Saving tasks to '" + mTestContext.getFilesDir() + "'");
mTaskStoreUnderTest =
JobStore.initAndGetForTesting(mTestContext, mTestContext.getFilesDir());
mComponent = new ComponentName(getContext().getPackageName(), StubClass.class.getName());
+
+ // Freeze the clocks at this moment in time
+ JobSchedulerService.sSystemClock =
+ Clock.fixed(Clock.systemUTC().instant(), ZoneOffset.UTC);
+ JobSchedulerService.sUptimeMillisClock =
+ Clock.fixed(SystemClock.uptimeMillisClock().instant(), ZoneOffset.UTC);
+ JobSchedulerService.sElapsedRealtimeClock =
+ Clock.fixed(SystemClock.elapsedRealtimeClock().instant(), ZoneOffset.UTC);
}
- @Override
+ @After
public void tearDown() throws Exception {
mTaskStoreUnderTest.clear();
}
+ private void waitForPendingIo() throws Exception {
+ final CountDownLatch latch = new CountDownLatch(1);
+ IoThread.getHandler().post(() -> {
+ latch.countDown();
+ });
+ latch.await(10, TimeUnit.SECONDS);
+ }
+
+ @Test
public void testMaybeWriteStatusToDisk() throws Exception {
int taskId = 5;
long runByMillis = 20000L; // 20s
@@ -61,7 +104,8 @@
.build();
final JobStatus ts = JobStatus.createFromJobInfo(task, SOME_UID, null, -1, null);
mTaskStoreUnderTest.add(ts);
- Thread.sleep(IO_WAIT);
+ waitForPendingIo();
+
// Manually load tasks from xml file.
final JobSet jobStatusSet = new JobSet();
mTaskStoreUnderTest.readJobMapFromDisk(jobStatusSet, true);
@@ -75,9 +119,9 @@
ts.getEarliestRunTime(), loadedTaskStatus.getEarliestRunTime());
compareTimestampsSubjectToIoLatency("Late run-times not the same after read.",
ts.getLatestRunTimeElapsed(), loadedTaskStatus.getLatestRunTimeElapsed());
-
}
+ @Test
public void testWritingTwoFilesToDisk() throws Exception {
final JobInfo task1 = new Builder(8, mComponent)
.setRequiresDeviceIdle(true)
@@ -96,7 +140,7 @@
final JobStatus taskStatus2 = JobStatus.createFromJobInfo(task2, SOME_UID, null, -1, null);
mTaskStoreUnderTest.add(taskStatus1);
mTaskStoreUnderTest.add(taskStatus2);
- Thread.sleep(IO_WAIT);
+ waitForPendingIo();
final JobSet jobStatusSet = new JobSet();
mTaskStoreUnderTest.readJobMapFromDisk(jobStatusSet, true);
@@ -125,9 +169,9 @@
taskStatus2.getEarliestRunTime(), loaded2.getEarliestRunTime());
compareTimestampsSubjectToIoLatency("Late run-times not the same after read.",
taskStatus2.getLatestRunTimeElapsed(), loaded2.getLatestRunTimeElapsed());
-
}
+ @Test
public void testWritingTaskWithExtras() throws Exception {
JobInfo.Builder b = new Builder(8, mComponent)
.setRequiresDeviceIdle(true)
@@ -144,7 +188,7 @@
JobStatus taskStatus = JobStatus.createFromJobInfo(task, SOME_UID, null, -1, null);
mTaskStoreUnderTest.add(taskStatus);
- Thread.sleep(IO_WAIT);
+ waitForPendingIo();
final JobSet jobStatusSet = new JobSet();
mTaskStoreUnderTest.readJobMapFromDisk(jobStatusSet, true);
@@ -152,6 +196,8 @@
JobStatus loaded = jobStatusSet.getAllJobs().iterator().next();
assertTasksEqual(task, loaded.getJob());
}
+
+ @Test
public void testWritingTaskWithSourcePackage() throws Exception {
JobInfo.Builder b = new Builder(8, mComponent)
.setRequiresDeviceIdle(true)
@@ -162,7 +208,7 @@
"com.google.android.gms", 0, null);
mTaskStoreUnderTest.add(taskStatus);
- Thread.sleep(IO_WAIT);
+ waitForPendingIo();
final JobSet jobStatusSet = new JobSet();
mTaskStoreUnderTest.readJobMapFromDisk(jobStatusSet, true);
@@ -174,6 +220,7 @@
taskStatus.getSourceUserId());
}
+ @Test
public void testWritingTaskWithFlex() throws Exception {
JobInfo.Builder b = new Builder(8, mComponent)
.setRequiresDeviceIdle(true)
@@ -183,7 +230,7 @@
JobStatus taskStatus = JobStatus.createFromJobInfo(b.build(), SOME_UID, null, -1, null);
mTaskStoreUnderTest.add(taskStatus);
- Thread.sleep(IO_WAIT);
+ waitForPendingIo();
final JobSet jobStatusSet = new JobSet();
mTaskStoreUnderTest.readJobMapFromDisk(jobStatusSet, true);
@@ -195,6 +242,7 @@
taskStatus.getJob().getFlexMillis());
}
+ @Test
public void testMassivePeriodClampedOnRead() throws Exception {
final long ONE_HOUR = 60*60*1000L; // flex
final long TWO_HOURS = 2 * ONE_HOUR; // period
@@ -214,7 +262,7 @@
persistedExecutionTimesUTC);
mTaskStoreUnderTest.add(js);
- Thread.sleep(IO_WAIT);
+ waitForPendingIo();
final JobSet jobStatusSet = new JobSet();
mTaskStoreUnderTest.readJobMapFromDisk(jobStatusSet, true);
@@ -231,6 +279,7 @@
loaded.getEarliestRunTime() <= newNowElapsed + TWO_HOURS + ONE_HOUR);
}
+ @Test
public void testPriorityPersisted() throws Exception {
JobInfo.Builder b = new Builder(92, mComponent)
.setOverrideDeadline(5000)
@@ -238,7 +287,8 @@
.setPersisted(true);
final JobStatus js = JobStatus.createFromJobInfo(b.build(), SOME_UID, null, -1, null);
mTaskStoreUnderTest.add(js);
- Thread.sleep(IO_WAIT);
+ waitForPendingIo();
+
final JobSet jobStatusSet = new JobSet();
mTaskStoreUnderTest.readJobMapFromDisk(jobStatusSet, true);
JobStatus loaded = jobStatusSet.getAllJobs().iterator().next();
@@ -248,6 +298,7 @@
/**
* Test that non persisted job is not written to disk.
*/
+ @Test
public void testNonPersistedTaskIsNotPersisted() throws Exception {
JobInfo.Builder b = new Builder(42, mComponent)
.setOverrideDeadline(10000)
@@ -259,7 +310,8 @@
.setPersisted(true);
JobStatus jsPersisted = JobStatus.createFromJobInfo(b.build(), SOME_UID, null, -1, null);
mTaskStoreUnderTest.add(jsPersisted);
- Thread.sleep(IO_WAIT);
+ waitForPendingIo();
+
final JobSet jobStatusSet = new JobSet();
mTaskStoreUnderTest.readJobMapFromDisk(jobStatusSet, true);
assertEquals("Job count is incorrect.", 1, jobStatusSet.size());
@@ -267,6 +319,62 @@
assertEquals("Wrong job persisted.", 43, jobStatus.getJobId());
}
+ @Test
+ public void testRequiredNetworkType() throws Exception {
+ assertPersistedEquals(new JobInfo.Builder(0, mComponent)
+ .setPersisted(true)
+ .setRequiresDeviceIdle(true)
+ .setRequiredNetworkType(JobInfo.NETWORK_TYPE_NONE).build());
+ assertPersistedEquals(new JobInfo.Builder(0, mComponent)
+ .setPersisted(true)
+ .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY).build());
+ assertPersistedEquals(new JobInfo.Builder(0, mComponent)
+ .setPersisted(true)
+ .setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED).build());
+ assertPersistedEquals(new JobInfo.Builder(0, mComponent)
+ .setPersisted(true)
+ .setRequiredNetworkType(JobInfo.NETWORK_TYPE_NOT_ROAMING).build());
+ assertPersistedEquals(new JobInfo.Builder(0, mComponent)
+ .setPersisted(true)
+ .setRequiredNetworkType(JobInfo.NETWORK_TYPE_CELLULAR).build());
+ }
+
+ @Test
+ public void testRequiredNetwork() throws Exception {
+ assertPersistedEquals(new JobInfo.Builder(0, mComponent)
+ .setPersisted(true)
+ .setRequiresDeviceIdle(true)
+ .setRequiredNetwork(null).build());
+ assertPersistedEquals(new JobInfo.Builder(0, mComponent)
+ .setPersisted(true)
+ .setRequiredNetwork(new NetworkRequest.Builder().build()).build());
+ assertPersistedEquals(new JobInfo.Builder(0, mComponent)
+ .setPersisted(true)
+ .setRequiredNetwork(new NetworkRequest.Builder()
+ .addTransportType(TRANSPORT_WIFI).build())
+ .build());
+ assertPersistedEquals(new JobInfo.Builder(0, mComponent)
+ .setPersisted(true)
+ .setRequiredNetwork(new NetworkRequest.Builder()
+ .addCapability(NET_CAPABILITY_IMS).build())
+ .build());
+ }
+
+ /**
+ * Helper function to kick a {@link JobInfo} through a persistence cycle and
+ * assert that it's unchanged.
+ */
+ private void assertPersistedEquals(JobInfo first) throws Exception {
+ mTaskStoreUnderTest.clear();
+ mTaskStoreUnderTest.add(JobStatus.createFromJobInfo(first, SOME_UID, null, -1, null));
+ waitForPendingIo();
+
+ final JobSet jobStatusSet = new JobSet();
+ mTaskStoreUnderTest.readJobMapFromDisk(jobStatusSet, true);
+ final JobStatus second = jobStatusSet.getAllJobs().iterator().next();
+ assertTasksEqual(first, second.getJob());
+ }
+
/**
* Helper function to throw an error if the provided task and TaskStatus objects are not equal.
*/
@@ -286,12 +394,10 @@
second.isRequireBatteryNotLow());
assertEquals("Invalid idle constraint.", first.isRequireDeviceIdle(),
second.isRequireDeviceIdle());
- assertEquals("Invalid unmetered constraint.",
- first.getNetworkType() == JobInfo.NETWORK_TYPE_UNMETERED,
- second.getNetworkType() == JobInfo.NETWORK_TYPE_UNMETERED);
- assertEquals("Invalid connectivity constraint.",
- first.getNetworkType() == JobInfo.NETWORK_TYPE_ANY,
- second.getNetworkType() == JobInfo.NETWORK_TYPE_ANY);
+ assertEquals("Invalid network type.",
+ first.getNetworkType(), second.getNetworkType());
+ assertEquals("Invalid network.",
+ first.getRequiredNetwork(), second.getRequiredNetwork());
assertEquals("Invalid deadline constraint.",
first.hasLateConstraint(),
second.hasLateConstraint());
@@ -302,6 +408,26 @@
first.getExtras().toString(), second.getExtras().toString());
assertEquals("Transient xtras don't match",
first.getTransientExtras().toString(), second.getTransientExtras().toString());
+
+ // Since people can forget to add tests here for new fields, do one last
+ // sanity check based on bits-on-wire equality.
+ final byte[] firstBytes = marshall(first);
+ final byte[] secondBytes = marshall(second);
+ if (!Arrays.equals(firstBytes, secondBytes)) {
+ Log.w(TAG, "First: " + HexDump.dumpHexString(firstBytes));
+ Log.w(TAG, "Second: " + HexDump.dumpHexString(secondBytes));
+ fail("Raw JobInfo aren't equal; see logs for details");
+ }
+ }
+
+ private static byte[] marshall(Parcelable p) {
+ final Parcel parcel = Parcel.obtain();
+ try {
+ p.writeToParcel(parcel, 0);
+ return parcel.marshall();
+ } finally {
+ parcel.recycle();
+ }
}
/**
@@ -312,7 +438,7 @@
*/
private void compareTimestampsSubjectToIoLatency(String error, long ts1, long ts2) {
final long DELTA_MILLIS = 700L; // We allow up to 700ms of latency for IO read/writes.
- assertTrue(error, Math.abs(ts1 - ts2) < DELTA_MILLIS + IO_WAIT);
+ assertTrue(error, Math.abs(ts1 - ts2) < DELTA_MILLIS);
}
private static class StubClass {}
diff --git a/services/tests/servicestests/src/com/android/server/net/watchlist/HarmfulDigestsTests.java b/services/tests/servicestests/src/com/android/server/net/watchlist/HarmfulDigestsTests.java
new file mode 100644
index 0000000..a34f95e
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/net/watchlist/HarmfulDigestsTests.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.net.watchlist;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import com.android.internal.util.HexDump;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Arrays;
+
+/**
+ * runtest frameworks-services -c com.android.server.net.watchlist.HarmfulDigestsTests
+ */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class HarmfulDigestsTests {
+
+ private static final byte[] TEST_DIGEST_1 = HexDump.hexStringToByteArray("AAAAAA");
+ private static final byte[] TEST_DIGEST_2 = HexDump.hexStringToByteArray("BBBBBB");
+ private static final byte[] TEST_DIGEST_3 = HexDump.hexStringToByteArray("AAAABB");
+ private static final byte[] TEST_DIGEST_4 = HexDump.hexStringToByteArray("BBBBAA");
+
+ @Before
+ public void setUp() throws Exception {
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ }
+
+ @Test
+ public void testHarmfulDigests_setAndContains() throws Exception {
+ HarmfulDigests harmfulDigests = new HarmfulDigests(
+ Arrays.asList(new byte[][] {TEST_DIGEST_1}));
+ assertTrue(harmfulDigests.contains(TEST_DIGEST_1));
+ assertFalse(harmfulDigests.contains(TEST_DIGEST_2));
+ assertFalse(harmfulDigests.contains(TEST_DIGEST_3));
+ assertFalse(harmfulDigests.contains(TEST_DIGEST_4));
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/net/watchlist/NetworkWatchlistServiceTests.java b/services/tests/servicestests/src/com/android/server/net/watchlist/NetworkWatchlistServiceTests.java
new file mode 100644
index 0000000..ccd3cdd
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/net/watchlist/NetworkWatchlistServiceTests.java
@@ -0,0 +1,217 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.net.watchlist;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.net.ConnectivityMetricsEvent;
+import android.net.IIpConnectivityMetrics;
+import android.net.INetdEventCallback;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Message;
+import android.os.Process;
+import android.os.RemoteException;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.MediumTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import com.android.server.ServiceThread;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * runtest frameworks-services -c com.android.server.net.watchlist.NetworkWatchlistServiceTests
+ */
+@RunWith(AndroidJUnit4.class)
+@MediumTest
+public class NetworkWatchlistServiceTests {
+
+ private static final long NETWOR_EVENT_TIMEOUT_SEC = 1;
+ private static final String TEST_HOST = "testhost.com";
+ private static final String TEST_IP = "7.6.8.9";
+ private static final String[] TEST_IPS =
+ new String[] {"1.2.3.4", "4.6.8.9", "2001:0db8:0001:0000:0000:0ab9:C0A8:0102"};
+
+ private static class TestHandler extends Handler {
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case WatchlistLoggingHandler.LOG_WATCHLIST_EVENT_MSG:
+ onLogEvent();
+ break;
+ case WatchlistLoggingHandler.REPORT_RECORDS_IF_NECESSARY_MSG:
+ onAggregateEvent();
+ break;
+ default:
+ fail("Unexpected message: " + msg.what);
+ }
+ }
+
+ public void onLogEvent() {}
+ public void onAggregateEvent() {}
+ }
+
+ private static class TestIIpConnectivityMetrics implements IIpConnectivityMetrics {
+
+ int counter = 0;
+ INetdEventCallback callback = null;
+
+ @Override
+ public IBinder asBinder() {
+ return null;
+ }
+
+ @Override
+ public int logEvent(ConnectivityMetricsEvent connectivityMetricsEvent)
+ throws RemoteException {
+ return 0;
+ }
+
+ @Override
+ public boolean addNetdEventCallback(int callerType, INetdEventCallback callback) {
+ counter++;
+ this.callback = callback;
+ return true;
+ }
+
+ @Override
+ public boolean removeNetdEventCallback(int callerType) {
+ counter--;
+ return true;
+ }
+ };
+
+ ServiceThread mHandlerThread;
+ WatchlistLoggingHandler mWatchlistHandler;
+ NetworkWatchlistService mWatchlistService;
+
+ @Before
+ public void setUp() {
+ mHandlerThread = new ServiceThread("NetworkWatchlistServiceTests",
+ Process.THREAD_PRIORITY_BACKGROUND, /* allowIo */ false);
+ mHandlerThread.start();
+ mWatchlistHandler = new WatchlistLoggingHandler(InstrumentationRegistry.getContext(),
+ mHandlerThread.getLooper());
+ mWatchlistService = new NetworkWatchlistService(InstrumentationRegistry.getContext(),
+ mHandlerThread, mWatchlistHandler, null);
+ }
+
+ @After
+ public void tearDown() {
+ mHandlerThread.quitSafely();
+ }
+
+ @Test
+ public void testStartStopWatchlistLogging() throws Exception {
+ TestIIpConnectivityMetrics connectivityMetrics = new TestIIpConnectivityMetrics() {
+ @Override
+ public boolean addNetdEventCallback(int callerType, INetdEventCallback callback) {
+ super.addNetdEventCallback(callerType, callback);
+ assertEquals(callerType, INetdEventCallback.CALLBACK_CALLER_NETWORK_WATCHLIST);
+ return true;
+ }
+
+ @Override
+ public boolean removeNetdEventCallback(int callerType) {
+ super.removeNetdEventCallback(callerType);
+ assertEquals(callerType, INetdEventCallback.CALLBACK_CALLER_NETWORK_WATCHLIST);
+ return true;
+ }
+ };
+ assertEquals(connectivityMetrics.counter, 0);
+ mWatchlistService.mIpConnectivityMetrics = connectivityMetrics;
+ assertTrue(mWatchlistService.startWatchlistLoggingImpl());
+ assertEquals(connectivityMetrics.counter, 1);
+ assertTrue(mWatchlistService.startWatchlistLoggingImpl());
+ assertEquals(connectivityMetrics.counter, 1);
+ assertTrue(mWatchlistService.stopWatchlistLoggingImpl());
+ assertEquals(connectivityMetrics.counter, 0);
+ assertTrue(mWatchlistService.stopWatchlistLoggingImpl());
+ assertEquals(connectivityMetrics.counter, 0);
+ assertTrue(mWatchlistService.startWatchlistLoggingImpl());
+ assertEquals(connectivityMetrics.counter, 1);
+ assertTrue(mWatchlistService.stopWatchlistLoggingImpl());
+ assertEquals(connectivityMetrics.counter, 0);
+ }
+
+ @Test
+ public void testNetworkEvents() throws Exception {
+ TestIIpConnectivityMetrics connectivityMetrics = new TestIIpConnectivityMetrics();
+ mWatchlistService.mIpConnectivityMetrics = connectivityMetrics;
+ assertTrue(mWatchlistService.startWatchlistLoggingImpl());
+
+ // Test DNS events
+ final CountDownLatch testDnsLatch = new CountDownLatch(1);
+ final Object[] dnsParams = new Object[3];
+ final WatchlistLoggingHandler testDnsHandler =
+ new WatchlistLoggingHandler(InstrumentationRegistry.getContext(),
+ mHandlerThread.getLooper()) {
+ @Override
+ public void asyncNetworkEvent(String host, String[] ipAddresses, int uid) {
+ dnsParams[0] = host;
+ dnsParams[1] = ipAddresses;
+ dnsParams[2] = uid;
+ testDnsLatch.countDown();
+ }
+ };
+ mWatchlistService.mNetworkWatchlistHandler = testDnsHandler;
+ connectivityMetrics.callback.onDnsEvent(TEST_HOST, TEST_IPS, TEST_IPS.length, 123L, 456);
+ if (!testDnsLatch.await(NETWOR_EVENT_TIMEOUT_SEC, TimeUnit.SECONDS)) {
+ fail("Timed out waiting for network event");
+ }
+ assertEquals(TEST_HOST, dnsParams[0]);
+ for (int i = 0; i < TEST_IPS.length; i++) {
+ assertEquals(TEST_IPS[i], ((String[])dnsParams[1])[i]);
+ }
+ assertEquals(456, dnsParams[2]);
+
+ // Test connect events
+ final CountDownLatch testConnectLatch = new CountDownLatch(1);
+ final Object[] connectParams = new Object[3];
+ final WatchlistLoggingHandler testConnectHandler =
+ new WatchlistLoggingHandler(InstrumentationRegistry.getContext(),
+ mHandlerThread.getLooper()) {
+ @Override
+ public void asyncNetworkEvent(String host, String[] ipAddresses, int uid) {
+ connectParams[0] = host;
+ connectParams[1] = ipAddresses;
+ connectParams[2] = uid;
+ testConnectLatch.countDown();
+ }
+ };
+ mWatchlistService.mNetworkWatchlistHandler = testConnectHandler;
+ connectivityMetrics.callback.onConnectEvent(TEST_IP, 80, 123L, 456);
+ if (!testConnectLatch.await(NETWOR_EVENT_TIMEOUT_SEC, TimeUnit.SECONDS)) {
+ fail("Timed out waiting for network event");
+ }
+ assertNull(connectParams[0]);
+ assertEquals(1, ((String[]) connectParams[1]).length);
+ assertEquals(TEST_IP, ((String[]) connectParams[1])[0]);
+ assertEquals(456, connectParams[2]);
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/net/watchlist/WatchlistLoggingHandlerTests.java b/services/tests/servicestests/src/com/android/server/net/watchlist/WatchlistLoggingHandlerTests.java
new file mode 100644
index 0000000..e356b13
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/net/watchlist/WatchlistLoggingHandlerTests.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.net.watchlist;
+
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Arrays;
+
+/**
+ * runtest frameworks-services -c com.android.server.net.watchlist.WatchlistLoggingHandlerTests
+ */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class WatchlistLoggingHandlerTests {
+
+ @Before
+ public void setUp() throws Exception {
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ }
+
+ @Test
+ public void testWatchlistLoggingHandler_getAllSubDomains() throws Exception {
+ String[] subDomains = WatchlistLoggingHandler.getAllSubDomains("abc.def.gh.i.jkl.mm");
+ assertTrue(Arrays.equals(subDomains, new String[] {"abc.def.gh.i.jkl.mm",
+ "def.gh.i.jkl.mm", "gh.i.jkl.mm", "i.jkl.mm", "jkl.mm", "mm"}));
+ subDomains = WatchlistLoggingHandler.getAllSubDomains(null);
+ assertNull(subDomains);
+ subDomains = WatchlistLoggingHandler.getAllSubDomains("jkl.mm");
+ assertTrue(Arrays.equals(subDomains, new String[] {"jkl.mm", "mm"}));
+ subDomains = WatchlistLoggingHandler.getAllSubDomains("abc");
+ assertTrue(Arrays.equals(subDomains, new String[] {"abc"}));
+ subDomains = WatchlistLoggingHandler.getAllSubDomains("jkl.mm.");
+ assertTrue(Arrays.equals(subDomains, new String[] {"jkl.mm.", "mm."}));
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/net/watchlist/WatchlistSettingsTests.java b/services/tests/servicestests/src/com/android/server/net/watchlist/WatchlistSettingsTests.java
new file mode 100644
index 0000000..f3cb980
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/net/watchlist/WatchlistSettingsTests.java
@@ -0,0 +1,195 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.net.watchlist;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.content.Context;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import com.android.internal.util.HexDump;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.util.Arrays;
+
+/**
+ * runtest frameworks-services -c com.android.server.net.watchlist.WatchlistSettingsTests
+ */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class WatchlistSettingsTests {
+
+ private static final String TEST_XML_1 = "NetworkWatchlistTest/watchlist_settings_test1.xml";
+ private static final String TEST_CC_DOMAIN = "test-cc-domain.com";
+ private static final String TEST_CC_IP = "127.0.0.2";
+ private static final String TEST_NOT_EXIST_CC_DOMAIN = "test-not-exist-cc-domain.com";
+ private static final String TEST_NOT_EXIST_CC_IP = "1.2.3.4";
+ private static final String TEST_SHA256_ONLY_DOMAIN = "test-cc-match-sha256-only.com";
+ private static final String TEST_SHA256_ONLY_IP = "127.0.0.3";
+ private static final String TEST_CRC32_ONLY_DOMAIN = "test-cc-match-crc32-only.com";
+ private static final String TEST_CRC32_ONLY_IP = "127.0.0.4";
+
+ private static final String TEST_NEW_CC_DOMAIN = "test-new-cc-domain.com";
+ private static final byte[] TEST_NEW_CC_DOMAIN_SHA256 = HexDump.hexStringToByteArray(
+ "B86F9D37425340B635F43D6BC2506630761ADA71F5E6BBDBCA4651C479F9FB43");
+ private static final byte[] TEST_NEW_CC_DOMAIN_CRC32 = HexDump.hexStringToByteArray("76795BD3");
+
+ private static final String TEST_NEW_CC_IP = "1.1.1.2";
+ private static final byte[] TEST_NEW_CC_IP_SHA256 = HexDump.hexStringToByteArray(
+ "721BAB5E313CF0CC76B10F9592F18B9D1B8996497501A3306A55B3AE9F1CC87C");
+ private static final byte[] TEST_NEW_CC_IP_CRC32 = HexDump.hexStringToByteArray("940B8BEE");
+
+ private Context mContext;
+ private File mTestXmlFile;
+
+ @Before
+ public void setUp() throws Exception {
+ mContext = InstrumentationRegistry.getContext();
+ mTestXmlFile = new File(mContext.getFilesDir(), "test_watchlist_settings.xml");
+ mTestXmlFile.delete();
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ mTestXmlFile.delete();
+ }
+
+ @Test
+ public void testWatchlistSettings_parsing() throws Exception {
+ copyWatchlistSettingsXml(mContext, TEST_XML_1, mTestXmlFile);
+ WatchlistSettings settings = new WatchlistSettings(mTestXmlFile);
+ assertTrue(settings.containsDomain(TEST_CC_DOMAIN));
+ assertTrue(settings.containsIp(TEST_CC_IP));
+ assertFalse(settings.containsDomain(TEST_NOT_EXIST_CC_DOMAIN));
+ assertFalse(settings.containsIp(TEST_NOT_EXIST_CC_IP));
+ assertFalse(settings.containsDomain(TEST_SHA256_ONLY_DOMAIN));
+ assertFalse(settings.containsIp(TEST_SHA256_ONLY_IP));
+ assertFalse(settings.containsDomain(TEST_CRC32_ONLY_DOMAIN));
+ assertFalse(settings.containsIp(TEST_CRC32_ONLY_IP));
+ }
+
+ @Test
+ public void testWatchlistSettings_writeSettingsToDisk() throws Exception {
+ copyWatchlistSettingsXml(mContext, TEST_XML_1, mTestXmlFile);
+ WatchlistSettings settings = new WatchlistSettings(mTestXmlFile);
+ settings.writeSettingsToDisk(Arrays.asList(TEST_NEW_CC_DOMAIN_CRC32),
+ Arrays.asList(TEST_NEW_CC_DOMAIN_SHA256), Arrays.asList(TEST_NEW_CC_IP_CRC32),
+ Arrays.asList(TEST_NEW_CC_IP_SHA256));
+ // Ensure old watchlist is not in memory
+ assertFalse(settings.containsDomain(TEST_CC_DOMAIN));
+ assertFalse(settings.containsIp(TEST_CC_IP));
+ assertFalse(settings.containsDomain(TEST_NOT_EXIST_CC_DOMAIN));
+ assertFalse(settings.containsIp(TEST_NOT_EXIST_CC_IP));
+ assertFalse(settings.containsDomain(TEST_SHA256_ONLY_DOMAIN));
+ assertFalse(settings.containsIp(TEST_SHA256_ONLY_IP));
+ assertFalse(settings.containsDomain(TEST_CRC32_ONLY_DOMAIN));
+ assertFalse(settings.containsIp(TEST_CRC32_ONLY_IP));
+ // Ensure new watchlist is in memory
+ assertTrue(settings.containsDomain(TEST_NEW_CC_DOMAIN));
+ assertTrue(settings.containsIp(TEST_NEW_CC_IP));
+ // Reload settings from disk and test again
+ settings = new WatchlistSettings(mTestXmlFile);
+ // Ensure old watchlist is not in memory
+ assertFalse(settings.containsDomain(TEST_CC_DOMAIN));
+ assertFalse(settings.containsIp(TEST_CC_IP));
+ assertFalse(settings.containsDomain(TEST_NOT_EXIST_CC_DOMAIN));
+ assertFalse(settings.containsIp(TEST_NOT_EXIST_CC_IP));
+ assertFalse(settings.containsDomain(TEST_SHA256_ONLY_DOMAIN));
+ assertFalse(settings.containsIp(TEST_SHA256_ONLY_IP));
+ assertFalse(settings.containsDomain(TEST_CRC32_ONLY_DOMAIN));
+ assertFalse(settings.containsIp(TEST_CRC32_ONLY_IP));
+ // Ensure new watchlist is in memory
+ assertTrue(settings.containsDomain(TEST_NEW_CC_DOMAIN));
+ assertTrue(settings.containsIp(TEST_NEW_CC_IP));
+ }
+
+ @Test
+ public void testWatchlistSettings_writeSettingsToMemory() throws Exception {
+ copyWatchlistSettingsXml(mContext, TEST_XML_1, mTestXmlFile);
+ WatchlistSettings settings = new WatchlistSettings(mTestXmlFile);
+ settings.writeSettingsToMemory(Arrays.asList(TEST_NEW_CC_DOMAIN_CRC32),
+ Arrays.asList(TEST_NEW_CC_DOMAIN_SHA256), Arrays.asList(TEST_NEW_CC_IP_CRC32),
+ Arrays.asList(TEST_NEW_CC_IP_SHA256));
+ // Ensure old watchlist is not in memory
+ assertFalse(settings.containsDomain(TEST_CC_DOMAIN));
+ assertFalse(settings.containsIp(TEST_CC_IP));
+ assertFalse(settings.containsDomain(TEST_NOT_EXIST_CC_DOMAIN));
+ assertFalse(settings.containsIp(TEST_NOT_EXIST_CC_IP));
+ assertFalse(settings.containsDomain(TEST_SHA256_ONLY_DOMAIN));
+ assertFalse(settings.containsIp(TEST_SHA256_ONLY_IP));
+ assertFalse(settings.containsDomain(TEST_CRC32_ONLY_DOMAIN));
+ assertFalse(settings.containsIp(TEST_CRC32_ONLY_IP));
+ // Ensure new watchlist is in memory
+ assertTrue(settings.containsDomain(TEST_NEW_CC_DOMAIN));
+ assertTrue(settings.containsIp(TEST_NEW_CC_IP));
+ // Reload settings from disk and test again
+ settings = new WatchlistSettings(mTestXmlFile);
+ // Ensure old watchlist is in memory
+ assertTrue(settings.containsDomain(TEST_CC_DOMAIN));
+ assertTrue(settings.containsIp(TEST_CC_IP));
+ assertFalse(settings.containsDomain(TEST_NOT_EXIST_CC_DOMAIN));
+ assertFalse(settings.containsIp(TEST_NOT_EXIST_CC_IP));
+ assertFalse(settings.containsDomain(TEST_SHA256_ONLY_DOMAIN));
+ assertFalse(settings.containsIp(TEST_SHA256_ONLY_IP));
+ assertFalse(settings.containsDomain(TEST_CRC32_ONLY_DOMAIN));
+ assertFalse(settings.containsIp(TEST_CRC32_ONLY_IP));
+ // Ensure new watchlist is not in memory
+ assertFalse(settings.containsDomain(TEST_NEW_CC_DOMAIN));
+ assertFalse(settings.containsIp(TEST_NEW_CC_IP));;
+ }
+
+ private static void copyWatchlistSettingsXml(Context context, String xmlAsset, File outFile)
+ throws IOException {
+ writeToFile(outFile, readAsset(context, xmlAsset));
+
+ }
+
+ private static String readAsset(Context context, String assetPath) throws IOException {
+ final StringBuilder sb = new StringBuilder();
+ try (BufferedReader br = new BufferedReader(
+ new InputStreamReader(
+ context.getResources().getAssets().open(assetPath)))) {
+ String line;
+ while ((line = br.readLine()) != null) {
+ sb.append(line);
+ sb.append(System.lineSeparator());
+ }
+ }
+ return sb.toString();
+ }
+
+ private static void writeToFile(File path, String content)
+ throws IOException {
+ path.getParentFile().mkdirs();
+
+ try (FileWriter writer = new FileWriter(path)) {
+ writer.write(content);
+ }
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/pm/crossprofile/CrossProfileAppsServiceImplTest.java b/services/tests/servicestests/src/com/android/server/pm/crossprofile/CrossProfileAppsServiceImplTest.java
new file mode 100644
index 0000000..880b77e
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/pm/crossprofile/CrossProfileAppsServiceImplTest.java
@@ -0,0 +1,449 @@
+package com.android.server.pm.crossprofile;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static junit.framework.Assert.assertEquals;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.nullable;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+import static org.testng.Assert.assertThrows;
+
+import android.app.AppOpsManager;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManagerInternal;
+import android.content.pm.ResolveInfo;
+import android.os.Bundle;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.platform.test.annotations.Presubmit;
+import android.util.SparseArray;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Build/Install/Run:
+ * bit FrameworksServicesTests:com.android.server.pm.crossprofile.CrossProfileAppsServiceImplTest
+ */
+@Presubmit
+@RunWith(MockitoJUnitRunner.class)
+public class CrossProfileAppsServiceImplTest {
+ private static final String PACKAGE_ONE = "com.one";
+ private static final int PACKAGE_ONE_UID = 1111;
+ private static final ComponentName ACTIVITY_COMPONENT =
+ new ComponentName("com.one", "test");
+
+ private static final String PACKAGE_TWO = "com.two";
+ private static final int PACKAGE_TWO_UID = 2222;
+
+ private static final int PRIMARY_USER = 0;
+ private static final int PROFILE_OF_PRIMARY_USER = 10;
+ private static final int SECONDARY_USER = 11;
+
+ @Mock
+ private Context mContext;
+ @Mock
+ private UserManager mUserManager;
+ @Mock
+ private PackageManager mPackageManager;
+ @Mock
+ private PackageManagerInternal mPackageManagerInternal;
+ @Mock
+ private AppOpsManager mAppOpsManager;
+
+ private TestInjector mTestInjector;
+ private ActivityInfo mActivityInfo;
+ private CrossProfileAppsServiceImpl mCrossProfileAppsServiceImpl;
+
+ private SparseArray<Boolean> mUserEnabled = new SparseArray<>();
+
+ @Before
+ public void initCrossProfileAppsServiceImpl() {
+ mTestInjector = new TestInjector();
+ mCrossProfileAppsServiceImpl = new CrossProfileAppsServiceImpl(mContext, mTestInjector);
+ }
+
+ @Before
+ public void setupEnabledProfiles() {
+ mUserEnabled.put(PRIMARY_USER, true);
+ mUserEnabled.put(PROFILE_OF_PRIMARY_USER, true);
+ mUserEnabled.put(SECONDARY_USER, true);
+
+ when(mUserManager.getEnabledProfileIds(anyInt())).thenAnswer(
+ invocation -> {
+ List<Integer> users = new ArrayList<>();
+ final int targetUser = invocation.getArgument(0);
+ users.add(targetUser);
+
+ int profileUserId = -1;
+ if (targetUser == PRIMARY_USER) {
+ profileUserId = PROFILE_OF_PRIMARY_USER;
+ } else if (targetUser == PROFILE_OF_PRIMARY_USER) {
+ profileUserId = PRIMARY_USER;
+ }
+
+ if (profileUserId != -1 && mUserEnabled.get(profileUserId)) {
+ users.add(profileUserId);
+ }
+ return users.stream().mapToInt(i -> i).toArray();
+ });
+ }
+
+ @Before
+ public void setupCaller() {
+ mTestInjector.setCallingUid(PACKAGE_ONE_UID);
+ mTestInjector.setCallingUserId(PRIMARY_USER);
+ }
+
+ @Before
+ public void setupPackage() throws Exception {
+ // PACKAGE_ONE are installed in all users.
+ mockAppsInstalled(PACKAGE_ONE, PRIMARY_USER, true);
+ mockAppsInstalled(PACKAGE_ONE, PROFILE_OF_PRIMARY_USER, true);
+ mockAppsInstalled(PACKAGE_ONE, SECONDARY_USER, true);
+
+ // Packages are resolved to their corresponding UID.
+ doAnswer(invocation -> {
+ final int uid = invocation.getArgument(0);
+ final String packageName = invocation.getArgument(1);
+ if (uid == PACKAGE_ONE_UID && PACKAGE_ONE.equals(packageName)) {
+ return null;
+ } else if (uid ==PACKAGE_TWO_UID && PACKAGE_TWO.equals(packageName)) {
+ return null;
+ }
+ throw new SecurityException("Not matching");
+ }).when(mAppOpsManager).checkPackage(anyInt(), anyString());
+
+ // The intent is resolved to the ACTIVITY_COMPONENT.
+ mockActivityLaunchIntentResolvedTo(ACTIVITY_COMPONENT);
+ }
+
+ @Test
+ public void getTargetUserProfiles_fromPrimaryUser_installed() throws Exception {
+ List<UserHandle> targetProfiles =
+ mCrossProfileAppsServiceImpl.getTargetUserProfiles(PACKAGE_ONE);
+ assertThat(targetProfiles).containsExactly(UserHandle.of(PROFILE_OF_PRIMARY_USER));
+ }
+
+ @Test
+ public void getTargetUserProfiles_fromPrimaryUser_notInstalled() throws Exception {
+ mockAppsInstalled(PACKAGE_ONE, PROFILE_OF_PRIMARY_USER, false);
+
+ List<UserHandle> targetProfiles =
+ mCrossProfileAppsServiceImpl.getTargetUserProfiles(PACKAGE_ONE);
+ assertThat(targetProfiles).isEmpty();
+ }
+
+ @Test
+ public void getTargetUserProfiles_fromPrimaryUser_userNotEnabled() throws Exception {
+ mUserEnabled.put(PROFILE_OF_PRIMARY_USER, false);
+
+ List<UserHandle> targetProfiles =
+ mCrossProfileAppsServiceImpl.getTargetUserProfiles(PACKAGE_ONE);
+ assertThat(targetProfiles).isEmpty();
+ }
+
+ @Test
+ public void getTargetUserProfiles_fromSecondaryUser() throws Exception {
+ mTestInjector.setCallingUserId(SECONDARY_USER);
+
+ List<UserHandle> targetProfiles =
+ mCrossProfileAppsServiceImpl.getTargetUserProfiles(PACKAGE_ONE);
+ assertThat(targetProfiles).isEmpty();
+ }
+
+ @Test
+ public void getTargetUserProfiles_fromProfile_installed() throws Exception {
+ mTestInjector.setCallingUserId(PROFILE_OF_PRIMARY_USER);
+
+ List<UserHandle> targetProfiles =
+ mCrossProfileAppsServiceImpl.getTargetUserProfiles(PACKAGE_ONE);
+ assertThat(targetProfiles).containsExactly(UserHandle.of(PRIMARY_USER));
+ }
+
+ @Test
+ public void getTargetUserProfiles_fromProfile_notInstalled() throws Exception {
+ mTestInjector.setCallingUserId(PROFILE_OF_PRIMARY_USER);
+ mockAppsInstalled(PACKAGE_ONE, PRIMARY_USER, false);
+
+ List<UserHandle> targetProfiles =
+ mCrossProfileAppsServiceImpl.getTargetUserProfiles(PACKAGE_ONE);
+ assertThat(targetProfiles).isEmpty();
+ }
+
+ @Test(expected = SecurityException.class)
+ public void getTargetUserProfiles_fakeCaller() throws Exception {
+ mCrossProfileAppsServiceImpl.getTargetUserProfiles(PACKAGE_TWO);
+ }
+
+ @Test
+ public void startActivityAsUser_currentUser() throws Exception {
+ assertThrows(
+ SecurityException.class,
+ () ->
+ mCrossProfileAppsServiceImpl.startActivityAsUser(
+ PACKAGE_ONE,
+ ACTIVITY_COMPONENT,
+ null,
+ null,
+ UserHandle.of(PRIMARY_USER)));
+
+ verify(mContext, never())
+ .startActivityAsUser(
+ any(Intent.class),
+ nullable(Bundle.class),
+ any(UserHandle.class));
+ }
+
+ @Test
+ public void startActivityAsUser_profile_successWithOption() throws Exception {
+ Bundle options = Bundle.forPair("test_key", "test_value");
+
+ mCrossProfileAppsServiceImpl.startActivityAsUser(
+ PACKAGE_ONE,
+ ACTIVITY_COMPONENT,
+ null,
+ options,
+ UserHandle.of(PROFILE_OF_PRIMARY_USER));
+
+ ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
+ ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class);
+
+ verify(mContext)
+ .startActivityAsUser(
+ intentCaptor.capture(),
+ bundleCaptor.capture(),
+ eq(UserHandle.of(PROFILE_OF_PRIMARY_USER)));
+
+ Intent intent = intentCaptor.getValue();
+ assertEquals(ACTIVITY_COMPONENT, intent.getComponent());
+
+ Bundle bundle = bundleCaptor.getValue();
+ assertEquals("test_value", bundle.getString("test_key"));
+ }
+
+ @Test
+ public void startActivityAsUser_profile_notInstalled() throws Exception {
+ mockAppsInstalled(PACKAGE_ONE, PROFILE_OF_PRIMARY_USER, false);
+
+ assertThrows(
+ SecurityException.class,
+ () ->
+ mCrossProfileAppsServiceImpl.startActivityAsUser(
+ PACKAGE_ONE,
+ ACTIVITY_COMPONENT,
+ null,
+ null,
+ UserHandle.of(PROFILE_OF_PRIMARY_USER)));
+
+ verify(mContext, never())
+ .startActivityAsUser(
+ any(Intent.class),
+ nullable(Bundle.class),
+ any(UserHandle.class));
+ }
+
+ @Test
+ public void startActivityAsUser_profile_fakeCaller() throws Exception {
+ assertThrows(
+ SecurityException.class,
+ () ->
+ mCrossProfileAppsServiceImpl.startActivityAsUser(
+ PACKAGE_TWO,
+ ACTIVITY_COMPONENT,
+ null,
+ null,
+ UserHandle.of(PROFILE_OF_PRIMARY_USER)));
+
+ verify(mContext, never())
+ .startActivityAsUser(
+ any(Intent.class),
+ nullable(Bundle.class),
+ any(UserHandle.class));
+ }
+
+ @Test
+ public void startActivityAsUser_profile_notExported() throws Exception {
+ mActivityInfo.exported = false;
+
+ assertThrows(
+ SecurityException.class,
+ () ->
+ mCrossProfileAppsServiceImpl.startActivityAsUser(
+ PACKAGE_ONE,
+ ACTIVITY_COMPONENT,
+ null,
+ null,
+ UserHandle.of(PROFILE_OF_PRIMARY_USER)));
+
+ verify(mContext, never())
+ .startActivityAsUser(
+ any(Intent.class),
+ nullable(Bundle.class),
+ any(UserHandle.class));
+ }
+
+ @Test
+ public void startActivityAsUser_profile_anotherPackage() throws Exception {
+ assertThrows(
+ SecurityException.class,
+ () ->
+ mCrossProfileAppsServiceImpl.startActivityAsUser(
+ PACKAGE_ONE,
+ new ComponentName(PACKAGE_TWO, "test"),
+ null,
+ null,
+ UserHandle.of(PROFILE_OF_PRIMARY_USER)));
+
+ verify(mContext, never())
+ .startActivityAsUser(
+ any(Intent.class),
+ nullable(Bundle.class),
+ any(UserHandle.class));
+ }
+
+ @Test
+ public void startActivityAsUser_secondaryUser() throws Exception {
+ assertThrows(
+ SecurityException.class,
+ () ->
+ mCrossProfileAppsServiceImpl.startActivityAsUser(
+ PACKAGE_ONE,
+ ACTIVITY_COMPONENT,
+ null,
+ null,
+ UserHandle.of(SECONDARY_USER)));
+
+ verify(mContext, never())
+ .startActivityAsUser(
+ any(Intent.class),
+ nullable(Bundle.class),
+ any(UserHandle.class));
+ }
+
+ @Test
+ public void startActivityAsUser_fromProfile_success() throws Exception {
+ mTestInjector.setCallingUserId(PROFILE_OF_PRIMARY_USER);
+
+ mCrossProfileAppsServiceImpl.startActivityAsUser(
+ PACKAGE_ONE,
+ ACTIVITY_COMPONENT,
+ null,
+ null,
+ UserHandle.of(PRIMARY_USER));
+
+ verify(mContext)
+ .startActivityAsUser(
+ any(Intent.class),
+ nullable(Bundle.class),
+ eq(UserHandle.of(PRIMARY_USER)));
+ }
+
+ private void mockAppsInstalled(String packageName, int user, boolean installed) {
+ when(mPackageManagerInternal.getPackageInfo(
+ eq(packageName),
+ anyInt(),
+ anyInt(),
+ eq(user)))
+ .thenReturn(installed ? createInstalledPackageInfo() : null);
+ }
+
+ private PackageInfo createInstalledPackageInfo() {
+ PackageInfo packageInfo = new PackageInfo();
+ packageInfo.applicationInfo = new ApplicationInfo();
+ packageInfo.applicationInfo.enabled = true;
+ return packageInfo;
+ }
+
+ private void mockActivityLaunchIntentResolvedTo(ComponentName componentName) {
+ ResolveInfo resolveInfo = new ResolveInfo();
+ ActivityInfo activityInfo = new ActivityInfo();
+ activityInfo.packageName = componentName.getPackageName();
+ activityInfo.name = componentName.getClassName();
+ activityInfo.exported = true;
+ resolveInfo.activityInfo = activityInfo;
+ mActivityInfo = activityInfo;
+
+ when(mPackageManagerInternal.queryIntentActivities(
+ any(Intent.class), anyInt(), anyInt(), anyInt()))
+ .thenReturn(Collections.singletonList(resolveInfo));
+ }
+
+ private class TestInjector implements CrossProfileAppsServiceImpl.Injector {
+ private int mCallingUid;
+ private int mCallingUserId;
+
+ public void setCallingUid(int uid) {
+ mCallingUid = uid;
+ }
+
+ public void setCallingUserId(int userId) {
+ mCallingUserId = userId;
+ }
+
+ @Override
+ public int getCallingUid() {
+ return mCallingUid;
+ }
+
+ @Override
+ public int getCallingUserId() {
+ return mCallingUserId;
+ }
+
+ @Override
+ public UserHandle getCallingUserHandle() {
+ return UserHandle.of(mCallingUserId);
+ }
+
+ @Override
+ public long clearCallingIdentity() {
+ return 0;
+ }
+
+ @Override
+ public void restoreCallingIdentity(long token) {
+ }
+
+ @Override
+ public UserManager getUserManager() {
+ return mUserManager;
+ }
+
+ @Override
+ public PackageManagerInternal getPackageManagerInternal() {
+ return mPackageManagerInternal;
+ }
+
+ @Override
+ public PackageManager getPackageManager() {
+ return mPackageManager;
+ }
+
+ @Override
+ public AppOpsManager getAppOpsManager() {
+ return mAppOpsManager;
+ }
+ }
+}
diff --git a/services/usb/java/com/android/server/usb/UsbAlsaManager.java b/services/usb/java/com/android/server/usb/UsbAlsaManager.java
index 7bea8a1..d359b70 100644
--- a/services/usb/java/com/android/server/usb/UsbAlsaManager.java
+++ b/services/usb/java/com/android/server/usb/UsbAlsaManager.java
@@ -255,7 +255,6 @@
}
private void alsaFileAdded(String name) {
- Slog.i(TAG, "alsaFileAdded(" + name + ")");
int type = AlsaDevice.TYPE_UNKNOWN;
int card = -1, device = -1;
diff --git a/services/usb/java/com/android/server/usb/UsbHostManager.java b/services/usb/java/com/android/server/usb/UsbHostManager.java
index c29a335..095fdc6 100644
--- a/services/usb/java/com/android/server/usb/UsbHostManager.java
+++ b/services/usb/java/com/android/server/usb/UsbHostManager.java
@@ -19,8 +19,13 @@
import android.annotation.Nullable;
import android.content.ComponentName;
import android.content.Context;
+import android.hardware.usb.UsbConfiguration;
import android.hardware.usb.UsbConstants;
import android.hardware.usb.UsbDevice;
+import android.hardware.usb.UsbDeviceConnection;
+import android.hardware.usb.UsbEndpoint;
+import android.hardware.usb.UsbInterface;
+import android.hardware.usb.UsbManager;
import android.os.Bundle;
import android.os.ParcelFileDescriptor;
import android.text.TextUtils;
@@ -32,6 +37,7 @@
import com.android.server.usb.descriptors.report.TextReportCanvas;
import com.android.server.usb.descriptors.tree.UsbDescriptorsTree;
+import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
@@ -44,23 +50,28 @@
private final Context mContext;
+ // contains all connected USB devices
+ private final HashMap<String, UsbDevice> mDevices = new HashMap<>();
+
// USB busses to exclude from USB host support
private final String[] mHostBlacklist;
+ private final Object mLock = new Object();
+
+ private UsbDevice mNewDevice;
+ private UsbConfiguration mNewConfiguration;
+ private UsbInterface mNewInterface;
+ private ArrayList<UsbConfiguration> mNewConfigurations;
+ private ArrayList<UsbInterface> mNewInterfaces;
+ private ArrayList<UsbEndpoint> mNewEndpoints;
+
private final UsbAlsaManager mUsbAlsaManager;
private final UsbSettingsManager mSettingsManager;
- private final Object mLock = new Object();
@GuardedBy("mLock")
- // contains all connected USB devices
- private final HashMap<String, UsbDevice> mDevices = new HashMap<>();
-
- private Object mSettingsLock = new Object();
- @GuardedBy("mSettingsLock")
private UsbProfileGroupSettingsManager mCurrentSettings;
- private Object mHandlerLock = new Object();
- @GuardedBy("mHandlerLock")
+ @GuardedBy("mLock")
private ComponentName mUsbDeviceConnectionHandler;
public UsbHostManager(Context context, UsbAlsaManager alsaManager,
@@ -80,33 +91,33 @@
}
public void setCurrentUserSettings(UsbProfileGroupSettingsManager settings) {
- synchronized (mSettingsLock) {
+ synchronized (mLock) {
mCurrentSettings = settings;
}
}
private UsbProfileGroupSettingsManager getCurrentUserSettings() {
- synchronized (mSettingsLock) {
+ synchronized (mLock) {
return mCurrentSettings;
}
}
public void setUsbDeviceConnectionHandler(@Nullable ComponentName usbDeviceConnectionHandler) {
- synchronized (mHandlerLock) {
+ synchronized (mLock) {
mUsbDeviceConnectionHandler = usbDeviceConnectionHandler;
}
}
private @Nullable ComponentName getUsbDeviceConnectionHandler() {
- synchronized (mHandlerLock) {
+ synchronized (mLock) {
return mUsbDeviceConnectionHandler;
}
}
- private boolean isBlackListed(String deviceAddress) {
+ private boolean isBlackListed(String deviceName) {
int count = mHostBlacklist.length;
for (int i = 0; i < count; i++) {
- if (deviceAddress.startsWith(mHostBlacklist[i])) {
+ if (deviceName.startsWith(mHostBlacklist[i])) {
return true;
}
}
@@ -125,73 +136,166 @@
}
/* Called from JNI in monitorUsbHostBus() to report new USB devices
- Returns true if successful, i.e. the USB Audio device descriptors are
- correctly parsed and the unique device is added to the audio device list.
+ Returns true if successful, in which case the JNI code will continue adding configurations,
+ interfaces and endpoints, and finally call endUsbDeviceAdded after all descriptors
+ have been processed
*/
@SuppressWarnings("unused")
- private boolean usbDeviceAdded(String deviceAddress, int deviceClass, int deviceSubclass,
- byte[] descriptors) {
+ private boolean beginUsbDeviceAdded(String deviceName, int vendorID, int productID,
+ int deviceClass, int deviceSubclass, int deviceProtocol,
+ String manufacturerName, String productName, int version, String serialNumber) {
+
if (DEBUG) {
- Slog.d(TAG, "usbDeviceAdded(" + deviceAddress + ") - start");
+ Slog.d(TAG, "usb:UsbHostManager.beginUsbDeviceAdded(" + deviceName + ")");
+ // Audio Class Codes:
+ // Audio: 0x01
+ // Audio Subclass Codes:
+ // undefined: 0x00
+ // audio control: 0x01
+ // audio streaming: 0x02
+ // midi streaming: 0x03
+
+ // some useful debugging info
+ Slog.d(TAG, "usb: nm:" + deviceName + " vnd:" + vendorID + " prd:" + productID + " cls:"
+ + deviceClass + " sub:" + deviceSubclass + " proto:" + deviceProtocol);
}
- // check class/subclass first as it is more likely to be blacklisted
- if (isBlackListed(deviceClass, deviceSubclass) || isBlackListed(deviceAddress)) {
- if (DEBUG) {
- Slog.d(TAG, "device is black listed");
- }
+ // OK this is non-obvious, but true. One can't tell if the device being attached is even
+ // potentially an audio device without parsing the interface descriptors, so punt on any
+ // such test until endUsbDeviceAdded() when we have that info.
+
+ if (isBlackListed(deviceName) ||
+ isBlackListed(deviceClass, deviceSubclass)) {
return false;
}
synchronized (mLock) {
- if (mDevices.get(deviceAddress) != null) {
- Slog.w(TAG, "device already on mDevices list: " + deviceAddress);
- //TODO If this is the same peripheral as is being connected, replace
- // it with the new connection.
+ if (mDevices.get(deviceName) != null) {
+ Slog.w(TAG, "device already on mDevices list: " + deviceName);
return false;
}
- UsbDescriptorParser parser = new UsbDescriptorParser(deviceAddress);
- if (parser.parseDescriptors(descriptors)) {
-
- UsbDevice newDevice = parser.toAndroidUsbDevice();
- mDevices.put(deviceAddress, newDevice);
-
- // It is fine to call this only for the current user as all broadcasts are sent to
- // all profiles of the user and the dialogs should only show once.
- ComponentName usbDeviceConnectionHandler = getUsbDeviceConnectionHandler();
- if (usbDeviceConnectionHandler == null) {
- getCurrentUserSettings().deviceAttached(newDevice);
- } else {
- getCurrentUserSettings().deviceAttachedForFixedHandler(newDevice,
- usbDeviceConnectionHandler);
- }
-
- // Headset?
- boolean isInputHeadset = parser.isInputHeadset();
- boolean isOutputHeadset = parser.isOutputHeadset();
- Slog.i(TAG, "---- isHeadset[in: " + isInputHeadset
- + " , out: " + isOutputHeadset + "]");
-
- mUsbAlsaManager.usbDeviceAdded(newDevice, isInputHeadset, isOutputHeadset);
- } else {
- Slog.e(TAG, "Error parsing USB device descriptors for " + deviceAddress);
+ if (mNewDevice != null) {
+ Slog.e(TAG, "mNewDevice is not null in endUsbDeviceAdded");
return false;
}
- }
- if (DEBUG) {
- Slog.d(TAG, "beginUsbDeviceAdded(" + deviceAddress + ") end");
+ // Create version string in "%.%" format
+ String versionString = Integer.toString(version >> 8) + "." + (version & 0xFF);
+
+ mNewDevice = new UsbDevice(deviceName, vendorID, productID,
+ deviceClass, deviceSubclass, deviceProtocol,
+ manufacturerName, productName, versionString, serialNumber);
+
+ mNewConfigurations = new ArrayList<>();
+ mNewInterfaces = new ArrayList<>();
+ mNewEndpoints = new ArrayList<>();
}
return true;
}
+ /* Called from JNI in monitorUsbHostBus() to report new USB configuration for the device
+ currently being added. Returns true if successful, false in case of error.
+ */
+ @SuppressWarnings("unused")
+ private void addUsbConfiguration(int id, String name, int attributes, int maxPower) {
+ if (mNewConfiguration != null) {
+ mNewConfiguration.setInterfaces(
+ mNewInterfaces.toArray(new UsbInterface[mNewInterfaces.size()]));
+ mNewInterfaces.clear();
+ }
+
+ mNewConfiguration = new UsbConfiguration(id, name, attributes, maxPower);
+ mNewConfigurations.add(mNewConfiguration);
+ }
+
+ /* Called from JNI in monitorUsbHostBus() to report new USB interface for the device
+ currently being added. Returns true if successful, false in case of error.
+ */
+ @SuppressWarnings("unused")
+ private void addUsbInterface(int id, String name, int altSetting,
+ int Class, int subClass, int protocol) {
+ if (mNewInterface != null) {
+ mNewInterface.setEndpoints(
+ mNewEndpoints.toArray(new UsbEndpoint[mNewEndpoints.size()]));
+ mNewEndpoints.clear();
+ }
+
+ mNewInterface = new UsbInterface(id, altSetting, name, Class, subClass, protocol);
+ mNewInterfaces.add(mNewInterface);
+ }
+
+ /* Called from JNI in monitorUsbHostBus() to report new USB endpoint for the device
+ currently being added. Returns true if successful, false in case of error.
+ */
+ @SuppressWarnings("unused")
+ private void addUsbEndpoint(int address, int attributes, int maxPacketSize, int interval) {
+ mNewEndpoints.add(new UsbEndpoint(address, attributes, maxPacketSize, interval));
+ }
+
+ /* Called from JNI in monitorUsbHostBus() to finish adding a new device */
+ @SuppressWarnings("unused")
+ private void endUsbDeviceAdded() {
+ if (DEBUG) {
+ Slog.d(TAG, "usb:UsbHostManager.endUsbDeviceAdded()");
+ }
+ if (mNewInterface != null) {
+ mNewInterface.setEndpoints(
+ mNewEndpoints.toArray(new UsbEndpoint[mNewEndpoints.size()]));
+ }
+ if (mNewConfiguration != null) {
+ mNewConfiguration.setInterfaces(
+ mNewInterfaces.toArray(new UsbInterface[mNewInterfaces.size()]));
+ }
+
+
+ synchronized (mLock) {
+ if (mNewDevice != null) {
+ mNewDevice.setConfigurations(
+ mNewConfigurations.toArray(
+ new UsbConfiguration[mNewConfigurations.size()]));
+ mDevices.put(mNewDevice.getDeviceName(), mNewDevice);
+ Slog.d(TAG, "Added device " + mNewDevice);
+
+ // It is fine to call this only for the current user as all broadcasts are sent to
+ // all profiles of the user and the dialogs should only show once.
+ ComponentName usbDeviceConnectionHandler = getUsbDeviceConnectionHandler();
+ if (usbDeviceConnectionHandler == null) {
+ getCurrentUserSettings().deviceAttached(mNewDevice);
+ } else {
+ getCurrentUserSettings().deviceAttachedForFixedHandler(mNewDevice,
+ usbDeviceConnectionHandler);
+ }
+ // deviceName is something like: "/dev/bus/usb/001/001"
+ UsbDescriptorParser parser = new UsbDescriptorParser();
+ boolean isInputHeadset = false;
+ boolean isOutputHeadset = false;
+ if (parser.parseDevice(mNewDevice.getDeviceName())) {
+ isInputHeadset = parser.isInputHeadset();
+ isOutputHeadset = parser.isOutputHeadset();
+ Slog.i(TAG, "---- isHeadset[in: " + isInputHeadset
+ + " , out: " + isOutputHeadset + "]");
+ }
+ mUsbAlsaManager.usbDeviceAdded(mNewDevice,
+ isInputHeadset, isOutputHeadset);
+ } else {
+ Slog.e(TAG, "mNewDevice is null in endUsbDeviceAdded");
+ }
+ mNewDevice = null;
+ mNewConfigurations = null;
+ mNewInterfaces = null;
+ mNewEndpoints = null;
+ mNewConfiguration = null;
+ mNewInterface = null;
+ }
+ }
+
/* Called from JNI in monitorUsbHostBus to report USB device removal */
@SuppressWarnings("unused")
- private void usbDeviceRemoved(String deviceAddress) {
+ private void usbDeviceRemoved(String deviceName) {
synchronized (mLock) {
- UsbDevice device = mDevices.remove(deviceAddress);
+ UsbDevice device = mDevices.remove(deviceName);
if (device != null) {
mUsbAlsaManager.usbDeviceRemoved(device);
mSettingsManager.usbDeviceRemoved(device);
@@ -218,37 +322,32 @@
}
}
- /**
- * Opens the specified USB device
- * @hide
- */
- public ParcelFileDescriptor openDevice(String deviceAddress, UsbUserSettingsManager settings) {
+ /* Opens the specified USB device */
+ public ParcelFileDescriptor openDevice(String deviceName, UsbUserSettingsManager settings) {
synchronized (mLock) {
- if (isBlackListed(deviceAddress)) {
+ if (isBlackListed(deviceName)) {
throw new SecurityException("USB device is on a restricted bus");
}
- UsbDevice device = mDevices.get(deviceAddress);
+ UsbDevice device = mDevices.get(deviceName);
if (device == null) {
// if it is not in mDevices, it either does not exist or is blacklisted
throw new IllegalArgumentException(
- "device " + deviceAddress + " does not exist or is restricted");
+ "device " + deviceName + " does not exist or is restricted");
}
settings.checkPermission(device);
- return nativeOpenDevice(deviceAddress);
+ return nativeOpenDevice(deviceName);
}
}
public void dump(IndentingPrintWriter pw) {
- pw.println("USB Host State:");
- synchronized (mHandlerLock) {
- if (mUsbDeviceConnectionHandler != null) {
- pw.println("Default USB Host Connection handler: " + mUsbDeviceConnectionHandler);
- }
- }
synchronized (mLock) {
+ pw.println("USB Host State:");
for (String name : mDevices.keySet()) {
pw.println(" " + name + ": " + mDevices.get(name));
}
+ if (mUsbDeviceConnectionHandler != null) {
+ pw.println("Default USB Host Connection handler: " + mUsbDeviceConnectionHandler);
+ }
Collection<UsbDevice> devices = mDevices.values();
if (devices.size() != 0) {
@@ -256,12 +355,17 @@
for (UsbDevice device : devices) {
StringBuilder stringBuilder = new StringBuilder();
- UsbDescriptorParser parser = new UsbDescriptorParser(device.getDeviceName());
- if (parser.parseDevice()) {
+ UsbDescriptorParser parser = new UsbDescriptorParser();
+ if (parser.parseDevice(device.getDeviceName())) {
UsbDescriptorsTree descriptorTree = new UsbDescriptorsTree();
descriptorTree.parse(parser);
- descriptorTree.report(new TextReportCanvas(parser, stringBuilder));
+ UsbManager usbManager =
+ (UsbManager) mContext.getSystemService(Context.USB_SERVICE);
+ UsbDeviceConnection connection = usbManager.openDevice(device);
+
+ descriptorTree.report(new TextReportCanvas(connection, stringBuilder));
+ connection.close();
stringBuilder.append("isHeadset[in: " + parser.isInputHeadset()
+ " , out: " + parser.isOutputHeadset() + "]");
@@ -277,5 +381,5 @@
}
private native void monitorUsbHostBus();
- private native ParcelFileDescriptor nativeOpenDevice(String deviceAddress);
+ private native ParcelFileDescriptor nativeOpenDevice(String deviceName);
}
diff --git a/services/usb/java/com/android/server/usb/UsbProfileGroupSettingsManager.java b/services/usb/java/com/android/server/usb/UsbProfileGroupSettingsManager.java
index ebb5a62..917e651 100644
--- a/services/usb/java/com/android/server/usb/UsbProfileGroupSettingsManager.java
+++ b/services/usb/java/com/android/server/usb/UsbProfileGroupSettingsManager.java
@@ -32,9 +32,10 @@
import android.content.pm.ResolveInfo;
import android.content.pm.UserInfo;
import android.content.res.XmlResourceParser;
+import android.hardware.usb.AccessoryFilter;
+import android.hardware.usb.DeviceFilter;
import android.hardware.usb.UsbAccessory;
import android.hardware.usb.UsbDevice;
-import android.hardware.usb.UsbInterface;
import android.hardware.usb.UsbManager;
import android.os.AsyncTask;
import android.os.Environment;
@@ -58,7 +59,6 @@
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
-import org.xmlpull.v1.XmlSerializer;
import java.io.File;
import java.io.FileInputStream;
@@ -71,7 +71,6 @@
import java.util.Iterator;
import java.util.List;
import java.util.Map;
-import java.util.Objects;
class UsbProfileGroupSettingsManager {
private static final String TAG = UsbProfileGroupSettingsManager.class.getSimpleName();
@@ -157,404 +156,6 @@
}
}
- // This class is used to describe a USB device.
- // When used in HashMaps all values must be specified,
- // but wildcards can be used for any of the fields in
- // the package meta-data.
- private static class DeviceFilter {
- // USB Vendor ID (or -1 for unspecified)
- public final int mVendorId;
- // USB Product ID (or -1 for unspecified)
- public final int mProductId;
- // USB device or interface class (or -1 for unspecified)
- public final int mClass;
- // USB device subclass (or -1 for unspecified)
- public final int mSubclass;
- // USB device protocol (or -1 for unspecified)
- public final int mProtocol;
- // USB device manufacturer name string (or null for unspecified)
- public final String mManufacturerName;
- // USB device product name string (or null for unspecified)
- public final String mProductName;
- // USB device serial number string (or null for unspecified)
- public final String mSerialNumber;
-
- public DeviceFilter(int vid, int pid, int clasz, int subclass, int protocol,
- String manufacturer, String product, String serialnum) {
- mVendorId = vid;
- mProductId = pid;
- mClass = clasz;
- mSubclass = subclass;
- mProtocol = protocol;
- mManufacturerName = manufacturer;
- mProductName = product;
- mSerialNumber = serialnum;
- }
-
- public DeviceFilter(UsbDevice device) {
- mVendorId = device.getVendorId();
- mProductId = device.getProductId();
- mClass = device.getDeviceClass();
- mSubclass = device.getDeviceSubclass();
- mProtocol = device.getDeviceProtocol();
- mManufacturerName = device.getManufacturerName();
- mProductName = device.getProductName();
- mSerialNumber = device.getSerialNumber();
- }
-
- public static DeviceFilter read(XmlPullParser parser)
- throws XmlPullParserException, IOException {
- int vendorId = -1;
- int productId = -1;
- int deviceClass = -1;
- int deviceSubclass = -1;
- int deviceProtocol = -1;
- String manufacturerName = null;
- String productName = null;
- String serialNumber = null;
-
- int count = parser.getAttributeCount();
- for (int i = 0; i < count; i++) {
- String name = parser.getAttributeName(i);
- String value = parser.getAttributeValue(i);
- // Attribute values are ints or strings
- if ("manufacturer-name".equals(name)) {
- manufacturerName = value;
- } else if ("product-name".equals(name)) {
- productName = value;
- } else if ("serial-number".equals(name)) {
- serialNumber = value;
- } else {
- int intValue;
- int radix = 10;
- if (value != null && value.length() > 2 && value.charAt(0) == '0' &&
- (value.charAt(1) == 'x' || value.charAt(1) == 'X')) {
- // allow hex values starting with 0x or 0X
- radix = 16;
- value = value.substring(2);
- }
- try {
- intValue = Integer.parseInt(value, radix);
- } catch (NumberFormatException e) {
- Slog.e(TAG, "invalid number for field " + name, e);
- continue;
- }
- if ("vendor-id".equals(name)) {
- vendorId = intValue;
- } else if ("product-id".equals(name)) {
- productId = intValue;
- } else if ("class".equals(name)) {
- deviceClass = intValue;
- } else if ("subclass".equals(name)) {
- deviceSubclass = intValue;
- } else if ("protocol".equals(name)) {
- deviceProtocol = intValue;
- }
- }
- }
- return new DeviceFilter(vendorId, productId,
- deviceClass, deviceSubclass, deviceProtocol,
- manufacturerName, productName, serialNumber);
- }
-
- public void write(XmlSerializer serializer) throws IOException {
- serializer.startTag(null, "usb-device");
- if (mVendorId != -1) {
- serializer.attribute(null, "vendor-id", Integer.toString(mVendorId));
- }
- if (mProductId != -1) {
- serializer.attribute(null, "product-id", Integer.toString(mProductId));
- }
- if (mClass != -1) {
- serializer.attribute(null, "class", Integer.toString(mClass));
- }
- if (mSubclass != -1) {
- serializer.attribute(null, "subclass", Integer.toString(mSubclass));
- }
- if (mProtocol != -1) {
- serializer.attribute(null, "protocol", Integer.toString(mProtocol));
- }
- if (mManufacturerName != null) {
- serializer.attribute(null, "manufacturer-name", mManufacturerName);
- }
- if (mProductName != null) {
- serializer.attribute(null, "product-name", mProductName);
- }
- if (mSerialNumber != null) {
- serializer.attribute(null, "serial-number", mSerialNumber);
- }
- serializer.endTag(null, "usb-device");
- }
-
- private boolean matches(int clasz, int subclass, int protocol) {
- return ((mClass == -1 || clasz == mClass) &&
- (mSubclass == -1 || subclass == mSubclass) &&
- (mProtocol == -1 || protocol == mProtocol));
- }
-
- public boolean matches(UsbDevice device) {
- if (mVendorId != -1 && device.getVendorId() != mVendorId) return false;
- if (mProductId != -1 && device.getProductId() != mProductId) return false;
- if (mManufacturerName != null && device.getManufacturerName() == null) return false;
- if (mProductName != null && device.getProductName() == null) return false;
- if (mSerialNumber != null && device.getSerialNumber() == null) return false;
- if (mManufacturerName != null && device.getManufacturerName() != null &&
- !mManufacturerName.equals(device.getManufacturerName())) return false;
- if (mProductName != null && device.getProductName() != null &&
- !mProductName.equals(device.getProductName())) return false;
- if (mSerialNumber != null && device.getSerialNumber() != null &&
- !mSerialNumber.equals(device.getSerialNumber())) return false;
-
- // check device class/subclass/protocol
- if (matches(device.getDeviceClass(), device.getDeviceSubclass(),
- device.getDeviceProtocol())) return true;
-
- // if device doesn't match, check the interfaces
- int count = device.getInterfaceCount();
- for (int i = 0; i < count; i++) {
- UsbInterface intf = device.getInterface(i);
- if (matches(intf.getInterfaceClass(), intf.getInterfaceSubclass(),
- intf.getInterfaceProtocol())) return true;
- }
-
- return false;
- }
-
- /**
- * If the device described by {@code device} covered by this filter?
- *
- * @param device The device
- *
- * @return {@code true} iff this filter covers the {@code device}
- */
- public boolean contains(DeviceFilter device) {
- // -1 and null means "match anything"
-
- if (mVendorId != -1 && device.mVendorId != mVendorId) return false;
- if (mProductId != -1 && device.mProductId != mProductId) return false;
- if (mManufacturerName != null && !Objects.equals(mManufacturerName,
- device.mManufacturerName)) {
- return false;
- }
- if (mProductName != null && !Objects.equals(mProductName, device.mProductName)) {
- return false;
- }
- if (mSerialNumber != null
- && !Objects.equals(mSerialNumber, device.mSerialNumber)) {
- return false;
- }
-
- // check device class/subclass/protocol
- return matches(device.mClass, device.mSubclass, device.mProtocol);
- }
-
- @Override
- public boolean equals(Object obj) {
- // can't compare if we have wildcard strings
- if (mVendorId == -1 || mProductId == -1 ||
- mClass == -1 || mSubclass == -1 || mProtocol == -1) {
- return false;
- }
- if (obj instanceof DeviceFilter) {
- DeviceFilter filter = (DeviceFilter)obj;
-
- if (filter.mVendorId != mVendorId ||
- filter.mProductId != mProductId ||
- filter.mClass != mClass ||
- filter.mSubclass != mSubclass ||
- filter.mProtocol != mProtocol) {
- return(false);
- }
- if ((filter.mManufacturerName != null &&
- mManufacturerName == null) ||
- (filter.mManufacturerName == null &&
- mManufacturerName != null) ||
- (filter.mProductName != null &&
- mProductName == null) ||
- (filter.mProductName == null &&
- mProductName != null) ||
- (filter.mSerialNumber != null &&
- mSerialNumber == null) ||
- (filter.mSerialNumber == null &&
- mSerialNumber != null)) {
- return(false);
- }
- if ((filter.mManufacturerName != null &&
- mManufacturerName != null &&
- !mManufacturerName.equals(filter.mManufacturerName)) ||
- (filter.mProductName != null &&
- mProductName != null &&
- !mProductName.equals(filter.mProductName)) ||
- (filter.mSerialNumber != null &&
- mSerialNumber != null &&
- !mSerialNumber.equals(filter.mSerialNumber))) {
- return false;
- }
- return true;
- }
- if (obj instanceof UsbDevice) {
- UsbDevice device = (UsbDevice)obj;
- if (device.getVendorId() != mVendorId ||
- device.getProductId() != mProductId ||
- device.getDeviceClass() != mClass ||
- device.getDeviceSubclass() != mSubclass ||
- device.getDeviceProtocol() != mProtocol) {
- return(false);
- }
- if ((mManufacturerName != null && device.getManufacturerName() == null) ||
- (mManufacturerName == null && device.getManufacturerName() != null) ||
- (mProductName != null && device.getProductName() == null) ||
- (mProductName == null && device.getProductName() != null) ||
- (mSerialNumber != null && device.getSerialNumber() == null) ||
- (mSerialNumber == null && device.getSerialNumber() != null)) {
- return(false);
- }
- if ((device.getManufacturerName() != null &&
- !mManufacturerName.equals(device.getManufacturerName())) ||
- (device.getProductName() != null &&
- !mProductName.equals(device.getProductName())) ||
- (device.getSerialNumber() != null &&
- !mSerialNumber.equals(device.getSerialNumber()))) {
- return false;
- }
- return true;
- }
- return false;
- }
-
- @Override
- public int hashCode() {
- return (((mVendorId << 16) | mProductId) ^
- ((mClass << 16) | (mSubclass << 8) | mProtocol));
- }
-
- @Override
- public String toString() {
- return "DeviceFilter[mVendorId=" + mVendorId + ",mProductId=" + mProductId +
- ",mClass=" + mClass + ",mSubclass=" + mSubclass +
- ",mProtocol=" + mProtocol + ",mManufacturerName=" + mManufacturerName +
- ",mProductName=" + mProductName + ",mSerialNumber=" + mSerialNumber +
- "]";
- }
- }
-
- // This class is used to describe a USB accessory.
- // When used in HashMaps all values must be specified,
- // but wildcards can be used for any of the fields in
- // the package meta-data.
- private static class AccessoryFilter {
- // USB accessory manufacturer (or null for unspecified)
- public final String mManufacturer;
- // USB accessory model (or null for unspecified)
- public final String mModel;
- // USB accessory version (or null for unspecified)
- public final String mVersion;
-
- public AccessoryFilter(String manufacturer, String model, String version) {
- mManufacturer = manufacturer;
- mModel = model;
- mVersion = version;
- }
-
- public AccessoryFilter(UsbAccessory accessory) {
- mManufacturer = accessory.getManufacturer();
- mModel = accessory.getModel();
- mVersion = accessory.getVersion();
- }
-
- public static AccessoryFilter read(XmlPullParser parser)
- throws XmlPullParserException, IOException {
- String manufacturer = null;
- String model = null;
- String version = null;
-
- int count = parser.getAttributeCount();
- for (int i = 0; i < count; i++) {
- String name = parser.getAttributeName(i);
- String value = parser.getAttributeValue(i);
-
- if ("manufacturer".equals(name)) {
- manufacturer = value;
- } else if ("model".equals(name)) {
- model = value;
- } else if ("version".equals(name)) {
- version = value;
- }
- }
- return new AccessoryFilter(manufacturer, model, version);
- }
-
- public void write(XmlSerializer serializer)throws IOException {
- serializer.startTag(null, "usb-accessory");
- if (mManufacturer != null) {
- serializer.attribute(null, "manufacturer", mManufacturer);
- }
- if (mModel != null) {
- serializer.attribute(null, "model", mModel);
- }
- if (mVersion != null) {
- serializer.attribute(null, "version", mVersion);
- }
- serializer.endTag(null, "usb-accessory");
- }
-
- public boolean matches(UsbAccessory acc) {
- if (mManufacturer != null && !acc.getManufacturer().equals(mManufacturer)) return false;
- if (mModel != null && !acc.getModel().equals(mModel)) return false;
- return !(mVersion != null && !acc.getVersion().equals(mVersion));
- }
-
- /**
- * Is the accessories described {@code accessory} covered by this filter?
- *
- * @param accessory A filter describing the accessory
- *
- * @return {@code true} iff this the filter covers the accessory
- */
- public boolean contains(AccessoryFilter accessory) {
- if (mManufacturer != null && !Objects.equals(accessory.mManufacturer, mManufacturer)) {
- return false;
- }
- if (mModel != null && !Objects.equals(accessory.mModel, mModel)) return false;
- return !(mVersion != null && !Objects.equals(accessory.mVersion, mVersion));
- }
-
- @Override
- public boolean equals(Object obj) {
- // can't compare if we have wildcard strings
- if (mManufacturer == null || mModel == null || mVersion == null) {
- return false;
- }
- if (obj instanceof AccessoryFilter) {
- AccessoryFilter filter = (AccessoryFilter)obj;
- return (mManufacturer.equals(filter.mManufacturer) &&
- mModel.equals(filter.mModel) &&
- mVersion.equals(filter.mVersion));
- }
- if (obj instanceof UsbAccessory) {
- UsbAccessory accessory = (UsbAccessory)obj;
- return (mManufacturer.equals(accessory.getManufacturer()) &&
- mModel.equals(accessory.getModel()) &&
- mVersion.equals(accessory.getVersion()));
- }
- return false;
- }
-
- @Override
- public int hashCode() {
- return ((mManufacturer == null ? 0 : mManufacturer.hashCode()) ^
- (mModel == null ? 0 : mModel.hashCode()) ^
- (mVersion == null ? 0 : mVersion.hashCode()));
- }
-
- @Override
- public String toString() {
- return "AccessoryFilter[mManufacturer=\"" + mManufacturer +
- "\", mModel=\"" + mModel +
- "\", mVersion=\"" + mVersion + "\"]";
- }
- }
-
private class MyPackageMonitor extends PackageMonitor {
@Override
public void onPackageAdded(String packageName, int uid) {
diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbConfigDescriptor.java b/services/usb/java/com/android/server/usb/descriptors/UsbConfigDescriptor.java
index 511e59a..75279c6 100644
--- a/services/usb/java/com/android/server/usb/descriptors/UsbConfigDescriptor.java
+++ b/services/usb/java/com/android/server/usb/descriptors/UsbConfigDescriptor.java
@@ -15,13 +15,8 @@
*/
package com.android.server.usb.descriptors;
-import android.hardware.usb.UsbConfiguration;
-import android.hardware.usb.UsbInterface;
-
import com.android.server.usb.descriptors.report.ReportCanvas;
-import java.util.ArrayList;
-
/**
* @hide
* An USB Config Descriptor.
@@ -40,9 +35,6 @@
// D4..0 Reserved, set to 0.
private byte mMaxPower; // 8:1 Maximum Power Consumption in 2mA units
- private ArrayList<UsbInterfaceDescriptor> mInterfaceDescriptors =
- new ArrayList<UsbInterfaceDescriptor>();
-
UsbConfigDescriptor(int length, byte type) {
super(length, type);
mHierarchyLevel = 2;
@@ -72,22 +64,6 @@
return mMaxPower;
}
- void addInterfaceDescriptor(UsbInterfaceDescriptor interfaceDesc) {
- mInterfaceDescriptors.add(interfaceDesc);
- }
-
- UsbConfiguration toAndroid(UsbDescriptorParser parser) {
- String name = parser.getDescriptorString(mConfigIndex);
- UsbConfiguration config = new
- UsbConfiguration(mConfigValue, name, mAttribs, mMaxPower);
- UsbInterface[] interfaces = new UsbInterface[mInterfaceDescriptors.size()];
- for (int index = 0; index < mInterfaceDescriptors.size(); index++) {
- interfaces[index] = mInterfaceDescriptors.get(index).toAndroid(parser);
- }
- config.setInterfaces(interfaces);
- return config;
- }
-
@Override
public int parseRawDescriptors(ByteStream stream) {
mTotalLength = stream.unpackUsbShort();
diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbDescriptorParser.java b/services/usb/java/com/android/server/usb/descriptors/UsbDescriptorParser.java
index c5052c8..ad7bde5c 100644
--- a/services/usb/java/com/android/server/usb/descriptors/UsbDescriptorParser.java
+++ b/services/usb/java/com/android/server/usb/descriptors/UsbDescriptorParser.java
@@ -15,7 +15,6 @@
*/
package com.android.server.usb.descriptors;
-import android.hardware.usb.UsbDevice;
import android.util.Log;
import java.util.ArrayList;
@@ -26,16 +25,11 @@
*/
public final class UsbDescriptorParser {
private static final String TAG = "UsbDescriptorParser";
- private static final boolean DEBUG = false;
-
- private final String mDeviceAddr;
// Descriptor Objects
- private static final int DESCRIPTORS_ALLOC_SIZE = 128;
private ArrayList<UsbDescriptor> mDescriptors = new ArrayList<UsbDescriptor>();
private UsbDeviceDescriptor mDeviceDescriptor;
- private UsbConfigDescriptor mCurConfigDescriptor;
private UsbInterfaceDescriptor mCurInterfaceDescriptor;
// The AudioClass spec implemented by the AudioClass Interfaces
@@ -43,13 +37,7 @@
// Obtained from the first AudioClass Header descriptor.
private int mACInterfacesSpec = UsbDeviceDescriptor.USBSPEC_1_0;
- public UsbDescriptorParser(String deviceAddr) {
- mDeviceAddr = deviceAddr;
- }
-
- public String getDeviceAddr() {
- return mDeviceAddr;
- }
+ public UsbDescriptorParser() {}
/**
* @return the USB Spec value associated with the Device descriptor for the
@@ -72,18 +60,6 @@
public int getACInterfaceSpec() {
return mACInterfacesSpec;
}
-
- private class UsbDescriptorsStreamFormatException extends Exception {
- String mMessage;
- UsbDescriptorsStreamFormatException(String message) {
- mMessage = message;
- }
-
- public String toString() {
- return "Descriptor Stream Format Exception: " + mMessage;
- }
- }
-
/**
* The probability (as returned by getHeadsetProbability() at which we conclude
* the peripheral is a headset.
@@ -91,8 +67,7 @@
private static final float IN_HEADSET_TRIGGER = 0.75f;
private static final float OUT_HEADSET_TRIGGER = 0.75f;
- private UsbDescriptor allocDescriptor(ByteStream stream)
- throws UsbDescriptorsStreamFormatException {
+ private UsbDescriptor allocDescriptor(ByteStream stream) {
stream.resetReadCount();
int length = stream.getUnsignedByte();
@@ -108,38 +83,15 @@
break;
case UsbDescriptor.DESCRIPTORTYPE_CONFIG:
- descriptor = mCurConfigDescriptor = new UsbConfigDescriptor(length, type);
- if (mDeviceDescriptor != null) {
- mDeviceDescriptor.addConfigDescriptor(mCurConfigDescriptor);
- } else {
- Log.e(TAG, "Config Descriptor found with no associated Device Descriptor!");
- throw new UsbDescriptorsStreamFormatException(
- "Config Descriptor found with no associated Device Descriptor!");
- }
+ descriptor = new UsbConfigDescriptor(length, type);
break;
case UsbDescriptor.DESCRIPTORTYPE_INTERFACE:
descriptor = mCurInterfaceDescriptor = new UsbInterfaceDescriptor(length, type);
- if (mCurConfigDescriptor != null) {
- mCurConfigDescriptor.addInterfaceDescriptor(mCurInterfaceDescriptor);
- } else {
- Log.e(TAG, "Interface Descriptor found with no associated Config Descriptor!");
- throw new UsbDescriptorsStreamFormatException(
- "Interface Descriptor found with no associated Config Descriptor!");
- }
break;
case UsbDescriptor.DESCRIPTORTYPE_ENDPOINT:
descriptor = new UsbEndpointDescriptor(length, type);
- if (mCurInterfaceDescriptor != null) {
- mCurInterfaceDescriptor.addEndpointDescriptor(
- (UsbEndpointDescriptor) descriptor);
- } else {
- Log.e(TAG,
- "Endpoint Descriptor found with no associated Interface Descriptor!");
- throw new UsbDescriptorsStreamFormatException(
- "Endpoint Descriptor found with no associated Interface Descriptor!");
- }
break;
/*
@@ -192,12 +144,8 @@
/**
* @hide
*/
- public boolean parseDescriptors(byte[] descriptors) {
- if (DEBUG) {
- Log.d(TAG, "parseDescriptors() - start");
- }
- // This will allow us to (probably) alloc mDescriptors just once.
- mDescriptors = new ArrayList<UsbDescriptor>(DESCRIPTORS_ALLOC_SIZE);
+ public void parseDescriptors(byte[] descriptors) {
+ mDescriptors.clear();
ByteStream stream = new ByteStream(descriptors);
while (stream.available() > 0) {
@@ -225,36 +173,21 @@
}
}
}
- if (DEBUG) {
- Log.d(TAG, "parseDescriptors() - end " + mDescriptors.size() + " descriptors.");
+ }
+
+ /**
+ * @hide
+ */
+ public boolean parseDevice(String deviceAddr) {
+ byte[] rawDescriptors = getRawDescriptors(deviceAddr);
+ if (rawDescriptors != null) {
+ parseDescriptors(rawDescriptors);
+ return true;
}
- return true;
+ return false;
}
- /**
- * @hide
- */
- public boolean parseDevice() {
- byte[] rawDescriptors = getRawDescriptors();
-
- return rawDescriptors != null
- ? parseDescriptors(rawDescriptors) : false;
- }
-
- private byte[] getRawDescriptors() {
- return getRawDescriptors_native(mDeviceAddr);
- }
-
- private native byte[] getRawDescriptors_native(String deviceAddr);
-
- /**
- * @hide
- */
- public String getDescriptorString(int stringId) {
- return getDescriptorString_native(mDeviceAddr, stringId);
- }
-
- private native String getDescriptorString_native(String deviceAddr, int stringId);
+ private native byte[] getRawDescriptors(String deviceAddr);
public int getParsingSpec() {
return mDeviceDescriptor != null ? mDeviceDescriptor.getSpec() : 0;
@@ -267,17 +200,6 @@
/**
* @hide
*/
- public UsbDevice toAndroidUsbDevice() {
- if (mDeviceDescriptor == null) {
- return null;
- }
-
- return mDeviceDescriptor.toAndroid(this);
- }
-
- /**
- * @hide
- */
public ArrayList<UsbDescriptor> getDescriptors(byte type) {
ArrayList<UsbDescriptor> list = new ArrayList<UsbDescriptor>();
for (UsbDescriptor descriptor : mDescriptors) {
@@ -433,6 +355,8 @@
* to count on the peripheral being a headset.
*/
public boolean isInputHeadset() {
+ // TEMP
+ Log.i(TAG, "---- isInputHeadset() prob:" + (getInputHeadsetProbability() * 100f) + "%");
return getInputHeadsetProbability() >= IN_HEADSET_TRIGGER;
}
@@ -486,6 +410,8 @@
* to count on the peripheral being a headset.
*/
public boolean isOutputHeadset() {
+ // TEMP
+ Log.i(TAG, "---- isOutputHeadset() prob:" + (getOutputHeadsetProbability() * 100f) + "%");
return getOutputHeadsetProbability() >= OUT_HEADSET_TRIGGER;
}
diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbDeviceDescriptor.java b/services/usb/java/com/android/server/usb/descriptors/UsbDeviceDescriptor.java
index c89d9b6..d5cb89e 100644
--- a/services/usb/java/com/android/server/usb/descriptors/UsbDeviceDescriptor.java
+++ b/services/usb/java/com/android/server/usb/descriptors/UsbDeviceDescriptor.java
@@ -15,14 +15,9 @@
*/
package com.android.server.usb.descriptors;
-import android.hardware.usb.UsbConfiguration;
-import android.hardware.usb.UsbDevice;
-
import com.android.server.usb.descriptors.report.ReportCanvas;
import com.android.server.usb.descriptors.report.UsbStrings;
-import java.util.ArrayList;
-
/**
* @hide
* A USB Device Descriptor.
@@ -49,9 +44,6 @@
private byte mSerialNum; // 16:1 Index of Serial Number String Descriptor
private byte mNumConfigs; // 17:1 Number of Possible Configurations
- private ArrayList<UsbConfigDescriptor> mConfigDescriptors =
- new ArrayList<UsbConfigDescriptor>();
-
UsbDeviceDescriptor(int length, byte type) {
super(length, type);
mHierarchyLevel = 1;
@@ -105,35 +97,6 @@
return mNumConfigs;
}
- void addConfigDescriptor(UsbConfigDescriptor config) {
- mConfigDescriptors.add(config);
- }
-
- /**
- * @hide
- */
- public UsbDevice toAndroid(UsbDescriptorParser parser) {
- String mfgName = parser.getDescriptorString(mMfgIndex);
- String prodName = parser.getDescriptorString(mProductIndex);
-
- // Create version string in "%.%" format
- String versionString =
- Integer.toString(mDeviceRelease >> 8) + "." + (mDeviceRelease & 0xFF);
- String serialStr = parser.getDescriptorString(mSerialNum);
-
- UsbDevice device = new UsbDevice(parser.getDeviceAddr(), mVendorID, mProductID,
- mDevClass, mDevSubClass,
- mProtocol, mfgName, prodName,
- versionString, serialStr);
- UsbConfiguration[] configs = new UsbConfiguration[mConfigDescriptors.size()];
- for (int index = 0; index < mConfigDescriptors.size(); index++) {
- configs[index] = mConfigDescriptors.get(index).toAndroid(parser);
- }
- device.setConfigurations(configs);
-
- return device;
- }
-
@Override
public int parseRawDescriptors(ByteStream stream) {
mSpec = stream.unpackUsbShort();
@@ -171,11 +134,12 @@
+ " Product ID: " + ReportCanvas.getHexString(getProductID())
+ " Product Release: " + ReportCanvas.getBCDString(getDeviceRelease()));
- UsbDescriptorParser parser = canvas.getParser();
byte mfgIndex = getMfgIndex();
- String manufacturer = parser.getDescriptorString(mfgIndex);
+ String manufacturer =
+ UsbDescriptor.getUsbDescriptorString(canvas.getConnection(), mfgIndex);
byte productIndex = getProductIndex();
- String product = parser.getDescriptorString(productIndex);
+ String product =
+ UsbDescriptor.getUsbDescriptorString(canvas.getConnection(), productIndex);
canvas.writeListItem("Manufacturer " + mfgIndex + ": " + manufacturer
+ " Product " + productIndex + ": " + product);
diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbEndpointDescriptor.java b/services/usb/java/com/android/server/usb/descriptors/UsbEndpointDescriptor.java
index 9cb8f05..6322fbe 100644
--- a/services/usb/java/com/android/server/usb/descriptors/UsbEndpointDescriptor.java
+++ b/services/usb/java/com/android/server/usb/descriptors/UsbEndpointDescriptor.java
@@ -15,8 +15,6 @@
*/
package com.android.server.usb.descriptors;
-import android.hardware.usb.UsbEndpoint;
-
import com.android.server.usb.descriptors.report.ReportCanvas;
/**
@@ -107,10 +105,6 @@
return mSyncAddress;
}
- /* package */ UsbEndpoint toAndroid(UsbDescriptorParser parser) {
- return new UsbEndpoint(mEndpointAddress, mAttributes, mPacketSize, mInterval);
- }
-
@Override
public int parseRawDescriptors(ByteStream stream) {
mEndpointAddress = stream.getByte();
diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbInterfaceDescriptor.java b/services/usb/java/com/android/server/usb/descriptors/UsbInterfaceDescriptor.java
index 645807d..4eef6ca 100644
--- a/services/usb/java/com/android/server/usb/descriptors/UsbInterfaceDescriptor.java
+++ b/services/usb/java/com/android/server/usb/descriptors/UsbInterfaceDescriptor.java
@@ -15,14 +15,9 @@
*/
package com.android.server.usb.descriptors;
-import android.hardware.usb.UsbEndpoint;
-import android.hardware.usb.UsbInterface;
-
import com.android.server.usb.descriptors.report.ReportCanvas;
import com.android.server.usb.descriptors.report.UsbStrings;
-import java.util.ArrayList;
-
/**
* @hide
* A common super-class for all USB Interface Descritor subtypes.
@@ -39,9 +34,6 @@
protected byte mProtocol; // 7:1 Protocol Code
protected byte mDescrIndex; // 8:1 Index of String Descriptor Describing this interface
- private ArrayList<UsbEndpointDescriptor> mEndpointDescriptors =
- new ArrayList<UsbEndpointDescriptor>();
-
UsbInterfaceDescriptor(int length, byte type) {
super(length, type);
mHierarchyLevel = 3;
@@ -88,22 +80,6 @@
return mDescrIndex;
}
- void addEndpointDescriptor(UsbEndpointDescriptor endpoint) {
- mEndpointDescriptors.add(endpoint);
- }
-
- UsbInterface toAndroid(UsbDescriptorParser parser) {
- String name = parser.getDescriptorString(mDescrIndex);
- UsbInterface ntrface = new UsbInterface(
- mInterfaceNumber, mAlternateSetting, name, mUsbClass, mUsbSubclass, mProtocol);
- UsbEndpoint[] endpoints = new UsbEndpoint[mEndpointDescriptors.size()];
- for (int index = 0; index < mEndpointDescriptors.size(); index++) {
- endpoints[index] = mEndpointDescriptors.get(index).toAndroid(parser);
- }
- ntrface.setEndpoints(endpoints);
- return ntrface;
- }
-
@Override
public void report(ReportCanvas canvas) {
super.report(canvas);
diff --git a/services/usb/java/com/android/server/usb/descriptors/report/HTMLReportCanvas.java b/services/usb/java/com/android/server/usb/descriptors/report/HTMLReportCanvas.java
index adfc514..99ebcca 100644
--- a/services/usb/java/com/android/server/usb/descriptors/report/HTMLReportCanvas.java
+++ b/services/usb/java/com/android/server/usb/descriptors/report/HTMLReportCanvas.java
@@ -15,7 +15,7 @@
*/
package com.android.server.usb.descriptors.report;
-import com.android.server.usb.descriptors.UsbDescriptorParser;
+import android.hardware.usb.UsbDeviceConnection;
/**
* @hide
@@ -32,8 +32,8 @@
* from the USB device.
* @param stringBuilder Generated output gets written into this object.
*/
- public HTMLReportCanvas(UsbDescriptorParser parser, StringBuilder stringBuilder) {
- super(parser);
+ public HTMLReportCanvas(UsbDeviceConnection connection, StringBuilder stringBuilder) {
+ super(connection);
mStringBuilder = stringBuilder;
}
diff --git a/services/usb/java/com/android/server/usb/descriptors/report/ReportCanvas.java b/services/usb/java/com/android/server/usb/descriptors/report/ReportCanvas.java
index c34dc98..9e0adf5 100644
--- a/services/usb/java/com/android/server/usb/descriptors/report/ReportCanvas.java
+++ b/services/usb/java/com/android/server/usb/descriptors/report/ReportCanvas.java
@@ -15,7 +15,7 @@
*/
package com.android.server.usb.descriptors.report;
-import com.android.server.usb.descriptors.UsbDescriptorParser;
+import android.hardware.usb.UsbDeviceConnection;
/**
* @hide
@@ -24,19 +24,22 @@
public abstract class ReportCanvas {
private static final String TAG = "ReportCanvas";
- private final UsbDescriptorParser mParser;
+ private final UsbDeviceConnection mConnection;
/**
* Constructor.
* @param connection The USB connection object used to retrieve strings
* from the USB device.
*/
- public ReportCanvas(UsbDescriptorParser parser) {
- mParser = parser;
+ public ReportCanvas(UsbDeviceConnection connection) {
+ mConnection = connection;
}
- public UsbDescriptorParser getParser() {
- return mParser;
+ /**
+ * @returns the UsbDeviceConnection member (mConnection).
+ */
+ public UsbDeviceConnection getConnection() {
+ return mConnection;
}
/**
diff --git a/services/usb/java/com/android/server/usb/descriptors/report/TextReportCanvas.java b/services/usb/java/com/android/server/usb/descriptors/report/TextReportCanvas.java
index 1e19ea1..a43569d 100644
--- a/services/usb/java/com/android/server/usb/descriptors/report/TextReportCanvas.java
+++ b/services/usb/java/com/android/server/usb/descriptors/report/TextReportCanvas.java
@@ -15,7 +15,7 @@
*/
package com.android.server.usb.descriptors.report;
-import com.android.server.usb.descriptors.UsbDescriptorParser;
+import android.hardware.usb.UsbDeviceConnection;
/**
* @hide
@@ -34,8 +34,8 @@
* from the USB device.
* @param stringBuilder Generated output gets written into this object.
*/
- public TextReportCanvas(UsbDescriptorParser parser, StringBuilder stringBuilder) {
- super(parser);
+ public TextReportCanvas(UsbDeviceConnection connection, StringBuilder stringBuilder) {
+ super(connection);
mStringBuilder = stringBuilder;
}
diff --git a/telecomm/java/android/telecom/CallAudioState.java b/telecomm/java/android/telecom/CallAudioState.java
index f601d8b..4b827d2 100644
--- a/telecomm/java/android/telecom/CallAudioState.java
+++ b/telecomm/java/android/telecom/CallAudioState.java
@@ -16,16 +16,35 @@
package android.telecom;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.bluetooth.BluetoothDevice;
import android.os.Parcel;
import android.os.Parcelable;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
import java.util.Locale;
+import java.util.Objects;
+import java.util.stream.Collectors;
/**
* Encapsulates the telecom audio state, including the current audio routing, supported audio
* routing and mute.
*/
public final class CallAudioState implements Parcelable {
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(value={ROUTE_EARPIECE, ROUTE_BLUETOOTH, ROUTE_WIRED_HEADSET, ROUTE_SPEAKER},
+ flag=true)
+ public @interface CallAudioRoute {}
+
/** Direct the audio stream through the device's earpiece. */
public static final int ROUTE_EARPIECE = 0x00000001;
@@ -55,6 +74,8 @@
private final boolean isMuted;
private final int route;
private final int supportedRouteMask;
+ private final BluetoothDevice activeBluetoothDevice;
+ private final Collection<BluetoothDevice> supportedBluetoothDevices;
/**
* Constructor for a {@link CallAudioState} object.
@@ -73,10 +94,21 @@
* {@link #ROUTE_WIRED_HEADSET}
* {@link #ROUTE_SPEAKER}
*/
- public CallAudioState(boolean muted, int route, int supportedRouteMask) {
- this.isMuted = muted;
+ public CallAudioState(boolean muted, @CallAudioRoute int route,
+ @CallAudioRoute int supportedRouteMask) {
+ this(muted, route, supportedRouteMask, null, Collections.emptyList());
+ }
+
+ /** @hide */
+ public CallAudioState(boolean isMuted, @CallAudioRoute int route,
+ @CallAudioRoute int supportedRouteMask,
+ @Nullable BluetoothDevice activeBluetoothDevice,
+ @NonNull Collection<BluetoothDevice> supportedBluetoothDevices) {
+ this.isMuted = isMuted;
this.route = route;
this.supportedRouteMask = supportedRouteMask;
+ this.activeBluetoothDevice = activeBluetoothDevice;
+ this.supportedBluetoothDevices = supportedBluetoothDevices;
}
/** @hide */
@@ -84,6 +116,8 @@
isMuted = state.isMuted();
route = state.getRoute();
supportedRouteMask = state.getSupportedRouteMask();
+ activeBluetoothDevice = state.activeBluetoothDevice;
+ supportedBluetoothDevices = state.getSupportedBluetoothDevices();
}
/** @hide */
@@ -92,6 +126,8 @@
isMuted = state.isMuted();
route = state.getRoute();
supportedRouteMask = state.getSupportedRouteMask();
+ activeBluetoothDevice = null;
+ supportedBluetoothDevices = Collections.emptyList();
}
@Override
@@ -103,17 +139,32 @@
return false;
}
CallAudioState state = (CallAudioState) obj;
- return isMuted() == state.isMuted() && getRoute() == state.getRoute() &&
- getSupportedRouteMask() == state.getSupportedRouteMask();
+ if (supportedBluetoothDevices.size() != state.supportedBluetoothDevices.size()) {
+ return false;
+ }
+ for (BluetoothDevice device : supportedBluetoothDevices) {
+ if (!state.supportedBluetoothDevices.contains(device)) {
+ return false;
+ }
+ }
+ return Objects.equals(activeBluetoothDevice, state.activeBluetoothDevice) && isMuted() ==
+ state.isMuted() && getRoute() == state.getRoute() && getSupportedRouteMask() ==
+ state.getSupportedRouteMask();
}
@Override
public String toString() {
+ String bluetoothDeviceList = supportedBluetoothDevices.stream()
+ .map(BluetoothDevice::getAddress).collect(Collectors.joining(", "));
+
return String.format(Locale.US,
- "[AudioState isMuted: %b, route: %s, supportedRouteMask: %s]",
+ "[AudioState isMuted: %b, route: %s, supportedRouteMask: %s, " +
+ "activeBluetoothDevice: [%s], supportedBluetoothDevices: [%s]]",
isMuted,
audioRouteToString(route),
- audioRouteToString(supportedRouteMask));
+ audioRouteToString(supportedRouteMask),
+ activeBluetoothDevice,
+ bluetoothDeviceList);
}
/**
@@ -126,6 +177,7 @@
/**
* @return The current audio route being used.
*/
+ @CallAudioRoute
public int getRoute() {
return route;
}
@@ -133,11 +185,27 @@
/**
* @return Bit mask of all routes supported by this call.
*/
+ @CallAudioRoute
public int getSupportedRouteMask() {
return supportedRouteMask;
}
/**
+ * @return The {@link BluetoothDevice} through which audio is being routed.
+ * Will not be {@code null} if {@link #getRoute()} returns {@link #ROUTE_BLUETOOTH}.
+ */
+ public BluetoothDevice getActiveBluetoothDevice() {
+ return activeBluetoothDevice;
+ }
+
+ /**
+ * @return {@link List} of {@link BluetoothDevice}s that can be used for this call.
+ */
+ public Collection<BluetoothDevice> getSupportedBluetoothDevices() {
+ return supportedBluetoothDevices;
+ }
+
+ /**
* Converts the provided audio route into a human readable string representation.
*
* @param route to convert into a string.
@@ -177,7 +245,13 @@
boolean isMuted = source.readByte() == 0 ? false : true;
int route = source.readInt();
int supportedRouteMask = source.readInt();
- return new CallAudioState(isMuted, route, supportedRouteMask);
+ BluetoothDevice activeBluetoothDevice = source.readParcelable(
+ ClassLoader.getSystemClassLoader());
+ List<BluetoothDevice> supportedBluetoothDevices = new ArrayList<>();
+ source.readParcelableList(supportedBluetoothDevices,
+ ClassLoader.getSystemClassLoader());
+ return new CallAudioState(isMuted, route,
+ supportedRouteMask, activeBluetoothDevice, supportedBluetoothDevices);
}
@Override
@@ -202,6 +276,8 @@
destination.writeByte((byte) (isMuted ? 1 : 0));
destination.writeInt(route);
destination.writeInt(supportedRouteMask);
+ destination.writeParcelable(activeBluetoothDevice, 0);
+ destination.writeParcelableList(new ArrayList<>(supportedBluetoothDevices), 0);
}
private static void listAppend(StringBuffer buffer, String str) {
diff --git a/telecomm/java/android/telecom/Connection.java b/telecomm/java/android/telecom/Connection.java
index 8ba934c..ffb5e93 100644
--- a/telecomm/java/android/telecom/Connection.java
+++ b/telecomm/java/android/telecom/Connection.java
@@ -25,6 +25,7 @@
import android.annotation.SystemApi;
import android.annotation.TestApi;
import android.app.Notification;
+import android.bluetooth.BluetoothDevice;
import android.content.Intent;
import android.hardware.camera2.CameraManager;
import android.net.Uri;
@@ -819,7 +820,7 @@
public void onConnectionEvent(Connection c, String event, Bundle extras) {}
/** @hide */
public void onConferenceSupportedChanged(Connection c, boolean isConferenceSupported) {}
- public void onAudioRouteChanged(Connection c, int audioRoute) {}
+ public void onAudioRouteChanged(Connection c, int audioRoute, String bluetoothAddress) {}
public void onRttInitiationSuccess(Connection c) {}
public void onRttInitiationFailure(Connection c, int reason) {}
public void onRttSessionRemotelyTerminated(Connection c) {}
@@ -2576,7 +2577,29 @@
*/
public final void setAudioRoute(int route) {
for (Listener l : mListeners) {
- l.onAudioRouteChanged(this, route);
+ l.onAudioRouteChanged(this, route, null);
+ }
+ }
+
+ /**
+ *
+ * Request audio routing to a specific bluetooth device. Calling this method may result in
+ * the device routing audio to a different bluetooth device than the one specified if the
+ * bluetooth stack is unable to route audio to the requested device.
+ * A list of available devices can be obtained via
+ * {@link CallAudioState#getSupportedBluetoothDevices()}
+ *
+ * <p>
+ * Used by self-managed {@link ConnectionService}s which wish to use bluetooth audio for a
+ * self-managed {@link Connection} (see {@link PhoneAccount#CAPABILITY_SELF_MANAGED}.)
+ * <p>
+ * See also {@link InCallService#requestBluetoothAudio(String)}
+ * @param bluetoothAddress The address of the bluetooth device to connect to, as returned by
+ * {@link BluetoothDevice#getAddress()}.
+ */
+ public void requestBluetoothAudio(@NonNull String bluetoothAddress) {
+ for (Listener l : mListeners) {
+ l.onAudioRouteChanged(this, CallAudioState.ROUTE_BLUETOOTH, bluetoothAddress);
}
}
diff --git a/telecomm/java/android/telecom/ConnectionService.java b/telecomm/java/android/telecom/ConnectionService.java
index a81fba9..35804f6 100644
--- a/telecomm/java/android/telecom/ConnectionService.java
+++ b/telecomm/java/android/telecom/ConnectionService.java
@@ -1294,10 +1294,10 @@
}
@Override
- public void onAudioRouteChanged(Connection c, int audioRoute) {
+ public void onAudioRouteChanged(Connection c, int audioRoute, String bluetoothAddress) {
String id = mIdByConnection.get(c);
if (id != null) {
- mAdapter.setAudioRoute(id, audioRoute);
+ mAdapter.setAudioRoute(id, audioRoute, bluetoothAddress);
}
}
diff --git a/telecomm/java/android/telecom/ConnectionServiceAdapter.java b/telecomm/java/android/telecom/ConnectionServiceAdapter.java
index 111fcc7..92a9dc2 100644
--- a/telecomm/java/android/telecom/ConnectionServiceAdapter.java
+++ b/telecomm/java/android/telecom/ConnectionServiceAdapter.java
@@ -520,11 +520,14 @@
* @param callId The unique ID of the call.
* @param audioRoute The new audio route (see {@code CallAudioState#ROUTE_*}).
*/
- void setAudioRoute(String callId, int audioRoute) {
- Log.v(this, "setAudioRoute: %s %s", callId, CallAudioState.audioRouteToString(audioRoute));
+ void setAudioRoute(String callId, int audioRoute, String bluetoothAddress) {
+ Log.v(this, "setAudioRoute: %s %s %s", callId,
+ CallAudioState.audioRouteToString(audioRoute),
+ bluetoothAddress);
for (IConnectionServiceAdapter adapter : mAdapters) {
try {
- adapter.setAudioRoute(callId, audioRoute, Log.getExternalSession());
+ adapter.setAudioRoute(callId, audioRoute,
+ bluetoothAddress, Log.getExternalSession());
} catch (RemoteException ignored) {
}
}
diff --git a/telecomm/java/android/telecom/ConnectionServiceAdapterServant.java b/telecomm/java/android/telecom/ConnectionServiceAdapterServant.java
index b1617f4..3fbdeb1 100644
--- a/telecomm/java/android/telecom/ConnectionServiceAdapterServant.java
+++ b/telecomm/java/android/telecom/ConnectionServiceAdapterServant.java
@@ -298,8 +298,8 @@
case MSG_SET_AUDIO_ROUTE: {
SomeArgs args = (SomeArgs) msg.obj;
try {
- mDelegate.setAudioRoute((String) args.arg1, args.argi1,
- (Session.Info) args.arg2);
+ mDelegate.setAudioRoute((String) args.arg1, args.argi1, (String) args.arg2,
+ (Session.Info) args.arg3);
} finally {
args.recycle();
}
@@ -548,12 +548,12 @@
@Override
public final void setAudioRoute(String connectionId, int audioRoute,
- Session.Info sessionInfo) {
-
+ String bluetoothAddress, Session.Info sessionInfo) {
SomeArgs args = SomeArgs.obtain();
args.arg1 = connectionId;
args.argi1 = audioRoute;
- args.arg2 = sessionInfo;
+ args.arg2 = bluetoothAddress;
+ args.arg3 = sessionInfo;
mHandler.obtainMessage(MSG_SET_AUDIO_ROUTE, args).sendToTarget();
}
diff --git a/telecomm/java/android/telecom/InCallAdapter.java b/telecomm/java/android/telecom/InCallAdapter.java
index 9559a28..9bf0467 100644
--- a/telecomm/java/android/telecom/InCallAdapter.java
+++ b/telecomm/java/android/telecom/InCallAdapter.java
@@ -16,6 +16,7 @@
package android.telecom;
+import android.bluetooth.BluetoothDevice;
import android.os.Bundle;
import android.os.RemoteException;
@@ -128,7 +129,22 @@
*/
public void setAudioRoute(int route) {
try {
- mAdapter.setAudioRoute(route);
+ mAdapter.setAudioRoute(route, null);
+ } catch (RemoteException e) {
+ }
+ }
+
+ /**
+ * Request audio routing to a specific bluetooth device. Calling this method may result in
+ * the device routing audio to a different bluetooth device than the one specified. A list of
+ * available devices can be obtained via {@link CallAudioState#getSupportedBluetoothDevices()}
+ *
+ * @param bluetoothAddress The address of the bluetooth device to connect to, as returned by
+ * {@link BluetoothDevice#getAddress()}, or {@code null} if no device is preferred.
+ */
+ public void requestBluetoothAudio(String bluetoothAddress) {
+ try {
+ mAdapter.setAudioRoute(CallAudioState.ROUTE_BLUETOOTH, bluetoothAddress);
} catch (RemoteException e) {
}
}
diff --git a/telecomm/java/android/telecom/InCallService.java b/telecomm/java/android/telecom/InCallService.java
index e384d46..d558bba 100644
--- a/telecomm/java/android/telecom/InCallService.java
+++ b/telecomm/java/android/telecom/InCallService.java
@@ -16,9 +16,11 @@
package android.telecom;
+import android.annotation.NonNull;
import android.annotation.SdkConstant;
import android.annotation.SystemApi;
import android.app.Service;
+import android.bluetooth.BluetoothDevice;
import android.content.Intent;
import android.hardware.camera2.CameraManager;
import android.net.Uri;
@@ -377,6 +379,22 @@
}
/**
+ * Request audio routing to a specific bluetooth device. Calling this method may result in
+ * the device routing audio to a different bluetooth device than the one specified if the
+ * bluetooth stack is unable to route audio to the requested device.
+ * A list of available devices can be obtained via
+ * {@link CallAudioState#getSupportedBluetoothDevices()}
+ *
+ * @param bluetoothAddress The address of the bluetooth device to connect to, as returned by
+ * {@link BluetoothDevice#getAddress()}.
+ */
+ public final void requestBluetoothAudio(@NonNull String bluetoothAddress) {
+ if (mPhone != null) {
+ mPhone.requestBluetoothAudio(bluetoothAddress);
+ }
+ }
+
+ /**
* Invoked when the {@code Phone} has been created. This is a signal to the in-call experience
* to start displaying in-call information to the user. Each instance of {@code InCallService}
* will have only one {@code Phone}, and this method will be called exactly once in the lifetime
diff --git a/telecomm/java/android/telecom/Phone.java b/telecomm/java/android/telecom/Phone.java
index 066f6c2..421b1a4 100644
--- a/telecomm/java/android/telecom/Phone.java
+++ b/telecomm/java/android/telecom/Phone.java
@@ -17,7 +17,9 @@
package android.telecom;
import android.annotation.SystemApi;
+import android.bluetooth.BluetoothDevice;
import android.os.Bundle;
+import android.os.RemoteException;
import android.util.ArrayMap;
import java.util.Collections;
@@ -295,6 +297,18 @@
}
/**
+ * Request audio routing to a specific bluetooth device. Calling this method may result in
+ * the device routing audio to a different bluetooth device than the one specified. A list of
+ * available devices can be obtained via {@link CallAudioState#getSupportedBluetoothDevices()}
+ *
+ * @param bluetoothAddress The address of the bluetooth device to connect to, as returned by
+ * {@link BluetoothDevice#getAddress()}, or {@code null} if no device is preferred.
+ */
+ public void requestBluetoothAudio(String bluetoothAddress) {
+ mInCallAdapter.requestBluetoothAudio(bluetoothAddress);
+ }
+
+ /**
* Turns the proximity sensor on. When this request is made, the proximity sensor will
* become active, and the touch screen and display will be turned off when the user's face
* is detected to be in close proximity to the screen. This operation is a no-op on devices
diff --git a/telecomm/java/android/telecom/RemoteConnectionService.java b/telecomm/java/android/telecom/RemoteConnectionService.java
index 2cc4314..85906ad 100644
--- a/telecomm/java/android/telecom/RemoteConnectionService.java
+++ b/telecomm/java/android/telecom/RemoteConnectionService.java
@@ -398,7 +398,8 @@
}
@Override
- public void setAudioRoute(String callId, int audioRoute, Session.Info sessionInfo) {
+ public void setAudioRoute(String callId, int audioRoute, String bluetoothAddress,
+ Session.Info sessionInfo) {
if (hasConnection(callId)) {
// TODO(3pcalls): handle this for remote connections.
// Likely we don't want to do anything since it doesn't make sense for self-managed
diff --git a/telecomm/java/com/android/internal/telecom/IConnectionServiceAdapter.aidl b/telecomm/java/com/android/internal/telecom/IConnectionServiceAdapter.aidl
index d20da18..da2015f 100644
--- a/telecomm/java/com/android/internal/telecom/IConnectionServiceAdapter.aidl
+++ b/telecomm/java/com/android/internal/telecom/IConnectionServiceAdapter.aidl
@@ -103,7 +103,8 @@
void removeExtras(String callId, in List<String> keys, in Session.Info sessionInfo);
- void setAudioRoute(String callId, int audioRoute, in Session.Info sessionInfo);
+ void setAudioRoute(String callId, int audioRoute, String bluetoothAddress,
+ in Session.Info sessionInfo);
void onConnectionEvent(String callId, String event, in Bundle extras,
in Session.Info sessionInfo);
diff --git a/telecomm/java/com/android/internal/telecom/IInCallAdapter.aidl b/telecomm/java/com/android/internal/telecom/IInCallAdapter.aidl
index 73fa29a..bce3392 100644
--- a/telecomm/java/com/android/internal/telecom/IInCallAdapter.aidl
+++ b/telecomm/java/com/android/internal/telecom/IInCallAdapter.aidl
@@ -39,7 +39,7 @@
void mute(boolean shouldMute);
- void setAudioRoute(int route);
+ void setAudioRoute(int route, String bluetoothAddress);
void playDtmfTone(String callId, char digit);
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 061e55a..0030ab6 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -1650,6 +1650,26 @@
public static final String KEY_IDENTIFY_HIGH_DEFINITION_CALLS_IN_CALL_LOG_BOOL =
"identify_high_definition_calls_in_call_log_bool";
+ /**
+ * Flag specifying whether to use the {@link ServiceState} roaming status, which can be
+ * affected by other carrier configs (e.g.
+ * {@link #KEY_GSM_NONROAMING_NETWORKS_STRING_ARRAY}), when setting the SPN display.
+ * <p>
+ * If {@code true}, the SPN display uses {@link ServiceState#getRoaming}.
+ * If {@code false} the SPN display checks if the current MCC/MNC is different from the
+ * SIM card's MCC/MNC.
+ *
+ * @see KEY_GSM_ROAMING_NETWORKS_STRING_ARRAY
+ * @see KEY_GSM_NONROAMING_NETWORKS_STRING_ARRAY
+ * @see KEY_NON_ROAMING_OPERATOR_STRING_ARRAY
+ * @see KEY_ROAMING_OPERATOR_STRING_ARRAY
+ * @see KEY_FORCE_HOME_NETWORK_BOOL
+ *
+ * @hide
+ */
+ public static final String KEY_SPN_DISPLAY_RULE_USE_ROAMING_FROM_SERVICE_STATE_BOOL =
+ "spn_display_rule_use_roaming_from_service_state_bool";
+
/** The default value for every variable. */
private final static PersistableBundle sDefaults;
@@ -1928,6 +1948,7 @@
sDefaults.putBoolean(KEY_DISABLE_CHARGE_INDICATION_BOOL, false);
sDefaults.putStringArray(KEY_FEATURE_ACCESS_CODES_STRING_ARRAY, null);
sDefaults.putBoolean(KEY_IDENTIFY_HIGH_DEFINITION_CALLS_IN_CALL_LOG_BOOL, false);
+ sDefaults.putBoolean(KEY_SPN_DISPLAY_RULE_USE_ROAMING_FROM_SERVICE_STATE_BOOL, false);
}
/**
diff --git a/telephony/java/android/telephony/PhoneStateListener.java b/telephony/java/android/telephony/PhoneStateListener.java
index afff6d5..9ccfa94 100644
--- a/telephony/java/android/telephony/PhoneStateListener.java
+++ b/telephony/java/android/telephony/PhoneStateListener.java
@@ -204,16 +204,6 @@
public static final int LISTEN_VOLTE_STATE = 0x00004000;
/**
- * Listen for OEM hook raw event
- *
- * @see #onOemHookRawEvent
- * @hide
- * @deprecated OEM needs a vendor-extension hal and their apps should use that instead
- */
- @Deprecated
- public static final int LISTEN_OEM_HOOK_RAW_EVENT = 0x00008000;
-
- /**
* Listen for carrier network changes indicated by a carrier app.
*
* @see #onCarrierNetworkRequest
@@ -359,9 +349,6 @@
case LISTEN_DATA_ACTIVATION_STATE:
PhoneStateListener.this.onDataActivationStateChanged((int)msg.obj);
break;
- case LISTEN_OEM_HOOK_RAW_EVENT:
- PhoneStateListener.this.onOemHookRawEvent((byte[])msg.obj);
- break;
case LISTEN_CARRIER_NETWORK_CHANGE:
PhoneStateListener.this.onCarrierNetworkChange((boolean)msg.obj);
break;
@@ -556,16 +543,6 @@
}
/**
- * Callback invoked when OEM hook raw event is received. Requires
- * the READ_PRIVILEGED_PHONE_STATE permission.
- * @param rawData is the byte array of the OEM hook raw data.
- * @hide
- */
- public void onOemHookRawEvent(byte[] rawData) {
- // default implementation empty
- }
-
- /**
* Callback invoked when telephony has received notice from a carrier
* app that a network action that could result in connectivity loss
* has been requested by an app using
@@ -677,10 +654,6 @@
send(LISTEN_DATA_ACTIVATION_STATE, 0, 0, activationState);
}
- public void onOemHookRawEvent(byte[] rawData) {
- send(LISTEN_OEM_HOOK_RAW_EVENT, 0, 0, rawData);
- }
-
public void onCarrierNetworkChange(boolean active) {
send(LISTEN_CARRIER_NETWORK_CHANGE, 0, 0, active);
}
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 42c3de5..4ffb3c3 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -5709,29 +5709,6 @@
return retVal;
}
- /**
- * Returns the result and response from RIL for oem request
- *
- * @param oemReq the data is sent to ril.
- * @param oemResp the respose data from RIL.
- * @return negative value request was not handled or get error
- * 0 request was handled succesfully, but no response data
- * positive value success, data length of response
- * @hide
- * @deprecated OEM needs a vendor-extension hal and their apps should use that instead
- */
- @Deprecated
- public int invokeOemRilRequestRaw(byte[] oemReq, byte[] oemResp) {
- try {
- ITelephony telephony = getITelephony();
- if (telephony != null)
- return telephony.invokeOemRilRequestRaw(oemReq, oemResp);
- } catch (RemoteException ex) {
- } catch (NullPointerException ex) {
- }
- return -1;
- }
-
/** @hide */
@SystemApi
@RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
diff --git a/telephony/java/android/telephony/euicc/DownloadableSubscription.java b/telephony/java/android/telephony/euicc/DownloadableSubscription.java
index b5484e34..01041c8 100644
--- a/telephony/java/android/telephony/euicc/DownloadableSubscription.java
+++ b/telephony/java/android/telephony/euicc/DownloadableSubscription.java
@@ -53,6 +53,8 @@
@Nullable
public final String encodedActivationCode;
+ @Nullable private String confirmationCode;
+
// see getCarrierName and setCarrierName
@Nullable
private String carrierName;
@@ -66,6 +68,7 @@
private DownloadableSubscription(Parcel in) {
encodedActivationCode = in.readString();
+ confirmationCode = in.readString();
carrierName = in.readString();
accessRules = in.createTypedArray(UiccAccessRule.CREATOR);
}
@@ -83,6 +86,21 @@
}
/**
+ * Sets the confirmation code.
+ */
+ public void setConfirmationCode(String confirmationCode) {
+ this.confirmationCode = confirmationCode;
+ }
+
+ /**
+ * Returns the confirmation code.
+ */
+ @Nullable
+ public String getConfirmationCode() {
+ return confirmationCode;
+ }
+
+ /**
* Set the user-visible carrier name.
* @hide
*
@@ -134,6 +152,7 @@
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(encodedActivationCode);
+ dest.writeString(confirmationCode);
dest.writeString(carrierName);
dest.writeTypedArray(accessRules, flags);
}
diff --git a/telephony/java/com/android/internal/telephony/IPhoneStateListener.aidl b/telephony/java/com/android/internal/telephony/IPhoneStateListener.aidl
index e9c5461..ac16139 100644
--- a/telephony/java/com/android/internal/telephony/IPhoneStateListener.aidl
+++ b/telephony/java/com/android/internal/telephony/IPhoneStateListener.aidl
@@ -45,7 +45,6 @@
void onVoLteServiceStateChanged(in VoLteServiceState lteState);
void onVoiceActivationStateChanged(int activationState);
void onDataActivationStateChanged(int activationState);
- void onOemHookRawEvent(in byte[] rawData);
void onCarrierNetworkChange(in boolean active);
}
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index 2ac11b5..3cc9bde 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -1000,17 +1000,6 @@
in List<String> cdmaNonRoamingList);
/**
- * Returns the result and response from RIL for oem request
- *
- * @param oemReq the data is sent to ril.
- * @param oemResp the respose data from RIL.
- * @return negative value request was not handled or get error
- * 0 request was handled succesfully, but no response data
- * positive value success, data length of response
- */
- int invokeOemRilRequestRaw(in byte[] oemReq, out byte[] oemResp);
-
- /**
* Check if any mobile Radios need to be shutdown.
*
* @return true is any mobile radio needs to be shutdown
diff --git a/telephony/java/com/android/internal/telephony/ITelephonyRegistry.aidl b/telephony/java/com/android/internal/telephony/ITelephonyRegistry.aidl
index 2c2206c..75d8f3f 100644
--- a/telephony/java/com/android/internal/telephony/ITelephonyRegistry.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephonyRegistry.aidl
@@ -67,7 +67,6 @@
void notifyVoLteServiceStateChanged(in VoLteServiceState lteState);
void notifySimActivationStateChangedForPhoneId(in int phoneId, in int subId,
int activationState, int activationType);
- void notifyOemHookRawEventForSubscriber(in int subId, in byte[] rawData);
void notifySubscriptionInfoChanged();
void notifyCarrierNetworkChange(in boolean active);
}
diff --git a/tests/FeatureSplit/feature1/Android.mk b/tests/FeatureSplit/feature1/Android.mk
index b3ea97b..e6ba5c2 100644
--- a/tests/FeatureSplit/feature1/Android.mk
+++ b/tests/FeatureSplit/feature1/Android.mk
@@ -26,6 +26,6 @@
LOCAL_RES_LIBRARIES := FeatureSplitBase
LOCAL_AAPT_FLAGS += --package-id 0x80
-LOCAL_AAPT_FLAGS += --rename-manifest-package com.android.test.split.feature
+LOCAL_AAPT_FLAGS += --custom-package com.android.test.split.feature.one
include $(BUILD_PACKAGE)
diff --git a/tests/FeatureSplit/feature1/AndroidManifest.xml b/tests/FeatureSplit/feature1/AndroidManifest.xml
index 4e7d151..b87361f 100644
--- a/tests/FeatureSplit/feature1/AndroidManifest.xml
+++ b/tests/FeatureSplit/feature1/AndroidManifest.xml
@@ -15,13 +15,13 @@
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.android.test.split.feature.one"
+ package="com.android.test.split.feature"
featureSplit="feature1">
<uses-sdk android:minSdkVersion="21" />
<application>
- <activity android:name=".One" android:label="Feature One">
+ <activity android:name=".one.One" android:label="Feature One">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
diff --git a/tests/FeatureSplit/feature1/res/values/values.xml b/tests/FeatureSplit/feature1/res/values/values.xml
index 0e3e73c..7d58865 100644
--- a/tests/FeatureSplit/feature1/res/values/values.xml
+++ b/tests/FeatureSplit/feature1/res/values/values.xml
@@ -20,7 +20,7 @@
<integer name="test_integer2">200</integer>
<color name="test_color2">#00ff00</color>
<string-array name="string_array2">
- <item>@*com.android.test.split.feature:string/app_title</item>
+ <item>@string/app_title</item>
</string-array>
</resources>
diff --git a/tests/FeatureSplit/feature2/Android.mk b/tests/FeatureSplit/feature2/Android.mk
index e2fd903..c8e8609 100644
--- a/tests/FeatureSplit/feature2/Android.mk
+++ b/tests/FeatureSplit/feature2/Android.mk
@@ -26,6 +26,6 @@
LOCAL_RES_LIBRARIES := FeatureSplitBase
LOCAL_AAPT_FLAGS += --package-id 0x81
-LOCAL_AAPT_FLAGS += --rename-manifest-package com.android.test.split.feature
+LOCAL_AAPT_FLAGS += --custom-package com.android.test.split.feature.two
include $(BUILD_PACKAGE)
diff --git a/tests/FeatureSplit/feature2/AndroidManifest.xml b/tests/FeatureSplit/feature2/AndroidManifest.xml
index bfe6f38..abd0b5e 100644
--- a/tests/FeatureSplit/feature2/AndroidManifest.xml
+++ b/tests/FeatureSplit/feature2/AndroidManifest.xml
@@ -15,7 +15,7 @@
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.android.test.split.feature.two"
+ package="com.android.test.split.feature"
featureSplit="feature2">
<uses-sdk android:minSdkVersion="21" />
diff --git a/tests/FeatureSplit/feature2/res/values/values.xml b/tests/FeatureSplit/feature2/res/values/values.xml
index 2fa6f90..af5ed1b 100644
--- a/tests/FeatureSplit/feature2/res/values/values.xml
+++ b/tests/FeatureSplit/feature2/res/values/values.xml
@@ -18,7 +18,7 @@
<integer name="test_integer3">300</integer>
<color name="test_color3">#0000ff</color>
<string-array name="string_array3">
- <item>@*com.android.test.split.feature:string/app_title</item>
+ <item>@string/app_title</item>
</string-array>
</resources>
diff --git a/tests/net/java/android/net/MacAddressTest.java b/tests/net/java/android/net/MacAddressTest.java
index 3fa9c3a..fcbb9da 100644
--- a/tests/net/java/android/net/MacAddressTest.java
+++ b/tests/net/java/android/net/MacAddressTest.java
@@ -19,12 +19,14 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.fail;
import android.net.MacAddress.MacAddressType;
import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;
import java.util.Arrays;
+import java.util.Random;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -64,10 +66,139 @@
String msg = String.format("expected type of %s to be %s, but got %s",
Arrays.toString(t.addr), t.expected, got);
assertEquals(msg, t.expected, got);
+
+ if (got != null) {
+ assertEquals(got, new MacAddress(t.addr).addressType());
+ }
}
}
- static byte[] toByteArray(int[] in) {
+ @Test
+ public void testIsMulticastAddress() {
+ MacAddress[] multicastAddresses = {
+ MacAddress.BROADCAST_ADDRESS,
+ new MacAddress("07:00:d3:56:8a:c4"),
+ new MacAddress("33:33:aa:bb:cc:dd"),
+ };
+ MacAddress[] unicastAddresses = {
+ MacAddress.ALL_ZEROS_ADDRESS,
+ new MacAddress("00:01:44:55:66:77"),
+ new MacAddress("08:00:22:33:44:55"),
+ new MacAddress("06:00:00:00:00:00"),
+ };
+
+ for (MacAddress mac : multicastAddresses) {
+ String msg = mac.toString() + " expected to be a multicast address";
+ assertTrue(msg, mac.isMulticastAddress());
+ }
+ for (MacAddress mac : unicastAddresses) {
+ String msg = mac.toString() + " expected not to be a multicast address";
+ assertFalse(msg, mac.isMulticastAddress());
+ }
+ }
+
+ @Test
+ public void testIsLocallyAssignedAddress() {
+ MacAddress[] localAddresses = {
+ new MacAddress("06:00:00:00:00:00"),
+ new MacAddress("07:00:d3:56:8a:c4"),
+ new MacAddress("33:33:aa:bb:cc:dd"),
+ };
+ MacAddress[] universalAddresses = {
+ new MacAddress("00:01:44:55:66:77"),
+ new MacAddress("08:00:22:33:44:55"),
+ };
+
+ for (MacAddress mac : localAddresses) {
+ String msg = mac.toString() + " expected to be a locally assigned address";
+ assertTrue(msg, mac.isLocallyAssigned());
+ }
+ for (MacAddress mac : universalAddresses) {
+ String msg = mac.toString() + " expected not to be globally unique address";
+ assertFalse(msg, mac.isLocallyAssigned());
+ }
+ }
+
+ @Test
+ public void testMacAddressConversions() {
+ final int iterations = 10000;
+ for (int i = 0; i < iterations; i++) {
+ MacAddress mac = MacAddress.getRandomAddress();
+
+ String stringRepr = mac.toString();
+ byte[] bytesRepr = mac.toByteArray();
+
+ assertEquals(mac, new MacAddress(stringRepr));
+ assertEquals(mac, new MacAddress(bytesRepr));
+ }
+ }
+
+ @Test
+ public void testMacAddressRandomGeneration() {
+ final int iterations = 1000;
+ final String expectedAndroidOui = "da:a1:19";
+ for (int i = 0; i < iterations; i++) {
+ MacAddress mac = MacAddress.getRandomAddress();
+ String stringRepr = mac.toString();
+
+ assertTrue(stringRepr + " expected to be a locally assigned address",
+ mac.isLocallyAssigned());
+ assertTrue(stringRepr + " expected to begin with " + expectedAndroidOui,
+ stringRepr.startsWith(expectedAndroidOui));
+ }
+
+ final Random r = new Random();
+ final String anotherOui = "24:5f:78";
+ final String expectedLocalOui = "26:5f:78";
+ final MacAddress base = new MacAddress(anotherOui + ":0:0:0");
+ for (int i = 0; i < iterations; i++) {
+ MacAddress mac = MacAddress.getRandomAddress(base, r);
+ String stringRepr = mac.toString();
+
+ assertTrue(stringRepr + " expected to be a locally assigned address",
+ mac.isLocallyAssigned());
+ assertTrue(stringRepr + " expected to begin with " + expectedLocalOui,
+ stringRepr.startsWith(expectedLocalOui));
+ }
+ }
+
+ @Test
+ public void testConstructorInputValidation() {
+ String[] invalidStringAddresses = {
+ null,
+ "",
+ "abcd",
+ "1:2:3:4:5",
+ "1:2:3:4:5:6:7",
+ "10000:2:3:4:5:6",
+ };
+
+ for (String s : invalidStringAddresses) {
+ try {
+ MacAddress mac = new MacAddress(s);
+ fail("new MacAddress(" + s + ") should have failed, but returned " + mac);
+ } catch (IllegalArgumentException excepted) {
+ }
+ }
+
+ byte[][] invalidBytesAddresses = {
+ null,
+ {},
+ {1,2,3,4,5},
+ {1,2,3,4,5,6,7},
+ };
+
+ for (byte[] b : invalidBytesAddresses) {
+ try {
+ MacAddress mac = new MacAddress(b);
+ fail("new MacAddress(" + Arrays.toString(b)
+ + ") should have failed, but returned " + mac);
+ } catch (IllegalArgumentException excepted) {
+ }
+ }
+ }
+
+ static byte[] toByteArray(int... in) {
byte[] out = new byte[in.length];
for (int i = 0; i < in.length; i++) {
out[i] = (byte) in[i];
diff --git a/tools/aapt2/LoadedApk.cpp b/tools/aapt2/LoadedApk.cpp
index 921d853..5981401 100644
--- a/tools/aapt2/LoadedApk.cpp
+++ b/tools/aapt2/LoadedApk.cpp
@@ -235,8 +235,7 @@
return false;
}
} else {
- uint32_t compression_flags = file->WasCompressed() ? ArchiveEntry::kCompress : 0u;
- if (!io::CopyFileToArchive(context, file, path, compression_flags, writer)) {
+ if (!io::CopyFileToArchivePreserveCompression(context, file, path, writer)) {
return false;
}
}
diff --git a/tools/aapt2/ResourceValues.cpp b/tools/aapt2/ResourceValues.cpp
index b38d259..a782cd3 100644
--- a/tools/aapt2/ResourceValues.cpp
+++ b/tools/aapt2/ResourceValues.cpp
@@ -118,7 +118,7 @@
bool Reference::Flatten(android::Res_value* out_value) const {
const ResourceId resid = id.value_or_default(ResourceId(0));
const bool dynamic = resid.is_valid_dynamic() && resid.package_id() != kFrameworkPackageId &&
- resid.package_id() != kAppPackageId;
+ resid.package_id() < kAppPackageId;
if (reference_type == Reference::Type::kResource) {
if (dynamic) {
diff --git a/tools/aapt2/cmd/Convert.cpp b/tools/aapt2/cmd/Convert.cpp
index 89ae9e8..027ac8f 100644
--- a/tools/aapt2/cmd/Convert.cpp
+++ b/tools/aapt2/cmd/Convert.cpp
@@ -37,38 +37,39 @@
namespace aapt {
-static bool FlattenXml(IAaptContext* context, const xml::XmlResource& xml,
- const std::string& entry_path, bool utf16, IArchiveWriter* writer) {
- BigBuffer buffer(4096);
- XmlFlattenerOptions options = {};
- options.use_utf16 = utf16;
- XmlFlattener flattener(&buffer, options);
- if (!flattener.Consume(context, &xml)) {
- return false;
- }
- io::BigBufferInputStream input_stream(&buffer);
- return io::CopyInputStreamToArchive(context, &input_stream, entry_path, ArchiveEntry::kCompress,
- writer);
-}
+class ISerializer {
+ public:
+ ISerializer(IAaptContext* context, const Source& source) : context_(context), source_(source) {}
+
+ virtual bool SerializeXml(const xml::XmlResource* xml, const std::string& path, bool utf16,
+ IArchiveWriter* writer) = 0;
+ virtual bool SerializeTable(ResourceTable* table, IArchiveWriter* writer) = 0;
+ virtual bool SerializeFile(const FileReference* file, IArchiveWriter* writer) = 0;
+
+ virtual ~ISerializer() = default;
+
+ protected:
+ IAaptContext* context_;
+ Source source_;
+};
bool ConvertProtoApkToBinaryApk(IAaptContext* context, unique_ptr<LoadedApk> apk,
- const TableFlattenerOptions& options, IArchiveWriter* writer) {
- if (!FlattenXml(context, *apk->GetManifest(), kAndroidManifestPath, true /*utf16*/, writer)) {
+ ISerializer* serializer, IArchiveWriter* writer) {
+ // AndroidManifest.xml
+ if (!serializer->SerializeXml(apk->GetManifest(), kAndroidManifestPath, true /*utf16*/, writer)) {
+ context->GetDiagnostics()->Error(DiagMessage(apk->GetSource())
+ << "failed to serialize AndroidManifest.xml");
return false;
}
- BigBuffer buffer(4096);
- TableFlattener table_flattener(options, &buffer);
- if (!table_flattener.Consume(context, apk->GetResourceTable())) {
+ // Resource table
+ if (!serializer->SerializeTable(apk->GetResourceTable(), writer)) {
+ context->GetDiagnostics()->Error(DiagMessage(apk->GetSource())
+ << "failed to serialize the resource table");
return false;
}
- io::BigBufferInputStream input_stream(&buffer);
- if (!io::CopyInputStreamToArchive(context, &input_stream, kApkResourceTablePath,
- ArchiveEntry::kAlign, writer)) {
- return false;
- }
-
+ // Resources
for (const auto& package : apk->GetResourceTable()->packages) {
for (const auto& type : package->types) {
for (const auto& entry : type->entries) {
@@ -76,59 +77,131 @@
const FileReference* file = ValueCast<FileReference>(config_value->value.get());
if (file != nullptr) {
if (file->file == nullptr) {
- context->GetDiagnostics()->Warn(DiagMessage(apk->GetSource())
+ context->GetDiagnostics()->Error(DiagMessage(apk->GetSource())
<< "no file associated with " << *file);
return false;
}
- if (file->type == ResourceFile::Type::kProtoXml) {
- unique_ptr<io::InputStream> in = file->file->OpenInputStream();
- if (in == nullptr) {
- context->GetDiagnostics()->Error(DiagMessage(apk->GetSource())
- << "failed to open file " << *file->path);
- return false;
- }
-
- pb::XmlNode pb_node;
- io::ZeroCopyInputAdaptor adaptor(in.get());
- if (!pb_node.ParseFromZeroCopyStream(&adaptor)) {
- context->GetDiagnostics()->Error(DiagMessage(apk->GetSource())
- << "failed to parse proto XML " << *file->path);
- return false;
- }
-
- std::string error;
- unique_ptr<xml::XmlResource> xml = DeserializeXmlResourceFromPb(pb_node, &error);
- if (xml == nullptr) {
- context->GetDiagnostics()->Error(DiagMessage(apk->GetSource())
- << "failed to deserialize proto XML "
- << *file->path << ": " << error);
- return false;
- }
-
- if (!FlattenXml(context, *xml, *file->path, false /*utf16*/, writer)) {
- context->GetDiagnostics()->Error(DiagMessage(apk->GetSource())
- << "failed to serialize XML " << *file->path);
- return false;
- }
- } else {
- if (!io::CopyFileToArchive(context, file->file, *file->path,
- file->file->WasCompressed() ? ArchiveEntry::kCompress : 0u,
- writer)) {
- context->GetDiagnostics()->Error(DiagMessage(apk->GetSource())
- << "failed to copy file " << *file->path);
- return false;
- }
+ if (!serializer->SerializeFile(file, writer)) {
+ context->GetDiagnostics()->Error(DiagMessage(apk->GetSource())
+ << "failed to serialize file " << *file->path);
+ return false;
}
-
} // file
} // config_value
} // entry
} // type
} // package
+
+ // Other files
+ std::unique_ptr<io::IFileCollectionIterator> iterator = apk->GetFileCollection()->Iterator();
+ while (iterator->HasNext()) {
+ io::IFile* file = iterator->Next();
+
+ std::string path = file->GetSource().path;
+ // The name of the path has the format "<zip-file-name>@<path-to-file>".
+ path = path.substr(path.find('@') + 1);
+
+ // Manifest, resource table and resources have already been taken care of.
+ if (path == kAndroidManifestPath ||
+ path == kApkResourceTablePath ||
+ path == kProtoResourceTablePath ||
+ path.find("res/") == 0) {
+ continue;
+ }
+
+ if (!io::CopyFileToArchivePreserveCompression(context, file, path, writer)) {
+ context->GetDiagnostics()->Error(DiagMessage(apk->GetSource())
+ << "failed to copy file " << path);
+ return false;
+ }
+ }
+
return true;
}
+
+class BinarySerializer : public ISerializer {
+ public:
+ BinarySerializer(IAaptContext* context, const Source& source,
+ const TableFlattenerOptions& options)
+ : ISerializer(context, source), tableFlattenerOptions_(options) {}
+
+ bool SerializeXml(const xml::XmlResource* xml, const std::string& path, bool utf16,
+ IArchiveWriter* writer) {
+ BigBuffer buffer(4096);
+ XmlFlattenerOptions options = {};
+ options.use_utf16 = utf16;
+ XmlFlattener flattener(&buffer, options);
+ if (!flattener.Consume(context_, xml)) {
+ return false;
+ }
+
+ io::BigBufferInputStream input_stream(&buffer);
+ return io::CopyInputStreamToArchive(context_, &input_stream, path, ArchiveEntry::kCompress,
+ writer);
+ }
+
+ bool SerializeTable(ResourceTable* table, IArchiveWriter* writer) {
+ BigBuffer buffer(4096);
+ TableFlattener table_flattener(tableFlattenerOptions_, &buffer);
+ if (!table_flattener.Consume(context_, table)) {
+ return false;
+ }
+
+ io::BigBufferInputStream input_stream(&buffer);
+ return io::CopyInputStreamToArchive(context_, &input_stream, kApkResourceTablePath,
+ ArchiveEntry::kAlign, writer);
+ }
+
+ bool SerializeFile(const FileReference* file, IArchiveWriter* writer) {
+ if (file->type == ResourceFile::Type::kProtoXml) {
+ unique_ptr<io::InputStream> in = file->file->OpenInputStream();
+ if (in == nullptr) {
+ context_->GetDiagnostics()->Error(DiagMessage(source_)
+ << "failed to open file " << *file->path);
+ return false;
+ }
+
+ pb::XmlNode pb_node;
+ io::ZeroCopyInputAdaptor adaptor(in.get());
+ if (!pb_node.ParseFromZeroCopyStream(&adaptor)) {
+ context_->GetDiagnostics()->Error(DiagMessage(source_)
+ << "failed to parse proto XML " << *file->path);
+ return false;
+ }
+
+ std::string error;
+ unique_ptr<xml::XmlResource> xml = DeserializeXmlResourceFromPb(pb_node, &error);
+ if (xml == nullptr) {
+ context_->GetDiagnostics()->Error(DiagMessage(source_)
+ << "failed to deserialize proto XML "
+ << *file->path << ": " << error);
+ return false;
+ }
+
+ if (!SerializeXml(xml.get(), *file->path, false /*utf16*/, writer)) {
+ context_->GetDiagnostics()->Error(DiagMessage(source_)
+ << "failed to serialize proto XML " << *file->path);
+ return false;
+ }
+ } else {
+ if (!io::CopyFileToArchivePreserveCompression(context_, file->file, *file->path, writer)) {
+ context_->GetDiagnostics()->Error(DiagMessage(source_)
+ << "failed to copy file " << *file->path);
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ private:
+ TableFlattenerOptions tableFlattenerOptions_;
+
+ DISALLOW_COPY_AND_ASSIGN(BinarySerializer);
+};
+
class Context : public IAaptContext {
public:
Context() : mangler_({}), symbols_(&mangler_) {
@@ -222,7 +295,8 @@
if (writer == nullptr) {
return 1;
}
- return ConvertProtoApkToBinaryApk(&context, std::move(apk), options, writer.get()) ? 0 : 1;
+ BinarySerializer serializer(&context, apk->GetSource(), options);
+ return ConvertProtoApkToBinaryApk(&context, std::move(apk), &serializer, writer.get()) ? 0 : 1;
}
} // namespace aapt
diff --git a/tools/aapt2/cmd/Link.cpp b/tools/aapt2/cmd/Link.cpp
index b372923..66b5a7a 100644
--- a/tools/aapt2/cmd/Link.cpp
+++ b/tools/aapt2/cmd/Link.cpp
@@ -795,6 +795,9 @@
for (auto& entry : asset_source->GetAssignedPackageIds()) {
if (entry.first > kFrameworkPackageId && entry.first < kAppPackageId) {
final_table_.included_packages_[entry.first] = entry.second;
+ } else if (entry.first == kAppPackageId) {
+ // Capture the included base feature package.
+ included_feature_base_ = entry.second;
}
}
@@ -1465,11 +1468,48 @@
return false;
}
- if (!FlattenTable(table, options_.output_format, writer)) {
- context_->GetDiagnostics()->Error(DiagMessage() << "failed to write resource table");
- return false;
+ // Hack to fix b/68820737.
+ // We need to modify the ResourceTable's package name, but that should NOT affect
+ // anything else being generated, which includes the Java classes.
+ // If required, the package name is modifed before flattening, and then modified back
+ // to its original name.
+ ResourceTablePackage* package_to_rewrite = nullptr;
+ if (context_->GetPackageId() > kAppPackageId &&
+ included_feature_base_ == make_value(context_->GetCompilationPackage())) {
+ // The base APK is included, and this is a feature split. If the base package is
+ // the same as this package, then we are building an old style Android Instant Apps feature
+ // split and must apply this workaround to avoid requiring namespaces support.
+ package_to_rewrite = table->FindPackage(context_->GetCompilationPackage());
+ if (package_to_rewrite != nullptr) {
+ CHECK_EQ(1u, table->packages.size()) << "can't change name of package when > 1 package";
+
+ std::string new_package_name =
+ StringPrintf("%s.%s", package_to_rewrite->name.c_str(),
+ app_info_.split_name.value_or_default("feature").c_str());
+
+ if (context_->IsVerbose()) {
+ context_->GetDiagnostics()->Note(
+ DiagMessage() << "rewriting resource package name for feature split to '"
+ << new_package_name << "'");
+ }
+ package_to_rewrite->name = new_package_name;
+ }
}
- return true;
+
+ bool success = FlattenTable(table, options_.output_format, writer);
+
+ if (package_to_rewrite != nullptr) {
+ // Change the name back.
+ package_to_rewrite->name = context_->GetCompilationPackage();
+ if (package_to_rewrite->id) {
+ table->included_packages_.erase(package_to_rewrite->id.value());
+ }
+ }
+
+ if (!success) {
+ context_->GetDiagnostics()->Error(DiagMessage() << "failed to write resource table");
+ }
+ return success;
}
int Run(const std::vector<std::string>& input_files) {
@@ -1498,8 +1538,8 @@
return 1;
}
- const AppInfo& app_info = maybe_app_info.value();
- context_->SetMinSdkVersion(app_info.min_sdk_version.value_or_default(0));
+ app_info_ = maybe_app_info.value();
+ context_->SetMinSdkVersion(app_info_.min_sdk_version.value_or_default(0));
context_->SetNameManglerPolicy(NameManglerPolicy{context_->GetCompilationPackage()});
@@ -1699,7 +1739,7 @@
// Generate an AndroidManifest.xml for each split.
std::unique_ptr<xml::XmlResource> split_manifest =
- GenerateSplitManifest(app_info, *split_constraints_iter);
+ GenerateSplitManifest(app_info_, *split_constraints_iter);
XmlReferenceLinker linker;
if (!linker.Consume(context_, split_manifest.get())) {
@@ -1799,6 +1839,8 @@
LinkContext* context_;
ResourceTable final_table_;
+ AppInfo app_info_;
+
std::unique_ptr<TableMerger> table_merger_;
// A pointer to the FileCollection representing the filesystem (not archives).
@@ -1816,6 +1858,9 @@
// The set of shared libraries being used, mapping their assigned package ID to package name.
std::map<size_t, std::string> shared_libs_;
+
+ // The package name of the base application, if it is included.
+ Maybe<std::string> included_feature_base_;
};
int Link(const std::vector<StringPiece>& args, IDiagnostics* diagnostics) {
diff --git a/tools/aapt2/cmd/Optimize.cpp b/tools/aapt2/cmd/Optimize.cpp
index 688b6a7..2bf91a5 100644
--- a/tools/aapt2/cmd/Optimize.cpp
+++ b/tools/aapt2/cmd/Optimize.cpp
@@ -256,10 +256,8 @@
for (auto& entry : config_sorted_files) {
FileReference* file_ref = entry.second;
- uint32_t compression_flags =
- file_ref->file->WasCompressed() ? ArchiveEntry::kCompress : 0u;
- if (!io::CopyFileToArchive(context_, file_ref->file, *file_ref->path, compression_flags,
- writer)) {
+ if (!io::CopyFileToArchivePreserveCompression(context_, file_ref->file, *file_ref->path,
+ writer)) {
return false;
}
}
diff --git a/tools/aapt2/cmd/Util.cpp b/tools/aapt2/cmd/Util.cpp
index d39f43e8..8b3a670 100644
--- a/tools/aapt2/cmd/Util.cpp
+++ b/tools/aapt2/cmd/Util.cpp
@@ -140,12 +140,27 @@
return decl;
}
+// Returns a copy of 'name' which conforms to the regex '[a-zA-Z]+[a-zA-Z0-9_]*' by
+// replacing nonconforming characters with underscores.
+//
+// See frameworks/base/core/java/android/content/pm/PackageParser.java which
+// checks this at runtime.
static std::string MakePackageSafeName(const std::string &name) {
std::string result(name);
+ bool first = true;
for (char &c : result) {
- if (c == '-') {
- c = '_';
+ if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) {
+ first = false;
+ continue;
}
+ if (!first) {
+ if (c >= '0' && c <= '9') {
+ continue;
+ }
+ }
+
+ c = '_';
+ first = false;
}
return result;
}
diff --git a/tools/aapt2/cmd/Util_test.cpp b/tools/aapt2/cmd/Util_test.cpp
index 9c33135..0c527f6 100644
--- a/tools/aapt2/cmd/Util_test.cpp
+++ b/tools/aapt2/cmd/Util_test.cpp
@@ -24,15 +24,16 @@
TEST(UtilTest, SplitNamesAreSanitized) {
AppInfo app_info{"com.pkg"};
- SplitConstraints split_constraints{{test::ParseConfigOrDie("en-rUS-land")}};
+ SplitConstraints split_constraints{
+ {test::ParseConfigOrDie("en-rUS-land"), test::ParseConfigOrDie("b+sr+Latn")}};
const auto doc = GenerateSplitManifest(app_info, split_constraints);
const auto &root = doc->root;
EXPECT_EQ(root->name, "manifest");
- // split names cannot contain hyphens
- EXPECT_EQ(root->FindAttribute("", "split")->value, "config.en_rUS_land");
+ // split names cannot contain hyphens or plus signs.
+ EXPECT_EQ(root->FindAttribute("", "split")->value, "config.b_sr_Latn_en_rUS_land");
// but we should use resource qualifiers verbatim in 'targetConfig'.
- EXPECT_EQ(root->FindAttribute("", "targetConfig")->value, "en-rUS-land");
+ EXPECT_EQ(root->FindAttribute("", "targetConfig")->value, "b+sr+Latn,en-rUS-land");
}
} // namespace aapt
diff --git a/tools/aapt2/format/binary/TableFlattener.cpp b/tools/aapt2/format/binary/TableFlattener.cpp
index 4a1b46c..2a51df3 100644
--- a/tools/aapt2/format/binary/TableFlattener.cpp
+++ b/tools/aapt2/format/binary/TableFlattener.cpp
@@ -23,6 +23,7 @@
#include "android-base/logging.h"
#include "android-base/macros.h"
+#include "android-base/stringprintf.h"
#include "ResourceTable.h"
#include "ResourceValues.h"
@@ -579,7 +580,15 @@
// Write a self mapping entry for this package if the ID is non-standard (0x7f).
const uint8_t package_id = package->id.value();
if (package_id != kFrameworkPackageId && package_id != kAppPackageId) {
- table->included_packages_[package_id] = package->name;
+ auto result = table->included_packages_.insert({package_id, package->name});
+ if (!result.second && result.first->second != package->name) {
+ // A mapping for this package ID already exists, and is a different package. Error!
+ context->GetDiagnostics()->Error(
+ DiagMessage() << android::base::StringPrintf(
+ "can't map package ID %02x to '%s'. Already mapped to '%s'", package_id,
+ package->name.c_str(), result.first->second.c_str()));
+ return false;
+ }
}
}
diff --git a/tools/aapt2/io/Util.cpp b/tools/aapt2/io/Util.cpp
index 7ee1016..9751632 100644
--- a/tools/aapt2/io/Util.cpp
+++ b/tools/aapt2/io/Util.cpp
@@ -48,6 +48,12 @@
return CopyInputStreamToArchive(context, data.get(), out_path, compression_flags, writer);
}
+bool CopyFileToArchivePreserveCompression(IAaptContext* context, io::IFile* file,
+ const std::string& out_path, IArchiveWriter* writer) {
+ uint32_t compression_flags = file->WasCompressed() ? ArchiveEntry::kCompress : 0u;
+ return CopyFileToArchive(context, file, out_path, compression_flags, writer);
+}
+
bool CopyProtoToArchive(IAaptContext* context, ::google::protobuf::MessageLite* proto_msg,
const std::string& out_path, uint32_t compression_flags,
IArchiveWriter* writer) {
diff --git a/tools/aapt2/io/Util.h b/tools/aapt2/io/Util.h
index de2ab39..b07fb53 100644
--- a/tools/aapt2/io/Util.h
+++ b/tools/aapt2/io/Util.h
@@ -35,6 +35,9 @@
bool CopyFileToArchive(IAaptContext* context, IFile* file, const std::string& out_path,
uint32_t compression_flags, IArchiveWriter* writer);
+bool CopyFileToArchivePreserveCompression(IAaptContext* context, IFile* file,
+ const std::string& out_path, IArchiveWriter* writer);
+
bool CopyProtoToArchive(IAaptContext* context, ::google::protobuf::MessageLite* proto_msg,
const std::string& out_path, uint32_t compression_flags,
IArchiveWriter* writer);
diff --git a/tools/aapt2/java/ProguardRules.cpp b/tools/aapt2/java/ProguardRules.cpp
index b9ae654..b214d21 100644
--- a/tools/aapt2/java/ProguardRules.cpp
+++ b/tools/aapt2/java/ProguardRules.cpp
@@ -345,15 +345,14 @@
*out << "# Referenced at " << location.source << "\n";
}
if (keep_set.conditional_keep_rules_ && can_be_conditional) {
- *out << "-keep class " << entry.first << " {\n ifused class **.R$layout {\n";
+ *out << "-if class **.R$layout {\n";
for (const UsageLocation& location : locations) {
auto transformed_name = JavaClassGenerator::TransformToFieldName(location.name.entry);
- *out << " int " << transformed_name << ";\n";
+ *out << " int " << transformed_name << ";\n";
}
- *out << " };\n <init>(...);\n}\n" << std::endl;
- } else {
- *out << "-keep class " << entry.first << " { <init>(...); }\n" << std::endl;
+ *out << "}\n";
}
+ *out << "-keep class " << entry.first << " { <init>(...); }\n" << std::endl;
}
for (const auto& entry : keep_set.method_set_) {
diff --git a/tools/aapt2/java/ProguardRules_test.cpp b/tools/aapt2/java/ProguardRules_test.cpp
index df3ac8b..802c56a 100644
--- a/tools/aapt2/java/ProguardRules_test.cpp
+++ b/tools/aapt2/java/ProguardRules_test.cpp
@@ -130,7 +130,7 @@
ASSERT_TRUE(proguard::WriteKeepSet(&out, set));
std::string actual = out.str();
- EXPECT_THAT(actual, HasSubstr("ifused class **.R$layout"));
+ EXPECT_THAT(actual, HasSubstr("-if class **.R$layout"));
EXPECT_THAT(actual, HasSubstr("int foo"));
EXPECT_THAT(actual, HasSubstr("int bar"));
EXPECT_THAT(actual, HasSubstr("com.foo.Bar"));
@@ -152,7 +152,7 @@
ASSERT_TRUE(proguard::WriteKeepSet(&out, set));
std::string actual = out.str();
- EXPECT_THAT(actual, HasSubstr("ifused class **.R$layout"));
+ EXPECT_THAT(actual, HasSubstr("-if class **.R$layout"));
EXPECT_THAT(actual, HasSubstr("int foo"));
EXPECT_THAT(actual, HasSubstr("int bar"));
EXPECT_THAT(actual, HasSubstr("com.foo.Bar"));
@@ -174,7 +174,7 @@
ASSERT_TRUE(proguard::WriteKeepSet(&out, set));
std::string actual = out.str();
- EXPECT_THAT(actual, Not(HasSubstr("ifused")));
+ EXPECT_THAT(actual, Not(HasSubstr("-if")));
}
TEST(ProguardRulesTest, ViewOnClickRuleIsEmitted) {
diff --git a/tools/apilint/apilint.py b/tools/apilint/apilint.py
index 2eab22e..c9f3199 100644
--- a/tools/apilint/apilint.py
+++ b/tools/apilint/apilint.py
@@ -1371,7 +1371,9 @@
print
"""
- print "%s API style issues %s\n" % ((format(fg=WHITE, bg=BLUE, bold=True), format(reset=True)))
- for f in sorted(cur_fail):
- print cur_fail[f]
- print
+ if len(cur_fail) != 0:
+ print "%s API style issues %s\n" % ((format(fg=WHITE, bg=BLUE, bold=True), format(reset=True)))
+ for f in sorted(cur_fail):
+ print cur_fail[f]
+ print
+ sys.exit(77)
diff --git a/tools/apilint/apilint_sha.sh b/tools/apilint/apilint_sha.sh
new file mode 100755
index 0000000..2a45b10
--- /dev/null
+++ b/tools/apilint/apilint_sha.sh
@@ -0,0 +1,4 @@
+#!/bin/bash
+if git show --name-only --pretty=format: $1 | grep api/ > /dev/null; then
+ python tools/apilint/apilint.py <(git show $1:api/current.txt) <(git show $1^:api/current.txt)
+fi
diff --git a/tools/streaming_proto/cpp/main.cpp b/tools/streaming_proto/cpp/main.cpp
index 4816984..9aef562 100644
--- a/tools/streaming_proto/cpp/main.cpp
+++ b/tools/streaming_proto/cpp/main.cpp
@@ -18,6 +18,12 @@
return file_descriptor.name() + ".h";
}
+static inline bool
+should_generate_enums_mapping(const EnumDescriptorProto& enu)
+{
+ return enu.options().GetExtension(stream_enum).enable_enums_mapping();
+}
+
static void
write_enum(stringstream& text, const EnumDescriptorProto& enu, const string& indent)
{
@@ -29,6 +35,23 @@
<< make_constant_name(value.name())
<< " = " << value.number() << ";" << endl;
}
+
+ if (should_generate_enums_mapping(enu)) {
+ string name = make_constant_name(enu.name());
+ string prefix = name + "_";
+ text << indent << "const int _ENUM_" << name << "_COUNT = " << N << ";" << endl;
+ text << indent << "const char* _ENUM_" << name << "_NAMES[" << N << "] = {" << endl;
+ for (int i=0; i<N; i++) {
+ text << indent << INDENT << "\"" << stripPrefix(enu.value(i).name(), prefix) << "\"," << endl;
+ }
+ text << indent << "};" << endl;
+ text << indent << "const uint32_t _ENUM_" << name << "_VALUES[" << N << "] = {" << endl;
+ for (int i=0; i<N; i++) {
+ text << indent << INDENT << make_constant_name(enu.value(i).name()) << "," << endl;
+ }
+ text << indent << "};" << endl;
+ }
+
text << endl;
}
@@ -59,7 +82,7 @@
static inline bool
should_generate_fields_mapping(const DescriptorProto& message)
{
- return message.options().GetExtension(stream).enable_fields_mapping();
+ return message.options().GetExtension(stream_msg).enable_fields_mapping();
}
static void
diff --git a/tools/streaming_proto/stream.proto b/tools/streaming_proto/stream.proto
index 123506c..c081209 100644
--- a/tools/streaming_proto/stream.proto
+++ b/tools/streaming_proto/stream.proto
@@ -21,12 +21,22 @@
package android.stream_proto;
// This option tells streaming proto plugin to compile .proto files with extra features.
-message StreamFlags {
+message MessageOptions {
// creates a mapping of field names of the message to its field ids
optional bool enable_fields_mapping = 1;
}
extend google.protobuf.MessageOptions {
// Flags used by streaming proto plugins
- optional StreamFlags stream = 126856794;
+ optional MessageOptions stream_msg = 126856794;
+}
+
+message EnumOptions {
+ // creates a mapping of enum names to its values, strip its prefix enum type for each value
+ optional bool enable_enums_mapping = 1;
+}
+
+extend google.protobuf.EnumOptions {
+ // Flags used by streaming proto plugins
+ optional EnumOptions stream_enum = 126856794;
}
diff --git a/tools/streaming_proto/string_utils.cpp b/tools/streaming_proto/string_utils.cpp
index bd34ab7..607d820 100644
--- a/tools/streaming_proto/string_utils.cpp
+++ b/tools/streaming_proto/string_utils.cpp
@@ -108,6 +108,17 @@
return result;
}
+string
+stripPrefix(const string& str, const string& prefix)
+{
+ if (str.size() <= prefix.size()) return str;
+ size_t i = 0, len = prefix.size();
+ for (; i<len; i++) {
+ if (str[i] != prefix[i]) return str;
+ }
+ return str.substr(i);
+}
+
} // namespace stream_proto
} // namespace android
diff --git a/tools/streaming_proto/string_utils.h b/tools/streaming_proto/string_utils.h
index d6f195f..315b275 100644
--- a/tools/streaming_proto/string_utils.h
+++ b/tools/streaming_proto/string_utils.h
@@ -26,15 +26,20 @@
string file_base_name(const string& str);
/**
- * Replace all occurances of 'replace' with 'with'.
+ * Replaces all occurances of 'replace' with 'with'.
*/
string replace_string(const string& str, const char replace, const char with);
/**
- * Split a string to parts by delimiter.
+ * Splits a string to parts by delimiter.
*/
vector<string> split(const string& str, const char delimiter);
+/**
+ * Returns the rest of str if it has prefix, otherwise return all.
+ */
+string stripPrefix(const string& str, const string& prefix);
+
} // namespace stream_proto
} // namespace android