diff --git a/.gitignore b/.gitignore index 2b90c1c2..ccc2a3bb 100644 --- a/.gitignore +++ b/.gitignore @@ -56,3 +56,5 @@ SMBSync2/src/main/java/com/sentaroh/android/SMBSync2/KeyStoreUtil2.java SMBSync2/src/main/java/com/sentaroh/android/SMBSync2/SyncTaskXml.java SMBSync2/src/main/java/com/sentaroh/android/SMBSync2/TwoWaySync.java SMBSync2/src/main/java/com/sentaroh/android/SMBSync2/TwoWaySyncFile.java + +tmpsrc/ \ No newline at end of file diff --git a/SMBSync2/build.gradle b/SMBSync2/build.gradle index df417e02..62fd5793 100644 --- a/SMBSync2/build.gradle +++ b/SMBSync2/build.gradle @@ -1,107 +1,87 @@ apply plugin: 'com.android.application' android { - compileSdkVersion 29 - buildToolsVersion '30.0.3' + compileSdkVersion 35 + namespace "com.sentaroh.android.SMBSync2" defaultConfig { applicationId "com.sentaroh.android.SMBSync2" - minSdkVersion 21 -// targetSdkVersion 21 //Android 5.0 -// targetSdkVersion 23 //Android 6.0 - targetSdkVersion 29 + minSdkVersion 26 + targetSdkVersion 35 // versionCode 287 versionName "2.55" compileOptions { - sourceCompatibility JavaVersion.VERSION_1_8 - targetCompatibility JavaVersion.VERSION_1_8 + sourceCompatibility JavaVersion.VERSION_17 + targetCompatibility JavaVersion.VERSION_17 } } + buildFeatures { + aidl true + buildConfig true + } + lintOptions { disable 'MissingTranslation' abortOnError false + checkReleaseBuilds false + } + + packagingOptions { + pickFirst '**/*.jar' } buildTypes { debug { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt' - - applicationVariants.all { variant -> - if (variant.buildType.name.equals("debug")) { - variant.outputs.all { - def versionName = variant.versionName - def versionNumber = variant.versionCode.toString() - def date = new java.text.SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date()) - def mod_name = "${rootProject.name}" - def fileExtension= outputFileName.toString().substring(outputFileName.toString().lastIndexOf(".")+1) - def newName = "${mod_name}_debug.${fileExtension}" -// outputFileName = "../../../../../release/"+newName - outputFileName = newName - - task copyDebugApk1(type: Copy) { - from 'build/outputs/apk/debug' -// into '../debug_apk/' // Into the project root, one level above the app folder - into '../release/' // Into the project root, one level above the app folder - include '**/*.apk' - } - - afterEvaluate { - packageDebug.finalizedBy(copyDebugApk1) - } - } - } - } - } release { - minifyEnabled false - proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt' + minifyEnabled true + shrinkResources true + signingConfig signingConfigs.debug + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.txt' + } + } - applicationVariants.all { variant -> - if (variant.buildType.name.equals("release")) { - variant.outputs.all { - def versionName = variant.versionName - def versionNumber = variant.versionCode.toString() - def date = new java.text.SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date()) - def mod_name = "${rootProject.name}" - def fileExtension= outputFileName.toString().substring(outputFileName.toString().lastIndexOf(".")+1) - def newName = "${mod_name}_${versionName}_${versionNumber}_release.${fileExtension}" - outputFileName = newName - } - } + applicationVariants.all { variant -> + if (variant.buildType.name.equals("debug")) { + variant.outputs.all { output -> + def mod_name = "${rootProject.name}" + def fileExtension = outputFileName.toString().substring(outputFileName.toString().lastIndexOf(".") + 1) + def newName = "${mod_name}_debug.${fileExtension}" + outputFileName = newName + } + } else if (variant.buildType.name.equals("release")) { + variant.outputs.all { output -> + def versionName = variant.versionName + def versionNumber = variant.versionCode.toString() + def mod_name = "${rootProject.name}" + def fileExtension = outputFileName.toString().substring(outputFileName.toString().lastIndexOf(".") + 1) + def newName = "${mod_name}_${versionName}_${versionNumber}_release.${fileExtension}" + outputFileName = newName } - } } } dependencies { - implementation files('libs/jcifs-ng-2.1.0-mod-08.jar') - implementation files('libs/jcifs-ng-2.1.1-20190202-03.jar') -// implementation files('libs/jcifs-ng-2.1.3-20190427-03.jar') - implementation files('libs/jcifs-ng-2.1.3-20190427-05.jar') - implementation files('libs/jcifs-ng-2.1.4-20200413-02.jar') - implementation files('libs/jcifs-1.3.17_patch.jar') -// implementation files('libs/JcifsFile-1.0.6.jar') - implementation files('libs/JcifsFile-1.0.9.jar') -// implementation files('libs/slf4j-api-1.7.7.jar') + implementation files('libs/jcifs-ng-2.1.11-SNAPSHOT.jar') + implementation files('libs/WrapperForSlf4j-1.0.2.jar') implementation files('libs/bcprov-jdk15on-158.jar') -// implementation files('libs/WrapperForSlf4j-1.0.2.jar') implementation files('libs/Utilities-1.0.19.aar') - implementation 'com.android.support:support-v4:28.0.0' - implementation 'com.android.support:appcompat-v7:28.0.0' - implementation 'com.android.support:preference-v7:28.0.0' + implementation 'androidx.legacy:legacy-support-v4:1.0.0' + implementation 'androidx.appcompat:appcompat:1.6.1' + implementation 'androidx.preference:preference:1.2.1' - implementation 'com.android.support:design:28.0.0' + implementation 'com.google.android.material:material:1.11.0' - implementation 'com.android.support:recyclerview-v7:28.0.0' + implementation 'androidx.recyclerview:recyclerview:1.3.2' implementation files('libs/metadata-extractor-2.11.0.jar') // implementation files('libs/xmpcore-5.1.3.jar') diff --git a/SMBSync2/libs/JcifsFile-1.0.9.jar b/SMBSync2/libs/JcifsFile-1.0.9.jar deleted file mode 100644 index 6e689f9d..00000000 Binary files a/SMBSync2/libs/JcifsFile-1.0.9.jar and /dev/null differ diff --git a/SMBSync2/libs/Utilities-1.0.19.aar b/SMBSync2/libs/Utilities-1.0.19.aar index 4f092672..6769ef31 100644 Binary files a/SMBSync2/libs/Utilities-1.0.19.aar and b/SMBSync2/libs/Utilities-1.0.19.aar differ diff --git a/SMBSync2/libs/jcifs-1.3.17_patch.jar b/SMBSync2/libs/jcifs-1.3.17_patch.jar deleted file mode 100644 index 882adad9..00000000 Binary files a/SMBSync2/libs/jcifs-1.3.17_patch.jar and /dev/null differ diff --git a/SMBSync2/libs/jcifs-ng-2.1.0-mod-08.jar b/SMBSync2/libs/jcifs-ng-2.1.0-mod-08.jar deleted file mode 100644 index 29b84cc9..00000000 Binary files a/SMBSync2/libs/jcifs-ng-2.1.0-mod-08.jar and /dev/null differ diff --git a/SMBSync2/libs/jcifs-ng-2.1.1-20190202-03.jar b/SMBSync2/libs/jcifs-ng-2.1.1-20190202-03.jar deleted file mode 100644 index f1d763e4..00000000 Binary files a/SMBSync2/libs/jcifs-ng-2.1.1-20190202-03.jar and /dev/null differ diff --git a/SMBSync2/libs/jcifs-ng-2.1.11-SNAPSHOT.jar b/SMBSync2/libs/jcifs-ng-2.1.11-SNAPSHOT.jar new file mode 100644 index 00000000..fbc443f2 Binary files /dev/null and b/SMBSync2/libs/jcifs-ng-2.1.11-SNAPSHOT.jar differ diff --git a/SMBSync2/libs/jcifs-ng-2.1.3-20190427-05.jar b/SMBSync2/libs/jcifs-ng-2.1.3-20190427-05.jar deleted file mode 100644 index f9c0c725..00000000 Binary files a/SMBSync2/libs/jcifs-ng-2.1.3-20190427-05.jar and /dev/null differ diff --git a/SMBSync2/libs/jcifs-ng-2.1.4-20200413-02.jar b/SMBSync2/libs/jcifs-ng-2.1.4-20200413-02.jar deleted file mode 100644 index 619c5616..00000000 Binary files a/SMBSync2/libs/jcifs-ng-2.1.4-20200413-02.jar and /dev/null differ diff --git a/SMBSync2/proguard-rules.txt b/SMBSync2/proguard-rules.txt new file mode 100644 index 00000000..c950232a --- /dev/null +++ b/SMBSync2/proguard-rules.txt @@ -0,0 +1,5 @@ +# Disable obfuscation per user request, but keep code shrinking and optimizations +-dontobfuscate +-dontwarn com.adobe.xmp.** +-dontwarn org.slf4j.** +-dontwarn org.patched_slf4j.** diff --git a/SMBSync2/src/main/AndroidManifest.xml b/SMBSync2/src/main/AndroidManifest.xml index bd93f749..7892584a 100644 --- a/SMBSync2/src/main/AndroidManifest.xml +++ b/SMBSync2/src/main/AndroidManifest.xml @@ -1,7 +1,6 @@ + xmlns:tools="http://schemas.android.com/tools"> @@ -16,10 +15,14 @@ + + + + @@ -77,6 +80,7 @@ android:configChanges="mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation|orientation|screenLayout|uiMode|screenSize|smallestScreenSize|layoutDirection|fontScale" android:screenOrientation="unspecified" android:alwaysRetainTaskState="true" + android:exported="true" > @@ -111,6 +115,7 @@ android:taskAffinity=".ActivityIntentHandler" android:configChanges="mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation|orientation|screenLayout|uiMode|screenSize|smallestScreenSize|layoutDirection|fontScale" android:screenOrientation="unspecified" + android:exported="true" > @@ -135,7 +140,9 @@ > - + diff --git a/SMBSync2/src/main/java/com/sentaroh/android/SMBSync2/ActivityMain.java b/SMBSync2/src/main/java/com/sentaroh/android/SMBSync2/ActivityMain.java index f49a2ce3..f9493f70 100644 --- a/SMBSync2/src/main/java/com/sentaroh/android/SMBSync2/ActivityMain.java +++ b/SMBSync2/src/main/java/com/sentaroh/android/SMBSync2/ActivityMain.java @@ -42,17 +42,18 @@ this software and associated documentation files (the "Software"), to deal import android.net.Uri; import android.os.Build; import android.os.Bundle; +import android.os.Environment; import android.os.Handler; import android.os.IBinder; import android.os.RemoteException; import android.os.StrictMode; import android.os.storage.StorageVolume; import android.provider.Settings; -import android.support.design.widget.TabLayout; -import android.support.v4.content.FileProvider; -import android.support.v4.view.ViewPager; -import android.support.v7.app.ActionBar; -import android.support.v7.app.AppCompatActivity; +import com.google.android.material.tabs.TabLayout; +import androidx.core.content.FileProvider; +import androidx.viewpager.widget.ViewPager; +import androidx.appcompat.app.ActionBar; +import androidx.appcompat.app.AppCompatActivity; import android.text.ClipboardManager; import android.text.Editable; import android.text.InputType; @@ -205,9 +206,13 @@ public void onCreate(Bundle savedInstanceState) { setTheme(mGp.applicationTheme); mGp.themeColorList = CommonUtilities.getThemeColorList(mActivity); // getWindow().setNavigationBarColor(mGp.themeColorList.window_background_color_content); + supportRequestWindowFeature(Window.FEATURE_NO_TITLE); super.onCreate(savedInstanceState); setContentView(R.layout.main_screen); + androidx.appcompat.widget.Toolbar toolbar = (androidx.appcompat.widget.Toolbar) findViewById(R.id.main_toolbar); + setSupportActionBar(toolbar); + mUtil = new CommonUtilities(mContext, "Main", mGp, getSupportFragmentManager()); mUtil.addDebugMsg(1, "I", CommonUtilities.getExecutedMethodName() + " entered, " + "mStartStatus=" + mStartStatus +", settingScreenThemeLanguage="+mGp.settingScreenThemeLanguage); mActionBar = getSupportActionBar(); @@ -376,7 +381,7 @@ public void positiveResponse(Context c, Object[] o) { if (mGp.syncTaskList.size()==0) mGp.syncTaskEmptyMessage.setVisibility(TextView.VISIBLE); else mGp.syncTaskEmptyMessage.setVisibility(TextView.GONE); if (mGp.syncThreadActive) { - mMainTabLayout.setCurrentTabByName(mTabNameMessage); + mMainTabLayout.getTabAt(3).select(); } else { mGp.messageListViewMoveToBottomRequired=true; } @@ -417,13 +422,13 @@ private void setMainListener() { if (mRestoreType == RESTART_BY_KILLED) { restoreTaskData(); mUtil.addLogMsg("W", mContext.getString(R.string.msgs_smbsync_main_restart_by_killed)); - mMainTabLayout.setCurrentTabByName(mTabNameMessage); + mMainTabLayout.getTabAt(3).select(); } else if (mRestoreType == RESTART_BY_DESTROYED) { restoreTaskData(); mUtil.addLogMsg("W", mContext.getString(R.string.msgs_smbsync_main_restart_by_destroyed)); - mMainTabLayout.setCurrentTabByName(mTabNameMessage); + mMainTabLayout.getTabAt(3).select(); } else { - if (mGp.syncThreadActive) mMainTabLayout.setCurrentTabByName(mTabNameMessage); + if (mGp.syncThreadActive) mMainTabLayout.getTabAt(3).select(); } checkStorageStatus(); setMessageContextButtonListener(); @@ -919,7 +924,7 @@ private ViewSaveArea saveViewContent() { private void restoreViewContent(final ViewSaveArea vsa) { mWhileRestoreViewProcess=true; - mMainTabLayout.setCurrentTabByPosition(vsa.current_tab_pos); + mMainTabLayout.getTabAt(vsa.current_tab_pos).select(); mMainViewPager.setCurrentItem(vsa.current_pager_pos); mWhileRestoreViewProcess=false; @@ -989,7 +994,7 @@ private void initAdapterAndView() { private LinearLayout mMessageView; private CustomViewPager mMainViewPager; - private CustomTabLayout mMainTabLayout; + private TabLayout mMainTabLayout; private boolean mWhileRestoreViewProcess=false; private void createTabView() { @@ -1101,13 +1106,12 @@ public boolean onLongClick(View v) { createContextView(); - mMainTabLayout = (CustomTabLayout) findViewById(R.id.main_tab_layout); - mMainTabLayout.addTab(mTabNameTask); - mMainTabLayout.addTab(mTabNameSchedule); - mMainTabLayout.addTab(mTabNameHistory); - mMainTabLayout.addTab(mTabNameMessage); + mMainTabLayout = (TabLayout) findViewById(R.id.main_tab_layout); + mMainTabLayout.addTab(mMainTabLayout.newTab().setText(mTabNameTask).setTag(mTabNameTask)); + mMainTabLayout.addTab(mMainTabLayout.newTab().setText(mTabNameSchedule).setTag(mTabNameSchedule)); + mMainTabLayout.addTab(mMainTabLayout.newTab().setText(mTabNameHistory).setTag(mTabNameHistory)); + mMainTabLayout.addTab(mMainTabLayout.newTab().setText(mTabNameMessage).setTag(mTabNameMessage)); mMainTabLayout.setTabMode(TabLayout.MODE_SCROLLABLE); - mMainTabLayout.adjustTabWidth(); View[] tab_view=new View[]{mSyncTaskView, mScheduleView, mHistoryView, mMessageView}; CustomViewPagerAdapter adapter = new CustomViewPagerAdapter(mActivity, tab_view); @@ -1118,7 +1122,7 @@ public boolean onLongClick(View v) { @Override public void onPageSelected(int position) { mUtil.addDebugMsg(2,"I","onPageSelected entered, pos="+position); - mMainTabLayout.setCurrentTabByPosition(position); + mMainTabLayout.getTabAt(position).select(); if (isUiEnabled()) setUiEnabled(); } @@ -1133,7 +1137,7 @@ public void onPageScrolled(int position, float positionOffset, int positionOffse } }); - mMainTabLayout.setCurrentTabByName(mTabNameTask); + mMainTabLayout.getTabAt(0).select(); mMainViewPager.setCurrentItem(0); mMainTabLayout.setOnTabSelectedListener(new TabLayout.OnTabSelectedListener() { @@ -1440,117 +1444,140 @@ private void setMenuItemEnabled(Menu menu, MenuItem menu_item, boolean enabled) @Override public boolean onOptionsItemSelected(MenuItem item) { - switch (item.getItemId()) { - case android.R.id.home: - processHomeButtonPress(); - return true; - case R.id.menu_top_sync: - if (isUiEnabled()) { - if (mGp.syncTaskAdapter.isShowCheckBox()) { - if (SyncTaskUtil.getSyncTaskSelectedItemCount(mGp.syncTaskAdapter) > 0) { - syncSelectedSyncTask(); - } else { - //no sync task is selected - mUtil.showCommonDialog(false, "W", mContext.getString(R.string.msgs_main_sync_select_prof_no_active_profile), "", null); - return true;//do not reset to normal view to let user select a task - } + int id = item.getItemId(); + if (id == android.R.id.home) { + processHomeButtonPress(); + return true; + } + if (id == R.id.menu_top_sync) { + if (isUiEnabled()) { + if (mGp.syncTaskAdapter.isShowCheckBox()) { + if (SyncTaskUtil.getSyncTaskSelectedItemCount(mGp.syncTaskAdapter) > 0) { + syncSelectedSyncTask(); } else { - syncAutoSyncTask(); + //no sync task is selected + mUtil.showCommonDialog(false, "W", mContext.getString(R.string.msgs_main_sync_select_prof_no_active_profile), "", null); + return true;//do not reset to normal view to let user select a task } - SyncTaskUtil.setAllSyncTaskToUnchecked(true, mGp.syncTaskAdapter); - setSyncTaskContextButtonNormalMode(); + } else { + syncAutoSyncTask(); } - return true; - case R.id.menu_top_exec_schedule: - if (isUiEnabled()) { - if (mGp.syncScheduleAdapter.isSelectMode()) { - if (mGp.syncScheduleAdapter.getSelectedItemCount() > 0) { - executeSelectedSchedule(); - } else { - //no schedule is selected - mUtil.showCommonDialog(false, "W", mContext.getString(R.string.msgs_schedule_sync_selected_schedule_not_found), "", null); - return true;//do not reset to normal view to let user select a schedule - } + SyncTaskUtil.setAllSyncTaskToUnchecked(true, mGp.syncTaskAdapter); + setSyncTaskContextButtonNormalMode(); + } + return true; + } + if (id == R.id.menu_top_exec_schedule) { + if (isUiEnabled()) { + if (mGp.syncScheduleAdapter.isSelectMode()) { + if (mGp.syncScheduleAdapter.getSelectedItemCount() > 0) { + executeSelectedSchedule(); } else { - executeAllEnabledSchedule(); + //no schedule is selected + mUtil.showCommonDialog(false, "W", mContext.getString(R.string.msgs_schedule_sync_selected_schedule_not_found), "", null); + return true;//do not reset to normal view to let user select a schedule } - SyncTaskUtil.setAllSyncTaskToUnchecked(true, mGp.syncTaskAdapter); - setScheduleContextButtonNormalMode(); + } else { + executeAllEnabledSchedule(); } - return true; - case R.id.menu_top_browse_log: - invokeLogFileBrowser(); - setContextButtonNormalMode(); - return true; - case R.id.menu_top_export: - NotifyEvent ntfy=new NotifyEvent(mContext); - ntfy.setListener(new NotifyEventListener() { - @Override - public void positiveResponse(Context context, Object[] objects) { - mTaskUtil.exportSyncTaskListDlg(); - setContextButtonNormalMode(); - } - @Override - public void negativeResponse(Context context, Object[] objects) {} - }); - ApplicationPasswordUtil.applicationPasswordAuthentication(mGp, mActivity, getSupportFragmentManager(), - mUtil, false, ntfy, ApplicationPasswordUtil.APPLICATION_PASSWORD_RESOURCE_EXPORT_TASK_LIST); - return true; - case R.id.menu_top_import: - importSyncTaskAndParms(); - setContextButtonNormalMode(); - return true; - case R.id.menu_top_log_management: - invokeLogManagement(); - setContextButtonNormalMode(); - return true; - case R.id.menu_top_scheduler: - toggleScheduleEnabled(); - return true; - case R.id.menu_top_about: - aboutSMBSync(); - setContextButtonNormalMode(); - return true; - case R.id.menu_top_settings: - invokeSettingsActivity(); - setContextButtonNormalMode(); - return true; - case R.id.menu_top_edit_force_usb_uuid_list: - EditUsbUuidList eu=new EditUsbUuidList(mActivity, mUtil); - return true; - case R.id.menu_top_kill: - killTerminateApplication(); - setContextButtonNormalMode(); - return true; - case R.id.menu_top_housekeep: - houseKeepManagementFile(); - return true; - case R.id.menu_top_add_shortcut: - addShortcut(); - return true; - case R.id.menu_top_show_battery_optimization: - showBatteryOptimization(); - return true; - case R.id.menu_top_list_storage: - showSystemInfo(); - return true; - case R.id.menu_top_select_storage: - reselectSdcard("", ""); - return true; -// case R.id.menu_top_request_grant_coarse_location: -// mGp.setSettingGrantCoarseLocationRequired(mContext, true); -// checkLocationPermission(false); -// return true; -// case R.id.menu_top_start_logcat: -// LogCatUtil.startLogCat(mGp, mGp.getLogDirName(),"logcat.txt"); -// return true; -// case R.id.menu_top_stop_logcat: -// LogCatUtil.stopLogCat(mGp, mUtil); -// return true; -// case R.id.menu_top_send_logcat: -// LogCatUtil.sendLogCat(mActivity, mGp, mUtil, mGp.getLogDirName(), "logcat.txt"); -// return true; + SyncTaskUtil.setAllSyncTaskToUnchecked(true, mGp.syncTaskAdapter); + setScheduleContextButtonNormalMode(); + } + return true; + } + if (id == R.id.menu_top_browse_log) { + invokeLogFileBrowser(); + setContextButtonNormalMode(); + return true; + } + if (id == R.id.menu_top_export) { + NotifyEvent ntfy = new NotifyEvent(mContext); + ntfy.setListener(new NotifyEventListener() { + @Override + public void positiveResponse(Context context, Object[] objects) { + mTaskUtil.exportSyncTaskListDlg(); + setContextButtonNormalMode(); + } + + @Override + public void negativeResponse(Context context, Object[] objects) { + } + }); + ApplicationPasswordUtil.applicationPasswordAuthentication(mGp, mActivity, getSupportFragmentManager(), + mUtil, false, ntfy, ApplicationPasswordUtil.APPLICATION_PASSWORD_RESOURCE_EXPORT_TASK_LIST); + return true; + } + if (id == R.id.menu_top_import) { + importSyncTaskAndParms(); + setContextButtonNormalMode(); + return true; + } + if (id == R.id.menu_top_log_management) { + invokeLogManagement(); + setContextButtonNormalMode(); + return true; + } + if (id == R.id.menu_top_scheduler) { + toggleScheduleEnabled(); + return true; + } + if (id == R.id.menu_top_about) { + aboutSMBSync(); + setContextButtonNormalMode(); + return true; + } + if (id == R.id.menu_top_settings) { + invokeSettingsActivity(); + setContextButtonNormalMode(); + return true; } + if (id == R.id.menu_top_edit_force_usb_uuid_list) { + EditUsbUuidList eu = new EditUsbUuidList(mActivity, mUtil); + return true; + } + if (id == R.id.menu_top_kill) { + killTerminateApplication(); + setContextButtonNormalMode(); + return true; + } + if (id == R.id.menu_top_housekeep) { + houseKeepManagementFile(); + return true; + } + if (id == R.id.menu_top_add_shortcut) { + addShortcut(); + return true; + } + if (id == R.id.menu_top_show_battery_optimization) { + showBatteryOptimization(); + return true; + } + if (id == R.id.menu_top_list_storage) { + showSystemInfo(); + return true; + } + if (id == R.id.menu_top_select_storage) { + reselectSdcard("", ""); + return true; + } + +// if (id == R.id.menu_top_request_grant_coarse_location) { +// mGp.setSettingGrantCoarseLocationRequired(mContext, true); +// checkLocationPermission(false); +// return true; +// } +// if (id == R.id.menu_top_start_logcat) { +// LogCatUtil.startLogCat(mGp, mGp.getLogDirName(),"logcat.txt"); +// return true; +// } +// if (id == R.id.menu_top_stop_logcat) { +// LogCatUtil.stopLogCat(mGp, mUtil); +// return true; +// } +// if (id == R.id.menu_top_send_logcat) { +// LogCatUtil.sendLogCat(mActivity, mGp, mUtil, mGp.getLogDirName(), "logcat.txt"); +// return true; +// } if (isUiEnabled()) { } return false; @@ -2018,12 +2045,10 @@ private void aboutSMBSync() { title.setText(getString(R.string.msgs_dlg_title_about) + " (Ver " + SystemInfo.getApplVersionName(mContext) + ")"); // get our tabHost from the xml - final CustomTabLayout tab_layout = (CustomTabLayout) dialog.findViewById(R.id.tab_layout); - tab_layout.addTab(mContext.getString(R.string.msgs_about_dlg_func_btn)); - tab_layout.addTab(mContext.getString(R.string.msgs_about_dlg_privacy_btn)); - tab_layout.addTab(mContext.getString(R.string.msgs_about_dlg_change_btn)); - - tab_layout.adjustTabWidth(); + final TabLayout tab_layout = (TabLayout) dialog.findViewById(R.id.tab_layout); + tab_layout.addTab(tab_layout.newTab().setText(mContext.getString(R.string.msgs_about_dlg_func_btn))); + tab_layout.addTab(tab_layout.newTab().setText(mContext.getString(R.string.msgs_about_dlg_privacy_btn))); + tab_layout.addTab(tab_layout.newTab().setText(mContext.getString(R.string.msgs_about_dlg_change_btn))); LayoutInflater vi = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE); @@ -2168,22 +2193,22 @@ public boolean onKey(View v, int keyCode, KeyEvent event){ } private void terminateApplication() { - if (mMainTabLayout.getSelectedTabName().equals(mTabNameTask)) {// + if (mMainTabLayout.getSelectedTabPosition() == 0) {// if (mGp.syncTaskAdapter.isShowCheckBox()) { mGp.syncTaskAdapter.setShowCheckBox(false); mGp.syncTaskAdapter.notifyDataSetChanged(); setSyncTaskContextButtonNormalMode(); return; } - } else if (mMainTabLayout.getSelectedTabName().equals(mTabNameSchedule)) { + } else if (mMainTabLayout.getSelectedTabPosition() == 1) { if (mGp.syncScheduleAdapter.isSelectMode()) { mGp.syncScheduleAdapter.setSelectMode(false); mGp.syncScheduleAdapter.notifyDataSetChanged(); setScheduleContextButtonNormalMode(); return; } - } else if (mMainTabLayout.getSelectedTabName().equals(mTabNameMessage)) { - } else if (mMainTabLayout.getSelectedTabName().equals(mTabNameHistory)) { + } else if (mMainTabLayout.getSelectedTabPosition() == 3) { + } else if (mMainTabLayout.getSelectedTabPosition() == 2) { if (mGp.syncHistoryAdapter.isShowCheckBox()) { mGp.syncHistoryAdapter.setShowCheckBox(false); mGp.syncHistoryAdapter.notifyDataSetChanged(); @@ -2388,7 +2413,7 @@ public void negativeResponse(Context c, Object[] o) { private NotifyEvent mNtfyExternalStoragePermission=null; private void checkRequiredPermissions(final NotifyEvent p_ntfy) { - if (Build.VERSION.SDK_INT >= 23) { + if (Build.VERSION.SDK_INT >= 23 && Build.VERSION.SDK_INT < 30) { mUtil.addDebugMsg(1, "I", "Prermission WriteExternalStorage=" + checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) + ", WakeLock=" + checkSelfPermission(Manifest.permission.WAKE_LOCK)); if (checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { @@ -2424,9 +2449,86 @@ public void negativeResponse(Context c, Object[] o) {} } else { p_ntfy.notifyToListener(true, null); } + } else if (Build.VERSION.SDK_INT >= 30) { + if (!Environment.isExternalStorageManager()) { + NotifyEvent ntfy = new NotifyEvent(mContext); + ntfy.setListener(new NotifyEventListener() { + @Override + public void positiveResponse(Context c, Object[] o) { + try { + Intent intent = new Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION); + intent.addCategory("android.intent.category.DEFAULT"); + intent.setData(Uri.parse(String.format("package:%s", getPackageName()))); + startActivity(intent); + } catch (Exception e) { + Intent intent = new Intent(); + intent.setAction(Settings.ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION); + startActivity(intent); + } + } + + @Override + public void negativeResponse(Context c, Object[] o) { + NotifyEvent ntfy_term = new NotifyEvent(mContext); + ntfy_term.setListener(new NotifyEventListener() { + @Override + public void positiveResponse(Context c, Object[] o) { + isTaskTermination = true; + finish(); + } + + @Override + public void negativeResponse(Context c, Object[] o) {} + }); + mUtil.showCommonDialog(false, "W", + mContext.getString(R.string.msgs_main_permission_all_files_access_title), + mContext.getString(R.string.msgs_main_permission_all_files_access_denied_msg), ntfy_term); + } + }); + mUtil.showCommonDialog(false, "W", + mContext.getString(R.string.msgs_main_permission_all_files_access_title), + mContext.getString(R.string.msgs_main_permission_all_files_access_request_msg), ntfy); + } else { + checkExactAlarmPermission(p_ntfy); + } } else { - p_ntfy.notifyToListener(true, null); + checkExactAlarmPermission(p_ntfy); + } + } + + private void checkExactAlarmPermission(final NotifyEvent p_ntfy) { + if (Build.VERSION.SDK_INT >= 31) { + android.app.AlarmManager am = (android.app.AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE); + if (am != null && !am.canScheduleExactAlarms()) { + NotifyEvent ntfy = new NotifyEvent(mContext); + ntfy.setListener(new NotifyEventListener() { + @Override + public void positiveResponse(Context c, Object[] o) { + try { + Intent intent = new Intent(android.provider.Settings.ACTION_REQUEST_SCHEDULE_EXACT_ALARM); + intent.setData(android.net.Uri.parse("package:" + mContext.getPackageName())); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + mContext.startActivity(intent); + } catch (Exception e) { + mUtil.addDebugMsg(1, "E", "Failed to launch exact alarm settings: " + e.getMessage()); + } + // Proceed regardless to not hard block the application on older devices + if (p_ntfy != null) p_ntfy.notifyToListener(true, null); + } + + @Override + public void negativeResponse(Context c, Object[] o) { + if (p_ntfy != null) p_ntfy.notifyToListener(true, null); + } + }); + mUtil.showCommonDialog(false, "W", + "Exact Alarms Permission Required", + "Android 14 requires explicit permission to set exact alarms for the scheduler to work reliably. Please click OK to open settings and enable 'Alarms & reminders' for SMBSync2.", + ntfy); + return; + } } + if (p_ntfy != null) p_ntfy.notifyToListener(true, null); } @Override @@ -2438,7 +2540,7 @@ public void onRequestPermissionsResult(int requestCode, String[] permissions, in mUiHandler.postDelayed(new Runnable() { @Override public void run() { - if (mNtfyExternalStoragePermission!=null) mNtfyExternalStoragePermission.notifyToListener(true, null); + if (mNtfyExternalStoragePermission != null) checkExactAlarmPermission(mNtfyExternalStoragePermission); } }, 500); } else { diff --git a/SMBSync2/src/main/java/com/sentaroh/android/SMBSync2/ActivityPasswordSettings.java b/SMBSync2/src/main/java/com/sentaroh/android/SMBSync2/ActivityPasswordSettings.java index 01526769..fdf0e920 100644 --- a/SMBSync2/src/main/java/com/sentaroh/android/SMBSync2/ActivityPasswordSettings.java +++ b/SMBSync2/src/main/java/com/sentaroh/android/SMBSync2/ActivityPasswordSettings.java @@ -27,8 +27,8 @@ this software and associated documentation files (the "Software"), to deal import android.content.SharedPreferences; import android.os.Bundle; import android.preference.PreferenceManager; -import android.support.v7.app.ActionBar; -import android.support.v7.app.AppCompatActivity; +import androidx.appcompat.app.ActionBar; +import androidx.appcompat.app.AppCompatActivity; import android.view.View; import android.view.Window; import android.widget.Button; diff --git a/SMBSync2/src/main/java/com/sentaroh/android/SMBSync2/ApplicationPasswordUtil.java b/SMBSync2/src/main/java/com/sentaroh/android/SMBSync2/ApplicationPasswordUtil.java index 622ad2d6..d4fd28de 100644 --- a/SMBSync2/src/main/java/com/sentaroh/android/SMBSync2/ApplicationPasswordUtil.java +++ b/SMBSync2/src/main/java/com/sentaroh/android/SMBSync2/ApplicationPasswordUtil.java @@ -29,7 +29,7 @@ this software and associated documentation files (the "Software"), to deal import android.content.SharedPreferences; import android.os.Build; import android.os.Handler; -import android.support.v4.app.FragmentManager; +import androidx.fragment.app.FragmentManager; import android.text.Editable; import android.text.TextWatcher; import android.view.View; diff --git a/SMBSync2/src/main/java/com/sentaroh/android/SMBSync2/CommonUtilities.java b/SMBSync2/src/main/java/com/sentaroh/android/SMBSync2/CommonUtilities.java index 1d38b891..0fc1f954 100644 --- a/SMBSync2/src/main/java/com/sentaroh/android/SMBSync2/CommonUtilities.java +++ b/SMBSync2/src/main/java/com/sentaroh/android/SMBSync2/CommonUtilities.java @@ -42,7 +42,7 @@ this software and associated documentation files (the "Software"), to deal import android.os.BatteryManager; import android.os.Build; import android.os.storage.StorageManager; -import android.support.v4.app.FragmentManager; +import androidx.fragment.app.FragmentManager; import android.util.Log; import android.util.TypedValue; import android.view.Gravity; diff --git a/SMBSync2/src/main/java/com/sentaroh/android/SMBSync2/CustomTabLayout.java b/SMBSync2/src/main/java/com/sentaroh/android/SMBSync2/CustomTabLayout.java index 8c0e94a6..dbcaab8b 100644 --- a/SMBSync2/src/main/java/com/sentaroh/android/SMBSync2/CustomTabLayout.java +++ b/SMBSync2/src/main/java/com/sentaroh/android/SMBSync2/CustomTabLayout.java @@ -23,9 +23,9 @@ this software and associated documentation files (the "Software"), to deal package com.sentaroh.android.SMBSync2; import android.content.Context; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import android.support.design.widget.TabLayout; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import com.google.android.material.tabs.TabLayout; import android.util.AttributeSet; import android.view.MotionEvent; import android.view.View; diff --git a/SMBSync2/src/main/java/com/sentaroh/android/SMBSync2/EditSyncTaskList.java b/SMBSync2/src/main/java/com/sentaroh/android/SMBSync2/EditSyncTaskList.java index cbe61edf..e595bbb0 100644 --- a/SMBSync2/src/main/java/com/sentaroh/android/SMBSync2/EditSyncTaskList.java +++ b/SMBSync2/src/main/java/com/sentaroh/android/SMBSync2/EditSyncTaskList.java @@ -4,11 +4,11 @@ import android.app.Dialog; import android.content.Context; import android.graphics.drawable.Drawable; -import android.support.annotation.NonNull; -import android.support.v7.widget.DividerItemDecoration; -import android.support.v7.widget.LinearLayoutManager; -import android.support.v7.widget.RecyclerView; -import android.support.v7.widget.helper.ItemTouchHelper; +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.DividerItemDecoration; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; +import androidx.recyclerview.widget.ItemTouchHelper; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; diff --git a/SMBSync2/src/main/java/com/sentaroh/android/SMBSync2/EditUsbUuidList.java b/SMBSync2/src/main/java/com/sentaroh/android/SMBSync2/EditUsbUuidList.java index 51697f8c..1d8ef0c2 100644 --- a/SMBSync2/src/main/java/com/sentaroh/android/SMBSync2/EditUsbUuidList.java +++ b/SMBSync2/src/main/java/com/sentaroh/android/SMBSync2/EditUsbUuidList.java @@ -6,7 +6,7 @@ import android.content.DialogInterface; import android.os.storage.StorageManager; import android.os.storage.StorageVolume; -import android.support.annotation.NonNull; +import androidx.annotation.NonNull; import android.text.Editable; import android.text.TextWatcher; import android.view.KeyEvent; diff --git a/SMBSync2/src/main/java/com/sentaroh/android/SMBSync2/GlobalParameters.java b/SMBSync2/src/main/java/com/sentaroh/android/SMBSync2/GlobalParameters.java index f0fde479..708dca7f 100644 --- a/SMBSync2/src/main/java/com/sentaroh/android/SMBSync2/GlobalParameters.java +++ b/SMBSync2/src/main/java/com/sentaroh/android/SMBSync2/GlobalParameters.java @@ -46,9 +46,9 @@ this software and associated documentation files (the "Software"), to deal import android.os.PowerManager; import android.os.PowerManager.WakeLock; import android.preference.PreferenceManager; -import android.support.annotation.RequiresApi; -import android.support.v4.app.NotificationCompat.BigTextStyle; -import android.support.v4.app.NotificationCompat.Builder; +import androidx.annotation.RequiresApi; +import androidx.core.app.NotificationCompat.BigTextStyle; +import androidx.core.app.NotificationCompat.Builder; import android.util.DisplayMetrics; import android.view.View.OnClickListener; import android.view.WindowManager; @@ -66,7 +66,7 @@ this software and associated documentation files (the "Software"), to deal import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.slf4j.LoggerWriter; +// import org.slf4j.LoggerWriter; import java.io.IOException; import java.io.OutputStream; @@ -79,7 +79,7 @@ this software and associated documentation files (the "Software"), to deal import java.util.Set; import java.util.concurrent.ArrayBlockingQueue; -import jcifs.util.LogStream; +// import jcifs.util.LogStream; import static android.content.Context.WINDOW_SERVICE; import static com.sentaroh.android.SMBSync2.Constants.APPLICATION_TAG; @@ -107,6 +107,8 @@ public class GlobalParameters extends CommonGlobalParms { public boolean externalStorageAccessIsPermitted = false; public String internalRootDirectory = "/"; public String applicationRootDirectory = "/"; + public String appSpecificMgtDir = ""; + public String profilePassword = ""; public final String profileKeyPrefix = "*SMBSync2*"; @@ -300,7 +302,7 @@ public class GlobalParameters extends CommonGlobalParms { public SafManager safMgr = null; - private static LogStream logStream=null;//JCIFS logStream +// private static LogStream logStream=null;//JCIFS logStream private static Logger slf4jLog = LoggerFactory.getLogger(GlobalParameters.class); public GlobalParameters() { @@ -352,11 +354,14 @@ synchronized public void initGlobalParamter(Context c) { try { String fp=c.getExternalFilesDirs(null)[0].getPath(); internalRootDirectory = fp.substring(0, fp.indexOf("/Android/data")); + appSpecificMgtDir = fp + "/" + APPLICATION_TAG; } catch(Exception ex) { internalRootDirectory = Environment.getExternalStorageDirectory().toString(); + appSpecificMgtDir = c.getExternalFilesDir(null).getPath() + "/" + APPLICATION_TAG; } applicationRootDirectory = c.getFilesDir().toString(); + java.security.Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider()); final LogUtil jcifs_ng_lu = new LogUtil(c, "SLF4J", this); final LogUtil jcifs_old_lu = new LogUtil(c, "JCIFS-V1", this); @@ -389,11 +394,11 @@ public void write(byte[] buff, int buff_offset, int buff_length) throws IOExcept } }; ps=new PrintStream(os); - LogStream.setInstance(ps); - logStream=LogStream.getInstance();//Initial create JCIFS logStream object +// LogStream.setInstance(ps); +// logStream=LogStream.getInstance();//Initial create JCIFS logStream object - JcifsNgLogWriter jcifs_ng_lw=new JcifsNgLogWriter(jcifs_ng_lu); - slf4jLog.setWriter(jcifs_ng_lw); +// JcifsNgLogWriter jcifs_ng_lw=new JcifsNgLogWriter(jcifs_ng_lu); +// slf4jLog.setWriter(jcifs_ng_lw); initStorageStatus(c); @@ -578,20 +583,22 @@ public void loadSettingsParms(Context c) { SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(c); settingDebugLevel = Integer.parseInt(prefs.getString(c.getString(R.string.settings_log_level), "0")); - slf4jLog.setAppendTime(false); +// slf4jLog.setAppendTime(false); +/* if (settingDebugLevel==0) { - LogStream.setLevel(1); +// LogStream.setLevel(1); slf4jLog.setLogOption(false, true, false, false, false); } else if (settingDebugLevel==1) { - LogStream.setLevel(2); +// LogStream.setLevel(2); slf4jLog.setLogOption(false, true, true, false, false); } else if (settingDebugLevel==2) { - LogStream.setLevel(5); +// LogStream.setLevel(5); slf4jLog.setLogOption(true, true, true, false, true); } else if (settingDebugLevel==3) { - LogStream.setLevel(5); +// LogStream.setLevel(5); slf4jLog.setLogOption(true, true, true, true, true); } +*/ settingExitClean=prefs.getBoolean(c.getString(R.string.settings_exit_clean), true); settingLogMaxFileCount = Integer.valueOf(prefs.getString(c.getString(R.string.settings_log_file_max_count), "5")); @@ -684,7 +691,11 @@ public void saveForceUsbUuidList(Context c) { public String getManagementDirectory() { - return internalRootDirectory + "/" + APPLICATION_TAG; + if (Build.VERSION.SDK_INT >= 30) { + return appSpecificMgtDir; + } else { + return internalRootDirectory + "/" + APPLICATION_TAG; + } } public void setScheduleEnabled(Context c, boolean enabled) { @@ -957,6 +968,7 @@ static public boolean isScreenOn(Context context, CommonUtilities util) { return pm.isInteractive(); } +/* class JcifsNgLogWriter extends LoggerWriter { private LogUtil mLu =null; public JcifsNgLogWriter(LogUtil lu) { @@ -967,5 +979,6 @@ public void write(String msg) { mLu.addDebugMsg(1,"I", msg); } } +*/ } diff --git a/SMBSync2/src/main/java/com/sentaroh/android/SMBSync2/Log/LogFileListDialogFragment.java b/SMBSync2/src/main/java/com/sentaroh/android/SMBSync2/Log/LogFileListDialogFragment.java index 5b913cb5..212c6312 100644 --- a/SMBSync2/src/main/java/com/sentaroh/android/SMBSync2/Log/LogFileListDialogFragment.java +++ b/SMBSync2/src/main/java/com/sentaroh/android/SMBSync2/Log/LogFileListDialogFragment.java @@ -29,7 +29,7 @@ this software and associated documentation files (the "Software"), to deal import android.net.Uri; import android.os.Build; import android.os.Bundle; -import android.support.v4.content.FileProvider; +import androidx.core.content.FileProvider; import com.sentaroh.android.SMBSync2.BuildConfig; import com.sentaroh.android.SMBSync2.R; diff --git a/SMBSync2/src/main/java/com/sentaroh/android/SMBSync2/NotificationUtil.java b/SMBSync2/src/main/java/com/sentaroh/android/SMBSync2/NotificationUtil.java index eee08f0f..d61b3e78 100644 --- a/SMBSync2/src/main/java/com/sentaroh/android/SMBSync2/NotificationUtil.java +++ b/SMBSync2/src/main/java/com/sentaroh/android/SMBSync2/NotificationUtil.java @@ -32,7 +32,7 @@ this software and associated documentation files (the "Software"), to deal import android.media.RingtoneManager; import android.net.Uri; import android.os.Build; -import android.support.v4.app.NotificationCompat; +import androidx.core.app.NotificationCompat; import com.sentaroh.android.SMBSync2.Log.LogUtil; @@ -67,7 +67,7 @@ static final public void initNotification(GlobalParameters gwa, CommonUtilities gwa.notificationIntent.addCategory(Intent.CATEGORY_LAUNCHER); gwa.notificationPendingIntent = PendingIntent.getActivity(c, 0, gwa.notificationIntent, - PendingIntent.FLAG_UPDATE_CURRENT); + PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE); // gwa.notificationLargeIcon = BitmapFactory.decodeResource(c.getResources(), gwa.notificationSmallIcon); gwa.notificationBuilder = new NotificationCompat.Builder(c); gwa.notificationBuilder.setContentIntent(gwa.notificationPendingIntent) @@ -271,7 +271,7 @@ final static public void showNoticeMsg(Context c, GlobalParameters gwa, CommonUt } if (gwa.callbackStub != null || (gwa.syncMessageList != null && gwa.syncMessageList.size() > 0)) { Intent activity_intent = new Intent(c, ActivityMain.class); - PendingIntent activity_pi = PendingIntent.getActivity(c, 0, activity_intent, PendingIntent.FLAG_UPDATE_CURRENT); + PendingIntent activity_pi = PendingIntent.getActivity(c, 0, activity_intent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE); builder.setContentIntent(activity_pi); } if (isNotificationEnabled(gwa)) { diff --git a/SMBSync2/src/main/java/com/sentaroh/android/SMBSync2/ScheduleItemEditor.java b/SMBSync2/src/main/java/com/sentaroh/android/SMBSync2/ScheduleItemEditor.java index ab40b524..9184cdc7 100644 --- a/SMBSync2/src/main/java/com/sentaroh/android/SMBSync2/ScheduleItemEditor.java +++ b/SMBSync2/src/main/java/com/sentaroh/android/SMBSync2/ScheduleItemEditor.java @@ -23,14 +23,18 @@ this software and associated documentation files (the "Software"), to deal */ +import android.app.AlarmManager; import android.app.Dialog; +import android.content.Intent; +import android.net.Uri; +import android.provider.Settings; import android.content.Context; import android.content.DialogInterface; import android.content.DialogInterface.OnCancelListener; import android.graphics.Color; import android.os.Build; import android.os.Handler; -import android.support.v7.app.AppCompatActivity; +import androidx.appcompat.app.AppCompatActivity; import android.text.Editable; import android.text.TextWatcher; import android.view.LayoutInflater; @@ -339,6 +343,33 @@ public void onClick(View v) { boolean isChecked = ctv_sched_enabled.isChecked(); ScheduleItem n_sli = mSched.clone(); if (isChecked) { + if (Build.VERSION.SDK_INT >= 31) { + AlarmManager am = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE); + if (am != null && !am.canScheduleExactAlarms()) { + ctv_sched_enabled.setChecked(false); + NotifyEvent ntfy = new NotifyEvent(mContext); + ntfy.setListener(new NotifyEventListener() { + @Override + public void positiveResponse(Context c, Object[] o) { + try { + Intent intent = new Intent(Settings.ACTION_REQUEST_SCHEDULE_EXACT_ALARM); + intent.setData(Uri.parse("package:" + mContext.getPackageName())); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + mContext.startActivity(intent); + } catch (Exception e) { + mUtil.addDebugMsg(1, "E", "Failed to launch exact alarm settings: " + e.getMessage()); + } + } + @Override + public void negativeResponse(Context c, Object[] o) {} + }); + mUtil.showCommonDialog(false, "W", + "Exact Alarms Permission Required", + "Android 14 requires explicit permission to set exact alarms for the scheduler to work reliably. Please click OK to open settings and enable 'Alarms & reminders' for SMBSync2.", + ntfy); + return; + } + } if (!mSched.scheduleEnabled) { n_sli.scheduleEnabled = true; n_sli.scheduleLastExecTime = System.currentTimeMillis(); diff --git a/SMBSync2/src/main/java/com/sentaroh/android/SMBSync2/ShortcutAutoSync.java b/SMBSync2/src/main/java/com/sentaroh/android/SMBSync2/ShortcutAutoSync.java index 0a6efe95..4c265ae8 100644 --- a/SMBSync2/src/main/java/com/sentaroh/android/SMBSync2/ShortcutAutoSync.java +++ b/SMBSync2/src/main/java/com/sentaroh/android/SMBSync2/ShortcutAutoSync.java @@ -30,7 +30,7 @@ this software and associated documentation files (the "Software"), to deal import android.os.Build; import android.os.Bundle; import android.os.Handler; -import android.support.v4.app.FragmentActivity; +import androidx.fragment.app.FragmentActivity; import android.view.Window; import com.sentaroh.android.Utilities.Dialog.MessageDialogAppFragment; diff --git a/SMBSync2/src/main/java/com/sentaroh/android/SMBSync2/SyncReceiver.java b/SMBSync2/src/main/java/com/sentaroh/android/SMBSync2/SyncReceiver.java index cfb613e7..755f2754 100644 --- a/SMBSync2/src/main/java/com/sentaroh/android/SMBSync2/SyncReceiver.java +++ b/SMBSync2/src/main/java/com/sentaroh/android/SMBSync2/SyncReceiver.java @@ -211,7 +211,7 @@ static private void setTimer() { in.setAction(SCHEDULER_INTENT_TIMER_EXPIRED); in.putExtra(SCHEDULER_SCHEDULE_NAME_KEY, sched_names); in.setClass(mContext, SyncReceiver.class); - PendingIntent pi = PendingIntent.getBroadcast(mContext, 0, in, PendingIntent.FLAG_UPDATE_CURRENT); + PendingIntent pi = PendingIntent.getBroadcast(mContext, 0, in, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE); AlarmManager am = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE); try { if (Build.VERSION.SDK_INT >= 23) am.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, time, pi); @@ -237,7 +237,7 @@ private static boolean isTimerScheduled() { Intent iw = new Intent(); iw.setAction(SCHEDULER_INTENT_TIMER_EXPIRED); iw.setClass(mContext, SyncReceiver.class); - PendingIntent pi = PendingIntent.getBroadcast(mContext, 0, iw, PendingIntent.FLAG_NO_CREATE); + PendingIntent pi = PendingIntent.getBroadcast(mContext, 0, iw, PendingIntent.FLAG_NO_CREATE | PendingIntent.FLAG_IMMUTABLE); if (pi == null) { return false; } else { @@ -250,7 +250,7 @@ static private void cancelTimer() { Intent in = new Intent(); in.setClass(mContext, SyncReceiver.class); in.setAction(SCHEDULER_INTENT_TIMER_EXPIRED); - PendingIntent pi = PendingIntent.getBroadcast(mContext, 0, in, PendingIntent.FLAG_CANCEL_CURRENT); + PendingIntent pi = PendingIntent.getBroadcast(mContext, 0, in, PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE); AlarmManager am = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE); am.cancel(pi); } diff --git a/SMBSync2/src/main/java/com/sentaroh/android/SMBSync2/SyncTaskEditor.java b/SMBSync2/src/main/java/com/sentaroh/android/SMBSync2/SyncTaskEditor.java index 080d643a..5d0fc207 100644 --- a/SMBSync2/src/main/java/com/sentaroh/android/SMBSync2/SyncTaskEditor.java +++ b/SMBSync2/src/main/java/com/sentaroh/android/SMBSync2/SyncTaskEditor.java @@ -37,11 +37,11 @@ this software and associated documentation files (the "Software"), to deal import android.os.storage.StorageManager; import android.os.storage.StorageVolume; import android.preference.PreferenceManager; -import android.support.design.widget.TextInputLayout; -import android.support.v4.app.DialogFragment; -import android.support.v4.app.Fragment; -import android.support.v4.app.FragmentManager; -import android.support.v4.app.FragmentTransaction; +import com.google.android.material.textfield.TextInputLayout; +import androidx.fragment.app.DialogFragment; +import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentManager; +import androidx.fragment.app.FragmentTransaction; import android.text.Editable; import android.text.TextWatcher; import android.text.method.HideReturnsTransformationMethod; diff --git a/SMBSync2/src/main/java/com/sentaroh/android/SMBSync2/SyncTaskUtil.java b/SMBSync2/src/main/java/com/sentaroh/android/SMBSync2/SyncTaskUtil.java index 2a21b2e9..e5b41e0c 100644 --- a/SMBSync2/src/main/java/com/sentaroh/android/SMBSync2/SyncTaskUtil.java +++ b/SMBSync2/src/main/java/com/sentaroh/android/SMBSync2/SyncTaskUtil.java @@ -36,7 +36,7 @@ this software and associated documentation files (the "Software"), to deal import android.os.Handler; import android.os.SystemClock; import android.preference.PreferenceManager; -import android.support.v4.app.FragmentManager; +import androidx.fragment.app.FragmentManager; import android.text.Editable; import android.text.TextUtils; import android.text.TextWatcher; diff --git a/SMBSync2/src/main/java/com/sentaroh/android/SMBSync2/SyncThreadSyncFile.java b/SMBSync2/src/main/java/com/sentaroh/android/SMBSync2/SyncThreadSyncFile.java index 85c74a1b..53bde5d5 100644 --- a/SMBSync2/src/main/java/com/sentaroh/android/SMBSync2/SyncThreadSyncFile.java +++ b/SMBSync2/src/main/java/com/sentaroh/android/SMBSync2/SyncThreadSyncFile.java @@ -23,6 +23,7 @@ this software and associated documentation files (the "Software"), to deal */ +import android.util.Log; import android.os.Build; import android.os.SystemClock; @@ -1324,11 +1325,13 @@ static private int moveCopyInternalToSmb(SyncThreadWorkArea stwa, SyncTaskItem s } } else { if (stwa.gp.settingDebugLevel >= 1) stwa.util.addDebugMsg(1, "I", "Directory was null, dir=" + mf.getPath()); + Log.e("SMBSync2", "listFiles() returned null for directory: " + mf.getPath()); } } else { stwa.totalIgnoreCount++; SyncThread.showMsg(stwa, true, sti.getSyncTaskName(), "W", "", "", stwa.context.getString(R.string.msgs_mirror_task_directory_ignored_because_can_not_read, from_path + "/" + mf.getName())); + Log.e("SMBSync2", "canRead() returned false for directory: " + mf.getPath()); } } } else { // file copy @@ -1435,6 +1438,8 @@ static private int moveCopyInternalToSmb(SyncThreadWorkArea stwa, SyncTaskItem s stwa.util.addLogMsg("E", stwa.context.getString(R.string.msgs_mirror_directory_with_same_name_as_the_file_found)+parsed_to_path); sync_result = SyncTaskItem.SYNC_STATUS_ERROR; } + } else { + Log.d("SMBSync2", "File skipped by filters: " + mf.getPath()); } } } else { diff --git a/SMBSync2/src/main/java/com/sentaroh/jcifs/JcifsAuth.java b/SMBSync2/src/main/java/com/sentaroh/jcifs/JcifsAuth.java new file mode 100644 index 00000000..1e98e60e --- /dev/null +++ b/SMBSync2/src/main/java/com/sentaroh/jcifs/JcifsAuth.java @@ -0,0 +1,82 @@ +package com.sentaroh.jcifs; + +import java.util.Properties; +import jcifs.CIFSContext; +import jcifs.context.BaseContext; +import jcifs.config.BaseConfiguration; +import jcifs.config.PropertyConfiguration; +import jcifs.smb.NtlmPasswordAuthenticator; + +public class JcifsAuth { + public static final int JCIFS_FILE_SMB1 = 1; + public static final int JCIFS_FILE_SMB201 = 2; + public static final int JCIFS_FILE_SMB211 = 3; + public static final int JCIFS_FILE_SMB212 = 4; + public static final int JCIFS_FILE_SMB214 = 5; + + private int smbLevel; + private String domain; + private String userName; + private String userPass; + private CIFSContext context; + + public JcifsAuth(int smbLevel, String domain, String userName, String userPass) { + this.smbLevel = smbLevel; + this.domain = domain; + this.userName = userName; + this.userPass = userPass; + initContext(); + } + + public JcifsAuth(int smbLevel, String domain, String userName, String userPass, boolean ipcSigningEnforced) { + this(smbLevel, domain, userName, userPass); + } + + public JcifsAuth(int smbLevel, String domain, String userName, String userPass, boolean ipcSigningEnforced, boolean useSmb2Negotiation) { + this(smbLevel, domain, userName, userPass); + } + + public JcifsAuth(int smbLevel, String domain, String userName, String userPass, boolean ipcSigningEnforced, boolean useSmb2Negotiation, Properties customProps) { + this(smbLevel, domain, userName, userPass); + } + + public JcifsAuth(int smbLevel, String domain, String userName, String userPass, boolean ipcSigningEnforced, String hostName, String ipAddress) { + this(smbLevel, domain, userName, userPass); + } + + private void initContext() { + try { + Properties props = new Properties(); + props.setProperty("jcifs.smb.client.enableSMB2", "true"); + props.setProperty("jcifs.smb.client.useSMB2Negotiation", "true"); + // Force SMB2/3 since SMB1 is dropped + props.setProperty("jcifs.smb.client.disableSMB1", "true"); + + PropertyConfiguration config = new PropertyConfiguration(props); + BaseContext baseCtx = new BaseContext(config); + NtlmPasswordAuthenticator auth = new NtlmPasswordAuthenticator(domain, userName, userPass); + this.context = baseCtx.withCredentials(auth); + } catch (Exception e) { + e.printStackTrace(); + } + } + + public int getSmbLevel() { return smbLevel; } + public boolean isSmb1() { return false; } // SMB1 is dropped + public boolean isSmb201() { return smbLevel == JCIFS_FILE_SMB201; } + public boolean isSmb211() { return smbLevel == JCIFS_FILE_SMB211; } + public boolean isSmb212() { return smbLevel == JCIFS_FILE_SMB212; } + public boolean isSmb214() { return smbLevel == JCIFS_FILE_SMB214; } + + public jcifs.smb.NtlmPasswordAuthentication getSmb1Auth() { return null; } + public CIFSContext getSmb201Auth() { return context; } + public CIFSContext getSmb211Auth() { return context; } + public CIFSContext getSmb212Auth() { return context; } + public CIFSContext getSmb214Auth() { return context; } + + public String getDomain() { return domain; } + public String getUserName() { return userName; } + public String getUserPass() { return userPass; } + + public CIFSContext getContext() { return context; } +} diff --git a/SMBSync2/src/main/java/com/sentaroh/jcifs/JcifsException.java b/SMBSync2/src/main/java/com/sentaroh/jcifs/JcifsException.java new file mode 100644 index 00000000..c89b0b2d --- /dev/null +++ b/SMBSync2/src/main/java/com/sentaroh/jcifs/JcifsException.java @@ -0,0 +1,28 @@ +package com.sentaroh.jcifs; + +public class JcifsException extends Exception { + private int ntStatus; + + public JcifsException(String message) { + super(message); + } + + public JcifsException(Throwable cause, int ntStatus, Throwable originalCause) { + super(cause); + this.ntStatus = ntStatus; + } + + public int getNtStatus() { + return ntStatus; + } + + @Override + public Throwable getCause() { + return super.getCause(); + } + + @Override + public String getMessage() { + return super.getMessage(); + } +} diff --git a/SMBSync2/src/main/java/com/sentaroh/jcifs/JcifsFile.java b/SMBSync2/src/main/java/com/sentaroh/jcifs/JcifsFile.java new file mode 100644 index 00000000..572ea999 --- /dev/null +++ b/SMBSync2/src/main/java/com/sentaroh/jcifs/JcifsFile.java @@ -0,0 +1,156 @@ +package com.sentaroh.jcifs; + +import java.io.InputStream; +import java.io.OutputStream; +import java.net.MalformedURLException; +import jcifs.smb.SmbFile; + +public class JcifsFile { + public static final int JCIFS_FILE_SMB1 = 1; + public static final int JCIFS_FILE_SMB201 = 2; + public static final int JCIFS_FILE_SMB211 = 3; + public static final int JCIFS_FILE_SMB212 = 4; + public static final int JCIFS_FILE_SMB214 = 5; + + private SmbFile delegate; + private JcifsAuth auth; + private String url; + + public JcifsFile(String url, JcifsAuth auth) throws MalformedURLException, JcifsException { + this.auth = auth; + this.url = url; + try { + this.delegate = new SmbFile(url, auth.getContext()); + } catch (MalformedURLException e) { + throw e; + } catch (Exception e) { + throw new JcifsException(e.getMessage()); + } + } + + private JcifsFile(SmbFile delegate, JcifsAuth auth) { + this.delegate = delegate; + this.auth = auth; + this.url = delegate.getURL().toString(); + } + + public boolean isSmb1File() { return false; } + public boolean isSmb201File() { return auth != null && auth.isSmb201(); } + public boolean isSmb211File() { return auth != null && auth.isSmb211(); } + public boolean isSmb212File() { return auth != null && auth.isSmb212(); } + public boolean isSmb214File() { return auth != null && auth.isSmb214(); } + + public jcifs.smb.SmbFile getSmb1File() { return null; } + public SmbFile getSmb201File() { return delegate; } + public SmbFile getSmb211File() { return delegate; } + public SmbFile getSmb212File() { return delegate; } + public SmbFile getSmb214File() { return delegate; } + + public boolean exists() throws JcifsException { + try { return delegate.exists(); } catch (Exception e) { throw new JcifsException(e.getMessage()); } + } + + public void delete() throws JcifsException { + try { delegate.delete(); } catch (Exception e) { throw new JcifsException(e.getMessage()); } + } + + public void mkdir() throws JcifsException { + try { delegate.mkdir(); } catch (Exception e) { throw new JcifsException(e.getMessage()); } + } + + public void mkdirs() throws JcifsException { + try { delegate.mkdirs(); } catch (Exception e) { throw new JcifsException(e.getMessage()); } + } + + public int getAttributes() throws JcifsException { + try { return delegate.getAttributes(); } catch (Exception e) { throw new JcifsException(e.getMessage()); } + } + + public InputStream getInputStream() throws JcifsException { + try { return delegate.getInputStream(); } catch (Exception e) { throw new JcifsException(e.getMessage()); } + } + + public OutputStream getOutputStream() throws JcifsException { + try { return delegate.getOutputStream(); } catch (Exception e) { throw new JcifsException(e.getMessage()); } + } + + public void close() throws JcifsException { + try { delegate.close(); } catch (Exception e) { throw new JcifsException(e.getMessage()); } + } + + public void connect() throws JcifsException { + try { delegate.connect(); } catch (Exception e) { throw new JcifsException(e.getMessage()); } + } + + public void createNew() throws JcifsException { + try { delegate.createNewFile(); } catch (Exception e) { throw new JcifsException(e.getMessage()); } + } + + public String getName() { return delegate.getName(); } + public String getPath() { return delegate.getPath(); } + public String getCanonicalPath() { return delegate.getCanonicalPath(); } + public String getShare() { return delegate.getShare(); } + + public int getType() throws JcifsException { + try { return delegate.getType(); } catch (Exception e) { throw new JcifsException(e.getMessage()); } + } + + public String getUncPath() { return delegate.getUncPath(); } + public String getParent() { return delegate.getParent(); } + + public boolean canRead() throws JcifsException { + try { return delegate.canRead(); } catch (Exception e) { throw new JcifsException(e.getMessage()); } + } + + public boolean canWrite() throws JcifsException { + try { return delegate.canWrite(); } catch (Exception e) { throw new JcifsException(e.getMessage()); } + } + + public boolean isDirectory() throws JcifsException { + try { return delegate.isDirectory(); } catch (Exception e) { throw new JcifsException(e.getMessage()); } + } + + public boolean isFile() throws JcifsException { + try { return delegate.isFile(); } catch (Exception e) { throw new JcifsException(e.getMessage()); } + } + + public boolean isHidden() throws JcifsException { + try { return delegate.isHidden(); } catch (Exception e) { throw new JcifsException(e.getMessage()); } + } + + public long length() throws JcifsException { + try { return delegate.length(); } catch (Exception e) { throw new JcifsException(e.getMessage()); } + } + + public String[] list() throws JcifsException { + try { return delegate.list(); } catch (Exception e) { throw new JcifsException(e.getMessage()); } + } + + public JcifsFile[] listFiles() throws JcifsException { + try { + SmbFile[] original = delegate.listFiles(); + if (original == null) return null; + JcifsFile[] result = new JcifsFile[original.length]; + for (int i = 0; i < original.length; i++) { + result[i] = new JcifsFile(original[i], auth); + } + return result; + } catch (Exception e) { + throw new JcifsException(e.getMessage()); + } + } + + public void renameTo(JcifsFile dest) throws JcifsException { + try { delegate.renameTo(dest.delegate); } catch (Exception e) { throw new JcifsException(e.getMessage()); } + } + + public JcifsAuth getAuth() { return auth; } + + public void setLastModified(long time) throws JcifsException { + try { delegate.setLastModified(time); } catch (Exception e) { throw new JcifsException(e.getMessage()); } + } + + public long getLastModified() throws JcifsException { + try { return delegate.getLastModified(); } catch (Exception e) { throw new JcifsException(e.getMessage()); } + } +} diff --git a/SMBSync2/src/main/java/com/sentaroh/jcifs/JcifsUtil.java b/SMBSync2/src/main/java/com/sentaroh/jcifs/JcifsUtil.java new file mode 100644 index 00000000..ad47a760 --- /dev/null +++ b/SMBSync2/src/main/java/com/sentaroh/jcifs/JcifsUtil.java @@ -0,0 +1,52 @@ +package com.sentaroh.jcifs; + +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.Socket; + +public class JcifsUtil { + + public static boolean isValidIpAddress(String in) { + if (in == null || in.isEmpty()) return false; + try { + return InetAddress.getByName(in).getHostAddress().equals(in); + } catch (Exception e) { + return false; + } + } + + public static String getSmbHostIpAddressByHostName(int smbLevel, String hostname) { + try { + return InetAddress.getByName(hostname).getHostAddress(); + } catch (Exception e) { + return null; + } + } + + public static boolean isIpAddressAndPortConnected(String addr, int port, int timeout) { + try (Socket socket = new Socket()) { + socket.connect(new InetSocketAddress(addr, port), timeout); + return true; + } catch (Exception e) { + return false; + } + } + + public static String getSmbHostNameByAddress(int smbLevel, String address) { + try { + return InetAddress.getByName(address).getHostName(); + } catch (Exception e) { + return address; + } + } + + public static boolean isNetbiosAddress(int smbLevel, String address) { + // NetBIOS is unsupported for SMB2/3 purely + return false; + } + + public static String[] analyzeNtStatusCode(JcifsException e, String url, String defaultMsg) { + // Return basic error strings + return new String[] { "Error", e.getMessage() != null ? e.getMessage() : defaultMsg }; + } +} diff --git a/SMBSync2/src/main/res/layout/about_dialog.xml b/SMBSync2/src/main/res/layout/about_dialog.xml index 26b256b2..5e34aedb 100644 --- a/SMBSync2/src/main/res/layout/about_dialog.xml +++ b/SMBSync2/src/main/res/layout/about_dialog.xml @@ -27,7 +27,7 @@ android:layout_width="fill_parent" android:layout_height="wrap_content" > - + + + + + + + + + + + + + + + + + +