From 91dd3ef2814857199eb5a2ba941b67c2b7b5f9ae Mon Sep 17 00:00:00 2001 From: tateisu Date: Tue, 7 Mar 2017 09:18:00 +0900 Subject: [PATCH] 1.4: minSdkVersion 14 --- app/build.gradle | 6 +- app/src/main/AndroidManifest.xml | 8 + .../java/jp/juggler/fadownloader/ActMain.java | 74 +++++-- .../juggler/fadownloader/DownloadWorker.java | 209 +++++------------- .../jp/juggler/fadownloader/FilePathX.java | 193 ++++++++++++++++ .../jp/juggler/fadownloader/FolderPicker.java | 154 +++++++++++++ .../jp/juggler/fadownloader/HTTPClient.java | 17 +- .../java/jp/juggler/fadownloader/Page0.java | 39 ++-- .../fadownloader/PermissionChecker.java | 3 +- .../java/jp/juggler/fadownloader/Utils.java | 19 +- .../jp/juggler/fadownloader/WiFiTracker.java | 27 ++- .../btn_help_bg.xml} | 0 app/src/main/res/drawable/btn_help_bg.xml | 13 ++ app/src/main/res/layout/folder_picker.xml | 30 +++ app/src/main/res/layout/help_local_folder.xml | 6 +- app/src/main/res/layout/help_mode.xml | 6 +- app/src/main/res/layout/help_single_text.xml | 6 +- app/src/main/res/layout/page0.xml | 7 +- app/src/main/res/layout/page1.xml | 4 +- app/src/main/res/layout/page2.xml | 8 +- app/src/main/res/values-ja/strings.xml | 5 + app/src/main/res/values/strings.xml | 5 + app/src/main/res/values/styles.xml | 27 ++- exif/build.gradle | 2 +- 24 files changed, 645 insertions(+), 223 deletions(-) create mode 100644 app/src/main/java/jp/juggler/fadownloader/FilePathX.java create mode 100644 app/src/main/java/jp/juggler/fadownloader/FolderPicker.java rename app/src/main/res/{drawable/btn_borderless_material.xml => drawable-v21/btn_help_bg.xml} (100%) create mode 100644 app/src/main/res/drawable/btn_help_bg.xml create mode 100644 app/src/main/res/layout/folder_picker.xml diff --git a/app/build.gradle b/app/build.gradle index 27a518a..2abcdfc 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -5,10 +5,10 @@ android { buildToolsVersion "25.0.2" defaultConfig { applicationId "jp.juggler.fadownloader" - minSdkVersion 21 + minSdkVersion 14 targetSdkVersion 25 - versionCode 6 - versionName "1.3" + versionCode 8 + versionName "1.4" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" } buildTypes { diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 13fcd91..0ef1802 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -54,6 +54,14 @@ + + \ No newline at end of file diff --git a/app/src/main/java/jp/juggler/fadownloader/ActMain.java b/app/src/main/java/jp/juggler/fadownloader/ActMain.java index 34f474a..c9a6d1c 100644 --- a/app/src/main/java/jp/juggler/fadownloader/ActMain.java +++ b/app/src/main/java/jp/juggler/fadownloader/ActMain.java @@ -10,6 +10,7 @@ import android.content.IntentSender; import android.content.SharedPreferences; import android.net.Uri; +import android.os.Build; import android.os.Handler; import android.provider.Settings; import android.support.annotation.NonNull; @@ -47,6 +48,8 @@ import com.google.android.gms.location.LocationSettingsResult; import com.google.android.gms.location.LocationSettingsStatusCodes; +import java.io.File; +import java.io.FileOutputStream; import java.lang.ref.WeakReference; import java.util.ArrayList; @@ -61,6 +64,7 @@ public class ActMain static final int REQUEST_CODE_DOCUMENT = 2; static final int REQUEST_CHECK_SETTINGS = 3; static final int REQUEST_PURCHASE = 4; + static final int REQUEST_FOLDER_PICKER = 5; TextView tvStatus; @@ -165,15 +169,55 @@ public class ActMain if( requestCode == REQUEST_CODE_DOCUMENT ){ if( resultCode == Activity.RESULT_OK ){ - Uri treeUri = resultData.getData(); - // 永続的な許可を取得 - getContentResolver().takePersistableUriPermission( treeUri, Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION ); - // 覚えておく - Pref.pref( this ).edit() - .putString( Pref.UI_FOLDER_URI, treeUri.toString() ) - .apply(); + if( Build.VERSION.SDK_INT >= 21){ + try{ + Uri treeUri = resultData.getData(); + // 永続的な許可を取得 + getContentResolver().takePersistableUriPermission( treeUri, Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION ); + // 覚えておく + Pref.pref( this ).edit() + .putString( Pref.UI_FOLDER_URI, treeUri.toString() ) + .apply(); + }catch(Throwable ex){ + ex.printStackTrace( ); + Toast.makeText(this,String.format("folder access failed. %s %s",ex.getClass().getSimpleName(),ex.getMessage()),Toast.LENGTH_LONG).show(); + } + } + } + Page0 page = pager_adapter.getPage( 0 ); + if( page != null ) page.folder_view_update(); + return; + }else if ( requestCode == REQUEST_FOLDER_PICKER ){ + if( resultCode == Activity.RESULT_OK ){ + try{ + String path = resultData.getStringExtra( FolderPicker.EXTRA_FOLDER ); + String dummy = Thread.currentThread().getId()+"."+android.os.Process.myPid(); + File test_dir = new File( new File( path ), dummy ); + test_dir.mkdir(); + try{ + File test_file = new File( test_dir, dummy ); + try{ + FileOutputStream fos = new FileOutputStream( test_file ); + try{ + fos.write( Utils.encodeUTF8( "TEST" ) ); + }finally{ + fos.close(); + } + }finally{ + test_file.delete(); + } + }finally{ + test_dir.delete(); + } + // 覚えておく + Pref.pref( this ).edit() + .putString( Pref.UI_FOLDER_URI, path ) + .apply(); + }catch(Throwable ex){ + ex.printStackTrace( ); + Toast.makeText(this,String.format("folder access failed. %s %s",ex.getClass().getSimpleName(),ex.getMessage()),Toast.LENGTH_LONG).show(); + } } - Page0 page = pager_adapter.getPage( 0 ); if( page != null ) page.folder_view_update(); return; @@ -397,14 +441,18 @@ void startDownloadService(){ String folder_uri = null; sv = pref.getString( Pref.UI_FOLDER_URI, null ); if( ! TextUtils.isEmpty( sv ) ){ - DocumentFile folder = DocumentFile.fromTreeUri( this, Uri.parse( sv ) ); - if( folder != null ){ - if( folder.exists() && folder.canWrite() ){ - folder_uri = sv; + if( Build.VERSION.SDK_INT >= 21 ){ + DocumentFile folder = DocumentFile.fromTreeUri( this, Uri.parse( sv ) ); + if( folder != null ){ + if( folder.exists() && folder.canWrite() ){ + folder_uri = sv; + } } + }else{ + folder_uri = sv; } } - if( folder_uri == null ){ + if( TextUtils.isEmpty( folder_uri ) ){ Toast.makeText( this, getString( R.string.folder_not_ok ), Toast.LENGTH_SHORT ).show(); return; } diff --git a/app/src/main/java/jp/juggler/fadownloader/DownloadWorker.java b/app/src/main/java/jp/juggler/fadownloader/DownloadWorker.java index e4e6711..7e0d5a1 100644 --- a/app/src/main/java/jp/juggler/fadownloader/DownloadWorker.java +++ b/app/src/main/java/jp/juggler/fadownloader/DownloadWorker.java @@ -6,19 +6,18 @@ import android.content.Intent; import android.content.SharedPreferences; import android.location.Location; +import android.net.ConnectivityManager; import android.net.Network; +import android.net.NetworkInfo; import android.net.Uri; import android.os.Build; import android.os.SystemClock; -import android.support.v4.provider.DocumentFile; import it.sephiroth.android.library.exif2.ExifInterface; import java.io.InputStream; import java.io.OutputStream; import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; import java.util.LinkedList; import java.util.concurrent.atomic.AtomicReference; import java.util.regex.Matcher; @@ -61,13 +60,13 @@ public DownloadWorker( DownloadService service, Intent intent, Callback callback this.folder_uri = intent.getStringExtra( DownloadService.EXTRA_FOLDER_URI ); this.interval = intent.getIntExtra( DownloadService.EXTRA_INTERVAL, 86400 ); this.file_type = intent.getStringExtra( DownloadService.EXTRA_FILE_TYPE ); - boolean force_wifi = intent.getBooleanExtra( DownloadService.EXTRA_FORCE_WIFI ,false); - String ssid =intent.getStringExtra( DownloadService.EXTRA_SSID ); + boolean force_wifi = intent.getBooleanExtra( DownloadService.EXTRA_FORCE_WIFI, false ); + String ssid = intent.getStringExtra( DownloadService.EXTRA_SSID ); LocationTracker.Setting location_setting = new LocationTracker.Setting(); - location_setting.interval_desired = intent.getLongExtra( DownloadService.EXTRA_LOCATION_INTERVAL_DESIRED ,LocationTracker.DEFAULT_INTERVAL_DESIRED); - location_setting.interval_min = intent.getLongExtra( DownloadService.EXTRA_LOCATION_INTERVAL_MIN ,LocationTracker.DEFAULT_INTERVAL_MIN); - location_setting.mode = intent.getIntExtra( DownloadService.EXTRA_LOCATION_MODE ,LocationTracker.DEFAULT_MODE); + location_setting.interval_desired = intent.getLongExtra( DownloadService.EXTRA_LOCATION_INTERVAL_DESIRED, LocationTracker.DEFAULT_INTERVAL_DESIRED ); + location_setting.interval_min = intent.getLongExtra( DownloadService.EXTRA_LOCATION_INTERVAL_MIN, LocationTracker.DEFAULT_INTERVAL_MIN ); + location_setting.mode = intent.getIntExtra( DownloadService.EXTRA_LOCATION_MODE, LocationTracker.DEFAULT_MODE ); Pref.pref( service ).edit() .putBoolean( Pref.WORKER_REPEAT, repeat ) @@ -76,7 +75,7 @@ public DownloadWorker( DownloadService service, Intent intent, Callback callback .putInt( Pref.WORKER_INTERVAL, interval ) .putString( Pref.WORKER_FILE_TYPE, file_type ) .putLong( Pref.WORKER_LOCATION_INTERVAL_DESIRED, location_setting.interval_desired ) - .putLong( Pref.WORKER_LOCATION_INTERVAL_MIN, location_setting.interval_min ) + .putLong( Pref.WORKER_LOCATION_INTERVAL_MIN, location_setting.interval_min ) .putInt( Pref.WORKER_LOCATION_MODE, location_setting.mode ) .putBoolean( Pref.WORKER_FORCE_WIFI, force_wifi ) .putString( Pref.WORKER_SSID, ssid ) @@ -84,9 +83,9 @@ 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 ); - service.location_tracker.updateSetting(location_setting); + service.location_tracker.updateSetting( location_setting ); } public DownloadWorker( DownloadService service, Callback callback ){ @@ -103,18 +102,18 @@ public DownloadWorker( DownloadService service, Callback callback ){ this.file_type = pref.getString( Pref.WORKER_FILE_TYPE, null ); boolean force_wifi = pref.getBoolean( Pref.WORKER_FORCE_WIFI, false ); - String ssid =pref.getString( Pref.WORKER_SSID, null ); + String ssid = pref.getString( Pref.WORKER_SSID, null ); LocationTracker.Setting location_setting = new LocationTracker.Setting(); - location_setting.interval_desired = pref.getLong(Pref.WORKER_LOCATION_INTERVAL_DESIRED ,LocationTracker.DEFAULT_INTERVAL_DESIRED); - location_setting.interval_min = pref.getLong(Pref.WORKER_LOCATION_INTERVAL_MIN,LocationTracker.DEFAULT_INTERVAL_MIN); - location_setting.mode = pref.getInt(Pref.WORKER_LOCATION_MODE ,LocationTracker.DEFAULT_MODE); + location_setting.interval_desired = pref.getLong( Pref.WORKER_LOCATION_INTERVAL_DESIRED, LocationTracker.DEFAULT_INTERVAL_DESIRED ); + location_setting.interval_min = pref.getLong( Pref.WORKER_LOCATION_INTERVAL_MIN, LocationTracker.DEFAULT_INTERVAL_MIN ); + location_setting.mode = pref.getInt( Pref.WORKER_LOCATION_MODE, LocationTracker.DEFAULT_MODE ); file_type_list = file_type_parse(); - service.wifi_tracker.updateSetting(force_wifi,ssid); + service.wifi_tracker.updateSetting( force_wifi, ssid ); - service.location_tracker.updateSetting(location_setting); + service.location_tracker.updateSetting( location_setting ); } final AtomicReference status = new AtomicReference<>( "?" ); @@ -173,88 +172,16 @@ void waitEx( long ms ){ } } - static class FilePathX{ - - DocumentFile document_file; - String name; - FilePathX parent; - ArrayList file_list; - - public ArrayList getFileList(){ - if( document_file != null ){ - if( file_list != null ) return file_list; - ArrayList result = new ArrayList<>(); - Collections.addAll( result, document_file.listFiles() ); - Collections.sort( result, new Comparator(){ - @Override public int compare( DocumentFile a, DocumentFile b ){ - return a.getName().compareTo( b.getName() ); - } - } ); - return file_list = result; - }else if( parent != null ){ - ArrayList parent_childs = parent.getFileList(); - if( parent_childs != null ){ - DocumentFile file = bsearch( parent_childs, name ); - if( file != null ){ - this.document_file = file; - return getFileList(); - } - } - } - return null; - } - - private DocumentFile prepareDirectory( LogWriter log ){ - try{ - if( document_file != null ) return document_file; - if( parent != null ){ - DocumentFile parent_dir = parent.prepareDirectory( log ); - if( parent_dir == null ) return null; - - ArrayList parent_list = parent.getFileList(); - DocumentFile file = bsearch( parent_list, name ); - if( file == null ){ - log.i( R.string.folder_create, name ); - file = parent_dir.createDirectory( name ); - } - return document_file = file; - } - }catch( Throwable ex ){ - log.e( R.string.folder_create_failed, ex.getClass().getSimpleName(), ex.getMessage() ); - } - return null; - } - - public DocumentFile prepareFile( LogWriter log ){ - try{ - if( document_file != null ) return document_file; - if( parent != null ){ - DocumentFile parent_dir = parent.prepareDirectory( log ); - if( parent_dir == null ) return null; - - DocumentFile file = bsearch( parent.getFileList(), name ); - if( file == null ){ - file = parent_dir.createFile( "application/octet-stream", name ); - } - return document_file = file; - } - }catch( Throwable ex ){ - log.e( R.string.file_create_failed, ex.getClass().getSimpleName(), ex.getMessage() ); - } - return null; - } - } - static class Item{ final String air_path; - final FilePathX local_path; + final FilePathX local_file; final boolean is_file; final long size; - Item( String air_path, FilePathX local_path, boolean is_file, long size ){ + Item( String air_path, FilePathX local_file, boolean is_file, long size ){ this.air_path = air_path; - this.local_path = local_path; + this.local_file = local_file; this.is_file = is_file; this.size = size; } @@ -263,23 +190,6 @@ static class Item{ static final Pattern reLine = Pattern.compile( "([^\\x0d\\x0a]+)" ); static final Pattern reAttr = Pattern.compile( ",(\\d+),(\\d+),(\\d+),(\\d+)$" ); - private static DocumentFile bsearch( ArrayList local_files, String fname ){ - int start = 0; - int end = local_files.size(); - while( ( end - start ) > 0 ){ - int mid = ( ( start + end ) >> 1 ); - DocumentFile x = local_files.get( mid ); - int i = fname.compareTo( x.getName() ); - if( i < 0 ){ - end = mid; - }else if( i > 0 ){ - start = mid + 1; - }else{ - return x; - } - } - return null; - } @Override public void run(){ @@ -287,7 +197,6 @@ private static DocumentFile bsearch( ArrayList local_files, String boolean allow_stop_service = false; - callback.onThreadStart(); while( ! isCancelled() ){ @@ -349,10 +258,10 @@ private static DocumentFile bsearch( ArrayList local_files, String status.set( service.getString( R.string.wifi_check ) ); // 通信の安定を確認 - Network network = null; long network_check_start = SystemClock.elapsedRealtime(); + Object network = null; while( ! isCancelled() ){ - network = Utils.getWiFiNetwork( service ); + network = (Object) getWiFiNetwork( service ); if( network != null ) break; // long er_now = SystemClock.elapsedRealtime(); @@ -405,8 +314,7 @@ private static DocumentFile bsearch( ArrayList local_files, String // フォルダを探索する final LinkedList job_queue = new LinkedList<>(); { - FilePathX local_path = new FilePathX(); - local_path.document_file = DocumentFile.fromTreeUri( service, Uri.parse( folder_uri ) ); + FilePathX local_path = new FilePathX(service,folder_uri); job_queue.add( new Item( "/", local_path, false, 0L ) ); } boolean has_error = false; @@ -466,8 +374,8 @@ private static DocumentFile bsearch( ArrayList local_files, String int time = Integer.parseInt( mAttr.group( 4 ), 10 ); // https://flashair-developers.com/ja/support/forum/#/discussion/3/%E3%82%AB%E3%83%B3%E3%83%9E%E5%8C%BA%E5%88%87%E3%82%8A - String dir = (item.air_path.equals( "/" )? "": item.air_path); - String fname = line.substring( dir.length() + 1, mAttr.start() ); + String dir = ( item.air_path.equals( "/" ) ? "" : item.air_path ); + String file_name = line.substring( dir.length() + 1, mAttr.start() ); if( ( attr & 2 ) != 0 ){ // skip hidden file @@ -477,11 +385,9 @@ private static DocumentFile bsearch( ArrayList local_files, String continue; } - String child_air_path = dir + "/" + fname; + String child_air_path = dir + "/" + file_name; - FilePathX local_child = new FilePathX(); - local_child.parent = item.local_path; - local_child.name = fname; + final FilePathX local_child = new FilePathX(item.local_file,file_name); if( ( attr & 0x10 ) != 0 ){ // サブフォルダはキューに追加する @@ -495,7 +401,7 @@ private static DocumentFile bsearch( ArrayList local_files, String // file type matching boolean bMatch = false; for( Pattern re : file_type_list ){ - if( ! re.matcher( fname ).find() ) continue; + if( ! re.matcher( file_name ).find() ) continue; bMatch = true; break; } @@ -503,25 +409,23 @@ private static DocumentFile bsearch( ArrayList local_files, String continue; } - DocumentFile file = local_child.prepareFile( log ); - if( file == null ){ - log.e( "%s//%s :skip. can not prepare local file.", item.air_path, fname ); + if( ! local_child.prepareFile( log ) ){ + log.e( "%s//%s :skip. can not prepare local file.", item.air_path, file_name ); continue; - }else if( file.length() >= size ){ + }else if( local_child.length() >= size ){ // log.f( "%s//%s :skip. same file size.",item.air_path, fname ); continue; } status.set( service.getString( R.string.download_file, child_air_path ) ); - final Uri file_uri = file.getUri(); final String get_url = flashair_url + Uri.encode( child_air_path ); data = client.getHTTP( log, network, get_url, new HTTPClientReceiver(){ final byte[] buf = new byte[ 2048 ]; public byte[] onHTTPClientStream( LogWriter log, CancelChecker cancel_checker, InputStream in, int content_length ){ try{ - OutputStream os = service.getContentResolver().openOutputStream( file_uri ); + OutputStream os = local_child.openOutputStream(service); if( os == null ){ log.e( "cannot open local output file." ); }else{ @@ -554,7 +458,7 @@ public byte[] onHTTPClientStream( LogWriter log, CancelChecker cancel_checker, I if( isCancelled() ){ // no log. }else if( data == null ){ - log.e( "FILE %s :HTTP error %s", fname, client.last_error ); + log.e( "FILE %s :HTTP error %s", file_name, client.last_error ); if( client.last_error.contains( "UnknownHostException" ) ){ client.last_error = service.getString( R.string.flashair_host_error ); @@ -564,13 +468,13 @@ public byte[] onHTTPClientStream( LogWriter log, CancelChecker cancel_checker, I has_error = true; }else{ - log.i( "FILE %s :download complete. %dms", fname, SystemClock.elapsedRealtime() - time_start ); + log.i( "FILE %s :download complete. %dms", file_name, SystemClock.elapsedRealtime() - time_start ); // 位置情報を取得する時にファイルの日時が使えないかと思ったけど // タイムゾーンがわからん… Location location = callback.getLocation(); - if( location != null && reJPEG.matcher( fname ).find() ){ + if( location != null && reJPEG.matcher( file_name ).find() ){ status.set( service.getString( R.string.exif_update, child_air_path ) ); updateFileLocation( location, local_child ); } @@ -593,16 +497,15 @@ public byte[] onHTTPClientStream( LogWriter log, CancelChecker cancel_checker, I private void updateFileLocation( final Location location, FilePathX file ){ try{ - FilePathX tmp_path = new FilePathX(); - tmp_path.parent = file.parent; - tmp_path.name = "tmp-" + currentThread().getId() + "-" + android.os.Process.myPid() + "-" +file.name; + FilePathX tmp_path = new FilePathX(file.parent,"tmp-" + currentThread().getId() + "-" + android.os.Process.myPid() + "-" + file.name); - DocumentFile tmp_file = tmp_path.prepareFile( log ); - if( tmp_file != null ){ + if(!tmp_path.prepareFile( log )){ + throw new RuntimeException( "create file failed." ); + }else{ boolean bModifyFailed = false; - OutputStream os = service.getContentResolver().openOutputStream( tmp_file.getUri() ); + OutputStream os = tmp_path.openOutputStream( service ); try{ - InputStream is = service.getContentResolver().openInputStream( file.document_file.getUri() ); + InputStream is = file.openInputStream(service); try{ ExifInterface.modifyExifTag( is, ExifInterface.Options.OPTION_ALL , os, new ExifInterface.ModifyExifTagCallback(){ @@ -639,27 +542,22 @@ private void updateFileLocation( final Location location, FilePathX file ){ } } - - if( bModifyFailed ){ - try{ - tmp_file.delete(); - }catch( Throwable ignored ){ + tmp_path.delete(); - } }else{ try{ // 更新後の方がファイルが小さいことがあるのか? - if(tmp_file.length() < file.document_file.length() ){ - log.e("EXIF付与したファイルの方が小さい!付与前後のファイルを残しておく"); + if( tmp_path.length() < file.length() ){ + log.e( "EXIF付与したファイルの方が小さい!付与前後のファイルを残しておく" ); // この場合両方のファイルを残しておく }else{ - if( ! file.document_file.delete() ){ + if( ! file.delete() ){ log.e( "EXIF追加後のファイル操作に失敗" ); - }else if( ! tmp_file.renameTo( file.name ) ){ + }else if( ! tmp_path.renameTo( file.name ) ){ log.e( "EXIF追加後のファイル操作に失敗" ); }else{ - log.i("%s に位置情報を付与しました",file.name ); + log.i( "%s に位置情報を付与しました", file.name ); } } @@ -676,4 +574,19 @@ private void updateFileLocation( final Location location, FilePathX file ){ } + @SuppressWarnings( "deprecation" ) + public static Object getWiFiNetwork( Context context ){ + ConnectivityManager cm = (ConnectivityManager) context.getSystemService( Context.CONNECTIVITY_SERVICE ); + if( Build.VERSION.SDK_INT >= 21 ){ + for( Network n : cm.getAllNetworks() ){ + NetworkInfo info = cm.getNetworkInfo( n ); + if( info.isConnected() && info.getType() == ConnectivityManager.TYPE_WIFI ) return n; + } + }else{ + for( NetworkInfo info : cm.getAllNetworkInfo() ){ + if( info.isConnected() && info.getType() == ConnectivityManager.TYPE_WIFI ) return info; + } + } + return null; + } } diff --git a/app/src/main/java/jp/juggler/fadownloader/FilePathX.java b/app/src/main/java/jp/juggler/fadownloader/FilePathX.java new file mode 100644 index 0000000..eb5ffd7 --- /dev/null +++ b/app/src/main/java/jp/juggler/fadownloader/FilePathX.java @@ -0,0 +1,193 @@ +package jp.juggler.fadownloader; + +import android.content.Context; +import android.net.Uri; +import android.os.Build; +import android.support.v4.provider.DocumentFile; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; + +public class FilePathX{ + + static final int DOCUMENT_FILE_VERSION = 21; + + private static Object bsearch( ArrayList local_files, String target_name ){ + int start = 0; + int end = local_files.size(); + while( ( end - start ) > 0 ){ + int mid = ( ( start + end ) >> 1 ); + Object x = local_files.get( mid ); + int i; + if( Build.VERSION.SDK_INT >= DOCUMENT_FILE_VERSION ){ + i = target_name.compareTo( ( (DocumentFile) x ).getName() ); + }else{ + i = target_name.compareTo( ( (File) x ).getName() ); + } + if( i < 0 ){ + end = mid; + }else if( i > 0 ){ + start = mid + 1; + }else{ + return x; + } + } + return null; + } + + + + FilePathX parent; + String name; + Object local_file; + ArrayList file_list; + + public FilePathX( Context context, String folder_uri ){ + if( Build.VERSION.SDK_INT >= DOCUMENT_FILE_VERSION ){ + local_file = DocumentFile.fromTreeUri( context, Uri.parse( folder_uri ) ); + }else{ + local_file = new File( folder_uri ); + } + } + + public FilePathX( FilePathX parent, String name ){ + this.parent = parent; + this.name = name; + } + + public ArrayList getFileList(){ + if( local_file != null ){ + if( file_list != null ) return file_list; + ArrayList result = new ArrayList<>(); + if( Build.VERSION.SDK_INT >= DOCUMENT_FILE_VERSION ){ + Collections.addAll( result, ( (DocumentFile) local_file ).listFiles() ); + Collections.sort( result, new Comparator(){ + @Override public int compare( Object a, Object b ){ + return ( (DocumentFile) a ).getName().compareTo( ( (DocumentFile) b ).getName() ); + } + } ); + }else{ + Collections.addAll( result, ( (File) local_file ).listFiles() ); + Collections.sort( result, new Comparator(){ + @Override public int compare( Object a, Object b ){ + return ( (File) a ).getName().compareTo( ( (File) b ).getName() ); + } + } ); + } + return file_list = result; + }else if( parent != null ){ + ArrayList parent_childs = parent.getFileList(); + if( parent_childs != null ){ + Object file = bsearch( parent_childs, name ); + if( file != null ){ + this.local_file = file; + return getFileList(); + } + } + } + return null; + } + + private Object prepareDirectory( LogWriter log ){ + try{ + if( local_file != null ) return local_file; + if( parent != null ){ + Object parent_dir = parent.prepareDirectory( log ); + if( parent_dir == null ) return null; + + ArrayList parent_list = parent.getFileList(); + Object file = bsearch( parent_list, name ); + if( file == null ){ + log.i( R.string.folder_create, name ); + if( Build.VERSION.SDK_INT >= DOCUMENT_FILE_VERSION ){ + file = ( (DocumentFile) parent_dir ).createDirectory( name ); + }else{ + file = new File( (File) parent_dir, name ); + if( ! ( (File) file ).mkdir() ){ + throw new RuntimeException( "mkdir failed." ); + } + } + } + return this.local_file = file; + } + }catch( Throwable ex ){ + log.e( R.string.folder_create_failed, ex.getClass().getSimpleName(), ex.getMessage() ); + } + return null; + } + + public boolean prepareFile( LogWriter log ){ + try{ + if( local_file != null ) return true; + if( parent != null ){ + Object parent_dir = parent.prepareDirectory( log ); + if( parent_dir == null ) return false; + + Object file = bsearch( parent.getFileList(), name ); + if( file == null ){ + if( Build.VERSION.SDK_INT >= DOCUMENT_FILE_VERSION ){ + file = ( (DocumentFile) parent_dir ).createFile( "application/octet-stream", name ); + }else{ + file = new File( (File) parent_dir, name ); + } + } + this.local_file = file; + return true; + } + }catch( Throwable ex ){ + log.e( R.string.file_create_failed, ex.getClass().getSimpleName(), ex.getMessage() ); + } + return false; + } + + public long length(){ + if( Build.VERSION.SDK_INT >= DOCUMENT_FILE_VERSION ){ + return ( (DocumentFile) local_file ).length(); + }else{ + return ( (File) local_file ).length(); + } + } + + public OutputStream openOutputStream( Context context ) throws FileNotFoundException{ + if( Build.VERSION.SDK_INT >= DOCUMENT_FILE_VERSION ){ + Uri file_uri = ( (DocumentFile) local_file ).getUri(); + return context.getContentResolver().openOutputStream( file_uri ); + }else{ + return new FileOutputStream( ( (File) local_file ) ); + } + } + + public InputStream openInputStream( Context context ) throws FileNotFoundException{ + if( Build.VERSION.SDK_INT >= DOCUMENT_FILE_VERSION ){ + Uri file_uri = ( (DocumentFile) local_file ).getUri(); + return context.getContentResolver().openInputStream( file_uri ); + }else{ + return new FileInputStream( ( (File) local_file ) ); + } + } + + public boolean delete(){ + if( Build.VERSION.SDK_INT >= DOCUMENT_FILE_VERSION ){ + return ( (DocumentFile) local_file ).delete(); + }else{ + return ( (File) local_file ).delete(); + } + } + + public boolean renameTo( String name ){ + if( Build.VERSION.SDK_INT >= DOCUMENT_FILE_VERSION ){ + return ( (DocumentFile) local_file ).renameTo( name ); + }else{ + return ( (File) local_file ).renameTo( + new File( ( (File) local_file ).getParentFile(), name ) + ); + } + } +} diff --git a/app/src/main/java/jp/juggler/fadownloader/FolderPicker.java b/app/src/main/java/jp/juggler/fadownloader/FolderPicker.java new file mode 100644 index 0000000..914fde1 --- /dev/null +++ b/app/src/main/java/jp/juggler/fadownloader/FolderPicker.java @@ -0,0 +1,154 @@ +package jp.juggler.fadownloader; + +import android.app.Activity; +import android.content.Intent; +import android.os.AsyncTask; +import android.os.Bundle; +import android.os.Environment; +import android.support.annotation.Nullable; +import android.support.v7.app.AppCompatActivity; +import android.text.TextUtils; +import android.view.View; +import android.widget.AdapterView; +import android.widget.ArrayAdapter; +import android.widget.Button; +import android.widget.ListView; +import android.widget.TextView; +import android.widget.Toast; + +import java.io.File; +import java.util.ArrayList; +import java.util.Collections; + +public class FolderPicker extends AppCompatActivity implements View.OnClickListener, AdapterView.OnItemClickListener{ + + static File parseExistPath( String path ){ + for( ; ; ){ + try{ + if( TextUtils.isEmpty( path ) ) path = Environment.getExternalStoragePublicDirectory( Environment.DIRECTORY_PICTURES ).getAbsolutePath(); + }catch( Throwable ignored ){ + } + try{ + if( TextUtils.isEmpty( path ) ) path = Environment.getExternalStorageDirectory().getAbsolutePath(); + }catch( Throwable ignored ){ + } + try{ + if( TextUtils.isEmpty( path ) ) path = "/"; + }catch( Throwable ignored ){ + } + @SuppressWarnings( "ConstantConditions" ) + File f = new File( path ); + if( f.isDirectory() ) return f; + path = null; + } + } + + public static final String EXTRA_FOLDER = "folder"; + + public static void open( Activity activity, int request_code, String path ){ + try{ + Intent intent = new Intent( activity, FolderPicker.class ); + intent.putExtra( FolderPicker.EXTRA_FOLDER, path ); + activity.startActivityForResult( intent, request_code ); + }catch( Throwable ex ){ + ex.printStackTrace(); + } + } + + TextView tvCurrentFolder; + View btnFolderUp; + ListView lvFileList; + Button btnSelectFolder; + File showing_folder; + ArrayAdapter list_adapter; + + @Override public void onClick( View v ){ + switch( v.getId() ){ + case R.id.btnFolderUp: + loadFolder( showing_folder.getParentFile() ); + break; + case R.id.btnSelectFolder: + Intent intent = new Intent(); + intent.putExtra( EXTRA_FOLDER, showing_folder.getAbsolutePath() ); + setResult( Activity.RESULT_OK ,intent); + finish(); + break; + } + } + + @Override public void onItemClick( AdapterView parent, View view, int position, long id ){ + File folder = new File( showing_folder, list_adapter.getItem( position ).toString() ); + if( ! folder.isDirectory() ){ + Toast.makeText( this, getString( R.string.folder_not_directory ), Toast.LENGTH_SHORT ).show(); + }else if( ! folder.canWrite() ){ + Toast.makeText( this, getString( R.string.folder_not_writable ), Toast.LENGTH_SHORT ).show(); + }else{ + loadFolder( folder ); + } + } + + @Override protected void onSaveInstanceState( Bundle outState ){ + super.onSaveInstanceState( outState ); + outState.putString( EXTRA_FOLDER, showing_folder.getAbsolutePath() ); + } + + @Override protected void onCreate( @Nullable Bundle savedInstanceState ){ + super.onCreate( savedInstanceState ); + setContentView( R.layout.folder_picker ); + + tvCurrentFolder = (TextView) findViewById( R.id.tvCurrentFolder ); + btnFolderUp = findViewById( R.id.btnFolderUp ); + lvFileList = (ListView) findViewById( R.id.lvFileList ); + btnSelectFolder = (Button) findViewById( R.id.btnSelectFolder ); + + btnFolderUp.setOnClickListener( this ); + btnSelectFolder.setOnClickListener( this ); + + list_adapter = new ArrayAdapter<>( this, android.R.layout.simple_list_item_1 ); + lvFileList.setAdapter( list_adapter ); + lvFileList.setOnItemClickListener( this ); + + if( savedInstanceState == null ){ + showing_folder = parseExistPath( getIntent().getStringExtra( EXTRA_FOLDER ) ); + }else{ + showing_folder = parseExistPath( savedInstanceState.getString( EXTRA_FOLDER ) ); + } + loadFolder( showing_folder ); + } + + private void loadFolder( final File folder ){ + tvCurrentFolder.setText( "(loading..)" ); + btnFolderUp.setEnabled( false ); + btnSelectFolder.setEnabled( false ); + list_adapter.clear(); + new AsyncTask>(){ + @Override protected ArrayList doInBackground( Void... params ){ + ArrayList result = new ArrayList<>(); + try{ + for( File sub : folder.listFiles() ){ + if( ! sub.isDirectory() ) continue; + String name = sub.getName(); + if( "..".equals( name ) ) continue; + if( ".".equals( name ) ) continue; + result.add( name ); + } + }catch( Throwable ex ){ + ex.printStackTrace(); + } + Collections.sort( result, String.CASE_INSENSITIVE_ORDER ); + return result; + } + + @Override protected void onPostExecute( ArrayList result ){ + if( result != null ){ + showing_folder = folder; + tvCurrentFolder.setText( folder.getAbsolutePath() ); + btnFolderUp.setEnabled( ! folder.getAbsolutePath().equals( "/" ) ); + btnSelectFolder.setText( getString( R.string.folder_select, folder.getAbsolutePath() ) ); + btnSelectFolder.setEnabled( true ); + list_adapter.addAll( result ); + } + } + }.execute(); + } +} diff --git a/app/src/main/java/jp/juggler/fadownloader/HTTPClient.java b/app/src/main/java/jp/juggler/fadownloader/HTTPClient.java index f35d51d..bb77e11 100644 --- a/app/src/main/java/jp/juggler/fadownloader/HTTPClient.java +++ b/app/src/main/java/jp/juggler/fadownloader/HTTPClient.java @@ -25,6 +25,7 @@ import org.w3c.dom.Element; import android.net.Network; +import android.os.Build; import android.os.SystemClock; //! リトライつきHTTPクライアント @@ -148,12 +149,12 @@ public synchronized void cancel( LogWriter log ){ // HTTPリクエスト処理 @SuppressWarnings( "unused" ) - public byte[] getHTTP( LogWriter log, Network network, String url ){ + public byte[] getHTTP( LogWriter log, Object network, String url ){ return getHTTP( log, network, url, default_receiver ); } @SuppressWarnings( "ConstantConditions" ) - public byte[] getHTTP( LogWriter log, Network network, String url, HTTPClientReceiver receiver ){ + public byte[] getHTTP( LogWriter log, Object network, String url, HTTPClientReceiver receiver ){ // // http://android-developers.blogspot.jp/2011/09/androids-http-clients.html // // HTTP connection reuse which was buggy pre-froyo @@ -190,12 +191,14 @@ public byte[] getHTTP( LogWriter log, Network network, String url, HTTPClientRec if( cancel_checker.isCancelled() ) return null; // http connection - HttpURLConnection conn; - if( network == null ){ - conn = (HttpURLConnection) urlObject.openConnection(); - }else{ - conn = (HttpURLConnection) network.openConnection( urlObject ); + HttpURLConnection conn = null; + if( Build.VERSION.SDK_INT >= 21 ){ + if( network instanceof Network ){ + conn = (HttpURLConnection) ((Network)network).openConnection( urlObject ); + } } + if( conn == null) conn = (HttpURLConnection) urlObject.openConnection(); + if( user_agent != null ) conn.setRequestProperty( "User-Agent", user_agent ); // 追加ヘッダがあれば記録する diff --git a/app/src/main/java/jp/juggler/fadownloader/Page0.java b/app/src/main/java/jp/juggler/fadownloader/Page0.java index e8538e6..16f8a47 100644 --- a/app/src/main/java/jp/juggler/fadownloader/Page0.java +++ b/app/src/main/java/jp/juggler/fadownloader/Page0.java @@ -4,6 +4,8 @@ import android.content.Intent; import android.content.SharedPreferences; import android.net.Uri; +import android.os.Build; +import android.os.Environment; import android.support.v4.provider.DocumentFile; import android.text.TextUtils; import android.view.View; @@ -153,8 +155,7 @@ void ui_value_load(){ boolean bv = pref.getBoolean( Pref.UI_FORCE_WIFI, false ); swForceWifi.setChecked( bv ); // - sv = pref.getString( Pref.UI_SSID, "" ); - if( sv != null ) etSSID.setText( sv ); + etSSID.setText( pref.getString( Pref.UI_SSID, "" ) ); updateFormEnabled(); } @@ -184,28 +185,34 @@ void ui_value_save(){ // 転送先フォルダの選択を開始 void folder_pick(){ - Intent intent = new Intent( Intent.ACTION_OPEN_DOCUMENT_TREE ); - activity.startActivityForResult( intent, ActMain.REQUEST_CODE_DOCUMENT ); + if( Build.VERSION.SDK_INT >= 21){ + Intent intent = new Intent( Intent.ACTION_OPEN_DOCUMENT_TREE ); + activity.startActivityForResult( intent, ActMain.REQUEST_CODE_DOCUMENT ); + }else{ + FolderPicker.open(activity,ActMain.REQUEST_FOLDER_PICKER,tvFolder.getText().toString()); + + } } // フォルダの表示を更新 void folder_view_update(){ - String folder_uri = null; - + String name = null; String sv = Pref.pref( activity ).getString( Pref.UI_FOLDER_URI, null ); - if( ! TextUtils.isEmpty( sv ) ){ - DocumentFile folder; - folder = DocumentFile.fromTreeUri( activity, Uri.parse( sv ) ); - if( folder != null ){ - if( folder.exists() && folder.canWrite() ){ - folder_uri = sv; - tvFolder.setText( folder.getName() ); + if( ! TextUtils.isEmpty( sv) ){ + if( Build.VERSION.SDK_INT >= 21){ + DocumentFile folder; + folder = DocumentFile.fromTreeUri( activity, Uri.parse( sv ) ); + if( folder != null ){ + if( folder.exists() && folder.canWrite() ){ + name = folder.getName(); + } } + }else{ + name = sv; } } - if( folder_uri == null ){ - tvFolder.setText( R.string.not_selected ); - } + tvFolder.setText( TextUtils.isEmpty( name ) + ? activity.getString(R.string.not_selected) : name ); } } diff --git a/app/src/main/java/jp/juggler/fadownloader/PermissionChecker.java b/app/src/main/java/jp/juggler/fadownloader/PermissionChecker.java index d8816f8..9337170 100644 --- a/app/src/main/java/jp/juggler/fadownloader/PermissionChecker.java +++ b/app/src/main/java/jp/juggler/fadownloader/PermissionChecker.java @@ -1,6 +1,7 @@ package jp.juggler.fadownloader; import android.Manifest; +import android.annotation.TargetApi; import android.content.Context; import android.content.pm.PackageManager; import android.os.Build; @@ -15,7 +16,7 @@ public class PermissionChecker{ Manifest.permission.ACCESS_WIFI_STATE, Manifest.permission.ACCESS_NETWORK_STATE, Manifest.permission.WRITE_EXTERNAL_STORAGE, - Manifest.permission.READ_EXTERNAL_STORAGE, + "android.permission.READ_EXTERNAL_STORAGE", Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION, }; diff --git a/app/src/main/java/jp/juggler/fadownloader/Utils.java b/app/src/main/java/jp/juggler/fadownloader/Utils.java index 35e798e..3d55c8e 100644 --- a/app/src/main/java/jp/juggler/fadownloader/Utils.java +++ b/app/src/main/java/jp/juggler/fadownloader/Utils.java @@ -1,9 +1,12 @@ package jp.juggler.fadownloader; import android.annotation.SuppressLint; +import android.annotation.TargetApi; import android.app.PendingIntent; +import android.content.ContentUris; import android.content.Context; import android.content.Intent; +import android.database.Cursor; import android.net.ConnectivityManager; import android.net.Network; import android.net.NetworkInfo; @@ -34,22 +37,19 @@ import org.xml.sax.InputSource; import org.xml.sax.SAXException; +import android.net.Uri; +import android.os.Build; import android.os.Bundle; +import android.os.Environment; +import android.provider.DocumentsContract; +import android.provider.MediaStore; import android.text.TextUtils; import android.util.Base64; +import android.util.Log; import android.util.SparseBooleanArray; public class Utils{ - public static Network getWiFiNetwork( Context context ){ - ConnectivityManager cm = (ConnectivityManager) context.getSystemService( Context.CONNECTIVITY_SERVICE ); - for( Network n : cm.getAllNetworks() ){ - NetworkInfo info = cm.getNetworkInfo( n ); - if( info.isConnected() && info.getType() == ConnectivityManager.TYPE_WIFI ) return n; - } - return null; - } - @SuppressLint( "DefaultLocale" ) public static String formatTimeDuration( long t ){ StringBuilder sb = new StringBuilder(); @@ -559,4 +559,5 @@ public static String ellipsize( String t, int max ){ // log.e("missing resid for %s",name); // return R.string.Dialog_Cancel; // } + } diff --git a/app/src/main/java/jp/juggler/fadownloader/WiFiTracker.java b/app/src/main/java/jp/juggler/fadownloader/WiFiTracker.java index cec4053..dc7f1ae 100644 --- a/app/src/main/java/jp/juggler/fadownloader/WiFiTracker.java +++ b/app/src/main/java/jp/juggler/fadownloader/WiFiTracker.java @@ -109,12 +109,15 @@ boolean keep_ap(){ WifiConfiguration target_config = null; boolean current_connection_found = false; - + int priority_max = 0; try{ // 設定済みのAPを列挙 for( WifiConfiguration wc : wifiManager.getConfiguredNetworks() ){ 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; @@ -151,16 +154,18 @@ boolean keep_ap(){ try{ // priority の変更 int p = target_config.priority; - 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 = p * 2; - wifiManager.updateNetwork( target_config ); - wifiManager.saveConfiguration(); - ////頻出するのでログ出さない log.d( R.string.wifi_ap_priority_changed ); + 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(); diff --git a/app/src/main/res/drawable/btn_borderless_material.xml b/app/src/main/res/drawable-v21/btn_help_bg.xml similarity index 100% rename from app/src/main/res/drawable/btn_borderless_material.xml rename to app/src/main/res/drawable-v21/btn_help_bg.xml diff --git a/app/src/main/res/drawable/btn_help_bg.xml b/app/src/main/res/drawable/btn_help_bg.xml new file mode 100644 index 0000000..0e24115 --- /dev/null +++ b/app/src/main/res/drawable/btn_help_bg.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/folder_picker.xml b/app/src/main/res/layout/folder_picker.xml new file mode 100644 index 0000000..7fbee9b --- /dev/null +++ b/app/src/main/res/layout/folder_picker.xml @@ -0,0 +1,30 @@ + + + +