package me.barrasso.android.volume;
import android.annotation.TargetApi;
import android.app.Notification;
import android.app.PendingIntent;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.app.Service;
import android.accessibilityservice.AccessibilityServiceInfo;
import android.accessibilityservice.AccessibilityService;
import android.graphics.BitmapFactory;
import android.os.*;
import android.os.Process;
import android.preference.PreferenceManager;
import android.service.notification.NotificationListenerService;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
import android.view.accessibility.AccessibilityEvent;
import android.view.KeyEvent;
import android.content.SharedPreferences;
import com.levelup.logutils.FLog;
import com.squareup.otto.Produce;
import me.barrasso.android.volume.media.VolumePanelInfo;
import me.barrasso.android.volume.popup.FullscreenPopupWindow;
import me.barrasso.android.volume.popup.PopupWindow;
import me.barrasso.android.volume.popup.PopupWindowManager;
import me.barrasso.android.volume.popup.StatusBarVolumePanel;
import me.barrasso.android.volume.popup.VolumeBarPanel;
import me.barrasso.android.volume.popup.VolumePanel;
import me.barrasso.android.volume.utils.Constants;
import com.squareup.otto.MainThreadBus;
import java.util.HashSet;
import java.util.Set;
import me.barrasso.android.volume.utils.SettingsHelper;
import me.barrasso.android.volume.utils.Utils;
import static me.barrasso.android.volume.LogUtils.LOGE;
import static me.barrasso.android.volume.LogUtils.LOGI;
import static me.barrasso.android.volume.LogUtils.LOGV;
/**
* {@link AccessibilityService} meant to override the default behavior of
* the system volume buttons (up/ down), improving the interface and adding
* functionality (multi-stream control, media playback control, etc).
*/
@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2)
public final class VolumeAccessibilityService extends AccessibilityService
implements SharedPreferences.OnSharedPreferenceChangeListener {
// A note from com.android.server.accessibility.AccessibilityManagerService#notifyKeyEventLocked
// Now we are giving the key events to the last enabled
// service that can handle them Ideally, the user should
// make the call which service handles key events. However,
// only one service should handle key events to avoid user
// frustration when different behavior is observed from
// different combinations of enabled accessibility services.
public static final String TAG = LogUtils.makeLogTag(VolumeAccessibilityService.class);
/** @return True if {@link VolumeAccessibilityService} is running. */
public static boolean isEnabled(Context mContext) {
return Utils.isAccessibilityServiceEnabled(mContext, VolumeAccessibilityService.class);
}
// ========== Constants ==========
// Handler messages.
private static final int MESSAGE_START = 0x00000001;
private static final int MESSAGE_SHUTDOWN = 0x00000010;
private static final int MESSAGE_KEY_EVENT = 0x00000100;
private static final int MESSAGE_TOP_ACTIVITY = 0x00001000;
private static final int MESSAGE_TOP_PACKAGE = 0x00010000;
private static final int MESSAGE_FULLSCREEN = 0x00100000;
// com.android.internal.app.ChooserActivity
// com.android.systemui.recent.RecentsActivity
private static final String ANDROID = "android";
private static final String SYSTEMUI = "com.android.systemui";
private static final String KEYGUARD = "com.android.keyguard";
// private static final String GOOGLE_PLAY = "com.android.vending";
// private static final String REVIEW_ACTIVITY_1 = "com.google.android.finsky.activities.RateReviewActivity";
// private static final String REVIEW_ACTIVITY = "com.google.android.finsky.activities.ReviewsActivity";
// ========== Instance Variables ==========
protected PopupWindowManager pWindowManager;
protected VolumePanel mVolumePanel;
protected FullscreenPopupWindow mFullscreenWindow;
protected final AccessibilityServiceInfo mInfo = new AccessibilityServiceInfo();
protected String mCurrentActivityName = null;
protected String mCurrentActivityClass = null;
protected String mCurrentActivityPackage = null;
private String PREF_TIMEOUT;
private String PREF_MUSIC_APP;
private String PREF_MASTER_VOLUME;
private String PREF_RINGER_MODE;
private String PREF_LONG_PRESS_DOWN;
private String PREF_LONG_PRESS_UP;
private String PREF_FOREGROUND_COLOR;
private String PREF_BACKGROUND_COLOR;
private String PREF_SEEK;
private String PREF_NO_LONG_PRESS;
private String PREF_HIDE_FULLSCREEN;
private String PREF_HIDE_CAMERA;
private String PREF_BAR_HEIGHT;
private String PREF_STRETCH;
private String PREF_DEFAULT_STREAM;
private String PREF_LINK_NOTIF_RING;
private String PREF_TERTIARY_COLOR;
private String PREF_ALWAYS_EXPAND;
private String PREF_FIRST_REVEAL;
private final Set<String> blacklist = new HashSet<String>();
private int disabledButtons = KeyEvent.KEYCODE_UNKNOWN;
private SettingsHelper mPreferences;
private Context mContext;
private boolean isInfrastructureInitialized = false;
// ========== AccessibilityManager ==========
public static class TopApp {
public String mCurrentPackage;
public String mCurrentActivityClass;
}
@Produce
public TopApp produceTopApp() {
TopApp topApp = new TopApp();
topApp.mCurrentActivityClass = mCurrentActivityClass;
topApp.mCurrentPackage = mCurrentActivityPackage;
return topApp;
}
/** {@link Handler} for executing messages on the service main thread. */
private final Handler mHandler = new Handler() {
@Override
public void handleMessage(Message message) {
switch (message.what) {
// obj: KeyEvent; from onKey
case MESSAGE_KEY_EVENT:
final KeyEvent event = (KeyEvent) message.obj;
mVolumePanel.onKey(mVolumePanel.peekDecor(), event.getKeyCode(), event);
MainThreadBus.get().post(event);
break;
case MESSAGE_START:
initVolumePanel();
break;
case MESSAGE_SHUTDOWN:
// NOTE: do something async when we start/ stop?
break;
case MESSAGE_TOP_ACTIVITY:
case MESSAGE_TOP_PACKAGE:
TopApp app = produceTopApp();
if (null != mVolumePanel)
mVolumePanel.onTopAppChanged(app);
MainThreadBus.get().post(app);
if (message.what == MESSAGE_TOP_PACKAGE) {
if (blacklist.contains(mCurrentActivityPackage)) {
if (null != mVolumePanel) mVolumePanel.hide();
}
}
break;
// arg1: boolean/ int, 1 == true, 0 == false
case MESSAGE_FULLSCREEN:
boolean fullscreen = (message.arg1 == 1);
if (null != mVolumePanel)
mVolumePanel.onFullscreenChange(fullscreen);
break;
}
}
};
@Override
public void onAccessibilityEvent(AccessibilityEvent event) {
if (null == event) return; // WTF?
if (event.isPassword()) return; // Nope, don't do it!
LOGV(TAG, "onAccessibilityEvent(" + event.toString() + ')');
// Android SystemUI Package, has become focused.
if (SYSTEMUI.equals(event.getPackageName()) || ANDROID.equals(event.getPackageName()) &&
(event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED)) {
// NOTE: Android 5.0 introduces a Volume Panel that takes focus. In doing so,
// it also pretends to be the top activity (but never tells us when it goes away).
if (blacklist.contains(mCurrentActivityPackage)) return;
if (null != mVolumePanel) {
// Make sure we've waited several seconds before hiding.
if ((System.currentTimeMillis() - mLastNotificationEventTime) >
mVolumePanel.getAutoHideDuration()) {
LOGI(TAG, "Hiding panel from package: " + event.getPackageName());
mVolumePanel.hide();
mLastNotificationEventTime = 0;
}
}
}
// Get and store information about the last event, current window, etc.
switch (event.getEventType()) {
// Window state changes, new window, app, dialog, etc.
case AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED:
final String mOldActivityPackage = mCurrentActivityPackage;
final String mOldActivityClass = mCurrentActivityClass;
mCurrentActivityClass = String.valueOf(event.getClassName());
mCurrentActivityPackage = String.valueOf(event.getPackageName());
if (event.getText().size() > 0) {
mCurrentActivityName = String.valueOf(event.getText().get(0));
} else {
mCurrentActivityName = null;
}
// Has the top package changed?
if (!TextUtils.isEmpty(mCurrentActivityPackage) &&
!mCurrentActivityPackage.equals(mOldActivityPackage)) {
Message.obtain(mHandler, MESSAGE_TOP_PACKAGE, mCurrentActivityPackage).sendToTarget();
}
// Has the top activity changed?
if (!TextUtils.isEmpty(mCurrentActivityClass) &&
!mCurrentActivityClass.equals(mOldActivityClass)) {
Message.obtain(mHandler, MESSAGE_TOP_ACTIVITY, mCurrentActivityClass).sendToTarget();
}
break;
}
}
@Override
protected boolean onKeyEvent(KeyEvent event) {
if (null == event) return true; // WTF??
// Make sure we're not in the middle of a phone call.
if (null != mVolumePanel && mVolumePanel.getCallState() != TelephonyManager.CALL_STATE_IDLE)
return super.onKeyEvent(event);
final int flags = event.getFlags();
final int code = event.getKeyCode();
final boolean system = ((flags & KeyEvent.FLAG_FROM_SYSTEM) == KeyEvent.FLAG_FROM_SYSTEM);
// Specifically avoid software keys or "fake" hardware buttons.
if (((flags & KeyEvent.FLAG_SOFT_KEYBOARD) == KeyEvent.FLAG_SOFT_KEYBOARD) ||
((flags & KeyEvent.FLAG_VIRTUAL_HARD_KEY) == KeyEvent.FLAG_VIRTUAL_HARD_KEY)) {
return super.onKeyEvent(event);
} else {
// Specifically avoid handling certain keys. We never want to interfere
// with them or harm performance in any way.
switch (code) {
case KeyEvent.KEYCODE_BACK:
case KeyEvent.KEYCODE_HOME:
case KeyEvent.KEYCODE_MENU:
case KeyEvent.KEYCODE_POWER:
case KeyEvent.KEYCODE_SEARCH:
return super.onKeyEvent(event);
}
}
if (!system) return super.onKeyEvent(event);
LOGI(TAG, "--onKeyEvent(code=" + event.getKeyCode() + ", action=" + event.getAction() +
", topPackage=" + mCurrentActivityPackage + ", disabledButtons=" + disabledButtons + ')');
// Check if we're supposed to disable Noyze for a Blacklisted app.
if (blacklist.contains(mCurrentActivityPackage)) {
if (null != mVolumePanel) mVolumePanel.setEnabled(false);
return super.onKeyEvent(event);
} else {
// NOTE: we need a "safe" way to enable the volume panel that
// takes into consideration its previous state.
if (null != mVolumePanel) mVolumePanel.enable();
}
// If we're told to disable one or more of the volume buttons, do so (returning true consumes the event).
if (disabledButtons == code) return true;
// NOTE: KeyEvent#KEYCODE_VOLUME_DOWN + KeyEvent#KEYCODE_VOLUME_UP == KeyEvent_KEYCODE_U
final int upAndDown = (KeyEvent.KEYCODE_VOLUME_DOWN + KeyEvent.KEYCODE_VOLUME_UP); // 49
final int upSquared = KeyEvent.KEYCODE_VOLUME_UP * KeyEvent.KEYCODE_VOLUME_UP; // 576
if (disabledButtons == upAndDown && Utils.isVolumeKeyCode(upAndDown - code)) return true;
if (disabledButtons == upSquared && mVolumePanel != null && mVolumePanel.isLocked()) return true;
if (disabledButtons == upSquared && KEYGUARD.equals(mCurrentActivityPackage)) return true;
// Check if the volume panel has been disabled, or shouldn't handle this event (e.g. "safe volume").
if (null != mVolumePanel && mVolumePanel.isEnabled()) {
if (Utils.isVolumeKeyCode(code)) {
Message.obtain(mHandler, MESSAGE_KEY_EVENT, event).sendToTarget(); // Run asynchronously
return true;
}
}
return super.onKeyEvent(event);
}
@Override
protected void onServiceConnected() {
if (isInfrastructureInitialized) { return; }
super.onServiceConnected();
LOGI(TAG, "onServiceConnected(pid=" + android.os.Process.myPid() +
", uid=" + Process.myUid() +
", tid=" + Process.myTid() + ")");
mContext = this;
mPreferences = SettingsHelper.getInstance(mContext);
mPreferences.registerOnSharedPreferenceChangeListener(this);
initServiceInfo();
initPrefNames();
pWindowManager = new PopupWindowManager(mContext);
updateDisabledButtons(mPreferences.getSharedPreferences());
updateMediaCloak();
updateBlacklist();
isInfrastructureInitialized = true;
peekIntoTheNexus();
Process.setThreadPriority(Process.THREAD_PRIORITY_URGENT_AUDIO);
MainThreadBus.get().register(this);
mHandler.sendEmptyMessage(MESSAGE_START);
}
protected void updateDisabledButtons(SharedPreferences prefs) {
String value = prefs.getString(
Constants.PREF_DISABLED_BUTTONS, String.valueOf(disabledButtons));
try {
disabledButtons = Integer.parseInt(value, 10);
} catch (NumberFormatException nfe) {
LOGE(TAG, "Error with " + Constants.PREF_DISABLED_BUTTONS + " formatting: " + value);
disabledButtons = KeyEvent.KEYCODE_UNKNOWN;
}
}
// Start/ stop listening for full screen changes.
protected void updateMediaCloak() {
boolean mediaCloak = mPreferences.getProperty(
VolumePanel.class, VolumePanel.HIDE_FULLSCREEN, false);
LOGI(TAG, "updateMediaCloak(" + mediaCloak + ')');
// Add a show immediately our fullscreen test view.
if (mediaCloak) {
mFullscreenWindow = new FullscreenPopupWindow(pWindowManager);
mFullscreenWindow.addOnFullscreenChangeListener(mFSListener);
mFullscreenWindow.show();
} else {
if (null != mFullscreenWindow) {
mFullscreenWindow.onDestroy();
mFullscreenWindow = null;
}
}
}
/*package*/ FullscreenPopupWindow.OnFullscreenChangeListener mFSListener =
new FullscreenPopupWindow.OnFullscreenChangeListener() {
@Override
public void onFullscreenChange(boolean fullscreen) {
LOGI(TAG, "onFullscreenChange(" + fullscreen + ')');
Message.obtain(mHandler, MESSAGE_FULLSCREEN, (fullscreen) ? 1 : 0, 0).sendToTarget();
}
};
/*package*/ void initPrefNames() {
PREF_TIMEOUT = mPreferences.getName(PopupWindow.class, PopupWindow.TIMEOUT);
PREF_MUSIC_APP = mPreferences.getName(VolumePanel.class, VolumePanel.MUSIC_APP);
PREF_RINGER_MODE = mPreferences.getName(VolumePanel.class, VolumePanel.RINGER_MODE);
PREF_MASTER_VOLUME = mPreferences.getName(VolumePanel.class, VolumePanel.MASTER_VOLUME);
PREF_LONG_PRESS_DOWN = mPreferences.getName(VolumePanel.class, VolumePanel.ACTION_LONG_PRESS_VOLUME_DOWN);
PREF_LONG_PRESS_UP = mPreferences.getName(VolumePanel.class, VolumePanel.ACTION_LONG_PRESS_VOLUME_UP);
PREF_FOREGROUND_COLOR = mPreferences.getName(VolumePanel.class, VolumePanel.COLOR);
PREF_BACKGROUND_COLOR = mPreferences.getName(VolumePanel.class, VolumePanel.BACKGROUND);
PREF_SEEK = mPreferences.getName(VolumePanel.class, VolumePanel.SEEK);
PREF_NO_LONG_PRESS = mPreferences.getName(VolumePanel.class, VolumePanel.NO_LONG_PRESS);
PREF_HIDE_FULLSCREEN = mPreferences.getName(VolumePanel.class, VolumePanel.HIDE_FULLSCREEN);
PREF_HIDE_CAMERA = mPreferences.getName(VolumePanel.class, VolumePanel.HIDE_CAMERA);
PREF_BAR_HEIGHT = mPreferences.getName(VolumeBarPanel.class, VolumeBarPanel.BAR_HEIGHT);
PREF_STRETCH = mPreferences.getName(VolumePanel.class, VolumePanel.STRETCH);
PREF_DEFAULT_STREAM = mPreferences.getName(VolumePanel.class, VolumePanel.DEFAULT_STREAM);
PREF_LINK_NOTIF_RING = mPreferences.getName(VolumePanel.class, VolumePanel.LINK_NOTIF_RINGER);
PREF_TERTIARY_COLOR = mPreferences.getName(VolumePanel.class, VolumePanel.TERTIARY);
PREF_ALWAYS_EXPAND = mPreferences.getName(VolumePanel.class, VolumePanel.ALWAYS_EXPANDED);
PREF_FIRST_REVEAL = mPreferences.getName(VolumePanel.class, VolumePanel.FIRST_REVEAL);
}
public static class VolumePanelChangeEvent {
private final String vPanelName;
private final boolean supportsMedia;
public VolumePanelChangeEvent(VolumePanel vPanel) {
vPanelName = (null == vPanel) ? null : vPanel.getClass().getSimpleName();
supportsMedia = (null != vPanel && vPanel.supportsMediaPlayback());
}
public VolumePanelChangeEvent(String name, boolean media) {
vPanelName = name;
supportsMedia = media;
}
public String getName() { return vPanelName; }
public boolean supportsMediaPlayback() { return supportsMedia; }
}
public VolumePanelChangeEvent produceVolumePanelChangeEvent() {
return new VolumePanelChangeEvent(mVolumePanel);
}
/** Initialize the VolumePanel based on SharedPreferences values. */
/*package*/ void initVolumePanel() {
// Destroy the current VolumePanel if one exists.
if (mVolumePanel != null)
mVolumePanel.onDestroy();
mVolumePanel = null;
// Check preferences for the preferred VolumePanel and set it.
final String volumePanelName = mPreferences.get(Constants.PREF_VOLUME_PANEL, StatusBarVolumePanel.TAG);
VolumePanelInfo volumePanelInfo = Constants.getInfoForName(volumePanelName);
LOGI(TAG, "Initializing VolumePanel: " + volumePanelName);
// If we've removed a theme or some similar issue, default to the status bar theme.
if (null == volumePanelInfo) volumePanelInfo = StatusBarVolumePanel.VOLUME_PANEL_INFO;
mVolumePanel = volumePanelInfo.getInstance(pWindowManager);
if (null != mVolumePanel) {
((MainThreadBus) MainThreadBus.get()).postSafely(produceVolumePanelChangeEvent());
}
}
/*package*/ void updateBlacklist() {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
String packs = prefs.getString(Constants.PREF_BLACKLIST, "");
TextUtils.SimpleStringSplitter splitter = new TextUtils.SimpleStringSplitter(':');
splitter.setString(packs);
blacklist.clear();
while (splitter.hasNext()) {
blacklist.add(splitter.next());
}
LOGI(TAG, "updateBlacklist(" + blacklist.size() + ')');
}
@Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
boolean handled = true;
if (null == mVolumePanel && !Constants.PREF_FOREGROUND.equals(key)) return;
if (Constants.PREF_VOLUME_PANEL.equals(key)) {
initVolumePanel();
} else if (PREF_TIMEOUT.equals(key)) {
try {
mVolumePanel.setAutoHideDuration(
mPreferences.getIntProperty(PopupWindow.class, PopupWindow.TIMEOUT, 5000));
} catch (NumberFormatException e) {
LOGE(TAG, "Error parsing " + PopupWindow.TIMEOUT.getName() + '.', e);
}
} else if (PREF_MUSIC_APP.equals(key)) {
mVolumePanel.setMusicUri(mPreferences.getProperty(
VolumePanel.class, VolumePanel.MUSIC_APP, mVolumePanel.getMusicUri()));
} else if (PREF_MASTER_VOLUME.equals(key)) {
mVolumePanel.setOneVolume(mPreferences.getProperty(
VolumePanel.class, VolumePanel.MASTER_VOLUME, mVolumePanel.isOneVolume()));
} else if (PREF_RINGER_MODE.equals(key)) {
mVolumePanel.setRingerMode(mPreferences.getIntProperty(
VolumePanel.class, VolumePanel.RINGER_MODE, mVolumePanel.getRingerMode()));
} else if (PREF_LONG_PRESS_DOWN.equals(key)) {
mVolumePanel.setLongPressActionDown(mPreferences.getProperty(
VolumePanel.class, VolumePanel.ACTION_LONG_PRESS_VOLUME_DOWN, mVolumePanel.getLongPressActionDown()));
} else if (PREF_LONG_PRESS_UP.equals(key)) {
mVolumePanel.setLongPressActionUp(mPreferences.getProperty(
VolumePanel.class, VolumePanel.ACTION_LONG_PRESS_VOLUME_UP, mVolumePanel.getLongPressActionUp()));
} else if (PREF_FOREGROUND_COLOR.equals(key)) {
mVolumePanel.setColor(mPreferences.getProperty(
VolumePanel.class, VolumePanel.COLOR, mVolumePanel.getColor()));
} else if (PREF_BACKGROUND_COLOR.equals(key)) {
mVolumePanel.setBackgroundColor(mPreferences.getProperty(
VolumePanel.class, VolumePanel.BACKGROUND, mVolumePanel.getBackgroundColor()));
} else if (Constants.PREF_FOREGROUND.equals(key)) {
peekIntoTheNexus();
} else if (PREF_SEEK.equals(key)) {
mVolumePanel.setSeek(mPreferences.getProperty(
VolumePanel.class, VolumePanel.SEEK, mVolumePanel.isSeek()));
} else if (PREF_NO_LONG_PRESS.equals(key)) {
mVolumePanel.setNoLongPress(mPreferences.getProperty(
VolumePanel.class, VolumePanel.NO_LONG_PRESS, mVolumePanel.isNoLongPress()));
} else if (PREF_HIDE_FULLSCREEN.equals(key)) {
mVolumePanel.setHideFullscreen(mPreferences.getProperty(
VolumePanel.class, VolumePanel.HIDE_FULLSCREEN, mVolumePanel.getHideFullscreen()));
updateMediaCloak();
} else if (PREF_HIDE_CAMERA.equals(key)) {
mVolumePanel.setHideCamera(mPreferences.getProperty(
VolumePanel.class, VolumePanel.HIDE_CAMERA, mVolumePanel.getHideCamera()));
} else if (PREF_BAR_HEIGHT.equals(key)) {
// VolumeBarPanel specific setting for bar height
if (mVolumePanel instanceof VolumeBarPanel) {
VolumeBarPanel bar = (VolumeBarPanel) mVolumePanel;
bar.setBarHeight(mPreferences.getIntProperty(
VolumeBarPanel.class, VolumeBarPanel.BAR_HEIGHT, bar.getBarHeight()));
}
} else if (Constants.PREF_DISABLED_BUTTONS.equals(key)) {
updateDisabledButtons(sharedPreferences);
} else if (PREF_STRETCH.equals(key)) {
mVolumePanel.setStretch(mPreferences.getProperty(
VolumePanel.class, VolumePanel.STRETCH, mVolumePanel.isStretch()));
} else if (PREF_DEFAULT_STREAM.equals(key)) {
mVolumePanel.setDefaultStream(mPreferences.getIntProperty(
VolumePanel.class, VolumePanel.DEFAULT_STREAM, mVolumePanel.getDefaultStream()));
} else if (Constants.PREF_BLACKLIST.equals(key)) {
updateBlacklist();
} else if (PREF_LINK_NOTIF_RING.equals(key)) {
mVolumePanel.setLinkNotifRinger(mPreferences.getProperty(
VolumePanel.class, VolumePanel.LINK_NOTIF_RINGER, mVolumePanel.isLinkNotifRinger()));
} else if (PREF_TERTIARY_COLOR.equals(key)) {
mVolumePanel.setTertiaryColor(mPreferences.getProperty(
VolumePanel.class, VolumePanel.TERTIARY, mVolumePanel.getTertiaryColor()));
} else if (PREF_ALWAYS_EXPAND.equals(key)) {
mVolumePanel.setAlwaysExpanded(mPreferences.getProperty(
VolumePanel.class, VolumePanel.ALWAYS_EXPANDED, mVolumePanel.isAlwaysExpanded()));
} else if (PREF_FIRST_REVEAL.equals(key)) {
mVolumePanel.setFirstReveal(mPreferences.getProperty(
VolumePanel.class, VolumePanel.FIRST_REVEAL, mVolumePanel.getFirstReveal()));
} else {
handled = false;
}
LOGI(TAG, "onSharedPreferenceChanged(" + key + "), handled=" + handled);
}
protected void initServiceInfo() {
mInfo.eventTypes = AccessibilityEvent.TYPES_ALL_MASK;
mInfo.notificationTimeout = 100;
// This is the KEY (to KeyEvents)! Sweet deal.
mInfo.flags = AccessibilityServiceInfo.FLAG_REQUEST_FILTER_KEY_EVENTS;
// We'll respond with a popup (visual), and possibly a noise (audible)
// and/ or a vibration (haptic). No spoken feedback here!
mInfo.feedbackType = (AccessibilityServiceInfo.FEEDBACK_VISUAL |
AccessibilityServiceInfo.FEEDBACK_AUDIBLE |
AccessibilityServiceInfo.FEEDBACK_HAPTIC );
setServiceInfo(mInfo);
}
protected Notification mNotification;
protected void enterTheNexus() {
// One-off to create the notification and display it.
if (null == mNotification) {
Notification.Builder builder = new Notification.Builder(getApplicationContext());
/*Intent home = getPackageManager().getLaunchIntentForPackage(getPackageName());
home.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);*/
Intent accessibility = new Intent(getApplicationContext(), getClass());
accessibility.putExtra("show", true);
PendingIntent intent = PendingIntent.getService(getApplicationContext(), 0, accessibility, 0);
builder.setSmallIcon(R.drawable.ic_notif)
.setLargeIcon(BitmapFactory.decodeResource(getResources(), R.drawable.ic_notif_large))
.setContentTitle(getString(R.string.app_name))
.setContentText(getString(R.string.tap_configure))
.setPriority(Notification.PRIORITY_MIN)
.setContentIntent(intent);
// addPriorityActions(builder);
mNotification = builder.build();
}
startForeground(mNotification.hashCode(), mNotification);
}
/*@TargetApi(Build.VERSION_CODES.LOLLIPOP)
protected void addPriorityActions(Notification.Builder builder) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) return;
PendingIntent intentNone = PendingIntent.getService(getApplicationContext(), 0,
MediaControllerService.getInterruptionFilterRequestIntent(this, NotificationListenerService.INTERRUPTION_FILTER_NONE), PendingIntent.FLAG_ONE_SHOT);
PendingIntent intentAll = PendingIntent.getService(getApplicationContext(), 0,
MediaControllerService.getInterruptionFilterRequestIntent(this, NotificationListenerService.INTERRUPTION_FILTER_ALL), PendingIntent.FLAG_ONE_SHOT);
PendingIntent intentPriority = PendingIntent.getService(getApplicationContext(), 0,
MediaControllerService.getInterruptionFilterRequestIntent(this, NotificationListenerService.INTERRUPTION_FILTER_PRIORITY), PendingIntent.FLAG_ONE_SHOT);
builder.addAction(R.drawable.empty_pixel, getString(R.string.zen_mode_none), intentNone);
builder.addAction(R.drawable.empty_pixel, getString(R.string.zen_mode_priority), intentPriority);
builder.addAction(R.drawable.empty_pixel, getString(R.string.zen_mode_all), intentAll);
}*/
protected void peekIntoTheNexus() {
// Make/ remove the notification based on this setting.
boolean foreground = mPreferences.get(Constants.PREF_FOREGROUND, false);
if (foreground) enterTheNexus();
else exitTheNexus();
}
protected void exitTheNexus() {
stopForeground(true);
}
/** Destroy this {@link Service}. NOT reversible! */
private void destroy() {
FLog.clear();
if (isInfrastructureInitialized) {
if (null != mVolumePanel) mVolumePanel.onDestroy();
if (null != mFullscreenWindow) mFullscreenWindow.onDestroy();
if (null != pWindowManager) pWindowManager.destroy();
mPreferences.unregisterOnSharedPreferenceChangeListener(this);
mHandler.sendEmptyMessage(MESSAGE_SHUTDOWN);
MainThreadBus.get().unregister(this);
isInfrastructureInitialized = false;
mContext = null;
mPreferences = null;
pWindowManager = null;
mVolumePanel = null;
mFullscreenWindow = null;
}
}
private long mLastNotificationEventTime;
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
LOGI(TAG, "--onStartComment(" + intent + ", " + flags + ", " + startId + ')');
// If this app is sending us a request, show the volume panel!
ComponentName thisComponent = new ComponentName(getApplicationContext(), getClass());
if (intent != null && thisComponent.equals(intent.getComponent())) {
mLastNotificationEventTime = System.currentTimeMillis();
if (null != mVolumePanel) {
mVolumePanel.reveal();
}
}
return super.onStartCommand(intent, flags, startId);
}
@Override
public void onInterrupt() {
LOGI(TAG, "--onInterrupt()");
}
@Override
public boolean onUnbind(Intent intent) {
destroy();
LOGI(TAG, "--onUnbind()");
return super.onUnbind(intent);
}
@Override
public void onDestroy() {
destroy();
LOGI(TAG, "--onDestroy()");
super.onDestroy();
}
}