/* * Copyright (C) 2010 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 org.exalm.tabletkat.statusbar.tablet; import android.animation.LayoutTransition; import android.animation.ObjectAnimator; import android.app.ActivityManager; import android.app.Notification; import android.app.PendingIntent; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.SharedPreferences; import android.content.res.Configuration; import android.content.res.Resources; import android.content.res.XModuleResources; import android.content.res.XResources; import android.database.ContentObserver; import android.graphics.PixelFormat; import android.graphics.Point; import android.graphics.Typeface; import android.graphics.drawable.Drawable; import android.graphics.drawable.LayerDrawable; import android.inputmethodservice.InputMethodService; import android.os.Handler; import android.os.IBinder; import android.os.Message; import android.provider.Settings; import android.service.notification.StatusBarNotification; import android.text.TextUtils; import android.text.format.DateFormat; import android.util.DisplayMetrics; import android.util.Log; import android.util.TypedValue; import android.view.Display; import android.view.Gravity; import android.view.KeyEvent; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.SoundEffectConstants; import android.view.VelocityTracker; import android.view.View; import android.view.ViewConfiguration; import android.view.ViewGroup; import android.view.ViewStub; import android.view.WindowManager; import android.view.accessibility.AccessibilityEvent; import android.widget.FrameLayout; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.RelativeLayout; import android.widget.ScrollView; import android.widget.TextView; import android.widget.Toast; import org.exalm.tabletkat.CustomDimenReplacement; import org.exalm.tabletkat.OnPreferenceChangedListener; import org.exalm.tabletkat.R; import org.exalm.tabletkat.StatusBarManager; import org.exalm.tabletkat.SystemR; import org.exalm.tabletkat.TabletKatModule; import org.exalm.tabletkat.TkR; import org.exalm.tabletkat.ViewConstants; import org.exalm.tabletkat.ViewHelper; import org.exalm.tabletkat.statusbar.BaseStatusBarMod; import org.exalm.tabletkat.statusbar.CommandQueue; import org.exalm.tabletkat.statusbar.DoNotDisturb; import org.exalm.tabletkat.statusbar.phone.BarTransitions; import org.exalm.tabletkat.statusbar.policy.BatteryPercentView; import org.exalm.tabletkat.statusbar.policy.EventHole; import org.exalm.tabletkat.statusbar.policy.Prefs; import org.exalm.tabletkat.statusbar.policy.TabletBluetoothController; import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.ArrayList; import java.util.Date; import de.robv.android.xposed.XC_MethodHook; import de.robv.android.xposed.XC_MethodReplacement; import de.robv.android.xposed.XSharedPreferences; import de.robv.android.xposed.XposedBridge; import de.robv.android.xposed.XposedHelpers; import de.robv.android.xposed.callbacks.XC_LayoutInflated; /* Transforms TvStatusBar into a TabletStatusBar */ public class TabletStatusBarMod extends BaseStatusBarMod implements InputMethodsPanel.OnHardKeyboardEnabledChangeListener { public static final boolean DEBUG = false; public static final boolean DEBUG_COMPAT_HELP = false; public static final String TAG = "TabletStatusBarMod"; public static final int MSG_OPEN_NOTIFICATION_PANEL = 1000; public static final int MSG_CLOSE_NOTIFICATION_PANEL = 1001; public static final int MSG_OPEN_NOTIFICATION_PEEK = 1002; public static final int MSG_CLOSE_NOTIFICATION_PEEK = 1003; // 1020-1030 reserved for BaseStatusBar public static final int MSG_SHOW_CHROME = 1031; public static final int MSG_HIDE_CHROME = 1032; public static final int MSG_OPEN_INPUT_METHODS_PANEL = 1040; public static final int MSG_CLOSE_INPUT_METHODS_PANEL = 1041; public static final int MSG_OPEN_COMPAT_MODE_PANEL = 1050; public static final int MSG_CLOSE_COMPAT_MODE_PANEL = 1051; public static final int MSG_STOP_TICKER = 2000; // Fitts' Law assistance for LatinIME; see policy.EventHole private static final boolean FAKE_SPACE_BAR = true; // Notification "peeking" (flyover preview of individual notifications) final static boolean NOTIFICATION_PEEK_ENABLED = false; final static int NOTIFICATION_PEEK_HOLD_THRESH = 200; // ms final static int NOTIFICATION_PEEK_FADE_DELAY = 3000; // ms private static final long AUTOHIDE_TIMEOUT_MS = 3000; private static final int NOTIFICATION_PRIORITY_MULTIPLIER = 10; // see NotificationManagerService private static final int HIDE_ICONS_BELOW_SCORE = Notification.PRIORITY_LOW * NOTIFICATION_PRIORITY_MULTIPLIER; // The height of the bar, as definied by the build. It may be taller if we're plugged // into hdmi. int mNaturalBarHeight = -1; int mIconSize = -1; int mIconHPadding = -1; int mNavIconWidth = -1; int mMenuNavIconWidth = -1; private int mMaxNotificationIcons = 5; TabletStatusBarView mStatusBarView; View mNotificationArea; View mNotificationTrigger; NotificationIconArea mNotificationIconArea; ViewGroup mNavigationArea; boolean mNotificationDNDMode; Object mNotificationDNDDummyEntry; ImageView mBackButton; View mHomeButton; View mMenuButton; View mRecentButton; private LinearLayout mStatusIcons; InputMethodButton mInputMethodSwitchButton; CompatModeButton mCompatModeButton; NotificationPanel mNotificationPanel; WindowManager.LayoutParams mNotificationPanelParams; NotificationPeekPanel mNotificationPeekWindow; ViewGroup mNotificationPeekRow; int mNotificationPeekIndex; IBinder mNotificationPeekKey; LayoutTransition mNotificationPeekScrubLeft, mNotificationPeekScrubRight; int mNotificationPeekTapDuration; int mNotificationFlingVelocity; TabletBluetoothController mBluetoothController; Object mLocationController; Object mNetworkController; DoNotDisturb mDoNotDisturb; ViewGroup mBarContents; // hide system chrome ("lights out") support View mShadow; NotificationIconArea.IconLayout mIconLayout; TabletTicker mTicker; View mFakeSpaceBar; KeyEvent mSpaceBarKeyEvent = null; private int mStatusBarWindowState = StatusBarManager.WINDOW_STATE_SHOWING; View mCompatibilityHelpDialog = null; // for disabling the status bar int mDisabled = 0; private InputMethodsPanel mInputMethodsPanel; private CompatModePanel mCompatModePanel; private int mSystemUiVisibility = 0; private int mNavigationIconHints = 0; private int mShowSearchHoldoff = 0; private int mStatusBarMode; private boolean checkBarModes; private boolean mAutohideSuspended; private int mInteractingWindows; private Boolean mScreenOn; private Context mLargeIconContext; private BroadcastReceiver mSettingsReceiver; // for heads up notifications FrameLayout mHeadsUpNotificationView; private int mHeadsUpNotificationDecay; @Override public void reset() { super.reset(); mNaturalBarHeight = -1; mIconSize = -1; mIconHPadding = -1; mNavIconWidth = -1; mMenuNavIconWidth = -1; mMaxNotificationIcons = 5; mStatusBarView = null; mNotificationArea = null; mNotificationTrigger = null; mNotificationIconArea = null; mNavigationArea = null; mNotificationDNDMode = false; mNotificationDNDDummyEntry = null; mBackButton = null; mHomeButton = null; mMenuButton = null; mRecentButton = null; mStatusIcons = null; mInputMethodSwitchButton = null; mCompatModeButton = null; mNotificationPanel = null; mNotificationPanelParams = null; mNotificationPeekWindow = null; mNotificationPeekRow = null; mNotificationPeekIndex = 0; mNotificationPeekKey = null; mNotificationPeekScrubLeft = null; mNotificationPeekScrubRight = null; mNotificationPeekTapDuration = 0; mNotificationFlingVelocity = 0; mBluetoothController = null; mLocationController = null; mNetworkController = null; mDoNotDisturb = null; mBarContents = null; mShadow = null; mIconLayout = null; mTicker = null; mFakeSpaceBar = null; mSpaceBarKeyEvent = null; mStatusBarWindowState = 0; mCompatibilityHelpDialog = null; mDisabled = 0; mInputMethodsPanel = null; mCompatModePanel = null; mSystemUiVisibility = 0; mNavigationIconHints = 0; mShowSearchHoldoff = 0; mStatusBarMode = 0; checkBarModes = false; mAutohideSuspended = false; mInteractingWindows = 0; mScreenOn = null; mLargeIconContext = null; mSettingsReceiver = null; mHeadsUpNotificationDecay = 0; mHeadsUpNotificationView = null; mUserSetup = false; } public Context getContext() { return mContext; } private Runnable mShowSearchPanel = new Runnable() { public void run() { showSearchPanel(); awakenDreams(); } }; private View.OnTouchListener mHomeSearchActionListener = new View.OnTouchListener() { public boolean onTouch(View v, MotionEvent event) { switch(event.getAction()) { case MotionEvent.ACTION_DOWN: if (!shouldDisableNavbarGestures() && !inKeyguardRestrictedInputMode()) { mHandler.removeCallbacks(mShowSearchPanel); mHandler.postDelayed(mShowSearchPanel, mShowSearchHoldoff); } break; case MotionEvent.ACTION_UP: case MotionEvent.ACTION_CANCEL: mHandler.removeCallbacks(mShowSearchPanel); awakenDreams(); break; } return false; } }; private void awakenDreams() { Object mDreamManager = XposedHelpers.getObjectField(self, "mDreamManager"); if (mDreamManager != null) { try { XposedHelpers.callMethod(mDreamManager, "awaken"); } catch (Exception e) { // fine, stay asleep then } } } // ensure quick settings is disabled until the current user makes it through the setup wizard private boolean mUserSetup = false; private ContentObserver mUserSetupObserver = new ContentObserver(new Handler()) { @Override public void onChange(boolean selfChange) { final boolean userSetup = 0 != Settings.Secure.getIntForUser( mContext.getContentResolver(), Settings.Secure.USER_SETUP_COMPLETE, 0 /*default */, XposedHelpers.getIntField(self, "mCurrentUserId")); if (MULTIUSER_DEBUG) Log.d(TAG, String.format("User setup changed: " + "selfChange=%s userSetup=%s mUserSetup=%s", selfChange, userSetup, mUserSetup)); if (mNotificationPanel != null) { mNotificationPanel.setQuickSettingsEnabled(userSetup); } if (userSetup != mUserSetup) { mUserSetup = userSetup; } } }; private final ContentObserver mHeadsUpObserver = new ContentObserver(mHandler) { @Override public void onChange(boolean selfChange) { if (self == null) { return; } boolean wasUsing = XposedHelpers.getBooleanField(self, "mUseHeadsUp"); boolean mUseHeadsUp = ENABLE_HEADS_UP && 0 != Settings.Global.getInt( mContext.getContentResolver(), SETTING_HEADS_UP, 0); XposedHelpers.setBooleanField(self, "mUseHeadsUp", mUseHeadsUp); Log.d(TAG, "heads up is " + (mUseHeadsUp ? "enabled" : "disabled")); if (wasUsing != mUseHeadsUp) { if (!mUseHeadsUp) { Log.d(TAG, "dismissing any existing heads up notification on disable event"); mHandler.sendEmptyMessage(MSG_HIDE_HEADS_UP); removeHeadsUpView(); } else { addHeadsUpView(); } } } }; private void addStatusBarWindow() { final View sb = makeStatusBarView(); mWindowManager = (WindowManager) XposedHelpers.getObjectField(self, "mWindowManager"); final WindowManager.LayoutParams lp = new WindowManager.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT, XposedHelpers.getStaticIntField(TabletKatModule.mWindowManagerLayoutParamsClass, "TYPE_NAVIGATION_BAR"), WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_TOUCHABLE_WHEN_WAKING | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH, PixelFormat.TRANSLUCENT); if ((Boolean) XposedHelpers.callStaticMethod(ActivityManager.class, "isHighEndGfx")) { lp.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED; } lp.gravity = getStatusBarGravity(); lp.setTitle("NavigationBar"); //Should be SystemBar, but autohide is mor lp.packageName = mContext.getPackageName(); mWindowManager.addView(sb, lp); } protected void addPanelWindows() { final Context context = mContext; final Resources res = mContext.getResources(); // Notification Panel RelativeLayout l = (RelativeLayout)View.inflate(context, TkR.layout.system_bar_notification_panel, null); ViewHelper.replaceView(l, TkR.id.title_area, new NotificationPanelTitle(context, mLargeIconContext, null)); mNotificationPanel = (NotificationPanel) ViewHelper.replaceView(l, new NotificationPanel(context, null)); mNotificationPanel.onFinishInflate(); mNotificationPanel.setBar(this); mNotificationPanel.show(false, false); mNotificationPanel.setOnTouchListener(new TouchOutsideListener(MSG_CLOSE_NOTIFICATION_PANEL, mNotificationPanel)); mBluetoothController.addView((ImageView)mNotificationPanel.findViewById(TkR.id.bluetooth)); // network icons: either a combo icon that switches between mobile and data, or distinct // mobile and data icons XposedHelpers.callMethod(mNetworkController, "addCombinedLabelView", (TextView) mNotificationPanel.findViewById(TkR.id.network_text)); FrameLayout f = (FrameLayout) mNotificationPanel.findViewById(SystemR.id.signal_cluster); ViewStub cluster = new ViewStub(mLargeIconContext); ViewHelper.replaceView(f.getChildAt(0), cluster); cluster.setLayoutResource(SystemR.layout.signal_cluster_view); cluster.inflate(); XposedHelpers.callMethod(mNetworkController, "addSignalCluster", f.getChildAt(0)); XposedHelpers.callMethod(mNetworkController, "addCombinedLabelView", (TextView)mBarContents.findViewById(TkR.id.network_text)); mStatusBarView.setIgnoreChildren(0, mNotificationTrigger, mNotificationPanel); WindowManager.LayoutParams lp = mNotificationPanelParams = new WindowManager.LayoutParams( res.getDimensionPixelSize(SystemR.dimen.notification_panel_width), getNotificationPanelHeight(), XposedHelpers.getStaticIntField(TabletKatModule.mWindowManagerLayoutParamsClass, "TYPE_NAVIGATION_BAR_PANEL"), WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS | WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED, PixelFormat.TRANSLUCENT); lp.gravity = Gravity.BOTTOM | Gravity.END; lp.setTitle("NotificationPanel"); lp.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_STATE_UNCHANGED | WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING; // lp.windowAnimations = XposedHelpers.getStaticIntField(TabletKatModule.mComAndroidInternalRStyleClass, "Animation"); // == no animation lp.windowAnimations = XposedHelpers.getStaticIntField(TabletKatModule.mComAndroidInternalRStyleClass, "Animation_ZoomButtons"); // simple fade mWindowManager.addView(mNotificationPanel, lp); if (NOTIFICATION_PEEK_ENABLED) { RelativeLayout temp = (RelativeLayout) View.inflate(context, TkR.layout.system_bar_notification_peek, null); mNotificationPeekWindow = (NotificationPeekPanel) ViewHelper.replaceView(temp, new NotificationPeekPanel(context, null)); mNotificationPeekWindow.setBar(this); int width = (int) TypedValue.applyDimension( TypedValue.COMPLEX_UNIT_DIP, 512, res.getDisplayMetrics()); mNotificationPeekRow = (ViewGroup) mNotificationPeekWindow.findViewById(SystemR.id.content); mNotificationPeekWindow.setVisibility(View.GONE); mNotificationPeekWindow.setOnTouchListener( new TouchOutsideListener(MSG_CLOSE_NOTIFICATION_PEEK, mNotificationPeekWindow)); mNotificationPeekScrubRight = new LayoutTransition(); mNotificationPeekScrubRight.setAnimator(LayoutTransition.APPEARING, ObjectAnimator.ofInt(null, "left", -width, 0)); mNotificationPeekScrubRight.setAnimator(LayoutTransition.DISAPPEARING, ObjectAnimator.ofInt(null, "left", -width, 0)); mNotificationPeekScrubRight.setDuration(500); mNotificationPeekScrubLeft = new LayoutTransition(); mNotificationPeekScrubLeft.setAnimator(LayoutTransition.APPEARING, ObjectAnimator.ofInt(null, "left", width, 0)); mNotificationPeekScrubLeft.setAnimator(LayoutTransition.DISAPPEARING, ObjectAnimator.ofInt(null, "left", width, 0)); mNotificationPeekScrubLeft.setDuration(500); // XXX: setIgnoreChildren? lp = new WindowManager.LayoutParams( width, // ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT, WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL, WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS | WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH, PixelFormat.TRANSLUCENT); lp.gravity = Gravity.BOTTOM | Gravity.END; lp.y = res.getDimensionPixelOffset(SystemR.dimen.peek_window_y_offset); lp.setTitle("NotificationPeekWindow"); lp.windowAnimations = com.android.internal.R.style.Animation_Toast; mWindowManager.addView(mNotificationPeekWindow, lp); } if (ENABLE_HEADS_UP) { mHeadsUpNotificationView = (FrameLayout) View.inflate(context, SystemR.layout.heads_up, null); mHeadsUpNotificationView.setVisibility(View.GONE); XposedHelpers.callMethod(mHeadsUpNotificationView, "setBar", self); } // Search Panel mStatusBarView.setBar(self); mHomeButton.setOnTouchListener(mHomeSearchActionListener); mShowSearchHoldoff = mContext.getResources().getInteger( SystemR.integer.config_show_search_delay); updateSearchPanel(); // Input methods Panel LinearLayout layout = (LinearLayout)View.inflate(context, TkR.layout.system_bar_input_methods_panel, null); mInputMethodsPanel = (InputMethodsPanel) ViewHelper.replaceView(layout, new InputMethodsPanel(context, null)); mInputMethodsPanel.onFinishInflate(); mInputMethodsPanel.setHardKeyboardEnabledChangeListener(this); mInputMethodsPanel.setOnTouchListener(new TouchOutsideListener(MSG_CLOSE_INPUT_METHODS_PANEL, mInputMethodsPanel)); mInputMethodsPanel.setImeSwitchButton(mInputMethodSwitchButton); mStatusBarView.setIgnoreChildren(2, mInputMethodSwitchButton, mInputMethodsPanel); lp = new WindowManager.LayoutParams( ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT, XposedHelpers.getStaticIntField(TabletKatModule.mWindowManagerLayoutParamsClass, "TYPE_NAVIGATION_BAR_PANEL"), WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN | WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED, PixelFormat.TRANSLUCENT); lp.gravity = Gravity.BOTTOM | Gravity.END; lp.setTitle("InputMethodsPanel"); // lp.windowAnimations = android.R.style.Animation_InputMethod; //TODO: lp.windowAnimations = R.style.Animation_RecentPanel; mWindowManager.addView(mInputMethodsPanel, lp); // Compatibility mode selector panel FrameLayout frame = (FrameLayout) View.inflate(context, TkR.layout.system_bar_compat_mode_panel, null); mCompatModePanel = (CompatModePanel) ViewHelper.replaceView(frame, new CompatModePanel(context, null)); mCompatModePanel.setOnTouchListener(new TouchOutsideListener(MSG_CLOSE_COMPAT_MODE_PANEL, mCompatModePanel)); mCompatModePanel.setTrigger(mCompatModeButton); mCompatModePanel.setVisibility(View.GONE); mCompatModePanel.onFinishInflate(); mStatusBarView.setIgnoreChildren(3, mCompatModeButton, mCompatModePanel); lp = new WindowManager.LayoutParams( ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT, XposedHelpers.getStaticIntField(TabletKatModule.mWindowManagerLayoutParamsClass, "TYPE_NAVIGATION_BAR_PANEL"), WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN | WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED, PixelFormat.TRANSLUCENT); lp.gravity = Gravity.BOTTOM | Gravity.END; lp.setTitle("CompatModePanel"); lp.windowAnimations = android.R.style.Animation_Dialog; mWindowManager.addView(mCompatModePanel, lp); mRecentButton.setOnTouchListener(mRecentsPreloadOnTouchListener); mPile = (ViewGroup) mNotificationPanel.findViewById(SystemR.id.content); XposedHelpers.setObjectField(self, "mPile", mPile); mPile.removeAllViews(); XposedHelpers.callMethod(mPile, "setLongPressListener", getNotificationLongClicker()); ScrollView scroller = (ScrollView) mPile.getParent(); scroller.setFillViewport(true); } private void addHeadsUpView() { WindowManager.LayoutParams lp = new WindowManager.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT, WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL, // above the status bar! WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH, PixelFormat.TRANSLUCENT); lp.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED; lp.gravity = Gravity.BOTTOM; lp.setTitle("Heads Up"); lp.packageName = mContext.getPackageName(); lp.windowAnimations = android.R.style.Animation_InputMethod; mWindowManager.addView(mHeadsUpNotificationView, lp); } private void removeHeadsUpView() { mWindowManager.removeView(mHeadsUpNotificationView); } private int getNotificationPanelHeight() { final Display d = mWindowManager.getDefaultDisplay(); final Point size = new Point(); d.getRealSize(size); return size.y; } protected void loadDimens() { final Resources res = mContext.getResources(); Configuration c = new Configuration(res.getConfiguration()); XposedBridge.log("Native density: " + c.densityDpi); if (c.densityDpi >= 280) { c.densityDpi = DisplayMetrics.DENSITY_XXHIGH; }else if (c.densityDpi >= 187) { c.densityDpi = DisplayMetrics.DENSITY_XHIGH; } else { c.densityDpi = DisplayMetrics.DENSITY_HIGH; } XposedBridge.log("Status bar icons will use density: " + c.densityDpi); mLargeIconContext = mContext.createConfigurationContext(c); mNaturalBarHeight = res.getDimensionPixelSize( TkR.dimen.system_bar_height); int newIconSize = res.getDimensionPixelSize( TkR.dimen.system_bar_icon_drawing_size); int newIconHPadding = res.getDimensionPixelSize( TkR.dimen.system_bar_icon_padding); loadDimens2(false); mHeadsUpNotificationDecay = res.getInteger(SystemR.integer.heads_up_notification_decay); XposedHelpers.setIntField(self, "mRowHeight", res.getDimensionPixelSize(SystemR.dimen.notification_row_min_height)); if (newIconHPadding != mIconHPadding || newIconSize != mIconSize) { // Log.d(TAG, "size=" + newIconSize + " padding=" + newIconHPadding); mIconHPadding = newIconHPadding; mIconSize = newIconSize; reloadAllNotificationIcons(); // reload the tray } final int numIcons = res.getInteger(SystemR.integer.config_maxNotificationIcons); if (numIcons != mMaxNotificationIcons) { mMaxNotificationIcons = numIcons; if (DEBUG) Log.d(TAG, "max notification icons: " + mMaxNotificationIcons); reloadAllNotificationIcons(); } } protected void loadDimens2(boolean force) { final Resources res = mContext.getResources(); int newNavIconWidth = res.getDimensionPixelSize(TkR.dimen.system_bar_navigation_key_width); int newMenuNavIconWidth = res.getDimensionPixelSize(TkR.dimen.system_bar_navigation_menu_key_width); if (mNavigationArea != null && (newNavIconWidth != mNavIconWidth)) { mNavIconWidth = newNavIconWidth; LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams( mNavIconWidth, ViewGroup.LayoutParams.MATCH_PARENT); mBackButton.setLayoutParams(lp); mHomeButton.setLayoutParams(lp); mRecentButton.setLayoutParams(lp); RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.WRAP_CONTENT, RelativeLayout.LayoutParams.MATCH_PARENT); params.width = mNavIconWidth; params.addRule(RelativeLayout.ALIGN_PARENT_START); params.leftMargin = mNavIconWidth; mStatusBarView.findViewById(TkR.id.tablet_search_light).setLayoutParams(params); } if (mNavigationArea != null && (force || newMenuNavIconWidth != mMenuNavIconWidth)) { mMenuNavIconWidth = newMenuNavIconWidth; LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams( mMenuNavIconWidth, ViewGroup.LayoutParams.MATCH_PARENT); mMenuButton.setLayoutParams(lp); } } protected View makeStatusBarView() { mWindowManagerService = XposedHelpers.getObjectField(self, "mWindowManagerService"); mHandler = (Handler) XposedHelpers.getObjectField(self, "mHandler"); final Context context = mContext; loadDimens(); FrameLayout temp = (FrameLayout)View.inflate(context, TkR.layout.system_bar, null); ViewHelper.replaceView(temp, TkR.id.fake_space_bar, new EventHole(context, null)); ViewHelper.replaceView(temp, TkR.id.notificationArea, new NotificationArea(context, null)); ViewHelper.replaceView(temp, TkR.id.imeSwitchButton, new InputMethodButton(context, null)); ViewHelper.replaceView(temp, TkR.id.compatModeButton, new CompatModeButton(context, null)); ViewHelper.replaceView(temp, SystemR.id.notificationIcons, new NotificationIconArea(context, null)); ViewHelper.replaceView(temp, TkR.id.icons, new NotificationIconArea.IconLayout(context, null)); final TabletStatusBarView sb = (TabletStatusBarView) ViewHelper.replaceView(temp, new TabletStatusBarView(context, mBarService)); finalizeStatusBarView(sb); sb.getBarTransitions().init(); mStatusBarView = sb; mStatusBarView.setDisabledFlags(mDisabled); mStatusBarView.setOnTouchListener(new View.OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { checkUserAutohide(v, event); return false; }}); sb.setHandler(mHandler); mBarContents = (ViewGroup) sb.findViewById(TkR.id.bar_contents); // the whole right-hand side of the bar mNotificationArea = sb.findViewById(TkR.id.notificationArea); if (!NOTIFICATION_PEEK_ENABLED) { mNotificationArea.setOnTouchListener(new NotificationTriggerTouchListener()); } mNotificationArea.setVisibility(View.VISIBLE); // the button to open the notification area mNotificationTrigger = sb.findViewById(TkR.id.notificationTrigger); if (NOTIFICATION_PEEK_ENABLED) { mNotificationTrigger.setOnTouchListener(new NotificationTriggerTouchListener()); } // the more notifications icon mNotificationIconArea = (NotificationIconArea)sb.findViewById(SystemR.id.notificationIcons); mStatusIcons = (LinearLayout)sb.findViewById(SystemR.id.statusIcons); // where the icons go mIconLayout = (NotificationIconArea.IconLayout) sb.findViewById(TkR.id.icons); if (NOTIFICATION_PEEK_ENABLED) { mIconLayout.setOnTouchListener(new NotificationIconTouchListener()); } mNotificationPeekTapDuration = ViewConfiguration.getTapTimeout(); mNotificationFlingVelocity = 300; // px/s mTicker = new TabletTicker(this); // The icons mLocationController = XposedHelpers.newInstance(TabletKatModule.mLocationControllerClass, mContext); // will post a notification // watch the PREF_DO_NOT_DISTURB and convert to appropriate disable() calls mDoNotDisturb = new DoNotDisturb(mContext); mBluetoothController = new TabletBluetoothController(mLargeIconContext); mBluetoothController.addView((ImageView)sb.findViewById(TkR.id.bluetooth)); mNetworkController = XposedHelpers.newInstance(TabletKatModule.mNetworkControllerClass, mContext); final View signalCluster = ((FrameLayout) sb.findViewById(SystemR.id.signal_cluster)).getChildAt(0); XposedHelpers.callMethod(mNetworkController, "addSignalCluster", signalCluster); // The navigation buttons mBackButton = (ImageView)sb.findViewById(TkR.id.tablet_back); mNavigationArea = (ViewGroup) sb.findViewById(SystemR.id.nav_buttons); mHomeButton = mNavigationArea.findViewById(TkR.id.tablet_home); mMenuButton = mNavigationArea.findViewById(TkR.id.tablet_menu); mRecentButton = mNavigationArea.findViewById(TkR.id.tablet_recent_apps); mRecentButton.setOnClickListener(mOnClickListener); loadDimens2(true); LayoutTransition lt = new LayoutTransition(); lt.setDuration(250); // don't wait for these transitions; we just want icons to fade in/out, not move around lt.setDuration(LayoutTransition.CHANGE_APPEARING, 0); lt.setDuration(LayoutTransition.CHANGE_DISAPPEARING, 0); lt.addTransitionListener(new LayoutTransition.TransitionListener() { public void endTransition(LayoutTransition transition, ViewGroup container, View view, int transitionType) { // ensure the menu button doesn't stick around on the status bar after it's been // removed mBarContents.invalidate(); } public void startTransition(LayoutTransition transition, ViewGroup container, View view, int transitionType) {} }); mNavigationArea.setLayoutTransition(lt); // no multi-touch on the nav buttons mNavigationArea.setMotionEventSplittingEnabled(false); mInputMethodSwitchButton = (InputMethodButton) sb.findViewById(TkR.id.imeSwitchButton); // Overwrite the lister mInputMethodSwitchButton.setOnClickListener(mOnClickListener); mInputMethodSwitchButton.setPadding(mIconHPadding, 0, mIconHPadding, 0); mCompatModeButton = (CompatModeButton) sb.findViewById(TkR.id.compatModeButton); mCompatModeButton.setOnClickListener(mOnClickListener); mCompatModeButton.setVisibility(View.GONE); mCompatModeButton.setPadding(mIconHPadding, 0, mIconHPadding, 0); // for redirecting errant bar taps to the IME mFakeSpaceBar = sb.findViewById(TkR.id.fake_space_bar); // "shadows" of the status bar features, for lights-out mode mShadow = sb.findViewById(TkR.id.bar_shadow); mShadow.setOnTouchListener( new View.OnTouchListener() { public boolean onTouch(View v, MotionEvent ev) { if (ev.getAction() == MotionEvent.ACTION_DOWN) { // even though setting the systemUI visibility below will turn these views // on, we need them to come up faster so that they can catch this motion // event mShadow.setVisibility(View.GONE); mBarContents.setVisibility(View.VISIBLE); try { XposedHelpers.callMethod(mBarService, "setSystemUiVisibility", 0, View.SYSTEM_UI_FLAG_LOW_PROFILE); } catch (Exception ex) { // system process dead } } return false; } }); // set the initial view visibility setAreThereNotifications(); // receive broadcasts IntentFilter filter = new IntentFilter(); filter.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS); filter.addAction(Intent.ACTION_SCREEN_OFF); context.registerReceiver(mBroadcastReceiver, filter); // listen for USER_SETUP_COMPLETE setting (per-user) resetUserSetupObserver(); return sb; } public void setClockFont(TextView clock, boolean useOldFont){ DisplayMetrics d = getContext().getResources().getDisplayMetrics(); LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) clock.getLayoutParams(); if (useOldFont){ params.setMarginStart((int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 8, d)); params.bottomMargin = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 3, d); clock.setTypeface(Typeface.createFromFile("/system/fonts/AndroidClock_Solid.ttf")); clock.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 40); }else{ params.setMarginStart((int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 6, d)); params.bottomMargin = 0; clock.setTypeface(Typeface.DEFAULT); clock.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 30); } clock.setLayoutParams(params); clock.requestLayout(); } //TODO: Refactor private void finalizeStatusBarView(TabletStatusBarView v) { final TextView clock = (TextView) XposedHelpers.newInstance(TabletKatModule.mClockClass, mContext); ViewHelper.replaceView(v, SystemR.id.clock, clock); clock.setTextColor(mContext.getResources().getColor(SystemR.color.status_bar_clock_color)); FrameLayout f = (FrameLayout) v.findViewById(SystemR.id.signal_cluster); ViewStub cluster = new ViewStub(mLargeIconContext); ViewHelper.replaceView(f.getChildAt(0), cluster); cluster.setLayoutResource(SystemR.layout.signal_cluster_view); try { cluster.inflate(); }catch (Throwable t){ XposedBridge.log(t); } View battery = ViewHelper.replaceView(v, SystemR.id.battery, (View) XposedHelpers.newInstance(TabletKatModule.mBatteryMeterViewClass, mLargeIconContext)); try { XposedHelpers.callMethod(battery, "updateSettings", false); XposedHelpers.callMethod(battery, "setColors", false); } catch (NoSuchMethodError e) { } ViewHelper.replaceView(v, TkR.id.battery_text, new BatteryPercentView(mContext)); final BatteryPercentView percent = (BatteryPercentView) v.findViewById(TkR.id.battery_text); percent.setTextColor(mContext.getResources().getColor(SystemR.color.status_bar_clock_color)); ViewGroup view = (ViewGroup)v.findViewById(SystemR.id.nav_buttons); int[] ids = {TkR.id.tablet_back, TkR.id.tablet_home, TkR.id.tablet_recent_apps, TkR.id.tablet_menu}; int[] keyCodes = {4, 3, -1, 82}; int[] src = { SystemR.drawable.ic_sysbar_back, SystemR.drawable.ic_sysbar_home, SystemR.drawable.ic_sysbar_recent, SystemR.drawable.ic_sysbar_menu }; int[] descriptions = { SystemR.string.accessibility_back, SystemR.string.accessibility_home, SystemR.string.accessibility_recent, SystemR.string.accessibility_menu }; for (int i = 0; i < ids.length; i++) { ImageView button = (ImageView) XposedHelpers.newInstance(TabletKatModule.mKeyButtonViewClass, mContext, null); button.setId(ids[i]); button.setImageResource(src[i]); button.setContentDescription(mContext.getResources().getString(descriptions[i])); if (keyCodes[i] > 0){ XposedHelpers.setIntField(button, "mCode", keyCodes[i]); } Drawable glowBackground = mContext.getResources().getDrawable(SystemR.drawable.ic_sysbar_highlight); XposedHelpers.setObjectField(button, "mGlowBG", glowBackground); XposedHelpers.setObjectField(button, "mGlowWidth", glowBackground.getIntrinsicWidth()); XposedHelpers.setObjectField(button, "mGlowHeight", glowBackground.getIntrinsicHeight()); if (ids[i] == TkR.id.tablet_menu){ button.setVisibility(View.INVISIBLE); } view.addView(button); } ImageView searchLight = (ImageView) XposedHelpers.newInstance(TabletKatModule.mKeyButtonViewClass, mContext, null); searchLight.setId(TkR.id.tablet_search_light); searchLight.setImageResource(SystemR.drawable.search_light); searchLight.setContentDescription(mContext.getResources().getString(SystemR.string.accessibility_search_light)); searchLight.setScaleType(ImageView.ScaleType.CENTER); searchLight.setVisibility(View.GONE); ((ViewGroup) view.getParent()).addView(searchLight); ((ImageView) v.findViewById(TkR.id.dot0)).setImageResource(SystemR.drawable.ic_sysbar_lights_out_dot_small); ((ImageView) v.findViewById(TkR.id.dot1)).setImageResource(SystemR.drawable.ic_sysbar_lights_out_dot_large); ((ImageView) v.findViewById(TkR.id.dot2)).setImageResource(SystemR.drawable.ic_sysbar_lights_out_dot_small); ((ImageView) v.findViewById(TkR.id.dot3)).setImageResource(SystemR.drawable.ic_sysbar_lights_out_dot_small); mSettingsReceiver = TabletKatModule.registerReceiver(mContext, new OnPreferenceChangedListener() { @Override public void onPreferenceChanged(String key, boolean value) { if (key.equals("ics_clock_font")) { setClockFont(clock, value); } if (key.equals("battery_percents")){ percent.setVisibility(value ? View.VISIBLE : View.GONE); } } @Override public void onPreferenceChanged(String key, int value) { } @Override public void init(XSharedPreferences pref) { setClockFont(clock, pref.getBoolean("ics_clock_font", false)); percent.setVisibility(pref.getBoolean("battery_percents", false) ? View.VISIBLE : View.GONE); } }); } public int getStatusBarHeight() { return mStatusBarView != null ? mStatusBarView.getHeight() : mContext.getResources().getDimensionPixelSize( TkR.dimen.system_bar_height); } protected int getStatusBarGravity() { return Gravity.BOTTOM | Gravity.FILL_HORIZONTAL; } public void onBarHeightChanged(int height) { final WindowManager.LayoutParams lp = (WindowManager.LayoutParams)mStatusBarView.getLayoutParams(); if (lp == null) { // haven't been added yet return; } if (lp.height != height) { lp.height = height; mWindowManager.updateViewLayout(mStatusBarView, lp); } } public void handleMessage(Message m) { switch (m.what) { case MSG_OPEN_NOTIFICATION_PEEK: if (DEBUG) Log.d(TAG, "opening notification peek window; arg=" + m.arg1); awakenDreams(); if (m.arg1 >= 0) { final int N = (Integer) XposedHelpers.callMethod(mNotificationData, "size"); if (!mNotificationDNDMode) { if (mNotificationPeekIndex >= 0 && mNotificationPeekIndex < N) { Object entry = XposedHelpers.callMethod(mNotificationData, "get", N - 1 - mNotificationPeekIndex); Object icon = XposedHelpers.getObjectField(entry, "icon"); XposedHelpers.callMethod(icon, "setBackgroundColor", 0); mNotificationPeekIndex = -1; mNotificationPeekKey = null; } } final int peekIndex = m.arg1; if (peekIndex < N) { //Log.d(TAG, "loading peek: " + peekIndex); Object entry = mNotificationDNDMode ? mNotificationDNDDummyEntry : XposedHelpers.callMethod(mNotificationData, "get", N-1-peekIndex); Object copy = XposedHelpers.newInstance(TabletKatModule.mNotificationDataEntryClass, XposedHelpers.getObjectField(entry, "key"), XposedHelpers.getObjectField(entry, "notification"), XposedHelpers.getObjectField(entry, "icon")); inflateViews(copy, mNotificationPeekRow); if (mNotificationDNDMode) { View content = (View) XposedHelpers.getObjectField(copy, "content"); content.setOnClickListener(new View.OnClickListener() { public void onClick(View v) { SharedPreferences.Editor editor = Prefs.edit(mContext); editor.putBoolean(Prefs.DO_NOT_DISTURB_PREF, false); editor.apply(); animateCollapsePanels(); visibilityChanged(false); } }); } Object icon = XposedHelpers.getObjectField(entry, "icon"); XposedHelpers.callMethod(icon, "setBackgroundColor", 0x20FFFFFF); // mNotificationPeekRow.setLayoutTransition( // peekIndex < mNotificationPeekIndex // ? mNotificationPeekScrubLeft // : mNotificationPeekScrubRight); mNotificationPeekRow.removeAllViews(); mNotificationPeekRow.addView((View) XposedHelpers.getObjectField(copy, "row")); mNotificationPeekWindow.setVisibility(View.VISIBLE); mNotificationPanel.show(false, true); mNotificationPeekIndex = peekIndex; mNotificationPeekKey = (IBinder) XposedHelpers.getObjectField(entry, "key"); } } break; case MSG_CLOSE_NOTIFICATION_PEEK: if (DEBUG) Log.d(TAG, "closing notification peek window"); mNotificationPeekWindow.setVisibility(View.GONE); mNotificationPeekRow.removeAllViews(); final int N = (Integer) XposedHelpers.callMethod(mNotificationData, "size"); if (mNotificationPeekIndex >= 0 && mNotificationPeekIndex < N) { Object entry = mNotificationDNDMode ? mNotificationDNDDummyEntry : XposedHelpers.callMethod(mNotificationData, "get", N-1-mNotificationPeekIndex); Object icon = XposedHelpers.getObjectField(entry, "icon"); XposedHelpers.callMethod(icon, "setBackgroundColor", 0); } mNotificationPeekIndex = -1; mNotificationPeekKey = null; break; case MSG_OPEN_NOTIFICATION_PANEL: if (DEBUG) Log.d(TAG, "opening notifications panel"); awakenDreams(); if (!mNotificationPanel.isShowing()) { mNotificationPanel.show(true, true); mNotificationArea.setAlpha(0); if (NOTIFICATION_PEEK_ENABLED) { mNotificationPeekWindow.setVisibility(View.GONE); } mTicker.halt(); } break; case MSG_CLOSE_NOTIFICATION_PANEL: if (DEBUG) Log.d(TAG, "closing notifications panel"); if (mNotificationPanel.isShowing()) { mNotificationPanel.show(false, true); mNotificationArea.setAlpha(1); } break; case MSG_OPEN_INPUT_METHODS_PANEL: awakenDreams(); if (DEBUG) Log.d(TAG, "opening input methods panel"); if (mInputMethodsPanel != null) mInputMethodsPanel.openPanel(); break; case MSG_CLOSE_INPUT_METHODS_PANEL: if (DEBUG) Log.d(TAG, "closing input methods panel"); if (mInputMethodsPanel != null) mInputMethodsPanel.closePanel(false); break; case MSG_OPEN_COMPAT_MODE_PANEL: awakenDreams(); if (DEBUG) Log.d(TAG, "opening compat panel"); if (mCompatModePanel != null) mCompatModePanel.openPanel(); break; case MSG_CLOSE_COMPAT_MODE_PANEL: if (DEBUG) Log.d(TAG, "closing compat panel"); if (mCompatModePanel != null) mCompatModePanel.closePanel(); break; case MSG_SHOW_CHROME: if (DEBUG) Log.d(TAG, "hiding shadows (lights on)"); mBarContents.setVisibility(View.VISIBLE); mShadow.setVisibility(View.GONE); mSystemUiVisibility &= ~View.SYSTEM_UI_FLAG_LOW_PROFILE; notifyUiVisibilityChanged(mSystemUiVisibility); break; case MSG_HIDE_CHROME: if (DEBUG) Log.d(TAG, "showing shadows (lights out)"); animateCollapsePanels(); visibilityChanged(false); mBarContents.setVisibility(View.GONE); mShadow.setVisibility(View.VISIBLE); mSystemUiVisibility |= View.SYSTEM_UI_FLAG_LOW_PROFILE; notifyUiVisibilityChanged(mSystemUiVisibility); break; case MSG_STOP_TICKER: mTicker.halt(); break; case MSG_SHOW_HEADS_UP: setHeadsUpVisibility(true); break; case MSG_HIDE_HEADS_UP: setHeadsUpVisibility(false); break; case MSG_ESCALATE_HEADS_UP: escalateHeadsUp(); setHeadsUpVisibility(false); break; } } /** if the interrupting notification had a fullscreen intent, fire it now. */ private void escalateHeadsUp() { Object entry = XposedHelpers.getObjectField(self, "mInterruptingNotificationEntry"); if (entry != null) { final StatusBarNotification sbn = (StatusBarNotification) XposedHelpers.getObjectField(entry, "notification"); final Notification notification = sbn.getNotification(); if (notification.fullScreenIntent != null) { if (DEBUG) Log.d(TAG, "converting a heads up to fullScreen"); try { notification.fullScreenIntent.send(); } catch (PendingIntent.CanceledException e) { } } } } public void showClock(boolean show) { View clock = mBarContents.findViewById(SystemR.id.clock); View network_text = mBarContents.findViewById(TkR.id.network_text); if (clock != null) { clock.setVisibility(show ? View.VISIBLE : View.GONE); } if (network_text != null) { network_text.setVisibility((!show) ? View.VISIBLE : View.GONE); } } private boolean hasTicker(Notification n) { return n.tickerView != null || !TextUtils.isEmpty(n.tickerText); } // called by TabletTicker when it's done with all queued ticks public void startTicking() { mNotificationArea.setVisibility(View.INVISIBLE); } // called by TabletTicker when it's done with all queued ticks public void doneTicking() { if (mNotificationArea != null) { mNotificationArea.setVisibility(View.VISIBLE); } } public void animateCollapsePanels() { animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE); } //FIXME public void setNavigationIconHints(int hints) { if (hints == mNavigationIconHints) return; if (DEBUG) { android.widget.Toast.makeText(mContext, "Navigation icon hints = " + hints, Toast.LENGTH_SHORT).show(); } mNavigationIconHints = hints; mStatusBarView.setNavigationIconHints(hints); checkBarModes(); } private void notifyUiVisibilityChanged(int vis) { try { XposedHelpers.callMethod(mWindowManagerService, "statusBarVisibilityChanged", vis); } catch (Exception ex) { } } public void setLightsOn(boolean on) { // Policy note: if the frontmost activity needs the menu key, we assume it is a legacy app // that can't handle lights-out mode. if (mMenuButton.getVisibility() == View.VISIBLE) { on = true; } Log.v(TAG, "setLightsOn(" + on + ")"); if (on) { setSystemUiVisibility(0, View.SYSTEM_UI_FLAG_LOW_PROFILE); } else { setSystemUiVisibility(View.SYSTEM_UI_FLAG_LOW_PROFILE, View.SYSTEM_UI_FLAG_LOW_PROFILE); } } private void showCompatibilityHelp() { if (mCompatibilityHelpDialog != null) { return; } mCompatibilityHelpDialog = View.inflate(mContext, TkR.layout.compat_mode_help, null); View button = mCompatibilityHelpDialog.findViewById(TkR.id.button); button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { hideCompatibilityHelp(); SharedPreferences.Editor editor = Prefs.edit(mContext); editor.putBoolean(Prefs.SHOWN_COMPAT_MODE_HELP, true); editor.apply(); } }); WindowManager.LayoutParams lp = new WindowManager.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT, WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG, WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS | WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM, PixelFormat.TRANSLUCENT); lp.setTitle("CompatibilityModeDialog"); lp.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_STATE_UNCHANGED | WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING; lp.windowAnimations = XposedHelpers.getStaticIntField(TabletKatModule.mComAndroidInternalRStyleClass, "Animation_ZoomButtons"); // simple fade mWindowManager.addView(mCompatibilityHelpDialog, lp); } private void hideCompatibilityHelp() { if (mCompatibilityHelpDialog != null) { mWindowManager.removeView(mCompatibilityHelpDialog); mCompatibilityHelpDialog = null; } } private boolean isImmersive() { try { Object o = XposedHelpers.callStaticMethod(TabletKatModule.mActivityManagerNativeClass, "getDefault"); return (Boolean) XposedHelpers.callMethod(o, "isTopActivityImmersive"); //Log.d(TAG, "Top activity is " + (immersive?"immersive":"not immersive")); } catch (Exception ex) { // the end is nigh return false; } } private View.OnClickListener mOnClickListener = new View.OnClickListener() { public void onClick(View v) { if (v == mRecentButton) { onClickRecentButton(); } else if (v == mInputMethodSwitchButton) { onClickInputMethodSwitchButton(); } else if (v == mCompatModeButton) { onClickCompatModeButton(); } } }; public void onClickRecentButton() { if (DEBUG) Log.d(TAG, "clicked recent apps; disabled=" + mDisabled); if ((mDisabled & StatusBarManager.DISABLE_EXPAND) == 0) { awakenDreams(); toggleRecentApps(); } } public void onClickInputMethodSwitchButton() { if (DEBUG) Log.d(TAG, "clicked input methods panel; disabled=" + mDisabled); int msg = (mInputMethodsPanel.getVisibility() == View.GONE) ? MSG_OPEN_INPUT_METHODS_PANEL : MSG_CLOSE_INPUT_METHODS_PANEL; mHandler.removeMessages(msg); mHandler.sendEmptyMessage(msg); } public void onClickCompatModeButton() { int msg = (mCompatModePanel.getVisibility() == View.GONE) ? MSG_OPEN_COMPAT_MODE_PANEL : MSG_CLOSE_COMPAT_MODE_PANEL; mHandler.removeMessages(msg); mHandler.sendEmptyMessage(msg); } public void setOverlayRecentsVisible(boolean visible) { if (mIsTv && mStatusBarView != null) { mStatusBarView.setBlockEvents(visible); } } private class NotificationTriggerTouchListener implements View.OnTouchListener { VelocityTracker mVT; float mInitialTouchX, mInitialTouchY; int mTouchSlop; public NotificationTriggerTouchListener() { mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop(); } private Runnable mHiliteOnR = new Runnable() { public void run() { mNotificationArea.setBackgroundResource( XposedHelpers.getStaticIntField(TabletKatModule.mComAndroidInternalRDrawableClass, "list_selector_pressed_holo_dark")); }}; public void hilite(final boolean on) { if (on) { mNotificationArea.postDelayed(mHiliteOnR, 100); } else { mNotificationArea.removeCallbacks(mHiliteOnR); mNotificationArea.setBackground(null); } } public boolean onTouch(View v, MotionEvent event) { // Log.d(TAG, String.format("touch: (%.1f, %.1f) initial: (%.1f, %.1f)", // event.getX(), // event.getY(), // mInitialTouchX, // mInitialTouchY)); if ((mDisabled & StatusBarManager.DISABLE_EXPAND) != 0) { return true; } final int action = event.getAction(); switch (action) { case MotionEvent.ACTION_DOWN: mVT = VelocityTracker.obtain(); mInitialTouchX = event.getX(); mInitialTouchY = event.getY(); hilite(true); // fall through case MotionEvent.ACTION_OUTSIDE: case MotionEvent.ACTION_MOVE: // check for fling if (mVT != null) { mVT.addMovement(event); mVT.computeCurrentVelocity(1000); // pixels per second // require a little more oomph once we're already in peekaboo mode if (mVT.getYVelocity() < -mNotificationFlingVelocity) { animateExpandNotificationsPanel(); visibilityChanged(true); hilite(false); mVT.recycle(); mVT = null; } } return true; case MotionEvent.ACTION_UP: case MotionEvent.ACTION_CANCEL: hilite(false); if (mVT != null) { if (action == MotionEvent.ACTION_UP // was this a sloppy tap? && Math.abs(event.getX() - mInitialTouchX) < mTouchSlop && Math.abs(event.getY() - mInitialTouchY) < (mTouchSlop / 3) // dragging off the bottom doesn't count && (int)event.getY() < v.getBottom()) { animateExpandNotificationsPanel(); visibilityChanged(true); v.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED); v.playSoundEffect(SoundEffectConstants.CLICK); } mVT.recycle(); mVT = null; return true; } } return false; } } public void resetNotificationPeekFadeTimer() { if (DEBUG) { Log.d(TAG, "setting peek fade timer for " + NOTIFICATION_PEEK_FADE_DELAY + "ms from now"); } mHandler.removeMessages(MSG_CLOSE_NOTIFICATION_PEEK); mHandler.sendEmptyMessageDelayed(MSG_CLOSE_NOTIFICATION_PEEK, NOTIFICATION_PEEK_FADE_DELAY); } private void reloadAllNotificationIcons() { if (mIconLayout == null) return; mIconLayout.removeAllViews(); mCompatModeButton.setPadding(mIconHPadding, 0, mIconHPadding, 0); mInputMethodSwitchButton.setPadding(mIconHPadding, 0, mIconHPadding, 0); updateNotificationIcons(); } private void loadNotificationPanel() { mNotificationData = XposedHelpers.getObjectField(self, "mNotificationData"); int N = (Integer) XposedHelpers.callMethod(mNotificationData, "size"); ArrayList<View> toShow = new ArrayList<View>(); final boolean provisioned = isDeviceProvisioned(); // If the device hasn't been through Setup, we only show system notifications for (int i=0; i<N; i++) { Object ent = XposedHelpers.callMethod(mNotificationData, "get", N-i-1); StatusBarNotification n = (StatusBarNotification) XposedHelpers.getObjectField(ent, "notification"); if (!(provisioned || showNotificationEvenIfUnprovisioned(n))) { continue; } if (!notificationIsForCurrentUser(n)) { continue; } toShow.add((View) XposedHelpers.getObjectField(ent, "row")); } ArrayList<View> toRemove = new ArrayList<View>(); int n = (Integer) XposedHelpers.callMethod(mPile, "getChildCount"); for (int i=0; i<n; i++) { View child = (View) XposedHelpers.callMethod(mPile, "getChildAt", i); if (!toShow.contains(child)) { toRemove.add(child); } } for (View remove : toRemove) { XposedHelpers.callMethod(mPile, "removeView", remove); } for (int i=0; i<toShow.size(); i++) { View v = toShow.get(i); if (v.getParent() == null) { // the notification panel has the most important things at the bottom int count = (Integer) XposedHelpers.callMethod(mPile, "getChildCount"); XposedHelpers.callMethod(mPile, "addView", v, Math.min(toShow.size() - 1 - i, count)); } } mNotificationPanel.setNotificationCount(toShow.size()); mNotificationPanel.setSettingsEnabled(isDeviceProvisioned()); } public void clearAll() { try { XposedHelpers.callMethod(mBarService, "onClearAllNotifications"); } catch (Exception ex) { // system process is dead if we're here. } animateCollapsePanels(); visibilityChanged(false); } private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { public void onReceive(Context context, Intent intent) { String action = intent.getAction(); if (Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(action)) { int flags = CommandQueue.FLAG_EXCLUDE_NONE; if (Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(action)) { String reason = intent.getStringExtra("reason"); if (reason != null && reason.equals(SYSTEM_DIALOG_REASON_RECENT_APPS)) { flags |= CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL; } } mStatusBarView.notifyScreenOn(false); animateCollapsePanels(flags); } if (Intent.ACTION_SCREEN_OFF.equals(action)) { mScreenOn = false; // no waiting! notifyHeadsUpScreenOn(false); finishBarAnimations(); mStatusBarView.notifyScreenOn(true); } else if (Intent.ACTION_SCREEN_ON.equals(action)) { mScreenOn = true; } } }; private void resetUserSetupObserver() { mContext.getContentResolver().unregisterContentObserver(mUserSetupObserver); mUserSetupObserver.onChange(false); mContext.getContentResolver().registerContentObserver( Settings.Secure.getUriFor(Settings.Secure.USER_SETUP_COMPLETE), true, mUserSetupObserver, XposedHelpers.getIntField(self, "mCurrentUserId")); } private void setHeadsUpVisibility(boolean vis) { if (!ENABLE_HEADS_UP) return; if (DEBUG) Log.v(TAG, (vis ? "showing" : "hiding") + " heads up window"); mHeadsUpNotificationView.setVisibility(vis ? View.VISIBLE : View.GONE); if (!vis) { if (DEBUG) Log.d(TAG, "setting heads up entry to null"); XposedHelpers.setObjectField(self, "mInterruptingNotificationEntry", null); } } public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { pw.print("mDisabled=0x"); pw.println(Integer.toHexString(mDisabled)); pw.println("mNetworkController:"); XposedHelpers.callMethod(mNetworkController, "dump", fd, pw, args); } @Override public void addHooks(ClassLoader cl) { super.addHooks(cl); Class tv = TabletKatModule.mTvStatusBarClass; Class base = TabletKatModule.mBaseStatusBarClass; Class h = TabletKatModule.mBaseStatusBarHClass; XposedHelpers.findAndHookMethod(tv, "setWindowState", int.class, int.class, new XC_MethodReplacement() { @Override protected Object replaceHookedMethod(MethodHookParam methodHookParam) throws Throwable { int window = (Integer) methodHookParam.args[0]; int state = (Integer) methodHookParam.args[1]; boolean showing = state == StatusBarManager.WINDOW_STATE_SHOWING; if (mStatusBarView != null && window == StatusBarManager.WINDOW_NAVIGATION_BAR && mStatusBarWindowState != state) { mStatusBarWindowState = state; if (!showing) { animateCollapsePanels(); } } return null; } }); XposedHelpers.findAndHookMethod(tv, "setImeWindowStatus", IBinder.class, int.class, int.class, new XC_MethodReplacement() { @Override protected Object replaceHookedMethod(MethodHookParam methodHookParam) throws Throwable { int IME_ACTIVE = XposedHelpers.getStaticIntField(InputMethodService.class, "IME_ACTIVE"); int IME_VISIBLE = XposedHelpers.getStaticIntField(InputMethodService.class, "IME_VISIBLE"); IBinder token = (IBinder) methodHookParam.args[0]; int vis = (Integer) methodHookParam.args[1]; int backDisposition = (Integer) methodHookParam.args[2]; mInputMethodSwitchButton.setImeWindowStatus(token, (vis & IME_ACTIVE) != 0); updateNotificationIcons(); mInputMethodsPanel.setImeToken(token); boolean altBack = (backDisposition == InputMethodService.BACK_DISPOSITION_WILL_DISMISS) || ((vis & IME_VISIBLE) != 0); setNavigationIconHints( altBack ? (mNavigationIconHints | StatusBarManager.NAVIGATION_HINT_BACK_ALT) : (mNavigationIconHints & ~StatusBarManager.NAVIGATION_HINT_BACK_ALT)); if (FAKE_SPACE_BAR) { mFakeSpaceBar.setVisibility(((vis & IME_VISIBLE) != 0) ? View.VISIBLE : View.GONE); } return null; } }); XposedHelpers.findAndHookMethod(tv, "createAndAddWindows", new XC_MethodReplacement() { @Override protected Object replaceHookedMethod(MethodHookParam methodHookParam) throws Throwable { addStatusBarWindow(); addPanelWindows(); return null; } }); try { XposedHelpers.findAndHookMethod(tv, "getExpandedViewMaxHeight", new XC_MethodReplacement() { @Override protected Object replaceHookedMethod(MethodHookParam methodHookParam) throws Throwable { return getNotificationPanelHeight(); } }); }catch(NoSuchMethodError e){ } XposedHelpers.findAndHookMethod(base, "onConfigurationChanged", Configuration.class, new XC_MethodHook() { @Override protected void afterHookedMethod(MethodHookParam param) throws Throwable { TabletKatModule.recentsMod.onConfigurationChanged((Configuration) param.args[0]); if (!mIsTv){ return; } loadDimens(); mNotificationPanelParams.height = getNotificationPanelHeight(); mWindowManager.updateViewLayout(mNotificationPanel, mNotificationPanelParams); mShowSearchHoldoff = mContext.getResources().getInteger( SystemR.integer.config_show_search_delay); updateSearchPanel(); } }); XposedHelpers.findAndHookMethod(tv, "refreshLayout", int.class, new XC_MethodReplacement() { @Override protected Object replaceHookedMethod(MethodHookParam methodHookParam) throws Throwable { mNotificationPanel.refreshLayout((Integer)methodHookParam.args[0]); return null; } }); XposedHelpers.findAndHookMethod(tv, "getStatusBarView", new XC_MethodReplacement() { @Override protected Object replaceHookedMethod(MethodHookParam methodHookParam) throws Throwable { return mStatusBarView; } }); XposedHelpers.findAndHookMethod(tv, "getSearchLayoutParams", ViewGroup.LayoutParams.class, new XC_MethodReplacement() { @Override protected Object replaceHookedMethod(MethodHookParam methodHookParam) throws Throwable { boolean opaque = false; WindowManager.LayoutParams lp = new WindowManager.LayoutParams( ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT, XposedHelpers.getStaticIntField(TabletKatModule.mWindowManagerLayoutParamsClass, "TYPE_NAVIGATION_BAR_PANEL"), WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN | WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH, (opaque ? PixelFormat.OPAQUE : PixelFormat.TRANSLUCENT)); if ((Boolean) XposedHelpers.callStaticMethod(ActivityManager.class, "isHighEndGfx")) { lp.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED; } lp.gravity = Gravity.BOTTOM | Gravity.START; lp.setTitle("SearchPanel"); lp.windowAnimations = XposedHelpers.getStaticIntField(TabletKatModule.mComAndroidInternalRStyleClass, "Animation_RecentApplications"); lp.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_STATE_UNCHANGED | WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING; return lp; } }); try { XposedHelpers.findAndHookMethod(base, "updateSearchPanel", new XC_MethodReplacement() { @Override protected Object replaceHookedMethod(MethodHookParam methodHookParam) throws Throwable { if (!mIsTv) { return TabletKatModule.invokeOriginalMethod(methodHookParam); } View mSearchPanelView = (View) XposedHelpers.getObjectField(self, "mSearchPanelView"); // Search Panel boolean visible = false; if (mSearchPanelView != null) { visible = (Boolean) XposedHelpers.callMethod(mSearchPanelView, "isShowing"); mWindowManager.removeView(mSearchPanelView); } // Provide SearchPanel with a temporary parent to allow layout params to work. LinearLayout tmpRoot = new LinearLayout(mContext); Configuration c = new Configuration(mContext.getResources().getConfiguration()); c.smallestScreenWidthDp = 720; Context c2 = mContext.createConfigurationContext(c); mSearchPanelView = LayoutInflater.from(c2).inflate( SystemR.layout.status_bar_search_panel, tmpRoot, false); mSearchPanelView.setOnTouchListener( new TouchOutsideListener(MSG_CLOSE_SEARCH_PANEL, mSearchPanelView)); mSearchPanelView.setVisibility(View.GONE); WindowManager.LayoutParams lp = getSearchLayoutParams(mSearchPanelView.getLayoutParams()); mWindowManager.addView(mSearchPanelView, lp); XposedHelpers.callMethod(mSearchPanelView, "setBar", self); if (visible) { XposedHelpers.callMethod(mSearchPanelView, "show", true, false); } mStatusBarView.setDelegateView(mSearchPanelView); XposedHelpers.setObjectField(self, "mSearchPanelView", mSearchPanelView); return null; } }); }catch (NoSuchMethodError e){} XposedHelpers.findAndHookMethod(base, "showSearchPanel", new XC_MethodHook() { @Override protected void afterHookedMethod(MethodHookParam param) throws Throwable { if (!mIsTv){ return; } WindowManager.LayoutParams lp = (android.view.WindowManager.LayoutParams) mStatusBarView.getLayoutParams(); lp.flags &= ~WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL; mWindowManager.updateViewLayout(mStatusBarView, lp); } }); XposedHelpers.findAndHookMethod(base, "hideSearchPanel", new XC_MethodHook() { @Override protected void afterHookedMethod(MethodHookParam param) throws Throwable { if (!mIsTv){ return; } WindowManager.LayoutParams lp = (android.view.WindowManager.LayoutParams) mStatusBarView.getLayoutParams(); lp.flags |= WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL; mWindowManager.updateViewLayout(mStatusBarView, lp); } }); XposedHelpers.findAndHookMethod(h, "handleMessage", Message.class, new XC_MethodHook() { @Override protected void afterHookedMethod(MethodHookParam param) throws Throwable { if (!mIsTv){ return; } handleMessage((Message) param.args[0]); } }); XposedHelpers.findAndHookMethod(tv, "tick", IBinder.class, StatusBarNotification.class, boolean.class, new XC_MethodReplacement() { @Override protected Object replaceHookedMethod(MethodHookParam methodHookParam) throws Throwable { IBinder key = (IBinder) methodHookParam.args[0]; StatusBarNotification n = (StatusBarNotification) methodHookParam.args[1]; boolean firstTime = (Boolean) methodHookParam.args[2]; // no ticking in Setup if (!isDeviceProvisioned()) { return null; } // not for you if (!notificationIsForCurrentUser(n)) { return null; } // Don't show the ticker when the windowshade is open. if (mNotificationPanel.isShowing()) { return null; } // If they asked for FLAG_ONLY_ALERT_ONCE, then only show this notification // if it's a new notification. if (!firstTime && (n.getNotification().flags & Notification.FLAG_ONLY_ALERT_ONCE) != 0) { return null; } // Don't show minimum priority notifications if (n.getNotification().priority == Notification.PRIORITY_MIN) { return null; } // Show the ticker if one is requested. Also don't do this // until status bar window is attached to the window manager, // because... well, what's the point otherwise? And trying to // run a ticker without being attached will crash! if (hasTicker(n.getNotification()) && mStatusBarView.getWindowToken() != null) { if (0 == (mDisabled & (StatusBarManager.DISABLE_NOTIFICATION_ICONS | StatusBarManager.DISABLE_NOTIFICATION_TICKER))) { mTicker.add(key, n); } } return null; } }); /* XposedHelpers.findAndHookMethod(tv, "animateExpandSettingsPanel", XC_MethodReplacement.DO_NOTHING); */ XposedHelpers.findAndHookMethod(tv, "setSystemUiVisibility", int.class, int.class, new XC_MethodReplacement() { @Override protected Object replaceHookedMethod(MethodHookParam methodHookParam) throws Throwable { int vis = (Integer) methodHookParam.args[0]; int mask = (Integer) methodHookParam.args[1]; final int oldVal = mSystemUiVisibility; final int newVal = (oldVal & ~mask) | (vis & mask); final int diff = newVal ^ oldVal; if (diff != 0) { mSystemUiVisibility = newVal; if (0 != (diff & View.SYSTEM_UI_FLAG_LOW_PROFILE)) { final boolean lightsOut = (vis & View.SYSTEM_UI_FLAG_LOW_PROFILE) != 0; if (lightsOut) { animateCollapsePanels(); // if (mTicking) { // XposedHelpers.callMethod(self, "haltTicker"); // } } setAreThereNotifications(); } final int sbMode = mStatusBarView == null ? -1 : computeBarMode( oldVal, newVal, mStatusBarView.getBarTransitions(), ViewConstants.NAVIGATION_BAR_TRANSIENT, ViewConstants.NAVIGATION_BAR_TRANSLUCENT); final boolean sbModeChanged = sbMode != -1; if (sbModeChanged && sbMode != mStatusBarMode) { mStatusBarMode = sbMode; checkBarModes = true; } if (checkBarModes) { checkBarModes(); } if (sbModeChanged) { // update transient bar autohide if (sbMode == BarTransitions.MODE_SEMI_TRANSPARENT) { scheduleAutohide(); } else { cancelAutohide(); } } // ready to unhide if ((vis & ViewConstants.NAVIGATION_BAR_UNHIDE) != 0) { mSystemUiVisibility &= ~ViewConstants.NAVIGATION_BAR_UNHIDE; } // send updated sysui visibility to window manager notifyUiVisibilityChanged(mSystemUiVisibility); } return null; } }); XposedHelpers.findAndHookMethod(tv, "setHardKeyboardStatus", boolean.class, boolean.class, new XC_MethodReplacement() { @Override protected Object replaceHookedMethod(MethodHookParam methodHookParam) throws Throwable { boolean available = (Boolean) methodHookParam.args[0]; boolean enabled = (Boolean) methodHookParam.args[1]; if (DEBUG) { Log.d(TAG, "Set hard keyboard status: available=" + available + ", enabled=" + enabled); } mInputMethodSwitchButton.setHardKeyboardStatus(available); updateNotificationIcons(); mInputMethodsPanel.setHardKeyboardStatus(available, enabled); return null; } }); XposedHelpers.findAndHookMethod(tv, "setAreThereNotifications", new XC_MethodReplacement() { @Override protected Object replaceHookedMethod(MethodHookParam methodHookParam) throws Throwable { if (mNotificationPanel != null) { final boolean any = (Integer) XposedHelpers.callMethod(mNotificationData, "size") > 0; final boolean clearable = any && (Boolean) XposedHelpers.callMethod(mNotificationData, "hasClearableItems"); mNotificationPanel.setClearable(isDeviceProvisioned() && clearable); } return null; } }); XposedHelpers.findAndHookMethod(tv, "updateNotificationIcons", new XC_MethodReplacement() { @Override protected Object replaceHookedMethod(MethodHookParam methodHookParam) throws Throwable { // XXX: need to implement a new limited linear layout class // to avoid removing & readding everything if (mIconLayout == null) return null; // first, populate the main notification panel loadNotificationPanel(); final LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(mIconSize + 2 * mIconHPadding, mNaturalBarHeight); // alternate behavior in DND mode if (mNotificationDNDMode) { if (mIconLayout.getChildCount() == 0) { final Notification dndNotification = new Notification.Builder(mContext) .setContentTitle(mContext.getText(TkR.string.notifications_off_title)) .setContentText(mContext.getText(TkR.string.notifications_off_text)) .setSmallIcon(TkR.drawable.ic_notification_dnd) .setOngoing(true) .getNotification(); final ImageView iconView = (ImageView) XposedHelpers.newInstance(TabletKatModule.mStatusBarIconViewClass, mLargeIconContext, "_dnd", dndNotification); iconView.setImageResource(TkR.drawable.ic_notification_dnd); iconView.setScaleType(ImageView.ScaleType.CENTER_INSIDE); iconView.setPadding(mIconHPadding, 0, mIconHPadding, 0); mNotificationDNDDummyEntry = XposedHelpers.newInstance(TabletKatModule.mNotificationDataEntryClass, new Class<?>[]{IBinder.class, StatusBarNotification.class, TabletKatModule.mStatusBarIconViewClass}, null, new StatusBarNotification("", null, 0, "", 0, 0, Notification.PRIORITY_MAX, dndNotification, android.os.Process.myUserHandle(), System.currentTimeMillis()), iconView); mIconLayout.addView(iconView, params); } return null; } else if (0 != (mDisabled & StatusBarManager.DISABLE_NOTIFICATION_ICONS)) { // if icons are disabled but we're not in DND mode, this is probably Setup and we should // just leave the area totally empty return null; } int N = (Integer) XposedHelpers.callMethod(mNotificationData, "size"); if (DEBUG) { Log.d(TAG, "refreshing icons: " + N + " notifications, mIconLayout=" + mIconLayout); } ArrayList<View> toShow = new ArrayList<View>(); // Extra Special Icons // The IME switcher and compatibility mode icons take the place of notifications. You didn't // need to see all those new emails, did you? int maxNotificationIconsCount = mMaxNotificationIcons; if (mInputMethodSwitchButton.getVisibility() != View.GONE) maxNotificationIconsCount--; if (mCompatModeButton.getVisibility() != View.GONE) maxNotificationIconsCount--; final boolean provisioned = isDeviceProvisioned(); // If the device hasn't been through Setup, we only show system notifications for (int i = 0; toShow.size() < maxNotificationIconsCount; i++) { if (i >= N) break; Object ent = XposedHelpers.callMethod(mNotificationData, "get", N - i - 1); StatusBarNotification n = (StatusBarNotification) XposedHelpers.getObjectField(ent, "notification"); if (!((provisioned && (Integer) XposedHelpers.callMethod(n, "getScore") >= HIDE_ICONS_BELOW_SCORE) || showNotificationEvenIfUnprovisioned(n))) { continue; } if (!notificationIsForCurrentUser(n)) { continue; } toShow.add((View) XposedHelpers.getObjectField(ent, "icon")); } ArrayList<View> toRemove = new ArrayList<View>(); for (int i = 0; i < mIconLayout.getChildCount(); i++) { View child = mIconLayout.getChildAt(i); if (!toShow.contains(child)) { toRemove.add(child); } } for (View remove : toRemove) { mIconLayout.removeView(remove); } for (int i = 0; i < toShow.size(); i++) { View v = toShow.get(i); v.setPadding(mIconHPadding, 0, mIconHPadding, 0); if (v.getParent() == null) { mIconLayout.addView(v, i, params); } } return null; } }); XposedHelpers.findAndHookMethod(base, "workAroundBadLayerDrawableOpacity", View.class, new XC_MethodReplacement() { @Override protected Object replaceHookedMethod(MethodHookParam methodHookParam) throws Throwable { if (!mIsTv){ return TabletKatModule.invokeOriginalMethod(methodHookParam); } View v = (View) methodHookParam.args[0]; Drawable bgd = v.getBackground(); if (!(bgd instanceof LayerDrawable)) return null; LayerDrawable d = (LayerDrawable) bgd; v.setBackground(null); d.setOpacity(PixelFormat.TRANSLUCENT); v.setBackground(d); return null; } }); XposedHelpers.findAndHookMethod(base, "isTopNotification", ViewGroup.class, TabletKatModule.mNotificationDataEntryClass, new XC_MethodReplacement() { @Override protected Object replaceHookedMethod(MethodHookParam methodHookParam) throws Throwable { if (!mIsTv){ return TabletKatModule.invokeOriginalMethod(methodHookParam); } ViewGroup parent = (ViewGroup) methodHookParam.args[0]; Object entry = methodHookParam.args[1]; if (parent == null || entry == null) return false; Object row = XposedHelpers.getObjectField(entry, "row"); int index = (Integer) XposedHelpers.callMethod(parent, "indexOfChild", row); return index == parent.getChildCount() - 1; } }); try { XposedHelpers.findAndHookMethod(tv, "haltTicker", new XC_MethodReplacement() { @Override protected Object replaceHookedMethod(MethodHookParam methodHookParam) throws Throwable { mTicker.halt(); return null; } }); }catch (NoSuchMethodError e){} XposedHelpers.findAndHookMethod(tv, "updateExpandedViewPos", int.class, XC_MethodReplacement.DO_NOTHING); XposedHelpers.findAndHookMethod(tv, "shouldDisableNavbarGestures", new XC_MethodReplacement() { @Override protected Object replaceHookedMethod(MethodHookParam methodHookParam) throws Throwable { return mNotificationPanel.getVisibility() == View.VISIBLE || (mDisabled & StatusBarManager.DISABLE_SEARCH) != 0; } }); /* XposedHelpers.findAndHookMethod(tv, "onDestroy", new XC_MethodHook() { @Override protected void afterHookedMethod(MethodHookParam param) throws Throwable { mContext.unregisterReceiver(mBroadcastReceiver); } });*/ XposedHelpers.findAndHookMethod(tv, "addIcon", String.class, int.class, int.class, TabletKatModule.mStatusBarIconClass, new XC_MethodReplacement() { @Override protected Object replaceHookedMethod(MethodHookParam methodHookParam) throws Throwable { String slot = (String) methodHookParam.args[0]; int viewIndex = 0; //(Integer) methodHookParam.args[2]; Object icon = methodHookParam.args[3]; if (DEBUG) Log.d(TAG, "addIcon(" + slot + ") -> " + icon); // Toast.makeText(mContext, "addIcon(" + slot + ") -> " + icon, Toast.LENGTH_SHORT).show(); if (!allowIcon(slot)){ return null; } ImageView view = (ImageView) XposedHelpers.newInstance(TabletKatModule.mStatusBarIconViewClass, mLargeIconContext, slot, null); view.setScaleType(ImageView.ScaleType.FIT_CENTER); view.setPadding(0, 0, 0, 0); XposedHelpers.callMethod(view, "set", icon); mStatusIcons.addView(view, viewIndex, new LinearLayout.LayoutParams(mIconSize, mIconSize)); return null; }; }); XposedHelpers.findAndHookMethod(tv, "updateIcon", String.class, int.class, int.class, TabletKatModule.mStatusBarIconClass, TabletKatModule.mStatusBarIconClass, new XC_MethodReplacement() { @Override protected Object replaceHookedMethod(MethodHookParam methodHookParam) throws Throwable { String slot = (String) methodHookParam.args[0]; int viewIndex = 0;//(Integer) methodHookParam.args[2]; Object icon = methodHookParam.args[3]; if (DEBUG) Log.d(TAG, "updateIcon(" + slot + ") -> " + icon); // Toast.makeText(mContext, "updateIcon(" + slot + ") -> " + icon, Toast.LENGTH_SHORT).show(); if (!allowIcon(slot)){ return null; } ImageView view = (ImageView) mStatusIcons.getChildAt(viewIndex); XposedHelpers.callMethod(view, "set", icon); return null; } }); XposedHelpers.findAndHookMethod(tv, "removeIcon", String.class, int.class, int.class, new XC_MethodReplacement() { @Override protected Object replaceHookedMethod(MethodHookParam methodHookParam) throws Throwable { String slot = (String) methodHookParam.args[0]; int viewIndex = 0;//(Integer) methodHookParam.args[2]; if (DEBUG) Log.d(TAG, "removeIcon(" + slot + ")"); // Toast.makeText(mContext, "removeIcon(" + slot + ")", Toast.LENGTH_SHORT).show(); if (!allowIcon(slot)){ return null; } mStatusIcons.removeViewAt(viewIndex); return null; } }); XposedHelpers.findAndHookMethod(tv, "addNotification", IBinder.class, StatusBarNotification.class, new XC_MethodReplacement() { @Override protected Object replaceHookedMethod(MethodHookParam methodHookParam) throws Throwable { IBinder key = (IBinder) methodHookParam.args[0]; StatusBarNotification notification = (StatusBarNotification) methodHookParam.args[1]; if (isNotificationBlacklisted(notification)){ return null; } if (DEBUG) Log.d(TAG, "addNotification(" + key + " -> " + notification + ")"); Object shadeEntry = addNotificationViews(key, notification); if (XposedHelpers.getBooleanField(self, "mUseHeadsUp") && shouldInterrupt(notification)) { if (DEBUG) Log.d(TAG, "launching notification in heads up mode"); Object interruptionCandidate = XposedHelpers.newInstance( TabletKatModule.mNotificationDataEntryClass, key, notification, null); if (inflateViews(interruptionCandidate, (ViewGroup) XposedHelpers.callMethod(mHeadsUpNotificationView, "getHolder"))) { XposedHelpers.setLongField(self, "mInterruptingNotificationTime", System.currentTimeMillis()); XposedHelpers.setObjectField(self, "mInterruptingNotificationEntry", interruptionCandidate); XposedHelpers.callMethod(shadeEntry, "setInterruption"); // 1. Populate mHeadsUpNotificationView XposedHelpers.callMethod(mHeadsUpNotificationView, "setNotification", interruptionCandidate); // 2. Animate mHeadsUpNotificationView in mHandler.sendEmptyMessage(MSG_SHOW_HEADS_UP); // 3. Set alarm to age the notification off resetHeadsUpDecayTimer(); } } else if (notification.getNotification().fullScreenIntent != null) { // Stop screensaver if the notification has a full-screen intent. // (like an incoming phone call) awakenDreams(); // not immersive & a full-screen alert should be shown Log.w(TAG, "Notification has fullScreenIntent and activity is not immersive;" + " sending fullScreenIntent"); try { notification.getNotification().fullScreenIntent.send(); } catch (PendingIntent.CanceledException e) { } } else { tick(key, notification, true); } setAreThereNotifications(); return null; } }); try { XposedHelpers.findAndHookMethod(tv, "resetHeadsUpDecayTimer", new XC_MethodReplacement() { @Override protected Object replaceHookedMethod(MethodHookParam methodHookParam) throws Throwable { if (XposedHelpers.getBooleanField(self, "mUseHeadsUp") && mHeadsUpNotificationDecay > 0 && (Boolean) XposedHelpers.callMethod(mHeadsUpNotificationView, "isClearable")) { mHandler.removeMessages(MSG_HIDE_HEADS_UP); mHandler.sendEmptyMessageDelayed(MSG_HIDE_HEADS_UP, mHeadsUpNotificationDecay); } return null; } }); } catch (NoSuchMethodError e) { ENABLE_HEADS_UP = false; } XposedHelpers.findAndHookMethod(tv, "removeNotification", IBinder.class, new XC_MethodReplacement() { @Override protected Object replaceHookedMethod(MethodHookParam methodHookParam) throws Throwable { IBinder key = (IBinder) methodHookParam.args[0]; if (DEBUG) Log.d(TAG, "removeNotification(" + key + ")"); StatusBarNotification old = removeNotificationViews(key); mTicker.remove(key); setAreThereNotifications(); mNotificationPanel.updateClearButton(); Object entry = XposedHelpers.getObjectField(self, "mInterruptingNotificationEntry"); if (ENABLE_HEADS_UP && entry != null && old == XposedHelpers.getObjectField(entry, "notification")) { mHandler.sendEmptyMessage(MSG_HIDE_HEADS_UP); } if (NOTIFICATION_PEEK_ENABLED && key == mNotificationPeekKey) { // must close the peek as well, since it's gone mHandler.sendEmptyMessage(MSG_CLOSE_NOTIFICATION_PEEK); } return null; } }); XposedHelpers.findAndHookMethod(tv, "animateCollapsePanels", int.class, new XC_MethodReplacement() { @Override protected Object replaceHookedMethod(MethodHookParam methodHookParam) throws Throwable { int flags = (Integer) methodHookParam.args[0]; if ((flags & CommandQueue.FLAG_EXCLUDE_NOTIFICATION_PANEL) == 0) { mHandler.removeMessages(MSG_CLOSE_NOTIFICATION_PANEL); mHandler.sendEmptyMessage(MSG_CLOSE_NOTIFICATION_PANEL); } if (NOTIFICATION_PEEK_ENABLED) { mHandler.removeMessages(MSG_CLOSE_NOTIFICATION_PEEK); mHandler.removeMessages(MSG_OPEN_NOTIFICATION_PEEK); mHandler.sendEmptyMessage(MSG_CLOSE_NOTIFICATION_PEEK); } if ((flags & CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL) == 0) { mHandler.removeMessages(MSG_CLOSE_RECENTS_PANEL); mHandler.sendEmptyMessage(MSG_CLOSE_RECENTS_PANEL); } if ((flags & CommandQueue.FLAG_EXCLUDE_SEARCH_PANEL) == 0) { mHandler.removeMessages(MSG_CLOSE_SEARCH_PANEL); mHandler.sendEmptyMessage(MSG_CLOSE_SEARCH_PANEL); } if ((flags & CommandQueue.FLAG_EXCLUDE_INPUT_METHODS_PANEL) == 0) { mHandler.removeMessages(MSG_CLOSE_INPUT_METHODS_PANEL); mHandler.sendEmptyMessage(MSG_CLOSE_INPUT_METHODS_PANEL); } if ((flags & CommandQueue.FLAG_EXCLUDE_COMPAT_MODE_PANEL) == 0) { mHandler.removeMessages(MSG_CLOSE_COMPAT_MODE_PANEL); mHandler.sendEmptyMessage(MSG_CLOSE_COMPAT_MODE_PANEL); } return null; } }); XposedHelpers.findAndHookMethod(tv, "disable", int.class, new XC_MethodReplacement() { @Override protected Object replaceHookedMethod(MethodHookParam methodHookParam) throws Throwable { int state = (Integer) methodHookParam.args[0]; int old = mDisabled; int diff = state ^ old; mDisabled = state; // act accordingly if ((diff & StatusBarManager.DISABLE_CLOCK) != 0) { boolean show = (state & StatusBarManager.DISABLE_CLOCK) == 0; Log.i(TAG, "DISABLE_CLOCK: " + (show ? "no" : "yes")); showClock(show); } if ((diff & StatusBarManager.DISABLE_SYSTEM_INFO) != 0) { boolean show = (state & StatusBarManager.DISABLE_SYSTEM_INFO) == 0; Log.i(TAG, "DISABLE_SYSTEM_INFO: " + (show ? "no" : "yes")); mNotificationTrigger.setVisibility(show ? View.VISIBLE : View.GONE); } if ((diff & StatusBarManager.DISABLE_EXPAND) != 0) { if ((state & StatusBarManager.DISABLE_EXPAND) != 0) { Log.i(TAG, "DISABLE_EXPAND: yes"); animateCollapsePanels(); visibilityChanged(false); } } if ((diff & StatusBarManager.DISABLE_NOTIFICATION_ICONS) != 0) { mNotificationDNDMode = Prefs.read(mContext) .getBoolean(Prefs.DO_NOT_DISTURB_PREF, Prefs.DO_NOT_DISTURB_DEFAULT); if ((state & StatusBarManager.DISABLE_NOTIFICATION_ICONS) != 0) { Log.i(TAG, "DISABLE_NOTIFICATION_ICONS: yes" + (mNotificationDNDMode?" (DND)":"")); mTicker.halt(); } else { Log.i(TAG, "DISABLE_NOTIFICATION_ICONS: no" + (mNotificationDNDMode?" (DND)":"")); } // refresh icons to show either notifications or the DND message reloadAllNotificationIcons(); } else if ((diff & StatusBarManager.DISABLE_NOTIFICATION_TICKER) != 0) { if ((state & StatusBarManager.DISABLE_NOTIFICATION_TICKER) != 0) { mTicker.halt(); } } if ((diff & (StatusBarManager.DISABLE_RECENT | StatusBarManager.DISABLE_BACK | StatusBarManager.DISABLE_HOME | StatusBarManager.DISABLE_SEARCH)) != 0) { mStatusBarView.setDisabledFlags(state); if ((state & StatusBarManager.DISABLE_RECENT) != 0) { // close recents if it's visible mHandler.removeMessages(MSG_CLOSE_RECENTS_PANEL); mHandler.sendEmptyMessage(MSG_CLOSE_RECENTS_PANEL); } } return null; } }); XposedHelpers.findAndHookMethod(tv, "topAppWindowChanged", boolean.class, new XC_MethodReplacement() { @Override protected Object replaceHookedMethod(MethodHookParam methodHookParam) throws Throwable { if (!mIsTv){ return TabletKatModule.invokeOriginalMethod(methodHookParam); } if (mMenuButton == null) { return null; } boolean showMenu = (Boolean) methodHookParam.args[0]; if (DEBUG) { Log.d(TAG, (showMenu?"showing":"hiding") + " the MENU button"); } mMenuButton.setVisibility(showMenu ? View.VISIBLE : View.GONE); // See above re: lights-out policy for legacy apps. if (showMenu) setLightsOn(true); mCompatModeButton.refresh(); if (mCompatModeButton.getVisibility() == View.VISIBLE) { if (DEBUG_COMPAT_HELP || ! Prefs.read(mContext).getBoolean(Prefs.SHOWN_COMPAT_MODE_HELP, false)) { showCompatibilityHelp(); } } else { hideCompatibilityHelp(); mCompatModePanel.closePanel(); } return null; } }); XposedHelpers.findAndHookMethod(tv, "animateExpandNotificationsPanel", new XC_MethodReplacement() { @Override protected Object replaceHookedMethod(MethodHookParam methodHookParam) throws Throwable { mHandler.removeMessages(MSG_OPEN_NOTIFICATION_PANEL); mHandler.sendEmptyMessage(MSG_OPEN_NOTIFICATION_PANEL); return null; } }); XposedHelpers.findAndHookMethod(base, "onShowSearchPanel", new XC_MethodReplacement() { @Override protected Object replaceHookedMethod(MethodHookParam methodHookParam) throws Throwable { if (!mIsTv){ return TabletKatModule.invokeOriginalMethod(methodHookParam); } mStatusBarView.getBarTransitions().setContentVisible(false); return null; } }); XposedHelpers.findAndHookMethod(base, "onHideSearchPanel", new XC_MethodReplacement() { @Override protected Object replaceHookedMethod(MethodHookParam methodHookParam) throws Throwable { if (!mIsTv){ return TabletKatModule.invokeOriginalMethod(methodHookParam); } mStatusBarView.getBarTransitions().setContentVisible(true); return null; } }); XposedHelpers.findAndHookMethod(base, "setInteracting", int.class, boolean.class, new XC_MethodReplacement() { @Override protected Object replaceHookedMethod(MethodHookParam methodHookParam) throws Throwable { if (!mIsTv){ return TabletKatModule.invokeOriginalMethod(methodHookParam); } int barWindow = (Integer) methodHookParam.args[0]; boolean interacting = (Boolean) methodHookParam.args[1]; mInteractingWindows = interacting ? (mInteractingWindows | barWindow) : (mInteractingWindows & ~barWindow); if (mInteractingWindows != 0) { suspendAutohide(); } else { resumeSuspendedAutohide(); } checkBarModes(); return null; } }); XposedHelpers.findAndHookMethod(base, "destroy", new XC_MethodHook() { @Override protected void afterHookedMethod(MethodHookParam param) throws Throwable { if (!mIsTv) { return; } if (mStatusBarView != null) { mWindowManager.removeViewImmediate(mStatusBarView); } if (mNotificationPanel != null) { mWindowManager.removeViewImmediate(mNotificationPanel); } if (mInputMethodsPanel != null) { mWindowManager.removeViewImmediate(mInputMethodsPanel); } if (mCompatModePanel != null) { mWindowManager.removeViewImmediate(mCompatModePanel); } if (mCompatibilityHelpDialog != null) { mWindowManager.removeViewImmediate(mCompatibilityHelpDialog); } if (mTicker != null) { mTicker.halt(); } mContext.unregisterReceiver(mBroadcastReceiver); reset(); } }); XposedHelpers.findAndHookMethod(base, "userSwitched", int.class, new XC_MethodReplacement() { @Override protected Object replaceHookedMethod(MethodHookParam methodHookParam) throws Throwable { animateCollapsePanels(); updateNotificationIcons(); resetUserSetupObserver(); XposedHelpers.callMethod(self, "preloadRecentApps"); return null; } }); try { XposedHelpers.findAndHookMethod(base, "onHeadsUpDismissed", new XC_MethodReplacement() { @Override protected Object replaceHookedMethod(MethodHookParam methodHookParam) throws Throwable { Object mInterruptingNotificationEntry = XposedHelpers.getObjectField(self, "mInterruptingNotificationEntry"); if (mInterruptingNotificationEntry == null) return null; mHandler.sendEmptyMessage(MSG_HIDE_HEADS_UP); if ((Boolean) XposedHelpers.callMethod(mHeadsUpNotificationView, "isClearable")) { try { StatusBarNotification notification = (StatusBarNotification) XposedHelpers.getObjectField(mInterruptingNotificationEntry, "notification"); XposedHelpers.callMethod(mBarService, "onNotificationClear", notification.getPackageName(), notification.getTag(), notification.getId()); } catch (Exception ex) { // oh well } } return null; } }); } catch (NoSuchMethodError e) { ENABLE_HEADS_UP = false; } XposedHelpers.findAndHookMethod(TabletKatModule.mDateViewClass, "updateClock", new XC_MethodReplacement() { @Override protected Object replaceHookedMethod(MethodHookParam methodHookParam) throws Throwable { if (!mIsTv){ return TabletKatModule.invokeOriginalMethod(methodHookParam); } TextView text = (TextView) methodHookParam.thisObject; final Context context = getContext(); Date now = new Date(); CharSequence dow = DateFormat.format("EEEE", now); CharSequence date = DateFormat.getLongDateFormat(context).format(now); text.setText(context.getString(TkR.string.status_bar_date_formatter, dow, date)); return null; } }); } @Override protected void updatePeek(IBinder key) { if (NOTIFICATION_PEEK_ENABLED && key == mNotificationPeekKey) { // must update the peek window Message peekMsg = mHandler.obtainMessage(MSG_OPEN_NOTIFICATION_PEEK); peekMsg.arg1 = mNotificationPeekIndex; mHandler.removeMessages(MSG_OPEN_NOTIFICATION_PEEK); mHandler.sendMessage(peekMsg); } } private boolean allowIcon(String slot) { return "location".equals(slot); } private void resumeSuspendedAutohide() { if (mAutohideSuspended) { scheduleAutohide(); mHandler.postDelayed(mCheckBarModes, 500); // longer than home -> launcher } } private void suspendAutohide() { mHandler.removeCallbacks(mAutohide); mHandler.removeCallbacks(mCheckBarModes); mAutohideSuspended = (mSystemUiVisibility & ViewConstants.NAVIGATION_BAR_TRANSIENT) != 0; } private void cancelAutohide() { mAutohideSuspended = false; mHandler.removeCallbacks(mAutohide); } private void scheduleAutohide() { cancelAutohide(); mHandler.postDelayed(mAutohide, AUTOHIDE_TIMEOUT_MS); } private void checkUserAutohide(View v, MotionEvent event) { if ((mSystemUiVisibility & ViewConstants.NAVIGATION_BAR_TRANSIENT) != 0 // a transient bar is revealed && event.getAction() == MotionEvent.ACTION_OUTSIDE // touch outside the source bar && event.getX() == 0 && event.getY() == 0 // a touch outside both bars ) { userAutohide(); } } private void userAutohide() { cancelAutohide(); mHandler.postDelayed(mAutohide, 350); // longer than app gesture -> flag clear } //We have an IME switcher button, so we don't want any notifications about that private boolean isNotificationBlacklisted(StatusBarNotification n) { if ("android".equals(n.getPackageName())) { String[] kind = (String[]) XposedHelpers.getObjectField(n.getNotification(), "kind"); if (kind != null) { for (String aKind : kind) { // IME switcher, created by InputMethodManagerService if ("android.system.imeswitcher".equals(aKind)) return true; } } } return false; } public void animateExpandNotificationsPanel() { XposedHelpers.callMethod(self, "animateExpandNotificationsPanel"); } @Override public void onHardKeyboardEnabledChange(boolean enabled) { try { XposedHelpers.callMethod(mBarService, "setHardKeyboardEnabled", enabled); } catch (Exception ex) { } } private int computeBarMode(int oldVis, int newVis, BarTransitions transitions, int transientFlag, int translucentFlag) { final int oldMode = barMode(oldVis, transientFlag, translucentFlag); final int newMode = barMode(newVis, transientFlag, translucentFlag); if (oldMode == newMode) { return -1; // no mode change } return newMode; } private int barMode(int vis, int transientFlag, int translucentFlag) { return (vis & transientFlag) != 0 ? BarTransitions.MODE_SEMI_TRANSPARENT : (vis & translucentFlag) != 0 ? BarTransitions.MODE_TRANSLUCENT : (vis & View.SYSTEM_UI_FLAG_LOW_PROFILE) != 0 ? BarTransitions.MODE_LIGHTS_OUT : BarTransitions.MODE_OPAQUE; } private void checkBarModes() { int sbMode = mStatusBarMode; if (TabletKatModule.recentsMod.isOverlayShowing()) { sbMode = BarTransitions.MODE_OPAQUE; } checkBarMode(sbMode, mStatusBarWindowState, mStatusBarView.getBarTransitions()); } private void checkBarMode(int mode, int windowState, BarTransitions transitions) { final boolean anim = (mScreenOn == null || mScreenOn) && windowState != StatusBarManager.WINDOW_STATE_HIDDEN; transitions.transitionTo(mode, anim); } private void finishBarAnimations() { mStatusBarView.getBarTransitions().finishAnimations(); } private final Runnable mAutohide = new Runnable() { @Override public void run() { int requested = mSystemUiVisibility & ~ViewConstants.NAVIGATION_BAR_TRANSIENT; if (mSystemUiVisibility != requested) { notifyUiVisibilityChanged(requested); } }}; private final Runnable mCheckBarModes = new Runnable() { @Override public void run() { checkBarModes(); }}; @Override public void onStart() { if (!mIsTv) { return; } super.onStart(); mHeadsUpObserver.onChange(true); // set up if (ENABLE_HEADS_UP) { mContext.getContentResolver().registerContentObserver( Settings.Global.getUriFor(SETTING_HEADS_UP), true, mHeadsUpObserver); } } private void replaceDimen(final XResources res, final XModuleResources res2, final int id, final String str, final boolean inline){ final float orig = inline ? 0 : res.getDimension(res.getIdentifier(str, "dimen", TabletKatModule.SYSTEMUI_PACKAGE)); res.setReplacement(TabletKatModule.SYSTEMUI_PACKAGE, "dimen", str, new CustomDimenReplacement() { @Override protected float getValue() { if (mIsTv) { return res2.getDimension(id); } return !inline ? orig : res.getDimension(res.getIdentifier(str, "dimen", TabletKatModule.SYSTEMUI_PACKAGE)); } } ); } @Override public void initResources(final XResources res, final XModuleResources res2) { super.initResources(res, res2); try { res.hookLayout(TabletKatModule.SYSTEMUI_PACKAGE, "layout", "heads_up", new XC_LayoutInflated() { @Override public void handleLayoutInflated(LayoutInflatedParam param) throws Throwable { if (!mIsTv) { return; } ViewGroup v = (ViewGroup) param.view; DisplayMetrics d = v.getResources().getDisplayMetrics(); int content_slider = param.res.getIdentifier("content_slider", "id", TabletKatModule.SYSTEMUI_PACKAGE); View contentSlider = v.findViewById(content_slider); FrameLayout.LayoutParams params = (FrameLayout.LayoutParams) contentSlider.getLayoutParams(); params.gravity = Gravity.END; params.width = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 478, d); params.setMarginStart(0); params.setMarginEnd(0); contentSlider.setLayoutParams(params); } }); } catch (Resources.NotFoundException e){ ENABLE_HEADS_UP = false; } replaceDimen(res, res2, R.dimen.system_bar_icon_drawing_size, "status_bar_icon_drawing_size", false); replaceDimen(res, res2, R.dimen.navbar_search_panel_height, "navbar_search_panel_height", true); replaceDimen(res, res2, R.dimen.navbar_search_outerring_diameter, "navbar_search_outerring_diameter", true); replaceDimen(res, res2, R.dimen.navbar_search_outerring_radius, "navbar_search_outerring_radius", true); res.hookLayout(TabletKatModule.SYSTEMUI_PACKAGE, "layout", "status_bar_search_panel", new XC_LayoutInflated() { @Override public void handleLayoutInflated(LayoutInflatedParam param) throws Throwable { if (!mIsTv){ return; } ViewGroup v = (ViewGroup) param.view; DisplayMetrics dm = v.getContext().getResources().getDisplayMetrics(); int margin = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, -150, dm); int glow_pad_view = param.res.getIdentifier("glow_pad_view", "id", TabletKatModule.SYSTEMUI_PACKAGE); View glowpad = v.findViewById(glow_pad_view); FrameLayout.LayoutParams params = (FrameLayout.LayoutParams) glowpad.getLayoutParams(); params.setMarginStart(margin); params.gravity = Gravity.START | Gravity.BOTTOM; glowpad.setLayoutParams(params); XposedHelpers.setIntField(glowpad, "mGravity", Gravity.TOP | Gravity.END); } }); } private class NotificationIconTouchListener implements View.OnTouchListener { VelocityTracker mVT; int mPeekIndex; float mInitialTouchX, mInitialTouchY; int mTouchSlop; public NotificationIconTouchListener() { mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop(); } public boolean onTouch(View v, MotionEvent event) { boolean peeking = mNotificationPeekWindow.getVisibility() != View.GONE; boolean panelShowing = mNotificationPanel.isShowing(); if (panelShowing) return false; int numIcons = mIconLayout.getChildCount(); int newPeekIndex = (int)(event.getX() * numIcons / mIconLayout.getWidth()); if (newPeekIndex > numIcons - 1) newPeekIndex = numIcons - 1; else if (newPeekIndex < 0) newPeekIndex = 0; final int action = event.getAction(); switch (action) { case MotionEvent.ACTION_DOWN: mVT = VelocityTracker.obtain(); mInitialTouchX = event.getX(); mInitialTouchY = event.getY(); mPeekIndex = -1; // fall through case MotionEvent.ACTION_OUTSIDE: case MotionEvent.ACTION_MOVE: // peek and switch icons if necessary if (newPeekIndex != mPeekIndex) { mPeekIndex = newPeekIndex; if (DEBUG) Log.d(TAG, "will peek at notification #" + mPeekIndex); Message peekMsg = mHandler.obtainMessage(MSG_OPEN_NOTIFICATION_PEEK); peekMsg.arg1 = mPeekIndex; mHandler.removeMessages(MSG_OPEN_NOTIFICATION_PEEK); if (peeking) { // no delay if we're scrubbing left-right mHandler.sendMessage(peekMsg); } else { // wait for fling mHandler.sendMessageDelayed(peekMsg, NOTIFICATION_PEEK_HOLD_THRESH); } } // check for fling if (mVT != null) { mVT.addMovement(event); mVT.computeCurrentVelocity(1000); // pixels per second // require a little more oomph once we're already in peekaboo mode if (!panelShowing && ( (peeking && mVT.getYVelocity() < -mNotificationFlingVelocity*3) || (mVT.getYVelocity() < -mNotificationFlingVelocity))) { mHandler.removeMessages(MSG_OPEN_NOTIFICATION_PEEK); mHandler.removeMessages(MSG_OPEN_NOTIFICATION_PANEL); mHandler.sendEmptyMessage(MSG_CLOSE_NOTIFICATION_PEEK); mHandler.sendEmptyMessage(MSG_OPEN_NOTIFICATION_PANEL); } } return true; case MotionEvent.ACTION_UP: case MotionEvent.ACTION_CANCEL: mHandler.removeMessages(MSG_OPEN_NOTIFICATION_PEEK); if (!peeking) { if (action == MotionEvent.ACTION_UP // was this a sloppy tap? && Math.abs(event.getX() - mInitialTouchX) < mTouchSlop && Math.abs(event.getY() - mInitialTouchY) < (mTouchSlop / 3) // dragging off the bottom doesn't count && (int)event.getY() < v.getBottom()) { Message peekMsg = mHandler.obtainMessage(MSG_OPEN_NOTIFICATION_PEEK); peekMsg.arg1 = mPeekIndex; mHandler.removeMessages(MSG_OPEN_NOTIFICATION_PEEK); mHandler.sendMessage(peekMsg); v.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED); v.playSoundEffect(SoundEffectConstants.CLICK); peeking = true; // not technically true yet, but the next line will run } } if (peeking) { resetNotificationPeekFadeTimer(); } mVT.recycle(); mVT = null; return true; } return false; } } }