Permalink
Browse files

HALO (1/2)

HALO is Androids one and only multitasking solution.

We explicitly do not go the Samsung/Cornerstone route. This solution is basically an extension of
the regular notificationshade, which is an aggregator for notifications that can be checked out when
time permits. HALO completes it by allowing you to respond to tasks on the spot, trying to be as
unintrusive as possible and flexible in regards to policies (white/black lists).

Features:

- Apps open up in floating state, apps underneath do not pause
- complete SystemUI & Core integration, making HALO feel like a native feature, source/SDK and UI/UX
- HALO has white and blacklists, which for now allow fine control for apps pinging through HALO
- multiple gestures to navigate HALO:
  - double-tap-move: move
  - tap-drag-left/right: task through notifications, finger up launches
  - tap-drag-up: dismiss notification
  - tap-drag-down: put HALO into semi-hidden state
  - swipe while HALO is hidden wakes it up
  - drag-to-X to remove HALO altogether
- comes with as few settings as possible, everything should be self explanatory and intutive,
  users must be informed about the double-tap gestures which is the only catch.

Known issues:

- Integration has been finished for PhoneUI. TabletUI and PIE must be integrated still.
- Packageinstaller must allow HALO through its overlay check or it will disable the install-app button.
- The low level has a few remaining issues:
  - Some apps still go up fullscale (they trick us with services, circumventing the intent flagging)
  - Some 3D games still pause
  - There is one known app that refuses to end on back or touch-outside, that is WeChat
  - The activitystack pause implementation works but is makeshift

To-Do:

- On-screen tutorial at first start up to teach users the gestures
- Fixing all known issues, full integration in the remaining UI's
- Perhaps hybrid engine integration for running floating apps in a decreased size

Merge branch 'multiwindow' into jellybean
2 parents 3c308f1 + 4cbe154 commit 518c329e8c67fa96b8a8fc22e1962ddb8497c333 @drcmda drcmda committed Jun 10, 2013
Showing with 2,144 additions and 122 deletions.
  1. +74 −3 core/java/android/app/Activity.java
  2. +1 −6 core/java/android/app/ActivityThread.java
  3. +7 −0 core/java/android/app/INotificationManager.aidl
  4. +15 −3 core/java/android/app/TaskStackBuilder.java
  5. +5 −1 core/java/android/content/Intent.java
  6. +18 −0 core/java/android/provider/Settings.java
  7. +2 −0 core/java/android/view/Window.java
  8. +4 −4 core/java/com/android/internal/widget/ActionBarView.java
  9. BIN core/res/res/drawable-nodpi/floating_frame.9.png
  10. +2 −0 core/res/res/values/symbols.xml
  11. +24 −0 core/res/res/values/themes_device_defaults.xml
  12. +6 −0 packages/SystemUI/AndroidManifest.xml
  13. +9 −0 packages/SystemUI/proguard.flags
  14. BIN packages/SystemUI/res/drawable-hdpi/halo_back_left.png
  15. BIN packages/SystemUI/res/drawable-hdpi/halo_back_right.png
  16. BIN packages/SystemUI/res/drawable-hdpi/halo_bg.png
  17. BIN packages/SystemUI/res/drawable-hdpi/halo_bigred.png
  18. BIN packages/SystemUI/res/drawable-hdpi/halo_black_x.png
  19. BIN packages/SystemUI/res/drawable-hdpi/halo_dismiss.png
  20. BIN packages/SystemUI/res/drawable-hdpi/halo_marker_b.png
  21. BIN packages/SystemUI/res/drawable-hdpi/halo_marker_l.png
  22. BIN packages/SystemUI/res/drawable-hdpi/halo_marker_r.png
  23. BIN packages/SystemUI/res/drawable-hdpi/halo_marker_t.png
  24. BIN packages/SystemUI/res/drawable-hdpi/halo_number.9.png
  25. BIN packages/SystemUI/res/drawable-hdpi/halo_pulse1.png
  26. BIN packages/SystemUI/res/drawable-hdpi/halo_x.png
  27. BIN packages/SystemUI/res/drawable-hdpi/ic_launcher_clear_active_holo.png
  28. BIN packages/SystemUI/res/drawable-hdpi/ic_launcher_clear_normal_holo.png
  29. BIN packages/SystemUI/res/drawable-hdpi/ic_notify_halo_normal.png
  30. BIN packages/SystemUI/res/drawable-hdpi/ic_notify_halo_pressed.png
  31. BIN packages/SystemUI/res/drawable-mdpi/halo_back_left.png
  32. BIN packages/SystemUI/res/drawable-mdpi/halo_back_right.png
  33. BIN packages/SystemUI/res/drawable-mdpi/halo_bg.png
  34. BIN packages/SystemUI/res/drawable-mdpi/halo_bigred.png
  35. BIN packages/SystemUI/res/drawable-mdpi/halo_black_x.png
  36. BIN packages/SystemUI/res/drawable-mdpi/halo_dismiss.png
  37. BIN packages/SystemUI/res/drawable-mdpi/halo_marker_b.png
  38. BIN packages/SystemUI/res/drawable-mdpi/halo_marker_l.png
  39. BIN packages/SystemUI/res/drawable-mdpi/halo_marker_r.png
  40. BIN packages/SystemUI/res/drawable-mdpi/halo_marker_t.png
  41. BIN packages/SystemUI/res/drawable-mdpi/halo_number.9.png
  42. BIN packages/SystemUI/res/drawable-mdpi/halo_pulse1.png
  43. BIN packages/SystemUI/res/drawable-mdpi/halo_x.png
  44. BIN packages/SystemUI/res/drawable-mdpi/ic_launcher_clear_active_holo.png
  45. BIN packages/SystemUI/res/drawable-mdpi/ic_launcher_clear_normal_holo.png
  46. BIN packages/SystemUI/res/drawable-mdpi/ic_notify_halo_normal.png
  47. BIN packages/SystemUI/res/drawable-mdpi/ic_notify_halo_pressed.png
  48. BIN packages/SystemUI/res/drawable-nodpi/bubble_black_l.9.png
  49. BIN packages/SystemUI/res/drawable-nodpi/bubble_black_r.9.png
  50. BIN packages/SystemUI/res/drawable-nodpi/bubble_l.9.png
  51. BIN packages/SystemUI/res/drawable-nodpi/bubble_r.9.png
  52. BIN packages/SystemUI/res/drawable-xhdpi/halo_back_left.png
  53. BIN packages/SystemUI/res/drawable-xhdpi/halo_back_right.png
  54. BIN packages/SystemUI/res/drawable-xhdpi/halo_bg.png
  55. BIN packages/SystemUI/res/drawable-xhdpi/halo_bigred.png
  56. BIN packages/SystemUI/res/drawable-xhdpi/halo_black_x.png
  57. BIN packages/SystemUI/res/drawable-xhdpi/halo_dismiss.png
  58. BIN packages/SystemUI/res/drawable-xhdpi/halo_marker_b.png
  59. BIN packages/SystemUI/res/drawable-xhdpi/halo_marker_l.png
  60. BIN packages/SystemUI/res/drawable-xhdpi/halo_marker_r.png
  61. BIN packages/SystemUI/res/drawable-xhdpi/halo_marker_t.png
  62. BIN packages/SystemUI/res/drawable-xhdpi/halo_number.9.png
  63. BIN packages/SystemUI/res/drawable-xhdpi/halo_pulse1.png
  64. BIN packages/SystemUI/res/drawable-xhdpi/halo_x.png
  65. BIN packages/SystemUI/res/drawable-xhdpi/ic_notify_halo_normal.png
  66. BIN packages/SystemUI/res/drawable-xhdpi/ic_notify_halo_pressed.png
  67. +36 −0 packages/SystemUI/res/layout/halo_bubble.xml
  68. +22 −0 packages/SystemUI/res/layout/halo_number.xml
  69. +49 −0 packages/SystemUI/res/layout/halo_speech.xml
  70. +7 −0 packages/SystemUI/res/layout/halo_trigger.xml
  71. +11 −1 packages/SystemUI/res/layout/status_bar_expanded_header.xml
  72. +1 −0 packages/SystemUI/res/values-land/dimens.xml
  73. +2 −0 packages/SystemUI/res/values/dimens.xml
  74. +3 −0 packages/SystemUI/res/values/strings.xml
  75. +20 −0 packages/SystemUI/src/com/android/systemui/Transparent.java
  76. +144 −10 packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
  77. +17 −0 packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java
  78. +90 −0 packages/SystemUI/src/com/android/systemui/statusbar/halo/CustomObjectAnimator.java
  79. +1,092 −0 packages/SystemUI/src/com/android/systemui/statusbar/halo/Halo.java
  80. +164 −0 packages/SystemUI/src/com/android/systemui/statusbar/halo/HaloProperties.java
  81. +73 −23 packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
  82. +32 −17 packages/SystemUI/src/com/android/systemui/statusbar/phone/Ticker.java
  83. +1 −0 packages/SystemUI/src/com/android/systemui/statusbar/view/PieStatusPanel.java
  84. +147 −41 services/java/com/android/server/NotificationManagerService.java
  85. +53 −1 services/java/com/android/server/am/ActivityRecord.java
  86. +13 −12 services/java/com/android/server/am/ActivityStack.java
View
77 core/java/android/app/Activity.java
@@ -59,13 +59,17 @@
import android.util.ColorUtils;
import android.util.EventLog;
import android.util.ExtendedPropertiesUtils;
+import android.util.DisplayMetrics;
import android.util.Log;
+import android.util.TypedValue;
import android.util.Slog;
import android.util.SparseArray;
import android.view.ActionMode;
import android.view.ContextMenu;
import android.view.ContextMenu.ContextMenuInfo;
import android.view.ContextThemeWrapper;
+import android.view.Display;
+import android.view.Gravity;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.Menu;
@@ -1463,6 +1467,9 @@ public void onConfigurationChanged(Configuration newConfig) {
if (mWindow != null) {
// Pass the configuration changed event to the window
mWindow.onConfigurationChanged(newConfig);
+ if (mWindow.mIsFloatingWindow) {
+ scaleFloatingWindow(null);
+ }
}
if (mActionBar != null) {
@@ -5060,11 +5067,47 @@ final void attach(Context context, ActivityThread aThread,
CharSequence title, Activity parent, String id,
NonConfigurationInstances lastNonConfigurationInstances,
Configuration config) {
+
@h3r3x3
h3r3x3 added a line comment Jul 1, 2013

I think these blank lines shouldn't appear in this patch...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
attachBaseContext(context);
mFragments.attachActivity(this, mContainer, null);
-
- mWindow = PolicyManager.makeNewWindow(this);
+
+ boolean floating = (intent.getFlags()&Intent.FLAG_FLOATING_WINDOW) == Intent.FLAG_FLOATING_WINDOW;
+ if (intent != null && floating) {
+ TypedArray styleArray = context.obtainStyledAttributes(info.theme, com.android.internal.R.styleable.Window);
+ TypedValue backgroundValue = styleArray.peekValue(com.android.internal.R.styleable.Window_windowBackground);
+
+ // Apps that have no title don't need no title bar
+ TypedValue outValue = new TypedValue();
+ boolean result = styleArray.getValue(com.android.internal.R.styleable.Window_windowNoTitle, outValue);
+
+ if (backgroundValue != null && backgroundValue.toString().contains("light")) {
+ context.getTheme().applyStyle(com.android.internal.R.style.Theme_DeviceDefault_FloatingWindowLight, true);
+ } else {
+ context.getTheme().applyStyle(com.android.internal.R.style.Theme_DeviceDefault_FloatingWindow, true);
+ }
+
+ parent = null;
+
+ // Create our new window
+ mWindow = PolicyManager.makeNewWindow(this);
+ mWindow.mIsFloatingWindow = true;
+ mWindow.setCloseOnTouchOutsideIfNotSet(true);
+ mWindow.setGravity(Gravity.CENTER);
+
+ mWindow.setFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND,
+ WindowManager.LayoutParams.FLAG_DIM_BEHIND);
+ WindowManager.LayoutParams params = mWindow.getAttributes();
+ params.alpha = 1f;
+ params.dimAmount = 0.25f;
+ mWindow.setAttributes((android.view.WindowManager.LayoutParams) params);
+
+ // Scale it
+ scaleFloatingWindow(context);
+ } else {
+ mWindow = PolicyManager.makeNewWindow(this);
+ }
+
mWindow.setCallback(this);
mWindow.getLayoutInflater().setPrivateFactory(this);
if (info.softInputMode != WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED) {
@@ -5099,6 +5142,26 @@ final void attach(Context context, ActivityThread aThread,
mCurrentConfig = config;
}
+ private void scaleFloatingWindow(Context context) {
+ if (!mWindow.mIsFloatingWindow) {
+ return;
+ }
+ WindowManager wm = null;
+ if (context != null) {
+ wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
+ } else {
+ wm = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
+ }
+ Display display = wm.getDefaultDisplay();
+ DisplayMetrics metrics = new DisplayMetrics();
+ display.getMetrics(metrics);
+ if (metrics.heightPixels > metrics.widthPixels) {
+ mWindow.setLayout((int)(metrics.widthPixels * 0.9f), (int)(metrics.heightPixels * 0.7f));
+ } else {
+ mWindow.setLayout((int)(metrics.widthPixels * 0.7f), (int)(metrics.heightPixels * 0.8f));
+ }
+ }
+
/** @hide */
public final IBinder getActivityToken() {
return mParent != null ? mParent.getActivityToken() : mToken;
@@ -5265,7 +5328,7 @@ final void performUserLeaving() {
onUserInteraction();
onUserLeaveHint();
}
-
+
final void performStop() {
if (mLoadersStarted) {
mLoadersStarted = false;
@@ -5311,6 +5374,14 @@ final void performStop() {
mStopped = true;
}
mResumed = false;
+
+ // Floatingwindows activities should be kept volatile to prevent new activities taking
+ // up front in a minimized space. Every stop call, for instance when pressing home,
+ // will terminate the activity. If the activity is already finishing we might just
+ // as well let it go.
+ if (!mChangingConfigurations && mWindow != null && mWindow.mIsFloatingWindow && !isFinishing()) {
+ finish();
+ }
}
final void performDestroy() {
View
7 core/java/android/app/ActivityThread.java
@@ -2871,12 +2871,7 @@ public final ActivityClientRecord performResumeActivity(IBinder token,
r.stopped = false;
r.state = null;
} catch (Exception e) {
- if (!mInstrumentation.onException(r.activity, e)) {
- throw new RuntimeException(
- "Unable to resume activity "
- + r.intent.getComponent().toShortString()
- + ": " + e.toString(), e);
- }
+ // Unable to resume activity
}
}
return r;
View
7 core/java/android/app/INotificationManager.aidl
@@ -34,5 +34,12 @@ interface INotificationManager
void setNotificationsEnabledForPackage(String pkg, boolean enabled);
boolean areNotificationsEnabledForPackage(String pkg);
+
+ void setHaloPolicyBlack(boolean state);
+ void setHaloStatus(String pkg, boolean status);
+ void setHaloBlacklistStatus(String pkg, boolean status);
+ void setHaloWhitelistStatus(String pkg, boolean status);
+ boolean isHaloPolicyBlack();
+ boolean isPackageAllowedForHalo(String pkg);
}
View
18 core/java/android/app/TaskStackBuilder.java
@@ -62,6 +62,7 @@
private final ArrayList<Intent> mIntents = new ArrayList<Intent>();
private final Context mSourceContext;
+ private boolean mFirstTaskOnHome = true;
private TaskStackBuilder(Context a) {
mSourceContext = a;
@@ -78,6 +79,10 @@ public static TaskStackBuilder create(Context context) {
return new TaskStackBuilder(context);
}
+ public void setTaskOnHome(boolean firstTaskOnHome) {
+ mFirstTaskOnHome = firstTaskOnHome;
+ }
+
/**
* Add a new Intent to the task stack. The most recently added Intent will invoke
* the Activity at the top of the final task stack.
@@ -298,9 +303,16 @@ public PendingIntent getPendingIntent(int requestCode, int flags, Bundle options
Intent[] intents = new Intent[mIntents.size()];
if (intents.length == 0) return intents;
- intents[0] = new Intent(mIntents.get(0)).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
- Intent.FLAG_ACTIVITY_CLEAR_TASK |
- Intent.FLAG_ACTIVITY_TASK_ON_HOME);
+ Intent newIntent = new Intent(mIntents.get(0));
+ newIntent.addFlags(
+ Intent.FLAG_ACTIVITY_NEW_TASK |
+ Intent.FLAG_ACTIVITY_CLEAR_TASK);
+
+ if (mFirstTaskOnHome) {
+ newIntent.addFlags(Intent.FLAG_ACTIVITY_TASK_ON_HOME);
+ }
+
+ intents[0] = newIntent;
for (int i = 1; i < intents.length; i++) {
intents[i] = new Intent(mIntents.get(i));
}
View
6 core/java/android/content/Intent.java
@@ -3101,7 +3101,6 @@ public static Intent createChooser(Intent target, CharSequence title) {
* places where the framework may automatically set the exclude flag).
*/
public static final int FLAG_INCLUDE_STOPPED_PACKAGES = 0x00000020;
-
/**
* If set, the new activity is not kept in the history stack. As soon as
* the user navigates away from it, the activity is finished. This may also
@@ -3318,6 +3317,11 @@ public static Intent createChooser(Intent target, CharSequence title) {
*/
public static final int FLAG_ACTIVITY_TASK_ON_HOME = 0X00004000;
/**
+ * If set, this intent will always match start up as a floating window
+ * in mutil window scenarios.
+ */
+ public static final int FLAG_FLOATING_WINDOW = 0x00002000;
+ /**
* If set, when sending a broadcast only registered receivers will be
* called -- no BroadcastReceiver components will be launched.
*/
View
18 core/java/android/provider/Settings.java
@@ -2561,6 +2561,24 @@ public static void setShowGTalkServiceStatusForUser(ContentResolver cr, boolean
public static final String DISABLE_FULLSCREEN_KEYBOARD = "disable_fullscreen_keyboard";
/**
+ * HALO, should default to 0 (no, do not show)
+ * @hide
+ */
+ public static final String HALO_ACTIVE = "halo_active";
+
+ /**
+ * HALO reversed?, should default to 1 (yes, reverse)
+ * @hide
+ */
+ public static final String HALO_REVERSED = "halo_reversed";
+
+ /**
+ * HALO hide?, should default to 0 (no, do not hide)
+ * @hide
+ */
+ public static final String HALO_HIDE = "halo_hide";
+
+ /**
* Pie menu, should default to 1 (yes, show)
* @hide
*/
View
2 core/java/android/view/Window.java
@@ -152,6 +152,8 @@
private boolean mDestroyed;
+ public boolean mIsFloatingWindow = false;
+
// The current window attributes.
private final WindowManager.LayoutParams mWindowAttributes =
new WindowManager.LayoutParams();
View
8 core/java/com/android/internal/widget/ActionBarView.java
@@ -904,14 +904,14 @@ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
if (widthMode != MeasureSpec.EXACTLY) {
- throw new IllegalStateException(getClass().getSimpleName() + " can only be used " +
- "with android:layout_width=\"match_parent\" (or fill_parent)");
+ //throw new IllegalStateException(getClass().getSimpleName() + " can only be used " +
+ // "with android:layout_width=\"match_parent\" (or fill_parent)");
}
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
if (heightMode != MeasureSpec.AT_MOST) {
- throw new IllegalStateException(getClass().getSimpleName() + " can only be used " +
- "with android:layout_height=\"wrap_content\"");
+ //throw new IllegalStateException(getClass().getSimpleName() + " can only be used " +
+ // "with android:layout_height=\"wrap_content\"");
}
int contentWidth = MeasureSpec.getSize(widthMeasureSpec);
View
BIN core/res/res/drawable-nodpi/floating_frame.9.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
2 core/res/res/values/symbols.xml
@@ -1159,6 +1159,8 @@
<java-symbol type="style" name="Theme.DeviceDefault.Dialog.NoFrame" />
<java-symbol type="style" name="Theme.IconMenu" />
<java-symbol type="style" name="Theme.Panel.Volume" />
+ <java-symbol type="style" name="Theme.DeviceDefault.FloatingWindow" />
+ <java-symbol type="style" name="Theme.DeviceDefault.FloatingWindowLight" />
<java-symbol type="attr" name="mediaRouteButtonStyle" />
<java-symbol type="attr" name="externalRouteEnabledDrawable" />
View
24 core/res/res/values/themes_device_defaults.xml
@@ -485,8 +485,32 @@ easier.
decorations, so you basically have an empty rectangle in which to place your content. It makes
the window floating, with a transparent background, and turns off dimming behind the window. -->
<style name="Theme.DeviceDefault.Panel" parent="Theme.Holo.Panel" >
+ </style>
+
+ <style name="Theme.DeviceDefault.FloatingWindow">
+ <item name="android:windowIsFloating">false</item>
+ <item name="android:windowIsTranslucent">true</item>
+ <item name="android:windowFrame">@null</item>
+ <item name="android:windowContentOverlay">@null</item>
+ <item name="android:windowAnimationStyle">@android:style/Animation.Dialog</item>
+ <item name="android:windowActionModeOverlay">true</item>
+ <item name="android:windowCloseOnTouchOutside">true</item>
+ <item name="android:windowFullscreen">false</item>
+ <item name="android:windowSoftInputMode">stateAlwaysHidden|adjustPan</item>
+ </style>
+ <style name="Theme.DeviceDefault.FloatingWindowLight" parent="Theme.Holo.Light.Dialog">
+ <item name="android:windowIsFloating">false</item>
+ <item name="android:windowIsTranslucent">true</item>
+ <item name="android:windowFrame">@null</item>
+ <item name="android:windowContentOverlay">@null</item>
+ <item name="android:windowAnimationStyle">@android:style/Animation.Dialog</item>
+ <item name="android:windowActionModeOverlay">true</item>
+ <item name="android:windowCloseOnTouchOutside">true</item>
+ <item name="android:windowFullscreen">false</item>
+ <item name="android:windowSoftInputMode">stateAlwaysHidden|adjustPan</item>
</style>
+
<!-- DeviceDefault light theme for panel windows. This removes all extraneous window
decorations, so you basically have an empty rectangle in which to place your content. It makes
the window floating, with a transparent background, and turns off dimming behind the window. -->
View
6 packages/SystemUI/AndroidManifest.xml
@@ -74,6 +74,9 @@
<uses-permission android:name="android.permission.READ_DREAM_STATE" />
<uses-permission android:name="android.permission.WRITE_DREAM_STATE" />
+ <!--Halo-->
+ <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
+
<application
android:persistent="true"
android:allowClearUserData="false"
@@ -243,5 +246,8 @@
<category android:name="android.intent.category.DESK_DOCK" />
</intent-filter>
</activity>
+
+ <activity android:name="com.android.systemui.Transparent"
+ android:theme="@android:style/Theme.Translucent.NoTitleBar" />
</application>
</manifest>
View
9 packages/SystemUI/proguard.flags
@@ -14,4 +14,13 @@
public void setGlowScale(float);
}
+-keep class com.android.systemui.statusbar.halo.HaloProperties {
+ public int getHaloX();
+ public int getHaloY();
+ public float getHaloContentAlpha();
+ public void setHaloX(int);
+ public void setHaloY(int);
+ public void setHaloContentAlpha(float);
+}
+
-keep class com.android.systemui.statusbar.tv.TvStatusBar
View
BIN packages/SystemUI/res/drawable-hdpi/halo_back_left.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
BIN packages/SystemUI/res/drawable-hdpi/halo_back_right.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
BIN packages/SystemUI/res/drawable-hdpi/halo_bg.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
BIN packages/SystemUI/res/drawable-hdpi/halo_bigred.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
BIN packages/SystemUI/res/drawable-hdpi/halo_black_x.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
BIN packages/SystemUI/res/drawable-hdpi/halo_dismiss.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
BIN packages/SystemUI/res/drawable-hdpi/halo_marker_b.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
BIN packages/SystemUI/res/drawable-hdpi/halo_marker_l.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
BIN packages/SystemUI/res/drawable-hdpi/halo_marker_r.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
BIN packages/SystemUI/res/drawable-hdpi/halo_marker_t.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
BIN packages/SystemUI/res/drawable-hdpi/halo_number.9.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
BIN packages/SystemUI/res/drawable-hdpi/halo_pulse1.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
BIN packages/SystemUI/res/drawable-hdpi/halo_x.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
BIN packages/SystemUI/res/drawable-hdpi/ic_launcher_clear_active_holo.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
BIN packages/SystemUI/res/drawable-hdpi/ic_launcher_clear_normal_holo.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
BIN packages/SystemUI/res/drawable-hdpi/ic_notify_halo_normal.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
BIN packages/SystemUI/res/drawable-hdpi/ic_notify_halo_pressed.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
BIN packages/SystemUI/res/drawable-mdpi/halo_back_left.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
BIN packages/SystemUI/res/drawable-mdpi/halo_back_right.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
BIN packages/SystemUI/res/drawable-mdpi/halo_bg.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
BIN packages/SystemUI/res/drawable-mdpi/halo_bigred.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
BIN packages/SystemUI/res/drawable-mdpi/halo_black_x.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
BIN packages/SystemUI/res/drawable-mdpi/halo_dismiss.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
BIN packages/SystemUI/res/drawable-mdpi/halo_marker_b.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
BIN packages/SystemUI/res/drawable-mdpi/halo_marker_l.png
Diff not rendered.
View
BIN packages/SystemUI/res/drawable-mdpi/halo_marker_r.png
Diff not rendered.
View
BIN packages/SystemUI/res/drawable-mdpi/halo_marker_t.png
Diff not rendered.
View
BIN packages/SystemUI/res/drawable-mdpi/halo_number.9.png
Diff not rendered.
View
BIN packages/SystemUI/res/drawable-mdpi/halo_pulse1.png
Diff not rendered.
View
BIN packages/SystemUI/res/drawable-mdpi/halo_x.png
Diff not rendered.
View
BIN packages/SystemUI/res/drawable-mdpi/ic_launcher_clear_active_holo.png
Diff not rendered.
View
BIN packages/SystemUI/res/drawable-mdpi/ic_launcher_clear_normal_holo.png
Diff not rendered.
View
BIN packages/SystemUI/res/drawable-mdpi/ic_notify_halo_normal.png
Diff not rendered.
View
BIN packages/SystemUI/res/drawable-mdpi/ic_notify_halo_pressed.png
Diff not rendered.
View
BIN packages/SystemUI/res/drawable-nodpi/bubble_black_l.9.png
Diff not rendered.
View
BIN packages/SystemUI/res/drawable-nodpi/bubble_black_r.9.png
Diff not rendered.
View
BIN packages/SystemUI/res/drawable-nodpi/bubble_l.9.png
Diff not rendered.
View
BIN packages/SystemUI/res/drawable-nodpi/bubble_r.9.png
Diff not rendered.
View
BIN packages/SystemUI/res/drawable-xhdpi/halo_back_left.png
Diff not rendered.
View
BIN packages/SystemUI/res/drawable-xhdpi/halo_back_right.png
Diff not rendered.
View
BIN packages/SystemUI/res/drawable-xhdpi/halo_bg.png
Diff not rendered.
View
BIN packages/SystemUI/res/drawable-xhdpi/halo_bigred.png
Diff not rendered.
View
BIN packages/SystemUI/res/drawable-xhdpi/halo_black_x.png
Diff not rendered.
View
BIN packages/SystemUI/res/drawable-xhdpi/halo_dismiss.png
Diff not rendered.
View
BIN packages/SystemUI/res/drawable-xhdpi/halo_marker_b.png
Diff not rendered.
View
BIN packages/SystemUI/res/drawable-xhdpi/halo_marker_l.png
Diff not rendered.
View
BIN packages/SystemUI/res/drawable-xhdpi/halo_marker_r.png
Diff not rendered.
View
BIN packages/SystemUI/res/drawable-xhdpi/halo_marker_t.png
Diff not rendered.
View
BIN packages/SystemUI/res/drawable-xhdpi/halo_number.9.png
Diff not rendered.
View
BIN packages/SystemUI/res/drawable-xhdpi/halo_pulse1.png
Diff not rendered.
View
BIN packages/SystemUI/res/drawable-xhdpi/halo_x.png
Diff not rendered.
View
BIN packages/SystemUI/res/drawable-xhdpi/ic_notify_halo_normal.png
Diff not rendered.
View
BIN packages/SystemUI/res/drawable-xhdpi/ic_notify_halo_pressed.png
Diff not rendered.
View
36 packages/SystemUI/res/layout/halo_bubble.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<FrameLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content">
+
+ <RelativeLayout
+ android:id="@+id/halo_content"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content">
+
+ <ImageView
+ android:id="@+id/halo_bg"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:src="@drawable/halo_bg"/>
+
+ <ImageView
+ android:id="@+id/app_icon"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_centerVertical="true"
+ android:layout_centerHorizontal="true"/>
+
+ <ImageView
+ android:id="@+id/halo_overlay"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_centerVertical="true"
+ android:layout_centerHorizontal="true"
+ android:alpha="0"/>
+
+ </RelativeLayout>
+
+</FrameLayout>
View
22 packages/SystemUI/res/layout/halo_number.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content">
+
+ <TextView
+ android:id="@+id/number"
+ android:animateLayoutChanges="true"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:paddingLeft="5dp"
+ android:paddingRight="5dp"
+ android:paddingTop="2dp"
+ android:paddingBottom="2dp"
+ android:background="@drawable/halo_number"
+ android:textColor="#fff"
+ android:textStyle="bold"
+ android:textSize="16sp"/>
+
+</LinearLayout>
View
49 packages/SystemUI/res/layout/halo_speech.xml
@@ -0,0 +1,49 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content">
+
+ <RelativeLayout
+ android:layout_width="@dimen/halo_content_max_width"
+ android:layout_height="wrap_content">
+
+ <RelativeLayout
+ android:id="@+id/ticker"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content">
+
+ <TextView
+ android:id="@+id/bubble_r"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:paddingLeft="20dp"
+ android:paddingRight="20dp"
+ android:paddingTop="17dp"
+ android:paddingBottom="30dp"
+ android:background="@drawable/bubble_black_r"
+ android:singleLine="false"
+ android:textColor="#ffffff"
+ android:maxLines = "3"
+ android:visibility="gone"/>
+
+ <TextView
+ android:id="@+id/bubble_l"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:paddingLeft="20dp"
+ android:paddingRight="20dp"
+ android:paddingTop="17dp"
+ android:paddingBottom="30dp"
+ android:background="@drawable/bubble_black_l"
+ android:singleLine="false"
+ android:textColor="#ffffff"
+ android:maxLines = "3"
+ android:visibility="gone"/>
+
+ </RelativeLayout>
+
+ </RelativeLayout>
+
+</LinearLayout>
View
7 packages/SystemUI/res/layout/halo_trigger.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<com.android.systemui.statusbar.halo.Halo
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+</com.android.systemui.statusbar.halo.Halo>
View
12 packages/SystemUI/res/layout/status_bar_expanded_header.xml
@@ -74,9 +74,19 @@
android:padding="2dp"
/>
+ <ImageView android:id="@+id/halo_button"
+ android:layout_width="50dp"
+ android:layout_height="50dp"
+ android:scaleType="center"
+ android:src="@drawable/ic_notify_halo_normal"
+ android:background="@drawable/ic_notify_button_bg"
+ android:contentDescription="@string/accessibility_halo"
+ />
+
<ImageView android:id="@+id/clear_all_button"
android:layout_width="50dp"
android:layout_height="50dp"
+ android:layout_marginLeft="5dp"
android:scaleType="center"
android:src="@drawable/ic_notify_clear"
android:background="@drawable/ic_notify_button_bg"
@@ -86,7 +96,7 @@
<FrameLayout android:id="@+id/settings_button_holder"
android:layout_width="50dp"
android:layout_height="50dp"
- android:layout_marginLeft="12dp"
+ android:layout_marginLeft="5dp"
>
<ImageView android:id="@+id/settings_button"
android:layout_width="50dp"
View
1 packages/SystemUI/res/values-land/dimens.xml
@@ -42,4 +42,5 @@
<dimen name="quick_settings_cell_height">100dp</dimen>
<dimen name="pie_panel_padding">100dp</dimen>
+ <dimen name="halo_content_max_width">350dp</dimen>
</resources>
View
2 packages/SystemUI/res/values/dimens.xml
@@ -242,4 +242,6 @@
<dimen name="pie_panel_padding">20dp</dimen>
<!-- PIE PIE PIE PIE PIE PIE PIE PIE PIE PIE PIE PIE PIE PIE -->
+ <dimen name="halo_content_max_width">250dp</dimen>
+
</resources>
View
3 packages/SystemUI/res/values/strings.xml
@@ -436,6 +436,9 @@
<!-- Notification text: when GPS has found a fix [CHAR LIMIT=50] -->
<string name="gps_notification_found_text">Location set by GPS</string>
+ <!-- Content description of the halo button in the notification panel for accessibility (not shown on the screen). [CHAR LIMIT=NONE] -->
+ <string name="accessibility_halo">Create notification halo.</string>
+
<!-- Content description of the clear button in the notification panel for accessibility (not shown on the screen). [CHAR LIMIT=NONE] -->
<string name="accessibility_clear_all">Clear all notifications.</string>
View
20 packages/SystemUI/src/com/android/systemui/Transparent.java
@@ -0,0 +1,20 @@
+package com.android.systemui;
+
+import android.app.Activity;
+import android.os.Handler;
+
+public class Transparent extends Activity {
+
+ @Override
+ public void onStart() {
+ super.onStart();
+
+ new Handler().postDelayed(new Runnable() {
+
+ @Override
+ public void run() {
+ Transparent.this.finish();
+ }
+ }, 500);
+ }
+}
View
154 packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
@@ -25,6 +25,7 @@
import android.app.TaskStackBuilder;
import android.content.ActivityNotFoundException;
import android.content.BroadcastReceiver;
+import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
@@ -45,6 +46,7 @@
import android.graphics.PixelFormat;
import android.graphics.PorterDuff;
import android.graphics.PorterDuff.Mode;
+import android.graphics.PorterDuffXfermode;
import android.graphics.Rect;
import android.net.Uri;
import android.os.Build;
@@ -90,7 +92,9 @@
import com.android.systemui.recent.RecentTasksLoader;
import com.android.systemui.recent.RecentsActivity;
import com.android.systemui.recent.TaskDescription;
+import com.android.systemui.statusbar.halo.Halo;
import com.android.systemui.statusbar.phone.QuickSettingsContainerView;
+import com.android.systemui.statusbar.phone.Ticker;
import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.statusbar.policy.Clock;
import com.android.systemui.statusbar.policy.NetworkController;
@@ -155,6 +159,14 @@
public View[] mPieDummyTrigger = new View[4];
int mIndex;
+ // Halo
+ protected Halo mHalo = null;
+ protected Ticker mTicker;
+ protected boolean mHaloActive;
+ protected boolean mHaloTaskerActive = false;
+ protected ImageView mHaloButton;
+ protected boolean mHaloButtonVisible = true;
+
// Policy
public NetworkController mNetworkController;
public BatteryController mBatteryController;
@@ -188,6 +200,10 @@
private boolean mDeviceProvisioned = false;
+ public Ticker getTicker() {
+ return mTicker;
+ }
+
public void collapse() {
}
@@ -366,6 +382,9 @@ public void start() {
// If the system process isn't there we're doomed anyway.
}
+ mHaloActive = Settings.System.getInt(mContext.getContentResolver(),
+ Settings.System.HALO_ACTIVE, 0) == 1;
+
createAndAddWindows();
disable(switches[0]);
@@ -494,10 +513,58 @@ public void onChange(boolean selfChange) {
attachPie();
+ // Listen for HALO state
+ mContext.getContentResolver().registerContentObserver(
+ Settings.System.getUriFor(Settings.System.HALO_ACTIVE), false, new ContentObserver(new Handler()) {
+ @Override
+ public void onChange(boolean selfChange) {
+ updateHalo();
+ }});
@JBirdVegas
JBirdVegas added a line comment Jun 12, 2013

Are you worried about leaking this ContentObserver? I don't see it unregistered anywhere... unless I missed it. Wouldn't this could cause memory leaks as new Observers get created?

@danyh
danyh added a line comment Jun 15, 2013

what

@JBirdVegas
JBirdVegas added a line comment Jun 15, 2013

What part are you confused about?

@aaronpoweruser
ParanoidAndroid member
aaronpoweruser added a line comment Jun 27, 2013

Thanks, didnt think about that. I will fix it, though technically system ui is a never removed from the window and thus is never removed from the window

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+
+ updateHalo();
+
SettingsObserver settingsObserver = new SettingsObserver(new Handler());
settingsObserver.observe();
}
+ public void setHaloTaskerActive(boolean haloTaskerActive, boolean updateNotificationIcons) {
+ mHaloTaskerActive = haloTaskerActive;
+ if (updateNotificationIcons) {
+ updateNotificationIcons();
+ }
+ }
+
+ protected void updateHaloButton() {
+ if (mHaloButton != null) {
+ mHaloButton.setVisibility(mHaloButtonVisible && !mHaloActive ? View.VISIBLE : View.GONE);
+ }
+ }
+
+ protected void updateHalo() {
+ mHaloActive = Settings.System.getInt(mContext.getContentResolver(),
+ Settings.System.HALO_ACTIVE, 0) == 1;
+
+ updateHaloButton();
+
+ if (mHaloActive) {
+ if (mHalo == null) {
+ LayoutInflater inflater = (LayoutInflater) mContext
+ .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+ mHalo = (Halo)inflater.inflate(R.layout.halo_trigger, null);
+ mHalo.setLayerType (View.LAYER_TYPE_HARDWARE, null);
+ WindowManager.LayoutParams params = mHalo.getWMParams();
+ mWindowManager.addView(mHalo,params);
+ mHalo.setStatusBar(this);
+ }
+ } else {
+ if (mHalo != null) {
+ mHalo.cleanUp();
+ mWindowManager.removeView(mHalo);
+ mHalo = null;
+ }
+ }
+ }
+
private boolean showPie() {
boolean expanded = Settings.System.getInt(mContext.getContentResolver(),
Settings.System.EXPANDED_DESKTOP_STATE, 0) == 1;
@@ -1234,11 +1301,12 @@ public NotificationClicker makeClicker(PendingIntent intent, String pkg, String
return new NotificationClicker(intent, pkg, tag, id);
}
- private class NotificationClicker implements View.OnClickListener {
- private PendingIntent mIntent;
- private String mPkg;
- private String mTag;
- private int mId;
+ public class NotificationClicker implements View.OnClickListener {
+ public PendingIntent mIntent;
+ public String mPkg;
+ public String mTag;
+ public int mId;
+ public boolean mFloat;
NotificationClicker(PendingIntent intent, String pkg, String tag, int id) {
mIntent = intent;
@@ -1247,6 +1315,10 @@ public NotificationClicker makeClicker(PendingIntent intent, String pkg, String
mId = id;
}
+ public void makeFloating(boolean floating) {
+ mFloat = floating;
+ }
+
public void onClick(View v) {
try {
// The intent we are sending is for the application, which
@@ -1261,9 +1333,17 @@ public void onClick(View v) {
}
if (mIntent != null) {
+
+ if (mFloat && !"android".equals(mPkg) && !"com.paranoid.halo".equals(mPkg)) {
+ Intent transparent = new Intent(mContext, com.android.systemui.Transparent.class);
+ transparent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_FLOATING_WINDOW);
+ mContext.startActivity(transparent);
+ }
+
int[] pos = new int[2];
v.getLocationOnScreen(pos);
Intent overlay = new Intent();
+ if (mFloat) overlay.addFlags(Intent.FLAG_FLOATING_WINDOW | Intent.FLAG_ACTIVITY_CLEAR_TASK);
overlay.setSourceBounds(
new Rect(pos[0], pos[1], pos[0]+v.getWidth(), pos[1]+v.getHeight()));
try {
@@ -1340,6 +1420,41 @@ protected StatusBarNotification removeNotificationViews(IBinder key) {
return entry.notification;
}
+ private Bitmap createRoundIcon(StatusBarNotification notification) {
+ // Construct the round icon
+ BitmapDrawable bd = (BitmapDrawable) mContext.getResources().getDrawable(R.drawable.halo_bg);
+ int iconSize = bd.getBitmap().getWidth();
+ int smallIconSize = mContext.getResources().getDimensionPixelSize(R.dimen.status_bar_icon_size);
+ Bitmap roundIcon = Bitmap.createBitmap(iconSize, iconSize, Bitmap.Config.ARGB_8888);
+ Canvas canvas = new Canvas(roundIcon);
+ canvas.drawARGB(0, 0, 0, 0);
+
+ if (notification.notification.largeIcon != null) {
+ Paint smoothingPaint = new Paint();
+ smoothingPaint.setAntiAlias(true);
+ smoothingPaint.setFilterBitmap(true);
+ smoothingPaint.setDither(true);
+ canvas.drawCircle(iconSize / 2, iconSize / 2, iconSize / 2.3f, smoothingPaint);
+ smoothingPaint.setXfermode(new PorterDuffXfermode(Mode.SRC_IN));
+ Bitmap scaledBitmap = Bitmap.createScaledBitmap(notification.notification.largeIcon, iconSize, iconSize, true);
+ canvas.drawBitmap(scaledBitmap, null, new Rect(0, 0,
+ iconSize, iconSize), smoothingPaint);
+ } else {
+ try {
+ Drawable icon = StatusBarIconView.getIcon(mContext,
+ new StatusBarIcon(notification.pkg, notification.user, notification.notification.icon,
+ notification.notification.iconLevel, 0, notification.notification.tickerText));
+ if (icon == null) icon = mContext.getPackageManager().getApplicationIcon(notification.pkg);
+ int margin = (iconSize - smallIconSize) / 2;
+ icon.setBounds(margin, margin, iconSize - margin, iconSize - margin);
+ icon.draw(canvas);
+ } catch (Exception e) {
+ // NameNotFoundException
+ }
+ }
+ return roundIcon;
+ }
+
protected StatusBarIconView addNotificationViews(IBinder key,
StatusBarNotification notification) {
if (DEBUG) {
@@ -1361,8 +1476,19 @@ protected StatusBarIconView addNotificationViews(IBinder key,
handleNotificationError(key, notification, "Couldn't create icon: " + ic);
return null;
}
+
+ NotificationData.Entry entry = new NotificationData.Entry(key, notification, iconView,
+ createRoundIcon(notification));
+ entry.hide = entry.notification.pkg.equals("com.paranoid.halo");
+
+ final PendingIntent contentIntent = notification.notification.contentIntent;
+ if (contentIntent != null) {
+ entry.floatingIntent = makeClicker(contentIntent,
+ notification.pkg, notification.tag, notification.id);
+ entry.floatingIntent.makeFloating(true);
+ }
+
// Construct the expanded view.
- NotificationData.Entry entry = new NotificationData.Entry(key, notification, iconView);
if (!inflateViews(entry, mPile)) {
handleNotificationError(key, notification, "Couldn't expand RemoteViews for: "
+ notification);
@@ -1392,6 +1518,7 @@ protected boolean expandView(NotificationData.Entry entry, boolean expand) {
lp.height = rowHeight;
}
entry.row.setLayoutParams(lp);
+ if (entry.hide) entry.row.setVisibility(View.GONE);
return expand;
}
@@ -1480,10 +1607,10 @@ public void updateNotification(IBinder key, StatusBarNotification notification)
boolean orderUnchanged = notification.notification.when==oldNotification.notification.when
&& notification.score == oldNotification.score;
// score now encompasses/supersedes isOngoing()
-
- boolean updateTicker = notification.notification.tickerText != null
+
+ boolean updateTicker = (notification.notification.tickerText != null
&& !TextUtils.equals(notification.notification.tickerText,
- oldEntry.notification.notification.tickerText);
+ oldEntry.notification.notification.tickerText)) || mHaloActive;
boolean isTopAnyway = isTopNotification(rowParent, oldEntry);
if (contentsUnchanged && bigContentsUnchanged && (orderUnchanged || isTopAnyway)) {
if (DEBUG) Slog.d(TAG, "reusing notification for key: " + key);
@@ -1494,15 +1621,22 @@ public void updateNotification(IBinder key, StatusBarNotification notification)
if (bigContentView != null && oldEntry.getLargeView() != null) {
bigContentView.reapply(mContext, oldEntry.getLargeView(), mOnClickHandler);
}
- // update the contentIntent
+ // update contentIntent and floatingIntent
final PendingIntent contentIntent = notification.notification.contentIntent;
if (contentIntent != null) {
final View.OnClickListener listener = makeClicker(contentIntent,
notification.pkg, notification.tag, notification.id);
oldEntry.content.setOnClickListener(listener);
+ oldEntry.floatingIntent = makeClicker(contentIntent,
+ notification.pkg, notification.tag, notification.id);
+ oldEntry.floatingIntent.makeFloating(true);
} else {
oldEntry.content.setOnClickListener(null);
+ oldEntry.floatingIntent = null;
}
+ // Update the roundIcon
+ oldEntry.roundIcon = createRoundIcon(notification);
+
// Update the icon.
final StatusBarIcon ic = new StatusBarIcon(notification.pkg,
notification.user,
View
17 packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java
@@ -17,10 +17,12 @@
package com.android.systemui.statusbar;
import android.app.Notification;
+import android.graphics.Bitmap;
import android.os.IBinder;
import android.view.View;
import android.widget.ImageView;
+import com.android.systemui.statusbar.BaseStatusBar.NotificationClicker;
import com.android.internal.statusbar.StatusBarNotification;
import com.android.systemui.R;
@@ -39,20 +41,35 @@
public View content; // takes the click events and sends the PendingIntent
public View expanded; // the inflated RemoteViews
public ImageView largeIcon;
+ protected boolean hide = false;
+ protected Bitmap roundIcon;
protected View expandedLarge;
+ protected NotificationClicker floatingIntent;
public Entry() {}
public Entry(IBinder key, StatusBarNotification n, StatusBarIconView ic) {
this.key = key;
this.notification = n;
this.icon = ic;
}
+ public Entry(IBinder key, StatusBarNotification n, StatusBarIconView ic, Bitmap ri) {
+ this.key = key;
+ this.notification = n;
+ this.icon = ic;
+ this.roundIcon = ri;
+ }
public void setLargeView(View expandedLarge) {
this.expandedLarge = expandedLarge;
writeBooleanTag(row, R.id.expandable_tag, expandedLarge != null);
}
public View getLargeView() {
return expandedLarge;
}
+ public NotificationClicker getFloatingIntent() {
+ return floatingIntent;
+ }
+ public Bitmap getRoundIcon() {
+ return roundIcon;
+ }
/**
* Return whether the entry can be expanded.
*/
View
90 packages/SystemUI/src/com/android/systemui/statusbar/halo/CustomObjectAnimator.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2013 ParanoidAndroid.
+ *
+ * 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.halo;
+
+import android.os.Handler;
+import android.animation.ObjectAnimator;
+import android.animation.TimeInterpolator;
+import android.animation.ValueAnimator.AnimatorUpdateListener;
+import android.animation.ValueAnimator;
+import android.animation.Animator;
+import android.view.View;
+
+public class CustomObjectAnimator {
+
+ private View rootView;
+ private Handler handler = new Handler();
+ private ObjectAnimator animator;
+
+ public CustomObjectAnimator(View root) {
+ rootView = root;
+ }
+
+ public void animate(ObjectAnimator newInstance, TimeInterpolator interpolator, AnimatorUpdateListener update) {
+ runAnimation(newInstance, interpolator, update, null);
+ }
+
+ public void animate(final ObjectAnimator newInstance, final TimeInterpolator interpolator,
+ final AnimatorUpdateListener update, long startDelay, final Runnable executeAfter) {
+
+ handler.postDelayed(new Runnable() {
+ public void run() {
+ runAnimation(newInstance, interpolator, update, executeAfter);
+ }}, startDelay);
+ }
+
+ private void runAnimation(ObjectAnimator newInstance, TimeInterpolator interpolator,
+ AnimatorUpdateListener update, final Runnable executeAfter) {
+
+ // Terminate old instance, if present
+ cancel(false);
+ animator = newInstance;
+
+ // Invalidate
+ if (update == null) {
+ animator.addUpdateListener(new AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(ValueAnimator animation) {
+ rootView.invalidate();
+ }});
+ } else {
+ animator.addUpdateListener(update);
+ }
+
+ animator.setInterpolator(interpolator);
+
+ if (executeAfter != null) {
+ animator.addListener(new Animator.AnimatorListener() {
+ boolean canceled = false;
+ @Override public void onAnimationRepeat(Animator animation) {}
+ @Override public void onAnimationStart(Animator animation) {}
+ @Override public void onAnimationCancel(Animator animation) {
+ canceled = true;
+ }
+ @Override public void onAnimationEnd(Animator animation) {
+ if (!canceled) executeAfter.run();
+ }});
+ }
+
+ animator.start();
+ }
+
+ public void cancel(boolean unschedule) {
+ if (unschedule) handler.removeCallbacksAndMessages(null);
+ if (animator != null) animator.cancel();
+ }
+}
View
1,092 packages/SystemUI/src/com/android/systemui/statusbar/halo/Halo.java
@@ -0,0 +1,1092 @@
+/*
+ * Copyright (C) 2013 ParanoidAndroid.
+ *
+ * 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.halo;
+
+import android.app.Activity;
+import android.app.ActivityManagerNative;
+import android.app.KeyguardManager;
+import android.app.PendingIntent;
+import android.app.Notification;
+import android.app.INotificationManager;
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
+import android.animation.ValueAnimator;
+import android.animation.ValueAnimator.AnimatorUpdateListener;
+import android.animation.Keyframe;
+import android.animation.PropertyValuesHolder;
+import android.content.Context;
+import android.content.ContentResolver;
+import android.content.res.Configuration;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.database.ContentObserver;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Rect;
+import android.graphics.Paint;
+import android.graphics.Paint.Style;
+import android.graphics.Point;
+import android.graphics.PixelFormat;
+import android.graphics.PorterDuff;
+import android.graphics.PorterDuff.Mode;
+import android.graphics.PorterDuffXfermode;
+import android.graphics.PorterDuffColorFilter;
+import android.graphics.Matrix;
+import android.os.Handler;
+import android.os.RemoteException;
+import android.os.Vibrator;
+import android.os.ServiceManager;
+import android.provider.Settings;
+import android.util.AttributeSet;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import android.view.animation.Animation;
+import android.view.animation.Animation.AnimationListener;
+import android.view.animation.AccelerateInterpolator;
+import android.view.animation.DecelerateInterpolator;
+import android.view.animation.AccelerateDecelerateInterpolator;
+import android.view.animation.OvershootInterpolator;
+import android.view.animation.AlphaAnimation;
+import android.view.animation.TranslateAnimation;
+import android.animation.TimeInterpolator;
+import android.view.Display;
+import android.view.View;
+import android.view.Gravity;
+import android.view.GestureDetector;
+import android.view.ViewGroup;
+import android.view.LayoutInflater;
+import android.view.MotionEvent;
+import android.view.WindowManager;
+import android.view.View.OnTouchListener;
+import android.view.ViewTreeObserver;
+import android.view.ViewTreeObserver.OnGlobalLayoutListener;
+import android.view.SoundEffectConstants;
+import android.view.Window;
+import android.view.WindowManager;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
+import android.widget.ImageView.ScaleType;
+import android.widget.TextView;
+import android.widget.LinearLayout;
+import android.widget.RelativeLayout;
+import android.widget.ScrollView;
+
+import com.android.systemui.R;
+import com.android.systemui.statusbar.BaseStatusBar.NotificationClicker;
+import com.android.internal.statusbar.StatusBarNotification;
+import com.android.internal.statusbar.StatusBarIcon;
+import com.android.systemui.statusbar.StatusBarIconView;
+import com.android.systemui.statusbar.NotificationData;
+import com.android.systemui.statusbar.BaseStatusBar;
+import com.android.systemui.statusbar.phone.Ticker;
+
+public class Halo extends FrameLayout implements Ticker.TickerCallback {
+
+ public static final String TAG = "HaloLauncher";
+
+ private static final boolean DEBUG = true;
+
+ enum State {
+ IDLE,
+ HIDDEN,
+ DRAG,
+ GESTURES
+ }
+
+ enum Gesture {
+ NONE,
+ TASK,
+ UP,
+ DOWN
+ }
+
+ private State mState = State.IDLE;
+ private Gesture mGesture = Gesture.NONE;
+
+ private WindowManager.LayoutParams mTriggerPos;
+ private HaloEffect mEffect;
+ private boolean mHideTicker;
+ private INotificationManager mNotificationManager;
+
+ private int id;
+ private String appName;
+
+ private Context mContext;
+ private PackageManager mPm;
+ private Handler mHandler;
+ private BaseStatusBar mBar;
+ private WindowManager mWindowManager;
+ private View mRoot;
+ private int mIconSize, mIconHalfSize;
+
+ private boolean isBeingDragged = false;
+ private boolean mHapticFeedback;
+ private Vibrator mVibrator;
+ private LayoutInflater mInflater;
+
+ private Display mDisplay;
+ private View mContent, mHaloContent;
+ private NotificationData.Entry mLastNotificationEntry = null;
+ private NotificationData.Entry mCurrentNotficationEntry = null;
+ private NotificationClicker mContentIntent, mTaskIntent;
+ private NotificationData mNotificationData;
+ private String mNotificationText = "";
+ private GestureDetector mGestureDetector;
+
+ private Paint mPaintHoloBlue = new Paint();
+ private Paint mPaintWhite = new Paint();
+ private Paint mPaintHoloRed = new Paint();
+
+ public boolean mExpanded = false;
+ public boolean mSnapped = true;
+
+ public boolean mFirstStart = true;
+ private boolean mInitialized = false;
+ private boolean mTickerLeft = true;
+ private boolean mIsNotificationNew = true;
+ private boolean mOverX = false;
+
+ private boolean mInteractionReversed = true;
+
+ private int mScreenWidth, mScreenHeight;
+
+ private int mKillX, mKillY;
+ private int mMarkerIndex = -1;
+
+ private SettingsObserver mSettingsObserver;
+
+ private final class SettingsObserver extends ContentObserver {
+ SettingsObserver(Handler handler) {
+ super(handler);
+ }
+
+ void observe() {
+ ContentResolver resolver = mContext.getContentResolver();
+ resolver.registerContentObserver(Settings.System.getUriFor(
+ Settings.System.HALO_REVERSED), false, this);
+ resolver.registerContentObserver(Settings.System.getUriFor(
+ Settings.System.HALO_HIDE), false, this);
+ resolver.registerContentObserver(Settings.System.getUriFor(
+ Settings.System.HAPTIC_FEEDBACK_ENABLED), false, this);
+ }
+
+ @Override
+ public void onChange(boolean selfChange) {
+ mInteractionReversed =
+ Settings.System.getInt(mContext.getContentResolver(), Settings.System.HALO_REVERSED, 1) == 1;
+ mHideTicker =
+ Settings.System.getInt(mContext.getContentResolver(), Settings.System.HALO_HIDE, 0) == 1;
+ mHapticFeedback = Settings.System.getInt(mContext.getContentResolver(),
+ Settings.System.HAPTIC_FEEDBACK_ENABLED, 1) != 0;
+
+ if (!selfChange) {
+ mEffect.wake();
+ mEffect.ping(mPaintHoloBlue, HaloEffect.WAKE_TIME);
+ mEffect.nap(HaloEffect.SNAP_TIME + 1000);
+ if (mHideTicker) mEffect.sleep(HaloEffect.SNAP_TIME + HaloEffect.NAP_TIME + 2500, HaloEffect.SLEEP_TIME);
+ }
+ }
+ }
+
+ public Halo(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public Halo(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ mContext = context;
+ mPm = mContext.getPackageManager();
+ mWindowManager = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);
+ mInflater = (LayoutInflater)mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+ mVibrator = (Vibrator) mContext.getSystemService(Context.VIBRATOR_SERVICE);
+ mNotificationManager = INotificationManager.Stub.asInterface(
+ ServiceManager.getService(Context.NOTIFICATION_SERVICE));
+ mDisplay = mWindowManager.getDefaultDisplay();
+ mGestureDetector = new GestureDetector(mContext, new GestureListener());
+ mHandler = new Handler();
+ mRoot = this;
+
+ mSettingsObserver = new SettingsObserver(new Handler());
+ mSettingsObserver.observe();
+ mSettingsObserver.onChange(true);
+
+ // Init variables
+ BitmapDrawable bd = (BitmapDrawable) mContext.getResources().getDrawable(R.drawable.halo_bg);
+ mIconSize = bd.getBitmap().getWidth();
+ mIconHalfSize = mIconSize / 2;
+
+ mTriggerPos = getWMParams();
+
+ // Init colors
+ mPaintHoloBlue.setAntiAlias(true);
+ mPaintHoloBlue.setColor(0xff33b5e5);
+ mPaintWhite.setAntiAlias(true);
+ mPaintWhite.setColor(0xfff0f0f0);
+ mPaintHoloRed.setAntiAlias(true);
+ mPaintHoloRed.setColor(0xffcc0000);
+
+ // Create effect layer
+ mEffect = new HaloEffect(mContext);
+ mEffect.setLayerType (View.LAYER_TYPE_HARDWARE, null);
+ mEffect.pingMinRadius = mIconHalfSize;
+ mEffect.pingMaxRadius = (int)(mIconSize * 1.1f);
+ WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
+ WindowManager.LayoutParams.MATCH_PARENT,
+ WindowManager.LayoutParams.MATCH_PARENT,
+ WindowManager.LayoutParams.TYPE_SYSTEM_ALERT,
+ WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
+ | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
+ | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
+ | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH
+ | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED,
+ PixelFormat.TRANSLUCENT);
+ lp.gravity = Gravity.LEFT|Gravity.TOP;
+ mWindowManager.addView(mEffect, lp);
+ }
+
+ private void initControl() {
+ if (mInitialized) return;
+
+ mInitialized = true;
+
+ // Get actual screen size
+ mScreenWidth = mEffect.getWidth();
+ mScreenHeight = mEffect.getHeight();
+
+ mKillX = mScreenWidth / 2;
+ mKillY = mIconHalfSize;
+
+ if (!mFirstStart) {
+ if (mEffect.getHaloY() < 0) mEffect.setHaloY(0);
+ if (mEffect.getHaloY() > mScreenHeight-mIconSize) mEffect.setHaloY(mScreenHeight-mIconSize);
+ mEffect.nap(500);
+ if (mHideTicker) mEffect.sleep(HaloEffect.SNAP_TIME + HaloEffect.NAP_TIME + 2500, HaloEffect.SLEEP_TIME);
+ } else {
+ // Do the startup animations only once
+ mFirstStart = false;
+ updateTriggerPosition(0, mScreenHeight / 2 - mIconHalfSize);
+ mEffect.setHaloX(0);
+ mEffect.setHaloY(mScreenHeight / 2 - mIconHalfSize);
+ mEffect.nap(500);
+ if (mHideTicker) mEffect.sleep(HaloEffect.SNAP_TIME + HaloEffect.NAP_TIME + 2500, HaloEffect.SLEEP_TIME);
+ }
+ }
+
+ private void updateTriggerPosition(int x, int y) {
+ try {
+ mTriggerPos.x = x;
+ mTriggerPos.y = y;
+ mWindowManager.updateViewLayout(mRoot, mTriggerPos);
+ } catch(Exception e) {
+ // Probably some animation still looking to move stuff around
+ }
+ }
+
+ private void loadLastNotification(boolean includeCurrentDismissable) {
+ if (mNotificationData.size() > 0) {
+ //oldEntry = mLastNotificationEntry;
+ mLastNotificationEntry = mNotificationData.get(mNotificationData.size() - 1);
+
+ // If the current notification is dismissable we might want to skip it if so desired
+ if (!includeCurrentDismissable) {
+ if (mNotificationData.size() > 1 && mLastNotificationEntry != null &&
+ mLastNotificationEntry.notification == mCurrentNotficationEntry.notification) {
+ boolean cancel = (mLastNotificationEntry.notification.notification.flags &
+ Notification.FLAG_AUTO_CANCEL) == Notification.FLAG_AUTO_CANCEL;
+ if (cancel) mLastNotificationEntry = mNotificationData.get(mNotificationData.size() - 2);
+ } else if (mNotificationData.size() == 1) {
+ boolean cancel = (mLastNotificationEntry.notification.notification.flags &
+ Notification.FLAG_AUTO_CANCEL) == Notification.FLAG_AUTO_CANCEL;
+ if (cancel) {
+ // We have one notification left and it is dismissable, clear it...
+ clearTicker();
+ return;
+ }
+ }
+ }
+
+ if (mLastNotificationEntry.notification != null
+ && mLastNotificationEntry.notification.notification != null
+ && mLastNotificationEntry.notification.notification.tickerText != null) {
+ mNotificationText = mLastNotificationEntry.notification.notification.tickerText.toString();
+ }
+
+ tick(mLastNotificationEntry, "", 0, 0);
+ } else {
+ clearTicker();
+ }
+ }
+
+ public void setStatusBar(BaseStatusBar bar) {
+ mBar = bar;
+ if (mBar.getTicker() != null) mBar.getTicker().setUpdateEvent(this);
+ mNotificationData = mBar.getNotificationData();
+ loadLastNotification(true);
+ }
+
+ void launchTask(NotificationClicker intent) {
+
+ // Do not launch tasks in hidden state
+ if (mState == State.HIDDEN) return;
+
+ try {
+ ActivityManagerNative.getDefault().resumeAppSwitches();
+ ActivityManagerNative.getDefault().dismissKeyguardOnNextActivity();
+ } catch (RemoteException e) {
+ // ...
+ }
+
+ if (intent!= null) {
+ intent.onClick(mRoot);
+ }
+ }
+
+ class GestureListener extends GestureDetector.SimpleOnGestureListener {
+
+ @Override
+ public boolean onSingleTapUp (MotionEvent event) {
+ playSoundEffect(SoundEffectConstants.CLICK);
+ return true;
+ }
+
+ @Override
+ public boolean onFling(MotionEvent event1, MotionEvent event2,
+ float velocityX, float velocityY) {
+ return true;
+ }
+
+ @Override
+ public boolean onSingleTapConfirmed(MotionEvent event) {
+ if (mState != State.DRAG) {
+ launchTask(mContentIntent);
+ }
+ return true;
+ }
+
+ @Override
+ public boolean onDoubleTap(MotionEvent event) {
+ if (!mInteractionReversed) {
+ mState = State.GESTURES;
+ mEffect.wake();
+ mBar.setHaloTaskerActive(true, true);
+ } else {
+ // Move
+ mState = State.DRAG;
+ mEffect.intro();
+ }
+ return true;
+ }
+ }
+
+ void resetIcons() {
+ final float originalAlpha = mContext.getResources().getFraction(R.dimen.status_bar_icon_drawing_alpha, 1, 1);
+ for (int i = 0; i < mNotificationData.size(); i++) {
+ NotificationData.Entry entry = mNotificationData.get(i);
+ entry.icon.setAlpha(originalAlpha);
+ }
+ }
+
+ void setIcon(int index) {
+ float originalAlpha = mContext.getResources().getFraction(R.dimen.status_bar_icon_drawing_alpha, 1, 1);
+ for (int i = 0; i < mNotificationData.size(); i++) {
+ NotificationData.Entry entry = mNotificationData.get(i);
+ entry.icon.setAlpha(index == i ? 1f : originalAlpha);
+ }
+ }
+
+ private float initialX = 0;
+ private float initialY = 0;
+ private int oldIconIndex = -1;
+ private boolean hiddenState = false;
+
+ @Override
+ public boolean onTouchEvent(MotionEvent event) {
+ mGestureDetector.onTouchEvent(event);
+
+ final int action = event.getAction();
+ switch(action) {
+ case MotionEvent.ACTION_DOWN:
+
+ // Stop HALO from moving around, unschedule sleeping patterns
+ if (mState != State.GESTURES) mEffect.unscheduleSleep();
+
+ mMarkerIndex = -1;
+ oldIconIndex = -1;
+ mGesture = Gesture.NONE;
+ hiddenState = mState == State.HIDDEN;
+ if (mState == State.HIDDEN) {
+ mEffect.wake();
+ if (mHideTicker) {
+ mEffect.sleep(2500, HaloEffect.SLEEP_TIME);
+ } else {
+ mEffect.nap(2500);
+ }
+ return true;
+ }
+
+ initialX = event.getRawX();
+ initialY = event.getRawY();
+ break;
+ case MotionEvent.ACTION_CANCEL:
+ case MotionEvent.ACTION_UP:
+ if (hiddenState) break;
+
+ resetIcons();
+ mBar.setHaloTaskerActive(false, true);
+ mEffect.setHaloOverlay(HaloProperties.Overlay.NONE, 0f);
+ updateTriggerPosition(mEffect.getHaloX(), mEffect.getHaloY());
+
+ mEffect.outro();
+ mEffect.killTicker();
+ mEffect.unscheduleSleep();
+
+ // Do we erase ourselves?
+ if (mOverX) {
+ Settings.System.putInt(mContext.getContentResolver(),
+ Settings.System.HALO_ACTIVE, 0);
+ return true;
+ }
+
+ if (mGesture == Gesture.TASK) {
+ // Launch tasks
+ if (mTaskIntent != null) {
+ playSoundEffect(SoundEffectConstants.CLICK);
+ launchTask(mTaskIntent);
+ }
+ mEffect.nap(0);
+ if (mHideTicker) mEffect.sleep(HaloEffect.NAP_TIME + 1500, HaloEffect.SLEEP_TIME);
+ } else if (mGesture == Gesture.DOWN) {
+ // Hide from sight
+ playSoundEffect(SoundEffectConstants.CLICK);
+ mEffect.sleep(0, HaloEffect.NAP_TIME / 2);
+ } else if (mGesture == Gesture.UP) {
+ // Dismiss notification
+ playSoundEffect(SoundEffectConstants.CLICK);
+
+ if (mContentIntent != null) {
+ try {
+ mBar.getService().onNotificationClear(mContentIntent.mPkg, mContentIntent.mTag, mContentIntent.mId);
+ } catch (RemoteException ex) {
+ // system process is dead if we're here.
+ }
+ }
+
+ // Find next entry
+ NotificationData.Entry entry = null;
+ if (mNotificationData.size() > 0) {
+ for (int i = mNotificationData.size() - 1; i >= 0; i--) {
+ NotificationData.Entry item = mNotificationData.get(i);
+ if (mCurrentNotficationEntry != null
+ && mCurrentNotficationEntry.notification == item.notification) {
+ continue;
+ }
+ boolean cancel = (item.notification.notification.flags &
+ Notification.FLAG_AUTO_CANCEL) == Notification.FLAG_AUTO_CANCEL;
+ if (cancel) {
+ entry = item;
+ break;
+ }
+ }
+ }
+
+ // When no entry was found, take the last one
+ if (entry == null && mNotificationData.size() > 0) {
+ loadLastNotification(false);
+ } else {
+ tick(entry, "", 0, 0);
+ }
+
+ mEffect.nap(1500);
+ if (mHideTicker) mEffect.sleep(HaloEffect.NAP_TIME + 3000, HaloEffect.SLEEP_TIME);
+ } else {
+ // No gesture, just snap HALO
+ mEffect.snap(0);
+ mEffect.nap(HaloEffect.SNAP_TIME + 1000);
+ if (mHideTicker) mEffect.sleep(HaloEffect.SNAP_TIME + HaloEffect.NAP_TIME + 2500, HaloEffect.SLEEP_TIME);
+ }
+
+ mState = State.IDLE;
+ mGesture = Gesture.NONE;
+ break;
+
+ case MotionEvent.ACTION_MOVE:
+ if (hiddenState) break;
+
+ float distanceX = mKillX-event.getRawX();
+ float distanceY = mKillY-event.getRawY();
+ float distanceToKill = (float)Math.sqrt(Math.pow(distanceX, 2) + Math.pow(distanceY, 2));
+
+ distanceX = initialX-event.getRawX();
+ distanceY = initialY-event.getRawY();
+ float initialDistance = (float)Math.sqrt(Math.pow(distanceX, 2) + Math.pow(distanceY, 2));
+
+ if (mState != State.GESTURES) {
+ // Check kill radius
+ if (distanceToKill < mIconSize) {
+ // Magnetize X
+ mEffect.setHaloX((int)mKillX - mIconHalfSize);
+ mEffect.setHaloY((int)(mKillY - mIconHalfSize));
+
+ if (!mOverX) {
+ if (mHapticFeedback) mVibrator.vibrate(25);
+ mEffect.ping(mPaintHoloRed, 0);
+ mEffect.setHaloOverlay(HaloProperties.Overlay.BLACK_X, 1f);
+ mOverX = true;
+ }
+
+ return false;
+ } else {
+ if (mOverX) mEffect.setHaloOverlay(HaloProperties.Overlay.NONE, 0f);
+ mOverX = false;
+ }
+
+ // Drag
+ if (mState != State.DRAG) {
+ if (initialDistance > mIconSize * 0.7f) {
+ if (mInteractionReversed) {
+ mState = State.GESTURES;
+ mEffect.wake();
+ mBar.setHaloTaskerActive(true, true);
+ } else {
+ mState = State.DRAG;
+ mEffect.intro();
+ if (mHapticFeedback) mVibrator.vibrate(25);
+ }
+ }
+ } else {
+ int posX = (int)event.getRawX() - mIconHalfSize;
+ int posY = (int)event.getRawY() - mIconHalfSize;
+ if (posX < 0) posX = 0;
+ if (posY < 0) posY = 0;
+ if (posX > mScreenWidth-mIconSize) posX = mScreenWidth-mIconSize;
+ if (posY > mScreenHeight-mIconSize) posY = mScreenHeight-mIconSize;
+ mEffect.setHaloX(posX);
+ mEffect.setHaloY(posY);
+
+ // Update resources when the side changes
+ boolean oldTickerPos = mTickerLeft;
+ mTickerLeft = (posX + mIconHalfSize < mScreenWidth / 2);
+ if (oldTickerPos != mTickerLeft) {
+ mEffect.updateResources();
+ }
+ }
+ } else {
+ // We have three basic gestures, one horizontal for switching through tasks and
+ // two vertical for dismissing tasks or making HALO fall asleep
+
+ int deltaX = (int)(mTickerLeft ? event.getRawX() : mScreenWidth - event.getRawX());
+ int deltaY = (int)(mEffect.getHaloY() - event.getRawY() + mIconSize);
+ int horizontalThreshold = (int)(mIconSize * 1.5f);
+ int verticalThreshold = mIconSize;
+
+ // Switch icons
+ if (deltaX > horizontalThreshold) {
+ if (mGesture != Gesture.TASK) mEffect.setHaloOverlay(HaloProperties.Overlay.NONE, 0f);
+
+ mGesture = Gesture.TASK;
+
+ deltaX -= horizontalThreshold;
+ if (mNotificationData != null && mNotificationData.size() > 0) {
+ int items = mNotificationData.size();
+
+ // This will be the lenght we are going to use
+ int indexLength = (mScreenWidth - mIconSize * 2) / items;
+
+ // Calculate index
+ mMarkerIndex = mTickerLeft ? (items - deltaX / indexLength) - 1 : (deltaX / indexLength);
+
+ // Watch out for margins!