├── assets
└── xposed_init
├── ic_launcher-web.png
├── res
├── values
│ ├── strings.xml
│ └── styles.xml
├── drawable-hdpi
│ └── ic_launcher.png
├── drawable-mdpi
│ └── ic_launcher.png
├── drawable-xhdpi
│ └── ic_launcher.png
└── drawable-xxhdpi
│ └── ic_launcher.png
├── libs
└── android-support-v4.jar
├── .classpath
├── project.properties
├── proguard-project.txt
├── AndroidManifest.xml
├── .project
└── src
└── com
└── mohammadag
└── statusbarscrolltotop
└── XposedMod.java
/assets/xposed_init:
--------------------------------------------------------------------------------
1 | com.mohammadag.statusbarscrolltotop.XposedMod
--------------------------------------------------------------------------------
/ic_launcher-web.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MohammadAG/Xposed-StatusBar-Scroll-To-Top/HEAD/ic_launcher-web.png
--------------------------------------------------------------------------------
/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | Statusbar Scroll to Top
3 |
4 |
--------------------------------------------------------------------------------
/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/libs/android-support-v4.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MohammadAG/Xposed-StatusBar-Scroll-To-Top/HEAD/libs/android-support-v4.jar
--------------------------------------------------------------------------------
/res/drawable-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MohammadAG/Xposed-StatusBar-Scroll-To-Top/HEAD/res/drawable-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/res/drawable-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MohammadAG/Xposed-StatusBar-Scroll-To-Top/HEAD/res/drawable-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/res/drawable-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MohammadAG/Xposed-StatusBar-Scroll-To-Top/HEAD/res/drawable-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/res/drawable-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MohammadAG/Xposed-StatusBar-Scroll-To-Top/HEAD/res/drawable-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/.classpath:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/project.properties:
--------------------------------------------------------------------------------
1 | # This file is automatically generated by Android Tools.
2 | # Do not modify this file -- YOUR CHANGES WILL BE ERASED!
3 | #
4 | # This file must be checked in Version Control Systems.
5 | #
6 | # To customize properties used by the Ant build system edit
7 | # "ant.properties", and override values to adapt the script to your
8 | # project structure.
9 | #
10 | # To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home):
11 | #proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt
12 |
13 | # Project target.
14 | target=android-19
15 |
--------------------------------------------------------------------------------
/proguard-project.txt:
--------------------------------------------------------------------------------
1 | # To enable ProGuard in your project, edit project.properties
2 | # to define the proguard.config property as described in that file.
3 | #
4 | # Add project specific ProGuard rules here.
5 | # By default, the flags in this file are appended to flags specified
6 | # in ${sdk.dir}/tools/proguard/proguard-android.txt
7 | # You can edit the include path and order by changing the ProGuard
8 | # include property in project.properties.
9 | #
10 | # For more details, see
11 | # http://developer.android.com/guide/developing/tools/proguard.html
12 |
13 | # Add any project specific keep options here:
14 |
15 | # If your project uses WebView with JS, uncomment the following
16 | # and specify the fully qualified class name to the JavaScript interface
17 | # class:
18 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
19 | # public *;
20 | #}
21 |
--------------------------------------------------------------------------------
/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
5 |
6 |
9 |
10 |
15 |
18 |
21 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/.project:
--------------------------------------------------------------------------------
1 |
2 |
3 | StatusbarScrollToTop
4 |
5 |
6 |
7 |
8 |
9 | com.android.ide.eclipse.adt.ResourceManagerBuilder
10 |
11 |
12 |
13 |
14 | com.android.ide.eclipse.adt.PreCompilerBuilder
15 |
16 |
17 |
18 |
19 | org.eclipse.jdt.core.javabuilder
20 |
21 |
22 |
23 |
24 | com.android.ide.eclipse.adt.ApkBuilder
25 |
26 |
27 |
28 |
29 |
30 | com.android.ide.eclipse.adt.AndroidNature
31 | org.eclipse.jdt.core.javanature
32 |
33 |
34 |
--------------------------------------------------------------------------------
/src/com/mohammadag/statusbarscrolltotop/XposedMod.java:
--------------------------------------------------------------------------------
1 | package com.mohammadag.statusbarscrolltotop;
2 |
3 | import static de.robv.android.xposed.XposedHelpers.findAndHookMethod;
4 | import static de.robv.android.xposed.XposedHelpers.findClass;
5 |
6 | import java.util.ArrayList;
7 |
8 | import android.app.Activity;
9 | import android.content.BroadcastReceiver;
10 | import android.content.Context;
11 | import android.content.Intent;
12 | import android.content.IntentFilter;
13 | import android.graphics.Rect;
14 | import android.view.MotionEvent;
15 | import android.view.View;
16 | import android.widget.AbsListView;
17 | import android.widget.ScrollView;
18 | import de.robv.android.xposed.IXposedHookLoadPackage;
19 | import de.robv.android.xposed.IXposedHookZygoteInit;
20 | import de.robv.android.xposed.XC_MethodHook;
21 | import de.robv.android.xposed.XposedBridge;
22 | import de.robv.android.xposed.XposedHelpers;
23 | import de.robv.android.xposed.callbacks.XC_LoadPackage.LoadPackageParam;
24 |
25 | public class XposedMod implements IXposedHookLoadPackage, IXposedHookZygoteInit {
26 | /* My name's here so we don't conflict with other fields, deal with it :p */
27 | private static final String KEY_RECEIVERS = "mMohammadAG_scrolToTopReceivers";
28 |
29 | /* You can trigger this with any app that can fire intents! */
30 | private static final String INTENT_SCROLL_TO_TOP = "com.mohammadag.statusbarscrolltotop.SCROLL_TO_TOP";
31 |
32 | /* We get a MotionEvent when the status bar is tapped, we need to know if it's a click or a drag */
33 | private float mDownX;
34 | private float mDownY;
35 | private final float SCROLL_THRESHOLD = 10;
36 | private boolean mIsClick;
37 |
38 | /* Some sort of crappy IPC */
39 | class ScrollViewReceiver extends BroadcastReceiver {
40 | private ScrollView mScrollView;
41 | public ScrollViewReceiver(ScrollView view) {
42 | mScrollView = view;
43 | }
44 |
45 | @Override
46 | public void onReceive(Context context, Intent intent) {
47 | if (isViewInViewBounds(getContentViewFromContext(mScrollView.getContext()), mScrollView))
48 | mScrollView.smoothScrollTo(0, 0);
49 | }
50 | };
51 |
52 | class AbsListViewReceiver extends BroadcastReceiver {
53 | private AbsListView mListView;
54 | public AbsListViewReceiver(AbsListView view) {
55 | mListView = view;
56 | }
57 |
58 | @Override
59 | public void onReceive(Context context, Intent intent) {
60 | if (isViewInViewBounds(getContentViewFromContext(mListView.getContext()), mListView))
61 | mListView.smoothScrollToPosition(0);
62 | }
63 | }
64 |
65 | @Override
66 | public void initZygote(StartupParam startupParam) throws Throwable {
67 | /* AbsListView, it's one instance of a scroller */
68 | findAndHookMethod(AbsListView.class, "initAbsListView", new XC_MethodHook() {
69 | @Override
70 | protected void afterHookedMethod(MethodHookParam param) throws Throwable {
71 | AbsListView view = (AbsListView) param.thisObject;
72 | if (!(view.getContext() instanceof Activity))
73 | return;
74 | Activity activity = (Activity) view.getContext();
75 | AbsListViewReceiver receiver = new AbsListViewReceiver(view);
76 | addReceiverToActivity(activity, receiver);
77 | activity.registerReceiver(receiver, new IntentFilter(INTENT_SCROLL_TO_TOP));
78 | }
79 | });
80 |
81 | /* Another one */
82 | findAndHookMethod(ScrollView.class, "initScrollView", new XC_MethodHook() {
83 | @Override
84 | protected void afterHookedMethod(MethodHookParam param) throws Throwable {
85 | ScrollView view = (ScrollView) param.thisObject;
86 | if (!(view.getContext() instanceof Activity))
87 | return;
88 | Activity activity = (Activity) view.getContext();
89 | ScrollViewReceiver receiver = new ScrollViewReceiver(view);
90 | addReceiverToActivity(activity, receiver);
91 | activity.registerReceiver(receiver, new IntentFilter(INTENT_SCROLL_TO_TOP));
92 | }
93 | });
94 |
95 | /* FYI, there are some manufacturer specific ones, like Samsung's TouchWiz ones.
96 | * I'll look into those later on...
97 | */
98 |
99 | /* We need to register and unregister receivers in onPause and onResume
100 | * otherwise, Android bitches about it (for good (memory) reason(s) probably). We do that
101 | * by keeping an ArrayList of BroadcastReceivers in all activities. There might be a better
102 | * way...
103 | */
104 | Class> ActivityClass = findClass("android.app.Activity", null);
105 | findAndHookMethod(ActivityClass, "onResume", new XC_MethodHook() {
106 | @Override
107 | protected void afterHookedMethod(MethodHookParam param) throws Throwable {
108 | Activity activity = (Activity) param.thisObject;
109 | resumeBroadcastReceivers(activity);
110 | }
111 | });
112 |
113 | findAndHookMethod(ActivityClass, "onPause", new XC_MethodHook() {
114 | @Override
115 | protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
116 | Activity activity = (Activity) param.thisObject;
117 | pauseBroadcastReceivers(activity);
118 | }
119 | });
120 | }
121 |
122 | @Override
123 | public void handleLoadPackage(LoadPackageParam lpparam) throws Throwable {
124 | if (!lpparam.packageName.equals("com.android.systemui"))
125 | return;
126 |
127 | Class> StatusBarWindowView = findClass("com.android.systemui.statusbar.phone.StatusBarWindowView",
128 | lpparam.classLoader);
129 |
130 | findAndHookMethod(StatusBarWindowView, "onInterceptTouchEvent", MotionEvent.class, new XC_MethodHook() {
131 | @Override
132 | protected void afterHookedMethod(MethodHookParam param) throws Throwable {
133 | MotionEvent ev = (MotionEvent) param.args[0];
134 | View view = (View) param.thisObject;
135 | switch (ev.getAction() & MotionEvent.ACTION_MASK) {
136 | case MotionEvent.ACTION_DOWN:
137 | mDownX = ev.getX();
138 | mDownY = ev.getY();
139 | mIsClick = true;
140 | break;
141 | case MotionEvent.ACTION_CANCEL:
142 | case MotionEvent.ACTION_UP:
143 | if (mIsClick) {
144 | try {
145 | /* Get NotificationPanelView instance, it subclasses PanelView */
146 | Object notificationPanelView =
147 | XposedHelpers.getObjectField(param.thisObject, "mNotificationPanel");
148 |
149 | float expandedFraction = (Float) XposedHelpers.callMethod(notificationPanelView,
150 | "getExpandedFraction");
151 |
152 | if (expandedFraction < 0.1)
153 | view.getContext().sendBroadcast(new Intent(INTENT_SCROLL_TO_TOP));
154 | } catch (Throwable t) {
155 | XposedBridge.log("StatusBarScrollToTop: Unable to determine expanded fraction: " + t.getMessage());
156 | t.printStackTrace();
157 | view.getContext().sendBroadcast(new Intent(INTENT_SCROLL_TO_TOP));
158 | }
159 | }
160 | break;
161 | case MotionEvent.ACTION_MOVE:
162 | if (mIsClick && (Math.abs(mDownX - ev.getX()) > SCROLL_THRESHOLD || Math.abs(mDownY - ev.getY()) > SCROLL_THRESHOLD)) {
163 | mIsClick = false;
164 | }
165 | break;
166 | default:
167 | break;
168 | }
169 | }
170 | });
171 | }
172 |
173 | /* Helpers so the code looks less like shit */
174 | private static void addReceiverToActivity(Activity activity, BroadcastReceiver receiver) {
175 | ArrayList receivers = getReceiversForActivity(activity);
176 | receivers.add(receiver);
177 | XposedHelpers.setAdditionalInstanceField(activity, KEY_RECEIVERS, receivers);
178 | }
179 |
180 | @SuppressWarnings("unchecked")
181 | private static ArrayList getReceiversForActivity(Activity activity) {
182 | ArrayList receivers = null;
183 | try {
184 | receivers = (ArrayList) XposedHelpers.getAdditionalInstanceField(activity,
185 | KEY_RECEIVERS);
186 | } catch (Throwable t) {
187 | t.printStackTrace();
188 | }
189 |
190 | if (receivers == null) {
191 | receivers = new ArrayList();
192 | }
193 |
194 | return receivers;
195 | }
196 |
197 | private static void resumeBroadcastReceivers(Activity activity) {
198 | ArrayList receivers = getReceiversForActivity(activity);
199 | for (BroadcastReceiver receiver : receivers) {
200 | activity.registerReceiver(receiver, new IntentFilter(INTENT_SCROLL_TO_TOP));
201 | }
202 | }
203 |
204 | private static void pauseBroadcastReceivers(Activity activity) {
205 | ArrayList receivers = getReceiversForActivity(activity);
206 | for (BroadcastReceiver receiver : receivers) {
207 | activity.unregisterReceiver(receiver);
208 | }
209 | }
210 |
211 | /* Check if the View is visible to the user, i.e on screen.
212 | * We do this since in tabbed interfaces, we can cause a scrollbar that's off screen
213 | * to go to the top as well as the visible one.
214 | */
215 | private static boolean isViewInViewBounds(View mainView, View view) {
216 | /* Failsafe */
217 | if (mainView == null)
218 | return true;
219 |
220 | Rect bounds = new Rect();
221 | mainView.getHitRect(bounds);
222 | return view.getLocalVisibleRect(bounds);
223 | }
224 |
225 | /* This works because Context is actually Activity downcasted */
226 | private static View getContentViewFromContext(Context context) {
227 | if (!(context instanceof Activity))
228 | return null;
229 |
230 | return ((Activity) context).findViewById(android.R.id.content);
231 | }
232 |
233 | /* And that's a wrap */
234 | }
235 |
--------------------------------------------------------------------------------