summaryrefslogtreecommitdiffstats
path: root/packages/SystemUI/src/com
diff options
context:
space:
mode:
Diffstat (limited to 'packages/SystemUI/src/com')
-rw-r--r--packages/SystemUI/src/com/android/systemui/BatteryLevelTextView.java139
-rwxr-xr-xpackages/SystemUI/src/com/android/systemui/BatteryMeterView.java980
-rw-r--r--packages/SystemUI/src/com/android/systemui/DockBatteryLevelTextView.java30
-rwxr-xr-xpackages/SystemUI/src/com/android/systemui/DockBatteryMeterView.java150
-rw-r--r--packages/SystemUI/src/com/android/systemui/EventLogTags.logtags25
-rw-r--r--packages/SystemUI/src/com/android/systemui/ExpandHelper.java13
-rw-r--r--packages/SystemUI/src/com/android/systemui/ImageWallpaper.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/SwipeHelper.java98
-rw-r--r--packages/SystemUI/src/com/android/systemui/SystemUIApplication.java16
-rw-r--r--packages/SystemUI/src/com/android/systemui/cm/GlowBackground.java83
-rw-r--r--packages/SystemUI/src/com/android/systemui/cm/LockscreenShortcutsActivity.java265
-rw-r--r--packages/SystemUI/src/com/android/systemui/cm/LockscreenShortcutsHelper.java201
-rw-r--r--packages/SystemUI/src/com/android/systemui/cm/SpamMessageProvider.java204
-rw-r--r--packages/SystemUI/src/com/android/systemui/cm/SpamOpenHelper.java46
-rw-r--r--packages/SystemUI/src/com/android/systemui/cm/UserContentObserver.java89
-rw-r--r--packages/SystemUI/src/com/android/systemui/doze/DozeService.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/egg/CMLand.java43
-rw-r--r--packages/SystemUI/src/com/android/systemui/egg/MLand.java13
-rw-r--r--packages/SystemUI/src/com/android/systemui/egg/MLandActivity.java10
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java6
-rwxr-xr-x[-rw-r--r--]packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java348
-rw-r--r--packages/SystemUI/src/com/android/systemui/power/PowerUI.java48
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSBooleanSettingRow.java178
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSContainer.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSDetailClipper.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSDetailItemsGrid.java154
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSDetailItemsList.java213
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSDragPanel.java2331
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSPage.java161
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSPanel.java135
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSPanelTopView.java377
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSSettings.java141
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSTile.java89
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSTileView.java158
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSViewPager.java105
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/AdbOverNetworkTile.java130
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/AirplaneModeTile.java10
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/AmbientDisplayTile.java107
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/BatterySaverTile.java136
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java86
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/CaffeineTile.java202
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java25
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java68
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/ColorInversionTile.java41
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/CompassTile.java194
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/CustomQSTile.java349
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java24
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/EditTile.java98
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/HeadsUpTile.java107
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java90
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/LocationTile.java216
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/LockscreenToggleTile.java163
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/NfcTile.java158
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/PerfProfileTile.java258
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/ProfilesTile.java287
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/RotationLockTile.java11
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenTimeoutTile.java344
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/SyncTile.java109
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/UsbTetherTile.java119
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/VolumeTile.java68
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java116
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/Constants.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/Recents.java39
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java66
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java17
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java30
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java13
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java91
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java11
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java9
-rw-r--r--packages/SystemUI/src/com/android/systemui/settings/BrightnessController.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/settings/NotificationBrightnessController.java247
-rw-r--r--packages/SystemUI/src/com/android/systemui/settings/NotificationBrightnessDialog.java71
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java30
-rwxr-xr-x[-rw-r--r--]packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java309
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java13
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/CustomTileData.java68
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java28
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/ExpandableOutlineView.java10
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/ExpandableView.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/KeyguardAffordanceView.java66
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java69
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/MediaExpandableNotificationRow.java231
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/MediaNotificationGuts.java93
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java16
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationGuts.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/QueueView.java254
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/QueueViewRow.java100
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/SignalClusterView.java87
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/StackScrollerDecorView.java1
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java132
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/VisualizerView.java395
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/BackButtonDrawable.java139
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/BarTransitions.java68
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/BlurLayer.java196
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/ClockController.java149
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java42
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeScrimController.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/FingerprintUnlockController.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/IconMerger.java20
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardAffordanceHelper.java20
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java336
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java54
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java101
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/LockIcon.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserSwitch.java30
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/NavBarInsetLayout.java218
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/NavbarEditor.java580
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarTransitions.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java414
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java688
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java266
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java1492
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java271
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarTransitions.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/QSTileHost.java271
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/SettingsButton.java42
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeaderView.java355
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java51
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java84
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowManager.java172
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java187
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java16
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/ViewLinker.java76
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java91
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryStateRegistar.java28
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/BrightnessMirrorController.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/CallbackHandler.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/CastControllerImpl.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java26
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/DateView.java15
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/DeadZone.java34
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/DockBatteryController.java159
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/FlashlightController.java117
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java12
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonRipple.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonView.java145
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/LiveLockScreenController.java349
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationController.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java63
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java54
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java11
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/RotationLockControllerImpl.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/SignalCallbackAdapter.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/SignalController.java1
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/SuController.java30
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/SuControllerImpl.java132
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/TelephonyIcons.java62
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/UserInfoController.java42
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/WeatherController.java33
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/WeatherControllerImpl.java184
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java26
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java12
-rw-r--r--packages/SystemUI/src/com/android/systemui/tuner/DemoModeFragment.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/tuner/QsTuner.java279
-rw-r--r--packages/SystemUI/src/com/android/systemui/tuner/StatusBarIconBlacklistFragment.java71
-rw-r--r--packages/SystemUI/src/com/android/systemui/tuner/TunerActivity.java41
-rw-r--r--packages/SystemUI/src/com/android/systemui/tuner/TunerFragment.java48
-rw-r--r--packages/SystemUI/src/com/android/systemui/tuner/TunerService.java51
-rw-r--r--packages/SystemUI/src/com/android/systemui/usb/StorageNotification.java12
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/VolumeDialog.java147
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/VolumeDialogComponent.java9
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/VolumeDialogController.java117
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java18
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/ZenFooter.java26
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/ZenModePanel.java18
-rw-r--r--packages/SystemUI/src/com/viewpagerindicator/CirclePageIndicator.java527
-rw-r--r--packages/SystemUI/src/com/viewpagerindicator/PageIndicator.java63
173 files changed, 20459 insertions, 1880 deletions
diff --git a/packages/SystemUI/src/com/android/systemui/BatteryLevelTextView.java b/packages/SystemUI/src/com/android/systemui/BatteryLevelTextView.java
new file mode 100644
index 0000000..247e965
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/BatteryLevelTextView.java
@@ -0,0 +1,139 @@
+/*
+ * Copyright (C) 2014 The CyanogenMod Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui;
+
+import com.android.systemui.statusbar.policy.BatteryController;
+import com.android.systemui.statusbar.policy.BatteryStateRegistar;
+
+import android.content.Context;
+import android.content.res.Configuration;
+import android.util.AttributeSet;
+import android.util.TypedValue;
+import android.widget.TextView;
+
+import java.text.NumberFormat;
+
+public class BatteryLevelTextView extends TextView implements
+ BatteryController.BatteryStateChangeCallback{
+
+ private BatteryStateRegistar mBatteryStateRegistar;
+ private boolean mBatteryPresent;
+
+ private boolean mBatteryCharging;
+ private boolean mForceShow;
+ private boolean mAttached;
+ private int mRequestedVisibility;
+ private int mStyle;
+ private int mPercentMode;
+
+ public BatteryLevelTextView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ // setBatteryStateRegistar (if called) will made the view visible and ready to be hidden
+ // if the view shouldn't be displayed. Otherwise this view should be hidden from start.
+ mRequestedVisibility = GONE;
+ }
+
+ public void setForceShown(boolean forceShow) {
+ mForceShow = forceShow;
+ updateVisibility();
+ }
+
+ public void setBatteryStateRegistar(BatteryStateRegistar batteryStateRegistar) {
+ mRequestedVisibility = VISIBLE;
+ mBatteryStateRegistar = batteryStateRegistar;
+ if (mAttached) {
+ mBatteryStateRegistar.addStateChangedCallback(this);
+ }
+ }
+
+ @Override
+ public void setVisibility(int visibility) {
+ mRequestedVisibility = visibility;
+ updateVisibility();
+ }
+
+ @Override
+ protected void onConfigurationChanged(Configuration newConfig) {
+ super.onConfigurationChanged(newConfig);
+
+ // Respect font size setting.
+ setTextSize(TypedValue.COMPLEX_UNIT_PX,
+ getResources().getDimensionPixelSize(R.dimen.battery_level_text_size));
+ }
+
+ @Override
+ public void onBatteryLevelChanged(boolean present, int level, boolean pluggedIn,
+ boolean charging) {
+ String percentage = NumberFormat.getPercentInstance().format((double) level / 100.0);
+ setText(percentage);
+ if (mBatteryPresent != present || mBatteryCharging != charging) {
+ mBatteryPresent = present;
+ mBatteryCharging = charging;
+ updateVisibility();
+ }
+ }
+
+ @Override
+ public void onPowerSaveChanged() {
+ // Not used
+ }
+
+ @Override
+ public void onBatteryStyleChanged(int style, int percentMode) {
+ mStyle = style;
+ mPercentMode = percentMode;
+ updateVisibility();
+ }
+
+ @Override
+ public void onAttachedToWindow() {
+ super.onAttachedToWindow();
+
+ if (mBatteryStateRegistar != null) {
+ mBatteryStateRegistar.addStateChangedCallback(this);
+ }
+
+ mAttached = true;
+ }
+
+ @Override
+ public void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+ mAttached = false;
+
+ if (mBatteryStateRegistar != null) {
+ mBatteryStateRegistar.removeStateChangedCallback(this);
+ }
+ }
+
+ private void updateVisibility() {
+ boolean showNextPercent = mBatteryPresent && (
+ mPercentMode == BatteryController.PERCENTAGE_MODE_OUTSIDE
+ || (mBatteryCharging && mPercentMode == BatteryController.PERCENTAGE_MODE_INSIDE));
+ if (mStyle == BatteryController.STYLE_GONE) {
+ showNextPercent = false;
+ } else if (mStyle == BatteryController.STYLE_TEXT) {
+ showNextPercent = true;
+ }
+
+ if (mBatteryStateRegistar != null && (showNextPercent || mForceShow)) {
+ super.setVisibility(mRequestedVisibility);
+ } else {
+ super.setVisibility(GONE);
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java b/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java
index 95b58e5..06c2957 100755
--- a/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java
+++ b/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java
@@ -1,5 +1,6 @@
/*
- * Copyright (C) 2013 The Android Open Source Project
+ * Copyright (C) 2013-14 The Android Open Source Project
+ * Copyright (C) 2016 The CyanogenMod Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -21,63 +22,59 @@ import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.pm.PackageManager;
import android.content.res.Resources;
+import android.content.res.ThemeConfig;
import android.content.res.TypedArray;
-import android.database.ContentObserver;
+import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
-import android.graphics.Path;
-import android.graphics.RectF;
+import android.graphics.PorterDuff;
+import android.graphics.PorterDuffXfermode;
+import android.graphics.Rect;
import android.graphics.Typeface;
-import android.net.Uri;
+import android.graphics.drawable.AnimatedVectorDrawable;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.LayerDrawable;
import android.os.BatteryManager;
import android.os.Bundle;
-import android.os.Handler;
-import android.provider.Settings;
import android.util.AttributeSet;
+import android.util.Log;
+import android.view.Gravity;
import android.view.View;
import com.android.systemui.statusbar.policy.BatteryController;
+import com.android.systemui.statusbar.policy.BatteryStateRegistar;
+
+import org.cyanogenmod.graphics.drawable.StopMotionVectorDrawable;
public class BatteryMeterView extends View implements DemoMode,
BatteryController.BatteryStateChangeCallback {
public static final String TAG = BatteryMeterView.class.getSimpleName();
public static final String ACTION_LEVEL_TEST = "com.android.systemui.BATTERY_LEVEL_TEST";
- public static final String SHOW_PERCENT_SETTING = "status_bar_show_battery_percent";
-
- private static final boolean SINGLE_DIGIT_PERCENT = false;
-
- private static final int FULL = 96;
-
- private static final float BOLT_LEVEL_THRESHOLD = 0.3f; // opaque bolt below this fraction
private final int[] mColors;
- private boolean mShowPercent;
- private float mButtonHeightFraction;
- private float mSubpixelSmoothingLeft;
- private float mSubpixelSmoothingRight;
- private final Paint mFramePaint, mBatteryPaint, mWarningTextPaint, mTextPaint, mBoltPaint;
- private float mTextHeight, mWarningTextHeight;
- private int mIconTint = Color.WHITE;
+ protected boolean mShowPercent = true;
+
+ public enum BatteryMeterMode {
+ BATTERY_METER_GONE,
+ BATTERY_METER_ICON_PORTRAIT,
+ BATTERY_METER_ICON_LANDSCAPE,
+ BATTERY_METER_CIRCLE,
+ BATTERY_METER_TEXT
+ }
private int mHeight;
private int mWidth;
private String mWarningString;
private final int mCriticalLevel;
- private int mChargeColor;
- private final float[] mBoltPoints;
- private final Path mBoltPath = new Path();
-
- private final RectF mFrame = new RectF();
- private final RectF mButtonFrame = new RectF();
- private final RectF mBoltFrame = new RectF();
- private final Path mShapePath = new Path();
- private final Path mClipPath = new Path();
- private final Path mTextPath = new Path();
+ private boolean mAnimationsEnabled;
+ private BatteryStateRegistar mBatteryStateRegistar;
private BatteryController mBatteryController;
private boolean mPowerSaveEnabled;
@@ -87,8 +84,135 @@ public class BatteryMeterView extends View implements DemoMode,
private int mLightModeBackgroundColor;
private int mLightModeFillColor;
- private BatteryTracker mTracker = new BatteryTracker();
- private final SettingObserver mSettingObserver = new SettingObserver();
+ protected BatteryMeterMode mMeterMode = null;
+
+ protected boolean mAttached;
+
+ private boolean mDemoMode;
+ protected BatteryTracker mDemoTracker = new BatteryTracker();
+ protected BatteryTracker mTracker = new BatteryTracker();
+ private BatteryMeterDrawable mBatteryMeterDrawable;
+ private int mIconTint = Color.WHITE;
+
+ private int mCurrentBackgroundColor = 0;
+ private int mCurrentFillColor = 0;
+
+ protected class BatteryTracker extends BroadcastReceiver {
+ public static final int UNKNOWN_LEVEL = -1;
+
+ // current battery status
+ boolean present = true;
+ int level = UNKNOWN_LEVEL;
+ String percentStr;
+ int plugType;
+ boolean plugged;
+ int health;
+ int status;
+ String technology;
+ int voltage;
+ int temperature;
+ boolean testmode = false;
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ final String action = intent.getAction();
+ if (action.equals(Intent.ACTION_BATTERY_CHANGED)) {
+ if (testmode && ! intent.getBooleanExtra("testmode", false)) return;
+
+ level = (int)(100f
+ * intent.getIntExtra(BatteryManager.EXTRA_LEVEL, 0)
+ / intent.getIntExtra(BatteryManager.EXTRA_SCALE, 100));
+
+ present = intent.getBooleanExtra(BatteryManager.EXTRA_PRESENT, true);
+ plugType = intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, 0);
+ plugged = plugType != 0;
+ health = intent.getIntExtra(BatteryManager.EXTRA_HEALTH,
+ BatteryManager.BATTERY_HEALTH_UNKNOWN);
+ status = intent.getIntExtra(BatteryManager.EXTRA_STATUS,
+ BatteryManager.BATTERY_STATUS_UNKNOWN);
+ technology = intent.getStringExtra(BatteryManager.EXTRA_TECHNOLOGY);
+ voltage = intent.getIntExtra(BatteryManager.EXTRA_VOLTAGE, 0);
+ temperature = intent.getIntExtra(BatteryManager.EXTRA_TEMPERATURE, 0);
+ setContentDescription(
+ context.getString(R.string.accessibility_battery_level, level));
+ if (mBatteryMeterDrawable != null) {
+ setVisibility(View.VISIBLE);
+ invalidate();
+ }
+ } else if (action.equals(ACTION_LEVEL_TEST)) {
+ testmode = true;
+ post(new Runnable() {
+ int curLevel = 0;
+ int incr = 1;
+ int saveLevel = level;
+ int savePlugged = plugType;
+ Intent dummy = new Intent(Intent.ACTION_BATTERY_CHANGED);
+ @Override
+ public void run() {
+ if (curLevel < 0) {
+ testmode = false;
+ dummy.putExtra("level", saveLevel);
+ dummy.putExtra("plugged", savePlugged);
+ dummy.putExtra("testmode", false);
+ } else {
+ dummy.putExtra("level", curLevel);
+ dummy.putExtra("plugged", incr > 0
+ ? BatteryManager.BATTERY_PLUGGED_AC : 0);
+ dummy.putExtra("testmode", true);
+ }
+ getContext().sendBroadcast(dummy);
+
+ if (!testmode) return;
+
+ curLevel += incr;
+ if (curLevel == 100) {
+ incr *= -1;
+ }
+ postDelayed(this, 200);
+ }
+ });
+ }
+ }
+
+ protected boolean shouldIndicateCharging() {
+ if (status == BatteryManager.BATTERY_STATUS_CHARGING) {
+ return true;
+ }
+ if (plugged) {
+ return status == BatteryManager.BATTERY_STATUS_FULL;
+ }
+ return false;
+ }
+ }
+
+ @Override
+ public void onAttachedToWindow() {
+ super.onAttachedToWindow();
+
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(Intent.ACTION_BATTERY_CHANGED);
+ filter.addAction(ACTION_LEVEL_TEST);
+ final Intent sticky = getContext().registerReceiver(mTracker, filter);
+ if (sticky != null) {
+ // preload the battery level
+ mTracker.onReceive(getContext(), sticky);
+ }
+ if (mBatteryStateRegistar != null) {
+ mBatteryStateRegistar.addStateChangedCallback(this);
+ }
+ mAttached = true;
+ }
+
+ @Override
+ public void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+
+ mAttached = false;
+ getContext().unregisterReceiver(mTracker);
+ if (mBatteryStateRegistar != null) {
+ mBatteryStateRegistar.removeStateChangedCallback(this);
+ }
+ }
public BatteryMeterView(Context context) {
this(context, null, 0);
@@ -104,8 +228,6 @@ public class BatteryMeterView extends View implements DemoMode,
final Resources res = context.getResources();
TypedArray atts = context.obtainStyledAttributes(attrs, R.styleable.BatteryMeterView,
defStyle, 0);
- final int frameColor = atts.getColor(R.styleable.BatteryMeterView_frameColor,
- context.getColor(R.color.batterymeter_frame_color));
TypedArray levels = res.obtainTypedArray(R.array.batterymeter_color_levels);
TypedArray colors = res.obtainTypedArray(R.array.batterymeter_color_values);
@@ -118,44 +240,9 @@ public class BatteryMeterView extends View implements DemoMode,
levels.recycle();
colors.recycle();
atts.recycle();
- updateShowPercent();
mWarningString = context.getString(R.string.battery_meter_very_low_overlay_symbol);
- mCriticalLevel = mContext.getResources().getInteger(
+ mCriticalLevel = getContext().getResources().getInteger(
com.android.internal.R.integer.config_criticalBatteryWarningLevel);
- mButtonHeightFraction = context.getResources().getFraction(
- R.fraction.battery_button_height_fraction, 1, 1);
- mSubpixelSmoothingLeft = context.getResources().getFraction(
- R.fraction.battery_subpixel_smoothing_left, 1, 1);
- mSubpixelSmoothingRight = context.getResources().getFraction(
- R.fraction.battery_subpixel_smoothing_right, 1, 1);
-
- mFramePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
- mFramePaint.setColor(frameColor);
- mFramePaint.setDither(true);
- mFramePaint.setStrokeWidth(0);
- mFramePaint.setStyle(Paint.Style.FILL_AND_STROKE);
-
- mBatteryPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
- mBatteryPaint.setDither(true);
- mBatteryPaint.setStrokeWidth(0);
- mBatteryPaint.setStyle(Paint.Style.FILL_AND_STROKE);
-
- mTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
- Typeface font = Typeface.create("sans-serif-condensed", Typeface.BOLD);
- mTextPaint.setTypeface(font);
- mTextPaint.setTextAlign(Paint.Align.CENTER);
-
- mWarningTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
- mWarningTextPaint.setColor(mColors[1]);
- font = Typeface.create("sans-serif", Typeface.BOLD);
- mWarningTextPaint.setTypeface(font);
- mWarningTextPaint.setTextAlign(Paint.Align.CENTER);
-
- mChargeColor = context.getColor(R.color.batterymeter_charge_color);
-
- mBoltPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
- mBoltPaint.setColor(context.getColor(R.color.batterymeter_bolt_color));
- mBoltPoints = loadBoltPoints(res);
mDarkModeBackgroundColor =
context.getColor(R.color.dark_mode_icon_color_dual_tone_background);
@@ -163,32 +250,38 @@ public class BatteryMeterView extends View implements DemoMode,
mLightModeBackgroundColor =
context.getColor(R.color.light_mode_icon_color_dual_tone_background);
mLightModeFillColor = context.getColor(R.color.light_mode_icon_color_dual_tone_fill);
- }
- @Override
- public void onAttachedToWindow() {
- super.onAttachedToWindow();
+ setAnimationsEnabled(true);
+ }
- IntentFilter filter = new IntentFilter();
- filter.addAction(Intent.ACTION_BATTERY_CHANGED);
- filter.addAction(ACTION_LEVEL_TEST);
- final Intent sticky = getContext().registerReceiver(mTracker, filter);
- if (sticky != null) {
- // preload the battery level
- mTracker.onReceive(getContext(), sticky);
+ protected BatteryMeterDrawable createBatteryMeterDrawable(BatteryMeterMode mode) {
+ Resources res = getResources();
+ switch (mode) {
+ case BATTERY_METER_TEXT:
+ case BATTERY_METER_GONE:
+ return null;
+ default:
+ return new AllInOneBatteryMeterDrawable(res, mode);
}
- mBatteryController.addStateChangedCallback(this);
- getContext().getContentResolver().registerContentObserver(
- Settings.System.getUriFor(SHOW_PERCENT_SETTING), false, mSettingObserver);
}
@Override
- public void onDetachedFromWindow() {
- super.onDetachedFromWindow();
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ int width = MeasureSpec.getSize(widthMeasureSpec);
+ int height = MeasureSpec.getSize(heightMeasureSpec);
- getContext().unregisterReceiver(mTracker);
- mBatteryController.removeStateChangedCallback(this);
- getContext().getContentResolver().unregisterContentObserver(mSettingObserver);
+ if (mMeterMode == BatteryMeterMode.BATTERY_METER_TEXT) {
+ onSizeChanged(width, height, 0, 0); // Force a size changed event
+ }
+
+ setMeasuredDimension(width, height);
+ }
+
+ public void setBatteryStateRegistar(BatteryStateRegistar batteryStateRegistar) {
+ mBatteryStateRegistar = batteryStateRegistar;
+ if (!mAttached) {
+ mBatteryStateRegistar.addStateChangedCallback(this);
+ }
}
public void setBatteryController(BatteryController batteryController) {
@@ -197,7 +290,8 @@ public class BatteryMeterView extends View implements DemoMode,
}
@Override
- public void onBatteryLevelChanged(int level, boolean pluggedIn, boolean charging) {
+ public void onBatteryLevelChanged(boolean present, int level, boolean pluggedIn,
+ boolean charging) {
// TODO: Use this callback instead of own broadcast receiver.
}
@@ -207,36 +301,80 @@ public class BatteryMeterView extends View implements DemoMode,
invalidate();
}
- private static float[] loadBoltPoints(Resources res) {
- final int[] pts = res.getIntArray(R.array.batterymeter_bolt_points);
- int maxX = 0, maxY = 0;
- for (int i = 0; i < pts.length; i += 2) {
- maxX = Math.max(maxX, pts[i]);
- maxY = Math.max(maxY, pts[i + 1]);
+ public void setAnimationsEnabled(boolean enabled) {
+ if (mAnimationsEnabled != enabled) {
+ mAnimationsEnabled = enabled;
+ setLayerType(mAnimationsEnabled ? LAYER_TYPE_HARDWARE : LAYER_TYPE_NONE, null);
+ invalidate();
}
- final float[] ptsF = new float[pts.length];
- for (int i = 0; i < pts.length; i += 2) {
- ptsF[i] = (float)pts[i] / maxX;
- ptsF[i + 1] = (float)pts[i + 1] / maxY;
+ }
+
+ @Override
+ public void onBatteryStyleChanged(int style, int percentMode) {
+ boolean showInsidePercent = percentMode == BatteryController.PERCENTAGE_MODE_INSIDE;
+ BatteryMeterMode meterMode = BatteryMeterMode.BATTERY_METER_ICON_PORTRAIT;
+
+ switch (style) {
+ case BatteryController.STYLE_CIRCLE:
+ meterMode = BatteryMeterMode.BATTERY_METER_CIRCLE;
+ break;
+ case BatteryController.STYLE_GONE:
+ meterMode = BatteryMeterMode.BATTERY_METER_GONE;
+ showInsidePercent = false;
+ break;
+ case BatteryController.STYLE_ICON_LANDSCAPE:
+ meterMode = BatteryMeterMode.BATTERY_METER_ICON_LANDSCAPE;
+ break;
+ case BatteryController.STYLE_TEXT:
+ meterMode = BatteryMeterMode.BATTERY_METER_TEXT;
+ showInsidePercent = false;
+ break;
+ default:
+ break;
}
- return ptsF;
+
+ setMode(meterMode);
+ mShowPercent = showInsidePercent;
+ invalidate();
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
+ super.onSizeChanged(w, h, oldw, oldh);
mHeight = h;
mWidth = w;
- mWarningTextPaint.setTextSize(h * 0.75f);
- mWarningTextHeight = -mWarningTextPaint.getFontMetrics().ascent;
+ if (mBatteryMeterDrawable != null) {
+ mBatteryMeterDrawable.onSizeChanged(w, h, oldw, oldh);
+ }
}
- private void updateShowPercent() {
- mShowPercent = 0 != Settings.System.getInt(getContext().getContentResolver(),
- SHOW_PERCENT_SETTING, 0);
- }
+ public void setMode(BatteryMeterMode mode) {
+ if (mMeterMode == mode) {
+ return;
+ }
- private int getColorForLevel(int percent) {
+ mMeterMode = mode;
+ BatteryTracker tracker = mDemoMode ? mDemoTracker : mTracker;
+ if (mode == BatteryMeterMode.BATTERY_METER_GONE ||
+ mode == BatteryMeterMode.BATTERY_METER_TEXT) {
+ setVisibility(View.GONE);
+ mBatteryMeterDrawable = null;
+ } else {
+ if (mBatteryMeterDrawable != null) {
+ mBatteryMeterDrawable.onDispose();
+ }
+ mBatteryMeterDrawable = createBatteryMeterDrawable(mode);
+ if (tracker.present) {
+ setVisibility(View.VISIBLE);
+ requestLayout();
+ invalidate();
+ } else {
+ setVisibility(View.GONE);
+ }
+ }
+ }
+ public int getColorForLevel(int percent) {
// If we are in power save mode, always use the normal color.
if (mPowerSaveEnabled) {
return mColors[mColors.length-1];
@@ -259,13 +397,11 @@ public class BatteryMeterView extends View implements DemoMode,
}
public void setDarkIntensity(float darkIntensity) {
- int backgroundColor = getBackgroundColor(darkIntensity);
- int fillColor = getFillColor(darkIntensity);
- mIconTint = fillColor;
- mFramePaint.setColor(backgroundColor);
- mBoltPaint.setColor(fillColor);
- mChargeColor = fillColor;
- invalidate();
+ if (mBatteryMeterDrawable != null) {
+ mCurrentBackgroundColor = getBackgroundColor(darkIntensity);
+ mCurrentFillColor = getFillColor(darkIntensity);
+ mBatteryMeterDrawable.setDarkIntensity(mCurrentBackgroundColor, mCurrentFillColor);
+ }
}
private int getBackgroundColor(float darkIntensity) {
@@ -283,261 +419,425 @@ public class BatteryMeterView extends View implements DemoMode,
}
@Override
- public void draw(Canvas c) {
- BatteryTracker tracker = mDemoMode ? mDemoTracker : mTracker;
- final int level = tracker.level;
-
- if (level == BatteryTracker.UNKNOWN_LEVEL) return;
-
- float drawFrac = (float) level / 100f;
- final int pt = getPaddingTop();
- final int pl = getPaddingLeft();
- final int pr = getPaddingRight();
- final int pb = getPaddingBottom();
- final int height = mHeight - pt - pb;
- final int width = mWidth - pl - pr;
-
- final int buttonHeight = (int) (height * mButtonHeightFraction);
-
- mFrame.set(0, 0, width, height);
- mFrame.offset(pl, pt);
-
- // button-frame: area above the battery body
- mButtonFrame.set(
- mFrame.left + Math.round(width * 0.25f),
- mFrame.top,
- mFrame.right - Math.round(width * 0.25f),
- mFrame.top + buttonHeight);
-
- mButtonFrame.top += mSubpixelSmoothingLeft;
- mButtonFrame.left += mSubpixelSmoothingLeft;
- mButtonFrame.right -= mSubpixelSmoothingRight;
-
- // frame: battery body area
- mFrame.top += buttonHeight;
- mFrame.left += mSubpixelSmoothingLeft;
- mFrame.top += mSubpixelSmoothingLeft;
- mFrame.right -= mSubpixelSmoothingRight;
- mFrame.bottom -= mSubpixelSmoothingRight;
-
- // set the battery charging color
- mBatteryPaint.setColor(tracker.plugged ? mChargeColor : getColorForLevel(level));
-
- if (level >= FULL) {
- drawFrac = 1f;
- } else if (level <= mCriticalLevel) {
- drawFrac = 0f;
- }
-
- final float levelTop = drawFrac == 1f ? mButtonFrame.top
- : (mFrame.top + (mFrame.height() * (1f - drawFrac)));
-
- // define the battery shape
- mShapePath.reset();
- mShapePath.moveTo(mButtonFrame.left, mButtonFrame.top);
- mShapePath.lineTo(mButtonFrame.right, mButtonFrame.top);
- mShapePath.lineTo(mButtonFrame.right, mFrame.top);
- mShapePath.lineTo(mFrame.right, mFrame.top);
- mShapePath.lineTo(mFrame.right, mFrame.bottom);
- mShapePath.lineTo(mFrame.left, mFrame.bottom);
- mShapePath.lineTo(mFrame.left, mFrame.top);
- mShapePath.lineTo(mButtonFrame.left, mFrame.top);
- mShapePath.lineTo(mButtonFrame.left, mButtonFrame.top);
-
- if (tracker.plugged) {
- // define the bolt shape
- final float bl = mFrame.left + mFrame.width() / 4.5f;
- final float bt = mFrame.top + mFrame.height() / 6f;
- final float br = mFrame.right - mFrame.width() / 7f;
- final float bb = mFrame.bottom - mFrame.height() / 10f;
- if (mBoltFrame.left != bl || mBoltFrame.top != bt
- || mBoltFrame.right != br || mBoltFrame.bottom != bb) {
- mBoltFrame.set(bl, bt, br, bb);
- mBoltPath.reset();
- mBoltPath.moveTo(
- mBoltFrame.left + mBoltPoints[0] * mBoltFrame.width(),
- mBoltFrame.top + mBoltPoints[1] * mBoltFrame.height());
- for (int i = 2; i < mBoltPoints.length; i += 2) {
- mBoltPath.lineTo(
- mBoltFrame.left + mBoltPoints[i] * mBoltFrame.width(),
- mBoltFrame.top + mBoltPoints[i + 1] * mBoltFrame.height());
+ protected void onDraw(Canvas canvas) {
+ if (mBatteryMeterDrawable != null) {
+ BatteryTracker tracker = mDemoMode ? mDemoTracker : mTracker;
+ mBatteryMeterDrawable.onDraw(canvas, tracker);
+ }
+ }
+
+ @Override
+ public boolean hasOverlappingRendering() {
+ return false;
+ }
+
+ @Override
+ public void dispatchDemoCommand(String command, Bundle args) {
+ if (getVisibility() == View.VISIBLE) {
+ if (!mDemoMode && command.equals(COMMAND_ENTER)) {
+ mDemoMode = true;
+ mDemoTracker.level = mTracker.level;
+ mDemoTracker.plugged = mTracker.plugged;
+ } else if (mDemoMode && command.equals(COMMAND_EXIT)) {
+ mDemoMode = false;
+ postInvalidate();
+ } else if (mDemoMode && command.equals(COMMAND_BATTERY)) {
+ String level = args.getString("level");
+ String plugged = args.getString("plugged");
+ if (level != null) {
+ mDemoTracker.level = Math.min(Math.max(Integer.parseInt(level), 0), 100);
+ }
+ if (plugged != null) {
+ mDemoTracker.plugged = Boolean.parseBoolean(plugged);
}
- mBoltPath.lineTo(
- mBoltFrame.left + mBoltPoints[0] * mBoltFrame.width(),
- mBoltFrame.top + mBoltPoints[1] * mBoltFrame.height());
+ postInvalidate();
}
+ }
+ }
+
+ protected interface BatteryMeterDrawable {
+ void onDraw(Canvas c, BatteryTracker tracker);
+ void onSizeChanged(int w, int h, int oldw, int oldh);
+ void onDispose();
+ void setDarkIntensity(int backgroundColor, int fillColor);
+ }
+
+ protected class AllInOneBatteryMeterDrawable implements BatteryMeterDrawable {
+ private static final boolean SINGLE_DIGIT_PERCENT = false;
+ private static final boolean SHOW_100_PERCENT = false;
+
+ private boolean mDisposed;
- float boltPct = (mBoltFrame.bottom - levelTop) / (mBoltFrame.bottom - mBoltFrame.top);
- boltPct = Math.min(Math.max(boltPct, 0), 1);
- if (boltPct <= BOLT_LEVEL_THRESHOLD) {
- // draw the bolt if opaque
- c.drawPath(mBoltPath, mBoltPaint);
+ private boolean mIsAnimating; // stores charge-animation status to remove callbacks
+
+ private float mTextX, mTextY; // precalculated position for drawText() to appear centered
+
+ private boolean mInitialized;
+
+ private Paint mTextAndBoltPaint;
+ private Paint mWarningTextPaint;
+ private Paint mClearPaint;
+
+ private LayerDrawable mBatteryDrawable;
+ private Drawable mFrameDrawable;
+ private StopMotionVectorDrawable mLevelDrawable;
+ private Drawable mBoltDrawable;
+
+ private BatteryMeterMode mMode;
+ private int mTextGravity;
+
+ public AllInOneBatteryMeterDrawable(Resources res, BatteryMeterMode mode) {
+ super();
+
+ loadBatteryDrawables(res, mode);
+
+ mMode = mode;
+ mDisposed = false;
+
+ // load text gravity and blend mode
+ int[] attrs = new int[] {android.R.attr.gravity, R.attr.blendMode};
+ int resId = getBatteryDrawableStyleResourceForMode(mode);
+ PorterDuff.Mode xferMode = PorterDuff.Mode.XOR;
+ if (resId != 0) {
+ TypedArray a = getContext().obtainStyledAttributes(
+ getBatteryDrawableStyleResourceForMode(mode), attrs);
+ mTextGravity = a.getInt(0, Gravity.CENTER);
+ xferMode = PorterDuff.intToMode(a.getInt(1,
+ PorterDuff.modeToInt(PorterDuff.Mode.XOR)));
} else {
- // otherwise cut the bolt out of the overall shape
- mShapePath.op(mBoltPath, Path.Op.DIFFERENCE);
+ mTextGravity = Gravity.CENTER;
}
+ Log.d(TAG, "mTextGravity=" + mTextGravity);
+
+ mTextAndBoltPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+ Typeface font = Typeface.create("sans-serif-condensed", Typeface.BOLD);
+ mTextAndBoltPaint.setTypeface(font);
+ mTextAndBoltPaint.setTextAlign(getPaintAlignmentFromGravity(mTextGravity));
+ mTextAndBoltPaint.setXfermode(new PorterDuffXfermode(xferMode));
+ mTextAndBoltPaint.setColor(mCurrentFillColor != 0
+ ? mCurrentFillColor
+ : res.getColor(R.color.batterymeter_bolt_color));
+
+ mWarningTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+ mWarningTextPaint.setColor(mColors[1]);
+ font = Typeface.create("sans-serif", Typeface.BOLD);
+ mWarningTextPaint.setTypeface(font);
+ mWarningTextPaint.setTextAlign(getPaintAlignmentFromGravity(mTextGravity));
+
+ mClearPaint = new Paint();
+ mClearPaint.setColor(0);
}
- // compute percentage text
- boolean pctOpaque = false;
- float pctX = 0, pctY = 0;
- String pctText = null;
- if (!tracker.plugged && level > mCriticalLevel && mShowPercent) {
- mTextPaint.setColor(getColorForLevel(level));
- mTextPaint.setTextSize(height *
- (SINGLE_DIGIT_PERCENT ? 0.75f
- : (tracker.level == 100 ? 0.38f : 0.5f)));
- mTextHeight = -mTextPaint.getFontMetrics().ascent;
- pctText = String.valueOf(SINGLE_DIGIT_PERCENT ? (level/10) : level);
- pctX = mWidth * 0.5f;
- pctY = (mHeight + mTextHeight) * 0.47f;
- pctOpaque = levelTop > pctY;
- if (!pctOpaque) {
- mTextPath.reset();
- mTextPaint.getTextPath(pctText, 0, pctText.length(), pctX, pctY, mTextPath);
- // cut the percentage text out of the overall shape
- mShapePath.op(mTextPath, Path.Op.DIFFERENCE);
+ @Override
+ public void onDraw(Canvas c, BatteryTracker tracker) {
+ if (mDisposed) return;
+
+ if (!mInitialized) {
+ init();
+ }
+
+ drawBattery(c, tracker);
+ if (mAnimationsEnabled) {
+ // TODO: Allow custom animations to be used
}
}
- // draw the battery shape background
- c.drawPath(mShapePath, mFramePaint);
+ @Override
+ public void onDispose() {
+ mDisposed = true;
+ }
- // draw the battery shape, clipped to charging level
- mFrame.top = levelTop;
- mClipPath.reset();
- mClipPath.addRect(mFrame, Path.Direction.CCW);
- mShapePath.op(mClipPath, Path.Op.INTERSECT);
- c.drawPath(mShapePath, mBatteryPaint);
+ @Override
+ public void setDarkIntensity(int backgroundColor, int fillColor) {
+ mIconTint = fillColor;
+ // Make bolt fully opaque for increased visibility
+ mBoltDrawable.setTint(0xff000000 | fillColor);
+ mFrameDrawable.setTint(backgroundColor);
+ updateBoltDrawableLayer(mBatteryDrawable, mBoltDrawable);
+ invalidate();
+ }
- if (!tracker.plugged) {
- if (level <= mCriticalLevel) {
- // draw the warning text
- final float x = mWidth * 0.5f;
- final float y = (mHeight + mWarningTextHeight) * 0.48f;
- c.drawText(mWarningString, x, y, mWarningTextPaint);
- } else if (pctOpaque) {
- // draw the percentage text
- c.drawText(pctText, pctX, pctY, mTextPaint);
+ @Override
+ public void onSizeChanged(int w, int h, int oldw, int oldh) {
+ init();
+ }
+
+ private boolean isThemeApplied() {
+ ThemeConfig themeConfig = ThemeConfig.getBootTheme(getContext().getContentResolver());
+ return themeConfig != null &&
+ !ThemeConfig.SYSTEM_DEFAULT.equals(themeConfig.getOverlayForStatusBar());
+ }
+
+ private void checkBatteryMeterDrawableValid(Resources res, BatteryMeterMode mode) {
+ final int resId = getBatteryDrawableResourceForMode(mode);
+ final Drawable batteryDrawable;
+ try {
+ batteryDrawable = res.getDrawable(resId);
+ } catch (Resources.NotFoundException e) {
+ throw new BatteryMeterDrawableException(res.getResourceName(resId) + " is an " +
+ "invalid drawable", e);
+ }
+
+ // check that the drawable is a LayerDrawable
+ if (!(batteryDrawable instanceof LayerDrawable)) {
+ throw new BatteryMeterDrawableException("Expected a LayerDrawable but received a " +
+ batteryDrawable.getClass().getSimpleName());
+ }
+
+ final LayerDrawable layerDrawable = (LayerDrawable) batteryDrawable;
+ final Drawable frame = layerDrawable.findDrawableByLayerId(R.id.battery_frame);
+ final Drawable level = layerDrawable.findDrawableByLayerId(R.id.battery_fill);
+ final Drawable bolt = layerDrawable.findDrawableByLayerId(
+ R.id.battery_charge_indicator);
+ // now check that the required layers exist and are of the correct type
+ if (frame == null) {
+ throw new BatteryMeterDrawableException("Missing battery_frame drawble");
+ }
+ if (bolt == null) {
+ throw new BatteryMeterDrawableException(
+ "Missing battery_charge_indicator drawable");
+ }
+ if (level != null) {
+ // check that the level drawable is an AnimatedVectorDrawable
+ if (!(level instanceof AnimatedVectorDrawable)) {
+ throw new BatteryMeterDrawableException("Expected a AnimatedVectorDrawable " +
+ "but received a " + level.getClass().getSimpleName());
+ }
+ // make sure we can stop motion animate the level drawable
+ try {
+ StopMotionVectorDrawable smvd = new StopMotionVectorDrawable(level);
+ smvd.setCurrentFraction(0.5f);
+ } catch (Exception e) {
+ throw new BatteryMeterDrawableException("Unable to perform stop motion on " +
+ "battery_fill drawable", e);
+ }
+ } else {
+ throw new BatteryMeterDrawableException("Missing battery_fill drawable");
}
}
- }
- @Override
- public boolean hasOverlappingRendering() {
- return false;
- }
+ private void loadBatteryDrawables(Resources res, BatteryMeterMode mode) {
+ if (isThemeApplied()) {
+ try {
+ checkBatteryMeterDrawableValid(res, mode);
+ } catch (BatteryMeterDrawableException e) {
+ Log.w(TAG, "Invalid themed battery meter drawable, falling back to system", e);
+ final Context context = getContext();
+ PackageManager pm = getContext().getPackageManager();
+ try {
+ res = pm.getThemedResourcesForApplication(context.getPackageName(),
+ ThemeConfig.SYSTEM_DEFAULT);
+ } catch (PackageManager.NameNotFoundException nnfe) {
+ /* ignore, this should not happen */
+ }
+ }
+ }
- private boolean mDemoMode;
- private BatteryTracker mDemoTracker = new BatteryTracker();
+ int drawableResId = getBatteryDrawableResourceForMode(mode);
+ mBatteryDrawable = (LayerDrawable) res.getDrawable(drawableResId);
+ mFrameDrawable = mBatteryDrawable.findDrawableByLayerId(R.id.battery_frame);
+ mFrameDrawable.setTint(mCurrentBackgroundColor != 0
+ ? mCurrentBackgroundColor
+ : res.getColor(R.color.batterymeter_frame_color));
+ // set the animated vector drawable we will be stop animating
+ Drawable levelDrawable = mBatteryDrawable.findDrawableByLayerId(R.id.battery_fill);
+ mLevelDrawable = new StopMotionVectorDrawable(levelDrawable);
+ mBoltDrawable = mBatteryDrawable.findDrawableByLayerId(R.id.battery_charge_indicator);
+ }
- @Override
- public void dispatchDemoCommand(String command, Bundle args) {
- if (!mDemoMode && command.equals(COMMAND_ENTER)) {
- mDemoMode = true;
- mDemoTracker.level = mTracker.level;
- mDemoTracker.plugged = mTracker.plugged;
- } else if (mDemoMode && command.equals(COMMAND_EXIT)) {
- mDemoMode = false;
- postInvalidate();
- } else if (mDemoMode && command.equals(COMMAND_BATTERY)) {
- String level = args.getString("level");
- String plugged = args.getString("plugged");
- if (level != null) {
- mDemoTracker.level = Math.min(Math.max(Integer.parseInt(level), 0), 100);
- }
- if (plugged != null) {
- mDemoTracker.plugged = Boolean.parseBoolean(plugged);
- }
- postInvalidate();
+ private void drawBattery(Canvas canvas, BatteryTracker tracker) {
+ boolean unknownStatus = tracker.status == BatteryManager.BATTERY_STATUS_UNKNOWN;
+ int level = tracker.level;
+
+ if (unknownStatus || tracker.status == BatteryManager.BATTERY_STATUS_FULL) {
+ level = 100;
+ }
+
+ mTextAndBoltPaint.setColor(getColorForLevel(level));
+
+ // Make sure we don't draw the charge indicator if not plugged in
+ Drawable d = mBatteryDrawable.findDrawableByLayerId(R.id.battery_charge_indicator);
+ if (d instanceof BitmapDrawable) {
+ // In case we are using a BitmapDrawable, which we should be unless something bad
+ // happened, we need to change the paint rather than the alpha in case the blendMode
+ // has been set to clear. Clear always clears regardless of alpha level ;)
+ BitmapDrawable bd = (BitmapDrawable) d;
+ bd.getPaint().set(tracker.plugged ? mTextAndBoltPaint : mClearPaint);
+ } else {
+ d.setAlpha(tracker.plugged ? 255 : 0);
+ }
+
+ // Now draw the level indicator
+ // set the level and tint color of the fill drawable
+ mLevelDrawable.setCurrentFraction(level / 100f);
+ mLevelDrawable.setTint(getColorForLevel(level));
+ mBatteryDrawable.draw(canvas);
+
+ // if chosen by options, draw percentage text in the middle
+ // always skip percentage when 100, so layout doesnt break
+ if (unknownStatus) {
+ mTextAndBoltPaint.setColor(getContext().getColor(R.color.batterymeter_frame_color));
+ canvas.drawText("?", mTextX, mTextY, mTextAndBoltPaint);
+
+ } else if (!tracker.plugged) {
+ drawPercentageText(canvas, tracker);
+ }
}
- }
- private final class BatteryTracker extends BroadcastReceiver {
- public static final int UNKNOWN_LEVEL = -1;
+ private void drawPercentageText(Canvas canvas, BatteryTracker tracker) {
+ final int level = tracker.level;
+ if (level > mCriticalLevel
+ && (mShowPercent && !(level == 100 && !SHOW_100_PERCENT))) {
+ // draw the percentage text
+ String pctText = String.valueOf(SINGLE_DIGIT_PERCENT ? (level/10) : level);
+ mTextAndBoltPaint.setColor(getColorForLevel(level));
+ canvas.drawText(pctText, mTextX, mTextY, mTextAndBoltPaint);
+ } else if (level <= mCriticalLevel) {
+ // draw the warning text
+ canvas.drawText(mWarningString, mTextX, mTextY, mWarningTextPaint);
+ }
+ }
- // current battery status
- int level = UNKNOWN_LEVEL;
- String percentStr;
- int plugType;
- boolean plugged;
- int health;
- int status;
- String technology;
- int voltage;
- int temperature;
- boolean testmode = false;
+ /**
+ * initializes all size dependent variables
+ */
+ private void init() {
+ // not much we can do with zero width or height, we'll get another pass later
+ if (mWidth <= 0 || mHeight <=0) return;
+
+ final float widthDiv2 = mWidth / 2f;
+ // text size is width / 2 - 2dp for wiggle room
+ final float textSize = widthDiv2 - getResources().getDisplayMetrics().density * 2;
+ mTextAndBoltPaint.setTextSize(textSize);
+ mWarningTextPaint.setTextSize(textSize);
+
+ int pLeft = getPaddingLeft();
+ Rect iconBounds = new Rect(pLeft, 0, pLeft + mWidth, mHeight);
+ mBatteryDrawable.setBounds(iconBounds);
+
+ // calculate text position
+ Rect bounds = new Rect();
+ mTextAndBoltPaint.getTextBounds("99", 0, "99".length(), bounds);
+ boolean isRtl = isLayoutRtl();
+
+ // compute mTextX based on text gravity
+ if ((mTextGravity & Gravity.START) == Gravity.START) {
+ mTextX = isRtl ? mWidth : 0;
+ } else if ((mTextGravity & Gravity.END) == Gravity.END) {
+ mTextX = isRtl ? 0 : mWidth;
+ } else if ((mTextGravity & Gravity.LEFT) == Gravity.LEFT) {
+ mTextX = 0;
+ }else if ((mTextGravity & Gravity.RIGHT) == Gravity.RIGHT) {
+ mTextX = mWidth;
+ } else {
+ mTextX = widthDiv2 + pLeft;
+ }
- @Override
- public void onReceive(Context context, Intent intent) {
- final String action = intent.getAction();
- if (action.equals(Intent.ACTION_BATTERY_CHANGED)) {
- if (testmode && ! intent.getBooleanExtra("testmode", false)) return;
+ // compute mTextY based on text gravity
+ if ((mTextGravity & Gravity.TOP) == Gravity.TOP) {
+ mTextY = bounds.height();
+ } else if ((mTextGravity & Gravity.BOTTOM) == Gravity.BOTTOM) {
+ mTextY = mHeight;
+ } else {
+ mTextY = widthDiv2 + bounds.height() / 2.0f;
+ }
- level = (int)(100f
- * intent.getIntExtra(BatteryManager.EXTRA_LEVEL, 0)
- / intent.getIntExtra(BatteryManager.EXTRA_SCALE, 100));
+ updateBoltDrawableLayer(mBatteryDrawable, mBoltDrawable);
- plugType = intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, 0);
- plugged = plugType != 0;
- health = intent.getIntExtra(BatteryManager.EXTRA_HEALTH,
- BatteryManager.BATTERY_HEALTH_UNKNOWN);
- status = intent.getIntExtra(BatteryManager.EXTRA_STATUS,
- BatteryManager.BATTERY_STATUS_UNKNOWN);
- technology = intent.getStringExtra(BatteryManager.EXTRA_TECHNOLOGY);
- voltage = intent.getIntExtra(BatteryManager.EXTRA_VOLTAGE, 0);
- temperature = intent.getIntExtra(BatteryManager.EXTRA_TEMPERATURE, 0);
+ mInitialized = true;
+ }
- setContentDescription(
- context.getString(R.string.accessibility_battery_level, level));
- postInvalidate();
- } else if (action.equals(ACTION_LEVEL_TEST)) {
- testmode = true;
- post(new Runnable() {
- int curLevel = 0;
- int incr = 1;
- int saveLevel = level;
- int savePlugged = plugType;
- Intent dummy = new Intent(Intent.ACTION_BATTERY_CHANGED);
- @Override
- public void run() {
- if (curLevel < 0) {
- testmode = false;
- dummy.putExtra("level", saveLevel);
- dummy.putExtra("plugged", savePlugged);
- dummy.putExtra("testmode", false);
- } else {
- dummy.putExtra("level", curLevel);
- dummy.putExtra("plugged", incr > 0 ? BatteryManager.BATTERY_PLUGGED_AC
- : 0);
- dummy.putExtra("testmode", true);
- }
- getContext().sendBroadcast(dummy);
+ private int getBatteryDrawableResourceForMode(BatteryMeterMode mode) {
+ switch (mode) {
+ case BATTERY_METER_ICON_LANDSCAPE:
+ return R.drawable.ic_battery_landscape;
+ case BATTERY_METER_CIRCLE:
+ return R.drawable.ic_battery_circle;
+ case BATTERY_METER_ICON_PORTRAIT:
+ return R.drawable.ic_battery_portrait;
+ default:
+ return 0;
+ }
+ }
- if (!testmode) return;
+ private int getBatteryDrawableStyleResourceForMode(BatteryMeterMode mode) {
+ switch (mode) {
+ case BATTERY_METER_ICON_LANDSCAPE:
+ return R.style.BatteryMeterViewDrawable_Landscape;
+ case BATTERY_METER_CIRCLE:
+ return R.style.BatteryMeterViewDrawable_Circle;
+ case BATTERY_METER_ICON_PORTRAIT:
+ return R.style.BatteryMeterViewDrawable_Portrait;
+ default:
+ return R.style.BatteryMeterViewDrawable;
+ }
+ }
- curLevel += incr;
- if (curLevel == 100) {
- incr *= -1;
- }
- postDelayed(this, 200);
- }
- });
+ private Paint.Align getPaintAlignmentFromGravity(int gravity) {
+ boolean isRtl = isLayoutRtl();
+ if ((gravity & Gravity.START) == Gravity.START) {
+ return isRtl ? Paint.Align.RIGHT : Paint.Align.LEFT;
+ }
+ if ((gravity & Gravity.END) == Gravity.END) {
+ return isRtl ? Paint.Align.LEFT : Paint.Align.RIGHT;
}
+ if ((gravity & Gravity.LEFT) == Gravity.LEFT) return Paint.Align.LEFT;
+ if ((gravity & Gravity.RIGHT) == Gravity.RIGHT) return Paint.Align.RIGHT;
+
+ // default to center
+ return Paint.Align.CENTER;
}
- }
- private final class SettingObserver extends ContentObserver {
- public SettingObserver() {
- super(new Handler());
+ // Creates a BitmapDrawable of the bolt so we can make use of the XOR xfer mode with vector
+ // based drawables
+ private void updateBoltDrawableLayer(LayerDrawable batteryDrawable, Drawable boltDrawable) {
+ BitmapDrawable newBoltDrawable;
+ if (boltDrawable instanceof BitmapDrawable) {
+ newBoltDrawable = (BitmapDrawable) boltDrawable.mutate();
+ } else {
+ Bitmap boltBitmap = createBoltBitmap(boltDrawable);
+ if (boltBitmap == null) {
+ // not much to do with a null bitmap so keep original bolt for now
+ return;
+ }
+ Rect bounds = boltDrawable.getBounds();
+ newBoltDrawable = new BitmapDrawable(getResources(), boltBitmap);
+ newBoltDrawable.setBounds(bounds);
+ }
+ newBoltDrawable.getPaint().set(mTextAndBoltPaint);
+ batteryDrawable.setDrawableByLayerId(R.id.battery_charge_indicator, newBoltDrawable);
}
- @Override
- public void onChange(boolean selfChange, Uri uri) {
- super.onChange(selfChange, uri);
- updateShowPercent();
- postInvalidate();
+ private Bitmap createBoltBitmap(Drawable boltDrawable) {
+ // not much we can do with zero width or height, we'll get another pass later
+ if (mWidth <= 0 || mHeight <= 0) return null;
+
+ Bitmap bolt;
+ if (!(boltDrawable instanceof BitmapDrawable)) {
+ int pLeft = getPaddingLeft();
+ Rect iconBounds = new Rect(pLeft, 0, pLeft + mWidth, mHeight);
+ bolt = Bitmap.createBitmap(iconBounds.width(), iconBounds.height(),
+ Bitmap.Config.ARGB_8888);
+ if (bolt != null) {
+ Canvas c = new Canvas(bolt);
+ c.drawColor(-1, PorterDuff.Mode.CLEAR);
+ boltDrawable.draw(c);
+ }
+ } else {
+ bolt = ((BitmapDrawable) boltDrawable).getBitmap();
+ }
+
+ return bolt;
}
- }
+ private class BatteryMeterDrawableException extends RuntimeException {
+ public BatteryMeterDrawableException(String detailMessage) {
+ super(detailMessage);
+ }
+
+ public BatteryMeterDrawableException(String detailMessage, Throwable throwable) {
+ super(detailMessage, throwable);
+ }
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/DockBatteryLevelTextView.java b/packages/SystemUI/src/com/android/systemui/DockBatteryLevelTextView.java
new file mode 100644
index 0000000..1678e94
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/DockBatteryLevelTextView.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2015 The CyanogenMod Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui;
+
+import android.content.Context;
+import android.graphics.Paint;
+import android.util.AttributeSet;
+
+public class DockBatteryLevelTextView extends BatteryLevelTextView {
+
+ public DockBatteryLevelTextView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ setPaintFlags(getPaintFlags() | Paint.UNDERLINE_TEXT_FLAG);
+ }
+
+}
diff --git a/packages/SystemUI/src/com/android/systemui/DockBatteryMeterView.java b/packages/SystemUI/src/com/android/systemui/DockBatteryMeterView.java
new file mode 100755
index 0000000..a29b16c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/DockBatteryMeterView.java
@@ -0,0 +1,150 @@
+/*
+ * Copyright (C) 2016 The CyanogenMod Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui;
+
+import android.content.Context;
+import android.content.Intent;
+import android.os.BatteryManager;
+import android.util.AttributeSet;
+import android.view.View;
+
+public class DockBatteryMeterView extends BatteryMeterView {
+
+ private BatteryManager mBatteryService;
+ private final boolean mSupported;
+
+ private class DockBatteryTracker extends BatteryTracker {
+
+ public DockBatteryTracker() {
+ super();
+ present = false;
+ }
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ final String action = intent.getAction();
+ if (action.equals(Intent.ACTION_BATTERY_CHANGED)) {
+ if (testmode && ! intent.getBooleanExtra("testmode", false)) return;
+
+ if (mSupported) {
+ level = (int)(100f
+ * intent.getIntExtra(BatteryManager.EXTRA_DOCK_LEVEL, 0)
+ / intent.getIntExtra(BatteryManager.EXTRA_DOCK_SCALE, 100));
+
+ present = intent.getBooleanExtra(BatteryManager.EXTRA_DOCK_PRESENT, false);
+ plugType = intent.getIntExtra(BatteryManager.EXTRA_DOCK_PLUGGED, 0);
+ // We need to add a extra check over the status because of dock batteries
+ // PlugType doesn't means that the dock battery is charging (some devices
+ // doesn't charge under dock usb)
+ plugged = plugType != 0 && (status == BatteryManager.BATTERY_STATUS_CHARGING ||
+ status == BatteryManager.BATTERY_STATUS_FULL);
+ health = intent.getIntExtra(BatteryManager.EXTRA_DOCK_HEALTH,
+ BatteryManager.BATTERY_HEALTH_UNKNOWN);
+ status = intent.getIntExtra(BatteryManager.EXTRA_DOCK_STATUS,
+ BatteryManager.BATTERY_STATUS_UNKNOWN);
+ technology = intent.getStringExtra(BatteryManager.EXTRA_DOCK_TECHNOLOGY);
+ voltage = intent.getIntExtra(BatteryManager.EXTRA_DOCK_VOLTAGE, 0);
+ temperature = intent.getIntExtra(BatteryManager.EXTRA_DOCK_TEMPERATURE, 0);
+
+
+ if (present && (mMeterMode != BatteryMeterMode.BATTERY_METER_GONE &&
+ mMeterMode != BatteryMeterMode.BATTERY_METER_TEXT)) {
+ setContentDescription(context.getString(
+ R.string.accessibility_dock_battery_level, level));
+ setVisibility(View.VISIBLE);
+ invalidate();
+ } else {
+ setContentDescription(null);
+ setVisibility(View.GONE);
+ }
+ } else {
+ setContentDescription(null);
+ setVisibility(View.GONE);
+
+ // If dock is not supported then we don't need this receiver anymore
+ getContext().unregisterReceiver(this);
+ }
+ } else if (action.equals(ACTION_LEVEL_TEST)) {
+ testmode = true;
+ post(new Runnable() {
+ int curLevel = 0;
+ int incr = 1;
+ int saveLevel = level;
+ int savePlugged = plugType;
+ Intent dummy = new Intent(Intent.ACTION_BATTERY_CHANGED);
+ @Override
+ public void run() {
+ if (curLevel < 0) {
+ testmode = false;
+ dummy.putExtra("level", saveLevel);
+ dummy.putExtra("plugged", savePlugged);
+ dummy.putExtra("testmode", false);
+ } else {
+ dummy.putExtra("level", curLevel);
+ dummy.putExtra("plugged", incr > 0
+ ? BatteryManager.BATTERY_DOCK_PLUGGED_AC : 0);
+ dummy.putExtra("testmode", true);
+ }
+ getContext().sendBroadcast(dummy);
+
+ if (!testmode) return;
+
+ curLevel += incr;
+ if (curLevel == 100) {
+ incr *= -1;
+ }
+ postDelayed(this, 200);
+ }
+ });
+ }
+ }
+ }
+
+ public DockBatteryMeterView(Context context) {
+ this(context, null, 0);
+ }
+
+ public DockBatteryMeterView(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public DockBatteryMeterView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ mBatteryService = ((BatteryManager) context.getSystemService(Context.BATTERY_SERVICE));
+ mSupported = mBatteryService.isDockBatterySupported();
+ mDemoTracker = new DockBatteryTracker();
+ mTracker = new DockBatteryTracker();
+ }
+
+ @Override
+ public void onDetachedFromWindow() {
+ // We already unregistered the listener once when we decided
+ // support was absent. Don't do it again.
+ if (mSupported) {
+ super.onDetachedFromWindow();
+ }
+ }
+
+ @Override
+ public void setMode(BatteryMeterMode mode) {
+ super.setMode(mode);
+ int visibility = getVisibility();
+ if (visibility == View.VISIBLE && !mSupported) {
+ setVisibility(View.GONE);
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/EventLogTags.logtags b/packages/SystemUI/src/com/android/systemui/EventLogTags.logtags
index a584cf6..a08d4b7 100644
--- a/packages/SystemUI/src/com/android/systemui/EventLogTags.logtags
+++ b/packages/SystemUI/src/com/android/systemui/EventLogTags.logtags
@@ -51,3 +51,28 @@ option java_package com.android.systemui;
# SearchPanelView.java
# ---------------------------
36050 sysui_searchpanel_touch (type|1),(x|1),(y|1)
+
+# ---------------------------
+# LiveLockScreenController.java
+# ---------------------------
+# sysui_lls_keyguard_showing
+## screenOn: 0:screen turned off
+## 1:screen turned on
+36060 sysui_lls_keyguard_showing (screenOn|1)
+# sysui_lls_keyguard_dismissed: Logged when user unlocks the device
+## onLls: 0:dismissed while showing notifications
+## 1:dismissed while user interacting with LLS
+36061 sysui_lls_keyguard_dismissed (onLls|1)
+# sysui_lls_notification_panel_shown: Logged when the notification panel is swiped in and out
+## shown: 0:panel is hidden
+## 1:panel is visible
+36062 sysui_lls_notification_panel_shown (shown|1)
+
+# ---------------------------
+# RecentsView.java
+# ---------------------------
+36070 sysui_recents_event (what|1)
+## what: 1: OPEN
+## 2: CLOSE
+## 3: CHOSE_TASK
+## 4: CLOSE_ALL_TASKS
diff --git a/packages/SystemUI/src/com/android/systemui/ExpandHelper.java b/packages/SystemUI/src/com/android/systemui/ExpandHelper.java
index 1dca149..5911916 100644
--- a/packages/SystemUI/src/com/android/systemui/ExpandHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/ExpandHelper.java
@@ -36,6 +36,7 @@ import android.view.ViewConfiguration;
import com.android.systemui.statusbar.ExpandableNotificationRow;
import com.android.systemui.statusbar.ExpandableView;
import com.android.systemui.statusbar.FlingAnimationUtils;
+import com.android.systemui.statusbar.MediaExpandableNotificationRow;
import com.android.systemui.statusbar.policy.ScrollAdapter;
public class ExpandHelper implements Gefingerpoken {
@@ -96,7 +97,7 @@ public class ExpandHelper implements Gefingerpoken {
private float mCurrentHeight;
private int mSmallSize;
- private int mLargeSize;
+ private int mLargeSize, mInitialLargeSize;
private float mMaximumStretch;
private boolean mOnlyMovements;
@@ -161,6 +162,7 @@ public class ExpandHelper implements Gefingerpoken {
mSmallSize = small;
mMaximumStretch = mSmallSize * STRETCH_INTERVAL;
mLargeSize = large;
+ mInitialLargeSize = large;
mContext = context;
mCallback = callback;
mScaler = new ViewScaler();
@@ -511,7 +513,14 @@ public class ExpandHelper implements Gefingerpoken {
mCurrentHeight = mOldHeight;
if (mCallback.canChildBeExpanded(v)) {
if (DEBUG) Log.d(TAG, "working on an expandable child");
- mNaturalHeight = mScaler.getNaturalHeight(mLargeSize);
+ if (v instanceof MediaExpandableNotificationRow) {
+ final int maxHeight = ((MediaExpandableNotificationRow) v).getMaxContentHeight();
+ mLargeSize = maxHeight;
+ mNaturalHeight = maxHeight;
+ } else {
+ mLargeSize = mInitialLargeSize;
+ mNaturalHeight = mScaler.getNaturalHeight(mLargeSize);
+ }
} else {
if (DEBUG) Log.d(TAG, "working on a non-expandable child");
mNaturalHeight = mOldHeight;
diff --git a/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java b/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java
index 8556afc..5a6f3c9 100644
--- a/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java
+++ b/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java
@@ -236,7 +236,9 @@ public class ImageWallpaper extends WallpaperService {
Log.d(TAG, "Visibility changed to visible=" + visible);
}
mVisible = visible;
- drawFrame();
+ if (visible) {
+ drawFrame();
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
index 33f6564..c3e5043 100644
--- a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
@@ -45,9 +45,15 @@ public class SwipeHelper implements Gefingerpoken {
public static final int X = 0;
public static final int Y = 1;
+ public static final int SWIPE_ZONE_LEFT = 0x1;
+ public static final int SWIPE_ZONE_RIGHT = 0x2;
+ public static final int SWIPE_ZONE_TOP = 0x4;
+ public static final int SWIPE_ZONE_BOTTOM = 0x8;
private static LinearInterpolator sLinearInterpolator = new LinearInterpolator();
private final Interpolator mFastOutLinearInInterpolator;
+ private final int mTouchSlop;
+ private int mSwipeZone;
private float SWIPE_ESCAPE_VELOCITY = 100f; // dp/sec
private int DEFAULT_ESCAPE_ANIMATION_DURATION = 200; // ms
@@ -69,6 +75,7 @@ public class SwipeHelper implements Gefingerpoken {
private VelocityTracker mVelocityTracker;
private float mInitialTouchPos;
+ private float mPerpendicularInitialTouchPos;
private boolean mDragging;
private View mCurrView;
private View mCurrAnimView;
@@ -84,6 +91,8 @@ public class SwipeHelper implements Gefingerpoken {
private int mFalsingThreshold;
private boolean mTouchAboveFalsingThreshold;
+ private float mSwipeProgressFadeEnd;
+
public SwipeHelper(int swipeDirection, Callback callback, Context context) {
mCallback = callback;
mHandler = new Handler();
@@ -91,12 +100,28 @@ public class SwipeHelper implements Gefingerpoken {
mVelocityTracker = VelocityTracker.obtain();
mDensityScale = context.getResources().getDisplayMetrics().density;
mPagingTouchSlop = ViewConfiguration.get(context).getScaledPagingTouchSlop();
+ mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
mLongPressTimeout = (long) (ViewConfiguration.getLongPressTimeout() * 1.5f); // extra long-press!
mFastOutLinearInInterpolator = AnimationUtils.loadInterpolator(context,
android.R.interpolator.fast_out_linear_in);
mFalsingThreshold = context.getResources().getDimensionPixelSize(
R.dimen.swipe_helper_falsing_threshold);
+ if (swipeDirection == X) {
+ mSwipeZone = SWIPE_ZONE_LEFT | SWIPE_ZONE_RIGHT;
+ } else {
+ mSwipeZone = SWIPE_ZONE_TOP | SWIPE_ZONE_BOTTOM;
+ }
+ mSwipeProgressFadeEnd = SWIPE_PROGRESS_FADE_END;
+ }
+
+ public SwipeHelper(int swipeDirection, int swipeZone, Callback callback, Context context) {
+ this(swipeDirection, callback, context);
+ mSwipeZone = swipeZone;
+ }
+
+ public boolean isDragging() {
+ return mDragging;
}
public void setLongPressListener(LongPressListener listener) {
@@ -115,6 +140,10 @@ public class SwipeHelper implements Gefingerpoken {
return mSwipeDirection == X ? ev.getX() : ev.getY();
}
+ private float getPerpendicularPos(MotionEvent ev) {
+ return mSwipeDirection == X ? ev.getY() : ev.getX();
+ }
+
private float getTranslation(View v) {
return mSwipeDirection == X ? v.getTranslationX() : v.getTranslationY();
}
@@ -158,7 +187,7 @@ public class SwipeHelper implements Gefingerpoken {
private float getSwipeProgressForOffset(View view) {
float viewSize = getSize(view);
- final float fadeSize = SWIPE_PROGRESS_FADE_END * viewSize;
+ final float fadeSize = mSwipeProgressFadeEnd * viewSize;
float result = 1.0f;
float pos = getTranslation(view);
if (pos >= viewSize * SWIPE_PROGRESS_FADE_START) {
@@ -236,6 +265,7 @@ public class SwipeHelper implements Gefingerpoken {
mCanCurrViewBeDimissed = mCallback.canChildBeDismissed(mCurrView);
mVelocityTracker.addMovement(ev);
mInitialTouchPos = getPos(ev);
+ mPerpendicularInitialTouchPos = getPerpendicularPos(ev);
if (mLongPressListener != null) {
if (mWatchLongPress == null) {
@@ -413,11 +443,30 @@ public class SwipeHelper implements Gefingerpoken {
case MotionEvent.ACTION_OUTSIDE:
case MotionEvent.ACTION_MOVE:
if (mCurrView != null) {
+ float pos = getPos(ev);
+ float altPos = getPerpendicularPos(ev);
float delta = getPos(ev) - mInitialTouchPos;
float absDelta = Math.abs(delta);
if (absDelta >= getFalsingThreshold()) {
mTouchAboveFalsingThreshold = true;
}
+
+ boolean touchBeyondZoneLimit = true;
+ if (mSwipeDirection == X) {
+ if ((mSwipeZone & SWIPE_ZONE_RIGHT) == 0 && pos > mInitialTouchPos) {
+ touchBeyondZoneLimit = false;
+ } else if ((mSwipeZone & SWIPE_ZONE_LEFT) == 0 && pos < mInitialTouchPos) {
+ touchBeyondZoneLimit = false;
+ }
+ } else {
+ if ((mSwipeZone & SWIPE_ZONE_TOP) == 0 && altPos < mPerpendicularInitialTouchPos) {
+ touchBeyondZoneLimit = false;
+ } else if ((mSwipeZone & SWIPE_ZONE_BOTTOM) == 0 && pos > mInitialTouchPos) {
+ touchBeyondZoneLimit = false;
+ }
+ }
+ if (!touchBeyondZoneLimit) return false;
+
// don't let items that can't be dismissed be dragged more than
// maxScrollDistance
if (CONSTRAIN_SWIPE && !mCallback.canChildBeDismissed(mCurrView)) {
@@ -470,6 +519,10 @@ public class SwipeHelper implements Gefingerpoken {
return true;
}
+ public void setSwipeProgressFadeEnd(float end) {
+ mSwipeProgressFadeEnd = end;
+ }
+
private int getFalsingThreshold() {
float factor = mCallback.getFalsingThresholdFactor();
return (int) (mFalsingThreshold * factor);
@@ -505,6 +558,49 @@ public class SwipeHelper implements Gefingerpoken {
float getFalsingThresholdFactor();
}
+ public static abstract class SimpleCallback implements Callback {
+ public abstract View getChildAtPosition(MotionEvent ev);
+ public abstract View getChildContentView(View v);
+
+ @Override
+ public boolean canChildBeDismissed(View v) {
+ return false;
+ }
+
+ @Override
+ public boolean isAntiFalsingNeeded() {
+ return false;
+ }
+
+ @Override
+ public void onBeginDrag(View v) {
+ }
+
+ @Override
+ public void onChildDismissed(View v) {
+ }
+
+ @Override
+ public void onDragCancelled(View v) {
+ }
+
+ @Override
+ public void onChildSnappedBack(View animView) {
+ }
+
+ @Override
+ public boolean updateSwipeProgress(View animView,
+ boolean dismissable,
+ float swipeProgress) {
+ return false;
+ }
+
+ @Override
+ public float getFalsingThresholdFactor() {
+ return 0;
+ }
+ }
+
/**
* Equivalent to View.OnLongClickListener with coordinates
*/
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
index 0b066af..565fbd7 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
@@ -22,6 +22,7 @@ import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.res.Configuration;
+import android.content.res.ThemeConfig;
import android.os.SystemProperties;
import android.util.Log;
@@ -58,6 +59,7 @@ public class SystemUIApplication extends Application {
private boolean mServicesStarted;
private boolean mBootCompleted;
private final Map<Class<?>, Object> mComponents = new HashMap<Class<?>, Object>();
+ private Configuration mConfig;
@Override
public void onCreate() {
@@ -85,6 +87,7 @@ public class SystemUIApplication extends Application {
}
}
}, filter);
+ mConfig = new Configuration(getResources().getConfiguration());
}
/**
@@ -134,6 +137,12 @@ public class SystemUIApplication extends Application {
@Override
public void onConfigurationChanged(Configuration newConfig) {
+ if (isThemeChange(mConfig, newConfig)) {
+ // theme resource changed so recreate styles and attributes
+ recreateTheme();
+ }
+
+ mConfig.setTo(newConfig);
if (mServicesStarted) {
int len = mServices.length;
for (int i = 0; i < len; i++) {
@@ -150,4 +159,11 @@ public class SystemUIApplication extends Application {
public SystemUI[] getServices() {
return mServices;
}
+
+ private static boolean isThemeChange(Configuration oldConfig, Configuration newConfig) {
+ final ThemeConfig oldThemeConfig = oldConfig != null ? oldConfig.themeConfig : null;
+ final ThemeConfig newThemeConfig = newConfig != null ? newConfig.themeConfig : null;
+
+ return newThemeConfig != null && !newThemeConfig.equals(oldThemeConfig);
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/cm/GlowBackground.java b/packages/SystemUI/src/com/android/systemui/cm/GlowBackground.java
new file mode 100644
index 0000000..8b4bf06
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/cm/GlowBackground.java
@@ -0,0 +1,83 @@
+package com.android.systemui.cm;
+
+import android.animation.Animator;
+import android.animation.ObjectAnimator;
+import android.animation.ValueAnimator;
+import android.graphics.Canvas;
+import android.graphics.ColorFilter;
+import android.graphics.Paint;
+import android.graphics.drawable.Drawable;
+import android.view.animation.AnticipateOvershootInterpolator;
+
+public class GlowBackground extends Drawable implements ValueAnimator.AnimatorUpdateListener {
+
+ private static final int MAX_CIRCLE_SIZE = 150;
+
+ private final Paint mPaint;
+ private Animator mAnimator;
+ private float mCircleSize;
+
+ public GlowBackground(int color) {
+ mPaint = new Paint();
+ mPaint.setColor(color);
+ }
+
+ private void startAnimation(boolean hide) {
+ if (mAnimator != null) {
+ mAnimator.cancel();
+ }
+ if (hide && mCircleSize == 0f) {
+ return;
+ } else if (!hide && mCircleSize == MAX_CIRCLE_SIZE) {
+ return;
+ }
+ mAnimator = getAnimator(hide);
+ mAnimator.start();
+ }
+
+ private Animator getAnimator(boolean hide) {
+ float start = mCircleSize;
+ float end = MAX_CIRCLE_SIZE;
+ if (hide) {
+ end = 0f;
+ }
+ ValueAnimator animator = ObjectAnimator.ofFloat(start, end);
+ animator.setInterpolator(new AnticipateOvershootInterpolator());
+ animator.setDuration(300);
+ animator.addUpdateListener(this);
+ return animator;
+ }
+
+ @Override
+ public void draw(Canvas canvas) {
+ canvas.drawCircle(getBounds().width() / 2, getBounds().height() / 2, mCircleSize, mPaint);
+ }
+
+ @Override
+ public void setAlpha(int i) {
+ }
+
+ @Override
+ public void setColorFilter(ColorFilter colorFilter) {
+ }
+
+ @Override
+ public int getOpacity() {
+ return 0;
+ }
+
+ @Override
+ public void onAnimationUpdate(ValueAnimator valueAnimator) {
+ mCircleSize = (Float) valueAnimator.getAnimatedValue();
+ invalidateSelf();
+ }
+
+ public void hideGlow() {
+ startAnimation(true);
+ }
+
+ public void showGlow() {
+ startAnimation(false);
+ }
+}
+
diff --git a/packages/SystemUI/src/com/android/systemui/cm/LockscreenShortcutsActivity.java b/packages/SystemUI/src/com/android/systemui/cm/LockscreenShortcutsActivity.java
new file mode 100644
index 0000000..3cd86fe
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/cm/LockscreenShortcutsActivity.java
@@ -0,0 +1,265 @@
+package com.android.systemui.cm;
+
+import com.android.internal.app.AssistUtils;
+import com.android.settingslib.cm.ShortcutPickHelper;
+import com.android.systemui.R;
+import com.android.systemui.cm.LockscreenShortcutsHelper.Shortcuts;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.res.ColorStateList;
+import android.graphics.Color;
+import android.graphics.ColorMatrix;
+import android.graphics.ColorMatrixColorFilter;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.text.TextUtils;
+import android.view.MenuItem;
+import android.view.MotionEvent;
+import android.view.View;
+import android.widget.ImageView;
+
+import java.util.ArrayList;
+
+public class LockscreenShortcutsActivity extends Activity implements View.OnClickListener,
+ ShortcutPickHelper.OnPickListener, View.OnTouchListener, LockscreenShortcutsHelper.OnChangeListener {
+
+ private static final int[] sIconIds = new int[]{R.id.left_button, R.id.right_button};
+ private static final String ACTION_APP = "action_app";
+
+ private ActionHolder mActions;
+ private ShortcutPickHelper mPicker;
+ private LockscreenShortcutsHelper mShortcutHelper;
+ private View mSelectedView;
+ private ColorMatrixColorFilter mFilter;
+ private ColorStateList mDefaultTintList;
+ private AssistUtils mAssistUtils;
+
+ @Override
+ public void shortcutPicked(String uri, String friendlyName, boolean isApplication) {
+ onTargetChange(uri);
+ }
+
+ @Override
+ public boolean onTouch(View v, MotionEvent event) {
+ switch (event.getAction()) {
+ case MotionEvent.ACTION_DOWN:
+ GlowBackground background = (GlowBackground) v.getBackground();
+ background.showGlow();
+ break;
+ case MotionEvent.ACTION_UP:
+ case MotionEvent.ACTION_CANCEL:
+ background = (GlowBackground) v.getBackground();
+ background.hideGlow();
+ break;
+ }
+ return false;
+ }
+
+ @Override
+ public void onChange() {
+ updateDrawables();
+ }
+
+ private class ActionHolder {
+ private ArrayList<CharSequence> mAvailableEntries = new ArrayList<CharSequence>();
+ private ArrayList<String> mAvailableValues = new ArrayList<String>();
+
+ public void addAction(String action, int entryResId) {
+ mAvailableEntries.add(getString(entryResId));
+ mAvailableValues.add(action);
+ }
+
+ public int getActionIndex(String action) {
+ int count = mAvailableValues.size();
+ for (int i = 0; i < count; i++) {
+ if (TextUtils.equals(mAvailableValues.get(i), action)) {
+ return i;
+ }
+ }
+
+ return -1;
+ }
+
+ public String getAction(int index) {
+ if (index > mAvailableValues.size()) {
+ return null;
+ }
+
+ return mAvailableValues.get(index);
+ }
+
+ public CharSequence[] getEntries() {
+ return mAvailableEntries.toArray(new CharSequence[mAvailableEntries.size()]);
+ }
+ };
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.lockscreen_shortcuts);
+ getActionBar().setDisplayHomeAsUpEnabled(true);
+
+ mPicker = new ShortcutPickHelper(this, this);
+ mShortcutHelper = new LockscreenShortcutsHelper(this, this);
+ ColorMatrix cm = new ColorMatrix();
+ cm.setSaturation(0);
+ mFilter = new ColorMatrixColorFilter(cm);
+ ImageView unlockButton = (ImageView) findViewById(R.id.middle_button);
+ mDefaultTintList = unlockButton.getImageTintList();
+ mAssistUtils = new AssistUtils(this);
+ createActionList();
+ initiateViews();
+ updateDrawables();
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ switch (item.getItemId()) {
+ case android.R.id.home:
+ finish();
+ return true;
+ }
+ return super.onOptionsItemSelected(item);
+ }
+
+ private void initiateViews() {
+ int size = getResources().getDimensionPixelSize(R.dimen.lockscreen_icon_size);
+ for (int id : sIconIds) {
+ View v = findViewById(id);
+ v.setOnClickListener(this);
+ v.setOnTouchListener(this);
+ GlowBackground background = new GlowBackground(Color.WHITE);
+ background.setBounds(0, 0, size, size);
+ v.setBackground(background);
+ }
+ }
+
+ private void updateDrawables() {
+ for (int i = 0; i < sIconIds.length; i++) {
+ int id = sIconIds[i];
+ ImageView v = (ImageView) findViewById(id);
+ v.setImageTintList(null);
+ v.setColorFilter(null);
+ Shortcuts shortcut = Shortcuts.values()[i];
+ v.setTag(mShortcutHelper.getUriForTarget(shortcut));
+ Drawable drawable;
+ if (mShortcutHelper.isTargetEmpty(shortcut)) {
+ drawable = getResources().getDrawable(R.drawable.ic_lockscreen_shortcuts_blank);
+ } else {
+ if (shortcut == Shortcuts.LEFT_SHORTCUT &&
+ !mShortcutHelper.isTargetCustom(shortcut)) {
+ drawable = getLeftAffordanceDrawable();
+ v.setImageTintList(mDefaultTintList);
+ } else {
+ drawable = mShortcutHelper.getDrawableForTarget(shortcut);
+ if (drawable == null) {
+ drawable = (shortcut == Shortcuts.LEFT_SHORTCUT) ?
+ getLeftAffordanceDrawable()
+ : getResources().getDrawable(R.drawable.ic_camera_alt_24dp);
+ v.setImageTintList(mDefaultTintList);
+ } else {
+ v.setColorFilter(mFilter);
+ }
+ }
+ }
+ v.setImageDrawable(drawable);
+ }
+ }
+
+ private Drawable getLeftAffordanceDrawable() {
+ Drawable drawable;
+ if (canLaunchVoiceAssist()) {
+ drawable = getResources().getDrawable(R.drawable.ic_mic_26dp);
+ } else {
+ drawable = getResources().getDrawable(R.drawable.ic_phone_24dp);
+ }
+ return drawable;
+ }
+
+ private boolean canLaunchVoiceAssist() {
+ return mAssistUtils.activeServiceSupportsLaunchFromKeyguard();
+ }
+
+ private void createActionList() {
+ mActions = new ActionHolder();
+ mActions.addAction(LockscreenShortcutsHelper.NONE, R.string.lockscreen_none_target);
+ mActions.addAction(LockscreenShortcutsHelper.DEFAULT, R.string.lockscreen_default_target);
+ mActions.addAction(ACTION_APP, R.string.select_application);
+ }
+
+ @Override
+ public void onActivityResult(int requestCode, int resultCode, Intent data) {
+ mPicker.onActivityResult(requestCode, resultCode, data);
+ super.onActivityResult(requestCode, resultCode, data);
+ }
+
+ @Override
+ public void onClick(View v) {
+ mSelectedView = v;
+
+ final GlowBackground background = (GlowBackground) mSelectedView.getBackground();
+
+ mSelectedView.postOnAnimation(new Runnable() {
+ @Override
+ public void run() {
+ background.showGlow();
+ }
+ });
+
+ final DialogInterface.OnClickListener l = new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int item) {
+ onTargetChange(mActions.getAction(item));
+ dialog.dismiss();
+ }
+ };
+
+ final DialogInterface.OnCancelListener cancel = new DialogInterface.OnCancelListener() {
+ @Override
+ public void onCancel(DialogInterface dialog) {
+ onTargetChange(null);
+ }
+ };
+
+ final AlertDialog dialog = new AlertDialog.Builder(this)
+ .setTitle(R.string.lockscreen_choose_action_title)
+ .setItems(mActions.getEntries(), l)
+ .setOnCancelListener(cancel)
+ .create();
+
+ dialog.show();
+ }
+
+ private void onTargetChange(String uri) {
+ if (uri == null) {
+ if (mSelectedView != null) {
+ final GlowBackground background = (GlowBackground) mSelectedView.getBackground();
+ background.hideGlow();
+ }
+ return;
+ }
+
+ if (uri.equals(ACTION_APP)) {
+ mPicker.pickShortcut(null, null, 0, false);
+ } else {
+ mSelectedView.setTag(uri);
+ saveCustomActions();
+ GlowBackground background = (GlowBackground) mSelectedView.getBackground();
+ background.hideGlow();
+ }
+ }
+
+ private void saveCustomActions() {
+ ArrayList<String> targets = new ArrayList<String>();
+ for (int id : sIconIds) {
+ View v = findViewById(id);
+ String uri = (String) v.getTag();
+ targets.add(uri);
+ }
+ mShortcutHelper.saveTargets(targets);
+ }
+
+}
diff --git a/packages/SystemUI/src/com/android/systemui/cm/LockscreenShortcutsHelper.java b/packages/SystemUI/src/com/android/systemui/cm/LockscreenShortcutsHelper.java
new file mode 100644
index 0000000..b47b69f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/cm/LockscreenShortcutsHelper.java
@@ -0,0 +1,201 @@
+package com.android.systemui.cm;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.pm.PackageManager;
+import android.content.res.Resources;
+import android.database.ContentObserver;
+import android.graphics.Bitmap;
+import android.graphics.ColorFilter;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.Looper;
+
+import java.net.URISyntaxException;
+import java.util.ArrayList;
+import java.util.List;
+
+import com.android.systemui.R;
+import cyanogenmod.providers.CMSettings;
+
+public class LockscreenShortcutsHelper {
+
+ private Handler mHandler;
+
+ public enum Shortcuts {
+ LEFT_SHORTCUT(0),
+ RIGHT_SHORTCUT(1);
+
+ private final int index;
+
+ Shortcuts(int index) {
+ this.index = index;
+ }
+ }
+
+ public static final String DEFAULT = "default";
+ public static final String NONE = "none";
+ private static final String DELIMITER = "|";
+
+ private final Context mContext;
+ private OnChangeListener mListener;
+ private List<String> mTargetActivities;
+
+ public interface OnChangeListener {
+ void onChange();
+ }
+
+ public LockscreenShortcutsHelper(Context context, OnChangeListener listener) {
+ mContext = context;
+ if (listener != null) {
+ mListener = listener;
+ mHandler = new Handler(Looper.getMainLooper());
+ registerAndFetchTargets();
+ } else {
+ fetchTargets();
+ }
+ }
+
+ public void registerAndFetchTargets() {
+ mContext.getContentResolver().registerContentObserver(
+ CMSettings.Secure.getUriFor(CMSettings.Secure.LOCKSCREEN_TARGETS), false, mObserver);
+ fetchTargets();
+ }
+
+ private ContentObserver mObserver = new ContentObserver(null) {
+ @Override
+ public void onChange(boolean selfChange, Uri uri) {
+ fetchTargets();
+ if (mListener != null && mHandler != null) {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ mListener.onChange();
+ }
+ });
+ }
+ }
+ };
+
+ public void cleanup() {
+ mContext.getContentResolver().unregisterContentObserver(mObserver);
+ }
+
+ public static class TargetInfo {
+ public Drawable icon;
+ public ColorFilter colorFilter;
+ public String uri;
+ public TargetInfo(Drawable icon, ColorFilter colorFilter, String uri) {
+ this.icon = icon;
+ this.colorFilter = colorFilter;
+ this.uri = uri;
+ }
+ }
+
+ private void fetchTargets() {
+ mTargetActivities = CMSettings.Secure.getDelimitedStringAsList(mContext.getContentResolver(),
+ CMSettings.Secure.LOCKSCREEN_TARGETS, DELIMITER);
+ int itemsToPad = Shortcuts.values().length - mTargetActivities.size();
+ if (itemsToPad > 0) {
+ for (int i = 0; i < itemsToPad; i++) {
+ mTargetActivities.add(DEFAULT);
+ }
+ }
+ }
+
+ public Drawable getDrawableForTarget(Shortcuts shortcut) {
+ Intent intent = getIntent(shortcut);
+ if (intent != null) {
+ PackageManager pm = mContext.getPackageManager();
+ ActivityInfo info = intent.resolveActivityInfo(pm, PackageManager.GET_ACTIVITIES);
+ if (info != null) {
+ return getScaledDrawable(info.loadIcon(pm));
+ }
+ }
+ return null;
+ }
+
+ public String getUriForTarget(Shortcuts shortcuts) {
+ return mTargetActivities.get(shortcuts.index);
+ }
+
+ private Drawable getScaledDrawable(Drawable drawable) {
+ if (drawable instanceof BitmapDrawable) {
+ Resources res = mContext.getResources();
+ int width = res.getDimensionPixelSize(R.dimen.keyguard_affordance_icon_width);
+ int height = res.getDimensionPixelSize(R.dimen.keyguard_affordance_icon_height);
+ return new BitmapDrawable(mContext.getResources(),
+ Bitmap.createScaledBitmap(((BitmapDrawable) drawable).getBitmap(),
+ width, height, true));
+ } else {
+ return drawable;
+ }
+ }
+
+ private String getFriendlyActivityName(Intent intent, boolean labelOnly) {
+ PackageManager pm = mContext.getPackageManager();
+ ActivityInfo ai = intent.resolveActivityInfo(pm, PackageManager.GET_ACTIVITIES);
+ String friendlyName = null;
+ if (ai != null) {
+ friendlyName = ai.loadLabel(pm).toString();
+ if (friendlyName == null && !labelOnly) {
+ friendlyName = ai.name;
+ }
+ }
+ return friendlyName != null || labelOnly ? friendlyName : intent.toUri(0);
+ }
+
+ private String getFriendlyShortcutName(Intent intent) {
+ String activityName = getFriendlyActivityName(intent, true);
+ String name = intent.getStringExtra(Intent.EXTRA_SHORTCUT_NAME);
+
+ if (activityName != null && name != null) {
+ return activityName + ": " + name;
+ }
+ return name != null ? name : intent.toUri(0);
+ }
+
+ public String getFriendlyNameForUri(Shortcuts shortcut) {
+ Intent intent = getIntent(shortcut);
+ if (Intent.ACTION_MAIN.equals(intent.getAction())) {
+ return getFriendlyActivityName(intent, false);
+ }
+ return getFriendlyShortcutName(intent);
+ }
+
+ public boolean isTargetCustom(Shortcuts shortcut) {
+ if (mTargetActivities == null || mTargetActivities.isEmpty()) {
+ return false;
+ }
+ String action = mTargetActivities.get(shortcut.index);
+ if (DEFAULT.equals(action)) {
+ return false;
+ }
+
+ return NONE.equals(action) || getIntent(shortcut) != null;
+ }
+
+ public boolean isTargetEmpty(Shortcuts shortcut) {
+ return mTargetActivities != null &&
+ !mTargetActivities.isEmpty() &&
+ mTargetActivities.get(shortcut.index).equals(NONE);
+ }
+
+ public Intent getIntent(Shortcuts shortcut) {
+ Intent intent = null;
+ try {
+ intent = Intent.parseUri(mTargetActivities.get(shortcut.index), 0);
+ } catch (URISyntaxException e) {
+ e.printStackTrace();
+ }
+ return intent;
+ }
+
+ public void saveTargets(ArrayList<String> targets) {
+ CMSettings.Secure.putListAsDelimitedString(mContext.getContentResolver(),
+ CMSettings.Secure.LOCKSCREEN_TARGETS, DELIMITER, targets);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/cm/SpamMessageProvider.java b/packages/SystemUI/src/com/android/systemui/cm/SpamMessageProvider.java
new file mode 100644
index 0000000..7afa04d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/cm/SpamMessageProvider.java
@@ -0,0 +1,204 @@
+package com.android.systemui.cm;
+
+import android.content.ContentProvider;
+import android.content.ContentValues;
+import android.content.UriMatcher;
+import android.database.Cursor;
+import android.database.DatabaseUtils;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteQueryBuilder;
+import android.net.Uri;
+import android.text.TextUtils;
+
+import com.android.internal.util.cm.SpamFilter;
+import com.android.internal.util.cm.SpamFilter.SpamContract.PackageTable;
+import com.android.internal.util.cm.SpamFilter.SpamContract.NotificationTable;
+
+public class SpamMessageProvider extends ContentProvider {
+ public static final String AUTHORITY = SpamFilter.AUTHORITY;
+
+ private static final String UPDATE_COUNT_QUERY =
+ "UPDATE " + NotificationTable.TABLE_NAME +
+ " SET " + NotificationTable.LAST_BLOCKED + "=%d," +
+ NotificationTable.COUNT + "=" + NotificationTable.COUNT + "+1 " +
+ " WHERE " + NotificationTable.ID + "='%s'";
+
+ private static final UriMatcher sURIMatcher = new UriMatcher(UriMatcher.NO_MATCH);
+ private static final int PACKAGES = 0;
+ private static final int MESSAGES = 1;
+ private static final int PACKAGE_FOR_ID = 2;
+ private static final int MESSAGE_UPDATE_COUNT = 3;
+ private static final int MESSAGE_FOR_ID = 4;
+ static {
+ sURIMatcher.addURI(AUTHORITY, "packages", PACKAGES);
+ sURIMatcher.addURI(AUTHORITY, "messages", MESSAGES);
+ sURIMatcher.addURI(AUTHORITY, "message/#", MESSAGE_FOR_ID);
+ sURIMatcher.addURI(AUTHORITY, "message/inc_count/#", MESSAGE_UPDATE_COUNT);
+ }
+
+ private SpamOpenHelper mDbHelper;
+
+ @Override
+ public boolean onCreate() {
+ mDbHelper = new SpamOpenHelper(getContext());
+ return true;
+ }
+
+ @Override
+ public Cursor query(Uri uri, String[] projection, String selection,
+ String[] selectionArgs, String sortOrder) {
+ int match = sURIMatcher.match(uri);
+ switch (match) {
+ case PACKAGE_FOR_ID:
+ return mDbHelper.getReadableDatabase().query(PackageTable.TABLE_NAME,
+ new String[]{NotificationTable.ID}, PackageTable.PACKAGE_NAME + "=?",
+ new String[]{uri.getLastPathSegment()}, null, null, null);
+ case PACKAGES:
+ return mDbHelper.getReadableDatabase().query(PackageTable.TABLE_NAME,
+ projection, selection, selectionArgs, null, null, sortOrder);
+ case MESSAGES:
+ SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
+ qb.setTables(NotificationTable.TABLE_NAME + " LEFT OUTER JOIN " + PackageTable.TABLE_NAME +
+ " ON (" + NotificationTable.TABLE_NAME + "." + NotificationTable.PACKAGE_ID + " = "
+ + PackageTable.TABLE_NAME + "." + PackageTable.ID + ")");
+ SQLiteDatabase db = mDbHelper.getReadableDatabase();
+ return qb.query(db, projection, selection, selectionArgs,
+ null, null, null);
+ case MESSAGE_FOR_ID:
+ qb = new SQLiteQueryBuilder();
+ qb.setTables(NotificationTable.TABLE_NAME + " LEFT OUTER JOIN " + PackageTable.TABLE_NAME +
+ " ON (" + NotificationTable.TABLE_NAME + "." + NotificationTable.PACKAGE_ID + " = "
+ + PackageTable.TABLE_NAME + "." + PackageTable.ID + ")");
+ qb.appendWhere(NotificationTable.TABLE_NAME + "." + NotificationTable.ID + "="
+ + uri.getLastPathSegment());
+ return qb.query(mDbHelper.getReadableDatabase(),
+ null, null, null, null,
+ null, null);
+ default:
+ return null;
+ }
+ }
+
+ private long getPackageId(String pkg) {
+ long rowId = -1;
+ Cursor idCursor = mDbHelper.getReadableDatabase().query(PackageTable.TABLE_NAME,
+ new String[]{NotificationTable.ID}, PackageTable.PACKAGE_NAME + "=?",
+ new String[]{pkg}, null, null, null);
+ if (idCursor != null) {
+ if (idCursor.moveToFirst()) {
+ rowId = idCursor.getLong(0);
+ }
+ idCursor.close();
+ }
+ return rowId;
+ }
+
+ @Override
+ public String getType(Uri uri) {
+ return null;
+ }
+
+ @Override
+ public Uri insert(Uri uri, ContentValues values) {
+ if (values == null) {
+ return null;
+ }
+ int match = sURIMatcher.match(uri);
+ switch (match) {
+ case MESSAGES:
+ String msgText = values.getAsString(NotificationTable.MESSAGE_TEXT);
+ String packageName = values.getAsString(PackageTable.PACKAGE_NAME);
+ if (TextUtils.isEmpty(msgText) || TextUtils.isEmpty(packageName)) {
+ return null;
+ }
+ values.clear();
+ values.put(PackageTable.PACKAGE_NAME, packageName);
+ long packageId = getPackageId(packageName);
+ if (packageId == -1) {
+ SQLiteDatabase writableDb = mDbHelper.getWritableDatabase();
+ packageId = writableDb.insert(
+ PackageTable.TABLE_NAME, null, values);
+ }
+ if (packageId != -1) {
+ values.clear();
+ values.put(NotificationTable.MESSAGE_TEXT, msgText);
+ values.put(NotificationTable.NORMALIZED_TEXT,
+ SpamFilter.getNormalizedContent(msgText));
+ values.put(NotificationTable.PACKAGE_ID, packageId);
+ values.put(NotificationTable.LAST_BLOCKED, System.currentTimeMillis());
+ long id = mDbHelper.getReadableDatabase().insert(NotificationTable.TABLE_NAME,
+ null, values);
+ if (id != -1) {
+ notifyChange(String.valueOf(id));
+ }
+ }
+ return null;
+ default:
+ return null;
+ }
+ }
+
+ private void notifyChange(String id) {
+ Uri uri = Uri.withAppendedPath(SpamFilter.NOTIFICATION_URI,
+ id);
+ getContext().getContentResolver().notifyChange(uri, null);
+ }
+
+ private void removePackageIfNecessary(int packageId) {
+ long numEntries = DatabaseUtils.queryNumEntries(mDbHelper.getReadableDatabase(),
+ NotificationTable.TABLE_NAME, NotificationTable.PACKAGE_ID + "=?",
+ new String[]{String.valueOf(packageId)});
+ if (numEntries == 0) {
+ SQLiteDatabase writableDb = mDbHelper.getWritableDatabase();
+ writableDb.delete(PackageTable.TABLE_NAME, PackageTable.ID + "=?",
+ new String[]{String.valueOf(packageId)});
+ }
+ }
+
+ @Override
+ public int delete(Uri uri, String selection, String[] selectionArgs) {
+ int match = sURIMatcher.match(uri);
+ switch (match) {
+ case MESSAGE_FOR_ID:
+ int packageId = -1;
+ Cursor idCursor = mDbHelper.getReadableDatabase().query(NotificationTable.TABLE_NAME,
+ new String[]{NotificationTable.PACKAGE_ID}, NotificationTable.ID + "=?",
+ new String[]{uri.getLastPathSegment()}, null, null, null);
+ if (idCursor != null) {
+ if (idCursor.moveToFirst()) {
+ packageId = idCursor.getInt(0);
+ }
+ idCursor.close();
+ }
+ SQLiteDatabase writableDb = mDbHelper.getWritableDatabase();
+ String id = uri.getLastPathSegment();
+ int result = writableDb.delete(NotificationTable.TABLE_NAME,
+ NotificationTable.ID + "=?", new String[]{id});
+ removePackageIfNecessary(packageId);
+ if (result > 0) {
+ notifyChange(id);
+ }
+ return result;
+ default:
+ return 0;
+ }
+ }
+
+ @Override
+ public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
+ int match = sURIMatcher.match(uri);
+ switch (match) {
+ case MESSAGE_UPDATE_COUNT:
+ String id = uri.getLastPathSegment();
+ String formattedQuery = String.format(UPDATE_COUNT_QUERY,
+ System.currentTimeMillis(), id);
+ SQLiteDatabase writableDb = mDbHelper.getWritableDatabase();
+ writableDb.execSQL(formattedQuery);
+ notifyChange(id);
+ return 0;
+ default:
+ return 0;
+ }
+ }
+
+}
diff --git a/packages/SystemUI/src/com/android/systemui/cm/SpamOpenHelper.java b/packages/SystemUI/src/com/android/systemui/cm/SpamOpenHelper.java
new file mode 100644
index 0000000..45dc91c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/cm/SpamOpenHelper.java
@@ -0,0 +1,46 @@
+package com.android.systemui.cm;
+
+import com.android.internal.util.cm.SpamFilter.SpamContract.NotificationTable;
+import com.android.internal.util.cm.SpamFilter.SpamContract.PackageTable;
+
+import android.content.Context;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteOpenHelper;
+
+public class SpamOpenHelper extends SQLiteOpenHelper {
+
+ private static final String DATABASE_NAME = "spam.db";
+ private static final int VERSION = 4;
+ private static final String CREATE_PACKAGES_TABLE =
+ "create table " + PackageTable.TABLE_NAME + "(" +
+ PackageTable.ID + " INTEGER PRIMARY KEY," +
+ PackageTable.PACKAGE_NAME + " TEXT UNIQUE);";
+ private static final String CREATE_NOTIFICATIONS_TABLE =
+ "create table " + NotificationTable.TABLE_NAME + "(" +
+ NotificationTable.ID + " INTEGER PRIMARY KEY AUTOINCREMENT," +
+ NotificationTable.PACKAGE_ID + " INTEGER," +
+ NotificationTable.MESSAGE_TEXT + " STRING," +
+ NotificationTable.LAST_BLOCKED + " INTEGER," +
+ NotificationTable.NORMALIZED_TEXT + " STRING," +
+ NotificationTable.COUNT + " INTEGER DEFAULT 0);";
+
+ private Context mContext;
+
+ public SpamOpenHelper(Context context) {
+ super(context, DATABASE_NAME, null, VERSION);
+ mContext = context;
+ }
+
+ @Override
+ public void onCreate(SQLiteDatabase db) {
+ db.execSQL(CREATE_PACKAGES_TABLE);
+ db.execSQL(CREATE_NOTIFICATIONS_TABLE);
+ }
+
+ @Override
+ public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
+ mContext.deleteDatabase(DATABASE_NAME);
+ onCreate(db);
+ }
+
+}
diff --git a/packages/SystemUI/src/com/android/systemui/cm/UserContentObserver.java b/packages/SystemUI/src/com/android/systemui/cm/UserContentObserver.java
new file mode 100644
index 0000000..5ece744
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/cm/UserContentObserver.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2014 The CyanogenMod 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.cm;
+
+import android.app.ActivityManagerNative;
+import android.app.IUserSwitchObserver;
+import android.database.ContentObserver;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.IRemoteCallback;
+import android.os.RemoteException;
+import android.util.Log;
+
+/**
+ * Simple extension of ContentObserver that also listens for user switch events to call update
+ */
+public abstract class UserContentObserver extends ContentObserver {
+ private static final String TAG = "UserContentObserver";
+
+ private Runnable mUpdateRunnable;
+
+ private IUserSwitchObserver mUserSwitchObserver = new IUserSwitchObserver.Stub() {
+ @Override
+ public void onUserSwitching(int newUserId, IRemoteCallback reply) {
+ }
+ @Override
+ public void onUserSwitchComplete(int newUserId) throws RemoteException {
+ mHandler.post(mUpdateRunnable);
+ }
+ @Override
+ public void onForegroundProfileSwitch(int newProfileId) {
+ }
+ };
+
+ private Handler mHandler;
+
+ public UserContentObserver(Handler handler) {
+ super(handler);
+ mHandler = handler;
+ mUpdateRunnable = new Runnable() {
+ @Override
+ public void run() {
+ update();
+ }
+ };
+ }
+
+ protected void observe() {
+ try {
+ ActivityManagerNative.getDefault().registerUserSwitchObserver(mUserSwitchObserver);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Unable to register user switch observer!", e);
+ }
+ }
+
+ protected void unobserve() {
+ try {
+ mHandler.removeCallbacks(mUpdateRunnable);
+ ActivityManagerNative.getDefault().unregisterUserSwitchObserver(mUserSwitchObserver);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Unable to unregister user switch observer!", e);
+ }
+ }
+
+ protected abstract void update();
+
+ @Override
+ public void onChange(boolean selfChange) {
+ update();
+ }
+
+ @Override
+ public void onChange(boolean selfChange, Uri uri) {
+ update();
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeService.java b/packages/SystemUI/src/com/android/systemui/doze/DozeService.java
index 39423f2..f223400 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeService.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeService.java
@@ -218,7 +218,7 @@ public class DozeService extends DreamService {
// Here we need a wakelock to stay awake until the pulse is finished.
mWakeLock.acquire();
mPulsing = true;
- if (!mDozeParameters.getProxCheckBeforePulse()) {
+ if (!mDozeParameters.getProxCheckBeforePulse(reason)) {
// skip proximity check
continuePulsing(reason);
return;
@@ -265,6 +265,7 @@ public class DozeService extends DreamService {
@Override
public void onPulseStarted() {
if (mPulsing && mDreaming) {
+ mContext.sendBroadcast(new Intent(Intent.ACTION_DOZE_PULSE_STARTING));
turnDisplayOn();
}
}
@@ -356,7 +357,7 @@ public class DozeService extends DreamService {
if (DEBUG) Log.d(mTag, "No more schedule resets remaining");
return;
}
- final long pulseDuration = mDozeParameters.getPulseDuration(false /*pickup*/);
+ final long pulseDuration = mDozeParameters.getPulseDuration(DozeLog.PULSE_REASON_NOTIFICATION);
boolean pulseImmediately = System.currentTimeMillis() >= notificationTimeMs;
if ((notificationTimeMs - mLastScheduleResetTime) >= pulseDuration) {
mScheduleResetsRemaining--;
diff --git a/packages/SystemUI/src/com/android/systemui/egg/CMLand.java b/packages/SystemUI/src/com/android/systemui/egg/CMLand.java
new file mode 100644
index 0000000..6020b45
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/egg/CMLand.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2014-2015 The CyanogenMod 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.egg;
+
+import com.android.systemui.R;
+
+import android.content.Context;
+import android.util.AttributeSet;
+
+public class CMLand extends MLand {
+ public static final String TAG = "CMLand";
+
+ public CMLand(Context context) {
+ this(context, null);
+ }
+
+ public CMLand(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public CMLand(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ }
+
+ @Override
+ protected int getEggPlayer() {
+ return R.drawable.cid;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/egg/MLand.java b/packages/SystemUI/src/com/android/systemui/egg/MLand.java
index b84777b..1b22e04 100644
--- a/packages/SystemUI/src/com/android/systemui/egg/MLand.java
+++ b/packages/SystemUI/src/com/android/systemui/egg/MLand.java
@@ -1,5 +1,6 @@
/*
* Copyright (C) 2015 The Android Open Source Project
+ * Copyright (C) 2014-2015 The CyanogenMod Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -210,6 +211,8 @@ public class MLand extends FrameLayout {
// we assume everything will be laid out left|top
setLayoutDirection(LAYOUT_DIRECTION_LTR);
+ Player.eggPlayer = getEggPlayer();
+
setupPlayers(DEFAULT_PLAYERS);
MetricsLogger.count(getContext(), "egg_mland_create", 1);
@@ -1007,7 +1010,7 @@ public class MLand extends FrameLayout {
public void step(long t_ms, long dt_ms, float t, float dt);
}
- private static class Player extends ImageView implements GameView {
+ protected static class Player extends ImageView implements GameView {
public float dv;
public int color;
private MLand mLand;
@@ -1017,6 +1020,8 @@ public class MLand extends FrameLayout {
private int mScore;
private TextView mScoreField;
+ protected static int eggPlayer;
+
private final int[] sColors = new int[] {
//0xFF78C557,
0xFFDB4437,
@@ -1088,7 +1093,7 @@ public class MLand extends FrameLayout {
public Player(Context context) {
super(context);
- setBackgroundResource(R.drawable.android);
+ setBackgroundResource(eggPlayer);
getBackground().setTintMode(PorterDuff.Mode.SRC_ATOP);
color = sColors[(sNextColor++%sColors.length)];
getBackground().setTint(color);
@@ -1438,4 +1443,8 @@ public class MLand extends FrameLayout {
v = z = 0;
}
}
+
+ protected int getEggPlayer() {
+ return R.drawable.android;
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/egg/MLandActivity.java b/packages/SystemUI/src/com/android/systemui/egg/MLandActivity.java
index cdda45f..6fd7387 100644
--- a/packages/SystemUI/src/com/android/systemui/egg/MLandActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/egg/MLandActivity.java
@@ -1,5 +1,6 @@
/*
* Copyright (C) 2015 The Android Open Source Project
+ * Copyright (C) 2014-2015 The CyanogenMod Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -29,7 +30,14 @@ public class MLandActivity extends Activity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- setContentView(R.layout.mland);
+ final boolean isCM = getIntent().getBooleanExtra("is_cm", false);
+ if (isCM) {
+ setContentView(R.layout.cmland);
+ mLand = (CMLand) findViewById(R.id.world);
+ } else {
+ setContentView(R.layout.mland);
+ mLand = (MLand) findViewById(R.id.world);
+ }
mLand = (MLand) findViewById(R.id.world);
mLand.setScoreFieldHolder((ViewGroup) findViewById(R.id.scores));
final View welcome = findViewById(R.id.welcome);
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
index d2c60ef..a6ca6a0 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
@@ -90,6 +90,12 @@ public class KeyguardService extends Service {
mKeyguardViewMediator.setOccluded(isOccluded);
}
+ @Override
+ public void showKeyguard() {
+ checkPermission();
+ mKeyguardViewMediator.showKeyguard();
+ }
+
@Override // Binder interface
public void dismiss() {
checkPermission();
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 055b5ef..2833759 100644..100755
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -23,13 +23,16 @@ import android.app.AlarmManager;
import android.app.PendingIntent;
import android.app.SearchManager;
import android.app.StatusBarManager;
+import android.app.admin.DevicePolicyManager;
import android.app.trust.TrustManager;
import android.content.BroadcastReceiver;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.SharedPreferences;
import android.content.pm.UserInfo;
+import android.database.ContentObserver;
import android.media.AudioManager;
import android.media.SoundPool;
import android.os.Bundle;
@@ -50,12 +53,19 @@ import android.util.EventLog;
import android.util.Log;
import android.util.Slog;
import android.view.IWindowManager;
+import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManagerGlobal;
import android.view.WindowManagerPolicy;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
+import com.android.systemui.cm.UserContentObserver;
+import com.android.systemui.qs.tiles.LockscreenToggleTile;
+import com.android.systemui.statusbar.StatusBarState;
+import cyanogenmod.app.Profile;
+import cyanogenmod.app.ProfileManager;
+
import com.android.internal.policy.IKeyguardDrawnCallback;
import com.android.internal.policy.IKeyguardExitCallback;
import com.android.internal.policy.IKeyguardStateCallback;
@@ -73,6 +83,7 @@ import com.android.systemui.statusbar.phone.PhoneStatusBar;
import com.android.systemui.statusbar.phone.ScrimController;
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
import com.android.systemui.statusbar.phone.StatusBarWindowManager;
+import cyanogenmod.providers.CMSettings;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -135,6 +146,15 @@ public class KeyguardViewMediator extends SystemUI {
private static final String DELAYED_KEYGUARD_ACTION =
"com.android.internal.policy.impl.PhoneWindowManager.DELAYED_KEYGUARD";
+ private static final String DISMISS_KEYGUARD_SECURELY_ACTION =
+ "com.android.keyguard.action.DISMISS_KEYGUARD_SECURELY";
+
+ private static final String KEYGUARD_SERVICE_ACTION_STATE_CHANGE =
+ "com.android.internal.action.KEYGUARD_SERVICE_STATE_CHANGED";
+ private static final String KEYGUARD_SERVICE_EXTRA_ACTIVE = "active";
+
+ private static final String DECRYPT_STATE = "trigger_restart_framework";
+
// used for handler messages
private static final int SHOW = 2;
private static final int HIDE = 3;
@@ -155,6 +175,7 @@ public class KeyguardViewMediator extends SystemUI {
private static final int NOTIFY_SCREEN_TURNED_ON = 22;
private static final int NOTIFY_SCREEN_TURNED_OFF = 23;
private static final int NOTIFY_STARTED_GOING_TO_SLEEP = 24;
+ private static final int NOTIFY_KEYGUARD_PANEL_FOCUS_CHANGED = 25;
/**
* The default amount of time we stay awake (used for all key input)
@@ -187,7 +208,6 @@ public class KeyguardViewMediator extends SystemUI {
private AudioManager mAudioManager;
private StatusBarManager mStatusBarManager;
private boolean mSwitchingUser;
-
private boolean mSystemReady;
private boolean mBootCompleted;
private boolean mBootSendUserPresent;
@@ -237,6 +257,8 @@ public class KeyguardViewMediator extends SystemUI {
// true if the keyguard is hidden by another window
private boolean mOccluded = false;
+ private boolean mKeyguardPanelFocused = false;
+
/**
* Helps remember whether the screen has turned on since the last time
* it turned off due to timeout. see {@link #onScreenTurnedOff(int)}
@@ -250,6 +272,11 @@ public class KeyguardViewMediator extends SystemUI {
*/
private IKeyguardExitCallback mExitSecureCallback;
+ /**
+ * Whether we are bound to the service delegate
+ */
+ private boolean mKeyguardBound;
+
// the properties of the keyguard
private KeyguardUpdateMonitor mUpdateMonitor;
@@ -267,6 +294,11 @@ public class KeyguardViewMediator extends SystemUI {
private boolean mHiding;
/**
+ * Whether we are disabling the lock screen internally
+ */
+ private boolean mInternallyDisabled = false;
+
+ /**
* we send this intent when the keyguard is dismissed.
*/
private static final Intent USER_PRESENT_INTENT = new Intent(Intent.ACTION_USER_PRESENT)
@@ -318,9 +350,57 @@ public class KeyguardViewMediator extends SystemUI {
*/
private boolean mPendingLock;
+ private boolean mCryptKeeperEnabled = true;
+
private boolean mWakeAndUnlocking;
private IKeyguardDrawnCallback mDrawnCallback;
+ private LockscreenEnabledSettingsObserver mSettingsObserver;
+ private PhoneStatusBar mStatusBar;
+
+ public static class LockscreenEnabledSettingsObserver extends UserContentObserver {
+
+ private static final String KEY_ENABLED = "lockscreen_enabled";
+
+ private boolean mObserving;
+ private SharedPreferences mPrefs;
+ private Context mContext;
+
+ public LockscreenEnabledSettingsObserver(Context context, Handler handler) {
+ super(handler);
+ mContext = context;
+ mPrefs = mContext.getSharedPreferences("quicksettings", Context.MODE_PRIVATE);
+ }
+
+ public boolean getPersistedDefaultOldSetting() {
+ return mPrefs.getBoolean(KEY_ENABLED, true);
+ }
+
+ @Override
+ public void observe() {
+ if (mObserving) {
+ return;
+ }
+ mObserving = true;
+ mContext.getContentResolver().registerContentObserver(CMSettings.Secure.getUriFor(
+ CMSettings.Secure.LOCKSCREEN_INTERNALLY_ENABLED), false, this,
+ UserHandle.USER_ALL);
+ update();
+ }
+
+ @Override
+ public void unobserve() {
+ if (mObserving) {
+ mObserving = false;
+ mContext.getContentResolver().unregisterContentObserver(this);
+ }
+ }
+
+ @Override
+ public void update() {
+ }
+ }
+
KeyguardUpdateMonitorCallback mUpdateCallback = new KeyguardUpdateMonitorCallback() {
@Override
@@ -407,21 +487,20 @@ public class KeyguardViewMediator extends SystemUI {
// only force lock screen in case of missing sim if user hasn't
// gone through setup wizard
synchronized (this) {
- if (shouldWaitForProvisioning()) {
- if (!mShowing) {
- if (DEBUG_SIM_STATES) Log.d(TAG, "ICC_ABSENT isn't showing,"
- + " we need to show the keyguard since the "
- + "device isn't provisioned yet.");
- doKeyguardLocked(null);
- } else {
- resetStateLocked();
- }
+ if (shouldWaitForProvisioning() && !mShowing) {
+ if (DEBUG_SIM_STATES) Log.d(TAG, "ICC_ABSENT isn't showing,"
+ + " we need to show the keyguard since the "
+ + "device isn't provisioned yet.");
+ doKeyguardLocked(null);
+ } else {
+ resetStateLocked();
}
}
break;
case PIN_REQUIRED:
case PUK_REQUIRED:
synchronized (this) {
+ mStatusBar.hideHeadsUp();
if (!mShowing) {
if (DEBUG_SIM_STATES) Log.d(TAG,
"INTENT_VALUE_ICC_LOCKED and keygaurd isn't "
@@ -447,7 +526,10 @@ public class KeyguardViewMediator extends SystemUI {
break;
case READY:
synchronized (this) {
- if (mShowing) {
+ if ((mInternallyDisabled || isProfileDisablingKeyguard())
+ && !mUpdateMonitor.isSimPinSecure()) {
+ hideLocked();
+ } else if (mShowing) {
resetStateLocked();
}
}
@@ -555,8 +637,12 @@ public class KeyguardViewMediator extends SystemUI {
mShowKeyguardWakeLock = mPM.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "show keyguard");
mShowKeyguardWakeLock.setReferenceCounted(false);
-
mContext.registerReceiver(mBroadcastReceiver, new IntentFilter(DELAYED_KEYGUARD_ACTION));
+ mContext.registerReceiver(mBroadcastReceiver, new IntentFilter(DISMISS_KEYGUARD_SECURELY_ACTION),
+ android.Manifest.permission.CONTROL_KEYGUARD, null);
+ mContext.registerReceiver(mBroadcastReceiver, new IntentFilter(KEYGUARD_SERVICE_ACTION_STATE_CHANGE),
+ android.Manifest.permission.CONTROL_KEYGUARD, null);
+ mContext.registerReceiver(mBroadcastReceiver, new IntentFilter(TelephonyManager.ACTION_PHONE_STATE_CHANGED));
mKeyguardDisplayManager = new KeyguardDisplayManager(mContext);
@@ -608,6 +694,25 @@ public class KeyguardViewMediator extends SystemUI {
mHideAnimation = AnimationUtils.loadAnimation(mContext,
com.android.internal.R.anim.lock_screen_behind_enter);
+
+ mSettingsObserver = new LockscreenEnabledSettingsObserver(mContext, new Handler()) {
+ @Override
+ public void update() {
+ boolean newDisabledState = CMSettings.Secure.getIntForUser(mContext.getContentResolver(),
+ CMSettings.Secure.LOCKSCREEN_INTERNALLY_ENABLED,
+ getPersistedDefaultOldSetting() ? 1 : 0,
+ UserHandle.USER_CURRENT) == 0;
+
+ synchronized (KeyguardViewMediator.this) {
+ if (mKeyguardBound) {
+ if (newDisabledState != mInternallyDisabled) {
+ // it was updated,
+ setKeyguardEnabledInternal(!newDisabledState);
+ }
+ }
+ }
+ }
+ };
}
@Override
@@ -662,7 +767,7 @@ public class KeyguardViewMediator extends SystemUI {
Slog.w(TAG, "Failed to call onKeyguardExitResult(false)", e);
}
mExitSecureCallback = null;
- if (!mExternallyEnabled) {
+ if (!mInternallyDisabled && !mExternallyEnabled) {
hideLocked();
}
} else if (mShowing) {
@@ -762,6 +867,10 @@ public class KeyguardViewMediator extends SystemUI {
mDelayedShowingSequence++;
}
+ public boolean isKeyguardBound() {
+ return mKeyguardBound;
+ }
+
/**
* Let's us know when the device is waking up.
*/
@@ -793,7 +902,7 @@ public class KeyguardViewMediator extends SystemUI {
}
private void maybeSendUserPresentBroadcast() {
- if (mSystemReady && mLockPatternUtils.isLockScreenDisabled(
+ if (mSystemReady && isKeyguardDisabled(
KeyguardUpdateMonitor.getCurrentUser())) {
// Lock screen is disabled because the user has set the preference to "None".
// In this case, send out ACTION_USER_PRESENT here instead of in
@@ -802,6 +911,37 @@ public class KeyguardViewMediator extends SystemUI {
}
}
+ private boolean isKeyguardDisabled(int userId) {
+ if (!mExternallyEnabled) {
+ if (DEBUG) Log.d(TAG, "isKeyguardDisabled: keyguard is disabled externally");
+ return true;
+ }
+ if (mLockPatternUtils.isLockScreenDisabled(userId)) {
+ if (DEBUG) Log.d(TAG, "isKeyguardDisabled: keyguard is disabled by setting");
+ return true;
+ }
+ if (mInternallyDisabled) {
+ if (DEBUG) Log.d(TAG, "isKeyguardDisabled: keyguard is disabled internally");
+ return true;
+ }
+ if (isProfileDisablingKeyguard()) {
+ if (DEBUG) Log.d(TAG, "isKeyguardDisabled: keyguard is disabled by profile");
+ return true;
+ }
+ return false;
+ }
+
+ private boolean isCryptKeeperEnabled() {
+ if (!mCryptKeeperEnabled) {
+ // once it's disabled, it's disabled.
+ return false;
+ }
+ final String state = SystemProperties.get("vold.decrypt");
+ mCryptKeeperEnabled = !"".equals(state) && !DECRYPT_STATE.equals(state);
+ if (DEBUG) Log.w(TAG, "updated crypt keeper state to: " + mCryptKeeperEnabled);
+ return mCryptKeeperEnabled;
+ }
+
/**
* A dream started. We should lock after the usual screen-off lock timeout but only
* if there is a secure lock pattern.
@@ -827,6 +967,33 @@ public class KeyguardViewMediator extends SystemUI {
}
/**
+ * Set the internal keyguard enabled state. This allows SystemUI to disable the lockscreen,
+ * overriding any apps.
+ * @param enabled
+ */
+ public void setKeyguardEnabledInternal(boolean enabled) {
+ mInternallyDisabled = !enabled;
+ if (!mUpdateMonitor.isSimPinSecure()) {
+ // disable when sim is ready
+ return;
+ }
+ setKeyguardEnabled(enabled);
+ if (mInternallyDisabled) {
+ mNeedToReshowWhenReenabled = false;
+ }
+ }
+
+ public boolean getKeyguardEnabledInternal() {
+ return !mInternallyDisabled;
+ }
+
+ public boolean isProfileDisablingKeyguard() {
+ final Profile activeProfile = ProfileManager.getInstance(mContext).getActiveProfile();
+ return activeProfile != null
+ && activeProfile.getScreenLockMode().getValue() == Profile.LockMode.DISABLE;
+ }
+
+ /**
* Same semantics as {@link android.view.WindowManagerPolicy#enableKeyguard}; provide
* a way for external stuff to override normal keyguard behavior. For instance
* the phone app disables the keyguard when it receives incoming calls.
@@ -834,8 +1001,14 @@ public class KeyguardViewMediator extends SystemUI {
public void setKeyguardEnabled(boolean enabled) {
synchronized (this) {
if (DEBUG) Log.d(TAG, "setKeyguardEnabled(" + enabled + ")");
-
mExternallyEnabled = enabled;
+ if (mInternallyDisabled
+ && enabled
+ && !lockscreenEnforcedByDevicePolicy()) {
+ // if keyguard is forcefully disabled internally (by lock screen tile), don't allow
+ // it to be enabled externally, unless the device policy manager says so.
+ return;
+ }
if (!enabled && mShowing) {
if (mExitSecureCallback != null) {
@@ -848,7 +1021,7 @@ public class KeyguardViewMediator extends SystemUI {
// hiding keyguard that is showing, remember to reshow later
if (DEBUG) Log.d(TAG, "remembering to reshow, hiding keyguard, "
+ "disabling status bar expansion");
- mNeedToReshowWhenReenabled = true;
+ mNeedToReshowWhenReenabled = !isProfileDisablingKeyguard();
updateInputRestrictedLocked();
hideLocked();
} else if (enabled && mNeedToReshowWhenReenabled) {
@@ -1028,22 +1201,6 @@ public class KeyguardViewMediator extends SystemUI {
* Enable the keyguard if the settings are appropriate.
*/
private void doKeyguardLocked(Bundle options) {
- // if another app is disabling us, don't show
- if (!mExternallyEnabled) {
- if (DEBUG) Log.d(TAG, "doKeyguard: not showing because externally disabled");
-
- // note: we *should* set mNeedToReshowWhenReenabled=true here, but that makes
- // for an occasional ugly flicker in this situation:
- // 1) receive a call with the screen on (no keyguard) or make a call
- // 2) screen times out
- // 3) user hits key to turn screen back on
- // instead, we reenable the keyguard when we know the screen is off and the call
- // ends (see the broadcast receiver below)
- // TODO: clean this up when we have better support at the window manager level
- // for apps that wish to be on top of the keyguard
- return;
- }
-
// if the keyguard is already showing, don't bother
if (mStatusBarKeyguardViewManager.isShowing()) {
if (DEBUG) Log.d(TAG, "doKeyguard: not showing because it is already showing");
@@ -1051,6 +1208,14 @@ public class KeyguardViewMediator extends SystemUI {
return;
}
+ // Ugly hack to ensure keyguard is not shown on top of the CryptKeeper which prevents
+ // a user from being able to decrypt their device.
+ if (isCryptKeeperEnabled()) {
+ if (DEBUG) Log.d(TAG, "doKeyguard: not showing because CryptKeeper is enabled");
+ resetStateLocked();
+ return;
+ }
+
// if the setup wizard hasn't run yet, don't show
final boolean requireSim = !SystemProperties.getBoolean("keyguard.no_require_sim", false);
final boolean absent = SubscriptionManager.isValidSubscriptionId(
@@ -1066,9 +1231,30 @@ public class KeyguardViewMediator extends SystemUI {
return;
}
- if (mLockPatternUtils.isLockScreenDisabled(KeyguardUpdateMonitor.getCurrentUser())
+ // if another app is disabling us, don't show
+ if (!mExternallyEnabled && !lockedOrMissing) {
+ if (DEBUG) Log.d(TAG, "doKeyguard: not showing because externally disabled");
+
+ // note: we *should* set mNeedToReshowWhenReenabled=true here, but that makes
+ // for an occasional ugly flicker in this situation:
+ // 1) receive a call with the screen on (no keyguard) or make a call
+ // 2) screen times out
+ // 3) user hits key to turn screen back on
+ // instead, we reenable the keyguard when we know the screen is off and the call
+ // ends (see the broadcast receiver below)
+ // TODO: clean this up when we have better support at the window manager level
+ // for apps that wish to be on top of the keyguard
+ return;
+ }
+
+ if (isKeyguardDisabled(KeyguardUpdateMonitor.getCurrentUser())
&& !lockedOrMissing) {
if (DEBUG) Log.d(TAG, "doKeyguard: not showing because lockscreen is off");
+ // update state
+ setShowingLocked(false);
+ updateActivityLockScreenState();
+ adjustStatusBarLocked();
+ userActivity();
return;
}
@@ -1089,6 +1275,15 @@ public class KeyguardViewMediator extends SystemUI {
return !mUpdateMonitor.isDeviceProvisioned() && !isSecure();
}
+ public boolean lockscreenEnforcedByDevicePolicy() {
+ DevicePolicyManager dpm = (DevicePolicyManager)
+ mContext.getSystemService(Context.DEVICE_POLICY_SERVICE);
+ if (dpm != null) {
+ return dpm.requireSecureKeyguard();
+ }
+ return false;
+ }
+
/**
* Dismiss the keyguard through the security layers.
*/
@@ -1102,6 +1297,36 @@ public class KeyguardViewMediator extends SystemUI {
mHandler.sendEmptyMessage(DISMISS);
}
+ public void showKeyguard() {
+ // This is to prevent left edge from interfering
+ // with affordances.
+ if (mStatusBar.isAffordanceSwipeInProgress()
+ || mStatusBar.getBarState() == StatusBarState.KEYGUARD) {
+ return;
+ }
+
+ // Disable edge detector once we're back on lockscreen
+ try {
+ WindowManagerGlobal.getWindowManagerService()
+ .setLiveLockscreenEdgeDetector(false);
+ } catch (RemoteException e){
+ Log.e(TAG, e.getMessage());
+ }
+
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ // Hide status bar window to avoid flicker,
+ // slideNotificationPanelIn will make it visible later.
+ mStatusBar.getStatusBarWindow().setVisibility(View.INVISIBLE);
+ // Get the keyguard into the correct state by calling mStatusBar.showKeyguard()
+ mStatusBar.showKeyguard();
+ // Now have the notification panel slid back into view
+ mStatusBar.slideNotificationPanelIn();
+ }
+ });
+ }
+
/**
* Send message to keyguard telling it to reset its state.
* @see #handleReset
@@ -1203,6 +1428,20 @@ public class KeyguardViewMediator extends SystemUI {
doKeyguardLocked(null);
}
}
+ } else if (DISMISS_KEYGUARD_SECURELY_ACTION.equals(intent.getAction())) {
+ synchronized (KeyguardViewMediator.this) {
+ dismiss();
+ }
+ } else if (KEYGUARD_SERVICE_ACTION_STATE_CHANGE.equals(intent.getAction())) {
+ mKeyguardBound = intent.getBooleanExtra(KEYGUARD_SERVICE_EXTRA_ACTIVE, false);
+ if (mKeyguardBound) {
+ mSettingsObserver.observe();
+ } else {
+ mSettingsObserver.unobserve();
+ }
+ } else if (TelephonyManager.ACTION_PHONE_STATE_CHANGED.equals(intent.getAction())) {
+ mPhoneState = intent.getStringExtra(TelephonyManager.EXTRA_STATE);
+ if (DEBUG) Log.d(TAG, "phone state change, new state: " + mPhoneState);
}
}
};
@@ -1282,6 +1521,9 @@ public class KeyguardViewMediator extends SystemUI {
case ON_ACTIVITY_DRAWN:
handleOnActivityDrawn();
break;
+ case NOTIFY_KEYGUARD_PANEL_FOCUS_CHANGED:
+ notifyKeyguardPanelFocusChanged(msg.arg1 != 0);
+ break;
}
}
};
@@ -1368,6 +1610,10 @@ public class KeyguardViewMediator extends SystemUI {
private void playSound(int soundId) {
if (soundId == 0) return;
+ if (mInternallyDisabled) {
+ Log.d(TAG, "suppressing lock screen sounds because it is disabled");
+ return;
+ }
final ContentResolver cr = mContext.getContentResolver();
if (Settings.System.getInt(cr, Settings.System.LOCKSCREEN_SOUNDS_ENABLED, 1) == 1) {
@@ -1437,7 +1683,8 @@ public class KeyguardViewMediator extends SystemUI {
ActivityManagerNative.getDefault().keyguardGoingAway(
mStatusBarKeyguardViewManager.shouldDisableWindowAnimationsForUnlock()
|| mWakeAndUnlocking,
- mStatusBarKeyguardViewManager.isGoingToNotificationShade());
+ mStatusBarKeyguardViewManager.isGoingToNotificationShade(),
+ mStatusBarKeyguardViewManager.isKeyguardShowingMedia());
} catch (RemoteException e) {
Log.e(TAG, "Error while calling WindowManager", e);
}
@@ -1497,7 +1744,9 @@ public class KeyguardViewMediator extends SystemUI {
// only play "unlock" noises if not on a call (since the incall UI
// disables the keyguard)
if (TelephonyManager.EXTRA_STATE_IDLE.equals(mPhoneState)) {
- playSounds(false);
+ if (mShowing && mDeviceInteractive) {
+ playSounds(false);
+ }
}
setShowingLocked(false);
@@ -1550,7 +1799,7 @@ public class KeyguardViewMediator extends SystemUI {
private void handleReset() {
synchronized (KeyguardViewMediator.this) {
if (DEBUG) Log.d(TAG, "handleReset");
- mStatusBarKeyguardViewManager.reset();
+ mStatusBarKeyguardViewManager.reset(false);
}
}
@@ -1655,6 +1904,7 @@ public class KeyguardViewMediator extends SystemUI {
FingerprintUnlockController fingerprintUnlockController) {
mStatusBarKeyguardViewManager.registerStatusBar(phoneStatusBar, container,
statusBarWindowManager, scrimController, fingerprintUnlockController);
+ mStatusBar = phoneStatusBar;
return mStatusBarKeyguardViewManager;
}
@@ -1725,6 +1975,31 @@ public class KeyguardViewMediator extends SystemUI {
}
}
+ public void setKeyguardPanelFocused(boolean focused) {
+ if (DEBUG) Log.d(TAG, "setSlideOffset " + focused);
+ mHandler.removeMessages(NOTIFY_KEYGUARD_PANEL_FOCUS_CHANGED);
+ Message msg = mHandler.obtainMessage(NOTIFY_KEYGUARD_PANEL_FOCUS_CHANGED,
+ focused ? 1 : 0, 0);
+ mHandler.sendMessage(msg);
+ }
+
+ public void notifyKeyguardPanelFocusChanged(boolean focused) {
+ if (focused != mKeyguardPanelFocused) {
+ mKeyguardPanelFocused = focused;
+ int size = mKeyguardStateCallbacks.size();
+ for (int i = size - 1; i >= 0; i--) {
+ try {
+ mKeyguardStateCallbacks.get(i).onKeyguardPanelFocusChanged(focused);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Failed to call onShowingStateChanged", e);
+ if (e instanceof DeadObjectException) {
+ mKeyguardStateCallbacks.remove(i);
+ }
+ }
+ }
+ }
+ }
+
public void addStateMonitorCallback(IKeyguardStateCallback callback) {
synchronized (this) {
mKeyguardStateCallbacks.add(callback);
@@ -1732,6 +2007,7 @@ public class KeyguardViewMediator extends SystemUI {
callback.onSimSecureStateChanged(mUpdateMonitor.isSimPinSecure());
callback.onShowingStateChanged(mShowing);
callback.onInputRestrictedStateChanged(mInputRestricted);
+ callback.onKeyguardPanelFocusChanged(mKeyguardPanelFocused);
} catch (RemoteException e) {
Slog.w(TAG, "Failed to call onShowingStateChanged or onSimSecureStateChanged or onInputRestrictedStateChanged", e);
}
diff --git a/packages/SystemUI/src/com/android/systemui/power/PowerUI.java b/packages/SystemUI/src/com/android/systemui/power/PowerUI.java
index 9459740..e519e34 100644
--- a/packages/SystemUI/src/com/android/systemui/power/PowerUI.java
+++ b/packages/SystemUI/src/com/android/systemui/power/PowerUI.java
@@ -16,23 +16,30 @@
package com.android.systemui.power;
+import android.app.Notification;
+import android.app.NotificationManager;
import android.content.BroadcastReceiver;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.database.ContentObserver;
+import android.media.Ringtone;
+import android.media.RingtoneManager;
+import android.net.Uri;
import android.os.BatteryManager;
import android.os.Handler;
import android.os.PowerManager;
import android.os.SystemClock;
import android.os.UserHandle;
+import android.os.Vibrator;
import android.provider.Settings;
import android.util.Log;
import android.util.Slog;
import com.android.systemui.SystemUI;
import com.android.systemui.statusbar.phone.PhoneStatusBar;
+import cyanogenmod.providers.CMSettings;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -57,6 +64,9 @@ public class PowerUI extends SystemUI {
private long mScreenOffTime = -1;
+ // For filtering ACTION_POWER_DISCONNECTED on boot
+ boolean mIgnoreFirstPowerEvent = true;
+
public void start() {
mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
mScreenOffTime = mPowerManager.isScreenOn() ? -1 : SystemClock.elapsedRealtime();
@@ -140,6 +150,8 @@ public class PowerUI extends SystemUI {
filter.addAction(Intent.ACTION_USER_SWITCHED);
filter.addAction(PowerManager.ACTION_POWER_SAVE_MODE_CHANGING);
filter.addAction(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED);
+ filter.addAction(Intent.ACTION_POWER_CONNECTED);
+ filter.addAction(Intent.ACTION_POWER_DISCONNECTED);
mContext.registerReceiver(this, filter, null, mHandler);
updateSaverMode();
}
@@ -165,6 +177,10 @@ public class PowerUI extends SystemUI {
final boolean plugged = mPlugType != 0;
final boolean oldPlugged = oldPlugType != 0;
+ if (mIgnoreFirstPowerEvent && plugged) {
+ mIgnoreFirstPowerEvent = false;
+ }
+
int oldBucket = findBatteryLevelBucket(oldBatteryLevel);
int bucket = findBatteryLevelBucket(mBatteryLevel);
@@ -214,12 +230,44 @@ public class PowerUI extends SystemUI {
updateSaverMode();
} else if (PowerManager.ACTION_POWER_SAVE_MODE_CHANGING.equals(action)) {
setSaverMode(intent.getBooleanExtra(PowerManager.EXTRA_POWER_SAVE_MODE, false));
+ } else if (Intent.ACTION_POWER_CONNECTED.equals(action)
+ || Intent.ACTION_POWER_DISCONNECTED.equals(action)) {
+ final ContentResolver cr = mContext.getContentResolver();
+
+ if (mIgnoreFirstPowerEvent) {
+ mIgnoreFirstPowerEvent = false;
+ } else {
+ if (Settings.Global.getInt(cr,
+ Settings.Global.CHARGING_SOUNDS_ENABLED, 0) == 1) {
+ playPowerNotificationSound();
+ }
+ }
} else {
Slog.w(TAG, "unknown intent: " + intent);
}
}
};
+ void playPowerNotificationSound() {
+ final ContentResolver cr = mContext.getContentResolver();
+ final String soundPath =
+ CMSettings.Global.getString(cr, CMSettings.Global.POWER_NOTIFICATIONS_RINGTONE);
+
+ if (soundPath != null) {
+ Ringtone powerRingtone = RingtoneManager.getRingtone(mContext, Uri.parse(soundPath));
+ if (powerRingtone != null) {
+ powerRingtone.play();
+ }
+ }
+ if (CMSettings.Global.getInt(cr,
+ CMSettings.Global.POWER_NOTIFICATIONS_VIBRATE, 0) == 1) {
+ Vibrator vibrator = (Vibrator) mContext.getSystemService(Context.VIBRATOR_SERVICE);
+ if (vibrator != null) {
+ vibrator.vibrate(250);
+ }
+ }
+ }
+
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
pw.print("mLowBatteryAlertCloseLevel=");
pw.println(mLowBatteryAlertCloseLevel);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSBooleanSettingRow.java b/packages/SystemUI/src/com/android/systemui/qs/QSBooleanSettingRow.java
new file mode 100644
index 0000000..50845da
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSBooleanSettingRow.java
@@ -0,0 +1,178 @@
+/*
+ * Copyright (C) 2015 The CyanogenMod 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;
+
+import android.annotation.Nullable;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.provider.Settings;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.View;
+import android.widget.CompoundButton;
+import android.widget.LinearLayout;
+import android.widget.Switch;
+import android.widget.TextView;
+
+import com.android.systemui.R;
+import cyanogenmod.providers.CMSettings;
+
+public class QSBooleanSettingRow extends LinearLayout implements View.OnClickListener {
+
+ private static final String TAG = "QSSettingRow";
+
+ public static final int TABLE_SYSTEM = 0;
+ public static final int TABLE_GLOBAL = 1;
+ public static final int TABLE_SECURE = 2;
+
+ public static final int TABLE_CM_SYSTEM = 3;
+ public static final int TABLE_CM_GLOBAL = 4;
+ public static final int TABLE_CM_SECURE = 5;
+
+ int mWhichTable;
+ String mTitle;
+ String mKey;
+ private TextView mText;
+ private Switch mSwitch;
+ private int mDefaultValue;
+ private CompoundButton.OnCheckedChangeListener mOnCheckedChangeListener;
+
+ public QSBooleanSettingRow(Context context) {
+ this(context, null);
+ }
+
+ public QSBooleanSettingRow(Context context, @Nullable AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public QSBooleanSettingRow(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ public QSBooleanSettingRow(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ View.inflate(context, R.layout.qs_settings_row, this);
+
+ setOrientation(HORIZONTAL);
+ setClickable(true);
+ setOnClickListener(this);
+
+ mText = (TextView) findViewById(R.id.title);
+ mSwitch = (Switch) findViewById(R.id.switcher);
+
+ TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.QuickSettingsRow,
+ defStyleAttr, defStyleRes);
+
+ mWhichTable = a.getInteger(R.styleable.QuickSettingsRow_table, -1);
+
+ mTitle = a.getString(R.styleable.QuickSettingsRow_android_title);
+ mKey = a.getString(R.styleable.QuickSettingsRow_android_key);
+ mDefaultValue = a.getInt(R.styleable.QuickSettingsRow_defaultValue, 0);
+
+ if (mText != null) {
+ mText.setText(mTitle);
+ mText.setClickable(false);
+ mText.setFocusable(false);
+ }
+
+ if (mSwitch != null) {
+ mSwitch.setClickable(false);
+ mSwitch.setFocusable(false);
+ mSwitch.setChecked(getCurrent());
+ mSwitch.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
+ @Override
+ public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
+ if (false) Log.d(TAG, "onCheckedChanged() called with "
+ + "buttonView = [" + buttonView + "], isChecked = [" + isChecked
+ + "] and table: " + mWhichTable + ", and key: " + mKey);
+ applyChange(isChecked);
+ if (mOnCheckedChangeListener != null) {
+ mOnCheckedChangeListener.onCheckedChanged(buttonView, isChecked);
+ }
+ }
+ });
+ }
+
+ a.recycle();
+ }
+
+ public void setChecked(boolean checked) {
+ if (mSwitch.isChecked() == checked) {
+ return;
+ }
+ mSwitch.setChecked(checked);
+ }
+
+ private void applyChange(boolean value) {
+ ContentResolver cr = getContext().getContentResolver();
+ switch (mWhichTable) {
+ case TABLE_GLOBAL:
+ Settings.Global.putInt(cr, mKey, value ? 1 : 0);
+ break;
+ case TABLE_SECURE:
+ Settings.Secure.putInt(cr, mKey, value ? 1 : 0);
+ break;
+ case TABLE_SYSTEM:
+ Settings.System.putInt(cr, mKey, value ? 1 : 0);
+ break;
+ case TABLE_CM_GLOBAL:
+ CMSettings.Global.putInt(cr, mKey, value ? 1 : 0);
+ break;
+ case TABLE_CM_SECURE:
+ CMSettings.Secure.putInt(cr, mKey, value ? 1 : 0);
+ break;
+ case TABLE_CM_SYSTEM:
+ CMSettings.System.putInt(cr, mKey, value ? 1 : 0);
+ break;
+ }
+ }
+
+ private boolean getCurrent() {
+ ContentResolver cr = getContext().getContentResolver();
+ int ret = 0;
+ switch (mWhichTable) {
+ case TABLE_GLOBAL:
+ ret = Settings.Global.getInt(cr, mKey, mDefaultValue);
+ break;
+ case TABLE_SECURE:
+ ret = Settings.Secure.getInt(cr, mKey, mDefaultValue);
+ break;
+ case TABLE_SYSTEM:
+ ret = Settings.System.getInt(cr, mKey, mDefaultValue);
+ break;
+ case TABLE_CM_GLOBAL:
+ ret = CMSettings.Global.getInt(cr, mKey, mDefaultValue);
+ break;
+ case TABLE_CM_SECURE:
+ ret = CMSettings.Secure.getInt(cr, mKey, mDefaultValue);
+ break;
+ case TABLE_CM_SYSTEM:
+ ret = CMSettings.System.getInt(cr, mKey, mDefaultValue);
+ break;
+ }
+ return ret == 1;
+ }
+
+ @Override
+ public void onClick(View v) {
+ mSwitch.setChecked(!mSwitch.isChecked());
+ }
+
+ public void setOnCheckedChangeListener(CompoundButton.OnCheckedChangeListener l) {
+ mOnCheckedChangeListener = l;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSContainer.java b/packages/SystemUI/src/com/android/systemui/qs/QSContainer.java
index cfe8d07..74f4cbb 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSContainer.java
@@ -63,7 +63,7 @@ public class QSContainer extends FrameLayout {
*/
public int getDesiredHeight() {
if (mQSPanel.isClosingDetail()) {
- return mQSPanel.getGridHeight() + getPaddingTop() + getPaddingBottom();
+ return mQSPanel.getGridHeight() + getPaddingBottom();
} else {
return getMeasuredHeight();
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSDetailClipper.java b/packages/SystemUI/src/com/android/systemui/qs/QSDetailClipper.java
index a318efc..8b89a65 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSDetailClipper.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSDetailClipper.java
@@ -36,6 +36,10 @@ public class QSDetailClipper {
mBackground = (TransitionDrawable) detail.getBackground();
}
+ public boolean isAnimating() {
+ return mAnimator != null && mAnimator.isRunning();
+ }
+
public void animateCircularClip(int x, int y, boolean in, AnimatorListener listener) {
if (mAnimator != null) {
mAnimator.cancel();
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSDetailItemsGrid.java b/packages/SystemUI/src/com/android/systemui/qs/QSDetailItemsGrid.java
new file mode 100644
index 0000000..07e01e4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSDetailItemsGrid.java
@@ -0,0 +1,154 @@
+/**
+ * Copyright (c) 2015, The CyanogenMod 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;
+
+import android.content.Context;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewGroup;
+
+import android.widget.BaseAdapter;
+
+import cyanogenmod.app.CustomTile;
+
+import com.android.systemui.qs.tiles.UserDetailItemView;
+import com.android.systemui.R;
+
+/**
+ * Quick settings common detail grid view with circular items.
+ */
+public class QSDetailItemsGrid extends PseudoGridView {
+ private static final String TAG = "QSDetailItemsGrid";
+ private QSDetailItemsGridAdapter mDetailItemsGridAdapter;
+
+ public QSDetailItemsGrid(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public static QSDetailItemsGrid inflate(Context context, ViewGroup parent, boolean attach) {
+ return (QSDetailItemsGrid) LayoutInflater.from(context).inflate(
+ R.layout.qs_detail_items_grid, parent, attach);
+ }
+
+ public QSDetailItemsGridAdapter createAndSetAdapter(String externalPackage,
+ CustomTile.ExpandedItem[] items) {
+ mDetailItemsGridAdapter = new QSDetailItemsGridAdapter(externalPackage, mContext, items);
+ ViewGroupAdapterBridge.link(this, mDetailItemsGridAdapter);
+ return mDetailItemsGridAdapter;
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+ setOnTouchListener(new OnTouchListener() {
+ // Setting on Touch Listener for handling the touch inside ScrollView
+ @Override
+ public boolean onTouch(View v, MotionEvent event) {
+ // Disallow the touch request for parent scroll on touch of child view
+ v.getParent().requestDisallowInterceptTouchEvent(true);
+ return false;
+ }
+ });
+ }
+
+ public static class QSDetailItemsGridAdapter extends BaseAdapter implements OnClickListener {
+ private String mPkg;
+ private Context mContext;
+ private CustomTile.ExpandedItem[] mItems;
+ private OnPseudoGriditemClickListener mOnPseudoGridItemClickListener;
+
+ public interface OnPseudoGriditemClickListener {
+ void onPsuedoGridItemClick(View view, CustomTile.ExpandedItem item);
+ }
+
+ public QSDetailItemsGridAdapter(String packageName,
+ Context context, CustomTile.ExpandedItem[] items) {
+ mPkg = packageName;
+ mContext = context;
+ mItems = items;
+ }
+
+ public void setOnPseudoGridItemClickListener(OnPseudoGriditemClickListener
+ onPseudoGridItemClickListener) {
+ mOnPseudoGridItemClickListener = onPseudoGridItemClickListener;
+ }
+
+ @Override
+ public int getCount() {
+ return mItems.length;
+ }
+
+ @Override
+ public CustomTile.ExpandedItem getItem(int position) {
+ return mItems[position];
+ }
+
+ @Override
+ public long getItemId(int position) {
+ return position;
+ }
+
+ @Override
+ public View getView(int i, View convertView, ViewGroup parent) {
+ UserDetailItemView v = UserDetailItemView.convertOrInflate(
+ mContext, convertView, parent);
+ CustomTile.ExpandedItem item = getItem(i);
+ Drawable d = null;
+ if (item.itemDrawableResourceId != 0 && item.itemBitmapResource == null) {
+ try {
+ d = getPackageContext(mPkg, mContext).getResources()
+ .getDrawable(item.itemDrawableResourceId);
+ } catch (Throwable t) {
+ Log.w(TAG, "Error creating package context" + mPkg +
+ " id=" + item.itemDrawableResourceId, t);
+ }
+ } else {
+ d = new BitmapDrawable(mContext.getResources(), item.itemBitmapResource);
+ }
+ if (v != convertView) {
+ v.setOnClickListener(this);
+ }
+ String name = item.itemTitle;
+ v.setActivated(true);
+ v.bind(name, d);
+ v.setTag(item);
+ return v;
+ }
+
+ @Override
+ public void onClick(View view) {
+ CustomTile.ExpandedItem item = (CustomTile.ExpandedItem) view.getTag();
+ mOnPseudoGridItemClickListener.onPsuedoGridItemClick(view, item);
+ }
+ }
+
+ private static Context getPackageContext(String pkg, Context context) {
+ Context packageContext;
+ try {
+ packageContext = context.createPackageContext(pkg, 0);
+ } catch (Throwable t) {
+ Log.w(TAG, "Error creating package context" + pkg, t);
+ return null;
+ }
+ return packageContext;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSDetailItemsList.java b/packages/SystemUI/src/com/android/systemui/qs/QSDetailItemsList.java
new file mode 100644
index 0000000..3e0ab8b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSDetailItemsList.java
@@ -0,0 +1,213 @@
+/*
+ * 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;
+
+import android.content.Context;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.text.TextUtils;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ArrayAdapter;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.ListAdapter;
+import android.widget.ListView;
+
+import android.widget.TextView;
+import com.android.systemui.R;
+
+import cyanogenmod.app.CustomTile;
+
+import java.util.List;
+
+/**
+ * Quick settings common detail list view with line items.
+ */
+public class QSDetailItemsList extends FrameLayout {
+ private static final String TAG = "QSDetailItemsList";
+
+ private ListView mListView;
+ private View mEmpty;
+ private TextView mEmptyText;
+ private ImageView mEmptyIcon;
+
+ public QSDetailItemsList(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ mContext = context;
+ mTag = TAG;
+ }
+
+ public static QSDetailItemsList convertOrInflate(Context context,
+ View convertView, ViewGroup parent) {
+ if (convertView instanceof QSDetailItemsList) {
+ return (QSDetailItemsList) convertView;
+ }
+ LayoutInflater inflater = LayoutInflater.from(context);
+ return (QSDetailItemsList) inflater.inflate(R.layout.qs_detail_items_list, parent, false);
+ }
+
+ public void setAdapter(ListAdapter adapter) {
+ mListView.setAdapter(adapter);
+ }
+
+ public ListView getListView() {
+ return mListView;
+ }
+
+ public void setEmptyState(int icon, int text) {
+ mEmptyIcon.setImageResource(icon);
+ mEmptyText.setText(text);
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+ mListView = (ListView) findViewById(android.R.id.list);
+ mListView.setOnTouchListener(new OnTouchListener() {
+ // Setting on Touch Listener for handling the touch inside ScrollView
+ @Override
+ public boolean onTouch(View v, MotionEvent event) {
+ // Disallow the touch request for parent scroll on touch of child view
+ v.getParent().requestDisallowInterceptTouchEvent(true);
+ return false;
+ }
+ });
+ mEmpty = findViewById(android.R.id.empty);
+ mEmpty.setVisibility(GONE);
+ mEmptyText = (TextView) mEmpty.findViewById(android.R.id.title);
+ mEmptyIcon = (ImageView) mEmpty.findViewById(android.R.id.icon);
+ mListView.setEmptyView(mEmpty);
+ }
+
+ public static class QSCustomDetailListAdapter extends ArrayAdapter<CustomTile.ExpandedItem> {
+ private String mPackage;
+
+ public QSCustomDetailListAdapter(String externalPackage, Context context,
+ List<CustomTile.ExpandedItem> objects) {
+ super(context, R.layout.qs_detail_item, objects);
+ mPackage = externalPackage;
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ LayoutInflater inflater = LayoutInflater.from(getContext());
+ LinearLayout view = (LinearLayout) inflater.inflate(
+ R.layout.qs_detail_item, parent, false);
+
+ final CustomTile.ExpandedItem item = getItem(position);
+ Drawable d = null;
+ if (item.itemDrawableResourceId != 0 && item.itemBitmapResource == null) {
+ try {
+ d = getPackageContext(mPackage, getContext()).getResources()
+ .getDrawable(item.itemDrawableResourceId);
+ } catch (Throwable t) {
+ Log.w(TAG, "Error creating package context" + mPackage +
+ " id=" + item.itemDrawableResourceId, t);
+ }
+ } else {
+ d = new BitmapDrawable(getContext().getResources(), item.itemBitmapResource);
+ }
+ final ImageView iv = (ImageView) view.findViewById(android.R.id.icon);
+ iv.setImageDrawable(d);
+ iv.getOverlay().clear();
+ //TODO: hide icon for the time being until the API supports granular item manipulation
+ final ImageView iv2 = (ImageView) view.findViewById(android.R.id.icon2);
+ iv2.setVisibility(View.GONE);
+ final TextView title = (TextView) view.findViewById(android.R.id.title);
+ title.setText(item.itemTitle);
+ final TextView summary = (TextView) view.findViewById(android.R.id.summary);
+ final boolean twoLines = !TextUtils.isEmpty(item.itemSummary);
+ title.setMaxLines(twoLines ? 1 : 2);
+ summary.setVisibility(twoLines ? VISIBLE : GONE);
+ summary.setText(twoLines ? item.itemSummary : null);
+ view.setMinimumHeight(getContext().getResources().getDimensionPixelSize(
+ twoLines ? R.dimen.qs_detail_item_height_twoline
+ : R.dimen.qs_detail_item_height));
+ return view;
+ }
+ }
+
+ private static Context getPackageContext(String pkg, Context context) {
+ Context packageContext;
+ try {
+ packageContext = context.createPackageContext(pkg, 0);
+ } catch (Throwable t) {
+ Log.w(TAG, "Error creating package context" + pkg, t);
+ return null;
+ }
+ return packageContext;
+ }
+
+ public static class QSDetailListAdapter extends ArrayAdapter<QSDetailItems.Item> {
+ private QSDetailItems.Callback mCallback;
+
+ public QSDetailListAdapter(Context context, List<QSDetailItems.Item> objects) {
+ super(context, R.layout.qs_detail_item, objects);
+ }
+
+ public void setCallback(QSDetailItems.Callback cb) {
+ mCallback = cb;
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ LayoutInflater inflater = LayoutInflater.from(getContext());
+ LinearLayout view = (LinearLayout) inflater.inflate(
+ R.layout.qs_detail_item, parent, false);
+
+ view.setClickable(false); // let list view handle this
+
+ final QSDetailItems.Item item = getItem(position);
+
+ final ImageView iv = (ImageView) view.findViewById(android.R.id.icon);
+ iv.setImageResource(item.icon);
+ iv.getOverlay().clear();
+ if (item.overlay != null) {
+ item.overlay.setBounds(0, 0, item.overlay.getIntrinsicWidth(),
+ item.overlay.getIntrinsicHeight());
+ iv.getOverlay().add(item.overlay);
+ }
+ final TextView title = (TextView) view.findViewById(android.R.id.title);
+ title.setText(item.line1);
+ final TextView summary = (TextView) view.findViewById(android.R.id.summary);
+ final boolean twoLines = !TextUtils.isEmpty(item.line2);
+ title.setMaxLines(twoLines ? 1 : 2);
+ summary.setVisibility(twoLines ? VISIBLE : GONE);
+ summary.setText(twoLines ? item.line2 : null);
+ view.setMinimumHeight(getContext().getResources().getDimensionPixelSize(
+ twoLines ? R.dimen.qs_detail_item_height_twoline : R.dimen.qs_detail_item_height));
+
+ final ImageView disconnect = (ImageView) view.findViewById(android.R.id.icon2);
+ disconnect.setVisibility(item.canDisconnect ? VISIBLE : GONE);
+ disconnect.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ if (mCallback != null) {
+ mCallback.onDetailItemDisconnect(item);
+ }
+ }
+ });
+ return view;
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSDragPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSDragPanel.java
new file mode 100644
index 0000000..13f552c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSDragPanel.java
@@ -0,0 +1,2331 @@
+/*
+ * Copyright (C) 2015 The CyanogenMod 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;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.app.ActivityManager;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Point;
+import android.graphics.PointF;
+import android.graphics.PorterDuff;
+import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.Drawable;
+import android.os.Handler;
+import android.os.UserHandle;
+import android.support.v4.view.PagerAdapter;
+import android.support.v4.view.ViewPager;
+import android.util.ArrayMap;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.DragEvent;
+import android.view.LayoutInflater;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.BaseExpandableListAdapter;
+import android.widget.ExpandableListView;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+import com.android.internal.logging.MetricsLogger;
+import com.android.systemui.FontSizeUtils;
+import com.android.systemui.R;
+import com.android.systemui.cm.UserContentObserver;
+import com.android.systemui.qs.tiles.CustomQSTile;
+import com.android.systemui.qs.tiles.EditTile;
+import com.android.systemui.settings.BrightnessController;
+import com.android.systemui.settings.ToggleSlider;
+import com.android.systemui.statusbar.phone.NotificationPanelView;
+import com.android.systemui.statusbar.phone.QSTileHost;
+import com.android.systemui.statusbar.policy.BrightnessMirrorController;
+import com.android.systemui.tuner.QsTuner;
+import com.viewpagerindicator.CirclePageIndicator;
+import cyanogenmod.app.StatusBarPanelCustomTile;
+import cyanogenmod.providers.CMSettings;
+import org.cyanogenmod.internal.logging.CMMetricsLogger;
+import org.cyanogenmod.internal.util.QSUtils;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.ListIterator;
+import java.util.Map;
+
+import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
+
+public class QSDragPanel extends QSPanel implements View.OnDragListener, View.OnLongClickListener {
+
+ private static final String TAG = "QSDragPanel";
+
+ public static final boolean DEBUG_TILES = false;
+ public static final boolean DEBUG_DRAG = false;
+
+ private static final int MAX_ROW_COUNT = 3;
+
+ // how long to wait before resetting the page
+ private static final int PAGE_RESET_DELAY = 10000;
+
+ protected final ArrayList<QSPage> mPages = new ArrayList<>();
+
+ private NotificationPanelView mPanelView;
+ protected QSViewPager mViewPager;
+ protected PagerAdapter mPagerAdapter;
+ QSPanelTopView mQsPanelTop;
+ CirclePageIndicator mPageIndicator;
+ private int mPageIndicatorHeight;
+
+ private TextView mDetailRemoveButton;
+ private DragTileRecord mDraggingRecord, mLastDragRecord;
+ private ViewGroup mDetailButtons;
+ private boolean mEditing;
+ private boolean mDragging;
+ private float mLastTouchLocationX, mLastTouchLocationY;
+ private int mLocationHits;
+ private int mLastLeftShift = -1;
+ private int mLastRightShift = -1;
+ private boolean mRestored;
+ private boolean mRestoring;
+ // whether the current view we are dragging in has shifted tiles
+ private boolean mMovedByLocation = false;
+
+ protected boolean mFirstRowLarge = true;
+ private SettingsObserver mSettingsObserver;
+
+ List<TileRecord> mCurrentlyAnimating
+ = Collections.synchronizedList(new ArrayList<TileRecord>());
+
+ private Runnable mResetPage = new Runnable() {
+ @Override
+ public void run() {
+ if (!mExpanded) {
+ // only reset when the user isn't interacting at all
+ mViewPager.setCurrentItem(0);
+ mPagerAdapter.notifyDataSetChanged();
+ }
+ }
+ };
+
+ public QSDragPanel(Context context) {
+ this(context, null);
+ }
+
+ public QSDragPanel(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ @Override
+ protected void setupViews() {
+ updateResources();
+
+ mDetail = LayoutInflater.from(mContext).inflate(R.layout.qs_detail, this, false);
+ mDetailButtons = (ViewGroup) mDetail.findViewById(R.id.buttons);
+ mDetailContent = (ViewGroup) mDetail.findViewById(android.R.id.content);
+ mDetailRemoveButton = (TextView) mDetail.findViewById(android.R.id.button3);
+ mDetailSettingsButton = (TextView) mDetail.findViewById(android.R.id.button2);
+ mDetailDoneButton = (TextView) mDetail.findViewById(android.R.id.button1);
+ updateDetailText();
+ mDetail.setVisibility(GONE);
+ mDetail.setClickable(true);
+
+ mQsPanelTop = (QSPanelTopView) LayoutInflater.from(mContext).inflate(R.layout.qs_tile_top,
+ this, false);
+
+ mBrightnessView = mQsPanelTop.getBrightnessView();
+ mFooter = new QSFooter(this, mContext);
+
+ // add target click listener
+ mQsPanelTop.getAddTarget().setOnClickListener(
+ new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ TilesListAdapter adapter = new TilesListAdapter(mContext, QSDragPanel.this);
+ showDetailAdapter(true, adapter, v.getLocationOnScreen());
+ mDetail.bringToFront();
+ }
+ });
+ mViewPager = new QSViewPager(getContext());
+ mViewPager.setDragPanel(this);
+
+ mPageIndicator = new CirclePageIndicator(getContext());
+ addView(mDetail);
+ addView(mQsPanelTop);
+ addView(mViewPager);
+ addView(mPageIndicator);
+ addView(mFooter.getView());
+
+ mClipper = new QSDetailClipper(mDetail);
+
+ mBrightnessController = new BrightnessController(getContext(),
+ (ImageView) mQsPanelTop.getBrightnessView().findViewById(R.id.brightness_icon),
+ (ToggleSlider) mQsPanelTop.getBrightnessView().findViewById(R.id.brightness_slider));
+
+ mDetailDoneButton.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ announceForAccessibility(
+ mContext.getString(R.string.accessibility_desc_quick_settings));
+ closeDetail();
+ }
+ });
+
+ mPagerAdapter = new PagerAdapter() {
+ @Override
+ public Object instantiateItem(ViewGroup container, int position) {
+ if (DEBUG_TILES) {
+ Log.d(TAG, "instantiateItem() called with "
+ + "container = [" + container + "], position = [" + position + "]");
+ }
+
+ if (mEditing && position == 0) {
+ QSSettings qss = (QSSettings)
+ View.inflate(container.getContext(), R.layout.qs_settings, null);
+ qss.setHost(mHost);
+ container.addView(qss, 0);
+ return qss;
+ } else {
+ final int adjustedPosition = mEditing ? position - 1 : position;
+ QSPage page = mPages.get(adjustedPosition);
+ if (!page.isAttachedToWindow()) {
+ container.addView(page);
+ }
+ return page;
+ }
+ }
+
+ @Override
+ public void destroyItem(ViewGroup container, int position, Object object) {
+ if (DEBUG_TILES) {
+ Log.d(TAG, "destroyItem() called with " + "container = ["
+ + container + "], position = [" + position + "], object = ["
+ + object + "]");
+ }
+ if (object instanceof View) {
+ container.removeView((View) object);
+ }
+ }
+
+ @Override
+ public int getItemPosition(Object object) {
+ if (object instanceof QSPage) {
+ if (mEditing != ((QSPage) object).getAdapterEditingState()) {
+ // position of item changes when we set change the editing mode,
+ // sync it and send the new position
+ ((QSPage) object).setAdapterEditingState(mEditing);
+
+ // calculate new position
+ int indexOf = ((QSPage) object).getPageIndex();
+ if (mEditing) return indexOf + 1;
+ else return indexOf;
+
+ } else if (!mPages.contains(object) && !mDragging) {
+ // only return none if we aren't dragging (object may be removed from
+ // the records array temporarily and we might think we have less pages,
+ // we don't want to prematurely remove this page
+ return POSITION_NONE;
+ } else {
+
+ return POSITION_UNCHANGED;
+ }
+
+ } else if (object instanceof QSSettings) {
+ if (((QSSettings) object).getAdapterEditingState() != mEditing) {
+ ((QSSettings) object).setAdapterEditingState(mEditing);
+ if (mEditing) return 0 /* locked at position 0 */;
+ else return POSITION_NONE;
+ } else {
+ return POSITION_UNCHANGED;
+ }
+ }
+ return super.getItemPosition(object);
+ }
+
+ @Override
+ public int getCount() {
+ final int qsPages = Math.max(getCurrentMaxPageCount(), 1);
+
+ if (mPages != null && qsPages > mPages.size()) {
+ for(int i = mPages.size(); i < qsPages; i++) {
+ mPages.add(i, new QSPage(mViewPager.getContext(), QSDragPanel.this, i));
+ }
+ }
+
+ if (mEditing) return qsPages + 1;
+ return qsPages;
+ }
+
+ @Override
+ public boolean isViewFromObject(View view, Object object) {
+ return view == object;
+ }
+ };
+ mViewPager.setAdapter(mPagerAdapter);
+
+ mPageIndicator.setViewPager(mViewPager);
+ mPageIndicator.setOnPageChangeListener(new ViewPager.OnPageChangeListener() {
+
+ @Override
+ public void onPageScrolled(int position, float positionOffset,
+ int positionOffsetPixels) {
+ if (DEBUG_DRAG) {
+ Log.i(TAG, "onPageScrolled() called with " + "position = ["
+ + position + "], positionOffset = [" + positionOffset
+ + "], positionOffsetPixels = [" + positionOffsetPixels + "]");
+ }
+
+ if (mEditing) {
+ float targetTranslationX = 0;
+
+ // targetTranslationX = where it's supposed to be - diff
+ int homeLocation = mViewPager.getMeasuredWidth();
+
+ // how far away from homeLocation is the scroll?
+ if (positionOffsetPixels < homeLocation
+ && position == 0) {
+ targetTranslationX = homeLocation - positionOffsetPixels;
+ }
+ mQsPanelTop.setTranslationX(targetTranslationX);
+ }
+ }
+
+ @Override
+ public void onPageSelected(int position) {
+ if (mDragging && position != mDraggingRecord.page
+ && !mViewPager.isFakeDragging() && !mRestoring) {
+ if (DEBUG_DRAG) {
+ Log.w(TAG, "moving drag record to page: " + position);
+ }
+
+ // remove it from the previous page and add it here
+ final QSPage sourceP = getPage(mDraggingRecord.page);
+ final QSPage targetP = getPage(position);
+
+ sourceP.removeView(mDraggingRecord.tileView);
+ mDraggingRecord.page = position;
+ targetP.addView(mDraggingRecord.tileView);
+
+ // set coords off screen until we're ready to place it
+ mDraggingRecord.tileView.setX(-mDraggingRecord.tileView.getMeasuredWidth());
+ mDraggingRecord.tileView.setY(-mDraggingRecord.tileView.getMeasuredHeight());
+ }
+ }
+
+ @Override
+ public void onPageScrollStateChanged(int state) {
+ }
+ });
+ mViewPager.setOverScrollMode(OVER_SCROLL_NEVER);
+
+ setClipChildren(false);
+
+ mSettingsObserver = new SettingsObserver(new Handler());
+
+ mViewPager.setOnDragListener(QSDragPanel.this);
+ mQsPanelTop.setOnDragListener(QSDragPanel.this);
+ mPageIndicator.setOnDragListener(QSDragPanel.this);
+ setOnDragListener(QSDragPanel.this);
+
+ mViewPager.setOverScrollMode(View.OVER_SCROLL_NEVER);
+ }
+
+ @Override
+ public boolean hasOverlappingRendering() {
+ return mClipper.isAnimating() || mEditing || !mCurrentlyAnimating.isEmpty();
+ }
+
+ @Override
+ public void setBrightnessMirror(BrightnessMirrorController c) {
+ super.onFinishInflate();
+ ToggleSlider brightnessSlider =
+ (ToggleSlider) mQsPanelTop.findViewById(R.id.brightness_slider);
+ ToggleSlider mirror = (ToggleSlider) c.getMirror().findViewById(R.id.brightness_slider);
+ brightnessSlider.setMirror(mirror);
+ brightnessSlider.setMirrorController(c);
+ }
+
+ protected void drawTile(TileRecord r, QSTile.State state) {
+ if (mEditing) {
+ if ((r.tile instanceof CustomQSTile)
+ && (((CustomQSTile) r.tile).isUserRemoved()
+ || ((CustomQSTile) r.tile).getTile() == null)) {
+ // don't modify visibility state if removed, or not yet published
+ } else {
+ state.visible = true;
+ state.enabled = true;
+ }
+ }
+ final int visibility = state.visible ? VISIBLE : GONE;
+ setTileVisibility(r.tileView, visibility);
+ setTileEnabled(r.tileView, state.enabled);
+ r.tileView.onStateChanged(state);
+ }
+
+ @Override
+ public void setListening(boolean listening) {
+ if (mListening == listening) return;
+ mListening = listening;
+ for (TileRecord r : mRecords) {
+ r.tile.setListening(mListening);
+ }
+ mFooter.setListening(mListening);
+ mQsPanelTop.setListening(mListening);
+ if (mListening) {
+ refreshAllTiles();
+ }
+ if (mListening) {
+ mSettingsObserver.observe();
+ } else {
+ mSettingsObserver.unobserve();
+ }
+
+ if (isLaidOut() && listening && showBrightnessSlider()) {
+ mBrightnessController.registerCallbacks();
+ } else {
+ mBrightnessController.unregisterCallbacks();
+ }
+ }
+
+ private void persistRecords() {
+ // persist the new config.
+ List<String> newTiles = new ArrayList<>();
+ for (TileRecord record : mRecords) {
+ newTiles.add(mHost.getSpec(record.tile));
+ }
+ mHost.setTiles(newTiles);
+ }
+
+ public void setEditing(boolean editing) {
+ if (mEditing == editing) return;
+ final boolean isOnSettings = isOnSettingsPage();
+
+ mQsPanelTop.setEditing(editing, isOnSettings);
+ if (!editing) {
+ persistRecords();
+
+ refreshAllTiles();
+
+ mQsPanelTop.setTranslationX(0);
+ if (isOnSettings) {
+ mViewPager.setCurrentItem(1, true);
+ }
+ }
+ mEditing = editing;
+ mPagerAdapter.notifyDataSetChanged();
+
+ mPageIndicator.setEditing(editing);
+ mViewPager.setOffscreenPageLimit(mEditing ? getCurrentMaxPageCount() + 1 : 1);
+ mPagerAdapter.notifyDataSetChanged();
+
+ // clear the record state
+ for (TileRecord record : mRecords) {
+ setupRecord(record);
+ drawTile(record, record.tile.getState());
+ }
+
+ requestLayout();
+ }
+
+ protected void onStartDrag() {
+ mQsPanelTop.onStartDrag();
+ }
+
+ protected void onStopDrag() {
+ mDraggingRecord.tileView.setAlpha(1f);
+
+ mLastDragRecord = mDraggingRecord;
+ mDraggingRecord = null;
+ mDragging = false;
+ mRestored = false;
+
+ mLastLeftShift = -1;
+ mLastRightShift = -1;
+
+ mQsPanelTop.onStopDrag();
+ }
+
+ protected View getDropTarget() {
+ return mQsPanelTop.getDropTarget();
+ }
+
+ public View getBrightnessView() {
+ return mQsPanelTop.getBrightnessView();
+ }
+
+ public boolean isEditing() {
+ return mEditing;
+ }
+
+ protected int getPagesForCount(int tileCount) {
+ if (tileCount == 0) {
+ return 1;
+ }
+ tileCount = Math.max(0, tileCount - getTilesPerPage(true));
+ // first page + rest of tiles
+ return 1 + (int) Math.ceil(tileCount / (double) getTilesPerPage(false));
+ }
+
+ protected int getCurrentMaxPageCount() {
+ int initialSize = mRecords.size();
+ return getPagesForCount(initialSize);
+ }
+
+ @Override
+ protected void updateDetailText() {
+ super.updateDetailText();
+ mDetailRemoveButton.setText(R.string.quick_settings_remove);
+ }
+
+ public void setTiles(final Collection<QSTile<?>> tilesCollection) {
+ // we try to be as efficient as possible here because this can happen while the user
+ // is in edit mode, or maybe even while tiles are animating
+ // step 1: stop all animations
+ // step 2: remove tiles no longer to be used, cache ones that are still valid
+ // step 3: remove empty viewpager pages
+ // step 4: generate new tiles, re-add cached ones
+
+ if (DEBUG_TILES) {
+ Log.i(TAG, "setTiles() called with tiles = [" + tilesCollection + "]");
+ }
+ if (mLastDragRecord != null && mRecords.indexOf(mLastDragRecord) == -1) {
+ // the last removed record might be stored in mLastDragRecord if we just shifted
+ // re-add it to the list so we'll clean it up below
+ mRecords.add(mLastDragRecord);
+ mLastDragRecord = null;
+ }
+
+ // step kinda-1
+ if (mDraggingRecord != null) {
+ // dragging record might be animating back, force it to finished position
+ mDraggingRecord.tileView.animate().cancel();
+ }
+
+ int currentViewPagerPage = mViewPager.getCurrentItem();
+ int removedPages = 0;
+
+ Map<QSTile<?>, DragTileRecord> cachedRecords = new ArrayMap<>();
+ ListIterator<TileRecord> iterator = mRecords.listIterator(mRecords.size());
+
+ int recordsRemoved = 0;
+ // cleanup current records
+ while (iterator.hasPrevious()) { // mRecords
+ DragTileRecord dr = (DragTileRecord) iterator.previous();
+
+ // step 1
+ dr.tileView.animate().cancel();
+
+ // step 2
+ if (tilesCollection.contains(dr.tile)) {
+ if (DEBUG_TILES) {
+ Log.i(TAG, "caching tile: " + dr.tile);
+ }
+ cachedRecords.put(dr.tile, dr);
+ } else {
+ if (dr.page >= 0) {
+ if (DEBUG_TILES) {
+ Log.w(TAG, "removed dr.tileView: " + dr.tileView + " from page: "
+ + dr.page + " (dest page: " + dr.destinationPage + ")");
+ }
+
+ removeTileView(dr.tileView);
+ }
+ if (DEBUG_TILES) {
+ Log.i(TAG, "removing tile: " + dr.tile);
+ }
+
+ // remove record
+ iterator.remove();
+ recordsRemoved++;
+
+ dr.page = -1;
+ dr.destinationPage = -1;
+ }
+ }
+
+ // at this point cachedRecords should have all retained tiles, no new or old tiles
+ int delta = tilesCollection.size() - cachedRecords.size() - recordsRemoved;
+ if (DEBUG_TILES) {
+ Log.i(TAG, "record map delta: " + delta);
+ }
+
+ // step 3
+ final Iterator<QSPage> pageIterator = mPages.iterator();
+ while (pageIterator.hasNext()) {
+ final QSPage page = pageIterator.next();
+ final int viewpagerIndex = page.getPageIndex() + (mEditing ? 1 : 0);
+ final int childCount = page.getChildCount();
+
+ if (DEBUG_TILES) {
+ Log.d(TAG, "page " + viewpagerIndex + " has " + childCount);
+ }
+ if (page.getPageIndex() >= getCurrentMaxPageCount() - 1) {
+ if (DEBUG_TILES) {
+ Log.d(TAG, "page : " + page + " has " + childCount + " children");
+ }
+ if (childCount == 0) {
+ removedPages++;
+
+ page.removeAllViews();
+ mPagerAdapter.startUpdate(mViewPager);
+ mPagerAdapter.destroyItem(mViewPager, viewpagerIndex, page);
+ mPagerAdapter.finishUpdate(mViewPager);
+ mPagerAdapter.notifyDataSetChanged();
+ }
+ }
+ }
+
+ if (removedPages > 0) {
+ // even though we explicitly destroy old pages, without this call,
+ // the viewpager doesn't seem to want to pick up the fact that we have less pages
+ // and allows "empty" scrolls to the right where there is no page.
+ if (DEBUG_TILES) {
+ Log.d(TAG, "re-setting adapter, page: " + currentViewPagerPage);
+ }
+ mViewPager.setAdapter(mPagerAdapter);
+ mViewPager.setCurrentItem(Math.min(currentViewPagerPage, mPagerAdapter.getCount()),
+ false);
+ mPagerAdapter.notifyDataSetChanged();
+ }
+
+ // step 4
+ mRecords.ensureCapacity(tilesCollection.size());
+ int runningCount = 0;
+
+ final Iterator<QSTile<?>> newTileIterator = tilesCollection.iterator();
+ while (newTileIterator.hasNext()) {
+ QSTile<?> tile = newTileIterator.next();
+ if (tile instanceof CustomQSTile) {
+ if (((CustomQSTile) tile).isUserRemoved()
+ || ((CustomQSTile) tile).getTile() == null) {
+ // tile not published yet
+ continue;
+ }
+ }
+ final int tileDestPage = getPagesForCount(runningCount + 1) - 1;
+
+ if (DEBUG_TILES) {
+ Log.d(TAG, "tile at : " + runningCount + ": " + tile
+ + " to dest page: " + tileDestPage);
+ }
+ DragTileRecord record;
+ if (!cachedRecords.containsKey(tile)) {
+ if (DEBUG_TILES) {
+ Log.d(TAG, "tile at: " + runningCount + " not cached, adding it to records");
+ }
+ record = makeRecord(tile);
+ record.destinationPage = tileDestPage;
+ mRecords.add(runningCount, record);
+ mPagerAdapter.notifyDataSetChanged();
+ } else {
+ record = cachedRecords.get(tile);
+ if (DEBUG_TILES) {
+ Log.d(TAG, "tile at : " + runningCount + ": cached, restoring: " + record);
+ }
+
+ mPages.get(record.page).removeView(record.tileView);
+
+ record.page = -1;
+ record.destinationPage = tileDestPage;
+
+ mRecords.remove(record);
+ mRecords.add(runningCount, record);
+ mPagerAdapter.notifyDataSetChanged();
+ }
+ if (record.page == -1) {
+ // add the view
+ mPages.get(record.destinationPage).addView(record.tileView);
+ record.page = record.destinationPage;
+ if (DEBUG_TILES) {
+ Log.d(TAG, "added view " + record);
+ }
+ }
+ runningCount++;
+ }
+
+ if (isShowingDetail()) {
+ mDetail.bringToFront();
+ }
+ mPagerAdapter.notifyDataSetChanged();
+
+ refreshAllTiles();
+ requestLayout();
+ }
+
+ private DragTileRecord makeRecord(final QSTile<?> tile) {
+ if (DEBUG_TILES) {
+ Log.d(TAG, "+++ makeRecord() called with " + "tile = [" + tile + "]");
+ }
+ final DragTileRecord r = new DragTileRecord();
+
+
+ r.tile = tile;
+ r.page = -1;
+ r.destinationPage = -1;
+ r.tileView = tile.createTileView(mContext);
+ final QSTile.Callback callback = new QSTile.Callback() {
+ @Override
+ public void onStateChanged(QSTile.State state) {
+ if (!r.openingDetail) {
+ drawTile(r, state);
+ }
+ }
+
+ @Override
+ public void onShowDetail(boolean show) {
+ showDetail(show, r);
+ }
+
+ @Override
+ public void onToggleStateChanged(boolean state) {
+ if (mDetailRecord == r) {
+ fireToggleStateChanged(state);
+ }
+ }
+
+ @Override
+ public void onScanStateChanged(boolean state) {
+ r.scanState = state;
+ if (mDetailRecord == r) {
+ fireScanStateChanged(r.scanState);
+ }
+ }
+
+ @Override
+ public void onAnnouncementRequested(CharSequence announcement) {
+ announceForAccessibility(announcement);
+ }
+ };
+ r.tile.setCallback(callback);
+ final OnClickListener click = new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ if (!mEditing || r.tile instanceof EditTile) {
+ r.tile.click();
+ }
+ }
+ };
+ final OnClickListener clickSecondary = new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ if (!mEditing) {
+ r.tile.secondaryClick();
+ }
+ }
+ };
+ final OnLongClickListener longClick = new OnLongClickListener() {
+ @Override
+ public boolean onLongClick(View v) {
+ if (!mEditing) {
+ r.tile.longClick();
+ } else {
+ QSDragPanel.this.onLongClick(r.tileView);
+ }
+ return true;
+ }
+ };
+ r.tileView.init(click, clickSecondary, longClick);
+ r.tile.setListening(mListening);
+ r.tile.refreshState();
+ r.tileView.setVisibility(mEditing ? View.VISIBLE : View.GONE);
+ callback.onStateChanged(r.tile.getState());
+
+ if (DEBUG_TILES) {
+ Log.d(TAG, "--- makeRecord() called with " + "tile = [" + tile + "]");
+ }
+ return r;
+ }
+
+ private void removeTileView(QSTileView v) {
+ for (QSPage page : mPages) {
+ page.removeView(v);
+ page.removeTransientView(v);
+ }
+
+ }
+
+ private void removeDraggingRecord() {
+ // what spec is this tile?
+ String spec = mHost.getSpec(mDraggingRecord.tile);
+ if (DEBUG_TILES) {
+ Log.w(TAG, "removing tile: " + mDraggingRecord + " with spec: " + spec);
+ }
+ onStopDrag();
+ mHost.remove(spec);
+ }
+
+ public int getTilesPerPage(boolean firstPage) {
+ if ((!mFirstRowLarge && firstPage) || !firstPage) {
+ return QSTileHost.TILES_PER_PAGE + 1;
+ }
+ return QSTileHost.TILES_PER_PAGE;
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ final int width = MeasureSpec.getSize(widthMeasureSpec);
+
+ mQsPanelTop.measure(exactly(width), MeasureSpec.UNSPECIFIED);
+ mViewPager.measure(exactly(width), MeasureSpec.UNSPECIFIED);
+ mPageIndicator.measure(exactly(width), atMost(mPageIndicatorHeight));
+ mFooter.getView().measure(exactly(width), MeasureSpec.UNSPECIFIED);
+
+ int h = getRowTop(getCurrentMaxRow() + 1) + mPanelPaddingBottom;
+
+ if (mFooter.hasFooter()) {
+ h += mFooter.getView().getMeasuredHeight();
+ }
+ mGridHeight = h;
+
+ mDetail.measure(exactly(width), MeasureSpec.UNSPECIFIED);
+
+ if (mDetail.getMeasuredHeight() < h) {
+ mDetail.measure(exactly(width), exactly(h));
+ }
+ if (isShowingDetail() && !isClosingDetail() && mExpanded) {
+ h = mDetail.getMeasuredHeight();
+ }
+
+ setMeasuredDimension(width, h);
+ for (TileRecord record : mRecords) {
+ setupRecord(record);
+ }
+ }
+
+ private void setupRecord(TileRecord record) {
+ record.tileView.setEditing(mEditing);
+ record.tileView.setOnDragListener(mEditing ? this : null);
+ }
+
+ public static int exactly(int size) {
+ return MeasureSpec.makeMeasureSpec(size, MeasureSpec.EXACTLY);
+ }
+
+ public static int atMost(int size) {
+ return MeasureSpec.makeMeasureSpec(size, MeasureSpec.AT_MOST);
+ }
+
+ @Override
+ protected void handleShowDetailTile(TileRecord r, boolean show) {
+ if (r instanceof DragTileRecord) {
+ if ((mDetailRecord != null) == show && mDetailRecord == r) return;
+
+ if (show) {
+ r.detailAdapter = r.tile.getDetailAdapter();
+ if (r.detailAdapter == null) return;
+ }
+ r.tile.setDetailListening(show);
+ int x = (int) ((DragTileRecord) r).destination.x + r.tileView.getWidth() / 2;
+ int y = mViewPager.getTop()
+ + (int) ((DragTileRecord) r).destination.y + r.tileView.getHeight() / 2;
+ handleShowDetailImpl(r, show, x, y);
+ } else {
+ super.handleShowDetailTile(r, show);
+ }
+ mPageIndicator.setVisibility(!show ? View.VISIBLE : View.INVISIBLE);
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int l, int t, int r, int b) {
+ final int w = getWidth();
+
+ mQsPanelTop.layout(0, 0, w, mQsPanelTop.getMeasuredHeight());
+
+ int viewPagerBottom = mQsPanelTop.getMeasuredHeight() + mViewPager.getMeasuredHeight();
+ // view pager laid out from top of brightness view to bottom to page through settings
+ mViewPager.layout(0, 0, w, viewPagerBottom);
+
+ mDetail.layout(0, 0, w, mDetail.getMeasuredHeight());
+
+ if (mFooter.hasFooter()) {
+ View footer = mFooter.getView();
+ footer.layout(0, getMeasuredHeight() - footer.getMeasuredHeight(),
+ footer.getMeasuredWidth(), getMeasuredHeight());
+ }
+
+ if (!isShowingDetail() && !isClosingDetail()) {
+ mQsPanelTop.bringToFront();
+
+ }
+ // layout page indicator inside viewpager inset
+ mPageIndicator.layout(0, b - mPageIndicatorHeight, w, b);
+ }
+
+ protected int getRowTop(int row) {
+ int baseHeight = mQsPanelTop.getMeasuredHeight();
+ if (row <= 0) return baseHeight;
+ return baseHeight + mLargeCellHeight - mDualTileUnderlap + (row - 1) * mCellHeight;
+ }
+
+ public int getColumnCount() {
+ return mColumns;
+ }
+
+ public int getColumnCount(int page, int row, boolean smart) {
+ int cols = 0;
+ for (Record record : mRecords) {
+ if (record instanceof DragTileRecord) {
+ DragTileRecord dr = (DragTileRecord) record;
+ if (dr.tileView.getVisibility() == GONE) continue;
+ if (dr.destinationPage != page) continue;
+ if (dr.row == row) cols++;
+ }
+ }
+
+ if (smart && isEditing() && (isDragging() || mRestoring) && !isDragRecordAttached()) {
+ // if shifting tiles back, and one moved from previous page
+
+ // if it's the very last row on the last page, we should add an extra column to account
+ // for where teh dragging lastRecord would go
+ DragTileRecord lastRecord = (DragTileRecord) mRecords.get(mRecords.size() - 1);
+ if (lastRecord.destinationPage == page && lastRecord.row == row
+ && cols < getColumnCount()) {
+ cols++;
+ if (DEBUG_DRAG) {
+ boolean draggingRecordBefore = isBefore(mDraggingRecord, lastRecord);
+ Log.w(TAG, "adding another col, cols: " + cols + ", last: " + lastRecord
+ + ", drag: " + mDraggingRecord
+ + ", and dragging record before last: " + draggingRecordBefore);
+ }
+ }
+ }
+ return cols;
+ }
+
+ public int getColumnCount(int page, int row) {
+ return getColumnCount(page, row, true);
+ }
+
+ public int getCurrentMaxRow() {
+ int max = 0;
+ for (TileRecord record : mRecords) {
+ if (record.row > max) {
+ max = record.row;
+ }
+ }
+ return max;
+ }
+
+ public int getLeft(int page, int row, int col) {
+ final boolean firstRowLarge = mFirstRowLarge && page == 0 && row == 0;
+ int cols = firstRowLarge ? 2 : mColumns;
+ return getLeft(row, col, cols, firstRowLarge);
+ }
+
+ public int getLeft(int page, int row, int col, int cols) {
+ final boolean firstRowLarge = mFirstRowLarge && page == 0 && row == 0;
+ return getLeft(row, col, cols, firstRowLarge);
+ }
+
+ public int getLeft(int row, int col, int cols, boolean firstRowLarge) {
+ final int cw = row == 0 && firstRowLarge ? mLargeCellWidth : mCellWidth;
+ final int extra = (getWidth() - cw * cols) / (cols + 1);
+ int left = col * cw + (col + 1) * extra;
+ return left;
+ }
+
+ public QSPage getCurrentPage() {
+ return mPages.get(mViewPager.getCurrentItem());
+ }
+
+ public QSPage getPage(int pos) {
+ if (pos >= mPages.size()) {
+ return null;
+ }
+ return mPages.get(pos);
+ }
+
+ private TileRecord getRecord(View v) {
+ for (TileRecord record : mRecords) {
+ if (record.tileView == v) {
+ return record;
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public boolean onDrag(View v, DragEvent event) {
+ final DragTileRecord targetTile = (DragTileRecord) getRecord(v);
+ boolean originatingTileEvent = mDraggingRecord != null && v == mDraggingRecord.tileView;
+
+ final int dragRecordIndex = mRecords.indexOf(mDraggingRecord);
+ boolean dragRecordAttached = dragRecordIndex != -1;
+ switch (event.getAction()) {
+ case DragEvent.ACTION_DRAG_STARTED:
+ if (DEBUG_DRAG) {
+ Log.v(TAG, "ACTION_DRAG_STARTED on view: " + v);
+ }
+
+ if (originatingTileEvent) {
+ if (DEBUG_DRAG) {
+ Log.v(TAG, "ACTION_DRAG_STARTED on target view.");
+ }
+ mRestored = false;
+ mQsPanelTop.setDropIcon(R.drawable.ic_qs_tile_delete_disable, R.color.qs_tile_trash_normal_tint);
+ }
+
+ break;
+
+ case DragEvent.ACTION_DRAG_ENTERED:
+ if (DEBUG_DRAG) {
+ if (targetTile != null) {
+ Log.v(TAG, "ACTION_DRAG_ENTERED on view with tile: " + targetTile);
+ } else {
+ Log.v(TAG, "ACTION_DRAG_ENTERED on view: " + v);
+ }
+ }
+ mLocationHits = 0;
+ mMovedByLocation = false;
+
+ if (v == mQsPanelTop) {
+ int icon, color;
+ if (mDraggingRecord.tile instanceof EditTile) {
+ // use a different warning, user can't erase this one
+ icon = R.drawable.ic_qs_tile_delete_disable_avd;
+ color = R.color.qs_tile_trash_delete_tint_warning;
+ } else {
+ icon = R.drawable.ic_qs_tile_delete_disable;
+ color = R.color.qs_tile_trash_delete_tint;
+ }
+
+ mQsPanelTop.setDropIcon(icon, color);
+ }
+
+ if (!originatingTileEvent && v != getDropTarget() && targetTile != null) {
+ if (DEBUG_DRAG) {
+ Log.e(TAG, "entered tile " + targetTile);
+ }
+ if (mCurrentlyAnimating.isEmpty()
+ && !mViewPager.isFakeDragging()
+ && !dragRecordAttached) {
+ mMovedByLocation = true;
+ shiftTiles(targetTile, true);
+ } else {
+ if (DEBUG_DRAG) {
+ Log.w(TAG, "ignoring action enter for animating tiles and fake drags");
+ }
+ }
+ }
+
+ break;
+ case DragEvent.ACTION_DRAG_ENDED:
+ if (DEBUG_DRAG) {
+ Log.v(TAG, "ACTION_DRAG_ENDED on view: " + v + "(tile: "
+ + targetTile + "), result: " + event.getResult());
+ }
+ if (originatingTileEvent && !event.getResult()) {
+ // view pager probably ate the event
+ restoreDraggingTilePosition(v, null);
+ }
+
+ break;
+
+ case DragEvent.ACTION_DROP:
+ if (DEBUG_DRAG) {
+ Log.v(TAG, "ACTION_DROP, event loc: " + event.getX() + ", " + event.getY()
+ + " + with tile: " + targetTile + " and view: " + v);
+ }
+ mLastTouchLocationX = event.getX();
+ mLastTouchLocationY = event.getY();
+
+ if (isDropTargetEvent(event, v)) {
+ if (DEBUG_DRAG) {
+ Log.d(TAG, "dropping on delete target!!");
+ }
+ if (mDraggingRecord.tile instanceof EditTile) {
+ final QSTileView editTileView = mDraggingRecord.tileView;
+
+ mQsPanelTop.toast(R.string.quick_settings_cannot_delete_edit_tile);
+ restoreDraggingTilePosition(v, new Runnable() {
+ @Override
+ public void run() {
+ // move edit tile to the back
+ final TileRecord editTile = getRecord(editTileView);
+ if (mRecords.remove(editTile)) {
+ // we depend on mHost.setTiles() placing it on the end
+ persistRecords();
+ }
+ }
+ });
+ break;
+ } else if (mDraggingRecord.tile instanceof CustomQSTile) {
+ ((CustomQSTile) mDraggingRecord.tile).setUserRemoved(true);
+ final String spec = mHost.getSpec(mDraggingRecord.tile);
+ restoreDraggingTilePosition(v, new Runnable() {
+ @Override
+ public void run() {
+ // it might get added back later by the app, but that's ok,
+ // we just want to reset its position after it has been removed.
+ mHost.remove(spec);
+ }
+ });
+ } else {
+ mRestored = true;
+ removeDraggingRecord();
+ }
+ } else {
+ restoreDraggingTilePosition(v, null);
+ }
+ break;
+
+ case DragEvent.ACTION_DRAG_EXITED:
+ if (DEBUG_DRAG) {
+ if (targetTile != null) {
+ Log.v(TAG, "ACTION_DRAG_EXITED on view with tile: " + targetTile);
+ } else {
+ Log.v(TAG, "ACTION_DRAG_EXITED on view: " + v);
+ }
+ }
+
+ if (v == mQsPanelTop) {
+ mQsPanelTop.setDropIcon(R.drawable.ic_qs_tile_delete_disable, R.color.qs_tile_trash_normal_tint);
+ }
+
+ if (originatingTileEvent
+ && mCurrentlyAnimating.isEmpty()
+ && !mViewPager.isFakeDragging()
+ && dragRecordAttached
+ && mLastLeftShift == -1) {
+
+ if (DEBUG_DRAG) {
+ Log.v(TAG, "target: " + targetTile + ", hit mLastRightShift: "
+ + mLastRightShift + ", mLastLeftShift: "
+ + mLastLeftShift + ", dragRecordIndex: "
+ + dragRecordIndex);
+ }
+
+ // move tiles back
+ shiftTiles(mDraggingRecord, false);
+ break;
+ }
+ // fall through so exit events can trigger a left shift
+ case DragEvent.ACTION_DRAG_LOCATION:
+ mLastTouchLocationX = event.getX();
+ mLastTouchLocationY = event.getY();
+
+ // do nothing if we're animating tiles
+ if (mCurrentlyAnimating.isEmpty() && !mViewPager.isFakeDragging()) {
+ if (v == mViewPager) {
+ // do we need to change pages?
+ int x = (int) event.getX();
+ int width = mViewPager.getWidth();
+ int scrollPadding = (int) (width * QSViewPager.SCROLL_PERCENT);
+ if (x < scrollPadding) {
+ if (mViewPager.canScrollHorizontally(-1)) {
+ mViewPager.animatePagerTransition(false);
+ return true;
+ }
+ } else if (x > width - scrollPadding) {
+ if (mViewPager.canScrollHorizontally(1)) {
+ mViewPager.animatePagerTransition(true);
+ return true;
+ }
+ }
+ }
+ if (DEBUG_DRAG) {
+ Log.v(TAG, "location hit:// target: " + targetTile
+ + ", hit mLastRightShift: " + mLastRightShift
+ + ", mLastLeftShift: " + mLastLeftShift
+ + ", dragRecordIndex: " + dragRecordIndex
+ + ", originatingTileEvent: " + originatingTileEvent
+ + ", mLocationHits: " + mLocationHits
+ + ", mMovedByLocation: " + mMovedByLocation);
+ }
+
+ if (v != getDropTarget() && targetTile != null && !dragRecordAttached) {
+ // dragging around on another tile
+ if (mLocationHits++ == 30) {
+ if (DEBUG_DRAG) {
+ Log.w(TAG, "shifting right due to location hits.");
+ }
+ // add dragging tile to current page
+ shiftTiles(targetTile, true);
+ mMovedByLocation = true;
+ } else {
+ mLocationHits++;
+ }
+ } else if (mLastRightShift != -1 // right has shifted recently
+ && mLastLeftShift == -1 // -1 means its attached
+ && dragRecordIndex == mLastRightShift
+ && !originatingTileEvent
+ && !mMovedByLocation /* helps avoid continuous shifting */) {
+ // check if the location is on another tile/view
+ // that is not the last drag index, shift back left to revert back and
+ // potentially get ready for shifting right
+ if (DEBUG_DRAG) {
+ Log.w(TAG, "conditions met to reverse!!!! shifting left. <<<<<<<");
+ }
+ shiftTiles((DragTileRecord) mRecords.get(mLastRightShift), false);
+ mMovedByLocation = true;
+ }
+
+ } else {
+ if (DEBUG_DRAG) {
+ Log.i(TAG, "ignoring location event because things are animating, size: "
+ + mCurrentlyAnimating.size());
+ }
+ }
+ break;
+
+ default:
+ Log.w(TAG, "unhandled event");
+ return false;
+ }
+ return true;
+ }
+
+ private boolean isDropTargetEvent(DragEvent event, View v) {
+ if (DEBUG_DRAG) {
+ Log.d(TAG, "isDropTargetEvent() called with " + "event = [" + event + "], v = [" + v + "]");
+ }
+ if (v == getDropTarget() || v == mQsPanelTop) {
+ if (DEBUG_DRAG) {
+ Log.d(TAG, "isDropTargetEvent() returns true by view");
+ }
+ return true;
+ }
+
+ if (v == mViewPager && mLastTouchLocationY <= getRowTop(0)) {
+ if (DEBUG_DRAG) {
+ Log.d(TAG, "isDropTargetEvent() returns true by loc");
+ }
+ return true;
+ }
+
+ return false;
+ }
+
+ private void restoreDraggingTilePosition(View v, final Runnable onAnimationFinishedRunnable) {
+ if (mRestored) {
+ return;
+ }
+ mRestored = true;
+ mRestoring = true;
+ mCurrentlyAnimating.add(mDraggingRecord);
+
+ if (DEBUG_DRAG) {
+ Log.i(TAG, "restoreDraggingTilePosition() called with "
+ + "v = [" + (v.getTag() != null ? v.getTag() : v) + "]");
+ }
+ final boolean dragRecordDetached = mRecords.indexOf(mDraggingRecord) == -1;
+
+ if (DEBUG_DRAG) {
+ Log.v(TAG, "mLastLeftShift: " + mLastLeftShift
+ + ", detached: " + dragRecordDetached + ", drag record: " + mDraggingRecord);
+ }
+
+ final QSPage originalPage = getPage(mDraggingRecord.page);
+ originalPage.removeView(mDraggingRecord.tileView);
+ addTransientView(mDraggingRecord.tileView, 0);
+ mDraggingRecord.tileView.setTransitionVisibility(View.VISIBLE);
+
+ // need to move center of the dragging view to the coords of the event.
+ final float touchEventBoxLeft = v.getX()
+ + (mLastTouchLocationX - (mDraggingRecord.tileView.getWidth() / 2));
+ final float touchEventBoxTop = v.getY()
+ + (mLastTouchLocationY - (mDraggingRecord.tileView.getHeight() / 2));
+
+ mDraggingRecord.tileView.setX(touchEventBoxLeft);
+ mDraggingRecord.tileView.setY(touchEventBoxTop);
+
+ if (dragRecordDetached) {
+ setToLastDestination(mDraggingRecord);
+ if (DEBUG_DRAG) {
+ Log.d(TAG, "setting drag record view to coords: x:" + touchEventBoxLeft
+ + ", y:" + touchEventBoxTop);
+ Log.d(TAG, "animating drag record to: " + mDraggingRecord + ", loc: "
+ + mDraggingRecord.destination);
+ }
+ } else {
+ mDraggingRecord.destination.x = getLeft(mDraggingRecord.destinationPage,
+ mDraggingRecord.row, mDraggingRecord.col,
+ getColumnCount(mDraggingRecord.destinationPage, mDraggingRecord.row));
+
+ mDraggingRecord.destination.y = getRowTop(mDraggingRecord.row);
+ }
+
+ // setup x destination to animate to
+ float destinationX = mDraggingRecord.destination.x;
+
+ // see if we should animate this to the left or right off the page
+ // the +1's are to account for the edit page
+ if (mDraggingRecord.destinationPage > mViewPager.getCurrentItem() - 1) {
+ if (DEBUG_DRAG) {
+ Log.d(TAG, "adding width to animate out >>>>>");
+ }
+ destinationX += getWidth();
+ } else if (mDraggingRecord.destinationPage < mViewPager.getCurrentItem() - 1) {
+ if (DEBUG_DRAG) {
+ Log.d(TAG, "removing width to animate out <<<<<");
+ }
+ destinationX -= getWidth();
+ }
+
+ // setup y
+ float destinationY = mDraggingRecord.destination.y + mViewPager.getTop();
+
+ mDraggingRecord.tileView.animate()
+ .withLayer()
+ .x(destinationX)
+ .y(destinationY) // part of the viewpager now
+ .setListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationStart(Animator animation) {
+ mDraggingRecord.tileView.setAlpha(1);
+ }
+
+ @Override
+ public void onAnimationCancel(Animator animation) {
+ mViewPager.requestDisallowInterceptTouchEvent(false);
+ removeTransientView(mDraggingRecord.tileView);
+ mCurrentlyAnimating.remove(mDraggingRecord);
+ mRestoring = false;
+ mPagerAdapter.notifyDataSetChanged();
+ onStopDrag();
+
+ if (onAnimationFinishedRunnable != null) {
+ postOnAnimation(onAnimationFinishedRunnable);
+ }
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mViewPager.requestDisallowInterceptTouchEvent(false);
+
+ removeTransientView(mDraggingRecord.tileView);
+
+ final QSPage targetP = getPage(mDraggingRecord.destinationPage);
+
+ if (DEBUG_DRAG) {
+ if (dragRecordDetached) {
+ Log.i(TAG, "drag record was detached");
+ } else {
+ Log.i(TAG, "drag record was attached");
+ }
+ }
+ targetP.addView(mDraggingRecord.tileView);
+ mDraggingRecord.page = mDraggingRecord.destinationPage;
+
+ mDraggingRecord.tileView.setX(mDraggingRecord.destination.x);
+ // reset this to be in the coords of the page, not viewpager anymore
+ mDraggingRecord.tileView.setY(mDraggingRecord.destination.y);
+
+ mCurrentlyAnimating.remove(mDraggingRecord);
+
+ mRestoring = false;
+
+ if (dragRecordDetached) {
+ mRecords.add(mDraggingRecord);
+ mPagerAdapter.notifyDataSetChanged();
+ }
+ onStopDrag();
+
+ if (onAnimationFinishedRunnable != null) {
+ postOnAnimation(onAnimationFinishedRunnable);
+ } else {
+ requestLayout();
+ }
+ }
+ });
+ }
+
+ private void setToNextDestination(DragTileRecord tile) {
+ if (DEBUG_DRAG) {
+ Log.i(TAG, "+++setToNextDestination() called with " + "tile = [" + tile + "], at: "
+ + tile.destination);
+ }
+ tile.col++;
+ int maxCols = getColumnCount();
+
+ if (tile.col >= maxCols) {
+ tile.col = 0;
+ tile.row++;
+ if (DEBUG_DRAG) {
+ Log.w(TAG, "reached max column count, shifting to next row: " + tile.row);
+ }
+ }
+
+ // clamp this value to the max count we want.
+ int maxRows = Math.min(MAX_ROW_COUNT - 1 /* we are 0 based */, getCurrentMaxRow());
+
+ if (tile.row > maxRows) {
+ tile.destinationPage = tile.destinationPage + 1;
+ tile.row = 0;
+ tile.col = 0;
+
+ if (DEBUG_DRAG) {
+ Log.w(TAG, "tile's destination page moved to: " + tile.destinationPage);
+ }
+ }
+ int columnCount = Math.max(1, getColumnCount(tile.destinationPage, tile.row, false));
+ if (columnCount < maxCols) {
+ // if columncount gives us 1 and we're at col 2
+ columnCount = Math.max((tile.col + 1), columnCount);
+ }
+ if (DEBUG_DRAG) {
+ Log.w(TAG, "columCount at: " + columnCount);
+ }
+
+ boolean firstRowLarge = mFirstRowLarge && tile.row == 0 && tile.destinationPage == 0;
+
+ tile.destination.x = getLeft(tile.row, tile.col, columnCount, firstRowLarge);
+ tile.destination.y = getRowTop(tile.row);
+
+ if (DEBUG_DRAG) {
+ Log.i(TAG, "---setToNextDestination() called with " + "tile = [" + tile + "], now at: "
+ + tile.destination);
+ }
+ }
+
+ private boolean isBefore(DragTileRecord r1, DragTileRecord r2) {
+ if (DEBUG_DRAG) {
+ Log.d(TAG, "isBefore() called with " + "r1 = [" + r1 + "], r2 = [" + r2 + "]");
+ }
+ boolean isBefore = r1.destinationPage <= r2.destinationPage;
+ if (r1.destinationPage == r2.destinationPage) {
+ isBefore = r1.row <= r2.row;
+ if (r1.row == r2.row) {
+ isBefore = r1.col <= r2.col;
+ }
+ }
+
+ if (DEBUG_DRAG) {
+ Log.d(TAG, "r1 isBefore r2: " + isBefore);
+ }
+ return isBefore;
+ }
+
+ private void setToLastDestination(DragTileRecord record) {
+ DragTileRecord last = (DragTileRecord) mRecords.get(mRecords.size() - 1);
+ if (DEBUG_DRAG) {
+ Log.d(TAG, "setToLastDestination() called with record = ["
+ + record + "], and last record is: " + last);
+ }
+
+ if (isBefore(record, last)) {
+ // if the record is before the last record in the records list, set it to the
+ // last location, then spoof it one space forward
+ record.destinationPage = last.destinationPage;
+ record.row = last.row;
+ record.col = last.col;
+ record.destination.x = last.destination.x;
+ record.destination.y = last.destination.y;
+ setToNextDestination(record);
+ }
+ }
+
+ @Override
+ public boolean onLongClick(View v) {
+ final DragTileRecord record = (DragTileRecord) getRecord(v);
+ if (record == null) {
+ // TODO couldn't find a matching tag?
+ Log.e(TAG, "got a null record on touch down.");
+ return false;
+ }
+
+ mDraggingRecord = record;
+
+ mDraggingRecord.tileView.setAlpha(0);
+ mDraggingRecord.tileView.setDual(false, false);
+ TileShadow mTileShadow = new TileShadow(mDraggingRecord.tileView);
+
+ v.startDrag(null, mTileShadow, null, 0);
+
+ mViewPager.requestDisallowInterceptTouchEvent(true);
+
+ onStartDrag();
+ mDragging = true;
+ return true;
+ }
+
+ private void shiftTiles(DragTileRecord startingTile, boolean forward) {
+ if (DEBUG_DRAG) {
+ Log.i(TAG, "shiftTiles() called with " + "startingTile = [" + startingTile
+ + "], forward = [" + forward + "]");
+ }
+
+ if (forward) {
+ // startingTile and all after will need to be shifted one to the right
+ // dragging tile needs room
+
+ final int destP = startingTile.destinationPage;
+ final int rowF = startingTile.row;
+ final int colF = startingTile.col;
+ PointF loc = new PointF(startingTile.destination.x, startingTile.destination.y);
+
+ // the index of the original position of the statingTile before it moved
+ int startingIndex = mRecords.indexOf(startingTile);
+ mLastRightShift = startingIndex;
+ mLastLeftShift = -1;
+
+ shiftAllTilesRight(startingIndex);
+ mRecords.add(startingIndex, mDraggingRecord);
+
+ mPagerAdapter.notifyDataSetChanged();
+
+ mDraggingRecord.col = colF;
+ mDraggingRecord.row = rowF;
+ mDraggingRecord.destination = loc;
+ mDraggingRecord.destinationPage = destP;
+
+ mDraggingRecord.tileView.setX(mDraggingRecord.destination.x);
+ mDraggingRecord.tileView.setY(mDraggingRecord.destination.y);
+
+ } else {
+ // it is also probably the dragging tile
+ final int startingIndex = mRecords.indexOf(startingTile);
+ mLastLeftShift = startingIndex;
+ mLastRightShift = -1;
+
+ final int draggingIndex = mRecords.indexOf(mDraggingRecord);
+
+ if (startingIndex != draggingIndex) {
+ if (DEBUG_DRAG) {
+ Log.e(TAG, "startinIndex: " + startingIndex + ", draggingIndex: "
+ + draggingIndex + ", and they differ!!!!");
+ }
+ }
+
+ // startingTile should be the "empty" tile that things should start shifting into
+ shiftAllTilesLeft(startingIndex);
+
+ // remove the dragging record
+ if (mRecords.remove(mDraggingRecord)) {
+ mPagerAdapter.notifyDataSetChanged();
+ if (DEBUG_DRAG) {
+ Log.v(TAG, "removed dragging record after moving tiles back");
+ }
+ }
+
+ // set coords off screen until we're ready to place it
+ mDraggingRecord.tileView.setX(-mDraggingRecord.tileView.getMeasuredWidth());
+ mDraggingRecord.tileView.setY(-mDraggingRecord.tileView.getMeasuredHeight());
+ }
+
+ mViewPager.getAdapter().notifyDataSetChanged();
+ }
+
+ private void shiftAllTilesRight(int startingIndex) {
+ int desiredColumnCount = -1;
+ for (int j = startingIndex; j < mRecords.size() - 1; j++) {
+ final DragTileRecord ti = (DragTileRecord) mRecords.get(j);
+ final DragTileRecord tnext = (DragTileRecord) mRecords.get(j + 1);
+
+ mCurrentlyAnimating.add(ti);
+ if (tnext.row != ti.row || desiredColumnCount == -1) {
+ desiredColumnCount = getColumnCount(tnext.destinationPage, tnext.row);
+ //Log.w(TAG, "updated desiredColumnCount: " + desiredColumnCount);
+ }
+
+ if (DEBUG_DRAG) {
+ Log.v(TAG, "moving " + ti + " to page " + tnext.destinationPage + ", at coords: "
+ + tnext.row + ", col: " + tnext.col + ", dest: " + tnext.destination);
+ }
+
+ ti.row = tnext.row;
+ ti.col = tnext.col;
+ ti.destination.x = getLeft(tnext.destinationPage, ti.row, ti.col, desiredColumnCount);
+ ti.destination.y = getRowTop(ti.row);
+
+ if (ti.destinationPage != tnext.destinationPage) {
+ ti.destinationPage = tnext.destinationPage;
+
+ final QSPage tilePageSource = getPage(ti.page);
+ final QSPage tilePageTarget = getPage(ti.destinationPage);
+ tilePageSource.removeView(ti.tileView);
+
+ tilePageSource.addTransientView(ti.tileView, 0);
+ ti.tileView.animate()
+ .withLayer()
+ .x(ti.destination.x + getWidth())
+ .y(ti.destination.y)
+ .setListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationCancel(Animator animation) {
+ tilePageSource.removeTransientView(ti.tileView);
+ mCurrentlyAnimating.remove(ti);
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ tilePageSource.removeTransientView(ti.tileView);
+ tilePageTarget.addView(ti.tileView);
+ ti.page = tilePageTarget.getPageIndex();
+ ti.tileView.setX(ti.destination.x);
+ ti.tileView.setY(ti.destination.y);
+
+ mCurrentlyAnimating.remove(ti);
+ requestLayout();
+ }
+ });
+
+ } else {
+ ti.tileView.animate()
+ .withLayer()
+ .x(ti.destination.x)
+ .y(ti.destination.y)
+ .setListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationCancel(Animator animation) {
+ mCurrentlyAnimating.remove(ti);
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mCurrentlyAnimating.remove(ti);
+ final boolean dual = getPage(ti.destinationPage).dualRecord(ti);
+ if (ti.tileView.setDual(dual, ti.tile.hasDualTargetsDetails())) {
+ if (DEBUG_DRAG) {
+ Log.w(TAG, ti + " changed dual state to : "
+ + ti.tileView.isDual());
+ }
+ }
+ requestLayout();
+ }
+ });
+ }
+ }
+
+ // need to do last tile manually
+ final DragTileRecord last = (DragTileRecord) mRecords.get(mRecords.size() - 1);
+ mCurrentlyAnimating.add(last);
+
+ if (DEBUG_DRAG) {
+ Log.i(TAG, "last tile shifting to the right: " + last);
+ }
+ setToNextDestination(last);
+ if (last.page != last.destinationPage) {
+ final QSPage tilePageSource = getPage(last.page);
+ final QSPage tilePageTarget = getPage(last.destinationPage);
+ tilePageSource.removeView(last.tileView);
+ tilePageSource.addTransientView(last.tileView, 0);
+
+ last.tileView.animate()
+ .withLayer()
+ .x(last.destination.x + getWidth())
+ .y(last.destination.y)
+ .setListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationCancel(Animator animation) {
+ tilePageSource.removeTransientView(last.tileView);
+ mCurrentlyAnimating.remove(last);
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ tilePageSource.removeTransientView(last.tileView);
+ tilePageTarget.addView(last.tileView);
+ last.page = tilePageTarget.getPageIndex();
+ last.tileView.setX(last.destination.x);
+ last.tileView.setY(last.destination.y);
+
+ if (DEBUG_DRAG) {
+ Log.i(TAG, "page shift finished: " + last);
+ }
+
+ mCurrentlyAnimating.remove(last);
+ requestLayout();
+ }
+ });
+ } else {
+ last.tileView.animate()
+ .withLayer()
+ .x(last.destination.x)
+ .y(last.destination.y)
+ .setListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationCancel(Animator animation) {
+ mCurrentlyAnimating.remove(last);
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ if (DEBUG_DRAG) {
+ Log.i(TAG, "shift finished: " + last);
+ }
+
+ mCurrentlyAnimating.remove(last);
+ requestLayout();
+ }
+ });
+ }
+ }
+
+ private void shiftAllTilesLeft(int startingIndex) {
+ DragTileRecord startingTile = (DragTileRecord) mRecords.get(startingIndex);
+
+ final PointF lastLocation = new PointF(startingTile.destination.x,
+ startingTile.destination.y);
+ PointF reallyTempLoc = new PointF();
+ int lastRow = startingTile.row, lastCol = startingTile.col, tempRow,
+ tempCol, lastPage = startingTile.destinationPage, tempPage;
+
+ int desiredColCount = getColumnCount(startingTile.destinationPage, startingTile.row);
+ for (int j = startingIndex + 1; j < mRecords.size(); j++) {
+ final DragTileRecord ti = (DragTileRecord) mRecords.get(j);
+
+ mCurrentlyAnimating.add(ti);
+
+ if (DEBUG_DRAG) {
+ Log.v(TAG, "moving " + ti + " to " + lastPage + ", at coords: "
+ + lastRow + ", col: " + lastCol);
+ Log.i(TAG, "and will have desiredColCount: " + desiredColCount);
+ }
+
+ final int columnCountF = desiredColCount;
+
+ if (ti.row != lastRow) {
+ desiredColCount = getColumnCount(ti.destinationPage, ti.row);
+ if (DEBUG_DRAG) {
+ Log.e(TAG, "updating desired colum count to: " + desiredColCount);
+ }
+ }
+
+ // save current tile's loc
+ reallyTempLoc.x = ti.destination.x;
+ reallyTempLoc.y = ti.destination.y;
+
+ tempRow = ti.row;
+ tempCol = ti.col;
+ tempPage = ti.destinationPage;
+
+ ti.row = lastRow;
+ ti.col = lastCol;
+
+ ti.destination.x = getLeft(lastRow, lastCol, columnCountF,
+ lastPage == 0 && lastRow == 0 && mFirstRowLarge);
+ ti.destination.y = getRowTop(lastRow);
+
+ final boolean dual = getPage(ti.destinationPage).dualRecord(ti);
+
+ if (ti.destinationPage != lastPage) {
+ ti.destinationPage = lastPage;
+
+ ti.tileView.setX(reallyTempLoc.x + getWidth());
+ ti.tileView.setY(reallyTempLoc.y);
+
+ final QSPage originalPage = getPage(ti.page);
+ final QSPage page = getPage(lastPage);
+
+ originalPage.removeView(ti.tileView);
+
+ ti.tileView.animate()
+ .withLayer()
+ .x(ti.destination.x)
+ .y(ti.destination.y)
+ .setListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationStart(Animator animation) {
+ page.addTransientView(ti.tileView, 0);
+ }
+
+ @Override
+ public void onAnimationCancel(Animator animation) {
+ page.removeTransientView(ti.tileView);
+ mCurrentlyAnimating.remove(ti);
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ page.removeTransientView(ti.tileView);
+ page.addView(ti.tileView);
+ ti.page = page.getPageIndex();
+
+ mCurrentlyAnimating.remove(ti);
+ requestLayout();
+ }
+ });
+ } else {
+ ti.tileView.animate()
+ .withLayer()
+ .x(ti.destination.x)
+ .y(ti.destination.y)
+ .setListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mCurrentlyAnimating.remove(ti);
+ if (ti.tileView.setDual(dual, ti.tile.hasDualTargetsDetails())) {
+ if (DEBUG_DRAG) {
+ Log.w(TAG, ti + " changed dual state to : "
+ + ti.tileView.isDual());
+ }
+ }
+ requestLayout();
+ }
+ });
+ }
+
+ // update previous location
+ lastLocation.x = reallyTempLoc.x;
+ lastLocation.y = reallyTempLoc.y;
+
+ lastRow = tempRow;
+ lastCol = tempCol;
+ lastPage = tempPage;
+ }
+ }
+
+ @Override
+ protected void handleShowDetailImpl(Record r, boolean show, int x, int y) {
+ super.handleShowDetailImpl(r, show, x, y);
+ if (show) {
+ final StatusBarPanelCustomTile customTile = r.detailAdapter.getCustomTile();
+ mDetailRemoveButton.setVisibility(customTile != null &&
+ !(customTile.getPackage().equals(mContext.getPackageName())
+ || customTile.getUid() == android.os.Process.SYSTEM_UID)
+ ? VISIBLE : GONE);
+ mDetailRemoveButton.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ mHost.removeCustomTile(customTile);
+ closeDetail();
+ }
+ });
+ }
+ mPanelView.setDetailRequestedScrollLock(mExpanded && show
+ && getResources().getConfiguration().orientation == ORIENTATION_LANDSCAPE);
+ }
+
+ @Override
+ protected void onConfigurationChanged(Configuration newConfig) {
+ super.onConfigurationChanged(newConfig);
+ FontSizeUtils.updateFontSize(mDetailRemoveButton, R.dimen.qs_detail_button_text_size);
+ mPanelView.setDetailRequestedScrollLock(mExpanded && isShowingDetail()
+ && getResources().getConfiguration().orientation == ORIENTATION_LANDSCAPE);
+ }
+
+ @Override
+ public void setExpanded(boolean expanded) {
+ super.setExpanded(expanded);
+ // reset the page when inactive for a while
+ if (expanded) {
+ removeCallbacks(mResetPage);
+ } else {
+ postDelayed(mResetPage, PAGE_RESET_DELAY);
+ }
+ if (!expanded) {
+ if (mEditing) {
+ mHost.setEditing(false);
+ }
+ }
+ }
+
+ public void updateResources() {
+ final Resources res = mContext.getResources();
+ final int columns = Math.max(1, res.getInteger(R.integer.quick_settings_num_columns));
+ mCellHeight = res.getDimensionPixelSize(R.dimen.qs_tile_height);
+ mCellWidth = (int) (mCellHeight * TILE_ASPECT);
+ mLargeCellHeight = res.getDimensionPixelSize(R.dimen.qs_dual_tile_height);
+ mLargeCellWidth = (int) (mLargeCellHeight * TILE_ASPECT);
+ mPanelPaddingBottom = res.getDimensionPixelSize(R.dimen.qs_panel_padding_bottom);
+ mDualTileUnderlap = res.getDimensionPixelSize(R.dimen.qs_dual_tile_padding_vertical);
+ mBrightnessPaddingTop = res.getDimensionPixelSize(R.dimen.qs_brightness_padding_top);
+ mPageIndicatorHeight = res.getDimensionPixelSize(R.dimen.qs_panel_page_indicator_height);
+ if (mColumns != columns) {
+ mColumns = columns;
+ if (isLaidOut()) postInvalidate();
+ }
+ if (isLaidOut()) {
+ for (TileRecord r : mRecords) {
+ r.tile.clearState();
+ }
+ updateDetailText();
+ mQsPanelTop.updateResources();
+ if (mListening) {
+ refreshAllTiles();
+ }
+ }
+ }
+
+ public boolean isAnimating(TileRecord t) {
+ return mCurrentlyAnimating.contains(t);
+ }
+
+ public void cleanup() {
+ if (mSettingsObserver != null) {
+ mSettingsObserver.unobserve();
+ }
+ }
+
+ public void setPanelView(NotificationPanelView panelView) {
+ this.mPanelView = panelView;
+ }
+
+ public static class TilesListAdapter extends BaseExpandableListAdapter
+ implements QSTile.DetailAdapter {
+
+ public static final String PACKAGE_ANDROID = "android";
+
+ Context mContext;
+ QSTileHost mHost;
+ QSDragPanel mPanel;
+
+ ArrayMap<String, List<String>> mPackageTileMap = new ArrayMap<>();
+
+ public TilesListAdapter(Context context, QSDragPanel panel) {
+ mContext = context;
+ mHost = panel.getHost();
+ mPanel = panel;
+
+ List<String> currentTileSpec = mHost.getTileSpecs();
+ final Collection<String> tiles = QSUtils.getAvailableTiles(mContext);
+ tiles.removeAll(currentTileSpec);
+
+ // we'll always have a system tiles category
+ mPackageTileMap.put(PACKAGE_ANDROID, new ArrayList<String>());
+
+ final Iterator<String> i = tiles.iterator();
+ while (i.hasNext()) {
+ final String spec = i.next();
+ if (QSUtils.isStaticQsTile(spec)
+ || QSUtils.isDynamicQsTile(extractTileTagFromSpec(spec))) {
+ List<String> packageList = mPackageTileMap.get(PACKAGE_ANDROID);
+ packageList.add(spec);
+ } else {
+ String tilePackage = getCustomTilePackage(spec);
+ List<String> packageList = mPackageTileMap.get(tilePackage);
+ if (packageList == null) {
+ mPackageTileMap.put(tilePackage, packageList = new ArrayList<>());
+ }
+ packageList.add(spec);
+ }
+ }
+
+ final Map<String, ?> stringMap = CustomQSTile.getCustomQSTilePrefs(mContext).getAll();
+ for (Map.Entry<String, ?> entry : stringMap.entrySet()) {
+ if (entry.getValue() instanceof Boolean) {
+ if ((Boolean)entry.getValue()) {
+ final String key = entry.getKey();
+ if (QSUtils.isDynamicQsTile(extractTileTagFromSpec(key))) {
+ mPackageTileMap.get(PACKAGE_ANDROID).add(key);
+ } else {
+ final String customTilePackage = getCustomTilePackage(key);
+ List<String> packageList = mPackageTileMap.get(customTilePackage);
+ if (packageList == null) {
+ mPackageTileMap.put(customTilePackage,
+ packageList = new ArrayList<>());
+ }
+ packageList.add(key);
+
+ }
+ }
+ }
+ };
+
+ final List<String> systemTiles = mPackageTileMap.get(PACKAGE_ANDROID);
+ Collections.sort(systemTiles);
+ }
+
+ private String getCustomTilePackage(String spec) {
+ if (mHost.getCustomTileData().get(spec) != null) {
+ StatusBarPanelCustomTile sbc = mHost.getCustomTileData().get(spec).sbc;
+ return sbc.getPackage();
+ } else {
+ return extractPackageFromCustomTileSpec(spec);
+ }
+ }
+
+ private static String extractPackageFromCustomTileSpec(String spec) {
+ if (spec != null && !spec.isEmpty()) {
+ final String[] split = spec.split("\\|");
+ if (split != null && split.length > 2) {
+ return split[1];
+ }
+ return spec;
+ }
+ return null;
+ }
+
+ private static String extractTileTagFromSpec(String spec) {
+ if (spec != null && !spec.isEmpty()) {
+ final String[] split = spec.split("\\|");
+ if (split != null && split.length == 5) {
+ /** for {@link cyanogenmod.app.StatusBarPanelCustomTile#key() **/
+ return split[3];
+ } else if (split != null && split.length == 3) {
+ /** for {@link cyanogenmod.app.StatusBarPanelCustomTile#persistableKey()} **/
+ return split[2];
+ }
+ return spec;
+ }
+ return null;
+ }
+
+ private Drawable getQSTileIcon(String spec) {
+ if (QSUtils.isDynamicQsTile(extractTileTagFromSpec(spec))) {
+ return QSTile.ResourceIcon.get(QSUtils.getDynamicQSTileResIconId(mContext,
+ UserHandle.myUserId(), extractTileTagFromSpec(spec))).getDrawable(mContext);
+ } else if (QSUtils.isStaticQsTile(spec)) {
+ final int res = QSTileHost.getIconResource(spec);
+ if (res != 0) {
+ return QSTile.ResourceIcon.get(res).getDrawable(mContext);
+ } else {
+ return mContext.getPackageManager().getDefaultActivityIcon();
+ }
+ } else {
+ QSTile<?> tile = mHost.getTile(spec);
+ if (tile != null) {
+ QSTile.State state = tile.getState();
+ if (state != null && state.icon != null) {
+ return state.icon.getDrawable(mContext);
+ }
+ }
+ return getPackageDrawable(getCustomTilePackage(spec));
+ }
+ }
+
+ private String getPackageLabel(String packageName) {
+ try {
+ return mContext.getPackageManager().getApplicationLabel(
+ mContext.getPackageManager().getApplicationInfo(packageName, 0)).toString();
+ } catch (PackageManager.NameNotFoundException e) {
+ e.printStackTrace();
+ }
+ return null;
+ }
+
+ private Drawable getPackageDrawable(String packageName) {
+ try {
+ return mContext.getPackageManager().getApplicationIcon(packageName);
+ } catch (PackageManager.NameNotFoundException e) {
+ e.printStackTrace();
+ }
+ return null;
+ }
+
+ private String getQSTileLabel(String spec) {
+ if (QSUtils.isStaticQsTile(spec)) {
+ int resource = QSTileHost.getLabelResource(spec);
+ if (resource != 0) {
+ return mContext.getText(resource).toString();
+ } else {
+ return spec;
+ }
+ } else if (QSUtils.isDynamicQsTile(extractTileTagFromSpec(spec))) {
+ return QSUtils.getDynamicQSTileLabel(mContext,
+ UserHandle.myUserId(), extractTileTagFromSpec(spec));
+ } else {
+ return getPackageLabel(getCustomTilePackage(spec));
+ }
+ }
+
+ @Override
+ public int getGroupCount() {
+ return mPackageTileMap.keySet().size();
+ }
+
+ @Override
+ public int getChildrenCount(int groupPosition) {
+ return mPackageTileMap.valueAt(groupPosition).size();
+ }
+
+ @Override
+ public String getGroup(int groupPosition) {
+ return mPackageTileMap.keyAt(groupPosition);
+ }
+
+ @Override
+ public String getChild(int groupPosition, int childPosition) {
+ return mPackageTileMap.valueAt(groupPosition).get(childPosition);
+ }
+
+ @Override
+ public long getGroupId(int groupPosition) {
+ return groupPosition;
+ }
+
+ @Override
+ public long getChildId(int groupPosition, int childPosition) {
+ return mPackageTileMap.valueAt(groupPosition).get(childPosition).hashCode();
+ }
+
+ @Override
+ public boolean hasStableIds() {
+ return true;
+ }
+
+ @Override
+ public View getGroupView(int groupPosition, boolean isExpanded, View convertView,
+ ViewGroup parent) {
+ LinearLayout row = (LinearLayout) convertView;
+ if (row == null) {
+ row = (LinearLayout) LayoutInflater.from(mContext)
+ .inflate(R.layout.qs_tile_category_row, parent, false);
+ }
+ TextView title = (TextView) row.findViewById(android.R.id.title);
+
+ ImageView systemOrAppIcon = (ImageView) row.findViewById(android.R.id.icon);
+ ImageView expansionIndicator = (ImageView) row.findViewById(android.R.id.icon2);
+
+ expansionIndicator.setImageResource(isExpanded ? R.drawable.ic_qs_tile_contract
+ : R.drawable.ic_qs_tile_expand);
+ // hide indicator when there's only 1 group
+ final boolean singleGroupMode = getGroupCount() == 1;
+ expansionIndicator.setVisibility(singleGroupMode ? View.GONE : View.VISIBLE);
+
+ String group = getGroup(groupPosition);
+ if (group.equals(PACKAGE_ANDROID)) {
+ group = mContext.getText(R.string.quick_settings_tiles_category_system).toString();
+ // special icon
+ systemOrAppIcon.setImageResource(R.drawable.ic_qs_tile_category_system);
+ } else {
+ group = getPackageLabel(group);
+ systemOrAppIcon.setImageResource(R.drawable.ic_qs_tile_category_other);
+ }
+ title.setText(group);
+
+ if (isExpanded) {
+ expansionIndicator.setColorFilter(
+ mContext.getColor(
+ R.color.qs_detailed_expansion_indicator_color), PorterDuff.Mode.SRC_ATOP);
+ systemOrAppIcon.setColorFilter(
+ mContext.getColor(R.color.qs_detailed_icon_tint_color), PorterDuff.Mode.SRC_ATOP);
+ title.setTextColor(mContext.getColor(R.color.qs_detailed_title_text_color));
+ } else {
+ title.setTextColor(mContext.getColor(R.color.qs_detailed_default_text_color));
+ systemOrAppIcon.setColorFilter(null);
+ expansionIndicator.setColorFilter(null);
+ }
+ return row;
+ }
+
+ @Override
+ public View getChildView(int groupPosition, int childPosition, boolean isLastChild,
+ View convertView, ViewGroup parent) {
+ LinearLayout child = (LinearLayout) convertView;
+ if (child == null) {
+ child = (LinearLayout) LayoutInflater.from(mContext)
+ .inflate(R.layout.qs_tile_child_row, parent, false);
+ }
+ String spec = getChild(groupPosition, childPosition);
+
+ TextView title = (TextView) child.findViewById(android.R.id.title);
+ title.setText(getQSTileLabel(spec));
+
+ ImageView icon = (ImageView) child.findViewById(android.R.id.icon);
+ icon.setImageDrawable(getQSTileIcon(spec));
+
+ return child;
+ }
+
+ @Override
+ public boolean isChildSelectable(int groupPosition, int childPosition) {
+ return true;
+ }
+
+ @Override
+ public int getTitle() {
+ return R.string.quick_settings_tiles_add_tiles;
+ }
+
+ @Override
+ public Boolean getToggleState() {
+ return null;
+ }
+
+ @Override
+ public View createDetailView(Context context, View convertView, ViewGroup parent) {
+ ExpandableListView lv = (ExpandableListView) convertView;
+ if (lv == null) {
+ lv = new ExpandableListView(parent.getContext());
+ lv.setOnTouchListener(new OnTouchListener() {
+ // Setting on Touch Listener for handling the touch inside ScrollView
+ @Override
+ public boolean onTouch(View v, MotionEvent event) {
+ // Disallow the touch request for parent scroll on touch of child view
+ v.getParent().requestDisallowInterceptTouchEvent(true);
+ return false;
+ }
+ });
+ }
+ lv.setAdapter(this);
+ lv.expandGroup(mPackageTileMap.indexOfKey(PACKAGE_ANDROID));
+ lv.setGroupIndicator(null);
+ lv.setChildIndicator(null);
+ lv.setChildDivider(new ColorDrawable(Color.TRANSPARENT));
+ lv.setOnChildClickListener(new ExpandableListView.OnChildClickListener() {
+ @Override
+ public boolean onChildClick(ExpandableListView parent, View v,
+ int groupPosition, int childPosition, long id) {
+ String spec = getChild(groupPosition, childPosition);
+
+ final QSTile<?> tile = mHost.getTile(spec);
+ if (tile != null && tile instanceof CustomQSTile) {
+ // already present
+ ((CustomQSTile) tile).setUserRemoved(false);
+ mPanel.refreshAllTiles();
+ } else {
+ // reset its state just in case it's not published
+ CustomQSTile.getCustomQSTilePrefs(mContext)
+ .edit()
+ .remove(spec)
+ .apply();
+ mPanel.add(spec);
+ // TODO notify user the app isn't publishing the tile, but it now can be!
+ }
+ mPanel.closeDetail();
+ return true;
+ }
+ });
+ lv.setOnGroupClickListener(new ExpandableListView.OnGroupClickListener() {
+ @Override
+ public boolean onGroupClick(ExpandableListView parent, View v, int groupPosition,
+ long id) {
+ if (getGroupCount() == 1) {
+ // disable contracting/expanding group when there's only 1
+ return true;
+ }
+ return false;
+ }
+ });
+ return lv;
+ }
+
+ @Override
+ public Intent getSettingsIntent() {
+ return null;
+ }
+
+ @Override
+ public StatusBarPanelCustomTile getCustomTile() {
+ return null;
+ }
+
+ @Override
+ public void setToggleState(boolean state) {
+
+ }
+
+ @Override
+ public int getMetricsCategory() {
+ return CMMetricsLogger.DONT_LOG;
+ }
+
+ private boolean isValid(String action) {
+ for (int i = 0; i < action.length(); i++) {
+ char c = action.charAt(i);
+ if (!Character.isAlphabetic(c) && !Character.isDigit(c) && c != '.') {
+ return false;
+ }
+ }
+ return true;
+ }
+ }
+
+ public void add(String tile) {
+ MetricsLogger.action(getContext(), MetricsLogger.TUNER_QS_ADD, tile);
+ List<String> tiles = new ArrayList<>(mHost.getTileSpecs());
+ tiles.add(tile);
+ mHost.setTiles(tiles);
+ }
+
+ public boolean isDragging() {
+ return mDragging;
+ }
+
+ public boolean isDragRecordAttached() {
+ return mRecords.indexOf(mDraggingRecord) >= 0;
+ }
+
+ public boolean isOnSettingsPage() {
+ return mEditing && mViewPager.getCurrentItem() == 0;
+ }
+
+ public void goToSettingsPage() {
+ if (mEditing) {
+ mViewPager.setCurrentItem(0, true);
+ }
+ }
+
+ class SettingsObserver extends UserContentObserver {
+ SettingsObserver(Handler handler) {
+ super(handler);
+ }
+
+ @Override
+ protected void observe() {
+ super.observe();
+
+ ContentResolver resolver = mContext.getContentResolver();
+ resolver.registerContentObserver(CMSettings.Secure.getUriFor(
+ CMSettings.Secure.QS_USE_MAIN_TILES), false, this, UserHandle.USER_ALL);
+ update();
+ }
+
+ @Override
+ protected void unobserve() {
+ super.unobserve();
+
+ ContentResolver resolver = mContext.getContentResolver();
+ resolver.unregisterContentObserver(this);
+ }
+
+ @Override
+ public void update() {
+ ContentResolver resolver = mContext.getContentResolver();
+ int currentUserId = ActivityManager.getCurrentUser();
+ boolean firstRowLarge = CMSettings.Secure.getIntForUser(resolver,
+ CMSettings.Secure.QS_USE_MAIN_TILES, 1, currentUserId) == 1;
+ if (firstRowLarge != mFirstRowLarge) {
+ mFirstRowLarge = firstRowLarge;
+ setTiles(mHost.getTiles());
+ mPagerAdapter.notifyDataSetChanged();
+ }
+ }
+ }
+
+ public static final class DragTileRecord extends TileRecord {
+ public int page;
+ public int destinationPage;
+ public PointF destination = new PointF();
+
+ @Override
+ public String toString() {
+ String label = tile instanceof QsTuner.DraggableTile ? tile.toString() :
+ tile.getClass().getSimpleName();
+
+ String p = "at page: " + page;
+ if (destinationPage != page) {
+ p += "{-> " + destinationPage + "} ";
+ }
+
+ return "[" + label + ", coords: (" + row + ", " + col + ") " + p + "]";
+ }
+ }
+
+ private static class TileShadow extends View.DragShadowBuilder {
+
+ public TileShadow(View view) {
+ super(view);
+ Drawable shadow = view.getContext().getDrawable(R.drawable.qs_tile_background_drag);
+ view.setBackground(shadow);
+ }
+
+ @Override
+ public void onDrawShadow(Canvas canvas) {
+ super.onDrawShadow(canvas);
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPage.java b/packages/SystemUI/src/com/android/systemui/qs/QSPage.java
new file mode 100644
index 0000000..7871a62
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPage.java
@@ -0,0 +1,161 @@
+package com.android.systemui.qs;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.util.Log;
+import android.view.View;
+import android.view.ViewGroup;
+import com.android.systemui.R;
+
+public class QSPage extends ViewGroup {
+
+ private static final String TAG = "QSPage";
+
+ static final float TILE_ASPECT = 1.2f;
+
+ private int mCellWidth;
+ private int mCellHeight;
+ private int mLargeCellWidth;
+ private int mLargeCellHeight;
+ private int mGridHeight;
+
+ private QSDragPanel mPanel;
+
+ private int mPage;
+
+ private boolean mAdapterEditingState;
+
+ public QSPage(Context context, QSDragPanel panel, int page) {
+ super(context);
+ mPanel = panel;
+ mPage = page;
+ updateResources();
+ setClipChildren(false);
+ setClipToPadding(false);
+ setClipToOutline(false);
+ }
+
+ @Override
+ public boolean hasOverlappingRendering() {
+ return false;
+ }
+
+ public int getPageIndex() {
+ return mPage;
+ }
+
+ public void updateResources() {
+ final Resources res = mContext.getResources();
+ final int columns = Math.max(1, res.getInteger(R.integer.quick_settings_num_columns));
+ mCellHeight = res.getDimensionPixelSize(R.dimen.qs_tile_height);
+ mCellWidth = (int)(mCellHeight * TILE_ASPECT);
+ mLargeCellHeight = res.getDimensionPixelSize(R.dimen.qs_dual_tile_height);
+ mLargeCellWidth = (int)(mLargeCellHeight * TILE_ASPECT);
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ final int width = MeasureSpec.getSize(widthMeasureSpec);
+ if (mPanel.mCurrentlyAnimating.isEmpty() && !mPanel.isDragging()) {
+ int r = -1;
+ int c = -1;
+ int rows = 0;
+ for (QSPanel.TileRecord ts : mPanel.mRecords) {
+ QSDragPanel.DragTileRecord record = (QSDragPanel.DragTileRecord) ts;
+ if (record.page != mPage) continue;
+ if (record.tileView.getVisibility() == GONE) continue;
+
+ if (mPage == 0 && r == 0 && c == 1 && mPanel.mFirstRowLarge) {
+ r = 1;
+ c = 0;
+ } else if (r == -1 || c == (mPanel.getColumnCount() - 1)) {
+ r++;
+ c = 0;
+ } else {
+ c++;
+ }
+ record.row = r;
+ record.col = c;
+ rows = r + 1;
+ }
+ mGridHeight = mPanel.getRowTop(rows);
+ }
+
+ View previousView = mPanel.getBrightnessView();
+ for (QSPanel.TileRecord ts : mPanel.mRecords) {
+ QSDragPanel.DragTileRecord record = (QSDragPanel.DragTileRecord) ts;
+ if (record.page != mPage) continue;
+ if (record.page != record.destinationPage) continue;
+
+ final boolean dual = dualRecord(record);
+ if (record.tileView.setDual(dual, record.tile.hasDualTargetsDetails())) {
+ record.tileView.handleStateChanged(record.tile.getState());
+ }
+ if (record.tileView.getVisibility() == GONE) continue;
+ final int cw = dual ? mLargeCellWidth : mCellWidth;
+ final int ch = dual ? mLargeCellHeight : mCellHeight;
+ record.tileView.measure(exactly(cw), exactly(ch));
+ previousView = record.tileView.updateAccessibilityOrder(previousView);
+ }
+ setMeasuredDimension(width, mGridHeight);
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int l, int t, int r, int b) {
+ final int w = getWidth();
+ boolean isRtl = getLayoutDirection() == LAYOUT_DIRECTION_RTL;
+ for (QSPanel.TileRecord ts : mPanel.mRecords) {
+ QSDragPanel.DragTileRecord record = (QSDragPanel.DragTileRecord) ts;
+ if (record.page != mPage) continue;
+ if (record.page != record.destinationPage) continue;
+ if (record.tileView.getVisibility() == GONE) continue;
+
+ final int cols = mPanel.getColumnCount(mPage, record.row);
+
+ int left = mPanel.getLeft(record.row, record.col, cols, dualRecord(record));
+ final int top = mPanel.getRowTop(record.row);
+ int right;
+ int tileWith = record.tileView.getMeasuredWidth();
+ if (isRtl) {
+ right = w - left;
+ left = right - tileWith;
+ } else {
+ right = left + tileWith;
+ }
+ if (mPanel.isAnimating(record)) {
+ record.tileView.layout(record.tileView.getLeft(), record.tileView.getTop(),
+ record.tileView.getRight(), record.tileView.getBottom());
+ continue;
+ }
+ if (false) {
+ Log.v(TAG + "-" + mPage, "laying out " + record + ", top: " + top + ", left: " + left);
+ Log.d(TAG, record + " wiping translations: "
+ + record.tileView.getTranslationX()
+ + ", " + record.tileView.getTranslationY());
+ }
+ record.tileView.setTranslationX(0);
+ record.tileView.setTranslationY(0);
+
+ record.destination.x = record.tileView.getX();
+ record.destination.y = record.tileView.getY();
+
+ record.tileView.layout(left, top, right, top + record.tileView.getMeasuredHeight());
+ }
+ }
+
+ public boolean getAdapterEditingState() {
+ return mAdapterEditingState;
+ }
+
+ public void setAdapterEditingState(boolean editing) {
+ this.mAdapterEditingState = editing;
+ }
+
+ public boolean dualRecord(QSPanel.TileRecord record) {
+ return mPanel.mFirstRowLarge && record.row == 0 && mPage == 0;
+ }
+
+ private static int exactly(int size) {
+ return MeasureSpec.makeMeasureSpec(size, MeasureSpec.EXACTLY);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
index 94d5170..77ede93 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
@@ -25,6 +25,8 @@ import android.content.res.Configuration;
import android.content.res.Resources;
import android.os.Handler;
import android.os.Message;
+import android.os.UserHandle;
+import android.provider.Settings;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
@@ -45,40 +47,42 @@ import com.android.systemui.statusbar.policy.BrightnessMirrorController;
import java.util.ArrayList;
import java.util.Collection;
+import cyanogenmod.app.StatusBarPanelCustomTile;
+import cyanogenmod.providers.CMSettings;
+
/** View that represents the quick settings tile panel. **/
public class QSPanel extends ViewGroup {
- private static final float TILE_ASPECT = 1.2f;
-
- private final Context mContext;
- protected final ArrayList<TileRecord> mRecords = new ArrayList<TileRecord>();
- private final View mDetail;
- private final ViewGroup mDetailContent;
- private final TextView mDetailSettingsButton;
- private final TextView mDetailDoneButton;
- protected final View mBrightnessView;
- private final QSDetailClipper mClipper;
+ protected static final float TILE_ASPECT = 1.2f;
+
+ protected final ArrayList<TileRecord> mRecords = new ArrayList<>();
+ protected View mDetail;
+ protected ViewGroup mDetailContent;
+ protected TextView mDetailSettingsButton;
+ protected TextView mDetailDoneButton;
+ protected View mBrightnessView;
+ protected QSDetailClipper mClipper;
private final H mHandler = new H();
- private int mColumns;
- private int mCellWidth;
- private int mCellHeight;
- private int mLargeCellWidth;
- private int mLargeCellHeight;
- private int mPanelPaddingBottom;
- private int mDualTileUnderlap;
- private int mBrightnessPaddingTop;
- private int mGridHeight;
- private boolean mExpanded;
- private boolean mListening;
+ protected int mColumns;
+ protected int mCellWidth;
+ protected int mCellHeight;
+ protected int mLargeCellWidth;
+ protected int mLargeCellHeight;
+ protected int mPanelPaddingBottom;
+ protected int mDualTileUnderlap;
+ protected int mBrightnessPaddingTop;
+ protected int mGridHeight;
+ protected boolean mExpanded;
+ protected boolean mListening;
private boolean mClosingDetail;
- private Record mDetailRecord;
+ protected Record mDetailRecord;
private Callback mCallback;
- private BrightnessController mBrightnessController;
- private QSTileHost mHost;
+ protected BrightnessController mBrightnessController;
+ protected QSTileHost mHost;
- private QSFooter mFooter;
- private boolean mGridContentVisible = true;
+ protected QSFooter mFooter;
+ protected boolean mGridContentVisible = true;
public QSPanel(Context context) {
this(context, null);
@@ -87,17 +91,23 @@ public class QSPanel extends ViewGroup {
public QSPanel(Context context, AttributeSet attrs) {
super(context, attrs);
mContext = context;
+ setupViews();
+ }
- mDetail = LayoutInflater.from(context).inflate(R.layout.qs_detail, this, false);
+ /**
+ * THIS IS OVERRIDDEN in QSDragPanel
+ */
+ protected void setupViews() {
+ mDetail = LayoutInflater.from(mContext).inflate(R.layout.qs_detail, this, false);
mDetailContent = (ViewGroup) mDetail.findViewById(android.R.id.content);
mDetailSettingsButton = (TextView) mDetail.findViewById(android.R.id.button2);
mDetailDoneButton = (TextView) mDetail.findViewById(android.R.id.button1);
updateDetailText();
mDetail.setVisibility(GONE);
mDetail.setClickable(true);
- mBrightnessView = LayoutInflater.from(context).inflate(
+ mBrightnessView = LayoutInflater.from(mContext).inflate(
R.layout.quick_settings_brightness_dialog, this, false);
- mFooter = new QSFooter(this, context);
+ mFooter = new QSFooter(this, mContext);
addView(mDetail);
addView(mBrightnessView);
addView(mFooter.getView());
@@ -118,7 +128,26 @@ public class QSPanel extends ViewGroup {
});
}
- private void updateDetailText() {
+ /**
+ * Enable/disable brightness slider.
+ */
+ protected boolean showBrightnessSlider() {
+ boolean brightnessSliderEnabled = CMSettings.System.getIntForUser(
+ mContext.getContentResolver(), CMSettings.System.QS_SHOW_BRIGHTNESS_SLIDER,
+ 1, UserHandle.USER_CURRENT) == 1;
+ ToggleSlider brightnessSlider = (ToggleSlider) findViewById(R.id.brightness_slider);
+ if (brightnessSliderEnabled) {
+ mBrightnessView.setVisibility(VISIBLE);
+ brightnessSlider.setVisibility(VISIBLE);
+ } else {
+ mBrightnessView.setVisibility(GONE);
+ brightnessSlider.setVisibility(GONE);
+ }
+ updateResources();
+ return brightnessSliderEnabled;
+ }
+
+ protected void updateDetailText() {
mDetailDoneButton.setText(R.string.quick_settings_done);
mDetailSettingsButton.setText(R.string.quick_settings_more_settings);
}
@@ -206,7 +235,7 @@ public class QSPanel extends ViewGroup {
if (mListening) {
refreshAllTiles();
}
- if (listening) {
+ if (listening && showBrightnessSlider()) {
mBrightnessController.registerCallbacks();
} else {
mBrightnessController.unregisterCallbacks();
@@ -236,11 +265,11 @@ public class QSPanel extends ViewGroup {
showDetail(show, r);
}
- private void showDetail(boolean show, Record r) {
+ protected void showDetail(boolean show, Record r) {
mHandler.obtainMessage(H.SHOW_DETAIL, show ? 1 : 0, 0, r).sendToTarget();
}
- private void setTileVisibility(View v, int visibility) {
+ protected void setTileVisibility(View v, int visibility) {
mHandler.obtainMessage(H.SET_TILE_VISIBILITY, visibility, 0, v).sendToTarget();
}
@@ -252,6 +281,15 @@ public class QSPanel extends ViewGroup {
v.setVisibility(visibility);
}
+ protected void setTileEnabled(View v, boolean enabled) {
+ mHandler.obtainMessage(H.SET_TILE_ENABLED, enabled ? 1 : 0, 0, v).sendToTarget();
+ }
+
+ private void handleSetTileEnabled(View v, boolean enabled) {
+ if (enabled == v.isEnabled()) return;
+ v.setEnabled(enabled);
+ }
+
public void setTiles(Collection<QSTile<?>> tiles) {
for (TileRecord record : mRecords) {
removeView(record.tileView);
@@ -265,7 +303,7 @@ public class QSPanel extends ViewGroup {
}
}
- private void drawTile(TileRecord r, QSTile.State state) {
+ protected void drawTile(TileRecord r, QSTile.State state) {
final int visibility = state.visible ? VISIBLE : GONE;
setTileVisibility(r.tileView, visibility);
r.tileView.onStateChanged(state);
@@ -365,7 +403,7 @@ public class QSPanel extends ViewGroup {
}
}
- private void handleShowDetailTile(TileRecord r, boolean show) {
+ protected void handleShowDetailTile(TileRecord r, boolean show) {
if ((mDetailRecord != null) == show && mDetailRecord == r) return;
if (show) {
@@ -378,7 +416,7 @@ public class QSPanel extends ViewGroup {
handleShowDetailImpl(r, show, x, y);
}
- private void handleShowDetailImpl(Record r, boolean show, int x, int y) {
+ protected void handleShowDetailImpl(Record r, boolean show, int x, int y) {
boolean visibleDiff = (mDetailRecord != null) != show;
if (!visibleDiff && mDetailRecord == r) return; // already in right state
DetailAdapter detailAdapter = null;
@@ -425,7 +463,7 @@ public class QSPanel extends ViewGroup {
}
}
- private void setGridContentVisibility(boolean visible) {
+ protected void setGridContentVisibility(boolean visible) {
int newVis = visible ? VISIBLE : INVISIBLE;
for (int i = 0; i < mRecords.size(); i++) {
TileRecord tileRecord = mRecords.get(i);
@@ -433,7 +471,7 @@ public class QSPanel extends ViewGroup {
tileRecord.tileView.setVisibility(newVis);
}
}
- mBrightnessView.setVisibility(newVis);
+ mBrightnessView.setVisibility(showBrightnessSlider() ? newVis : GONE);
if (mGridContentVisible != visible) {
MetricsLogger.visibility(mContext, MetricsLogger.QS_PANEL, newVis);
}
@@ -458,15 +496,12 @@ public class QSPanel extends ViewGroup {
int r = -1;
int c = -1;
int rows = 0;
- boolean rowIsDual = false;
for (TileRecord record : mRecords) {
if (record.tileView.getVisibility() == GONE) continue;
// wrap to next column if we've reached the max # of columns
- // also don't allow dual + single tiles on the same row
- if (r == -1 || c == (mColumns - 1) || rowIsDual != record.tile.supportsDualTargets()) {
+ if (r == -1 || c == (mColumns - 1)) {
r++;
c = 0;
- rowIsDual = record.tile.supportsDualTargets();
} else {
c++;
}
@@ -477,7 +512,8 @@ public class QSPanel extends ViewGroup {
View previousView = mBrightnessView;
for (TileRecord record : mRecords) {
- if (record.tileView.setDual(record.tile.supportsDualTargets())) {
+ final boolean dualTarget = record.tile.hasDualTargetsDetails();
+ if (record.tileView.setDual(dualTarget, dualTarget)) {
record.tileView.handleStateChanged(record.tile.getState());
}
if (record.tileView.getVisibility() == GONE) continue;
@@ -535,7 +571,7 @@ public class QSPanel extends ViewGroup {
}
}
- private int getRowTop(int row) {
+ protected int getRowTop(int row) {
if (row <= 0) return mBrightnessView.getMeasuredHeight() + mBrightnessPaddingTop;
return mBrightnessView.getMeasuredHeight() + mBrightnessPaddingTop
+ mLargeCellHeight - mDualTileUnderlap + (row - 1) * mCellHeight;
@@ -556,13 +592,13 @@ public class QSPanel extends ViewGroup {
}
}
- private void fireToggleStateChanged(boolean state) {
+ protected void fireToggleStateChanged(boolean state) {
if (mCallback != null) {
mCallback.onToggleStateChanged(state);
}
}
- private void fireScanStateChanged(boolean state) {
+ protected void fireScanStateChanged(boolean state) {
if (mCallback != null) {
mCallback.onScanStateChanged(state);
}
@@ -579,24 +615,27 @@ public class QSPanel extends ViewGroup {
private class H extends Handler {
private static final int SHOW_DETAIL = 1;
private static final int SET_TILE_VISIBILITY = 2;
+ private static final int SET_TILE_ENABLED = 3;
@Override
public void handleMessage(Message msg) {
if (msg.what == SHOW_DETAIL) {
handleShowDetail((Record)msg.obj, msg.arg1 != 0);
} else if (msg.what == SET_TILE_VISIBILITY) {
handleSetTileVisibility((View)msg.obj, msg.arg1);
+ } else if (msg.what == SET_TILE_ENABLED) {
+ handleSetTileEnabled((View)msg.obj, msg.arg1 == 1);
}
}
}
- private static class Record {
+ protected static class Record {
View detailView;
DetailAdapter detailAdapter;
int x;
int y;
}
- protected static final class TileRecord extends Record {
+ protected static class TileRecord extends Record {
public QSTile<?> tile;
public QSTileView tileView;
public int row;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanelTopView.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanelTopView.java
new file mode 100644
index 0000000..b00483c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanelTopView.java
@@ -0,0 +1,377 @@
+/*
+ * Copyright (C) 2015 The CyanogenMod 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;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
+import android.annotation.Nullable;
+import android.app.ActivityManager;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.graphics.PorterDuff;
+import android.graphics.drawable.Animatable;
+import android.graphics.drawable.Drawable;
+import android.os.Handler;
+import android.os.UserHandle;
+import android.support.v4.graphics.drawable.DrawableCompat;
+import android.support.v4.view.animation.FastOutSlowInInterpolator;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.View;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
+import android.widget.TextView;
+import com.android.systemui.R;
+import com.android.systemui.cm.UserContentObserver;
+import com.android.systemui.settings.ToggleSlider;
+
+import cyanogenmod.providers.CMSettings;
+
+public class QSPanelTopView extends FrameLayout {
+
+ private static final String TAG = "QSPanelTopView";
+
+ public static final int TOAST_DURATION = 2000;
+
+ protected View mEditTileInstructionView;
+ protected View mDropTarget;
+ protected View mBrightnessView;
+ protected TextView mToastView;
+ protected View mAddTarget;
+ protected TextView mEditInstructionText;
+
+ private boolean mEditing = false;
+ private boolean mDisplayingInstructions = false;
+ private boolean mDisplayingTrash = false;
+ private boolean mDisplayingToast = false;
+ public boolean mHasBrightnessSliderToDisplay = true;
+
+ private AnimatorSet mAnimator;
+ private ImageView mDropTargetIcon;
+
+ private SettingsObserver mSettingsObserver;
+ private boolean mListening;
+ private boolean mSkipAnimations;
+
+ public QSPanelTopView(Context context, @Nullable AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public QSPanelTopView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ public QSPanelTopView(Context context, @Nullable AttributeSet attrs, int defStyleAttr,
+ int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ setFocusable(true);
+ mSettingsObserver = new SettingsObserver(new Handler());
+ }
+
+ @Override
+ public boolean hasOverlappingRendering() {
+ return mEditing;
+ }
+
+ public View getDropTarget() {
+ return mDropTarget;
+ }
+
+ public ImageView getDropTargetIcon() {
+ return mDropTargetIcon;
+ }
+
+ public View getBrightnessView() {
+ return mBrightnessView;
+ }
+
+ public View getAddTarget() {
+ return mAddTarget;
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+ mDropTarget = findViewById(R.id.delete_container);
+ mDropTargetIcon = (ImageView) findViewById(R.id.delete_target);
+ mEditTileInstructionView = findViewById(R.id.edit_container);
+ mBrightnessView = findViewById(R.id.brightness_container);
+ mToastView = (TextView) findViewById(R.id.qs_toast);
+ mAddTarget = findViewById(R.id.add_target);
+ mEditInstructionText = (TextView) findViewById(R.id.edit_text_instruction);
+ updateResources();
+ }
+
+ public void updateResources() {
+ if (mEditInstructionText != null) {
+ mEditInstructionText.setText(R.string.qs_tile_edit_header_instruction);
+ }
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ final int width = MeasureSpec.getSize(widthMeasureSpec);
+ mBrightnessView.measure(QSDragPanel.exactly(width), MeasureSpec.UNSPECIFIED);
+ mEditTileInstructionView.measure(QSDragPanel.exactly(width), MeasureSpec.UNSPECIFIED);
+ mToastView.measure(QSDragPanel.exactly(width), MeasureSpec.UNSPECIFIED);
+
+ // if we are showing a brightness slider, always fit to that, otherwise only
+ // declare a height when editing.
+ int dh = mHasBrightnessSliderToDisplay ? mBrightnessView.getMeasuredHeight()
+ : mEditing ? mEditTileInstructionView.getMeasuredHeight() : 0;
+
+ mDropTarget.measure(QSDragPanel.exactly(width), QSDragPanel.atMost(dh));
+ mEditTileInstructionView.measure(QSDragPanel.exactly(width), QSDragPanel.atMost(dh));
+ mToastView.measure(QSDragPanel.exactly(width), QSDragPanel.atMost(dh));
+
+ setMeasuredDimension(width, dh);
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+ boolean animateToState = !isLaidOut();
+ super.onLayout(changed, left, top, right, bottom);
+ if (animateToState) {
+ goToState();
+ }
+ }
+
+ public void setEditing(boolean editing, boolean skipAnim) {
+ mEditing = editing;
+ if (editing) {
+ mDisplayingInstructions = true;
+ mDisplayingTrash = false;
+ } else {
+ mDisplayingInstructions = false;
+ mDisplayingTrash = false;
+ }
+ if (skipAnim) {
+ goToState();
+ } else {
+ animateToState();
+ }
+ }
+
+ public void onStopDrag() {
+ mDisplayingTrash = false;
+ animateToState();
+ }
+
+ public void onStartDrag() {
+ mDisplayingTrash = true;
+ animateToState();
+ }
+
+ public void setDropIcon(int resourceId, int colorResourceId) {
+ mDropTargetIcon.setImageResource(resourceId);
+ final Drawable drawable = mDropTargetIcon.getDrawable();
+
+ DrawableCompat.setTintMode(drawable, PorterDuff.Mode.SRC_ATOP);
+ DrawableCompat.setTint(drawable, mContext.getColor(colorResourceId));
+
+ if (drawable instanceof Animatable) {
+ ((Animatable) drawable).start();
+ }
+ }
+
+ public void toast(int textStrResId) {
+ mDisplayingToast = true;
+ mToastView.setText(textStrResId);
+ animateToState();
+ }
+
+ private Runnable mAnimateRunnable = new Runnable() {
+ @Override
+ public void run() {
+ if (mAnimator != null) {
+ mAnimator.cancel();
+ }
+ mAnimator = new AnimatorSet();
+
+ final boolean showToast = mDisplayingToast;
+ final boolean showTrash = mDisplayingTrash && !mDisplayingToast;
+ final boolean showBrightness = !mEditing && !mDisplayingToast;
+ final boolean showInstructions = mEditing
+ && mDisplayingInstructions
+ && !mDisplayingTrash
+ && !mDisplayingToast;
+
+ /*Log.d(TAG, "animating to state: "
+ + " showBrightness: " + showBrightness
+ + " showInstructions: " + showInstructions
+ + " showTrash: " + showTrash
+ + " showToast: " + showToast
+ );*/
+
+ final Animator brightnessAnimator = showBrightnessSlider(showBrightness);
+ final Animator instructionAnimator = showInstructions(showInstructions);
+ final Animator trashAnimator = showTrash(showTrash);
+ final Animator toastAnimator = showToast(showToast);
+
+ mAnimator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationStart(Animator animation) {
+ // if the view is already visible, keep it visible on animation start
+ // to animate it out, otherwise set it as invisible (to not affect view height)
+ mEditTileInstructionView.setVisibility(
+ getVisibilityForAnimation(mEditTileInstructionView, showInstructions));
+ mDropTarget.setVisibility(
+ getVisibilityForAnimation(mDropTarget, showTrash));
+ mToastView.setVisibility(
+ getVisibilityForAnimation(mToastView, showToast));
+ if (mHasBrightnessSliderToDisplay) {
+ mBrightnessView.setVisibility(
+ getVisibilityForAnimation(mBrightnessView, showBrightness));
+ }
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mToastView.setVisibility(showToast ? View.VISIBLE : View.GONE);
+ mEditTileInstructionView.setVisibility(showInstructions
+ ? View.VISIBLE : View.GONE);
+ mDropTarget.setVisibility(showTrash ? View.VISIBLE : View.GONE);
+ if (mHasBrightnessSliderToDisplay) {
+ mBrightnessView.setVisibility(showBrightness ? View.VISIBLE : View.GONE);
+ }
+
+ mAnimator = null;
+
+ requestLayout();
+
+ if (showToast) {
+ mToastView.bringToFront();
+ mToastView.postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ mDisplayingToast = false;
+ animateToState();
+ }
+ }, TOAST_DURATION);
+ }
+ }
+ });
+
+ mAnimator.setDuration(mSkipAnimations ? 0 : 500);
+ mAnimator.setInterpolator(new FastOutSlowInInterpolator());
+ mAnimator.setStartDelay(mSkipAnimations ? 0 : 100);
+ mAnimator.playTogether(instructionAnimator, trashAnimator,
+ brightnessAnimator, toastAnimator);
+ mAnimator.start();
+ }
+ };
+
+ private int getVisibilityForAnimation(View view, boolean show) {
+ if (show || view.getVisibility() != View.GONE) {
+ return View.VISIBLE;
+ }
+ return View.INVISIBLE;
+ }
+
+ private void animateToState() {
+ mSkipAnimations = false;
+ post(mAnimateRunnable);
+ }
+
+ private void goToState() {
+ mSkipAnimations = true;
+ post(mAnimateRunnable);
+ }
+
+ private Animator animateView(View v, boolean show) {
+ return ObjectAnimator.ofFloat(v, "translationY", show ? 0 : -getMeasuredHeight());
+ }
+
+ private Animator showBrightnessSlider(boolean show) {
+ return animateView(mBrightnessView, show);
+ }
+
+ private Animator showInstructions(boolean show) {
+ return animateView(mEditTileInstructionView, show);
+ }
+
+ private Animator showTrash(boolean show) {
+ return animateView(mDropTarget, show);
+ }
+
+ private Animator showToast(boolean show) {
+ return animateView(mToastView, show);
+ }
+
+ public void setListening(boolean listening) {
+ if (mListening == listening) return;
+ mListening = listening;
+ if (mListening) {
+ mSettingsObserver.observe();
+ } else {
+ mSettingsObserver.unobserve();
+ }
+
+ }
+
+ class SettingsObserver extends UserContentObserver {
+ SettingsObserver(Handler handler) {
+ super(handler);
+ }
+
+ @Override
+ protected void observe() {
+ super.observe();
+
+ ContentResolver resolver = mContext.getContentResolver();
+ resolver.registerContentObserver(CMSettings.System.getUriFor(
+ CMSettings.System.QS_SHOW_BRIGHTNESS_SLIDER), false, this, UserHandle.USER_ALL);
+ update();
+ }
+
+ @Override
+ protected void unobserve() {
+ super.unobserve();
+
+ ContentResolver resolver = mContext.getContentResolver();
+ resolver.unregisterContentObserver(this);
+ }
+
+ @Override
+ public void update() {
+ ContentResolver resolver = mContext.getContentResolver();
+ int currentUserId = ActivityManager.getCurrentUser();
+ boolean showSlider = CMSettings.System.getIntForUser(resolver,
+ CMSettings.System.QS_SHOW_BRIGHTNESS_SLIDER, 1, currentUserId) == 1;
+ if (showSlider != mHasBrightnessSliderToDisplay) {
+ if (mAnimator != null) {
+ mAnimator.cancel(); // cancel everything we're animating
+ mAnimator = null;
+ }
+ mHasBrightnessSliderToDisplay = showSlider;
+ if (mBrightnessView != null) {
+ mBrightnessView.setVisibility(showSlider ? View.VISIBLE : View.GONE);
+
+ // as per showBrightnessSlider() in QSPanel.java, we look it up on-the-go
+ ToggleSlider brightnessSlider = (ToggleSlider) findViewById(R.id.brightness_slider);
+ if (brightnessSlider != null) {
+ brightnessSlider.setVisibility(showSlider ? View.VISIBLE : View.GONE);
+ }
+
+ }
+ getParent().requestLayout();
+ animateToState();
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSSettings.java b/packages/SystemUI/src/com/android/systemui/qs/QSSettings.java
new file mode 100644
index 0000000..0a2b937
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSSettings.java
@@ -0,0 +1,141 @@
+/*
+ * Copyright (C) 2015 The CyanogenMod 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;
+
+import android.Manifest;
+import android.annotation.Nullable;
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.ResultReceiver;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.CompoundButton;
+import android.widget.ScrollView;
+
+import com.android.systemui.R;
+import com.android.systemui.statusbar.phone.QSTileHost;
+import com.android.systemui.statusbar.phone.SystemUIDialog;
+
+public class QSSettings extends ScrollView {
+
+ private static final String RESULT_RECEIVER_EXTRA = "result_receiver";
+ private static final String LOCK_CLOCK_PACKAGENAME = "com.cyanogenmod.lockclock";
+ private static final String LOCK_CLOCK_PERM_CLASS = LOCK_CLOCK_PACKAGENAME
+ + ".weather.PermissionRequestActivity";
+
+ private QSTileHost mHost;
+
+ private boolean mAdapterEditingState;
+ private QSBooleanSettingRow mShowWeather;
+ private ResultReceiver mResultReceiver;
+
+ public QSSettings(Context context, @Nullable AttributeSet attrs) {
+ super(context, attrs);
+ setFillViewport(true);
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+
+ findViewById(R.id.reset_tiles).setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ initiateTileReset();
+ }
+ });
+
+ mShowWeather = (QSBooleanSettingRow) findViewById(R.id.show_weather);
+ mShowWeather.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
+ @Override
+ public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
+ if (isChecked) {
+ PackageManager packageManager = getContext().getPackageManager();
+ if (packageManager.checkPermission(Manifest.permission.ACCESS_COARSE_LOCATION,
+ LOCK_CLOCK_PACKAGENAME) != PackageManager.PERMISSION_GRANTED) {
+ mShowWeather.setChecked(false);
+ requestPermission();
+ mHost.collapsePanels();
+ }
+ }
+ }
+ });
+ }
+
+ public Parcelable getResultReceiverForSending() {
+ if (mResultReceiver == null) {
+ mResultReceiver = new ResultReceiver(new Handler()) {
+ @Override
+ protected void onReceiveResult(int resultCode, Bundle resultData) {
+ super.onReceiveResult(resultCode, resultData);
+ if (resultCode == Activity.RESULT_OK) {
+ mShowWeather.setChecked(true);
+ }
+ mResultReceiver = null;
+ }
+ };
+ }
+ Parcel parcel = Parcel.obtain();
+ mResultReceiver.writeToParcel(parcel, 0);
+ parcel.setDataPosition(0);
+ ResultReceiver receiverForSending = ResultReceiver.CREATOR.createFromParcel(parcel);
+ parcel.recycle();
+ return receiverForSending;
+ }
+
+ private void requestPermission() {
+ Intent i = new Intent();
+ i.setClassName(LOCK_CLOCK_PACKAGENAME, LOCK_CLOCK_PERM_CLASS);
+ i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ i.putExtra(RESULT_RECEIVER_EXTRA, getResultReceiverForSending());
+ getContext().startActivity(i);
+ }
+
+ private void initiateTileReset() {
+ final AlertDialog d = new AlertDialog.Builder(mContext)
+ .setMessage(R.string.qs_tiles_reset_confirmation)
+ .setNegativeButton(R.string.cancel, null)
+ .setPositiveButton(com.android.internal.R.string.reset,
+ new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ mHost.initiateReset();
+ }
+ }).create();
+ SystemUIDialog.makeSystemUIDialog(d);
+ d.show();
+ }
+
+ public void setHost(QSTileHost host) {
+ mHost = host;
+ }
+
+ public boolean getAdapterEditingState() {
+ return mAdapterEditingState;
+ }
+
+ public void setAdapterEditingState(boolean editing) {
+ this.mAdapterEditingState = editing;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTile.java b/packages/SystemUI/src/com/android/systemui/qs/QSTile.java
index e4a37fb..01a170f 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSTile.java
@@ -20,7 +20,9 @@ import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.graphics.drawable.Animatable;
+import android.graphics.Bitmap;
import android.graphics.drawable.AnimatedVectorDrawable;
+import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.os.Handler;
import android.os.Looper;
@@ -30,7 +32,9 @@ import android.util.SparseArray;
import android.view.View;
import android.view.ViewGroup;
+import android.widget.RemoteViews;
import com.android.systemui.qs.QSTile.State;
+import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.statusbar.policy.BluetoothController;
import com.android.systemui.statusbar.policy.CastController;
import com.android.systemui.statusbar.policy.FlashlightController;
@@ -41,6 +45,7 @@ import com.android.systemui.statusbar.policy.LocationController;
import com.android.systemui.statusbar.policy.NetworkController;
import com.android.systemui.statusbar.policy.RotationLockController;
import com.android.systemui.statusbar.policy.ZenModeController;
+import cyanogenmod.app.StatusBarPanelCustomTile;
import java.util.Collection;
import java.util.Objects;
@@ -85,7 +90,7 @@ public abstract class QSTile<TState extends State> implements Listenable {
mHandler = new H(host.getLooper());
}
- public boolean supportsDualTargets() {
+ public boolean hasDualTargetsDetails() {
return false;
}
@@ -106,6 +111,7 @@ public abstract class QSTile<TState extends State> implements Listenable {
Boolean getToggleState();
View createDetailView(Context context, View convertView, ViewGroup parent);
Intent getSettingsIntent();
+ StatusBarPanelCustomTile getCustomTile();
void setToggleState(boolean state);
int getMetricsCategory();
}
@@ -320,10 +326,12 @@ public abstract class QSTile<TState extends State> implements Listenable {
}
public interface Host {
+ void removeCustomTile(StatusBarPanelCustomTile customTile);
void startActivityDismissingKeyguard(Intent intent);
void startActivityDismissingKeyguard(PendingIntent intent);
void warn(String message, Throwable t);
void collapsePanels();
+ RemoteViews.OnClickHandler getOnClickHandler();
Looper getLooper();
Context getContext();
Collection<QSTile<?>> getTiles();
@@ -337,9 +345,18 @@ public abstract class QSTile<TState extends State> implements Listenable {
CastController getCastController();
FlashlightController getFlashlightController();
KeyguardMonitor getKeyguardMonitor();
+ BatteryController getBatteryController();
+ boolean isEditing();
+ void setEditing(boolean editing);
+ void resetTiles();
+ void goToSettingsPage();
public interface Callback {
void onTilesChanged();
+ void setEditing(boolean editing);
+ boolean isEditing();
+ void goToSettingsPage();
+ void resetTiles();
}
}
@@ -352,6 +369,42 @@ public abstract class QSTile<TState extends State> implements Listenable {
}
}
+ protected class ExternalIcon extends AnimationIcon {
+ private Context mPackageContext;
+ private String mPkg;
+ private int mResId;
+
+ public ExternalIcon(String pkg, int resId) {
+ super(resId);
+ mPkg = pkg;
+ mResId = resId;
+ }
+
+ @Override
+ public Drawable getDrawable(Context context) {
+ // Get the drawable from the package context
+ Drawable d = null;
+ try {
+ d = super.getDrawable(getPackageContext());
+ } catch (Throwable t) {
+ Log.w(TAG, "Error creating package context" + mPkg + " id=" + mResId, t);
+ }
+ return d;
+ }
+
+ private Context getPackageContext() {
+ if (mPackageContext == null) {
+ try {
+ mPackageContext = mContext.createPackageContext(mPkg, 0);
+ } catch (Throwable t) {
+ Log.w(TAG, "Error creating package context" + mPkg, t);
+ return null;
+ }
+ }
+ return mPackageContext;
+ }
+ }
+
public static class ResourceIcon extends Icon {
private static final SparseArray<Icon> ICONS = new SparseArray<Icon>();
@@ -390,6 +443,21 @@ public abstract class QSTile<TState extends State> implements Listenable {
}
}
+ protected class ExternalBitmapIcon extends Icon {
+ private Bitmap mBitmap;
+
+ public ExternalBitmapIcon(Bitmap bitmap) {
+ mBitmap = bitmap;
+ }
+
+ @Override
+ public Drawable getDrawable(Context context) {
+ // This is gross
+ BitmapDrawable bitmapDrawable = new BitmapDrawable(context.getResources(), mBitmap);
+ return bitmapDrawable;
+ }
+ }
+
protected class AnimationIcon extends ResourceIcon {
private boolean mAllowAnimation;
@@ -404,13 +472,14 @@ public abstract class QSTile<TState extends State> implements Listenable {
@Override
public Drawable getDrawable(Context context) {
// workaround: get a clean state for every new AVD
- final AnimatedVectorDrawable d = (AnimatedVectorDrawable) context.getDrawable(mResId)
- .getConstantState().newDrawable();
- d.start();
- if (mAllowAnimation) {
- mAllowAnimation = false;
- } else {
- d.stop(); // skip directly to end state
+ final Drawable d = super.getDrawable(context).getConstantState().newDrawable();
+ if (d instanceof AnimatedVectorDrawable) {
+ ((AnimatedVectorDrawable)d).start();
+ if (mAllowAnimation) {
+ mAllowAnimation = false;
+ } else {
+ ((AnimatedVectorDrawable)d).stop(); // skip directly to end state
+ }
}
return d;
}
@@ -431,6 +500,7 @@ public abstract class QSTile<TState extends State> implements Listenable {
public static class State {
public boolean visible;
+ public boolean enabled = true;
public Icon icon;
public String label;
public String contentDescription;
@@ -441,6 +511,7 @@ public abstract class QSTile<TState extends State> implements Listenable {
if (other == null) throw new IllegalArgumentException();
if (!other.getClass().equals(getClass())) throw new IllegalArgumentException();
final boolean changed = other.visible != visible
+ || !Objects.equals(other.enabled, enabled)
|| !Objects.equals(other.icon, icon)
|| !Objects.equals(other.label, label)
|| !Objects.equals(other.contentDescription, contentDescription)
@@ -448,6 +519,7 @@ public abstract class QSTile<TState extends State> implements Listenable {
|| !Objects.equals(other.dualLabelContentDescription,
dualLabelContentDescription);
other.visible = visible;
+ other.enabled = enabled;
other.icon = icon;
other.label = label;
other.contentDescription = contentDescription;
@@ -464,6 +536,7 @@ public abstract class QSTile<TState extends State> implements Listenable {
protected StringBuilder toStringBuilder() {
final StringBuilder sb = new StringBuilder(getClass().getSimpleName()).append('[');
sb.append("visible=").append(visible);
+ sb.append(",enabled=").append(enabled);
sb.append(",icon=").append(icon);
sb.append(",label=").append(label);
sb.append(",contentDescription=").append(contentDescription);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTileView.java b/packages/SystemUI/src/com/android/systemui/qs/QSTileView.java
index 6d26a3b..7c63782 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSTileView.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSTileView.java
@@ -1,5 +1,6 @@
/*
* Copyright (C) 2014 The Android Open Source Project
+ * Copyright (C) 2015 The CyanogenMod Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,10 +17,13 @@
package com.android.systemui.qs;
+import android.annotation.Nullable;
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.content.res.TypedArray;
+import android.graphics.Color;
+import android.graphics.PorterDuff;
import android.graphics.Typeface;
import android.graphics.drawable.Animatable;
import android.graphics.drawable.Drawable;
@@ -27,6 +31,7 @@ import android.graphics.drawable.RippleDrawable;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
+import android.util.Log;
import android.util.MathUtils;
import android.util.TypedValue;
import android.view.Gravity;
@@ -48,6 +53,8 @@ public class QSTileView extends ViewGroup {
private static final Typeface CONDENSED = Typeface.create("sans-serif-condensed",
Typeface.NORMAL);
+ private static final String TAG = "QSTileView";
+
protected final Context mContext;
private final View mIcon;
private final View mDivider;
@@ -62,6 +69,7 @@ public class QSTileView extends ViewGroup {
private TextView mLabel;
private QSDualTileLabel mDualLabel;
private boolean mDual;
+ private boolean mDualDetails;
private OnClickListener mClickPrimary;
private OnClickListener mClickSecondary;
private OnLongClickListener mLongClick;
@@ -124,31 +132,34 @@ public class QSTileView extends ViewGroup {
private void recreateLabel() {
CharSequence labelText = null;
CharSequence labelDescription = null;
- if (mLabel != null) {
+ if (mLabel != null && mLabel.isAttachedToWindow()) {
labelText = mLabel.getText();
removeView(mLabel);
- mLabel = null;
}
- if (mDualLabel != null) {
+ if (mDualLabel != null && mDualLabel.isAttachedToWindow()) {
labelText = mDualLabel.getText();
- labelDescription = mLabel.getContentDescription();
+ labelDescription = mDualLabel.getContentDescription();
removeView(mDualLabel);
- mDualLabel = null;
}
final Resources res = mContext.getResources();
if (mDual) {
- mDualLabel = new QSDualTileLabel(mContext);
- mDualLabel.setId(View.generateViewId());
- mDualLabel.setBackgroundResource(R.drawable.btn_borderless_rect);
- mDualLabel.setFirstLineCaret(mContext.getDrawable(R.drawable.qs_dual_tile_caret));
- mDualLabel.setTextColor(mContext.getColor(R.color.qs_tile_text));
- mDualLabel.setPadding(0, mDualTileVerticalPaddingPx, 0, mDualTileVerticalPaddingPx);
- mDualLabel.setTypeface(CONDENSED);
- mDualLabel.setTextSize(TypedValue.COMPLEX_UNIT_PX,
- res.getDimensionPixelSize(R.dimen.qs_tile_text_size));
- mDualLabel.setClickable(true);
- mDualLabel.setOnClickListener(mClickSecondary);
- mDualLabel.setFocusable(true);
+ if (mDualLabel == null) {
+ mDualLabel = new QSDualTileLabel(mContext);
+ mDualLabel.setId(View.generateViewId());
+ mDualLabel.setBackgroundResource(R.drawable.btn_borderless_rect);
+ if (mDualDetails) {
+ mDualLabel.setFirstLineCaret(mContext.getDrawable(R.drawable.qs_dual_tile_caret));
+ }
+ mDualLabel.setTextColor(mContext.getColor(R.color.qs_tile_text));
+ mDualLabel.setPadding(0, mDualTileVerticalPaddingPx, 0, mDualTileVerticalPaddingPx);
+ mDualLabel.setTypeface(CONDENSED);
+ mDualLabel.setTextSize(TypedValue.COMPLEX_UNIT_PX,
+ res.getDimensionPixelSize(R.dimen.qs_tile_text_size));
+ mDualLabel.setClickable(true);
+ mDualLabel.setFocusable(true);
+ mDualLabel.setOnClickListener(mDualDetails ? mClickSecondary : mClickPrimary);
+ mDualLabel.setOnLongClickListener(mLongClick);
+ }
if (labelText != null) {
mDualLabel.setText(labelText);
}
@@ -158,15 +169,18 @@ public class QSTileView extends ViewGroup {
addView(mDualLabel);
mDualLabel.setAccessibilityTraversalAfter(mTopBackgroundView.getId());
} else {
- mLabel = new TextView(mContext);
- mLabel.setTextColor(mContext.getColor(R.color.qs_tile_text));
- mLabel.setGravity(Gravity.CENTER_HORIZONTAL);
- mLabel.setMinLines(2);
- mLabel.setPadding(0, 0, 0, 0);
- mLabel.setTypeface(CONDENSED);
- mLabel.setTextSize(TypedValue.COMPLEX_UNIT_PX,
- res.getDimensionPixelSize(R.dimen.qs_tile_text_size));
- mLabel.setClickable(false);
+ if (mLabel == null) {
+ mLabel = new TextView(mContext);
+ mLabel.setTextColor(mContext.getColor(R.color.qs_tile_text));
+ mLabel.setGravity(Gravity.CENTER_HORIZONTAL);
+ mLabel.setMinLines(2);
+ mLabel.setPadding(0, 0, 0, 0);
+ mLabel.setTypeface(CONDENSED);
+ mLabel.setTextSize(TypedValue.COMPLEX_UNIT_PX,
+ res.getDimensionPixelSize(R.dimen.qs_tile_text_size));
+ mLabel.setClickable(false);
+ mLabel.setFocusable(false);
+ }
if (labelText != null) {
mLabel.setText(labelText);
}
@@ -174,39 +188,59 @@ public class QSTileView extends ViewGroup {
}
}
- public boolean setDual(boolean dual) {
+ public boolean isDual() {
+ return mDual;
+ }
+
+ public boolean setDual(boolean dual, boolean hasDetails) {
final boolean changed = dual != mDual;
mDual = dual;
+ mDualDetails = hasDetails;
if (changed) {
recreateLabel();
}
- if (mTileBackground instanceof RippleDrawable) {
- setRipple((RippleDrawable) mTileBackground);
- }
+
if (dual) {
mTopBackgroundView.setOnClickListener(mClickPrimary);
+ mTopBackgroundView.setOnLongClickListener(mLongClick);
setOnClickListener(null);
- setClickable(false);
+ setOnLongClickListener(null);
setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO);
- mTopBackgroundView.setBackground(mTileBackground);
} else {
mTopBackgroundView.setOnClickListener(null);
- mTopBackgroundView.setClickable(false);
+ mTopBackgroundView.setOnLongClickListener(null);
setOnClickListener(mClickPrimary);
setOnLongClickListener(mLongClick);
setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES);
- setBackground(mTileBackground);
}
+ setTileBackground();
+ mTopBackgroundView.setClickable(dual);
mTopBackgroundView.setFocusable(dual);
+ setClickable(!dual);
setFocusable(!dual);
mDivider.setVisibility(dual ? VISIBLE : GONE);
+ mTopBackgroundView.setVisibility(dual ? VISIBLE : GONE);
+
+ if (changed) {
+ getParent().requestLayout();
+ }
postInvalidate();
return changed;
}
+ protected void setTileBackground() {
+ if (mTileBackground instanceof RippleDrawable) {
+ setRipple((RippleDrawable) mTileBackground);
+ } else {
+ setRipple(null);
+ }
+ mTopBackgroundView.setBackground(mDual ? mTileBackground : null);
+ setBackground(!mDual ? mTileBackground : null);
+ }
+
private void setRipple(RippleDrawable tileBackground) {
mRipple = tileBackground;
- if (getWidth() != 0) {
+ if (getWidth() != 0 && mRipple != null) {
updateRippleSize(getWidth(), getHeight());
}
}
@@ -225,7 +259,7 @@ public class QSTileView extends ViewGroup {
return icon;
}
- private Drawable newTileBackground() {
+ public Drawable newTileBackground() {
final int[] attrs = new int[] { android.R.attr.selectableItemBackgroundBorderless };
final TypedArray ta = mContext.obtainStyledAttributes(attrs);
final Drawable d = ta.getDrawable(0);
@@ -285,7 +319,7 @@ public class QSTileView extends ViewGroup {
private void updateRippleSize(int width, int height) {
// center the touch feedback on the center of the icon, and dial it down a bit
final int cx = width / 2;
- final int cy = mDual ? mIcon.getTop() + mIcon.getHeight() / 2 : height / 2;
+ final int cy = mDual ? mIcon.getTop() + mIcon.getHeight() : height / 2;
final int rad = (int)(mIcon.getHeight() * 1.25f);
mRipple.setHotspotBounds(cx - rad, cy - rad, cx + rad, cy + rad);
}
@@ -302,9 +336,20 @@ public class QSTileView extends ViewGroup {
mDualLabel.setText(state.label);
mDualLabel.setContentDescription(state.dualLabelContentDescription);
mTopBackgroundView.setContentDescription(state.contentDescription);
+ if (!Objects.equals(state.enabled, mDualLabel.isEnabled())) {
+ mTopBackgroundView.setEnabled(state.enabled);
+ mDualLabel.setEnabled(state.enabled);
+ mDualLabel.setTextColor(mContext.getResources().getColor(state.enabled ?
+ R.color.qs_tile_text : R.color.qs_tile_text_disabled));
+ }
} else {
mLabel.setText(state.label);
setContentDescription(state.contentDescription);
+ if (!Objects.equals(state.enabled, mLabel.isEnabled())) {
+ mLabel.setEnabled(state.enabled);
+ mLabel.setTextColor(mContext.getResources().getColor(state.enabled ?
+ R.color.qs_tile_text : R.color.qs_tile_text_disabled));
+ }
}
}
@@ -323,6 +368,14 @@ public class QSTileView extends ViewGroup {
}
}
}
+ if (!Objects.equals(state.enabled, iv.isEnabled())) {
+ iv.setEnabled(state.enabled);
+ if (state.enabled) {
+ iv.setColorFilter(null);
+ } else {
+ iv.setColorFilter(Color.GRAY, PorterDuff.Mode.MULTIPLY);
+ }
+ }
}
public void onStateChanged(QSTile.State state) {
@@ -349,6 +402,37 @@ public class QSTileView extends ViewGroup {
return lastView;
}
+ public void setEditing(boolean editing) {
+ if (mDual) {
+ if (mTopBackgroundView != null) {
+ mTopBackgroundView.setFocusable(!editing);
+ mTopBackgroundView.setClickable(!editing);
+ }
+ if (mDualLabel != null) {
+ mDualLabel.setFocusable(!editing);
+ mDualLabel.setClickable(!editing);
+ }
+ setClickable(editing);
+ setFocusable(editing);
+ } else {
+ if (mLabel != null) {
+ mLabel.setFocusable(!editing);
+ }
+ if (mRipple != null) {
+ mRipple.setVisible(!editing, false);
+ }
+ }
+
+ // clean up extra label view if needed
+ if (!editing) {
+ if (mDual && mLabel != null) {
+ mLabel = null;
+ } else if (!mDual && mDualLabel != null) {
+ mDualLabel = null;
+ }
+ }
+ }
+
private class H extends Handler {
private static final int STATE_CHANGED = 1;
public H() {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSViewPager.java b/packages/SystemUI/src/com/android/systemui/qs/QSViewPager.java
new file mode 100644
index 0000000..3dc5d27
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSViewPager.java
@@ -0,0 +1,105 @@
+package com.android.systemui.qs;
+
+import android.animation.Animator;
+import android.animation.ValueAnimator;
+import android.content.Context;
+import android.support.v4.view.ViewPager;
+import android.util.Log;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.animation.AccelerateInterpolator;
+
+public class QSViewPager extends ViewPager {
+
+ private static final String TAG = "QSViewPager";
+
+ protected static final float SCROLL_PERCENT = .10f;
+
+ QSDragPanel mDragPanel;
+
+ public QSViewPager(Context context) {
+ super(context);
+ }
+
+ public void setDragPanel(QSDragPanel p) {
+ mDragPanel = p;
+ }
+
+ @Override
+ public boolean hasOverlappingRendering() {
+ return mDragPanel.isEditing();
+ }
+
+ @Override
+ public boolean canScrollHorizontally(int direction) {
+ if (direction < 0
+ && mDragPanel.isDragging()
+ && getCurrentItem() == 1) {
+ // can't scroll left while not editing, OR dragging on the first page
+ return false;
+ }
+ return super.canScrollHorizontally(direction);
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ int height = 0;
+ for (int i = 0; i < getChildCount(); i++) {
+ View child = getChildAt(i);
+ child.measure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
+ int h = child.getMeasuredHeight();
+ if (h > height && !(child instanceof QSSettings)) height = h;
+ }
+ heightMeasureSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+ }
+
+ public void animatePagerTransition(final boolean forward) {
+ ValueAnimator animator = ValueAnimator.ofInt(0, getWidth());
+ animator.addListener(new Animator.AnimatorListener() {
+ @Override
+ public void onAnimationStart(Animator animation) {
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ if (isFakeDragging()) {
+ endFakeDrag();
+ }
+ }
+
+ @Override
+ public void onAnimationCancel(Animator animation) {
+ if (isFakeDragging()) {
+ endFakeDrag();
+ }
+ }
+
+ @Override
+ public void onAnimationRepeat(Animator animation) {
+ }
+ });
+
+ animator.setInterpolator(new AccelerateInterpolator());
+ animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
+
+ private int oldDragPosition = 0;
+
+ @Override
+ public void onAnimationUpdate(ValueAnimator animation) {
+ if (isFakeDragging()) {
+ int dragPosition = (Integer) animation.getAnimatedValue();
+ int dragOffset = dragPosition - oldDragPosition;
+ oldDragPosition = dragPosition;
+ fakeDragBy(dragOffset * (forward ? -1 : 1));
+ }
+ }
+ });
+ if (beginFakeDrag()) {
+ animator.setDuration(500);
+ animator.start();
+ } else {
+ Log.e(TAG, "can't start fake drag?");
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/AdbOverNetworkTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/AdbOverNetworkTile.java
new file mode 100644
index 0000000..b970a4c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/AdbOverNetworkTile.java
@@ -0,0 +1,130 @@
+/*
+ * Copyright (C) 2015 The CyanogenMod 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.database.ContentObserver;
+import android.net.NetworkUtils;
+import android.net.Uri;
+import android.net.wifi.WifiInfo;
+import android.net.wifi.WifiManager;
+import android.os.UserHandle;
+import android.provider.Settings;
+
+import com.android.systemui.R;
+import com.android.systemui.qs.QSTile;
+
+import java.net.InetAddress;
+
+import cyanogenmod.providers.CMSettings;
+import org.cyanogenmod.internal.logging.CMMetricsLogger;
+
+public class AdbOverNetworkTile extends QSTile<QSTile.BooleanState> {
+
+ private boolean mListening;
+
+ private static final Intent SETTINGS_DEVELOPMENT =
+ new Intent(Settings.ACTION_APPLICATION_DEVELOPMENT_SETTINGS);
+
+ @Override
+ protected BooleanState newTileState() {
+ return new BooleanState();
+ }
+
+ @Override
+ protected void handleClick() {
+ CMSettings.Secure.putIntForUser(mContext.getContentResolver(),
+ CMSettings.Secure.ADB_PORT, getState().value ? -1 : 5555,
+ UserHandle.USER_CURRENT);
+ }
+
+ @Override
+ protected void handleLongClick() {
+ mHost.startActivityDismissingKeyguard(SETTINGS_DEVELOPMENT);
+ }
+
+ @Override
+ protected void handleUpdateState(BooleanState state, Object arg) {
+ state.visible = isAdbEnabled();
+ if (!state.visible) {
+ return;
+ }
+ state.value = isAdbNetworkEnabled();
+ if (state.value) {
+ WifiManager wifiManager = (WifiManager) mContext.getSystemService(Context.WIFI_SERVICE);
+ WifiInfo wifiInfo = wifiManager.getConnectionInfo();
+
+ if (wifiInfo != null) {
+ // if wifiInfo is not null, set the label to "hostAddress"
+ InetAddress address = NetworkUtils.intToInetAddress(wifiInfo.getIpAddress());
+ state.label = address.getHostAddress();
+ } else {
+ // if wifiInfo is null, set the label without host address
+ state.label = mContext.getString(R.string.quick_settings_network_adb_label);
+ }
+ state.icon = ResourceIcon.get(R.drawable.ic_qs_network_adb_on);
+ } else {
+ // Otherwise set the disabled label and icon
+ state.label = mContext.getString(R.string.quick_settings_network_adb_label);
+ state.icon = ResourceIcon.get(R.drawable.ic_qs_network_adb_off);
+ }
+ }
+
+ @Override
+ public int getMetricsCategory() {
+ return CMMetricsLogger.TILE_ADB_OVER_NETWORK;
+ }
+
+ private boolean isAdbEnabled() {
+ return Settings.Global.getInt(mContext.getContentResolver(),
+ Settings.Global.ADB_ENABLED, 0) > 0;
+ }
+
+ private boolean isAdbNetworkEnabled() {
+ return CMSettings.Secure.getInt(mContext.getContentResolver(),
+ CMSettings.Secure.ADB_PORT, 0) > 0;
+ }
+
+ public AdbOverNetworkTile(Host host) {
+ super(host);
+ }
+
+ private ContentObserver mObserver = new ContentObserver(mHandler) {
+ @Override
+ public void onChange(boolean selfChange, Uri uri) {
+ refreshState();
+ }
+ };
+
+ @Override
+ public void setListening(boolean listening) {
+ if (mListening != listening) {
+ mListening = listening;
+ if (listening) {
+ mContext.getContentResolver().registerContentObserver(
+ CMSettings.Secure.getUriFor(CMSettings.Secure.ADB_PORT),
+ false, mObserver);
+ mContext.getContentResolver().registerContentObserver(
+ Settings.Global.getUriFor(Settings.Global.ADB_ENABLED),
+ false, mObserver);
+ } else {
+ mContext.getContentResolver().unregisterContentObserver(mObserver);
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/AirplaneModeTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/AirplaneModeTile.java
index 49f8d1c..bf28dbf 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/AirplaneModeTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/AirplaneModeTile.java
@@ -1,5 +1,6 @@
/*
* Copyright (C) 2014 The Android Open Source Project
+ * Copyright (C) 2015 The CyanogenMod Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -21,6 +22,7 @@ import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.net.ConnectivityManager;
+import android.provider.Settings;
import android.provider.Settings.Global;
import com.android.internal.logging.MetricsLogger;
@@ -30,6 +32,9 @@ import com.android.systemui.qs.QSTile;
/** Quick settings tile: Airplane mode **/
public class AirplaneModeTile extends QSTile<QSTile.BooleanState> {
+
+ private static final Intent WIRELESS_SETTINGS = new Intent(Settings.ACTION_WIRELESS_SETTINGS);
+
private final AnimationIcon mEnable =
new AnimationIcon(R.drawable.ic_signal_airplane_enable_animation);
private final AnimationIcon mDisable =
@@ -62,6 +67,11 @@ public class AirplaneModeTile extends QSTile<QSTile.BooleanState> {
mDisable.setAllowAnimation(true);
}
+ @Override
+ public void handleLongClick() {
+ mHost.startActivityDismissingKeyguard(WIRELESS_SETTINGS);
+ }
+
private void setEnabled(boolean enabled) {
final ConnectivityManager mgr =
(ConnectivityManager) mContext.getSystemService(Context.CONNECTIVITY_SERVICE);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/AmbientDisplayTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/AmbientDisplayTile.java
new file mode 100644
index 0000000..cc08d02
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/AmbientDisplayTile.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2015 The CyanogenMod 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.provider.Settings;
+import android.provider.Settings.Secure;
+
+import com.android.systemui.qs.SecureSetting;
+import com.android.systemui.qs.QSTile;
+import com.android.systemui.R;
+import org.cyanogenmod.internal.logging.CMMetricsLogger;
+
+/** Quick settings tile: Ambient Display **/
+public class AmbientDisplayTile extends QSTile<QSTile.BooleanState> {
+
+ private static final Intent DISPLAY_SETTINGS = new Intent("android.settings.DISPLAY_SETTINGS");
+
+ private final SecureSetting mSetting;
+
+ public AmbientDisplayTile(Host host) {
+ super(host);
+
+ mSetting = new SecureSetting(mContext, mHandler, Secure.DOZE_ENABLED) {
+ @Override
+ protected void handleValueChanged(int value, boolean observedChange) {
+ handleRefreshState(value);
+ }
+ };
+ }
+
+ @Override
+ protected BooleanState newTileState() {
+ return new BooleanState();
+ }
+
+ @Override
+ protected void handleClick() {
+ setEnabled(!mState.value);
+ refreshState();
+ }
+
+ @Override
+ protected void handleLongClick() {
+ mHost.startActivityDismissingKeyguard(DISPLAY_SETTINGS);
+ }
+
+ private void setEnabled(boolean enabled) {
+ Settings.Secure.putInt(mContext.getContentResolver(),
+ Settings.Secure.DOZE_ENABLED,
+ enabled ? 1 : 0);
+ }
+
+ @Override
+ protected void handleUpdateState(BooleanState state, Object arg) {
+ final int value = arg instanceof Integer ? (Integer)arg : mSetting.getValue();
+ final boolean enable = value != 0;
+ state.value = enable;
+ state.visible = true;
+ state.label = mContext.getString(R.string.quick_settings_ambient_display_label);
+ if (enable) {
+ state.icon = ResourceIcon.get(R.drawable.ic_qs_ambientdisplay_on);
+ state.contentDescription = mContext.getString(
+ R.string.accessibility_quick_settings_ambient_display_on);
+ } else {
+ state.icon = ResourceIcon.get(R.drawable.ic_qs_ambientdisplay_off);
+ state.contentDescription = mContext.getString(
+ R.string.accessibility_quick_settings_ambient_display_off);
+ }
+ }
+
+ @Override
+ public int getMetricsCategory() {
+ return CMMetricsLogger.TILE_AMBIENT_DISPLAY;
+ }
+
+ @Override
+ protected String composeChangeAnnouncement() {
+ if (mState.value) {
+ return mContext.getString(
+ R.string.accessibility_quick_settings_ambient_display_changed_on);
+ } else {
+ return mContext.getString(
+ R.string.accessibility_quick_settings_ambient_display_changed_off);
+ }
+ }
+
+ @Override
+ public void setListening(boolean listening) {
+ // Do nothing
+ }
+} \ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/BatterySaverTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/BatterySaverTile.java
new file mode 100644
index 0000000..1a60fa9
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/BatterySaverTile.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright (C) 2015 The CyanogenMod 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.database.ContentObserver;
+import android.net.Uri;
+import android.os.PowerManager;
+import android.provider.Settings;
+
+import com.android.systemui.qs.QSTile;
+import com.android.systemui.R;
+import com.android.systemui.statusbar.policy.BatteryController;
+import com.android.systemui.statusbar.policy.BatteryStateRegistar;
+
+import cyanogenmod.power.PerformanceManager;
+
+import org.cyanogenmod.internal.logging.CMMetricsLogger;
+
+/** Quick settings tile: Battery saver **/
+public class BatterySaverTile extends QSTile<QSTile.BooleanState> {
+
+ private static final Intent BATTERY_SETTINGS = new Intent(Intent.ACTION_POWER_USAGE_SUMMARY);
+
+ private final PowerManager mPm;
+ private final boolean mHasPowerProfiles;
+
+ private boolean mListening;
+ private boolean mPluggedIn;
+
+ public BatterySaverTile(Host host) {
+ super(host);
+ mPm = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
+ mHasPowerProfiles = PerformanceManager.getInstance(mContext).getNumberOfProfiles() > 0;
+ }
+
+ @Override
+ protected BooleanState newTileState() {
+ return new BooleanState();
+ }
+
+ @Override
+ public void handleClick() {
+ mPm.setPowerSaveMode(!mState.value);
+ refreshState(!mState.value);
+ }
+
+ @Override
+ public void handleLongClick() {
+ mHost.startActivityDismissingKeyguard(BATTERY_SETTINGS);
+ }
+
+ @Override
+ protected void handleUpdateState(BooleanState state, Object arg) {
+ state.value = arg instanceof Boolean ? (boolean) arg : mPm.isPowerSaveMode();
+ state.visible = !mHasPowerProfiles;
+ state.label = mContext.getString(R.string.quick_settings_battery_saver_label);
+ if (state.value) {
+ state.icon = ResourceIcon.get(R.drawable.ic_qs_battery_saver_on);
+ state.contentDescription = mContext.getString(
+ R.string.accessibility_quick_settings_battery_saver_on);
+ } else {
+ state.icon = ResourceIcon.get(R.drawable.ic_qs_battery_saver_off);
+ state.contentDescription = mContext.getString(
+ R.string.accessibility_quick_settings_battery_saver_off);
+ }
+
+ state.enabled = !mPluggedIn;
+ if (mPluggedIn) {
+ state.label = mContext.getString(R.string.quick_settings_battery_saver_label_charging);
+ }
+ }
+
+ @Override
+ protected String composeChangeAnnouncement() {
+ if (mState.value) {
+ return mContext.getString(
+ R.string.accessibility_quick_settings_battery_saver_changed_on);
+ } else {
+ return mContext.getString(
+ R.string.accessibility_quick_settings_battery_saver_changed_off);
+ }
+ }
+
+ @Override
+ public int getMetricsCategory() {
+ return CMMetricsLogger.TILE_BATTERY_SAVER;
+ }
+
+ private BatteryStateRegistar.BatteryStateChangeCallback mBatteryState
+ = new BatteryStateRegistar.BatteryStateChangeCallback() {
+ @Override
+ public void onBatteryLevelChanged(boolean present, int level, boolean pluggedIn,
+ boolean charging) {
+ mPluggedIn = pluggedIn || charging;
+ refreshState();
+ }
+
+ @Override
+ public void onPowerSaveChanged() {
+ refreshState();
+ }
+
+ @Override
+ public void onBatteryStyleChanged(int style, int percentMode) {
+ // ignore
+ }
+ };
+
+ @Override
+ public void setListening(boolean listening) {
+ if (mListening == listening) return;
+ mListening = listening;
+
+ if (listening) {
+ getHost().getBatteryController().addStateChangedCallback(mBatteryState);
+ } else {
+ getHost().getBatteryController().removeStateChangedCallback(mBatteryState);
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
index abce31f..cdedc26 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
@@ -1,5 +1,6 @@
/*
* Copyright (C) 2014 The Android Open Source Project
+ * Copyright (C) 2015 The CyanogenMod Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -24,17 +25,25 @@ import android.provider.Settings;
import android.text.TextUtils;
import android.view.View;
import android.view.ViewGroup;
+import android.widget.AdapterView;
+import android.widget.ListView;
import com.android.internal.logging.MetricsLogger;
+
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
+
import com.android.systemui.R;
import com.android.systemui.qs.QSDetailItems;
import com.android.systemui.qs.QSDetailItems.Item;
+import com.android.systemui.qs.QSDetailItemsList;
import com.android.systemui.qs.QSTile;
import com.android.systemui.statusbar.policy.BluetoothController;
+import cyanogenmod.app.StatusBarPanelCustomTile;
+
+import java.util.ArrayList;
import java.util.Collection;
-import java.util.Set;
+import java.util.List;
/** Quick settings tile: Bluetooth **/
public class BluetoothTile extends QSTile<QSTile.BooleanState> {
@@ -50,7 +59,7 @@ public class BluetoothTile extends QSTile<QSTile.BooleanState> {
}
@Override
- public boolean supportsDualTargets() {
+ public boolean hasDualTargetsDetails() {
return true;
}
@@ -90,6 +99,11 @@ public class BluetoothTile extends QSTile<QSTile.BooleanState> {
}
@Override
+ protected void handleLongClick() {
+ mHost.startActivityDismissingKeyguard(BLUETOOTH_SETTINGS);
+ }
+
+ @Override
protected void handleUpdateState(BooleanState state, Object arg) {
final boolean supported = mController.isBluetoothSupported();
final boolean enabled = mController.isBluetoothEnabled();
@@ -165,8 +179,11 @@ public class BluetoothTile extends QSTile<QSTile.BooleanState> {
}
};
- private final class BluetoothDetailAdapter implements DetailAdapter, QSDetailItems.Callback {
- private QSDetailItems mItems;
+ private final class BluetoothDetailAdapter implements DetailAdapter,
+ QSDetailItems.Callback, AdapterView.OnItemClickListener {
+ private QSDetailItemsList mItemsList;
+ private QSDetailItemsList.QSDetailListAdapter mAdapter;
+ private List<Item> mBluetoothItems = new ArrayList<>();
@Override
public int getTitle() {
@@ -184,6 +201,11 @@ public class BluetoothTile extends QSTile<QSTile.BooleanState> {
}
@Override
+ public StatusBarPanelCustomTile getCustomTile() {
+ return null;
+ }
+
+ @Override
public void setToggleState(boolean state) {
MetricsLogger.action(mContext, MetricsLogger.QS_BLUETOOTH_TOGGLE, state);
mController.setBluetoothEnabled(state);
@@ -197,29 +219,35 @@ public class BluetoothTile extends QSTile<QSTile.BooleanState> {
@Override
public View createDetailView(Context context, View convertView, ViewGroup parent) {
- mItems = QSDetailItems.convertOrInflate(context, convertView, parent);
- mItems.setTagSuffix("Bluetooth");
- mItems.setEmptyState(R.drawable.ic_qs_bluetooth_detail_empty,
+ mItemsList = QSDetailItemsList.convertOrInflate(context, convertView, parent);
+ ListView listView = mItemsList.getListView();
+ listView.setDivider(null);
+ listView.setOnItemClickListener(this);
+ listView.setAdapter(mAdapter =
+ new QSDetailItemsList.QSDetailListAdapter(context, mBluetoothItems));
+ mAdapter.setCallback(this);
+ mItemsList.setEmptyState(R.drawable.ic_qs_bluetooth_detail_empty,
R.string.quick_settings_bluetooth_detail_empty_text);
- mItems.setCallback(this);
- mItems.setMinHeightInItems(0);
+
updateItems();
- setItemsVisible(mState.value);
- return mItems;
+ return mItemsList;
}
public void setItemsVisible(boolean visible) {
- if (mItems == null) return;
- mItems.setItemsVisible(visible);
+ if (mAdapter == null) return;
+ if (visible) {
+ updateItems();
+ } else {
+ mBluetoothItems.clear();
+ }
+ mAdapter.notifyDataSetChanged();
}
private void updateItems() {
- if (mItems == null) return;
- Item[] items = null;
- final Collection<CachedBluetoothDevice> devices = mController.getDevices();
+ if (mAdapter == null) return;
+ final Collection<CachedBluetoothDevice> devices = mController.getDevices();
if (devices != null) {
- items = new Item[getBondedCount(devices)];
- int i = 0;
+ mBluetoothItems.clear();
for (CachedBluetoothDevice device : devices) {
if (device.getBondState() == BluetoothDevice.BOND_NONE) continue;
final Item item = new Item();
@@ -235,10 +263,10 @@ public class BluetoothTile extends QSTile<QSTile.BooleanState> {
item.line2 = mContext.getString(R.string.quick_settings_connecting);
}
item.tag = device;
- items[i++] = item;
+ mBluetoothItems.add(item);
}
}
- mItems.setItems(items);
+ mAdapter.notifyDataSetChanged();
}
private int getBondedCount(Collection<CachedBluetoothDevice> devices) {
@@ -253,12 +281,7 @@ public class BluetoothTile extends QSTile<QSTile.BooleanState> {
@Override
public void onDetailItemClick(Item item) {
- if (item == null || item.tag == null) return;
- final CachedBluetoothDevice device = (CachedBluetoothDevice) item.tag;
- if (device != null && device.getMaxConnectionState()
- == BluetoothProfile.STATE_DISCONNECTED) {
- mController.connect(device);
- }
+ // noop
}
@Override
@@ -269,5 +292,16 @@ public class BluetoothTile extends QSTile<QSTile.BooleanState> {
mController.disconnect(device);
}
}
+
+ @Override
+ public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
+ Item item = (Item) parent.getItemAtPosition(position);
+ if (item == null || item.tag == null) return;
+ final CachedBluetoothDevice device = (CachedBluetoothDevice) item.tag;
+ if (device != null && device.getMaxConnectionState()
+ == BluetoothProfile.STATE_DISCONNECTED) {
+ mController.connect(device);
+ }
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/CaffeineTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/CaffeineTile.java
new file mode 100644
index 0000000..fb6ec42
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/CaffeineTile.java
@@ -0,0 +1,202 @@
+/*
+ * Copyright (C) 2016 The CyanogenMod 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.database.ContentObserver;
+import android.net.Uri;
+import android.os.CountDownTimer;
+import android.os.PowerManager;
+import android.os.SystemClock;
+import android.provider.Settings;
+
+import com.android.systemui.qs.QSTile;
+import com.android.systemui.R;
+
+import org.cyanogenmod.internal.logging.CMMetricsLogger;
+
+/** Quick settings tile: Caffeine **/
+public class CaffeineTile extends QSTile<QSTile.BooleanState> {
+
+ private final PowerManager.WakeLock mWakeLock;
+ private int mSecondsRemaining;
+ private int mDuration;
+ private static int[] DURATIONS = new int[] {
+ 5 * 60, // 5 min
+ 10 * 60, // 10 min
+ 30 * 60, // 30 min
+ -1, // infinity
+ };
+ private CountDownTimer mCountdownTimer = null;
+ public long mLastClickTime = -1;
+ private final Receiver mReceiver = new Receiver();
+ private boolean mListening;
+
+ public CaffeineTile(Host host) {
+ super(host);
+ mWakeLock = ((PowerManager) mContext.getSystemService(Context.POWER_SERVICE)).newWakeLock(
+ PowerManager.FULL_WAKE_LOCK, "CaffeineTile");
+ mReceiver.init();
+ }
+
+ @Override
+ protected BooleanState newTileState() {
+ return new BooleanState();
+ }
+
+ @Override
+ protected void handleDestroy() {
+ super.handleDestroy();
+ stopCountDown();
+ mReceiver.destroy();
+ if (mWakeLock.isHeld()) {
+ mWakeLock.release();
+ }
+ }
+
+ @Override
+ public void setListening(boolean listening) {
+ }
+
+ @Override
+ public void handleClick() {
+ // If last user clicks < 5 seconds
+ // we cycle different duration
+ // otherwise toggle on/off
+ if (mWakeLock.isHeld() && (mLastClickTime != -1) &&
+ (SystemClock.elapsedRealtime() - mLastClickTime < 5000)) {
+ // cycle duration
+ mDuration++;
+ if (mDuration >= DURATIONS.length) {
+ // all durations cycled, turn if off
+ mDuration = -1;
+ stopCountDown();
+ if (mWakeLock.isHeld()) {
+ mWakeLock.release();
+ }
+ } else {
+ // change duration
+ startCountDown(DURATIONS[mDuration]);
+ if (!mWakeLock.isHeld()) {
+ mWakeLock.acquire();
+ }
+ }
+ } else {
+ // toggle
+ if (mWakeLock.isHeld()) {
+ mWakeLock.release();
+ stopCountDown();
+ } else {
+ mWakeLock.acquire();
+ mDuration = 0;
+ startCountDown(DURATIONS[mDuration]);
+ }
+ }
+ mLastClickTime = SystemClock.elapsedRealtime();
+ refreshState();
+ }
+
+ private void startCountDown(long duration) {
+ stopCountDown();
+ mSecondsRemaining = (int)duration;
+ if (duration == -1) {
+ // infinity timing, no need to start timer
+ return;
+ }
+ mCountdownTimer = new CountDownTimer(duration * 1000, 1000) {
+
+ @Override
+ public void onTick(long millisUntilFinished) {
+ mSecondsRemaining = (int) (millisUntilFinished / 1000);
+ refreshState();
+ }
+
+ @Override
+ public void onFinish() {
+ if (mWakeLock.isHeld())
+ mWakeLock.release();
+ refreshState();
+ }
+
+ }.start();
+ }
+
+ private void stopCountDown() {
+ if (mCountdownTimer != null) {
+ mCountdownTimer.cancel();
+ mCountdownTimer = null;
+ }
+ }
+
+ private String formatValueWithRemainingTime() {
+ if (mSecondsRemaining == -1) {
+ return "\u221E"; // infinity
+ }
+ return String.format("%02d:%02d",
+ mSecondsRemaining / 60 % 60, mSecondsRemaining % 60);
+ }
+
+ @Override
+ protected void handleUpdateState(BooleanState state, Object arg) {
+ state.value = mWakeLock.isHeld();
+ state.visible = true;
+ if (state.value) {
+ state.label = formatValueWithRemainingTime();
+ state.icon = ResourceIcon.get(R.drawable.ic_qs_caffeine_on);
+ state.contentDescription = mContext.getString(
+ R.string.accessibility_quick_settings_caffeine_on);
+ } else {
+ state.label = mContext.getString(R.string.quick_settings_caffeine_label);
+ state.icon = ResourceIcon.get(R.drawable.ic_qs_caffeine_off);
+ state.contentDescription = mContext.getString(
+ R.string.accessibility_quick_settings_caffeine_off);
+ }
+ }
+
+ @Override
+ public int getMetricsCategory() {
+ return CMMetricsLogger.TILE_CAFFEINE;
+ }
+
+ private final class Receiver extends BroadcastReceiver {
+ public void init() {
+ // Register for Intent broadcasts for...
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(Intent.ACTION_SCREEN_OFF);
+ mContext.registerReceiver(this, filter, null, mHandler);
+ }
+
+ public void destroy() {
+ mContext.unregisterReceiver(this);
+ }
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+ if (Intent.ACTION_SCREEN_OFF.equals(action)) {
+ // disable caffeine if user force off (power button)
+ stopCountDown();
+ if (mWakeLock.isHeld())
+ mWakeLock.release();
+ refreshState();
+ }
+ }
+ }
+}
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 61695b2..cd608d6 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java
@@ -1,5 +1,6 @@
/*
* Copyright (C) 2014 The Android Open Source Project
+ * Copyright (C) 2015 The CyanogenMod Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -32,6 +33,7 @@ import com.android.systemui.qs.QSTile;
import com.android.systemui.statusbar.policy.CastController;
import com.android.systemui.statusbar.policy.CastController.CastDevice;
import com.android.systemui.statusbar.policy.KeyguardMonitor;
+import cyanogenmod.app.StatusBarPanelCustomTile;
import java.util.LinkedHashMap;
import java.util.Set;
@@ -91,6 +93,16 @@ public class CastTile extends QSTile<QSTile.BooleanState> {
}
@Override
+ protected void handleSecondaryClick() {
+ handleClick();
+ }
+
+ @Override
+ protected void handleLongClick() {
+ mHost.startActivityDismissingKeyguard(CAST_SETTINGS);
+ }
+
+ @Override
protected void handleUpdateState(BooleanState state, Object arg) {
state.visible = !mKeyguard.isSecure() || !mKeyguard.isShowing()
|| mKeyguard.canSkipBouncer();
@@ -129,6 +141,11 @@ public class CastTile extends QSTile<QSTile.BooleanState> {
return null;
}
+ @Override
+ public boolean hasDualTargetsDetails() {
+ return true;
+ }
+
private String getDeviceName(CastDevice device) {
return device.name != null ? device.name
: mContext.getString(R.string.quick_settings_cast_device_default_name);
@@ -167,6 +184,11 @@ public class CastTile extends QSTile<QSTile.BooleanState> {
}
@Override
+ public StatusBarPanelCustomTile getCustomTile() {
+ return null;
+ }
+
+ @Override
public void setToggleState(boolean state) {
// noop
}
@@ -186,12 +208,14 @@ public class CastTile extends QSTile<QSTile.BooleanState> {
@Override
public void onViewAttachedToWindow(View v) {
if (DEBUG) Log.d(TAG, "onViewAttachedToWindow");
+ mController.setDiscovering(true);
}
@Override
public void onViewDetachedFromWindow(View v) {
if (DEBUG) Log.d(TAG, "onViewDetachedFromWindow");
mVisibleOrder.clear();
+ mController.setDiscovering(false);
}
});
}
@@ -199,7 +223,6 @@ public class CastTile extends QSTile<QSTile.BooleanState> {
R.string.quick_settings_cast_detail_empty_text);
mItems.setCallback(this);
updateItems(mController.getCastDevices());
- mController.setDiscovering(true);
return mItems;
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java
index f3ad9d8..038fa5e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/CellularTile.java
@@ -1,5 +1,6 @@
/*
* Copyright (C) 2014 The Android Open Source Project
+ * Copyright (C) 2015 The CyanogenMod Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,10 +17,13 @@
package com.android.systemui.qs.tiles;
+import android.app.ActivityManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.res.Resources;
+import android.os.UserHandle;
+import android.telephony.TelephonyManager;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@@ -35,15 +39,23 @@ import com.android.systemui.statusbar.policy.NetworkController.MobileDataControl
import com.android.systemui.statusbar.policy.NetworkController.MobileDataController.DataUsageInfo;
import com.android.systemui.statusbar.policy.NetworkController.SignalCallback;
import com.android.systemui.statusbar.policy.SignalCallbackAdapter;
+import cyanogenmod.app.StatusBarPanelCustomTile;
/** Quick settings tile: Cellular **/
public class CellularTile extends QSTile<QSTile.SignalState> {
- private static final Intent CELLULAR_SETTINGS = new Intent().setComponent(new ComponentName(
+
+ private static final Intent DATA_USAGE_SETTINGS = new Intent().setComponent(new ComponentName(
"com.android.settings", "com.android.settings.Settings$DataUsageSummaryActivity"));
+ private static final Intent MOBILE_NETWORK_SETTINGS = new Intent(Intent.ACTION_MAIN)
+ .setComponent(new ComponentName("com.android.phone",
+ "com.android.phone.MobileNetworkSettings"));
+ private static final Intent MOBILE_NETWORK_SETTINGS_MSIM
+ = new Intent("com.android.settings.sim.SIM_SUB_INFO_SETTINGS");
private final NetworkController mController;
private final MobileDataController mDataController;
private final CellularDetailAdapter mDetailAdapter;
+ private final TelephonyManager mTelephonyManager;
private final CellSignalCallback mSignalCallback = new CellSignalCallback();
@@ -52,6 +64,7 @@ public class CellularTile extends QSTile<QSTile.SignalState> {
mController = host.getNetworkController();
mDataController = mController.getMobileDataController();
mDetailAdapter = new CellularDetailAdapter();
+ mTelephonyManager = (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
}
@Override
@@ -79,18 +92,40 @@ public class CellularTile extends QSTile<QSTile.SignalState> {
}
@Override
+ protected void handleUserSwitch(int newUserId) {
+ if (newUserId != UserHandle.USER_OWNER) {
+ refreshState();
+ }
+ }
+
+ @Override
protected void handleClick() {
MetricsLogger.action(mContext, getMetricsCategory());
if (mDataController.isMobileDataSupported()) {
showDetail(true);
} else {
- mHost.startActivityDismissingKeyguard(CELLULAR_SETTINGS);
+ mHost.startActivityDismissingKeyguard(DATA_USAGE_SETTINGS);
+ }
+ }
+
+ @Override
+ protected void handleSecondaryClick() {
+ handleClick();
+ }
+
+ @Override
+ protected void handleLongClick() {
+ if (mTelephonyManager.getDefault().getPhoneCount() > 1) {
+ mHost.startActivityDismissingKeyguard(MOBILE_NETWORK_SETTINGS_MSIM);
+ } else {
+ mHost.startActivityDismissingKeyguard(MOBILE_NETWORK_SETTINGS);
}
}
@Override
protected void handleUpdateState(SignalState state, Object arg) {
- state.visible = mController.hasMobileDataFeature();
+ state.visible = mController.hasMobileDataFeature()
+ && (ActivityManager.getCurrentUser() == UserHandle.USER_OWNER);
if (!state.visible) return;
CallbackInfo cb = (CallbackInfo) arg;
if (cb == null) {
@@ -112,7 +147,9 @@ public class CellularTile extends QSTile<QSTile.SignalState> {
state.label = cb.enabled
? removeTrailingPeriod(cb.enabledDesc)
- : r.getString(R.string.quick_settings_rssi_emergency_only);
+ : mDataController.isMobileDataSupported() ?
+ r.getString(R.string.data_sim_not_configured) :
+ r.getString(R.string.quick_settings_rssi_emergency_only);
final String signalContentDesc = cb.enabled && (cb.mobileSignalIconId > 0)
? cb.signalContentDescription
@@ -131,6 +168,11 @@ public class CellularTile extends QSTile<QSTile.SignalState> {
return MetricsLogger.QS_CELLULAR;
}
+ @Override
+ public boolean hasDualTargetsDetails() {
+ return true;
+ }
+
// Remove the period from the network name
public static String removeTrailingPeriod(String string) {
if (string == null) return null;
@@ -168,7 +210,7 @@ public class CellularTile extends QSTile<QSTile.SignalState> {
@Override
public void setMobileDataIndicators(IconState statusIcon, IconState qsIcon, int statusType,
int qsType, boolean activityIn, boolean activityOut, String typeContentDescription,
- String description, boolean isWide, int subId) {
+ String description, boolean isWide, boolean showSeparateRoaming, int subId) {
if (qsIcon == null) {
// Not data sim, don't display.
return;
@@ -192,10 +234,14 @@ public class CellularTile extends QSTile<QSTile.SignalState> {
// Make sure signal gets cleared out when no sims.
mInfo.mobileSignalIconId = 0;
mInfo.dataTypeIconId = 0;
- // Show a No SIMs description to avoid emergency calls message.
+ // Show a No SIMs description if we're incapable of supporting mobile data
+ // to avoid showing an emergency mode description. If we're still capable of
+ // supporting mobile data, notify the user that the data sim is not configured
+ // only relevant in MSIM scenario: CYNGNOS-2211
mInfo.enabled = true;
- mInfo.enabledDesc = mContext.getString(
- R.string.keyguard_missing_sim_message_short);
+ mInfo.enabledDesc = mDataController.isMobileDataSupported() ?
+ mContext.getString(R.string.data_sim_not_configured)
+ : mContext.getString(R.string.keyguard_missing_sim_message_short);
mInfo.signalContentDescription = mInfo.enabledDesc;
}
refreshState(mInfo);
@@ -214,6 +260,10 @@ public class CellularTile extends QSTile<QSTile.SignalState> {
};
private final class CellularDetailAdapter implements DetailAdapter {
+ @Override
+ public StatusBarPanelCustomTile getCustomTile() {
+ return null;
+ }
@Override
public int getTitle() {
@@ -229,7 +279,7 @@ public class CellularTile extends QSTile<QSTile.SignalState> {
@Override
public Intent getSettingsIntent() {
- return CELLULAR_SETTINGS;
+ return DATA_USAGE_SETTINGS;
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorInversionTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorInversionTile.java
index c6fc6ff..f49d97e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorInversionTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorInversionTile.java
@@ -1,5 +1,6 @@
/*
* Copyright (C) 2014 The Android Open Source Project
+ * Copyright (C) 2015 The CyanogenMod Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,6 +17,8 @@
package com.android.systemui.qs.tiles;
+import android.content.Intent;
+import android.provider.Settings;
import android.provider.Settings.Secure;
import com.android.internal.logging.MetricsLogger;
@@ -28,12 +31,14 @@ import com.android.systemui.qs.UsageTracker;
/** Quick settings tile: Invert colors **/
public class ColorInversionTile extends QSTile<QSTile.BooleanState> {
+ private static final Intent ACCESSIBILITY_SETTINGS = new Intent(
+ Settings.ACTION_ACCESSIBILITY_SETTINGS);
+
private final AnimationIcon mEnable
= new AnimationIcon(R.drawable.ic_invert_colors_enable_animation);
private final AnimationIcon mDisable
= new AnimationIcon(R.drawable.ic_invert_colors_disable_animation);
private final SecureSetting mSetting;
- private final UsageTracker mUsageTracker;
private boolean mListening;
@@ -44,29 +49,11 @@ public class ColorInversionTile extends QSTile<QSTile.BooleanState> {
Secure.ACCESSIBILITY_DISPLAY_INVERSION_ENABLED) {
@Override
protected void handleValueChanged(int value, boolean observedChange) {
- if (value != 0 || observedChange) {
- mUsageTracker.trackUsage();
- }
if (mListening) {
handleRefreshState(value);
}
}
};
- mUsageTracker = new UsageTracker(host.getContext(),
- Prefs.Key.COLOR_INVERSION_TILE_LAST_USED, ColorInversionTile.class,
- R.integer.days_to_show_color_inversion_tile);
- if (mSetting.getValue() != 0 && !mUsageTracker.isRecentlyUsed()) {
- mUsageTracker.trackUsage();
- }
- mUsageTracker.setListening(true);
- mSetting.setListening(true);
- }
-
- @Override
- protected void handleDestroy() {
- super.handleDestroy();
- mUsageTracker.setListening(false);
- mSetting.setListening(false);
}
@Override
@@ -76,7 +63,11 @@ public class ColorInversionTile extends QSTile<QSTile.BooleanState> {
@Override
public void setListening(boolean listening) {
+ if (mListening == listening) {
+ return;
+ }
mListening = listening;
+ mSetting.setListening(mListening);
}
@Override
@@ -95,22 +86,14 @@ public class ColorInversionTile extends QSTile<QSTile.BooleanState> {
@Override
protected void handleLongClick() {
- if (mState.value) return; // don't allow usage reset if inversion is active
- final String title = mContext.getString(R.string.quick_settings_reset_confirmation_title,
- mState.label);
- mUsageTracker.showResetConfirmation(title, new Runnable() {
- @Override
- public void run() {
- refreshState();
- }
- });
+ mHost.startActivityDismissingKeyguard(ACCESSIBILITY_SETTINGS);
}
@Override
protected void handleUpdateState(BooleanState state, Object arg) {
final int value = arg instanceof Integer ? (Integer) arg : mSetting.getValue();
final boolean enabled = value != 0;
- state.visible = enabled || mUsageTracker.isRecentlyUsed();
+ state.visible = true;
state.value = enabled;
state.label = mContext.getString(R.string.quick_settings_inversion_label);
state.icon = enabled ? mEnable : mDisable;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/CompassTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/CompassTile.java
new file mode 100644
index 0000000..85790d1
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/CompassTile.java
@@ -0,0 +1,194 @@
+/*
+ * Copyright (C) 2015 The CyanogenMod 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.hardware.Sensor;
+import android.hardware.SensorEvent;
+import android.hardware.SensorEventListener;
+import android.hardware.SensorManager;
+import android.widget.ImageView;
+
+import com.android.systemui.R;
+import com.android.systemui.qs.QSTile;
+import com.android.systemui.qs.QSTileView;
+import org.cyanogenmod.internal.logging.CMMetricsLogger;
+
+public class CompassTile extends QSTile<QSTile.BooleanState> implements SensorEventListener {
+ private final static float ALPHA = 0.97f;
+
+ private boolean mActive = false;
+ private boolean mListening = false;
+
+ private SensorManager mSensorManager;
+ private Sensor mAccelerationSensor;
+ private Sensor mGeomagneticFieldSensor;
+
+ private float[] mAcceleration;
+ private float[] mGeomagnetic;
+
+ private ImageView mImage;
+ private boolean mListeningSensors;
+
+ public CompassTile(Host host) {
+ super(host);
+ mSensorManager = (SensorManager) mContext.getSystemService(Context.SENSOR_SERVICE);
+ mAccelerationSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
+ mGeomagneticFieldSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD);
+ }
+
+ @Override
+ protected BooleanState newTileState() {
+ return new BooleanState();
+ }
+
+ @Override
+ protected void handleDestroy() {
+ super.handleDestroy();
+ setListeningSensors(false);
+ mSensorManager = null;
+ mImage = null;
+ }
+
+ @Override
+ public QSTileView createTileView(Context context) {
+ QSTileView tileView = super.createTileView(context);
+ mImage = (ImageView) tileView.findViewById(android.R.id.icon);
+ return tileView;
+ }
+
+ @Override
+ protected void handleClick() {
+ mActive = !mActive;
+ refreshState();
+ setListeningSensors(mActive);
+ }
+
+ private void setListeningSensors(boolean listening) {
+ if (listening == mListeningSensors) return;
+ mListeningSensors = listening;
+ if (mListeningSensors) {
+ mSensorManager.registerListener(
+ this, mAccelerationSensor, SensorManager.SENSOR_DELAY_GAME);
+ mSensorManager.registerListener(
+ this, mGeomagneticFieldSensor, SensorManager.SENSOR_DELAY_GAME);
+ } else {
+ mSensorManager.unregisterListener(this);
+ }
+ }
+
+ @Override
+ protected void handleUpdateState(BooleanState state, Object arg) {
+ Float degrees = arg == null ? 0 :(float) arg;
+
+ state.visible = true;
+ state.value = mActive && mListening;
+
+ if (state.value) {
+ state.icon = ResourceIcon.get(R.drawable.ic_qs_compass_on);
+ if (arg != null) {
+ state.label = formatValueWithCardinalDirection(degrees);
+
+ float target = 360 - degrees;
+ float relative = target - mImage.getRotation();
+ if (relative > 180) relative -= 360;
+
+ mImage.setRotation(mImage.getRotation() + relative / 2);
+
+ } else {
+ state.label = mContext.getString(R.string.quick_settings_compass_init);
+ mImage.setRotation(0);
+ }
+ } else {
+ state.icon = ResourceIcon.get(R.drawable.ic_qs_compass_off);
+ state.label = mContext.getString(R.string.quick_settings_compass_label);
+ mImage.setRotation(0);
+ }
+ }
+
+ @Override
+ public int getMetricsCategory() {
+ return CMMetricsLogger.TILE_COMPASS;
+ }
+
+ @Override
+ public void setListening(boolean listening) {
+ // setListening might get called multiple times with the same value, we check for it
+ // in setListeningSensors
+ mListening = listening;
+ setListeningSensors(mListening && mActive);
+ }
+
+ private String formatValueWithCardinalDirection(float degree) {
+ int cardinalDirectionIndex = (int) (Math.floor(((degree - 22.5) % 360) / 45) + 1) % 8;
+ String[] cardinalDirections = mContext.getResources().getStringArray(
+ R.array.cardinal_directions);
+
+ return mContext.getString(R.string.quick_settings_compass_value, degree,
+ cardinalDirections[cardinalDirectionIndex]);
+ }
+
+ @Override
+ public void onSensorChanged(SensorEvent event) {
+ float[] values;
+ if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER) {
+ if (mAcceleration == null) {
+ mAcceleration = event.values.clone();
+ }
+
+ values = mAcceleration;
+ } else {
+ // Magnetic field sensor
+ if (mGeomagnetic == null) {
+ mGeomagnetic = event.values.clone();
+ }
+
+ values = mGeomagnetic;
+ }
+
+ for (int i = 0; i < 3; i++) {
+ values[i] = ALPHA * values[i] + (1 - ALPHA) * event.values[i];
+ }
+
+ if (!mActive || !mListeningSensors || mAcceleration == null || mGeomagnetic == null) {
+ // Nothing to do at this moment
+ return;
+ }
+
+ float R[] = new float[9];
+ float I[] = new float[9];
+ if (!SensorManager.getRotationMatrix(R, I, mAcceleration, mGeomagnetic)) {
+ // Rotation matrix couldn't be calculated
+ return;
+ }
+
+ // Get the current orientation
+ float[] orientation = new float[3];
+ SensorManager.getOrientation(R, orientation);
+
+ // Convert azimuth to degrees
+ Float newDegree = Float.valueOf((float) Math.toDegrees(orientation[0]));
+ newDegree = (newDegree + 360) % 360;
+
+ refreshState(newDegree);
+ }
+
+ @Override
+ public void onAccuracyChanged(Sensor sensor, int accuracy) {
+ // noop
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/CustomQSTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/CustomQSTile.java
new file mode 100644
index 0000000..40c7184
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/CustomQSTile.java
@@ -0,0 +1,349 @@
+/*
+ * Copyright (C) 2015 The CyanogenMod 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.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.content.res.Configuration;
+import android.content.res.ThemeConfig;
+import android.net.Uri;
+import android.os.Process;
+import android.os.UserHandle;
+import android.text.TextUtils;
+import android.util.Log;
+
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AdapterView;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.ListView;
+import android.widget.RemoteViews;
+import android.widget.TextView;
+
+import com.android.systemui.qs.QSDetailItemsGrid;
+import com.android.systemui.qs.QSDetailItemsList;
+import cyanogenmod.app.CustomTile;
+import cyanogenmod.app.StatusBarPanelCustomTile;
+
+import com.android.systemui.R;
+import com.android.systemui.qs.QSTile;
+import org.cyanogenmod.internal.logging.CMMetricsLogger;
+
+import java.util.Arrays;
+
+public class CustomQSTile extends QSTile<QSTile.State> {
+
+ private static final String HIDDEN_TILES_PREF_NAME = "user_hidden_qs_tiles";
+
+ private CustomTile.ExpandedStyle mExpandedStyle;
+ private PendingIntent mOnClick;
+ private PendingIntent mOnLongClick;
+ private Uri mOnClickUri;
+ private int mCurrentUserId;
+ private StatusBarPanelCustomTile mTile;
+ private CustomQSDetailAdapter mDetailAdapter;
+ private boolean mCollapsePanel;
+ private boolean mUserRemoved;
+ private String mPersistedPlaceHolderKey;
+
+ public CustomQSTile(Host host, String persistedSpec) {
+ super(host);
+ mTile = null;
+ mPersistedPlaceHolderKey = persistedSpec;
+ }
+
+ public CustomQSTile(Host host, StatusBarPanelCustomTile tile) {
+ super(host);
+ mTile = tile;
+ mUserRemoved = getIsUserRemovedPersisted();
+ }
+
+ private String getPersistableKey() {
+ if (mPersistedPlaceHolderKey != null) {
+ return mPersistedPlaceHolderKey;
+ } else {
+ return getTile().persistableKey();
+ }
+ }
+
+ private boolean getIsUserRemovedPersisted() {
+ return getCustomQSTilePrefs(mContext).getBoolean(getPersistableKey(), false);
+ }
+
+ public boolean isUserRemoved() {
+ return mUserRemoved;
+ }
+
+ public void setUserRemoved(boolean removed) {
+ if (mUserRemoved != removed) {
+ if (removed) {
+ getCustomQSTilePrefs(mContext).edit().putBoolean(getPersistableKey(), true).apply();
+ } else {
+ getCustomQSTilePrefs(mContext).edit().remove(getPersistableKey()).apply();
+ }
+ mUserRemoved = removed;
+ refreshState();
+ }
+ }
+
+ public static SharedPreferences getCustomQSTilePrefs(Context context) {
+ return context.getSharedPreferences(HIDDEN_TILES_PREF_NAME, Context.MODE_PRIVATE);
+ }
+
+ @Override
+ public DetailAdapter getDetailAdapter() {
+ return mDetailAdapter;
+ }
+
+ @Override
+ public void setListening(boolean listening) {
+ }
+
+ @Override
+ protected State newTileState() {
+ return new State();
+ }
+
+ @Override
+ protected void handleUserSwitch(int newUserId) {
+ super.handleUserSwitch(newUserId);
+ mCurrentUserId = newUserId;
+ }
+
+ public void update(StatusBarPanelCustomTile customTile) {
+ refreshState(customTile);
+ }
+
+ @Override
+ protected void handleLongClick() {
+ if (mOnLongClick != null) {
+ if (mOnLongClick.isActivity()) {
+ getHost().collapsePanels();
+ }
+ try {
+ mOnLongClick.send();
+ } catch (Throwable e) {
+ Log.w(TAG, "Error sending long click intent", e);
+ }
+ } else if (mExpandedStyle == null) {
+ showDetail(true);
+ }
+ }
+
+ @Override
+ protected void handleClick() {
+ try {
+ if (mExpandedStyle != null &&
+ mExpandedStyle.getStyle() != CustomTile.ExpandedStyle.NO_STYLE) {
+ showDetail(true);
+ return;
+ }
+ if (mCollapsePanel) {
+ mHost.collapsePanels();
+ }
+ if (mOnClick != null) {
+ mOnClick.send();
+ } else if (mOnClickUri != null) {
+ mHost.collapsePanels();
+ final Intent intent = new Intent().setData(mOnClickUri);
+ mContext.sendBroadcastAsUser(intent, new UserHandle(mCurrentUserId));
+ }
+ } catch (Throwable t) {
+ Log.w(TAG, "Error sending click intent", t);
+ }
+ }
+
+ public StatusBarPanelCustomTile getTile() {
+ return mTile;
+ }
+
+ @Override
+ protected void handleUpdateState(State state, Object arg) {
+ if (arg instanceof StatusBarPanelCustomTile) {
+ mTile = (StatusBarPanelCustomTile) arg;
+ mPersistedPlaceHolderKey = null;
+ mUserRemoved = getIsUserRemovedPersisted();
+ }
+ if (mTile == null) {
+ state.visible = false;
+ // nothing to show, it's a place holder for now
+ return;
+ }
+ final CustomTile customTile = mTile.getCustomTile();
+ state.contentDescription = customTile.contentDescription;
+ state.label = customTile.label;
+ state.visible = !mUserRemoved;
+ final int iconId = customTile.icon;
+ if (iconId != 0 && (customTile.remoteIcon == null)) {
+ final String iconPackage = mTile.getResPkg();
+ if (!TextUtils.isEmpty(iconPackage)) {
+ state.icon = new ExternalIcon(iconPackage, iconId);
+ }
+ } else {
+ state.icon = new ExternalBitmapIcon(customTile.remoteIcon);
+ }
+ mOnClick = customTile.onClick;
+ mOnLongClick = customTile.onLongClick;
+ mOnClickUri = customTile.onClickUri;
+ mExpandedStyle = customTile.expandedStyle;
+ mCollapsePanel = customTile.collapsePanel;
+ mDetailAdapter = new CustomQSDetailAdapter();
+ }
+
+ @Override
+ public int getMetricsCategory() {
+ return CMMetricsLogger.TILE_CUSTOM_QS;
+ }
+
+ private boolean isDynamicTile() {
+ return mTile.getPackage().equals(mContext.getPackageName())
+ || mTile.getUid() == Process.SYSTEM_UID;
+ }
+
+ private class CustomQSDetailAdapter implements DetailAdapter, AdapterView.OnItemClickListener,
+ QSDetailItemsGrid.QSDetailItemsGridAdapter.OnPseudoGriditemClickListener {
+ private QSDetailItemsList.QSCustomDetailListAdapter mListAdapter;
+ private QSDetailItemsGrid.QSDetailItemsGridAdapter mGridAdapter;
+
+ public int getTitle() {
+ if (isDynamicTile()) {
+ return mContext.getResources().getIdentifier(
+ String.format("dynamic_qs_tile_%s_label", mTile.getTag()),
+ "string", mContext.getPackageName());
+ }
+ return R.string.quick_settings_custom_tile_detail_title;
+ }
+
+ @Override
+ public Boolean getToggleState() {
+ return null;
+ }
+
+
+ @Override
+ public Intent getSettingsIntent() {
+ return mTile.getCustomTile().onSettingsClick;
+ }
+
+ @Override
+ public StatusBarPanelCustomTile getCustomTile() {
+ return mTile;
+ }
+
+ @Override
+ public void setToggleState(boolean state) {
+ // noop
+ }
+
+ @Override
+ public int getMetricsCategory() {
+ return CMMetricsLogger.TILE_CUSTOM_QS_DETAIL;
+ }
+
+ @Override
+ public View createDetailView(Context context, View convertView, ViewGroup parent) {
+ View rootView = null;
+ if (mExpandedStyle == null) {
+ rootView = (LinearLayout) LayoutInflater.from(context)
+ .inflate(R.layout.qs_custom_detail, parent, false);
+ ImageView imageView = (ImageView)
+ rootView.findViewById(R.id.custom_qs_tile_icon);
+ TextView customTileTitle = (TextView)
+ rootView.findViewById(R.id.custom_qs_tile_title);
+ TextView customTilePkg = (TextView) rootView
+ .findViewById(R.id.custom_qs_tile_package);
+ TextView customTileContentDesc = (TextView) rootView
+ .findViewById(R.id.custom_qs_tile_content_description);
+ // icon is cached in state, fetch it
+ imageView.setImageDrawable(getState().icon.getDrawable(mContext));
+ customTileTitle.setText(mTile.getCustomTile().label);
+ if (isDynamicTile()) {
+ customTilePkg.setText(R.string.quick_settings_dynamic_tile_detail_title);
+ } else {
+ customTilePkg.setText(mTile.getPackage());
+ customTileContentDesc.setText(mTile.getCustomTile().contentDescription);
+ }
+ } else {
+ switch (mExpandedStyle.getStyle()) {
+ case CustomTile.ExpandedStyle.GRID_STYLE:
+ rootView = QSDetailItemsGrid.inflate(context, parent, false);
+ mGridAdapter = ((QSDetailItemsGrid) rootView)
+ .createAndSetAdapter(mTile.getPackage(),
+ mExpandedStyle.getExpandedItems());
+ mGridAdapter.setOnPseudoGridItemClickListener(this);
+ break;
+ case CustomTile.ExpandedStyle.REMOTE_STYLE:
+ rootView = (LinearLayout) LayoutInflater.from(context)
+ .inflate(R.layout.qs_custom_detail_remote, parent, false);
+ RemoteViews remoteViews = mExpandedStyle.getContentViews();
+ if (remoteViews != null) {
+ View localView = mTile.getCustomTile().expandedStyle.getContentViews()
+ .apply(context, (ViewGroup) rootView,
+ mHost.getOnClickHandler(), getThemePackageName());
+ ((LinearLayout) rootView).addView(localView);
+ } else {
+ Log.d(TAG, "Unable to add null remoteview for " + mTile.getOpPkg());
+ }
+ break;
+ case CustomTile.ExpandedStyle.LIST_STYLE:
+ default:
+ rootView = QSDetailItemsList.convertOrInflate(context, convertView, parent);
+ ListView listView = ((QSDetailItemsList) rootView).getListView();
+ listView.setDivider(null);
+ listView.setOnItemClickListener(this);
+ listView.setAdapter(mListAdapter =
+ new QSDetailItemsList.QSCustomDetailListAdapter(mTile.getPackage(),
+ context, Arrays.asList(mExpandedStyle.getExpandedItems())));
+ break;
+ }
+ }
+ return rootView;
+ }
+
+ @Override
+ public void onItemClick(AdapterView<?> adapterView, View view, int position, long l) {
+ CustomTile.ExpandedItem item = mListAdapter.getItem(position);
+ sendEvent(item.onClickPendingIntent);
+ }
+
+ @Override
+ public void onPsuedoGridItemClick(View view, CustomTile.ExpandedItem item) {
+ sendEvent(item.onClickPendingIntent);
+ }
+
+ private void sendEvent(PendingIntent intent) {
+ try {
+ if (intent.isActivity()) {
+ mHost.collapsePanels();
+ }
+ intent.send();
+ } catch (PendingIntent.CanceledException e) {
+ //
+ }
+ }
+
+ private String getThemePackageName() {
+ final Configuration config = mContext.getResources().getConfiguration();
+ final ThemeConfig themeConfig = config != null ? config.themeConfig : null;
+ return themeConfig != null ? themeConfig.getOverlayForStatusBar() : null;
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java
index 781ab1c..0d43f8c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java
@@ -1,5 +1,6 @@
/*
* Copyright (C) 2015 The Android Open Source Project
+ * Copyright (C) 2015 The CyanogenMod Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -37,6 +38,7 @@ import com.android.systemui.SysUIToast;
import com.android.systemui.qs.QSTile;
import com.android.systemui.statusbar.policy.ZenModeController;
import com.android.systemui.volume.ZenModePanel;
+import cyanogenmod.app.StatusBarPanelCustomTile;
/** Quick settings tile: Do not disturb **/
public class DndTile extends QSTile<QSTile.BooleanState> {
@@ -121,6 +123,16 @@ public class DndTile extends QSTile<QSTile.BooleanState> {
}
@Override
+ protected void handleSecondaryClick() {
+ handleClick();
+ }
+
+ @Override
+ public void handleLongClick() {
+ mHost.startActivityDismissingKeyguard(ZEN_SETTINGS);
+ }
+
+ @Override
protected void handleUpdateState(BooleanState state, Object arg) {
final int zen = arg instanceof Integer ? (Integer) arg : mController.getZen();
final boolean newValue = zen != Global.ZEN_MODE_OFF;
@@ -129,7 +141,7 @@ public class DndTile extends QSTile<QSTile.BooleanState> {
state.visible = isVisible(mContext);
switch (zen) {
case Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS:
- state.icon = ResourceIcon.get(R.drawable.ic_qs_dnd_on);
+ state.icon = ResourceIcon.get(R.drawable.ic_qs_dnd_on_priority);
state.label = mContext.getString(R.string.quick_settings_dnd_priority_label);
state.contentDescription = mContext.getString(
R.string.accessibility_quick_settings_dnd_priority_on);
@@ -188,6 +200,11 @@ public class DndTile extends QSTile<QSTile.BooleanState> {
}
}
+ @Override
+ public boolean hasDualTargetsDetails() {
+ return true;
+ }
+
private final OnSharedPreferenceChangeListener mPrefListener
= new OnSharedPreferenceChangeListener() {
@Override
@@ -233,6 +250,11 @@ public class DndTile extends QSTile<QSTile.BooleanState> {
}
@Override
+ public StatusBarPanelCustomTile getCustomTile() {
+ return null;
+ }
+
+ @Override
public void setToggleState(boolean state) {
MetricsLogger.action(mContext, MetricsLogger.QS_DND_TOGGLE, state);
if (!state) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/EditTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/EditTile.java
new file mode 100644
index 0000000..7173786
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/EditTile.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2015 The CyanogenMod 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 com.android.systemui.R;
+import com.android.systemui.qs.QSTile;
+import com.android.systemui.statusbar.policy.KeyguardMonitor;
+import org.cyanogenmod.internal.logging.CMMetricsLogger;
+
+public class EditTile extends QSTile<QSTile.BooleanState> implements KeyguardMonitor.Callback {
+
+ private boolean mListening;
+
+ public EditTile(Host host) {
+ super(host);
+ refreshState();
+ }
+
+ @Override
+ protected void handleDestroy() {
+ super.handleDestroy();
+ }
+
+ @Override
+ protected BooleanState newTileState() {
+ return new BooleanState();
+ }
+
+ @Override
+ protected void handleClick() {
+ getHost().setEditing(!mState.value);
+ refreshState(!mState.value);
+ }
+
+ @Override
+ protected void handleLongClick() {
+ getHost().goToSettingsPage();
+ refreshState(true);
+ }
+
+ @Override
+ protected void handleUpdateState(BooleanState state, Object arg) {
+ final boolean showing = getHost().getKeyguardMonitor().isShowing();
+ final boolean secure = getHost().getKeyguardMonitor().isSecure();
+ state.visible = !showing || !secure;
+ state.enabled = !showing;
+ state.label = mContext.getString(R.string.quick_settings_edit_label);
+
+ if (arg instanceof Boolean) {
+ state.value = (boolean) arg;
+ } else {
+ state.value = getHost().isEditing();
+ }
+ state.icon = ResourceIcon.get(R.drawable.ic_qs_edit_tiles);
+ }
+
+ @Override
+ public int getMetricsCategory() {
+ return CMMetricsLogger.TILE_EDIT;
+ }
+
+ @Override
+ protected String composeChangeAnnouncement() {
+ // TODO
+ return null;
+ }
+
+ @Override
+ public void setListening(boolean listening) {
+ if (mListening == listening) return;
+ mListening = listening;
+ if (mListening) {
+ mHost.getKeyguardMonitor().addCallback(this);
+ } else {
+ mHost.getKeyguardMonitor().removeCallback(this);
+ }
+ refreshState();
+ }
+
+ @Override
+ public void onKeyguardChanged() {
+ refreshState();
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/HeadsUpTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/HeadsUpTile.java
new file mode 100644
index 0000000..d30bd91
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/HeadsUpTile.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2015 The CyanogenMod 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.provider.Settings;
+import android.provider.Settings.Global;
+
+import com.android.systemui.qs.GlobalSetting;
+import com.android.systemui.qs.QSTile;
+import com.android.systemui.R;
+
+import org.cyanogenmod.internal.logging.CMMetricsLogger;
+
+/** Quick settings tile: Heads up **/
+public class HeadsUpTile extends QSTile<QSTile.BooleanState> {
+
+ private static final Intent NOTIFICATION_SETTINGS =
+ new Intent("android.settings.NOTIFICATION_MANAGER");
+
+ private final GlobalSetting mSetting;
+
+ public HeadsUpTile(Host host) {
+ super(host);
+
+ mSetting = new GlobalSetting(mContext, mHandler, Global.HEADS_UP_NOTIFICATIONS_ENABLED) {
+ @Override
+ protected void handleValueChanged(int value) {
+ handleRefreshState(value);
+ }
+ };
+ }
+
+ @Override
+ protected BooleanState newTileState() {
+ return new BooleanState();
+ }
+
+ @Override
+ protected void handleClick() {
+ setEnabled(!mState.value);
+ refreshState();
+ }
+
+ @Override
+ protected void handleLongClick() {
+ mHost.startActivityDismissingKeyguard(NOTIFICATION_SETTINGS);
+ }
+
+ private void setEnabled(boolean enabled) {
+ Settings.Global.putInt(mContext.getContentResolver(),
+ Settings.Global.HEADS_UP_NOTIFICATIONS_ENABLED,
+ enabled ? 1 : 0);
+ }
+
+ @Override
+ protected void handleUpdateState(BooleanState state, Object arg) {
+ final int value = arg instanceof Integer ? (Integer)arg : mSetting.getValue();
+ final boolean headsUp = value != 0;
+ state.value = headsUp;
+ state.visible = true;
+ state.label = mContext.getString(R.string.quick_settings_heads_up_label);
+ if (headsUp) {
+ state.icon = ResourceIcon.get(R.drawable.ic_qs_heads_up_on);
+ state.contentDescription = mContext.getString(
+ R.string.accessibility_quick_settings_heads_up_on);
+ } else {
+ state.icon = ResourceIcon.get(R.drawable.ic_qs_heads_up_off);
+ state.contentDescription = mContext.getString(
+ R.string.accessibility_quick_settings_heads_up_off);
+ }
+ }
+
+ @Override
+ protected String composeChangeAnnouncement() {
+ if (mState.value) {
+ return mContext.getString(R.string.accessibility_quick_settings_heads_up_changed_on);
+ } else {
+ return mContext.getString(R.string.accessibility_quick_settings_heads_up_changed_off);
+ }
+ }
+
+ @Override
+ public int getMetricsCategory() {
+ return CMMetricsLogger.TILE_HEADS_UP;
+ }
+
+ @Override
+ public void setListening(boolean listening) {
+ // Do nothing
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java
index 7b83e6a..6080358 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java
@@ -1,5 +1,6 @@
/*
* Copyright (C) 2014 The Android Open Source Project
+ * Copyright (C) 2015 The CyanogenMod Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,37 +18,44 @@
package com.android.systemui.qs.tiles;
import android.content.BroadcastReceiver;
+import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
+import android.content.IntentFilter;
+import android.net.ConnectivityManager;
+import android.net.wifi.WifiDevice;
+import android.provider.Settings;
import com.android.internal.logging.MetricsLogger;
import com.android.systemui.Prefs;
import com.android.systemui.R;
import com.android.systemui.qs.QSTile;
import com.android.systemui.qs.UsageTracker;
+import com.android.systemui.statusbar.phone.SystemUIDialog;
import com.android.systemui.statusbar.policy.HotspotController;
+import java.util.List;
+
/** Quick settings tile: Hotspot **/
public class HotspotTile extends QSTile<QSTile.BooleanState> {
+
+ private static final Intent TETHER_SETTINGS = new Intent().setComponent(new ComponentName(
+ "com.android.settings", "com.android.settings.TetherSettings"));
+
private final AnimationIcon mEnable =
new AnimationIcon(R.drawable.ic_hotspot_enable_animation);
private final AnimationIcon mDisable =
new AnimationIcon(R.drawable.ic_hotspot_disable_animation);
private final HotspotController mController;
private final Callback mCallback = new Callback();
- private final UsageTracker mUsageTracker;
+ private final ConnectivityManager mConnectivityManager;
+ private boolean mListening;
+ private int mNumConnectedClients = 0;
public HotspotTile(Host host) {
super(host);
mController = host.getHotspotController();
- mUsageTracker = newUsageTracker(host.getContext());
- mUsageTracker.setListening(true);
- }
-
- @Override
- protected void handleDestroy() {
- super.handleDestroy();
- mUsageTracker.setListening(false);
+ mConnectivityManager = host.getContext().getSystemService(ConnectivityManager.class);
}
@Override
@@ -57,15 +65,31 @@ public class HotspotTile extends QSTile<QSTile.BooleanState> {
@Override
public void setListening(boolean listening) {
+ if (mListening == listening) return;
if (listening) {
mController.addCallback(mCallback);
+ mContext.registerReceiver(mTetherConnectStateChangedReceiver,
+ new IntentFilter(ConnectivityManager.TETHER_CONNECT_STATE_CHANGED));
} else {
mController.removeCallback(mCallback);
+ mContext.unregisterReceiver(mTetherConnectStateChangedReceiver);
}
+ mListening = listening;
}
@Override
protected void handleClick() {
+ boolean airplaneMode = (Settings.Global.getInt(mContext.getContentResolver(),
+ Settings.Global.AIRPLANE_MODE_ON, 0) == 1);
+ if (airplaneMode) {
+ SystemUIDialog d = new SystemUIDialog(mContext);
+ d.setTitle(R.string.quick_settings_hotspot_label);
+ d.setMessage(R.string.hotspot_apm_message);
+ d.setPositiveButton(com.android.internal.R.string.ok, null);
+ d.setShowForAllUsers(true);
+ d.show();
+ return;
+ }
final boolean isEnabled = (Boolean) mState.value;
MetricsLogger.action(mContext, getMetricsCategory(), !isEnabled);
mController.setHotspotEnabled(!isEnabled);
@@ -75,27 +99,25 @@ public class HotspotTile extends QSTile<QSTile.BooleanState> {
@Override
protected void handleLongClick() {
- if (mState.value) return; // don't allow usage reset if hotspot is active
- final String title = mContext.getString(R.string.quick_settings_reset_confirmation_title,
- mState.label);
- mUsageTracker.showResetConfirmation(title, new Runnable() {
- @Override
- public void run() {
- refreshState();
- }
- });
+ mHost.startActivityDismissingKeyguard(TETHER_SETTINGS);
}
@Override
protected void handleUpdateState(BooleanState state, Object arg) {
- state.visible = mController.isHotspotSupported() && mUsageTracker.isRecentlyUsed();
- state.label = mContext.getString(R.string.quick_settings_hotspot_label);
+ state.visible = mController.isHotspotSupported();
if (arg instanceof Boolean) {
state.value = (boolean) arg;
} else {
state.value = mController.isHotspotEnabled();
}
+ if (state.visible && state.value) {
+ state.label = mContext.getResources().getQuantityString(
+ R.plurals.wifi_hotspot_connected_clients_label, mNumConnectedClients,
+ mNumConnectedClients);
+ } else {
+ state.label = mContext.getString(R.string.quick_settings_hotspot_label);
+ }
state.icon = state.visible && state.value ? mEnable : mDisable;
}
@@ -113,10 +135,14 @@ public class HotspotTile extends QSTile<QSTile.BooleanState> {
}
}
- private static UsageTracker newUsageTracker(Context context) {
- return new UsageTracker(context, Prefs.Key.HOTSPOT_TILE_LAST_USED, HotspotTile.class,
- R.integer.days_to_show_hotspot_tile);
- }
+ private BroadcastReceiver mTetherConnectStateChangedReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ final List<WifiDevice> clients = mConnectivityManager.getTetherConnectedSta();
+ mNumConnectedClients = clients != null ? clients.size() : 0;
+ refreshState();
+ }
+ };
private final class Callback implements HotspotController.Callback {
@Override
@@ -124,20 +150,4 @@ public class HotspotTile extends QSTile<QSTile.BooleanState> {
refreshState(enabled);
}
};
-
- /**
- * This will catch broadcasts for changes in hotspot state so we can show
- * the hotspot tile for a number of days after use.
- */
- public static class APChangedReceiver extends BroadcastReceiver {
- private UsageTracker mUsageTracker;
-
- @Override
- public void onReceive(Context context, Intent intent) {
- if (mUsageTracker == null) {
- mUsageTracker = newUsageTracker(context);
- }
- mUsageTracker.trackUsage();
- }
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/LocationTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/LocationTile.java
index e6fade4..3598bba 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/LocationTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/LocationTile.java
@@ -1,5 +1,6 @@
/*
* Copyright (C) 2014 The Android Open Source Project
+ * Copyright (C) 2015 The CyanogenMod Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,32 +17,69 @@
package com.android.systemui.qs.tiles;
+import com.android.internal.logging.MetricsConstants;
import com.android.internal.logging.MetricsLogger;
+import android.content.Context;
+import android.content.Intent;
+import android.provider.Settings;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AbsListView;
+import android.widget.AdapterView;
+import android.widget.ArrayAdapter;
+import android.widget.CheckedTextView;
+import android.widget.ListView;
+
import com.android.systemui.R;
+import com.android.systemui.qs.QSDetailItems;
+import com.android.systemui.qs.QSDetailItemsList;
import com.android.systemui.qs.QSTile;
import com.android.systemui.statusbar.policy.KeyguardMonitor;
import com.android.systemui.statusbar.policy.LocationController;
import com.android.systemui.statusbar.policy.LocationController.LocationSettingsChangeCallback;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import cyanogenmod.app.StatusBarPanelCustomTile;
+
/** Quick settings tile: Location **/
public class LocationTile extends QSTile<QSTile.BooleanState> {
+ private static final Intent LOCATION_SETTINGS_INTENT
+ = new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS);
+ public static final Integer[] LOCATION_SETTINGS = new Integer[]{
+ Settings.Secure.LOCATION_MODE_BATTERY_SAVING,
+ Settings.Secure.LOCATION_MODE_SENSORS_ONLY,
+ Settings.Secure.LOCATION_MODE_HIGH_ACCURACY
+ };
+
private final AnimationIcon mEnable =
new AnimationIcon(R.drawable.ic_signal_location_enable_animation);
private final AnimationIcon mDisable =
new AnimationIcon(R.drawable.ic_signal_location_disable_animation);
private final LocationController mController;
+ private final LocationDetailAdapter mDetailAdapter;
private final KeyguardMonitor mKeyguard;
private final Callback mCallback = new Callback();
+ private final List<Integer> mLocationList = new ArrayList<Integer>();
public LocationTile(Host host) {
super(host);
mController = host.getLocationController();
+ mDetailAdapter = new LocationDetailAdapter();
mKeyguard = host.getKeyguardMonitor();
}
@Override
+ public DetailAdapter getDetailAdapter() {
+ return mDetailAdapter;
+ }
+
+ @Override
protected BooleanState newTileState() {
return new BooleanState();
}
@@ -59,32 +97,74 @@ public class LocationTile extends QSTile<QSTile.BooleanState> {
@Override
protected void handleClick() {
- final boolean wasEnabled = (Boolean) mState.value;
- MetricsLogger.action(mContext, getMetricsCategory(), !wasEnabled);
- mController.setLocationEnabled(!wasEnabled);
+ if(mController.isAdvancedSettingsEnabled()) {
+ showDetail(true);
+ } else {
+ boolean wasEnabled = mController.isLocationEnabled();
+ mController.setLocationEnabled(!wasEnabled);
+ MetricsLogger.action(mContext, getMetricsCategory(), !wasEnabled);
+ refreshState();
+ }
+
mEnable.setAllowAnimation(true);
mDisable.setAllowAnimation(true);
}
@Override
+ protected void handleLongClick() {
+ mHost.startActivityDismissingKeyguard(LOCATION_SETTINGS_INTENT);
+ }
+
+ @Override
protected void handleUpdateState(BooleanState state, Object arg) {
- final boolean locationEnabled = mController.isLocationEnabled();
+ final int currentState = mController.getLocationCurrentState();
// Work around for bug 15916487: don't show location tile on top of lock screen. After the
// bug is fixed, this should be reverted to only hiding it on secure lock screens:
// state.visible = !(mKeyguard.isSecure() && mKeyguard.isShowing());
state.visible = !mKeyguard.isShowing();
- state.value = locationEnabled;
- if (locationEnabled) {
- state.icon = mEnable;
- state.label = mContext.getString(R.string.quick_settings_location_label);
- state.contentDescription = mContext.getString(
- R.string.accessibility_quick_settings_location_on);
- } else {
- state.icon = mDisable;
- state.label = mContext.getString(R.string.quick_settings_location_label);
- state.contentDescription = mContext.getString(
- R.string.accessibility_quick_settings_location_off);
+ state.label = mContext.getString(getStateLabelRes(currentState));
+
+ switch (currentState) {
+ case Settings.Secure.LOCATION_MODE_OFF:
+ state.contentDescription = mContext.getString(
+ R.string.accessibility_quick_settings_location_off);
+ state.icon = mDisable;
+ break;
+ case Settings.Secure.LOCATION_MODE_BATTERY_SAVING:
+ state.contentDescription = mContext.getString(
+ R.string.accessibility_quick_settings_location_battery_saving);
+ state.icon = ResourceIcon.get(R.drawable.ic_qs_location_battery_saving);
+ break;
+ case Settings.Secure.LOCATION_MODE_SENSORS_ONLY:
+ state.contentDescription = mContext.getString(
+ R.string.accessibility_quick_settings_location_gps_only);
+ state.icon = mEnable;
+ break;
+ case Settings.Secure.LOCATION_MODE_HIGH_ACCURACY:
+ state.contentDescription = mContext.getString(
+ R.string.accessibility_quick_settings_location_high_accuracy);
+ state.icon = mEnable;
+ break;
+ default:
+ state.contentDescription = mContext.getString(
+ R.string.accessibility_quick_settings_location_on);
+ state.icon = mEnable;
+ }
+ }
+
+ private int getStateLabelRes(int currentState) {
+ switch (currentState) {
+ case Settings.Secure.LOCATION_MODE_OFF:
+ return R.string.quick_settings_location_off_label;
+ case Settings.Secure.LOCATION_MODE_BATTERY_SAVING:
+ return R.string.quick_settings_location_battery_saving_label;
+ case Settings.Secure.LOCATION_MODE_SENSORS_ONLY:
+ return R.string.quick_settings_location_gps_only_label;
+ case Settings.Secure.LOCATION_MODE_HIGH_ACCURACY:
+ return R.string.quick_settings_location_high_accuracy_label;
+ default:
+ return R.string.quick_settings_location_label;
}
}
@@ -95,10 +175,22 @@ public class LocationTile extends QSTile<QSTile.BooleanState> {
@Override
protected String composeChangeAnnouncement() {
- if (mState.value) {
- return mContext.getString(R.string.accessibility_quick_settings_location_changed_on);
- } else {
- return mContext.getString(R.string.accessibility_quick_settings_location_changed_off);
+ switch (mController.getLocationCurrentState()) {
+ case Settings.Secure.LOCATION_MODE_OFF:
+ return mContext.getString(
+ R.string.accessibility_quick_settings_location_changed_off);
+ case Settings.Secure.LOCATION_MODE_BATTERY_SAVING:
+ return mContext.getString(
+ R.string.accessibility_quick_settings_location_changed_battery_saving);
+ case Settings.Secure.LOCATION_MODE_SENSORS_ONLY:
+ return mContext.getString(
+ R.string.accessibility_quick_settings_location_changed_gps_only);
+ case Settings.Secure.LOCATION_MODE_HIGH_ACCURACY:
+ return mContext.getString(
+ R.string.accessibility_quick_settings_location_changed_high_accuracy);
+ default:
+ return mContext.getString(
+ R.string.accessibility_quick_settings_location_changed_on);
}
}
@@ -114,4 +206,90 @@ public class LocationTile extends QSTile<QSTile.BooleanState> {
refreshState();
}
};
+
+ private class AdvancedLocationAdapter extends ArrayAdapter<Integer> {
+ public AdvancedLocationAdapter(Context context) {
+ super(context, android.R.layout.simple_list_item_single_choice, mLocationList);
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ LayoutInflater inflater = LayoutInflater.from(mContext);
+ CheckedTextView label = (CheckedTextView) inflater.inflate(
+ android.R.layout.simple_list_item_single_choice, parent, false);
+ label.setText(getStateLabelRes(getItem(position)));
+ return label;
+ }
+ }
+
+ private class LocationDetailAdapter implements DetailAdapter, AdapterView.OnItemClickListener {
+
+ private AdvancedLocationAdapter mAdapter;
+ private QSDetailItemsList mDetails;
+
+ @Override
+ public int getTitle() {
+ return R.string.quick_settings_location_detail_title;
+ }
+
+ @Override
+ public Boolean getToggleState() {
+ boolean state = mController.getLocationCurrentState()
+ != Settings.Secure.LOCATION_MODE_OFF;
+ rebuildLocationList(state);
+ return state;
+ }
+
+ @Override
+ public Intent getSettingsIntent() {
+ return LOCATION_SETTINGS_INTENT;
+ }
+
+ @Override
+ public StatusBarPanelCustomTile getCustomTile() {
+ return null;
+ }
+
+ @Override
+ public void setToggleState(boolean state) {
+ mController.setLocationEnabled(state);
+ rebuildLocationList(state);
+ fireToggleStateChanged(state);
+ }
+
+ @Override
+ public int getMetricsCategory() {
+ return MetricsLogger.QS_LOCATION;
+ }
+
+ @Override
+ public View createDetailView(Context context, View convertView, ViewGroup parent) {
+ mDetails = QSDetailItemsList.convertOrInflate(context, convertView, parent);
+ mDetails.setEmptyState(R.drawable.ic_qs_location_off,
+ R.string.accessibility_quick_settings_location_off);
+ mAdapter = new LocationTile.AdvancedLocationAdapter(context);
+ mDetails.setAdapter(mAdapter);
+
+ final ListView list = mDetails.getListView();
+ list.setChoiceMode(AbsListView.CHOICE_MODE_SINGLE);
+ list.setOnItemClickListener(this);
+
+ return mDetails;
+ }
+
+ private void rebuildLocationList(boolean populate) {
+ mLocationList.clear();
+ if (populate) {
+ mLocationList.addAll(Arrays.asList(LOCATION_SETTINGS));
+ mDetails.getListView().setItemChecked(mAdapter.getPosition(
+ mController.getLocationCurrentState()), true);
+ }
+ mAdapter.notifyDataSetChanged();
+ }
+
+ @Override
+ public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
+ mController.setLocationMode((Integer) parent.getItemAtPosition(position));
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/LockscreenToggleTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/LockscreenToggleTile.java
new file mode 100644
index 0000000..a147d30
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/LockscreenToggleTile.java
@@ -0,0 +1,163 @@
+/*
+ * Copyright (C) 2015-2016 The CyanogenMod 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.Intent;
+import android.os.UserHandle;
+import com.android.systemui.R;
+import com.android.systemui.SystemUIApplication;
+import com.android.systemui.keyguard.KeyguardViewMediator;
+import com.android.systemui.qs.QSTile;
+import com.android.systemui.statusbar.policy.KeyguardMonitor;
+
+import cyanogenmod.app.Profile;
+import cyanogenmod.app.ProfileManager;
+import cyanogenmod.providers.CMSettings;
+import org.cyanogenmod.internal.logging.CMMetricsLogger;
+
+public class LockscreenToggleTile extends QSTile<QSTile.BooleanState>
+ implements KeyguardMonitor.Callback {
+
+ private static final Intent LOCK_SCREEN_SETTINGS =
+ new Intent("android.settings.LOCK_SCREEN_SETTINGS");
+
+ private KeyguardMonitor mKeyguard;
+ private boolean mListening;
+
+ private KeyguardViewMediator.LockscreenEnabledSettingsObserver mSettingsObserver;
+
+ public LockscreenToggleTile(Host host) {
+ super(host);
+
+ mKeyguard = host.getKeyguardMonitor();
+
+ mSettingsObserver = new KeyguardViewMediator.LockscreenEnabledSettingsObserver(mContext,
+ mUiHandler) {
+
+ @Override
+ public void update() {
+ refreshState();
+ }
+ };
+
+ }
+
+ @Override
+ public void setListening(boolean listening) {
+ if (mListening == listening) {
+ return;
+ }
+ mListening = listening;
+ if (listening) {
+ mSettingsObserver.observe();
+ mKeyguard.addCallback(this);
+ refreshState();
+ } else {
+ mSettingsObserver.unobserve();
+ mKeyguard.removeCallback(this);
+ }
+ }
+
+ @Override
+ protected BooleanState newTileState() {
+ return new BooleanState();
+ }
+
+ @Override
+ protected void handleClick() {
+ final boolean newState = !getState().value;
+ setPersistedState(newState);
+ refreshState(newState);
+ }
+
+ @Override
+ protected void handleLongClick() {
+ mHost.startActivityDismissingKeyguard(LOCK_SCREEN_SETTINGS);
+ }
+
+ @Override
+ protected void handleUpdateState(BooleanState state, Object arg) {
+ KeyguardViewMediator mediator = ((SystemUIApplication)
+ mContext.getApplicationContext()).getComponent(KeyguardViewMediator.class);
+
+ if (mediator == null) {
+ state.visible = false;
+ state.value = false;
+ state.enabled = false;
+ } else {
+ final boolean lockscreenEnforced = mediator.lockscreenEnforcedByDevicePolicy();
+ final boolean lockscreenEnabled = lockscreenEnforced ||
+ arg != null ? (Boolean) arg : mediator.getKeyguardEnabledInternal();
+
+ state.visible = mediator.isKeyguardBound();
+
+ if (mediator.isProfileDisablingKeyguard()) {
+ state.value = false;
+ state.enabled = false;
+ state.label = mContext.getString(
+ R.string.quick_settings_lockscreen_label_locked_by_profile);
+ } else if (lockscreenEnforced) {
+ state.value = true;
+ state.enabled = false;
+ state.label = mContext.getString(
+ R.string.quick_settings_lockscreen_label_enforced);
+ } else {
+ state.value = lockscreenEnabled;
+ state.enabled = !mKeyguard.isShowing() || !mKeyguard.isSecure();
+
+ state.label = mContext.getString(R.string.quick_settings_lockscreen_label);
+ }
+ // update icon
+ if (lockscreenEnabled) {
+ state.icon = ResourceIcon.get(R.drawable.ic_qs_lock_screen_on);
+ state.contentDescription = mContext.getString(
+ R.string.accessibility_quick_settings_lock_screen_on);
+ } else {
+ state.icon = ResourceIcon.get(R.drawable.ic_qs_lock_screen_off);
+ state.contentDescription = mContext.getString(
+ R.string.accessibility_quick_settings_lock_screen_off);
+ }
+ }
+ }
+
+ @Override
+ public int getMetricsCategory() {
+ return CMMetricsLogger.TILE_LOCKSCREEN_TOGGLE;
+ }
+
+ @Override
+ protected String composeChangeAnnouncement() {
+ if (mState.value) {
+ return mContext.getString(
+ R.string.accessibility_quick_settings_lock_screen_changed_on);
+ } else {
+ return mContext.getString(
+ R.string.accessibility_quick_settings_lock_screen_changed_off);
+ }
+ }
+
+ @Override
+ public void onKeyguardChanged() {
+ refreshState();
+ }
+
+ private void setPersistedState(boolean enabled) {
+ CMSettings.Secure.putIntForUser(mContext.getContentResolver(),
+ CMSettings.Secure.LOCKSCREEN_INTERNALLY_ENABLED,
+ enabled ? 1 : 0, UserHandle.USER_CURRENT);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/NfcTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/NfcTile.java
new file mode 100644
index 0000000..a5ffd23
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/NfcTile.java
@@ -0,0 +1,158 @@
+/*
+ * Copyright (C) 2015 The CyanogenMod 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.nfc.NfcAdapter;
+
+import android.util.Log;
+import com.android.systemui.R;
+import com.android.systemui.qs.QSTile;
+import org.cyanogenmod.internal.logging.CMMetricsLogger;
+import org.cyanogenmod.internal.util.QSUtils;
+
+public class NfcTile extends QSTile<QSTile.BooleanState> {
+
+ private boolean mListening;
+
+ private BroadcastReceiver mReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ refreshState();
+ }
+ };
+ private final boolean mSupportsNfc;
+
+ public NfcTile(Host host) {
+ super(host);
+ mSupportsNfc = QSUtils.deviceSupportsNfc(mContext);
+ }
+
+ @Override
+ protected BooleanState newTileState() {
+ return new BooleanState();
+ }
+
+ @Override
+ protected void handleClick() {
+ boolean newState = !getState().value;
+ setState(newState);
+ refreshState();
+ }
+
+ @Override
+ protected void handleLongClick() {
+ mHost.startActivityDismissingKeyguard(new Intent("android.settings.NFC_SETTINGS"));
+ }
+
+ private void setState(boolean on) {
+ try {
+ NfcAdapter nfcAdapter = NfcAdapter.getNfcAdapter(mContext);
+ if (nfcAdapter == null) {
+ Log.e(TAG, "tried to set NFC state, but no NFC adapter was found");
+ return;
+ }
+ if (on) {
+ nfcAdapter.enable();
+ } else {
+ nfcAdapter.disable();
+ }
+ } catch (UnsupportedOperationException e) {
+ // ignore
+ }
+ }
+
+ private int getNfcAdapterState() {
+ try {
+ NfcAdapter nfcAdapter = NfcAdapter.getNfcAdapter(mContext);
+ if (nfcAdapter == null) {
+ Log.e(TAG, "tried to get NFC state, but no NFC adapter was found");
+ return NfcAdapter.STATE_OFF;
+ }
+ return nfcAdapter.getAdapterState();
+ } catch (UnsupportedOperationException e) {
+ // ignore
+ return NfcAdapter.STATE_OFF;
+ }
+ }
+
+ /**
+ * Helper method to encapsulate intermediate states (turning off/on) to help determine whether
+ * the adapter will be on or off.
+ * @param nfcState The current NFC adapter state.
+ * @return boolean representing what state the adapter is/will be in
+ */
+ private static boolean isEnabled(int nfcState) {
+ switch (nfcState) {
+ case NfcAdapter.STATE_TURNING_ON:
+ case NfcAdapter.STATE_ON:
+ return true;
+ case NfcAdapter.STATE_TURNING_OFF:
+ case NfcAdapter.STATE_OFF:
+ default:
+ return false;
+ }
+ }
+
+ /**
+ * Helper method to determine intermediate states
+ * @param nfcState The current NFC adapter state.
+ * @return boolean representing if the adapter is in an intermediate state
+ */
+ private static boolean isEnablingDisabling(int nfcState) {
+ switch (nfcState) {
+ case NfcAdapter.STATE_TURNING_OFF:
+ case NfcAdapter.STATE_TURNING_ON:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ @Override
+ protected void handleUpdateState(BooleanState state, Object arg) {
+ state.visible = mSupportsNfc;
+ final int nfcState = getNfcAdapterState();
+ state.value = mSupportsNfc && isEnabled(nfcState);
+ state.enabled = mSupportsNfc && !isEnablingDisabling(nfcState);
+
+ state.icon = ResourceIcon.get(state.value ?
+ R.drawable.ic_qs_nfc_on : R.drawable.ic_qs_nfc_off);
+ state.label = mContext.getString(R.string.quick_settings_nfc_label);
+ }
+
+ @Override
+ public int getMetricsCategory() {
+ return CMMetricsLogger.TILE_NFC;
+ }
+
+ @Override
+ public void setListening(boolean listening) {
+ if (mListening == listening) return;
+ mListening = listening;
+ if (listening) {
+ mContext.registerReceiver(mReceiver,
+ new IntentFilter(NfcAdapter.ACTION_ADAPTER_STATE_CHANGED));
+ refreshState();
+ } else {
+ mContext.unregisterReceiver(mReceiver);
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/PerfProfileTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/PerfProfileTile.java
new file mode 100644
index 0000000..4863683
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/PerfProfileTile.java
@@ -0,0 +1,258 @@
+/*
+ * Copyright (C) 2015 The CyanogenMod 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.content.res.Resources;
+import android.database.ContentObserver;
+import android.os.Handler;
+import android.os.PowerManager;
+import android.provider.Settings;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AdapterView;
+import android.widget.ArrayAdapter;
+import android.widget.ListView;
+
+import com.android.systemui.R;
+import com.android.systemui.qs.QSDetailItemsList;
+import com.android.systemui.qs.QSTile;
+
+import org.cyanogenmod.internal.logging.CMMetricsLogger;
+import org.cyanogenmod.internal.util.QSUtils;
+
+import cyanogenmod.app.StatusBarPanelCustomTile;
+import cyanogenmod.power.PerformanceManager;
+import cyanogenmod.providers.CMSettings;
+
+public class PerfProfileTile extends QSTile<PerfProfileTile.ProfileState> {
+
+ private static final Intent BATTERY_SETTINGS = new Intent(Intent.ACTION_POWER_USAGE_SUMMARY);
+
+ private final String[] mEntries;
+ private final String[] mDescriptionEntries;
+ private final String[] mAnnouncementEntries;
+ private final int[] mPerfProfileValues;
+ private final int mNumPerfProfiles;
+ private final Icon mIcon;
+
+ private final PowerManager mPm;
+ private final PerformanceManager mPerformanceManager;
+ private boolean mListening;
+
+ private PerformanceProfileObserver mObserver;
+
+ public PerfProfileTile(Host host) {
+ super(host);
+ mObserver = new PerformanceProfileObserver(mHandler);
+ mPm = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
+ mPerformanceManager = PerformanceManager.getInstance(mContext);
+ mNumPerfProfiles = mPerformanceManager.getNumberOfProfiles();
+
+ mPerfProfileValues = new int[mNumPerfProfiles];
+ mEntries = new String[mNumPerfProfiles];
+ mDescriptionEntries = new String[mNumPerfProfiles];
+ mAnnouncementEntries = new String[mNumPerfProfiles];
+
+ mIcon = ResourceIcon.get(R.drawable.ic_qs_perf_profile);
+
+ // Filter out unsupported profiles
+ Resources res = mContext.getResources();
+ final int[] perfProfileValues = res.getIntArray(
+ org.cyanogenmod.platform.internal.R.array.perf_profile_values);
+ final String[] entries = res.getStringArray(
+ org.cyanogenmod.platform.internal.R.array.perf_profile_entries);
+ final String[] descriptionEntries = res.getStringArray(
+ R.array.perf_profile_description);
+ final String[] announcementEntries = res.getStringArray(
+ R.array.perf_profile_announcement);
+ int i = 0;
+
+ for (int j = 0; j < perfProfileValues.length; j++) {
+ if (perfProfileValues[j] < mNumPerfProfiles) {
+ mPerfProfileValues[i] = perfProfileValues[j];
+ mEntries[i] = entries[j];
+ mDescriptionEntries[i] = descriptionEntries[j];
+ mAnnouncementEntries[i] = announcementEntries[j];
+ i++;
+ }
+ }
+ }
+
+ @Override
+ protected ProfileState newTileState() {
+ return new ProfileState();
+ }
+
+ @Override
+ protected void handleClick() {
+ showDetail(true);
+ }
+
+ @Override
+ public DetailAdapter getDetailAdapter() {
+ return new PerfProfileDetailAdapter();
+ }
+
+ @Override
+ protected void handleLongClick() {
+ mHost.startActivityDismissingKeyguard(BATTERY_SETTINGS);
+ }
+
+ @Override
+ protected void handleUpdateState(ProfileState state, Object arg) {
+ state.visible = mPerformanceManager.getNumberOfProfiles() > 0;
+ state.profile = arg == null ? getCurrentProfileIndex() : (Integer) arg;
+ state.label = mEntries[state.profile];
+ state.icon = mIcon;
+ state.contentDescription = mDescriptionEntries[state.profile];
+ }
+
+ @Override
+ public int getMetricsCategory() {
+ return CMMetricsLogger.TILE_PERF_PROFILE;
+ }
+
+ @Override
+ protected String composeChangeAnnouncement() {
+ return mAnnouncementEntries[getCurrentProfileIndex()];
+ }
+
+ @Override
+ public void setListening(boolean listening) {
+ if (mListening == listening) return;
+ mListening = listening;
+ if (listening) {
+ mObserver.startObserving();
+ } else {
+ mObserver.endObserving();
+ }
+ }
+
+ private class PerformanceProfileObserver extends ContentObserver {
+ public PerformanceProfileObserver(Handler handler) {
+ super(handler);
+ }
+
+ @Override
+ public void onChange(boolean selfChange) {
+ refreshState(getCurrentProfileIndex());
+ }
+
+ public void startObserving() {
+ mContext.getContentResolver().registerContentObserver(
+ CMSettings.Secure.getUriFor(CMSettings.Secure.PERFORMANCE_PROFILE),
+ false, this);
+ }
+
+ public void endObserving() {
+ mContext.getContentResolver().unregisterContentObserver(this);
+ }
+ }
+
+ private int getCurrentProfileIndex() {
+ int index = 0;
+ int perfProfile = mPerformanceManager.getPowerProfile();
+
+ int count = mPerfProfileValues.length;
+ for (int i = 0; i < count; i++) {
+ if (mPerfProfileValues[i] == perfProfile) {
+ index = i;
+ break;
+ }
+ }
+
+ return index;
+ }
+
+ private void changeToProfile(int profileIndex) {
+ mPerformanceManager.setPowerProfile(mPerfProfileValues[profileIndex]); // content observer will notify
+ }
+
+ public static class ProfileState extends QSTile.State {
+ public int profile;
+
+ @Override
+ public boolean copyTo(State other) {
+ final ProfileState o = (ProfileState) other;
+ final boolean changed = profile != o.profile;
+ return super.copyTo(other) || changed;
+ }
+
+ @Override
+ protected StringBuilder toStringBuilder() {
+ final StringBuilder rt = super.toStringBuilder();
+ rt.insert(rt.length() - 1, ",profile=" + profile);
+ return rt;
+ }
+ }
+
+ private class PerfProfileDetailAdapter implements DetailAdapter,
+ AdapterView.OnItemClickListener {
+ private QSDetailItemsList mItems;
+
+ @Override
+ public int getTitle() {
+ return R.string.quick_settings_performance_profile_detail_title;
+ }
+
+ @Override
+ public Boolean getToggleState() {
+ return null;
+ }
+
+ @Override
+ public Intent getSettingsIntent() {
+ return BATTERY_SETTINGS;
+ }
+
+ @Override
+ public StatusBarPanelCustomTile getCustomTile() {
+ return null;
+ }
+
+ @Override
+ public void setToggleState(boolean state) {
+ // noop
+ }
+
+ @Override
+ public int getMetricsCategory() {
+ return CMMetricsLogger.TILE_PERF_PROFILE_DETAIL;
+ }
+
+ @Override
+ public View createDetailView(Context context, View convertView, ViewGroup parent) {
+ mItems = QSDetailItemsList.convertOrInflate(context, convertView, parent);
+ ArrayAdapter adapter = new ArrayAdapter<String>(mContext,
+ android.R.layout.simple_list_item_single_choice, mEntries);
+ ListView listView = mItems.getListView();
+ listView.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
+ listView.setAdapter(adapter);
+ listView.setOnItemClickListener(this);
+ listView.setDivider(null);
+ listView.setItemChecked(getCurrentProfileIndex(), true);
+ return mItems;
+ }
+
+ @Override
+ public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
+ changeToProfile(position);
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/ProfilesTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/ProfilesTile.java
new file mode 100644
index 0000000..6f65f6c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/ProfilesTile.java
@@ -0,0 +1,287 @@
+/*
+ * Copyright (C) 2015 The CyanogenMod 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.database.ContentObserver;
+import android.media.AudioManager;
+import android.os.Handler;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AbsListView;
+import android.widget.AdapterView;
+import android.widget.ArrayAdapter;
+import android.widget.CheckedTextView;
+import android.widget.ListView;
+
+import com.android.systemui.R;
+import com.android.systemui.qs.QSDetailItemsList;
+import com.android.systemui.qs.QSTile;
+import com.android.systemui.statusbar.policy.KeyguardMonitor;
+
+import cyanogenmod.app.Profile;
+import cyanogenmod.app.ProfileManager;
+import cyanogenmod.app.StatusBarPanelCustomTile;
+import cyanogenmod.providers.CMSettings;
+import org.cyanogenmod.internal.logging.CMMetricsLogger;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.UUID;
+
+public class ProfilesTile extends QSTile<QSTile.State> implements KeyguardMonitor.Callback {
+
+ private static final Intent PROFILES_SETTINGS =
+ new Intent("android.settings.PROFILES_SETTINGS");
+
+ private boolean mListening;
+ private ProfilesObserver mObserver;
+ private ProfileManager mProfileManager;
+ private QSDetailItemsList mDetails;
+ private ProfileAdapter mAdapter;
+ private KeyguardMonitor mKeyguardMonitor;
+
+ public ProfilesTile(Host host) {
+ super(host);
+ mProfileManager = ProfileManager.getInstance(mContext);
+ mObserver = new ProfilesObserver(mHandler);
+ mKeyguardMonitor = host.getKeyguardMonitor();
+ mKeyguardMonitor.addCallback(this);
+ }
+
+ @Override
+ protected void handleDestroy() {
+ mKeyguardMonitor.removeCallback(this);
+ }
+
+ @Override
+ protected State newTileState() {
+ return new State();
+ }
+
+ @Override
+ protected void handleClick() {
+ showDetail(true);
+ }
+
+ @Override
+ protected void handleLongClick() {
+ mHost.startActivityDismissingKeyguard(PROFILES_SETTINGS);
+ }
+
+ @Override
+ protected void handleUpdateState(State state, Object arg) {
+ state.visible = true;
+
+
+
+ state.enabled = !mKeyguardMonitor.isShowing() || !mKeyguardMonitor.isSecure();
+ if (profilesEnabled()) {
+ state.icon = ResourceIcon.get(R.drawable.ic_qs_profiles_on);
+ state.label = mProfileManager.getActiveProfile().getName();
+ state.contentDescription = mContext.getString(
+ R.string.accessibility_quick_settings_profiles, state.label);
+ } else {
+ state.icon = ResourceIcon.get(R.drawable.ic_qs_profiles_off);
+ state.label = mContext.getString(R.string.quick_settings_profiles_off);
+ state.contentDescription = mContext.getString(
+ R.string.accessibility_quick_settings_profiles_off);
+ }
+ }
+
+ @Override
+ protected String composeChangeAnnouncement() {
+ if (profilesEnabled()) {
+ return mContext.getString(R.string.accessibility_quick_settings_profiles_changed,
+ mState.label);
+ } else {
+ return mContext.getString(R.string.accessibility_quick_settings_profiles_changed_off);
+ }
+ }
+
+ private boolean profilesEnabled() {
+ return CMSettings.System.getInt(mContext.getContentResolver(),
+ CMSettings.System.SYSTEM_PROFILES_ENABLED, 1) == 1;
+ }
+
+ @Override
+ public int getMetricsCategory() {
+ return CMMetricsLogger.TILE_PROFILES;
+ }
+
+ @Override
+ public void setListening(boolean listening) {
+ if (mListening == listening) return;
+ mListening = listening;
+ if (listening) {
+ mObserver.startObserving();
+ final IntentFilter filter = new IntentFilter();
+ filter.addAction(ProfileManager.INTENT_ACTION_PROFILE_SELECTED);
+ filter.addAction(ProfileManager.INTENT_ACTION_PROFILE_UPDATED);
+ mContext.registerReceiver(mReceiver, filter);
+ refreshState();
+ } else {
+ mObserver.endObserving();
+ mContext.unregisterReceiver(mReceiver);
+ }
+ }
+
+ @Override
+ public DetailAdapter getDetailAdapter() {
+ return new ProfileDetailAdapter();
+ }
+
+ @Override
+ public void onKeyguardChanged() {
+ refreshState();
+ }
+
+ private class ProfileAdapter extends ArrayAdapter<Profile> {
+ public ProfileAdapter(Context context, List<Profile> profiles) {
+ super(context, android.R.layout.simple_list_item_single_choice, profiles);
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ LayoutInflater inflater = LayoutInflater.from(mContext);
+ CheckedTextView label = (CheckedTextView) inflater.inflate(
+ android.R.layout.simple_list_item_single_choice, parent, false);
+
+ Profile p = getItem(position);
+ label.setText(p.getName());
+
+ return label;
+ }
+ }
+
+ private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (ProfileManager.INTENT_ACTION_PROFILE_SELECTED.equals(intent.getAction())
+ || ProfileManager.INTENT_ACTION_PROFILE_UPDATED.equals(intent.getAction())) {
+ refreshState();
+ }
+ }
+ };
+
+ public class ProfileDetailAdapter implements DetailAdapter, AdapterView.OnItemClickListener {
+
+ private List<Profile> mProfilesList;
+
+ @Override
+ public int getTitle() {
+ return R.string.quick_settings_profiles;
+ }
+
+ @Override
+ public Boolean getToggleState() {
+ boolean enabled = profilesEnabled();
+ rebuildProfilesList(enabled);
+ return enabled;
+ }
+
+ @Override
+ public int getMetricsCategory() {
+ return CMMetricsLogger.TILE_PROFILES_DETAIL;
+ }
+
+ @Override
+ public View createDetailView(Context context, View convertView, ViewGroup parent) {
+ mDetails = QSDetailItemsList.convertOrInflate(context, convertView, parent);
+ mProfilesList = new ArrayList<>();
+ mDetails.setAdapter(mAdapter = new ProfileAdapter(context, mProfilesList));
+
+ final ListView list = mDetails.getListView();
+ list.setChoiceMode(AbsListView.CHOICE_MODE_SINGLE);
+ list.setOnItemClickListener(this);
+
+ mDetails.setEmptyState(R.drawable.ic_qs_profiles_off,
+ R.string.quick_settings_profiles_off);
+
+ return mDetails;
+ }
+
+ private void rebuildProfilesList(boolean populate) {
+ mProfilesList.clear();
+ if (populate) {
+ int selected = -1;
+
+ final Profile[] profiles = mProfileManager.getProfiles();
+ final Profile activeProfile = mProfileManager.getActiveProfile();
+ final UUID activeUuid = activeProfile != null ? activeProfile.getUuid() : null;
+
+ for (int i = 0; i < profiles.length; i++) {
+ mProfilesList.add(profiles[i]);
+ if (activeUuid != null && activeUuid.equals(profiles[i].getUuid())) {
+ selected = i;
+ }
+ }
+ mDetails.getListView().setItemChecked(selected, true);
+ }
+ mAdapter.notifyDataSetChanged();
+ }
+
+ @Override
+ public Intent getSettingsIntent() {
+ return PROFILES_SETTINGS;
+ }
+
+ @Override
+ public StatusBarPanelCustomTile getCustomTile() {
+ return null;
+ }
+
+ @Override
+ public void setToggleState(boolean state) {
+ CMSettings.System.putInt(mContext.getContentResolver(),
+ CMSettings.System.SYSTEM_PROFILES_ENABLED, state ? 1 : 0);
+ fireToggleStateChanged(state);
+ rebuildProfilesList(state);
+ }
+
+ @Override
+ public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
+ Profile selected = (Profile) parent.getItemAtPosition(position);
+ mProfileManager.setActiveProfile(selected.getUuid());
+ }
+ }
+
+ private class ProfilesObserver extends ContentObserver {
+ public ProfilesObserver(Handler handler) {
+ super(handler);
+ }
+
+ @Override
+ public void onChange(boolean selfChange) {
+ refreshState();
+ }
+
+ public void startObserving() {
+ mContext.getContentResolver().registerContentObserver(
+ CMSettings.System.getUriFor(CMSettings.System.SYSTEM_PROFILES_ENABLED),
+ false, this);
+ }
+
+ public void endObserving() {
+ mContext.getContentResolver().unregisterContentObserver(this);
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/RotationLockTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/RotationLockTile.java
index 1a26a4d..f074d9d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/RotationLockTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/RotationLockTile.java
@@ -1,5 +1,6 @@
/*
* Copyright (C) 2014 The Android Open Source Project
+ * Copyright (C) 2015 The CyanogenMod Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,6 +17,7 @@
package com.android.systemui.qs.tiles;
+import android.content.Intent;
import android.content.res.Configuration;
import com.android.internal.logging.MetricsLogger;
@@ -26,6 +28,10 @@ import com.android.systemui.statusbar.policy.RotationLockController.RotationLock
/** Quick settings tile: Rotation **/
public class RotationLockTile extends QSTile<QSTile.BooleanState> {
+
+ private static final Intent DISPLAY_ROTATION_SETTINGS =
+ new Intent("android.settings.DISPLAY_ROTATION_SETTINGS");
+
private final AnimationIcon mPortraitToAuto
= new AnimationIcon(R.drawable.ic_portrait_to_auto_rotate_animation);
private final AnimationIcon mAutoToPortrait
@@ -67,6 +73,11 @@ public class RotationLockTile extends QSTile<QSTile.BooleanState> {
}
@Override
+ protected void handleLongClick() {
+ mHost.startActivityDismissingKeyguard(DISPLAY_ROTATION_SETTINGS);
+ }
+
+ @Override
protected void handleUpdateState(BooleanState state, Object arg) {
if (mController == null) return;
final boolean rotationLocked = arg != null ? ((UserBoolean) arg).value
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenTimeoutTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenTimeoutTile.java
new file mode 100644
index 0000000..e933787
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenTimeoutTile.java
@@ -0,0 +1,344 @@
+/*
+ * Copyright (C) 2015 The CyanogenMod 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.content.pm.PackageManager;
+import android.content.res.Resources;
+import android.database.ContentObserver;
+import android.graphics.drawable.AnimatedVectorDrawable;
+import android.graphics.drawable.Drawable;
+import android.net.Uri;
+import android.provider.Settings;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AdapterView;
+import android.widget.ArrayAdapter;
+import android.widget.ListView;
+
+import com.android.systemui.R;
+import com.android.systemui.qs.QSDetailItemsList;
+import com.android.systemui.qs.QSTile;
+import cyanogenmod.app.StatusBarPanelCustomTile;
+import org.cyanogenmod.internal.logging.CMMetricsLogger;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+
+public class ScreenTimeoutTile extends QSTile<ScreenTimeoutTile.TimeoutState> {
+
+ private static final Intent SETTINGS_INTENT = new Intent(Settings.ACTION_DISPLAY_SETTINGS);
+ private static final String TIMEOUT_ENTRIES_NAME = "screen_timeout_entries";
+ private static final String TIMEOUT_VALUES_NAME = "screen_timeout_values";
+ private static final String SETTINGS_PACKAGE_NAME = "com.android.settings";
+
+ private final AnimationIcon mShort =
+ new AnimationIcon(R.drawable.ic_qs_screen_timeout_short_avd);
+ private final AnimationIcon mShortReverse =
+ new AnimationIcon(R.drawable.ic_qs_screen_timeout_short_reverse_avd);
+ private final AnimationIcon mMedium =
+ new AnimationIcon(R.drawable.ic_qs_screen_timeout_med_avd);
+ private final AnimationIcon mMediumReverse =
+ new AnimationIcon(R.drawable.ic_qs_screen_timeout_med_reverse_avd);
+ private final AnimationIcon mLong =
+ new AnimationIcon(R.drawable.ic_qs_screen_timeout_long_avd);
+ private final AnimationIcon mLongReverse =
+ new AnimationIcon(R.drawable.ic_qs_screen_timeout_long_reverse_avd);
+
+ private String[] mEntries, mValues;
+
+ public ScreenTimeoutTile(Host host) {
+ super(host);
+ populateList();
+ }
+
+ private void populateList() {
+ try {
+ Context context = mContext.createPackageContext(SETTINGS_PACKAGE_NAME, 0);
+ Resources mSettingsResources = context.getResources();
+ int id = mSettingsResources.getIdentifier(TIMEOUT_ENTRIES_NAME,
+ "array", SETTINGS_PACKAGE_NAME);
+ if (id <= 0) {
+ return;
+ }
+ mEntries = mSettingsResources.getStringArray(id);
+ id = mSettingsResources.getIdentifier(TIMEOUT_VALUES_NAME,
+ "array", SETTINGS_PACKAGE_NAME);
+ if (id <= 0) {
+ return;
+ }
+ mValues = mSettingsResources.getStringArray(id);
+ } catch (PackageManager.NameNotFoundException e) {
+ e.printStackTrace();
+ }
+ }
+
+ private int getScreenTimeout() {
+ return Settings.System.getInt(mContext.getContentResolver(),
+ Settings.System.SCREEN_OFF_TIMEOUT, 0);
+ }
+
+ @Override
+ public DetailAdapter getDetailAdapter() {
+ return new ScreenTimeoutDetailAdapter();
+ }
+
+ private ContentObserver mObserver = new ContentObserver(mHandler) {
+ @Override
+ public void onChange(boolean selfChange, Uri uri) {
+ refreshState();
+ }
+ };
+
+ @Override
+ public void setListening(boolean listening) {
+ if (listening) {
+ mContext.getContentResolver().registerContentObserver(
+ Settings.System.getUriFor(Settings.System.SCREEN_OFF_TIMEOUT),
+ false, mObserver);
+ } else {
+ mContext.getContentResolver().unregisterContentObserver(mObserver);
+ }
+ }
+
+ @Override
+ protected TimeoutState newTileState() {
+ return new TimeoutState();
+ }
+
+ @Override
+ protected void handleClick() {
+ if (mEntries.length > 0) {
+ showDetail(true);
+ }
+ }
+
+ @Override
+ protected void handleLongClick() {
+ mHost.startActivityDismissingKeyguard(SETTINGS_INTENT);
+ }
+
+ private String makeTimeoutSummaryString(int timeout) {
+ Resources res = mContext.getResources();
+ int resId;
+
+ /* ms -> seconds */
+ timeout /= 1000;
+
+ if (timeout >= 60 && timeout % 60 == 0) {
+ /* seconds -> minutes */
+ timeout /= 60;
+ if (timeout >= 60 && timeout % 60 == 0) {
+ /* minutes -> hours */
+ timeout /= 60;
+ resId = com.android.internal.R.plurals.duration_hours;
+ } else {
+ resId = com.android.internal.R.plurals.duration_minutes;
+ }
+ } else {
+ resId = com.android.internal.R.plurals.duration_seconds;
+ }
+
+ return res.getQuantityString(resId, timeout, timeout);
+ }
+
+ public static final class TimeoutState extends QSTile.State {
+ int previousTimeout;
+ }
+
+ private enum Bucket {
+ SMALL(0, 30000),
+ MEDIUM(60000,300000),
+ LARGE(600000, 1800000);
+ private final int start;
+ private final int stop;
+
+ Bucket(int start, int stop) {
+ this.start = start;
+ this.stop = stop;
+ }
+
+ public static Bucket getBucket(int value) {
+ for (Bucket item : Bucket.values()) {
+ if (value >= item.start && value <= item.stop) {
+ return item;
+ }
+ }
+ return null;
+ }
+
+ }
+ @Override
+ protected void handleUpdateState(final TimeoutState state, Object arg) {
+ int newTimeout = getScreenTimeout();
+
+ AnimationIcon d = null;
+ Bucket nextBucket = Bucket.getBucket(newTimeout);
+ Bucket previousBucket = Bucket.getBucket(state.previousTimeout);
+
+ if (state.previousTimeout < 60000) {
+ // Default
+ d = mMediumReverse;
+ if (nextBucket == Bucket.MEDIUM) {
+ // Medium
+ d = mShort;
+ } else if (nextBucket == Bucket.LARGE) {
+ // Large
+ d = mShortReverse;
+ }
+ } else if (state.previousTimeout < 600000) {
+ // Default
+ d = mShort;
+ if (nextBucket == Bucket.SMALL) {
+ // Small
+ d = mMediumReverse;
+ } else if (nextBucket == Bucket.LARGE) {
+ // Large
+ d = mMedium;
+ }
+ } else {
+ d = mMedium;
+ if (nextBucket == Bucket.MEDIUM) {
+ // Small
+ d = mLongReverse;
+ } else if (nextBucket == Bucket.SMALL) {
+ // Large
+ d = mLong;
+ }
+ }
+
+ if (state.icon == null || previousBucket != nextBucket) {
+ if (arg instanceof Boolean && (Boolean) arg) {
+ d.setAllowAnimation(true);
+ }
+ state.icon = d;
+ }
+
+ state.visible = true;
+ state.label = makeTimeoutSummaryString(newTimeout);
+ state.contentDescription = mContext.getString(
+ R.string.accessibility_quick_settings_screen_timeout, state.label);
+ state.previousTimeout = newTimeout;
+ }
+
+ @Override
+ public int getMetricsCategory() {
+ return CMMetricsLogger.TILE_SCREEN_TIME_OUT;
+ }
+
+ @Override
+ protected String composeChangeAnnouncement() {
+ return mContext.getString(R.string.accessibility_quick_settings_screen_timeout_changed,
+ mState.label);
+ }
+
+ private class RadioAdapter extends ArrayAdapter<String> {
+
+ public RadioAdapter(Context context, int resource, String[] objects) {
+ super(context, resource, objects);
+ }
+
+ public RadioAdapter(Context context, int resource,
+ int textViewResourceId, String[] objects) {
+ super(context, resource, textViewResourceId, objects);
+ }
+
+ @Override
+ public View getView(int position, View view, ViewGroup parent) {
+ view = super.getView(position, view, parent);
+
+ view.setMinimumHeight(mContext.getResources() .getDimensionPixelSize(
+ R.dimen.qs_detail_item_height));
+
+ return view;
+ }
+
+ }
+ private class ScreenTimeoutDetailAdapter implements DetailAdapter,
+ AdapterView.OnItemClickListener {
+ private QSDetailItemsList mItems;
+
+ @Override
+ public int getTitle() {
+ return R.string.quick_settings_screen_timeout_detail_title;
+ }
+
+ @Override
+ public Boolean getToggleState() {
+ return null;
+ }
+
+ @Override
+ public Intent getSettingsIntent() {
+ return SETTINGS_INTENT;
+ }
+
+ @Override
+ public StatusBarPanelCustomTile getCustomTile() {
+ return null;
+ }
+
+ @Override
+ public void setToggleState(boolean state) {
+ // noop
+ }
+
+ @Override
+ public int getMetricsCategory() {
+ return CMMetricsLogger.TILE_SCREEN_TIME_OUT_DETAIL;
+ }
+
+ @Override
+ public View createDetailView(Context context, View convertView, ViewGroup parent) {
+ mItems = QSDetailItemsList.convertOrInflate(context, convertView, parent);
+ ListView listView = mItems.getListView();
+ listView.setOnItemClickListener(this);
+ listView.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
+ listView.setDivider(null);
+ RadioAdapter adapter = new RadioAdapter(context,
+ android.R.layout.simple_list_item_single_choice, mEntries);
+ int indexOfSelection = Arrays.asList(mValues).indexOf(String.valueOf(getScreenTimeout()));
+ mItems.setAdapter(adapter);
+ listView.setItemChecked(indexOfSelection, true);
+ mItems.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() {
+ @Override
+ public void onViewAttachedToWindow(View v) {
+ }
+
+ @Override
+ public void onViewDetachedFromWindow(View v) {
+ mUiHandler.postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ refreshState(true);
+ }
+ }, 100);
+
+ }
+ });
+ return mItems;
+ }
+
+ @Override
+ public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
+ int selectedTimeout = Integer.valueOf(mValues[position]);
+ Settings.System.putInt(mContext.getContentResolver(),
+ Settings.System.SCREEN_OFF_TIMEOUT, selectedTimeout);
+ }
+ }
+} \ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/SyncTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/SyncTile.java
new file mode 100644
index 0000000..7ffebc7
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/SyncTile.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2015 The CyanogenMod 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.ContentResolver;
+import android.content.Intent;
+import android.content.SyncStatusObserver;
+
+import com.android.systemui.R;
+import com.android.systemui.qs.QSTile;
+import org.cyanogenmod.internal.logging.CMMetricsLogger;
+
+/** Quick settings tile: Sync **/
+public class SyncTile extends QSTile<QSTile.BooleanState> {
+
+ private Object mSyncObserverHandle = null;
+ private boolean mListening;
+
+ public SyncTile(Host host) {
+ super(host);
+ }
+
+ @Override
+ protected BooleanState newTileState() {
+ return new BooleanState();
+ }
+
+ @Override
+ public void handleClick() {
+ ContentResolver.setMasterSyncAutomatically(!mState.value);
+ refreshState();
+ }
+
+ @Override
+ public void handleLongClick() {
+ Intent intent = new Intent("android.settings.SYNC_SETTINGS");
+ intent.addCategory(Intent.CATEGORY_DEFAULT);
+ mHost.startActivityDismissingKeyguard(intent);
+ }
+
+ @Override
+ protected void handleUpdateState(BooleanState state, Object arg) {
+ state.value = ContentResolver.getMasterSyncAutomatically();
+ state.visible = true;
+ state.label = mContext.getString(R.string.quick_settings_sync_label);
+ if (state.value) {
+ state.icon = ResourceIcon.get(R.drawable.ic_qs_sync_on);
+ state.contentDescription = mContext.getString(
+ R.string.accessibility_quick_settings_sync_on);
+ } else {
+ state.icon = ResourceIcon.get(R.drawable.ic_qs_sync_off);
+ state.contentDescription = mContext.getString(
+ R.string.accessibility_quick_settings_sync_off);
+ }
+ }
+
+ @Override
+ public int getMetricsCategory() {
+ return CMMetricsLogger.TILE_SYNC;
+ }
+
+ @Override
+ protected String composeChangeAnnouncement() {
+ if (mState.value) {
+ return mContext.getString(R.string.accessibility_quick_settings_sync_changed_on);
+ } else {
+ return mContext.getString(R.string.accessibility_quick_settings_sync_changed_off);
+ }
+ }
+
+ @Override
+ public void setListening(boolean listening) {
+ if (mListening == listening) return;
+ mListening = listening;
+
+ if (listening) {
+ mSyncObserverHandle = ContentResolver.addStatusChangeListener(
+ ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS, mSyncObserver);
+ } else {
+ ContentResolver.removeStatusChangeListener(mSyncObserverHandle);
+ mSyncObserverHandle = null;
+ }
+ }
+
+ private SyncStatusObserver mSyncObserver = new SyncStatusObserver() {
+ public void onStatusChanged(int which) {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ refreshState();
+ }
+ });
+ }
+ };
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/UsbTetherTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/UsbTetherTile.java
new file mode 100644
index 0000000..1274195
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/UsbTetherTile.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2015 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.hardware.usb.UsbManager;
+import android.provider.Settings;
+import android.net.ConnectivityManager;
+
+import com.android.systemui.R;
+import com.android.systemui.qs.QSTile;
+import org.cyanogenmod.internal.logging.CMMetricsLogger;
+
+/**
+ * USB Tether quick settings tile
+ */
+public class UsbTetherTile extends QSTile<QSTile.BooleanState> {
+ private static final Intent WIRELESS_SETTINGS = new Intent(Settings.ACTION_WIRELESS_SETTINGS);
+
+ private final ConnectivityManager mConnectivityManager;
+
+ private boolean mListening;
+
+ private boolean mUsbTethered = false;
+ private boolean mUsbConnected = false;
+
+ public UsbTetherTile(Host host) {
+ super(host);
+ mConnectivityManager = (ConnectivityManager) mContext
+ .getSystemService(Context.CONNECTIVITY_SERVICE);
+ }
+
+ protected BooleanState newTileState() {
+ return new BooleanState();
+ }
+
+ @Override
+ public void setListening(boolean listening) {
+ if (mListening == listening)
+ return;
+ mListening = listening;
+ if (listening) {
+ final IntentFilter filter = new IntentFilter();
+ filter.addAction(UsbManager.ACTION_USB_STATE);
+ mContext.registerReceiver(mReceiver, filter);
+ } else {
+ mContext.unregisterReceiver(mReceiver);
+ }
+ }
+
+ @Override
+ protected void handleClick() {
+ mConnectivityManager.setUsbTethering(!mUsbTethered);
+ }
+
+ @Override
+ protected void handleLongClick() {
+ mHost.startActivityDismissingKeyguard(WIRELESS_SETTINGS);
+ }
+
+ private void updateState() {
+ String[] tetheredIfaces = mConnectivityManager.getTetheredIfaces();
+ String[] usbRegexs = mConnectivityManager.getTetherableUsbRegexs();
+
+ mUsbTethered = false;
+ for (String s : tetheredIfaces) {
+ for (String regex : usbRegexs) {
+ if (s.matches(regex)) {
+ mUsbTethered = true;
+ return;
+ }
+ }
+ }
+ }
+
+ private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ mUsbConnected = intent.getBooleanExtra(UsbManager.USB_CONNECTED, false);
+ if (mUsbConnected && mConnectivityManager.isTetheringSupported()) {
+ updateState();
+ } else {
+ mUsbTethered = false;
+ }
+ refreshState();
+ }
+ };
+
+ @Override
+ protected void handleUpdateState(BooleanState state, Object arg) {
+ state.visible = mUsbConnected && mConnectivityManager.isTetheringSupported();
+ state.value = mUsbTethered;
+ state.label = mContext.getString(R.string.quick_settings_usb_tether_label);
+ state.icon = mUsbTethered ? ResourceIcon.get(R.drawable.ic_qs_usb_tether_on)
+ : ResourceIcon.get(R.drawable.ic_qs_usb_tether_off);
+ }
+
+ @Override
+ public int getMetricsCategory() {
+ return CMMetricsLogger.TILE_USB_TETHER;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/VolumeTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/VolumeTile.java
new file mode 100644
index 0000000..ae29f16
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/VolumeTile.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2015 The CyanogenMod 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.media.AudioManager;
+import android.provider.Settings;
+
+import com.android.systemui.R;
+import com.android.systemui.qs.QSTile;
+import org.cyanogenmod.internal.logging.CMMetricsLogger;
+
+public class VolumeTile extends QSTile<QSTile.BooleanState> {
+
+ private static final Intent SOUND_SETTINGS = new Intent("android.settings.SOUND_SETTINGS");
+
+ public VolumeTile(Host host) {
+ super(host);
+ }
+
+ @Override
+ protected void handleClick() {
+ AudioManager am = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
+ am.adjustVolume(AudioManager.ADJUST_SAME, AudioManager.FLAG_SHOW_UI);
+ }
+
+ @Override
+ protected void handleLongClick() {
+ mHost.startActivityDismissingKeyguard(SOUND_SETTINGS);
+ }
+
+ @Override
+ protected void handleUpdateState(BooleanState state, Object arg) {
+ state.visible = true;
+ state.label = mContext.getString(R.string.quick_settings_volume_panel_label);
+ state.icon = ResourceIcon.get(R.drawable.ic_qs_volume_panel); // TODO needs own icon
+ }
+
+ @Override
+ public int getMetricsCategory() {
+ return CMMetricsLogger.TILE_VOLUME;
+ }
+
+ @Override
+ protected BooleanState newTileState() {
+ return new BooleanState();
+ }
+
+ @Override
+ public void setListening(boolean listening) {
+ // Do nothing
+ }
+}
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 e654efd..abc9acd 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/WifiTile.java
@@ -1,5 +1,6 @@
/*
* Copyright (C) 2014 The Android Open Source Project
+ * Copyright (C) 2015 The CyanogenMod Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -19,16 +20,20 @@ package com.android.systemui.qs.tiles;
import android.content.Context;
import android.content.Intent;
import android.content.res.Resources;
+import android.os.Looper;
import android.provider.Settings;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
+import android.widget.AdapterView;
+import android.widget.ListView;
import com.android.internal.logging.MetricsLogger;
import com.android.settingslib.wifi.AccessPoint;
+
import com.android.systemui.R;
-import com.android.systemui.qs.QSDetailItems;
import com.android.systemui.qs.QSDetailItems.Item;
+import com.android.systemui.qs.QSDetailItemsList;
import com.android.systemui.qs.QSTile;
import com.android.systemui.qs.QSTileView;
import com.android.systemui.qs.SignalTileView;
@@ -37,6 +42,9 @@ import com.android.systemui.statusbar.policy.NetworkController.AccessPointContro
import com.android.systemui.statusbar.policy.NetworkController.IconState;
import com.android.systemui.statusbar.policy.SignalCallbackAdapter;
+import cyanogenmod.app.StatusBarPanelCustomTile;
+
+import java.util.ArrayList;
import java.util.List;
/** Quick settings tile: Wifi **/
@@ -58,7 +66,7 @@ public class WifiTile extends QSTile<QSTile.SignalState> {
}
@Override
- public boolean supportsDualTargets() {
+ public boolean hasDualTargetsDetails() {
return true;
}
@@ -116,19 +124,36 @@ public class WifiTile extends QSTile<QSTile.SignalState> {
}
@Override
+ protected void handleLongClick() {
+ mHost.startActivityDismissingKeyguard(WIFI_SETTINGS);
+ }
+
+ @Override
protected void handleUpdateState(SignalState state, Object arg) {
state.visible = true;
if (DEBUG) Log.d(TAG, "handleUpdateState arg=" + arg);
- CallbackInfo cb = (CallbackInfo) arg;
- if (cb == null) {
+ final CallbackInfo cb;
+ if (arg == null) {
cb = mSignalCallback.mInfo;
+ } else {
+ cb = (CallbackInfo) arg;
}
boolean wifiConnected = cb.enabled && (cb.wifiSignalIconId > 0) && (cb.enabledDesc != null);
boolean wifiNotConnected = (cb.wifiSignalIconId > 0) && (cb.enabledDesc == null);
boolean enabledChanging = state.enabled != cb.enabled;
if (enabledChanging) {
- mDetailAdapter.setItemsVisible(cb.enabled);
+ if (Looper.myLooper() == Looper.getMainLooper()) {
+ // on main thread, bypass the handler
+ mDetailAdapter.setItemsVisible(cb.enabled);
+ } else {
+ mUiHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ mDetailAdapter.setItemsVisible(cb.enabled);
+ }
+ });
+ }
fireToggleStateChanged(cb.enabled);
}
state.enabled = cb.enabled;
@@ -235,10 +260,12 @@ public class WifiTile extends QSTile<QSTile.SignalState> {
};
private final class WifiDetailAdapter implements DetailAdapter,
- NetworkController.AccessPointController.AccessPointCallback, QSDetailItems.Callback {
+ AccessPointController.AccessPointCallback, AdapterView.OnItemClickListener {
- private QSDetailItems mItems;
- private AccessPoint[] mAccessPoints;
+ private QSDetailItemsList mItemsList;
+ private List<AccessPoint> mAccessPoints;
+ private List<Item> mDisplayedAccessPoints = new ArrayList<>();
+ private QSDetailItemsList.QSDetailListAdapter mAdapter;
@Override
public int getTitle() {
@@ -250,6 +277,11 @@ public class WifiTile extends QSTile<QSTile.SignalState> {
}
@Override
+ public StatusBarPanelCustomTile getCustomTile() {
+ return null;
+ }
+
+ @Override
public Boolean getToggleState() {
return mState.enabled;
}
@@ -273,19 +305,21 @@ public class WifiTile extends QSTile<QSTile.SignalState> {
mAccessPoints = null;
mWifiController.scanForAccessPoints();
fireScanStateChanged(true);
- mItems = QSDetailItems.convertOrInflate(context, convertView, parent);
- mItems.setTagSuffix("Wifi");
- mItems.setCallback(this);
- mItems.setEmptyState(R.drawable.ic_qs_wifi_detail_empty,
+ mItemsList = QSDetailItemsList.convertOrInflate(context, convertView, parent);
+ ListView listView = mItemsList.getListView();
+ listView.setDivider(null);
+ listView.setOnItemClickListener(this);
+ listView.setAdapter(mAdapter =
+ new QSDetailItemsList.QSDetailListAdapter(context, mDisplayedAccessPoints));
+ mItemsList.setEmptyState(R.drawable.ic_qs_wifi_detail_empty,
R.string.quick_settings_wifi_detail_empty_text);
updateItems();
- setItemsVisible(mState.enabled);
- return mItems;
+ return mItemsList;
}
@Override
public void onAccessPointsChanged(final List<AccessPoint> accessPoints) {
- mAccessPoints = accessPoints.toArray(new AccessPoint[accessPoints.size()]);
+ mAccessPoints = accessPoints;
updateItems();
if (accessPoints != null && accessPoints.size() > 0) {
fireScanStateChanged(false);
@@ -297,35 +331,22 @@ public class WifiTile extends QSTile<QSTile.SignalState> {
mHost.startActivityDismissingKeyguard(settingsIntent);
}
- @Override
- public void onDetailItemClick(Item item) {
- if (item == null || item.tag == null) return;
- final AccessPoint ap = (AccessPoint) item.tag;
- if (!ap.isActive()) {
- if (mWifiController.connect(ap)) {
- mHost.collapsePanels();
- }
- }
- showDetail(false);
- }
-
- @Override
- public void onDetailItemDisconnect(Item item) {
- // noop
- }
-
public void setItemsVisible(boolean visible) {
- if (mItems == null) return;
- mItems.setItemsVisible(visible);
+ if (mAdapter == null) return;
+ if (visible) {
+ updateItems();
+ } else {
+ mDisplayedAccessPoints.clear();
+ }
+ mAdapter.notifyDataSetChanged();
}
private void updateItems() {
- if (mItems == null) return;
- Item[] items = null;
+ if (mAdapter == null) return;
if (mAccessPoints != null) {
- items = new Item[mAccessPoints.length];
- for (int i = 0; i < mAccessPoints.length; i++) {
- final AccessPoint ap = mAccessPoints[i];
+ mDisplayedAccessPoints.clear();
+ for (int i = 0; i < mAccessPoints.size(); i++) {
+ final AccessPoint ap = mAccessPoints.get(i);
final Item item = new Item();
item.tag = ap;
item.icon = mWifiController.getIcon(ap);
@@ -334,10 +355,23 @@ public class WifiTile extends QSTile<QSTile.SignalState> {
item.overlay = ap.getSecurity() != AccessPoint.SECURITY_NONE
? mContext.getDrawable(R.drawable.qs_ic_wifi_lock)
: null;
- items[i] = item;
+ mDisplayedAccessPoints.add(item);
+ }
+ }
+ mAdapter.notifyDataSetChanged();
+ }
+
+ @Override
+ public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
+ Item item = (Item) parent.getItemAtPosition(position);
+ if (item == null || item.tag == null) return;
+ final AccessPoint ap = (AccessPoint) item.tag;
+ if (!ap.isActive()) {
+ if (mWifiController.connect(ap)) {
+ mHost.collapsePanels();
}
}
- mItems.setItems(items);
+ showDetail(false);
}
};
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/Constants.java b/packages/SystemUI/src/com/android/systemui/recents/Constants.java
index a4acf83..b482a50 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/Constants.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/Constants.java
@@ -38,7 +38,7 @@ public class Constants {
// Enables the filtering of tasks according to their grouping
public static final boolean EnableTaskFiltering = false;
// Enables dismiss-all
- public static final boolean EnableDismissAll = false;
+ public static final boolean EnableDismissAll = true;
// Enables debug mode
public static final boolean EnableDebugMode = false;
// Enables the search bar layout
diff --git a/packages/SystemUI/src/com/android/systemui/recents/Recents.java b/packages/SystemUI/src/com/android/systemui/recents/Recents.java
index 3917bab..b3a3dfd 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/Recents.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/Recents.java
@@ -46,6 +46,7 @@ import com.android.systemui.R;
import com.android.systemui.RecentsComponent;
import com.android.systemui.SystemUI;
import com.android.systemui.SystemUIApplication;
+import com.android.systemui.cm.UserContentObserver;
import com.android.systemui.recents.misc.Console;
import com.android.systemui.recents.misc.SystemServicesProxy;
import com.android.systemui.recents.model.RecentsTaskLoadPlan;
@@ -61,6 +62,8 @@ import com.android.systemui.statusbar.phone.PhoneStatusBar;
import java.util.ArrayList;
+import cyanogenmod.providers.CMSettings;
+
/**
* Annotation for a method that is only called from the primary user's SystemUI process and will be
* proxied to the current user.
@@ -161,6 +164,35 @@ public class Recents extends SystemUI
}
}
+ class RecentsSettingsObserver extends UserContentObserver {
+
+ public RecentsSettingsObserver(Handler handler) {
+ super(handler);
+ }
+
+ @Override
+ protected void observe() {
+ super.observe();
+ mContext.getContentResolver().registerContentObserver(
+ CMSettings.System.getUriFor(CMSettings.System.RECENTS_SHOW_SEARCH_BAR),
+ false, this);
+ update();
+ }
+
+ @Override
+ protected void unobserve() {
+ super.unobserve();
+ mContext.getContentResolver().unregisterContentObserver(this);
+ }
+
+ @Override
+ protected void update() {
+ if (mConfig.updateShowSearch(mContext)) {
+ reloadHeaderBarLayout();
+ }
+ }
+ }
+
static RecentsComponent.Callbacks sRecentsComponentCallbacks;
static RecentsTaskLoadPlan sInstanceLoadPlan;
static Recents sInstance;
@@ -171,6 +203,7 @@ public class Recents extends SystemUI
TaskStackListenerImpl mTaskStackListener;
RecentsOwnerEventProxyReceiver mProxyBroadcastReceiver;
RecentsAppWidgetHost mAppWidgetHost;
+ RecentsSettingsObserver mSettingsObserver;
boolean mBootCompleted;
boolean mStartAnimationTriggered;
boolean mCanReuseTaskStackViews = true;
@@ -259,6 +292,9 @@ public class Recents extends SystemUI
// Load the header bar layout
reloadHeaderBarLayout();
+ mSettingsObserver = new RecentsSettingsObserver(mHandler);
+ mSettingsObserver.observe();
+
// When we start, preload the data associated with the previous recent tasks.
// We can use a new plan since the caches will be the same.
RecentsTaskLoader loader = RecentsTaskLoader.getInstance();
@@ -549,7 +585,8 @@ public class Recents extends SystemUI
// Try and pre-emptively bind the search widget on startup to ensure that we
// have the right thumbnail bounds to animate to.
// Note: We have to reload the widget id before we get the task stack bounds below
- if (mSystemServicesProxy.getOrBindSearchAppWidget(mContext, mAppWidgetHost) != null) {
+ if (mConfig.searchBarEnabled &&
+ mSystemServicesProxy.getOrBindSearchAppWidget(mContext, mAppWidgetHost) != null) {
mConfig.getSearchBarBounds(mWindowRect.width(), mWindowRect.height(),
mStatusBarHeight, searchBarBounds);
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
index d0876fa..9e08599 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
@@ -48,6 +48,7 @@ import com.android.systemui.recents.views.DebugOverlayView;
import com.android.systemui.recents.views.RecentsView;
import com.android.systemui.recents.views.SystemBarScrimViews;
import com.android.systemui.recents.views.ViewAnimation;
+import cyanogenmod.providers.CMSettings;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
@@ -84,6 +85,8 @@ public class RecentsActivity extends Activity implements RecentsView.RecentsView
// Runnable to be executed after we paused ourselves
Runnable mAfterPauseRunnable;
+ private ReferenceCountedTrigger mExitTrigger;
+
/**
* A common Runnable to finish Recents either by calling finish() (with a custom animation) or
* launching Home with some ActivityOptions. Generally we always launch home when we exit
@@ -94,6 +97,7 @@ public class RecentsActivity extends Activity implements RecentsView.RecentsView
class FinishRecentsRunnable implements Runnable {
Intent mLaunchIntent;
ActivityOptions mLaunchOpts;
+ boolean mAbort = false;
/**
* Creates a finish runnable that starts the specified intent, using the given
@@ -104,8 +108,15 @@ public class RecentsActivity extends Activity implements RecentsView.RecentsView
mLaunchOpts = opts;
}
+ public void setAbort(boolean run) {
+ this.mAbort = run;
+ }
+
@Override
public void run() {
+ if (mAbort) {
+ return;
+ }
// Finish Recents
if (mLaunchIntent != null) {
try {
@@ -207,9 +218,7 @@ public class RecentsActivity extends Activity implements RecentsView.RecentsView
ArrayList<TaskStack> stacks = plan.getAllTaskStacks();
mConfig.launchedWithNoRecentTasks = !plan.hasTasks();
- if (!mConfig.launchedWithNoRecentTasks) {
- mRecentsView.setTaskStacks(stacks);
- }
+ mRecentsView.setTaskStacks(stacks);
// Create the home intent runnable
Intent homeIntent = new Intent(Intent.ACTION_MAIN, null);
@@ -218,10 +227,8 @@ public class RecentsActivity extends Activity implements RecentsView.RecentsView
Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
mFinishLaunchHomeRunnable = new FinishRecentsRunnable(homeIntent,
ActivityOptions.makeCustomAnimation(this,
- mConfig.launchedFromSearchHome ? R.anim.recents_to_search_launcher_enter :
- R.anim.recents_to_launcher_enter,
- mConfig.launchedFromSearchHome ? R.anim.recents_to_search_launcher_exit :
- R.anim.recents_to_launcher_exit));
+ R.anim.recents_to_search_launcher_enter,
+ R.anim.recents_to_search_launcher_exit));
// Mark the task that is the launch target
int taskStackCount = stacks.size();
@@ -248,15 +255,26 @@ public class RecentsActivity extends Activity implements RecentsView.RecentsView
mEmptyView = mEmptyViewStub.inflate();
}
mEmptyView.setVisibility(View.VISIBLE);
+ mEmptyView.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ dismissRecentsToHome(true);
+ }
+ });
mRecentsView.setSearchBarVisibility(View.GONE);
} else {
if (mEmptyView != null) {
mEmptyView.setVisibility(View.GONE);
+ mEmptyView.setOnClickListener(null);
}
- if (mRecentsView.hasValidSearchBar()) {
- mRecentsView.setSearchBarVisibility(View.VISIBLE);
+ if (!mConfig.searchBarEnabled) {
+ mRecentsView.setSearchBarVisibility(View.GONE);
} else {
- refreshSearchWidgetView();
+ if (mRecentsView.hasValidSearchBar()) {
+ mRecentsView.setSearchBarVisibility(View.VISIBLE);
+ } else {
+ refreshSearchWidgetView();
+ }
}
}
@@ -309,13 +327,26 @@ public class RecentsActivity extends Activity implements RecentsView.RecentsView
return false;
}
+ @Override
+ public void onWindowFocusChanged(boolean hasFocus) {
+ super.onWindowFocusChanged(hasFocus);
+ if (!hasFocus && mExitTrigger != null && mExitTrigger.getCount() > 0) {
+ // we are animating recents out and the window has lost focus during the
+ // animation. we need to stop everything we're doing now and get out
+ // without any animations (since we were already animating)
+ mFinishLaunchHomeRunnable.setAbort(true);
+ finish();
+ overridePendingTransition(0, 0);
+ }
+ }
+
/** Dismisses Recents directly to Home. */
void dismissRecentsToHomeRaw(boolean animated) {
if (animated) {
- ReferenceCountedTrigger exitTrigger = new ReferenceCountedTrigger(this,
+ mExitTrigger = new ReferenceCountedTrigger(this,
null, mFinishLaunchHomeRunnable, null);
mRecentsView.startExitToHomeAnimation(
- new ViewAnimation.TaskViewExitContext(exitTrigger));
+ new ViewAnimation.TaskViewExitContext(mExitTrigger));
} else {
mFinishLaunchHomeRunnable.run();
}
@@ -431,6 +462,14 @@ public class RecentsActivity extends Activity implements RecentsView.RecentsView
}
@Override
+ protected void onResume() {
+ if (mConfig.searchBarEnabled && mConfig.launchedFromHome) {
+ overridePendingTransition(0, 0);
+ }
+ super.onResume();
+ }
+
+ @Override
protected void onPause() {
super.onPause();
if (mAfterPauseRunnable != null) {
@@ -442,6 +481,9 @@ public class RecentsActivity extends Activity implements RecentsView.RecentsView
@Override
protected void onStop() {
super.onStop();
+
+ mExitTrigger = null;
+
MetricsLogger.hidden(this, MetricsLogger.OVERVIEW_ACTIVITY);
RecentsTaskLoader loader = RecentsTaskLoader.getInstance();
SystemServicesProxy ssp = loader.getSystemServicesProxy();
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java
index dfe7e96..d7e8b99 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java
@@ -31,6 +31,7 @@ import com.android.systemui.R;
import com.android.systemui.recents.misc.Console;
import com.android.systemui.recents.misc.SystemServicesProxy;
+import cyanogenmod.providers.CMSettings;
/** A static Recents configuration for the current context
* NOTE: We should not hold any references to a Context from a static instance */
@@ -73,6 +74,7 @@ public class RecentsConfiguration {
public int maxNumTasksToLoad;
/** Search bar */
+ public boolean searchBarEnabled = true;
public int searchBarSpaceHeightPx;
/** Task stack */
@@ -175,6 +177,14 @@ public class RecentsConfiguration {
return sInstance;
}
+ /** Returns the current recents configuration or creates and populates it if required */
+ public static RecentsConfiguration getInstance(Context context, SystemServicesProxy ssp) {
+ if (sInstance == null) {
+ sInstance = reinitialize(context, ssp);
+ }
+ return sInstance;
+ }
+
/** Updates the state, given the specified context */
void update(Context context) {
Resources res = context.getResources();
@@ -271,6 +281,13 @@ public class RecentsConfiguration {
svelteLevel = res.getInteger(R.integer.recents_svelte_level);
}
+ public boolean updateShowSearch(Context context) {
+ boolean wasEnabled = searchBarEnabled;
+ searchBarEnabled = CMSettings.System.getInt(context.getContentResolver(),
+ CMSettings.System.RECENTS_SHOW_SEARCH_BAR, 1) == 1;
+ return wasEnabled != searchBarEnabled;
+ }
+
/** Updates the system insets */
public void updateSystemInsets(Rect insets) {
systemInsets.set(insets);
diff --git a/packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java b/packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java
index cbf5c05..b1413c9 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java
@@ -30,9 +30,11 @@ import android.graphics.drawable.ColorDrawable;
import android.os.RemoteException;
import android.util.DisplayMetrics;
import android.view.Gravity;
+import android.view.IWindowManager;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
+import android.view.WindowManagerGlobal;
import android.view.accessibility.AccessibilityManager;
import android.view.animation.DecelerateInterpolator;
import android.widget.Button;
@@ -49,6 +51,7 @@ public class ScreenPinningRequest implements View.OnClickListener {
private final AccessibilityManager mAccessibilityService;
private final WindowManager mWindowManager;
+ private final IWindowManager mWindowManagerService;
private RequestWindowView mRequestWindow;
@@ -58,6 +61,7 @@ public class ScreenPinningRequest implements View.OnClickListener {
mContext.getSystemService(Context.ACCESSIBILITY_SERVICE);
mWindowManager = (WindowManager)
mContext.getSystemService(Context.WINDOW_SERVICE);
+ mWindowManagerService = WindowManagerGlobal.getWindowManagerService();
}
public void clearPrompt() {
@@ -215,19 +219,33 @@ public class ScreenPinningRequest implements View.OnClickListener {
.setVisibility(View.INVISIBLE);
}
- final int description = mAccessibilityService.isEnabled()
+ final int description;
+ if (hasNavigationBar()) {
+ description = mAccessibilityService.isEnabled()
? R.string.screen_pinning_description_accessible
: R.string.screen_pinning_description;
+ final int backBgVis =
+ mAccessibilityService.isEnabled() ? View.INVISIBLE : View.VISIBLE;
+ mLayout.findViewById(R.id.screen_pinning_back_bg).setVisibility(backBgVis);
+ mLayout.findViewById(R.id.screen_pinning_back_bg_light).setVisibility(backBgVis);
+ } else {
+ description = R.string.screen_pinning_description_no_navbar;
+ ((ViewGroup) buttons.getParent()).removeView(buttons);
+ }
((TextView) mLayout.findViewById(R.id.screen_pinning_description))
.setText(description);
- final int backBgVisibility =
- mAccessibilityService.isEnabled() ? View.INVISIBLE : View.VISIBLE;
- mLayout.findViewById(R.id.screen_pinning_back_bg).setVisibility(backBgVisibility);
- mLayout.findViewById(R.id.screen_pinning_back_bg_light).setVisibility(backBgVisibility);
-
+
addView(mLayout, getRequestLayoutParams(isLandscape));
}
+ private boolean hasNavigationBar() {
+ try {
+ return mWindowManagerService.hasNavigationBar();
+ } catch (RemoteException e) {
+ //ignore
+ }
+ return false;
+ }
private void swapChildrenIfRtlAndVertical(View group) {
if (mContext.getResources().getConfiguration().getLayoutDirection()
!= View.LAYOUT_DIRECTION_RTL) {
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java
index ad25c85..4b3b391 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java
@@ -458,7 +458,7 @@ public class RecentsTaskLoader {
/** Creates a new plan for loading the recent tasks. */
public RecentsTaskLoadPlan createLoadPlan(Context context) {
- RecentsConfiguration config = RecentsConfiguration.getInstance();
+ RecentsConfiguration config = RecentsConfiguration.getInstance(context, mSystemServicesProxy);
RecentsTaskLoadPlan plan = new RecentsTaskLoadPlan(context, config, mSystemServicesProxy);
return plan;
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
index 947c19c..2f11c56 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
@@ -31,6 +31,7 @@ import android.os.UserHandle;
import android.provider.Settings;
import android.util.AttributeSet;
import android.util.Log;
+import android.util.EventLog;
import android.view.LayoutInflater;
import android.view.View;
import android.view.WindowInsets;
@@ -48,6 +49,8 @@ import com.android.systemui.recents.model.RecentsTaskLoader;
import com.android.systemui.recents.model.Task;
import com.android.systemui.recents.model.TaskStack;
+import com.android.systemui.EventLogTags;
+
import java.util.ArrayList;
import java.util.List;
@@ -267,6 +270,8 @@ public class RecentsView extends FrameLayout implements TaskStackView.TaskStackV
stackView.startEnterRecentsAnimation(ctx);
}
ctx.postAnimationTrigger.decrement();
+
+ EventLog.writeEvent(EventLogTags.SYSUI_RECENTS_EVENT, 1 /* opened */);
}
/** Requests all task stacks to start their exit-recents animation */
@@ -323,7 +328,7 @@ public class RecentsView extends FrameLayout implements TaskStackView.TaskStackV
// Get the search bar bounds and measure the search bar layout
Rect searchBarSpaceBounds = new Rect();
- if (mSearchBar != null) {
+ if (mSearchBar != null && mConfig.searchBarEnabled) {
mConfig.getSearchBarBounds(width, height, mConfig.systemInsets.top, searchBarSpaceBounds);
mSearchBar.measure(
MeasureSpec.makeMeasureSpec(searchBarSpaceBounds.width(), MeasureSpec.EXACTLY),
@@ -360,7 +365,7 @@ public class RecentsView extends FrameLayout implements TaskStackView.TaskStackV
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
// Get the search bar bounds so that we lay it out
- if (mSearchBar != null) {
+ if (mSearchBar != null && mConfig.searchBarEnabled) {
Rect searchBarSpaceBounds = new Rect();
mConfig.getSearchBarBounds(getMeasuredWidth(), getMeasuredHeight(),
mConfig.systemInsets.top, searchBarSpaceBounds);
@@ -620,6 +625,8 @@ public class RecentsView extends FrameLayout implements TaskStackView.TaskStackV
launchRunnable.run();
}
}
+
+ EventLog.writeEvent(EventLogTags.SYSUI_RECENTS_EVENT, 3 /* chose task */);
}
@Override
@@ -659,6 +666,7 @@ public class RecentsView extends FrameLayout implements TaskStackView.TaskStackV
// Keep track of all-deletions
MetricsLogger.count(getContext(), "overview_task_all_dismissed", 1);
+ EventLog.writeEvent(EventLogTags.SYSUI_RECENTS_EVENT, 4 /* closed all */);
}
/** Final callback after Recents is finally hidden. */
@@ -670,6 +678,7 @@ public class RecentsView extends FrameLayout implements TaskStackView.TaskStackV
TaskStackView stackView = stackViews.get(i);
stackView.onRecentsHidden();
}
+ EventLog.writeEvent(EventLogTags.SYSUI_RECENTS_EVENT, 2 /* closed */);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
index 0068f84..43b9a3e 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
@@ -17,18 +17,27 @@
package com.android.systemui.recents.views;
import android.animation.ValueAnimator;
+import android.app.ActivityManager;
+import android.app.admin.DevicePolicyManager;
import android.content.ComponentName;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.Rect;
import android.os.Bundle;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.IPackageDataObserver;
+import android.content.pm.PackageManager;
+import android.net.Uri;
import android.view.LayoutInflater;
+import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.View;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.FrameLayout;
+import android.widget.PopupMenu;
import com.android.internal.logging.MetricsLogger;
import com.android.systemui.R;
@@ -85,6 +94,9 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
boolean mDismissAllButtonAnimating;
int mFocusedTaskIndex = -1;
int mPrevAccessibilityFocusedIndex = -1;
+
+ private PopupMenu mPopup;
+
// Optimizations
int mStackViewsAnimationDuration;
boolean mStackViewsDirty = true;
@@ -1372,6 +1384,85 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
}
@Override
+ public void onTaskViewLongClicked(final TaskView tv) {
+ final PopupMenu popup = new PopupMenu(getContext(), tv.mHeaderView.mApplicationIcon);
+ mPopup = popup;
+ popup.getMenuInflater().inflate(R.menu.recent_popup_menu, popup.getMenu());
+
+ final Task task = tv.getTask();
+ final String packageName = task.key.baseIntent.getComponent().getPackageName();
+
+ try {
+ PackageManager pm = (PackageManager) getContext().getPackageManager();
+ ApplicationInfo appInfo = pm.getApplicationInfo(packageName, 0);
+ DevicePolicyManager dpm = (DevicePolicyManager) getContext()
+ .getSystemService(Context.DEVICE_POLICY_SERVICE);
+
+ boolean hasActiveAdmins = dpm.packageHasActiveAdmins(packageName);
+ boolean isClearable = (appInfo.flags &
+ (ApplicationInfo.FLAG_ALLOW_CLEAR_USER_DATA | ApplicationInfo.FLAG_SYSTEM)) !=
+ ApplicationInfo.FLAG_SYSTEM;
+ if (!isClearable || hasActiveAdmins) {
+ popup.getMenu().findItem(R.id.recent_wipe_app).setEnabled(false);
+ popup.getMenu().findItem(R.id.recent_uninstall).setEnabled(false);
+ }
+ } catch (PackageManager.NameNotFoundException ex) {
+ }
+ popup.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
+ @Override
+ public boolean onMenuItemClick(MenuItem item) {
+ switch (item.getItemId()) {
+ case R.id.recent_remove_item:
+ onTaskViewDismissed(tv);
+ break;
+ case R.id.recent_inspect_item:
+ onTaskViewAppInfoClicked(tv);
+ break;
+ case R.id.recent_force_stop:
+ {
+ ActivityManager am = (ActivityManager) getContext()
+ .getSystemService(Context.ACTIVITY_SERVICE);
+ am.forceStopPackage(packageName);
+ onTaskViewDismissed(tv);
+ break;
+ }
+ case R.id.recent_wipe_app:
+ {
+ ActivityManager am = (ActivityManager) getContext()
+ .getSystemService(Context.ACTIVITY_SERVICE);
+ am.clearApplicationUserData(packageName, new IPackageDataObserver.Stub() {
+ @Override
+ public void onRemoveCompleted(String packageName, boolean succeeded) {}
+ });
+ onTaskViewDismissed(tv);
+ break;
+ }
+ case R.id.recent_uninstall:
+ {
+ Uri packageUri = Uri.parse("package:" + packageName);
+ Intent uninstallIntent = new Intent(Intent.ACTION_UNINSTALL_PACKAGE, packageUri);
+ uninstallIntent.putExtra(Intent.EXTRA_UNINSTALL_ALL_USERS, true);
+ getContext().startActivity(uninstallIntent);
+ onTaskViewDismissed(tv);
+ break;
+ }
+ default:
+ return false;
+ }
+ return true;
+ }
+ });
+ popup.setOnDismissListener(new PopupMenu.OnDismissListener() {
+ @Override
+ public void onDismiss(PopupMenu menu) {
+ mPopup = null;
+ }
+ });
+ popup.show();
+ }
+
+
+ @Override
public void onTaskViewClicked(TaskView tv, Task task, boolean lockToTask) {
// Cancel any doze triggers
mUIDozeTrigger.stopDozing();
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
index cbfe842..b7e46f4 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
@@ -34,6 +34,7 @@ import com.android.systemui.recents.RecentsConfiguration;
import com.android.systemui.recents.misc.Utilities;
import com.android.systemui.recents.model.Task;
import com.android.systemui.statusbar.phone.PhoneStatusBar;
+import cyanogenmod.providers.CMSettings;
/* A task view */
public class TaskView extends FrameLayout implements Task.TaskCallbacks,
@@ -43,6 +44,7 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks,
interface TaskViewCallbacks {
public void onTaskViewAppIconClicked(TaskView tv);
public void onTaskViewAppInfoClicked(TaskView tv);
+ public void onTaskViewLongClicked(TaskView tv);
public void onTaskViewClicked(TaskView tv, Task task, boolean lockToTask);
public void onTaskViewDismissed(TaskView tv);
public void onTaskViewClipStateChanged(TaskView tv);
@@ -769,7 +771,14 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks,
public boolean onLongClick(View v) {
if (v == mHeaderView.mApplicationIcon) {
if (mCb != null) {
- mCb.onTaskViewAppInfoClicked(this);
+ boolean showDevShortcuts =
+ CMSettings.Secure.getInt(v.getContext().getContentResolver(),
+ CMSettings.Secure.DEVELOPMENT_SHORTCUT, 0) != 0;
+ if (showDevShortcuts) {
+ mCb.onTaskViewLongClicked(this);
+ } else {
+ mCb.onTaskViewAppInfoClicked(this);
+ }
return true;
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java
index 9e3cf37..260f625 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java
@@ -430,6 +430,7 @@ class GlobalScreenshot {
private MediaActionSound mCameraSound;
+ private final int mSfHwRotation;
/**
* @param context everything needs a context :(
@@ -496,6 +497,9 @@ class GlobalScreenshot {
// Setup the Camera shutter sound
mCameraSound = new MediaActionSound();
mCameraSound.load(MediaActionSound.SHUTTER_CLICK);
+
+ // Load hardware rotation from prop
+ mSfHwRotation = android.os.SystemProperties.getInt("ro.sf.hwrotation",0) / 90;
}
/**
@@ -539,7 +543,10 @@ class GlobalScreenshot {
// only in the natural orientation of the device :!)
mDisplay.getRealMetrics(mDisplayMetrics);
float[] dims = {mDisplayMetrics.widthPixels, mDisplayMetrics.heightPixels};
- float degrees = getDegreesForRotation(mDisplay.getRotation());
+ int rot = mDisplay.getRotation();
+ // Allow for abnormal hardware orientation
+ rot = (rot + mSfHwRotation) % 4;
+ float degrees = getDegreesForRotation(rot);
boolean requiresRotation = (degrees > 0);
if (requiresRotation) {
// Get the dimensions of the device in its native orientation
diff --git a/packages/SystemUI/src/com/android/systemui/settings/BrightnessController.java b/packages/SystemUI/src/com/android/systemui/settings/BrightnessController.java
index 77c27fa..e74edd0 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/BrightnessController.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/BrightnessController.java
@@ -42,7 +42,7 @@ public class BrightnessController implements ToggleSlider.Listener {
* {@link android.provider.Settings.System#SCREEN_AUTO_BRIGHTNESS_ADJ} uses the range [-1, 1].
* Using this factor, it is converted to [0, BRIGHTNESS_ADJ_RESOLUTION] for the SeekBar.
*/
- private static final float BRIGHTNESS_ADJ_RESOLUTION = 2048;
+ public static final float BRIGHTNESS_ADJ_RESOLUTION = 2048;
private final int mMinimumBacklight;
private final int mMaximumBacklight;
diff --git a/packages/SystemUI/src/com/android/systemui/settings/NotificationBrightnessController.java b/packages/SystemUI/src/com/android/systemui/settings/NotificationBrightnessController.java
new file mode 100644
index 0000000..617ef8f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/settings/NotificationBrightnessController.java
@@ -0,0 +1,247 @@
+/*
+ * Copyright (C) 2012-2015 The CyanogenMod 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.settings;
+
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.database.ContentObserver;
+import android.net.Uri;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
+import android.os.UserHandle;
+import android.provider.Settings;
+
+import com.android.systemui.R;
+
+import java.lang.Exception;
+import java.util.ArrayList;
+
+import cyanogenmod.providers.CMSettings;
+
+public class NotificationBrightnessController implements ToggleSlider.Listener {
+ private static final String TAG = "StatusBar.NotificationBrightnessController";
+
+ public static final int LIGHT_BRIGHTNESS_MINIMUM = 1;
+ public static final int LIGHT_BRIGHTNESS_MAXIMUM = 255;
+
+ // Minimum delay between LED notification updates
+ private final static long LED_UPDATE_DELAY_MS = 250;
+
+ private int mCurrentBrightness;
+ private final int mMinimumBrightness;
+ private final int mMaximumBrightness;
+
+ private final Context mContext;
+ private final ToggleSlider mControl;
+ private final CurrentUserTracker mUserTracker;
+ private final Handler mHandler;
+ private final NotificationBrightnessObserver mBrightnessObserver;
+
+ private ArrayList<BrightnessStateChangeCallback> mChangeCallbacks =
+ new ArrayList<BrightnessStateChangeCallback>();
+
+ private boolean mListening;
+ private boolean mExternalChange;
+
+ private boolean mNotificationAllow;
+ private final Bundle mNotificationBundle;
+ private final Notification.Builder mNotificationBuilder;
+ private NotificationManager mNotificationManager;
+
+ public interface BrightnessStateChangeCallback {
+ public void onBrightnessLevelChanged();
+ }
+
+ /** ContentObserver to watch brightness **/
+ private class NotificationBrightnessObserver extends ContentObserver {
+
+ private final Uri NOTIFICATION_LIGHT_BRIGHTNESS_LEVEL_URI =
+ CMSettings.System.getUriFor(CMSettings.System.NOTIFICATION_LIGHT_BRIGHTNESS_LEVEL);
+
+ public NotificationBrightnessObserver(Handler handler) {
+ super(handler);
+ }
+
+ @Override
+ public void onChange(boolean selfChange) {
+ onChange(selfChange, null);
+ }
+
+ @Override
+ public void onChange(boolean selfChange, Uri uri) {
+ if (selfChange) return;
+ try {
+ mExternalChange = true;
+ updateSlider();
+ for (BrightnessStateChangeCallback cb : mChangeCallbacks) {
+ cb.onBrightnessLevelChanged();
+ }
+ } finally {
+ mExternalChange = false;
+ }
+ }
+
+ public void startObserving() {
+ final ContentResolver cr = mContext.getContentResolver();
+ cr.unregisterContentObserver(this);
+ cr.registerContentObserver(
+ NOTIFICATION_LIGHT_BRIGHTNESS_LEVEL_URI,
+ false, this, UserHandle.USER_ALL);
+ }
+
+ public void stopObserving() {
+ final ContentResolver cr = mContext.getContentResolver();
+ cr.unregisterContentObserver(this);
+ }
+
+ }
+
+ public NotificationBrightnessController(Context context, ToggleSlider control) {
+ mContext = context;
+ mControl = control;
+ mHandler = new Handler();
+ mUserTracker = new CurrentUserTracker(mContext) {
+ @Override
+ public void onUserSwitched(int newUserId) {
+ updateSlider();
+ }
+ };
+ mBrightnessObserver = new NotificationBrightnessObserver(mHandler);
+
+ mMinimumBrightness = LIGHT_BRIGHTNESS_MINIMUM;
+ mMaximumBrightness = LIGHT_BRIGHTNESS_MAXIMUM;
+ mCurrentBrightness = LIGHT_BRIGHTNESS_MAXIMUM;
+
+ mNotificationManager =
+ (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
+
+ mNotificationBundle = new Bundle();
+ mNotificationBuilder = new Notification.Builder(mContext);
+
+ mNotificationBundle.putBoolean(Notification.EXTRA_FORCE_SHOW_LIGHTS, true);
+ mNotificationBuilder.setExtras(mNotificationBundle)
+ .setContentTitle(mContext.getString(R.string.led_notification_title))
+ .setContentText(mContext.getString(R.string.led_notification_text))
+ .setSmallIcon(R.drawable.ic_settings)
+ .setOngoing(true);
+ }
+
+ private Handler mLedHandler = new Handler() {
+ public void handleMessage(Message msg) {
+ updateNotification();
+ }
+ };
+
+ public void addStateChangedCallback(BrightnessStateChangeCallback cb) {
+ mChangeCallbacks.add(cb);
+ }
+
+ public boolean removeStateChangedCallback(BrightnessStateChangeCallback cb) {
+ return mChangeCallbacks.remove(cb);
+ }
+
+ @Override
+ public void onInit(ToggleSlider control) {
+ // Do nothing
+ }
+
+ public void registerCallbacks() {
+ if (mListening) {
+ return;
+ }
+
+ // Update the slider and mode before attaching the listener so we don't
+ // receive the onChanged notifications for the initial values.
+ mNotificationAllow = true;
+ updateSlider();
+
+ mBrightnessObserver.startObserving();
+ mUserTracker.startTracking();
+
+ mControl.setOnChangedListener(this);
+ mListening = true;
+ }
+
+ /** Unregister all call backs, both to and from the controller */
+ public void unregisterCallbacks() {
+ if (!mListening) {
+ return;
+ }
+
+ mNotificationAllow = false;
+ mBrightnessObserver.stopObserving();
+ mUserTracker.stopTracking();
+ mControl.setOnChangedListener(null);
+ mNotificationManager.cancel(1);
+ mListening = false;
+
+ CMSettings.System.putIntForUser(mContext.getContentResolver(),
+ CMSettings.System.NOTIFICATION_LIGHT_BRIGHTNESS_LEVEL,
+ mCurrentBrightness, UserHandle.USER_CURRENT);
+ }
+
+ @Override
+ public void onChanged(ToggleSlider view, boolean tracking, boolean automatic, int value,
+ boolean stopTracking) {
+ if (mExternalChange) return;
+
+ mCurrentBrightness = value + mMinimumBrightness;
+ updateNotification();
+
+ for (BrightnessStateChangeCallback cb : mChangeCallbacks) {
+ cb.onBrightnessLevelChanged();
+ }
+ }
+
+ /** Fetch the brightness from the system settings and update the slider */
+ private void updateSlider() {
+ mCurrentBrightness = CMSettings.System.getIntForUser(mContext.getContentResolver(),
+ CMSettings.System.NOTIFICATION_LIGHT_BRIGHTNESS_LEVEL,
+ mMaximumBrightness, UserHandle.USER_CURRENT);
+
+ CMSettings.System.putIntForUser(mContext.getContentResolver(),
+ CMSettings.System.NOTIFICATION_LIGHT_BRIGHTNESS_LEVEL,
+ mMaximumBrightness, UserHandle.USER_CURRENT);
+
+ mControl.setMax(mMaximumBrightness - mMinimumBrightness);
+ mControl.setValue(mCurrentBrightness - mMinimumBrightness);
+ updateNotification();
+ }
+
+ /** Fetch the brightness from the system settings and update the slider */
+ private void updateNotification() {
+ // Dampen rate of consecutive LED changes
+ if (mLedHandler.hasMessages(0)) {
+ return;
+ }
+
+ if (mNotificationAllow) {
+ mLedHandler.sendEmptyMessageDelayed(0, LED_UPDATE_DELAY_MS);
+
+ // Instead of canceling the notification, force it to update with the color.
+ // Use a white light for a better preview of the brightness.
+ int notificationColor = 0xFFFFFF | (mCurrentBrightness << 24);
+ mNotificationBuilder.setLights(notificationColor, 1, 0);
+ mNotificationManager.notify(1, mNotificationBuilder.build());
+ }
+ }
+
+}
diff --git a/packages/SystemUI/src/com/android/systemui/settings/NotificationBrightnessDialog.java b/packages/SystemUI/src/com/android/systemui/settings/NotificationBrightnessDialog.java
new file mode 100644
index 0000000..82c1b3c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/settings/NotificationBrightnessDialog.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2012-2015 The CyanogenMod 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.settings;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.view.Gravity;
+import android.view.KeyEvent;
+import android.view.Window;
+import android.view.WindowManager;
+
+import com.android.systemui.R;
+
+/** A dialog that provides controls for adjusting the notifications brightness. */
+public class NotificationBrightnessDialog extends Activity {
+
+ private NotificationBrightnessController mNotificationBrightnessController;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ final Window window = getWindow();
+
+ window.setGravity(Gravity.TOP);
+ window.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND);
+ window.requestFeature(Window.FEATURE_NO_TITLE);
+
+ setContentView(R.layout.quick_settings_notification_brightness_dialog);
+
+ final ToggleSlider slider = (ToggleSlider) findViewById(R.id.notification_brightness_slider);
+ mNotificationBrightnessController = new NotificationBrightnessController(this, slider);
+ }
+
+ @Override
+ protected void onStart() {
+ super.onStart();
+ mNotificationBrightnessController.registerCallbacks();
+ }
+
+ @Override
+ protected void onStop() {
+ super.onStop();
+ mNotificationBrightnessController.unregisterCallbacks();
+ }
+
+ @Override
+ public boolean onKeyDown(int keyCode, KeyEvent event) {
+ if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN
+ || keyCode == KeyEvent.KEYCODE_VOLUME_UP
+ || keyCode == KeyEvent.KEYCODE_VOLUME_MUTE) {
+ finish();
+ }
+
+ return super.onKeyDown(keyCode, event);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java
index 7f17885..dba74de 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java
@@ -34,6 +34,7 @@ import android.view.animation.LinearInterpolator;
import android.view.animation.PathInterpolator;
import com.android.systemui.R;
+import com.android.systemui.ViewInvertHelper;
/**
* Base class for both {@link ExpandableNotificationRow} and {@link NotificationOverflowContainer}
@@ -128,6 +129,8 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView
private final int mNormalColor;
private final int mLowPriorityColor;
private boolean mIsBelowSpeedBump;
+ private ViewInvertHelper mBackgroundNormalInvertHelper;
+ private ViewInvertHelper mBackgroundDimmedInvertHelper;
public ActivatableNotificationView(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -145,6 +148,9 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView
mNormalColor = context.getColor(R.color.notification_material_background_color);
mLowPriorityColor = context.getColor(
R.color.notification_material_background_low_priority_color);
+ int roundedRectCornerRadius = getResources().getDimensionPixelSize(
+ R.dimen.notification_material_rounded_rect_radius);
+ setRoundCornerRadius(roundedRectCornerRadius); // Themes: For drop-shadow rounded corners
mTintedRippleColor = context.getColor(
R.color.notification_ripple_tinted_color);
mLowPriorityRippleColor = context.getColor(
@@ -160,6 +166,10 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView
mBackgroundDimmed = (NotificationBackgroundView) findViewById(R.id.backgroundDimmed);
mBackgroundNormal.setCustomBackground(R.drawable.notification_material_bg);
mBackgroundDimmed.setCustomBackground(R.drawable.notification_material_bg_dim);
+ mBackgroundNormalInvertHelper =
+ new ViewInvertHelper(mBackgroundNormal, DARK_ANIMATION_LENGTH);
+ mBackgroundDimmedInvertHelper =
+ new ViewInvertHelper(mBackgroundDimmed, DARK_ANIMATION_LENGTH);
updateBackground();
updateBackgroundTint();
}
@@ -393,15 +403,15 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView
*/
private void fadeInFromDark(long delay) {
final View background = mDimmed ? mBackgroundDimmed : mBackgroundNormal;
- background.setAlpha(0f);
- background.setPivotX(mBackgroundDimmed.getWidth() / 2f);
- background.setPivotY(getActualHeight() / 2f);
- background.setScaleX(DARK_EXIT_SCALE_START);
- background.setScaleY(DARK_EXIT_SCALE_START);
+ if (mDimmed) {
+ mBackgroundDimmedInvertHelper.fade(false, delay);
+ mBackgroundNormalInvertHelper.update(false);
+ } else {
+ mBackgroundDimmedInvertHelper.update(false);
+ mBackgroundNormalInvertHelper.fade(false, delay);
+ }
background.animate()
.alpha(1f)
- .scaleX(1f)
- .scaleY(1f)
.setDuration(DARK_ANIMATION_LENGTH)
.setStartDelay(delay)
.setInterpolator(mLinearOutSlowInInterpolator)
@@ -409,8 +419,6 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView
@Override
public void onAnimationCancel(Animator animation) {
// Jump state if we are cancelled
- background.setScaleX(1f);
- background.setScaleY(1f);
background.setAlpha(1f);
}
})
@@ -463,9 +471,11 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView
private void updateBackground() {
cancelFadeAnimations();
+ mBackgroundNormalInvertHelper.update(mDark);
+ mBackgroundDimmedInvertHelper.update(mDark);
if (mDark) {
mBackgroundDimmed.setVisibility(View.INVISIBLE);
- mBackgroundNormal.setVisibility(View.INVISIBLE);
+ mBackgroundNormal.setVisibility(View.VISIBLE);
} else if (mDimmed) {
mBackgroundDimmed.setVisibility(View.VISIBLE);
mBackgroundNormal.setVisibility(View.INVISIBLE);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
index 9ff86eb..e958ee1 100644..100755
--- a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
@@ -29,6 +29,8 @@ import android.app.admin.DevicePolicyManager;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
+import android.content.ContentResolver;
+import android.content.ContentValues;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.ApplicationInfo;
@@ -38,10 +40,13 @@ import android.content.pm.ResolveInfo;
import android.content.pm.UserInfo;
import android.content.res.Configuration;
import android.content.res.Resources;
+import android.content.res.ThemeConfig;
import android.database.ContentObserver;
import android.graphics.PorterDuff;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.Icon;
+import android.media.session.MediaController;
+import android.net.Uri;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
@@ -87,6 +92,9 @@ import com.android.internal.logging.MetricsLogger;
import com.android.internal.statusbar.IStatusBarService;
import com.android.internal.statusbar.StatusBarIcon;
import com.android.internal.statusbar.StatusBarIconList;
+import com.android.internal.util.cm.SpamFilter;
+import com.android.internal.util.cm.SpamFilter.SpamContract.NotificationTable;
+import com.android.internal.util.cm.SpamFilter.SpamContract.PackageTable;
import com.android.internal.util.NotificationColorUtil;
import com.android.internal.widget.LockPatternUtils;
import com.android.keyguard.KeyguardUpdateMonitor;
@@ -97,6 +105,7 @@ import com.android.systemui.SwipeHelper;
import com.android.systemui.SystemUI;
import com.android.systemui.assist.AssistManager;
import com.android.systemui.recents.Recents;
+import com.android.systemui.cm.SpamMessageProvider;
import com.android.systemui.statusbar.NotificationData.Entry;
import com.android.systemui.statusbar.phone.NavigationBarView;
import com.android.systemui.statusbar.phone.NotificationGroupManager;
@@ -109,6 +118,8 @@ import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
+import cyanogenmod.providers.CMSettings;
+
import static com.android.keyguard.KeyguardHostView.OnDismissAction;
public abstract class BaseStatusBar extends SystemUI implements
@@ -146,6 +157,14 @@ public abstract class BaseStatusBar extends SystemUI implements
private static final String BANNER_ACTION_SETUP =
"com.android.systemui.statusbar.banner_action_setup";
+ protected static final int SYSTEM_UI_VISIBILITY_MASK = 0xffffffff;
+
+ private static final Uri SPAM_MESSAGE_URI = new Uri.Builder()
+ .scheme(ContentResolver.SCHEME_CONTENT)
+ .authority(SpamMessageProvider.AUTHORITY)
+ .appendPath("messages")
+ .build();
+
protected CommandQueue mCommandQueue;
protected IStatusBarService mBarService;
protected H mHandler = createHandler();
@@ -207,6 +226,8 @@ public abstract class BaseStatusBar extends SystemUI implements
protected WindowManager mWindowManager;
protected IWindowManager mWindowManagerService;
+ private NotificationManager mNoMan;
+
protected abstract void refreshLayout(int layoutDirection);
protected Display mDisplay;
@@ -237,6 +258,10 @@ public abstract class BaseStatusBar extends SystemUI implements
protected AssistManager mAssistManager;
+ // last theme that was applied in order to detect theme change (as opposed
+ // to some other configuration change).
+ protected ThemeConfig mCurrentTheme;
+
@Override // NotificationData.Environment
public boolean isDeviceProvisioned() {
return mDeviceProvisioned;
@@ -245,8 +270,8 @@ public abstract class BaseStatusBar extends SystemUI implements
protected final ContentObserver mSettingsObserver = new ContentObserver(mHandler) {
@Override
public void onChange(boolean selfChange) {
- final boolean provisioned = 0 != Settings.Global.getInt(
- mContext.getContentResolver(), Settings.Global.DEVICE_PROVISIONED, 0);
+ final boolean provisioned = 0 != CMSettings.Secure.getInt(
+ mContext.getContentResolver(), CMSettings.Secure.CM_SETUP_WIZARD_COMPLETED, 0);
if (provisioned != mDeviceProvisioned) {
mDeviceProvisioned = provisioned;
updateNotifications();
@@ -270,6 +295,10 @@ public abstract class BaseStatusBar extends SystemUI implements
}
};
+ public RemoteViews.OnClickHandler getOnClickHandler() {
+ return mOnClickHandler;
+ }
+
private RemoteViews.OnClickHandler mOnClickHandler = new RemoteViews.OnClickHandler() {
@Override
public boolean onClickHandler(
@@ -405,9 +434,7 @@ public abstract class BaseStatusBar extends SystemUI implements
}
}
} else if (BANNER_ACTION_CANCEL.equals(action) || BANNER_ACTION_SETUP.equals(action)) {
- NotificationManager noMan = (NotificationManager)
- mContext.getSystemService(Context.NOTIFICATION_SERVICE);
- noMan.cancel(R.id.notification_hidden);
+ mNoMan.cancel(R.id.notification_hidden);
Settings.Secure.putInt(mContext.getContentResolver(),
Settings.Secure.SHOW_NOTE_ABOUT_NOTIFICATION_HIDING, 0);
@@ -536,6 +563,9 @@ public abstract class BaseStatusBar extends SystemUI implements
public void start() {
mWindowManager = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);
mWindowManagerService = WindowManagerGlobal.getWindowManagerService();
+
+ mNoMan = (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
+
mDisplay = mWindowManager.getDefaultDisplay();
mDevicePolicyManager = (DevicePolicyManager)mContext.getSystemService(
Context.DEVICE_POLICY_SERVICE);
@@ -552,8 +582,8 @@ public abstract class BaseStatusBar extends SystemUI implements
mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
mContext.getContentResolver().registerContentObserver(
- Settings.Global.getUriFor(Settings.Global.DEVICE_PROVISIONED), true,
- mSettingsObserver);
+ CMSettings.Secure.getUriFor(CMSettings.Secure.CM_SETUP_WIZARD_COMPLETED), false,
+ mSettingsObserver, UserHandle.USER_ALL);
mContext.getContentResolver().registerContentObserver(
Settings.Global.getUriFor(Settings.Global.ZEN_MODE), false,
mSettingsObserver);
@@ -602,7 +632,7 @@ public abstract class BaseStatusBar extends SystemUI implements
mSettingsObserver.onChange(false); // set up
disable(switches[0], switches[6], false /* animate */);
- setSystemUiVisibility(switches[1], 0xffffffff);
+ setSystemUiVisibility(switches[1], SYSTEM_UI_VISIBILITY_MASK);
topAppWindowChanged(switches[2] != 0);
// StatusBarManagerService has a back up of IME token and it's restored here.
setImeWindowStatus(binders.get(0), switches[3], switches[4], switches[5] != 0);
@@ -701,9 +731,7 @@ public abstract class BaseStatusBar extends SystemUI implements
mContext.getString(R.string.hidden_notifications_setup),
setupIntent);
- NotificationManager noMan =
- (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
- noMan.notify(R.id.notification_hidden, note.build());
+ mNoMan.notify(R.id.notification_hidden, note.build());
}
}
@@ -747,6 +775,10 @@ public abstract class BaseStatusBar extends SystemUI implements
return null;
}
+ protected MediaController getCurrentMediaController() {
+ return null;
+ }
+
@Override
public NotificationGroupManager getGroupManager() {
return mGroupManager;
@@ -915,6 +947,7 @@ public abstract class BaseStatusBar extends SystemUI implements
final View settingsButton = guts.findViewById(R.id.notification_inspect_item);
final View appSettingsButton
= guts.findViewById(R.id.notification_inspect_app_provided_settings);
+ final View filterButton = guts.findViewById(R.id.notification_inspect_filter_notification);
if (appUid >= 0) {
final int appUidF = appUid;
settingsButton.setOnClickListener(new View.OnClickListener() {
@@ -924,6 +957,26 @@ public abstract class BaseStatusBar extends SystemUI implements
}
});
+ Notification notification = sbn.getNotification();
+ filterButton.setVisibility(SpamFilter.hasFilterableContent(notification)
+ ? View.VISIBLE : View.GONE);
+ filterButton.setOnClickListener(new View.OnClickListener() {
+ public void onClick(View v) {
+ AsyncTask.execute(new Runnable() {
+ @Override
+ public void run() {
+ ContentValues values = new ContentValues();
+ String message = SpamFilter.getNotificationContent(
+ sbn.getNotification());
+ values.put(NotificationTable.MESSAGE_TEXT, message);
+ values.put(PackageTable.PACKAGE_NAME, pkg);
+ mContext.getContentResolver().insert(SPAM_MESSAGE_URI, values);
+ }
+ });
+ removeNotification(sbn.getKey(), null);
+ }
+ });
+
final Intent appSettingsQueryIntent
= new Intent(Intent.ACTION_MAIN)
.addCategory(Notification.INTENT_CATEGORY_NOTIFICATION_PREFERENCES)
@@ -953,6 +1006,7 @@ public abstract class BaseStatusBar extends SystemUI implements
} else {
settingsButton.setVisibility(View.GONE);
appSettingsButton.setVisibility(View.GONE);
+ filterButton.setVisibility(View.GONE);
}
}
@@ -972,6 +1026,10 @@ public abstract class BaseStatusBar extends SystemUI implements
}
ExpandableNotificationRow row = (ExpandableNotificationRow) v;
+ if (v instanceof MediaExpandableNotificationRow
+ && !((MediaExpandableNotificationRow) v).inflateGuts()) {
+ return false;
+ }
bindGuts(row);
// Assume we are a status_bar_notification_row
@@ -1092,26 +1150,6 @@ public abstract class BaseStatusBar extends SystemUI implements
protected abstract View getStatusBarView();
- protected View.OnTouchListener mRecentsPreloadOnTouchListener = new View.OnTouchListener() {
- // additional optimization when we have software system buttons - start loading the recent
- // tasks on touch down
- @Override
- public boolean onTouch(View v, MotionEvent event) {
- int action = event.getAction() & MotionEvent.ACTION_MASK;
- if (action == MotionEvent.ACTION_DOWN) {
- preloadRecents();
- } else if (action == MotionEvent.ACTION_CANCEL) {
- cancelPreloadingRecents();
- } else if (action == MotionEvent.ACTION_UP) {
- if (!v.isPressed()) {
- cancelPreloadingRecents();
- }
-
- }
- return false;
- }
- };
-
/** Proxy for RecentsComponent */
protected void showRecents(boolean triggeredFromAltTab) {
@@ -1258,6 +1296,20 @@ public abstract class BaseStatusBar extends SystemUI implements
}
protected boolean inflateViews(Entry entry, ViewGroup parent) {
+ final StatusBarNotification sbn = entry.notification;
+ String themePackageName = mCurrentTheme != null
+ ? mCurrentTheme.getOverlayPkgNameForApp(sbn.getPackageName()) : null;
+ boolean inflated = inflateViews(entry, parent, true);
+ if (!inflated && themePackageName != null
+ && !ThemeConfig.SYSTEM_DEFAULT.equals(themePackageName)) {
+ Log.w(TAG, "Couldn't expand themed RemoteViews, trying unthemed for: " + sbn);
+ inflated = inflateViews(entry, mStackScroller, false);
+ }
+
+ return inflated;
+ }
+
+ protected boolean inflateViews(Entry entry, ViewGroup parent, boolean isThemeable) {
PackageManager pmUser = getPackageManagerForUser(
entry.notification.getUser().getIdentifier());
@@ -1298,8 +1350,19 @@ public abstract class BaseStatusBar extends SystemUI implements
// create the row view
LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(
Context.LAYOUT_INFLATER_SERVICE);
- row = (ExpandableNotificationRow) inflater.inflate(R.layout.status_bar_notification_row,
- parent, false);
+
+ // cannot use isMediaNotification()
+ if (sbn.getNotification().category != null
+ && sbn.getNotification().category.equals(Notification.CATEGORY_TRANSPORT)) {
+ row = (MediaExpandableNotificationRow) inflater.inflate(
+ R.layout.status_bar_notification_row_media, parent, false);
+ ((MediaExpandableNotificationRow)row).setMediaController(
+ getCurrentMediaController());
+ } else {
+ row = (ExpandableNotificationRow) inflater.inflate(
+ R.layout.status_bar_notification_row,
+ parent, false);
+ }
row.setExpansionLogger(this, entry.notification.getKey());
row.setGroupManager(mGroupManager);
}
@@ -1323,22 +1386,52 @@ public abstract class BaseStatusBar extends SystemUI implements
View contentViewLocal = null;
View bigContentViewLocal = null;
View headsUpContentViewLocal = null;
+ String themePackageName = (isThemeable && mCurrentTheme != null)
+ ? mCurrentTheme.getOverlayPkgNameForApp(sbn.getPackageName())
+ : ThemeConfig.SYSTEM_DEFAULT;
+ String statusBarThemePackageName = (isThemeable && mCurrentTheme != null)
+ ? mCurrentTheme.getOverlayForStatusBar()
+ : ThemeConfig.SYSTEM_DEFAULT;
+
try {
contentViewLocal = contentView.apply(
sbn.getPackageContext(mContext),
contentContainer,
- mOnClickHandler);
+ mOnClickHandler,
+ statusBarThemePackageName);
+
+ final int platformTemplateRootViewId =
+ com.android.internal.R.id.status_bar_latest_event_content;
+ final String inflationThemePackageName;
+ if (themePackageName != null
+ && !TextUtils.equals(themePackageName, statusBarThemePackageName)
+ && contentViewLocal.getId() != platformTemplateRootViewId) {
+ // This notification uses custom RemoteViews, and its app uses a different
+ // theme than the status bar. Re-inflate the views using the app's theme,
+ // as the RemoteViews likely will contain resources of the app, not the platform
+ inflationThemePackageName = themePackageName;
+ contentViewLocal = contentView.apply(
+ sbn.getPackageContext(mContext),
+ contentContainer,
+ mOnClickHandler,
+ inflationThemePackageName);
+ } else {
+ inflationThemePackageName = statusBarThemePackageName;
+ }
+
if (bigContentView != null) {
bigContentViewLocal = bigContentView.apply(
sbn.getPackageContext(mContext),
contentContainer,
- mOnClickHandler);
+ mOnClickHandler,
+ inflationThemePackageName);
}
if (headsUpContentView != null) {
headsUpContentViewLocal = headsUpContentView.apply(
sbn.getPackageContext(mContext),
contentContainer,
- mOnClickHandler);
+ mOnClickHandler,
+ inflationThemePackageName);
}
}
catch (RuntimeException e) {
@@ -1389,8 +1482,10 @@ public abstract class BaseStatusBar extends SystemUI implements
}
if (publicViewLocal == null) {
+ final Context layoutContext = isThemeable ? mContext
+ : maybeGetThemedContext(mContext, ThemeConfig.SYSTEM_DEFAULT);
// Add a basic notification template
- publicViewLocal = LayoutInflater.from(mContext).inflate(
+ publicViewLocal = LayoutInflater.from(layoutContext).inflate(
R.layout.notification_public_default,
contentContainerPublic, false);
publicViewLocal.setIsRootNamespace(true);
@@ -1403,49 +1498,12 @@ public abstract class BaseStatusBar extends SystemUI implements
title.setText(entry.notification.getPackageName());
}
- final ImageView icon = (ImageView) publicViewLocal.findViewById(R.id.icon);
- final ImageView profileBadge = (ImageView) publicViewLocal.findViewById(
- R.id.profile_badge_line3);
-
- final StatusBarIcon ic = new StatusBarIcon(
- entry.notification.getUser(),
- entry.notification.getPackageName(),
- entry.notification.getNotification().getSmallIcon(),
- entry.notification.getNotification().iconLevel,
- entry.notification.getNotification().number,
- entry.notification.getNotification().tickerText);
-
- Drawable iconDrawable = StatusBarIconView.getIcon(mContext, ic);
- icon.setImageDrawable(iconDrawable);
- if (entry.targetSdk >= Build.VERSION_CODES.LOLLIPOP
- || mNotificationColorUtil.isGrayscaleIcon(iconDrawable)) {
- icon.setBackgroundResource(
- com.android.internal.R.drawable.notification_icon_legacy_bg);
- int padding = mContext.getResources().getDimensionPixelSize(
- com.android.internal.R.dimen.notification_large_icon_circle_padding);
- icon.setPadding(padding, padding, padding, padding);
- if (sbn.getNotification().color != Notification.COLOR_DEFAULT) {
- icon.getBackground().setColorFilter(
- sbn.getNotification().color, PorterDuff.Mode.SRC_ATOP);
- }
- }
-
- if (profileBadge != null) {
- Drawable profileDrawable = mContext.getPackageManager().getUserBadgeForDensity(
- entry.notification.getUser(), 0);
- if (profileDrawable != null) {
- profileBadge.setImageDrawable(profileDrawable);
- profileBadge.setVisibility(View.VISIBLE);
- } else {
- profileBadge.setVisibility(View.GONE);
- }
- }
+ updatePublicViewProperties(publicViewLocal, entry);
final View privateTime = contentViewLocal.findViewById(com.android.internal.R.id.time);
final DateTimeView time = (DateTimeView) publicViewLocal.findViewById(R.id.time);
if (privateTime != null && privateTime.getVisibility() == View.VISIBLE) {
time.setVisibility(View.VISIBLE);
- time.setTime(entry.notification.getNotification().when);
}
final TextView text = (TextView) publicViewLocal.findViewById(R.id.text);
@@ -1862,7 +1920,18 @@ public abstract class BaseStatusBar extends SystemUI implements
}
private boolean shouldShowOnKeyguard(StatusBarNotification sbn) {
- return mShowLockscreenNotifications && !mNotificationData.isAmbient(sbn.getKey());
+ if (!mShowLockscreenNotifications || mNotificationData.isAmbient(sbn.getKey())) {
+ return false;
+ }
+ final int showOnKeyguard = mNoMan.getShowNotificationForPackageOnKeyguard(
+ sbn.getPackageName(), sbn.getUid());
+ boolean isKeyguardAllowedForApp =
+ (showOnKeyguard & Notification.SHOW_ALL_NOTI_ON_KEYGUARD) != 0;
+ if (isKeyguardAllowedForApp && sbn.isOngoing()) {
+ isKeyguardAllowedForApp =
+ (showOnKeyguard & Notification.SHOW_NO_ONGOING_NOTI_ON_KEYGUARD) == 0;
+ }
+ return isKeyguardAllowedForApp;
}
protected void setZenMode(int mode) {
@@ -1914,8 +1983,9 @@ public abstract class BaseStatusBar extends SystemUI implements
boolean shouldInterrupt = shouldInterrupt(entry, notification);
boolean alertAgain = alertAgain(entry, n);
+ final StatusBarNotification oldNotification = entry.notification;
entry.notification = notification;
- mGroupManager.onEntryUpdated(entry, entry.notification);
+ mGroupManager.onEntryUpdated(entry, oldNotification);
boolean updateSuccessful = false;
if (applyInPlace) {
@@ -1957,7 +2027,9 @@ public abstract class BaseStatusBar extends SystemUI implements
entry.icon.set(ic);
inflateViews(entry, mStackScroller);
}
- updateHeadsUp(key, entry, shouldInterrupt, alertAgain);
+ if (mUseHeadsUp) {
+ updateHeadsUp(key, entry, shouldInterrupt, alertAgain);
+ }
mNotificationData.updateRanking(ranking);
updateNotifications();
@@ -2051,6 +2123,7 @@ public abstract class BaseStatusBar extends SystemUI implements
final Notification publicVersion = notification.getNotification().publicVersion;
final RemoteViews publicContentView = publicVersion != null ? publicVersion.contentView
: null;
+ final View publicLocalView = entry.getPublicContentView();
// Reapply the RemoteViews
contentView.reapply(mContext, entry.getContentView(), mOnClickHandler);
@@ -2064,13 +2137,18 @@ public abstract class BaseStatusBar extends SystemUI implements
headsUpContentView.reapply(notification.getPackageContext(mContext),
headsUpChild, mOnClickHandler);
}
- if (publicContentView != null && entry.getPublicContentView() != null) {
- publicContentView.reapply(notification.getPackageContext(mContext),
- entry.getPublicContentView(), mOnClickHandler);
+ if (publicLocalView != null) {
+ if (publicContentView != null) {
+ publicContentView.reapply(notification.getPackageContext(mContext),
+ publicLocalView, mOnClickHandler);
+ } else {
+ updatePublicViewProperties(publicLocalView, entry);
+ }
}
// update the contentIntent
mNotificationClicker.register(entry.row, notification);
+ applyColorsAndBackgrounds(notification, entry);
entry.row.setStatusBarNotification(notification);
entry.row.notifyContentUpdated();
entry.row.resetHeight();
@@ -2080,6 +2158,55 @@ public abstract class BaseStatusBar extends SystemUI implements
maybeEscalateHeadsUp();
}
+ private void updatePublicViewProperties(View publicView, Entry entry) {
+ final StatusBarNotification n = entry.notification;
+ final ImageView icon = (ImageView) publicView.findViewById(R.id.icon);
+ final ImageView profileBadge =
+ (ImageView) publicView.findViewById(R.id.profile_badge_line3);
+ final DateTimeView time = (DateTimeView) publicView.findViewById(R.id.time);
+
+ if (icon != null) {
+ final StatusBarIcon ic = new StatusBarIcon(
+ n.getUser(), n.getPackageName(),
+ n.getNotification().getSmallIcon(),
+ n.getNotification().iconLevel,
+ n.getNotification().number,
+ n.getNotification().tickerText);
+
+ Drawable iconDrawable = StatusBarIconView.getIcon(mContext, ic);
+ icon.setImageDrawable(iconDrawable);
+ if (entry.targetSdk >= Build.VERSION_CODES.LOLLIPOP
+ || mNotificationColorUtil.isGrayscaleIcon(iconDrawable)) {
+ icon.setBackgroundResource(
+ com.android.internal.R.drawable.notification_icon_legacy_bg);
+ int padding = mContext.getResources().getDimensionPixelSize(
+ com.android.internal.R.dimen.notification_large_icon_circle_padding);
+ icon.setPadding(padding, padding, padding, padding);
+ if (n.getNotification().color != Notification.COLOR_DEFAULT) {
+ icon.getBackground().setColorFilter(
+ n.getNotification().color, PorterDuff.Mode.SRC_ATOP);
+ }
+ } else {
+ icon.setBackgroundDrawable(null);
+ }
+ }
+
+ if (time != null) {
+ time.setTime(entry.notification.getNotification().when);
+ }
+
+ if (profileBadge != null) {
+ Drawable profileDrawable = mContext.getPackageManager().getUserBadgeForDensity(
+ n.getUser(), 0);
+ if (profileDrawable != null) {
+ profileBadge.setImageDrawable(profileDrawable);
+ profileBadge.setVisibility(View.VISIBLE);
+ } else {
+ profileBadge.setVisibility(View.GONE);
+ }
+ }
+ }
+
private boolean alertAgain(Entry oldEntry, Notification newNotification) {
return oldEntry == null || !oldEntry.hasInterrupted()
|| (newNotification.flags & Notification.FLAG_ONLY_ALERT_ONCE) == 0;
@@ -2215,4 +2342,24 @@ public abstract class BaseStatusBar extends SystemUI implements
mAssistManager.startAssist(args);
}
}
+
+ /**
+ * Returns a context with the given theme applied or the original context if we fail to get a
+ * themed context.
+ */
+ private Context maybeGetThemedContext(Context context, String themePkg) {
+ Context themedContext;
+ try {
+ ApplicationInfo ai = context.getPackageManager().getApplicationInfo(
+ context.getPackageName(), 0);
+ themedContext = context.createApplicationContext(ai, themePkg,
+ 0);
+ } catch (PackageManager.NameNotFoundException e) {
+ themedContext = null;
+ }
+ if (themedContext == null) {
+ themedContext = context;
+ }
+ return themedContext;
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
index 897f5e5..83b2fb5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
@@ -78,6 +78,7 @@ public class CommandQueue extends IStatusBar.Stub {
private StatusBarIconList mList;
private Callbacks mCallbacks;
private Handler mHandler = new H();
+ private boolean mPaused = false;
/**
* These methods are called back on the main thread.
@@ -295,6 +296,14 @@ public class CommandQueue extends IStatusBar.Stub {
}
}
+ public void pause() {
+ mPaused = true;
+ }
+
+ public void resume() {
+ mPaused = false;
+ }
+
@Override
public void onCameraLaunchGestureDetected(int source) {
synchronized (mList) {
@@ -305,6 +314,10 @@ public class CommandQueue extends IStatusBar.Stub {
private final class H extends Handler {
public void handleMessage(Message msg) {
+ if (mPaused) {
+ this.sendMessageAtFrontOfQueue(Message.obtain(msg));
+ return;
+ }
final int what = msg.what & MSG_MASK;
switch (what) {
case MSG_ICON: {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CustomTileData.java b/packages/SystemUI/src/com/android/systemui/statusbar/CustomTileData.java
new file mode 100644
index 0000000..42974ba
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/CustomTileData.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2015 The CyanogenMod 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 cyanogenmod.app.StatusBarPanelCustomTile;
+
+import android.util.ArrayMap;
+
+/**
+ * Custom tile data to keep track of created 3rd party tiles
+ */
+public class CustomTileData {
+ public static final class Entry {
+ public final String key;
+ public final StatusBarPanelCustomTile sbc;
+
+ public Entry(StatusBarPanelCustomTile sbc) {
+ this.key = sbc.persistableKey();
+ this.sbc = sbc;
+ }
+ }
+
+ private final ArrayMap<String, Entry> mEntries = new ArrayMap<>();
+
+ public ArrayMap<String, Entry> getEntries() {
+ return mEntries;
+ }
+
+ public void add(Entry entry) {
+ mEntries.put(entry.key, entry);
+ }
+
+ public Entry remove(String key) {
+ Entry removed = mEntries.remove(key);
+ if (removed == null) return null;
+ return removed;
+ }
+
+ public Entry get(String key) {
+ return mEntries.get(key);
+ }
+
+ public Entry get(int i) {
+ return mEntries.valueAt(i);
+ }
+
+ public void clear() {
+ mEntries.clear();
+ }
+
+ public int size() {
+ return mEntries.size();
+ }
+} \ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
index 56e9af5..be51e57 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
@@ -64,7 +64,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView {
private boolean mShowingPublic;
private boolean mSensitive;
private boolean mShowingPublicInitialized;
- private boolean mHideSensitiveForIntrinsicHeight;
+ protected boolean mHideSensitiveForIntrinsicHeight;
/**
* Is this notification expanded by the system. The expansion state can be overridden by the
@@ -77,10 +77,10 @@ public class ExpandableNotificationRow extends ActivatableNotificationView {
*/
private boolean mExpansionDisabled;
- private NotificationContentView mPublicLayout;
- private NotificationContentView mPrivateLayout;
- private int mMaxExpandHeight;
- private int mHeadsUpHeight;
+ protected NotificationContentView mPublicLayout;
+ protected NotificationContentView mPrivateLayout;
+ protected int mMaxExpandHeight;
+ protected int mHeadsUpHeight;
private View mVetoButton;
private boolean mClearable;
private ExpansionLogger mLogger;
@@ -101,7 +101,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView {
private ValueAnimator mChildExpandAnimator;
private float mChildrenExpandProgress;
private float mExpandButtonStart;
- private ViewStub mGutsStub;
+ protected ViewStub mGutsStub;
private boolean mHasExpandAction;
private boolean mIsSystemChildExpanded;
private boolean mIsPinned;
@@ -443,10 +443,11 @@ public class ExpandableNotificationRow extends ActivatableNotificationView {
mVetoButton = findViewById(R.id.veto);
}
- public void inflateGuts() {
+ public boolean inflateGuts() {
if (mGuts == null) {
mGutsStub.inflate();
}
+ return false;
}
private void updateChildrenVisibility(boolean animated) {
@@ -531,6 +532,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView {
public void setExpandable(boolean expandable) {
mExpandable = expandable;
+ setClipToOutline(expandable);
}
/**
@@ -644,7 +646,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView {
} else if (mChildrenExpanded) {
maxContentHeight = mChildrenContainer.getIntrinsicHeight();
} else {
- maxContentHeight = getMaxExpandHeight();
+ maxContentHeight = mMaxExpandHeight;
}
return maxContentHeight + getBottomDecorHeight();
}
@@ -668,7 +670,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView {
*
* @return whether the view state is currently expanded.
*/
- private boolean isExpanded() {
+ protected boolean isExpanded() {
return !mExpansionDisabled
&& (!hasUserChangedExpansion() && (isSystemExpanded() || isSystemChildExpanded())
|| isUserExpanded());
@@ -702,7 +704,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView {
return super.isChildInvisible(child) || isInvisibleChildContainer;
}
- private void updateMaxHeights() {
+ protected void updateMaxHeights() {
int intrinsicBefore = getIntrinsicHeight();
View expandedChild = mPrivateLayout.getExpandedChild();
if (expandedChild == null) {
@@ -901,10 +903,6 @@ public class ExpandableNotificationRow extends ActivatableNotificationView {
}
}
- public int getMaxExpandHeight() {
- return mMaxExpandHeight;
- }
-
@Override
public boolean isContentExpandable() {
NotificationContentView showingLayout = getShowingLayout();
@@ -960,7 +958,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView {
return mMaxExpandHeight != 0;
}
- private NotificationContentView getShowingLayout() {
+ protected NotificationContentView getShowingLayout() {
return mShowingPublic ? mPublicLayout : mPrivateLayout;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableOutlineView.java b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableOutlineView.java
index a6fc4bb..cc50e49 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableOutlineView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableOutlineView.java
@@ -34,6 +34,7 @@ public abstract class ExpandableOutlineView extends ExpandableView {
private final Rect mOutlineRect = new Rect();
protected final int mRoundedRectCornerRadius;
private boolean mCustomOutline;
+ private float mRoundCornerRadius = 0;
private float mOutlineAlpha = 1f;
public ExpandableOutlineView(Context context, AttributeSet attrs) {
@@ -44,12 +45,12 @@ public abstract class ExpandableOutlineView extends ExpandableView {
@Override
public void getOutline(View view, Outline outline) {
if (!mCustomOutline) {
- outline.setRect(0,
+ outline.setRoundRect(0,
mClipTopAmount,
getWidth(),
- Math.max(getActualHeight(), mClipTopAmount));
+ Math.max(getActualHeight(), mClipTopAmount), mRoundCornerRadius);
} else {
- outline.setRoundRect(mOutlineRect, mRoundedRectCornerRadius);
+ outline.setRoundRect(mOutlineRect, mRoundCornerRadius);
}
outline.setAlpha(mOutlineAlpha);
}
@@ -96,4 +97,7 @@ public abstract class ExpandableOutlineView extends ExpandableView {
invalidateOutline();
}
+ protected void setRoundCornerRadius(float roundRadius) {
+ mRoundCornerRadius = roundRadius;
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableView.java b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableView.java
index 71baf57..76858ab 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableView.java
@@ -42,7 +42,7 @@ public abstract class ExpandableView extends FrameLayout {
private boolean mDark;
private ArrayList<View> mMatchParentViews = new ArrayList<View>();
private int mClipTopOptimization;
- private static Rect mClipRect = new Rect();
+ private Rect mClipRect = new Rect();
private boolean mWillBeGone;
private int mMinClipTopAmount = 0;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardAffordanceView.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardAffordanceView.java
index 8058933..1f7e687 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardAffordanceView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardAffordanceView.java
@@ -25,9 +25,13 @@ import android.content.Context;
import android.graphics.Canvas;
import android.graphics.CanvasProperty;
import android.graphics.Color;
+import android.graphics.ColorFilter;
import android.graphics.Paint;
import android.graphics.PorterDuff;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
+import android.support.v7.graphics.Palette;
import android.util.AttributeSet;
import android.view.DisplayListCanvas;
import android.view.RenderNodeAnimator;
@@ -45,7 +49,7 @@ import com.android.systemui.statusbar.phone.PhoneStatusBar;
* An ImageView which does not have overlapping renderings commands and therefore does not need a
* layer when alpha is changed.
*/
-public class KeyguardAffordanceView extends ImageView {
+public class KeyguardAffordanceView extends ImageView implements Palette.PaletteAsyncListener {
private static final long CIRCLE_APPEAR_DURATION = 80;
private static final long CIRCLE_DISAPPEAR_MAX_DURATION = 200;
@@ -81,6 +85,7 @@ public class KeyguardAffordanceView extends ImageView {
private boolean mSupportHardware;
private boolean mFinishing;
private boolean mLaunchingAffordance;
+ private ColorFilter mDefaultFilter;
private CanvasProperty<Float> mHwCircleRadius;
private CanvasProperty<Float> mHwCenterX;
@@ -162,21 +167,66 @@ public class KeyguardAffordanceView extends ImageView {
canvas.restore();
}
+
+ @Override
+ public void setImageDrawable(Drawable drawable) {
+ super.setImageDrawable(drawable);
+ doPaletteIfNecessary();
+ }
+
+ private void doPaletteIfNecessary() {
+ if (mDefaultFilter != null && getDrawable() instanceof BitmapDrawable) {
+ Palette.generateAsync(((BitmapDrawable) getDrawable()).getBitmap(), this);
+ }
+ }
+
+
public void setPreviewView(View v) {
View oldPreviewView = mPreviewView;
mPreviewView = v;
if (mPreviewView != null) {
mPreviewView.setVisibility(mLaunchingAffordance
? oldPreviewView.getVisibility() : INVISIBLE);
+ mPreviewView.setVisibility(GONE);
+ addOverlay();
+ }
+ }
+
+ private void addOverlay() {
+ if (mPreviewView != null) {
+ mPreviewView.getOverlay().clear();
+ if (mDefaultFilter != null) {
+ ColorDrawable d = new ColorDrawable(mCircleColor);
+ d.setBounds(0, 0, mPreviewView.getWidth(), mPreviewView.getHeight());
+ mPreviewView.getOverlay().add(d);
+ }
}
}
+ public void setDefaultFilter(ColorFilter filter) {
+ mDefaultFilter = filter;
+ mCircleColor = Color.WHITE;
+ addOverlay();
+ updateIconColor();
+ }
+
private void updateIconColor() {
+ if (getDrawable() == null) {
+ return;
+ }
Drawable drawable = getDrawable().mutate();
float alpha = mCircleRadius / mMinBackgroundRadius;
alpha = Math.min(1.0f, alpha);
int color = (int) mColorInterpolator.evaluate(alpha, mNormalColor, mInverseColor);
- drawable.setColorFilter(color, PorterDuff.Mode.SRC_ATOP);
+ if (mDefaultFilter != null) {
+ if (alpha == 0) {
+ drawable.setColorFilter(mDefaultFilter);
+ } else {
+ drawable.setColorFilter(color, PorterDuff.Mode.DST_IN);
+ }
+ } else {
+ drawable.setColorFilter(color, PorterDuff.Mode.SRC_ATOP);
+ }
}
private void drawBackgroundCircle(Canvas canvas) {
@@ -233,7 +283,7 @@ public class KeyguardAffordanceView extends ImageView {
});
animatorToRadius.start();
setImageAlpha(0, true);
- if (mPreviewView != null) {
+ if (mPreviewView != null && mPreviewView.getVisibility() == View.VISIBLE) {
mPreviewView.setVisibility(View.VISIBLE);
mPreviewClipper = ViewAnimationUtils.createCircularReveal(
mPreviewView, getLeft() + mCenterX, getTop() + mCenterY, mCircleRadius,
@@ -336,7 +386,7 @@ public class KeyguardAffordanceView extends ImageView {
invalidate();
if (nowHidden) {
if (mPreviewView != null) {
- mPreviewView.setVisibility(View.INVISIBLE);
+ mPreviewView.setVisibility(View.GONE);
}
}
} else if (!mCircleWillBeHidden) {
@@ -375,7 +425,7 @@ public class KeyguardAffordanceView extends ImageView {
mPreviewClipper.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
- mPreviewView.setVisibility(View.INVISIBLE);
+ mPreviewView.setVisibility(View.GONE);
}
});
mPreviewClipper.start();
@@ -551,4 +601,10 @@ public class KeyguardAffordanceView extends ImageView {
public void setLaunchingAffordance(boolean launchingAffordance) {
mLaunchingAffordance = launchingAffordance;
}
+
+ @Override
+ public void onGenerated(Palette palette) {
+ mCircleColor = palette.getDarkVibrantColor(Color.WHITE);
+ addOverlay();
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
index fd84345..e4a0196 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
@@ -20,6 +20,7 @@ import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.res.ColorStateList;
import android.content.res.Resources;
import android.graphics.Color;
import android.hardware.fingerprint.FingerprintManager;
@@ -74,16 +75,20 @@ public class KeyguardIndicationController {
private int mChargingSpeed;
private int mChargingCurrent;
private String mMessageToShowOnScreenOn;
+ private IndicationDirection mIndicationDirection;
+ private boolean mScreenOnHintsEnabled;
public KeyguardIndicationController(Context context, KeyguardIndicationTextView textView,
LockIcon lockIcon) {
mContext = context;
mTextView = textView;
mLockIcon = lockIcon;
+ mIndicationDirection = IndicationDirection.NONE;
Resources res = context.getResources();
mSlowThreshold = res.getInteger(R.integer.config_chargingSlowlyThreshold);
mFastThreshold = res.getInteger(R.integer.config_chargingFastThreshold);
+ mScreenOnHintsEnabled = res.getBoolean(R.bool.config_showScreenOnLockScreenHints);
mBatteryInfo = IBatteryStats.Stub.asInterface(
@@ -121,6 +126,20 @@ public class KeyguardIndicationController {
/**
* Shows {@param transientIndication} until it is hidden by {@link #hideTransientIndication}.
*/
+ public void showTransientIndication(int transientIndication, IndicationDirection direction) {
+ showTransientIndication(mContext.getResources().getString(transientIndication), direction);
+ }
+
+ /**
+ * Shows {@param transientIndication} until it is hidden by {@link #hideTransientIndication}.
+ */
+ public void showTransientIndication(String transientIndication, IndicationDirection direction) {
+ showTransientIndication(transientIndication, Color.WHITE, direction);
+ }
+
+ /**
+ * Shows {@param transientIndication} until it is hidden by {@link #hideTransientIndication}.
+ */
public void showTransientIndication(int transientIndication) {
showTransientIndication(mContext.getResources().getString(transientIndication));
}
@@ -129,15 +148,24 @@ public class KeyguardIndicationController {
* Shows {@param transientIndication} until it is hidden by {@link #hideTransientIndication}.
*/
public void showTransientIndication(String transientIndication) {
- showTransientIndication(transientIndication, Color.WHITE);
+ showTransientIndication(transientIndication, Color.WHITE, IndicationDirection.NONE);
}
/**
* Shows {@param transientIndication} until it is hidden by {@link #hideTransientIndication}.
*/
public void showTransientIndication(String transientIndication, int textColor) {
+ showTransientIndication(transientIndication, textColor, IndicationDirection.NONE);
+ }
+
+ /**
+ * Shows {@param transientIndication} until it is hidden by {@link #hideTransientIndication}.
+ */
+ public void showTransientIndication(String transientIndication, int textColor,
+ IndicationDirection direction) {
mTransientIndication = transientIndication;
mTransientTextColor = textColor;
+ mIndicationDirection = direction;
mHandler.removeMessages(MSG_HIDE_TRANSIENT);
updateIndication();
}
@@ -153,10 +181,38 @@ public class KeyguardIndicationController {
}
}
+ public void cleanup() {
+ KeyguardUpdateMonitor.getInstance(mContext).removeCallback(mUpdateMonitor);
+ mContext.unregisterReceiver(mReceiver);
+ }
+
private void updateIndication() {
if (mVisible) {
+ final int color = computeColor();
mTextView.switchIndication(computeIndication());
- mTextView.setTextColor(computeColor());
+ mTextView.setTextColor(color);
+ int top = 0, left = 0, right = 0;
+ // pad the bottom using ic_empty_space to keep text vertically aligned if needed
+ int bottom = mScreenOnHintsEnabled ? R.drawable.ic_empty_space : 0;
+ switch (mIndicationDirection) {
+ case UP:
+ top = R.drawable.ic_keyboard_arrow_up;
+ break;
+ case DOWN:
+ bottom = R.drawable.ic_keyboard_arrow_down;
+ break;
+ case LEFT:
+ left = R.drawable.ic_keyboard_arrow_left;
+ break;
+ case RIGHT:
+ right = R.drawable.ic_keyboard_arrow_right;
+ break;
+ case NONE:
+ default:
+ break;
+ }
+ mTextView.setCompoundDrawablesWithIntrinsicBounds(left, top, right, bottom);
+ mTextView.setCompoundDrawableTintList(ColorStateList.valueOf(color));
}
}
@@ -171,6 +227,7 @@ public class KeyguardIndicationController {
if (!TextUtils.isEmpty(mTransientIndication)) {
return mTransientIndication;
}
+ mIndicationDirection = IndicationDirection.NONE;
if (mPowerPluggedIn) {
String indication = computePowerIndication();
if (DEBUG_CHARGING_CURRENT) {
@@ -321,4 +378,12 @@ public class KeyguardIndicationController {
StatusBarKeyguardViewManager statusBarKeyguardViewManager) {
mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
}
+
+ public enum IndicationDirection {
+ NONE,
+ UP,
+ DOWN,
+ LEFT,
+ RIGHT
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/MediaExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/MediaExpandableNotificationRow.java
new file mode 100644
index 0000000..c25f146
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/MediaExpandableNotificationRow.java
@@ -0,0 +1,231 @@
+/*
+ * Copyright (C) 2015 The CyanogenMod 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.content.res.Resources;
+import android.media.session.MediaController;
+import android.os.Build;
+import android.os.Handler;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewGroup;
+
+import com.android.systemui.R;
+import com.android.systemui.cm.UserContentObserver;
+
+import cyanogenmod.providers.CMSettings;
+
+public class MediaExpandableNotificationRow extends ExpandableNotificationRow {
+
+ private static final String TAG = MediaExpandableNotificationRow.class.getSimpleName();
+ public static final boolean DEBUG = false;
+
+ public static final int MAX_QUEUE_ENTRIES = 3;
+
+ private QueueView mQueue;
+
+ private int mMaxQueueHeight;
+ private int mRowHeight;
+ private int mShadowHeight;
+ private int mDisplayedRows;
+
+ private SettingsObserver mSettingsObserver;
+ private boolean mQueueEnabled;
+
+ public MediaExpandableNotificationRow(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ mSettingsObserver = new SettingsObserver(new Handler());
+ mQueueEnabled = isQueueEnabled(context);
+
+ Resources res = mContext.getResources();
+
+ // 3 * queue_row_height + shadow height
+ mRowHeight = res.getDimensionPixelSize(R.dimen.queue_row_height);
+ mShadowHeight = res.getDimensionPixelSize(R.dimen.queue_top_shadow);
+ }
+
+ public boolean inflateGuts() {
+ if (getGuts() == null) {
+ mGutsStub.inflate();
+ }
+ if (!mQueueEnabled) {
+ return true;
+ }
+ return !mQueue.isUserSelectingRow();
+ }
+
+ @Override
+ protected void updateMaxHeights() {
+ // update queue height based on number of rows
+ int rows = mQueue != null ? mQueue.getCurrentQueueRowCount() : 0;
+ if (rows != mDisplayedRows) {
+ mMaxQueueHeight = rows * mRowHeight;
+ if (mMaxQueueHeight > 0) {
+ mMaxQueueHeight += mShadowHeight;
+ }
+ mDisplayedRows = rows;
+ }
+
+ int intrinsicBefore = getIntrinsicHeight();
+ View expandedChild = mPrivateLayout.getExpandedChild();
+ if (expandedChild == null) {
+ expandedChild = mPrivateLayout.getContractedChild();
+ }
+ mMaxExpandHeight = expandedChild.getHeight() + mMaxQueueHeight;
+
+ View headsUpChild = mPrivateLayout.getHeadsUpChild();
+ if (headsUpChild == null) {
+ headsUpChild = mPrivateLayout.getContractedChild();
+ }
+ mHeadsUpHeight = headsUpChild.getHeight();
+ if (intrinsicBefore != getIntrinsicHeight()) {
+ notifyHeightChanged(false /* needsAnimation */);
+ }
+ invalidateOutline();
+ }
+
+ @Override
+ public int getIntrinsicHeight() {
+ if (getGuts() != null && getGuts().isShown()) {
+ return getGuts().getActualHeight();
+ }
+ if (!mQueueEnabled) {
+ return super.getIntrinsicHeight();
+ }
+ if (mHideSensitiveForIntrinsicHeight) {
+ return getMinHeight();
+ }
+ if (isUserLocked()) {
+ return getActualHeight();
+ }
+ boolean inExpansionState = isExpanded();
+ if (!inExpansionState) {
+ // not expanded, so we return the collapsed size
+ return getMinHeight();
+ }
+ return getMaxContentHeight();
+ }
+
+ @Override
+ public int getMaxContentHeight() {
+ /**
+ * calls into NotificationContentView.getMaxHeight()
+ */
+ return getShowingLayout().getMaxHeight() + mMaxQueueHeight;
+ }
+
+ @Override
+ public void setHeightRange(int rowMinHeight, int rowMaxHeight) {
+ super.setHeightRange(rowMinHeight, rowMaxHeight);
+ mMaxViewHeight = Math.max(rowMaxHeight, getMaxContentHeight());
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+ mQueue = (QueueView) findViewById(R.id.queue_view);
+ mQueue.setQueueEnabled(mQueueEnabled);
+ mQueue.setVisibility(mQueueEnabled ? View.VISIBLE : View.GONE);
+ }
+
+ public void setMediaController(MediaController mediaController) {
+ if (DEBUG) Log.d(TAG, "setMediaController() called with "
+ + "mediaController = [" + mediaController + "]");
+ if (mQueue.setController(mediaController) && mQueueEnabled) {
+ notifyHeightChanged(true);
+ }
+ }
+
+ @Override
+ public boolean dispatchGenericMotionEvent(MotionEvent ev) {
+ if (filterMotionEvent(ev)) {
+ return super.dispatchGenericMotionEvent(ev);
+ }
+ return false;
+ }
+
+ @Override
+ public boolean dispatchTouchEvent(MotionEvent ev) {
+ if (filterMotionEvent(ev)) {
+ return super.dispatchTouchEvent(ev);
+ }
+ return false;
+ }
+
+ @Override
+ protected boolean filterMotionEvent(MotionEvent event) {
+ if (!mQueueEnabled) {
+ return super.filterMotionEvent(event);
+ }
+ if (isExpanded() && mQueue.isUserSelectingRow()
+ && event.getActionMasked() != MotionEvent.ACTION_DOWN
+ && event.getActionMasked() != MotionEvent.ACTION_UP
+ && event.getActionMasked() != MotionEvent.ACTION_CANCEL) {
+ // this is for hotspot propogation?
+ return false;
+ }
+ return super.filterMotionEvent(event);
+ }
+
+ @Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+ mSettingsObserver.observe();
+ }
+
+ @Override
+ protected void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+ mSettingsObserver.unobserve();
+ }
+
+ private class SettingsObserver extends UserContentObserver {
+
+ public SettingsObserver(Handler handler) {
+ super(handler);
+ }
+
+ @Override
+ protected void observe() {
+ super.observe();
+ mContext.getContentResolver().registerContentObserver(
+ CMSettings.System.getUriFor(CMSettings.System.NOTIFICATION_PLAY_QUEUE),
+ true, this);
+ }
+
+ @Override
+ protected void unobserve() {
+ super.unobserve();
+ mContext.getContentResolver().unregisterContentObserver(this);
+ }
+
+ @Override
+ protected void update() {
+ mQueueEnabled = isQueueEnabled(mContext);
+ mQueue.setQueueEnabled(mQueueEnabled);
+ mQueue.setVisibility(mQueueEnabled ? View.VISIBLE : View.GONE);
+ requestLayout();
+ }
+ }
+
+ public static boolean isQueueEnabled(Context context) {
+ return CMSettings.System.getInt(context.getContentResolver(),
+ CMSettings.System.NOTIFICATION_PLAY_QUEUE, 1) == 1;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/MediaNotificationGuts.java b/packages/SystemUI/src/com/android/systemui/statusbar/MediaNotificationGuts.java
new file mode 100644
index 0000000..14ce8e1
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/MediaNotificationGuts.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2015 The CyanogenMod 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.graphics.Canvas;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.CompoundButton;
+import android.widget.Switch;
+import android.widget.TextView;
+import com.android.systemui.R;
+import cyanogenmod.providers.CMSettings;
+
+/**
+ * The guts of a media notification revealed when performing a long press.
+ */
+public class MediaNotificationGuts extends NotificationGuts {
+
+ private static final String TAG = MediaNotificationGuts.class.getSimpleName();
+
+ private ViewGroup mQueueGroup;
+ private TextView mText;
+ private Switch mSwitch;
+
+ public MediaNotificationGuts(Context context, AttributeSet attrs) {
+ super(context, attrs);
+
+ setWillNotDraw(true);
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ // do nothing!
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+ mQueueGroup = (ViewGroup) findViewById(R.id.queue_group);
+ mSwitch = (Switch) findViewById(R.id.queue_switch);
+ mSwitch.setChecked(MediaExpandableNotificationRow.isQueueEnabled(getContext()));
+ mText = (TextView) findViewById(R.id.switch_label);
+ mText.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ mSwitch.toggle();
+ }
+ });
+ mSwitch.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
+ @Override
+ public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
+ buttonView.setChecked(isChecked);
+ CMSettings.System.putInt(getContext().getContentResolver(),
+ CMSettings.System.NOTIFICATION_PLAY_QUEUE,
+ isChecked ? 1 : 0);
+ }
+ });
+ }
+
+
+ @Override
+ public void setActualHeight(int actualHeight) {
+ super.setActualHeight(actualHeight);
+ }
+
+ @Override
+ public int getActualHeight() {
+ return getHeight();
+ }
+
+ @Override
+ public boolean hasOverlappingRendering() {
+
+ // Prevents this view from creating a layer when alpha is animating.
+ return false;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java
index aedae52..c8c318b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java
@@ -191,6 +191,14 @@ public class NotificationData {
return mEntries.get(key);
}
+ public Entry get(int i) {
+ return mEntries.valueAt(i);
+ }
+
+ public RankingMap getRankingMap() {
+ return mRankingMap;
+ }
+
public void add(Entry entry, RankingMap ranking) {
mEntries.put(entry.notification.getKey(), entry);
updateRankingAndSort(ranking);
@@ -288,6 +296,14 @@ public class NotificationData {
return false;
}
+ public void clear() {
+ mEntries.clear();
+ }
+
+ public int size() {
+ return mEntries.size();
+ }
+
// Q: What kinds of notifications should show during setup?
// A: Almost none! Only things coming from the system (package is "android") that also
// have special "kind" tags marking them as relevant for setup (see below).
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGuts.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGuts.java
index 46e0bf8..23912c4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGuts.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGuts.java
@@ -28,9 +28,9 @@ import com.android.systemui.R;
*/
public class NotificationGuts extends FrameLayout {
- private Drawable mBackground;
- private int mClipTopAmount;
- private int mActualHeight;
+ protected Drawable mBackground;
+ protected int mClipTopAmount;
+ protected int mActualHeight;
public NotificationGuts(Context context, AttributeSet attrs) {
super(context, attrs);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/QueueView.java b/packages/SystemUI/src/com/android/systemui/statusbar/QueueView.java
new file mode 100644
index 0000000..1da2e5e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/QueueView.java
@@ -0,0 +1,254 @@
+/*
+ * Copyright (C) 2015 The CyanogenMod 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.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.media.MediaDescription;
+import android.media.MediaMetadata;
+import android.media.session.MediaController;
+import android.media.session.MediaSession;
+import android.media.session.PlaybackState;
+import android.os.Handler;
+import android.provider.Settings;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AdapterView;
+import android.widget.ArrayAdapter;
+import android.widget.LinearLayout;
+import android.widget.ListView;
+import com.android.systemui.R;
+import com.android.systemui.cm.UserContentObserver;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class QueueView extends LinearLayout implements
+ QueueViewRow.UserRowInteractionListener, AdapterView.OnItemClickListener,
+ AdapterView.OnItemLongClickListener {
+
+ private static final String TAG = QueueView.class.getSimpleName();
+ private static final boolean DEBUG = MediaExpandableNotificationRow.DEBUG;
+
+ private MediaController mController;
+
+ private List<MediaSession.QueueItem> mQueue = new ArrayList<>(getMaxQueueRowCount());
+
+ private QueueItemAdapter mAdapter;
+ private ListView mList;
+ private boolean mQueueEnabled;
+
+ long mLastUserInteraction = -1;
+
+ private MediaController.Callback mCallback = new MediaController.Callback() {
+ @Override
+ public void onPlaybackStateChanged(@NonNull PlaybackState state) {
+ super.onPlaybackStateChanged(state);
+
+ if (getParent() != null && updateQueue(mController.getQueue())) {
+ getParent().requestLayout();
+ }
+ }
+
+ @Override
+ public void onSessionDestroyed() {
+ if (DEBUG) Log.d(TAG, "onSessionDestroyed() called with " + "");
+ super.onSessionDestroyed();
+ setController(null);
+ }
+ };
+
+ public QueueView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ mAdapter = new QueueItemAdapter(context);
+ setClipToOutline(false);
+ setClipToPadding(false);
+ }
+
+ public void setQueueEnabled(boolean enabled) {
+ mQueueEnabled = enabled;
+ mAdapter.notifyDataSetChanged();
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+ mList = (ListView) findViewById(R.id.queue_list);
+ mList.setItemsCanFocus(true);
+ mList.setDrawSelectorOnTop(true);
+ mList.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
+ mList.setAdapter(mAdapter);
+ mList.setOnItemLongClickListener(this);
+ mList.setOnItemClickListener(this);
+ mList.setVerticalScrollBarEnabled(false);
+ }
+
+ private class QueueItemAdapter extends ArrayAdapter<MediaSession.QueueItem> {
+
+ public QueueItemAdapter(Context context) {
+ super(context, R.layout.queue_adapter_row, mQueue);
+ }
+
+ @Override
+ public boolean hasStableIds() {
+ return true;
+ }
+
+ @Override
+ public long getItemId(int position) {
+ if (position > getCount() - 1) {
+ return -1;
+ }
+ return getItem(position).getQueueId();
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ final MediaSession.QueueItem queueItem = getItem(position);
+
+ if (convertView == null) {
+ convertView = LayoutInflater.from(parent.getContext())
+ .inflate(R.layout.queue_adapter_row, parent, false);
+ }
+
+ QueueViewRow row = (QueueViewRow) convertView;
+ row.setHotSpotChangeListener(QueueView.this);
+
+ row.setQueueItem(queueItem);
+
+ return convertView;
+ }
+
+ @Override
+ public int getCount() {
+ if (!mQueueEnabled) {
+ return 0;
+ }
+ return super.getCount();
+ }
+ }
+
+ public boolean isUserSelectingRow() {
+ final long delta = System.currentTimeMillis() - mLastUserInteraction;
+ if (DEBUG) Log.i(TAG, "isUserSelectingRow() delta=" + delta);
+
+ if (mLastUserInteraction > 0 && delta < 500) {
+ if (DEBUG) Log.w(TAG, "user selecting row bc of hotspot change.");
+ return true;
+ }
+
+ return false;
+ }
+
+ public int getMaxQueueRowCount() {
+ return MediaExpandableNotificationRow.MAX_QUEUE_ENTRIES;
+ }
+
+ public int getCurrentQueueRowCount() {
+ if (mAdapter == null) {
+ return 0;
+ }
+ return mAdapter.getCount();
+ }
+
+ @Override
+ public void onHotSpotChanged(float x, float y) {
+ mLastUserInteraction = System.currentTimeMillis();
+ }
+
+ @Override
+ public void onDrawableStateChanged() {
+ mLastUserInteraction = System.currentTimeMillis();
+ }
+
+ /**
+ * @param queue
+ * @return whether the queue size has changed
+ */
+ public boolean updateQueue(List<MediaSession.QueueItem> queue) {
+ int queueSizeBefore = mAdapter.getCount();
+
+ mQueue.clear();
+
+ if (queue != null) {
+ // add everything *after* the currently playing item
+ boolean foundNowPlaying = false;
+
+ final PlaybackState playbackState = mController.getPlaybackState();
+
+ long activeQueueId = -1;
+ if (playbackState != null) {
+ activeQueueId = playbackState.getActiveQueueItemId();
+ }
+
+ for (int i = 0; i < queue.size() && mQueue.size() < getMaxQueueRowCount(); i++) {
+ final MediaSession.QueueItem item = queue.get(i);
+ if (!foundNowPlaying
+ && activeQueueId != -1
+ && activeQueueId == item.getQueueId()) {
+ foundNowPlaying = true;
+ continue;
+ }
+ if (foundNowPlaying) {
+ mQueue.add(item);
+ }
+ }
+
+ // add everything
+ if (!foundNowPlaying) {
+ for(int i = 0; i < getMaxQueueRowCount() && i < queue.size(); i++) {
+ mQueue.add(queue.get(i));
+ }
+ }
+ }
+ mAdapter.notifyDataSetChanged();
+
+ return mAdapter.getCount() != queueSizeBefore;
+ }
+
+ public boolean setController(MediaController controller) {
+ if (mController != null) {
+ mController.unregisterCallback(mCallback);
+ }
+ mController = controller;
+ if (mController != null) {
+ mController.registerCallback(mCallback);
+ }
+
+ return updateQueue(mController != null
+ ? mController.getQueue() : null);
+ }
+
+ @Override
+ public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
+ final MediaSession.QueueItem itemAtPosition = (MediaSession.QueueItem)
+ parent.getItemAtPosition(position);
+ if (itemAtPosition != null && mController != null) {
+ mController.getTransportControls().skipToQueueItem(itemAtPosition.getQueueId());
+ }
+ mAdapter.notifyDataSetChanged();
+ }
+
+ @Override
+ public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) {
+ return true;
+ }
+
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/QueueViewRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/QueueViewRow.java
new file mode 100644
index 0000000..dab89ff
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/QueueViewRow.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2015 The CyanogenMod 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.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.media.MediaDescription;
+import android.media.session.MediaSession;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.View;
+import android.widget.ImageView;
+import android.widget.RelativeLayout;
+import android.widget.TextView;
+import com.android.systemui.R;
+
+public class QueueViewRow extends RelativeLayout {
+
+ private static final String TAG = QueueViewRow.class.getSimpleName();
+
+ private UserRowInteractionListener mHotSpotChangeListener;
+
+ private ImageView mArt;
+ private TextView mTitle;
+ private TextView mSummary;
+
+ public QueueViewRow(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+ mArt = (ImageView) findViewById(R.id.art);
+ mTitle = (TextView) findViewById(R.id.title);
+ mSummary = (TextView) findViewById(R.id.summary);
+ }
+
+ @Override
+ protected void drawableStateChanged() {
+ super.drawableStateChanged();
+ if (mHotSpotChangeListener != null) {
+ mHotSpotChangeListener.onDrawableStateChanged();
+ }
+ }
+
+ @Override
+ public void dispatchDrawableHotspotChanged(float x, float y) {
+ super.dispatchDrawableHotspotChanged(x, y);
+ if (mHotSpotChangeListener != null) {
+ mHotSpotChangeListener.onHotSpotChanged(x, y);
+ }
+ }
+
+ public void setHotSpotChangeListener(UserRowInteractionListener listener) {
+ mHotSpotChangeListener = listener;
+ }
+
+ public TextView getTitle() {
+ return mTitle;
+ }
+
+ public TextView getSummary() {
+ return mSummary;
+ }
+
+ public void setQueueItem(MediaSession.QueueItem queueItem) {
+ setTag(queueItem);
+
+ MediaDescription metadata = queueItem.getDescription();
+
+ final Bitmap bitmap = metadata.getIconBitmap();
+ mArt.setImageBitmap(bitmap);
+ mArt.setVisibility(bitmap != null ? View.VISIBLE : View.GONE);
+
+ mTitle.setText(metadata.getTitle());
+ mSummary.setText(metadata.getSubtitle());
+ }
+
+ /* package */ interface UserRowInteractionListener {
+ public void onHotSpotChanged(float x, float y);
+ public void onDrawableStateChanged();
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/SignalClusterView.java b/packages/SystemUI/src/com/android/systemui/statusbar/SignalClusterView.java
index cc30882..ecaa809 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/SignalClusterView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/SignalClusterView.java
@@ -88,8 +88,6 @@ public class SignalClusterView
private int mWideTypeIconStartPadding;
private int mSecondaryTelephonyPadding;
- private int mEndPadding;
- private int mEndPaddingNothingVisible;
private boolean mBlockAirplane;
private boolean mBlockMobile;
@@ -138,9 +136,14 @@ public class SignalClusterView
public void setSecurityController(SecurityController sc) {
if (DEBUG) Log.d(TAG, "SecurityController=" + sc);
+ if (sc == null && mSC != null) {
+ mSC.removeCallback(this);
+ }
mSC = sc;
- mSC.addCallback(this);
- mVpnVisible = mSC.isVpnEnabled();
+ if (mSC != null) {
+ mSC.addCallback(this);
+ mVpnVisible = mSC.isVpnEnabled();
+ }
}
@Override
@@ -150,10 +153,6 @@ public class SignalClusterView
R.dimen.wide_type_icon_start_padding);
mSecondaryTelephonyPadding = getContext().getResources().getDimensionPixelSize(
R.dimen.secondary_telephony_padding);
- mEndPadding = getContext().getResources().getDimensionPixelSize(
- R.dimen.signal_cluster_battery_padding);
- mEndPaddingNothingVisible = getContext().getResources().getDimensionPixelSize(
- R.dimen.no_signal_cluster_battery_padding);
}
@Override
@@ -204,8 +203,10 @@ public class SignalClusterView
post(new Runnable() {
@Override
public void run() {
- mVpnVisible = mSC.isVpnEnabled();
- apply();
+ if (mSC != null) {
+ mVpnVisible = mSC.isVpnEnabled();
+ apply();
+ }
}
});
}
@@ -223,7 +224,7 @@ public class SignalClusterView
@Override
public void setMobileDataIndicators(IconState statusIcon, IconState qsIcon, int statusType,
int qsType, boolean activityIn, boolean activityOut, String typeContentDescription,
- String description, boolean isWide, int subId) {
+ String description, boolean isWide, boolean showRoamingIndicator, int subId) {
PhoneState state = getState(subId);
if (state == null) {
return;
@@ -234,6 +235,7 @@ public class SignalClusterView
state.mMobileDescription = statusIcon.contentDescription;
state.mMobileTypeDescription = typeContentDescription;
state.mIsMobileTypeIconWide = statusType != 0 && isWide;
+ state.mShowRoamingIndicator = showRoamingIndicator;
apply();
}
@@ -351,10 +353,13 @@ public class SignalClusterView
for (PhoneState state : mPhoneStates) {
if (state.mMobile != null) {
+ state.maybeStopAnimatableDrawable(state.mMobile);
state.mMobile.setImageDrawable(null);
+ state.mLastMobileStrengthId = -1;
}
if (state.mMobileType != null) {
state.mMobileType.setImageDrawable(null);
+ state.mLastMobileTypeId = -1;
}
}
@@ -447,10 +452,6 @@ public class SignalClusterView
}
mNoSimsCombo.setVisibility(mNoSimsVisible ? View.VISIBLE : View.GONE);
-
- boolean anythingVisible = mNoSimsVisible || mWifiVisible || mIsAirplaneMode
- || anyMobileVisible || mVpnVisible || mEthernetVisible;
- setPaddingRelative(0, 0, anythingVisible ? mEndPadding : mEndPaddingNothingVisible, 0);
}
public void setIconTint(int tint, float darkIntensity) {
@@ -486,11 +487,15 @@ public class SignalClusterView
private final int mSubId;
private boolean mMobileVisible = false;
private int mMobileStrengthId = 0, mMobileTypeId = 0;
+ private int mLastMobileStrengthId = -1;
+ private int mLastMobileTypeId = -1;
private boolean mIsMobileTypeIconWide;
private String mMobileDescription, mMobileTypeDescription;
+ private boolean mShowRoamingIndicator;
private ViewGroup mMobileGroup;
private ImageView mMobile, mMobileDark, mMobileType;
+ private ImageView mMobileRoaming;
public PhoneState(int subId, Context context) {
ViewGroup root = (ViewGroup) LayoutInflater.from(context)
@@ -504,32 +509,25 @@ public class SignalClusterView
mMobile = (ImageView) root.findViewById(R.id.mobile_signal);
mMobileDark = (ImageView) root.findViewById(R.id.mobile_signal_dark);
mMobileType = (ImageView) root.findViewById(R.id.mobile_type);
+ mMobileRoaming = (ImageView) root.findViewById(R.id.mobile_roaming);
}
public boolean apply(boolean isSecondaryIcon) {
if (mMobileVisible && !mIsAirplaneMode) {
- mMobile.setImageResource(mMobileStrengthId);
- Drawable mobileDrawable = mMobile.getDrawable();
- if (mobileDrawable instanceof Animatable) {
- Animatable ad = (Animatable) mobileDrawable;
- if (!ad.isRunning()) {
- ad.start();
- }
+ if (mLastMobileStrengthId != mMobileStrengthId) {
+ updateAnimatableIcon(mMobile, mMobileStrengthId);
+ updateAnimatableIcon(mMobileDark, mMobileStrengthId);
+ mLastMobileStrengthId = mMobileStrengthId;
}
- mMobileDark.setImageResource(mMobileStrengthId);
- Drawable mobileDarkDrawable = mMobileDark.getDrawable();
- if (mobileDarkDrawable instanceof Animatable) {
- Animatable ad = (Animatable) mobileDarkDrawable;
- if (!ad.isRunning()) {
- ad.start();
- }
+ if (mLastMobileTypeId != mMobileTypeId) {
+ mMobileType.setImageResource(mMobileTypeId);
+ mLastMobileTypeId = mMobileTypeId;
}
-
- mMobileType.setImageResource(mMobileTypeId);
mMobileGroup.setContentDescription(mMobileTypeDescription
+ " " + mMobileDescription);
mMobileGroup.setVisibility(View.VISIBLE);
+ mMobileRoaming.setVisibility(mShowRoamingIndicator ? View.VISIBLE : View.GONE);
} else {
mMobileGroup.setVisibility(View.GONE);
}
@@ -550,6 +548,32 @@ public class SignalClusterView
return mMobileVisible;
}
+ private void updateAnimatableIcon(ImageView view, int resId) {
+ maybeStopAnimatableDrawable(view);
+ view.setImageResource(resId);
+ maybeStartAnimatableDrawable(view);
+ }
+
+ private void maybeStopAnimatableDrawable(ImageView view) {
+ Drawable drawable = view.getDrawable();
+ if (drawable instanceof Animatable) {
+ Animatable ad = (Animatable) drawable;
+ if (ad.isRunning()) {
+ ad.stop();
+ }
+ }
+ }
+
+ private void maybeStartAnimatableDrawable(ImageView view) {
+ Drawable drawable = view.getDrawable();
+ if (drawable instanceof Animatable) {
+ Animatable ad = (Animatable) drawable;
+ if (!ad.isRunning()) {
+ ad.start();
+ }
+ }
+ }
+
public void populateAccessibilityEvent(AccessibilityEvent event) {
if (mMobileVisible && mMobileGroup != null
&& mMobileGroup.getContentDescription() != null) {
@@ -560,6 +584,7 @@ public class SignalClusterView
public void setIconTint(int tint, float darkIntensity) {
applyDarkIntensity(darkIntensity, mMobile, mMobileDark);
setTint(mMobileType, tint);
+ setTint(mMobileRoaming, tint);
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StackScrollerDecorView.java b/packages/SystemUI/src/com/android/systemui/statusbar/StackScrollerDecorView.java
index 2f66c41..9103525 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StackScrollerDecorView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StackScrollerDecorView.java
@@ -87,6 +87,7 @@ public abstract class StackScrollerDecorView extends ExpandableView {
mAnimating = true;
mContent.animate()
.alpha(endValue)
+ .withLayer()
.setInterpolator(interpolator)
.setDuration(260)
.withEndAction(new Runnable() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
index baac8ac..4371cce 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
@@ -19,12 +19,16 @@ package com.android.systemui.statusbar;
import android.app.Notification;
import android.content.Context;
import android.content.res.Resources;
+import android.content.res.ThemeConfig;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.Icon;
+import android.graphics.Typeface;
+import android.os.Handler;
import android.os.UserHandle;
+import android.provider.Settings;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.Log;
@@ -34,8 +38,12 @@ import android.widget.ImageView;
import com.android.internal.statusbar.StatusBarIcon;
import com.android.systemui.R;
+import com.android.systemui.cm.UserContentObserver;
import java.text.NumberFormat;
+import java.util.ArrayList;
+
+import cyanogenmod.providers.CMSettings;
public class StatusBarIconView extends AnimatedImageView {
private static final String TAG = "StatusBarIconView";
@@ -43,12 +51,14 @@ public class StatusBarIconView extends AnimatedImageView {
private StatusBarIcon mIcon;
@ViewDebug.ExportedProperty private String mSlot;
private Drawable mNumberBackground;
- private Paint mNumberPain;
+ private Paint mNumberPaint;
private int mNumberX;
private int mNumberY;
private String mNumberText;
private Notification mNotification;
private final boolean mBlocked;
+ private boolean mShowNotificationCount;
+ private GlobalSettingsObserver mObserver;
public StatusBarIconView(Context context, String slot, Notification notification) {
this(context, slot, notification, false);
@@ -60,12 +70,10 @@ public class StatusBarIconView extends AnimatedImageView {
final Resources res = context.getResources();
mBlocked = blocked;
mSlot = slot;
- mNumberPain = new Paint();
- mNumberPain.setTextAlign(Paint.Align.CENTER);
- mNumberPain.setColor(context.getColor(R.drawable.notification_number_text_color));
- mNumberPain.setAntiAlias(true);
setNotification(notification);
+ mObserver = GlobalSettingsObserver.getInstance(context);
+
// We do not resize and scale system icons (on the right), only notification icons (on the
// left).
if (notification != null) {
@@ -81,6 +89,8 @@ public class StatusBarIconView extends AnimatedImageView {
public void setNotification(Notification notification) {
mNotification = notification;
+ mShowNotificationCount = CMSettings.System.getIntForUser(mContext.getContentResolver(),
+ CMSettings.System.STATUS_BAR_NOTIF_COUNT, 0, UserHandle.USER_CURRENT) == 1;
setContentDescription(notification);
}
@@ -124,6 +134,10 @@ public class StatusBarIconView extends AnimatedImageView {
* Returns whether the set succeeded.
*/
public boolean set(StatusBarIcon icon) {
+ return set(icon, false);
+ }
+
+ private boolean set(StatusBarIcon icon, boolean force) {
final boolean iconEquals = mIcon != null && equalIcons(mIcon.icon, icon.icon);
final boolean levelEquals = iconEquals
&& mIcon.iconLevel == icon.iconLevel;
@@ -133,17 +147,25 @@ public class StatusBarIconView extends AnimatedImageView {
&& mIcon.number == icon.number;
mIcon = icon.clone();
setContentDescription(icon.contentDescription);
- if (!iconEquals) {
+ if (!iconEquals || force) {
if (!updateDrawable(false /* no clear */)) return false;
}
- if (!levelEquals) {
+ if (!levelEquals || force) {
setImageLevel(icon.iconLevel);
}
- if (!numberEquals) {
- if (icon.number > 0 && getContext().getResources().getBoolean(
- R.bool.config_statusBarShowNumber)) {
+ if (!numberEquals || force) {
+ if (icon.number > 1 && mShowNotificationCount) {
if (mNumberBackground == null) {
+ final Resources res = mContext.getResources();
+ final float densityMultiplier = res.getDisplayMetrics().density;
+ final float scaledPx = 8 * densityMultiplier;
+ mNumberPaint = new Paint();
+ mNumberPaint.setTextAlign(Paint.Align.CENTER);
+ mNumberPaint.setColor(res.getColor(R.drawable.notification_number_text_color));
+ mNumberPaint.setAntiAlias(true);
+ mNumberPaint.setTypeface(Typeface.DEFAULT_BOLD);
+ mNumberPaint.setTextSize(scaledPx);
mNumberBackground = getContext().getResources().getDrawable(
R.drawable.ic_notification_overlay);
}
@@ -151,10 +173,11 @@ public class StatusBarIconView extends AnimatedImageView {
} else {
mNumberBackground = null;
mNumberText = null;
+ mNumberPaint = null;
}
invalidate();
}
- if (!visibilityEquals) {
+ if (!visibilityEquals || force) {
setVisibility(icon.visible && !mBlocked ? VISIBLE : GONE);
}
return true;
@@ -211,6 +234,10 @@ public class StatusBarIconView extends AnimatedImageView {
}
}
+ public String getStatusBarSlot() {
+ return mSlot;
+ }
+
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
@@ -231,7 +258,25 @@ public class StatusBarIconView extends AnimatedImageView {
if (mNumberBackground != null) {
mNumberBackground.draw(canvas);
- canvas.drawText(mNumberText, mNumberX, mNumberY, mNumberPain);
+ canvas.drawText(mNumberText, mNumberX, mNumberY, mNumberPaint);
+ }
+ }
+
+ @Override
+ public void onAttachedToWindow() {
+ super.onAttachedToWindow();
+
+ if (mObserver != null) {
+ mObserver.attach(this);
+ }
+ }
+
+ @Override
+ public void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+
+ if (mObserver != null) {
+ mObserver.detach(this);
}
}
@@ -248,7 +293,7 @@ public class StatusBarIconView extends AnimatedImageView {
android.R.integer.status_bar_notification_info_maxnum);
if (mIcon.number > tooBig) {
str = getContext().getResources().getString(
- android.R.string.status_bar_notification_info_overflow);
+ R.string.status_bar_notification_info_overflow);
} else {
NumberFormat f = NumberFormat.getIntegerInstance();
str = f.format(mIcon.number);
@@ -258,7 +303,7 @@ public class StatusBarIconView extends AnimatedImageView {
final int w = getWidth();
final int h = getHeight();
final Rect r = new Rect();
- mNumberPain.getTextBounds(str, 0, str.length(), r);
+ mNumberPaint.getTextBounds(str, 0, str.length(), r);
final int tw = r.right - r.left;
final int th = r.bottom - r.top;
mNumberBackground.getPadding(r);
@@ -292,4 +337,63 @@ public class StatusBarIconView extends AnimatedImageView {
public String getSlot() {
return mSlot;
}
+
+ static class GlobalSettingsObserver extends UserContentObserver {
+ private static GlobalSettingsObserver sInstance;
+ private ArrayList<StatusBarIconView> mIconViews = new ArrayList<StatusBarIconView>();
+ private Context mContext;
+
+ GlobalSettingsObserver(Handler handler, Context context) {
+ super(handler);
+ mContext = context.getApplicationContext();
+ }
+
+ static GlobalSettingsObserver getInstance(Context context) {
+ if (sInstance == null) {
+ sInstance = new GlobalSettingsObserver(new Handler(), context);
+ }
+ return sInstance;
+ }
+
+ void attach(StatusBarIconView sbiv) {
+ if (mIconViews.isEmpty()) {
+ observe();
+ }
+ mIconViews.add(sbiv);
+ }
+
+ void detach(StatusBarIconView sbiv) {
+ mIconViews.remove(sbiv);
+ if (mIconViews.isEmpty()) {
+ unobserve();
+ }
+ }
+
+ @Override
+ protected void observe() {
+ super.observe();
+
+ mContext.getContentResolver().registerContentObserver(
+ CMSettings.System.getUriFor(CMSettings.System.STATUS_BAR_NOTIF_COUNT),
+ false, this);
+ }
+
+ @Override
+ protected void unobserve() {
+ super.unobserve();
+
+ mContext.getContentResolver().unregisterContentObserver(this);
+ }
+
+ @Override
+ public void update() {
+ boolean showIconCount = CMSettings.System.getIntForUser(mContext.getContentResolver(),
+ CMSettings.System.STATUS_BAR_NOTIF_COUNT, 0, UserHandle.USER_CURRENT) == 1;
+ for (StatusBarIconView sbiv : mIconViews) {
+ sbiv.mShowNotificationCount = showIconCount;
+ sbiv.set(sbiv.mIcon, true);
+ }
+ }
+ }
}
+
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/VisualizerView.java b/packages/SystemUI/src/com/android/systemui/statusbar/VisualizerView.java
new file mode 100644
index 0000000..538140c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/VisualizerView.java
@@ -0,0 +1,395 @@
+/*
+* Copyright (C) 2015 The CyanogenMod 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.animation.ObjectAnimator;
+import android.animation.ValueAnimator;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.media.audiofx.Visualizer;
+import android.os.AsyncTask;
+import android.os.Handler;
+import android.os.UserHandle;
+import android.support.v7.graphics.Palette;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.View;
+
+import com.android.systemui.cm.UserContentObserver;
+import cyanogenmod.providers.CMSettings;
+
+public class VisualizerView extends View implements Palette.PaletteAsyncListener {
+
+ private static final String TAG = VisualizerView.class.getSimpleName();
+ private static final boolean DEBUG = false;
+
+ private Paint mPaint;
+ private Visualizer mVisualizer;
+ private ObjectAnimator mVisualizerColorAnimator;
+
+ private ValueAnimator[] mValueAnimators;
+ private float[] mFFTPoints;
+
+ private int mStatusBarState;
+ private boolean mVisualizerEnabled = false;
+ private boolean mVisible = false;
+ private boolean mPlaying = false;
+ private boolean mPowerSaveMode = false;
+ private boolean mDisplaying = false; // the state we're animating to
+ private boolean mDozing = false;
+ private boolean mOccluded = false;
+
+ private int mColor;
+ private Bitmap mCurrentBitmap;
+
+ private SettingsObserver mObserver;
+
+ private Visualizer.OnDataCaptureListener mVisualizerListener =
+ new Visualizer.OnDataCaptureListener() {
+ byte rfk, ifk;
+ int dbValue;
+ float magnitude;
+
+ @Override
+ public void onWaveFormDataCapture(Visualizer visualizer, byte[] bytes, int samplingRate) {
+ }
+
+ @Override
+ public void onFftDataCapture(Visualizer visualizer, byte[] fft, int samplingRate) {
+ for (int i = 0; i < 32; i++) {
+ mValueAnimators[i].cancel();
+ rfk = fft[i * 2 + 2];
+ ifk = fft[i * 2 + 3];
+ magnitude = rfk * rfk + ifk * ifk;
+ dbValue = magnitude > 0 ? (int) (10 * Math.log10(magnitude)) : 0;
+
+ mValueAnimators[i].setFloatValues(mFFTPoints[i * 4 + 1],
+ mFFTPoints[3] - (dbValue * 16f));
+ mValueAnimators[i].start();
+ }
+ }
+ };
+
+ private final Runnable mLinkVisualizer = new Runnable() {
+ @Override
+ public void run() {
+ if (DEBUG) {
+ Log.w(TAG, "+++ mLinkVisualizer run()");
+ }
+
+ try {
+ mVisualizer = new Visualizer(0);
+ } catch (Exception e) {
+ Log.e(TAG, "error initializing visualizer", e);
+ return;
+ }
+
+ mVisualizer.setEnabled(false);
+ mVisualizer.setCaptureSize(66);
+ mVisualizer.setDataCaptureListener(mVisualizerListener,Visualizer.getMaxCaptureRate(),
+ false, true);
+ mVisualizer.setEnabled(true);
+
+ if (DEBUG) {
+ Log.w(TAG, "--- mLinkVisualizer run()");
+ }
+ }
+ };
+
+ private final Runnable mAsyncUnlinkVisualizer = new Runnable() {
+ @Override
+ public void run() {
+ AsyncTask.execute(mUnlinkVisualizer);
+ }
+ };
+
+ private final Runnable mUnlinkVisualizer = new Runnable() {
+ @Override
+ public void run() {
+ if (DEBUG) {
+ Log.w(TAG, "+++ mUnlinkVisualizer run(), mVisualizer: " + mVisualizer);
+ }
+ if (mVisualizer != null) {
+ mVisualizer.setEnabled(false);
+ mVisualizer.release();
+ mVisualizer = null;
+ }
+ if (DEBUG) {
+ Log.w(TAG, "--- mUninkVisualizer run()");
+ }
+ }
+ };
+
+ public VisualizerView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+
+ mColor = Color.TRANSPARENT;
+
+ mPaint = new Paint();
+ mPaint.setAntiAlias(true);
+ mPaint.setColor(mColor);
+
+ mFFTPoints = new float[128];
+ mValueAnimators = new ValueAnimator[32];
+ for (int i = 0; i < 32; i++) {
+ final int j = i * 4 + 1;
+ mValueAnimators[i] = new ValueAnimator();
+ mValueAnimators[i].setDuration(128);
+ mValueAnimators[i].addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(ValueAnimator animation) {
+ mFFTPoints[j] = (float) animation.getAnimatedValue();
+ postInvalidate();
+ }
+ });
+ }
+ }
+
+ public VisualizerView(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public VisualizerView(Context context) {
+ this(context, null, 0);
+ }
+
+ private void updateViewVisibility() {
+ final int curVis = getVisibility();
+ final int newVis = mStatusBarState != StatusBarState.SHADE
+ && mVisualizerEnabled ? View.VISIBLE : View.GONE;
+ if (curVis != newVis) {
+ setVisibility(newVis);
+ checkStateChanged();
+ }
+ }
+
+ @Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+ mObserver = new SettingsObserver(new Handler());
+ mObserver.observe();
+ mObserver.update();
+ }
+
+ @Override
+ protected void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+ mObserver.unobserve();
+ mObserver = null;
+ mCurrentBitmap = null;
+ }
+
+ @Override
+ protected void onSizeChanged(int w, int h, int oldw, int oldh) {
+ super.onSizeChanged(w, h, oldw, oldh);
+
+ float barUnit = w / 32f;
+ float barWidth = barUnit * 8f / 9f;
+ barUnit = barWidth + (barUnit - barWidth) * 32f / 31f;
+ mPaint.setStrokeWidth(barWidth);
+
+ for (int i = 0; i < 32; i++) {
+ mFFTPoints[i * 4] = mFFTPoints[i * 4 + 2] = i * barUnit + (barWidth / 2);
+ mFFTPoints[i * 4 + 1] = h;
+ mFFTPoints[i * 4 + 3] = h;
+ }
+ }
+
+ @Override
+ public boolean hasOverlappingRendering() {
+ return false;
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ super.onDraw(canvas);
+
+ if (mVisualizer != null) {
+ canvas.drawLines(mFFTPoints, mPaint);
+ }
+ }
+
+ public void setVisible(boolean visible) {
+ if (mVisible != visible) {
+ if (DEBUG) {
+ Log.i(TAG, "setVisible() called with visible = [" + visible + "]");
+ }
+ mVisible = visible;
+ checkStateChanged();
+ }
+ }
+
+ public void setDozing(boolean dozing) {
+ if (mDozing != dozing) {
+ if (DEBUG) {
+ Log.i(TAG, "setDozing() called with dozing = [" + dozing + "]");
+ }
+ mDozing = dozing;
+ checkStateChanged();
+ }
+ }
+
+ public void setPlaying(boolean playing) {
+ if (mPlaying != playing) {
+ if (DEBUG) {
+ Log.i(TAG, "setPlaying() called with playing = [" + playing + "]");
+ }
+ mPlaying = playing;
+ checkStateChanged();
+ }
+ }
+
+ public void setPowerSaveMode(boolean powerSaveMode) {
+ if (mPowerSaveMode != powerSaveMode) {
+ if (DEBUG) {
+ Log.i(TAG, "setPowerSaveMode() called with powerSaveMode = [" + powerSaveMode + "]");
+ }
+ mPowerSaveMode = powerSaveMode;
+ checkStateChanged();
+ }
+ }
+
+ public void setOccluded(boolean occluded) {
+ if (mOccluded != occluded) {
+ if (DEBUG) {
+ Log.i(TAG, "setOccluded() called with occluded = [" + occluded + "]");
+ }
+ mOccluded = occluded;
+ checkStateChanged();
+ }
+ }
+
+ public void setStatusBarState(int statusBarState) {
+ if (mStatusBarState != statusBarState) {
+ mStatusBarState = statusBarState;
+ updateViewVisibility();
+ }
+ }
+
+ public void setBitmap(Bitmap bitmap) {
+ if (mCurrentBitmap == bitmap) {
+ return;
+ }
+ mCurrentBitmap = bitmap;
+ if (bitmap != null) {
+ Palette.generateAsync(bitmap, this);
+ } else {
+ setColor(Color.TRANSPARENT);
+ }
+ }
+
+ @Override
+ public void onGenerated(Palette palette) {
+ int color = Color.TRANSPARENT;
+
+ color = palette.getVibrantColor(color);
+ if (color == Color.TRANSPARENT) {
+ color = palette.getLightVibrantColor(color);
+ if (color == Color.TRANSPARENT) {
+ color = palette.getDarkVibrantColor(color);
+ }
+ }
+
+ setColor(color);
+ }
+
+ private void setColor(int color) {
+ if (color == Color.TRANSPARENT) {
+ color = Color.WHITE;
+ }
+
+ color = Color.argb(140, Color.red(color), Color.green(color), Color.blue(color));
+
+ if (mColor != color) {
+ mColor = color;
+
+ if (mVisualizer != null) {
+ if (mVisualizerColorAnimator != null) {
+ mVisualizerColorAnimator.cancel();
+ }
+
+ mVisualizerColorAnimator = ObjectAnimator.ofArgb(mPaint, "color",
+ mPaint.getColor(), mColor);
+ mVisualizerColorAnimator.setStartDelay(600);
+ mVisualizerColorAnimator.setDuration(1200);
+ mVisualizerColorAnimator.start();
+ } else {
+ mPaint.setColor(mColor);
+ }
+ }
+ }
+
+ private void checkStateChanged() {
+ if (getVisibility() == View.VISIBLE && mVisible && mPlaying && !mDozing && !mPowerSaveMode
+ && mVisualizerEnabled && !mOccluded) {
+ if (!mDisplaying) {
+ mDisplaying = true;
+ AsyncTask.execute(mLinkVisualizer);
+ animate()
+ .alpha(1f)
+ .withEndAction(null)
+ .setDuration(800);
+ }
+ } else {
+ if (mDisplaying) {
+ mDisplaying = false;
+ if (mVisible) {
+ animate()
+ .alpha(0f)
+ .withEndAction(mAsyncUnlinkVisualizer)
+ .setDuration(600);
+ } else {
+ animate().
+ alpha(0f)
+ .withEndAction(mAsyncUnlinkVisualizer)
+ .setDuration(0);
+ }
+ }
+ }
+ }
+
+ private class SettingsObserver extends UserContentObserver {
+
+ public SettingsObserver(Handler handler) {
+ super(handler);
+ }
+
+ @Override
+ protected void update() {
+ mVisualizerEnabled = CMSettings.Secure.getInt(getContext().getContentResolver(),
+ CMSettings.Secure.LOCKSCREEN_VISUALIZER_ENABLED, 1) != 0;
+ checkStateChanged();
+ updateViewVisibility();
+ }
+
+ @Override
+ protected void observe() {
+ super.observe();
+ getContext().getContentResolver().registerContentObserver(
+ CMSettings.Secure.getUriFor(CMSettings.Secure.LOCKSCREEN_VISUALIZER_ENABLED),
+ false, this, UserHandle.USER_CURRENT);
+ }
+
+ @Override
+ protected void unobserve() {
+ super.unobserve();
+ getContext().getContentResolver().unregisterContentObserver(this);
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BackButtonDrawable.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BackButtonDrawable.java
new file mode 100644
index 0000000..c76b5d3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BackButtonDrawable.java
@@ -0,0 +1,139 @@
+/*
+ * Copyright (C) 2014 The CyanogenMod 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.ObjectAnimator;
+import android.annotation.NonNull;
+import android.app.ActivityManager;
+import android.graphics.Canvas;
+import android.graphics.ColorFilter;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.util.FloatProperty;
+import android.util.Property;
+
+public class BackButtonDrawable extends Drawable {
+ private final Drawable mWrappedDrawable;
+ private float mRotation;
+ private Animator mCurrentAnimator;
+
+ private static final int ANIMATION_DURATION = 200;
+ public static final Property<BackButtonDrawable, Float> ROTATION
+ = new FloatProperty<BackButtonDrawable>("rotation") {
+ @Override
+ public void setValue(BackButtonDrawable object, float value) {
+ object.setRotation(value);
+ }
+
+ @Override
+ public Float get(BackButtonDrawable object) {
+ return object.getRotation();
+ }
+ };
+
+ public BackButtonDrawable(Drawable wrappedDrawable) {
+ mWrappedDrawable = wrappedDrawable;
+ }
+
+ @Override
+ public void draw(Canvas canvas) {
+ final Rect bounds = mWrappedDrawable.getBounds();
+ final int boundsCenterX = bounds.width() / 2;
+ final int boundsCenterY = bounds.height() / 2;
+
+ canvas.translate(boundsCenterX, boundsCenterY);
+ canvas.rotate(mRotation);
+ canvas.translate(- boundsCenterX, - boundsCenterY);
+
+ mWrappedDrawable.draw(canvas);
+ }
+
+ @Override
+ public void setBounds(Rect bounds) {
+ mWrappedDrawable.setBounds(bounds);
+ }
+
+ @Override
+ public void setBounds(int left, int top, int right, int bottom) {
+ mWrappedDrawable.setBounds(left, top, right, bottom);
+ }
+
+ @Override
+ protected void onBoundsChange(Rect bounds) {
+ mWrappedDrawable.setBounds(bounds);
+ }
+
+ @Override
+ public void setAlpha(int alpha) {
+ mWrappedDrawable.setAlpha(alpha);
+ if (mCurrentAnimator != null) {
+ mCurrentAnimator.end();
+ }
+ }
+
+ @Override
+ public int getAlpha() {
+ return mWrappedDrawable.getAlpha();
+ }
+
+ @Override
+ public void setColorFilter(ColorFilter cf) {
+ }
+
+ @Override
+ public int getOpacity() {
+ return mWrappedDrawable.getOpacity();
+ }
+
+ @Override
+ public int getIntrinsicWidth() {
+ return mWrappedDrawable.getIntrinsicWidth();
+ }
+
+ @Override
+ public int getIntrinsicHeight() {
+ return mWrappedDrawable.getIntrinsicHeight();
+ }
+
+ public void setRotation(float rotation) {
+ mRotation = rotation;
+ invalidateSelf();
+ }
+
+ public float getRotation() {
+ return mRotation;
+ }
+
+ public void setImeVisible(boolean ime) {
+ if (mCurrentAnimator != null) {
+ mCurrentAnimator.cancel();
+ }
+
+ final float nextRotation = ime ? - 90 : 0;
+ if (mRotation == nextRotation) {
+ return;
+ }
+
+ if (isVisible() && ActivityManager.isHighEndGfx()) {
+ mCurrentAnimator = ObjectAnimator.ofFloat(this, ROTATION, nextRotation)
+ .setDuration(ANIMATION_DURATION);
+ mCurrentAnimator.start();
+ } else {
+ setRotation(nextRotation);
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BarTransitions.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BarTransitions.java
index 1601b83..093d18c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BarTransitions.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BarTransitions.java
@@ -57,15 +57,28 @@ public class BarTransitions {
private int mMode;
- public BarTransitions(View view, int gradientResourceId) {
+ public BarTransitions(View view, int gradientResourceId, int opaqueColorResourceId,
+ int semiTransparentColorResourceId, int transparentColorResourceId,
+ int warningColorResourceId) {
mTag = "BarTransitions." + view.getClass().getSimpleName();
mView = view;
- mBarBackground = new BarBackgroundDrawable(mView.getContext(), gradientResourceId);
+ mBarBackground = new BarBackgroundDrawable(mView.getContext(), gradientResourceId,
+ opaqueColorResourceId, semiTransparentColorResourceId,
+ transparentColorResourceId, warningColorResourceId);
if (HIGH_END) {
mView.setBackground(mBarBackground);
}
}
+ protected void setGradientResourceId(int gradientResourceId) {
+ mBarBackground.setGradientResourceId(mView.getContext().getResources(),
+ gradientResourceId);
+ }
+
+ public void updateResources(Resources res) {
+ mBarBackground.updateResources(res);
+ }
+
public int getMode() {
return mMode;
}
@@ -119,11 +132,11 @@ public class BarTransitions {
}
private static class BarBackgroundDrawable extends Drawable {
- private final int mOpaque;
- private final int mSemiTransparent;
- private final int mTransparent;
- private final int mWarning;
- private final Drawable mGradient;
+ private int mOpaque;
+ private int mSemiTransparent;
+ private int mTransparent;
+ private int mWarning;
+ private Drawable mGradient;
private final TimeInterpolator mInterpolator;
private int mMode = -1;
@@ -137,7 +150,15 @@ public class BarTransitions {
private int mGradientAlphaStart;
private int mColorStart;
- public BarBackgroundDrawable(Context context, int gradientResourceId) {
+ private int mGradientResourceId;
+ private final int mOpaqueColorResourceId;
+ private final int mSemiTransparentColorResourceId;
+ private final int mTransparentColorResourceId;
+ private final int mWarningColorResourceId;
+
+ public BarBackgroundDrawable(Context context, int gradientResourceId,
+ int opaqueColorResourceId, int semiTransparentColorResourceId,
+ int transparentColorResourceId, int warningColorResourceId) {
final Resources res = context.getResources();
if (DEBUG_COLORS) {
mOpaque = 0xff0000ff;
@@ -145,13 +166,36 @@ public class BarTransitions {
mTransparent = 0x2f0000ff;
mWarning = 0xffff0000;
} else {
- mOpaque = context.getColor(R.color.system_bar_background_opaque);
- mSemiTransparent = context.getColor(R.color.system_bar_background_semi_transparent);
- mTransparent = context.getColor(R.color.system_bar_background_transparent);
- mWarning = context.getColor(com.android.internal.R.color.battery_saver_mode_color);
+ mOpaque = res.getColor(R.color.system_bar_background_opaque);
+ mSemiTransparent = res.getColor(R.color.system_bar_background_semi_transparent);
+ mTransparent = res.getColor(transparentColorResourceId);
+ mWarning = res.getColor(warningColorResourceId);
}
mGradient = context.getDrawable(gradientResourceId);
mInterpolator = new LinearInterpolator();
+ mGradientResourceId = gradientResourceId;
+ mOpaqueColorResourceId = opaqueColorResourceId;
+ mSemiTransparentColorResourceId = semiTransparentColorResourceId;
+ mTransparentColorResourceId = transparentColorResourceId;
+ mWarningColorResourceId = warningColorResourceId;
+ }
+
+ public void setGradientResourceId(Resources res, int gradientResourceId) {
+ mGradient = res.getDrawable(gradientResourceId);
+ mGradientResourceId = gradientResourceId;
+ }
+
+ public void updateResources(Resources res) {
+ mOpaque = res.getColor(mOpaqueColorResourceId);
+ mSemiTransparent = res.getColor(mSemiTransparentColorResourceId);
+ mTransparent = res.getColor(mTransparentColorResourceId);
+ mWarning = res.getColor(mWarningColorResourceId);
+ // Retrieve the current bounds for mGradient so they can be set to
+ // the new drawable being loaded, otherwise the bounds will be (0, 0, 0, 0)
+ // and the gradient will not be drawn.
+ Rect bounds = mGradient.getBounds();
+ mGradient = res.getDrawable(mGradientResourceId);
+ mGradient.setBounds(bounds);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BlurLayer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BlurLayer.java
new file mode 100644
index 0000000..a966409
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BlurLayer.java
@@ -0,0 +1,196 @@
+/*
+ * Copyright (c) 2014, The Linux Foundation. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ * * Neither the name of The Linux Foundation nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+ * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.android.systemui.statusbar.phone;
+
+import android.graphics.PixelFormat;
+import android.util.Slog;
+import android.view.SurfaceControl;
+import android.view.SurfaceSession;
+
+import java.io.PrintWriter;
+
+public class BlurLayer {
+ private static final String TAG = "BlurLayer";
+ private static final boolean DEBUG = true;
+ private SurfaceControl mBlurSurface;
+ private int mLayer = -1;
+ private float mAlpha = 0;
+ private float mBlur = 0;
+ private int mX, mY;
+ private int mW, mH;
+ private boolean mIsShow;
+
+ public BlurLayer(SurfaceSession mFxSession, int w, int h, String tag) {
+ this(mFxSession, 0, 0, w, h, tag);
+ }
+
+ public BlurLayer(SurfaceSession mFxSession, int x, int y, int w, int h, String tag) {
+ mX = x;
+ mY = y;
+ mW = w;
+ mH = h;
+ mIsShow = false;
+
+ SurfaceControl.openTransaction();
+ try {
+ mBlurSurface = new SurfaceControl(mFxSession, TAG+"_"+tag, 16, 16, PixelFormat.OPAQUE,
+ SurfaceControl.FX_SURFACE_BLUR | SurfaceControl.HIDDEN);
+ mBlurSurface.setLayerStack(0);
+ mBlurSurface.setPosition(mX, mY);
+ mBlurSurface.setSize(mW, mH);
+ } catch (Exception e) {
+ Slog.e(TAG, "Exception creating BlurLayer surface", e);
+ } finally {
+ SurfaceControl.closeTransaction();
+ }
+ }
+
+ public void setSize(int w, int h) {
+ if (mBlurSurface != null && (mW != w || mH != h) ) {
+ SurfaceControl.openTransaction();
+ try {
+ mBlurSurface.setSize(w, h);
+ mW = w;
+ mH = h;
+ } catch (RuntimeException e) {
+ Slog.w(TAG, "Failure setting setSize immediately", e);
+ } catch (Exception e) {
+ Slog.e(TAG, "Exception setSize", e);
+ } finally {
+ SurfaceControl.closeTransaction();
+ }
+ }
+ }
+
+ public void setPosition(int x, int y) {
+ if (mBlurSurface != null && (mX != x || mY != y) ) {
+ SurfaceControl.openTransaction();
+ try {
+ mBlurSurface.setPosition(x, y);
+ mX = x;
+ mY = y;
+ } catch (RuntimeException e) {
+ Slog.w(TAG, "Failure setting setPosition immediately", e);
+ } catch (Exception e) {
+ Slog.e(TAG, "Exception setPosition", e);
+ } finally {
+ SurfaceControl.closeTransaction();
+ }
+ }
+ }
+
+ public void setLayer(int layer) {
+ if (mBlurSurface != null && mLayer != layer) {
+ SurfaceControl.openTransaction();
+ try {
+ mBlurSurface.setLayer(layer);
+ mLayer = layer;
+ } catch (RuntimeException e) {
+ Slog.w(TAG, "Failure setting setLayer immediately", e);
+ } catch (Exception e) {
+ Slog.e(TAG, "Exception setLayer", e);
+ } finally {
+ SurfaceControl.closeTransaction();
+ }
+ }
+ }
+
+ public void setAlpha(float alpha){
+ if(mBlurSurface != null && mAlpha != alpha){
+ SurfaceControl.openTransaction();
+ try {
+ mBlurSurface.setAlpha(alpha);
+ mAlpha = alpha;
+ } catch (RuntimeException e) {
+ Slog.w(TAG, "Failure setting alpha immediately", e);
+ } catch (Exception e) {
+ Slog.e(TAG, "Exception setAlpha", e);
+ } finally {
+ SurfaceControl.closeTransaction();
+ }
+ }
+ }
+
+ public void setBlur(float blur){
+ if(mBlurSurface != null && mBlur != blur ){
+ SurfaceControl.openTransaction();
+ try {
+ mBlurSurface.setBlur(blur);
+ mBlur = blur;
+ } catch (RuntimeException e) {
+ Slog.w(TAG, "Failure setting blur immediately", e);
+ } catch (Exception e) {
+ Slog.e(TAG, "Exception setBlur", e);
+ } finally {
+ SurfaceControl.closeTransaction();
+ }
+ }
+ }
+
+ public void show() {
+ if(mBlurSurface != null && !mIsShow ){
+ try {
+ mBlurSurface.show();
+ mIsShow = true;
+ } catch (RuntimeException e) {
+ Slog.w(TAG, "Failure show()", e);
+ } catch (Exception e) {
+ Slog.e(TAG, "Exception show()", e);
+ } finally {
+ SurfaceControl.closeTransaction();
+ }
+ }
+ }
+
+ public void hide(){
+ if(mBlurSurface != null && mIsShow ){
+ try {
+ mBlurSurface.hide();
+ mIsShow = false;
+ } catch (RuntimeException e) {
+ Slog.w(TAG, "Failure hide()", e);
+ } catch (Exception e) {
+ Slog.e(TAG, "Exception hide()", e);
+ } finally {
+ SurfaceControl.closeTransaction();
+ }
+ }
+ }
+
+ public void destroySurface() {
+ if (DEBUG) Slog.v(TAG, "destroySurface.");
+ if (mBlurSurface != null) {
+ mBlurSurface.destroy();
+ mBlurSurface = null;
+ }
+ }
+
+}
+
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ClockController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ClockController.java
new file mode 100644
index 0000000..84eeb31
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ClockController.java
@@ -0,0 +1,149 @@
+package com.android.systemui.statusbar.phone;
+
+import com.android.systemui.FontSizeUtils;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.database.ContentObserver;
+import android.graphics.Color;
+import android.os.Handler;
+import android.os.UserHandle;
+import android.view.View;
+import com.android.systemui.R;
+import com.android.systemui.cm.UserContentObserver;
+import com.android.systemui.statusbar.policy.Clock;
+
+import cyanogenmod.providers.CMSettings;
+
+/**
+ * To control your...clock
+ */
+public class ClockController {
+
+ public static final int STYLE_HIDE_CLOCK = 0;
+ public static final int STYLE_CLOCK_RIGHT = 1;
+ public static final int STYLE_CLOCK_CENTER = 2;
+ public static final int STYLE_CLOCK_LEFT = 3;
+
+ private final IconMerger mNotificationIcons;
+ private final Context mContext;
+ private final SettingsObserver mSettingsObserver;
+ private Clock mRightClock, mCenterClock, mLeftClock, mActiveClock;
+
+ private int mClockLocation;
+ private int mAmPmStyle;
+ private int mIconTint = Color.WHITE;
+
+ class SettingsObserver extends UserContentObserver {
+ SettingsObserver(Handler handler) {
+ super(handler);
+ }
+
+ @Override
+ protected void observe() {
+ super.observe();
+ ContentResolver resolver = mContext.getContentResolver();
+ resolver.registerContentObserver(CMSettings.System.getUriFor(
+ CMSettings.System.STATUS_BAR_AM_PM), false, this, UserHandle.USER_ALL);
+ resolver.registerContentObserver(CMSettings.System.getUriFor(
+ CMSettings.System.STATUS_BAR_CLOCK), false, this, UserHandle.USER_ALL);
+ updateSettings();
+ }
+
+ @Override
+ protected void unobserve() {
+ super.unobserve();
+ mContext.getContentResolver().unregisterContentObserver(this);
+ }
+
+ @Override
+ public void update() {
+ updateSettings();
+ }
+ }
+
+ public ClockController(View statusBar, IconMerger notificationIcons, Handler handler) {
+ mRightClock = (Clock) statusBar.findViewById(R.id.clock);
+ mCenterClock = (Clock) statusBar.findViewById(R.id.center_clock);
+ mLeftClock = (Clock) statusBar.findViewById(R.id.left_clock);
+ mNotificationIcons = notificationIcons;
+ mContext = statusBar.getContext();
+
+ mActiveClock = mRightClock;
+ mSettingsObserver = new SettingsObserver(handler);
+ mSettingsObserver.observe();
+ }
+
+ private Clock getClockForCurrentLocation() {
+ Clock clockForAlignment;
+ switch (mClockLocation) {
+ case STYLE_CLOCK_CENTER:
+ clockForAlignment = mCenterClock;
+ break;
+ case STYLE_CLOCK_LEFT:
+ clockForAlignment = mLeftClock;
+ break;
+ case STYLE_CLOCK_RIGHT:
+ case STYLE_HIDE_CLOCK:
+ default:
+ clockForAlignment = mRightClock;
+ break;
+ }
+ return clockForAlignment;
+ }
+
+ private void updateActiveClock() {
+ mActiveClock.setVisibility(View.GONE);
+ if (mClockLocation == STYLE_HIDE_CLOCK) {
+ return;
+ }
+
+ mActiveClock = getClockForCurrentLocation();
+ mActiveClock.setVisibility(View.VISIBLE);
+ mActiveClock.setAmPmStyle(mAmPmStyle);
+
+ setClockAndDateStatus();
+ setTextColor(mIconTint);
+ updateFontSize();
+ }
+
+ private void updateSettings() {
+ ContentResolver resolver = mContext.getContentResolver();
+ mAmPmStyle = CMSettings.System.getIntForUser(resolver,
+ CMSettings.System.STATUS_BAR_AM_PM, Clock.AM_PM_STYLE_GONE,
+ UserHandle.USER_CURRENT);
+ mClockLocation = CMSettings.System.getIntForUser(
+ resolver, CMSettings.System.STATUS_BAR_CLOCK, STYLE_CLOCK_RIGHT,
+ UserHandle.USER_CURRENT);
+ updateActiveClock();
+ }
+
+ private void setClockAndDateStatus() {
+ if (mNotificationIcons != null) {
+ mNotificationIcons.setClockAndDateStatus(mClockLocation);
+ }
+ }
+
+ public void setVisibility(boolean visible) {
+ if (mActiveClock != null) {
+ mActiveClock.setVisibility(visible ? View.VISIBLE : View.GONE);
+ }
+ }
+
+ public void setTextColor(int iconTint) {
+ mIconTint = iconTint;
+ if (mActiveClock != null) {
+ mActiveClock.setTextColor(iconTint);
+ }
+ }
+
+ public void updateFontSize() {
+ if (mActiveClock != null) {
+ FontSizeUtils.updateFontSize(mActiveClock, R.dimen.status_bar_clock_size);
+ }
+ }
+
+ public void cleanup() {
+ mSettingsObserver.unobserve();
+ }
+} \ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
index 1d890d0..9897098 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
@@ -22,6 +22,7 @@ import android.text.TextUtils;
import android.util.Log;
import android.util.MathUtils;
+import com.android.systemui.doze.DozeLog;
import com.android.systemui.R;
import java.io.PrintWriter;
@@ -46,17 +47,20 @@ public class DozeParameters {
public void dump(PrintWriter pw) {
pw.println(" DozeParameters:");
pw.print(" getDisplayStateSupported(): "); pw.println(getDisplayStateSupported());
- pw.print(" getPulseDuration(pickup=false): "); pw.println(getPulseDuration(false));
- pw.print(" getPulseDuration(pickup=true): "); pw.println(getPulseDuration(true));
- pw.print(" getPulseInDuration(pickup=false): "); pw.println(getPulseInDuration(false));
- pw.print(" getPulseInDuration(pickup=true): "); pw.println(getPulseInDuration(true));
+ pw.print(" getPulseDuration(notification): "); pw.println(getPulseDuration(DozeLog.PULSE_REASON_NOTIFICATION));
+ pw.print(" getPulseDuration(pickup): "); pw.println(getPulseDuration(DozeLog.PULSE_REASON_SENSOR_PICKUP));
+ pw.print(" getPulseDuration(intent): "); pw.println(getPulseDuration(DozeLog.PULSE_REASON_INTENT));
+ pw.print(" getPulseInDuration(notification): "); pw.println(getPulseInDuration(DozeLog.PULSE_REASON_NOTIFICATION));
+ pw.print(" getPulseInDuration(pickup): "); pw.println(getPulseInDuration(DozeLog.PULSE_REASON_SENSOR_PICKUP));
+ pw.print(" getPulseInDuration(intent): "); pw.println(getPulseInDuration(DozeLog.PULSE_REASON_INTENT));
pw.print(" getPulseInVisibleDuration(): "); pw.println(getPulseVisibleDuration());
pw.print(" getPulseOutDuration(): "); pw.println(getPulseOutDuration());
pw.print(" getPulseOnSigMotion(): "); pw.println(getPulseOnSigMotion());
pw.print(" getVibrateOnSigMotion(): "); pw.println(getVibrateOnSigMotion());
pw.print(" getPulseOnPickup(): "); pw.println(getPulseOnPickup());
pw.print(" getVibrateOnPickup(): "); pw.println(getVibrateOnPickup());
- pw.print(" getProxCheckBeforePulse(): "); pw.println(getProxCheckBeforePulse());
+ pw.print(" getProxCheckBeforePulse(pickup): "); pw.println(getProxCheckBeforePulse(DozeLog.PULSE_REASON_SENSOR_PICKUP));
+ pw.print(" getProxCheckBeforePulse(intent): "); pw.println(getProxCheckBeforePulse(DozeLog.PULSE_REASON_INTENT));
pw.print(" getPulseOnNotifications(): "); pw.println(getPulseOnNotifications());
pw.print(" getPulseSchedule(): "); pw.println(getPulseSchedule());
pw.print(" getPulseScheduleResets(): "); pw.println(getPulseScheduleResets());
@@ -68,14 +72,19 @@ public class DozeParameters {
return getBoolean("doze.display.supported", R.bool.doze_display_state_supported);
}
- public int getPulseDuration(boolean pickup) {
- return getPulseInDuration(pickup) + getPulseVisibleDuration() + getPulseOutDuration();
+ public int getPulseDuration(int reason) {
+ return getPulseInDuration(reason) + getPulseVisibleDuration() + getPulseOutDuration();
}
- public int getPulseInDuration(boolean pickup) {
- return pickup
- ? getInt("doze.pulse.duration.in.pickup", R.integer.doze_pulse_duration_in_pickup)
- : getInt("doze.pulse.duration.in", R.integer.doze_pulse_duration_in);
+ public int getPulseInDuration(int reason) {
+ switch(reason) {
+ case DozeLog.PULSE_REASON_SENSOR_PICKUP:
+ return getInt("doze.pulse.duration.in.pickup", R.integer.doze_pulse_duration_in_pickup);
+ case DozeLog.PULSE_REASON_INTENT:
+ return getInt("doze.pulse.duration.in.intent", R.integer.doze_pulse_duration_in_intent);
+ default:
+ return getInt("doze.pulse.duration.in", R.integer.doze_pulse_duration_in);
+ }
}
public int getPulseVisibleDuration() {
@@ -102,8 +111,15 @@ public class DozeParameters {
return SystemProperties.getBoolean("doze.vibrate.pickup", false);
}
- public boolean getProxCheckBeforePulse() {
- return getBoolean("doze.pulse.proxcheck", R.bool.doze_proximity_check_before_pulse);
+ public boolean getProxCheckBeforePulse(int reason) {
+ switch(reason) {
+ case DozeLog.PULSE_REASON_SENSOR_PICKUP:
+ return getBoolean("doze.pulse.proxcheck.pickup", R.bool.doze_proximity_check_before_pulse);
+ case DozeLog.PULSE_REASON_INTENT:
+ return getBoolean("doze.pulse.proxcheck.intent", R.bool.doze_proximity_check_before_pulse_intent);
+ default:
+ return getBoolean("doze.pulse.proxcheck", R.bool.doze_proximity_check_before_pulse);
+ }
}
public boolean getPickupPerformsProxCheck() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeScrimController.java
index 3ff69c9..b3e0104 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeScrimController.java
@@ -115,7 +115,7 @@ public class DozeScrimController {
if (isPulsing()) {
final boolean pickup = mPulseReason == DozeLog.PULSE_REASON_SENSOR_PICKUP;
startScrimAnimation(true /* inFront */, 0f,
- mDozeParameters.getPulseInDuration(pickup),
+ mDozeParameters.getPulseInDuration(mPulseReason),
pickup ? mPulseInInterpolatorPickup : mPulseInInterpolator,
mPulseInFinished);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/FingerprintUnlockController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/FingerprintUnlockController.java
index 2912963..7135836 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/FingerprintUnlockController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/FingerprintUnlockController.java
@@ -173,7 +173,7 @@ public class FingerprintUnlockController extends KeyguardUpdateMonitorCallback {
if (DEBUG_FP_WAKELOCK) {
Log.i(TAG, "fp wakelock: Authenticated, waking up...");
}
- mPowerManager.wakeUp(SystemClock.uptimeMillis());
+ mPowerManager.wakeUp(SystemClock.uptimeMillis(), "android.policy:FINGERPRINT");
}
releaseFingerprintWakeLock();
switch (mMode) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/IconMerger.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/IconMerger.java
index 50ead3d..5750372 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/IconMerger.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/IconMerger.java
@@ -22,12 +22,14 @@ import android.view.View;
import android.widget.LinearLayout;
import com.android.systemui.R;
+import com.android.systemui.statusbar.policy.Clock;
public class IconMerger extends LinearLayout {
private static final String TAG = "IconMerger";
private static final boolean DEBUG = false;
private int mIconSize;
+ private int mClockLocation;
private View mMoreView;
public IconMerger(Context context, AttributeSet attrs) {
@@ -50,6 +52,10 @@ public class IconMerger extends LinearLayout {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
// we need to constrain this to an integral multiple of our children
int width = getMeasuredWidth();
+ if (mClockLocation == ClockController.STYLE_CLOCK_CENTER) {
+ int totalWidth = getResources().getDisplayMetrics().widthPixels;
+ width = totalWidth / 2 - mIconSize * 2;
+ }
setMeasuredDimension(width - (width % mIconSize), getMeasuredHeight());
}
@@ -69,7 +75,14 @@ public class IconMerger extends LinearLayout {
}
final boolean overflowShown = (mMoreView.getVisibility() == View.VISIBLE);
// let's assume we have one more slot if the more icon is already showing
- if (overflowShown) visibleChildren --;
+ if (overflowShown) {
+ int totalWidth = getResources().getDisplayMetrics().widthPixels;
+ if ((mClockLocation != ClockController.STYLE_CLOCK_CENTER &&
+ mClockLocation != ClockController.STYLE_CLOCK_LEFT) ||
+ (visibleChildren > (totalWidth / mIconSize / 2 + 1))) {
+ visibleChildren--;
+ }
+ }
final boolean moreRequired = visibleChildren * mIconSize > width;
if (moreRequired != overflowShown) {
post(new Runnable() {
@@ -80,4 +93,9 @@ public class IconMerger extends LinearLayout {
});
}
}
+
+ public void setClockAndDateStatus(int mode) {
+ mClockLocation = mode;
+
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardAffordanceHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardAffordanceHelper.java
index 60ebfdf..e1a345f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardAffordanceHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardAffordanceHelper.java
@@ -49,7 +49,7 @@ public class KeyguardAffordanceHelper {
private VelocityTracker mVelocityTracker;
private boolean mSwipingInProgress;
private float mInitialTouchX;
- private float mInitialTouchY;
+ private float mInitialTouchYRaw;
private float mTranslation;
private float mTranslationOnDown;
private int mTouchSlop;
@@ -128,7 +128,7 @@ public class KeyguardAffordanceHelper {
if (mMotionCancelled && action != MotionEvent.ACTION_DOWN) {
return false;
}
- final float y = event.getY();
+ final float y = event.getRawY();
final float x = event.getX();
boolean isUp = false;
@@ -146,7 +146,7 @@ public class KeyguardAffordanceHelper {
}
startSwiping(targetView);
mInitialTouchX = x;
- mInitialTouchY = y;
+ mInitialTouchYRaw = y;
mTranslationOnDown = mTranslation;
initVelocityTracker();
trackMovement(event);
@@ -159,7 +159,7 @@ public class KeyguardAffordanceHelper {
case MotionEvent.ACTION_MOVE:
trackMovement(event);
float xDist = x - mInitialTouchX;
- float yDist = y - mInitialTouchY;
+ float yDist = y - mInitialTouchYRaw;
float distance = (float) Math.hypot(xDist, yDist);
if (!mTouchSlopExeeded && distance > mTouchSlop) {
mTouchSlopExeeded = true;
@@ -211,8 +211,9 @@ public class KeyguardAffordanceHelper {
}
private boolean isOnIcon(View icon, float x, float y) {
+ int[] location = icon.getLocationOnScreen();
float iconX = icon.getX() + icon.getWidth() / 2.0f;
- float iconY = icon.getY() + icon.getHeight() / 2.0f;
+ float iconY = location[1] + icon.getHeight() / 2.0f;
double distance = Math.hypot(x - iconX, y - iconY);
return distance <= mTouchTargetSize / 2;
}
@@ -241,6 +242,13 @@ public class KeyguardAffordanceHelper {
return false;
}
+ public boolean isOnLockIcon(MotionEvent event) {
+ final float x = event.getX();
+ final float y = event.getRawY();
+
+ return isOnIcon(mCenterIcon, x, y);
+ }
+
public void startHintAnimation(boolean right,
Runnable onFinishedListener) {
cancelAnimation();
@@ -488,7 +496,7 @@ public class KeyguardAffordanceHelper {
float aX = mVelocityTracker.getXVelocity();
float aY = mVelocityTracker.getYVelocity();
float bX = lastX - mInitialTouchX;
- float bY = lastY - mInitialTouchY;
+ float bY = lastY - mInitialTouchYRaw;
float bLen = (float) Math.hypot(bX, bY);
// Project the velocity onto the distance vector: a * b / |b|
float projectedVelocity = (aX * bX + aY * bY) / bLen;
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 14176a6..b244e26 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
@@ -29,6 +29,10 @@ import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.res.Configuration;
+import android.graphics.PixelFormat;
+import android.graphics.ColorMatrix;
+import android.graphics.ColorMatrixColorFilter;
+import android.graphics.drawable.Drawable;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.IBinder;
@@ -42,14 +46,16 @@ import android.telecom.TelecomManager;
import android.util.AttributeSet;
import android.util.Log;
import android.util.TypedValue;
+import android.view.Gravity;
+import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
+import android.view.WindowManager;
import android.view.accessibility.AccessibilityNodeInfo;
import android.view.animation.AnimationUtils;
import android.view.animation.Interpolator;
import android.widget.FrameLayout;
import android.widget.TextView;
-
import com.android.internal.widget.LockPatternUtils;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.keyguard.KeyguardUpdateMonitorCallback;
@@ -57,6 +63,8 @@ import com.android.systemui.EventLogConstants;
import com.android.systemui.EventLogTags;
import com.android.systemui.R;
import com.android.systemui.assist.AssistManager;
+import com.android.systemui.cm.LockscreenShortcutsHelper;
+import com.android.systemui.cm.LockscreenShortcutsHelper.Shortcuts;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.KeyguardAffordanceView;
import com.android.systemui.statusbar.KeyguardIndicationController;
@@ -64,6 +72,8 @@ import com.android.systemui.statusbar.policy.AccessibilityController;
import com.android.systemui.statusbar.policy.FlashlightController;
import com.android.systemui.statusbar.policy.PreviewInflater;
+import java.util.Objects;
+
import static android.view.accessibility.AccessibilityNodeInfo.ACTION_CLICK;
import static android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
@@ -72,7 +82,7 @@ import static android.view.accessibility.AccessibilityNodeInfo.AccessibilityActi
* text.
*/
public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickListener,
- UnlockMethodCache.OnUnlockMethodChangedListener,
+ UnlockMethodCache.OnUnlockMethodChangedListener, LockscreenShortcutsHelper.OnChangeListener,
AccessibilityController.AccessibilityStateChangedCallback, View.OnLongClickListener {
final static String TAG = "PhoneStatusBar/KeyguardBottomAreaView";
@@ -80,6 +90,7 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL
public static final String CAMERA_LAUNCH_SOURCE_AFFORDANCE = "lockscreen_affordance";
public static final String CAMERA_LAUNCH_SOURCE_WIGGLE = "wiggle_gesture";
public static final String CAMERA_LAUNCH_SOURCE_POWER_DOUBLE_TAP = "power_double_tap";
+ public static final String CAMERA_LAUNCH_SOURCE_SCREEN_GESTURE = "screen_gesture";
public static final String EXTRA_CAMERA_LAUNCH_SOURCE
= "com.android.systemui.camera_launch_source";
@@ -110,11 +121,20 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL
private KeyguardIndicationController mIndicationController;
private AccessibilityController mAccessibilityController;
private PhoneStatusBar mPhoneStatusBar;
+ private LockscreenShortcutsHelper mShortcutHelper;
+ private final ColorMatrixColorFilter mGrayScaleFilter;
private final Interpolator mLinearOutSlowInInterpolator;
private boolean mUserSetupComplete;
private boolean mPrewarmBound;
private Messenger mPrewarmMessenger;
+ private final WindowManager mWindowManager;
+ private boolean mBottomAreaAttached;
+ private final WindowManager.LayoutParams mWindowLayoutParams;
+ private OnInterceptTouchEventListener mInterceptTouchListener;
+ private BroadcastReceiver mDevicePolicyReceiver;
+ private Intent mLastCameraIntent;
+
private final ServiceConnection mPrewarmConnection = new ServiceConnection() {
@Override
@@ -128,7 +148,48 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL
}
};
- private boolean mLeftIsVoiceAssist;
+ @Override
+ public void setVisibility(int visibility) {
+ if (visibility == View.VISIBLE) {
+ if (!mBottomAreaAttached) {
+ addKeyguardBottomArea(false);
+ }
+ } else if (mBottomAreaAttached) {
+ removeKeyguardBottomArea();
+ }
+ super.setVisibility(visibility);
+ }
+
+ public void expand(boolean expand) {
+ addKeyguardBottomArea(expand);
+ }
+
+ private void addKeyguardBottomArea(boolean fullyExpand) {
+ mWindowLayoutParams.height = fullyExpand ? WindowManager.LayoutParams.MATCH_PARENT :
+ WindowManager.LayoutParams.WRAP_CONTENT;
+ if (!mBottomAreaAttached) {
+ try {
+ mWindowManager.addView(this, mWindowLayoutParams);
+ } catch (IllegalStateException e) {
+ Log.e(TAG, e.getMessage());
+ }
+ mBottomAreaAttached = true;
+ } else {
+ mWindowManager.updateViewLayout(this, mWindowLayoutParams);
+ }
+ }
+
+ private void removeKeyguardBottomArea() {
+ if (mBottomAreaAttached) {
+ try {
+ mWindowManager.removeView(this);
+ } catch (IllegalArgumentException e) {
+ Log.e(TAG, e.getMessage());
+ }
+ mBottomAreaAttached = false;
+ }
+ }
+
private AssistManager mAssistManager;
public KeyguardBottomAreaView(Context context) {
@@ -148,6 +209,23 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL
super(context, attrs, defStyleAttr, defStyleRes);
mLinearOutSlowInInterpolator =
AnimationUtils.loadInterpolator(context, android.R.interpolator.linear_out_slow_in);
+ ColorMatrix cm = new ColorMatrix();
+ cm.setSaturation(0);
+ mGrayScaleFilter = new ColorMatrixColorFilter(cm);
+ mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
+
+ mWindowLayoutParams = new WindowManager.LayoutParams();
+ mWindowLayoutParams.type = WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL;
+ mWindowLayoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL |
+ WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN |
+ WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
+ mWindowLayoutParams.privateFlags =
+ WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION;
+ mWindowLayoutParams.width = WindowManager.LayoutParams.MATCH_PARENT;
+ mWindowLayoutParams.height = WindowManager.LayoutParams.WRAP_CONTENT;
+ mWindowLayoutParams.format = PixelFormat.TRANSPARENT;
+ mWindowLayoutParams.setTitle("KeyguardBottomArea");
+ mWindowLayoutParams.gravity = Gravity.BOTTOM;
}
private AccessibilityDelegate mAccessibilityDelegate = new AccessibilityDelegate() {
@@ -158,12 +236,20 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL
if (host == mLockIcon) {
label = getResources().getString(R.string.unlock_label);
} else if (host == mCameraImageView) {
- label = getResources().getString(R.string.camera_label);
+ if (isTargetCustom(Shortcuts.RIGHT_SHORTCUT)) {
+ label = mShortcutHelper.getFriendlyNameForUri(Shortcuts.RIGHT_SHORTCUT);
+ } else {
+ label = getResources().getString(R.string.camera_label);
+ }
} else if (host == mLeftAffordanceView) {
- if (mLeftIsVoiceAssist) {
- label = getResources().getString(R.string.voice_assist_label);
+ if (isTargetCustom(Shortcuts.LEFT_SHORTCUT)) {
+ label = mShortcutHelper.getFriendlyNameForUri(Shortcuts.LEFT_SHORTCUT);
} else {
- label = getResources().getString(R.string.phone_label);
+ if (isLeftVoiceAssist()) {
+ label = getResources().getString(R.string.voice_assist_label);
+ } else {
+ label = getResources().getString(R.string.phone_label);
+ }
}
}
info.addAction(new AccessibilityAction(ACTION_CLICK, label));
@@ -197,20 +283,47 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL
mLeftAffordanceView = (KeyguardAffordanceView) findViewById(R.id.left_button);
mLockIcon = (LockIcon) findViewById(R.id.lock_icon);
mIndicationText = (TextView) findViewById(R.id.keyguard_indication_text);
+ mShortcutHelper = new LockscreenShortcutsHelper(mContext, this);
watchForCameraPolicyChanges();
updateCameraVisibility();
+ updateLeftButtonVisibility();
mUnlockMethodCache = UnlockMethodCache.getInstance(getContext());
mUnlockMethodCache.addListener(this);
mLockIcon.update();
setClipChildren(false);
setClipToPadding(false);
mPreviewInflater = new PreviewInflater(mContext, new LockPatternUtils(mContext));
- inflateCameraPreview();
mLockIcon.setOnClickListener(this);
mLockIcon.setOnLongClickListener(this);
mCameraImageView.setOnClickListener(this);
mLeftAffordanceView.setOnClickListener(this);
initAccessibility();
+ updateCustomShortcuts();
+ }
+
+ private void updateCustomShortcuts() {
+ updateLeftAffordanceIcon();
+ updateRightAffordanceIcon();
+ inflateCameraPreview();
+ }
+
+ private void updateRightAffordanceIcon() {
+ Drawable drawable;
+ String contentDescription;
+ boolean shouldGrayScale = false;
+ if (isTargetCustom(Shortcuts.RIGHT_SHORTCUT)) {
+ drawable = mShortcutHelper.getDrawableForTarget(Shortcuts.RIGHT_SHORTCUT);
+ shouldGrayScale = true;
+ contentDescription = mShortcutHelper.getFriendlyNameForUri(Shortcuts.RIGHT_SHORTCUT);
+ } else {
+ drawable = mContext.getDrawable(R.drawable.ic_camera_alt_24dp);
+ contentDescription = mContext.getString(R.string.accessibility_camera_button);
+ }
+ mCameraImageView.setImageDrawable(drawable);
+ mCameraImageView.setContentDescription(contentDescription);
+ mCameraImageView.setDefaultFilter(shouldGrayScale ? mGrayScaleFilter : null);
+ updateCameraVisibility();
+ updateLeftButtonVisibility();
}
private void initAccessibility() {
@@ -253,11 +366,13 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL
public void setPhoneStatusBar(PhoneStatusBar phoneStatusBar) {
mPhoneStatusBar = phoneStatusBar;
updateCameraVisibility(); // in case onFinishInflate() was called too early
+ updateLeftButtonVisibility();
}
public void setUserSetupComplete(boolean userSetupComplete) {
mUserSetupComplete = userSetupComplete;
updateCameraVisibility();
+ updateLeftButtonVisibility();
updateLeftAffordanceIcon();
}
@@ -278,38 +393,66 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL
KeyguardUpdateMonitor.getCurrentUser());
}
+ private void updateLeftButtonVisibility() {
+ if (mLeftAffordanceView == null) {
+ return;
+ }
+ boolean visible = mUserSetupComplete;
+ if (visible) {
+ if (isTargetCustom(Shortcuts.LEFT_SHORTCUT)) {
+ visible = !mShortcutHelper.isTargetEmpty(Shortcuts.LEFT_SHORTCUT);
+ } else {
+ // Display left shortcut
+ }
+ }
+ mLeftAffordanceView.setVisibility(visible ? View.VISIBLE : View.GONE);
+ }
+
private void updateCameraVisibility() {
if (mCameraImageView == null) {
// Things are not set up yet; reply hazy, ask again later
return;
}
- ResolveInfo resolved = resolveCameraIntent();
- boolean visible = !isCameraDisabledByDpm() && resolved != null
- && getResources().getBoolean(R.bool.config_keyguardShowCameraAffordance)
- && mUserSetupComplete;
+ boolean visible = mUserSetupComplete;
+ if (visible) {
+ if (isTargetCustom(Shortcuts.RIGHT_SHORTCUT)) {
+ visible = !mShortcutHelper.isTargetEmpty(Shortcuts.RIGHT_SHORTCUT);
+ } else {
+ ResolveInfo resolved = resolveCameraIntent();
+ visible = !isCameraDisabledByDpm() && resolved != null
+ && getResources().getBoolean(R.bool.config_keyguardShowCameraAffordance);
+ }
+ }
mCameraImageView.setVisibility(visible ? View.VISIBLE : View.GONE);
}
private void updateLeftAffordanceIcon() {
- mLeftIsVoiceAssist = canLaunchVoiceAssist();
- int drawableId;
- int contentDescription;
+ Drawable drawable;
+ String contentDescription;
+ boolean shouldGrayScale = false;
boolean visible = mUserSetupComplete;
- if (mLeftIsVoiceAssist) {
- drawableId = R.drawable.ic_mic_26dp;
- contentDescription = R.string.accessibility_voice_assist_button;
+ if (mShortcutHelper.isTargetCustom(Shortcuts.LEFT_SHORTCUT)) {
+ drawable = mShortcutHelper.getDrawableForTarget(Shortcuts.LEFT_SHORTCUT);
+ shouldGrayScale = true;
+ contentDescription = mShortcutHelper.getFriendlyNameForUri(Shortcuts.LEFT_SHORTCUT);
+ visible |= !mShortcutHelper.isTargetEmpty(Shortcuts.LEFT_SHORTCUT);
+ } else if (canLaunchVoiceAssist()) {
+ drawable = mContext.getDrawable(R.drawable.ic_mic_26dp);
+ contentDescription = mContext.getString(R.string.accessibility_voice_assist_button);
} else {
visible &= isPhoneVisible();
- drawableId = R.drawable.ic_phone_24dp;
- contentDescription = R.string.accessibility_phone_button;
+ drawable = mContext.getDrawable(R.drawable.ic_phone_24dp);
+ contentDescription = mContext.getString(R.string.accessibility_phone_button);
}
mLeftAffordanceView.setVisibility(visible ? View.VISIBLE : View.GONE);
- mLeftAffordanceView.setImageDrawable(mContext.getDrawable(drawableId));
- mLeftAffordanceView.setContentDescription(mContext.getString(contentDescription));
+ mLeftAffordanceView.setImageDrawable(drawable);
+ mLeftAffordanceView.setContentDescription(contentDescription);
+ mLeftAffordanceView.setDefaultFilter(shouldGrayScale ? mGrayScaleFilter : null);
+ updateLeftButtonVisibility();
}
public boolean isLeftVoiceAssist() {
- return mLeftIsVoiceAssist;
+ return !isTargetCustom(Shortcuts.LEFT_SHORTCUT) && canLaunchVoiceAssist();
}
private boolean isPhoneVisible() {
@@ -339,6 +482,7 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL
private void watchForCameraPolicyChanges() {
final IntentFilter filter = new IntentFilter();
filter.addAction(DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED);
+ mDevicePolicyReceiver = new DevicePolicyBroadcastReceiver();
getContext().registerReceiverAsUser(mDevicePolicyReceiver,
UserHandle.ALL, filter, null, null);
KeyguardUpdateMonitor.getInstance(mContext).registerCallback(mUpdateMonitorCallback);
@@ -425,8 +569,15 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL
}
public void launchCamera(String source) {
- final Intent intent = getCameraIntent();
- intent.putExtra(EXTRA_CAMERA_LAUNCH_SOURCE, source);
+ final Intent intent;
+ if (source.equals(CAMERA_LAUNCH_SOURCE_POWER_DOUBLE_TAP) ||
+ source.equals(CAMERA_LAUNCH_SOURCE_SCREEN_GESTURE) ||
+ !mShortcutHelper.isTargetCustom(LockscreenShortcutsHelper.Shortcuts.RIGHT_SHORTCUT)) {
+ intent = getCameraIntent();
+ } else {
+ intent = mShortcutHelper.getIntent(LockscreenShortcutsHelper.Shortcuts.RIGHT_SHORTCUT);
+ intent.putExtra(EXTRA_CAMERA_LAUNCH_SOURCE, source);
+ }
boolean wouldLaunchResolverActivity = PreviewInflater.wouldLaunchResolverActivity(
mContext, intent, KeyguardUpdateMonitor.getCurrentUser());
if (intent == SECURE_CAMERA_INTENT && !wouldLaunchResolverActivity) {
@@ -475,7 +626,10 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL
}
public void launchLeftAffordance() {
- if (mLeftIsVoiceAssist) {
+ if (mShortcutHelper.isTargetCustom(Shortcuts.LEFT_SHORTCUT)) {
+ Intent intent = mShortcutHelper.getIntent(Shortcuts.LEFT_SHORTCUT);
+ mActivityStarter.startActivity(intent, false /* dismissShade */);
+ } else if (isLeftVoiceAssist()) {
launchVoiceAssist();
} else {
launchPhone();
@@ -499,6 +653,9 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL
}
private boolean canLaunchVoiceAssist() {
+ if (mAssistManager == null) {
+ return false;
+ }
return mAssistManager.canVoiceAssistBeLaunchedFromKeyguard();
}
@@ -523,6 +680,7 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL
if (changedView == this && visibility == VISIBLE) {
mLockIcon.update();
updateCameraVisibility();
+ updateLeftButtonVisibility();
}
}
@@ -559,13 +717,27 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL
public void onUnlockMethodStateChanged() {
mLockIcon.update();
updateCameraVisibility();
+ updateLeftButtonVisibility();
}
private void inflateCameraPreview() {
- mCameraPreview = mPreviewInflater.inflatePreview(getCameraIntent());
- if (mCameraPreview != null) {
- mPreviewContainer.addView(mCameraPreview);
- mCameraPreview.setVisibility(View.INVISIBLE);
+ if (isTargetCustom(Shortcuts.RIGHT_SHORTCUT)) {
+ mPreviewContainer.removeView(mCameraPreview);
+ } else {
+ Intent cameraIntent = getCameraIntent();
+ if (!Objects.equals(cameraIntent, mLastCameraIntent)) {
+ if (mCameraPreview != null) {
+ mPreviewContainer.removeView(mCameraPreview);
+ }
+ mCameraPreview = mPreviewInflater.inflatePreview(cameraIntent);
+ if (mCameraPreview != null) {
+ mPreviewContainer.addView(mCameraPreview);
+ }
+ }
+ mLastCameraIntent = cameraIntent;
+ if (mCameraPreview != null) {
+ mCameraPreview.setVisibility(View.GONE);
+ }
}
}
@@ -574,7 +746,11 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL
if (previewBefore != null) {
mPreviewContainer.removeView(previewBefore);
}
- if (mLeftIsVoiceAssist) {
+ if (isTargetCustom(Shortcuts.LEFT_SHORTCUT)) {
+ // Custom shortcuts don't support previews
+ return;
+ }
+ if (isLeftVoiceAssist()) {
mLeftPreview = mPreviewInflater.inflatePreviewFromService(
mAssistManager.getVoiceInteractorComponentName());
} else {
@@ -582,7 +758,7 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL
}
if (mLeftPreview != null) {
mPreviewContainer.addView(mLeftPreview);
- mLeftPreview.setVisibility(View.INVISIBLE);
+ mLeftPreview.setVisibility(View.GONE);
}
}
@@ -615,13 +791,18 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL
.setDuration(DOZE_ANIMATION_ELEMENT_DURATION);
}
- private final BroadcastReceiver mDevicePolicyReceiver = new BroadcastReceiver() {
+ public void cleanup() {
+ removeKeyguardBottomArea();
+ }
+
+ private final class DevicePolicyBroadcastReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
post(new Runnable() {
@Override
public void run() {
updateCameraVisibility();
+ updateLeftButtonVisibility();
}
});
}
@@ -632,6 +813,7 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL
@Override
public void onUserSwitchComplete(int userId) {
updateCameraVisibility();
+ updateLeftButtonVisibility();
}
@Override
@@ -684,4 +866,92 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL
updateLeftAffordanceIcon();
updateLeftPreview();
}
+
+ private String getIndexHint(LockscreenShortcutsHelper.Shortcuts shortcut) {
+ if (mShortcutHelper.isTargetCustom(shortcut)) {
+ boolean isRtl = getLayoutDirection() == LAYOUT_DIRECTION_RTL;
+ String label = mShortcutHelper.getFriendlyNameForUri(shortcut);
+ int resId = 0;
+ switch (shortcut) {
+ case LEFT_SHORTCUT:
+ resId = isRtl ? R.string.right_shortcut_hint : R.string.left_shortcut_hint;
+ break;
+ case RIGHT_SHORTCUT:
+ resId = isRtl ? R.string.left_shortcut_hint : R.string.right_shortcut_hint;
+ break;
+ }
+ return mContext.getString(resId, label);
+ } else {
+ return null;
+ }
+ }
+
+ public String getLeftHint() {
+ String label = getIndexHint(LockscreenShortcutsHelper.Shortcuts.LEFT_SHORTCUT);
+ if (label == null) {
+ if (isLeftVoiceAssist()) {
+ label = mContext.getString(R.string.voice_hint);
+ } else {
+ label = mContext.getString(R.string.phone_hint);
+ }
+ }
+ return label;
+ }
+
+ public String getRightHint() {
+ String label = getIndexHint(LockscreenShortcutsHelper.Shortcuts.RIGHT_SHORTCUT);
+ if (label == null) {
+ label = mContext.getString(R.string.camera_hint);
+ }
+ return label;
+ }
+
+ public boolean isTargetCustom(LockscreenShortcutsHelper.Shortcuts shortcut) {
+ return mShortcutHelper.isTargetCustom(shortcut);
+ }
+
+ @Override
+ public void onChange() {
+ updateCustomShortcuts();
+ }
+
+ @Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+ if (mAccessibilityController != null) {
+ mAccessibilityController.addStateChangedCallback(this);
+ }
+ mShortcutHelper.registerAndFetchTargets();
+ updateCustomShortcuts();
+ mUnlockMethodCache.addListener(this);
+ watchForCameraPolicyChanges();
+ }
+
+ @Override
+ protected void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+ mAccessibilityController.removeStateChangedCallback(this);
+ if (mDevicePolicyReceiver != null) {
+ mContext.unregisterReceiver(mDevicePolicyReceiver);
+ mDevicePolicyReceiver = null;
+ }
+ mShortcutHelper.cleanup();
+ mUnlockMethodCache.removeListener(this);
+ }
+
+ public interface OnInterceptTouchEventListener {
+ boolean onInterceptTouchEvent(MotionEvent e);
+ }
+
+ public void setOnInterceptTouchListener(OnInterceptTouchEventListener listener) {
+ mInterceptTouchListener = listener;
+ }
+
+ @Override
+ public boolean onInterceptTouchEvent(MotionEvent ev) {
+ if (mInterceptTouchListener != null) {
+ return mInterceptTouchListener.onInterceptTouchEvent(ev);
+ }
+ return super.onInterceptTouchEvent(ev);
+ }
}
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 893b352..d992b17 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
@@ -32,6 +32,8 @@ import com.android.keyguard.R;
import com.android.keyguard.ViewMediatorCallback;
import com.android.systemui.DejankUtils;
+import org.cyanogenmod.internal.util.CmLockPatternUtils;
+
import static com.android.keyguard.KeyguardHostView.OnDismissAction;
import static com.android.keyguard.KeyguardSecurityModel.SecurityMode;
@@ -40,15 +42,21 @@ import static com.android.keyguard.KeyguardSecurityModel.SecurityMode;
*/
public class KeyguardBouncer {
+ public static final int UNLOCK_SEQUENCE_DEFAULT = 0;
+ public static final int UNLOCK_SEQUENCE_BOUNCER_FIRST = 1;
+ public static final int UNLOCK_SEQUENCE_FORCE_BOUNCER = 2;
+
private Context mContext;
private ViewMediatorCallback mCallback;
private LockPatternUtils mLockPatternUtils;
+ private CmLockPatternUtils mCmLockPatternUtils;
private ViewGroup mContainer;
private StatusBarWindowManager mWindowManager;
private KeyguardHostView mKeyguardView;
private ViewGroup mRoot;
private boolean mShowingSoon;
private int mBouncerPromptReason;
+ private PhoneStatusBar mPhoneStatusBar;
private KeyguardUpdateMonitorCallback mUpdateMonitorCallback =
new KeyguardUpdateMonitorCallback() {
@Override
@@ -59,12 +67,14 @@ public class KeyguardBouncer {
public KeyguardBouncer(Context context, ViewMediatorCallback callback,
LockPatternUtils lockPatternUtils, StatusBarWindowManager windowManager,
- ViewGroup container) {
+ ViewGroup container, PhoneStatusBar phoneStatusBar) {
mContext = context;
mCallback = callback;
mLockPatternUtils = lockPatternUtils;
mContainer = container;
mWindowManager = windowManager;
+ mCmLockPatternUtils = new CmLockPatternUtils(mContext);
+ mPhoneStatusBar = phoneStatusBar;
KeyguardUpdateMonitor.getInstance(mContext).registerCallback(mUpdateMonitorCallback);
}
@@ -78,7 +88,15 @@ public class KeyguardBouncer {
if (mRoot.getVisibility() == View.VISIBLE || mShowingSoon) {
return;
}
-
+ // ensure external keyguard view does not have focus
+ mPhoneStatusBar.unfocusKeyguardExternalView();
+ mPhoneStatusBar.getScrimController().forceHideScrims(false);
+ // Don't hide bottom area if we are in the middle of a affordance
+ // launch transition, since once the animation is finished, NPV
+ // will take care of setting it invisible.
+ if (!mPhoneStatusBar.mNotificationPanel.isLaunchTransitionRunning()) {
+ mPhoneStatusBar.mKeyguardBottomArea.setVisibility(View.GONE);
+ }
// 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()) {
@@ -195,7 +213,7 @@ public class KeyguardBouncer {
mRoot.setSystemUiVisibility(View.STATUS_BAR_DISABLE_HOME);
}
- private void removeView() {
+ void removeView() {
if (mRoot != null && mRoot.getParent() == mContainer) {
mContainer.removeView(mRoot);
mRoot = null;
@@ -207,28 +225,42 @@ public class KeyguardBouncer {
}
/**
- * @return True if and only if the security method should be shown before showing the
- * notifications on Keyguard, like SIM PIN/PUK.
+ * @return Whether the bouncer should be shown first, this could be because of SIM PIN/PUK
+ * or it just could be chosen to be shown first.
*/
- public boolean needsFullscreenBouncer() {
+ public int needsFullscreenBouncer() {
ensureView();
if (mKeyguardView != null) {
SecurityMode mode = mKeyguardView.getSecurityMode();
- return mode == SecurityMode.SimPin || mode == SecurityMode.SimPuk;
+ if (mode == SecurityMode.SimPin || mode == SecurityMode.SimPuk)
+ return UNLOCK_SEQUENCE_FORCE_BOUNCER;
+ // "Bouncer first" mode currently only available to some security methods.
+ else if ((mode == SecurityMode.Pattern || mode == SecurityMode.Password
+ || mode == SecurityMode.PIN) && (mLockPatternUtils != null &&
+ mCmLockPatternUtils.shouldPassToSecurityView(
+ KeyguardUpdateMonitor.getCurrentUser())))
+ return UNLOCK_SEQUENCE_BOUNCER_FIRST;
}
- return false;
+ return UNLOCK_SEQUENCE_DEFAULT;
}
/**
* Like {@link #needsFullscreenBouncer}, but uses the currently visible security method, which
* makes this method much faster.
*/
- public boolean isFullscreenBouncer() {
+ public int isFullscreenBouncer() {
if (mKeyguardView != null) {
SecurityMode mode = mKeyguardView.getCurrentSecurityMode();
- return mode == SecurityMode.SimPin || mode == SecurityMode.SimPuk;
+ if (mode == SecurityMode.SimPin || mode == SecurityMode.SimPuk)
+ return UNLOCK_SEQUENCE_FORCE_BOUNCER;
+ // "Bouncer first" mode currently only available to some security methods.
+ else if ((mode == SecurityMode.Pattern || mode == SecurityMode.Password
+ || mode == SecurityMode.PIN) && (mLockPatternUtils != null &&
+ mCmLockPatternUtils.shouldPassToSecurityView(
+ KeyguardUpdateMonitor.getCurrentUser())))
+ return UNLOCK_SEQUENCE_BOUNCER_FIRST;
}
- return false;
+ return UNLOCK_SEQUENCE_DEFAULT;
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
index b93fc76..ec307de 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
@@ -1,6 +1,6 @@
/*
* Copyright (C) 2014 The Android Open Source Project
- *
+ * Copyright (C) 2016 The CyanogenMod 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
@@ -29,37 +29,37 @@ import android.widget.ImageView;
import android.widget.RelativeLayout;
import android.widget.TextView;
+import com.android.systemui.BatteryLevelTextView;
import com.android.systemui.BatteryMeterView;
+import com.android.systemui.DockBatteryMeterView;
import com.android.systemui.R;
import com.android.systemui.statusbar.policy.BatteryController;
+import com.android.systemui.statusbar.policy.DockBatteryController;
import com.android.systemui.statusbar.policy.KeyguardUserSwitcher;
import com.android.systemui.statusbar.policy.UserInfoController;
import com.android.systemui.statusbar.policy.UserSwitcherController;
-import java.text.NumberFormat;
-
/**
* The header group on Keyguard.
*/
-public class KeyguardStatusBarView extends RelativeLayout
- implements BatteryController.BatteryStateChangeCallback {
+public class KeyguardStatusBarView extends RelativeLayout {
- private boolean mBatteryCharging;
private boolean mKeyguardUserSwitcherShowing;
- private boolean mBatteryListening;
private TextView mCarrierLabel;
private View mSystemIconsSuperContainer;
private MultiUserSwitch mMultiUserSwitch;
private ImageView mMultiUserAvatar;
- private TextView mBatteryLevel;
+ private BatteryLevelTextView mBatteryLevel;
+ private BatteryLevelTextView mDockBatteryLevel;
- private BatteryController mBatteryController;
private KeyguardUserSwitcher mKeyguardUserSwitcher;
private int mSystemIconsSwitcherHiddenExpandedMargin;
private Interpolator mFastOutSlowInInterpolator;
+ private UserInfoController mUserInfoController;
+
public KeyguardStatusBarView(Context context, AttributeSet attrs) {
super(context, attrs);
}
@@ -70,12 +70,14 @@ public class KeyguardStatusBarView extends RelativeLayout
mSystemIconsSuperContainer = findViewById(R.id.system_icons_super_container);
mMultiUserSwitch = (MultiUserSwitch) findViewById(R.id.multi_user_switch);
mMultiUserAvatar = (ImageView) findViewById(R.id.multi_user_avatar);
- mBatteryLevel = (TextView) findViewById(R.id.battery_level);
+ mBatteryLevel = (BatteryLevelTextView) findViewById(R.id.battery_level_text);
+ mDockBatteryLevel = (BatteryLevelTextView) findViewById(R.id.dock_battery_level_text);
mCarrierLabel = (TextView) findViewById(R.id.keyguard_carrier_text);
loadDimens();
mFastOutSlowInInterpolator = AnimationUtils.loadInterpolator(getContext(),
android.R.interpolator.fast_out_slow_in);
updateUserSwitcher();
+ updateVisibilities();
}
@Override
@@ -86,8 +88,14 @@ public class KeyguardStatusBarView extends RelativeLayout
mCarrierLabel.setTextSize(TypedValue.COMPLEX_UNIT_PX,
getResources().getDimensionPixelSize(
com.android.internal.R.dimen.text_size_small_material));
- mBatteryLevel.setTextSize(TypedValue.COMPLEX_UNIT_PX,
- getResources().getDimensionPixelSize(R.dimen.battery_level_text_size));
+ }
+
+ @Override
+ protected void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+ if (mUserInfoController != null) {
+ mUserInfoController.removeListener(mUserInfoChangedListener);
+ }
}
private void loadDimens() {
@@ -104,7 +112,10 @@ public class KeyguardStatusBarView extends RelativeLayout
} else if (mMultiUserSwitch.getParent() == this && mKeyguardUserSwitcherShowing) {
removeView(mMultiUserSwitch);
}
- mBatteryLevel.setVisibility(mBatteryCharging ? View.VISIBLE : View.GONE);
+ mBatteryLevel.setVisibility(View.VISIBLE);
+ if (mDockBatteryLevel != null) {
+ mDockBatteryLevel.setVisibility(View.VISIBLE);
+ }
}
private void updateSystemIconsLayoutParams() {
@@ -117,18 +128,6 @@ public class KeyguardStatusBarView extends RelativeLayout
}
}
- public void setListening(boolean listening) {
- if (listening == mBatteryListening) {
- return;
- }
- mBatteryListening = listening;
- if (mBatteryListening) {
- mBatteryController.addStateChangedCallback(this);
- } else {
- mBatteryController.removeStateChangedCallback(this);
- }
- }
-
private void updateUserSwitcher() {
boolean keyguardSwitcherAvailable = mKeyguardUserSwitcher != null;
mMultiUserSwitch.setClickable(keyguardSwitcherAvailable);
@@ -137,37 +136,43 @@ public class KeyguardStatusBarView extends RelativeLayout
}
public void setBatteryController(BatteryController batteryController) {
- mBatteryController = batteryController;
- ((BatteryMeterView) findViewById(R.id.battery)).setBatteryController(batteryController);
+ BatteryMeterView v = ((BatteryMeterView) findViewById(R.id.battery));
+ v.setBatteryStateRegistar(batteryController);
+ v.setBatteryController(batteryController);
+ mBatteryLevel.setBatteryStateRegistar(batteryController);
}
- public void setUserSwitcherController(UserSwitcherController controller) {
- mMultiUserSwitch.setUserSwitcherController(controller);
+ public void setDockBatteryController(DockBatteryController dockBatteryController) {
+ DockBatteryMeterView v = ((DockBatteryMeterView) findViewById(R.id.dock_battery));
+ if (dockBatteryController != null) {
+ v.setBatteryStateRegistar(dockBatteryController);
+ mDockBatteryLevel.setBatteryStateRegistar(dockBatteryController);
+ } else {
+ if (v != null ) {
+ removeView(v);
+ }
+ if (mDockBatteryLevel != null) {
+ removeView(mDockBatteryLevel);
+ mDockBatteryLevel = null;
+ }
+ }
}
- public void setUserInfoController(UserInfoController userInfoController) {
- userInfoController.addListener(new UserInfoController.OnUserInfoChangedListener() {
- @Override
- public void onUserInfoChanged(String name, Drawable picture) {
- mMultiUserAvatar.setImageDrawable(picture);
- }
- });
+ public void setUserSwitcherController(UserSwitcherController controller) {
+ mMultiUserSwitch.setUserSwitcherController(controller);
}
- @Override
- public void onBatteryLevelChanged(int level, boolean pluggedIn, boolean charging) {
- String percentage = NumberFormat.getPercentInstance().format((double) level / 100.0);
- mBatteryLevel.setText(percentage);
- boolean changed = mBatteryCharging != charging;
- mBatteryCharging = charging;
- if (changed) {
- updateVisibilities();
+ private UserInfoController.OnUserInfoChangedListener mUserInfoChangedListener =
+ new UserInfoController.OnUserInfoChangedListener() {
+ @Override
+ public void onUserInfoChanged(String name, Drawable picture) {
+ mMultiUserAvatar.setImageDrawable(picture);
}
- }
+ };
- @Override
- public void onPowerSaveChanged() {
- // could not care less
+ public void setUserInfoController(UserInfoController userInfoController) {
+ mUserInfoController = userInfoController;
+ userInfoController.addListener(mUserInfoChangedListener);
}
public void setKeyguardUserSwitcher(KeyguardUserSwitcher keyguardUserSwitcher) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockIcon.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockIcon.java
index 8e58d14..cf39655 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockIcon.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockIcon.java
@@ -61,7 +61,8 @@ public class LockIcon extends KeyguardAffordanceView {
@Override
protected void onVisibilityChanged(View changedView, int visibility) {
super.onVisibilityChanged(changedView, visibility);
- if (isShown()) {
+ if (isShown() &&
+ KeyguardUpdateMonitor.getInstance(mContext).isDeviceInteractive()) {
mTrustDrawable.start();
} else {
mTrustDrawable.stop();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserSwitch.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserSwitch.java
index e70d146..e89cd3f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserSwitch.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserSwitch.java
@@ -30,6 +30,7 @@ import android.widget.FrameLayout;
import com.android.systemui.R;
import com.android.systemui.qs.QSPanel;
import com.android.systemui.statusbar.policy.KeyguardUserSwitcher;
+import com.android.systemui.statusbar.policy.UserInfoController;
import com.android.systemui.statusbar.policy.UserSwitcherController;
/**
@@ -37,16 +38,20 @@ import com.android.systemui.statusbar.policy.UserSwitcherController;
*/
public class MultiUserSwitch extends FrameLayout implements View.OnClickListener {
+ public static final String INTENT_EXTRA_NEW_LOCAL_PROFILE = "newLocalProfile";
+
private QSPanel mQsPanel;
private KeyguardUserSwitcher mKeyguardUserSwitcher;
private boolean mKeyguardMode;
private UserSwitcherController.BaseUserAdapter mUserListener;
final UserManager mUserManager;
+ private ActivityStarter mActivityStarter;
private final int[] mTmpInt2 = new int[2];
private UserSwitcherController mUserSwitcherController;
+ private UserInfoController mUserInfoController;
public MultiUserSwitch(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -101,6 +106,10 @@ public class MultiUserSwitch extends FrameLayout implements View.OnClickListener
}
}
+ public void setActivityStarter(ActivityStarter activityStarter) {
+ mActivityStarter = activityStarter;
+ }
+
@Override
public void onClick(View v) {
if (UserSwitcherController.isUserSwitcherAvailable(mUserManager)) {
@@ -120,10 +129,20 @@ public class MultiUserSwitch extends FrameLayout implements View.OnClickListener
mTmpInt2);
}
} else {
- Intent intent = ContactsContract.QuickContact.composeQuickContactsIntent(
- getContext(), v, ContactsContract.Profile.CONTENT_URI,
- ContactsContract.QuickContact.MODE_LARGE, null);
- getContext().startActivityAsUser(intent, new UserHandle(UserHandle.USER_CURRENT));
+ Intent intent;
+ if (mUserInfoController == null || mUserInfoController.isProfileSetup()) {
+ intent = ContactsContract.QuickContact.composeQuickContactsIntent(
+ getContext(), v, ContactsContract.Profile.CONTENT_URI,
+ ContactsContract.QuickContact.MODE_LARGE, null);
+ } else {
+ intent = new Intent(Intent.ACTION_INSERT, ContactsContract.Contacts.CONTENT_URI);
+ intent.putExtra(INTENT_EXTRA_NEW_LOCAL_PROFILE, true);
+ }
+ if (mActivityStarter != null) {
+ mActivityStarter.startActivity(intent, true /* dismissShade */);
+ } else {
+ getContext().startActivityAsUser(intent, new UserHandle(UserHandle.USER_CURRENT));
+ }
}
}
@@ -171,4 +190,7 @@ public class MultiUserSwitch extends FrameLayout implements View.OnClickListener
return false;
}
+ public void setUserInfoController(UserInfoController userInfoController) {
+ mUserInfoController = userInfoController;
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavBarInsetLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavBarInsetLayout.java
new file mode 100644
index 0000000..2028132
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavBarInsetLayout.java
@@ -0,0 +1,218 @@
+/*
+ * Copyright (C) 2016 The CyanogenMod 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.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.PorterDuff;
+import android.graphics.PorterDuffXfermode;
+import android.graphics.Rect;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.UserHandle;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.WindowManager;
+import android.view.WindowManagerGlobal;
+import android.widget.FrameLayout;
+import com.android.systemui.R;
+import com.android.systemui.cm.UserContentObserver;
+import com.android.systemui.statusbar.BaseStatusBar;
+import cyanogenmod.providers.CMSettings;
+
+public class NavBarInsetLayout extends FrameLayout {
+ public static final String TAG = "NavBarInsetLayout";
+ public static final boolean DEBUG = BaseStatusBar.DEBUG;
+
+ boolean mLeftInsetMode = false;
+
+ private int mLeftInset = 0;
+ private int mRightInset = 0;
+
+ private final Paint mTransparentSrcPaint = new Paint();
+
+ private SettingsObserver mSettingsObserver;
+
+ @Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+ mSettingsObserver.observe();
+ }
+
+ @Override
+ protected void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+ mSettingsObserver.unobserve();
+ }
+
+ public NavBarInsetLayout(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ setMotionEventSplittingEnabled(false);
+ mTransparentSrcPaint.setColor(0);
+ mTransparentSrcPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC));
+
+ mSettingsObserver = new SettingsObserver(new Handler());
+ }
+
+ @Override
+ protected boolean fitSystemWindows(Rect insets) {
+ if (getFitsSystemWindows()) {
+ boolean paddingChanged;
+
+ if (mLeftInsetMode) {
+ paddingChanged = insets.right != getPaddingRight()
+ || insets.top != getPaddingTop()
+ || insets.bottom != getPaddingBottom();
+
+ if (insets.left != mLeftInset) {
+ mLeftInset = insets.left;
+ applyMargins();
+ }
+ } else {
+ paddingChanged = insets.left != getPaddingLeft()
+ || insets.top != getPaddingTop()
+ || insets.bottom != getPaddingBottom();
+
+ if (insets.right != mRightInset) {
+ mRightInset = insets.right;
+ applyMargins();
+ }
+ }
+
+ // Drop top inset, apply left inset and pass through bottom inset.
+ if (paddingChanged) {
+ setPadding(mLeftInsetMode ? 0 : insets.left,
+ 0,
+ mLeftInsetMode ? insets.right : 0,
+ 0);
+ }
+ insets.left = 0;
+ insets.top = 0;
+ insets.right = 0;
+ } else {
+ boolean applyMargins = false;
+ if (mLeftInset != 0) {
+ mLeftInset = 0;
+ applyMargins = true;
+ }
+ if (mRightInset != 0) {
+ mRightInset = 0;
+ applyMargins = true;
+ }
+ if (applyMargins) {
+ applyMargins();
+ }
+ boolean changed = getPaddingLeft() != 0
+ || getPaddingRight() != 0
+ || getPaddingTop() != 0
+ || getPaddingBottom() != 0;
+ if (changed) {
+ setPadding(0, 0, 0, 0);
+ }
+ insets.top = 0;
+ }
+ return false;
+ }
+
+ private void applyMargins() {
+ final int N = getChildCount();
+ for (int i = 0; i < N; i++) {
+ View child = getChildAt(i);
+ if (child.getLayoutParams() instanceof InsetLayoutParams) {
+ InsetLayoutParams lp = (InsetLayoutParams) child.getLayoutParams();
+ if (!lp.ignoreRightInset) {
+ if (mLeftInsetMode && lp.leftMargin != mLeftInset) {
+ lp.leftMargin = mLeftInset;
+ if (lp.rightMargin != 0) {
+ lp.rightMargin = 0;
+ }
+ } else if (lp.rightMargin != mRightInset) {
+ lp.rightMargin = mRightInset;
+ if (lp.leftMargin != 0) {
+ lp.leftMargin = 0;
+ }
+ }
+ child.requestLayout();
+ }
+ }
+ }
+ }
+
+ @Override
+ public FrameLayout.LayoutParams generateLayoutParams(AttributeSet attrs) {
+ return new InsetLayoutParams(getContext(), attrs);
+ }
+
+ @Override
+ protected FrameLayout.LayoutParams generateDefaultLayoutParams() {
+ return new InsetLayoutParams(InsetLayoutParams.MATCH_PARENT,
+ InsetLayoutParams.MATCH_PARENT);
+ }
+
+ private class SettingsObserver extends UserContentObserver {
+
+ public SettingsObserver(Handler handler) {
+ super(handler);
+ }
+
+ @Override
+ protected void observe() {
+ super.observe();
+ mContext.getContentResolver().registerContentObserver(
+ CMSettings.System.getUriFor(CMSettings.System.NAVBAR_LEFT_IN_LANDSCAPE), false,
+ this, UserHandle.USER_CURRENT);
+ update();
+ }
+
+ @Override
+ protected void unobserve() {
+ super.unobserve();
+ mContext.getContentResolver().unregisterContentObserver(this);
+ }
+
+ @Override
+ protected void update() {
+ boolean before = mLeftInsetMode;
+ mLeftInsetMode = CMSettings.System.getIntForUser(mContext.getContentResolver(),
+ CMSettings.System.NAVBAR_LEFT_IN_LANDSCAPE, 0, UserHandle.USER_CURRENT) == 1;
+ if (mLeftInsetMode != before) {
+ applyMargins();
+ }
+ }
+ }
+
+ public static class InsetLayoutParams extends FrameLayout.LayoutParams {
+
+ public boolean ignoreRightInset;
+
+ public InsetLayoutParams(int width, int height) {
+ super(width, height);
+ }
+
+ public InsetLayoutParams(Context c, AttributeSet attrs) {
+ super(c, attrs);
+
+ TypedArray a = c.obtainStyledAttributes(attrs, R.styleable.StatusBarWindowView_Layout);
+ ignoreRightInset = a.getBoolean(
+ R.styleable.StatusBarWindowView_Layout_ignoreRightInset, false);
+ a.recycle();
+ }
+ }
+}
+
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavbarEditor.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavbarEditor.java
new file mode 100644
index 0000000..7b4d7f9
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavbarEditor.java
@@ -0,0 +1,580 @@
+/*
+ * Copyright (C) 2015 The CyanogenMod Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.phone;
+
+import android.app.AlertDialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.res.Resources;
+import android.graphics.PorterDuff;
+import android.graphics.PorterDuffColorFilter;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.util.DisplayMetrics;
+import android.view.DisplayInfo;
+import android.view.HapticFeedbackConstants;
+import android.view.KeyEvent;
+import android.view.LayoutInflater;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewConfiguration;
+import android.view.ViewGroup;
+import android.view.WindowManager;
+import android.widget.ArrayAdapter;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import com.android.internal.util.ArrayUtils;
+import com.android.systemui.R;
+import com.android.systemui.statusbar.policy.KeyButtonView;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+import cyanogenmod.providers.CMSettings;
+
+/**
+ * Handles the editing of the navigation bar
+ * @author Danesh M
+ * @hide
+ */
+public class NavbarEditor implements View.OnTouchListener {
+ /**
+ * Holds reference to all assignable button ids
+ */
+ private static final int[] BUTTON_IDS =
+ { R.id.one, R.id.two, R.id.three, R.id.four, R.id.five, R.id.six };
+
+ /**
+ * Subset of BUTTON_IDS, to differentiate small/side buttons
+ * since they can be assigned additional functionality.
+ */
+ private static final int[] SMALL_BUTTON_IDS = { R.id.one, R.id.six };
+
+ // holds the button views in the order they currently appear on screen
+ private final ArrayList<KeyButtonView> mButtonViews;
+ private final boolean mRtl;
+
+ private Context mContext;
+ private static Boolean sIsDevicePhone = null;
+ private boolean mInEditMode = false;
+
+ // Holds reference to the parent/root of the inflated view
+ private View mParent;
+
+ // Button chooser dialog
+ private AlertDialog mDialog;
+
+ // true == we're in landscape mode
+ private boolean mVertical;
+ // true == we're currently checking for long press
+ private boolean mLongPressed;
+ // start point of the current drag operation
+ private float mDragOrigin;
+
+ // just to avoid reallocations
+ private static final int[] sLocation = new int[2];
+
+ private Resources mResources;
+
+ /**
+ * Longpress runnable to assign buttons in edit mode
+ */
+ private Runnable mCheckLongPress = new Runnable() {
+ public void run() {
+ if (mInEditMode) {
+ mLongPressed = true;
+ mParent.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY);
+ }
+ }
+ };
+
+ //Available buttons
+ public static final ButtonInfo NAVBAR_EMPTY = new ButtonInfo("empty",
+ R.string.navbar_empty_button, R.string.accessibility_clear_all,
+ 0, R.drawable.ic_sysbar_add,
+ R.drawable.ic_sysbar_add_land, R.drawable.ic_sysbar_add_side);
+ public static final ButtonInfo NAVBAR_HOME = new ButtonInfo("home",
+ R.string.navbar_home_button, R.string.accessibility_home,
+ KeyEvent.KEYCODE_HOME, R.drawable.ic_sysbar_home,
+ R.drawable.ic_sysbar_home, R.drawable.ic_sysbar_home);
+ public static final ButtonInfo NAVBAR_BACK = new ButtonInfo("back",
+ R.string.navbar_back_button, R.string.accessibility_back,
+ KeyEvent.KEYCODE_BACK, R.drawable.ic_sysbar_back,
+ R.drawable.ic_sysbar_back, R.drawable.ic_sysbar_back_side);
+ public static final ButtonInfo NAVBAR_SEARCH = new ButtonInfo("search",
+ R.string.navbar_search_button, R.string.accessibility_back,
+ KeyEvent.KEYCODE_SEARCH, R.drawable.ic_sysbar_search,
+ R.drawable.ic_sysbar_search_land, R.drawable.ic_sysbar_search_side);
+ public static final ButtonInfo NAVBAR_RECENT = new ButtonInfo("recent",
+ R.string.navbar_recent_button, R.string.accessibility_recent,
+ 0, R.drawable.ic_sysbar_recent,
+ R.drawable.ic_sysbar_recent, R.drawable.ic_sysbar_recent_side);
+ public static final ButtonInfo NAVBAR_CONDITIONAL_MENU = new ButtonInfo("menu0",
+ R.string.navbar_menu_conditional_button, R.string.accessibility_menu,
+ KeyEvent.KEYCODE_MENU, R.drawable.ic_sysbar_menu,
+ R.drawable.ic_sysbar_menu, R.drawable.ic_sysbar_menu);
+ public static final ButtonInfo NAVBAR_ALWAYS_MENU = new ButtonInfo("menu1",
+ R.string.navbar_menu_always_button, R.string.accessibility_menu,
+ KeyEvent.KEYCODE_MENU, R.drawable.ic_sysbar_menu,
+ R.drawable.ic_sysbar_menu, R.drawable.ic_sysbar_menu);
+ public static final ButtonInfo NAVBAR_MENU_BIG = new ButtonInfo("menu2",
+ R.string.navbar_menu_big_button, R.string.accessibility_menu,
+ KeyEvent.KEYCODE_MENU, R.drawable.ic_sysbar_menu_big,
+ R.drawable.ic_sysbar_menu_big_land, 0);
+ public static final ButtonInfo NAVBAR_DPAD_LEFT = new ButtonInfo("dpad_left",
+ 0, R.string.accessibility_dpad_left,
+ KeyEvent.KEYCODE_DPAD_LEFT, 0,
+ 0, R.drawable.ic_sysbar_ime_left);
+ public static final ButtonInfo NAVBAR_DPAD_RIGHT = new ButtonInfo("dpad_right",
+ 0, R.string.accessibility_dpad_right,
+ KeyEvent.KEYCODE_DPAD_RIGHT, 0,
+ 0, R.drawable.ic_sysbar_ime_right);
+
+ private static final ButtonInfo[] ALL_BUTTONS = new ButtonInfo[] {
+ NAVBAR_EMPTY, NAVBAR_HOME, NAVBAR_BACK, NAVBAR_SEARCH,
+ NAVBAR_RECENT, NAVBAR_CONDITIONAL_MENU, NAVBAR_ALWAYS_MENU, NAVBAR_MENU_BIG
+ };
+
+ private static final String DEFAULT_SETTING_STRING = "empty|back|home|recent|empty|menu0";
+
+ public NavbarEditor (View parent, boolean orientation, boolean isRtl, Resources res) {
+ mContext = parent.getContext();
+ mParent = parent;
+ mVertical = orientation;
+ mRtl = isRtl;
+ mResources = res;
+
+ mButtonViews = new ArrayList<KeyButtonView>();
+
+ KeyButtonView dpadLeft = (KeyButtonView) mParent.findViewById(R.id.dpad_left);
+ dpadLeft.setInfo(NAVBAR_DPAD_LEFT, orientation, true);
+ mButtonViews.add(dpadLeft);
+
+ for (int id : BUTTON_IDS) {
+ mButtonViews.add((KeyButtonView) mParent.findViewById(id));
+ }
+
+ KeyButtonView dpadRight = (KeyButtonView) mParent.findViewById(R.id.dpad_right);
+ dpadRight.setInfo(NAVBAR_DPAD_RIGHT, orientation, true);
+ mButtonViews.add(dpadRight);
+ }
+
+ public void setEditMode(boolean editMode) {
+ mInEditMode = editMode;
+ for (Integer id : BUTTON_IDS) {
+ KeyButtonView button = (KeyButtonView) mParent.findViewById(id);
+ if (button != null) {
+ button.setEditMode(editMode);
+ button.setOnTouchListener(editMode ? this : null);
+ }
+ }
+ if (!editMode && mDialog != null && mDialog.isShowing()) {
+ mDialog.dismiss();
+ }
+ }
+
+ public static boolean isDevicePhone(Context context) {
+ if (sIsDevicePhone == null) {
+ WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
+ DisplayInfo outDisplayInfo = new DisplayInfo();
+
+ wm.getDefaultDisplay().getDisplayInfo(outDisplayInfo);
+
+ int shortSize = Math.min(outDisplayInfo.logicalHeight, outDisplayInfo.logicalWidth);
+ int shortSizeDp = shortSize * DisplayMetrics.DENSITY_DEFAULT / outDisplayInfo.logicalDensityDpi;
+
+ // 0-599dp: "phone" UI with a separate status & navigation bar
+ sIsDevicePhone = shortSizeDp < 600;
+ }
+
+ return sIsDevicePhone;
+ }
+
+ /**
+ * Find intersecting view in mButtonViews
+ * @param pos - pointer location
+ * @param v - view being dragged
+ * @return intersecting view or null
+ */
+ private View findInterceptingView(float pos, View v) {
+ for (KeyButtonView otherView : mButtonViews) {
+ if (otherView == v) {
+ continue;
+ }
+
+ if (ArrayUtils.contains(SMALL_BUTTON_IDS, otherView.getId())) {
+ continue;
+ }
+
+ otherView.getLocationOnScreen(sLocation);
+ float otherPos = sLocation[mVertical ? 1 : 0];
+ float otherDimension = mVertical ? v.getHeight() : v.getWidth();
+
+ if (pos > (otherPos + otherDimension / 4) && pos < (otherPos + otherDimension)) {
+ return otherView;
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public boolean onTouch(final View view, MotionEvent event) {
+ if (!mInEditMode || (mDialog != null && mDialog.isShowing())) {
+ return false;
+ }
+
+ if (event.getAction() == MotionEvent.ACTION_DOWN) {
+ view.setPressed(true);
+ view.getLocationOnScreen(sLocation);
+ mDragOrigin = sLocation[mVertical ? 1 : 0];
+ view.postDelayed(mCheckLongPress, ViewConfiguration.getLongPressTimeout());
+ } else if (event.getAction() == MotionEvent.ACTION_MOVE) {
+ view.setPressed(false);
+
+ if (!mLongPressed || ArrayUtils.contains(SMALL_BUTTON_IDS, view.getId())) {
+ return false;
+ }
+
+ ViewGroup viewParent = (ViewGroup) view.getParent();
+ float pos = mVertical ? event.getRawY() : event.getRawX();
+ float buttonSize = mVertical ? view.getHeight() : view.getWidth();
+ float min = mVertical ? viewParent.getTop() : (viewParent.getLeft() - buttonSize / 2);
+ float max = mVertical ? (viewParent.getTop() + viewParent.getHeight())
+ : (viewParent.getLeft() + viewParent.getWidth());
+
+ // Prevents user from dragging view outside of bounds
+ if (pos < min || pos > max) {
+ return false;
+ }
+ if (!mVertical) {
+ view.setX(pos - viewParent.getLeft() - buttonSize / 2);
+ } else {
+ view.setY(pos - viewParent.getTop() - buttonSize / 2);
+ }
+ View affectedView = findInterceptingView(pos, view);
+ if (affectedView == null) {
+ return false;
+ }
+ switchId(affectedView, view);
+ } else if (event.getAction() == MotionEvent.ACTION_UP
+ || event.getAction() == MotionEvent.ACTION_CANCEL) {
+ view.setPressed(false);
+ view.removeCallbacks(mCheckLongPress);
+
+ if (!mLongPressed && !view.getTag().equals(NAVBAR_HOME) &&
+ !view.getTag().equals(NAVBAR_RECENT) && !view.getTag().equals(NAVBAR_BACK)) {
+ final boolean isSmallButton = ArrayUtils.contains(SMALL_BUTTON_IDS, view.getId());
+ final ButtonAdapter list = new ButtonAdapter(mContext, mButtonViews, isSmallButton,
+ getResources());
+
+ AlertDialog.Builder builder = new AlertDialog.Builder(mContext)
+ .setTitle(mContext.getString(R.string.navbar_dialog_title))
+ .setAdapter(list, new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ KeyButtonView button = (KeyButtonView) view;
+ ButtonInfo info = list.getItem(which);
+
+ button.setInfo(info, mVertical, isSmallButton);
+ }
+ })
+ .setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int id) {
+ dialog.cancel();
+ }
+ });
+
+ mDialog = builder.create();
+ mDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG);
+ mDialog.setCanceledOnTouchOutside(false);
+ mDialog.show();
+ } else {
+ // Reset the dragged view to its original location
+ ViewGroup parent = (ViewGroup) view.getParent();
+
+ if (!mVertical) {
+ view.setX(mDragOrigin - parent.getLeft());
+ } else {
+ view.setY(mDragOrigin - parent.getTop());
+ }
+ }
+ mLongPressed = false;
+ }
+ return true;
+ }
+
+ /**
+ * Switches positions of two views and
+ * updates their mButtonViews entry
+ * @param targetView - view to be replaced
+ * @param view - view being dragged
+ */
+ private void switchId(View targetView, View view) {
+ ViewGroup parent = (ViewGroup) view.getParent();
+
+ targetView.getLocationOnScreen(sLocation);
+ if (!mVertical) {
+ targetView.setX(mDragOrigin - parent.getLeft());
+ mDragOrigin = sLocation[0];
+ } else {
+ targetView.setY(mDragOrigin - parent.getTop());
+ mDragOrigin = sLocation[1];
+ }
+
+ int targetIndex = mButtonViews.indexOf(targetView);
+ int draggedIndex = mButtonViews.indexOf(view);
+ Collections.swap(mButtonViews, draggedIndex, targetIndex);
+ }
+
+ /**
+ * Saves the current key arrangement
+ * to the settings provider
+ */
+ protected void saveKeys() {
+ StringBuilder sb = new StringBuilder();
+ for (int i = 0; i < BUTTON_IDS.length; i++) {
+ int idIndex = mVertical && !mRtl ? BUTTON_IDS.length - (i + 1) : i;
+ ButtonInfo info = (ButtonInfo) mButtonViews.get(idIndex).getTag();
+ if (i != 0) sb.append("|");
+ sb.append(info.key);
+ }
+ CMSettings.System.putStringForUser(mContext.getContentResolver(),
+ CMSettings.System.NAV_BUTTONS, sb.toString(), UserHandle.USER_CURRENT);
+ }
+
+ /**
+ * Updates the buttons according to the
+ * key arrangement stored in settings provider
+ */
+ protected void updateKeys() {
+ String saved = CMSettings.System.getStringForUser(mContext.getContentResolver(),
+ CMSettings.System.NAV_BUTTONS, UserHandle.USER_CURRENT);
+ if (saved == null) {
+ saved = DEFAULT_SETTING_STRING;
+ }
+
+ String[] buttons = saved.split("\\|");
+ if (buttons.length < BUTTON_IDS.length) {
+ buttons = DEFAULT_SETTING_STRING.split("\\|");
+ }
+
+ int visibleCount = 0;
+
+ for (int i = 0; i < BUTTON_IDS.length; i++) {
+ int id = BUTTON_IDS[i];
+ int index = mVertical && !mRtl ? BUTTON_IDS.length - i - 1 : i;
+ String key = index < buttons.length ? buttons[index] : null;
+ KeyButtonView buttonView = (KeyButtonView) mParent.findViewById(id);
+ boolean isSmallButton = ArrayUtils.contains(SMALL_BUTTON_IDS, id);
+ ButtonInfo button = NAVBAR_EMPTY;
+
+ for (ButtonInfo info : ALL_BUTTONS) {
+ if (info.key.equals(key)) {
+ button = info;
+ break;
+ }
+ }
+
+ buttonView.setInfo(button, mVertical, isSmallButton, getResources());
+ if (button != NAVBAR_EMPTY && !isSmallButton) {
+ visibleCount++;
+ }
+
+ buttonView.setTranslationX(0);
+ mButtonViews.set(i, buttonView);
+ }
+
+ if (isDevicePhone(mContext)) {
+ adjustPadding(visibleCount);
+ }
+ updateLowLights(visibleCount);
+ }
+
+ /**
+ * Accommodates the padding between keys based on
+ * number of keys in use.
+ */
+ private void adjustPadding(int visibleCount) {
+ ViewGroup viewParent = (ViewGroup) mParent.findViewById(R.id.mid_nav_buttons);
+ int totalViews = viewParent.getChildCount();
+
+ for (int v = 0; v < totalViews; v++) {
+ View currentKey = viewParent.getChildAt(v);
+ if (!(currentKey instanceof KeyButtonView)) {
+ continue;
+ }
+ View nextPadding = viewParent.getChildAt(v + 1);
+ if (nextPadding == null) {
+ continue;
+ }
+
+ View nextKey = viewParent.getChildAt(v + 2);
+ ButtonInfo nextInfo = nextKey == null ? null : (ButtonInfo) nextKey.getTag();
+ ButtonInfo currentInfo = (ButtonInfo) currentKey.getTag();
+
+ if (nextInfo != null && currentInfo != null && currentInfo != NAVBAR_EMPTY) {
+ if (nextInfo != NAVBAR_EMPTY || visibleCount > 1) {
+ nextPadding.setVisibility(View.VISIBLE);
+ } else {
+ nextPadding.setVisibility(View.GONE);
+ }
+ visibleCount--;
+ } else {
+ nextPadding.setVisibility(View.GONE);
+ }
+ }
+ }
+
+ protected void updateLowLights(int visibleCount) {
+ ViewGroup lowLights = (ViewGroup) mParent.findViewById(R.id.lights_out);
+ int totalViews = lowLights.getChildCount();
+
+ for (int v = 0;v < totalViews; v++) {
+ View currentView = lowLights.getChildAt(v);
+ if (!(currentView instanceof ImageView)) {
+ continue;
+ }
+
+ if (visibleCount <= 0) {
+ currentView.setVisibility(View.GONE);
+ } else {
+ currentView.setVisibility(View.VISIBLE);
+ visibleCount--;
+ }
+
+ View blank = lowLights.getChildAt(v + 1);
+ if (blank != null) {
+ blank.setVisibility(visibleCount > 0 ? View.VISIBLE : View.GONE);
+ }
+ }
+ }
+
+ private Resources getResources() {
+ return mResources != null ? mResources : mContext.getResources();
+ }
+
+ public void updateResources(Resources res) {
+ mResources = res;
+ }
+
+ /**
+ * Class to store info about supported buttons
+ */
+ public static final class ButtonInfo {
+ private final String key;
+ public int displayId;
+ public int contentDescription;
+ public int keyCode;
+ public int portResource;
+ public int landResource;
+ public int sideResource;
+ /**
+ * Constructor for new button type
+ * @param key - the internal key of the button
+ * @param rId - resource id of text shown to user in choose dialog
+ * @param cD - accessibility information regarding button
+ * @param mC - keyCode to execute on button press
+ * @param pR - portrait resource used to display button
+ * @param lR - landscape resource used to display button
+ * @param sR - smaller scaled resource for side buttons
+ */
+ ButtonInfo (String key, int rId, int cD, int mC, int pR, int lR, int sR) {
+ this.key = key;
+ displayId = rId;
+ contentDescription = cD;
+ keyCode = mC;
+ portResource = pR;
+ landResource = lR;
+ sideResource = sR;
+ }
+
+ @Override
+ public String toString() {
+ return "ButtonInfo[" + key + "]";
+ }
+ }
+
+ private static class ButtonAdapter extends ArrayAdapter<ButtonInfo> {
+ private ArrayList<ButtonInfo> mTakenItems;
+ private Resources mResources;
+
+ public ButtonAdapter(Context context,
+ ArrayList<KeyButtonView> buttons, boolean smallButtons, Resources resources) {
+ super(context, R.layout.navigation_bar_edit_menu_item, R.id.key_text,
+ buildItems(smallButtons));
+
+ mTakenItems = new ArrayList<ButtonInfo>();
+ for (KeyButtonView button : buttons) {
+ ButtonInfo info = (ButtonInfo) button.getTag();
+ if (info != null && info != NAVBAR_EMPTY) {
+ mTakenItems.add(info);
+ }
+ }
+ mResources = resources;
+ }
+
+ private static List<ButtonInfo> buildItems(boolean smallButtons) {
+ List<ButtonInfo> items = new ArrayList<ButtonInfo>(Arrays.asList(ALL_BUTTONS));
+
+ // Not re-assignable
+ items.remove(NAVBAR_HOME);
+ items.remove(NAVBAR_RECENT);
+ items.remove(NAVBAR_BACK);
+ // menu buttons can only be assigned to side buttons
+ if (!smallButtons) {
+ items.remove(NAVBAR_CONDITIONAL_MENU);
+ items.remove(NAVBAR_ALWAYS_MENU);
+ } else {
+ items.remove(NAVBAR_MENU_BIG);
+ }
+
+ return items;
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ View view = super.getView(position, convertView, parent);
+ ButtonInfo info = getItem(position);
+ boolean enabled = isEnabled(position);
+
+ TextView text = (TextView) view.findViewById(R.id.key_text);
+ text.setText(getContext().getResources().getString(info.displayId));
+ text.setEnabled(enabled);
+
+ ImageView icon = (ImageView) view.findViewById(R.id.key_icon);
+ icon.setImageDrawable(mResources.getDrawable(info.portResource));
+ icon.setColorFilter(new PorterDuffColorFilter(
+ text.getCurrentTextColor(), PorterDuff.Mode.SRC_IN));
+
+ return view;
+ }
+
+ @Override
+ public boolean areAllItemsEnabled() {
+ return true;
+ }
+
+ @Override
+ public boolean isEnabled(int position) {
+ return !mTakenItems.contains(getItem(position));
+ }
+ }
+}
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 134c579..50656ed 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarTransitions.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarTransitions.java
@@ -35,7 +35,10 @@ public final class NavigationBarTransitions extends BarTransitions {
private boolean mLightsOut;
public NavigationBarTransitions(NavigationBarView view) {
- super(view, R.drawable.nav_background);
+ super(view, R.drawable.nav_background, R.color.navigation_bar_background_opaque,
+ R.color.navigation_bar_background_semi_transparent,
+ R.color.navigation_bar_background_transparent,
+ com.android.internal.R.color.battery_saver_mode_color);
mView = view;
mBarService = IStatusBarService.Stub.asInterface(
ServiceManager.getService(Context.STATUS_BAR_SERVICE));
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 8046eb5..c10f45b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
@@ -23,19 +23,25 @@ import android.animation.TimeInterpolator;
import android.animation.ValueAnimator;
import android.app.ActivityManagerNative;
import android.app.StatusBarManager;
+import android.content.BroadcastReceiver;
+import android.content.ContentResolver;
import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
import android.content.res.Configuration;
import android.content.res.Resources;
+import android.database.ContentObserver;
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
+import android.net.Uri;
import android.os.Handler;
import android.os.Message;
import android.os.RemoteException;
+import android.os.UserHandle;
import android.util.AttributeSet;
import android.util.Log;
import android.view.Display;
-import android.view.Gravity;
import android.view.MotionEvent;
import android.view.Surface;
import android.view.View;
@@ -43,7 +49,6 @@ import android.view.ViewGroup;
import android.view.ViewRootImpl;
import android.view.WindowManager;
import android.view.inputmethod.InputMethodManager;
-import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.LinearLayout;
@@ -53,7 +58,8 @@ import com.android.systemui.statusbar.policy.KeyButtonView;
import java.io.FileDescriptor;
import java.io.PrintWriter;
-import java.util.ArrayList;
+
+import cyanogenmod.providers.CMSettings;
public class NavigationBarView extends LinearLayout {
final static boolean DEBUG = false;
@@ -69,26 +75,56 @@ public class NavigationBarView extends LinearLayout {
int mBarSize;
boolean mVertical;
boolean mScreenOn;
+ boolean mLeftInLandscape;
boolean mShowMenu;
int mDisabledFlags = 0;
int mNavigationIconHints = 0;
- private Drawable mBackIcon, mBackLandIcon, mBackAltIcon, mBackAltLandIcon;
+ private BackButtonDrawable mBackIcon, mBackLandIcon;
private Drawable mRecentIcon;
private Drawable mRecentLandIcon;
+ private Drawable mHomeIcon, mHomeLandIcon;
private NavigationBarViewTaskSwitchHelper mTaskSwitchHelper;
private DeadZone mDeadZone;
private final NavigationBarTransitions mBarTransitions;
+ /**
+ * Tracks the current visibilities of the far left (R.id.one) and right (R.id.six) buttons
+ * while dpad arrow keys are visible.
+ *
+ * We keep track of the orientations separately because they can get in different states,
+ * We can be showing dpad arrow keys on vertical, but on portrait that may not be so.
+ */
+ public int[][] mSideButtonVisibilities = new int[][] {
+ {-1, -1} /* portrait */, {-1, -1} /* vertical */
+ };
+
+
// workaround for LayoutTransitions leaving the nav buttons in a weird state (bug 5549288)
final static boolean WORKAROUND_INVALID_LAYOUT = true;
final static int MSG_CHECK_INVALID_LAYOUT = 8686;
+ final static String NAVBAR_EDIT_ACTION = "android.intent.action.NAVBAR_EDIT";
+
+ private boolean mInEditMode;
+ private NavbarEditor mEditBar;
+ private NavBarReceiver mNavBarReceiver;
+ private OnClickListener mRecentsClickListener;
+ private OnTouchListener mRecentsPreloadListener;
+ private OnTouchListener mHomeSearchActionListener;
+ private OnLongClickListener mRecentsBackListener;
+ private OnLongClickListener mLongPressHomeListener;
+
+ private SettingsObserver mSettingsObserver;
+ private boolean mShowDpadArrowKeys;
+
// performs manual animation in sync with layout transitions
private final NavTransitionListener mTransitionListener = new NavTransitionListener();
+ private Resources mThemedResources;
+
private OnVerticalChangedListener mOnVerticalChangedListener;
private boolean mIsLayoutRtl;
private boolean mLayoutTransitionsEnabled = true;
@@ -104,9 +140,9 @@ public class NavigationBarView extends LinearLayout {
@Override
public void startTransition(LayoutTransition transition, ViewGroup container,
View view, int transitionType) {
- if (view.getId() == R.id.back) {
+ if (NavbarEditor.NAVBAR_BACK.equals(view.getTag())) {
mBackTransitioning = true;
- } else if (view.getId() == R.id.home && transitionType == LayoutTransition.APPEARING) {
+ } else if (NavbarEditor.NAVBAR_HOME.equals(view.getTag()) && transitionType == LayoutTransition.APPEARING) {
mHomeAppearing = true;
mStartDelay = transition.getStartDelay(transitionType);
mDuration = transition.getDuration(transitionType);
@@ -117,9 +153,9 @@ public class NavigationBarView extends LinearLayout {
@Override
public void endTransition(LayoutTransition transition, ViewGroup container,
View view, int transitionType) {
- if (view.getId() == R.id.back) {
+ if (NavbarEditor.NAVBAR_BACK.equals(view.getTag())) {
mBackTransitioning = false;
- } else if (view.getId() == R.id.home && transitionType == LayoutTransition.APPEARING) {
+ } else if (NavbarEditor.NAVBAR_HOME.equals(view.getTag()) && transitionType == LayoutTransition.APPEARING) {
mHomeAppearing = false;
}
}
@@ -176,7 +212,7 @@ public class NavigationBarView extends LinearLayout {
mDisplay = ((WindowManager)context.getSystemService(
Context.WINDOW_SERVICE)).getDefaultDisplay();
- final Resources res = getContext().getResources();
+ final Resources res = getResources();
mBarSize = res.getDimensionPixelSize(R.dimen.navigation_bar_size);
mVertical = false;
mShowMenu = false;
@@ -185,6 +221,11 @@ public class NavigationBarView extends LinearLayout {
getIcons(res);
mBarTransitions = new NavigationBarTransitions(this);
+
+ mNavBarReceiver = new NavBarReceiver();
+ getContext().registerReceiverAsUser(mNavBarReceiver, UserHandle.ALL,
+ new IntentFilter(NAVBAR_EDIT_ACTION), null, null);
+ mSettingsObserver = new SettingsObserver(new Handler());
}
@Override
@@ -194,6 +235,13 @@ public class NavigationBarView extends LinearLayout {
if (root != null) {
root.setDrawDuringWindowsAnimating(true);
}
+ mSettingsObserver.observe();
+ }
+
+ @Override
+ protected void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+ mSettingsObserver.unobserve();
}
public BarTransitions getBarTransitions() {
@@ -211,7 +259,7 @@ public class NavigationBarView extends LinearLayout {
@Override
public boolean onTouchEvent(MotionEvent event) {
- if (mTaskSwitchHelper.onTouchEvent(event)) {
+ if (!mInEditMode && mTaskSwitchHelper.onTouchEvent(event)) {
return true;
}
if (mDeadZone != null && event.getAction() == MotionEvent.ACTION_OUTSIDE) {
@@ -222,7 +270,7 @@ public class NavigationBarView extends LinearLayout {
@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
- return mTaskSwitchHelper.onInterceptTouchEvent(event);
+ return !mInEditMode && mTaskSwitchHelper.onInterceptTouchEvent(event);
}
public void abortCurrentGesture() {
@@ -236,19 +284,19 @@ public class NavigationBarView extends LinearLayout {
}
public View getRecentsButton() {
- return mCurrentView.findViewById(R.id.recent_apps);
+ return mCurrentView.findViewWithTag(NavbarEditor.NAVBAR_RECENT);
}
public View getMenuButton() {
- return mCurrentView.findViewById(R.id.menu);
+ return mCurrentView.findViewWithTag(NavbarEditor.NAVBAR_CONDITIONAL_MENU);
}
public View getBackButton() {
- return mCurrentView.findViewById(R.id.back);
+ return mCurrentView.findViewWithTag(NavbarEditor.NAVBAR_BACK);
}
public KeyButtonView getHomeButton() {
- return (KeyButtonView) mCurrentView.findViewById(R.id.home);
+ return (KeyButtonView) mCurrentView.findViewWithTag(NavbarEditor.NAVBAR_HOME);
}
public View getImeSwitchButton() {
@@ -256,17 +304,51 @@ public class NavigationBarView extends LinearLayout {
}
private void getIcons(Resources res) {
- mBackIcon = res.getDrawable(R.drawable.ic_sysbar_back);
- mBackLandIcon = mBackIcon;
- mBackAltIcon = res.getDrawable(R.drawable.ic_sysbar_back_ime);
- mBackAltLandIcon = mBackAltIcon;
+ mBackIcon = new BackButtonDrawable(res.getDrawable(R.drawable.ic_sysbar_back));
+ mBackLandIcon = mBackIcon;
mRecentIcon = res.getDrawable(R.drawable.ic_sysbar_recent);
mRecentLandIcon = mRecentIcon;
+ mHomeIcon = res.getDrawable(R.drawable.ic_sysbar_home);
+ mHomeLandIcon = mHomeIcon;
+ }
+
+ public void updateResources(Resources res) {
+ mThemedResources = res;
+ getIcons(mThemedResources);
+ mBarTransitions.updateResources(res);
+ for (int i = 0; i < mRotatedViews.length; i++) {
+ ViewGroup container = (ViewGroup) mRotatedViews[i];
+ if (container != null) {
+ updateLightsOutResources(container);
+ }
+ }
+ if (mEditBar != null) {
+ mEditBar.updateResources(res);
+ }
+ }
+
+ private void updateLightsOutResources(ViewGroup container) {
+ ViewGroup lightsOut = (ViewGroup) container.findViewById(R.id.lights_out);
+ if (lightsOut != null) {
+ final int nChildren = lightsOut.getChildCount();
+ for (int i = 0; i < nChildren; i++) {
+ final View child = lightsOut.getChildAt(i);
+ if (child instanceof ImageView) {
+ final ImageView iv = (ImageView) child;
+ // clear out the existing drawable, this is required since the
+ // ImageView keeps track of the resource ID and if it is the same
+ // it will not update the drawable.
+ iv.setImageDrawable(null);
+ iv.setImageDrawable(mThemedResources.getDrawable(
+ R.drawable.ic_sysbar_lights_out_dot_large));
+ }
+ }
+ }
}
@Override
public void setLayoutDirection(int layoutDirection) {
- getIcons(getContext().getResources());
+ getIcons(getResources());
super.setLayoutDirection(layoutDirection);
}
@@ -294,19 +376,74 @@ public class NavigationBarView extends LinearLayout {
mNavigationIconHints = hints;
- ((ImageView)getBackButton()).setImageDrawable(backAlt
- ? (mVertical ? mBackAltLandIcon : mBackAltIcon)
- : (mVertical ? mBackLandIcon : mBackIcon));
+ ((ImageView)getBackButton()).setImageDrawable(null);
+ ((ImageView)getBackButton()).setImageDrawable(mVertical ? mBackLandIcon : mBackIcon);
+ mBackLandIcon.setImeVisible(backAlt);
+ mBackIcon.setImeVisible(backAlt);
((ImageView)getRecentsButton()).setImageDrawable(mVertical ? mRecentLandIcon : mRecentIcon);
+ ((ImageView)getHomeButton()).setImageDrawable(mVertical ? mHomeLandIcon : mHomeIcon);
- final boolean showImeButton = ((hints & StatusBarManager.NAVIGATION_HINT_IME_SHOWN) != 0);
+ final boolean showImeButton = ((hints & StatusBarManager.NAVIGATION_HINT_IME_SHOWN) != 0)
+ && !mShowDpadArrowKeys;
getImeSwitchButton().setVisibility(showImeButton ? View.VISIBLE : View.INVISIBLE);
+
+ setDisabledFlags(mDisabledFlags, true);
+
// Update menu button in case the IME state has changed.
setMenuVisibility(mShowMenu, true);
+ if (mShowDpadArrowKeys) { // overrides IME button
+ final boolean showingIme = ((mNavigationIconHints
+ & StatusBarManager.NAVIGATION_HINT_BACK_ALT) != 0);
- setDisabledFlags(mDisabledFlags, true);
+ setVisibleOrGone(getCurrentView().findViewById(R.id.dpad_left), showingIme);
+ setVisibleOrGone(getCurrentView().findViewById(R.id.dpad_right), showingIme);
+
+ View one = getCurrentView().findViewById(mVertical ? R.id.six : R.id.one);
+ View six = getCurrentView().findViewById(mVertical ? R.id.one : R.id.six);
+ if (showingIme) {
+ if (one.getVisibility() != View.GONE) {
+ setSideButtonVisibility(true, one.getVisibility());
+ setVisibleOrGone(one, false);
+ }
+
+ if (six.getVisibility() != View.GONE) {
+ setSideButtonVisibility(false, six.getVisibility());
+ setVisibleOrGone(six, false);
+ }
+ } else {
+ if (getSideButtonVisibility(true) != -1) {
+ one.setVisibility(getSideButtonVisibility(true));
+ setSideButtonVisibility(true, - 1);
+ }
+ if (getSideButtonVisibility(false) != -1) {
+ six.setVisibility(getSideButtonVisibility(false));
+ setSideButtonVisibility(false, -1);
+ }
+ }
+ } else {
+ setVisibleOrGone(getCurrentView().findViewById(R.id.dpad_left), false);
+ setVisibleOrGone(getCurrentView().findViewById(R.id.dpad_right), false);
+ View one = getCurrentView().findViewById(mVertical ? R.id.six : R.id.one);
+ View six = getCurrentView().findViewById(mVertical ? R.id.one : R.id.six);
+ if (getSideButtonVisibility(true) != -1) {
+ one.setVisibility(getSideButtonVisibility(true));
+ setSideButtonVisibility(true, - 1);
+ }
+ if (getSideButtonVisibility(false) != -1) {
+ six.setVisibility(getSideButtonVisibility(false));
+ setSideButtonVisibility(false, -1);
+ }
+ }
+ }
+
+ private int getSideButtonVisibility(boolean left) {
+ return mSideButtonVisibilities[mVertical ? 1 : 0][left ? 0 : 1];
+ }
+
+ private void setSideButtonVisibility(boolean left, int vis) {
+ mSideButtonVisibilities[mVertical ? 1 : 0][left ? 0 : 1] = vis;
}
public void setDisabledFlags(int disabledFlags) {
@@ -343,9 +480,10 @@ public class NavigationBarView extends LinearLayout {
disableRecent = false;
}
- getBackButton() .setVisibility(disableBack ? View.INVISIBLE : View.VISIBLE);
- getHomeButton() .setVisibility(disableHome ? View.INVISIBLE : View.VISIBLE);
- getRecentsButton().setVisibility(disableRecent ? View.INVISIBLE : View.VISIBLE);
+ setButtonWithTagVisibility(NavbarEditor.NAVBAR_BACK, !disableBack);
+ setButtonWithTagVisibility(NavbarEditor.NAVBAR_HOME, !disableHome);
+ setButtonWithTagVisibility(NavbarEditor.NAVBAR_RECENT, !disableRecent);
+ setButtonWithTagVisibility(NavbarEditor.NAVBAR_SEARCH, !disableSearch);
}
private boolean inLockTask() {
@@ -436,18 +574,19 @@ public class NavigationBarView extends LinearLayout {
// Only show Menu if IME switcher not shown.
final boolean shouldShow = mShowMenu &&
((mNavigationIconHints & StatusBarManager.NAVIGATION_HINT_IME_SHOWN) == 0);
- getMenuButton().setVisibility(shouldShow ? View.VISIBLE : View.INVISIBLE);
+ final boolean shouldShowAlwaysMenu = (mNavigationIconHints &
+ StatusBarManager.NAVIGATION_HINT_IME_SHOWN) == 0;
+ setButtonWithTagVisibility(NavbarEditor.NAVBAR_ALWAYS_MENU, shouldShowAlwaysMenu);
+ setButtonWithTagVisibility(NavbarEditor.NAVBAR_CONDITIONAL_MENU, shouldShow);
+ setButtonWithTagVisibility(NavbarEditor.NAVBAR_SEARCH, shouldShowAlwaysMenu);
}
@Override
public void onFinishInflate() {
mRotatedViews[Surface.ROTATION_0] =
- mRotatedViews[Surface.ROTATION_180] = findViewById(R.id.rot0);
-
+ mRotatedViews[Surface.ROTATION_180] = findViewById(R.id.rot0);
mRotatedViews[Surface.ROTATION_90] = findViewById(R.id.rot90);
-
mRotatedViews[Surface.ROTATION_270] = mRotatedViews[Surface.ROTATION_90];
-
mCurrentView = mRotatedViews[Surface.ROTATION_0];
getImeSwitchButton().setOnClickListener(mImeSwitcherClickListener);
@@ -459,6 +598,11 @@ public class NavigationBarView extends LinearLayout {
return mVertical;
}
+ public void setLeftInLandscape(boolean leftInLandscape) {
+ mLeftInLandscape = leftInLandscape;
+ mDeadZone.setStartFromRight(leftInLandscape);
+ }
+
public void reorient() {
final int rot = mDisplay.getRotation();
for (int i=0; i<4; i++) {
@@ -466,11 +610,22 @@ public class NavigationBarView extends LinearLayout {
}
mCurrentView = mRotatedViews[rot];
mCurrentView.setVisibility(View.VISIBLE);
+
updateLayoutTransitionsEnabled();
+ if (NavbarEditor.isDevicePhone(getContext())) {
+ int rotation = mDisplay.getRotation();
+ mVertical = rotation == Surface.ROTATION_90 || rotation == Surface.ROTATION_270;
+ } else {
+ mVertical = getWidth() > 0 && getHeight() > getWidth();
+ }
+ mEditBar = new NavbarEditor(mCurrentView, mVertical, mIsLayoutRtl, getResources());
+ updateSettings();
+
getImeSwitchButton().setOnClickListener(mImeSwitcherClickListener);
mDeadZone = (DeadZone) mCurrentView.findViewById(R.id.deadzone);
+ mDeadZone.setStartFromRight(mLeftInLandscape);
// force the low profile & disabled states into compliance
mBarTransitions.init();
@@ -529,55 +684,8 @@ public class NavigationBarView extends LinearLayout {
boolean isLayoutRtl = getResources().getConfiguration()
.getLayoutDirection() == LAYOUT_DIRECTION_RTL;
if (mIsLayoutRtl != isLayoutRtl) {
-
- // We swap all children of the 90 and 270 degree layouts, since they are vertical
- View rotation90 = mRotatedViews[Surface.ROTATION_90];
- swapChildrenOrderIfVertical(rotation90.findViewById(R.id.nav_buttons));
- adjustExtraKeyGravity(rotation90, isLayoutRtl);
-
- View rotation270 = mRotatedViews[Surface.ROTATION_270];
- if (rotation90 != rotation270) {
- swapChildrenOrderIfVertical(rotation270.findViewById(R.id.nav_buttons));
- adjustExtraKeyGravity(rotation270, isLayoutRtl);
- }
mIsLayoutRtl = isLayoutRtl;
- }
- }
-
- private void adjustExtraKeyGravity(View navBar, boolean isLayoutRtl) {
- View menu = navBar.findViewById(R.id.menu);
- View imeSwitcher = navBar.findViewById(R.id.ime_switcher);
- if (menu != null) {
- FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) menu.getLayoutParams();
- lp.gravity = isLayoutRtl ? Gravity.BOTTOM : Gravity.TOP;
- menu.setLayoutParams(lp);
- }
- if (imeSwitcher != null) {
- FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) imeSwitcher.getLayoutParams();
- lp.gravity = isLayoutRtl ? Gravity.BOTTOM : Gravity.TOP;
- imeSwitcher.setLayoutParams(lp);
- }
- }
-
- /**
- * Swaps the children order of a LinearLayout if it's orientation is Vertical
- *
- * @param group The LinearLayout to swap the children from.
- */
- private void swapChildrenOrderIfVertical(View group) {
- if (group instanceof LinearLayout) {
- LinearLayout linearLayout = (LinearLayout) group;
- if (linearLayout.getOrientation() == VERTICAL) {
- int childCount = linearLayout.getChildCount();
- ArrayList<View> childList = new ArrayList<>(childCount);
- for (int i = 0; i < childCount; i++) {
- childList.add(linearLayout.getChildAt(i));
- }
- linearLayout.removeAllViews();
- for (int i = childCount - 1; i >= 0; i--) {
- linearLayout.addView(childList.get(i));
- }
- }
+ reorient();
}
}
@@ -605,7 +713,7 @@ public class NavigationBarView extends LinearLayout {
private String getResourceName(int resId) {
if (resId != 0) {
- final android.content.res.Resources res = getContext().getResources();
+ final android.content.res.Resources res = getResources();
try {
return res.getResourceName(resId);
} catch (android.content.res.Resources.NotFoundException ex) {
@@ -671,9 +779,9 @@ public class NavigationBarView extends LinearLayout {
pw.print("null");
} else {
pw.print(PhoneStatusBar.viewInfo(button)
- + " " + visibilityToString(button.getVisibility())
- + " alpha=" + button.getAlpha()
- );
+ + " " + visibilityToString(button.getVisibility())
+ + " alpha=" + button.getAlpha()
+ );
}
pw.println();
}
@@ -681,4 +789,140 @@ public class NavigationBarView extends LinearLayout {
public interface OnVerticalChangedListener {
void onVerticalChanged(boolean isVertical);
}
+
+ void setListeners(OnClickListener recentsClickListener, OnTouchListener recentsPreloadListener,
+ OnLongClickListener recentsBackListener, OnTouchListener homeSearchActionListener,
+ OnLongClickListener longPressHomeListener) {
+ mRecentsClickListener = recentsClickListener;
+ mRecentsPreloadListener = recentsPreloadListener;
+ mHomeSearchActionListener = homeSearchActionListener;
+ mRecentsBackListener = recentsBackListener;
+ mLongPressHomeListener = longPressHomeListener;
+ updateButtonListeners();
+ }
+
+ private void removeButtonListeners() {
+ ViewGroup container = (ViewGroup) mCurrentView.findViewById(R.id.container);
+ int viewCount = container.getChildCount();
+ for (int i = 0; i < viewCount; i++) {
+ View button = container.getChildAt(i);
+ if (button instanceof KeyButtonView) {
+ button.setOnClickListener(null);
+ button.setOnTouchListener(null);
+ button.setLongClickable(false);
+ button.setOnLongClickListener(null);
+ }
+ }
+ }
+
+ protected void updateButtonListeners() {
+ View recentView = mCurrentView.findViewWithTag(NavbarEditor.NAVBAR_RECENT);
+ if (recentView != null) {
+ recentView.setOnClickListener(mRecentsClickListener);
+ recentView.setOnTouchListener(mRecentsPreloadListener);
+ recentView.setLongClickable(true);
+ recentView.setOnLongClickListener(mRecentsBackListener);
+ }
+ View backView = mCurrentView.findViewWithTag(NavbarEditor.NAVBAR_BACK);
+ if (backView != null) {
+ backView.setLongClickable(true);
+ backView.setOnLongClickListener(mRecentsBackListener);
+ }
+ View homeView = mCurrentView.findViewWithTag(NavbarEditor.NAVBAR_HOME);
+ if (homeView != null) {
+ homeView.setOnTouchListener(mHomeSearchActionListener);
+ homeView.setLongClickable(true);
+ homeView.setOnLongClickListener(mLongPressHomeListener);
+ }
+ }
+
+ public boolean isInEditMode() {
+ return mInEditMode;
+ }
+
+ private void setButtonWithTagVisibility(Object tag, boolean visible) {
+ View findView = mCurrentView.findViewWithTag(tag);
+ if (findView == null) {
+ return;
+ }
+ int visibility = visible ? View.VISIBLE : View.INVISIBLE;
+ // if we're showing dpad arrow keys (e.g. the side button visibility where it's shown != -1)
+ // then don't actually update that buttons visibility, but update the stored value
+ if (getSideButtonVisibility(true) != -1
+ && findView.getId() == (mVertical ? R.id.six : R.id.one)) {
+ setSideButtonVisibility(true, visibility);
+ } else if (getSideButtonVisibility(false) != -1
+ && findView.getId() == (mVertical ? R.id.one : R.id.six)) {
+ setSideButtonVisibility(false, visibility);
+ } else {
+ findView.setVisibility(visibility);
+ }
+ }
+
+ @Override
+ public Resources getResources() {
+ return mThemedResources != null ? mThemedResources : getContext().getResources();
+ }
+
+ public class NavBarReceiver extends BroadcastReceiver {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ boolean edit = intent.getBooleanExtra("edit", false);
+ boolean save = intent.getBooleanExtra("save", false);
+ if (edit != mInEditMode) {
+ mInEditMode = edit;
+ if (edit) {
+ removeButtonListeners();
+ mEditBar.setEditMode(true);
+ } else {
+ if (save) {
+ mEditBar.saveKeys();
+ }
+ mEditBar.setEditMode(false);
+ updateSettings();
+ }
+ }
+ }
+ }
+
+ public void updateSettings() {
+ mEditBar.updateKeys();
+ removeButtonListeners();
+ updateButtonListeners();
+ updateShowDpadKeys();
+ setMenuVisibility(mShowMenu, true);
+ }
+
+ private class SettingsObserver extends ContentObserver {
+
+ SettingsObserver(Handler handler) {
+ super(handler);
+ }
+
+ public void observe() {
+ ContentResolver resolver = getContext().getContentResolver();
+ resolver.registerContentObserver(
+ CMSettings.System.getUriFor(CMSettings.System.NAVIGATION_BAR_MENU_ARROW_KEYS),
+ false, this);
+
+ // intialize mModlockDisabled
+ onChange(false);
+ }
+
+ public void unobserve() {
+ getContext().getContentResolver().unregisterContentObserver(this);
+ }
+
+ @Override
+ public void onChange(boolean selfChange, Uri uri) {
+ super.onChange(selfChange, uri);
+ updateShowDpadKeys();
+ }
+ }
+
+ private void updateShowDpadKeys() {
+ mShowDpadArrowKeys = CMSettings.System.getIntForUser(getContext().getContentResolver(),
+ CMSettings.System.NAVIGATION_BAR_MENU_ARROW_KEYS, 0, UserHandle.USER_CURRENT) != 0;
+ setNavigationIconHints(mNavigationIconHints, true);
+ }
}
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 bdd2c73..e4e02a7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
@@ -18,25 +18,41 @@ package com.android.systemui.statusbar.phone;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
+import android.animation.ArgbEvaluator;
import android.animation.ObjectAnimator;
import android.animation.PropertyValuesHolder;
import android.animation.ValueAnimator;
+import android.content.ContentResolver;
import android.app.ActivityManager;
import android.app.StatusBarManager;
import android.content.Context;
+import android.content.SharedPreferences;
import android.content.pm.ResolveInfo;
import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.database.ContentObserver;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
+import android.graphics.Point;
import android.graphics.Rect;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.PowerManager;
+import android.os.RemoteException;
+import android.preference.PreferenceManager;
import android.util.AttributeSet;
import android.util.MathUtils;
+import android.view.Display;
+import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
+import android.view.ViewConfiguration;
import android.view.ViewTreeObserver;
import android.view.WindowInsets;
+import android.view.WindowManager;
+import android.view.WindowManagerGlobal;
import android.view.accessibility.AccessibilityEvent;
import android.view.animation.AnimationUtils;
import android.view.animation.Interpolator;
@@ -50,8 +66,9 @@ import com.android.systemui.DejankUtils;
import com.android.systemui.EventLogConstants;
import com.android.systemui.EventLogTags;
import com.android.systemui.R;
+import com.android.systemui.SwipeHelper;
import com.android.systemui.qs.QSContainer;
-import com.android.systemui.qs.QSPanel;
+import com.android.systemui.qs.QSDragPanel;
import com.android.systemui.statusbar.ExpandableNotificationRow;
import com.android.systemui.statusbar.ExpandableView;
import com.android.systemui.statusbar.FlingAnimationUtils;
@@ -61,16 +78,22 @@ import com.android.systemui.statusbar.NotificationData;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.policy.HeadsUpManager;
import com.android.systemui.statusbar.policy.KeyguardUserSwitcher;
+import com.android.systemui.statusbar.policy.LiveLockScreenController;
+import com.android.systemui.statusbar.policy.WeatherController;
+import com.android.systemui.statusbar.policy.WeatherControllerImpl;
import com.android.systemui.statusbar.stack.NotificationStackScrollLayout;
import com.android.systemui.statusbar.stack.StackStateAnimator;
+import cyanogenmod.providers.CMSettings;
+import cyanogenmod.weather.util.WeatherUtils;
+
import java.util.List;
public class NotificationPanelView extends PanelView implements
ExpandableView.OnHeightChangedListener, ObservableScrollView.Listener,
View.OnClickListener, NotificationStackScrollLayout.OnOverscrollTopChangedListener,
KeyguardAffordanceHelper.Callback, NotificationStackScrollLayout.OnEmptySpaceClickListener,
- HeadsUpManager.OnHeadsUpChangedListener {
+ HeadsUpManager.OnHeadsUpChangedListener, WeatherController.Callback {
private static final boolean DEBUG = false;
@@ -88,14 +111,26 @@ public class NotificationPanelView extends PanelView implements
private static final Rect mDummyDirtyRect = new Rect(0, 0, 1, 1);
+ private static final long SLIDE_PANEL_IN_ANIMATION_DURATION = 300;
+
+ private static final String KEY_USER_EXPANDED_NOTIFICATIONS_IN_KEYGUARD =
+ "user_expanded_notifications_in_keyguard";
+ private static final String KEY_USER_INTERACTED_WITH_LLS =
+ "user_interacted_with_lls";
+ private static final String KEY_USER_UNLOCKED =
+ "user_unlocked";
+ private static final String KEY_USER_RETURNED_FROM_LLS =
+ "user_returned_from_lls";
+
public static final long DOZE_ANIMATION_DURATION = 700;
+
private KeyguardAffordanceHelper mAfforanceHelper;
private StatusBarHeaderView mHeader;
private KeyguardUserSwitcher mKeyguardUserSwitcher;
private KeyguardStatusBarView mKeyguardStatusBar;
private QSContainer mQsContainer;
- private QSPanel mQsPanel;
+ private QSDragPanel mQsPanel;
private KeyguardStatusView mKeyguardStatusView;
private ObservableScrollView mScrollView;
private TextView mClockView;
@@ -217,15 +252,201 @@ public class NotificationPanelView extends PanelView implements
private final Interpolator mTouchResponseInterpolator =
new PathInterpolator(0.3f, 0f, 0.1f, 1f);
+ private Handler mHandler = new Handler();
+ private SettingsObserver mSettingsObserver;
+
+ private int mOneFingerQuickSettingsIntercept;
+ private boolean mDoubleTapToSleepEnabled;
+ private int mStatusBarHeaderHeight;
+ private GestureDetector mDoubleTapGesture;
+
+ // Used to identify whether showUnlock() can dismiss the keyguard
+ // or not.
+ // TODO - add a new state to make it easier to identify keyguard vs
+ // LiveLockscreen
+ public boolean mCanDismissKeyguard;
+
+ // Used to track which direction the user is currently
+ // interacting with and ensure they don't alternate back
+ // and forth. Reset every MOTION_UP/MOTION_CANCEL
+ private SwipeLockedDirection mLockedDirection;
+
+ private SwipeHelper mSwipeHelper;
+ private final int mMinimumFlingVelocity;
+ private final int mScreenHeight;
+ private LiveLockScreenController mLiveLockscreenController;
+ private final GestureDetector mGestureDetector;
+ private ViewLinker mViewLinker;
+ private final UnlockMethodCache mUnlockMethodCache;
+ private boolean mDetailScrollLock;
+
+ private boolean mKeyguardWeatherEnabled;
+ private TextView mKeyguardWeatherInfo;
+ private WeatherControllerImpl mWeatherController;
+
+ // Keep track of common user interactions on the lock screen
+ private boolean mUserUnlocked;
+ private boolean mUserExpandedNotifications;
+ private boolean mUserInteractedWithLiveLockScreen;
+ private boolean mUserReturnedFromLiveLockScreen;
+
+ private boolean mScreenOnHintsEnabled;
+
+ private enum SwipeLockedDirection {
+ UNKNOWN,
+ HORIZONTAL,
+ VERTICAL
+ }
+
+ // Handles swiping to the LiveLockscreen from keyguard
+ SwipeHelper.SimpleCallback mSwipeCallback = new SwipeHelper.SimpleCallback() {
+ @Override
+ public View getChildAtPosition(MotionEvent ev) {
+ return mViewLinker.getParent();
+ }
+
+ @Override
+ public View getChildContentView(View v) {
+ return mViewLinker.getParent();
+ }
+
+ @Override
+ public boolean canChildBeDismissed(View v) {
+ return true;
+ }
+
+ @Override
+ public void onChildDismissed(View v) {
+ mCanDismissKeyguard = false;
+ mStatusBar.focusKeyguardExternalView();
+ mLiveLockscreenController.onLiveLockScreenFocusChanged(true /* hasFocus */);
+ if (!mUserInteractedWithLiveLockScreen) {
+ mUserInteractedWithLiveLockScreen = true;
+ saveUserInteractedWithLls(true);
+ }
+ if (!mUserReturnedFromLiveLockScreen) {
+ startShowNotificationsHintAnimation();
+ }
+ resetAlphaTranslation();
+ // Enables the left edge gesture to allow user
+ // to return to keyguard
+ try {
+ WindowManagerGlobal.getWindowManagerService()
+ .setLiveLockscreenEdgeDetector(true);
+ } catch (RemoteException e){
+ e.printStackTrace();
+ }
+ }
+
+ @Override
+ public boolean updateSwipeProgress(View animView, boolean dismissable, float swipeProgress) {
+ // Let live lockscreen know of swipe progress to allow
+ // them to translate content in.
+ mLiveLockscreenController.getLiveLockScreenView()
+ .onLockscreenSlideOffsetChanged(swipeProgress);
+
+ // Fade out scrim background
+ float alpha = ScrimController.SCRIM_BEHIND_ALPHA_KEYGUARD - (1f - swipeProgress);
+ alpha = Math.max(0, alpha);
+ mStatusBar.getScrimController().setScrimBehindColor(alpha);
+ return false;
+ }
+
+ private void resetAlphaTranslation() {
+ mNotificationStackScroller.setTranslationX(0);
+ mNotificationStackScroller.setAlpha(1f);
+
+ mKeyguardStatusView.setTranslationX(0);
+ mKeyguardStatusView.setAlpha(1f);
+ }
+ };
+
public NotificationPanelView(Context context, AttributeSet attrs) {
super(context, attrs);
setWillNotDraw(!DEBUG);
+
+ mSettingsObserver = new SettingsObserver(mHandler);
+ mDoubleTapGesture = new GestureDetector(mContext, new GestureDetector.SimpleOnGestureListener() {
+ @Override
+ public boolean onDoubleTap(MotionEvent e) {
+ PowerManager pm = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
+ if(pm != null)
+ pm.goToSleep(e.getEventTime());
+ return true;
+ }
+ });
+
+ Resources res = getContext().getResources();
+ final int gradientStart = res.getColor(R.color.live_lockscreen_gradient_start);
+ final int gradientEnd = res.getColor(R.color.live_lockscreen_gradient_end);
+ mGestureDetector = new GestureDetector(getContext(),
+ new GestureDetector.SimpleOnGestureListener() {
+ private float mDown;
+
+ @Override
+ public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
+ // Ensure we only capture swipes in the up direction
+ if (velocityY > 0 || Math.abs(velocityY) <= mMinimumFlingVelocity) {
+ return false;
+ }
+ mCanDismissKeyguard = true;
+ mStatusBar.showBouncer();
+ return true;
+ }
+
+ @Override
+ public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
+ float delta = mDown - e2.getRawY();
+ delta = Math.max(0, delta);
+ float screenHeightHalf = (float) mScreenHeight / 2f;
+ int color = (Integer) ArgbEvaluator.getInstance()
+ .evaluate(delta / screenHeightHalf, gradientStart, gradientEnd);
+ mKeyguardBottomArea.setBackgroundColor(color);
+ return super.onScroll(e1, e2, distanceX, distanceY);
+ }
+
+ @Override
+ public boolean onDown(MotionEvent e) {
+ mDown = e.getRawY();
+ mKeyguardBottomArea.expand(true);
+ return true;
+ }
+ });
+
+ mSwipeHelper = new SwipeHelper(SwipeHelper.X,
+ SwipeHelper.SWIPE_ZONE_LEFT, mSwipeCallback, mContext);
+ mSwipeHelper.setSwipeProgressFadeEnd(1.0f);
+ mMinimumFlingVelocity = ViewConfiguration.get(getContext())
+ .getScaledMinimumFlingVelocity();
+
+ WindowManager windowManager = (WindowManager) mContext
+ .getSystemService(Context.WINDOW_SERVICE);
+ Display display = windowManager.getDefaultDisplay();
+ Point point = new Point();
+ display.getSize(point);
+ mScreenHeight = point.y;
+ mUnlockMethodCache = UnlockMethodCache.getInstance(context);
+
+ mScreenOnHintsEnabled = res.getBoolean(R.bool.config_showScreenOnLockScreenHints);
+ mUserUnlocked = getUserUnlocked();
+ mUserExpandedNotifications = getUserExpandedNotificationsInKeyguard();
+ mUserInteractedWithLiveLockScreen = getUserInteractedWithLls();
+ mUserReturnedFromLiveLockScreen = getUserReturnedFromLls();
}
public void setStatusBar(PhoneStatusBar bar) {
mStatusBar = bar;
}
+ public void setLiveController(LiveLockScreenController liveController) {
+ mLiveLockscreenController = liveController;
+ }
+
+ public void setWeatherController(WeatherControllerImpl weatherController) {
+ mWeatherController = weatherController;
+ mWeatherController.addCallback(this);
+ }
+
@Override
protected void onFinishInflate() {
super.onFinishInflate();
@@ -234,10 +455,10 @@ public class NotificationPanelView extends PanelView implements
mKeyguardStatusBar = (KeyguardStatusBarView) findViewById(R.id.keyguard_header);
mKeyguardStatusView = (KeyguardStatusView) findViewById(R.id.keyguard_status_view);
mQsContainer = (QSContainer) findViewById(R.id.quick_settings_container);
- mQsPanel = (QSPanel) findViewById(R.id.quick_settings_panel);
+ mQsPanel = (QSDragPanel) findViewById(R.id.quick_settings_panel);
+ mQsPanel.setPanelView(this);
mClockView = (TextView) findViewById(R.id.clock_view);
mScrollView = (ObservableScrollView) findViewById(R.id.scroll_view);
- mScrollView.setListener(this);
mScrollView.setFocusable(false);
mReserveNotificationSpace = findViewById(R.id.reserve_notification_space);
mNotificationContainerParent = (NotificationsQuickSettingsContainer)
@@ -254,7 +475,64 @@ public class NotificationPanelView extends PanelView implements
android.R.interpolator.fast_out_linear_in);
mDozeAnimationInterpolator = AnimationUtils.loadInterpolator(getContext(),
android.R.interpolator.linear_out_slow_in);
- mKeyguardBottomArea = (KeyguardBottomAreaView) findViewById(R.id.keyguard_bottom_area);
+
+ mViewLinker = new ViewLinker<NotificationStackScrollLayout>(mNotificationStackScroller,
+ new ViewLinker.LinkInfo(mKeyguardStatusBar, ViewLinker.LINK_ALPHA),
+ new ViewLinker.LinkInfo(mKeyguardStatusView, ViewLinker.LINK_ALPHA
+ | ViewLinker.LINK_TRANSLATION));
+
+ mKeyguardBottomArea = (KeyguardBottomAreaView) View.inflate(getContext(),
+ R.layout.keyguard_bottom_area, null);
+ /** Keyguard bottom area lives in a separate window, and as such,
+ * we must redirect its touch events through the proper flow
+ */
+ mKeyguardBottomArea.setOnInterceptTouchListener(new KeyguardBottomAreaView.OnInterceptTouchEventListener() {
+ @Override
+ public boolean onInterceptTouchEvent(MotionEvent e) {
+ boolean intercept = false;
+ if (mLiveLockscreenController.getLiveLockScreenHasFocus()) {
+ // Handles swipe up to fade/dismiss when showing
+ // live lock screen
+ intercept = mAfforanceHelper.onInterceptTouchEvent(e);
+ if (!intercept) {
+ intercept = mGestureDetector.onTouchEvent(e);
+ }
+ } else {
+ intercept = NotificationPanelView.this.onInterceptTouchEvent(e);
+ }
+ return intercept;
+ }
+ });
+ mKeyguardBottomArea.setOnTouchListener(new OnTouchListener() {
+ @Override
+ public boolean onTouch(View v, MotionEvent e) {
+ int action = e.getAction();
+
+ boolean isCancelOrUp = action == MotionEvent.ACTION_UP ||
+ action == MotionEvent.ACTION_CANCEL;
+ if (isCancelOrUp) {
+ mKeyguardBottomArea.setBackground(null);
+ }
+
+ boolean intercept = false;
+ if (mLiveLockscreenController.getLiveLockScreenHasFocus()) {
+ intercept = mAfforanceHelper.onTouchEvent(e);
+ // If the touch did not originate on the affordance helper,
+ // we must collapse the panel here since we can't rely on
+ // the swipe callbacks from being invoked.
+ if (isCancelOrUp && !isAffordanceSwipeInProgress()) {
+ mKeyguardBottomArea.expand(false);
+ }
+ if (!intercept) {
+ intercept = mGestureDetector.onTouchEvent(e);
+ }
+ } else {
+ intercept = NotificationPanelView.this.onTouchEvent(e);
+ }
+ return intercept;
+ }
+ });
+
mQsNavbarScrim = findViewById(R.id.qs_navbar_scrim);
mAfforanceHelper = new KeyguardAffordanceHelper(this, getContext());
mLastOrientation = getResources().getConfiguration().orientation;
@@ -271,6 +549,26 @@ public class NotificationPanelView extends PanelView implements
}
}
});
+
+ mKeyguardWeatherInfo = (TextView) mKeyguardStatusView.findViewById(R.id.weather_info);
+ }
+
+ public boolean isAffordanceSwipeInProgress() {
+ return mAfforanceHelper.isSwipingInProgress();
+ }
+
+ @Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+ mSettingsObserver.observe();
+ mScrollView.setListener(this);
+ }
+
+ @Override
+ protected void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+ mSettingsObserver.unobserve();
+ mWeatherController.removeCallback(this);
}
@Override
@@ -292,6 +590,7 @@ public class NotificationPanelView extends PanelView implements
R.dimen.qs_falsing_threshold);
mPositionMinSideMargin = getResources().getDimensionPixelSize(
R.dimen.notification_panel_min_side_margin);
+ mStatusBarHeaderHeight = getResources().getDimensionPixelSize(R.dimen.status_bar_header_height);
}
public void updateResources() {
@@ -382,6 +681,7 @@ public class NotificationPanelView extends PanelView implements
@Override
public void onAnimationEnd(Animator animation) {
mQsSizeChangeAnimator = null;
+ mQsContainer.setHeightOverride(-1);
}
});
mQsSizeChangeAnimator.start();
@@ -548,6 +848,10 @@ public class NotificationPanelView extends PanelView implements
@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
+ // Reset locked direction
+ mLockedDirection = SwipeLockedDirection.UNKNOWN;
+ mCanDismissKeyguard = true;
+
if (mBlockTouches) {
return false;
}
@@ -558,10 +862,26 @@ public class NotificationPanelView extends PanelView implements
MetricsLogger.count(mContext, COUNTER_PANEL_OPEN_PEEK, 1);
return true;
}
+ if (mQsPanel.isOnSettingsPage() && isInQsArea(event.getX(), event.getY(), false)
+ && mQsExpanded) {
+ mIntercepting = false;
+ // we explicitly do not intercept the touch event here to let the qs settings page
+ // scroll as necessary while not blocking horizontal swipes and allowing the panel
+ // to be collapsed when grabbed below the qs settings page as well.
+ return false;
+ }
if (!isFullyCollapsed() && onQsIntercept(event)) {
return true;
}
- return super.onInterceptTouchEvent(event);
+
+ if (isKeyguardInteractiveAndShowing() || mStatusBar.isKeyguardShowingMedia() ||
+ (mUnlockMethodCache.isTrustManaged() && mAfforanceHelper.isOnLockIcon(event))) {
+ return super.onInterceptTouchEvent(event);
+ }
+
+ // We want both, we really do
+ return mSwipeHelper.onInterceptTouchEvent(event)
+ & super.onInterceptTouchEvent(event);
}
private boolean onQsIntercept(MotionEvent event) {
@@ -716,6 +1036,17 @@ public class NotificationPanelView extends PanelView implements
if (mBlockTouches) {
return false;
}
+
+ int action = event.getActionMasked();
+ if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
+ mKeyguardBottomArea.setBackground(null);
+ }
+
+ if (mDoubleTapToSleepEnabled
+ && mStatusBarState == StatusBarState.KEYGUARD
+ && event.getY() < mStatusBarHeaderHeight) {
+ mDoubleTapGesture.onTouchEvent(event);
+ }
initDownStates(event);
if (mListenForHeadsUp && !mHeadsUpTouchHelper.isTrackingHeadsUp()
&& mHeadsUpTouchHelper.onInterceptTouchEvent(event)) {
@@ -724,7 +1055,8 @@ public class NotificationPanelView extends PanelView implements
}
if ((!mIsExpanding || mHintAnimationRunning)
&& !mQsExpanded
- && mStatusBar.getBarState() != StatusBarState.SHADE) {
+ && (mStatusBar.getBarState() != StatusBarState.SHADE
+ || mLiveLockscreenController.getLiveLockScreenHasFocus())) {
mAfforanceHelper.onTouchEvent(event);
}
if (mOnlyAffordanceInThisMotion) {
@@ -738,10 +1070,39 @@ public class NotificationPanelView extends PanelView implements
MetricsLogger.count(mContext, COUNTER_PANEL_OPEN, 1);
updateVerticalPanelPosition(event.getX());
}
- super.onTouchEvent(event);
+
+ if (isKeyguardInteractiveAndShowing() || mStatusBar.isKeyguardShowingMedia() ||
+ (mUnlockMethodCache.isTrustManaged() && mAfforanceHelper.isOnLockIcon(event))) {
+ super.onTouchEvent(event);
+ return true;
+ }
+
+ if ((!mIsExpanding || mHintAnimationRunning)
+ && !mQsExpanded
+ && mLockedDirection != SwipeLockedDirection.VERTICAL
+ && mStatusBar.getBarState() != StatusBarState.SHADE) {
+ mSwipeHelper.onTouchEvent(event);
+ if (mSwipeHelper.isDragging()) {
+ mLockedDirection = SwipeLockedDirection.HORIZONTAL;
+ }
+ if (mLockedDirection == SwipeLockedDirection.HORIZONTAL) {
+ requestDisallowInterceptTouchEvent(true);
+ return true;
+ }
+ }
+
+ if (super.onTouchEvent(event)) {
+ mLockedDirection = SwipeLockedDirection.VERTICAL;
+ }
return true;
}
+ private boolean isKeyguardInteractiveAndShowing() {
+ return mLiveLockscreenController.getLiveLockScreenHasFocus() ||
+ mStatusBar.getBarState() != StatusBarState.KEYGUARD ||
+ !mLiveLockscreenController.isLiveLockScreenInteractive();
+ }
+
private boolean handleQsTouch(MotionEvent event) {
final int action = event.getActionMasked();
if (action == MotionEvent.ACTION_DOWN && getExpandedFraction() == 1f
@@ -773,7 +1134,8 @@ public class NotificationPanelView extends PanelView implements
mTwoFingerQsExpandPossible = true;
}
if (mTwoFingerQsExpandPossible && isOpenQsEvent(event)
- && event.getY(event.getActionIndex()) < mStatusBarMinHeight) {
+ && event.getY(event.getActionIndex()) < mStatusBarMinHeight
+ && mExpandedHeight <= mQsPeekHeight) {
MetricsLogger.count(mContext, COUNTER_PANEL_OPEN_QS, 1);
mQsExpandImmediate = true;
requestPanelHeightUpdate();
@@ -786,9 +1148,14 @@ public class NotificationPanelView extends PanelView implements
}
private boolean isInQsArea(float x, float y) {
+ return isInQsArea(x, y, true);
+ }
+
+ private boolean isInQsArea(float x, float y, boolean includeNotifications) {
return (x >= mScrollView.getX() && x <= mScrollView.getX() + mScrollView.getWidth()) &&
- (y <= mNotificationStackScroller.getBottomMostNotificationBottom()
- || y <= mQsContainer.getY() + mQsContainer.getHeight());
+ ((includeNotifications
+ && y <= mNotificationStackScroller.getBottomMostNotificationBottom())
+ || y <= mQsContainer.getY() + mQsContainer.getHeight());
}
private boolean isOpenQsEvent(MotionEvent event) {
@@ -806,12 +1173,27 @@ public class NotificationPanelView extends PanelView implements
&& (event.isButtonPressed(MotionEvent.BUTTON_SECONDARY)
|| event.isButtonPressed(MotionEvent.BUTTON_TERTIARY));
- return twoFingerDrag || stylusButtonClickDrag || mouseButtonClickDrag;
+ final float w = getMeasuredWidth();
+ final float x = event.getX();
+ float region = (w * (1.f/4.f)); // TODO overlay region fraction?
+ boolean showQsOverride = false;
+
+ switch (mOneFingerQuickSettingsIntercept) {
+ case 1: // Right side pulldown
+ showQsOverride = isLayoutRtl() ? (x < region) : (w - region < x);
+ break;
+ case 2: // Left side pulldown
+ showQsOverride = isLayoutRtl() ? (w - region < x) : (x < region);
+ break;
+ }
+ showQsOverride &= mStatusBarState == StatusBarState.SHADE;
+
+ return twoFingerDrag || showQsOverride || stylusButtonClickDrag || mouseButtonClickDrag;
}
private void handleQsDown(MotionEvent event) {
if (event.getActionMasked() == MotionEvent.ACTION_DOWN
- && shouldQuickSettingsIntercept(event.getX(), event.getY(), -1)) {
+ && shouldQuickSettingsIntercept(event.getX(), event.getRawY(), -1)) {
mQsTracking = true;
onQsExpansionStarted();
mInitialHeightOnTouch = mQsExpansionHeight;
@@ -893,8 +1275,13 @@ public class NotificationPanelView extends PanelView implements
mTrackingPointer = -1;
trackMovement(event);
float fraction = getQsExpansionFraction();
- if ((fraction != 0f || y >= mInitialTouchY)
- && (fraction != 1f || y <= mInitialTouchY)) {
+ final boolean fling = (fraction != 0f || y >= mInitialTouchY)
+ && (fraction != 1f || y <= mInitialTouchY);
+ final boolean flingExpand = Math.abs(getCurrentVelocity())
+ > mFlingAnimationUtils.getMinVelocityPxPerSecond();
+ final boolean detailFling = mDetailScrollLock && mQsExpanded
+ && flingExpand;
+ if ((fling && !mDetailScrollLock) || detailFling) {
flingQsWithCurrentVelocity(y,
event.getActionMasked() == MotionEvent.ACTION_CANCEL);
} else {
@@ -995,9 +1382,17 @@ public class NotificationPanelView extends PanelView implements
mStatusBarState = statusBarState;
mKeyguardShowing = keyguardShowing;
+ if (oldState != statusBarState && statusBarState == StatusBarState.KEYGUARD) {
+ mCanDismissKeyguard = true;
+ }
- if (goingToFullShade || (oldState == StatusBarState.KEYGUARD
- && statusBarState == StatusBarState.SHADE_LOCKED)) {
+ boolean keyguardToShadeLocked = oldState == StatusBarState.KEYGUARD
+ && statusBarState == StatusBarState.SHADE_LOCKED;
+ if (goingToFullShade || keyguardToShadeLocked) {
+ if (keyguardToShadeLocked && !mUserExpandedNotifications) {
+ mUserExpandedNotifications = true;
+ saveUserExpandedNotificationsInKeyguard(true);
+ }
animateKeyguardStatusBarOut();
animateHeaderSlidingIn();
} else if (oldState == StatusBarState.SHADE_LOCKED
@@ -1012,7 +1407,13 @@ public class NotificationPanelView extends PanelView implements
mAfforanceHelper.updatePreviews();
}
}
- if (keyguardShowing) {
+ if (oldState != StatusBarState.SHADE && statusBarState == StatusBarState.SHADE &&
+ !mUserUnlocked) {
+ mUserUnlocked = true;
+ saveUserUnlocked(true);
+ }
+ if (statusBarState == StatusBarState.KEYGUARD ||
+ statusBarState == StatusBarState.SHADE_LOCKED) {
updateDozingVisibilities(false /* animate */);
}
resetVerticalPanelPosition();
@@ -1289,8 +1690,7 @@ public class NotificationPanelView extends PanelView implements
if (mKeyguardShowing) {
updateHeaderKeyguard();
}
- if (mStatusBarState == StatusBarState.SHADE_LOCKED
- || mStatusBarState == StatusBarState.KEYGUARD) {
+ if (mStatusBarState == StatusBarState.KEYGUARD) {
updateKeyguardBottomAreaAlpha();
}
if (mStatusBarState == StatusBarState.SHADE && mQsExpanded
@@ -1469,8 +1869,10 @@ public class NotificationPanelView extends PanelView implements
View header = mKeyguardShowing ? mKeyguardStatusBar : mHeader;
boolean onHeader = x >= header.getX() && x <= header.getX() + header.getWidth()
&& y >= header.getTop() && y <= header.getBottom();
+
if (mQsExpanded) {
- return onHeader || (mScrollView.isScrolledToBottom() && yDiff < 0) && isInQsArea(x, y);
+ return onHeader || mDetailScrollLock
+ || (mScrollView.isScrolledToBottom() && yDiff < 0) && isInQsArea(x, y);
} else {
return onHeader;
}
@@ -1560,7 +1962,7 @@ public class NotificationPanelView extends PanelView implements
*/
private int getTempQsMaxExpansion() {
int qsTempMaxExpansion = mQsMaxExpansionHeight;
- if (mScrollYOverride != -1) {
+ if (mScrollYOverride != -1 && !mDetailScrollLock) {
qsTempMaxExpansion -= mScrollYOverride;
}
return qsTempMaxExpansion;
@@ -1727,6 +2129,9 @@ public class NotificationPanelView extends PanelView implements
}
private void updateHeaderKeyguardAlpha() {
+ if (mSwipeHelper.isDragging()) {
+ return;
+ }
float alphaQsExpansion = 1 - Math.min(1, getQsExpansionFraction() * 2);
mKeyguardStatusBar.setAlpha(Math.min(getKeyguardContentsAlpha(), alphaQsExpansion)
* mKeyguardStatusBarAnimateAlpha);
@@ -1741,6 +2146,9 @@ public class NotificationPanelView extends PanelView implements
private void updateKeyguardBottomAreaAlpha() {
float alpha = Math.min(getKeyguardContentsAlpha(), 1 - getQsExpansionFraction());
+ if (mLiveLockscreenController.getLiveLockScreenHasFocus()) {
+ alpha = 1f;
+ }
mKeyguardBottomArea.setAlpha(alpha);
mKeyguardBottomArea.setImportantForAccessibility(alpha == 0f
? IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
@@ -1802,7 +2210,6 @@ public class NotificationPanelView extends PanelView implements
private void setListening(boolean listening) {
mHeader.setListening(listening);
- mKeyguardStatusBar.setListening(listening);
mQsPanel.setListening(listening);
}
@@ -1965,10 +2372,12 @@ public class NotificationPanelView extends PanelView implements
mLaunchAnimationEndRunnable.run();
mLaunchAnimationEndRunnable = null;
}
+ mKeyguardBottomArea.setVisibility(View.GONE);
}
@Override
protected void startUnlockHintAnimation() {
+ mKeyguardBottomArea.expand(true);
super.startUnlockHintAnimation();
startHighlightIconAnimation(getCenterIcon());
}
@@ -2003,11 +2412,13 @@ public class NotificationPanelView extends PanelView implements
requestDisallowInterceptTouchEvent(true);
mOnlyAffordanceInThisMotion = true;
mQsTracking = false;
+ mKeyguardBottomArea.expand(true);
}
@Override
public void onSwipingAborted() {
mKeyguardBottomArea.unbindCameraPrewarmService(false /* launched */);
+ mKeyguardBottomArea.expand(false);
}
@Override
@@ -2016,6 +2427,8 @@ public class NotificationPanelView extends PanelView implements
return;
}
mHintAnimationRunning = true;
+ mKeyguardBottomArea.expand(true);
+ mKeyguardBottomArea.getIndicationView().animate().cancel();
mAfforanceHelper.startHintAnimation(rightIcon, new Runnable() {
@Override
public void run() {
@@ -2025,13 +2438,9 @@ public class NotificationPanelView extends PanelView implements
});
rightIcon = getLayoutDirection() == LAYOUT_DIRECTION_RTL ? !rightIcon : rightIcon;
if (rightIcon) {
- mStatusBar.onCameraHintStarted();
+ mStatusBar.onCameraHintStarted(mKeyguardBottomArea.getRightHint());
} else {
- if (mKeyguardBottomArea.isLeftVoiceAssist()) {
- mStatusBar.onVoiceAssistHintStarted();
- } else {
- mStatusBar.onPhoneHintStarted();
- }
+ mStatusBar.onLeftHintStarted(mKeyguardBottomArea.getLeftHint());
}
}
@@ -2223,6 +2632,11 @@ public class NotificationPanelView extends PanelView implements
public void onScreenTurningOn() {
mKeyguardStatusView.refreshTime();
+ if (shouldShowScreenOnHints()) {
+ startScreenOnHintAnimation(mLiveLockscreenController.isLiveLockScreenInteractive() &&
+ !mUserInteractedWithLiveLockScreen,
+ !mUserUnlocked, !mUserExpandedNotifications);
+ }
}
@Override
@@ -2342,7 +2756,7 @@ public class NotificationPanelView extends PanelView implements
* @param x the x-coordinate the touch event
*/
private void updateVerticalPanelPosition(float x) {
- if (mNotificationStackScroller.getWidth() * 1.75f > getWidth()) {
+ if (mNotificationStackScroller.getWidth() * 1.75f >= getWidth()) {
resetVerticalPanelPosition();
return;
}
@@ -2384,6 +2798,58 @@ public class NotificationPanelView extends PanelView implements
return mHeadsUpManager.hasPinnedHeadsUp() || mHeadsUpAnimatingAway;
}
+ public KeyguardBottomAreaView getKeyguardBottomArea() {
+ return mKeyguardBottomArea;
+ }
+
+ class SettingsObserver extends ContentObserver {
+ SettingsObserver(Handler handler) {
+ super(handler);
+ }
+
+ void observe() {
+ ContentResolver resolver = mContext.getContentResolver();
+ resolver.registerContentObserver(CMSettings.System.getUriFor(
+ CMSettings.System.STATUS_BAR_QUICK_QS_PULLDOWN), false, this);
+ resolver.registerContentObserver(CMSettings.System.getUriFor(
+ CMSettings.System.DOUBLE_TAP_SLEEP_GESTURE), false, this);
+ resolver.registerContentObserver(CMSettings.Secure.getUriFor(
+ CMSettings.Secure.LOCK_SCREEN_WEATHER_ENABLED), false, this);
+ update();
+ }
+
+ void unobserve() {
+ ContentResolver resolver = mContext.getContentResolver();
+ resolver.unregisterContentObserver(this);
+ }
+
+ @Override
+ public void onChange(boolean selfChange) {
+ update();
+ }
+
+ @Override
+ public void onChange(boolean selfChange, Uri uri) {
+ update();
+ }
+
+ public void update() {
+ ContentResolver resolver = mContext.getContentResolver();
+ mOneFingerQuickSettingsIntercept = CMSettings.System.getInt(
+ resolver, CMSettings.System.STATUS_BAR_QUICK_QS_PULLDOWN, 1);
+ mDoubleTapToSleepEnabled = CMSettings.System.getInt(
+ resolver, CMSettings.System.DOUBLE_TAP_SLEEP_GESTURE, 1) == 1;
+
+ boolean wasKeyguardWeatherEnabled = mKeyguardWeatherEnabled;
+ mKeyguardWeatherEnabled = CMSettings.Secure.getInt(
+ resolver, CMSettings.Secure.LOCK_SCREEN_WEATHER_ENABLED, 0) == 1;
+ if (mWeatherController != null
+ && wasKeyguardWeatherEnabled != mKeyguardWeatherEnabled) {
+ onWeatherChanged(mWeatherController.getWeatherInfo());
+ }
+ }
+ }
+
@Override
public boolean hasOverlappingRendering() {
return !mDozing;
@@ -2394,6 +2860,8 @@ public class NotificationPanelView extends PanelView implements
mLastCameraLaunchSource = KeyguardBottomAreaView.CAMERA_LAUNCH_SOURCE_POWER_DOUBLE_TAP;
} else if (source == StatusBarManager.CAMERA_LAUNCH_SOURCE_WIGGLE) {
mLastCameraLaunchSource = KeyguardBottomAreaView.CAMERA_LAUNCH_SOURCE_WIGGLE;
+ } else if (source == StatusBarManager.CAMERA_LAUNCH_SOURCE_SCREEN_GESTURE) {
+ mLastCameraLaunchSource = KeyguardBottomAreaView.CAMERA_LAUNCH_SOURCE_SCREEN_GESTURE;
} else {
// Default.
@@ -2403,7 +2871,8 @@ public class NotificationPanelView extends PanelView implements
// If we are launching it when we are occluded already we don't want it to animate,
// nor setting these flags, since the occluded state doesn't change anymore, hence it's
// never reset.
- if (!isFullyCollapsed()) {
+ if (!isFullyCollapsed() && mLastCameraLaunchSource ==
+ KeyguardBottomAreaView.CAMERA_LAUNCH_SOURCE_AFFORDANCE) {
mLaunchingAffordance = true;
setLaunchingAffordance(true);
} else {
@@ -2451,4 +2920,159 @@ public class NotificationPanelView extends PanelView implements
List<ActivityManager.RunningTaskInfo> tasks = am.getRunningTasks(1);
return !tasks.isEmpty() && pkgName.equals(tasks.get(0).topActivity.getPackageName());
}
+
+ public void setDetailRequestedScrollLock(boolean detailScrollFlag) {
+ if (mDetailScrollLock != detailScrollFlag) {
+ if (mStatusBarState != StatusBarState.SHADE) {
+ mDetailScrollLock = false;
+ } else {
+ mDetailScrollLock = detailScrollFlag;
+ }
+ if (!detailScrollFlag && getQsExpansionFraction() > 0.3f) {
+ flingSettings(getCurrentVelocity(), true, new Runnable() {
+ @Override
+ public void run() {
+ mStackScrollerOverscrolling = false;
+ mQsExpansionFromOverscroll = false;
+ updateQsState();
+ updateHeader();
+ updateMaxHeadsUpTranslation();
+ updatePanelExpanded();
+ requestLayout();
+ }
+ }, false);
+ } else {
+ requestLayout();
+ }
+ }
+ }
+
+ @Override
+ public void onWeatherChanged(WeatherController.WeatherInfo info) {
+ if (!mKeyguardWeatherEnabled || Double.isNaN(info.temp) || info.condition == null) {
+ mKeyguardWeatherInfo.setVisibility(GONE);
+ } else {
+ mKeyguardWeatherInfo.setText(mContext.getString(
+ R.string.keyguard_status_view_weather_format,
+ WeatherUtils.formatTemperature(info.temp, info.tempUnit),
+ info.condition));
+ mKeyguardWeatherInfo.setVisibility(VISIBLE);
+ }
+ }
+
+ private class SlideInAnimationListener implements ValueAnimator.AnimatorUpdateListener,
+ ValueAnimator.AnimatorListener {
+ @Override
+ public void onAnimationStart(Animator animator) {}
+
+ @Override
+ public void onAnimationEnd(Animator animator) {
+ animationFinished(animator);
+ }
+
+ @Override
+ public void onAnimationCancel(Animator animator) {
+ animationFinished(animator);
+ }
+
+ @Override
+ public void onAnimationRepeat(Animator animator) {}
+
+ @Override
+ public void onAnimationUpdate(ValueAnimator valueAnimator) {
+ View statusBarView = mStatusBar.getStatusBarWindow();
+ if (valueAnimator.getAnimatedFraction() > 0 &&
+ statusBarView.getVisibility() != View.VISIBLE) {
+ statusBarView.setVisibility(View.VISIBLE);
+ }
+ float translationX = (Float) valueAnimator.getAnimatedValue();
+ float alpha = valueAnimator.getAnimatedFraction();
+
+ mViewLinker.getParent().setTranslationX(translationX);
+ mViewLinker.getParent().setAlpha(alpha);
+
+ float alpha1 = ScrimController.SCRIM_BEHIND_ALPHA_KEYGUARD * alpha;
+ alpha1 = Math.max(0, alpha1);
+ mStatusBar.getScrimController().setScrimBehindColor(alpha1);
+ mLiveLockscreenController.getLiveLockScreenView()
+ .onLockscreenSlideOffsetChanged(alpha);
+ }
+
+ private void animationFinished(Animator animator) {
+ mLiveLockscreenController.onLiveLockScreenFocusChanged(false);
+ }
+ }
+
+ private SlideInAnimationListener mSlideInAnimationListener = new SlideInAnimationListener();
+
+ public void slideLockScreenIn() {
+ mNotificationStackScroller.setVisibility(View.VISIBLE);
+ mNotificationStackScroller.setAlpha(0f);
+ mNotificationStackScroller.setTranslationX(-mNotificationStackScroller.getWidth());
+ mKeyguardStatusView.setVisibility(View.VISIBLE);
+ mKeyguardStatusView.setAlpha(0f);
+ mKeyguardStatusView.setTranslationX(mNotificationStackScroller.getTranslationX());
+ mKeyguardStatusBar.setAlpha(0f);
+
+ mStatusBar.getScrimController().setScrimBehindColor(0f);
+ ValueAnimator animator = ValueAnimator.ofFloat(
+ mNotificationStackScroller.getTranslationX(),
+ 0f);
+ animator.setDuration(SLIDE_PANEL_IN_ANIMATION_DURATION);
+ animator.addUpdateListener(mSlideInAnimationListener);
+ animator.addListener(mSlideInAnimationListener);
+ animator.start();
+
+ if (!mUserReturnedFromLiveLockScreen) {
+ mUserReturnedFromLiveLockScreen = true;
+ saveUserReturnedFromLls(true);
+ }
+ }
+
+ private void saveBooleanSharedPreference(String key, boolean value) {
+ SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(mContext);
+ prefs.edit().putBoolean(key, value).apply();
+ }
+
+ private boolean getSharedPreferenceBoolean(String key, boolean defValue) {
+ SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(mContext);
+ return prefs.getBoolean(key, defValue);
+ }
+
+ private void saveUserExpandedNotificationsInKeyguard(boolean expanded) {
+ saveBooleanSharedPreference(KEY_USER_EXPANDED_NOTIFICATIONS_IN_KEYGUARD, expanded);
+ }
+
+ private boolean getUserExpandedNotificationsInKeyguard() {
+ return getSharedPreferenceBoolean(KEY_USER_EXPANDED_NOTIFICATIONS_IN_KEYGUARD, false);
+ }
+
+ private void saveUserInteractedWithLls(boolean interacted) {
+ saveBooleanSharedPreference(KEY_USER_INTERACTED_WITH_LLS, interacted);
+ }
+
+ private boolean getUserInteractedWithLls() {
+ return getSharedPreferenceBoolean(KEY_USER_INTERACTED_WITH_LLS, false);
+ }
+
+ private void saveUserUnlocked(boolean unlocked) {
+ saveBooleanSharedPreference(KEY_USER_UNLOCKED, unlocked);
+ }
+
+ private boolean getUserUnlocked() {
+ return getSharedPreferenceBoolean(KEY_USER_UNLOCKED, false);
+ }
+
+ private void saveUserReturnedFromLls(boolean revealed) {
+ saveBooleanSharedPreference(KEY_USER_RETURNED_FROM_LLS, revealed);
+ }
+
+ private boolean getUserReturnedFromLls() {
+ return getSharedPreferenceBoolean(KEY_USER_RETURNED_FROM_LLS, false);
+ }
+
+ public boolean shouldShowScreenOnHints() {
+ return mScreenOnHintsEnabled && mStatusBar.isDeviceProvisioned() &&
+ mStatusBarState == StatusBarState.KEYGUARD;
+ }
}
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 e03bcfb..3a2c84c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
@@ -19,6 +19,7 @@ package com.android.systemui.statusbar.phone;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
+import android.animation.PropertyValuesHolder;
import android.animation.ValueAnimator;
import android.content.Context;
import android.content.res.Configuration;
@@ -26,6 +27,7 @@ import android.content.res.Resources;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
+import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewTreeObserver;
import android.view.animation.AnimationUtils;
@@ -47,6 +49,9 @@ public abstract class PanelView extends FrameLayout {
public static final boolean DEBUG = PanelBar.DEBUG;
public static final String TAG = PanelView.class.getSimpleName();
+ private static final long ANIMATION_FADE_DURATION = 1000L;
+ private static final long HINT_DELAY_DURATION = 1500L;
+
private final void logf(String fmt, Object... args) {
Log.v(TAG, (mViewName != null ? (mViewName + ": ") : "") + String.format(fmt, args));
}
@@ -86,6 +91,20 @@ public abstract class PanelView extends FrameLayout {
private VelocityTrackerInterface mVelocityTracker;
private FlingAnimationUtils mFlingAnimationUtils;
+ private boolean mUpdateExpandOnLayout;
+ private View.OnLayoutChangeListener mLayoutChangeListener = new OnLayoutChangeListener() {
+ @Override
+ public void onLayoutChange(View v, int left, int top, int right, int bottom,
+ int oldLeft, int oldTop, int oldRight, int oldBottom) {
+ // update expand height
+ if (mHeightAnimator != null && mExpanding && mUpdateExpandOnLayout && !mJustPeeked) {
+ final int maxPanelHeight = getMaxPanelHeight();
+ final PropertyValuesHolder[] values = mHeightAnimator.getValues();
+ values[0].setFloatValues(maxPanelHeight);
+ }
+ }
+ };
+
/**
* Whether an instant expand request is currently pending and we are just waiting for layout.
*/
@@ -105,6 +124,9 @@ public abstract class PanelView extends FrameLayout {
private boolean mPeekPending;
private boolean mCollapseAfterPeek;
+ private boolean mShowExpandHint;
+ private boolean mShowUnlockHint;
+ private boolean mScreenOnHintAnimationRunning;
/**
* Speed-up factor to be used when {@link #mFlingCollapseRunnable} runs the next time.
@@ -122,6 +144,18 @@ public abstract class PanelView extends FrameLayout {
}
};
+ private ViewTreeObserver.OnGlobalLayoutListener mInstantExpandLayoutListener =
+ new ViewTreeObserver.OnGlobalLayoutListener() {
+ @Override
+ public void onGlobalLayout() {
+ if (mStatusBar.getStatusBarWindow().getHeight() != mStatusBar.getStatusBarHeight()) {
+ getViewTreeObserver().removeOnGlobalLayoutListener(this);
+ setExpandedFraction(1f);
+ mInstantExpanding = false;
+ }
+ }
+ };
+
protected void onExpandingFinished() {
mBar.onExpandingFinished();
}
@@ -623,7 +657,7 @@ public abstract class PanelView extends FrameLayout {
flingToHeight(vel, expand, target, collapseSpeedUpFactor, expandBecauseOfFalsing);
}
- protected void flingToHeight(float vel, boolean expand, float target,
+ protected void flingToHeight(float vel, final boolean expand, float target,
float collapseSpeedUpFactor, boolean expandBecauseOfFalsing) {
// Hack to make the expand transition look nice when clear all button is visible - we make
// the animation only to the last notification, and then jump to the maximum panel height so
@@ -644,8 +678,9 @@ public abstract class PanelView extends FrameLayout {
if (expandBecauseOfFalsing) {
vel = 0;
}
+ mUpdateExpandOnLayout = isFullyCollapsed();
mFlingAnimationUtils.apply(animator, mExpandedHeight, target, vel, getHeight());
- if (expandBecauseOfFalsing) {
+ if (expandBecauseOfFalsing && vel == 0) {
animator.setDuration(350);
}
} else {
@@ -659,16 +694,23 @@ public abstract class PanelView extends FrameLayout {
/ collapseSpeedUpFactor));
}
}
+
animator.addListener(new AnimatorListenerAdapter() {
private boolean mCancelled;
@Override
+ public void onAnimationStart(Animator animation) {
+ if (expand) PanelView.this.addOnLayoutChangeListener(mLayoutChangeListener);
+ }
+
+ @Override
public void onAnimationCancel(Animator animation) {
mCancelled = true;
}
@Override
public void onAnimationEnd(Animator animation) {
+ if (expand) PanelView.this.removeOnLayoutChangeListener(mLayoutChangeListener);
if (clearAllExpandHack && !mCancelled) {
setExpandedHeightInternal(getMaxPanelHeight());
}
@@ -874,18 +916,7 @@ public abstract class PanelView extends FrameLayout {
// Wait for window manager to pickup the change, so we know the maximum height of the panel
// then.
- getViewTreeObserver().addOnGlobalLayoutListener(
- new ViewTreeObserver.OnGlobalLayoutListener() {
- @Override
- public void onGlobalLayout() {
- if (mStatusBar.getStatusBarWindow().getHeight()
- != mStatusBar.getStatusBarHeight()) {
- getViewTreeObserver().removeOnGlobalLayoutListener(this);
- setExpandedFraction(1f);
- mInstantExpanding = false;
- }
- }
- });
+ getViewTreeObserver().addOnGlobalLayoutListener(mInstantExpandLayoutListener);
// Make sure a layout really happens.
requestLayout();
@@ -894,6 +925,7 @@ public abstract class PanelView extends FrameLayout {
public void instantCollapse() {
abortAnimations();
setExpandedFraction(0f);
+ getViewTreeObserver().removeOnGlobalLayoutListener(mInstantExpandLayoutListener);
if (mExpanding) {
notifyExpandingFinished();
}
@@ -902,6 +934,7 @@ public abstract class PanelView extends FrameLayout {
private void abortAnimations() {
cancelPeek();
cancelHeightAnimator();
+ mKeyguardBottomArea.getIndicationView().animate().cancel();
removeCallbacks(mPostCollapseRunnable);
removeCallbacks(mFlingCollapseRunnable);
}
@@ -920,6 +953,8 @@ public abstract class PanelView extends FrameLayout {
}
cancelPeek();
notifyExpandingStarted();
+ mKeyguardBottomArea.getIndicationView().animate().cancel();
+ mStatusBar.onUnlockHintStarted();
startUnlockHintAnimationPhase1(new Runnable() {
@Override
public void run() {
@@ -928,8 +963,8 @@ public abstract class PanelView extends FrameLayout {
mHintAnimationRunning = false;
}
});
- mStatusBar.onUnlockHintStarted();
mHintAnimationRunning = true;
+ mShowExpandHint = false;
}
/**
@@ -963,6 +998,7 @@ public abstract class PanelView extends FrameLayout {
mKeyguardBottomArea.getIndicationView().animate()
.translationY(-mHintDistance)
.setDuration(250)
+ .setStartDelay(0)
.setInterpolator(mFastOutSlowInInterpolator)
.withEndAction(new Runnable() {
@Override
@@ -985,17 +1021,213 @@ public abstract class PanelView extends FrameLayout {
animator.setDuration(450);
animator.setInterpolator(mBounceInterpolator);
animator.addListener(new AnimatorListenerAdapter() {
+ private boolean mCancelled;
+
+ @Override
+ public void onAnimationCancel(Animator animation) {
+ mCancelled = true;
+ }
+
@Override
public void onAnimationEnd(Animator animation) {
mHeightAnimator = null;
- onAnimationFinished.run();
- notifyBarPanelExpansionChanged();
+ if (mCancelled) {
+ onAnimationFinished.run();
+ } else {
+ if (mShowExpandHint) {
+ startUnlockHintFadeOutAnimationPhase(onAnimationFinished);
+ } else {
+ onAnimationFinished.run();
+ notifyBarPanelExpansionChanged();
+ }
+ }
}
});
animator.start();
mHeightAnimator = animator;
}
+ /**
+ * Fade in unlock hint
+ */
+ private void startUnlockHintFadeInAnimationPhase(final Runnable onAnimationFinished) {
+ mStatusBar.onUnlockHintStarted();
+ mKeyguardBottomArea.getIndicationView().animate()
+ .alpha(1)
+ .setDuration(ANIMATION_FADE_DURATION)
+ .setStartDelay(0)
+ .setInterpolator(null)
+ .withEndAction(new Runnable() {
+ @Override
+ public void run() {
+ if (mShowExpandHint) {
+ startUnlockHintFadeOutAnimationPhase(onAnimationFinished);
+ } else {
+ onAnimationFinished.run();
+ notifyBarPanelExpansionChanged();
+ }
+ }
+ })
+ .start();
+ }
+
+ /**
+ * Fade out unlock hint
+ */
+ private void startUnlockHintFadeOutAnimationPhase(final Runnable onAnimationFinished) {
+ mKeyguardBottomArea.getIndicationView().animate()
+ .alpha(0)
+ .setDuration(ANIMATION_FADE_DURATION)
+ .setStartDelay(HINT_DELAY_DURATION)
+ .setInterpolator(null)
+ .withEndAction(new Runnable() {
+ @Override
+ public void run() {
+ startExpandHintAnimation(onAnimationFinished);
+ }
+ })
+ .start();
+ }
+
+ /**
+ * Fade in Lls hint
+ */
+ private void startLlsHintFadeInAnimationPhase(final Runnable onAnimationFinished) {
+ mKeyguardBottomArea.getIndicationView().setAlpha(0);
+ mKeyguardBottomArea.getIndicationView().animate()
+ .alpha(1)
+ .setDuration(ANIMATION_FADE_DURATION)
+ .setStartDelay(0)
+ .setInterpolator(null)
+ .withEndAction(new Runnable() {
+ @Override
+ public void run() {
+ if (mShowUnlockHint || mShowExpandHint) {
+ startLlsHintFadeOutAnimationPhase(onAnimationFinished);
+ } else {
+ onAnimationFinished.run();;
+ }
+ }
+ })
+ .start();
+ }
+
+ /**
+ * Fade out Lls hint
+ */
+ private void startLlsHintFadeOutAnimationPhase(final Runnable onAnimationFinished) {
+ mKeyguardBottomArea.getIndicationView().animate()
+ .alpha(0)
+ .setDuration(ANIMATION_FADE_DURATION)
+ .setStartDelay(HINT_DELAY_DURATION)
+ .setInterpolator(null)
+ .withEndAction(new Runnable() {
+ @Override
+ public void run() {
+ if (mShowUnlockHint) {
+ startUnlockHintFadeInAnimationPhase(onAnimationFinished);
+ } else if (mShowExpandHint) {
+ startExpandHintAnimation(onAnimationFinished);
+ } else {
+ onAnimationFinished.run();
+ }
+ }
+ })
+ .start();
+ }
+
+ /**
+ * Fade in expand hint
+ */
+ private void startExpandHintAnimation(final Runnable onAnimationFinished) {
+ mStatusBar.onExpandHintStarted();
+ mKeyguardBottomArea.getIndicationView().animate()
+ .alpha(1)
+ .setDuration(ANIMATION_FADE_DURATION)
+ .setStartDelay(0)
+ .setInterpolator(null)
+ .withEndAction(new Runnable() {
+ @Override
+ public void run() {
+ onAnimationFinished.run();
+ }
+ })
+ .start();
+ }
+
+ /**
+ * Show notifications hint (swipe right hint)
+ */
+ protected void startShowNotificationsHintAnimation() {
+ cancelPeek();
+ mStatusBar.onNotificationsHintStarted();
+ mHintAnimationRunning = true;
+ mKeyguardBottomArea.getIndicationView().setAlpha(0);
+ mKeyguardBottomArea.getIndicationView().animate()
+ .alpha(1)
+ .setDuration(ANIMATION_FADE_DURATION)
+ .setInterpolator(null)
+ .withEndAction(new Runnable() {
+ @Override
+ public void run() {
+ mStatusBar.onHintFinished();
+ mHintAnimationRunning = false;
+ }
+ })
+ .start();
+ }
+
+ protected void startScreenOnHintAnimation(boolean showSwipeLeftHint, boolean showUnlockHint,
+ boolean showExpandHint) {
+ // We don't need to hint the user if an animation is already running or the user is changing
+ // the expansion.
+ if (mHintAnimationRunning || mScreenOnHintAnimationRunning) return;
+
+ final View indicationView = mKeyguardBottomArea.getIndicationView();
+ indicationView.animate().cancel();
+ indicationView.animate().setListener(
+ new Animator.AnimatorListener() {
+ @Override
+ public void onAnimationStart(Animator animation) { }
+
+ @Override
+ public void onAnimationEnd(Animator animation) { }
+
+ @Override
+ public void onAnimationCancel(Animator animation) {
+ mScreenOnHintAnimationRunning = false;
+ indicationView.setAlpha(1f);
+ }
+
+ @Override
+ public void onAnimationRepeat(Animator animation) { }
+ });
+
+ Runnable r = new Runnable() {
+ @Override
+ public void run() {
+ mStatusBar.onHintFinished();
+ mScreenOnHintAnimationRunning = false;
+ }
+ };
+ if (showSwipeLeftHint) {
+ mStatusBar.onLlsHintStarted();
+ startLlsHintFadeInAnimationPhase(r);
+ } else if (showUnlockHint) {
+ mStatusBar.onUnlockHintStarted();
+ startUnlockHintFadeInAnimationPhase(r);
+ } else if (showExpandHint) {
+ mStatusBar.onExpandHintStarted();
+ startExpandHintAnimation(r);
+ } else {
+ return;
+ }
+ indicationView.setAlpha(0);
+ mShowUnlockHint = showUnlockHint;
+ mShowExpandHint = showExpandHint;
+ mScreenOnHintAnimationRunning = true;
+ }
+
private ValueAnimator createHeightAnimator(float targetHeight) {
ValueAnimator animator = ValueAnimator.ofFloat(mExpandedHeight, targetHeight);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
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 73361bd..7df8346 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
@@ -26,13 +26,20 @@ import android.app.IActivityManager;
import android.app.Notification;
import android.app.PendingIntent;
import android.app.StatusBarManager;
+import android.app.WallpaperManager;
+import android.content.ActivityNotFoundException;
import android.content.BroadcastReceiver;
import android.content.ComponentCallbacks2;
+import android.content.ComponentName;
+import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.IPackageManager;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
import android.content.res.Configuration;
+import android.content.res.ThemeConfig;
import android.content.res.Resources;
import android.database.ContentObserver;
import android.graphics.Bitmap;
@@ -54,10 +61,12 @@ import android.media.session.MediaSession;
import android.media.session.MediaSessionManager;
import android.media.session.PlaybackState;
import android.os.AsyncTask;
+import android.os.BatteryManager;
import android.os.Bundle;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
+import android.os.IPowerManager;
import android.os.Message;
import android.os.PowerManager;
import android.os.Process;
@@ -75,44 +84,55 @@ import android.util.ArraySet;
import android.util.DisplayMetrics;
import android.util.EventLog;
import android.util.Log;
+import android.util.Pair;
import android.view.Display;
import android.view.KeyEvent;
import android.view.LayoutInflater;
+import android.view.HapticFeedbackConstants;
import android.view.MotionEvent;
import android.view.ThreadedRenderer;
import android.view.VelocityTracker;
import android.view.View;
+import android.view.WindowManagerGlobal;
+import android.view.ViewConfiguration;
+import android.view.ViewGroup;
import android.view.ViewGroup.LayoutParams;
import android.view.ViewStub;
+import android.view.ViewTreeObserver;
import android.view.WindowManager;
-import android.view.WindowManagerGlobal;
import android.view.accessibility.AccessibilityEvent;
import android.view.animation.AccelerateDecelerateInterpolator;
import android.view.animation.AccelerateInterpolator;
import android.view.animation.Interpolator;
import android.view.animation.LinearInterpolator;
import android.view.animation.PathInterpolator;
+import android.widget.AdapterView;
+import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.TextView;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.statusbar.NotificationVisibility;
import com.android.internal.statusbar.StatusBarIcon;
+import com.android.internal.util.cm.ActionUtils;
import com.android.keyguard.KeyguardHostView.OnDismissAction;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.keyguard.KeyguardUpdateMonitorCallback;
import com.android.keyguard.ViewMediatorCallback;
import com.android.systemui.BatteryMeterView;
+import com.android.systemui.BatteryLevelTextView;
import com.android.systemui.DemoMode;
+import com.android.systemui.DockBatteryMeterView;
import com.android.systemui.EventLogConstants;
import com.android.systemui.EventLogTags;
import com.android.systemui.Prefs;
import com.android.systemui.R;
import com.android.systemui.assist.AssistManager;
+import com.android.systemui.cm.UserContentObserver;
import com.android.systemui.doze.DozeHost;
import com.android.systemui.doze.DozeLog;
import com.android.systemui.keyguard.KeyguardViewMediator;
-import com.android.systemui.qs.QSPanel;
+import com.android.systemui.qs.QSDragPanel;
import com.android.systemui.recents.ScreenPinningRequest;
import com.android.systemui.statusbar.ActivatableNotificationView;
import com.android.systemui.statusbar.BackDropView;
@@ -124,40 +144,51 @@ import com.android.systemui.statusbar.EmptyShadeView;
import com.android.systemui.statusbar.ExpandableNotificationRow;
import com.android.systemui.statusbar.GestureRecorder;
import com.android.systemui.statusbar.KeyguardIndicationController;
+import com.android.systemui.statusbar.MediaExpandableNotificationRow;
import com.android.systemui.statusbar.NotificationData;
import com.android.systemui.statusbar.NotificationData.Entry;
import com.android.systemui.statusbar.NotificationOverflowContainer;
import com.android.systemui.statusbar.ScrimView;
import com.android.systemui.statusbar.SignalClusterView;
import com.android.systemui.statusbar.SpeedBumpView;
+import com.android.systemui.statusbar.StatusBarIconView;
import com.android.systemui.statusbar.StatusBarState;
+import com.android.systemui.statusbar.VisualizerView;
import com.android.systemui.statusbar.phone.UnlockMethodCache.OnUnlockMethodChangedListener;
import com.android.systemui.statusbar.policy.AccessibilityController;
import com.android.systemui.statusbar.policy.BatteryController;
-import com.android.systemui.statusbar.policy.BatteryController.BatteryStateChangeCallback;
+import com.android.systemui.statusbar.policy.BatteryStateRegistar.BatteryStateChangeCallback;
import com.android.systemui.statusbar.policy.BluetoothControllerImpl;
import com.android.systemui.statusbar.policy.BrightnessMirrorController;
import com.android.systemui.statusbar.policy.CastControllerImpl;
+import com.android.systemui.statusbar.policy.DockBatteryController;
import com.android.systemui.statusbar.policy.FlashlightController;
import com.android.systemui.statusbar.policy.HeadsUpManager;
import com.android.systemui.statusbar.policy.HotspotControllerImpl;
import com.android.systemui.statusbar.policy.KeyButtonView;
import com.android.systemui.statusbar.policy.KeyguardMonitor;
import com.android.systemui.statusbar.policy.KeyguardUserSwitcher;
+import com.android.systemui.statusbar.policy.LiveLockScreenController;
import com.android.systemui.statusbar.policy.LocationControllerImpl;
+import com.android.systemui.statusbar.policy.NetworkController;
import com.android.systemui.statusbar.policy.NetworkControllerImpl;
import com.android.systemui.statusbar.policy.NextAlarmController;
import com.android.systemui.statusbar.policy.PreviewInflater;
import com.android.systemui.statusbar.policy.RotationLockControllerImpl;
import com.android.systemui.statusbar.policy.SecurityControllerImpl;
+import com.android.systemui.statusbar.policy.SuControllerImpl;
import com.android.systemui.statusbar.policy.UserInfoController;
import com.android.systemui.statusbar.policy.UserSwitcherController;
+import com.android.systemui.statusbar.policy.WeatherControllerImpl;
import com.android.systemui.statusbar.policy.ZenModeController;
import com.android.systemui.statusbar.stack.NotificationStackScrollLayout;
import com.android.systemui.statusbar.stack.NotificationStackScrollLayout.OnChildLocationsChangedListener;
import com.android.systemui.statusbar.stack.StackStateAnimator;
import com.android.systemui.statusbar.stack.StackViewState;
import com.android.systemui.volume.VolumeComponent;
+import cyanogenmod.app.CMContextConstants;
+import cyanogenmod.app.CustomTileListenerService;
+import cyanogenmod.app.StatusBarPanelCustomTile;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -183,6 +214,9 @@ import static com.android.systemui.statusbar.phone.BarTransitions.MODE_TRANSLUCE
import static com.android.systemui.statusbar.phone.BarTransitions.MODE_TRANSPARENT;
import static com.android.systemui.statusbar.phone.BarTransitions.MODE_WARNING;
+import cyanogenmod.providers.CMSettings;
+import cyanogenmod.themes.IThemeService;
+
public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
DragDownHelper.DragDownCallback, ActivityStarter, OnUnlockMethodChangedListener,
HeadsUpManager.OnHeadsUpChangedListener {
@@ -231,6 +265,10 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
.setUsage(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION)
.build();
+ private static final float BRIGHTNESS_CONTROL_PADDING = 0.15f;
+ private static final int BRIGHTNESS_CONTROL_LONG_PRESS_TIMEOUT = 750; // ms
+ private static final int BRIGHTNESS_CONTROL_LINGER_THRESHOLD = 20;
+
public static final int FADE_KEYGUARD_START_DELAY = 100;
public static final int FADE_KEYGUARD_DURATION = 300;
public static final int FADE_KEYGUARD_DURATION_PULSING = 96;
@@ -258,7 +296,9 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
// These are no longer handled by the policy, because we need custom strategies for them
BluetoothControllerImpl mBluetoothController;
SecurityControllerImpl mSecurityController;
+ BatteryManager mBatteryManager;
BatteryController mBatteryController;
+ DockBatteryController mDockBatteryController;
LocationControllerImpl mLocationController;
NetworkControllerImpl mNetworkController;
HotspotControllerImpl mHotspotController;
@@ -274,7 +314,10 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
KeyguardMonitor mKeyguardMonitor;
BrightnessMirrorController mBrightnessMirrorController;
AccessibilityController mAccessibilityController;
+ WeatherControllerImpl mWeatherController;
+ SuControllerImpl mSuController;
FingerprintUnlockController mFingerprintUnlockController;
+ LiveLockScreenController mLiveLockScreenController;
int mNaturalBarHeight = -1;
@@ -282,7 +325,8 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
Point mCurrentDisplaySize = new Point();
StatusBarWindowView mStatusBarWindow;
- PhoneStatusBarView mStatusBarView;
+ FrameLayout mStatusBarWindowContent;
+ private PhoneStatusBarView mStatusBarView;
private int mStatusBarWindowState = WINDOW_STATE_SHOWING;
private StatusBarWindowManager mStatusBarWindowManager;
private UnlockMethodCache mUnlockMethodCache;
@@ -290,6 +334,8 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
private boolean mWakeUpComingFromTouch;
private PointF mWakeUpTouchLocation;
private boolean mScreenTurningOn;
+ private BatteryMeterView mBatteryView;
+ private BatteryLevelTextView mBatteryTextView;
int mPixelFormat;
Object mQueueLock = new Object();
@@ -302,7 +348,9 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
TextView mNotificationPanelDebugText;
// settings
- private QSPanel mQSPanel;
+ private QSDragPanel mQSPanel;
+ private QSTileHost mQSTileHost;
+ private DevForceNavbarObserver mDevForceNavbarObserver;
// top bar
StatusBarHeaderView mHeader;
@@ -316,15 +364,20 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
private boolean mKeyguardGoingAway;
// Keyguard is actually fading away now.
private boolean mKeyguardFadingAway;
+ private boolean mKeyguardShowingMedia;
private long mKeyguardFadingAwayDelay;
private long mKeyguardFadingAwayDuration;
+ private Bitmap mKeyguardWallpaper;
+
int mKeyguardMaxNotificationCount;
boolean mExpandedVisible;
private int mNavigationBarWindowState = WINDOW_STATE_SHOWING;
+ private int mStatusBarHeaderHeight;
+
// the tracker view
int mTrackingPosition; // the position of the top of the tracking view.
@@ -335,6 +388,18 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
int[] mAbsPos = new int[2];
ArrayList<Runnable> mPostCollapseRunnables = new ArrayList<>();
+ private boolean mAutomaticBrightness;
+ private boolean mBrightnessControl;
+ private boolean mBrightnessChanged;
+ private float mScreenWidth;
+ private int mMinBrightness;
+ private boolean mJustPeeked;
+ int mLinger;
+ int mInitialTouchX;
+ int mInitialTouchY;
+
+ private boolean mRecreating = false;
+
// for disabling the status bar
int mDisabled1 = 0;
int mDisabled2 = 0;
@@ -357,6 +422,127 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
private int mNavigationIconHints = 0;
private HandlerThread mHandlerThread;
+ private IThemeService mThemeService;
+ private long mLastThemeChangeTime = 0;
+
+ Runnable mLongPressBrightnessChange = new Runnable() {
+ @Override
+ public void run() {
+ mStatusBarView.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
+ adjustBrightness(mInitialTouchX);
+ mLinger = BRIGHTNESS_CONTROL_LINGER_THRESHOLD + 1;
+ }
+ };
+
+ // Custom Recents Long Press
+ // - Tracks Event state for custom (user-configurable) Long Presses.
+ private boolean mCustomRecentsLongPressed = false;
+ // - The ArrayList is updated when packages are added and removed.
+ private List<ComponentName> mCustomRecentsLongPressHandlerCandidates = new ArrayList<>();
+ // - The custom Recents Long Press, if selected. When null, use default (switch last app).
+ private ComponentName mCustomRecentsLongPressHandler = null;
+
+ class SettingsObserver extends UserContentObserver {
+ SettingsObserver(Handler handler) {
+ super(handler);
+ }
+
+ @Override
+ protected void observe() {
+ super.observe();
+
+ ContentResolver resolver = mContext.getContentResolver();
+ resolver.registerContentObserver(CMSettings.System.getUriFor(
+ CMSettings.System.STATUS_BAR_BRIGHTNESS_CONTROL), false, this,
+ UserHandle.USER_ALL);
+ resolver.registerContentObserver(Settings.System.getUriFor(
+ Settings.System.SCREEN_BRIGHTNESS_MODE), false, this, UserHandle.USER_ALL);
+ resolver.registerContentObserver(CMSettings.System.getUriFor(
+ CMSettings.System.NAVBAR_LEFT_IN_LANDSCAPE), false, this, UserHandle.USER_ALL);
+ resolver.registerContentObserver(CMSettings.System.getUriFor(
+ CMSettings.Secure.RECENTS_LONG_PRESS_ACTIVITY), false, this);
+ update();
+ }
+
+ @Override
+ protected void unobserve() {
+ super.unobserve();
+ ContentResolver resolver = mContext.getContentResolver();
+ resolver.unregisterContentObserver(this);
+ }
+
+ @Override
+ public void update() {
+ ContentResolver resolver = mContext.getContentResolver();
+ int mode = Settings.System.getIntForUser(mContext.getContentResolver(),
+ Settings.System.SCREEN_BRIGHTNESS_MODE,
+ Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL,
+ UserHandle.USER_CURRENT);
+ mAutomaticBrightness = mode != Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL;
+ mBrightnessControl = CMSettings.System.getIntForUser(
+ resolver, CMSettings.System.STATUS_BAR_BRIGHTNESS_CONTROL, 0,
+ UserHandle.USER_CURRENT) == 1;
+
+ if (mNavigationBarView != null) {
+ boolean navLeftInLandscape = CMSettings.System.getIntForUser(resolver,
+ CMSettings.System.NAVBAR_LEFT_IN_LANDSCAPE, 0, UserHandle.USER_CURRENT) == 1;
+ mNavigationBarView.setLeftInLandscape(navLeftInLandscape);
+ }
+
+ // This method reads CMSettings.Secure.RECENTS_LONG_PRESS_ACTIVITY
+ updateCustomRecentsLongPressHandler(false);
+ }
+ }
+
+ public void setStatusBarViewVisibility(boolean visible) {
+ mStatusBarView.setVisibility(visible ? View.VISIBLE : View.INVISIBLE);
+ }
+
+ class DevForceNavbarObserver extends UserContentObserver {
+ DevForceNavbarObserver(Handler handler) {
+ super(handler);
+ }
+
+ @Override
+ protected void observe() {
+ super.observe();
+ ContentResolver resolver = mContext.getContentResolver();
+ resolver.registerContentObserver(CMSettings.Global.getUriFor(
+ CMSettings.Global.DEV_FORCE_SHOW_NAVBAR), false, this, UserHandle.USER_ALL);
+ }
+
+ @Override
+ public void update() {
+ boolean visible = CMSettings.Global.getIntForUser(mContext.getContentResolver(),
+ CMSettings.Global.DEV_FORCE_SHOW_NAVBAR, 0, UserHandle.USER_CURRENT) == 1;
+
+ if (visible) {
+ forceAddNavigationBar();
+ } else {
+ removeNavigationBar();
+ }
+
+ // Send a broadcast to Settings to update Key disabling when user changes
+ Intent intent = new Intent("com.cyanogenmod.action.UserChanged");
+ intent.setPackage("com.android.settings");
+ mContext.sendBroadcastAsUser(intent, new UserHandle(UserHandle.USER_CURRENT));
+ }
+ }
+
+ private void forceAddNavigationBar() {
+ // If we have no Navbar view and we should have one, create it
+ if (mNavigationBarView != null) {
+ return;
+ }
+
+ mNavigationBarView =
+ (NavigationBarView) View.inflate(mContext, R.layout.navigation_bar, null);
+
+ mNavigationBarView.setDisabledFlags(mDisabled1);
+ mNavigationBarView.setBar(this);
+ addNavigationBar(true); // dynamically adding nav bar, reset System UI visibility!
+ }
+
// ensure quick settings is disabled until the current user makes it through the setup wizard
private boolean mUserSetup = false;
private ContentObserver mUserSetupObserver = new ContentObserver(new Handler()) {
@@ -438,6 +624,9 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
private PorterDuffXfermode mSrcXferMode = new PorterDuffXfermode(PorterDuff.Mode.SRC);
private PorterDuffXfermode mSrcOverXferMode = new PorterDuffXfermode(PorterDuff.Mode.SRC_OVER);
+ private VisualizerView mVisualizerView;
+ private boolean mScreenOn;
+
private MediaSessionManager mMediaSessionManager;
private MediaController mMediaController;
private String mMediaNotificationKey;
@@ -453,6 +642,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
clearCurrentMediaNotification();
updateMediaMetaData(true);
}
+ mVisualizerView.setPlaying(state.getState() == PlaybackState.STATE_PLAYING);
}
}
@@ -603,6 +793,10 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
private RankingMap mLatestRankingMap;
private boolean mNoAnimationOnNextBarModeChange;
+ public ScrimController getScrimController() {
+ return mScrimController;
+ }
+
@Override
public void start() {
mDisplay = ((WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE))
@@ -611,6 +805,16 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
mScrimSrcModeEnabled = mContext.getResources().getBoolean(
R.bool.config_status_bar_scrim_behind_use_src);
+ ThemeConfig currentTheme = mContext.getResources().getConfiguration().themeConfig;
+ if (currentTheme != null) {
+ mCurrentTheme = (ThemeConfig)currentTheme.clone();
+ } else {
+ mCurrentTheme = ThemeConfig.getBootTheme(mContext.getContentResolver());
+ }
+
+ mStatusBarWindow = new StatusBarWindowView(mContext, null);
+ mStatusBarWindow.setService(this);
+
super.start(); // calls createAndAddWindows()
mMediaSessionManager
@@ -618,11 +822,25 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
// TODO: use MediaSessionManager.SessionListener to hook us up to future updates
// in session state
- addNavigationBar();
+ addNavigationBar(false);
+
+ // Developer options - Force Navigation bar
+ try {
+ boolean needsNav = mWindowManagerService.needsNavigationBar();
+ if (!needsNav) {
+ mDevForceNavbarObserver = new DevForceNavbarObserver(mHandler);
+ mDevForceNavbarObserver.observe();
+ }
+ } catch (RemoteException ex) {
+ // no window manager? good luck with that
+ }
+
+ SettingsObserver observer = new SettingsObserver(mHandler);
+ observer.observe();
// Lastly, call to the icon policy to install/update all the icons.
mIconPolicy = new PhoneStatusBarPolicy(mContext, mCastController, mHotspotController,
- mUserInfoController, mBluetoothController);
+ mUserInfoController, mBluetoothController, mSuController);
mIconPolicy.setCurrentUserSetup(mUserSetup);
mSettingsObserver.onChange(false); // set up
@@ -635,6 +853,11 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
Settings.Global.getUriFor(SETTING_HEADS_UP_TICKER), true,
mHeadsUpObserver);
}
+
+ WallpaperManager wallpaperManager = (WallpaperManager) mContext.getSystemService(
+ Context.WALLPAPER_SERVICE);
+ mKeyguardWallpaper = wallpaperManager.getKeyguardBitmap();
+
mUnlockMethodCache = UnlockMethodCache.getInstance(mContext);
mUnlockMethodCache.addListener(this);
startKeyguard();
@@ -649,6 +872,11 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
notifyUserAboutHiddenNotifications();
mScreenPinningRequest = new ScreenPinningRequest(mContext);
+
+ updateCustomRecentsLongPressHandler(true);
+
+ mThemeService = IThemeService.Stub.asInterface(ServiceManager.getService(
+ CMContextConstants.CM_THEME_SERVICE));
}
// ================================================================================
@@ -659,13 +887,16 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
Resources res = context.getResources();
+ mScreenWidth = (float) res.getDisplayMetrics().widthPixels;
+ mMinBrightness = res.getInteger(com.android.internal.R.integer.config_screenBrightnessDim);
+
updateDisplaySize(); // populates mDisplayMetrics
- updateResources();
+ updateResources(null);
- mStatusBarWindow = (StatusBarWindowView) View.inflate(context,
+ mStatusBarWindowContent = (FrameLayout) View.inflate(context,
R.layout.super_status_bar, null);
mStatusBarWindow.setService(this);
- mStatusBarWindow.setOnTouchListener(new View.OnTouchListener() {
+ mStatusBarWindowContent.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
checkUserAutohide(v, event);
@@ -674,17 +905,17 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
animateCollapsePanels();
}
}
- return mStatusBarWindow.onTouchEvent(event);
+ return mStatusBarWindowContent.onTouchEvent(event);
}
});
- mStatusBarView = (PhoneStatusBarView) mStatusBarWindow.findViewById(R.id.status_bar);
+ mStatusBarView = (PhoneStatusBarView) mStatusBarWindowContent.findViewById(R.id.status_bar);
mStatusBarView.setBar(this);
- PanelHolder holder = (PanelHolder) mStatusBarWindow.findViewById(R.id.panel_holder);
+ PanelHolder holder = (PanelHolder) mStatusBarWindowContent.findViewById(R.id.panel_holder);
mStatusBarView.setPanelHolder(holder);
- mNotificationPanel = (NotificationPanelView) mStatusBarWindow.findViewById(
+ mNotificationPanel = (NotificationPanelView) mStatusBarWindowContent.findViewById(
R.id.notification_panel);
mNotificationPanel.setStatusBar(this);
@@ -693,8 +924,16 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
mNotificationPanel.setBackground(new FastColorDrawable(context.getColor(
R.color.notification_panel_solid_background)));
}
+ mLiveLockScreenController = new LiveLockScreenController(mContext, this,
+ mNotificationPanel);
+ mNotificationPanel.setLiveController(mLiveLockScreenController);
+ if (mStatusBarWindowManager != null) {
+ mStatusBarWindowManager.setLiveLockscreenController(mLiveLockScreenController);
+ }
- mHeadsUpManager = new HeadsUpManager(context, mStatusBarWindow);
+ if (mHeadsUpManager == null) {
+ mHeadsUpManager = new HeadsUpManager(context, mStatusBarWindow);
+ }
mHeadsUpManager.setBar(this);
mHeadsUpManager.addListener(this);
mHeadsUpManager.addListener(mNotificationPanel);
@@ -710,9 +949,10 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
try {
boolean showNav = mWindowManagerService.hasNavigationBar();
if (DEBUG) Log.v(TAG, "hasNavigationBar=" + showNav);
- if (showNav) {
+ if (showNav && !mRecreating) {
mNavigationBarView =
(NavigationBarView) View.inflate(context, R.layout.navigation_bar, null);
+ mNavigationBarView.updateResources(getNavbarThemedResources());
mNavigationBarView.setDisabledFlags(mDisabled1);
mNavigationBarView.setBar(this);
@@ -737,12 +977,17 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
// no window manager? good luck with that
}
- mAssistManager = new AssistManager(this, context);
+ if (mAssistManager == null) {
+ mAssistManager = new AssistManager(this, context);
+ }
+ if (mNavigationBarView == null) {
+ mAssistManager.onConfigurationChanged();
+ }
// figure out which pixel-format to use for the status bar.
mPixelFormat = PixelFormat.OPAQUE;
- mStackScroller = (NotificationStackScrollLayout) mStatusBarWindow.findViewById(
+ mStackScroller = (NotificationStackScrollLayout) mStatusBarWindowContent.findViewById(
R.id.notification_stack_scroller);
mStackScroller.setLongPressListener(getNotificationLongClicker());
mStackScroller.setPhoneStatusBar(this);
@@ -775,13 +1020,15 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
mStackScroller.setDismissView(mDismissView);
mExpandedContents = mStackScroller;
- mBackdrop = (BackDropView) mStatusBarWindow.findViewById(R.id.backdrop);
+ mBackdrop = (BackDropView) mStatusBarWindowContent.findViewById(R.id.backdrop);
mBackdropFront = (ImageView) mBackdrop.findViewById(R.id.backdrop_front);
mBackdropBack = (ImageView) mBackdrop.findViewById(R.id.backdrop_back);
- ScrimView scrimBehind = (ScrimView) mStatusBarWindow.findViewById(R.id.scrim_behind);
- ScrimView scrimInFront = (ScrimView) mStatusBarWindow.findViewById(R.id.scrim_in_front);
- View headsUpScrim = mStatusBarWindow.findViewById(R.id.heads_up_scrim);
+ FrameLayout scrimView = (FrameLayout) mStatusBarWindowContent.findViewById(R.id.scrimview);
+ ScrimView scrimBehind = (ScrimView) scrimView.findViewById(R.id.scrim_behind);
+ ScrimView scrimInFront =
+ (ScrimView) mStatusBarWindowContent.findViewById(R.id.scrim_in_front);
+ View headsUpScrim = mStatusBarWindowContent.findViewById(R.id.heads_up_scrim);
mScrimController = new ScrimController(scrimBehind, scrimInFront, headsUpScrim,
mScrimSrcModeEnabled);
mHeadsUpManager.addListener(mScrimController);
@@ -789,18 +1036,19 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
mScrimController.setBackDropView(mBackdrop);
mStatusBarView.setScrimController(mScrimController);
mDozeScrimController = new DozeScrimController(mScrimController, context);
+ mVisualizerView = (VisualizerView) scrimView.findViewById(R.id.visualizerview);
- mHeader = (StatusBarHeaderView) mStatusBarWindow.findViewById(R.id.header);
+ mHeader = (StatusBarHeaderView) mStatusBarWindowContent.findViewById(R.id.header);
mHeader.setActivityStarter(this);
- mKeyguardStatusBar = (KeyguardStatusBarView) mStatusBarWindow.findViewById(R.id.keyguard_header);
- mKeyguardStatusView = mStatusBarWindow.findViewById(R.id.keyguard_status_view);
- mKeyguardBottomArea =
- (KeyguardBottomAreaView) mStatusBarWindow.findViewById(R.id.keyguard_bottom_area);
+ mKeyguardStatusBar = (KeyguardStatusBarView) mStatusBarWindowContent.findViewById(R.id.keyguard_header);
+ mKeyguardStatusView = mStatusBarWindowContent.findViewById(R.id.keyguard_status_view);
+ mKeyguardBottomArea = mNotificationPanel.getKeyguardBottomArea();
+
mKeyguardBottomArea.setActivityStarter(this);
mKeyguardBottomArea.setAssistManager(mAssistManager);
mKeyguardIndicationController = new KeyguardIndicationController(mContext,
- (KeyguardIndicationTextView) mStatusBarWindow.findViewById(
- R.id.keyguard_indication_text),
+ (KeyguardIndicationTextView) mKeyguardBottomArea.findViewById(
+ R.id.keyguard_indication_text),
mKeyguardBottomArea.getLockIcon());
mKeyguardBottomArea.setKeyguardIndicationController(mKeyguardIndicationController);
@@ -815,35 +1063,74 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
mHandlerThread.start();
// Other icons
- mLocationController = new LocationControllerImpl(mContext,
- mHandlerThread.getLooper()); // will post a notification
- mBatteryController = new BatteryController(mContext);
- mBatteryController.addStateChangedCallback(new BatteryStateChangeCallback() {
- @Override
- public void onPowerSaveChanged() {
- mHandler.post(mCheckBarModes);
- if (mDozeServiceHost != null) {
- mDozeServiceHost.firePowerSaveChanged(mBatteryController.isPowerSave());
+ if (mLocationController == null) {
+ mLocationController = new LocationControllerImpl(mContext,
+ mHandlerThread.getLooper()); // will post a notification
+ }
+ if (mBatteryManager == null) {
+ mBatteryManager = (BatteryManager) mContext.getSystemService(Context.BATTERY_SERVICE);
+ }
+ if (mBatteryController == null) {
+ mBatteryController = new BatteryController(mContext, mHandler);
+ mBatteryController.addStateChangedCallback(new BatteryStateChangeCallback() {
+ @Override
+ public void onPowerSaveChanged() {
+ mHandler.post(mCheckBarModes);
+ if (mDozeServiceHost != null) {
+ mDozeServiceHost.firePowerSaveChanged(mBatteryController.isPowerSave());
+ }
}
+
+ @Override
+ public void onBatteryLevelChanged(boolean present, int level,
+ boolean pluggedIn, boolean charging) {
+ // noop
+ }
+
+ @Override
+ public void onBatteryStyleChanged(int style, int percentMode) {
+ // noop
+ }
+ });
+ }
+ if (mBatteryManager.isDockBatterySupported()) {
+ if (mDockBatteryController == null) {
+ mDockBatteryController = new DockBatteryController(mContext, mHandler);
}
- @Override
- public void onBatteryLevelChanged(int level, boolean pluggedIn, boolean charging) {
- // noop
- }
- });
- mNetworkController = new NetworkControllerImpl(mContext, mHandlerThread.getLooper());
- mHotspotController = new HotspotControllerImpl(mContext);
- mBluetoothController = new BluetoothControllerImpl(mContext, mHandlerThread.getLooper());
- mSecurityController = new SecurityControllerImpl(mContext);
+ }
+ if (mNetworkController == null) {
+ mNetworkController = new NetworkControllerImpl(mContext, mHandlerThread.getLooper());
+ }
+ if (mHotspotController == null) {
+ mHotspotController = new HotspotControllerImpl(mContext);
+ }
+ if (mBluetoothController == null) {
+ mBluetoothController = new BluetoothControllerImpl(mContext,
+ mHandlerThread.getLooper());
+ }
+ if (mSecurityController == null) {
+ mSecurityController = new SecurityControllerImpl(mContext);
+ }
if (mContext.getResources().getBoolean(R.bool.config_showRotationLock)) {
- mRotationLockController = new RotationLockControllerImpl(mContext);
+ if (mRotationLockController == null) {
+ mRotationLockController = new RotationLockControllerImpl(mContext);
+ }
+ }
+ if (mUserInfoController == null) {
+ mUserInfoController = new UserInfoController(mContext);
}
- mUserInfoController = new UserInfoController(mContext);
mVolumeComponent = getComponent(VolumeComponent.class);
if (mVolumeComponent != null) {
- mZenModeController = mVolumeComponent.getZenController();
+ if (mZenModeController == null) {
+ mZenModeController = mVolumeComponent.getZenController();
+ }
+ }
+ if (mCastController == null) {
+ mCastController = new CastControllerImpl(mContext);
+ }
+ if (mSuController == null) {
+ mSuController = new SuControllerImpl(mContext);
}
- mCastController = new CastControllerImpl(mContext);
final SignalClusterView signalCluster =
(SignalClusterView) mStatusBarView.findViewById(R.id.signal_cluster);
final SignalClusterView signalClusterKeyguard =
@@ -864,45 +1151,143 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
mNetworkController.addEmergencyListener(mHeader);
}
- mFlashlightController = new FlashlightController(mContext);
+ if (mFlashlightController == null) {
+ mFlashlightController = new FlashlightController(mContext);
+ }
mKeyguardBottomArea.setFlashlightController(mFlashlightController);
mKeyguardBottomArea.setPhoneStatusBar(this);
mKeyguardBottomArea.setUserSetupComplete(mUserSetup);
- mAccessibilityController = new AccessibilityController(mContext);
+ if (mAccessibilityController == null) {
+ mAccessibilityController = new AccessibilityController(mContext);
+ }
mKeyguardBottomArea.setAccessibilityController(mAccessibilityController);
- mNextAlarmController = new NextAlarmController(mContext);
- mKeyguardMonitor = new KeyguardMonitor(mContext);
+ if (mNextAlarmController == null) {
+ mNextAlarmController = new NextAlarmController(mContext);
+ }
+ if (mKeyguardMonitor == null) {
+ mKeyguardMonitor = new KeyguardMonitor(mContext);
+ }
if (UserSwitcherController.isUserSwitcherAvailable(UserManager.get(mContext))) {
- mUserSwitcherController = new UserSwitcherController(mContext, mKeyguardMonitor,
- mHandler);
+ if (mUserSwitcherController == null) {
+ mUserSwitcherController = new UserSwitcherController(mContext, mKeyguardMonitor,
+ mHandler);
+ }
+ }
+ if (mWeatherController == null) {
+ mWeatherController = new WeatherControllerImpl(mContext);
}
+
mKeyguardUserSwitcher = new KeyguardUserSwitcher(mContext,
- (ViewStub) mStatusBarWindow.findViewById(R.id.keyguard_user_switcher),
+ (ViewStub) mStatusBarWindowContent.findViewById(R.id.keyguard_user_switcher),
mKeyguardStatusBar, mNotificationPanel, mUserSwitcherController);
// Set up the quick settings tile panel
- mQSPanel = (QSPanel) mStatusBarWindow.findViewById(R.id.quick_settings_panel);
+ mQSPanel = (QSDragPanel) mStatusBarWindowContent.findViewById(R.id.quick_settings_panel);
if (mQSPanel != null) {
- final QSTileHost qsh = new QSTileHost(mContext, this,
- mBluetoothController, mLocationController, mRotationLockController,
- mNetworkController, mZenModeController, mHotspotController,
- mCastController, mFlashlightController,
- mUserSwitcherController, mKeyguardMonitor,
- mSecurityController);
- mQSPanel.setHost(qsh);
- mQSPanel.setTiles(qsh.getTiles());
- mBrightnessMirrorController = new BrightnessMirrorController(mStatusBarWindow);
+ if (mQSTileHost == null) {
+ mQSTileHost = new QSTileHost(mContext, this,
+ mBluetoothController, mLocationController, mRotationLockController,
+ mNetworkController, mZenModeController, mHotspotController,
+ mCastController, mFlashlightController,
+ mUserSwitcherController, mKeyguardMonitor,
+ mSecurityController, mBatteryController);
+ }
+ mQSPanel.setHost(mQSTileHost);
+ if (mBrightnessMirrorController == null) {
+ mBrightnessMirrorController = new BrightnessMirrorController(mStatusBarWindowContent);
+ }
mQSPanel.setBrightnessMirror(mBrightnessMirrorController);
+ mQSPanel.setTiles(mQSTileHost.getTiles());
mHeader.setQSPanel(mQSPanel);
- qsh.setCallback(new QSTileHost.Callback() {
+ mQSTileHost.setCallback(new QSTileHost.Callback() {
@Override
public void onTilesChanged() {
- mQSPanel.setTiles(qsh.getTiles());
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ mQSPanel.setTiles(mQSTileHost.getTiles());
+ }
+ });
+ }
+
+ @Override
+ public void setEditing(final boolean editing) {
+ if (mState != StatusBarState.SHADE) {
+ return;
+ }
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ mQSPanel.setEditing(editing);
+ mHeader.setEditing(editing);
+ }
+ });
+ }
+
+ @Override
+ public boolean isEditing() {
+ return mQSPanel.isEditing();
+ }
+
+ @Override
+ public void goToSettingsPage() {
+ if (mState != StatusBarState.SHADE) {
+ return;
+ }
+ setEditing(true);
+ mHandler.postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ mQSPanel.goToSettingsPage();
+ }
+ }, 500);
+ }
+
+ @Override
+ public void resetTiles() {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ mQSPanel.setEditing(false);
+ mHeader.setEditing(false);
+
+ // unregister custom tile service while we reset to not get
+ // callbacks from custom tiles
+ try {
+ mCustomTileListenerService.unregisterAsSystemService();
+ } catch (RemoteException e) {
+ Log.e(TAG, "Unable to unregister custom tile listener", e);
+ }
+
+ mQSTileHost.resetTiles();
+
+ // reregister service
+ try {
+ mCustomTileListenerService.registerAsSystemService(mContext,
+ new ComponentName(mContext.getPackageName(),
+ PhoneStatusBar.this.getClass().getCanonicalName()),
+ UserHandle.USER_ALL);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Unable to register custom tile listener", e);
+ }
+ }
+ });
}
});
}
+ // Set up the initial custom tile listener state.
+ try {
+ mCustomTileListenerService.registerAsSystemService(mContext,
+ new ComponentName(mContext.getPackageName(), getClass().getCanonicalName()),
+ UserHandle.USER_ALL);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Unable to register custom tile listener", e);
+ }
+
+ mQSPanel.getHost().setCustomTileListenerService(mCustomTileListenerService);
+
// User info. Trigger first load.
mHeader.setUserInfoController(mUserInfoController);
mKeyguardStatusBar.setUserInfoController(mUserInfoController);
@@ -910,10 +1295,40 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
mUserInfoController.reloadUserInfo();
mHeader.setBatteryController(mBatteryController);
- ((BatteryMeterView) mStatusBarView.findViewById(R.id.battery)).setBatteryController(
- mBatteryController);
+
+ BatteryMeterView batteryMeterView =
+ ((BatteryMeterView) mStatusBarView.findViewById(R.id.battery));
+ batteryMeterView.setBatteryStateRegistar(mBatteryController);
+ batteryMeterView.setBatteryController(mBatteryController);
+ batteryMeterView.setAnimationsEnabled(false);
+ ((BatteryLevelTextView) mStatusBarView.findViewById(R.id.battery_level_text))
+ .setBatteryStateRegistar(mBatteryController);
mKeyguardStatusBar.setBatteryController(mBatteryController);
+ mHeader.setDockBatteryController(mDockBatteryController);
+ mKeyguardStatusBar.setDockBatteryController(mDockBatteryController);
+ if (mDockBatteryController != null) {
+ DockBatteryMeterView dockBatteryMeterView =
+ ((DockBatteryMeterView) mStatusBarView.findViewById(R.id.dock_battery));
+ dockBatteryMeterView.setBatteryStateRegistar(mDockBatteryController);
+ ((BatteryLevelTextView) mStatusBarView.findViewById(R.id.dock_battery_level_text))
+ .setBatteryStateRegistar(mDockBatteryController);
+ } else {
+ DockBatteryMeterView dockBatteryMeterView =
+ (DockBatteryMeterView) mStatusBarView.findViewById(R.id.dock_battery);
+ if (dockBatteryMeterView != null) {
+ mStatusBarView.removeView(dockBatteryMeterView);
+ }
+ BatteryLevelTextView dockBatteryLevel =
+ (BatteryLevelTextView) mStatusBarView.findViewById(R.id.dock_battery_level_text);
+ if (dockBatteryLevel != null) {
+ mStatusBarView.removeView(dockBatteryLevel);
+ }
+ }
+
mHeader.setNextAlarmController(mNextAlarmController);
+ mHeader.setWeatherController(mWeatherController);
+
+ mNotificationPanel.setWeatherController(mWeatherController);
PowerManager pm = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
mBroadcastReceiver.onReceive(mContext,
@@ -927,6 +1342,8 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
filter.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
filter.addAction(Intent.ACTION_SCREEN_OFF);
filter.addAction(Intent.ACTION_SCREEN_ON);
+ filter.addAction(Intent.ACTION_KEYGUARD_WALLPAPER_CHANGED);
+ filter.addAction(cyanogenmod.content.Intent.ACTION_SCREEN_CAMERA_GESTURE);
context.registerReceiverAsUser(mBroadcastReceiver, UserHandle.ALL, filter, null, null);
IntentFilter demoFilter = new IntentFilter();
@@ -937,6 +1354,15 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
context.registerReceiverAsUser(mDemoReceiver, UserHandle.ALL, demoFilter,
android.Manifest.permission.DUMP, null);
+ // receive broadcasts for packages
+ IntentFilter packageFilter = new IntentFilter();
+ packageFilter.addAction(Intent.ACTION_PACKAGE_ADDED);
+ packageFilter.addAction(Intent.ACTION_PACKAGE_CHANGED);
+ packageFilter.addAction(Intent.ACTION_PACKAGE_FULLY_REMOVED);
+ packageFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
+ packageFilter.addDataScheme("package");
+ context.registerReceiver(mPackageBroadcastReceiver, packageFilter);
+
// listen for USER_SETUP_COMPLETE setting (per-user)
resetUserSetupObserver();
@@ -1062,6 +1488,41 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
return mNaturalBarHeight;
}
+ private final CustomTileListenerService mCustomTileListenerService =
+ new CustomTileListenerService() {
+ @Override
+ public void onListenerConnected() {
+ //Connected
+ }
+ @Override
+ public void onCustomTilePosted(final StatusBarPanelCustomTile sbc) {
+ if (DEBUG) Log.d(TAG, "onCustomTilePosted: " + sbc.getCustomTile());
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ boolean isUpdate = mQSPanel.getHost().getCustomTileData()
+ .get(sbc.persistableKey()) != null;
+ if (isUpdate) {
+ mQSPanel.getHost().updateCustomTile(sbc);
+ } else {
+ mQSPanel.getHost().addCustomTile(sbc);
+ }
+ }
+ });
+ }
+
+ @Override
+ public void onCustomTileRemoved(final StatusBarPanelCustomTile sbc) {
+ if (DEBUG) Log.d(TAG, "onCustomTileRemoved: " + sbc.getCustomTile());
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ mQSPanel.getHost().removeCustomTileSysUi(sbc.persistableKey());
+ }
+ });
+ }
+ };
+
private View.OnClickListener mRecentsClickListener = new View.OnClickListener() {
public void onClick(View v) {
awakenDreams();
@@ -1118,34 +1579,53 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
}
}
- private void prepareNavigationBarView() {
+ private void prepareNavigationBarView(boolean forceReset) {
mNavigationBarView.reorient();
- mNavigationBarView.getRecentsButton().setOnClickListener(mRecentsClickListener);
- mNavigationBarView.getRecentsButton().setOnTouchListener(mRecentsPreloadOnTouchListener);
- mNavigationBarView.getRecentsButton().setLongClickable(true);
- mNavigationBarView.getRecentsButton().setOnLongClickListener(mLongPressBackRecentsListener);
- mNavigationBarView.getBackButton().setLongClickable(true);
- mNavigationBarView.getBackButton().setOnLongClickListener(mLongPressBackRecentsListener);
- mNavigationBarView.getHomeButton().setOnTouchListener(mHomeActionListener);
- mNavigationBarView.getHomeButton().setOnLongClickListener(mLongPressHomeListener);
+ mNavigationBarView.setListeners(mRecentsClickListener, mRecentsPreloadOnTouchListener,
+ mLongPressBackRecentsListener, mHomeActionListener, mLongPressHomeListener);
mAssistManager.onConfigurationChanged();
+ if (forceReset) {
+ // Nav Bar was added dynamically - we need to reset the mSystemUiVisibility and call
+ // setSystemUiVisibility so that mNavigationBarMode is set to the correct value
+ int newVal = mSystemUiVisibility;
+ mSystemUiVisibility = 0;
+ setSystemUiVisibility(newVal, SYSTEM_UI_VISIBILITY_MASK);
+ checkBarMode(mNavigationBarMode,
+ mNavigationBarWindowState, mNavigationBarView.getBarTransitions(),
+ mNoAnimationOnNextBarModeChange);
+ }
}
// For small-screen devices (read: phones) that lack hardware navigation buttons
- private void addNavigationBar() {
+ private void addNavigationBar(boolean forceReset) {
if (DEBUG) Log.v(TAG, "addNavigationBar: about to add " + mNavigationBarView);
if (mNavigationBarView == null) return;
- prepareNavigationBarView();
+ ThemeConfig newTheme = mContext.getResources().getConfiguration().themeConfig;
+ if (newTheme != null &&
+ (mCurrentTheme == null || !mCurrentTheme.equals(newTheme))) {
+ // Nevermind, this will be re-created
+ return;
+ }
+
+ prepareNavigationBarView(forceReset);
mWindowManager.addView(mNavigationBarView, getNavigationBarLayoutParams());
}
+ private void removeNavigationBar() {
+ if (DEBUG) Log.d(TAG, "removeNavigationBar: about to remove " + mNavigationBarView);
+ if (mNavigationBarView == null) return;
+
+ mWindowManager.removeView(mNavigationBarView);
+ mNavigationBarView = null;
+ }
+
private void repositionNavigationBar() {
if (mNavigationBarView == null || !mNavigationBarView.isAttachedToWindow()) return;
- prepareNavigationBarView();
+ prepareNavigationBarView(false);
mWindowManager.updateViewLayout(mNavigationBarView, getNavigationBarLayoutParams());
}
@@ -1176,6 +1656,18 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
return lp;
}
+ private Resources getNavbarThemedResources() {
+ String pkgName = mCurrentTheme.getOverlayForNavBar();
+ Resources res = null;
+ try {
+ res = mContext.getPackageManager().getThemedResourcesForApplication(
+ mContext.getPackageName(), pkgName);
+ } catch (PackageManager.NameNotFoundException e) {
+ res = mContext.getResources();
+ }
+ return res;
+ }
+
public void addIcon(String slot, int index, int viewIndex, StatusBarIcon icon) {
mIconController.addSystemIcon(slot, index, viewIndex, icon);
}
@@ -1272,6 +1764,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
if (mNavigationBarView != null) {
mNavigationBarView.setLayoutDirection(layoutDirection);
}
+ mIconController.refreshAllStatusBarIcons();
}
private void updateNotificationShade() {
@@ -1494,7 +1987,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
}
@Override
- protected void updateRowStates() {
+ public void updateRowStates() {
super.updateRowStates();
mNotificationPanel.notifyVisibleChildrenChanged();
}
@@ -1608,6 +2101,12 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
Log.v(TAG, "DEBUG_MEDIA: insert listener, receive metadata: "
+ mMediaMetadata);
}
+ if (mediaNotification != null
+ && mediaNotification.row != null
+ && mediaNotification.row instanceof MediaExpandableNotificationRow) {
+ ((MediaExpandableNotificationRow) mediaNotification.row)
+ .setMediaController(controller);
+ }
if (mediaNotification != null) {
mMediaNotificationKey = mediaNotification.notification.getKey();
@@ -1699,18 +2198,46 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
+ " state=" + mState);
}
- Bitmap artworkBitmap = null;
+ Bitmap backdropBitmap = null;
+
+ // apply any album artwork first
if (mMediaMetadata != null) {
- artworkBitmap = mMediaMetadata.getBitmap(MediaMetadata.METADATA_KEY_ART);
- if (artworkBitmap == null) {
- artworkBitmap = mMediaMetadata.getBitmap(MediaMetadata.METADATA_KEY_ALBUM_ART);
+ backdropBitmap = mMediaMetadata.getBitmap(MediaMetadata.METADATA_KEY_ART);
+ if (backdropBitmap == null) {
+ backdropBitmap = mMediaMetadata.getBitmap(MediaMetadata.METADATA_KEY_ALBUM_ART);
// might still be null
}
}
- final boolean hasArtwork = artworkBitmap != null;
+ // HACK: Consider keyguard as visible if showing sim pin security screen
+ KeyguardUpdateMonitor updateMonitor = KeyguardUpdateMonitor.getInstance(mContext);
+ boolean keyguardVisible = mState != StatusBarState.SHADE || updateMonitor.isSimPinSecure();
+
+ if (!mKeyguardFadingAway && keyguardVisible && backdropBitmap != null && mScreenOn) {
+ // if there's album art, ensure visualizer is visible
+ mVisualizerView.setPlaying(mMediaController != null
+ && mMediaController.getPlaybackState() != null
+ && mMediaController.getPlaybackState().getState()
+ == PlaybackState.STATE_PLAYING);
+ }
+
+ // apply user lockscreen image
+ if (backdropBitmap == null && !mLiveLockScreenController.isShowingLiveLockScreenView()) {
+ backdropBitmap = mKeyguardWallpaper;
+ }
+
+ if (keyguardVisible) {
+ // always use current backdrop to color eq
+ mVisualizerView.setBitmap(backdropBitmap);
+ }
+
+ final boolean hasBackdrop = backdropBitmap != null;
+ mKeyguardShowingMedia = hasBackdrop;
+ if (mStatusBarWindowManager != null) {
+ mStatusBarWindowManager.setShowingMedia(mKeyguardShowingMedia);
+ }
- if ((hasArtwork || DEBUG_MEDIA_FAKE_ARTWORK)
+ if ((hasBackdrop || DEBUG_MEDIA_FAKE_ARTWORK)
&& (mState == StatusBarState.KEYGUARD || mState == StatusBarState.SHADE_LOCKED)
&& mFingerprintUnlockController.getMode()
!= FingerprintUnlockController.MODE_WAKE_AND_UNLOCK_PULSING) {
@@ -1743,7 +2270,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
mBackdropBack.setBackgroundColor(0xFFFFFFFF);
mBackdropBack.setImageDrawable(new ColorDrawable(c));
} else {
- mBackdropBack.setImageBitmap(artworkBitmap);
+ mBackdropBack.setImageBitmap(backdropBitmap);
}
if (mScrimSrcModeEnabled) {
mBackdropBack.getDrawable().mutate().setXfermode(mSrcXferMode);
@@ -1947,6 +2474,10 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
return mLeaveOpenOnKeyguardHide;
}
+ public boolean isKeyguardShowingMedia() {
+ return mKeyguardShowingMedia;
+ }
+
public boolean isQsExpanded() {
return mNotificationPanel.isQsExpanded();
}
@@ -1955,6 +2486,10 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
return mWakeUpComingFromTouch;
}
+ void setBlur(float b){
+ mStatusBarWindowManager.setBlur(b);
+ }
+
public boolean isFalsingThresholdNeeded() {
return getBarState() == StatusBarState.KEYGUARD;
}
@@ -1968,6 +2503,11 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
return mMediaNotificationKey;
}
+ @Override
+ protected MediaController getCurrentMediaController() {
+ return mMediaController;
+ }
+
public boolean isScrimSrcModeEnabled() {
return mScrimSrcModeEnabled;
}
@@ -2295,6 +2835,98 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
}
}
+ private void adjustBrightness(int x) {
+ mBrightnessChanged = true;
+ float raw = ((float) x) / mScreenWidth;
+
+ // Add a padding to the brightness control on both sides to
+ // make it easier to reach min/max brightness
+ float padded = Math.min(1.0f - BRIGHTNESS_CONTROL_PADDING,
+ Math.max(BRIGHTNESS_CONTROL_PADDING, raw));
+ float value = (padded - BRIGHTNESS_CONTROL_PADDING) /
+ (1 - (2.0f * BRIGHTNESS_CONTROL_PADDING));
+ try {
+ IPowerManager power = IPowerManager.Stub.asInterface(
+ ServiceManager.getService("power"));
+ if (power != null) {
+ if (mAutomaticBrightness) {
+ float adj = (2 * value) - 1;
+ adj = Math.max(adj, -1);
+ adj = Math.min(adj, 1);
+ final float val = adj;
+ power.setTemporaryScreenAutoBrightnessAdjustmentSettingOverride(val);
+ AsyncTask.execute(new Runnable() {
+ public void run() {
+ Settings.System.putFloatForUser(mContext.getContentResolver(),
+ Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ, val,
+ UserHandle.USER_CURRENT);
+ }
+ });
+ } else {
+ int newBrightness = mMinBrightness + (int) Math.round(value *
+ (android.os.PowerManager.BRIGHTNESS_ON - mMinBrightness));
+ newBrightness = Math.min(newBrightness, android.os.PowerManager.BRIGHTNESS_ON);
+ newBrightness = Math.max(newBrightness, mMinBrightness);
+ final int val = newBrightness;
+ power.setTemporaryScreenBrightnessSettingOverride(val);
+ AsyncTask.execute(new Runnable() {
+ @Override
+ public void run() {
+ Settings.System.putIntForUser(mContext.getContentResolver(),
+ Settings.System.SCREEN_BRIGHTNESS, val,
+ UserHandle.USER_CURRENT);
+ }
+ });
+ }
+
+
+ }
+ } catch (RemoteException e) {
+ Log.w(TAG, "Setting Brightness failed: " + e);
+ }
+ }
+
+ private void brightnessControl(MotionEvent event) {
+ final int action = event.getAction();
+ final int x = (int) event.getRawX();
+ final int y = (int) event.getRawY();
+ if (action == MotionEvent.ACTION_DOWN) {
+ if (y < mStatusBarHeaderHeight) {
+ mLinger = 0;
+ mInitialTouchX = x;
+ mInitialTouchY = y;
+ mJustPeeked = true;
+ mHandler.removeCallbacks(mLongPressBrightnessChange);
+ mHandler.postDelayed(mLongPressBrightnessChange,
+ BRIGHTNESS_CONTROL_LONG_PRESS_TIMEOUT);
+ }
+ } else if (action == MotionEvent.ACTION_MOVE) {
+ if (y < mStatusBarHeaderHeight && mJustPeeked) {
+ if (mLinger > BRIGHTNESS_CONTROL_LINGER_THRESHOLD) {
+ adjustBrightness(x);
+ } else {
+ final int xDiff = Math.abs(x - mInitialTouchX);
+ final int yDiff = Math.abs(y - mInitialTouchY);
+ final int touchSlop = ViewConfiguration.get(mContext).getScaledTouchSlop();
+ if (xDiff > yDiff) {
+ mLinger++;
+ }
+ if (xDiff > touchSlop || yDiff > touchSlop) {
+ mHandler.removeCallbacks(mLongPressBrightnessChange);
+ }
+ }
+ } else {
+ if (y > mStatusBarHeaderHeight) {
+ mJustPeeked = false;
+ }
+ mHandler.removeCallbacks(mLongPressBrightnessChange);
+ }
+ } else if (action == MotionEvent.ACTION_UP
+ || action == MotionEvent.ACTION_CANCEL) {
+ mHandler.removeCallbacks(mLongPressBrightnessChange);
+ }
+ }
+
public boolean interceptTouchEvent(MotionEvent event) {
if (DEBUG_GESTURES) {
if (event.getActionMasked() != MotionEvent.ACTION_MOVE) {
@@ -2321,16 +2953,29 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
mGestureRec.add(event);
}
+ if (mBrightnessControl) {
+ brightnessControl(event);
+ if ((mDisabled1 & StatusBarManager.DISABLE_EXPAND) != 0) {
+ return true;
+ }
+ }
+
+ final boolean upOrCancel =
+ event.getAction() == MotionEvent.ACTION_UP ||
+ event.getAction() == MotionEvent.ACTION_CANCEL;
if (mStatusBarWindowState == WINDOW_STATE_SHOWING) {
- final boolean upOrCancel =
- event.getAction() == MotionEvent.ACTION_UP ||
- event.getAction() == MotionEvent.ACTION_CANCEL;
if (upOrCancel && !mExpandedVisible) {
setInteracting(StatusBarManager.WINDOW_STATUS_BAR, false);
} else {
setInteracting(StatusBarManager.WINDOW_STATUS_BAR, true);
}
}
+ if (mBrightnessChanged && upOrCancel && !isQsExpanded()) {
+ mBrightnessChanged = false;
+ if (mJustPeeked && mExpandedVisible) {
+ mNotificationPanel.fling(10, false);
+ }
+ }
return false;
}
@@ -2771,6 +3416,9 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
if (mBatteryController != null) {
mBatteryController.dump(fd, pw, args);
}
+ if (mDockBatteryController != null) {
+ mDockBatteryController.dump(fd, pw, args);
+ }
if (mNextAlarmController != null) {
mNextAlarmController.dump(fd, pw, args);
}
@@ -2813,8 +3461,13 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
private void addStatusBarWindow() {
makeStatusBarView();
- mStatusBarWindowManager = new StatusBarWindowManager(mContext);
+ mStatusBarWindow.addContent(mStatusBarWindowContent);
+ mStatusBarWindowManager = new StatusBarWindowManager(mContext, mKeyguardMonitor);
+ mStatusBarWindowManager.setShowingMedia(mKeyguardShowingMedia);
mStatusBarWindowManager.add(mStatusBarWindow, getStatusBarHeight());
+ if (mLiveLockScreenController != null) {
+ mStatusBarWindowManager.setLiveLockscreenController(mLiveLockScreenController);
+ }
}
// called by makeStatusbar and also by PhoneStatusBarView
@@ -2838,10 +3491,16 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
public void startActivityDismissingKeyguard(final Intent intent, boolean onlyProvisioned,
final boolean dismissShade, final Callback callback) {
- if (onlyProvisioned && !isDeviceProvisioned()) return;
-
final boolean afterKeyguardGone = PreviewInflater.wouldLaunchResolverActivity(
mContext, intent, mCurrentUserId);
+ startActivityDismissingKeyguard(intent, onlyProvisioned, dismissShade, afterKeyguardGone,
+ callback);
+ }
+
+ public void startActivityDismissingKeyguard(final Intent intent, boolean onlyProvisioned,
+ final boolean dismissShade, final boolean afterKeyguardGone, final Callback callback) {
+ if (onlyProvisioned && !isDeviceProvisioned()) return;
+
final boolean keyguardShowing = mStatusBarKeyguardViewManager.isShowing();
Runnable runnable = new Runnable() {
public void run() {
@@ -2924,13 +3583,33 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
}
}
else if (Intent.ACTION_SCREEN_OFF.equals(action)) {
+ mScreenOn = false;
notifyNavigationBarScreenOn(false);
notifyHeadsUpScreenOff();
finishBarAnimations();
resetUserExpandedStates();
}
else if (Intent.ACTION_SCREEN_ON.equals(action)) {
+ mScreenOn = true;
notifyNavigationBarScreenOn(true);
+ } else if (Intent.ACTION_KEYGUARD_WALLPAPER_CHANGED.equals(action)) {
+ WallpaperManager wm = (WallpaperManager) mContext.getSystemService(
+ Context.WALLPAPER_SERVICE);
+ mKeyguardWallpaper = wm.getKeyguardBitmap();
+ updateMediaMetaData(true);
+ } else if (cyanogenmod.content.Intent.ACTION_SCREEN_CAMERA_GESTURE.equals(action)) {
+ boolean userSetupComplete = Settings.Secure.getInt(mContext.getContentResolver(),
+ Settings.Secure.USER_SETUP_COMPLETE, 0) != 0;
+ if (!userSetupComplete || !isDeviceProvisioned()) {
+ if (DEBUG) {
+ Log.d(TAG, String.format("userSetupComplete = $1%s, " +
+ "deviceProvisioned = $2%s, ignoring camera launch gesture.",
+ userSetupComplete, isDeviceProvisioned()));
+ }
+ return;
+ }
+
+ onCameraLaunchGestureDetected(StatusBarManager.CAMERA_LAUNCH_SOURCE_SCREEN_GESTURE);
}
}
};
@@ -2955,6 +3634,24 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
if (DEBUG_MEDIA_FAKE_ARTWORK) {
updateMediaMetaData(true);
}
+ } else if (Intent.ACTION_KEYGUARD_WALLPAPER_CHANGED.equals(action)) {
+ WallpaperManager wm = (WallpaperManager) mContext.getSystemService(
+ Context.WALLPAPER_SERVICE);
+ mKeyguardWallpaper = wm.getKeyguardBitmap();
+ updateMediaMetaData(true);
+ }
+ }
+ };
+
+ private BroadcastReceiver mPackageBroadcastReceiver = new BroadcastReceiver() {
+ public void onReceive(Context context, Intent intent) {
+ if (DEBUG) Log.v(TAG, "onReceive: " + intent);
+ String action = intent.getAction();
+ if (Intent.ACTION_PACKAGE_ADDED.equals(action) ||
+ Intent.ACTION_PACKAGE_CHANGED.equals(action) ||
+ Intent.ACTION_PACKAGE_FULLY_REMOVED.equals(action) ||
+ Intent.ACTION_PACKAGE_REMOVED.equals(action)) {
+ updateCustomRecentsLongPressHandler(true);
}
}
};
@@ -2995,24 +3692,42 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
}
updateDisplaySize(); // populates mDisplayMetrics
- updateResources();
+ updateResources(newConfig);
repositionNavigationBar();
updateRowStates();
mIconController.updateResources();
mScreenPinningRequest.onConfigurationChanged();
mNetworkController.onConfigurationChanged();
+ mStatusBarWindowManager.onConfigurationChanged(newConfig);
}
@Override
public void userSwitched(int newUserId) {
super.userSwitched(newUserId);
if (MULTIUSER_DEBUG) mNotificationPanelDebugText.setText("USER " + newUserId);
+ WallpaperManager wm = (WallpaperManager)
+ mContext.getSystemService(Context.WALLPAPER_SERVICE);
+ mKeyguardWallpaper = null;
+ wm.forgetLoadedKeyguardWallpaper();
+
animateCollapsePanels();
updatePublicMode();
updateNotifications();
resetUserSetupObserver();
setControllerUsers();
mAssistManager.onUserSwitched(newUserId);
+
+ mKeyguardWallpaper = wm.getKeyguardBitmap();
+ updateMediaMetaData(true);
+ if (mNavigationBarView != null) {
+ mNavigationBarView.updateSettings();
+ }
+ }
+
+ public void hideHeadsUp() {
+ if (mUseHeadsUp && mHeadsUpManager != null) {
+ mHeadsUpManager.releaseAllImmediately();
+ }
}
private void setControllerUsers() {
@@ -3022,6 +3737,12 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
if (mSecurityController != null) {
mSecurityController.onUserSwitched(mCurrentUserId);
}
+ if (mBatteryController != null) {
+ mBatteryController.setUserId(mCurrentUserId);
+ }
+ if (mDockBatteryController != null) {
+ mDockBatteryController.setUserId(mCurrentUserId);
+ }
}
private void resetUserSetupObserver() {
@@ -3032,6 +3753,164 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
mUserSetupObserver, mCurrentUserId);
}
+ private static void copyNotifications(ArrayList<Pair<String, StatusBarNotification>> dest,
+ NotificationData source) {
+ int N = source.size();
+ for (int i = 0; i < N; i++) {
+ NotificationData.Entry entry = source.get(i);
+ dest.add(Pair.create(entry.key, entry.notification));
+ }
+ }
+
+ private void removeSignalCallbacks(NetworkController networkController) {
+ final SignalClusterView signalCluster =
+ (SignalClusterView) mStatusBarView.findViewById(R.id.signal_cluster);
+ final SignalClusterView signalClusterKeyguard =
+ (SignalClusterView) mKeyguardStatusBar.findViewById(R.id.signal_cluster);
+ final SignalClusterView signalClusterQs =
+ (SignalClusterView) mHeader.findViewById(R.id.signal_cluster);
+ networkController.removeSignalCallback(signalCluster);
+ networkController.removeSignalCallback(signalClusterKeyguard);
+ networkController.removeSignalCallback(signalClusterQs);
+
+ if (signalCluster != null) signalCluster.setSecurityController(null);
+ if (signalClusterKeyguard != null) signalClusterKeyguard.setSecurityController(null);
+ if (signalClusterQs != null) signalClusterQs.setSecurityController(null);
+ }
+
+ private void recreateStatusBar() {
+ mRecreating = true;
+
+ if (mNetworkController != null) {
+ removeSignalCallbacks(mNetworkController);
+ if (mNetworkController.hasVoiceCallingFeature()) {
+ mNetworkController.removeEmergencyListener(mHeader);
+ }
+ }
+ if (mHeadsUpManager != null) {
+ mHeadsUpManager.removeListener(mNotificationPanel);
+ mHeadsUpManager.removeListener(mScrimController);
+ }
+ if (mIconController != null) {
+ mIconController.cleanup();
+ }
+ if (mKeyguardIndicationController != null) {
+ mKeyguardIndicationController.cleanup();
+ }
+ if (mLiveLockScreenController != null) {
+ mLiveLockScreenController.cleanup();
+ }
+
+ mKeyguardBottomArea.cleanup();
+ mStatusBarWindow.removeContent(mStatusBarWindowContent);
+ mStatusBarWindow.clearDisappearingChildren();
+
+ // extract icons from the soon-to-be recreated viewgroup.
+ ViewGroup statusIcons = mIconController.getStatusIcons();
+ int nIcons = statusIcons != null ? statusIcons.getChildCount() : 0;
+ ArrayList<StatusBarIcon> icons = new ArrayList<StatusBarIcon>(nIcons);
+ ArrayList<String> iconSlots = new ArrayList<String>(nIcons);
+ for (int i = 0; i < nIcons; i++) {
+ StatusBarIconView iconView = (StatusBarIconView) statusIcons.getChildAt(i);
+ icons.add(iconView.getStatusBarIcon());
+ iconSlots.add(iconView.getStatusBarSlot());
+ }
+
+ removeAllViews(mStatusBarWindowContent);
+
+ // extract notifications.
+ RankingMap rankingMap = mNotificationData.getRankingMap();
+ int nNotifs = mNotificationData.size();
+ ArrayList<Pair<String, StatusBarNotification>> notifications = new ArrayList<>(nNotifs);
+ copyNotifications(notifications, mNotificationData);
+ // now remove all the notifications since we'll be re-creating these with the copied data
+ mNotificationData.clear();
+
+ if (mCustomTileListenerService != null) {
+ try {
+ mCustomTileListenerService.unregisterAsSystemService();
+ } catch (RemoteException e) {
+ Log.e(TAG, "Unable to unregister custom tile listener", e);
+ }
+ }
+
+ mQSPanel.getHost().setCustomTileListenerService(null);
+ mQSPanel.setListening(false);
+
+ makeStatusBarView();
+ repositionNavigationBar();
+
+ // re-add status icons
+ for (int i = 0; i < nIcons; i++) {
+ StatusBarIcon icon = icons.get(i);
+ String slot = iconSlots.get(i);
+ addIcon(slot, i, i, icon);
+ }
+
+ // recreate notifications.
+ for (int i = 0; i < nNotifs; i++) {
+ Pair<String, StatusBarNotification> notifData = notifications.get(i);
+ addNotificationViews(createNotificationViews(notifData.second), rankingMap);
+ }
+ mNotificationData.filterAndSort();
+
+ setAreThereNotifications();
+
+ mStatusBarWindow.addContent(mStatusBarWindowContent);
+
+ checkBarModes();
+
+ // Stop the command queue until the new status bar container settles and has a layout pass
+ mCommandQueue.pause();
+
+ // fix notification panel being shifted to the left by calling
+ // instantCollapseNotificationPanel()
+ instantCollapseNotificationPanel();
+
+ mStatusBarWindow.requestLayout();
+ mStatusBarWindow.getViewTreeObserver().addOnGlobalLayoutListener(
+ new ViewTreeObserver.OnGlobalLayoutListener() {
+ @Override
+ public void onGlobalLayout() {
+ mStatusBarWindow.getViewTreeObserver().removeOnGlobalLayoutListener(this);
+ mCommandQueue.resume();
+ mRecreating = false;
+ }
+ });
+ // restart the keyguard so it picks up the newly created ScrimController
+ startKeyguard();
+
+ // if the keyguard was showing while this change occurred we'll need to do some extra work
+ if (mState == StatusBarState.KEYGUARD) {
+ // this will make sure the keyguard is showing
+ showKeyguard();
+ // make sure to hide the notification icon area and system iconography
+ // to avoid overlap (CYNGNOS-2253)
+ mIconController.hideNotificationIconArea(false);
+ mIconController.hideSystemIconArea(false);
+ }
+
+ // update mLastThemeChangeTime
+ try {
+ mLastThemeChangeTime = mThemeService.getLastThemeChangeTime();
+ } catch (RemoteException e) {
+ /* ignore */
+ }
+ }
+
+ private void removeAllViews(ViewGroup parent) {
+ int N = parent.getChildCount();
+ for (int i = 0; i < N; i++) {
+ View child = parent.getChildAt(i);
+ if (child instanceof ViewGroup) {
+ removeAllViews((ViewGroup) child);
+ }
+ }
+
+ // AdapterView does not support removeAllViews so check before calling
+ if (!(parent instanceof AdapterView)) parent.removeAllViews();
+ }
+
/**
* Reload some of our resources when the configuration changes.
*
@@ -3039,7 +3918,18 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
* should, but getting that smooth is tough. Someday we'll fix that. In the
* meantime, just update the things that we know change.
*/
- void updateResources() {
+ void updateResources(Configuration newConfig) {
+ // detect theme change.
+ ThemeConfig newTheme = newConfig != null ? newConfig.themeConfig : null;
+ final boolean updateStatusBar = shouldUpdateStatusbar(mCurrentTheme, newTheme);
+ final boolean updateNavBar = shouldUpdateNavbar(mCurrentTheme, newTheme);
+ if (newTheme != null) mCurrentTheme = (ThemeConfig) newTheme.clone();
+ if (updateStatusBar) {
+ recreateStatusBar();
+ } else {
+ loadDimens();
+ }
+
// Update the quick setting tiles
if (mQSPanel != null) {
mQSPanel.updateResources();
@@ -3053,6 +3943,66 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
if (mBrightnessMirrorController != null) {
mBrightnessMirrorController.updateResources();
}
+
+ if (mNavigationBarView != null && updateNavBar) {
+ mNavigationBarView.updateResources(getNavbarThemedResources());
+ }
+ }
+
+ /**
+ * Determines if we need to recreate the status bar due to a theme change. We currently
+ * check if the overlay for the status bar, fonts, or icons, or last theme change time is
+ * greater than mLastThemeChangeTime
+ *
+ * @param oldTheme
+ * @param newTheme
+ * @return True if we should recreate the status bar
+ */
+ private boolean shouldUpdateStatusbar(ThemeConfig oldTheme, ThemeConfig newTheme) {
+ // no newTheme, so no need to update status bar
+ if (newTheme == null) return false;
+
+ final String overlay = newTheme.getOverlayForStatusBar();
+ final String icons = newTheme.getIconPackPkgName();
+ final String fonts = newTheme.getFontPkgName();
+ boolean isNewThemeChange = false;
+ try {
+ isNewThemeChange = mLastThemeChangeTime < mThemeService.getLastThemeChangeTime();
+ } catch (RemoteException e) {
+ /* ignore */
+ }
+
+ return oldTheme == null ||
+ (overlay != null && !overlay.equals(oldTheme.getOverlayForStatusBar()) ||
+ (fonts != null && !fonts.equals(oldTheme.getFontPkgName())) ||
+ (icons != null && !icons.equals(oldTheme.getIconPackPkgName())) ||
+ isNewThemeChange);
+ }
+
+ /**
+ * Determines if we need to update the navbar resources due to a theme change. We currently
+ * check if the overlay for the navbar, or last theme change time is greater than
+ * mLastThemeChangeTime
+ *
+ * @param oldTheme
+ * @param newTheme
+ * @return True if we should update the navbar
+ */
+ private boolean shouldUpdateNavbar(ThemeConfig oldTheme, ThemeConfig newTheme) {
+ // no newTheme, so no need to update navbar
+ if (newTheme == null) return false;
+
+ final String overlay = newTheme.getOverlayForNavBar();
+ boolean isNewThemeChange = false;
+ try {
+ isNewThemeChange = mLastThemeChangeTime < mThemeService.getLastThemeChangeTime();
+ } catch (RemoteException e) {
+ /* ignore */
+ }
+
+ return oldTheme == null ||
+ (overlay != null && !overlay.equals(oldTheme.getOverlayForNavBar()) ||
+ isNewThemeChange);
}
protected void loadDimens() {
@@ -3066,6 +4016,8 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
mKeyguardMaxNotificationCount = res.getInteger(R.integer.keyguard_max_notification_count);
+ mStatusBarHeaderHeight = res.getDimensionPixelSize(R.dimen.status_bar_header_height);
+
if (DEBUG) Log.v(TAG, "updateResources");
}
@@ -3207,7 +4159,8 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
@Override
public boolean shouldDisableNavbarGestures() {
- return !isDeviceProvisioned() || (mDisabled1 & StatusBarManager.DISABLE_SEARCH) != 0;
+ return !isDeviceProvisioned() || (mDisabled1 & StatusBarManager.DISABLE_SEARCH) != 0
+ || (mNavigationBarView != null && mNavigationBarView.isInEditMode());
}
public void postStartActivityDismissingKeyguard(final PendingIntent intent) {
@@ -3327,6 +4280,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
}
if (modeChange || command.equals(COMMAND_BATTERY)) {
dispatchDemoCommandToView(command, args, R.id.battery);
+ dispatchDemoCommandToView(command, args, R.id.dock_battery);
}
if (modeChange || command.equals(COMMAND_STATUS)) {
mIconController.dispatchDemoCommand(command, args);
@@ -3407,6 +4361,18 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
mDraggedDownRow = null;
}
mAssistManager.onLockscreenShown();
+ mKeyguardBottomArea.requestFocus();
+ try {
+ WindowManagerGlobal.getWindowManagerService()
+ .setLiveLockscreenEdgeDetector(false);
+ } catch (RemoteException e){
+ e.printStackTrace();
+ }
+ if (mLiveLockScreenController.isShowingLiveLockScreenView()) {
+ mLiveLockScreenController.onLiveLockScreenFocusChanged(false);
+ mLiveLockScreenController.getLiveLockScreenView().onKeyguardShowing(
+ mStatusBarKeyguardViewManager.isScreenTurnedOn());
+ }
}
private void onLaunchTransitionFadingEnded() {
@@ -3557,6 +4523,9 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
mNotificationPanel.onAffordanceLaunchEnded();
mNotificationPanel.animate().cancel();
mNotificationPanel.setAlpha(1f);
+ if (mLiveLockScreenController.isShowingLiveLockScreenView()) {
+ mLiveLockScreenController.getLiveLockScreenView().onKeyguardDismissed();
+ }
return staying;
}
@@ -3566,6 +4535,14 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
}
}
+ boolean isSecure() {
+ return mStatusBarKeyguardViewManager != null && mStatusBarKeyguardViewManager.isSecure();
+ }
+
+ public boolean isKeyguardInputRestricted() {
+ return mStatusBarKeyguardViewManager != null && mStatusBarKeyguardViewManager.isInputRestricted();
+ }
+
public long calculateGoingToFullShadeDelay() {
return mKeyguardFadingAwayDelay + mKeyguardFadingAwayDuration;
}
@@ -3641,6 +4618,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
mIconPolicy.setKeyguardShowing(false);
}
mNotificationPanel.setBarState(mState, mKeyguardFadingAway, goingToFullShade);
+ mLiveLockScreenController.setBarState(mState);
updateDozingState();
updatePublicMode();
updateStackScrollerState(goingToFullShade);
@@ -3662,12 +4640,15 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
mDozeScrimController.setDozing(mDozing &&
mFingerprintUnlockController.getMode()
!= FingerprintUnlockController.MODE_WAKE_AND_UNLOCK_PULSING, animate);
+ mVisualizerView.setDozing(mDozing);
}
public void updateStackScrollerState(boolean goingToFullShade) {
if (mStackScroller == null) return;
boolean onKeyguard = mState == StatusBarState.KEYGUARD;
- mStackScroller.setHideSensitive(isLockscreenPublicMode(), goingToFullShade);
+ mStackScroller.setHideSensitive(isLockscreenPublicMode()
+ || (!userAllowsPrivateNotificationsInPublic(mCurrentUserId) && onKeyguard),
+ goingToFullShade);
mStackScroller.setDimmed(onKeyguard, false /* animate */);
mStackScroller.setExpandingEnabled(!onKeyguard);
ActivatableNotificationView activatedChild = mStackScroller.getActivatedChild();
@@ -3726,13 +4707,34 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
return false;
}
- private void showBouncer() {
- if (mState == StatusBarState.KEYGUARD || mState == StatusBarState.SHADE_LOCKED) {
+ public void showBouncer() {
+ if (!mRecreating && mNotificationPanel.mCanDismissKeyguard
+ && (mState != StatusBarState.SHADE || mLiveLockScreenController.getLiveLockScreenHasFocus())) {
mWaitingForKeyguardExit = mStatusBarKeyguardViewManager.isShowing();
mStatusBarKeyguardViewManager.dismiss();
}
}
+ protected void showBouncerOrFocusKeyguardExternalView() {
+ if (mLiveLockScreenController.isShowingLiveLockScreenView() && !isKeyguardShowingMedia() &&
+ mLiveLockScreenController.isLiveLockScreenInteractive()) {
+ focusKeyguardExternalView();
+ } else {
+ showBouncer();
+ }
+ }
+
+ protected void unfocusKeyguardExternalView() {
+ mStatusBarKeyguardViewManager.setKeyguardExternalViewFocus(false);
+ }
+
+ public void focusKeyguardExternalView() {
+ mStatusBarView.collapseAllPanels(/*animate=*/ false, false /* delayed*/,
+ 1.0f /* speedUpFactor */);
+ mStatusBarKeyguardViewManager.setKeyguardExternalViewFocus(true);
+ setBarState(StatusBarState.SHADE);
+ }
+
private void instantExpandNotificationsPanel() {
// Make our window larger and the panel expanded.
@@ -3770,6 +4772,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
clearNotificationEffects();
}
mState = state;
+ mVisualizerView.setStatusBarState(state);
mGroupManager.setStatusBarState(state);
mStatusBarWindowManager.setStatusBarState(state);
updateDozing();
@@ -3792,24 +4795,41 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
}
public void onUnlockHintStarted() {
- mKeyguardIndicationController.showTransientIndication(R.string.keyguard_unlock);
+ mKeyguardIndicationController.showTransientIndication(R.string.keyguard_unlock,
+ mNotificationPanel.shouldShowScreenOnHints() ?
+ KeyguardIndicationController.IndicationDirection.UP :
+ KeyguardIndicationController.IndicationDirection.NONE);
+ }
+
+ public void onLlsHintStarted() {
+ String llsName = mLiveLockScreenController.getLiveLockScreenName();
+ mKeyguardIndicationController.showTransientIndication(
+ mContext.getString(R.string.swipe_left_hint, llsName),
+ KeyguardIndicationController.IndicationDirection.LEFT);
+ }
+
+ public void onExpandHintStarted() {
+ mKeyguardIndicationController.showTransientIndication(R.string.expand_hint,
+ KeyguardIndicationController.IndicationDirection.DOWN);
+ }
+
+ public void onNotificationsHintStarted() {
+ mKeyguardIndicationController.showTransientIndication(R.string.swipe_right_hint,
+ KeyguardIndicationController.IndicationDirection.RIGHT);
}
public void onHintFinished() {
// Delay the reset a bit so the user can read the text.
mKeyguardIndicationController.hideTransientIndicationDelayed(HINT_RESET_DELAY_MS);
+ mKeyguardBottomArea.expand(false);
}
- public void onCameraHintStarted() {
- mKeyguardIndicationController.showTransientIndication(R.string.camera_hint);
- }
-
- public void onVoiceAssistHintStarted() {
- mKeyguardIndicationController.showTransientIndication(R.string.voice_hint);
+ public void onCameraHintStarted(String hint) {
+ mKeyguardIndicationController.showTransientIndication(hint);
}
- public void onPhoneHintStarted() {
- mKeyguardIndicationController.showTransientIndication(R.string.phone_hint);
+ public void onLeftHintStarted(String hint) {
+ mKeyguardIndicationController.showTransientIndication(hint);
}
public void onTrackingStopped(boolean expand) {
@@ -3822,7 +4842,14 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
@Override
protected int getMaxKeyguardNotifications() {
- return mKeyguardMaxNotificationCount;
+ int max = mKeyguardMaxNotificationCount;
+ // When an interactive live lockscreen is showing
+ // we want to limit the number of maximum notifications
+ // by 1 so there is additional space for the user to dismiss keygard
+ if (mLiveLockScreenController.isLiveLockScreenInteractive()) {
+ max--;
+ }
+ return max;
}
public NavigationBarView getNavigationBarView() {
@@ -3938,6 +4965,9 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
mWakeUpTouchLocation = null;
mStackScroller.setAnimationsEnabled(false);
updateVisibleToUser();
+ if (mQSTileHost.isEditing()) {
+ mQSTileHost.setEditing(false);
+ }
if (mLaunchCameraOnFinishedGoingToSleep) {
mLaunchCameraOnFinishedGoingToSleep = false;
@@ -3970,12 +5000,23 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
private void vibrateForCameraGesture() {
// Make sure to pass -1 for repeat so VibratorService doesn't stop us when going to sleep.
- mVibrator.vibrate(new long[] { 0, 750L }, -1 /* repeat */);
+ mVibrator.vibrate(new long[] { 0, 250L }, -1 /* repeat */);
}
public void onScreenTurnedOn() {
mScreenTurningOn = false;
mDozeScrimController.onScreenTurnedOn();
+ mVisualizerView.setVisible(true);
+ if (mLiveLockScreenController.isShowingLiveLockScreenView()) {
+ mLiveLockScreenController.onScreenTurnedOn();
+ }
+ }
+
+ public void onScreenTurnedOff() {
+ mVisualizerView.setVisible(false);
+ if (mLiveLockScreenController.isShowingLiveLockScreenView()) {
+ mLiveLockScreenController.onScreenTurnedOff();
+ }
}
/**
@@ -3994,6 +5035,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
private void handleLongPressBackRecents(View v) {
try {
boolean sendBackLongPress = false;
+ boolean hijackRecentsLongPress = false;
IActivityManager activityManager = ActivityManagerNative.getDefault();
boolean isAccessiblityEnabled = mAccessibilityManager.isEnabled();
if (activityManager.isInLockTaskMode() && !isAccessiblityEnabled) {
@@ -4004,17 +5046,23 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
activityManager.stopLockTaskModeOnCurrent();
// When exiting refresh disabled flags.
mNavigationBarView.setDisabledFlags(mDisabled1, true);
- } else if ((v.getId() == R.id.back)
+ } else if (NavbarEditor.NAVBAR_BACK.equals(v.getTag())
&& !mNavigationBarView.getRecentsButton().isPressed()) {
// If we aren't pressing recents right now then they presses
// won't be together, so send the standard long-press action.
sendBackLongPress = true;
+ } else if (NavbarEditor.NAVBAR_RECENT.equals(v.getTag())
+ && !activityManager.isInLockTaskMode()) {
+ hijackRecentsLongPress = true;
}
mLastLockToAppLongPress = time;
} else {
// If this is back still need to handle sending the long-press event.
- if (v.getId() == R.id.back) {
+ if (NavbarEditor.NAVBAR_BACK.equals(v.getTag())) {
sendBackLongPress = true;
+ } else if (NavbarEditor.NAVBAR_RECENT.equals(v.getTag())
+ && !activityManager.isInLockTaskMode()) {
+ hijackRecentsLongPress = true;
} else if (isAccessiblityEnabled && activityManager.isInLockTaskMode()) {
// When in accessibility mode a long press that is recents (not back)
// should stop lock task.
@@ -4028,11 +5076,201 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
keyButtonView.sendEvent(KeyEvent.ACTION_DOWN, KeyEvent.FLAG_LONG_PRESS);
keyButtonView.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_LONG_CLICKED);
}
+
+ if (hijackRecentsLongPress) {
+ // If there is a user-selected, registered handler for the
+ // recents long press, start the Intent. Otherwise,
+ // perform the default action, which is last app switching.
+
+ // Copy it so the value doesn't change between now and when the activity is started.
+ ComponentName customRecentsLongPressHandler = mCustomRecentsLongPressHandler;
+ if (customRecentsLongPressHandler != null) {
+ startCustomRecentsLongPressActivity(customRecentsLongPressHandler);
+ } else {
+ ActionUtils.switchToLastApp(mContext, mCurrentUserId);
+ }
+ }
} catch (RemoteException e) {
Log.d(TAG, "Unable to reach activity manager", e);
}
}
+ protected View.OnTouchListener mRecentsPreloadOnTouchListener = new View.OnTouchListener() {
+ @Override
+ public boolean onTouch(View v, MotionEvent event) {
+ int action = event.getAction() & MotionEvent.ACTION_MASK;
+
+ // Handle Document switcher
+ // Additional optimization when we have software system buttons - start loading the recent
+ // tasks on touch down
+ if (action == MotionEvent.ACTION_DOWN) {
+ preloadRecents();
+ } else if (action == MotionEvent.ACTION_CANCEL) {
+ cancelPreloadingRecents();
+ } else if (action == MotionEvent.ACTION_UP) {
+ if (!v.isPressed()) {
+ cancelPreloadingRecents();
+ }
+ }
+
+ // Handle custom recents long press
+ if (action == MotionEvent.ACTION_CANCEL ||
+ action == MotionEvent.ACTION_UP) {
+ cleanupCustomRecentsLongPressHandler();
+ }
+ return false;
+ }
+ };
+
+ /**
+ * If a custom Recents Long Press activity was dispatched, then the certain
+ * handlers need to be cleaned up after the event ends.
+ */
+ private void cleanupCustomRecentsLongPressHandler() {
+ if (mCustomRecentsLongPressed) {
+ mNavigationBarView.setSlippery(false);
+ }
+ mCustomRecentsLongPressed = false;
+ }
+
+ /**
+ * An ACTION_RECENTS_LONG_PRESS intent was received, and a custom handler is
+ * set and points to a valid app. Start this activity.
+ */
+ private void startCustomRecentsLongPressActivity(ComponentName customComponentName) {
+ Intent intent = new Intent(cyanogenmod.content.Intent.ACTION_RECENTS_LONG_PRESS);
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
+
+ // Include the package name of the app currently in the foreground
+ IActivityManager am = ActivityManagerNative.getDefault();
+ List<ActivityManager.RecentTaskInfo> recentTasks = null;
+ try {
+ recentTasks = am.getRecentTasks(
+ 1, ActivityManager.RECENT_WITH_EXCLUDED, UserHandle.myUserId());
+ } catch (RemoteException e) {
+ Log.e(TAG, "Cannot get recent tasks", e);
+ }
+ if (recentTasks != null && recentTasks.size() > 0) {
+ String pkgName = recentTasks.get(0).baseIntent.getComponent().getPackageName();
+ intent.putExtra(Intent.EXTRA_CURRENT_PACKAGE_NAME, pkgName);
+ }
+
+ intent.setComponent(customComponentName);
+ try {
+ // Allow the touch event to continue into the new activity.
+ mNavigationBarView.setSlippery(true);
+ mCustomRecentsLongPressed = true;
+
+ mContext.startActivityAsUser(intent, new UserHandle(UserHandle.USER_CURRENT));
+
+ } catch (ActivityNotFoundException e) {
+ Log.e(TAG, "Cannot start activity", e);
+
+ // If the activity failed to launch, clean up
+ cleanupCustomRecentsLongPressHandler();
+ }
+ }
+
+ /**
+ * Get component name for the recent long press setting. Null means default switch to last app.
+ *
+ * Note: every time packages changed, the setting must be re-evaluated. This is to check that the
+ * component was not uninstalled or disabled.
+ */
+ private void updateCustomRecentsLongPressHandler(boolean scanPackages) {
+ // scanPackages should be true when the PhoneStatusBar is starting for
+ // the first time, and when any package activity occurred.
+ if (scanPackages) {
+ updateCustomRecentsLongPressCandidates();
+ }
+
+ String componentString = CMSettings.Secure.getString(mContext.getContentResolver(),
+ CMSettings.Secure.RECENTS_LONG_PRESS_ACTIVITY);
+ if (componentString == null) {
+ mCustomRecentsLongPressHandler = null;
+ return;
+ }
+
+ ComponentName customComponentName = ComponentName.unflattenFromString(componentString);
+ synchronized (mCustomRecentsLongPressHandlerCandidates) {
+ for (ComponentName candidate : mCustomRecentsLongPressHandlerCandidates) {
+ if (candidate.equals(customComponentName)) {
+ // Found match
+ mCustomRecentsLongPressHandler = customComponentName;
+
+ return;
+ }
+ }
+
+ // Did not find match, probably because the selected application has
+ // now been uninstalled for some reason. Since user-selected app is
+ // still saved inside Settings, PhoneStatusBar should fall back to
+ // the default for now. (We will update this either when the
+ // package is reinstalled or when the user selects another Setting.)
+ mCustomRecentsLongPressHandler = null;
+ }
+ }
+
+ /**
+ * Updates the cache of Recents Long Press applications.
+ *
+ * These applications must:
+ * - handle the cyanogenmod.contentIntent.ACTION_RECENTS_LONG_PRESS
+ * (which is permissions protected); and
+ * - not be disabled by the user or the system.
+ *
+ * More than one handler can be a candidate. When the action is invoked,
+ * the user setting (stored in CMSettings.Secure) is consulted.
+ */
+ private void updateCustomRecentsLongPressCandidates() {
+ synchronized (mCustomRecentsLongPressHandlerCandidates) {
+ mCustomRecentsLongPressHandlerCandidates.clear();
+
+ PackageManager pm = mContext.getPackageManager();
+ Intent intent = new Intent(cyanogenmod.content.Intent.ACTION_RECENTS_LONG_PRESS);
+
+ // Search for all apps that can handle ACTION_RECENTS_LONG_PRESS
+ List<ResolveInfo> activities = pm.queryIntentActivities(intent,
+ PackageManager.MATCH_DEFAULT_ONLY);
+ for (ResolveInfo info : activities) {
+ // Only cache packages that are not disabled
+ int packageState = mContext.getPackageManager().getApplicationEnabledSetting(
+ info.activityInfo.packageName);
+
+ if (packageState != PackageManager.COMPONENT_ENABLED_STATE_DISABLED &&
+ packageState != PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER) {
+
+ mCustomRecentsLongPressHandlerCandidates.add(
+ new ComponentName(info.activityInfo.packageName, info.activityInfo.name));
+ }
+
+ }
+ }
+ }
+
+ private ActivityManager.RunningTaskInfo getLastTask(final ActivityManager am) {
+ final String defaultHomePackage = resolveCurrentLauncherPackage();
+ List<ActivityManager.RunningTaskInfo> tasks = am.getRunningTasks(5);
+
+ for (int i = 1; i < tasks.size(); i++) {
+ String packageName = tasks.get(i).topActivity.getPackageName();
+ if (!packageName.equals(defaultHomePackage)
+ && !packageName.equals(mContext.getPackageName())) {
+ return tasks.get(i);
+ }
+ }
+
+ return null;
+ }
+
+ private String resolveCurrentLauncherPackage() {
+ final Intent launcherIntent = new Intent(Intent.ACTION_MAIN)
+ .addCategory(Intent.CATEGORY_HOME);
+ final PackageManager pm = mContext.getPackageManager();
+ final ResolveInfo launcherInfo = pm.resolveActivity(launcherIntent, 0);
+ return launcherInfo.activityInfo.packageName;
+ }
+
// Recents
@Override
@@ -4143,7 +5381,9 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
pm.wakeUp(SystemClock.uptimeMillis(), "com.android.systemui:CAMERA_GESTURE");
mStatusBarKeyguardViewManager.notifyDeviceWakeUpRequested();
}
- vibrateForCameraGesture();
+ if (source != StatusBarManager.CAMERA_LAUNCH_SOURCE_SCREEN_GESTURE) {
+ vibrateForCameraGesture();
+ }
if (!mStatusBarKeyguardViewManager.isShowing()) {
startActivity(KeyguardBottomAreaView.INSECURE_CAMERA_INTENT,
true /* dismissShade */);
@@ -4178,6 +5418,18 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
updateDozingState();
}
+ public VisualizerView getVisualizer() {
+ return mVisualizerView;
+ }
+
+ public boolean isShowingLiveLockScreenView() {
+ return mLiveLockScreenController.isShowingLiveLockScreenView();
+ }
+
+ public void slideNotificationPanelIn() {
+ mNotificationPanel.slideLockScreenIn();
+ }
+
private final class ShadeUpdates {
private final ArraySet<String> mVisibleNotifications = new ArraySet<String>();
private final ArraySet<String> mNewVisibleNotifications = new ArraySet<String>();
@@ -4330,4 +5582,8 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
}
}
}
+
+ public boolean isAffordanceSwipeInProgress() {
+ return mNotificationPanel.isAffordanceSwipeInProgress();
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
index 83edc96..1395ff6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
@@ -20,18 +20,29 @@ import android.app.ActivityManagerNative;
import android.app.AlarmManager;
import android.app.AlarmManager.AlarmClockInfo;
import android.app.IUserSwitchObserver;
+import android.app.PendingIntent;
import android.app.StatusBarManager;
+import android.bluetooth.BluetoothAssignedNumbers;
+import android.bluetooth.BluetoothHeadset;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.UserInfo;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.database.ContentObserver;
+import android.graphics.Bitmap;
import android.media.AudioManager;
+import android.net.Uri;
+import android.os.Binder;
import android.os.Handler;
import android.os.IRemoteCallback;
import android.os.RemoteException;
import android.os.UserHandle;
import android.os.UserManager;
+import android.provider.Settings;
import android.provider.Settings.Global;
import android.telecom.TelecomManager;
import android.util.Log;
@@ -46,6 +57,17 @@ import com.android.systemui.statusbar.policy.CastController;
import com.android.systemui.statusbar.policy.CastController.CastDevice;
import com.android.systemui.statusbar.policy.HotspotController;
import com.android.systemui.statusbar.policy.UserInfoController;
+import com.android.systemui.statusbar.policy.SuController;
+
+import cyanogenmod.app.CMStatusBarManager;
+import cyanogenmod.app.CustomTile;
+import cyanogenmod.providers.CMSettings;
+
+import org.cyanogenmod.internal.util.QSUtils;
+import org.cyanogenmod.internal.util.QSUtils.OnQSChanged;
+import org.cyanogenmod.internal.util.QSConstants;
+
+import java.util.ArrayList;
/**
* This class contains all of the policy about which icons are installed in the status
@@ -64,6 +86,7 @@ public class PhoneStatusBarPolicy implements Callback {
private static final String SLOT_VOLUME = "volume";
private static final String SLOT_ALARM_CLOCK = "alarm_clock";
private static final String SLOT_MANAGED_PROFILE = "managed_profile";
+ private static final String SLOT_SU = "su";
private final Context mContext;
private final StatusBarManager mService;
@@ -72,6 +95,8 @@ public class PhoneStatusBarPolicy implements Callback {
private final HotspotController mHotspot;
private final AlarmManager mAlarmManager;
private final UserInfoController mUserInfoController;
+ private boolean mAlarmIconVisible;
+ private final SuController mSuController;
// Assume it's all good unless we hear otherwise. We don't always seem
// to get broadcasts that it *is* there.
@@ -80,6 +105,7 @@ public class PhoneStatusBarPolicy implements Callback {
private boolean mZenVisible;
private boolean mVolumeVisible;
private boolean mCurrentUserSetup;
+ private Float mBluetoothBatteryLevel = null;
private int mZen;
@@ -106,6 +132,9 @@ public class PhoneStatusBarPolicy implements Callback {
else if (action.equals(TelecomManager.ACTION_CURRENT_TTY_MODE_CHANGED)) {
updateTTY(intent);
}
+ else if (action.equals(BluetoothHeadset.ACTION_VENDOR_SPECIFIC_HEADSET_EVENT)) {
+ updateBluetoothBattery(intent);
+ }
}
};
@@ -117,8 +146,15 @@ public class PhoneStatusBarPolicy implements Callback {
}
};
+ private final OnQSChanged mQSListener = new OnQSChanged() {
+ @Override
+ public void onQSChanged() {
+ processQSChangedLocked();
+ }
+ };
+
public PhoneStatusBarPolicy(Context context, CastController cast, HotspotController hotspot,
- UserInfoController userInfoController, BluetoothController bluetooth) {
+ UserInfoController userInfoController, BluetoothController bluetooth, SuController su) {
mContext = context;
mCast = cast;
mHotspot = hotspot;
@@ -127,6 +163,7 @@ public class PhoneStatusBarPolicy implements Callback {
mService = (StatusBarManager) context.getSystemService(Context.STATUS_BAR_SERVICE);
mAlarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
mUserInfoController = userInfoController;
+ mSuController = su;
// listen for broadcasts
IntentFilter filter = new IntentFilter();
@@ -135,6 +172,9 @@ public class PhoneStatusBarPolicy implements Callback {
filter.addAction(AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION);
filter.addAction(TelephonyIntents.ACTION_SIM_STATE_CHANGED);
filter.addAction(TelecomManager.ACTION_CURRENT_TTY_MODE_CHANGED);
+ filter.addAction(BluetoothHeadset.ACTION_VENDOR_SPECIFIC_HEADSET_EVENT);
+ filter.addCategory(BluetoothHeadset.VENDOR_SPECIFIC_HEADSET_EVENT_COMPANY_ID_CATEGORY
+ + "." + Integer.toString(BluetoothAssignedNumbers.APPLE));
mContext.registerReceiver(mIntentReceiver, filter, null, mHandler);
// listen for user / profile change.
@@ -151,9 +191,16 @@ public class PhoneStatusBarPolicy implements Callback {
// bluetooth status
updateBluetooth();
+ //Update initial tty mode
+ updateTTYMode();
+
// Alarm clock
mService.setIcon(SLOT_ALARM_CLOCK, R.drawable.stat_sys_alarm, 0, null);
mService.setIconVisibility(SLOT_ALARM_CLOCK, false);
+ mAlarmIconObserver.onChange(true);
+ mContext.getContentResolver().registerContentObserver(
+ CMSettings.System.getUriFor(CMSettings.System.SHOW_ALARM_ICON),
+ false, mAlarmIconObserver);
// zen
mService.setIcon(SLOT_ZEN, R.drawable.stat_sys_zen_important, 0, null);
@@ -175,12 +222,33 @@ public class PhoneStatusBarPolicy implements Callback {
mService.setIconVisibility(SLOT_HOTSPOT, mHotspot.isHotspotEnabled());
mHotspot.addCallback(mHotspotCallback);
+ // su
+ mService.setIcon(SLOT_SU, R.drawable.stat_sys_su, 0, null);
+ mService.setIconVisibility(SLOT_SU, false);
+ mSuController.addCallback(mSuCallback);
+
// managed profile
mService.setIcon(SLOT_MANAGED_PROFILE, R.drawable.stat_sys_managed_profile_status, 0,
mContext.getString(R.string.accessibility_managed_profile));
mService.setIconVisibility(SLOT_MANAGED_PROFILE, false);
+
+ QSUtils.registerObserverForQSChanges(mContext, mQSListener);
}
+ private ContentObserver mAlarmIconObserver = new ContentObserver(null) {
+ @Override
+ public void onChange(boolean selfChange, Uri uri) {
+ mAlarmIconVisible = CMSettings.System.getInt(mContext.getContentResolver(),
+ CMSettings.System.SHOW_ALARM_ICON, 1) == 1;
+ updateAlarm();
+ }
+
+ @Override
+ public void onChange(boolean selfChange) {
+ onChange(selfChange, null);
+ }
+ };
+
public void setZenMode(int zen) {
mZen = zen;
updateVolumeZen();
@@ -192,7 +260,7 @@ public class PhoneStatusBarPolicy implements Callback {
final boolean zenNone = mZen == Global.ZEN_MODE_NO_INTERRUPTIONS;
mService.setIcon(SLOT_ALARM_CLOCK, zenNone ? R.drawable.stat_sys_alarm_dim
: R.drawable.stat_sys_alarm, 0, null);
- mService.setIconVisibility(SLOT_ALARM_CLOCK, mCurrentUserSetup && hasAlarm);
+ mService.setIconVisibility(SLOT_ALARM_CLOCK, mCurrentUserSetup && hasAlarm && mAlarmIconVisible);
}
private final void updateSimState(Intent intent) {
@@ -236,8 +304,17 @@ public class PhoneStatusBarPolicy implements Callback {
if (DndTile.isVisible(mContext) || DndTile.isCombinedIcon(mContext)) {
zenVisible = mZen != Global.ZEN_MODE_OFF;
- zenIconId = mZen == Global.ZEN_MODE_NO_INTERRUPTIONS
- ? R.drawable.stat_sys_dnd_total_silence : R.drawable.stat_sys_dnd;
+ switch(mZen) {
+ case Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS:
+ zenIconId = R.drawable.stat_sys_dnd_priority;
+ break;
+ case Global.ZEN_MODE_NO_INTERRUPTIONS:
+ zenIconId = R.drawable.stat_sys_dnd_total_silence;
+ break;
+ default:
+ zenIconId = R.drawable.stat_sys_dnd;
+ break;
+ }
zenDescription = mContext.getString(R.string.quick_settings_dnd_label);
} else if (mZen == Global.ZEN_MODE_NO_INTERRUPTIONS) {
zenVisible = true;
@@ -289,6 +366,27 @@ public class PhoneStatusBarPolicy implements Callback {
updateBluetooth();
}
+ private void updateBluetoothBattery(Intent intent) {
+ if (intent.hasExtra(BluetoothHeadset.EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD)) {
+ String command = intent.getStringExtra(BluetoothHeadset.EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD);
+ if ("+IPHONEACCEV".equals(command)) {
+ Object[] args = (Object[]) intent.getSerializableExtra(BluetoothHeadset.EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_ARGS);
+ if (args.length >= 3 && args[0] instanceof Integer && ((Integer)args[0])*2+1<=args.length) {
+ for (int i=0;i<((Integer)args[0]);i++) {
+ if (!(args[i*2+1] instanceof Integer) || !(args[i*2+2] instanceof Integer)) {
+ continue;
+ }
+ if (args[i*2+1].equals(1)) {
+ mBluetoothBatteryLevel = (((Integer)args[i*2+2])+1)/10.0f;
+ updateBluetooth();
+ break;
+ }
+ }
+ }
+ }
+ }
+ }
+
private final void updateBluetooth() {
int iconId = R.drawable.stat_sys_data_bluetooth;
String contentDescription =
@@ -297,8 +395,24 @@ public class PhoneStatusBarPolicy implements Callback {
if (mBluetooth != null) {
bluetoothEnabled = mBluetooth.isBluetoothEnabled();
if (mBluetooth.isBluetoothConnected()) {
- iconId = R.drawable.stat_sys_data_bluetooth_connected;
+ if (mBluetoothBatteryLevel == null) {
+ iconId = R.drawable.stat_sys_data_bluetooth_connected;
+ } else {
+ if (mBluetoothBatteryLevel<=0.15f) {
+ iconId = R.drawable.stat_sys_data_bluetooth_connected_battery_1;
+ } else if (mBluetoothBatteryLevel<=0.375f) {
+ iconId = R.drawable.stat_sys_data_bluetooth_connected_battery_2;
+ } else if (mBluetoothBatteryLevel<=0.625f) {
+ iconId = R.drawable.stat_sys_data_bluetooth_connected_battery_3;
+ } else if (mBluetoothBatteryLevel<=0.85f) {
+ iconId = R.drawable.stat_sys_data_bluetooth_connected_battery_4;
+ } else {
+ iconId = R.drawable.stat_sys_data_bluetooth_connected_battery_5;
+ }
+ }
contentDescription = mContext.getString(R.string.accessibility_bluetooth_connected);
+ } else {
+ mBluetoothBatteryLevel = null;
}
}
@@ -326,6 +440,29 @@ public class PhoneStatusBarPolicy implements Callback {
}
}
+ private boolean isWiredHeadsetOn() {
+ AudioManager audioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
+ return audioManager.isWiredHeadsetOn();
+ }
+
+ private final void updateTTYMode() {
+ int ttyMode = Settings.Secure.getInt(mContext.getContentResolver(),
+ Settings.Secure.PREFERRED_TTY_MODE, TelecomManager.TTY_MODE_OFF);
+ boolean enabled = ttyMode != TelecomManager.TTY_MODE_OFF;
+ if (DEBUG) Log.v(TAG, "updateTTYMode: enabled: " + enabled);
+ if (enabled && isWiredHeadsetOn()) {
+ // TTY is on
+ if (DEBUG) Log.v(TAG, "updateTTYMode: set TTY on");
+ mService.setIcon(SLOT_TTY, R.drawable.stat_sys_tty_mode, 0,
+ mContext.getString(R.string.accessibility_tty_enabled));
+ mService.setIconVisibility(SLOT_TTY, true);
+ } else {
+ // TTY is off
+ if (DEBUG) Log.v(TAG, "updateTTYMode: set TTY off");
+ mService.setIconVisibility(SLOT_TTY, false);
+ }
+ }
+
private void updateCast() {
boolean isCasting = false;
for (CastDevice device : mCast.getCastDevices()) {
@@ -410,6 +547,16 @@ public class PhoneStatusBarPolicy implements Callback {
}
};
+ private void updateSu() {
+ mService.setIconVisibility(SLOT_SU, mSuController.hasActiveSessions());
+ final int userId = UserHandle.myUserId();
+ if (isSuEnabledForUser(userId)) {
+ publishSuCustomTile();
+ } else {
+ unpublishSuCustomTile();
+ }
+ }
+
private final CastController.Callback mCastCallback = new CastController.Callback() {
@Override
public void onCastDevicesChanged() {
@@ -431,4 +578,118 @@ public class PhoneStatusBarPolicy implements Callback {
mCurrentUserSetup = userSetup;
updateAlarm();
}
+
+ private final SuController.Callback mSuCallback = new SuController.Callback() {
+ @Override
+ public void onSuSessionsChanged() {
+ updateSu();
+ }
+ };
+
+ private void publishSuCustomTile() {
+ // This action should be performed as system
+ final int userId = UserHandle.myUserId();
+ long token = Binder.clearCallingIdentity();
+ try {
+ final UserHandle user = new UserHandle(userId);
+ final int icon = QSUtils.getDynamicQSTileResIconId(mContext, userId,
+ QSConstants.DYNAMIC_TILE_SU);
+ final String contentDesc = QSUtils.getDynamicQSTileLabel(mContext, userId,
+ QSConstants.DYNAMIC_TILE_SU);
+ final Context resourceContext = QSUtils.getQSTileContext(mContext, userId);
+
+ CustomTile.ListExpandedStyle style = new CustomTile.ListExpandedStyle();
+ ArrayList<CustomTile.ExpandedListItem> items = new ArrayList<>();
+ for (String pkg : mSuController.getPackageNamesWithActiveSuSessions()) {
+ CustomTile.ExpandedListItem item = new CustomTile.ExpandedListItem();
+ int appIconIdentifier = getActiveSuApkDrawableId(pkg);
+ if (appIconIdentifier != -1) {
+ item.setExpandedListItemDrawable(appIconIdentifier);
+ } else {
+ item.setExpandedListItemDrawable(icon);
+ }
+ item.setExpandedListItemTitle(getActiveSuApkLabel(pkg));
+ item.setExpandedListItemSummary(pkg);
+ item.setExpandedListItemOnClickIntent(getCustomTilePendingIntent(pkg));
+ items.add(item);
+ }
+ style.setListItems(items);
+
+ CMStatusBarManager statusBarManager = CMStatusBarManager.getInstance(mContext);
+ CustomTile tile = new CustomTile.Builder(resourceContext)
+ .setLabel(contentDesc)
+ .setContentDescription(contentDesc)
+ .setIcon(icon)
+ .setOnSettingsClickIntent(getCustomTileSettingsIntent())
+ .setExpandedStyle(style)
+ .build();
+ statusBarManager.publishTileAsUser(QSConstants.DYNAMIC_TILE_SU,
+ PhoneStatusBarPolicy.class.hashCode(), tile, user);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ private void unpublishSuCustomTile() {
+ // This action should be performed as system
+ final int userId = UserHandle.myUserId();
+ long token = Binder.clearCallingIdentity();
+ try {
+ CMStatusBarManager statusBarManager = CMStatusBarManager.getInstance(mContext);
+ statusBarManager.removeTileAsUser(QSConstants.DYNAMIC_TILE_SU,
+ PhoneStatusBarPolicy.class.hashCode(), new UserHandle(userId));
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ private PendingIntent getCustomTilePendingIntent(String pkg) {
+ Intent i = new Intent(Intent.ACTION_MAIN);
+ i.setPackage(pkg);
+ i.addCategory(Intent.CATEGORY_LAUNCHER);
+ i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ return PendingIntent.getActivity(mContext, 0, i, PendingIntent.FLAG_UPDATE_CURRENT);
+ }
+
+ private Intent getCustomTileSettingsIntent() {
+ Intent i = new Intent(Settings.ACTION_APPLICATION_DEVELOPMENT_SETTINGS);
+ i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ return i;
+ }
+
+ private String getActiveSuApkLabel(String pkg) {
+ final PackageManager pm = mContext.getPackageManager();
+ ApplicationInfo ai = null;
+ try {
+ ai = pm.getApplicationInfo(pkg, 0);
+ } catch (final NameNotFoundException e) {
+ // Ignore
+ }
+ return (String) (ai != null ? pm.getApplicationLabel(ai) : pkg);
+ }
+
+ private int getActiveSuApkDrawableId(String pkg) {
+ final PackageManager pm = mContext.getPackageManager();
+ ApplicationInfo ai;
+ try {
+ ai = pm.getApplicationInfo(pkg, 0);
+ } catch (final NameNotFoundException e) {
+ return -1;
+ }
+ return ai.icon;
+ }
+
+ private boolean isSuEnabledForUser(int userId) {
+ final boolean hasSuAccess = mSuController.hasActiveSessions();
+ return (userId == UserHandle.USER_OWNER) && hasSuAccess;
+ }
+
+ private void processQSChangedLocked() {
+ final int userId = UserHandle.myUserId();
+ if (isSuEnabledForUser(userId)) {
+ publishSuCustomTile();
+ } else {
+ unpublishSuCustomTile();
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarTransitions.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarTransitions.java
index fb1addf..bb3095e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarTransitions.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarTransitions.java
@@ -36,7 +36,10 @@ public final class PhoneStatusBarTransitions extends BarTransitions {
private Animator mCurrentAnimation;
public PhoneStatusBarTransitions(PhoneStatusBarView view) {
- super(view, R.drawable.status_background);
+ super(view, R.drawable.status_background, R.color.status_bar_background_opaque,
+ R.color.status_bar_background_semi_transparent,
+ R.color.status_bar_background_transparent,
+ com.android.internal.R.color.battery_saver_mode_color);
mView = view;
final Resources res = mView.getContext().getResources();
mIconAlphaWhenOpaque = res.getFraction(R.dimen.status_bar_icon_drawing_alpha, 1, 1);
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 c0887ca..8c9daee 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
@@ -111,6 +111,7 @@ public class PhoneStatusBarView extends PanelBar {
@Override
public void onPanelPeeked() {
super.onPanelPeeked();
+ removePendingHideExpandedRunnables();
mBar.makeExpandedVisible(false);
}
@@ -193,6 +194,7 @@ public class PhoneStatusBarView extends PanelBar {
super.panelExpansionChanged(panel, frac, expanded);
mPanelFraction = frac;
updateScrimFraction();
+ mBar.setBlur(frac);
}
private void updateScrimFraction() {
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 e66c63b..e88ed73 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QSTileHost.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QSTileHost.java
@@ -16,29 +16,57 @@
package com.android.systemui.statusbar.phone;
+import android.app.ActivityManager;
import android.app.PendingIntent;
+import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.res.Resources;
+import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
import android.os.Process;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.text.TextUtils;
+import android.util.ArrayMap;
import android.util.Log;
+import com.android.internal.logging.MetricsLogger;
+import android.widget.RemoteViews;
import com.android.systemui.R;
import com.android.systemui.qs.QSTile;
+import com.android.systemui.qs.tiles.AdbOverNetworkTile;
import com.android.systemui.qs.tiles.AirplaneModeTile;
+import com.android.systemui.qs.tiles.AmbientDisplayTile;
+import com.android.systemui.qs.tiles.BatterySaverTile;
import com.android.systemui.qs.tiles.BluetoothTile;
+import com.android.systemui.qs.tiles.CaffeineTile;
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.CompassTile;
+import com.android.systemui.qs.tiles.CustomQSTile;
import com.android.systemui.qs.tiles.DndTile;
+import com.android.systemui.qs.tiles.EditTile;
import com.android.systemui.qs.tiles.FlashlightTile;
+import com.android.systemui.qs.tiles.HeadsUpTile;
import com.android.systemui.qs.tiles.HotspotTile;
import com.android.systemui.qs.tiles.IntentTile;
import com.android.systemui.qs.tiles.LocationTile;
+import com.android.systemui.qs.tiles.LockscreenToggleTile;
+import com.android.systemui.qs.tiles.NfcTile;
+import com.android.systemui.qs.tiles.PerfProfileTile;
+import com.android.systemui.qs.tiles.ProfilesTile;
import com.android.systemui.qs.tiles.RotationLockTile;
+import com.android.systemui.qs.tiles.ScreenTimeoutTile;
+import com.android.systemui.qs.tiles.SyncTile;
+import com.android.systemui.qs.tiles.UsbTetherTile;
+import com.android.systemui.qs.tiles.VolumeTile;
import com.android.systemui.qs.tiles.WifiTile;
+import com.android.systemui.statusbar.CustomTileData;
+import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.statusbar.policy.BluetoothController;
import com.android.systemui.statusbar.policy.CastController;
import com.android.systemui.statusbar.policy.FlashlightController;
@@ -53,6 +81,10 @@ import com.android.systemui.statusbar.policy.ZenModeController;
import com.android.systemui.tuner.TunerService;
import com.android.systemui.tuner.TunerService.Tunable;
+import cyanogenmod.app.CustomTileListenerService;
+import cyanogenmod.app.StatusBarPanelCustomTile;
+import cyanogenmod.providers.CMSettings;
+
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
@@ -65,7 +97,7 @@ public class QSTileHost implements QSTile.Host, Tunable {
private static final String TAG = "QSTileHost";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
- protected static final String TILES_SETTING = "sysui_qs_tiles";
+ public static final int TILES_PER_PAGE = 8;
private final Context mContext;
private final PhoneStatusBar mStatusBar;
@@ -83,6 +115,10 @@ public class QSTileHost implements QSTile.Host, Tunable {
private final UserSwitcherController mUserSwitcherController;
private final KeyguardMonitor mKeyguard;
private final SecurityController mSecurity;
+ private final BatteryController mBattery;
+
+ private CustomTileData mCustomTileData;
+ private CustomTileListenerService mCustomTileListenerService;
private Callback mCallback;
@@ -92,7 +128,7 @@ public class QSTileHost implements QSTile.Host, Tunable {
ZenModeController zen, HotspotController hotspot,
CastController cast, FlashlightController flashlight,
UserSwitcherController userSwitcher, KeyguardMonitor keyguard,
- SecurityController security) {
+ SecurityController security, BatteryController battery) {
mContext = context;
mStatusBar = statusBar;
mBluetooth = bluetooth;
@@ -106,19 +142,36 @@ public class QSTileHost implements QSTile.Host, Tunable {
mUserSwitcherController = userSwitcher;
mKeyguard = keyguard;
mSecurity = security;
+ mBattery = battery;
+ mCustomTileData = new CustomTileData();
final HandlerThread ht = new HandlerThread(QSTileHost.class.getSimpleName(),
Process.THREAD_PRIORITY_BACKGROUND);
ht.start();
mLooper = ht.getLooper();
- TunerService.get(mContext).addTunable(this, TILES_SETTING);
+ TunerService.get(mContext).addTunableByProvider(this, CMSettings.Secure.QS_TILES, true);
}
public void destroy() {
TunerService.get(mContext).removeTunable(this);
}
+ public boolean isEditing() {
+ if (mCallback != null) {
+ return mCallback.isEditing();
+ }
+ return false;
+ }
+
+ public void setEditing(boolean editing) {
+ mCallback.setEditing(editing);
+ }
+
+ void setCustomTileListenerService(CustomTileListenerService customTileListenerService) {
+ mCustomTileListenerService = customTileListenerService;
+ }
+
@Override
public void setCallback(Callback callback) {
mCallback = callback;
@@ -129,12 +182,33 @@ public class QSTileHost implements QSTile.Host, Tunable {
return mTiles.values();
}
+ public List<String> getTileSpecs() {
+ return mTileSpecs;
+ }
+
+ public String getSpec(QSTile<?> tile) {
+ for (Map.Entry<String, QSTile<?>> entry : mTiles.entrySet()) {
+ if (entry.getValue() == tile) {
+ return entry.getKey();
+ }
+ }
+ return null;
+ }
+
@Override
public void startActivityDismissingKeyguard(final Intent intent) {
mStatusBar.postStartActivityDismissingKeyguard(intent, 0);
}
@Override
+ public void removeCustomTile(StatusBarPanelCustomTile customTile) {
+ if (mCustomTileListenerService != null) {
+ mCustomTileListenerService.removeCustomTile(customTile.getPackage(),
+ customTile.getTag(), customTile.getId());
+ }
+ }
+
+ @Override
public void startActivityDismissingKeyguard(PendingIntent intent) {
mStatusBar.postStartActivityDismissingKeyguard(intent);
}
@@ -150,6 +224,11 @@ public class QSTileHost implements QSTile.Host, Tunable {
}
@Override
+ public RemoteViews.OnClickHandler getOnClickHandler() {
+ return mStatusBar.getOnClickHandler();
+ }
+
+ @Override
public Looper getLooper() {
return mLooper;
}
@@ -204,6 +283,11 @@ public class QSTileHost implements QSTile.Host, Tunable {
return mKeyguard;
}
+ @Override
+ public BatteryController getBatteryController() {
+ return mBattery;
+ }
+
public UserSwitcherController getUserSwitcherController() {
return mUserSwitcherController;
}
@@ -214,14 +298,14 @@ public class QSTileHost implements QSTile.Host, Tunable {
@Override
public void onTuningChanged(String key, String newValue) {
- if (!TILES_SETTING.equals(key)) {
+ if (!CMSettings.Secure.QS_TILES.equals(key)) {
return;
}
if (DEBUG) Log.d(TAG, "Recreating tiles");
final List<String> tileSpecs = loadTileSpecs(newValue);
if (tileSpecs.equals(mTileSpecs)) return;
for (Map.Entry<String, QSTile<?>> tile : mTiles.entrySet()) {
- if (!tileSpecs.contains(tile.getKey())) {
+ if (!tileSpecs.contains(tile.getKey()) && mCustomTileData.get(tile.getKey()) == null) {
if (DEBUG) Log.d(TAG, "Destroying tile: " + tile.getKey());
tile.getValue().destroy();
}
@@ -233,7 +317,14 @@ public class QSTileHost implements QSTile.Host, Tunable {
} else {
if (DEBUG) Log.d(TAG, "Creating tile: " + tileSpec);
try {
- newTiles.put(tileSpec, createTile(tileSpec));
+ if (mCustomTileData.get(tileSpec) != null) {
+ final CustomQSTile value = new CustomQSTile(this,
+ mCustomTileData.get(tileSpec).sbc);
+ newTiles.put(tileSpec, value);
+ } else {
+ final QSTile<?> tile = createTile(tileSpec);
+ newTiles.put(tileSpec, tile);
+ }
} catch (Throwable t) {
Log.w(TAG, "Error creating tile for spec: " + tileSpec, t);
}
@@ -248,7 +339,14 @@ public class QSTileHost implements QSTile.Host, Tunable {
}
}
- protected QSTile<?> createTile(String tileSpec) {
+ @Override
+ public void goToSettingsPage() {
+ if (mCallback != null) {
+ mCallback.goToSettingsPage();
+ }
+ }
+
+ public QSTile<?> createTile(String tileSpec) {
if (tileSpec.equals("wifi")) return new WifiTile(this);
else if (tileSpec.equals("bt")) return new BluetoothTile(this);
else if (tileSpec.equals("inversion")) return new ColorInversionTile(this);
@@ -260,13 +358,34 @@ public class QSTileHost implements QSTile.Host, Tunable {
else if (tileSpec.equals("location")) return new LocationTile(this);
else if (tileSpec.equals("cast")) return new CastTile(this);
else if (tileSpec.equals("hotspot")) return new HotspotTile(this);
+ else if (tileSpec.equals("edit")) return new EditTile(this);
+ else if (tileSpec.equals("adb_network")) return new AdbOverNetworkTile(this);
+ else if (tileSpec.equals("compass")) return new CompassTile(this);
+ else if (tileSpec.equals("nfc")) return new NfcTile(this);
+ else if (tileSpec.equals("profiles")) return new ProfilesTile(this);
+ else if (tileSpec.equals("sync")) return new SyncTile(this);
+ else if (tileSpec.equals("volume_panel")) return new VolumeTile(this);
+ else if (tileSpec.equals("usb_tether")) return new UsbTetherTile(this);
+ else if (tileSpec.equals("screen_timeout")) return new ScreenTimeoutTile(this);
+ else if (tileSpec.equals("performance")) return new PerfProfileTile(this);
+ else if (tileSpec.equals("lockscreen")) return new LockscreenToggleTile(this);
+ else if (tileSpec.equals("ambient_display")) return new AmbientDisplayTile(this);
+ else if (tileSpec.equals("heads_up")) return new HeadsUpTile(this);
+ else if (tileSpec.equals("battery_saver")) return new BatterySaverTile(this);
+ else if (tileSpec.equals("caffeine")) return new CaffeineTile(this);
else if (tileSpec.startsWith(IntentTile.PREFIX)) return IntentTile.create(this,tileSpec);
- else throw new IllegalArgumentException("Bad tile spec: " + tileSpec);
+ else if (TextUtils.split(tileSpec, "\\|").length == 3) {
+ /** restores placeholder for
+ * {@link cyanogenmod.app.StatusBarPanelCustomTile#persistableKey()} **/
+ return new CustomQSTile(this, tileSpec);
+ } else
+ throw new IllegalArgumentException("Bad tile spec: " + tileSpec);
}
protected List<String> loadTileSpecs(String tileList) {
final Resources res = mContext.getResources();
- final String defaultTileList = res.getString(R.string.quick_settings_tiles_default);
+ final String defaultTileList = res.getString(org.cyanogenmod.platform.internal.
+ R.string.config_defaultQuickSettingsTiles);
if (tileList == null) {
tileList = res.getString(R.string.quick_settings_tiles);
if (DEBUG) Log.d(TAG, "Loaded tile specs from config: " + tileList);
@@ -287,6 +406,140 @@ public class QSTileHost implements QSTile.Host, Tunable {
tiles.add(tile);
}
}
+ // ensure edit tile is present, default placement should be handled in the default
+ // tile list.
+ if (!tiles.contains("edit")) {
+ tiles.add("edit");
+ }
return tiles;
}
+
+ public void remove(String tile) {
+ MetricsLogger.action(getContext(), MetricsLogger.TUNER_QS_REMOVE, tile);
+ List<String> tiles = new ArrayList<>(mTileSpecs);
+ tiles.remove(tile);
+ setTiles(tiles);
+ }
+
+ public void setTiles(List<String> tiles) {
+ CMSettings.Secure.putStringForUser(getContext().getContentResolver(),
+ CMSettings.Secure.QS_TILES,
+ TextUtils.join(",", tiles), ActivityManager.getCurrentUser());
+ }
+
+ public void initiateReset() {
+ if (mCallback != null) {
+ mCallback.resetTiles();
+ }
+ }
+
+ @Override
+ public void resetTiles() {
+ CMSettings.Secure.putStringForUser(getContext().getContentResolver(),
+ CMSettings.Secure.QS_TILES, "default", ActivityManager.getCurrentUser());
+ }
+
+ public QSTile<?> getTile(String spec) {
+ return mTiles.get(spec);
+ }
+
+ public static int getLabelResource(String spec) {
+ if (spec.equals("wifi")) return R.string.quick_settings_wifi_label;
+ else if (spec.equals("bt")) return R.string.quick_settings_bluetooth_label;
+ else if (spec.equals("inversion")) return R.string.quick_settings_inversion_label;
+ else if (spec.equals("cell")) return R.string.quick_settings_cellular_detail_title;
+ else if (spec.equals("airplane")) return R.string.airplane_mode;
+ else if (spec.equals("dnd")) return R.string.quick_settings_dnd_label;
+ else if (spec.equals("rotation")) return R.string.quick_settings_rotation_locked_label;
+ else if (spec.equals("flashlight")) return R.string.quick_settings_flashlight_label;
+ else if (spec.equals("location")) return R.string.quick_settings_location_label;
+ else if (spec.equals("cast")) return R.string.quick_settings_cast_title;
+ else if (spec.equals("hotspot")) return R.string.quick_settings_hotspot_label;
+ else if (spec.equals("edit")) return R.string.quick_settings_edit_label;
+ else if (spec.equals("adb_network")) return R.string.quick_settings_network_adb_label;
+ else if (spec.equals("compass")) return R.string.quick_settings_compass_label;
+ else if (spec.equals("nfc")) return R.string.quick_settings_nfc_label;
+ else if (spec.equals("profiles")) return R.string.quick_settings_profiles;
+ else if (spec.equals("sync")) return R.string.quick_settings_sync_label;
+ else if (spec.equals("volume_panel")) return R.string.quick_settings_volume_panel_label;
+ else if (spec.equals("usb_tether")) return R.string.quick_settings_usb_tether_label;
+ else if (spec.equals("screen_timeout")) return R.string.quick_settings_screen_timeout_detail_title;
+ else if (spec.equals("performance")) return R.string.qs_tile_performance;
+ else if (spec.equals("lockscreen")) return R.string.quick_settings_lockscreen_label;
+ else if (spec.equals("ambient_display")) return R.string.quick_settings_ambient_display_label;
+ else if (spec.equals("heads_up")) return R.string.quick_settings_heads_up_label;
+ else if (spec.equals("battery_saver")) return R.string.quick_settings_battery_saver_label;
+ else if (spec.equals("caffeine")) return R.string.quick_settings_caffeine_label;
+ return 0;
+ }
+
+ public static int getIconResource(String spec) {
+ if (spec.equals("wifi")) return R.drawable.ic_qs_wifi_full_4;
+ else if (spec.equals("bt")) return R.drawable.ic_qs_bluetooth_on;
+ else if (spec.equals("inversion")) return R.drawable.ic_invert_colors_enable_animation;
+ else if (spec.equals("cell")) return R.drawable.ic_qs_signal_full_4;
+ else if (spec.equals("airplane")) return R.drawable.ic_signal_airplane_enable;
+ else if (spec.equals("dnd")) return R.drawable.ic_dnd;
+ else if (spec.equals("rotation")) return R.drawable.ic_portrait_from_auto_rotate;
+ else if (spec.equals("flashlight")) return R.drawable.ic_signal_flashlight_enable;
+ else if (spec.equals("location")) return R.drawable.ic_signal_location_enable;
+ else if (spec.equals("cast")) return R.drawable.ic_qs_cast_on;
+ else if (spec.equals("hotspot")) return R.drawable.ic_hotspot_enable;
+ else if (spec.equals("edit")) return R.drawable.ic_qs_edit_tiles;
+ else if (spec.equals("adb_network")) return R.drawable.ic_qs_network_adb_on;
+ else if (spec.equals("compass")) return R.drawable.ic_qs_compass_on;
+ else if (spec.equals("nfc")) return R.drawable.ic_qs_nfc_on;
+ else if (spec.equals("profiles")) return R.drawable.ic_qs_profiles_on;
+ else if (spec.equals("sync")) return R.drawable.ic_qs_sync_on;
+ else if (spec.equals("volume_panel")) return R.drawable.ic_qs_volume_panel;
+ else if (spec.equals("usb_tether")) return R.drawable.ic_qs_usb_tether_on;
+ else if (spec.equals("screen_timeout")) return R.drawable.ic_qs_screen_timeout_short_avd;
+ else if (spec.equals("performance")) return R.drawable.ic_qs_perf_profile;
+ else if (spec.equals("lockscreen")) return R.drawable.ic_qs_lock_screen_on;
+ else if (spec.equals("ambient_display")) return R.drawable.ic_qs_ambientdisplay_on;
+ else if (spec.equals("heads_up")) return R.drawable.ic_qs_heads_up_on;
+ else if (spec.equals("battery_saver")) return R.drawable.ic_qs_battery_saver_on;
+ else if (spec.equals("caffeine")) return R.drawable.ic_qs_caffeine_on;
+ return 0;
+ }
+
+ void updateCustomTile(StatusBarPanelCustomTile sbc) {
+ synchronized (mTiles) {
+ if (mTiles.containsKey(sbc.persistableKey())) {
+ QSTile<?> tile = mTiles.get(sbc.persistableKey());
+ if (tile instanceof CustomQSTile) {
+ CustomQSTile qsTile = (CustomQSTile) tile;
+ qsTile.update(sbc);
+ }
+ }
+ }
+ }
+
+ void addCustomTile(StatusBarPanelCustomTile sbc) {
+ synchronized (mTiles) {
+ mCustomTileData.add(new CustomTileData.Entry(sbc));
+ mTileSpecs.add(sbc.persistableKey());
+ mTiles.put(sbc.persistableKey(), new CustomQSTile(this, sbc));
+ if (mCallback != null) {
+ mCallback.onTilesChanged();
+ }
+ }
+ }
+
+ void removeCustomTileSysUi(String key) {
+ synchronized (mTiles) {
+ if (mTiles.containsKey(key)) {
+ mTileSpecs.remove(key);
+ mTiles.remove(key);
+ mCustomTileData.remove(key);
+ if (mCallback != null) {
+ mCallback.onTilesChanged();
+ }
+ }
+ }
+ }
+
+ public CustomTileData getCustomTileData() {
+ return mCustomTileData;
+ }
}
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 b9e9292..975cb77 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
@@ -45,9 +45,9 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener,
public static final long ANIMATION_DURATION = 220;
public static final Interpolator KEYGUARD_FADE_OUT_INTERPOLATOR
= new PathInterpolator(0f, 0, 0.7f, 1f);
+ public static final float SCRIM_BEHIND_ALPHA_KEYGUARD = 0.45f;
private static final float SCRIM_BEHIND_ALPHA = 0.62f;
- private static final float SCRIM_BEHIND_ALPHA_KEYGUARD = 0.45f;
private static final float SCRIM_BEHIND_ALPHA_UNLOCKING = 0.2f;
private static final float SCRIM_IN_FRONT_ALPHA = 0.75f;
private static final int TAG_KEY_ANIM = R.id.scrim;
@@ -255,7 +255,7 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener,
}
}
- private void setScrimBehindColor(float alpha) {
+ public void setScrimBehindColor(float alpha) {
setScrimColor(mScrimBehind, alpha);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SettingsButton.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SettingsButton.java
index 18db5b8..3e0aa18 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SettingsButton.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SettingsButton.java
@@ -17,18 +17,18 @@ package com.android.systemui.statusbar.phone;
import android.animation.Animator;
import android.animation.Animator.AnimatorListener;
+import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
import android.content.Context;
-import android.graphics.drawable.RippleDrawable;
-import android.os.Handler;
-import android.os.Message;
import android.util.AttributeSet;
+import android.view.HapticFeedbackConstants;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
+import android.view.animation.DecelerateInterpolator;
import com.android.keyguard.AlphaOptimizedImageButton;
public class SettingsButton extends AlphaOptimizedImageButton {
@@ -56,6 +56,10 @@ public class SettingsButton extends AlphaOptimizedImageButton {
return mUpToSpeed;
}
+ public void consumeClick() {
+ mUpToSpeed = false;
+ }
+
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getActionMasked()) {
@@ -78,6 +82,7 @@ public class SettingsButton extends AlphaOptimizedImageButton {
if ((x < -mSlop) || (y < -mSlop) || (x > getWidth() + mSlop)
|| (y > getHeight() + mSlop)) {
cancelLongClick();
+ startExitAnimation();
}
break;
}
@@ -99,32 +104,11 @@ public class SettingsButton extends AlphaOptimizedImageButton {
}
private void startExitAnimation() {
+ cancelAnimation();
animate()
- .translationX(((View) getParent().getParent()).getWidth() - getX())
- .alpha(0)
- .setDuration(RUN_DURATION)
- .setInterpolator(AnimationUtils.loadInterpolator(mContext,
- android.R.interpolator.accelerate_cubic))
- .setListener(new AnimatorListener() {
- @Override
- public void onAnimationStart(Animator animation) {
- }
-
- @Override
- public void onAnimationRepeat(Animator animation) {
- }
-
- @Override
- public void onAnimationEnd(Animator animation) {
- setAlpha(1f);
- setTranslationX(0);
- cancelLongClick();
- }
-
- @Override
- public void onAnimationCancel(Animator animation) {
- }
- })
+ .rotation(0)
+ .setDuration(ACCEL_LENGTH)
+ .setInterpolator(new DecelerateInterpolator(4))
.start();
}
@@ -164,6 +148,8 @@ public class SettingsButton extends AlphaOptimizedImageButton {
mAnimator.setDuration(FULL_SPEED_LENGTH);
mAnimator.setRepeatCount(Animation.INFINITE);
mAnimator.start();
+
+ performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
}
private final Runnable mLongPressCallback = new Runnable() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeaderView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeaderView.java
index 971978d..f9b1f38 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeaderView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeaderView.java
@@ -16,18 +16,31 @@
package com.android.systemui.statusbar.phone;
+import android.app.ActivityManager;
import android.app.AlarmManager;
import android.app.PendingIntent;
+import android.content.ContentUris;
+import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.res.Configuration;
import android.content.res.Resources;
+import android.database.ContentObserver;
import android.graphics.Outline;
import android.graphics.Rect;
import android.graphics.drawable.Animatable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.RippleDrawable;
+import android.os.Handler;
+import android.os.IRemoteCallback;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.provider.AlarmClock;
+import android.provider.CalendarContract;
+import android.provider.Settings;
+import android.net.Uri;
import android.util.AttributeSet;
+import android.util.Log;
import android.util.MathUtils;
import android.util.TypedValue;
import android.view.View;
@@ -41,30 +54,42 @@ import android.widget.TextView;
import android.widget.Toast;
import com.android.keyguard.KeyguardStatusView;
+import com.android.systemui.BatteryLevelTextView;
import com.android.systemui.BatteryMeterView;
+import com.android.systemui.DockBatteryMeterView;
import com.android.systemui.FontSizeUtils;
import com.android.systemui.R;
+import com.android.systemui.cm.UserContentObserver;
+import com.android.systemui.qs.QSDragPanel;
import com.android.systemui.qs.QSPanel;
import com.android.systemui.qs.QSTile;
import com.android.systemui.statusbar.policy.BatteryController;
+import com.android.systemui.statusbar.policy.DockBatteryController;
import com.android.systemui.statusbar.policy.NetworkControllerImpl.EmergencyListener;
import com.android.systemui.statusbar.policy.NextAlarmController;
import com.android.systemui.statusbar.policy.UserInfoController;
+import com.android.systemui.statusbar.policy.WeatherController;
+import com.android.systemui.statusbar.policy.WeatherControllerImpl;
import com.android.systemui.tuner.TunerService;
import java.text.NumberFormat;
+import cyanogenmod.app.StatusBarPanelCustomTile;
+import cyanogenmod.providers.CMSettings;
+import cyanogenmod.weather.util.WeatherUtils;
+import org.cyanogenmod.internal.logging.CMMetricsLogger;
+
/**
* The view to manage the header area in the expanded status bar.
*/
public class StatusBarHeaderView extends RelativeLayout implements View.OnClickListener,
- BatteryController.BatteryStateChangeCallback, NextAlarmController.NextAlarmChangeCallback,
- EmergencyListener {
+ NextAlarmController.NextAlarmChangeCallback, WeatherController.Callback, EmergencyListener {
private boolean mExpanded;
private boolean mListening;
private ViewGroup mSystemIconsContainer;
+ private ViewGroup mWeatherContainer;
private View mSystemIconsSuperContainer;
private View mDateGroup;
private View mClock;
@@ -83,8 +108,11 @@ public class StatusBarHeaderView extends RelativeLayout implements View.OnClickL
private Switch mQsDetailHeaderSwitch;
private ImageView mQsDetailHeaderProgress;
private TextView mEmergencyCallsOnly;
- private TextView mBatteryLevel;
+ private BatteryLevelTextView mBatteryLevel;
+ private BatteryLevelTextView mDockBatteryLevel;
private TextView mAlarmStatus;
+ private TextView mWeatherLine1, mWeatherLine2;
+ private TextView mEditTileDoneText;
private boolean mShowEmergencyCallsOnly;
private boolean mAlarmShowing;
@@ -112,9 +140,9 @@ public class StatusBarHeaderView extends RelativeLayout implements View.OnClickL
private float mAvatarCollapsedScaleFactor;
private ActivityStarter mActivityStarter;
- private BatteryController mBatteryController;
private NextAlarmController mNextAlarmController;
- private QSPanel mQSPanel;
+ private WeatherController mWeatherController;
+ private QSDragPanel mQSPanel;
private final Rect mClipBounds = new Rect();
@@ -127,6 +155,14 @@ public class StatusBarHeaderView extends RelativeLayout implements View.OnClickL
private float mCurrentT;
private boolean mShowingDetail;
private boolean mDetailTransitioning;
+ private SettingsObserver mSettingsObserver;
+ private boolean mShowWeather;
+ private boolean mShowBatteryTextExpanded;
+
+ private QSTile.DetailAdapter mEditingDetailAdapter;
+ private boolean mEditing;
+
+ private UserInfoController mUserInfoController;
public StatusBarHeaderView(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -139,7 +175,9 @@ public class StatusBarHeaderView extends RelativeLayout implements View.OnClickL
mSystemIconsContainer = (ViewGroup) findViewById(R.id.system_icons_container);
mSystemIconsSuperContainer.setOnClickListener(this);
mDateGroup = findViewById(R.id.date_group);
+ mDateGroup.setOnClickListener(this);
mClock = findViewById(R.id.clock);
+ mClock.setOnClickListener(this);
mTime = (TextView) findViewById(R.id.time_view);
mAmPm = (TextView) findViewById(R.id.am_pm_view);
mMultiUserSwitch = (MultiUserSwitch) findViewById(R.id.multi_user_switch);
@@ -155,11 +193,18 @@ public class StatusBarHeaderView extends RelativeLayout implements View.OnClickL
mQsDetailHeaderSwitch = (Switch) mQsDetailHeader.findViewById(android.R.id.toggle);
mQsDetailHeaderProgress = (ImageView) findViewById(R.id.qs_detail_header_progress);
mEmergencyCallsOnly = (TextView) findViewById(R.id.header_emergency_calls_only);
- mBatteryLevel = (TextView) findViewById(R.id.battery_level);
+ mBatteryLevel = (BatteryLevelTextView) findViewById(R.id.battery_level_text);
+ mDockBatteryLevel = (BatteryLevelTextView) findViewById(R.id.dock_battery_level_text);
mAlarmStatus = (TextView) findViewById(R.id.alarm_status);
mAlarmStatus.setOnClickListener(this);
mSignalCluster = findViewById(R.id.signal_cluster);
mSystemIcons = (LinearLayout) findViewById(R.id.system_icons);
+ mWeatherContainer = (LinearLayout) findViewById(R.id.weather_container);
+ mWeatherContainer.setOnClickListener(this);
+ mWeatherLine1 = (TextView) findViewById(R.id.weather_line_1);
+ mWeatherLine2 = (TextView) findViewById(R.id.weather_line_2);
+ mEditTileDoneText = (TextView) findViewById(R.id.done);
+ mSettingsObserver = new SettingsObserver(new Handler());
loadDimens();
updateVisibilities();
updateClockScale();
@@ -188,9 +233,18 @@ public class StatusBarHeaderView extends RelativeLayout implements View.OnClickL
// RenderThread is doing more harm than good when touching the header (to expand quick
// settings), so disable it for this view
- ((RippleDrawable) getBackground()).setForceSoftware(true);
- ((RippleDrawable) mSettingsButton.getBackground()).setForceSoftware(true);
- ((RippleDrawable) mSystemIconsSuperContainer.getBackground()).setForceSoftware(true);
+ Drawable d = getBackground();
+ if (d instanceof RippleDrawable) {
+ ((RippleDrawable) d).setForceSoftware(true);
+ }
+ d = mSettingsButton.getBackground();
+ if (d instanceof RippleDrawable) {
+ ((RippleDrawable) d).setForceSoftware(true);
+ }
+ d = mSystemIconsSuperContainer.getBackground();
+ if (d instanceof RippleDrawable) {
+ ((RippleDrawable) d).setForceSoftware(true);
+ }
}
@Override
@@ -211,7 +265,6 @@ public class StatusBarHeaderView extends RelativeLayout implements View.OnClickL
@Override
protected void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
- FontSizeUtils.updateFontSize(mBatteryLevel, R.dimen.battery_level_text_size);
FontSizeUtils.updateFontSize(mEmergencyCallsOnly,
R.dimen.qs_emergency_calls_only_text_size);
FontSizeUtils.updateFontSize(mDateCollapsed, R.dimen.qs_date_collapsed_size);
@@ -228,10 +281,23 @@ public class StatusBarHeaderView extends RelativeLayout implements View.OnClickL
mClockExpandedSize = getResources().getDimensionPixelSize(R.dimen.qs_time_expanded_size);
mClockCollapsedScaleFactor = (float) mClockCollapsedSize / (float) mClockExpandedSize;
+ if (mEditTileDoneText != null) {
+ mEditTileDoneText.setText(R.string.quick_settings_done);
+ }
+
updateClockScale();
updateClockCollapsedMargin();
}
+ @Override
+ protected void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+ if (mUserInfoController != null) {
+ mUserInfoController.removeListener(mUserInfoChangedListener);
+ }
+ setListening(false);
+ }
+
private void updateClockCollapsedMargin() {
Resources res = getResources();
int padding = res.getDimensionPixelSize(R.dimen.clock_collapsed_bottom_margin);
@@ -274,17 +340,42 @@ public class StatusBarHeaderView extends RelativeLayout implements View.OnClickL
public void setActivityStarter(ActivityStarter activityStarter) {
mActivityStarter = activityStarter;
+ if (mMultiUserSwitch != null) {
+ mMultiUserSwitch.setActivityStarter(activityStarter);
+ }
}
public void setBatteryController(BatteryController batteryController) {
- mBatteryController = batteryController;
- ((BatteryMeterView) findViewById(R.id.battery)).setBatteryController(batteryController);
+ BatteryMeterView v = ((BatteryMeterView) findViewById(R.id.battery));
+ v.setBatteryStateRegistar(batteryController);
+ v.setBatteryController(batteryController);
+ mBatteryLevel.setBatteryStateRegistar(batteryController);
+ }
+
+ public void setDockBatteryController(DockBatteryController dockBatteryController) {
+ DockBatteryMeterView v = ((DockBatteryMeterView) findViewById(R.id.dock_battery));
+ if (dockBatteryController != null) {
+ v.setBatteryStateRegistar(dockBatteryController);
+ mDockBatteryLevel.setBatteryStateRegistar(dockBatteryController);
+ } else {
+ if (v != null) {
+ removeView(v);
+ }
+ if (mDockBatteryLevel != null) {
+ removeView(mDockBatteryLevel);
+ mDockBatteryLevel = null;
+ }
+ }
}
public void setNextAlarmController(NextAlarmController nextAlarmController) {
mNextAlarmController = nextAlarmController;
}
+ public void setWeatherController(WeatherController weatherController) {
+ mWeatherController = weatherController;
+ }
+
public int getCollapsedHeight() {
return mCollapsedHeight;
}
@@ -305,6 +396,9 @@ public class StatusBarHeaderView extends RelativeLayout implements View.OnClickL
boolean changed = expanded != mExpanded;
mExpanded = expanded;
if (changed) {
+ if (mShowingDetail && !expanded) {
+ mQsPanelCallback.onShowingDetail(null);
+ }
updateEverything();
}
}
@@ -335,14 +429,18 @@ public class StatusBarHeaderView extends RelativeLayout implements View.OnClickL
mDateExpanded.setVisibility(mExpanded && mAlarmShowing ? View.INVISIBLE : View.VISIBLE);
mAlarmStatus.setVisibility(mExpanded && mAlarmShowing ? View.VISIBLE : View.INVISIBLE);
mSettingsContainer.setVisibility(mExpanded ? View.VISIBLE : View.INVISIBLE);
- mQsDetailHeader.setVisibility(mExpanded && mShowingDetail? View.VISIBLE : View.INVISIBLE);
+ mWeatherContainer.setVisibility(mExpanded && mShowWeather ? View.VISIBLE : View.GONE);
+ mQsDetailHeader.setVisibility(mExpanded && mShowingDetail ? View.VISIBLE : View.INVISIBLE);
if (mSignalCluster != null) {
updateSignalClusterDetachment();
}
mEmergencyCallsOnly.setVisibility(mExpanded && mShowEmergencyCallsOnly ? VISIBLE : GONE);
- mBatteryLevel.setVisibility(mExpanded ? View.VISIBLE : View.GONE);
- mSettingsContainer.findViewById(R.id.tuner_icon).setVisibility(
- TunerService.isTunerEnabled(mContext) ? View.VISIBLE : View.INVISIBLE);
+ mBatteryLevel.setForceShown(mExpanded && mShowBatteryTextExpanded);
+ mBatteryLevel.setVisibility(View.VISIBLE);
+ if (mDockBatteryLevel != null) {
+ mDockBatteryLevel.setForceShown(mExpanded && mShowBatteryTextExpanded);
+ mDockBatteryLevel.setVisibility(View.VISIBLE);
+ }
}
private void updateSignalClusterDetachment() {
@@ -375,11 +473,13 @@ public class StatusBarHeaderView extends RelativeLayout implements View.OnClickL
private void updateListeners() {
if (mListening) {
- mBatteryController.addStateChangedCallback(this);
+ mSettingsObserver.observe();
mNextAlarmController.addStateChangedCallback(this);
+ mWeatherController.addCallback(this);
} else {
- mBatteryController.removeStateChangedCallback(this);
mNextAlarmController.removeStateChangedCallback(this);
+ mWeatherController.removeCallback(this);
+ mSettingsObserver.unobserve();
}
}
@@ -408,17 +508,6 @@ public class StatusBarHeaderView extends RelativeLayout implements View.OnClickL
}
@Override
- public void onBatteryLevelChanged(int level, boolean pluggedIn, boolean charging) {
- String percentage = NumberFormat.getPercentInstance().format((double) level / 100.0);
- mBatteryLevel.setText(percentage);
- }
-
- @Override
- public void onPowerSaveChanged() {
- // could not care less
- }
-
- @Override
public void onNextAlarmChanged(AlarmManager.AlarmClockInfo nextAlarm) {
mNextAlarm = nextAlarm;
if (nextAlarm != null) {
@@ -429,6 +518,19 @@ public class StatusBarHeaderView extends RelativeLayout implements View.OnClickL
requestCaptureValues();
}
+ @Override
+ public void onWeatherChanged(WeatherController.WeatherInfo info) {
+ if (Double.isNaN(info.temp) || info.condition == null) {
+ mWeatherLine1.setText(null);
+ } else {
+ mWeatherLine1.setText(mContext.getString(
+ R.string.status_bar_expanded_header_weather_format,
+ WeatherUtils.formatTemperature(info.temp, info.tempUnit),
+ info.condition));
+ }
+ mWeatherLine2.setText(info.city);
+ }
+
private void updateClickTargets() {
mMultiUserSwitch.setClickable(mExpanded);
mMultiUserSwitch.setFocusable(mExpanded);
@@ -496,33 +598,31 @@ public class StatusBarHeaderView extends RelativeLayout implements View.OnClickL
invalidateOutline();
}
+ private UserInfoController.OnUserInfoChangedListener mUserInfoChangedListener =
+ new UserInfoController.OnUserInfoChangedListener() {
+ @Override
+ public void onUserInfoChanged(String name, Drawable picture) {
+ mMultiUserAvatar.setImageDrawable(picture);
+ }
+ };
+
public void setUserInfoController(UserInfoController userInfoController) {
- userInfoController.addListener(new UserInfoController.OnUserInfoChangedListener() {
- @Override
- public void onUserInfoChanged(String name, Drawable picture) {
- mMultiUserAvatar.setImageDrawable(picture);
- }
- });
+ mUserInfoController = userInfoController;
+ userInfoController.addListener(mUserInfoChangedListener);
+ if (mMultiUserSwitch != null) {
+ mMultiUserSwitch.setUserInfoController(mUserInfoController);
+ }
}
@Override
public void onClick(View v) {
if (v == mSettingsButton) {
if (mSettingsButton.isTunerClick()) {
- if (TunerService.isTunerEnabled(mContext)) {
- TunerService.showResetRequest(mContext, new Runnable() {
- @Override
- public void run() {
- // Relaunch settings so that the tuner disappears.
- startSettingsActivity();
- }
- });
- } else {
- Toast.makeText(getContext(), R.string.tuner_toast, Toast.LENGTH_LONG).show();
- TunerService.setTunerEnabled(mContext, true);
- }
+ mSettingsButton.consumeClick();
+ mQSPanel.getHost().setEditing(!mQSPanel.getHost().isEditing());
+ } else {
+ startSettingsActivity();
}
- startSettingsActivity();
} else if (v == mSystemIconsSuperContainer) {
startBatteryActivity();
} else if (v == mAlarmStatus && mNextAlarm != null) {
@@ -530,6 +630,12 @@ public class StatusBarHeaderView extends RelativeLayout implements View.OnClickL
if (showIntent != null) {
mActivityStarter.startPendingIntentDismissingKeyguard(showIntent);
}
+ } else if (v == mClock) {
+ startClockActivity();
+ } else if (v == mDateGroup) {
+ startDateActivity();
+ } else if (v == mWeatherContainer) {
+ startForecastActivity();
}
}
@@ -543,7 +649,27 @@ public class StatusBarHeaderView extends RelativeLayout implements View.OnClickL
true /* dismissShade */);
}
- public void setQSPanel(QSPanel qsp) {
+ private void startClockActivity() {
+ mActivityStarter.startActivity(new Intent(AlarmClock.ACTION_SHOW_ALARMS),
+ true /* dismissShade */);
+ }
+
+ private void startDateActivity() {
+ Uri.Builder builder = CalendarContract.CONTENT_URI.buildUpon();
+ builder.appendPath("time");
+ ContentUris.appendId(builder, System.currentTimeMillis());
+ Intent intent = new Intent(Intent.ACTION_VIEW).setData(builder.build());
+ mActivityStarter.startActivity(intent, true /* dismissShade */);
+ }
+
+ private void startForecastActivity() {
+ Intent intent = new Intent(Intent.ACTION_MAIN);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ intent.setComponent(WeatherControllerImpl.COMPONENT_WEATHER_FORECAST);
+ mActivityStarter.startActivity(intent, true /* dismissShade */);
+ }
+
+ public void setQSPanel(QSDragPanel qsp) {
mQSPanel = qsp;
if (mQSPanel != null) {
mQSPanel.setCallback(mQsPanelCallback);
@@ -585,6 +711,7 @@ public class StatusBarHeaderView extends RelativeLayout implements View.OnClickL
target.avatarScale = mMultiUserAvatar.getScaleX();
target.avatarX = mMultiUserSwitch.getLeft() + mMultiUserAvatar.getLeft();
target.avatarY = mMultiUserSwitch.getTop() + mMultiUserAvatar.getTop();
+ target.weatherY = mClock.getBottom() - mWeatherLine1.getHeight();
if (getLayoutDirection() == LAYOUT_DIRECTION_LTR) {
target.batteryX = mSystemIconsSuperContainer.getLeft()
+ mSystemIconsContainer.getRight();
@@ -623,6 +750,7 @@ public class StatusBarHeaderView extends RelativeLayout implements View.OnClickL
mTime.setScaleY(values.timeScale);
mClock.setY(values.clockY - mClock.getHeight());
mDateGroup.setY(values.dateY);
+ mWeatherContainer.setY(values.weatherY);
mAlarmStatus.setY(values.dateY - mAlarmStatus.getPaddingTop());
mMultiUserAvatar.setScaleX(values.avatarScale);
mMultiUserAvatar.setScaleY(values.avatarScale);
@@ -662,7 +790,12 @@ public class StatusBarHeaderView extends RelativeLayout implements View.OnClickL
applyAlpha(mDateCollapsed, values.dateCollapsedAlpha);
applyAlpha(mDateExpanded, values.dateExpandedAlpha);
applyAlpha(mBatteryLevel, values.batteryLevelAlpha);
+ if (mDockBatteryLevel != null) {
+ applyAlpha(mDockBatteryLevel, values.batteryLevelAlpha);
+ }
applyAlpha(mSettingsContainer, values.settingsAlpha);
+ applyAlpha(mWeatherLine1, values.settingsAlpha);
+ applyAlpha(mWeatherLine2, values.settingsAlpha);
applyAlpha(mSignalCluster, values.signalClusterAlpha);
if (!mExpanded) {
mTime.setScaleX(1f);
@@ -671,6 +804,50 @@ public class StatusBarHeaderView extends RelativeLayout implements View.OnClickL
updateAmPmTranslation();
}
+ public void setEditing(boolean editing) {
+ mEditing = editing;
+ if (editing && mEditingDetailAdapter == null) {
+ mEditingDetailAdapter = new QSTile.DetailAdapter() {
+ @Override
+ public int getTitle() {
+ return R.string.quick_settings_edit_label;
+ }
+
+ @Override
+ public Boolean getToggleState() {
+ return null;
+ }
+
+ @Override
+ public View createDetailView(Context context, View convertView, ViewGroup parent) {
+ return null;
+ }
+
+ @Override
+ public Intent getSettingsIntent() {
+ return null;
+ }
+
+ @Override
+ public StatusBarPanelCustomTile getCustomTile() {
+ return null;
+ }
+
+ @Override
+ public void setToggleState(boolean state) {
+
+ }
+
+ @Override
+ public int getMetricsCategory() {
+ return CMMetricsLogger.DONT_LOG;
+ }
+ };
+ }
+ mQsPanelCallback.onShowingDetail(mEditing ? mEditingDetailAdapter : null);
+ updateEverything();
+ }
+
/**
* Captures all layout values (position, visibility) for a certain state. This is used for
* animations.
@@ -690,10 +867,12 @@ public class StatusBarHeaderView extends RelativeLayout implements View.OnClickL
float batteryX;
float batteryY;
float batteryLevelAlpha;
+ float batteryLevelExpandedAlpha;
float settingsAlpha;
float settingsTranslation;
float signalClusterAlpha;
float settingsRotation;
+ float weatherY;
public void interpoloate(LayoutValues v1, LayoutValues v2, float t) {
timeScale = v1.timeScale * (1 - t) + v2.timeScale * t;
@@ -705,6 +884,7 @@ public class StatusBarHeaderView extends RelativeLayout implements View.OnClickL
batteryX = v1.batteryX * (1 - t) + v2.batteryX * t;
batteryY = v1.batteryY * (1 - t) + v2.batteryY * t;
settingsTranslation = v1.settingsTranslation * (1 - t) + v2.settingsTranslation * t;
+ weatherY = v1.weatherY * (1 - t) + v2.weatherY * t;
float t1 = Math.max(0, t - 0.5f) * 2;
settingsRotation = v1.settingsRotation * (1 - t1) + v2.settingsRotation * t1;
@@ -720,6 +900,8 @@ public class StatusBarHeaderView extends RelativeLayout implements View.OnClickL
dateExpandedAlpha = v1.dateExpandedAlpha * (1 - t3) + v2.dateExpandedAlpha * t3;
dateCollapsedAlpha = v1.dateCollapsedAlpha * (1 - t3) + v2.dateCollapsedAlpha * t3;
alarmStatusAlpha = v1.alarmStatusAlpha * (1 - t3) + v2.alarmStatusAlpha * t3;
+ batteryLevelExpandedAlpha =
+ v1.batteryLevelExpandedAlpha * (1 - t3) + v2.batteryLevelExpandedAlpha * t3;
}
}
@@ -742,7 +924,7 @@ public class StatusBarHeaderView extends RelativeLayout implements View.OnClickL
post(new Runnable() {
@Override
public void run() {
- handleShowingDetail(detail);
+ handleShowingDetail(mEditing && detail == null ? mEditingDetailAdapter : detail);
}
});
}
@@ -778,18 +960,33 @@ public class StatusBarHeaderView extends RelativeLayout implements View.OnClickL
final boolean showingDetail = detail != null;
transition(mClock, !showingDetail);
transition(mDateGroup, !showingDetail);
+ if (mShowWeather) {
+ transition(mWeatherContainer, !showingDetail);
+ }
if (mAlarmShowing) {
- transition(mAlarmStatus, !showingDetail);
+ transition(mAlarmStatus, !showingDetail && !mDetailTransitioning);
}
transition(mQsDetailHeader, showingDetail);
mShowingDetail = showingDetail;
if (showingDetail) {
mQsDetailHeaderTitle.setText(detail.getTitle());
final Boolean toggleState = detail.getToggleState();
- if (toggleState == null) {
+ if (detail.getTitle() == R.string.quick_settings_edit_label) {
+ mEditTileDoneText.setVisibility(View.VISIBLE);
mQsDetailHeaderSwitch.setVisibility(INVISIBLE);
+ mQsDetailHeader.setClickable(true);
+ mQsDetailHeader.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ mQSPanel.getHost().setEditing(false);
+ }
+ });
+ } else if (toggleState == null) {
+ mQsDetailHeaderSwitch.setVisibility(INVISIBLE);
+ mEditTileDoneText.setVisibility(View.GONE);
mQsDetailHeader.setClickable(false);
} else {
+ mEditTileDoneText.setVisibility(View.GONE);
mQsDetailHeaderSwitch.setVisibility(VISIBLE);
mQsDetailHeaderSwitch.setChecked(toggleState);
mQsDetailHeader.setClickable(true);
@@ -829,4 +1026,58 @@ public class StatusBarHeaderView extends RelativeLayout implements View.OnClickL
.start();
}
};
+
+ class SettingsObserver extends UserContentObserver {
+ SettingsObserver(Handler handler) {
+ super(handler);
+ }
+
+ @Override
+ protected void observe() {
+ super.observe();
+
+ ContentResolver resolver = mContext.getContentResolver();
+ resolver.registerContentObserver(CMSettings.System.getUriFor(
+ CMSettings.System.STATUS_BAR_SHOW_WEATHER), false, this, UserHandle.USER_ALL);
+ resolver.registerContentObserver(CMSettings.System.getUriFor(
+ CMSettings.System.STATUS_BAR_BATTERY_STYLE), false, this, UserHandle.USER_ALL);
+ resolver.registerContentObserver(CMSettings.System.getUriFor(
+ CMSettings.System.STATUS_BAR_SHOW_BATTERY_PERCENT), false, this, UserHandle.USER_ALL);
+ update();
+ }
+
+ @Override
+ protected void unobserve() {
+ super.unobserve();
+
+ ContentResolver resolver = mContext.getContentResolver();
+ resolver.unregisterContentObserver(this);
+ }
+
+ @Override
+ public void update() {
+
+ ContentResolver resolver = mContext.getContentResolver();
+ int currentUserId = ActivityManager.getCurrentUser();
+ int batteryStyle = CMSettings.System.getIntForUser(resolver,
+ CMSettings.System.STATUS_BAR_BATTERY_STYLE, 0, currentUserId);
+ boolean showExpandedBatteryPercentage = CMSettings.System.getIntForUser(resolver,
+ CMSettings.System.STATUS_BAR_SHOW_BATTERY_PERCENT, 0, currentUserId) == 0;
+
+ switch (batteryStyle) {
+ case 4: //BATTERY_METER_GONE
+ case 6: //BATTERY_METER_TEXT
+ showExpandedBatteryPercentage = false;
+ break;
+ default:
+ break;
+ }
+
+ mShowBatteryTextExpanded = showExpandedBatteryPercentage;
+ mShowWeather = CMSettings.System.getInt(
+ resolver, CMSettings.System.STATUS_BAR_SHOW_WEATHER, 1) == 1;
+ updateVisibilities();
+ requestCaptureValues();
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
index 5de1c13..a7fa27d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
@@ -24,6 +24,7 @@ import android.graphics.Color;
import android.os.Bundle;
import android.os.Handler;
import android.os.SystemClock;
+import android.provider.Settings;
import android.text.TextUtils;
import android.util.ArraySet;
import android.view.View;
@@ -36,12 +37,14 @@ import android.widget.TextView;
import com.android.internal.statusbar.StatusBarIcon;
import com.android.internal.util.NotificationColorUtil;
+import com.android.systemui.BatteryLevelTextView;
import com.android.systemui.BatteryMeterView;
import com.android.systemui.FontSizeUtils;
import com.android.systemui.R;
import com.android.systemui.statusbar.NotificationData;
import com.android.systemui.statusbar.SignalClusterView;
import com.android.systemui.statusbar.StatusBarIconView;
+import com.android.systemui.statusbar.policy.Clock;
import com.android.systemui.tuner.TunerService;
import com.android.systemui.tuner.TunerService.Tunable;
@@ -73,8 +76,10 @@ public class StatusBarIconController implements Tunable {
private IconMerger mNotificationIcons;
private View mNotificationIconArea;
private ImageView mMoreIcon;
+ private BatteryLevelTextView mBatteryLevelTextView;
private BatteryMeterView mBatteryMeterView;
- private TextView mClock;
+ private ClockController mClockController;
+ private View mCenterClockLayout;
private int mIconSize;
private int mIconHPadding;
@@ -117,8 +122,9 @@ public class StatusBarIconController implements Tunable {
mMoreIcon = (ImageView) statusBar.findViewById(R.id.moreIcon);
mNotificationIcons.setOverflowIndicator(mMoreIcon);
mStatusIconsKeyguard = (LinearLayout) keyguardStatusBar.findViewById(R.id.statusIcons);
+ mBatteryLevelTextView =
+ (BatteryLevelTextView) statusBar.findViewById(R.id.battery_level_text);
mBatteryMeterView = (BatteryMeterView) statusBar.findViewById(R.id.battery);
- mClock = (TextView) statusBar.findViewById(R.id.clock);
mLinearOutSlowIn = AnimationUtils.loadInterpolator(mContext,
android.R.interpolator.linear_out_slow_in);
mFastOutSlowIn = AnimationUtils.loadInterpolator(mContext,
@@ -126,6 +132,8 @@ public class StatusBarIconController implements Tunable {
mDarkModeIconColorSingleTone = context.getColor(R.color.dark_mode_icon_color_single_tone);
mLightModeIconColorSingleTone = context.getColor(R.color.light_mode_icon_color_single_tone);
mHandler = new Handler();
+ mClockController = new ClockController(statusBar, mNotificationIcons, mHandler);
+ mCenterClockLayout = statusBar.findViewById(R.id.center_clock_layout);
updateResources();
TunerService.get(mContext).addTunable(this, ICON_BLACKLIST);
@@ -158,7 +166,7 @@ public class StatusBarIconController implements Tunable {
com.android.internal.R.dimen.status_bar_icon_size);
mIconHPadding = mContext.getResources().getDimensionPixelSize(
R.dimen.status_bar_icon_padding);
- FontSizeUtils.updateFontSize(mClock, R.dimen.status_bar_clock_size);
+ mClockController.updateFontSize();
}
public void addSystemIcon(String slot, int index, int viewIndex, StatusBarIcon icon) {
@@ -247,22 +255,26 @@ public class StatusBarIconController implements Tunable {
public void hideSystemIconArea(boolean animate) {
animateHide(mSystemIconArea, animate);
+ animateHide(mCenterClockLayout, animate);
}
public void showSystemIconArea(boolean animate) {
animateShow(mSystemIconArea, animate);
+ animateShow(mCenterClockLayout, animate);
}
public void hideNotificationIconArea(boolean animate) {
animateHide(mNotificationIconArea, animate);
+ animateHide(mCenterClockLayout, animate);
}
public void showNotificationIconArea(boolean animate) {
animateShow(mNotificationIconArea, animate);
+ animateShow(mCenterClockLayout, animate);
}
public void setClockVisibility(boolean visible) {
- mClock.setVisibility(visible ? View.VISIBLE : View.GONE);
+ mClockController.setVisibility(visible);
}
public void dump(PrintWriter pw) {
@@ -392,8 +404,9 @@ public class StatusBarIconController implements Tunable {
}
mSignalCluster.setIconTint(mIconTint, mDarkIntensity);
mMoreIcon.setImageTintList(ColorStateList.valueOf(mIconTint));
+ mBatteryLevelTextView.setTextColor(mIconTint);
mBatteryMeterView.setDarkIntensity(mDarkIntensity);
- mClock.setTextColor(mIconTint);
+ mClockController.setTextColor(mIconTint);
applyNotificationIconsTint();
}
@@ -462,4 +475,32 @@ public class StatusBarIconController implements Tunable {
}
return ret;
}
+
+ public void refreshAllStatusBarIcons() {
+ refreshAllIconsForLayout(mStatusIcons);
+ refreshAllIconsForLayout(mStatusIconsKeyguard);
+ refreshAllIconsForLayout(mNotificationIcons);
+ }
+
+ public LinearLayout getStatusIcons() {
+ return mStatusIcons;
+ }
+
+ public void cleanup() {
+ TunerService.get(mContext).removeTunable(this);
+ mClockController.cleanup();
+ if (mSignalCluster != null) {
+ mSignalCluster.setSecurityController(null);
+ }
+ }
+
+ private void refreshAllIconsForLayout(LinearLayout ll) {
+ final int count = ll.getChildCount();
+ for (int n = 0; n < count; n++) {
+ View child = ll.getChildAt(n);
+ if (child instanceof StatusBarIconView) {
+ ((StatusBarIconView) child).updateDrawable();
+ }
+ }
+ }
}
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 05f6e57..96cf093 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -18,17 +18,21 @@ package com.android.systemui.statusbar.phone;
import android.content.ComponentCallbacks2;
import android.content.Context;
+import android.graphics.PixelFormat;
import android.os.Bundle;
import android.os.SystemClock;
import android.os.Trace;
+import android.view.Gravity;
import android.view.KeyEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewRootImpl;
+import android.view.WindowManager;
import android.view.WindowManagerGlobal;
import com.android.internal.widget.LockPatternUtils;
import com.android.keyguard.KeyguardUpdateMonitor;
+import com.android.keyguard.R;
import com.android.keyguard.ViewMediatorCallback;
import com.android.systemui.statusbar.CommandQueue;
@@ -94,9 +98,10 @@ public class StatusBarKeyguardViewManager {
mContainer = container;
mStatusBarWindowManager = statusBarWindowManager;
mScrimController = scrimController;
+ if (mBouncer != null) mBouncer.removeView();
mFingerprintUnlockController = fingerprintUnlockController;
mBouncer = new KeyguardBouncer(mContext, mViewMediatorCallback, mLockPatternUtils,
- mStatusBarWindowManager, container);
+ mStatusBarWindowManager, container, mPhoneStatusBar);
}
/**
@@ -107,23 +112,38 @@ public class StatusBarKeyguardViewManager {
mShowing = true;
mStatusBarWindowManager.setKeyguardShowing(true);
mScrimController.abortKeyguardFadingOut();
- reset();
+ reset(false);
}
/**
* Shows the notification keyguard or the bouncer depending on
* {@link KeyguardBouncer#needsFullscreenBouncer()}.
*/
- private void showBouncerOrKeyguard() {
- if (mBouncer.needsFullscreenBouncer()) {
-
- // The keyguard might be showing (already). So we need to hide it.
- mPhoneStatusBar.hideKeyguard();
- mBouncer.show(true /* resetSecuritySelection */);
- } else {
- mPhoneStatusBar.showKeyguard();
- mBouncer.hide(false /* destroyView */);
- mBouncer.prepare();
+ private void showBouncerOrKeyguard(boolean isBackPressed) {
+ switch (mBouncer.needsFullscreenBouncer()) {
+ case KeyguardBouncer.UNLOCK_SEQUENCE_FORCE_BOUNCER:
+ // SIM PIN/PUK
+ // The keyguard might be showing (already). So we need to hide it.
+ mPhoneStatusBar.hideKeyguard();
+ mBouncer.show(true /* resetSecuritySelection */);
+ break;
+ case KeyguardBouncer.UNLOCK_SEQUENCE_BOUNCER_FIRST:
+ // Pattern/PIN/Password with "Directly pass to security view" enabled
+ if (isBackPressed) {
+ mPhoneStatusBar.showKeyguard();
+ mBouncer.hide(false /* destroyView */);
+ mBouncer.prepare();
+ } else {
+ // The keyguard might be showing (already). So we need to hide it.
+ mPhoneStatusBar.hideKeyguard();
+ mBouncer.show(true /* resetSecuritySelection */);
+ }
+ break;
+ case KeyguardBouncer.UNLOCK_SEQUENCE_DEFAULT:
+ mPhoneStatusBar.showKeyguard();
+ mBouncer.hide(false /* destroyView */);
+ mBouncer.prepare();
+ break;
}
}
@@ -150,14 +170,14 @@ public class StatusBarKeyguardViewManager {
/**
* Reset the state of the view.
*/
- public void reset() {
+ public void reset(boolean isBackPressed) {
if (mShowing) {
if (mOccluded) {
mPhoneStatusBar.hideKeyguard();
mPhoneStatusBar.stopWaitingForKeyguardExit();
mBouncer.hide(false /* destroyView */);
} else {
- showBouncerOrKeyguard();
+ showBouncerOrKeyguard(isBackPressed);
}
KeyguardUpdateMonitor.getInstance(mContext).sendKeyguardReset();
updateStates();
@@ -197,10 +217,12 @@ public class StatusBarKeyguardViewManager {
updateStates();
}
mPhoneStatusBar.onScreenTurnedOn();
+ mStatusBarWindowManager.onKeyguardChanged();
}
public void onScreenTurnedOff() {
mScreenTurnedOn = false;
+ mPhoneStatusBar.onScreenTurnedOff();
}
public void notifyDeviceWakeUpRequested() {
@@ -224,7 +246,7 @@ public class StatusBarKeyguardViewManager {
@Override
public void run() {
mStatusBarWindowManager.setKeyguardOccluded(mOccluded);
- reset();
+ reset(false);
}
});
return;
@@ -232,7 +254,11 @@ public class StatusBarKeyguardViewManager {
}
mOccluded = occluded;
mStatusBarWindowManager.setKeyguardOccluded(occluded);
- reset();
+ mPhoneStatusBar.getVisualizer().setOccluded(occluded);
+ if (!occluded) {
+ mPhoneStatusBar.mKeyguardBottomArea.setVisibility(View.GONE);
+ }
+ reset(false);
}
public boolean isOccluded() {
@@ -359,16 +385,14 @@ public class StatusBarKeyguardViewManager {
private void executeAfterKeyguardGoneAction() {
if (mAfterKeyguardGoneAction != null) {
+ dismiss();
mAfterKeyguardGoneAction.onDismiss();
mAfterKeyguardGoneAction = null;
}
}
- /**
- * Dismisses the keyguard by going to the next screen or making it gone.
- */
public void dismiss() {
- if (mDeviceInteractive || mDeviceWillWakeUp) {
+ if ((mDeviceInteractive || mDeviceWillWakeUp)) {
showBouncer();
}
}
@@ -395,7 +419,7 @@ public class StatusBarKeyguardViewManager {
public boolean onBackPressed() {
if (mBouncer.isShowing()) {
mPhoneStatusBar.endAffordanceLaunch();
- reset();
+ reset(true);
return true;
}
return false;
@@ -428,7 +452,8 @@ public class StatusBarKeyguardViewManager {
boolean showing = mShowing;
boolean occluded = mOccluded;
boolean bouncerShowing = mBouncer.isShowing();
- boolean bouncerDismissible = !mBouncer.isFullscreenBouncer();
+ boolean bouncerDismissible = (mBouncer.isFullscreenBouncer() !=
+ KeyguardBouncer.UNLOCK_SEQUENCE_FORCE_BOUNCER);
if ((bouncerDismissible || !showing) != (mLastBouncerDismissible || !mLastShowing)
|| mFirstUpdate) {
@@ -503,7 +528,8 @@ public class StatusBarKeyguardViewManager {
}
public boolean shouldDisableWindowAnimationsForUnlock() {
- return mPhoneStatusBar.isInLaunchTransition();
+ return mPhoneStatusBar.isInLaunchTransition() ||
+ mPhoneStatusBar.isShowingLiveLockScreenView();
}
public boolean isGoingToNotificationShade() {
@@ -525,6 +551,10 @@ public class StatusBarKeyguardViewManager {
public void animateCollapsePanels(float speedUpFactor) {
mPhoneStatusBar.animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE, true /* force */,
false /* delayed */, speedUpFactor);
+ if (mStatusBarWindowManager.keyguardExternalViewHasFocus()) {
+ mStatusBarWindowManager.setKeyguardExternalViewFocus(false);
+ dismiss();
+ }
}
/**
@@ -542,4 +572,12 @@ public class StatusBarKeyguardViewManager {
public ViewRootImpl getViewRootImpl() {
return mPhoneStatusBar.getStatusBarView().getViewRootImpl();
}
+
+ public boolean isKeyguardShowingMedia() {
+ return mPhoneStatusBar.isKeyguardShowingMedia();
+ }
+
+ public void setKeyguardExternalViewFocus(boolean hasFocus) {
+ mStatusBarWindowManager.setKeyguardExternalViewFocus(hasFocus);
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowManager.java
index ccfa0dd..f0d7828 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowManager.java
@@ -18,19 +18,28 @@ package com.android.systemui.statusbar.phone;
import android.content.Context;
import android.content.pm.ActivityInfo;
+import android.content.res.Configuration;
import android.content.res.Resources;
+import android.database.ContentObserver;
+import android.graphics.Point;
import android.graphics.PixelFormat;
+import android.os.Handler;
import android.os.SystemProperties;
+import android.provider.Settings;
import android.view.Gravity;
+import android.view.Display;
+import android.view.SurfaceSession;
import android.view.View;
import android.view.ViewGroup;
-import android.view.Window;
import android.view.WindowManager;
import com.android.keyguard.R;
import com.android.systemui.keyguard.KeyguardViewMediator;
import com.android.systemui.statusbar.BaseStatusBar;
import com.android.systemui.statusbar.StatusBarState;
+import com.android.systemui.statusbar.policy.KeyguardMonitor;
+import com.android.systemui.statusbar.policy.LiveLockScreenController;
+import cyanogenmod.providers.CMSettings;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -39,7 +48,7 @@ import java.lang.reflect.Field;
/**
* Encapsulates all logic for the status bar window state management.
*/
-public class StatusBarWindowManager {
+public class StatusBarWindowManager implements KeyguardMonitor.Callback {
private final Context mContext;
private final WindowManager mWindowManager;
@@ -47,22 +56,49 @@ public class StatusBarWindowManager {
private WindowManager.LayoutParams mLp;
private WindowManager.LayoutParams mLpChanged;
private int mBarHeight;
- private final boolean mKeyguardScreenRotation;
+ private boolean mKeyguardScreenRotation;
private final float mScreenBrightnessDoze;
+ private final boolean mBlurSupported;
+
+ private boolean mKeyguardBlurEnabled;
+ private boolean mShowingMedia;
+ private BlurLayer mKeyguardBlur;
+ private final SurfaceSession mFxSession;
+
+ private final KeyguardMonitor mKeyguardMonitor;
+ private int mCurrentOrientation;
+
+ private static final int TYPE_LAYER_MULTIPLIER = 10000; // refer to WindowManagerService.TYPE_LAYER_MULTIPLIER
+ private static final int TYPE_LAYER_OFFSET = 1000; // refer to WindowManagerService.TYPE_LAYER_OFFSET
+
+ private static final int STATUS_BAR_LAYER = 16 * TYPE_LAYER_MULTIPLIER + TYPE_LAYER_OFFSET;
+
private final State mCurrentState = new State();
+ private LiveLockScreenController mLiveLockScreenController;
- public StatusBarWindowManager(Context context) {
+ public StatusBarWindowManager(Context context, KeyguardMonitor kgm) {
mContext = context;
mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
mKeyguardScreenRotation = shouldEnableKeyguardScreenRotation();
mScreenBrightnessDoze = mContext.getResources().getInteger(
com.android.internal.R.integer.config_screenBrightnessDoze) / 255f;
+ mBlurSupported = mContext.getResources().getBoolean(
+ com.android.internal.R.bool.config_ui_blur_enabled);
+
+ mKeyguardMonitor = kgm;
+ mKeyguardMonitor.addCallback(this);
+ mFxSession = new SurfaceSession();
}
private boolean shouldEnableKeyguardScreenRotation() {
Resources res = mContext.getResources();
+ boolean enableAccelerometerRotation = Settings.System.getInt(mContext.getContentResolver(),
+ Settings.System.ACCELEROMETER_ROTATION, 1) != 0;
+ boolean enableLockScreenRotation = CMSettings.System.getInt(mContext.getContentResolver(),
+ CMSettings.System.LOCKSCREEN_ROTATION, 0) != 0;
return SystemProperties.getBoolean("lockscreen.rot_override", false)
- || res.getBoolean(R.bool.config_enableLockScreenRotation);
+ || (res.getBoolean(R.bool.config_enableLockScreenRotation)
+ && (enableLockScreenRotation && enableAccelerometerRotation));
}
/**
@@ -72,7 +108,6 @@ public class StatusBarWindowManager {
* @param barHeight The height of the status bar in collapsed state.
*/
public void add(View statusBarView, int barHeight) {
-
// Now that the status bar window encompasses the sliding panel and its
// translucent backdrop, the entire thing is made TRANSLUCENT and is
// hardware-accelerated.
@@ -96,15 +131,37 @@ public class StatusBarWindowManager {
mWindowManager.addView(mStatusBarView, mLp);
mLpChanged = new WindowManager.LayoutParams();
mLpChanged.copyFrom(mLp);
+
+ mKeyguardBlurEnabled = mBlurSupported ?
+ CMSettings.Secure.getInt(mContext.getContentResolver(),
+ CMSettings.Secure.LOCK_SCREEN_BLUR_ENABLED, 1) == 1 : false;
+ if (mBlurSupported) {
+ Display display = mWindowManager.getDefaultDisplay();
+ Point xy = new Point();
+ display.getRealSize(xy);
+ mCurrentOrientation = mContext.getResources().getConfiguration().orientation;
+ mKeyguardBlur = new BlurLayer(mFxSession, xy.x, xy.y, "KeyGuard");
+ if (mKeyguardBlur != null) {
+ mKeyguardBlur.setLayer(STATUS_BAR_LAYER - 2);
+ }
+ }
+
+ SettingsObserver observer = new SettingsObserver(new Handler());
+ observer.observe(mContext);
}
private void applyKeyguardFlags(State state) {
if (state.keyguardShowing) {
- mLpChanged.flags |= WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER;
mLpChanged.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_KEYGUARD;
+ if (!mKeyguardBlurEnabled || mShowingMedia) {
+ mLpChanged.flags |= WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER;
+ }
} else {
mLpChanged.flags &= ~WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER;
mLpChanged.privateFlags &= ~WindowManager.LayoutParams.PRIVATE_FLAG_KEYGUARD;
+ if (mKeyguardBlurEnabled && mKeyguardBlur != null) {
+ mKeyguardBlur.hide();
+ }
}
}
@@ -217,13 +274,33 @@ public class StatusBarWindowManager {
}
}
+ private void applyKeyguardBlurShow(){
+ boolean isblur = false;
+ if (mCurrentState.keyguardShowing && mKeyguardBlurEnabled
+ && !mCurrentState.keyguardOccluded
+ && !mShowingMedia) {
+ isblur = true;
+ }
+ if (mKeyguardBlur != null) {
+ if (isblur) {
+ mKeyguardBlur.show();
+ } else {
+ mKeyguardBlur.hide();
+ }
+ }
+ }
+
public void setKeyguardShowing(boolean showing) {
mCurrentState.keyguardShowing = showing;
apply(mCurrentState);
}
public void setKeyguardOccluded(boolean occluded) {
+ final boolean oldOccluded = mCurrentState.keyguardOccluded;
mCurrentState.keyguardOccluded = occluded;
+ if (oldOccluded != occluded) {
+ applyKeyguardBlurShow();
+ }
apply(mCurrentState);
}
@@ -263,6 +340,39 @@ public class StatusBarWindowManager {
apply(mCurrentState);
}
+ void setBlur(float b){
+ if (mKeyguardBlurEnabled && mKeyguardBlur != null) {
+ float minBlur = mKeyguardMonitor.isSecure() ? 1.0f : 0.0f;
+ if (b < minBlur) {
+ b = minBlur;
+ } else if (b > 1.0f) {
+ b = 1.0f;
+ }
+ mKeyguardBlur.setBlur(b);
+ }
+ }
+
+ public void setShowingMedia(boolean showingMedia) {
+ mShowingMedia = showingMedia;
+ applyKeyguardBlurShow();
+ }
+
+ public void setKeyguardExternalViewFocus(boolean hasFocus) {
+ mLiveLockScreenController.onLiveLockScreenFocusChanged(hasFocus);
+ // make the keyguard occluded so the external view gets full focus
+ setKeyguardOccluded(hasFocus);
+ }
+
+ public void onConfigurationChanged(Configuration newConfig) {
+ if (mKeyguardBlur != null && newConfig.orientation != mCurrentOrientation) {
+ Display display = mWindowManager.getDefaultDisplay();
+ Point xy = new Point();
+ display.getRealSize(xy);
+ mKeyguardBlur.setSize(xy.x, xy.y);
+ mCurrentOrientation = newConfig.orientation;
+ }
+ }
+
/**
* @param state The {@link StatusBarState} of the status bar.
*/
@@ -300,11 +410,24 @@ public class StatusBarWindowManager {
apply(mCurrentState);
}
+ @Override
+ public void onKeyguardChanged() {
+ applyKeyguardBlurShow();
+ }
+
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
pw.println("StatusBarWindowManager state:");
pw.println(mCurrentState);
}
+ public boolean keyguardExternalViewHasFocus() {
+ return mLiveLockScreenController.getLiveLockScreenHasFocus();
+ }
+
+ public void setLiveLockscreenController(LiveLockScreenController liveLockScreenController) {
+ mLiveLockScreenController = liveLockScreenController;
+ }
+
private static class State {
boolean keyguardShowing;
boolean keyguardOccluded;
@@ -355,4 +478,39 @@ public class StatusBarWindowManager {
return result.toString();
}
}
+
+ private class SettingsObserver extends ContentObserver {
+ public SettingsObserver(Handler handler) {
+ super(handler);
+ }
+
+ public void observe(Context context) {
+ context.getContentResolver().registerContentObserver(
+ CMSettings.Secure.getUriFor(CMSettings.Secure.LOCK_SCREEN_BLUR_ENABLED),
+ false,
+ this);
+ context.getContentResolver().registerContentObserver(
+ Settings.System.getUriFor(Settings.System.ACCELEROMETER_ROTATION),
+ false,
+ this);
+ context.getContentResolver().registerContentObserver(
+ CMSettings.System.getUriFor(CMSettings.System.LOCKSCREEN_ROTATION),
+ false,
+ this);
+ }
+
+ public void unobserve(Context context) {
+ context.getContentResolver().unregisterContentObserver(this);
+ }
+
+ @Override
+ public void onChange(boolean selfChange) {
+ mKeyguardBlurEnabled = mBlurSupported ?
+ CMSettings.Secure.getInt(mContext.getContentResolver(),
+ CMSettings.Secure.LOCK_SCREEN_BLUR_ENABLED, 1) == 1 : false;
+ mKeyguardScreenRotation = shouldEnableKeyguardScreenRotation();
+ // update the state
+ apply(mCurrentState);
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java
index 0e22aa8..56ced84 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java
@@ -17,29 +17,42 @@
package com.android.systemui.statusbar.phone;
import android.app.StatusBarManager;
+import android.content.ContentResolver;
import android.content.Context;
import android.content.res.TypedArray;
+import android.database.ContentObserver;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.graphics.Rect;
import android.media.session.MediaSessionLegacyHelper;
+import android.net.Uri;
+import android.os.Handler;
import android.os.IBinder;
+import android.os.IPowerManager;
+import android.os.PowerManager;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.PowerManager;
+import android.provider.Settings;
import android.util.AttributeSet;
+import android.util.Log;
+import android.view.GestureDetector;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;
+import android.view.ViewConfiguration;
import android.view.ViewRootImpl;
import android.view.WindowManager;
import android.view.WindowManagerGlobal;
import android.widget.FrameLayout;
-
import com.android.systemui.R;
import com.android.systemui.statusbar.BaseStatusBar;
import com.android.systemui.statusbar.DragDownHelper;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.stack.NotificationStackScrollLayout;
+import cyanogenmod.providers.CMSettings;
public class StatusBarWindowView extends FrameLayout {
@@ -51,76 +64,24 @@ public class StatusBarWindowView extends FrameLayout {
private NotificationPanelView mNotificationPanel;
private View mBrightnessMirror;
- private int mRightInset = 0;
-
private PhoneStatusBar mService;
private final Paint mTransparentSrcPaint = new Paint();
+ private int mStatusBarHeaderHeight;
+
+ private boolean mDoubleTapToSleepEnabled;
+ private GestureDetector mDoubleTapGesture;
+ private Handler mHandler = new Handler();
+ private SettingsObserver mSettingsObserver;
+
public StatusBarWindowView(Context context, AttributeSet attrs) {
super(context, attrs);
setMotionEventSplittingEnabled(false);
mTransparentSrcPaint.setColor(0);
mTransparentSrcPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC));
- }
-
- @Override
- protected boolean fitSystemWindows(Rect insets) {
- if (getFitsSystemWindows()) {
- boolean paddingChanged = insets.left != getPaddingLeft()
- || insets.top != getPaddingTop()
- || insets.bottom != getPaddingBottom();
-
- // Super-special right inset handling, because scrims and backdrop need to ignore it.
- if (insets.right != mRightInset) {
- mRightInset = insets.right;
- applyMargins();
- }
- // Drop top inset, apply left inset and pass through bottom inset.
- if (paddingChanged) {
- setPadding(insets.left, 0, 0, 0);
- }
- insets.left = 0;
- insets.top = 0;
- insets.right = 0;
- } else {
- if (mRightInset != 0) {
- mRightInset = 0;
- applyMargins();
- }
- boolean changed = getPaddingLeft() != 0
- || getPaddingRight() != 0
- || getPaddingTop() != 0
- || getPaddingBottom() != 0;
- if (changed) {
- setPadding(0, 0, 0, 0);
- }
- insets.top = 0;
- }
- return false;
- }
-
- private void applyMargins() {
- final int N = getChildCount();
- for (int i = 0; i < N; i++) {
- View child = getChildAt(i);
- if (child.getLayoutParams() instanceof LayoutParams) {
- LayoutParams lp = (LayoutParams) child.getLayoutParams();
- if (!lp.ignoreRightInset && lp.rightMargin != mRightInset) {
- lp.rightMargin = mRightInset;
- child.requestLayout();
- }
- }
- }
- }
-
- @Override
- public FrameLayout.LayoutParams generateLayoutParams(AttributeSet attrs) {
- return new LayoutParams(getContext(), attrs);
- }
-
- @Override
- protected FrameLayout.LayoutParams generateDefaultLayoutParams() {
- return new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
+ mStatusBarHeaderHeight = context
+ .getResources().getDimensionPixelSize(R.dimen.status_bar_header_height);
+ mSettingsObserver = new SettingsObserver(mHandler);
}
@Override
@@ -141,6 +102,21 @@ public class StatusBarWindowView extends FrameLayout {
protected void onAttachedToWindow () {
super.onAttachedToWindow();
+ mSettingsObserver.observe();
+ mDoubleTapGesture = new GestureDetector(mContext, new GestureDetector.SimpleOnGestureListener() {
+ @Override
+ public boolean onDoubleTap(MotionEvent e) {
+ PowerManager pm = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
+ Log.d(TAG, "Gesture!!");
+ if(pm != null)
+ pm.goToSleep(e.getEventTime());
+ else
+ Log.d(TAG, "getSystemService returned null PowerManager");
+
+ return true;
+ }
+ });
+
// We really need to be able to animate while window animations are going on
// so that activities may be started asynchronously from panel animations
final ViewRootImpl root = getViewRootImpl();
@@ -152,11 +128,13 @@ public class StatusBarWindowView extends FrameLayout {
// occur if our window is translucent. Since we are drawing the whole window anyway with
// the scrim, we don't need the window to be cleared in the beginning.
if (mService.isScrimSrcModeEnabled()) {
- IBinder windowToken = getWindowToken();
- WindowManager.LayoutParams lp = (WindowManager.LayoutParams) getLayoutParams();
- lp.token = windowToken;
- setLayoutParams(lp);
- WindowManagerGlobal.getInstance().changeCanvasOpacity(windowToken, true);
+ if (getLayoutParams() instanceof WindowManager.LayoutParams) {
+ WindowManager.LayoutParams lp = (WindowManager.LayoutParams) getLayoutParams();
+ IBinder windowToken = getWindowToken();
+ lp.token = windowToken;
+ setLayoutParams(lp);
+ WindowManagerGlobal.getInstance().changeCanvasOpacity(windowToken, true);
+ }
setWillNotDraw(false);
} else {
setWillNotDraw(!DEBUG);
@@ -164,6 +142,22 @@ public class StatusBarWindowView extends FrameLayout {
}
@Override
+ protected void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+ mSettingsObserver.unobserve();
+ }
+
+ @Override
+ protected boolean fitSystemWindows(Rect insets) {
+ insets.bottom = 0;
+ insets.top = 0;
+ insets.right = 0;
+ insets.left = 0;
+ super.fitSystemWindows(insets);
+ return false;
+ }
+
+ @Override
public boolean dispatchKeyEvent(KeyEvent event) {
boolean down = event.getAction() == KeyEvent.ACTION_DOWN;
switch (event.getKeyCode()) {
@@ -211,6 +205,11 @@ public class StatusBarWindowView extends FrameLayout {
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
boolean intercept = false;
+ if (mDoubleTapToSleepEnabled
+ && ev.getY() < mStatusBarHeaderHeight) {
+ if (DEBUG) Log.w(TAG, "logging double tap gesture");
+ mDoubleTapGesture.onTouchEvent(ev);
+ }
if (mNotificationPanel.isFullyExpanded()
&& mStackScrollLayout.getVisibility() == View.VISIBLE
&& mService.getBarState() == StatusBarState.KEYGUARD
@@ -251,7 +250,7 @@ public class StatusBarWindowView extends FrameLayout {
}
@Override
- public void onDraw(Canvas canvas) {
+ protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (mService.isScrimSrcModeEnabled()) {
// We need to ensure that our window is always drawn fully even when we have paddings,
@@ -288,21 +287,51 @@ public class StatusBarWindowView extends FrameLayout {
}
}
- public class LayoutParams extends FrameLayout.LayoutParams {
+ public void addContent(View content) {
+ addView(content);
+ mStackScrollLayout = (NotificationStackScrollLayout) content.findViewById(
+ R.id.notification_stack_scroller);
+ mNotificationPanel = (NotificationPanelView) content.findViewById(R.id.notification_panel);
+ mDragDownHelper = new DragDownHelper(getContext(), this, mStackScrollLayout, mService);
+ mBrightnessMirror = content.findViewById(R.id.brightness_mirror);
- public boolean ignoreRightInset;
+ }
- public LayoutParams(int width, int height) {
- super(width, height);
+ public void removeContent(View content) {
+ removeView(content);
+ }
+
+ class SettingsObserver extends ContentObserver {
+ SettingsObserver(Handler handler) {
+ super(handler);
}
- public LayoutParams(Context c, AttributeSet attrs) {
- super(c, attrs);
+ void observe() {
+ ContentResolver resolver = mContext.getContentResolver();
+ resolver.registerContentObserver(CMSettings.System.getUriFor(
+ CMSettings.System.DOUBLE_TAP_SLEEP_GESTURE), false, this);
+ update();
+ }
+
+ void unobserve() {
+ ContentResolver resolver = mContext.getContentResolver();
+ resolver.unregisterContentObserver(this);
+ }
+
+ @Override
+ public void onChange(boolean selfChange) {
+ update();
+ }
+
+ @Override
+ public void onChange(boolean selfChange, Uri uri) {
+ update();
+ }
- TypedArray a = c.obtainStyledAttributes(attrs, R.styleable.StatusBarWindowView_Layout);
- ignoreRightInset = a.getBoolean(
- R.styleable.StatusBarWindowView_Layout_ignoreRightInset, false);
- a.recycle();
+ public void update() {
+ ContentResolver resolver = mContext.getContentResolver();
+ mDoubleTapToSleepEnabled = CMSettings.System
+ .getInt(resolver, CMSettings.System.DOUBLE_TAP_SLEEP_GESTURE, 1) == 1;
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java
index d701b3c..9ebb79f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java
@@ -33,12 +33,7 @@ public class SystemUIDialog extends AlertDialog {
super(context, R.style.Theme_SystemUI_Dialog);
mContext = context;
- getWindow().setType(WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL);
- getWindow().addFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM
- | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED);
- WindowManager.LayoutParams attrs = getWindow().getAttributes();
- attrs.setTitle(getClass().getSimpleName());
- getWindow().setAttributes(attrs);
+ makeSystemUIDialog(this);
}
public void setShowForAllUsers(boolean show) {
@@ -62,4 +57,13 @@ public class SystemUIDialog extends AlertDialog {
public void setNegativeButton(int resId, OnClickListener onClick) {
setButton(BUTTON_NEGATIVE, mContext.getString(resId), onClick);
}
+
+ public static void makeSystemUIDialog(AlertDialog d) {
+ d.getWindow().setType(WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL);
+ d.getWindow().addFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM
+ | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED);
+ WindowManager.LayoutParams attrs = d.getWindow().getAttributes();
+ attrs.setTitle(SystemUIDialog.class.getClass().getSimpleName());
+ d.getWindow().setAttributes(attrs);
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ViewLinker.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ViewLinker.java
new file mode 100644
index 0000000..48457c6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ViewLinker.java
@@ -0,0 +1,76 @@
+package com.android.systemui.statusbar.phone;
+
+import android.view.View;
+
+/*
+ Allows mirroring of view states such as alpha, translation...etc
+ */
+public class ViewLinker<T extends View & ViewLinker.ViewLinkerParent> {
+
+ public static final int LINK_ALPHA = 0x1;
+ public static final int LINK_TRANSLATION = 0x2;
+
+ private final LinkInfo[] mLinkedViews;
+ private final T mParent;
+
+ public interface ViewLinkerCallback {
+ void onAlphaChanged(float alpha);
+ void onTranslationXChanged(float translationX);
+ }
+
+ public interface ViewLinkerParent {
+ void registerLinker(ViewLinkerCallback callback);
+ }
+
+ public static class LinkInfo {
+ private View mView;
+ private int mFlags;
+ public LinkInfo(View v, int linkFlags) {
+ mView = v;
+ mFlags = linkFlags;
+ }
+ private boolean supportsFlag(int flag) {
+ return (mFlags & flag) != 0;
+ }
+ }
+
+ private ViewLinkerCallback mCallback = new ViewLinkerCallback() {
+ @Override
+ public void onAlphaChanged(float alpha) {
+ for (LinkInfo v : mLinkedViews) {
+ if (v.supportsFlag(LINK_ALPHA)) {
+ v.mView.setAlpha(alpha);
+ }
+ }
+ }
+
+ @Override
+ public void onTranslationXChanged(float translationX) {
+ for (LinkInfo v : mLinkedViews) {
+ if (v.supportsFlag(LINK_TRANSLATION)) {
+ v.mView.setTranslationX(translationX);
+ }
+ }
+ }
+ };
+
+ public ViewLinker(T parent, LinkInfo... viewsToLink) {
+ mLinkedViews = viewsToLink;
+ mParent = parent;
+ ensureParentNotInLink();
+ parent.registerLinker(mCallback);
+ }
+
+ private void ensureParentNotInLink() {
+ for (LinkInfo v : mLinkedViews) {
+ if (v.mView == mParent) {
+ throw new IllegalStateException("Parent cannot be" +
+ "one of the linked views");
+ }
+ }
+ }
+
+ public View getParent() {
+ return mParent;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java
index d1b69ab..c59a0d8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java
@@ -17,10 +17,14 @@
package com.android.systemui.statusbar.policy;
import android.content.BroadcastReceiver;
+import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.database.ContentObserver;
+import android.net.Uri;
import android.os.BatteryManager;
+import android.os.Handler;
import android.os.PowerManager;
import android.util.Log;
@@ -28,20 +32,38 @@ import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
-public class BatteryController extends BroadcastReceiver {
+import cyanogenmod.providers.CMSettings;
+
+public class BatteryController extends BroadcastReceiver implements BatteryStateRegistar {
private static final String TAG = "BatteryController";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+ public static final int STYLE_ICON_PORTRAIT = 0;
+ public static final int STYLE_CIRCLE = 2;
+ public static final int STYLE_GONE = 4;
+ public static final int STYLE_ICON_LANDSCAPE = 5;
+ public static final int STYLE_TEXT = 6;
+
+ public static final int PERCENTAGE_MODE_OFF = 0;
+ public static final int PERCENTAGE_MODE_INSIDE = 1;
+ public static final int PERCENTAGE_MODE_OUTSIDE = 2;
+
private final ArrayList<BatteryStateChangeCallback> mChangeCallbacks = new ArrayList<>();
private final PowerManager mPowerManager;
private int mLevel;
+ private boolean mPresent;
private boolean mPluggedIn;
private boolean mCharging;
private boolean mCharged;
private boolean mPowerSave;
- public BatteryController(Context context) {
+ private int mStyle;
+ private int mPercentMode;
+ private int mUserId;
+ private SettingsObserver mObserver;
+
+ public BatteryController(Context context, Handler handler) {
mPowerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
IntentFilter filter = new IntentFilter();
@@ -51,22 +73,34 @@ public class BatteryController extends BroadcastReceiver {
context.registerReceiver(this, filter);
updatePowerSave();
+
+ mObserver = new SettingsObserver(context, handler);
+ mObserver.observe();
+ }
+
+ public void setUserId(int userId) {
+ mUserId = userId;
+ mObserver.observe();
}
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
pw.println("BatteryController state:");
pw.print(" mLevel="); pw.println(mLevel);
+ pw.print(" mPresent="); pw.println(mPresent);
pw.print(" mPluggedIn="); pw.println(mPluggedIn);
pw.print(" mCharging="); pw.println(mCharging);
pw.print(" mCharged="); pw.println(mCharged);
pw.print(" mPowerSave="); pw.println(mPowerSave);
}
+ @Override
public void addStateChangedCallback(BatteryStateChangeCallback cb) {
mChangeCallbacks.add(cb);
- cb.onBatteryLevelChanged(mLevel, mPluggedIn, mCharging);
+ cb.onBatteryLevelChanged(mPresent, mLevel, mPluggedIn, mCharging);
+ cb.onBatteryStyleChanged(mStyle, mPercentMode);
}
+ @Override
public void removeStateChangedCallback(BatteryStateChangeCallback cb) {
mChangeCallbacks.remove(cb);
}
@@ -77,6 +111,7 @@ public class BatteryController extends BroadcastReceiver {
mLevel = (int)(100f
* intent.getIntExtra(BatteryManager.EXTRA_LEVEL, 0)
/ intent.getIntExtra(BatteryManager.EXTRA_SCALE, 100));
+ mPresent = intent.getBooleanExtra(BatteryManager.EXTRA_PRESENT, false);
mPluggedIn = intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, 0) != 0;
final int status = intent.getIntExtra(BatteryManager.EXTRA_STATUS,
@@ -110,7 +145,7 @@ public class BatteryController extends BroadcastReceiver {
private void fireBatteryLevelChanged() {
final int N = mChangeCallbacks.size();
for (int i = 0; i < N; i++) {
- mChangeCallbacks.get(i).onBatteryLevelChanged(mLevel, mPluggedIn, mCharging);
+ mChangeCallbacks.get(i).onBatteryLevelChanged(mPresent, mLevel, mPluggedIn, mCharging);
}
}
@@ -121,8 +156,50 @@ public class BatteryController extends BroadcastReceiver {
}
}
- public interface BatteryStateChangeCallback {
- void onBatteryLevelChanged(int level, boolean pluggedIn, boolean charging);
- void onPowerSaveChanged();
+ private void fireSettingsChanged() {
+ final int N = mChangeCallbacks.size();
+ for (int i = 0; i < N; i++) {
+ mChangeCallbacks.get(i).onBatteryStyleChanged(mStyle, mPercentMode);
+ }
}
+
+ private final class SettingsObserver extends ContentObserver {
+ private ContentResolver mResolver;
+ private boolean mRegistered;
+
+ private final Uri STYLE_URI =
+ CMSettings.System.getUriFor(CMSettings.System.STATUS_BAR_BATTERY_STYLE);
+ private final Uri PERCENT_URI =
+ CMSettings.System.getUriFor(CMSettings.System.STATUS_BAR_SHOW_BATTERY_PERCENT);
+
+ public SettingsObserver(Context context, Handler handler) {
+ super(handler);
+ mResolver = context.getContentResolver();
+ }
+
+ public void observe() {
+ if (mRegistered) {
+ mResolver.unregisterContentObserver(this);
+ }
+ mResolver.registerContentObserver(STYLE_URI, false, this, mUserId);
+ mResolver.registerContentObserver(PERCENT_URI, false, this, mUserId);
+ mRegistered = true;
+
+ update();
+ }
+
+ @Override
+ public void onChange(boolean selfChange, Uri uri) {
+ update();
+ }
+
+ private void update() {
+ mStyle = CMSettings.System.getIntForUser(mResolver,
+ CMSettings.System.STATUS_BAR_BATTERY_STYLE, 0, mUserId);
+ mPercentMode = CMSettings.System.getIntForUser(mResolver,
+ CMSettings.System.STATUS_BAR_SHOW_BATTERY_PERCENT, 0, mUserId);
+
+ fireSettingsChanged();
+ }
+ };
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryStateRegistar.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryStateRegistar.java
new file mode 100644
index 0000000..9fe9bb4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryStateRegistar.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2016 The CyanogenMod 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.policy;
+
+public interface BatteryStateRegistar {
+ interface BatteryStateChangeCallback {
+ void onBatteryLevelChanged(boolean present, int level, boolean pluggedIn, boolean charging);
+ void onPowerSaveChanged();
+ void onBatteryStyleChanged(int style, int percentMode);
+ }
+
+ public void addStateChangedCallback(BatteryStateChangeCallback cb);
+ public void removeStateChangedCallback(BatteryStateChangeCallback cb);
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BrightnessMirrorController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BrightnessMirrorController.java
index 0340984..1ccf80b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BrightnessMirrorController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BrightnessMirrorController.java
@@ -38,7 +38,7 @@ public class BrightnessMirrorController {
private final View mPanelHolder;
private final int[] mInt2Cache = new int[2];
- public BrightnessMirrorController(StatusBarWindowView statusBarWindow) {
+ public BrightnessMirrorController(View statusBarWindow) {
mScrimBehind = (ScrimView) statusBarWindow.findViewById(R.id.scrim_behind);
mBrightnessMirror = statusBarWindow.findViewById(R.id.brightness_mirror);
mPanelHolder = statusBarWindow.findViewById(R.id.panel_holder);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/CallbackHandler.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/CallbackHandler.java
index e618cb8..e7f65b4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/CallbackHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/CallbackHandler.java
@@ -126,14 +126,15 @@ public class CallbackHandler extends Handler implements EmergencyListener, Signa
public void setMobileDataIndicators(final IconState statusIcon, final IconState qsIcon,
final int statusType, final int qsType,final boolean activityIn,
final boolean activityOut, final String typeContentDescription,
- final String description, final boolean isWide, final int subId) {
+ final String description, final boolean isWide, final boolean showSeparateRoaming,
+ final int subId) {
post(new Runnable() {
@Override
public void run() {
for (SignalCallback signalCluster : mSignalCallbacks) {
signalCluster.setMobileDataIndicators(statusIcon, qsIcon, statusType, qsType,
activityIn, activityOut, typeContentDescription, description, isWide,
- subId);
+ showSeparateRoaming, subId);
}
}
});
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/CastControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/CastControllerImpl.java
index e344954..835c8ad 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/CastControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/CastControllerImpl.java
@@ -115,7 +115,7 @@ public class CastControllerImpl implements CastController {
}
if (mDiscovering) {
mMediaRouter.addCallback(ROUTE_TYPE_REMOTE_DISPLAY, mMediaCallback,
- MediaRouter.CALLBACK_FLAG_REQUEST_DISCOVERY);
+ MediaRouter.CALLBACK_FLAG_PERFORM_ACTIVE_SCAN);
mCallbackRegistered = true;
} else if (mCallbacks.size() != 0) {
mMediaRouter.addCallback(ROUTE_TYPE_REMOTE_DISPLAY, mMediaCallback,
@@ -164,7 +164,7 @@ public class CastControllerImpl implements CastController {
@Override
public void startCasting(CastDevice device) {
- if (device == null || device.tag == null) return;
+ if (device == null || !(device.tag instanceof RouteInfo)) return;
final RouteInfo route = (RouteInfo) device.tag;
if (DEBUG) Log.d(TAG, "startCasting: " + routeToString(route));
mMediaRouter.selectRoute(ROUTE_TYPE_REMOTE_DISPLAY, route);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java
index 61986ad..0cc82dc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java
@@ -34,6 +34,7 @@ import android.widget.TextView;
import com.android.systemui.DemoMode;
import com.android.systemui.R;
+import com.android.systemui.cm.UserContentObserver;
import java.text.SimpleDateFormat;
import java.util.Calendar;
@@ -52,11 +53,11 @@ public class Clock extends TextView implements DemoMode {
private SimpleDateFormat mClockFormat;
private Locale mLocale;
- private static final int AM_PM_STYLE_NORMAL = 0;
- private static final int AM_PM_STYLE_SMALL = 1;
- private static final int AM_PM_STYLE_GONE = 2;
+ public static final int AM_PM_STYLE_NORMAL = 0;
+ public static final int AM_PM_STYLE_SMALL = 1;
+ public static final int AM_PM_STYLE_GONE = 2;
- private final int mAmPmStyle;
+ private int mAmPmStyle = AM_PM_STYLE_GONE;
public Clock(Context context) {
this(context, null);
@@ -68,15 +69,6 @@ public class Clock extends TextView implements DemoMode {
public Clock(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
- TypedArray a = context.getTheme().obtainStyledAttributes(
- attrs,
- R.styleable.Clock,
- 0, 0);
- try {
- mAmPmStyle = a.getInt(R.styleable.Clock_amPmStyle, AM_PM_STYLE_GONE);
- } finally {
- a.recycle();
- }
}
@Override
@@ -138,7 +130,7 @@ public class Clock extends TextView implements DemoMode {
};
final void updateClock() {
- if (mDemoMode) return;
+ if (mDemoMode || mCalendar == null) return;
mCalendar.setTimeInMillis(System.currentTimeMillis());
setText(getSmallTime());
}
@@ -244,5 +236,11 @@ public class Clock extends TextView implements DemoMode {
setText(getSmallTime());
}
}
+
+ public void setAmPmStyle(int style) {
+ mAmPmStyle = style;
+ mClockFormatString = "";
+ updateClock();
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DateView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DateView.java
index 186005c..720ab44 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DateView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DateView.java
@@ -1,4 +1,7 @@
/*
+ * Copyright (c) 2013-2014, The Linux Foundation. All rights reserved.
+ * Not a Contribution.
+ *
* Copyright (C) 2008 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -105,11 +108,19 @@ public class DateView extends TextView {
}
mCurrentTime.setTime(System.currentTimeMillis());
-
- final String text = mDateFormat.format(mCurrentTime);
+ final String text = getDateFormat();
if (!text.equals(mLastText)) {
setText(text);
mLastText = text;
}
}
+
+ private String getDateFormat() {
+ if (getContext().getResources().getBoolean(
+ com.android.internal.R.bool.def_custom_dateformat)) {
+ return DateFormat.getDateFormat(getContext()).format(mCurrentTime);
+ } else {
+ return mDateFormat.format(mCurrentTime);
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeadZone.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeadZone.java
index 6eb88be..9864a0a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeadZone.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeadZone.java
@@ -46,6 +46,7 @@ public class DeadZone extends View {
// mHold ms, then move back over the course of mDecay ms
private int mHold, mDecay;
private boolean mVertical;
+ private boolean mStartFromRight;
private long mLastPokeTime;
private final Runnable mDebugFlash = new Runnable() {
@@ -73,6 +74,7 @@ public class DeadZone extends View {
int index = a.getInt(R.styleable.DeadZone_orientation, -1);
mVertical = (index == VERTICAL);
+ mStartFromRight = false; // Assume deadzone is starting from the left side of the zone
if (DEBUG)
Slog.v(TAG, this + " size=[" + mSizeMin + "-" + mSizeMax + "] hold=" + mHold
@@ -100,6 +102,7 @@ public class DeadZone extends View {
mShouldFlash = dbg;
mFlashFrac = 0f;
postInvalidate();
+ mFlashFrac = dbg ? 1f : 0f;
}
// I made you a touch event...
@@ -117,7 +120,19 @@ public class DeadZone extends View {
Slog.v(TAG, this + " ACTION_DOWN: " + event.getX() + "," + event.getY());
}
int size = (int) getSize(event.getEventTime());
- if ((mVertical && event.getX() < size) || event.getY() < size) {
+ boolean isCaptured;
+ if (mVertical && mStartFromRight) {
+ // Landscape on the left side of the screen
+ float pixelsFromRight = getWidth() - event.getX();
+ isCaptured = 0 <= pixelsFromRight && pixelsFromRight < size;
+ } else if (mVertical) {
+ // Landscape
+ isCaptured = event.getX() < size;
+ } else {
+ // Portrait
+ isCaptured = event.getY() < size;
+ }
+ if (isCaptured) {
if (CHATTY) {
Slog.v(TAG, "consuming errant click: (" + event.getX() + "," + event.getY() + ")");
}
@@ -147,6 +162,11 @@ public class DeadZone extends View {
return mFlashFrac;
}
+ public void setStartFromRight(boolean startFromRight) {
+ mStartFromRight = startFromRight;
+ if (mShouldFlash) postInvalidate();
+ }
+
@Override
public void onDraw(Canvas can) {
if (!mShouldFlash || mFlashFrac <= 0f) {
@@ -154,7 +174,17 @@ public class DeadZone extends View {
}
final int size = (int) getSize(SystemClock.uptimeMillis());
- can.clipRect(0, 0, mVertical ? size : can.getWidth(), mVertical ? can.getHeight() : size);
+ if (mVertical && mStartFromRight) {
+ // Landscape on the left side of the screen
+ can.clipRect(can.getWidth() - size, 0, can.getWidth(), can.getHeight());
+ } else if (mVertical) {
+ // Landscape
+ can.clipRect(0, 0, size, can.getHeight());
+ } else {
+ // Portrait
+ can.clipRect(0, 0, can.getWidth(), size);
+ }
+
final float frac = DEBUG ? (mFlashFrac - 0.5f) + 0.5f : mFlashFrac;
can.drawARGB((int) (frac * 0xFF), 0xDD, 0xEE, 0xAA);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DockBatteryController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DockBatteryController.java
new file mode 100644
index 0000000..3faf7d0
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DockBatteryController.java
@@ -0,0 +1,159 @@
+/*
+ * Copyright (C) 2016 The CyanogenMod 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.policy;
+
+import android.content.BroadcastReceiver;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.database.ContentObserver;
+import android.net.Uri;
+import android.os.BatteryManager;
+import android.os.Handler;
+import android.provider.Settings;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+
+import cyanogenmod.providers.CMSettings;
+
+public class DockBatteryController extends BroadcastReceiver implements BatteryStateRegistar {
+
+ private final ArrayList<BatteryStateChangeCallback> mChangeCallbacks = new ArrayList<>();
+
+ private int mLevel;
+ private boolean mPresent;
+ private boolean mPluggedIn;
+ private boolean mCharging;
+ private boolean mCharged;
+ private boolean mPowerSave;
+
+ private int mStyle;
+ private int mPercentMode;
+ private int mUserId;
+ private SettingsObserver mObserver;
+
+ public DockBatteryController(Context context, Handler handler) {
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(Intent.ACTION_BATTERY_CHANGED);
+ context.registerReceiver(this, filter);
+
+ mObserver = new SettingsObserver(context, handler);
+ mObserver.observe();
+ }
+
+ public void setUserId(int userId) {
+ mUserId = userId;
+ mObserver.observe();
+ }
+
+ public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ pw.println("BatteryController state:");
+ pw.print(" mLevel="); pw.println(mLevel);
+ pw.print(" mPresent="); pw.println(mPresent);
+ pw.print(" mPluggedIn="); pw.println(mPluggedIn);
+ pw.print(" mCharging="); pw.println(mCharging);
+ pw.print(" mCharged="); pw.println(mCharged);
+ pw.print(" mPowerSave="); pw.println(mPowerSave);
+ }
+
+ @Override
+ public void addStateChangedCallback(BatteryStateChangeCallback cb) {
+ mChangeCallbacks.add(cb);
+ cb.onBatteryLevelChanged(mPresent, mLevel, mPluggedIn, mCharging);
+ cb.onBatteryStyleChanged(mStyle, mPercentMode);
+ }
+
+ @Override
+ public void removeStateChangedCallback(BatteryStateChangeCallback cb) {
+ mChangeCallbacks.remove(cb);
+ }
+
+ public void onReceive(Context context, Intent intent) {
+ final String action = intent.getAction();
+ if (action.equals(Intent.ACTION_BATTERY_CHANGED)) {
+ mLevel = (int)(100f
+ * intent.getIntExtra(BatteryManager.EXTRA_DOCK_LEVEL, 0)
+ / intent.getIntExtra(BatteryManager.EXTRA_DOCK_SCALE, 100));
+ mPresent = intent.getBooleanExtra(BatteryManager.EXTRA_DOCK_PRESENT, false);
+ mPluggedIn = intent.getIntExtra(BatteryManager.EXTRA_DOCK_PLUGGED, 0) != 0;
+
+ final int status = intent.getIntExtra(BatteryManager.EXTRA_DOCK_STATUS,
+ BatteryManager.BATTERY_STATUS_UNKNOWN);
+ mCharged = status == BatteryManager.BATTERY_STATUS_FULL;
+ mCharging = mPluggedIn && (mCharged || status == BatteryManager.BATTERY_STATUS_CHARGING);
+
+ fireBatteryLevelChanged();
+ }
+ }
+
+ private void fireBatteryLevelChanged() {
+ final int N = mChangeCallbacks.size();
+ for (int i = 0; i < N; i++) {
+ mChangeCallbacks.get(i).onBatteryLevelChanged(mPresent, mLevel, mPresent, mCharging);
+ }
+ }
+
+ private void fireSettingsChanged() {
+ final int N = mChangeCallbacks.size();
+ for (int i = 0; i < N; i++) {
+ mChangeCallbacks.get(i).onBatteryStyleChanged(mStyle, mPercentMode);
+ }
+ }
+
+ private final class SettingsObserver extends ContentObserver {
+ private ContentResolver mResolver;
+ private boolean mRegistered;
+
+ private final Uri STYLE_URI =
+ CMSettings.System.getUriFor(CMSettings.System.STATUS_BAR_BATTERY_STYLE);
+ private final Uri PERCENT_URI =
+ CMSettings.System.getUriFor(CMSettings.System.STATUS_BAR_SHOW_BATTERY_PERCENT);
+
+ public SettingsObserver(Context context, Handler handler) {
+ super(handler);
+ mResolver = context.getContentResolver();
+ }
+
+ public void observe() {
+ if (mRegistered) {
+ mResolver.unregisterContentObserver(this);
+ }
+ mResolver.registerContentObserver(STYLE_URI, false, this, mUserId);
+ mResolver.registerContentObserver(PERCENT_URI, false, this, mUserId);
+ mRegistered = true;
+
+ update();
+ }
+
+ @Override
+ public void onChange(boolean selfChange, Uri uri) {
+ update();
+ }
+
+ private void update() {
+ mStyle = CMSettings.System.getIntForUser(mResolver,
+ CMSettings.System.STATUS_BAR_BATTERY_STYLE, 0, mUserId);
+ mPercentMode = CMSettings.System.getIntForUser(mResolver,
+ CMSettings.System.STATUS_BAR_SHOW_BATTERY_PERCENT, 0, mUserId);
+
+ fireSettingsChanged();
+ }
+ };
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/FlashlightController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/FlashlightController.java
index 29a8f67..52a2825 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/FlashlightController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/FlashlightController.java
@@ -16,16 +16,26 @@
package com.android.systemui.statusbar.policy;
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
import android.hardware.camera2.CameraAccessException;
import android.hardware.camera2.CameraCharacteristics;
import android.hardware.camera2.CameraManager;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Process;
+import android.os.PowerManager;
+import android.os.PowerManager.WakeLock;
import android.text.TextUtils;
import android.util.Log;
+import com.android.systemui.R;
+
import java.lang.ref.WeakReference;
import java.util.ArrayList;
@@ -41,6 +51,12 @@ public class FlashlightController {
private static final int DISPATCH_CHANGED = 1;
private static final int DISPATCH_AVAILABILITY_CHANGED = 2;
+ private static boolean mUseWakeLock;
+
+ private static final String ACTION_TURN_FLASHLIGHT_OFF =
+ "com.android.systemui.action.TURN_FLASHLIGHT_OFF";
+
+ private Context mContext;
private final CameraManager mCameraManager;
/** Call {@link #ensureHandler()} before using */
private Handler mHandler;
@@ -54,7 +70,28 @@ public class FlashlightController {
private final String mCameraId;
private boolean mTorchAvailable;
+ private WakeLock mWakeLock;
+
+ private Notification mNotification = null;
+ private boolean mReceiverRegistered;
+ private BroadcastReceiver mReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (ACTION_TURN_FLASHLIGHT_OFF.equals(intent.getAction())) {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ setFlashlight(false);
+ }
+ });
+ } else if (Intent.ACTION_SCREEN_ON.equals(intent.getAction())) {
+ setNotificationShown(true);
+ }
+ }
+ };
+
public FlashlightController(Context mContext) {
+ this.mContext = mContext;
mCameraManager = (CameraManager) mContext.getSystemService(Context.CAMERA_SERVICE);
String cameraId = null;
@@ -67,6 +104,11 @@ public class FlashlightController {
mCameraId = cameraId;
}
+ mUseWakeLock = mContext.getResources().getBoolean(R.bool.flashlight_use_wakelock);
+
+ PowerManager pm = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
+ mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
+
if (mCameraId != null) {
ensureHandler();
mCameraManager.registerTorchCallback(mTorchCallback, mHandler);
@@ -78,12 +120,25 @@ public class FlashlightController {
synchronized (this) {
if (mFlashlightEnabled != enabled) {
mFlashlightEnabled = enabled;
+
+ if (mUseWakeLock) {
+ if (enabled) {
+ if (!mWakeLock.isHeld()) mWakeLock.acquire();
+ } else {
+ if (mWakeLock.isHeld()) mWakeLock.release();
+ }
+ }
+
try {
mCameraManager.setTorchMode(mCameraId, enabled);
} catch (CameraAccessException e) {
Log.e(TAG, "Couldn't set torch mode", e);
mFlashlightEnabled = false;
pendingError = true;
+
+ if (mUseWakeLock && mWakeLock.isHeld()) {
+ mWakeLock.release();
+ }
}
}
}
@@ -93,6 +148,56 @@ public class FlashlightController {
}
}
+ private void setNotificationShown(boolean showNotification) {
+ NotificationManager nm = (NotificationManager)
+ mContext.getSystemService(Context.NOTIFICATION_SERVICE);
+ if (showNotification) {
+ nm.notify(R.string.quick_settings_tile_flashlight_not_title, buildNotification());
+ } else {
+ nm.cancel(R.string.quick_settings_tile_flashlight_not_title);
+ mNotification = null;
+ }
+ }
+
+ private void setListenForScreenOff(boolean listen) {
+ if (listen && !mReceiverRegistered) {
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(ACTION_TURN_FLASHLIGHT_OFF);
+ filter.addAction(Intent.ACTION_SCREEN_ON);
+ mContext.registerReceiver(mReceiver, filter);
+ mReceiverRegistered = true;
+ } else if (!listen) {
+ if (mReceiverRegistered) {
+ mContext.unregisterReceiver(mReceiver);
+ mReceiverRegistered = false;
+ }
+ setNotificationShown(false);
+ }
+ }
+
+ private Notification buildNotification() {
+ if (mNotification == null) {
+ Intent fireMe = new Intent(ACTION_TURN_FLASHLIGHT_OFF);
+ fireMe.addFlags(Intent.FLAG_FROM_BACKGROUND);
+ fireMe.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
+ fireMe.setPackage(mContext.getPackageName());
+
+ final PendingIntent pendingIntent = PendingIntent.getBroadcast(mContext, 0, fireMe, 0);
+ mNotification = new Notification.Builder(mContext)
+ .setContentTitle(
+ mContext.getString(R.string.quick_settings_tile_flashlight_not_title))
+ .setContentText(
+ mContext.getString(R.string.quick_settings_tile_flashlight_not_summary))
+ .setAutoCancel(false)
+ .setOngoing(true)
+ .setVisibility(Notification.VISIBILITY_PUBLIC)
+ .setSmallIcon(R.drawable.ic_signal_flashlight_disable)
+ .setContentIntent(pendingIntent)
+ .build();
+ }
+ return mNotification;
+ }
+
public synchronized boolean isEnabled() {
return mFlashlightEnabled;
}
@@ -188,6 +293,7 @@ public class FlashlightController {
public void onTorchModeUnavailable(String cameraId) {
if (TextUtils.equals(cameraId, mCameraId)) {
setCameraAvailable(false);
+ setListenForScreenOff(false);
}
}
@@ -196,6 +302,7 @@ public class FlashlightController {
if (TextUtils.equals(cameraId, mCameraId)) {
setCameraAvailable(true);
setTorchMode(enabled);
+ setListenForScreenOff(enabled);
}
}
@@ -204,6 +311,11 @@ public class FlashlightController {
synchronized (FlashlightController.this) {
changed = mTorchAvailable != available;
mTorchAvailable = available;
+
+ if (mUseWakeLock && !available) {
+ if (mWakeLock.isHeld())
+ mWakeLock.release();
+ }
}
if (changed) {
if (DEBUG) Log.d(TAG, "dispatchAvailabilityChanged(" + available + ")");
@@ -216,6 +328,11 @@ public class FlashlightController {
synchronized (FlashlightController.this) {
changed = mFlashlightEnabled != enabled;
mFlashlightEnabled = enabled;
+
+ if (mUseWakeLock && !enabled) {
+ if (mWakeLock.isHeld())
+ mWakeLock.release();
+ }
}
if (changed) {
if (DEBUG) Log.d(TAG, "dispatchModeChanged(" + enabled + ")");
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java
index 4a95d3a..3d1212b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java
@@ -160,6 +160,10 @@ public class HeadsUpManager implements ViewTreeObserver.OnComputeInternalInsetsL
mListeners.add(listener);
}
+ public void removeListener(OnHeadsUpChangedListener listener) {
+ mListeners.remove(listener);
+ }
+
public PhoneStatusBar getBar() {
return mBar;
}
@@ -453,7 +457,10 @@ public class HeadsUpManager implements ViewTreeObserver.OnComputeInternalInsetsL
mReleaseOnExpandFinish = false;
} else {
for (NotificationData.Entry entry : mEntriesToRemoveAfterExpand) {
- removeHeadsUpEntry(entry);
+ if (isHeadsUp(entry.key)) {
+ // Maybe the heads-up was removed already
+ removeHeadsUpEntry(entry);
+ }
}
}
mEntriesToRemoveAfterExpand.clear();
@@ -572,6 +579,9 @@ public class HeadsUpManager implements ViewTreeObserver.OnComputeInternalInsetsL
earliestRemovaltime = currentTime + mMinimumDisplayTime;
postTime = Math.max(postTime, currentTime);
removeAutoRemovalCallbacks();
+ if (mEntriesToRemoveAfterExpand.contains(entry)) {
+ mEntriesToRemoveAfterExpand.remove(entry);
+ }
if (!hasFullScreenIntent(entry)) {
long finishTime = postTime + mHeadsUpNotificationDecay;
long removeDelay = Math.max(finishTime - currentTime, mMinimumDisplayTime);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonRipple.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonRipple.java
index 3f63b5f..ed41121 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonRipple.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonRipple.java
@@ -66,16 +66,19 @@ public class KeyButtonRipple extends Drawable {
private final HashSet<Animator> mRunningAnimations = new HashSet<>();
private final ArrayList<Animator> mTmpArray = new ArrayList<>();
+ private int mRippleColor;
+
public KeyButtonRipple(Context ctx, View targetView) {
mMaxWidth = ctx.getResources().getDimensionPixelSize(R.dimen.key_button_ripple_max_width);
mTargetView = targetView;
+ mRippleColor = ctx.getResources().getColor(R.color.navbutton_ripple_color);
}
private Paint getRipplePaint() {
if (mRipplePaint == null) {
mRipplePaint = new Paint();
mRipplePaint.setAntiAlias(true);
- mRipplePaint.setColor(0xffffffff);
+ mRipplePaint.setColor(mRippleColor);
}
return mRipplePaint;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonView.java
index 4d268ce..6fef3d4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonView.java
@@ -18,6 +18,9 @@ package com.android.systemui.statusbar.policy;
import android.app.ActivityManager;
import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.res.Resources;
+import android.content.res.ThemeConfig;
import android.content.res.Configuration;
import android.content.res.TypedArray;
import android.hardware.input.InputManager;
@@ -39,28 +42,46 @@ import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.ImageView;
import com.android.systemui.R;
+import com.android.systemui.statusbar.phone.NavbarEditor;
import static android.view.accessibility.AccessibilityNodeInfo.ACTION_CLICK;
import static android.view.accessibility.AccessibilityNodeInfo.ACTION_LONG_CLICK;
+import cyanogenmod.power.PerformanceManager;
+
public class KeyButtonView extends ImageView {
+ public static final int CURSOR_REPEAT_FLAGS = KeyEvent.FLAG_SOFT_KEYBOARD
+ | KeyEvent.FLAG_KEEP_TOUCH_MODE;
+
private int mContentDescriptionRes;
private long mDownTime;
private int mCode;
+ private boolean mIsSmall;
private int mTouchSlop;
private boolean mSupportsLongpress = true;
+ private boolean mInEditMode;
private AudioManager mAudioManager;
private boolean mGestureAborted;
+ private boolean mPerformedLongClick;
+
+ private PerformanceManager mPerf;
private final Runnable mCheckLongPress = new Runnable() {
public void run() {
if (isPressed()) {
// Log.d("KeyButtonView", "longpressed: " + this);
- if (isLongClickable()) {
+ if (mCode == KeyEvent.KEYCODE_DPAD_LEFT || mCode == KeyEvent.KEYCODE_DPAD_RIGHT) {
+ sendEvent(KeyEvent.ACTION_UP, CURSOR_REPEAT_FLAGS,
+ System.currentTimeMillis(), false);
+ sendEvent(KeyEvent.ACTION_DOWN, CURSOR_REPEAT_FLAGS,
+ System.currentTimeMillis(), false);
+ postDelayed(mCheckLongPress, ViewConfiguration.getKeyRepeatDelay());
+ } else if (isLongClickable()) {
// Just an old-fashioned ImageView
+ mPerformedLongClick = true;
performLongClick();
- } else if (mSupportsLongpress) {
+ } else {
sendEvent(KeyEvent.ACTION_DOWN, KeyEvent.FLAG_LONG_PRESS);
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_LONG_CLICKED);
}
@@ -94,6 +115,7 @@ public class KeyButtonView extends ImageView {
mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
setBackground(new KeyButtonRipple(context, this));
+ mPerf = PerformanceManager.getInstance(context);
}
@Override
@@ -142,7 +164,83 @@ public class KeyButtonView extends ImageView {
return super.performAccessibilityActionInternal(action, arguments);
}
+ @Override
+ public Resources getResources() {
+ ThemeConfig themeConfig = mContext.getResources().getConfiguration().themeConfig;
+ Resources res = null;
+ if (themeConfig != null) {
+ try {
+ final String navbarThemePkgName = themeConfig.getOverlayForNavBar();
+ final String sysuiThemePkgName = themeConfig.getOverlayForStatusBar();
+ // Check if the same theme is applied for systemui, if so we can skip this
+ if (navbarThemePkgName != null && !navbarThemePkgName.equals(sysuiThemePkgName)) {
+ res = mContext.getPackageManager().getThemedResourcesForApplication(
+ mContext.getPackageName(), navbarThemePkgName);
+ }
+ } catch (PackageManager.NameNotFoundException e) {
+ // don't care since we'll handle res being null below
+ }
+ }
+
+ return res != null ? res : super.getResources();
+ }
+
+ public void setEditMode(boolean editMode) {
+ mInEditMode = editMode;
+ updateVisibility();
+ }
+
+ public void setInfo(NavbarEditor.ButtonInfo item, boolean isVertical, boolean isSmall) {
+ final Resources res = getResources();
+ setInfo(item, isVertical, isSmall, res);
+ }
+
+ public void setInfo(NavbarEditor.ButtonInfo item, boolean isVertical, boolean isSmall,
+ Resources res) {
+ final int keyDrawableResId;
+
+ setTag(item);
+ setContentDescription(res.getString(item.contentDescription));
+ mCode = item.keyCode;
+ mIsSmall = isSmall;
+
+ if (isSmall) {
+ keyDrawableResId = item.sideResource;
+ } else if (!isVertical) {
+ keyDrawableResId = item.portResource;
+ } else {
+ keyDrawableResId = item.landResource;
+ }
+ // The reason for setImageDrawable vs setImageResource is because setImageResource calls
+ // relayout() w/o any checks. setImageDrawable performs size checks and only calls relayout
+ // if necessary. We rely on this because otherwise the setX/setY attributes which are post
+ // layout cause it to mess up the layout.
+ setImageDrawable(res.getDrawable(keyDrawableResId));
+ updateVisibility();
+ }
+
+ private void updateVisibility() {
+ if (mInEditMode) {
+ setVisibility(View.VISIBLE);
+ return;
+ }
+
+ NavbarEditor.ButtonInfo info = (NavbarEditor.ButtonInfo) getTag();
+ if (info == NavbarEditor.NAVBAR_EMPTY) {
+ setVisibility(mIsSmall ? View.INVISIBLE : View.GONE);
+ } else if (info == NavbarEditor.NAVBAR_CONDITIONAL_MENU) {
+ setVisibility(View.INVISIBLE);
+ }
+ }
+
+ private boolean supportsLongPress() {
+ return mSupportsLongpress;
+ }
+
public boolean onTouchEvent(MotionEvent ev) {
+ if (mInEditMode) {
+ return false;
+ }
final int action = ev.getAction();
int x, y;
if (action == MotionEvent.ACTION_DOWN) {
@@ -152,22 +250,32 @@ public class KeyButtonView extends ImageView {
return false;
}
+ // A lot of stuff is about to happen. Lets get ready.
+ mPerf.cpuBoost(750000);
+
switch (action) {
case MotionEvent.ACTION_DOWN:
mDownTime = SystemClock.uptimeMillis();
setPressed(true);
- if (mCode != 0) {
+ if (mCode == KeyEvent.KEYCODE_DPAD_LEFT || mCode == KeyEvent.KEYCODE_DPAD_RIGHT) {
+ sendEvent(KeyEvent.ACTION_DOWN, KeyEvent.FLAG_VIRTUAL_HARD_KEY
+ | KeyEvent.FLAG_KEEP_TOUCH_MODE, mDownTime, false);
+ } else if (mCode != 0) {
sendEvent(KeyEvent.ACTION_DOWN, 0, mDownTime);
} else {
// Provide the same haptic feedback that the system offers for virtual keys.
performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY);
}
- removeCallbacks(mCheckLongPress);
- postDelayed(mCheckLongPress, ViewConfiguration.getLongPressTimeout());
+
+ if (supportsLongPress()) {
+ removeCallbacks(mCheckLongPress);
+ postDelayed(mCheckLongPress, ViewConfiguration.getLongPressTimeout());
+ }
+
break;
case MotionEvent.ACTION_MOVE:
- x = (int)ev.getX();
- y = (int)ev.getY();
+ x = (int) ev.getX();
+ y = (int) ev.getY();
setPressed(x >= -mTouchSlop
&& x < getWidth() + mTouchSlop
&& y >= -mTouchSlop
@@ -178,7 +286,13 @@ public class KeyButtonView extends ImageView {
if (mCode != 0) {
sendEvent(KeyEvent.ACTION_UP, KeyEvent.FLAG_CANCELED);
}
+
removeCallbacks(mCheckLongPress);
+
+ if (supportsLongPress()) {
+ removeCallbacks(mCheckLongPress);
+ }
+
break;
case MotionEvent.ACTION_UP:
final boolean doIt = isPressed();
@@ -193,11 +307,17 @@ public class KeyButtonView extends ImageView {
}
} else {
// no key code, just a regular ImageView
- if (doIt) {
+ if (doIt && !mPerformedLongClick) {
performClick();
}
}
+
removeCallbacks(mCheckLongPress);
+
+ if (supportsLongPress()) {
+ removeCallbacks(mCheckLongPress);
+ }
+ mPerformedLongClick = false;
break;
}
@@ -213,10 +333,17 @@ public class KeyButtonView extends ImageView {
}
void sendEvent(int action, int flags, long when) {
+ sendEvent(action, flags, when, true);
+ }
+
+ void sendEvent(int action, int flags, long when, boolean applyDefaultFlags) {
final int repeatCount = (flags & KeyEvent.FLAG_LONG_PRESS) != 0 ? 1 : 0;
+ if (applyDefaultFlags) {
+ flags |= KeyEvent.FLAG_FROM_SYSTEM | KeyEvent.FLAG_VIRTUAL_HARD_KEY;
+ }
final KeyEvent ev = new KeyEvent(mDownTime, when, action, mCode, repeatCount,
0, KeyCharacterMap.VIRTUAL_KEYBOARD, 0,
- flags | KeyEvent.FLAG_FROM_SYSTEM | KeyEvent.FLAG_VIRTUAL_HARD_KEY,
+ flags,
InputDevice.SOURCE_KEYBOARD);
InputManager.getInstance().injectInputEvent(ev,
InputManager.INJECT_INPUT_EVENT_MODE_ASYNC);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/LiveLockScreenController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/LiveLockScreenController.java
new file mode 100644
index 0000000..2f290cc
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/LiveLockScreenController.java
@@ -0,0 +1,349 @@
+package com.android.systemui.statusbar.policy;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.PowerManager;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.util.EventLog;
+
+import android.view.View;
+import com.android.keyguard.KeyguardUpdateMonitor;
+import com.android.systemui.EventLogTags;
+import com.android.systemui.SystemUIApplication;
+import com.android.systemui.keyguard.KeyguardViewMediator;
+import com.android.systemui.statusbar.StatusBarState;
+import com.android.systemui.statusbar.phone.NotificationPanelView;
+import com.android.systemui.statusbar.phone.PhoneStatusBar;
+
+import cyanogenmod.app.CMContextConstants;
+import cyanogenmod.app.ILiveLockScreenChangeListener;
+import cyanogenmod.app.ILiveLockScreenManager;
+import cyanogenmod.app.LiveLockScreenInfo;
+import cyanogenmod.externalviews.KeyguardExternalView;
+
+import java.util.Objects;
+
+public class LiveLockScreenController {
+ private static final String TAG = LiveLockScreenController.class.getSimpleName();
+
+ private ILiveLockScreenManager mLLSM;
+ private Context mContext;
+ private PhoneStatusBar mBar;
+ private NotificationPanelView mPanelView;
+ private ComponentName mLiveLockScreenComponentName;
+ private KeyguardExternalView mLiveLockScreenView;
+ private Handler mHandler;
+
+ private int mStatusBarState;
+
+ private PowerManager mPowerManager;
+
+ private boolean mLlsHasFocus = false;
+
+ private boolean mScreenOnAndInteractive;
+
+ private String mLlsName;
+ private KeyguardViewMediator mKeyguardViewMediator;
+
+ public LiveLockScreenController(Context context, PhoneStatusBar bar,
+ NotificationPanelView panelView) {
+ mContext = context;
+ mHandler = new Handler(Looper.getMainLooper());
+
+ mLLSM = ILiveLockScreenManager.Stub.asInterface(ServiceManager.getService(
+ CMContextConstants.CM_LIVE_LOCK_SCREEN_SERVICE));
+ mBar = bar;
+ mPanelView = panelView;
+ mPowerManager = context.getSystemService(PowerManager.class);
+ mKeyguardViewMediator = ((SystemUIApplication)
+ mContext.getApplicationContext()).getComponent(KeyguardViewMediator.class);
+ registerListener();
+ try {
+ LiveLockScreenInfo llsInfo = mLLSM.getCurrentLiveLockScreen();
+ if (llsInfo != null && llsInfo.component != null) {
+ updateLiveLockScreenView(llsInfo.component);
+ }
+ } catch (RemoteException e) {
+ /* ignore */
+ }
+ }
+
+ public void cleanup() {
+ unregisterListener();
+ mPanelView = null;
+ if (mLiveLockScreenView != null) {
+ mLiveLockScreenView.setProviderComponent(null);
+ }
+ mLiveLockScreenView = null;
+ mLiveLockScreenComponentName = null;
+ }
+
+ public void setBarState(int statusBarState) {
+ if (mStatusBarState != StatusBarState.SHADE && statusBarState == StatusBarState.SHADE) {
+ // going from KEYGUARD or SHADE_LOCKED to SHADE so device has been unlocked
+ onKeyguardDismissed();
+ }
+
+ if (statusBarState == StatusBarState.KEYGUARD) {
+ mBar.getScrimController().forceHideScrims(false);
+ }
+
+ mStatusBarState = statusBarState;
+ if (statusBarState == StatusBarState.KEYGUARD ||
+ statusBarState == StatusBarState.SHADE_LOCKED) {
+ if (mLiveLockScreenComponentName != null) {
+ if (mLiveLockScreenView == null) {
+ mLiveLockScreenView =
+ getExternalKeyguardView(mLiveLockScreenComponentName);
+ if (mLiveLockScreenView != null) {
+ mLiveLockScreenView.registerKeyguardExternalViewCallback(
+ mExternalKeyguardViewCallbacks);
+ }
+ }
+ if (mLiveLockScreenView != null && !mLiveLockScreenView.isAttachedToWindow()) {
+ mBar.updateRowStates();
+ mPanelView.addView(mLiveLockScreenView, 0);
+ }
+ }
+ } else {
+ if (isShowingLiveLockScreenView() && !mBar.isKeyguardInputRestricted()) {
+ mPanelView.removeView(mLiveLockScreenView);
+ }
+ mLlsHasFocus = false;
+ }
+ }
+
+ private ILiveLockScreenChangeListener mChangeListener =
+ new ILiveLockScreenChangeListener.Stub() {
+ @Override
+ public void onLiveLockScreenChanged(LiveLockScreenInfo llsInfo) throws RemoteException {
+ if (mPanelView != null) {
+ updateLiveLockScreenView(llsInfo != null ? llsInfo.component : null);
+ }
+ }
+ };
+
+ private void registerListener() {
+ try {
+ mLLSM.registerChangeListener(mChangeListener);
+ } catch (RemoteException e) {
+ /* ignore */
+ }
+ }
+
+ private void unregisterListener() {
+ try {
+ mLLSM.unregisterChangeListener(mChangeListener);
+ } catch (RemoteException e) {
+ /* ignore */
+ }
+ }
+
+ private KeyguardExternalView getExternalKeyguardView(ComponentName componentName) {
+ try {
+ return new KeyguardExternalView(mContext, null, componentName);
+ } catch (Exception e) {
+ // just return null below and move on
+ }
+ return null;
+ }
+
+ private KeyguardExternalView.KeyguardExternalViewCallbacks mExternalKeyguardViewCallbacks =
+ new KeyguardExternalView.KeyguardExternalViewCallbacks() {
+ @Override
+ public boolean requestDismiss() {
+ if (isShowingLiveLockScreenView()) {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ mBar.showKeyguard();
+ mBar.showBouncer();
+ }
+ });
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public boolean requestDismissAndStartActivity(final Intent intent) {
+ if (isShowingLiveLockScreenView()) {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ mBar.startActivityDismissingKeyguard(intent, false, true, true,
+ null);
+ }
+ });
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public void providerDied() {
+ mLiveLockScreenView.unregisterKeyguardExternalViewCallback(
+ mExternalKeyguardViewCallbacks);
+ mLiveLockScreenView = null;
+ // make sure we're showing the notification panel if the LLS crashed while it had focus
+ if (mLlsHasFocus) {
+ mLlsHasFocus = false;
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ mBar.showKeyguard();
+ }
+ });
+ }
+ }
+
+ @Override
+ public void slideLockscreenIn() {
+ if (mLlsHasFocus) {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ mBar.showKeyguard();
+ }
+ });
+ }
+ }
+ };
+
+ public boolean isShowingLiveLockScreenView() {
+ return mLiveLockScreenView != null && mLiveLockScreenView.isAttachedToWindow();
+ }
+
+ public boolean isLiveLockScreenInteractive() {
+ return mLiveLockScreenView != null && mLiveLockScreenView.isInteractive();
+ }
+
+ public KeyguardExternalView getLiveLockScreenView() {
+ return mLiveLockScreenView;
+ }
+
+ public void onScreenTurnedOn() {
+ mScreenOnAndInteractive = mPowerManager.isInteractive();
+ if (mScreenOnAndInteractive) {
+ if (mLiveLockScreenView != null) mLiveLockScreenView.onScreenTurnedOn();
+ EventLog.writeEvent(EventLogTags.SYSUI_LLS_KEYGUARD_SHOWING, 1);
+ }
+ }
+
+ public void onScreenTurnedOff() {
+ if (mScreenOnAndInteractive) {
+ if (mLiveLockScreenView != null) mLiveLockScreenView.onScreenTurnedOff();
+ if (mStatusBarState != StatusBarState.SHADE) {
+ EventLog.writeEvent(EventLogTags.SYSUI_LLS_KEYGUARD_SHOWING, 0);
+ }
+ mScreenOnAndInteractive = false;
+ }
+ }
+
+ public void onLiveLockScreenFocusChanged(boolean hasFocus) {
+ mKeyguardViewMediator.notifyKeyguardPanelFocusChanged(hasFocus);
+ if (mLiveLockScreenView != null) {
+ // make sure the LLS knows where the notification panel is
+ mLiveLockScreenView.onLockscreenSlideOffsetChanged(hasFocus ? 0f : 1f);
+ }
+ // don't log focus changes when screen is not interactive
+ if (hasFocus != mLlsHasFocus && mPowerManager.isInteractive()) {
+ EventLog.writeEvent(EventLogTags.SYSUI_LLS_NOTIFICATION_PANEL_SHOWN,
+ hasFocus ? 0 : 1);
+ }
+ // Hide statusbar and scrim if live lockscreen
+ // currently has focus
+ mBar.setStatusBarViewVisibility(!hasFocus);
+ mBar.getScrimController().forceHideScrims(hasFocus);
+ mLlsHasFocus = hasFocus;
+ }
+
+ public void onKeyguardDismissed() {
+ if (mLiveLockScreenView != null) mLiveLockScreenView.onKeyguardDismissed();
+ EventLog.writeEvent(EventLogTags.SYSUI_LLS_KEYGUARD_DISMISSED, mLlsHasFocus ? 1 : 0);
+ // Ensure we reset visibility when keyguard is dismissed
+ mBar.setStatusBarViewVisibility(true);
+ mBar.getScrimController().forceHideScrims(false);
+ }
+
+ public boolean getLiveLockScreenHasFocus() {
+ return mLlsHasFocus;
+ }
+
+ public String getLiveLockScreenName() {
+ return mLlsName;
+ }
+
+ private String getLlsNameFromComponentName(ComponentName cn) {
+ if (cn == null) return null;
+
+ PackageManager pm = mContext.getPackageManager();
+ Intent intent = new Intent();
+ intent.setComponent(cn);
+ ResolveInfo ri = pm.resolveService(intent, 0);
+ return ri != null ? ri.serviceInfo.loadLabel(pm).toString() : null;
+ }
+
+ private Runnable mAddNewLiveLockScreenRunnable = new Runnable() {
+ @Override
+ public void run() {
+ if (mLiveLockScreenComponentName != null) {
+ mLiveLockScreenView =
+ getExternalKeyguardView(mLiveLockScreenComponentName);
+ mLiveLockScreenView.registerKeyguardExternalViewCallback(
+ mExternalKeyguardViewCallbacks);
+ if (mStatusBarState != StatusBarState.SHADE) {
+ mPanelView.addView(mLiveLockScreenView);
+ mLiveLockScreenView.onKeyguardShowing(true);
+ }
+ } else {
+ mLiveLockScreenView = null;
+ }
+ }
+ };
+
+ private void updateLiveLockScreenView(final ComponentName cn) {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ // If mThirdPartyKeyguardViewComponent differs from cn, go ahead and update
+ if (!Objects.equals(mLiveLockScreenComponentName, cn)) {
+ mLiveLockScreenComponentName = cn;
+ mLlsName = getLlsNameFromComponentName(cn);
+ if (mLiveLockScreenView != null) {
+ mLiveLockScreenView.unregisterKeyguardExternalViewCallback(
+ mExternalKeyguardViewCallbacks);
+ // setProviderComponent(null) will unbind the existing service
+ mLiveLockScreenView.setProviderComponent(null);
+ if (mPanelView.indexOfChild(mLiveLockScreenView) >= 0) {
+ mLiveLockScreenView.registerOnWindowAttachmentChangedListener(
+ new KeyguardExternalView.OnWindowAttachmentChangedListener() {
+ @Override
+ public void onAttachedToWindow() {
+ }
+
+ @Override
+ public void onDetachedFromWindow() {
+ mLiveLockScreenView
+ .unregisterOnWindowAttachmentChangedListener(
+ this);
+ mHandler.post(mAddNewLiveLockScreenRunnable);
+ }
+ }
+ );
+ mPanelView.removeView(mLiveLockScreenView);
+ } else {
+ mAddNewLiveLockScreenRunnable.run();
+ }
+ }
+ }
+ }
+ });
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationController.java
index 29a8981..a88f22d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationController.java
@@ -19,6 +19,9 @@ package com.android.systemui.statusbar.policy;
public interface LocationController {
boolean isLocationEnabled();
boolean setLocationEnabled(boolean enabled);
+ boolean setLocationMode(int mode);
+ int getLocationCurrentState();
+ boolean isAdvancedSettingsEnabled();
void addSettingsChangedCallback(LocationSettingsChangeCallback cb);
void removeSettingsChangedCallback(LocationSettingsChangeCallback cb);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java
index 93a8fd8..e7867b6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java
@@ -37,6 +37,8 @@ import com.android.systemui.R;
import java.util.ArrayList;
import java.util.List;
+import cyanogenmod.providers.CMSettings;
+
/**
* A controller to manage changes of location related states and update the views accordingly.
*/
@@ -55,6 +57,7 @@ public class LocationControllerImpl extends BroadcastReceiver implements Locatio
private StatusBarManager mStatusBarManager;
private boolean mAreActiveLocationRequests;
+ private int mLastActiveMode;
private ArrayList<LocationSettingsChangeCallback> mSettingsChangeCallbacks =
new ArrayList<LocationSettingsChangeCallback>();
@@ -63,6 +66,11 @@ public class LocationControllerImpl extends BroadcastReceiver implements Locatio
public LocationControllerImpl(Context context, Looper bgLooper) {
mContext = context;
+ // Initialize last active mode. If state was off use the default high accuracy mode
+ mLastActiveMode = getLocationCurrentState();
+ if(mLastActiveMode == Settings.Secure.LOCATION_MODE_OFF)
+ mLastActiveMode = Settings.Secure.LOCATION_MODE_HIGH_ACCURACY;
+
// Register to listen for changes in location settings.
IntentFilter filter = new IntentFilter();
filter.addAction(LocationManager.HIGH_POWER_REQUEST_CHANGE_ACTION);
@@ -107,10 +115,17 @@ public class LocationControllerImpl extends BroadcastReceiver implements Locatio
return false;
}
final ContentResolver cr = mContext.getContentResolver();
+
+ // Store last active mode if we are switching off
+ // so we can restore it at the next enable
+ if(!enabled) {
+ mLastActiveMode = getLocationCurrentState();
+ }
+
// When enabling location, a user consent dialog will pop up, and the
// setting won't be fully enabled until the user accepts the agreement.
int mode = enabled
- ? Settings.Secure.LOCATION_MODE_HIGH_ACCURACY : Settings.Secure.LOCATION_MODE_OFF;
+ ? mLastActiveMode : Settings.Secure.LOCATION_MODE_OFF;
// QuickSettings always runs as the owner, so specifically set the settings
// for the current foreground user.
return Settings.Secure
@@ -118,6 +133,44 @@ public class LocationControllerImpl extends BroadcastReceiver implements Locatio
}
/**
+ * Enable or disable location in settings to a specific mode.
+ *
+ * <p>This will attempt to enable/disable every type of location setting
+ * (e.g. high and balanced power).
+ *
+ * <p>If enabling, a user consent dialog will pop up prompting the user to accept.
+ * If the user doesn't accept, network location won't be enabled.
+ *
+ * @return true if attempt to change setting was successful.
+ */
+ public boolean setLocationMode(int mode) {
+ int currentUserId = ActivityManager.getCurrentUser();
+ if (isUserLocationRestricted(currentUserId)) {
+ return false;
+ }
+ final ContentResolver cr = mContext.getContentResolver();
+ // When enabling location, a user consent dialog will pop up, and the
+ // setting won't be fully enabled until the user accepts the agreement.
+ // QuickSettings always runs as the owner, so specifically set the settings
+ // for the current foreground user.
+ return Settings.Secure.putIntForUser(cr, Settings.Secure.LOCATION_MODE,
+ mode, currentUserId);
+ }
+
+ /**
+ * Returns int corresponding to current location mode in settings.
+ */
+ public int getLocationCurrentState() {
+ int currentUserId = ActivityManager.getCurrentUser();
+ if (isUserLocationRestricted(currentUserId)) {
+ return Settings.Secure.LOCATION_MODE_OFF;
+ }
+ final ContentResolver cr = mContext.getContentResolver();
+ return Settings.Secure.getIntForUser(cr, Settings.Secure.LOCATION_MODE,
+ Settings.Secure.LOCATION_MODE_OFF, currentUserId);
+ }
+
+ /**
* Returns true if location isn't disabled in settings.
*/
public boolean isLocationEnabled() {
@@ -130,6 +183,14 @@ public class LocationControllerImpl extends BroadcastReceiver implements Locatio
}
/**
+ * Check if advanced location tile is enabled in settings
+ */
+ public boolean isAdvancedSettingsEnabled() {
+ return CMSettings.Secure.getIntForUser(mContext.getContentResolver(),
+ CMSettings.Secure.QS_LOCATION_ADVANCED, 0, ActivityManager.getCurrentUser()) == 1;
+ }
+
+ /**
* Returns true if the current user is restricted from using location.
*/
private boolean isUserLocationRestricted(int userId) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java
index b65bf43..f7d6f85 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java
@@ -19,6 +19,7 @@ import android.content.Context;
import android.content.Intent;
import android.net.NetworkCapabilities;
import android.os.Looper;
+import android.os.SystemProperties;
import android.telephony.PhoneStateListener;
import android.telephony.ServiceState;
import android.telephony.SignalStrength;
@@ -159,6 +160,7 @@ public class MobileSignalController extends SignalController<
mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_EVDO_B, TelephonyIcons.THREE_G);
mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_EHRPD, TelephonyIcons.THREE_G);
mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_UMTS, TelephonyIcons.THREE_G);
+ mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_TD_SCDMA, TelephonyIcons.THREE_G);
if (!mConfig.showAtLeast3G) {
mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_UNKNOWN,
@@ -187,12 +189,18 @@ public class MobileSignalController extends SignalController<
mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_HSDPA, hGroup);
mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_HSUPA, hGroup);
mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_HSPA, hGroup);
+ if (mConfig.hspaDataDistinguishable) {
+ hGroup = TelephonyIcons.HP;
+ }
mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_HSPAP, hGroup);
if (mConfig.show4gForLte) {
mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_LTE, TelephonyIcons.FOUR_G);
+ mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_LTE_CA,
+ TelephonyIcons.FOUR_G_PLUS);
} else {
mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_LTE, TelephonyIcons.LTE);
+ mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_LTE_CA, TelephonyIcons.LTE);
}
mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_IWLAN, TelephonyIcons.WFC);
}
@@ -231,6 +239,7 @@ public class MobileSignalController extends SignalController<
int typeIcon = showDataIcon ? icons.mDataType : 0;
mCallbackHandler.setMobileDataIndicators(statusIcon, qsIcon, typeIcon, qsTypeIcon,
activityIn, activityOut, dataContentDescription, description, icons.mIsWide,
+ mCurrentState.showSeparateRoaming,
mSubscriptionInfo.getSubscriptionId());
}
@@ -270,13 +279,15 @@ public class MobileSignalController extends SignalController<
}
private boolean isRoaming() {
- if (isCdma()) {
+ if (mServiceState == null) {
+ return false;
+ } else if (isCdma()) {
final int iconMode = mServiceState.getCdmaEriIconMode();
return mServiceState.getCdmaEriIconIndex() != EriInfo.ROAMING_INDICATOR_OFF
&& (iconMode == EriInfo.ROAMING_ICON_MODE_NORMAL
|| iconMode == EriInfo.ROAMING_ICON_MODE_FLASH);
} else {
- return mServiceState != null && mServiceState.getRoaming();
+ return mServiceState.getRoaming();
}
}
@@ -371,6 +382,13 @@ public class MobileSignalController extends SignalController<
mCurrentState.level = mSignalStrength.getCdmaLevel();
} else {
mCurrentState.level = mSignalStrength.getLevel();
+ if (mConfig.showRsrpSignalLevelforLTE) {
+ int dataType = mServiceState.getDataNetworkType();
+ if (dataType == TelephonyManager.NETWORK_TYPE_LTE ||
+ dataType == TelephonyManager.NETWORK_TYPE_LTE_CA) {
+ mCurrentState.level = getAlternateLteLevel(mSignalStrength);
+ }
+ }
}
}
if (mNetworkToIconLookup.indexOfKey(mDataNetType) >= 0) {
@@ -379,12 +397,18 @@ public class MobileSignalController extends SignalController<
mCurrentState.iconGroup = mDefaultIcons;
}
mCurrentState.dataConnected = mCurrentState.connected
- && mDataState == TelephonyManager.DATA_CONNECTED;
+ && mDataState == TelephonyManager.DATA_CONNECTED
+ && mCurrentState.dataSim;
+ mCurrentState.showSeparateRoaming = false;
if (isCarrierNetworkChangeActive()) {
mCurrentState.iconGroup = TelephonyIcons.CARRIER_NETWORK_CHANGE;
} else if (isRoaming()) {
- mCurrentState.iconGroup = TelephonyIcons.ROAMING;
+ if (SystemProperties.getBoolean("ro.config.always_show_roaming", false)) {
+ mCurrentState.showSeparateRoaming = true;
+ } else {
+ mCurrentState.iconGroup = TelephonyIcons.ROAMING;
+ }
}
if (isEmergencyOnly() != mCurrentState.isEmergency) {
mCurrentState.isEmergency = isEmergencyOnly();
@@ -399,6 +423,21 @@ public class MobileSignalController extends SignalController<
notifyListenersIfNecessary();
}
+ private int getAlternateLteLevel(SignalStrength signalStrength) {
+ int lteRsrp = signalStrength.getLteDbm();
+ int rsrpLevel = SignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN;
+ if (lteRsrp > -44) rsrpLevel = SignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN;
+ else if (lteRsrp >= -97) rsrpLevel = SignalStrength.SIGNAL_STRENGTH_GREAT;
+ else if (lteRsrp >= -105) rsrpLevel = SignalStrength.SIGNAL_STRENGTH_GOOD;
+ else if (lteRsrp >= -113) rsrpLevel = SignalStrength.SIGNAL_STRENGTH_MODERATE;
+ else if (lteRsrp >= -120) rsrpLevel = SignalStrength.SIGNAL_STRENGTH_POOR;
+ else if (lteRsrp >= -140) rsrpLevel = SignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN;
+ if (DEBUG) {
+ Log.d(mTag, "getAlternateLteLevel lteRsrp:" + lteRsrp + " rsrpLevel = " + rsrpLevel);
+ }
+ return rsrpLevel;
+ }
+
@VisibleForTesting
void setActivity(int activity) {
mCurrentState.activityIn = activity == TelephonyManager.DATA_ACTIVITY_INOUT
@@ -440,6 +479,7 @@ public class MobileSignalController extends SignalController<
+ " dataState=" + state.getDataRegState());
}
mServiceState = state;
+ mDataNetType = state.getDataNetworkType();
updateTelephony();
}
@@ -501,6 +541,7 @@ public class MobileSignalController extends SignalController<
boolean airplaneMode;
boolean carrierNetworkChangeMode;
boolean isDefault;
+ boolean showSeparateRoaming;
@Override
public void copyFrom(State s) {
@@ -514,6 +555,7 @@ public class MobileSignalController extends SignalController<
isEmergency = state.isEmergency;
airplaneMode = state.airplaneMode;
carrierNetworkChangeMode = state.carrierNetworkChangeMode;
+ showSeparateRoaming = state.showSeparateRoaming;
}
@Override
@@ -528,6 +570,7 @@ public class MobileSignalController extends SignalController<
builder.append("isEmergency=").append(isEmergency).append(',');
builder.append("airplaneMode=").append(airplaneMode).append(',');
builder.append("carrierNetworkChangeMode=").append(carrierNetworkChangeMode);
+ builder.append("showSeparateRoaming=").append(showSeparateRoaming);
}
@Override
@@ -540,7 +583,8 @@ public class MobileSignalController extends SignalController<
&& ((MobileState) o).isEmergency == isEmergency
&& ((MobileState) o).airplaneMode == airplaneMode
&& ((MobileState) o).carrierNetworkChangeMode == carrierNetworkChangeMode
- && ((MobileState) o).isDefault == isDefault;
+ && ((MobileState) o).isDefault == isDefault
+ && ((MobileState) o).showSeparateRoaming == showSeparateRoaming;
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java
index 38656ee..57b0dba 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java
@@ -40,7 +40,7 @@ public interface NetworkController {
void setMobileDataIndicators(IconState statusIcon, IconState qsIcon, int statusType,
int qsType, boolean activityIn, boolean activityOut, String typeContentDescription,
- String description, boolean isWide, int subId);
+ String description, boolean isWide, boolean showSeparateRoaming, int subId);
void setSubs(List<SubscriptionInfo> subs);
void setNoSims(boolean show);
@@ -82,7 +82,7 @@ public interface NetworkController {
public interface AccessPointCallback {
void onAccessPointsChanged(List<AccessPoint> accessPoints);
- void onSettingsActivityTriggered(Intent settingsIntent);
+ void onSettingsActivityTriggered(Intent intent);
}
}
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 2996808..7e1bf05 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
@@ -245,6 +245,10 @@ public class NetworkControllerImpl extends BroadcastReceiver
mCallbackHandler.setEmergencyCallsOnly(isEmergencyOnly());
}
+ public void removeEmergencyListener(EmergencyListener listener) {
+ mCallbackHandler.setListening(listener, false);
+ }
+
public boolean hasMobileDataFeature() {
return mHasMobileDataFeature;
}
@@ -751,9 +755,11 @@ public class NetworkControllerImpl extends BroadcastReceiver
datatype.equals("1x") ? TelephonyIcons.ONE_X :
datatype.equals("3g") ? TelephonyIcons.THREE_G :
datatype.equals("4g") ? TelephonyIcons.FOUR_G :
+ datatype.equals("4g+") ? TelephonyIcons.FOUR_G_PLUS :
datatype.equals("e") ? TelephonyIcons.E :
datatype.equals("g") ? TelephonyIcons.G :
datatype.equals("h") ? TelephonyIcons.H :
+ datatype.equals("h+") ? TelephonyIcons.HP :
datatype.equals("lte") ? TelephonyIcons.LTE :
datatype.equals("roam") ? TelephonyIcons.ROAMING :
TelephonyIcons.UNKNOWN;
@@ -780,7 +786,7 @@ public class NetworkControllerImpl extends BroadcastReceiver
private SubscriptionInfo addSignalController(int id, int simSlotIndex) {
SubscriptionInfo info = new SubscriptionInfo(id, "", simSlotIndex, "", "", 0, 0, "", 0,
- null, 0, 0, "");
+ null, 0, 0, "", 0);
mMobileSignalControllers.put(id, new MobileSignalController(mContext,
mConfig, mHasMobileDataFeature, mPhone, mCallbackHandler, this, info,
mSubDefaults, mReceiverHandler.getLooper()));
@@ -825,6 +831,7 @@ public class NetworkControllerImpl extends BroadcastReceiver
boolean alwaysShowCdmaRssi = false;
boolean show4gForLte = false;
boolean hspaDataDistinguishable;
+ boolean showRsrpSignalLevelforLTE;
static Config readConfig(Context context) {
Config config = new Config();
@@ -836,6 +843,8 @@ public class NetworkControllerImpl extends BroadcastReceiver
config.show4gForLte = res.getBoolean(R.bool.config_show4GForLTE);
config.hspaDataDistinguishable =
res.getBoolean(R.bool.config_hspa_data_distinguishable);
+ config.showRsrpSignalLevelforLTE =
+ res.getBoolean(R.bool.config_showRsrpSignalLevelforLTE);
return config;
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RotationLockControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RotationLockControllerImpl.java
index c3bcd94..e54df32 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RotationLockControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RotationLockControllerImpl.java
@@ -64,7 +64,7 @@ public final class RotationLockControllerImpl implements RotationLockController
}
public boolean isRotationLockAffordanceVisible() {
- return RotationPolicy.isRotationLockToggleVisible(mContext);
+ return RotationPolicy.isRotationSupported(mContext);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SignalCallbackAdapter.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SignalCallbackAdapter.java
index dce889f..f13ef9f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SignalCallbackAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SignalCallbackAdapter.java
@@ -37,7 +37,7 @@ public class SignalCallbackAdapter implements SignalCallback {
@Override
public void setMobileDataIndicators(IconState statusIcon, IconState qsIcon, int statusType,
int qsType, boolean activityIn, boolean activityOut, String typeContentDescription,
- String description, boolean isWide, int subId) {
+ String description, boolean isWide, boolean showSeparateRoaming, int subId) {
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SignalController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SignalController.java
index 5e9447e..72e3a06 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SignalController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SignalController.java
@@ -256,6 +256,7 @@ public abstract class SignalController<T extends SignalController.State,
IconGroup iconGroup;
int inetCondition;
int rssi; // Only for logging.
+ boolean showSeparateRoaming;
// Not used for comparison, just used for logging.
long time;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SuController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SuController.java
new file mode 100644
index 0000000..de67261
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SuController.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2014 The CyanogenMod 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.policy;
+
+import java.util.List;
+
+public interface SuController {
+ void addCallback(Callback callback);
+ void removeCallback(Callback callback);
+ boolean hasActiveSessions();
+ List<String> getPackageNamesWithActiveSuSessions();
+
+ public interface Callback {
+ void onSuSessionsChanged();
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SuControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SuControllerImpl.java
new file mode 100644
index 0000000..c663bab
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SuControllerImpl.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2014 The CyanogenMod 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.policy;
+
+import android.app.AppOpsManager;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Handler;
+import android.os.UserHandle;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A controller to manage changes to superuser-related states and update the views accordingly.
+ */
+public class SuControllerImpl implements SuController {
+ private static final String TAG = "SuControllerImpl";
+
+ private static final int[] mSuOpArray = new int[] {AppOpsManager.OP_SU};
+
+ private ArrayList<Callback> mCallbacks = new ArrayList<Callback>();
+
+ private AppOpsManager mAppOpsManager;
+
+ private List<String> mActiveSuSessions = new ArrayList<>();
+
+ public SuControllerImpl(Context context) {
+ mAppOpsManager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
+
+ IntentFilter intentFilter = new IntentFilter();
+ intentFilter.addAction(AppOpsManager.ACTION_SU_SESSION_CHANGED);
+ context.registerReceiverAsUser(new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ Log.i(TAG, "Got change");
+ String action = intent.getAction();
+ if (AppOpsManager.ACTION_SU_SESSION_CHANGED.equals(action)) {
+ updateActiveSuSessions();
+ }
+ }
+ }, UserHandle.ALL, intentFilter, null, new Handler());
+
+ updateActiveSuSessions();
+ }
+
+ @Override
+ public void addCallback(Callback callback) {
+ mCallbacks.add(callback);
+ fireCallback(callback);
+ }
+
+ @Override
+ public void removeCallback(Callback callback) {
+ mCallbacks.remove(callback);
+ }
+
+ @Override
+ public boolean hasActiveSessions() {
+ synchronized (mActiveSuSessions) {
+ return mActiveSuSessions.size() > 0;
+ }
+ }
+
+ private void fireCallback(Callback callback) {
+ callback.onSuSessionsChanged();
+ }
+
+ private void fireCallbacks() {
+ for (Callback callback : mCallbacks) {
+ callback.onSuSessionsChanged();
+ }
+ }
+
+ // Return the list of package names that currently have an active su session
+ @Override
+ public List<String> getPackageNamesWithActiveSuSessions() {
+ List<String> packageNames = new ArrayList<>();
+ List<AppOpsManager.PackageOps> packages
+ = mAppOpsManager.getPackagesForOps(mSuOpArray);
+ // AppOpsManager can return null when there is no requested data.
+ if (packages != null) {
+ final int numPackages = packages.size();
+ for (int packageInd = 0; packageInd < numPackages; packageInd++) {
+ AppOpsManager.PackageOps packageOp = packages.get(packageInd);
+ List<AppOpsManager.OpEntry> opEntries = packageOp.getOps();
+ if (opEntries != null) {
+ final int numOps = opEntries.size();
+ for (int opInd = 0; opInd < numOps; opInd++) {
+ AppOpsManager.OpEntry opEntry = opEntries.get(opInd);
+ if (opEntry.getOp() == AppOpsManager.OP_SU) {
+ if (opEntry.isRunning()) {
+ packageNames.add(packageOp.getPackageName());
+ break;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ return packageNames;
+ }
+
+ private synchronized void updateActiveSuSessions() {
+ List<String> newList = getPackageNamesWithActiveSuSessions();
+ synchronized (mActiveSuSessions) {
+ if (!newList.equals(mActiveSuSessions)) {
+ mActiveSuSessions.clear();
+ mActiveSuSessions.addAll(newList);
+ fireCallbacks();
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/TelephonyIcons.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/TelephonyIcons.java
index 83e0446..d770681 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/TelephonyIcons.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/TelephonyIcons.java
@@ -153,6 +153,20 @@ class TelephonyIcons {
static final int QS_DATA_H = R.drawable.ic_qs_signal_h;
+ //HSPA+
+ static final int[][] DATA_HP = {
+ { R.drawable.stat_sys_data_fully_connected_hp,
+ R.drawable.stat_sys_data_fully_connected_hp,
+ R.drawable.stat_sys_data_fully_connected_hp,
+ R.drawable.stat_sys_data_fully_connected_hp },
+ { R.drawable.stat_sys_data_fully_connected_hp,
+ R.drawable.stat_sys_data_fully_connected_hp,
+ R.drawable.stat_sys_data_fully_connected_hp,
+ R.drawable.stat_sys_data_fully_connected_hp }
+ };
+
+ static final int QS_DATA_HP = R.drawable.ic_qs_signal_hp;
+
//CDMA
// Use 3G icons for EVDO data and 1x icons for 1XRTT data
static final int[][] DATA_1X = {
@@ -182,6 +196,19 @@ class TelephonyIcons {
static final int QS_DATA_4G = R.drawable.ic_qs_signal_4g;
+ static final int[][] DATA_4G_PLUS = {
+ { R.drawable.stat_sys_data_fully_connected_4g_plus,
+ R.drawable.stat_sys_data_fully_connected_4g_plus,
+ R.drawable.stat_sys_data_fully_connected_4g_plus,
+ R.drawable.stat_sys_data_fully_connected_4g_plus },
+ { R.drawable.stat_sys_data_fully_connected_4g_plus,
+ R.drawable.stat_sys_data_fully_connected_4g_plus,
+ R.drawable.stat_sys_data_fully_connected_4g_plus,
+ R.drawable.stat_sys_data_fully_connected_4g_plus }
+ };
+
+ static final int QS_DATA_4G_PLUS = R.drawable.ic_qs_signal_4g_plus;
+
// LTE branded "LTE"
static final int[][] DATA_LTE = {
{ R.drawable.stat_sys_data_fully_connected_lte,
@@ -202,8 +229,10 @@ class TelephonyIcons {
static final int ICON_G = R.drawable.stat_sys_data_fully_connected_g;
static final int ICON_E = R.drawable.stat_sys_data_fully_connected_e;
static final int ICON_H = R.drawable.stat_sys_data_fully_connected_h;
+ static final int ICON_HP = R.drawable.stat_sys_data_fully_connected_hp;
static final int ICON_3G = R.drawable.stat_sys_data_fully_connected_3g;
static final int ICON_4G = R.drawable.stat_sys_data_fully_connected_4g;
+ static final int ICON_4G_PLUS = R.drawable.stat_sys_data_fully_connected_4g_plus;
static final int ICON_1X = R.drawable.stat_sys_data_fully_connected_1x;
static final int ICON_CARRIER_NETWORK_CHANGE =
R.drawable.stat_sys_signal_carrier_network_change_animation;
@@ -211,6 +240,7 @@ class TelephonyIcons {
static final int QS_ICON_LTE = R.drawable.ic_qs_signal_lte;
static final int QS_ICON_3G = R.drawable.ic_qs_signal_3g;
static final int QS_ICON_4G = R.drawable.ic_qs_signal_4g;
+ static final int QS_ICON_4G_PLUS = R.drawable.ic_qs_signal_4g_plus;
static final int QS_ICON_1X = R.drawable.ic_qs_signal_1x;
static final int QS_ICON_CARRIER_NETWORK_CHANGE =
R.drawable.ic_qs_signal_carrier_network_change_animation;
@@ -323,12 +353,27 @@ class TelephonyIcons {
TelephonyIcons.TELEPHONY_NO_NETWORK,
TelephonyIcons.QS_TELEPHONY_NO_NETWORK,
AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH[0],
- R.string.accessibility_data_connection_3_5g,
+ R.string.accessibility_data_connection_hspa,
TelephonyIcons.ICON_H,
false,
TelephonyIcons.QS_DATA_H
);
+ static final MobileIconGroup HP = new MobileIconGroup(
+ "HP",
+ TelephonyIcons.TELEPHONY_SIGNAL_STRENGTH,
+ TelephonyIcons.QS_TELEPHONY_SIGNAL_STRENGTH,
+ AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH,
+ 0, 0,
+ TelephonyIcons.TELEPHONY_NO_NETWORK,
+ TelephonyIcons.QS_TELEPHONY_NO_NETWORK,
+ AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH[0],
+ R.string.accessibility_data_connection_hspap,
+ TelephonyIcons.ICON_HP,
+ false,
+ TelephonyIcons.QS_DATA_HP
+ );
+
static final MobileIconGroup FOUR_G = new MobileIconGroup(
"4G",
TelephonyIcons.TELEPHONY_SIGNAL_STRENGTH,
@@ -344,6 +389,21 @@ class TelephonyIcons {
TelephonyIcons.QS_DATA_4G
);
+ static final MobileIconGroup FOUR_G_PLUS = new MobileIconGroup(
+ "4G+",
+ TelephonyIcons.TELEPHONY_SIGNAL_STRENGTH,
+ TelephonyIcons.QS_TELEPHONY_SIGNAL_STRENGTH,
+ AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH,
+ 0,0,
+ TelephonyIcons.TELEPHONY_NO_NETWORK,
+ TelephonyIcons.QS_TELEPHONY_NO_NETWORK,
+ AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH[0],
+ R.string.accessibility_data_connection_4g_plus,
+ TelephonyIcons.ICON_4G_PLUS,
+ true,
+ TelephonyIcons.QS_DATA_4G_PLUS
+ );
+
static final MobileIconGroup LTE = new MobileIconGroup(
"LTE",
TelephonyIcons.TELEPHONY_SIGNAL_STRENGTH,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserInfoController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserInfoController.java
index a8d4f13..58168e8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserInfoController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserInfoController.java
@@ -54,6 +54,7 @@ public final class UserInfoController {
private boolean mUseDefaultAvatar;
private String mUserName;
private Drawable mUserDrawable;
+ private boolean mProfileSetup;
public UserInfoController(Context context) {
mContext = context;
@@ -73,6 +74,10 @@ public final class UserInfoController {
mCallbacks.add(callback);
}
+ public void removeListener (OnUserInfoChangedListener callback) {
+ mCallbacks.remove(callback);
+ }
+
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
@@ -158,24 +163,23 @@ public final class UserInfoController {
mUseDefaultAvatar = true;
}
- // If it's a single-user device, get the profile name, since the nickname is not
- // usually valid
- if (um.getUsers().size() <= 1) {
- // Try and read the display name from the local profile
- final Cursor cursor = context.getContentResolver().query(
- ContactsContract.Profile.CONTENT_URI, new String[] {
- ContactsContract.CommonDataKinds.Phone._ID,
- ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME
- }, null, null, null);
- if (cursor != null) {
- try {
- if (cursor.moveToFirst()) {
- name = cursor.getString(cursor.getColumnIndex(
- ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME));
- }
- } finally {
- cursor.close();
+ mProfileSetup = false;
+
+ // Try and read the display name from the local profile
+ final Cursor cursor = context.getContentResolver().query(
+ ContactsContract.Profile.CONTENT_URI, new String[] {
+ ContactsContract.CommonDataKinds.Phone._ID,
+ ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME
+ }, null, null, null);
+ if (cursor != null) {
+ try {
+ if (cursor.moveToFirst()) {
+ mProfileSetup = true;
+ name = cursor.getString(cursor.getColumnIndex(
+ ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME));
}
+ } finally {
+ cursor.close();
}
}
return new Pair<String, Drawable>(name, avatar);
@@ -198,6 +202,10 @@ public final class UserInfoController {
}
}
+ public boolean isProfileSetup() {
+ return mProfileSetup;
+ }
+
public interface OnUserInfoChangedListener {
public void onUserInfoChanged(String name, Drawable picture);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
index 3e8d4e9..38fb275 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
@@ -53,6 +53,7 @@ import com.android.systemui.R;
import com.android.systemui.qs.QSTile;
import com.android.systemui.qs.tiles.UserDetailView;
import com.android.systemui.statusbar.phone.SystemUIDialog;
+import cyanogenmod.app.StatusBarPanelCustomTile;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -587,6 +588,11 @@ public class UserSwitcherController {
}
@Override
+ public StatusBarPanelCustomTile getCustomTile() {
+ return null;
+ }
+
+ @Override
public Boolean getToggleState() {
return null;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/WeatherController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/WeatherController.java
new file mode 100644
index 0000000..0f71bcc
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/WeatherController.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2014 The CyanogenMod 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.policy;
+
+public interface WeatherController {
+ void addCallback(Callback callback);
+ void removeCallback(Callback callback);
+ WeatherInfo getWeatherInfo();
+
+ public interface Callback {
+ void onWeatherChanged(WeatherInfo temp);
+ }
+ public static class WeatherInfo {
+ public double temp = Double.NaN;
+ public String city = null;
+ public String condition = null;
+ public int tempUnit;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/WeatherControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/WeatherControllerImpl.java
new file mode 100644
index 0000000..1a798f0
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/WeatherControllerImpl.java
@@ -0,0 +1,184 @@
+/*
+ * Copyright (C) 2014 The CyanogenMod 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.policy;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.database.ContentObserver;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Handler;
+import android.util.Log;
+import cyanogenmod.providers.CMSettings;
+import cyanogenmod.providers.WeatherContract;
+import cyanogenmod.weather.CMWeatherManager;
+import cyanogenmod.weather.util.WeatherUtils;
+
+import java.util.ArrayList;
+
+import static cyanogenmod.providers.WeatherContract.WeatherColumns.CURRENT_CITY;
+import static cyanogenmod.providers.WeatherContract.WeatherColumns.CURRENT_CONDITION;
+import static cyanogenmod.providers.WeatherContract.WeatherColumns.CURRENT_TEMPERATURE;
+import static cyanogenmod.providers.WeatherContract.WeatherColumns.CURRENT_TEMPERATURE_UNIT;
+import static cyanogenmod.providers.WeatherContract.WeatherColumns.TempUnit.CELSIUS;
+import static cyanogenmod.providers.WeatherContract.WeatherColumns.TempUnit.FAHRENHEIT;
+
+public class WeatherControllerImpl implements WeatherController {
+
+ private static final String TAG = WeatherController.class.getSimpleName();
+ private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+ private WeatherContentObserver mWeatherContentObserver;
+ private Handler mHandler;
+ private int mWeatherUnit;
+ private Uri mWeatherTempetarureUri;
+
+ public static final ComponentName COMPONENT_WEATHER_FORECAST = new ComponentName(
+ "com.cyanogenmod.lockclock", "com.cyanogenmod.lockclock.weather.ForecastActivity");
+ public static final String ACTION_FORCE_WEATHER_UPDATE
+ = "com.cyanogenmod.lockclock.action.FORCE_WEATHER_UPDATE";
+ private static final String[] WEATHER_PROJECTION = new String[]{
+ CURRENT_TEMPERATURE,
+ CURRENT_TEMPERATURE_UNIT,
+ CURRENT_CITY,
+ CURRENT_CONDITION
+ };
+
+ private final ArrayList<Callback> mCallbacks = new ArrayList<Callback>();
+ private final Context mContext;
+
+ private WeatherInfo mCachedInfo = new WeatherInfo();
+
+ public WeatherControllerImpl(Context context) {
+ mContext = context;
+ mContext.getSystemService(Context.CONNECTIVITY_SERVICE);
+ mHandler = new Handler();
+ mWeatherContentObserver = new WeatherContentObserver(mHandler);
+ mWeatherTempetarureUri
+ = CMSettings.Global.getUriFor(CMSettings.Global.WEATHER_TEMPERATURE_UNIT);
+ mContext.getContentResolver().registerContentObserver(
+ WeatherContract.WeatherColumns.CURRENT_WEATHER_URI,true, mWeatherContentObserver);
+ mContext.getContentResolver().registerContentObserver(mWeatherTempetarureUri, true,
+ mWeatherContentObserver);
+ queryWeatherTempUnit();
+ queryWeather();
+ }
+
+ public void addCallback(Callback callback) {
+ if (callback == null || mCallbacks.contains(callback)) return;
+ if (DEBUG) Log.d(TAG, "addCallback " + callback);
+ mCallbacks.add(callback);
+ callback.onWeatherChanged(mCachedInfo); // immediately update with current values
+ }
+
+ public void removeCallback(Callback callback) {
+ if (callback == null) return;
+ if (DEBUG) Log.d(TAG, "removeCallback " + callback);
+ mCallbacks.remove(callback);
+ }
+
+ @Override
+ public WeatherInfo getWeatherInfo() {
+ return mCachedInfo;
+ }
+
+ private void queryWeather() {
+ Cursor c = mContext.getContentResolver().query(
+ WeatherContract.WeatherColumns.CURRENT_WEATHER_URI, WEATHER_PROJECTION,
+ null, null, null);
+ if (c == null) {
+ if(DEBUG) Log.e(TAG, "cursor was null for temperature, forcing weather update");
+ //LockClock keeps track of the user settings (temp unit, search by geo location/city)
+ //so we delegate the responsibility of handling a weather update to LockClock
+ mContext.sendBroadcast(new Intent(ACTION_FORCE_WEATHER_UPDATE));
+ } else {
+ try {
+ c.moveToFirst();
+ double temp = c.getDouble(0);
+ int reportedUnit = c.getInt(1);
+ if (reportedUnit == CELSIUS && mWeatherUnit == FAHRENHEIT) {
+ temp = WeatherUtils.celsiusToFahrenheit(temp);
+ } else if (reportedUnit == FAHRENHEIT && mWeatherUnit == CELSIUS) {
+ temp = WeatherUtils.fahrenheitToCelsius(temp);
+ }
+
+ mCachedInfo.temp = temp;
+ mCachedInfo.tempUnit = mWeatherUnit;
+ mCachedInfo.city = c.getString(2);
+ mCachedInfo.condition = c.getString(3);
+ } finally {
+ c.close();
+ }
+ }
+ }
+
+ private void fireCallback() {
+ for (Callback callback : mCallbacks) {
+ callback.onWeatherChanged(mCachedInfo);
+ }
+ }
+
+ private class WeatherContentObserver extends ContentObserver {
+
+ public WeatherContentObserver(Handler handler) {
+ super(handler);
+ }
+
+ @Override
+ public void onChange(boolean selfChange, Uri uri) {
+ if (uri != null) {
+ if (uri.compareTo(WeatherContract.WeatherColumns.CURRENT_WEATHER_URI) == 0) {
+ queryWeather();
+ fireCallback();
+ } else if (uri.compareTo(mWeatherTempetarureUri) == 0) {
+ queryWeatherTempUnit();
+ fixCachedWeatherInfo();
+ fireCallback();
+ } else {
+ super.onChange(selfChange, uri);
+ }
+ }
+ }
+
+ @Override
+ public void onChange(boolean selfChange) {
+ onChange(selfChange, null);
+ }
+ }
+
+ private void queryWeatherTempUnit() {
+ try {
+ mWeatherUnit = CMSettings.Global.getInt(mContext.getContentResolver(),
+ CMSettings.Global.WEATHER_TEMPERATURE_UNIT);
+ } catch (CMSettings.CMSettingNotFoundException e) {
+ //CMSettingsProvider should have taken care of setting a default value for this setting
+ //so how is that we ended up here?? We need to set a valid temp unit anyway to keep
+ //this going
+ mWeatherUnit = WeatherContract.WeatherColumns.TempUnit.CELSIUS;
+ }
+ }
+
+ private void fixCachedWeatherInfo() {
+ if (mCachedInfo.tempUnit == CELSIUS && mWeatherUnit == FAHRENHEIT) {
+ mCachedInfo.temp = WeatherUtils.celsiusToFahrenheit(mCachedInfo.temp);
+ mCachedInfo.tempUnit = FAHRENHEIT;
+ } else if (mCachedInfo.tempUnit == FAHRENHEIT && mWeatherUnit == CELSIUS) {
+ mCachedInfo.temp = WeatherUtils.fahrenheitToCelsius(mCachedInfo.temp);
+ mCachedInfo.tempUnit = CELSIUS;
+ }
+ }
+}
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 af38f5f..b6e131a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
@@ -50,9 +50,12 @@ import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.phone.NotificationGroupManager;
import com.android.systemui.statusbar.phone.PhoneStatusBar;
import com.android.systemui.statusbar.phone.ScrimController;
+import com.android.systemui.statusbar.phone.ViewLinker;
import com.android.systemui.statusbar.policy.HeadsUpManager;
import com.android.systemui.statusbar.policy.ScrollAdapter;
+import cyanogenmod.power.PerformanceManager;
+
import java.util.ArrayList;
import java.util.HashSet;
@@ -61,7 +64,8 @@ import java.util.HashSet;
*/
public class NotificationStackScrollLayout extends ViewGroup
implements SwipeHelper.Callback, ExpandHelper.Callback, ScrollAdapter,
- ExpandableView.OnHeightChangedListener, NotificationGroupManager.OnGroupChangeListener {
+ ExpandableView.OnHeightChangedListener, NotificationGroupManager.OnGroupChangeListener,
+ ViewLinker.ViewLinkerParent {
private static final String TAG = "NotificationStackScrollLayout";
private static final boolean DEBUG = false;
@@ -208,6 +212,7 @@ public class NotificationStackScrollLayout extends ViewGroup
private boolean mDelegateToScrollView;
private boolean mDisallowScrollingInThisMotion;
private long mGoToFullShadeDelay;
+
private ViewTreeObserver.OnPreDrawListener mChildrenUpdater
= new ViewTreeObserver.OnPreDrawListener() {
@Override
@@ -231,6 +236,7 @@ public class NotificationStackScrollLayout extends ViewGroup
private boolean mForceNoOverlappingRendering;
private NotificationOverflowContainer mOverflowContainer;
private final ArrayList<Pair<ExpandableNotificationRow, Boolean>> mTmpList = new ArrayList<>();
+ private ViewLinker.ViewLinkerCallback mLinkerCallback;
public NotificationStackScrollLayout(Context context) {
this(context, null);
@@ -839,6 +845,7 @@ public class NotificationStackScrollLayout extends ViewGroup
&& !mOnlyScrollingInThisMotion) {
horizontalSwipeWantsIt = mSwipeHelper.onTouchEvent(ev);
}
+
return horizontalSwipeWantsIt || scrollerWantsIt || expandWantsIt || super.onTouchEvent(ev);
}
@@ -2875,6 +2882,23 @@ public class NotificationStackScrollLayout extends ViewGroup
return !mForceNoOverlappingRendering && super.hasOverlappingRendering();
}
+ @Override
+ public void registerLinker(ViewLinker.ViewLinkerCallback callback) {
+ mLinkerCallback = callback;
+ }
+
+ @Override
+ public void setAlpha(float alpha) {
+ super.setAlpha(alpha);
+ mLinkerCallback.onAlphaChanged(alpha);
+ }
+
+ @Override
+ public void setTranslationX(float translationX) {
+ super.setTranslationX(translationX);
+ mLinkerCallback.onTranslationXChanged(translationX);
+ }
+
/**
* A listener that is notified when some child locations might have changed.
*/
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 cf696a1..93995f8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java
@@ -42,6 +42,8 @@ public class StackScrollAlgorithm {
private static final int MAX_ITEMS_IN_BOTTOM_STACK = 3;
private static final int MAX_ITEMS_IN_TOP_STACK = 3;
+ private static final int DEFAULT_CORNER_RADIUS = 2;
+
public static final float DIMMED_SCALE = 0.95f;
private int mPaddingBetweenElements;
@@ -72,6 +74,7 @@ public class StackScrollAlgorithm {
private int mMaxNotificationHeight;
private boolean mScaleDimmed;
private HeadsUpManager mHeadsUpManager;
+ private boolean mPerformClipping;
public StackScrollAlgorithm(Context context) {
initConstants(context);
@@ -128,6 +131,12 @@ public class StackScrollAlgorithm {
R.dimen.notification_collapse_second_card_padding);
mScaleDimmed = context.getResources().getDisplayMetrics().densityDpi
>= DisplayMetrics.DENSITY_420;
+
+ // We don't want to clip the notification if a theme overrides the corner radius with
+ // a value larger than the default.
+ mPerformClipping = context.getResources()
+ .getDimension(R.dimen.notification_material_rounded_rect_radius) <=
+ DEFAULT_CORNER_RADIUS * context.getResources().getDisplayMetrics().density;
}
public boolean shouldScaleDimmed() {
@@ -228,8 +237,7 @@ public class StackScrollAlgorithm {
// In the unlocked shade we have to clip a little bit higher because of the rounded
// corners of the notifications, but only if we are not fully overlapped by
// the top card.
- float clippingCorrection = state.dimmed
- ? 0
+ float clippingCorrection = state.dimmed ? (mPerformClipping ? 0 : newHeight)
: mRoundedRectCornerRadius * state.scale;
clipHeight += clippingCorrection;
}
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/DemoModeFragment.java b/packages/SystemUI/src/com/android/systemui/tuner/DemoModeFragment.java
index a2b062c..0ae34bf 100644
--- a/packages/SystemUI/src/com/android/systemui/tuner/DemoModeFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/tuner/DemoModeFragment.java
@@ -155,7 +155,7 @@ public class DemoModeFragment extends PreferenceFragment implements OnPreference
getContext().sendBroadcast(intent);
intent.putExtra(DemoMode.EXTRA_COMMAND, DemoMode.COMMAND_CLOCK);
- intent.putExtra("hhmm", "0600");
+ intent.putExtra("hhmm", "1230");
getContext().sendBroadcast(intent);
intent.putExtra(DemoMode.EXTRA_COMMAND, DemoMode.COMMAND_NETWORK);
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/QsTuner.java b/packages/SystemUI/src/com/android/systemui/tuner/QsTuner.java
index 37ac098..53fbef7 100644
--- a/packages/SystemUI/src/com/android/systemui/tuner/QsTuner.java
+++ b/packages/SystemUI/src/com/android/systemui/tuner/QsTuner.java
@@ -15,14 +15,23 @@
*/
package com.android.systemui.tuner;
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
import android.app.ActivityManager;
import android.app.AlertDialog;
import android.app.Fragment;
import android.content.ClipData;
+import android.content.ClipDescription;
import android.content.Context;
import android.content.DialogInterface;
+import android.graphics.Canvas;
+import android.graphics.Point;
+import android.graphics.PointF;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.provider.Settings.Secure;
+import android.support.v4.view.ViewPager;
import android.text.TextUtils;
import android.util.Log;
import android.view.DragEvent;
@@ -30,11 +39,9 @@ import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
-import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.View.OnDragListener;
-import android.view.View.OnTouchListener;
import android.view.ViewGroup;
import android.widget.EditText;
import android.widget.FrameLayout;
@@ -42,6 +49,8 @@ import android.widget.ScrollView;
import com.android.internal.logging.MetricsLogger;
import com.android.systemui.R;
+import com.android.systemui.qs.QSDragPanel;
+import com.android.systemui.qs.QSPage;
import com.android.systemui.qs.QSPanel;
import com.android.systemui.qs.QSTile;
import com.android.systemui.qs.QSTile.Host.Callback;
@@ -52,6 +61,7 @@ import com.android.systemui.statusbar.phone.QSTileHost;
import com.android.systemui.statusbar.policy.SecurityController;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.List;
public class QsTuner extends Fragment implements Callback {
@@ -59,16 +69,13 @@ public class QsTuner extends Fragment implements Callback {
private static final String TAG = "QsTuner";
private static final int MENU_RESET = Menu.FIRST;
+ private static final int MENU_EDIT = Menu.FIRST + 1;
private DraggableQsPanel mQsPanel;
private CustomHost mTileHost;
- private FrameLayout mDropTarget;
-
private ScrollView mScrollRoot;
- private FrameLayout mAddTarget;
-
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -78,6 +85,7 @@ public class QsTuner extends Fragment implements Callback {
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
menu.add(0, MENU_RESET, 0, com.android.internal.R.string.reset);
+ menu.add(0, MENU_EDIT, 0, "toggle edit");
}
public void onResume() {
@@ -93,8 +101,11 @@ public class QsTuner extends Fragment implements Callback {
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
+ case MENU_EDIT:
+ mQsPanel.setEditing(!mQsPanel.isEditing());
+ break;
case MENU_RESET:
- mTileHost.reset();
+ mTileHost.resetTiles();
break;
case android.R.id.home:
getFragmentManager().popBackStack();
@@ -105,7 +116,7 @@ public class QsTuner extends Fragment implements Callback {
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
- Bundle savedInstanceState) {
+ Bundle savedInstanceState) {
mScrollRoot = (ScrollView) inflater.inflate(R.layout.tuner_qs, container, false);
mQsPanel = new DraggableQsPanel(getContext());
@@ -116,10 +127,6 @@ public class QsTuner extends Fragment implements Callback {
mQsPanel.refreshAllTiles();
((ViewGroup) mScrollRoot.findViewById(R.id.all_details)).addView(mQsPanel, 0);
- mDropTarget = (FrameLayout) mScrollRoot.findViewById(R.id.remove_target);
- setupDropTarget();
- mAddTarget = (FrameLayout) mScrollRoot.findViewById(R.id.add_target);
- setupAddTarget();
return mScrollRoot;
}
@@ -129,89 +136,38 @@ public class QsTuner extends Fragment implements Callback {
super.onDestroyView();
}
- private void setupDropTarget() {
- QSTileView tileView = new QSTileView(getContext());
- QSTile.State state = new QSTile.State();
- state.visible = true;
- state.icon = ResourceIcon.get(R.drawable.ic_delete);
- state.label = getString(com.android.internal.R.string.delete);
- tileView.onStateChanged(state);
- mDropTarget.addView(tileView);
- mDropTarget.setVisibility(View.GONE);
- new DragHelper(tileView, new DropListener() {
- @Override
- public void onDrop(String sourceText) {
- mTileHost.remove(sourceText);
- }
- });
- }
-
- private void setupAddTarget() {
- QSTileView tileView = new QSTileView(getContext());
- QSTile.State state = new QSTile.State();
- state.visible = true;
- state.icon = ResourceIcon.get(R.drawable.ic_add_circle_qs);
- state.label = getString(R.string.add_tile);
- tileView.onStateChanged(state);
- mAddTarget.addView(tileView);
- tileView.setClickable(true);
- tileView.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View v) {
- mTileHost.showAddDialog();
- }
- });
+ @Override
+ public void onTilesChanged() {
+ mQsPanel.setTiles(mTileHost.getTiles());
}
- public void onStartDrag() {
- mDropTarget.post(new Runnable() {
- @Override
- public void run() {
- mDropTarget.setVisibility(View.VISIBLE);
- mAddTarget.setVisibility(View.GONE);
- }
- });
+ @Override
+ public void setEditing(boolean editing) {
+ mQsPanel.setEditing(editing);
}
- public void stopDrag() {
- mDropTarget.post(new Runnable() {
- @Override
- public void run() {
- mDropTarget.setVisibility(View.GONE);
- mAddTarget.setVisibility(View.VISIBLE);
- }
- });
+ @Override
+ public boolean isEditing() {
+ return mTileHost.isEditing();
}
@Override
- public void onTilesChanged() {
- mQsPanel.setTiles(mTileHost.getTiles());
+ public void goToSettingsPage() {
}
- private static int getLabelResource(String spec) {
- if (spec.equals("wifi")) return R.string.quick_settings_wifi_label;
- else if (spec.equals("bt")) return R.string.quick_settings_bluetooth_label;
- else if (spec.equals("inversion")) return R.string.quick_settings_inversion_label;
- else if (spec.equals("cell")) return R.string.quick_settings_cellular_detail_title;
- else if (spec.equals("airplane")) return R.string.airplane_mode;
- else if (spec.equals("dnd")) return R.string.quick_settings_dnd_label;
- else if (spec.equals("rotation")) return R.string.quick_settings_rotation_locked_label;
- else if (spec.equals("flashlight")) return R.string.quick_settings_flashlight_label;
- else if (spec.equals("location")) return R.string.quick_settings_location_label;
- else if (spec.equals("cast")) return R.string.quick_settings_cast_title;
- else if (spec.equals("hotspot")) return R.string.quick_settings_hotspot_label;
- return 0;
+ @Override
+ public void resetTiles() {
}
private static class CustomHost extends QSTileHost {
public CustomHost(Context context) {
super(context, null, null, null, null, null, null, null, null, null,
- null, null, new BlankSecurityController());
+ null, null, new BlankSecurityController(), null);
}
@Override
- protected QSTile<?> createTile(String tileSpec) {
+ public QSTile<?> createTile(String tileSpec) {
return new DraggableTile(this, tileSpec);
}
@@ -232,97 +188,6 @@ public class QsTuner extends Fragment implements Callback {
setTiles(order);
}
- public void remove(String tile) {
- MetricsLogger.action(getContext(), MetricsLogger.TUNER_QS_REMOVE, tile);
- List<String> tiles = new ArrayList<>(mTileSpecs);
- tiles.remove(tile);
- setTiles(tiles);
- }
-
- public void add(String tile) {
- MetricsLogger.action(getContext(), MetricsLogger.TUNER_QS_ADD, tile);
- List<String> tiles = new ArrayList<>(mTileSpecs);
- tiles.add(tile);
- setTiles(tiles);
- }
-
- public void reset() {
- Secure.putStringForUser(getContext().getContentResolver(),
- TILES_SETTING, "default", ActivityManager.getCurrentUser());
- }
-
- private void setTiles(List<String> tiles) {
- Secure.putStringForUser(getContext().getContentResolver(), TILES_SETTING,
- TextUtils.join(",", tiles), ActivityManager.getCurrentUser());
- }
-
- public void showAddDialog() {
- List<String> tiles = mTileSpecs;
- int numBroadcast = 0;
- for (int i = 0; i < tiles.size(); i++) {
- if (tiles.get(i).startsWith(IntentTile.PREFIX)) {
- numBroadcast++;
- }
- }
- String[] defaults =
- getContext().getString(R.string.quick_settings_tiles_default).split(",");
- final String[] available = new String[defaults.length + 1
- - (tiles.size() - numBroadcast)];
- final String[] availableTiles = new String[available.length];
- int index = 0;
- for (int i = 0; i < defaults.length; i++) {
- if (tiles.contains(defaults[i])) {
- continue;
- }
- int resource = getLabelResource(defaults[i]);
- if (resource != 0) {
- availableTiles[index] = defaults[i];
- available[index++] = getContext().getString(resource);
- } else {
- availableTiles[index] = defaults[i];
- available[index++] = defaults[i];
- }
- }
- available[index++] = getContext().getString(R.string.broadcast_tile);
- new AlertDialog.Builder(getContext())
- .setTitle(R.string.add_tile)
- .setItems(available, new DialogInterface.OnClickListener() {
- public void onClick(DialogInterface dialog, int which) {
- if (which < available.length - 1) {
- add(availableTiles[which]);
- } else {
- showBroadcastTileDialog();
- }
- }
- }).show();
- }
-
- public void showBroadcastTileDialog() {
- final EditText editText = new EditText(getContext());
- new AlertDialog.Builder(getContext())
- .setTitle(R.string.broadcast_tile)
- .setView(editText)
- .setNegativeButton(android.R.string.cancel, null)
- .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
- public void onClick(DialogInterface dialog, int which) {
- String action = editText.getText().toString();
- if (isValid(action)) {
- add(IntentTile.PREFIX + action + ')');
- }
- }
- }).show();
- }
-
- private boolean isValid(String action) {
- for (int i = 0; i < action.length(); i++) {
- char c = action.charAt(i);
- if (!Character.isAlphabetic(c) && !Character.isDigit(c) && c != '.') {
- return false;
- }
- }
- return true;
- }
-
private static class BlankSecurityController implements SecurityController {
@Override
public boolean hasDeviceOwner() {
@@ -373,8 +238,7 @@ public class QsTuner extends Fragment implements Callback {
}
}
- private static class DraggableTile extends QSTile<QSTile.State>
- implements DropListener {
+ public static class DraggableTile extends QSTile<QSTile.State> {
private String mSpec;
private QSTileView mView;
@@ -391,7 +255,7 @@ public class QsTuner extends Fragment implements Callback {
}
@Override
- public boolean supportsDualTargets() {
+ public boolean hasDualTargetsDetails() {
return "wifi".equals(mSpec) || "bt".equals(mSpec);
}
@@ -416,7 +280,7 @@ public class QsTuner extends Fragment implements Callback {
}
private String getLabel() {
- int resource = getLabelResource(mSpec);
+ int resource = QSTileHost.getLabelResource(mSpec);
if (resource != 0) {
return mContext.getString(resource);
}
@@ -443,6 +307,7 @@ public class QsTuner extends Fragment implements Callback {
else if (mSpec.equals("location")) return R.drawable.ic_signal_location_enable;
else if (mSpec.equals("cast")) return R.drawable.ic_qs_cast_on;
else if (mSpec.equals("hotspot")) return R.drawable.ic_hotspot_enable;
+ else if (mSpec.equals("adb_network")) return R.drawable.ic_qs_network_adb_on;
return R.drawable.android;
}
@@ -460,81 +325,19 @@ public class QsTuner extends Fragment implements Callback {
}
@Override
- public void onDrop(String sourceText) {
- ((CustomHost) mHost).replace(mSpec, sourceText);
- }
-
- }
-
- private class DragHelper implements OnDragListener {
-
- private final View mView;
- private final DropListener mListener;
-
- public DragHelper(View view, DropListener dropListener) {
- mView = view;
- mListener = dropListener;
- mView.setOnDragListener(this);
- }
-
- @Override
- public boolean onDrag(View v, DragEvent event) {
- switch (event.getAction()) {
- case DragEvent.ACTION_DRAG_ENTERED:
- mView.setBackgroundColor(0x77ffffff);
- break;
- case DragEvent.ACTION_DRAG_ENDED:
- stopDrag();
- case DragEvent.ACTION_DRAG_EXITED:
- mView.setBackgroundColor(0x0);
- break;
- case DragEvent.ACTION_DROP:
- stopDrag();
- String text = event.getClipData().getItemAt(0).getText().toString();
- mListener.onDrop(text);
- break;
- }
- return true;
+ public String toString() {
+ return mSpec;
}
-
}
- public interface DropListener {
- void onDrop(String sourceText);
- }
+ private class DraggableQsPanel extends QSDragPanel {
- private class DraggableQsPanel extends QSPanel implements OnTouchListener {
public DraggableQsPanel(Context context) {
super(context);
- mBrightnessView.setVisibility(View.GONE);
- }
- @Override
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- super.onMeasure(widthMeasureSpec, heightMeasureSpec);
- for (TileRecord r : mRecords) {
- new DragHelper(r.tileView, (DraggableTile) r.tile);
- r.tileView.setTag(r.tile);
- r.tileView.setOnTouchListener(this);
-
- for (int i = 0; i < r.tileView.getChildCount(); i++) {
- r.tileView.getChildAt(i).setClickable(false);
- }
- }
+ setEditing(true);
}
- @Override
- public boolean onTouch(View v, MotionEvent event) {
- switch (event.getAction()) {
- case MotionEvent.ACTION_DOWN:
- String tileSpec = (String) ((DraggableTile) v.getTag()).mSpec;
- ClipData data = ClipData.newPlainText(tileSpec, tileSpec);
- v.startDrag(data, new View.DragShadowBuilder(v), null, 0);
- onStartDrag();
- return true;
- }
- return false;
- }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/StatusBarIconBlacklistFragment.java b/packages/SystemUI/src/com/android/systemui/tuner/StatusBarIconBlacklistFragment.java
new file mode 100644
index 0000000..c339541
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/tuner/StatusBarIconBlacklistFragment.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2016 The CyanogenMod 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.tuner;
+
+import android.annotation.Nullable;
+import android.os.Bundle;
+import android.preference.Preference;
+import android.preference.PreferenceFragment;
+
+import android.preference.PreferenceGroup;
+import com.android.systemui.R;
+import com.android.systemui.statusbar.phone.StatusBarIconController;
+
+public class StatusBarIconBlacklistFragment extends PreferenceFragment {
+ @Override
+ public void onCreate(@Nullable Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ addPreferencesFromResource(R.xml.tuner_statusbar_icons);
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ registerPrefs(getPreferenceScreen());
+ }
+
+ @Override
+ public void onPause() {
+ super.onPause();
+ unregisterPrefs(getPreferenceScreen());
+ }
+
+ private void registerPrefs(PreferenceGroup group) {
+ TunerService tunerService = TunerService.get(getContext());
+ final int N = group.getPreferenceCount();
+ for (int i = 0; i < N; i++) {
+ Preference pref = group.getPreference(i);
+ if (pref instanceof StatusBarSwitch) {
+ tunerService.addTunable((TunerService.Tunable) pref, StatusBarIconController.ICON_BLACKLIST);
+ } else if (pref instanceof PreferenceGroup) {
+ registerPrefs((PreferenceGroup) pref);
+ }
+ }
+ }
+
+ private void unregisterPrefs(PreferenceGroup group) {
+ TunerService tunerService = TunerService.get(getContext());
+ final int N = group.getPreferenceCount();
+ for (int i = 0; i < N; i++) {
+ Preference pref = group.getPreference(i);
+ if (pref instanceof TunerService.Tunable) {
+ tunerService.removeTunable((TunerService.Tunable) pref);
+ } else if (pref instanceof PreferenceGroup) {
+ registerPrefs((PreferenceGroup) pref);
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/TunerActivity.java b/packages/SystemUI/src/com/android/systemui/tuner/TunerActivity.java
index c84f618..401fb0e 100644
--- a/packages/SystemUI/src/com/android/systemui/tuner/TunerActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/tuner/TunerActivity.java
@@ -16,7 +16,9 @@
package com.android.systemui.tuner;
import android.app.Activity;
+import android.app.Fragment;
import android.os.Bundle;
+import android.view.MenuItem;
public class TunerActivity extends Activity {
@@ -27,4 +29,43 @@ public class TunerActivity extends Activity {
.commit();
}
+ /**
+ * Base class for direct entry points into
+ * tuner fragments
+ */
+ private static abstract class FragmentTunerActivityBase extends Activity {
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ getActionBar().setDisplayHomeAsUpEnabled(true);
+ getFragmentManager().beginTransaction().replace(android.R.id.content,
+ getFragment()).commit();
+ }
+
+ protected abstract Fragment getFragment();
+
+ @Override
+ public final boolean onOptionsItemSelected(MenuItem item) {
+ switch (item.getItemId()) {
+ case android.R.id.home:
+ finish();
+ return true;
+ }
+ return super.onOptionsItemSelected(item);
+ }
+ }
+
+ public static final class DemoModeActivity extends FragmentTunerActivityBase {
+ @Override
+ protected Fragment getFragment() {
+ return new DemoModeFragment();
+ }
+ }
+
+ public static final class StatusBarIconActivity extends FragmentTunerActivityBase {
+ @Override
+ protected Fragment getFragment() {
+ return new StatusBarIconBlacklistFragment();
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/TunerFragment.java b/packages/SystemUI/src/com/android/systemui/tuner/TunerFragment.java
index 71b5de5..0bc663b 100644
--- a/packages/SystemUI/src/com/android/systemui/tuner/TunerFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/tuner/TunerFragment.java
@@ -15,8 +15,6 @@
*/
package com.android.systemui.tuner;
-import static com.android.systemui.BatteryMeterView.SHOW_PERCENT_SETTING;
-
import android.app.AlertDialog;
import android.app.FragmentTransaction;
import android.content.DialogInterface;
@@ -46,18 +44,13 @@ public class TunerFragment extends PreferenceFragment {
private static final String TAG = "TunerFragment";
- private static final String KEY_QS_TUNER = "qs_tuner";
+ private static final String KEY_STATUSBAR_BLACKLIST = "statusbar_icon_blacklist";
private static final String KEY_DEMO_MODE = "demo_mode";
- private static final String KEY_BATTERY_PCT = "battery_pct";
public static final String SETTING_SEEN_TUNER_WARNING = "seen_tuner_warning";
private static final int MENU_REMOVE = Menu.FIRST + 1;
- private final SettingObserver mSettingObserver = new SettingObserver();
-
- private SwitchPreference mBatteryPct;
-
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -65,11 +58,12 @@ public class TunerFragment extends PreferenceFragment {
getActivity().getActionBar().setDisplayHomeAsUpEnabled(true);
setHasOptionsMenu(true);
- findPreference(KEY_QS_TUNER).setOnPreferenceClickListener(new OnPreferenceClickListener() {
+ findPreference(KEY_STATUSBAR_BLACKLIST).setOnPreferenceClickListener(new OnPreferenceClickListener() {
@Override
public boolean onPreferenceClick(Preference preference) {
FragmentTransaction ft = getFragmentManager().beginTransaction();
- ft.replace(android.R.id.content, new QsTuner(), "QsTuner");
+ ft.replace(android.R.id.content, new StatusBarIconBlacklistFragment(),
+ "StatusBarBlacklist");
ft.addToBackStack(null);
ft.commit();
return true;
@@ -85,7 +79,6 @@ public class TunerFragment extends PreferenceFragment {
return true;
}
});
- mBatteryPct = (SwitchPreference) findPreference(KEY_BATTERY_PCT);
if (Settings.Secure.getInt(getContext().getContentResolver(), SETTING_SEEN_TUNER_WARNING,
0) == 0) {
new AlertDialog.Builder(getContext())
@@ -104,9 +97,6 @@ public class TunerFragment extends PreferenceFragment {
@Override
public void onResume() {
super.onResume();
- updateBatteryPct();
- getContext().getContentResolver().registerContentObserver(
- System.getUriFor(SHOW_PERCENT_SETTING), false, mSettingObserver);
registerPrefs(getPreferenceScreen());
MetricsLogger.visibility(getContext(), MetricsLogger.TUNER, true);
@@ -115,7 +105,6 @@ public class TunerFragment extends PreferenceFragment {
@Override
public void onPause() {
super.onPause();
- getContext().getContentResolver().unregisterContentObserver(mSettingObserver);
unregisterPrefs(getPreferenceScreen());
MetricsLogger.visibility(getContext(), MetricsLogger.TUNER, false);
@@ -169,33 +158,4 @@ public class TunerFragment extends PreferenceFragment {
}
return super.onOptionsItemSelected(item);
}
-
- private void updateBatteryPct() {
- mBatteryPct.setOnPreferenceChangeListener(null);
- mBatteryPct.setChecked(System.getInt(getContext().getContentResolver(),
- SHOW_PERCENT_SETTING, 0) != 0);
- mBatteryPct.setOnPreferenceChangeListener(mBatteryPctChange);
- }
-
- private final class SettingObserver extends ContentObserver {
- public SettingObserver() {
- super(new Handler());
- }
-
- @Override
- public void onChange(boolean selfChange, Uri uri, int userId) {
- super.onChange(selfChange, uri, userId);
- updateBatteryPct();
- }
- }
-
- private final OnPreferenceChangeListener mBatteryPctChange = new OnPreferenceChangeListener() {
- @Override
- public boolean onPreferenceChange(Preference preference, Object newValue) {
- final boolean v = (Boolean) newValue;
- MetricsLogger.action(getContext(), MetricsLogger.TUNER_BATTERY_PERCENTAGE, v);
- System.putInt(getContext().getContentResolver(), SHOW_PERCENT_SETTING, v ? 1 : 0);
- return true;
- }
- };
}
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/TunerService.java b/packages/SystemUI/src/com/android/systemui/tuner/TunerService.java
index 50234b2..3e7477c 100644
--- a/packages/SystemUI/src/com/android/systemui/tuner/TunerService.java
+++ b/packages/SystemUI/src/com/android/systemui/tuner/TunerService.java
@@ -31,6 +31,7 @@ import android.os.Handler;
import android.os.Looper;
import android.os.UserHandle;
import android.provider.Settings;
+import android.text.TextUtils;
import android.util.ArrayMap;
import com.android.systemui.BatteryMeterView;
@@ -40,6 +41,7 @@ import com.android.systemui.SystemUI;
import com.android.systemui.SystemUIApplication;
import com.android.systemui.settings.CurrentUserTracker;
import com.android.systemui.statusbar.phone.SystemUIDialog;
+import cyanogenmod.providers.CMSettings;
import java.util.ArrayList;
import java.util.HashMap;
@@ -84,17 +86,31 @@ public class TunerService extends SystemUI {
}
private void addTunable(Tunable tunable, String key) {
+ addTunableByProvider(tunable, key, false);
+ }
+
+ public void addTunableByProvider(Tunable tunable, String key, boolean cm) {
if (!mTunableLookup.containsKey(key)) {
mTunableLookup.put(key, new ArrayList<Tunable>());
}
mTunableLookup.get(key).add(tunable);
- Uri uri = Settings.Secure.getUriFor(key);
+ Uri uri;
+ if (!cm) {
+ uri = Settings.Secure.getUriFor(key);
+ } else {
+ uri = CMSettings.Secure.getUriFor(key);
+ }
if (!mListeningUris.containsKey(uri)) {
mListeningUris.put(uri, key);
mContentResolver.registerContentObserver(uri, false, mObserver, mCurrentUser);
}
// Send the first state.
- String value = Settings.Secure.getStringForUser(mContentResolver, key, mCurrentUser);
+ String value;
+ if (cm) {
+ value = CMSettings.Secure.getStringForUser(mContentResolver, key, mCurrentUser);
+ } else {
+ value = Settings.Secure.getStringForUser(mContentResolver, key, mCurrentUser);
+ }
tunable.onTuningChanged(key, value);
}
@@ -116,7 +132,18 @@ public class TunerService extends SystemUI {
public void reloadSetting(Uri uri) {
String key = mListeningUris.get(uri);
- String value = Settings.Secure.getStringForUser(mContentResolver, key, mCurrentUser);
+
+ // Handle possible null keys
+ if (TextUtils.isEmpty(key)) {
+ return;
+ }
+
+ String value;
+ if (uri.getAuthority().equals(CMSettings.AUTHORITY)) {
+ value = CMSettings.Secure.getStringForUser(mContentResolver, key, mCurrentUser);
+ } else {
+ value = Settings.Secure.getStringForUser(mContentResolver, key, mCurrentUser);
+ }
for (Tunable tunable : mTunableLookup.get(key)) {
tunable.onTuningChanged(key, value);
}
@@ -124,8 +151,14 @@ public class TunerService extends SystemUI {
private void reloadAll() {
for (String key : mTunableLookup.keySet()) {
- String value = Settings.Secure.getStringForUser(mContentResolver, key,
- mCurrentUser);
+ String value;
+ Uri uri = CMSettings.Secure.getUriFor(key);
+ if (uri.getAuthority() != null && uri.getAuthority().equals(CMSettings.AUTHORITY)) {
+ value = CMSettings.Secure.getStringForUser(mContentResolver, key, mCurrentUser);
+ } else {
+ value = Settings.Secure.getStringForUser(mContentResolver, key,
+ mCurrentUser);
+ }
for (Tunable tunable : mTunableLookup.get(key)) {
tunable.onTuningChanged(key, value);
}
@@ -135,13 +168,17 @@ public class TunerService extends SystemUI {
public void clearAll() {
// A couple special cases.
Settings.Global.putString(mContentResolver, DemoMode.DEMO_MODE_ALLOWED, null);
- Settings.System.putString(mContentResolver, BatteryMeterView.SHOW_PERCENT_SETTING, null);
Intent intent = new Intent(DemoMode.ACTION_DEMO);
intent.putExtra(DemoMode.EXTRA_COMMAND, DemoMode.COMMAND_EXIT);
mContext.sendBroadcast(intent);
for (String key : mTunableLookup.keySet()) {
- Settings.Secure.putString(mContentResolver, key, null);
+ Uri uri = CMSettings.Secure.getUriFor(key);
+ if (uri.getAuthority() != null && uri.getAuthority().equals(CMSettings.AUTHORITY)) {
+ CMSettings.Secure.putString(mContentResolver, key, null);
+ } else {
+ Settings.Secure.putString(mContentResolver, key, null);
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/usb/StorageNotification.java b/packages/SystemUI/src/com/android/systemui/usb/StorageNotification.java
index 180d918..baa5321 100644
--- a/packages/SystemUI/src/com/android/systemui/usb/StorageNotification.java
+++ b/packages/SystemUI/src/com/android/systemui/usb/StorageNotification.java
@@ -256,6 +256,11 @@ public class StorageNotification extends SystemUI {
}
private void onPublicVolumeStateChangedInternal(VolumeInfo vol) {
+ // Do not notify for volumes on non-removable disks
+ if (vol.disk.isNonRemovable()) {
+ return;
+ }
+
Log.d(TAG, "Notifying about public volume: " + vol.toString());
final Notification notif;
@@ -322,7 +327,7 @@ public class StorageNotification extends SystemUI {
// Don't annoy when user dismissed in past. (But make sure the disk is adoptable; we
// used to allow snoozing non-adoptable disks too.)
- if (rec.isSnoozed() && disk.isAdoptable()) {
+ if (rec == null || (rec.isSnoozed() && disk.isAdoptable())) {
return null;
}
@@ -359,6 +364,11 @@ public class StorageNotification extends SystemUI {
.setContentIntent(browseIntent)
.setCategory(Notification.CATEGORY_SYSTEM)
.setPriority(Notification.PRIORITY_LOW);
+ // USB disks notification can be persistent
+ if (disk.isUsb()) {
+ builder.setOngoing(mContext.getResources().getBoolean(
+ R.bool.config_persistUsbDriveNotification));
+ }
// Non-adoptable disks can't be snoozed.
if (disk.isAdoptable()) {
builder.setDeleteIntent(buildSnoozeIntent(vol.getFsUuid()));
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialog.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialog.java
index e9f1095..9fda531 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialog.java
@@ -31,6 +31,7 @@ import android.content.res.ColorStateList;
import android.content.res.Resources;
import android.graphics.Color;
import android.graphics.PixelFormat;
+import android.graphics.PorterDuff;
import android.graphics.Rect;
import android.graphics.drawable.AnimatedVectorDrawable;
import android.graphics.drawable.ColorDrawable;
@@ -42,6 +43,7 @@ import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.os.SystemClock;
+import android.provider.Settings;
import android.provider.Settings.Global;
import android.util.DisplayMetrics;
import android.util.Log;
@@ -104,7 +106,6 @@ public class VolumeDialog {
private final SpTexts mSpTexts;
private final SparseBooleanArray mDynamic = new SparseBooleanArray();
private final KeyguardManager mKeyguard;
- private final AudioManager mAudioManager;
private final int mExpandButtonAnimationDuration;
private final ZenFooter mZenFooter;
private final LayoutTransition mLayoutTransition;
@@ -128,15 +129,15 @@ public class VolumeDialog {
private boolean mPendingStateChanged;
private boolean mPendingRecheckAll;
private long mCollapseTime;
+ private int mLastActiveStream;
public VolumeDialog(Context context, int windowType, VolumeDialogController controller,
- ZenModeController zenModeController, Callback callback) {
+ ZenModeController zenModeController, Callback callback) {
mContext = context;
mController = controller;
mCallback = callback;
mSpTexts = new SpTexts(mContext);
mKeyguard = (KeyguardManager) context.getSystemService(Context.KEYGUARD_SERVICE);
- mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
mDialog = new CustomDialog(mContext);
@@ -163,7 +164,7 @@ public class VolumeDialog {
window.setAttributes(lp);
window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING);
- mActiveSliderTint = loadColorStateList(R.color.system_accent_color);
+ mActiveSliderTint = loadColorStateList(R.color.volume_slider_active);
mInactiveSliderTint = loadColorStateList(R.color.volume_slider_inactive);
mDialog.setContentView(R.layout.volume_dialog);
mDialogView = (ViewGroup) mDialog.findViewById(R.id.volume_dialog);
@@ -177,19 +178,19 @@ public class VolumeDialog {
mDialogContentView.setLayoutTransition(mLayoutTransition);
mMotion = new VolumeDialogMotion(mDialog, mDialogView, mDialogContentView, mExpandButton,
new VolumeDialogMotion.Callback() {
- @Override
- public void onAnimatingChanged(boolean animating) {
- if (animating) return;
- if (mPendingStateChanged) {
- mHandler.sendEmptyMessage(H.STATE_CHANGED);
- mPendingStateChanged = false;
- }
- if (mPendingRecheckAll) {
- mHandler.sendEmptyMessage(H.RECHECK_ALL);
- mPendingRecheckAll = false;
- }
- }
- });
+ @Override
+ public void onAnimatingChanged(boolean animating) {
+ if (animating) return;
+ if (mPendingStateChanged) {
+ mHandler.sendEmptyMessage(H.STATE_CHANGED);
+ mPendingStateChanged = false;
+ }
+ if (mPendingRecheckAll) {
+ mHandler.sendEmptyMessage(H.RECHECK_ALL);
+ mPendingRecheckAll = false;
+ }
+ }
+ });
addRow(AudioManager.STREAM_RING,
R.drawable.ic_volume_ringer, R.drawable.ic_volume_ringer_mute, true);
@@ -272,11 +273,15 @@ public class VolumeDialog {
row.settingsButton.addOnLayoutChangeListener(new OnLayoutChangeListener() {
@Override
public void onLayoutChange(View v, int left, int top, int right, int bottom,
- int oldLeft, int oldTop, int oldRight, int oldBottom) {
- final boolean moved = oldLeft != left || oldTop != top;
+ int oldLeft, int oldTop, int oldRight, int oldBottom) {
+ final boolean moved = mLastActiveStream != mActiveStream ||
+ oldLeft != left || oldTop != top;
if (D.BUG) Log.d(TAG, "onLayoutChange moved=" + moved
+ " old=" + new Rect(oldLeft, oldTop, oldRight, oldBottom).toShortString()
- + " new=" + new Rect(left,top,right,bottom).toShortString());
+ + "," + mLastActiveStream
+ + " new=" + new Rect(left,top,right,bottom).toShortString()
+ + "," + mActiveStream);
+ mLastActiveStream = mActiveStream;
if (moved) {
for (int i = 0; i < mDialogContentView.getChildCount(); i++) {
final View c = mDialogContentView.getChildAt(i);
@@ -362,6 +367,8 @@ public class VolumeDialog {
row.header = (TextView) row.view.findViewById(R.id.volume_row_header);
mSpTexts.add(row.header);
row.slider = (SeekBar) row.view.findViewById(R.id.volume_row_slider);
+ row.slider.setProgressTintMode(PorterDuff.Mode.SRC_ATOP);
+ row.slider.setThumbTintMode(PorterDuff.Mode.SRC_ATOP);
row.slider.setOnSeekBarChangeListener(new VolumeSeekBarChangeListener(row));
// forward events above the slider into the slider
@@ -597,13 +604,17 @@ public class VolumeDialog {
final VolumeRow row = mRows.get(i);
if (row.ss == null || !row.ss.dynamic) continue;
if (!mDynamic.get(row.stream)) {
- mRows.remove(i);
- mDialogContentView.removeView(row.view);
- mDialogContentView.removeView(row.space);
+ removeRow(row);
}
}
}
+ private void removeRow(VolumeRow volumeRow) {
+ mRows.remove(volumeRow);
+ mDialogContentView.removeView(volumeRow.view);
+ mDialogContentView.removeView(volumeRow.space);
+ }
+
private void onStateChangedH(State state) {
final boolean animating = mMotion.isAnimating();
if (D.BUG) Log.d(TAG, "onStateChangedH animating=" + animating);
@@ -624,6 +635,8 @@ public class VolumeDialog {
}
}
+ updateNotificationRowH();
+
if (mActiveStream != state.activeStream) {
mActiveStream = state.activeStream;
updateRowsH();
@@ -635,11 +648,23 @@ public class VolumeDialog {
updateFooterH();
}
+ private void updateNotificationRowH() {
+ VolumeRow notificationRow = findRow(AudioManager.STREAM_NOTIFICATION);
+ if (notificationRow != null) {
+ if (mState.linkedNotification) {
+ removeRow(notificationRow);
+ }
+ } else if (!mState.linkedNotification) {
+ addRow(AudioManager.STREAM_NOTIFICATION,
+ R.drawable.ic_volume_notification, R.drawable.ic_volume_notification_mute,
+ true);
+ }
+ }
+
private void updateFooterH() {
if (D.BUG) Log.d(TAG, "updateFooterH");
final boolean wasVisible = mZenFooter.getVisibility() == View.VISIBLE;
- final boolean visible = mState.zenMode != Global.ZEN_MODE_OFF
- && mAudioManager.isStreamAffectedByRingerMode(mActiveStream);
+ final boolean visible = mState.zenMode != Global.ZEN_MODE_OFF;
if (wasVisible != visible && !visible) {
prepareForCollapse();
}
@@ -663,8 +688,9 @@ public class VolumeDialog {
final boolean isSystemStream = row.stream == AudioManager.STREAM_SYSTEM;
final boolean isAlarmStream = row.stream == AudioManager.STREAM_ALARM;
final boolean isMusicStream = row.stream == AudioManager.STREAM_MUSIC;
- final boolean isRingVibrate = isRingStream
- && mState.ringerModeInternal == AudioManager.RINGER_MODE_VIBRATE;
+ final boolean isNotificationStream = row.stream == AudioManager.STREAM_NOTIFICATION;
+ final boolean isVibrate = mState.ringerModeInternal == AudioManager.RINGER_MODE_VIBRATE;
+ final boolean isRingVibrate = isRingStream && isVibrate;
final boolean isRingSilent = isRingStream
&& mState.ringerModeInternal == AudioManager.RINGER_MODE_SILENT;
final boolean isZenAlarms = mState.zenMode == Global.ZEN_MODE_ALARMS;
@@ -672,8 +698,9 @@ public class VolumeDialog {
final boolean isZenPriority = mState.zenMode == Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS;
final boolean isRingZenNone = (isRingStream || isSystemStream) && isZenNone;
final boolean isRingLimited = isRingStream && isZenPriority;
- final boolean zenMuted = isZenAlarms ? (isRingStream || isSystemStream)
- : isZenNone ? (isRingStream || isSystemStream || isAlarmStream || isMusicStream)
+ final boolean zenMuted = isZenAlarms ? (isRingStream || isSystemStream || isNotificationStream)
+ : isZenNone ? (isRingStream || isSystemStream || isAlarmStream || isMusicStream || isNotificationStream)
+ : isVibrate ? (isNotificationStream)
: false;
// update slider max
@@ -708,12 +735,12 @@ public class VolumeDialog {
row.icon.setAlpha(iconEnabled ? 1 : 0.5f);
final int iconRes =
isRingVibrate ? R.drawable.ic_volume_ringer_vibrate
- : isRingSilent || zenMuted ? row.cachedIconRes
- : ss.routedToBluetooth ?
+ : isRingSilent || zenMuted ? row.cachedIconRes
+ : ss.routedToBluetooth ?
(ss.muted ? R.drawable.ic_volume_media_bt_mute
: R.drawable.ic_volume_media_bt)
- : mAutomute && ss.level == 0 ? row.iconMuteRes
- : (ss.muted ? row.iconMuteRes : row.iconRes);
+ : mAutomute && ss.level == 0 ? row.iconMuteRes
+ : (ss.muted ? row.iconMuteRes : row.iconRes);
if (iconRes != row.cachedIconRes) {
if (row.cachedIconRes != 0 && isRingVibrate) {
mController.vibrate();
@@ -723,11 +750,11 @@ public class VolumeDialog {
}
row.iconState =
iconRes == R.drawable.ic_volume_ringer_vibrate ? Events.ICON_STATE_VIBRATE
- : (iconRes == R.drawable.ic_volume_media_bt_mute || iconRes == row.iconMuteRes)
+ : (iconRes == R.drawable.ic_volume_media_bt_mute || iconRes == row.iconMuteRes)
? Events.ICON_STATE_MUTE
- : (iconRes == R.drawable.ic_volume_media_bt || iconRes == row.iconRes)
+ : (iconRes == R.drawable.ic_volume_media_bt || iconRes == row.iconRes)
? Events.ICON_STATE_UNMUTE
- : Events.ICON_STATE_UNKNOWN;
+ : Events.ICON_STATE_UNKNOWN;
row.icon.setContentDescription(ss.name);
// update slider
@@ -934,6 +961,12 @@ public class VolumeDialog {
}
};
+ public void cleanup() {
+ mController.removeCallback(mControllerCallbackH);
+ mZenFooter.cleanup();
+ mAccessibility.cleanup();
+ }
+
private final class H extends Handler {
private static final int SHOW = 1;
private static final int DISMISS = 2;
@@ -1056,36 +1089,21 @@ public class VolumeDialog {
public void init() {
mMgr = (AccessibilityManager) mContext.getSystemService(Context.ACCESSIBILITY_SERVICE);
- mDialogView.addOnAttachStateChangeListener(new OnAttachStateChangeListener() {
- @Override
- public void onViewDetachedFromWindow(View v) {
- if (D.BUG) Log.d(TAG, "onViewDetachedFromWindow");
- // noop
- }
-
- @Override
- public void onViewAttachedToWindow(View v) {
- if (D.BUG) Log.d(TAG, "onViewAttachedToWindow");
- updateFeedbackEnabled();
- }
- });
- mDialogView.setAccessibilityDelegate(this);
- mMgr.addAccessibilityStateChangeListener(new AccessibilityStateChangeListener() {
- @Override
- public void onAccessibilityStateChanged(boolean enabled) {
- updateFeedbackEnabled();
- }
- });
+ mDialogView.addOnAttachStateChangeListener(mOnAttachStateChangeListener);
updateFeedbackEnabled();
}
@Override
public boolean onRequestSendAccessibilityEvent(ViewGroup host, View child,
- AccessibilityEvent event) {
+ AccessibilityEvent event) {
rescheduleTimeoutH();
return super.onRequestSendAccessibilityEvent(host, child, event);
}
+ public void cleanup() {
+ mDialogView.removeOnAttachStateChangeListener(mOnAttachStateChangeListener);
+ }
+
private void updateFeedbackEnabled() {
mFeedbackEnabled = computeFeedbackEnabled();
}
@@ -1101,6 +1119,21 @@ public class VolumeDialog {
}
return false;
}
+
+ private OnAttachStateChangeListener mOnAttachStateChangeListener =
+ new OnAttachStateChangeListener() {
+ @Override
+ public void onViewDetachedFromWindow(View v) {
+ if (D.BUG) Log.d(TAG, "onViewDetachedFromWindow");
+ // noop
+ }
+
+ @Override
+ public void onViewAttachedToWindow(View v) {
+ if (D.BUG) Log.d(TAG, "onViewAttachedToWindow");
+ updateFeedbackEnabled();
+ }
+ };
}
private static class VolumeRow {
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogComponent.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogComponent.java
index 1083f40..742b18b 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogComponent.java
@@ -43,7 +43,7 @@ public class VolumeDialogComponent implements VolumeComponent {
private final Context mContext;
private final VolumeDialogController mController;
private final ZenModeController mZenModeController;
- private final VolumeDialog mDialog;
+ private VolumeDialog mDialog;
private final VolumePolicy mVolumePolicy = new VolumePolicy(
true, // volumeDownToEnterSilent
true, // volumeUpToExitSilent
@@ -138,4 +138,11 @@ public class VolumeDialogComponent implements VolumeComponent {
}
};
+ public void recreateDialog() {
+ if (mDialog != null) mDialog.cleanup();
+
+ mDialog = new VolumeDialog(mContext, WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY,
+ mController, mZenModeController, mVolumeDialogCallback);
+ applyConfiguration();
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogController.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogController.java
index 32d6805..9269c1c 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogController.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogController.java
@@ -29,6 +29,7 @@ import android.database.ContentObserver;
import android.media.AudioManager;
import android.media.AudioSystem;
import android.media.IVolumeController;
+import android.media.ToneGenerator;
import android.media.VolumePolicy;
import android.media.session.MediaController.PlaybackInfo;
import android.media.session.MediaSession.Token;
@@ -54,6 +55,8 @@ import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
+import cyanogenmod.providers.CMSettings;
+
/**
* Source of truth for all state / events related to the volume dialog. No presentation.
*
@@ -67,6 +70,9 @@ public class VolumeDialogController {
private static final int DYNAMIC_STREAM_START_INDEX = 100;
private static final int VIBRATE_HINT_DURATION = 50;
+ private static final int FREE_DELAY = 10000;
+ private static final int BEEP_DURATION = 150;
+
private static final int[] STREAMS = {
AudioSystem.STREAM_ALARM,
AudioSystem.STREAM_BLUETOOTH_SCO,
@@ -102,10 +108,13 @@ public class VolumeDialogController {
private VolumePolicy mVolumePolicy;
private boolean mShowDndTile = true;
+ private ToneGenerator mToneGenerators[];
+
public VolumeDialogController(Context context, ComponentName component) {
mContext = context.getApplicationContext();
Events.writeEvent(mContext, Events.EVENT_COLLECTION_STARTED);
mComponent = component;
+ mToneGenerators = new ToneGenerator[AudioSystem.getNumStreamTypes()];
mWorkerThread = new HandlerThread(VolumeDialogController.class.getSimpleName());
mWorkerThread.start();
mWorker = new W(mWorkerThread.getLooper());
@@ -289,6 +298,7 @@ public class VolumeDialogController {
final boolean fromKey = (flags & AudioManager.FLAG_FROM_KEY) != 0;
final boolean showVibrateHint = (flags & AudioManager.FLAG_SHOW_VIBRATE_HINT) != 0;
final boolean showSilentHint = (flags & AudioManager.FLAG_SHOW_SILENT_HINT) != 0;
+ final boolean playSound = (flags & AudioManager.FLAG_PLAY_SOUND) != 0;
boolean changed = false;
if (showUI) {
changed |= updateActiveStreamW(stream);
@@ -308,6 +318,19 @@ public class VolumeDialogController {
if (showSilentHint) {
mCallbacks.onShowSilentHint();
}
+ if (playSound) {
+ if ((flags & AudioManager.FLAG_PLAY_SOUND) != 0) {
+ mWorker.removeMessages(W.PLAY_SOUND);
+ mWorker.sendMessageDelayed(mWorker.obtainMessage(W.PLAY_SOUND, stream, flags),
+ AudioSystem.PLAY_SOUND_DELAY);
+ }
+
+ if ((flags & AudioManager.FLAG_REMOVE_SOUND_AND_VIBRATE) != 0) {
+ mWorker.removeMessages(W.PLAY_SOUND);
+ onStopSoundsW();
+ }
+
+ }
if (changed && fromKey) {
Events.writeEvent(mContext, Events.EVENT_KEY, stream, lastAudibleStreamVolume);
}
@@ -348,6 +371,7 @@ public class VolumeDialogController {
updateZenModeW();
updateEffectsSuppressorW(mNoMan.getEffectsSuppressor());
updateZenModeConfigW();
+ updateLinkNotificationConfigW();
mCallbacks.onStateChanged(mState);
}
@@ -400,6 +424,16 @@ public class VolumeDialogController {
return stream == AudioManager.STREAM_RING || stream == AudioManager.STREAM_NOTIFICATION;
}
+ private boolean updateLinkNotificationConfigW() {
+ boolean linkNotificationWithVolume = Settings.Secure.getInt(mContext.getContentResolver(),
+ Settings.Secure.VOLUME_LINK_NOTIFICATION, 1) == 1;
+ if (mState.linkedNotification == linkNotificationWithVolume) {
+ return false;
+ }
+ mState.linkedNotification = linkNotificationWithVolume;
+ return true;
+ }
+
private boolean updateZenModeConfigW() {
final ZenModeConfig zenModeConfig = getZenModeConfig();
if (Objects.equals(mState.zenModeConfig, zenModeConfig)) return false;
@@ -556,6 +590,9 @@ public class VolumeDialogController {
private static final int NOTIFY_VISIBLE = 12;
private static final int USER_ACTIVITY = 13;
private static final int SHOW_SAFETY_WARNING = 14;
+ private static final int PLAY_SOUND = 15;
+ private static final int STOP_SOUNDS = 16;
+ private static final int FREE_RESOURCES = 17;
W(Looper looper) {
super(looper);
@@ -578,6 +615,9 @@ public class VolumeDialogController {
case NOTIFY_VISIBLE: onNotifyVisibleW(msg.arg1 != 0); break;
case USER_ACTIVITY: onUserActivityW(); break;
case SHOW_SAFETY_WARNING: onShowSafetyWarningW(msg.arg1); break;
+ case PLAY_SOUND: onPlaySoundW(msg.arg1, msg.arg2); break;
+ case STOP_SOUNDS: onStopSoundsW(); break;
+ case FREE_RESOURCES: onFreeResourcesW(); break;
}
}
}
@@ -707,6 +747,66 @@ public class VolumeDialogController {
}
+ protected void onPlaySoundW(int streamType, int flags) {
+
+ // If preference is no sound - just exit here
+ if (CMSettings.System.getInt(mContext.getContentResolver(),
+ CMSettings.System.VOLUME_ADJUST_SOUNDS_ENABLED, 1) == 0) {
+ return;
+ }
+
+ if (mWorker.hasMessages(W.STOP_SOUNDS)) {
+ mWorker.removeMessages(W.STOP_SOUNDS);
+ // Force stop right now
+ onStopSoundsW();
+ }
+
+ ToneGenerator toneGen = getOrCreateToneGeneratorW(streamType);
+ if (toneGen != null) {
+ toneGen.startTone(ToneGenerator.TONE_PROP_BEEP);
+ mWorker.sendMessageDelayed(mWorker.obtainMessage(W.STOP_SOUNDS), BEEP_DURATION);
+ }
+
+ mWorker.removeMessages(W.FREE_RESOURCES);
+ mWorker.sendMessageDelayed(mWorker.obtainMessage(W.FREE_RESOURCES), FREE_DELAY);
+ }
+
+ protected void onStopSoundsW() {
+ int numStreamTypes = AudioSystem.getNumStreamTypes();
+ for (int i = numStreamTypes - 1; i >= 0; i--) {
+ ToneGenerator toneGen = mToneGenerators[i];
+ if (toneGen != null) {
+ toneGen.stopTone();
+ }
+ }
+ }
+
+ private ToneGenerator getOrCreateToneGeneratorW(int streamType) {
+ if (mToneGenerators[streamType] == null) {
+ try {
+ mToneGenerators[streamType] = new ToneGenerator(streamType,
+ ToneGenerator.MAX_VOLUME);
+ } catch (RuntimeException e) {
+ if (false) {
+ Log.d(TAG, "ToneGenerator constructor failed with "
+ + "RuntimeException: " + e);
+ }
+ }
+ }
+ return mToneGenerators[streamType];
+ }
+
+ protected void onFreeResourcesW() {
+ synchronized (this) {
+ for (int i = mToneGenerators.length - 1; i >= 0; i--) {
+ if (mToneGenerators[i] != null) {
+ mToneGenerators[i].release();
+ }
+ mToneGenerators[i] = null;
+ }
+ }
+ }
+
private final class SettingObserver extends ContentObserver {
private final Uri SERVICE_URI = Settings.Secure.getUriFor(
Settings.Secure.VOLUME_CONTROLLER_SERVICE_COMPONENT);
@@ -714,6 +814,8 @@ public class VolumeDialogController {
Settings.Global.getUriFor(Settings.Global.ZEN_MODE);
private final Uri ZEN_MODE_CONFIG_URI =
Settings.Global.getUriFor(Settings.Global.ZEN_MODE_CONFIG_ETAG);
+ private final Uri VOLUME_LINK_NOTIFICATION_URI =
+ Settings.Secure.getUriFor(Settings.Secure.VOLUME_LINK_NOTIFICATION);
public SettingObserver(Handler handler) {
super(handler);
@@ -723,6 +825,8 @@ public class VolumeDialogController {
mContext.getContentResolver().registerContentObserver(SERVICE_URI, false, this);
mContext.getContentResolver().registerContentObserver(ZEN_MODE_URI, false, this);
mContext.getContentResolver().registerContentObserver(ZEN_MODE_CONFIG_URI, false, this);
+ mContext.getContentResolver().registerContentObserver(VOLUME_LINK_NOTIFICATION_URI,
+ false, this);
onChange(true, SERVICE_URI);
}
@@ -750,6 +854,9 @@ public class VolumeDialogController {
if (ZEN_MODE_CONFIG_URI.equals(uri)) {
changed = updateZenModeConfigW();
}
+ if (VOLUME_LINK_NOTIFICATION_URI.equals(uri)) {
+ changed = updateLinkNotificationConfigW();
+ }
if (changed) {
mCallbacks.onStateChanged(mState);
}
@@ -868,6 +975,13 @@ public class VolumeDialogController {
@Override
public void onRemoteVolumeChanged(Token token, int flags) {
+ // If an inactive session changed the remoteVolume, bail
+ // since we don't have any active streams to update
+ if (!mRemoteStreams.containsKey(token)) {
+ Log.i(TAG, "onRemoteVolumeChanged called on inactive" +
+ "stream. Ignoring");
+ return;
+ }
final int stream = mRemoteStreams.get(token);
final boolean showUI = (flags & AudioManager.FLAG_SHOW_UI) != 0;
boolean changed = updateActiveStreamW(stream);
@@ -947,6 +1061,7 @@ public class VolumeDialogController {
public String effectsSuppressorName;
public ZenModeConfig zenModeConfig;
public int activeStream = NO_ACTIVE_STREAM;
+ public boolean linkedNotification;
public State copy() {
final State rt = new State();
@@ -960,6 +1075,7 @@ public class VolumeDialogController {
rt.effectsSuppressorName = effectsSuppressorName;
if (zenModeConfig != null) rt.zenModeConfig = zenModeConfig.copy();
rt.activeStream = activeStream;
+ rt.linkedNotification = linkedNotification;
return rt;
}
@@ -989,6 +1105,7 @@ public class VolumeDialogController {
sep(sb, indent); sb.append("effectsSuppressorName:").append(effectsSuppressorName);
sep(sb, indent); sb.append("zenModeConfig:").append(zenModeConfig);
sep(sb, indent); sb.append("activeStream:").append(activeStream);
+ sep(sb, indent); sb.append("linkedNotification:").append(linkedNotification);
if (indent > 0) sep(sb, indent);
return sb.append('}').toString();
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java
index 2688813..fa0de7d 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeUI.java
@@ -27,6 +27,7 @@ import android.content.DialogInterface.OnClickListener;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.ApplicationInfo;
+import android.content.pm.ActivityInfo;
import android.content.res.Configuration;
import android.media.AudioManager;
import android.media.session.MediaSessionManager;
@@ -63,6 +64,8 @@ public class VolumeUI extends SystemUI {
private VolumeDialogComponent mVolumeComponent;
+ private Configuration mConfiguration;
+
@Override
public void start() {
mEnabled = mContext.getResources().getBoolean(R.bool.enable_volume_ui);
@@ -80,6 +83,7 @@ public class VolumeUI extends SystemUI {
mContext, Settings.Secure.VOLUME_CONTROLLER_SERVICE_COMPONENT,
new ServiceMonitorCallbacks());
mVolumeControllerService.start();
+ mConfiguration = new Configuration(mContext.getResources().getConfiguration());
}
private VolumeComponent getVolumeComponent() {
@@ -91,6 +95,20 @@ public class VolumeUI extends SystemUI {
super.onConfigurationChanged(newConfig);
if (!mEnabled) return;
getVolumeComponent().onConfigurationChanged(newConfig);
+
+ if (isThemeChange(newConfig)) {
+ mContext.recreateTheme();
+ mVolumeComponent.recreateDialog();
+ }
+ mConfiguration.setTo(newConfig);
+ }
+
+ private boolean isThemeChange(Configuration newConfig) {
+ if (mConfiguration != null) {
+ int changes = mConfiguration.updateFrom(newConfig);
+ return (changes & ActivityInfo.CONFIG_THEME_RESOURCE) != 0;
+ }
+ return false;
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/volume/ZenFooter.java b/packages/SystemUI/src/com/android/systemui/volume/ZenFooter.java
index af7ee08..0fb80c0 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/ZenFooter.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/ZenFooter.java
@@ -69,17 +69,19 @@ public class ZenFooter extends LinearLayout {
mSpTexts.add(mEndNowButton);
}
+ private ZenModeController.Callback mZenModeCallback = new ZenModeController.Callback() {
+ @Override
+ public void onZenChanged(int zen) {
+ setZen(zen);
+ }
+ @Override
+ public void onConfigChanged(ZenModeConfig config) {
+ setConfig(config);
+ }
+ };
+
public void init(final ZenModeController controller) {
- controller.addCallback(new ZenModeController.Callback() {
- @Override
- public void onZenChanged(int zen) {
- setZen(zen);
- }
- @Override
- public void onConfigChanged(ZenModeConfig config) {
- setConfig(config);
- }
- });
+ controller.addCallback(mZenModeCallback);
mEndNowButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
@@ -142,4 +144,8 @@ public class ZenFooter extends LinearLayout {
mSpTexts.update();
}
+ public void cleanup() {
+ mController.removeCallback(mZenModeCallback);
+ }
+
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/ZenModePanel.java b/packages/SystemUI/src/com/android/systemui/volume/ZenModePanel.java
index 07ec843..ddf623a 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/ZenModePanel.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/ZenModePanel.java
@@ -70,9 +70,7 @@ public class ZenModePanel extends LinearLayout {
private static final int SECONDS_MS = 1000;
private static final int MINUTES_MS = 60 * SECONDS_MS;
- private static final int[] MINUTE_BUCKETS = DEBUG
- ? new int[] { 0, 1, 2, 5, 15, 30, 45, 60, 120, 180, 240, 480 }
- : ZenModeConfig.MINUTE_BUCKETS;
+ private static final int[] MINUTE_BUCKETS = ZenModeConfig.MINUTE_BUCKETS;
private static final int MIN_BUCKET_MINUTES = MINUTE_BUCKETS[0];
private static final int MAX_BUCKET_MINUTES = MINUTE_BUCKETS[MINUTE_BUCKETS.length - 1];
private static final int DEFAULT_BUCKET_INDEX = Arrays.binarySearch(MINUTE_BUCKETS, 60);
@@ -421,8 +419,13 @@ public class ZenModePanel extends LinearLayout {
mZenIntroductionCustomize.setVisibility(zenImportant ? VISIBLE : GONE);
}
final String warning = computeAlarmWarningText(zenNone);
- mZenAlarmWarning.setVisibility(warning != null ? VISIBLE : GONE);
+ final int oldVis = mZenAlarmWarning.getVisibility();
+ final int newVis = warning != null ? VISIBLE : GONE;
+ mZenAlarmWarning.setVisibility(newVis);
mZenAlarmWarning.setText(warning);
+ if (newVis != oldVis) {
+ requestLayout();
+ }
}
private String computeAlarmWarningText(boolean zenNone) {
@@ -603,13 +606,6 @@ public class ZenModePanel extends LinearLayout {
if (DEBUG) Log.d(mTag, "bind i=" + mZenConditions.indexOfChild(row) + " first=" + first
+ " condition=" + conditionId);
tag.rb.setEnabled(enabled);
- final boolean checked = (mSessionExitCondition != null
- || mAttachedZen != Global.ZEN_MODE_OFF)
- && (sameConditionId(mSessionExitCondition, tag.condition));
- if (checked != tag.rb.isChecked()) {
- if (DEBUG) Log.d(mTag, "bind checked=" + checked + " condition=" + conditionId);
- tag.rb.setChecked(checked);
- }
tag.rb.setOnCheckedChangeListener(new OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
diff --git a/packages/SystemUI/src/com/viewpagerindicator/CirclePageIndicator.java b/packages/SystemUI/src/com/viewpagerindicator/CirclePageIndicator.java
new file mode 100644
index 0000000..4286983
--- /dev/null
+++ b/packages/SystemUI/src/com/viewpagerindicator/CirclePageIndicator.java
@@ -0,0 +1,527 @@
+/*
+ * Copyright (C) 2011 Patrik Akerfeldt
+ * Copyright (C) 2011 Jake Wharton
+ *
+ * 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.viewpagerindicator;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Paint.Style;
+import android.graphics.drawable.Drawable;
+import android.support.v4.view.MotionEventCompat;
+import android.support.v4.view.ViewConfigurationCompat;
+import android.support.v4.view.ViewPager;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewConfiguration;
+import com.android.systemui.R;
+
+import static android.graphics.Paint.ANTI_ALIAS_FLAG;
+import static android.widget.LinearLayout.HORIZONTAL;
+import static android.widget.LinearLayout.VERTICAL;
+
+/**
+ * Draws circles (one for each view). The current view position is filled and
+ * others are only stroked.
+ */
+public class CirclePageIndicator extends View implements PageIndicator {
+ private static final int INVALID_POINTER = -1;
+
+ private float mRadius;
+ private final Paint mPaintPageFill = new Paint(ANTI_ALIAS_FLAG);
+ private final Paint mPaintStroke = new Paint(ANTI_ALIAS_FLAG);
+ private final Paint mPaintFill = new Paint(ANTI_ALIAS_FLAG);
+ private ViewPager mViewPager;
+ private ViewPager.OnPageChangeListener mListener;
+ private int mCurrentPage;
+ private int mSnapPage;
+ private float mPageOffset;
+ private int mScrollState;
+ private int mOrientation;
+ private boolean mCentered;
+ private boolean mSnap;
+
+ private int mTouchSlop;
+ private float mLastMotionX = -1;
+ private int mActivePointerId = INVALID_POINTER;
+ private boolean mIsDragging;
+
+ private Bitmap mSettingsIcon;
+ private boolean mEditing;
+
+ public CirclePageIndicator(Context context) {
+ this(context, null);
+ }
+
+ public CirclePageIndicator(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public CirclePageIndicator(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ if (isInEditMode()) return;
+
+ //Load defaults from resources
+ final Resources res = getResources();
+ final int defaultPageColor = res.getColor(R.color.default_circle_indicator_page_color);
+ final int defaultFillColor = res.getColor(R.color.default_circle_indicator_fill_color);
+ final int defaultOrientation = res.getInteger(R.integer.default_circle_indicator_orientation);
+ final int defaultStrokeColor = res.getColor(R.color.default_circle_indicator_stroke_color);
+ final float defaultStrokeWidth = res.getDimension(R.dimen.default_circle_indicator_stroke_width);
+ final float defaultRadius = res.getDimension(R.dimen.default_circle_indicator_radius);
+ final boolean defaultCentered = res.getBoolean(R.bool.default_circle_indicator_centered);
+ final boolean defaultSnap = res.getBoolean(R.bool.default_circle_indicator_snap);
+
+ //Retrieve styles attributes
+ TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CirclePageIndicator, defStyle, 0);
+
+ mCentered = a.getBoolean(R.styleable.CirclePageIndicator_centered, defaultCentered);
+ mOrientation = a.getInt(R.styleable.CirclePageIndicator_android_orientation, defaultOrientation);
+ mPaintPageFill.setStyle(Style.FILL);
+ mPaintPageFill.setColor(a.getColor(R.styleable.CirclePageIndicator_pageColor, defaultPageColor));
+ mPaintStroke.setStyle(Style.STROKE);
+ mPaintStroke.setColor(a.getColor(R.styleable.CirclePageIndicator_strokeColor, defaultStrokeColor));
+ mPaintStroke.setStrokeWidth(a.getDimension(R.styleable.CirclePageIndicator_strokeWidth, defaultStrokeWidth));
+ mPaintFill.setStyle(Style.FILL);
+ mPaintFill.setColor(a.getColor(R.styleable.CirclePageIndicator_indicatorFillColor, defaultFillColor));
+ mRadius = a.getDimension(R.styleable.CirclePageIndicator_radius, defaultRadius);
+ mSnap = a.getBoolean(R.styleable.CirclePageIndicator_snap, defaultSnap);
+
+ Drawable background = a.getDrawable(R.styleable.CirclePageIndicator_android_background);
+ if (background != null) {
+ setBackgroundDrawable(background);
+ }
+
+ a.recycle();
+
+ final ViewConfiguration configuration = ViewConfiguration.get(context);
+ mTouchSlop = ViewConfigurationCompat.getScaledPagingTouchSlop(configuration);
+
+ mSettingsIcon = BitmapFactory.decodeResource(res, R.drawable.ic_mini_settings);
+ }
+
+ @Override
+ public boolean hasOverlappingRendering() {
+ return false;
+ }
+
+ public void setCentered(boolean centered) {
+ mCentered = centered;
+ invalidate();
+ }
+
+ public boolean isCentered() {
+ return mCentered;
+ }
+
+ public void setPageColor(int pageColor) {
+ mPaintPageFill.setColor(pageColor);
+ invalidate();
+ }
+
+ public int getPageColor() {
+ return mPaintPageFill.getColor();
+ }
+
+ public void setFillColor(int fillColor) {
+ mPaintFill.setColor(fillColor);
+ invalidate();
+ }
+
+ public int getFillColor() {
+ return mPaintFill.getColor();
+ }
+
+ public void setOrientation(int orientation) {
+ switch (orientation) {
+ case HORIZONTAL:
+ case VERTICAL:
+ mOrientation = orientation;
+ requestLayout();
+ break;
+
+ default:
+ throw new IllegalArgumentException("Orientation must be either HORIZONTAL or VERTICAL.");
+ }
+ }
+
+ public int getOrientation() {
+ return mOrientation;
+ }
+
+ public void setStrokeColor(int strokeColor) {
+ mPaintStroke.setColor(strokeColor);
+ invalidate();
+ }
+
+ public int getStrokeColor() {
+ return mPaintStroke.getColor();
+ }
+
+ public void setStrokeWidth(float strokeWidth) {
+ mPaintStroke.setStrokeWidth(strokeWidth);
+ invalidate();
+ }
+
+ public float getStrokeWidth() {
+ return mPaintStroke.getStrokeWidth();
+ }
+
+ public void setRadius(float radius) {
+ mRadius = radius;
+ invalidate();
+ }
+
+ public float getRadius() {
+ return mRadius;
+ }
+
+ public void setSnap(boolean snap) {
+ mSnap = snap;
+ invalidate();
+ }
+
+ public boolean isSnap() {
+ return mSnap;
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ super.onDraw(canvas);
+
+ if (mViewPager == null) {
+ return;
+ }
+ final int count = mViewPager.getAdapter().getCount();
+ if (count == 0) {
+ return;
+ }
+
+ if (mCurrentPage >= count) {
+ setCurrentItem(count - 1);
+ return;
+ }
+
+ int longSize;
+ int longPaddingBefore;
+ int longPaddingAfter;
+ int shortPaddingBefore;
+ if (mOrientation == HORIZONTAL) {
+ longSize = getWidth();
+ longPaddingBefore = getPaddingLeft();
+ longPaddingAfter = getPaddingRight();
+ shortPaddingBefore = getPaddingTop();
+ } else {
+ longSize = getHeight();
+ longPaddingBefore = getPaddingTop();
+ longPaddingAfter = getPaddingBottom();
+ shortPaddingBefore = getPaddingLeft();
+ }
+
+ final float threeRadius = mRadius * 3;
+ final float shortOffset = shortPaddingBefore + mRadius;
+ float longOffset = longPaddingBefore + mRadius;
+ if (mCentered) {
+ longOffset += ((longSize - longPaddingBefore - longPaddingAfter) / 2.0f) - ((count * threeRadius) / 2.0f);
+ }
+
+ float dX;
+ float dY;
+
+ float pageFillRadius = mRadius;
+ if (mPaintStroke.getStrokeWidth() > 0) {
+ pageFillRadius -= mPaintStroke.getStrokeWidth() / 2.0f;
+ }
+
+ //Draw stroked circles
+ for (int iLoop = 0; iLoop < count; iLoop++) {
+ float drawLong = longOffset + (iLoop * threeRadius);
+ if (mOrientation == HORIZONTAL) {
+ dX = drawLong;
+ dY = shortOffset;
+ } else {
+ dX = shortOffset;
+ dY = drawLong;
+ }
+ // Only paint fill if not completely transparent
+ if (mPaintPageFill.getAlpha() > 0) {
+ if (mEditing && iLoop == 0) {
+ canvas.drawBitmap(mSettingsIcon,
+ (int) (dX - mRadius),
+ (int) (dY - mRadius),
+ mPaintPageFill);
+ } else {
+ canvas.drawCircle(dX, dY, (float) (pageFillRadius / 1.5f), mPaintPageFill);
+ }
+ }
+
+ // Only paint stroke if a stroke width was non-zero
+ if (pageFillRadius != mRadius) {
+ canvas.drawCircle(dX, dY, mRadius, mPaintStroke);
+ }
+ }
+
+ //Draw the filled circle according to the current scroll
+ float cx = (mSnap ? mSnapPage : mCurrentPage) * threeRadius;
+ if (!mSnap) {
+ cx += mPageOffset * threeRadius;
+ }
+ if (mOrientation == HORIZONTAL) {
+ dX = longOffset + cx;
+ dY = shortOffset;
+ } else {
+ dX = shortOffset;
+ dY = longOffset + cx;
+ }
+ canvas.drawCircle(dX, dY, mRadius, mPaintFill);
+ }
+
+ public boolean onTouchEvent(MotionEvent ev) {
+ if (super.onTouchEvent(ev)) {
+ return true;
+ }
+ if ((mViewPager == null) || (mViewPager.getAdapter().getCount() == 0)) {
+ return false;
+ }
+
+ final int action = ev.getAction() & MotionEventCompat.ACTION_MASK;
+ switch (action) {
+ case MotionEvent.ACTION_DOWN:
+ mActivePointerId = MotionEventCompat.getPointerId(ev, 0);
+ mLastMotionX = ev.getX();
+ break;
+
+ case MotionEvent.ACTION_MOVE: {
+ final int activePointerIndex = MotionEventCompat.findPointerIndex(ev, mActivePointerId);
+ final float x = MotionEventCompat.getX(ev, activePointerIndex);
+ final float deltaX = x - mLastMotionX;
+
+ if (!mIsDragging) {
+ if (Math.abs(deltaX) > mTouchSlop) {
+ mIsDragging = true;
+ }
+ }
+
+ if (mIsDragging) {
+ mLastMotionX = x;
+ if (mViewPager.isFakeDragging() || mViewPager.beginFakeDrag()) {
+ mViewPager.fakeDragBy(deltaX);
+ }
+ }
+
+ break;
+ }
+
+ case MotionEvent.ACTION_CANCEL:
+ case MotionEvent.ACTION_UP:
+ if (!mIsDragging) {
+ final int count = mViewPager.getAdapter().getCount();
+ final int width = getWidth();
+ final float halfWidth = width / 2f;
+ final float sixthWidth = width / 6f;
+
+ if ((mCurrentPage > 0) && (ev.getX() < halfWidth - sixthWidth)) {
+ if (action != MotionEvent.ACTION_CANCEL) {
+ mViewPager.setCurrentItem(mCurrentPage - 1);
+ }
+ return true;
+ } else if ((mCurrentPage < count - 1) && (ev.getX() > halfWidth + sixthWidth)) {
+ if (action != MotionEvent.ACTION_CANCEL) {
+ mViewPager.setCurrentItem(mCurrentPage + 1);
+ }
+ return true;
+ }
+ }
+
+ mIsDragging = false;
+ mActivePointerId = INVALID_POINTER;
+ if (mViewPager.isFakeDragging()) mViewPager.endFakeDrag();
+ break;
+
+ case MotionEventCompat.ACTION_POINTER_DOWN: {
+ final int index = MotionEventCompat.getActionIndex(ev);
+ mLastMotionX = MotionEventCompat.getX(ev, index);
+ mActivePointerId = MotionEventCompat.getPointerId(ev, index);
+ break;
+ }
+
+ case MotionEventCompat.ACTION_POINTER_UP:
+ final int pointerIndex = MotionEventCompat.getActionIndex(ev);
+ final int pointerId = MotionEventCompat.getPointerId(ev, pointerIndex);
+ if (pointerId == mActivePointerId) {
+ final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
+ mActivePointerId = MotionEventCompat.getPointerId(ev, newPointerIndex);
+ }
+ mLastMotionX = MotionEventCompat.getX(ev, MotionEventCompat.findPointerIndex(ev, mActivePointerId));
+ break;
+ }
+
+ return true;
+ }
+
+ @Override
+ public void setViewPager(ViewPager view) {
+ if (mViewPager == view) {
+ return;
+ }
+ if (mViewPager != null) {
+ mViewPager.setOnPageChangeListener(null);
+ }
+ if (view.getAdapter() == null) {
+ throw new IllegalStateException("ViewPager does not have adapter instance.");
+ }
+ mViewPager = view;
+ mViewPager.setOnPageChangeListener(this);
+ invalidate();
+ }
+
+ @Override
+ public void setViewPager(ViewPager view, int initialPosition) {
+ setViewPager(view);
+ setCurrentItem(initialPosition);
+ }
+
+ @Override
+ public void setCurrentItem(int item) {
+ if (mViewPager == null) {
+ throw new IllegalStateException("ViewPager has not been bound.");
+ }
+ mViewPager.setCurrentItem(item);
+ mCurrentPage = item;
+ invalidate();
+ }
+
+ @Override
+ public void notifyDataSetChanged() {
+ invalidate();
+ }
+
+ @Override
+ public void onPageScrollStateChanged(int state) {
+ mScrollState = state;
+
+ if (mListener != null) {
+ mListener.onPageScrollStateChanged(state);
+ }
+ }
+
+ @Override
+ public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
+ mCurrentPage = position;
+ mPageOffset = positionOffset;
+ invalidate();
+
+ if (mListener != null) {
+ mListener.onPageScrolled(position, positionOffset, positionOffsetPixels);
+ }
+ }
+
+ @Override
+ public void onPageSelected(int position) {
+ if (mSnap || mScrollState == ViewPager.SCROLL_STATE_IDLE) {
+ mCurrentPage = position;
+ mSnapPage = position;
+ invalidate();
+ }
+
+ if (mListener != null) {
+ mListener.onPageSelected(position);
+ }
+ }
+
+ @Override
+ public void setOnPageChangeListener(ViewPager.OnPageChangeListener listener) {
+ mListener = listener;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see android.view.View#onMeasure(int, int)
+ */
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ if (mOrientation == HORIZONTAL) {
+ setMeasuredDimension(measureLong(widthMeasureSpec), measureShort(heightMeasureSpec));
+ } else {
+ setMeasuredDimension(measureShort(widthMeasureSpec), measureLong(heightMeasureSpec));
+ }
+ }
+
+ /**
+ * Determines the width of this view
+ *
+ * @param measureSpec
+ * A measureSpec packed into an int
+ * @return The width of the view, honoring constraints from measureSpec
+ */
+ private int measureLong(int measureSpec) {
+ int result;
+ int specMode = MeasureSpec.getMode(measureSpec);
+ int specSize = MeasureSpec.getSize(measureSpec);
+
+ if ((specMode == MeasureSpec.EXACTLY) || (mViewPager == null)) {
+ //We were told how big to be
+ result = specSize;
+ } else {
+ //Calculate the width according the views count
+ final int count = mViewPager.getAdapter().getCount();
+ result = (int)(getPaddingLeft() + getPaddingRight()
+ + (count * 2 * mRadius) + (count - 1) * mRadius + 1);
+ //Respect AT_MOST value if that was what is called for by measureSpec
+ if (specMode == MeasureSpec.AT_MOST) {
+ result = Math.min(result, specSize);
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Determines the height of this view
+ *
+ * @param measureSpec
+ * A measureSpec packed into an int
+ * @return The height of the view, honoring constraints from measureSpec
+ */
+ private int measureShort(int measureSpec) {
+ int result;
+ int specMode = MeasureSpec.getMode(measureSpec);
+ int specSize = MeasureSpec.getSize(measureSpec);
+
+ if (specMode == MeasureSpec.EXACTLY) {
+ //We were told how big to be
+ result = specSize;
+ } else {
+ //Measure the height
+ result = (int)(2 * mRadius + getPaddingTop() + getPaddingBottom() + 1);
+ //Respect AT_MOST value if that was what is called for by measureSpec
+ if (specMode == MeasureSpec.AT_MOST) {
+ result = Math.min(result, specSize);
+ }
+ }
+ return result;
+ }
+
+ public void setEditing(boolean editing) {
+ mEditing = editing;
+ invalidate();
+ }
+}
diff --git a/packages/SystemUI/src/com/viewpagerindicator/PageIndicator.java b/packages/SystemUI/src/com/viewpagerindicator/PageIndicator.java
new file mode 100644
index 0000000..c08c00a
--- /dev/null
+++ b/packages/SystemUI/src/com/viewpagerindicator/PageIndicator.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2011 Patrik Akerfeldt
+ * Copyright (C) 2011 Jake Wharton
+ *
+ * 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.viewpagerindicator;
+
+import android.support.v4.view.ViewPager;
+
+/**
+ * A PageIndicator is responsible to show an visual indicator on the total views
+ * number and the current visible view.
+ */
+public interface PageIndicator extends ViewPager.OnPageChangeListener {
+ /**
+ * Bind the indicator to a ViewPager.
+ *
+ * @param view
+ */
+ void setViewPager(ViewPager view);
+
+ /**
+ * Bind the indicator to a ViewPager.
+ *
+ * @param view
+ * @param initialPosition
+ */
+ void setViewPager(ViewPager view, int initialPosition);
+
+ /**
+ * <p>Set the current page of both the ViewPager and indicator.</p>
+ *
+ * <p>This <strong>must</strong> be used if you need to set the page before
+ * the views are drawn on screen (e.g., default start page).</p>
+ *
+ * @param item
+ */
+ void setCurrentItem(int item);
+
+ /**
+ * Set a page change listener which will receive forwarded events.
+ *
+ * @param listener
+ */
+ void setOnPageChangeListener(ViewPager.OnPageChangeListener listener);
+
+ /**
+ * Notify the indicator that the fragment list has changed.
+ */
+ void notifyDataSetChanged();
+}