diff --git a/config/quality.gradle b/config/quality.gradle index a76119bb..f90fe325 100644 --- a/config/quality.gradle +++ b/config/quality.gradle @@ -108,5 +108,5 @@ android { //------------------------ ALL ------------------------// task checkCode(type: GradleBuild) { - tasks = ['pmd', 'checkstyle', 'lint', 'findbugs'] + tasks = ['pmd', 'checkstyle', 'lint', 'findbugs', 'testDebugUnitTest'] } diff --git a/skunkworks_crow/build.gradle b/skunkworks_crow/build.gradle index f645190b..e7df0ffe 100644 --- a/skunkworks_crow/build.gradle +++ b/skunkworks_crow/build.gradle @@ -21,6 +21,11 @@ android { targetCompatibility 1.8 sourceCompatibility 1.8 } + testOptions { + unitTests { + includeAndroidResources = true + } + } } dependencies { @@ -32,6 +37,12 @@ dependencies { androidTestImplementation 'androidx.test:runner:1.1.1' androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.0-alpha4' + // testing-only + testImplementation "junit:junit:4.12" + testImplementation "org.mockito:mockito-core:2.11.0" + testImplementation "org.robolectric:robolectric:4.3" + testImplementation "org.robolectric:shadows-multidex:4.3" + // butterknife implementation "com.jakewharton:butterknife:${rootProject.butterknifeVersion}" annotationProcessor "com.jakewharton:butterknife-compiler:${rootProject.butterknifeVersion}" @@ -59,4 +70,8 @@ dependencies { //MPAndroidChart implementation 'com.github.PhilJay:MPAndroidChart:v3.0.3' + + // Permissions Dispatcher + implementation "org.permissionsdispatcher:permissionsdispatcher:4.5.0" + annotationProcessor "org.permissionsdispatcher:permissionsdispatcher-processor:4.5.0" } diff --git a/skunkworks_crow/src/main/AndroidManifest.xml b/skunkworks_crow/src/main/AndroidManifest.xml index dcf03f56..c54a1d05 100644 --- a/skunkworks_crow/src/main/AndroidManifest.xml +++ b/skunkworks_crow/src/main/AndroidManifest.xml @@ -17,6 +17,8 @@ + + - + + + - - + + android:name="android.support.PARENT_ACTIVITY" + android:value=".views.ui.main.MainActivity" /> - - - - - + + + + + android:name="android.support.PARENT_ACTIVITY" + android:value=".views.ui.main.MainActivity" /> - - + + android:name="android.support.PARENT_ACTIVITY" + android:value=".views.ui.main.MainActivity" /> - - + - + + android:name="android.support.PARENT_ACTIVITY" + android:value=".views.ui.main.MainActivity" /> - \ No newline at end of file diff --git a/skunkworks_crow/src/main/assets/open_source_licenses.html b/skunkworks_crow/src/main/assets/open_source_licenses.html index 646aafd4..f63ca2eb 100644 --- a/skunkworks_crow/src/main/assets/open_source_licenses.html +++ b/skunkworks_crow/src/main/assets/open_source_licenses.html @@ -28,6 +28,9 @@

Apache License Version 2.0, January 2004

    +
  • + PermissionsDispatcher +
  • Dagger 2
  • @@ -85,5 +88,24 @@

    Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. +

    + The MIT License (MIT) +

    +
      +
    • + Mockito +
    • +
    • + Robolectric +
    • +
    +
    +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
    +
    +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
    +
    + diff --git a/skunkworks_crow/src/main/java/org/odk/collect/android/dao/InstancesDao.java b/skunkworks_crow/src/main/java/org/odk/collect/android/dao/InstancesDao.java index 2248a2ed..e880aeaf 100644 --- a/skunkworks_crow/src/main/java/org/odk/collect/android/dao/InstancesDao.java +++ b/skunkworks_crow/src/main/java/org/odk/collect/android/dao/InstancesDao.java @@ -312,7 +312,6 @@ public List getInstancesFromCursor(Cursor cursor) { int jrVersionColumnIndex = cursor.getColumnIndex(InstanceProviderAPI.InstanceColumns.JR_VERSION); int statusColumnIndex = cursor.getColumnIndex(InstanceProviderAPI.InstanceColumns.STATUS); int lastStatusChangeDateColumnIndex = cursor.getColumnIndex(InstanceProviderAPI.InstanceColumns.LAST_STATUS_CHANGE_DATE); - int displaySubtextColumnIndex = cursor.getColumnIndex(InstanceProviderAPI.InstanceColumns.DISPLAY_SUBTEXT); int deletedDateColumnIndex = cursor.getColumnIndex(InstanceProviderAPI.InstanceColumns.DELETED_DATE); int databaseIdIndex = cursor.getColumnIndex(InstanceProviderAPI.InstanceColumns._ID); @@ -326,7 +325,6 @@ public List getInstancesFromCursor(Cursor cursor) { .jrVersion(cursor.getString(jrVersionColumnIndex)) .status(cursor.getString(statusColumnIndex)) .lastStatusChangeDate(cursor.getLong(lastStatusChangeDateColumnIndex)) - .displaySubtext(cursor.getString(displaySubtextColumnIndex)) .deletedDate(cursor.getLong(deletedDateColumnIndex)) .databaseId(cursor.getLong(databaseIdIndex)) .build(); @@ -353,7 +351,6 @@ public HashMap getMapFromCursor(Cursor cursor) { int jrVersionColumnIndex = cursor.getColumnIndex(InstanceProviderAPI.InstanceColumns.JR_VERSION); int statusColumnIndex = cursor.getColumnIndex(InstanceProviderAPI.InstanceColumns.STATUS); int lastStatusChangeDateColumnIndex = cursor.getColumnIndex(InstanceProviderAPI.InstanceColumns.LAST_STATUS_CHANGE_DATE); - int displaySubtextColumnIndex = cursor.getColumnIndex(InstanceProviderAPI.InstanceColumns.DISPLAY_SUBTEXT); int deletedDateColumnIndex = cursor.getColumnIndex(InstanceProviderAPI.InstanceColumns.DELETED_DATE); Instance instance = new Instance.Builder() @@ -365,7 +362,6 @@ public HashMap getMapFromCursor(Cursor cursor) { .jrVersion(cursor.getString(jrVersionColumnIndex)) .status(cursor.getString(statusColumnIndex)) .lastStatusChangeDate(cursor.getLong(lastStatusChangeDateColumnIndex)) - .displaySubtext(cursor.getString(displaySubtextColumnIndex)) .deletedDate(cursor.getLong(deletedDateColumnIndex)) .build(); @@ -381,7 +377,7 @@ public HashMap getMapFromCursor(Cursor cursor) { /** * Returns the values of an instance as a ContentValues object for use with * {@link #saveInstance(ContentValues)} or {@link #updateInstance(ContentValues, String, String[])} - *

    + * * Does NOT include the database ID. */ public ContentValues getValuesFromInstanceObject(Instance instance) { @@ -394,7 +390,6 @@ public ContentValues getValuesFromInstanceObject(Instance instance) { values.put(InstanceProviderAPI.InstanceColumns.JR_VERSION, instance.getJrVersion()); values.put(InstanceProviderAPI.InstanceColumns.STATUS, instance.getStatus()); values.put(InstanceProviderAPI.InstanceColumns.LAST_STATUS_CHANGE_DATE, instance.getLastStatusChangeDate()); - values.put(InstanceProviderAPI.InstanceColumns.DISPLAY_SUBTEXT, instance.getDisplaySubtext()); values.put(InstanceProviderAPI.InstanceColumns.DELETED_DATE, instance.getDeletedDate()); return values; diff --git a/skunkworks_crow/src/main/java/org/odk/collect/android/dto/Instance.java b/skunkworks_crow/src/main/java/org/odk/collect/android/dto/Instance.java index cd2483e2..786bbf58 100644 --- a/skunkworks_crow/src/main/java/org/odk/collect/android/dto/Instance.java +++ b/skunkworks_crow/src/main/java/org/odk/collect/android/dto/Instance.java @@ -16,7 +16,7 @@ package org.odk.collect.android.dto; -public class Instance { +public final class Instance { private final String displayName; private final String submissionUri; private final String canEditWhenComplete; @@ -25,7 +25,6 @@ public class Instance { private final String jrVersion; private final String status; private final Long lastStatusChangeDate; - private final String displaySubtext; private final Long deletedDate; private final Long databaseId; @@ -39,7 +38,6 @@ private Instance(Builder builder) { jrVersion = builder.jrVersion; status = builder.status; lastStatusChangeDate = builder.lastStatusChangeDate; - displaySubtext = builder.displaySubtext; deletedDate = builder.deletedDate; databaseId = builder.databaseId; @@ -54,7 +52,6 @@ public static class Builder { private String jrVersion; private String status; private Long lastStatusChangeDate; - private String displaySubtext; private Long deletedDate; private Long databaseId; @@ -99,11 +96,6 @@ public Builder lastStatusChangeDate(Long lastStatusChangeDate) { return this; } - public Builder displaySubtext(String displaySubtext) { - this.displaySubtext = displaySubtext; - return this; - } - public Builder deletedDate(Long deletedDate) { this.deletedDate = deletedDate; return this; @@ -151,10 +143,6 @@ public Long getLastStatusChangeDate() { return lastStatusChangeDate; } - public String getDisplaySubtext() { - return displaySubtext; - } - public Long getDeletedDate() { return deletedDate; } @@ -162,4 +150,15 @@ public Long getDeletedDate() { public Long getDatabaseId() { return databaseId; } -} + + @Override + public boolean equals(Object other) { + return other == this || other instanceof Instance + && this.instanceFilePath.equals(((Instance) other).instanceFilePath); + } + + @Override + public int hashCode() { + return instanceFilePath.hashCode(); + } +} \ No newline at end of file diff --git a/skunkworks_crow/src/main/java/org/odk/collect/android/provider/InstanceProviderAPI.java b/skunkworks_crow/src/main/java/org/odk/collect/android/provider/InstanceProviderAPI.java index bbe8fda0..b3b85a5b 100644 --- a/skunkworks_crow/src/main/java/org/odk/collect/android/provider/InstanceProviderAPI.java +++ b/skunkworks_crow/src/main/java/org/odk/collect/android/provider/InstanceProviderAPI.java @@ -19,9 +19,6 @@ import android.net.Uri; import android.provider.BaseColumns; -/** - * Convenience definitions for NotePadProvider - */ public final class InstanceProviderAPI { public static final String AUTHORITY = "org.odk.collect.android.provider.odk.instances"; @@ -35,9 +32,6 @@ private InstanceProviderAPI() { public static final String STATUS_SUBMITTED = "submitted"; public static final String STATUS_SUBMISSION_FAILED = "submissionFailed"; - /** - * Notes table - */ public static final class InstanceColumns implements BaseColumns { // This class cannot be instantiated private InstanceColumns() { @@ -47,26 +41,15 @@ private InstanceColumns() { public static final String CONTENT_TYPE = "vnd.android.cursor.dir/vnd.odk.instance"; public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/vnd.odk.instance"; - // These are the only things needed for an insert + // instance column names public static final String DISPLAY_NAME = "displayName"; public static final String SUBMISSION_URI = "submissionUri"; public static final String INSTANCE_FILE_PATH = "instanceFilePath"; public static final String JR_FORM_ID = "jrFormId"; public static final String JR_VERSION = "jrVersion"; - //public static final String FORM_ID = "formId"; - - // these are generated for you (but you can insert something else if you want) public static final String STATUS = "status"; public static final String CAN_EDIT_WHEN_COMPLETE = "canEditWhenComplete"; public static final String LAST_STATUS_CHANGE_DATE = "date"; - public static final String DISPLAY_SUBTEXT = "displaySubtext"; public static final String DELETED_DATE = "deletedDate"; - //public static final String DISPLAY_SUB_SUBTEXT = "displaySubSubtext"; - - // public static final String DEFAULT_SORT_ORDER = "modified DESC"; - // public static final String TITLE = "title"; - // public static final String NOTE = "note"; - // public static final String CREATED_DATE = "created"; - // public static final String MODIFIED_DATE = "modified"; } -} +} \ No newline at end of file diff --git a/skunkworks_crow/src/main/java/org/odk/share/adapters/basecursoradapter/ItemClickListener.java b/skunkworks_crow/src/main/java/org/odk/share/adapters/basecursoradapter/ItemClickListener.java deleted file mode 100644 index 4662188e..00000000 --- a/skunkworks_crow/src/main/java/org/odk/share/adapters/basecursoradapter/ItemClickListener.java +++ /dev/null @@ -1,6 +0,0 @@ -package org.odk.share.adapters.basecursoradapter; - -public interface ItemClickListener { - - void onItemClick(BaseCursorViewHolder holder, int position); -} diff --git a/skunkworks_crow/src/main/java/org/odk/share/application/Share.java b/skunkworks_crow/src/main/java/org/odk/share/application/Share.java index 3c206efd..2e8b478b 100644 --- a/skunkworks_crow/src/main/java/org/odk/share/application/Share.java +++ b/skunkworks_crow/src/main/java/org/odk/share/application/Share.java @@ -3,6 +3,8 @@ import android.content.Context; import android.os.Environment; +import androidx.annotation.IntDef; + import com.evernote.android.job.JobManager; import com.evernote.android.job.JobManagerCreateException; @@ -12,6 +14,8 @@ import org.odk.share.tasks.ShareJobCreator; import java.io.File; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import dagger.android.AndroidInjector; import dagger.android.DaggerApplication; @@ -23,6 +27,14 @@ public class Share extends DaggerApplication { + // define the different methods for data transferring. + @IntDef({TransferMethod.BLUETOOTH, TransferMethod.HOTSPOT}) + @Retention(RetentionPolicy.SOURCE) + public @interface TransferMethod { + int BLUETOOTH = 0x101; + int HOTSPOT = 0x110; + } + public static final String ODK_ROOT = Environment.getExternalStorageDirectory() + File.separator + "share"; public static final String ODK_COLLECT_ROOT = Environment.getExternalStorageDirectory() + File.separator + "odk"; public static final String FORMS_DIR_NAME = "forms"; diff --git a/skunkworks_crow/src/main/java/org/odk/share/bluetooth/BluetoothReceiver.java b/skunkworks_crow/src/main/java/org/odk/share/bluetooth/BluetoothReceiver.java new file mode 100644 index 00000000..956bdb13 --- /dev/null +++ b/skunkworks_crow/src/main/java/org/odk/share/bluetooth/BluetoothReceiver.java @@ -0,0 +1,76 @@ +package org.odk.share.bluetooth; + +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothDevice; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; + +import timber.log.Timber; + +/** + * @author huangyz0918 (huangyz0918@gmail.com) + * @since 04/06/2019 + */ +public class BluetoothReceiver extends BroadcastReceiver { + + private final BluetoothReceiverListener bluetoothReceiverListener; + + public BluetoothReceiver(Context context, BluetoothReceiverListener bluetoothReceiverListener) { + this.bluetoothReceiverListener = bluetoothReceiverListener; + IntentFilter filter = new IntentFilter(); + filter.addAction(BluetoothAdapter.ACTION_DISCOVERY_STARTED); //Bluetooth starts searching. + filter.addAction(BluetoothAdapter.ACTION_DISCOVERY_FINISHED); //Bluetooth search ends. + filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED); //Bluetooth state changed. + filter.addAction(BluetoothDevice.ACTION_FOUND); //Bluetooth discovers new devices (unpaired devices). + context.registerReceiver(this, filter); + } + + @Override + public void onReceive(Context context, Intent intent) { + String action = intent.getAction(); + if (action == null) { + return; + } + + switch (action) { + case BluetoothAdapter.ACTION_STATE_CHANGED: + final int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR); + bluetoothReceiverListener.onStateChanged(state); + break; + case BluetoothAdapter.ACTION_DISCOVERY_STARTED: + // if the discovery started, update the ui in activity. + Timber.d("bluetooth devices discovery started..."); + bluetoothReceiverListener.onDiscoveryStarted(); + break; + case BluetoothDevice.ACTION_FOUND: + // once the bluetooth device was found, update the ui. + BluetoothDevice bluetoothDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); + if (bluetoothDevice != null) { + short rssi = intent.getShortExtra(BluetoothDevice.EXTRA_RSSI, Short.MAX_VALUE); + Timber.d("EXTRA_RSSI: %s", rssi); + bluetoothReceiverListener.onDeviceFound(bluetoothDevice); + } + break; + case BluetoothAdapter.ACTION_DISCOVERY_FINISHED: + // if the discovery finished, update the ui in activity. + Timber.d("bluetooth devices discovery finished..."); + bluetoothReceiverListener.onDiscoveryFinished(); + break; + } + } + + /** + * Listener for bluetooth devices when we found a new {@link BluetoothDevice}. + */ + public interface BluetoothReceiverListener { + void onDeviceFound(BluetoothDevice device); + + void onDiscoveryStarted(); + + void onDiscoveryFinished(); + + void onStateChanged(int state); + } +} diff --git a/skunkworks_crow/src/main/java/org/odk/share/bluetooth/BluetoothUtils.java b/skunkworks_crow/src/main/java/org/odk/share/bluetooth/BluetoothUtils.java new file mode 100644 index 00000000..4a94d49e --- /dev/null +++ b/skunkworks_crow/src/main/java/org/odk/share/bluetooth/BluetoothUtils.java @@ -0,0 +1,94 @@ +package org.odk.share.bluetooth; + +import android.app.Activity; +import android.bluetooth.BluetoothAdapter; +import android.os.Build; + +import java.util.Random; +import java.util.UUID; + +/** + * Basic tools for bluetooth features. + * + * @author huangyz0918 (huangyz0918@gmail.com) + * @since 04/06/2019 + */ +public class BluetoothUtils { + + // UUID for security bluetooth pairing. + public static final UUID SPP_UUID = getRandomUUID(); + public static final int UUID_SEED = 0x110; + public static final int RANDOM_BOUND = 5; + + private BluetoothUtils() { + + } + + private static UUID getRandomUUID() { + return UUID.nameUUIDFromBytes(random().getBytes()); + } + + private static String random() { + Random generator = new Random(UUID_SEED); + StringBuilder randomStringBuilder = new StringBuilder(); + int randomLength = generator.nextInt(RANDOM_BOUND); + char tempChar; + for (int i = 0; i < randomLength; i++) { + tempChar = (char) (generator.nextInt(96) + 32); + randomStringBuilder.append(tempChar); + } + return randomStringBuilder.toString(); + } + + private static BluetoothAdapter getBluetoothAdapter() { + return BluetoothAdapter.getDefaultAdapter(); + } + + /** + * Checking if current device supports bluetooth. + * + * @return true if the device supports Bluetooth otherwise throws an {@link IllegalStateException}. + */ + public static boolean isBluetoothSupported() { + return getBluetoothAdapter() != null; + } + + /** + * Checking if bluetooth is enabled. + * + * @return true if the bluetooth enabled. + */ + public static boolean isBluetoothEnabled() { + return isBluetoothSupported() && getBluetoothAdapter().isEnabled(); + } + + /** + * Disable the bluetooth. + */ + public static void disableBluetooth() { + if (isBluetoothEnabled()) { + getBluetoothAdapter().disable(); + } + } + + /** + * Enable the bluetooth. + */ + public static void enableBluetooth() { + if (!isBluetoothEnabled()) { + getBluetoothAdapter().enable(); + } + } + + /** + * Checking if the current {@link android.app.Activity} is finished or about to be finished. + */ + public static boolean isActivityDestroyed(Activity activity) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { + return activity.isDestroyed(); + } else { + return activity.isChangingConfigurations() || activity.isFinishing(); + } + } + +} diff --git a/skunkworks_crow/src/main/java/org/odk/share/events/BluetoothEvent.java b/skunkworks_crow/src/main/java/org/odk/share/events/BluetoothEvent.java new file mode 100644 index 00000000..00a01c0c --- /dev/null +++ b/skunkworks_crow/src/main/java/org/odk/share/events/BluetoothEvent.java @@ -0,0 +1,23 @@ +package org.odk.share.events; + +public class BluetoothEvent extends RxEvent { + + private Status status; + + public BluetoothEvent(Status status) { + this.status = status; + } + + public Status getStatus() { + return status; + } + + public enum Status { + CONNECTED, DISCONNECTED + } + + @Override + public String toString() { + return getClass().getName() + " : " + getStatus(); + } +} diff --git a/skunkworks_crow/src/main/java/org/odk/share/events/DownloadEvent.java b/skunkworks_crow/src/main/java/org/odk/share/events/DownloadEvent.java index a422af9a..9bd8ad18 100644 --- a/skunkworks_crow/src/main/java/org/odk/share/events/DownloadEvent.java +++ b/skunkworks_crow/src/main/java/org/odk/share/events/DownloadEvent.java @@ -1,6 +1,6 @@ package org.odk.share.events; -public class DownloadEvent { +public class DownloadEvent extends RxEvent { private Status status; private String result; diff --git a/skunkworks_crow/src/main/java/org/odk/share/events/HotspotEvent.java b/skunkworks_crow/src/main/java/org/odk/share/events/HotspotEvent.java index f7a5196d..52b67c9e 100644 --- a/skunkworks_crow/src/main/java/org/odk/share/events/HotspotEvent.java +++ b/skunkworks_crow/src/main/java/org/odk/share/events/HotspotEvent.java @@ -1,6 +1,6 @@ package org.odk.share.events; -public class HotspotEvent { +public class HotspotEvent extends RxEvent { private Status status; diff --git a/skunkworks_crow/src/main/java/org/odk/share/events/RxEvent.java b/skunkworks_crow/src/main/java/org/odk/share/events/RxEvent.java new file mode 100644 index 00000000..e237a463 --- /dev/null +++ b/skunkworks_crow/src/main/java/org/odk/share/events/RxEvent.java @@ -0,0 +1,18 @@ +package org.odk.share.events; + +/** + * Base event that all RxEvent classes should extend. + * All classes being passed through the event bus should have names ending in RxEvent so that other + * developers know its purpose. + */ +public abstract class RxEvent { + private String description; + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } +} diff --git a/skunkworks_crow/src/main/java/org/odk/share/events/UploadEvent.java b/skunkworks_crow/src/main/java/org/odk/share/events/UploadEvent.java index bf93af44..8e9a3e3f 100644 --- a/skunkworks_crow/src/main/java/org/odk/share/events/UploadEvent.java +++ b/skunkworks_crow/src/main/java/org/odk/share/events/UploadEvent.java @@ -1,6 +1,6 @@ package org.odk.share.events; -public class UploadEvent { +public class UploadEvent extends RxEvent { private Status status; private String result; diff --git a/skunkworks_crow/src/main/java/org/odk/share/injection/ActivityBuilder.java b/skunkworks_crow/src/main/java/org/odk/share/injection/ActivityBuilder.java index a401d247..ba6604e4 100644 --- a/skunkworks_crow/src/main/java/org/odk/share/injection/ActivityBuilder.java +++ b/skunkworks_crow/src/main/java/org/odk/share/injection/ActivityBuilder.java @@ -1,12 +1,14 @@ package org.odk.share.injection; -import org.odk.share.activities.InstanceManagerTabs; -import org.odk.share.activities.InstancesList; -import org.odk.share.activities.MainActivity; -import org.odk.share.activities.ReviewFormActivity; -import org.odk.share.activities.SendActivity; -import org.odk.share.activities.SendFormsActivity; -import org.odk.share.activities.WifiActivity; +import org.odk.share.views.ui.bluetooth.BtReceiverActivity; +import org.odk.share.views.ui.bluetooth.BtSenderActivity; +import org.odk.share.views.ui.instance.InstanceManagerTabs; +import org.odk.share.views.ui.instance.InstancesList; +import org.odk.share.views.ui.main.MainActivity; +import org.odk.share.views.ui.hotspot.HpReceiverActivity; +import org.odk.share.views.ui.review.ReviewFormActivity; +import org.odk.share.views.ui.hotspot.HpSenderActivity; +import org.odk.share.views.ui.send.SendFormsActivity; import org.odk.share.injection.config.scopes.PerActivity; import dagger.Module; @@ -24,11 +26,11 @@ public abstract class ActivityBuilder { @PerActivity @ContributesAndroidInjector - abstract SendActivity provideSendActivity(); + abstract HpSenderActivity provideSendActivity(); @PerActivity @ContributesAndroidInjector - abstract WifiActivity provideWifiActivity(); + abstract HpReceiverActivity provideWifiActivity(); @PerActivity @ContributesAndroidInjector @@ -41,4 +43,12 @@ public abstract class ActivityBuilder { @PerActivity @ContributesAndroidInjector abstract SendFormsActivity provideSendFormsActivity(); + + @PerActivity + @ContributesAndroidInjector + abstract BtReceiverActivity provideBtReceiverActivity(); + + @PerActivity + @ContributesAndroidInjector + abstract BtSenderActivity provideBtSenderActivity(); } diff --git a/skunkworks_crow/src/main/java/org/odk/share/injection/FragmentBuilder.java b/skunkworks_crow/src/main/java/org/odk/share/injection/FragmentBuilder.java index 172891e7..8adce8a7 100644 --- a/skunkworks_crow/src/main/java/org/odk/share/injection/FragmentBuilder.java +++ b/skunkworks_crow/src/main/java/org/odk/share/injection/FragmentBuilder.java @@ -1,11 +1,11 @@ package org.odk.share.injection; -import org.odk.share.fragments.BlankFormsFragment; -import org.odk.share.fragments.FilledFormsFragment; -import org.odk.share.fragments.ReceivedInstancesFragment; -import org.odk.share.fragments.ReviewedInstancesFragment; -import org.odk.share.fragments.SentInstancesFragment; -import org.odk.share.fragments.StatisticsFragment; +import org.odk.share.views.ui.send.fragment.BlankFormsFragment; +import org.odk.share.views.ui.send.fragment.FilledFormsFragment; +import org.odk.share.views.ui.instance.fragment.ReceivedInstancesFragment; +import org.odk.share.views.ui.instance.fragment.ReviewedInstancesFragment; +import org.odk.share.views.ui.instance.fragment.SentInstancesFragment; +import org.odk.share.views.ui.instance.fragment.StatisticsFragment; import org.odk.share.injection.config.scopes.PerActivity; import dagger.Module; diff --git a/skunkworks_crow/src/main/java/org/odk/share/services/HotspotService.java b/skunkworks_crow/src/main/java/org/odk/share/services/HotspotService.java index 8321d0fd..e49100b4 100644 --- a/skunkworks_crow/src/main/java/org/odk/share/services/HotspotService.java +++ b/skunkworks_crow/src/main/java/org/odk/share/services/HotspotService.java @@ -32,7 +32,7 @@ import androidx.core.app.NotificationCompat; import org.odk.share.R; -import org.odk.share.activities.MainActivity; +import org.odk.share.views.ui.main.MainActivity; import org.odk.share.application.Share; import org.odk.share.events.HotspotEvent; import org.odk.share.network.WifiHospotConnector; diff --git a/skunkworks_crow/src/main/java/org/odk/share/services/ReceiverService.java b/skunkworks_crow/src/main/java/org/odk/share/services/ReceiverService.java index bcae7ad7..79c5bd9e 100644 --- a/skunkworks_crow/src/main/java/org/odk/share/services/ReceiverService.java +++ b/skunkworks_crow/src/main/java/org/odk/share/services/ReceiverService.java @@ -5,6 +5,7 @@ import com.evernote.android.job.JobRequest; import com.evernote.android.job.util.support.PersistableBundleCompat; +import org.odk.share.application.Share; import org.odk.share.events.DownloadEvent; import org.odk.share.rx.RxEventBus; import org.odk.share.rx.schedulers.BaseSchedulerProvider; @@ -36,7 +37,6 @@ private void addDownloadJobSubscription() { .observeOn(schedulerProvider.androidThread()) .doOnNext(downloadEvent -> { switch (downloadEvent.getStatus()) { - case CANCELLED: case ERROR: jobs.clear(); @@ -52,15 +52,24 @@ private void addDownloadJobSubscription() { break; } }).subscribe(); + } + public void startBtDownloading(String macAddress) { + PersistableBundleCompat extras = new PersistableBundleCompat(); + extras.putInt("MODE_OF_TRANSFER", Share.TransferMethod.BLUETOOTH); + extras.putString("mac", macAddress); + startJob(extras); } - public void startDownloading(String ip, int port) { + public void startHpDownloading(String ip, int port) { PersistableBundleCompat extras = new PersistableBundleCompat(); + extras.putInt("MODE_OF_TRANSFER", Share.TransferMethod.HOTSPOT); extras.putString(DownloadJob.IP, ip); extras.putInt(DownloadJob.PORT, port); + startJob(extras); + } - // Build request + private void startJob(PersistableBundleCompat extras) { JobRequest request = new JobRequest.Builder(DownloadJob.TAG) .addExtras(extras) .startNow() diff --git a/skunkworks_crow/src/main/java/org/odk/share/services/SenderService.java b/skunkworks_crow/src/main/java/org/odk/share/services/SenderService.java index 09dc2d6b..f37100bf 100644 --- a/skunkworks_crow/src/main/java/org/odk/share/services/SenderService.java +++ b/skunkworks_crow/src/main/java/org/odk/share/services/SenderService.java @@ -5,6 +5,7 @@ import com.evernote.android.job.JobRequest; import com.evernote.android.job.util.support.PersistableBundleCompat; +import org.odk.share.application.Share; import org.odk.share.events.UploadEvent; import org.odk.share.rx.RxEventBus; import org.odk.share.rx.schedulers.BaseSchedulerProvider; @@ -18,7 +19,7 @@ import timber.log.Timber; -import static org.odk.share.fragments.ReviewedInstancesFragment.MODE; +import static org.odk.share.views.ui.instance.fragment.ReviewedInstancesFragment.MODE; @Singleton public class SenderService { @@ -58,16 +59,35 @@ private void addUploadJobSubscription() { break; } }).subscribe(); - } public void startUploading(long[] instancesToSend, int port, int mode) { PersistableBundleCompat extras = new PersistableBundleCompat(); + extras.putInt("MODE_OF_TRANSFER", Share.TransferMethod.HOTSPOT); extras.putLongArray(UploadJob.INSTANCES, instancesToSend); extras.putInt(UploadJob.PORT, port); extras.putInt(MODE, mode); + startJob(extras); + } + + public void startUploading(long[] instancesToSend, int mode) { + PersistableBundleCompat extras = new PersistableBundleCompat(); + extras.putInt("MODE_OF_TRANSFER", Share.TransferMethod.BLUETOOTH); + extras.putLongArray(UploadJob.INSTANCES, instancesToSend); + extras.putInt(MODE, mode); + startJob(extras); + } - // Build request + /** + * start the uploading job or canceling the job. + */ + private void startJob(JobRequest request) { + request.schedule(); + Timber.d("Starting upload job %d : ", request.getJobId()); + currentJob = request; + } + + private void startJob(PersistableBundleCompat extras) { JobRequest request = new JobRequest.Builder(UploadJob.TAG) .addExtras(extras) .startNow() @@ -80,12 +100,6 @@ public void startUploading(long[] instancesToSend, int port, int mode) { } } - private void startJob(JobRequest request) { - request.schedule(); - Timber.d("Starting upload job %d : ", request.getJobId()); - currentJob = request; - } - public void cancel() { if (currentJob != null) { Job job = JobManager.instance().getJob(currentJob.getJobId()); diff --git a/skunkworks_crow/src/main/java/org/odk/share/tasks/DownloadJob.java b/skunkworks_crow/src/main/java/org/odk/share/tasks/DownloadJob.java index 7b6da9da..8a218059 100644 --- a/skunkworks_crow/src/main/java/org/odk/share/tasks/DownloadJob.java +++ b/skunkworks_crow/src/main/java/org/odk/share/tasks/DownloadJob.java @@ -1,10 +1,16 @@ package org.odk.share.tasks; +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothSocket; import android.content.ContentValues; import android.content.SharedPreferences; import android.database.Cursor; import android.net.Uri; import android.preference.PreferenceManager; + +import androidx.annotation.NonNull; + import com.evernote.android.job.Job; import org.odk.collect.android.dao.FormsDao; @@ -17,10 +23,11 @@ import org.odk.share.dao.TransferDao; import org.odk.share.database.ShareDatabaseHelper; import org.odk.share.dto.TransferInstance; +import org.odk.share.events.BluetoothEvent; import org.odk.share.events.DownloadEvent; -import org.odk.share.preferences.PreferenceKeys; import org.odk.share.rx.RxEventBus; import org.odk.share.utilities.ApplicationConstants; +import org.odk.share.views.ui.settings.PreferenceKeys; import java.io.BufferedInputStream; import java.io.DataInputStream; @@ -36,7 +43,6 @@ import javax.inject.Inject; -import androidx.annotation.NonNull; import timber.log.Timber; import static org.odk.collect.android.provider.InstanceProviderAPI.InstanceColumns.CAN_EDIT_WHEN_COMPLETE; @@ -48,6 +54,7 @@ import static org.odk.collect.android.provider.InstanceProviderAPI.InstanceColumns.SUBMISSION_URI; import static org.odk.share.application.Share.FORMS_DIR_NAME; import static org.odk.share.application.Share.INSTANCES_DIR_NAME; +import static org.odk.share.bluetooth.BluetoothUtils.SPP_UUID; import static org.odk.share.dto.InstanceMap.INSTANCE_UUID; import static org.odk.share.dto.TransferInstance.INSTANCE_ID; import static org.odk.share.dto.TransferInstance.INSTRUCTIONS; @@ -80,11 +87,15 @@ public class DownloadJob extends Job { private String ip; private int port; + private Socket socket; + private String targetMacAddress; + private BluetoothSocket bluetoothSocket; + private int total; private int progress; - private Socket socket; private DataInputStream dis; private DataOutputStream dos; + public static final String RESULT_DIVIDER = "---------------\n"; private StringBuilder sbResult; @@ -94,26 +105,68 @@ protected Result onRunJob(@NonNull Params params) { ((Share) getContext().getApplicationContext()).getAppComponent().inject(this); initJob(params); - rxEventBus.post(receiveForms()); return null; } private void initJob(Params params) { - ip = params.getExtras().getString(IP, ""); - port = params.getExtras().getInt(PORT, -1); sbResult = new StringBuilder(); + int method = params.getExtras().getInt("MODE_OF_TRANSFER", -1); + switch (method) { + case Share.TransferMethod.HOTSPOT: + ip = params.getExtras().getString(IP, ""); + port = params.getExtras().getInt(PORT, -1); + break; + case Share.TransferMethod.BLUETOOTH: + targetMacAddress = params.getExtras().getString("mac", null); + break; + } + + setupDataStreamsAndReceive(method); } - private DownloadEvent receiveForms() { - Timber.d("Socket " + ip + " " + port); + private void setupDataStreamsAndReceive(@Share.TransferMethod int method) { + try { + Timber.d("Waiting for sender"); + if (method == Share.TransferMethod.BLUETOOTH && targetMacAddress != null) { + BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); + BluetoothDevice bluetoothDevice = bluetoothAdapter.getRemoteDevice(targetMacAddress); + if (bluetoothDevice != null) { + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getContext().getApplicationContext()); + boolean isSecureMode = prefs.getBoolean(PreferenceKeys.KEY_BLUETOOTH_SECURE_MODE, true); + if (isSecureMode) { + bluetoothSocket = bluetoothDevice.createRfcommSocketToServiceRecord(SPP_UUID); + } else { + bluetoothSocket = bluetoothDevice.createInsecureRfcommSocketToServiceRecord(SPP_UUID); + } + + if (!bluetoothSocket.isConnected()) { + bluetoothSocket.connect(); + } + + rxEventBus.post(new BluetoothEvent(BluetoothEvent.Status.CONNECTED)); + + dos = new DataOutputStream(bluetoothSocket.getOutputStream()); + dis = new DataInputStream(bluetoothSocket.getInputStream()); + } + } else { + Timber.d("Socket %s, %s", ip, port); + socket = new Socket(); + socket.connect(new InetSocketAddress(ip, port), TIMEOUT); + Timber.d("Socket connected"); + dis = new DataInputStream(new BufferedInputStream(socket.getInputStream())); + dos = new DataOutputStream(socket.getOutputStream()); + } + + rxEventBus.post(receiveForms()); + } catch (IOException e) { + Timber.e(e); + cancel(); + } + } + private DownloadEvent receiveForms() { try { - socket = new Socket(); - socket.connect(new InetSocketAddress(ip, port), TIMEOUT); - Timber.d("Socket connected"); - dis = new DataInputStream(new BufferedInputStream(socket.getInputStream())); - dos = new DataOutputStream(socket.getOutputStream()); int mode = dis.readInt(); if (mode == SEND_FILL_FORM_MODE) { total = dis.readInt(); @@ -133,10 +186,7 @@ private DownloadEvent receiveForms() { } } - // close connection - socket.close(); - dos.close(); - dis.close(); + closeConnections(); } catch (IOException | IllegalArgumentException e) { Timber.e(e); @@ -146,18 +196,28 @@ private DownloadEvent receiveForms() { return new DownloadEvent(DownloadEvent.Status.FINISHED, sbResult.toString()); } + /** + * Close all the connections. + */ + private void closeConnections() throws IOException { + if (dos != null) { + dos.close(); + } + if (dis != null) { + dis.close(); + } + if (socket != null) { + socket.close(); + } + if (bluetoothSocket != null) { + bluetoothSocket.close(); + } + } + @Override protected void onCancel() { try { - if (socket != null) { - socket.close(); - } - if (dos != null) { - dos.close(); - } - if (dis != null) { - dis.close(); - } + closeConnections(); } catch (IOException e) { Timber.e(e); } @@ -166,6 +226,7 @@ protected void onCancel() { private boolean readFormAndInstances() { try { Timber.d("readFormAndInstances"); + String displayName = dis.readUTF(); String formId = dis.readUTF(); String formVersion = dis.readUTF(); Timber.d(formId + " " + formVersion); @@ -181,6 +242,10 @@ private boolean readFormAndInstances() { if (!formExists) { // read form readForm(); + } else { + setupResultFormInfo(displayName, formVersion, formId); + sbResult.append(getContext().getString(R.string.form_transfer_result, getContext().getString(R.string.msg_form_already_exist))); + sbResult.append(RESULT_DIVIDER); } // readInstances @@ -196,6 +261,7 @@ private void readBlankForm() { String formId = null; try { Timber.d("Reading blank form"); + String displayName = dis.readUTF(); formId = dis.readUTF(); String formVersion = dis.readUTF(); Timber.d(formId + " " + formVersion); @@ -211,6 +277,10 @@ private void readBlankForm() { if (!formExists) { // read form readForm(); + } else { + setupResultFormInfo(displayName, formVersion, formId); + sbResult.append(getContext().getString(R.string.form_transfer_result, getContext().getString(R.string.msg_form_already_exist))); + sbResult.append(RESULT_DIVIDER); } } catch (IOException e) { Timber.e(e); @@ -269,18 +339,25 @@ private void readForm() { values.put(FormsProviderAPI.FormsColumns.FORM_MEDIA_PATH, formMediaPath); formsDao.saveForm(values); - sbResult.append(displayName + " "); - if (formVersion != null) { - sbResult.append(getContext().getString(R.string.version, formVersion)); - } - sbResult.append(getContext().getString(R.string.id, formId) + " " + - getContext().getString(R.string.success, getContext().getString(R.string.blank_form_count, - getContext().getString(R.string.received)))); + setupResultFormInfo(displayName, formVersion, formId); + sbResult.append(getContext().getString(R.string.form_transfer_result, + getContext().getString(R.string.success, ", " + + getContext().getString(R.string.blank_form_count, + getContext().getString(R.string.received))))); + sbResult.append(RESULT_DIVIDER); } catch (IOException e) { Timber.e(e); } } + private void setupResultFormInfo(String displayName, String formVersion, String formId) { + sbResult.append(getContext().getString(R.string.form_name, displayName) + "\n"); + if (formVersion != null) { + sbResult.append(getContext().getString(R.string.version, formVersion) + "\n"); + } + sbResult.append(getContext().getString(R.string.id, formId) + "\n"); + } + private String getFormsPath() { return getOdkDestinationDir() + File.separator + FORMS_DIR_NAME; } @@ -325,8 +402,10 @@ private void readInstances(String formId, String formVersion) { // send acknowledgement that form is not needed here Timber.d("Form not sent from this device for review"); dos.writeBoolean(false); - sbResult.append(displayName + " " + getContext().getString(R.string.failed, - getContext().getString(R.string.not_sent_for_review))); + sbResult.append(getContext().getString(R.string.form_name, displayName) + "\n"); + sbResult.append(getContext().getString(R.string.form_transfer_result, + getContext().getString(R.string.failed, ", " + getContext().getString(R.string.not_sent_for_review)))); + sbResult.append(RESULT_DIVIDER); continue; } } @@ -383,8 +462,10 @@ private void readInstances(String formId, String formVersion) { ContentValues shareValues = new ContentValues(); shareValues.put(INSTANCE_ID, Long.parseLong(uri.getLastPathSegment())); shareValues.put(TRANSFER_STATUS, STATUS_FORM_RECEIVE); - sbResult.append(displayName + getContext().getString(R.string.success, - getContext().getString(R.string.received_for_review))); + sbResult.append(getContext().getString(R.string.form_name, displayName) + "\n"); + sbResult.append(getContext().getString(R.string.form_transfer_result, + getContext().getString(R.string.success, ", " + getContext().getString(R.string.received_for_review)))); + sbResult.append(RESULT_DIVIDER); new ShareDatabaseHelper(getContext()).insertInstance(shareValues); } else { String selection = InstanceProviderAPI.InstanceColumns._ID + "=?"; @@ -398,12 +479,18 @@ private void readInstances(String formId, String formVersion) { selection = TransferInstance.ID + " =?"; selectionArgs = new String[]{String.valueOf(transferInstance.getId())}; transferDao.updateInstance(shareValues, selection, selectionArgs); - sbResult.append(displayName + getContext().getString(R.string.success, getContext().getString(R.string.review_received))); + sbResult.append(getContext().getString(R.string.form_name, displayName) + "\n"); + sbResult.append(getContext().getString(R.string.form_transfer_result, + getContext().getString(R.string.success, ", " + getContext().getString(R.string.review_received)))); + sbResult.append(RESULT_DIVIDER); } else { Timber.d("Writing received not first time"); dos.writeBoolean(true); - sbResult.append(displayName + getContext().getString(R.string.success, getContext().getString(R.string.updated))); + sbResult.append(getContext().getString(R.string.form_name, displayName) + "\n"); + sbResult.append(getContext().getString(R.string.form_transfer_result, + getContext().getString(R.string.success, ", " + getContext().getString(R.string.updated)))); + sbResult.append(RESULT_DIVIDER); } } } diff --git a/skunkworks_crow/src/main/java/org/odk/share/tasks/UploadJob.java b/skunkworks_crow/src/main/java/org/odk/share/tasks/UploadJob.java index 56ffb08a..fb833c50 100644 --- a/skunkworks_crow/src/main/java/org/odk/share/tasks/UploadJob.java +++ b/skunkworks_crow/src/main/java/org/odk/share/tasks/UploadJob.java @@ -1,8 +1,13 @@ package org.odk.share.tasks; +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothServerSocket; +import android.bluetooth.BluetoothSocket; import android.content.ContentValues; import android.database.Cursor; +import androidx.annotation.NonNull; + import com.evernote.android.job.Job; import org.odk.collect.android.dao.FormsDao; @@ -15,6 +20,7 @@ import org.odk.share.dao.TransferDao; import org.odk.share.database.ShareDatabaseHelper; import org.odk.share.dto.TransferInstance; +import org.odk.share.events.BluetoothEvent; import org.odk.share.events.UploadEvent; import org.odk.share.rx.RxEventBus; import org.odk.share.utilities.ApplicationConstants; @@ -38,16 +44,17 @@ import javax.inject.Inject; -import androidx.annotation.NonNull; import timber.log.Timber; +import static org.odk.share.bluetooth.BluetoothUtils.SPP_UUID; import static org.odk.share.dto.InstanceMap.INSTANCE_UUID; import static org.odk.share.dto.TransferInstance.INSTANCE_ID; import static org.odk.share.dto.TransferInstance.STATUS_FORM_SENT; import static org.odk.share.dto.TransferInstance.TRANSFER_STATUS; -import static org.odk.share.fragments.ReviewedInstancesFragment.MODE; +import static org.odk.share.tasks.DownloadJob.RESULT_DIVIDER; import static org.odk.share.utilities.ApplicationConstants.SEND_BLANK_FORM_MODE; import static org.odk.share.utilities.ApplicationConstants.SEND_FILL_FORM_MODE; +import static org.odk.share.views.ui.instance.fragment.ReviewedInstancesFragment.MODE; public class UploadJob extends Job { @@ -76,6 +83,8 @@ public class UploadJob extends Job { private ServerSocket serverSocket; private DataOutputStream dos; private DataInputStream dis; + private BluetoothServerSocket bluetoothServerSocket; + private BluetoothSocket bluetoothSocket; private int progress; private int total; private int mode; @@ -89,36 +98,78 @@ protected Result onRunJob(@NonNull Params params) { initJob(params); - rxEventBus.post(uploadInstances()); - return null; } private void initJob(Params params) { - instancesToSend = ArrayUtils.toObject(params.getExtras().getLongArray(INSTANCES)); - port = params.getExtras().getInt(PORT, -1); - mode = params.getExtras().getInt(MODE, ApplicationConstants.ASK_REVIEW_MODE); sbResult = new StringBuilder(); + int method = params.getExtras().getInt("MODE_OF_TRANSFER", -1); + mode = params.getExtras().getInt(MODE, ApplicationConstants.ASK_REVIEW_MODE); + instancesToSend = ArrayUtils.toObject(params.getExtras().getLongArray(INSTANCES)); + if (method == Share.TransferMethod.HOTSPOT) { + port = params.getExtras().getInt(PORT, -1); + } + + setupDataStreamsAndRun(method); } - private UploadEvent uploadInstances() { + private void setupDataStreamsAndRun(@Share.TransferMethod int method) { try { Timber.d("Waiting for receiver"); + switch (method) { + case Share.TransferMethod.BLUETOOTH: + BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); + bluetoothServerSocket = bluetoothAdapter.listenUsingInsecureRfcommWithServiceRecord(TAG, SPP_UUID); + bluetoothSocket = bluetoothServerSocket.accept(); + + if (bluetoothSocket.isConnected()) { + rxEventBus.post(new BluetoothEvent(BluetoothEvent.Status.CONNECTED)); + } - serverSocket = new ServerSocket(port); - socket = serverSocket.accept(); - dos = new DataOutputStream(socket.getOutputStream()); - dis = new DataInputStream(new BufferedInputStream(socket.getInputStream())); + dos = new DataOutputStream(bluetoothSocket.getOutputStream()); + dis = new DataInputStream(bluetoothSocket.getInputStream()); + break; + case Share.TransferMethod.HOTSPOT: + serverSocket = new ServerSocket(port); + socket = serverSocket.accept(); + dos = new DataOutputStream(socket.getOutputStream()); + dis = new DataInputStream(new BufferedInputStream(socket.getInputStream())); + break; + } - // show dialog and connected - Timber.d("Start Sending"); - processSelectedFiles(instancesToSend); + rxEventBus.post(uploadInstances()); + } catch (IOException e) { + Timber.e(e); + } + } - // close connection - socket.close(); - serverSocket.close(); + private void closeConnections() throws IOException { + if (dos != null) { dos.close(); + } + if (dis != null) { dis.close(); + } + if (socket != null) { + socket.close(); + } + if (serverSocket != null) { + serverSocket.close(); + } + if (bluetoothSocket != null) { + bluetoothSocket.close(); + } + if (bluetoothServerSocket != null) { + bluetoothServerSocket.close(); + } + } + + private UploadEvent uploadInstances() { + try { + // show dialog and connected + Timber.d("Start Sending"); + processSelectedFiles(instancesToSend); + closeConnections(); } catch (IOException e) { Timber.e(e); return new UploadEvent(UploadEvent.Status.ERROR, e.getMessage()); @@ -130,18 +181,7 @@ private UploadEvent uploadInstances() { @Override protected void onCancel() { try { - if (socket != null) { - socket.close(); - } - if (serverSocket != null) { - serverSocket.close(); - } - if (dos != null) { - dos.close(); - } - if (dis != null) { - dis.close(); - } + closeConnections(); } catch (IOException e) { Timber.e(e); } @@ -294,6 +334,7 @@ private void sendBlankForm(Cursor cursor) { String formVersion = cursor.getString(cursor.getColumnIndex(FormsProviderAPI.FormsColumns.JR_VERSION)); try { + dos.writeUTF(displayName); dos.writeUTF(formId); if (formVersion == null) { dos.writeUTF("-1"); @@ -311,6 +352,12 @@ private void sendBlankForm(Cursor cursor) { boolean formExistAtReceiver = dis.readBoolean(); Timber.d("Form exists %b ", formExistAtReceiver); + sbResult.append(getContext().getString(R.string.form_name, displayName) + "\n"); + if (formVersion != null) { + sbResult.append(getContext().getString(R.string.version, formVersion) + "\n"); + } + sbResult.append(getContext().getString(R.string.id, formId) + "\n"); + if (!formExistAtReceiver) { Timber.d("Form sent to the receiver"); @@ -345,14 +392,14 @@ private void sendBlankForm(Cursor cursor) { dos.writeInt(0); } - sbResult.append(displayName + " "); - if (formVersion != null) { - sbResult.append(getContext().getString(R.string.version, formVersion)); - } - sbResult.append(getContext().getString(R.string.id, formId) + " " + - getContext().getString(R.string.success, getContext().getString(R.string.blank_form_count, - getContext().getString(R.string.sent)))); + sbResult.append(getContext().getString(R.string.form_transfer_result, + getContext().getString(R.string.success, ", " + + getContext().getString(R.string.blank_form_count, + getContext().getString(R.string.sent))))); + } else { + sbResult.append(getContext().getString(R.string.form_transfer_result, getContext().getString(R.string.msg_form_already_exist))); } + sbResult.append(RESULT_DIVIDER); } catch (IOException e) { Timber.e(e); } @@ -415,8 +462,8 @@ private void sendInstances(List instanceIds, int progress, int total) { boolean isFormSentForReview = dis.readBoolean(); Timber.d("isFormSentForReview " + isFormSentForReview); if (!isFormSentForReview) { - sbResult.append(displayName + getContext().getString(R.string.failed, - getContext().getString(R.string.review_not_asked))); + setupResultText(displayName, getContext().getString(R.string.failed, + ", " + getContext().getString(R.string.review_not_asked))); continue; } else { TransferInstance transferInstance = transferDao.getReceivedTransferInstanceFromInstanceId(id); @@ -438,8 +485,8 @@ private void sendInstances(List instanceIds, int progress, int total) { if (mode == ApplicationConstants.SEND_REVIEW_MODE) { // sent the review with the updated files - sbResult.append(displayName + getContext().getString(R.string.success, - getContext().getString(R.string.review_sent))); + setupResultText(displayName, getContext().getString(R.string.success, + ", " + getContext().getString(R.string.review_sent))); } else { // sent for review and update in transfer.db // check if it already exists at receiver end or not @@ -451,16 +498,16 @@ private void sendInstances(List instanceIds, int progress, int total) { boolean isFormAlreadySentForReview = dis.readBoolean(); Timber.d("isFormAlreadySentForReview " + isFormAlreadySentForReview); if (isFormAlreadySentForReview) { - sbResult.append(displayName + getContext().getString(R.string.success, - getContext().getString(R.string.sent_again))); + setupResultText(displayName, getContext().getString(R.string.success, + ", " + getContext().getString(R.string.sent_again))); } else { ContentValues values = new ContentValues(); values.put(INSTANCE_ID, c.getLong(c.getColumnIndex(InstanceProviderAPI.InstanceColumns._ID))); values.put(TRANSFER_STATUS, STATUS_FORM_SENT); new ShareDatabaseHelper(getContext()).insertInstance(values); - sbResult.append(displayName + getContext().getString(R.string.success, - getContext().getString(R.string.sent_for_review))); + setupResultText(displayName, getContext().getString(R.string.success, + ", " + getContext().getString(R.string.sent_for_review))); } } } @@ -474,6 +521,12 @@ private void sendInstances(List instanceIds, int progress, int total) { } } + private void setupResultText(String formName, String status) { + sbResult.append(getContext().getString(R.string.form_name, formName) + "\n"); + sbResult.append(getContext().getString(R.string.form_transfer_result, status)); + sbResult.append(RESULT_DIVIDER); + } + private void sendFile(String filePath) { byte[] bytes = new byte[4096]; try { diff --git a/skunkworks_crow/src/main/java/org/odk/share/utilities/ActivityUtils.java b/skunkworks_crow/src/main/java/org/odk/share/utilities/ActivityUtils.java new file mode 100644 index 00000000..5dfc75e7 --- /dev/null +++ b/skunkworks_crow/src/main/java/org/odk/share/utilities/ActivityUtils.java @@ -0,0 +1,48 @@ +package org.odk.share.utilities; + +import android.app.Activity; +import android.content.Context; +import android.content.ContextWrapper; +import android.content.Intent; + +/** + * @author huangyz0918 (huangyz0918@gmail.com) + */ +public class ActivityUtils { + + private ActivityUtils() { + } + + /** + * Getting the {@link Activity} from {@link Context}. + * + * @param context input context + */ + public static Activity getActivity(Context context) { + if (context == null) { + return null; + } else if (context instanceof ContextWrapper) { + if (context instanceof Activity) { + return (Activity) context; + } else { + return getActivity(((ContextWrapper) context).getBaseContext()); + } + } + + return null; + } + + /** + * launch an {@link Activity}. + */ + public static void launchActivity(Activity thisActivity, + Class targetActivity, + boolean shouldFinish) { + Intent intent = new Intent(thisActivity, targetActivity); + thisActivity.startActivity(intent); + + if (shouldFinish) { + thisActivity.finish(); + } + } +} diff --git a/skunkworks_crow/src/main/java/org/odk/share/utilities/DialogUtils.java b/skunkworks_crow/src/main/java/org/odk/share/utilities/DialogUtils.java new file mode 100644 index 00000000..e0b1d99c --- /dev/null +++ b/skunkworks_crow/src/main/java/org/odk/share/utilities/DialogUtils.java @@ -0,0 +1,106 @@ +package org.odk.share.utilities; + +import android.app.Activity; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.SharedPreferences; +import android.preference.PreferenceManager; + +import androidx.annotation.NonNull; +import androidx.appcompat.app.AlertDialog; + +import org.odk.share.R; +import org.odk.share.views.ui.bluetooth.BtReceiverActivity; +import org.odk.share.views.ui.bluetooth.BtSenderActivity; +import org.odk.share.views.ui.hotspot.HpReceiverActivity; +import org.odk.share.views.ui.hotspot.HpSenderActivity; +import org.odk.share.views.ui.settings.PreferenceKeys; + +/** + * @author huangyz0918 (huangyz0918@gmail.com) + * @since 12/07/2019 + */ +public class DialogUtils { + + private DialogUtils() { + } + + /** + * Create a simple {@link AlertDialog} for showing messages, after that, we finish the {@link Activity}. + */ + public static AlertDialog createSimpleDialog(@NonNull Context context, + @NonNull String title, + @NonNull String message) { + AlertDialog alertDialog = new AlertDialog.Builder(context).create(); + alertDialog.setTitle(title); + alertDialog.setMessage(message); + alertDialog.setCancelable(false); + alertDialog.setButton(DialogInterface.BUTTON_POSITIVE, context.getString(R.string.ok), (dialog, i) -> ((Activity) context).finish()); + return alertDialog; + } + + /** + * Create an {@link AlertDialog} for switching receive method. + */ + public static AlertDialog createMethodSwitchDialog(@NonNull Activity targetActivity, DialogInterface.OnClickListener listener) { + AlertDialog alertDialog = new AlertDialog.Builder(targetActivity).create(); + alertDialog.setCancelable(false); + alertDialog.setButton(DialogInterface.BUTTON_POSITIVE, targetActivity.getString(R.string.cancel), + (dialog, i) -> { + dialog.dismiss(); + }); + + alertDialog.setButton(DialogInterface.BUTTON_NEGATIVE, targetActivity.getString(R.string.switch_method), listener); + + //the icons set in the dialog are for destinations. + if (targetActivity instanceof BtReceiverActivity || targetActivity instanceof BtSenderActivity) { + alertDialog.setIcon(R.drawable.ic_wifi_tethering_black_24dp); + alertDialog.setTitle(targetActivity.getString(R.string.switch_to_hotspot_title)); + alertDialog.setMessage(targetActivity.getString(R.string.hotspot_switch_method)); + } else if (targetActivity instanceof HpReceiverActivity || targetActivity instanceof HpSenderActivity) { + alertDialog.setIcon(R.drawable.ic_bluetooth_black_24dp); + alertDialog.setTitle(targetActivity.getString(R.string.switch_to_bluetooth_title)); + alertDialog.setMessage(targetActivity.getString(R.string.bluetooth_switch_method)); + } else { + throw new IllegalArgumentException("Not a valid activity parameter"); + } + + return alertDialog; + } + + /** + * Detecting the default set by user, and using that as main sending method. + */ + public static void switchToDefaultSendingMethod(@NonNull Context context, @NonNull Intent intent) { + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context.getApplicationContext()); + String defaultMethod = prefs.getString(PreferenceKeys.KEY_DEFAULT_TRANSFER_METHOD, context.getString(R.string.default_hotspot_ssid)); + if (context.getString(R.string.default_hotspot_ssid).equals(defaultMethod)) { + intent.setClass(ActivityUtils.getActivity(context), HpSenderActivity.class); + } else if (context.getString(R.string.bluetooth).equals(defaultMethod)) { + intent.setClass(ActivityUtils.getActivity(context), BtSenderActivity.class); + } else { + throw new IllegalArgumentException("No such default sending method!"); + } + + context.startActivity(intent); + } + + /** + * Detecting the default set by user, and using that as main receiving method. + */ + public static void switchToDefaultReceivingMethod(@NonNull Context context) { + Intent intent = new Intent(); + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context.getApplicationContext()); + String defaultMethod = prefs.getString(PreferenceKeys.KEY_DEFAULT_TRANSFER_METHOD, context.getString(R.string.default_hotspot_ssid)); + if (context.getString(R.string.default_hotspot_ssid).equals(defaultMethod)) { + intent.setClass(ActivityUtils.getActivity(context), HpReceiverActivity.class); + } else if (context.getString(R.string.bluetooth).equals(defaultMethod)) { + intent.setClass(ActivityUtils.getActivity(context), BtReceiverActivity.class); + } else { + throw new IllegalArgumentException("No such default receiving method!"); + } + + context.startActivity(intent); + } +} diff --git a/skunkworks_crow/src/main/java/org/odk/share/utilities/PermissionUtils.java b/skunkworks_crow/src/main/java/org/odk/share/utilities/PermissionUtils.java new file mode 100644 index 00000000..792447c5 --- /dev/null +++ b/skunkworks_crow/src/main/java/org/odk/share/utilities/PermissionUtils.java @@ -0,0 +1,92 @@ +package org.odk.share.utilities; + + +import android.app.Activity; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.location.LocationManager; +import android.net.Uri; +import android.provider.Settings; +import android.widget.Toast; + +import androidx.appcompat.app.AlertDialog; + +import org.odk.share.R; + +import static android.content.Context.LOCATION_SERVICE; + +/** + * @author huangyz0918 (huangyz0918@gmail.com) + * For putting utils for permission checks. + */ +public class PermissionUtils { + + private static final String SCHEME = "package"; + public static final int APP_SETTING_REQUEST_CODE = 0x120; + + private PermissionUtils() { + } + + /** + * Checking if the location permission has been enabled. + */ + public static boolean isGPSEnabled(Context targetActivity) { + LocationManager locationManager = (LocationManager) targetActivity.getSystemService(LOCATION_SERVICE); + boolean gpsEnabled = false; + if (locationManager != null) { + gpsEnabled = locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER); + } + + return gpsEnabled; + } + + /** + * Showing an alert dialog for user to enable the location permission from system settings. + */ + public static void showLocationAlertDialog(Activity targetActivity) { + AlertDialog.Builder builder = new AlertDialog.Builder(targetActivity); + builder.setMessage(R.string.location_settings_dialog); + + builder.setPositiveButton(targetActivity.getString(R.string.settings), (DialogInterface dialog, int which) -> { + Intent intent = new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS); + targetActivity.startActivity(intent); + }); + + builder.setNegativeButton(targetActivity.getString(R.string.cancel), (DialogInterface dialog, int which) -> { + dialog.dismiss(); + targetActivity.finish(); + }); + + builder.setCancelable(false); + builder.show(); + } + + /** + * Showing an alert dialog and send users to app info page. + */ + public static void showAppInfo(Activity targetActivity, String packageName, + String msg, String deniedMsg) { + AlertDialog.Builder builder = new AlertDialog.Builder(targetActivity); + builder.setMessage(msg); + + builder.setPositiveButton(targetActivity.getString(R.string.permission_open_info_button), (DialogInterface dialog, int which) -> { + Intent intent = new Intent(); + intent.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS); + Uri uri = Uri.fromParts(SCHEME, packageName, null); + intent.setData(uri); + targetActivity.startActivityForResult(intent, APP_SETTING_REQUEST_CODE); + }); + + builder.setNegativeButton(targetActivity.getString(R.string.cancel), (DialogInterface dialog, int which) -> { + dialog.dismiss(); + Toast.makeText(targetActivity, deniedMsg, Toast.LENGTH_SHORT).show(); + targetActivity.finish(); + }); + + builder.setCancelable(false); + builder.show(); + } +} + + diff --git a/skunkworks_crow/src/main/java/org/odk/share/views/WifiView.java b/skunkworks_crow/src/main/java/org/odk/share/views/customui/WifiView.java similarity index 97% rename from skunkworks_crow/src/main/java/org/odk/share/views/WifiView.java rename to skunkworks_crow/src/main/java/org/odk/share/views/customui/WifiView.java index 4a1495c3..edf3073f 100644 --- a/skunkworks_crow/src/main/java/org/odk/share/views/WifiView.java +++ b/skunkworks_crow/src/main/java/org/odk/share/views/customui/WifiView.java @@ -1,4 +1,4 @@ -package org.odk.share.views; +package org.odk.share.views.customui; import android.content.Context; import android.net.wifi.WifiManager; diff --git a/skunkworks_crow/src/main/java/org/odk/share/views/listeners/ItemClickListener.java b/skunkworks_crow/src/main/java/org/odk/share/views/listeners/ItemClickListener.java new file mode 100644 index 00000000..14ab6551 --- /dev/null +++ b/skunkworks_crow/src/main/java/org/odk/share/views/listeners/ItemClickListener.java @@ -0,0 +1,8 @@ +package org.odk.share.views.listeners; + +import org.odk.share.views.ui.common.basecursor.BaseCursorViewHolder; + +public interface ItemClickListener { + + void onItemClick(BaseCursorViewHolder holder, int position); +} diff --git a/skunkworks_crow/src/main/java/org/odk/share/listeners/OnItemClickListener.java b/skunkworks_crow/src/main/java/org/odk/share/views/listeners/OnItemClickListener.java similarity index 79% rename from skunkworks_crow/src/main/java/org/odk/share/listeners/OnItemClickListener.java rename to skunkworks_crow/src/main/java/org/odk/share/views/listeners/OnItemClickListener.java index 093ec4e1..7537bf70 100644 --- a/skunkworks_crow/src/main/java/org/odk/share/listeners/OnItemClickListener.java +++ b/skunkworks_crow/src/main/java/org/odk/share/views/listeners/OnItemClickListener.java @@ -1,4 +1,4 @@ -package org.odk.share.listeners; +package org.odk.share.views.listeners; import android.view.View; diff --git a/skunkworks_crow/src/main/java/org/odk/share/listeners/ProgressListener.java b/skunkworks_crow/src/main/java/org/odk/share/views/listeners/ProgressListener.java similarity index 83% rename from skunkworks_crow/src/main/java/org/odk/share/listeners/ProgressListener.java rename to skunkworks_crow/src/main/java/org/odk/share/views/listeners/ProgressListener.java index 5d469176..e1354788 100644 --- a/skunkworks_crow/src/main/java/org/odk/share/listeners/ProgressListener.java +++ b/skunkworks_crow/src/main/java/org/odk/share/views/listeners/ProgressListener.java @@ -1,4 +1,4 @@ -package org.odk.share.listeners; +package org.odk.share.views.listeners; /** * Created by laksh on 5/30/2018. diff --git a/skunkworks_crow/src/main/java/org/odk/share/activities/AboutActivity.java b/skunkworks_crow/src/main/java/org/odk/share/views/ui/about/AboutActivity.java similarity index 77% rename from skunkworks_crow/src/main/java/org/odk/share/activities/AboutActivity.java rename to skunkworks_crow/src/main/java/org/odk/share/views/ui/about/AboutActivity.java index 858d41e7..29330ca6 100644 --- a/skunkworks_crow/src/main/java/org/odk/share/activities/AboutActivity.java +++ b/skunkworks_crow/src/main/java/org/odk/share/views/ui/about/AboutActivity.java @@ -1,22 +1,21 @@ -package org.odk.share.activities; +package org.odk.share.views.ui.about; import android.content.Intent; import android.os.Bundle; - import android.view.View; -import org.odk.share.R; -import org.odk.share.adapters.AboutAdapter; -import org.odk.share.listeners.OnItemClickListener; - import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.widget.Toolbar; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; + +import org.odk.share.R; +import org.odk.share.views.listeners.OnItemClickListener; + import butterknife.BindView; import butterknife.ButterKnife; -import static org.odk.share.activities.WebViewActivity.OPEN_URL; +import static org.odk.share.views.ui.about.WebViewActivity.OPEN_URL; public class AboutActivity extends AppCompatActivity implements OnItemClickListener { @@ -38,13 +37,15 @@ protected void onCreate(Bundle savedInstanceState) { setTitle(getString(R.string.about)); setSupportActionBar(toolbar); - getSupportActionBar().setDisplayHomeAsUpEnabled(true); + if (getSupportActionBar() != null) { + getSupportActionBar().setDisplayHomeAsUpEnabled(true); + } LinearLayoutManager llm = new LinearLayoutManager(this); llm.setOrientation(RecyclerView.VERTICAL); recyclerView.setLayoutManager(llm); - int[][] listItems = {{R.string.open_source_licenses, R.drawable.ic_stars}}; - adapter = new AboutAdapter(this, listItems, this); + adapter = new AboutAdapter(this, this); + adapter.addItem(new AboutItem(R.string.open_source_licenses, R.drawable.ic_stars)); recyclerView.setAdapter(adapter); } diff --git a/skunkworks_crow/src/main/java/org/odk/share/adapters/AboutAdapter.java b/skunkworks_crow/src/main/java/org/odk/share/views/ui/about/AboutAdapter.java similarity index 52% rename from skunkworks_crow/src/main/java/org/odk/share/adapters/AboutAdapter.java rename to skunkworks_crow/src/main/java/org/odk/share/views/ui/about/AboutAdapter.java index 97169bdf..c4d13ca0 100644 --- a/skunkworks_crow/src/main/java/org/odk/share/adapters/AboutAdapter.java +++ b/skunkworks_crow/src/main/java/org/odk/share/views/ui/about/AboutAdapter.java @@ -1,4 +1,4 @@ -package org.odk.share.adapters; +package org.odk.share.views.ui.about; import android.content.Context; import android.view.LayoutInflater; @@ -7,11 +7,15 @@ import android.widget.ImageView; import android.widget.TextView; -import org.odk.share.R; -import org.odk.share.listeners.OnItemClickListener; - import androidx.annotation.NonNull; import androidx.recyclerview.widget.RecyclerView; + +import org.odk.share.R; +import org.odk.share.views.listeners.OnItemClickListener; + +import java.util.ArrayList; +import java.util.List; + import butterknife.BindView; import butterknife.ButterKnife; @@ -21,37 +25,57 @@ public class AboutAdapter extends RecyclerView.Adapter { private Context context; - private int [][]listItems; + private List aboutItems; private final OnItemClickListener listener; - public AboutAdapter(Context context, int [][]listItems, OnItemClickListener listener) { + public void addItem(AboutItem aboutItem) { + aboutItems.add(aboutItem); + } + + public void addItems(List aboutItemList) { + aboutItems.addAll(aboutItemList); + } + + public void setItems(List aboutItemList) { + aboutItems = aboutItemList; + } + + public AboutAdapter(Context context, OnItemClickListener listener) { + this.context = context; + this.listener = listener; + aboutItems = new ArrayList<>(); + } + + public AboutAdapter(Context context, List aboutItems, OnItemClickListener listener) { this.context = context; - this.listItems = listItems; + this.aboutItems = aboutItems; this.listener = listener; } @Override - public AboutAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + public AboutAdapter.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { View view = LayoutInflater.from(context).inflate(R.layout.about_list_item, null); return new AboutAdapter.ViewHolder(view); } @Override public void onBindViewHolder(@NonNull final AboutAdapter.ViewHolder holder, int position) { - holder.title.setText(context.getString(listItems[position][0])); - holder.icon.setImageDrawable(context.getResources().getDrawable(listItems[position][1])); + holder.title.setText(context.getString(aboutItems.get(position).getTitle())); + holder.icon.setImageDrawable(context.getResources().getDrawable(aboutItems.get(position).getIcon())); holder.itemView.setOnClickListener(v -> listener.onItemClick(v, holder.getAdapterPosition())); } @Override public int getItemCount() { - return listItems.length; + return aboutItems.size(); } static class ViewHolder extends RecyclerView.ViewHolder { - @BindView(R.id.tvTitle) public TextView title; - @BindView(R.id.ivIcon) public ImageView icon; + @BindView(R.id.tvTitle) + public TextView title; + @BindView(R.id.ivIcon) + public ImageView icon; ViewHolder(View view) { super(view); diff --git a/skunkworks_crow/src/main/java/org/odk/share/views/ui/about/AboutItem.java b/skunkworks_crow/src/main/java/org/odk/share/views/ui/about/AboutItem.java new file mode 100644 index 00000000..25ba52f7 --- /dev/null +++ b/skunkworks_crow/src/main/java/org/odk/share/views/ui/about/AboutItem.java @@ -0,0 +1,39 @@ +package org.odk.share.views.ui.about; + + +import androidx.annotation.DrawableRes; +import androidx.annotation.StringRes; + +/** + * AboutItem: Data Wrapper for About Page Item. + * + * @author huangyz0918 (huangyz0918@gmail.com) + */ +public class AboutItem { + + private @StringRes + int titleRes; + private @DrawableRes + int iconRes; + + public AboutItem(@StringRes int titleRes, @DrawableRes int iconRes) { + this.titleRes = titleRes; + this.iconRes = iconRes; + } + + public int getTitle() { + return titleRes; + } + + public void setTitle(@StringRes int titleRes) { + this.titleRes = titleRes; + } + + public int getIcon() { + return iconRes; + } + + public void setIcon(@DrawableRes int iconRes) { + this.iconRes = iconRes; + } +} \ No newline at end of file diff --git a/skunkworks_crow/src/main/java/org/odk/share/activities/WebViewActivity.java b/skunkworks_crow/src/main/java/org/odk/share/views/ui/about/WebViewActivity.java similarity index 93% rename from skunkworks_crow/src/main/java/org/odk/share/activities/WebViewActivity.java rename to skunkworks_crow/src/main/java/org/odk/share/views/ui/about/WebViewActivity.java index 2b26722d..02900967 100644 --- a/skunkworks_crow/src/main/java/org/odk/share/activities/WebViewActivity.java +++ b/skunkworks_crow/src/main/java/org/odk/share/views/ui/about/WebViewActivity.java @@ -12,7 +12,7 @@ * the License. */ -package org.odk.share.activities; +package org.odk.share.views.ui.about; import android.graphics.Bitmap; import android.os.Bundle; @@ -40,14 +40,14 @@ protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_web_view); - Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); + Toolbar toolbar = findViewById(R.id.toolbar); setSupportActionBar(toolbar); getSupportActionBar().setDisplayHomeAsUpEnabled(true); getSupportActionBar().setHomeAsUpIndicator(R.drawable.ic_close); String url = getIntent().getStringExtra(OPEN_URL); - webView = (WebView) findViewById(R.id.webView); - progressBar = (ProgressBar) findViewById(R.id.progressBar); + webView = findViewById(R.id.webView); + progressBar = findViewById(R.id.progressBar); webView.setWebViewClient(new WebViewClient() { @Override diff --git a/skunkworks_crow/src/main/java/org/odk/share/views/ui/bluetooth/BluetoothListAdapter.java b/skunkworks_crow/src/main/java/org/odk/share/views/ui/bluetooth/BluetoothListAdapter.java new file mode 100644 index 00000000..bf225542 --- /dev/null +++ b/skunkworks_crow/src/main/java/org/odk/share/views/ui/bluetooth/BluetoothListAdapter.java @@ -0,0 +1,134 @@ +package org.odk.share.views.ui.bluetooth; + +import android.bluetooth.BluetoothDevice; +import android.content.Context; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.RecyclerView; + +import org.odk.share.R; + +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + +import butterknife.BindView; +import butterknife.ButterKnife; + +import static android.bluetooth.BluetoothDevice.BOND_BONDED; +import static android.bluetooth.BluetoothDevice.BOND_NONE; + + +/** + * BluetoothListAdapter: {@link RecyclerView.Adapter} for {@link BluetoothDevice}. + * + * @author huangyz0918 (huangyz0918@gmail.com) + * @since 12/06/2019 + */ +public class BluetoothListAdapter extends RecyclerView.Adapter { + + private List bluetoothDeviceList; + private OnDeviceClickListener btDeviceClickListener; + + public BluetoothListAdapter(OnDeviceClickListener btDeviceClickListener) { + this.btDeviceClickListener = btDeviceClickListener; + this.bluetoothDeviceList = new ArrayList<>(); + } + + /** + * Methods for updating data. + */ + public void setDevices(List dateSet) { + this.bluetoothDeviceList = dateSet; + } + + public void addDevice(BluetoothDevice bluetoothDevice) { + if (!bluetoothDeviceList.contains(bluetoothDevice)) { + this.bluetoothDeviceList.add(bluetoothDevice); + } + notifyDataSetChanged(); + } + + public void addDevices(Set devices) { + if (!bluetoothDeviceList.containsAll(devices)) { + this.bluetoothDeviceList.addAll(devices); + } + notifyDataSetChanged(); + } + + @Override + public void onBindViewHolder(@NonNull final ViewHolder viewHolder, int position) { + Context context = viewHolder.itemView.getContext(); + BluetoothDevice device = bluetoothDeviceList.get(position); + String deviceName = device.getName(); + String deviceAddress = device.getAddress(); + int deviceBondState = device.getBondState(); + viewHolder.deviceName.setText(deviceName == null ? viewHolder.itemView.getResources().getString(R.string.bluetooth_instance_name_default) : deviceName); + viewHolder.deviceAddress.setText(String.format("%s (%s)", deviceAddress, + deviceBondState == BOND_NONE ? context.getString(R.string.bluetooth_unpaired) : context.getString(R.string.bluetooth_paired))); + if (deviceBondState == BOND_BONDED) { + viewHolder.deviceLogo.setImageResource(R.drawable.ic_smart_phone_yellow); + } + } + + @Override + public int getItemCount() { + return bluetoothDeviceList.size(); + } + + @NonNull + @Override + public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.list_item_bt_device, null); + return new ViewHolder(view); + } + + /** + * View holder for the bluetooth devices. + */ + class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener { + + @BindView(R.id.tv_device_name) + TextView deviceName; + + @BindView(R.id.tv_device_address) + TextView deviceAddress; + + @BindView(R.id.iv_device) + ImageView deviceLogo; + + ViewHolder(View view) { + super(view); + ButterKnife.bind(this, view); + itemView.setOnClickListener(this); + } + + @Override + public void onClick(View v) { + int position = getAdapterPosition(); + if (position >= 0 && position < bluetoothDeviceList.size()) { + btDeviceClickListener.onItemClick(bluetoothDeviceList.get(position)); + } + } + } + + /** + * clear all the data in the list. + */ + public void clearBluetoothDeviceList() { + bluetoothDeviceList.clear(); + notifyDataSetChanged(); + } + + /** + * listener for bluetooth devices clicked. + */ + public interface OnDeviceClickListener { + void onItemClick(BluetoothDevice dev); + } +} diff --git a/skunkworks_crow/src/main/java/org/odk/share/views/ui/bluetooth/BtReceiverActivity.java b/skunkworks_crow/src/main/java/org/odk/share/views/ui/bluetooth/BtReceiverActivity.java new file mode 100644 index 00000000..42487b57 --- /dev/null +++ b/skunkworks_crow/src/main/java/org/odk/share/views/ui/bluetooth/BtReceiverActivity.java @@ -0,0 +1,390 @@ +package org.odk.share.views.ui.bluetooth; + + +import android.Manifest; +import android.app.AlertDialog; +import android.app.ProgressDialog; +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothDevice; +import android.content.DialogInterface; +import android.content.Intent; +import android.os.Bundle; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; +import android.widget.Button; +import android.widget.Toast; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.appcompat.widget.Toolbar; +import androidx.recyclerview.widget.DividerItemDecoration; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + +import org.odk.share.R; +import org.odk.share.bluetooth.BluetoothReceiver; +import org.odk.share.bluetooth.BluetoothUtils; +import org.odk.share.events.BluetoothEvent; +import org.odk.share.events.DownloadEvent; +import org.odk.share.rx.RxEventBus; +import org.odk.share.rx.schedulers.BaseSchedulerProvider; +import org.odk.share.services.ReceiverService; +import org.odk.share.utilities.ActivityUtils; +import org.odk.share.utilities.DialogUtils; +import org.odk.share.utilities.PermissionUtils; +import org.odk.share.views.ui.common.injectable.InjectableActivity; +import org.odk.share.views.ui.hotspot.HpReceiverActivity; + +import javax.inject.Inject; + +import butterknife.BindView; +import butterknife.ButterKnife; +import butterknife.OnClick; +import io.reactivex.disposables.CompositeDisposable; +import io.reactivex.disposables.Disposable; +import permissions.dispatcher.NeedsPermission; +import permissions.dispatcher.OnNeverAskAgain; +import permissions.dispatcher.OnPermissionDenied; +import permissions.dispatcher.RuntimePermissions; +import timber.log.Timber; + +import static org.odk.share.utilities.PermissionUtils.APP_SETTING_REQUEST_CODE; + +/** + * Bluetooth receiver activity. + * + * @author huangyz0918 (huangyz0918@gmail.com) + */ +@RuntimePermissions +public class BtReceiverActivity extends InjectableActivity implements + BluetoothReceiver.BluetoothReceiverListener, BluetoothListAdapter.OnDeviceClickListener { + + @BindView(R.id.toolbar) + Toolbar toolbar; + + @BindView(R.id.btn_refresh) + Button btnRefresh; + + @BindView(R.id.list_bt_device) + RecyclerView recyclerView; + + @BindView(R.id.no_devices_view) + View emptyDevicesView; + + @Inject + ReceiverService receiverService; + + @Inject + RxEventBus rxEventBus; + + @Inject + BaseSchedulerProvider schedulerProvider; + + private BluetoothAdapter bluetoothAdapter; + private BluetoothReceiver bluetoothReceiver; + private final BluetoothListAdapter bluetoothListAdapter = new BluetoothListAdapter(this); + private boolean isConnected = false; + private ProgressDialog progressDialog; + private ProgressDialog scanningDialog; + private AlertDialog resultDialog; + + private final CompositeDisposable compositeDisposable = new CompositeDisposable(); + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_bt_receive); + ButterKnife.bind(this); + + setTitle(" " + getString(R.string.connect_bluetooth_title)); + setSupportActionBar(toolbar); + if (getSupportActionBar() != null) { + getSupportActionBar().setIcon(R.drawable.ic_bluetooth_white_24dp); + } + + initEvents(); + setupDialogs(); + } + + /** + * Init the basic events for our views. + */ + private void initEvents() { + bluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); + LinearLayoutManager linearLayoutManager = new LinearLayoutManager(this); + DividerItemDecoration dividerItemDecoration = new DividerItemDecoration(recyclerView.getContext(), + linearLayoutManager.getOrientation()); + recyclerView.addItemDecoration(dividerItemDecoration); + recyclerView.setLayoutManager(linearLayoutManager); + recyclerView.setAdapter(bluetoothListAdapter); + bluetoothReceiver = new BluetoothReceiver(this, this); + + BtReceiverActivityPermissionsDispatcher.updateDeviceListWithPermissionCheck(this); + } + + /** + * build a new progress dialog waiting for the scanning progress. + */ + private void setupDialogs() { + //scanning dialog + scanningDialog = new ProgressDialog(this); + scanningDialog.setCancelable(false); + scanningDialog.setTitle(getString(R.string.scanning_title)); + scanningDialog.setMessage(getString(R.string.scanning_msg)); + scanningDialog.setButton(DialogInterface.BUTTON_NEGATIVE, getString(R.string.stop), (DialogInterface dialog, int which) -> { + dialog.dismiss(); + bluetoothAdapter.cancelDiscovery(); + }); + + //result dialog + resultDialog = new AlertDialog.Builder(this) + .setTitle(getString(R.string.transfer_result)) + .setCancelable(false) + .setNegativeButton(getString(R.string.ok), (DialogInterface dialog, int which) -> { + dialog.dismiss(); + receiverService.cancel(); + if (BluetoothUtils.isBluetoothEnabled()) { + BluetoothUtils.disableBluetooth(); + } + finish(); + }) + .create(); + } + + /** + * Rescan the bluetooth devices and update the list. + */ + @NeedsPermission({Manifest.permission.ACCESS_FINE_LOCATION, + Manifest.permission.ACCESS_COARSE_LOCATION}) + void updateDeviceList() { + if (!BluetoothUtils.isBluetoothEnabled()) { + Toast.makeText(this, getString(R.string.turning_on_bluetooth_message), Toast.LENGTH_SHORT).show(); + BluetoothUtils.enableBluetooth(); + } + if (!bluetoothAdapter.isDiscovering()) { + bluetoothAdapter.startDiscovery(); + } + bluetoothListAdapter.notifyDataSetChanged(); + } + + /** + * If the permission was denied, finishing this activity. + */ + @OnPermissionDenied({Manifest.permission.ACCESS_FINE_LOCATION, + Manifest.permission.ACCESS_COARSE_LOCATION}) + void showDeniedForLocation() { + Toast.makeText(this, R.string.permission_location_denied, Toast.LENGTH_LONG).show(); + finish(); + } + + /** + * If clicked the "never ask", we should show a toast to guide user. + */ + @OnNeverAskAgain({Manifest.permission.ACCESS_FINE_LOCATION, + Manifest.permission.ACCESS_COARSE_LOCATION}) + void showNeverAskForLocation() { + PermissionUtils.showAppInfo(this, getPackageName(), getString(R.string.permission_open_location_info), getString(R.string.permission_location_denied)); + } + + @Override + public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults); + BtReceiverActivityPermissionsDispatcher.onRequestPermissionsResult(this, requestCode, grantResults); + } + + /** + * Clicking to refresh the devices list. + */ + @OnClick(R.id.btn_refresh) + public void refresh() { + if (BluetoothUtils.isBluetoothEnabled()) { + BtReceiverActivityPermissionsDispatcher.updateDeviceListWithPermissionCheck(this); + } else { + BluetoothUtils.enableBluetooth(); + Toast.makeText(this, getString(R.string.turning_on_bluetooth_message), Toast.LENGTH_SHORT).show(); + } + } + + /** + * Creates a subscription for listening to all hotspot events being send through the + * application's {@link RxEventBus} + */ + private Disposable addBluetoothEventSubscription() { + return rxEventBus.register(BluetoothEvent.class) + .subscribeOn(schedulerProvider.io()) + .observeOn(schedulerProvider.androidThread()) + .subscribe(bluetoothEvent -> { + switch (bluetoothEvent.getStatus()) { + case CONNECTED: + if (progressDialog != null) { + progressDialog.setMessage(getString(R.string.connected_bluetooth_downloading)); + } + isConnected = true; + break; + case DISCONNECTED: + isConnected = false; + break; + } + }); + } + + private Disposable addDownloadEventSubscription() { + return rxEventBus.register(DownloadEvent.class) + .subscribeOn(schedulerProvider.io()) + .observeOn(schedulerProvider.androidThread()) + .subscribe(downloadEvent -> { + switch (downloadEvent.getStatus()) { + case QUEUED: + Timber.d(getString(R.string.download_queued)); + break; + case DOWNLOADING: + int progress = downloadEvent.getCurrentProgress(); + int total = downloadEvent.getTotalSize(); + String alertMsg = getString(R.string.receiving_items, String.valueOf(progress), String.valueOf(total)); + progressDialog.setTitle(getString(R.string.receiving_title)); + progressDialog.setMessage(alertMsg); + break; + case FINISHED: + progressDialog.dismiss(); + String result = downloadEvent.getResult(); + resultDialog.setMessage(result); + resultDialog.show(); + break; + case ERROR: + progressDialog.dismiss(); + Toast.makeText(this, getString(R.string.error_while_downloading, downloadEvent.getResult()), Toast.LENGTH_SHORT).show(); + break; + case CANCELLED: + progressDialog.dismiss(); + Toast.makeText(this, getString(R.string.canceled), Toast.LENGTH_LONG).show(); + break; + } + }, Timber::e); + } + + /** + * To check if the bluetooth devices list is empty, and present an empty view for users. + */ + private void checkEmptyList() { + if (bluetoothListAdapter.getItemCount() == 0) { + emptyDevicesView.setVisibility(View.VISIBLE); + } else { + emptyDevicesView.setVisibility(View.GONE); + } + } + + @Override + public void onDeviceFound(BluetoothDevice device) { + bluetoothListAdapter.addDevice(device); + checkEmptyList(); + } + + @Override + public void onDiscoveryStarted() { + scanningDialog.show(); + checkEmptyList(); + bluetoothListAdapter.clearBluetoothDeviceList(); + } + + @Override + public void onDiscoveryFinished() { + scanningDialog.dismiss(); + checkEmptyList(); + } + + @Override + public void onStateChanged(int state) { + if (state == BluetoothAdapter.STATE_ON) { + BtReceiverActivityPermissionsDispatcher.updateDeviceListWithPermissionCheck(this); + } + } + + /** + * Clicking the item to connect. + */ + @Override + public void onItemClick(BluetoothDevice device) { + if (BluetoothUtils.isBluetoothEnabled()) { + if (isConnected) { + Toast.makeText(this, getString(R.string.dev_already_connected), Toast.LENGTH_SHORT).show(); + } + + receiverService.startBtDownloading(device.getAddress()); + progressDialog = new ProgressDialog(this); + progressDialog.setTitle(getString(R.string.connecting_title)); + progressDialog.setMessage(getString(R.string.connecting_message)); + progressDialog.setIndeterminate(true); + progressDialog.setButton(DialogInterface.BUTTON_NEGATIVE, getString(R.string.stop), (DialogInterface dialog, int which) -> { + receiverService.cancel(); + dialog.dismiss(); + }); + progressDialog.show(); + } else { + Toast.makeText(this, getString(R.string.turning_on_bluetooth_message), Toast.LENGTH_SHORT).show(); + BluetoothUtils.enableBluetooth(); + } + } + + /** + * Create the switch method button in the menu. + */ + @Override + public boolean onCreateOptionsMenu(Menu menu) { + getMenuInflater().inflate(R.menu.switch_method_menu, menu); + final MenuItem switchItem = menu.findItem(R.id.menu_switch); + switchItem.setOnMenuItemClickListener((MenuItem item) -> { + DialogUtils.createMethodSwitchDialog(this, (DialogInterface dialog, int which) -> { + receiverService.cancel(); + if (BluetoothUtils.isBluetoothEnabled()) { + BluetoothUtils.disableBluetooth(); + } + ActivityUtils.launchActivity(this, HpReceiverActivity.class, true); + }).show(); + return true; + }); + return super.onCreateOptionsMenu(menu); + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) { + super.onActivityResult(requestCode, resultCode, data); + if (requestCode == APP_SETTING_REQUEST_CODE) { + BtReceiverActivityPermissionsDispatcher.updateDeviceListWithPermissionCheck(this); + } + } + + @Override + protected void onResume() { + super.onResume(); + compositeDisposable.add(addDownloadEventSubscription()); + compositeDisposable.add(addBluetoothEventSubscription()); + } + + @Override + protected void onDestroy() { + super.onDestroy(); + unregisterReceiver(bluetoothReceiver); + } + + @Override + public void onBackPressed() { + if (BluetoothUtils.isBluetoothEnabled()) { + new AlertDialog.Builder(this) + .setTitle(getString(R.string.disable_bluetooth)) + .setMessage(getString(R.string.disable_bluetooth_receiver_msg)) + .setPositiveButton(R.string.quit, (DialogInterface dialog, int which) -> { + receiverService.cancel(); + BluetoothUtils.disableBluetooth(); + super.onBackPressed(); + }) + .setNegativeButton(android.R.string.no, (DialogInterface dialog, int which) -> { + dialog.dismiss(); + }) + .create() + .show(); + } else { + super.onBackPressed(); + } + } +} diff --git a/skunkworks_crow/src/main/java/org/odk/share/views/ui/bluetooth/BtSenderActivity.java b/skunkworks_crow/src/main/java/org/odk/share/views/ui/bluetooth/BtSenderActivity.java new file mode 100644 index 00000000..62ba253d --- /dev/null +++ b/skunkworks_crow/src/main/java/org/odk/share/views/ui/bluetooth/BtSenderActivity.java @@ -0,0 +1,411 @@ +package org.odk.share.views.ui.bluetooth; + + +import android.Manifest; +import android.app.AlertDialog; +import android.app.ProgressDialog; +import android.bluetooth.BluetoothAdapter; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.pm.PackageManager; +import android.os.Bundle; +import android.os.CountDownTimer; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; +import android.widget.ProgressBar; +import android.widget.TextView; +import android.widget.Toast; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.appcompat.widget.Toolbar; +import androidx.core.content.ContextCompat; + +import org.odk.share.R; +import org.odk.share.bluetooth.BluetoothUtils; +import org.odk.share.events.BluetoothEvent; +import org.odk.share.events.UploadEvent; +import org.odk.share.rx.RxEventBus; +import org.odk.share.rx.schedulers.BaseSchedulerProvider; +import org.odk.share.services.SenderService; +import org.odk.share.utilities.DialogUtils; +import org.odk.share.utilities.PermissionUtils; +import org.odk.share.views.ui.common.injectable.InjectableActivity; +import org.odk.share.views.ui.hotspot.HpSenderActivity; + +import javax.inject.Inject; + +import butterknife.BindView; +import butterknife.ButterKnife; +import io.reactivex.disposables.CompositeDisposable; +import io.reactivex.disposables.Disposable; +import permissions.dispatcher.NeedsPermission; +import permissions.dispatcher.OnNeverAskAgain; +import permissions.dispatcher.OnPermissionDenied; +import permissions.dispatcher.RuntimePermissions; +import timber.log.Timber; + +import static org.odk.share.utilities.ApplicationConstants.ASK_REVIEW_MODE; +import static org.odk.share.utilities.PermissionUtils.APP_SETTING_REQUEST_CODE; +import static org.odk.share.views.ui.instance.InstancesList.INSTANCE_IDS; +import static org.odk.share.views.ui.instance.fragment.ReviewedInstancesFragment.MODE; +import static org.odk.share.views.ui.send.fragment.BlankFormsFragment.FORM_IDS; + + +/** + * Bluetooth sender activity. + * + * @author huangyz0918 (huangyz0918@gmail.com) + */ +@RuntimePermissions +public class BtSenderActivity extends InjectableActivity { + + @BindView(R.id.test_text_view) + TextView activityTextView; + + @BindView(R.id.toolbar) + Toolbar toolbar; + + @BindView(R.id.progressBar) + ProgressBar progressBar; + + @Inject + RxEventBus rxEventBus; + + @Inject + BaseSchedulerProvider schedulerProvider; + + @Inject + SenderService senderService; + + private final CompositeDisposable compositeDisposable = new CompositeDisposable(); + private boolean isFinished = false; + private boolean isDiscovering = false; + + private CountDownTimer countDownTimer; + private ProgressDialog progressDialog; + private AlertDialog resultDialog; + private static final int CONNECT_TIMEOUT = 120; + private static final int COUNT_DOWN_INTERVAL = 1000; + private Intent receivedIntent; + private static final int DISCOVERABLE_CODE = 0x121; + private static final int SUCCESS_CODE = 120; + + private long[] formIds; + private int mode; + + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_bt_send); + ButterKnife.bind(this); + + setTitle(" " + getString(R.string.send_instance_title)); + setSupportActionBar(toolbar); + if (getSupportActionBar() != null) { + getSupportActionBar().setIcon(R.drawable.ic_bluetooth_white_24dp); + } + + if (!BluetoothUtils.isBluetoothEnabled()) { + BluetoothUtils.enableBluetooth(); + } + + if (getIntent() != null) { + receivedIntent = getIntent(); + } else { + throw new IllegalArgumentException("No received intent"); + } + + setupDialog(); + registerReceiver(bluetoothReceiver, new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED)); + formIds = getIntent().getLongArrayExtra(INSTANCE_IDS); + mode = getIntent().getIntExtra(MODE, ASK_REVIEW_MODE); + if (formIds == null) { + formIds = receivedIntent.getLongArrayExtra(FORM_IDS); + } + + if (BluetoothUtils.isBluetoothEnabled()) { + BtSenderActivityPermissionsDispatcher.enableDiscoveryWithPermissionCheck(this); + } + } + + private void setupDialog() { + //ProgressDialog + progressDialog = new ProgressDialog(this); + progressDialog.setTitle(getString(R.string.sending_title)); + progressDialog.setIndeterminate(true); + progressDialog.setButton(DialogInterface.BUTTON_NEGATIVE, getString(R.string.stop), + (DialogInterface dialog, int which) -> { + dialog.dismiss(); + senderService.cancel(); + finish(); + }); + + //AlertDialog + resultDialog = new AlertDialog.Builder(this) + .setTitle(getString(R.string.transfer_result)) + .setCancelable(false) + .setNegativeButton(getString(R.string.ok), (DialogInterface dialog, int which) -> { + dialog.dismiss(); + senderService.cancel(); + if (BluetoothUtils.isBluetoothEnabled()) { + BluetoothUtils.disableBluetooth(); + } + finish(); + }) + .create(); + } + + /** + * Create the switch method button in the menu. + */ + @Override + public boolean onCreateOptionsMenu(Menu menu) { + getMenuInflater().inflate(R.menu.switch_method_menu, menu); + final MenuItem switchItem = menu.findItem(R.id.menu_switch); + switchItem.setOnMenuItemClickListener((MenuItem item) -> { + DialogUtils.createMethodSwitchDialog(this, (DialogInterface dialog, int which) -> { + receivedIntent.setClass(this, HpSenderActivity.class); + senderService.cancel(); + if (BluetoothUtils.isBluetoothEnabled()) { + BluetoothUtils.disableBluetooth(); + } + startActivity(receivedIntent); + finish(); + }).show(); + return true; + }); + + return super.onCreateOptionsMenu(menu); + } + + /** + * Creates a subscription for listening to all hotspot events being send through the + * application's {@link RxEventBus} + */ + private Disposable addBluetoothEventSubscription() { + return rxEventBus.register(BluetoothEvent.class) + .subscribeOn(schedulerProvider.io()) + .observeOn(schedulerProvider.androidThread()) + .subscribe(bluetoothEvent -> { + switch (bluetoothEvent.getStatus()) { + case CONNECTED: + countDownTimer.cancel(); + progressDialog.setMessage(getString(R.string.connecting_transfer_message)); + progressDialog.show(); + activityTextView.setVisibility(View.GONE); + break; + case DISCONNECTED: + progressDialog.dismiss(); + break; + } + }); + } + + private Disposable addUploadEventSubscription() { + return rxEventBus.register(UploadEvent.class) + .subscribeOn(schedulerProvider.io()) + .observeOn(schedulerProvider.androidThread()) + .subscribe(uploadEvent -> { + switch (uploadEvent.getStatus()) { + case QUEUED: + isFinished = false; + Toast.makeText(this, R.string.upload_queued, Toast.LENGTH_SHORT).show(); + break; + case UPLOADING: + int progress = uploadEvent.getCurrentProgress(); + int total = uploadEvent.getTotalSize(); + String alertMsg = getString(R.string.sending_items, String.valueOf(progress), String.valueOf(total)); + progressDialog.setMessage(alertMsg); + break; + case FINISHED: + progressDialog.dismiss(); + isFinished = true; + progressBar.setVisibility(View.GONE); + String result = uploadEvent.getResult(); + resultDialog.setMessage(result); + resultDialog.show(); + break; + case ERROR: + progressBar.setVisibility(View.GONE); + if (progressDialog != null && progressDialog.isShowing()) { + progressDialog.dismiss(); + } + Toast.makeText(this, getString(R.string.error_while_uploading, uploadEvent.getResult()), Toast.LENGTH_SHORT).show(); + break; + case CANCELLED: + progressBar.setVisibility(View.GONE); + if (progressDialog != null && progressDialog.isShowing()) { + progressDialog.dismiss(); + } + Toast.makeText(this, getString(R.string.canceled), Toast.LENGTH_LONG).show(); + break; + } + }, Timber::e); + } + + /** + * Enable the bluetooth discovery for other devices. The timeout is specific seconds. + */ + @NeedsPermission({Manifest.permission.ACCESS_FINE_LOCATION, + Manifest.permission.ACCESS_COARSE_LOCATION}) + void enableDiscovery() { + if (BluetoothUtils.isBluetoothEnabled()) { + Intent discoverableIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE); + // set the discovery timeout for 120s. + discoverableIntent.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, CONNECT_TIMEOUT); + startActivityForResult(discoverableIntent, DISCOVERABLE_CODE); + } + } + + private final BroadcastReceiver bluetoothReceiver = new BroadcastReceiver() { + public void onReceive(Context context, Intent intent) { + String action = intent.getAction(); + if (BluetoothAdapter.ACTION_STATE_CHANGED.equals(action)) { + if (intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, -1) + == BluetoothAdapter.STATE_ON && !isDiscovering) { + BtSenderActivityPermissionsDispatcher.enableDiscoveryWithPermissionCheck(BtSenderActivity.this); + } + } + } + }; + + /** + * If the permission was denied, finishing this activity. + */ + @OnPermissionDenied({Manifest.permission.ACCESS_FINE_LOCATION, + Manifest.permission.ACCESS_COARSE_LOCATION}) + void showDeniedForLocation() { + Toast.makeText(this, R.string.permission_location_denied, Toast.LENGTH_LONG).show(); + finish(); + } + + /** + * If clicked the "never ask", we should show a toast to guide user. + */ + @OnNeverAskAgain({Manifest.permission.ACCESS_FINE_LOCATION, + Manifest.permission.ACCESS_COARSE_LOCATION}) + void showNeverAskForLocation() { + PermissionUtils.showAppInfo(this, getPackageName(), getString(R.string.permission_open_location_info), getString(R.string.permission_location_denied)); + } + + @Override + public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults); + BtSenderActivityPermissionsDispatcher.onRequestPermissionsResult(this, requestCode, grantResults); + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) { + super.onActivityResult(requestCode, resultCode, data); + switch (requestCode) { + case DISCOVERABLE_CODE: + if (resultCode == SUCCESS_CODE) { + isDiscovering = true; + startCheckingDiscoverableDuration(); + } else { + isDiscovering = false; + finish(); + } + break; + case APP_SETTING_REQUEST_CODE: + if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) + != PackageManager.PERMISSION_GRANTED) { + PermissionUtils.showAppInfo(this, getPackageName(), getString(R.string.permission_open_location_info), getString(R.string.permission_location_denied)); + } else { + BtSenderActivityPermissionsDispatcher.enableDiscoveryWithPermissionCheck(this); + } + break; + } + } + + /** + * Checking the discoverable time, if the device is no longer discoverable, we should show + * an {@link AlertDialog} to notice our users. + */ + private void startCheckingDiscoverableDuration() { + senderService.startUploading(formIds, mode); + AlertDialog alertDialog = new AlertDialog.Builder(this) + .setTitle(getString(R.string.timeout)) + .setMessage(getString(R.string.bluetooth_send_time_up)) + .setCancelable(false) + .setNegativeButton(R.string.quit, (DialogInterface dialog, int which) -> { + finish(); + }) + .create(); + + activityTextView.setText(getString(R.string.tv_sender_wait_for_connect)); + countDownTimer = new CountDownTimer(CONNECT_TIMEOUT * COUNT_DOWN_INTERVAL, COUNT_DOWN_INTERVAL) { + @Override + public void onTick(long millisUntilFinished) { + activityTextView.setText(String.format(getString(R.string.tv_sender_wait_for_connect), + String.valueOf(millisUntilFinished / COUNT_DOWN_INTERVAL))); + } + + @Override + public void onFinish() { + isDiscovering = false; + if (!(BtSenderActivity.this).isFinishing()) { + alertDialog.show(); + } + } + }.start(); + } + + @Override + protected void onResume() { + super.onResume(); + compositeDisposable.add(addUploadEventSubscription()); + compositeDisposable.add(addBluetoothEventSubscription()); + } + + @Override + protected void onPause() { + super.onPause(); + compositeDisposable.clear(); + } + + @Override + protected void onDestroy() { + super.onDestroy(); + unregisterReceiver(bluetoothReceiver); + } + + @Override + public void onBackPressed() { + if (!isFinished) { + new AlertDialog.Builder(this) + .setTitle(getString(R.string.stop_sending)) + .setMessage(getString(R.string.stop_sending_msg)) + .setPositiveButton(R.string.stop, (DialogInterface dialog, int which) -> { + senderService.cancel(); + BluetoothUtils.disableBluetooth(); + super.onBackPressed(); + }) + .setNegativeButton(R.string.cancel, (DialogInterface dialog, int which) -> { + dialog.dismiss(); + }) + .create() + .show(); + } else { + new AlertDialog.Builder(this) + .setTitle(getString(R.string.disable_bluetooth)) + .setMessage(getString(R.string.disable_bluetooth_sender_msg)) + .setPositiveButton(R.string.quit, (DialogInterface dialog, int which) -> { + senderService.cancel(); + BluetoothUtils.disableBluetooth(); + super.onBackPressed(); + }) + .setNegativeButton(android.R.string.no, (DialogInterface dialog, int which) -> { + dialog.dismiss(); + }) + .create() + .show(); + } + } +} \ No newline at end of file diff --git a/skunkworks_crow/src/main/java/org/odk/share/fragments/InstanceListFragment.java b/skunkworks_crow/src/main/java/org/odk/share/views/ui/common/InstanceListFragment.java similarity index 95% rename from skunkworks_crow/src/main/java/org/odk/share/fragments/InstanceListFragment.java rename to skunkworks_crow/src/main/java/org/odk/share/views/ui/common/InstanceListFragment.java index 08d90c89..6bccf0fc 100644 --- a/skunkworks_crow/src/main/java/org/odk/share/fragments/InstanceListFragment.java +++ b/skunkworks_crow/src/main/java/org/odk/share/views/ui/common/InstanceListFragment.java @@ -11,9 +11,10 @@ limitations under the License. */ -package org.odk.share.fragments; +package org.odk.share.views.ui.common; import org.odk.collect.android.provider.InstanceProviderAPI; +import org.odk.share.views.ui.common.applist.AppListFragment; import static org.odk.share.utilities.ApplicationConstants.SortingOrder.BY_DATE_ASC; import static org.odk.share.utilities.ApplicationConstants.SortingOrder.BY_DATE_DESC; diff --git a/skunkworks_crow/src/main/java/org/odk/share/adapters/ViewPagerAdapter.java b/skunkworks_crow/src/main/java/org/odk/share/views/ui/common/ViewPagerAdapter.java similarity index 96% rename from skunkworks_crow/src/main/java/org/odk/share/adapters/ViewPagerAdapter.java rename to skunkworks_crow/src/main/java/org/odk/share/views/ui/common/ViewPagerAdapter.java index 1506f97b..9922e4bd 100644 --- a/skunkworks_crow/src/main/java/org/odk/share/adapters/ViewPagerAdapter.java +++ b/skunkworks_crow/src/main/java/org/odk/share/views/ui/common/ViewPagerAdapter.java @@ -1,4 +1,4 @@ -package org.odk.share.adapters; +package org.odk.share.views.ui.common; import java.util.ArrayList; import java.util.List; diff --git a/skunkworks_crow/src/main/java/org/odk/share/activities/AppListActivity.java b/skunkworks_crow/src/main/java/org/odk/share/views/ui/common/applist/AppListActivity.java similarity index 97% rename from skunkworks_crow/src/main/java/org/odk/share/activities/AppListActivity.java rename to skunkworks_crow/src/main/java/org/odk/share/views/ui/common/applist/AppListActivity.java index 2c5161fc..82a2a9d2 100644 --- a/skunkworks_crow/src/main/java/org/odk/share/activities/AppListActivity.java +++ b/skunkworks_crow/src/main/java/org/odk/share/views/ui/common/applist/AppListActivity.java @@ -1,4 +1,4 @@ -package org.odk.share.activities; +package org.odk.share.views.ui.common.applist; /* * Copyright 2017 Nafundi @@ -30,7 +30,7 @@ import com.google.android.material.bottomsheet.BottomSheetDialog; import org.odk.share.R; -import org.odk.share.adapters.SortDialogAdapter; +import org.odk.share.views.ui.common.injectable.InjectableActivity; import java.util.LinkedHashSet; @@ -42,7 +42,7 @@ import static org.odk.share.utilities.ApplicationConstants.SortingOrder.BY_NAME_ASC; -abstract class AppListActivity extends InjectableActivity { +public abstract class AppListActivity extends InjectableActivity { private static final String SELECTED_INSTANCES = "selectedInstances"; private static final String IS_SEARCH_BOX_SHOWN = "isSearchBoxShown"; diff --git a/skunkworks_crow/src/main/java/org/odk/share/fragments/AppListFragment.java b/skunkworks_crow/src/main/java/org/odk/share/views/ui/common/applist/AppListFragment.java similarity index 95% rename from skunkworks_crow/src/main/java/org/odk/share/fragments/AppListFragment.java rename to skunkworks_crow/src/main/java/org/odk/share/views/ui/common/applist/AppListFragment.java index 73e7ff49..7324da68 100644 --- a/skunkworks_crow/src/main/java/org/odk/share/fragments/AppListFragment.java +++ b/skunkworks_crow/src/main/java/org/odk/share/views/ui/common/applist/AppListFragment.java @@ -11,9 +11,13 @@ limitations under the License. */ -package org.odk.share.fragments; +package org.odk.share.views.ui.common.applist; import android.preference.PreferenceManager; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.View; import androidx.appcompat.widget.SearchView; import androidx.core.content.ContextCompat; @@ -22,21 +26,16 @@ import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; -import android.view.Menu; -import android.view.MenuInflater; -import android.view.MenuItem; -import android.view.View; - import com.google.android.material.bottomsheet.BottomSheetDialog; import org.odk.share.R; -import org.odk.share.adapters.SortDialogAdapter; +import org.odk.share.views.ui.common.injectable.InjectableFragment; import java.util.LinkedHashSet; import static org.odk.share.utilities.ApplicationConstants.SortingOrder.BY_NAME_ASC; -abstract class AppListFragment extends InjectableFragment { +public abstract class AppListFragment extends InjectableFragment { protected String[] sortingOptions; protected LinkedHashSet selectedInstances = new LinkedHashSet<>(); @@ -55,8 +54,7 @@ public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { final SearchView searchView = (SearchView) MenuItemCompat.getActionView(searchItem); searchView.setQueryHint(getResources().getString(R.string.search)); searchView.setMaxWidth(Integer.MAX_VALUE); - SearchView.SearchAutoComplete searchAutoComplete = - (SearchView.SearchAutoComplete) searchView.findViewById(androidx.appcompat.R.id.search_src_text); + SearchView.SearchAutoComplete searchAutoComplete = searchView.findViewById(androidx.appcompat.R.id.search_src_text); searchAutoComplete.setCursorVisible(true); searchAutoComplete.setHintTextColor(ContextCompat.getColor(getActivity(), android.R.color.white)); searchAutoComplete.setTextColor(ContextCompat.getColor(getActivity(), android.R.color.white)); diff --git a/skunkworks_crow/src/main/java/org/odk/share/adapters/SortDialogAdapter.java b/skunkworks_crow/src/main/java/org/odk/share/views/ui/common/applist/SortDialogAdapter.java similarity index 98% rename from skunkworks_crow/src/main/java/org/odk/share/adapters/SortDialogAdapter.java rename to skunkworks_crow/src/main/java/org/odk/share/views/ui/common/applist/SortDialogAdapter.java index 19bb8ea1..9865a88c 100644 --- a/skunkworks_crow/src/main/java/org/odk/share/adapters/SortDialogAdapter.java +++ b/skunkworks_crow/src/main/java/org/odk/share/views/ui/common/applist/SortDialogAdapter.java @@ -12,7 +12,7 @@ * the License. */ -package org.odk.share.adapters; +package org.odk.share.views.ui.common.applist; import android.content.Context; import android.content.res.ColorStateList; diff --git a/skunkworks_crow/src/main/java/org/odk/share/adapters/basecursoradapter/BaseCursorViewHolder.java b/skunkworks_crow/src/main/java/org/odk/share/views/ui/common/basecursor/BaseCursorViewHolder.java similarity index 84% rename from skunkworks_crow/src/main/java/org/odk/share/adapters/basecursoradapter/BaseCursorViewHolder.java rename to skunkworks_crow/src/main/java/org/odk/share/views/ui/common/basecursor/BaseCursorViewHolder.java index b8afcff4..f248e588 100644 --- a/skunkworks_crow/src/main/java/org/odk/share/adapters/basecursoradapter/BaseCursorViewHolder.java +++ b/skunkworks_crow/src/main/java/org/odk/share/views/ui/common/basecursor/BaseCursorViewHolder.java @@ -1,9 +1,11 @@ -package org.odk.share.adapters.basecursoradapter; +package org.odk.share.views.ui.common.basecursor; import android.view.View; import androidx.recyclerview.widget.RecyclerView; +import org.odk.share.views.listeners.ItemClickListener; + public class BaseCursorViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener { diff --git a/skunkworks_crow/src/main/java/org/odk/share/adapters/basecursoradapter/CursorRecyclerViewAdapter.java b/skunkworks_crow/src/main/java/org/odk/share/views/ui/common/basecursor/CursorRecyclerViewAdapter.java similarity index 97% rename from skunkworks_crow/src/main/java/org/odk/share/adapters/basecursoradapter/CursorRecyclerViewAdapter.java rename to skunkworks_crow/src/main/java/org/odk/share/views/ui/common/basecursor/CursorRecyclerViewAdapter.java index 14777bba..2ce0afc8 100644 --- a/skunkworks_crow/src/main/java/org/odk/share/adapters/basecursoradapter/CursorRecyclerViewAdapter.java +++ b/skunkworks_crow/src/main/java/org/odk/share/views/ui/common/basecursor/CursorRecyclerViewAdapter.java @@ -1,4 +1,4 @@ -package org.odk.share.adapters.basecursoradapter; +package org.odk.share.views.ui.common.basecursor; import android.content.Context; import android.database.Cursor; @@ -7,6 +7,8 @@ import androidx.annotation.NonNull; import androidx.recyclerview.widget.RecyclerView; +import org.odk.share.views.listeners.ItemClickListener; + public abstract class CursorRecyclerViewAdapter extends RecyclerView.Adapter { diff --git a/skunkworks_crow/src/main/java/org/odk/share/activities/InjectableActivity.java b/skunkworks_crow/src/main/java/org/odk/share/views/ui/common/injectable/InjectableActivity.java similarity index 90% rename from skunkworks_crow/src/main/java/org/odk/share/activities/InjectableActivity.java rename to skunkworks_crow/src/main/java/org/odk/share/views/ui/common/injectable/InjectableActivity.java index f18d247d..151d2c79 100644 --- a/skunkworks_crow/src/main/java/org/odk/share/activities/InjectableActivity.java +++ b/skunkworks_crow/src/main/java/org/odk/share/views/ui/common/injectable/InjectableActivity.java @@ -1,4 +1,4 @@ -package org.odk.share.activities; +package org.odk.share.views.ui.common.injectable; import android.annotation.SuppressLint; diff --git a/skunkworks_crow/src/main/java/org/odk/share/fragments/InjectableFragment.java b/skunkworks_crow/src/main/java/org/odk/share/views/ui/common/injectable/InjectableFragment.java similarity index 89% rename from skunkworks_crow/src/main/java/org/odk/share/fragments/InjectableFragment.java rename to skunkworks_crow/src/main/java/org/odk/share/views/ui/common/injectable/InjectableFragment.java index ea713929..d27d583b 100644 --- a/skunkworks_crow/src/main/java/org/odk/share/fragments/InjectableFragment.java +++ b/skunkworks_crow/src/main/java/org/odk/share/views/ui/common/injectable/InjectableFragment.java @@ -1,4 +1,4 @@ -package org.odk.share.fragments; +package org.odk.share.views.ui.common.injectable; import dagger.android.support.DaggerFragment; diff --git a/skunkworks_crow/src/main/java/org/odk/share/activities/WifiActivity.java b/skunkworks_crow/src/main/java/org/odk/share/views/ui/hotspot/HpReceiverActivity.java similarity index 91% rename from skunkworks_crow/src/main/java/org/odk/share/activities/WifiActivity.java rename to skunkworks_crow/src/main/java/org/odk/share/views/ui/hotspot/HpReceiverActivity.java index afbd6fc1..875ab69d 100644 --- a/skunkworks_crow/src/main/java/org/odk/share/activities/WifiActivity.java +++ b/skunkworks_crow/src/main/java/org/odk/share/views/ui/hotspot/HpReceiverActivity.java @@ -1,4 +1,4 @@ -package org.odk.share.activities; +package org.odk.share.views.ui.hotspot; import android.app.Dialog; import android.app.ProgressDialog; @@ -12,6 +12,8 @@ import android.os.Bundle; import android.text.InputType; import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuItem; import android.view.View; import android.view.WindowManager; import android.widget.Button; @@ -21,16 +23,19 @@ import android.widget.TextView; import android.widget.Toast; +import androidx.appcompat.app.AlertDialog; +import androidx.appcompat.widget.Toolbar; +import androidx.recyclerview.widget.DefaultItemAnimator; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + import com.google.zxing.integration.android.IntentIntegrator; import com.google.zxing.integration.android.IntentResult; -import com.journeyapps.barcodescanner.CaptureActivity; import org.json.JSONException; import org.json.JSONObject; import org.odk.share.R; -import org.odk.share.adapters.WifiResultAdapter; import org.odk.share.events.DownloadEvent; -import org.odk.share.listeners.OnItemClickListener; import org.odk.share.network.WifiConnector; import org.odk.share.network.WifiNetworkInfo; import org.odk.share.network.listeners.WifiStateListener; @@ -38,6 +43,12 @@ import org.odk.share.rx.RxEventBus; import org.odk.share.rx.schedulers.BaseSchedulerProvider; import org.odk.share.services.ReceiverService; +import org.odk.share.utilities.ActivityUtils; +import org.odk.share.utilities.DialogUtils; +import org.odk.share.views.listeners.OnItemClickListener; +import org.odk.share.views.ui.bluetooth.BtReceiverActivity; +import org.odk.share.views.ui.common.injectable.InjectableActivity; +import org.odk.share.views.ui.receive.ScannerActivity; import java.util.ArrayList; import java.util.Arrays; @@ -45,11 +56,6 @@ import javax.inject.Inject; -import androidx.appcompat.app.AlertDialog; -import androidx.appcompat.widget.Toolbar; -import androidx.recyclerview.widget.DefaultItemAnimator; -import androidx.recyclerview.widget.LinearLayoutManager; -import androidx.recyclerview.widget.RecyclerView; import butterknife.BindView; import butterknife.ButterKnife; import butterknife.OnClick; @@ -63,7 +69,7 @@ import static org.odk.share.utilities.QRCodeUtils.PROTECTED; import static org.odk.share.utilities.QRCodeUtils.SSID; -public class WifiActivity extends InjectableActivity implements OnItemClickListener, WifiStateListener { +public class HpReceiverActivity extends InjectableActivity implements OnItemClickListener, WifiStateListener { private static final int DIALOG_DOWNLOAD_PROGRESS = 1; private static final int DIALOG_CONNECTING = 2; @@ -115,8 +121,11 @@ protected void onCreate(Bundle savedInstanceState) { setContentView(R.layout.activity_wifi); ButterKnife.bind(this); - setTitle(getString(R.string.connect_wifi)); + setTitle(" " + getString(R.string.connect_wifi)); setSupportActionBar(toolbar); + if (getSupportActionBar() != null) { + getSupportActionBar().setIcon(R.drawable.ic_wifi_tethering_white_24dp); + } getSupportActionBar().setDisplayHomeAsUpEnabled(true); @@ -195,6 +204,24 @@ private Disposable addDownloadEventSubscription() { }, Timber::e); } + /** + * Create the switch method button in the menu. + */ + @Override + public boolean onCreateOptionsMenu(Menu menu) { + getMenuInflater().inflate(R.menu.switch_method_menu, menu); + final MenuItem switchItem = menu.findItem(R.id.menu_switch); + switchItem.setOnMenuItemClickListener((MenuItem item) -> { + DialogUtils.createMethodSwitchDialog(this, (DialogInterface dialog, int which) -> { + receiverService.cancel(); + wifiConnector.disableWifi(wifiNetworkSSID); + ActivityUtils.launchActivity(this, BtReceiverActivity.class, true); + }).show(); + return true; + }); + return super.onCreateOptionsMenu(menu); + } + @Override protected void onStop() { super.onStop(); @@ -314,7 +341,7 @@ private void setEmptyViewVisibility(String text) { private void startReceiveTask() { showDialog(DIALOG_DOWNLOAD_PROGRESS); String dstAddress = wifiConnector.getAccessPointIpAddress(); - receiverService.startDownloading(dstAddress, port); + receiverService.startHpDownloading(dstAddress, port); } @OnClick(R.id.bScan) @@ -329,7 +356,7 @@ public void scanQRCode() { .setDesiredBarcodeFormats(QR_CODE_TYPES) .setCameraId(0) .setOrientationLocked(false) - .setCaptureActivity(CaptureActivity.class) + .setCaptureActivity(ScannerActivity.class) .setBeepEnabled(true) .initiateScan(); } @@ -378,11 +405,7 @@ protected void onDestroy() { } private void createAlertDialog(String title, String message) { - alertDialog = new AlertDialog.Builder(this).create(); - alertDialog.setTitle(title); - alertDialog.setMessage(message); - alertDialog.setCancelable(false); - alertDialog.setButton(DialogInterface.BUTTON_POSITIVE, getString(R.string.ok), (dialog, i) -> finish()); + alertDialog = DialogUtils.createSimpleDialog(this, title, message); alertDialog.show(); } diff --git a/skunkworks_crow/src/main/java/org/odk/share/activities/SendActivity.java b/skunkworks_crow/src/main/java/org/odk/share/views/ui/hotspot/HpSenderActivity.java similarity index 77% rename from skunkworks_crow/src/main/java/org/odk/share/activities/SendActivity.java rename to skunkworks_crow/src/main/java/org/odk/share/views/ui/hotspot/HpSenderActivity.java index 6aa2064b..63e69202 100644 --- a/skunkworks_crow/src/main/java/org/odk/share/activities/SendActivity.java +++ b/skunkworks_crow/src/main/java/org/odk/share/views/ui/hotspot/HpSenderActivity.java @@ -1,4 +1,4 @@ -package org.odk.share.activities; +package org.odk.share.views.ui.hotspot; import android.Manifest; import android.app.Dialog; @@ -7,19 +7,23 @@ import android.content.Context; import android.content.DialogInterface; import android.content.Intent; - import android.content.pm.PackageManager; -import android.location.LocationManager; import android.net.wifi.WifiConfiguration; import android.net.wifi.WifiManager; import android.os.Build; import android.os.Bundle; import android.os.Handler; -import android.provider.Settings; +import android.view.Menu; +import android.view.MenuItem; import android.widget.ImageView; import android.widget.TextView; import android.widget.Toast; +import androidx.annotation.NonNull; +import androidx.annotation.RequiresApi; +import androidx.appcompat.app.AlertDialog; +import androidx.appcompat.widget.Toolbar; + import org.odk.share.R; import org.odk.share.events.HotspotEvent; import org.odk.share.events.UploadEvent; @@ -29,15 +33,15 @@ import org.odk.share.rx.schedulers.BaseSchedulerProvider; import org.odk.share.services.HotspotService; import org.odk.share.services.SenderService; +import org.odk.share.utilities.DialogUtils; +import org.odk.share.utilities.PermissionUtils; import org.odk.share.utilities.QRCodeUtils; import org.odk.share.utilities.SocketUtils; +import org.odk.share.views.ui.bluetooth.BtSenderActivity; +import org.odk.share.views.ui.common.injectable.InjectableActivity; import javax.inject.Inject; -import androidx.annotation.NonNull; -import androidx.annotation.RequiresApi; -import androidx.appcompat.app.AlertDialog; -import androidx.appcompat.widget.Toolbar; import butterknife.BindView; import butterknife.ButterKnife; import io.reactivex.disposables.CompositeDisposable; @@ -45,16 +49,16 @@ import timber.log.Timber; import static android.view.View.VISIBLE; -import static org.odk.share.activities.InstancesList.INSTANCE_IDS; -import static org.odk.share.fragments.BlankFormsFragment.FORM_IDS; -import static org.odk.share.fragments.ReviewedInstancesFragment.MODE; import static org.odk.share.utilities.ApplicationConstants.ASK_REVIEW_MODE; +import static org.odk.share.views.ui.instance.InstancesList.INSTANCE_IDS; +import static org.odk.share.views.ui.instance.fragment.ReviewedInstancesFragment.MODE; +import static org.odk.share.views.ui.send.fragment.BlankFormsFragment.FORM_IDS; /** * Created by laksh on 6/9/2018. */ -public class SendActivity extends InjectableActivity { +public class HpSenderActivity extends InjectableActivity { public static final String DEFAULT_SSID = "ODK-SKUNKWORKS"; private static final int PROGRESS_DIALOG = 1; @@ -87,6 +91,7 @@ public class SendActivity extends InjectableActivity { private int port; private long[] formIds; private int mode; + private Intent receivedIntent; private WifiManager.LocalOnlyHotspotReservation hotspotReservation; private WifiConfiguration currentConfig; @@ -97,11 +102,15 @@ protected void onCreate(Bundle savedInstanceState) { setContentView(R.layout.activity_send); ButterKnife.bind(this); - setTitle(getString(R.string.send_forms)); + setTitle(" " + getString(R.string.send_forms)); setSupportActionBar(toolbar); + if (getSupportActionBar() != null) { + getSupportActionBar().setIcon(R.drawable.ic_wifi_tethering_white_24dp); + } - formIds = getIntent().getLongArrayExtra(INSTANCE_IDS); - mode = getIntent().getIntExtra(MODE, ASK_REVIEW_MODE); + receivedIntent = getIntent(); + formIds = receivedIntent.getLongArrayExtra(INSTANCE_IDS); + mode = receivedIntent.getIntExtra(MODE, ASK_REVIEW_MODE); if (formIds == null) { formIds = getIntent().getLongArrayExtra(FORM_IDS); } @@ -137,6 +146,30 @@ public void onBackPressed() { } } + /** + * Create the switch method button in the menu. + */ + @Override + public boolean onCreateOptionsMenu(Menu menu) { + getMenuInflater().inflate(R.menu.switch_method_menu, menu); + final MenuItem switchItem = menu.findItem(R.id.menu_switch); + switchItem.setOnMenuItemClickListener((MenuItem item) -> { + DialogUtils.createMethodSwitchDialog(this, (DialogInterface dialog, int which) -> { + receivedIntent.setClass(this, BtSenderActivity.class); + + if (isHotspotRunning) { + stopHotspot(); + } + + startActivity(receivedIntent); + finish(); + }).show(); + return true; + }); + + return super.onCreateOptionsMenu(menu); + } + /** * Creates a subscription for listening to all hotspot events being send through the * application's {@link RxEventBus} @@ -168,21 +201,11 @@ private void startHotspot() { } } - private boolean isGPSEnabled() { - LocationManager locationManager = (LocationManager) getSystemService(LOCATION_SERVICE); - boolean gpsEnabled = false; - if (locationManager != null) { - gpsEnabled = locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER); - } - - return gpsEnabled; - } - private void initiateHotspot() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - if (!isGPSEnabled()) { + if (!PermissionUtils.isGPSEnabled(this)) { isHotspotInitiated = false; - showLocationAlertDialog(); + PermissionUtils.showLocationAlertDialog(this); return; } turnOnHotspot(); @@ -207,53 +230,26 @@ private void initiateHotspot() { } } - private void showLocationAlertDialog() { - AlertDialog.Builder builder = new AlertDialog.Builder(this); - builder.setMessage(R.string.location_settings_dialog); - builder.setPositiveButton(getString(R.string.settings), new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - Intent intent = new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS); - startActivity(intent); - } - }); - - builder.setNegativeButton(getString(R.string.cancel), new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - dialog.dismiss(); - finish(); - } - }); - builder.setCancelable(false); - builder.show(); - } - private void showAlertDialog() { AlertDialog.Builder builder = new AlertDialog.Builder(this); builder.setMessage(R.string.hotspot_settings_dialog); - builder.setPositiveButton(getString(R.string.settings), new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - wifiHotspot.saveLastConfig(); - WifiConfiguration newWifiConfig = wifiHotspot.createNewConfig(DEFAULT_SSID + getString(R.string.hotspot_name_suffix)); - wifiHotspot.setCurrConfig(newWifiConfig); - wifiHotspot.setWifiConfig(newWifiConfig); - final Intent intent = new Intent(Intent.ACTION_MAIN, null); - intent.addCategory(Intent.CATEGORY_LAUNCHER); - final ComponentName cn = new ComponentName("com.android.settings", "com.android.settings.TetherSettings"); - intent.setComponent(cn); - intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - startActivity(intent); - openSettings = true; - } + builder.setPositiveButton(getString(R.string.settings), (DialogInterface dialog, int which) -> { + wifiHotspot.saveLastConfig(); + WifiConfiguration newWifiConfig = wifiHotspot.createNewConfig(DEFAULT_SSID + getString(R.string.hotspot_name_suffix)); + wifiHotspot.setCurrConfig(newWifiConfig); + wifiHotspot.setWifiConfig(newWifiConfig); + final Intent intent = new Intent(Intent.ACTION_MAIN, null); + intent.addCategory(Intent.CATEGORY_LAUNCHER); + final ComponentName cn = new ComponentName("com.android.settings", "com.android.settings.TetherSettings"); + intent.setComponent(cn); + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + startActivity(intent); + openSettings = true; }); - builder.setNegativeButton(getString(R.string.cancel), new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - dialog.dismiss(); - finish(); - } + + builder.setNegativeButton(getString(R.string.cancel), (DialogInterface dialog, int which) -> { + dialog.dismiss(); + finish(); }); builder.setCancelable(false); @@ -263,18 +259,13 @@ public void onClick(DialogInterface dialog, int which) { private void stopHotspotAlertDialog() { AlertDialog.Builder builder = new AlertDialog.Builder(this); builder.setMessage(R.string.stop_sending); - builder.setPositiveButton(getString(R.string.stop), new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - stopHotspot(); - finish(); - } + builder.setPositiveButton(getString(R.string.stop), (DialogInterface dialog, int which) -> { + stopHotspot(); + finish(); }); - builder.setNegativeButton(getString(R.string.cancel), new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - dialog.dismiss(); - } + + builder.setNegativeButton(getString(R.string.cancel), (DialogInterface dialog, int which) -> { + dialog.dismiss(); }); builder.setCancelable(false); @@ -294,7 +285,7 @@ private void stopHotspot() { Timber.d("Hotspot Stopped"); compositeDisposable.dispose(); } - + @Override protected void onResume() { super.onResume(); @@ -424,13 +415,10 @@ protected Dialog onCreateDialog(int id) { progressDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER); progressDialog.setCancelable(false); progressDialog.setButton(DialogInterface.BUTTON_NEGATIVE, getString(R.string.cancel), - new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - senderService.cancel(); - dialog.dismiss(); - finish(); - } + (DialogInterface dialog, int which) -> { + senderService.cancel(); + dialog.dismiss(); + finish(); }); return progressDialog; } @@ -442,17 +430,15 @@ private void createAlertDialog(String title, String message) { AlertDialog alertDialog = new AlertDialog.Builder(this).create(); alertDialog.setTitle(title); alertDialog.setMessage(message); - DialogInterface.OnClickListener quitListener = new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int i) { - switch (i) { - case DialogInterface.BUTTON_POSITIVE: - stopHotspot(); - finish(); - break; - } + DialogInterface.OnClickListener quitListener = (DialogInterface dialog, int i) -> { + switch (i) { + case DialogInterface.BUTTON_POSITIVE: + stopHotspot(); + finish(); + break; } }; + alertDialog.setCancelable(false); alertDialog.setButton(DialogInterface.BUTTON_POSITIVE, getString(R.string.ok), quitListener); alertDialog.show(); @@ -465,7 +451,7 @@ private void checkLocationPermission() { toggleHotspot(); } else { - requestPermissions(new String[]{Manifest.permission.ACCESS_COARSE_LOCATION} , LOCATION_PERMISSION_REQUEST_CODE); + requestPermissions(new String[]{Manifest.permission.ACCESS_COARSE_LOCATION}, LOCATION_PERMISSION_REQUEST_CODE); } } diff --git a/skunkworks_crow/src/main/java/org/odk/share/adapters/WifiResultAdapter.java b/skunkworks_crow/src/main/java/org/odk/share/views/ui/hotspot/WifiResultAdapter.java similarity index 94% rename from skunkworks_crow/src/main/java/org/odk/share/adapters/WifiResultAdapter.java rename to skunkworks_crow/src/main/java/org/odk/share/views/ui/hotspot/WifiResultAdapter.java index cf33c0d0..79041654 100644 --- a/skunkworks_crow/src/main/java/org/odk/share/adapters/WifiResultAdapter.java +++ b/skunkworks_crow/src/main/java/org/odk/share/views/ui/hotspot/WifiResultAdapter.java @@ -1,4 +1,4 @@ -package org.odk.share.adapters; +package org.odk.share.views.ui.hotspot; import android.content.Context; import android.net.wifi.WifiConfiguration; @@ -8,9 +8,9 @@ import android.widget.TextView; import org.odk.share.R; -import org.odk.share.listeners.OnItemClickListener; +import org.odk.share.views.listeners.OnItemClickListener; import org.odk.share.network.WifiNetworkInfo; -import org.odk.share.views.WifiView; +import org.odk.share.views.customui.WifiView; import java.util.List; diff --git a/skunkworks_crow/src/main/java/org/odk/share/activities/InstanceListActivity.java b/skunkworks_crow/src/main/java/org/odk/share/views/ui/instance/InstanceListActivity.java similarity index 89% rename from skunkworks_crow/src/main/java/org/odk/share/activities/InstanceListActivity.java rename to skunkworks_crow/src/main/java/org/odk/share/views/ui/instance/InstanceListActivity.java index d7f140ac..fbb9b745 100644 --- a/skunkworks_crow/src/main/java/org/odk/share/activities/InstanceListActivity.java +++ b/skunkworks_crow/src/main/java/org/odk/share/views/ui/instance/InstanceListActivity.java @@ -1,13 +1,14 @@ -package org.odk.share.activities; +package org.odk.share.views.ui.instance; import org.odk.collect.android.provider.InstanceProviderAPI; +import org.odk.share.views.ui.common.applist.AppListActivity; import static org.odk.share.utilities.ApplicationConstants.SortingOrder.BY_DATE_ASC; import static org.odk.share.utilities.ApplicationConstants.SortingOrder.BY_DATE_DESC; import static org.odk.share.utilities.ApplicationConstants.SortingOrder.BY_NAME_ASC; import static org.odk.share.utilities.ApplicationConstants.SortingOrder.BY_NAME_DESC; -abstract class InstanceListActivity extends AppListActivity { +public abstract class InstanceListActivity extends AppListActivity { protected String getSortingOrder() { String sortingOrder = InstanceProviderAPI.InstanceColumns.DISPLAY_NAME + " COLLATE NOCASE ASC, " + InstanceProviderAPI.InstanceColumns.STATUS + " DESC"; switch (getSelectedSortingOrder()) { diff --git a/skunkworks_crow/src/main/java/org/odk/share/activities/InstanceManagerTabs.java b/skunkworks_crow/src/main/java/org/odk/share/views/ui/instance/InstanceManagerTabs.java similarity index 88% rename from skunkworks_crow/src/main/java/org/odk/share/activities/InstanceManagerTabs.java rename to skunkworks_crow/src/main/java/org/odk/share/views/ui/instance/InstanceManagerTabs.java index 4579aa9c..74cbef88 100644 --- a/skunkworks_crow/src/main/java/org/odk/share/activities/InstanceManagerTabs.java +++ b/skunkworks_crow/src/main/java/org/odk/share/views/ui/instance/InstanceManagerTabs.java @@ -1,4 +1,4 @@ -package org.odk.share.activities; +package org.odk.share.views.ui.instance; import android.os.Bundle; @@ -13,18 +13,19 @@ import com.google.android.material.tabs.TabLayout; import org.odk.share.R; -import org.odk.share.adapters.ViewPagerAdapter; -import org.odk.share.fragments.ReceivedInstancesFragment; -import org.odk.share.fragments.ReviewedInstancesFragment; -import org.odk.share.fragments.SentInstancesFragment; -import org.odk.share.fragments.StatisticsFragment; +import org.odk.share.views.ui.common.ViewPagerAdapter; +import org.odk.share.views.ui.instance.fragment.ReceivedInstancesFragment; +import org.odk.share.views.ui.instance.fragment.ReviewedInstancesFragment; +import org.odk.share.views.ui.instance.fragment.SentInstancesFragment; +import org.odk.share.views.ui.instance.fragment.StatisticsFragment; +import org.odk.share.views.ui.common.injectable.InjectableActivity; import androidx.fragment.app.Fragment; import androidx.viewpager.widget.ViewPager; import butterknife.BindView; import butterknife.ButterKnife; -import static org.odk.share.activities.MainActivity.FORM_ID; +import static org.odk.share.views.ui.main.MainActivity.FORM_ID; public class InstanceManagerTabs extends InjectableActivity implements TabLayout.OnTabSelectedListener { diff --git a/skunkworks_crow/src/main/java/org/odk/share/activities/InstancesList.java b/skunkworks_crow/src/main/java/org/odk/share/views/ui/instance/InstancesList.java similarity index 89% rename from skunkworks_crow/src/main/java/org/odk/share/activities/InstancesList.java rename to skunkworks_crow/src/main/java/org/odk/share/views/ui/instance/InstancesList.java index ae9e8658..1694d363 100644 --- a/skunkworks_crow/src/main/java/org/odk/share/activities/InstancesList.java +++ b/skunkworks_crow/src/main/java/org/odk/share/views/ui/instance/InstancesList.java @@ -1,35 +1,36 @@ -package org.odk.share.activities; +package org.odk.share.views.ui.instance; import android.content.Intent; import android.database.Cursor; import android.os.Bundle; - -import androidx.annotation.NonNull; -import androidx.appcompat.widget.Toolbar; import android.view.View; import android.widget.Button; import android.widget.CheckBox; +import androidx.annotation.NonNull; +import androidx.appcompat.widget.Toolbar; +import androidx.loader.app.LoaderManager; +import androidx.loader.content.Loader; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + import org.odk.collect.android.dao.InstancesDao; import org.odk.collect.android.provider.InstanceProviderAPI; import org.odk.share.R; -import org.odk.share.adapters.InstanceAdapter; import org.odk.share.utilities.ApplicationConstants; import org.odk.share.utilities.ArrayUtils; +import org.odk.share.utilities.DialogUtils; +import org.odk.share.views.ui.instance.adapter.InstanceAdapter; import java.util.LinkedHashSet; import javax.inject.Inject; -import androidx.loader.app.LoaderManager; -import androidx.loader.content.Loader; -import androidx.recyclerview.widget.LinearLayoutManager; -import androidx.recyclerview.widget.RecyclerView; import butterknife.BindView; import butterknife.ButterKnife; import butterknife.OnClick; -import static org.odk.share.fragments.ReviewedInstancesFragment.MODE; +import static org.odk.share.views.ui.instance.fragment.ReviewedInstancesFragment.MODE; public class InstancesList extends InstanceListActivity implements LoaderManager.LoaderCallbacks { @@ -37,8 +38,10 @@ public class InstancesList extends InstanceListActivity implements LoaderManager RecyclerView recyclerView; @BindView(R.id.toolbar) Toolbar toolbar; - @BindView(R.id.send_button) Button sendButton; - @BindView(R.id.toggle_button) Button toggleButton; + @BindView(R.id.send_button) + Button sendButton; + @BindView(R.id.toggle_button) + Button toggleButton; private static final String INSTANCE_LIST_ACTIVITY_SORTING_ORDER = "instanceListActivitySortingOrder"; @@ -84,8 +87,8 @@ public void onLoadFinished(@NonNull Loader loader, Cursor cursor) { instanceAdapter = new InstanceAdapter(this, cursor, this::onListItemClick, selectedInstances); recyclerView.setAdapter(instanceAdapter); if (instanceAdapter.getItemCount() > 0) { - toggleButton.setText(getString(R.string.select_all)); - toggleButton.setEnabled(true); + toggleButton.setText(getString(R.string.select_all)); + toggleButton.setEnabled(true); } else { toggleButton.setEnabled(false); } @@ -118,13 +121,13 @@ private void onListItemClick(View view, int position) { @OnClick(R.id.send_button) public void send() { - Intent intent = new Intent(this, SendActivity.class); + Intent intent = new Intent(); Long[] arr = selectedInstances.toArray(new Long[selectedInstances.size()]); long[] a = ArrayUtils.toPrimitive(arr); intent.putExtra(INSTANCE_IDS, a); intent.putExtra(MODE, ApplicationConstants.ASK_REVIEW_MODE); - startActivity(intent); - finish(); + + DialogUtils.switchToDefaultSendingMethod(this, intent); } @OnClick(R.id.toggle_button) diff --git a/skunkworks_crow/src/main/java/org/odk/share/adapters/InstanceAdapter.java b/skunkworks_crow/src/main/java/org/odk/share/views/ui/instance/adapter/InstanceAdapter.java similarity index 57% rename from skunkworks_crow/src/main/java/org/odk/share/adapters/InstanceAdapter.java rename to skunkworks_crow/src/main/java/org/odk/share/views/ui/instance/adapter/InstanceAdapter.java index 67391e64..9ac3d10e 100644 --- a/skunkworks_crow/src/main/java/org/odk/share/adapters/InstanceAdapter.java +++ b/skunkworks_crow/src/main/java/org/odk/share/views/ui/instance/adapter/InstanceAdapter.java @@ -1,4 +1,4 @@ -package org.odk.share.adapters; +package org.odk.share.views.ui.instance.adapter; import android.content.Context; import android.database.Cursor; @@ -11,12 +11,16 @@ import org.odk.collect.android.provider.InstanceProviderAPI; import org.odk.share.R; +import java.text.SimpleDateFormat; +import java.util.Date; import java.util.LinkedHashSet; +import java.util.Locale; import androidx.annotation.NonNull; import androidx.recyclerview.widget.RecyclerView; import butterknife.BindView; import butterknife.ButterKnife; +import timber.log.Timber; /** * Created by laksh on 5/20/2018. @@ -54,7 +58,12 @@ public void onClick(View v) { } }); holder.title.setText(cursor.getString(cursor.getColumnIndex(InstanceProviderAPI.InstanceColumns.DISPLAY_NAME))); - holder.subtitle.setText(cursor.getString(cursor.getColumnIndex(InstanceProviderAPI.InstanceColumns.DISPLAY_SUBTEXT))); + + long lastStatusChangeDate = getCursor().getLong(getCursor().getColumnIndex(InstanceProviderAPI.InstanceColumns.LAST_STATUS_CHANGE_DATE)); + String status = getCursor().getString(getCursor().getColumnIndex(InstanceProviderAPI.InstanceColumns.STATUS)); + String subtext = getDisplaySubtext(context, status, new Date(lastStatusChangeDate)); + + holder.subtitle.setText(subtext); long id = cursor.getLong(cursor.getColumnIndex(InstanceProviderAPI.InstanceColumns._ID)); holder.checkBox.setChecked(selectedInstances.contains(id)); holder.reviewedForms.setVisibility(View.GONE); @@ -62,6 +71,34 @@ public void onClick(View v) { } + public static String getDisplaySubtext(Context context, String state, Date date) { + try { + if (state == null) { + return new SimpleDateFormat(context.getString(R.string.added_on_date_at_time), + Locale.getDefault()).format(date); + } else if (InstanceProviderAPI.STATUS_INCOMPLETE.equalsIgnoreCase(state)) { + return new SimpleDateFormat(context.getString(R.string.saved_on_date_at_time), + Locale.getDefault()).format(date); + } else if (InstanceProviderAPI.STATUS_COMPLETE.equalsIgnoreCase(state)) { + return new SimpleDateFormat(context.getString(R.string.finalized_on_date_at_time), + Locale.getDefault()).format(date); + } else if (InstanceProviderAPI.STATUS_SUBMITTED.equalsIgnoreCase(state)) { + return new SimpleDateFormat(context.getString(R.string.sent_on_date_at_time), + Locale.getDefault()).format(date); + } else if (InstanceProviderAPI.STATUS_SUBMISSION_FAILED.equalsIgnoreCase(state)) { + return new SimpleDateFormat( + context.getString(R.string.sending_failed_on_date_at_time), + Locale.getDefault()).format(date); + } else { + return new SimpleDateFormat(context.getString(R.string.added_on_date_at_time), + Locale.getDefault()).format(date); + } + } catch (IllegalArgumentException e) { + Timber.e(e); + return ""; + } + } + @Override public int getItemCount() { return !cursor.isClosed() ? cursor.getCount() : 0; diff --git a/skunkworks_crow/src/main/java/org/odk/share/adapters/TransferInstanceAdapter.java b/skunkworks_crow/src/main/java/org/odk/share/views/ui/instance/adapter/TransferInstanceAdapter.java similarity index 97% rename from skunkworks_crow/src/main/java/org/odk/share/adapters/TransferInstanceAdapter.java rename to skunkworks_crow/src/main/java/org/odk/share/views/ui/instance/adapter/TransferInstanceAdapter.java index f2f0fcdb..46bc2412 100644 --- a/skunkworks_crow/src/main/java/org/odk/share/adapters/TransferInstanceAdapter.java +++ b/skunkworks_crow/src/main/java/org/odk/share/views/ui/instance/adapter/TransferInstanceAdapter.java @@ -1,4 +1,4 @@ -package org.odk.share.adapters; +package org.odk.share.views.ui.instance.adapter; import android.content.Context; import android.view.LayoutInflater; @@ -9,7 +9,7 @@ import org.odk.share.R; import org.odk.share.dto.TransferInstance; -import org.odk.share.listeners.OnItemClickListener; +import org.odk.share.views.listeners.OnItemClickListener; import java.text.SimpleDateFormat; import java.util.Date; diff --git a/skunkworks_crow/src/main/java/org/odk/share/fragments/ReceivedInstancesFragment.java b/skunkworks_crow/src/main/java/org/odk/share/views/ui/instance/fragment/ReceivedInstancesFragment.java similarity index 90% rename from skunkworks_crow/src/main/java/org/odk/share/fragments/ReceivedInstancesFragment.java rename to skunkworks_crow/src/main/java/org/odk/share/views/ui/instance/fragment/ReceivedInstancesFragment.java index 98a3c67f..e98d3805 100644 --- a/skunkworks_crow/src/main/java/org/odk/share/fragments/ReceivedInstancesFragment.java +++ b/skunkworks_crow/src/main/java/org/odk/share/views/ui/instance/fragment/ReceivedInstancesFragment.java @@ -1,4 +1,4 @@ -package org.odk.share.fragments; +package org.odk.share.views.ui.instance.fragment; import android.content.Intent; import android.database.Cursor; @@ -13,11 +13,12 @@ import org.odk.collect.android.dto.Instance; import org.odk.collect.android.provider.InstanceProviderAPI; import org.odk.share.R; -import org.odk.share.activities.ReviewFormActivity; -import org.odk.share.adapters.TransferInstanceAdapter; +import org.odk.share.views.ui.common.InstanceListFragment; +import org.odk.share.views.ui.review.ReviewFormActivity; +import org.odk.share.views.ui.instance.adapter.TransferInstanceAdapter; import org.odk.share.dao.TransferDao; import org.odk.share.dto.TransferInstance; -import org.odk.share.listeners.OnItemClickListener; +import org.odk.share.views.listeners.OnItemClickListener; import java.util.ArrayList; import java.util.HashMap; @@ -32,11 +33,11 @@ import butterknife.BindView; import butterknife.ButterKnife; -import static org.odk.share.activities.MainActivity.FORM_DISPLAY_NAME; -import static org.odk.share.activities.MainActivity.FORM_ID; -import static org.odk.share.activities.MainActivity.FORM_VERSION; -import static org.odk.share.activities.ReviewFormActivity.INSTANCE_ID; -import static org.odk.share.activities.ReviewFormActivity.TRANSFER_ID; +import static org.odk.share.views.ui.main.MainActivity.FORM_DISPLAY_NAME; +import static org.odk.share.views.ui.main.MainActivity.FORM_ID; +import static org.odk.share.views.ui.main.MainActivity.FORM_VERSION; +import static org.odk.share.views.ui.review.ReviewFormActivity.INSTANCE_ID; +import static org.odk.share.views.ui.review.ReviewFormActivity.TRANSFER_ID; /** * Created by laksh on 6/27/2018. diff --git a/skunkworks_crow/src/main/java/org/odk/share/fragments/ReviewedInstancesFragment.java b/skunkworks_crow/src/main/java/org/odk/share/views/ui/instance/fragment/ReviewedInstancesFragment.java similarity index 91% rename from skunkworks_crow/src/main/java/org/odk/share/fragments/ReviewedInstancesFragment.java rename to skunkworks_crow/src/main/java/org/odk/share/views/ui/instance/fragment/ReviewedInstancesFragment.java index 4cc3b4f1..b539d834 100644 --- a/skunkworks_crow/src/main/java/org/odk/share/fragments/ReviewedInstancesFragment.java +++ b/skunkworks_crow/src/main/java/org/odk/share/views/ui/instance/fragment/ReviewedInstancesFragment.java @@ -1,4 +1,4 @@ -package org.odk.share.fragments; +package org.odk.share.views.ui.instance.fragment; import android.content.Intent; import android.database.Cursor; @@ -11,17 +11,22 @@ import android.widget.LinearLayout; import android.widget.TextView; +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + import org.odk.collect.android.dao.InstancesDao; import org.odk.collect.android.dto.Instance; import org.odk.collect.android.provider.InstanceProviderAPI; import org.odk.share.R; -import org.odk.share.activities.SendActivity; -import org.odk.share.adapters.TransferInstanceAdapter; import org.odk.share.dao.TransferDao; import org.odk.share.dto.TransferInstance; -import org.odk.share.listeners.OnItemClickListener; import org.odk.share.utilities.ApplicationConstants; import org.odk.share.utilities.ArrayUtils; +import org.odk.share.utilities.DialogUtils; +import org.odk.share.views.listeners.OnItemClickListener; +import org.odk.share.views.ui.common.InstanceListFragment; +import org.odk.share.views.ui.instance.adapter.TransferInstanceAdapter; import java.util.ArrayList; import java.util.HashMap; @@ -30,17 +35,14 @@ import javax.inject.Inject; -import androidx.annotation.NonNull; -import androidx.recyclerview.widget.LinearLayoutManager; -import androidx.recyclerview.widget.RecyclerView; import butterknife.BindView; import butterknife.ButterKnife; import butterknife.OnClick; -import static org.odk.share.activities.InstancesList.INSTANCE_IDS; -import static org.odk.share.activities.MainActivity.FORM_DISPLAY_NAME; -import static org.odk.share.activities.MainActivity.FORM_ID; -import static org.odk.share.activities.MainActivity.FORM_VERSION; +import static org.odk.share.views.ui.instance.InstancesList.INSTANCE_IDS; +import static org.odk.share.views.ui.main.MainActivity.FORM_DISPLAY_NAME; +import static org.odk.share.views.ui.main.MainActivity.FORM_ID; +import static org.odk.share.views.ui.main.MainActivity.FORM_VERSION; /** * Created by laksh on 6/27/2018. @@ -86,7 +88,6 @@ public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, instanceMap = new HashMap<>(); transferInstanceList = new ArrayList<>(); selectedInstances = new LinkedHashSet<>(); - LinearLayoutManager llm = new LinearLayoutManager(getActivity()); llm.setOrientation(RecyclerView.VERTICAL); recyclerView.setLayoutManager(llm); @@ -224,11 +225,15 @@ public void sendForms() { instanceIds.add(transferInstance.getInstanceId()); } } - Intent intent = new Intent(getContext(), SendActivity.class); + + Intent intent = new Intent(); Long[] arr = instanceIds.toArray(new Long[instanceIds.size()]); long[] a = ArrayUtils.toPrimitive(arr); intent.putExtra(INSTANCE_IDS, a); intent.putExtra(MODE, ApplicationConstants.SEND_REVIEW_MODE); - startActivity(intent); + + if (getContext() != null) { + DialogUtils.switchToDefaultSendingMethod(getContext(), intent); + } } } diff --git a/skunkworks_crow/src/main/java/org/odk/share/fragments/SentInstancesFragment.java b/skunkworks_crow/src/main/java/org/odk/share/views/ui/instance/fragment/SentInstancesFragment.java similarity index 93% rename from skunkworks_crow/src/main/java/org/odk/share/fragments/SentInstancesFragment.java rename to skunkworks_crow/src/main/java/org/odk/share/views/ui/instance/fragment/SentInstancesFragment.java index 98a488a5..18b47093 100644 --- a/skunkworks_crow/src/main/java/org/odk/share/fragments/SentInstancesFragment.java +++ b/skunkworks_crow/src/main/java/org/odk/share/views/ui/instance/fragment/SentInstancesFragment.java @@ -1,4 +1,4 @@ -package org.odk.share.fragments; +package org.odk.share.views.ui.instance.fragment; import android.database.Cursor; import android.os.Bundle; @@ -12,9 +12,10 @@ import org.odk.collect.android.dto.Instance; import org.odk.collect.android.provider.InstanceProviderAPI; import org.odk.share.R; -import org.odk.share.adapters.TransferInstanceAdapter; +import org.odk.share.views.ui.instance.adapter.TransferInstanceAdapter; import org.odk.share.dao.TransferDao; import org.odk.share.dto.TransferInstance; +import org.odk.share.views.ui.common.InstanceListFragment; import java.util.ArrayList; import java.util.HashMap; @@ -29,9 +30,9 @@ import butterknife.BindView; import butterknife.ButterKnife; -import static org.odk.share.activities.MainActivity.FORM_DISPLAY_NAME; -import static org.odk.share.activities.MainActivity.FORM_ID; -import static org.odk.share.activities.MainActivity.FORM_VERSION; +import static org.odk.share.views.ui.main.MainActivity.FORM_DISPLAY_NAME; +import static org.odk.share.views.ui.main.MainActivity.FORM_ID; +import static org.odk.share.views.ui.main.MainActivity.FORM_VERSION; /** * Created by laksh on 6/27/2018. diff --git a/skunkworks_crow/src/main/java/org/odk/share/fragments/StatisticsFragment.java b/skunkworks_crow/src/main/java/org/odk/share/views/ui/instance/fragment/StatisticsFragment.java similarity index 95% rename from skunkworks_crow/src/main/java/org/odk/share/fragments/StatisticsFragment.java rename to skunkworks_crow/src/main/java/org/odk/share/views/ui/instance/fragment/StatisticsFragment.java index 9ab3e8df..7d5211bf 100644 --- a/skunkworks_crow/src/main/java/org/odk/share/fragments/StatisticsFragment.java +++ b/skunkworks_crow/src/main/java/org/odk/share/views/ui/instance/fragment/StatisticsFragment.java @@ -1,4 +1,4 @@ -package org.odk.share.fragments; +package org.odk.share.views.ui.instance.fragment; import android.database.Cursor; import android.graphics.Typeface; @@ -22,6 +22,7 @@ import org.odk.share.R; import org.odk.share.dao.TransferDao; import org.odk.share.dto.TransferInstance; +import org.odk.share.views.ui.common.injectable.InjectableFragment; import java.util.ArrayList; import java.util.HashMap; @@ -33,9 +34,9 @@ import butterknife.BindView; import butterknife.ButterKnife; -import static org.odk.share.activities.MainActivity.FORM_DISPLAY_NAME; -import static org.odk.share.activities.MainActivity.FORM_ID; -import static org.odk.share.activities.MainActivity.FORM_VERSION; +import static org.odk.share.views.ui.main.MainActivity.FORM_DISPLAY_NAME; +import static org.odk.share.views.ui.main.MainActivity.FORM_ID; +import static org.odk.share.views.ui.main.MainActivity.FORM_VERSION; /** * Created by laksh on 6/27/2018. diff --git a/skunkworks_crow/src/main/java/org/odk/share/activities/FormListActivity.java b/skunkworks_crow/src/main/java/org/odk/share/views/ui/main/FormListActivity.java similarity index 89% rename from skunkworks_crow/src/main/java/org/odk/share/activities/FormListActivity.java rename to skunkworks_crow/src/main/java/org/odk/share/views/ui/main/FormListActivity.java index e51a8fc8..5fcb7737 100644 --- a/skunkworks_crow/src/main/java/org/odk/share/activities/FormListActivity.java +++ b/skunkworks_crow/src/main/java/org/odk/share/views/ui/main/FormListActivity.java @@ -1,14 +1,15 @@ -package org.odk.share.activities; +package org.odk.share.views.ui.main; import org.odk.collect.android.provider.FormsProviderAPI; +import org.odk.share.views.ui.common.applist.AppListActivity; import static org.odk.share.utilities.ApplicationConstants.SortingOrder.BY_DATE_ASC; import static org.odk.share.utilities.ApplicationConstants.SortingOrder.BY_DATE_DESC; import static org.odk.share.utilities.ApplicationConstants.SortingOrder.BY_NAME_ASC; import static org.odk.share.utilities.ApplicationConstants.SortingOrder.BY_NAME_DESC; -abstract class FormListActivity extends AppListActivity { +public abstract class FormListActivity extends AppListActivity { protected static final String SORT_BY_NAME_ASC = FormsProviderAPI.FormsColumns.DISPLAY_NAME + " COLLATE NOCASE ASC"; diff --git a/skunkworks_crow/src/main/java/org/odk/share/adapters/FormsAdapter.java b/skunkworks_crow/src/main/java/org/odk/share/views/ui/main/FormsAdapter.java similarity index 95% rename from skunkworks_crow/src/main/java/org/odk/share/adapters/FormsAdapter.java rename to skunkworks_crow/src/main/java/org/odk/share/views/ui/main/FormsAdapter.java index f9b973e9..a2c8bc51 100644 --- a/skunkworks_crow/src/main/java/org/odk/share/adapters/FormsAdapter.java +++ b/skunkworks_crow/src/main/java/org/odk/share/views/ui/main/FormsAdapter.java @@ -1,4 +1,4 @@ -package org.odk.share.adapters; +package org.odk.share.views.ui.main; import android.content.Context; import android.database.Cursor; @@ -15,9 +15,9 @@ import org.odk.collect.android.provider.FormsProviderAPI; import org.odk.collect.android.provider.InstanceProviderAPI; import org.odk.share.R; -import org.odk.share.adapters.basecursoradapter.BaseCursorViewHolder; -import org.odk.share.adapters.basecursoradapter.CursorRecyclerViewAdapter; -import org.odk.share.adapters.basecursoradapter.ItemClickListener; +import org.odk.share.views.ui.common.basecursor.BaseCursorViewHolder; +import org.odk.share.views.ui.common.basecursor.CursorRecyclerViewAdapter; +import org.odk.share.views.listeners.ItemClickListener; import org.odk.share.dao.TransferDao; import org.odk.share.dto.TransferInstance; @@ -73,7 +73,7 @@ public void onBindViewHolder(FormHolder viewHolder, Cursor cursor) { viewHolder.tvSubtitle.setText(sb.toString()); viewHolder.checkBox.setVisibility(selectedForms != null ? View.VISIBLE : View.GONE); viewHolder.checkBox.setChecked(selectedForms != null && selectedForms.contains(((long) form.getId()))); - viewHolder.filledicon.setImageResource(R.drawable.blank_form); + viewHolder.filledIcon.setImageResource(R.drawable.ic_blank_form); if (selectedForms == null) { String[] selectionArgs; @@ -153,7 +153,7 @@ public static class FormHolder extends BaseCursorViewHolder { @BindView(R.id.checkbox) CheckBox checkBox; @BindView(R.id.iconfilledform) - ImageView filledicon; + ImageView filledIcon; private Form form; diff --git a/skunkworks_crow/src/main/java/org/odk/share/activities/MainActivity.java b/skunkworks_crow/src/main/java/org/odk/share/views/ui/main/MainActivity.java similarity index 71% rename from skunkworks_crow/src/main/java/org/odk/share/activities/MainActivity.java rename to skunkworks_crow/src/main/java/org/odk/share/views/ui/main/MainActivity.java index 199e7ae7..20175c5b 100644 --- a/skunkworks_crow/src/main/java/org/odk/share/activities/MainActivity.java +++ b/skunkworks_crow/src/main/java/org/odk/share/views/ui/main/MainActivity.java @@ -1,6 +1,7 @@ -package org.odk.share.activities; +package org.odk.share.views.ui.main; import android.Manifest; +import android.content.ActivityNotFoundException; import android.content.DialogInterface; import android.content.Intent; import android.content.pm.PackageManager; @@ -13,33 +14,44 @@ import android.view.View; import android.widget.Button; import android.widget.TextView; +import android.widget.Toast; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.appcompat.app.AlertDialog; +import androidx.appcompat.widget.Toolbar; +import androidx.core.content.ContextCompat; +import androidx.loader.app.LoaderManager; +import androidx.loader.content.Loader; +import androidx.recyclerview.widget.DividerItemDecoration; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; import org.odk.collect.android.dao.FormsDao; import org.odk.collect.android.dao.InstancesDao; import org.odk.collect.android.dto.Form; import org.odk.share.R; -import org.odk.share.adapters.FormsAdapter; -import org.odk.share.adapters.basecursoradapter.BaseCursorViewHolder; -import org.odk.share.adapters.basecursoradapter.ItemClickListener; import org.odk.share.application.Share; import org.odk.share.dao.TransferDao; -import org.odk.share.preferences.SettingsPreference; +import org.odk.share.utilities.ActivityUtils; +import org.odk.share.utilities.DialogUtils; +import org.odk.share.utilities.PermissionUtils; +import org.odk.share.views.listeners.ItemClickListener; +import org.odk.share.views.ui.about.AboutActivity; +import org.odk.share.views.ui.common.basecursor.BaseCursorViewHolder; +import org.odk.share.views.ui.instance.InstanceManagerTabs; +import org.odk.share.views.ui.send.SendFormsActivity; +import org.odk.share.views.ui.settings.SettingsActivity; import javax.inject.Inject; -import androidx.annotation.NonNull; -import androidx.appcompat.app.AlertDialog; -import androidx.appcompat.widget.Toolbar; -import androidx.loader.app.LoaderManager; -import androidx.loader.content.Loader; -import androidx.recyclerview.widget.DividerItemDecoration; -import androidx.recyclerview.widget.LinearLayoutManager; -import androidx.recyclerview.widget.RecyclerView; import butterknife.BindView; import butterknife.ButterKnife; import butterknife.OnClick; import timber.log.Timber; +import static org.odk.share.utilities.PermissionUtils.APP_SETTING_REQUEST_CODE; + public class MainActivity extends FormListActivity implements LoaderManager.LoaderCallbacks, ItemClickListener { public static final String FORM_VERSION = "form_version"; @@ -54,7 +66,7 @@ public class MainActivity extends FormListActivity implements LoaderManager.Load Toolbar toolbar; @BindView(R.id.bSendForms) Button sendForms; - @BindView(R.id.bViewWifi) + @BindView(R.id.bReceiveForms) Button viewWifi; @BindView(R.id.recyclerview) RecyclerView recyclerView; @@ -100,16 +112,14 @@ private void setupAdapter() { recyclerView.setAdapter(formAdapter); } - @OnClick(R.id.bViewWifi) - public void viewWifiNetworks() { - Intent intent = new Intent(this, WifiActivity.class); - startActivity(intent); + @OnClick(R.id.bReceiveForms) + public void chooseReceivingMethods() { + DialogUtils.switchToDefaultReceivingMethod(this); } @OnClick(R.id.bSendForms) public void selectForms() { - Intent intent = new Intent(this, SendFormsActivity.class); - startActivity(intent); + ActivityUtils.launchActivity(this, SendFormsActivity.class, false); } @Override @@ -122,10 +132,10 @@ public boolean onCreateOptionsMenu(Menu menu) { public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case R.id.menu_settings: - startActivity(new Intent(this, SettingsPreference.class)); + ActivityUtils.launchActivity(this, SettingsActivity.class, false); return true; case R.id.menu_about: - startActivity(new Intent(this, AboutActivity.class)); + ActivityUtils.launchActivity(this, AboutActivity.class, false); } return super.onOptionsItemSelected(item); } @@ -200,22 +210,18 @@ private boolean isCollectInstalled() { private void showAlertDialog() { AlertDialog.Builder builder = new AlertDialog.Builder(this); builder.setMessage(R.string.install_collect); - builder.setPositiveButton(getString(R.string.install), new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - try { - startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("market://details?id=" + COLLECT_PACKAGE))); - } catch (android.content.ActivityNotFoundException e) { - startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("https://play.google.com/store/apps/details?id=" + COLLECT_PACKAGE))); - } + + builder.setPositiveButton(getString(R.string.install), (DialogInterface dialog, int which) -> { + try { + startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("market://details?id=" + COLLECT_PACKAGE))); + } catch (ActivityNotFoundException e) { + startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("https://play.google.com/store/apps/details?id=" + COLLECT_PACKAGE))); } }); - builder.setNegativeButton(getString(R.string.cancel), new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - dialog.dismiss(); - finish(); - } + + builder.setNegativeButton(getString(R.string.cancel), (DialogInterface dialog, int which) -> { + dialog.dismiss(); + finish(); }); builder.setCancelable(false); @@ -245,20 +251,35 @@ private void addListItemDivider() { @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { super.onRequestPermissionsResult(requestCode, permissions, grantResults); - - switch (requestCode) { - case STORAGE_PERMISSION_REQUEST_CODE: - if (grantResults[0] == PackageManager.PERMISSION_GRANTED) { - setUpLoader(); + if (requestCode == STORAGE_PERMISSION_REQUEST_CODE) { + if (grantResults[0] == PackageManager.PERMISSION_GRANTED) { + setUpLoader(); + } else { + if ((Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && + !shouldShowRequestPermissionRationale(Manifest.permission.WRITE_EXTERNAL_STORAGE))) { + PermissionUtils.showAppInfo(this, getPackageName(), getString(R.string.permission_open_storage_info), getString(R.string.permission_storage_denied)); } else { - //close the app if the permission is denied + Toast.makeText(this, getString(R.string.permission_storage_denied), Toast.LENGTH_SHORT).show(); finish(); } + } } } - private void setUpLoader() { + @Override + protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) { + super.onActivityResult(requestCode, resultCode, data); + if (requestCode == APP_SETTING_REQUEST_CODE) { + if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) + != PackageManager.PERMISSION_GRANTED) { + PermissionUtils.showAppInfo(this, getPackageName(), getString(R.string.permission_open_storage_info), getString(R.string.permission_storage_denied)); + } else { + setUpLoader(); + } + } + } + private void setUpLoader() { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M || checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) { @@ -270,9 +291,6 @@ private void setUpLoader() { } else { showAlertDialog(); } - } else { - requestPermissions(new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE , Manifest.permission.READ_EXTERNAL_STORAGE}, - STORAGE_PERMISSION_REQUEST_CODE); } } } \ No newline at end of file diff --git a/skunkworks_crow/src/main/java/org/odk/share/views/ui/receive/ScannerActivity.java b/skunkworks_crow/src/main/java/org/odk/share/views/ui/receive/ScannerActivity.java new file mode 100644 index 00000000..f5859003 --- /dev/null +++ b/skunkworks_crow/src/main/java/org/odk/share/views/ui/receive/ScannerActivity.java @@ -0,0 +1,116 @@ +package org.odk.share.views.ui.receive; + +/* + * created by Chromicle + */ + +// I got resource from one of stackoverflow project https://stackoverflow.com/questions/32731869/turn-on-off-flashlight-in-zxing-fragment-lib/43886809l + +import android.content.pm.PackageManager; +import android.os.Bundle; +import android.view.KeyEvent; +import android.view.View; +import android.view.WindowManager; +import android.widget.Button; + +import com.journeyapps.barcodescanner.CaptureManager; +import com.journeyapps.barcodescanner.DecoratedBarcodeView; + +import org.odk.share.R; + +import androidx.annotation.NonNull; +import androidx.appcompat.app.AppCompatActivity; +import butterknife.BindView; +import butterknife.ButterKnife; +import butterknife.OnClick; + + +public class ScannerActivity extends AppCompatActivity implements + DecoratedBarcodeView.TorchListener { + + @BindView(R.id.switch_flashlight) + Button toggleFlashlightButton; + @BindView(R.id.zxing_barcode_scanner) + DecoratedBarcodeView decoratedScannerView; + private CaptureManager capture; + private boolean isFlashLightOn; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_scanner); + getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN); + + ButterKnife.bind(this); + decoratedScannerView.setTorchListener(this); + + + if (!isFlashlightSupported()) { + toggleFlashlightButton.setVisibility(View.GONE); + } + + capture = new CaptureManager(this, decoratedScannerView); + capture.initializeFromIntent(getIntent(), savedInstanceState); + capture.decode(); + } + + private boolean isFlashlightSupported() { + return getApplicationContext().getPackageManager() + .hasSystemFeature(PackageManager.FEATURE_CAMERA_FLASH); + } + + public void toggleFlashlight() { + if (isFlashLightOn) { + decoratedScannerView.setTorchOff(); + isFlashLightOn = false; + } else { + decoratedScannerView.setTorchOn(); + isFlashLightOn = true; + } + } + + @Override + public void onTorchOn() { + toggleFlashlightButton.setBackgroundResource(R.drawable.ic_flash_white_on); + } + + @Override + public void onTorchOff() { + toggleFlashlightButton.setBackgroundResource(R.drawable.ic_flash_white_off); + } + + @Override + protected void onResume() { + super.onResume(); + capture.onResume(); + } + + @Override + protected void onPause() { + super.onPause(); + capture.onPause(); + } + + @Override + protected void onDestroy() { + super.onDestroy(); + capture.onDestroy(); + } + + @Override + protected void onSaveInstanceState(@NonNull Bundle outState) { + super.onSaveInstanceState(outState); + capture.onSaveInstanceState(outState); + } + + @OnClick(R.id.switch_flashlight) + public void toggleButton() { + toggleFlashlight(); + } + + @Override + public boolean onKeyDown(int keyCode, KeyEvent event) { + return decoratedScannerView.onKeyDown(keyCode, event) || super.onKeyDown(keyCode, event); + } + +} \ No newline at end of file diff --git a/skunkworks_crow/src/main/java/org/odk/share/activities/ReviewFormActivity.java b/skunkworks_crow/src/main/java/org/odk/share/views/ui/review/ReviewFormActivity.java similarity index 95% rename from skunkworks_crow/src/main/java/org/odk/share/activities/ReviewFormActivity.java rename to skunkworks_crow/src/main/java/org/odk/share/views/ui/review/ReviewFormActivity.java index eccb3aec..97b4fc21 100644 --- a/skunkworks_crow/src/main/java/org/odk/share/activities/ReviewFormActivity.java +++ b/skunkworks_crow/src/main/java/org/odk/share/views/ui/review/ReviewFormActivity.java @@ -1,4 +1,4 @@ -package org.odk.share.activities; +package org.odk.share.views.ui.review; import android.content.ContentValues; import android.content.Intent; @@ -10,11 +10,14 @@ import android.widget.TextView; import android.widget.Toast; +import androidx.appcompat.widget.Toolbar; + import org.odk.collect.android.dao.InstancesDao; import org.odk.collect.android.provider.InstanceProviderAPI; import org.odk.share.R; import org.odk.share.dao.TransferDao; import org.odk.share.dto.TransferInstance; +import org.odk.share.views.ui.common.injectable.InjectableActivity; import javax.inject.Inject; @@ -38,6 +41,8 @@ public class ReviewFormActivity extends InjectableActivity { TextView description; @BindView(R.id.save_feedback) EditText feedback; + @BindView(R.id.toolbar) + Toolbar toolbar; @Inject InstancesDao instancesDao; @@ -58,6 +63,9 @@ protected void onCreate(Bundle savedInstanceState) { setContentView(R.layout.activity_review_form); ButterKnife.bind(this); + setTitle(getString(R.string.review_form)); + setSupportActionBar(toolbar); + transferID = getIntent().getLongExtra(TRANSFER_ID, -1); instanceID = getIntent().getLongExtra(INSTANCE_ID, -1); diff --git a/skunkworks_crow/src/main/java/org/odk/share/activities/SendFormsActivity.java b/skunkworks_crow/src/main/java/org/odk/share/views/ui/send/SendFormsActivity.java similarity index 80% rename from skunkworks_crow/src/main/java/org/odk/share/activities/SendFormsActivity.java rename to skunkworks_crow/src/main/java/org/odk/share/views/ui/send/SendFormsActivity.java index 32b67768..392aace2 100644 --- a/skunkworks_crow/src/main/java/org/odk/share/activities/SendFormsActivity.java +++ b/skunkworks_crow/src/main/java/org/odk/share/views/ui/send/SendFormsActivity.java @@ -1,13 +1,14 @@ -package org.odk.share.activities; +package org.odk.share.views.ui.send; import android.os.Bundle; import com.google.android.material.tabs.TabLayout; import org.odk.share.R; -import org.odk.share.adapters.ViewPagerAdapter; -import org.odk.share.fragments.BlankFormsFragment; -import org.odk.share.fragments.FilledFormsFragment; +import org.odk.share.views.ui.common.ViewPagerAdapter; +import org.odk.share.views.ui.common.injectable.InjectableActivity; +import org.odk.share.views.ui.send.fragment.BlankFormsFragment; +import org.odk.share.views.ui.send.fragment.FilledFormsFragment; import androidx.appcompat.widget.Toolbar; import androidx.viewpager.widget.ViewPager; diff --git a/skunkworks_crow/src/main/java/org/odk/share/fragments/BlankFormsFragment.java b/skunkworks_crow/src/main/java/org/odk/share/views/ui/send/fragment/BlankFormsFragment.java similarity index 91% rename from skunkworks_crow/src/main/java/org/odk/share/fragments/BlankFormsFragment.java rename to skunkworks_crow/src/main/java/org/odk/share/views/ui/send/fragment/BlankFormsFragment.java index 9d43bb5c..02434240 100644 --- a/skunkworks_crow/src/main/java/org/odk/share/fragments/BlankFormsFragment.java +++ b/skunkworks_crow/src/main/java/org/odk/share/views/ui/send/fragment/BlankFormsFragment.java @@ -1,6 +1,6 @@ -package org.odk.share.fragments; +package org.odk.share.views.ui.send.fragment; import android.content.Intent; import android.database.Cursor; @@ -12,33 +12,34 @@ import android.widget.LinearLayout; import android.widget.TextView; +import androidx.annotation.NonNull; +import androidx.loader.app.LoaderManager; +import androidx.loader.content.Loader; +import androidx.recyclerview.widget.DividerItemDecoration; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + import org.odk.collect.android.dao.FormsDao; import org.odk.collect.android.dao.InstancesDao; import org.odk.collect.android.provider.FormsProviderAPI; import org.odk.share.R; -import org.odk.share.activities.SendActivity; -import org.odk.share.adapters.FormsAdapter; -import org.odk.share.adapters.basecursoradapter.BaseCursorViewHolder; -import org.odk.share.adapters.basecursoradapter.ItemClickListener; import org.odk.share.dao.TransferDao; import org.odk.share.utilities.ApplicationConstants; import org.odk.share.utilities.ArrayUtils; +import org.odk.share.utilities.DialogUtils; +import org.odk.share.views.listeners.ItemClickListener; +import org.odk.share.views.ui.common.basecursor.BaseCursorViewHolder; +import org.odk.share.views.ui.main.FormsAdapter; import java.util.LinkedHashSet; import javax.inject.Inject; -import androidx.annotation.NonNull; -import androidx.loader.app.LoaderManager; -import androidx.loader.content.Loader; -import androidx.recyclerview.widget.DividerItemDecoration; -import androidx.recyclerview.widget.LinearLayoutManager; -import androidx.recyclerview.widget.RecyclerView; import butterknife.BindView; import butterknife.ButterKnife; import butterknife.OnClick; -import static org.odk.share.fragments.ReviewedInstancesFragment.MODE; +import static org.odk.share.views.ui.instance.fragment.ReviewedInstancesFragment.MODE; /** * Created by laksh on 10/29/2018. @@ -156,13 +157,18 @@ private void setEmptyViewVisibility(int len) { @OnClick(R.id.send_button) public void send() { - Intent intent = new Intent(getActivity(), SendActivity.class); + if (getContext() != null) { + Intent intent = new Intent(); + setupSendingIntent(intent); + DialogUtils.switchToDefaultSendingMethod(getContext(), intent); + } + } + + private void setupSendingIntent(Intent intent) { Long[] arr = selectedForms.toArray(new Long[selectedForms.size()]); long[] a = ArrayUtils.toPrimitive(arr); intent.putExtra(FORM_IDS, a); intent.putExtra(MODE, ApplicationConstants.SEND_BLANK_FORM_MODE); - startActivity(intent); - getActivity().finish(); } @OnClick(R.id.toggle_button) diff --git a/skunkworks_crow/src/main/java/org/odk/share/fragments/FilledFormsFragment.java b/skunkworks_crow/src/main/java/org/odk/share/views/ui/send/fragment/FilledFormsFragment.java similarity index 92% rename from skunkworks_crow/src/main/java/org/odk/share/fragments/FilledFormsFragment.java rename to skunkworks_crow/src/main/java/org/odk/share/views/ui/send/fragment/FilledFormsFragment.java index 4ffb7183..c6b25e4f 100644 --- a/skunkworks_crow/src/main/java/org/odk/share/fragments/FilledFormsFragment.java +++ b/skunkworks_crow/src/main/java/org/odk/share/views/ui/send/fragment/FilledFormsFragment.java @@ -1,4 +1,4 @@ -package org.odk.share.fragments; +package org.odk.share.views.ui.send.fragment; import android.content.Intent; import android.database.Cursor; @@ -11,29 +11,31 @@ import android.widget.LinearLayout; import android.widget.TextView; +import androidx.annotation.NonNull; +import androidx.loader.app.LoaderManager; +import androidx.loader.content.Loader; +import androidx.recyclerview.widget.DividerItemDecoration; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + import org.odk.collect.android.dao.InstancesDao; import org.odk.collect.android.provider.InstanceProviderAPI; import org.odk.share.R; -import org.odk.share.activities.SendActivity; -import org.odk.share.adapters.InstanceAdapter; import org.odk.share.utilities.ApplicationConstants; import org.odk.share.utilities.ArrayUtils; +import org.odk.share.utilities.DialogUtils; +import org.odk.share.views.ui.common.InstanceListFragment; +import org.odk.share.views.ui.instance.adapter.InstanceAdapter; import java.util.LinkedHashSet; import javax.inject.Inject; -import androidx.annotation.NonNull; -import androidx.loader.app.LoaderManager; -import androidx.loader.content.Loader; -import androidx.recyclerview.widget.DividerItemDecoration; -import androidx.recyclerview.widget.LinearLayoutManager; -import androidx.recyclerview.widget.RecyclerView; import butterknife.BindView; import butterknife.ButterKnife; import butterknife.OnClick; -import static org.odk.share.fragments.ReviewedInstancesFragment.MODE; +import static org.odk.share.views.ui.instance.fragment.ReviewedInstancesFragment.MODE; /** * Created by laksh on 10/29/2018. @@ -149,13 +151,18 @@ private void setEmptyViewVisibility(int len) { @OnClick(R.id.send_button) public void send() { - Intent intent = new Intent(getActivity(), SendActivity.class); + if (getContext() != null) { + Intent intent = new Intent(); + setupSendingIntent(intent); + DialogUtils.switchToDefaultSendingMethod(getContext(), intent); + } + } + + private void setupSendingIntent(Intent intent) { Long[] arr = selectedInstances.toArray(new Long[selectedInstances.size()]); long[] a = ArrayUtils.toPrimitive(arr); intent.putExtra(INSTANCE_IDS, a); intent.putExtra(MODE, ApplicationConstants.ASK_REVIEW_MODE); - startActivity(intent); - getActivity().finish(); } @OnClick(R.id.toggle_button) diff --git a/skunkworks_crow/src/main/java/org/odk/share/fragments/FormListFragment.java b/skunkworks_crow/src/main/java/org/odk/share/views/ui/send/fragment/FormListFragment.java similarity index 93% rename from skunkworks_crow/src/main/java/org/odk/share/fragments/FormListFragment.java rename to skunkworks_crow/src/main/java/org/odk/share/views/ui/send/fragment/FormListFragment.java index d4b8f682..08dd720f 100644 --- a/skunkworks_crow/src/main/java/org/odk/share/fragments/FormListFragment.java +++ b/skunkworks_crow/src/main/java/org/odk/share/views/ui/send/fragment/FormListFragment.java @@ -1,6 +1,7 @@ -package org.odk.share.fragments; +package org.odk.share.views.ui.send.fragment; import org.odk.collect.android.provider.FormsProviderAPI; +import org.odk.share.views.ui.common.applist.AppListFragment; import static org.odk.share.utilities.ApplicationConstants.SortingOrder.BY_DATE_ASC; import static org.odk.share.utilities.ApplicationConstants.SortingOrder.BY_DATE_DESC; diff --git a/skunkworks_crow/src/main/java/org/odk/share/preferences/PreferenceKeys.java b/skunkworks_crow/src/main/java/org/odk/share/views/ui/settings/PreferenceKeys.java similarity index 59% rename from skunkworks_crow/src/main/java/org/odk/share/preferences/PreferenceKeys.java rename to skunkworks_crow/src/main/java/org/odk/share/views/ui/settings/PreferenceKeys.java index b972fcd5..8d026d51 100644 --- a/skunkworks_crow/src/main/java/org/odk/share/preferences/PreferenceKeys.java +++ b/skunkworks_crow/src/main/java/org/odk/share/views/ui/settings/PreferenceKeys.java @@ -1,4 +1,4 @@ -package org.odk.share.preferences; +package org.odk.share.views.ui.settings; /** * Created by laksh on 5/28/2018. @@ -10,6 +10,9 @@ public class PreferenceKeys { public static final String KEY_HOTSPOT_PASSWORD = "hotspot_password"; public static final String KEY_HOTSPOT_PWD_REQUIRE = "hotspot_pwd_require"; public static final String KEY_ODK_DESTINATION_DIR = "odk_destination_dir"; + public static final String KEY_DEFAULT_TRANSFER_METHOD = "default_transfer_method"; + public static final String KEY_BLUETOOTH_NAME = "bluetooth_name"; + public static final String KEY_BLUETOOTH_SECURE_MODE = "bluetooth_secure_mode"; private PreferenceKeys() { diff --git a/skunkworks_crow/src/main/java/org/odk/share/preferences/SettingsPreference.java b/skunkworks_crow/src/main/java/org/odk/share/views/ui/settings/SettingsActivity.java similarity index 51% rename from skunkworks_crow/src/main/java/org/odk/share/preferences/SettingsPreference.java rename to skunkworks_crow/src/main/java/org/odk/share/views/ui/settings/SettingsActivity.java index d54c7a9d..dd9d517b 100644 --- a/skunkworks_crow/src/main/java/org/odk/share/preferences/SettingsPreference.java +++ b/skunkworks_crow/src/main/java/org/odk/share/views/ui/settings/SettingsActivity.java @@ -1,33 +1,45 @@ -package org.odk.share.preferences; +package org.odk.share.views.ui.settings; +import android.bluetooth.BluetoothAdapter; import android.content.SharedPreferences; import android.os.Build; import android.os.Bundle; import android.preference.CheckBoxPreference; import android.preference.EditTextPreference; +import android.preference.ListPreference; import android.preference.Preference; import android.preference.PreferenceActivity; import android.preference.PreferenceManager; +import android.text.TextUtils; +import android.view.LayoutInflater; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; +import android.view.WindowManager; import android.widget.Toast; -import org.odk.share.R; - +import androidx.appcompat.app.AlertDialog; import androidx.appcompat.widget.Toolbar; +import com.google.android.material.textfield.TextInputLayout; + +import org.odk.share.R; + /** * Created by laksh on 5/27/2018. */ -public class SettingsPreference extends PreferenceActivity { +public class SettingsActivity extends PreferenceActivity { EditTextPreference hotspotNamePreference; - EditTextPreference hotspotPasswordPreference; + EditTextPreference bluetoothNamePreference; + Preference hotspotPasswordPreference; CheckBoxPreference passwordRequirePreference; + CheckBoxPreference btSecureModePreference; EditTextPreference odkDestinationDirPreference; + ListPreference defaultMethodPreference; + private SharedPreferences prefs; @Override protected void onCreate(Bundle savedInstanceState) { @@ -50,26 +62,52 @@ public boolean onOptionsItemSelected(MenuItem item) { } private void addPreferences() { + defaultMethodPreference = (ListPreference) findPreference(PreferenceKeys.KEY_DEFAULT_TRANSFER_METHOD); hotspotNamePreference = (EditTextPreference) findPreference(PreferenceKeys.KEY_HOTSPOT_NAME); - hotspotPasswordPreference = (EditTextPreference) findPreference(PreferenceKeys.KEY_HOTSPOT_PASSWORD); + bluetoothNamePreference = (EditTextPreference) findPreference(PreferenceKeys.KEY_BLUETOOTH_NAME); + hotspotPasswordPreference = findPreference(PreferenceKeys.KEY_HOTSPOT_PASSWORD); passwordRequirePreference = (CheckBoxPreference) findPreference(PreferenceKeys.KEY_HOTSPOT_PWD_REQUIRE); + btSecureModePreference = (CheckBoxPreference) findPreference(PreferenceKeys.KEY_BLUETOOTH_SECURE_MODE); odkDestinationDirPreference = (EditTextPreference) findPreference(PreferenceKeys.KEY_ODK_DESTINATION_DIR); - SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getApplicationContext()); + prefs = PreferenceManager.getDefaultSharedPreferences(getApplicationContext()); + defaultMethodPreference.setSummary(prefs.getString(PreferenceKeys.KEY_DEFAULT_TRANSFER_METHOD, + getString(R.string.default_hotspot_ssid))); hotspotNamePreference.setSummary(prefs.getString(PreferenceKeys.KEY_HOTSPOT_NAME, getString(R.string.default_hotspot_ssid))); + String defaultBluetoothName = BluetoothAdapter.getDefaultAdapter().getName(); + bluetoothNamePreference.setText(defaultBluetoothName); + bluetoothNamePreference.setDefaultValue(defaultBluetoothName); + bluetoothNamePreference.setSummary(prefs.getString(PreferenceKeys.KEY_BLUETOOTH_NAME, defaultBluetoothName)); boolean isPasswordSet = prefs.getBoolean(PreferenceKeys.KEY_HOTSPOT_PWD_REQUIRE, false); odkDestinationDirPreference.setSummary(prefs.getString(PreferenceKeys.KEY_ODK_DESTINATION_DIR, getString(R.string.default_odk_destination_dir))); + boolean isSecureMode = prefs.getBoolean(PreferenceKeys.KEY_BLUETOOTH_SECURE_MODE, true); hotspotPasswordPreference.setEnabled(isPasswordSet); passwordRequirePreference.setChecked(isPasswordSet); + btSecureModePreference.setChecked(isSecureMode); hotspotNamePreference.setOnPreferenceChangeListener(preferenceChangeListener()); + bluetoothNamePreference.setOnPreferenceChangeListener(preferenceChangeListener()); hotspotPasswordPreference.setOnPreferenceChangeListener(preferenceChangeListener()); passwordRequirePreference.setOnPreferenceChangeListener(preferenceChangeListener()); odkDestinationDirPreference.setOnPreferenceChangeListener(preferenceChangeListener()); + defaultMethodPreference.setOnPreferenceChangeListener(preferenceChangeListener()); + + hotspotPasswordPreference.setOnPreferenceClickListener(preferenceClickListener()); + } + + private Preference.OnPreferenceClickListener preferenceClickListener() { + return preference -> { + switch (preference.getKey()) { + case PreferenceKeys.KEY_HOTSPOT_PASSWORD: + showPasswordDialog(); + break; + } + return false; + }; } private Preference.OnPreferenceChangeListener preferenceChangeListener() { @@ -84,6 +122,17 @@ private Preference.OnPreferenceChangeListener preferenceChangeListener() { hotspotNamePreference.setSummary(name); } break; + case PreferenceKeys.KEY_BLUETOOTH_NAME: + String bluetoothName = newValue.toString(); + if (bluetoothName.length() == 0) { + Toast.makeText(getBaseContext(), getString(R.string.bluetooth_name_error), Toast.LENGTH_LONG).show(); + return false; + } else { + bluetoothNamePreference.setSummary(bluetoothName); + BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); + bluetoothAdapter.setName(bluetoothName); + } + break; case PreferenceKeys.KEY_HOTSPOT_PASSWORD: String password = newValue.toString(); if (password.length() < 8) { @@ -108,6 +157,12 @@ private Preference.OnPreferenceChangeListener preferenceChangeListener() { odkDestinationDirPreference.setSummary(dir); } break; + case PreferenceKeys.KEY_DEFAULT_TRANSFER_METHOD: + String method = newValue.toString(); + if (!TextUtils.isEmpty(method)) { + defaultMethodPreference.setSummary(method); + } + break; } return true; }; @@ -121,4 +176,26 @@ private ViewGroup getRootView() { } } -} \ No newline at end of file + private void showPasswordDialog() { + AlertDialog.Builder builder = new AlertDialog.Builder(this); + LayoutInflater factory = LayoutInflater.from(this); + + View dialogView = factory.inflate(R.layout.dialog_password_til, null); + TextInputLayout tlPassword = dialogView.findViewById(R.id.et_password_layout); + tlPassword.getEditText().setText(prefs.getString(PreferenceKeys.KEY_HOTSPOT_PASSWORD, getString(R.string.default_hotspot_password))); + + builder.setTitle(getString(R.string.title_hotspot_password)); + builder.setView(dialogView); + builder.setPositiveButton(getString(R.string.ok), (dialog, which) -> { + String password = tlPassword.getEditText().getText().toString(); + prefs.edit().putString(PreferenceKeys.KEY_HOTSPOT_PASSWORD, password).apply(); + }); + builder.setNegativeButton(getString(R.string.cancel), (dialog, which) -> dialog.dismiss()); + + builder.setCancelable(false); + AlertDialog alertDialog = builder.create(); + alertDialog.show(); + alertDialog.setCancelable(true); + alertDialog.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE); + } +} diff --git a/skunkworks_crow/src/main/res/drawable/blank_form.xml b/skunkworks_crow/src/main/res/drawable/ic_blank_form.xml similarity index 100% rename from skunkworks_crow/src/main/res/drawable/blank_form.xml rename to skunkworks_crow/src/main/res/drawable/ic_blank_form.xml diff --git a/skunkworks_crow/src/main/res/drawable/ic_bluetooth_black_24dp.xml b/skunkworks_crow/src/main/res/drawable/ic_bluetooth_black_24dp.xml new file mode 100644 index 00000000..1094756b --- /dev/null +++ b/skunkworks_crow/src/main/res/drawable/ic_bluetooth_black_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/skunkworks_crow/src/main/res/drawable/ic_bluetooth_logo.xml b/skunkworks_crow/src/main/res/drawable/ic_bluetooth_logo.xml new file mode 100644 index 00000000..8d0ff0e9 --- /dev/null +++ b/skunkworks_crow/src/main/res/drawable/ic_bluetooth_logo.xml @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + diff --git a/skunkworks_crow/src/main/res/drawable/ic_bluetooth_white_24dp.xml b/skunkworks_crow/src/main/res/drawable/ic_bluetooth_white_24dp.xml new file mode 100644 index 00000000..243bf696 --- /dev/null +++ b/skunkworks_crow/src/main/res/drawable/ic_bluetooth_white_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/skunkworks_crow/src/main/res/drawable/ic_compare_arrows_black_24dp.xml b/skunkworks_crow/src/main/res/drawable/ic_compare_arrows_black_24dp.xml new file mode 100644 index 00000000..23417034 --- /dev/null +++ b/skunkworks_crow/src/main/res/drawable/ic_compare_arrows_black_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/skunkworks_crow/src/main/res/drawable/ic_compare_arrows_white_24dp.xml b/skunkworks_crow/src/main/res/drawable/ic_compare_arrows_white_24dp.xml new file mode 100644 index 00000000..20b2c2a6 --- /dev/null +++ b/skunkworks_crow/src/main/res/drawable/ic_compare_arrows_white_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/skunkworks_crow/src/main/res/drawable/ic_flash_white_off.xml b/skunkworks_crow/src/main/res/drawable/ic_flash_white_off.xml new file mode 100644 index 00000000..e0a6bc32 --- /dev/null +++ b/skunkworks_crow/src/main/res/drawable/ic_flash_white_off.xml @@ -0,0 +1,9 @@ + + + \ No newline at end of file diff --git a/skunkworks_crow/src/main/res/drawable/ic_flash_white_on.xml b/skunkworks_crow/src/main/res/drawable/ic_flash_white_on.xml new file mode 100644 index 00000000..92c9fcc5 --- /dev/null +++ b/skunkworks_crow/src/main/res/drawable/ic_flash_white_on.xml @@ -0,0 +1,9 @@ + + + \ No newline at end of file diff --git a/skunkworks_crow/src/main/res/drawable/ic_no_devices_found.xml b/skunkworks_crow/src/main/res/drawable/ic_no_devices_found.xml new file mode 100644 index 00000000..f283a4b5 --- /dev/null +++ b/skunkworks_crow/src/main/res/drawable/ic_no_devices_found.xml @@ -0,0 +1,13 @@ + + + + + diff --git a/skunkworks_crow/src/main/res/drawable/ic_security_black_24dp.xml b/skunkworks_crow/src/main/res/drawable/ic_security_black_24dp.xml new file mode 100644 index 00000000..91864239 --- /dev/null +++ b/skunkworks_crow/src/main/res/drawable/ic_security_black_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/skunkworks_crow/src/main/res/drawable/ic_smart_phone_black.xml b/skunkworks_crow/src/main/res/drawable/ic_smart_phone_black.xml new file mode 100644 index 00000000..477d05e1 --- /dev/null +++ b/skunkworks_crow/src/main/res/drawable/ic_smart_phone_black.xml @@ -0,0 +1,6 @@ + + + + + diff --git a/skunkworks_crow/src/main/res/drawable/ic_smart_phone_yellow.xml b/skunkworks_crow/src/main/res/drawable/ic_smart_phone_yellow.xml new file mode 100644 index 00000000..9fb99dcc --- /dev/null +++ b/skunkworks_crow/src/main/res/drawable/ic_smart_phone_yellow.xml @@ -0,0 +1,6 @@ + + + + + diff --git a/skunkworks_crow/src/main/res/drawable/ic_wifi_tethering_white_24dp.xml b/skunkworks_crow/src/main/res/drawable/ic_wifi_tethering_white_24dp.xml new file mode 100644 index 00000000..72beeaf4 --- /dev/null +++ b/skunkworks_crow/src/main/res/drawable/ic_wifi_tethering_white_24dp.xml @@ -0,0 +1,5 @@ + + + diff --git a/skunkworks_crow/src/main/res/layout/about_list_item.xml b/skunkworks_crow/src/main/res/layout/about_list_item.xml index 692b28c4..daedd9da 100644 --- a/skunkworks_crow/src/main/res/layout/about_list_item.xml +++ b/skunkworks_crow/src/main/res/layout/about_list_item.xml @@ -1,20 +1,17 @@ diff --git a/skunkworks_crow/src/main/res/layout/activity_about.xml b/skunkworks_crow/src/main/res/layout/activity_about.xml index 41801ba5..449bcdc9 100644 --- a/skunkworks_crow/src/main/res/layout/activity_about.xml +++ b/skunkworks_crow/src/main/res/layout/activity_about.xml @@ -1,6 +1,5 @@ @@ -8,7 +7,6 @@ + style="@style/MatchParent" + android:layout_below="@id/toolbar" /> \ No newline at end of file diff --git a/skunkworks_crow/src/main/res/layout/activity_bt_receive.xml b/skunkworks_crow/src/main/res/layout/activity_bt_receive.xml new file mode 100644 index 00000000..b90a285c --- /dev/null +++ b/skunkworks_crow/src/main/res/layout/activity_bt_receive.xml @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + +