/* * Copyright (C) 2014 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package com.android.systemui.statusbar.phone; import android.app.ActivityManager; import android.app.AlarmManager; import android.app.PendingIntent; import android.content.ContentUris; import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.res.Configuration; import android.content.res.Resources; import android.database.ContentObserver; import android.graphics.Outline; import android.graphics.Rect; import android.graphics.drawable.Animatable; import android.graphics.drawable.Drawable; import android.graphics.drawable.RippleDrawable; import android.os.Handler; import android.os.IRemoteCallback; import android.os.RemoteException; import android.os.UserHandle; import android.provider.AlarmClock; import android.provider.CalendarContract; import android.provider.Settings; import android.net.Uri; import android.util.AttributeSet; import android.util.Log; import android.util.MathUtils; import android.util.TypedValue; import android.view.View; import android.view.ViewGroup; import android.view.ViewOutlineProvider; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.RelativeLayout; import android.widget.Switch; import android.widget.TextView; import android.widget.Toast; import com.android.keyguard.KeyguardStatusView; import com.android.systemui.BatteryLevelTextView; import com.android.systemui.BatteryMeterView; import com.android.systemui.DockBatteryMeterView; import com.android.systemui.FontSizeUtils; import com.android.systemui.R; import com.android.systemui.cm.UserContentObserver; import com.android.systemui.qs.QSDragPanel; import com.android.systemui.qs.QSPanel; import com.android.systemui.qs.QSTile; import com.android.systemui.statusbar.policy.BatteryController; import com.android.systemui.statusbar.policy.DockBatteryController; import com.android.systemui.statusbar.policy.NetworkControllerImpl.EmergencyListener; import com.android.systemui.statusbar.policy.NextAlarmController; import com.android.systemui.statusbar.policy.UserInfoController; import com.android.systemui.statusbar.policy.WeatherController; import com.android.systemui.statusbar.policy.WeatherControllerImpl; import com.android.systemui.tuner.TunerService; import java.text.NumberFormat; import cyanogenmod.app.StatusBarPanelCustomTile; import cyanogenmod.providers.CMSettings; import cyanogenmod.weather.util.WeatherUtils; import org.cyanogenmod.internal.logging.CMMetricsLogger; /** * The view to manage the header area in the expanded status bar. */ public class StatusBarHeaderView extends RelativeLayout implements View.OnClickListener, NextAlarmController.NextAlarmChangeCallback, WeatherController.Callback, EmergencyListener { private boolean mExpanded; private boolean mListening; private ViewGroup mSystemIconsContainer; private ViewGroup mWeatherContainer; private View mSystemIconsSuperContainer; private View mDateGroup; private View mClock; private TextView mTime; private TextView mAmPm; private MultiUserSwitch mMultiUserSwitch; private ImageView mMultiUserAvatar; private TextView mDateCollapsed; private TextView mDateExpanded; private LinearLayout mSystemIcons; private View mSignalCluster; private SettingsButton mSettingsButton; private View mSettingsContainer; private View mQsDetailHeader; private TextView mQsDetailHeaderTitle; private Switch mQsDetailHeaderSwitch; private ImageView mQsDetailHeaderProgress; private TextView mEmergencyCallsOnly; private BatteryLevelTextView mBatteryLevel; private BatteryLevelTextView mDockBatteryLevel; private TextView mAlarmStatus; private TextView mWeatherLine1, mWeatherLine2; private TextView mEditTileDoneText; private boolean mShowEmergencyCallsOnly; private boolean mAlarmShowing; private AlarmManager.AlarmClockInfo mNextAlarm; private int mCollapsedHeight; private int mExpandedHeight; private int mMultiUserExpandedMargin; private int mMultiUserCollapsedMargin; private int mClockMarginBottomExpanded; private int mClockMarginBottomCollapsed; private int mMultiUserSwitchWidthCollapsed; private int mMultiUserSwitchWidthExpanded; private int mClockCollapsedSize; private int mClockExpandedSize; /** * In collapsed QS, the clock and avatar are scaled down a bit post-layout to allow for a nice * transition. These values determine that factor. */ private float mClockCollapsedScaleFactor; private float mAvatarCollapsedScaleFactor; private ActivityStarter mActivityStarter; private NextAlarmController mNextAlarmController; private WeatherController mWeatherController; private QSDragPanel mQSPanel; private final Rect mClipBounds = new Rect(); private boolean mCaptureValues; private boolean mSignalClusterDetached; private final LayoutValues mCollapsedValues = new LayoutValues(); private final LayoutValues mExpandedValues = new LayoutValues(); private final LayoutValues mCurrentValues = new LayoutValues(); private float mCurrentT; private boolean mShowingDetail; private boolean mDetailTransitioning; private SettingsObserver mSettingsObserver; private boolean mShowWeather; private boolean mShowBatteryTextExpanded; private QSTile.DetailAdapter mEditingDetailAdapter; private boolean mEditing; private UserInfoController mUserInfoController; public StatusBarHeaderView(Context context, AttributeSet attrs) { super(context, attrs); } @Override protected void onFinishInflate() { super.onFinishInflate(); mSystemIconsSuperContainer = findViewById(R.id.system_icons_super_container); mSystemIconsContainer = (ViewGroup) findViewById(R.id.system_icons_container); mSystemIconsSuperContainer.setOnClickListener(this); mDateGroup = findViewById(R.id.date_group); mDateGroup.setOnClickListener(this); mClock = findViewById(R.id.clock); mClock.setOnClickListener(this); mTime = (TextView) findViewById(R.id.time_view); mAmPm = (TextView) findViewById(R.id.am_pm_view); mMultiUserSwitch = (MultiUserSwitch) findViewById(R.id.multi_user_switch); mMultiUserAvatar = (ImageView) findViewById(R.id.multi_user_avatar); mDateCollapsed = (TextView) findViewById(R.id.date_collapsed); mDateExpanded = (TextView) findViewById(R.id.date_expanded); mSettingsButton = (SettingsButton) findViewById(R.id.settings_button); mSettingsContainer = findViewById(R.id.settings_button_container); mSettingsButton.setOnClickListener(this); mQsDetailHeader = findViewById(R.id.qs_detail_header); mQsDetailHeader.setAlpha(0); mQsDetailHeaderTitle = (TextView) mQsDetailHeader.findViewById(android.R.id.title); mQsDetailHeaderSwitch = (Switch) mQsDetailHeader.findViewById(android.R.id.toggle); mQsDetailHeaderProgress = (ImageView) findViewById(R.id.qs_detail_header_progress); mEmergencyCallsOnly = (TextView) findViewById(R.id.header_emergency_calls_only); mBatteryLevel = (BatteryLevelTextView) findViewById(R.id.battery_level_text); mDockBatteryLevel = (BatteryLevelTextView) findViewById(R.id.dock_battery_level_text); mAlarmStatus = (TextView) findViewById(R.id.alarm_status); mAlarmStatus.setOnClickListener(this); mSignalCluster = findViewById(R.id.signal_cluster); mSystemIcons = (LinearLayout) findViewById(R.id.system_icons); mWeatherContainer = (LinearLayout) findViewById(R.id.weather_container); mWeatherContainer.setOnClickListener(this); mWeatherLine1 = (TextView) findViewById(R.id.weather_line_1); mWeatherLine2 = (TextView) findViewById(R.id.weather_line_2); mEditTileDoneText = (TextView) findViewById(R.id.done); mSettingsObserver = new SettingsObserver(new Handler()); loadDimens(); updateVisibilities(); updateClockScale(); updateAvatarScale(); addOnLayoutChangeListener(new View.OnLayoutChangeListener() { @Override public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) { if ((right - left) != (oldRight - oldLeft)) { // width changed, update clipping setClipping(getHeight()); } boolean rtl = getLayoutDirection() == LAYOUT_DIRECTION_RTL; mTime.setPivotX(rtl ? mTime.getWidth() : 0); mTime.setPivotY(mTime.getBaseline()); updateAmPmTranslation(); } }); setOutlineProvider(new ViewOutlineProvider() { @Override public void getOutline(View view, Outline outline) { outline.setRect(mClipBounds); } }); requestCaptureValues(); // RenderThread is doing more harm than good when touching the header (to expand quick // settings), so disable it for this view Drawable d = getBackground(); if (d instanceof RippleDrawable) { ((RippleDrawable) d).setForceSoftware(true); } d = mSettingsButton.getBackground(); if (d instanceof RippleDrawable) { ((RippleDrawable) d).setForceSoftware(true); } d = mSystemIconsSuperContainer.getBackground(); if (d instanceof RippleDrawable) { ((RippleDrawable) d).setForceSoftware(true); } } @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { super.onLayout(changed, l, t, r, b); if (mCaptureValues) { if (mExpanded) { captureLayoutValues(mExpandedValues); } else { captureLayoutValues(mCollapsedValues); } mCaptureValues = false; updateLayoutValues(mCurrentT); } mAlarmStatus.setX(mDateGroup.getLeft() + mDateCollapsed.getRight()); } @Override protected void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); FontSizeUtils.updateFontSize(mEmergencyCallsOnly, R.dimen.qs_emergency_calls_only_text_size); FontSizeUtils.updateFontSize(mDateCollapsed, R.dimen.qs_date_collapsed_size); FontSizeUtils.updateFontSize(mDateExpanded, R.dimen.qs_date_collapsed_size); FontSizeUtils.updateFontSize(mAlarmStatus, R.dimen.qs_date_collapsed_size); FontSizeUtils.updateFontSize(this, android.R.id.title, R.dimen.qs_detail_header_text_size); FontSizeUtils.updateFontSize(this, android.R.id.toggle, R.dimen.qs_detail_header_text_size); FontSizeUtils.updateFontSize(mAmPm, R.dimen.qs_time_collapsed_size); FontSizeUtils.updateFontSize(this, R.id.empty_time_view, R.dimen.qs_time_expanded_size); mEmergencyCallsOnly.setText(com.android.internal.R.string.emergency_calls_only); mClockCollapsedSize = getResources().getDimensionPixelSize(R.dimen.qs_time_collapsed_size); mClockExpandedSize = getResources().getDimensionPixelSize(R.dimen.qs_time_expanded_size); mClockCollapsedScaleFactor = (float) mClockCollapsedSize / (float) mClockExpandedSize; if (mEditTileDoneText != null) { mEditTileDoneText.setText(R.string.quick_settings_done); } updateClockScale(); updateClockCollapsedMargin(); } @Override protected void onDetachedFromWindow() { super.onDetachedFromWindow(); if (mUserInfoController != null) { mUserInfoController.removeListener(mUserInfoChangedListener); } setListening(false); } private void updateClockCollapsedMargin() { Resources res = getResources(); int padding = res.getDimensionPixelSize(R.dimen.clock_collapsed_bottom_margin); int largePadding = res.getDimensionPixelSize( R.dimen.clock_collapsed_bottom_margin_large_text); float largeFactor = (MathUtils.constrain(getResources().getConfiguration().fontScale, 1.0f, FontSizeUtils.LARGE_TEXT_SCALE) - 1f) / (FontSizeUtils.LARGE_TEXT_SCALE - 1f); mClockMarginBottomCollapsed = Math.round((1 - largeFactor) * padding + largeFactor * largePadding); requestLayout(); } private void requestCaptureValues() { mCaptureValues = true; requestLayout(); } private void loadDimens() { mCollapsedHeight = getResources().getDimensionPixelSize(R.dimen.status_bar_header_height); mExpandedHeight = getResources().getDimensionPixelSize( R.dimen.status_bar_header_height_expanded); mMultiUserExpandedMargin = getResources().getDimensionPixelSize(R.dimen.multi_user_switch_expanded_margin); mMultiUserCollapsedMargin = getResources().getDimensionPixelSize(R.dimen.multi_user_switch_collapsed_margin); mClockMarginBottomExpanded = getResources().getDimensionPixelSize(R.dimen.clock_expanded_bottom_margin); updateClockCollapsedMargin(); mMultiUserSwitchWidthCollapsed = getResources().getDimensionPixelSize(R.dimen.multi_user_switch_width_collapsed); mMultiUserSwitchWidthExpanded = getResources().getDimensionPixelSize(R.dimen.multi_user_switch_width_expanded); mAvatarCollapsedScaleFactor = getResources().getDimensionPixelSize(R.dimen.multi_user_avatar_collapsed_size) / (float) mMultiUserAvatar.getLayoutParams().width; mClockCollapsedSize = getResources().getDimensionPixelSize(R.dimen.qs_time_collapsed_size); mClockExpandedSize = getResources().getDimensionPixelSize(R.dimen.qs_time_expanded_size); mClockCollapsedScaleFactor = (float) mClockCollapsedSize / (float) mClockExpandedSize; } public void setActivityStarter(ActivityStarter activityStarter) { mActivityStarter = activityStarter; if (mMultiUserSwitch != null) { mMultiUserSwitch.setActivityStarter(activityStarter); } } public void setBatteryController(BatteryController batteryController) { BatteryMeterView v = ((BatteryMeterView) findViewById(R.id.battery)); v.setBatteryStateRegistar(batteryController); v.setBatteryController(batteryController); mBatteryLevel.setBatteryStateRegistar(batteryController); } public void setDockBatteryController(DockBatteryController dockBatteryController) { DockBatteryMeterView v = ((DockBatteryMeterView) findViewById(R.id.dock_battery)); if (dockBatteryController != null) { v.setBatteryStateRegistar(dockBatteryController); mDockBatteryLevel.setBatteryStateRegistar(dockBatteryController); } else { if (v != null) { removeView(v); } if (mDockBatteryLevel != null) { removeView(mDockBatteryLevel); mDockBatteryLevel = null; } } } public void setNextAlarmController(NextAlarmController nextAlarmController) { mNextAlarmController = nextAlarmController; } public void setWeatherController(WeatherController weatherController) { mWeatherController = weatherController; } public int getCollapsedHeight() { return mCollapsedHeight; } public int getExpandedHeight() { return mExpandedHeight; } public void setListening(boolean listening) { if (listening == mListening) { return; } mListening = listening; updateListeners(); } public void setExpanded(boolean expanded) { boolean changed = expanded != mExpanded; mExpanded = expanded; if (changed) { if (mShowingDetail && !expanded) { mQsPanelCallback.onShowingDetail(null); } updateEverything(); } } public void updateEverything() { updateHeights(); updateVisibilities(); updateSystemIconsLayoutParams(); updateClickTargets(); updateMultiUserSwitch(); updateClockScale(); updateAvatarScale(); updateClockLp(); requestCaptureValues(); } private void updateHeights() { int height = mExpanded ? mExpandedHeight : mCollapsedHeight; ViewGroup.LayoutParams lp = getLayoutParams(); if (lp.height != height) { lp.height = height; setLayoutParams(lp); } } private void updateVisibilities() { mDateCollapsed.setVisibility(mExpanded && mAlarmShowing ? View.VISIBLE : View.INVISIBLE); mDateExpanded.setVisibility(mExpanded && mAlarmShowing ? View.INVISIBLE : View.VISIBLE); mAlarmStatus.setVisibility(mExpanded && mAlarmShowing ? View.VISIBLE : View.INVISIBLE); mSettingsContainer.setVisibility(mExpanded ? View.VISIBLE : View.INVISIBLE); mWeatherContainer.setVisibility(mExpanded && mShowWeather ? View.VISIBLE : View.GONE); mQsDetailHeader.setVisibility(mExpanded && mShowingDetail ? View.VISIBLE : View.INVISIBLE); if (mSignalCluster != null) { updateSignalClusterDetachment(); } mEmergencyCallsOnly.setVisibility(mExpanded && mShowEmergencyCallsOnly ? VISIBLE : GONE); mBatteryLevel.setForceShown(mExpanded && mShowBatteryTextExpanded); mBatteryLevel.setVisibility(View.VISIBLE); if (mDockBatteryLevel != null) { mDockBatteryLevel.setForceShown(mExpanded && mShowBatteryTextExpanded); mDockBatteryLevel.setVisibility(View.VISIBLE); } } private void updateSignalClusterDetachment() { boolean detached = mExpanded; if (detached != mSignalClusterDetached) { if (detached) { getOverlay().add(mSignalCluster); } else { reattachSignalCluster(); } } mSignalClusterDetached = detached; } private void reattachSignalCluster() { getOverlay().remove(mSignalCluster); mSystemIcons.addView(mSignalCluster, 1); } private void updateSystemIconsLayoutParams() { RelativeLayout.LayoutParams lp = (LayoutParams) mSystemIconsSuperContainer.getLayoutParams(); int rule = mExpanded ? mSettingsContainer.getId() : mMultiUserSwitch.getId(); if (rule != lp.getRules()[RelativeLayout.START_OF]) { lp.addRule(RelativeLayout.START_OF, rule); mSystemIconsSuperContainer.setLayoutParams(lp); } } private void updateListeners() { if (mListening) { mSettingsObserver.observe(); mNextAlarmController.addStateChangedCallback(this); mWeatherController.addCallback(this); } else { mNextAlarmController.removeStateChangedCallback(this); mWeatherController.removeCallback(this); mSettingsObserver.unobserve(); } } private void updateAvatarScale() { if (mExpanded) { mMultiUserAvatar.setScaleX(1f); mMultiUserAvatar.setScaleY(1f); } else { mMultiUserAvatar.setScaleX(mAvatarCollapsedScaleFactor); mMultiUserAvatar.setScaleY(mAvatarCollapsedScaleFactor); } } private void updateClockScale() { mTime.setTextSize(TypedValue.COMPLEX_UNIT_PX, mExpanded ? mClockExpandedSize : mClockCollapsedSize); mTime.setScaleX(1f); mTime.setScaleY(1f); updateAmPmTranslation(); } private void updateAmPmTranslation() { boolean rtl = getLayoutDirection() == LAYOUT_DIRECTION_RTL; mAmPm.setTranslationX((rtl ? 1 : -1) * mTime.getWidth() * (1 - mTime.getScaleX())); } @Override public void onNextAlarmChanged(AlarmManager.AlarmClockInfo nextAlarm) { mNextAlarm = nextAlarm; if (nextAlarm != null) { mAlarmStatus.setText(KeyguardStatusView.formatNextAlarm(getContext(), nextAlarm)); } mAlarmShowing = nextAlarm != null; updateEverything(); requestCaptureValues(); } @Override public void onWeatherChanged(WeatherController.WeatherInfo info) { if (Double.isNaN(info.temp) || info.condition == null) { mWeatherLine1.setText(null); } else { mWeatherLine1.setText(mContext.getString( R.string.status_bar_expanded_header_weather_format, WeatherUtils.formatTemperature(info.temp, info.tempUnit), info.condition)); } mWeatherLine2.setText(info.city); } private void updateClickTargets() { mMultiUserSwitch.setClickable(mExpanded); mMultiUserSwitch.setFocusable(mExpanded); mSystemIconsSuperContainer.setClickable(mExpanded); mSystemIconsSuperContainer.setFocusable(mExpanded); mAlarmStatus.setClickable(mNextAlarm != null && mNextAlarm.getShowIntent() != null); } private void updateClockLp() { int marginBottom = mExpanded ? mClockMarginBottomExpanded : mClockMarginBottomCollapsed; LayoutParams lp = (LayoutParams) mDateGroup.getLayoutParams(); if (marginBottom != lp.bottomMargin) { lp.bottomMargin = marginBottom; mDateGroup.setLayoutParams(lp); } } private void updateMultiUserSwitch() { int marginEnd; int width; if (mExpanded) { marginEnd = mMultiUserExpandedMargin; width = mMultiUserSwitchWidthExpanded; } else { marginEnd = mMultiUserCollapsedMargin; width = mMultiUserSwitchWidthCollapsed; } MarginLayoutParams lp = (MarginLayoutParams) mMultiUserSwitch.getLayoutParams(); if (marginEnd != lp.getMarginEnd() || lp.width != width) { lp.setMarginEnd(marginEnd); lp.width = width; mMultiUserSwitch.setLayoutParams(lp); } } public void setExpansion(float t) { if (!mExpanded) { t = 0f; } mCurrentT = t; float height = mCollapsedHeight + t * (mExpandedHeight - mCollapsedHeight); if (height < mCollapsedHeight) { height = mCollapsedHeight; } if (height > mExpandedHeight) { height = mExpandedHeight; } setClipping(height); updateLayoutValues(t); } private void updateLayoutValues(float t) { if (mCaptureValues) { return; } mCurrentValues.interpoloate(mCollapsedValues, mExpandedValues, t); applyLayoutValues(mCurrentValues); } private void setClipping(float height) { mClipBounds.set(getPaddingLeft(), 0, getWidth() - getPaddingRight(), (int) height); setClipBounds(mClipBounds); invalidateOutline(); } private UserInfoController.OnUserInfoChangedListener mUserInfoChangedListener = new UserInfoController.OnUserInfoChangedListener() { @Override public void onUserInfoChanged(String name, Drawable picture) { mMultiUserAvatar.setImageDrawable(picture); } }; public void setUserInfoController(UserInfoController userInfoController) { mUserInfoController = userInfoController; userInfoController.addListener(mUserInfoChangedListener); if (mMultiUserSwitch != null) { mMultiUserSwitch.setUserInfoController(mUserInfoController); } } @Override public void onClick(View v) { if (v == mSettingsButton) { if (mSettingsButton.isTunerClick()) { mSettingsButton.consumeClick(); mQSPanel.getHost().setEditing(!mQSPanel.getHost().isEditing()); } else { startSettingsActivity(); } } else if (v == mSystemIconsSuperContainer) { startBatteryActivity(); } else if (v == mAlarmStatus && mNextAlarm != null) { PendingIntent showIntent = mNextAlarm.getShowIntent(); if (showIntent != null) { mActivityStarter.startPendingIntentDismissingKeyguard(showIntent); } } else if (v == mClock) { startClockActivity(); } else if (v == mDateGroup) { startDateActivity(); } else if (v == mWeatherContainer) { startForecastActivity(); } } private void startSettingsActivity() { mActivityStarter.startActivity(new Intent(android.provider.Settings.ACTION_SETTINGS), true /* dismissShade */); } private void startBatteryActivity() { mActivityStarter.startActivity(new Intent(Intent.ACTION_POWER_USAGE_SUMMARY), true /* dismissShade */); } private void startClockActivity() { mActivityStarter.startActivity(new Intent(AlarmClock.ACTION_SHOW_ALARMS), true /* dismissShade */); } private void startDateActivity() { Uri.Builder builder = CalendarContract.CONTENT_URI.buildUpon(); builder.appendPath("time"); ContentUris.appendId(builder, System.currentTimeMillis()); Intent intent = new Intent(Intent.ACTION_VIEW).setData(builder.build()); mActivityStarter.startActivity(intent, true /* dismissShade */); } private void startForecastActivity() { Intent intent = new Intent(Intent.ACTION_MAIN); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); intent.setComponent(WeatherControllerImpl.COMPONENT_WEATHER_FORECAST); mActivityStarter.startActivity(intent, true /* dismissShade */); } public void setQSPanel(QSDragPanel qsp) { mQSPanel = qsp; if (mQSPanel != null) { mQSPanel.setCallback(mQsPanelCallback); } mMultiUserSwitch.setQsPanel(qsp); } @Override public boolean shouldDelayChildPressedState() { return true; } @Override public void setEmergencyCallsOnly(boolean show) { boolean changed = show != mShowEmergencyCallsOnly; if (changed) { mShowEmergencyCallsOnly = show; if (mExpanded) { updateEverything(); requestCaptureValues(); } } } @Override protected void dispatchSetPressed(boolean pressed) { // We don't want that everything lights up when we click on the header, so block the request // here. } private void captureLayoutValues(LayoutValues target) { target.timeScale = mExpanded ? 1f : mClockCollapsedScaleFactor; target.clockY = mClock.getBottom(); target.dateY = mDateGroup.getTop(); target.emergencyCallsOnlyAlpha = getAlphaForVisibility(mEmergencyCallsOnly); target.alarmStatusAlpha = getAlphaForVisibility(mAlarmStatus); target.dateCollapsedAlpha = getAlphaForVisibility(mDateCollapsed); target.dateExpandedAlpha = getAlphaForVisibility(mDateExpanded); target.avatarScale = mMultiUserAvatar.getScaleX(); target.avatarX = mMultiUserSwitch.getLeft() + mMultiUserAvatar.getLeft(); target.avatarY = mMultiUserSwitch.getTop() + mMultiUserAvatar.getTop(); target.weatherY = mClock.getBottom() - mWeatherLine1.getHeight(); if (getLayoutDirection() == LAYOUT_DIRECTION_LTR) { target.batteryX = mSystemIconsSuperContainer.getLeft() + mSystemIconsContainer.getRight(); } else { target.batteryX = mSystemIconsSuperContainer.getLeft() + mSystemIconsContainer.getLeft(); } target.batteryY = mSystemIconsSuperContainer.getTop() + mSystemIconsContainer.getTop(); target.batteryLevelAlpha = getAlphaForVisibility(mBatteryLevel); target.settingsAlpha = getAlphaForVisibility(mSettingsContainer); target.settingsTranslation = mExpanded ? 0 : mMultiUserSwitch.getLeft() - mSettingsContainer.getLeft(); target.signalClusterAlpha = mSignalClusterDetached ? 0f : 1f; target.settingsRotation = !mExpanded ? 90f : 0f; } private float getAlphaForVisibility(View v) { return v == null || v.getVisibility() == View.VISIBLE ? 1f : 0f; } private void applyAlpha(View v, float alpha) { if (v == null || v.getVisibility() == View.GONE) { return; } if (alpha == 0f) { v.setVisibility(View.INVISIBLE); } else { v.setVisibility(View.VISIBLE); v.setAlpha(alpha); } } private void applyLayoutValues(LayoutValues values) { mTime.setScaleX(values.timeScale); mTime.setScaleY(values.timeScale); mClock.setY(values.clockY - mClock.getHeight()); mDateGroup.setY(values.dateY); mWeatherContainer.setY(values.weatherY); mAlarmStatus.setY(values.dateY - mAlarmStatus.getPaddingTop()); mMultiUserAvatar.setScaleX(values.avatarScale); mMultiUserAvatar.setScaleY(values.avatarScale); mMultiUserAvatar.setX(values.avatarX - mMultiUserSwitch.getLeft()); mMultiUserAvatar.setY(values.avatarY - mMultiUserSwitch.getTop()); if (getLayoutDirection() == LAYOUT_DIRECTION_LTR) { mSystemIconsSuperContainer.setX(values.batteryX - mSystemIconsContainer.getRight()); } else { mSystemIconsSuperContainer.setX(values.batteryX - mSystemIconsContainer.getLeft()); } mSystemIconsSuperContainer.setY(values.batteryY - mSystemIconsContainer.getTop()); if (mSignalCluster != null && mExpanded) { if (getLayoutDirection() == LAYOUT_DIRECTION_LTR) { mSignalCluster.setX(mSystemIconsSuperContainer.getX() - mSignalCluster.getWidth()); } else { mSignalCluster.setX(mSystemIconsSuperContainer.getX() + mSystemIconsSuperContainer.getWidth()); } mSignalCluster.setY( mSystemIconsSuperContainer.getY() + mSystemIconsSuperContainer.getHeight()/2 - mSignalCluster.getHeight()/2); } else if (mSignalCluster != null) { mSignalCluster.setTranslationX(0f); mSignalCluster.setTranslationY(0f); } if (!mSettingsButton.isAnimating()) { mSettingsContainer.setTranslationY(mSystemIconsSuperContainer.getTranslationY()); mSettingsContainer.setTranslationX(values.settingsTranslation); mSettingsButton.setRotation(values.settingsRotation); } applyAlpha(mEmergencyCallsOnly, values.emergencyCallsOnlyAlpha); if (!mShowingDetail && !mDetailTransitioning) { // Otherwise it needs to stay invisible applyAlpha(mAlarmStatus, values.alarmStatusAlpha); } applyAlpha(mDateCollapsed, values.dateCollapsedAlpha); applyAlpha(mDateExpanded, values.dateExpandedAlpha); applyAlpha(mBatteryLevel, values.batteryLevelAlpha); if (mDockBatteryLevel != null) { applyAlpha(mDockBatteryLevel, values.batteryLevelAlpha); } applyAlpha(mSettingsContainer, values.settingsAlpha); applyAlpha(mWeatherLine1, values.settingsAlpha); applyAlpha(mWeatherLine2, values.settingsAlpha); applyAlpha(mSignalCluster, values.signalClusterAlpha); if (!mExpanded) { mTime.setScaleX(1f); mTime.setScaleY(1f); } updateAmPmTranslation(); } public void setEditing(boolean editing) { mEditing = editing; if (editing && mEditingDetailAdapter == null) { mEditingDetailAdapter = new QSTile.DetailAdapter() { @Override public int getTitle() { return R.string.quick_settings_edit_label; } @Override public Boolean getToggleState() { return null; } @Override public View createDetailView(Context context, View convertView, ViewGroup parent) { return null; } @Override public Intent getSettingsIntent() { return null; } @Override public StatusBarPanelCustomTile getCustomTile() { return null; } @Override public void setToggleState(boolean state) { } @Override public int getMetricsCategory() { return CMMetricsLogger.DONT_LOG; } }; } mQsPanelCallback.onShowingDetail(mEditing ? mEditingDetailAdapter : null); updateEverything(); } /** * Captures all layout values (position, visibility) for a certain state. This is used for * animations. */ private static final class LayoutValues { float dateExpandedAlpha; float dateCollapsedAlpha; float emergencyCallsOnlyAlpha; float alarmStatusAlpha; float timeScale = 1f; float clockY; float dateY; float avatarScale; float avatarX; float avatarY; float batteryX; float batteryY; float batteryLevelAlpha; float batteryLevelExpandedAlpha; float settingsAlpha; float settingsTranslation; float signalClusterAlpha; float settingsRotation; float weatherY; public void interpoloate(LayoutValues v1, LayoutValues v2, float t) { timeScale = v1.timeScale * (1 - t) + v2.timeScale * t; clockY = v1.clockY * (1 - t) + v2.clockY * t; dateY = v1.dateY * (1 - t) + v2.dateY * t; avatarScale = v1.avatarScale * (1 - t) + v2.avatarScale * t; avatarX = v1.avatarX * (1 - t) + v2.avatarX * t; avatarY = v1.avatarY * (1 - t) + v2.avatarY * t; batteryX = v1.batteryX * (1 - t) + v2.batteryX * t; batteryY = v1.batteryY * (1 - t) + v2.batteryY * t; settingsTranslation = v1.settingsTranslation * (1 - t) + v2.settingsTranslation * t; weatherY = v1.weatherY * (1 - t) + v2.weatherY * t; float t1 = Math.max(0, t - 0.5f) * 2; settingsRotation = v1.settingsRotation * (1 - t1) + v2.settingsRotation * t1; emergencyCallsOnlyAlpha = v1.emergencyCallsOnlyAlpha * (1 - t1) + v2.emergencyCallsOnlyAlpha * t1; float t2 = Math.min(1, 2 * t); signalClusterAlpha = v1.signalClusterAlpha * (1 - t2) + v2.signalClusterAlpha * t2; float t3 = Math.max(0, t - 0.7f) / 0.3f; batteryLevelAlpha = v1.batteryLevelAlpha * (1 - t3) + v2.batteryLevelAlpha * t3; settingsAlpha = v1.settingsAlpha * (1 - t3) + v2.settingsAlpha * t3; dateExpandedAlpha = v1.dateExpandedAlpha * (1 - t3) + v2.dateExpandedAlpha * t3; dateCollapsedAlpha = v1.dateCollapsedAlpha * (1 - t3) + v2.dateCollapsedAlpha * t3; alarmStatusAlpha = v1.alarmStatusAlpha * (1 - t3) + v2.alarmStatusAlpha * t3; batteryLevelExpandedAlpha = v1.batteryLevelExpandedAlpha * (1 - t3) + v2.batteryLevelExpandedAlpha * t3; } } private final QSPanel.Callback mQsPanelCallback = new QSPanel.Callback() { private boolean mScanState; @Override public void onToggleStateChanged(final boolean state) { post(new Runnable() { @Override public void run() { handleToggleStateChanged(state); } }); } @Override public void onShowingDetail(final QSTile.DetailAdapter detail) { mDetailTransitioning = true; post(new Runnable() { @Override public void run() { handleShowingDetail(mEditing && detail == null ? mEditingDetailAdapter : detail); } }); } @Override public void onScanStateChanged(final boolean state) { post(new Runnable() { @Override public void run() { handleScanStateChanged(state); } }); } private void handleToggleStateChanged(boolean state) { mQsDetailHeaderSwitch.setChecked(state); } private void handleScanStateChanged(boolean state) { if (mScanState == state) return; mScanState = state; final Animatable anim = (Animatable) mQsDetailHeaderProgress.getDrawable(); if (state) { mQsDetailHeaderProgress.animate().alpha(1f); anim.start(); } else { mQsDetailHeaderProgress.animate().alpha(0f); anim.stop(); } } private void handleShowingDetail(final QSTile.DetailAdapter detail) { final boolean showingDetail = detail != null; transition(mClock, !showingDetail); transition(mDateGroup, !showingDetail); if (mShowWeather) { transition(mWeatherContainer, !showingDetail); } if (mAlarmShowing) { transition(mAlarmStatus, !showingDetail && !mDetailTransitioning); } transition(mQsDetailHeader, showingDetail); mShowingDetail = showingDetail; if (showingDetail) { mQsDetailHeaderTitle.setText(detail.getTitle()); final Boolean toggleState = detail.getToggleState(); if (detail.getTitle() == R.string.quick_settings_edit_label) { mEditTileDoneText.setVisibility(View.VISIBLE); mQsDetailHeaderSwitch.setVisibility(INVISIBLE); mQsDetailHeader.setClickable(true); mQsDetailHeader.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { mQSPanel.getHost().setEditing(false); } }); } else if (toggleState == null) { mQsDetailHeaderSwitch.setVisibility(INVISIBLE); mEditTileDoneText.setVisibility(View.GONE); mQsDetailHeader.setClickable(false); } else { mEditTileDoneText.setVisibility(View.GONE); mQsDetailHeaderSwitch.setVisibility(VISIBLE); mQsDetailHeaderSwitch.setChecked(toggleState); mQsDetailHeader.setClickable(true); mQsDetailHeader.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { boolean checked = !mQsDetailHeaderSwitch.isChecked(); mQsDetailHeaderSwitch.setChecked(checked); detail.setToggleState(checked); } }); } } else { mQsDetailHeader.setClickable(false); } } private void transition(final View v, final boolean in) { if (in) { v.bringToFront(); v.setVisibility(VISIBLE); } if (v.hasOverlappingRendering()) { v.animate().withLayer(); } v.animate() .alpha(in ? 1 : 0) .withEndAction(new Runnable() { @Override public void run() { if (!in) { v.setVisibility(INVISIBLE); } mDetailTransitioning = false; } }) .start(); } }; class SettingsObserver extends UserContentObserver { SettingsObserver(Handler handler) { super(handler); } @Override protected void observe() { super.observe(); ContentResolver resolver = mContext.getContentResolver(); resolver.registerContentObserver(CMSettings.System.getUriFor( CMSettings.System.STATUS_BAR_SHOW_WEATHER), false, this, UserHandle.USER_ALL); resolver.registerContentObserver(CMSettings.System.getUriFor( CMSettings.System.STATUS_BAR_BATTERY_STYLE), false, this, UserHandle.USER_ALL); resolver.registerContentObserver(CMSettings.System.getUriFor( CMSettings.System.STATUS_BAR_SHOW_BATTERY_PERCENT), false, this, UserHandle.USER_ALL); update(); } @Override protected void unobserve() { super.unobserve(); ContentResolver resolver = mContext.getContentResolver(); resolver.unregisterContentObserver(this); } @Override public void update() { ContentResolver resolver = mContext.getContentResolver(); int currentUserId = ActivityManager.getCurrentUser(); int batteryStyle = CMSettings.System.getIntForUser(resolver, CMSettings.System.STATUS_BAR_BATTERY_STYLE, 0, currentUserId); boolean showExpandedBatteryPercentage = CMSettings.System.getIntForUser(resolver, CMSettings.System.STATUS_BAR_SHOW_BATTERY_PERCENT, 0, currentUserId) == 0; switch (batteryStyle) { case 4: //BATTERY_METER_GONE case 6: //BATTERY_METER_TEXT showExpandedBatteryPercentage = false; break; default: break; } mShowBatteryTextExpanded = showExpandedBatteryPercentage; mShowWeather = CMSettings.System.getInt( resolver, CMSettings.System.STATUS_BAR_SHOW_WEATHER, 1) == 1; updateVisibilities(); requestCaptureValues(); } } }