diff options
author | Steve Kondik <steve@cyngn.com> | 2015-12-07 18:50:52 -0800 |
---|---|---|
committer | Steve Kondik <steve@cyngn.com> | 2015-12-07 18:50:52 -0800 |
commit | 10c07f778a611d8723619f67e5709cbd9e502a07 (patch) | |
tree | 2747712ffb2d61348ec7b241d0c2a764397a07a5 /packages/SystemUI/src | |
parent | 45c11b1020a64aae88b859870d5b2e312dab4f76 (diff) | |
parent | 4d70bd7a928903b35c92619437c70bc382587b71 (diff) | |
download | frameworks_base-10c07f778a611d8723619f67e5709cbd9e502a07.zip frameworks_base-10c07f778a611d8723619f67e5709cbd9e502a07.tar.gz frameworks_base-10c07f778a611d8723619f67e5709cbd9e502a07.tar.bz2 |
Merge tag 'android-6.0.1_r3' of https://android.googlesource.com/platform/frameworks/base into cm-13.0
Android 6.0.1 release 3
Change-Id: I59b9e5a943e0860d43bcfb36ee0e8b8b072412ea
Diffstat (limited to 'packages/SystemUI/src')
23 files changed, 888 insertions, 138 deletions
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java index 33bd726..0b066af 100644 --- a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java +++ b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java @@ -48,6 +48,7 @@ public class SystemUIApplication extends Application { com.android.systemui.usb.StorageNotification.class, com.android.systemui.power.PowerUI.class, com.android.systemui.media.RingtonePlayer.class, + com.android.systemui.keyboard.KeyboardUI.class, }; /** diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/BluetoothDialog.java b/packages/SystemUI/src/com/android/systemui/keyboard/BluetoothDialog.java new file mode 100644 index 0000000..64f3e13 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyboard/BluetoothDialog.java @@ -0,0 +1,34 @@ +/* + * 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.keyboard; + +import android.app.AlertDialog; +import android.content.Context; +import android.view.WindowManager; + +import com.android.systemui.R; +import com.android.systemui.statusbar.phone.SystemUIDialog; + +public class BluetoothDialog extends SystemUIDialog { + + public BluetoothDialog(Context context) { + super(context); + + getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG); + setShowForAllUsers(true); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/KeyboardUI.java b/packages/SystemUI/src/com/android/systemui/keyboard/KeyboardUI.java new file mode 100644 index 0000000..96ee397 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyboard/KeyboardUI.java @@ -0,0 +1,573 @@ +/* + * 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.keyboard; + +import android.app.AlertDialog; +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothManager; +import android.bluetooth.le.BluetoothLeScanner; +import android.bluetooth.le.ScanCallback; +import android.bluetooth.le.ScanFilter; +import android.bluetooth.le.ScanRecord; +import android.bluetooth.le.ScanResult; +import android.bluetooth.le.ScanSettings; +import android.content.ContentResolver; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.res.Configuration; +import android.hardware.input.InputManager; +import android.os.Handler; +import android.os.HandlerThread; +import android.os.Looper; +import android.os.Message; +import android.os.Process; +import android.os.SystemClock; +import android.os.UserHandle; +import android.provider.Settings.Secure; +import android.text.TextUtils; +import android.util.Slog; +import android.view.WindowManager; + +import com.android.settingslib.bluetooth.BluetoothCallback; +import com.android.settingslib.bluetooth.CachedBluetoothDevice; +import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager; +import com.android.settingslib.bluetooth.LocalBluetoothAdapter; +import com.android.settingslib.bluetooth.LocalBluetoothManager; +import com.android.settingslib.bluetooth.LocalBluetoothProfileManager; +import com.android.systemui.R; +import com.android.systemui.SystemUI; + +import java.io.FileDescriptor; +import java.io.PrintWriter; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Set; + +public class KeyboardUI extends SystemUI implements InputManager.OnTabletModeChangedListener { + private static final String TAG = "KeyboardUI"; + private static final boolean DEBUG = false; + + // Give BT some time to start after SyUI comes up. This avoids flashing a dialog in the user's + // face because BT starts a little bit later in the boot process than SysUI and it takes some + // time for us to receive the signal that it's starting. + private static final long BLUETOOTH_START_DELAY_MILLIS = 10 * 1000; + + private static final int STATE_NOT_ENABLED = -1; + private static final int STATE_UNKNOWN = 0; + private static final int STATE_WAITING_FOR_BOOT_COMPLETED = 1; + private static final int STATE_WAITING_FOR_TABLET_MODE_EXIT = 2; + private static final int STATE_WAITING_FOR_DEVICE_DISCOVERY = 3; + private static final int STATE_WAITING_FOR_BLUETOOTH = 4; + private static final int STATE_WAITING_FOR_STATE_PAIRED = 5; + private static final int STATE_PAIRING = 6; + private static final int STATE_PAIRED = 7; + private static final int STATE_USER_CANCELLED = 8; + private static final int STATE_DEVICE_NOT_FOUND = 9; + + private static final int MSG_INIT = 0; + private static final int MSG_ON_BOOT_COMPLETED = 1; + private static final int MSG_PROCESS_KEYBOARD_STATE = 2; + private static final int MSG_ENABLE_BLUETOOTH = 3; + private static final int MSG_ON_BLUETOOTH_STATE_CHANGED = 4; + private static final int MSG_ON_DEVICE_BOND_STATE_CHANGED = 5; + private static final int MSG_ON_BLUETOOTH_DEVICE_ADDED = 6; + private static final int MSG_ON_BLE_SCAN_FAILED = 7; + private static final int MSG_SHOW_BLUETOOTH_DIALOG = 8; + private static final int MSG_DISMISS_BLUETOOTH_DIALOG = 9; + + private volatile KeyboardHandler mHandler; + private volatile KeyboardUIHandler mUIHandler; + + protected volatile Context mContext; + + private boolean mEnabled; + private String mKeyboardName; + private CachedBluetoothDeviceManager mCachedDeviceManager; + private LocalBluetoothAdapter mLocalBluetoothAdapter; + private LocalBluetoothProfileManager mProfileManager; + private boolean mBootCompleted; + private long mBootCompletedTime; + + private int mInTabletMode = InputManager.SWITCH_STATE_UNKNOWN; + private ScanCallback mScanCallback; + private BluetoothDialog mDialog; + + private int mState; + + @Override + public void start() { + mContext = super.mContext; + HandlerThread thread = new HandlerThread("Keyboard", Process.THREAD_PRIORITY_BACKGROUND); + thread.start(); + mHandler = new KeyboardHandler(thread.getLooper()); + mHandler.sendEmptyMessage(MSG_INIT); + } + + @Override + protected void onConfigurationChanged(Configuration newConfig) { + } + + @Override + public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + pw.println("KeyboardUI:"); + pw.println(" mEnabled=" + mEnabled); + pw.println(" mBootCompleted=" + mEnabled); + pw.println(" mBootCompletedTime=" + mBootCompletedTime); + pw.println(" mKeyboardName=" + mKeyboardName); + pw.println(" mInTabletMode=" + mInTabletMode); + pw.println(" mState=" + stateToString(mState)); + } + + @Override + protected void onBootCompleted() { + mHandler.sendEmptyMessage(MSG_ON_BOOT_COMPLETED); + } + + @Override + public void onTabletModeChanged(long whenNanos, boolean inTabletMode) { + if (DEBUG) { + Slog.d(TAG, "onTabletModeChanged(" + whenNanos + ", " + inTabletMode + ")"); + } + + if (inTabletMode && mInTabletMode != InputManager.SWITCH_STATE_ON + || !inTabletMode && mInTabletMode != InputManager.SWITCH_STATE_OFF) { + mInTabletMode = inTabletMode ? + InputManager.SWITCH_STATE_ON : InputManager.SWITCH_STATE_OFF; + processKeyboardState(); + } + } + + // Shoud only be called on the handler thread + private void init() { + Context context = mContext; + mKeyboardName = + context.getString(com.android.internal.R.string.config_packagedKeyboardName); + if (TextUtils.isEmpty(mKeyboardName)) { + if (DEBUG) { + Slog.d(TAG, "No packaged keyboard name given."); + } + return; + } + + LocalBluetoothManager bluetoothManager = LocalBluetoothManager.getInstance(context, null); + if (bluetoothManager == null) { + if (DEBUG) { + Slog.e(TAG, "Failed to retrieve LocalBluetoothManager instance"); + } + return; + } + mEnabled = true; + mCachedDeviceManager = bluetoothManager.getCachedDeviceManager(); + mLocalBluetoothAdapter = bluetoothManager.getBluetoothAdapter(); + mProfileManager = bluetoothManager.getProfileManager(); + bluetoothManager.getEventManager().registerCallback(new BluetoothCallbackHandler()); + + InputManager im = (InputManager) context.getSystemService(Context.INPUT_SERVICE); + im.registerOnTabletModeChangedListener(this, mHandler); + mInTabletMode = im.isInTabletMode(); + + processKeyboardState(); + mUIHandler = new KeyboardUIHandler(); + } + + // Should only be called on the handler thread + private void processKeyboardState() { + mHandler.removeMessages(MSG_PROCESS_KEYBOARD_STATE); + + if (!mEnabled) { + mState = STATE_NOT_ENABLED; + return; + } + + if (!mBootCompleted) { + mState = STATE_WAITING_FOR_BOOT_COMPLETED; + return; + } + + if (mInTabletMode != InputManager.SWITCH_STATE_OFF) { + if (mState == STATE_WAITING_FOR_DEVICE_DISCOVERY) { + stopScanning(); + } + mState = STATE_WAITING_FOR_TABLET_MODE_EXIT; + return; + } + + final int btState = mLocalBluetoothAdapter.getState(); + if (btState == BluetoothAdapter.STATE_TURNING_ON || btState == BluetoothAdapter.STATE_ON + && mState == STATE_WAITING_FOR_BLUETOOTH) { + // If we're waiting for bluetooth but it has come on in the meantime, or is coming + // on, just dismiss the dialog. This frequently happens during device startup. + mUIHandler.sendEmptyMessage(MSG_DISMISS_BLUETOOTH_DIALOG); + } + + if (btState == BluetoothAdapter.STATE_TURNING_ON) { + mState = STATE_WAITING_FOR_BLUETOOTH; + // Wait for bluetooth to fully come on. + return; + } + + if (btState != BluetoothAdapter.STATE_ON) { + mState = STATE_WAITING_FOR_BLUETOOTH; + showBluetoothDialog(); + return; + } + + CachedBluetoothDevice device = getPairedKeyboard(); + if (mState == STATE_WAITING_FOR_TABLET_MODE_EXIT || mState == STATE_WAITING_FOR_BLUETOOTH) { + if (device != null) { + // If we're just coming out of tablet mode or BT just turned on, + // then we want to go ahead and automatically connect to the + // keyboard. We want to avoid this in other cases because we might + // be spuriously called after the user has manually disconnected + // the keyboard, meaning we shouldn't try to automtically connect + // it again. + mState = STATE_PAIRED; + device.connect(false); + return; + } + mCachedDeviceManager.clearNonBondedDevices(); + } + + device = getDiscoveredKeyboard(); + if (device != null) { + mState = STATE_PAIRING; + device.startPairing(); + } else { + mState = STATE_WAITING_FOR_DEVICE_DISCOVERY; + startScanning(); + } + } + + // Should only be called on the handler thread + public void onBootCompletedInternal() { + mBootCompleted = true; + mBootCompletedTime = SystemClock.uptimeMillis(); + if (mState == STATE_WAITING_FOR_BOOT_COMPLETED) { + processKeyboardState(); + } + } + + // Should only be called on the handler thread + private void showBluetoothDialog() { + if (isUserSetupComplete()) { + long now = SystemClock.uptimeMillis(); + long earliestDialogTime = mBootCompletedTime + BLUETOOTH_START_DELAY_MILLIS; + if (earliestDialogTime < now) { + mUIHandler.sendEmptyMessage(MSG_SHOW_BLUETOOTH_DIALOG); + } else { + mHandler.sendEmptyMessageAtTime(MSG_PROCESS_KEYBOARD_STATE, earliestDialogTime); + } + } else { + // If we're in setup wizard and the keyboard is docked, just automatically enable BT. + mLocalBluetoothAdapter.enable(); + } + } + + private boolean isUserSetupComplete() { + ContentResolver resolver = mContext.getContentResolver(); + return Secure.getIntForUser( + resolver, Secure.USER_SETUP_COMPLETE, 0, UserHandle.USER_CURRENT) != 0; + } + + private CachedBluetoothDevice getPairedKeyboard() { + Set<BluetoothDevice> devices = mLocalBluetoothAdapter.getBondedDevices(); + for (BluetoothDevice d : devices) { + if (mKeyboardName.equals(d.getName())) { + return getCachedBluetoothDevice(d); + } + } + return null; + } + + private CachedBluetoothDevice getDiscoveredKeyboard() { + Collection<CachedBluetoothDevice> devices = mCachedDeviceManager.getCachedDevicesCopy(); + for (CachedBluetoothDevice d : devices) { + if (d.getName().equals(mKeyboardName)) { + return d; + } + } + return null; + } + + + private CachedBluetoothDevice getCachedBluetoothDevice(BluetoothDevice d) { + CachedBluetoothDevice cachedDevice = mCachedDeviceManager.findDevice(d); + if (cachedDevice == null) { + cachedDevice = mCachedDeviceManager.addDevice( + mLocalBluetoothAdapter, mProfileManager, d); + } + return cachedDevice; + } + + private void startScanning() { + BluetoothLeScanner scanner = mLocalBluetoothAdapter.getBluetoothLeScanner(); + ScanFilter filter = (new ScanFilter.Builder()).setDeviceName(mKeyboardName).build(); + ScanSettings settings = (new ScanSettings.Builder()) + .setCallbackType(ScanSettings.CALLBACK_TYPE_ALL_MATCHES) + .setNumOfMatches(ScanSettings.MATCH_NUM_ONE_ADVERTISEMENT) + .setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY) + .setReportDelay(0) + .build(); + mScanCallback = new KeyboardScanCallback(); + scanner.startScan(Arrays.asList(filter), settings, mScanCallback); + } + + private void stopScanning() { + if (mScanCallback != null) { + mLocalBluetoothAdapter.getBluetoothLeScanner().stopScan(mScanCallback); + mScanCallback = null; + } + } + + // Should only be called on the handler thread + private void onDeviceAddedInternal(CachedBluetoothDevice d) { + if (mState == STATE_WAITING_FOR_DEVICE_DISCOVERY && d.getName().equals(mKeyboardName)) { + stopScanning(); + d.startPairing(); + mState = STATE_PAIRING; + } + } + + // Should only be called on the handler thread + private void onBluetoothStateChangedInternal(int bluetoothState) { + if (bluetoothState == BluetoothAdapter.STATE_ON && mState == STATE_WAITING_FOR_BLUETOOTH) { + processKeyboardState(); + } + } + + // Should only be called on the handler thread + private void onDeviceBondStateChangedInternal(CachedBluetoothDevice d, int bondState) { + if (d.getName().equals(mKeyboardName) && bondState == BluetoothDevice.BOND_BONDED) { + // We don't need to manually connect to the device here because it will automatically + // try to connect after it has been paired. + mState = STATE_PAIRED; + } + } + + // Should only be called on the handler thread + private void onBleScanFailedInternal() { + mScanCallback = null; + if (mState == STATE_WAITING_FOR_DEVICE_DISCOVERY) { + mState = STATE_DEVICE_NOT_FOUND; + } + } + + private final class KeyboardUIHandler extends Handler { + public KeyboardUIHandler() { + super(Looper.getMainLooper(), null, true /*async*/); + } + @Override + public void handleMessage(Message msg) { + switch(msg.what) { + case MSG_SHOW_BLUETOOTH_DIALOG: { + DialogInterface.OnClickListener listener = new BluetoothDialogClickListener(); + mDialog = new BluetoothDialog(mContext); + mDialog.setTitle(R.string.enable_bluetooth_title); + mDialog.setMessage(R.string.enable_bluetooth_message); + mDialog.setPositiveButton(R.string.enable_bluetooth_confirmation_ok, listener); + mDialog.setNegativeButton(android.R.string.cancel, listener); + mDialog.show(); + break; + } + case MSG_DISMISS_BLUETOOTH_DIALOG: { + if (mDialog != null) { + mDialog.dismiss(); + mDialog = null; + } + break; + } + } + } + } + + private final class KeyboardHandler extends Handler { + public KeyboardHandler(Looper looper) { + super(looper, null, true /*async*/); + } + + @Override + public void handleMessage(Message msg) { + switch(msg.what) { + case MSG_INIT: { + init(); + break; + } + case MSG_ON_BOOT_COMPLETED: { + onBootCompletedInternal(); + break; + } + case MSG_PROCESS_KEYBOARD_STATE: { + processKeyboardState(); + break; + } + case MSG_ENABLE_BLUETOOTH: { + boolean enable = msg.arg1 == 1; + if (enable) { + mLocalBluetoothAdapter.enable(); + } else { + mState = STATE_USER_CANCELLED; + } + } + case MSG_ON_BLUETOOTH_STATE_CHANGED: { + int bluetoothState = msg.arg1; + onBluetoothStateChangedInternal(bluetoothState); + break; + } + case MSG_ON_DEVICE_BOND_STATE_CHANGED: { + CachedBluetoothDevice d = (CachedBluetoothDevice)msg.obj; + int bondState = msg.arg1; + onDeviceBondStateChangedInternal(d, bondState); + break; + } + case MSG_ON_BLUETOOTH_DEVICE_ADDED: { + BluetoothDevice d = (BluetoothDevice)msg.obj; + CachedBluetoothDevice cachedDevice = getCachedBluetoothDevice(d); + onDeviceAddedInternal(cachedDevice); + break; + + } + case MSG_ON_BLE_SCAN_FAILED: { + onBleScanFailedInternal(); + break; + } + } + } + } + + private final class BluetoothDialogClickListener implements DialogInterface.OnClickListener { + @Override + public void onClick(DialogInterface dialog, int which) { + int enable = DialogInterface.BUTTON_POSITIVE == which ? 1 : 0; + mHandler.obtainMessage(MSG_ENABLE_BLUETOOTH, enable, 0).sendToTarget(); + mDialog = null; + } + } + + private final class KeyboardScanCallback extends ScanCallback { + + private boolean isDeviceDiscoverable(ScanResult result) { + final ScanRecord scanRecord = result.getScanRecord(); + final int flags = scanRecord.getAdvertiseFlags(); + final int BT_DISCOVERABLE_MASK = 0x03; + + return (flags & BT_DISCOVERABLE_MASK) != 0; + } + + @Override + public void onBatchScanResults(List<ScanResult> results) { + if (DEBUG) { + Slog.d(TAG, "onBatchScanResults(" + results.size() + ")"); + } + + BluetoothDevice bestDevice = null; + int bestRssi = Integer.MIN_VALUE; + + for (ScanResult result : results) { + if (DEBUG) { + Slog.d(TAG, "onBatchScanResults: considering " + result); + } + + if (isDeviceDiscoverable(result) && result.getRssi() > bestRssi) { + bestDevice = result.getDevice(); + bestRssi = result.getRssi(); + } + } + + if (bestDevice != null) { + mHandler.obtainMessage(MSG_ON_BLUETOOTH_DEVICE_ADDED, bestDevice).sendToTarget(); + } + } + + @Override + public void onScanFailed(int errorCode) { + if (DEBUG) { + Slog.d(TAG, "onScanFailed(" + errorCode + ")"); + } + mHandler.obtainMessage(MSG_ON_BLE_SCAN_FAILED).sendToTarget(); + } + + @Override + public void onScanResult(int callbackType, ScanResult result) { + if (DEBUG) { + Slog.d(TAG, "onScanResult(" + callbackType + ", " + result + ")"); + } + + if (isDeviceDiscoverable(result)) { + mHandler.obtainMessage(MSG_ON_BLUETOOTH_DEVICE_ADDED, + result.getDevice()).sendToTarget(); + } else if (DEBUG) { + Slog.d(TAG, "onScanResult: device " + result.getDevice() + + " is not discoverable, ignoring"); + } + } + } + + private final class BluetoothCallbackHandler implements BluetoothCallback { + @Override + public void onBluetoothStateChanged(int bluetoothState) { + mHandler.obtainMessage(MSG_ON_BLUETOOTH_STATE_CHANGED, + bluetoothState, 0).sendToTarget(); + } + + @Override + public void onDeviceBondStateChanged(CachedBluetoothDevice cachedDevice, int bondState) { + mHandler.obtainMessage(MSG_ON_DEVICE_BOND_STATE_CHANGED, + bondState, 0, cachedDevice).sendToTarget(); + } + + @Override + public void onDeviceAdded(CachedBluetoothDevice cachedDevice) { } + @Override + public void onDeviceDeleted(CachedBluetoothDevice cachedDevice) { } + @Override + public void onScanningStateChanged(boolean started) { } + @Override + public void onConnectionStateChanged(CachedBluetoothDevice cachedDevice, int state) { } + } + + private static String stateToString(int state) { + switch (state) { + case STATE_NOT_ENABLED: + return "STATE_NOT_ENABLED"; + case STATE_WAITING_FOR_BOOT_COMPLETED: + return "STATE_WAITING_FOR_BOOT_COMPLETED"; + case STATE_WAITING_FOR_TABLET_MODE_EXIT: + return "STATE_WAITING_FOR_TABLET_MODE_EXIT"; + case STATE_WAITING_FOR_DEVICE_DISCOVERY: + return "STATE_WAITING_FOR_DEVICE_DISCOVERY"; + case STATE_WAITING_FOR_BLUETOOTH: + return "STATE_WAITING_FOR_BLUETOOTH"; + case STATE_WAITING_FOR_STATE_PAIRED: + return "STATE_WAITING_FOR_STATE_PAIRED"; + case STATE_PAIRING: + return "STATE_PAIRING"; + case STATE_PAIRED: + return "STATE_PAIRED"; + case STATE_USER_CANCELLED: + return "STATE_USER_CANCELLED"; + case STATE_DEVICE_NOT_FOUND: + return "STATE_DEVICE_NOT_FOUND"; + case STATE_UNKNOWN: + default: + return "STATE_UNKNOWN"; + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/media/NotificationPlayer.java b/packages/SystemUI/src/com/android/systemui/media/NotificationPlayer.java index 803a014..728d558 100644 --- a/packages/SystemUI/src/com/android/systemui/media/NotificationPlayer.java +++ b/packages/SystemUI/src/com/android/systemui/media/NotificationPlayer.java @@ -21,6 +21,7 @@ import android.media.AudioAttributes; import android.media.AudioManager; import android.media.MediaPlayer; import android.media.MediaPlayer.OnCompletionListener; +import android.media.MediaPlayer.OnErrorListener; import android.net.Uri; import android.os.Looper; import android.os.PowerManager; @@ -36,7 +37,7 @@ import java.util.LinkedList; * - whenever audio is played, audio focus is requested, * - whenever audio playback is stopped or the playback completed, audio focus is abandoned. */ -public class NotificationPlayer implements OnCompletionListener { +public class NotificationPlayer implements OnCompletionListener, OnErrorListener { private static final int PLAY = 1; private static final int STOP = 2; private static final boolean mDebug = false; @@ -112,6 +113,7 @@ public class NotificationPlayer implements OnCompletionListener { // done playing. This class should be modified to use a single thread, on which // command are issued, and on which it receives the completion callbacks. player.setOnCompletionListener(NotificationPlayer.this); + player.setOnErrorListener(NotificationPlayer.this); player.start(); if (mPlayer != null) { mPlayer.release(); @@ -245,6 +247,13 @@ public class NotificationPlayer implements OnCompletionListener { } } + public boolean onError(MediaPlayer mp, int what, int extra) { + Log.e(mTag, "error " + what + " (extra=" + extra + ") playing notification"); + // error happened, handle it just like a completion + onCompletion(mp); + return true; + } + private String mTag; private CmdThread mThread; private CreationAndCompletionThread mCompletionThread; diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTile.java b/packages/SystemUI/src/com/android/systemui/qs/QSTile.java index 9e98ea3..7d7e516 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSTile.java @@ -16,6 +16,7 @@ package com.android.systemui.qs; +import android.app.PendingIntent; import android.content.Context; import android.content.Intent; import android.graphics.drawable.Animatable; @@ -326,6 +327,7 @@ 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(); diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/IntentTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/IntentTile.java index 3d0dc7b..c7f2284 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/IntentTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/IntentTile.java @@ -99,7 +99,7 @@ public class IntentTile extends QSTile<QSTile.State> { try { if (pi != null) { if (pi.isActivity()) { - getHost().startActivityDismissingKeyguard(pi.getIntent()); + getHost().startActivityDismissingKeyguard(pi); } else { pi.send(); } diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java index 2e0b80a..be618e2 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java @@ -162,6 +162,14 @@ class TaskStackViewTouchHandler implements SwipeHelper.Callback { mVelocityTracker.addMovement(createMotionEventForStackScroll(ev)); break; } + case MotionEvent.ACTION_POINTER_DOWN: { + final int index = ev.getActionIndex(); + mActivePointerId = ev.getPointerId(index); + mLastMotionX = (int) ev.getX(index); + mLastMotionY = (int) ev.getY(index); + mLastP = mSv.mLayoutAlgorithm.screenYToCurveProgress(mLastMotionY); + break; + } case MotionEvent.ACTION_MOVE: { if (mActivePointerId == INACTIVE_POINTER_ID) break; @@ -187,6 +195,20 @@ class TaskStackViewTouchHandler implements SwipeHelper.Callback { mLastP = mSv.mLayoutAlgorithm.screenYToCurveProgress(mLastMotionY); break; } + case MotionEvent.ACTION_POINTER_UP: { + int pointerIndex = ev.getActionIndex(); + int pointerId = ev.getPointerId(pointerIndex); + if (pointerId == mActivePointerId) { + // Select a new active pointer id and reset the motion state + final int newPointerIndex = (pointerIndex == 0) ? 1 : 0; + mActivePointerId = ev.getPointerId(newPointerIndex); + mLastMotionX = (int) ev.getX(newPointerIndex); + mLastMotionY = (int) ev.getY(newPointerIndex); + mLastP = mSv.mLayoutAlgorithm.screenYToCurveProgress(mLastMotionY); + mVelocityTracker.clear(); + } + break; + } case MotionEvent.ACTION_CANCEL: case MotionEvent.ACTION_UP: { // Animate the scroll back if we've cancelled diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java index 32da495..5213857 100755 --- a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java @@ -1514,6 +1514,59 @@ public abstract class BaseStatusBar extends SystemUI implements return true; } + public void startPendingIntentDismissingKeyguard(final PendingIntent intent) { + if (!isDeviceProvisioned()) return; + + final boolean keyguardShowing = mStatusBarKeyguardViewManager.isShowing(); + final boolean afterKeyguardGone = intent.isActivity() + && PreviewInflater.wouldLaunchResolverActivity(mContext, intent.getIntent(), + mCurrentUserId); + dismissKeyguardThenExecute(new OnDismissAction() { + public boolean onDismiss() { + new Thread() { + @Override + public void run() { + try { + if (keyguardShowing && !afterKeyguardGone) { + ActivityManagerNative.getDefault() + .keyguardWaitingForActivityDrawn(); + } + + // The intent we are sending is for the application, which + // won't have permission to immediately start an activity after + // the user switches to home. We know it is safe to do at this + // point, so make sure new activity switches are now allowed. + ActivityManagerNative.getDefault().resumeAppSwitches(); + } catch (RemoteException e) { + } + + try { + intent.send(); + } catch (PendingIntent.CanceledException e) { + // the stack trace isn't very helpful here. + // Just log the exception message. + Log.w(TAG, "Sending intent failed: " + e); + + // TODO: Dismiss Keyguard. + } + if (intent.isActivity()) { + mAssistManager.hideAssist(); + overrideActivityPendingAppTransition(keyguardShowing + && !afterKeyguardGone); + } + } + }.start(); + + // close the shade if it was open + animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL, + true /* force */, true /* delayed */); + visibilityChanged(false); + + return true; + } + }, afterKeyguardGone); + } + private final class NotificationClicker implements View.OnClickListener { public void onClick(final View v) { if (!(v instanceof ExpandableNotificationRow)) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java index b7fe261..c964ca7 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java @@ -32,6 +32,7 @@ import android.view.View; import android.view.ViewStub; import android.view.accessibility.AccessibilityEvent; import android.view.animation.LinearInterpolator; +import android.widget.Chronometer; import android.widget.ImageView; import com.android.systemui.R; @@ -88,6 +89,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView { private NotificationGuts mGuts; private StatusBarNotification mStatusBarNotification; private boolean mIsHeadsUp; + private boolean mLastChronometerRunning = true; private View mExpandButton; private View mExpandButtonDivider; private ViewStub mExpandButtonStub; @@ -294,6 +296,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView { */ public void setPinned(boolean pinned) { mIsPinned = pinned; + setChronometerRunning(mLastChronometerRunning); } public boolean isPinned() { @@ -319,6 +322,41 @@ public class ExpandableNotificationRow extends ActivatableNotificationView { return mJustClicked; } + public void setChronometerRunning(boolean running) { + mLastChronometerRunning = running; + setChronometerRunning(running, mPrivateLayout); + setChronometerRunning(running, mPublicLayout); + if (mChildrenContainer != null) { + List<ExpandableNotificationRow> notificationChildren = + mChildrenContainer.getNotificationChildren(); + for (int i = 0; i < notificationChildren.size(); i++) { + ExpandableNotificationRow child = notificationChildren.get(i); + child.setChronometerRunning(running); + } + } + } + + private void setChronometerRunning(boolean running, NotificationContentView layout) { + if (layout != null) { + running = running || isPinned(); + View contractedChild = layout.getContractedChild(); + View expandedChild = layout.getExpandedChild(); + View headsUpChild = layout.getHeadsUpChild(); + setChronometerRunningForChild(running, contractedChild); + setChronometerRunningForChild(running, expandedChild); + setChronometerRunningForChild(running, headsUpChild); + } + } + + private void setChronometerRunningForChild(boolean running, View child) { + if (child != null) { + View chronometer = child.findViewById(com.android.internal.R.id.chronometer); + if (chronometer instanceof Chronometer) { + ((Chronometer) chronometer).setStarted(running); + } + } + } + public interface ExpansionLogger { public void logNotificationExpansion(String key, boolean userAction, boolean expanded); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java index 50d274d..fd84345 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java @@ -200,12 +200,12 @@ public class KeyguardIndicationController { switch (mChargingSpeed) { case KeyguardUpdateMonitor.BatteryStatus.CHARGING_FAST: chargingId = hasChargingTime - ? R.string.keyguard_indication_charging_time_fast_if_translated + ? R.string.keyguard_indication_charging_time_fast : R.string.keyguard_plugged_in_charging_fast; break; case KeyguardUpdateMonitor.BatteryStatus.CHARGING_SLOWLY: chargingId = hasChargingTime - ? R.string.keyguard_indication_charging_time_slowly_if_translated + ? R.string.keyguard_indication_charging_time_slowly : R.string.keyguard_plugged_in_charging_slowly; break; default: diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarter.java index 9ef320b..8f689c6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarter.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarter.java @@ -16,6 +16,7 @@ package com.android.systemui.statusbar.phone; +import android.app.PendingIntent; import android.content.Intent; /** @@ -24,6 +25,7 @@ import android.content.Intent; * Keyguard. */ public interface ActivityStarter { + void startPendingIntentDismissingKeyguard(PendingIntent intent); void startActivity(Intent intent, boolean dismissShade); void startActivity(Intent intent, boolean dismissShade, Callback callback); void preventNextAnimation(); 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 64f1eb3..9a8fa98 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java @@ -139,7 +139,6 @@ public abstract class PanelView extends FrameLayout { }; protected void onExpandingFinished() { - endClosing(); mBar.onExpandingFinished(); } @@ -154,6 +153,7 @@ public abstract class PanelView extends FrameLayout { } protected final void notifyExpandingFinished() { + endClosing(); if (mExpanding) { mExpanding = false; onExpandingFinished(); 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 9ea8880..6de6348 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java @@ -3934,6 +3934,15 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, || (mNavigationBarView != null && mNavigationBarView.isInEditMode()); } + public void postStartActivityDismissingKeyguard(final PendingIntent intent) { + mHandler.post(new Runnable() { + @Override + public void run() { + startPendingIntentDismissingKeyguard(intent); + } + }); + } + public void postStartActivityDismissingKeyguard(final Intent intent, int delay) { mHandler.postDelayed(new Runnable() { @Override 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 f44b6e5..adba01f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java @@ -118,6 +118,14 @@ public class PhoneStatusBarPolicy implements Callback { } }; + private Runnable mRemoveCastIconRunnable = new Runnable() { + @Override + public void run() { + if (DEBUG) Log.v(TAG, "updateCast: hiding icon NOW"); + mService.setIconVisibility(SLOT_CAST, false); + } + }; + public PhoneStatusBarPolicy(Context context, CastController cast, HotspotController hotspot, UserInfoController userInfoController, BluetoothController bluetooth, SuController su) { mContext = context; @@ -361,11 +369,17 @@ public class PhoneStatusBarPolicy implements Callback { } } if (DEBUG) Log.v(TAG, "updateCast: isCasting: " + isCasting); + mHandler.removeCallbacks(mRemoveCastIconRunnable); if (isCasting) { mService.setIcon(SLOT_CAST, R.drawable.stat_sys_cast, 0, mContext.getString(R.string.accessibility_casting)); + mService.setIconVisibility(SLOT_CAST, true); + } else { + // don't turn off the screen-record icon for a few seconds, just to make sure the user + // has seen it + if (DEBUG) Log.v(TAG, "updateCast: hiding icon in 3 sec..."); + mHandler.postDelayed(mRemoveCastIconRunnable, 3000); } - mService.setIconVisibility(SLOT_CAST, isCasting); } private void profileChanged(int userId) { 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 5828e70..b6bb995 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QSTileHost.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QSTileHost.java @@ -16,8 +16,12 @@ package com.android.systemui.statusbar.phone; +<<<<<<< HEAD import android.app.ActivityManager; import android.content.ComponentName; +======= +import android.app.PendingIntent; +>>>>>>> 888ef59df9262e1308d383fb56b8d077fb4170d7 import android.content.Context; import android.content.Intent; import android.content.res.Resources; @@ -182,11 +186,16 @@ public class QSTileHost implements QSTile.Host, Tunable { } @Override +<<<<<<< HEAD public void removeCustomTile(StatusBarPanelCustomTile customTile) { if (mCustomTileListenerService != null) { mCustomTileListenerService.removeCustomTile(customTile.getPackage(), customTile.getTag(), customTile.getId()); } +======= + public void startActivityDismissingKeyguard(PendingIntent intent) { + mStatusBar.postStartActivityDismissingKeyguard(intent); +>>>>>>> 888ef59df9262e1308d383fb56b8d077fb4170d7 } @Override 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 3e5152e..6624b30 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeaderView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeaderView.java @@ -571,8 +571,8 @@ public class StatusBarHeaderView extends RelativeLayout implements View.OnClickL startBatteryActivity(); } else if (v == mAlarmStatus && mNextAlarm != null) { PendingIntent showIntent = mNextAlarm.getShowIntent(); - if (showIntent != null && showIntent.isActivity()) { - mActivityStarter.startActivity(showIntent.getIntent(), true /* dismissShade */); + if (showIntent != null) { + mActivityStarter.startPendingIntentDismissingKeyguard(showIntent); } } else if (v == mClock) { startClockActivity(); 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 ed9b123..4a95d3a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java @@ -169,7 +169,6 @@ public class HeadsUpManager implements ViewTreeObserver.OnComputeInternalInsetsL */ public void showNotification(NotificationData.Entry headsUp) { if (DEBUG) Log.v(TAG, "showNotification"); - MetricsLogger.count(mContext, "note_peek", 1); addHeadsUpEntry(headsUp); updateNotification(headsUp, true); headsUp.setInterruption(); @@ -246,6 +245,9 @@ public class HeadsUpManager implements ViewTreeObserver.OnComputeInternalInsetsL return; } mHasPinnedNotification = hasPinnedNotification; + if (mHasPinnedNotification) { + MetricsLogger.count(mContext, "note_peek", 1); + } updateTouchableRegionListener(); for (OnHeadsUpChangedListener listener : mListeners) { listener.onHeadsUpPinnedModeChanged(hasPinnedNotification); @@ -580,6 +582,13 @@ public class HeadsUpManager implements ViewTreeObserver.OnComputeInternalInsetsL @Override public int compareTo(HeadsUpEntry o) { + boolean selfFullscreen = hasFullScreenIntent(entry); + boolean otherFullscreen = hasFullScreenIntent(o.entry); + if (selfFullscreen && !otherFullscreen) { + return -1; + } else if (!selfFullscreen && otherFullscreen) { + return 1; + } return postTime < o.postTime ? 1 : postTime == o.postTime ? entry.key.compareTo(o.entry.key) : -1; 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 ad0a3cc..f6a4566 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonView.java @@ -21,12 +21,14 @@ 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; import android.media.AudioManager; import android.os.Bundle; import android.os.SystemClock; import android.util.AttributeSet; +import android.util.TypedValue; import android.view.HapticFeedbackConstants; import android.view.InputDevice; import android.view.KeyCharacterMap; @@ -52,6 +54,7 @@ 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; @@ -100,8 +103,14 @@ public class KeyButtonView extends ImageView { mSupportsLongpress = a.getBoolean(R.styleable.KeyButtonView_keyRepeat, true); + TypedValue value = new TypedValue(); + if (a.getValue(R.styleable.KeyButtonView_android_contentDescription, value)) { + mContentDescriptionRes = value.resourceId; + } + a.recycle(); + setClickable(true); mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop(); mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); @@ -110,6 +119,15 @@ public class KeyButtonView extends ImageView { } @Override + protected void onConfigurationChanged(Configuration newConfig) { + super.onConfigurationChanged(newConfig); + + if (mContentDescriptionRes != 0) { + setContentDescription(mContext.getString(mContentDescriptionRes)); + } + } + + @Override public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { super.onInitializeAccessibilityNodeInfo(info); if (mCode != 0) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeController.java index b2df40a..0e91b0b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeController.java @@ -27,7 +27,6 @@ public interface ZenModeController { void removeCallback(Callback callback); void setZen(int zen, Uri conditionId, String reason); int getZen(); - void requestConditions(boolean request); ZenRule getManualRule(); ZenModeConfig getConfig(); long getNextAlarm(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java index c07f1a8..96efea1 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java @@ -121,15 +121,6 @@ public class ZenModeControllerImpl implements ZenModeController { } @Override - public void requestConditions(boolean request) { - mRequesting = request; - mNoMan.requestZenModeConditions(mListener, request ? Condition.FLAG_RELEVANT_NOW : 0); - if (!mRequesting) { - mConditions.clear(); - } - } - - @Override public ZenRule getManualRule() { return mConfig == null ? null : mConfig.manualRule; } 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 772fab6..4ec8b43 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java @@ -1790,6 +1790,7 @@ public class NotificationStackScrollLayout extends ViewGroup ((ExpandableView) child).setOnHeightChangedListener(this); generateAddAnimation(child, false /* fromMoreCard */); updateAnimationState(child); + updateChronometerForChild(child); if (canChildBeDismissed(child)) { // Make sure the dismissButton is visible and not in the animated state. // We need to do this to avoid a race where a clearable notification is added after the @@ -2299,6 +2300,21 @@ public class NotificationStackScrollLayout extends ViewGroup mStackScrollAlgorithm.setIsExpanded(isExpanded); if (changed) { updateNotificationAnimationStates(); + updateChronometers(); + } + } + + private void updateChronometers() { + int childCount = getChildCount(); + for (int i = 0; i < childCount; i++) { + updateChronometerForChild(getChildAt(i)); + } + } + + private void updateChronometerForChild(View child) { + if (child instanceof ExpandableNotificationRow) { + ExpandableNotificationRow row = (ExpandableNotificationRow) child; + row.setChronometerRunning(mIsExpanded); } } @@ -2321,6 +2337,7 @@ public class NotificationStackScrollLayout extends ViewGroup } mStackScrollAlgorithm.onReset(view); updateAnimationState(view); + updateChronometerForChild(view); } private void updateScrollPositionOnExpandInBottom(ExpandableView view) { diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialog.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialog.java index 7836411..e9f1095 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialog.java +++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialog.java @@ -104,6 +104,7 @@ 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; @@ -135,6 +136,7 @@ public class VolumeDialog { mCallback = callback; mSpTexts = new SpTexts(mContext); mKeyguard = (KeyguardManager) context.getSystemService(Context.KEYGUARD_SERVICE); + mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); mDialog = new CustomDialog(mContext); @@ -636,7 +638,8 @@ public class VolumeDialog { 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; + final boolean visible = mState.zenMode != Global.ZEN_MODE_OFF + && mAudioManager.isStreamAffectedByRingerMode(mActiveStream); if (wasVisible != visible && !visible) { prepareForCollapse(); } diff --git a/packages/SystemUI/src/com/android/systemui/volume/ZenModePanel.java b/packages/SystemUI/src/com/android/systemui/volume/ZenModePanel.java index 3c9a7fc..3337714 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/ZenModePanel.java +++ b/packages/SystemUI/src/com/android/systemui/volume/ZenModePanel.java @@ -1,4 +1,4 @@ -/* +/** * Copyright (C) 2014 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -58,6 +58,8 @@ import com.android.systemui.statusbar.policy.ZenModeController; import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.Arrays; +import java.util.Calendar; +import java.util.GregorianCalendar; import java.util.Locale; import java.util.Objects; @@ -76,6 +78,8 @@ public class ZenModePanel extends LinearLayout { private static final int DEFAULT_BUCKET_INDEX = Arrays.binarySearch(MINUTE_BUCKETS, 60); private static final int FOREVER_CONDITION_INDEX = 0; private static final int COUNTDOWN_CONDITION_INDEX = 1; + private static final int COUNTDOWN_ALARM_CONDITION_INDEX = 2; + private static final int COUNTDOWN_CONDITION_COUNT = 2; public static final Intent ZEN_SETTINGS = new Intent(Settings.ACTION_ZEN_MODE_SETTINGS); @@ -86,7 +90,6 @@ public class ZenModePanel extends LinearLayout { private final LayoutInflater mInflater; private final H mHandler = new H(); private final ZenPrefs mPrefs; - private final IconPulser mIconPulser; private final TransitionHelper mTransitionHelper = new TransitionHelper(); private final Uri mForeverId; private final SpTexts mSpTexts; @@ -104,9 +107,6 @@ public class ZenModePanel extends LinearLayout { private Callback mCallback; private ZenModeController mController; private boolean mCountdownConditionSupported; - private int mMaxConditions; - private int mMaxOptionalConditions; - private int mFirstConditionIndex; private boolean mRequestingConditions; private Condition mExitCondition; private int mBucketIndex = -1; @@ -125,7 +125,6 @@ public class ZenModePanel extends LinearLayout { mContext = context; mPrefs = new ZenPrefs(); mInflater = LayoutInflater.from(mContext.getApplicationContext()); - mIconPulser = new IconPulser(mContext); mForeverId = Condition.newId(mContext).appendPath("forever").build(); mSpTexts = new SpTexts(mContext); mVoiceCapable = Util.isVoiceCapable(mContext); @@ -135,7 +134,6 @@ public class ZenModePanel extends LinearLayout { public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { pw.println("ZenModePanel state:"); pw.print(" mCountdownConditionSupported="); pw.println(mCountdownConditionSupported); - pw.print(" mMaxConditions="); pw.println(mMaxConditions); pw.print(" mRequestingConditions="); pw.println(mRequestingConditions); pw.print(" mAttached="); pw.println(mAttached); pw.print(" mHidden="); pw.println(mHidden); @@ -286,14 +284,6 @@ public class ZenModePanel extends LinearLayout { if (mRequestingConditions == requesting) return; if (DEBUG) Log.d(mTag, "setRequestingConditions " + requesting); mRequestingConditions = requesting; - if (mController != null) { - AsyncTask.execute(new Runnable() { - @Override - public void run() { - mController.requestConditions(requesting); - } - }); - } if (mRequestingConditions) { mTimeCondition = parseExistingTimeCondition(mContext, mExitCondition); if (mTimeCondition != null) { @@ -304,6 +294,7 @@ public class ZenModePanel extends LinearLayout { MINUTE_BUCKETS[mBucketIndex], ActivityManager.getCurrentUser()); } if (DEBUG) Log.d(mTag, "Initial bucket index: " + mBucketIndex); + mConditions = null; // reset conditions handleUpdateConditions(); } else { @@ -314,13 +305,9 @@ public class ZenModePanel extends LinearLayout { public void init(ZenModeController controller) { mController = controller; mCountdownConditionSupported = mController.isCountdownConditionSupported(); - final int countdownDelta = mCountdownConditionSupported ? 1 : 0; - mFirstConditionIndex = COUNTDOWN_CONDITION_INDEX + countdownDelta; + final int countdownDelta = mCountdownConditionSupported ? COUNTDOWN_CONDITION_COUNT : 0; final int minConditions = 1 /*forever*/ + countdownDelta; - mMaxConditions = MathUtils.constrain(mContext.getResources() - .getInteger(R.integer.zen_mode_max_conditions), minConditions, 100); - mMaxOptionalConditions = mMaxConditions - minConditions; - for (int i = 0; i < mMaxConditions; i++) { + for (int i = 0; i < minConditions; i++) { mZenConditions.addView(mInflater.inflate(R.layout.zen_mode_condition, this, false)); } mSessionZen = getSelectedZen(-1); @@ -357,28 +344,10 @@ public class ZenModePanel extends LinearLayout { return condition == null ? null : condition.copy(); } - public static String getExitConditionText(Context context, Condition exitCondition) { - if (exitCondition == null) { - return foreverSummary(context); - } else if (isCountdown(exitCondition)) { - final Condition condition = parseExistingTimeCondition(context, exitCondition); - return condition != null ? condition.summary : foreverSummary(context); - } else { - return exitCondition.summary; - } - } - public void setCallback(Callback callback) { mCallback = callback; } - public void showSilentHint() { - if (DEBUG) Log.d(mTag, "showSilentHint"); - if (mZenButtons == null || mZenButtons.getChildCount() == 0) return; - final View noneButton = mZenButtons.getChildAt(0); - mIconPulser.start(noneButton); - } - private void handleUpdateManualRule(ZenRule rule) { final int zen = rule != null ? rule.zenMode : Global.ZEN_MODE_OFF; handleUpdateZen(zen); @@ -410,7 +379,7 @@ public class ZenModePanel extends LinearLayout { final ConditionTag tag = getConditionTagAt(i); if (tag != null) { if (sameConditionId(tag.condition, mExitCondition)) { - bind(exitCondition, mZenConditions.getChildAt(i)); + bind(exitCondition, mZenConditions.getChildAt(i), i); } } } @@ -495,64 +464,32 @@ public class ZenModePanel extends LinearLayout { final long span = time - now; if (span <= 0 || span > MAX_BUCKET_MINUTES * MINUTES_MS) return null; return ZenModeConfig.toTimeCondition(context, - time, Math.round(span / (float) MINUTES_MS), now, ActivityManager.getCurrentUser(), + time, Math.round(span / (float) MINUTES_MS), ActivityManager.getCurrentUser(), false /*shortVersion*/); } - private void handleUpdateConditions(Condition[] conditions) { - conditions = trimConditions(conditions); - if (Arrays.equals(conditions, mConditions)) { - final int count = mConditions == null ? 0 : mConditions.length; - if (DEBUG) Log.d(mTag, "handleUpdateConditions unchanged conditionCount=" + count); - return; - } - mConditions = conditions; - handleUpdateConditions(); - } - - private Condition[] trimConditions(Condition[] conditions) { - if (conditions == null || conditions.length <= mMaxOptionalConditions) { - // no need to trim - return conditions; - } - // look for current exit condition, ensure it is included if found - int found = -1; - for (int i = 0; i < conditions.length; i++) { - final Condition c = conditions[i]; - if (mSessionExitCondition != null && sameConditionId(mSessionExitCondition, c)) { - found = i; - break; - } - } - final Condition[] rt = Arrays.copyOf(conditions, mMaxOptionalConditions); - if (found >= mMaxOptionalConditions) { - // found after the first N, promote to the end of the first N - rt[mMaxOptionalConditions - 1] = conditions[found]; - } - return rt; - } - private void handleUpdateConditions() { if (mTransitionHelper.isTransitioning()) { - mTransitionHelper.pendingUpdateConditions(); return; } final int conditionCount = mConditions == null ? 0 : mConditions.length; if (DEBUG) Log.d(mTag, "handleUpdateConditions conditionCount=" + conditionCount); // forever - bind(forever(), mZenConditions.getChildAt(FOREVER_CONDITION_INDEX)); + bind(forever(), mZenConditions.getChildAt(FOREVER_CONDITION_INDEX), + FOREVER_CONDITION_INDEX); // countdown if (mCountdownConditionSupported && mTimeCondition != null) { - bind(mTimeCondition, mZenConditions.getChildAt(COUNTDOWN_CONDITION_INDEX)); - } - // provider conditions - for (int i = 0; i < conditionCount; i++) { - bind(mConditions[i], mZenConditions.getChildAt(mFirstConditionIndex + i)); - } - // hide the rest - for (int i = mZenConditions.getChildCount() - 1; i > mFirstConditionIndex + conditionCount; - i--) { - mZenConditions.getChildAt(i).setVisibility(GONE); + bind(mTimeCondition, mZenConditions.getChildAt(COUNTDOWN_CONDITION_INDEX), + COUNTDOWN_CONDITION_INDEX); + } + // countdown until alarm + if (mCountdownConditionSupported) { + Condition nextAlarmCondition = getTimeUntilNextAlarmCondition(); + if (nextAlarmCondition != null) { + bind(nextAlarmCondition, + mZenConditions.getChildAt(COUNTDOWN_ALARM_CONDITION_INDEX), + COUNTDOWN_ALARM_CONDITION_INDEX); + } } // ensure something is selected if (mExpanded && isShown()) { @@ -569,6 +506,33 @@ public class ZenModePanel extends LinearLayout { return context.getString(com.android.internal.R.string.zen_mode_forever); } + // Returns a time condition if the next alarm is within the next week. + private Condition getTimeUntilNextAlarmCondition() { + GregorianCalendar weekRange = new GregorianCalendar(); + final long now = weekRange.getTimeInMillis(); + setToMidnight(weekRange); + weekRange.roll(Calendar.DATE, 6); + final long nextAlarmMs = mController.getNextAlarm(); + if (nextAlarmMs > 0) { + GregorianCalendar nextAlarm = new GregorianCalendar(); + nextAlarm.setTimeInMillis(nextAlarmMs); + setToMidnight(nextAlarm); + + if (weekRange.compareTo(nextAlarm) >= 0) { + return ZenModeConfig.toNextAlarmCondition(mContext, now, nextAlarmMs, + ActivityManager.getCurrentUser()); + } + } + return null; + } + + private void setToMidnight(Calendar calendar) { + calendar.set(Calendar.HOUR_OF_DAY, 0); + calendar.set(Calendar.MINUTE, 0); + calendar.set(Calendar.SECOND, 0); + calendar.set(Calendar.MILLISECOND, 0); + } + private ConditionTag getConditionTagAt(int index) { return (ConditionTag) mZenConditions.getChildAt(index).getTag(); } @@ -610,7 +574,8 @@ public class ZenModePanel extends LinearLayout { mTimeCondition = ZenModeConfig.toTimeCondition(mContext, MINUTE_BUCKETS[favoriteIndex], ActivityManager.getCurrentUser()); mBucketIndex = favoriteIndex; - bind(mTimeCondition, mZenConditions.getChildAt(COUNTDOWN_CONDITION_INDEX)); + bind(mTimeCondition, mZenConditions.getChildAt(COUNTDOWN_CONDITION_INDEX), + COUNTDOWN_CONDITION_INDEX); getConditionTagAt(COUNTDOWN_CONDITION_INDEX).rb.setChecked(true); } } @@ -623,7 +588,7 @@ public class ZenModePanel extends LinearLayout { return c != null && mForeverId.equals(c.id); } - private void bind(final Condition condition, final View row) { + private void bind(final Condition condition, final View row, final int rowId) { if (condition == null) throw new IllegalArgumentException("condition must not be null"); final boolean enabled = condition.state == Condition.STATE_TRUE; final ConditionTag tag = @@ -640,8 +605,7 @@ public class ZenModePanel extends LinearLayout { tag.rb.setEnabled(enabled); final boolean checked = (mSessionExitCondition != null || mAttachedZen != Global.ZEN_MODE_OFF) - && (sameConditionId(mSessionExitCondition, tag.condition) - || isCountdown(mSessionExitCondition) && isCountdown(tag.condition)); + && (sameConditionId(mSessionExitCondition, tag.condition)); if (checked != tag.rb.isChecked()) { if (DEBUG) Log.d(mTag, "bind checked=" + checked + " condition=" + conditionId); tag.rb.setChecked(checked); @@ -692,7 +656,7 @@ public class ZenModePanel extends LinearLayout { button1.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { - onClickTimeButton(row, tag, false /*down*/); + onClickTimeButton(row, tag, false /*down*/, rowId); } }); @@ -700,7 +664,7 @@ public class ZenModePanel extends LinearLayout { button2.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { - onClickTimeButton(row, tag, true /*up*/); + onClickTimeButton(row, tag, true /*up*/, rowId); } }); tag.lines.setOnClickListener(new OnClickListener() { @@ -711,7 +675,7 @@ public class ZenModePanel extends LinearLayout { }); final long time = ZenModeConfig.tryParseCountdownConditionId(conditionId); - if (time > 0) { + if (rowId != COUNTDOWN_ALARM_CONDITION_INDEX && time > 0) { button1.setVisibility(VISIBLE); button2.setVisibility(VISIBLE); if (mBucketIndex > -1) { @@ -761,7 +725,7 @@ public class ZenModePanel extends LinearLayout { tag.line1.getText())); } - private void onClickTimeButton(View row, ConditionTag tag, boolean up) { + private void onClickTimeButton(View row, ConditionTag tag, boolean up, int rowId) { MetricsLogger.action(mContext, MetricsLogger.QS_DND_TIME, up); Condition newCondition = null; final int N = MINUTE_BUCKETS.length; @@ -777,7 +741,7 @@ public class ZenModePanel extends LinearLayout { if (up && bucketTime > time || !up && bucketTime < time) { mBucketIndex = j; newCondition = ZenModeConfig.toTimeCondition(mContext, - bucketTime, bucketMinutes, now, ActivityManager.getCurrentUser(), + bucketTime, bucketMinutes, ActivityManager.getCurrentUser(), false /*shortVersion*/); break; } @@ -794,7 +758,7 @@ public class ZenModePanel extends LinearLayout { MINUTE_BUCKETS[mBucketIndex], ActivityManager.getCurrentUser()); } mTimeCondition = newCondition; - bind(mTimeCondition, row); + bind(mTimeCondition, row, rowId); tag.rb.setChecked(true); select(mTimeCondition); announceConditionSelection(tag); @@ -838,18 +802,12 @@ public class ZenModePanel extends LinearLayout { private final ZenModeController.Callback mZenCallback = new ZenModeController.Callback() { @Override - public void onConditionsChanged(Condition[] conditions) { - mHandler.obtainMessage(H.UPDATE_CONDITIONS, conditions).sendToTarget(); - } - - @Override public void onManualRuleChanged(ZenRule rule) { mHandler.obtainMessage(H.MANUAL_RULE_CHANGED, rule).sendToTarget(); } }; private final class H extends Handler { - private static final int UPDATE_CONDITIONS = 1; private static final int MANUAL_RULE_CHANGED = 2; private static final int UPDATE_WIDGETS = 3; @@ -860,7 +818,6 @@ public class ZenModePanel extends LinearLayout { @Override public void handleMessage(Message msg) { switch (msg.what) { - case UPDATE_CONDITIONS: handleUpdateConditions((Condition[]) msg.obj); break; case MANUAL_RULE_CHANGED: handleUpdateManualRule((ZenRule) msg.obj); break; case UPDATE_WIDGETS: updateWidgets(); break; } @@ -1005,26 +962,20 @@ public class ZenModePanel extends LinearLayout { private final ArraySet<View> mTransitioningViews = new ArraySet<View>(); private boolean mTransitioning; - private boolean mPendingUpdateConditions; private boolean mPendingUpdateWidgets; public void clear() { mTransitioningViews.clear(); - mPendingUpdateConditions = mPendingUpdateWidgets = false; + mPendingUpdateWidgets = false; } public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { pw.println(" TransitionHelper state:"); - pw.print(" mPendingUpdateConditions="); pw.println(mPendingUpdateConditions); pw.print(" mPendingUpdateWidgets="); pw.println(mPendingUpdateWidgets); pw.print(" mTransitioning="); pw.println(mTransitioning); pw.print(" mTransitioningViews="); pw.println(mTransitioningViews); } - public void pendingUpdateConditions() { - mPendingUpdateConditions = true; - } - public void pendingUpdateWidgets() { mPendingUpdateWidgets = true; } @@ -1050,15 +1001,11 @@ public class ZenModePanel extends LinearLayout { @Override public void run() { if (DEBUG) Log.d(mTag, "TransitionHelper run" - + " mPendingUpdateWidgets=" + mPendingUpdateWidgets - + " mPendingUpdateConditions=" + mPendingUpdateConditions); + + " mPendingUpdateWidgets=" + mPendingUpdateWidgets); if (mPendingUpdateWidgets) { updateWidgets(); } - if (mPendingUpdateConditions) { - handleUpdateConditions(); - } - mPendingUpdateWidgets = mPendingUpdateConditions = false; + mPendingUpdateWidgets = false; } private void updateTransitioning() { @@ -1067,10 +1014,10 @@ public class ZenModePanel extends LinearLayout { mTransitioning = transitioning; if (DEBUG) Log.d(mTag, "TransitionHelper mTransitioning=" + mTransitioning); if (!mTransitioning) { - if (mPendingUpdateConditions || mPendingUpdateWidgets) { + if (mPendingUpdateWidgets) { mHandler.post(this); } else { - mPendingUpdateConditions = mPendingUpdateWidgets = false; + mPendingUpdateWidgets = false; } } } |