Browse Source

暂时提交

liuyuqi-dellpc 6 years ago
parent
commit
7d4ec2d054

+ 1 - 0
.gitignore

@@ -9,3 +9,4 @@
 .externalNativeBuild
 /.idea
 /.vs
+/docs

+ 153 - 2
app/src/main/java/me/yoqi/app/wxredpacket/activities/MainActivity.java

@@ -1,15 +1,166 @@
 package me.yoqi.app.wxredpacket.activities;
 
-import android.support.v7.app.AppCompatActivity;
+import android.accessibilityservice.AccessibilityServiceInfo;
+import android.annotation.TargetApi;
+import android.app.Activity;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Build;
 import android.os.Bundle;
+import android.preference.PreferenceManager;
+import android.provider.Settings;
+import android.view.View;
+import android.view.Window;
+import android.view.WindowManager;
+import android.view.accessibility.AccessibilityManager;
+import android.widget.ImageView;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import com.tencent.bugly.Bugly;
+
+import java.util.List;
 
 import me.yoqi.app.wxredpacket.R;
+import me.yoqi.app.wxredpacket.utils.ConnectivityUtil;
+import me.yoqi.app.wxredpacket.utils.UpdateTask;
+
+public class MainActivity extends Activity implements AccessibilityManager.AccessibilityStateChangeListener {
 
-public class MainActivity extends AppCompatActivity {
+    //开关切换按钮
+    private TextView pluginStatusText;
+    private ImageView pluginStatusIcon;
+    private AccessibilityManager accessibilityManager;
 
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
+        Bugly.init(getApplicationContext(), "9410cbd743", false);
         setContentView(R.layout.activity_main);
+        pluginStatusText = findViewById(R.id.layout_control_accessibility_text);
+        pluginStatusIcon = findViewById(R.id.layout_control_accessibility_icon);
+
+        handleMaterialStatusBar();
+
+        explicitlyLoadPreferences();
+
+        //监听AccessibilityService 变化
+        accessibilityManager = (AccessibilityManager) getSystemService(Context.ACCESSIBILITY_SERVICE);
+        accessibilityManager.addAccessibilityStateChangeListener(this);
+        updateServiceStatus();
+    }
+
+    private void explicitlyLoadPreferences() {
+        PreferenceManager.setDefaultValues(this, R.xml.general_preferences, false);
+    }
+
+    /**
+     * 适配MIUI沉浸状态栏
+     */
+    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
+    private void handleMaterialStatusBar() {
+        // Not supported in APK level lower than 21
+        if (android.os.Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) return;
+
+        Window window = this.getWindow();
+
+        window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
+
+        window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
+
+        window.setStatusBarColor(0xffE46C62);
+
+    }
+
+    @Override
+    protected void onPause() {
+        super.onPause();
+    }
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+
+        updateServiceStatus();
+        // Check for update when WIFI is connected or on first time.
+        if (ConnectivityUtil.isWifi(this) || UpdateTask.count == 0)
+            new UpdateTask(this, false).update();
+    }
+
+    @Override
+    protected void onDestroy() {
+        //移除监听服务
+        accessibilityManager.removeAccessibilityStateChangeListener(this);
+        super.onDestroy();
+    }
+
+    public void openAccessibility(View view) {
+        try {
+            Toast.makeText(this, getString(R.string.turn_on_toast) + pluginStatusText.getText(), Toast.LENGTH_SHORT).show();
+            Intent accessibleIntent = new Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS);
+            startActivity(accessibleIntent);
+        } catch (Exception e) {
+            Toast.makeText(this, getString(R.string.turn_on_error_toast), Toast.LENGTH_LONG).show();
+            e.printStackTrace();
+        }
+
+    }
+
+    public void openGitHub(View view) {
+        Intent webViewIntent = new Intent(this, WebViewActivity.class);
+        webViewIntent.putExtra("title", getString(R.string.webview_github_title));
+        webViewIntent.putExtra("url", "https://github.com/geeeeeeeeek/WeChatLuckyMoney");
+        startActivity(webViewIntent);
+    }
+
+    public void openUber(View view) {
+        Intent webViewIntent = new Intent(this, WebViewActivity.class);
+        webViewIntent.putExtra("title", getString(R.string.webview_uber_title));
+        String[] couponList = new String[]{"https://dc.tt/oTLtXH2BHsD", "https://dc.tt/ozFJHDnfLky"};
+        int index = (int) (Math.random() * 2);
+        webViewIntent.putExtra("url", couponList[index]);
+        startActivity(webViewIntent);
+    }
+
+    public void openSettings(View view) {
+        Intent settingsIntent = new Intent(this, SettingsActivity.class);
+        settingsIntent.putExtra("title", getString(R.string.preference));
+        settingsIntent.putExtra("frag_id", "GeneralSettingsFragment");
+        startActivity(settingsIntent);
+    }
+
+
+    @Override
+    public void onAccessibilityStateChanged(boolean enabled) {
+        updateServiceStatus();
+    }
+
+    /**
+     * 更新当前 HongbaoService 显示状态
+     */
+    private void updateServiceStatus() {
+        if (isServiceEnabled()) {
+            pluginStatusText.setText(R.string.service_off);
+            pluginStatusIcon.setBackgroundResource(R.mipmap.ic_stop);
+        } else {
+            pluginStatusText.setText(R.string.service_on);
+            pluginStatusIcon.setBackgroundResource(R.mipmap.ic_start);
+        }
+    }
+
+    /**
+     * 获取 HongbaoService 是否启用状态
+     *
+     * @return
+     */
+    private boolean isServiceEnabled() {
+        List<AccessibilityServiceInfo> accessibilityServices =
+                accessibilityManager.getEnabledAccessibilityServiceList(AccessibilityServiceInfo.FEEDBACK_GENERIC);
+        for (AccessibilityServiceInfo info : accessibilityServices) {
+            if (info.getId().equals(getPackageName() + "/.services.HongbaoService")) {
+                return true;
+            }
+        }
+        return false;
     }
 }

+ 91 - 0
app/src/main/java/me/yoqi/app/wxredpacket/activities/SeekBarPreference.java

