summaryrefslogtreecommitdiffstats
path: root/packages/SystemUI/src
diff options
context:
space:
mode:
Diffstat (limited to 'packages/SystemUI/src')
-rw-r--r--packages/SystemUI/src/com/android/systemui/ImageUtils.java82
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java226
2 files changed, 302 insertions, 6 deletions
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 0000000..540ba20
--- /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 fb11743..eb07d88 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<View> viewStack = new Stack<View>();
+ 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<numChildren;i++){
+ viewStack.push(currentGroup.getChildAt(i));
+ }
+ }
+ if (current instanceof TextView) {
+ processLegacyTextView((TextView) current);
+ }
+ }
+ }
+
+ private void processLegacyTextView(TextView textView) {
+ if (textView.getText() instanceof Spanned) {
+ Spanned ss = (Spanned) textView.getText();
+ Object[] spans = ss.getSpans(0, ss.length(), Object.class);
+ SpannableStringBuilder builder = new SpannableStringBuilder(ss.toString());
+ for (Object span : spans) {
+ Object resultSpan = span;
+ if (span instanceof TextAppearanceSpan) {
+ resultSpan = processTextAppearanceSpan((TextAppearanceSpan) span);
+ }
+ builder.setSpan(resultSpan, ss.getSpanStart(span), ss.getSpanEnd(span),
+ ss.getSpanFlags(span));
+ }
+ textView.setText(builder);
+ }
+ }
+
+ private TextAppearanceSpan processTextAppearanceSpan(TextAppearanceSpan span) {
+ ColorStateList colorStateList = span.getTextColor();
+ if (colorStateList != null) {
+ int[] colors = colorStateList.getColors();
+ boolean changed = false;
+ for (int i = 0; i < colors.length; i++) {
+ if (mImageUtils.isGrayscale(colors[i])) {
+ colors[i] = processColor(colors[i]);
+ changed = true;
+ }
+ }
+ if (changed) {
+ return new TextAppearanceSpan(
+ span.getFamily(), span.getTextStyle(), span.getTextSize(),
+ new ColorStateList(colorStateList.getStates(), colors),
+ span.getLinkTextColor());
+ }
+ }
+ return span;
+ }
+
+ private int processColor(int color) {
+ return Color.argb(Color.alpha(color),
+ 255 - Color.red(color),
+ 255 - Color.green(color),
+ 255 - Color.blue(color));
+ }
+
private void startApplicationDetailsActivity(String packageName) {
Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS,
Uri.fromParts("package", packageName, null));
@@ -748,9 +917,11 @@ public abstract class BaseStatusBar extends SystemUI implements
View contentViewLocal = null;
View bigContentViewLocal = null;
try {
- contentViewLocal = contentView.apply(mContext, expanded, mOnClickHandler);
+ contentViewLocal = contentView.apply(getInflationContext(sbn), expanded,
+ mOnClickHandler);
if (bigContentView != null) {
- bigContentViewLocal = bigContentView.apply(mContext, expanded, mOnClickHandler);
+ bigContentViewLocal = bigContentView.apply(getInflationContext(sbn), expanded,
+ mOnClickHandler);
}
}
catch (RuntimeException e) {
@@ -780,7 +951,7 @@ public abstract class BaseStatusBar extends SystemUI implements
View publicViewLocal = null;
if (publicNotification != null) {
try {
- publicViewLocal = publicNotification.contentView.apply(mContext,
+ publicViewLocal = publicNotification.contentView.apply(getInflationContext(sbn),
expandedPublic, mOnClickHandler);
if (publicViewLocal != null) {
@@ -831,6 +1002,13 @@ public abstract class BaseStatusBar extends SystemUI implements
row.setDrawingCacheEnabled(true);
applyLegacyRowBackground(sbn, content);
+ processLegacyHoloNotification(sbn, contentViewLocal);
+ if (bigContentViewLocal != null) {
+ processLegacyHoloNotification(sbn, bigContentViewLocal);
+ }
+ if (publicViewLocal != null) {
+ processLegacyHoloNotification(sbn, publicViewLocal);
+ }
if (MULTIUSER_DEBUG) {
TextView debug = (TextView) row.findViewById(R.id.debug_info);
@@ -1245,12 +1423,17 @@ public abstract class BaseStatusBar extends SystemUI implements
: null;
// Reapply the RemoteViews
- contentView.reapply(mContext, entry.expanded, mOnClickHandler);
+ contentView.reapply(getInflationContext(notification), entry.expanded, mOnClickHandler);
+ processLegacyHoloNotification(notification, entry.expanded);
if (bigContentView != null && entry.getBigContentView() != null) {
- bigContentView.reapply(mContext, entry.getBigContentView(), mOnClickHandler);
+ bigContentView.reapply(getInflationContext(notification), entry.getBigContentView(),
+ mOnClickHandler);
+ processLegacyHoloNotification(notification, entry.getBigContentView());
}
if (publicContentView != null && entry.getPublicContentView() != null) {
- publicContentView.reapply(mContext, entry.getPublicContentView(), mOnClickHandler);
+ publicContentView.reapply(getInflationContext(notification),
+ entry.getPublicContentView(), mOnClickHandler);
+ processLegacyHoloNotification(notification, entry.getPublicContentView());
}
// update the contentIntent
final PendingIntent contentIntent = notification.getNotification().contentIntent;
@@ -1330,4 +1513,35 @@ public abstract class BaseStatusBar extends SystemUI implements
}
mContext.unregisterReceiver(mBroadcastReceiver);
}
+
+ /**
+ * A custom context theme wrapper that applies a platform theme to a created package context.
+ * This is useful if you want to inflate {@link RemoteViews} with a custom theme (normally, the
+ * theme used there is the default platform theme).
+ */
+ private static class RemoteViewsThemeContextWrapper extends ContextThemeWrapper {
+
+ private int mThemeRes;
+
+ private RemoteViewsThemeContextWrapper(Context base, int themeres) {
+ super(base, themeres);
+ mThemeRes = themeres;
+ }
+
+ @Override
+ public Context createPackageContextAsUser(String packageName, int flags, UserHandle user)
+ throws NameNotFoundException {
+ Context c = super.createPackageContextAsUser(packageName, flags, user);
+ c.setTheme(mThemeRes);
+ return c;
+ }
+
+ @Override
+ public Context createPackageContext(String packageName, int flags)
+ throws NameNotFoundException {
+ Context c = super.createPackageContext(packageName, flags);
+ c.setTheme(mThemeRes);
+ return c;
+ }
+ }
}