diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index cd1fbf6fff61f..12a8ff6b98590 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -611,6 +611,13 @@ public class Notification implements Parcelable
*/
public static final String EXTRA_AS_HEADS_UP = "headsup";
+ /**
+ * Extra added from {@link Notification.Builder} to indicate that the remote views were inflated
+ * from the builder, as opposed to being created directly from the application.
+ * @hide
+ */
+ public static final String EXTRA_BUILDER_REMOTE_VIEWS = "android.builderRemoteViews";
+
/**
* Value for {@link #EXTRA_AS_HEADS_UP}.
* @hide
@@ -1273,6 +1280,7 @@ public class Notification implements Parcelable
private boolean mShowWhen = true;
private int mVisibility = VISIBILITY_PRIVATE;
private Notification mPublicVersion = null;
+ private boolean mQuantumTheme;
/**
* Constructs a new Builder with the defaults:
@@ -1300,6 +1308,9 @@ public class Notification implements Parcelable
mWhen = System.currentTimeMillis();
mAudioStreamType = STREAM_DEFAULT;
mPriority = PRIORITY_DEFAULT;
+
+ // TODO: Decide on targetSdk from calling app whether to use quantum theme.
+ mQuantumTheme = true;
}
/**
@@ -1807,7 +1818,7 @@ public class Notification implements Parcelable
contentView.setImageViewBitmap(R.id.icon, mLargeIcon);
smallIconImageViewId = R.id.right_icon;
}
- if (mPriority < PRIORITY_LOW) {
+ 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,
@@ -1921,7 +1932,7 @@ public class Notification implements Parcelable
if (mContentView != null) {
return mContentView;
} else {
- return applyStandardTemplate(R.layout.notification_template_base, true); // no more special large_icon flavor
+ return applyStandardTemplate(getBaseLayoutResource(), true); // no more special large_icon flavor
}
}
@@ -1942,21 +1953,21 @@ public class Notification implements Parcelable
private RemoteViews makeBigContentView() {
if (mActions.size() == 0) return null;
- return applyStandardTemplateWithActions(R.layout.notification_template_big_base);
+ return applyStandardTemplateWithActions(getBigBaseLayoutResource());
}
- private RemoteViews makeHEadsUpContentView() {
+ private RemoteViews makeHeadsUpContentView() {
if (mActions.size() == 0) return null;
- return applyStandardTemplateWithActions(R.layout.notification_template_big_base);
+ return applyStandardTemplateWithActions(getBigBaseLayoutResource());
}
private RemoteViews generateActionButton(Action action) {
final boolean tombstone = (action.actionIntent == null);
RemoteViews button = new RemoteViews(mContext.getPackageName(),
- tombstone ? R.layout.notification_action_tombstone
- : R.layout.notification_action);
+ tombstone ? getActionTombstoneLayoutResource()
+ : getActionLayoutResource());
button.setTextViewCompoundDrawablesRelative(R.id.action0, action.icon, 0, 0, 0);
button.setTextViewText(R.id.action0, action.title);
if (!tombstone) {
@@ -1992,7 +2003,7 @@ public class Notification implements Parcelable
n.defaults = mDefaults;
n.flags = mFlags;
n.bigContentView = makeBigContentView();
- n.headsUpContentView = makeHEadsUpContentView();
+ n.headsUpContentView = makeHeadsUpContentView();
if (mLedOnMs != 0 || mLedOffMs != 0) {
n.flags |= FLAG_SHOW_LIGHTS;
}
@@ -2037,6 +2048,7 @@ public class Notification implements Parcelable
extras.putBoolean(EXTRA_PROGRESS_INDETERMINATE, mProgressIndeterminate);
extras.putBoolean(EXTRA_SHOW_CHRONOMETER, mUseChronometer);
extras.putBoolean(EXTRA_SHOW_WHEN, mShowWhen);
+ extras.putBoolean(EXTRA_BUILDER_REMOTE_VIEWS, mContentView == null);
if (mLargeIcon != null) {
extras.putParcelable(EXTRA_LARGE_ICON, mLargeIcon);
}
@@ -2080,6 +2092,49 @@ public class Notification implements Parcelable
build().cloneInto(n, true);
return n;
}
+
+
+ private int getBaseLayoutResource() {
+ return mQuantumTheme
+ ? R.layout.notification_template_quantum_base
+ : R.layout.notification_template_base;
+ }
+
+ private int getBigBaseLayoutResource() {
+ return mQuantumTheme
+ ? R.layout.notification_template_quantum_big_base
+ : R.layout.notification_template_big_base;
+ }
+
+ private int getBigPictureLayoutResource() {
+ return mQuantumTheme
+ ? R.layout.notification_template_quantum_big_picture
+ : R.layout.notification_template_big_picture;
+ }
+
+ private int getBigTextLayoutResource() {
+ return mQuantumTheme
+ ? R.layout.notification_template_quantum_big_text
+ : R.layout.notification_template_big_text;
+ }
+
+ private int getInboxLayoutResource() {
+ return mQuantumTheme
+ ? R.layout.notification_template_quantum_inbox
+ : R.layout.notification_template_inbox;
+ }
+
+ private int getActionLayoutResource() {
+ return mQuantumTheme
+ ? R.layout.notification_quantum_action
+ : R.layout.notification_action;
+ }
+
+ private int getActionTombstoneLayoutResource() {
+ return mQuantumTheme
+ ? R.layout.notification_quantum_action_tombstone
+ : R.layout.notification_action_tombstone;
+ }
}
/**
@@ -2249,7 +2304,7 @@ public class Notification implements Parcelable
}
private RemoteViews makeBigContentView() {
- RemoteViews contentView = getStandardView(R.layout.notification_template_big_picture);
+ RemoteViews contentView = getStandardView(mBuilder.getBigPictureLayoutResource());
contentView.setImageViewBitmap(R.id.big_picture, mPicture);
@@ -2348,7 +2403,7 @@ public class Notification implements Parcelable
final boolean hadThreeLines = (mBuilder.mContentText != null && mBuilder.mSubText != null);
mBuilder.mContentText = null;
- RemoteViews contentView = getStandardView(R.layout.notification_template_big_text);
+ RemoteViews contentView = getStandardView(mBuilder.getBigTextLayoutResource());
if (hadThreeLines) {
// vertical centering
@@ -2442,7 +2497,7 @@ public class Notification implements Parcelable
private RemoteViews makeBigContentView() {
// Remove the content text so line3 disappears unless you have a summary
mBuilder.mContentText = null;
- RemoteViews contentView = getStandardView(R.layout.notification_template_inbox);
+ RemoteViews contentView = getStandardView(mBuilder.getInboxLayoutResource());
contentView.setViewVisibility(R.id.text2, View.GONE);
diff --git a/core/java/android/content/res/ColorStateList.java b/core/java/android/content/res/ColorStateList.java
index 2893522d17c58..419abf212379b 100644
--- a/core/java/android/content/res/ColorStateList.java
+++ b/core/java/android/content/res/ColorStateList.java
@@ -315,6 +315,24 @@ public class ColorStateList implements Parcelable {
return mDefaultColor;
}
+ /**
+ * Return the states in this {@link ColorStateList}.
+ * @return the states in this {@link ColorStateList}
+ * @hide
+ */
+ public int[][] getStates() {
+ return mStateSpecs;
+ }
+
+ /**
+ * Return the colors in this {@link ColorStateList}.
+ * @return the colors in this {@link ColorStateList}
+ * @hide
+ */
+ public int[] getColors() {
+ return mColors;
+ }
+
@Override
public String toString() {
return "ColorStateList{" +
diff --git a/core/res/res/drawable/notification_quantum_background.xml b/core/res/res/drawable/notification_quantum_background.xml
new file mode 100644
index 0000000000000..f33e2e36621c7
--- /dev/null
+++ b/core/res/res/drawable/notification_quantum_background.xml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/core/res/res/drawable/notification_quantum_bg.xml b/core/res/res/drawable/notification_quantum_bg.xml
new file mode 100644
index 0000000000000..608115e631e9a
--- /dev/null
+++ b/core/res/res/drawable/notification_quantum_bg.xml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/core/res/res/drawable/notification_quantum_press.xml b/core/res/res/drawable/notification_quantum_press.xml
new file mode 100644
index 0000000000000..4999f55cc0f62
--- /dev/null
+++ b/core/res/res/drawable/notification_quantum_press.xml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/core/res/res/layout/notification_quantum_action.xml b/core/res/res/layout/notification_quantum_action.xml
new file mode 100644
index 0000000000000..775182f4a699f
--- /dev/null
+++ b/core/res/res/layout/notification_quantum_action.xml
@@ -0,0 +1,31 @@
+
+
+
+
diff --git a/core/res/res/layout/notification_quantum_action_list.xml b/core/res/res/layout/notification_quantum_action_list.xml
new file mode 100644
index 0000000000000..a8aef97e48ab7
--- /dev/null
+++ b/core/res/res/layout/notification_quantum_action_list.xml
@@ -0,0 +1,30 @@
+
+
+
+
+
+
diff --git a/core/res/res/layout/notification_quantum_action_tombstone.xml b/core/res/res/layout/notification_quantum_action_tombstone.xml
new file mode 100644
index 0000000000000..9104991585fbc
--- /dev/null
+++ b/core/res/res/layout/notification_quantum_action_tombstone.xml
@@ -0,0 +1,33 @@
+
+
+
+
diff --git a/core/res/res/layout/notification_template_quantum_base.xml b/core/res/res/layout/notification_template_quantum_base.xml
new file mode 100644
index 0000000000000..3e97b2aecc0c3
--- /dev/null
+++ b/core/res/res/layout/notification_template_quantum_base.xml
@@ -0,0 +1,137 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/core/res/res/layout/notification_template_quantum_big_base.xml b/core/res/res/layout/notification_template_quantum_big_base.xml
new file mode 100644
index 0000000000000..d86004580d0f2
--- /dev/null
+++ b/core/res/res/layout/notification_template_quantum_big_base.xml
@@ -0,0 +1,167 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/core/res/res/layout/notification_template_quantum_big_picture.xml b/core/res/res/layout/notification_template_quantum_big_picture.xml
new file mode 100644
index 0000000000000..e49c3bd9265f0
--- /dev/null
+++ b/core/res/res/layout/notification_template_quantum_big_picture.xml
@@ -0,0 +1,62 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/core/res/res/layout/notification_template_quantum_big_text.xml b/core/res/res/layout/notification_template_quantum_big_text.xml
new file mode 100644
index 0000000000000..585be8038bd06
--- /dev/null
+++ b/core/res/res/layout/notification_template_quantum_big_text.xml
@@ -0,0 +1,182 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/core/res/res/layout/notification_template_quantum_inbox.xml b/core/res/res/layout/notification_template_quantum_inbox.xml
new file mode 100644
index 0000000000000..31ed50878f3a7
--- /dev/null
+++ b/core/res/res/layout/notification_template_quantum_inbox.xml
@@ -0,0 +1,266 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/core/res/res/values/styles.xml b/core/res/res/values/styles.xml
index e525ef71190cc..be875ffb2b04d 100644
--- a/core/res/res/values/styles.xml
+++ b/core/res/res/values/styles.xml
@@ -271,6 +271,33 @@ please see styles_device_defaults.xml.
- #CCCCCC
+
+
+
+
+
+
+
+
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index a4f97629ae344..3a45d8c342dbd 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -1627,7 +1627,16 @@
-
+
+
+
+
+
+
+
+
+
+
diff --git a/packages/SystemUI/res/drawable/notification_icon_legacy_bg.xml b/packages/SystemUI/res/drawable/notification_icon_legacy_bg.xml
new file mode 100644
index 0000000000000..4ac67c3e47657
--- /dev/null
+++ b/packages/SystemUI/res/drawable/notification_icon_legacy_bg.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
diff --git a/packages/SystemUI/res/drawable/notification_icon_legacy_bg_inset.xml b/packages/SystemUI/res/drawable/notification_icon_legacy_bg_inset.xml
new file mode 100644
index 0000000000000..96c557366189f
--- /dev/null
+++ b/packages/SystemUI/res/drawable/notification_icon_legacy_bg_inset.xml
@@ -0,0 +1,21 @@
+
+
+
+
diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml
index 5cf0453aafe24..a59dc75989e0b 100644
--- a/packages/SystemUI/res/values/colors.xml
+++ b/packages/SystemUI/res/values/colors.xml
@@ -39,6 +39,8 @@
#FFFFFFFF
#ff111111
#ff454545
+ #ff4285F4
+ #ff555555
#ff404040
diff --git a/packages/SystemUI/src/com/android/systemui/ImageUtils.java b/packages/SystemUI/src/com/android/systemui/ImageUtils.java
new file mode 100644
index 0000000000000..540ba20c143a6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/ImageUtils.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui;
+
+import android.graphics.Bitmap;
+
+/**
+ * Utility class for image analysis and processing.
+ */
+public class ImageUtils {
+
+ // Amount (max is 255) that two channels can differ before the color is no longer "gray".
+ private static final int TOLERANCE = 20;
+
+ // Alpha amount for which values below are considered transparent.
+ private static final int ALPHA_TOLERANCE = 50;
+
+ private int[] mTempBuffer;
+
+ /**
+ * Checks whether a bitmap is grayscale. Grayscale here means "very close to a perfect
+ * gray".
+ */
+ public boolean isGrayscale(Bitmap bitmap) {
+ final int height = bitmap.getHeight();
+ final int width = bitmap.getWidth();
+ int size = height*width;
+
+ ensureBufferSize(size);
+ bitmap.getPixels(mTempBuffer, 0, width, 0, 0, width, height);
+ for (int i = 0; i < size; i++) {
+ if (!isGrayscale(mTempBuffer[i])) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Makes sure that {@code mTempBuffer} has at least length {@code size}.
+ */
+ private void ensureBufferSize(int size) {
+ if (mTempBuffer == null || mTempBuffer.length < size) {
+ mTempBuffer = new int[size];
+ }
+ }
+
+ /**
+ * Classifies a color as grayscale or not. Grayscale here means "very close to a perfect
+ * gray"; if all three channels are approximately equal, this will return true.
+ *
+ * Note that really transparent colors are always grayscale.
+ */
+ public boolean isGrayscale(int color) {
+ int alpha = 0xFF & (color >> 24);
+ if (alpha < ALPHA_TOLERANCE) {
+ return true;
+ }
+
+ int r = 0xFF & (color >> 16);
+ int g = 0xFF & (color >> 8);
+ int b = 0xFF & color;
+
+ return Math.abs(r - g) < TOLERANCE
+ && Math.abs(r - b) < TOLERANCE
+ && Math.abs(g - b) < TOLERANCE;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
index fb117432db152..eb07d888610be 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
@@ -28,9 +28,15 @@ import android.content.IntentFilter;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.res.ColorStateList;
import android.content.res.Configuration;
import android.database.ContentObserver;
+import android.graphics.Color;
+import android.graphics.PorterDuff;
import android.graphics.Rect;
+import android.graphics.drawable.AnimationDrawable;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Build;
import android.os.Handler;
@@ -44,9 +50,13 @@ import android.provider.Settings;
import android.service.dreams.DreamService;
import android.service.dreams.IDreamManager;
import android.service.notification.StatusBarNotification;
+import android.text.SpannableStringBuilder;
+import android.text.Spanned;
import android.text.TextUtils;
+import android.text.style.TextAppearanceSpan;
import android.util.Log;
import android.util.SparseBooleanArray;
+import android.view.ContextThemeWrapper;
import android.view.Display;
import android.view.IWindowManager;
import android.view.LayoutInflater;
@@ -57,6 +67,7 @@ import android.view.ViewGroup;
import android.view.ViewGroup.LayoutParams;
import android.view.WindowManager;
import android.view.WindowManagerGlobal;
+import android.widget.Button;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.PopupMenu;
@@ -67,6 +78,7 @@ import com.android.internal.statusbar.IStatusBarService;
import com.android.internal.statusbar.StatusBarIcon;
import com.android.internal.statusbar.StatusBarIconList;
import com.android.internal.widget.SizeAdaptiveLayout;
+import com.android.systemui.ImageUtils;
import com.android.systemui.R;
import com.android.systemui.RecentsComponent;
import com.android.systemui.SearchPanelView;
@@ -76,6 +88,7 @@ import com.android.systemui.statusbar.policy.NotificationRowLayout;
import java.util.ArrayList;
import java.util.Locale;
+import java.util.Stack;
public abstract class BaseStatusBar extends SystemUI implements
CommandQueue.Callbacks {
@@ -140,6 +153,8 @@ public abstract class BaseStatusBar extends SystemUI implements
// public mode, private notifications, etc
private boolean mLockscreenPublicMode = false;
private final SparseBooleanArray mUsersAllowingPrivateNotifications = new SparseBooleanArray();
+ private Context mLightThemeContext;
+ private ImageUtils mImageUtils = new ImageUtils();
// UI-specific methods
@@ -261,6 +276,8 @@ public abstract class BaseStatusBar extends SystemUI implements
true,
mLockscreenSettingsObserver,
UserHandle.USER_ALL);
+ mLightThemeContext = new RemoteViewsThemeContextWrapper(mContext,
+ android.R.style.Theme_Holo_Light);
mBarService = IStatusBarService.Stub.asInterface(
ServiceManager.getService(Context.STATUS_BAR_SERVICE));
@@ -412,6 +429,158 @@ public abstract class BaseStatusBar extends SystemUI implements
}
}
+ private void processLegacyHoloNotification(StatusBarNotification sbn, View content) {
+
+ // TODO: Also skip processing if it is a holo-style notification.
+ // If the notification is custom, we can't process it.
+ if (!sbn.getNotification().extras.getBoolean(Notification.EXTRA_BUILDER_REMOTE_VIEWS)) {
+ return;
+ }
+
+ processLegacyHoloLargeIcon(content);
+ processLegacyHoloActions(content);
+ processLegacyNotificationIcon(content);
+ processLegacyTextViews(content);
+ }
+
+ /**
+ * @return the context to be used for the inflation of the specified {@code sbn}; this is
+ * dependent whether the notification is quantum-style or holo-style
+ */
+ private Context getInflationContext(StatusBarNotification sbn) {
+
+ // TODO: Adjust this logic when we change the theme of the status bar windows.
+ if (sbn.getNotification().extras.getBoolean(Notification.EXTRA_BUILDER_REMOTE_VIEWS)) {
+ return mLightThemeContext;
+ } else {
+ return mContext;
+ }
+ }
+
+ private void processLegacyNotificationIcon(View content) {
+ View v = content.findViewById(com.android.internal.R.id.right_icon);
+ if (v != null & v instanceof ImageView) {
+ ImageView iv = (ImageView) v;
+ Drawable d = iv.getDrawable();
+ if (isMonochrome(d)) {
+ d.mutate();
+ d.setColorFilter(mLightThemeContext.getResources().getColor(
+ R.color.notification_action_legacy_color_filter), PorterDuff.Mode.MULTIPLY);
+ }
+ }
+ }
+
+ private void processLegacyHoloLargeIcon(View content) {
+ View v = content.findViewById(com.android.internal.R.id.icon);
+ if (v != null & v instanceof ImageView) {
+ ImageView iv = (ImageView) v;
+ if (isMonochrome(iv.getDrawable())) {
+ iv.setBackground(mLightThemeContext.getResources().getDrawable(
+ R.drawable.notification_icon_legacy_bg_inset));
+ }
+ }
+ }
+
+ private boolean isMonochrome(Drawable d) {
+ if (d == null) {
+ return false;
+ } else if (d instanceof BitmapDrawable) {
+ BitmapDrawable bd = (BitmapDrawable) d;
+ return bd.getBitmap() != null && mImageUtils.isGrayscale(bd.getBitmap());
+ } else if (d instanceof AnimationDrawable) {
+ AnimationDrawable ad = (AnimationDrawable) d;
+ int count = ad.getNumberOfFrames();
+ return count > 0 && isMonochrome(ad.getFrame(0));
+ } else {
+ return false;
+ }
+ }
+
+ private void processLegacyHoloActions(View content) {
+ View v = content.findViewById(com.android.internal.R.id.actions);
+ if (v != null & v instanceof ViewGroup) {
+ ViewGroup vg = (ViewGroup) v;
+ int childCount = vg.getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ View child = vg.getChildAt(i);
+ if (child instanceof Button) {
+ Button button = (Button) child;
+ Drawable[] compoundDrawables = button.getCompoundDrawablesRelative();
+ if (isMonochrome(compoundDrawables[0])) {
+ Drawable d = compoundDrawables[0];
+ d.mutate();
+ d.setColorFilter(mLightThemeContext.getResources().getColor(
+ R.color.notification_action_legacy_color_filter),
+ PorterDuff.Mode.MULTIPLY);
+ }
+ }
+ }
+ }
+ }
+
+ private void processLegacyTextViews(View content) {
+ Stack viewStack = new Stack();
+ viewStack.push(content);
+ while(!viewStack.isEmpty()) {
+ View current = viewStack.pop();
+ if(current instanceof ViewGroup){
+ ViewGroup currentGroup = (ViewGroup) current;
+ int numChildren = currentGroup.getChildCount();
+ for(int i=0;i