@@ -0,0 +1,91 @@
+package me.yoqi.app.wxredpacket.activities;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.preference.DialogPreference;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.SeekBar;
+import android.widget.TextView;
+
+import xyz.monkeytong.hongbao.R;
+
+/**
+ * Created by Zhongyi on 2/3/16.
+ */
+public class SeekBarPreference extends DialogPreference {
+    private SeekBar seekBar;
+    private TextView textView;
+    private String hintText, prefKind;
+
+    public SeekBarPreference(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        setDialogLayoutResource(R.layout.preference_seekbar);
+
+        for (int i = 0; i < attrs.getAttributeCount(); i++) {
+            String attr = attrs.getAttributeName(i);
+            if (attr.equalsIgnoreCase("pref_kind")) {
+                prefKind = attrs.getAttributeValue(i);
+                break;
+            }
+        }
+        if (prefKind.equals("pref_open_delay")) {
+            hintText = getContext().getString(R.string.delay_open);
+        } else if (prefKind.equals("pref_comment_delay")) {
+            hintText = "发送回复(暂不支持延时)";
+        }
+    }
+
+    @Override
+    protected void onBindDialogView(View view) {
+        super.onBindDialogView(view);
+
+        SharedPreferences pref = getSharedPreferences();
+
+        int delay = pref.getInt(prefKind, 0);
+        this.seekBar = view.findViewById(R.id.delay_seekBar);
+        this.seekBar.setProgress(delay);
+
+        if (prefKind.equals("pref_comment_delay")) {
+            this.seekBar.setEnabled(false);
+        }
+
+        this.textView = view.findViewById(R.id.pref_seekbar_textview);
+        setHintText(0);
+
+        this.seekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
+            @Override
+            public void onProgressChanged(SeekBar seekBar, int i, boolean b) {
+                setHintText(i);
+            }
+
+            @Override
+            public void onStartTrackingTouch(SeekBar seekBar) {
+
+            }
+
+            @Override
+            public void onStopTrackingTouch(SeekBar seekBar) {
+
+            }
+        });
+    }
+
+    @Override
+    protected void onDialogClosed(boolean positiveResult) {
+        if (positiveResult) {
+            SharedPreferences.Editor editor = getEditor();
+            editor.putInt(prefKind, this.seekBar.getProgress());
+            editor.commit();
+        }
+        super.onDialogClosed(positiveResult);
+    }
+
+    private void setHintText(int delay) {
+        if (delay == 0) {
+            this.textView.setText(getContext().getString(R.string.delay_instantly) + hintText);
+        } else {
+            this.textView.setText(getContext().getString(R.string.delay_delay) + delay + getContext().getString(R.string.delay_sec) + getContext().getString(R.string.delay_then) + hintText);
+        }
+    }
+}

+ 78 - 5
app/src/main/java/me/yoqi/app/wxredpacket/activities/SettingsActivity.java

@@ -1,15 +1,88 @@
 package me.yoqi.app.wxredpacket.activities;
 
+import android.annotation.TargetApi;
+import android.app.FragmentManager;
+import android.app.FragmentTransaction;
+import android.content.Intent;
+import android.os.Build;
 import android.os.Bundle;
-import android.support.v7.app.AppCompatActivity;
+import android.provider.Settings;
+import android.support.v4.app.FragmentActivity;
+import android.view.View;
+import android.view.Window;
+import android.view.WindowManager;
+import android.widget.TextView;
+import android.widget.Toast;
 
-import me.yoqi.app.wxredpacket.R;
-
-public class SettingsActivity extends AppCompatActivity {
+import xyz.monkeytong.hongbao.R;
+import xyz.monkeytong.hongbao.fragments.CommentSettingsFragment;
+import xyz.monkeytong.hongbao.fragments.GeneralSettingsFragment;
 
+/**
+ * Created by Zhongyi on 1/19/16.
+ * Settings page.
+ */
+public class SettingsActivity extends FragmentActivity {
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
-        setContentView(R.layout.activity_settings);
+        setContentView(R.layout.activity_preferences);
+
+        loadUI();
+        prepareSettings();
+    }
+
+    private void prepareSettings() {
+        String title, fragId;
+        Bundle bundle = getIntent().getExtras();
+        if (bundle != null) {
+            title = bundle.getString("title");
+            fragId = bundle.getString("frag_id");
+        } else {
+            title = getString(R.string.preference);
+            fragId = "GeneralSettingsFragment";
+        }
+
+        TextView textView = findViewById(R.id.settings_bar);
+        textView.setText(title);
+
+        FragmentManager fragmentManager = getFragmentManager();
+        FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
+
+        if ("GeneralSettingsFragment".equals(fragId)) {
+            fragmentTransaction.replace(R.id.preferences_fragment, new GeneralSettingsFragment());
+        } else if ("CommentSettingsFragment".equals(fragId)) {
+            fragmentTransaction.replace(R.id.preferences_fragment, new CommentSettingsFragment());
+        }
+        fragmentTransaction.commit();
+    }
+
+    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
+    private void loadUI() {
+        if (android.os.Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) return;
+
+        Window window = this.getWindow();
+
+        window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
+
+        window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
+
+        window.setStatusBarColor(0xffE46C62);
+    }
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+    }
+
+    public void performBack(View view) {
+        super.onBackPressed();
+    }
+
+    public void enterAccessibilityPage(View view) {
+        Toast.makeText(this, getString(R.string.turn_on_toast), Toast.LENGTH_SHORT).show();
+        Intent mAccessibleIntent =
+                new Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS);
+        startActivity(mAccessibleIntent);
     }
 }

+ 132 - 0
app/src/main/java/me/yoqi/app/wxredpacket/activities/WebViewActivity.java

@@ -0,0 +1,132 @@
+package me.yoqi.app.wxredpacket.activities;
+
+import android.annotation.TargetApi;
+import android.app.Activity;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Bundle;
+import android.view.KeyEvent;
+import android.view.View;
+import android.view.Window;
+import android.view.WindowManager;
+import android.webkit.CookieSyncManager;
+import android.webkit.WebSettings;
+import android.webkit.WebView;
+import android.webkit.WebViewClient;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import xyz.monkeytong.hongbao.R;
+import xyz.monkeytong.hongbao.utils.DownloadUtil;
+
+/**
+ * Created by Zhongyi on 1/19/16.
+ * Settings page.
+ */
+public class WebViewActivity extends Activity {
+    private WebView webView;
+    private String webViewUrl, webViewTitle;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        loadUI();
+
+        Bundle bundle = getIntent().getExtras();
+        if (bundle != null && !bundle.isEmpty()) {
+            webViewTitle = bundle.getString("title");
+            webViewUrl = bundle.getString("url");
+
+            final TextView webViewBar = findViewById(R.id.webview_bar);
+            webViewBar.setText(webViewTitle);
+
+            webView = findViewById(R.id.webView);
+            webView.getSettings().setBuiltInZoomControls(false);
+            webView.getSettings().setJavaScriptEnabled(true);
+            webView.getSettings().setDomStorageEnabled(true);
+            webView.getSettings().setCacheMode(WebSettings.LOAD_DEFAULT);
+            webView.setWebViewClient(new WebViewClient() {
+                @Override
+                public boolean shouldOverrideUrlLoading(WebView view, String url) {
+                    if (url.contains("apk")) {
+                        Toast.makeText(getApplicationContext(), getString(R.string.download_backend), Toast.LENGTH_SHORT).show();
+                        (new DownloadUtil()).enqueue(url, getApplicationContext());
+                        return true;
+                    } else if (!url.contains("http")) {
+                        Toast.makeText(getApplicationContext(), getString(R.string.download_redirect), Toast.LENGTH_LONG).show();
+                        webViewBar.setText(getString(R.string.download_hint));
+                        return false;
+                    } else {
+                        view.loadUrl(url);
+                        return false;
+                    }
+                }
+
+                @Override
+                public void onPageFinished(WebView view, String url) {
+                    CookieSyncManager.getInstance().sync();
+                }
+            });
+            webView.loadUrl(webViewUrl);
+        }
+    }
+
+    @Override
+    protected void onPause() {
+        super.onPause();
+    }
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+    }
+
+    @Override
+    protected void onDestroy() {
+        super.onDestroy();
+    }
+
+    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
+    private void loadUI() {
+        setContentView(R.layout.activity_webview);
+
+        if (android.os.Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) return;
+
+        Window window = this.getWindow();
+
+        window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
+
+        window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
+
+        window.setStatusBarColor(0xffE46C62);
+    }
+
+    public void performBack(View view) {
+        super.onBackPressed();
+    }
+
+    @Override
+    public boolean onKeyDown(int keyCode, KeyEvent event) {
+        if (event.getAction() == KeyEvent.ACTION_DOWN) {
+            switch (keyCode) {
+                case KeyEvent.KEYCODE_BACK:
+                    if (webView.canGoBack()) {
+                        webView.goBack();
+                    } else {
+                        finish();
+                    }
+                    return true;
+            }
+
+        }
+        return super.onKeyDown(keyCode, event);
+    }
+
+    public void openLink(View view) {
+        Intent intent = new Intent(Intent.ACTION_VIEW,
+                Uri.parse(this.webViewUrl));
+        startActivity(intent);
+    }
+}

