diff options
59 files changed, 2219 insertions, 609 deletions
@@ -320,6 +320,7 @@ LOCAL_SRC_FILES += \ wifi/java/android/net/wifi/IWifiManager.aidl \ wifi/java/android/net/wifi/hotspot/IWifiHotspotManager.aidl \ wifi/java/android/net/wifi/p2p/IWifiP2pManager.aidl \ + wifi/java/android/net/wifi/IWifiScanner.aidl \ packages/services/PacProcessor/com/android/net/IProxyService.aidl \ packages/services/Proxy/com/android/net/IProxyCallback.aidl \ packages/services/Proxy/com/android/net/IProxyPortListener.aidl \ diff --git a/api/current.txt b/api/current.txt index 79f8e10..302e77e 100644 --- a/api/current.txt +++ b/api/current.txt @@ -4438,6 +4438,7 @@ package android.app { field public static final java.lang.String CATEGORY_STATUS = "status"; field public static final java.lang.String CATEGORY_SYSTEM = "sys"; field public static final java.lang.String CATEGORY_TRANSPORT = "transport"; + field public static final int COLOR_DEFAULT = 0; // 0x0 field public static final android.os.Parcelable.Creator CREATOR; field public static final int DEFAULT_ALL = -1; // 0xffffffff field public static final int DEFAULT_LIGHTS = 4; // 0x4 @@ -4483,6 +4484,7 @@ package android.app { field public int audioStreamType; field public android.widget.RemoteViews bigContentView; field public java.lang.String category; + field public int color; field public android.app.PendingIntent contentIntent; field public android.widget.RemoteViews contentView; field public int defaults; @@ -4545,6 +4547,7 @@ package android.app { method public deprecated android.app.Notification getNotification(); method public android.app.Notification.Builder setAutoCancel(boolean); method public android.app.Notification.Builder setCategory(java.lang.String); + method public android.app.Notification.Builder setColor(int); method public android.app.Notification.Builder setContent(android.widget.RemoteViews); method public android.app.Notification.Builder setContentInfo(java.lang.CharSequence); method public android.app.Notification.Builder setContentIntent(android.app.PendingIntent); @@ -5027,6 +5030,7 @@ package android.app.admin { method public void clearUserRestriction(android.content.ComponentName, java.lang.String); method public void enableSystemApp(android.content.ComponentName, java.lang.String); method public int enableSystemApp(android.content.ComponentName, android.content.Intent); + method public java.lang.String[] getAccountTypesWithManagementDisabled(); method public java.util.List<android.content.ComponentName> getActiveAdmins(); method public android.os.Bundle getApplicationRestrictions(android.content.ComponentName, java.lang.String); method public boolean getCameraDisabled(android.content.ComponentName); @@ -5056,6 +5060,7 @@ package android.app.admin { method public void lockNow(); method public void removeActiveAdmin(android.content.ComponentName); method public boolean resetPassword(java.lang.String, int); + method public void setAccountManagementDisabled(android.content.ComponentName, java.lang.String, boolean); method public void setApplicationRestrictions(android.content.ComponentName, java.lang.String, android.os.Bundle); method public void setCameraDisabled(android.content.ComponentName, boolean); method public void setKeyguardDisabledFeatures(android.content.ComponentName, int); @@ -39827,11 +39832,13 @@ package java.net { method public java.lang.String getValue(); method public int getVersion(); method public boolean hasExpired(); + method public boolean isHttpOnly(); method public static java.util.List<java.net.HttpCookie> parse(java.lang.String); method public void setComment(java.lang.String); method public void setCommentURL(java.lang.String); method public void setDiscard(boolean); method public void setDomain(java.lang.String); + method public void setHttpOnly(boolean); method public void setMaxAge(long); method public void setPath(java.lang.String); method public void setPortlist(java.lang.String); @@ -40784,7 +40791,7 @@ package java.nio.channels { ctor public ConnectionPendingException(); } - public abstract class DatagramChannel extends java.nio.channels.spi.AbstractSelectableChannel implements java.nio.channels.ByteChannel java.nio.channels.GatheringByteChannel java.nio.channels.ScatteringByteChannel { + public abstract class DatagramChannel extends java.nio.channels.spi.AbstractSelectableChannel implements java.nio.channels.ByteChannel java.nio.channels.GatheringByteChannel java.nio.channels.MulticastChannel java.nio.channels.ScatteringByteChannel { ctor protected DatagramChannel(java.nio.channels.spi.SelectorProvider); method public java.nio.channels.DatagramChannel bind(java.net.SocketAddress) throws java.io.IOException; method public abstract java.nio.channels.DatagramChannel connect(java.net.SocketAddress) throws java.io.IOException; @@ -40792,6 +40799,8 @@ package java.nio.channels { method public java.net.SocketAddress getLocalAddress() throws java.io.IOException; method public T getOption(java.net.SocketOption<T>) throws java.io.IOException; method public abstract boolean isConnected(); + method public java.nio.channels.MembershipKey join(java.net.InetAddress, java.net.NetworkInterface) throws java.io.IOException; + method public java.nio.channels.MembershipKey join(java.net.InetAddress, java.net.NetworkInterface, java.net.InetAddress) throws java.io.IOException; method public static java.nio.channels.DatagramChannel open() throws java.io.IOException; method public abstract int read(java.nio.ByteBuffer) throws java.io.IOException; method public abstract long read(java.nio.ByteBuffer[], int, int) throws java.io.IOException; @@ -40839,6 +40848,7 @@ package java.nio.channels { public abstract class FileLock implements java.lang.AutoCloseable { ctor protected FileLock(java.nio.channels.FileChannel, long, long, boolean); + method public java.nio.channels.Channel acquiredBy(); method public final java.nio.channels.FileChannel channel(); method public final void close() throws java.io.IOException; method public final boolean isShared(); @@ -40871,6 +40881,24 @@ package java.nio.channels { method public abstract void close() throws java.io.IOException; } + public abstract class MembershipKey { + ctor protected MembershipKey(); + method public abstract java.nio.channels.MembershipKey block(java.net.InetAddress) throws java.io.IOException; + method public abstract java.nio.channels.MulticastChannel channel(); + method public abstract void drop(); + method public abstract java.net.InetAddress group(); + method public abstract boolean isValid(); + method public abstract java.net.NetworkInterface networkInterface(); + method public abstract java.net.InetAddress sourceAddress(); + method public abstract java.nio.channels.MembershipKey unblock(java.net.InetAddress); + } + + public abstract interface MulticastChannel implements java.nio.channels.NetworkChannel { + method public abstract void close() throws java.io.IOException; + method public abstract java.nio.channels.MembershipKey join(java.net.InetAddress, java.net.NetworkInterface) throws java.io.IOException; + method public abstract java.nio.channels.MembershipKey join(java.net.InetAddress, java.net.NetworkInterface, java.net.InetAddress) throws java.io.IOException; + } + public abstract interface NetworkChannel implements java.lang.AutoCloseable java.nio.channels.Channel java.io.Closeable { method public abstract java.nio.channels.NetworkChannel bind(java.net.SocketAddress) throws java.io.IOException; method public abstract java.net.SocketAddress getLocalAddress() throws java.io.IOException; @@ -44976,6 +45004,7 @@ package java.util { method public java.lang.String getDisplayName(java.util.Locale); method public static java.util.Currency getInstance(java.lang.String); method public static java.util.Currency getInstance(java.util.Locale); + method public int getNumericCode(); method public java.lang.String getSymbol(); method public java.lang.String getSymbol(java.util.Locale); } @@ -48058,8 +48087,10 @@ package java.util.zip { public class ZipFile implements java.io.Closeable { ctor public ZipFile(java.io.File) throws java.io.IOException, java.util.zip.ZipException; + ctor public ZipFile(java.io.File, java.nio.charset.Charset) throws java.io.IOException, java.util.zip.ZipException; ctor public ZipFile(java.lang.String) throws java.io.IOException; ctor public ZipFile(java.io.File, int) throws java.io.IOException; + ctor public ZipFile(java.io.File, int, java.nio.charset.Charset) throws java.io.IOException; method public void close() throws java.io.IOException; method public java.util.Enumeration<? extends java.util.zip.ZipEntry> entries(); method public java.lang.String getComment(); @@ -48113,6 +48144,7 @@ package java.util.zip { public class ZipInputStream extends java.util.zip.InflaterInputStream { ctor public ZipInputStream(java.io.InputStream); + ctor public ZipInputStream(java.io.InputStream, java.nio.charset.Charset); method public void closeEntry() throws java.io.IOException; method protected java.util.zip.ZipEntry createZipEntry(java.lang.String); method public java.util.zip.ZipEntry getNextEntry() throws java.io.IOException; @@ -48160,6 +48192,7 @@ package java.util.zip { public class ZipOutputStream extends java.util.zip.DeflaterOutputStream { ctor public ZipOutputStream(java.io.OutputStream); + ctor public ZipOutputStream(java.io.OutputStream, java.nio.charset.Charset); method public void closeEntry() throws java.io.IOException; method public void putNextEntry(java.util.zip.ZipEntry) throws java.io.IOException; method public void setComment(java.lang.String); diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java index 4df486a..36f9f4b 100644 --- a/core/java/android/app/Activity.java +++ b/core/java/android/app/Activity.java @@ -5698,7 +5698,7 @@ public class Activity extends ContextThemeWrapper * or reaching the home screen. * * Lock task mode will only start if the activity has been whitelisted by the - * Device Owner through {@link DevicePolicyManager#setLockTaskComponents}. + * Device Owner through DevicePolicyManager#setLockTaskComponents. */ public void startLockTask() { try { diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java index c621696..5ed5030 100644 --- a/core/java/android/app/ContextImpl.java +++ b/core/java/android/app/ContextImpl.java @@ -16,6 +16,8 @@ package android.app; +import android.net.wifi.IWifiScanner; +import android.net.wifi.WifiScanner; import android.os.Build; import com.android.internal.policy.PolicyManager; @@ -589,6 +591,13 @@ class ContextImpl extends Context { return new WifiP2pManager(service); }}); + registerService(WIFI_SCANNING_SERVICE, new ServiceFetcher() { + public Object createService(ContextImpl ctx) { + IBinder b = ServiceManager.getService(WIFI_SCANNING_SERVICE); + IWifiScanner service = IWifiScanner.Stub.asInterface(b); + return new WifiScanner(ctx.getOuterContext(), service); + }}); + registerService(WINDOW_SERVICE, new ServiceFetcher() { Display mDefaultDisplay; public Object getService(ContextImpl ctx) { diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index 25a1493..bba6caf 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -17,7 +17,7 @@ package android.app; import com.android.internal.R; -import com.android.internal.util.LegacyNotificationUtil; +import com.android.internal.util.NotificationColorUtil; import android.annotation.IntDef; import android.content.Context; @@ -28,6 +28,7 @@ import android.graphics.PorterDuff; import android.media.AudioManager; import android.net.Uri; import android.os.BadParcelableException; +import android.os.Build; import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; @@ -420,6 +421,21 @@ public class Notification implements Parcelable @Priority public int priority; + /** + * Accent color (an ARGB integer like the constants in {@link android.graphics.Color}) + * to be applied by the standard Style templates when presenting this notification. + * + * The current template design constructs a colorful header image by overlaying the + * {@link #icon} image (stenciled in white) atop a field of this color. Alpha components are + * ignored. + */ + public int color = COLOR_DEFAULT; + + /** + * Special value of {@link #color} telling the system not to decorate this notification with + * any special color but instead use default colors when presenting this notification. + */ + public static final int COLOR_DEFAULT = 0; // AKA Color.TRANSPARENT /** * Sphere of visibility of this notification, which affects how and when the SystemUI reveals @@ -877,6 +893,8 @@ public class Notification implements Parcelable if (parcel.readInt() != 0) { publicVersion = Notification.CREATOR.createFromParcel(parcel); } + + color = parcel.readInt(); } @Override @@ -968,6 +986,8 @@ public class Notification implements Parcelable this.publicVersion.cloneInto(that.publicVersion, heavy); } + that.color = this.color; + if (!heavy) { that.lightenPayload(); // will clean out extras } @@ -1110,6 +1130,8 @@ public class Notification implements Parcelable } else { parcel.writeInt(0); } + + parcel.writeInt(color); } /** @@ -1218,6 +1240,7 @@ public class Notification implements Parcelable sb.append(Integer.toHexString(this.defaults)); sb.append(" flags=0x"); sb.append(Integer.toHexString(this.flags)); + sb.append(String.format(" color=0x%08x", this.color)); sb.append(" category="); sb.append(this.category); if (actions != null) { sb.append(" "); @@ -1309,9 +1332,10 @@ public class Notification implements Parcelable private boolean mShowWhen = true; private int mVisibility = VISIBILITY_PRIVATE; private Notification mPublicVersion = null; - private boolean mQuantumTheme; - private final LegacyNotificationUtil mLegacyNotificationUtil; + private final NotificationColorUtil mColorUtil; private ArrayList<String> mPeople; + private boolean mPreQuantum; + private int mColor = COLOR_DEFAULT; /** * Constructs a new Builder with the defaults: @@ -1341,12 +1365,8 @@ public class Notification implements Parcelable mPriority = PRIORITY_DEFAULT; mPeople = new ArrayList<String>(); - // TODO: Decide on targetSdk from calling app whether to use quantum theme. - mQuantumTheme = true; - - // TODO: Decide on targetSdk from calling app whether to instantiate the processor at - // all. - mLegacyNotificationUtil = LegacyNotificationUtil.getInstance(); + mPreQuantum = context.getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.L; + mColorUtil = NotificationColorUtil.getInstance(); } /** @@ -1853,29 +1873,38 @@ public class Notification implements Parcelable } } + /** + * Sets {@link Notification#color}. + * + * @param argb The accent color to use + * + * @return The same Builder. + */ + public Builder setColor(int argb) { + mColor = argb; + return this; + } + private RemoteViews applyStandardTemplate(int resId, boolean fitIn1U) { RemoteViews contentView = new RemoteViews(mContext.getPackageName(), resId); boolean showLine3 = false; boolean showLine2 = false; int smallIconImageViewId = R.id.icon; - if (!mQuantumTheme && mPriority < PRIORITY_LOW) { - contentView.setInt(R.id.icon, - "setBackgroundResource", R.drawable.notification_template_icon_low_bg); - contentView.setInt(R.id.status_bar_latest_event_content, - "setBackgroundResource", R.drawable.notification_bg_low); + if (mPriority < PRIORITY_LOW) { + // TODO: Low priority presentation } if (mLargeIcon != null) { contentView.setImageViewBitmap(R.id.icon, mLargeIcon); - processLegacyLargeIcon(mLargeIcon, contentView); + processLargeIcon(mLargeIcon, contentView); smallIconImageViewId = R.id.right_icon; } if (mSmallIcon != 0) { contentView.setImageViewResource(smallIconImageViewId, mSmallIcon); contentView.setViewVisibility(smallIconImageViewId, View.VISIBLE); if (mLargeIcon != null) { - processLegacySmallIcon(mSmallIcon, smallIconImageViewId, contentView); + processSmallRightIcon(mSmallIcon, smallIconImageViewId, contentView); } else { - processLegacyLargeIcon(mSmallIcon, contentView); + processSmallIconAsLarge(mSmallIcon, contentView); } } else { @@ -2035,12 +2064,12 @@ public class Notification implements Parcelable * doesn't create quantum notifications by itself) app. */ private boolean isLegacy() { - return mLegacyNotificationUtil != null; + return mColorUtil != null; } private void processLegacyAction(Action action, RemoteViews button) { if (isLegacy()) { - if (mLegacyNotificationUtil.isGrayscale(mContext, action.icon)) { + if (mColorUtil.isGrayscale(mContext, action.icon)) { button.setTextViewCompoundDrawablesRelativeColorFilter(R.id.action0, 0, mContext.getResources().getColor( R.color.notification_action_legacy_color_filter), @@ -2051,45 +2080,68 @@ public class Notification implements Parcelable private CharSequence processLegacyText(CharSequence charSequence) { if (isLegacy()) { - return mLegacyNotificationUtil.invertCharSequenceColors(charSequence); + return mColorUtil.invertCharSequenceColors(charSequence); } else { return charSequence; } } - private void processLegacyLargeIcon(int largeIconId, RemoteViews contentView) { - if (isLegacy()) { - processLegacyLargeIcon( - mLegacyNotificationUtil.isGrayscale(mContext, largeIconId), - contentView); + /** + * Apply any necessary background to smallIcons being used in the largeIcon spot. + */ + private void processSmallIconAsLarge(int largeIconId, RemoteViews contentView) { + if (!isLegacy() || mColorUtil.isGrayscale(mContext, largeIconId)) { + applyLargeIconBackground(contentView); } } - private void processLegacyLargeIcon(Bitmap largeIcon, RemoteViews contentView) { - if (isLegacy()) { - processLegacyLargeIcon( - mLegacyNotificationUtil.isGrayscale(largeIcon), - contentView); + /** + * Apply any necessary background to a largeIcon if it's a fake smallIcon (that is, + * if it's grayscale). + */ + // TODO: also check bounds, transparency, that sort of thing. + private void processLargeIcon(Bitmap largeIcon, RemoteViews contentView) { + if (!isLegacy() || mColorUtil.isGrayscale(largeIcon)) { + applyLargeIconBackground(contentView); } } - private void processLegacyLargeIcon(boolean isGrayscale, RemoteViews contentView) { - if (isLegacy() && isGrayscale) { - contentView.setInt(R.id.icon, "setBackgroundResource", - R.drawable.notification_icon_legacy_bg_inset); - } + /** + * Add a colored circle behind the largeIcon slot. + */ + private void applyLargeIconBackground(RemoteViews contentView) { + contentView.setInt(R.id.icon, "setBackgroundResource", + R.drawable.notification_icon_legacy_bg_inset); + + contentView.setDrawableParameters( + R.id.icon, + true, + -1, + mColor, + PorterDuff.Mode.SRC_ATOP, + -1); } - private void processLegacySmallIcon(int smallIconDrawableId, int smallIconImageViewId, + /** + * Recolor small icons when used in the R.id.right_icon slot. + */ + private void processSmallRightIcon(int smallIconDrawableId, int smallIconImageViewId, RemoteViews contentView) { - if (isLegacy()) { - if (mLegacyNotificationUtil.isGrayscale(mContext, smallIconDrawableId)) { - contentView.setDrawableParameters(smallIconImageViewId, false, -1, - mContext.getResources().getColor( - R.color.notification_action_legacy_color_filter), - PorterDuff.Mode.MULTIPLY, -1); - } + if (!isLegacy() || mColorUtil.isGrayscale(mContext, smallIconDrawableId)) { + contentView.setDrawableParameters(smallIconImageViewId, false, -1, + mContext.getResources().getColor( + R.color.notification_action_legacy_color_filter), + PorterDuff.Mode.MULTIPLY, -1); + } + } + + private int resolveColor() { + if (mColor == COLOR_DEFAULT) { + mColor = mContext.getResources().getColor(R.color.notification_icon_bg_color); + } else { + mColor |= 0xFF000000; // no alpha for custom colors } + return mColor; } /** @@ -2102,6 +2154,9 @@ public class Notification implements Parcelable n.icon = mSmallIcon; n.iconLevel = mSmallIconLevel; n.number = mNumber; + + n.color = resolveColor(); + n.contentView = makeContentView(); n.contentIntent = mContentIntent; n.deleteIntent = mDeleteIntent; @@ -2207,45 +2262,31 @@ public class Notification implements Parcelable private int getBaseLayoutResource() { - return mQuantumTheme - ? R.layout.notification_template_quantum_base - : R.layout.notification_template_base; + return R.layout.notification_template_quantum_base; } private int getBigBaseLayoutResource() { - return mQuantumTheme - ? R.layout.notification_template_quantum_big_base - : R.layout.notification_template_big_base; + return R.layout.notification_template_quantum_big_base; } private int getBigPictureLayoutResource() { - return mQuantumTheme - ? R.layout.notification_template_quantum_big_picture - : R.layout.notification_template_big_picture; + return R.layout.notification_template_quantum_big_picture; } private int getBigTextLayoutResource() { - return mQuantumTheme - ? R.layout.notification_template_quantum_big_text - : R.layout.notification_template_big_text; + return R.layout.notification_template_quantum_big_text; } private int getInboxLayoutResource() { - return mQuantumTheme - ? R.layout.notification_template_quantum_inbox - : R.layout.notification_template_inbox; + return R.layout.notification_template_quantum_inbox; } private int getActionLayoutResource() { - return mQuantumTheme - ? R.layout.notification_quantum_action - : R.layout.notification_action; + return R.layout.notification_quantum_action; } private int getActionTombstoneLayoutResource() { - return mQuantumTheme - ? R.layout.notification_quantum_action_tombstone - : R.layout.notification_action_tombstone; + return R.layout.notification_quantum_action_tombstone; } } diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index 209c536..61ff60a 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -2062,6 +2062,28 @@ public class DevicePolicyManager { } /** + * Called by a profile owner to disable account management for a specific type of account. + * + * <p>The calling device admin must be a profile owner. If it is not, a + * security exception will be thrown. + * + * @param admin Which {@link DeviceAdminReceiver} this request is associated with. + * @param accountType For which account management is disabled or enabled. + * @param disabled The boolean indicating that account management will be disabled (true) or + * enabled (false). + */ + public void setAccountManagementDisabled(ComponentName admin, String accountType, + boolean disabled) { + if (mService != null) { + try { + mService.setAccountManagementDisabled(admin, accountType, disabled); + } catch (RemoteException e) { + Log.w(TAG, "Failed talking with device policy service", e); + } + } + } + + /** * Called by profile or device owner to re-enable system apps by intent that were disabled * by default when the managed profile was created. This should only be called from a profile * or device owner running within a managed profile. @@ -2081,4 +2103,26 @@ public class DevicePolicyManager { } return 0; } + + /** + * Gets the array of accounts for which account management is disabled by the profile owner. + * + * <p> Account management can be disabled/enabled by calling + * {@link #setAccountManagementDisabled}. + * + * @return a list of account types for which account management has been disabled. + * + * @see #setAccountManagementDisabled + */ + public String[] getAccountTypesWithManagementDisabled() { + if (mService != null) { + try { + return mService.getAccountTypesWithManagementDisabled(); + } catch (RemoteException e) { + Log.w(TAG, "Failed talking with device policy service", e); + } + } + + return null; + } } diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl index b30f1b9..0096580 100644 --- a/core/java/android/app/admin/IDevicePolicyManager.aidl +++ b/core/java/android/app/admin/IDevicePolicyManager.aidl @@ -126,4 +126,7 @@ interface IDevicePolicyManager { void enableSystemApp(in ComponentName admin, in String packageName); int enableSystemAppWithIntent(in ComponentName admin, in Intent intent); + + void setAccountManagementDisabled(in ComponentName who, in String accountType, in boolean disabled); + String[] getAccountTypesWithManagementDisabled(); } diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index 7c625bd..042ee28 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -1982,6 +1982,7 @@ public abstract class Context { WIFI_SERVICE, WIFI_HOTSPOT_SERVICE, WIFI_P2P_SERVICE, + WIFI_SCANNING_SERVICE, NSD_SERVICE, AUDIO_SERVICE, MEDIA_ROUTER_SERVICE, @@ -2054,6 +2055,9 @@ public abstract class Context { * <dt> {@link #WIFI_SERVICE} ("wifi") * <dd> A {@link android.net.wifi.WifiManager WifiManager} for management of * Wi-Fi connectivity. + * <dt> {@link #WIFI_P2P_SERVICE} ("wifip2p") + * <dd> A {@link android.net.wifi.p2p.WifiP2pManager WifiP2pManager} for management of + * Wi-Fi Direct connectivity. * <dt> {@link #INPUT_METHOD_SERVICE} ("input_method") * <dd> An {@link android.view.inputmethod.InputMethodManager InputMethodManager} * for management of input methods. @@ -2357,6 +2361,16 @@ public abstract class Context { /** * Use with {@link #getSystemService} to retrieve a {@link + * android.net.wifi.WifiScanner} for scanning the wifi universe + * + * @see #getSystemService + * @see android.net.wifi.WifiScanner + * @hide + */ + public static final String WIFI_SCANNING_SERVICE = "wifiscanner"; + + /** + * Use with {@link #getSystemService} to retrieve a {@link * android.net.nsd.NsdManager} for handling management of network service * discovery * diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index d5a3bcb..e0ac60b 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -2838,6 +2838,8 @@ public final class Settings { MOVED_TO_GLOBAL.add(Settings.Global.WIFI_SAVED_STATE); MOVED_TO_GLOBAL.add(Settings.Global.WIFI_SUPPLICANT_SCAN_INTERVAL_MS); MOVED_TO_GLOBAL.add(Settings.Global.WIFI_SUSPEND_OPTIMIZATIONS_ENABLED); + MOVED_TO_GLOBAL.add(Settings.Global.WIFI_ENHANCED_AUTO_JOIN); + MOVED_TO_GLOBAL.add(Settings.Global.WIFI_NETWORK_SHOW_RSSI); MOVED_TO_GLOBAL.add(Settings.Global.WIFI_WATCHDOG_ON); MOVED_TO_GLOBAL.add(Settings.Global.WIFI_WATCHDOG_POOR_NETWORK_TEST_ENABLED); MOVED_TO_GLOBAL.add(Settings.Global.WIMAX_NETWORKS_AVAILABLE_NOTIFICATION_ON); @@ -5469,7 +5471,21 @@ public final class Settings { public static final String WIFI_SUPPLICANT_SCAN_INTERVAL_MS = "wifi_supplicant_scan_interval_ms"; - /** + /** + * whether frameworks handles wifi auto-join + * @hide + */ + public static final String WIFI_ENHANCED_AUTO_JOIN = + "wifi_enhanced_auto_join"; + + /** + * whether settings show RSSI + * @hide + */ + public static final String WIFI_NETWORK_SHOW_RSSI = + "wifi_network_show_rssi"; + + /** * The interval in milliseconds to scan at supplicant when p2p is connected * @hide */ diff --git a/core/java/android/widget/ShareActionProvider.java b/core/java/android/widget/ShareActionProvider.java index cde8080..e4ad354 100644 --- a/core/java/android/widget/ShareActionProvider.java +++ b/core/java/android/widget/ShareActionProvider.java @@ -276,6 +276,8 @@ public class ShareActionProvider extends ActionProvider { * @see Intent#ACTION_SEND_MULTIPLE */ public void setShareIntent(Intent shareIntent) { + shareIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT | + Intent.FLAG_ACTIVITY_AUTO_REMOVE_FROM_RECENTS); ActivityChooserModel dataModel = ActivityChooserModel.get(mContext, mShareHistoryFileName); dataModel.setIntent(shareIntent); @@ -292,7 +294,8 @@ public class ShareActionProvider extends ActionProvider { final int itemId = item.getItemId(); Intent launchIntent = dataModel.chooseActivity(itemId); if (launchIntent != null) { - launchIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET); + launchIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT | + Intent.FLAG_ACTIVITY_AUTO_REMOVE_FROM_RECENTS); mContext.startActivity(launchIntent); } return true; @@ -308,7 +311,7 @@ public class ShareActionProvider extends ActionProvider { return; } if (mOnChooseActivityListener == null) { - mOnChooseActivityListener = new ShareAcitivityChooserModelPolicy(); + mOnChooseActivityListener = new ShareActivityChooserModelPolicy(); } ActivityChooserModel dataModel = ActivityChooserModel.get(mContext, mShareHistoryFileName); dataModel.setOnChooseActivityListener(mOnChooseActivityListener); @@ -317,7 +320,7 @@ public class ShareActionProvider extends ActionProvider { /** * Policy that delegates to the {@link OnShareTargetSelectedListener}, if such. */ - private class ShareAcitivityChooserModelPolicy implements OnChooseActivityListener { + private class ShareActivityChooserModelPolicy implements OnChooseActivityListener { @Override public boolean onChooseActivity(ActivityChooserModel host, Intent intent) { if (mOnShareTargetSelectedListener != null) { diff --git a/core/java/com/android/internal/app/PlatLogoActivity.java b/core/java/com/android/internal/app/PlatLogoActivity.java index 8cdaf91..abd1791 100644 --- a/core/java/com/android/internal/app/PlatLogoActivity.java +++ b/core/java/com/android/internal/app/PlatLogoActivity.java @@ -18,156 +18,122 @@ package com.android.internal.app; import android.app.Activity; import android.content.ActivityNotFoundException; +import android.content.ContentResolver; +import android.content.Context; import android.content.Intent; +import android.graphics.Color; import android.graphics.Typeface; -import android.provider.Settings; import android.os.Build; import android.os.Bundle; -import android.os.Handler; -import android.text.method.AllCapsTransformationMethod; +import android.provider.Settings; +import android.util.AttributeSet; import android.util.DisplayMetrics; import android.view.Gravity; import android.view.View; -import android.view.animation.AccelerateInterpolator; -import android.view.animation.AnticipateOvershootInterpolator; -import android.view.animation.DecelerateInterpolator; import android.widget.FrameLayout; -import android.widget.ImageView; import android.widget.TextView; public class PlatLogoActivity extends Activity { - FrameLayout mContent; - int mCount; - final Handler mHandler = new Handler(); - static final int BGCOLOR = 0xffed1d24; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - DisplayMetrics metrics = new DisplayMetrics(); - getWindowManager().getDefaultDisplay().getMetrics(metrics); - - Typeface bold = Typeface.create("sans-serif", Typeface.BOLD); - Typeface light = Typeface.create("sans-serif-light", Typeface.NORMAL); - - mContent = new FrameLayout(this); - mContent.setBackgroundColor(0xC0000000); - - final FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams( - FrameLayout.LayoutParams.WRAP_CONTENT, - FrameLayout.LayoutParams.WRAP_CONTENT); - lp.gravity = Gravity.CENTER; - - final ImageView logo = new ImageView(this); - logo.setImageResource(com.android.internal.R.drawable.platlogo); - logo.setScaleType(ImageView.ScaleType.CENTER_INSIDE); - logo.setVisibility(View.INVISIBLE); - - final View bg = new View(this); - bg.setBackgroundColor(BGCOLOR); - bg.setAlpha(0f); - - final TextView letter = new TextView(this); - - letter.setTypeface(bold); - letter.setTextSize(300); - letter.setTextColor(0xFFFFFFFF); - letter.setGravity(Gravity.CENTER); - letter.setText(String.valueOf(Build.ID).substring(0, 1)); - - final int p = (int)(4 * metrics.density); - - final TextView tv = new TextView(this); - if (light != null) tv.setTypeface(light); - tv.setTextSize(30); - tv.setPadding(p, p, p, p); - tv.setTextColor(0xFFFFFFFF); - tv.setGravity(Gravity.CENTER); - tv.setTransformationMethod(new AllCapsTransformationMethod(this)); - tv.setText("Android " + Build.VERSION.RELEASE); - tv.setVisibility(View.INVISIBLE); - - mContent.addView(bg); - mContent.addView(letter, lp); - mContent.addView(logo, lp); + private static class Torso extends FrameLayout { + boolean mAnimate = false; + TextView mText; + + public Torso(Context context) { + this(context, null); + } + public Torso(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + public Torso(Context context, AttributeSet attrs, int flags) { + super(context, attrs, flags); + + for (int i=0; i<2; i++) { + final View v = new View(context); + v.setBackgroundColor(i % 2 == 0 ? Color.BLUE : Color.RED); + addView(v); + } - final FrameLayout.LayoutParams lp2 = new FrameLayout.LayoutParams(lp); - lp2.gravity = Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL; - lp2.bottomMargin = 10*p; + mText = new TextView(context); + mText.setTextColor(Color.BLACK); + mText.setTextSize(14 /* sp */); + mText.setTypeface(Typeface.create("monospace", Typeface.BOLD)); - mContent.addView(tv, lp2); + addView(mText, new FrameLayout.LayoutParams( + FrameLayout.LayoutParams.MATCH_PARENT, + FrameLayout.LayoutParams.WRAP_CONTENT, + Gravity.BOTTOM | Gravity.LEFT + )); + } - mContent.setOnClickListener(new View.OnClickListener() { - int clicks; + private Runnable mRunnable = new Runnable() { @Override - public void onClick(View v) { - clicks++; - if (clicks >= 6) { - mContent.performLongClick(); - return; + public void run() { + mText.setText(String.format("android_%s.flv - build %s", + Build.VERSION.CODENAME, + Build.VERSION.INCREMENTAL)); + final int N = getChildCount(); + final float parentw = getMeasuredWidth(); + final float parenth = getMeasuredHeight(); + for (int i=0; i<N; i++) { + final View v = getChildAt(i); + if (v instanceof TextView) continue; + + final int w = (int) (Math.random() * parentw); + final int h = (int) (Math.random() * parenth); + v.setLayoutParams(new FrameLayout.LayoutParams(w, h)); + + v.setX((float) Math.random() * (parentw - w)); + v.setY((float) Math.random() * (parenth - h)); } - letter.animate().cancel(); - final float offset = (int)letter.getRotation() % 360; - letter.animate() - .rotationBy((Math.random() > 0.5f ? 360 : -360) - offset) - .setInterpolator(new DecelerateInterpolator()) - .setDuration(700).start(); - } - }); - mContent.setOnLongClickListener(new View.OnLongClickListener() { - @Override - public boolean onLongClick(View v) { - if (logo.getVisibility() != View.VISIBLE) { - bg.setScaleX(0.01f); - bg.animate().alpha(1f).scaleX(1f).setStartDelay(500).start(); - letter.animate().alpha(0f).scaleY(0.5f).scaleX(0.5f) - .rotationBy(360) - .setInterpolator(new AccelerateInterpolator()) - .setDuration(1000) - .start(); - logo.setAlpha(0f); - logo.setVisibility(View.VISIBLE); - logo.setScaleX(0.5f); - logo.setScaleY(0.5f); - logo.animate().alpha(1f).scaleX(1f).scaleY(1f) - .setDuration(1000).setStartDelay(500) - .setInterpolator(new AnticipateOvershootInterpolator()) - .start(); - tv.setAlpha(0f); - tv.setVisibility(View.VISIBLE); - tv.animate().alpha(1f).setDuration(1000).setStartDelay(1000).start(); - return true; - } - return false; + if (mAnimate) postDelayed(this, 1000); } - }); + }; + @Override + protected void onAttachedToWindow() { + mAnimate = true; + post(mRunnable); + } + @Override + protected void onDetachedFromWindow() { + mAnimate = false; + removeCallbacks(mRunnable); + } + } - logo.setOnLongClickListener(new View.OnLongClickListener() { - @Override - public boolean onLongClick(View v) { - if (Settings.System.getLong(getContentResolver(), Settings.System.EGG_MODE, 0) - == 0) { - // For posterity: the moment this user unlocked the easter egg - Settings.System.putLong(getContentResolver(), - Settings.System.EGG_MODE, - System.currentTimeMillis()); - } - try { - startActivity(new Intent(Intent.ACTION_MAIN) - .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK - | Intent.FLAG_ACTIVITY_CLEAR_TASK - | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS) - .addCategory("com.android.internal.category.PLATLOGO")); - } catch (ActivityNotFoundException ex) { - android.util.Log.e("PlatLogoActivity", "Couldn't catch a break."); - } - finish(); - return true; - } - }); - - setContentView(mContent); + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + final Torso t = new Torso(this); + t.setBackgroundColor(Color.WHITE); + + t.getChildAt(0) + .setOnLongClickListener(new View.OnLongClickListener() { + @Override + public boolean onLongClick(View v) { + final ContentResolver cr = getContentResolver(); + if (Settings.System.getLong(cr, Settings.System.EGG_MODE, 0) + == 0) { + // For posterity: the moment this user unlocked the easter egg + Settings.System.putLong(cr, + Settings.System.EGG_MODE, + System.currentTimeMillis()); + } + try { + startActivity(new Intent(Intent.ACTION_MAIN) + .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK + | Intent.FLAG_ACTIVITY_CLEAR_TASK + | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS) + .addCategory("com.android.internal.category.PLATLOGO")); + } catch (ActivityNotFoundException ex) { + android.util.Log.e("PlatLogoActivity", "Couldn't catch a break."); + } + finish(); + return true; + } + }); + + setContentView(t); } } diff --git a/core/java/com/android/internal/util/LegacyNotificationUtil.java b/core/java/com/android/internal/util/NotificationColorUtil.java index 0394bbc..f38cbde 100644 --- a/core/java/com/android/internal/util/LegacyNotificationUtil.java +++ b/core/java/com/android/internal/util/NotificationColorUtil.java @@ -24,6 +24,7 @@ import android.graphics.Color; import android.graphics.drawable.AnimationDrawable; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; +import android.graphics.drawable.VectorDrawable; import android.text.SpannableStringBuilder; import android.text.Spanned; import android.text.style.TextAppearanceSpan; @@ -38,21 +39,21 @@ import java.util.WeakHashMap; * * @hide */ -public class LegacyNotificationUtil { +public class NotificationColorUtil { - private static final String TAG = "LegacyNotificationUtil"; + private static final String TAG = "NotificationColorUtil"; private static final Object sLock = new Object(); - private static LegacyNotificationUtil sInstance; + private static NotificationColorUtil sInstance; private final ImageUtils mImageUtils = new ImageUtils(); private final WeakHashMap<Bitmap, Pair<Boolean, Integer>> mGrayscaleBitmapCache = new WeakHashMap<Bitmap, Pair<Boolean, Integer>>(); - public static LegacyNotificationUtil getInstance() { + public static NotificationColorUtil getInstance() { synchronized (sLock) { if (sInstance == null) { - sInstance = new LegacyNotificationUtil(); + sInstance = new NotificationColorUtil(); } return sInstance; } @@ -107,6 +108,9 @@ public class LegacyNotificationUtil { AnimationDrawable ad = (AnimationDrawable) d; int count = ad.getNumberOfFrames(); return count > 0 && isGrayscale(ad.getFrame(0)); + } else if (d instanceof VectorDrawable) { + // We just assume you're doing the right thing if using vectors + return true; } else { return false; } diff --git a/core/java/com/android/internal/util/Protocol.java b/core/java/com/android/internal/util/Protocol.java index b380403..bc92c4a 100644 --- a/core/java/com/android/internal/util/Protocol.java +++ b/core/java/com/android/internal/util/Protocol.java @@ -46,6 +46,8 @@ public class Protocol { public static final int BASE_WIFI_MONITOR = 0x00024000; public static final int BASE_WIFI_MANAGER = 0x00025000; public static final int BASE_WIFI_CONTROLLER = 0x00026000; + public static final int BASE_WIFI_SCANNER = 0x00027000; + public static final int BASE_WIFI_SCANNER_SERVICE = 0x00027100; public static final int BASE_DHCP = 0x00030000; public static final int BASE_DATA_CONNECTION = 0x00040000; public static final int BASE_DATA_CONNECTION_AC = 0x00041000; diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 241500a..cdb77f1 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -1295,10 +1295,9 @@ <!-- @hide Fuller form of {@link android.Manifest.permission#INTERACT_ACROSS_USERS} that removes restrictions on where broadcasts can be sent and allows other types of interactions. --> - <!-- TODO: Remove the system protection level.--> <permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" android:permissionGroup="android.permission-group.SYSTEM_TOOLS" - android:protectionLevel="signature|system" + android:protectionLevel="signature" android:label="@string/permlab_interactAcrossUsersFull" android:description="@string/permdesc_interactAcrossUsersFull" /> diff --git a/core/res/res/drawable-hdpi/stat_sys_adb_am.png b/core/res/res/drawable-hdpi/stat_sys_adb_am.png Binary files differdeleted file mode 100644 index dad614c..0000000 --- a/core/res/res/drawable-hdpi/stat_sys_adb_am.png +++ /dev/null diff --git a/core/res/res/drawable-ldpi/stat_sys_adb_am.png b/core/res/res/drawable-ldpi/stat_sys_adb_am.png Binary files differdeleted file mode 100644 index 0171adb..0000000 --- a/core/res/res/drawable-ldpi/stat_sys_adb_am.png +++ /dev/null diff --git a/core/res/res/drawable-mdpi/stat_sys_adb_am.png b/core/res/res/drawable-mdpi/stat_sys_adb_am.png Binary files differdeleted file mode 100644 index 5482f34..0000000 --- a/core/res/res/drawable-mdpi/stat_sys_adb_am.png +++ /dev/null diff --git a/core/res/res/drawable-nodpi/platlogo.png b/core/res/res/drawable-nodpi/platlogo.png Binary files differdeleted file mode 100644 index 6351c2d..0000000 --- a/core/res/res/drawable-nodpi/platlogo.png +++ /dev/null diff --git a/core/res/res/drawable-nodpi/platlogo.xml b/core/res/res/drawable-nodpi/platlogo.xml new file mode 100644 index 0000000..8eb00fa --- /dev/null +++ b/core/res/res/drawable-nodpi/platlogo.xml @@ -0,0 +1,39 @@ +<!-- + 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" > + <size android:width="400dp" android:height="400dp"/> + + <viewport android:viewportHeight="25" android:viewportWidth="25" /> + + <group> + <path + android:name="shadow" + android:pathData="m12,2.5 a11,11 0 1,0 1,0 + M6.5,7.5 + l5,0 l0,7 l7,0 l0,5 l-12,0 z" + android:fill="#40000000" + /> + <path + android:name="circle-L-ranch" + android:pathData="m12,1.5 a11,11 0 1,0 1,0 + M6.5,6.5 + l5,0 l0,7 l7,0 l0,5 l-12,0 z" + android:fill="#FFFFFF40" + /> + </group> +</vector> + + diff --git a/core/res/res/drawable-nodpi/stat_sys_adb.xml b/core/res/res/drawable-nodpi/stat_sys_adb.xml new file mode 100644 index 0000000..37df348 --- /dev/null +++ b/core/res/res/drawable-nodpi/stat_sys_adb.xml @@ -0,0 +1,30 @@ +<!-- + 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" > + <size android:width="25dp" android:height="25dp"/> + + <viewport android:viewportHeight="25" android:viewportWidth="25" /> + + <group> + <path + android:name="adb" + android:pathData="m3,3l8,0l0,11l11,0l0,8l-18,0z" + android:fill="#FFFFFFFF" + /> + </group> +</vector> + + diff --git a/core/res/res/drawable-xhdpi/stat_sys_adb_am.png b/core/res/res/drawable-xhdpi/stat_sys_adb_am.png Binary files differdeleted file mode 100644 index e53f498..0000000 --- a/core/res/res/drawable-xhdpi/stat_sys_adb_am.png +++ /dev/null diff --git a/core/res/res/drawable-xxhdpi/stat_sys_adb_am.png b/core/res/res/drawable-xxhdpi/stat_sys_adb_am.png Binary files differdeleted file mode 100644 index d6018dd..0000000 --- a/core/res/res/drawable-xxhdpi/stat_sys_adb_am.png +++ /dev/null diff --git a/core/res/res/drawable/notification_icon_legacy_bg.xml b/core/res/res/drawable/notification_icon_legacy_bg.xml index 4ac67c3..cc5755d 100644 --- a/core/res/res/drawable/notification_icon_legacy_bg.xml +++ b/core/res/res/drawable/notification_icon_legacy_bg.xml @@ -18,5 +18,5 @@ <shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="oval"> <solid - android:color="@color/notification_icon_legacy_bg_color"/> + android:color="@color/notification_icon_bg_color"/> </shape> diff --git a/core/res/res/values/colors.xml b/core/res/res/values/colors.xml index 761170d..7d3fb44 100644 --- a/core/res/res/values/colors.xml +++ b/core/res/res/values/colors.xml @@ -123,7 +123,7 @@ <drawable name="notification_template_icon_bg">#3333B5E5</drawable> <drawable name="notification_template_icon_low_bg">#0cffffff</drawable> - <color name="notification_icon_legacy_bg_color">#ff4285F4</color> + <color name="notification_icon_bg_color">#ffa3a3a3</color> <color name="notification_action_legacy_color_filter">#ff555555</color> <!-- Keyguard colors --> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index a8a4b51..2bf72e8 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -1664,6 +1664,7 @@ <java-symbol type="layout" name="notification_template_quantum_big_text" /> <java-symbol type="layout" name="notification_template_quantum_inbox" /> <java-symbol type="color" name="notification_action_legacy_color_filter" /> + <java-symbol type="color" name="notification_icon_bg_color" /> <java-symbol type="drawable" name="notification_icon_legacy_bg_inset" /> <java-symbol type="drawable" name="notification_quantum_bg_dim" /> <java-symbol type="drawable" name="notification_quantum_bg" /> diff --git a/packages/SystemUI/res/layout/status_bar_notification_keyguard_overflow.xml b/packages/SystemUI/res/layout/status_bar_notification_keyguard_overflow.xml index 2e08bff..30eedee 100644 --- a/packages/SystemUI/res/layout/status_bar_notification_keyguard_overflow.xml +++ b/packages/SystemUI/res/layout/status_bar_notification_keyguard_overflow.xml @@ -18,7 +18,7 @@ <com.android.systemui.statusbar.NotificationOverflowContainer xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" - android:layout_height="32dp" + android:layout_height="40dp" android:focusable="true" android:clickable="true" > diff --git a/packages/SystemUI/res/values/ids.xml b/packages/SystemUI/res/values/ids.xml index e5168c4..4369741 100644 --- a/packages/SystemUI/res/values/ids.xml +++ b/packages/SystemUI/res/values/ids.xml @@ -18,11 +18,13 @@ <resources> <item type="id" name="translation_y_animator_tag"/> <item type="id" name="translation_z_animator_tag"/> + <item type="id" name="scale_animator_tag"/> <item type="id" name="alpha_animator_tag"/> <item type="id" name="top_inset_animator_tag"/> <item type="id" name="height_animator_tag"/> <item type="id" name="translation_y_animator_end_value_tag"/> <item type="id" name="translation_z_animator_end_value_tag"/> + <item type="id" name="scale_animator_end_value_tag"/> <item type="id" name="alpha_animator_end_value_tag"/> <item type="id" name="top_inset_animator_end_value_tag"/> <item type="id" name="height_animator_end_value_tag"/> diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java index 3371290..91df9ef 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java @@ -38,6 +38,7 @@ import com.android.internal.R; public abstract class ActivatableNotificationView extends ExpandableOutlineView { private static final long DOUBLETAP_TIMEOUT_MS = 1000; + private static final int BACKGROUND_ANIMATION_LENGTH_MS = 220; private boolean mDimmed; @@ -181,7 +182,7 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView mActivated = false; } if (mOnActivatedListener != null) { - mOnActivatedListener.onReset(this); + mOnActivatedListener.onActivationReset(this); } removeCallbacks(mTapTimeoutRunnable); } @@ -191,12 +192,6 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView && Math.abs(event.getY() - mDownY) < mTouchSlop; } - /** - * Sets the notification as dimmed, meaning that it will appear in a more gray variant. - * - * @param dimmed Whether the notification should be dimmed. - * @param fade Whether an animation should be played to change the state. - */ public void setDimmed(boolean dimmed, boolean fade) { if (mDimmed != dimmed) { mDimmed = dimmed; @@ -228,7 +223,7 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView } int startAlpha = mDimmed ? 255 : 0; int endAlpha = mDimmed ? 0 : 255; - int duration = NotificationActivator.ANIMATION_LENGTH_MS; + int duration = BACKGROUND_ANIMATION_LENGTH_MS; // Check whether there is already a background animation running. if (mBackgroundAnimator != null) { startAlpha = (Integer) mBackgroundAnimator.getAnimatedValue(); @@ -315,8 +310,8 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView } @Override - public void setActualHeight(int actualHeight) { - super.setActualHeight(actualHeight); + public void setActualHeight(int actualHeight, boolean notifyListeners) { + super.setActualHeight(actualHeight, notifyListeners); invalidate(); setPivotY(actualHeight / 2); } @@ -333,6 +328,6 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView public interface OnActivatedListener { void onActivated(View view); - void onReset(View view); + void onActivationReset(View view); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java index a325186..ecefc39 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java @@ -70,7 +70,7 @@ import android.widget.TextView; import com.android.internal.statusbar.IStatusBarService; import com.android.internal.statusbar.StatusBarIcon; import com.android.internal.statusbar.StatusBarIconList; -import com.android.internal.util.LegacyNotificationUtil; +import com.android.internal.util.NotificationColorUtil; import com.android.systemui.R; import com.android.systemui.RecentsComponent; import com.android.systemui.SearchPanelView; @@ -143,7 +143,7 @@ public abstract class BaseStatusBar extends SystemUI implements // public mode, private notifications, etc private boolean mLockscreenPublicMode = false; private final SparseBooleanArray mUsersAllowingPrivateNotifications = new SparseBooleanArray(); - private LegacyNotificationUtil mLegacyNotificationUtil = LegacyNotificationUtil.getInstance(); + private NotificationColorUtil mNotificationColorUtil = NotificationColorUtil.getInstance(); private UserManager mUserManager; @@ -852,7 +852,7 @@ public abstract class BaseStatusBar extends SystemUI implements Drawable iconDrawable = StatusBarIconView.getIcon(mContext, ic); icon.setImageDrawable(iconDrawable); - if (mLegacyNotificationUtil.isGrayscale(iconDrawable)) { + if (mNotificationColorUtil.isGrayscale(iconDrawable)) { icon.setBackgroundResource( com.android.internal.R.drawable.notification_icon_legacy_bg_inset); } @@ -1067,7 +1067,6 @@ public abstract class BaseStatusBar extends SystemUI implements entry.row.setSystemExpanded(top); } } - entry.row.setDimmed(onKeyguard, false /* fade */); boolean showOnKeyguard = shouldShowOnKeyguard(entry.notification); if (onKeyguard && (visibleNotifications >= maxKeyguardNotifications || !showOnKeyguard)) { @@ -1087,48 +1086,11 @@ public abstract class BaseStatusBar extends SystemUI implements if (onKeyguard && mKeyguardIconOverflowContainer.getIconsView().getChildCount() > 0) { mKeyguardIconOverflowContainer.setVisibility(View.VISIBLE); - mKeyguardIconOverflowContainer.setDimmed(true /* dimmed */, false /* fade */); } else { mKeyguardIconOverflowContainer.setVisibility(View.GONE); } } - @Override - public void onActivated(View view) { - int n = mNotificationData.size(); - for (int i = 0; i < n; i++) { - NotificationData.Entry entry = mNotificationData.get(i); - if (entry.row.getVisibility() != View.GONE) { - if (view == entry.row) { - entry.row.getActivator().activate(); - } else { - entry.row.getActivator().activateInverse(); - } - } - } - if (mKeyguardIconOverflowContainer.getVisibility() != View.GONE) { - if (view == mKeyguardIconOverflowContainer) { - mKeyguardIconOverflowContainer.getActivator().activate(); - } else { - mKeyguardIconOverflowContainer.getActivator().activateInverse(); - } - } - } - - @Override - public void onReset(View view) { - int n = mNotificationData.size(); - for (int i = 0; i < n; i++) { - NotificationData.Entry entry = mNotificationData.get(i); - if (entry.row.getVisibility() != View.GONE) { - entry.row.getActivator().reset(); - } - } - if (mKeyguardIconOverflowContainer.getVisibility() != View.GONE) { - mKeyguardIconOverflowContainer.getActivator().reset(); - } - } - private boolean shouldShowOnKeyguard(StatusBarNotification sbn) { return sbn.getNotification().priority >= Notification.PRIORITY_LOW; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/DragDownHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/DragDownHelper.java index e471754..5b2ea0b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/DragDownHelper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/DragDownHelper.java @@ -117,7 +117,7 @@ public class DragDownHelper implements Gefingerpoken { } else { if (mDraggedFarEnough) { mDraggedFarEnough = false; - mOnDragDownListener.onReset(); + mOnDragDownListener.onDragDownReset(); } } return true; @@ -188,7 +188,7 @@ public class DragDownHelper implements Gefingerpoken { cancelExpansion(mStartingChild); } mDraggingDown = false; - mOnDragDownListener.onReset(); + mOnDragDownListener.onDragDownReset(); } private ExpandableView findView(float x, float y) { @@ -200,7 +200,7 @@ public class DragDownHelper implements Gefingerpoken { public interface OnDragDownListener { void onDraggedDown(View startingChild); - void onReset(); + void onDragDownReset(); void onThresholdReached(); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java index e5512a3..39f2bb9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java @@ -52,7 +52,6 @@ public class ExpandableNotificationRow extends ActivatableNotificationView { private NotificationContentView mPublicLayout; private NotificationContentView mPrivateLayout; private int mMaxExpandHeight; - private NotificationActivator mActivator; public ExpandableNotificationRow(Context context, AttributeSet attrs) { super(context, attrs); @@ -63,8 +62,6 @@ public class ExpandableNotificationRow extends ActivatableNotificationView { super.onFinishInflate(); mPublicLayout = (NotificationContentView) findViewById(R.id.expandedPublic); mPrivateLayout = (NotificationContentView) findViewById(R.id.expanded); - - mActivator = new NotificationActivator(this, this); } @Override @@ -208,23 +205,10 @@ public class ExpandableNotificationRow extends ActivatableNotificationView { mPrivateLayout.setVisibility(show ? View.GONE : View.VISIBLE); } - /** - * Sets the notification as dimmed, meaning that it will appear in a more gray variant. - */ - @Override - public void setDimmed(boolean dimmed, boolean fade) { - super.setDimmed(dimmed, fade); - mActivator.setDimmed(dimmed, fade); - } - public int getMaxExpandHeight() { return mMaxExpandHeight; } - public NotificationActivator getActivator() { - return mActivator; - } - /** * @return the potential height this view could expand in addition. */ @@ -238,10 +222,10 @@ public class ExpandableNotificationRow extends ActivatableNotificationView { } @Override - public void setActualHeight(int height) { - mPrivateLayout.setActualHeight(height); + public void setActualHeight(int height, boolean notifyListeners) { + mPrivateLayout.setActualHeight(height, notifyListeners); invalidate(); - super.setActualHeight(height); + super.setActualHeight(height, notifyListeners); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableOutlineView.java b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableOutlineView.java index 43eb5b5..a42c194 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableOutlineView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableOutlineView.java @@ -33,8 +33,8 @@ public abstract class ExpandableOutlineView extends ExpandableView { } @Override - public void setActualHeight(int actualHeight) { - super.setActualHeight(actualHeight); + public void setActualHeight(int actualHeight, boolean notifyListeners) { + super.setActualHeight(actualHeight, notifyListeners); updateOutline(); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableView.java b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableView.java index 169521d..281bd2d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableView.java @@ -61,10 +61,19 @@ public abstract class ExpandableView extends FrameLayout { /** * Sets the actual height of this notification. This is different than the laid out * {@link View#getHeight()}, as we want to avoid layouting during scrolling and expanding. + * + * @param actualHeight The height of this notification. + * @param notifyListeners Whether the listener should be informed about the change. */ - public void setActualHeight(int actualHeight) { + public void setActualHeight(int actualHeight, boolean notifyListeners) { mActualHeight = actualHeight; - notifyHeightChanged(); + if (notifyListeners) { + notifyHeightChanged(); + } + } + + public void setActualHeight(int actualHeight) { + setActualHeight(actualHeight, true); } /** @@ -91,6 +100,15 @@ public abstract class ExpandableView extends FrameLayout { } /** + * Sets the notification as dimmed. The default implementation does nothing. + * + * @param dimmed Whether the notification should be dimmed. + * @param fade Whether an animation should be played to change the state. + */ + public void setDimmed(boolean dimmed, boolean fade) { + } + + /** * @return The desired notification height. */ public int getIntrinsicHeight() { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationActivator.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationActivator.java deleted file mode 100644 index a03aeec..0000000 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationActivator.java +++ /dev/null @@ -1,152 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License - */ - -package com.android.systemui.statusbar; - -import android.content.Context; -import android.view.View; -import android.view.animation.AnimationUtils; -import android.view.animation.Interpolator; - -import com.android.systemui.R; - -/** - * A helper class used by both {@link com.android.systemui.statusbar.ExpandableNotificationRow} and - * {@link com.android.systemui.statusbar.NotificationOverflowIconsView} to make a notification look - * active after tapping it once on the Keyguard. - */ -public class NotificationActivator { - - public static final int ANIMATION_LENGTH_MS = 220; - private static final float INVERSE_ALPHA = 0.9f; - private static final float DIMMED_SCALE = 0.95f; - - /** - * Normal state. Notification is fully interactable. - */ - private static final int STATE_NORMAL = 0; - - /** - * Dimmed state. Neutral state when on the lockscreen, with slight transparency and scaled down - * a bit. - */ - private static final int STATE_DIMMED = 1; - - /** - * Activated state. Used after tapping a notification on the lockscreen. Normal transparency and - * normal scale. - */ - private static final int STATE_ACTIVATED = 2; - - /** - * Inverse activated state. Used for the other notifications on the lockscreen when tapping on - * one. - */ - private static final int STATE_ACTIVATED_INVERSE = 3; - - private final View mTargetView; - private final View mHotspotView; - private final Interpolator mFastOutSlowInInterpolator; - private final Interpolator mLinearOutSlowInInterpolator; - private final int mTranslationZ; - - private int mState; - - public NotificationActivator(View targetView, View hotspotView) { - mTargetView = targetView; - mHotspotView = hotspotView; - Context ctx = targetView.getContext(); - mFastOutSlowInInterpolator = - AnimationUtils.loadInterpolator(ctx, android.R.interpolator.fast_out_slow_in); - mLinearOutSlowInInterpolator = - AnimationUtils.loadInterpolator(ctx, android.R.interpolator.linear_out_slow_in); - mTranslationZ = - ctx.getResources().getDimensionPixelSize(R.dimen.z_distance_between_notifications); - mTargetView.animate().setDuration(ANIMATION_LENGTH_MS); - } - - public void activateInverse() { - if (mState == STATE_ACTIVATED_INVERSE) { - return; - } - mTargetView.animate().cancel(); - mTargetView.animate().withLayer().alpha(INVERSE_ALPHA); - mState = STATE_ACTIVATED_INVERSE; - } - - public void addHotspot() { - mHotspotView.getBackground().setHotspot( - 0, mHotspotView.getWidth()/2, mHotspotView.getHeight()/2); - } - - public void activate() { - if (mState == STATE_ACTIVATED) { - return; - } - mTargetView.animate().cancel(); - mTargetView.animate() - .setInterpolator(mLinearOutSlowInInterpolator) - .scaleX(1) - .scaleY(1) - .translationZBy(mTranslationZ); - mState = STATE_ACTIVATED; - } - - public void reset() { - if (mState == STATE_DIMMED) { - return; - } - mTargetView.animate().cancel(); - mTargetView.animate() - .setInterpolator(mFastOutSlowInInterpolator) - .scaleX(DIMMED_SCALE) - .scaleY(DIMMED_SCALE) - .translationZBy(-mTranslationZ); - if (mTargetView.getAlpha() != 1.0f) { - mTargetView.animate().withLayer().alpha(1); - } - mState = STATE_DIMMED; - } - - public void setDimmed(boolean dimmed, boolean fade) { - if (dimmed) { - mTargetView.animate().cancel(); - if (fade) { - mTargetView.animate() - .setInterpolator(mFastOutSlowInInterpolator) - .scaleX(DIMMED_SCALE) - .scaleY(DIMMED_SCALE); - } else { - mTargetView.setScaleX(DIMMED_SCALE); - mTargetView.setScaleY(DIMMED_SCALE); - } - mState = STATE_DIMMED; - } else { - mTargetView.animate().cancel(); - if (fade) { - mTargetView.animate() - .setInterpolator(mFastOutSlowInInterpolator) - .scaleX(1) - .scaleY(1); - } else { - mTargetView.animate().cancel(); - mTargetView.setScaleX(1); - mTargetView.setScaleY(1); - } - mState = STATE_NORMAL; - } - } -} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java index 379bd05..9df2701 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java @@ -70,8 +70,8 @@ public class NotificationContentView extends ExpandableView { } @Override - public void setActualHeight(int actualHeight) { - super.setActualHeight(actualHeight); + public void setActualHeight(int actualHeight, boolean notifyListeners) { + super.setActualHeight(actualHeight, notifyListeners); selectLayout(); updateClipping(); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationOverflowContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationOverflowContainer.java index e6b5600..864c597 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationOverflowContainer.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationOverflowContainer.java @@ -28,14 +28,13 @@ import com.android.systemui.R; public class NotificationOverflowContainer extends ActivatableNotificationView { private NotificationOverflowIconsView mIconsView; - private NotificationActivator mActivator; public NotificationOverflowContainer(Context context, AttributeSet attrs) { super(context, attrs); } @Override - public void setActualHeight(int currentHeight) { + public void setActualHeight(int currentHeight, boolean notifyListeners) { // noop } @@ -54,22 +53,9 @@ public class NotificationOverflowContainer extends ActivatableNotificationView { super.onFinishInflate(); mIconsView = (NotificationOverflowIconsView) findViewById(R.id.overflow_icons_view); mIconsView.setMoreText((TextView) findViewById(R.id.more_text)); - - mActivator = new NotificationActivator(this, this); - setDimmed(true, false); - } - - @Override - public void setDimmed(boolean dimmed, boolean fade) { - super.setDimmed(dimmed, fade); - mActivator.setDimmed(dimmed, fade); } public NotificationOverflowIconsView getIconsView() { return mIconsView; } - - public NotificationActivator getActivator() { - return mActivator; - } } 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 c4ee6ff..92eee4e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java @@ -628,8 +628,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, R.layout.status_bar_notification_keyguard_overflow, mStackScroller, false); mKeyguardIconOverflowContainer.setOnActivatedListener(this); mKeyguardCarrierLabel = mStatusBarWindow.findViewById(R.id.keyguard_carrier_text); - // TODO: Comment in when transition is ready. - //mKeyguardIconOverflowContainer.setOnClickListener(mOverflowClickListener); + mKeyguardIconOverflowContainer.setOnClickListener(mOverflowClickListener); mStackScroller.addView(mKeyguardIconOverflowContainer); mExpandedContents = mStackScroller; @@ -2790,6 +2789,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, mSettingsContainer.setKeyguardShowing(false); } + updateStackScrollerState(); updatePublicMode(); updateRowStates(); checkBarModes(); @@ -2797,6 +2797,10 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, updateCarrierLabelVisibility(false); } + public void updateStackScrollerState() { + mStackScroller.setDimmed(mState == StatusBarState.KEYGUARD, false /* animate */); + } + public void userActivity() { if (mState == StatusBarState.KEYGUARD) { mKeyguardViewMediatorCallback.userActivity(); @@ -2850,7 +2854,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, public void onActivated(View view) { userActivity(); mKeyguardIndicationTextView.switchIndication(R.string.notification_tap_again); - super.onActivated(view); + mStackScroller.setActivatedChild(view); } /** @@ -2862,9 +2866,11 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, } @Override - public void onReset(View view) { - super.onReset(view); - mKeyguardIndicationTextView.switchIndication(mKeyguardHotwordPhrase); + public void onActivationReset(View view) { + if (view == mStackScroller.getActivatedChild()) { + mKeyguardIndicationTextView.switchIndication(mKeyguardHotwordPhrase); + mStackScroller.setActivatedChild(null); + } } public void onTrackingStarted() { @@ -2896,30 +2902,12 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, } @Override - public void onReset() { - int n = mNotificationData.size(); - for (int i = 0; i < n; i++) { - NotificationData.Entry entry = mNotificationData.get(i); - if (entry.row.getVisibility() != View.GONE) { - entry.row.setDimmed(true /* dimmed */, true /* fade */); - } - } - if (mKeyguardIconOverflowContainer.getVisibility() != View.GONE) { - mKeyguardIconOverflowContainer.setDimmed(true /* dimmed */, true /* fade */); - } + public void onDragDownReset() { + mStackScroller.setDimmed(true /* dimmed */, true /* animated */); } public void onThresholdReached() { - int n = mNotificationData.size(); - for (int i = 0; i < n; i++) { - NotificationData.Entry entry = mNotificationData.get(i); - if (entry.row.getVisibility() != View.GONE) { - entry.row.setDimmed(false /* dimmed */, true /* fade */); - } - } - if (mKeyguardIconOverflowContainer.getVisibility() != View.GONE) { - mKeyguardIconOverflowContainer.setDimmed(false /* dimmed */, true /* fade */); - } + mStackScroller.setDimmed(false /* dimmed */, true /* animate */); } /** diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/AmbientState.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/AmbientState.java new file mode 100644 index 0000000..4121a40 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/AmbientState.java @@ -0,0 +1,75 @@ +/* + * 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.stack; + +import android.view.View; + +import java.util.ArrayList; + +/** + * A global state to track all input states for the algorithm. + */ +public class AmbientState { + private ArrayList<View> mDraggedViews = new ArrayList<View>(); + private int mScrollY; + private boolean mDimmed; + private View mActivatedChild; + + public int getScrollY() { + return mScrollY; + } + + public void setScrollY(int scrollY) { + this.mScrollY = scrollY; + } + + public void onBeginDrag(View view) { + mDraggedViews.add(view); + } + + public void onDragFinished(View view) { + mDraggedViews.remove(view); + } + + public ArrayList<View> getDraggedViews() { + return mDraggedViews; + } + + /** + * @param dimmed Whether we are in a dimmed state (on the lockscreen), where the backgrounds are + * translucent and everything is scaled back a bit. + */ + public void setDimmed(boolean dimmed) { + mDimmed = dimmed; + } + + /** + * In dimmed mode, a child can be activated, which happens on the first tap of the double-tap + * interaction. This child is then scaled normally and its background is fully opaque. + */ + public void setActivatedChild(View activatedChild) { + mActivatedChild = activatedChild; + } + + public boolean isDimmed() { + return mDimmed; + } + + public View getActivatedChild() { + return mActivatedChild; + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/AnimationFilter.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/AnimationFilter.java new file mode 100644 index 0000000..41914ed --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/AnimationFilter.java @@ -0,0 +1,92 @@ +/* + * 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.stack; + +import java.util.ArrayList; + +/** + * Filters the animations for only a certain type of properties. + */ +public class AnimationFilter { + boolean animateAlpha; + boolean animateY; + boolean animateZ; + boolean animateScale; + boolean animateHeight; + boolean animateDimmed; + + public AnimationFilter animateAlpha() { + animateAlpha = true; + return this; + } + + public AnimationFilter animateY() { + animateY = true; + return this; + } + + public AnimationFilter animateZ() { + animateZ = true; + return this; + } + + public AnimationFilter animateScale() { + animateScale = true; + return this; + } + + public AnimationFilter animateHeight() { + animateHeight = true; + return this; + } + + public AnimationFilter animateDimmed() { + animateDimmed = true; + return this; + } + + /** + * Combines multiple filters into {@code this} filter, using or as the operand . + * + * @param events The animation events from the filters to combine. + */ + public void applyCombination(ArrayList<NotificationStackScrollLayout.AnimationEvent> events) { + reset(); + int size = events.size(); + for (int i = 0; i < size; i++) { + combineFilter(events.get(i).filter); + } + } + + private void combineFilter(AnimationFilter filter) { + animateAlpha |= filter.animateAlpha; + animateY |= filter.animateY; + animateZ |= filter.animateZ; + animateScale |= filter.animateScale; + animateHeight |= filter.animateHeight; + animateDimmed |= filter.animateDimmed; + } + + private void reset() { + animateAlpha = false; + animateY = false; + animateZ = false; + animateScale = false; + animateHeight = false; + animateDimmed = false; + } +} 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 59d717c..afd5068 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java @@ -84,7 +84,6 @@ public class NotificationStackScrollLayout extends ViewGroup private int mEmptyMarginBottom; private int mPaddingBetweenElements; private int mTopPadding; - private boolean mListenForHeightChanges = true; /** * The algorithm which calculates the properties for our children @@ -95,6 +94,7 @@ public class NotificationStackScrollLayout extends ViewGroup * The current State this Layout is in */ private StackScrollState mCurrentStackScrollState = new StackScrollState(this); + private AmbientState mAmbientState = new AmbientState(); private ArrayList<View> mChildrenToAddAnimated = new ArrayList<View>(); private ArrayList<View> mChildrenToRemoveAnimated = new ArrayList<View>(); private ArrayList<View> mSnappedBackChildren = new ArrayList<View>(); @@ -108,6 +108,8 @@ public class NotificationStackScrollLayout extends ViewGroup private ExpandableView.OnHeightChangedListener mOnHeightChangedListener; private boolean mNeedsAnimation; private boolean mTopPaddingNeedsAnimation; + private boolean mDimmedNeedsAnimation; + private boolean mActivateNeedsAnimation; private boolean mIsExpanded = true; private boolean mChildrenUpdateRequested; private ViewTreeObserver.OnPreDrawListener mChildrenUpdater @@ -267,8 +269,8 @@ public class NotificationStackScrollLayout extends ViewGroup * modifications to {@link #mOwnScrollY} are performed to reflect it in the view layout. */ private void updateChildren() { - mCurrentStackScrollState.setScrollY(mOwnScrollY); - mStackScrollAlgorithm.getStackScrollState(mCurrentStackScrollState); + mAmbientState.setScrollY(mOwnScrollY); + mStackScrollAlgorithm.getStackScrollState(mAmbientState, mCurrentStackScrollState); if (!isCurrentlyAnimating() && !mNeedsAnimation) { applyCurrentState(); } else { @@ -385,12 +387,12 @@ public class NotificationStackScrollLayout extends ViewGroup mDragAnimPendingChildren.remove(v); } mSwipedOutViews.add(v); - mStackScrollAlgorithm.onDragFinished(v); + mAmbientState.onDragFinished(v); } @Override public void onChildSnappedBack(View animView) { - mStackScrollAlgorithm.onDragFinished(animView); + mAmbientState.onDragFinished(animView); if (!mDragAnimPendingChildren.contains(animView)) { mSnappedBackChildren.add(animView); requestChildrenUpdate(); @@ -404,7 +406,7 @@ public class NotificationStackScrollLayout extends ViewGroup public void onBeginDrag(View v) { setSwipingInProgress(true); mDragAnimPendingChildren.add(v); - mStackScrollAlgorithm.onBeginDrag(v); + mAmbientState.onBeginDrag(v); requestChildrenUpdate(); mNeedsAnimation = true; } @@ -514,7 +516,7 @@ public class NotificationStackScrollLayout extends ViewGroup switch (action & MotionEvent.ACTION_MASK) { case MotionEvent.ACTION_DOWN: { - if (getChildCount() == 0) { + if (getChildCount() == 0 || !isInContentBounds(ev)) { return false; } boolean isBeingDragged = !mScroller.isFinished(); @@ -965,6 +967,8 @@ public class NotificationStackScrollLayout extends ViewGroup generateSnapBackEvents(); generateDragEvents(); generateTopPaddingEvent(); + generateActivateEvent(); + generateDimmedEvent(); mNeedsAnimation = false; } @@ -1012,6 +1016,22 @@ public class NotificationStackScrollLayout extends ViewGroup mTopPaddingNeedsAnimation = false; } + private void generateActivateEvent() { + if (mActivateNeedsAnimation) { + mAnimationEvents.add( + new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_ACTIVATED_CHILD)); + } + mActivateNeedsAnimation = false; + } + + private void generateDimmedEvent() { + if (mDimmedNeedsAnimation) { + mAnimationEvents.add( + new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_DIMMED)); + } + mDimmedNeedsAnimation = false; + } + private boolean onInterceptTouchEventScroll(MotionEvent ev) { /* * This method JUST determines whether we want to intercept the motion. @@ -1120,6 +1140,13 @@ public class NotificationStackScrollLayout extends ViewGroup return mIsBeingDragged; } + /** + * @return Whether the specified motion event is actually happening over the content. + */ + private boolean isInContentBounds(MotionEvent event) { + return event.getY() < getHeight() - getEmptyBottomMargin(); + } + private void setIsBeingDragged(boolean isDragged) { mIsBeingDragged = isDragged; if (isDragged) { @@ -1177,14 +1204,12 @@ public class NotificationStackScrollLayout extends ViewGroup @Override public void onHeightChanged(ExpandableView view) { - if (mListenForHeightChanges && !isCurrentlyAnimating()) { - updateContentHeight(); - updateScrollPositionIfNecessary(); - if (mOnHeightChangedListener != null) { - mOnHeightChangedListener.onHeightChanged(view); - } - requestChildrenUpdate(); + updateContentHeight(); + updateScrollPositionIfNecessary(); + if (mOnHeightChangedListener != null) { + mOnHeightChangedListener.onHeightChanged(view); } + requestChildrenUpdate(); } public void setOnHeightChangedListener( @@ -1197,10 +1222,34 @@ public class NotificationStackScrollLayout extends ViewGroup mAnimationEvents.clear(); } + /** + * See {@link AmbientState#setDimmed}. + */ + public void setDimmed(boolean dimmed, boolean animate) { + mAmbientState.setDimmed(dimmed); + if (animate) { + mDimmedNeedsAnimation = true; + mNeedsAnimation = true; + } + requestChildrenUpdate(); + } + + /** + * See {@link AmbientState#setActivatedChild}. + */ + public void setActivatedChild(View activatedChild) { + mAmbientState.setActivatedChild(activatedChild); + mActivateNeedsAnimation = true; + mNeedsAnimation = true; + requestChildrenUpdate(); + } + + public View getActivatedChild() { + return mAmbientState.getActivatedChild(); + } + private void applyCurrentState() { - mListenForHeightChanges = false; mCurrentStackScrollState.apply(); - mListenForHeightChanges = true; if (mListener != null) { mListener.onChildLocationsChanged(this); } @@ -1215,21 +1264,120 @@ public class NotificationStackScrollLayout extends ViewGroup static class AnimationEvent { - static int ANIMATION_TYPE_ADD = 1; - static int ANIMATION_TYPE_REMOVE = 2; - static int ANIMATION_TYPE_REMOVE_SWIPED_OUT = 3; - static int ANIMATION_TYPE_TOP_PADDING_CHANGED = 4; - static int ANIMATION_TYPE_START_DRAG = 5; - static int ANIMATION_TYPE_SNAP_BACK = 6; + static AnimationFilter[] FILTERS = new AnimationFilter[] { + + // ANIMATION_TYPE_ADD + new AnimationFilter() + .animateAlpha() + .animateHeight() + .animateY() + .animateZ(), + + // ANIMATION_TYPE_REMOVE + new AnimationFilter() + .animateAlpha() + .animateHeight() + .animateY() + .animateZ(), + + // ANIMATION_TYPE_REMOVE_SWIPED_OUT + new AnimationFilter() + .animateAlpha() + .animateHeight() + .animateY() + .animateZ(), + + // ANIMATION_TYPE_TOP_PADDING_CHANGED + new AnimationFilter() + .animateAlpha() + .animateHeight() + .animateY() + .animateDimmed() + .animateScale() + .animateZ(), + + // ANIMATION_TYPE_START_DRAG + new AnimationFilter() + .animateAlpha(), + + // ANIMATION_TYPE_SNAP_BACK + new AnimationFilter() + .animateAlpha(), + + // ANIMATION_TYPE_ACTIVATED_CHILD + new AnimationFilter() + .animateScale() + .animateAlpha(), + + // ANIMATION_TYPE_DIMMED + new AnimationFilter() + .animateScale() + .animateDimmed() + }; + + static int[] LENGTHS = new int[] { + + // ANIMATION_TYPE_ADD + StackStateAnimator.ANIMATION_DURATION_STANDARD, + + // ANIMATION_TYPE_REMOVE + StackStateAnimator.ANIMATION_DURATION_STANDARD, + + // ANIMATION_TYPE_REMOVE_SWIPED_OUT + StackStateAnimator.ANIMATION_DURATION_STANDARD, + + // ANIMATION_TYPE_TOP_PADDING_CHANGED + StackStateAnimator.ANIMATION_DURATION_STANDARD, + + // ANIMATION_TYPE_START_DRAG + StackStateAnimator.ANIMATION_DURATION_STANDARD, + + // ANIMATION_TYPE_SNAP_BACK + StackStateAnimator.ANIMATION_DURATION_STANDARD, + + // ANIMATION_TYPE_ACTIVATED_CHILD + StackStateAnimator.ANIMATION_DURATION_DIMMED_ACTIVATED, + + // ANIMATION_TYPE_DIMMED + StackStateAnimator.ANIMATION_DURATION_DIMMED_ACTIVATED, + }; + + static int ANIMATION_TYPE_ADD = 0; + static int ANIMATION_TYPE_REMOVE = 1; + static int ANIMATION_TYPE_REMOVE_SWIPED_OUT = 2; + static int ANIMATION_TYPE_TOP_PADDING_CHANGED = 3; + static int ANIMATION_TYPE_START_DRAG = 4; + static int ANIMATION_TYPE_SNAP_BACK = 5; + static int ANIMATION_TYPE_ACTIVATED_CHILD = 6; + static int ANIMATION_TYPE_DIMMED = 7; final long eventStartTime; final View changingView; final int animationType; + final AnimationFilter filter; + final long length; AnimationEvent(View view, int type) { eventStartTime = AnimationUtils.currentAnimationTimeMillis(); changingView = view; animationType = type; + filter = FILTERS[type]; + length = LENGTHS[type]; + } + + /** + * Combines the length of several animation events into a single value. + * + * @param events The events of the lengths to combine. + * @return The combined length. This is just the maximum of all length. + */ + static long combineLength(ArrayList<AnimationEvent> events) { + long length = 0; + int size = events.size(); + for (int i = 0; i < size; i++) { + length = Math.max(length, events.get(i).length); + } + return length; } } 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 f7818c0..5e4d496 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java @@ -39,6 +39,10 @@ public class StackScrollAlgorithm { private static final int MAX_ITEMS_IN_BOTTOM_STACK = 3; private static final int MAX_ITEMS_IN_TOP_STACK = 3; + /** When a child is activated, the other cards' alpha fade to this value. */ + private static final float ACTIVATED_INVERSE_ALPHA = 0.9f; + private static final float DIMMED_SCALE = 0.95f; + private int mPaddingBetweenElements; private int mCollapsedSize; private int mTopStackPeekSize; @@ -61,7 +65,6 @@ public class StackScrollAlgorithm { private ExpandableView mFirstChildWhileExpanding; private boolean mExpandedOnStart; private int mTopStackTotalSize; - private ArrayList<View> mDraggedViews = new ArrayList<View>(); public StackScrollAlgorithm(Context context) { initConstants(context); @@ -93,7 +96,7 @@ public class StackScrollAlgorithm { } - public void getStackScrollState(StackScrollState resultState) { + public void getStackScrollState(AmbientState ambientState, StackScrollState resultState) { // The state of the local variables are saved in an algorithmState to easily subdivide it // into multiple phases. StackScrollAlgorithmState algorithmState = mTempAlgorithmState; @@ -107,7 +110,7 @@ public class StackScrollAlgorithm { algorithmState.scrolledPixelsTop = 0; algorithmState.itemsInBottomStack = 0.0f; algorithmState.partialInBottom = 0.0f; - algorithmState.scrollY = resultState.getScrollY() + mCollapsedSize; + algorithmState.scrollY = ambientState.getScrollY() + mCollapsedSize; updateVisibleChildren(resultState, algorithmState); @@ -120,19 +123,42 @@ public class StackScrollAlgorithm { // Phase 3: updateZValuesForState(resultState, algorithmState); - handleDraggedViews(resultState, algorithmState); + handleDraggedViews(ambientState, resultState, algorithmState); + updateDimmedActivated(ambientState, resultState, algorithmState); + } + + /** + * Updates the dimmed and activated states of the children. + */ + private void updateDimmedActivated(AmbientState ambientState, StackScrollState resultState, + StackScrollAlgorithmState algorithmState) { + boolean dimmed = ambientState.isDimmed(); + View activatedChild = ambientState.getActivatedChild(); + int childCount = algorithmState.visibleChildren.size(); + for (int i = 0; i < childCount; i++) { + View child = algorithmState.visibleChildren.get(i); + StackScrollState.ViewState childViewState = resultState.getViewStateForView(child); + childViewState.dimmed = dimmed; + childViewState.scale = !dimmed || activatedChild == child + ? 1.0f + : DIMMED_SCALE; + if (dimmed && activatedChild != null && child != activatedChild) { + childViewState.alpha *= ACTIVATED_INVERSE_ALPHA; + } + } } /** * Handle the special state when views are being dragged */ - private void handleDraggedViews(StackScrollState resultState, + private void handleDraggedViews(AmbientState ambientState, StackScrollState resultState, StackScrollAlgorithmState algorithmState) { - for (View draggedView : mDraggedViews) { + ArrayList<View> draggedViews = ambientState.getDraggedViews(); + for (View draggedView : draggedViews) { int childIndex = algorithmState.visibleChildren.indexOf(draggedView); if (childIndex >= 0 && childIndex < algorithmState.visibleChildren.size() - 1) { View nextChild = algorithmState.visibleChildren.get(childIndex + 1); - if (!mDraggedViews.contains(nextChild)) { + if (!draggedViews.contains(nextChild)) { // only if the view is not dragged itself we modify its state to be fully // visible StackScrollState.ViewState viewState = resultState.getViewStateForView( @@ -595,14 +621,6 @@ public class StackScrollAlgorithm { } } - public void onBeginDrag(View view) { - mDraggedViews.add(view); - } - - public void onDragFinished(View view) { - mDraggedViews.remove(view); - } - class StackScrollAlgorithmState { /** diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollState.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollState.java index 70126f5..8fc26d8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollState.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollState.java @@ -37,19 +37,10 @@ public class StackScrollState { private final ViewGroup mHostView; private Map<ExpandableView, ViewState> mStateMap; - private int mScrollY; private final Rect mClipRect = new Rect(); private int mBackgroundRoundedRectCornerRadius; private final Outline mChildOutline = new Outline(); - public int getScrollY() { - return mScrollY; - } - - public void setScrollY(int scrollY) { - this.mScrollY = scrollY; - } - public StackScrollState(ViewGroup hostView) { mHostView = hostView; mStateMap = new HashMap<ExpandableView, ViewState>(); @@ -106,10 +97,12 @@ public class StackScrollState { float alpha = child.getAlpha(); float yTranslation = child.getTranslationY(); float zTranslation = child.getTranslationZ(); + float scale = child.getScaleX(); int height = child.getActualHeight(); float newAlpha = state.alpha; float newYTranslation = state.yTranslation; float newZTranslation = state.zTranslation; + float newScale = state.scale; int newHeight = state.height; boolean becomesInvisible = newAlpha == 0.0f; if (alpha != newAlpha) { @@ -147,11 +140,20 @@ public class StackScrollState { child.setTranslationZ(newZTranslation); } + // apply scale + if (scale != newScale) { + child.setScaleX(newScale); + child.setScaleY(newScale); + } + // apply height if (height != newHeight) { - child.setActualHeight(newHeight); + child.setActualHeight(newHeight, false /* notifyListeners */); } + // apply dimming + child.setDimmed(state.dimmed, false /* animate */); + // apply clipping and shadow float newNotificationEnd = newYTranslation + newHeight; @@ -228,6 +230,8 @@ public class StackScrollState { float zTranslation; int height; boolean gone; + float scale; + boolean dimmed; /** * The location this view is currently rendered at. diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackStateAnimator.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackStateAnimator.java index 2e700aa..c952698 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackStateAnimator.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackStateAnimator.java @@ -18,7 +18,9 @@ package com.android.systemui.statusbar.stack; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; +import android.animation.AnimatorSet; import android.animation.ObjectAnimator; +import android.animation.PropertyValuesHolder; import android.animation.ValueAnimator; import android.view.View; import android.view.animation.AnimationUtils; @@ -37,14 +39,18 @@ import java.util.Stack; */ public class StackStateAnimator { - private static final int ANIMATION_DURATION = 360; + public static final int ANIMATION_DURATION_STANDARD = 360; + public static final int ANIMATION_DURATION_DIMMED_ACTIVATED = 220; + private static final int TAG_ANIMATOR_TRANSLATION_Y = R.id.translation_y_animator_tag; private static final int TAG_ANIMATOR_TRANSLATION_Z = R.id.translation_z_animator_tag; + private static final int TAG_ANIMATOR_SCALE = R.id.scale_animator_tag; private static final int TAG_ANIMATOR_ALPHA = R.id.alpha_animator_tag; private static final int TAG_ANIMATOR_HEIGHT = R.id.height_animator_tag; private static final int TAG_ANIMATOR_TOP_INSET = R.id.top_inset_animator_tag; private static final int TAG_END_TRANSLATION_Y = R.id.translation_y_animator_end_value_tag; private static final int TAG_END_TRANSLATION_Z = R.id.translation_z_animator_end_value_tag; + private static final int TAG_END_SCALE = R.id.scale_animator_end_value_tag; private static final int TAG_END_ALPHA = R.id.alpha_animator_end_value_tag; private static final int TAG_END_HEIGHT = R.id.height_animator_end_value_tag; private static final int TAG_END_TOP_INSET = R.id.top_inset_animator_end_value_tag; @@ -58,6 +64,8 @@ public class StackStateAnimator { private Set<Animator> mAnimatorSet = new HashSet<Animator>(); private Stack<AnimatorListenerAdapter> mAnimationListenerPool = new Stack<AnimatorListenerAdapter>(); + private AnimationFilter mAnimationFilter = new AnimationFilter(); + private long mCurrentLength; public StackStateAnimator(NotificationStackScrollLayout hostLayout) { mHostLayout = hostLayout; @@ -75,8 +83,9 @@ public class StackStateAnimator { processAnimationEvents(mAnimationEvents, finalState); - boolean hasNewEvents = !mNewEvents.isEmpty(); int childCount = mHostLayout.getChildCount(); + mAnimationFilter.applyCombination(mNewEvents); + mCurrentLength = NotificationStackScrollLayout.AnimationEvent.combineLength(mNewEvents); for (int i = 0; i < childCount; i++) { final ExpandableView child = (ExpandableView) mHostLayout.getChildAt(i); StackScrollState.ViewState viewState = finalState.getViewStateForView(child); @@ -84,7 +93,7 @@ public class StackStateAnimator { continue; } - startAnimations(child, viewState, hasNewEvents); + startAnimations(child, viewState); child.setClipBounds(null); } @@ -97,8 +106,7 @@ public class StackStateAnimator { /** * Start an animation to the given viewState */ - private void startAnimations(final ExpandableView child, StackScrollState.ViewState viewState, - boolean hasNewEvents) { + private void startAnimations(final ExpandableView child, StackScrollState.ViewState viewState) { int childVisibility = child.getVisibility(); boolean wasVisible = childVisibility == View.VISIBLE; final float alpha = viewState.alpha; @@ -107,33 +115,40 @@ public class StackStateAnimator { } // start translationY animation if (child.getTranslationY() != viewState.yTranslation) { - startYTranslationAnimation(child, viewState, hasNewEvents); + startYTranslationAnimation(child, viewState); } // start translationZ animation if (child.getTranslationZ() != viewState.zTranslation) { - startZTranslationAnimation(child, viewState, hasNewEvents); + startZTranslationAnimation(child, viewState); + } + // start scale animation + if (child.getScaleX() != viewState.scale) { + startScaleAnimation(child, viewState); } // start alpha animation if (alpha != child.getAlpha()) { - startAlphaAnimation(child, viewState, hasNewEvents); + startAlphaAnimation(child, viewState); } // start height animation if (viewState.height != child.getActualHeight()) { - startHeightAnimation(child, viewState, hasNewEvents); + startHeightAnimation(child, viewState); } + // start dimmed animation + child.setDimmed(viewState.dimmed, mAnimationFilter.animateDimmed); } private void startHeightAnimation(final ExpandableView child, - StackScrollState.ViewState viewState, boolean hasNewEvents) { - Integer previousEndValue = getChildTag(child,TAG_END_HEIGHT); + StackScrollState.ViewState viewState) { + Integer previousEndValue = getChildTag(child, TAG_END_HEIGHT); if (previousEndValue != null && previousEndValue == viewState.height) { return; } ValueAnimator previousAnimator = getChildTag(child, TAG_ANIMATOR_HEIGHT); - long newDuration = cancelAnimatorAndGetNewDuration(previousAnimator, hasNewEvents); + long newDuration = cancelAnimatorAndGetNewDuration(previousAnimator, + mAnimationFilter.animateHeight); if (newDuration <= 0) { // no new animation needed, let's just apply the value - child.setActualHeight(viewState.height); + child.setActualHeight(viewState.height, false /* notifyListeners */); if (previousAnimator != null && !isRunning()) { onAnimationFinished(); } @@ -144,7 +159,8 @@ public class StackStateAnimator { animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { - child.setActualHeight((int) animation.getAnimatedValue()); + child.setActualHeight((int) animation.getAnimatedValue(), + false /* notifyListeners */); } }); animator.setInterpolator(mFastOutSlowInInterpolator); @@ -164,14 +180,15 @@ public class StackStateAnimator { } private void startAlphaAnimation(final ExpandableView child, - final StackScrollState.ViewState viewState, boolean hasNewEvents) { + final StackScrollState.ViewState viewState) { final float endAlpha = viewState.alpha; Float previousEndValue = getChildTag(child,TAG_END_ALPHA); if (previousEndValue != null && previousEndValue == endAlpha) { return; } ObjectAnimator previousAnimator = getChildTag(child, TAG_ANIMATOR_ALPHA); - long newDuration = cancelAnimatorAndGetNewDuration(previousAnimator, hasNewEvents); + long newDuration = cancelAnimatorAndGetNewDuration(previousAnimator, + mAnimationFilter.animateAlpha); if (newDuration <= 0) { // no new animation needed, let's just apply the value child.setAlpha(endAlpha); @@ -228,13 +245,14 @@ public class StackStateAnimator { } private void startZTranslationAnimation(final ExpandableView child, - final StackScrollState.ViewState viewState, boolean hasNewEvents) { + final StackScrollState.ViewState viewState) { Float previousEndValue = getChildTag(child,TAG_END_TRANSLATION_Z); if (previousEndValue != null && previousEndValue == viewState.zTranslation) { return; } ObjectAnimator previousAnimator = getChildTag(child, TAG_ANIMATOR_TRANSLATION_Z); - long newDuration = cancelAnimatorAndGetNewDuration(previousAnimator, hasNewEvents); + long newDuration = cancelAnimatorAndGetNewDuration(previousAnimator, + mAnimationFilter.animateZ); if (newDuration <= 0) { // no new animation needed, let's just apply the value child.setTranslationZ(viewState.zTranslation); @@ -264,13 +282,14 @@ public class StackStateAnimator { } private void startYTranslationAnimation(final ExpandableView child, - StackScrollState.ViewState viewState, boolean hasNewEvents) { + StackScrollState.ViewState viewState) { Float previousEndValue = getChildTag(child,TAG_END_TRANSLATION_Y); if (previousEndValue != null && previousEndValue == viewState.yTranslation) { return; } ObjectAnimator previousAnimator = getChildTag(child, TAG_ANIMATOR_TRANSLATION_Y); - long newDuration = cancelAnimatorAndGetNewDuration(previousAnimator, hasNewEvents); + long newDuration = cancelAnimatorAndGetNewDuration(previousAnimator, + mAnimationFilter.animateY); if (newDuration <= 0) { // no new animation needed, let's just apply the value child.setTranslationY(viewState.yTranslation); @@ -298,6 +317,46 @@ public class StackStateAnimator { child.setTag(TAG_END_TRANSLATION_Y, viewState.yTranslation); } + private void startScaleAnimation(final ExpandableView child, + StackScrollState.ViewState viewState) { + Float previousEndValue = getChildTag(child, TAG_END_SCALE); + if (previousEndValue != null && previousEndValue == viewState.scale) { + return; + } + ObjectAnimator previousAnimator = getChildTag(child, TAG_ANIMATOR_SCALE); + long newDuration = cancelAnimatorAndGetNewDuration(previousAnimator, + mAnimationFilter.animateScale); + if (newDuration <= 0) { + // no new animation needed, let's just apply the value + child.setScaleX(viewState.scale); + child.setScaleY(viewState.scale); + if (previousAnimator != null && !isRunning()) { + onAnimationFinished(); + } + return; + } + + PropertyValuesHolder holderX = + PropertyValuesHolder.ofFloat(View.SCALE_X, child.getScaleX(), viewState.scale); + PropertyValuesHolder holderY = + PropertyValuesHolder.ofFloat(View.SCALE_Y, child.getScaleY(), viewState.scale); + ObjectAnimator animator = ObjectAnimator.ofPropertyValuesHolder(child, holderX, holderY); + animator.setInterpolator(mFastOutSlowInInterpolator); + animator.setDuration(newDuration); + animator.addListener(getGlobalAnimationFinishedListener()); + // remove the tag when the animation is finished + animator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + child.setTag(TAG_ANIMATOR_SCALE, null); + child.setTag(TAG_END_SCALE, null); + } + }); + startInstantly(animator); + child.setTag(TAG_ANIMATOR_SCALE, animator); + child.setTag(TAG_END_SCALE, viewState.scale); + } + /** * Start an animator instantly instead of waiting on the next synchronization frame */ @@ -349,21 +408,22 @@ public class StackStateAnimator { * Cancel the previous animator and get the duration of the new animation. * * @param previousAnimator the animator which was running before - * @param hasNewEvents indicating whether new events came in in this animation + * @param newAnimationNeeded indicating whether a new animation should be started for this + * property * @return the new duration */ private long cancelAnimatorAndGetNewDuration(ValueAnimator previousAnimator, - boolean hasNewEvents) { - long newDuration = ANIMATION_DURATION; + boolean newAnimationNeeded) { + long newDuration = mCurrentLength; if (previousAnimator != null) { - if (!hasNewEvents) { + if (!newAnimationNeeded) { // This is only an update, no new event came in. lets just take the remaining // duration as the new duration newDuration = previousAnimator.getDuration() - previousAnimator.getCurrentPlayTime(); } previousAnimator.cancel(); - } else if (!hasNewEvents){ + } else if (!newAnimationNeeded){ newDuration = 0; } return newDuration; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java index d6a8885..9006c9a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java @@ -150,4 +150,11 @@ public class TvStatusBar extends BaseStatusBar { protected void refreshLayout(int layoutDirection) { } + @Override + public void onActivated(View view) { + } + + @Override + public void onActivationReset(View view) { + } } diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index a09b8d2..29c781b 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -16523,7 +16523,7 @@ public final class ActivityManagerService extends ActivityManagerNative } if ((userInfo.flags&UserInfo.FLAG_INITIALIZED) == 0) { - if (userId != 0) { + if (userId != UserHandle.USER_OWNER) { Intent intent = new Intent(Intent.ACTION_USER_INITIALIZE); intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND); broadcastIntentLocked(null, null, intent, null, @@ -16552,6 +16552,8 @@ public final class ActivityManagerService extends ActivityManagerNative EventLogTags.writeAmSwitchUser(userId); getUserManagerLocked().userForeground(userId); sendUserSwitchBroadcastsLocked(oldUserId, userId); + } else { + mStackSupervisor.startBackgroundUserLocked(userId, uss); } if (needStart) { @@ -16727,7 +16729,7 @@ public final class ActivityManagerService extends ActivityManagerNative } } - void finishUserSwitch(UserStartedState uss) { + void finishUserBoot(UserStartedState uss) { synchronized (this) { if (uss.mState == UserStartedState.STATE_BOOTING && mStartedUsers.get(uss.mHandle.getIdentifier()) == uss) { @@ -16741,6 +16743,12 @@ public final class ActivityManagerService extends ActivityManagerNative android.Manifest.permission.RECEIVE_BOOT_COMPLETED, AppOpsManager.OP_NONE, true, false, MY_PID, Process.SYSTEM_UID, userId); } + } + } + + void finishUserSwitch(UserStartedState uss) { + synchronized (this) { + finishUserBoot(uss); startProfilesLocked(); diff --git a/services/core/java/com/android/server/am/ActivityRecord.java b/services/core/java/com/android/server/am/ActivityRecord.java index 7c3f288..efd2b57 100755 --- a/services/core/java/com/android/server/am/ActivityRecord.java +++ b/services/core/java/com/android/server/am/ActivityRecord.java @@ -17,7 +17,6 @@ package com.android.server.am; import android.os.Trace; -import com.android.internal.R.styleable; import com.android.internal.app.ResolverActivity; import com.android.server.AttributeCache; import com.android.server.am.ActivityStack.ActivityState; @@ -480,7 +479,7 @@ final class ActivityRecord { void setTask(TaskRecord newTask, ThumbnailHolder newThumbHolder, boolean isRoot) { if (task != null && task.removeActivity(this)) { if (task != newTask) { - task.stack.removeTask(task, false); + task.stack.removeTask(task); } else { Slog.d(TAG, "!!! REMOVE THIS LOG !!! setTask: nearly removed stack=" + (newTask == null ? null : newTask.stack)); diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java index ee39b67..a95710b 100755 --- a/services/core/java/com/android/server/am/ActivityStack.java +++ b/services/core/java/com/android/server/am/ActivityStack.java @@ -36,8 +36,6 @@ import static com.android.server.am.ActivityStackSupervisor.DEBUG_SAVED_STATE; import static com.android.server.am.ActivityStackSupervisor.DEBUG_STATES; import static com.android.server.am.ActivityStackSupervisor.HOME_STACK_ID; -import static com.android.server.am.ActivityStackSupervisor.ActivityContainer.CONTAINER_STATE_HAS_SURFACE; - import android.service.voice.IVoiceInteractionSession; import com.android.internal.app.IVoiceInteractor; import com.android.internal.os.BatteryStatsImpl; @@ -2841,7 +2839,7 @@ final class ActivityStack { if (mStackSupervisor.isFrontStack(this) && task == topTask() && task.mOnTopOfHome) { mStackSupervisor.moveHomeToTop(); } - removeTask(task, false); + removeTask(task); } cleanUpActivityServicesLocked(r); r.removeUriPermissionsLocked(); @@ -3717,7 +3715,7 @@ final class ActivityStack { return starting; } - void removeTask(TaskRecord task, boolean moving) { + void removeTask(TaskRecord task) { mStackSupervisor.endLockTaskModeIfTaskEnding(task); mWindowManager.removeTask(task.taskId); final ActivityRecord r = mResumedActivity; @@ -3731,14 +3729,20 @@ final class ActivityStack { mTaskHistory.get(taskNdx + 1).mOnTopOfHome = true; } mTaskHistory.remove(task); - if (!moving && task.voiceSession != null) { - // This task was a voice interaction, so it should not remain on the - // recent tasks list. - try { - task.voiceSession.taskFinished(task.intent, task.taskId); - } catch (RemoteException e) { + + if (task.mActivities.isEmpty()) { + final boolean isVoiceSession = task.voiceSession != null; + if (isVoiceSession) { + try { + task.voiceSession.taskFinished(task.intent, task.taskId); + } catch (RemoteException e) { + } + } + if (task.autoRemoveFromRecents() || isVoiceSession) { + // Task creator asked to remove this when done, or this task was a voice + // interaction, so it should not remain on the recent tasks list. + mService.mRecentTasks.remove(task); } - mService.mRecentTasks.remove(task); } if (mTaskHistory.isEmpty()) { diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java index 2e979d2..00327ac 100644 --- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java +++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java @@ -193,6 +193,9 @@ public final class ActivityStackSupervisor implements DisplayListener { /** Used on user changes */ final ArrayList<UserStartedState> mStartingUsers = new ArrayList<UserStartedState>(); + /** Used to queue up any background users being started */ + final ArrayList<UserStartedState> mStartingBackgroundUsers = new ArrayList<UserStartedState>(); + /** Set to indicate whether to issue an onUserLeaving callback when a newly launched activity * is being brought in front of us. */ boolean mUserLeaving = false; @@ -1471,6 +1474,17 @@ public final class ActivityStackSupervisor implements DisplayListener { } } + switch (r.info.documentLaunchMode) { + case ActivityInfo.DOCUMENT_LAUNCH_NONE: + break; + case ActivityInfo.DOCUMENT_LAUNCH_ALWAYS: + intent.addFlags( + Intent.FLAG_ACTIVITY_NEW_DOCUMENT | Intent.FLAG_ACTIVITY_MULTIPLE_TASK); + break; + case ActivityInfo.DOCUMENT_LAUNCH_INTO_EXISTING: + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT); + break; + } final boolean newDocument = intent.isDocument(); if (sourceRecord == null) { // This activity is not being started from another... in this @@ -1988,9 +2002,20 @@ public final class ActivityStackSupervisor implements DisplayListener { if (booting) { mService.finishBooting(); - } else if (startingUsers != null) { - for (int i = 0; i < startingUsers.size(); i++) { - mService.finishUserSwitch(startingUsers.get(i)); + } else { + // Complete user switch + if (startingUsers != null) { + for (int i = 0; i < startingUsers.size(); i++) { + mService.finishUserSwitch(startingUsers.get(i)); + } + } + // Complete starting up of background users + if (mStartingBackgroundUsers.size() > 0) { + startingUsers = new ArrayList<UserStartedState>(mStartingBackgroundUsers); + mStartingBackgroundUsers.clear(); + for (int i = 0; i < startingUsers.size(); i++) { + mService.finishUserBoot(startingUsers.get(i)); + } } } @@ -2237,7 +2262,7 @@ public final class ActivityStackSupervisor implements DisplayListener { Slog.w(TAG, "moveTaskToStack: no stack for id=" + stackId); return; } - task.stack.removeTask(task, true); + task.stack.removeTask(task); stack.addTask(task, toTop, true); mWindowManager.addTask(taskId, stackId, toTop); resumeTopActivitiesLocked(); @@ -2496,6 +2521,15 @@ public final class ActivityStackSupervisor implements DisplayListener { return homeInFront; } + /** + * Add background users to send boot completed events to. + * @param userId The user being started in the background + * @param uss The state object for the user. + */ + public void startBackgroundUserLocked(int userId, UserStartedState uss) { + mStartingBackgroundUsers.add(uss); + } + final ArrayList<ActivityRecord> processStoppingActivitiesLocked(boolean remove) { int N = mStoppingActivities.size(); if (N <= 0) return null; diff --git a/services/core/java/com/android/server/am/TaskRecord.java b/services/core/java/com/android/server/am/TaskRecord.java index 9f0bc10..862932c 100644 --- a/services/core/java/com/android/server/am/TaskRecord.java +++ b/services/core/java/com/android/server/am/TaskRecord.java @@ -139,6 +139,9 @@ final class TaskRecord extends ThumbnailHolder { userId = UserHandle.getUserId(info.applicationInfo.uid); creatorUid = info.applicationInfo.uid; + if ((info.flags & ActivityInfo.FLAG_AUTO_REMOVE_FROM_RECENTS) != 0) { + intent.addFlags(Intent.FLAG_ACTIVITY_AUTO_REMOVE_FROM_RECENTS); + } } void disposeThumbnail() { @@ -246,6 +249,11 @@ final class TaskRecord extends ThumbnailHolder { return mActivities.size() == 0; } + boolean autoRemoveFromRecents() { + return intent != null && + (intent.getFlags() & Intent.FLAG_ACTIVITY_AUTO_REMOVE_FROM_RECENTS) != 0; + } + /** * Completely remove all activities associated with an existing * task starting at a specified index. diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index 7aa5d79..fce86e8 100644 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -198,8 +198,7 @@ public class NotificationManagerService extends SystemService { private final UserProfiles mUserProfiles = new UserProfiles(); private NotificationListeners mListeners; private ConditionProviders mConditionProviders; - - private final NotificationUsageStats mUsageStats = new NotificationUsageStats(); + private NotificationUsageStats mUsageStats; private static final String EXTRA_INTERCEPT = "android.intercept"; @@ -472,6 +471,7 @@ public class NotificationManagerService extends SystemService { pw.println(prefix + String.format(" defaults=0x%08x flags=0x%08x", notification.defaults, notification.flags)); pw.println(prefix + " sound=" + notification.sound); + pw.println(prefix + String.format(" color=0x%08x", notification.color)); pw.println(prefix + " vibrate=" + Arrays.toString(notification.vibrate)); pw.println(prefix + String.format(" led=0x%08x onMs=%d offMs=%d", notification.ledARGB, notification.ledOnMS, notification.ledOffMS)); @@ -858,6 +858,7 @@ public class NotificationManagerService extends SystemService { }); final File systemDir = new File(Environment.getDataDirectory(), "system"); mPolicyFile = new AtomicFile(new File(systemDir, "notification_policy.xml")); + mUsageStats = new NotificationUsageStats(getContext()); importOldBlockDb(); diff --git a/services/core/java/com/android/server/notification/NotificationUsageStats.java b/services/core/java/com/android/server/notification/NotificationUsageStats.java index d9e2b91..45ab3d3 100644 --- a/services/core/java/com/android/server/notification/NotificationUsageStats.java +++ b/services/core/java/com/android/server/notification/NotificationUsageStats.java @@ -18,8 +18,17 @@ package com.android.server.notification; import com.android.server.notification.NotificationManagerService.NotificationRecord; +import android.content.ContentValues; +import android.content.Context; +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteOpenHelper; +import android.os.Handler; +import android.os.HandlerThread; +import android.os.Message; import android.os.SystemClock; import android.service.notification.StatusBarNotification; +import android.util.Log; import java.io.PrintWriter; import java.util.HashMap; @@ -37,9 +46,13 @@ import java.util.Map; * {@hide} */ public class NotificationUsageStats { - // Guarded by synchronized(this). private final Map<String, AggregatedStats> mStats = new HashMap<String, AggregatedStats>(); + private final SQLiteLog mSQLiteLog; + + public NotificationUsageStats(Context context) { + mSQLiteLog = new SQLiteLog(context); + } /** * Called when a notification has been posted. @@ -49,6 +62,7 @@ public class NotificationUsageStats { for (AggregatedStats stats : getAggregatedStatsLocked(notification)) { stats.numPostedByApp++; } + mSQLiteLog.logPosted(notification); } /** @@ -68,6 +82,7 @@ public class NotificationUsageStats { stats.numRemovedByApp++; stats.collect(notification.stats); } + mSQLiteLog.logRemoved(notification); } /** @@ -79,6 +94,7 @@ public class NotificationUsageStats { stats.numDismissedByUser++; stats.collect(notification.stats); } + mSQLiteLog.logDismissed(notification); } /** @@ -89,6 +105,7 @@ public class NotificationUsageStats { for (AggregatedStats stats : getAggregatedStatsLocked(notification)) { stats.numClickedByUser++; } + mSQLiteLog.logClicked(notification); } /** @@ -146,6 +163,7 @@ public class NotificationUsageStats { for (AggregatedStats as : mStats.values()) { as.dump(pw, indent); } + mSQLiteLog.dump(pw, indent); } /** @@ -274,4 +292,211 @@ public class NotificationUsageStats { '}'; } } + + private static class SQLiteLog { + private static final String TAG = "NotificationSQLiteLog"; + + // Message types passed to the background handler. + private static final int MSG_POST = 1; + private static final int MSG_CLICK = 2; + private static final int MSG_REMOVE = 3; + private static final int MSG_DISMISS = 4; + + private static final String DB_NAME = "notification_log.db"; + private static final int DB_VERSION = 1; + + /** Age in ms after which events are pruned from the DB. */ + private static final long HORIZON_MS = 7 * 24 * 60 * 60 * 1000L; // 1 week + /** Delay between pruning the DB. Used to throttle pruning. */ + private static final long PRUNE_MIN_DELAY_MS = 6 * 60 * 60 * 1000L; // 6 hours + /** Mininum number of writes between pruning the DB. Used to throttle pruning. */ + private static final long PRUNE_MIN_WRITES = 1024; + + // Table 'log' + private static final String TAB_LOG = "log"; + private static final String COL_EVENT_USER_ID = "event_user_id"; + private static final String COL_EVENT_TYPE = "event_type"; + private static final String COL_EVENT_TIME = "event_time_ms"; + private static final String COL_KEY = "key"; + private static final String COL_PKG = "pkg"; + private static final String COL_NOTIFICATION_ID = "nid"; + private static final String COL_TAG = "tag"; + private static final String COL_WHEN_MS = "when_ms"; + private static final String COL_DEFAULTS = "defaults"; + private static final String COL_FLAGS = "flags"; + private static final String COL_PRIORITY = "priority"; + private static final String COL_CATEGORY = "category"; + private static final String COL_ACTION_COUNT = "action_count"; + + private static final int EVENT_TYPE_POST = 1; + private static final int EVENT_TYPE_CLICK = 2; + private static final int EVENT_TYPE_REMOVE = 3; + private static final int EVENT_TYPE_DISMISS = 4; + + private static long sLastPruneMs; + private static long sNumWrites; + + private final SQLiteOpenHelper mHelper; + private final Handler mWriteHandler; + + private static final long DAY_MS = 24 * 60 * 60 * 1000; + + public SQLiteLog(Context context) { + HandlerThread backgroundThread = new HandlerThread("notification-sqlite-log", + android.os.Process.THREAD_PRIORITY_BACKGROUND); + backgroundThread.start(); + mWriteHandler = new Handler(backgroundThread.getLooper()) { + @Override + public void handleMessage(Message msg) { + NotificationRecord r = (NotificationRecord) msg.obj; + long nowMs = System.currentTimeMillis(); + switch (msg.what) { + case MSG_POST: + writeEvent(r.sbn.getPostTime(), EVENT_TYPE_POST, r, true); + break; + case MSG_CLICK: + writeEvent(nowMs, EVENT_TYPE_CLICK, r, false); + break; + case MSG_REMOVE: + writeEvent(nowMs, EVENT_TYPE_REMOVE, r, false); + break; + case MSG_DISMISS: + writeEvent(nowMs, EVENT_TYPE_DISMISS, r, false); + break; + default: + Log.wtf(TAG, "Unknown message type: " + msg.what); + break; + } + } + }; + mHelper = new SQLiteOpenHelper(context, DB_NAME, null, DB_VERSION) { + @Override + public void onCreate(SQLiteDatabase db) { + db.execSQL("CREATE TABLE " + TAB_LOG + " (" + + "_id INTEGER PRIMARY KEY AUTOINCREMENT," + + COL_EVENT_USER_ID + " INT," + + COL_EVENT_TYPE + " INT," + + COL_EVENT_TIME + " INT," + + COL_KEY + " TEXT," + + COL_PKG + " TEXT," + + COL_NOTIFICATION_ID + " INT," + + COL_TAG + " TEXT," + + COL_WHEN_MS + " INT," + + COL_DEFAULTS + " INT," + + COL_FLAGS + " INT," + + COL_PRIORITY + " INT," + + COL_CATEGORY + " TEXT," + + COL_ACTION_COUNT + " INT" + + ")"); + } + + @Override + public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { + db.execSQL("DROP TABLE IF EXISTS " + TAB_LOG); + onCreate(db); + } + }; + } + + public void logPosted(NotificationRecord notification) { + mWriteHandler.sendMessage(mWriteHandler.obtainMessage(MSG_POST, notification)); + } + + public void logClicked(NotificationRecord notification) { + mWriteHandler.sendMessage(mWriteHandler.obtainMessage(MSG_CLICK, notification)); + } + + public void logRemoved(NotificationRecord notification) { + mWriteHandler.sendMessage(mWriteHandler.obtainMessage(MSG_REMOVE, notification)); + } + + public void logDismissed(NotificationRecord notification) { + mWriteHandler.sendMessage(mWriteHandler.obtainMessage(MSG_DISMISS, notification)); + } + + public void printPostFrequencies(PrintWriter pw, String indent) { + SQLiteDatabase db = mHelper.getReadableDatabase(); + long nowMs = System.currentTimeMillis(); + String q = "SELECT " + + COL_EVENT_USER_ID + ", " + + COL_PKG + ", " + + // Bucket by day by looking at 'floor((nowMs - eventTimeMs) / dayMs)' + "CAST(((" + nowMs + " - " + COL_EVENT_TIME + ") / " + DAY_MS + ") AS int) " + + "AS day, " + + "COUNT(*) AS cnt " + + "FROM " + TAB_LOG + " " + + "WHERE " + + COL_EVENT_TYPE + "=" + EVENT_TYPE_POST + " " + + "GROUP BY " + COL_EVENT_USER_ID + ", day, " + COL_PKG; + Cursor cursor = db.rawQuery(q, null); + try { + for (cursor.moveToFirst(); !cursor.isAfterLast(); cursor.moveToNext()) { + int userId = cursor.getInt(0); + String pkg = cursor.getString(1); + int day = cursor.getInt(2); + int count = cursor.getInt(3); + pw.println(indent + "post_frequency{user_id=" + userId + ",pkg=" + pkg + + ",day=" + day + ",count=" + count + "}"); + } + } finally { + cursor.close(); + } + } + + private void writeEvent(long eventTimeMs, int eventType, NotificationRecord r, + boolean populateNotificationDetails) { + ContentValues cv = new ContentValues(); + cv.put(COL_EVENT_USER_ID, r.sbn.getUser().getIdentifier()); + cv.put(COL_EVENT_TIME, eventTimeMs); + cv.put(COL_EVENT_TYPE, eventType); + putNotificationIdentifiers(r, cv); + if (populateNotificationDetails) { + putNotificationDetails(r, cv); + } + SQLiteDatabase db = mHelper.getWritableDatabase(); + if (db.insert(TAB_LOG, null, cv) < 0) { + Log.wtf(TAG, "Error while trying to insert values: " + cv); + } + sNumWrites++; + pruneIfNecessary(db); + } + + private void pruneIfNecessary(SQLiteDatabase db) { + // Prune if we haven't in a while. + long nowMs = System.currentTimeMillis(); + if (sNumWrites > PRUNE_MIN_WRITES || + nowMs - sLastPruneMs > PRUNE_MIN_DELAY_MS) { + sNumWrites = 0; + sLastPruneMs = nowMs; + long horizonStartMs = nowMs - HORIZON_MS; + int deletedRows = db.delete(TAB_LOG, COL_EVENT_TIME + " < ?", + new String[] { String.valueOf(horizonStartMs) }); + Log.d(TAG, "Pruned event entries: " + deletedRows); + } + } + + private static void putNotificationIdentifiers(NotificationRecord r, ContentValues outCv) { + outCv.put(COL_KEY, r.sbn.getKey()); + outCv.put(COL_PKG, r.sbn.getPackageName()); + } + + private static void putNotificationDetails(NotificationRecord r, ContentValues outCv) { + outCv.put(COL_NOTIFICATION_ID, r.sbn.getId()); + if (r.sbn.getTag() != null) { + outCv.put(COL_TAG, r.sbn.getTag()); + } + outCv.put(COL_WHEN_MS, r.sbn.getPostTime()); + outCv.put(COL_FLAGS, r.getNotification().flags); + outCv.put(COL_PRIORITY, r.getNotification().priority); + if (r.getNotification().category != null) { + outCv.put(COL_CATEGORY, r.getNotification().category); + } + outCv.put(COL_ACTION_COUNT, r.getNotification().actions != null ? + r.getNotification().actions.length : 0); + } + + public void dump(PrintWriter pw, String indent) { + printPostFrequencies(pw, indent); + } + } } diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java index 48e9737..bd28e04 100644 --- a/services/core/java/com/android/server/pm/LauncherAppsService.java +++ b/services/core/java/com/android/server/pm/LauncherAppsService.java @@ -239,8 +239,10 @@ public class LauncherAppsService extends ILauncherApps.Stub { private class MyPackageMonitor extends PackageMonitor { - /** Checks if user is a profile of or same as listeningUser. */ - private boolean isProfileOf(UserHandle user, UserHandle listeningUser, String debugMsg) { + /** Checks if user is a profile of or same as listeningUser. + * and the user is enabled. */ + private boolean isEnabledProfileOf(UserHandle user, UserHandle listeningUser, + String debugMsg) { if (user.getIdentifier() == listeningUser.getIdentifier()) { if (DEBUG) Log.d(TAG, "Delivering msg to same user " + debugMsg); return true; @@ -251,7 +253,8 @@ public class LauncherAppsService extends ILauncherApps.Stub { UserInfo listeningUserInfo = mUm.getUserInfo(listeningUser.getIdentifier()); if (userInfo == null || listeningUserInfo == null || userInfo.profileGroupId == UserInfo.NO_PROFILE_GROUP_ID - || userInfo.profileGroupId != listeningUserInfo.profileGroupId) { + || userInfo.profileGroupId != listeningUserInfo.profileGroupId + || !userInfo.isEnabled()) { if (DEBUG) { Log.d(TAG, "Not delivering msg from " + user + " to " + listeningUser + ":" + debugMsg); @@ -276,7 +279,7 @@ public class LauncherAppsService extends ILauncherApps.Stub { for (int i = 0; i < n; i++) { IOnAppsChangedListener listener = mListeners.getBroadcastItem(i); UserHandle listeningUser = (UserHandle) mListeners.getBroadcastCookie(i); - if (!isProfileOf(user, listeningUser, "onPackageAdded")) continue; + if (!isEnabledProfileOf(user, listeningUser, "onPackageAdded")) continue; try { listener.onPackageAdded(user, packageName); } catch (RemoteException re) { @@ -295,7 +298,7 @@ public class LauncherAppsService extends ILauncherApps.Stub { for (int i = 0; i < n; i++) { IOnAppsChangedListener listener = mListeners.getBroadcastItem(i); UserHandle listeningUser = (UserHandle) mListeners.getBroadcastCookie(i); - if (!isProfileOf(user, listeningUser, "onPackageRemoved")) continue; + if (!isEnabledProfileOf(user, listeningUser, "onPackageRemoved")) continue; try { listener.onPackageRemoved(user, packageName); } catch (RemoteException re) { @@ -314,7 +317,7 @@ public class LauncherAppsService extends ILauncherApps.Stub { for (int i = 0; i < n; i++) { IOnAppsChangedListener listener = mListeners.getBroadcastItem(i); UserHandle listeningUser = (UserHandle) mListeners.getBroadcastCookie(i); - if (!isProfileOf(user, listeningUser, "onPackageModified")) continue; + if (!isEnabledProfileOf(user, listeningUser, "onPackageModified")) continue; try { listener.onPackageChanged(user, packageName); } catch (RemoteException re) { @@ -333,7 +336,7 @@ public class LauncherAppsService extends ILauncherApps.Stub { for (int i = 0; i < n; i++) { IOnAppsChangedListener listener = mListeners.getBroadcastItem(i); UserHandle listeningUser = (UserHandle) mListeners.getBroadcastCookie(i); - if (!isProfileOf(user, listeningUser, "onPackagesAvailable")) continue; + if (!isEnabledProfileOf(user, listeningUser, "onPackagesAvailable")) continue; try { listener.onPackagesAvailable(user, packages, isReplacing()); } catch (RemoteException re) { @@ -352,7 +355,7 @@ public class LauncherAppsService extends ILauncherApps.Stub { for (int i = 0; i < n; i++) { IOnAppsChangedListener listener = mListeners.getBroadcastItem(i); UserHandle listeningUser = (UserHandle) mListeners.getBroadcastCookie(i); - if (!isProfileOf(user, listeningUser, "onPackagesUnavailable")) continue; + if (!isEnabledProfileOf(user, listeningUser, "onPackagesUnavailable")) continue; try { listener.onPackagesUnavailable(user, packages, isReplacing()); } catch (RemoteException re) { diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index 4318b0e..e746c1a 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -299,6 +299,7 @@ final class WindowState implements WindowManagerPolicy.WindowState { boolean mHasSurface = false; + boolean mNotOnAppsDisplay = false; DisplayContent mDisplayContent; /** When true this window can be displayed on screens owther than mOwnerUid's */ @@ -430,6 +431,10 @@ final class WindowState implements WindowManagerPolicy.WindowState { } mRootToken = appToken; mAppToken = appToken.appWindowToken; + if (mAppToken != null) { + final DisplayContent appDisplay = getDisplayContent(); + mNotOnAppsDisplay = displayContent != appDisplay; + } mWinAnimator = new WindowStateAnimator(this); mWinAnimator.mAlpha = a.alpha; @@ -717,7 +722,8 @@ final class WindowState implements WindowManagerPolicy.WindowState { } public DisplayContent getDisplayContent() { - return mAppToken == null ? mDisplayContent : getStack().getDisplayContent(); + return mAppToken == null || mNotOnAppsDisplay ? + mDisplayContent : getStack().getDisplayContent(); } public int getDisplayId() { diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index dcca837..9a9f1c8 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -100,6 +100,7 @@ import java.util.ArrayList; import java.util.Collections; import java.util.Date; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Set; @@ -232,6 +233,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { static class ActiveAdmin { private static final String TAG_DISABLE_KEYGUARD_FEATURES = "disable-keyguard-features"; private static final String TAG_DISABLE_CAMERA = "disable-camera"; + private static final String TAG_DISABLE_ACCOUNT_MANAGEMENT = "disable-account-management"; + private static final String TAG_ACCOUNT_TYPE = "account-type"; private static final String TAG_ENCRYPTION_REQUESTED = "encryption-requested"; private static final String TAG_PASSWORD_EXPIRATION_DATE = "password-expiration-date"; private static final String TAG_PASSWORD_EXPIRATION_TIMEOUT = "password-expiration-timeout"; @@ -297,6 +300,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { boolean encryptionRequested = false; boolean disableCamera = false; + Set<String> accountTypesWithManagementDisabled = new HashSet<String>(); // TODO: review implementation decisions with frameworks team boolean specifiesGlobalProxy = false; @@ -413,6 +417,15 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { out.attribute(null, ATTR_VALUE, Integer.toString(disabledKeyguardFeatures)); out.endTag(null, TAG_DISABLE_KEYGUARD_FEATURES); } + if (!accountTypesWithManagementDisabled.isEmpty()) { + out.startTag(null, TAG_DISABLE_ACCOUNT_MANAGEMENT); + for (String ac : accountTypesWithManagementDisabled) { + out.startTag(null, TAG_ACCOUNT_TYPE); + out.attribute(null, ATTR_VALUE, ac); + out.endTag(null, TAG_ACCOUNT_TYPE); + } + out.endTag(null, TAG_DISABLE_ACCOUNT_MANAGEMENT); + } } void readFromXml(XmlPullParser parser) @@ -484,6 +497,23 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } else if (TAG_DISABLE_KEYGUARD_FEATURES.equals(tag)) { disabledKeyguardFeatures = Integer.parseInt( parser.getAttributeValue(null, ATTR_VALUE)); + } else if (TAG_DISABLE_ACCOUNT_MANAGEMENT.equals(tag)) { + int outerDepthDAM = parser.getDepth(); + int typeDAM; + while ((typeDAM=parser.next()) != XmlPullParser.END_DOCUMENT + && (typeDAM != XmlPullParser.END_TAG + || parser.getDepth() > outerDepthDAM)) { + if (typeDAM == XmlPullParser.END_TAG || typeDAM == XmlPullParser.TEXT) { + continue; + } + String tagDAM = parser.getName(); + if (TAG_ACCOUNT_TYPE.equals(tagDAM)) { + accountTypesWithManagementDisabled.add( + parser.getAttributeValue(null, ATTR_VALUE)); + } else { + Slog.w(LOG_TAG, "Unknown tag under " + tag + ": " + tagDAM); + } + } } else { Slog.w(LOG_TAG, "Unknown admin tag: " + tag); } @@ -3178,7 +3208,6 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { if (who == null) { throw new NullPointerException("ComponentName is null"); } - getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER); int userId = UserHandle.getCallingUserId(); @@ -3277,4 +3306,42 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { ApplicationInfo appInfo = pm.getApplicationInfo(packageName, 0, userId); return (appInfo.flags & ApplicationInfo.FLAG_SYSTEM) > 0; } + + @Override + public void setAccountManagementDisabled(ComponentName who, String accountType, + boolean disabled) { + if (!mHasFeature) { + return; + } + synchronized (this) { + if (who == null) { + throw new NullPointerException("ComponentName is null"); + } + ActiveAdmin ap = getActiveAdminForCallerLocked(who, + DeviceAdminInfo.USES_POLICY_PROFILE_OWNER); + if (disabled) { + ap.accountTypesWithManagementDisabled.add(accountType); + } else { + ap.accountTypesWithManagementDisabled.remove(accountType); + } + saveSettingsLocked(UserHandle.getCallingUserId()); + } + } + + @Override + public String[] getAccountTypesWithManagementDisabled() { + if (!mHasFeature) { + return null; + } + synchronized (this) { + DevicePolicyData policy = getUserData(UserHandle.getCallingUserId()); + final int N = policy.mAdminList.size(); + HashSet<String> resultSet = new HashSet<String>(); + for (int i = 0; i < N; i++) { + ActiveAdmin admin = policy.mAdminList.get(i); + resultSet.addAll(admin.accountTypesWithManagementDisabled); + } + return resultSet.toArray(new String[resultSet.size()]); + } + } } diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index 722b797..716823c 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -605,6 +605,14 @@ public final class SystemServer { if (!disableNetwork) { try { + Slog.i(TAG, "Network Score Service"); + networkScore = new NetworkScoreService(context); + ServiceManager.addService(Context.NETWORK_SCORE_SERVICE, networkScore); + } catch (Throwable e) { + reportWtf("starting Network Score Service", e); + } + + try { Slog.i(TAG, "NetworkStats Service"); networkStats = new NetworkStatsService(context, networkManagement, alarm); ServiceManager.addService(Context.NETWORK_STATS_SERVICE, networkStats); @@ -642,6 +650,15 @@ public final class SystemServer { } try { + Slog.i(TAG, "Wi-Fi Scanning Service"); + mSystemServiceManager.startService( + "com.android.server.wifi.WifiScanningService"); + + } catch (Throwable e) { + reportWtf("starting Wi-Fi Scanning Service", e); + } + + try { Slog.i(TAG, "Connectivity Service"); connectivity = new ConnectivityService( context, networkManagement, networkStats, networkPolicy); @@ -653,14 +670,6 @@ public final class SystemServer { } try { - Slog.i(TAG, "Network Score Service"); - networkScore = new NetworkScoreService(context); - ServiceManager.addService(Context.NETWORK_SCORE_SERVICE, networkScore); - } catch (Throwable e) { - reportWtf("starting Network Score Service", e); - } - - try { Slog.i(TAG, "Network Service Discovery Service"); serviceDiscovery = NsdService.create(context); ServiceManager.addService( diff --git a/core/res/res/drawable/stat_sys_adb.xml b/wifi/java/android/net/wifi/IWifiScanner.aidl index dfc8563..fef2d11 100644 --- a/core/res/res/drawable/stat_sys_adb.xml +++ b/wifi/java/android/net/wifi/IWifiScanner.aidl @@ -1,13 +1,11 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- /* - * Copyright 2013, The Android Open Source Project + * Copyright (C) 2008 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 + * 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, @@ -15,9 +13,15 @@ * See the License for the specific language governing permissions and * limitations under the License. */ ---> -<bitmap xmlns:android="http://schemas.android.com/apk/res/android" - android:src="@drawable/stat_sys_adb_am" - android:autoMirrored="true"> -</bitmap> +package android.net.wifi; + +import android.os.Messenger; + +/** + * {@hide} + */ +interface IWifiScanner +{ + Messenger getMessenger(); +} diff --git a/wifi/java/android/net/wifi/ScanResult.java b/wifi/java/android/net/wifi/ScanResult.java index d7ecaff..1cb9546 100644 --- a/wifi/java/android/net/wifi/ScanResult.java +++ b/wifi/java/android/net/wifi/ScanResult.java @@ -56,6 +56,13 @@ public class ScanResult implements Parcelable { public long timestamp; /** + * Timestamp representing date when this result was last seen, in milliseconds from 1970 + * {@hide} + */ + public long seen; + + + /** * The approximate distance to the AP in centimeter, if available. Else * {@link UNSPECIFIED}. * {@hide} @@ -114,9 +121,17 @@ public class ScanResult implements Parcelable { timestamp = source.timestamp; distanceCm = source.distanceCm; distanceSdCm = source.distanceSdCm; + seen = source.seen; } } + /** empty scan result + * + * {@hide} + * */ + public ScanResult() { + } + @Override public String toString() { StringBuffer sb = new StringBuffer(); diff --git a/wifi/java/android/net/wifi/WifiConfiguration.java b/wifi/java/android/net/wifi/WifiConfiguration.java index 6562462..ce8c8b8 100644 --- a/wifi/java/android/net/wifi/WifiConfiguration.java +++ b/wifi/java/android/net/wifi/WifiConfiguration.java @@ -21,6 +21,7 @@ import android.os.Parcelable; import android.os.Parcel; import android.text.TextUtils; +import java.util.HashMap; import java.util.BitSet; /** @@ -302,6 +303,156 @@ public class WifiConfiguration implements Parcelable { /** * @hide + * dhcp server MAC address if known + */ + public String dhcpServer; + + /** + * @hide + * default Gateway MAC address if known + */ + public String defaultGwMacAddress; + + /** + * @hide + * BSSID list on which this configuration was seen. + * TODO: prevent this list to grow infinitely, age-out the results + */ + public HashMap<String, ScanResult> scanResultCache; + + /** @hide **/ + public static int INVALID_RSSI = -127; + + /** + * @hide + * A summary of the RSSI and Band status for that configuration + * This is used as a temporary value by the auto-join controller + */ + public final class Visibility + { + public int rssi5; // strongest 5GHz RSSI + public int rssi24; // strongest 2.4GHz RSSI + public int num5; // number of BSSIDs on 5GHz + public int num24; // number of BSSIDs on 2.4GHz + public long age5; // timestamp of the strongest 5GHz BSSID (last time it was seen) + public long age24; // timestamp of the strongest 2.4GHz BSSID (last time it was seen) + public Visibility() + { + rssi5 = INVALID_RSSI; + rssi24 = INVALID_RSSI; + } + public Visibility(Visibility source) + { + rssi5 = source.rssi5; + rssi24 = source.rssi24; + age24 = source.age24; + age5 = source.age5; + num24 = source.num24; + num5 = source.num5; + } + } + + /** @hide + * Cache the visibility status of this configuration. + * Visibility can change at any time depending on scan results availability. + * Owner of the WifiConfiguration is responsible to set this field based on + * recent scan results. + ***/ + public Visibility visibility; + + /** @hide + * calculate and set Visibility for that configuration. + * + * age in milliseconds: we will consider only ScanResults that are more recent, + * i.e. younger. + ***/ + public Visibility setVisibility(long age) { + if (scanResultCache == null) { + visibility = null; + return null; + } + + Visibility status = new Visibility(); + + long now_ms = System.currentTimeMillis(); + for(ScanResult result : scanResultCache.values()) { + if (result.seen == 0) + continue; + + if ((result.frequency > 4900) && (result.frequency < 5900)) { + //strictly speaking: [4915, 5825] + //number of known BSSID on 5GHz band + status.num5 = status.num5 + 1; + } else if ((result.frequency > 2400) && (result.frequency < 2500)) { + //strictly speaking: [2412, 2482] + //number of known BSSID on 2.4Ghz band + status.num24 = status.num24 + 1; + } + + if ((now_ms - result.seen) > age) continue; + + if ((result.frequency > 4900) && (result.frequency < 5900)) { + if (result.level > status.rssi5) { + status.rssi5 = result.level; + status.age5 = result.seen; + } + } else if ((result.frequency > 2400) && (result.frequency < 2500)) { + if (result.level > status.rssi24) { + status.rssi24 = result.level; + status.age24 = result.seen; + } + } + } + visibility = status; + return status; + } + + /** @hide */ + public static final int AUTO_JOIN_ENABLED = 0; + /** @hide */ + public static final int AUTO_JOIN_DISABLED_ON_AUTH_FAILURE = 1; + /** + * @hide + */ + public int autoJoinStatus; + + /** + * @hide + * Indicate that a WifiConfiguration is temporary and should not be saved + * nor considered by AutoJoin. + */ + public boolean ephemeral; + + /** + * @hide + * Connect choices + * + * remember the keys identifying the known WifiConfiguration over which this configuration + * was preferred by user or a "WiFi Network Management app", that is, + * a WifiManager.CONNECT_NETWORK or SELECT_NETWORK was received while this configuration + * was visible to the user: + * configKey is : "SSID"-WEP-WPA_PSK-WPA_EAP + * + * The integer represents the configuration's RSSI at that time (useful?) + * + * The overall auto-join algorithm make use of past connect choice so as to sort configuration, + * the exact algorithm still fluctuating as of 5/7/2014 + * + */ + public HashMap<String, Integer> connectChoices; + + /** + * @hide + * Linked Configurations: represent the set of Wificonfigurations that are equivalent + * regarding roaming and auto-joining. + * The linked configuration may or may not have same SSID, and may or may not have same + * credentials. + * For instance, linked configurations will have same defaultGwMacAddress or same dhcp server. + */ + public HashMap<String, Integer> linkedConfigurations; + + /** + * @hide */ public enum ProxySettings { /* No proxy is to be used. Any existing proxy settings @@ -346,6 +497,7 @@ public class WifiConfiguration implements Parcelable { ipAssignment = IpAssignment.UNASSIGNED; proxySettings = ProxySettings.UNASSIGNED; linkProperties = new LinkProperties(); + autoJoinStatus = AUTO_JOIN_ENABLED; } /** @@ -369,6 +521,32 @@ public class WifiConfiguration implements Parcelable { // TODO: Add more checks return true; + + } + + /** + * most recent time we have seen this configuration + * @return most recent scanResult + * @hide + */ + public ScanResult lastSeen() { + ScanResult mostRecent = null; + + if (scanResultCache == null) { + return null; + } + + for (ScanResult result : scanResultCache.values()) { + if (mostRecent == null) { + if (result.seen != 0) + mostRecent = result; + } else { + if (result.seen > mostRecent.seen) { + mostRecent = result; + } + } + } + return mostRecent; } @Override @@ -570,7 +748,48 @@ public class WifiConfiguration implements Parcelable { return KeyMgmt.NONE; } - /** Implement the Parcelable interface {@hide} */ + /* @hide + * Cache the config key, this seems useful as a speed up since a lot of + * lookups in the config store are done and based on this key. + */ + String mCachedConfigKey; + + /** @hide + * return the string used to calculate the hash in WifiConfigStore + * and uniquely identify this WifiConfiguration + */ + public String configKey(boolean allowCached) { + String key; + if (allowCached && mCachedConfigKey != null) { + key = mCachedConfigKey; + } else { + key = this.SSID; + if (key == null) + key = ""; + if (this.wepKeys[0] != null) { + key = key + "-WEP"; + } + if (this.allowedKeyManagement.get(KeyMgmt.WPA_PSK)) { + key = key + "-" + KeyMgmt.strings[KeyMgmt.WPA_PSK]; + } + if (this.allowedKeyManagement.get(KeyMgmt.WPA_EAP) || + this.allowedKeyManagement.get(KeyMgmt.IEEE8021X)) { + key = key + "-" + KeyMgmt.strings[KeyMgmt.WPA_EAP]; + } + mCachedConfigKey = key; + } + return key; + } + + /** @hide + * get configKey, force calculating the config string + */ + public String configKey() { + return configKey(false); + } + + + /** Implement the Parcelable interface {@hide} */ public int describeContents() { return 0; } @@ -603,8 +822,32 @@ public class WifiConfiguration implements Parcelable { ipAssignment = source.ipAssignment; proxySettings = source.proxySettings; + + defaultGwMacAddress = source.defaultGwMacAddress; + linkProperties = new LinkProperties(source.linkProperties); - } + if ((source.scanResultCache != null) && (source.scanResultCache.size() > 0)) { + scanResultCache = new HashMap<String, ScanResult>(); + scanResultCache.putAll(source.scanResultCache); + } + + if ((source.connectChoices != null) && (source.connectChoices.size() > 0)) { + connectChoices = new HashMap<String, Integer>(); + connectChoices.putAll(source.connectChoices); + } + + if ((source.linkedConfigurations != null) + && (source.linkedConfigurations.size() > 0)) { + linkedConfigurations = new HashMap<String, Integer>(); + linkedConfigurations.putAll(source.linkedConfigurations); + } + mCachedConfigKey = null; //force null configKey + autoJoinStatus = source.autoJoinStatus; + + if (source.visibility != null) { + visibility = new Visibility(source.visibility); + } + } } /** Implement the Parcelable interface {@hide} */ @@ -633,6 +876,10 @@ public class WifiConfiguration implements Parcelable { dest.writeString(ipAssignment.name()); dest.writeString(proxySettings.name()); dest.writeParcelable(linkProperties, flags); + + dest.writeString(dhcpServer); + dest.writeString(defaultGwMacAddress); + dest.writeInt(autoJoinStatus); } /** Implement the Parcelable interface {@hide} */ @@ -664,6 +911,10 @@ public class WifiConfiguration implements Parcelable { config.proxySettings = ProxySettings.valueOf(in.readString()); config.linkProperties = in.readParcelable(null); + config.dhcpServer = in.readString(); + config.defaultGwMacAddress = in.readString(); + config.autoJoinStatus = in.readInt(); + return config; } diff --git a/wifi/java/android/net/wifi/WifiScanner.java b/wifi/java/android/net/wifi/WifiScanner.java new file mode 100644 index 0000000..e02e14c --- /dev/null +++ b/wifi/java/android/net/wifi/WifiScanner.java @@ -0,0 +1,584 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net.wifi; + +import android.content.Context; +import android.os.Handler; +import android.os.HandlerThread; +import android.os.Looper; +import android.os.Message; +import android.os.Messenger; +import android.os.Parcel; +import android.os.Parcelable; +import android.os.RemoteException; +import android.util.Log; +import android.util.SparseArray; + +import com.android.internal.util.AsyncChannel; +import com.android.internal.util.Protocol; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CountDownLatch; + + +/** + * This class provides a way to scan the Wifi universe around the device + * Get an instance of this class by calling + * {@link android.content.Context#getSystemService(String) Context.getSystemService(Context + * .WIFI_SCANNING_SERVICE)}. + * @hide + */ +public class WifiScanner { + + public static final int WIFI_BAND_UNSPECIFIED = 0; /* not specified */ + public static final int WIFI_BAND_24_GHZ = 1; /* 2.4 GHz band */ + public static final int WIFI_BAND_5_GHZ = 2; /* 5 GHz band without DFS channels */ + public static final int WIFI_BAND_5_GHZ_DFS_ONLY = 4; /* 5 GHz band with DFS channels */ + public static final int WIFI_BAND_5_GHZ_WITH_DFS = 6; /* 5 GHz band with DFS channels */ + public static final int WIFI_BAND_BOTH = 3; /* both bands without DFS channels */ + public static final int WIFI_BAND_BOTH_WITH_DFS = 7; /* both bands with DFS channels */ + + public static final int MIN_SCAN_PERIOD_MS = 300; /* minimum supported period */ + public static final int MAX_SCAN_PERIOD_MS = 1024000; /* maximum supported period */ + + public static final int REASON_SUCCEEDED = 0; + public static final int REASON_UNSPECIFIED = -1; + public static final int REASON_INVALID_LISTENER = -2; + public static final int REASON_INVALID_REQUEST = -3; + public static final int REASON_CONFLICTING_REQUEST = -4; + + public static interface ActionListener { + public void onSuccess(Object result); + public void onFailure(int reason, Object exception); + } + + /** + * gives you all the possible channels; channel is specified as an + * integer with frequency in MHz i.e. channel 1 is 2412 + */ + public List<Integer> getAvailableChannels(int band) { + return null; + } + + /** + * provides channel specification to the APIs + */ + public static class ChannelSpec { + public int frequency; + public boolean passive; /* ignored on DFS channels */ + public int dwellTimeMS; /* not supported for now */ + + public ChannelSpec(int frequency) { + this.frequency = frequency; + passive = false; + dwellTimeMS = 0; + } + } + + public static final int REPORT_EVENT_AFTER_BUFFER_FULL = 0; + public static final int REPORT_EVENT_AFTER_EACH_SCAN = 1; + public static final int REPORT_EVENT_FULL_SCAN_RESULT = 2; + + /** + * scan configuration parameters + */ + public static class ScanSettings implements Parcelable { + + public int band; /* ignore channels if specified */ + public ChannelSpec[] channels; /* list of channels to scan */ + public int periodInMs; /* period of scan */ + public int reportEvents; /* a valid REPORT_EVENT value */ + + /** Implement the Parcelable interface {@hide} */ + public int describeContents() { + return 0; + } + + /** Implement the Parcelable interface {@hide} */ + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(band); + dest.writeInt(periodInMs); + dest.writeInt(channels.length); + + for (int i = 0; i < channels.length; i++) { + dest.writeInt(channels[i].frequency); + dest.writeInt(channels[i].dwellTimeMS); + dest.writeInt(channels[i].passive ? 1 : 0); + } + } + + /** Implement the Parcelable interface {@hide} */ + public static final Creator<ScanSettings> CREATOR = + new Creator<ScanSettings>() { + public ScanSettings createFromParcel(Parcel in) { + + ScanSettings settings = new ScanSettings(); + settings.band = in.readInt(); + settings.periodInMs = in.readInt(); + int num_channels = in.readInt(); + settings.channels = new ChannelSpec[num_channels]; + for (int i = 0; i < num_channels; i++) { + int frequency = in.readInt(); + + ChannelSpec spec = new ChannelSpec(frequency); + spec.dwellTimeMS = in.readInt(); + spec.passive = in.readInt() == 1; + settings.channels[i] = spec; + } + + return settings; + } + + public ScanSettings[] newArray(int size) { + return new ScanSettings[size]; + } + }; + + } + + public static class InformationElement { + public int id; + public byte[] bytes; + } + + public static class FullScanResult { + public ScanResult result; + public InformationElement informationElements[]; + } + + /** @hide */ + public static class ParcelableScanResults implements Parcelable { + public ScanResult mResults[]; + + public ParcelableScanResults(ScanResult[] results) { + mResults = results; + } + + public ScanResult[] getResults() { + return mResults; + } + + /** Implement the Parcelable interface {@hide} */ + public int describeContents() { + return 0; + } + + /** Implement the Parcelable interface {@hide} */ + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(mResults.length); + for (int i = 0; i < mResults.length; i++) { + ScanResult result = mResults[i]; + result.writeToParcel(dest, flags); + } + } + + /** Implement the Parcelable interface {@hide} */ + public static final Creator<ParcelableScanResults> CREATOR = + new Creator<ParcelableScanResults>() { + public ParcelableScanResults createFromParcel(Parcel in) { + int n = in.readInt(); + ScanResult results[] = new ScanResult[n]; + for (int i = 0; i < n; i++) { + results[i] = ScanResult.CREATOR.createFromParcel(in); + } + return new ParcelableScanResults(results); + } + + public ParcelableScanResults[] newArray(int size) { + return new ParcelableScanResults[size]; + } + }; + } + + /** + * Framework is co-ordinating scans across multiple apps; so it may not give exactly the + * same period requested. The period granted is stated on the onSuccess() event; and + * onPeriodChanged() will be called if/when it is changed because of multiple conflicting + * requests. This is similar to the way timers are handled. + */ + public interface ScanListener extends ActionListener { + public void onPeriodChanged(int periodInMs); + public void onResults(ScanResult[] results); + public void onFullResult(FullScanResult fullScanResult); + } + + public void scan(ScanSettings settings, ScanListener listener) { + validateChannel(); + sAsyncChannel.sendMessage(CMD_SCAN, 0, putListener(listener), settings); + } + public void startBackgroundScan(ScanSettings settings, ScanListener listener) { + validateChannel(); + sAsyncChannel.sendMessage(CMD_START_BACKGROUND_SCAN, 0, putListener(listener), settings); + } + public void stopBackgroundScan(boolean flush, ScanListener listener) { + validateChannel(); + sAsyncChannel.sendMessage(CMD_STOP_BACKGROUND_SCAN, 0, removeListener(listener)); + } + public void retrieveScanResults(boolean flush, ScanListener listener) { + validateChannel(); + sAsyncChannel.sendMessage(CMD_GET_SCAN_RESULTS, 0, getListenerKey(listener)); + } + + public static class HotspotInfo { + public String bssid; + public int low; /* minimum RSSI */ + public int high; /* maximum RSSI */ + } + + public static class WifiChangeSettings { + public int rssiSampleSize; /* sample size for RSSI averaging */ + public int lostApSampleSize; /* samples to confirm AP's loss */ + public int unchangedSampleSize; /* samples to confirm no change */ + public int minApsBreachingThreshold; /* change threshold to trigger event */ + public HotspotInfo[] hotspotInfos; + } + + /* overrides the significant wifi change state machine configuration */ + public void configureSignificantWifiChange( + int rssiSampleSize, /* sample size for RSSI averaging */ + int lostApSampleSize, /* samples to confirm AP's loss */ + int unchangedSampleSize, /* samples to confirm no change */ + int minApsBreachingThreshold, /* change threshold to trigger event */ + HotspotInfo[] hotspotInfos /* signal thresholds to crosss */ + ) + { + validateChannel(); + WifiChangeSettings settings = new WifiChangeSettings(); + settings.rssiSampleSize = rssiSampleSize; + settings.lostApSampleSize = lostApSampleSize; + settings.unchangedSampleSize = unchangedSampleSize; + settings.minApsBreachingThreshold = minApsBreachingThreshold; + settings.hotspotInfos = hotspotInfos; + + sAsyncChannel.sendMessage(CMD_CONFIGURE_WIFI_CHANGE, 0, 0, settings); + } + + public interface SignificantWifiChangeListener extends ActionListener { + public void onChanging(ScanResult[] results); /* changes are found */ + public void onQuiescence(ScanResult[] results); /* changes settled down */ + } + + public void trackSignificantWifiChange(SignificantWifiChangeListener listener) { + validateChannel(); + sAsyncChannel.sendMessage(CMD_START_TRACKING_CHANGE, 0, putListener(listener)); + } + public void untrackSignificantWifiChange(SignificantWifiChangeListener listener) { + validateChannel(); + sAsyncChannel.sendMessage(CMD_STOP_TRACKING_CHANGE, 0, removeListener(listener)); + } + + public void configureSignificantWifiChange(WifiChangeSettings settings) { + validateChannel(); + sAsyncChannel.sendMessage(CMD_CONFIGURE_WIFI_CHANGE, 0, 0, settings); + } + + public static interface HotlistListener extends ActionListener { + public void onFound(ScanResult[] results); + } + + /** @hide */ + public static class HotlistSettings implements Parcelable { + public HotspotInfo[] hotspotInfos; + public int apLostThreshold; + + /** Implement the Parcelable interface {@hide} */ + public int describeContents() { + return 0; + } + + /** Implement the Parcelable interface {@hide} */ + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(apLostThreshold); + dest.writeInt(hotspotInfos.length); + for (int i = 0; i < hotspotInfos.length; i++) { + HotspotInfo info = hotspotInfos[i]; + dest.writeString(info.bssid); + dest.writeInt(info.low); + dest.writeInt(info.high); + } + } + + /** Implement the Parcelable interface {@hide} */ + public static final Creator<HotlistSettings> CREATOR = + new Creator<HotlistSettings>() { + public HotlistSettings createFromParcel(Parcel in) { + HotlistSettings settings = new HotlistSettings(); + settings.apLostThreshold = in.readInt(); + int n = in.readInt(); + settings.hotspotInfos = new HotspotInfo[n]; + for (int i = 0; i < n; i++) { + HotspotInfo info = new HotspotInfo(); + info.bssid = in.readString(); + info.low = in.readInt(); + info.high = in.readInt(); + settings.hotspotInfos[i] = info; + } + return settings; + } + + public HotlistSettings[] newArray(int size) { + return new HotlistSettings[size]; + } + }; + } + + public void setHotlist(HotspotInfo[] hotspots, + int apLostThreshold, HotlistListener listener) { + validateChannel(); + HotlistSettings settings = new HotlistSettings(); + settings.hotspotInfos = hotspots; + sAsyncChannel.sendMessage(CMD_SET_HOTLIST, 0, putListener(listener), settings); + } + + public void resetHotlist(HotlistListener listener) { + validateChannel(); + sAsyncChannel.sendMessage(CMD_RESET_HOTLIST, 0, removeListener(listener)); + } + + + /* private members and methods */ + + private static final String TAG = "WifiScanner"; + private static final boolean DBG = true; + + /* commands for Wifi Service */ + private static final int BASE = Protocol.BASE_WIFI_SCANNER; + + /** @hide */ + public static final int CMD_SCAN = BASE + 0; + /** @hide */ + public static final int CMD_START_BACKGROUND_SCAN = BASE + 2; + /** @hide */ + public static final int CMD_STOP_BACKGROUND_SCAN = BASE + 3; + /** @hide */ + public static final int CMD_GET_SCAN_RESULTS = BASE + 4; + /** @hide */ + public static final int CMD_SCAN_RESULT = BASE + 5; + /** @hide */ + public static final int CMD_SET_HOTLIST = BASE + 6; + /** @hide */ + public static final int CMD_RESET_HOTLIST = BASE + 7; + /** @hide */ + public static final int CMD_AP_FOUND = BASE + 9; + /** @hide */ + public static final int CMD_AP_LOST = BASE + 10; + /** @hide */ + public static final int CMD_START_TRACKING_CHANGE = BASE + 11; + /** @hide */ + public static final int CMD_STOP_TRACKING_CHANGE = BASE + 12; + /** @hide */ + public static final int CMD_CONFIGURE_WIFI_CHANGE = BASE + 13; + /** @hide */ + public static final int CMD_WIFI_CHANGE_DETECTED = BASE + 15; + /** @hide */ + public static final int CMD_WIFI_CHANGES_STABILIZED = BASE + 16; + /** @hide */ + public static final int CMD_OP_SUCCEEDED = BASE + 17; + /** @hide */ + public static final int CMD_OP_FAILED = BASE + 18; + /** @hide */ + public static final int CMD_PERIOD_CHANGED = BASE + 19; + /** @hide */ + public static final int CMD_FULL_SCAN_RESULT = BASE + 20; + + private Context mContext; + private IWifiScanner mService; + + private static final int INVALID_KEY = 0; + private static int sListenerKey = 1; + + private static final SparseArray sListenerMap = new SparseArray(); + private static final Object sListenerMapLock = new Object(); + + private static AsyncChannel sAsyncChannel; + private static CountDownLatch sConnected; + + private static final Object sThreadRefLock = new Object(); + private static int sThreadRefCount; + private static HandlerThread sHandlerThread; + + /** + * Create a new WifiScanner instance. + * Applications will almost always want to use + * {@link android.content.Context#getSystemService Context.getSystemService()} to retrieve + * the standard {@link android.content.Context#WIFI_SERVICE Context.WIFI_SERVICE}. + * @param context the application context + * @param service the Binder interface + * @hide + */ + public WifiScanner(Context context, IWifiScanner service) { + mContext = context; + mService = service; + init(); + } + + private void init() { + synchronized (sThreadRefLock) { + if (++sThreadRefCount == 1) { + Messenger messenger = null; + try { + messenger = mService.getMessenger(); + } catch (RemoteException e) { + /* do nothing */ + } catch (SecurityException e) { + /* do nothing */ + } + + if (messenger == null) { + sAsyncChannel = null; + return; + } + + sHandlerThread = new HandlerThread("WifiScanner"); + sAsyncChannel = new AsyncChannel(); + sConnected = new CountDownLatch(1); + + sHandlerThread.start(); + Handler handler = new ServiceHandler(sHandlerThread.getLooper()); + sAsyncChannel.connect(mContext, handler, messenger); + try { + sConnected.await(); + } catch (InterruptedException e) { + Log.e(TAG, "interrupted wait at init"); + } + } + } + } + + private void validateChannel() { + if (sAsyncChannel == null) throw new IllegalStateException( + "No permission to access and change wifi or a bad initialization"); + } + + private static int putListener(Object listener) { + if (listener == null) return INVALID_KEY; + int key; + synchronized (sListenerMapLock) { + do { + key = sListenerKey++; + } while (key == INVALID_KEY); + sListenerMap.put(key, listener); + } + return key; + } + + private static Object getListener(int key) { + if (key == INVALID_KEY) return null; + synchronized (sListenerMapLock) { + Object listener = sListenerMap.get(key); + return listener; + } + } + + private static int getListenerKey(Object listener) { + if (listener == null) return INVALID_KEY; + synchronized (sListenerMapLock) { + int index = sListenerMap.indexOfValue(listener); + if (index == -1) { + return INVALID_KEY; + } else { + return sListenerMap.keyAt(index); + } + } + } + + private static Object removeListener(int key) { + if (key == INVALID_KEY) return null; + synchronized (sListenerMapLock) { + Object listener = sListenerMap.get(key); + sListenerMap.remove(key); + return listener; + } + } + + private static int removeListener(Object listener) { + int key = getListenerKey(listener); + if (key == INVALID_KEY) return key; + synchronized (sListenerMapLock) { + sListenerMap.remove(key); + return key; + } + } + + private static class ServiceHandler extends Handler { + ServiceHandler(Looper looper) { + super(looper); + } + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case AsyncChannel.CMD_CHANNEL_HALF_CONNECTED: + if (msg.arg1 == AsyncChannel.STATUS_SUCCESSFUL) { + sAsyncChannel.sendMessage(AsyncChannel.CMD_CHANNEL_FULL_CONNECTION); + } else { + Log.e(TAG, "Failed to set up channel connection"); + // This will cause all further async API calls on the WifiManager + // to fail and throw an exception + sAsyncChannel = null; + } + sConnected.countDown(); + return; + case AsyncChannel.CMD_CHANNEL_FULLY_CONNECTED: + return; + case AsyncChannel.CMD_CHANNEL_DISCONNECTED: + Log.e(TAG, "Channel connection lost"); + // This will cause all further async API calls on the WifiManager + // to fail and throw an exception + sAsyncChannel = null; + getLooper().quit(); + return; + } + + Object listener = getListener(msg.arg2); + if (DBG) Log.d(TAG, "listener key = " + msg.arg2); + + switch (msg.what) { + /* ActionListeners grouped together */ + case CMD_OP_SUCCEEDED : + ((ActionListener) listener).onSuccess(msg.obj); + break; + case CMD_OP_FAILED : + ((ActionListener) listener).onFailure(msg.arg1, msg.obj); + break; + case CMD_SCAN_RESULT : + ((ScanListener) listener).onResults( + ((ParcelableScanResults) msg.obj).getResults()); + return; + case CMD_FULL_SCAN_RESULT : + FullScanResult result = (FullScanResult) msg.obj; + ((ScanListener) listener).onFullResult(result); + return; + case CMD_AP_FOUND: + ((HotlistListener) listener).onFound( + ((ParcelableScanResults) msg.obj).getResults()); + return; + case CMD_WIFI_CHANGE_DETECTED: + ((SignificantWifiChangeListener) listener).onChanging( + ((ParcelableScanResults) msg.obj).getResults()); + return; + case CMD_WIFI_CHANGES_STABILIZED: + ((SignificantWifiChangeListener) listener).onQuiescence( + ((ParcelableScanResults) msg.obj).getResults()); + return; + default: + if (DBG) Log.d(TAG, "Ignoring message " + msg.what); + return; + } + } + } +} |