diff options
Diffstat (limited to 'packages')
91 files changed, 2623 insertions, 1461 deletions
diff --git a/packages/Keyguard/res/layout/keyguard_emergency_carrier_area.xml b/packages/Keyguard/res/layout/keyguard_emergency_carrier_area.xml index b2d0219..78b5746 100644 --- a/packages/Keyguard/res/layout/keyguard_emergency_carrier_area.xml +++ b/packages/Keyguard/res/layout/keyguard_emergency_carrier_area.xml @@ -37,6 +37,7 @@ android:textAppearance="?android:attr/textAppearanceMedium" android:textSize="@dimen/kg_status_line_font_size" android:textColor="?android:attr/textColorSecondary" + android:visibility="gone" androidprv:allCaps="@bool/kg_use_all_caps" /> <LinearLayout diff --git a/packages/Keyguard/res/layout/keyguard_pin_view.xml b/packages/Keyguard/res/layout/keyguard_pin_view.xml index a804c8c..a8e330b 100644 --- a/packages/Keyguard/res/layout/keyguard_pin_view.xml +++ b/packages/Keyguard/res/layout/keyguard_pin_view.xml @@ -41,28 +41,16 @@ android:layout_weight="1" android:layoutDirection="ltr" > - <LinearLayout + <RelativeLayout + android:id="@+id/row0" android:layout_width="match_parent" android:layout_height="0dp" - android:orientation="horizontal" android:layout_weight="1" > - <TextView android:id="@+id/pinEntry" - android:editable="true" - android:layout_width="0dip" - android:layout_height="match_parent" - android:layout_weight="1" - android:gravity="center" - android:layout_marginStart="@dimen/keyguard_lockscreen_pin_margin_left" - android:singleLine="true" - android:cursorVisible="false" - android:background="@null" - android:textAppearance="@style/TextAppearance.NumPadKey" - android:imeOptions="flagForceAscii|actionDone" - /> - <ImageButton android:id="@+id/delete_button" + <ImageButton android:id="@+id/delete_button" android:layout_width="wrap_content" android:layout_height="match_parent" + android:layout_alignParentEnd="true" android:gravity="center_vertical" android:src="@drawable/ic_input_delete" android:clickable="true" @@ -73,13 +61,30 @@ android:background="?android:attr/selectableItemBackground" android:contentDescription="@string/keyboardview_keycode_delete" /> - </LinearLayout> - <View - android:layout_width="wrap_content" - android:layout_height="1dp" - android:background="#55FFFFFF" - /> + <TextView android:id="@+id/pinEntry" + android:editable="true" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:layout_toStartOf="@+id/delete_button" + android:layout_alignParentStart="true" + android:gravity="center" + android:layout_marginStart="@dimen/keyguard_lockscreen_pin_margin_left" + android:singleLine="true" + android:cursorVisible="false" + android:background="@null" + android:textAppearance="@style/TextAppearance.NumPadKey" + android:imeOptions="flagForceAscii|actionDone" + /> + <View + android:id="@+id/divider" + android:layout_width="match_parent" + android:layout_height="1dp" + android:layout_alignParentBottom="true" + android:background="#55FFFFFF" + /> + </RelativeLayout> <LinearLayout + android:id="@+id/row1" android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="1" @@ -114,6 +119,7 @@ /> </LinearLayout> <LinearLayout + android:id="@+id/row2" android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="1" @@ -148,6 +154,7 @@ /> </LinearLayout> <LinearLayout + android:id="@+id/row3" android:layout_width="match_parent" android:layout_height="0dp" android:orientation="horizontal" @@ -182,6 +189,7 @@ /> </LinearLayout> <LinearLayout + android:id="@+id/row4" android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="1" diff --git a/packages/Keyguard/res/values/dimens.xml b/packages/Keyguard/res/values/dimens.xml index 3830df7..01d9ab3 100644 --- a/packages/Keyguard/res/values/dimens.xml +++ b/packages/Keyguard/res/values/dimens.xml @@ -161,4 +161,6 @@ <dimen name="widget_big_font_size">68dp</dimen> <dimen name="big_font_size">120dp</dimen> + <!-- The y translation to apply at the start in appear animations. --> + <dimen name="appear_y_translation_start">24dp</dimen> </resources> diff --git a/packages/Keyguard/src/com/android/keyguard/AppearAnimationUtils.java b/packages/Keyguard/src/com/android/keyguard/AppearAnimationUtils.java new file mode 100644 index 0000000..ea896d5 --- /dev/null +++ b/packages/Keyguard/src/com/android/keyguard/AppearAnimationUtils.java @@ -0,0 +1,99 @@ +/* + * 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.keyguard; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.TimeInterpolator; +import android.content.Context; +import android.view.View; +import android.view.ViewPropertyAnimator; +import android.view.animation.AnimationUtils; +import android.view.animation.Interpolator; + +/** + * A class to make nice appear transitions for views in a tabular layout. + */ +public class AppearAnimationUtils { + + public static final long APPEAR_DURATION = 220; + + private final Interpolator mLinearOutSlowIn; + private final float mStartTranslation; + + public AppearAnimationUtils(Context ctx) { + mLinearOutSlowIn = AnimationUtils.loadInterpolator( + ctx, android.R.interpolator.linear_out_slow_in); + mStartTranslation = + ctx.getResources().getDimensionPixelOffset(R.dimen.appear_y_translation_start); + } + + public void startAppearAnimation(View[][] views, final Runnable finishListener) { + long maxDelay = 0; + ViewPropertyAnimator maxDelayAnimator = null; + for (int row = 0; row < views.length; row++) { + View[] columns = views[row]; + for (int col = 0; col < columns.length; col++) { + long delay = calculateDelay(row, col); + ViewPropertyAnimator animator = startAppearAnimation(columns[col], delay); + if (animator != null && delay > maxDelay) { + maxDelay = delay; + maxDelayAnimator = animator; + } + } + } + if (maxDelayAnimator != null) { + maxDelayAnimator.setListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + finishListener.run(); + } + }); + } else { + finishListener.run(); + } + } + + private ViewPropertyAnimator startAppearAnimation(View view, long delay) { + if (view == null) return null; + view.setAlpha(0f); + view.setTranslationY(mStartTranslation); + view.animate() + .alpha(1f) + .translationY(0) + .setInterpolator(mLinearOutSlowIn) + .setDuration(APPEAR_DURATION) + .setStartDelay(delay) + .setListener(null); + if (view.hasOverlappingRendering()) { + view.animate().withLayer(); + } + return view.animate(); + } + + private long calculateDelay(int row, int col) { + return (long) (row * 40 + col * (Math.pow(row, 0.4) + 0.4) * 20); + } + + public TimeInterpolator getInterpolator() { + return mLinearOutSlowIn; + } + + public float getStartTranslation() { + return mStartTranslation; + } +} diff --git a/packages/Keyguard/src/com/android/keyguard/EmergencyCarrierArea.java b/packages/Keyguard/src/com/android/keyguard/EmergencyCarrierArea.java index 6d392fc..a592db9 100644 --- a/packages/Keyguard/src/com/android/keyguard/EmergencyCarrierArea.java +++ b/packages/Keyguard/src/com/android/keyguard/EmergencyCarrierArea.java @@ -17,13 +17,12 @@ package com.android.keyguard; import android.content.Context; +import android.content.res.TypedArray; import android.util.AttributeSet; import android.view.MotionEvent; import android.view.View; import android.widget.LinearLayout; -import com.android.keyguard.R; - public class EmergencyCarrierArea extends LinearLayout { private CarrierText mCarrierText; @@ -48,6 +47,7 @@ public class EmergencyCarrierArea extends LinearLayout { mEmergencyButton.setOnTouchListener(new OnTouchListener(){ @Override public boolean onTouch(View v, MotionEvent event) { + if (mCarrierText.getVisibility() != View.VISIBLE) return false; switch(event.getAction()) { case MotionEvent.ACTION_DOWN: mCarrierText.animate().alpha(0); @@ -59,4 +59,8 @@ public class EmergencyCarrierArea extends LinearLayout { return false; }}); } + + public void setCarrierTextVisible(boolean visible) { + mCarrierText.setVisibility(visible ? View.VISIBLE : View.GONE); + } } diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardAccountView.java b/packages/Keyguard/src/com/android/keyguard/KeyguardAccountView.java index f69fa5f..69abc7a 100644 --- a/packages/Keyguard/src/com/android/keyguard/KeyguardAccountView.java +++ b/packages/Keyguard/src/com/android/keyguard/KeyguardAccountView.java @@ -328,5 +328,10 @@ public class KeyguardAccountView extends LinearLayout implements KeyguardSecurit @Override public void hideBouncer(int duration) { } + + @Override + public void startAppearAnimation() { + // TODO. + } } diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardFaceUnlockView.java b/packages/Keyguard/src/com/android/keyguard/KeyguardFaceUnlockView.java index 701d15f..c9fe93c 100644 --- a/packages/Keyguard/src/com/android/keyguard/KeyguardFaceUnlockView.java +++ b/packages/Keyguard/src/com/android/keyguard/KeyguardFaceUnlockView.java @@ -349,4 +349,8 @@ public class KeyguardFaceUnlockView extends LinearLayout implements KeyguardSecu hideBouncer(mSecurityMessageDisplay, mEcaView, mBouncerFrame, duration); } + @Override + public void startAppearAnimation() { + // TODO. + } } diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardMessageArea.java b/packages/Keyguard/src/com/android/keyguard/KeyguardMessageArea.java index ede23ef..d589283 100644 --- a/packages/Keyguard/src/com/android/keyguard/KeyguardMessageArea.java +++ b/packages/Keyguard/src/com/android/keyguard/KeyguardMessageArea.java @@ -48,35 +48,19 @@ class KeyguardMessageArea extends TextView { */ private static final long ANNOUNCEMENT_DELAY = 250; - static final int CHARGING_ICON = 0; //R.drawable.ic_lock_idle_charging; - static final int BATTERY_LOW_ICON = 0; //R.drawable.ic_lock_idle_low_battery; - static final int SECURITY_MESSAGE_DURATION = 5000; protected static final int FADE_DURATION = 750; private static final String TAG = "KeyguardMessageArea"; - // are we showing battery information? - boolean mShowingBatteryInfo = false; - // is the bouncer up? boolean mShowingBouncer = false; - // last known plugged in state - boolean mCharging = false; - - // last known battery level - int mBatteryLevel = 100; - KeyguardUpdateMonitor mUpdateMonitor; // Timeout before we reset the message to show charging/owner info long mTimeout = SECURITY_MESSAGE_DURATION; - // Shadowed text values - protected boolean mBatteryCharged; - protected boolean mBatteryIsLow; - private Handler mHandler; CharSequence mMessage; @@ -146,16 +130,6 @@ class KeyguardMessageArea extends TextView { } private KeyguardUpdateMonitorCallback mInfoCallback = new KeyguardUpdateMonitorCallback() { - @Override - public void onRefreshBatteryInfo(KeyguardUpdateMonitor.BatteryStatus status) { - mShowingBatteryInfo = status.isPluggedIn() || status.isBatteryLow(); - mCharging = status.status == BatteryManager.BATTERY_STATUS_CHARGING - || status.status == BatteryManager.BATTERY_STATUS_FULL; - mBatteryLevel = status.level; - mBatteryCharged = status.isCharged(); - mBatteryIsLow = status.isBatteryLow(); - update(); - } public void onScreenTurnedOff(int why) { setSelected(false); }; @@ -212,7 +186,7 @@ class KeyguardMessageArea extends TextView { */ void update() { MutableInt icon = new MutableInt(0); - CharSequence status = concat(getChargeInfo(icon), getOwnerInfo(), getCurrentMessage()); + CharSequence status = concat(getOwnerInfo(), getCurrentMessage()); setCompoundDrawablesWithIntrinsicBounds(icon.value, 0, 0, 0); setText(status); } @@ -248,25 +222,6 @@ class KeyguardMessageArea extends TextView { return info; } - private CharSequence getChargeInfo(MutableInt icon) { - CharSequence string = null; - if (mShowingBatteryInfo && !mShowingMessage) { - // Battery status - if (mCharging) { - // Charging, charged or waiting to charge. - string = getContext().getString(mBatteryCharged - ? R.string.keyguard_charged - : R.string.keyguard_plugged_in, mBatteryLevel); - icon.value = CHARGING_ICON; - } else if (mBatteryIsLow) { - // Battery is low - string = getContext().getString(R.string.keyguard_low_battery); - icon.value = BATTERY_LOW_ICON; - } - } - return string; - } - private void hideMessage(int duration, boolean thenUpdate) { if (duration > 0) { Animator anim = ObjectAnimator.ofFloat(this, "alpha", 0f); diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardPINView.java b/packages/Keyguard/src/com/android/keyguard/KeyguardPINView.java index ca2d615..1f3c176 100644 --- a/packages/Keyguard/src/com/android/keyguard/KeyguardPINView.java +++ b/packages/Keyguard/src/com/android/keyguard/KeyguardPINView.java @@ -22,6 +22,7 @@ import android.text.TextWatcher; import android.text.method.DigitsKeyListener; import android.util.AttributeSet; import android.view.View; +import android.view.ViewGroup; import android.widget.TextView.OnEditorActionListener; /** @@ -30,12 +31,21 @@ import android.widget.TextView.OnEditorActionListener; public class KeyguardPINView extends KeyguardAbsKeyInputView implements KeyguardSecurityView, OnEditorActionListener, TextWatcher { + private final AppearAnimationUtils mAppearAnimationUtils; + private ViewGroup mKeyguardBouncerFrame; + private ViewGroup mRow0; + private ViewGroup mRow1; + private ViewGroup mRow2; + private ViewGroup mRow3; + private View mDivider; + public KeyguardPINView(Context context) { this(context, null); } public KeyguardPINView(Context context, AttributeSet attrs) { super(context, attrs); + mAppearAnimationUtils = new AppearAnimationUtils(context); } protected void resetState() { @@ -56,6 +66,12 @@ public class KeyguardPINView extends KeyguardAbsKeyInputView protected void onFinishInflate() { super.onFinishInflate(); + mKeyguardBouncerFrame = (ViewGroup) findViewById(R.id.keyguard_bouncer_frame); + mRow0 = (ViewGroup) findViewById(R.id.row0); + mRow1 = (ViewGroup) findViewById(R.id.row1); + mRow2 = (ViewGroup) findViewById(R.id.row2); + mRow3 = (ViewGroup) findViewById(R.id.row3); + mDivider = findViewById(R.id.divider); final View ok = findViewById(R.id.key_enter); if (ok != null) { ok.setOnClickListener(new View.OnClickListener() { @@ -114,4 +130,48 @@ public class KeyguardPINView extends KeyguardAbsKeyInputView public int getWrongPasswordStringId() { return R.string.kg_wrong_pin; } + + @Override + public void startAppearAnimation() { + enableClipping(false); + setTranslationY(mAppearAnimationUtils.getStartTranslation()); + animate() + .setDuration(500) + .setInterpolator(mAppearAnimationUtils.getInterpolator()) + .translationY(0); + mAppearAnimationUtils.startAppearAnimation(new View[][] { + new View[] { + mRow0, null, null + }, + new View[] { + findViewById(R.id.key1), findViewById(R.id.key2), findViewById(R.id.key3) + }, + new View[] { + findViewById(R.id.key4), findViewById(R.id.key5), findViewById(R.id.key6) + }, + new View[] { + findViewById(R.id.key7), findViewById(R.id.key8), findViewById(R.id.key9) + }, + new View[] { + null, findViewById(R.id.key0), findViewById(R.id.key_enter) + }, + new View[] { + null, mEcaView, null + }}, + new Runnable() { + @Override + public void run() { + enableClipping(true); + } + }); + } + + private void enableClipping(boolean enable) { + mKeyguardBouncerFrame.setClipToPadding(enable); + mKeyguardBouncerFrame.setClipChildren(enable); + mRow1.setClipToPadding(enable); + mRow2.setClipToPadding(enable); + mRow3.setClipToPadding(enable); + setClipChildren(enable); + } } diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardPasswordView.java b/packages/Keyguard/src/com/android/keyguard/KeyguardPasswordView.java index e733afc..0c385da 100644 --- a/packages/Keyguard/src/com/android/keyguard/KeyguardPasswordView.java +++ b/packages/Keyguard/src/com/android/keyguard/KeyguardPasswordView.java @@ -198,4 +198,11 @@ public class KeyguardPasswordView extends KeyguardAbsKeyInputView public int getWrongPasswordStringId() { return R.string.kg_wrong_password; } + + @Override + public void startAppearAnimation() { + // TODO: Fancy animation. + setAlpha(0); + animate().alpha(1).withLayer().setDuration(200); + } } diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardPatternView.java b/packages/Keyguard/src/com/android/keyguard/KeyguardPatternView.java index 98122fc..5853ff9 100644 --- a/packages/Keyguard/src/com/android/keyguard/KeyguardPatternView.java +++ b/packages/Keyguard/src/com/android/keyguard/KeyguardPatternView.java @@ -400,4 +400,11 @@ public class KeyguardPatternView extends LinearLayout implements KeyguardSecurit KeyguardSecurityViewHelper. hideBouncer(mSecurityMessageDisplay, mEcaView, mBouncerFrame, duration); } + + @Override + public void startAppearAnimation() { + // TODO: Fancy animation. + setAlpha(0); + animate().alpha(1).withLayer().setDuration(200); + } } diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardSecurityContainer.java b/packages/Keyguard/src/com/android/keyguard/KeyguardSecurityContainer.java index 94edc07..382cbec 100644 --- a/packages/Keyguard/src/com/android/keyguard/KeyguardSecurityContainer.java +++ b/packages/Keyguard/src/com/android/keyguard/KeyguardSecurityContainer.java @@ -81,6 +81,10 @@ public class KeyguardSecurityContainer extends FrameLayout implements KeyguardSe getSecurityView(mCurrentSecuritySelection).onPause(); } + public void startAppearAnimation() { + getSecurityView(mCurrentSecuritySelection).startAppearAnimation(); + } + void updateSecurityViews(boolean isBouncing) { int children = mSecurityViewFlipper.getChildCount(); for (int i = 0; i < children; i++) { diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardSecurityView.java b/packages/Keyguard/src/com/android/keyguard/KeyguardSecurityView.java index dfeacf3..86bd877 100644 --- a/packages/Keyguard/src/com/android/keyguard/KeyguardSecurityView.java +++ b/packages/Keyguard/src/com/android/keyguard/KeyguardSecurityView.java @@ -84,4 +84,9 @@ public interface KeyguardSecurityView { * @param duration millisends for the transisiton animation. */ void hideBouncer(int duration); + + /** + * Starts the animation which should run when the security view appears. + */ + void startAppearAnimation(); } diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardSecurityViewFlipper.java b/packages/Keyguard/src/com/android/keyguard/KeyguardSecurityViewFlipper.java index 07239d1..178ca5e 100644 --- a/packages/Keyguard/src/com/android/keyguard/KeyguardSecurityViewFlipper.java +++ b/packages/Keyguard/src/com/android/keyguard/KeyguardSecurityViewFlipper.java @@ -159,6 +159,14 @@ public class KeyguardSecurityViewFlipper extends ViewFlipper implements Keyguard } @Override + public void startAppearAnimation() { + KeyguardSecurityView ksv = getSecurityView(); + if (ksv != null) { + ksv.startAppearAnimation(); + } + } + + @Override protected boolean checkLayoutParams(ViewGroup.LayoutParams p) { return p instanceof LayoutParams; } diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardSelectorView.java b/packages/Keyguard/src/com/android/keyguard/KeyguardSelectorView.java index 03e7b07..98baa04 100644 --- a/packages/Keyguard/src/com/android/keyguard/KeyguardSelectorView.java +++ b/packages/Keyguard/src/com/android/keyguard/KeyguardSelectorView.java @@ -244,4 +244,9 @@ public class KeyguardSelectorView extends LinearLayout implements KeyguardSecuri KeyguardSecurityViewHelper. hideBouncer(mSecurityMessageDisplay, mFadeView, mBouncerFrame, duration); } + + @Override + public void startAppearAnimation() { + // noop. + } } diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardSimPinView.java b/packages/Keyguard/src/com/android/keyguard/KeyguardSimPinView.java index 4791956..09c4e7c 100644 --- a/packages/Keyguard/src/com/android/keyguard/KeyguardSimPinView.java +++ b/packages/Keyguard/src/com/android/keyguard/KeyguardSimPinView.java @@ -135,6 +135,9 @@ public class KeyguardSimPinView extends KeyguardAbsKeyInputView mPasswordEntry.requestFocus(); mSecurityMessageDisplay.setTimeout(0); // don't show ownerinfo/charging status by default + if (mEcaView instanceof EmergencyCarrierArea) { + ((EmergencyCarrierArea) mEcaView).setCarrierTextVisible(true); + } } @Override @@ -270,5 +273,10 @@ public class KeyguardSimPinView extends KeyguardAbsKeyInputView mCheckSimPinThread.start(); } } + + @Override + public void startAppearAnimation() { + // noop. + } } diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardSimPukView.java b/packages/Keyguard/src/com/android/keyguard/KeyguardSimPukView.java index b9c7f51..6215d34 100644 --- a/packages/Keyguard/src/com/android/keyguard/KeyguardSimPukView.java +++ b/packages/Keyguard/src/com/android/keyguard/KeyguardSimPukView.java @@ -186,6 +186,9 @@ public class KeyguardSimPukView extends KeyguardAbsKeyInputView mPasswordEntry.requestFocus(); mSecurityMessageDisplay.setTimeout(0); // don't show ownerinfo/charging status by default + if (mEcaView instanceof EmergencyCarrierArea) { + ((EmergencyCarrierArea) mEcaView).setCarrierTextVisible(true); + } } @Override @@ -339,6 +342,11 @@ public class KeyguardSimPukView extends KeyguardAbsKeyInputView protected void verifyPasswordAndUnlock() { mStateMachine.next(); } + + @Override + public void startAppearAnimation() { + // noop. + } } diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardViewBase.java b/packages/Keyguard/src/com/android/keyguard/KeyguardViewBase.java index 48b7be9..3e444fa 100644 --- a/packages/Keyguard/src/com/android/keyguard/KeyguardViewBase.java +++ b/packages/Keyguard/src/com/android/keyguard/KeyguardViewBase.java @@ -241,6 +241,13 @@ public abstract class KeyguardViewBase extends FrameLayout implements SecurityCa } /** + * Starts the animation when the Keyguard gets shown. + */ + public void startAppearAnimation() { + mSecurityContainer.startAppearAnimation(); + } + + /** * Verify that the user can get past the keyguard securely. This is called, * for example, when the phone disables the keyguard but then wants to launch * something else that requires secure access. diff --git a/packages/SystemUI/res/drawable-hdpi/search_light.png b/packages/SystemUI/res/drawable-hdpi/search_light.png Binary files differdeleted file mode 100644 index 3c0dc4e..0000000 --- a/packages/SystemUI/res/drawable-hdpi/search_light.png +++ /dev/null diff --git a/packages/SystemUI/res/drawable-hdpi/search_light_land.png b/packages/SystemUI/res/drawable-hdpi/search_light_land.png Binary files differdeleted file mode 100644 index 731f19b..0000000 --- a/packages/SystemUI/res/drawable-hdpi/search_light_land.png +++ /dev/null diff --git a/packages/SystemUI/res/drawable-mdpi/search_light.png b/packages/SystemUI/res/drawable-mdpi/search_light.png Binary files differdeleted file mode 100644 index 8010ce7..0000000 --- a/packages/SystemUI/res/drawable-mdpi/search_light.png +++ /dev/null diff --git a/packages/SystemUI/res/drawable-mdpi/search_light_land.png b/packages/SystemUI/res/drawable-mdpi/search_light_land.png Binary files differdeleted file mode 100644 index a4d82f0..0000000 --- a/packages/SystemUI/res/drawable-mdpi/search_light_land.png +++ /dev/null diff --git a/packages/SystemUI/res/drawable-xhdpi/search_light.png b/packages/SystemUI/res/drawable-xhdpi/search_light.png Binary files differdeleted file mode 100644 index 6d46fdd..0000000 --- a/packages/SystemUI/res/drawable-xhdpi/search_light.png +++ /dev/null diff --git a/packages/SystemUI/res/drawable-xhdpi/search_light_land.png b/packages/SystemUI/res/drawable-xhdpi/search_light_land.png Binary files differdeleted file mode 100644 index b62c74e..0000000 --- a/packages/SystemUI/res/drawable-xhdpi/search_light_land.png +++ /dev/null diff --git a/packages/SystemUI/res/drawable-xxhdpi/search_light.png b/packages/SystemUI/res/drawable-xxhdpi/search_light.png Binary files differdeleted file mode 100644 index 7742207..0000000 --- a/packages/SystemUI/res/drawable-xxhdpi/search_light.png +++ /dev/null diff --git a/packages/SystemUI/res/drawable-xxhdpi/search_light_land.png b/packages/SystemUI/res/drawable-xxhdpi/search_light_land.png Binary files differdeleted file mode 100644 index f364577..0000000 --- a/packages/SystemUI/res/drawable-xxhdpi/search_light_land.png +++ /dev/null diff --git a/packages/SystemUI/res/drawable/ic_qs_ringer_audible.xml b/packages/SystemUI/res/drawable/ic_qs_ringer_audible.xml index 3a20c58..787eec5 100644 --- a/packages/SystemUI/res/drawable/ic_qs_ringer_audible.xml +++ b/packages/SystemUI/res/drawable/ic_qs_ringer_audible.xml @@ -24,5 +24,5 @@ Copyright (C) 2014 The Android Open Source Project <path android:fill="#FFFFFFFF" - android:pathData="M6.6,3.6L5.2,2.2C2.8,4.0 1.2,6.8 1.0,10.0l2.0,0.0C3.2,7.3 4.5,5.0 6.6,3.6zM20.0,10.0l2.0,0.0c-0.2,-3.2 -1.7,-6.0 -4.1,-7.8l-1.4,1.4C18.5,5.0 19.8,7.3 20.0,10.0zM18.0,10.5c0.0,-3.1 -2.1,-5.6 -5.0,-6.3L13.0,3.5C13.0,2.7 12.3,2.0 11.5,2.0C10.7,2.0 10.0,2.7 10.0,3.5l0.0,0.7c-2.9,0.7 -5.0,3.2 -5.0,6.3L5.0,16.0l-2.0,2.0l0.0,1.0l17.0,0.0l0.0,-1.0l-2.0,-2.0L18.0,10.5zM11.5,22.0c0.1,0.0 0.3,0.0 0.4,0.0c0.7,-0.1 1.2,-0.6 1.4,-1.2c0.1,-0.2 0.2,-0.5 0.2,-0.8l-4.0,0.0C9.5,21.1 10.4,22.0 11.5,22.0z" /> + android:pathData="M11.5,22.0c1.1,0.0 2.0,-0.9 2.0,-2.0l-4.0,0.0C9.5,21.1 10.4,22.0 11.5,22.0zM18.0,16.0l0.0,-5.5c0.0,-3.1 -2.1,-5.6 -5.0,-6.3L13.0,3.5C13.0,2.7 12.3,2.0 11.5,2.0C10.7,2.0 10.0,2.7 10.0,3.5l0.0,0.7c-2.9,0.7 -5.0,3.2 -5.0,6.3L5.0,16.0l-2.0,2.0l0.0,1.0l17.0,0.0l0.0,-1.0L18.0,16.0z"/> </vector> diff --git a/packages/SystemUI/res/drawable/ic_ringer_audible.xml b/packages/SystemUI/res/drawable/ic_ringer_audible.xml new file mode 100644 index 0000000..2969948 --- /dev/null +++ b/packages/SystemUI/res/drawable/ic_ringer_audible.xml @@ -0,0 +1,28 @@ +<!-- +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. +--> +<vector xmlns:android="http://schemas.android.com/apk/res/android" > + <size + android:width="32dp" + android:height="32dp"/> + + <viewport + android:viewportWidth="24.0" + android:viewportHeight="24.0"/> + + <path + android:fill="#FFFFFFFF" + android:pathData="M11.5,22.0c1.1,0.0 2.0,-0.9 2.0,-2.0l-4.0,0.0C9.5,21.1 10.4,22.0 11.5,22.0zM18.0,16.0l0.0,-5.5c0.0,-3.1 -2.1,-5.6 -5.0,-6.3L13.0,3.5C13.0,2.7 12.3,2.0 11.5,2.0C10.7,2.0 10.0,2.7 10.0,3.5l0.0,0.7c-2.9,0.7 -5.0,3.2 -5.0,6.3L5.0,16.0l-2.0,2.0l0.0,1.0l17.0,0.0l0.0,-1.0L18.0,16.0z"/> +</vector> diff --git a/packages/SystemUI/res/drawable/ic_ringer_silent.xml b/packages/SystemUI/res/drawable/ic_ringer_silent.xml new file mode 100644 index 0000000..b5837f6 --- /dev/null +++ b/packages/SystemUI/res/drawable/ic_ringer_silent.xml @@ -0,0 +1,28 @@ +<!-- +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. +--> +<vector xmlns:android="http://schemas.android.com/apk/res/android" > + <size + android:width="32dp" + android:height="32dp"/> + + <viewport + android:viewportWidth="24.0" + android:viewportHeight="24.0"/> + + <path + android:fill="#FFFFFFFF" + android:pathData="M11.5,22.0c1.1,0.0 2.0,-0.9 2.0,-2.0l-4.0,0.0C9.5,21.1 10.4,22.0 11.5,22.0zM18.0,10.5c0.0,-3.1 -2.1,-5.6 -5.0,-6.3L13.0,3.5C13.0,2.7 12.3,2.0 11.5,2.0C10.7,2.0 10.0,2.7 10.0,3.5l0.0,0.7C9.5,4.3 9.0,4.5 8.6,4.7l9.4,9.4L18.0,10.5zM17.7,19.0l2.0,2.0l1.3,-1.3L4.3,3.0L3.0,4.3l2.9,2.9C5.3,8.2 5.0,9.3 5.0,10.5L5.0,16.0l-2.0,2.0l0.0,1.0L17.7,19.0z" /> +</vector> diff --git a/packages/SystemUI/res/drawable/ic_ringer_vibrate.xml b/packages/SystemUI/res/drawable/ic_ringer_vibrate.xml new file mode 100644 index 0000000..d8ded58 --- /dev/null +++ b/packages/SystemUI/res/drawable/ic_ringer_vibrate.xml @@ -0,0 +1,28 @@ +<!-- +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. +--> +<vector xmlns:android="http://schemas.android.com/apk/res/android" > + <size + android:width="32dp" + android:height="32dp"/> + + <viewport + android:viewportWidth="24.0" + android:viewportHeight="24.0"/> + + <path + android:fill="#FFFFFFFF" + android:pathData="M0.0,15.0l2.0,0.0L2.0,9.0L0.0,9.0L0.0,15.0zM3.0,17.0l2.0,0.0L5.0,7.0L3.0,7.0L3.0,17.0zM22.0,9.0l0.0,6.0l2.0,0.0L24.0,9.0L22.0,9.0zM19.0,17.0l2.0,0.0L21.0,7.0l-2.0,0.0L19.0,17.0zM16.5,3.0l-9.0,0.0C6.7,3.0 6.0,3.7 6.0,4.5l0.0,15.0C6.0,20.3 6.7,21.0 7.5,21.0l9.0,0.0c0.8,0.0 1.5,-0.7 1.5,-1.5l0.0,-15.0C18.0,3.7 17.3,3.0 16.5,3.0zM16.0,19.0L8.0,19.0L8.0,5.0l8.0,0.0L16.0,19.0z"/> +</vector> diff --git a/packages/SystemUI/res/drawable/ic_vol_zen_off.xml b/packages/SystemUI/res/drawable/ic_vol_zen_off.xml new file mode 100644 index 0000000..ea5ab70 --- /dev/null +++ b/packages/SystemUI/res/drawable/ic_vol_zen_off.xml @@ -0,0 +1,30 @@ +<!-- +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. +--> +<vector xmlns:android="http://schemas.android.com/apk/res/android" > + <size + android:width="32dp" + android:height="32dp"/> + + <viewport + android:viewportWidth="24.0" + android:viewportHeight="24.0"/> + + <path + android:fill="#00000000" + android:stroke="#CCCCCC" + android:strokeWidth="1.0" + android:pathData="M12.0,2.0C6.5,2.0 2.0,6.5 2.0,12.0s4.5,10.0 10.0,10.0c5.5,0.0 10.0,-4.5 10.0,-10.0S17.5,2.0 12.0,2.0zM4.0,12.0c0.0,-4.4 3.6,-8.0 8.0,-8.0c1.8,0.0 3.5,0.6 4.9,1.7L5.7,16.9C4.6,15.5 4.0,13.8 4.0,12.0zM12.0,20.0c-1.8,0.0 -3.5,-0.6 -4.9,-1.7L18.3,7.1C19.4,8.5 20.0,10.2 20.0,12.0C20.0,16.4 16.4,20.0 12.0,20.0z" /> +</vector> diff --git a/packages/SystemUI/res/drawable/ic_vol_zen_on.xml b/packages/SystemUI/res/drawable/ic_vol_zen_on.xml new file mode 100644 index 0000000..44024f3 --- /dev/null +++ b/packages/SystemUI/res/drawable/ic_vol_zen_on.xml @@ -0,0 +1,28 @@ +<!-- +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. +--> +<vector xmlns:android="http://schemas.android.com/apk/res/android" > + <size + android:width="32dp" + android:height="32dp"/> + + <viewport + android:viewportWidth="24.0" + android:viewportHeight="24.0"/> + + <path + android:fill="#FFFFFFFF" + android:pathData="M12.0,2.0C6.5,2.0 2.0,6.5 2.0,12.0s4.5,10.0 10.0,10.0c5.5,0.0 10.0,-4.5 10.0,-10.0S17.5,2.0 12.0,2.0zM4.0,12.0c0.0,-4.4 3.6,-8.0 8.0,-8.0c1.8,0.0 3.5,0.6 4.9,1.7L5.7,16.9C4.6,15.5 4.0,13.8 4.0,12.0zM12.0,20.0c-1.8,0.0 -3.5,-0.6 -4.9,-1.7L18.3,7.1C19.4,8.5 20.0,10.2 20.0,12.0C20.0,16.4 16.4,20.0 12.0,20.0z" /> +</vector> diff --git a/packages/SystemUI/res/layout-ldrtl/navigation_bar.xml b/packages/SystemUI/res/layout-ldrtl/navigation_bar.xml index 84f0950..a291495 100644 --- a/packages/SystemUI/res/layout-ldrtl/navigation_bar.xml +++ b/packages/SystemUI/res/layout-ldrtl/navigation_bar.xml @@ -158,17 +158,6 @@ /> </LinearLayout> - <com.android.systemui.statusbar.policy.KeyButtonView - android:layout_width="80dp" - android:id="@+id/search_light" - android:layout_height="match_parent" - android:layout_gravity="center_horizontal" - android:src="@drawable/search_light" - android:scaleType="center" - android:visibility="gone" - android:contentDescription="@string/accessibility_search_light" - /> - <com.android.systemui.statusbar.policy.DeadZone android:id="@+id/deadzone" android:layout_height="match_parent" @@ -316,17 +305,6 @@ /> </LinearLayout> - <com.android.systemui.statusbar.policy.KeyButtonView - android:id="@+id/search_light" - android:layout_height="80dp" - android:layout_width="match_parent" - android:layout_gravity="center_vertical" - android:src="@drawable/search_light" - android:scaleType="center" - android:visibility="gone" - android:contentDescription="@string/accessibility_search_light" - /> - <com.android.systemui.statusbar.policy.DeadZone android:id="@+id/deadzone" android:layout_height="match_parent" diff --git a/packages/SystemUI/res/layout-sw600dp/navigation_bar.xml b/packages/SystemUI/res/layout-sw600dp/navigation_bar.xml index 34b674b..f8b7bae 100644 --- a/packages/SystemUI/res/layout-sw600dp/navigation_bar.xml +++ b/packages/SystemUI/res/layout-sw600dp/navigation_bar.xml @@ -156,17 +156,6 @@ /> </LinearLayout> - <com.android.systemui.statusbar.policy.KeyButtonView - android:layout_width="128dp" - android:id="@+id/search_light" - android:layout_height="match_parent" - android:layout_gravity="center_horizontal" - android:src="@drawable/search_light" - android:scaleType="center" - android:visibility="gone" - android:contentDescription="@string/accessibility_search_light" - /> - <com.android.systemui.statusbar.policy.DeadZone android:id="@+id/deadzone" android:layout_height="match_parent" @@ -313,17 +302,6 @@ /> </LinearLayout> - <com.android.systemui.statusbar.policy.KeyButtonView - android:layout_width="162dp" - android:id="@+id/search_light" - android:layout_height="match_parent" - android:layout_gravity="center_horizontal" - android:src="@drawable/search_light" - android:scaleType="center" - android:visibility="gone" - android:contentDescription="@string/accessibility_search_light" - /> - <com.android.systemui.statusbar.policy.DeadZone android:id="@+id/deadzone" android:layout_height="match_parent" diff --git a/packages/SystemUI/res/layout/keyguard_bottom_area.xml b/packages/SystemUI/res/layout/keyguard_bottom_area.xml index 936f73b..9bf42b2 100644 --- a/packages/SystemUI/res/layout/keyguard_bottom_area.xml +++ b/packages/SystemUI/res/layout/keyguard_bottom_area.xml @@ -22,7 +22,7 @@ android:layout_height="match_parent" android:layout_width="match_parent" > - <com.android.systemui.statusbar.phone.SwipeAffordanceView + <com.android.systemui.statusbar.AlphaImageView android:id="@+id/camera_button" android:layout_height="64dp" android:layout_width="64dp" @@ -30,10 +30,9 @@ android:tint="#ffffffff" android:src="@drawable/ic_camera_alt_24dp" android:scaleType="center" - android:contentDescription="@string/accessibility_camera_button" - systemui:swipeDirection="start"/> + android:contentDescription="@string/accessibility_camera_button" /> - <com.android.systemui.statusbar.phone.SwipeAffordanceView + <com.android.systemui.statusbar.AlphaImageView android:id="@+id/phone_button" android:layout_height="64dp" android:layout_width="64dp" @@ -41,8 +40,7 @@ android:tint="#ffffffff" android:src="@drawable/ic_phone_24dp" android:scaleType="center" - android:contentDescription="@string/accessibility_phone_button" - systemui:swipeDirection="end"/> + android:contentDescription="@string/accessibility_phone_button" /> <com.android.systemui.statusbar.phone.KeyguardIndicationTextView android:id="@+id/keyguard_indication_text" @@ -54,15 +52,13 @@ android:textColor="#ffffff" android:textAppearance="?android:attr/textAppearanceSmall"/> - <ImageView + <com.android.systemui.statusbar.AlphaImageView android:id="@+id/lock_icon" android:layout_width="64dp" android:layout_height="64dp" android:layout_gravity="bottom|center_horizontal" android:src="@drawable/ic_lock_24dp" android:scaleType="center" - android:alpha="0.7" - android:layerType="hardware" - android:tint="#ffffffff"/> + android:tint="#ffffffff" /> </com.android.systemui.statusbar.phone.KeyguardBottomAreaView> diff --git a/packages/SystemUI/res/layout/navigation_bar.xml b/packages/SystemUI/res/layout/navigation_bar.xml index fa6f7f5..7616cb1 100644 --- a/packages/SystemUI/res/layout/navigation_bar.xml +++ b/packages/SystemUI/res/layout/navigation_bar.xml @@ -160,22 +160,6 @@ /> </LinearLayout> - <FrameLayout - android:layout_width="match_parent" - android:layout_height="match_parent"> - - <com.android.systemui.statusbar.policy.KeyButtonView - android:layout_width="80dp" - android:id="@+id/search_light" - android:layout_height="match_parent" - android:layout_gravity="center" - android:src="@drawable/search_light" - android:scaleType="center" - android:visibility="gone" - android:contentDescription="@string/accessibility_search_light" - /> - </FrameLayout> - <com.android.systemui.statusbar.policy.DeadZone android:id="@+id/deadzone" android:layout_height="match_parent" @@ -325,19 +309,6 @@ /> </LinearLayout> - <com.android.systemui.statusbar.policy.KeyButtonView - android:id="@+id/search_light" - android:layout_height="80dp" - android:layout_width="match_parent" - android:layout_gravity="center_vertical" - android:src="@drawable/search_light_land" - android:scaleType="center" - android:visibility="gone" - android:contentDescription="@string/accessibility_search_light" - /> - - <!-- No camera button in landscape mode --> - <com.android.systemui.statusbar.policy.DeadZone android:id="@+id/deadzone" android:layout_height="match_parent" diff --git a/packages/SystemUI/res/layout/qs_zen_mode_detail.xml b/packages/SystemUI/res/layout/qs_detail.xml index 85b294d..e73b431 100644 --- a/packages/SystemUI/res/layout/qs_zen_mode_detail.xml +++ b/packages/SystemUI/res/layout/qs_detail.xml @@ -14,64 +14,44 @@ See the License for the specific language governing permissions and limitations under the License. --> -<com.android.systemui.qs.tiles.ZenModeDetail xmlns:android="http://schemas.android.com/apk/res/android" +<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" - android:background="@color/system_secondary_color" > + android:background="@color/system_primary_color" > <ImageView android:id="@android:id/button1" - android:src="@drawable/ic_qs_close" android:layout_width="64dp" android:layout_height="64dp" android:layout_alignParentStart="true" - android:padding="@dimen/qs_panel_padding" /> - - <Switch - android:id="@android:id/checkbox" - android:layout_width="wrap_content" - android:layout_height="64dp" - android:layout_alignParentEnd="true" - android:gravity="center" - android:padding="@dimen/qs_panel_padding" /> + android:contentDescription="@string/accessibility_quick_settings_close" + android:padding="@dimen/qs_panel_padding" + android:src="@drawable/ic_qs_close" /> <TextView android:id="@android:id/title" android:layout_width="match_parent" android:layout_height="64dp" + android:layout_alignParentTop="true" android:layout_toEndOf="@android:id/button1" android:layout_toStartOf="@android:id/checkbox" - android:textAppearance="@style/TextAppearance.StatusBar.Expanded.Clock" android:gravity="center_vertical" - android:paddingStart="@dimen/qs_panel_padding" - android:text="@string/zen_mode_title" /> + android:textAppearance="@style/TextAppearance.QS.DetailHeader" /> - <View + <ImageView android:id="@android:id/custom" android:layout_width="match_parent" - android:layout_height="2dp" + android:layout_height="wrap_content" android:layout_below="@android:id/title" - android:background="#888" /> + android:layout_marginLeft="16dip" + android:layout_marginRight="16dip" + android:scaleType="fitXY" + android:src="?android:attr/dividerHorizontal" /> - <ListView + <FrameLayout android:id="@android:id/content" android:layout_width="match_parent" android:layout_height="match_parent" - android:layout_above="@android:id/button2" - android:layout_below="@android:id/custom" - android:divider="#00000000" - android:dividerHeight="0px" /> - - <TextView - android:id="@android:id/button2" - style="@style/QSBorderless" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_alignParentBottom="true" - android:layout_alignParentEnd="true" - android:textAppearance="@style/TextAppearance.StatusBar.Expanded.Clock" - android:padding="@dimen/qs_panel_padding" - android:text="@string/quick_settings_more_settings" - android:textAllCaps="true" /> + android:layout_below="@android:id/custom" /> -</com.android.systemui.qs.tiles.ZenModeDetail>
\ No newline at end of file +</RelativeLayout>
\ No newline at end of file diff --git a/packages/SystemUI/res/layout/status_bar_expanded_header.xml b/packages/SystemUI/res/layout/status_bar_expanded_header.xml index dfc3b22..7f34041 100644 --- a/packages/SystemUI/res/layout/status_bar_expanded_header.xml +++ b/packages/SystemUI/res/layout/status_bar_expanded_header.xml @@ -98,7 +98,8 @@ android:gravity="center_vertical" android:ellipsize="marquee" android:textAppearance="?android:attr/textAppearanceSmall" - android:textColor="#ffffff" /> + android:textColor="#ffffff" + android:singleLine="true" /> <include layout="@layout/quick_settings_brightness_dialog" diff --git a/packages/SystemUI/res/layout/user_switcher_host.xml b/packages/SystemUI/res/layout/user_switcher_host.xml index 70c5042..816af57 100644 --- a/packages/SystemUI/res/layout/user_switcher_host.xml +++ b/packages/SystemUI/res/layout/user_switcher_host.xml @@ -27,7 +27,7 @@ <FrameLayout android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_marginTop="@*android:dimen/volume_panel_top" + android:layout_marginTop="@dimen/volume_panel_top" android:background="@*android:drawable/dialog_full_holo_dark"> <ListView android:id="@android:id/list" android:layout_width="match_parent" diff --git a/packages/SystemUI/res/layout/volume_panel.xml b/packages/SystemUI/res/layout/volume_panel.xml new file mode 100644 index 0000000..bc7288d --- /dev/null +++ b/packages/SystemUI/res/layout/volume_panel.xml @@ -0,0 +1,67 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2007 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. +--> +<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/visible_panel" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="horizontal" > + + <FrameLayout + android:id="@+id/slider_panel" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_toLeftOf="@+id/expand_button_divider" /> + + <ImageView + android:id="@+id/expand_button_divider" + android:layout_width="wrap_content" + android:layout_height="32dip" + android:layout_gravity="top" + android:layout_marginBottom="16dip" + android:layout_marginTop="16dip" + android:layout_toLeftOf="@+id/expand_button" + android:scaleType="fitXY" + android:src="?android:attr/dividerVertical" /> + + <ImageView + android:id="@+id/expand_button" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_alignParentRight="true" + android:layout_gravity="top" + style="@style/BorderlessButton.Tiny" + android:padding="16dip" /> + + <ImageView + android:id="@+id/zen_panel_divider" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_below="@+id/slider_panel" + android:layout_marginLeft="16dip" + android:layout_marginRight="16dip" + android:scaleType="fitXY" + android:src="?android:attr/dividerHorizontal" /> + + <ViewStub + android:id="@+id/zen_panel_stub" + android:layout_below="@+id/zen_panel_divider" + android:inflatedId="@+id/zen_panel" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout="@layout/zen_mode_panel" /> + +</RelativeLayout> diff --git a/packages/SystemUI/res/layout/volume_panel_item.xml b/packages/SystemUI/res/layout/volume_panel_item.xml new file mode 100644 index 0000000..98cb8f4 --- /dev/null +++ b/packages/SystemUI/res/layout/volume_panel_item.xml @@ -0,0 +1,42 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2011 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. +--> + +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="80dip" + android:orientation="horizontal" + android:layout_marginTop="8dip" + android:layout_marginBottom="8dip" + android:gravity="start|center_vertical"> + + <ImageView + android:id="@+id/stream_icon" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:paddingLeft="16dip" + android:background="?android:attr/selectableItemBackground" + android:contentDescription="@null" /> + + <SeekBar + style="?android:attr/seekBarStyle" + android:id="@+id/seekbar" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_weight="1" + android:padding="16dip" + android:layout_marginEnd="16dip" /> + +</LinearLayout> diff --git a/packages/SystemUI/res/layout/qs_zen_mode_detail_condition.xml b/packages/SystemUI/res/layout/zen_mode_condition.xml index fd27aaf..8b34400 100644 --- a/packages/SystemUI/res/layout/qs_zen_mode_detail_condition.xml +++ b/packages/SystemUI/res/layout/zen_mode_condition.xml @@ -16,44 +16,47 @@ --> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" - android:layout_height="match_parent" > + android:layout_height="wrap_content" > <RadioButton android:id="@android:id/checkbox" android:layout_width="32dp" - android:layout_height="64dp" + android:layout_height="@dimen/zen_mode_condition_height" android:layout_alignParentStart="true" - android:layout_marginStart="@dimen/qs_panel_padding" android:gravity="center" /> <TextView android:id="@android:id/title" android:layout_width="match_parent" - android:layout_height="64dp" + android:layout_height="@dimen/zen_mode_condition_height" android:layout_toEndOf="@android:id/checkbox" android:layout_toStartOf="@android:id/button1" android:ellipsize="end" - android:textAppearance="@style/TextAppearance.StatusBar.Expanded.Clock" android:gravity="center_vertical" android:maxLines="1" - android:text="@string/accessibility_back" /> + android:textAppearance="@style/TextAppearance.QS.DetailItemPrimary" /> <ImageView android:id="@android:id/button1" - android:src="@drawable/ic_qs_minus" - android:layout_width="64dp" - android:layout_height="64dp" + style="@style/BorderlessButton" + android:layout_width="@dimen/zen_mode_condition_height" + android:layout_height="@dimen/zen_mode_condition_height" android:layout_alignParentEnd="true" - android:layout_marginEnd="48dp" - android:padding="@dimen/qs_panel_padding" - android:paddingRight="0px" /> + android:layout_centerVertical="true" + android:layout_marginEnd="@dimen/zen_mode_condition_height" + android:contentDescription="@string/accessibility_quick_settings_less_time" + android:padding="@dimen/zen_mode_condition_detail_button_padding" + android:src="@drawable/ic_qs_minus" /> <ImageView android:id="@android:id/button2" - android:src="@drawable/ic_qs_plus" - android:layout_width="64dp" - android:layout_height="64dp" + style="@style/BorderlessButton" + android:layout_width="@dimen/zen_mode_condition_height" + android:layout_height="@dimen/zen_mode_condition_height" android:layout_alignParentEnd="true" - android:padding="@dimen/qs_panel_padding" /> + android:layout_centerVertical="true" + android:contentDescription="@string/accessibility_quick_settings_more_time" + android:padding="@dimen/zen_mode_condition_detail_button_padding" + android:src="@drawable/ic_qs_plus" /> -</RelativeLayout> +</RelativeLayout>
\ No newline at end of file diff --git a/packages/SystemUI/res/layout/zen_mode_panel.xml b/packages/SystemUI/res/layout/zen_mode_panel.xml new file mode 100644 index 0000000..ae04bf5 --- /dev/null +++ b/packages/SystemUI/res/layout/zen_mode_panel.xml @@ -0,0 +1,51 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + 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. +--> +<!-- extends LinearLayout --> +<com.android.systemui.volume.ZenModePanel xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/zen_mode_panel" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:background="@color/system_primary_color" + android:orientation="vertical" + android:padding="@dimen/qs_panel_padding" > + + <TextView + android:id="@android:id/title" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_alignParentStart="true" + android:layout_marginBottom="8dp" + android:text="@string/zen_mode_title" + android:textAppearance="@style/TextAppearance.QS.DetailHeader" /> + + <LinearLayout + android:id="@android:id/content" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical" /> + + <TextView + android:id="@android:id/button2" + style="@style/BorderlessButton" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_alignParentEnd="true" + android:layout_gravity="end" + android:text="@string/quick_settings_more_settings" + android:textAppearance="@style/TextAppearance.QS.DetailButton" /> + +</com.android.systemui.volume.ZenModePanel>
\ No newline at end of file diff --git a/packages/SystemUI/res/values/attrs.xml b/packages/SystemUI/res/values/attrs.xml index 3549689..c453618 100644 --- a/packages/SystemUI/res/values/attrs.xml +++ b/packages/SystemUI/res/values/attrs.xml @@ -43,12 +43,6 @@ <declare-styleable name="BatteryMeterView"> <attr name="frameColor" format="color" /> </declare-styleable> - <declare-styleable name="SwipeAffordanceView"> - <attr name="swipeDirection" format="enum"> - <enum name="start" value="0" /> - <enum name="end" value="1" /> - </attr> - </declare-styleable> <declare-styleable name="Clock"> <attr name="amPmStyle" format="enum"> <enum name="normal" value="0" /> diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml index 0bd4f18..6405ae6 100644 --- a/packages/SystemUI/res/values/config.xml +++ b/packages/SystemUI/res/values/config.xml @@ -135,5 +135,8 @@ <!-- Defines the implementation of the velocity tracker to be used for the panel expansion. Can be 'platform' or 'noisy' (i.e. for noisy touch screens). --> <string name="velocity_tracker_impl" translatable="false">platform</string> + + <!-- Wait on the touch feedback this long before performing an action. --> + <integer name="feedback_start_delay">300</integer> </resources> diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index bf0cb68..610b376 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -199,6 +199,9 @@ <!-- How far the expanded QS panel peeks from the header in collapsed state. --> <dimen name="qs_peek_height">8dp</dimen> + <dimen name="zen_mode_condition_detail_button_padding">8dp</dimen> + <dimen name="zen_mode_condition_height">48dp</dimen> + <!-- used by DessertCase --> <dimen name="dessert_case_cell_size">192dp</dimen> @@ -291,4 +294,12 @@ <dimen name="keyguard_clock_notifications_margin_min">22dp</dimen> <dimen name="keyguard_clock_notifications_margin_max">36dp</dimen> + <!-- The minimum amount the user needs to swipe to go to the camera / phone. --> + <dimen name="keyguard_min_swipe_amount">75dp</dimen> + + <!-- Volume panel dialog y offset --> + <dimen name="volume_panel_top">16dp</dimen> + + <!-- Volume panel dialog width --> + <dimen name="volume_panel_width">300dp</dimen> </resources> diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index f5bc353..ef3956e 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -388,6 +388,12 @@ <string name="accessibility_quick_settings_location">Location <xliff:g id="state" example="Off">%s</xliff:g>.</string> <!-- Content description of the alarm tile in quick settings (not shown on the screen). [CHAR LIMIT=NONE] --> <string name="accessibility_quick_settings_alarm">Alarm set for <xliff:g id="time" example="Wed 3:30 PM">%s</xliff:g>.</string> + <!-- Content description of quick settings detail panel close button (not shown on the screen). [CHAR LIMIT=NONE] --> + <string name="accessibility_quick_settings_close">Close panel</string> + <!-- Content description of zen mode time condition plus button (not shown on the screen). [CHAR LIMIT=NONE] --> + <string name="accessibility_quick_settings_more_time">More time</string> + <!-- Content description of zen mode time condition minus button (not shown on the screen). [CHAR LIMIT=NONE] --> + <string name="accessibility_quick_settings_less_time">Less time</string> <!-- Title of dialog shown when 2G-3G data usage has exceeded limit and has been disabled. [CHAR LIMIT=48] --> <string name="data_usage_disabled_dialog_3g_title">2G-3G data disabled</string> @@ -512,6 +518,8 @@ <string name="quick_settings_tethering_label">Tethering</string> <!-- QuickSettings: Hotspot. [CHAR LIMIT=NONE] --> <string name="quick_settings_hotspot_label">Hotspot</string> + <!-- QuickSettings: Notifications [CHAR LIMIT=NONE] --> + <string name="quick_settings_notifications_label">Notifications</string> <!-- Recents: The empty recents string. [CHAR LIMIT=NONE] --> <string name="recents_empty_message">RECENTS</string> @@ -563,4 +571,19 @@ <string name="keyguard_unlock">Swipe up to unlock</string> <string name="bugreport_tile_extended" translatable="false">%s\n%s (%s)</string> + + <!-- Zen mode condition: no exit criteria. [CHAR LIMIT=NONE] --> + <string name="zen_mode_forever">Until you turn this off</string> + + <!-- Zen mode condition: time duration in minutes. [CHAR LIMIT=NONE] --> + <plurals name="zen_mode_duration_minutes"> + <item quantity="one">For one minute</item> + <item quantity="other">For %d minutes</item> + </plurals> + + <!-- Zen mode condition: time duration in hours. [CHAR LIMIT=NONE] --> + <plurals name="zen_mode_duration_hours"> + <item quantity="one">For one hour</item> + <item quantity="other">For %d hours</item> + </plurals> </resources> diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml index 19888a8..6a12232 100644 --- a/packages/SystemUI/res/values/styles.xml +++ b/packages/SystemUI/res/values/styles.xml @@ -133,6 +133,32 @@ <item name="android:fadingEdge">horizontal</item> </style> + <style name="TextAppearance.QS"> + <item name="android:textStyle">normal</item> + <item name="android:textColor">#ffffff</item> + <item name="android:fontFamily">sans-serif</item> + </style> + + <style name="TextAppearance.QS.DetailHeader"> + <item name="android:textSize">20sp</item> + <item name="android:fontFamily">sans-serif-medium</item> + </style> + + <style name="TextAppearance.QS.DetailItemPrimary"> + <item name="android:textSize">16sp</item> + </style> + + <style name="TextAppearance.QS.DetailItemSecondary"> + <item name="android:textSize">14sp</item> + <item name="android:textColor">#7fcac3</item> + </style> + + <style name="TextAppearance.QS.DetailButton"> + <item name="android:textSize">14sp</item> + <item name="android:textAllCaps">true</item> + <item name="android:fontFamily">sans-serif-medium</item> + </style> + <style name="BaseBrightnessDialogContainer"> <item name="android:layout_width">match_parent</item> <item name="android:layout_height">wrap_content</item> @@ -192,9 +218,9 @@ <item name="android:colorControlActivated">@color/system_accent_color</item> </style> - <style name="QSBorderless" parent="@android:style/Widget.Quantum.Button.Borderless" /> + <style name="BorderlessButton" parent="@android:style/Widget.Quantum.Button.Borderless" /> - <style name="QSBorderless.Tiny"> + <style name="BorderlessButton.Tiny"> <item name="android:minHeight">12dip</item> <item name="android:minWidth">12dip</item> </style> diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java index d7ce255..630ba13 100644 --- a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java +++ b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java @@ -42,12 +42,12 @@ public class SystemUIApplication extends Application { private final Class<?>[] SERVICES = new Class[] { com.android.systemui.keyguard.KeyguardViewMediator.class, com.android.systemui.recent.Recents.class, + com.android.systemui.volume.VolumeUI.class, com.android.systemui.statusbar.SystemBars.class, com.android.systemui.usb.StorageNotification.class, com.android.systemui.power.PowerUI.class, com.android.systemui.media.RingtonePlayer.class, com.android.systemui.settings.SettingsUI.class, - com.android.systemui.volume.VolumeUI.class, }; /** diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTile.java b/packages/SystemUI/src/com/android/systemui/qs/QSTile.java index 835a5c4..c76ee8c 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSTile.java @@ -26,6 +26,7 @@ import android.util.Log; import android.view.View; import android.view.ViewGroup; +import com.android.systemui.R; import com.android.systemui.qs.QSTile.State; import com.android.systemui.statusbar.policy.BluetoothController; import com.android.systemui.statusbar.policy.CastController; @@ -35,6 +36,7 @@ import com.android.systemui.statusbar.policy.NetworkController; import com.android.systemui.statusbar.policy.RotationLockController; import com.android.systemui.statusbar.policy.TetheringController; import com.android.systemui.statusbar.policy.ZenModeController; +import com.android.systemui.volume.VolumeComponent; import java.util.List; import java.util.Objects; @@ -49,12 +51,12 @@ import java.util.Objects; public abstract class QSTile<TState extends State> implements Listenable { protected final String TAG = "QSTile." + getClass().getSimpleName(); protected static final boolean DEBUG = false; - public static final int FEEDBACK_START_DELAY = 400; protected final Host mHost; protected final Context mContext; protected final H mHandler; protected final Handler mUiHandler = new Handler(Looper.getMainLooper()); + private final int mFeedbackStartDelay; private Callback mCallback; protected final TState mState = newTileState(); @@ -68,6 +70,7 @@ public abstract class QSTile<TState extends State> implements Listenable { mHost = host; mContext = host.getContext(); mHandler = new H(host.getLooper()); + mFeedbackStartDelay = mContext.getResources().getInteger(R.integer.feedback_start_delay); } public boolean supportsDualTargets() { @@ -116,6 +119,10 @@ public abstract class QSTile<TState extends State> implements Listenable { mHandler.obtainMessage(H.USER_SWITCH, newUserId).sendToTarget(); } + protected void postAfterFeedback(Runnable runnable) { + mHandler.postDelayed(runnable, mFeedbackStartDelay); + } + // call only on tile worker looper private void handleSetCallback(Callback callback) { @@ -213,6 +220,7 @@ public abstract class QSTile<TState extends State> implements Listenable { ZenModeController getZenModeController(); TetheringController getTetheringController(); CastController getCastController(); + VolumeComponent getVolumeComponent(); } public static class State { diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTileView.java b/packages/SystemUI/src/com/android/systemui/qs/QSTileView.java index 5eecc20..2edd8d5 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSTileView.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSTileView.java @@ -84,7 +84,8 @@ public class QSTileView extends ViewGroup { removeView(mLabel); } final Resources res = mContext.getResources(); - mLabel = new TextView(mDual ? new ContextThemeWrapper(mContext, R.style.QSBorderless_Tiny) + mLabel = new TextView(mDual + ? new ContextThemeWrapper(mContext, R.style.BorderlessButton_Tiny) : mContext); mLabel.setId(android.R.id.title); mLabel.setTextColor(res.getColor(R.color.qs_tile_text)); diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/BugreportTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/BugreportTile.java index fa41837..07ea825 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/BugreportTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/BugreportTile.java @@ -58,13 +58,13 @@ public class BugreportTile extends QSTile<QSTile.State> { @Override protected void handleClick() { - mHandler.postDelayed(new Runnable() { + postAfterFeedback(new Runnable() { @Override public void run() { mHost.collapsePanels(); mUiHandler.post(mShowDialog); } - }, FEEDBACK_START_DELAY); + }); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java index 907c77e..6793051 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java @@ -65,12 +65,12 @@ public class CastTile extends QSTile<QSTile.BooleanState> { @Override protected void handleClick() { - mHandler.postDelayed(new Runnable() { + postAfterFeedback(new Runnable() { public void run() { mHost.collapsePanels(); mUiHandler.post(mShowDialog); } - }, FEEDBACK_START_DELAY); + }); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/NotificationsTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/NotificationsTile.java new file mode 100644 index 0000000..267786b --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/NotificationsTile.java @@ -0,0 +1,172 @@ +/* + * 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.qs.tiles; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.media.AudioManager; +import android.util.Log; +import android.view.ContextThemeWrapper; +import android.view.LayoutInflater; +import android.view.View; +import android.view.View.OnAttachStateChangeListener; +import android.view.ViewGroup; +import android.widget.TextView; + +import com.android.systemui.R; +import com.android.systemui.qs.QSTile; +import com.android.systemui.statusbar.policy.ZenModeController; +import com.android.systemui.volume.VolumeComponent; +import com.android.systemui.volume.VolumePanel; +import com.android.systemui.volume.ZenModePanel; + +/** Quick settings tile: Notifications **/ +public class NotificationsTile extends QSTile<NotificationsTile.NotificationsState> { + private final ZenModeController mZenController; + private final AudioManager mAudioManager; + + public NotificationsTile(Host host) { + super(host); + mZenController = host.getZenModeController(); + mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE); + } + + @Override + public View createDetailView(Context context, ViewGroup root) { + final Context themedContext = new ContextThemeWrapper(mContext, R.style.QSAccentTheme); + final View v = LayoutInflater.from(themedContext).inflate(R.layout.qs_detail, root, false); + final TextView title = (TextView) v.findViewById(android.R.id.title); + title.setText(R.string.quick_settings_notifications_label); + final View close = v.findViewById(android.R.id.button1); + close.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + showDetail(false); + } + }); + final ViewGroup content = (ViewGroup) v.findViewById(android.R.id.content); + final VolumeComponent volumeComponent = mHost.getVolumeComponent(); + final VolumePanel vp = new VolumePanel(mContext, content, mZenController); + v.addOnAttachStateChangeListener(new OnAttachStateChangeListener() { + @Override + public void onViewDetachedFromWindow(View v) { + volumeComponent.setVolumePanel(null); + } + + @Override + public void onViewAttachedToWindow(View v) { + volumeComponent.setVolumePanel(vp); + } + }); + vp.setZenModePanelCallback(new ZenModePanel.Callback() { + @Override + public void onMoreSettings() { + mHost.startSettingsActivity(ZenModePanel.ZEN_SETTINGS); + } + + @Override + public void onInteraction() { + // noop + } + }); + vp.postVolumeChanged(AudioManager.STREAM_RING, AudioManager.FLAG_SHOW_UI); + return v; + } + + @Override + protected NotificationsState newTileState() { + return new NotificationsState(); + } + + @Override + public void setListening(boolean listening) { + if (listening) { + mZenController.addCallback(mCallback); + final IntentFilter filter = new IntentFilter(AudioManager.RINGER_MODE_CHANGED_ACTION); + mContext.registerReceiver(mReceiver, filter); + } else { + mZenController.removeCallback(mCallback); + mContext.unregisterReceiver(mReceiver); + } + } + + @Override + protected void handleClick() { + if (mState.zen) { + mZenController.setZen(false); + } else { + showDetail(true); + } + } + + @Override + protected void handleUpdateState(NotificationsState state, Object arg) { + state.visible = true; + state.zen = arg instanceof Boolean ? (Boolean) arg : mZenController.isZen(); + state.ringerMode = mAudioManager.getRingerMode(); + if (state.zen) { + state.iconId = R.drawable.ic_qs_zen_on; + } else if (state.ringerMode == AudioManager.RINGER_MODE_VIBRATE) { + state.iconId = R.drawable.ic_qs_ringer_vibrate; + } else if (state.ringerMode == AudioManager.RINGER_MODE_SILENT) { + state.iconId = R.drawable.ic_qs_ringer_silent; + } else { + state.iconId = R.drawable.ic_qs_ringer_audible; + } + state.label = mContext.getString(R.string.quick_settings_notifications_label); + } + + private final ZenModeController.Callback mCallback = new ZenModeController.Callback() { + @Override + public void onZenChanged(boolean zen) { + if (DEBUG) Log.d(TAG, "onZenChanged " + zen); + refreshState(zen); + } + }; + + public static final class NotificationsState extends QSTile.State { + public boolean zen; + public int ringerMode; + + @Override + public boolean copyTo(State other) { + final NotificationsState o = (NotificationsState) other; + final boolean changed = o.zen != zen || o.ringerMode != ringerMode; + o.zen = zen; + o.ringerMode = ringerMode; + return super.copyTo(other) || changed; + } + + @Override + protected StringBuilder toStringBuilder() { + final StringBuilder rt = super.toStringBuilder(); + rt.insert(rt.length() - 1, ",zen=" + zen + ",ringerMode=" + ringerMode); + return rt; + } + } + + private final BroadcastReceiver mReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + if (AudioManager.RINGER_MODE_CHANGED_ACTION.equals(intent.getAction())) { + refreshState(); + } + } + }; +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/RingerModeTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/RingerModeTile.java deleted file mode 100644 index c5e9b52..0000000 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/RingerModeTile.java +++ /dev/null @@ -1,108 +0,0 @@ -/* - * 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.qs.tiles; - -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.media.AudioManager; - -import com.android.systemui.R; -import com.android.systemui.qs.QSTile; - -/** Quick settings tile: Ringer mode **/ -public class RingerModeTile extends QSTile<RingerModeTile.IntState> { - - private final AudioManager mAudioManager; - - public RingerModeTile(Host host) { - super(host); - mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE); - } - - @Override - protected IntState newTileState() { - return new IntState(); - } - - @Override - public void setListening(boolean listening) { - if (listening) { - final IntentFilter filter = new IntentFilter(AudioManager.RINGER_MODE_CHANGED_ACTION); - mContext.registerReceiver(mReceiver, filter); - } else { - mContext.unregisterReceiver(mReceiver); - } - } - - @Override - protected void handleClick() { - final int oldValue = (Integer) mState.value; - final int newValue = - oldValue == AudioManager.RINGER_MODE_NORMAL ? AudioManager.RINGER_MODE_VIBRATE - : oldValue == AudioManager.RINGER_MODE_VIBRATE ? AudioManager.RINGER_MODE_SILENT - : AudioManager.RINGER_MODE_NORMAL; - - mAudioManager.setRingerMode(newValue); - } - - @Override - protected void handleUpdateState(IntState state, Object arg) { - final int ringerMode = mAudioManager.getRingerMode(); - state.visible = true; - state.value = ringerMode; - if (ringerMode == AudioManager.RINGER_MODE_VIBRATE) { - state.iconId = R.drawable.ic_qs_ringer_vibrate; - state.label = "Vibrate"; - } else if (ringerMode == AudioManager.RINGER_MODE_SILENT) { - state.iconId = R.drawable.ic_qs_ringer_silent; - state.label = "Silent"; - } else { - state.iconId = R.drawable.ic_qs_ringer_audible; - state.label = "Audible"; - } - } - - private final BroadcastReceiver mReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - if (AudioManager.RINGER_MODE_CHANGED_ACTION.equals(intent.getAction())) { - refreshState(); - } - } - }; - - public static class IntState extends QSTile.State { - public int value; - - @Override - public boolean copyTo(State other) { - final IntState o = (IntState) other; - final boolean changed = o.value != value; - o.value = value; - return super.copyTo(other) || changed; - } - - @Override - protected StringBuilder toStringBuilder() { - final StringBuilder rt = super.toStringBuilder(); - rt.insert(rt.length() - 1, ",value=" + value); - return rt; - } - } -} diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java index ef7fb89..a1e70b9 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java @@ -83,7 +83,7 @@ public class WifiTile extends QSTile<QSTile.SignalState> { boolean wifiConnected = cb.enabled && (cb.wifiSignalIconId > 0) && (cb.enabledDesc != null); boolean wifiNotConnected = (cb.wifiSignalIconId > 0) && (cb.enabledDesc == null); - state.enabled = wifiConnected; + state.enabled = cb.enabled; state.connected = wifiConnected; state.activityIn = cb.enabled && cb.activityIn; state.activityOut = cb.enabled && cb.activityOut; diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/ZenModeDetail.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/ZenModeDetail.java deleted file mode 100644 index f30f791..0000000 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/ZenModeDetail.java +++ /dev/null @@ -1,273 +0,0 @@ -/* - * 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.qs.tiles; - -import android.content.Context; -import android.content.Intent; -import android.net.Uri; -import android.os.Handler; -import android.os.Looper; -import android.os.Message; -import android.provider.Settings; -import android.service.notification.Condition; -import android.util.AttributeSet; -import android.view.ContextThemeWrapper; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.ArrayAdapter; -import android.widget.CompoundButton; -import android.widget.CompoundButton.OnCheckedChangeListener; -import android.widget.ImageView; -import android.widget.ListView; -import android.widget.RadioButton; -import android.widget.RelativeLayout; -import android.widget.Switch; -import android.widget.TextView; - -import com.android.systemui.R; -import com.android.systemui.qs.QSTile; -import com.android.systemui.statusbar.policy.ZenModeController; - -import java.util.HashSet; - -/** Quick settings control panel: Zen mode **/ -public class ZenModeDetail extends RelativeLayout { - private static final String TAG = "ZenModeDetail"; - private static final Intent ZEN_SETTINGS = new Intent(Settings.ACTION_ZEN_MODE_SETTINGS); - private static final int[] MINUTES = new int[] { 15, 30, 45, 60, 120, 180, 240, 480 }; - - private final H mHandler = new H(); - - private int mMinutesIndex = 3; - private Context mContext; - private ZenModeTile mTile; - private QSTile.Host mHost; - private ZenModeController mController; - - private Switch mSwitch; - private ConditionAdapter mAdapter; - - public ZenModeDetail(Context context, AttributeSet attrs) { - super(context, attrs); - } - - public void init(ZenModeTile tile) { - mTile = tile; - mHost = mTile.getHost(); - mContext = getContext(); - mController = mHost.getZenModeController(); - - final ImageView close = (ImageView) findViewById(android.R.id.button1); - close.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - mTile.showDetail(false); - } - }); - mSwitch = (Switch) findViewById(android.R.id.checkbox); - mSwitch.setOnCheckedChangeListener(new OnCheckedChangeListener() { - @Override - public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { - mController.setZen(isChecked); - } - }); - mSwitch.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - final boolean isChecked = mSwitch.isChecked(); - mController.setZen(isChecked); - if (!isChecked) { - mTile.showDetail(false); - } - } - }); - - final View moreSettings = findViewById(android.R.id.button2); - moreSettings.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - mHost.startSettingsActivity(ZEN_SETTINGS); - mTile.showDetail(false); - } - }); - final ListView conditions = (ListView) findViewById(android.R.id.content); - mAdapter = new ConditionAdapter(mContext); - conditions.setAdapter(mAdapter); - mAdapter.add(updateTimeCondition()); - - updateZen(mController.isZen()); - } - - private Condition updateTimeCondition() { - final int minutes = MINUTES[mMinutesIndex]; - final long millis = System.currentTimeMillis() + minutes * 60 * 1000; - final Uri id = new Uri.Builder().scheme(Condition.SCHEME).authority("android") - .appendPath("countdown").appendPath(Long.toString(millis)).build(); - final int num = minutes < 60 ? minutes : minutes / 60; - final String units = minutes < 60 ? "minutes" : minutes == 60 ? "hour" : "hours"; - return new Condition(id, "For " + num + " " + units, "", "", 0, Condition.STATE_TRUE, - Condition.FLAG_RELEVANT_NOW); - } - - private void editTimeCondition(int delta) { - final int i = mMinutesIndex + delta; - if (i < 0 || i >= MINUTES.length) return; - mMinutesIndex = i; - mAdapter.remove(mAdapter.getItem(0)); - final Condition c = updateTimeCondition(); - mAdapter.insert(c, 0); - select(c); - } - - private void select(Condition condition) { - mController.select(condition); - } - - private void updateZen(boolean zen) { - mHandler.obtainMessage(H.UPDATE_ZEN, zen ? 1 : 0, 0).sendToTarget(); - } - - private void updateConditions(Condition[] conditions) { - if (conditions == null) return; - mHandler.obtainMessage(H.UPDATE_CONDITIONS, conditions).sendToTarget(); - } - - private void handleUpdateZen(boolean zen) { - mSwitch.setChecked(zen); - } - - private void handleUpdateConditions(Condition[] conditions) { - for (int i = mAdapter.getCount() - 1; i > 0; i--) { - mAdapter.remove(mAdapter.getItem(i)); - } - for (Condition condition : conditions) { - mAdapter.add(condition); - } - } - - @Override - protected void onAttachedToWindow() { - super.onAttachedToWindow(); - mController.addCallback(mCallback); - mController.requestConditions(true); - } - - @Override - protected void onDetachedFromWindow() { - super.onDetachedFromWindow(); - mController.removeCallback(mCallback); - mController.requestConditions(false); - } - - private final class H extends Handler { - private static final int UPDATE_ZEN = 1; - private static final int UPDATE_CONDITIONS = 2; - - public H() { - super(Looper.getMainLooper()); - } - - @Override - public void handleMessage(Message msg) { - if (msg.what == UPDATE_ZEN) { - handleUpdateZen(msg.arg1 == 1); - } else if (msg.what == UPDATE_CONDITIONS) { - handleUpdateConditions((Condition[])msg.obj); - } - } - } - - private final ZenModeController.Callback mCallback = new ZenModeController.Callback() { - @Override - public void onZenChanged(boolean zen) { - updateZen(zen); - } - public void onConditionsChanged(Condition[] conditions) { - updateConditions(conditions); - } - }; - - private final class ConditionAdapter extends ArrayAdapter<Condition> { - private final LayoutInflater mInflater; - private final HashSet<RadioButton> mRadioButtons = new HashSet<RadioButton>(); - - public ConditionAdapter(Context context) { - super(context, 0); - mInflater = LayoutInflater.from(new ContextThemeWrapper(context, R.style.QSWhiteTheme)); - } - - @Override - public View getView(int position, View convertView, ViewGroup parent) { - final Condition condition = getItem(position); - final boolean enabled = condition.state == Condition.STATE_TRUE; - - final View row = convertView != null ? convertView : mInflater - .inflate(R.layout.qs_zen_mode_detail_condition, parent, false); - final RadioButton rb = (RadioButton) row.findViewById(android.R.id.checkbox); - mRadioButtons.add(rb); - rb.setEnabled(enabled); - rb.setOnCheckedChangeListener(new OnCheckedChangeListener() { - @Override - public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { - if (isChecked) { - for (RadioButton otherButton : mRadioButtons) { - if (otherButton == rb) continue; - otherButton.setChecked(false); - } - select(condition); - } - } - }); - final TextView title = (TextView) row.findViewById(android.R.id.title); - title.setText(condition.summary); - title.setEnabled(enabled); - title.setAlpha(enabled ? 1 : .5f); - final ImageView button1 = (ImageView) row.findViewById(android.R.id.button1); - button1.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View v) { - rb.setChecked(true); - editTimeCondition(-1); - } - }); - - final ImageView button2 = (ImageView) row.findViewById(android.R.id.button2); - button2.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View v) { - rb.setChecked(true); - editTimeCondition(1); - } - }); - title.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View v) { - rb.setChecked(true); - } - }); - if (position != 0) { - button1.setVisibility(View.GONE); - button2.setVisibility(View.GONE); - } - if (position == 0 && mRadioButtons.size() == 1) { - rb.setChecked(true); - } - return row; - } - } -} diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/ZenModeTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/ZenModeTile.java deleted file mode 100644 index bfa9c19..0000000 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/ZenModeTile.java +++ /dev/null @@ -1,87 +0,0 @@ -/* - * 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.qs.tiles; - -import android.content.Context; -import android.util.Log; -import android.view.ContextThemeWrapper; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; - -import com.android.systemui.R; -import com.android.systemui.qs.QSTile; -import com.android.systemui.statusbar.policy.ZenModeController; - -/** Quick settings tile: Zen mode **/ -public class ZenModeTile extends QSTile<QSTile.BooleanState> { - private final ZenModeController mController; - - public ZenModeTile(Host host) { - super(host); - mController = host.getZenModeController(); - } - - @Override - public View createDetailView(Context context, ViewGroup root) { - final Context themedContext = new ContextThemeWrapper(mContext, R.style.QSAccentTheme); - final ZenModeDetail v = (ZenModeDetail) LayoutInflater.from(themedContext) - .inflate(R.layout.qs_zen_mode_detail, root, false); - v.init(this); - return v; - } - - @Override - protected BooleanState newTileState() { - return new BooleanState(); - } - - @Override - public void setListening(boolean listening) { - if (listening) { - mController.addCallback(mCallback); - } else { - mController.removeCallback(mCallback); - } - } - - @Override - protected void handleClick() { - final boolean newZen = !mState.value; - mController.setZen(newZen); - if (newZen) { - showDetail(true); - } - } - - @Override - protected void handleUpdateState(BooleanState state, Object arg) { - final boolean zen = arg instanceof Boolean ? (Boolean)arg : mController.isZen(); - state.value = zen; - state.visible = true; - state.iconId = zen ? R.drawable.ic_qs_zen_on : R.drawable.ic_qs_zen_off; - state.label = mContext.getString(R.string.zen_mode_title); - } - - private final ZenModeController.Callback mCallback = new ZenModeController.Callback() { - @Override - public void onZenChanged(boolean zen) { - if (DEBUG) Log.d(TAG, "onZenChanged " + zen); - refreshState(zen); - } - }; -} diff --git a/packages/SystemUI/src/com/android/systemui/settings/BrightnessDialog.java b/packages/SystemUI/src/com/android/systemui/settings/BrightnessDialog.java index 27881c4..65e1cc6 100644 --- a/packages/SystemUI/src/com/android/systemui/settings/BrightnessDialog.java +++ b/packages/SystemUI/src/com/android/systemui/settings/BrightnessDialog.java @@ -72,8 +72,7 @@ public class BrightnessDialog extends Dialog implements window.setGravity(Gravity.TOP); WindowManager.LayoutParams lp = window.getAttributes(); // Offset from the top - lp.y = getContext().getResources().getDimensionPixelOffset( - com.android.internal.R.dimen.volume_panel_top); + lp.y = getContext().getResources().getDimensionPixelOffset(R.dimen.volume_panel_top); lp.type = WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY; lp.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java index e3dac4a..dcd187c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java @@ -149,6 +149,7 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView super.onFinishInflate(); mBackgroundNormal = (NotificationBackgroundView) findViewById(R.id.backgroundNormal); mBackgroundDimmed = (NotificationBackgroundView) findViewById(R.id.backgroundDimmed); + updateBackground(); updateBackgroundResources(); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/AlphaImageView.java b/packages/SystemUI/src/com/android/systemui/statusbar/AlphaImageView.java new file mode 100644 index 0000000..06dc4e6 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/AlphaImageView.java @@ -0,0 +1,48 @@ +/* + * 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; + +import android.content.Context; +import android.util.AttributeSet; +import android.widget.ImageView; + +/** + * An ImageView which does not have overlapping renderings commands and therefore does not need a + * layer when alpha is changed. + */ +public class AlphaImageView extends ImageView { + public AlphaImageView(Context context) { + super(context); + } + + public AlphaImageView(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public AlphaImageView(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + } + + public AlphaImageView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + } + + @Override + public boolean hasOverlappingRendering() { + return false; + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java index f4db625..fbe76f9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java @@ -48,6 +48,7 @@ import android.provider.Settings; import android.service.dreams.DreamService; import android.service.dreams.IDreamManager; import android.service.notification.NotificationListenerService; +import android.service.notification.NotificationListenerService.Ranking; import android.service.notification.StatusBarNotification; import android.text.TextUtils; import android.util.Log; @@ -77,11 +78,12 @@ import com.android.systemui.R; import com.android.systemui.RecentsComponent; import com.android.systemui.SearchPanelView; import com.android.systemui.SystemUI; +import com.android.systemui.statusbar.NotificationData.Entry; import com.android.systemui.statusbar.phone.KeyguardTouchDelegate; import com.android.systemui.statusbar.stack.NotificationStackScrollLayout; import java.util.ArrayList; -import java.util.Arrays; +import java.util.Collections; import java.util.Locale; import static com.android.keyguard.KeyguardHostView.OnDismissAction; @@ -91,7 +93,7 @@ public abstract class BaseStatusBar extends SystemUI implements public static final String TAG = "StatusBar"; public static final boolean DEBUG = false; public static final boolean MULTIUSER_DEBUG = false; - private static final boolean USE_NOTIFICATION_LISTENER = false; + private static final boolean USE_NOTIFICATION_LISTENER = true; protected static final int MSG_SHOW_RECENT_APPS = 1019; protected static final int MSG_HIDE_RECENT_APPS = 1020; @@ -194,7 +196,7 @@ public abstract class BaseStatusBar extends SystemUI implements mContext.getContentResolver(), Settings.Global.DEVICE_PROVISIONED, 0); if (provisioned != mDeviceProvisioned) { mDeviceProvisioned = provisioned; - updateNotificationIcons(); + updateNotifications(); } final int mode = Settings.Global.getInt(mContext.getContentResolver(), Settings.Global.ZEN_MODE, Settings.Global.ZEN_MODE_OFF); @@ -209,7 +211,7 @@ public abstract class BaseStatusBar extends SystemUI implements // so we just dump our cache ... mUsersAllowingPrivateNotifications.clear(); // ... and refresh all the notifications - updateNotificationIcons(); + updateNotifications(); } }; @@ -280,11 +282,12 @@ public abstract class BaseStatusBar extends SystemUI implements public void onListenerConnected() { if (DEBUG) Log.d(TAG, "onListenerConnected"); final StatusBarNotification[] notifications = getActiveNotifications(); + final Ranking currentRanking = getCurrentRanking(); mHandler.post(new Runnable() { @Override public void run() { for (StatusBarNotification sbn : notifications) { - addNotificationInternal(sbn); + addNotificationInternal(sbn, currentRanking); } } }); @@ -293,13 +296,14 @@ public abstract class BaseStatusBar extends SystemUI implements @Override public void onNotificationPosted(final StatusBarNotification sbn) { if (DEBUG) Log.d(TAG, "onNotificationPosted: " + sbn); + final Ranking currentRanking = getCurrentRanking(); mHandler.post(new Runnable() { @Override public void run() { if (mNotificationData.findByKey(sbn.getKey()) != null) { - updateNotificationInternal(sbn); + updateNotificationInternal(sbn, currentRanking); } else { - addNotificationInternal(sbn); + addNotificationInternal(sbn, currentRanking); } } }); @@ -308,10 +312,24 @@ public abstract class BaseStatusBar extends SystemUI implements @Override public void onNotificationRemoved(final StatusBarNotification sbn) { if (DEBUG) Log.d(TAG, "onNotificationRemoved: " + sbn); + final Ranking currentRanking = getCurrentRanking(); mHandler.post(new Runnable() { @Override public void run() { - removeNotificationInternal(sbn.getKey()); + removeNotificationInternal(sbn.getKey(), currentRanking); + } + }); + } + + @Override + public void onNotificationRankingUpdate() { + if (DEBUG) Log.d(TAG, "onRankingUpdate"); + final Ranking currentRanking = getCurrentRanking(); + mHandler.post(new Runnable() { + @Override + public void run() { + mNotificationData.updateRanking(currentRanking); + updateNotifications(); } }); } @@ -1113,19 +1131,13 @@ public abstract class BaseStatusBar extends SystemUI implements } } - protected StatusBarNotification removeNotificationViews(String key) { - NotificationData.Entry entry = mNotificationData.remove(key); + protected StatusBarNotification removeNotificationViews(String key, Ranking ranking) { + NotificationData.Entry entry = mNotificationData.remove(key, ranking); if (entry == null) { Log.w(TAG, "removeNotification for unknown key: " + key); return null; } - // Remove the expanded view. - ViewGroup rowParent = (ViewGroup)entry.row.getParent(); - if (rowParent != null) rowParent.removeView(entry.row); - updateRowStates(); - updateNotificationIcons(); - updateSpeedBump(); - + updateNotifications(); return entry.notification; } @@ -1159,35 +1171,17 @@ public abstract class BaseStatusBar extends SystemUI implements return entry; } - protected void addNotificationViews(NotificationData.Entry entry) { + protected void addNotificationViews(Entry entry, Ranking ranking) { if (entry == null) { return; } // Add the expanded view and icon. - int pos = mNotificationData.add(entry); - if (DEBUG) { - Log.d(TAG, "addNotificationViews: added at " + pos); - } - updateRowStates(); - updateNotificationIcons(); - updateSpeedBump(); - } - - protected void updateSpeedBump() { - int n = mNotificationData.size(); - int speedBumpIndex = -1; - for (int i = n-1; i >= 0; i--) { - NotificationData.Entry entry = mNotificationData.get(i); - if (entry.row.getVisibility() != View.GONE && speedBumpIndex == -1 - && entry.row.isBelowSpeedBump() ) { - speedBumpIndex = n - 1 - i; - } - } - mStackScroller.updateSpeedBumpIndex(speedBumpIndex); + mNotificationData.add(entry, ranking); + updateNotifications(); } - private void addNotificationViews(StatusBarNotification notification) { - addNotificationViews(createNotificationViews(notification)); + private void addNotificationViews(StatusBarNotification notification, Ranking ranking) { + addNotificationViews(createNotificationViews(notification), ranking); } /** @@ -1201,17 +1195,17 @@ public abstract class BaseStatusBar extends SystemUI implements protected void updateRowStates() { int maxKeyguardNotifications = getMaxKeyguardNotifications(); mKeyguardIconOverflowContainer.getIconsView().removeAllViews(); - int n = mNotificationData.size(); + final int N = mNotificationData.size(); int visibleNotifications = 0; boolean onKeyguard = mState == StatusBarState.KEYGUARD; - for (int i = n-1; i >= 0; i--) { + for (int i = 0; i < N; i++) { NotificationData.Entry entry = mNotificationData.get(i); if (onKeyguard) { entry.row.setExpansionDisabled(true); } else { entry.row.setExpansionDisabled(false); if (!entry.row.isUserLocked()) { - boolean top = (i == n-1); + boolean top = (i == 0); entry.row.setSystemExpanded(top); } } @@ -1238,6 +1232,9 @@ public abstract class BaseStatusBar extends SystemUI implements } else { mKeyguardIconOverflowContainer.setVisibility(View.GONE); } + // Move overflow container to last position. + mStackScroller.changeViewPosition(mKeyguardIconOverflowContainer, + mStackScroller.getChildCount() - 1); } private boolean shouldShowOnKeyguard(StatusBarNotification sbn) { @@ -1247,46 +1244,42 @@ public abstract class BaseStatusBar extends SystemUI implements protected void setZenMode(int mode) { if (!isDeviceProvisioned()) return; mZenMode = mode; - updateNotificationIcons(); + updateNotifications(); } protected abstract void haltTicker(); protected abstract void setAreThereNotifications(); - protected abstract void updateNotificationIcons(); + protected abstract void updateNotifications(); protected abstract void tick(StatusBarNotification n, boolean firstTime); protected abstract void updateExpandedViewPos(int expandedPosition); protected abstract boolean shouldDisableNavbarGestures(); - protected boolean isTopNotification(ViewGroup parent, NotificationData.Entry entry) { - return parent != null && parent.indexOfChild(entry.row) == 0; - } - - @Override public void addNotification(StatusBarNotification notification) { if (!USE_NOTIFICATION_LISTENER) { - addNotificationInternal(notification); + addNotificationInternal(notification, null); } } - public abstract void addNotificationInternal(StatusBarNotification notification); + public abstract void addNotificationInternal(StatusBarNotification notification, + Ranking ranking); @Override public void removeNotification(String key) { if (!USE_NOTIFICATION_LISTENER) { - removeNotificationInternal(key); + removeNotificationInternal(key, null); } } - protected abstract void removeNotificationInternal(String key); + protected abstract void removeNotificationInternal(String key, Ranking ranking); public void updateNotification(StatusBarNotification notification) { if (!USE_NOTIFICATION_LISTENER) { - updateNotificationInternal(notification); + updateNotificationInternal(notification, null); } } - public void updateNotificationInternal(StatusBarNotification notification) { + public void updateNotificationInternal(StatusBarNotification notification, Ranking ranking) { if (DEBUG) Log.d(TAG, "updateNotification(" + notification + ")"); final NotificationData.Entry oldEntry = mNotificationData.findByKey(notification.getKey()); @@ -1358,18 +1351,12 @@ public abstract class BaseStatusBar extends SystemUI implements && oldPublicContentView.getPackage().equals(publicContentView.getPackage()) && oldPublicContentView.getLayoutId() == publicContentView.getLayoutId()); - ViewGroup rowParent = (ViewGroup) oldEntry.row.getParent(); - boolean orderUnchanged = - notification.getNotification().when == oldNotification.getNotification().when - && notification.getScore() == oldNotification.getScore(); - // score now encompasses/supersedes isOngoing() boolean updateTicker = notification.getNotification().tickerText != null && !TextUtils.equals(notification.getNotification().tickerText, oldEntry.notification.getNotification().tickerText); - boolean isTopAnyway = isTopNotification(rowParent, oldEntry); - if (contentsUnchanged && bigContentsUnchanged && headsUpContentsUnchanged && publicUnchanged - && (orderUnchanged || isTopAnyway)) { + if (contentsUnchanged && bigContentsUnchanged && headsUpContentsUnchanged + && publicUnchanged) { if (DEBUG) Log.d(TAG, "reusing notification for key: " + notification.getKey()); oldEntry.notification = notification; try { @@ -1397,22 +1384,20 @@ public abstract class BaseStatusBar extends SystemUI implements handleNotificationError(notification, "Couldn't update icon: " + ic); return; } - updateRowStates(); - updateSpeedBump(); + mNotificationData.updateRanking(ranking); + updateNotifications(); } catch (RuntimeException e) { // It failed to add cleanly. Log, and remove the view from the panel. Log.w(TAG, "Couldn't reapply views for package " + contentView.getPackage(), e); - removeNotificationViews(notification.getKey()); - addNotificationViews(notification); + removeNotificationViews(notification.getKey(), ranking); + addNotificationViews(notification, ranking); } } else { if (DEBUG) Log.d(TAG, "not reusing notification for key: " + notification.getKey()); if (DEBUG) Log.d(TAG, "contents was " + (contentsUnchanged ? "unchanged" : "changed")); - if (DEBUG) Log.d(TAG, "order was " + (orderUnchanged ? "unchanged" : "changed")); - if (DEBUG) Log.d(TAG, "notification is " + (isTopAnyway ? "top" : "not top")); - removeNotificationViews(notification.getKey()); - addNotificationViews(notification); // will also replace the heads up + removeNotificationViews(notification.getKey(), ranking); + addNotificationViews(notification, ranking); // will also replace the heads up final NotificationData.Entry newEntry = mNotificationData.findByKey( notification.getKey()); final boolean userChangedExpansion = oldEntry.row.hasUserChangedExpansion(); @@ -1559,5 +1544,12 @@ public abstract class BaseStatusBar extends SystemUI implements mWindowManager.removeViewImmediate(mSearchPanelView); } mContext.unregisterReceiver(mBroadcastReceiver); + if (USE_NOTIFICATION_LISTENER) { + try { + mNotificationListener.unregisterAsSystemService(); + } catch (RemoteException e) { + // Ignore. + } + } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/FlingAnimationUtils.java b/packages/SystemUI/src/com/android/systemui/statusbar/FlingAnimationUtils.java index 7d576cb..5f1325b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/FlingAnimationUtils.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/FlingAnimationUtils.java @@ -18,6 +18,7 @@ package com.android.systemui.statusbar; import android.animation.ValueAnimator; import android.content.Context; +import android.view.ViewPropertyAnimator; import android.view.animation.AnimationUtils; import android.view.animation.Interpolator; import android.view.animation.PathInterpolator; @@ -46,6 +47,8 @@ public class FlingAnimationUtils { private float mMaxLengthSeconds; private float mHighVelocityPxPerSecond; + private AnimatorProperties mAnimatorProperties = new AnimatorProperties(); + public FlingAnimationUtils(Context ctx, float maxLengthSeconds) { mMaxLengthSeconds = maxLengthSeconds; mLinearOutSlowIn = new PathInterpolator(0, 0, LINEAR_OUT_SLOW_IN_X2, 1); @@ -80,18 +83,59 @@ public class FlingAnimationUtils { * @param currValue the current value * @param endValue the end value of the animator * @param velocity the current velocity of the motion + */ + public void apply(ViewPropertyAnimator animator, float currValue, float endValue, + float velocity) { + apply(animator, currValue, endValue, velocity, Math.abs(endValue - currValue)); + } + + /** + * Applies the interpolator and length to the animator, such that the fling animation is + * consistent with the finger motion. + * + * @param animator the animator to apply + * @param currValue the current value + * @param endValue the end value of the animator + * @param velocity the current velocity of the motion * @param maxDistance the maximum distance for this interaction; the maximum animation length * gets multiplied by the ratio between the actual distance and this value */ public void apply(ValueAnimator animator, float currValue, float endValue, float velocity, float maxDistance) { + AnimatorProperties properties = getProperties(currValue, endValue, velocity, + maxDistance); + animator.setDuration(properties.duration); + animator.setInterpolator(properties.interpolator); + } + + /** + * Applies the interpolator and length to the animator, such that the fling animation is + * consistent with the finger motion. + * + * @param animator the animator to apply + * @param currValue the current value + * @param endValue the end value of the animator + * @param velocity the current velocity of the motion + * @param maxDistance the maximum distance for this interaction; the maximum animation length + * gets multiplied by the ratio between the actual distance and this value + */ + public void apply(ViewPropertyAnimator animator, float currValue, float endValue, + float velocity, float maxDistance) { + AnimatorProperties properties = getProperties(currValue, endValue, velocity, + maxDistance); + animator.setDuration(properties.duration); + animator.setInterpolator(properties.interpolator); + } + + private AnimatorProperties getProperties(float currValue, + float endValue, float velocity, float maxDistance) { float maxLengthSeconds = (float) (mMaxLengthSeconds * Math.sqrt(Math.abs(endValue - currValue) / maxDistance)); float diff = Math.abs(endValue - currValue); float velAbs = Math.abs(velocity); float durationSeconds = LINEAR_OUT_SLOW_IN_START_GRADIENT * diff / velAbs; if (durationSeconds <= maxLengthSeconds) { - animator.setInterpolator(mLinearOutSlowIn); + mAnimatorProperties.interpolator = mLinearOutSlowIn; } else if (velAbs >= mMinVelocityPxPerSecond) { // Cross fade between fast-out-slow-in and linear interpolator with current velocity. @@ -100,14 +144,15 @@ public class FlingAnimationUtils { = new VelocityInterpolator(durationSeconds, velAbs, diff); InterpolatorInterpolator superInterpolator = new InterpolatorInterpolator( velocityInterpolator, mLinearOutSlowIn, mLinearOutSlowIn); - animator.setInterpolator(superInterpolator); + mAnimatorProperties.interpolator = superInterpolator; } else { // Just use a normal interpolator which doesn't take the velocity into account. durationSeconds = maxLengthSeconds; - animator.setInterpolator(mFastOutSlowIn); + mAnimatorProperties.interpolator = mFastOutSlowIn; } - animator.setDuration((long) (durationSeconds * 1000)); + mAnimatorProperties.duration = (long) (durationSeconds * 1000); + return mAnimatorProperties; } /** @@ -124,6 +169,34 @@ public class FlingAnimationUtils { */ public void applyDismissing(ValueAnimator animator, float currValue, float endValue, float velocity, float maxDistance) { + AnimatorProperties properties = getDismissingProperties(currValue, endValue, velocity, + maxDistance); + animator.setDuration(properties.duration); + animator.setInterpolator(properties.interpolator); + } + + /** + * Applies the interpolator and length to the animator, such that the fling animation is + * consistent with the finger motion for the case when the animation is making something + * disappear. + * + * @param animator the animator to apply + * @param currValue the current value + * @param endValue the end value of the animator + * @param velocity the current velocity of the motion + * @param maxDistance the maximum distance for this interaction; the maximum animation length + * gets multiplied by the ratio between the actual distance and this value + */ + public void applyDismissing(ViewPropertyAnimator animator, float currValue, float endValue, + float velocity, float maxDistance) { + AnimatorProperties properties = getDismissingProperties(currValue, endValue, velocity, + maxDistance); + animator.setDuration(properties.duration); + animator.setInterpolator(properties.interpolator); + } + + private AnimatorProperties getDismissingProperties(float currValue, float endValue, + float velocity, float maxDistance) { float maxLengthSeconds = (float) (mMaxLengthSeconds * Math.pow(Math.abs(endValue - currValue) / maxDistance, 0.5f)); float diff = Math.abs(endValue - currValue); @@ -135,7 +208,7 @@ public class FlingAnimationUtils { Interpolator mLinearOutFasterIn = new PathInterpolator(0, 0, 1, y2); float durationSeconds = startGradient * diff / velAbs; if (durationSeconds <= maxLengthSeconds) { - animator.setInterpolator(mLinearOutFasterIn); + mAnimatorProperties.interpolator = mLinearOutFasterIn; } else if (velAbs >= mMinVelocityPxPerSecond) { // Cross fade between linear-out-faster-in and linear interpolator with current @@ -145,14 +218,15 @@ public class FlingAnimationUtils { = new VelocityInterpolator(durationSeconds, velAbs, diff); InterpolatorInterpolator superInterpolator = new InterpolatorInterpolator( velocityInterpolator, mLinearOutFasterIn, mLinearOutSlowIn); - animator.setInterpolator(superInterpolator); + mAnimatorProperties.interpolator = superInterpolator; } else { // Just use a normal interpolator which doesn't take the velocity into account. durationSeconds = maxLengthSeconds; - animator.setInterpolator(mFastOutLinearIn); + mAnimatorProperties.interpolator = mFastOutLinearIn; } - animator.setDuration((long) (durationSeconds * 1000)); + mAnimatorProperties.duration = (long) (durationSeconds * 1000); + return mAnimatorProperties; } /** @@ -221,4 +295,10 @@ public class FlingAnimationUtils { return time * mVelocity / mDiff; } } + + private static class AnimatorProperties { + Interpolator interpolator; + long duration; + } + } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/InterceptedNotifications.java b/packages/SystemUI/src/com/android/systemui/statusbar/InterceptedNotifications.java index 466c405..24da5c2 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/InterceptedNotifications.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/InterceptedNotifications.java @@ -49,7 +49,7 @@ public class InterceptedNotifications { for (int i = 0; i < n; i++) { final StatusBarNotification sbn = mIntercepted.valueAt(i); sbn.getNotification().extras.putBoolean(EXTRA_INTERCEPT, false); - mBar.addNotificationInternal(sbn); + mBar.addNotificationInternal(sbn, null); } mIntercepted.clear(); updateSyntheticNotification(); @@ -87,7 +87,7 @@ public class InterceptedNotifications { private void updateSyntheticNotification() { if (mIntercepted.isEmpty()) { if (mSynKey != null) { - mBar.removeNotificationInternal(mSynKey); + mBar.removeNotificationInternal(mSynKey, null); mSynKey = null; } return; @@ -106,9 +106,9 @@ public class InterceptedNotifications { mBar.getCurrentUserHandle()); if (mSynKey == null) { mSynKey = sbn.getKey(); - mBar.addNotificationInternal(sbn); + mBar.addNotificationInternal(sbn, null); } else { - mBar.updateNotificationInternal(sbn); + mBar.updateNotificationInternal(sbn, null); } final NotificationData.Entry entry = mBar.mNotificationData.findByKey(mSynKey); entry.row.setOnClickListener(mSynClickListener); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java index 5696246..d829ac0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java @@ -16,15 +16,19 @@ package com.android.systemui.statusbar; +import android.app.Notification; +import android.service.notification.NotificationListenerService.Ranking; import android.service.notification.StatusBarNotification; import android.view.View; -import android.widget.ImageView; import java.util.ArrayList; +import java.util.Collections; import java.util.Comparator; /** * The list of currently displaying notifications. + * + * TODO: Rename to NotificationList. */ public class NotificationData { public static final class Entry { @@ -34,7 +38,6 @@ public class NotificationData { public ExpandableNotificationRow row; // the outer expanded view public View expanded; // the inflated RemoteViews public View expandedPublic; // for insecure lockscreens - public ImageView largeIcon; public View expandedBig; private boolean interruption; public Entry() {} @@ -64,18 +67,23 @@ public class NotificationData { } private final ArrayList<Entry> mEntries = new ArrayList<Entry>(); - private final Comparator<Entry> mEntryCmp = new Comparator<Entry>() { - // sort first by score, then by when + private Ranking mRanking; + private final Comparator<Entry> mRankingComparator = new Comparator<Entry>() { + @Override public int compare(Entry a, Entry b) { + if (mRanking != null) { + return mRanking.getRank(a.key) - mRanking.getRank(b.key); + } + final StatusBarNotification na = a.notification; final StatusBarNotification nb = b.notification; - int d = na.getScore() - nb.getScore(); + int d = nb.getScore() - na.getScore(); if (a.interruption != b.interruption) { - return a.interruption ? 1 : -1; + return a.interruption ? -1 : 1; } else if (d != 0) { return d; } else { - return (int) (na.getNotification().when - nb.getNotification().when); + return (int) (nb.getNotification().when - na.getNotification().when); } } }; @@ -97,26 +105,47 @@ public class NotificationData { return null; } - public int add(Entry entry) { - int i; - int N = mEntries.size(); - for (i = 0; i < N; i++) { - if (mEntryCmp.compare(mEntries.get(i), entry) > 0) { - break; - } - } - mEntries.add(i, entry); - return i; + public void add(Entry entry, Ranking ranking) { + mEntries.add(entry); + updateRankingAndSort(ranking); } - public Entry remove(String key) { + public Entry remove(String key, Ranking ranking) { Entry e = findByKey(key); - if (e != null) { - mEntries.remove(e); + if (e == null) { + return null; } + mEntries.remove(e); + updateRankingAndSort(ranking); return e; } + public void updateRanking(Ranking ranking) { + updateRankingAndSort(ranking); + } + + public boolean isAmbient(String key) { + // TODO: Remove when switching to NotificationListener. + if (mRanking == null) { + for (Entry entry : mEntries) { + if (key.equals(entry.key)) { + return entry.notification.getNotification().priority == + Notification.PRIORITY_MIN; + } + } + } else { + return mRanking.isAmbient(key); + } + return false; + } + + private void updateRankingAndSort(Ranking ranking) { + if (ranking != null) { + mRanking = ranking; + } + Collections.sort(mEntries, mRankingComparator); + } + /** * Return whether there are any visible items (i.e. items without an error). */ diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java index 714ad06..994b329 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java @@ -23,7 +23,6 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.pm.PackageManager; -import android.os.PowerManager; import android.os.RemoteException; import android.os.UserHandle; import android.provider.MediaStore; @@ -33,26 +32,23 @@ import android.view.View; import android.view.accessibility.AccessibilityManager; import android.widget.FrameLayout; import android.widget.ImageView; - import com.android.systemui.R; /** * Implementation for the bottom area of the Keyguard, including camera/phone affordance and status * text. */ -public class KeyguardBottomAreaView extends FrameLayout - implements SwipeAffordanceView.AffordanceListener, +public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickListener, UnlockMethodCache.OnUnlockMethodChangedListener { final static String TAG = "PhoneStatusBar/KeyguardBottomAreaView"; private static final Intent PHONE_INTENT = new Intent(Intent.ACTION_DIAL); - private SwipeAffordanceView mCameraButton; - private SwipeAffordanceView mPhoneButton; + private ImageView mCameraImageView; + private ImageView mPhoneImageView; private ImageView mLockIcon; - private PowerManager mPowerManager; private ActivityStarter mActivityStarter; private UnlockMethodCache mUnlockMethodCache; @@ -76,11 +72,9 @@ public class KeyguardBottomAreaView extends FrameLayout @Override protected void onFinishInflate() { super.onFinishInflate(); - mCameraButton = (SwipeAffordanceView) findViewById(R.id.camera_button); - mPhoneButton = (SwipeAffordanceView) findViewById(R.id.phone_button); + mCameraImageView = (ImageView) findViewById(R.id.camera_button); + mPhoneImageView = (ImageView) findViewById(R.id.phone_button); mLockIcon = (ImageView) findViewById(R.id.lock_icon); - mCameraButton.setAffordanceListener(this); - mPhoneButton.setAffordanceListener(this); watchForDevicePolicyChanges(); watchForAccessibilityChanges(); updateCameraVisibility(); @@ -88,7 +82,6 @@ public class KeyguardBottomAreaView extends FrameLayout mUnlockMethodCache = UnlockMethodCache.getInstance(getContext()); mUnlockMethodCache.addListener(this); updateTrust(); - mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE); } public void setActivityStarter(ActivityStarter activityStarter) { @@ -97,12 +90,12 @@ public class KeyguardBottomAreaView extends FrameLayout private void updateCameraVisibility() { boolean visible = !isCameraDisabledByDpm(); - mCameraButton.setVisibility(visible ? View.VISIBLE : View.GONE); + mCameraImageView.setVisibility(visible ? View.VISIBLE : View.GONE); } private void updatePhoneVisibility() { boolean visible = isPhoneVisible(); - mPhoneButton.setVisibility(visible ? View.VISIBLE : View.GONE); + mPhoneImageView.setVisibility(visible ? View.VISIBLE : View.GONE); } private boolean isPhoneVisible() { @@ -162,33 +155,31 @@ public class KeyguardBottomAreaView extends FrameLayout } private void enableAccessibility(boolean touchExplorationEnabled) { - mCameraButton.enableAccessibility(touchExplorationEnabled); - mPhoneButton.enableAccessibility(touchExplorationEnabled); + mCameraImageView.setOnClickListener(touchExplorationEnabled ? this : null); + mCameraImageView.setClickable(touchExplorationEnabled); + mPhoneImageView.setOnClickListener(touchExplorationEnabled ? this : null); + mPhoneImageView.setClickable(touchExplorationEnabled); } - private void launchCamera() { + @Override + public void onClick(View v) { + if (v == mCameraImageView) { + launchCamera(); + } else if (v == mPhoneImageView) { + launchPhone(); + } + } + + public void launchCamera() { mContext.startActivityAsUser( new Intent(MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE), UserHandle.CURRENT); } - private void launchPhone() { + public void launchPhone() { mActivityStarter.startActivity(PHONE_INTENT); } - @Override - public void onUserActivity(long when) { - mPowerManager.userActivity(when, false); - } - - @Override - public void onActionPerformed(SwipeAffordanceView view) { - if (view == mCameraButton) { - launchCamera(); - } else if (view == mPhoneButton) { - launchPhone(); - } - } @Override protected void onVisibilityChanged(View changedView, int visibility) { @@ -208,6 +199,18 @@ public class KeyguardBottomAreaView extends FrameLayout mLockIcon.setImageResource(iconRes); } + public ImageView getPhoneImageView() { + return mPhoneImageView; + } + + public ImageView getCameraImageView() { + return mCameraImageView; + } + + public ImageView getLockIcon() { + return mLockIcon; + } + @Override public void onMethodSecureChanged(boolean methodSecure) { updateTrust(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java index 2bb80bf..b7a7b0a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java @@ -56,12 +56,16 @@ public class KeyguardBouncer { public void show() { ensureView(); + if (mRoot.getVisibility() == View.VISIBLE) { + return; + } // Try to dismiss the Keyguard. If no security pattern is set, this will dismiss the whole // Keyguard. If we need to authenticate, show the bouncer. if (!mKeyguardView.dismiss()) { mRoot.setVisibility(View.VISIBLE); mKeyguardView.onResume(); + mKeyguardView.startAppearAnimation(); } } @@ -109,6 +113,10 @@ public class KeyguardBouncer { return mRoot != null && mRoot.getVisibility() == View.VISIBLE; } + public void prepare() { + ensureView(); + } + private void ensureView() { if (mRoot == null) { inflateView(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardIndicationTextView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardIndicationTextView.java index 769b1b1..c5c3fff 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardIndicationTextView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardIndicationTextView.java @@ -17,7 +17,9 @@ package com.android.systemui.statusbar.phone; import android.content.Context; +import android.text.TextUtils; import android.util.AttributeSet; +import android.view.View; import android.widget.TextView; /** @@ -50,7 +52,12 @@ public class KeyguardIndicationTextView extends TextView { public void switchIndication(CharSequence text) { // TODO: Animation, make sure that we will show one indication long enough. - setText(text); + if (TextUtils.isEmpty(text)) { + setVisibility(View.INVISIBLE); + } else { + setVisibility(View.VISIBLE); + setText(text); + } } /** diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardPageSwipeHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardPageSwipeHelper.java new file mode 100644 index 0000000..b4f4865 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardPageSwipeHelper.java @@ -0,0 +1,398 @@ +/* + * 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.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.ValueAnimator; +import android.content.Context; +import android.os.PowerManager; +import android.view.MotionEvent; +import android.view.VelocityTracker; +import android.view.View; +import android.view.ViewConfiguration; +import android.view.ViewPropertyAnimator; +import android.view.animation.AnimationUtils; +import android.view.animation.Interpolator; +import com.android.systemui.R; +import com.android.systemui.statusbar.FlingAnimationUtils; + +import java.util.ArrayList; + +/** + * A touch handler of the Keyguard which is responsible for swiping the content left or right. + */ +public class KeyguardPageSwipeHelper { + + private static final float SWIPE_MAX_ICON_SCALE_AMOUNT = 2.0f; + private static final float SWIPE_RESTING_ALPHA_AMOUNT = 0.7f; + private final Context mContext; + + private FlingAnimationUtils mFlingAnimationUtils; + private Callback mCallback; + private int mTrackingPointer; + private VelocityTracker mVelocityTracker; + private boolean mSwipingInProgress; + private float mInitialTouchX; + private float mInitialTouchY; + private float mTranslation; + private float mTranslationOnDown; + private int mTouchSlop; + private int mMinTranslationAmount; + private int mMinFlingVelocity; + private PowerManager mPowerManager; + private final View mLeftIcon; + private final View mCenterIcon; + private final View mRightIcon; + private Interpolator mFastOutSlowIn; + private Animator mSwipeAnimator; + private boolean mCallbackCalled; + + KeyguardPageSwipeHelper(Callback callback, Context context) { + mContext = context; + mCallback = callback; + mLeftIcon = mCallback.getLeftIcon(); + mCenterIcon = mCallback.getCenterIcon(); + mRightIcon = mCallback.getRightIcon(); + updateIcon(mLeftIcon, 1.0f, SWIPE_RESTING_ALPHA_AMOUNT, false); + updateIcon(mCenterIcon, 1.0f, SWIPE_RESTING_ALPHA_AMOUNT, false); + updateIcon(mRightIcon, 1.0f, SWIPE_RESTING_ALPHA_AMOUNT, false); + mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE); + initDimens(); + } + + private void initDimens() { + final ViewConfiguration configuration = ViewConfiguration.get(mContext); + mTouchSlop = configuration.getScaledTouchSlop(); + mMinFlingVelocity = configuration.getScaledMinimumFlingVelocity(); + mMinTranslationAmount = mContext.getResources().getDimensionPixelSize( + R.dimen.keyguard_min_swipe_amount); + mFlingAnimationUtils = new FlingAnimationUtils(mContext, 0.4f); + mFastOutSlowIn = AnimationUtils.loadInterpolator(mContext, + android.R.interpolator.fast_out_slow_in); + } + + public boolean onTouchEvent(MotionEvent event) { + int pointerIndex = event.findPointerIndex(mTrackingPointer); + if (pointerIndex < 0) { + pointerIndex = 0; + mTrackingPointer = event.getPointerId(pointerIndex); + } + final float y = event.getY(pointerIndex); + final float x = event.getX(pointerIndex); + + switch (event.getActionMasked()) { + case MotionEvent.ACTION_DOWN: + if (mSwipingInProgress) { + cancelAnimations(); + } + mInitialTouchY = y; + mInitialTouchX = x; + mTranslationOnDown = mTranslation; + initVelocityTracker(); + trackMovement(event); + break; + + case MotionEvent.ACTION_POINTER_UP: + final int upPointer = event.getPointerId(event.getActionIndex()); + if (mTrackingPointer == upPointer) { + // gesture is ongoing, find a new pointer to track + final int newIndex = event.getPointerId(0) != upPointer ? 0 : 1; + final float newY = event.getY(newIndex); + final float newX = event.getX(newIndex); + mTrackingPointer = event.getPointerId(newIndex); + mInitialTouchY = newY; + mInitialTouchX = newX; + mTranslationOnDown = mTranslation; + } + break; + + case MotionEvent.ACTION_MOVE: + final float w = x - mInitialTouchX; + trackMovement(event); + if (((leftSwipePossible() && w > mTouchSlop) + || (rightSwipePossible() && w < -mTouchSlop)) + && Math.abs(w) > Math.abs(y - mInitialTouchY) + && !mSwipingInProgress) { + cancelAnimations(); + mInitialTouchY = y; + mInitialTouchX = x; + mTranslationOnDown = mTranslation; + mSwipingInProgress = true; + } + if (mSwipingInProgress) { + setTranslation(mTranslationOnDown + x - mInitialTouchX, false); + onUserActivity(event.getEventTime()); + } + break; + + case MotionEvent.ACTION_UP: + case MotionEvent.ACTION_CANCEL: + mTrackingPointer = -1; + trackMovement(event); + if (mSwipingInProgress) { + flingWithCurrentVelocity(); + } + if (mVelocityTracker != null) { + mVelocityTracker.recycle(); + mVelocityTracker = null; + } + break; + } + return true; + } + + private boolean rightSwipePossible() { + return mRightIcon.getVisibility() == View.VISIBLE; + } + + private boolean leftSwipePossible() { + return mLeftIcon.getVisibility() == View.VISIBLE; + } + + public boolean onInterceptTouchEvent(MotionEvent ev) { + return false; + } + + private void onUserActivity(long when) { + mPowerManager.userActivity(when, false); + } + + private void cancelAnimations() { + ArrayList<View> targetViews = mCallback.getTranslationViews(); + for (View target : targetViews) { + target.animate().cancel(); + } + View targetView = mTranslation > 0 ? mLeftIcon : mRightIcon; + targetView.animate().cancel(); + if (mSwipeAnimator != null) { + mSwipeAnimator.removeAllListeners(); + mSwipeAnimator.cancel(); + hideInactiveIcons(true); + } + } + + private void flingWithCurrentVelocity() { + float vel = getCurrentVelocity(); + + // We snap back if the current translation is not far enough + boolean snapBack = Math.abs(mTranslation) < mMinTranslationAmount; + + // or if the velocity is in the opposite direction. + boolean velIsInWrongDirection = vel * mTranslation < 0; + snapBack |= Math.abs(vel) > mMinFlingVelocity && velIsInWrongDirection; + vel = snapBack ^ velIsInWrongDirection ? 0 : vel; + fling(vel, snapBack); + } + + private void fling(float vel, final boolean snapBack) { + float target = mTranslation < 0 ? -mCallback.getPageWidth() : mCallback.getPageWidth(); + target = snapBack ? 0 : target; + + // translation Animation + startTranslationAnimations(vel, target); + + // animate left / right icon + startIconAnimation(vel, snapBack, target); + + ValueAnimator animator = ValueAnimator.ofFloat(mTranslation, target); + mFlingAnimationUtils.apply(animator, mTranslation, target, vel); + animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(ValueAnimator animation) { + mTranslation = (float) animation.getAnimatedValue(); + } + }); + animator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + mSwipeAnimator = null; + mSwipingInProgress = false; + if (!snapBack && !mCallbackCalled) { + + // ensure that the callback is called eventually + mCallback.onAnimationToSideStarted(mTranslation < 0); + mCallbackCalled = true; + } + } + }); + if (!snapBack) { + mCallbackCalled = false; + animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { + int frameNumber; + + @Override + public void onAnimationUpdate(ValueAnimator animation) { + if (frameNumber == 2 && !mCallbackCalled) { + + // we have to wait for the second frame for this call, + // until the render thread has definitely kicked in, to avoid a lag. + mCallback.onAnimationToSideStarted(mTranslation < 0); + mCallbackCalled = true; + } + frameNumber++; + } + }); + } else { + showAllIcons(true); + } + animator.start(); + mSwipeAnimator = animator; + } + + private void startTranslationAnimations(float vel, float target) { + ArrayList<View> targetViews = mCallback.getTranslationViews(); + for (View targetView : targetViews) { + ViewPropertyAnimator animator = targetView.animate(); + mFlingAnimationUtils.apply(animator, mTranslation, target, vel); + animator.translationX(target); + } + } + + private void startIconAnimation(float vel, boolean snapBack, float target) { + float scale = snapBack ? 1.0f : SWIPE_MAX_ICON_SCALE_AMOUNT; + float alpha = snapBack ? SWIPE_RESTING_ALPHA_AMOUNT : 1.0f; + View targetView = mTranslation > 0 + ? mLeftIcon + : mRightIcon; + if (targetView.getVisibility() == View.VISIBLE) { + ViewPropertyAnimator iconAnimator = targetView.animate(); + mFlingAnimationUtils.apply(iconAnimator, mTranslation, target, vel); + iconAnimator.scaleX(scale); + iconAnimator.scaleY(scale); + iconAnimator.alpha(alpha); + } + } + + private void setTranslation(float translation, boolean isReset) { + translation = rightSwipePossible() ? translation : Math.max(0, translation); + translation = leftSwipePossible() ? translation : Math.min(0, translation); + if (translation != mTranslation) { + ArrayList<View> translatedViews = mCallback.getTranslationViews(); + for (View view : translatedViews) { + view.setTranslationX(translation); + } + if (translation == 0.0f) { + boolean animate = !isReset; + showAllIcons(animate); + } else { + View targetView = translation > 0 ? mLeftIcon : mRightIcon; + float progress = Math.abs(translation) / mCallback.getPageWidth(); + progress = Math.min(progress, 1.0f); + float alpha = SWIPE_RESTING_ALPHA_AMOUNT * (1.0f - progress) + progress; + float scale = (1.0f - progress) + progress * SWIPE_MAX_ICON_SCALE_AMOUNT; + updateIcon(targetView, scale, alpha, false); + View otherView = translation < 0 ? mLeftIcon : mRightIcon; + if (mTranslation * translation <= 0) { + // The sign of the translation has changed so we need to hide the other icons + updateIcon(otherView, 0, 0, true); + updateIcon(mCenterIcon, 0, 0, true); + } + } + mTranslation = translation; + } + } + + private void showAllIcons(boolean animate) { + float scale = 1.0f; + float alpha = SWIPE_RESTING_ALPHA_AMOUNT; + updateIcon(mRightIcon, scale, alpha, animate); + updateIcon(mCenterIcon, scale, alpha, animate); + updateIcon(mLeftIcon, scale, alpha, animate); + } + + private void hideInactiveIcons(boolean animate){ + View otherView = mTranslation < 0 ? mLeftIcon : mRightIcon; + updateIcon(otherView, 0, 0, animate); + updateIcon(mCenterIcon, 0, 0, animate); + } + + private void updateIcon(View view, float scale, float alpha, boolean animate) { + if (view.getVisibility() != View.VISIBLE) { + return; + } + if (!animate) { + view.setAlpha(alpha); + view.setScaleX(scale); + view.setScaleY(scale); + // TODO: remove this invalidate once the property setters invalidate it properly + view.invalidate(); + } else { + if (view.getAlpha() != alpha || view.getScaleX() != scale) { + view.animate() + .setInterpolator(mFastOutSlowIn) + .alpha(alpha) + .scaleX(scale) + .scaleY(scale); + } + } + } + + private void trackMovement(MotionEvent event) { + if (mVelocityTracker != null) { + mVelocityTracker.addMovement(event); + } + } + + private void initVelocityTracker() { + if (mVelocityTracker != null) { + mVelocityTracker.recycle(); + } + mVelocityTracker = VelocityTracker.obtain(); + } + + private float getCurrentVelocity() { + if (mVelocityTracker == null) { + return 0; + } + mVelocityTracker.computeCurrentVelocity(1000); + return mVelocityTracker.getXVelocity(); + } + + public void onConfigurationChanged() { + initDimens(); + } + + public void reset() { + setTranslation(0.0f, true); + mSwipingInProgress = false; + } + + public boolean isSwipingInProgress() { + return mSwipingInProgress; + } + + public interface Callback { + + /** + * Notifies the callback when an animation to a side page was started. + * + * @param rightPage Is the page animated to the right page? + */ + void onAnimationToSideStarted(boolean rightPage); + + float getPageWidth(); + + ArrayList<View> getTranslationViews(); + + View getLeftIcon(); + + View getCenterIcon(); + + View getRightIcon(); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarTransitions.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarTransitions.java index c83b479..3753a72 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarTransitions.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarTransitions.java @@ -30,7 +30,6 @@ import com.android.systemui.statusbar.policy.KeyButtonView; public final class NavigationBarTransitions extends BarTransitions { - private static final float KEYGUARD_QUIESCENT_ALPHA = 0.5f; private static final int CONTENT_FADE_DURATION = 200; private final NavigationBarView mView; @@ -81,8 +80,6 @@ public final class NavigationBarTransitions extends BarTransitions { setKeyButtonViewQuiescentAlpha(mView.getRecentsButton(), alpha, animate); setKeyButtonViewQuiescentAlpha(mView.getMenuButton(), alpha, animate); - setKeyButtonViewQuiescentAlpha(mView.getSearchLight(), KEYGUARD_QUIESCENT_ALPHA, animate); - applyBackButtonQuiescentAlpha(mode, animate); // apply to lights out @@ -96,7 +93,6 @@ public final class NavigationBarTransitions extends BarTransitions { public void applyBackButtonQuiescentAlpha(int mode, boolean animate) { float backAlpha = 0; - backAlpha = maxVisibleQuiescentAlpha(backAlpha, mView.getSearchLight()); backAlpha = maxVisibleQuiescentAlpha(backAlpha, mView.getHomeButton()); backAlpha = maxVisibleQuiescentAlpha(backAlpha, mView.getRecentsButton()); backAlpha = maxVisibleQuiescentAlpha(backAlpha, mView.getMenuButton()); @@ -116,7 +112,6 @@ public final class NavigationBarTransitions extends BarTransitions { public void setContentVisible(boolean visible) { final float alpha = visible ? 1 : 0; fadeContent(mView.getBackButton(), alpha); - fadeContent(mView.getSearchLight(), alpha); } private void fadeContent(View v, float alpha) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java index 089757a..21842bf 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java @@ -133,16 +133,6 @@ public class NavigationBarView extends LinearLayout { } } - // simplified click handler to be used when device is in accessibility mode - private final OnClickListener mAccessibilityClickListener = new OnClickListener() { - @Override - public void onClick(View v) { - if (v.getId() == R.id.search_light) { - KeyguardTouchDelegate.getInstance(getContext()).showAssistant(); - } - } - }; - private final OnClickListener mImeSwitcherClickListener = new OnClickListener() { @Override public void onClick(View view) { @@ -246,11 +236,6 @@ public class NavigationBarView extends LinearLayout { return mCurrentView.findViewById(R.id.ime_switcher); } - // for when home is disabled, but search isn't - public View getSearchLight() { - return mCurrentView.findViewById(R.id.search_light); - } - private void getIcons(Resources res) { mBackIcon = res.getDrawable(R.drawable.ic_sysbar_back); mBackLandIcon = res.getDrawable(R.drawable.ic_sysbar_back_land); @@ -345,9 +330,6 @@ public class NavigationBarView extends LinearLayout { getHomeButton() .setVisibility(disableHome ? View.INVISIBLE : View.VISIBLE); getRecentsButton().setVisibility(disableRecent ? View.INVISIBLE : View.VISIBLE); - final boolean showSearch = disableHome && !disableSearch; - setVisibleOrGone(getSearchLight(), showSearch); - mBarTransitions.applyBackButtonQuiescentAlpha(mBarTransitions.getMode(), true /*animate*/); } @@ -402,38 +384,6 @@ public class NavigationBarView extends LinearLayout { mCurrentView = mRotatedViews[Surface.ROTATION_0]; getImeSwitchButton().setOnClickListener(mImeSwitcherClickListener); - - watchForAccessibilityChanges(); - } - - private void watchForAccessibilityChanges() { - final AccessibilityManager am = - (AccessibilityManager) getContext().getSystemService(Context.ACCESSIBILITY_SERVICE); - - // Set the initial state - enableAccessibility(am.isTouchExplorationEnabled()); - - // Watch for changes - am.addTouchExplorationStateChangeListener(new TouchExplorationStateChangeListener() { - @Override - public void onTouchExplorationStateChanged(boolean enabled) { - enableAccessibility(enabled); - } - }); - } - - private void enableAccessibility(boolean touchEnabled) { - Log.v(TAG, "touchEnabled:" + touchEnabled); - - // Add a touch handler or accessibility click listener for camera and search buttons - // for all view orientations. - final OnClickListener onClickListener = touchEnabled ? mAccessibilityClickListener : null; - for (int i = 0; i < mRotatedViews.length; i++) { - final View searchLight = mRotatedViews[i].findViewById(R.id.search_light); - if (searchLight != null) { - searchLight.setOnClickListener(onClickListener); - } - } } public boolean isVertical() { @@ -571,7 +521,6 @@ public class NavigationBarView extends LinearLayout { dumpButton(pw, "home", getHomeButton()); dumpButton(pw, "rcnt", getRecentsButton()); dumpButton(pw, "menu", getMenuButton()); - dumpButton(pw, "srch", getSearchLight()); pw.println(" }"); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java index f5252a3..838e5e3 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java @@ -21,6 +21,7 @@ import android.animation.AnimatorListenerAdapter; import android.animation.ObjectAnimator; import android.animation.ValueAnimator; import android.content.Context; +import android.content.res.Configuration; import android.util.AttributeSet; import android.view.MotionEvent; import android.view.VelocityTracker; @@ -40,10 +41,13 @@ import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.stack.NotificationStackScrollLayout; import com.android.systemui.statusbar.stack.StackStateAnimator; +import java.util.ArrayList; + public class NotificationPanelView extends PanelView implements ExpandableView.OnHeightChangedListener, ObservableScrollView.Listener, - View.OnClickListener { + View.OnClickListener, KeyguardPageSwipeHelper.Callback { + private KeyguardPageSwipeHelper mPageSwiper; PhoneStatusBar mStatusBar; private StatusBarHeaderView mHeader; private View mQsContainer; @@ -58,7 +62,7 @@ public class NotificationPanelView extends PanelView implements private int mTrackingPointer; private VelocityTracker mVelocityTracker; - private boolean mTracking; + private boolean mQsTracking; /** * Whether we are currently handling a motion gesture in #onInterceptTouchEvent, but haven't @@ -92,6 +96,11 @@ public class NotificationPanelView extends PanelView implements new KeyguardClockPositionAlgorithm(); private KeyguardClockPositionAlgorithm.Result mClockPositionResult = new KeyguardClockPositionAlgorithm.Result(); + private boolean mIsSwipedHorizontally; + private boolean mIsExpanding; + private KeyguardBottomAreaView mKeyguardBottomArea; + private boolean mBlockTouches; + private ArrayList<View> mSwipeTranslationViews = new ArrayList<>(); public NotificationPanelView(Context context, AttributeSet attrs) { super(context, attrs); @@ -129,6 +138,10 @@ public class NotificationPanelView extends PanelView implements mNotificationStackScroller.setOnHeightChangedListener(this); mFastOutSlowInInterpolator = AnimationUtils.loadInterpolator(getContext(), android.R.interpolator.fast_out_slow_in); + mKeyguardBottomArea = (KeyguardBottomAreaView) findViewById(R.id.keyguard_bottom_area); + mSwipeTranslationViews.add(mNotificationStackScroller); + mSwipeTranslationViews.add(mKeyguardStatusView); + mPageSwiper = new KeyguardPageSwipeHelper(this, getContext()); } @Override @@ -247,6 +260,12 @@ public class NotificationPanelView extends PanelView implements mQsExpansionEnabled = qsExpansionEnabled; } + public void resetViews() { + mBlockTouches = false; + mPageSwiper.reset(); + closeQs(); + } + public void closeQs() { cancelAnimation(); setQsExpansion(mQsMinExpansionHeight); @@ -263,9 +282,7 @@ public class NotificationPanelView extends PanelView implements public void fling(float vel, boolean always) { GestureRecorder gr = ((PhoneStatusBarView) mBar).mBar.getGestureRecorder(); if (gr != null) { - gr.tag( - "fling " + ((vel > 0) ? "open" : "closed"), - "notifications,v=" + vel); + gr.tag("fling " + ((vel > 0) ? "open" : "closed"), "notifications,v=" + vel); } super.fling(vel, always); } @@ -283,6 +300,9 @@ public class NotificationPanelView extends PanelView implements @Override public boolean onInterceptTouchEvent(MotionEvent event) { + if (mBlockTouches) { + return false; + } int pointerIndex = event.findPointerIndex(mTrackingPointer); if (pointerIndex < 0) { pointerIndex = 0; @@ -298,7 +318,7 @@ public class NotificationPanelView extends PanelView implements mInitialTouchX = x; initVelocityTracker(); trackMovement(event); - if (shouldIntercept(mInitialTouchX, mInitialTouchY, 0)) { + if (shouldQuickSettingsIntercept(mInitialTouchX, mInitialTouchY, 0)) { getParent().requestDisallowInterceptTouchEvent(true); } break; @@ -316,7 +336,7 @@ public class NotificationPanelView extends PanelView implements case MotionEvent.ACTION_MOVE: final float h = y - mInitialTouchY; trackMovement(event); - if (mTracking) { + if (mQsTracking) { // Already tracking because onOverscrolled was called. We need to update here // so we don't stop for a frame until the next touch event gets handled in @@ -327,12 +347,12 @@ public class NotificationPanelView extends PanelView implements return true; } if (Math.abs(h) > mTouchSlop && Math.abs(h) > Math.abs(x - mInitialTouchX) - && shouldIntercept(mInitialTouchX, mInitialTouchY, h)) { + && shouldQuickSettingsIntercept(mInitialTouchX, mInitialTouchY, h)) { onQsExpansionStarted(); mInitialHeightOnTouch = mQsExpansionHeight; mInitialTouchY = y; mInitialTouchX = x; - mTracking = true; + mQsTracking = true; mIntercepting = false; return true; } @@ -341,9 +361,9 @@ public class NotificationPanelView extends PanelView implements case MotionEvent.ACTION_CANCEL: case MotionEvent.ACTION_UP: trackMovement(event); - if (mTracking) { - flingWithCurrentVelocity(); - mTracking = false; + if (mQsTracking) { + flingQsWithCurrentVelocity(); + mQsTracking = false; } mIntercepting = false; break; @@ -362,7 +382,7 @@ public class NotificationPanelView extends PanelView implements super.requestDisallowInterceptTouchEvent(disallowIntercept); } - private void flingWithCurrentVelocity() { + private void flingQsWithCurrentVelocity() { float vel = getCurrentVelocity(); // TODO: Better logic whether we should expand or not. @@ -371,65 +391,83 @@ public class NotificationPanelView extends PanelView implements @Override public boolean onTouchEvent(MotionEvent event) { + if (mBlockTouches) { + return false; + } // TODO: Handle doublefinger swipe to notifications again. Look at history for a reference // implementation. - if (mTracking) { - int pointerIndex = event.findPointerIndex(mTrackingPointer); - if (pointerIndex < 0) { - pointerIndex = 0; - mTrackingPointer = event.getPointerId(pointerIndex); + if (!mIsExpanding && !mQsExpanded && mStatusBar.getBarState() != StatusBarState.SHADE) { + mPageSwiper.onTouchEvent(event); + if (mPageSwiper.isSwipingInProgress()) { + return true; } - final float y = event.getY(pointerIndex); - final float x = event.getX(pointerIndex); + } + if (mQsTracking || mQsExpanded) { + return onQsTouch(event); + } - switch (event.getActionMasked()) { - case MotionEvent.ACTION_DOWN: - mTracking = true; - mInitialTouchY = y; - mInitialTouchX = x; - onQsExpansionStarted(); - mInitialHeightOnTouch = mQsExpansionHeight; - initVelocityTracker(); - trackMovement(event); - break; - - case MotionEvent.ACTION_POINTER_UP: - final int upPointer = event.getPointerId(event.getActionIndex()); - if (mTrackingPointer == upPointer) { - // gesture is ongoing, find a new pointer to track - final int newIndex = event.getPointerId(0) != upPointer ? 0 : 1; - final float newY = event.getY(newIndex); - final float newX = event.getX(newIndex); - mTrackingPointer = event.getPointerId(newIndex); - mInitialHeightOnTouch = mQsExpansionHeight; - mInitialTouchY = newY; - mInitialTouchX = newX; - } - break; + super.onTouchEvent(event); + return true; + } - case MotionEvent.ACTION_MOVE: - final float h = y - mInitialTouchY; - setQsExpansion(h + mInitialHeightOnTouch); - trackMovement(event); - break; + @Override + protected boolean hasConflictingGestures() { + return mStatusBar.getBarState() != StatusBarState.SHADE; + } - case MotionEvent.ACTION_UP: - case MotionEvent.ACTION_CANCEL: - mTracking = false; - mTrackingPointer = -1; - trackMovement(event); - flingWithCurrentVelocity(); - if (mVelocityTracker != null) { - mVelocityTracker.recycle(); - mVelocityTracker = null; - } - break; - } - return true; + private boolean onQsTouch(MotionEvent event) { + int pointerIndex = event.findPointerIndex(mTrackingPointer); + if (pointerIndex < 0) { + pointerIndex = 0; + mTrackingPointer = event.getPointerId(pointerIndex); } + final float y = event.getY(pointerIndex); + final float x = event.getX(pointerIndex); + + switch (event.getActionMasked()) { + case MotionEvent.ACTION_DOWN: + mQsTracking = true; + mInitialTouchY = y; + mInitialTouchX = x; + onQsExpansionStarted(); + mInitialHeightOnTouch = mQsExpansionHeight; + initVelocityTracker(); + trackMovement(event); + break; + + case MotionEvent.ACTION_POINTER_UP: + final int upPointer = event.getPointerId(event.getActionIndex()); + if (mTrackingPointer == upPointer) { + // gesture is ongoing, find a new pointer to track + final int newIndex = event.getPointerId(0) != upPointer ? 0 : 1; + final float newY = event.getY(newIndex); + final float newX = event.getX(newIndex); + mTrackingPointer = event.getPointerId(newIndex); + mInitialHeightOnTouch = mQsExpansionHeight; + mInitialTouchY = newY; + mInitialTouchX = newX; + } + break; - // Consume touch events when QS are expanded. - return mQsExpanded || super.onTouchEvent(event); + case MotionEvent.ACTION_MOVE: + final float h = y - mInitialTouchY; + setQsExpansion(h + mInitialHeightOnTouch); + trackMovement(event); + break; + + case MotionEvent.ACTION_UP: + case MotionEvent.ACTION_CANCEL: + mQsTracking = false; + mTrackingPointer = -1; + trackMovement(event); + flingQsWithCurrentVelocity(); + if (mVelocityTracker != null) { + mVelocityTracker.recycle(); + mVelocityTracker = null; + } + break; + } + return true; } @Override @@ -439,7 +477,7 @@ public class NotificationPanelView extends PanelView implements mInitialHeightOnTouch = mQsExpansionHeight; mInitialTouchY = mLastTouchY; mInitialTouchX = mLastTouchX; - mTracking = true; + mQsTracking = true; } } @@ -569,7 +607,7 @@ public class NotificationPanelView extends PanelView implements /** * @return Whether we should intercept a gesture to open Quick Settings. */ - private boolean shouldIntercept(float x, float y, float yDiff) { + private boolean shouldQuickSettingsIntercept(float x, float y, float yDiff) { if (!mQsExpansionEnabled) { return false; } @@ -647,12 +685,14 @@ public class NotificationPanelView extends PanelView implements protected void onExpandingStarted() { super.onExpandingStarted(); mNotificationStackScroller.onExpansionStarted(); + mIsExpanding = true; } @Override protected void onExpandingFinished() { super.onExpandingFinished(); mNotificationStackScroller.onExpansionStopped(); + mIsExpanding = false; } @Override @@ -664,8 +704,8 @@ public class NotificationPanelView extends PanelView implements } @Override - protected void onTrackingStopped() { - super.onTrackingStopped(); + protected void onTrackingStopped(boolean expand) { + super.onTrackingStopped(expand); mOverExpansion = 0.0f; mNotificationStackScroller.setOverScrolledPixels(0.0f, true /* onTop */, true /* animate */); @@ -686,6 +726,12 @@ public class NotificationPanelView extends PanelView implements } @Override + protected void onConfigurationChanged(Configuration newConfig) { + super.onConfigurationChanged(newConfig); + mPageSwiper.onConfigurationChanged(); + } + + @Override public void onClick(View v) { if (v == mHeader.getBackgroundView()) { onQsExpansionStarted(); @@ -696,4 +742,40 @@ public class NotificationPanelView extends PanelView implements } } } + + @Override + public void onAnimationToSideStarted(boolean rightPage) { + if (rightPage) { + mKeyguardBottomArea.launchCamera(); + } else { + mKeyguardBottomArea.launchPhone(); + } + mBlockTouches = true; + } + + + @Override + public float getPageWidth() { + return getWidth(); + } + + @Override + public ArrayList<View> getTranslationViews() { + return mSwipeTranslationViews; + } + + @Override + public View getLeftIcon() { + return mKeyguardBottomArea.getPhoneImageView(); + } + + @Override + public View getCenterIcon() { + return mKeyguardBottomArea.getLockIcon(); + } + + @Override + public View getRightIcon() { + return mKeyguardBottomArea.getCameraImageView(); + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelBar.java index 8800625..1015d5b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelBar.java @@ -222,7 +222,11 @@ public class PanelBar extends FrameLayout { } } - public void onTrackingStopped(PanelView panel) { + public void onTrackingStopped(PanelView panel, boolean expand) { mTracking = false; } + + public void onExpandingFinished() { + + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java index 7c1f2cf..f6db8a8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java @@ -35,7 +35,7 @@ import com.android.systemui.statusbar.FlingAnimationUtils; import java.io.FileDescriptor; import java.io.PrintWriter; -public class PanelView extends FrameLayout { +public abstract class PanelView extends FrameLayout { public static final boolean DEBUG = PanelBar.DEBUG; public static final String TAG = PanelView.class.getSimpleName(); protected float mOverExpansion; @@ -67,6 +67,7 @@ public class PanelView extends FrameLayout { private float mInitialTouchX; protected void onExpandingFinished() { + mBar.onExpandingFinished(); } protected void onExpandingStarted() { @@ -129,21 +130,24 @@ public class PanelView extends FrameLayout { final float y = event.getY(pointerIndex); final float x = event.getX(pointerIndex); + boolean waitForTouchSlop = hasConflictingGestures(); + switch (event.getActionMasked()) { case MotionEvent.ACTION_DOWN: - mTracking = true; mInitialTouchY = y; mInitialTouchX = x; + mInitialOffsetOnTouch = mExpandedHeight; if (mVelocityTracker == null) { initVelocityTracker(); } trackMovement(event); - if (mHeightAnimator != null) { - mHeightAnimator.cancel(); // end any outstanding animations + if (!waitForTouchSlop || mHeightAnimator != null) { + if (mHeightAnimator != null) { + mHeightAnimator.cancel(); // end any outstanding animations + } + onTrackingStarted(); } - onTrackingStarted(); - mInitialOffsetOnTouch = mExpandedHeight; if (mExpandedHeight == 0) { mJustPeeked = true; runPeekAnimation(); @@ -165,15 +169,27 @@ public class PanelView extends FrameLayout { break; case MotionEvent.ACTION_MOVE: - final float h = y - mInitialTouchY + mInitialOffsetOnTouch; - if (h > mPeekHeight) { + float h = y - mInitialTouchY; + if (waitForTouchSlop && !mTracking && Math.abs(h) > mTouchSlop + && Math.abs(h) > Math.abs(x - mInitialTouchX)) { + mInitialOffsetOnTouch = mExpandedHeight; + mInitialTouchX = x; + mInitialTouchY = y; + if (mHeightAnimator != null) { + mHeightAnimator.cancel(); // end any outstanding animations + } + onTrackingStarted(); + h = 0; + } + final float newHeight = h + mInitialOffsetOnTouch; + if (newHeight > mPeekHeight) { if (mPeekAnimator != null && mPeekAnimator.isStarted()) { mPeekAnimator.cancel(); } mJustPeeked = false; } - if (!mJustPeeked) { - setExpandedHeightInternal(h); + if (!mJustPeeked && (!waitForTouchSlop || mTracking)) { + setExpandedHeightInternal(newHeight); mBar.panelExpansionChanged(PanelView.this, mExpandedFraction); } @@ -182,25 +198,28 @@ public class PanelView extends FrameLayout { case MotionEvent.ACTION_UP: case MotionEvent.ACTION_CANCEL: - mTracking = false; mTrackingPointer = -1; - onTrackingStopped(); trackMovement(event); - flingWithCurrentVelocity(); + boolean expand = flingWithCurrentVelocity(); + onTrackingStopped(expand); if (mVelocityTracker != null) { mVelocityTracker.recycle(); mVelocityTracker = null; } break; } - return true; + return !waitForTouchSlop || mTracking; } - protected void onTrackingStopped() { - mBar.onTrackingStopped(PanelView.this); + protected abstract boolean hasConflictingGestures(); + + protected void onTrackingStopped(boolean expand) { + mTracking = false; + mBar.onTrackingStopped(PanelView.this, expand); } protected void onTrackingStarted() { + mTracking = true; mBar.onTrackingStarted(PanelView.this); onExpandingStarted(); } @@ -303,7 +322,10 @@ public class PanelView extends FrameLayout { mMaxPanelHeight = -1; } - private void flingWithCurrentVelocity() { + /** + * @return whether the panel will be expanded after the animation + */ + private boolean flingWithCurrentVelocity() { float vel = getCurrentVelocity(); boolean expand; if (Math.abs(vel) < mFlingAnimationUtils.getMinVelocityPxPerSecond()) { @@ -312,11 +334,16 @@ public class PanelView extends FrameLayout { expand = vel > 0; } fling(vel, expand); + return expand; } protected void fling(float vel, boolean expand) { cancelPeek(); float target = expand ? getMaxPanelHeight() : 0.0f; + if (target == mExpandedHeight) { + onExpandingFinished(); + return; + } ValueAnimator animator = ValueAnimator.ofFloat(mExpandedHeight, target); if (expand) { mFlingAnimationUtils.apply(animator, mExpandedHeight, target, vel, getHeight()); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java index b36c2ef..5dcd61c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java @@ -64,6 +64,7 @@ import android.os.SystemClock; import android.os.UserHandle; import android.provider.Settings; import android.provider.Settings.Global; +import android.service.notification.NotificationListenerService.Ranking; import android.service.notification.StatusBarNotification; import android.util.ArraySet; import android.util.DisplayMetrics; @@ -86,7 +87,6 @@ import android.view.animation.Animation; import android.view.animation.AnimationUtils; import android.view.animation.DecelerateInterpolator; import android.widget.FrameLayout; -import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.TextView; @@ -121,10 +121,12 @@ import com.android.systemui.statusbar.policy.UserInfoController; import com.android.systemui.statusbar.policy.LocationControllerImpl; import com.android.systemui.statusbar.policy.NetworkControllerImpl; import com.android.systemui.statusbar.policy.RotationLockControllerImpl; +import com.android.systemui.statusbar.policy.ZenModeController; import com.android.systemui.statusbar.policy.ZenModeControllerImpl; import com.android.systemui.statusbar.stack.NotificationStackScrollLayout; import com.android.systemui.statusbar.stack.NotificationStackScrollLayout.OnChildLocationsChangedListener; import com.android.systemui.statusbar.stack.StackScrollState.ViewState; +import com.android.systemui.volume.VolumeComponent; import java.io.FileDescriptor; import java.io.PrintWriter; @@ -196,8 +198,9 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, NetworkControllerImpl mNetworkController; RotationLockControllerImpl mRotationLockController; UserInfoController mUserInfoController; - ZenModeControllerImpl mZenModeController; + ZenModeController mZenModeController; CastControllerImpl mCastController; + VolumeComponent mVolumeComponent; int mNaturalBarHeight = -1; int mIconSize = -1; @@ -209,6 +212,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, PhoneStatusBarView mStatusBarView; private int mStatusBarWindowState = WINDOW_STATE_SHOWING; private StatusBarWindowManager mStatusBarWindowManager; + private UnlockMethodCache mUnlockMethodCache; int mPixelFormat; Object mQueueLock = new Object(); @@ -523,6 +527,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, Settings.Global.getUriFor(SETTING_HEADS_UP_TICKER), true, mHeadsUpObserver); } + mUnlockMethodCache = UnlockMethodCache.getInstance(mContext); startKeyguard(); } @@ -684,7 +689,8 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, mRotationLockController = new RotationLockControllerImpl(mContext); } mUserInfoController = new UserInfoController(mContext); - mZenModeController = new ZenModeControllerImpl(mContext, mHandler); + mVolumeComponent = getComponent(VolumeComponent.class); + mZenModeController = mVolumeComponent.getZenController(); mCastController = new CastControllerImpl(mContext); final SignalClusterView signalCluster = (SignalClusterView)mStatusBarView.findViewById(R.id.signal_cluster); @@ -747,7 +753,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, final QSTileHost qsh = new QSTileHost(mContext, this, mBluetoothController, mLocationController, mRotationLockController, mNetworkController, mZenModeController, null /*tethering*/, - mCastController); + mCastController, mVolumeComponent); for (QSTile<?> tile : qsh.getTiles()) { mQSPanel.addTile(tile); } @@ -884,7 +890,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, } }; - View.OnTouchListener mHomeSearchActionListener = new View.OnTouchListener() { + View.OnTouchListener mHomeActionListener = new View.OnTouchListener() { public boolean onTouch(View v, MotionEvent event) { switch(event.getAction()) { case MotionEvent.ACTION_DOWN: @@ -919,8 +925,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, mNavigationBarView.getRecentsButton().setOnClickListener(mRecentsClickListener); mNavigationBarView.getRecentsButton().setOnTouchListener(mRecentsPreloadOnTouchListener); - mNavigationBarView.getHomeButton().setOnTouchListener(mHomeSearchActionListener); - mNavigationBarView.getSearchLight().setOnTouchListener(mHomeSearchActionListener); + mNavigationBarView.getHomeButton().setOnTouchListener(mHomeActionListener); updateSearchPanel(); } @@ -1033,13 +1038,15 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, } @Override - public void addNotificationInternal(StatusBarNotification notification) { + public void addNotificationInternal(StatusBarNotification notification, Ranking ranking) { if (DEBUG) Log.d(TAG, "addNotification score=" + notification.getScore()); Entry shadeEntry = createNotificationViews(notification); if (shadeEntry == null) { return; } if (mZenMode != Global.ZEN_MODE_OFF && mIntercepted.tryIntercept(notification)) { + // Forward the ranking so we can sort the new notification. + mNotificationData.updateRanking(ranking); return; } if (mUseHeadsUp && shouldInterrupt(notification)) { @@ -1079,7 +1086,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, tick(notification, true); } } - addNotificationViews(shadeEntry); + addNotificationViews(shadeEntry, ranking); // Recalculate the position of the sliding windows and the titles. setAreThereNotifications(); updateExpandedViewPos(EXPANDED_LEAVE_ALONE); @@ -1095,14 +1102,14 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, } @Override - public void updateNotification(StatusBarNotification notification) { - super.updateNotification(notification); + public void updateNotificationInternal(StatusBarNotification notification, Ranking ranking) { + super.updateNotificationInternal(notification, ranking); mIntercepted.update(notification); } @Override - public void removeNotificationInternal(String key) { - StatusBarNotification old = removeNotificationViews(key); + public void removeNotificationInternal(String key, Ranking ranking) { + StatusBarNotification old = removeNotificationViews(key, ranking); if (SPEW) Log.d(TAG, "removeNotification key=" + key + " old=" + old); if (old != null) { @@ -1139,7 +1146,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, R.integer.config_show_search_delay); } - private void loadNotificationShade() { + private void updateNotificationShade() { if (mStackScroller == null) return; int N = mNotificationData.size(); @@ -1149,7 +1156,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, final boolean provisioned = isDeviceProvisioned(); // If the device hasn't been through Setup, we only show system notifications for (int i=0; i<N; i++) { - Entry ent = mNotificationData.get(N-i-1); + Entry ent = mNotificationData.get(i); if (!(provisioned || showNotificationEvenIfUnprovisioned(ent.notification))) continue; // TODO How do we want to badge notifcations from profiles. @@ -1180,26 +1187,75 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, for (int i=0; i<toShow.size(); i++) { View v = toShow.get(i); if (v.getParent() == null) { - mStackScroller.addView(v, i); + mStackScroller.addView(v); } } + // So after all this work notifications still aren't sorted correctly. + // Let's do that now by advancing through toShow and mStackScroller in + // lock-step, making sure mStackScroller matches what we see in toShow. + int j = 0; + for (int i = 0; i < mStackScroller.getChildCount(); i++) { + View child = mStackScroller.getChildAt(i); + if (!(child instanceof ExpandableNotificationRow)) { + // We don't care about non-notification views. + continue; + } + + if (child == toShow.get(j)) { + // Everything is well, advance both lists. + j++; + continue; + } + + // Oops, wrong notification at this position. Put the right one + // here and advance both lists. + mStackScroller.changeViewPosition(toShow.get(j), i); + j++; + } + updateRowStates(); + updateSpeedbump(); mNotificationPanel.setQsExpansionEnabled(provisioned && mUserSetup); } + private void updateSpeedbump() { + int speedbumpIndex = -1; + int currentIndex = 0; + for (int i = 0; i < mNotificationData.size(); i++) { + Entry entry = mNotificationData.get(i); + if (entry.row.getParent() == null) { + // This view isn't even added, so the stack scroller doesn't + // know about it. Ignore completely. + continue; + } + if (entry.row.getVisibility() != View.GONE && + mNotificationData.isAmbient(entry.key)) { + speedbumpIndex = currentIndex; + break; + } + currentIndex++; + } + mStackScroller.updateSpeedBumpIndex(speedbumpIndex); + } + @Override - protected void updateNotificationIcons() { + protected void updateNotifications() { + // TODO: Move this into updateNotificationIcons()? if (mNotificationIcons == null) return; - loadNotificationShade(); + updateNotificationShade(); + updateNotificationIcons(); + } + private void updateNotificationIcons() { final LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(mIconSize + 2*mIconHPadding, mNaturalBarHeight); int N = mNotificationData.size(); if (DEBUG) { - Log.d(TAG, "refreshing icons: " + N + " notifications, mNotificationIcons=" + mNotificationIcons); + Log.d(TAG, "refreshing icons: " + N + " notifications, mNotificationIcons=" + + mNotificationIcons); } ArrayList<View> toShow = new ArrayList<View>(); @@ -1207,7 +1263,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, final boolean provisioned = isDeviceProvisioned(); // If the device hasn't been through Setup, we only show system notifications for (int i=0; i<N; i++) { - Entry ent = mNotificationData.get(N-i-1); + Entry ent = mNotificationData.get(i); if (!((provisioned && ent.notification.getScore() >= HIDE_ICONS_BELOW_SCORE) || showNotificationEvenIfUnprovisioned(ent.notification))) continue; if (!notificationIsForCurrentProfiles(ent.notification)) continue; @@ -2384,7 +2440,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, public void userSwitched(int newUserId) { if (MULTIUSER_DEBUG) mNotificationPanelDebugText.setText("USER " + newUserId); animateCollapsePanels(); - updateNotificationIcons(); + updateNotifications(); resetUserSetupObserver(); } @@ -2770,7 +2826,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, mKeyguardStatusView.setVisibility(View.VISIBLE); mKeyguardIndicationTextView.setVisibility(View.VISIBLE); mKeyguardIndicationTextView.switchIndication(mKeyguardHotwordPhrase); - mNotificationPanel.closeQs(); + mNotificationPanel.resetViews(); } else { mKeyguardStatusView.setVisibility(View.GONE); mKeyguardIndicationTextView.setVisibility(View.GONE); @@ -2789,10 +2845,8 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, updateStackScrollerState(); updatePublicMode(); - updateRowStates(); - updateSpeedBump(); + updateNotifications(); checkBarModes(); - updateNotificationIcons(); updateCarrierLabelVisibility(false); } @@ -2882,10 +2936,15 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, } } - public void onTrackingStopped() { + public void onTrackingStopped(boolean expand) { if (mState == StatusBarState.KEYGUARD) { mKeyguardIndicationTextView.switchIndication(mKeyguardHotwordPhrase); } + if (mState == StatusBarState.KEYGUARD || mState == StatusBarState.SHADE_LOCKED) { + if (!expand && !mUnlockMethodCache.isMethodInsecure()) { + showBouncer(); + } + } } @Override diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java index 5fdf5bf..910d88c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java @@ -148,9 +148,15 @@ public class PhoneStatusBarView extends PanelBar { } @Override - public void onTrackingStopped(PanelView panel) { - super.onTrackingStopped(panel); - mBar.onTrackingStopped(); + public void onTrackingStopped(PanelView panel, boolean expand) { + super.onTrackingStopped(panel, expand); + mBar.onTrackingStopped(expand); + } + + @Override + public void onExpandingFinished() { + super.onExpandingFinished(); + mScrimController.onExpandingFinished(); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QSTileHost.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QSTileHost.java index 7029898..1344703 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QSTileHost.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QSTileHost.java @@ -21,6 +21,7 @@ import android.content.Intent; import android.os.HandlerThread; import android.os.Looper; +import com.android.systemui.R; import com.android.systemui.qs.QSTile; import com.android.systemui.qs.tiles.AirplaneModeTile; import com.android.systemui.qs.tiles.BluetoothTile; @@ -29,11 +30,10 @@ import com.android.systemui.qs.tiles.CastTile; import com.android.systemui.qs.tiles.CellularTile; import com.android.systemui.qs.tiles.ColorInversionTile; import com.android.systemui.qs.tiles.LocationTile; -import com.android.systemui.qs.tiles.RingerModeTile; +import com.android.systemui.qs.tiles.NotificationsTile; import com.android.systemui.qs.tiles.RotationLockTile; import com.android.systemui.qs.tiles.HotspotTile; import com.android.systemui.qs.tiles.WifiTile; -import com.android.systemui.qs.tiles.ZenModeTile; import com.android.systemui.settings.CurrentUserTracker; import com.android.systemui.statusbar.policy.BluetoothController; import com.android.systemui.statusbar.policy.CastController; @@ -42,6 +42,7 @@ import com.android.systemui.statusbar.policy.NetworkController; import com.android.systemui.statusbar.policy.RotationLockController; import com.android.systemui.statusbar.policy.TetheringController; import com.android.systemui.statusbar.policy.ZenModeController; +import com.android.systemui.volume.VolumeComponent; import java.util.ArrayList; import java.util.List; @@ -60,13 +61,15 @@ public class QSTileHost implements QSTile.Host { private final CastController mCast; private final Looper mLooper; private final CurrentUserTracker mUserTracker; + private final VolumeComponent mVolume; private final ArrayList<QSTile<?>> mTiles = new ArrayList<QSTile<?>>(); + private final int mFeedbackStartDelay; public QSTileHost(Context context, PhoneStatusBar statusBar, BluetoothController bluetooth, LocationController location, RotationLockController rotation, NetworkController network, ZenModeController zen, TetheringController tethering, - CastController cast) { + CastController cast, VolumeComponent volume) { mContext = context; mStatusBar = statusBar; mBluetooth = bluetooth; @@ -76,6 +79,7 @@ public class QSTileHost implements QSTile.Host { mZen = zen; mTethering = tethering; mCast = cast; + mVolume = volume; final HandlerThread ht = new HandlerThread(QSTileHost.class.getSimpleName()); ht.start(); @@ -86,8 +90,7 @@ public class QSTileHost implements QSTile.Host { mTiles.add(new ColorInversionTile(this)); mTiles.add(new CellularTile(this)); mTiles.add(new AirplaneModeTile(this)); - mTiles.add(new ZenModeTile(this)); - mTiles.add(new RingerModeTile(this)); + mTiles.add(new NotificationsTile(this)); mTiles.add(new RotationLockTile(this)); mTiles.add(new LocationTile(this)); mTiles.add(new CastTile(this)); @@ -103,6 +106,7 @@ public class QSTileHost implements QSTile.Host { } }; mUserTracker.startTracking(); + mFeedbackStartDelay = mContext.getResources().getInteger(R.integer.feedback_start_delay); } @Override @@ -112,7 +116,7 @@ public class QSTileHost implements QSTile.Host { @Override public void startSettingsActivity(final Intent intent) { - mStatusBar.postStartSettingsActivity(intent, QSTile.FEEDBACK_START_DELAY); + mStatusBar.postStartSettingsActivity(intent, mFeedbackStartDelay); } @Override @@ -169,4 +173,9 @@ public class QSTileHost implements QSTile.Host { public CastController getCastController() { return mCast; } + + @Override + public VolumeComponent getVolumeComponent() { + return mVolume; + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java index 95255d5..6156fc3 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java @@ -52,6 +52,7 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener { private boolean mBouncerShowing; private boolean mAnimateChange; private boolean mUpdatePending; + private boolean mExpanding; private final Interpolator mInterpolator = new DecelerateInterpolator(); @@ -67,9 +68,14 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener { } public void onTrackingStarted() { + mExpanding = true; mDarkenWhileDragging = !mUnlockMethodCache.isMethodInsecure(); } + public void onExpandingFinished() { + mExpanding = false; + } + public void setPanelExpansion(float fraction) { mFraction = fraction; scheduleUpdate(); @@ -77,7 +83,7 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener { public void setBouncerShowing(boolean showing) { mBouncerShowing = showing; - mAnimateChange = true; + mAnimateChange = !mExpanding; scheduleUpdate(); } @@ -98,14 +104,14 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener { } private void updateScrimKeyguard() { - if (mBouncerShowing) { - setScrimInFrontColor(SCRIM_IN_FRONT_ALPHA); - setScrimBehindColor(0f); - } else if (mDarkenWhileDragging) { + if (mExpanding && mDarkenWhileDragging) { float behindFraction = Math.max(0, Math.min(mFraction, 1)); float fraction = 1 - behindFraction; setScrimInFrontColor(fraction * SCRIM_IN_FRONT_ALPHA); setScrimBehindColor(behindFraction * SCRIM_BEHIND_ALPHA_KEYGUARD); + } else if (mBouncerShowing) { + setScrimInFrontColor(SCRIM_IN_FRONT_ALPHA); + setScrimBehindColor(0f); } else { setScrimInFrontColor(0f); setScrimBehindColor(SCRIM_BEHIND_ALPHA_KEYGUARD); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java index c3430c3..d5551b8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java @@ -102,6 +102,7 @@ public class StatusBarKeyguardViewManager { } else { mPhoneStatusBar.showKeyguard(); mBouncer.hide(false /* destroyView */); + mBouncer.prepare(); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SwipeAffordanceView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SwipeAffordanceView.java deleted file mode 100644 index 049c5fc..0000000 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SwipeAffordanceView.java +++ /dev/null @@ -1,222 +0,0 @@ -/* - * 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.content.Context; -import android.content.res.TypedArray; -import android.os.SystemClock; -import android.util.AttributeSet; -import android.view.MotionEvent; -import android.view.View; -import android.view.ViewConfiguration; -import android.view.animation.AccelerateInterpolator; -import android.view.animation.DecelerateInterpolator; -import android.widget.Button; - -import com.android.systemui.R; -import com.android.systemui.statusbar.policy.KeyButtonView; - -/** - * A swipeable button for affordances on the lockscreen. This is used for the camera and phone - * affordance. - */ -public class SwipeAffordanceView extends KeyButtonView { - - private static final int SWIPE_DIRECTION_START = 0; - private static final int SWIPE_DIRECTION_END = 1; - - private static final int SWIPE_DIRECTION_LEFT = 0; - private static final int SWIPE_DIRECTION_RIGHT = 1; - - private AffordanceListener mListener; - private int mScaledTouchSlop; - private float mDragDistance; - private int mResolvedSwipeDirection; - private int mSwipeDirection; - - public SwipeAffordanceView(Context context, AttributeSet attrs) { - this(context, attrs, 0); - } - - public SwipeAffordanceView(Context context, AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); - TypedArray a = context.getTheme().obtainStyledAttributes( - attrs, - R.styleable.SwipeAffordanceView, - 0, 0); - try { - mSwipeDirection = a.getInt(R.styleable.SwipeAffordanceView_swipeDirection, 0); - } finally { - a.recycle(); - } - } - - @Override - public void onRtlPropertiesChanged(int layoutDirection) { - super.onRtlPropertiesChanged(layoutDirection); - if (!isLayoutRtl()) { - mResolvedSwipeDirection = mSwipeDirection; - } else { - mResolvedSwipeDirection = mSwipeDirection == SWIPE_DIRECTION_START - ? SWIPE_DIRECTION_RIGHT - : SWIPE_DIRECTION_LEFT; - } - } - - @Override - protected void onFinishInflate() { - super.onFinishInflate(); - mDragDistance = getResources().getDimension(R.dimen.affordance_drag_distance); - mScaledTouchSlop = ViewConfiguration.get(mContext).getScaledTouchSlop(); - } - - public void enableAccessibility(boolean touchExplorationEnabled) { - - // Add a touch handler or accessibility click listener for camera button. - if (touchExplorationEnabled) { - setOnTouchListener(null); - setOnClickListener(mClickListener); - } else { - setOnTouchListener(mTouchListener); - setOnClickListener(null); - } - } - - public void setAffordanceListener(AffordanceListener listener) { - mListener = listener; - } - - private void onActionPerformed() { - if (mListener != null) { - mListener.onActionPerformed(this); - } - } - - private void onUserActivity(long when) { - if (mListener != null) { - mListener.onUserActivity(when); - } - } - - private final OnClickListener mClickListener = new OnClickListener() { - @Override - public void onClick(View v) { - onActionPerformed(); - } - }; - - private final OnTouchListener mTouchListener = new OnTouchListener() { - private float mStartX; - private boolean mTouchSlopReached; - private boolean mSkipCancelAnimation; - - @Override - public boolean onTouch(final View view, MotionEvent event) { - float realX = event.getRawX(); - switch (event.getAction()) { - case MotionEvent.ACTION_DOWN: - mStartX = realX; - mTouchSlopReached = false; - mSkipCancelAnimation = false; - break; - case MotionEvent.ACTION_MOVE: - if (mResolvedSwipeDirection == SWIPE_DIRECTION_LEFT - ? realX > mStartX - : realX < mStartX) { - realX = mStartX; - } - if (mResolvedSwipeDirection == SWIPE_DIRECTION_LEFT - ? realX < mStartX - mDragDistance - : realX > mStartX + mDragDistance) { - view.setPressed(true); - onUserActivity(event.getEventTime()); - } else { - view.setPressed(false); - } - if (mResolvedSwipeDirection == SWIPE_DIRECTION_LEFT - ? realX < mStartX - mScaledTouchSlop - : realX > mStartX + mScaledTouchSlop) { - mTouchSlopReached = true; - } - view.setTranslationX(mResolvedSwipeDirection == SWIPE_DIRECTION_LEFT - ? Math.max(realX - mStartX, -mDragDistance) - : Math.min(realX - mStartX, mDragDistance)); - break; - case MotionEvent.ACTION_UP: - if (mResolvedSwipeDirection == SWIPE_DIRECTION_LEFT - ? realX < mStartX - mDragDistance - : realX > mStartX + mDragDistance) { - onActionPerformed(); - view.animate().x(mResolvedSwipeDirection == SWIPE_DIRECTION_LEFT - ? -view.getWidth() - : ((View) view.getParent()).getWidth() + view.getWidth()) - .setInterpolator(new AccelerateInterpolator(2f)).withEndAction( - new Runnable() { - @Override - public void run() { - view.setTranslationX(0); - } - }); - mSkipCancelAnimation = true; - } - if (mResolvedSwipeDirection == SWIPE_DIRECTION_LEFT - ? realX < mStartX - mScaledTouchSlop - : realX > mStartX + mScaledTouchSlop) { - mTouchSlopReached = true; - } - if (!mTouchSlopReached) { - mSkipCancelAnimation = true; - view.animate().translationX(mResolvedSwipeDirection == SWIPE_DIRECTION_LEFT - ? -mDragDistance / 2 - : mDragDistance / 2). - setInterpolator(new DecelerateInterpolator()).withEndAction( - new Runnable() { - @Override - public void run() { - view.animate().translationX(0). - setInterpolator(new AccelerateInterpolator()); - } - }); - } - case MotionEvent.ACTION_CANCEL: - view.setPressed(false); - if (!mSkipCancelAnimation) { - view.animate().translationX(0) - .setInterpolator(new AccelerateInterpolator(2f)); - } - break; - } - return true; - } - }; - - public interface AffordanceListener { - - /** - * Called when the view would like to report user activity. - * - * @param when The timestamp of the user activity in {@link SystemClock#uptimeMillis} time - * base. - */ - void onUserActivity(long when); - - /** - * Called when the action of the affordance has been performed. - */ - void onActionPerformed(SwipeAffordanceView view); - } -} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java index 966c0b0..56402a5 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java @@ -339,7 +339,7 @@ public class NetworkControllerImpl extends BroadcastReceiver boolean wifiOut = wifiEnabled && mWifiSsid != null && (mWifiActivity == WifiManager.DATA_ACTIVITY_INOUT || mWifiActivity == WifiManager.DATA_ACTIVITY_OUT); - cb.onWifiSignalChanged(wifiEnabled, mQSWifiIconId, wifiIn, wifiOut, + cb.onWifiSignalChanged(mWifiEnabled, mQSWifiIconId, wifiIn, wifiOut, mContentDescriptionWifi, wifiDesc); boolean mobileIn = mDataConnected && (mDataActivity == TelephonyManager.DATA_ACTIVITY_INOUT diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/AnimationFilter.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/AnimationFilter.java index 5e2d06b..cf56fa57 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/AnimationFilter.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/AnimationFilter.java @@ -27,6 +27,7 @@ public class AnimationFilter { boolean animateZ; boolean animateScale; boolean animateHeight; + boolean animateTopInset; boolean animateDimmed; boolean hasDelays; @@ -60,6 +61,11 @@ public class AnimationFilter { return this; } + public AnimationFilter animateTopInset() { + animateTopInset = true; + return this; + } + public AnimationFilter animateDimmed() { animateDimmed = true; return this; @@ -84,6 +90,7 @@ public class AnimationFilter { animateZ |= filter.animateZ; animateScale |= filter.animateScale; animateHeight |= filter.animateHeight; + animateTopInset |= filter.animateTopInset; animateDimmed |= filter.animateDimmed; hasDelays |= filter.hasDelays; } @@ -94,6 +101,7 @@ public class AnimationFilter { animateZ = false; animateScale = false; animateHeight = false; + animateTopInset = false; animateDimmed = false; hasDelays = false; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java index 079b184..58176b9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java @@ -1599,6 +1599,7 @@ public class NotificationStackScrollLayout extends ViewGroup new AnimationFilter() .animateAlpha() .animateHeight() + .animateTopInset() .animateY() .animateZ() .hasDelays(), @@ -1607,6 +1608,7 @@ public class NotificationStackScrollLayout extends ViewGroup new AnimationFilter() .animateAlpha() .animateHeight() + .animateTopInset() .animateY() .animateZ() .hasDelays(), @@ -1615,6 +1617,7 @@ public class NotificationStackScrollLayout extends ViewGroup new AnimationFilter() .animateAlpha() .animateHeight() + .animateTopInset() .animateY() .animateZ() .hasDelays(), @@ -1623,6 +1626,7 @@ public class NotificationStackScrollLayout extends ViewGroup new AnimationFilter() .animateAlpha() .animateHeight() + .animateTopInset() .animateY() .animateDimmed() .animateScale() @@ -1651,6 +1655,7 @@ public class NotificationStackScrollLayout extends ViewGroup new AnimationFilter() .animateAlpha() .animateHeight() + .animateTopInset() .animateY() .animateZ() }; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java index bd2541a..2b52c7e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java @@ -49,6 +49,7 @@ public class StackScrollAlgorithm { private int mBottomStackPeekSize; private int mZDistanceBetweenElements; private int mZBasicHeight; + private int mRoundedRectCornerRadius; private StackIndentationFunctor mTopStackIndentationFunctor; private StackIndentationFunctor mBottomStackIndentationFunctor; @@ -111,6 +112,8 @@ public class StackScrollAlgorithm { mZBasicHeight = (MAX_ITEMS_IN_BOTTOM_STACK + 1) * mZDistanceBetweenElements; mBottomStackSlowDownLength = context.getResources() .getDimensionPixelSize(R.dimen.bottom_stack_slow_down_length); + mRoundedRectCornerRadius = context.getResources().getDimensionPixelSize( + com.android.internal.R.dimen.notification_quantum_rounded_rect_radius); } @@ -146,6 +149,67 @@ public class StackScrollAlgorithm { handleDraggedViews(ambientState, resultState, algorithmState); updateDimmedActivated(ambientState, resultState, algorithmState); + updateClipping(resultState, algorithmState); + } + + private void updateClipping(StackScrollState resultState, + StackScrollAlgorithmState algorithmState) { + float previousNotificationEnd = 0; + float previousNotificationStart = 0; + boolean previousNotificationIsSwiped = false; + int childCount = algorithmState.visibleChildren.size(); + for (int i = 0; i < childCount; i++) { + ExpandableView child = algorithmState.visibleChildren.get(i); + StackScrollState.ViewState state = resultState.getViewStateForView(child); + float newYTranslation = state.yTranslation; + int newHeight = state.height; + // apply clipping and shadow + float newNotificationEnd = newYTranslation + newHeight; + + // In the unlocked shade we have to clip a little bit higher because of the rounded + // corners of the notifications. + float clippingCorrection = state.dimmed ? 0 : mRoundedRectCornerRadius; + + // When the previous notification is swiped, we don't clip the content to the + // bottom of it. + float clipHeight = previousNotificationIsSwiped + ? newHeight + : newNotificationEnd - (previousNotificationEnd - clippingCorrection); + + updateChildClippingAndBackground(state, newHeight, clipHeight, + (int) (newHeight - (previousNotificationStart - newYTranslation))); + + if (!child.isTransparent()) { + // Only update the previous values if we are not transparent, + // otherwise we would clip to a transparent view. + previousNotificationStart = newYTranslation + child.getClipTopAmount(); + previousNotificationEnd = newNotificationEnd; + previousNotificationIsSwiped = child.getTranslationX() != 0; + } + } + } + + /** + * Updates the shadow outline and the clipping for a view. + * + * @param state the viewState to update + * @param realHeight the currently applied height of the view + * @param clipHeight the desired clip height, the rest of the view will be clipped from the top + * @param backgroundHeight the desired background height. The shadows of the view will be + * based on this height and the content will be clipped from the top + */ + private void updateChildClippingAndBackground(StackScrollState.ViewState state, int realHeight, + float clipHeight, int backgroundHeight) { + if (realHeight > clipHeight) { + state.topOverLap = (int) (realHeight - clipHeight); + } else { + state.topOverLap = 0; + } + if (realHeight > backgroundHeight) { + state.clipTopAmount = (realHeight - backgroundHeight); + } else { + state.clipTopAmount = 0; + } } /** diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollState.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollState.java index 44e10be..94cb16d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollState.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollState.java @@ -16,7 +16,6 @@ package com.android.systemui.statusbar.stack; -import android.graphics.Outline; import android.graphics.Rect; import android.util.Log; import android.view.View; @@ -37,15 +36,12 @@ public class StackScrollState { private static final String CHILD_NOT_FOUND_TAG = "StackScrollStateNoSuchChild"; private final ViewGroup mHostView; - private final int mRoundedRectCornerRadius; private Map<ExpandableView, ViewState> mStateMap; private final Rect mClipRect = new Rect(); public StackScrollState(ViewGroup hostView) { mHostView = hostView; mStateMap = new HashMap<ExpandableView, ViewState>(); - mRoundedRectCornerRadius = mHostView.getResources().getDimensionPixelSize( - com.android.internal.R.dimen.notification_quantum_rounded_rect_radius); } public ViewGroup getHostView() { @@ -83,9 +79,6 @@ public class StackScrollState { */ public void apply() { int numChildren = mHostView.getChildCount(); - float previousNotificationEnd = 0; - float previousNotificationStart = 0; - boolean previousNotificationIsSwiped = false; for (int i = 0; i < numChildren; i++) { ExpandableView child = (ExpandableView) mHostView.getChildAt(i); ViewState state = mStateMap.get(child); @@ -155,39 +148,41 @@ public class StackScrollState { // apply dimming child.setDimmed(state.dimmed, false /* animate */); - // apply clipping and shadow - float newNotificationEnd = newYTranslation + newHeight; - - // In the unlocked shade we have to clip a little bit higher because of the rounded - // corners of the notifications. - float clippingCorrection = state.dimmed ? 0 : mRoundedRectCornerRadius; - - // When the previous notification is swiped, we don't clip the content to the - // bottom of it. - float clipHeight = previousNotificationIsSwiped - ? newHeight - : newNotificationEnd - (previousNotificationEnd - clippingCorrection); - - updateChildClippingAndBackground(child, newHeight, - clipHeight, - (int) (newHeight - (previousNotificationStart - newYTranslation))); - - if (!child.isTransparent()) { - // Only update the previous values if we are not transparent, - // otherwise we would clip to a transparent view. - previousNotificationStart = newYTranslation + child.getClipTopAmount(); - previousNotificationEnd = newNotificationEnd; - previousNotificationIsSwiped = child.getTranslationX() != 0; + float oldClipTopAmount = child.getClipTopAmount(); + if (oldClipTopAmount != state.clipTopAmount) { + child.setClipTopAmount(state.clipTopAmount); + } + + if (state.topOverLap != 0) { + updateChildClip(child, newHeight, state.topOverLap); + } else { + child.setClipBounds(null); } if(child instanceof SpeedBumpView) { - performSpeedBumpAnimation(i, (SpeedBumpView) child, newNotificationEnd, + float speedBumpEnd = newYTranslation + newHeight; + performSpeedBumpAnimation(i, (SpeedBumpView) child, speedBumpEnd, newYTranslation); } } } } + /** + * Updates the clipping of a view + * + * @param child the view to update + * @param height the currently applied height of the view + * @param clipInset how much should this view be clipped from the top + */ + private void updateChildClip(View child, int height, int clipInset) { + mClipRect.set(0, + clipInset, + child.getWidth(), + height); + child.setClipBounds(mClipRect); + } + private void performSpeedBumpAnimation(int i, SpeedBumpView speedBump, float speedBumpEnd, float speedBumpStart) { View nextChild = getNextChildNotGone(i); @@ -216,45 +211,6 @@ public class StackScrollState { return null; } - /** - * Updates the shadow outline and the clipping for a view. - * - * @param child the view to update - * @param realHeight the currently applied height of the view - * @param clipHeight the desired clip height, the rest of the view will be clipped from the top - * @param backgroundHeight the desired background height. The shadows of the view will be - * based on this height and the content will be clipped from the top - */ - private void updateChildClippingAndBackground(ExpandableView child, int realHeight, - float clipHeight, int backgroundHeight) { - if (realHeight > clipHeight) { - updateChildClip(child, realHeight, clipHeight); - } else { - child.setClipBounds(null); - } - if (realHeight > backgroundHeight) { - child.setClipTopAmount(realHeight - backgroundHeight); - } else { - child.setClipTopAmount(0); - } - } - - /** - * Updates the clipping of a view - * - * @param child the view to update - * @param height the currently applied height of the view - * @param clipHeight the desired clip height, the rest of the view will be clipped from the top - */ - private void updateChildClip(View child, int height, float clipHeight) { - int clipInset = (int) (height - clipHeight); - mClipRect.set(0, - clipInset, - child.getWidth(), - height); - child.setClipBounds(mClipRect); - } - public static class ViewState { // These are flags such that we can create masks for filtering. @@ -276,6 +232,18 @@ public class StackScrollState { boolean dimmed; /** + * The amount which the view should be clipped from the top. This is calculated to + * perceive consistent shadows. + */ + int clipTopAmount; + + /** + * How much does the child overlap with the previous view on the top? Can be used for + * a clipping optimization + */ + int topOverLap; + + /** * The index of the view, only accounting for views not equal to GONE */ int notGoneIndex; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackStateAnimator.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackStateAnimator.java index f019e6c..f41ab3a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackStateAnimator.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackStateAnimator.java @@ -133,10 +133,11 @@ public class StackStateAnimator { boolean scaleChanging = child.getScaleX() != viewState.scale; boolean alphaChanging = alpha != child.getAlpha(); boolean heightChanging = viewState.height != child.getActualHeight(); + boolean topInsetChanging = viewState.clipTopAmount != child.getClipTopAmount(); boolean wasAdded = mNewAddChildren.contains(child); boolean hasDelays = mAnimationFilter.hasDelays; boolean isDelayRelevant = yTranslationChanging || zTranslationChanging || scaleChanging || - alphaChanging || heightChanging; + alphaChanging || heightChanging || topInsetChanging; long delay = 0; if (hasDelays && isDelayRelevant || wasAdded) { delay = calculateChildAnimationDelay(viewState, finalState); @@ -167,6 +168,11 @@ public class StackStateAnimator { startHeightAnimation(child, viewState, delay); } + // start top inset animation + if (topInsetChanging) { + startInsetAnimation(child, viewState, delay); + } + // start dimmed animation child.setDimmed(viewState.dimmed, mAnimationFilter.animateDimmed); @@ -280,6 +286,64 @@ public class StackStateAnimator { child.setTag(TAG_END_HEIGHT, newEndValue); } + private void startInsetAnimation(final ExpandableView child, + StackScrollState.ViewState viewState, long delay) { + Integer previousStartValue = getChildTag(child, TAG_START_TOP_INSET); + Integer previousEndValue = getChildTag(child, TAG_END_TOP_INSET); + int newEndValue = viewState.clipTopAmount; + if (previousEndValue != null && previousEndValue == newEndValue) { + return; + } + ValueAnimator previousAnimator = getChildTag(child, TAG_ANIMATOR_TOP_INSET); + if (!mAnimationFilter.animateTopInset) { + // just a local update was performed + if (previousAnimator != null) { + // we need to increase all animation keyframes of the previous animator by the + // relative change to the end value + PropertyValuesHolder[] values = previousAnimator.getValues(); + int relativeDiff = newEndValue - previousEndValue; + int newStartValue = previousStartValue + relativeDiff; + values[0].setIntValues(newStartValue, newEndValue); + child.setTag(TAG_START_TOP_INSET, newStartValue); + child.setTag(TAG_END_TOP_INSET, newEndValue); + previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime()); + return; + } else { + // no new animation needed, let's just apply the value + child.setClipTopAmount(newEndValue); + return; + } + } + + ValueAnimator animator = ValueAnimator.ofInt(child.getClipTopAmount(), newEndValue); + animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(ValueAnimator animation) { + child.setClipTopAmount((int) animation.getAnimatedValue()); + } + }); + animator.setInterpolator(mFastOutSlowInInterpolator); + long newDuration = cancelAnimatorAndGetNewDuration(previousAnimator); + animator.setDuration(newDuration); + if (delay > 0 && (previousAnimator == null || !previousAnimator.isRunning())) { + animator.setStartDelay(delay); + } + animator.addListener(getGlobalAnimationFinishedListener()); + // remove the tag when the animation is finished + animator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + child.setTag(TAG_ANIMATOR_TOP_INSET, null); + child.setTag(TAG_START_TOP_INSET, null); + child.setTag(TAG_END_TOP_INSET, null); + } + }); + startAnimator(animator); + child.setTag(TAG_ANIMATOR_TOP_INSET, animator); + child.setTag(TAG_START_TOP_INSET, child.getClipTopAmount()); + child.setTag(TAG_END_TOP_INSET, newEndValue); + } + private void startAlphaAnimation(final ExpandableView child, final StackScrollState.ViewState viewState, long delay) { Float previousStartValue = getChildTag(child,TAG_START_ALPHA); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java index 25147b4..c2bd1cb 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java @@ -17,12 +17,12 @@ package com.android.systemui.statusbar.tv; import android.os.IBinder; +import android.service.notification.NotificationListenerService.Ranking; import android.service.notification.StatusBarNotification; import android.view.View; import android.view.ViewGroup.LayoutParams; import android.view.WindowManager; -import com.android.internal.policy.IKeyguardShowCallback; import com.android.internal.statusbar.StatusBarIcon; import com.android.systemui.statusbar.BaseStatusBar; @@ -50,7 +50,7 @@ public class TvStatusBar extends BaseStatusBar { } @Override - public void addNotificationInternal(StatusBarNotification notification) { + public void addNotificationInternal(StatusBarNotification notification, Ranking ranking) { } @Override @@ -58,7 +58,7 @@ public class TvStatusBar extends BaseStatusBar { } @Override - protected void removeNotificationInternal(String key) { + protected void removeNotificationInternal(String key, Ranking ranking) { } @Override @@ -117,7 +117,7 @@ public class TvStatusBar extends BaseStatusBar { } @Override - protected void updateNotificationIcons() { + protected void updateNotifications() { } @Override diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeComponent.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeComponent.java new file mode 100644 index 0000000..5ee8925 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeComponent.java @@ -0,0 +1,24 @@ +/* + * 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.volume; + +import com.android.systemui.statusbar.policy.ZenModeController; + +public interface VolumeComponent { + ZenModeController getZenController(); + void setVolumePanel(VolumePanel panel); +} diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumePanel.java b/packages/SystemUI/src/com/android/systemui/volume/VolumePanel.java index 8657e07..06f4c2e 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/VolumePanel.java +++ b/packages/SystemUI/src/com/android/systemui/volume/VolumePanel.java @@ -16,14 +16,12 @@ package com.android.systemui.volume; -import com.android.internal.R; - import android.app.AlertDialog; import android.app.Dialog; -import android.content.DialogInterface.OnDismissListener; import android.content.BroadcastReceiver; import android.content.Context; import android.content.DialogInterface; +import android.content.DialogInterface.OnDismissListener; import android.content.Intent; import android.content.IntentFilter; import android.content.res.Resources; @@ -42,6 +40,7 @@ import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; +import android.view.ViewStub; import android.view.Window; import android.view.WindowManager; import android.view.WindowManager.LayoutParams; @@ -49,6 +48,9 @@ import android.widget.ImageView; import android.widget.SeekBar; import android.widget.SeekBar.OnSeekBarChangeListener; +import com.android.internal.R; +import com.android.systemui.statusbar.policy.ZenModeController; + import java.util.HashMap; /** @@ -57,7 +59,6 @@ import java.util.HashMap; * @hide */ public class VolumePanel extends Handler { - private static final String TAG = VolumePanel.class.getSimpleName(); private static boolean LOGD = false; private static final int PLAY_SOUND_DELAY = AudioService.PLAY_SOUND_DELAY; @@ -88,33 +89,48 @@ public class VolumePanel extends Handler { private static final int MSG_REMOTE_VOLUME_UPDATE_IF_SHOWN = 9; private static final int MSG_SLIDER_VISIBILITY_CHANGED = 10; private static final int MSG_DISPLAY_SAFE_VOLUME_WARNING = 11; + private static final int MSG_LAYOUT_DIRECTION = 12; + private static final int MSG_ZEN_MODE_CHANGED = 13; // Pseudo stream type for master volume private static final int STREAM_MASTER = -100; // Pseudo stream type for remote volume is defined in AudioService.STREAM_REMOTE_MUSIC + private final String mTag; protected final Context mContext; private final AudioManager mAudioManager; + private final ZenModeController mZenController; private boolean mRingIsSilent; - private boolean mShowCombinedVolumes; private boolean mVoiceCapable; + private boolean mZenModeCapable; // True if we want to play tones on the system stream when the master stream is specified. private final boolean mPlayMasterStreamTones; - /** Dialog containing all the sliders */ - private final Dialog mDialog; - /** Dialog's content view */ + + /** Volume panel content view */ private final View mView; + /** Dialog hosting the panel, if not embedded */ + private final Dialog mDialog; + /** Parent view hosting the panel, if embedded */ + private final ViewGroup mParent; /** The visible portion of the volume overlay */ private final ViewGroup mPanel; - /** Contains the sliders and their touchable icons */ - private final ViewGroup mSliderGroup; - /** The button that expands the dialog to show all sliders */ - private final View mMoreButton; - /** Dummy divider icon that needs to vanish with the more button */ - private final View mDivider; + /** Contains the slider and its touchable icons */ + private final ViewGroup mSliderPanel; + /** The button that expands the dialog to show the zen panel */ + private final ImageView mExpandButton; + /** Dummy divider icon that needs to vanish with the expand button */ + private final View mExpandDivider; + /** The zen mode configuration panel view stub */ + private final ViewStub mZenPanelStub; + /** The zen mode configuration panel view, once inflated */ + private ZenModePanel mZenPanel; + /** Dummy divider icon that needs to vanish with the zen panel */ + private final View mZenPanelDivider; + + private ZenModePanel.Callback mZenPanelCallback; /** Currently active stream that shows up at the top of the list of sliders */ private int mActiveStreamType = -1; @@ -129,8 +145,8 @@ public class VolumePanel extends Handler { false), RingerStream(AudioManager.STREAM_RING, R.string.volume_icon_description_ringer, - R.drawable.ic_audio_ring_notif, - R.drawable.ic_audio_ring_notif_mute, + com.android.systemui.R.drawable.ic_ringer_audible, + com.android.systemui.R.drawable.ic_ringer_silent, false), VoiceStream(AudioManager.STREAM_VOICE_CALL, R.string.volume_icon_description_incall, @@ -149,8 +165,8 @@ public class VolumePanel extends Handler { true), NotificationStream(AudioManager.STREAM_NOTIFICATION, R.string.volume_icon_description_notification, - R.drawable.ic_audio_notification, - R.drawable.ic_audio_notification_mute, + com.android.systemui.R.drawable.ic_ringer_audible, + com.android.systemui.R.drawable.ic_ringer_silent, true), // for now, use media resources for master volume MasterStream(STREAM_MASTER, @@ -245,8 +261,11 @@ public class VolumePanel extends Handler { } - public VolumePanel(Context context) { + public VolumePanel(Context context, ViewGroup parent, ZenModeController zenController) { + mTag = String.format("VolumePanel%s.%08x", parent == null ? "Dialog" : "", hashCode()); mContext = context; + mParent = parent; + mZenController = zenController; mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); // For now, only show master volume if master volume is supported @@ -258,74 +277,81 @@ public class VolumePanel extends Handler { streamRes.show = (streamRes.streamType == STREAM_MASTER); } } - - mDialog = new Dialog(context) { - @Override - public boolean onTouchEvent(MotionEvent event) { - if (isShowing() && event.getAction() == MotionEvent.ACTION_OUTSIDE && - sConfirmSafeVolumeDialog == null) { - forceTimeout(); - return true; + if (LOGD) Log.d(mTag, String.format("new VolumePanel hasParent=%s", parent != null)); + final int layoutId = com.android.systemui.R.layout.volume_panel; + if (parent == null) { + // dialog mode + mDialog = new Dialog(context) { + @Override + public boolean onTouchEvent(MotionEvent event) { + if (isShowing() && event.getAction() == MotionEvent.ACTION_OUTSIDE && + sConfirmSafeVolumeDialog == null) { + forceTimeout(); + return true; + } + return false; } - return false; - } - }; - - // Change some window properties - final Window window = mDialog.getWindow(); - final LayoutParams lp = window.getAttributes(); - lp.token = null; - // Offset from the top - lp.y = res.getDimensionPixelOffset(R.dimen.volume_panel_top); - lp.type = LayoutParams.TYPE_VOLUME_OVERLAY; - lp.windowAnimations = R.style.Animation_VolumePanel; - window.setAttributes(lp); - window.setGravity(Gravity.TOP); - window.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND); - window.requestFeature(Window.FEATURE_NO_TITLE); - window.addFlags(LayoutParams.FLAG_NOT_FOCUSABLE - | LayoutParams.FLAG_NOT_TOUCH_MODAL - | LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH); - - mDialog.setCanceledOnTouchOutside(true); - mDialog.setContentView(R.layout.volume_adjust); - mDialog.setOnDismissListener(new OnDismissListener() { - @Override - public void onDismiss(DialogInterface dialog) { - mActiveStreamType = -1; - mAudioManager.forceVolumeControlStream(mActiveStreamType); - } - }); + }; + + // Change some window properties + final Window window = mDialog.getWindow(); + final LayoutParams lp = window.getAttributes(); + lp.token = null; + // Offset from the top + lp.y = res.getDimensionPixelOffset(com.android.systemui.R.dimen.volume_panel_top); + lp.width = res.getDimensionPixelSize(com.android.systemui.R.dimen.volume_panel_width); + lp.type = LayoutParams.TYPE_VOLUME_OVERLAY; + lp.windowAnimations = R.style.Animation_VolumePanel; + window.setBackgroundDrawableResource(com.android.systemui.R.drawable.qs_panel_background); + window.setAttributes(lp); + window.setGravity(Gravity.TOP); + window.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND); + window.requestFeature(Window.FEATURE_NO_TITLE); + window.addFlags(LayoutParams.FLAG_NOT_FOCUSABLE + | LayoutParams.FLAG_NOT_TOUCH_MODAL + | LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH); + mDialog.setCanceledOnTouchOutside(true); + mDialog.setContentView(layoutId); + mDialog.setOnDismissListener(new OnDismissListener() { + @Override + public void onDismiss(DialogInterface dialog) { + mActiveStreamType = -1; + mAudioManager.forceVolumeControlStream(mActiveStreamType); + } + }); - mDialog.create(); + mDialog.create(); - mView = window.findViewById(R.id.content); - mView.setOnTouchListener(new View.OnTouchListener() { - @Override - public boolean onTouch(View v, MotionEvent event) { - resetTimeout(); - return false; - } - }); + mView = window.findViewById(R.id.content); + mView.setOnTouchListener(new View.OnTouchListener() { + @Override + public boolean onTouch(View v, MotionEvent event) { + resetTimeout(); + return false; + } + }); - mPanel = (ViewGroup) mView.findViewById(R.id.visible_panel); - mSliderGroup = (ViewGroup) mView.findViewById(R.id.slider_group); - mMoreButton = mView.findViewById(R.id.expand_button); - mDivider = mView.findViewById(R.id.expand_button_divider); + } else { + // embedded mode + mDialog = null; + mView = LayoutInflater.from(mContext).inflate(layoutId, parent, true); + } + mPanel = (ViewGroup) mView.findViewById(com.android.systemui.R.id.visible_panel); + mSliderPanel = (ViewGroup) mView.findViewById(com.android.systemui.R.id.slider_panel); + mExpandButton = (ImageView) mView.findViewById(com.android.systemui.R.id.expand_button); + mExpandDivider = mView.findViewById(com.android.systemui.R.id.expand_button_divider); + mZenPanelStub = (ViewStub)mView.findViewById(com.android.systemui.R.id.zen_panel_stub); + mZenPanelDivider = mView.findViewById(com.android.systemui.R.id.zen_panel_divider); mToneGenerators = new ToneGenerator[AudioSystem.getNumStreamTypes()]; mVibrator = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE); mVoiceCapable = context.getResources().getBoolean(R.bool.config_voice_capable); - // If we don't want to show multiple volumes, hide the settings button - // and divider. - mShowCombinedVolumes = !mVoiceCapable && !useMasterVolume; - if (!mShowCombinedVolumes) { - mMoreButton.setVisibility(View.GONE); - mDivider.setVisibility(View.GONE); - } else { - mMoreButton.setOnClickListener(mClickListener); - } + mZenModeCapable = !useMasterVolume && mZenController != null; + mZenPanelDivider.setVisibility(View.GONE); + mExpandButton.setOnClickListener(mClickListener); + updateZenMode(mZenController == null ? false : mZenController.isZen()); + mZenController.addCallback(mZenCallback); final boolean masterVolumeOnly = res.getBoolean(R.bool.config_useMasterVolume); final boolean masterVolumeKeySounds = res.getBoolean(R.bool.config_useVolumeKeySounds); @@ -334,7 +360,7 @@ public class VolumePanel extends Handler { listenToRingerMode(); } - public void setLayoutDirection(int layoutDirection) { + private void setLayoutDirection(int layoutDirection) { mPanel.setLayoutDirection(layoutDirection); updateStates(); } @@ -406,21 +432,19 @@ public class VolumePanel extends Handler { StreamResources streamRes = STREAMS[i]; final int streamType = streamRes.streamType; - if (mVoiceCapable && streamRes == StreamResources.NotificationStream) { - streamRes = StreamResources.RingerStream; - } final StreamControl sc = new StreamControl(); sc.streamType = streamType; - sc.group = (ViewGroup) inflater.inflate(R.layout.volume_adjust_item, null); + sc.group = (ViewGroup) inflater.inflate( + com.android.systemui.R.layout.volume_panel_item, null); sc.group.setTag(sc); - sc.icon = (ImageView) sc.group.findViewById(R.id.stream_icon); + sc.icon = (ImageView) sc.group.findViewById(com.android.systemui.R.id.stream_icon); sc.icon.setTag(sc); sc.icon.setContentDescription(res.getString(streamRes.descRes)); sc.iconRes = streamRes.iconRes; sc.iconMuteRes = streamRes.iconMuteRes; sc.icon.setImageResource(sc.iconRes); - sc.seekbarView = (SeekBar) sc.group.findViewById(R.id.seekbar); + sc.seekbarView = (SeekBar) sc.group.findViewById(com.android.systemui.R.id.seekbar); final int plusOne = (streamType == AudioSystem.STREAM_BLUETOOTH_SCO || streamType == AudioSystem.STREAM_VOICE_CALL) ? 1 : 0; sc.seekbarView.setMax(getStreamMaxVolume(streamType) + plusOne); @@ -431,34 +455,18 @@ public class VolumePanel extends Handler { } private void reorderSliders(int activeStreamType) { - mSliderGroup.removeAllViews(); + mSliderPanel.removeAllViews(); final StreamControl active = mStreamControls.get(activeStreamType); if (active == null) { Log.e("VolumePanel", "Missing stream type! - " + activeStreamType); mActiveStreamType = -1; } else { - mSliderGroup.addView(active.group); + mSliderPanel.addView(active.group); mActiveStreamType = activeStreamType; active.group.setVisibility(View.VISIBLE); updateSlider(active); - } - - addOtherVolumes(); - } - - private void addOtherVolumes() { - if (!mShowCombinedVolumes) return; - - for (int i = 0; i < STREAMS.length; i++) { - // Skip the phone specific ones and the active one - final int streamType = STREAMS[i].streamType; - if (!STREAMS[i].show || streamType == mActiveStreamType) { - continue; - } - StreamControl sc = mStreamControls.get(streamType); - mSliderGroup.addView(sc.group); - updateSlider(sc); + updateZenMode(mZenController == null ? false : mZenController.isZen()); } } @@ -472,7 +480,7 @@ public class VolumePanel extends Handler { if (((sc.streamType == AudioManager.STREAM_RING) || (sc.streamType == AudioManager.STREAM_NOTIFICATION)) && mAudioManager.getRingerMode() == AudioManager.RINGER_MODE_VIBRATE) { - sc.icon.setImageResource(R.drawable.ic_audio_ring_notif_vibrate); + sc.icon.setImageResource(com.android.systemui.R.drawable.ic_ringer_vibrate); } if (sc.streamType == AudioService.STREAM_REMOTE_MUSIC) { // never disable touch interactions for remote playback, the muting is not tied to @@ -486,32 +494,70 @@ public class VolumePanel extends Handler { } } + public void setZenModePanelCallback(ZenModePanel.Callback callback) { + mZenPanelCallback = callback; + } + private void expand() { - final int count = mSliderGroup.getChildCount(); - for (int i = 0; i < count; i++) { - mSliderGroup.getChildAt(i).setVisibility(View.VISIBLE); + if (LOGD) Log.d(mTag, "expand mZenPanel=" + mZenPanel); + if (mZenPanel == null) { + mZenPanel = (ZenModePanel) mZenPanelStub.inflate(); + mZenPanel.init(mZenController); + mZenPanel.setCallback(new ZenModePanel.Callback() { + @Override + public void onMoreSettings() { + if (mZenPanelCallback != null) { + mZenPanelCallback.onMoreSettings(); + } + } + + @Override + public void onInteraction() { + if (mZenPanelCallback != null) { + mZenPanelCallback.onInteraction(); + } + } + }); } - mMoreButton.setVisibility(View.INVISIBLE); - mDivider.setVisibility(View.INVISIBLE); + mZenPanel.setVisibility(View.VISIBLE); + mZenPanelDivider.setVisibility(View.VISIBLE); } private void collapse() { - mMoreButton.setVisibility(View.VISIBLE); - mDivider.setVisibility(View.VISIBLE); - final int count = mSliderGroup.getChildCount(); - for (int i = 1; i < count; i++) { - mSliderGroup.getChildAt(i).setVisibility(View.GONE); + if (LOGD) Log.d(mTag, "collapse mZenPanel=" + mZenPanel); + if (mZenPanel != null) { + mZenPanel.setVisibility(View.GONE); } + mZenPanelDivider.setVisibility(View.GONE); } public void updateStates() { - final int count = mSliderGroup.getChildCount(); + final int count = mSliderPanel.getChildCount(); for (int i = 0; i < count; i++) { - StreamControl sc = (StreamControl) mSliderGroup.getChildAt(i).getTag(); + StreamControl sc = (StreamControl) mSliderPanel.getChildAt(i).getTag(); updateSlider(sc); } } + private void updateZenMode(boolean zen) { + if (mZenModeCapable) { + final boolean show = mActiveStreamType == AudioManager.STREAM_NOTIFICATION + || mActiveStreamType == AudioManager.STREAM_RING; + mExpandButton.setVisibility(show ? View.VISIBLE : View.GONE); + mExpandDivider.setVisibility(show ? View.VISIBLE : View.GONE); + mExpandButton.setImageResource(zen ? com.android.systemui.R.drawable.ic_vol_zen_on + : com.android.systemui.R.drawable.ic_vol_zen_off); + } else { + mExpandButton.setVisibility(View.GONE); + mExpandDivider.setVisibility(View.GONE); + } + } + + public void postZenModeChanged(boolean zen) { + removeMessages(MSG_ZEN_MODE_CHANGED); + obtainMessage(MSG_ZEN_MODE_CHANGED, zen ? 1 : 0).sendToTarget(); + } + public void postVolumeChanged(int streamType, int flags) { if (hasMessages(MSG_VOLUME_CHANGED)) return; synchronized (this) { @@ -582,8 +628,12 @@ public class VolumePanel extends Handler { } public void postDismiss() { - removeMessages(MSG_TIMEOUT); - sendEmptyMessage(MSG_TIMEOUT); + forceTimeout(); + } + + public void postLayoutDirection(int layoutDirection) { + removeMessages(MSG_LAYOUT_DIRECTION); + obtainMessage(MSG_LAYOUT_DIRECTION, layoutDirection).sendToTarget(); } /** @@ -593,7 +643,7 @@ public class VolumePanel extends Handler { */ protected void onVolumeChanged(int streamType, int flags) { - if (LOGD) Log.d(TAG, "onVolumeChanged(streamType: " + streamType + ", flags: " + flags + ")"); + if (LOGD) Log.d(mTag, "onVolumeChanged(streamType: " + streamType + ", flags: " + flags + ")"); if ((flags & AudioManager.FLAG_SHOW_UI) != 0) { synchronized (this) { @@ -622,7 +672,7 @@ public class VolumePanel extends Handler { protected void onMuteChanged(int streamType, int flags) { - if (LOGD) Log.d(TAG, "onMuteChanged(streamType: " + streamType + ", flags: " + flags + ")"); + if (LOGD) Log.d(mTag, "onMuteChanged(streamType: " + streamType + ", flags: " + flags + ")"); StreamControl sc = mStreamControls.get(streamType); if (sc != null) { @@ -638,7 +688,7 @@ public class VolumePanel extends Handler { mRingIsSilent = false; if (LOGD) { - Log.d(TAG, "onShowVolumeChanged(streamType: " + streamType + Log.d(mTag, "onShowVolumeChanged(streamType: " + streamType + ", flags: " + flags + "), index: " + index); } @@ -707,7 +757,7 @@ public class VolumePanel extends Handler { } case AudioService.STREAM_REMOTE_MUSIC: { - if (LOGD) { Log.d(TAG, "showing remote volume "+index+" over "+ max); } + if (LOGD) { Log.d(mTag, "showing remote volume "+index+" over "+ max); } break; } } @@ -730,16 +780,18 @@ public class VolumePanel extends Handler { } } - if (!mDialog.isShowing()) { + if (!isShowing()) { int stream = (streamType == AudioService.STREAM_REMOTE_MUSIC) ? -1 : streamType; // when the stream is for remote playback, use -1 to reset the stream type evaluation mAudioManager.forceVolumeControlStream(stream); // Showing dialog - use collapsed state - if (mShowCombinedVolumes) { + if (mZenModeCapable) { collapse(); } - mDialog.show(); + if (mDialog != null) { + mDialog.show(); + } } // Do a little vibrate if applicable (only when going into vibrate mode) @@ -751,6 +803,10 @@ public class VolumePanel extends Handler { } } + private boolean isShowing() { + return mDialog != null ? mDialog.isShowing() : mParent.isAttachedToWindow(); + } + protected void onPlaySound(int streamType, int flags) { if (hasMessages(MSG_STOP_SOUNDS)) { @@ -795,9 +851,9 @@ public class VolumePanel extends Handler { // streamType is the real stream type being affected, but for the UI sliders, we // refer to AudioService.STREAM_REMOTE_MUSIC. We still play the beeps on the real // stream type. - if (LOGD) Log.d(TAG, "onRemoteVolumeChanged(stream:"+streamType+", flags: " + flags + ")"); + if (LOGD) Log.d(mTag, "onRemoteVolumeChanged(stream:"+streamType+", flags: " + flags + ")"); - if (((flags & AudioManager.FLAG_SHOW_UI) != 0) || mDialog.isShowing()) { + if (((flags & AudioManager.FLAG_SHOW_UI) != 0) || isShowing()) { synchronized (this) { if (mActiveStreamType != AudioService.STREAM_REMOTE_MUSIC) { reorderSliders(AudioService.STREAM_REMOTE_MUSIC); @@ -805,7 +861,7 @@ public class VolumePanel extends Handler { onShowVolumeChanged(AudioService.STREAM_REMOTE_MUSIC, flags); } } else { - if (LOGD) Log.d(TAG, "not calling onShowVolumeChanged(), no FLAG_SHOW_UI or no UI"); + if (LOGD) Log.d(mTag, "not calling onShowVolumeChanged(), no FLAG_SHOW_UI or no UI"); } if ((flags & AudioManager.FLAG_PLAY_SOUND) != 0 && ! mRingIsSilent) { @@ -825,8 +881,8 @@ public class VolumePanel extends Handler { } protected void onRemoteVolumeUpdateIfShown() { - if (LOGD) Log.d(TAG, "onRemoteVolumeUpdateIfShown()"); - if (mDialog.isShowing() + if (LOGD) Log.d(mTag, "onRemoteVolumeUpdateIfShown()"); + if (isShowing() && (mActiveStreamType == AudioService.STREAM_REMOTE_MUSIC) && (mStreamControls != null)) { onShowVolumeChanged(AudioService.STREAM_REMOTE_MUSIC, 0); @@ -842,7 +898,7 @@ public class VolumePanel extends Handler { * @param visible */ synchronized protected void onSliderVisibilityChanged(int streamType, int visible) { - if (LOGD) Log.d(TAG, "onSliderVisibilityChanged(stream="+streamType+", visi="+visible+")"); + if (LOGD) Log.d(mTag, "onSliderVisibilityChanged(stream="+streamType+", visi="+visible+")"); boolean isVisible = (visible == 1); for (int i = STREAMS.length - 1 ; i >= 0 ; i--) { StreamResources streamRes = STREAMS[i]; @@ -857,7 +913,7 @@ public class VolumePanel extends Handler { } protected void onDisplaySafeVolumeWarning(int flags) { - if ((flags & AudioManager.FLAG_SHOW_UI) != 0 || mDialog.isShowing()) { + if ((flags & AudioManager.FLAG_SHOW_UI) != 0 || isShowing()) { synchronized (sConfirmSafeVolumeLock) { if (sConfirmSafeVolumeDialog != null) { return; @@ -907,7 +963,7 @@ public class VolumePanel extends Handler { mToneGenerators[streamType] = new ToneGenerator(streamType, MAX_VOLUME); } catch (RuntimeException e) { if (LOGD) { - Log.d(TAG, "ToneGenerator constructor failed with " + Log.d(mTag, "ToneGenerator constructor failed with " + "RuntimeException: " + e); } } @@ -976,8 +1032,10 @@ public class VolumePanel extends Handler { } case MSG_TIMEOUT: { - if (mDialog.isShowing()) { - mDialog.dismiss(); + if (isShowing()) { + if (mDialog != null) { + mDialog.dismiss(); + } mActiveStreamType = -1; } synchronized (sConfirmSafeVolumeLock) { @@ -988,7 +1046,7 @@ public class VolumePanel extends Handler { break; } case MSG_RINGER_MODE_CHANGED: { - if (mDialog.isShowing()) { + if (isShowing()) { updateStates(); } break; @@ -1010,17 +1068,30 @@ public class VolumePanel extends Handler { case MSG_DISPLAY_SAFE_VOLUME_WARNING: onDisplaySafeVolumeWarning(msg.arg1); break; + + case MSG_LAYOUT_DIRECTION: + setLayoutDirection(msg.arg1); + break; + + case MSG_ZEN_MODE_CHANGED: + updateZenMode(msg.arg1 != 0); + break; } } - private void resetTimeout() { + public void resetTimeout() { + if (LOGD) Log.d(mTag, "resetTimeout at " + System.currentTimeMillis()); removeMessages(MSG_TIMEOUT); - sendMessageDelayed(obtainMessage(MSG_TIMEOUT), TIMEOUT_DELAY); + sendEmptyMessageDelayed(MSG_TIMEOUT, TIMEOUT_DELAY); } private void forceTimeout() { removeMessages(MSG_TIMEOUT); - sendMessage(obtainMessage(MSG_TIMEOUT)); + sendEmptyMessage(MSG_TIMEOUT); + } + + public ZenModeController getZenController() { + return mZenController; } private final OnSeekBarChangeListener mSeekListener = new OnSeekBarChangeListener() { @@ -1061,10 +1132,22 @@ public class VolumePanel extends Handler { private final View.OnClickListener mClickListener = new View.OnClickListener() { @Override public void onClick(View v) { - if (v == mMoreButton) { - expand(); + if (v == mExpandButton && mZenController != null) { + final boolean newZen = !mZenController.isZen(); + mZenController.setZen(newZen); + if (newZen) { + expand(); + } else { + collapse(); + } } resetTimeout(); } }; + + private final ZenModeController.Callback mZenCallback = new ZenModeController.Callback() { + public void onZenChanged(boolean zen) { + updateZenMode(zen); + } + }; } diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java index 9bd75b7..7da90d8 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java +++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java @@ -1,16 +1,22 @@ package com.android.systemui.volume; import android.content.Context; +import android.content.Intent; +import android.content.res.Resources; import android.database.ContentObserver; import android.media.AudioManager; import android.media.IVolumeController; import android.net.Uri; import android.os.Handler; import android.os.RemoteException; +import android.os.UserHandle; import android.provider.Settings; import android.util.Log; +import com.android.systemui.R; import com.android.systemui.SystemUI; +import com.android.systemui.statusbar.policy.ZenModeController; +import com.android.systemui.statusbar.policy.ZenModeControllerImpl; /* * Copyright (C) 2014 The Android Open Source Project @@ -34,21 +40,21 @@ public class VolumeUI extends SystemUI { private static final Uri SETTING_URI = Settings.Global.getUriFor(SETTING); private static final int DEFAULT = 1; // enabled by default + private final Handler mHandler = new Handler(); private AudioManager mAudioManager; private VolumeController mVolumeController; @Override public void start() { mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE); + mVolumeController = new VolumeController(mContext); + putComponent(VolumeComponent.class, mVolumeController); updateController(); mContext.getContentResolver().registerContentObserver(SETTING_URI, false, mObserver); } private void updateController() { if (Settings.Global.getInt(mContext.getContentResolver(), SETTING, DEFAULT) != 0) { - if (mVolumeController == null) { - mVolumeController = new VolumeController(mContext); - } Log.d(TAG, "Registering volume controller"); mAudioManager.setVolumeController(mVolumeController); } else { @@ -57,7 +63,7 @@ public class VolumeUI extends SystemUI { } } - private final ContentObserver mObserver = new ContentObserver(new Handler()) { + private final ContentObserver mObserver = new ContentObserver(mHandler) { public void onChange(boolean selfChange, Uri uri) { if (SETTING_URI.equals(uri)) { updateController(); @@ -66,13 +72,38 @@ public class VolumeUI extends SystemUI { }; /** For now, simply host an unmodified base volume panel in this process. */ - private final class VolumeController extends IVolumeController.Stub { - private final VolumePanel mPanel; + private final class VolumeController extends IVolumeController.Stub implements VolumeComponent { + private final VolumePanel mDialogPanel; + private VolumePanel mPanel; public VolumeController(Context context) { - mPanel = new VolumePanel(context); + mPanel = new VolumePanel(context, null, new ZenModeControllerImpl(mContext, mHandler)); + final int delay = context.getResources().getInteger(R.integer.feedback_start_delay); + mPanel.setZenModePanelCallback(new ZenModePanel.Callback() { + @Override + public void onMoreSettings() { + mHandler.removeCallbacks(mStartZenSettings); + mHandler.postDelayed(mStartZenSettings, delay); + } + + @Override + public void onInteraction() { + mDialogPanel.resetTimeout(); + } + }); + mDialogPanel = mPanel; } + private final Runnable mStartZenSettings = new Runnable() { + @Override + public void run() { + mDialogPanel.postDismiss(); + final Intent intent = ZenModePanel.ZEN_SETTINGS; + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP); + mContext.startActivityAsUser(intent, new UserHandle(UserHandle.USER_CURRENT)); + } + }; + @Override public void hasNewRemotePlaybackInfo() throws RemoteException { mPanel.postHasNewRemotePlaybackInfo(); @@ -114,12 +145,22 @@ public class VolumeUI extends SystemUI { @Override public void setLayoutDirection(int layoutDirection) throws RemoteException { - mPanel.setLayoutDirection(layoutDirection); + mPanel.postLayoutDirection(layoutDirection); } @Override public void dismiss() throws RemoteException { mPanel.postDismiss(); } + + @Override + public ZenModeController getZenController() { + return mDialogPanel.getZenController(); + } + + @Override + public void setVolumePanel(VolumePanel panel) { + mPanel = panel == null ? mDialogPanel : panel; + } } } diff --git a/packages/SystemUI/src/com/android/systemui/volume/ZenModePanel.java b/packages/SystemUI/src/com/android/systemui/volume/ZenModePanel.java new file mode 100644 index 0000000..77d267e --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/volume/ZenModePanel.java @@ -0,0 +1,248 @@ +/* + * 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.volume; + +import android.content.Context; +import android.content.Intent; +import android.net.Uri; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import android.provider.Settings; +import android.service.notification.Condition; +import android.util.AttributeSet; +import android.view.ContextThemeWrapper; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.CompoundButton; +import android.widget.CompoundButton.OnCheckedChangeListener; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.RadioButton; +import android.widget.TextView; + +import com.android.systemui.R; +import com.android.systemui.statusbar.policy.ZenModeController; + +import java.util.Arrays; +import java.util.HashSet; + +public class ZenModePanel extends LinearLayout { + private static final int[] MINUTES = new int[] { 15, 30, 45, 60, 120, 180, 240, 480 }; + public static final Intent ZEN_SETTINGS = new Intent(Settings.ACTION_ZEN_MODE_SETTINGS); + + private final LayoutInflater mInflater; + private final HashSet<RadioButton> mRadioButtons = new HashSet<RadioButton>(); + private final H mHandler = new H(); + private LinearLayout mConditions; + private int mMinutesIndex = Arrays.binarySearch(MINUTES, 60); // default to one hour + private Callback mCallback; + private ZenModeController mController; + private boolean mRequestingConditions; + + public ZenModePanel(Context context, AttributeSet attrs) { + super(context, attrs); + mInflater = LayoutInflater.from(new ContextThemeWrapper(context, R.style.QSWhiteTheme)); + } + + @Override + protected void onFinishInflate() { + super.onFinishInflate(); + mConditions = (LinearLayout) findViewById(android.R.id.content); + findViewById(android.R.id.button2).setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + fireMoreSettings(); + } + }); + } + + @Override + public void setVisibility(int visibility) { + super.setVisibility(visibility); + setRequestingConditions(visibility == VISIBLE); + } + + /** Start or stop requesting relevant zen mode exit conditions */ + private void setRequestingConditions(boolean requesting) { + if (mRequestingConditions == requesting) return; + mRequestingConditions = requesting; + if (mRequestingConditions) { + mController.addCallback(mZenCallback); + } else { + mController.removeCallback(mZenCallback); + } + mController.requestConditions(mRequestingConditions); + } + + public void init(ZenModeController controller) { + mController = controller; + mConditions.removeAllViews(); + bind(updateTimeCondition(), mConditions.getChildAt(0)); + handleUpdateConditions(new Condition[0]); + } + + public void setCallback(Callback callback) { + mCallback = callback; + } + + private Condition updateTimeCondition() { + final int minutes = MINUTES[mMinutesIndex]; + final long millis = System.currentTimeMillis() + minutes * 60 * 1000; + final Uri id = new Uri.Builder().scheme(Condition.SCHEME).authority("android") + .appendPath("countdown").appendPath(Long.toString(millis)).build(); + final int num = minutes < 60 ? minutes : minutes / 60; + final int resId = minutes < 60 + ? R.plurals.zen_mode_duration_minutes + : R.plurals.zen_mode_duration_hours; + final String caption = mContext.getResources().getQuantityString(resId, num, num); + return new Condition(id, caption, "", "", 0, Condition.STATE_TRUE, + Condition.FLAG_RELEVANT_NOW); + } + + private void handleUpdateConditions(Condition[] conditions) { + final int newCount = conditions == null ? 0 : conditions.length; + for (int i = mConditions.getChildCount() - 1; i > newCount; i--) { + mConditions.removeViewAt(i); + } + for (int i = 0; i < newCount; i++) { + bind(conditions[i], mConditions.getChildAt(i + 1)); + } + bind(null, mConditions.getChildAt(newCount + 1)); + } + + private void editTimeCondition(int delta) { + final int i = mMinutesIndex + delta; + if (i < 0 || i >= MINUTES.length) return; + mMinutesIndex = i; + final Condition c = updateTimeCondition(); + bind(c, mConditions.getChildAt(0)); + } + + private void bind(final Condition condition, View convertView) { + final boolean enabled = condition == null || condition.state == Condition.STATE_TRUE; + final View row; + if (convertView == null) { + row = mInflater.inflate(R.layout.zen_mode_condition, this, false); + mConditions.addView(row); + } else { + row = convertView; + } + final int position = mConditions.indexOfChild(row); + final RadioButton rb = (RadioButton) row.findViewById(android.R.id.checkbox); + mRadioButtons.add(rb); + rb.setEnabled(enabled); + rb.setOnCheckedChangeListener(new OnCheckedChangeListener() { + @Override + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { + if (isChecked) { + for (RadioButton otherButton : mRadioButtons) { + if (otherButton == rb) continue; + otherButton.setChecked(false); + } + mController.select(condition); + fireInteraction(); + } + } + }); + final TextView title = (TextView) row.findViewById(android.R.id.title); + if (condition == null) { + title.setText(R.string.zen_mode_forever); + } else { + title.setText(condition.summary); + } + title.setEnabled(enabled); + title.setAlpha(enabled ? 1 : .5f); + final ImageView button1 = (ImageView) row.findViewById(android.R.id.button1); + button1.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + rb.setChecked(true); + editTimeCondition(-1); + fireInteraction(); + } + }); + + final ImageView button2 = (ImageView) row.findViewById(android.R.id.button2); + button2.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + rb.setChecked(true); + editTimeCondition(1); + fireInteraction(); + } + }); + title.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + rb.setChecked(true); + fireInteraction(); + } + }); + if (position == 0) { + button1.setEnabled(mMinutesIndex > 0); + button2.setEnabled(mMinutesIndex < MINUTES.length - 1); + button1.setImageAlpha(button1.isEnabled() ? 0xff : 0x7f); + button2.setImageAlpha(button2.isEnabled() ? 0xff : 0x7f); + } else { + button1.setVisibility(View.GONE); + button2.setVisibility(View.GONE); + } + if (position == 0 && mConditions.getChildCount() == 1) { + rb.setChecked(true); + } + } + + private void fireMoreSettings() { + if (mCallback != null) { + mCallback.onMoreSettings(); + } + } + + private void fireInteraction() { + if (mCallback != null) { + mCallback.onInteraction(); + } + } + + private final ZenModeController.Callback mZenCallback = new ZenModeController.Callback() { + @Override + public void onConditionsChanged(Condition[] conditions) { + mHandler.obtainMessage(H.UPDATE_CONDITIONS, conditions).sendToTarget(); + } + }; + + private final class H extends Handler { + private static final int UPDATE_CONDITIONS = 1; + + private H() { + super(Looper.getMainLooper()); + } + + @Override + public void handleMessage(Message msg) { + if (msg.what == UPDATE_CONDITIONS) { + handleUpdateConditions((Condition[])msg.obj); + } + } + } + + public interface Callback { + void onMoreSettings(); + void onInteraction(); + } +} |