+ 51 - 0
app/src/main/java/me/yoqi/app/wxredpacket/fragments/CommentSettingsFragment.java

@@ -0,0 +1,51 @@
+package me.yoqi.app.wxredpacket.fragments;
+
+import android.os.Build;
+import android.os.Bundle;
+import android.preference.Preference;
+import android.preference.PreferenceFragment;
+import android.preference.PreferenceManager;
+import android.widget.Toast;
+
+import me.yoqi.app.wxredpacket.R;
+
+/**
+ * Created by Zhongyi on 2/4/16.
+ */
+public class CommentSettingsFragment extends PreferenceFragment {
+    private Bundle savedInstanceState;
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        this.savedInstanceState = savedInstanceState;
+        super.onCreate(savedInstanceState);
+        addPreferencesFromResource(R.xml.comment_preferences);
+        setPrefListeners();
+    }
+
+    private void setPrefListeners() {
+        Preference updatePref = findPreference("pref_comment_switch");
+        if (android.os.Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
+            updatePref.setEnabled(false);
+        }
+        Toast.makeText(getActivity(), "该功能尚处于实验中,只能自动填充感谢语,无法直接发送.", Toast.LENGTH_LONG).show();
+
+        Preference commentWordsPref = findPreference("pref_comment_words");
+        String summary = getResources().getString(R.string.pref_comment_words_summary);
+        String value = PreferenceManager.getDefaultSharedPreferences(getActivity()).getString("pref_comment_words", "");
+        if (value.length() > 0) commentWordsPref.setSummary(summary + ":" + value);
+
+        commentWordsPref.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
+            @Override
+            public boolean onPreferenceChange(Preference preference, Object o) {
+                String summary = getResources().getString(R.string.pref_comment_words_summary);
+                if (o != null && o.toString().length() > 0) {
+                    preference.setSummary(summary + ":" + o.toString());
+                } else {
+                    preference.setSummary(summary);
+                }
+                return true;
+            }
+        });
+    }
+}

+ 62 - 0
app/src/main/java/me/yoqi/app/wxredpacket/fragments/GeneralSettingsFragment.java

@@ -0,0 +1,62 @@
+package me.yoqi.app.wxredpacket.fragments;
+
+import android.content.Intent;
+import android.os.Bundle;
+import android.preference.Preference;
+import android.preference.PreferenceFragment;
+import android.preference.PreferenceManager;
+
+import me.yoqi.app.wxredpacket.R;
+import me.yoqi.app.wxredpacket.activities.WebViewActivity;
+import me.yoqi.app.wxredpacket.utils.UpdateTask;
+
+public class GeneralSettingsFragment extends PreferenceFragment {
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        addPreferencesFromResource(R.xml.general_preferences);
+        setPrefListeners();
+    }
+
+    private void setPrefListeners() {
+        // Check for updates
+        Preference updatePref = findPreference("pref_etc_check_update");
+        updatePref.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
+            public boolean onPreferenceClick(Preference preference) {
+                new UpdateTask(getActivity().getApplicationContext(), true).update();
+                return false;
+            }
+        });
+
+        // Open issue
+        Preference issuePref = findPreference("pref_etc_issue");
+        issuePref.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
+            public boolean onPreferenceClick(Preference preference) {
+                Intent webViewIntent = new Intent(getActivity(), WebViewActivity.class);
+                webViewIntent.putExtra("title", "GitHub Issues");
+                webViewIntent.putExtra("url", getString(R.string.url_github_issues));
+                webViewIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+                startActivity(webViewIntent);
+                return false;
+            }
+        });
+
+        Preference excludeWordsPref = findPreference("pref_watch_exclude_words");
+        String summary = getResources().getString(R.string.pref_watch_exclude_words_summary);
+        String value = PreferenceManager.getDefaultSharedPreferences(getActivity()).getString("pref_watch_exclude_words", "");
+        if (value.length() > 0) excludeWordsPref.setSummary(summary + ":" + value);
+
+        excludeWordsPref.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
+            @Override
+            public boolean onPreferenceChange(Preference preference, Object o) {
+                String summary = getResources().getString(R.string.pref_watch_exclude_words_summary);
+                if (o != null && o.toString().length() > 0) {
+                    preference.setSummary(summary + ":" + o.toString());
+                } else {
+                    preference.setSummary(summary);
+                }
+                return true;
+            }
+        });
+    }
+}

+ 386 - 0
app/src/main/java/me/yoqi/app/wxredpacket/services/HongbaoService.java

