16 | * myEditText.addTextChangedListener(new TextViewListener() {
17 | * \@Override
18 | * protected void onTextChanged(String before, String old, String aNew, String after) {
19 | * // intuitive usation of parametters
20 | * String completeOldText = before + old + after;
21 | * String completeNewText = before + aNew + after;
22 | *
23 | * // update TextView
24 | * startUpdates(); // to prevent infinite loop.
25 | * myEditText.setText(myNewText);
26 | * endUpdates();
27 | * }
28 | * }
29 | *
30 | * Created by Jeremy B.
31 | */
32 |
33 | public abstract class TextViewListener implements TextWatcher {
34 | /**
35 | * Unchanged sequence which is placed before the updated sequence.
36 | */
37 | private String _before;
38 |
39 | /**
40 | * Updated sequence before the update.
41 | */
42 | private String _old;
43 |
44 | /**
45 | * Updated sequence after the update.
46 | */
47 | private String _new;
48 |
49 | /**
50 | * Unchanged sequence which is placed after the updated sequence.
51 | */
52 | private String _after;
53 |
54 | /**
55 | * Indicates when changes are made from within the listener, should be omitted.
56 | */
57 | private boolean _ignore = false;
58 |
59 | @Override
60 | public void beforeTextChanged(CharSequence sequence, int start, int count, int after) {
61 | _before = sequence.subSequence(0, start).toString();
62 | _old = sequence.subSequence(start, start + count).toString();
63 | _after = sequence.subSequence(start + count, sequence.length()).toString();
64 | }
65 |
66 | @Override
67 | public void onTextChanged(CharSequence sequence, int start, int before, int count) {
68 | _new = sequence.subSequence(start, start + count).toString();
69 | }
70 |
71 | @Override
72 | public void afterTextChanged(Editable sequence) {
73 | if (_ignore)
74 | return;
75 |
76 | onTextChanged(_before, _old, _new, _after);
77 | }
78 |
79 | /**
80 | * Triggered method when the text in the text view has changed.
81 | * maxSwayPeriodDuration / 2
°) clockwise.
12 | * Then it sways to the axisymmetric position (in respect to the vertical center axis of the view) and sways back.
13 | * The view will keep swaying till the nextDegree
reaches 0.
14 | * Each time the sway amplitude is decreased by {@param swayDegreeDecrementStep}.
15 | *
16 | * @param view The view to be animated
17 | * @param maxSwayDegree Maximum sway amplitude.
18 | * @param maxSwayPeriodDuration The sway duration for maximum sway amplitude.
19 | * @param swayDegreeDecrementStep Step of sway amplitude decrement. Must be larger than 0.
20 | */
21 | public static void sway(View view, float maxSwayDegree, long maxSwayPeriodDuration, float swayDegreeDecrementStep) {
22 | if (Float.compare(swayDegreeDecrementStep, 0f) <= 0) {
23 | throw new IllegalArgumentException("swayDegreeDecrementStep must be >= 0!");
24 | }
25 | AnimationSet rotateSet = new AnimationSet(true);
26 | rotateSet.setInterpolator(new AccelerateDecelerateInterpolator());
27 | float lastDegree = maxSwayDegree / 2f;
28 | Animation prepareAnim = new RotateAnimation(0f, lastDegree, Animation.RELATIVE_TO_SELF, .5f, Animation.RELATIVE_TO_SELF, .5f);
29 | prepareAnim.setDuration(maxSwayPeriodDuration >> 1);
30 | rotateSet.addAnimation(prepareAnim);
31 | long startOffSet = maxSwayPeriodDuration >> 1;
32 | boolean run = true;
33 | while (run) {
34 | float nextDegree;
35 | if (lastDegree > 0f) {
36 | nextDegree = -lastDegree;
37 | } else {
38 | nextDegree = -lastDegree - swayDegreeDecrementStep;
39 | if (Float.compare(nextDegree, 0f) <= 0) {
40 | run = false;
41 | }
42 | }
43 | double degreeDistance = Math.abs(lastDegree) + Math.abs(nextDegree);
44 | long duration = (long) ((degreeDistance / maxSwayDegree) * maxSwayPeriodDuration);
45 | Animation rotate = new RotateAnimation(lastDegree, nextDegree, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
46 | rotate.setDuration(duration);
47 | rotate.setStartOffset(startOffSet);
48 | startOffSet += duration;
49 | lastDegree = nextDegree;
50 | rotateSet.addAnimation(rotate);
51 | }
52 | view.startAnimation(rotateSet);
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/app/src/main/java/io/github/trojan_gfw/igniter/common/utils/DecodeUtils.java:
--------------------------------------------------------------------------------
1 | package io.github.trojan_gfw.igniter.common.utils;
2 |
3 | import android.text.TextUtils;
4 | import android.util.Base64;
5 |
6 | import java.nio.charset.StandardCharsets;
7 |
8 | public class DecodeUtils {
9 |
10 | public static String decodeBase64(String rawStr) {
11 | if (TextUtils.isEmpty(rawStr)) {
12 | return rawStr;
13 | }
14 | try {
15 | byte[] decode = Base64.decode(rawStr, Base64.DEFAULT);
16 | return new String(decode, StandardCharsets.UTF_8);
17 | } catch (Exception e) {
18 | e.printStackTrace();
19 | return null;
20 | }
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/app/src/main/java/io/github/trojan_gfw/igniter/common/utils/DisplayUtils.java:
--------------------------------------------------------------------------------
1 | package io.github.trojan_gfw.igniter.common.utils;
2 |
3 | import android.content.Context;
4 | import android.content.res.Resources;
5 | import android.util.DisplayMetrics;
6 |
7 | public class DisplayUtils {
8 | public static int getScreenWidth() {
9 | Resources resources = Resources.getSystem();
10 | DisplayMetrics dm = resources.getDisplayMetrics();
11 | return dm.widthPixels;
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/app/src/main/java/io/github/trojan_gfw/igniter/common/utils/FileUtils.java:
--------------------------------------------------------------------------------
1 | package io.github.trojan_gfw.igniter.common.utils;
2 |
3 | import java.io.File;
4 | import java.io.FileInputStream;
5 | import java.io.FileOutputStream;
6 | import java.io.IOException;
7 |
8 | public class FileUtils {
9 |
10 | public static boolean copy(File src, File dest) {
11 | try (FileOutputStream fos = new FileOutputStream(dest);
12 | FileInputStream fis = new FileInputStream(src)) {
13 | byte[] buff = new byte[4096];
14 | int readBytes;
15 | while ((readBytes = fis.read(buff)) != -1) {
16 | fos.write(buff, 0, readBytes);
17 | }
18 | } catch (IOException e) {
19 | e.printStackTrace();
20 | }
21 | return false;
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/app/src/main/java/io/github/trojan_gfw/igniter/common/utils/PermissionUtils.java:
--------------------------------------------------------------------------------
1 | package io.github.trojan_gfw.igniter.common.utils;
2 |
3 | import android.Manifest;
4 | import android.content.Context;
5 | import android.content.pm.PackageManager;
6 |
7 | import androidx.core.content.ContextCompat;
8 |
9 | public abstract class PermissionUtils {
10 |
11 | public static boolean hasReadWriteExtStoragePermission(Context context) {
12 | return ContextCompat.checkSelfPermission(context,
13 | Manifest.permission.READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED
14 | && ContextCompat.checkSelfPermission(context,
15 | Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED;
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/app/src/main/java/io/github/trojan_gfw/igniter/common/utils/PreferenceUtils.java:
--------------------------------------------------------------------------------
1 | package io.github.trojan_gfw.igniter.common.utils;
2 |
3 | import android.content.ContentResolver;
4 | import android.content.ContentValues;
5 | import android.database.Cursor;
6 | import android.net.Uri;
7 |
8 | import androidx.core.content.ContentResolverCompat;
9 |
10 | public abstract class PreferenceUtils {
11 |
12 | public static boolean getBooleanPreference(ContentResolver resolver, Uri uri, String key, boolean defaultValue) {
13 | return getPreference(resolver, uri, key, defaultValue, ((cursor, columnIndex, type, defValue) -> {
14 | switch (type) {
15 | case Cursor.FIELD_TYPE_STRING:
16 | return Boolean.parseBoolean(cursor.getString(columnIndex));
17 | case Cursor.FIELD_TYPE_INTEGER:
18 | return cursor.getInt(columnIndex) == 1;
19 | default:
20 | return defValue;
21 | }
22 | }));
23 | }
24 |
25 | public static void putBooleanPreference(ContentResolver resolver, Uri uri, String key, boolean value) {
26 | putPreference(resolver, uri, key, value, (v, contentValues) -> contentValues.put(key, v));
27 | }
28 |
29 | public static String getStringPreference(ContentResolver resolver, Uri uri, String key, String defVal) {
30 | return getPreference(resolver, uri, key, defVal, ((cursor, columnIndex, type, defValue) -> {
31 | if (Cursor.FIELD_TYPE_STRING == type) {
32 | return cursor.getString(columnIndex);
33 | }
34 | return defVal;
35 | }));
36 | }
37 |
38 | public static void putStringPreference(ContentResolver resolver, Uri uri, String key, String value) {
39 | putPreference(resolver, uri, key, value, (v, contentValues) -> contentValues.put(key, v));
40 | }
41 |
42 | public static void putIntPreference(ContentResolver resolver, Uri uri, String key, int value) {
43 | putPreference(resolver, uri, key, value, (v, contentValues) -> contentValues.put(key, v));
44 | }
45 |
46 | public static int getIntPreference(ContentResolver resolver, Uri uri, String key, int defVal) {
47 | return getPreference(resolver, uri, key, defVal, ((cursor, columnIndex, type, defValue) -> {
48 | if (Cursor.FIELD_TYPE_INTEGER == type) {
49 | return cursor.getInt(columnIndex);
50 | }
51 | return defVal;
52 | }));
53 | }
54 |
55 | private static true
.
37 | */
38 | private boolean mTapPending;
39 |
40 | @Override
41 | public void onStartListening() {
42 | super.onStartListening();
43 | LogHelper.i(TAG, "onStartListening");
44 | mConnection.connect(this, this);
45 | }
46 |
47 | @Override
48 | public void onStopListening() {
49 | super.onStopListening();
50 | LogHelper.i(TAG, "onStopListening");
51 | mConnection.disconnect(this);
52 | }
53 |
54 | @Override
55 | public void onServiceConnected(ITrojanService service) {
56 | LogHelper.i(TAG, "onServiceConnected");
57 | try {
58 | int state = service.getState();
59 | updateTile(state);
60 | if (mTapPending) {
61 | mTapPending = false;
62 | onClick();
63 | }
64 | } catch (RemoteException e) {
65 | e.printStackTrace();
66 | }
67 | }
68 |
69 | @Override
70 | public void onServiceDisconnected() {
71 | LogHelper.i(TAG, "onServiceDisconnected");
72 | }
73 |
74 | @Override
75 | public void onStateChanged(int state, String msg) {
76 | LogHelper.i(TAG, "onStateChanged# state: " + state + ", msg: " + msg);
77 | updateTile(state);
78 | }
79 |
80 | @Override
81 | public void onTestResult(String testUrl, boolean connected, long delay, @NonNull String error) {
82 | // Do nothing, since TileService will not submit test request.
83 | }
84 |
85 | @Override
86 | public void onBinderDied() {
87 | LogHelper.i(TAG, "onBinderDied");
88 | }
89 |
90 | private void updateTile(final @ProxyService.ProxyState int state) {
91 | Tile tile = getQsTile();
92 | if (tile == null) {
93 | return;
94 | }
95 | LogHelper.i(TAG, "updateTile with state: " + state);
96 | switch (state) {
97 | case ProxyService.STATE_NONE:
98 | tile.setState(Tile.STATE_INACTIVE);
99 | break;
100 | case ProxyService.STOPPED:
101 | break;
102 | case ProxyService.STARTED:
103 | tile.setState(Tile.STATE_ACTIVE);
104 | break;
105 | case ProxyService.STARTING:
106 | case ProxyService.STOPPING:
107 | tile.setState(Tile.STATE_UNAVAILABLE);
108 | break;
109 | default:
110 | LogHelper.e(TAG, "Unknown state: " + state);
111 | break;
112 | }
113 | tile.updateTile();
114 | }
115 |
116 | private boolean isFirstStart() {
117 | return PreferenceUtils.getBooleanPreference(getContentResolver(), Uri.parse(Constants.PREFERENCE_URI),
118 | Constants.PREFERENCE_KEY_FIRST_START, true);
119 | }
120 |
121 | @Override
122 | public void onClick() {
123 | super.onClick();
124 | LogHelper.i(TAG, "onClick");
125 | if (isFirstStart()) {
126 | // if user never open Igniter before, when he/she clicks the tile, it is necessary
127 | // to start the launcher activity for resource preparation.
128 | Intent intent = new Intent(this, MainActivity.class);
129 | intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
130 | startActivity(intent);
131 | return;
132 | }
133 | ITrojanService service = mConnection.getService();
134 | if (service == null) {
135 | mTapPending = true;
136 | updateTile(ProxyService.STARTING);
137 | } else {
138 | try {
139 | @ProxyService.ProxyState int state = service.getState();
140 | updateTile(state);
141 | switch (state) {
142 | case ProxyService.STARTED:
143 | stopProxyService();
144 | break;
145 | case ProxyService.STARTING:
146 | case ProxyService.STOPPING:
147 | break;
148 | case ProxyService.STATE_NONE:
149 | case ProxyService.STOPPED:
150 | startProxyService();
151 | break;
152 | default:
153 | LogHelper.e(TAG, "Unknown state: " + state);
154 | break;
155 | }
156 | } catch (RemoteException e) {
157 | e.printStackTrace();
158 | }
159 | }
160 | }
161 |
162 | /**
163 | * Start ProxyService if everything is ready. Otherwise start the launcher Activity.
164 | */
165 | private void startProxyService() {
166 | if (ProxyHelper.isTrojanConfigValid() && ProxyHelper.isVPNServiceConsented(this)) {
167 | ProxyHelper.startProxyService(this);
168 | } else {
169 | ProxyHelper.startLauncherActivity(this);
170 | }
171 | }
172 |
173 | private void stopProxyService() {
174 | ProxyHelper.stopProxyService(this);
175 | }
176 | }
177 |
--------------------------------------------------------------------------------
/app/src/main/java/io/github/trojan_gfw/igniter/tile/ProxyHelper.java:
--------------------------------------------------------------------------------
1 | package io.github.trojan_gfw.igniter.tile;
2 |
3 | import android.content.Context;
4 | import android.content.Intent;
5 | import android.net.VpnService;
6 |
7 | import androidx.core.content.ContextCompat;
8 |
9 | import io.github.trojan_gfw.igniter.BuildConfig;
10 | import io.github.trojan_gfw.igniter.Globals;
11 | import io.github.trojan_gfw.igniter.MainActivity;
12 | import io.github.trojan_gfw.igniter.ProxyService;
13 | import io.github.trojan_gfw.igniter.R;
14 | import io.github.trojan_gfw.igniter.TrojanConfig;
15 | import io.github.trojan_gfw.igniter.TrojanHelper;
16 |
17 | /**
18 | * Helper class for starting or stopping {@link ProxyService}. Before starting {@link ProxyService},
19 | * make sure the TrojanConfig is valid (with the help of {@link #isTrojanConfigValid()} and whether
20 | * user has consented VPN Service (with the help of {@link #isVPNServiceConsented(Context)}.
21 | *