LEFT | RIGHT |
1 /* | 1 /* |
2 * This file is part of Adblock Plus <http://adblockplus.org/>, | 2 * This file is part of Adblock Plus <http://adblockplus.org/>, |
3 * Copyright (C) 2006-2014 Eyeo GmbH | 3 * Copyright (C) 2006-2014 Eyeo GmbH |
4 * | 4 * |
5 * Adblock Plus is free software: you can redistribute it and/or modify | 5 * Adblock Plus is free software: you can redistribute it and/or modify |
6 * it under the terms of the GNU General Public License version 3 as | 6 * it under the terms of the GNU General Public License version 3 as |
7 * published by the Free Software Foundation. | 7 * published by the Free Software Foundation. |
8 * | 8 * |
9 * Adblock Plus is distributed in the hope that it will be useful, | 9 * Adblock Plus is distributed in the hope that it will be useful, |
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of | 10 * but WITHOUT ANY WARRANTY; without even the implied warranty of |
(...skipping 41 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
52 import android.os.StrictMode; | 52 import android.os.StrictMode; |
53 import android.preference.PreferenceManager; | 53 import android.preference.PreferenceManager; |
54 import android.support.v4.app.NotificationCompat; | 54 import android.support.v4.app.NotificationCompat; |
55 import android.util.Log; | 55 import android.util.Log; |
56 | 56 |
57 import com.stericson.RootTools.RootTools; | 57 import com.stericson.RootTools.RootTools; |
58 import com.stericson.RootTools.RootToolsException; | 58 import com.stericson.RootTools.RootToolsException; |
59 | 59 |
60 public class ProxyService extends Service implements OnSharedPreferenceChangeLis
tener | 60 public class ProxyService extends Service implements OnSharedPreferenceChangeLis
tener |
61 { | 61 { |
| 62 private static final String LOCALHOST = "127.0.0.1"; |
| 63 /** |
| 64 * Indicates that system supports native proxy configuration. |
| 65 */ |
| 66 public static final boolean NATIVE_PROXY_SUPPORTED = Build.VERSION.SDK_INT >=
12; // Honeycomb 3.1 |
| 67 |
62 static | 68 static |
63 { | 69 { |
64 RootTools.debugMode = false; | 70 RootTools.debugMode = false; |
65 } | 71 } |
66 | 72 |
67 private static final String TAG = Utils.getTag(ProxyService.class); | 73 private static final String TAG = Utils.getTag(ProxyService.class); |
68 | |
69 private static final String LOCALHOST = "127.0.0.1"; | |
70 /** | |
71 * Indicates that system supports native proxy configuration. | |
72 */ | |
73 private static final boolean logRequests = false; | 74 private static final boolean logRequests = false; |
74 | 75 |
75 public static final boolean NATIVE_PROXY_SUPPORTED = Build.VERSION.SDK_INT >=
12; // Honeycomb | 76 // Do not use 8080 because it is a "dirty" port, Android uses it if something
goes wrong |
76 private static final int NOTRAFFIC_NOTIFICATION_ID = R.string.app_name + 3; | 77 // first element is reserved for previously used port |
77 static final int ONGOING_NOTIFICATION_ID = R.string.app_name; | 78 private static final int[] portVariants = new int[] {-1, 2020, 3030, 4040, 505
0, 6060, 7070, 9090, 1234, 12345, 4321, 0}; |
78 | |
79 private static final long POSITION_RIGHT = Build.VERSION.SDK_INT >= Build.VERS
ION_CODES.GINGERBREAD ? Long.MIN_VALUE : Long.MAX_VALUE; | |
80 | 79 |
81 private static final int DEFAULT_TIMEOUT = 3000; | 80 private static final int DEFAULT_TIMEOUT = 3000; |
82 private static final int NO_TRAFFIC_TIMEOUT = 5 * 60 * 1000; // 5 minutes | 81 private static final int NO_TRAFFIC_TIMEOUT = 5 * 60 * 1000; // 5 minutes |
83 | 82 |
84 // Do not use 8080 because it is a "dirty" port, Android uses it if something | 83 static final int ONGOING_NOTIFICATION_ID = R.string.app_name; |
85 // goes wrong | 84 private static final long POSITION_RIGHT = Build.VERSION.SDK_INT >= Build.VERS
ION_CODES.GINGERBREAD ? Long.MIN_VALUE : Long.MAX_VALUE; |
86 // first element is reserved for previously used port | 85 private static final int NOTRAFFIC_NOTIFICATION_ID = R.string.app_name + 3; |
87 private static final int[] portVariants = new int[] { -1, 2020, 3030, 4040, 50
50, 6060, 7070, 9090, 1234, 12345, 4321, 0 }; | |
88 | 86 |
89 /** | 87 /** |
90 * Broadcasted when service starts or stops. | 88 * Broadcasted when service starts or stops. |
91 */ | 89 */ |
92 public static final String BROADCAST_STATE_CHANGED = "org.adblockplus.android.
service.state"; | 90 public static final String BROADCAST_STATE_CHANGED = "org.adblockplus.android.
service.state"; |
93 /** | 91 /** |
94 * Broadcasted if proxy fails to start. | 92 * Broadcasted if proxy fails to start. |
95 */ | 93 */ |
96 public static final String BROADCAST_PROXY_FAILED = "org.adblockplus.android.p
roxy.failure"; | 94 public static final String BROADCAST_PROXY_FAILED = "org.adblockplus.android.p
roxy.failure"; |
97 | 95 |
(...skipping 23 matching lines...) Expand all Loading... |
121 private String iptables = null; | 119 private String iptables = null; |
122 | 120 |
123 @SuppressLint("NewApi") | 121 @SuppressLint("NewApi") |
124 @Override | 122 @Override |
125 public void onCreate() | 123 public void onCreate() |
126 { | 124 { |
127 super.onCreate(); | 125 super.onCreate(); |
128 | 126 |
129 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD) | 127 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD) |
130 { | 128 { |
131 // Proxy is running in separate thread, it's just some resolution request | 129 // Proxy is running in separate thread, it's just some resolution request
during initialization. |
132 // during initialization. | 130 // Not worth spawning a separate thread for this. |
133 // Not worth spawning a separate thread for | |
134 final StrictMode.ThreadPolicy policy = new StrictMode.ThreadPolicy.Builder
().permitNetwork().build(); | 131 final StrictMode.ThreadPolicy policy = new StrictMode.ThreadPolicy.Builder
().permitNetwork().build(); |
135 StrictMode.setThreadPolicy(policy); | 132 StrictMode.setThreadPolicy(policy); |
136 } | 133 } |
137 | 134 |
138 // Get port for local proxy | 135 // Get port for local proxy |
139 final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreference
s(this); | 136 final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreference
s(this); |
140 final Resources resources = getResources(); | 137 final Resources resources = getResources(); |
141 | 138 |
142 // Try to read user proxy settings | 139 // Try to read user proxy settings |
143 String proxyHost = null; | 140 String proxyHost = null; |
(...skipping 66 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
210 // Try to set native proxy | 207 // Try to set native proxy |
211 nativeProxyAutoConfigured = ProxySettings.setConnectionProxy(getApplicatio
nContext(), LOCALHOST, port, ""); | 208 nativeProxyAutoConfigured = ProxySettings.setConnectionProxy(getApplicatio
nContext(), LOCALHOST, port, ""); |
212 | 209 |
213 if (NATIVE_PROXY_SUPPORTED) | 210 if (NATIVE_PROXY_SUPPORTED) |
214 { | 211 { |
215 registerReceiver(connectionReceiver, new IntentFilter(ConnectivityManage
r.CONNECTIVITY_ACTION)); | 212 registerReceiver(connectionReceiver, new IntentFilter(ConnectivityManage
r.CONNECTIVITY_ACTION)); |
216 registerReceiver(connectionReceiver, new IntentFilter(Proxy.PROXY_CHANGE
_ACTION)); | 213 registerReceiver(connectionReceiver, new IntentFilter(Proxy.PROXY_CHANGE
_ACTION)); |
217 } | 214 } |
218 } | 215 } |
219 | 216 |
220 // Save current native proxy situation. The service is always started on the | 217 // Save current native proxy situation. The service is always started on the
first run so |
221 // first run so | |
222 // we will always have a correct value from the box | 218 // we will always have a correct value from the box |
223 final SharedPreferences.Editor editor = prefs.edit(); | 219 final SharedPreferences.Editor editor = prefs.edit(); |
224 editor.putBoolean(getString(R.string.pref_proxyautoconfigured), transparent
|| nativeProxyAutoConfigured); | 220 editor.putBoolean(getString(R.string.pref_proxyautoconfigured), transparent
|| nativeProxyAutoConfigured); |
225 editor.commit(); | 221 editor.commit(); |
226 | 222 |
227 registerReceiver(proxyReceiver, new IntentFilter(ProxyService.BROADCAST_PROX
Y_FAILED)); | 223 registerReceiver(proxyReceiver, new IntentFilter(ProxyService.BROADCAST_PROX
Y_FAILED)); |
228 registerReceiver(filterReceiver, new IntentFilter(AdblockPlus.BROADCAST_FILT
ERING_CHANGE)); | 224 registerReceiver(filterReceiver, new IntentFilter(AdblockPlus.BROADCAST_FILT
ERING_CHANGE)); |
229 registerReceiver(filterReceiver, new IntentFilter(AdblockPlus.BROADCAST_FILT
ER_MATCHES)); | 225 registerReceiver(filterReceiver, new IntentFilter(AdblockPlus.BROADCAST_FILT
ER_MATCHES)); |
230 | 226 |
231 // Start proxy | 227 // Start proxy |
232 if (proxy == null) | 228 if (proxy == null) |
233 { | 229 { |
234 // Select available port and bind to it, use previously selected port by | 230 // Select available port and bind to it, use previously selected port by d
efault |
235 // default | |
236 portVariants[0] = prefs.getInt(getString(R.string.pref_lastport), -1); | 231 portVariants[0] = prefs.getInt(getString(R.string.pref_lastport), -1); |
237 ServerSocket listen = null; | 232 ServerSocket listen = null; |
238 String msg = null; | 233 String msg = null; |
239 for (final int p : portVariants) | 234 for (final int p : portVariants) |
240 { | 235 { |
241 if (p < 0) | 236 if (p < 0) |
242 continue; | 237 continue; |
243 try | 238 try |
244 { | 239 { |
245 // Fix for #232, bind proxy socket to loopback only | 240 // Fix for #232, bind proxy socket to loopback only |
(...skipping 71 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
317 Log.e(TAG, "Failed to initialize iptables", e); | 312 Log.e(TAG, "Failed to initialize iptables", e); |
318 } | 313 } |
319 } | 314 } |
320 | 315 |
321 prefs.registerOnSharedPreferenceChangeListener(this); | 316 prefs.registerOnSharedPreferenceChangeListener(this); |
322 | 317 |
323 // Lock service | 318 // Lock service |
324 hideIcon = prefs.getBoolean(getString(R.string.pref_hideicon), resources.get
Boolean(R.bool.def_hideicon)); | 319 hideIcon = prefs.getBoolean(getString(R.string.pref_hideicon), resources.get
Boolean(R.bool.def_hideicon)); |
325 startForeground(ONGOING_NOTIFICATION_ID, getNotification()); | 320 startForeground(ONGOING_NOTIFICATION_ID, getNotification()); |
326 | 321 |
327 // If automatic setting of proxy was blocked, check if user has set it | 322 // If automatic setting of proxy was blocked, check if user has set it manua
lly |
328 // manually | |
329 final boolean manual = isManual(); | 323 final boolean manual = isManual(); |
330 if (manual && NATIVE_PROXY_SUPPORTED) | 324 if (manual && NATIVE_PROXY_SUPPORTED) |
331 { | 325 { |
332 final ConnectivityManager connectivityManager = (ConnectivityManager) getS
ystemService(Context.CONNECTIVITY_SERVICE); | 326 final ConnectivityManager connectivityManager = (ConnectivityManager) getS
ystemService(Context.CONNECTIVITY_SERVICE); |
333 updateNoTrafficCheck(connectivityManager); | 327 updateNoTrafficCheck(connectivityManager); |
334 } | 328 } |
335 | 329 |
336 sendStateChangedBroadcast(); | 330 sendStateChangedBroadcast(); |
337 Log.i(TAG, "Service started"); | 331 Log.i(TAG, "Service started"); |
338 } | 332 } |
(...skipping 49 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
388 if (proxy != null) | 382 if (proxy != null) |
389 proxy.close(); | 383 proxy.close(); |
390 | 384 |
391 // Release service lock | 385 // Release service lock |
392 stopForeground(true); | 386 stopForeground(true); |
393 | 387 |
394 Log.i(TAG, "Service stopped"); | 388 Log.i(TAG, "Service stopped"); |
395 } | 389 } |
396 | 390 |
397 /** | 391 /** |
398 * Restores system proxy settings via native call on Android 3.1+ devices usin
g Java reflection. | 392 * Restores system proxy settings via native call on Android 3.1+ devices |
| 393 * using Java reflection. |
399 */ | 394 */ |
400 private void clearConnectionProxy() | 395 private void clearConnectionProxy() |
401 { | 396 { |
402 final String proxyHost = proxyConfiguration.getProperty("adblock.proxyHost")
; | 397 final String proxyHost = proxyConfiguration.getProperty("adblock.proxyHost")
; |
403 final String proxyPort = proxyConfiguration.getProperty("adblock.proxyPort")
; | 398 final String proxyPort = proxyConfiguration.getProperty("adblock.proxyPort")
; |
404 final String proxyExcl = proxyConfiguration.getProperty("adblock.proxyExcl")
; | 399 final String proxyExcl = proxyConfiguration.getProperty("adblock.proxyExcl")
; |
405 int port = 0; | 400 int port = 0; |
406 try | 401 try |
407 { | 402 { |
408 if (proxyHost != null) | 403 if (proxyHost != null) |
(...skipping 136 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
545 { | 540 { |
546 return notrafficHandler != null; | 541 return notrafficHandler != null; |
547 } | 542 } |
548 | 543 |
549 /** | 544 /** |
550 * Checks if specified host is local. | 545 * Checks if specified host is local. |
551 */ | 546 */ |
552 private static final boolean isLocalHost(final String host) | 547 private static final boolean isLocalHost(final String host) |
553 { | 548 { |
554 if (host == null) | 549 if (host == null) |
555 { | |
556 return false; | 550 return false; |
557 } | |
558 | 551 |
559 try | 552 try |
560 { | 553 { |
561 if (host.equalsIgnoreCase("localhost")) | 554 if (host.equalsIgnoreCase("localhost")) |
562 return true; | 555 return true; |
563 | 556 |
564 final String className = "android.net.NetworkUtils"; | 557 final String className = "android.net.NetworkUtils"; |
565 final Class<?> c = Class.forName(className); | 558 final Class<?> c = Class.forName(className); |
566 /* | 559 /* |
567 * InetAddress address = NetworkUtils.numericToInetAddress(host); | 560 * InetAddress address = NetworkUtils.numericToInetAddress(host); |
(...skipping 68 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
636 return RootTools.sendShell(command, DEFAULT_TIMEOUT); | 629 return RootTools.sendShell(command, DEFAULT_TIMEOUT); |
637 } | 630 } |
638 catch (final Exception e) | 631 catch (final Exception e) |
639 { | 632 { |
640 Log.e(TAG, "Failed to get iptables configuration", e); | 633 Log.e(TAG, "Failed to get iptables configuration", e); |
641 return null; | 634 return null; |
642 } | 635 } |
643 } | 636 } |
644 | 637 |
645 /** | 638 /** |
646 * Raises or removes no traffic notification based on current link proxy setti
ngs | 639 * Raises or removes no traffic notification based on current link proxy |
| 640 * settings |
647 */ | 641 */ |
648 private void updateNoTrafficCheck(final ConnectivityManager connectivityManage
r) | 642 private void updateNoTrafficCheck(final ConnectivityManager connectivityManage
r) |
649 { | 643 { |
650 try | 644 try |
651 { | 645 { |
652 final Object pp = ProxySettings.getActiveLinkProxy(connectivityManager); | 646 final Object pp = ProxySettings.getActiveLinkProxy(connectivityManager); |
653 final String[] userProxy = ProxySettings.getUserProxy(pp); | 647 final String[] userProxy = ProxySettings.getUserProxy(pp); |
654 if (userProxy != null) | 648 if (userProxy != null) |
655 Log.i(TAG, "Proxy settings: " + userProxy[0] + ":" + userProxy[1] + "("
+ userProxy[2] + ")"); | 649 Log.i(TAG, "Proxy settings: " + userProxy[0] + ":" + userProxy[1] + "("
+ userProxy[2] + ")"); |
656 updateNoTrafficCheck(userProxy); | 650 updateNoTrafficCheck(userProxy); |
(...skipping 55 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
712 if (nativeProxyAutoConfigured || proxyManualyConfigured) | 706 if (nativeProxyAutoConfigured || proxyManualyConfigured) |
713 msgId = filtering ? R.string.notif_wifi : R.string.notif_wifi_nofiltering; | 707 msgId = filtering ? R.string.notif_wifi : R.string.notif_wifi_nofiltering; |
714 if (transparent) | 708 if (transparent) |
715 msgId = R.string.notif_all; | 709 msgId = R.string.notif_all; |
716 | 710 |
717 final NotificationCompat.Builder builder = new NotificationCompat.Builder(th
is); | 711 final NotificationCompat.Builder builder = new NotificationCompat.Builder(th
is); |
718 if (hideIcon && msgId != R.string.notif_waiting) | 712 if (hideIcon && msgId != R.string.notif_waiting) |
719 { | 713 { |
720 builder.setWhen(POSITION_RIGHT); | 714 builder.setWhen(POSITION_RIGHT); |
721 builder.setSmallIcon(R.drawable.transparent); | 715 builder.setSmallIcon(R.drawable.transparent); |
722 // builder.setContent(new RemoteViews(getPackageName(), | 716 // builder.setContent(new RemoteViews(getPackageName(), R.layout.notif_hid
den)); |
723 // R.layout.notif_hidden)); | |
724 } | 717 } |
725 else | 718 else |
726 { | 719 { |
727 builder.setWhen(0); | 720 builder.setWhen(0); |
728 builder.setSmallIcon(R.drawable.ic_stat_blocking); | 721 builder.setSmallIcon(R.drawable.ic_stat_blocking); |
729 } | 722 } |
730 final PendingIntent contentIntent = PendingIntent.getActivity(this, 0, new I
ntent(this, Preferences.class).addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.
FLAG_ACTIVITY_NEW_TASK), 0); | 723 final PendingIntent contentIntent = PendingIntent.getActivity(this, 0, new I
ntent(this, Preferences.class).addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.
FLAG_ACTIVITY_NEW_TASK), 0); |
731 builder.setContentIntent(contentIntent); | 724 builder.setContentIntent(contentIntent); |
732 builder.setContentTitle(getText(R.string.app_name)); | 725 builder.setContentTitle(getText(R.string.app_name)); |
733 builder.setContentText(getString(msgId, port)); | 726 builder.setContentText(getString(msgId, port)); |
(...skipping 30 matching lines...) Expand all Loading... |
764 } | 757 } |
765 } | 758 } |
766 | 759 |
767 @Override | 760 @Override |
768 public IBinder onBind(final Intent intent) | 761 public IBinder onBind(final Intent intent) |
769 { | 762 { |
770 return binder; | 763 return binder; |
771 } | 764 } |
772 | 765 |
773 /** | 766 /** |
774 * Executed if no traffic is detected after a period of time. Notifies user ab
out possible | 767 * Executed if no traffic is detected after a period of time. Notifies user |
775 * configuration problems. | 768 * about possible configuration problems. |
776 */ | 769 */ |
777 private final Runnable noTraffic = new Runnable() | 770 private final Runnable noTraffic = new Runnable() |
778 { | 771 { |
779 @Override | 772 @Override |
780 public void run() | 773 public void run() |
781 { | 774 { |
782 // It's weird but notrafficHandler.removeCallbacks(noTraffic) does not | 775 // It's weird but notrafficHandler.removeCallbacks(noTraffic) does not rem
ove this callback |
783 // remove this callback | |
784 if (notrafficHandler == null) | 776 if (notrafficHandler == null) |
785 { | |
786 return; | 777 return; |
787 } | |
788 // Show warning notification | 778 // Show warning notification |
789 final NotificationCompat.Builder builder = new NotificationCompat.Builder(
ProxyService.this); | 779 final NotificationCompat.Builder builder = new NotificationCompat.Builder(
ProxyService.this); |
790 builder.setSmallIcon(R.drawable.ic_stat_warning); | 780 builder.setSmallIcon(R.drawable.ic_stat_warning); |
791 builder.setWhen(System.currentTimeMillis()); | 781 builder.setWhen(System.currentTimeMillis()); |
792 builder.setAutoCancel(true); | 782 builder.setAutoCancel(true); |
793 final Intent intent = new Intent(ProxyService.this, ConfigurationActivity.
class).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); | 783 final Intent intent = new Intent(ProxyService.this, ConfigurationActivity.
class).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); |
794 intent.putExtra("port", port); | 784 intent.putExtra("port", port); |
795 final PendingIntent contentIntent = PendingIntent.getActivity(ProxyService
.this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT); | 785 final PendingIntent contentIntent = PendingIntent.getActivity(ProxyService
.this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT); |
796 builder.setContentIntent(contentIntent); | 786 builder.setContentIntent(contentIntent); |
797 builder.setContentTitle(getText(R.string.app_name)); | 787 builder.setContentTitle(getText(R.string.app_name)); |
(...skipping 40 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
838 public void onReceive(final Context context, final Intent intent) | 828 public void onReceive(final Context context, final Intent intent) |
839 { | 829 { |
840 if (intent.getAction().equals(ProxyService.BROADCAST_PROXY_FAILED)) | 830 if (intent.getAction().equals(ProxyService.BROADCAST_PROXY_FAILED)) |
841 { | 831 { |
842 stopSelf(); | 832 stopSelf(); |
843 } | 833 } |
844 } | 834 } |
845 }; | 835 }; |
846 | 836 |
847 /** | 837 /** |
848 * Monitors system network connection settings changes and updates proxy setti
ngs accordingly. | 838 * Monitors system network connection settings changes and updates proxy |
| 839 * settings accordingly. |
849 */ | 840 */ |
850 private final BroadcastReceiver connectionReceiver = new BroadcastReceiver() | 841 private final BroadcastReceiver connectionReceiver = new BroadcastReceiver() |
851 { | 842 { |
852 @Override | 843 @Override |
853 public void onReceive(final Context ctx, final Intent intent) | 844 public void onReceive(final Context ctx, final Intent intent) |
854 { | 845 { |
855 final String action = intent.getAction(); | 846 final String action = intent.getAction(); |
856 Log.i(TAG, "Action: " + action); | 847 Log.i(TAG, "Action: " + action); |
857 // Connectivity change | 848 // Connectivity change |
858 if (ConnectivityManager.CONNECTIVITY_ACTION.equals(action)) | 849 if (ConnectivityManager.CONNECTIVITY_ACTION.equals(action)) |
(...skipping 75 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
934 @Override | 925 @Override |
935 public void log(final int level, final Object obj, final String message) | 926 public void log(final int level, final Object obj, final String message) |
936 { | 927 { |
937 if (level <= logLevel) | 928 if (level <= logLevel) |
938 { | 929 { |
939 Log.println(7 - level, obj != null ? obj.toString() : TAG, message); | 930 Log.println(7 - level, obj != null ? obj.toString() : TAG, message); |
940 } | 931 } |
941 } | 932 } |
942 } | 933 } |
943 } | 934 } |
LEFT | RIGHT |