diff options
Diffstat (limited to 'packages/SystemUI/src')
-rw-r--r-- | packages/SystemUI/src/com/android/systemui/ImageUtils.java | 82 | ||||
-rw-r--r-- | packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java | 226 |
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; + } + } } |