@@ -0,0 +1,386 @@
+package me.yoqi.app.wxredpacket.services;
+
+import android.accessibilityservice.AccessibilityService;
+import android.accessibilityservice.GestureDescription;
+import android.app.Notification;
+import android.app.PendingIntent;
+import android.content.ComponentName;
+import android.content.SharedPreferences;
+import android.content.pm.PackageManager;
+import android.graphics.Path;
+import android.graphics.Rect;
+import android.os.Bundle;
+import android.os.Parcelable;
+import android.preference.PreferenceManager;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityNodeInfo;
+
+import java.util.List;
+
+import me.yoqi.app.wxredpacket.utils.HongbaoSignature;
+import me.yoqi.app.wxredpacket.utils.PowerUtil;
+
+public class HongbaoService extends AccessibilityService implements SharedPreferences.OnSharedPreferenceChangeListener {
+    private static final String WECHAT_DETAILS_EN = "Details";
+    private static final String WECHAT_DETAILS_CH = "红包详情";
+    private static final String WECHAT_BETTER_LUCK_EN = "Better luck next time!";
+    private static final String WECHAT_BETTER_LUCK_CH = "手慢了";
+    private static final String WECHAT_EXPIRES_CH = "已超过24小时";
+    private static final String WECHAT_VIEW_SELF_CH = "查看红包";
+    private static final String WECHAT_VIEW_OTHERS_CH = "领取红包";
+    private static final String WECHAT_NOTIFICATION_TIP = "[微信红包]";
+    private static final String WECHAT_LUCKMONEY_RECEIVE_ACTIVITY = "LuckyMoneyReceiveUI";
+    private static final String WECHAT_LUCKMONEY_DETAIL_ACTIVITY = "LuckyMoneyDetailUI";
+    private static final String WECHAT_LUCKMONEY_GENERAL_ACTIVITY = "LauncherUI";
+    private static final String WECHAT_LUCKMONEY_CHATTING_ACTIVITY = "ChattingUI";
+    private String currentActivityName = WECHAT_LUCKMONEY_GENERAL_ACTIVITY;
+
+    private AccessibilityNodeInfo rootNodeInfo, mReceiveNode, mUnpackNode;
+    private boolean mLuckyMoneyPicked, mLuckyMoneyReceived;
+    private int mUnpackCount = 0;
+    private boolean mMutex = false, mListMutex = false, mChatMutex = false;
+    private HongbaoSignature signature = new HongbaoSignature();
+
+    private PowerUtil powerUtil;
+    private SharedPreferences sharedPreferences;
+
+    /**
+     * AccessibilityEvent
+     *
+     * @param event 事件
+     */
+    @Override
+    public void onAccessibilityEvent(AccessibilityEvent event) {
+        if (sharedPreferences == null) return;
+
+        setCurrentActivityName(event);
+
+        /* 检测通知消息 */
+        if (!mMutex) {
+            if (sharedPreferences.getBoolean("pref_watch_notification", false) && watchNotifications(event)) return;
+            if (sharedPreferences.getBoolean("pref_watch_list", false) && watchList(event)) return;
+            mListMutex = false;
+        }
+
+        if (!mChatMutex) {
+            mChatMutex = true;
+            if (sharedPreferences.getBoolean("pref_watch_chat", false)) watchChat(event);
+            mChatMutex = false;
+        }
+    }
+
+    private void watchChat(AccessibilityEvent event) {
+        this.rootNodeInfo = getRootInActiveWindow();
+
+        if (rootNodeInfo == null) return;
+
+        mReceiveNode = null;
+        mUnpackNode = null;
+
+        checkNodeInfo(event.getEventType());
+
+        /* 如果已经接收到红包并且还没有戳开 */
+        if (mLuckyMoneyReceived && !mLuckyMoneyPicked && (mReceiveNode != null)) {
+            mMutex = true;
+
+            mReceiveNode.getParent().performAction(AccessibilityNodeInfo.ACTION_CLICK);
+            mLuckyMoneyReceived = false;
+            mLuckyMoneyPicked = true;
+        }
+        /* 如果戳开但还未领取 */
+        if (mUnpackCount == 1 && (mUnpackNode != null)) {
+            int delayFlag = sharedPreferences.getInt("pref_open_delay", 0) * 1000;
+            new android.os.Handler().postDelayed(
+                    new Runnable() {
+                        public void run() {
+                            try {
+                                openPacket();
+                            } catch (Exception e) {
+                                mMutex = false;
+                                mLuckyMoneyPicked = false;
+                                mUnpackCount = 0;
+                            }
+                        }
+                    },
+                    delayFlag);
+        }
+    }
+
+    private void openPacket() {
+        DisplayMetrics metrics = getResources().getDisplayMetrics();
+        float dpi = metrics.density;
+        if (android.os.Build.VERSION.SDK_INT <= 23) {
+            mUnpackNode.performAction(AccessibilityNodeInfo.ACTION_CLICK);
+        } else {
+            if (android.os.Build.VERSION.SDK_INT > 23) {
+
+                Path path = new Path();
+                if (640 == dpi) {
+                    path.moveTo(720, 1575);
+                } else {
+                    path.moveTo(540, 1060);
+                }
+                GestureDescription.Builder builder = new GestureDescription.Builder();
+                GestureDescription gestureDescription = builder.addStroke(new GestureDescription.StrokeDescription(path, 450, 50)).build();
+                dispatchGesture(gestureDescription, new GestureResultCallback() {
+                    @Override
+                    public void onCompleted(GestureDescription gestureDescription) {
+                        Log.d("test", "onCompleted");
+                        mMutex = false;
+                        super.onCompleted(gestureDescription);
+                    }
+
+                    @Override
+                    public void onCancelled(GestureDescription gestureDescription) {
+                        Log.d("test", "onCancelled");
+                        mMutex = false;
+                        super.onCancelled(gestureDescription);
+                    }
+                }, null);
+
+            }
+        }
+    }
+
+    private void setCurrentActivityName(AccessibilityEvent event) {
+        if (event.getEventType() != AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) {
+            return;
+        }
+
+        try {
+            ComponentName componentName = new ComponentName(
+                    event.getPackageName().toString(),
+                    event.getClassName().toString()
+            );
+
+            getPackageManager().getActivityInfo(componentName, 0);
+            currentActivityName = componentName.flattenToShortString();
+        } catch (PackageManager.NameNotFoundException e) {
+            currentActivityName = WECHAT_LUCKMONEY_GENERAL_ACTIVITY;
+        }
+    }
+
+    private boolean watchList(AccessibilityEvent event) {
+        if (mListMutex) return false;
+        mListMutex = true;
+        AccessibilityNodeInfo eventSource = event.getSource();
+        // Not a message
+        if (event.getEventType() != AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED || eventSource == null)
+            return false;
+
+        List<AccessibilityNodeInfo> nodes = eventSource.findAccessibilityNodeInfosByText(WECHAT_NOTIFICATION_TIP);
+        //增加条件判断currentActivityName.contains(WECHAT_LUCKMONEY_GENERAL_ACTIVITY)
+        //避免当订阅号中出现标题为“[微信红包]拜年红包”(其实并非红包)的信息时误判
+        if (!nodes.isEmpty() && currentActivityName.contains(WECHAT_LUCKMONEY_GENERAL_ACTIVITY)) {
+            AccessibilityNodeInfo nodeToClick = nodes.get(0);
+            if (nodeToClick == null) return false;
+            CharSequence contentDescription = nodeToClick.getContentDescription();
+            if (contentDescription != null && !signature.getContentDescription().equals(contentDescription)) {
+                nodeToClick.performAction(AccessibilityNodeInfo.ACTION_CLICK);
+                signature.setContentDescription(contentDescription.toString());
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private boolean watchNotifications(AccessibilityEvent event) {
+        // Not a notification
+        if (event.getEventType() != AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED)
+            return false;
+
+        // Not a hongbao
+        String tip = event.getText().toString();
+        if (!tip.contains(WECHAT_NOTIFICATION_TIP)) return true;
+
+        Parcelable parcelable = event.getParcelableData();
+        if (parcelable instanceof Notification) {
+            Notification notification = (Notification) parcelable;
+            try {
+                /* 清除signature,避免进入会话后误判 */
+                signature.cleanSignature();
+
+                notification.contentIntent.send();
+            } catch (PendingIntent.CanceledException e) {
+                e.printStackTrace();
+            }
+        }
+        return true;
+    }
+
+    @Override
+    public void onInterrupt() {
+
+    }
+
+    private AccessibilityNodeInfo findOpenButton(AccessibilityNodeInfo node) {
+        if (node == null)
+            return null;
+
+        //非layout元素
+        if (node.getChildCount() == 0) {
+            if ("android.widget.Button".equals(node.getClassName()))
+                return node;
+            else
+                return null;
+        }
+
+        //layout元素,遍历找button
+        AccessibilityNodeInfo button;
+        for (int i = 0; i < node.getChildCount(); i++) {
+            button = findOpenButton(node.getChild(i));
+            if (button != null)
+                return button;
+        }
+        return null;
+    }
+
+    private void checkNodeInfo(int eventType) {
+        if (this.rootNodeInfo == null) return;
+
+        if (signature.commentString != null) {
+            sendComment();
+            signature.commentString = null;
+        }
+
+        /* 聊天会话窗口,遍历节点匹配“领取红包”和"查看红包" */
+        AccessibilityNodeInfo node1 = (sharedPreferences.getBoolean("pref_watch_self", false)) ?
+                this.getTheLastNode(WECHAT_VIEW_OTHERS_CH, WECHAT_VIEW_SELF_CH) : this.getTheLastNode(WECHAT_VIEW_OTHERS_CH);
+        if (node1 != null &&
+                (currentActivityName.contains(WECHAT_LUCKMONEY_CHATTING_ACTIVITY)
+                        || currentActivityName.contains(WECHAT_LUCKMONEY_GENERAL_ACTIVITY))) {
+            String excludeWords = sharedPreferences.getString("pref_watch_exclude_words", "");
+            if (this.signature.generateSignature(node1, excludeWords)) {
+                mLuckyMoneyReceived = true;
+                mReceiveNode = node1;
+                Log.d("sig", this.signature.toString());
+            }
+            return;
+        }
+
+        /* 戳开红包,红包还没抢完,遍历节点匹配“拆红包” */
+        AccessibilityNodeInfo node2 = findOpenButton(this.rootNodeInfo);
+        if (node2 != null && "android.widget.Button".equals(node2.getClassName()) && currentActivityName.contains(WECHAT_LUCKMONEY_RECEIVE_ACTIVITY)) {
+            mUnpackNode = node2;
+            mUnpackCount += 1;
+            return;
+        }
+
+        /* 戳开红包,红包已被抢完,遍历节点匹配“红包详情”和“手慢了” */
+        boolean hasNodes = this.hasOneOfThoseNodes(
+                WECHAT_BETTER_LUCK_CH, WECHAT_DETAILS_CH,
+                WECHAT_BETTER_LUCK_EN, WECHAT_DETAILS_EN, WECHAT_EXPIRES_CH);
+        if (mMutex && eventType == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED && hasNodes
+                && (currentActivityName.contains(WECHAT_LUCKMONEY_DETAIL_ACTIVITY)
+                || currentActivityName.contains(WECHAT_LUCKMONEY_RECEIVE_ACTIVITY))) {
+            mMutex = false;
+            mLuckyMoneyPicked = false;
+            mUnpackCount = 0;
+            performGlobalAction(GLOBAL_ACTION_BACK);
+            signature.commentString = generateCommentString();
+        }
+    }
+
+    private void sendComment() {
+        try {
+            AccessibilityNodeInfo outNode =
+                    getRootInActiveWindow().getChild(0).getChild(0);
+            AccessibilityNodeInfo nodeToInput = outNode.getChild(outNode.getChildCount() - 1).getChild(0).getChild(1);
+
+            if ("android.widget.EditText".equals(nodeToInput.getClassName())) {
+                Bundle arguments = new Bundle();
+                arguments.putCharSequence(AccessibilityNodeInfo
+                        .ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE, signature.commentString);
+                nodeToInput.performAction(AccessibilityNodeInfo.ACTION_SET_TEXT, arguments);
+            }
+        } catch (Exception e) {
+            // Not supported
+        }
+    }
+
+
+    private boolean hasOneOfThoseNodes(String... texts) {
+        List<AccessibilityNodeInfo> nodes;
+        for (String text : texts) {
+            if (text == null) continue;
+
+            nodes = this.rootNodeInfo.findAccessibilityNodeInfosByText(text);
+
+            if (nodes != null && !nodes.isEmpty()) return true;
+        }
+        return false;
+    }
+
+    private AccessibilityNodeInfo getTheLastNode(String... texts) {
+        int bottom = 0;
+        AccessibilityNodeInfo lastNode = null, tempNode;
+        List<AccessibilityNodeInfo> nodes;
+
+        for (String text : texts) {
+            if (text == null) continue;
+
+            nodes = this.rootNodeInfo.findAccessibilityNodeInfosByText(text);
+
+            if (nodes != null && !nodes.isEmpty()) {
+                tempNode = nodes.get(nodes.size() - 1);
+                if (tempNode == null) return null;
+                Rect bounds = new Rect();
+                tempNode.getBoundsInScreen(bounds);
+                if (bounds.bottom > bottom) {
+                    bottom = bounds.bottom;
+                    lastNode = tempNode;
+                    signature.others = text.equals(WECHAT_VIEW_OTHERS_CH);
+                }
+            }
+        }
+        return lastNode;
+    }
+
+    @Override
+    public void onServiceConnected() {
+        super.onServiceConnected();
+        this.watchFlagsFromPreference();
+    }
+
+    private void watchFlagsFromPreference() {
+        sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
+        sharedPreferences.registerOnSharedPreferenceChangeListener(this);
+
+        this.powerUtil = new PowerUtil(this);
+        Boolean watchOnLockFlag = sharedPreferences.getBoolean("pref_watch_on_lock", false);
+        this.powerUtil.handleWakeLock(watchOnLockFlag);
+    }
+
+    @Override
+    public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
+        if (key.equals("pref_watch_on_lock")) {
+            Boolean changedValue = sharedPreferences.getBoolean(key, false);
+            this.powerUtil.handleWakeLock(changedValue);
+        }
+    }
+
+    @Override
+    public void onDestroy() {
+        this.powerUtil.handleWakeLock(false);
+        super.onDestroy();
+    }
+
+    private String generateCommentString() {
+        if (!signature.others) return null;
+
+        Boolean needComment = sharedPreferences.getBoolean("pref_comment_switch", false);
+        if (!needComment) return null;
+
+        String[] wordsArray = sharedPreferences.getString("pref_comment_words", "").split(" +");
+        if (wordsArray.length == 0) return null;
+
+        Boolean atSender = sharedPreferences.getBoolean("pref_comment_at", false);
+        if (atSender) {
+            return "@" + signature.sender + " " + wordsArray[(int) (Math.random() * wordsArray.length)];
+        } else {
+            return wordsArray[(int) (Math.random() * wordsArray.length)];
+        }
+    }
+}

+ 19 - 0
app/src/main/java/me/yoqi/app/wxredpacket/utils/ConnectivityUtil.java

@@ -0,0 +1,19 @@
+package me.yoqi.app.wxredpacket.utils;
+
+import android.content.Context;
+import android.net.ConnectivityManager;
+import android.net.NetworkInfo;
+
+/**
+ * Created by Zhongyi on 1/29/16.
+ */
+public class ConnectivityUtil {
+    public static boolean isWifi(Context context) {
+        ConnectivityManager cm =
+                (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
+
+        NetworkInfo activeNetwork = cm.getActiveNetworkInfo();
+        return activeNetwork != null && activeNetwork.isConnectedOrConnecting()
+                && activeNetwork.getType() == ConnectivityManager.TYPE_WIFI;
+    }
+}

+ 22 - 0
app/src/main/java/me/yoqi/app/wxredpacket/utils/DownloadUtil.java

@@ -0,0 +1,22 @@
+package me.yoqi.app.wxredpacket.utils;
+
+import android.app.DownloadManager;
+import android.content.Context;
+import android.net.Uri;
+import android.os.Environment;
+
+import static android.content.Context.DOWNLOAD_SERVICE;
+
+/**
+ * Created by Zhongyi on 8/1/16.
+ */
+public class DownloadUtil {
+    public void enqueue(String url, Context context) {
+        DownloadManager.Request r = new DownloadManager.Request(Uri.parse(url));
+        r.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, "Uber.apk");
+        r.allowScanningByMediaScanner();
+        r.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
+        DownloadManager dm = (DownloadManager) context.getSystemService(DOWNLOAD_SERVICE);
+        dm.enqueue(r);
+    }
+}

+ 37 - 0
app/src/main/java/me/yoqi/app/wxredpacket/utils/HongbaoLogger.java

@@ -0,0 +1,37 @@
+package me.yoqi.app.wxredpacket.utils;
+
+import android.content.Context;
+import android.database.sqlite.SQLiteDatabase;
+
+/**
+ * Created by Zhongyi on 1/22/16.
+ */
+public class HongbaoLogger {
+    private Context context;
+    private SQLiteDatabase database;
+
+    private static final int DATABASE_VERSION = 1;
+    private static final String DATABASE_NAME = "WeChatLuckyMoney.db";
+    private static final String createDatabaseSQL = "CREATE TABLE IF NOT EXISTS HongbaoLog (id INTEGER PRIMARY KEY AUTOINCREMENT, sender TEXT, content TEXT, time TEXT, amount TEXT);";
+
+    public HongbaoLogger(final Context context) {
+        this.context = context;
+        this.initSchemaAndDatabase();
+    }
+
+    private void initSchemaAndDatabase() {
+        this.database = context.openOrCreateDatabase("WeChatLuckyMoney", Context.MODE_PRIVATE, null);
+
+        this.database.beginTransaction();
+        this.database.execSQL(createDatabaseSQL);
+        this.database.endTransaction();
+    }
+
+    public void writeHongbaoLog(String sender, String content, String amount) {
+
+    }
+
+    public void getAllLogs() {
+
+    }
+}

+ 97 - 0
app/src/main/java/me/yoqi/app/wxredpacket/utils/HongbaoSignature.java

@@ -0,0 +1,97 @@
+package me.yoqi.app.wxredpacket.utils;
+
+import android.graphics.Rect;
+import android.view.accessibility.AccessibilityNodeInfo;
+
+/**
+ * Created by Zhongyi on 1/21/16.
+ */
+public class HongbaoSignature {
+    public String sender, content, time, contentDescription = "", commentString;
+    public boolean others;
+
+    public boolean generateSignature(AccessibilityNodeInfo node, String excludeWords) {
+        try {
+            /* The hongbao container node. It should be a LinearLayout. By specifying that, we can avoid text messages. */
+            AccessibilityNodeInfo hongbaoNode = node.getParent();
+            if (!"android.widget.LinearLayout".equals(hongbaoNode.getClassName())) return false;
+
+            /* The text in the hongbao. Should mean something. */
+            String hongbaoContent = hongbaoNode.getChild(0).getText().toString();
+            if (hongbaoContent == null || "查看红包".equals(hongbaoContent)) return false;
+
+            /* Check the user's exclude words list. */
+            String[] excludeWordsArray = excludeWords.split(" +");
+            for (String word : excludeWordsArray) {
+                if (word.length() > 0 && hongbaoContent.contains(word)) return false;
+            }
+
+            /* The container node for a piece of message. It should be inside the screen.
+                Or sometimes it will get opened twice while scrolling. */
+            AccessibilityNodeInfo messageNode = hongbaoNode.getParent();
+
+            Rect bounds = new Rect();
+            messageNode.getBoundsInScreen(bounds);
+            if (bounds.top < 0) return false;
+
+            /* The sender and possible timestamp. Should mean something too. */
+            String[] hongbaoInfo = getSenderContentDescriptionFromNode(messageNode);
+            if (this.getSignature(hongbaoInfo[0], hongbaoContent, hongbaoInfo[1]).equals(this.toString())) return false;
+
+            /* So far we make sure it's a valid new coming hongbao. */
+            this.sender = hongbaoInfo[0];
+            this.time = hongbaoInfo[1];
+            this.content = hongbaoContent;
+            return true;
+        } catch (Exception e) {
+            e.printStackTrace();
+            return false;
+        }
+    }
+
+    @Override
+    public String toString() {
+        return this.getSignature(this.sender, this.content, this.time);
+    }
+
+    private String getSignature(String... strings) {
+        String signature = "";
+        for (String str : strings) {
+            if (str == null) return null;
+            signature += str + "|";
+        }
+
+        return signature.substring(0, signature.length() - 1);
+    }
+
+    public String getContentDescription() {
+        return this.contentDescription;
+    }
+
+    public void setContentDescription(String description) {
+        this.contentDescription = description;
+    }
+
+    private String[] getSenderContentDescriptionFromNode(AccessibilityNodeInfo node) {
+        int count = node.getChildCount();
+        String[] result = {"unknownSender", "unknownTime"};
+        for (int i = 0; i < count; i++) {
+            AccessibilityNodeInfo thisNode = node.getChild(i);
+            if ("android.widget.ImageView".equals(thisNode.getClassName()) && "unknownSender".equals(result[0])) {
+                CharSequence contentDescription = thisNode.getContentDescription();
+                if (contentDescription != null) result[0] = contentDescription.toString().replaceAll("头像$", "");
+            } else if ("android.widget.TextView".equals(thisNode.getClassName()) && "unknownTime".equals(result[1])) {
+                CharSequence thisNodeText = thisNode.getText();
+                if (thisNodeText != null) result[1] = thisNodeText.toString();
+            }
+        }
+        return result;
+    }
+
+    public void cleanSignature() {
+        this.content = "";
+        this.time = "";
+        this.sender = "";
+    }
+
+}

+ 41 - 0
app/src/main/java/me/yoqi/app/wxredpacket/utils/PowerUtil.java

@@ -0,0 +1,41 @@
+package me.yoqi.app.wxredpacket.utils;
+
+import android.app.KeyguardManager;
+import android.content.Context;
+import android.os.PowerManager;
+
+/**
+ * Created by Zhongyi on 1/29/16.
+ */
+public class PowerUtil {
+    private PowerManager.WakeLock wakeLock;
+    private KeyguardManager.KeyguardLock keyguardLock;
+
+    public PowerUtil(Context context) {
+        PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
+        wakeLock = pm.newWakeLock(PowerManager.SCREEN_DIM_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP,
+                "HongbaoWakelock");
+        KeyguardManager km = (KeyguardManager) context.getSystemService(Context.KEYGUARD_SERVICE);
+        keyguardLock = km.newKeyguardLock("HongbaoKeyguardLock");
+    }
+
+    private void acquire() {
+        wakeLock.acquire(1800000);
+        keyguardLock.disableKeyguard();
+    }
+
+    private void release() {
+        if (wakeLock.isHeld()) {
+            wakeLock.release();
+            keyguardLock.reenableKeyguard();
+        }
+    }
+
+    public void handleWakeLock(boolean isWake) {
+        if (isWake) {
+            this.acquire();
+        } else {
+            this.release();
+        }
+    }
+}

+ 98 - 0
app/src/main/java/me/yoqi/app/wxredpacket/utils/UpdateTask.java

@@ -0,0 +1,98 @@
+package me.yoqi.app.wxredpacket.utils;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageInfo;
+import android.net.Uri;
+import android.os.AsyncTask;
+import android.widget.Toast;
+
+import org.apache.http.HttpResponse;
+import org.apache.http.StatusLine;
+import org.apache.http.client.HttpClient;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.impl.client.DefaultHttpClient;
+import org.json.JSONObject;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+
+import me.yoqi.app.wxredpacket.R;
+
+public class UpdateTask extends AsyncTask<String, String, String> {
+    public static int count = 0;
+    private Context context;
+    private boolean isUpdateOnRelease;
+    public static final String updateUrl = "https://api.github.com/repos/geeeeeeeeek/WeChatLuckyMoney/releases/latest";
+
+    public UpdateTask(Context context, boolean needUpdate) {
+        this.context = context;
+        this.isUpdateOnRelease = needUpdate;
+        if (this.isUpdateOnRelease) Toast.makeText(context, context.getString(R.string.checking_new_version), Toast.LENGTH_SHORT).show();
+    }
+
+    @Override
+    protected String doInBackground(String... uri) {
+        HttpClient httpclient = new DefaultHttpClient();
+        HttpResponse response;
+        String responseString = null;
+        try {
+            response = httpclient.execute(new HttpGet(uri[0]));
+            StatusLine statusLine = response.getStatusLine();
+            if (statusLine.getStatusCode() == 200) {
+                ByteArrayOutputStream out = new ByteArrayOutputStream();
+                response.getEntity().writeTo(out);
+                responseString = out.toString();
+                out.close();
+            } else {
+                // Close the connection.
+                response.getEntity().getContent().close();
+                throw new IOException(statusLine.getReasonPhrase());
+            }
+        } catch (Exception e) {
+            return null;
+        }
+        return responseString;
+    }
+
+    @Override
+    protected void onPostExecute(String result) {
+        super.onPostExecute(result);
+        try {
+            count += 1;
+            JSONObject release = new JSONObject(result);
+
+            // Get current version
+            PackageInfo pInfo = context.getPackageManager().getPackageInfo(context.getPackageName(), 0);
+            String version = pInfo.versionName;
+
+            String latestVersion = release.getString("tag_name");
+            boolean isPreRelease = release.getBoolean("prerelease");
+            if (!isPreRelease && version.compareToIgnoreCase(latestVersion) >= 0) {
+                // Your version is ahead of or same as the latest.
+                if (this.isUpdateOnRelease)
+                    Toast.makeText(context, R.string.update_already_latest, Toast.LENGTH_SHORT).show();
+            } else {
+                if (!isUpdateOnRelease) {
+                    Toast.makeText(context, context.getString(R.string.update_new_seg1) + latestVersion + context.getString(R.string.update_new_seg3), Toast.LENGTH_LONG).show();
+                    return;
+                }
+                // Need update.
+                String downloadUrl = release.getJSONArray("assets").getJSONObject(0).getString("browser_download_url");
+
+                // Give up on the fucking DownloadManager. The downloaded apk got renamed and unable to install. Fuck.
+                Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(downloadUrl));
+                browserIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+                context.startActivity(browserIntent);
+                Toast.makeText(context, context.getString(R.string.update_new_seg1) + latestVersion + context.getString(R.string.update_new_seg2), Toast.LENGTH_LONG).show();
+            }
+        } catch (Exception e) {
+            e.printStackTrace();
+            if (this.isUpdateOnRelease) Toast.makeText(context, R.string.update_error, Toast.LENGTH_LONG).show();
+        }
+    }
+
+    public void update() {
+        super.execute(updateUrl);
+    }
+}

+ 48 - 43
app/src/main/res/layout/activity_main.xml

@@ -18,16 +18,17 @@
             android:layout_above="@+id/layout_control"
             android:background="#E46C62"
             android:layout_marginBottom="12dp">
+
         <TextView
-                android:layout_width="fill_parent"
-                android:layout_height="wrap_content"
-                android:id="@+id/textView"
-                android:text="@string/app_name"
-                android:textColor="#fff"
-                android:gravity="center_vertical|center_horizontal"
-                android:textSize="28dp"
-                android:layout_marginTop="40dp"
-                android:textIsSelectable="false"/>
+            android:id="@+id/textView"
+            android:layout_width="fill_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginTop="40dp"
+            android:gravity="center_vertical|center_horizontal"
+            android:text="@string/app_name"
+            android:textColor="#fff"
+            android:textIsSelectable="false"
+            android:textSize="28dp" />
         <TextView android:layout_width="fill_parent"
                   android:layout_height="wrap_content"
                   android:id="@+id/textView5"
@@ -50,19 +51,18 @@
                   android:layout_marginLeft="12dp"
                   android:layout_marginRight="12dp"
                   android:layout_marginTop="8dp"
-                  android:layout_marginBottom="8dp">
+                  android:layout_marginBottom="8dp" android:baselineAligned="false">
         <LinearLayout style="?android:attr/borderlessButtonStyle"
                       android:id="@+id/layout_control_community"
                       android:layout_width="fill_parent"
                       android:layout_height="fill_parent"
                       android:background="#ffffff"
                       android:textColor="#858585"
-                      android:layout_above="@+id/layout_uber"
-                      android:textSize="20dp"
+                      android:textSize="20sp"
                       android:orientation="vertical"
                       android:layout_weight="0.35"
                       android:layout_marginRight="5dp"
-                      android:onClick="openGitHub">
+                      android:onClick="openGitHub" android:layout_marginEnd="5dp">
             <ImageView
                     android:layout_width="24dp"
                     android:layout_height="24dp"
@@ -77,32 +77,38 @@
                     android:layout_margin="5dp"
                     android:textColor="#858585" android:textSize="16sp" android:textStyle="bold"/>
         </LinearLayout>
+
         <LinearLayout
-                android:id="@+id/layout_control_accessibility"
-                android:layout_width="fill_parent"
-                android:layout_height="fill_parent"
-                android:onClick="openAccessibility"
-                android:background="#ffffff"
-                android:textColor="#858585"
-                style="?android:attr/borderlessButtonStyle"
-                android:layout_above="@+id/layout_uber"
-                android:textSize="20dp"
-                android:orientation="vertical"
-                android:layout_weight="0.3"
-                android:layout_marginRight="5dp"
-                android:layout_marginLeft="5dp">
-            <ImageView android:layout_width="32dp"
-                       android:layout_height="32dp"
-                       android:id="@+id/layout_control_accessibility_icon"
-                       android:layout_marginBottom="6dp"
-                       android:layout_marginTop="6dp"
-                       android:background="@mipmap/ic_start"/>
-            <TextView android:layout_width="wrap_content"
-                      android:layout_height="wrap_content"
-                      android:text="@string/service_on"
-                      android:id="@+id/layout_control_accessibility_text"
-                      android:layout_margin="5dp"
-                      android:textColor="#dfaa6a" android:textStyle="bold" android:textSize="16sp"/>
+            android:id="@+id/layout_control_accessibility"
+            style="?android:attr/borderlessButtonStyle"
+            android:layout_width="fill_parent"
+            android:layout_height="fill_parent"
+            android:layout_marginLeft="5dp"
+            android:layout_marginRight="5dp"
+            android:layout_weight="0.3"
+            android:background="#ffffff"
+            android:onClick="openAccessibility"
+            android:textColor="#858585"
+            android:textSize="20sp">
+
+            <ImageView
+                android:id="@+id/layout_control_accessibility_icon"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginBottom="6dp"
+                android:layout_marginTop="6dp"
+                android:background="@mipmap/ic_start"
+                android:baselineAligned="false" />
+
+            <TextView
+                android:id="@+id/layout_control_accessibility_text"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_margin="5dp"
+                android:text="@string/service_on"
+                android:textColor="#dfaa6a"
+                android:textSize="16sp"
+                android:textStyle="bold" />
         </LinearLayout>
         <LinearLayout style="?android:attr/borderlessButtonStyle"
                       android:id="@+id/layout_control_settings"
@@ -110,12 +116,11 @@
                       android:layout_height="fill_parent"
                       android:background="#ffffff"
                       android:textColor="#858585"
-                      android:layout_above="@+id/layout_uber"
-                      android:textSize="20dp"
+                      android:textSize="20sp"
                       android:orientation="vertical"
                       android:layout_weight="0.35"
                       android:layout_marginLeft="5dp"
-                      android:onClick="openSettings">
+                      android:onClick="openSettings" android:layout_marginStart="5dp">
             <ImageView android:layout_width="24dp"
                        android:layout_height="24dp"
                        android:id="@+id/imageView4"
@@ -150,7 +155,7 @@
                 android:layout_marginLeft="8dp"
                 android:layout_marginRight="10dp"
                 android:layout_gravity="center_vertical"
-                android:layout_marginBottom="2dp"/>
+                android:layout_marginBottom="2dp" android:layout_marginStart="8dp" android:layout_marginEnd="10dp"/>
         <LinearLayout
                 android:orientation="vertical"
                 android:layout_width="match_parent"
@@ -189,7 +194,7 @@
                    android:src="@mipmap/ic_github"
                    android:layout_marginLeft="15dp"
                    android:layout_marginRight="5dp"
-                   android:layout_gravity="center_vertical"/>
+                   android:layout_gravity="center_vertical" android:layout_marginStart="15dp" android:layout_marginEnd="5dp"/>
         <TextView android:layout_width="wrap_content"
                   android:layout_height="wrap_content"
                   android:text="@string/github_1"

+ 3 - 3
app/src/main/res/layout/preference_checkbox.xml

@@ -4,12 +4,12 @@
               android:layout_height="wrap_content"
               android:gravity="center_vertical"
               android:paddingLeft="16dp"
-              android:paddingRight="?android:attr/scrollbarSize">
+              android:paddingRight="?android:attr/scrollbarSize" android:baselineAligned="false" android:paddingStart="16dp">
 
 
 
     <RelativeLayout
-            android:layout_width="wrap_content"
+            android:layout_width="0dp"
             android:layout_height="wrap_content"
             android:layout_marginRight="6dp"
             android:layout_marginTop="12dp"
@@ -42,6 +42,6 @@
                   android:layout_height="match_parent"
                   android:layout_marginRight="6dp"
                   android:gravity="center"
-                  android:orientation="vertical" />
+                  android:orientation="vertical" android:layout_marginEnd="6dp"/>
 
 </LinearLayout>

+ 1 - 1
app/src/main/res/values-en/strings.xml

@@ -20,7 +20,7 @@
     <string name="settings">Settings</string>
     <string name="uber_ad_title">Your first trip is free up to ¥12</string>
     <string name="uber_ad_text">Get Free Ride Now</string>
-    <string name="github_1"/>
+    <string name="github_1">on GitHub</string>
     <string name="github_2">this app on GitHub to support us</string>
     <string name="star_2">Star</string>
     <string name="star_1">★</string>

+ 0 - 1
app/src/main/res/values/strings.xml

@@ -54,5 +54,4 @@
     <string name="delay_open">拆开红包</string>
     <string name="delay_sec">秒</string>
     <string name="delay_then">然后</string>
-    <string name="cc">ss</string>
 </resources>

+ 6 - 5
app/src/main/res/xml/comment_preferences.xml

@@ -7,11 +7,12 @@
             android:title="自动回复(Android 5.0以上系统)"
             android:layout="@layout/preference_category">
         <CheckBoxPreference
-                android:key="pref_comment_switch"
-                android:title="开启自动回复"
-                android:summary="在拆开其他人的红包后发送自动回复"
-                android:defaultValue="false"
-                android:layout="@layout/preference_checkbox"/>
+            android:defaultValue="false"
+            android:key="pref_comment_switch"
+            android:layout="@layout/preference_checkbox"
+            android:persistent="false"
+            android:summary="在拆开其他人的红包后发送自动回复"
+            android:title="开启自动回复" />
     </PreferenceCategory>
     <PreferenceCategory
             android:title="回复选项"