Merge "Don't accidentally delete renamed packages"
diff --git a/Android.bp b/Android.bp
index dba49ce..9088315 100644
--- a/Android.bp
+++ b/Android.bp
@@ -17,3 +17,7 @@
"native/android",
"native/graphics/jni",
]
+
+optional_subdirs = [
+ "core/tests/utiltests/jni",
+]
diff --git "a/\135" "b/\135"
deleted file mode 100644
index 5619151..0000000
--- "a/\135"
+++ /dev/null
@@ -1,12 +0,0 @@
-NetworkNotificationManager: logging improvements
-
-TODO: squash me
-# Please enter the commit message for your changes. Lines starting
-# with '#' will be ignored, and an empty message aborts the commit.
-# On branch notification_tagging
-# Your branch is ahead of 'goog/master' by 2 commits.
-# (use "git push" to publish your local commits)
-#
-# Changes to be committed:
-# modified: services/core/java/com/android/server/connectivity/NetworkNotificationManager.java
-#
diff --git a/api/current.txt b/api/current.txt
index d158e10..5a2df57 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -3479,6 +3479,7 @@
method public void dump(java.lang.String, java.io.FileDescriptor, java.io.PrintWriter, java.lang.String[]);
method public void enterPictureInPictureMode();
method public void enterPictureInPictureMode(float);
+ method public void enterPictureInPictureModeOnMoveToBackground(boolean);
method public android.view.View findViewById(int);
method public void finish();
method public void finishActivity(int);
@@ -37294,7 +37295,9 @@
field public static final java.lang.String KEY_HIDE_PREFERRED_NETWORK_TYPE_BOOL = "hide_preferred_network_type_bool";
field public static final java.lang.String KEY_HIDE_SIM_LOCK_SETTINGS_BOOL = "hide_sim_lock_settings_bool";
field public static final java.lang.String KEY_IGNORE_SIM_NETWORK_LOCKED_EVENTS_BOOL = "ignore_sim_network_locked_events_bool";
+ field public static final java.lang.String KEY_IMS_CONFERENCE_SIZE_LIMIT_INT = "ims_conference_size_limit_int";
field public static final java.lang.String KEY_IMS_DTMF_TONE_DELAY_INT = "ims_dtmf_tone_delay_int";
+ field public static final java.lang.String KEY_IS_IMS_CONFERENCE_SIZE_ENFORCED_BOOL = "is_ims_conference_size_enforced_bool";
field public static final java.lang.String KEY_MDN_IS_ADDITIONAL_VOICEMAIL_NUMBER_BOOL = "mdn_is_additional_voicemail_number_bool";
field public static final java.lang.String KEY_MMS_ALIAS_ENABLED_BOOL = "aliasEnabled";
field public static final java.lang.String KEY_MMS_ALIAS_MAX_CHARS_INT = "aliasMaxChars";
diff --git a/api/system-current.txt b/api/system-current.txt
index 9d48713..7a287df 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -3596,6 +3596,7 @@
method public void dump(java.lang.String, java.io.FileDescriptor, java.io.PrintWriter, java.lang.String[]);
method public void enterPictureInPictureMode();
method public void enterPictureInPictureMode(float);
+ method public void enterPictureInPictureModeOnMoveToBackground(boolean);
method public android.view.View findViewById(int);
method public void finish();
method public void finishActivity(int);
@@ -40398,7 +40399,9 @@
field public static final java.lang.String KEY_HIDE_PREFERRED_NETWORK_TYPE_BOOL = "hide_preferred_network_type_bool";
field public static final java.lang.String KEY_HIDE_SIM_LOCK_SETTINGS_BOOL = "hide_sim_lock_settings_bool";
field public static final java.lang.String KEY_IGNORE_SIM_NETWORK_LOCKED_EVENTS_BOOL = "ignore_sim_network_locked_events_bool";
+ field public static final java.lang.String KEY_IMS_CONFERENCE_SIZE_LIMIT_INT = "ims_conference_size_limit_int";
field public static final java.lang.String KEY_IMS_DTMF_TONE_DELAY_INT = "ims_dtmf_tone_delay_int";
+ field public static final java.lang.String KEY_IS_IMS_CONFERENCE_SIZE_ENFORCED_BOOL = "is_ims_conference_size_enforced_bool";
field public static final java.lang.String KEY_MDN_IS_ADDITIONAL_VOICEMAIL_NUMBER_BOOL = "mdn_is_additional_voicemail_number_bool";
field public static final java.lang.String KEY_MMS_ALIAS_ENABLED_BOOL = "aliasEnabled";
field public static final java.lang.String KEY_MMS_ALIAS_MAX_CHARS_INT = "aliasMaxChars";
diff --git a/api/test-current.txt b/api/test-current.txt
index 9cd08d5..20b723d 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -3481,6 +3481,7 @@
method public void dump(java.lang.String, java.io.FileDescriptor, java.io.PrintWriter, java.lang.String[]);
method public void enterPictureInPictureMode();
method public void enterPictureInPictureMode(float);
+ method public void enterPictureInPictureModeOnMoveToBackground(boolean);
method public android.view.View findViewById(int);
method public void finish();
method public void finishActivity(int);
@@ -37391,7 +37392,9 @@
field public static final java.lang.String KEY_HIDE_PREFERRED_NETWORK_TYPE_BOOL = "hide_preferred_network_type_bool";
field public static final java.lang.String KEY_HIDE_SIM_LOCK_SETTINGS_BOOL = "hide_sim_lock_settings_bool";
field public static final java.lang.String KEY_IGNORE_SIM_NETWORK_LOCKED_EVENTS_BOOL = "ignore_sim_network_locked_events_bool";
+ field public static final java.lang.String KEY_IMS_CONFERENCE_SIZE_LIMIT_INT = "ims_conference_size_limit_int";
field public static final java.lang.String KEY_IMS_DTMF_TONE_DELAY_INT = "ims_dtmf_tone_delay_int";
+ field public static final java.lang.String KEY_IS_IMS_CONFERENCE_SIZE_ENFORCED_BOOL = "is_ims_conference_size_enforced_bool";
field public static final java.lang.String KEY_MDN_IS_ADDITIONAL_VOICEMAIL_NUMBER_BOOL = "mdn_is_additional_voicemail_number_bool";
field public static final java.lang.String KEY_MMS_ALIAS_ENABLED_BOOL = "aliasEnabled";
field public static final java.lang.String KEY_MMS_ALIAS_MAX_CHARS_INT = "aliasMaxChars";
@@ -41525,11 +41528,15 @@
public final class ProtoOutputStream {
ctor public ProtoOutputStream();
ctor public ProtoOutputStream(int);
+ ctor public ProtoOutputStream(java.io.OutputStream);
+ ctor public ProtoOutputStream(java.io.FileDescriptor);
method public static int checkFieldId(long, long);
method public static int convertObjectIdToOrdinal(int);
method public void dump(java.lang.String);
+ method public void end(long);
method public void endObject(long);
method public void endRepeatedObject(long);
+ method public void flush();
method public byte[] getBytes();
method public static int getDepthFromToken(long);
method public static int getObjectIdFromToken(long);
@@ -41538,9 +41545,17 @@
method public static int getTagSizeFromToken(long);
method public static long makeFieldId(int, long);
method public static long makeToken(int, boolean, int, int, int);
+ method public long start(long);
method public long startObject(long);
method public long startRepeatedObject(long);
method public static java.lang.String token2String(long);
+ method public void write(long, double);
+ method public void write(long, float);
+ method public void write(long, int);
+ method public void write(long, long);
+ method public void write(long, boolean);
+ method public void write(long, java.lang.String);
+ method public void write(long, byte[]);
method public void writeBool(long, boolean);
method public void writeBytes(long, byte[]);
method public void writeDouble(long, double);
@@ -41550,6 +41565,8 @@
method public void writeFloat(long, float);
method public void writeInt32(long, int);
method public void writeInt64(long, long);
+ method public void writeObject(long, byte[]);
+ method public void writeObjectImpl(int, byte[]);
method public void writePackedBool(long, boolean[]);
method public void writePackedDouble(long, double[]);
method public void writePackedEnum(long, int[]);
@@ -41573,6 +41590,8 @@
method public void writeRepeatedFloat(long, float);
method public void writeRepeatedInt32(long, int);
method public void writeRepeatedInt64(long, long);
+ method public void writeRepeatedObject(long, byte[]);
+ method public void writeRepeatedObjectImpl(int, byte[]);
method public void writeRepeatedSFixed32(long, int);
method public void writeRepeatedSFixed64(long, long);
method public void writeRepeatedSInt32(long, int);
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index eb73e36..6dd488f 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -2030,6 +2030,28 @@
}
/**
+ * Requests to the system that the activity can be automatically put into picture-in-picture
+ * mode when the user leaves the activity causing it normally to be hidden. This is a *not*
+ * a guarantee that the activity will actually be put in picture-in-picture mode, and depends
+ * on a number of factors, including whether there is already something in picture-in-picture.
+ *
+ * If {@param enterPictureInPictureOnMoveToBg} is true, then you may also call
+ * {@link #setPictureInPictureAspectRatio(float)} to specify the aspect ratio to automatically
+ * enter picture-in-picture with.
+ *
+ * @param enterPictureInPictureOnMoveToBg whether or not this activity can automatically enter
+ * picture-in-picture
+ */
+ public void enterPictureInPictureModeOnMoveToBackground(
+ boolean enterPictureInPictureOnMoveToBg) {
+ try {
+ ActivityManagerNative.getDefault().enterPictureInPictureModeOnMoveToBackground(mToken,
+ enterPictureInPictureOnMoveToBg);
+ } catch (RemoteException e) {
+ }
+ }
+
+ /**
* Called by the system when the device configuration changes while your
* activity is running. Note that this will <em>only</em> be called if
* you have selected configurations you would like to handle with the
diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl
index 640ca6c..62b3977 100644
--- a/core/java/android/app/IActivityManager.aidl
+++ b/core/java/android/app/IActivityManager.aidl
@@ -468,6 +468,10 @@
boolean isInPictureInPictureMode(in IBinder token);
void killPackageDependents(in String packageName, int userId);
void enterPictureInPictureMode(in IBinder token);
+ void enterPictureInPictureModeWithAspectRatio(in IBinder token, float aspectRatio);
+ void enterPictureInPictureModeOnMoveToBackground(in IBinder token,
+ boolean enterPictureInPictureOnMoveToBg);
+ void setPictureInPictureAspectRatio(in IBinder token, float aspectRatio);
void activityRelaunched(in IBinder token);
IBinder getUriPermissionOwnerForActivity(in IBinder activityToken);
/**
@@ -568,8 +572,6 @@
boolean updateDisplayOverrideConfiguration(in Configuration values, int displayId);
void unregisterTaskStackListener(ITaskStackListener listener);
void moveStackToDisplay(int stackId, int displayId);
- void enterPictureInPictureModeWithAspectRatio(in IBinder token, float aspectRatio);
- void setPictureInPictureAspectRatio(in IBinder token, float aspectRatio);
boolean requestAutoFillData(in IResultReceiver receiver, in Bundle receiverExtras,
in IBinder activityToken);
void dismissKeyguard(in IBinder token, in IKeyguardDismissCallback callback);
@@ -578,4 +580,4 @@
// side. If so, make sure they are using the correct transaction ids.
// If a transaction which will also be used on the native side is being inserted, add it
// alongside with other transactions of this kind at the top of this file.
-}
\ No newline at end of file
+}
diff --git a/core/java/android/app/MediaRouteActionProvider.java b/core/java/android/app/MediaRouteActionProvider.java
index dffa969..85ca012 100644
--- a/core/java/android/app/MediaRouteActionProvider.java
+++ b/core/java/android/app/MediaRouteActionProvider.java
@@ -119,7 +119,6 @@
}
mButton = new MediaRouteButton(mContext);
- mButton.setCheatSheetEnabled(true);
mButton.setRouteTypes(mRouteTypes);
mButton.setExtendedSettingsClickListener(mExtendedSettingsListener);
mButton.setLayoutParams(new ViewGroup.LayoutParams(
diff --git a/core/java/android/app/MediaRouteButton.java b/core/java/android/app/MediaRouteButton.java
index 70a5e15..09e95df 100644
--- a/core/java/android/app/MediaRouteButton.java
+++ b/core/java/android/app/MediaRouteButton.java
@@ -24,18 +24,13 @@
import android.content.ContextWrapper;
import android.content.res.TypedArray;
import android.graphics.Canvas;
-import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.media.MediaRouter;
import android.media.MediaRouter.RouteGroup;
import android.media.MediaRouter.RouteInfo;
-import android.text.TextUtils;
import android.util.AttributeSet;
-import android.view.Gravity;
-import android.view.HapticFeedbackConstants;
import android.view.SoundEffectConstants;
import android.view.View;
-import android.widget.Toast;
public class MediaRouteButton extends View {
private final MediaRouter mRouter;
@@ -47,7 +42,6 @@
private Drawable mRemoteIndicator;
private boolean mRemoteActive;
- private boolean mCheatSheetEnabled;
private boolean mIsConnecting;
private int mMinWidth;
@@ -98,7 +92,6 @@
a.recycle();
setClickable(true);
- setLongClickable(true);
setRouteTypes(routeTypes);
}
@@ -178,12 +171,10 @@
throw new IllegalStateException("The MediaRouteButton's Context is not an Activity.");
}
- /**
- * Sets whether to enable showing a toast with the content descriptor of the
- * button when the button is long pressed.
- */
- void setCheatSheetEnabled(boolean enable) {
- mCheatSheetEnabled = enable;
+ @Override
+ public void setContentDescription(CharSequence contentDescription) {
+ super.setContentDescription(contentDescription);
+ setTooltip(contentDescription);
}
@Override
@@ -197,47 +188,6 @@
}
@Override
- public boolean performLongClick() {
- if (super.performLongClick()) {
- return true;
- }
-
- if (!mCheatSheetEnabled) {
- return false;
- }
-
- final CharSequence contentDesc = getContentDescription();
- if (TextUtils.isEmpty(contentDesc)) {
- // Don't show the cheat sheet if we have no description
- return false;
- }
-
- final int[] screenPos = new int[2];
- final Rect displayFrame = new Rect();
- getLocationOnScreen(screenPos);
- getWindowVisibleDisplayFrame(displayFrame);
-
- final Context context = getContext();
- final int width = getWidth();
- final int height = getHeight();
- final int midy = screenPos[1] + height / 2;
- final int screenWidth = context.getResources().getDisplayMetrics().widthPixels;
-
- Toast cheatSheet = Toast.makeText(context, contentDesc, Toast.LENGTH_SHORT);
- if (midy < displayFrame.height()) {
- // Show along the top; follow action buttons
- cheatSheet.setGravity(Gravity.TOP | Gravity.END,
- screenWidth - screenPos[0] - width / 2, height);
- } else {
- // Show along the bottom center
- cheatSheet.setGravity(Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL, 0, height);
- }
- cheatSheet.show();
- performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
- return true;
- }
-
- @Override
protected int[] onCreateDrawableState(int extraSpace) {
final int[] drawableState = super.onCreateDrawableState(extraSpace + 1);
diff --git a/core/java/android/app/SharedPreferencesImpl.java b/core/java/android/app/SharedPreferencesImpl.java
index c1180e2..c5a8288 100644
--- a/core/java/android/app/SharedPreferencesImpl.java
+++ b/core/java/android/app/SharedPreferencesImpl.java
@@ -26,6 +26,8 @@
import android.util.Log;
import com.google.android.collect.Maps;
+
+import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.XmlUtils;
import dalvik.system.BlockGuard;
@@ -72,6 +74,14 @@
private final WeakHashMap<OnSharedPreferenceChangeListener, Object> mListeners =
new WeakHashMap<OnSharedPreferenceChangeListener, Object>();
+ /** Current memory state (always increasing) */
+ @GuardedBy("this")
+ private long mCurrentMemoryStateGeneration;
+
+ /** Latest memory state that was committed to disk */
+ @GuardedBy("mWritingToDiskLock")
+ private long mDiskStateGeneration;
+
SharedPreferencesImpl(File file, int mode) {
mFile = file;
mBackupFile = makeBackupFile(file);
@@ -289,7 +299,7 @@
// Return value from EditorImpl#commitToMemory()
private static class MemoryCommitResult {
- public boolean changesMade; // any keys different?
+ public long memoryStateGeneration;
public List<String> keysModified; // may be null
public Set<OnSharedPreferenceChangeListener> listeners; // may be null
public Map<?, ?> mapToWriteToDisk;
@@ -412,9 +422,11 @@
}
synchronized (this) {
+ boolean changesMade = false;
+
if (mClear) {
if (!mMap.isEmpty()) {
- mcr.changesMade = true;
+ changesMade = true;
mMap.clear();
}
mClear = false;
@@ -441,13 +453,19 @@
mMap.put(k, v);
}
- mcr.changesMade = true;
+ changesMade = true;
if (hasListeners) {
mcr.keysModified.add(k);
}
}
mModified.clear();
+
+ if (changesMade) {
+ mCurrentMemoryStateGeneration++;
+ }
+
+ mcr.memoryStateGeneration = mCurrentMemoryStateGeneration;
}
}
return mcr;
@@ -509,10 +527,12 @@
*/
private void enqueueDiskWrite(final MemoryCommitResult mcr,
final Runnable postWriteRunnable) {
+ final boolean isFromSyncCommit = (postWriteRunnable == null);
+
final Runnable writeToDiskRunnable = new Runnable() {
public void run() {
synchronized (mWritingToDiskLock) {
- writeToFile(mcr);
+ writeToFile(mcr, isFromSyncCommit);
}
synchronized (SharedPreferencesImpl.this) {
mDiskWritesInFlight--;
@@ -523,8 +543,6 @@
}
};
- final boolean isFromSyncCommit = (postWriteRunnable == null);
-
// Typical #commit() path with fewer allocations, doing a write on
// the current thread.
if (isFromSyncCommit) {
@@ -538,6 +556,10 @@
}
}
+ if (DEBUG) {
+ Log.d(TAG, "added " + mcr.memoryStateGeneration + " -> " + mFile.getName());
+ }
+
QueuedWork.singleThreadExecutor().execute(writeToDiskRunnable);
}
@@ -565,17 +587,34 @@
}
// Note: must hold mWritingToDiskLock
- private void writeToFile(MemoryCommitResult mcr) {
+ private void writeToFile(MemoryCommitResult mcr, boolean isFromSyncCommit) {
// Rename the current file so it may be used as a backup during the next read
if (mFile.exists()) {
- if (!mcr.changesMade) {
- // If the file already exists, but no changes were
- // made to the underlying map, it's wasteful to
- // re-write the file. Return as if we wrote it
- // out.
+ boolean needsWrite = false;
+
+ if (isFromSyncCommit) {
+ // Only need to write if the disk state is older than this commit
+ if (mDiskStateGeneration < mcr.memoryStateGeneration) {
+ needsWrite = true;
+ }
+ } else {
+ synchronized (this) {
+ // No need to persist intermediate states. Just wait for the latest state to be
+ // persisted.
+ if (mCurrentMemoryStateGeneration == mcr.memoryStateGeneration) {
+ needsWrite = true;
+ }
+ }
+ }
+
+ if (!needsWrite) {
+ if (DEBUG) {
+ Log.d(TAG, "skipped " + mcr.memoryStateGeneration + " -> " + mFile.getName());
+ }
mcr.setDiskWriteResult(true);
return;
}
+
if (!mBackupFile.exists()) {
if (!mFile.renameTo(mBackupFile)) {
Log.e(TAG, "Couldn't rename file " + mFile
@@ -599,6 +638,11 @@
}
XmlUtils.writeMapXml(mcr.mapToWriteToDisk, str);
FileUtils.sync(str);
+
+ if (DEBUG) {
+ Log.d(TAG, "wrote " + mcr.memoryStateGeneration + " -> " + mFile.getName());
+ }
+
str.close();
ContextImpl.setFilePermissionsFromMode(mFile.getPath(), mMode, 0);
try {
@@ -612,7 +656,11 @@
}
// Writing was successful, delete the backup file if there is one.
mBackupFile.delete();
+
+ mDiskStateGeneration = mcr.memoryStateGeneration;
+
mcr.setDiskWriteResult(true);
+
return;
} catch (XmlPullParserException e) {
Log.w(TAG, "writeToFile: Got exception:", e);
diff --git a/core/java/android/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java
index e2ebd46..5d90acc 100644
--- a/core/java/android/content/pm/ActivityInfo.java
+++ b/core/java/android/content/pm/ActivityInfo.java
@@ -182,12 +182,30 @@
*/
public static final int RESIZE_MODE_RESIZEABLE_AND_PIPABLE = 3;
/**
- * Activity is does not support resizing, but we are forcing it to be resizeable. Only affects
+ * Activity does not support resizing, but we are forcing it to be resizeable. Only affects
* certain pre-N apps where we force them to be resizeable.
* @hide
*/
public static final int RESIZE_MODE_FORCE_RESIZEABLE = 4;
/**
+ * Activity does not support resizing, but we are forcing it to be resizeable as long
+ * as the size remains landscape.
+ * @hide
+ */
+ public static final int RESIZE_MODE_FORCE_RESIZABLE_LANDSCAPE_ONLY = 5;
+ /**
+ * Activity does not support resizing, but we are forcing it to be resizeable as long
+ * as the size remains portrait.
+ * @hide
+ */
+ public static final int RESIZE_MODE_FORCE_RESIZABLE_PORTRAIT_ONLY = 6;
+ /**
+ * Activity does not support resizing, but we are forcing it to be resizeable as long
+ * as the bounds remain in the same orientation as they are.
+ * @hide
+ */
+ public static final int RESIZE_MODE_FORCE_RESIZABLE_PRESERVE_ORIENTATION = 7;
+ /**
* Value indicating if the resizing mode the activity supports.
* See {@link android.R.attr#resizeableActivity}.
* @hide
@@ -859,26 +877,51 @@
* @hide
*/
boolean isFixedOrientation() {
- return screenOrientation == SCREEN_ORIENTATION_LANDSCAPE
- || screenOrientation == SCREEN_ORIENTATION_PORTRAIT
- || screenOrientation == SCREEN_ORIENTATION_SENSOR_LANDSCAPE
- || screenOrientation == SCREEN_ORIENTATION_SENSOR_PORTRAIT
- || screenOrientation == SCREEN_ORIENTATION_REVERSE_LANDSCAPE
- || screenOrientation == SCREEN_ORIENTATION_REVERSE_PORTRAIT
- || screenOrientation == SCREEN_ORIENTATION_USER_LANDSCAPE
- || screenOrientation == SCREEN_ORIENTATION_USER_PORTRAIT
+ return isFixedOrientationLandscape() || isFixedOrientationPortrait()
|| screenOrientation == SCREEN_ORIENTATION_LOCKED;
}
+ /**
+ * Returns true if the activity's orientation is fixed to landscape.
+ * @hide
+ */
+ boolean isFixedOrientationLandscape() {
+ return screenOrientation == SCREEN_ORIENTATION_LANDSCAPE
+ || screenOrientation == SCREEN_ORIENTATION_SENSOR_LANDSCAPE
+ || screenOrientation == SCREEN_ORIENTATION_REVERSE_LANDSCAPE
+ || screenOrientation == SCREEN_ORIENTATION_USER_LANDSCAPE;
+ }
+
+ /**
+ * Returns true if the activity's orientation is fixed to portrait.
+ * @hide
+ */
+ boolean isFixedOrientationPortrait() {
+ return screenOrientation == SCREEN_ORIENTATION_PORTRAIT
+ || screenOrientation == SCREEN_ORIENTATION_SENSOR_PORTRAIT
+ || screenOrientation == SCREEN_ORIENTATION_REVERSE_PORTRAIT
+ || screenOrientation == SCREEN_ORIENTATION_USER_PORTRAIT;
+ }
+
/** @hide */
public static boolean isResizeableMode(int mode) {
return mode == RESIZE_MODE_RESIZEABLE
|| mode == RESIZE_MODE_RESIZEABLE_AND_PIPABLE
|| mode == RESIZE_MODE_FORCE_RESIZEABLE
+ || mode == RESIZE_MODE_FORCE_RESIZABLE_PORTRAIT_ONLY
+ || mode == RESIZE_MODE_FORCE_RESIZABLE_LANDSCAPE_ONLY
+ || mode == RESIZE_MODE_FORCE_RESIZABLE_PRESERVE_ORIENTATION
|| mode == RESIZE_MODE_RESIZEABLE_VIA_SDK_VERSION;
}
/** @hide */
+ public static boolean isPreserveOrientationMode(int mode) {
+ return mode == RESIZE_MODE_FORCE_RESIZABLE_PORTRAIT_ONLY
+ || mode == RESIZE_MODE_FORCE_RESIZABLE_LANDSCAPE_ONLY
+ || mode == RESIZE_MODE_FORCE_RESIZABLE_PRESERVE_ORIENTATION;
+ }
+
+ /** @hide */
public static String resizeModeToString(int mode) {
switch (mode) {
case RESIZE_MODE_UNRESIZEABLE:
@@ -891,6 +934,12 @@
return "RESIZE_MODE_RESIZEABLE_AND_PIPABLE";
case RESIZE_MODE_FORCE_RESIZEABLE:
return "RESIZE_MODE_FORCE_RESIZEABLE";
+ case RESIZE_MODE_FORCE_RESIZABLE_PORTRAIT_ONLY:
+ return "RESIZE_MODE_FORCE_RESIZABLE_PORTRAIT_ONLY";
+ case RESIZE_MODE_FORCE_RESIZABLE_LANDSCAPE_ONLY:
+ return "RESIZE_MODE_FORCE_RESIZABLE_LANDSCAPE_ONLY";
+ case RESIZE_MODE_FORCE_RESIZABLE_PRESERVE_ORIENTATION:
+ return "RESIZE_MODE_FORCE_RESIZABLE_PRESERVE_ORIENTATION";
default:
return "unknown=" + mode;
}
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index f5eb4bd..9b2dd68 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -88,6 +88,9 @@
import static android.content.pm.ActivityInfo.FLAG_IMMERSIVE;
import static android.content.pm.ActivityInfo.FLAG_ON_TOP_LAUNCHER;
import static android.content.pm.ActivityInfo.RESIZE_MODE_FORCE_RESIZEABLE;
+import static android.content.pm.ActivityInfo.RESIZE_MODE_FORCE_RESIZABLE_LANDSCAPE_ONLY;
+import static android.content.pm.ActivityInfo.RESIZE_MODE_FORCE_RESIZABLE_PORTRAIT_ONLY;
+import static android.content.pm.ActivityInfo.RESIZE_MODE_FORCE_RESIZABLE_PRESERVE_ORIENTATION;
import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE;
import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE_AND_PIPABLE;
import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE_VIA_SDK_VERSION;
@@ -3896,8 +3899,15 @@
// resize preference isn't set and target sdk version doesn't support resizing apps by
// default. For the app to be resizeable if it isn't fixed orientation or immersive.
- aInfo.resizeMode = (aInfo.isFixedOrientation() || (aInfo.flags & FLAG_IMMERSIVE) != 0)
- ? RESIZE_MODE_UNRESIZEABLE : RESIZE_MODE_FORCE_RESIZEABLE;
+ if (aInfo.isFixedOrientationPortrait()) {
+ aInfo.resizeMode = RESIZE_MODE_FORCE_RESIZABLE_PORTRAIT_ONLY;
+ } else if (aInfo.isFixedOrientationLandscape()) {
+ aInfo.resizeMode = RESIZE_MODE_FORCE_RESIZABLE_LANDSCAPE_ONLY;
+ } else if (aInfo.isFixedOrientation()) {
+ aInfo.resizeMode = RESIZE_MODE_FORCE_RESIZABLE_PRESERVE_ORIENTATION;
+ } else {
+ aInfo.resizeMode = RESIZE_MODE_FORCE_RESIZEABLE;
+ }
}
private void parseLayout(Resources res, AttributeSet attrs, Activity a) {
diff --git a/core/java/android/hardware/camera2/legacy/LegacyCameraDevice.java b/core/java/android/hardware/camera2/legacy/LegacyCameraDevice.java
index 3e79118..1a05904 100644
--- a/core/java/android/hardware/camera2/legacy/LegacyCameraDevice.java
+++ b/core/java/android/hardware/camera2/legacy/LegacyCameraDevice.java
@@ -348,9 +348,7 @@
Size[] sizes = streamConfigurations.getOutputSizes(surfaceType);
if (sizes == null) {
- // WAR: Override default format to IMPLEMENTATION_DEFINED for b/9487482
- if ((surfaceType >= LegacyMetadataMapper.HAL_PIXEL_FORMAT_RGBA_8888 &&
- surfaceType <= LegacyMetadataMapper.HAL_PIXEL_FORMAT_BGRA_8888)) {
+ if (surfaceType == ImageFormat.PRIVATE) {
// YUV_420_888 is always present in LEGACY for all
// IMPLEMENTATION_DEFINED output sizes, and is publicly visible in the
@@ -649,7 +647,16 @@
*/
public static int detectSurfaceType(Surface surface) throws BufferQueueAbandonedException {
checkNotNull(surface);
- return LegacyExceptionUtils.throwOnError(nativeDetectSurfaceType(surface));
+ int surfaceType = nativeDetectSurfaceType(surface);
+
+ // TODO: remove this override since the default format should be
+ // ImageFormat.PRIVATE. b/9487482
+ if ((surfaceType >= LegacyMetadataMapper.HAL_PIXEL_FORMAT_RGBA_8888 &&
+ surfaceType <= LegacyMetadataMapper.HAL_PIXEL_FORMAT_BGRA_8888)) {
+ surfaceType = ImageFormat.PRIVATE;
+ }
+
+ return LegacyExceptionUtils.throwOnError(surfaceType);
}
/**
diff --git a/core/java/android/hardware/camera2/params/StreamConfigurationMap.java b/core/java/android/hardware/camera2/params/StreamConfigurationMap.java
index dfa19b0..dbe1394 100644
--- a/core/java/android/hardware/camera2/params/StreamConfigurationMap.java
+++ b/core/java/android/hardware/camera2/params/StreamConfigurationMap.java
@@ -408,12 +408,6 @@
// See if consumer is flexible.
boolean isFlexible = SurfaceUtils.isFlexibleConsumer(surface);
- // Override RGB formats to IMPLEMENTATION_DEFINED, b/9487482
- if ((surfaceFormat >= LegacyMetadataMapper.HAL_PIXEL_FORMAT_RGBA_8888 &&
- surfaceFormat <= LegacyMetadataMapper.HAL_PIXEL_FORMAT_BGRA_8888)) {
- surfaceFormat = HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED;
- }
-
StreamConfiguration[] configs =
surfaceDataspace != HAL_DATASPACE_DEPTH ? mConfigurations : mDepthConfigurations;
for (StreamConfiguration config : configs) {
diff --git a/core/java/android/hardware/camera2/utils/SurfaceUtils.java b/core/java/android/hardware/camera2/utils/SurfaceUtils.java
index 4b958df..e1e1c4f 100644
--- a/core/java/android/hardware/camera2/utils/SurfaceUtils.java
+++ b/core/java/android/hardware/camera2/utils/SurfaceUtils.java
@@ -118,15 +118,7 @@
* @param surface The high speed output surface to be checked.
*/
private static void checkHighSpeedSurfaceFormat(Surface surface) {
- // TODO: remove this override since the default format should be
- // ImageFormat.PRIVATE. b/9487482
- final int HAL_FORMAT_RGB_START = 1; // HAL_PIXEL_FORMAT_RGBA_8888 from graphics.h
- final int HAL_FORMAT_RGB_END = 5; // HAL_PIXEL_FORMAT_BGRA_8888 from graphics.h
int surfaceFormat = SurfaceUtils.getSurfaceFormat(surface);
- if (surfaceFormat >= HAL_FORMAT_RGB_START &&
- surfaceFormat <= HAL_FORMAT_RGB_END) {
- surfaceFormat = ImageFormat.PRIVATE;
- }
if (surfaceFormat != ImageFormat.PRIVATE) {
throw new IllegalArgumentException("Surface format(" + surfaceFormat + ") is not"
diff --git a/core/java/android/net/INetworkScoreService.aidl b/core/java/android/net/INetworkScoreService.aidl
index 59cbf6e..542a0a7 100644
--- a/core/java/android/net/INetworkScoreService.aidl
+++ b/core/java/android/net/INetworkScoreService.aidl
@@ -56,17 +56,26 @@
void disableScoring();
/**
- * Register a network subsystem for scoring.
+ * Register a cache to receive scoring updates.
*
* @param networkType the type of network this cache can handle. See {@link NetworkKey#type}.
* @param scoreCache implementation of {@link INetworkScoreCache} to store the scores.
* @throws SecurityException if the caller is not the system.
- * @throws IllegalArgumentException if a score cache is already registed for this type.
* @hide
*/
void registerNetworkScoreCache(int networkType, INetworkScoreCache scoreCache);
/**
+ * Unregister a cache to receive scoring updates.
+ *
+ * @param networkType the type of network this cache can handle. See {@link NetworkKey#type}.
+ * @param scoreCache implementation of {@link INetworkScoreCache} to store the scores.
+ * @throws SecurityException if the caller is not the system.
+ * @hide
+ */
+ void unregisterNetworkScoreCache(int networkType, INetworkScoreCache scoreCache);
+
+ /**
* Request a recommendation for the best network to connect to
* taking into account the inputs from the {@link RecommendationRequest}.
*
diff --git a/core/java/android/net/NetworkScoreManager.java b/core/java/android/net/NetworkScoreManager.java
index a2d2b58..236c6fc 100644
--- a/core/java/android/net/NetworkScoreManager.java
+++ b/core/java/android/net/NetworkScoreManager.java
@@ -279,6 +279,24 @@
}
/**
+ * Unregister a network score cache.
+ *
+ * @param networkType the type of network this cache can handle. See {@link NetworkKey#type}.
+ * @param scoreCache implementation of {@link INetworkScoreCache} to store the scores.
+ * @throws SecurityException if the caller does not hold the
+ * {@link android.Manifest.permission#BROADCAST_NETWORK_PRIVILEGED} permission.
+ * @throws IllegalArgumentException if a score cache is already registered for this type.
+ * @hide
+ */
+ public void unregisterNetworkScoreCache(int networkType, INetworkScoreCache scoreCache) {
+ try {
+ mService.unregisterNetworkScoreCache(networkType, scoreCache);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Request a recommendation for which network to connect to.
*
* @param request a {@link RecommendationRequest} instance containing additional
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 686938f..5006433 100755
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -7757,6 +7757,8 @@
/**
* Value to specify if Wi-Fi Wakeup feature is enabled.
+ *
+ * Type: int (0 for false, 1 for true)
* @hide
*/
@SystemApi
@@ -7765,6 +7767,8 @@
/**
* Value to specify if network recommendations from
* {@link com.android.server.NetworkScoreService} are enabled.
+ *
+ * Type: int (0 for false, 1 for true)
* @hide
*/
@SystemApi
diff --git a/core/java/android/util/MemoryIntArray.java b/core/java/android/util/MemoryIntArray.java
index ccaf204..749cf08 100644
--- a/core/java/android/util/MemoryIntArray.java
+++ b/core/java/android/util/MemoryIntArray.java
@@ -19,7 +19,6 @@
import android.os.Parcel;
import android.os.ParcelFileDescriptor;
import android.os.Parcelable;
-import android.os.Process;
import libcore.io.IoUtils;
import dalvik.system.CloseGuard;
@@ -37,13 +36,13 @@
* each other.
* <p>
* The data structure is designed to have one owner process that can
- * read/write. There may be multiple client processes that can only read or
- * read/write depending how the data structure was configured when
- * instantiated. The owner process is the process that created the array.
- * The shared memory is pinned (not reclaimed by the system) until the
- * owning process dies or the data structure is closed. This class
- * is <strong>not</strong> thread safe. You should not interact with
- * an instance of this class once it is closed.
+ * read/write. There may be multiple client processes that can only read.
+ * The owner process is the process that created the array. The shared
+ * memory is pinned (not reclaimed by the system) until the owning process
+ * dies or the data structure is closed. This class is <strong>not</strong>
+ * thread safe. You should not interact with an instance of this class
+ * once it is closed. If you pass back to the owner process an instance
+ * it will be read only even in the owning process.
* </p>
*
* @hide
@@ -55,8 +54,7 @@
private final CloseGuard mCloseGuard = CloseGuard.get();
- private final int mOwnerPid;
- private final boolean mClientWritable;
+ private final boolean mIsOwner;
private final long mMemoryAddr;
private int mFd;
@@ -65,35 +63,27 @@
*
* @param size The size of the array in terms of integer slots. Cannot be
* more than {@link #getMaxSize()}.
- * @param clientWritable Whether other processes can write to the array.
* @throws IOException If an error occurs while accessing the shared memory.
*/
- public MemoryIntArray(int size, boolean clientWritable) throws IOException {
+ public MemoryIntArray(int size) throws IOException {
if (size > MAX_SIZE) {
throw new IllegalArgumentException("Max size is " + MAX_SIZE);
}
- mOwnerPid = Process.myPid();
- mClientWritable = clientWritable;
+ mIsOwner = true;
final String name = UUID.randomUUID().toString();
mFd = nativeCreate(name, size);
- mMemoryAddr = nativeOpen(mFd, true, clientWritable);
+ mMemoryAddr = nativeOpen(mFd, mIsOwner);
mCloseGuard.open("close");
}
private MemoryIntArray(Parcel parcel) throws IOException {
- mOwnerPid = parcel.readInt();
- mClientWritable = (parcel.readInt() == 1);
+ mIsOwner = false;
ParcelFileDescriptor pfd = parcel.readParcelable(null);
if (pfd == null) {
throw new IOException("No backing file descriptor");
}
mFd = pfd.detachFd();
- final long memoryAddress = parcel.readLong();
- if (isOwner()) {
- mMemoryAddr = memoryAddress;
- } else {
- mMemoryAddr = nativeOpen(mFd, false, mClientWritable);
- }
+ mMemoryAddr = nativeOpen(mFd, mIsOwner);
mCloseGuard.open("close");
}
@@ -102,7 +92,7 @@
*/
public boolean isWritable() {
enforceNotClosed();
- return isOwner() || mClientWritable;
+ return mIsOwner;
}
/**
@@ -115,7 +105,7 @@
public int get(int index) throws IOException {
enforceNotClosed();
enforceValidIndex(index);
- return nativeGet(mFd, mMemoryAddr, index, isOwner());
+ return nativeGet(mFd, mMemoryAddr, index);
}
/**
@@ -131,7 +121,7 @@
enforceNotClosed();
enforceWritable();
enforceValidIndex(index);
- nativeSet(mFd, mMemoryAddr, index, value, isOwner());
+ nativeSet(mFd, mMemoryAddr, index, value);
}
/**
@@ -152,7 +142,7 @@
@Override
public void close() throws IOException {
if (!isClosed()) {
- nativeClose(mFd, mMemoryAddr, isOwner());
+ nativeClose(mFd, mMemoryAddr, mIsOwner);
mFd = -1;
mCloseGuard.close();
}
@@ -184,11 +174,8 @@
public void writeToParcel(Parcel parcel, int flags) {
ParcelFileDescriptor pfd = ParcelFileDescriptor.adoptFd(mFd);
try {
- parcel.writeInt(mOwnerPid);
- parcel.writeInt(mClientWritable ? 1 : 0);
// Don't let writing to a parcel to close our fd - plz
parcel.writeParcelable(pfd, flags & ~Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
- parcel.writeLong(mMemoryAddr);
} finally {
pfd.detachFd();
}
@@ -214,10 +201,6 @@
return mFd;
}
- private boolean isOwner() {
- return mOwnerPid == Process.myPid();
- }
-
private void enforceNotClosed() {
if (isClosed()) {
throw new IllegalStateException("cannot interact with a closed instance");
@@ -239,10 +222,10 @@
}
private native int nativeCreate(String name, int size);
- private native long nativeOpen(int fd, boolean owner, boolean writable);
+ private native long nativeOpen(int fd, boolean owner);
private native void nativeClose(int fd, long memoryAddr, boolean owner);
- private native int nativeGet(int fd, long memoryAddr, int index, boolean owner);
- private native void nativeSet(int fd, long memoryAddr, int index, int value, boolean owner);
+ private native int nativeGet(int fd, long memoryAddr, int index);
+ private native void nativeSet(int fd, long memoryAddr, int index, int value);
private native int nativeSize(int fd);
/**
@@ -259,8 +242,7 @@
try {
return new MemoryIntArray(parcel);
} catch (IOException ioe) {
- Log.e(TAG, "Error unparceling MemoryIntArray");
- return null;
+ throw new IllegalArgumentException("Error unparceling MemoryIntArray");
}
}
diff --git a/core/java/android/util/proto/ProtoOutputStream.java b/core/java/android/util/proto/ProtoOutputStream.java
index 8f99399..81251fc 100644
--- a/core/java/android/util/proto/ProtoOutputStream.java
+++ b/core/java/android/util/proto/ProtoOutputStream.java
@@ -19,6 +19,10 @@
import android.annotation.TestApi;
import android.util.Log;
+import java.io.FileDescriptor;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.util.List;
@@ -179,6 +183,11 @@
private EncodedBuffer mBuffer;
/**
+ * Our stream. If there is one.
+ */
+ private OutputStream mStream;
+
+ /**
* Current nesting depth of startObject calls.
*/
private int mDepth;
@@ -226,6 +235,690 @@
mBuffer = new EncodedBuffer(chunkSize);
}
+ /**
+ * Construct a ProtoOutputStream that sits on top of an OutputStream.
+ * @more
+ * The {@link #flush() flush()} method must be called when done writing
+ * to flush any remanining data, althought data *may* be written at intermediate
+ * points within the writing as well.
+ */
+ public ProtoOutputStream(OutputStream stream) {
+ this();
+ mStream = stream;
+ }
+
+ /**
+ * Construct a ProtoOutputStream that sits on top of a FileDescriptor.
+ * @more
+ * The {@link #flush() flush()} method must be called when done writing
+ * to flush any remanining data, althought data *may* be written at intermediate
+ * points within the writing as well.
+ */
+ public ProtoOutputStream(FileDescriptor fd) {
+ this(new FileOutputStream(fd));
+ }
+
+ /**
+ * Write a value for the given fieldId.
+ *
+ * Will automatically convert for the following field types, and
+ * throw an exception for others: double, float, int32, int64, uint32, uint64,
+ * sint32, sint64, fixed32, fixed64, sfixed32, sfixed64, bool, enum.
+ *
+ * @param fieldId The field identifier constant from the generated class.
+ * @param val The value.
+ */
+ public void write(long fieldId, double val) {
+ assertNotCompacted();
+ final int id = (int)fieldId;
+
+ switch ((int)((fieldId & (FIELD_TYPE_MASK | FIELD_COUNT_MASK)) >> FIELD_TYPE_SHIFT)) {
+ // double
+ case (int)((FIELD_TYPE_DOUBLE | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT):
+ writeDoubleImpl(id, (double)val);
+ break;
+ case (int)((FIELD_TYPE_DOUBLE | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT):
+ case (int)((FIELD_TYPE_DOUBLE | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT):
+ writeRepeatedDoubleImpl(id, (double)val);
+ break;
+ // float
+ case (int)((FIELD_TYPE_FLOAT | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT):
+ writeFloatImpl(id, (float)val);
+ break;
+ case (int)((FIELD_TYPE_FLOAT | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT):
+ case (int)((FIELD_TYPE_FLOAT | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT):
+ writeRepeatedFloatImpl(id, (float)val);
+ break;
+ // int32
+ case (int)((FIELD_TYPE_INT32 | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT):
+ writeInt32Impl(id, (int)val);
+ break;
+ case (int)((FIELD_TYPE_INT32 | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT):
+ case (int)((FIELD_TYPE_INT32 | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT):
+ writeRepeatedInt32Impl(id, (int)val);
+ break;
+ // int64
+ case (int)((FIELD_TYPE_INT64 | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT):
+ writeInt64Impl(id, (long)val);
+ break;
+ case (int)((FIELD_TYPE_INT64 | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT):
+ case (int)((FIELD_TYPE_INT64 | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT):
+ writeRepeatedInt64Impl(id, (long)val);
+ break;
+ // uint32
+ case (int)((FIELD_TYPE_UINT32 | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT):
+ writeUInt32Impl(id, (int)val);
+ break;
+ case (int)((FIELD_TYPE_UINT32 | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT):
+ case (int)((FIELD_TYPE_UINT32 | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT):
+ writeRepeatedUInt32Impl(id, (int)val);
+ break;
+ // uint64
+ case (int)((FIELD_TYPE_UINT64 | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT):
+ writeUInt64Impl(id, (long)val);
+ break;
+ case (int)((FIELD_TYPE_UINT64 | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT):
+ case (int)((FIELD_TYPE_UINT64 | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT):
+ writeRepeatedUInt64Impl(id, (long)val);
+ break;
+ // sint32
+ case (int)((FIELD_TYPE_SINT32 | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT):
+ writeSInt32Impl(id, (int)val);
+ break;
+ case (int)((FIELD_TYPE_SINT32 | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT):
+ case (int)((FIELD_TYPE_SINT32 | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT):
+ writeRepeatedSInt32Impl(id, (int)val);
+ break;
+ // sint64
+ case (int)((FIELD_TYPE_SINT64 | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT):
+ writeSInt64Impl(id, (long)val);
+ break;
+ case (int)((FIELD_TYPE_SINT64 | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT):
+ case (int)((FIELD_TYPE_SINT64 | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT):
+ writeRepeatedSInt64Impl(id, (long)val);
+ break;
+ // fixed32
+ case (int)((FIELD_TYPE_FIXED32 | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT):
+ writeFixed32Impl(id, (int)val);
+ break;
+ case (int)((FIELD_TYPE_FIXED32 | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT):
+ case (int)((FIELD_TYPE_FIXED32 | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT):
+ writeRepeatedFixed32Impl(id, (int)val);
+ break;
+ // fixed64
+ case (int)((FIELD_TYPE_FIXED64 | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT):
+ writeFixed64Impl(id, (long)val);
+ break;
+ case (int)((FIELD_TYPE_FIXED64 | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT):
+ case (int)((FIELD_TYPE_FIXED64 | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT):
+ writeRepeatedFixed64Impl(id, (long)val);
+ break;
+ // sfixed32
+ case (int)((FIELD_TYPE_SFIXED32 | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT):
+ writeSFixed32Impl(id, (int)val);
+ break;
+ case (int)((FIELD_TYPE_SFIXED32 | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT):
+ case (int)((FIELD_TYPE_SFIXED32 | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT):
+ writeRepeatedSFixed32Impl(id, (int)val);
+ break;
+ // sfixed64
+ case (int)((FIELD_TYPE_SFIXED64 | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT):
+ writeSFixed64Impl(id, (long)val);
+ break;
+ case (int)((FIELD_TYPE_SFIXED64 | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT):
+ case (int)((FIELD_TYPE_SFIXED64 | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT):
+ writeRepeatedSFixed64Impl(id, (long)val);
+ break;
+ // bool
+ case (int)((FIELD_TYPE_BOOL | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT):
+ writeBoolImpl(id, val != 0);
+ break;
+ case (int)((FIELD_TYPE_BOOL | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT):
+ case (int)((FIELD_TYPE_BOOL | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT):
+ writeRepeatedBoolImpl(id, val != 0);
+ break;
+ // enum
+ case (int)((FIELD_TYPE_ENUM | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT):
+ writeEnumImpl(id, (int)val);
+ break;
+ case (int)((FIELD_TYPE_ENUM | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT):
+ case (int)((FIELD_TYPE_ENUM | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT):
+ writeRepeatedEnumImpl(id, (int)val);
+ break;
+ // string, bytes, object not allowed here.
+ default: {
+ throw new IllegalArgumentException("Attempt to call write(long, double) with "
+ + getFieldIdString(fieldId));
+ }
+ }
+ }
+
+ /**
+ * Write a value for the given fieldId.
+ *
+ * Will automatically convert for the following field types, and
+ * throw an exception for others: double, float, int32, int64, uint32, uint64,
+ * sint32, sint64, fixed32, fixed64, sfixed32, sfixed64, bool, enum.
+ *
+ * @param fieldId The field identifier constant from the generated class.
+ * @param val The value.
+ */
+ public void write(long fieldId, float val) {
+ assertNotCompacted();
+ final int id = (int)fieldId;
+
+ switch ((int)((fieldId & (FIELD_TYPE_MASK | FIELD_COUNT_MASK)) >> FIELD_TYPE_SHIFT)) {
+ // double
+ case (int)((FIELD_TYPE_DOUBLE | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT):
+ writeDoubleImpl(id, (double)val);
+ break;
+ case (int)((FIELD_TYPE_DOUBLE | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT):
+ case (int)((FIELD_TYPE_DOUBLE | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT):
+ writeRepeatedDoubleImpl(id, (double)val);
+ break;
+ // float
+ case (int)((FIELD_TYPE_FLOAT | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT):
+ writeFloatImpl(id, (float)val);
+ break;
+ case (int)((FIELD_TYPE_FLOAT | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT):
+ case (int)((FIELD_TYPE_FLOAT | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT):
+ writeRepeatedFloatImpl(id, (float)val);
+ break;
+ // int32
+ case (int)((FIELD_TYPE_INT32 | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT):
+ writeInt32Impl(id, (int)val);
+ break;
+ case (int)((FIELD_TYPE_INT32 | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT):
+ case (int)((FIELD_TYPE_INT32 | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT):
+ writeRepeatedInt32Impl(id, (int)val);
+ break;
+ // int64
+ case (int)((FIELD_TYPE_INT64 | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT):
+ writeInt64Impl(id, (long)val);
+ break;
+ case (int)((FIELD_TYPE_INT64 | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT):
+ case (int)((FIELD_TYPE_INT64 | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT):
+ writeRepeatedInt64Impl(id, (long)val);
+ break;
+ // uint32
+ case (int)((FIELD_TYPE_UINT32 | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT):
+ writeUInt32Impl(id, (int)val);
+ break;
+ case (int)((FIELD_TYPE_UINT32 | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT):
+ case (int)((FIELD_TYPE_UINT32 | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT):
+ writeRepeatedUInt32Impl(id, (int)val);
+ break;
+ // uint64
+ case (int)((FIELD_TYPE_UINT64 | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT):
+ writeUInt64Impl(id, (long)val);
+ break;
+ case (int)((FIELD_TYPE_UINT64 | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT):
+ case (int)((FIELD_TYPE_UINT64 | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT):
+ writeRepeatedUInt64Impl(id, (long)val);
+ break;
+ // sint32
+ case (int)((FIELD_TYPE_SINT32 | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT):
+ writeSInt32Impl(id, (int)val);
+ break;
+ case (int)((FIELD_TYPE_SINT32 | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT):
+ case (int)((FIELD_TYPE_SINT32 | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT):
+ writeRepeatedSInt32Impl(id, (int)val);
+ break;
+ // sint64
+ case (int)((FIELD_TYPE_SINT64 | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT):
+ writeSInt64Impl(id, (long)val);
+ break;
+ case (int)((FIELD_TYPE_SINT64 | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT):
+ case (int)((FIELD_TYPE_SINT64 | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT):
+ writeRepeatedSInt64Impl(id, (long)val);
+ break;
+ // fixed32
+ case (int)((FIELD_TYPE_FIXED32 | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT):
+ writeFixed32Impl(id, (int)val);
+ break;
+ case (int)((FIELD_TYPE_FIXED32 | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT):
+ case (int)((FIELD_TYPE_FIXED32 | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT):
+ writeRepeatedFixed32Impl(id, (int)val);
+ break;
+ // fixed64
+ case (int)((FIELD_TYPE_FIXED64 | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT):
+ writeFixed64Impl(id, (long)val);
+ break;
+ case (int)((FIELD_TYPE_FIXED64 | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT):
+ case (int)((FIELD_TYPE_FIXED64 | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT):
+ writeRepeatedFixed64Impl(id, (long)val);
+ break;
+ // sfixed32
+ case (int)((FIELD_TYPE_SFIXED32 | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT):
+ writeSFixed32Impl(id, (int)val);
+ break;
+ case (int)((FIELD_TYPE_SFIXED32 | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT):
+ case (int)((FIELD_TYPE_SFIXED32 | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT):
+ writeRepeatedSFixed32Impl(id, (int)val);
+ break;
+ // sfixed64
+ case (int)((FIELD_TYPE_SFIXED64 | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT):
+ writeSFixed64Impl(id, (long)val);
+ break;
+ case (int)((FIELD_TYPE_SFIXED64 | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT):
+ case (int)((FIELD_TYPE_SFIXED64 | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT):
+ writeRepeatedSFixed64Impl(id, (long)val);
+ break;
+ // bool
+ case (int)((FIELD_TYPE_BOOL | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT):
+ writeBoolImpl(id, val != 0);
+ break;
+ case (int)((FIELD_TYPE_BOOL | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT):
+ case (int)((FIELD_TYPE_BOOL | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT):
+ writeRepeatedBoolImpl(id, val != 0);
+ break;
+ // enum
+ case (int)((FIELD_TYPE_ENUM | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT):
+ writeEnumImpl(id, (int)val);
+ break;
+ case (int)((FIELD_TYPE_ENUM | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT):
+ case (int)((FIELD_TYPE_ENUM | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT):
+ writeRepeatedEnumImpl(id, (int)val);
+ break;
+ // string, bytes, object not allowed here.
+ default: {
+ throw new IllegalArgumentException("Attempt to call write(long, float) with "
+ + getFieldIdString(fieldId));
+ }
+ }
+ }
+
+ /**
+ * Write a value for the given fieldId.
+ *
+ * Will automatically convert for the following field types, and
+ * throw an exception for others: double, float, int32, int64, uint32, uint64,
+ * sint32, sint64, fixed32, fixed64, sfixed32, sfixed64, bool, enum.
+ *
+ * @param fieldId The field identifier constant from the generated class.
+ * @param val The value.
+ */
+ public void write(long fieldId, int val) {
+ assertNotCompacted();
+ final int id = (int)fieldId;
+
+ switch ((int)((fieldId & (FIELD_TYPE_MASK | FIELD_COUNT_MASK)) >> FIELD_TYPE_SHIFT)) {
+ // double
+ case (int)((FIELD_TYPE_DOUBLE | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT):
+ writeDoubleImpl(id, (double)val);
+ break;
+ case (int)((FIELD_TYPE_DOUBLE | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT):
+ case (int)((FIELD_TYPE_DOUBLE | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT):
+ writeRepeatedDoubleImpl(id, (double)val);
+ break;
+ // float
+ case (int)((FIELD_TYPE_FLOAT | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT):
+ writeFloatImpl(id, (float)val);
+ break;
+ case (int)((FIELD_TYPE_FLOAT | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT):
+ case (int)((FIELD_TYPE_FLOAT | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT):
+ writeRepeatedFloatImpl(id, (float)val);
+ break;
+ // int32
+ case (int)((FIELD_TYPE_INT32 | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT):
+ writeInt32Impl(id, (int)val);
+ break;
+ case (int)((FIELD_TYPE_INT32 | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT):
+ case (int)((FIELD_TYPE_INT32 | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT):
+ writeRepeatedInt32Impl(id, (int)val);
+ break;
+ // int64
+ case (int)((FIELD_TYPE_INT64 | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT):
+ writeInt64Impl(id, (long)val);
+ break;
+ case (int)((FIELD_TYPE_INT64 | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT):
+ case (int)((FIELD_TYPE_INT64 | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT):
+ writeRepeatedInt64Impl(id, (long)val);
+ break;
+ // uint32
+ case (int)((FIELD_TYPE_UINT32 | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT):
+ writeUInt32Impl(id, (int)val);
+ break;
+ case (int)((FIELD_TYPE_UINT32 | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT):
+ case (int)((FIELD_TYPE_UINT32 | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT):
+ writeRepeatedUInt32Impl(id, (int)val);
+ break;
+ // uint64
+ case (int)((FIELD_TYPE_UINT64 | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT):
+ writeUInt64Impl(id, (long)val);
+ break;
+ case (int)((FIELD_TYPE_UINT64 | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT):
+ case (int)((FIELD_TYPE_UINT64 | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT):
+ writeRepeatedUInt64Impl(id, (long)val);
+ break;
+ // sint32
+ case (int)((FIELD_TYPE_SINT32 | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT):
+ writeSInt32Impl(id, (int)val);
+ break;
+ case (int)((FIELD_TYPE_SINT32 | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT):
+ case (int)((FIELD_TYPE_SINT32 | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT):
+ writeRepeatedSInt32Impl(id, (int)val);
+ break;
+ // sint64
+ case (int)((FIELD_TYPE_SINT64 | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT):
+ writeSInt64Impl(id, (long)val);
+ break;
+ case (int)((FIELD_TYPE_SINT64 | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT):
+ case (int)((FIELD_TYPE_SINT64 | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT):
+ writeRepeatedSInt64Impl(id, (long)val);
+ break;
+ // fixed32
+ case (int)((FIELD_TYPE_FIXED32 | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT):
+ writeFixed32Impl(id, (int)val);
+ break;
+ case (int)((FIELD_TYPE_FIXED32 | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT):
+ case (int)((FIELD_TYPE_FIXED32 | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT):
+ writeRepeatedFixed32Impl(id, (int)val);
+ break;
+ // fixed64
+ case (int)((FIELD_TYPE_FIXED64 | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT):
+ writeFixed64Impl(id, (long)val);
+ break;
+ case (int)((FIELD_TYPE_FIXED64 | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT):
+ case (int)((FIELD_TYPE_FIXED64 | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT):
+ writeRepeatedFixed64Impl(id, (long)val);
+ break;
+ // sfixed32
+ case (int)((FIELD_TYPE_SFIXED32 | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT):
+ writeSFixed32Impl(id, (int)val);
+ break;
+ case (int)((FIELD_TYPE_SFIXED32 | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT):
+ case (int)((FIELD_TYPE_SFIXED32 | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT):
+ writeRepeatedSFixed32Impl(id, (int)val);
+ break;
+ // sfixed64
+ case (int)((FIELD_TYPE_SFIXED64 | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT):
+ writeSFixed64Impl(id, (long)val);
+ break;
+ case (int)((FIELD_TYPE_SFIXED64 | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT):
+ case (int)((FIELD_TYPE_SFIXED64 | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT):
+ writeRepeatedSFixed64Impl(id, (long)val);
+ break;
+ // bool
+ case (int)((FIELD_TYPE_BOOL | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT):
+ writeBoolImpl(id, val != 0);
+ break;
+ case (int)((FIELD_TYPE_BOOL | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT):
+ case (int)((FIELD_TYPE_BOOL | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT):
+ writeRepeatedBoolImpl(id, val != 0);
+ break;
+ // enum
+ case (int)((FIELD_TYPE_ENUM | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT):
+ writeEnumImpl(id, (int)val);
+ break;
+ case (int)((FIELD_TYPE_ENUM | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT):
+ case (int)((FIELD_TYPE_ENUM | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT):
+ writeRepeatedEnumImpl(id, (int)val);
+ break;
+ // string, bytes, object not allowed here.
+ default: {
+ throw new IllegalArgumentException("Attempt to call write(long, int) with "
+ + getFieldIdString(fieldId));
+ }
+ }
+ }
+
+ /**
+ * Write a value for the given fieldId.
+ *
+ * Will automatically convert for the following field types, and
+ * throw an exception for others: double, float, int32, int64, uint32, uint64,
+ * sint32, sint64, fixed32, fixed64, sfixed32, sfixed64, bool, enum.
+ *
+ * @param fieldId The field identifier constant from the generated class.
+ * @param val The value.
+ */
+ public void write(long fieldId, long val) {
+ assertNotCompacted();
+ final int id = (int)fieldId;
+
+ switch ((int)((fieldId & (FIELD_TYPE_MASK | FIELD_COUNT_MASK)) >> FIELD_TYPE_SHIFT)) {
+ // double
+ case (int)((FIELD_TYPE_DOUBLE | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT):
+ writeDoubleImpl(id, (double)val);
+ break;
+ case (int)((FIELD_TYPE_DOUBLE | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT):
+ case (int)((FIELD_TYPE_DOUBLE | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT):
+ writeRepeatedDoubleImpl(id, (double)val);
+ break;
+ // float
+ case (int)((FIELD_TYPE_FLOAT | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT):
+ writeFloatImpl(id, (float)val);
+ break;
+ case (int)((FIELD_TYPE_FLOAT | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT):
+ case (int)((FIELD_TYPE_FLOAT | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT):
+ writeRepeatedFloatImpl(id, (float)val);
+ break;
+ // int32
+ case (int)((FIELD_TYPE_INT32 | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT):
+ writeInt32Impl(id, (int)val);
+ break;
+ case (int)((FIELD_TYPE_INT32 | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT):
+ case (int)((FIELD_TYPE_INT32 | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT):
+ writeRepeatedInt32Impl(id, (int)val);
+ break;
+ // int64
+ case (int)((FIELD_TYPE_INT64 | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT):
+ writeInt64Impl(id, (long)val);
+ break;
+ case (int)((FIELD_TYPE_INT64 | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT):
+ case (int)((FIELD_TYPE_INT64 | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT):
+ writeRepeatedInt64Impl(id, (long)val);
+ break;
+ // uint32
+ case (int)((FIELD_TYPE_UINT32 | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT):
+ writeUInt32Impl(id, (int)val);
+ break;
+ case (int)((FIELD_TYPE_UINT32 | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT):
+ case (int)((FIELD_TYPE_UINT32 | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT):
+ writeRepeatedUInt32Impl(id, (int)val);
+ break;
+ // uint64
+ case (int)((FIELD_TYPE_UINT64 | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT):
+ writeUInt64Impl(id, (long)val);
+ break;
+ case (int)((FIELD_TYPE_UINT64 | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT):
+ case (int)((FIELD_TYPE_UINT64 | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT):
+ writeRepeatedUInt64Impl(id, (long)val);
+ break;
+ // sint32
+ case (int)((FIELD_TYPE_SINT32 | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT):
+ writeSInt32Impl(id, (int)val);
+ break;
+ case (int)((FIELD_TYPE_SINT32 | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT):
+ case (int)((FIELD_TYPE_SINT32 | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT):
+ writeRepeatedSInt32Impl(id, (int)val);
+ break;
+ // sint64
+ case (int)((FIELD_TYPE_SINT64 | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT):
+ writeSInt64Impl(id, (long)val);
+ break;
+ case (int)((FIELD_TYPE_SINT64 | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT):
+ case (int)((FIELD_TYPE_SINT64 | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT):
+ writeRepeatedSInt64Impl(id, (long)val);
+ break;
+ // fixed32
+ case (int)((FIELD_TYPE_FIXED32 | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT):
+ writeFixed32Impl(id, (int)val);
+ break;
+ case (int)((FIELD_TYPE_FIXED32 | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT):
+ case (int)((FIELD_TYPE_FIXED32 | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT):
+ writeRepeatedFixed32Impl(id, (int)val);
+ break;
+ // fixed64
+ case (int)((FIELD_TYPE_FIXED64 | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT):
+ writeFixed64Impl(id, (long)val);
+ break;
+ case (int)((FIELD_TYPE_FIXED64 | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT):
+ case (int)((FIELD_TYPE_FIXED64 | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT):
+ writeRepeatedFixed64Impl(id, (long)val);
+ break;
+ // sfixed32
+ case (int)((FIELD_TYPE_SFIXED32 | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT):
+ writeSFixed32Impl(id, (int)val);
+ break;
+ case (int)((FIELD_TYPE_SFIXED32 | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT):
+ case (int)((FIELD_TYPE_SFIXED32 | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT):
+ writeRepeatedSFixed32Impl(id, (int)val);
+ break;
+ // sfixed64
+ case (int)((FIELD_TYPE_SFIXED64 | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT):
+ writeSFixed64Impl(id, (long)val);
+ break;
+ case (int)((FIELD_TYPE_SFIXED64 | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT):
+ case (int)((FIELD_TYPE_SFIXED64 | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT):
+ writeRepeatedSFixed64Impl(id, (long)val);
+ break;
+ // bool
+ case (int)((FIELD_TYPE_BOOL | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT):
+ writeBoolImpl(id, val != 0);
+ break;
+ case (int)((FIELD_TYPE_BOOL | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT):
+ case (int)((FIELD_TYPE_BOOL | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT):
+ writeRepeatedBoolImpl(id, val != 0);
+ break;
+ // enum
+ case (int)((FIELD_TYPE_ENUM | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT):
+ writeEnumImpl(id, (int)val);
+ break;
+ case (int)((FIELD_TYPE_ENUM | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT):
+ case (int)((FIELD_TYPE_ENUM | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT):
+ writeRepeatedEnumImpl(id, (int)val);
+ break;
+ // string, bytes, object not allowed here.
+ default: {
+ throw new IllegalArgumentException("Attempt to call write(long, long) with "
+ + getFieldIdString(fieldId));
+ }
+ }
+ }
+
+ /**
+ * Write a boolean value for the given fieldId.
+ *
+ * If the field is not a bool field, an exception will be thrown.
+ *
+ * @param fieldId The field identifier constant from the generated class.
+ * @param val The value.
+ */
+ public void write(long fieldId, boolean val) {
+ assertNotCompacted();
+ final int id = (int)fieldId;
+
+ switch ((int)((fieldId & (FIELD_TYPE_MASK | FIELD_COUNT_MASK)) >> FIELD_TYPE_SHIFT)) {
+ // bool
+ case (int)((FIELD_TYPE_BOOL | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT):
+ writeBoolImpl(id, val);
+ break;
+ case (int)((FIELD_TYPE_BOOL | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT):
+ case (int)((FIELD_TYPE_BOOL | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT):
+ writeRepeatedBoolImpl(id, val);
+ break;
+ // nothing else allowed
+ default: {
+ throw new IllegalArgumentException("Attempt to call write(long, boolean) with "
+ + getFieldIdString(fieldId));
+ }
+ }
+ }
+
+ /**
+ * Write a string value for the given fieldId.
+ *
+ * If the field is not a string field, an exception will be thrown.
+ *
+ * @param fieldId The field identifier constant from the generated class.
+ * @param val The value.
+ */
+ public void write(long fieldId, String val) {
+ assertNotCompacted();
+ final int id = (int)fieldId;
+
+ switch ((int)((fieldId & (FIELD_TYPE_MASK | FIELD_COUNT_MASK)) >> FIELD_TYPE_SHIFT)) {
+ // string
+ case (int)((FIELD_TYPE_STRING | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT):
+ writeStringImpl(id, val);
+ break;
+ case (int)((FIELD_TYPE_STRING | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT):
+ case (int)((FIELD_TYPE_STRING | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT):
+ writeRepeatedStringImpl(id, val);
+ break;
+ // nothing else allowed
+ default: {
+ throw new IllegalArgumentException("Attempt to call write(long, String) with "
+ + getFieldIdString(fieldId));
+ }
+ }
+ }
+
+ /**
+ * Write a byte[] value for the given fieldId.
+ *
+ * If the field is not a bytes or object field, an exception will be thrown.
+ *
+ * @param fieldId The field identifier constant from the generated class.
+ * @param val The value.
+ */
+ public void write(long fieldId, byte[] val) {
+ assertNotCompacted();
+ final int id = (int)fieldId;
+
+ switch ((int)((fieldId & (FIELD_TYPE_MASK | FIELD_COUNT_MASK)) >> FIELD_TYPE_SHIFT)) {
+ // bytes
+ case (int)((FIELD_TYPE_BYTES | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT):
+ writeBytesImpl(id, val);
+ break;
+ case (int)((FIELD_TYPE_BYTES | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT):
+ case (int)((FIELD_TYPE_BYTES | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT):
+ writeRepeatedBytesImpl(id, val);
+ break;
+ // Object
+ case (int)((FIELD_TYPE_OBJECT | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT):
+ writeObjectImpl(id, val);
+ break;
+ case (int)((FIELD_TYPE_OBJECT | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT):
+ case (int)((FIELD_TYPE_OBJECT | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT):
+ writeRepeatedObjectImpl(id, val);
+ break;
+ // nothing else allowed
+ default: {
+ throw new IllegalArgumentException("Attempt to call write(long, byte[]) with "
+ + getFieldIdString(fieldId));
+ }
+ }
+ }
+
+ /**
+ * Start a sub object.
+ */
+ public long start(long fieldId) {
+ assertNotCompacted();
+ final int id = (int)fieldId;
+
+ if ((fieldId & FIELD_TYPE_MASK) == FIELD_TYPE_OBJECT) {
+ final long count = fieldId & FIELD_COUNT_MASK;
+ if (count == FIELD_COUNT_SINGLE) {
+ return startObjectImpl(id, false);
+ } else if (count == FIELD_COUNT_REPEATED || count == FIELD_COUNT_PACKED) {
+ return startObjectImpl(id, true);
+ }
+ }
+ throw new IllegalArgumentException("Attempt to call start(long) with "
+ + getFieldIdString(fieldId));
+ }
+
+ /**
+ * End the object started by start() that returned token.
+ */
+ public void end(long token) {
+ endObjectImpl(token, getRepeatedFromToken(token));
+ }
+
//
// proto3 type: double
// java type: double
@@ -240,6 +933,10 @@
assertNotCompacted();
final int id = checkFieldId(fieldId, FIELD_COUNT_SINGLE | FIELD_TYPE_DOUBLE);
+ writeDoubleImpl(id, val);
+ }
+
+ private void writeDoubleImpl(int id, double val) {
if (val != 0) {
writeTag(id, WIRE_TYPE_FIXED64);
mBuffer.writeRawFixed64(Double.doubleToLongBits(val));
@@ -253,6 +950,10 @@
assertNotCompacted();
final int id = checkFieldId(fieldId, FIELD_COUNT_REPEATED | FIELD_TYPE_DOUBLE);
+ writeRepeatedDoubleImpl(id, val);
+ }
+
+ private void writeRepeatedDoubleImpl(int id, double val) {
writeTag(id, WIRE_TYPE_FIXED64);
mBuffer.writeRawFixed64(Double.doubleToLongBits(val));
}
@@ -287,6 +988,10 @@
assertNotCompacted();
final int id = checkFieldId(fieldId, FIELD_COUNT_SINGLE | FIELD_TYPE_FLOAT);
+ writeFloatImpl(id, val);
+ }
+
+ private void writeFloatImpl(int id, float val) {
if (val != 0) {
writeTag(id, WIRE_TYPE_FIXED32);
mBuffer.writeRawFixed32(Float.floatToIntBits(val));
@@ -300,6 +1005,10 @@
assertNotCompacted();
final int id = checkFieldId(fieldId, FIELD_COUNT_REPEATED | FIELD_TYPE_FLOAT);
+ writeRepeatedFloatImpl(id, val);
+ }
+
+ private void writeRepeatedFloatImpl(int id, float val) {
writeTag(id, WIRE_TYPE_FIXED32);
mBuffer.writeRawFixed32(Float.floatToIntBits(val));
}
@@ -357,6 +1066,10 @@
assertNotCompacted();
final int id = checkFieldId(fieldId, FIELD_COUNT_SINGLE | FIELD_TYPE_INT32);
+ writeInt32Impl(id, val);
+ }
+
+ private void writeInt32Impl(int id, int val) {
if (val != 0) {
writeTag(id, WIRE_TYPE_VARINT);
writeUnsignedVarintFromSignedInt(val);
@@ -374,6 +1087,10 @@
assertNotCompacted();
final int id = checkFieldId(fieldId, FIELD_COUNT_REPEATED | FIELD_TYPE_INT32);
+ writeRepeatedInt32Impl(id, val);
+ }
+
+ private void writeRepeatedInt32Impl(int id, int val) {
writeTag(id, WIRE_TYPE_VARINT);
writeUnsignedVarintFromSignedInt(val);
}
@@ -418,6 +1135,10 @@
assertNotCompacted();
final int id = checkFieldId(fieldId, FIELD_COUNT_SINGLE | FIELD_TYPE_INT64);
+ writeInt64Impl(id, val);
+ }
+
+ private void writeInt64Impl(int id, long val) {
if (val != 0) {
writeTag(id, WIRE_TYPE_VARINT);
mBuffer.writeRawVarint64(val);
@@ -431,6 +1152,10 @@
assertNotCompacted();
final int id = checkFieldId(fieldId, FIELD_COUNT_REPEATED | FIELD_TYPE_INT64);
+ writeRepeatedInt64Impl(id, val);
+ }
+
+ private void writeRepeatedInt64Impl(int id, long val) {
writeTag(id, WIRE_TYPE_VARINT);
mBuffer.writeRawVarint64(val);
}
@@ -470,6 +1195,10 @@
assertNotCompacted();
final int id = checkFieldId(fieldId, FIELD_COUNT_SINGLE | FIELD_TYPE_UINT32);
+ writeUInt32Impl(id, val);
+ }
+
+ private void writeUInt32Impl(int id, int val) {
if (val != 0) {
writeTag(id, WIRE_TYPE_VARINT);
mBuffer.writeRawVarint32(val);
@@ -483,6 +1212,10 @@
assertNotCompacted();
final int id = checkFieldId(fieldId, FIELD_COUNT_REPEATED | FIELD_TYPE_UINT32);
+ writeRepeatedUInt32Impl(id, val);
+ }
+
+ private void writeRepeatedUInt32Impl(int id, int val) {
writeTag(id, WIRE_TYPE_VARINT);
mBuffer.writeRawVarint32(val);
}
@@ -522,6 +1255,10 @@
assertNotCompacted();
final int id = checkFieldId(fieldId, FIELD_COUNT_SINGLE | FIELD_TYPE_UINT64);
+ writeUInt64Impl(id, val);
+ }
+
+ private void writeUInt64Impl(int id, long val) {
if (val != 0) {
writeTag(id, WIRE_TYPE_VARINT);
mBuffer.writeRawVarint64(val);
@@ -535,6 +1272,10 @@
assertNotCompacted();
final int id = checkFieldId(fieldId, FIELD_COUNT_REPEATED | FIELD_TYPE_UINT64);
+ writeRepeatedUInt64Impl(id, val);
+ }
+
+ private void writeRepeatedUInt64Impl(int id, long val) {
writeTag(id, WIRE_TYPE_VARINT);
mBuffer.writeRawVarint64(val);
}
@@ -574,6 +1315,10 @@
assertNotCompacted();
final int id = checkFieldId(fieldId, FIELD_COUNT_SINGLE | FIELD_TYPE_SINT32);
+ writeSInt32Impl(id, val);
+ }
+
+ private void writeSInt32Impl(int id, int val) {
if (val != 0) {
writeTag(id, WIRE_TYPE_VARINT);
mBuffer.writeRawZigZag32(val);
@@ -587,6 +1332,10 @@
assertNotCompacted();
final int id = checkFieldId(fieldId, FIELD_COUNT_REPEATED | FIELD_TYPE_SINT32);
+ writeRepeatedSInt32Impl(id, val);
+ }
+
+ private void writeRepeatedSInt32Impl(int id, int val) {
writeTag(id, WIRE_TYPE_VARINT);
mBuffer.writeRawZigZag32(val);
}
@@ -626,6 +1375,10 @@
assertNotCompacted();
final int id = checkFieldId(fieldId, FIELD_COUNT_SINGLE | FIELD_TYPE_SINT64);
+ writeSInt64Impl(id, val);
+ }
+
+ private void writeSInt64Impl(int id, long val) {
if (val != 0) {
writeTag(id, WIRE_TYPE_VARINT);
mBuffer.writeRawZigZag64(val);
@@ -639,6 +1392,10 @@
assertNotCompacted();
final int id = checkFieldId(fieldId, FIELD_COUNT_REPEATED | FIELD_TYPE_SINT64);
+ writeRepeatedSInt64Impl(id, val);
+ }
+
+ private void writeRepeatedSInt64Impl(int id, long val) {
writeTag(id, WIRE_TYPE_VARINT);
mBuffer.writeRawZigZag64(val);
}
@@ -677,6 +1434,10 @@
assertNotCompacted();
final int id = checkFieldId(fieldId, FIELD_COUNT_SINGLE | FIELD_TYPE_FIXED32);
+ writeFixed32Impl(id, val);
+ }
+
+ private void writeFixed32Impl(int id, int val) {
if (val != 0) {
writeTag(id, WIRE_TYPE_FIXED32);
mBuffer.writeRawFixed32(val);
@@ -690,6 +1451,10 @@
assertNotCompacted();
final int id = checkFieldId(fieldId, FIELD_COUNT_REPEATED | FIELD_TYPE_FIXED32);
+ writeRepeatedFixed32Impl(id, val);
+ }
+
+ private void writeRepeatedFixed32Impl(int id, int val) {
writeTag(id, WIRE_TYPE_FIXED32);
mBuffer.writeRawFixed32(val);
}
@@ -724,6 +1489,10 @@
assertNotCompacted();
final int id = checkFieldId(fieldId, FIELD_COUNT_SINGLE | FIELD_TYPE_FIXED64);
+ writeFixed64Impl(id, val);
+ }
+
+ private void writeFixed64Impl(int id, long val) {
if (val != 0) {
writeTag(id, WIRE_TYPE_FIXED64);
mBuffer.writeRawFixed64(val);
@@ -737,6 +1506,10 @@
assertNotCompacted();
final int id = checkFieldId(fieldId, FIELD_COUNT_REPEATED | FIELD_TYPE_FIXED64);
+ writeRepeatedFixed64(id, val);
+ }
+
+ private void writeRepeatedFixed64Impl(int id, long val) {
writeTag(id, WIRE_TYPE_FIXED64);
mBuffer.writeRawFixed64(val);
}
@@ -770,6 +1543,10 @@
assertNotCompacted();
final int id = checkFieldId(fieldId, FIELD_COUNT_SINGLE | FIELD_TYPE_SFIXED32);
+ writeSFixed32Impl(id, val);
+ }
+
+ private void writeSFixed32Impl(int id, int val) {
if (val != 0) {
writeTag(id, WIRE_TYPE_FIXED32);
mBuffer.writeRawFixed32(val);
@@ -783,6 +1560,10 @@
assertNotCompacted();
final int id = checkFieldId(fieldId, FIELD_COUNT_REPEATED | FIELD_TYPE_SFIXED32);
+ writeRepeatedSFixed32Impl(id, val);
+ }
+
+ private void writeRepeatedSFixed32Impl(int id, int val) {
writeTag(id, WIRE_TYPE_FIXED32);
mBuffer.writeRawFixed32(val);
}
@@ -817,6 +1598,10 @@
assertNotCompacted();
final int id = checkFieldId(fieldId, FIELD_COUNT_SINGLE | FIELD_TYPE_SFIXED64);
+ writeSFixed64Impl(id, val);
+ }
+
+ private void writeSFixed64Impl(int id, long val) {
if (val != 0) {
writeTag(id, WIRE_TYPE_FIXED64);
mBuffer.writeRawFixed64(val);
@@ -830,6 +1615,10 @@
assertNotCompacted();
final int id = checkFieldId(fieldId, FIELD_COUNT_REPEATED | FIELD_TYPE_SFIXED64);
+ writeRepeatedSFixed64(id, val);
+ }
+
+ private void writeRepeatedSFixed64Impl(int id, long val) {
writeTag(id, WIRE_TYPE_FIXED64);
mBuffer.writeRawFixed64(val);
}
@@ -864,6 +1653,10 @@
assertNotCompacted();
final int id = checkFieldId(fieldId, FIELD_COUNT_SINGLE | FIELD_TYPE_BOOL);
+ writeBoolImpl(id, val);
+ }
+
+ private void writeBoolImpl(int id, boolean val) {
if (val) {
writeTag(id, WIRE_TYPE_VARINT);
// 0 and 1 are the same as their varint counterparts
@@ -878,6 +1671,10 @@
assertNotCompacted();
final int id = checkFieldId(fieldId, FIELD_COUNT_REPEATED | FIELD_TYPE_BOOL);
+ writeRepeatedBool(id, val);
+ }
+
+ private void writeRepeatedBoolImpl(int id, boolean val) {
writeTag(id, WIRE_TYPE_VARINT);
mBuffer.writeRawByte((byte)(val ? 1 : 0));
}
@@ -916,6 +1713,10 @@
assertNotCompacted();
final int id = checkFieldId(fieldId, FIELD_COUNT_SINGLE | FIELD_TYPE_STRING);
+ writeStringImpl(id, val);
+ }
+
+ private void writeStringImpl(int id, String val) {
if (val != null && val.length() > 0) {
writeUtf8String(id, val);
}
@@ -928,6 +1729,10 @@
assertNotCompacted();
final int id = checkFieldId(fieldId, FIELD_COUNT_REPEATED | FIELD_TYPE_STRING);
+ writeRepeatedStringImpl(id, val);
+ }
+
+ private void writeRepeatedStringImpl(int id, String val) {
if (val == null || val.length() == 0) {
writeKnownLengthHeader(id, 0);
} else {
@@ -963,6 +1768,10 @@
assertNotCompacted();
final int id = checkFieldId(fieldId, FIELD_COUNT_SINGLE | FIELD_TYPE_BYTES);
+ writeBytesImpl(id, val);
+ }
+
+ private void writeBytesImpl(int id, byte[] val) {
if (val != null && val.length > 0) {
writeKnownLengthHeader(id, val.length);
mBuffer.writeRawBuffer(val);
@@ -976,6 +1785,10 @@
assertNotCompacted();
final int id = checkFieldId(fieldId, FIELD_COUNT_REPEATED | FIELD_TYPE_BYTES);
+ writeRepeatedBytesImpl(id, val);
+ }
+
+ private void writeRepeatedBytesImpl(int id, byte[] val) {
writeKnownLengthHeader(id, val == null ? 0 : val.length);
mBuffer.writeRawBuffer(val);
}
@@ -995,6 +1808,10 @@
assertNotCompacted();
final int id = checkFieldId(fieldId, FIELD_COUNT_SINGLE | FIELD_TYPE_ENUM);
+ writeEnumImpl(id, val);
+ }
+
+ private void writeEnumImpl(int id, int val) {
if (val != 0) {
writeTag(id, WIRE_TYPE_VARINT);
writeUnsignedVarintFromSignedInt(val);
@@ -1008,6 +1825,10 @@
assertNotCompacted();
final int id = checkFieldId(fieldId, FIELD_COUNT_REPEATED | FIELD_TYPE_ENUM);
+ writeRepeatedEnumImpl(id, val);
+ }
+
+ private void writeRepeatedEnumImpl(int id, int val) {
writeTag(id, WIRE_TYPE_VARINT);
writeUnsignedVarintFromSignedInt(val);
}
@@ -1239,6 +2060,38 @@
}
}
+ /**
+ * Write an object that has already been flattend.
+ */
+ public void writeObject(long fieldId, byte[] value) {
+ assertNotCompacted();
+ final int id = checkFieldId(fieldId, FIELD_COUNT_SINGLE | FIELD_TYPE_OBJECT);
+
+ writeObjectImpl(id, value);
+ }
+
+ public void writeObjectImpl(int id, byte[] value) {
+ if (value != null && value.length != 0) {
+ writeKnownLengthHeader(id, value.length);
+ mBuffer.writeRawBuffer(value);
+ }
+ }
+
+ /**
+ * Write an object that has already been flattend.
+ */
+ public void writeRepeatedObject(long fieldId, byte[] value) {
+ assertNotCompacted();
+ final int id = checkFieldId(fieldId, FIELD_COUNT_REPEATED | FIELD_TYPE_OBJECT);
+
+ writeRepeatedObjectImpl(id, value);
+ }
+
+ public void writeRepeatedObjectImpl(int id, byte[] value) {
+ writeKnownLengthHeader(id, value == null ? 0 : value.length);
+ mBuffer.writeRawBuffer(value);
+ }
+
//
// Tags
//
@@ -1358,6 +2211,25 @@
}
}
+ /**
+ * Get a debug string for a fieldId.
+ */
+ private String getFieldIdString(long fieldId) {
+ final long fieldCount = fieldId & FIELD_COUNT_MASK;
+ String countString = getFieldCountString(fieldCount);
+ if (countString == null) {
+ countString = "fieldCount=" + fieldCount;
+ }
+
+ final long fieldType = fieldId & FIELD_TYPE_MASK;
+ String typeString = getFieldTypeString(fieldType);
+ if (typeString == null) {
+ typeString = "fieldType=" + fieldType;
+ }
+
+ return fieldCount + " " + typeString + " tag=" + ((int)fieldId)
+ + " fieldId=0x" + Long.toHexString(fieldId);
+ }
/**
* Return how many bytes an encoded field tag will require.
@@ -1580,6 +2452,37 @@
}
/**
+ * Write remaining data to the output stream. If there is no output stream,
+ * this function does nothing. Any currently open objects (i.e. ones that
+ * have not had endObject called for them will not be written). Whether this
+ * writes objects that are closed if there are remaining open objects is
+ * undefined (current implementation does not write it, future ones will).
+ * For now, can either call getBytes() or flush(), but not both.
+ */
+ public void flush() {
+ if (mStream == null) {
+ return;
+ }
+ if (mDepth != 0) {
+ // TODO: The compacting code isn't ready yet to compact unless we're done.
+ // TODO: Fix that.
+ return;
+ }
+ if (mCompacted) {
+ // If we're compacted, we already wrote it finished.
+ return;
+ }
+ compactIfNecessary();
+ final byte[] data = mBuffer.getBytes(mBuffer.getReadableSize());
+ try {
+ mStream.write(data);
+ mStream.flush();
+ } catch (IOException ex) {
+ throw new RuntimeException("Error flushing proto to stream", ex);
+ }
+ }
+
+ /**
* Read a raw tag from the buffer.
*/
private int readRawTag() {
diff --git a/core/java/android/widget/PopupWindow.java b/core/java/android/widget/PopupWindow.java
index fc1520b..5fa1d2b 100644
--- a/core/java/android/widget/PopupWindow.java
+++ b/core/java/android/widget/PopupWindow.java
@@ -1835,7 +1835,8 @@
// can expect the OnAttachStateChangeListener to have been called prior
// to executing this method, so we can rely on that instead.
final Transition exitTransition = mExitTransition;
- if (mIsAnchorRootAttached && exitTransition != null && decorView.isLaidOut()) {
+ if (exitTransition != null && decorView.isLaidOut()
+ && (mIsAnchorRootAttached || mAnchorRoot == null)) {
// The decor view is non-interactive and non-IME-focusable during exit transitions.
final LayoutParams p = (LayoutParams) decorView.getLayoutParams();
p.flags |= LayoutParams.FLAG_NOT_TOUCHABLE;
@@ -1843,18 +1844,13 @@
p.flags &= ~LayoutParams.FLAG_ALT_FOCUSABLE_IM;
mWindowManager.updateViewLayout(decorView, p);
+ final View anchorRoot = mAnchorRoot != null ? mAnchorRoot.get() : null;
+ final Rect epicenter = getTransitionEpicenter();
+
// Once we start dismissing the decor view, all state (including
// the anchor root) needs to be moved to the decor view since we
// may open another popup while it's busy exiting.
- final View anchorRoot = mAnchorRoot != null ? mAnchorRoot.get() : null;
- final Rect epicenter = getTransitionEpicenter();
- exitTransition.setEpicenterCallback(new EpicenterCallback() {
- @Override
- public Rect onGetEpicenter(Transition transition) {
- return epicenter;
- }
- });
- decorView.startExitTransition(exitTransition, anchorRoot,
+ decorView.startExitTransition(exitTransition, anchorRoot, epicenter,
new TransitionListenerAdapter() {
@Override
public void onTransitionEnd(Transition transition) {
@@ -2349,8 +2345,9 @@
* its {@code onTransitionEnd} method called even if the transition
* never starts; however, it may be called with a {@code null} argument.
*/
- public void startExitTransition(Transition transition, final View anchorRoot,
- final TransitionListener listener) {
+ public void startExitTransition(@NonNull Transition transition,
+ @Nullable final View anchorRoot, @Nullable final Rect epicenter,
+ @NonNull final TransitionListener listener) {
if (transition == null) {
return;
}
@@ -2358,24 +2355,35 @@
// The anchor view's window may go away while we're executing our
// transition, in which case we need to end the transition
// immediately and execute the listener to remove the popup.
- anchorRoot.addOnAttachStateChangeListener(mOnAnchorRootDetachedListener);
+ if (anchorRoot != null) {
+ anchorRoot.addOnAttachStateChangeListener(mOnAnchorRootDetachedListener);
+ }
// The exit listener MUST be called for cleanup, even if the
// transition never starts or ends. Stash it for later.
mPendingExitListener = new TransitionListenerAdapter() {
@Override
- public void onTransitionEnd(Transition transition) {
- anchorRoot.removeOnAttachStateChangeListener(mOnAnchorRootDetachedListener);
- listener.onTransitionEnd(transition);
+ public void onTransitionEnd(Transition t) {
+ if (anchorRoot != null) {
+ anchorRoot.removeOnAttachStateChangeListener(mOnAnchorRootDetachedListener);
+ }
+
+ listener.onTransitionEnd(t);
// The listener was called. Our job here is done.
mPendingExitListener = null;
- transition.removeListener(this);
+ t.removeListener(this);
}
};
final Transition exitTransition = transition.clone();
exitTransition.addListener(mPendingExitListener);
+ exitTransition.setEpicenterCallback(new EpicenterCallback() {
+ @Override
+ public Rect onGetEpicenter(Transition transition) {
+ return epicenter;
+ }
+ });
final int count = getChildCount();
for (int i = 0; i < count; i++) {
diff --git a/core/java/com/android/internal/view/menu/ActionMenuItemView.java b/core/java/com/android/internal/view/menu/ActionMenuItemView.java
index 0bf170e..af4f777 100644
--- a/core/java/com/android/internal/view/menu/ActionMenuItemView.java
+++ b/core/java/com/android/internal/view/menu/ActionMenuItemView.java
@@ -20,28 +20,22 @@
import android.content.res.Configuration;
import android.content.res.Resources;
import android.content.res.TypedArray;
-import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.os.Parcelable;
import android.text.TextUtils;
import android.util.AttributeSet;
-import android.view.Gravity;
import android.view.MotionEvent;
import android.view.View;
-import android.view.ViewConfiguration;
import android.view.accessibility.AccessibilityEvent;
-import android.view.accessibility.AccessibilityManager;
import android.widget.ActionMenuView;
import android.widget.ForwardingListener;
import android.widget.TextView;
-import android.widget.Toast;
/**
* @hide
*/
public class ActionMenuItemView extends TextView
- implements MenuView.ItemView, View.OnClickListener, View.OnLongClickListener,
- ActionMenuView.ActionMenuChildView {
+ implements MenuView.ItemView, View.OnClickListener, ActionMenuView.ActionMenuChildView {
private static final String TAG = "ActionMenuItemView";
private MenuItemImpl mItemData;
@@ -59,9 +53,6 @@
private static final int MAX_ICON_SIZE = 32; // dp
private int mMaxIconSize;
- private Toast mTooltip;
- private Runnable mShowTooltipRunnable = () -> showTooltip(Toast.LENGTH_LONG);
-
public ActionMenuItemView(Context context) {
this(context, null);
}
@@ -88,7 +79,6 @@
mMaxIconSize = (int) (MAX_ICON_SIZE * density + 0.5f);
setOnClickListener(this);
- setOnLongClickListener(this);
mSavedPaddingLeft = -1;
setSaveEnabled(false);
@@ -193,6 +183,9 @@
(mItemData.showsTextAsAction() && (mAllowTextWithIcon || mExpandedFormat));
setText(visible ? mTitle : null);
+
+ // Show the tooltip for items that do not already show text.
+ setTooltip(visible ? null : mTitle);
}
public void setIcon(Drawable icon) {
@@ -249,7 +242,6 @@
@Override
public boolean dispatchHoverEvent(MotionEvent event) {
- updateTooltip(event);
// Don't allow children to hover; we want this to be treated as a single component.
return onHoverEvent(event);
}
@@ -267,68 +259,6 @@
}
@Override
- public boolean onLongClick(View v) {
- return showTooltip(Toast.LENGTH_SHORT);
- }
-
- private boolean showTooltip(@Toast.Duration int duration) {
- if (hasText()) {
- // Don't show the cheat sheet for items that already show text.
- return false;
- }
-
- final int[] screenPos = new int[2];
- final Rect displayFrame = new Rect();
- getLocationOnScreen(screenPos);
- getWindowVisibleDisplayFrame(displayFrame);
-
- final Context context = getContext();
- final int width = getWidth();
- final int height = getHeight();
- final int midy = screenPos[1] + height / 2;
- int referenceX = screenPos[0] + width / 2;
- if (getLayoutDirection() == View.LAYOUT_DIRECTION_LTR) {
- final int screenWidth = context.getResources().getDisplayMetrics().widthPixels;
- referenceX = screenWidth - referenceX; // mirror
- }
- hideTooltip ();
- mTooltip = Toast.makeText(context, mItemData.getTitle(), duration);
- if (midy < displayFrame.height()) {
- // Show along the top; follow action buttons
- mTooltip.setGravity(Gravity.TOP | Gravity.END, referenceX,
- screenPos[1] + height - displayFrame.top);
- } else {
- // Show along the bottom center
- mTooltip.setGravity(Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL, 0, height);
- }
- mTooltip.show();
- return true;
- }
-
- private void hideTooltip() {
- if (mTooltip != null) {
- mTooltip.cancel();
- mTooltip = null;
- }
- getHandler().removeCallbacks(mShowTooltipRunnable);
- }
-
- private void updateTooltip(MotionEvent event) {
- AccessibilityManager manager = AccessibilityManager.getInstance(mContext);
- if (manager.isEnabled() && manager.isTouchExplorationEnabled()) {
- return;
- }
-
- final int action = event.getAction();
- if (action == MotionEvent.ACTION_HOVER_MOVE) {
- hideTooltip();
- getHandler().postDelayed(mShowTooltipRunnable, ViewConfiguration.getLongPressTimeout());
- } else if (action == MotionEvent.ACTION_HOVER_EXIT) {
- hideTooltip();
- }
- }
-
- @Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
final boolean textVisible = hasText();
if (textVisible && mSavedPaddingLeft >= 0) {
diff --git a/core/java/com/android/internal/widget/ImageFloatingTextView.java b/core/java/com/android/internal/widget/ImageFloatingTextView.java
index 358be60..a5d2bf3 100644
--- a/core/java/com/android/internal/widget/ImageFloatingTextView.java
+++ b/core/java/com/android/internal/widget/ImageFloatingTextView.java
@@ -71,10 +71,14 @@
.setTextDirection(getTextDirectionHeuristic())
.setLineSpacing(getLineSpacingExtra(), getLineSpacingMultiplier())
.setIncludePad(getIncludeFontPadding())
- .setEllipsize(shouldEllipsize ? effectiveEllipsize : null)
- .setEllipsizedWidth(ellipsisWidth)
.setBreakStrategy(Layout.BREAK_STRATEGY_HIGH_QUALITY)
- .setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_FULL);
+ .setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_FULL)
+ .setMaxLines(getMaxLines() >= 0 ? getMaxLines() : Integer.MAX_VALUE);
+ if (shouldEllipsize) {
+ builder.setEllipsize(effectiveEllipsize)
+ .setEllipsizedWidth(ellipsisWidth);
+ }
+
// we set the endmargin on the requested number of lines.
int endMargin = getContext().getResources().getDimensionPixelSize(
R.dimen.notification_content_picture_margin);
diff --git a/core/java/com/android/internal/widget/MessagingLinearLayout.java b/core/java/com/android/internal/widget/MessagingLinearLayout.java
index d2a43b7..cb123a1 100644
--- a/core/java/com/android/internal/widget/MessagingLinearLayout.java
+++ b/core/java/com/android/internal/widget/MessagingLinearLayout.java
@@ -126,7 +126,8 @@
// Pretend we need the image padding for all views, we don't know which
// one will end up needing to do this (might end up not using all the space,
// but calculating this exactly would be more expensive).
- ((ImageFloatingTextView) child).setNumIndentLines(mIndentLines);
+ ((ImageFloatingTextView) child).setNumIndentLines(
+ mIndentLines == 2 ? 3 : mIndentLines);
}
measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
@@ -147,6 +148,9 @@
// Now that we know which views to take, fix up the indents and see what width we get.
int measuredWidth = mPaddingLeft + mPaddingRight;
int imageLines = mIndentLines;
+ // Need to redo the height because it may change due to changing indents.
+ totalHeight = mPaddingTop + mPaddingBottom;
+ first = true;
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
@@ -173,6 +177,9 @@
measuredWidth = Math.max(measuredWidth,
child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin
+ mPaddingLeft + mPaddingRight);
+ totalHeight = Math.max(totalHeight, totalHeight + child.getMeasuredHeight() +
+ lp.topMargin + lp.bottomMargin + (first ? 0 : mSpacing));
+ first = false;
}
diff --git a/core/java/com/android/internal/widget/ScrollingTabContainerView.java b/core/java/com/android/internal/widget/ScrollingTabContainerView.java
index ffd9b24..4466575 100644
--- a/core/java/com/android/internal/widget/ScrollingTabContainerView.java
+++ b/core/java/com/android/internal/widget/ScrollingTabContainerView.java
@@ -40,7 +40,6 @@
import android.widget.ListView;
import android.widget.Spinner;
import android.widget.TextView;
-import android.widget.Toast;
/**
* This widget implements the dynamic action bar tab behavior that can change
@@ -360,7 +359,7 @@
tabView.getTab().select();
}
- private class TabView extends LinearLayout implements OnLongClickListener {
+ private class TabView extends LinearLayout {
private ActionBar.Tab mTab;
private TextView mTextView;
private ImageView mIconView;
@@ -472,35 +471,10 @@
if (mIconView != null) {
mIconView.setContentDescription(tab.getContentDescription());
}
-
- if (!hasText && !TextUtils.isEmpty(tab.getContentDescription())) {
- setOnLongClickListener(this);
- } else {
- setOnLongClickListener(null);
- setLongClickable(false);
- }
+ setTooltip(hasText? null : tab.getContentDescription());
}
}
- public boolean onLongClick(View v) {
- final int[] screenPos = new int[2];
- getLocationOnScreen(screenPos);
-
- final Context context = getContext();
- final int width = getWidth();
- final int height = getHeight();
- final int screenWidth = context.getResources().getDisplayMetrics().widthPixels;
-
- Toast cheatSheet = Toast.makeText(context, mTab.getContentDescription(),
- Toast.LENGTH_SHORT);
- // Show under the tab
- cheatSheet.setGravity(Gravity.TOP | Gravity.CENTER_HORIZONTAL,
- (screenPos[0] + width / 2) - screenWidth / 2, height);
-
- cheatSheet.show();
- return true;
- }
-
public ActionBar.Tab getTab() {
return mTab;
}
diff --git a/core/jni/android_util_MemoryIntArray.cpp b/core/jni/android_util_MemoryIntArray.cpp
index 9513c8b..2dfbe3e 100644
--- a/core/jni/android_util_MemoryIntArray.cpp
+++ b/core/jni/android_util_MemoryIntArray.cpp
@@ -54,7 +54,7 @@
}
static jlong android_util_MemoryIntArray_open(JNIEnv* env, jobject clazz, jint fd,
- jboolean owner, jboolean writable)
+ jboolean owner)
{
if (fd < 0) {
jniThrowException(env, "java/io/IOException", "bad file descriptor");
@@ -72,19 +72,35 @@
return -1;
}
- int protMode = (owner || writable) ? (PROT_READ | PROT_WRITE) : PROT_READ;
+ // IMPORTANT: Ashmem allows the caller to change its size until
+ // it is memory mapped for the first time which lazily creates
+ // the underlying VFS file. So the size we get above may not
+ // reflect the size of the underlying shared memory region. Therefore,
+ // we first memory map to set the size in stone an verify if
+ // the underlying ashmem region has the same size as the one we
+ // memory mapped. This is critical as we use the underlying
+ // ashmem size for boundary checks and memory unmapping.
+ int protMode = owner ? (PROT_READ | PROT_WRITE) : PROT_READ;
void* ashmemAddr = mmap(NULL, ashmemSize, protMode, MAP_SHARED, fd, 0);
if (ashmemAddr == MAP_FAILED) {
jniThrowException(env, "java/io/IOException", "cannot mmap ashmem");
return -1;
}
+ // Check if the mapped size is the same as the ashmem region.
+ int mmapedSize = ashmem_get_size_region(fd);
+ if (mmapedSize != ashmemSize) {
+ munmap(reinterpret_cast<void *>(ashmemAddr), ashmemSize);
+ jniThrowException(env, "java/io/IOException", "bad file descriptor");
+ return -1;
+ }
+
if (owner) {
int size = ashmemSize / sizeof(std::atomic_int);
new (ashmemAddr) std::atomic_int[size];
}
- if (owner && !writable) {
+ if (owner) {
int setProtResult = ashmem_set_prot_region(fd, PROT_READ);
if (setProtResult < 0) {
jniThrowException(env, "java/io/IOException", "cannot set ashmem prot mode");
@@ -131,7 +147,7 @@
}
static jint android_util_MemoryIntArray_get(JNIEnv* env, jobject clazz,
- jint fd, jlong address, jint index, jboolean owner)
+ jint fd, jlong address, jint index)
{
if (fd < 0) {
jniThrowException(env, "java/io/IOException", "bad file descriptor");
@@ -153,7 +169,7 @@
}
static void android_util_MemoryIntArray_set(JNIEnv* env, jobject clazz,
- jint fd, jlong address, jint index, jint newValue, jboolean owner)
+ jint fd, jlong address, jint index, jint newValue)
{
if (fd < 0) {
jniThrowException(env, "java/io/IOException", "bad file descriptor");
@@ -195,10 +211,10 @@
static const JNINativeMethod methods[] = {
{"nativeCreate", "(Ljava/lang/String;I)I", (void*)android_util_MemoryIntArray_create},
- {"nativeOpen", "(IZZ)J", (void*)android_util_MemoryIntArray_open},
+ {"nativeOpen", "(IZ)J", (void*)android_util_MemoryIntArray_open},
{"nativeClose", "(IJZ)V", (void*)android_util_MemoryIntArray_close},
- {"nativeGet", "(IJIZ)I", (void*)android_util_MemoryIntArray_get},
- {"nativeSet", "(IJIIZ)V", (void*) android_util_MemoryIntArray_set},
+ {"nativeGet", "(IJI)I", (void*)android_util_MemoryIntArray_get},
+ {"nativeSet", "(IJII)V", (void*) android_util_MemoryIntArray_set},
{"nativeSize", "(I)I", (void*) android_util_MemoryIntArray_size},
};
diff --git a/core/tests/coretests/res/layout/messaging_linear_layout_test.xml b/core/tests/coretests/res/layout/messaging_linear_layout_test.xml
new file mode 100644
index 0000000..8ba3e07
--- /dev/null
+++ b/core/tests/coretests/res/layout/messaging_linear_layout_test.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2016 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<com.android.internal.widget.MessagingLinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:maxHeight="300px"
+ android:spacing="5px">
+
+</com.android.internal.widget.MessagingLinearLayout>
\ No newline at end of file
diff --git a/core/tests/coretests/src/com/android/internal/widget/ImageFloatingTextViewTest.java b/core/tests/coretests/src/com/android/internal/widget/ImageFloatingTextViewTest.java
new file mode 100644
index 0000000..5dc07c2
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/widget/ImageFloatingTextViewTest.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.widget;
+
+import static org.junit.Assert.assertEquals;
+
+import android.content.Context;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+import android.view.View.MeasureSpec;
+import android.widget.TextView;
+
+import org.junit.Before;
+import org.junit.Test;
+
+@SmallTest
+public class ImageFloatingTextViewTest {
+
+ private Context mContext;
+ private ImageFloatingTextView mView;
+ private TextView mTextView;
+
+ @Before
+ public void setup() {
+ mContext = InstrumentationRegistry.getTargetContext();
+ mView = new ImageFloatingTextView(mContext, null, 0, 0);
+ mTextView = new TextView(mContext, null, 0, 0);
+ mTextView.setMaxLines(9);
+ }
+
+ @Test
+ public void testEmpty() {
+ parametrizedTest("");
+ }
+
+ @Test
+ public void testSingleLine() {
+ parametrizedTest("Hello, World!");
+ }
+
+ @Test
+ public void testTwoLine() {
+ parametrizedTest("Hello, World!\nWhat a nice day!");
+ }
+
+ @Test
+ public void testShort() {
+ parametrizedTest("Hello, World! What a nice day! Let's try some more text. "
+ + "Yada yada, yada yada. Lorem ipsum dolor sit amet.");
+ }
+
+ @Test
+ public void testLong() {
+ parametrizedTest("Hello, World! What a nice day! Let's try some more text. "
+ + "Yada yada, yada yada. Lorem ipsum dolor sit amet."
+ + "Yada yada, yada yada. Lorem ipsum dolor sit amet."
+ + "Yada yada, yada yada. Lorem ipsum dolor sit amet."
+ + "Yada yada, yada yada. Lorem ipsum dolor sit amet."
+ + "Yada yada, yada yada. Lorem ipsum dolor sit amet."
+ + "Yada yada, yada yada. Lorem ipsum dolor sit amet."
+ + "Yada yada, yada yada. Lorem ipsum dolor sit amet."
+ + "Yada yada, yada yada. Lorem ipsum dolor sit amet."
+ + "Yada yada, yada yada. Lorem ipsum dolor sit amet."
+ + "Yada yada, yada yada. Lorem ipsum dolor sit amet."
+ + "Yada yada, yada yada. Lorem ipsum dolor sit amet."
+ + "Yada yada, yada yada. Lorem ipsum dolor sit amet."
+ + "Yada yada, yada yada. Lorem ipsum dolor sit amet."
+ + "Yada yada, yada yada. Lorem ipsum dolor sit amet."
+ + "Yada yada, yada yada. Lorem ipsum dolor sit amet."
+ + "Yada yada, yada yada. Lorem ipsum dolor sit amet."
+ + "Yada yada, yada yada. Lorem ipsum dolor sit amet."
+ + "Yada yada, yada yada. Lorem ipsum dolor sit amet."
+ + "Yada yada, yada yada. Lorem ipsum dolor sit amet."
+ + "Yada yada, yada yada. Lorem ipsum dolor sit amet."
+ + "Yada yada, yada yada. Lorem ipsum dolor sit amet."
+ + "Yada yada, yada yada. Lorem ipsum dolor sit amet."
+ + "Yada yada, yada yada. Lorem ipsum dolor sit amet."
+ + "Yada yada, yada yada. Lorem ipsum dolor sit amet."
+ + "Yada yada, yada yada. Lorem ipsum dolor sit amet."
+ + "Yada yada, yada yada. Lorem ipsum dolor sit amet."
+ + "Yada yada, yada yada. Lorem ipsum dolor sit amet."
+ + "Yada yada, yada yada. Lorem ipsum dolor sit amet."
+ + "Yada yada, yada yada. Lorem ipsum dolor sit amet."
+ + "Yada yada, yada yada. Lorem ipsum dolor sit amet."
+ + "Yada yada, yada yada. Lorem ipsum dolor sit amet."
+ + "Yada yada, yada yada. Lorem ipsum dolor sit amet."
+ + "Yada yada, yada yada. Lorem ipsum dolor sit amet.");
+ }
+
+ private void parametrizedTest(CharSequence text) {
+ int heightMeasureSpec = MeasureSpec.makeMeasureSpec(500, MeasureSpec.AT_MOST);
+ int widthMeasureSpec = MeasureSpec.makeMeasureSpec(500, MeasureSpec.EXACTLY);
+
+ mTextView.setText(text);
+ mView.setText(text);
+
+ mTextView.measure(widthMeasureSpec, heightMeasureSpec);
+ mView.measure(widthMeasureSpec, heightMeasureSpec);
+
+ assertEquals(mTextView.getMeasuredHeight(), mView.getMeasuredHeight());
+ }
+}
diff --git a/core/tests/coretests/src/com/android/internal/widget/MessagingLinearLayoutTest.java b/core/tests/coretests/src/com/android/internal/widget/MessagingLinearLayoutTest.java
new file mode 100644
index 0000000..75b2c1d
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/widget/MessagingLinearLayoutTest.java
@@ -0,0 +1,223 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.widget;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.content.Context;
+import android.os.Debug;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.espresso.core.deps.guava.base.Function;
+import android.support.test.filters.SmallTest;
+import android.view.LayoutInflater;
+import android.view.View.MeasureSpec;
+
+import com.android.frameworks.coretests.R;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import java.lang.reflect.Field;
+
+@SmallTest
+public class MessagingLinearLayoutTest {
+
+ public static final int WIDTH_SPEC = MeasureSpec.makeMeasureSpec(500, MeasureSpec.EXACTLY);
+ public static final int HEIGHT_SPEC = MeasureSpec.makeMeasureSpec(400, MeasureSpec.AT_MOST);
+ private Context mContext;
+ private MessagingLinearLayout mView;
+
+ @Before
+ public void setup() {
+ mContext = InstrumentationRegistry.getTargetContext();
+ // maxHeight: 300px
+ // spacing: 50px
+ mView = (MessagingLinearLayout) LayoutInflater.from(mContext).inflate(
+ R.layout.messaging_linear_layout_test, null);
+ }
+
+ @Test
+ public void testSingleChild() {
+ FakeImageFloatingTextView child = fakeChild((i) -> 3);
+
+ mView.setNumIndentLines(2);
+ mView.addView(child);
+
+ mView.measure(WIDTH_SPEC, HEIGHT_SPEC);
+ mView.layout(0, 0, mView.getMeasuredWidth(), mView.getMeasuredHeight());
+
+ assertEquals(3, child.getNumIndentLines());
+ assertFalse(child.isHidden());
+ assertEquals(150, mView.getMeasuredHeight());
+ }
+
+ @Test
+ public void testLargeSmall() {
+ FakeImageFloatingTextView child1 = fakeChild((i) -> 3);
+ FakeImageFloatingTextView child2 = fakeChild((i) -> 1);
+
+ mView.setNumIndentLines(2);
+ mView.addView(child1);
+ mView.addView(child2);
+
+ mView.measure(WIDTH_SPEC, HEIGHT_SPEC);
+ mView.layout(0, 0, mView.getMeasuredWidth(), mView.getMeasuredHeight());
+
+ assertEquals(3, child1.getNumIndentLines());
+ assertEquals(0, child2.getNumIndentLines());
+ assertFalse(child1.isHidden());
+ assertFalse(child2.isHidden());
+ assertEquals(205, mView.getMeasuredHeight());
+ }
+
+ @Test
+ public void testSmallSmall() {
+ FakeImageFloatingTextView child1 = fakeChild((i) -> 1);
+ FakeImageFloatingTextView child2 = fakeChild((i) -> 1);
+
+ mView.setNumIndentLines(2);
+ mView.addView(child1);
+ mView.addView(child2);
+
+ mView.measure(WIDTH_SPEC, HEIGHT_SPEC);
+ mView.layout(0, 0, mView.getMeasuredWidth(), mView.getMeasuredHeight());
+
+ assertEquals(2, child1.getNumIndentLines());
+ assertEquals(1, child2.getNumIndentLines());
+ assertFalse(child1.isHidden());
+ assertFalse(child2.isHidden());
+ assertEquals(105, mView.getMeasuredHeight());
+ }
+
+ @Test
+ public void testLargeLarge() {
+ FakeImageFloatingTextView child1 = fakeChild((i) -> 7);
+ FakeImageFloatingTextView child2 = fakeChild((i) -> 7);
+
+ mView.setNumIndentLines(2);
+ mView.addView(child1);
+ mView.addView(child2);
+
+ mView.measure(WIDTH_SPEC, HEIGHT_SPEC);
+ mView.layout(0, 0, mView.getMeasuredWidth(), mView.getMeasuredHeight());
+
+ assertEquals(3, child2.getNumIndentLines());
+ assertTrue(child1.isHidden());
+ assertFalse(child2.isHidden());
+ assertEquals(300, mView.getMeasuredHeight());
+ }
+
+ @Test
+ public void testLargeSmall_largeWrapsWith3indentbutnot3_andHitsMax() {
+ FakeImageFloatingTextView child1 = fakeChild((i) -> i > 2 ? 5 : 4);
+ FakeImageFloatingTextView child2 = fakeChild((i) -> 1);
+
+ mView.setNumIndentLines(2);
+ mView.addView(child1);
+ mView.addView(child2);
+
+ mView.measure(WIDTH_SPEC, HEIGHT_SPEC);
+ mView.layout(0, 0, mView.getMeasuredWidth(), mView.getMeasuredHeight());
+
+ assertTrue(child1.isHidden());
+ assertFalse(child2.isHidden());
+ assertEquals(50, mView.getMeasuredHeight());
+ assertEquals(2, child2.getNumIndentLines());
+ }
+
+ @Test
+ public void testLargeSmall_largeWrapsWith3indentbutnot3() {
+ FakeImageFloatingTextView child1 = fakeChild((i) -> i > 2 ? 4 : 3);
+ FakeImageFloatingTextView child2 = fakeChild((i) -> 1);
+
+ mView.setNumIndentLines(2);
+ mView.addView(child1);
+ mView.addView(child2);
+
+ mView.measure(WIDTH_SPEC, HEIGHT_SPEC);
+ mView.layout(0, 0, mView.getMeasuredWidth(), mView.getMeasuredHeight());
+
+ assertFalse(child1.isHidden());
+ assertFalse(child2.isHidden());
+ assertEquals(255, mView.getMeasuredHeight());
+ assertEquals(3, child1.getNumIndentLines());
+ assertEquals(0, child2.getNumIndentLines());
+ }
+
+ private class FakeImageFloatingTextView extends ImageFloatingTextView {
+
+ public static final int LINE_HEIGHT = 50;
+ private final Function<Integer, Integer> mLinesForIndent;
+ private int mNumIndentLines;
+
+ public FakeImageFloatingTextView(Context context,
+ Function<Integer, Integer> linesForIndent) {
+ super(context, null, 0, 0);
+ mLinesForIndent = linesForIndent;
+ }
+
+ @Override
+ public boolean setNumIndentLines(int lines) {
+ boolean changed = (mNumIndentLines != lines);
+ mNumIndentLines = lines;
+ return changed;
+ }
+
+ public int getNumIndentLines() {
+ return mNumIndentLines;
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ setMeasuredDimension(
+ getDefaultSize(500, widthMeasureSpec),
+ resolveSize(getDesiredHeight(), heightMeasureSpec));
+ }
+
+ @Override
+ public int getLineCount() {
+ return mLinesForIndent.apply(mNumIndentLines);
+ }
+
+ public int getDesiredHeight() {
+ return LINE_HEIGHT * getLineCount();
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+ // swallow
+ }
+
+ public boolean isHidden() {
+ MessagingLinearLayout.LayoutParams lp =
+ (MessagingLinearLayout.LayoutParams) getLayoutParams();
+ try {
+ Field hide = MessagingLinearLayout.LayoutParams.class.getDeclaredField("hide");
+ hide.setAccessible(true);
+ return hide.getBoolean(lp);
+ } catch (ReflectiveOperationException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ }
+
+ private FakeImageFloatingTextView fakeChild(Function<Integer,Integer> linesForIndent) {
+ return new FakeImageFloatingTextView(mContext, linesForIndent);
+ }
+}
diff --git a/core/tests/utiltests/Android.mk b/core/tests/utiltests/Android.mk
index 6e415f4..46a0d9b 100644
--- a/core/tests/utiltests/Android.mk
+++ b/core/tests/utiltests/Android.mk
@@ -12,6 +12,8 @@
LOCAL_SRC_FILES := $(call all-java-files-under, src)
LOCAL_SRC_FILES += src/android/util/IRemoteMemoryIntArray.aidl
+LOCAL_JNI_SHARED_LIBRARIES := libmemoryintarraytest libcutils libc++
+
LOCAL_STATIC_JAVA_LIBRARIES := \
android-support-test \
frameworks-base-testutils \
diff --git a/core/tests/utiltests/jni/Android.bp b/core/tests/utiltests/jni/Android.bp
new file mode 100644
index 0000000..e9a4144
--- /dev/null
+++ b/core/tests/utiltests/jni/Android.bp
@@ -0,0 +1,27 @@
+// Copyright (C) 2016 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+cc_library_shared {
+ name: "libmemoryintarraytest",
+ shared_libs: [
+ "libcutils",
+ ],
+ clang: true,
+ stl: "libc++",
+ srcs: [
+ "registration.cpp",
+ "android_util_MemoryIntArrayTest.cpp",
+ ],
+ cflags: ["-Werror"],
+}
\ No newline at end of file
diff --git a/core/tests/utiltests/jni/android_util_MemoryIntArrayTest.cpp b/core/tests/utiltests/jni/android_util_MemoryIntArrayTest.cpp
new file mode 100644
index 0000000..57ee2d5
--- /dev/null
+++ b/core/tests/utiltests/jni/android_util_MemoryIntArrayTest.cpp
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <atomic>
+#include <jni.h>
+#include <cutils/ashmem.h>
+#include <linux/ashmem.h>
+#include <sys/ioctl.h>
+#include <sys/mman.h>
+
+jint android_util_MemoryIntArrayTest_createAshmem(__attribute__((unused)) JNIEnv* env,
+ __attribute__((unused)) jobject clazz,
+ jstring name, jint size)
+{
+
+ if (name == NULL) {
+ return -1;
+ }
+
+ if (size < 0) {
+ return -1;
+ }
+
+ const char* nameStr = env->GetStringUTFChars(name, NULL);
+ const int ashmemSize = sizeof(std::atomic_int) * size;
+ int fd = ashmem_create_region(nameStr, ashmemSize);
+ env->ReleaseStringUTFChars(name, nameStr);
+
+ if (fd < 0) {
+ return -1;
+ }
+
+ int setProtResult = ashmem_set_prot_region(fd, PROT_READ | PROT_WRITE);
+ if (setProtResult < 0) {
+ return -1;
+ }
+
+ return fd;
+}
+
+void android_util_MemoryIntArrayTest_setAshmemSize(__attribute__((unused)) JNIEnv* env,
+ __attribute__((unused)) jobject clazz, jint fd, jint size)
+{
+ if (fd < 0) {
+ return;
+ }
+
+ if (size < 0) {
+ return;
+ }
+
+ ioctl(fd, ASHMEM_SET_SIZE, size);
+}
diff --git a/core/tests/utiltests/jni/registration.cpp b/core/tests/utiltests/jni/registration.cpp
new file mode 100644
index 0000000..0c84d98
--- /dev/null
+++ b/core/tests/utiltests/jni/registration.cpp
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <jni.h>
+
+extern jint android_util_MemoryIntArrayTest_createAshmem(JNIEnv* env,
+ jobject clazz, jstring name, jint size);
+extern void android_util_MemoryIntArrayTest_setAshmemSize(JNIEnv* env,
+ jobject clazz, jint fd, jint size);
+
+extern "C" {
+ JNIEXPORT jint JNICALL Java_android_util_MemoryIntArrayTest_nativeCreateAshmem(
+ JNIEnv * env, jobject obj, jstring name, jint size);
+ JNIEXPORT void JNICALL Java_android_util_MemoryIntArrayTest_nativeSetAshmemSize(
+ JNIEnv * env, jobject obj, jint fd, jint size);
+};
+
+JNIEXPORT jint JNICALL Java_android_util_MemoryIntArrayTest_nativeCreateAshmem(
+ __attribute__((unused)) JNIEnv * env,__attribute__((unused)) jobject obj,
+ jstring name, jint size)
+{
+ return android_util_MemoryIntArrayTest_createAshmem(env, obj, name, size);
+}
+
+JNIEXPORT void JNICALL Java_android_util_MemoryIntArrayTest_nativeSetAshmemSize(
+ __attribute__((unused)) JNIEnv * env,__attribute__((unused)) jobject obj,
+ jint fd, jint size)
+{
+ android_util_MemoryIntArrayTest_setAshmemSize(env, obj, fd, size);
+}
diff --git a/core/tests/utiltests/src/android/util/IRemoteMemoryIntArray.aidl b/core/tests/utiltests/src/android/util/IRemoteMemoryIntArray.aidl
index 0a65fff2..10d14f1 100644
--- a/core/tests/utiltests/src/android/util/IRemoteMemoryIntArray.aidl
+++ b/core/tests/utiltests/src/android/util/IRemoteMemoryIntArray.aidl
@@ -20,11 +20,12 @@
interface IRemoteMemoryIntArray {
MemoryIntArray peekInstance();
- void create(int size, boolean clientWritable);
+ void create(int size);
boolean isWritable();
int get(int index);
void set(int index, int value);
int size();
void close();
boolean isClosed();
+ void accessLastElementInRemoteProcess(in MemoryIntArray array);
}
diff --git a/core/tests/utiltests/src/android/util/MemoryIntArrayTest.java b/core/tests/utiltests/src/android/util/MemoryIntArrayTest.java
index 129e6b7..85817bb 100644
--- a/core/tests/utiltests/src/android/util/MemoryIntArrayTest.java
+++ b/core/tests/utiltests/src/android/util/MemoryIntArrayTest.java
@@ -28,14 +28,22 @@
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.lang.reflect.Field;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
@RunWith(AndroidJUnit4.class)
public class MemoryIntArrayTest {
+ static {
+ System.loadLibrary("cutils");
+ System.loadLibrary("memoryintarraytest");
+ }
@Test
public void testSize() throws Exception {
MemoryIntArray array = null;
try {
- array = new MemoryIntArray(3, false);
+ array = new MemoryIntArray(3);
assertEquals("size must be three", 3, array.size());
} finally {
IoUtils.closeQuietly(array);
@@ -46,7 +54,7 @@
public void testGetSet() throws Exception {
MemoryIntArray array = null;
try {
- array = new MemoryIntArray(3, false);
+ array = new MemoryIntArray(3);
array.set(0, 1);
array.set(1, 2);
@@ -64,7 +72,7 @@
public void testWritable() throws Exception {
MemoryIntArray array = null;
try {
- array = new MemoryIntArray(3, true);
+ array = new MemoryIntArray(3);
assertTrue("Must be mutable", array.isWritable());
} finally {
IoUtils.closeQuietly(array);
@@ -75,7 +83,7 @@
public void testClose() throws Exception {
MemoryIntArray array = null;
try {
- array = new MemoryIntArray(3, false);
+ array = new MemoryIntArray(3);
array.close();
assertTrue("Must be closed", array.isClosed());
} finally {
@@ -90,7 +98,7 @@
MemoryIntArray firstArray = null;
MemoryIntArray secondArray = null;
try {
- firstArray = new MemoryIntArray(3, false);
+ firstArray = new MemoryIntArray(3);
firstArray.set(0, 1);
firstArray.set(1, 2);
@@ -117,7 +125,7 @@
public void testInteractOnceClosed() throws Exception {
MemoryIntArray array = null;
try {
- array = new MemoryIntArray(3, false);
+ array = new MemoryIntArray(3);
array.close();
array.close();
@@ -160,7 +168,7 @@
public void testInteractPutOfBounds() throws Exception {
MemoryIntArray array = null;
try {
- array = new MemoryIntArray(3, false);
+ array = new MemoryIntArray(3);
try {
array.get(-1);
@@ -198,7 +206,7 @@
public void testOverMaxSize() throws Exception {
MemoryIntArray array = null;
try {
- array = new MemoryIntArray(MemoryIntArray.getMaxSize() + 1, false);
+ array = new MemoryIntArray(MemoryIntArray.getMaxSize() + 1);
fail("Cannot use over max size");
} catch (IllegalArgumentException e) {
/* expected */
@@ -209,7 +217,7 @@
@Test
public void testNotMutableByUnprivilegedClients() throws Exception {
- RemoteIntArray remoteIntArray = new RemoteIntArray(1, false);
+ RemoteIntArray remoteIntArray = new RemoteIntArray(1);
try {
assertNotNull("Couldn't get remote instance", remoteIntArray);
MemoryIntArray localIntArray = remoteIntArray.peekInstance();
@@ -230,4 +238,64 @@
remoteIntArray.destroy();
}
}
+
+ @Test
+ public void testAshmemSizeMatchesMemoryIntArraySize() throws Exception {
+ boolean success = false;
+
+ // Get a handle to a remote process to send the fd
+ RemoteIntArray remoteIntArray = new RemoteIntArray(1);
+ try {
+ // Let us try 100 times
+ for (int i = 0; i < 100; i++) {
+ // Create a MemoryIntArray to muck with
+ MemoryIntArray array = new MemoryIntArray(1);
+
+ // Create the fd to stuff in the MemoryIntArray
+ final int fd = nativeCreateAshmem("foo", 1);
+
+ // Replace the fd with our ahsmem region
+ Field fdFiled = MemoryIntArray.class.getDeclaredField("mFd");
+ fdFiled.setAccessible(true);
+ fdFiled.set(array, fd);
+
+ CountDownLatch countDownLatch = new CountDownLatch(2);
+
+ new Thread() {
+ @Override
+ public void run() {
+ for (int i = 2; i < Integer.MAX_VALUE; i++) {
+ if (countDownLatch.getCount() == 1) {
+ countDownLatch.countDown();
+ return;
+ }
+ nativeSetAshmemSize(fd, i);
+ }
+ }
+ }.start();
+
+ try {
+ remoteIntArray.accessLastElementInRemoteProcess(array);
+ } catch (IllegalArgumentException e) {
+ success = true;
+ }
+
+ countDownLatch.countDown();
+ countDownLatch.await(1000, TimeUnit.MILLISECONDS);
+
+ if (success) {
+ break;
+ }
+ }
+ } finally {
+ remoteIntArray.destroy();
+ }
+
+ if (!success) {
+ fail("MemoryIntArray should catch ahshmem size changing under it");
+ }
+ }
+
+ private native int nativeCreateAshmem(String name, int size);
+ private native void nativeSetAshmemSize(int fd, int size);
}
diff --git a/core/tests/utiltests/src/android/util/RemoteIntArray.java b/core/tests/utiltests/src/android/util/RemoteIntArray.java
index 10c325f..7dc3400 100644
--- a/core/tests/utiltests/src/android/util/RemoteIntArray.java
+++ b/core/tests/utiltests/src/android/util/RemoteIntArray.java
@@ -40,7 +40,7 @@
private android.util.IRemoteMemoryIntArray mRemoteInstance;
- public RemoteIntArray(int size, boolean clientWritable) throws IOException, TimeoutException {
+ public RemoteIntArray(int size) throws IOException, TimeoutException {
mIntent.setComponent(new ComponentName(InstrumentationRegistry.getContext(),
RemoteMemoryIntArrayService.class));
synchronized (mLock) {
@@ -48,7 +48,7 @@
bindLocked();
}
try {
- mRemoteInstance.create(size, clientWritable);
+ mRemoteInstance.create(size);
} catch (RemoteException e) {
throw new IOException(e);
}
@@ -148,6 +148,14 @@
}
}
+ public void accessLastElementInRemoteProcess(MemoryIntArray array) {
+ try {
+ mRemoteInstance.accessLastElementInRemoteProcess(array);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
synchronized (mLock) {
diff --git a/core/tests/utiltests/src/android/util/RemoteMemoryIntArrayService.java b/core/tests/utiltests/src/android/util/RemoteMemoryIntArrayService.java
index 35ae9a7..9264c6c 100644
--- a/core/tests/utiltests/src/android/util/RemoteMemoryIntArrayService.java
+++ b/core/tests/utiltests/src/android/util/RemoteMemoryIntArrayService.java
@@ -35,10 +35,10 @@
return new android.util.IRemoteMemoryIntArray.Stub() {
@Override
- public void create(int size, boolean clientWritable) {
+ public void create(int size) {
synchronized (mLock) {
try {
- mArray = new MemoryIntArray(size, clientWritable);
+ mArray = new MemoryIntArray(size);
} catch (IOException e) {
throw new IllegalStateException(e);
}
@@ -109,6 +109,15 @@
return mArray.isClosed();
}
}
+
+ @Override
+ public void accessLastElementInRemoteProcess(MemoryIntArray array) {
+ try {
+ array.get(array.size() - 1);
+ } catch (IOException e) {
+ throw new IllegalStateException(e);
+ }
+ }
};
}
}
diff --git a/packages/CaptivePortalLogin/src/com/android/captiveportallogin/CaptivePortalLoginActivity.java b/packages/CaptivePortalLogin/src/com/android/captiveportallogin/CaptivePortalLoginActivity.java
index bb8eb2c..03f0b4d 100644
--- a/packages/CaptivePortalLogin/src/com/android/captiveportallogin/CaptivePortalLoginActivity.java
+++ b/packages/CaptivePortalLogin/src/com/android/captiveportallogin/CaptivePortalLoginActivity.java
@@ -55,12 +55,14 @@
import java.util.Random;
public class CaptivePortalLoginActivity extends Activity {
- private static final String TAG = "CaptivePortalLogin";
+ private static final String TAG = CaptivePortalLoginActivity.class.getSimpleName();
+ private static final boolean DBG = true;
+
private static final int SOCKET_TIMEOUT_MS = 10000;
private enum Result { DISMISSED, UNWANTED, WANTED_AS_IS };
- private URL mURL;
+ private URL mUrl;
private Network mNetwork;
private CaptivePortal mCaptivePortal;
private NetworkCallback mNetworkCallback;
@@ -72,17 +74,18 @@
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mCm = ConnectivityManager.from(this);
- String url = getIntent().getStringExtra(ConnectivityManager.EXTRA_CAPTIVE_PORTAL_URL);
- if (url == null) url = mCm.getCaptivePortalServerUrl();
- try {
- mURL = new URL(url);
- } catch (MalformedURLException e) {
- // System misconfigured, bail out in a way that at least provides network access.
- Log.e(TAG, "Invalid captive portal URL, url=" + url);
- done(Result.WANTED_AS_IS);
- }
mNetwork = getIntent().getParcelableExtra(ConnectivityManager.EXTRA_NETWORK);
mCaptivePortal = getIntent().getParcelableExtra(ConnectivityManager.EXTRA_CAPTIVE_PORTAL);
+ mUrl = getUrl();
+ if (mUrl == null) {
+ // getUrl() failed to parse the url provided in the intent: bail out in a way that
+ // at least provides network access.
+ done(Result.WANTED_AS_IS);
+ return;
+ }
+ if (DBG) {
+ Log.d(TAG, String.format("onCreate for %s", mUrl.toString()));
+ }
// Also initializes proxy system properties.
mCm.bindProcessToNetwork(mNetwork);
@@ -149,6 +152,9 @@
}
private void done(Result result) {
+ if (DBG) {
+ Log.d(TAG, String.format("Result %s for %s", result.name(), mUrl.toString()));
+ }
if (mNetworkCallback != null) {
mCm.unregisterNetworkCallback(mNetworkCallback);
mNetworkCallback = null;
@@ -185,22 +191,31 @@
@Override
public boolean onOptionsItemSelected(MenuItem item) {
- int id = item.getItemId();
- if (id == R.id.action_use_network) {
- done(Result.WANTED_AS_IS);
- return true;
+ final Result result;
+ final String action;
+ final int id = item.getItemId();
+ switch (id) {
+ case R.id.action_use_network:
+ result = Result.WANTED_AS_IS;
+ action = "USE_NETWORK";
+ break;
+ case R.id.action_do_not_use_network:
+ result = Result.UNWANTED;
+ action = "DO_NOT_USE_NETWORK";
+ break;
+ default:
+ return super.onOptionsItemSelected(item);
}
- if (id == R.id.action_do_not_use_network) {
- done(Result.UNWANTED);
- return true;
+ if (DBG) {
+ Log.d(TAG, String.format("onOptionsItemSelect %s for %s", action, mUrl.toString()));
}
- return super.onOptionsItemSelected(item);
+ done(result);
+ return true;
}
@Override
public void onDestroy() {
super.onDestroy();
-
if (mNetworkCallback != null) {
mCm.unregisterNetworkCallback(mNetworkCallback);
mNetworkCallback = null;
@@ -215,10 +230,27 @@
} catch (InterruptedException e) {
}
}
- startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(mURL.toString())));
+ final String url = mUrl.toString();
+ if (DBG) {
+ Log.d(TAG, "starting activity with intent ACTION_VIEW for " + url);
+ }
+ startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(url)));
}
}
+ private URL getUrl() {
+ String url = getIntent().getStringExtra(ConnectivityManager.EXTRA_CAPTIVE_PORTAL_URL);
+ if (url == null) {
+ url = mCm.getCaptivePortalServerUrl();
+ }
+ try {
+ return new URL(url);
+ } catch (MalformedURLException e) {
+ Log.e(TAG, "Invalid captive portal URL " + url);
+ }
+ return null;
+ }
+
private void testForCaptivePortal() {
new Thread(new Runnable() {
public void run() {
@@ -230,7 +262,7 @@
HttpURLConnection urlConnection = null;
int httpResponseCode = 500;
try {
- urlConnection = (HttpURLConnection) mURL.openConnection();
+ urlConnection = (HttpURLConnection) mUrl.openConnection();
urlConnection.setInstanceFollowRedirects(false);
urlConnection.setConnectTimeout(SOCKET_TIMEOUT_MS);
urlConnection.setReadTimeout(SOCKET_TIMEOUT_MS);
@@ -292,7 +324,7 @@
// settings. Now prompt the WebView read the Network-specific proxy settings.
setWebViewProxy();
// Load the real page.
- view.loadUrl(mURL.toString());
+ view.loadUrl(mUrl.toString());
return;
} else if (mPagesLoaded == 2) {
// Prevent going back to empty first page.
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/GenerationRegistry.java b/packages/SettingsProvider/src/com/android/providers/settings/GenerationRegistry.java
index 222cc5c..3e6c3f2 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/GenerationRegistry.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/GenerationRegistry.java
@@ -119,7 +119,7 @@
// and twice max user count for system and secure.
final int size = 1 + 2 + 10 + 2 * UserManager.getMaxSupportedUsers();
try {
- mBackingStore = new MemoryIntArray(size, false);
+ mBackingStore = new MemoryIntArray(size);
if (DEBUG) {
Slog.e(LOG_TAG, "Created backing store " + mBackingStore);
}
diff --git a/packages/SystemUI/res/color/qs_tile_text.xml b/packages/SystemUI/res/color/qs_tile_text.xml
deleted file mode 100644
index 90e0bce..0000000
--- a/packages/SystemUI/res/color/qs_tile_text.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-
-<!--
- ~ Copyright (C) 2015 The Android Open Source Project
- ~
- ~ Licensed under the Apache License, Version 2.0 (the "License");
- ~ you may not use this file except in compliance with the License.
- ~ You may obtain a copy of the License at
- ~
- ~ http://www.apache.org/licenses/LICENSE-2.0
- ~
- ~ Unless required by applicable law or agreed to in writing, software
- ~ distributed under the License is distributed on an "AS IS" BASIS,
- ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- ~ See the License for the specific language governing permissions and
- ~ limitations under the License
- -->
-
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
- <item android:state_enabled="false" android:color="@color/qs_tile_disabled_color" />
- <item android:color="#B3FFFFFF" /> <!-- 70% white -->
-</selector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/ic_qs_no_sim.xml b/packages/SystemUI/res/drawable/ic_qs_no_sim.xml
index bd46012..2d831b2 100644
--- a/packages/SystemUI/res/drawable/ic_qs_no_sim.xml
+++ b/packages/SystemUI/res/drawable/ic_qs_no_sim.xml
@@ -17,7 +17,8 @@
android:width="32dp"
android:height="32dp"
android:viewportWidth="24.0"
- android:viewportHeight="24.0">
+ android:viewportHeight="24.0"
+ android:tint="?android:attr/colorControlNormal">
<path
android:fillColor="#4DFFFFFF"
diff --git a/packages/SystemUI/res/drawable/ic_qs_signal_0.xml b/packages/SystemUI/res/drawable/ic_qs_signal_0.xml
index b78d3bf..0673848 100644
--- a/packages/SystemUI/res/drawable/ic_qs_signal_0.xml
+++ b/packages/SystemUI/res/drawable/ic_qs_signal_0.xml
@@ -18,7 +18,8 @@
android:width="32.0dp"
android:height="32.0dp"
android:viewportWidth="24.0"
- android:viewportHeight="24.0">
+ android:viewportHeight="24.0"
+ android:tint="?android:attr/colorControlNormal">
<path
android:pathData="M14.1,14.1l7.9,0.0 0.0,-11.5 -20.0,20.0 12.1,0.0z"
android:fillAlpha="0.3"
diff --git a/packages/SystemUI/res/drawable/ic_qs_signal_1.xml b/packages/SystemUI/res/drawable/ic_qs_signal_1.xml
index e055de7..fbf9e71 100644
--- a/packages/SystemUI/res/drawable/ic_qs_signal_1.xml
+++ b/packages/SystemUI/res/drawable/ic_qs_signal_1.xml
@@ -18,7 +18,8 @@
android:width="32.0dp"
android:height="32.0dp"
android:viewportWidth="24.0"
- android:viewportHeight="24.0">
+ android:viewportHeight="24.0"
+ android:tint="?android:attr/colorControlNormal">
<path
android:pathData="M10.0,14.6l-8.0,8.0l8.0,0.0l0,-8z"
android:fillColor="#FFFFFF"/>
diff --git a/packages/SystemUI/res/drawable/ic_qs_signal_1x.xml b/packages/SystemUI/res/drawable/ic_qs_signal_1x.xml
index 71c40df..3a55623 100644
--- a/packages/SystemUI/res/drawable/ic_qs_signal_1x.xml
+++ b/packages/SystemUI/res/drawable/ic_qs_signal_1x.xml
@@ -17,7 +17,8 @@
android:width="16.0dp"
android:height="32dp"
android:viewportWidth="12.0"
- android:viewportHeight="24.0">
+ android:viewportHeight="24.0"
+ android:tint="?android:attr/colorControlNormal">
<path
android:fillColor="#FFFFFFFF"
android:pathData="M3.500000,11.000000L1.800000,11.000000L1.800000,4.400000L0.200000,5.100000L0.200000,3.700000l3.100000,-1.300000l0.200000,0.000000L3.500000,11.000000z"/>
diff --git a/packages/SystemUI/res/drawable/ic_qs_signal_2.xml b/packages/SystemUI/res/drawable/ic_qs_signal_2.xml
index 8a48817..e9f5a0b 100644
--- a/packages/SystemUI/res/drawable/ic_qs_signal_2.xml
+++ b/packages/SystemUI/res/drawable/ic_qs_signal_2.xml
@@ -18,7 +18,8 @@
android:width="32.0dp"
android:height="32.0dp"
android:viewportWidth="24.0"
- android:viewportHeight="24.0">
+ android:viewportHeight="24.0"
+ android:tint="?android:attr/colorControlNormal">
<path
android:pathData="M14.0,10.6l-12.0,12.0l12.0,0.0L14.0,10.6z"
android:fillColor="#FFFFFF"/>
diff --git a/packages/SystemUI/res/drawable/ic_qs_signal_3.xml b/packages/SystemUI/res/drawable/ic_qs_signal_3.xml
index 39cc94c..769d648 100644
--- a/packages/SystemUI/res/drawable/ic_qs_signal_3.xml
+++ b/packages/SystemUI/res/drawable/ic_qs_signal_3.xml
@@ -18,7 +18,8 @@
android:width="32.0dp"
android:height="32.0dp"
android:viewportWidth="24.0"
- android:viewportHeight="24.0">
+ android:viewportHeight="24.0"
+ android:tint="?android:attr/colorControlNormal">
<path
android:pathData="M14.1,14.1l7.9,0.0 0.0,-11.5 -20.0,20.0 12.1,0.0z"
android:fillAlpha="0.3"
diff --git a/packages/SystemUI/res/drawable/ic_qs_signal_3g.xml b/packages/SystemUI/res/drawable/ic_qs_signal_3g.xml
index e9a57ea..ddd8065 100644
--- a/packages/SystemUI/res/drawable/ic_qs_signal_3g.xml
+++ b/packages/SystemUI/res/drawable/ic_qs_signal_3g.xml
@@ -17,7 +17,8 @@
android:width="17.333334dp"
android:height="32dp"
android:viewportWidth="13.0"
- android:viewportHeight="24.0">
+ android:viewportHeight="24.0"
+ android:tint="?android:attr/colorControlNormal">
<path
android:fillColor="#FFFFFFFF"
android:pathData="M2.000000,6.000000l0.800000,0.000000c0.300000,0.000000 0.500000,-0.100000 0.700000,-0.300000s0.200000,-0.500000 0.200000,-0.900000c0.000000,-0.300000 -0.100000,-0.600000 -0.200000,-0.800000S3.200000,3.700000 2.900000,3.700000C2.700000,3.700000 2.500000,3.800000 2.300000,4.000000S2.100000,4.400000 2.100000,4.700000L0.500000,4.700000C0.500000,4.000000 0.700000,3.400000 1.100000,3.000000s1.000000,-0.600000 1.700000,-0.600000c0.800000,0.000000 1.400000,0.200000 1.900000,0.600000s0.700000,1.000000 0.700000,1.800000c0.000000,0.400000 -0.100000,0.700000 -0.300000,1.100000S4.600000,6.500000 4.300000,6.600000C4.700000,6.800000 5.000000,7.100000 5.200000,7.400000s0.300000,0.700000 0.300000,1.200000c0.000000,0.800000 -0.200000,1.400000 -0.700000,1.800000s-1.100000,0.700000 -1.900000,0.700000c-0.700000,0.000000 -1.300000,-0.200000 -1.800000,-0.600000s-0.700000,-1.000000 -0.700000,-1.800000L2.000000,8.700000C2.000000,9.000000 2.100000,9.300000 2.300000,9.500000s0.400000,0.300000 0.600000,0.300000c0.300000,0.000000 0.500000,-0.100000 0.700000,-0.300000S3.900000,9.000000 3.900000,8.600000c0.000000,-0.500000 -0.100000,-0.800000 -0.300000,-1.000000S3.200000,7.300000 2.800000,7.300000L2.000000,7.300000L2.000000,6.000000z"/>
diff --git a/packages/SystemUI/res/drawable/ic_qs_signal_4.xml b/packages/SystemUI/res/drawable/ic_qs_signal_4.xml
index 012e95e..1bec1b8 100644
--- a/packages/SystemUI/res/drawable/ic_qs_signal_4.xml
+++ b/packages/SystemUI/res/drawable/ic_qs_signal_4.xml
@@ -18,7 +18,8 @@
android:width="32.0dp"
android:height="32.0dp"
android:viewportWidth="24.0"
- android:viewportHeight="24.0">
+ android:viewportHeight="24.0"
+ android:tint="?android:attr/colorControlNormal">
<path
android:pathData="M14.1,14.1l7.9,0.0 0.0,-11.5 -20.0,20.0 12.1,0.0z"
android:fillColor="#FFFFFF"/>
diff --git a/packages/SystemUI/res/drawable/ic_qs_signal_4g.xml b/packages/SystemUI/res/drawable/ic_qs_signal_4g.xml
index 42045d1..8e1f8eb 100644
--- a/packages/SystemUI/res/drawable/ic_qs_signal_4g.xml
+++ b/packages/SystemUI/res/drawable/ic_qs_signal_4g.xml
@@ -17,7 +17,8 @@
android:width="16.0dp"
android:height="32dp"
android:viewportWidth="12.0"
- android:viewportHeight="24.0">
+ android:viewportHeight="24.0"
+ android:tint="?android:attr/colorControlNormal">
<path
android:fillColor="#FFFFFFFF"
android:pathData="M4.600000,7.800000l0.700000,0.000000l0.000000,1.300000L4.600000,9.100000L4.600000,11.000000L3.000000,11.000000L3.000000,9.200000L0.100000,9.200000L0.000000,8.100000L3.000000,2.500000l1.700000,0.000000L4.700000,7.800000zM1.600000,7.800000L3.000000,7.800000l0.000000,-3.000000L2.900000,5.000000L1.600000,7.800000z"/>
diff --git a/packages/SystemUI/res/drawable/ic_qs_signal_4g_plus.xml b/packages/SystemUI/res/drawable/ic_qs_signal_4g_plus.xml
index 4d7f325..e0c6b68 100644
--- a/packages/SystemUI/res/drawable/ic_qs_signal_4g_plus.xml
+++ b/packages/SystemUI/res/drawable/ic_qs_signal_4g_plus.xml
@@ -17,7 +17,8 @@
android:width="24.0dp"
android:height="24.0dp"
android:viewportWidth="24.0"
- android:viewportHeight="24.0">
+ android:viewportHeight="24.0"
+ android:tint="?android:attr/colorControlNormal">
<path
android:fillColor="#FFFFFFFF"
android:pathData="M4.6,7.8l0.7,0.0l0.0,1.3L4.6,9.1L4.6,11.0L3.0,11.0L3.0,9.2L0.1,9.2L0.0,8.2l3.0,-5.7l1.7,0.0L4.6,7.8L4.6,7.8zM1.7,7.8L3.0,7.8l0.0,-3.0L2.9,5.0L1.7,7.8z"/>
diff --git a/packages/SystemUI/res/drawable/ic_qs_signal_carrier_network_change.xml b/packages/SystemUI/res/drawable/ic_qs_signal_carrier_network_change.xml
index 96e2fd4..1c068e5 100644
--- a/packages/SystemUI/res/drawable/ic_qs_signal_carrier_network_change.xml
+++ b/packages/SystemUI/res/drawable/ic_qs_signal_carrier_network_change.xml
@@ -17,7 +17,8 @@
android:width="32dp"
android:height="32dp"
android:viewportWidth="24.0"
- android:viewportHeight="24.0">
+ android:viewportHeight="24.0"
+ android:tint="?android:attr/colorControlNormal">
<path
android:name="dot1"
android:fillColor="#FFFFFFFF"
diff --git a/packages/SystemUI/res/drawable/ic_qs_signal_disabled.xml b/packages/SystemUI/res/drawable/ic_qs_signal_disabled.xml
index 4f253e3..0a85392 100644
--- a/packages/SystemUI/res/drawable/ic_qs_signal_disabled.xml
+++ b/packages/SystemUI/res/drawable/ic_qs_signal_disabled.xml
@@ -18,7 +18,8 @@
android:width="32dp"
android:height="32dp"
android:viewportWidth="24.0"
- android:viewportHeight="24.0">
+ android:viewportHeight="24.0"
+ android:tint="?android:attr/colorControlNormal">
<path
android:fillColor="#4DFFFFFF"
android:pathData="M21.799999,22.299999l-1.199999,-1.299999 0.000000,0.000000 -9.600000,-10.000000 0.000000,0.000000 -6.400000,-6.700000 -1.300000,1.300000 6.400000,6.700000 -8.700000,8.700000 16.900000,0.000000 2.600000,2.700001z"/>
diff --git a/packages/SystemUI/res/drawable/ic_qs_signal_e.xml b/packages/SystemUI/res/drawable/ic_qs_signal_e.xml
index e49a409..4c90421 100644
--- a/packages/SystemUI/res/drawable/ic_qs_signal_e.xml
+++ b/packages/SystemUI/res/drawable/ic_qs_signal_e.xml
@@ -17,7 +17,8 @@
android:width="6.6666665dp"
android:height="32dp"
android:viewportWidth="5.0"
- android:viewportHeight="24.0">
+ android:viewportHeight="24.0"
+ android:tint="?android:attr/colorControlNormal">
<path
android:fillColor="#FFFFFFFF"
android:pathData="M4.400000,7.300000L1.700000,7.300000l0.000000,2.400000l3.300000,0.000000L5.000000,11.000000L0.000000,11.000000L0.000000,2.500000l4.900000,0.000000l0.000000,1.300000L1.700000,3.800000l0.000000,2.100000l2.800000,0.000000L4.500000,7.300000z"/>
diff --git a/packages/SystemUI/res/drawable/ic_qs_signal_full_0.xml b/packages/SystemUI/res/drawable/ic_qs_signal_full_0.xml
index 326373d..db4df76 100644
--- a/packages/SystemUI/res/drawable/ic_qs_signal_full_0.xml
+++ b/packages/SystemUI/res/drawable/ic_qs_signal_full_0.xml
@@ -18,7 +18,8 @@
android:width="32dp"
android:height="32dp"
android:viewportWidth="24.0"
- android:viewportHeight="24.0">
+ android:viewportHeight="24.0"
+ android:tint="?android:attr/colorControlNormal">
<path
android:fillColor="#4DFFFFFF"
android:pathData="M2.000000,22.000000l20.000000,0.000000 0.000000,-20.000000z"/>
diff --git a/packages/SystemUI/res/drawable/ic_qs_signal_full_1.xml b/packages/SystemUI/res/drawable/ic_qs_signal_full_1.xml
index 8baa4eb..6e5439d 100644
--- a/packages/SystemUI/res/drawable/ic_qs_signal_full_1.xml
+++ b/packages/SystemUI/res/drawable/ic_qs_signal_full_1.xml
@@ -18,7 +18,8 @@
android:width="32dp"
android:height="32dp"
android:viewportWidth="24.0"
- android:viewportHeight="24.0">
+ android:viewportHeight="24.0"
+ android:tint="?android:attr/colorControlNormal">
<path
android:fillColor="#4DFFFFFF"
android:pathData="M2.0,22.0l20.0,0.0 0.0,-20.0z"/>
diff --git a/packages/SystemUI/res/drawable/ic_qs_signal_full_2.xml b/packages/SystemUI/res/drawable/ic_qs_signal_full_2.xml
index bf19a71..194edc3 100644
--- a/packages/SystemUI/res/drawable/ic_qs_signal_full_2.xml
+++ b/packages/SystemUI/res/drawable/ic_qs_signal_full_2.xml
@@ -18,7 +18,8 @@
android:width="32dp"
android:height="32dp"
android:viewportWidth="24.0"
- android:viewportHeight="24.0">
+ android:viewportHeight="24.0"
+ android:tint="?android:attr/colorControlNormal">
<path
android:fillColor="#4DFFFFFF"
android:pathData="M2.000000,22.000000l20.000000,0.000000 0.000000,-20.000000z"/>
diff --git a/packages/SystemUI/res/drawable/ic_qs_signal_full_3.xml b/packages/SystemUI/res/drawable/ic_qs_signal_full_3.xml
index 01839e85..b57af5c 100644
--- a/packages/SystemUI/res/drawable/ic_qs_signal_full_3.xml
+++ b/packages/SystemUI/res/drawable/ic_qs_signal_full_3.xml
@@ -18,7 +18,8 @@
android:width="32dp"
android:height="32dp"
android:viewportWidth="24.0"
- android:viewportHeight="24.0">
+ android:viewportHeight="24.0"
+ android:tint="?android:attr/colorControlNormal">
<path
android:fillColor="#4DFFFFFF"
android:pathData="M2.000000,22.000000l20.000000,0.000000 0.000000,-20.000000z"/>
diff --git a/packages/SystemUI/res/drawable/ic_qs_signal_full_4.xml b/packages/SystemUI/res/drawable/ic_qs_signal_full_4.xml
index 48151ad..7d754a8 100644
--- a/packages/SystemUI/res/drawable/ic_qs_signal_full_4.xml
+++ b/packages/SystemUI/res/drawable/ic_qs_signal_full_4.xml
@@ -18,7 +18,8 @@
android:width="32dp"
android:height="32dp"
android:viewportWidth="24.0"
- android:viewportHeight="24.0">
+ android:viewportHeight="24.0"
+ android:tint="?android:attr/colorControlNormal">
<path
android:fillColor="#FFFFFFFF"
android:pathData="M2.000000,22.000000l20.000000,0.000000 0.000000,-20.000000z"/>
diff --git a/packages/SystemUI/res/drawable/ic_qs_signal_g.xml b/packages/SystemUI/res/drawable/ic_qs_signal_g.xml
index 9d42a44..64aadf9 100644
--- a/packages/SystemUI/res/drawable/ic_qs_signal_g.xml
+++ b/packages/SystemUI/res/drawable/ic_qs_signal_g.xml
@@ -17,7 +17,8 @@
android:width="9.333333dp"
android:height="32dp"
android:viewportWidth="7.0"
- android:viewportHeight="24.0">
+ android:viewportHeight="24.0"
+ android:tint="?android:attr/colorControlNormal">
<path
android:fillColor="#FFFFFFFF"
android:pathData="M6.500000,9.900000c-0.200000,0.400000 -0.600000,0.700000 -1.000000,0.900000s-1.000000,0.400000 -1.800000,0.400000c-0.900000,0.000000 -1.700000,-0.300000 -2.200000,-0.800000S0.700000,9.000000 0.700000,7.900000L0.700000,5.600000c0.000000,-1.100000 0.300000,-1.900000 0.800000,-2.400000s1.200000,-0.800000 2.100000,-0.800000c1.000000,0.000000 1.700000,0.200000 2.100000,0.700000s0.700000,1.200000 0.700000,2.100000L4.700000,5.200000c0.000000,-0.500000 -0.100000,-0.900000 -0.200000,-1.100000S4.000000,3.700000 3.600000,3.700000c-0.400000,0.000000 -0.700000,0.200000 -0.900000,0.500000S2.300000,5.000000 2.300000,5.600000l0.000000,2.300000c0.000000,0.700000 0.100000,1.100000 0.300000,1.400000s0.600000,0.500000 1.000000,0.500000c0.300000,0.000000 0.600000,0.000000 0.700000,-0.100000s0.300000,-0.200000 0.400000,-0.300000L4.700000,7.800000L3.500000,7.800000L3.500000,6.600000l2.900000,0.000000L6.400000,9.900000z"/>
diff --git a/packages/SystemUI/res/drawable/ic_qs_signal_h.xml b/packages/SystemUI/res/drawable/ic_qs_signal_h.xml
index f509d71..31918a9 100644
--- a/packages/SystemUI/res/drawable/ic_qs_signal_h.xml
+++ b/packages/SystemUI/res/drawable/ic_qs_signal_h.xml
@@ -17,7 +17,8 @@
android:width="8.0dp"
android:height="32dp"
android:viewportWidth="6.0"
- android:viewportHeight="24.0">
+ android:viewportHeight="24.0"
+ android:tint="?android:attr/colorControlNormal">
<path
android:fillColor="#FFFFFFFF"
android:pathData="M6.000000,11.000000L4.400000,11.000000L4.400000,7.500000L1.700000,7.500000L1.700000,11.000000L0.000000,11.000000L0.000000,2.500000l1.700000,0.000000l0.000000,3.700000l2.700000,0.000000L4.400000,2.500000L6.000000,2.500000L6.000000,11.000000z"/>
diff --git a/packages/SystemUI/res/drawable/ic_qs_signal_lte.xml b/packages/SystemUI/res/drawable/ic_qs_signal_lte.xml
index b7242e6..8766075 100644
--- a/packages/SystemUI/res/drawable/ic_qs_signal_lte.xml
+++ b/packages/SystemUI/res/drawable/ic_qs_signal_lte.xml
@@ -17,7 +17,8 @@
android:width="17.333334dp"
android:height="32dp"
android:viewportWidth="13.0"
- android:viewportHeight="24.0">
+ android:viewportHeight="24.0"
+ android:tint="?android:attr/colorControlNormal">
<path
android:fillColor="#FFFFFFFF"
android:pathData="M2.000000,9.700000l2.000000,0.000000L4.000000,11.000000L0.300000,11.000000L0.300000,2.500000L2.000000,2.500000L2.000000,9.700000z"/>
diff --git a/packages/SystemUI/res/drawable/ic_qs_signal_lte_plus.xml b/packages/SystemUI/res/drawable/ic_qs_signal_lte_plus.xml
index 3af0629..5ff7d85 100644
--- a/packages/SystemUI/res/drawable/ic_qs_signal_lte_plus.xml
+++ b/packages/SystemUI/res/drawable/ic_qs_signal_lte_plus.xml
@@ -17,7 +17,8 @@
android:width="24.0dp"
android:height="24.0dp"
android:viewportWidth="24.0"
- android:viewportHeight="24.0">
+ android:viewportHeight="24.0"
+ android:tint="?android:attr/colorControlNormal">
<path
android:fillColor="#FFFFFFFF"
android:pathData="M2.0,9.7l2.0,0.0L4.0,11.0L0.4,11.0L0.4,2.5L2.0,2.5L2.0,9.7z"/>
diff --git a/packages/SystemUI/res/drawable/ic_qs_signal_no_signal.xml b/packages/SystemUI/res/drawable/ic_qs_signal_no_signal.xml
index f7fd97c..4e65004 100644
--- a/packages/SystemUI/res/drawable/ic_qs_signal_no_signal.xml
+++ b/packages/SystemUI/res/drawable/ic_qs_signal_no_signal.xml
@@ -18,7 +18,8 @@
android:width="32dp"
android:height="32dp"
android:viewportWidth="24.0"
- android:viewportHeight="24.0">
+ android:viewportHeight="24.0"
+ android:tint="?android:attr/colorControlNormal">
<path
android:fillColor="#4DFFFFFF"
android:pathData="M2.000000,22.000000l20.000000,0.000000L22.000000,2.000000L2.000000,22.000000zM20.000000,20.000000L6.800000,20.000000L20.000000,6.800000L20.000000,20.000000z"/>
diff --git a/packages/SystemUI/res/drawable/ic_qs_signal_r.xml b/packages/SystemUI/res/drawable/ic_qs_signal_r.xml
index 66f64c9..2c10dc3f 100644
--- a/packages/SystemUI/res/drawable/ic_qs_signal_r.xml
+++ b/packages/SystemUI/res/drawable/ic_qs_signal_r.xml
@@ -17,7 +17,8 @@
android:width="8.0dp"
android:height="32dp"
android:viewportWidth="6.0"
- android:viewportHeight="24.0">
+ android:viewportHeight="24.0"
+ android:tint="?android:attr/colorControlNormal">
<path
android:fillColor="#FFFFFFFF"
android:pathData="M2.800000,7.900000l-1.000000,0.000000L1.800000,11.000000L0.200000,11.000000L0.200000,2.500000l2.700000,0.000000c0.900000,0.000000 1.500000,0.200000 2.000000,0.700000s0.700000,1.100000 0.700000,1.900000c0.000000,0.600000 -0.100000,1.100000 -0.300000,1.500000S4.800000,7.200000 4.400000,7.400000l1.500000,3.500000L5.900000,11.000000L4.100000,11.000000L2.800000,7.900000zM1.800000,6.500000l1.100000,0.000000c0.400000,0.000000 0.600000,-0.100000 0.800000,-0.400000S4.000000,5.600000 4.000000,5.200000c0.000000,-0.400000 -0.100000,-0.800000 -0.300000,-1.000000S3.300000,3.800000 2.900000,3.800000L1.800000,3.800000L1.800000,6.500000z"/>
diff --git a/packages/SystemUI/res/drawable/qs_dual_tile_caret.xml b/packages/SystemUI/res/drawable/qs_dual_tile_caret.xml
deleted file mode 100644
index 71400db..0000000
--- a/packages/SystemUI/res/drawable/qs_dual_tile_caret.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<!--
-Copyright (C) 2014 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24.0dp"
- android:height="24.0dp"
- android:viewportWidth="48.0"
- android:viewportHeight="48.0">
-
- <path
- android:fillColor="@color/qs_tile_text"
- android:pathData="M14.0,20.0l10.0,10.0 10.0,-10.0z"/>
-</vector>
diff --git a/packages/SystemUI/res/layout/qs_tile_label.xml b/packages/SystemUI/res/layout/qs_tile_label.xml
index cce9c0f..5b0f0df 100644
--- a/packages/SystemUI/res/layout/qs_tile_label.xml
+++ b/packages/SystemUI/res/layout/qs_tile_label.xml
@@ -22,7 +22,7 @@
<TextView android:id="@+id/tile_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:textColor="@color/qs_tile_text"
+ android:textColor="?android:attr/textColorSecondary"
android:gravity="center_horizontal"
android:minLines="2"
android:padding="0dp"
diff --git a/packages/SystemUI/res/layout/remote_input.xml b/packages/SystemUI/res/layout/remote_input.xml
index 73f26e2..e4ea08e 100644
--- a/packages/SystemUI/res/layout/remote_input.xml
+++ b/packages/SystemUI/res/layout/remote_input.xml
@@ -42,7 +42,7 @@
android:singleLine="true"
android:ellipsize="start"
android:inputType="textShortMessage|textAutoCorrect|textCapSentences"
- android:imeOptions="actionNone|flagNoExtractUi|flagNoFullscreen" />
+ android:imeOptions="actionSend|flagNoExtractUi|flagNoFullscreen" />
<FrameLayout
android:layout_width="wrap_content"
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 29b5b62..a7d4aa0 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -81,6 +81,9 @@
<!-- Height of a heads up notification in the status bar -->
<dimen name="notification_max_heads_up_height">148dp</dimen>
+ <!-- a threshold in dp per second that is considered fast scrolling -->
+ <dimen name="scroll_fast_threshold">1500dp</dimen>
+
<!-- Height of a the shelf with the notification icons -->
<dimen name="notification_shelf_height">32dp</dimen>
@@ -94,6 +97,9 @@
<!-- The amount the content shifts upwards when transforming into the icon -->
<dimen name="notification_icon_transform_content_shift">32dp</dimen>
+ <!-- The padding on the bottom of the notifications on the keyguard -->
+ <dimen name="keyguard_indication_bottom_padding">12sp</dimen>
+
<!-- Minimum layouted height of a notification in the statusbar-->
<dimen name="min_notification_layout_height">48dp</dimen>
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java
index c6dde46..09671e7 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java
@@ -156,6 +156,10 @@
@Override
public void onListenerRegistered(IPinnedStackController controller) {
mPinnedStackController = controller;
+
+ // Update the controller with the current tuner state
+ setMinimizedState(mIsMinimized);
+ setSnapToEdge(mEnableSnapToEdge);
}
@Override
@@ -353,10 +357,13 @@
*/
private void setSnapToEdge(boolean snapToEdge) {
mSnapAlgorithm.setSnapToEdge(snapToEdge);
- try {
- mPinnedStackController.setSnapToEdge(snapToEdge);
- } catch (RemoteException e) {
- Log.e(TAG, "Could not set snap mode to edge", e);
+
+ if (mPinnedStackController != null) {
+ try {
+ mPinnedStackController.setSnapToEdge(snapToEdge);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Could not set snap mode to edge", e);
+ }
}
}
@@ -365,10 +372,13 @@
*/
private void setMinimizedState(boolean isMinimized) {
mIsMinimized = isMinimized;
- try {
- mPinnedStackController.setIsMinimized(isMinimized);
- } catch (RemoteException e) {
- Log.e(TAG, "Could not set minimized state", e);
+
+ if (mPinnedStackController != null) {
+ try {
+ mPinnedStackController.setIsMinimized(isMinimized);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Could not set minimized state", e);
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java
index d46d267..cf8d332 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java
@@ -103,7 +103,7 @@
private boolean mDimmed;
private boolean mDark;
- private int mBgTint = 0;
+ private int mBgTint = NO_COLOR;
private float mBgAlpha = 1f;
/**
@@ -507,8 +507,10 @@
* Sets the tint color of the background
*/
public void setTintColor(int color, boolean animated) {
- mBgTint = color;
- updateBackgroundTint(animated);
+ if (color != mBgTint) {
+ mBgTint = color;
+ updateBackgroundTint(animated);
+ }
}
/**
@@ -567,13 +569,15 @@
}
private void setBackgroundTintColor(int color) {
- mCurrentBackgroundTint = color;
- if (color == mNormalColor) {
- // We don't need to tint a normal notification
- color = 0;
+ if (color != mCurrentBackgroundTint) {
+ mCurrentBackgroundTint = color;
+ if (color == mNormalColor) {
+ // We don't need to tint a normal notification
+ color = 0;
+ }
+ mBackgroundDimmed.setTint(color);
+ mBackgroundNormal.setTint(color);
}
- mBackgroundDimmed.setTint(color);
- mBackgroundNormal.setTint(color);
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
index 661cc3c..e89fe55 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
@@ -193,7 +193,7 @@
private View mChildAfterViewWhenDismissed;
private View mGroupParentWhenDismissed;
private boolean mRefocusOnDismiss;
- private float mIconTransformationAmount;
+ private float mContentTransformationAmount;
private boolean mIconsVisible = true;
private boolean mAboveShelf;
private boolean mIsLastChild;
@@ -837,23 +837,29 @@
/**
* Set how much this notification is transformed into an icon.
*
- * @param iconTransformationAmount A value from 0 to 1 indicating how much we are transformed
- * to an icon
+ * @param contentTransformationAmount A value from 0 to 1 indicating how much we are transformed
+ * to the content away
* @param isLastChild is this the last child in the list. If true, then the transformation is
* different since it's content fades out.
*/
- public void setIconTransformationAmount(float iconTransformationAmount, boolean isLastChild) {
+ public void setContentTransformationAmount(float contentTransformationAmount,
+ boolean isLastChild) {
boolean changeTransformation = isLastChild != mIsLastChild;
- changeTransformation |= mIconTransformationAmount != iconTransformationAmount;
+ changeTransformation |= mContentTransformationAmount != contentTransformationAmount;
mIsLastChild = isLastChild;
- mIconTransformationAmount = iconTransformationAmount;
+ mContentTransformationAmount = contentTransformationAmount;
if (changeTransformation) {
updateContentTransformation();
- boolean iconsVisible = mIconTransformationAmount == 0.0f;
- if (iconsVisible != mIconsVisible) {
- mIconsVisible = iconsVisible;
- updateIconVisibilities();
- }
+ }
+ }
+
+ /**
+ * Set the icons to be visible of this notification.
+ */
+ public void setIconsVisible(boolean iconsVisible) {
+ if (iconsVisible != mIconsVisible) {
+ mIconsVisible = iconsVisible;
+ updateIconVisibilities();
}
}
@@ -864,9 +870,9 @@
private void updateContentTransformation() {
float contentAlpha;
- float translationY = - mIconTransformationAmount * mIconTransformContentShift;
+ float translationY = -mContentTransformationAmount * mIconTransformContentShift;
if (mIsLastChild) {
- contentAlpha = 1.0f - mIconTransformationAmount;
+ contentAlpha = 1.0f - mContentTransformationAmount;
contentAlpha = Math.min(contentAlpha / 0.5f, 1.0f);
contentAlpha = Interpolators.ALPHA_OUT.getInterpolation(contentAlpha);
translationY *= 0.4f;
@@ -885,7 +891,9 @@
}
private void updateIconVisibilities() {
- boolean visible = isChildInGroup() || isBelowSpeedBump() || mIconsVisible;
+ boolean visible = isChildInGroup()
+ || (isBelowSpeedBump() && !NotificationShelf.SHOW_AMBIENT_ICONS)
+ || mIconsVisible;
mPublicLayout.setIconsVisible(visible);
mPrivateLayout.setIconsVisible(visible);
if (mChildrenContainer != null) {
@@ -1679,13 +1687,17 @@
@Override
public void setClipBottomAmount(int clipBottomAmount) {
- super.setClipBottomAmount(clipBottomAmount);
- mPrivateLayout.setClipBottomAmount(clipBottomAmount);
- mPublicLayout.setClipBottomAmount(clipBottomAmount);
- if (mGuts != null) {
- mGuts.setClipBottomAmount(clipBottomAmount);
+ if (clipBottomAmount != mClipBottomAmount) {
+ super.setClipBottomAmount(clipBottomAmount);
+ mPrivateLayout.setClipBottomAmount(clipBottomAmount);
+ mPublicLayout.setClipBottomAmount(clipBottomAmount);
+ if (mGuts != null) {
+ mGuts.setClipBottomAmount(clipBottomAmount);
+ }
}
if (mChildrenContainer != null) {
+ // We have to update this even if it hasn't changed, since the children locations can
+ // have changed
mChildrenContainer.setClipBottomAmount(clipBottomAmount);
}
}
@@ -1871,8 +1883,8 @@
}
@Override
- protected void onYTranslationAnimationFinished() {
- super.onYTranslationAnimationFinished();
+ protected void onYTranslationAnimationFinished(View view) {
+ super.onYTranslationAnimationFinished(view);
if (mHeadsupDisappearRunning) {
setHeadsUpAnimatingAway(false);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java
index 3687f6d..32f8ebb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java
@@ -208,6 +208,12 @@
expandedIcon = null;
throw new IconException("Couldn't create icon: " + ic);
}
+ expandedIcon.setOnVisibilityChangedListener(
+ newVisibility -> {
+ if (row != null) {
+ row.setIconsVisible(newVisibility != View.VISIBLE);
+ }
+ });
}
public void setIconTag(int key, Object tag) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
index 680562a..324eb2d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
@@ -18,6 +18,7 @@
import android.content.Context;
import android.content.res.Configuration;
+import android.os.SystemProperties;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
@@ -34,19 +35,18 @@
import com.android.systemui.statusbar.stack.NotificationStackScrollLayout;
import com.android.systemui.statusbar.stack.StackScrollState;
-import java.util.ArrayList;
-import java.util.WeakHashMap;
-
/**
* A notification shelf view that is placed inside the notification scroller. It manages the
* overflow icons that don't fit into the regular list anymore.
*/
public class NotificationShelf extends ActivatableNotificationView {
+ public static final boolean SHOW_AMBIENT_ICONS = true;
+ private static final boolean USE_ANIMATIONS_WHEN_OPENING =
+ SystemProperties.getBoolean("debug.icon_opening_animations", true);
private ViewInvertHelper mViewInvertHelper;
private boolean mDark;
private NotificationIconContainer mShelfIcons;
- private ArrayList<StatusBarIconView> mIcons = new ArrayList<>();
private ShelfState mShelfState;
private int[] mTmp = new int[2];
private boolean mHideBackground;
@@ -60,6 +60,7 @@
private int mNotGoneIndex;
private boolean mHasItemsInStableShelf;
private NotificationIconContainer mCollapsedIcons;
+ private int mScrollFastThreshold;
public NotificationShelf(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -101,6 +102,8 @@
setLayoutParams(layoutParams);
int padding = getResources().getDimensionPixelOffset(R.dimen.shelf_icon_container_padding);
mShelfIcons.setPadding(padding, 0, padding, 0);
+ mScrollFastThreshold = getResources().getDimensionPixelOffset(
+ R.dimen.scroll_fast_threshold);
}
@Override
@@ -162,6 +165,7 @@
mShelfState.notGoneIndex = Math.min(mShelfState.notGoneIndex, mNotGoneIndex);
}
mShelfState.hasItemsInStableShelf = lastViewState.inShelf;
+ mShelfState.hidden = !mAmbientState.isShadeExpanded();
} else {
mShelfState.hidden = true;
mShelfState.location = ExpandableViewState.LOCATION_GONE;
@@ -174,15 +178,15 @@
* the icons from the notification area into the shelf.
*/
public void updateAppearance() {
- WeakHashMap<View, NotificationIconContainer.IconState> iconStates =
- mShelfIcons.resetViewStates();
+ mShelfIcons.resetViewStates();
+ float shelfStart = getTranslationY();
float numViewsInShelf = 0.0f;
View lastChild = mAmbientState.getLastVisibleBackgroundChild();
mNotGoneIndex = -1;
float interpolationStart = mMaxLayoutHeight - getIntrinsicHeight() * 2;
float expandAmount = 0.0f;
- if (getTranslationY() >= interpolationStart) {
- expandAmount = (getTranslationY() - interpolationStart) / getIntrinsicHeight();
+ if (shelfStart >= interpolationStart) {
+ expandAmount = (shelfStart - interpolationStart) / getIntrinsicHeight();
expandAmount = Math.min(1.0f, expandAmount);
}
// find the first view that doesn't overlap with the shelf
@@ -196,6 +200,8 @@
int colorTwoBefore = NO_COLOR;
int previousColor = NO_COLOR;
float transitionAmount = 0.0f;
+ boolean scrollingFast = mAmbientState.getCurrentScrollVelocity() > mScrollFastThreshold;
+ int baseZHeight = mAmbientState.getBaseZHeight();
while (notificationIndex < mHostLayout.getChildCount()) {
ExpandableView child = (ExpandableView) mHostLayout.getChildAt(notificationIndex);
notificationIndex++;
@@ -204,30 +210,28 @@
continue;
}
ExpandableNotificationRow row = (ExpandableNotificationRow) child;
- StatusBarIconView icon = row.getEntry().expandedIcon;
- NotificationIconContainer.IconState iconState = iconStates.get(icon);
float notificationClipEnd;
- float shelfStart = getTranslationY();
- boolean aboveShelf = row.getTranslationZ() > mAmbientState.getBaseZHeight();
+ boolean aboveShelf = row.getTranslationZ() > baseZHeight;
boolean isLastChild = child == lastChild;
+ float rowTranslationY = row.getTranslationY();
if (isLastChild || aboveShelf || backgroundForceHidden) {
notificationClipEnd = shelfStart + getIntrinsicHeight();
} else {
notificationClipEnd = shelfStart - mPaddingBetweenElements;
- float height = notificationClipEnd - row.getTranslationY();
+ float height = notificationClipEnd - rowTranslationY;
if (!row.isBelowSpeedBump() && height <= getNotificationMergeSize()) {
// We want the gap to close when we reached the minimum size and only shrink
// before
notificationClipEnd = Math.min(shelfStart,
- row.getTranslationY() + getNotificationMergeSize());
+ rowTranslationY + getNotificationMergeSize());
}
}
updateNotificationClipHeight(row, notificationClipEnd);
- float inShelfAmount = updateIconAppearance(row, iconState, icon, expandAmount,
+ float inShelfAmount = updateIconAppearance(row, expandAmount, scrollingFast,
isLastChild);
numViewsInShelf += inShelfAmount;
int ownColorUntinted = row.getBackgroundColorWithoutTint();
- if (row.getTranslationY() >= getTranslationY() && mNotGoneIndex == -1) {
+ if (rowTranslationY >= shelfStart && mNotGoneIndex == -1) {
mNotGoneIndex = notGoneIndex;
setTintColor(previousColor);
setOverrideTintColor(colorTwoBefore, transitionAmount);
@@ -248,11 +252,9 @@
notGoneIndex++;
previousColor = ownColorUntinted;
}
+ mShelfIcons.setSpeedBumpIndex(mAmbientState.getSpeedBumpIndex());
mShelfIcons.calculateIconTranslations();
mShelfIcons.applyIconStates();
- setVisibility(numViewsInShelf != 0.0f && mAmbientState.isShadeExpanded()
- ? VISIBLE
- : INVISIBLE);
boolean hideBackground = numViewsInShelf < 1.0f;
setHideBackground(hideBackground || backgroundForceHidden);
if (mNotGoneIndex == -1) {
@@ -275,41 +277,109 @@
/**
* @return the icon amount how much this notification is in the shelf;
*/
- private float updateIconAppearance(ExpandableNotificationRow row,
- NotificationIconContainer.IconState iconState, StatusBarIconView icon,
- float expandAmount, boolean isLastChild) {
+ private float updateIconAppearance(ExpandableNotificationRow row, float expandAmount,
+ boolean scrollingFast, boolean isLastChild) {
// Let calculate how much the view is in the shelf
float viewStart = row.getTranslationY();
- int transformHeight = row.getActualHeight() + mPaddingBetweenElements;
+ int fullHeight = row.getActualHeight() + mPaddingBetweenElements;
+ float iconTransformDistance = getIntrinsicHeight() * 1.5f;
if (isLastChild) {
- transformHeight =
- Math.min(transformHeight, row.getMinHeight() - getIntrinsicHeight());
+ fullHeight = Math.min(fullHeight, row.getMinHeight() - getIntrinsicHeight());
+ iconTransformDistance = Math.min(iconTransformDistance, row.getMinHeight()
+ - getIntrinsicHeight());
}
- float viewEnd = viewStart + transformHeight;
- float iconAppearAmount;
- float yTranslation;
- float alpha = 1.0f;
- if (viewEnd >= getTranslationY() && (mAmbientState.isShadeExpanded()
+ float viewEnd = viewStart + fullHeight;
+ float fullTransitionAmount;
+ float iconTransitionAmount;
+ float shelfStart = getTranslationY();
+ if (viewEnd >= shelfStart && (mAmbientState.isShadeExpanded()
|| (!row.isPinned() && !row.isHeadsUpAnimatingAway()))) {
- if (viewStart < getTranslationY()) {
- float linearAmount = (getTranslationY() - viewStart) / transformHeight;
+ if (viewStart < shelfStart) {
+
+ float fullAmount = (shelfStart - viewStart) / fullHeight;
float interpolatedAmount = Interpolators.ACCELERATE_DECELERATE.getInterpolation(
- linearAmount);
+ fullAmount);
interpolatedAmount = NotificationUtils.interpolate(
- interpolatedAmount, linearAmount, expandAmount);
- iconAppearAmount = 1.0f - interpolatedAmount;
+ interpolatedAmount, fullAmount, expandAmount);
+ fullTransitionAmount = 1.0f - interpolatedAmount;
+
+ iconTransitionAmount = (shelfStart - viewStart) / iconTransformDistance;
+ iconTransitionAmount = Math.min(1.0f, iconTransitionAmount);
+ iconTransitionAmount = 1.0f - iconTransitionAmount;
+
} else {
- iconAppearAmount = 1.0f;
+ fullTransitionAmount = 1.0f;
+ iconTransitionAmount = 1.0f;
}
} else {
- iconAppearAmount = 0.0f;
+ fullTransitionAmount = 0.0f;
+ iconTransitionAmount = 0.0f;
}
+ updateIconPositioning(row, iconTransitionAmount, fullTransitionAmount, scrollingFast,
+ isLastChild);
+ return fullTransitionAmount;
+ }
- // Lets now calculate how much of the transformation has already happened. This is different
- // from the above, since we only start transforming when the view is already quite a bit
- // pushed in.
+ private void updateIconPositioning(ExpandableNotificationRow row, float iconTransitionAmount,
+ float fullTransitionAmount, boolean scrollingFast, boolean isLastChild) {
+ StatusBarIconView icon = row.getEntry().expandedIcon;
+ NotificationIconContainer.IconState iconState = getIconState(icon);
+ if (iconState == null) {
+ return;
+ }
+ float clampedAmount = iconTransitionAmount > 0.5f ? 1.0f : 0.0f;
+ if (clampedAmount == iconTransitionAmount) {
+ iconState.keepClampedPosition = false;
+ }
+ if (clampedAmount == fullTransitionAmount) {
+ iconState.useFullTransitionAmount = fullTransitionAmount == 0.0f || scrollingFast;
+ iconState.translateContent = mMaxLayoutHeight - getTranslationY()
+ - getIntrinsicHeight() > 0;
+ }
+ float transitionAmount;
+ boolean needCannedAnimation = iconState.clampedAppearAmount == 1.0f
+ && clampedAmount == 0.0f;
+ if (isLastChild || !USE_ANIMATIONS_WHEN_OPENING || iconState.useFullTransitionAmount) {
+ transitionAmount = iconTransitionAmount;
+ } else if (iconState.keepClampedPosition
+ && iconState.clampedAppearAmount != clampedAmount) {
+ // We animated to the clamped amount but then decided to go the other way. Let's
+ // animate it to the new position
+ transitionAmount = iconTransitionAmount;
+ iconState.needsCannedAnimation = true;
+ iconState.keepClampedPosition = false;
+ } else if (needCannedAnimation || iconState.keepClampedPosition
+ || iconState.iconAppearAmount == 1.0f) {
+ // We need to perform a canned animation since we crossed the treshhold
+ transitionAmount = clampedAmount;
+ iconState.keepClampedPosition = iconState.keepClampedPosition || needCannedAnimation;
+ iconState.needsCannedAnimation = needCannedAnimation;
+ } else {
+ transitionAmount = iconTransitionAmount;
+ }
+ iconState.iconAppearAmount = !USE_ANIMATIONS_WHEN_OPENING
+ || iconState.useFullTransitionAmount
+ ? fullTransitionAmount
+ : transitionAmount;
+ iconState.clampedAppearAmount = clampedAmount;
+ setIconTransformationAmount(row, transitionAmount);
+ float contentTransformationAmount = isLastChild || iconState.translateContent
+ ? iconTransitionAmount
+ : 0.0f;
+ row.setContentTransformationAmount(contentTransformationAmount, isLastChild);
+ }
+
+ private boolean isLastChild(ExpandableNotificationRow row) {
+ return row == mAmbientState.getLastVisibleBackgroundChild();
+ }
+
+ private void setIconTransformationAmount(ExpandableNotificationRow row,
+ float transitionAmount) {
+ StatusBarIconView icon = row.getEntry().expandedIcon;
+ NotificationIconContainer.IconState iconState = getIconState(icon);
+
View rowIcon = row.getNotificationIcon();
- float notificationIconPosition = viewStart;
+ float notificationIconPosition = row.getTranslationY();
float notificationIconSize = 0.0f;
int iconTopPadding;
if (rowIcon != null) {
@@ -322,28 +392,18 @@
float shelfIconPosition = getTranslationY() + icon.getTop();
shelfIconPosition += ((1.0f - icon.getIconScale()) * icon.getHeight()) / 2.0f;
float transitionDistance = getIntrinsicHeight() * 1.5f;
- if (isLastChild) {
+ if (row == mAmbientState.getLastVisibleBackgroundChild()) {
transitionDistance = Math.min(transitionDistance, row.getMinHeight()
- getIntrinsicHeight());
}
float transformationStartPosition = getTranslationY() - transitionDistance;
- float transitionAmount = 0.0f;
- if (viewStart < transformationStartPosition
- || (!mAmbientState.isShadeExpanded()
- && (row.isPinned() || row.isHeadsUpAnimatingAway()))) {
- // We simply place it on the icon of the notification
- yTranslation = notificationIconPosition - shelfIconPosition;
- } else {
- transitionAmount = (viewStart - transformationStartPosition)
- / transitionDistance;
- float startPosition = transformationStartPosition + iconTopPadding;
- yTranslation = NotificationUtils.interpolate(
- startPosition - shelfIconPosition, 0, transitionAmount);
- // If we are merging into the shelf, lets make sure the shelf is at least on our height,
- // otherwise the icons won't be visible.
- setTranslationZ(Math.max(getTranslationZ(), row.getTranslationZ()));
- }
+ float iconYTranslation = NotificationUtils.interpolate(
+ Math.min(notificationIconPosition, transformationStartPosition + iconTopPadding)
+ - shelfIconPosition,
+ 0,
+ transitionAmount);
float shelfIconSize = icon.getHeight() * icon.getIconScale();
+ float alpha = 1.0f;
if (!row.isShowingIcon()) {
// The view currently doesn't have an icon, lets transform it in!
alpha = transitionAmount;
@@ -352,15 +412,12 @@
// The notification size is different from the size in the shelf / statusbar
float newSize = NotificationUtils.interpolate(notificationIconSize, shelfIconSize,
transitionAmount);
- row.setIconTransformationAmount(transitionAmount, isLastChild);
if (iconState != null) {
iconState.scaleX = newSize / icon.getHeight() / icon.getIconScale();
iconState.scaleY = iconState.scaleX;
iconState.hidden = transitionAmount == 0.0f;
- iconState.iconAppearAmount = iconAppearAmount;
iconState.alpha = alpha;
- iconState.yTranslation = yTranslation;
- icon.setVisibility(transitionAmount == 0.0f ? INVISIBLE : VISIBLE);
+ iconState.yTranslation = iconYTranslation;
if (row.isInShelf() && !row.isTransformingIntoShelf()) {
iconState.iconAppearAmount = 1.0f;
iconState.alpha = 1.0f;
@@ -368,8 +425,14 @@
iconState.scaleY = 1.0f;
iconState.hidden = false;
}
+ if (row.isAboveShelf()) {
+ iconState.hidden = true;
+ }
}
- return iconAppearAmount;
+ }
+
+ private NotificationIconContainer.IconState getIconState(StatusBarIconView icon) {
+ return mShelfIcons.getIconState(icon);
}
private float getFullyClosedTranslation() {
@@ -386,9 +449,11 @@
}
private void setHideBackground(boolean hideBackground) {
- mHideBackground = hideBackground;
- updateBackground();
- updateOutline();
+ if (mHideBackground != hideBackground) {
+ mHideBackground = hideBackground;
+ updateBackground();
+ updateOutline();
+ }
}
public boolean hidesBackground() {
@@ -415,13 +480,23 @@
mShelfIcons.getWidth(),
openedAmount);
mShelfIcons.setActualLayoutWidth(width);
- float padding = NotificationUtils.interpolate(mCollapsedIcons.getPaddingEnd(),
+ boolean hasOverflow = mCollapsedIcons.hasOverflow();
+ int collapsedPadding = mCollapsedIcons.getPaddingEnd();
+ if (!hasOverflow) {
+ // we have to ensure that adding the low priority notification won't lead to an
+ // overflow
+ collapsedPadding -= (1.0f + NotificationIconContainer.OVERFLOW_EARLY_AMOUNT)
+ * mCollapsedIcons.getIconSize();
+ }
+ float padding = NotificationUtils.interpolate(collapsedPadding,
mShelfIcons.getPaddingEnd(),
openedAmount);
mShelfIcons.setActualPaddingEnd(padding);
float paddingStart = NotificationUtils.interpolate(start,
mShelfIcons.getPaddingStart(), openedAmount);
mShelfIcons.setActualPaddingStart(paddingStart);
+ mShelfIcons.setOpenedAmount(openedAmount);
+ mShelfIcons.setVisualOverflowAdaption(mCollapsedIcons.getVisualOverflowAdaption());
}
public void setMaxLayoutHeight(int maxLayoutHeight) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ScrimView.java b/packages/SystemUI/src/com/android/systemui/statusbar/ScrimView.java
index bfa43fd..dba7130 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ScrimView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ScrimView.java
@@ -42,7 +42,6 @@
private float mViewAlpha = 1.0f;
private ValueAnimator mAlphaAnimator;
private Rect mExcludedRect = new Rect();
- private int mLeftInset = 0;
private boolean mHasExcludedArea;
private ValueAnimator.AnimatorUpdateListener mAlphaUpdateListener
= new ValueAnimator.AnimatorUpdateListener() {
@@ -88,12 +87,12 @@
if (mExcludedRect.top > 0) {
canvas.drawRect(0, 0, getWidth(), mExcludedRect.top, mPaint);
}
- if (mExcludedRect.left + mLeftInset > 0) {
- canvas.drawRect(0, mExcludedRect.top, mExcludedRect.left + mLeftInset,
- mExcludedRect.bottom, mPaint);
+ if (mExcludedRect.left > 0) {
+ canvas.drawRect(0, mExcludedRect.top, mExcludedRect.left, mExcludedRect.bottom,
+ mPaint);
}
- if (mExcludedRect.right + mLeftInset < getWidth()) {
- canvas.drawRect(mExcludedRect.right + mLeftInset,
+ if (mExcludedRect.right < getWidth()) {
+ canvas.drawRect(mExcludedRect.right,
mExcludedRect.top,
getWidth(),
mExcludedRect.bottom,
@@ -184,14 +183,4 @@
public void setChangeRunnable(Runnable changeRunnable) {
mChangeRunnable = changeRunnable;
}
-
- public void setLeftInset(int leftInset) {
- if (mLeftInset != leftInset) {
- mLeftInset = leftInset;
-
- if (mHasExcludedArea) {
- invalidate();
- }
- }
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
index d635bb0..a2c2fd7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
@@ -37,6 +37,7 @@
import android.util.Log;
import android.util.Property;
import android.util.TypedValue;
+import android.view.View;
import android.view.ViewDebug;
import android.view.accessibility.AccessibilityEvent;
import android.view.animation.Interpolator;
@@ -102,6 +103,7 @@
private ObjectAnimator mIconAppearAnimator;
private ObjectAnimator mDotAnimator;
private float mDotAppearAmount;
+ private OnVisibilityChangedListener mOnVisibilityChangedListener;
public StatusBarIconView(Context context, String slot, Notification notification) {
this(context, slot, notification, false);
@@ -453,6 +455,7 @@
}
public void setVisibleState(int visibleState, boolean animate, Runnable endRunnable) {
+ boolean runnableAdded = false;
if (visibleState != mVisibleState) {
mVisibleState = visibleState;
if (animate) {
@@ -465,20 +468,22 @@
targetAmount = 1.0f;
interpolator = Interpolators.LINEAR_OUT_SLOW_IN;
}
- mIconAppearAnimator = ObjectAnimator.ofFloat(this, ICON_APPEAR_AMOUNT,
- targetAmount);
- mIconAppearAnimator.setInterpolator(interpolator);
- mIconAppearAnimator.setDuration(100);
- mIconAppearAnimator.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- mIconAppearAnimator = null;
- if (endRunnable != null) {
- endRunnable.run();
+ float currentAmount = getIconAppearAmount();
+ if (targetAmount != currentAmount) {
+ mIconAppearAnimator = ObjectAnimator.ofFloat(this, ICON_APPEAR_AMOUNT,
+ currentAmount, targetAmount);
+ mIconAppearAnimator.setInterpolator(interpolator);
+ mIconAppearAnimator.setDuration(100);
+ mIconAppearAnimator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mIconAppearAnimator = null;
+ runRunnable(endRunnable);
}
- }
- });
- mIconAppearAnimator.start();
+ });
+ mIconAppearAnimator.start();
+ runnableAdded = true;
+ }
if (mDotAnimator != null) {
mDotAnimator.cancel();
@@ -489,22 +494,41 @@
targetAmount = 1.0f;
interpolator = Interpolators.LINEAR_OUT_SLOW_IN;
}
- mDotAnimator = ObjectAnimator.ofFloat(this, DOT_APPEAR_AMOUNT,
- targetAmount);
- mDotAnimator.setInterpolator(interpolator);
- mDotAnimator.setDuration(100);
- mDotAnimator.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- mDotAnimator = null;
- }
- });
- mDotAnimator.start();
+ currentAmount = getDotAppearAmount();
+ if (targetAmount != currentAmount) {
+ mDotAnimator = ObjectAnimator.ofFloat(this, DOT_APPEAR_AMOUNT,
+ currentAmount, targetAmount);
+ mDotAnimator.setInterpolator(interpolator);
+ mDotAnimator.setDuration(100);
+ final boolean runRunnable = !runnableAdded;
+ mDotAnimator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mDotAnimator = null;
+ if (runRunnable) {
+ runRunnable(endRunnable);
+ }
+ }
+ });
+ mDotAnimator.start();
+ runnableAdded = true;
+ }
} else {
setIconAppearAmount(visibleState == STATE_ICON ? 1.0f : 0.0f);
- setDotAppearAmount(visibleState == STATE_DOT ? 1.0f : 0.0f);
+ setDotAppearAmount(visibleState == STATE_DOT ? 1.0f
+ : visibleState == STATE_ICON ? 2.0f
+ : 0.0f);
}
}
+ if (!runnableAdded) {
+ runRunnable(endRunnable);
+ }
+ }
+
+ private void runRunnable(Runnable runnable) {
+ if (runnable != null) {
+ runnable.run();
+ }
}
public void setIconAppearAmount(float iconAppearAmount) {
@@ -525,7 +549,23 @@
invalidate();
}
+ @Override
+ public void setVisibility(int visibility) {
+ super.setVisibility(visibility);
+ if (mOnVisibilityChangedListener != null) {
+ mOnVisibilityChangedListener.onVisibilityChanged(visibility);
+ }
+ }
+
public float getDotAppearAmount() {
return mDotAppearAmount;
}
+
+ public void setOnVisibilityChangedListener(OnVisibilityChangedListener listener) {
+ mOnVisibilityChangedListener = listener;
+ }
+
+ public interface OnVisibilityChangedListener {
+ void onVisibilityChanged(int newVisibility);
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ManagedProfileController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ManagedProfileController.java
index 1a46815..4969a1c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ManagedProfileController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ManagedProfileController.java
@@ -14,134 +14,16 @@
package com.android.systemui.statusbar.phone;
-import android.app.ActivityManager;
-import android.app.StatusBarManager;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.pm.UserInfo;
-import android.os.UserHandle;
-import android.os.UserManager;
-
import com.android.systemui.statusbar.phone.ManagedProfileController.Callback;
import com.android.systemui.statusbar.policy.CallbackController;
-import java.util.ArrayList;
-import java.util.LinkedList;
-import java.util.List;
+public interface ManagedProfileController extends CallbackController<Callback> {
-public class ManagedProfileController implements CallbackController<Callback> {
+ void setWorkModeEnabled(boolean enabled);
- private final List<Callback> mCallbacks = new ArrayList<>();
+ boolean hasActiveProfile();
- private final Context mContext;
- private final UserManager mUserManager;
- private final LinkedList<UserInfo> mProfiles;
- private boolean mListening;
- private int mCurrentUser;
-
- public ManagedProfileController(QSTileHost host) {
- mContext = host.getContext();
- mUserManager = UserManager.get(mContext);
- mProfiles = new LinkedList<UserInfo>();
- }
-
- public void addCallback(Callback callback) {
- mCallbacks.add(callback);
- if (mCallbacks.size() == 1) {
- setListening(true);
- }
- callback.onManagedProfileChanged();
- }
-
- public void removeCallback(Callback callback) {
- if (mCallbacks.remove(callback) && mCallbacks.size() == 0) {
- setListening(false);
- }
- }
-
- public void setWorkModeEnabled(boolean enableWorkMode) {
- synchronized (mProfiles) {
- for (UserInfo ui : mProfiles) {
- if (enableWorkMode) {
- if (!mUserManager.trySetQuietModeDisabled(ui.id, null)) {
- StatusBarManager statusBarManager = (StatusBarManager) mContext
- .getSystemService(android.app.Service.STATUS_BAR_SERVICE);
- statusBarManager.collapsePanels();
- }
- } else {
- mUserManager.setQuietModeEnabled(ui.id, true);
- }
- }
- }
- }
-
- private void reloadManagedProfiles() {
- synchronized (mProfiles) {
- boolean hadProfile = mProfiles.size() > 0;
- int user = ActivityManager.getCurrentUser();
- mProfiles.clear();
-
- for (UserInfo ui : mUserManager.getEnabledProfiles(user)) {
- if (ui.isManagedProfile()) {
- mProfiles.add(ui);
- }
- }
- if (mProfiles.size() == 0 && hadProfile && (user == mCurrentUser)) {
- for (Callback callback : mCallbacks) {
- callback.onManagedProfileRemoved();
- }
- }
- mCurrentUser = user;
- }
- }
-
- public boolean hasActiveProfile() {
- if (!mListening) reloadManagedProfiles();
- synchronized (mProfiles) {
- return mProfiles.size() > 0;
- }
- }
-
- public boolean isWorkModeEnabled() {
- if (!mListening) reloadManagedProfiles();
- synchronized (mProfiles) {
- for (UserInfo ui : mProfiles) {
- if (ui.isQuietModeEnabled()) {
- return false;
- }
- }
- return true;
- }
- }
-
- private void setListening(boolean listening) {
- mListening = listening;
- if (listening) {
- reloadManagedProfiles();
-
- final IntentFilter filter = new IntentFilter();
- filter.addAction(Intent.ACTION_USER_SWITCHED);
- filter.addAction(Intent.ACTION_MANAGED_PROFILE_ADDED);
- filter.addAction(Intent.ACTION_MANAGED_PROFILE_REMOVED);
- filter.addAction(Intent.ACTION_MANAGED_PROFILE_AVAILABLE);
- filter.addAction(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE);
- mContext.registerReceiverAsUser(mReceiver, UserHandle.ALL, filter, null, null);
- } else {
- mContext.unregisterReceiver(mReceiver);
- }
- }
-
- private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- reloadManagedProfiles();
- for (Callback callback : mCallbacks) {
- callback.onManagedProfileChanged();
- }
- }
- };
+ boolean isWorkModeEnabled();
public interface Callback {
void onManagedProfileChanged();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ManagedProfileControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ManagedProfileControllerImpl.java
new file mode 100644
index 0000000..fc33ace
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ManagedProfileControllerImpl.java
@@ -0,0 +1,142 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package com.android.systemui.statusbar.phone;
+
+import android.app.ActivityManager;
+import android.app.StatusBarManager;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.UserInfo;
+import android.os.UserHandle;
+import android.os.UserManager;
+
+import java.util.ArrayList;
+import java.util.LinkedList;
+import java.util.List;
+
+public class ManagedProfileControllerImpl implements ManagedProfileController {
+
+ private final List<Callback> mCallbacks = new ArrayList<>();
+
+ private final Context mContext;
+ private final UserManager mUserManager;
+ private final LinkedList<UserInfo> mProfiles;
+ private boolean mListening;
+ private int mCurrentUser;
+
+ public ManagedProfileControllerImpl(QSTileHost host) {
+ mContext = host.getContext();
+ mUserManager = UserManager.get(mContext);
+ mProfiles = new LinkedList<UserInfo>();
+ }
+
+ public void addCallback(Callback callback) {
+ mCallbacks.add(callback);
+ if (mCallbacks.size() == 1) {
+ setListening(true);
+ }
+ callback.onManagedProfileChanged();
+ }
+
+ public void removeCallback(Callback callback) {
+ if (mCallbacks.remove(callback) && mCallbacks.size() == 0) {
+ setListening(false);
+ }
+ }
+
+ public void setWorkModeEnabled(boolean enableWorkMode) {
+ synchronized (mProfiles) {
+ for (UserInfo ui : mProfiles) {
+ if (enableWorkMode) {
+ if (!mUserManager.trySetQuietModeDisabled(ui.id, null)) {
+ StatusBarManager statusBarManager = (StatusBarManager) mContext
+ .getSystemService(android.app.Service.STATUS_BAR_SERVICE);
+ statusBarManager.collapsePanels();
+ }
+ } else {
+ mUserManager.setQuietModeEnabled(ui.id, true);
+ }
+ }
+ }
+ }
+
+ private void reloadManagedProfiles() {
+ synchronized (mProfiles) {
+ boolean hadProfile = mProfiles.size() > 0;
+ int user = ActivityManager.getCurrentUser();
+ mProfiles.clear();
+
+ for (UserInfo ui : mUserManager.getEnabledProfiles(user)) {
+ if (ui.isManagedProfile()) {
+ mProfiles.add(ui);
+ }
+ }
+ if (mProfiles.size() == 0 && hadProfile && (user == mCurrentUser)) {
+ for (Callback callback : mCallbacks) {
+ callback.onManagedProfileRemoved();
+ }
+ }
+ mCurrentUser = user;
+ }
+ }
+
+ public boolean hasActiveProfile() {
+ if (!mListening) reloadManagedProfiles();
+ synchronized (mProfiles) {
+ return mProfiles.size() > 0;
+ }
+ }
+
+ public boolean isWorkModeEnabled() {
+ if (!mListening) reloadManagedProfiles();
+ synchronized (mProfiles) {
+ for (UserInfo ui : mProfiles) {
+ if (ui.isQuietModeEnabled()) {
+ return false;
+ }
+ }
+ return true;
+ }
+ }
+
+ private void setListening(boolean listening) {
+ mListening = listening;
+ if (listening) {
+ reloadManagedProfiles();
+
+ final IntentFilter filter = new IntentFilter();
+ filter.addAction(Intent.ACTION_USER_SWITCHED);
+ filter.addAction(Intent.ACTION_MANAGED_PROFILE_ADDED);
+ filter.addAction(Intent.ACTION_MANAGED_PROFILE_REMOVED);
+ filter.addAction(Intent.ACTION_MANAGED_PROFILE_AVAILABLE);
+ filter.addAction(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE);
+ mContext.registerReceiverAsUser(mReceiver, UserHandle.ALL, filter, null, null);
+ } else {
+ mContext.unregisterReceiver(mReceiver);
+ }
+ }
+
+ private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ reloadManagedProfiles();
+ for (Callback callback : mCallbacks) {
+ callback.onManagedProfileChanged();
+ }
+ }
+ };
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
index d543f49..345dcbd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
@@ -127,9 +127,9 @@
return mPhoneStatusBar.getStatusBarHeight();
}
- protected boolean shouldShowNotification(NotificationData.Entry entry,
- NotificationData notificationData) {
- if (notificationData.isAmbient(entry.key)
+ protected boolean shouldShowNotificationIcon(NotificationData.Entry entry,
+ NotificationData notificationData, boolean showAmbient) {
+ if (notificationData.isAmbient(entry.key) && !showAmbient
&& !NotificationData.showNotificationEvenIfUnprovisioned(entry.notification)) {
return false;
}
@@ -148,8 +148,10 @@
*/
public void updateNotificationIcons(NotificationData notificationData) {
- updateIconsForLayout(notificationData, entry -> entry.icon, mNotificationIcons);
- updateIconsForLayout(notificationData, entry -> entry.expandedIcon, mShelfIcons);
+ updateIconsForLayout(notificationData, entry -> entry.icon, mNotificationIcons,
+ false /* showAmbient */);
+ updateIconsForLayout(notificationData, entry -> entry.expandedIcon, mShelfIcons,
+ NotificationShelf.SHOW_AMBIENT_ICONS);
applyNotificationIconsTint();
ArrayList<NotificationData.Entry> activeNotifications
@@ -173,10 +175,11 @@
* @param notificationData the notification data to look up which notifications are relevant
* @param function A function to look up an icon view based on an entry
* @param hostLayout which layout should be updated
+ * @param showAmbient should ambient notification icons be shown
*/
private void updateIconsForLayout(NotificationData notificationData,
Function<NotificationData.Entry, StatusBarIconView> function,
- NotificationIconContainer hostLayout) {
+ NotificationIconContainer hostLayout, boolean showAmbient) {
ArrayList<StatusBarIconView> toShow = new ArrayList<>(
mNotificationScrollLayout.getChildCount());
@@ -185,7 +188,7 @@
View view = mNotificationScrollLayout.getChildAt(i);
if (view instanceof ExpandableNotificationRow) {
NotificationData.Entry ent = ((ExpandableNotificationRow) view).getEntry();
- if (shouldShowNotification(ent, notificationData)) {
+ if (shouldShowNotificationIcon(ent, notificationData, showAmbient)) {
toShow.add(function.apply(ent));
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
index 03697b8..d323e4f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
@@ -31,15 +31,23 @@
import com.android.systemui.statusbar.stack.AnimationProperties;
import com.android.systemui.statusbar.stack.ViewState;
-import java.util.WeakHashMap;
+import java.util.HashMap;
/**
* A container for notification icons. It handles overflowing icons properly and positions them
* correctly on the screen.
*/
public class NotificationIconContainer extends AlphaOptimizedFrameLayout {
+ /**
+ * A float value indicating how much before the overflow start the icons should transform into
+ * a dot. A value of 0 means that they are exactly at the end and a value of 1 means it starts
+ * 1 icon width early.
+ */
+ public static final float OVERFLOW_EARLY_AMOUNT = 0.2f;
+ private static final int NO_VALUE = Integer.MIN_VALUE;
private static final String TAG = "NotificationIconContainer";
private static final boolean DEBUG = false;
+ private static final int CANNED_ANIMATION_DURATION = 100;
private static final AnimationProperties DOT_ANIMATION_PROPERTIES = new AnimationProperties() {
private AnimationFilter mAnimationFilter = new AnimationFilter().animateX();
@@ -49,6 +57,26 @@
}
}.setDuration(200);
+ private static final AnimationProperties ICON_ANIMATION_PROPERTIES = new AnimationProperties() {
+ private AnimationFilter mAnimationFilter = new AnimationFilter().animateY().animateAlpha();
+ // TODO: add scale
+
+ @Override
+ public AnimationFilter getAnimationFilter() {
+ return mAnimationFilter;
+ }
+ }.setDuration(CANNED_ANIMATION_DURATION);
+
+ private static final AnimationProperties mTempProperties = new AnimationProperties() {
+ private AnimationFilter mAnimationFilter = new AnimationFilter();
+ // TODO: add scale
+
+ @Override
+ public AnimationFilter getAnimationFilter() {
+ return mAnimationFilter;
+ }
+ }.setDuration(CANNED_ANIMATION_DURATION);
+
private static final AnimationProperties ADD_ICON_PROPERTIES = new AnimationProperties() {
private AnimationFilter mAnimationFilter = new AnimationFilter().animateAlpha();
@@ -59,14 +87,19 @@
}.setDuration(200).setDelay(50);
private boolean mShowAllIcons = true;
- private WeakHashMap<View, IconState> mIconStates = new WeakHashMap<>();
+ private final HashMap<View, IconState> mIconStates = new HashMap<>();
private int mDotPadding;
private int mStaticDotRadius;
- private int mActualLayoutWidth = -1;
- private float mActualPaddingEnd = -1;
- private float mActualPaddingStart = -1;
+ private int mActualLayoutWidth = NO_VALUE;
+ private float mActualPaddingEnd = NO_VALUE;
+ private float mActualPaddingStart = NO_VALUE;
private boolean mChangingViewPositions;
- private int mAnimationStartIndex = -1;
+ private int mAddAnimationStartIndex = -1;
+ private int mCannedAnimationStartIndex = -1;
+ private int mSpeedBumpIndex = -1;
+ private int mIconSize;
+ private float mOpenedAmount = 0.0f;
+ private float mVisualOverflowAdaption;
public NotificationIconContainer(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -97,6 +130,7 @@
protected void onLayout(boolean changed, int l, int t, int r, int b) {
float centerY = getHeight() / 2.0f;
// we layout all our children on the left at the top
+ mIconSize = 0;
for (int i = 0; i < getChildCount(); i++) {
View child = getChildAt(i);
// We need to layout all children even the GONE ones, such that the heights are
@@ -105,6 +139,9 @@
int height = child.getMeasuredHeight();
int top = (int) (centerY - height / 2.0f);
child.layout(0, top, width, top + height);
+ if (i == 0) {
+ mIconSize = child.getWidth();
+ }
}
if (mShowAllIcons) {
resetViewStates();
@@ -121,7 +158,8 @@
childState.applyToView(child);
}
}
- mAnimationStartIndex = -1;
+ mAddAnimationStartIndex = -1;
+ mCannedAnimationStartIndex = -1;
}
@Override
@@ -133,10 +171,10 @@
int childIndex = indexOfChild(child);
if (childIndex < getChildCount() - 1
&& mIconStates.get(getChildAt(childIndex + 1)).iconAppearAmount > 0.0f) {
- if (mAnimationStartIndex < 0) {
- mAnimationStartIndex = childIndex;
+ if (mAddAnimationStartIndex < 0) {
+ mAddAnimationStartIndex = childIndex;
} else {
- mAnimationStartIndex = Math.min(mAnimationStartIndex, childIndex);
+ mAddAnimationStartIndex = Math.min(mAddAnimationStartIndex, childIndex);
}
}
}
@@ -149,10 +187,10 @@
if (icon.getVisibleState() != StatusBarIconView.STATE_HIDDEN
&& child.getVisibility() == VISIBLE) {
int animationStartIndex = findFirstViewIndexAfter(icon.getTranslationX());
- if (mAnimationStartIndex < 0) {
- mAnimationStartIndex = animationStartIndex;
+ if (mAddAnimationStartIndex < 0) {
+ mAddAnimationStartIndex = animationStartIndex;
} else {
- mAnimationStartIndex = Math.min(mAnimationStartIndex, animationStartIndex);
+ mAddAnimationStartIndex = Math.min(mAddAnimationStartIndex, animationStartIndex);
}
}
if (!mChangingViewPositions) {
@@ -177,14 +215,13 @@
return getChildCount();
}
- public WeakHashMap<View, IconState> resetViewStates() {
+ public void resetViewStates() {
for (int i = 0; i < getChildCount(); i++) {
View view = getChildAt(i);
ViewState iconState = mIconStates.get(view);
iconState.initFrom(view);
iconState.alpha = 1.0f;
}
- return mIconStates;
}
/**
@@ -194,42 +231,74 @@
*/
public void calculateIconTranslations() {
float translationX = getActualPaddingStart();
- int overflowingIconIndex = -1;
- int lastTwoIconWidth = 0;
+ int firstOverflowIndex = -1;
int childCount = getChildCount();
+ float layoutEnd = getLayoutEnd();
+ float overflowStart = layoutEnd - mIconSize * (2 + OVERFLOW_EARLY_AMOUNT);
+ boolean hasAmbient = mSpeedBumpIndex != -1 && mSpeedBumpIndex < getChildCount();
+ float visualOverflowStart = 0;
for (int i = 0; i < childCount; i++) {
View view = getChildAt(i);
IconState iconState = mIconStates.get(view);
iconState.xTranslation = translationX;
- iconState.visibleState = StatusBarIconView.STATE_ICON;
- translationX += iconState.iconAppearAmount * view.getWidth();
- if (translationX > getLayoutEnd()) {
- // we are overflowing it with this icon
- overflowingIconIndex = i - 1;
- lastTwoIconWidth = view.getWidth();
- break;
+ boolean isAmbient = mSpeedBumpIndex != -1 && i >= mSpeedBumpIndex
+ && iconState.iconAppearAmount > 0.0f;
+ boolean noOverflowAfter = i == childCount - 1;
+ if (mOpenedAmount != 0.0f) {
+ noOverflowAfter = noOverflowAfter && !hasAmbient;
}
+ iconState.visibleState = StatusBarIconView.STATE_ICON;
+ if (firstOverflowIndex == -1 && (isAmbient
+ || (translationX >= (noOverflowAfter ? layoutEnd - mIconSize : overflowStart)))) {
+ firstOverflowIndex = noOverflowAfter ? i - 1 : i;
+ int totalDotLength = mStaticDotRadius * 6 + 2 * mDotPadding;
+ visualOverflowStart = overflowStart + mIconSize * (1 + OVERFLOW_EARLY_AMOUNT)
+ - totalDotLength / 2
+ - mIconSize * 0.5f + mStaticDotRadius;
+ if (isAmbient) {
+ visualOverflowStart = Math.min(translationX, visualOverflowStart
+ + mStaticDotRadius * 2 + mDotPadding);
+ } else {
+ visualOverflowStart += (translationX - overflowStart) / mIconSize
+ * (mStaticDotRadius * 2 + mDotPadding);
+ }
+ if (mShowAllIcons) {
+ // We want to perfectly position the overflow in the static state, such that
+ // it's perfectly centered instead of measuring it from the end.
+ mVisualOverflowAdaption = 0;
+ if (firstOverflowIndex != -1) {
+ View firstOverflowView = getChildAt(i);
+ IconState overflowState = mIconStates.get(firstOverflowView);
+ float totalAmount = layoutEnd - overflowState.xTranslation;
+ float newPosition = overflowState.xTranslation + totalAmount / 2
+ - totalDotLength / 2
+ - mIconSize * 0.5f + mStaticDotRadius;
+ mVisualOverflowAdaption = newPosition - visualOverflowStart;
+ visualOverflowStart = newPosition;
+ }
+ } else {
+ visualOverflowStart += mVisualOverflowAdaption * (1f - mOpenedAmount);
+ }
+ }
+ translationX += iconState.iconAppearAmount * view.getWidth();
}
- if (overflowingIconIndex != -1) {
+ if (firstOverflowIndex != -1) {
int numDots = 1;
- View overflowIcon = getChildAt(overflowingIconIndex);
- IconState overflowState = mIconStates.get(overflowIcon);
- lastTwoIconWidth += overflowIcon.getWidth();
- int dotWidth = mStaticDotRadius * 2 + mDotPadding;
- int totalDotLength = mStaticDotRadius * 6 + 2 * mDotPadding;
- translationX = (getLayoutEnd() - lastTwoIconWidth / 2 - totalDotLength / 2)
- - overflowIcon.getWidth() * 0.3f + mStaticDotRadius;
- float overflowStart = getLayoutEnd() - lastTwoIconWidth;
- float overlapAmount = (overflowState.xTranslation - overflowStart)
- / overflowIcon.getWidth();
- translationX += overlapAmount * dotWidth;
- for (int i = overflowingIconIndex; i < childCount; i++) {
+ translationX = visualOverflowStart;
+ for (int i = firstOverflowIndex; i < childCount; i++) {
View view = getChildAt(i);
IconState iconState = mIconStates.get(view);
+ int dotWidth = mStaticDotRadius * 2 + mDotPadding;
iconState.xTranslation = translationX;
if (numDots <= 3) {
- iconState.visibleState = StatusBarIconView.STATE_DOT;
- translationX += numDots == 3 ? 3 * dotWidth : dotWidth;
+ if (numDots == 1 && iconState.iconAppearAmount < 0.8f) {
+ iconState.visibleState = StatusBarIconView.STATE_ICON;
+ numDots--;
+ } else {
+ iconState.visibleState = StatusBarIconView.STATE_DOT;
+ }
+ translationX += (numDots == 3 ? 3 * dotWidth : dotWidth)
+ * iconState.iconAppearAmount;
} else {
iconState.visibleState = StatusBarIconView.STATE_HIDDEN;
}
@@ -250,14 +319,14 @@
}
private float getActualPaddingEnd() {
- if (mActualPaddingEnd < 0) {
+ if (mActualPaddingEnd == NO_VALUE) {
return getPaddingEnd();
}
return mActualPaddingEnd;
}
private float getActualPaddingStart() {
- if (mActualPaddingStart < 0) {
+ if (mActualPaddingStart == NO_VALUE) {
return getPaddingStart();
}
return mActualPaddingStart;
@@ -295,7 +364,7 @@
}
public int getActualWidth() {
- if (mActualLayoutWidth < 0) {
+ if (mActualLayoutWidth == NO_VALUE) {
return getWidth();
}
return mActualLayoutWidth;
@@ -305,28 +374,90 @@
mChangingViewPositions = changingViewPositions;
}
+ public IconState getIconState(StatusBarIconView icon) {
+ return mIconStates.get(icon);
+ }
+
+ public void setSpeedBumpIndex(int speedBumpIndex) {
+ mSpeedBumpIndex = speedBumpIndex;
+ }
+
+ public void setOpenedAmount(float expandAmount) {
+ mOpenedAmount = expandAmount;
+ }
+
+ public float getVisualOverflowAdaption() {
+ return mVisualOverflowAdaption;
+ }
+
+ public void setVisualOverflowAdaption(float visualOverflowAdaption) {
+ mVisualOverflowAdaption = visualOverflowAdaption;
+ }
+
+ public boolean hasOverflow() {
+ float width = (getChildCount() + OVERFLOW_EARLY_AMOUNT) * mIconSize;
+ return width - (getWidth() - getActualPaddingStart() - getActualPaddingEnd()) > 0;
+ }
+
+ public int getIconSize() {
+ return mIconSize;
+ }
+
public class IconState extends ViewState {
public float iconAppearAmount = 1.0f;
+ public float clampedAppearAmount = 1.0f;
public int visibleState;
public boolean justAdded = true;
+ public boolean needsCannedAnimation;
+ public boolean keepClampedPosition;
+ public boolean useFullTransitionAmount;
+ public boolean translateContent;
@Override
public void applyToView(View view) {
if (view instanceof StatusBarIconView) {
StatusBarIconView icon = (StatusBarIconView) view;
- AnimationProperties animationProperties = DOT_ANIMATION_PROPERTIES;
+ boolean animate = false;
+ AnimationProperties animationProperties = null;
if (justAdded) {
super.applyToView(icon);
icon.setAlpha(0.0f);
icon.setVisibleState(StatusBarIconView.STATE_HIDDEN, false /* animate */);
animationProperties = ADD_ICON_PROPERTIES;
+ animate = true;
+ } else if (visibleState != icon.getVisibleState()) {
+ animationProperties = DOT_ANIMATION_PROPERTIES;
+ animate = true;
}
- boolean animate = visibleState != icon.getVisibleState() || justAdded;
- if (!animate && mAnimationStartIndex >= 0
+ if (!animate && mAddAnimationStartIndex >= 0
+ && indexOfChild(view) >= mAddAnimationStartIndex
&& (icon.getVisibleState() != StatusBarIconView.STATE_HIDDEN
|| visibleState != StatusBarIconView.STATE_HIDDEN)) {
- int viewIndex = indexOfChild(view);
- animate = viewIndex >= mAnimationStartIndex;
+ animationProperties = DOT_ANIMATION_PROPERTIES;
+ animate = true;
+ }
+ if (needsCannedAnimation) {
+ AnimationFilter animationFilter = mTempProperties.getAnimationFilter();
+ animationFilter.reset();
+ animationFilter.combineFilter(ICON_ANIMATION_PROPERTIES.getAnimationFilter());
+ if (animationProperties != null) {
+ animationFilter.combineFilter(animationProperties.getAnimationFilter());
+ }
+ animationProperties = mTempProperties;
+ animationProperties.setDuration(CANNED_ANIMATION_DURATION);
+ animate = true;
+ mCannedAnimationStartIndex = indexOfChild(view);
+ }
+ if (!animate && mCannedAnimationStartIndex >= 0
+ && indexOfChild(view) > mCannedAnimationStartIndex
+ && (icon.getVisibleState() != StatusBarIconView.STATE_HIDDEN
+ || visibleState != StatusBarIconView.STATE_HIDDEN)) {
+ AnimationFilter animationFilter = mTempProperties.getAnimationFilter();
+ animationFilter.reset();
+ animationFilter.animateX();
+ animationProperties = mTempProperties;
+ animationProperties.setDuration(CANNED_ANIMATION_DURATION);
+ animate = true;
}
icon.setVisibleState(visibleState);
if (animate) {
@@ -336,6 +467,13 @@
}
}
justAdded = false;
+ needsCannedAnimation = false;
+ }
+
+ protected void onYTranslationAnimationFinished(View view) {
+ if (hidden) {
+ view.setVisibility(INVISIBLE);
+ }
}
}
}
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 99e98f2e..b5865db 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
@@ -208,6 +208,7 @@
};
private NotificationGroupManager mGroupManager;
private boolean mOpening;
+ private int mIndicationBottomPadding;
public NotificationPanelView(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -273,6 +274,8 @@
R.dimen.notification_panel_min_side_margin);
mMaxFadeoutHeight = getResources().getDimensionPixelSize(
R.dimen.max_notification_fadeout_height);
+ mIndicationBottomPadding = getResources().getDimensionPixelSize(
+ R.dimen.keyguard_indication_bottom_padding);
}
public void updateResources() {
@@ -406,7 +409,8 @@
R.dimen.notification_divider_height));
float shelfSize = mNotificationStackScroller.getNotificationShelf().getIntrinsicHeight()
+ notificationPadding;
- float availableSpace = mNotificationStackScroller.getHeight() - minPadding - shelfSize;
+ float availableSpace = mNotificationStackScroller.getHeight() - minPadding - shelfSize
+ - mIndicationBottomPadding;
int count = 0;
for (int i = 0; i < mNotificationStackScroller.getChildCount(); i++) {
ExpandableView child = (ExpandableView) mNotificationStackScroller.getChildAt(i);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
index f16c834..75cabe7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
@@ -685,7 +685,7 @@
mOverExpandedBeforeFling = getOverExpansionAmount() > 0f;
ValueAnimator animator = createHeightAnimator(target);
if (expand) {
- if (expandBecauseOfFalsing) {
+ if (expandBecauseOfFalsing && vel < 0) {
vel = 0;
}
mFlingAnimationUtils.apply(animator, mExpandedHeight, target, vel, getHeight());
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
index 2b74c847..b2dae50 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
@@ -22,6 +22,7 @@
import static android.app.StatusBarManager.WINDOW_STATE_HIDDEN;
import static android.app.StatusBarManager.WINDOW_STATE_SHOWING;
import static android.app.StatusBarManager.windowStateToString;
+
import static com.android.systemui.statusbar.phone.BarTransitions.MODE_LIGHTS_OUT;
import static com.android.systemui.statusbar.phone.BarTransitions.MODE_LIGHTS_OUT_TRANSPARENT;
import static com.android.systemui.statusbar.phone.BarTransitions.MODE_OPAQUE;
@@ -83,8 +84,8 @@
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SystemClock;
-import android.os.Trace;
import android.os.SystemProperties;
+import android.os.Trace;
import android.os.UserHandle;
import android.os.UserManager;
import android.os.Vibrator;
@@ -123,13 +124,13 @@
import com.android.keyguard.KeyguardStatusView;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.keyguard.KeyguardUpdateMonitorCallback;
+import com.android.keyguard.LatencyTracker;
import com.android.keyguard.ViewMediatorCallback;
import com.android.systemui.BatteryMeterView;
import com.android.systemui.DemoMode;
import com.android.systemui.EventLogConstants;
import com.android.systemui.EventLogTags;
import com.android.systemui.Interpolators;
-import com.android.keyguard.LatencyTracker;
import com.android.systemui.Prefs;
import com.android.systemui.R;
import com.android.systemui.SystemUIFactory;
@@ -178,19 +179,20 @@
import com.android.systemui.statusbar.policy.BrightnessMirrorController;
import com.android.systemui.statusbar.policy.CastControllerImpl;
import com.android.systemui.statusbar.policy.EncryptionHelper;
-import com.android.systemui.statusbar.policy.FlashlightController;
+import com.android.systemui.statusbar.policy.FlashlightControllerImpl;
import com.android.systemui.statusbar.policy.HeadsUpManager;
import com.android.systemui.statusbar.policy.HotspotControllerImpl;
-import com.android.systemui.statusbar.policy.KeyguardMonitor;
+import com.android.systemui.statusbar.policy.KeyguardMonitorImpl;
import com.android.systemui.statusbar.policy.KeyguardUserSwitcher;
import com.android.systemui.statusbar.policy.LocationControllerImpl;
import com.android.systemui.statusbar.policy.NetworkController;
import com.android.systemui.statusbar.policy.NetworkControllerImpl;
import com.android.systemui.statusbar.policy.NextAlarmController;
+import com.android.systemui.statusbar.policy.NextAlarmControllerImpl;
import com.android.systemui.statusbar.policy.PreviewInflater;
import com.android.systemui.statusbar.policy.RotationLockControllerImpl;
import com.android.systemui.statusbar.policy.SecurityControllerImpl;
-import com.android.systemui.statusbar.policy.UserInfoController;
+import com.android.systemui.statusbar.policy.UserInfoControllerImpl;
import com.android.systemui.statusbar.policy.UserSwitcherController;
import com.android.systemui.statusbar.policy.ZenModeController;
import com.android.systemui.statusbar.stack.NotificationStackScrollLayout;
@@ -319,15 +321,15 @@
NetworkControllerImpl mNetworkController;
HotspotControllerImpl mHotspotController;
RotationLockControllerImpl mRotationLockController;
- UserInfoController mUserInfoController;
+ UserInfoControllerImpl mUserInfoController;
protected ZenModeController mZenModeController;
CastControllerImpl mCastController;
VolumeComponent mVolumeComponent;
KeyguardUserSwitcher mKeyguardUserSwitcher;
- FlashlightController mFlashlightController;
+ FlashlightControllerImpl mFlashlightController;
protected UserSwitcherController mUserSwitcherController;
- NextAlarmController mNextAlarmController;
- protected KeyguardMonitor mKeyguardMonitor;
+ NextAlarmControllerImpl mNextAlarmController;
+ protected KeyguardMonitorImpl mKeyguardMonitor;
BrightnessMirrorController mBrightnessMirrorController;
AccessibilityController mAccessibilityController;
protected FingerprintUnlockController mFingerprintUnlockController;
@@ -895,7 +897,7 @@
if (mContext.getResources().getBoolean(R.bool.config_showRotationLock)) {
mRotationLockController = new RotationLockControllerImpl(mContext);
}
- mUserInfoController = new UserInfoController(mContext);
+ mUserInfoController = new UserInfoControllerImpl(mContext);
mVolumeComponent = getComponent(VolumeComponent.class);
if (mVolumeComponent != null) {
mZenModeController = mVolumeComponent.getZenController();
@@ -906,16 +908,16 @@
initSignalCluster(mKeyguardStatusBar);
initEmergencyCryptkeeperText();
- mFlashlightController = new FlashlightController(mContext);
+ mFlashlightController = new FlashlightControllerImpl(mContext);
mKeyguardBottomArea.setFlashlightController(mFlashlightController);
mKeyguardBottomArea.setPhoneStatusBar(this);
mKeyguardBottomArea.setUserSetupComplete(mUserSetup);
mAccessibilityController = new AccessibilityController(mContext);
mKeyguardBottomArea.setAccessibilityController(mAccessibilityController);
- mNextAlarmController = new NextAlarmController(mContext);
+ mNextAlarmController = new NextAlarmControllerImpl(mContext);
mLightStatusBarController = new LightStatusBarController(mIconController,
mBatteryController);
- mKeyguardMonitor = new KeyguardMonitor(mContext);
+ mKeyguardMonitor = new KeyguardMonitorImpl(mContext);
mUserSwitcherController = createUserSwitcherController();
if (UserManager.get(mContext).isUserSwitcherEnabled()) {
createUserSwitcher();
@@ -3572,10 +3574,18 @@
final boolean dismissShade,
final boolean afterKeyguardGone,
final boolean deferred) {
- dismissKeyguardThenExecute(() -> {
+ final Runnable dismissAction = () -> {
if (runnable != null) {
AsyncTask.execute(runnable);
}
+ };
+ dismissKeyguardThenExecute(() -> {
+ if (mStatusBarKeyguardViewManager.isShowing()
+ && mStatusBarKeyguardViewManager.isOccluded()) {
+ mStatusBarKeyguardViewManager.addAfterKeyguardGoneRunnable(runnable);
+ } else {
+ dismissAction.run();
+ }
if (dismissShade) {
if (mExpandedVisible) {
animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL, true /* force */,
@@ -3664,8 +3674,6 @@
private void dismissKeyguardThenExecute(OnDismissAction action, Runnable cancelAction,
boolean afterKeyguardGone) {
- afterKeyguardGone |= mStatusBarKeyguardViewManager.isShowing()
- && mStatusBarKeyguardViewManager.isOccluded();
if (mStatusBarKeyguardViewManager.isShowing()) {
mStatusBarKeyguardViewManager.dismissWithAction(action, cancelAction,
afterKeyguardGone);
@@ -4559,6 +4567,7 @@
mGroupManager.setStatusBarState(state);
mFalsingManager.setStatusBarState(state);
mStatusBarWindowManager.setStatusBarState(state);
+ mStackScroller.setStatusBarState(state);
updateReportRejectedTouchVisibility();
updateDozing();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QSTileHost.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QSTileHost.java
index 8fd6bbf..567ab3b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QSTileHost.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QSTileHost.java
@@ -143,7 +143,7 @@
mBattery = battery;
mIconController = iconController;
mNextAlarmController = nextAlarmController;
- mProfileController = new ManagedProfileController(this);
+ mProfileController = new ManagedProfileControllerImpl(this);
mHandlerThread = new HandlerThread(QSTileHost.class.getSimpleName(),
Process.THREAD_PRIORITY_BACKGROUND);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStatusBarHeader.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStatusBarHeader.java
index 28aed87..9ab4d77 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStatusBarHeader.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStatusBarHeader.java
@@ -236,10 +236,17 @@
}
@Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+ mHost.getUserInfoController().addCallback(this);
+ }
+
+ @Override
protected void onDetachedFromWindow() {
setListening(false);
mHost.getUserInfoController().removeCallback(this);
mHost.getNetworkController().removeEmergencyListener(this);
+ mHost.getUserInfoController().removeCallback(this);
super.onDetachedFromWindow();
}
@@ -368,7 +375,7 @@
}
public void setUserInfoController(UserInfoController userInfoController) {
- userInfoController.addCallback(this);
+ // Don't care
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
index 0e74e57..749ff99e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
@@ -556,10 +556,6 @@
mScrimBehind.setExcludedArea(area);
}
- public void setLeftInset(int inset) {
- mScrimBehind.setLeftInset(inset);
- }
-
public int getScrimBehindColor() {
return mScrimBehind.getScrimColorWithAlpha();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index 4263670..2e279b2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -40,6 +40,8 @@
import static com.android.keyguard.KeyguardHostView.OnDismissAction;
import static com.android.systemui.statusbar.phone.FingerprintUnlockController.*;
+import java.util.ArrayList;
+
/**
* Manages creating, showing, hiding and resetting the keyguard within the status bar. Calls back
* via {@link ViewMediatorCallback} to poke the wake lock and report that the keyguard is done,
@@ -90,6 +92,7 @@
protected boolean mLastRemoteInputActive;
private OnDismissAction mAfterKeyguardGoneAction;
+ private final ArrayList<Runnable> mAfterKeyguardGoneRunnables = new ArrayList<>();
private boolean mDeviceWillWakeUp;
private boolean mDeferScrimFadeOut;
@@ -165,6 +168,13 @@
}
/**
+ * Adds a {@param runnable} to be executed after Keyguard is gone.
+ */
+ public void addAfterKeyguardGoneRunnable(Runnable runnable) {
+ mAfterKeyguardGoneRunnables.add(runnable);
+ }
+
+ /**
* Reset the state of the view.
*/
public void reset(boolean hideBouncerWhenShowing) {
@@ -418,6 +428,10 @@
mAfterKeyguardGoneAction.onDismiss();
mAfterKeyguardGoneAction = null;
}
+ for (int i = 0; i < mAfterKeyguardGoneRunnables.size(); i++) {
+ mAfterKeyguardGoneRunnables.get(i).run();
+ }
+ mAfterKeyguardGoneRunnables.clear();
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java
index f6dd88d9..487f0e7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java
@@ -130,7 +130,6 @@
}
private void applyMargins() {
- mService.mScrimController.setLeftInset(mLeftInset);
final int N = getChildCount();
for (int i = 0; i < N; i++) {
View child = getChildAt(i);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DataSaverController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DataSaverController.java
index e5f1e68..0df7859 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DataSaverController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DataSaverController.java
@@ -14,92 +14,14 @@
package com.android.systemui.statusbar.policy;
-import android.content.Context;
-import android.net.INetworkPolicyListener;
-import android.net.NetworkPolicyManager;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.RemoteException;
-
import com.android.systemui.statusbar.policy.DataSaverController.Listener;
-import java.util.ArrayList;
+public interface DataSaverController extends CallbackController<Listener> {
-public class DataSaverController implements CallbackController<Listener> {
-
- private final Handler mHandler = new Handler(Looper.getMainLooper());
- private final ArrayList<Listener> mListeners = new ArrayList<>();
- private final NetworkPolicyManager mPolicyManager;
-
- public DataSaverController(Context context) {
- mPolicyManager = NetworkPolicyManager.from(context);
- }
-
- private void handleRestrictBackgroundChanged(boolean isDataSaving) {
- synchronized (mListeners) {
- for (int i = 0; i < mListeners.size(); i++) {
- mListeners.get(i).onDataSaverChanged(isDataSaving);
- }
- }
- }
-
- public void addCallback(Listener listener) {
- synchronized (mListeners) {
- mListeners.add(listener);
- if (mListeners.size() == 1) {
- mPolicyManager.registerListener(mPolicyListener);
- }
- }
- listener.onDataSaverChanged(isDataSaverEnabled());
- }
-
- public void removeCallback(Listener listener) {
- synchronized (mListeners) {
- mListeners.remove(listener);
- if (mListeners.size() == 0) {
- mPolicyManager.unregisterListener(mPolicyListener);
- }
- }
- }
-
- public boolean isDataSaverEnabled() {
- return mPolicyManager.getRestrictBackground();
- }
-
- public void setDataSaverEnabled(boolean enabled) {
- mPolicyManager.setRestrictBackground(enabled);
- try {
- mPolicyListener.onRestrictBackgroundChanged(enabled);
- } catch (RemoteException e) {
- }
- }
-
- private final INetworkPolicyListener mPolicyListener = new INetworkPolicyListener.Stub() {
- @Override
- public void onUidRulesChanged(int uid, int uidRules) throws RemoteException {
- }
-
- @Override
- public void onMeteredIfacesChanged(String[] strings) throws RemoteException {
- }
-
- @Override
- public void onRestrictBackgroundChanged(final boolean isDataSaving) throws RemoteException {
- mHandler.post(new Runnable() {
- @Override
- public void run() {
- handleRestrictBackgroundChanged(isDataSaving);
- }
- });
- }
-
- @Override
- public void onUidPoliciesChanged(int uid, int uidPolicies) throws RemoteException {
- }
- };
+ boolean isDataSaverEnabled();
+ void setDataSaverEnabled(boolean enabled);
public interface Listener {
void onDataSaverChanged(boolean isDataSaving);
}
-
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DataSaverControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DataSaverControllerImpl.java
new file mode 100644
index 0000000..2951943
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DataSaverControllerImpl.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package com.android.systemui.statusbar.policy;
+
+import android.content.Context;
+import android.net.INetworkPolicyListener;
+import android.net.NetworkPolicyManager;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.RemoteException;
+
+import com.android.systemui.statusbar.policy.DataSaverController.Listener;
+
+import java.util.ArrayList;
+
+public class DataSaverControllerImpl implements DataSaverController {
+
+ private final Handler mHandler = new Handler(Looper.getMainLooper());
+ private final ArrayList<Listener> mListeners = new ArrayList<>();
+ private final NetworkPolicyManager mPolicyManager;
+
+ public DataSaverControllerImpl(Context context) {
+ mPolicyManager = NetworkPolicyManager.from(context);
+ }
+
+ private void handleRestrictBackgroundChanged(boolean isDataSaving) {
+ synchronized (mListeners) {
+ for (int i = 0; i < mListeners.size(); i++) {
+ mListeners.get(i).onDataSaverChanged(isDataSaving);
+ }
+ }
+ }
+
+ public void addCallback(Listener listener) {
+ synchronized (mListeners) {
+ mListeners.add(listener);
+ if (mListeners.size() == 1) {
+ mPolicyManager.registerListener(mPolicyListener);
+ }
+ }
+ listener.onDataSaverChanged(isDataSaverEnabled());
+ }
+
+ public void removeCallback(Listener listener) {
+ synchronized (mListeners) {
+ mListeners.remove(listener);
+ if (mListeners.size() == 0) {
+ mPolicyManager.unregisterListener(mPolicyListener);
+ }
+ }
+ }
+
+ public boolean isDataSaverEnabled() {
+ return mPolicyManager.getRestrictBackground();
+ }
+
+ public void setDataSaverEnabled(boolean enabled) {
+ mPolicyManager.setRestrictBackground(enabled);
+ try {
+ mPolicyListener.onRestrictBackgroundChanged(enabled);
+ } catch (RemoteException e) {
+ }
+ }
+
+ private final INetworkPolicyListener mPolicyListener = new INetworkPolicyListener.Stub() {
+ @Override
+ public void onUidRulesChanged(int uid, int uidRules) throws RemoteException {
+ }
+
+ @Override
+ public void onMeteredIfacesChanged(String[] strings) throws RemoteException {
+ }
+
+ @Override
+ public void onRestrictBackgroundChanged(final boolean isDataSaving) throws RemoteException {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ handleRestrictBackgroundChanged(isDataSaving);
+ }
+ });
+ }
+
+ @Override
+ public void onUidPoliciesChanged(int uid, int uidPolicies) throws RemoteException {
+ }
+ };
+
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/EmergencyCryptkeeperText.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/EmergencyCryptkeeperText.java
index 8abfb89..a2d1baf 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/EmergencyCryptkeeperText.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/EmergencyCryptkeeperText.java
@@ -17,6 +17,7 @@
package com.android.systemui.statusbar.policy;
import android.annotation.Nullable;
+import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
@@ -41,12 +42,20 @@
public class EmergencyCryptkeeperText extends TextView {
private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
- private KeyguardUpdateMonitorCallback mCallback = new KeyguardUpdateMonitorCallback() {
+ private final KeyguardUpdateMonitorCallback mCallback = new KeyguardUpdateMonitorCallback() {
@Override
public void onPhoneStateChanged(int phoneState) {
update();
}
};
+ private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (Intent.ACTION_AIRPLANE_MODE_CHANGED.equals(intent.getAction())) {
+ update();
+ }
+ }
+ };
public EmergencyCryptkeeperText(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
@@ -58,6 +67,8 @@
super.onAttachedToWindow();
mKeyguardUpdateMonitor = KeyguardUpdateMonitor.getInstance(mContext);
mKeyguardUpdateMonitor.registerCallback(mCallback);
+ getContext().registerReceiver(mReceiver,
+ new IntentFilter(Intent.ACTION_AIRPLANE_MODE_CHANGED));
update();
}
@@ -67,6 +78,7 @@
if (mKeyguardUpdateMonitor != null) {
mKeyguardUpdateMonitor.removeCallback(mCallback);
}
+ getContext().unregisterReceiver(mReceiver);
}
public void update() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/FlashlightController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/FlashlightController.java
index 0f77b03..6023f3e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/FlashlightController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/FlashlightController.java
@@ -1,255 +1,27 @@
/*
- * Copyright (C) 2014 The Android Open Source Project
+ * Copyright (C) 2016 The Android Open Source Project
*
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * 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
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
*/
package com.android.systemui.statusbar.policy;
-import android.content.Context;
-import android.content.pm.PackageManager;
-import android.hardware.camera2.CameraAccessException;
-import android.hardware.camera2.CameraCharacteristics;
-import android.hardware.camera2.CameraManager;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.os.Process;
-import android.text.TextUtils;
-import android.util.Log;
-
import com.android.systemui.statusbar.policy.FlashlightController.FlashlightListener;
-import java.io.FileDescriptor;
-import java.io.PrintWriter;
-import java.lang.ref.WeakReference;
-import java.util.ArrayList;
+public interface FlashlightController extends CallbackController<FlashlightListener> {
-/**
- * Manages the flashlight.
- */
-public class FlashlightController implements CallbackController<FlashlightListener> {
-
- private static final String TAG = "FlashlightController";
- private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
-
- private static final int DISPATCH_ERROR = 0;
- private static final int DISPATCH_CHANGED = 1;
- private static final int DISPATCH_AVAILABILITY_CHANGED = 2;
-
- private final CameraManager mCameraManager;
- private final Context mContext;
- /** Call {@link #ensureHandler()} before using */
- private Handler mHandler;
-
- /** Lock on mListeners when accessing */
- private final ArrayList<WeakReference<FlashlightListener>> mListeners = new ArrayList<>(1);
-
- /** Lock on {@code this} when accessing */
- private boolean mFlashlightEnabled;
-
- private String mCameraId;
- private boolean mTorchAvailable;
-
- public FlashlightController(Context context) {
- mContext = context;
- mCameraManager = (CameraManager) mContext.getSystemService(Context.CAMERA_SERVICE);
-
- tryInitCamera();
- }
-
- private void tryInitCamera() {
- try {
- mCameraId = getCameraId();
- } catch (Throwable e) {
- Log.e(TAG, "Couldn't initialize.", e);
- return;
- }
-
- if (mCameraId != null) {
- ensureHandler();
- mCameraManager.registerTorchCallback(mTorchCallback, mHandler);
- }
- }
-
- public void setFlashlight(boolean enabled) {
- boolean pendingError = false;
- synchronized (this) {
- if (mCameraId == null) return;
- if (mFlashlightEnabled != enabled) {
- mFlashlightEnabled = enabled;
- try {
- mCameraManager.setTorchMode(mCameraId, enabled);
- } catch (CameraAccessException e) {
- Log.e(TAG, "Couldn't set torch mode", e);
- mFlashlightEnabled = false;
- pendingError = true;
- }
- }
- }
- dispatchModeChanged(mFlashlightEnabled);
- if (pendingError) {
- dispatchError();
- }
- }
-
- public boolean hasFlashlight() {
- return mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA_FLASH);
- }
-
- public synchronized boolean isEnabled() {
- return mFlashlightEnabled;
- }
-
- public synchronized boolean isAvailable() {
- return mTorchAvailable;
- }
-
- public void addCallback(FlashlightListener l) {
- synchronized (mListeners) {
- if (mCameraId == null) {
- tryInitCamera();
- }
- cleanUpListenersLocked(l);
- mListeners.add(new WeakReference<>(l));
- }
- }
-
- public void removeCallback(FlashlightListener l) {
- synchronized (mListeners) {
- cleanUpListenersLocked(l);
- }
- }
-
- private synchronized void ensureHandler() {
- if (mHandler == null) {
- HandlerThread thread = new HandlerThread(TAG, Process.THREAD_PRIORITY_BACKGROUND);
- thread.start();
- mHandler = new Handler(thread.getLooper());
- }
- }
-
- private String getCameraId() throws CameraAccessException {
- String[] ids = mCameraManager.getCameraIdList();
- for (String id : ids) {
- CameraCharacteristics c = mCameraManager.getCameraCharacteristics(id);
- Boolean flashAvailable = c.get(CameraCharacteristics.FLASH_INFO_AVAILABLE);
- Integer lensFacing = c.get(CameraCharacteristics.LENS_FACING);
- if (flashAvailable != null && flashAvailable
- && lensFacing != null && lensFacing == CameraCharacteristics.LENS_FACING_BACK) {
- return id;
- }
- }
- return null;
- }
-
- private void dispatchModeChanged(boolean enabled) {
- dispatchListeners(DISPATCH_CHANGED, enabled);
- }
-
- private void dispatchError() {
- dispatchListeners(DISPATCH_CHANGED, false /* argument (ignored) */);
- }
-
- private void dispatchAvailabilityChanged(boolean available) {
- dispatchListeners(DISPATCH_AVAILABILITY_CHANGED, available);
- }
-
- private void dispatchListeners(int message, boolean argument) {
- synchronized (mListeners) {
- final int N = mListeners.size();
- boolean cleanup = false;
- for (int i = 0; i < N; i++) {
- FlashlightListener l = mListeners.get(i).get();
- if (l != null) {
- if (message == DISPATCH_ERROR) {
- l.onFlashlightError();
- } else if (message == DISPATCH_CHANGED) {
- l.onFlashlightChanged(argument);
- } else if (message == DISPATCH_AVAILABILITY_CHANGED) {
- l.onFlashlightAvailabilityChanged(argument);
- }
- } else {
- cleanup = true;
- }
- }
- if (cleanup) {
- cleanUpListenersLocked(null);
- }
- }
- }
-
- private void cleanUpListenersLocked(FlashlightListener listener) {
- for (int i = mListeners.size() - 1; i >= 0; i--) {
- FlashlightListener found = mListeners.get(i).get();
- if (found == null || found == listener) {
- mListeners.remove(i);
- }
- }
- }
-
- private final CameraManager.TorchCallback mTorchCallback =
- new CameraManager.TorchCallback() {
-
- @Override
- public void onTorchModeUnavailable(String cameraId) {
- if (TextUtils.equals(cameraId, mCameraId)) {
- setCameraAvailable(false);
- }
- }
-
- @Override
- public void onTorchModeChanged(String cameraId, boolean enabled) {
- if (TextUtils.equals(cameraId, mCameraId)) {
- setCameraAvailable(true);
- setTorchMode(enabled);
- }
- }
-
- private void setCameraAvailable(boolean available) {
- boolean changed;
- synchronized (FlashlightController.this) {
- changed = mTorchAvailable != available;
- mTorchAvailable = available;
- }
- if (changed) {
- if (DEBUG) Log.d(TAG, "dispatchAvailabilityChanged(" + available + ")");
- dispatchAvailabilityChanged(available);
- }
- }
-
- private void setTorchMode(boolean enabled) {
- boolean changed;
- synchronized (FlashlightController.this) {
- changed = mFlashlightEnabled != enabled;
- mFlashlightEnabled = enabled;
- }
- if (changed) {
- if (DEBUG) Log.d(TAG, "dispatchModeChanged(" + enabled + ")");
- dispatchModeChanged(enabled);
- }
- }
- };
-
- public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
- pw.println("FlashlightController state:");
-
- pw.print(" mCameraId=");
- pw.println(mCameraId);
- pw.print(" mFlashlightEnabled=");
- pw.println(mFlashlightEnabled);
- pw.print(" mTorchAvailable=");
- pw.println(mTorchAvailable);
- }
+ boolean hasFlashlight();
+ void setFlashlight(boolean newState);
+ boolean isAvailable();
+ boolean isEnabled();
public interface FlashlightListener {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/FlashlightControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/FlashlightControllerImpl.java
new file mode 100644
index 0000000..008d837
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/FlashlightControllerImpl.java
@@ -0,0 +1,253 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.statusbar.policy;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.hardware.camera2.CameraAccessException;
+import android.hardware.camera2.CameraCharacteristics;
+import android.hardware.camera2.CameraManager;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Process;
+import android.text.TextUtils;
+import android.util.Log;
+
+import com.android.systemui.statusbar.policy.FlashlightController.FlashlightListener;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+
+/**
+ * Manages the flashlight.
+ */
+public class FlashlightControllerImpl implements FlashlightController {
+
+ private static final String TAG = "FlashlightController";
+ private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+
+ private static final int DISPATCH_ERROR = 0;
+ private static final int DISPATCH_CHANGED = 1;
+ private static final int DISPATCH_AVAILABILITY_CHANGED = 2;
+
+ private final CameraManager mCameraManager;
+ private final Context mContext;
+ /** Call {@link #ensureHandler()} before using */
+ private Handler mHandler;
+
+ /** Lock on mListeners when accessing */
+ private final ArrayList<WeakReference<FlashlightListener>> mListeners = new ArrayList<>(1);
+
+ /** Lock on {@code this} when accessing */
+ private boolean mFlashlightEnabled;
+
+ private String mCameraId;
+ private boolean mTorchAvailable;
+
+ public FlashlightControllerImpl(Context context) {
+ mContext = context;
+ mCameraManager = (CameraManager) mContext.getSystemService(Context.CAMERA_SERVICE);
+
+ tryInitCamera();
+ }
+
+ private void tryInitCamera() {
+ try {
+ mCameraId = getCameraId();
+ } catch (Throwable e) {
+ Log.e(TAG, "Couldn't initialize.", e);
+ return;
+ }
+
+ if (mCameraId != null) {
+ ensureHandler();
+ mCameraManager.registerTorchCallback(mTorchCallback, mHandler);
+ }
+ }
+
+ public void setFlashlight(boolean enabled) {
+ boolean pendingError = false;
+ synchronized (this) {
+ if (mCameraId == null) return;
+ if (mFlashlightEnabled != enabled) {
+ mFlashlightEnabled = enabled;
+ try {
+ mCameraManager.setTorchMode(mCameraId, enabled);
+ } catch (CameraAccessException e) {
+ Log.e(TAG, "Couldn't set torch mode", e);
+ mFlashlightEnabled = false;
+ pendingError = true;
+ }
+ }
+ }
+ dispatchModeChanged(mFlashlightEnabled);
+ if (pendingError) {
+ dispatchError();
+ }
+ }
+
+ public boolean hasFlashlight() {
+ return mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA_FLASH);
+ }
+
+ public synchronized boolean isEnabled() {
+ return mFlashlightEnabled;
+ }
+
+ public synchronized boolean isAvailable() {
+ return mTorchAvailable;
+ }
+
+ public void addCallback(FlashlightListener l) {
+ synchronized (mListeners) {
+ if (mCameraId == null) {
+ tryInitCamera();
+ }
+ cleanUpListenersLocked(l);
+ mListeners.add(new WeakReference<>(l));
+ }
+ }
+
+ public void removeCallback(FlashlightListener l) {
+ synchronized (mListeners) {
+ cleanUpListenersLocked(l);
+ }
+ }
+
+ private synchronized void ensureHandler() {
+ if (mHandler == null) {
+ HandlerThread thread = new HandlerThread(TAG, Process.THREAD_PRIORITY_BACKGROUND);
+ thread.start();
+ mHandler = new Handler(thread.getLooper());
+ }
+ }
+
+ private String getCameraId() throws CameraAccessException {
+ String[] ids = mCameraManager.getCameraIdList();
+ for (String id : ids) {
+ CameraCharacteristics c = mCameraManager.getCameraCharacteristics(id);
+ Boolean flashAvailable = c.get(CameraCharacteristics.FLASH_INFO_AVAILABLE);
+ Integer lensFacing = c.get(CameraCharacteristics.LENS_FACING);
+ if (flashAvailable != null && flashAvailable
+ && lensFacing != null && lensFacing == CameraCharacteristics.LENS_FACING_BACK) {
+ return id;
+ }
+ }
+ return null;
+ }
+
+ private void dispatchModeChanged(boolean enabled) {
+ dispatchListeners(DISPATCH_CHANGED, enabled);
+ }
+
+ private void dispatchError() {
+ dispatchListeners(DISPATCH_CHANGED, false /* argument (ignored) */);
+ }
+
+ private void dispatchAvailabilityChanged(boolean available) {
+ dispatchListeners(DISPATCH_AVAILABILITY_CHANGED, available);
+ }
+
+ private void dispatchListeners(int message, boolean argument) {
+ synchronized (mListeners) {
+ final int N = mListeners.size();
+ boolean cleanup = false;
+ for (int i = 0; i < N; i++) {
+ FlashlightListener l = mListeners.get(i).get();
+ if (l != null) {
+ if (message == DISPATCH_ERROR) {
+ l.onFlashlightError();
+ } else if (message == DISPATCH_CHANGED) {
+ l.onFlashlightChanged(argument);
+ } else if (message == DISPATCH_AVAILABILITY_CHANGED) {
+ l.onFlashlightAvailabilityChanged(argument);
+ }
+ } else {
+ cleanup = true;
+ }
+ }
+ if (cleanup) {
+ cleanUpListenersLocked(null);
+ }
+ }
+ }
+
+ private void cleanUpListenersLocked(FlashlightListener listener) {
+ for (int i = mListeners.size() - 1; i >= 0; i--) {
+ FlashlightListener found = mListeners.get(i).get();
+ if (found == null || found == listener) {
+ mListeners.remove(i);
+ }
+ }
+ }
+
+ private final CameraManager.TorchCallback mTorchCallback =
+ new CameraManager.TorchCallback() {
+
+ @Override
+ public void onTorchModeUnavailable(String cameraId) {
+ if (TextUtils.equals(cameraId, mCameraId)) {
+ setCameraAvailable(false);
+ }
+ }
+
+ @Override
+ public void onTorchModeChanged(String cameraId, boolean enabled) {
+ if (TextUtils.equals(cameraId, mCameraId)) {
+ setCameraAvailable(true);
+ setTorchMode(enabled);
+ }
+ }
+
+ private void setCameraAvailable(boolean available) {
+ boolean changed;
+ synchronized (FlashlightControllerImpl.this) {
+ changed = mTorchAvailable != available;
+ mTorchAvailable = available;
+ }
+ if (changed) {
+ if (DEBUG) Log.d(TAG, "dispatchAvailabilityChanged(" + available + ")");
+ dispatchAvailabilityChanged(available);
+ }
+ }
+
+ private void setTorchMode(boolean enabled) {
+ boolean changed;
+ synchronized (FlashlightControllerImpl.this) {
+ changed = mFlashlightEnabled != enabled;
+ mFlashlightEnabled = enabled;
+ }
+ if (changed) {
+ if (DEBUG) Log.d(TAG, "dispatchModeChanged(" + enabled + ")");
+ dispatchModeChanged(enabled);
+ }
+ }
+ };
+
+ public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ pw.println("FlashlightController state:");
+
+ pw.print(" mCameraId=");
+ pw.println(mCameraId);
+ pw.print(" mFlashlightEnabled=");
+ pw.println(mFlashlightEnabled);
+ pw.print(" mTorchAvailable=");
+ pw.println(mTorchAvailable);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardMonitor.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardMonitor.java
index 0396613..de47267 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardMonitor.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardMonitor.java
@@ -1,126 +1,28 @@
/*
- * Copyright (C) 2014 The Android Open Source Project
+ * Copyright (C) 2016 The Android Open Source Project
*
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * 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.
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
*/
package com.android.systemui.statusbar.policy;
-import android.app.ActivityManager;
-import android.content.Context;
-import android.os.RemoteException;
-import android.view.WindowManagerGlobal;
-
-import com.android.keyguard.KeyguardUpdateMonitor;
-import com.android.keyguard.KeyguardUpdateMonitorCallback;
-import com.android.systemui.settings.CurrentUserTracker;
import com.android.systemui.statusbar.policy.KeyguardMonitor.Callback;
-import java.util.ArrayList;
+public interface KeyguardMonitor extends CallbackController<Callback> {
-public class KeyguardMonitor extends KeyguardUpdateMonitorCallback
- implements CallbackController<Callback> {
-
- private final ArrayList<Callback> mCallbacks = new ArrayList<Callback>();
-
- private final Context mContext;
- private final CurrentUserTracker mUserTracker;
- private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
-
- private int mCurrentUser;
- private boolean mShowing;
- private boolean mSecure;
- private boolean mOccluded;
- private boolean mCanSkipBouncer;
-
- private boolean mListening;
-
- public KeyguardMonitor(Context context) {
- mContext = context;
- mKeyguardUpdateMonitor = KeyguardUpdateMonitor.getInstance(mContext);
- mUserTracker = new CurrentUserTracker(mContext) {
- @Override
- public void onUserSwitched(int newUserId) {
- mCurrentUser = newUserId;
- updateCanSkipBouncerState();
- }
- };
- }
-
- public void addCallback(Callback callback) {
- mCallbacks.add(callback);
- if (mCallbacks.size() != 0 && !mListening) {
- mListening = true;
- mCurrentUser = ActivityManager.getCurrentUser();
- updateCanSkipBouncerState();
- mKeyguardUpdateMonitor.registerCallback(this);
- mUserTracker.startTracking();
- }
- }
-
- public void removeCallback(Callback callback) {
- if (mCallbacks.remove(callback) && mCallbacks.size() == 0 && mListening) {
- mListening = false;
- mKeyguardUpdateMonitor.removeCallback(this);
- mUserTracker.stopTracking();
- }
- }
-
- public boolean isShowing() {
- return mShowing;
- }
-
- public boolean isSecure() {
- return mSecure;
- }
-
- public boolean isOccluded() {
- return mOccluded;
- }
-
- public boolean canSkipBouncer() {
- return mCanSkipBouncer;
- }
-
- public void notifyKeyguardState(boolean showing, boolean secure, boolean occluded) {
- if (mShowing == showing && mSecure == secure && mOccluded == occluded) return;
- mShowing = showing;
- mSecure = secure;
- mOccluded = occluded;
- notifyKeyguardChanged();
- }
-
- @Override
- public void onTrustChanged(int userId) {
- updateCanSkipBouncerState();
- notifyKeyguardChanged();
- }
-
- public boolean isDeviceInteractive() {
- return mKeyguardUpdateMonitor.isDeviceInteractive();
- }
-
- private void updateCanSkipBouncerState() {
- mCanSkipBouncer = mKeyguardUpdateMonitor.getUserCanSkipBouncer(mCurrentUser);
- }
-
- private void notifyKeyguardChanged() {
- for (Callback callback : mCallbacks) {
- callback.onKeyguardChanged();
- }
- }
+ boolean isSecure();
+ boolean canSkipBouncer();
+ boolean isShowing();
public interface Callback {
void onKeyguardChanged();
}
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardMonitorImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardMonitorImpl.java
new file mode 100644
index 0000000..769f93f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardMonitorImpl.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.policy;
+
+import android.app.ActivityManager;
+import android.content.Context;
+import android.os.RemoteException;
+import android.view.WindowManagerGlobal;
+
+import com.android.keyguard.KeyguardUpdateMonitor;
+import com.android.keyguard.KeyguardUpdateMonitorCallback;
+import com.android.systemui.settings.CurrentUserTracker;
+import com.android.systemui.statusbar.policy.KeyguardMonitor.Callback;
+
+import java.util.ArrayList;
+
+public class KeyguardMonitorImpl extends KeyguardUpdateMonitorCallback
+ implements KeyguardMonitor {
+
+ private final ArrayList<Callback> mCallbacks = new ArrayList<Callback>();
+
+ private final Context mContext;
+ private final CurrentUserTracker mUserTracker;
+ private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+
+ private int mCurrentUser;
+ private boolean mShowing;
+ private boolean mSecure;
+ private boolean mOccluded;
+ private boolean mCanSkipBouncer;
+
+ private boolean mListening;
+
+ public KeyguardMonitorImpl(Context context) {
+ mContext = context;
+ mKeyguardUpdateMonitor = KeyguardUpdateMonitor.getInstance(mContext);
+ mUserTracker = new CurrentUserTracker(mContext) {
+ @Override
+ public void onUserSwitched(int newUserId) {
+ mCurrentUser = newUserId;
+ updateCanSkipBouncerState();
+ }
+ };
+ }
+
+ public void addCallback(Callback callback) {
+ mCallbacks.add(callback);
+ if (mCallbacks.size() != 0 && !mListening) {
+ mListening = true;
+ mCurrentUser = ActivityManager.getCurrentUser();
+ updateCanSkipBouncerState();
+ mKeyguardUpdateMonitor.registerCallback(this);
+ mUserTracker.startTracking();
+ }
+ }
+
+ public void removeCallback(Callback callback) {
+ if (mCallbacks.remove(callback) && mCallbacks.size() == 0 && mListening) {
+ mListening = false;
+ mKeyguardUpdateMonitor.removeCallback(this);
+ mUserTracker.stopTracking();
+ }
+ }
+
+ public boolean isShowing() {
+ return mShowing;
+ }
+
+ public boolean isSecure() {
+ return mSecure;
+ }
+
+ public boolean isOccluded() {
+ return mOccluded;
+ }
+
+ public boolean canSkipBouncer() {
+ return mCanSkipBouncer;
+ }
+
+ public void notifyKeyguardState(boolean showing, boolean secure, boolean occluded) {
+ if (mShowing == showing && mSecure == secure && mOccluded == occluded) return;
+ mShowing = showing;
+ mSecure = secure;
+ mOccluded = occluded;
+ notifyKeyguardChanged();
+ }
+
+ @Override
+ public void onTrustChanged(int userId) {
+ updateCanSkipBouncerState();
+ notifyKeyguardChanged();
+ }
+
+ public boolean isDeviceInteractive() {
+ return mKeyguardUpdateMonitor.isDeviceInteractive();
+ }
+
+ private void updateCanSkipBouncerState() {
+ mCanSkipBouncer = mKeyguardUpdateMonitor.getUserCanSkipBouncer(mCurrentUser);
+ }
+
+ private void notifyKeyguardChanged() {
+ for (Callback callback : mCallbacks) {
+ callback.onKeyguardChanged();
+ }
+ }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
index 1a9756f..a7fab41 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
@@ -159,7 +159,7 @@
mConfig = config;
mReceiverHandler = new Handler(bgLooper);
mCallbackHandler = callbackHandler;
- mDataSaverController = new DataSaverController(context);
+ mDataSaverController = new DataSaverControllerImpl(context);
mSubscriptionManager = subManager;
mSubDefaults = defaultsHandler;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NextAlarmController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NextAlarmController.java
index 28935bf..e5b0c03 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NextAlarmController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NextAlarmController.java
@@ -1,84 +1,24 @@
/*
- * Copyright (C) 2014 The Android Open Source Project
+ * Copyright (C) 2016 The Android Open Source Project
*
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * 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
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
*/
package com.android.systemui.statusbar.policy;
import android.app.AlarmManager;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.os.UserHandle;
import com.android.systemui.statusbar.policy.NextAlarmController.NextAlarmChangeCallback;
-import java.io.FileDescriptor;
-import java.io.PrintWriter;
-import java.util.ArrayList;
-
-public class NextAlarmController extends BroadcastReceiver
- implements CallbackController<NextAlarmChangeCallback> {
-
- private final ArrayList<NextAlarmChangeCallback> mChangeCallbacks = new ArrayList<>();
-
- private AlarmManager mAlarmManager;
- private AlarmManager.AlarmClockInfo mNextAlarm;
-
- public NextAlarmController(Context context) {
- mAlarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
- IntentFilter filter = new IntentFilter();
- filter.addAction(Intent.ACTION_USER_SWITCHED);
- filter.addAction(AlarmManager.ACTION_NEXT_ALARM_CLOCK_CHANGED);
- context.registerReceiverAsUser(this, UserHandle.ALL, filter, null, null);
- updateNextAlarm();
- }
-
- public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
- pw.println("NextAlarmController state:");
- pw.print(" mNextAlarm="); pw.println(mNextAlarm);
- }
-
- public void addCallback(NextAlarmChangeCallback cb) {
- mChangeCallbacks.add(cb);
- cb.onNextAlarmChanged(mNextAlarm);
- }
-
- public void removeCallback(NextAlarmChangeCallback cb) {
- mChangeCallbacks.remove(cb);
- }
-
- public void onReceive(Context context, Intent intent) {
- final String action = intent.getAction();
- if (action.equals(Intent.ACTION_USER_SWITCHED)
- || action.equals(AlarmManager.ACTION_NEXT_ALARM_CLOCK_CHANGED)) {
- updateNextAlarm();
- }
- }
-
- private void updateNextAlarm() {
- mNextAlarm = mAlarmManager.getNextAlarmClock(UserHandle.USER_CURRENT);
- fireNextAlarmChanged();
- }
-
- private void fireNextAlarmChanged() {
- int n = mChangeCallbacks.size();
- for (int i = 0; i < n; i++) {
- mChangeCallbacks.get(i).onNextAlarmChanged(mNextAlarm);
- }
- }
+public interface NextAlarmController extends CallbackController<NextAlarmChangeCallback> {
public interface NextAlarmChangeCallback {
void onNextAlarmChanged(AlarmManager.AlarmClockInfo nextAlarm);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NextAlarmControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NextAlarmControllerImpl.java
new file mode 100644
index 0000000..dfdeae1
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NextAlarmControllerImpl.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.statusbar.policy;
+
+import android.app.AlarmManager;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.UserHandle;
+
+import com.android.systemui.statusbar.policy.NextAlarmController.NextAlarmChangeCallback;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+
+public class NextAlarmControllerImpl extends BroadcastReceiver
+ implements NextAlarmController {
+
+ private final ArrayList<NextAlarmChangeCallback> mChangeCallbacks = new ArrayList<>();
+
+ private AlarmManager mAlarmManager;
+ private AlarmManager.AlarmClockInfo mNextAlarm;
+
+ public NextAlarmControllerImpl(Context context) {
+ mAlarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(Intent.ACTION_USER_SWITCHED);
+ filter.addAction(AlarmManager.ACTION_NEXT_ALARM_CLOCK_CHANGED);
+ context.registerReceiverAsUser(this, UserHandle.ALL, filter, null, null);
+ updateNextAlarm();
+ }
+
+ public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ pw.println("NextAlarmController state:");
+ pw.print(" mNextAlarm="); pw.println(mNextAlarm);
+ }
+
+ public void addCallback(NextAlarmChangeCallback cb) {
+ mChangeCallbacks.add(cb);
+ cb.onNextAlarmChanged(mNextAlarm);
+ }
+
+ public void removeCallback(NextAlarmChangeCallback cb) {
+ mChangeCallbacks.remove(cb);
+ }
+
+ public void onReceive(Context context, Intent intent) {
+ final String action = intent.getAction();
+ if (action.equals(Intent.ACTION_USER_SWITCHED)
+ || action.equals(AlarmManager.ACTION_NEXT_ALARM_CLOCK_CHANGED)) {
+ updateNextAlarm();
+ }
+ }
+
+ private void updateNextAlarm() {
+ mNextAlarm = mAlarmManager.getNextAlarmClock(UserHandle.USER_CURRENT);
+ fireNextAlarmChanged();
+ }
+
+ private void fireNextAlarmChanged() {
+ int n = mChangeCallbacks.size();
+ for (int i = 0; i < n; i++) {
+ mChangeCallbacks.get(i).onNextAlarmChanged(mNextAlarm);
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserInfoController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserInfoController.java
index c09747b..1e23a20 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserInfoController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserInfoController.java
@@ -1,233 +1,28 @@
/*
- * Copyright (C) 2014 The Android Open Source Project
+ * Copyright (C) 2016 The Android Open Source Project
*
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * 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
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
*/
package com.android.systemui.statusbar.policy;
-import android.app.ActivityManager;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.pm.PackageManager;
-import android.content.pm.UserInfo;
-import android.content.res.Resources;
-import android.database.Cursor;
-import android.graphics.Bitmap;
import android.graphics.drawable.Drawable;
-import android.os.AsyncTask;
-import android.os.RemoteException;
-import android.os.UserHandle;
-import android.os.UserManager;
-import android.provider.ContactsContract;
-import android.util.Log;
-import com.android.internal.util.UserIcons;
-import com.android.settingslib.drawable.UserIconDrawable;
-import com.android.systemui.R;
import com.android.systemui.statusbar.policy.UserInfoController.OnUserInfoChangedListener;
-import java.util.ArrayList;
+public interface UserInfoController extends CallbackController<OnUserInfoChangedListener> {
-public class UserInfoController implements CallbackController<OnUserInfoChangedListener> {
-
- private static final String TAG = "UserInfoController";
-
- private final Context mContext;
- private final ArrayList<OnUserInfoChangedListener> mCallbacks =
- new ArrayList<OnUserInfoChangedListener>();
- private AsyncTask<Void, Void, UserInfoQueryResult> mUserInfoTask;
-
- private String mUserName;
- private Drawable mUserDrawable;
- private String mUserAccount;
-
- public UserInfoController(Context context) {
- mContext = context;
- IntentFilter filter = new IntentFilter();
- filter.addAction(Intent.ACTION_USER_SWITCHED);
- mContext.registerReceiver(mReceiver, filter);
-
- IntentFilter profileFilter = new IntentFilter();
- profileFilter.addAction(ContactsContract.Intents.ACTION_PROFILE_CHANGED);
- profileFilter.addAction(Intent.ACTION_USER_INFO_CHANGED);
- mContext.registerReceiverAsUser(mProfileReceiver, UserHandle.ALL, profileFilter,
- null, null);
- }
-
- public void addCallback(OnUserInfoChangedListener callback) {
- mCallbacks.add(callback);
- callback.onUserInfoChanged(mUserName, mUserDrawable, mUserAccount);
- }
-
- public void removeCallback(OnUserInfoChangedListener callback) {
- mCallbacks.remove(callback);
- }
-
- private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- final String action = intent.getAction();
- if (Intent.ACTION_USER_SWITCHED.equals(action)) {
- reloadUserInfo();
- }
- }
- };
-
- private final BroadcastReceiver mProfileReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- final String action = intent.getAction();
- if (ContactsContract.Intents.ACTION_PROFILE_CHANGED.equals(action) ||
- Intent.ACTION_USER_INFO_CHANGED.equals(action)) {
- try {
- final int currentUser = ActivityManager.getService().getCurrentUser().id;
- final int changedUser =
- intent.getIntExtra(Intent.EXTRA_USER_HANDLE, getSendingUserId());
- if (changedUser == currentUser) {
- reloadUserInfo();
- }
- } catch (RemoteException e) {
- Log.e(TAG, "Couldn't get current user id for profile change", e);
- }
- }
- }
- };
-
- public void reloadUserInfo() {
- if (mUserInfoTask != null) {
- mUserInfoTask.cancel(false);
- mUserInfoTask = null;
- }
- queryForUserInformation();
- }
-
- private void queryForUserInformation() {
- Context currentUserContext;
- UserInfo userInfo;
- try {
- userInfo = ActivityManager.getService().getCurrentUser();
- currentUserContext = mContext.createPackageContextAsUser("android", 0,
- new UserHandle(userInfo.id));
- } catch (PackageManager.NameNotFoundException e) {
- Log.e(TAG, "Couldn't create user context", e);
- throw new RuntimeException(e);
- } catch (RemoteException e) {
- Log.e(TAG, "Couldn't get user info", e);
- throw new RuntimeException(e);
- }
- final int userId = userInfo.id;
- final boolean isGuest = userInfo.isGuest();
- final String userName = userInfo.name;
-
- final Resources res = mContext.getResources();
- final int avatarSize = Math.max(
- res.getDimensionPixelSize(R.dimen.multi_user_avatar_expanded_size),
- res.getDimensionPixelSize(R.dimen.multi_user_avatar_keyguard_size));
-
- final Context context = currentUserContext;
- mUserInfoTask = new AsyncTask<Void, Void, UserInfoQueryResult>() {
-
- @Override
- protected UserInfoQueryResult doInBackground(Void... params) {
- final UserManager um = UserManager.get(mContext);
-
- // Fall back to the UserManager nickname if we can't read the name from the local
- // profile below.
- String name = userName;
- Drawable avatar = null;
- Bitmap rawAvatar = um.getUserIcon(userId);
- if (rawAvatar != null) {
- avatar = new UserIconDrawable(avatarSize)
- .setIcon(rawAvatar).setBadgeIfManagedUser(mContext, userId).bake();
- } else {
- avatar = UserIcons.getDefaultUserIcon(isGuest? UserHandle.USER_NULL : userId,
- /* light= */ true);
- }
-
- // If it's a single-user device, get the profile name, since the nickname is not
- // usually valid
- if (um.getUsers().size() <= 1) {
- // Try and read the display name from the local profile
- final Cursor cursor = context.getContentResolver().query(
- ContactsContract.Profile.CONTENT_URI, new String[] {
- ContactsContract.CommonDataKinds.Phone._ID,
- ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME
- }, null, null, null);
- if (cursor != null) {
- try {
- if (cursor.moveToFirst()) {
- name = cursor.getString(cursor.getColumnIndex(
- ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME));
- }
- } finally {
- cursor.close();
- }
- }
- }
- String userAccount = um.getUserAccount(userId);
- return new UserInfoQueryResult(name, avatar, userAccount);
- }
-
- @Override
- protected void onPostExecute(UserInfoQueryResult result) {
- mUserName = result.getName();
- mUserDrawable = result.getAvatar();
- mUserAccount = result.getUserAccount();
- mUserInfoTask = null;
- notifyChanged();
- }
- };
- mUserInfoTask.execute();
- }
-
- private void notifyChanged() {
- for (OnUserInfoChangedListener listener : mCallbacks) {
- listener.onUserInfoChanged(mUserName, mUserDrawable, mUserAccount);
- }
- }
-
- public void onDensityOrFontScaleChanged() {
- reloadUserInfo();
- }
+ void reloadUserInfo();
public interface OnUserInfoChangedListener {
public void onUserInfoChanged(String name, Drawable picture, String userAccount);
}
-
- private static class UserInfoQueryResult {
- private String mName;
- private Drawable mAvatar;
- private String mUserAccount;
-
- public UserInfoQueryResult(String name, Drawable avatar, String userAccount) {
- mName = name;
- mAvatar = avatar;
- mUserAccount = userAccount;
- }
-
- public String getName() {
- return mName;
- }
-
- public Drawable getAvatar() {
- return mAvatar;
- }
-
- public String getUserAccount() {
- return mUserAccount;
- }
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserInfoControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserInfoControllerImpl.java
new file mode 100644
index 0000000..b1e4b03
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserInfoControllerImpl.java
@@ -0,0 +1,229 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.statusbar.policy;
+
+import android.app.ActivityManager;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+import android.content.pm.UserInfo;
+import android.content.res.Resources;
+import android.database.Cursor;
+import android.graphics.Bitmap;
+import android.graphics.drawable.Drawable;
+import android.os.AsyncTask;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.provider.ContactsContract;
+import android.util.Log;
+
+import com.android.internal.util.UserIcons;
+import com.android.settingslib.drawable.UserIconDrawable;
+import com.android.systemui.R;
+import com.android.systemui.statusbar.policy.UserInfoController.OnUserInfoChangedListener;
+
+import java.util.ArrayList;
+
+public class UserInfoControllerImpl implements UserInfoController {
+
+ private static final String TAG = "UserInfoController";
+
+ private final Context mContext;
+ private final ArrayList<OnUserInfoChangedListener> mCallbacks =
+ new ArrayList<OnUserInfoChangedListener>();
+ private AsyncTask<Void, Void, UserInfoQueryResult> mUserInfoTask;
+
+ private String mUserName;
+ private Drawable mUserDrawable;
+ private String mUserAccount;
+
+ public UserInfoControllerImpl(Context context) {
+ mContext = context;
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(Intent.ACTION_USER_SWITCHED);
+ mContext.registerReceiver(mReceiver, filter);
+
+ IntentFilter profileFilter = new IntentFilter();
+ profileFilter.addAction(ContactsContract.Intents.ACTION_PROFILE_CHANGED);
+ profileFilter.addAction(Intent.ACTION_USER_INFO_CHANGED);
+ mContext.registerReceiverAsUser(mProfileReceiver, UserHandle.ALL, profileFilter,
+ null, null);
+ }
+
+ public void addCallback(OnUserInfoChangedListener callback) {
+ mCallbacks.add(callback);
+ callback.onUserInfoChanged(mUserName, mUserDrawable, mUserAccount);
+ }
+
+ public void removeCallback(OnUserInfoChangedListener callback) {
+ mCallbacks.remove(callback);
+ }
+
+ private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ final String action = intent.getAction();
+ if (Intent.ACTION_USER_SWITCHED.equals(action)) {
+ reloadUserInfo();
+ }
+ }
+ };
+
+ private final BroadcastReceiver mProfileReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ final String action = intent.getAction();
+ if (ContactsContract.Intents.ACTION_PROFILE_CHANGED.equals(action) ||
+ Intent.ACTION_USER_INFO_CHANGED.equals(action)) {
+ try {
+ final int currentUser = ActivityManager.getService().getCurrentUser().id;
+ final int changedUser =
+ intent.getIntExtra(Intent.EXTRA_USER_HANDLE, getSendingUserId());
+ if (changedUser == currentUser) {
+ reloadUserInfo();
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "Couldn't get current user id for profile change", e);
+ }
+ }
+ }
+ };
+
+ public void reloadUserInfo() {
+ if (mUserInfoTask != null) {
+ mUserInfoTask.cancel(false);
+ mUserInfoTask = null;
+ }
+ queryForUserInformation();
+ }
+
+ private void queryForUserInformation() {
+ Context currentUserContext;
+ UserInfo userInfo;
+ try {
+ userInfo = ActivityManager.getService().getCurrentUser();
+ currentUserContext = mContext.createPackageContextAsUser("android", 0,
+ new UserHandle(userInfo.id));
+ } catch (PackageManager.NameNotFoundException e) {
+ Log.e(TAG, "Couldn't create user context", e);
+ throw new RuntimeException(e);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Couldn't get user info", e);
+ throw new RuntimeException(e);
+ }
+ final int userId = userInfo.id;
+ final boolean isGuest = userInfo.isGuest();
+ final String userName = userInfo.name;
+
+ final Resources res = mContext.getResources();
+ final int avatarSize = Math.max(
+ res.getDimensionPixelSize(R.dimen.multi_user_avatar_expanded_size),
+ res.getDimensionPixelSize(R.dimen.multi_user_avatar_keyguard_size));
+
+ final Context context = currentUserContext;
+ mUserInfoTask = new AsyncTask<Void, Void, UserInfoQueryResult>() {
+
+ @Override
+ protected UserInfoQueryResult doInBackground(Void... params) {
+ final UserManager um = UserManager.get(mContext);
+
+ // Fall back to the UserManager nickname if we can't read the name from the local
+ // profile below.
+ String name = userName;
+ Drawable avatar = null;
+ Bitmap rawAvatar = um.getUserIcon(userId);
+ if (rawAvatar != null) {
+ avatar = new UserIconDrawable(avatarSize)
+ .setIcon(rawAvatar).setBadgeIfManagedUser(mContext, userId).bake();
+ } else {
+ avatar = UserIcons.getDefaultUserIcon(isGuest? UserHandle.USER_NULL : userId,
+ /* light= */ true);
+ }
+
+ // If it's a single-user device, get the profile name, since the nickname is not
+ // usually valid
+ if (um.getUsers().size() <= 1) {
+ // Try and read the display name from the local profile
+ final Cursor cursor = context.getContentResolver().query(
+ ContactsContract.Profile.CONTENT_URI, new String[] {
+ ContactsContract.CommonDataKinds.Phone._ID,
+ ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME
+ }, null, null, null);
+ if (cursor != null) {
+ try {
+ if (cursor.moveToFirst()) {
+ name = cursor.getString(cursor.getColumnIndex(
+ ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME));
+ }
+ } finally {
+ cursor.close();
+ }
+ }
+ }
+ String userAccount = um.getUserAccount(userId);
+ return new UserInfoQueryResult(name, avatar, userAccount);
+ }
+
+ @Override
+ protected void onPostExecute(UserInfoQueryResult result) {
+ mUserName = result.getName();
+ mUserDrawable = result.getAvatar();
+ mUserAccount = result.getUserAccount();
+ mUserInfoTask = null;
+ notifyChanged();
+ }
+ };
+ mUserInfoTask.execute();
+ }
+
+ private void notifyChanged() {
+ for (OnUserInfoChangedListener listener : mCallbacks) {
+ listener.onUserInfoChanged(mUserName, mUserDrawable, mUserAccount);
+ }
+ }
+
+ public void onDensityOrFontScaleChanged() {
+ reloadUserInfo();
+ }
+
+ private static class UserInfoQueryResult {
+ private String mName;
+ private Drawable mAvatar;
+ private String mUserAccount;
+
+ public UserInfoQueryResult(String name, Drawable avatar, String userAccount) {
+ mName = name;
+ mAvatar = avatar;
+ mUserAccount = userAccount;
+ }
+
+ public String getName() {
+ return mName;
+ }
+
+ public Drawable getAvatar() {
+ return mAvatar;
+ }
+
+ public String getUserAccount() {
+ return mUserAccount;
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/AmbientState.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/AmbientState.java
index 26f74ea..94fc17a8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/AmbientState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/AmbientState.java
@@ -22,6 +22,7 @@
import com.android.systemui.R;
import com.android.systemui.statusbar.ActivatableNotificationView;
import com.android.systemui.statusbar.NotificationShelf;
+import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.policy.HeadsUpManager;
import java.util.ArrayList;
@@ -52,6 +53,8 @@
private int mBaseZHeight;
private int mMaxLayoutHeight;
private ActivatableNotificationView mLastVisibleBackgroundChild;
+ private float mCurrentScrollVelocity;
+ private int mStatusBarState;
public AmbientState(Context context) {
reload(context);
@@ -241,4 +244,20 @@
public ActivatableNotificationView getLastVisibleBackgroundChild() {
return mLastVisibleBackgroundChild;
}
+
+ public void setCurrentScrollVelocity(float currentScrollVelocity) {
+ mCurrentScrollVelocity = currentScrollVelocity;
+ }
+
+ public float getCurrentScrollVelocity() {
+ return mCurrentScrollVelocity;
+ }
+
+ public boolean isOnKeyguard() {
+ return mStatusBarState == StatusBarState.KEYGUARD;
+ }
+
+ public void setStatusBarState(int statusBarState) {
+ mStatusBarState = statusBarState;
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/AnimationFilter.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/AnimationFilter.java
index d3d58f9..38bb40e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/AnimationFilter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/AnimationFilter.java
@@ -120,7 +120,7 @@
}
}
- private void combineFilter(AnimationFilter filter) {
+ public void combineFilter(AnimationFilter filter) {
animateAlpha |= filter.animateAlpha;
animateX |= filter.animateX;
animateY |= filter.animateY;
@@ -134,7 +134,7 @@
hasDelays |= filter.hasDelays;
}
- private void reset() {
+ public void reset() {
animateAlpha = false;
animateX = false;
animateY = false;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationChildrenContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationChildrenContainer.java
index b8f8cb2..22709f6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationChildrenContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationChildrenContainer.java
@@ -213,7 +213,7 @@
mDividers.add(newIndex, divider);
updateGroupOverflow();
- row.setIconTransformationAmount(0, false /* isLastChild */);
+ row.setContentTransformationAmount(0, false /* isLastChild */);
}
public void removeNotification(ExpandableNotificationRow row) {
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 10d995c..543550d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
@@ -357,6 +357,7 @@
private Rect mRequestedClipBounds;
private boolean mInHeadsUpPinnedMode;
private boolean mHeadsUpAnimatingAway;
+ private int mStatusBarState;
public NotificationStackScrollLayout(Context context) {
this(context, null);
@@ -575,6 +576,9 @@
*/
private void updateChildren() {
updateScrollStateForAddedChildren();
+ mAmbientState.setCurrentScrollVelocity(mScroller.isFinished()
+ ? 0
+ : mScroller.getCurrVelocity());
mAmbientState.setScrollY(mOwnScrollY);
mStackScrollAlgorithm.getStackScrollState(mAmbientState, mCurrentStackScrollState);
if (!isCurrentlyAnimating() && !mNeedsAnimation) {
@@ -715,7 +719,6 @@
requestChildrenUpdate();
}
setStackTranslation(translationY);
- requestChildrenUpdate();
}
private void setRequestedClipBounds(Rect clipRect) {
@@ -1185,7 +1188,7 @@
}
private boolean onKeyguard() {
- return mPhoneStatusBar.getBarState() == StatusBarState.KEYGUARD;
+ return mStatusBarState == StatusBarState.KEYGUARD;
}
private void setSwipingInProgress(boolean isSwiped) {
@@ -2122,7 +2125,7 @@
top = mTopPadding;
bottom = top;
}
- if (mPhoneStatusBar.getBarState() != StatusBarState.KEYGUARD) {
+ if (mStatusBarState != StatusBarState.KEYGUARD) {
top = (int) Math.max(mTopPadding + mStackTranslation, top);
} else {
// otherwise the animation from the shade to the keyguard will jump as it's maxed
@@ -2356,7 +2359,7 @@
}
break;
case MotionEvent.ACTION_UP:
- if (mPhoneStatusBar.getBarState() != StatusBarState.KEYGUARD && mTouchIsClick &&
+ if (mStatusBarState != StatusBarState.KEYGUARD && mTouchIsClick &&
isBelowLastNotification(mInitialTouchX, mInitialTouchY)) {
mOnEmptySpaceClickListener.onEmptySpaceClicked(mInitialTouchX, mInitialTouchY);
}
@@ -3979,6 +3982,11 @@
updateClipping();
}
+ public void setStatusBarState(int statusBarState) {
+ mStatusBarState = statusBarState;
+ mAmbientState.setStatusBarState(statusBarState);
+ }
+
/**
* A listener that is notified when some child locations might have changed.
*/
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java
index 7afc7ba..50b6d70 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java
@@ -124,7 +124,8 @@
private void updateClipping(StackScrollState resultState,
StackScrollAlgorithmState algorithmState, AmbientState ambientState) {
- float drawStart = ambientState.getTopPadding() + ambientState.getStackTranslation();
+ float drawStart = !ambientState.isOnKeyguard() ? ambientState.getTopPadding()
+ + ambientState.getStackTranslation() : 0;
float previousNotificationEnd = 0;
float previousNotificationStart = 0;
int childCount = algorithmState.visibleChildren.size();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/ViewState.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/ViewState.java
index 8a5ddd4..a8e5ac6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/ViewState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/ViewState.java
@@ -99,36 +99,6 @@
// don't do anything with it
return;
}
- boolean becomesInvisible = this.alpha == 0.0f || this.hidden;
- boolean animatingAlpha = isAnimating(view, TAG_ANIMATOR_ALPHA);
- if (animatingAlpha) {
- updateAlphaAnimation(view);
- } else if (view.getAlpha() != this.alpha) {
- // apply layer type
- boolean becomesFullyVisible = this.alpha == 1.0f;
- boolean newLayerTypeIsHardware = !becomesInvisible && !becomesFullyVisible
- && view.hasOverlappingRendering();
- int layerType = view.getLayerType();
- int newLayerType = newLayerTypeIsHardware
- ? View.LAYER_TYPE_HARDWARE
- : View.LAYER_TYPE_NONE;
- if (layerType != newLayerType) {
- view.setLayerType(newLayerType, null);
- }
-
- // apply alpha
- view.setAlpha(this.alpha);
- }
-
- // apply visibility
- int oldVisibility = view.getVisibility();
- int newVisibility = becomesInvisible ? View.INVISIBLE : View.VISIBLE;
- if (newVisibility != oldVisibility) {
- if (!(view instanceof ExpandableView) || !((ExpandableView) view).willBeGone()) {
- // We don't want views to change visibility when they are animating to GONE
- view.setVisibility(newVisibility);
- }
- }
// apply xTranslation
boolean animatingX = isAnimating(view, TAG_ANIMATOR_TRANSLATION_X);
@@ -163,6 +133,53 @@
if (view.getScaleY() != this.scaleY) {
view.setScaleY(this.scaleY);
}
+
+ boolean becomesInvisible = this.alpha == 0.0f || (this.hidden && !isAnimating(view));
+ boolean animatingAlpha = isAnimating(view, TAG_ANIMATOR_ALPHA);
+ if (animatingAlpha) {
+ updateAlphaAnimation(view);
+ } else if (view.getAlpha() != this.alpha) {
+ // apply layer type
+ boolean becomesFullyVisible = this.alpha == 1.0f;
+ boolean newLayerTypeIsHardware = !becomesInvisible && !becomesFullyVisible
+ && view.hasOverlappingRendering();
+ int layerType = view.getLayerType();
+ int newLayerType = newLayerTypeIsHardware
+ ? View.LAYER_TYPE_HARDWARE
+ : View.LAYER_TYPE_NONE;
+ if (layerType != newLayerType) {
+ view.setLayerType(newLayerType, null);
+ }
+
+ // apply alpha
+ view.setAlpha(this.alpha);
+ }
+
+ // apply visibility
+ int oldVisibility = view.getVisibility();
+ int newVisibility = becomesInvisible ? View.INVISIBLE : View.VISIBLE;
+ if (newVisibility != oldVisibility) {
+ if (!(view instanceof ExpandableView) || !((ExpandableView) view).willBeGone()) {
+ // We don't want views to change visibility when they are animating to GONE
+ view.setVisibility(newVisibility);
+ }
+ }
+ }
+
+ protected boolean isAnimating(View view) {
+ if (isAnimating(view, TAG_ANIMATOR_TRANSLATION_X)) {
+ return true;
+ }
+ if (isAnimating(view, TAG_ANIMATOR_TRANSLATION_Y)) {
+ return true;
+ }
+ if (isAnimating(view, TAG_ANIMATOR_TRANSLATION_Z)) {
+ return true;
+ }
+ if (isAnimating(view, TAG_ANIMATOR_ALPHA)) {
+ return true;
+ }
+ return false;
}
private boolean isAnimating(View view, int tag) {
@@ -482,7 +499,7 @@
child.setTag(TAG_ANIMATOR_TRANSLATION_Y, null);
child.setTag(TAG_START_TRANSLATION_Y, null);
child.setTag(TAG_END_TRANSLATION_Y, null);
- onYTranslationAnimationFinished();
+ onYTranslationAnimationFinished(child);
}
});
startAnimator(animator, listener);
@@ -491,7 +508,10 @@
child.setTag(TAG_END_TRANSLATION_Y, newEndValue);
}
- protected void onYTranslationAnimationFinished() {
+ protected void onYTranslationAnimationFinished(View view) {
+ if (hidden) {
+ view.setVisibility(View.INVISIBLE);
+ }
}
protected void startAnimator(Animator animator, AnimatorListenerAdapter listener) {
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/TunerService.java b/packages/SystemUI/src/com/android/systemui/tuner/TunerService.java
index ebc962d..565ac08 100644
--- a/packages/SystemUI/src/com/android/systemui/tuner/TunerService.java
+++ b/packages/SystemUI/src/com/android/systemui/tuner/TunerService.java
@@ -92,6 +92,10 @@
mUserTracker.startTracking();
}
+ public void destroy() {
+ mUserTracker.stopTracking();
+ }
+
private void upgradeTuner(int oldVersion, int newVersion) {
if (oldVersion < 1) {
String blacklistStr = getValue(StatusBarIconController.ICON_BLACKLIST);
diff --git a/packages/SystemUI/src/com/android/systemui/util/LayoutInflaterBuilder.java b/packages/SystemUI/src/com/android/systemui/util/LayoutInflaterBuilder.java
new file mode 100644
index 0000000..f420921
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/LayoutInflaterBuilder.java
@@ -0,0 +1,162 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.util;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Builder class to create a {@link LayoutInflater} with various properties.
+ *
+ * Call any desired configuration methods on the Builder and then use
+ * {@link Builder#build} to create the LayoutInflater. This is an alternative to directly using
+ * {@link LayoutInflater#setFilter} and {@link LayoutInflater#setFactory}.
+ * @hide for use by framework
+ */
+public class LayoutInflaterBuilder {
+ private static final String TAG = "LayoutInflaterBuilder";
+
+ private Context mFromContext;
+ private Context mTargetContext;
+ private Map<String, String> mReplaceMap;
+ private Set<Class> mDisallowedClasses;
+ private LayoutInflater mBuiltInflater;
+
+ /**
+ * Creates a new Builder which will construct a LayoutInflater.
+ *
+ * @param fromContext This context's LayoutInflater will be cloned by the Builder using
+ * {@link LayoutInflater#cloneInContext}. By default, the new LayoutInflater will point at
+ * this same Context.
+ */
+ public LayoutInflaterBuilder(@NonNull Context fromContext) {
+ mFromContext = fromContext;
+ mTargetContext = fromContext;
+ mReplaceMap = null;
+ mDisallowedClasses = null;
+ mBuiltInflater = null;
+ }
+
+ /**
+ * Instructs the Builder to point the LayoutInflater at a different Context.
+ *
+ * @param targetContext Context to be provided to
+ * {@link LayoutInflater#cloneInContext(Context)}.
+ * @return Builder object post-modification.
+ */
+ public LayoutInflaterBuilder target(@NonNull Context targetContext) {
+ assertIfAlreadyBuilt();
+ mTargetContext = targetContext;
+ return this;
+ }
+
+ /**
+ * Instructs the Builder to configure the LayoutInflater such that all instances
+ * of one {@link View} will be replaced with instances of another during inflation.
+ *
+ * @param from Instances of this class will be replaced during inflation.
+ * @param to Instances of this class will be inflated as replacements.
+ * @return Builder object post-modification.
+ */
+ public LayoutInflaterBuilder replace(@NonNull Class from, @NonNull Class to) {
+ assertIfAlreadyBuilt();
+ if (mReplaceMap == null) {
+ mReplaceMap = new ArrayMap<String, String>();
+ }
+ mReplaceMap.put(from.getName(), to.getName());
+ return this;
+ }
+
+ /**
+ * Instructs the Builder to configure the LayoutInflater such that any attempt to inflate
+ * a {@link View} of a given type will throw a {@link InflateException}.
+ *
+ * @param disallowedClass The Class type that will be disallowed.
+ * @return Builder object post-modification.
+ */
+ public LayoutInflaterBuilder disallow(@NonNull Class disallowedClass) {
+ assertIfAlreadyBuilt();
+ if (mDisallowedClasses == null) {
+ mDisallowedClasses = new ArraySet<Class>();
+ }
+ mDisallowedClasses.add(disallowedClass);
+ return this;
+ }
+
+ /**
+ * Builds and returns the LayoutInflater. Afterwards, this Builder can no longer can be
+ * used, all future calls on the Builder will throw {@link AssertionError}.
+ */
+ public LayoutInflater build() {
+ assertIfAlreadyBuilt();
+ mBuiltInflater =
+ LayoutInflater.from(mFromContext).cloneInContext(mTargetContext);
+ setFactoryIfNeeded(mBuiltInflater);
+ setFilterIfNeeded(mBuiltInflater);
+ return mBuiltInflater;
+ }
+
+ private void assertIfAlreadyBuilt() {
+ if (mBuiltInflater != null) {
+ throw new AssertionError("Cannot use this Builder after build() has been called.");
+ }
+ }
+
+ private void setFactoryIfNeeded(LayoutInflater inflater) {
+ if (mReplaceMap == null) {
+ return;
+ }
+ inflater.setFactory(
+ new LayoutInflater.Factory() {
+ @Override
+ public View onCreateView(String name, Context context, AttributeSet attrs) {
+ String replacingClassName = mReplaceMap.get(name);
+ if (replacingClassName != null) {
+ try {
+ return inflater.createView(replacingClassName, null, attrs);
+ } catch (ClassNotFoundException e) {
+ Log.e(TAG, "Could not replace " + name
+ + " with " + replacingClassName
+ + ", Exception: ", e);
+ }
+ }
+ return null;
+ }
+ });
+ }
+
+ private void setFilterIfNeeded(LayoutInflater inflater) {
+ if (mDisallowedClasses == null) {
+ return;
+ }
+ inflater.setFilter(
+ new LayoutInflater.Filter() {
+ @Override
+ public boolean onLoadClass(Class clazz) {
+ return !mDisallowedClasses.contains(clazz);
+ }
+ });
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/FragmentTestCase.java b/packages/SystemUI/tests/src/com/android/systemui/FragmentTestCase.java
index f87336c..447edac 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/FragmentTestCase.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/FragmentTestCase.java
@@ -26,6 +26,8 @@
import android.view.View;
import android.widget.FrameLayout;
+import com.android.systemui.utils.leaks.LeakCheckedTest;
+
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
@@ -139,7 +141,7 @@
private class HostCallbacks extends FragmentHostCallback<FragmentTestCase> {
public HostCallbacks() {
- super(getTrackedContext(), FragmentTestCase.this.mHandler, 0);
+ super(mContext, FragmentTestCase.this.mHandler, 0);
}
@Override
diff --git a/packages/SystemUI/tests/src/com/android/systemui/LeakCheckedTest.java b/packages/SystemUI/tests/src/com/android/systemui/LeakCheckedTest.java
deleted file mode 100644
index d64669d..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/LeakCheckedTest.java
+++ /dev/null
@@ -1,287 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
- * except in compliance with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software distributed under the
- * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the specific language governing
- * permissions and limitations under the License.
- */
-
-package com.android.systemui;
-
-import static org.mockito.Matchers.any;
-import static org.mockito.Mockito.doAnswer;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
-import android.content.BroadcastReceiver;
-import android.content.ComponentCallbacks;
-import android.content.Context;
-import android.content.ContextWrapper;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.ServiceConnection;
-import android.os.Handler;
-import android.os.UserHandle;
-import android.util.ArrayMap;
-import android.util.Log;
-
-import com.android.systemui.statusbar.phone.ManagedProfileController;
-import com.android.systemui.statusbar.policy.BatteryController;
-import com.android.systemui.statusbar.policy.BluetoothController;
-import com.android.systemui.statusbar.policy.CastController;
-import com.android.systemui.statusbar.policy.DataSaverController;
-import com.android.systemui.statusbar.policy.FlashlightController;
-import com.android.systemui.statusbar.policy.HotspotController;
-import com.android.systemui.statusbar.policy.KeyguardMonitor;
-import com.android.systemui.statusbar.policy.LocationController;
-import com.android.systemui.statusbar.policy.NetworkController;
-import com.android.systemui.statusbar.policy.NextAlarmController;
-import com.android.systemui.statusbar.policy.RotationLockController;
-import com.android.systemui.statusbar.policy.SecurityController;
-import com.android.systemui.statusbar.policy.CallbackController;
-import com.android.systemui.statusbar.policy.UserInfoController;
-import com.android.systemui.statusbar.policy.ZenModeController;
-
-import org.junit.After;
-import org.junit.Assert;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.rules.TestWatcher;
-import org.junit.runner.Description;
-import org.mockito.invocation.InvocationOnMock;
-import org.mockito.stubbing.Answer;
-
-import java.io.PrintWriter;
-import java.io.StringWriter;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-/**
- * Base class for tests to check if receivers are left registered, services bound, or other
- * listeners listening.
- */
-public class LeakCheckedTest extends SysuiTestCase {
- private static final String TAG = "LeakCheckedTest";
-
- private final Map<String, Tracker> mTrackers = new HashMap<>();
- private final Map<Class, Object> mLeakCheckers = new ArrayMap<>();
- private TrackingContext mTrackedContext;
-
- @Rule
- public TestWatcher successWatcher = new TestWatcher() {
- @Override
- protected void succeeded(Description description) {
- verify();
- }
- };
-
- @Before
- public void setup() {
- mTrackedContext = new TrackingContext(mContext);
- addSupportedLeakCheckers();
- }
-
- public <T> T getLeakChecker(Class<T> cls) {
- T obj = (T) mLeakCheckers.get(cls);
- if (obj == null) {
- Assert.fail(cls.getName() + " is not supported by LeakCheckedTest yet");
- }
- return obj;
- }
-
- public Context getTrackedContext() {
- return mTrackedContext;
- }
-
- private Tracker getTracker(String tag) {
- Tracker t = mTrackers.get(tag);
- if (t == null) {
- t = new Tracker();
- mTrackers.put(tag, t);
- }
- return t;
- }
-
- public void verify() {
- mTrackers.values().forEach(Tracker::verify);
- }
-
- public static class Tracker {
- private Map<Object, LeakInfo> mObjects = new ArrayMap<>();
-
- LeakInfo getLeakInfo(Object object) {
- LeakInfo leakInfo = mObjects.get(object);
- if (leakInfo == null) {
- leakInfo = new LeakInfo();
- mObjects.put(object, leakInfo);
- }
- return leakInfo;
- }
-
- private void verify() {
- mObjects.values().forEach(LeakInfo::verify);
- }
- }
-
- public static class LeakInfo {
- private List<Throwable> mThrowables = new ArrayList<>();
-
- private LeakInfo() {
- }
-
- private void addAllocation(Throwable t) {
- // TODO: Drop off the first element in the stack trace here to have a cleaner stack.
- mThrowables.add(t);
- }
-
- private void clearAllocations() {
- mThrowables.clear();
- }
-
- public void verify() {
- if (mThrowables.size() == 0) return;
- Log.e(TAG, "Listener or binding not properly released");
- for (Throwable t : mThrowables) {
- Log.e(TAG, "Allocation found", t);
- }
- StringWriter writer = new StringWriter();
- mThrowables.get(0).printStackTrace(new PrintWriter(writer));
- Assert.fail("Listener or binding not properly released\n"
- + writer.toString());
- }
- }
-
- private void addSupportedLeakCheckers() {
- addListening("bluetooth", BluetoothController.class);
- addListening("location", LocationController.class);
- addListening("rotation", RotationLockController.class);
- addListening("zen", ZenModeController.class);
- addListening("cast", CastController.class);
- addListening("hotspot", HotspotController.class);
- addListening("flashlight", FlashlightController.class);
- addListening("user", UserInfoController.class);
- addListening("keyguard", KeyguardMonitor.class);
- addListening("battery", BatteryController.class);
- addListening("security", SecurityController.class);
- addListening("profile", ManagedProfileController.class);
- addListening("alarm", NextAlarmController.class);
- NetworkController network = addListening("network", NetworkController.class);
- doAnswer(new Answer<Void>() {
- @Override
- public Void answer(InvocationOnMock invocation) throws Throwable {
- getTracker("emergency").getLeakInfo(invocation.getArguments()[0])
- .addAllocation(new Throwable());
- return null;
- }
- }).when(network).addEmergencyListener(any());
- doAnswer(new Answer<Void>() {
- @Override
- public Void answer(InvocationOnMock invocation) throws Throwable {
- getTracker("emergency").getLeakInfo(invocation.getArguments()[0]).clearAllocations();
- return null;
- }
- }).when(network).removeEmergencyListener(any());
- DataSaverController datasaver = addListening("datasaver", DataSaverController.class);
- when(network.getDataSaverController()).thenReturn(datasaver);
- }
-
- private <T extends CallbackController> T addListening(final String tag, Class<T> cls) {
- T mock = mock(cls);
- doAnswer(new Answer<Void>() {
- @Override
- public Void answer(InvocationOnMock invocation) throws Throwable {
- getTracker(tag).getLeakInfo(invocation.getArguments()[0])
- .addAllocation(new Throwable());
- return null;
- }
- }).when(mock).addCallback(any());
- doAnswer(new Answer<Void>() {
- @Override
- public Void answer(InvocationOnMock invocation) throws Throwable {
- getTracker(tag).getLeakInfo(invocation.getArguments()[0]).clearAllocations();
- return null;
- }
- }).when(mock).removeCallback(any());
- mLeakCheckers.put(cls, mock);
- return mock;
- }
-
- class TrackingContext extends ContextWrapper {
- public TrackingContext(Context base) {
- super(base);
- }
-
- @Override
- public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter) {
- getTracker("receiver").getLeakInfo(receiver).addAllocation(new Throwable());
- return super.registerReceiver(receiver, filter);
- }
-
- @Override
- public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter,
- String broadcastPermission, Handler scheduler) {
- getTracker("receiver").getLeakInfo(receiver).addAllocation(new Throwable());
- return super.registerReceiver(receiver, filter, broadcastPermission, scheduler);
- }
-
- @Override
- public Intent registerReceiverAsUser(BroadcastReceiver receiver, UserHandle user,
- IntentFilter filter, String broadcastPermission, Handler scheduler) {
- getTracker("receiver").getLeakInfo(receiver).addAllocation(new Throwable());
- return super.registerReceiverAsUser(receiver, user, filter, broadcastPermission,
- scheduler);
- }
-
- @Override
- public void unregisterReceiver(BroadcastReceiver receiver) {
- getTracker("receiver").getLeakInfo(receiver).clearAllocations();
- super.unregisterReceiver(receiver);
- }
-
- @Override
- public boolean bindService(Intent service, ServiceConnection conn, int flags) {
- getTracker("service").getLeakInfo(conn).addAllocation(new Throwable());
- return super.bindService(service, conn, flags);
- }
-
- @Override
- public boolean bindServiceAsUser(Intent service, ServiceConnection conn, int flags,
- Handler handler, UserHandle user) {
- getTracker("service").getLeakInfo(conn).addAllocation(new Throwable());
- return super.bindServiceAsUser(service, conn, flags, handler, user);
- }
-
- @Override
- public boolean bindServiceAsUser(Intent service, ServiceConnection conn, int flags,
- UserHandle user) {
- getTracker("service").getLeakInfo(conn).addAllocation(new Throwable());
- return super.bindServiceAsUser(service, conn, flags, user);
- }
-
- @Override
- public void unbindService(ServiceConnection conn) {
- getTracker("service").getLeakInfo(conn).clearAllocations();
- super.unbindService(conn);
- }
-
- @Override
- public void registerComponentCallbacks(ComponentCallbacks callback) {
- getTracker("component").getLeakInfo(callback).addAllocation(new Throwable());
- super.registerComponentCallbacks(callback);
- }
-
- @Override
- public void unregisterComponentCallbacks(ComponentCallbacks callback) {
- getTracker("component").getLeakInfo(callback).clearAllocations();
- super.unregisterComponentCallbacks(callback);
- }
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/SysuiTestCase.java b/packages/SystemUI/tests/src/com/android/systemui/SysuiTestCase.java
index 5dac8e5..008580a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/SysuiTestCase.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/SysuiTestCase.java
@@ -22,6 +22,7 @@
import android.os.MessageQueue;
import com.android.systemui.utils.TestableContext;
+import com.android.systemui.utils.leaks.Tracker;
import org.junit.After;
import org.junit.Before;
@@ -29,14 +30,14 @@
/**
* Base class that does System UI specific setup.
*/
-public class SysuiTestCase {
+public abstract class SysuiTestCase {
private Handler mHandler;
protected TestableContext mContext;
@Before
public void SysuiSetup() throws Exception {
- mContext = new TestableContext(InstrumentationRegistry.getTargetContext());
+ mContext = new TestableContext(InstrumentationRegistry.getTargetContext(), this);
}
@After
@@ -71,6 +72,11 @@
}
}
+ // Used for leak tracking, returns null to indicate no leak tracking by default.
+ public Tracker getTracker(String tag) {
+ return null;
+ }
+
public static final class EmptyRunnable implements Runnable {
public void run() {
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java
index 6ceaead..1973b54 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java
@@ -38,6 +38,7 @@
import com.android.systemui.statusbar.policy.UserInfoController;
import com.android.systemui.statusbar.policy.UserSwitcherController;
import com.android.systemui.statusbar.policy.ZenModeController;
+import com.android.systemui.tuner.TunerService;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -59,7 +60,7 @@
KeyguardMonitor keyguardMonitor = getLeakChecker(KeyguardMonitor.class);
when(userSwitcher.getKeyguardMonitor()).thenReturn(keyguardMonitor);
when(userSwitcher.getUsers()).thenReturn(new ArrayList<>());
- QSTileHost host = new QSTileHost(getTrackedContext(),
+ QSTileHost host = new QSTileHost(mContext,
mock(PhoneStatusBar.class),
getLeakChecker(BluetoothController.class),
getLeakChecker(LocationController.class),
@@ -86,5 +87,7 @@
waitForIdleSync(h);
host.destroy();
+ // Ensure the tuner cleans up its persistent listeners.
+ TunerService.get(mContext).destroy();
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/TestableContext.java b/packages/SystemUI/tests/src/com/android/systemui/utils/TestableContext.java
index 5179823..bf73416 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/utils/TestableContext.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/utils/TestableContext.java
@@ -14,18 +14,31 @@
package com.android.systemui.utils;
+import android.content.BroadcastReceiver;
+import android.content.ComponentCallbacks;
import android.content.ContentProviderClient;
-import android.content.ContentResolver;
import android.content.Context;
import android.content.ContextWrapper;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.ServiceConnection;
+import android.os.Handler;
+import android.os.UserHandle;
import android.provider.Settings;
+import com.android.systemui.utils.leaks.Tracker;
+import com.android.systemui.SysuiTestCase;
+
public class TestableContext extends ContextWrapper {
private final FakeContentResolver mFakeContentResolver;
private final FakeSettingsProvider mSettingsProvider;
- public TestableContext(Context base) {
+ private Tracker mReceiver;
+ private Tracker mService;
+ private Tracker mComponent;
+
+ public TestableContext(Context base, SysuiTestCase test) {
super(base);
mFakeContentResolver = new FakeContentResolver(base);
ContentProviderClient settings = base.getContentResolver()
@@ -33,6 +46,9 @@
mSettingsProvider = FakeSettingsProvider.getFakeSettingsProvider(settings,
mFakeContentResolver);
mFakeContentResolver.addProvider(Settings.AUTHORITY, mSettingsProvider);
+ mReceiver = test.getTracker("receiver");
+ mService = test.getTracker("service");
+ mComponent = test.getTracker("component");
}
public FakeSettingsProvider getSettingsProvider() {
@@ -49,4 +65,69 @@
// Return this so its always a TestableContext.
return this;
}
+
+ @Override
+ public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter) {
+ if (mReceiver != null) mReceiver.getLeakInfo(receiver).addAllocation(new Throwable());
+ return super.registerReceiver(receiver, filter);
+ }
+
+ @Override
+ public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter,
+ String broadcastPermission, Handler scheduler) {
+ if (mReceiver != null) mReceiver.getLeakInfo(receiver).addAllocation(new Throwable());
+ return super.registerReceiver(receiver, filter, broadcastPermission, scheduler);
+ }
+
+ @Override
+ public Intent registerReceiverAsUser(BroadcastReceiver receiver, UserHandle user,
+ IntentFilter filter, String broadcastPermission, Handler scheduler) {
+ if (mReceiver != null) mReceiver.getLeakInfo(receiver).addAllocation(new Throwable());
+ return super.registerReceiverAsUser(receiver, user, filter, broadcastPermission,
+ scheduler);
+ }
+
+ @Override
+ public void unregisterReceiver(BroadcastReceiver receiver) {
+ if (mReceiver != null) mReceiver.getLeakInfo(receiver).clearAllocations();
+ super.unregisterReceiver(receiver);
+ }
+
+ @Override
+ public boolean bindService(Intent service, ServiceConnection conn, int flags) {
+ if (mService != null) mService.getLeakInfo(conn).addAllocation(new Throwable());
+ return super.bindService(service, conn, flags);
+ }
+
+ @Override
+ public boolean bindServiceAsUser(Intent service, ServiceConnection conn, int flags,
+ Handler handler, UserHandle user) {
+ if (mService != null) mService.getLeakInfo(conn).addAllocation(new Throwable());
+ return super.bindServiceAsUser(service, conn, flags, handler, user);
+ }
+
+ @Override
+ public boolean bindServiceAsUser(Intent service, ServiceConnection conn, int flags,
+ UserHandle user) {
+ if (mService != null) mService.getLeakInfo(conn).addAllocation(new Throwable());
+ return super.bindServiceAsUser(service, conn, flags, user);
+ }
+
+ @Override
+ public void unbindService(ServiceConnection conn) {
+ if (mService != null) mService.getLeakInfo(conn).clearAllocations();
+ super.unbindService(conn);
+ }
+
+ @Override
+ public void registerComponentCallbacks(ComponentCallbacks callback) {
+ if (mComponent != null) mComponent.getLeakInfo(callback).addAllocation(new Throwable());
+ super.registerComponentCallbacks(callback);
+ }
+
+ @Override
+ public void unregisterComponentCallbacks(ComponentCallbacks callback) {
+ if (mComponent != null) mComponent.getLeakInfo(callback).clearAllocations();
+ super.unregisterComponentCallbacks(callback);
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/BaseLeakChecker.java b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/BaseLeakChecker.java
new file mode 100644
index 0000000..0238bf7
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/BaseLeakChecker.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package com.android.systemui.utils.leaks;
+
+import com.android.systemui.statusbar.policy.CallbackController;
+
+public class BaseLeakChecker<T> implements CallbackController<T> {
+
+ private final Tracker mTracker;
+
+ public BaseLeakChecker(LeakCheckedTest test, String tag) {
+ mTracker = test.getTracker(tag);
+ }
+
+ protected final Tracker getTracker() {
+ return mTracker;
+ }
+
+ @Override
+ public void addCallback(T listener) {
+ mTracker.getLeakInfo(listener).addAllocation(new Throwable());
+ }
+
+ @Override
+ public void removeCallback(T listener) {
+ mTracker.getLeakInfo(listener).clearAllocations();
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeBatteryController.java b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeBatteryController.java
new file mode 100644
index 0000000..fa07d33
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeBatteryController.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package com.android.systemui.utils.leaks;
+
+import android.os.Bundle;
+
+import com.android.systemui.statusbar.policy.BatteryController;
+import com.android.systemui.statusbar.policy.BatteryController.BatteryStateChangeCallback;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+
+public class FakeBatteryController extends BaseLeakChecker<BatteryStateChangeCallback>
+ implements BatteryController {
+ public FakeBatteryController(LeakCheckedTest test) {
+ super(test, "battery");
+ }
+
+ @Override
+ public void dispatchDemoCommand(String command, Bundle args) {
+
+ }
+
+ @Override
+ public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+
+ }
+
+ @Override
+ public void setPowerSaveMode(boolean powerSave) {
+
+ }
+
+ @Override
+ public boolean isPowerSave() {
+ return false;
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeBluetoothController.java b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeBluetoothController.java
new file mode 100644
index 0000000..6074a01
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeBluetoothController.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package com.android.systemui.utils.leaks;
+
+import com.android.settingslib.bluetooth.CachedBluetoothDevice;
+import com.android.systemui.statusbar.policy.BluetoothController;
+import com.android.systemui.statusbar.policy.BluetoothController.Callback;
+
+import java.util.Collection;
+
+public class FakeBluetoothController extends BaseLeakChecker<Callback> implements
+ BluetoothController {
+
+ public FakeBluetoothController(LeakCheckedTest test) {
+ super(test, "bluetooth");
+ }
+
+ @Override
+ public boolean isBluetoothSupported() {
+ return false;
+ }
+
+ @Override
+ public boolean isBluetoothEnabled() {
+ return false;
+ }
+
+ @Override
+ public int getBluetoothState() {
+ return 0;
+ }
+
+ @Override
+ public boolean isBluetoothConnected() {
+ return false;
+ }
+
+ @Override
+ public boolean isBluetoothConnecting() {
+ return false;
+ }
+
+ @Override
+ public String getLastDeviceName() {
+ return null;
+ }
+
+ @Override
+ public void setBluetoothEnabled(boolean enabled) {
+
+ }
+
+ @Override
+ public Collection<CachedBluetoothDevice> getDevices() {
+ return null;
+ }
+
+ @Override
+ public void connect(CachedBluetoothDevice device) {
+
+ }
+
+ @Override
+ public void disconnect(CachedBluetoothDevice device) {
+
+ }
+
+ @Override
+ public boolean canConfigBluetooth() {
+ return false;
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeCastController.java b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeCastController.java
new file mode 100644
index 0000000..08211f8
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeCastController.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package com.android.systemui.utils.leaks;
+
+import com.android.systemui.statusbar.policy.CastController;
+import com.android.systemui.statusbar.policy.CastController.Callback;
+
+import java.util.Set;
+
+public class FakeCastController extends BaseLeakChecker<Callback> implements CastController {
+ public FakeCastController(LeakCheckedTest test) {
+ super(test, "cast");
+ }
+
+ @Override
+ public void setDiscovering(boolean request) {
+
+ }
+
+ @Override
+ public void setCurrentUserId(int currentUserId) {
+
+ }
+
+ @Override
+ public Set<CastDevice> getCastDevices() {
+ return null;
+ }
+
+ @Override
+ public void startCasting(CastDevice device) {
+
+ }
+
+ @Override
+ public void stopCasting(CastDevice device) {
+
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeDataSaverController.java b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeDataSaverController.java
new file mode 100644
index 0000000..857a785
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeDataSaverController.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package com.android.systemui.utils.leaks;
+
+import com.android.systemui.statusbar.policy.DataSaverController;
+import com.android.systemui.statusbar.policy.DataSaverController.Listener;
+
+public class FakeDataSaverController extends BaseLeakChecker<Listener> implements DataSaverController {
+
+ public FakeDataSaverController(LeakCheckedTest test) {
+ super(test, "datasaver");
+ }
+
+ @Override
+ public boolean isDataSaverEnabled() {
+ return false;
+ }
+
+ @Override
+ public void setDataSaverEnabled(boolean enabled) {
+
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeFlashlightController.java b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeFlashlightController.java
new file mode 100644
index 0000000..630abd7
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeFlashlightController.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package com.android.systemui.utils.leaks;
+
+import com.android.systemui.statusbar.policy.FlashlightController;
+import com.android.systemui.statusbar.policy.FlashlightController.FlashlightListener;
+
+public class FakeFlashlightController extends BaseLeakChecker<FlashlightListener>
+ implements FlashlightController {
+ public FakeFlashlightController(LeakCheckedTest test) {
+ super(test, "flashlight");
+ }
+
+ @Override
+ public boolean hasFlashlight() {
+ return false;
+ }
+
+ @Override
+ public void setFlashlight(boolean newState) {
+
+ }
+
+ @Override
+ public boolean isAvailable() {
+ return false;
+ }
+
+ @Override
+ public boolean isEnabled() {
+ return false;
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeHotspotController.java b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeHotspotController.java
new file mode 100644
index 0000000..781960d
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeHotspotController.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package com.android.systemui.utils.leaks;
+
+import com.android.systemui.statusbar.policy.HotspotController;
+import com.android.systemui.statusbar.policy.HotspotController.Callback;
+
+public class FakeHotspotController extends BaseLeakChecker<Callback> implements HotspotController {
+
+ public FakeHotspotController(LeakCheckedTest test) {
+ super(test, "hotspot");
+ }
+
+ @Override
+ public boolean isHotspotEnabled() {
+ return false;
+ }
+
+ @Override
+ public void setHotspotEnabled(boolean enabled) {
+
+ }
+
+ @Override
+ public boolean isHotspotSupported() {
+ return false;
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeKeyguardMonitor.java b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeKeyguardMonitor.java
new file mode 100644
index 0000000..39bbf2d
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeKeyguardMonitor.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package com.android.systemui.utils.leaks;
+
+import com.android.systemui.statusbar.policy.KeyguardMonitor;
+
+public class FakeKeyguardMonitor implements KeyguardMonitor {
+
+ private final BaseLeakChecker<Callback> mCallbackController;
+
+ public FakeKeyguardMonitor(LeakCheckedTest test) {
+ mCallbackController = new BaseLeakChecker<Callback>(test, "keyguard");
+ }
+
+ @Override
+ public void addCallback(Callback callback) {
+ mCallbackController.addCallback(callback);
+ }
+
+ @Override
+ public void removeCallback(Callback callback) {
+ mCallbackController.removeCallback(callback);
+ }
+
+ @Override
+ public boolean isSecure() {
+ return false;
+ }
+
+ @Override
+ public boolean isShowing() {
+ return false;
+ }
+
+ @Override
+ public boolean canSkipBouncer() {
+ return false;
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeLocationController.java b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeLocationController.java
new file mode 100644
index 0000000..eab436c
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeLocationController.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package com.android.systemui.utils.leaks;
+
+import com.android.systemui.statusbar.policy.LocationController;
+import com.android.systemui.statusbar.policy.LocationController.LocationSettingsChangeCallback;
+
+public class FakeLocationController extends BaseLeakChecker<LocationSettingsChangeCallback>
+ implements LocationController {
+ public FakeLocationController(LeakCheckedTest test) {
+ super(test, "location");
+ }
+
+ @Override
+ public boolean isLocationEnabled() {
+ return false;
+ }
+
+ @Override
+ public boolean setLocationEnabled(boolean enabled) {
+ return false;
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeManagedProfileController.java b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeManagedProfileController.java
new file mode 100644
index 0000000..0ec0d77
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeManagedProfileController.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package com.android.systemui.utils.leaks;
+
+import com.android.systemui.statusbar.phone.ManagedProfileController;
+import com.android.systemui.statusbar.phone.ManagedProfileController.Callback;
+
+public class FakeManagedProfileController extends BaseLeakChecker<Callback> implements
+ ManagedProfileController {
+ public FakeManagedProfileController(LeakCheckedTest test) {
+ super(test, "profile");
+ }
+
+ @Override
+ public void setWorkModeEnabled(boolean enabled) {
+
+ }
+
+ @Override
+ public boolean hasActiveProfile() {
+ return false;
+ }
+
+ @Override
+ public boolean isWorkModeEnabled() {
+ return false;
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeNetworkController.java b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeNetworkController.java
new file mode 100644
index 0000000..fcfe9aa
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeNetworkController.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package com.android.systemui.utils.leaks;
+
+import com.android.settingslib.net.DataUsageController;
+import com.android.systemui.statusbar.policy.DataSaverController;
+import com.android.systemui.statusbar.policy.NetworkController;
+import com.android.systemui.statusbar.policy.NetworkController.SignalCallback;
+
+public class FakeNetworkController extends BaseLeakChecker<SignalCallback>
+ implements NetworkController {
+
+ private final FakeDataSaverController mDataSaverController;
+ private final BaseLeakChecker<EmergencyListener> mEmergencyChecker;
+
+ public FakeNetworkController(LeakCheckedTest test) {
+ super(test, "network");
+ mDataSaverController = new FakeDataSaverController(test);
+ mEmergencyChecker = new BaseLeakChecker<EmergencyListener>(test, "emergency");
+ }
+
+ @Override
+ public void addEmergencyListener(EmergencyListener listener) {
+ mEmergencyChecker.addCallback(listener);
+ }
+
+ @Override
+ public void removeEmergencyListener(EmergencyListener listener) {
+ mEmergencyChecker.removeCallback(listener);
+ }
+
+ @Override
+ public DataSaverController getDataSaverController() {
+ return mDataSaverController;
+ }
+
+ @Override
+ public boolean hasMobileDataFeature() {
+ return false;
+ }
+
+ @Override
+ public void setWifiEnabled(boolean enabled) {
+
+ }
+
+ @Override
+ public void onUserSwitched(int newUserId) {
+
+ }
+
+ @Override
+ public AccessPointController getAccessPointController() {
+ return null;
+ }
+
+ @Override
+ public DataUsageController getMobileDataController() {
+ return null;
+ }
+
+ @Override
+ public boolean hasVoiceCallingFeature() {
+ return false;
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeNextAlarmController.java b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeNextAlarmController.java
new file mode 100644
index 0000000..707fc4b
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeNextAlarmController.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package com.android.systemui.utils.leaks;
+
+import com.android.systemui.statusbar.policy.NextAlarmController;
+import com.android.systemui.statusbar.policy.NextAlarmController.NextAlarmChangeCallback;
+
+public class FakeNextAlarmController extends BaseLeakChecker<NextAlarmChangeCallback>
+ implements NextAlarmController {
+
+ public FakeNextAlarmController(LeakCheckedTest test) {
+ super(test, "alarm");
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeRotationLockController.java b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeRotationLockController.java
new file mode 100644
index 0000000..00e2404
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeRotationLockController.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package com.android.systemui.utils.leaks;
+
+import com.android.systemui.statusbar.policy.RotationLockController;
+import com.android.systemui.statusbar.policy.RotationLockController.RotationLockControllerCallback;
+
+public class FakeRotationLockController extends BaseLeakChecker<RotationLockControllerCallback>
+ implements RotationLockController {
+ public FakeRotationLockController(LeakCheckedTest test) {
+ super(test, "rotation");
+ }
+
+ @Override
+ public void setListening(boolean listening) {
+
+ }
+
+ @Override
+ public int getRotationLockOrientation() {
+ return 0;
+ }
+
+ @Override
+ public boolean isRotationLockAffordanceVisible() {
+ return false;
+ }
+
+ @Override
+ public boolean isRotationLocked() {
+ return false;
+ }
+
+ @Override
+ public void setRotationLocked(boolean locked) {
+
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeSecurityController.java b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeSecurityController.java
new file mode 100644
index 0000000..331df58
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeSecurityController.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package com.android.systemui.utils.leaks;
+
+import com.android.systemui.statusbar.policy.SecurityController;
+import com.android.systemui.statusbar.policy.SecurityController.SecurityControllerCallback;
+
+public class FakeSecurityController extends BaseLeakChecker<SecurityControllerCallback>
+ implements SecurityController {
+ public FakeSecurityController(LeakCheckedTest test) {
+ super(test, "security");
+ }
+
+ @Override
+ public boolean isDeviceManaged() {
+ return false;
+ }
+
+ @Override
+ public boolean hasProfileOwner() {
+ return false;
+ }
+
+ @Override
+ public String getDeviceOwnerName() {
+ return null;
+ }
+
+ @Override
+ public String getProfileOwnerName() {
+ return null;
+ }
+
+ @Override
+ public CharSequence getDeviceOwnerOrganizationName() {
+ return null;
+ }
+
+ @Override
+ public boolean isVpnEnabled() {
+ return false;
+ }
+
+ @Override
+ public boolean isVpnRestricted() {
+ return false;
+ }
+
+ @Override
+ public boolean isVpnBranded() {
+ return false;
+ }
+
+ @Override
+ public String getPrimaryVpnName() {
+ return null;
+ }
+
+ @Override
+ public String getProfileVpnName() {
+ return null;
+ }
+
+ @Override
+ public void onUserSwitched(int newUserId) {
+
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeUserInfoController.java b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeUserInfoController.java
new file mode 100644
index 0000000..578b310
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeUserInfoController.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package com.android.systemui.utils.leaks;
+
+import com.android.systemui.statusbar.policy.UserInfoController;
+import com.android.systemui.statusbar.policy.UserInfoController.OnUserInfoChangedListener;
+
+public class FakeUserInfoController extends BaseLeakChecker<OnUserInfoChangedListener>
+ implements UserInfoController {
+ public FakeUserInfoController(LeakCheckedTest test) {
+ super(test, "user_info");
+ }
+
+ @Override
+ public void reloadUserInfo() {
+
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeZenModeController.java b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeZenModeController.java
new file mode 100644
index 0000000..13ea385
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeZenModeController.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package com.android.systemui.utils.leaks;
+
+import android.content.ComponentName;
+import android.net.Uri;
+import android.service.notification.ZenModeConfig;
+import android.service.notification.ZenModeConfig.ZenRule;
+
+import com.android.systemui.statusbar.policy.ZenModeController;
+import com.android.systemui.statusbar.policy.ZenModeController.Callback;
+
+public class FakeZenModeController extends BaseLeakChecker<Callback> implements ZenModeController {
+ public FakeZenModeController(LeakCheckedTest test) {
+ super(test, "zen");
+ }
+
+ @Override
+ public void setZen(int zen, Uri conditionId, String reason) {
+
+ }
+
+ @Override
+ public int getZen() {
+ return 0;
+ }
+
+ @Override
+ public ZenRule getManualRule() {
+ return null;
+ }
+
+ @Override
+ public ZenModeConfig getConfig() {
+ return null;
+ }
+
+ @Override
+ public long getNextAlarm() {
+ return 0;
+ }
+
+ @Override
+ public void setUserId(int userId) {
+
+ }
+
+ @Override
+ public boolean isZenAvailable() {
+ return false;
+ }
+
+ @Override
+ public ComponentName getEffectsSuppressor() {
+ return null;
+ }
+
+ @Override
+ public boolean isCountdownConditionSupported() {
+ return false;
+ }
+
+ @Override
+ public int getCurrentUser() {
+ return 0;
+ }
+
+ @Override
+ public boolean isVolumeRestricted() {
+ return false;
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/LeakCheckedTest.java b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/LeakCheckedTest.java
new file mode 100644
index 0000000..728ed60
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/LeakCheckedTest.java
@@ -0,0 +1,140 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package com.android.systemui.utils.leaks;
+
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.doAnswer;
+
+import android.util.ArrayMap;
+
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.statusbar.phone.ManagedProfileController;
+import com.android.systemui.statusbar.policy.BatteryController;
+import com.android.systemui.statusbar.policy.BluetoothController;
+import com.android.systemui.statusbar.policy.CallbackController;
+import com.android.systemui.statusbar.policy.CastController;
+import com.android.systemui.statusbar.policy.FlashlightController;
+import com.android.systemui.statusbar.policy.HotspotController;
+import com.android.systemui.statusbar.policy.KeyguardMonitor;
+import com.android.systemui.statusbar.policy.LocationController;
+import com.android.systemui.statusbar.policy.NetworkController;
+import com.android.systemui.statusbar.policy.NextAlarmController;
+import com.android.systemui.statusbar.policy.RotationLockController;
+import com.android.systemui.statusbar.policy.SecurityController;
+import com.android.systemui.statusbar.policy.UserInfoController;
+import com.android.systemui.statusbar.policy.ZenModeController;
+
+import org.junit.Assert;
+import org.junit.Rule;
+import org.junit.rules.TestWatcher;
+import org.junit.runner.Description;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Base class for tests to check if receivers are left registered, services bound, or other
+ * listeners listening.
+ */
+public abstract class LeakCheckedTest extends SysuiTestCase {
+ private static final String TAG = "LeakCheckedTest";
+
+ private final Map<String, Tracker> mTrackers = new HashMap<>();
+ private final Map<Class, Object> mLeakCheckers = new ArrayMap<>();
+
+ @Rule
+ public TestWatcher successWatcher = new TestWatcher() {
+ @Override
+ protected void succeeded(Description description) {
+ verify();
+ }
+ };
+
+ public <T> T getLeakChecker(Class<T> cls) {
+ Object obj = mLeakCheckers.get(cls);
+ if (obj == null) {
+ // Lazy create checkers so we only have the ones we need.
+ if (cls == BluetoothController.class) {
+ obj = new FakeBluetoothController(this);
+ } else if (cls == LocationController.class) {
+ obj = new FakeLocationController(this);
+ } else if (cls == RotationLockController.class) {
+ obj = new FakeRotationLockController(this);
+ } else if (cls == ZenModeController.class) {
+ obj = new FakeZenModeController(this);
+ } else if (cls == CastController.class) {
+ obj = new FakeCastController(this);
+ } else if (cls == HotspotController.class) {
+ obj = new FakeHotspotController(this);
+ } else if (cls == FlashlightController.class) {
+ obj = new FakeFlashlightController(this);
+ } else if (cls == UserInfoController.class) {
+ obj = new FakeUserInfoController(this);
+ } else if (cls == KeyguardMonitor.class) {
+ obj = new FakeKeyguardMonitor(this);
+ } else if (cls == BatteryController.class) {
+ obj = new FakeBatteryController(this);
+ } else if (cls == SecurityController.class) {
+ obj = new FakeSecurityController(this);
+ } else if (cls == ManagedProfileController.class) {
+ obj = new FakeManagedProfileController(this);
+ } else if (cls == NextAlarmController.class) {
+ obj = new FakeNextAlarmController(this);
+ } else if (cls == NetworkController.class) {
+ obj = new FakeNetworkController(this);
+ } else {
+ Assert.fail(cls.getName() + " is not supported by LeakCheckedTest yet");
+ }
+ mLeakCheckers.put(cls, obj);
+ }
+ return (T) obj;
+ }
+
+ @Override
+ public Tracker getTracker(String tag) {
+ Tracker t = mTrackers.get(tag);
+ if (t == null) {
+ t = new Tracker();
+ mTrackers.put(tag, t);
+ }
+ return t;
+ }
+
+ public void verify() {
+ mTrackers.values().forEach(Tracker::verify);
+ }
+
+ public <T extends CallbackController> T addListening(T mock, Class<T> cls, String tag) {
+ doAnswer(new Answer<Void>() {
+ @Override
+ public Void answer(InvocationOnMock invocation) throws Throwable {
+ getTracker(tag).getLeakInfo(invocation.getArguments()[0])
+ .addAllocation(new Throwable());
+ return null;
+ }
+ }).when(mock).addCallback(any());
+ doAnswer(new Answer<Void>() {
+ @Override
+ public Void answer(InvocationOnMock invocation) throws Throwable {
+ getTracker(tag).getLeakInfo(invocation.getArguments()[0]).clearAllocations();
+ return null;
+ }
+ }).when(mock).removeCallback(any());
+ mLeakCheckers.put(cls, mock);
+ return mock;
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/LeakInfo.java b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/LeakInfo.java
new file mode 100644
index 0000000..1d016fb
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/LeakInfo.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package com.android.systemui.utils.leaks;
+
+import android.util.Log;
+
+import org.junit.Assert;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.ArrayList;
+import java.util.List;
+
+public class LeakInfo {
+ private static final String TAG = "LeakInfo";
+ private List<Throwable> mThrowables = new ArrayList<>();
+
+ LeakInfo() {
+ }
+
+ public void addAllocation(Throwable t) {
+ // TODO: Drop off the first element in the stack trace here to have a cleaner stack.
+ mThrowables.add(t);
+ }
+
+ public void clearAllocations() {
+ mThrowables.clear();
+ }
+
+ void verify() {
+ if (mThrowables.size() == 0) return;
+ Log.e(TAG, "Listener or binding not properly released");
+ for (Throwable t : mThrowables) {
+ Log.e(TAG, "Allocation found", t);
+ }
+ StringWriter writer = new StringWriter();
+ mThrowables.get(0).printStackTrace(new PrintWriter(writer));
+ Assert.fail("Listener or binding not properly released\n"
+ + writer.toString());
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/Tracker.java b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/Tracker.java
new file mode 100644
index 0000000..26ffd10
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/Tracker.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package com.android.systemui.utils.leaks;
+
+import android.util.ArrayMap;
+
+import com.android.systemui.utils.leaks.LeakInfo;
+
+import java.util.Map;
+
+public class Tracker {
+ private Map<Object, LeakInfo> mObjects = new ArrayMap<>();
+
+ public LeakInfo getLeakInfo(Object object) {
+ LeakInfo leakInfo = mObjects.get(object);
+ if (leakInfo == null) {
+ leakInfo = new LeakInfo();
+ mObjects.put(object, leakInfo);
+ }
+ return leakInfo;
+ }
+
+ void verify() {
+ mObjects.values().forEach(LeakInfo::verify);
+ }
+}
diff --git a/proto/src/ipconnectivity.proto b/proto/src/ipconnectivity.proto
index 29b318f..cf372bc 100644
--- a/proto/src/ipconnectivity.proto
+++ b/proto/src/ipconnectivity.proto
@@ -17,6 +17,22 @@
optional int32 network_id = 1;
};
+// Transport describes a physical technology used by a network. It is a subset
+// of the TRANSPORT_* constants defined in android.net.NetworkCapabilities.
+enum Transport {
+ UNKNOWN = 0;
+ BLUETOOTH = 1;
+ CELLULAR = 2;
+ ETHERNET = 3;
+ WIFI = 4;
+};
+
+// A pair of (key, value) integers for describing histogram-like statistics.
+message Pair {
+ optional int32 key = 1;
+ optional int32 value = 2;
+};
+
// Logs changes in the system default network. Changes can be 1) acquiring a
// default network with no previous default, 2) a switch of the system default
// network to a new default network, 3) a loss of the system default network.
@@ -49,7 +65,8 @@
// This message is associated to android.net.metrics.IpReachabilityEvent.
message IpReachabilityEvent {
// The interface name (wlan, rmnet, lo, ...) on which the probe was sent.
- optional string if_name = 1;
+ // Deprecated since version 2, replaced by transport field.
+ optional string if_name = 1 [deprecated = true];
// The event type code of the probe, represented by constants defined in
// android.net.metrics.IpReachabilityEvent.
@@ -93,6 +110,7 @@
// Logs DNS lookup latencies. Repeated fields must have the same length.
// This message is associated to android.net.metrics.DnsEvent.
+// Deprecated since version 2.
message DNSLookupBatch {
// The id of the network on which the DNS lookups took place.
optional NetworkId network_id = 1;
@@ -107,13 +125,62 @@
repeated int32 latencies_ms = 4;
};
+// Represents a collections of DNS lookup latencies and counters for a
+// particular combination of DNS query type and return code.
+// Since version 2.
+message DNSLatencies {
+ // The type of the DNS lookups, as defined in android.net.metrics.DnsEvent.
+ // Acts as a key for a set of DNS query results.
+ // Possible values are: 0 for getaddrinfo, 1 for gethostbyname.
+ optional int32 type = 1;
+
+ // The return value of the DNS resolver for the DNS lookups.
+ // Acts as a key for a set of DNS query results.
+ // Possible values are: 0 for success, or errno code for failures.
+ optional int32 return_code = 2;
+
+ // The number of query operations recorded.
+ optional int32 query_count = 3;
+
+ // The number of query operations returning A IPv4 records.
+ optional int32 a_count = 4;
+
+ // The number of query operations returning AAAA IPv6 records.
+ optional int32 aaaa_count = 5;
+
+ // The time it took for each DNS lookup to complete. The number of repeated
+ // values can be less than query_count in case of event rate-limiting.
+ repeated int32 latencies_ms = 6;
+};
+
+// Represents latency and errno statistics of the connect() system call.
+// Since version 2.
+message ConnectStatistics {
+ // The number of connect() operations recorded.
+ optional int32 connect_count = 1;
+
+ // The number of connect() operations with IPv6 socket address.
+ optional int32 ipv6_addr_count = 2;
+
+ // The time it took for each successful connect() operation to complete.
+ // The number of repeated values can be less than connect_count in case of
+ // event rate-limiting.
+ repeated int32 latencies_ms = 3;
+
+ // Counts of all error values returned by failed connect() operations.
+ // The Pair key field is the errno code. The Pair value field is the count
+ // for that errno code.
+ repeated Pair errnos_counters = 4;
+};
+
// Represents a DHCP event on a single interface, which can be a DHCPClient
// state transition or a response packet parsing error.
// This message is associated to android.net.metrics.DhcpClientEvent and
// android.net.metrics.DhcpErrorEvent.
message DHCPEvent {
// The interface name (wlan, rmnet, lo, ...) on which the event happened.
- optional string if_name = 1;
+ // Deprecated since version 2, replaced by transport field.
+ optional string if_name = 1 [deprecated = true];
oneof value {
// The name of a state in the DhcpClient state machine, represented by
@@ -217,7 +284,8 @@
// This message is associated to android.net.metrics.IpManagerEvent.
message IpProvisioningEvent {
// The interface name (wlan, rmnet, lo, ...) on which the probe was sent.
- optional string if_name = 1;
+ // Deprecated since version 2, replaced by transport field.
+ optional string if_name = 1 [deprecated = true];
// The code of the IP provisioning event, represented by constants defined in
// android.net.metrics.IpManagerEvent.
@@ -228,11 +296,15 @@
}
// Represents one of the IP connectivity event defined in this file.
-// Next tag: 12
+// Next tag: 15
message IpConnectivityEvent {
// Time in ms when the event was recorded.
optional int64 time_ms = 1;
+ // Physical transport of the network on which the event happened.
+ // Since version 2.
+ optional Transport transport = 12;
+
// Event type.
oneof event {
@@ -246,7 +318,14 @@
NetworkEvent network_event = 4;
// A batch of DNS lookups.
- DNSLookupBatch dns_lookup_batch = 5;
+ // Deprecated in the nyc-mr2 release since version 2, and replaced by dns_latencies.
+ DNSLookupBatch dns_lookup_batch = 5 [deprecated = true];
+
+ // DNS lookup latency statistics.
+ DNSLatencies dns_latencies = 13;
+
+ // Connect latency and errno statistics.
+ ConnectStatistics connect_statistics = 14;
// A DHCP client event or DHCP receive error.
DHCPEvent dhcp_event = 6;
@@ -277,9 +356,9 @@
optional int32 dropped_events = 2;
// The version number of the metrics events being collected.
- // nyc-dev: not populated, implicitly 0
- // nyc-dr1: not populated, implicitly 1 (sailfish and marlin only)
- // nyc-mr1: not populated, implicitly 1
- // nyc-mr2: 2
+ // nyc-dev: not populated, implicitly 0.
+ // nyc-dr1: not populated, implicitly 1 (sailfish and marlin only).
+ // nyc-mr1: not populated, implicitly 1.
+ // nyc-mr2: 2.
optional int32 version = 3;
};
diff --git a/services/core/java/com/android/server/NetworkScoreService.java b/services/core/java/com/android/server/NetworkScoreService.java
index 4c9ea58..6412e01 100644
--- a/services/core/java/com/android/server/NetworkScoreService.java
+++ b/services/core/java/com/android/server/NetworkScoreService.java
@@ -36,10 +36,12 @@
import android.net.wifi.WifiConfiguration;
import android.os.Binder;
import android.os.IBinder;
+import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.os.UserHandle;
import android.provider.Settings;
import android.text.TextUtils;
+import android.util.ArrayMap;
import android.util.Log;
import com.android.internal.R;
@@ -52,11 +54,11 @@
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.HashSet;
+import java.util.Collection;
+import java.util.Collections;
import java.util.List;
import java.util.Map;
-import java.util.Set;
+import java.util.function.Consumer;
/**
* Backing service for {@link android.net.NetworkScoreManager}.
@@ -68,7 +70,8 @@
private final Context mContext;
private final NetworkScorerAppManager mNetworkScorerAppManager;
- private final Map<Integer, INetworkScoreCache> mScoreCaches;
+ @GuardedBy("mScoreCaches")
+ private final Map<Integer, RemoteCallbackList<INetworkScoreCache>> mScoreCaches;
/** Lock used to update mPackageMonitor when scorer package changes occur. */
private final Object mPackageMonitorLock = new Object[0];
@@ -166,7 +169,7 @@
NetworkScoreService(Context context, NetworkScorerAppManager networkScoreAppManager) {
mContext = context;
mNetworkScorerAppManager = networkScoreAppManager;
- mScoreCaches = new HashMap<>();
+ mScoreCaches = new ArrayMap<>();
IntentFilter filter = new IntentFilter(Intent.ACTION_USER_UNLOCKED);
// TODO: Need to update when we support per-user scorers. http://b/23422763
mContext.registerReceiverAsUser(
@@ -276,7 +279,7 @@
}
// Separate networks by type.
- Map<Integer, List<ScoredNetwork>> networksByType = new HashMap<>();
+ Map<Integer, List<ScoredNetwork>> networksByType = new ArrayMap<>();
for (ScoredNetwork network : networks) {
List<ScoredNetwork> networkList = networksByType.get(network.networkKey.type);
if (networkList == null) {
@@ -287,19 +290,32 @@
}
// Pass the scores of each type down to the appropriate network scorer.
- for (Map.Entry<Integer, List<ScoredNetwork>> entry : networksByType.entrySet()) {
- INetworkScoreCache scoreCache = mScoreCaches.get(entry.getKey());
- if (scoreCache != null) {
- try {
- scoreCache.updateScores(entry.getValue());
- } catch (RemoteException e) {
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "Unable to update scores of type " + entry.getKey(), e);
+ for (final Map.Entry<Integer, List<ScoredNetwork>> entry : networksByType.entrySet()) {
+ final RemoteCallbackList<INetworkScoreCache> callbackList;
+ final boolean isEmpty;
+ synchronized (mScoreCaches) {
+ callbackList = mScoreCaches.get(entry.getKey());
+ isEmpty = callbackList == null || callbackList.getRegisteredCallbackCount() == 0;
+ }
+ if (isEmpty) {
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "No scorer registered for type " + entry.getKey() + ", discarding");
+ }
+ continue;
+ }
+
+ sendCallback(new Consumer<INetworkScoreCache>() {
+ @Override
+ public void accept(INetworkScoreCache networkScoreCache) {
+ try {
+ networkScoreCache.updateScores(entry.getValue());
+ } catch (RemoteException e) {
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "Unable to update scores of type " + entry.getKey(), e);
+ }
}
}
- } else if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "No scorer registered for type " + entry.getKey() + ", discarding");
- }
+ }, Collections.singleton(callbackList));
}
return true;
@@ -394,28 +410,52 @@
/** Clear scores. Callers are responsible for checking permissions as appropriate. */
private void clearInternal() {
- Set<INetworkScoreCache> cachesToClear = getScoreCaches();
-
- for (INetworkScoreCache scoreCache : cachesToClear) {
- try {
- scoreCache.clearScores();
- } catch (RemoteException e) {
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
- Log.v(TAG, "Unable to clear scores", e);
+ sendCallback(new Consumer<INetworkScoreCache>() {
+ @Override
+ public void accept(INetworkScoreCache networkScoreCache) {
+ try {
+ networkScoreCache.clearScores();
+ } catch (RemoteException e) {
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "Unable to clear scores", e);
+ }
}
}
- }
+ }, getScoreCacheLists());
}
@Override
public void registerNetworkScoreCache(int networkType, INetworkScoreCache scoreCache) {
mContext.enforceCallingOrSelfPermission(permission.BROADCAST_NETWORK_PRIVILEGED, TAG);
synchronized (mScoreCaches) {
- if (mScoreCaches.containsKey(networkType)) {
- throw new IllegalArgumentException(
- "Score cache already registered for type " + networkType);
+ RemoteCallbackList<INetworkScoreCache> callbackList = mScoreCaches.get(networkType);
+ if (callbackList == null) {
+ callbackList = new RemoteCallbackList<>();
+ mScoreCaches.put(networkType, callbackList);
}
- mScoreCaches.put(networkType, scoreCache);
+ if (!callbackList.register(scoreCache)) {
+ if (callbackList.getRegisteredCallbackCount() == 0) {
+ mScoreCaches.remove(networkType);
+ }
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "Unable to register NetworkScoreCache for type " + networkType);
+ }
+ }
+ }
+ }
+
+ @Override
+ public void unregisterNetworkScoreCache(int networkType, INetworkScoreCache scoreCache) {
+ mContext.enforceCallingOrSelfPermission(permission.BROADCAST_NETWORK_PRIVILEGED, TAG);
+ synchronized (mScoreCaches) {
+ RemoteCallbackList<INetworkScoreCache> callbackList = mScoreCaches.get(networkType);
+ if (callbackList == null || !callbackList.unregister(scoreCache)) {
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "Unable to unregister NetworkScoreCache for type " + networkType);
+ }
+ } else if (callbackList.getRegisteredCallbackCount() == 0) {
+ mScoreCaches.remove(networkType);
+ }
}
}
@@ -430,7 +470,7 @@
}
@Override
- protected void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
+ protected void dump(final FileDescriptor fd, final PrintWriter writer, final String[] args) {
mContext.enforceCallingOrSelfPermission(permission.DUMP, TAG);
NetworkScorerAppData currentScorer = mNetworkScorerAppManager.getActiveScorer();
if (currentScorer == null) {
@@ -439,13 +479,17 @@
}
writer.println("Current scorer: " + currentScorer.mPackageName);
- for (INetworkScoreCache scoreCache : getScoreCaches()) {
- try {
- TransferPipe.dumpAsync(scoreCache.asBinder(), fd, args);
- } catch (IOException | RemoteException e) {
- writer.println("Failed to dump score cache: " + e);
+ sendCallback(new Consumer<INetworkScoreCache>() {
+ @Override
+ public void accept(INetworkScoreCache networkScoreCache) {
+ try {
+ TransferPipe.dumpAsync(networkScoreCache.asBinder(), fd, args);
+ } catch (IOException | RemoteException e) {
+ writer.println("Failed to dump score cache: " + e);
+ }
}
- }
+ }, getScoreCacheLists());
+
if (mServiceConnection != null) {
mServiceConnection.dump(fd, writer, args);
} else {
@@ -455,14 +499,30 @@
}
/**
- * Returns a set of all score caches that are currently active.
+ * Returns a {@link Collection} of all {@link RemoteCallbackList}s that are currently active.
*
* <p>May be used to perform an action on all score caches without potentially strange behavior
* if a new scorer is registered during that action's execution.
*/
- private Set<INetworkScoreCache> getScoreCaches() {
+ private Collection<RemoteCallbackList<INetworkScoreCache>> getScoreCacheLists() {
synchronized (mScoreCaches) {
- return new HashSet<>(mScoreCaches.values());
+ return new ArrayList<>(mScoreCaches.values());
+ }
+ }
+
+ private void sendCallback(Consumer<INetworkScoreCache> consumer,
+ Collection<RemoteCallbackList<INetworkScoreCache>> remoteCallbackLists) {
+ for (RemoteCallbackList<INetworkScoreCache> callbackList : remoteCallbackLists) {
+ synchronized (callbackList) { // Ensure only one active broadcast per RemoteCallbackList
+ final int count = callbackList.beginBroadcast();
+ try {
+ for (int i = 0; i < count; i++) {
+ consumer.accept(callbackList.getBroadcastItem(i));
+ }
+ } finally {
+ callbackList.finishBroadcast();
+ }
+ }
}
}
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index a5b3020..e6e4b2d 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -269,10 +269,8 @@
import static android.app.ActivityManager.StackId.DOCKED_STACK_ID;
import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID;
import static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID;
-import static android.app.ActivityManager.StackId.HOME_STACK_ID;
import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
-import static android.app.ActivityManager.StackId.RECENTS_STACK_ID;
import static android.content.pm.PackageManager.FEATURE_FREEFORM_WINDOW_MANAGEMENT;
import static android.content.pm.PackageManager.FEATURE_LEANBACK_ONLY;
import static android.content.pm.PackageManager.FEATURE_PICTURE_IN_PICTURE;
@@ -7496,45 +7494,51 @@
@Override
public void enterPictureInPictureMode(IBinder token) {
- enterPictureInPictureMode(token, DEFAULT_DISPLAY, null /* aspectRatio */);
+ enterPictureInPictureMode(token, DEFAULT_DISPLAY, -1f /* aspectRatio */,
+ false /* checkAspectRatio */);
}
@Override
public void enterPictureInPictureModeWithAspectRatio(IBinder token, float aspectRatio) {
- enterPictureInPictureMode(token, DEFAULT_DISPLAY, aspectRatio);
+ enterPictureInPictureMode(token, DEFAULT_DISPLAY, aspectRatio, true /* checkAspectRatio */);
}
- public void enterPictureInPictureMode(IBinder token, int displayId, Float aspectRatio) {
+ private void enterPictureInPictureMode(IBinder token, int displayId, float aspectRatio,
+ boolean checkAspectRatio) {
final long origId = Binder.clearCallingIdentity();
try {
synchronized(this) {
- if (!mSupportsPictureInPicture) {
- throw new IllegalStateException("enterPictureInPictureMode: "
- + "Device doesn't support picture-in-picture mode.");
- }
+ final ActivityRecord r = ensureValidPictureInPictureActivityLocked(
+ "enterPictureInPictureMode", token, aspectRatio, checkAspectRatio,
+ true /* checkActivityVisibility */);
- final ActivityRecord r = ActivityRecord.forTokenLocked(token);
- if (r == null) {
- throw new IllegalStateException("enterPictureInPictureMode: "
- + "Can't find activity for token=" + token);
- }
+ enterPictureInPictureModeLocked(r, displayId, aspectRatio,
+ true /* moveHomeStackToFront */, "enterPictureInPictureMode");
+ }
+ } finally {
+ Binder.restoreCallingIdentity(origId);
+ }
+ }
- if (!r.supportsPictureInPicture()) {
- throw new IllegalArgumentException("enterPictureInPictureMode: "
- + "Picture-In-Picture not supported for r=" + r);
- }
+ void enterPictureInPictureModeLocked(ActivityRecord r, int displayId, float aspectRatio,
+ boolean moveHomeStackToFront, String reason) {
+ final Rect bounds = isValidPictureInPictureAspectRatio(aspectRatio)
+ ? mWindowManager.getPictureInPictureBounds(displayId, aspectRatio)
+ : mWindowManager.getPictureInPictureDefaultBounds(displayId);
+ mStackSupervisor.moveActivityToPinnedStackLocked(r, reason, bounds, moveHomeStackToFront);
+ }
- if (aspectRatio != null && !isValidPictureInPictureAspectRatio(aspectRatio)) {
- throw new IllegalArgumentException(String.format("enterPictureInPictureMode: "
- + "Aspect ratio is too extreme (must be between %f and %f).",
- mMinPipAspectRatio, mMaxPipAspectRatio));
- }
+ @Override
+ public void enterPictureInPictureModeOnMoveToBackground(IBinder token,
+ boolean enterPictureInPictureOnMoveToBg) {
+ final long origId = Binder.clearCallingIdentity();
+ try {
+ synchronized(this) {
+ final ActivityRecord r = ensureValidPictureInPictureActivityLocked(
+ "requestAutoEnterPictureInPicture", token, -1f /* aspectRatio */,
+ false /* checkAspectRatio */, false /* checkActivityVisibility */);
- final Rect bounds = isValidPictureInPictureAspectRatio(aspectRatio)
- ? mWindowManager.getPictureInPictureBounds(displayId, aspectRatio)
- : mWindowManager.getPictureInPictureDefaultBounds(displayId);
- mStackSupervisor.moveActivityToPinnedStackLocked(r, "enterPictureInPictureMode",
- bounds);
+ r.supportsPipOnMoveToBackground = enterPictureInPictureOnMoveToBg;
}
} finally {
Binder.restoreCallingIdentity(origId);
@@ -7546,33 +7550,68 @@
final long origId = Binder.clearCallingIdentity();
try {
synchronized(this) {
- final ActivityRecord r = ActivityRecord.forTokenLocked(token);
- if (r == null || r.getStack().mStackId != PINNED_STACK_ID) {
- throw new IllegalStateException("setPictureInPictureAspectRatio: "
- + "Requesting activity must be in picture-in-picture mode.");
- }
+ final ActivityRecord r = ensureValidPictureInPictureActivityLocked(
+ "setPictureInPictureAspectRatio", token, aspectRatio,
+ true /* checkAspectRatio */, false /* checkActivityVisibility */);
- if (!isValidPictureInPictureAspectRatio(aspectRatio)) {
- throw new IllegalArgumentException(String.format(
- "setPictureInPictureAspectRatio: Aspect ratio is too extreme (must be "
- + "between %f and %f).", mMinPipAspectRatio,
- mMaxPipAspectRatio));
+ if (r.getStack().getStackId() == PINNED_STACK_ID) {
+ // If the activity is already in picture-in-picture, update the pinned stack now
+ mWindowManager.setPictureInPictureAspectRatio(aspectRatio);
}
-
- mWindowManager.setPictureInPictureAspectRatio(aspectRatio);
+ r.pictureInPictureAspectRatio = aspectRatio;
}
} finally {
Binder.restoreCallingIdentity(origId);
}
}
- private boolean isValidPictureInPictureAspectRatio(Float aspectRatio) {
- if (aspectRatio == null) {
- return false;
- }
+ private boolean isValidPictureInPictureAspectRatio(float aspectRatio) {
return mMinPipAspectRatio <= aspectRatio && aspectRatio <= mMaxPipAspectRatio;
}
+ /**
+ * Checks the state of the system and the activity associated with the given {@param token} to
+ * verify that picture-in-picture is supported for that activity.
+ *
+ * @param checkAspectRatio whether or not to check {@param aspectRatio} is within a valid range
+ * @param checkActivityVisibility whether or not to enforce that the activity is currently
+ * visible
+ *
+ * @return the activity record for the given {@param token} if all the checks pass.
+ */
+ private ActivityRecord ensureValidPictureInPictureActivityLocked(String caller, IBinder token,
+ float aspectRatio, boolean checkAspectRatio, boolean checkActivityVisibility) {
+ if (!mSupportsPictureInPicture) {
+ throw new IllegalStateException(caller
+ + ": Device doesn't support picture-in-picture mode.");
+ }
+
+ final ActivityRecord r = ActivityRecord.forTokenLocked(token);
+ if (r == null) {
+ throw new IllegalStateException(caller
+ + ": Can't find activity for token=" + token);
+ }
+
+ if (!r.canEnterPictureInPicture(checkActivityVisibility)) {
+ throw new IllegalArgumentException(caller
+ + "Current activity does not support picture-in-picture or is not "
+ + "visible r=" + r);
+ }
+
+ if (r.getStack().isHomeStack()) {
+ throw new IllegalStateException(caller
+ + ": Activities on the home stack not supported");
+ }
+
+ if (checkAspectRatio && !isValidPictureInPictureAspectRatio(aspectRatio)) {
+ throw new IllegalArgumentException(String.format(caller
+ + ": Aspect ratio is too extreme (must be between %f and %f).",
+ mMinPipAspectRatio, mMaxPipAspectRatio));
+ }
+
+ return r;
+ }
+
// =========================================================
// PROCESS INFO
// =========================================================
diff --git a/services/core/java/com/android/server/am/ActivityRecord.java b/services/core/java/com/android/server/am/ActivityRecord.java
index d2a560f..13c422b 100644
--- a/services/core/java/com/android/server/am/ActivityRecord.java
+++ b/services/core/java/com/android/server/am/ActivityRecord.java
@@ -21,7 +21,6 @@
import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID;
import static android.app.ActivityManager.StackId.HOME_STACK_ID;
import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
-import static android.app.ActivityManager.StackId.RECENTS_STACK_ID;
import static android.content.pm.ActivityInfo.CONFIG_ORIENTATION;
import static android.content.pm.ActivityInfo.CONFIG_SCREEN_LAYOUT;
import static android.content.pm.ActivityInfo.CONFIG_SCREEN_SIZE;
@@ -84,7 +83,6 @@
import android.util.TimeUtils;
import android.view.AppTransitionAnimationSpec;
import android.view.IApplicationToken;
-import android.view.WindowManager;
import android.view.WindowManager.LayoutParams;
import com.android.internal.app.ResolverActivity;
@@ -218,6 +216,14 @@
boolean frozenBeforeDestroy;// has been frozen but not yet destroyed.
boolean immersive; // immersive mode (don't interrupt if possible)
boolean forceNewConfig; // force re-create with new config next time
+ boolean supportsPipOnMoveToBackground; // Supports automatically entering picture-in-picture
+ // when this activity is hidden. This flag is requested by the activity.
+ private boolean enterPipOnMoveToBackground; // Flag to enter picture in picture when this
+ // activity is made invisible. This flag is set specifically when another task is being
+ // launched or moved to the front which may cause this activity to try and enter PiP
+ // when it is next made invisible.
+ float pictureInPictureAspectRatio; // The aspect ratio to use when auto-entering
+ // picture-in-picture
int launchCount; // count of launches since last state
long lastLaunchTime; // time of last launch of this activity
ComponentName requestedVrComponent; // the requested component for handling VR mode.
@@ -434,6 +440,11 @@
if (info != null) {
pw.println(prefix + "resizeMode=" + ActivityInfo.resizeModeToString(info.resizeMode));
}
+ if (supportsPipOnMoveToBackground) {
+ pw.println(prefix + "supportsPipOnMoveToBackground=1 "
+ + "enterPipOnMoveToBackground="
+ + (enterPipOnMoveToBackground ? 1 : 0));
+ }
}
private boolean crossesHorizontalSizeThreshold(int firstDp, int secondDp) {
@@ -842,6 +853,23 @@
}
/**
+ * If this activity has requested that it auto-enter picture-in-picture and we can actually do
+ * this, then mark it to enter picture in picture at that point.
+ */
+ void setEnterPipOnMoveToBackground(boolean enterPipOnInvisible) {
+ if (supportsPipOnMoveToBackground) {
+ enterPipOnMoveToBackground = enterPipOnInvisible;
+ }
+ }
+
+ /**
+ * @return whether to enter PiP when this activity is made invisible.
+ */
+ public boolean shouldEnterPictureInPictureOnInvisible() {
+ return enterPipOnMoveToBackground;
+ }
+
+ /**
* @return Stack value from current task, null if there is no task.
*/
ActivityStack getStack() {
@@ -922,10 +950,34 @@
&& info.resizeMode != RESIZE_MODE_RESIZEABLE_VIA_SDK_VERSION;
}
+ /**
+ * @return whether this activity's resize mode supports PIP.
+ */
boolean supportsPictureInPicture() {
return !isHomeActivity() && info.resizeMode == RESIZE_MODE_RESIZEABLE_AND_PIPABLE;
}
+ /**
+ * @return whether this activity is currently allowed to enter PIP, if
+ * {@param checkActivityVisibility} is set, then the current activity visibility is taken into
+ * account.
+ */
+ boolean canEnterPictureInPicture(boolean checkActivityVisibility) {
+ if (!checkActivityVisibility) {
+ return supportsPictureInPicture();
+ }
+
+ if (supportsPictureInPicture() && visible) {
+ switch (state) {
+ case RESUMED:
+ case PAUSING:
+ case PAUSED:
+ return true;
+ }
+ }
+ return false;
+ }
+
boolean canGoInDockedStack() {
return !isHomeActivity() && isResizeableOrForced();
}
diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java
index d94d3cd..d160a46 100644
--- a/services/core/java/com/android/server/am/ActivityStack.java
+++ b/services/core/java/com/android/server/am/ActivityStack.java
@@ -22,7 +22,6 @@
import static android.app.ActivityManager.StackId.HOME_STACK_ID;
import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
-import static android.app.ActivityManager.StackId.RECENTS_STACK_ID;
import static android.content.pm.ActivityInfo.CONFIG_SCREEN_LAYOUT;
import static android.content.pm.ActivityInfo.FLAG_RESUME_WHILE_PAUSING;
import static android.content.pm.ActivityInfo.FLAG_SHOW_FOR_ALL_USERS;
@@ -723,7 +722,6 @@
mStacks.remove(this);
int addIndex = mStacks.size();
-
if (addIndex > 0) {
final ActivityStack topStack = mStacks.get(addIndex - 1);
if (StackId.isAlwaysOnTop(topStack.mStackId) && topStack != this) {
@@ -1718,7 +1716,9 @@
+ stackInvisible + " behindFullscreenActivity="
+ behindFullscreenActivity + " mLaunchTaskBehind="
+ r.mLaunchTaskBehind);
- makeInvisible(r, visibleBehind);
+ if (!enterPictureInPictureOnActivityInvisible(r)) {
+ makeInvisible(r, visibleBehind);
+ }
}
}
if (mStackId == FREEFORM_WORKSPACE_STACK_ID) {
@@ -1876,6 +1876,32 @@
return false;
}
+ /**
+ * Attempts to enter picture-in-picture if the activity that is being made invisible supports
+ * it. If not, then
+ *
+ * @return whether or not picture-in-picture mode was entered.
+ */
+ private boolean enterPictureInPictureOnActivityInvisible(ActivityRecord r) {
+ final boolean hasPinnedStack =
+ mStackSupervisor.getStack(PINNED_STACK_ID) != null;
+ if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY, " enterPictureInPictureOnInvisible="
+ + r.shouldEnterPictureInPictureOnInvisible()
+ + " hasPinnedStack=" + hasPinnedStack);
+ if (!hasPinnedStack && r.visible && r.shouldEnterPictureInPictureOnInvisible()) {
+ r.setEnterPipOnMoveToBackground(false);
+
+ // Enter picture in picture, but don't move the home stack to the front
+ // since it will affect the focused stack's visibility and occlude
+ // starting activities
+ mService.enterPictureInPictureModeLocked(r, r.getDisplayId(),
+ r.pictureInPictureAspectRatio, false /* moveHomeStackToFront */,
+ "ensureActivitiesVisibleLocked");
+ return true;
+ }
+ return false;
+ }
+
private void makeInvisible(ActivityRecord r, ActivityRecord visibleBehind) {
if (!r.visible) {
if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY, "Already invisible: " + r);
@@ -2613,8 +2639,8 @@
mWindowManager.moveTaskToTop(task.taskId);
}
- final void startActivityLocked(ActivityRecord r, boolean newTask, boolean keepCurTransition,
- ActivityOptions options) {
+ final void startActivityLocked(ActivityRecord r, ActivityRecord focusedTopActivity,
+ boolean newTask, boolean keepCurTransition, ActivityOptions options) {
TaskRecord rTask = r.task;
final int taskId = rTask.taskId;
// mLaunchTaskBehind tasks get placed at the back of the task stack.
@@ -2693,11 +2719,20 @@
mWindowManager.prepareAppTransition(TRANSIT_NONE, keepCurTransition);
mNoAnimActivities.add(r);
} else {
- mWindowManager.prepareAppTransition(newTask
- ? r.mLaunchTaskBehind
- ? TRANSIT_TASK_OPEN_BEHIND
- : TRANSIT_TASK_OPEN
- : TRANSIT_ACTIVITY_OPEN, keepCurTransition);
+ int transit = TRANSIT_ACTIVITY_OPEN;
+ if (newTask) {
+ if (r.mLaunchTaskBehind) {
+ transit = TRANSIT_TASK_OPEN_BEHIND;
+ } else {
+ // If a new task is being launched, then mark the existing top activity to
+ // enter picture-in-picture if it supports auto-entering PiP
+ if (focusedTopActivity != null) {
+ focusedTopActivity.setEnterPipOnMoveToBackground(true);
+ }
+ transit = TRANSIT_TASK_OPEN;
+ }
+ }
+ mWindowManager.prepareAppTransition(transit, keepCurTransition);
mNoAnimActivities.remove(r);
}
addConfigOverride(r, task);
@@ -4193,6 +4228,8 @@
AppTimeTracker timeTracker, String reason) {
if (DEBUG_SWITCH) Slog.v(TAG_SWITCH, "moveTaskToFront: " + tr);
+ final ActivityRecord focusedTopActivity = mStackSupervisor.getFocusedStack() != null
+ ? mStackSupervisor.getFocusedStack().topActivity() : null;
final int numTasks = mTaskHistory.size();
final int index = mTaskHistory.indexOf(tr);
if (numTasks == 0 || index < 0) {
@@ -4238,6 +4275,11 @@
} else {
updateTransitLocked(TRANSIT_TASK_TO_FRONT, options);
}
+ // If a new task is moved to the front, then mark the existing top activity to enter
+ // picture-in-picture if it supports auto-entering PiP
+ if (focusedTopActivity != null) {
+ focusedTopActivity.setEnterPipOnMoveToBackground(true);
+ }
mStackSupervisor.resumeFocusedStackTopActivityLocked();
EventLog.writeEvent(EventLogTags.AM_TASK_TO_FRONT, tr.userId, tr.taskId);
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
index dde948f..281812c 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -2322,6 +2322,11 @@
return true;
}
+ if (!task.canResizeToBounds(bounds)) {
+ throw new IllegalArgumentException("resizeTaskLocked: Can not resize task=" + task
+ + " to bounds=" + bounds + " resizeMode=" + task.mResizeMode);
+ }
+
// Do not move the task to another stack here.
// This method assumes that the task is already placed in the right stack.
// we do not mess with that decision and we only do the resize!
@@ -2638,11 +2643,13 @@
return false;
}
- moveActivityToPinnedStackLocked(r, "moveTopActivityToPinnedStack", bounds);
+ moveActivityToPinnedStackLocked(r, "moveTopActivityToPinnedStack", bounds,
+ true /* moveHomeStackToFront */);
return true;
}
- void moveActivityToPinnedStackLocked(ActivityRecord r, String reason, Rect bounds) {
+ void moveActivityToPinnedStackLocked(ActivityRecord r, String reason, Rect bounds,
+ boolean moveHomeStackToFront) {
mWindowManager.deferSurfaceLayout();
try {
final TaskRecord task = r.task;
@@ -2666,7 +2673,7 @@
if (task.mActivities.size() == 1) {
// There is only one activity in the task. So, we can just move the task over to
// the stack without re-parenting the activity in a different task.
- if (task.getTaskToReturnTo() == HOME_ACTIVITY_TYPE) {
+ if (moveHomeStackToFront && task.getTaskToReturnTo() == HOME_ACTIVITY_TYPE) {
// Move the home stack forward if the task we just moved to the pinned stack
// was launched from home so home should be visible behind it.
moveHomeStackToFront(reason);
@@ -2674,6 +2681,8 @@
moveTaskToStackLocked(
task.taskId, PINNED_STACK_ID, ON_TOP, FORCE_FOCUS, reason, !ANIMATE);
} else {
+ // There are multiple activities in the task and moving the top activity should
+ // reveal/leave the other activities in their original task
stack.moveActivityToStack(r);
}
} finally {
diff --git a/services/core/java/com/android/server/am/ActivityStarter.java b/services/core/java/com/android/server/am/ActivityStarter.java
index 64bf3ad..d0960a0 100644
--- a/services/core/java/com/android/server/am/ActivityStarter.java
+++ b/services/core/java/com/android/server/am/ActivityStarter.java
@@ -1068,6 +1068,7 @@
// If the activity being launched is the same as the one currently at the top, then
// we need to check if it should only be launched once.
final ActivityStack topStack = mSupervisor.mFocusedStack;
+ final ActivityRecord topFocused = topStack.topActivity();
final ActivityRecord top = topStack.topRunningNonDelayedActivityLocked(mNotTop);
final boolean dontStart = top != null && mStartActivity.resultTo == null
&& top.realActivity.equals(mStartActivity.realActivity)
@@ -1139,7 +1140,8 @@
sendPowerHintForLaunchStartIfNeeded(false /* forceSend */);
- mTargetStack.startActivityLocked(mStartActivity, newTask, mKeepCurTransition, mOptions);
+ mTargetStack.startActivityLocked(mStartActivity, topFocused, newTask, mKeepCurTransition,
+ mOptions);
if (mDoResume) {
final ActivityRecord topTaskActivity = mStartActivity.task.topRunningActivityLocked();
if (!mTargetStack.isFocusable()
diff --git a/services/core/java/com/android/server/am/TaskRecord.java b/services/core/java/com/android/server/am/TaskRecord.java
index 5c352e1..383f106 100644
--- a/services/core/java/com/android/server/am/TaskRecord.java
+++ b/services/core/java/com/android/server/am/TaskRecord.java
@@ -73,6 +73,9 @@
import static android.content.pm.ActivityInfo.LOCK_TASK_LAUNCH_MODE_DEFAULT;
import static android.content.pm.ActivityInfo.LOCK_TASK_LAUNCH_MODE_IF_WHITELISTED;
import static android.content.pm.ActivityInfo.LOCK_TASK_LAUNCH_MODE_NEVER;
+import static android.content.pm.ActivityInfo.RESIZE_MODE_FORCE_RESIZABLE_LANDSCAPE_ONLY;
+import static android.content.pm.ActivityInfo.RESIZE_MODE_FORCE_RESIZABLE_PORTRAIT_ONLY;
+import static android.content.pm.ActivityInfo.RESIZE_MODE_FORCE_RESIZABLE_PRESERVE_ORIENTATION;
import static android.content.pm.ActivityInfo.RESIZE_MODE_FORCE_RESIZEABLE;
import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE;
import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE_VIA_SDK_VERSION;
@@ -1077,12 +1080,32 @@
&& !mTemporarilyUnresizable;
}
+ /**
+ * Check that a given bounds matches the application requested orientation.
+ *
+ * @param bounds The bounds to be tested.
+ * @return True if the requested bounds are okay for a resizing request.
+ */
+ boolean canResizeToBounds(Rect bounds) {
+ if (bounds == null || getStackId() != FREEFORM_WORKSPACE_STACK_ID) {
+ // Note: If not on the freeform workspace, we ignore the bounds.
+ return true;
+ }
+ final boolean landscape = bounds.width() > bounds.height();
+ if (mResizeMode == RESIZE_MODE_FORCE_RESIZABLE_PRESERVE_ORIENTATION) {
+ return mBounds == null || landscape == (mBounds.width() > mBounds.height());
+ }
+ return (mResizeMode != RESIZE_MODE_FORCE_RESIZABLE_PORTRAIT_ONLY || !landscape)
+ && (mResizeMode != RESIZE_MODE_FORCE_RESIZABLE_LANDSCAPE_ONLY || landscape);
+ }
+
boolean isOnTopLauncher() {
return isHomeTask() && mIsOnTopLauncher;
}
boolean canGoInDockedStack() {
- return isResizeable();
+ return isResizeable() &&
+ !ActivityInfo.isPreserveOrientationMode(mResizeMode);
}
/**
diff --git a/services/core/java/com/android/server/wm/PinnedStackController.java b/services/core/java/com/android/server/wm/PinnedStackController.java
index 01a50b7..0ad4e0a 100644
--- a/services/core/java/com/android/server/wm/PinnedStackController.java
+++ b/services/core/java/com/android/server/wm/PinnedStackController.java
@@ -135,7 +135,7 @@
mService = service;
mDisplayContent = displayContent;
mSnapAlgorithm = new PipSnapAlgorithm(service.mContext);
- mMotionHelper = new PipMotionHelper(BackgroundThread.getHandler());
+ mMotionHelper = new PipMotionHelper(UiThread.getHandler());
mDisplayInfo.copyFrom(mDisplayContent.getDisplayInfo());
reloadResources();
}
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 29afc8d..612af75 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -19,7 +19,9 @@
import static android.app.ActivityManager.RESIZE_MODE_SYSTEM_SCREEN_ROTATION;
import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID;
-
+import static android.content.pm.ActivityInfo.RESIZE_MODE_FORCE_RESIZABLE_LANDSCAPE_ONLY;
+import static android.content.pm.ActivityInfo.RESIZE_MODE_FORCE_RESIZABLE_PRESERVE_ORIENTATION;
+import static android.content.pm.ActivityInfo.RESIZE_MODE_FORCE_RESIZABLE_PORTRAIT_ONLY;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_STACK;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
@@ -280,6 +282,17 @@
return ActivityInfo.isResizeableMode(mResizeMode) || mService.mForceResizableTasks;
}
+ /**
+ * Tests if the orientation should be preserved upon user interactive resizig operations.
+
+ * @return true if orientation should not get changed upon resizing operation.
+ */
+ boolean preserveOrientationOnResize() {
+ return mResizeMode == RESIZE_MODE_FORCE_RESIZABLE_PORTRAIT_ONLY
+ || mResizeMode == RESIZE_MODE_FORCE_RESIZABLE_LANDSCAPE_ONLY
+ || mResizeMode == RESIZE_MODE_FORCE_RESIZABLE_PRESERVE_ORIENTATION;
+ }
+
boolean isOnTopLauncher() {
return mIsOnTopLauncher;
}
diff --git a/services/core/java/com/android/server/wm/TaskPositioner.java b/services/core/java/com/android/server/wm/TaskPositioner.java
index 6887312..267566b 100644
--- a/services/core/java/com/android/server/wm/TaskPositioner.java
+++ b/services/core/java/com/android/server/wm/TaskPositioner.java
@@ -50,9 +50,9 @@
import android.view.InputDevice;
import android.view.InputEvent;
import android.view.MotionEvent;
-import android.view.SurfaceControl;
import android.view.WindowManager;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.server.input.InputApplicationHandle;
import com.android.server.input.InputWindowHandle;
import com.android.server.wm.WindowManagerService.H;
@@ -61,6 +61,7 @@
import java.lang.annotation.RetentionPolicy;
class TaskPositioner implements DimLayer.DimLayerUser {
+ private static final boolean DEBUG_ORIENTATION_VIOLATIONS = false;
private static final String TAG_LOCAL = "TaskPositioner";
private static final String TAG = TAG_WITH_CLASS_NAME ? TAG_LOCAL : TAG_WM;
@@ -89,6 +90,12 @@
public static final int RESIZING_HINT_DURATION_MS = 0;
+ // The minimal aspect ratio which needs to be met to count as landscape (or 1/.. for portrait).
+ // Note: We do not use the 1.33 from the CDD here since the user is allowed to use what ever
+ // aspect he desires.
+ @VisibleForTesting
+ static final float MIN_ASPECT = 1.2f;
+
private final WindowManagerService mService;
private WindowPositionerEventReceiver mInputEventReceiver;
private Display mDisplay;
@@ -103,8 +110,11 @@
private Task mTask;
private boolean mResizing;
+ private boolean mPreserveOrientation;
+ private boolean mStartOrientationWasLandscape;
private final Rect mWindowOriginalBounds = new Rect();
private final Rect mWindowDragBounds = new Rect();
+ private final Point mMaxVisibleSize = new Point();
private float mStartDragX;
private float mStartDragY;
@CtrlType
@@ -226,6 +236,11 @@
mService = service;
}
+ @VisibleForTesting
+ Rect getWindowDragBounds() {
+ return mWindowDragBounds;
+ }
+
/**
* @param display The Display that the window being dragged is on.
*/
@@ -294,6 +309,7 @@
mSideMargin = dipToPixel(SIDE_MARGIN_DIP, mDisplayMetrics);
mMinVisibleWidth = dipToPixel(MINIMUM_VISIBLE_WIDTH_IN_DP, mDisplayMetrics);
mMinVisibleHeight = dipToPixel(MINIMUM_VISIBLE_HEIGHT_IN_DP, mDisplayMetrics);
+ mDisplay.getRealSize(mMaxVisibleSize);
mDragEnded = false;
}
@@ -335,44 +351,57 @@
mService.resumeRotationLocked();
}
- void startDragLocked(WindowState win, boolean resize, float startX, float startY) {
+ void startDrag(WindowState win, boolean resize, boolean preserveOrientation, float startX,
+ float startY) {
if (DEBUG_TASK_POSITIONING) {
- Slog.d(TAG, "startDragLocked: win=" + win + ", resize=" + resize
- + ", {" + startX + ", " + startY + "}");
+ Slog.d(TAG, "startDrag: win=" + win + ", resize=" + resize
+ + ", preserveOrientation=" + preserveOrientation + ", {" + startX + ", "
+ + startY + "}");
}
- mCtrlType = CTRL_NONE;
mTask = win.getTask();
- mStartDragX = startX;
- mStartDragY = startY;
-
// Use the dim bounds, not the original task bounds. The cursor
// movement should be calculated relative to the visible bounds.
// Also, use the dim bounds of the task which accounts for
// multiple app windows. Don't use any bounds from win itself as it
// may not be the same size as the task.
mTask.getDimBounds(mTmpRect);
+ startDrag(resize, preserveOrientation, startX, startY, mTmpRect);
+ }
+
+ @VisibleForTesting
+ void startDrag(boolean resize, boolean preserveOrientation,
+ float startX, float startY, Rect startBounds) {
+ mCtrlType = CTRL_NONE;
+ mStartDragX = startX;
+ mStartDragY = startY;
+ mPreserveOrientation = preserveOrientation;
if (resize) {
- if (startX < mTmpRect.left) {
+ if (startX < startBounds.left) {
mCtrlType |= CTRL_LEFT;
}
- if (startX > mTmpRect.right) {
+ if (startX > startBounds.right) {
mCtrlType |= CTRL_RIGHT;
}
- if (startY < mTmpRect.top) {
+ if (startY < startBounds.top) {
mCtrlType |= CTRL_TOP;
}
- if (startY > mTmpRect.bottom) {
+ if (startY > startBounds.bottom) {
mCtrlType |= CTRL_BOTTOM;
}
- mResizing = true;
+ mResizing = mCtrlType != CTRL_NONE;
}
- mWindowOriginalBounds.set(mTmpRect);
+ // In case of !isDockedInEffect we are using the union of all task bounds. These might be
+ // made up out of multiple windows which are only partially overlapping. When that happens,
+ // the orientation from the window of interest to the entire stack might diverge. However
+ // for now we treat them as the same.
+ mStartOrientationWasLandscape = startBounds.width() >= startBounds.height();
+ mWindowOriginalBounds.set(startBounds);
// Make sure we always have valid drag bounds even if the drag ends before any move events
// have been handled.
- mWindowDragBounds.set(mTmpRect);
+ mWindowDragBounds.set(startBounds);
}
private void endDragLocked() {
@@ -387,26 +416,7 @@
}
if (mCtrlType != CTRL_NONE) {
- // This is a resizing operation.
- final int deltaX = Math.round(x - mStartDragX);
- final int deltaY = Math.round(y - mStartDragY);
- int left = mWindowOriginalBounds.left;
- int top = mWindowOriginalBounds.top;
- int right = mWindowOriginalBounds.right;
- int bottom = mWindowOriginalBounds.bottom;
- if ((mCtrlType & CTRL_LEFT) != 0) {
- left = Math.min(left + deltaX, right - mMinVisibleWidth);
- }
- if ((mCtrlType & CTRL_TOP) != 0) {
- top = Math.min(top + deltaY, bottom - mMinVisibleHeight);
- }
- if ((mCtrlType & CTRL_RIGHT) != 0) {
- right = Math.max(left + mMinVisibleWidth, right + deltaX);
- }
- if ((mCtrlType & CTRL_BOTTOM) != 0) {
- bottom = Math.max(top + mMinVisibleHeight, bottom + deltaY);
- }
- mWindowDragBounds.set(left, top, right, bottom);
+ resizeDrag(x, y);
mTask.setDragResizing(true, DRAG_RESIZE_MODE_FREEFORM);
return false;
}
@@ -428,6 +438,168 @@
return false;
}
+ /**
+ * The user is drag - resizing the window.
+ *
+ * @param x The x coordinate of the current drag coordinate.
+ * @param y the y coordinate of the current drag coordinate.
+ */
+ @VisibleForTesting
+ void resizeDrag(float x, float y) {
+ // This is a resizing operation.
+ // We need to keep various constraints:
+ // 1. mMinVisible[Width/Height] <= [width/height] <= mMaxVisibleSize.[x/y]
+ // 2. The orientation is kept - if required.
+ final int deltaX = Math.round(x - mStartDragX);
+ final int deltaY = Math.round(y - mStartDragY);
+ int left = mWindowOriginalBounds.left;
+ int top = mWindowOriginalBounds.top;
+ int right = mWindowOriginalBounds.right;
+ int bottom = mWindowOriginalBounds.bottom;
+
+ // The aspect which we have to respect. Note that if the orientation does not need to be
+ // preserved the aspect will be calculated as 1.0 which neutralizes the following
+ // computations.
+ final float minAspect = !mPreserveOrientation
+ ? 1.0f
+ : (mStartOrientationWasLandscape ? MIN_ASPECT : (1.0f / MIN_ASPECT));
+ // Calculate the resulting width and height of the drag operation.
+ int width = right - left;
+ int height = bottom - top;
+ if ((mCtrlType & CTRL_LEFT) != 0) {
+ width = Math.max(mMinVisibleWidth, width - deltaX);
+ } else if ((mCtrlType & CTRL_RIGHT) != 0) {
+ width = Math.max(mMinVisibleWidth, width + deltaX);
+ }
+ if ((mCtrlType & CTRL_TOP) != 0) {
+ height = Math.max(mMinVisibleHeight, height - deltaY);
+ } else if ((mCtrlType & CTRL_BOTTOM) != 0) {
+ height = Math.max(mMinVisibleHeight, height + deltaY);
+ }
+
+ // If we have to preserve the orientation - check that we are doing so.
+ final float aspect = (float) width / (float) height;
+ if (mPreserveOrientation && ((mStartOrientationWasLandscape && aspect < MIN_ASPECT)
+ || (!mStartOrientationWasLandscape && aspect > (1.0 / MIN_ASPECT)))) {
+ // Calculate 2 rectangles fulfilling all requirements for either X or Y being the major
+ // drag axis. What ever is producing the bigger rectangle will be chosen.
+ int width1;
+ int width2;
+ int height1;
+ int height2;
+ if (mStartOrientationWasLandscape) {
+ // Assuming that the width is our target we calculate the height.
+ width1 = Math.max(mMinVisibleWidth, Math.min(mMaxVisibleSize.x, width));
+ height1 = Math.min(height, Math.round((float)width1 / MIN_ASPECT));
+ if (height1 < mMinVisibleHeight) {
+ // If the resulting height is too small we adjust to the minimal size.
+ height1 = mMinVisibleHeight;
+ width1 = Math.max(mMinVisibleWidth,
+ Math.min(mMaxVisibleSize.x, Math.round((float)height1 * MIN_ASPECT)));
+ }
+ // Assuming that the height is our target we calculate the width.
+ height2 = Math.max(mMinVisibleHeight, Math.min(mMaxVisibleSize.y, height));
+ width2 = Math.max(width, Math.round((float)height2 * MIN_ASPECT));
+ if (width2 < mMinVisibleWidth) {
+ // If the resulting width is too small we adjust to the minimal size.
+ width2 = mMinVisibleWidth;
+ height2 = Math.max(mMinVisibleHeight,
+ Math.min(mMaxVisibleSize.y, Math.round((float)width2 / MIN_ASPECT)));
+ }
+ } else {
+ // Assuming that the width is our target we calculate the height.
+ width1 = Math.max(mMinVisibleWidth, Math.min(mMaxVisibleSize.x, width));
+ height1 = Math.max(height, Math.round((float)width1 * MIN_ASPECT));
+ if (height1 < mMinVisibleHeight) {
+ // If the resulting height is too small we adjust to the minimal size.
+ height1 = mMinVisibleHeight;
+ width1 = Math.max(mMinVisibleWidth,
+ Math.min(mMaxVisibleSize.x, Math.round((float)height1 / MIN_ASPECT)));
+ }
+ // Assuming that the height is our target we calculate the width.
+ height2 = Math.max(mMinVisibleHeight, Math.min(mMaxVisibleSize.y, height));
+ width2 = Math.min(width, Math.round((float)height2 / MIN_ASPECT));
+ if (width2 < mMinVisibleWidth) {
+ // If the resulting width is too small we adjust to the minimal size.
+ width2 = mMinVisibleWidth;
+ height2 = Math.max(mMinVisibleHeight,
+ Math.min(mMaxVisibleSize.y, Math.round((float)width2 * MIN_ASPECT)));
+ }
+ }
+
+ // Use the bigger of the two rectangles if the major change was positive, otherwise
+ // do the opposite.
+ final boolean grows = width > (right - left) || height > (bottom - top);
+ if (grows == (width1 * height1 > width2 * height2)) {
+ width = width1;
+ height = height1;
+ } else {
+ width = width2;
+ height = height2;
+ }
+ }
+
+ // Update mWindowDragBounds to the new drag size.
+ updateDraggedBounds(left, top, right, bottom, width, height);
+ }
+
+ /**
+ * Given the old coordinates and the new width and height, update the mWindowDragBounds.
+ *
+ * @param left The original left bound before the user started dragging.
+ * @param top The original top bound before the user started dragging.
+ * @param right The original right bound before the user started dragging.
+ * @param bottom The original bottom bound before the user started dragging.
+ * @param newWidth The new dragged width.
+ * @param newHeight The new dragged height.
+ */
+ void updateDraggedBounds(int left, int top, int right, int bottom, int newWidth,
+ int newHeight) {
+ // Generate the final bounds by keeping the opposite drag edge constant.
+ if ((mCtrlType & CTRL_LEFT) != 0) {
+ left = right - newWidth;
+ } else { // Note: The right might have changed - if we pulled at the right or not.
+ right = left + newWidth;
+ }
+ if ((mCtrlType & CTRL_TOP) != 0) {
+ top = bottom - newHeight;
+ } else { // Note: The height might have changed - if we pulled at the bottom or not.
+ bottom = top + newHeight;
+ }
+
+ mWindowDragBounds.set(left, top, right, bottom);
+
+ checkBoundsForOrientationViolations(mWindowDragBounds);
+ }
+
+ /**
+ * Validate bounds against orientation violations (if DEBUG_ORIENTATION_VIOLATIONS is set).
+ *
+ * @param bounds The bounds to be checked.
+ */
+ private void checkBoundsForOrientationViolations(Rect bounds) {
+ // When using debug check that we are not violating the given constraints.
+ if (DEBUG_ORIENTATION_VIOLATIONS) {
+ if (mStartOrientationWasLandscape != (bounds.width() >= bounds.height())) {
+ Slog.e(TAG, "Orientation violation detected! should be "
+ + (mStartOrientationWasLandscape ? "landscape" : "portrait")
+ + " but is the other");
+ } else {
+ Slog.v(TAG, "new bounds size: " + bounds.width() + " x " + bounds.height());
+ }
+ if (mMinVisibleWidth > bounds.width() || mMinVisibleHeight > bounds.height()) {
+ Slog.v(TAG, "Minimum requirement violated: Width(min, is)=(" + mMinVisibleWidth
+ + ", " + bounds.width() + ") Height(min,is)=("
+ + mMinVisibleHeight + ", " + bounds.height() + ")");
+ }
+ if (mMaxVisibleSize.x < bounds.width() || mMaxVisibleSize.y < bounds.height()) {
+ Slog.v(TAG, "Maximum requirement violated: Width(min, is)=(" + mMaxVisibleSize.x
+ + ", " + bounds.width() + ") Height(min,is)=("
+ + mMaxVisibleSize.y + ", " + bounds.height() + ")");
+ }
+ }
+ }
+
private void updateWindowDragBounds(int x, int y, Rect stackBounds) {
final int offsetX = Math.round(x - mStartDragX);
final int offsetY = Math.round(y - mStartDragY);
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 04cf2e3..b3d1b13 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -5726,7 +5726,8 @@
win = windowForClientLocked(null, window, false);
// win shouldn't be null here, pass it down to startPositioningLocked
// to get warning if it's null.
- if (!startPositioningLocked(win, false /*resize*/, startX, startY)) {
+ if (!startPositioningLocked(
+ win, false /*resize*/, false /*preserveOrientation*/, startX, startY)) {
return false;
}
}
@@ -5741,8 +5742,8 @@
synchronized (mWindowMap) {
final Task task = displayContent.findTaskForResizePoint(x, y);
if (task != null) {
- if (!startPositioningLocked(
- task.getTopVisibleAppMainWindow(), true /*resize*/, x, y)) {
+ if (!startPositioningLocked(task.getTopVisibleAppMainWindow(), true /*resize*/,
+ task.preserveOrientationOnResize(), x, y)) {
return;
}
taskId = task.mTaskId;
@@ -5757,10 +5758,12 @@
}
}
- private boolean startPositioningLocked(
- WindowState win, boolean resize, float startX, float startY) {
- if (DEBUG_TASK_POSITIONING) Slog.d(TAG_WM, "startPositioningLocked: "
- + "win=" + win + ", resize=" + resize + ", {" + startX + ", " + startY + "}");
+ private boolean startPositioningLocked(WindowState win, boolean resize,
+ boolean preserveOrientation, float startX, float startY) {
+ if (DEBUG_TASK_POSITIONING)
+ Slog.d(TAG_WM, "startPositioningLocked: "
+ + "win=" + win + ", resize=" + resize + ", preserveOrientation="
+ + preserveOrientation + ", {" + startX + ", " + startY + "}");
if (win == null || win.getAppToken() == null) {
Slog.w(TAG_WM, "startPositioningLocked: Bad window " + win);
@@ -5801,7 +5804,7 @@
return false;
}
- mTaskPositioner.startDragLocked(win, resize, startX, startY);
+ mTaskPositioner.startDrag(win, resize, preserveOrientation, startX, startY);
return true;
}
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 5eab795..e2027fd 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -2883,7 +2883,7 @@
mShowToOwnerOnly = showToOwnerOnly;
}
- boolean isHiddenFromUserLocked() {
+ private boolean isHiddenFromUserLocked() {
// Child windows are evaluated based on their parent window.
final WindowState win = getTopParentWindow();
if (win.mAttrs.type < FIRST_SYSTEM_WINDOW
@@ -3552,16 +3552,23 @@
/** Returns the topmost parent window if this is a child of another window, else this. */
WindowState getTopParentWindow() {
- WindowState w = this;
- while (w != null && w.mIsChildWindow) {
- w = w.getParentWindow();
+ WindowState current = this;
+ WindowState topParent = current;
+ while (current != null && current.mIsChildWindow) {
+ current = current.getParentWindow();
+ // Parent window can be null if the child is detached from it's parent already, but
+ // someone still has a reference to access it. So, we return the top parent value we
+ // already have instead of null.
+ if (current != null) {
+ topParent = current;
+ }
}
- return w;
+ return topParent;
}
boolean isParentWindowHidden() {
final WindowState parent = getParentWindow();
- return (parent == null) ? false : parent.mHidden;
+ return parent != null && parent.mHidden;
}
void setWillReplaceWindow(boolean animate) {
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 45f3698..e82e7fb 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -3093,6 +3093,7 @@
// If admin is a device or profile owner tidy that up first.
if (isDeviceOwner(adminReceiver, userHandle)) {
clearDeviceOwnerLocked(getDeviceOwnerAdminLocked(), userHandle);
+ clearDeviceOwnerUserRestrictionLocked(UserHandle.of(userHandle));
}
if (isProfileOwner(adminReceiver, userHandle)) {
final ActiveAdmin admin = getActiveAdminUncheckedLocked(adminReceiver,
@@ -3108,6 +3109,15 @@
}
}
+ // It's temporary solution to clear DISALLOW_ADD_USER after CTS
+ // TODO: b/31952368 when the restriction is moved from system to the device owner,
+ // it can be removed.
+ private void clearDeviceOwnerUserRestrictionLocked(UserHandle userHandle) {
+ if (mUserManager.hasUserRestriction(UserManager.DISALLOW_ADD_USER, userHandle)) {
+ mUserManager.setUserRestriction(UserManager.DISALLOW_ADD_USER, false, userHandle);
+ }
+ }
+
/**
* Return if a given package has testOnly="true", in which case we'll relax certain rules
* for CTS.
diff --git a/services/tests/servicestests/src/com/android/server/NetworkScoreServiceTest.java b/services/tests/servicestests/src/com/android/server/NetworkScoreServiceTest.java
new file mode 100644
index 0000000..0139671
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/NetworkScoreServiceTest.java
@@ -0,0 +1,422 @@
+/*
+ * Copyright (C) 2012 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;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertTrue;
+import static junit.framework.Assert.fail;
+
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Matchers.anyListOf;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
+
+import android.Manifest.permission;
+import android.content.ComponentName;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.content.pm.PackageManager;
+import android.content.res.Resources;
+import android.net.INetworkScoreCache;
+import android.net.NetworkKey;
+import android.net.NetworkScoreManager;
+import android.net.NetworkScorerAppManager;
+import android.net.NetworkScorerAppManager.NetworkScorerAppData;
+import android.net.ScoredNetwork;
+import android.net.WifiKey;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.provider.Settings.Global;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.MediumTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import com.android.internal.R;
+import com.android.server.devicepolicy.MockUtils;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.List;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * Tests for {@link NetworkScoreService}.
+ */
+@RunWith(AndroidJUnit4.class)
+@MediumTest
+public class NetworkScoreServiceTest {
+ private static final ScoredNetwork SCORED_NETWORK =
+ new ScoredNetwork(new NetworkKey(new WifiKey("\"ssid\"", "00:00:00:00:00:00")),
+ null /* rssiCurve*/);
+ private static final NetworkScorerAppData PREV_SCORER = new NetworkScorerAppData(
+ "prevPackageName", 0, "prevScorerName", null /* configurationActivityClassName */,
+ "prevScoringServiceClass");
+ private static final NetworkScorerAppData NEW_SCORER = new NetworkScorerAppData(
+ "newPackageName", 1, "newScorerName", null /* configurationActivityClassName */,
+ "newScoringServiceClass");
+
+ @Mock private PackageManager mPackageManager;
+ @Mock private NetworkScorerAppManager mNetworkScorerAppManager;
+ @Mock private Context mContext;
+ @Mock private Resources mResources;
+ @Mock private INetworkScoreCache.Stub mNetworkScoreCache, mNetworkScoreCache2;
+ @Mock private IBinder mIBinder, mIBinder2;
+ @Captor private ArgumentCaptor<List<ScoredNetwork>> mScoredNetworkCaptor;
+
+ private ContentResolver mContentResolver;
+ private NetworkScoreService mNetworkScoreService;
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+ when(mNetworkScoreCache.asBinder()).thenReturn(mIBinder);
+ when(mNetworkScoreCache2.asBinder()).thenReturn(mIBinder2);
+ mContentResolver = InstrumentationRegistry.getContext().getContentResolver();
+ when(mContext.getContentResolver()).thenReturn(mContentResolver);
+ when(mContext.getResources()).thenReturn(mResources);
+ mNetworkScoreService = new NetworkScoreService(mContext, mNetworkScorerAppManager);
+ }
+
+ @Test
+ public void testSystemReady_networkScorerProvisioned() throws Exception {
+ Settings.Global.putInt(mContentResolver, Global.NETWORK_SCORING_PROVISIONED, 1);
+
+ mNetworkScoreService.systemReady();
+
+ verify(mNetworkScorerAppManager, never()).setActiveScorer(anyString());
+ }
+
+ @Test
+ public void testSystemReady_networkScorerNotProvisioned_defaultScorer() throws Exception {
+ Settings.Global.putInt(mContentResolver, Global.NETWORK_SCORING_PROVISIONED, 0);
+
+ when(mResources.getString(R.string.config_defaultNetworkScorerPackageName))
+ .thenReturn(NEW_SCORER.mPackageName);
+
+ mNetworkScoreService.systemReady();
+
+ verify(mNetworkScorerAppManager).setActiveScorer(NEW_SCORER.mPackageName);
+ assertEquals(1,
+ Settings.Global.getInt(mContentResolver, Global.NETWORK_SCORING_PROVISIONED));
+
+ }
+
+ @Test
+ public void testSystemReady_networkScorerNotProvisioned_noDefaultScorer() throws Exception {
+ Settings.Global.putInt(mContentResolver, Global.NETWORK_SCORING_PROVISIONED, 0);
+
+ when(mResources.getString(R.string.config_defaultNetworkScorerPackageName))
+ .thenReturn(null);
+
+ mNetworkScoreService.systemReady();
+
+ verify(mNetworkScorerAppManager, never()).setActiveScorer(anyString());
+ assertEquals(1,
+ Settings.Global.getInt(mContentResolver, Global.NETWORK_SCORING_PROVISIONED));
+ }
+
+ @Test
+ public void testSystemRunning() {
+ when(mNetworkScorerAppManager.getActiveScorer()).thenReturn(NEW_SCORER);
+
+ mNetworkScoreService.systemRunning();
+
+ verify(mContext).bindServiceAsUser(MockUtils.checkIntent(new Intent().setComponent(
+ new ComponentName(NEW_SCORER.mPackageName, NEW_SCORER.mScoringServiceClassName))),
+ any(ServiceConnection.class),
+ eq(Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE),
+ eq(UserHandle.SYSTEM));
+ }
+
+ @Test
+ public void testUpdateScores_notActiveScorer() {
+ when(mNetworkScorerAppManager.isCallerActiveScorer(anyInt())).thenReturn(false);
+
+ try {
+ mNetworkScoreService.updateScores(new ScoredNetwork[0]);
+ fail("SecurityException expected");
+ } catch (SecurityException e) {
+ // expected
+ }
+ }
+
+ @Test
+ public void testUpdateScores_oneRegisteredCache() throws RemoteException {
+ when(mNetworkScorerAppManager.isCallerActiveScorer(anyInt())).thenReturn(true);
+
+ mNetworkScoreService.registerNetworkScoreCache(NetworkKey.TYPE_WIFI, mNetworkScoreCache);
+
+ mNetworkScoreService.updateScores(new ScoredNetwork[]{SCORED_NETWORK});
+
+ verify(mNetworkScoreCache).updateScores(mScoredNetworkCaptor.capture());
+
+ assertEquals(1, mScoredNetworkCaptor.getValue().size());
+ assertEquals(SCORED_NETWORK, mScoredNetworkCaptor.getValue().get(0));
+ }
+
+ @Test
+ public void testUpdateScores_twoRegisteredCaches() throws RemoteException {
+ when(mNetworkScorerAppManager.isCallerActiveScorer(anyInt())).thenReturn(true);
+
+ mNetworkScoreService.registerNetworkScoreCache(NetworkKey.TYPE_WIFI, mNetworkScoreCache);
+ mNetworkScoreService.registerNetworkScoreCache(
+ NetworkKey.TYPE_WIFI, mNetworkScoreCache2);
+
+ // updateScores should update both caches
+ mNetworkScoreService.updateScores(new ScoredNetwork[]{SCORED_NETWORK});
+
+ verify(mNetworkScoreCache).updateScores(anyListOf(ScoredNetwork.class));
+ verify(mNetworkScoreCache2).updateScores(anyListOf(ScoredNetwork.class));
+
+ mNetworkScoreService.unregisterNetworkScoreCache(
+ NetworkKey.TYPE_WIFI, mNetworkScoreCache2);
+
+ // updateScores should only update the first cache since the 2nd has been unregistered
+ mNetworkScoreService.updateScores(new ScoredNetwork[]{SCORED_NETWORK});
+
+ verify(mNetworkScoreCache, times(2)).updateScores(anyListOf(ScoredNetwork.class));
+
+ mNetworkScoreService.unregisterNetworkScoreCache(
+ NetworkKey.TYPE_WIFI, mNetworkScoreCache);
+
+ // updateScores should not update any caches since they are both unregistered
+ mNetworkScoreService.updateScores(new ScoredNetwork[]{SCORED_NETWORK});
+
+ verifyNoMoreInteractions(mNetworkScoreCache, mNetworkScoreCache2);
+ }
+
+ @Test
+ public void testClearScores_notActiveScorer_noBroadcastNetworkPermission() {
+ when(mNetworkScorerAppManager.isCallerActiveScorer(anyInt())).thenReturn(false);
+ when(mContext.checkCallingOrSelfPermission(permission.BROADCAST_NETWORK_PRIVILEGED))
+ .thenReturn(PackageManager.PERMISSION_DENIED);
+ try {
+ mNetworkScoreService.clearScores();
+ fail("SecurityException expected");
+ } catch (SecurityException e) {
+ // expected
+ }
+ }
+
+ @Test
+ public void testClearScores_activeScorer_noBroadcastNetworkPermission() {
+ when(mNetworkScorerAppManager.isCallerActiveScorer(anyInt())).thenReturn(true);
+ when(mContext.checkCallingOrSelfPermission(permission.BROADCAST_NETWORK_PRIVILEGED))
+ .thenReturn(PackageManager.PERMISSION_DENIED);
+
+ mNetworkScoreService.clearScores();
+ }
+
+ @Test
+ public void testClearScores_activeScorer() throws RemoteException {
+ when(mNetworkScorerAppManager.isCallerActiveScorer(anyInt())).thenReturn(true);
+
+ mNetworkScoreService.registerNetworkScoreCache(NetworkKey.TYPE_WIFI, mNetworkScoreCache);
+ mNetworkScoreService.clearScores();
+
+ verify(mNetworkScoreCache).clearScores();
+ }
+
+ @Test
+ public void testClearScores_notActiveScorer_hasBroadcastNetworkPermission()
+ throws RemoteException {
+ when(mNetworkScorerAppManager.isCallerActiveScorer(anyInt())).thenReturn(false);
+ when(mContext.checkCallingOrSelfPermission(permission.BROADCAST_NETWORK_PRIVILEGED))
+ .thenReturn(PackageManager.PERMISSION_GRANTED);
+
+ mNetworkScoreService.registerNetworkScoreCache(NetworkKey.TYPE_WIFI, mNetworkScoreCache);
+ mNetworkScoreService.clearScores();
+
+ verify(mNetworkScoreCache).clearScores();
+ }
+
+ @Test
+ public void testSetActiveScorer_noScoreNetworksPermission() {
+ doThrow(new SecurityException()).when(mContext)
+ .enforceCallingOrSelfPermission(eq(permission.SCORE_NETWORKS), anyString());
+
+ try {
+ mNetworkScoreService.setActiveScorer(null);
+ fail("SecurityException expected");
+ } catch (SecurityException e) {
+ // expected
+ }
+ }
+
+ @Test
+ public void testSetActiveScorer_failure() throws RemoteException {
+ when(mNetworkScorerAppManager.getActiveScorer()).thenReturn(PREV_SCORER);
+ when(mNetworkScorerAppManager.setActiveScorer(NEW_SCORER.mPackageName)).thenReturn(false);
+ mNetworkScoreService.registerNetworkScoreCache(NetworkKey.TYPE_WIFI, mNetworkScoreCache);
+
+ boolean success = mNetworkScoreService.setActiveScorer(NEW_SCORER.mPackageName);
+
+ assertFalse(success);
+ verify(mNetworkScoreCache).clearScores();
+ verify(mContext).bindServiceAsUser(MockUtils.checkIntent(new Intent().setComponent(
+ new ComponentName(PREV_SCORER.mPackageName, PREV_SCORER.mScoringServiceClassName))),
+ any(ServiceConnection.class),
+ eq(Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE),
+ eq(UserHandle.SYSTEM));
+ }
+
+ @Test
+ public void testSetActiveScorer_success() throws RemoteException {
+ when(mNetworkScorerAppManager.getActiveScorer()).thenReturn(PREV_SCORER, NEW_SCORER);
+ when(mNetworkScorerAppManager.setActiveScorer(NEW_SCORER.mPackageName)).thenReturn(true);
+ mNetworkScoreService.registerNetworkScoreCache(NetworkKey.TYPE_WIFI, mNetworkScoreCache);
+
+ boolean success = mNetworkScoreService.setActiveScorer(NEW_SCORER.mPackageName);
+
+ assertTrue(success);
+ verify(mNetworkScoreCache).clearScores();
+ verify(mContext).bindServiceAsUser(MockUtils.checkIntent(new Intent().setComponent(
+ new ComponentName(NEW_SCORER.mPackageName, NEW_SCORER.mScoringServiceClassName))),
+ any(ServiceConnection.class),
+ eq(Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE),
+ eq(UserHandle.SYSTEM));
+ verify(mContext, times(2)).sendBroadcastAsUser(
+ MockUtils.checkIntentAction(NetworkScoreManager.ACTION_SCORER_CHANGED),
+ eq(UserHandle.SYSTEM));
+ }
+
+ @Test
+ public void testDisableScoring_notActiveScorer_noBroadcastNetworkPermission() {
+ when(mNetworkScorerAppManager.isCallerActiveScorer(anyInt())).thenReturn(false);
+ when(mContext.checkCallingOrSelfPermission(permission.BROADCAST_NETWORK_PRIVILEGED))
+ .thenReturn(PackageManager.PERMISSION_DENIED);
+
+ try {
+ mNetworkScoreService.disableScoring();
+ fail("SecurityException expected");
+ } catch (SecurityException e) {
+ // expected
+ }
+
+ }
+
+ @Test
+ public void testDisableScoring_activeScorer() throws RemoteException {
+ when(mNetworkScorerAppManager.isCallerActiveScorer(anyInt())).thenReturn(true);
+ when(mNetworkScorerAppManager.getActiveScorer()).thenReturn(PREV_SCORER, null);
+ when(mNetworkScorerAppManager.setActiveScorer(null)).thenReturn(true);
+ mNetworkScoreService.registerNetworkScoreCache(NetworkKey.TYPE_WIFI, mNetworkScoreCache);
+
+ mNetworkScoreService.disableScoring();
+
+ verify(mNetworkScoreCache).clearScores();
+ verify(mContext).sendBroadcastAsUser(
+ MockUtils.checkIntent(new Intent(NetworkScoreManager.ACTION_SCORER_CHANGED)
+ .setPackage(PREV_SCORER.mPackageName)),
+ eq(UserHandle.SYSTEM));
+ verify(mContext, never()).bindServiceAsUser(any(Intent.class),
+ any(ServiceConnection.class), anyInt(), any(UserHandle.class));
+ }
+
+ @Test
+ public void testDisableScoring_notActiveScorer_hasBroadcastNetworkPermission()
+ throws RemoteException {
+ when(mNetworkScorerAppManager.isCallerActiveScorer(anyInt())).thenReturn(false);
+ when(mContext.checkCallingOrSelfPermission(permission.BROADCAST_NETWORK_PRIVILEGED))
+ .thenReturn(PackageManager.PERMISSION_GRANTED);
+ when(mNetworkScorerAppManager.getActiveScorer()).thenReturn(PREV_SCORER, null);
+ when(mNetworkScorerAppManager.setActiveScorer(null)).thenReturn(true);
+ mNetworkScoreService.registerNetworkScoreCache(NetworkKey.TYPE_WIFI, mNetworkScoreCache);
+
+ mNetworkScoreService.disableScoring();
+
+ verify(mNetworkScoreCache).clearScores();
+ verify(mContext).sendBroadcastAsUser(
+ MockUtils.checkIntent(new Intent(NetworkScoreManager.ACTION_SCORER_CHANGED)
+ .setPackage(PREV_SCORER.mPackageName)),
+ eq(UserHandle.SYSTEM));
+ verify(mContext, never()).bindServiceAsUser(any(Intent.class),
+ any(ServiceConnection.class), anyInt(), any(UserHandle.class));
+ }
+
+ @Test
+ public void testRegisterNetworkScoreCache_noBroadcastNetworkPermission() {
+ doThrow(new SecurityException()).when(mContext).enforceCallingOrSelfPermission(
+ eq(permission.BROADCAST_NETWORK_PRIVILEGED), anyString());
+
+ try {
+ mNetworkScoreService.registerNetworkScoreCache(
+ NetworkKey.TYPE_WIFI, mNetworkScoreCache);
+ fail("SecurityException expected");
+ } catch (SecurityException e) {
+ // expected
+ }
+ }
+
+ @Test
+ public void testUnregisterNetworkScoreCache_noBroadcastNetworkPermission() {
+ doThrow(new SecurityException()).when(mContext).enforceCallingOrSelfPermission(
+ eq(permission.BROADCAST_NETWORK_PRIVILEGED), anyString());
+
+ try {
+ mNetworkScoreService.unregisterNetworkScoreCache(
+ NetworkKey.TYPE_WIFI, mNetworkScoreCache);
+ fail("SecurityException expected");
+ } catch (SecurityException e) {
+ // expected
+ }
+ }
+
+ @Test
+ public void testDump_noDumpPermission() {
+ doThrow(new SecurityException()).when(mContext).enforceCallingOrSelfPermission(
+ eq(permission.DUMP), anyString());
+
+ try {
+ mNetworkScoreService.dump(
+ new FileDescriptor(), new PrintWriter(new StringWriter()), new String[0]);
+ fail("SecurityException expected");
+ } catch (SecurityException e) {
+ // expected
+ }
+ }
+
+ @Test
+ public void testDump_doesNotCrash() {
+ when(mNetworkScorerAppManager.getActiveScorer()).thenReturn(NEW_SCORER);
+ StringWriter stringWriter = new StringWriter();
+
+ mNetworkScoreService.dump(
+ new FileDescriptor(), new PrintWriter(stringWriter), new String[0]);
+
+ assertFalse(stringWriter.toString().isEmpty());
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/MockUtils.java b/services/tests/servicestests/src/com/android/server/devicepolicy/MockUtils.java
index 58db192..3806da6 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/MockUtils.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/MockUtils.java
@@ -82,6 +82,21 @@
return Mockito.argThat(m);
}
+ public static Intent checkIntent(final Intent intent) {
+ final Matcher<Intent> m = new BaseMatcher<Intent>() {
+ @Override
+ public boolean matches(Object item) {
+ if (item == null) return false;
+ return intent.filterEquals((Intent) item);
+ }
+ @Override
+ public void describeTo(Description description) {
+ description.appendText(intent.toString());
+ }
+ };
+ return Mockito.argThat(m);
+ }
+
public static Bundle checkUserRestrictions(String... keys) {
final Bundle expected = DpmTestUtils.newRestrictions(Preconditions.checkNotNull(keys));
final Matcher<Bundle> m = new BaseMatcher<Bundle>() {
diff --git a/services/tests/servicestests/src/com/android/server/wm/TaskPositionerTests.java b/services/tests/servicestests/src/com/android/server/wm/TaskPositionerTests.java
new file mode 100644
index 0000000..aec6dec
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/wm/TaskPositionerTests.java
@@ -0,0 +1,456 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.wm;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import android.graphics.Rect;
+import android.os.Binder;
+import android.platform.test.annotations.Presubmit;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import android.view.Display;
+
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
+import static com.android.server.wm.TaskPositioner.MIN_ASPECT;
+import static com.android.server.wm.WindowManagerService.dipToPixel;
+import static com.android.server.wm.WindowState.MINIMUM_VISIBLE_HEIGHT_IN_DP;
+import static com.android.server.wm.WindowState.MINIMUM_VISIBLE_WIDTH_IN_DP;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * Tests for the {@link TaskPositioner} class.
+ *
+ * runtest frameworks-services -c com.android.server.wm.TaskPositionerTests
+ */
+@SmallTest
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class TaskPositionerTests extends WindowTestsBase {
+
+ private final boolean DEBUGGING = false;
+ private final String TAG = "TaskPositionerTest";
+
+ private final static int MOUSE_DELTA_X = 5;
+ private final static int MOUSE_DELTA_Y = 5;
+
+ private int mMinVisibleWidth;
+ private int mMinVisibleHeight;
+ private TaskPositioner mPositioner;
+
+ @Before
+ public void setUp() throws Exception {
+ super.setUp();
+ final Display display = sDisplayContent.getDisplay();
+ final DisplayMetrics dm = new DisplayMetrics();
+ display.getMetrics(dm);
+
+ // This should be the same calculation as the TaskPositioner uses.
+ mMinVisibleWidth = dipToPixel(MINIMUM_VISIBLE_WIDTH_IN_DP, dm);
+ mMinVisibleHeight = dipToPixel(MINIMUM_VISIBLE_HEIGHT_IN_DP, dm);
+
+ mPositioner = new TaskPositioner(sWm);
+ mPositioner.register(display);
+ }
+
+ /**
+ * This tests that free resizing will allow to change the orientation as well
+ * as does some basic tests (e.g. dragging in Y only will keep X stable).
+ */
+ @Test
+ public void testBasicFreeWindowResizing() throws Exception {
+ final Rect r = new Rect(100, 220, 700, 520);
+ final int midY = (r.top + r.bottom) / 2;
+
+ // Start a drag resize starting upper left.
+ mPositioner.startDrag(true /*resizing*/,
+ false /*preserveOrientation*/, r.left - MOUSE_DELTA_X, r.top - MOUSE_DELTA_Y, r);
+ assertBoundsEquals(r, mPositioner.getWindowDragBounds());
+
+ // Drag to a good landscape size.
+ mPositioner.resizeDrag(0.0f, 0.0f);
+ assertBoundsEquals(new Rect(MOUSE_DELTA_X, MOUSE_DELTA_Y, r.right, r.bottom),
+ mPositioner.getWindowDragBounds());
+
+ // Drag to a good portrait size.
+ mPositioner.resizeDrag(400.0f, 0.0f);
+ assertBoundsEquals(new Rect(400 + MOUSE_DELTA_X, MOUSE_DELTA_Y, r.right, r.bottom),
+ mPositioner.getWindowDragBounds());
+
+ // Drag to a too small size for the width.
+ mPositioner.resizeDrag(2000.0f, r.top);
+ assertBoundsEquals(
+ new Rect(r.right - mMinVisibleWidth, r.top + MOUSE_DELTA_Y, r.right, r.bottom),
+ mPositioner.getWindowDragBounds());
+
+ // Drag to a too small size for the height.
+ mPositioner.resizeDrag(r.left, 2000.0f);
+ assertBoundsEquals(
+ new Rect(r.left + MOUSE_DELTA_X, r.bottom - mMinVisibleHeight, r.right, r.bottom),
+ mPositioner.getWindowDragBounds());
+
+ // Start a drag resize left and see that only the left coord changes..
+ mPositioner.startDrag(true /*resizing*/,
+ false /*preserveOrientation*/, r.left - MOUSE_DELTA_X, midY, r);
+
+ // Drag to the left.
+ mPositioner.resizeDrag(0.0f, midY);
+ assertBoundsEquals(new Rect(MOUSE_DELTA_X, r.top, r.right, r.bottom),
+ mPositioner.getWindowDragBounds());
+
+ // Drag to the right.
+ mPositioner.resizeDrag(200.0f, midY);
+ assertBoundsEquals(new Rect(200 + MOUSE_DELTA_X, r.top, r.right, r.bottom),
+ mPositioner.getWindowDragBounds());
+
+ // Drag to the top
+ mPositioner.resizeDrag(r.left, 0.0f);
+ assertBoundsEquals(new Rect(r.left + MOUSE_DELTA_X, r.top, r.right, r.bottom),
+ mPositioner.getWindowDragBounds());
+
+ // Drag to the bottom
+ mPositioner.resizeDrag(r.left, 1000.0f);
+ assertBoundsEquals(new Rect(r.left + MOUSE_DELTA_X, r.top, r.right, r.bottom),
+ mPositioner.getWindowDragBounds());
+ }
+
+ /**
+ * This tests that by dragging any edge, the fixed / opposite edge(s) remains anchored.
+ */
+ @Test
+ public void testFreeWindowResizingTestAllEdges() throws Exception {
+ final Rect r = new Rect(100, 220, 700, 520);
+ final int midX = (r.left + r.right) / 2;
+ final int midY = (r.top + r.bottom) / 2;
+
+ // Drag upper left.
+ mPositioner.startDrag(true /*resizing*/,
+ false /*preserveOrientation*/, r.left - MOUSE_DELTA_X, r.top - MOUSE_DELTA_Y, r);
+ mPositioner.resizeDrag(0.0f, 0.0f);
+ assertTrue(r.left != mPositioner.getWindowDragBounds().left);
+ assertEquals(r.right, mPositioner.getWindowDragBounds().right);
+ assertTrue(r.top != mPositioner.getWindowDragBounds().top);
+ assertEquals(r.bottom, mPositioner.getWindowDragBounds().bottom);
+
+ // Drag upper.
+ mPositioner.startDrag(true /*resizing*/,
+ false /*preserveOrientation*/, midX, r.top - MOUSE_DELTA_Y, r);
+ mPositioner.resizeDrag(0.0f, 0.0f);
+ assertEquals(r.left, mPositioner.getWindowDragBounds().left);
+ assertEquals(r.right, mPositioner.getWindowDragBounds().right);
+ assertTrue(r.top != mPositioner.getWindowDragBounds().top);
+ assertEquals(r.bottom, mPositioner.getWindowDragBounds().bottom);
+
+ // Drag upper right.
+ mPositioner.startDrag(true /*resizing*/,
+ false /*preserveOrientation*/, r.right + MOUSE_DELTA_X, r.top - MOUSE_DELTA_Y, r);
+ mPositioner.resizeDrag(r.right + 100, 0.0f);
+ assertEquals(r.left, mPositioner.getWindowDragBounds().left);
+ assertTrue(r.right != mPositioner.getWindowDragBounds().right);
+ assertTrue(r.top != mPositioner.getWindowDragBounds().top);
+ assertEquals(r.bottom, mPositioner.getWindowDragBounds().bottom);
+
+ // Drag right.
+ mPositioner.startDrag(true /*resizing*/,
+ false /*preserveOrientation*/, r.right + MOUSE_DELTA_X, midY, r);
+ mPositioner.resizeDrag(r.right + 100, 0.0f);
+ assertEquals(r.left, mPositioner.getWindowDragBounds().left);
+ assertTrue(r.right != mPositioner.getWindowDragBounds().right);
+ assertEquals(r.top, mPositioner.getWindowDragBounds().top);
+ assertEquals(r.bottom, mPositioner.getWindowDragBounds().bottom);
+
+ // Drag bottom right.
+ mPositioner.startDrag(true /*resizing*/,
+ false /*preserveOrientation*/,
+ r.right + MOUSE_DELTA_X, r.bottom + MOUSE_DELTA_Y, r);
+ mPositioner.resizeDrag(r.right + 100, r.bottom + 100);
+ assertEquals(r.left, mPositioner.getWindowDragBounds().left);
+ assertTrue(r.right != mPositioner.getWindowDragBounds().right);
+ assertEquals(r.top, mPositioner.getWindowDragBounds().top);
+ assertTrue(r.bottom != mPositioner.getWindowDragBounds().bottom);
+
+ // Drag bottom.
+ mPositioner.startDrag(true /*resizing*/,
+ false /*preserveOrientation*/, midX, r.bottom + MOUSE_DELTA_Y, r);
+ mPositioner.resizeDrag(r.right + 100, r.bottom + 100);
+ assertEquals(r.left, mPositioner.getWindowDragBounds().left);
+ assertEquals(r.right, mPositioner.getWindowDragBounds().right);
+ assertEquals(r.top, mPositioner.getWindowDragBounds().top);
+ assertTrue(r.bottom != mPositioner.getWindowDragBounds().bottom);
+
+ // Drag bottom left.
+ mPositioner.startDrag(true /*resizing*/,
+ false /*preserveOrientation*/, r.left - MOUSE_DELTA_X, r.bottom + MOUSE_DELTA_Y, r);
+ mPositioner.resizeDrag(0.0f, r.bottom + 100);
+ assertTrue(r.left != mPositioner.getWindowDragBounds().left);
+ assertEquals(r.right, mPositioner.getWindowDragBounds().right);
+ assertEquals(r.top, mPositioner.getWindowDragBounds().top);
+ assertTrue(r.bottom != mPositioner.getWindowDragBounds().bottom);
+
+ // Drag left.
+ mPositioner.startDrag(true /*resizing*/,
+ false /*preserveOrientation*/, r.left - MOUSE_DELTA_X, midX, r);
+ mPositioner.resizeDrag(0.0f, r.bottom + 100);
+ assertTrue(r.left != mPositioner.getWindowDragBounds().left);
+ assertEquals(r.right, mPositioner.getWindowDragBounds().right);
+ assertEquals(r.top, mPositioner.getWindowDragBounds().top);
+ assertEquals(r.bottom, mPositioner.getWindowDragBounds().bottom);
+ }
+
+ /**
+ * This tests that a constrained landscape window will keep the aspect and do the
+ * right things upon resizing when dragged from the top left corner.
+ */
+ @Test
+ public void testLandscapePreservedWindowResizingDragTopLeft() throws Exception {
+ final Rect r = new Rect(100, 220, 700, 520);
+
+ mPositioner.startDrag(true /*resizing*/,
+ true /*preserveOrientation*/, r.left - MOUSE_DELTA_X, r.top - MOUSE_DELTA_Y, r);
+ assertBoundsEquals(r, mPositioner.getWindowDragBounds());
+
+ // Drag to a good landscape size.
+ mPositioner.resizeDrag(0.0f, 0.0f);
+ assertBoundsEquals(new Rect(MOUSE_DELTA_X, MOUSE_DELTA_Y, r.right, r.bottom),
+ mPositioner.getWindowDragBounds());
+
+ // Drag to a good portrait size.
+ mPositioner.resizeDrag(400.0f, 0.0f);
+ int width = Math.round((float) (r.bottom - MOUSE_DELTA_Y) * MIN_ASPECT);
+ assertBoundsEquals(new Rect(r.right - width, MOUSE_DELTA_Y, r.right, r.bottom),
+ mPositioner.getWindowDragBounds());
+
+ // Drag to a too small size for the width.
+ mPositioner.resizeDrag(2000.0f, r.top);
+ final int w = mMinVisibleWidth;
+ final int h = Math.round(w / MIN_ASPECT);
+ assertBoundsEquals(new Rect(r.right - w, r.bottom - h, r.right, r.bottom),
+ mPositioner.getWindowDragBounds());
+
+ // Drag to a too small size for the height.
+ mPositioner.resizeDrag(r.left, 2000.0f);
+ assertBoundsEquals(
+ new Rect(r.left + MOUSE_DELTA_X, r.bottom - mMinVisibleHeight, r.right, r.bottom),
+ mPositioner.getWindowDragBounds());
+ }
+
+ /**
+ * This tests that a constrained landscape window will keep the aspect and do the
+ * right things upon resizing when dragged from the left corner.
+ */
+ @Test
+ public void testLandscapePreservedWindowResizingDragLeft() throws Exception {
+ final Rect r = new Rect(100, 220, 700, 520);
+ final int midY = (r.top + r.bottom) / 2;
+
+ mPositioner.startDrag(true /*resizing*/,
+ true /*preserveOrientation*/, r.left - MOUSE_DELTA_X, midY, r);
+
+ // Drag to the left.
+ mPositioner.resizeDrag(0.0f, midY);
+ assertBoundsEquals(new Rect(MOUSE_DELTA_X, r.top, r.right, r.bottom),
+ mPositioner.getWindowDragBounds());
+
+ // Drag to the right.
+ mPositioner.resizeDrag(200.0f, midY);
+ assertBoundsEquals(new Rect(200 + MOUSE_DELTA_X, r.top, r.right, r.bottom),
+ mPositioner.getWindowDragBounds());
+
+ // Drag all the way to the right and see the height also shrinking.
+ mPositioner.resizeDrag(2000.0f, midY);
+ final int w = mMinVisibleWidth;
+ final int h = Math.round((float)w / MIN_ASPECT);
+ assertBoundsEquals(new Rect(r.right - w, r.top, r.right, r.top + h),
+ mPositioner.getWindowDragBounds());
+
+ // Drag to the top.
+ mPositioner.resizeDrag(r.left, 0.0f);
+ assertBoundsEquals(new Rect(r.left + MOUSE_DELTA_X, r.top, r.right, r.bottom),
+ mPositioner.getWindowDragBounds());
+
+ // Drag to the bottom.
+ mPositioner.resizeDrag(r.left, 1000.0f);
+ assertBoundsEquals(new Rect(r.left + MOUSE_DELTA_X, r.top, r.right, r.bottom),
+ mPositioner.getWindowDragBounds());
+ }
+
+ /**
+ * This tests that a constrained landscape window will keep the aspect and do the
+ * right things upon resizing when dragged from the top corner.
+ */
+ @Test
+ public void testLandscapePreservedWindowResizingDragTop() throws Exception {
+ final Rect r = new Rect(100, 220, 700, 520);
+ final int midX = (r.left + r.right) / 2;
+
+ mPositioner.startDrag(true /*resizing*/,
+ true /*preserveOrientation*/, midX, r.top - MOUSE_DELTA_Y, r);
+
+ // Drag to the left (no change).
+ mPositioner.resizeDrag(0.0f, r.top);
+ assertBoundsEquals(new Rect(r.left, r.top + MOUSE_DELTA_Y, r.right, r.bottom),
+ mPositioner.getWindowDragBounds());
+
+ // Drag to the right (no change).
+ mPositioner.resizeDrag(2000.0f, r.top);
+ assertBoundsEquals(new Rect(r.left , r.top + MOUSE_DELTA_Y, r.right, r.bottom),
+ mPositioner.getWindowDragBounds());
+
+ // Drag to the top.
+ mPositioner.resizeDrag(300.0f, 0.0f);
+ int h = r.bottom - MOUSE_DELTA_Y;
+ int w = Math.max(r.right - r.left, Math.round(h * MIN_ASPECT));
+ assertBoundsEquals(new Rect(r.left, MOUSE_DELTA_Y, r.left + w, r.bottom),
+ mPositioner.getWindowDragBounds());
+
+ // Drag to the bottom.
+ mPositioner.resizeDrag(r.left, 1000.0f);
+ h = mMinVisibleHeight;
+ assertBoundsEquals(new Rect(r.left, r.bottom - h, r.right, r.bottom),
+ mPositioner.getWindowDragBounds());
+ }
+
+ /**
+ * This tests that a constrained portrait window will keep the aspect and do the
+ * right things upon resizing when dragged from the top left corner.
+ */
+ @Test
+ public void testPortraitPreservedWindowResizingDragTopLeft() throws Exception {
+ final Rect r = new Rect(330, 100, 630, 600);
+
+ mPositioner.startDrag(true /*resizing*/,
+ true /*preserveOrientation*/, r.left - MOUSE_DELTA_X, r.top - MOUSE_DELTA_Y, r);
+ assertBoundsEquals(r, mPositioner.getWindowDragBounds());
+
+ // Drag to a good landscape size.
+ mPositioner.resizeDrag(0.0f, 0.0f);
+ int height = Math.round((float) (r.right - MOUSE_DELTA_X) * MIN_ASPECT);
+ assertBoundsEquals(new Rect(MOUSE_DELTA_X, r.bottom - height, r.right, r.bottom),
+ mPositioner.getWindowDragBounds());
+
+ // Drag to a good portrait size.
+ mPositioner.resizeDrag(500.0f, 0.0f);
+ assertBoundsEquals(new Rect(500 + MOUSE_DELTA_X, MOUSE_DELTA_Y, r.right, r.bottom),
+ mPositioner.getWindowDragBounds());
+
+ // Drag to a too small size for the height and the the width shrinking.
+ mPositioner.resizeDrag(r.left + MOUSE_DELTA_X, 2000.0f);
+ final int w = Math.max(mMinVisibleWidth, Math.round(mMinVisibleHeight / MIN_ASPECT));
+ final int h = Math.max(mMinVisibleHeight, Math.round(w * MIN_ASPECT));
+ assertBoundsEquals(
+ new Rect(r.right - w, r.bottom - h, r.right, r.bottom),
+ mPositioner.getWindowDragBounds());
+ }
+
+ /**
+ * This tests that a constrained portrait window will keep the aspect and do the
+ * right things upon resizing when dragged from the left corner.
+ */
+ @Test
+ public void testPortraitPreservedWindowResizingDragLeft() throws Exception {
+ final Rect r = new Rect(330, 100, 630, 600);
+ final int midY = (r.top + r.bottom) / 2;
+
+ mPositioner.startDrag(true /*resizing*/,
+ true /*preserveOrientation*/, r.left - MOUSE_DELTA_X, midY, r);
+
+ // Drag to the left.
+ mPositioner.resizeDrag(0.0f, midY);
+ int w = r.right - MOUSE_DELTA_X;
+ int h = Math.round(w * MIN_ASPECT);
+ assertBoundsEquals(new Rect(MOUSE_DELTA_X, r.top, r.right, r.top + h),
+ mPositioner.getWindowDragBounds());
+
+ // Drag to the right.
+ mPositioner.resizeDrag(450.0f, midY);
+ assertBoundsEquals(new Rect(450 + MOUSE_DELTA_X, r.top, r.right, r.bottom),
+ mPositioner.getWindowDragBounds());
+
+ // Drag all the way to the right.
+ mPositioner.resizeDrag(2000.0f, midY);
+ w = mMinVisibleWidth;
+ h = Math.max(Math.round((float)w * MIN_ASPECT), r.height());
+ assertBoundsEquals(new Rect(r.right - w, r.top, r.right, r.top + h),
+ mPositioner.getWindowDragBounds());
+
+ // Drag to the top.
+ mPositioner.resizeDrag(r.left, 0.0f);
+ assertBoundsEquals(new Rect(r.left + MOUSE_DELTA_X, r.top, r.right, r.bottom),
+ mPositioner.getWindowDragBounds());
+
+ // Drag to the bottom.
+ mPositioner.resizeDrag(r.left, 1000.0f);
+ assertBoundsEquals(new Rect(r.left + MOUSE_DELTA_X, r.top, r.right, r.bottom),
+ mPositioner.getWindowDragBounds());
+ }
+
+ /**
+ * This tests that a constrained portrait window will keep the aspect and do the
+ * right things upon resizing when dragged from the top corner.
+ */
+ @Test
+ public void testPortraitPreservedWindowResizingDragTop() throws Exception {
+ final Rect r = new Rect(330, 100, 630, 600);
+ final int midX = (r.left + r.right) / 2;
+
+ mPositioner.startDrag(true /*resizing*/,
+ true /*preserveOrientation*/, midX, r.top - MOUSE_DELTA_Y, r);
+
+ // Drag to the left (no change).
+ mPositioner.resizeDrag(0.0f, r.top);
+ assertBoundsEquals(new Rect(r.left, r.top + MOUSE_DELTA_Y, r.right, r.bottom),
+ mPositioner.getWindowDragBounds());
+
+ // Drag to the right (no change).
+ mPositioner.resizeDrag(2000.0f, r.top);
+ assertBoundsEquals(new Rect(r.left , r.top + MOUSE_DELTA_Y, r.right, r.bottom),
+ mPositioner.getWindowDragBounds());
+
+ // Drag to the top.
+ mPositioner.resizeDrag(300.0f, 0.0f);
+ int h = r.bottom - MOUSE_DELTA_Y;
+ int w = Math.min(r.width(), Math.round(h / MIN_ASPECT));
+ assertBoundsEquals(new Rect(r.left, MOUSE_DELTA_Y, r.left + w, r.bottom),
+ mPositioner.getWindowDragBounds());
+
+ // Drag to the bottom.
+ mPositioner.resizeDrag(r.left, 1000.0f);
+ h = Math.max(mMinVisibleHeight, Math.round(mMinVisibleWidth * MIN_ASPECT));
+ w = Math.round(h / MIN_ASPECT);
+ assertBoundsEquals(new Rect(r.left, r.bottom - h, r.left + w, r.bottom),
+ mPositioner.getWindowDragBounds());
+ }
+
+ private void assertBoundsEquals(Rect expected, Rect actual) {
+ if (DEBUGGING) {
+ if (!expected.equals(actual)) {
+ Log.e(TAG, "rect(" + actual.toString() + ") != isRect(" + actual.toString()
+ + ") " + Log.getStackTraceString(new Throwable()));
+ }
+ }
+ assertEquals(expected.left, actual.left);
+ assertEquals(expected.right, actual.right);
+ assertEquals(expected.top, actual.top);
+ assertEquals(expected.bottom, actual.bottom);
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/wm/WindowStateTests.java b/services/tests/servicestests/src/com/android/server/wm/WindowStateTests.java
index 69bfc8f..df35b7ee 100644
--- a/services/tests/servicestests/src/com/android/server/wm/WindowStateTests.java
+++ b/services/tests/servicestests/src/com/android/server/wm/WindowStateTests.java
@@ -116,6 +116,11 @@
assertEquals(root, child1.getTopParentWindow());
assertEquals(child1, child2.getParentWindow());
assertEquals(root, child2.getTopParentWindow());
+
+ // Test case were child is detached from parent.
+ root.removeChild(child1);
+ assertEquals(child1, child1.getTopParentWindow());
+ assertEquals(child1, child2.getParentWindow());
}
@Test
diff --git a/telecomm/java/android/telecom/Conference.java b/telecomm/java/android/telecom/Conference.java
index a012082..177759e 100644
--- a/telecomm/java/android/telecom/Conference.java
+++ b/telecomm/java/android/telecom/Conference.java
@@ -28,6 +28,7 @@
import java.util.Collections;
import java.util.List;
import java.util.Locale;
+import java.util.Objects;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.CopyOnWriteArraySet;
@@ -453,6 +454,10 @@
* @param conferenceableConnections The set of connections this connection can conference with.
*/
public final void setConferenceableConnections(List<Connection> conferenceableConnections) {
+ if (Objects.equals(mConferenceableConnections, conferenceableConnections)) {
+ return;
+ }
+
clearConferenceableList();
for (Connection c : conferenceableConnections) {
// If statement checks for duplicates in input. It makes it N^2 but we're dealing with a
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index a0f5217..a652a0a 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -658,6 +658,24 @@
public static final String KEY_SUPPORT_CONFERENCE_CALL_BOOL = "support_conference_call_bool";
/**
+ * Determines whether a maximum size limit for IMS conference calls is enforced on the device.
+ * When {@code true}, IMS conference calls will be limited to at most
+ * {@link #KEY_IMS_CONFERENCE_SIZE_LIMIT_INT} participants. When {@code false}, no attempt is made
+ * to limit the number of participants in a conference (the carrier will raise an error when an
+ * attempt is made to merge too many participants into a conference).
+ */
+ public static final String KEY_IS_IMS_CONFERENCE_SIZE_ENFORCED_BOOL =
+ "is_ims_conference_size_enforced_bool";
+
+ /**
+ * Determines the maximum number of participants the carrier supports for a conference call.
+ * This number is exclusive of the current device. A conference between 3 devices, for example,
+ * would have a size limit of 2 participants.
+ * Enforced when {@link #KEY_IS_IMS_CONFERENCE_SIZE_ENFORCED_BOOL} is {@code true}.
+ */
+ public static final String KEY_IMS_CONFERENCE_SIZE_LIMIT_INT = "ims_conference_size_limit_int";
+
+ /**
* Determines whether High Definition audio property is displayed in the dialer UI.
* If {@code false}, remove the HD audio property from the connection so that HD audio related
* UI is not displayed. If {@code true}, keep HD audio property as it is configured.
@@ -1232,6 +1250,8 @@
sDefaults.putInt(KEY_CDMA_3WAYCALL_FLASH_DELAY_INT , 0);
sDefaults.putBoolean(KEY_SUPPORT_CONFERENCE_CALL_BOOL, true);
sDefaults.putBoolean(KEY_SUPPORT_VIDEO_CONFERENCE_CALL_BOOL, false);
+ sDefaults.putBoolean(KEY_IS_IMS_CONFERENCE_SIZE_ENFORCED_BOOL, false);
+ sDefaults.putInt(KEY_IMS_CONFERENCE_SIZE_LIMIT_INT, 5);
sDefaults.putBoolean(KEY_DISPLAY_HD_AUDIO_PROPERTY_BOOL, true);
sDefaults.putBoolean(KEY_EDITABLE_ENHANCED_4G_LTE_BOOL, true);
sDefaults.putBoolean(KEY_HIDE_IMS_APN_BOOL, false);
diff --git a/tools/layoutlib/bridge/src/android/view/SurfaceView.java b/tools/layoutlib/bridge/src/android/view/SurfaceView.java
index 1e7dfbe..ebb2af4 100644
--- a/tools/layoutlib/bridge/src/android/view/SurfaceView.java
+++ b/tools/layoutlib/bridge/src/android/view/SurfaceView.java
@@ -21,6 +21,7 @@
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Rect;
+import android.graphics.Region;
import android.util.AttributeSet;
/**
@@ -49,6 +50,19 @@
super(context, attrs, defStyleAttr, defStyleRes);
}
+ public boolean gatherTransparentRegion(Region region) {
+ return false;
+ }
+
+ public void setZOrderMediaOverlay(boolean isMediaOverlay) {
+ }
+
+ public void setZOrderOnTop(boolean onTop) {
+ }
+
+ public void setSecure(boolean isSecure) {
+ }
+
public SurfaceHolder getHolder() {
return mSurfaceHolder;
}
diff --git a/tools/streaming_proto/main.cpp b/tools/streaming_proto/main.cpp
index 5435728..5b4ba04 100644
--- a/tools/streaming_proto/main.cpp
+++ b/tools/streaming_proto/main.cpp
@@ -94,7 +94,7 @@
* Figure out the name of the file we are generating.
*/
static string
-make_file_name(const FileDescriptorProto& file_descriptor)
+make_file_name(const FileDescriptorProto& file_descriptor, const string& class_name)
{
string const package = make_java_package(file_descriptor);
string result;
@@ -103,7 +103,7 @@
result += '/';
}
- result += make_outer_class_name(file_descriptor);
+ result += class_name;
result += ".java";
return result;
@@ -320,10 +320,16 @@
/**
* Write the contents of a file.
+ *
+ * If there are enums and generate_outer is false, invalid java code will be generated.
*/
static void
-write_file(stringstream& text, const FileDescriptorProto& file_descriptor)
+write_file(CodeGeneratorResponse* response, const FileDescriptorProto& file_descriptor,
+ const string& filename, bool generate_outer,
+ const vector<EnumDescriptorProto>& enums, const vector<DescriptorProto>& messages)
{
+ stringstream text;
+
string const package_name = make_java_package(file_descriptor);
string const outer_class_name = make_outer_class_name(file_descriptor);
@@ -338,27 +344,92 @@
}
// This bit of policy is android api rules specific: Raw proto classes
- // must never be in the API, but they should all be available for testing.
+ // must never be in the API
text << "/** @hide */" << endl;
- text << "@android.annotation.TestApi" << endl;
+// text << "@android.annotation.TestApi" << endl;
- text << "public final class " << outer_class_name << " {" << endl;
- text << endl;
+ if (generate_outer) {
+ text << "public final class " << outer_class_name << " {" << endl;
+ text << endl;
+ }
- int N;
- const string indented = indent_more("");
+ size_t N;
+ const string indented = generate_outer ? indent_more("") : string();
+ N = enums.size();
+ for (size_t i=0; i<N; i++) {
+ write_enum(text, enums[i], indented);
+ }
+
+ N = messages.size();
+ for (size_t i=0; i<N; i++) {
+ write_message(text, messages[i], indented);
+ }
+
+ if (generate_outer) {
+ text << "}" << endl;
+ }
+
+ CodeGeneratorResponse::File* file_response = response->add_file();
+ file_response->set_name(filename);
+ file_response->set_content(text.str());
+}
+
+/**
+ * Write one file per class. Put all of the enums into the "outer" class.
+ */
+static void
+write_multiple_files(CodeGeneratorResponse* response, const FileDescriptorProto& file_descriptor)
+{
+ // If there is anything to put in the outer class file, create one
+ if (file_descriptor.enum_type_size() > 0) {
+ vector<EnumDescriptorProto> enums;
+ int N = file_descriptor.enum_type_size();
+ for (int i=0; i<N; i++) {
+ enums.push_back(file_descriptor.enum_type(i));
+ }
+
+ vector<DescriptorProto> messages;
+
+ write_file(response, file_descriptor,
+ make_file_name(file_descriptor, make_outer_class_name(file_descriptor)),
+ true, enums, messages);
+ }
+
+ // For each of the message types, make a file
+ int N = file_descriptor.message_type_size();
+ for (int i=0; i<N; i++) {
+ vector<EnumDescriptorProto> enums;
+
+ vector<DescriptorProto> messages;
+ messages.push_back(file_descriptor.message_type(i));
+
+ write_file(response, file_descriptor,
+ make_file_name(file_descriptor, file_descriptor.message_type(i).name()),
+ false, enums, messages);
+ }
+}
+
+static void
+write_single_file(CodeGeneratorResponse* response, const FileDescriptorProto& file_descriptor)
+{
+ int N;
+
+ vector<EnumDescriptorProto> enums;
N = file_descriptor.enum_type_size();
for (int i=0; i<N; i++) {
- write_enum(text, file_descriptor.enum_type(i), indented);
+ enums.push_back(file_descriptor.enum_type(i));
}
+ vector<DescriptorProto> messages;
N = file_descriptor.message_type_size();
for (int i=0; i<N; i++) {
- write_message(text, file_descriptor.message_type(i), indented);
+ messages.push_back(file_descriptor.message_type(i));
}
- text << "}" << endl;
+ write_file(response, file_descriptor,
+ make_file_name(file_descriptor, make_outer_class_name(file_descriptor)),
+ true, enums, messages);
}
/**
@@ -383,16 +454,11 @@
for (int i=0; i<N; i++) {
const FileDescriptorProto& file_descriptor = request.proto_file(i);
if (should_generate_for_file(request, file_descriptor.name())) {
- // Generate the text
- stringstream text;
- write_file(text, file_descriptor);
-
- // Put the text in the response
- CodeGeneratorResponse::File* file_response = response.add_file();
- file_response->set_name(make_file_name(file_descriptor));
- file_response->set_content(text.str());
-
- cerr << "writing file: " << file_response->name() << endl;
+ if (file_descriptor.options().java_multiple_files()) {
+ write_multiple_files(&response, file_descriptor);
+ } else {
+ write_single_file(&response, file_descriptor);
+ }
}
}