diff --git a/app/build.gradle b/app/build.gradle index aeca196..e531194 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,3 +1,5 @@ +import java.text.SimpleDateFormat + apply plugin: 'com.android.application' android { @@ -31,14 +33,14 @@ android { // Generate Signed APK のファイル名を変更 applicationVariants.all { variant -> - if (variant.buildType.name.equals("release")) { + if (variant.buildType.name == "release") { variant.outputs.each { output -> if (output.outputFile != null && output.outputFile.name.endsWith('.apk')) { // Rename APK def versionCode = defaultConfig.versionCode def versionName = defaultConfig.versionName def flavor = variant.flavorName - def date = new java.text.SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date()) + def date = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date()) def newName = "FADownloader-${flavor}-${versionCode}-${versionName}-${date}.apk" output.outputFile = new File((String) output.outputFile.parent, (String) newName) } diff --git a/app/src/main/java/jp/juggler/fadownloader/ActMain.java b/app/src/main/java/jp/juggler/fadownloader/ActMain.java index f1e2d59..aaccb9c 100644 --- a/app/src/main/java/jp/juggler/fadownloader/ActMain.java +++ b/app/src/main/java/jp/juggler/fadownloader/ActMain.java @@ -494,6 +494,14 @@ void startDownloadService(){ boolean repeat = pref.getBoolean( Pref.UI_REPEAT, false ); // 設定から値を読んでバリデーション + + + int target_type = pref.getInt( Pref.UI_TARGET_TYPE, - 1 ); + if( target_type < 0 || target_type > LocationTracker.LOCATION_HIGH_ACCURACY ){ + showToast( true, getString( R.string.target_type_invalid ) ); + return; + } + String flashair_url = pref.getString( Pref.UI_FLASHAIR_URL, "" ).trim(); if( TextUtils.isEmpty( flashair_url ) ){ showToast( true, getString( R.string.url_not_ok ) ); @@ -593,6 +601,8 @@ void startDownloadService(){ // 転送サービスを開始 Intent intent = new Intent( this, DownloadService.class ); intent.setAction( DownloadService.ACTION_START ); + + intent.putExtra( DownloadService.EXTRA_TARGET_TYPE, target_type ); intent.putExtra( DownloadService.EXTRA_REPEAT, repeat ); intent.putExtra( DownloadService.EXTRA_URI, flashair_url ); intent.putExtra( DownloadService.EXTRA_FOLDER_URI, folder_uri ); diff --git a/app/src/main/java/jp/juggler/fadownloader/DownloadService.java b/app/src/main/java/jp/juggler/fadownloader/DownloadService.java index b9cfb15..359a66e 100644 --- a/app/src/main/java/jp/juggler/fadownloader/DownloadService.java +++ b/app/src/main/java/jp/juggler/fadownloader/DownloadService.java @@ -37,6 +37,7 @@ public class DownloadService extends Service{ static final String EXTRA_LOCATION_MODE = "location_mode"; static final String EXTRA_FORCE_WIFI = "force_wifi"; static final String EXTRA_SSID = "ssid"; + static final String EXTRA_TARGET_TYPE = "target_type" ; static final int NOTIFICATION_ID_SERVICE = 1; @@ -369,4 +370,6 @@ void setServiceNotification( String status ){ public static Location getLocation(){ return location; } + + } diff --git a/app/src/main/java/jp/juggler/fadownloader/DownloadWorker.java b/app/src/main/java/jp/juggler/fadownloader/DownloadWorker.java index 2aa3413..dbb55fe 100644 --- a/app/src/main/java/jp/juggler/fadownloader/DownloadWorker.java +++ b/app/src/main/java/jp/juggler/fadownloader/DownloadWorker.java @@ -30,10 +30,14 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; -public class DownloadWorker extends Thread implements CancelChecker{ +public class DownloadWorker extends WorkerBase { static final boolean RECORD_QUEUED_STATE = false; + + public static final int TARGET_TYPE_FLASHAIR_AP = 0; + public static final int TARGET_TYPE_FLASHAIR_STA = 1; + public interface Callback{ void releaseWakeLock(); @@ -51,7 +55,7 @@ public interface Callback{ final Callback callback; final boolean repeat; - final String flashair_url; + String flashair_url; final String folder_uri; final int interval; final String file_type; @@ -59,6 +63,7 @@ public interface Callback{ final ArrayList file_type_list; final boolean force_wifi; final String ssid; + final int target_type; public DownloadWorker( DownloadService service, Intent intent, Callback callback ){ this.service = service; @@ -73,6 +78,7 @@ public DownloadWorker( DownloadService service, Intent intent, Callback callback this.file_type = intent.getStringExtra( DownloadService.EXTRA_FILE_TYPE ); this.force_wifi = intent.getBooleanExtra( DownloadService.EXTRA_FORCE_WIFI, false ); this.ssid = intent.getStringExtra( DownloadService.EXTRA_SSID ); + this.target_type =intent.getIntExtra( DownloadService.EXTRA_TARGET_TYPE, 0 ); LocationTracker.Setting location_setting = new LocationTracker.Setting(); location_setting.interval_desired = intent.getLongExtra( DownloadService.EXTRA_LOCATION_INTERVAL_DESIRED, LocationTracker.DEFAULT_INTERVAL_DESIRED ); @@ -81,6 +87,7 @@ public DownloadWorker( DownloadService service, Intent intent, Callback callback Pref.pref( service ).edit() .putBoolean( Pref.WORKER_REPEAT, repeat ) + .putInt( Pref.WORKER_TARGET_TYPE, target_type ) .putString( Pref.WORKER_FLASHAIR_URL, flashair_url ) .putString( Pref.WORKER_FOLDER_URI, folder_uri ) .putInt( Pref.WORKER_INTERVAL, interval ) @@ -94,7 +101,7 @@ public DownloadWorker( DownloadService service, Intent intent, Callback callback file_type_list = file_type_parse(); - service.wifi_tracker.updateSetting( force_wifi, ssid ); + service.wifi_tracker.updateSetting( force_wifi, ssid ,target_type,flashair_url); service.location_tracker.updateSetting( location_setting ); } @@ -114,6 +121,8 @@ public DownloadWorker( DownloadService service, String cause, Callback callback this.force_wifi = pref.getBoolean( Pref.WORKER_FORCE_WIFI, false ); this.ssid = pref.getString( Pref.WORKER_SSID, null ); + this.target_type = pref.getInt( Pref.WORKER_TARGET_TYPE, 0); + LocationTracker.Setting location_setting = new LocationTracker.Setting(); location_setting.interval_desired = pref.getLong( Pref.WORKER_LOCATION_INTERVAL_DESIRED, LocationTracker.DEFAULT_INTERVAL_DESIRED ); @@ -122,44 +131,22 @@ public DownloadWorker( DownloadService service, String cause, Callback callback file_type_list = file_type_parse(); - service.wifi_tracker.updateSetting( force_wifi, ssid ); + service.wifi_tracker.updateSetting( force_wifi, ssid ,target_type ,flashair_url); service.location_tracker.updateSetting( location_setting ); } final HTTPClient client = new HTTPClient( 30000, 4, "HTTP Client", this ); - final AtomicReference cancel_reason = new AtomicReference<>( null ); - - @Override public boolean isCancelled(){ - return cancel_reason.get() != null; - } - public void cancel( String reason ){ + public boolean cancel( String reason ){ + boolean rv = super.cancel( reason ); + if( rv ) log.i( R.string.thread_cancelled, reason ); try{ - if( cancel_reason.compareAndSet( null, reason ) ){ - log.i( R.string.thread_cancelled, reason ); - } - synchronized( this ){ - notify(); - } client.cancel( log ); }catch( Throwable ex ){ ex.printStackTrace(); } - } - - void waitEx( long ms ){ - try{ - synchronized( this ){ - wait( ms ); - } - }catch( Throwable ex ){ - ex.printStackTrace(); - } - } - - public synchronized void notifyEx(){ - notify(); + return rv; } static final Pattern reJPEG = Pattern.compile( "\\.jp(g|eg?)\\z", Pattern.CASE_INSENSITIVE ); @@ -706,30 +693,59 @@ public String getStatus(){ } callback.acquireWakeLock(); + Object network = null; // 通信状態の確認 - setStatus( false, service.getString( R.string.wifi_check ) ); + setStatus( false, service.getString( R.string.network_check ) ); long network_check_start = SystemClock.elapsedRealtime(); - Object network = null; - while( ! isCancelled() ){ - network = getWiFiNetwork(); - if( network != null ) break; - - // 一定時間待機してもダメならスレッドを停止する - // 通信状態変化でまた起こされる - long er_now = SystemClock.elapsedRealtime(); - if( er_now - network_check_start >= 60 * 1000L ){ - // Pref.pref( service ).edit().putLong( Pref.LAST_IDLE_START, System.currentTimeMillis() ).apply(); - job_queue = null; - cancel( service.getString( R.string.wifi_not_good ) ); - break; + + if( target_type == TARGET_TYPE_FLASHAIR_STA ){ + while( ! isCancelled() ){ + boolean tracker_last_result = service.wifi_tracker.last_result.get(); + String air_url = service.wifi_tracker.last_flash_air_url.get(); + if( tracker_last_result && air_url != null ){ + this.flashair_url = air_url; + break; + } + + // 一定時間待機してもダメならスレッドを停止する + // 通信状態変化でまた起こされる + long er_now = SystemClock.elapsedRealtime(); + if( er_now - network_check_start >= 60 * 1000L ){ + // Pref.pref( service ).edit().putLong( Pref.LAST_IDLE_START, System.currentTimeMillis() ).apply(); + job_queue = null; + cancel( service.getString( R.string.network_not_good ) ); + break; + } + + // 少し待って再確認 + waitEx( 10000L ); } + }else{ - // 少し待って再確認 - waitEx( 10000L ); + while( ! isCancelled() ){ + network = getWiFiNetwork(); + if( network != null ) break; + + // 一定時間待機してもダメならスレッドを停止する + // 通信状態変化でまた起こされる + long er_now = SystemClock.elapsedRealtime(); + if( er_now - network_check_start >= 60 * 1000L ){ + // Pref.pref( service ).edit().putLong( Pref.LAST_IDLE_START, System.currentTimeMillis() ).apply(); + job_queue = null; + cancel( service.getString( R.string.network_not_good ) ); + break; + } + + // 少し待って再確認 + waitEx( 10000L ); + } } if( isCancelled() ) break; + + + // ファイルスキャンの開始 if( job_queue == null ){ Pref.pref( service ).edit().putLong( Pref.LAST_IDLE_START, System.currentTimeMillis() ).apply(); diff --git a/app/src/main/java/jp/juggler/fadownloader/PageSetting.java b/app/src/main/java/jp/juggler/fadownloader/PageSetting.java index e56f80f..0fae445 100644 --- a/app/src/main/java/jp/juggler/fadownloader/PageSetting.java +++ b/app/src/main/java/jp/juggler/fadownloader/PageSetting.java @@ -30,6 +30,8 @@ public class PageSetting extends PagerAdapterBase.PageViewHolder implements View EditText etSSID; Switch swThumbnailAutoRotate; Switch swCopyBeforeViewSend; + Spinner spTargetType; +View btnSSIDPicker; public PageSetting( Activity activity, View ignored ){ super( activity, ignored ); @@ -37,6 +39,7 @@ public PageSetting( Activity activity, View ignored ){ @Override protected void onPageCreate( int page_idx, View root ) throws Throwable{ + spTargetType =(Spinner) root.findViewById( R.id.spTargetType ); etURL = (EditText) root.findViewById( R.id.etURL ); tvFolder = (TextView) root.findViewById( R.id.tvFolder ); etInterval = (EditText) root.findViewById( R.id.etInterval ); @@ -48,6 +51,7 @@ public PageSetting( Activity activity, View ignored ){ etSSID = (EditText) root.findViewById( R.id.etSSID ); swThumbnailAutoRotate = (Switch) root.findViewById( R.id.swThumbnailAutoRotate ); swCopyBeforeViewSend = (Switch) root.findViewById( R.id.swCopyBeforeViewSend ); + btnSSIDPicker = root.findViewById( R.id.btnSSIDPicker ); root.findViewById( R.id.btnFolderPicker ).setOnClickListener( this ); root.findViewById( R.id.btnFolderPickerHelp ).setOnClickListener( this ); @@ -62,6 +66,7 @@ public PageSetting( Activity activity, View ignored ){ root.findViewById( R.id.btnSSIDPicker ).setOnClickListener( this ); root.findViewById( R.id.btnThumbnailAutoRotateHelp ).setOnClickListener( this ); root.findViewById( R.id.btnCopyBeforeViewSendHelp ).setOnClickListener( this ); + root.findViewById( R.id.btnTargetTypeHelp ).setOnClickListener( this ); ArrayAdapter location_mode_adapter = new ArrayAdapter<>( activity @@ -86,6 +91,29 @@ public PageSetting( Activity activity, View ignored ){ updateFormEnabled(); } } ); + + + ArrayAdapter target_type_adapter = new ArrayAdapter<>( + activity + , android.R.layout.simple_spinner_item + ); + target_type_adapter.setDropDownViewResource( R.layout.spinner_dropdown ); + + target_type_adapter.addAll( + activity.getString( R.string.target_type_0 ), + activity.getString( R.string.target_type_1 ) + ); + spTargetType.setAdapter( target_type_adapter ); + spTargetType.setOnItemSelectedListener( new AdapterView.OnItemSelectedListener(){ + @Override public void onItemSelected( AdapterView parent, View view, int position, long id ){ + updateFormEnabled(); + } + + @Override public void onNothingSelected( AdapterView parent ){ + updateFormEnabled(); + } + } ); + swForceWifi.setOnCheckedChangeListener( new CompoundButton.OnCheckedChangeListener(){ @Override public void onCheckedChanged( CompoundButton buttonView, boolean isChecked ){ updateFormEnabled(); @@ -160,6 +188,11 @@ public PageSetting( Activity activity, View ignored ){ case R.id.btnCopyBeforeViewSendHelp: ( (ActMain) activity ).openHelp( activity.getString( R.string.help_copy_before_view_send ) ); break; + case R.id.btnTargetTypeHelp: + ( (ActMain) activity ).openHelp( activity.getString( R.string.help_target_type ) ); + break; + + } } @@ -169,6 +202,9 @@ void ui_value_load(){ String sv; int iv; // + iv = pref.getInt( Pref.UI_TARGET_TYPE, - 1 ); + if( iv >= 0 && iv < spTargetType.getCount() ) spTargetType.setSelection( iv ); + // sv = pref.getString( Pref.UI_FLASHAIR_URL, null ); if( sv != null ) etURL.setText( sv ); // @@ -204,13 +240,18 @@ private void updateFormEnabled(){ etLocationIntervalDesired.setEnabled( location_enabled ); etLocationIntervalMin.setEnabled( location_enabled ); - boolean force_wifi_enabled = swForceWifi.isChecked(); - etSSID.setEnabled( force_wifi_enabled ); + boolean force_wifi_enabled = spTargetType.getSelectedItemPosition() == 0; + swForceWifi.setEnabled( force_wifi_enabled ); + + boolean ssid_enabled = force_wifi_enabled && swForceWifi.isChecked(); + etSSID.setEnabled( ssid_enabled ); + btnSSIDPicker.setEnabled( ssid_enabled ); } // UIフォームの値を設定ファイルに保存 void ui_value_save( SharedPreferences.Editor e ){ e + .putInt( Pref.UI_TARGET_TYPE, spTargetType.getSelectedItemPosition() ) .putString( Pref.UI_FLASHAIR_URL, etURL.getText().toString() ) .putString( Pref.UI_INTERVAL, etInterval.getText().toString() ) .putString( Pref.UI_FILE_TYPE, etFileType.getText().toString() ) diff --git a/app/src/main/java/jp/juggler/fadownloader/Pref.java b/app/src/main/java/jp/juggler/fadownloader/Pref.java index 43bbccc..694e85b 100644 --- a/app/src/main/java/jp/juggler/fadownloader/Pref.java +++ b/app/src/main/java/jp/juggler/fadownloader/Pref.java @@ -14,6 +14,7 @@ public static SharedPreferences pref( Context context ){ // UI画面に表示されている情報の永続化 public static final String UI_REPEAT = "ui_repeat"; public static final String UI_LAST_PAGE = "ui_last_page"; + public static final String UI_TARGET_TYPE = "ui_target_type"; public static final String UI_FLASHAIR_URL = "ui_flashair_url"; public static final String UI_FOLDER_URI = "ui_folder_uri"; public static final String UI_INTERVAL = "ui_interval"; @@ -88,6 +89,7 @@ public static void initialize( Context context ){ // 最後にWorkerを手動開始した時の設定 public static final String WORKER_REPEAT = "worker_repeat"; + public static final String WORKER_TARGET_TYPE = "worker_target_type"; public static final String WORKER_FLASHAIR_URL = "worker_flashair_url"; public static final String WORKER_FOLDER_URI = "worker_folder_uri"; public static final String WORKER_INTERVAL = "worker_interval"; diff --git a/app/src/main/java/jp/juggler/fadownloader/WifiTracker.java b/app/src/main/java/jp/juggler/fadownloader/WifiTracker.java index a0273b7..f48f69b 100644 --- a/app/src/main/java/jp/juggler/fadownloader/WifiTracker.java +++ b/app/src/main/java/jp/juggler/fadownloader/WifiTracker.java @@ -17,11 +17,35 @@ import android.os.SystemClock; import android.text.TextUtils; +import org.apache.commons.io.IOUtils; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.net.ConnectException; +import java.net.DatagramPacket; +import java.net.DatagramSocket; +import java.net.HttpURLConnection; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.MalformedURLException; +import java.net.NetworkInterface; +import java.net.Socket; +import java.net.SocketException; +import java.net.URL; +import java.net.UnknownHostException; +import java.nio.channels.SocketChannel; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; +import java.util.Enumeration; import java.util.LinkedList; import java.util.List; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicReference; +import java.util.regex.Matcher; +import java.util.regex.Pattern; public class WifiTracker{ @@ -38,6 +62,8 @@ interface Callback{ final WifiManager wifiManager; final ConnectivityManager cm; + Worker worker; + public WifiTracker( Context context, LogWriter log, Callback callback ){ this.log = log; this.context = context; @@ -48,61 +74,63 @@ public WifiTracker( Context context, LogWriter log, Callback callback ){ context.registerReceiver( receiver, new IntentFilter( WifiManager.SCAN_RESULTS_AVAILABLE_ACTION ) ); context.registerReceiver( receiver, new IntentFilter( ConnectivityManager.CONNECTIVITY_ACTION ) ); + worker = new Worker(); + worker.start(); } - boolean is_dispose = false; + volatile boolean is_dispose = false; public void dispose(){ is_dispose = true; - handler.removeCallbacks( proc_interval ); context.unregisterReceiver( receiver ); + if( worker != null ){ + worker.cancel( "disposed" ); + worker = null; + } } - boolean force_wifi; - String target_ssid; + final BroadcastReceiver receiver = new BroadcastReceiver(){ + @Override public void onReceive( Context context, Intent intent ){ + if( is_dispose ) return; + if( worker != null ) worker.notifyEx(); + } + }; + + volatile boolean force_wifi; + volatile String target_ssid; + volatile int target_type; // カードはAPモードではなくSTAモードもしくはインターネット同時接続もーどで動作している + volatile String target_url; - public void updateSetting( boolean force_wifi, String ssid ){ + public void updateSetting( boolean force_wifi, String ssid, int target_type, String target_url ){ + if( is_dispose ) return; this.force_wifi = force_wifi; this.target_ssid = ssid; - handler.post( proc_interval ); + this.target_type = target_type; + this.target_url = target_url; + if( worker != null ) worker.notifyEx(); } - static final int SCAN_INTERVAL = 100000; - long last_scan_start; - final LinkedList priority_list = new LinkedList<>(); - boolean last_status = false; - final Runnable proc_interval = new Runnable(){ - @Override public void run(){ - handler.removeCallbacks( proc_interval ); - if( is_dispose || ! force_wifi ) return; - long next = 3000L; - try{ - boolean b = keep_ap(); - if( b != last_status ){ - callback.onConnectionEvent( true, "Wi-Fi tracker" ); - } - last_status = b; - next = b ? 30000L : 3000L; - }catch( Throwable ex ){ - log.e( ex, "connection event handling failed." ); - } - handler.postDelayed( proc_interval, next ); - } - }; + //////////////////////////////////////////////////////////////////////// - final BroadcastReceiver receiver = new BroadcastReceiver(){ - @Override public void onReceive( Context context, Intent intent ){ + static String readStringFile( String path ){ + try{ + FileInputStream fis = new FileInputStream( new File( path ) ); try{ - boolean b = keep_ap(); - if( b != last_status ){ - callback.onConnectionEvent( keep_ap(), intent.getAction() ); + ByteArrayOutputStream bao = new ByteArrayOutputStream(); + IOUtils.copy( fis, bao ); + return Utils.decodeUTF8( bao.toByteArray() ); + }finally{ + try{ + fis.close(); + }catch( Throwable ignored ){ + } - last_status = b; - }catch( Throwable ex ){ - log.e( ex, "connection event handling failed." ); } + }catch( Throwable ex ){ + ex.printStackTrace(); } - }; + return null; + } static class NetworkStatus{ @@ -110,10 +138,38 @@ static class NetworkStatus{ String type_name; String sub_name; String strWifiStatus; + } + + static class NetworkStateList extends ArrayList{ + NetworkStatus wifi_status = null; + boolean other_active = false; + + public void eachNetworkInfo( boolean is_active, NetworkInfo ni ){ + boolean is_wifi = ( ni.getType() == ConnectivityManager.TYPE_WIFI ); + if( ! is_wifi && ! ni.isConnected() ) return; + NetworkStatus ns = new NetworkStatus(); + this.add( ns ); + if( is_wifi ) wifi_status = ns; + ns.type_name = ni.getTypeName(); + ns.sub_name = ni.getSubtypeName(); + + if( is_active ){ + ns.is_active = true; + if( ! is_wifi ) other_active = true; + } + } + + public void afterAllNetwork(){ + if( wifi_status == null ){ + wifi_status = new NetworkStatus(); + this.add( wifi_status ); + wifi_status.type_name = "WIFI"; + } + } } - private String buildCurrentStatus( ArrayList ns_list ){ + static String buildCurrentStatus( ArrayList ns_list ){ Collections.sort( ns_list, new Comparator(){ @Override public int compare( NetworkStatus a, NetworkStatus b ){ if( a.is_active && ! b.is_active ) return - 1; @@ -124,9 +180,9 @@ private String buildCurrentStatus( ArrayList ns_list ){ StringBuilder sb = new StringBuilder(); for( NetworkStatus ns : ns_list ){ if( sb.length() > 0 ) sb.append( " / " ); - if( ns.is_active ) sb.append("(Active)"); + if( ns.is_active ) sb.append( "(Active)" ); if( ns.strWifiStatus != null ){ - sb.append( "Wi-Fi(" ).append( ns.strWifiStatus ).append(')'); + sb.append( "Wi-Fi(" ).append( ns.strWifiStatus ).append( ')' ); }else{ sb.append( ns.type_name ); if( ! TextUtils.isEmpty( ns.sub_name ) ){ @@ -137,275 +193,466 @@ private String buildCurrentStatus( ArrayList ns_list ){ return sb.toString(); } + static final Pattern reIPAddr = Pattern.compile( "(\\d+\\.\\d+\\.\\d+\\.\\d+)" ); + static final Pattern reArp = Pattern.compile( "(\\d+\\.\\d+\\.\\d+\\.\\d+)\\s*(0x\\d+)\\s*(0x\\d+)\\s*([0-9A-Fa-f:]+)" ); + + //////////////////////////////////////////////////////////////////////// + + final AtomicBoolean last_result = new AtomicBoolean(); + final AtomicReference last_flash_air_url = new AtomicReference<>(); + final AtomicReference last_current_status = new AtomicReference<>(); + public void getStatus( StringBuilder sb ){ sb.append( last_current_status ); } - String last_current_status; - String last_force_status; - String last_error_status; - long last_wifi_ap_change; + class Worker extends WorkerBase{ + + public boolean cancel( String reason ){ + boolean rv = super.cancel( reason ); + try{ + this.interrupt(); + }catch( Throwable ignored ){ + } + return rv; + } - @SuppressWarnings( "ConstantConditions" ) boolean keep_ap(){ - if( is_dispose ) return false; + String getWiFiAPAddress(){ - ArrayList ns_list = new ArrayList<>(); - String force_status = null; - String error_status = null; - try{ - NetworkStatus wifi_status = null; - boolean other_active = false; - String active_name = null; - if( Build.VERSION.SDK_INT >= 23 ){ - Network n = cm.getActiveNetwork(); - if( n != null ){ - NetworkInfo ni = cm.getNetworkInfo( n ); - if( ni != null ){ - active_name = ni.getTypeName(); + try{ + Enumeration en = NetworkInterface.getNetworkInterfaces(); + while( en.hasMoreElements() ){ + NetworkInterface ni = en.nextElement(); + try{ + if( ! ni.isUp() ) continue; + if( ni.isLoopback() ) continue; + if( ni.isVirtual() ) continue; + if( ni.isPointToPoint() ) continue; + if( ni.getHardwareAddress() == null ) continue; + Enumeration eip = ni.getInetAddresses(); + while( eip.hasMoreElements() ){ + InetAddress addr = eip.nextElement(); + if( addr.getAddress().length == 4 ){ + return addr.getHostAddress().replaceAll( "[^\\d\\.]+", "" ); + } + } + }catch( SocketException ex ){ + ex.printStackTrace(); } } - }else{ - NetworkInfo ni = cm.getActiveNetworkInfo(); - if( ni != null ){ - active_name = ni.getTypeName(); - } + }catch( SocketException ex ){ + ex.printStackTrace(); } + return null; + } - NetworkInfo[] ni_list; - if( Build.VERSION.SDK_INT >= 21 ){ - Network[] src_list = cm.getAllNetworks(); - ni_list = new NetworkInfo[ src_list == null ? 0 : src_list.length ]; - for( int i = 0, ie = ni_list.length ; i < ie ; ++ i ){ - ni_list[ i ] = cm.getNetworkInfo( src_list[ i ] ); - } - }else{ - ni_list = cm.getAllNetworkInfo(); - } - for( NetworkInfo ni : ni_list ){ - boolean is_wifi = ( ni.getType() == ConnectivityManager.TYPE_WIFI ); - if( ! is_wifi && ! ni.isConnected() ) continue; - NetworkStatus ns = new NetworkStatus(); - ns_list.add( ns ); - if( is_wifi ) wifi_status = ns; - ns.type_name = ni.getTypeName(); - ns.sub_name = ni.getSubtypeName(); - - if( active_name != null && active_name.equals( ns.type_name ) ){ - ns.is_active = true; - if( !is_wifi) other_active = true; + void spray( String nw_ip ){ + long start = SystemClock.elapsedRealtime(); + String ip_base = nw_ip.replaceAll( "\\d+$", "" ); + + try{ + byte[] data = new byte[ 1 ]; + int port = 80; + DatagramSocket socket = new DatagramSocket(); + for( int n = 2 ; n <= 254 ; ++ n ){ + String try_ip = ip_base + n; + if( try_ip.equals( nw_ip ) ) continue; + try{ + + DatagramPacket packet = new DatagramPacket( + data, data.length + , InetAddress.getByName( try_ip ) + , port ); + + socket.send( packet ); + }catch( Throwable ex ){ + ex.printStackTrace(); + } } + socket.close(); + }catch( Throwable ex ){ + ex.printStackTrace(); } + log.d( "sent UDP packet to '%s*' time=%s", ip_base, Utils.formatTimeDuration( SystemClock.elapsedRealtime() - start ) ); + } - if( wifi_status == null ){ - wifi_status = new NetworkStatus(); - ns_list.add( wifi_status ); - wifi_status.type_name = "WIFI"; + boolean checkFlashAirUrl( String check_url ){ + + log.h( "checkFlashAirUrl %s", check_url ); + + final String test_url = check_url + "command.cgi?op=108"; + int rcode; + + URL urlObject; + try{ + urlObject = new URL( test_url ); + }catch( MalformedURLException ex ){ + ex.printStackTrace(); + return false; } - // Wi-Fiが無効なら有効にする + HttpURLConnection conn; try{ - wifi_status.strWifiStatus = "?"; - if( ! wifiManager.isWifiEnabled() ){ - wifi_status.strWifiStatus = context.getString( R.string.not_enabled ); - if( force_wifi ) wifiManager.setWifiEnabled( true ); - return false; - } - }catch( Throwable ex ){ + conn = (HttpURLConnection) urlObject.openConnection(); + }catch( IOException ex ){ ex.printStackTrace(); - error_status = LogWriter.formatError( ex, "setWifiEnabled() failed." ); return false; } - // Wi-Fiの現在の状態を取得する - WifiInfo info; - SupplicantState current_supp_state = null; - String current_ssid = null; try{ - info = wifiManager.getConnectionInfo(); - if( info != null ){ - current_supp_state = info.getSupplicantState(); - String sv = info.getSSID(); - current_ssid = sv == null ? null : sv.replace( "\"", "" ); - } - }catch( Throwable ex ){ + conn.setDoInput( true ); + conn.setConnectTimeout( 5000 ); + conn.setReadTimeout( 5000 ); + conn.setDoOutput( false ); + conn.connect(); + }catch( IOException ignored ){ + return false; + } + + try{ + rcode = conn.getResponseCode(); + }catch( IOException ex ){ ex.printStackTrace(); - error_status = LogWriter.formatError( ex, "getConnectionInfo() failed." ); return false; } - // 設定済みのAPを列挙する - int current_network_id = 0; - WifiConfiguration target_config = null; - int priority_max = 0; + boolean bFound = false; + + if( rcode == 200 ){ + if( ! check_url.equals( last_flash_air_url.get() ) ){ + log.i( "FlashAir found. %s", check_url ); + } + last_flash_air_url.set( check_url ); + bFound = true; + } + try{ - wifi_status.strWifiStatus = context.getString( R.string.no_ap_associated ); + conn.disconnect(); + }catch( Throwable ignored ){ + } - List wc_list = wifiManager.getConfiguredNetworks(); - if( wc_list == null){ - // getConfiguredNetworks() はたまにnullを返す - return false; + return bFound; + } + + boolean checkStaModeFlashAir(){ + + // 設定で指定されたURLを最初に試す + // ただしURLにIPアドレスが書かれている場合のみ + if( reIPAddr.matcher( target_url ).find() ){ + if( checkFlashAirUrl( target_url ) ){ + return true; + } + } + + // ARPテーブルにあるアドレスを試す + String strArp = readStringFile( "/proc/net/arp" ); + if( strArp != null ){ + Matcher m = reArp.matcher( strArp ); + int nCount = 0; + while( m.find() ){ + if( m.group( 4 ).equals( "00:00:00:00:00:00" ) ) continue; + ++ nCount; + final String url = "http://" + m.group( 1 ) + "/"; + if( checkFlashAirUrl( url ) ){ + return true; + } + } + final String ap_addr = getWiFiAPAddress(); + if( ap_addr == null ){ + log.w( "missing Wi-Fi Tethering IP address." ); }else{ - for( WifiConfiguration wc : wc_list ){ - String ssid = wc.SSID.replace( "\"", "" ); + spray( ap_addr ); + } + } + return false; + } - if( wc.priority > priority_max ){ - priority_max = wc.priority; - } + final LinkedList priority_list = new LinkedList<>(); - // 目的のAPを覚えておく - if( target_ssid != null && target_ssid.equals( ssid ) ){ - target_config = wc; - } + String last_force_status; + String last_error_status; + long last_wifi_ap_change; - // 接続中のAPの情報 - if( ssid.equals( current_ssid ) ){ - current_network_id = wc.networkId; - // - String strState = ( current_supp_state == null ? "?" : current_supp_state.toString() ); - strState = Utils.toCamelCase( strState ); - if( "Completed".equals( strState ) ) strState = "Connected"; - wifi_status.strWifiStatus = ssid + "," + strState; - if( ! force_wifi ){ - // AP強制ではないなら、何かアクティブな接続が生きていればOK - return current_supp_state == SupplicantState.COMPLETED; - } + static final int WIFI_SCAN_INTERVAL = 100000; + long last_wifi_scan_start; + + @SuppressWarnings( "ConstantConditions" ) boolean keep_ap(){ + if( isCancelled() ) return false; + + final NetworkStateList ns_list = new NetworkStateList(); + String force_status = null; + String error_status = null; + try{ + if( Build.VERSION.SDK_INT >= 23 ){ + Long active_handle = null; + Network an = cm.getActiveNetwork(); + if( an != null ){ + active_handle = an.getNetworkHandle(); + } + Network[] src_list = cm.getAllNetworks(); + if( src_list != null ){ + for( Network n : src_list ){ + boolean is_active = ( active_handle != null && active_handle == n.getNetworkHandle() ); + NetworkInfo ni = cm.getNetworkInfo( n ); + ns_list.eachNetworkInfo( is_active, ni ); } } + }else{ + String active_name = null; + NetworkInfo ani = cm.getActiveNetworkInfo(); + if( ani != null ){ + active_name = ani.getTypeName(); + } + @SuppressWarnings( "deprecation" ) + NetworkInfo[] src_list = cm.getAllNetworkInfo(); + if( src_list != null ){ + for( NetworkInfo ni : src_list ){ + boolean is_active = ( active_name != null && active_name.equals( ni.getTypeName() ) ); + ns_list.eachNetworkInfo( is_active, ni ); + } + } + } + ns_list.afterAllNetwork(); - if( ! force_wifi ){ - // AP強制ではない場合、接続中のAPがなければNGを返す - return false; - }else if( target_config == null ){ - force_status = context.getString( R.string.wifi_target_ssid_not_found, target_ssid ); + if( target_type == DownloadWorker.TARGET_TYPE_FLASHAIR_STA ){ + return checkStaModeFlashAir(); + } + + // Wi-Fiが無効なら有効にする + try{ + ns_list.wifi_status.strWifiStatus = "?"; + if( ! wifiManager.isWifiEnabled() ){ + ns_list.wifi_status.strWifiStatus = context.getString( R.string.not_enabled ); + if( force_wifi ) wifiManager.setWifiEnabled( true ); return false; } + }catch( Throwable ex ){ + ex.printStackTrace(); + error_status = LogWriter.formatError( ex, "setWifiEnabled() failed." ); + return false; } - }catch( Throwable ex ){ - ex.printStackTrace(); - error_status = LogWriter.formatError( ex, "getConfiguredNetworks() failed." ); - return false; - } - - try{ - // priority の変更 - int p = target_config.priority; - if( p != priority_max ){ - priority_list.add( p ); - if( priority_list.size() > 5 ) priority_list.removeFirst(); - if( priority_list.size() < 5 - || priority_list.getFirst().intValue() != priority_list.getLast().intValue() - ){ - // まだ上がるか試してみる - target_config.priority = priority_max + 1; - wifiManager.updateNetwork( target_config ); - wifiManager.saveConfiguration(); - ////頻出するのでログ出さない log.d( R.string.wifi_ap_priority_changed ); + // Wi-Fiの現在の状態を取得する + WifiInfo info; + SupplicantState current_supp_state = null; + String current_ssid = null; + try{ + info = wifiManager.getConnectionInfo(); + if( info != null ){ + current_supp_state = info.getSupplicantState(); + String sv = info.getSSID(); + current_ssid = sv == null ? null : sv.replace( "\"", "" ); } + }catch( Throwable ex ){ + ex.printStackTrace(); + error_status = LogWriter.formatError( ex, "getConnectionInfo() failed." ); + return false; } - }catch( Throwable ex ){ - ex.printStackTrace(); - error_status = LogWriter.formatError( ex, "updateNetwork() or saveConfiguration() failed." ); - } - // 目的のAPが選択されていた場合 - if( current_ssid != null && current_network_id == target_config.networkId ){ - switch( current_supp_state ){ - case COMPLETED: - // その接続の認証が終わっていて、他の種類の接続がActiveでなければOK - return ! other_active; - case ASSOCIATING: - case ASSOCIATED: - case AUTHENTICATING: - case FOUR_WAY_HANDSHAKE: - case GROUP_HANDSHAKE: - // 現在のstateが何か作業中なら、余計なことはしないがOKでもない + // 設定済みのAPを列挙する + int current_network_id = 0; + WifiConfiguration target_config = null; + int priority_max = 0; + try{ + ns_list.wifi_status.strWifiStatus = context.getString( R.string.no_ap_associated ); + + List wc_list = wifiManager.getConfiguredNetworks(); + if( wc_list == null ){ + // getConfiguredNetworks() はたまにnullを返す + return false; + }else{ + for( WifiConfiguration wc : wc_list ){ + String ssid = wc.SSID.replace( "\"", "" ); + + if( wc.priority > priority_max ){ + priority_max = wc.priority; + } + + // 目的のAPを覚えておく + if( target_ssid != null && target_ssid.equals( ssid ) ){ + target_config = wc; + } + + // 接続中のAPの情報 + if( ssid.equals( current_ssid ) ){ + current_network_id = wc.networkId; + // + String strState = ( current_supp_state == null ? "?" : current_supp_state.toString() ); + strState = Utils.toCamelCase( strState ); + if( "Completed".equals( strState ) ) strState = "Connected"; + ns_list.wifi_status.strWifiStatus = ssid + "," + strState; + if( ! force_wifi ){ + // AP強制ではないなら、何かアクティブな接続が生きていればOK + return current_supp_state == SupplicantState.COMPLETED; + } + } + } + + if( ! force_wifi ){ + // AP強制ではない場合、接続中のAPがなければNGを返す + return false; + }else if( target_config == null ){ + force_status = context.getString( R.string.wifi_target_ssid_not_found, target_ssid ); + return false; + } + } + + }catch( Throwable ex ){ + ex.printStackTrace(); + error_status = LogWriter.formatError( ex, "getConfiguredNetworks() failed." ); return false; } - } - // スキャン範囲内に目的のSSIDがあるか? - boolean found_in_scan = false; - try{ - for( ScanResult result : wifiManager.getScanResults() ){ - if( target_ssid != null && target_ssid.equals( result.SSID.replace( "\"", "" ) ) ){ - found_in_scan = true; - break; + try{ + // priority の変更 + int p = target_config.priority; + if( p != priority_max ){ + priority_list.add( p ); + if( priority_list.size() > 5 ) priority_list.removeFirst(); + if( priority_list.size() < 5 + || priority_list.getFirst().intValue() != priority_list.getLast().intValue() + ){ + // まだ上がるか試してみる + target_config.priority = priority_max + 1; + wifiManager.updateNetwork( target_config ); + wifiManager.saveConfiguration(); + ////頻出するのでログ出さない log.d( R.string.wifi_ap_priority_changed ); + } } + }catch( Throwable ex ){ + ex.printStackTrace(); + error_status = LogWriter.formatError( ex, "updateNetwork() or saveConfiguration() failed." ); } - }catch( Throwable ex ){ - ex.printStackTrace(); - error_status = LogWriter.formatError( ex, "getScanResults() failed." ); - return false; - } - // スキャン範囲内にない場合、定期的にスキャン開始 - if( ! found_in_scan ){ - force_status = context.getString( R.string.wifi_target_ssid_not_scanned, target_ssid ); + // 目的のAPが選択されていた場合 + if( current_ssid != null && current_network_id == target_config.networkId ){ + switch( current_supp_state ){ + case COMPLETED: + // その接続の認証が終わっていて、他の種類の接続がActiveでなければOK + return ! ns_list.other_active; + case ASSOCIATING: + case ASSOCIATED: + case AUTHENTICATING: + case FOUR_WAY_HANDSHAKE: + case GROUP_HANDSHAKE: + // 現在のstateが何か作業中なら、余計なことはしないがOKでもない + return false; + } + } - // 定期的にスキャン開始 + // スキャン範囲内に目的のSSIDがあるか? + boolean found_in_scan = false; try{ - long now = SystemClock.elapsedRealtime(); - if( now - last_scan_start >= SCAN_INTERVAL ){ - last_scan_start = now; - wifiManager.startScan(); - log.d( R.string.wifi_scan_start ); + for( ScanResult result : wifiManager.getScanResults() ){ + if( target_ssid != null && target_ssid.equals( result.SSID.replace( "\"", "" ) ) ){ + found_in_scan = true; + break; + } } }catch( Throwable ex ){ ex.printStackTrace(); - error_status = LogWriter.formatError( ex, "startScan() failed." ); + error_status = LogWriter.formatError( ex, "getScanResults() failed." ); + return false; } - return false; - } + // スキャン範囲内にない場合、定期的にスキャン開始 + if( ! found_in_scan ){ + force_status = context.getString( R.string.wifi_target_ssid_not_scanned, target_ssid ); + + // 定期的にスキャン開始 + try{ + long now = SystemClock.elapsedRealtime(); + if( now - last_wifi_scan_start >= WIFI_SCAN_INTERVAL ){ + last_wifi_scan_start = now; + wifiManager.startScan(); + log.d( R.string.wifi_scan_start ); + } + }catch( Throwable ex ){ + ex.printStackTrace(); + error_status = LogWriter.formatError( ex, "startScan() failed." ); + } - long now = SystemClock.elapsedRealtime(); - if( now - last_wifi_ap_change >= 5000L ){ - last_wifi_ap_change = now; + return false; + } - try{ - // 先に既存接続を無効にする - for( WifiConfiguration wc : wifiManager.getConfiguredNetworks() ){ - if( wc.networkId != target_config.networkId ){ - String ssid = wc.SSID.replace( "\"", "" ); - if( wc.status == WifiConfiguration.Status.CURRENT ){ - log.d( "%sから切断させます", ssid ); - wifiManager.disableNetwork( wc.networkId ); - }else if( wc.status == WifiConfiguration.Status.ENABLED ){ - log.d( "%sへの自動接続を無効化します", ssid ); - wifiManager.disableNetwork( wc.networkId ); + long now = SystemClock.elapsedRealtime(); + if( now - last_wifi_ap_change >= 5000L ){ + last_wifi_ap_change = now; + + try{ + // 先に既存接続を無効にする + for( WifiConfiguration wc : wifiManager.getConfiguredNetworks() ){ + if( wc.networkId != target_config.networkId ){ + String ssid = wc.SSID.replace( "\"", "" ); + if( wc.status == WifiConfiguration.Status.CURRENT ){ + log.d( "%sから切断させます", ssid ); + wifiManager.disableNetwork( wc.networkId ); + }else if( wc.status == WifiConfiguration.Status.ENABLED ){ + log.d( "%sへの自動接続を無効化します", ssid ); + wifiManager.disableNetwork( wc.networkId ); + } } } - } - String target_ssid = target_config.SSID.replace( "\"", "" ); - log.d( "%s への接続を試みます", target_ssid ); - wifiManager.enableNetwork( target_config.networkId, true ); + String target_ssid = target_config.SSID.replace( "\"", "" ); + log.d( "%s への接続を試みます", target_ssid ); + wifiManager.enableNetwork( target_config.networkId, true ); - return false; + return false; - }catch( Throwable ex ){ - ex.printStackTrace(); - error_status = LogWriter.formatError( ex, "disableNetwork() or enableNetwork() failed." ); + }catch( Throwable ex ){ + ex.printStackTrace(); + error_status = LogWriter.formatError( ex, "disableNetwork() or enableNetwork() failed." ); + } } - } - return false; - }finally{ - String current_status = buildCurrentStatus( ns_list ); - if( current_status != null && ! current_status.equals( last_current_status ) ){ - last_current_status = current_status; - log.d( context.getString( R.string.network_status, current_status ) ); - } + return false; + }finally{ + String current_status = buildCurrentStatus( ns_list ); + if( current_status != null && ! current_status.equals( last_current_status.get() ) ){ + last_current_status.set( current_status ); + log.d( context.getString( R.string.network_status, current_status ) ); + } + + if( error_status != null && ! error_status.equals( last_error_status ) ){ + log.e( last_error_status = error_status ); + } - if( error_status != null && ! error_status.equals( last_error_status ) ){ - log.e( last_error_status = error_status ); + if( force_status != null && ! force_status.equals( last_force_status ) ){ + log.w( last_force_status = force_status ); + } } + } - if( force_status != null && ! force_status.equals( last_force_status ) ){ - log.w( last_force_status = force_status ); + @Override public void run(){ + while( ! isCancelled() ){ + boolean result; + try{ + log.h( "WifiTracker check start." ); + result = keep_ap(); + log.h( "WifiTracker check end." ); + if( isCancelled() ) break; + }catch( Throwable ex ){ + log.e( ex, "network check failed." ); + result = false; + } + + if( result != last_result.get() ){ + last_result.set( result ); + handler.post( new Runnable(){ + @Override public void run(){ + if( is_dispose ) return; + try{ + callback.onConnectionEvent( true, "Wi-Fi tracker" ); + }catch( Throwable ex ){ + log.e( ex, "connection event handling failed." ); + } + } + } ); + } + long next = result ? 30000L : 3000L; + waitEx( next ); } } } - } diff --git a/app/src/main/java/jp/juggler/fadownloader/WorkerBase.java b/app/src/main/java/jp/juggler/fadownloader/WorkerBase.java new file mode 100644 index 0000000..a49d069 --- /dev/null +++ b/app/src/main/java/jp/juggler/fadownloader/WorkerBase.java @@ -0,0 +1,31 @@ +package jp.juggler.fadownloader; + +import java.util.concurrent.atomic.AtomicReference; + +abstract public class WorkerBase extends Thread implements CancelChecker{ + + public synchronized void waitEx( long ms ){ + try{ + wait( ms ); + }catch( InterruptedException ignored ){ + } + } + + public synchronized void notifyEx(){ + notify(); + } + + final AtomicReference cancel_reason = new AtomicReference<>( null ); + + @Override public boolean isCancelled(){ + return cancel_reason.get() != null; + } + + public boolean cancel(String reason){ + boolean rv = cancel_reason.compareAndSet( null, reason ); + notifyEx(); + return rv; + } + + public abstract void run(); +} diff --git a/app/src/main/res/layout/page_setting.xml b/app/src/main/res/layout/page_setting.xml index 6738451..c584045 100644 --- a/app/src/main/res/layout/page_setting.xml +++ b/app/src/main/res/layout/page_setting.xml @@ -23,6 +23,27 @@ android:orientation="vertical" > + + + + + + + + +