├── .gitignore
├── .idea
├── caches
│ └── build_file_checksums.ser
├── codeStyles
│ └── Project.xml
├── misc.xml
├── modules.xml
├── runConfigurations.xml
└── vcs.xml
├── CHANGELOG
├── LICENSE
├── README.md
├── add_to_app_manifest.xml
├── bintray_upload_v1.gradle
├── build.gradle
├── devicesetup
├── .gitignore
├── build.gradle
├── lint.xml
├── proguard-rules.pro
└── src
│ ├── androidTest
│ └── java
│ │ └── io
│ │ └── particle
│ │ └── sdk
│ │ └── library
│ │ └── ApplicationTest.java
│ └── main
│ ├── AndroidManifest.xml
│ ├── assets
│ └── fonts
│ │ ├── roboto_light.ttf
│ │ ├── roboto_medium.ttf
│ │ ├── roboto_regular.ttf
│ │ └── roboto_thin_italic.ttf
│ ├── java
│ └── io
│ │ └── particle
│ │ └── android
│ │ └── sdk
│ │ ├── accountsetup
│ │ ├── CreateAccountActivity.java
│ │ ├── LoginActivity.java
│ │ ├── PasswordResetActivity.java
│ │ └── TwoFactorActivity.java
│ │ ├── devicesetup
│ │ ├── ApConnector.java
│ │ ├── ParticleDeviceSetupLibrary.java
│ │ ├── SetupCompleteIntentBuilder.java
│ │ ├── SetupProcessException.java
│ │ ├── SetupResult.java
│ │ ├── SimpleReceiver.java
│ │ ├── commands
│ │ │ ├── Command.java
│ │ │ ├── CommandClient.java
│ │ │ ├── CommandClientFactory.java
│ │ │ ├── CommandClientUtils.java
│ │ │ ├── ConfigureApCommand.java
│ │ │ ├── ConnectAPCommand.java
│ │ │ ├── DeviceIdCommand.java
│ │ │ ├── NetworkBindingSocketFactory.java
│ │ │ ├── NoArgsCommand.java
│ │ │ ├── PublicKeyCommand.java
│ │ │ ├── ScanApCommand.java
│ │ │ ├── SetCommand.java
│ │ │ ├── VersionCommand.java
│ │ │ └── data
│ │ │ │ └── WifiSecurity.java
│ │ ├── loaders
│ │ │ ├── ScanApCommandLoader.java
│ │ │ └── WifiScanResultLoader.java
│ │ ├── model
│ │ │ ├── ScanAPCommandResult.java
│ │ │ ├── ScanResultNetwork.java
│ │ │ └── WifiNetwork.java
│ │ ├── setupsteps
│ │ │ ├── CheckIfDeviceClaimedStep.java
│ │ │ ├── ConfigureAPStep.java
│ │ │ ├── ConnectDeviceToNetworkStep.java
│ │ │ ├── EnsureSoftApNotVisible.java
│ │ │ ├── SetupStep.java
│ │ │ ├── SetupStepApReconnector.java
│ │ │ ├── SetupStepException.java
│ │ │ ├── SetupStepsFactory.java
│ │ │ ├── SetupStepsRunnerTask.java
│ │ │ ├── StepConfig.java
│ │ │ ├── StepProgress.java
│ │ │ ├── WaitForCloudConnectivityStep.java
│ │ │ └── WaitForDisconnectionFromDeviceStep.java
│ │ └── ui
│ │ │ ├── ConnectToApFragment.java
│ │ │ ├── ConnectingActivity.java
│ │ │ ├── ConnectingProcessWorkerTask.java
│ │ │ ├── DeviceSetupState.java
│ │ │ ├── DiscoverDeviceActivity.java
│ │ │ ├── DiscoverProcessWorker.java
│ │ │ ├── GetReadyActivity.java
│ │ │ ├── ManualNetworkEntryActivity.java
│ │ │ ├── PasswordEntryActivity.java
│ │ │ ├── PermissionsFragment.java
│ │ │ ├── RequiresWifiScansActivity.java
│ │ │ ├── SelectNetworkActivity.java
│ │ │ ├── SuccessActivity.java
│ │ │ └── WifiListFragment.java
│ │ ├── di
│ │ ├── ActivityInjectorComponent.java
│ │ ├── ApModule.java
│ │ ├── ApplicationComponent.java
│ │ ├── ApplicationModule.java
│ │ ├── CloudModule.java
│ │ └── PerActivity.java
│ │ ├── ui
│ │ ├── BaseActivity.java
│ │ └── NextActivitySelector.java
│ │ └── utils
│ │ ├── BetterAsyncTaskLoader.java
│ │ ├── CoreNameGenerator.java
│ │ ├── Crypto.java
│ │ ├── ParticleDeviceSetupInternalStringUtils.java
│ │ ├── SEGAnalytics.java
│ │ ├── SSID.java
│ │ ├── SoftAPConfigRemover.java
│ │ ├── WifiFacade.java
│ │ ├── WorkerFragment.java
│ │ └── ui
│ │ ├── ParticleUi.java
│ │ ├── SoftKeyboardVisibilityDetectingLinearLayout.java
│ │ ├── Toaster.java
│ │ ├── Ui.java
│ │ └── WebViewActivity.java
│ └── res
│ ├── drawable-hdpi
│ └── ic_clear_black_24dp.png
│ ├── drawable-mdpi
│ └── ic_clear_black_24dp.png
│ ├── drawable-xhdpi
│ ├── checkmark.png
│ └── ic_clear_black_24dp.png
│ ├── drawable-xxhdpi
│ ├── fail.png
│ ├── ic_clear_black_24dp.png
│ ├── particle_vertical_blue.png
│ ├── photon_vector.png
│ ├── photon_vector_small.png
│ ├── success.png
│ ├── the_wifi.png
│ └── trianglifybackground.png
│ ├── drawable-xxxhdpi
│ ├── ic_clear_black_24dp.png
│ ├── lock.png
│ ├── particle_horizontal_blue.png
│ └── particle_horizontal_head.png
│ ├── drawable
│ ├── button_text_color_selector.xml
│ ├── link_text_selector.xml
│ ├── progress_indicator_graphic.png
│ └── progress_spinner.xml
│ ├── layout-xlarge
│ └── activity_connecting.xml
│ ├── layout
│ ├── activity_connecting.xml
│ ├── activity_create_account.xml
│ ├── activity_discover_device.xml
│ ├── activity_get_ready.xml
│ ├── activity_manual_network_entry.xml
│ ├── activity_password_entry.xml
│ ├── activity_password_reset.xml
│ ├── activity_select_network.xml
│ ├── activity_success.xml
│ ├── activity_two_factor.xml
│ ├── activity_web_view.xml
│ ├── brand_image_header.xml
│ ├── particle_activity_login.xml
│ └── row_wifi_scan_result.xml
│ ├── values-sw820dp
│ └── dimens.xml
│ ├── values-v19
│ ├── dimens.xml
│ └── themes.xml
│ ├── values-v21
│ └── themes.xml
│ ├── values-xlarge
│ └── dimens_font_size.xml
│ └── values
│ ├── colors.xml
│ ├── customization.xml
│ ├── devicesetup_styles.xml
│ ├── dimens.xml
│ ├── dimens_font_size.xml
│ ├── ids.xml
│ ├── strings.xml
│ ├── strings_activity_login.xml
│ ├── styles.xml
│ ├── success_failure_messages.xml
│ └── themes.xml
├── exampleapp
├── build.gradle
├── lint.xml
├── proguard-rules.pro
└── src
│ └── main
│ ├── AndroidManifest.xml
│ ├── java
│ └── io
│ │ └── particle
│ │ └── devicesetup
│ │ └── exampleapp
│ │ ├── ExampleSetupCompleteIntentBuilder.java
│ │ └── MainActivity.java
│ └── res
│ ├── layout
│ └── activity_main.xml
│ ├── menu
│ └── menu_main.xml
│ ├── mipmap-hdpi
│ └── ic_launcher.png
│ ├── mipmap-mdpi
│ └── ic_launcher.png
│ ├── mipmap-xhdpi
│ └── ic_launcher.png
│ ├── mipmap-xxhdpi
│ └── ic_launcher.png
│ ├── values-w820dp
│ └── dimens.xml
│ └── values
│ ├── dimens.xml
│ ├── strings.xml
│ └── styles.xml
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── pom_generator_v1.gradle
├── settings.gradle
└── testapp
├── .gitignore
├── build.gradle
├── proguard-rules.pro
└── src
├── androidTest
└── java
│ └── io
│ └── particle
│ └── devicesetup
│ └── testapp
│ ├── ApplicationTest.java
│ ├── CustomAndroidTestRunner.java
│ ├── CustomApplication.java
│ ├── EspressoDaggerMockRule.java
│ └── accountsetup
│ └── SetupFlowTest.java
├── main
├── AndroidManifest.xml
├── java
│ └── io
│ │ └── particle
│ │ └── devicesetup
│ │ └── testapp
│ │ └── MainActivity.java
└── res
│ ├── layout
│ └── activity_main.xml
│ ├── mipmap-hdpi
│ ├── ic_launcher.png
│ └── ic_launcher_round.png
│ ├── mipmap-mdpi
│ ├── ic_launcher.png
│ └── ic_launcher_round.png
│ ├── mipmap-xhdpi
│ ├── ic_launcher.png
│ └── ic_launcher_round.png
│ ├── mipmap-xxhdpi
│ ├── ic_launcher.png
│ └── ic_launcher_round.png
│ ├── mipmap-xxxhdpi
│ ├── ic_launcher.png
│ └── ic_launcher_round.png
│ └── values
│ ├── colors.xml
│ ├── strings.xml
│ └── styles.xml
└── test
└── java
└── io
└── particle
└── devicesetup
└── testapp
└── ExampleUnitTest.java
/.gitignore:
--------------------------------------------------------------------------------
1 | # Built application files
2 | *.apk
3 | *.ap_
4 |
5 | # Files for the Dalvik VM
6 | *.dex
7 |
8 | # Java class files
9 | *.class
10 |
11 | # Generated files
12 | bin/
13 | gen/
14 |
15 | # Gradle files
16 | .gradle/
17 | build/
18 | /*/build/
19 |
20 | # Local configuration file (sdk path, etc)
21 | local.properties
22 | # this file shouldn't be anywhere under the root of this repo in the first place, but
23 | # better safe than sorry, in case of copy/paste accidents, etc.
24 | bintray_user_auth_secrets.properties
25 |
26 | # Proguard folder generated by Eclipse
27 | proguard/
28 |
29 | # Log Files
30 | *.log
31 |
32 |
33 | # IntelliJ project files that shouldn't be checked in
34 | /.idea/workspace.xml
35 | /.idea/tasks.xml
36 | /.idea/gradle.xml
37 | /.idea/libraries
38 | *.iml
39 | .idea/inspectionProfiles/Project_Default.xml
40 | .idea/inspectionProfiles/profiles_settings.xml
41 | .idea/qaplug_profiles.xml
42 |
43 | # OS X noise
44 | .DS_Store
45 |
46 |
--------------------------------------------------------------------------------
/.idea/caches/build_file_checksums.ser:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/particle-iot/spark-setup-android/60290536c1be65b78ac8ef0d475999eb468a24c0/.idea/caches/build_file_checksums.ser
--------------------------------------------------------------------------------
/.idea/codeStyles/Project.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
17 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/.idea/runConfigurations.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
11 |
12 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Particle Device Setup library
2 |
3 | ## MOVED!
4 |
5 | The Device Setup Library has moved to [the new Particle Android repository!](https://github.com/particle-iot/particle-android) This repository is now deprecated and will not be updated.
6 |
7 |
--------------------------------------------------------------------------------
/add_to_app_manifest.xml:
--------------------------------------------------------------------------------
1 |
7 |
13 |
19 |
25 |
31 |
36 |
41 |
47 |
53 |
59 |
65 |
--------------------------------------------------------------------------------
/bintray_upload_v1.gradle:
--------------------------------------------------------------------------------
1 | // lifted from https://raw.githubusercontent.com/nuuneoi/JCenter/master/bintrayv1.gradle
2 | // and copied into this repo to maintain a hermetic build
3 | apply plugin: 'com.jfrog.bintray'
4 |
5 | version = libraryVersion
6 |
7 | task sourcesJar(type: Jar) {
8 | from android.sourceSets.main.java.srcDirs
9 | classifier = 'sources'
10 | }
11 |
12 | task javadoc(type: Javadoc) {
13 | source = android.sourceSets.main.java.srcDirs
14 | classpath += project.files(android.getBootClasspath().join(File.pathSeparator))
15 | }
16 |
17 | task javadocJar(type: Jar, dependsOn: javadoc) {
18 | classifier = 'javadoc'
19 | from javadoc.destinationDir
20 | }
21 |
22 | artifacts {
23 | archives javadocJar
24 | archives sourcesJar
25 | }
26 |
27 | // FIXME: this feels hackish, but it works for now, and it shouldn't
28 | // have any side effects* for anyone building the lib locally,
29 | // so #SHIPIT
30 | //
31 | // * My apologies if this turns out not to be true. Patches welcome!
32 | Properties authDataProps = new Properties()
33 | try {
34 | authDataProps.load(project.rootProject.file('../bintray_user_auth_secrets.properties').newDataInputStream())
35 | } catch (Exception ignore) {
36 | // do nothing; this is the default state for everyone who isn't publishing
37 | // the lib.
38 | }
39 |
40 | bintray {
41 | user = authDataProps.getProperty("bintray.user")
42 | key = authDataProps.getProperty("bintray.apikey")
43 |
44 | configurations = ['archives']
45 | pkg {
46 | userOrg = bintrayOrg
47 | repo = bintrayRepo
48 | name = bintrayName
49 | desc = libraryDescription
50 | websiteUrl = siteUrl
51 | vcsUrl = gitUrl
52 | licenses = allLicenses
53 | publish = true
54 | publicDownloadNumbers = false
55 | version {
56 | // desc = libraryDescription
57 | gpg {
58 | sign = false // Determines whether to GPG sign the files. The default is false
59 | // passphrase = properties.getProperty("bintray.gpg.password") // Optional. The passphrase for GPG signing'
60 | }
61 | }
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 |
3 | buildscript {
4 | repositories {
5 | google()
6 | jcenter()
7 | }
8 |
9 | dependencies {
10 | classpath 'com.android.tools.build:gradle:3.1.4'
11 | classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.8.4'
12 | classpath 'com.github.dcendents:android-maven-gradle-plugin:2.0'
13 | classpath 'com.jakewharton:butterknife-gradle-plugin:8.4.0'
14 | }
15 | }
16 |
17 |
18 | allprojects {
19 | repositories {
20 | google()
21 | jcenter()
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/devicesetup/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/devicesetup/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.library'
2 | apply plugin: 'com.jakewharton.butterknife'
3 |
4 | // This is the library version used when deploying the artifact
5 | version = '0.6.3'
6 |
7 | ext {
8 | bintrayRepo = 'android'
9 | bintrayName = 'devicesetup'
10 | bintrayOrg = 'particle'
11 |
12 | publishedGroupId = 'io.particle'
13 | libraryName = 'Particle (formerly Spark) Android Device Setup library'
14 | artifact = 'devicesetup'
15 |
16 | libraryDescription = "The Particle Device Setup library provides everything you need to " +
17 | "offer your users a simple initial setup process for Particle-powered devices. This " +
18 | "includes all the necessary device communication code, an easily customizable UI, and " +
19 | "a simple developer API."
20 |
21 | siteUrl = 'https://github.com/spark/spark-setup-android'
22 | gitUrl = 'https://github.com/spark/spark-setup-android.git'
23 |
24 | libraryVersion = project.version
25 |
26 | developerId = 'idok'
27 | developerName = 'Ido Kleinman'
28 | developerEmail = 'ido@particle.io'
29 |
30 | licenseName = 'The Apache Software License, Version 2.0'
31 | licenseUrl = 'http://www.apache.org/licenses/LICENSE-2.0.txt'
32 | allLicenses = ["Apache-2.0"]
33 | }
34 |
35 |
36 | android {
37 | compileSdkVersion 27
38 | buildToolsVersion '27.0.3'
39 |
40 | dexOptions {
41 | javaMaxHeapSize "2g"
42 | }
43 |
44 | defaultConfig {
45 | minSdkVersion 15
46 | targetSdkVersion 27
47 | versionCode 1
48 | versionName "1.0"
49 | }
50 |
51 | compileOptions {
52 | sourceCompatibility JavaVersion.VERSION_1_8
53 | targetCompatibility JavaVersion.VERSION_1_8
54 | }
55 |
56 | buildTypes {
57 | release {
58 | minifyEnabled false
59 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
60 | }
61 | }
62 |
63 | lintOptions {
64 | abortOnError true
65 | }
66 |
67 | }
68 |
69 | // TESTING ONLY: to build against a locally built version of the cloud SDK, uncomment these
70 | // lines, and the "compile(name:'cloudsdk', ext:'aar')" line below under dependencies.
71 | // (If you don't know what this means or why we (the SDK maintainers at Particle) would want to
72 | // do this, then you can safely ignore all this and keep it commented out. :)
73 | //repositories {
74 | // flatDir {
75 | // dirs 'libs'
76 | // }
77 | //}
78 |
79 |
80 | dependencies {
81 | api fileTree(include: ['*.jar'], dir: 'libs')
82 |
83 | api 'io.particle:cloudsdk:0.5.1'
84 |
85 | api 'com.squareup.phrase:phrase:1.0.3'
86 | api 'uk.co.chrisjenx:calligraphy:2.3.0'
87 | api 'com.segment.analytics.android:analytics:4.3.1'
88 | api 'com.madgag.spongycastle:core:1.58.0.0'
89 |
90 | api 'com.google.dagger:dagger:2.15'
91 | annotationProcessor 'com.google.dagger:dagger-compiler:2.15'
92 | api 'com.jakewharton:butterknife:8.8.1'
93 | annotationProcessor 'com.jakewharton:butterknife-compiler:8.8.1'
94 |
95 | api 'com.android.support:appcompat-v7:27.1.1'
96 | api 'com.android.support:recyclerview-v7:27.1.1'
97 |
98 | // TESTING ONLY (see other TESTING comments further up)
99 | // compile(name: 'cloudsdk', ext: 'aar')
100 | }
101 |
102 | apply from: '../pom_generator_v1.gradle'
103 | apply from: '../bintray_upload_v1.gradle'
104 |
105 | // disable insane, build-breaking doclint tool in Java 8
106 | if (JavaVersion.current().isJava8Compatible()) {
107 | tasks.withType(Javadoc) {
108 | //noinspection SpellCheckingInspection
109 | options.addStringOption('Xdoclint:none', '-quiet')
110 | }
111 | }
112 |
--------------------------------------------------------------------------------
/devicesetup/lint.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/devicesetup/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # By default, the flags in this file are appended to flags specified
3 | # in /home/jensck/Android/Sdk/tools/proguard/proguard-android.txt
4 | # You can edit the include path and order by changing the proguardFiles
5 | # directive in build.gradle.
6 | #
7 | # For more details, see
8 | # http://developer.android.com/guide/developing/tools/proguard.html
9 |
10 | # Add any project specific keep options here:
11 |
12 | # If your project uses WebView with JS, uncomment the following
13 | # and specify the fully qualified class name to the JavaScript interface
14 | # class:
15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
16 | # public *;
17 | #}
18 |
--------------------------------------------------------------------------------
/devicesetup/src/androidTest/java/io/particle/sdk/library/ApplicationTest.java:
--------------------------------------------------------------------------------
1 | package io.particle.sdk.library;
2 |
3 | import android.app.Application;
4 | import android.test.ApplicationTestCase;
5 |
6 | /**
7 | * Testing Fundamentals
8 | */
9 | public class ApplicationTest extends ApplicationTestCase {
10 | public ApplicationTest() {
11 | super(Application.class);
12 | }
13 | }
--------------------------------------------------------------------------------
/devicesetup/src/main/assets/fonts/roboto_light.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/particle-iot/spark-setup-android/60290536c1be65b78ac8ef0d475999eb468a24c0/devicesetup/src/main/assets/fonts/roboto_light.ttf
--------------------------------------------------------------------------------
/devicesetup/src/main/assets/fonts/roboto_medium.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/particle-iot/spark-setup-android/60290536c1be65b78ac8ef0d475999eb468a24c0/devicesetup/src/main/assets/fonts/roboto_medium.ttf
--------------------------------------------------------------------------------
/devicesetup/src/main/assets/fonts/roboto_regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/particle-iot/spark-setup-android/60290536c1be65b78ac8ef0d475999eb468a24c0/devicesetup/src/main/assets/fonts/roboto_regular.ttf
--------------------------------------------------------------------------------
/devicesetup/src/main/assets/fonts/roboto_thin_italic.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/particle-iot/spark-setup-android/60290536c1be65b78ac8ef0d475999eb468a24c0/devicesetup/src/main/assets/fonts/roboto_thin_italic.ttf
--------------------------------------------------------------------------------
/devicesetup/src/main/java/io/particle/android/sdk/devicesetup/SetupCompleteIntentBuilder.java:
--------------------------------------------------------------------------------
1 | package io.particle.android.sdk.devicesetup;
2 |
3 | import android.content.Context;
4 | import android.content.Intent;
5 | import android.support.annotation.Nullable;
6 |
7 | public interface SetupCompleteIntentBuilder {
8 | Intent buildIntent(Context ctx, @Nullable SetupResult result);
9 | }
10 |
--------------------------------------------------------------------------------
/devicesetup/src/main/java/io/particle/android/sdk/devicesetup/SetupProcessException.java:
--------------------------------------------------------------------------------
1 | package io.particle.android.sdk.devicesetup;
2 |
3 |
4 | import io.particle.android.sdk.devicesetup.setupsteps.SetupStep;
5 |
6 | public class SetupProcessException extends Exception {
7 |
8 | public final SetupStep failedStep;
9 |
10 | public SetupProcessException(String msg, SetupStep failedStep) {
11 | super(msg);
12 | this.failedStep = failedStep;
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/devicesetup/src/main/java/io/particle/android/sdk/devicesetup/SetupResult.java:
--------------------------------------------------------------------------------
1 | package io.particle.android.sdk.devicesetup;
2 |
3 | import android.os.Parcel;
4 | import android.os.Parcelable;
5 |
6 | public class SetupResult implements Parcelable {
7 | private final boolean wasSuccessful;
8 | private final String configuredDeviceId;
9 |
10 | public SetupResult(boolean wasSuccessful, String configuredDeviceId) {
11 | this.wasSuccessful = wasSuccessful;
12 | this.configuredDeviceId = configuredDeviceId;
13 | }
14 |
15 | public boolean wasSuccessful() {
16 | return wasSuccessful;
17 | }
18 |
19 | public String getConfiguredDeviceId() {
20 | return configuredDeviceId;
21 | }
22 |
23 | @Override
24 | public int describeContents() {
25 | return 0;
26 | }
27 |
28 | @Override
29 | public void writeToParcel(Parcel dest, int flags) {
30 | dest.writeInt(wasSuccessful ? 1 : 0);
31 | dest.writeString(configuredDeviceId);
32 | }
33 |
34 | public static final Parcelable.Creator CREATOR = new Parcelable.Creator() {
35 | @Override
36 | public SetupResult createFromParcel(Parcel source) {
37 | return new SetupResult(source);
38 | }
39 |
40 | @Override
41 | public SetupResult[] newArray(int size) {
42 | return new SetupResult[size];
43 | }
44 | };
45 |
46 | private SetupResult(Parcel source) {
47 | wasSuccessful = source.readInt() == 1;
48 | configuredDeviceId = source.readString();
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/devicesetup/src/main/java/io/particle/android/sdk/devicesetup/SimpleReceiver.java:
--------------------------------------------------------------------------------
1 | package io.particle.android.sdk.devicesetup;
2 |
3 |
4 | import android.content.BroadcastReceiver;
5 | import android.content.Context;
6 | import android.content.Intent;
7 | import android.content.IntentFilter;
8 |
9 |
10 | public class SimpleReceiver extends BroadcastReceiver {
11 |
12 | public interface LambdafiableBroadcastReceiver {
13 | void onReceive(Context context, Intent intent);
14 | }
15 |
16 |
17 | public static SimpleReceiver newReceiver(Context ctx, IntentFilter intentFilter,
18 | LambdafiableBroadcastReceiver receiver) {
19 | return new SimpleReceiver(ctx, intentFilter, receiver);
20 | }
21 |
22 |
23 | public static SimpleReceiver newRegisteredReceiver(Context ctx, IntentFilter intentFilter,
24 | LambdafiableBroadcastReceiver receiver) {
25 | SimpleReceiver sr = new SimpleReceiver(ctx, intentFilter, receiver);
26 | sr.register();
27 | return sr;
28 | }
29 |
30 |
31 | private final Context appContext;
32 | private final IntentFilter intentFilter;
33 | private final LambdafiableBroadcastReceiver receiver;
34 |
35 | private boolean registered = false;
36 |
37 | private SimpleReceiver(Context ctx, IntentFilter intentFilter,
38 | LambdafiableBroadcastReceiver receiver) {
39 | this.appContext = ctx.getApplicationContext();
40 | this.intentFilter = intentFilter;
41 | this.receiver = receiver;
42 | }
43 |
44 | @Override
45 | public void onReceive(Context context, Intent intent) {
46 | receiver.onReceive(context, intent);
47 | }
48 |
49 | public void register() {
50 | if (registered) {
51 | return;
52 | }
53 | appContext.registerReceiver(this, intentFilter);
54 | registered = true;
55 | }
56 |
57 | public void unregister() {
58 | if (!registered) {
59 | return;
60 | }
61 | appContext.unregisterReceiver(this);
62 | registered = false;
63 | }
64 |
65 | }
66 |
--------------------------------------------------------------------------------
/devicesetup/src/main/java/io/particle/android/sdk/devicesetup/commands/Command.java:
--------------------------------------------------------------------------------
1 | package io.particle.android.sdk.devicesetup.commands;
2 |
3 | import com.google.gson.Gson;
4 |
5 |
6 | public abstract class Command {
7 |
8 | public abstract String getCommandName();
9 |
10 | // override if you want a different implementation
11 | public String argsAsJsonString(Gson gson) {
12 | return gson.toJson(this);
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/devicesetup/src/main/java/io/particle/android/sdk/devicesetup/commands/CommandClient.java:
--------------------------------------------------------------------------------
1 | package io.particle.android.sdk.devicesetup.commands;
2 |
3 | import android.support.annotation.CheckResult;
4 |
5 | import com.google.gson.Gson;
6 |
7 | import java.io.IOException;
8 | import java.net.Socket;
9 | import java.util.concurrent.TimeUnit;
10 |
11 | import javax.net.SocketFactory;
12 |
13 | import io.particle.android.sdk.utils.EZ;
14 | import io.particle.android.sdk.utils.TLog;
15 | import okio.BufferedSink;
16 | import okio.BufferedSource;
17 | import okio.Okio;
18 |
19 | import static io.particle.android.sdk.utils.Py.truthy;
20 |
21 |
22 | public class CommandClient {
23 | static final int DEFAULT_TIMEOUT_SECONDS = 10;
24 |
25 | private static final TLog log = TLog.get(CommandClient.class);
26 | private static final Gson gson = new Gson();
27 |
28 | private final String ipAddress;
29 | private final int port;
30 | private final SocketFactory socketFactory;
31 |
32 | CommandClient(String ipAddress, int port, SocketFactory socketFactory) {
33 | this.ipAddress = ipAddress;
34 | this.port = port;
35 | this.socketFactory = socketFactory;
36 | }
37 |
38 | public void sendCommand(Command command) throws IOException {
39 | sendAndMaybeReceive(command, Void.class);
40 | }
41 |
42 | @CheckResult
43 | public T sendCommand(Command command, Class responseType) throws IOException {
44 | return sendAndMaybeReceive(command, responseType);
45 | }
46 |
47 |
48 | private T sendAndMaybeReceive(Command command, Class responseType) throws IOException {
49 | log.i("Preparing to send command '" + command.getCommandName() + "'");
50 | String commandData = buildCommandData(command);
51 |
52 | BufferedSink buffer = null;
53 | try {
54 | // send command
55 | Socket socket = socketFactory.createSocket(ipAddress, port);
56 | buffer = wrapSocket(socket, DEFAULT_TIMEOUT_SECONDS);
57 | log.d("Writing command data");
58 | buffer.writeUtf8(commandData);
59 | buffer.flush();
60 |
61 | // if no response defined, just exit early.
62 | if (responseType.equals(Void.class)) {
63 | log.d("Done.");
64 | return null;
65 | }
66 |
67 | return readResponse(socket, responseType, DEFAULT_TIMEOUT_SECONDS);
68 |
69 | } finally {
70 | EZ.closeThisThingOrMaybeDont(buffer);
71 | }
72 | }
73 |
74 | private BufferedSink wrapSocket(Socket socket, int timeoutValueInSeconds) throws IOException {
75 | BufferedSink sink = Okio.buffer(Okio.sink(socket));
76 | sink.timeout().timeout(timeoutValueInSeconds, TimeUnit.SECONDS);
77 | return sink;
78 | }
79 |
80 | private String buildCommandData(Command command) {
81 | StringBuilder commandData = new StringBuilder()
82 | .append(command.getCommandName())
83 | .append("\n");
84 |
85 | String commandArgs = command.argsAsJsonString(gson);
86 | if (truthy(commandArgs)) {
87 | commandData.append(commandArgs.length());
88 | commandData.append("\n\n");
89 | commandData.append(commandArgs);
90 | } else {
91 | commandData.append("0\n\n");
92 | }
93 |
94 | String built = commandData.toString();
95 | log.i("*** BUILT COMMAND DATA: '" + CommandClientUtils.escapeJava(built) + "'");
96 | return built;
97 | }
98 |
99 | private T readResponse(Socket socket, Class responseType, int timeoutValueInSeconds)
100 | throws IOException {
101 | BufferedSource buffer = Okio.buffer(Okio.source(socket));
102 | buffer.timeout().timeout(timeoutValueInSeconds, TimeUnit.SECONDS);
103 |
104 | log.d("Reading response data...");
105 | String line;
106 | do {
107 | // read (and throw away, for now) any headers
108 | line = buffer.readUtf8LineStrict();
109 | } while (truthy(line));
110 |
111 | String responseData = buffer.readUtf8();
112 | log.d("Command response (raw): " + CommandClientUtils.escapeJava(responseData));
113 | T tee = gson.fromJson(responseData, responseType);
114 | log.d("Command response: " + tee);
115 | EZ.closeThisThingOrMaybeDont(buffer);
116 | return tee;
117 | }
118 |
119 | }
120 |
--------------------------------------------------------------------------------
/devicesetup/src/main/java/io/particle/android/sdk/devicesetup/commands/CommandClientFactory.java:
--------------------------------------------------------------------------------
1 | package io.particle.android.sdk.devicesetup.commands;
2 |
3 | import io.particle.android.sdk.utils.SSID;
4 | import io.particle.android.sdk.utils.WifiFacade;
5 |
6 | import static io.particle.android.sdk.devicesetup.commands.CommandClient.DEFAULT_TIMEOUT_SECONDS;
7 |
8 | public class CommandClientFactory {
9 |
10 | public CommandClient newClient(WifiFacade wifiFacade, SSID softApSSID, String ipAddress, int port) {
11 | return new CommandClient(ipAddress, port,
12 | new NetworkBindingSocketFactory(wifiFacade, softApSSID, DEFAULT_TIMEOUT_SECONDS * 1000));
13 | }
14 |
15 | // FIXME: set these defaults in a resource file?
16 | public CommandClient newClientUsingDefaultsForDevices(WifiFacade wifiFacade, SSID softApSSID) {
17 | return newClient(wifiFacade, softApSSID, "192.168.0.1", 5609);
18 | }
19 |
20 | }
21 |
--------------------------------------------------------------------------------
/devicesetup/src/main/java/io/particle/android/sdk/devicesetup/commands/ConfigureApCommand.java:
--------------------------------------------------------------------------------
1 | package io.particle.android.sdk.devicesetup.commands;
2 |
3 | import com.google.gson.annotations.SerializedName;
4 |
5 | import io.particle.android.sdk.devicesetup.commands.data.WifiSecurity;
6 |
7 | import static io.particle.android.sdk.utils.Py.all;
8 | import static io.particle.android.sdk.utils.Py.truthy;
9 |
10 |
11 | /**
12 | * Configure the access point details to connect to when connect-ap is called. The AP doesn't have
13 | * to be in the list from scan-ap, allowing manual entry of hidden networks.
14 | */
15 | public class ConfigureApCommand extends Command {
16 |
17 | public final Integer idx;
18 |
19 | public final String ssid;
20 |
21 | @SerializedName("pwd")
22 | public final String encryptedPasswordHex;
23 |
24 | @SerializedName("sec")
25 | public final Integer wifiSecurityType;
26 |
27 | @SerializedName("ch")
28 | public final Integer channel;
29 |
30 | public static Builder newBuilder() {
31 | return new Builder();
32 | }
33 |
34 | @Override
35 | public String getCommandName() {
36 | return "configure-ap";
37 | }
38 |
39 | // private constructor -- use .newBuilder() instead.
40 | private ConfigureApCommand(int idx, String ssid, String encryptedPasswordHex,
41 | WifiSecurity wifiSecurityType, int channel) {
42 | this.idx = idx;
43 | this.ssid = ssid;
44 | this.encryptedPasswordHex = encryptedPasswordHex;
45 | this.wifiSecurityType = wifiSecurityType.asInt();
46 | this.channel = channel;
47 | }
48 |
49 |
50 | public static class Response {
51 |
52 | @SerializedName("r")
53 | public final Integer responseCode; // 0 == OK, non-zero == problem with index/data
54 |
55 | public Response(Integer responseCode) {
56 | this.responseCode = responseCode;
57 | }
58 |
59 | // FIXME: do this for the other ones with just the "responseCode" field
60 | public boolean isOk() {
61 | return responseCode == 0;
62 | }
63 |
64 | @Override
65 | public String toString() {
66 | return "Response{" +
67 | "responseCode=" + responseCode +
68 | '}';
69 | }
70 | }
71 |
72 |
73 | public static class Builder {
74 | private Integer idx;
75 | private String ssid;
76 | private String encryptedPasswordHex;
77 | private WifiSecurity securityType;
78 | private Integer channel;
79 |
80 | public Builder setIdx(int idx) {
81 | this.idx = idx;
82 | return this;
83 | }
84 |
85 | public Builder setSsid(String ssid) {
86 | this.ssid = ssid;
87 | return this;
88 | }
89 |
90 | public Builder setEncryptedPasswordHex(String encryptedPasswordHex) {
91 | this.encryptedPasswordHex = encryptedPasswordHex;
92 | return this;
93 | }
94 |
95 | public Builder setSecurityType(WifiSecurity securityType) {
96 | this.securityType = securityType;
97 | return this;
98 | }
99 |
100 | public Builder setChannel(int channel) {
101 | this.channel = channel;
102 | return this;
103 | }
104 |
105 | public ConfigureApCommand build() {
106 | if (!all(ssid, securityType)
107 | || (truthy(encryptedPasswordHex) && securityType == WifiSecurity.OPEN)) {
108 | throw new IllegalArgumentException(
109 | "One or more required arguments was not set on ConfigureApCommand");
110 | }
111 | if (idx == null) {
112 | idx = 0;
113 | }
114 | return new ConfigureApCommand(idx, ssid, encryptedPasswordHex, securityType, channel);
115 | }
116 | }
117 |
118 | }
119 |
--------------------------------------------------------------------------------
/devicesetup/src/main/java/io/particle/android/sdk/devicesetup/commands/ConnectAPCommand.java:
--------------------------------------------------------------------------------
1 | package io.particle.android.sdk.devicesetup.commands;
2 |
3 | import com.google.gson.annotations.SerializedName;
4 |
5 | /**
6 | * Connects to an AP previously configured with configure-ap. This disconnects the soft-ap after
7 | * the response code has been sent. Note that the response code doesn't indicate successful
8 | * connection to the AP, but only that the command was acknowledged and the AP will be
9 | * connected to after the result is sent to the client.
10 | *
11 | * If the AP connection is unsuccessful, the soft-AP will be reinstated so the user can enter
12 | * new credentials/try again.
13 | */
14 | public class ConnectAPCommand extends Command {
15 |
16 | @SerializedName("idx")
17 | public final int index;
18 |
19 | public ConnectAPCommand(int index) {
20 | this.index = index;
21 | }
22 |
23 | @Override
24 | public String getCommandName() {
25 | return "connect-ap";
26 | }
27 |
28 |
29 | public static class Response {
30 |
31 | @SerializedName("r")
32 | public final int responseCode; // 0 == OK, non-zero == problem with index/data
33 |
34 | public Response(int responseCode) {
35 | this.responseCode = responseCode;
36 | }
37 |
38 | public boolean isOK() {
39 | return responseCode == 0;
40 | }
41 |
42 | @Override
43 | public String toString() {
44 | return "Response{" +
45 | "responseCode=" + responseCode +
46 | '}';
47 | }
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/devicesetup/src/main/java/io/particle/android/sdk/devicesetup/commands/DeviceIdCommand.java:
--------------------------------------------------------------------------------
1 | package io.particle.android.sdk.devicesetup.commands;
2 |
3 | import com.google.gson.annotations.SerializedName;
4 |
5 | /**
6 | * Retrieves the unique device ID as a 24-digit hex string
7 | */
8 | public class DeviceIdCommand extends NoArgsCommand {
9 |
10 |
11 | @Override
12 | public String getCommandName() {
13 | return "device-id";
14 | }
15 |
16 |
17 | public static class Response {
18 |
19 | @SerializedName("id")
20 | public final String deviceIdHex;
21 |
22 | @SerializedName("c")
23 | public final int isClaimed;
24 |
25 |
26 | public Response(String deviceIdHex, int isClaimed) {
27 | this.deviceIdHex = deviceIdHex;
28 | this.isClaimed = isClaimed;
29 | }
30 |
31 | @Override
32 | public String toString() {
33 | return "Response{" +
34 | "deviceIdHex='" + deviceIdHex + '\'' +
35 | ", isClaimed=" + isClaimed +
36 | '}';
37 | }
38 | }
39 |
40 | }
41 |
--------------------------------------------------------------------------------
/devicesetup/src/main/java/io/particle/android/sdk/devicesetup/commands/NetworkBindingSocketFactory.java:
--------------------------------------------------------------------------------
1 | package io.particle.android.sdk.devicesetup.commands;
2 |
3 | import android.annotation.TargetApi;
4 | import android.net.Network;
5 | import android.os.Build.VERSION;
6 | import android.os.Build.VERSION_CODES;
7 |
8 | import java.io.IOException;
9 | import java.net.InetAddress;
10 | import java.net.InetSocketAddress;
11 | import java.net.Socket;
12 | import java.net.UnknownHostException;
13 |
14 | import javax.net.SocketFactory;
15 |
16 | import io.particle.android.sdk.utils.SSID;
17 | import io.particle.android.sdk.utils.TLog;
18 | import io.particle.android.sdk.utils.WifiFacade;
19 |
20 | /**
21 | * Factory for Sockets which binds communication to a particular {@link android.net.Network}
22 | */
23 | public class NetworkBindingSocketFactory extends SocketFactory {
24 |
25 | private static final TLog log = TLog.get(NetworkBindingSocketFactory.class);
26 |
27 | private final WifiFacade wifiFacade;
28 | private final SSID softAPSSID;
29 | // used as connection timeout and read timeout
30 | private final int timeoutMillis;
31 |
32 | public NetworkBindingSocketFactory(WifiFacade wifiFacade, SSID softAPSSID, int timeoutMillis) {
33 | this.wifiFacade = wifiFacade;
34 | this.softAPSSID = softAPSSID;
35 | this.timeoutMillis = timeoutMillis;
36 | }
37 |
38 | @Override
39 | public Socket createSocket() throws IOException {
40 | return buildSocket();
41 | }
42 |
43 | @Override
44 | public Socket createSocket(String host, int port) throws IOException, UnknownHostException {
45 | Socket socket = buildSocket();
46 | socket.connect(new InetSocketAddress(host, port), timeoutMillis);
47 | return socket;
48 | }
49 |
50 | @Override
51 | public Socket createSocket(String host, int port, InetAddress localHost, int localPort)
52 | throws IOException, UnknownHostException {
53 | throw new UnsupportedOperationException(
54 | "Specifying a localHost or localPort arg is not supported.");
55 | }
56 |
57 | @Override
58 | public Socket createSocket(InetAddress host, int port) throws IOException {
59 | Socket socket = buildSocket();
60 | socket.connect(new InetSocketAddress(host, port), timeoutMillis);
61 | return socket;
62 | }
63 |
64 | @Override
65 | public Socket createSocket(InetAddress address, int port, InetAddress localAddress,
66 | int localPort) throws IOException {
67 | throw new UnsupportedOperationException(
68 | "Specifying a localHost or localPort arg is not supported.");
69 | }
70 |
71 |
72 | private Socket buildSocket() throws IOException {
73 | Socket socket = new Socket();
74 | socket.setSoTimeout(timeoutMillis);
75 |
76 | if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) {
77 | bindSocketToSoftAp(socket);
78 | }
79 |
80 | return socket;
81 | }
82 |
83 | @TargetApi(VERSION_CODES.LOLLIPOP)
84 | private void bindSocketToSoftAp(Socket socket) throws IOException {
85 | Network softAp = wifiFacade.getNetworkObjectForCurrentWifiConnection();
86 |
87 | if (softAp == null) {
88 | // If this ever fails, fail VERY LOUDLY to make sure we hear about it...
89 | // FIXME: report this error via analytics
90 | throw new SocketBindingException("Could not find Network for SSID " + softAPSSID);
91 | }
92 |
93 | softAp.bindSocket(socket);
94 | }
95 |
96 |
97 | private static class SocketBindingException extends IOException {
98 |
99 | SocketBindingException(String msg) {
100 | super(msg);
101 | }
102 | }
103 |
104 | }
105 |
--------------------------------------------------------------------------------
/devicesetup/src/main/java/io/particle/android/sdk/devicesetup/commands/NoArgsCommand.java:
--------------------------------------------------------------------------------
1 | package io.particle.android.sdk.devicesetup.commands;
2 |
3 | import com.google.gson.Gson;
4 |
5 | /**
6 | * Convenience class for commands with no argument data
7 | */
8 | public abstract class NoArgsCommand extends Command {
9 |
10 | @Override
11 | public String argsAsJsonString(Gson gson) {
12 | // this command has no argument data
13 | return null;
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/devicesetup/src/main/java/io/particle/android/sdk/devicesetup/commands/PublicKeyCommand.java:
--------------------------------------------------------------------------------
1 | package io.particle.android.sdk.devicesetup.commands;
2 |
3 | import com.google.gson.annotations.SerializedName;
4 |
5 | public class PublicKeyCommand extends NoArgsCommand {
6 |
7 | @Override
8 | public String getCommandName() {
9 | return "public-key";
10 | }
11 |
12 |
13 | public static class Response {
14 |
15 | @SerializedName("r")
16 | public final int responseCode;
17 |
18 | // Hex-encoded public key, in DER format
19 | @SerializedName("b")
20 | public final String publicKey;
21 |
22 | public Response(int responseCode, String publicKey) {
23 | this.responseCode = responseCode;
24 | this.publicKey = publicKey;
25 | }
26 | }
27 |
28 | }
29 |
--------------------------------------------------------------------------------
/devicesetup/src/main/java/io/particle/android/sdk/devicesetup/commands/ScanApCommand.java:
--------------------------------------------------------------------------------
1 | package io.particle.android.sdk.devicesetup.commands;
2 |
3 |
4 | import com.google.gson.annotations.SerializedName;
5 |
6 | import java.util.Arrays;
7 | import java.util.List;
8 |
9 | public class ScanApCommand extends NoArgsCommand {
10 |
11 | @Override
12 | public String getCommandName() {
13 | return "scan-ap";
14 | }
15 |
16 |
17 | public static class Response {
18 |
19 | // using an array here instead of a generic
20 | // collection makes Gson usage simpler
21 | public final Scan[] scans;
22 |
23 | public Response(Scan[] scans) {
24 | this.scans = scans;
25 | }
26 |
27 | public List getScans() {
28 | return Arrays.asList(scans);
29 | }
30 |
31 | @Override
32 | public String toString() {
33 | return "Response{" +
34 | "scans=" + Arrays.toString(scans) +
35 | '}';
36 | }
37 | }
38 |
39 |
40 | public static class Scan {
41 |
42 | public final String ssid;
43 |
44 | @SerializedName("sec")
45 | public final Integer wifiSecurityType;
46 |
47 | @SerializedName("ch")
48 | public final Integer channel;
49 |
50 | public Scan(String ssid, Integer wifiSecurityType, Integer channel) {
51 | this.ssid = ssid;
52 | this.wifiSecurityType = wifiSecurityType;
53 | this.channel = channel;
54 | }
55 |
56 | @Override
57 | public String toString() {
58 | return "Scan{" +
59 | "ssid='" + ssid + '\'' +
60 | ", wifiSecurityType=" + wifiSecurityType +
61 | ", channel=" + channel +
62 | '}';
63 | }
64 | }
65 |
66 | }
67 |
--------------------------------------------------------------------------------
/devicesetup/src/main/java/io/particle/android/sdk/devicesetup/commands/SetCommand.java:
--------------------------------------------------------------------------------
1 | package io.particle.android.sdk.devicesetup.commands;
2 |
3 | import com.google.gson.annotations.SerializedName;
4 |
5 | import io.particle.android.sdk.utils.Preconditions;
6 |
7 |
8 | public class SetCommand extends Command {
9 |
10 | @SerializedName("k")
11 | public final String key;
12 |
13 | @SerializedName("v")
14 | public final String value;
15 |
16 | public SetCommand(String key, String value) {
17 | Preconditions.checkNotNull(key, "Key cannot be null");
18 | Preconditions.checkNotNull(value, "Value cannot be null");
19 | this.key = key;
20 | this.value = value;
21 | }
22 |
23 | @Override
24 | public String getCommandName() {
25 | return "set";
26 | }
27 |
28 |
29 | public static class Response {
30 |
31 | @SerializedName("r")
32 | public final int responseCode;
33 |
34 | public Response(int responseCode) {
35 | this.responseCode = responseCode;
36 | }
37 | }
38 |
39 | }
40 |
--------------------------------------------------------------------------------
/devicesetup/src/main/java/io/particle/android/sdk/devicesetup/commands/VersionCommand.java:
--------------------------------------------------------------------------------
1 | package io.particle.android.sdk.devicesetup.commands;
2 |
3 | import com.google.gson.annotations.SerializedName;
4 |
5 |
6 | public class VersionCommand extends NoArgsCommand {
7 |
8 | @Override
9 | public String getCommandName() {
10 | return "version";
11 | }
12 |
13 |
14 | public static class Response {
15 |
16 | @SerializedName("v")
17 | public final int version;
18 |
19 | public Response(int version) {
20 | this.version = version;
21 | }
22 |
23 | @Override
24 | public String toString() {
25 | return "Response{" +
26 | "version=" + version +
27 | '}';
28 | }
29 | }
30 |
31 | }
32 |
--------------------------------------------------------------------------------
/devicesetup/src/main/java/io/particle/android/sdk/devicesetup/commands/data/WifiSecurity.java:
--------------------------------------------------------------------------------
1 | package io.particle.android.sdk.devicesetup.commands.data;
2 |
3 |
4 | import android.util.SparseArray;
5 |
6 | import io.particle.android.sdk.utils.Preconditions;
7 |
8 |
9 | public enum WifiSecurity {
10 |
11 | OPEN(0), // Unsecured
12 | WEP_PSK(1), // WEP Security with open authentication
13 | WEP_SHARED(0x8001), // WEP Security with shared authentication
14 | WPA_TKIP_PSK(0x00200002), // WPA Security with TKIP
15 | WPA_AES_PSK(0x00200004), // WPA Security with AES
16 | WPA_MIXED_PSK(0x00200006), // WPA Security with AES & TKIP
17 | WPA2_AES_PSK(0x00400004), // WPA2 Security with AES
18 | WPA2_TKIP_PSK(0x00400002), // WPA2 Security with TKIP
19 | WPA2_MIXED_PSK(0x00400006); // WPA2 Security with AES & TKIP
20 |
21 |
22 | static final SparseArray fromIntMap;
23 |
24 | static {
25 | fromIntMap = new SparseArray<>();
26 | fromIntMap.put(OPEN.asInt(), OPEN);
27 | fromIntMap.put(WEP_PSK.asInt(), WEP_PSK);
28 | fromIntMap.put(WEP_SHARED.asInt(), WEP_SHARED);
29 | fromIntMap.put(WPA_TKIP_PSK.asInt(), WPA_TKIP_PSK);
30 | fromIntMap.put(WPA_MIXED_PSK.asInt(), WPA_MIXED_PSK);
31 | fromIntMap.put(WPA_AES_PSK.asInt(), WPA_AES_PSK);
32 | fromIntMap.put(WPA2_AES_PSK.asInt(), WPA2_AES_PSK);
33 | fromIntMap.put(WPA2_TKIP_PSK.asInt(), WPA2_TKIP_PSK);
34 | fromIntMap.put(WPA2_MIXED_PSK.asInt(), WPA2_MIXED_PSK);
35 | }
36 |
37 | private final int intValue;
38 |
39 | WifiSecurity(int intValue) {
40 | this.intValue = intValue;
41 | }
42 |
43 | private static final int ENTERPRISE_ENABLED_MASK = 0x02000000;
44 |
45 | // FIXME: accommodate this better
46 | public static boolean isEnterpriseNetwork(int value) {
47 | return (ENTERPRISE_ENABLED_MASK & value) != 0;
48 | }
49 |
50 | public static WifiSecurity fromInteger(Integer value) {
51 | Preconditions.checkNotNull(value);
52 | fromIntMap.indexOfKey(value);
53 | Preconditions.checkArgument(
54 | fromIntMap.indexOfKey(value) >= 0,
55 | "Value not found in map: " + value);
56 |
57 | return fromIntMap.get(value);
58 | }
59 |
60 | public int asInt() {
61 | return intValue;
62 | }
63 |
64 | }
65 |
--------------------------------------------------------------------------------
/devicesetup/src/main/java/io/particle/android/sdk/devicesetup/loaders/ScanApCommandLoader.java:
--------------------------------------------------------------------------------
1 | package io.particle.android.sdk.devicesetup.loaders;
2 |
3 | import android.content.Context;
4 |
5 | import java.io.IOException;
6 | import java.util.Set;
7 |
8 | import javax.annotation.ParametersAreNonnullByDefault;
9 |
10 | import io.particle.android.sdk.devicesetup.commands.CommandClient;
11 | import io.particle.android.sdk.devicesetup.commands.ScanApCommand;
12 | import io.particle.android.sdk.devicesetup.model.ScanAPCommandResult;
13 | import io.particle.android.sdk.utils.BetterAsyncTaskLoader;
14 | import io.particle.android.sdk.utils.Funcy;
15 | import io.particle.android.sdk.utils.TLog;
16 |
17 | import static io.particle.android.sdk.utils.Py.set;
18 |
19 |
20 | /**
21 | * Returns the results of the "scan-ap" command from the device.
22 | *
23 | * Will return null if an exception is thrown when trying to send the command
24 | * and receive a reply from the device.
25 | */
26 | @ParametersAreNonnullByDefault
27 | public class ScanApCommandLoader extends BetterAsyncTaskLoader> {
28 |
29 | private static final TLog log = TLog.get(ScanApCommandLoader.class);
30 |
31 | private final CommandClient commandClient;
32 | private final Set accumulatedResults = set();
33 |
34 | public ScanApCommandLoader(Context context, CommandClient client) {
35 | super(context);
36 | commandClient = client;
37 | }
38 |
39 | @Override
40 | public boolean hasContent() {
41 | return !accumulatedResults.isEmpty();
42 | }
43 |
44 | @Override
45 | public Set getLoadedContent() {
46 | return accumulatedResults;
47 | }
48 |
49 | @Override
50 | protected void onStartLoading() {
51 | super.onStartLoading();
52 | forceLoad();
53 | }
54 |
55 | @Override
56 | protected void onStopLoading() {
57 | cancelLoad();
58 | }
59 |
60 | @Override
61 | public Set loadInBackground() {
62 | try {
63 | ScanApCommand.Response response = commandClient.sendCommand(new ScanApCommand(),
64 | ScanApCommand.Response.class);
65 | accumulatedResults.addAll(Funcy.transformList(response.getScans(), ScanAPCommandResult::new));
66 | log.d("Latest accumulated scan results: " + accumulatedResults);
67 | return set(accumulatedResults);
68 |
69 | } catch (IOException e) {
70 | log.e("Error running scan-ap command: ", e);
71 | return null;
72 | }
73 | }
74 |
75 | }
76 |
--------------------------------------------------------------------------------
/devicesetup/src/main/java/io/particle/android/sdk/devicesetup/loaders/WifiScanResultLoader.java:
--------------------------------------------------------------------------------
1 | package io.particle.android.sdk.devicesetup.loaders;
2 |
3 | import android.content.Context;
4 | import android.content.IntentFilter;
5 | import android.net.wifi.ScanResult;
6 | import android.net.wifi.WifiManager;
7 |
8 | import java.util.List;
9 | import java.util.Locale;
10 | import java.util.Set;
11 |
12 | import io.particle.android.sdk.devicesetup.R;
13 | import io.particle.android.sdk.devicesetup.SimpleReceiver;
14 | import io.particle.android.sdk.devicesetup.model.ScanResultNetwork;
15 | import io.particle.android.sdk.utils.BetterAsyncTaskLoader;
16 | import io.particle.android.sdk.utils.Funcy;
17 | import io.particle.android.sdk.utils.Funcy.Predicate;
18 | import io.particle.android.sdk.utils.TLog;
19 | import io.particle.android.sdk.utils.WifiFacade;
20 |
21 | import static io.particle.android.sdk.utils.Py.set;
22 | import static io.particle.android.sdk.utils.Py.truthy;
23 |
24 |
25 | public class WifiScanResultLoader extends BetterAsyncTaskLoader> {
26 |
27 | private static final TLog log = TLog.get(WifiScanResultLoader.class);
28 |
29 |
30 | private final WifiFacade wifiFacade;
31 | private final SimpleReceiver receiver;
32 | private volatile Set mostRecentResult;
33 | private volatile int loadCount = 0;
34 |
35 | public WifiScanResultLoader(Context context, WifiFacade wifiFacade) {
36 | super(context);
37 | Context appCtx = context.getApplicationContext();
38 | receiver = SimpleReceiver.newReceiver(
39 | appCtx, new IntentFilter(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION),
40 | (ctx, intent) -> {
41 | log.d("Received WifiManager.SCAN_RESULTS_AVAILABLE_ACTION broadcast");
42 | forceLoad();
43 | });
44 | this.wifiFacade = wifiFacade;
45 | }
46 |
47 | @Override
48 | public boolean hasContent() {
49 | return mostRecentResult != null;
50 | }
51 |
52 | @Override
53 | public Set getLoadedContent() {
54 | return mostRecentResult;
55 | }
56 |
57 | @Override
58 | protected void onStartLoading() {
59 | super.onStartLoading();
60 | receiver.register();
61 | forceLoad();
62 | }
63 |
64 | @Override
65 | protected void onStopLoading() {
66 | receiver.unregister();
67 | cancelLoad();
68 | }
69 |
70 | @Override
71 | public Set loadInBackground() {
72 | List scanResults = wifiFacade.getScanResults();
73 | log.d("Latest (unfiltered) scan results: " + scanResults);
74 |
75 | if (loadCount % 3 == 0) {
76 | wifiFacade.startScan();
77 | }
78 |
79 | loadCount++;
80 | // filter the list, transform the matched results, then wrap those in a Set
81 | mostRecentResult = set(Funcy.transformList(
82 | scanResults, ssidStartsWithProductName, ScanResultNetwork::new));
83 |
84 | if (mostRecentResult.isEmpty()) {
85 | log.i("No SSID scan results returned after filtering by product name. " +
86 | "Do you need to change the 'network_name_prefix' resource?");
87 | }
88 |
89 | return mostRecentResult;
90 | }
91 |
92 |
93 | private final Predicate ssidStartsWithProductName = input -> {
94 | if (!truthy(input.SSID)) {
95 | return false;
96 | }
97 | String softApPrefix = (getContext().getString(R.string.network_name_prefix) + "-").toLowerCase(Locale.ROOT);
98 | return input.SSID.toLowerCase(Locale.ROOT).startsWith(softApPrefix);
99 | };
100 |
101 | }
102 |
--------------------------------------------------------------------------------
/devicesetup/src/main/java/io/particle/android/sdk/devicesetup/model/ScanAPCommandResult.java:
--------------------------------------------------------------------------------
1 | package io.particle.android.sdk.devicesetup.model;
2 |
3 | import io.particle.android.sdk.devicesetup.commands.ScanApCommand;
4 | import io.particle.android.sdk.devicesetup.commands.data.WifiSecurity;
5 | import io.particle.android.sdk.utils.SSID;
6 |
7 |
8 | // FIXME: this naming is not ideal.
9 | public class ScanAPCommandResult implements WifiNetwork {
10 |
11 | public final ScanApCommand.Scan scan;
12 | public final SSID ssid;
13 |
14 | public ScanAPCommandResult(ScanApCommand.Scan scan) {
15 | this.scan = scan;
16 | ssid = SSID.from(scan.ssid);
17 | }
18 |
19 | @Override
20 | public SSID getSsid() {
21 | return ssid;
22 | }
23 |
24 | @Override
25 | public boolean isSecured() {
26 | return scan.wifiSecurityType != WifiSecurity.OPEN.asInt();
27 | }
28 |
29 | @Override
30 | public String toString() {
31 | return "ScanAPCommandResult{" +
32 | "scan=" + scan +
33 | '}';
34 | }
35 |
36 | @Override
37 | public boolean equals(Object o) {
38 | if (this == o) return true;
39 | if (o == null || getClass() != o.getClass()) return false;
40 |
41 | ScanAPCommandResult that = (ScanAPCommandResult) o;
42 |
43 | return getSsid() != null ? getSsid().equals(that.getSsid()) : that.getSsid() == null;
44 | }
45 |
46 | @Override
47 | public int hashCode() {
48 | return getSsid() != null ? getSsid().hashCode() : 0;
49 | }
50 |
51 | }
52 |
--------------------------------------------------------------------------------
/devicesetup/src/main/java/io/particle/android/sdk/devicesetup/model/ScanResultNetwork.java:
--------------------------------------------------------------------------------
1 | package io.particle.android.sdk.devicesetup.model;
2 |
3 | import android.net.wifi.ScanResult;
4 |
5 | import java.util.Set;
6 |
7 | import io.particle.android.sdk.utils.SSID;
8 |
9 | import static io.particle.android.sdk.utils.Py.set;
10 |
11 |
12 | // FIXME: this naming... is not ideal.
13 | public class ScanResultNetwork implements WifiNetwork {
14 |
15 | private static final Set wifiSecurityTypes = set("WEP", "PSK", "EAP");
16 |
17 |
18 | private final ScanResult scanResult;
19 | private final SSID ssid;
20 |
21 | public ScanResultNetwork(ScanResult scanResult) {
22 | this.scanResult = scanResult;
23 | ssid = SSID.from(scanResult.SSID);
24 | }
25 |
26 | @Override
27 | public SSID getSsid() {
28 | return ssid;
29 | }
30 |
31 | @Override
32 | public boolean isSecured() {
33 | //
34 | // this seems like a bad joke of an "API", but this is basically what
35 | // Android does internally (see: http://goo.gl/GCRIKi)
36 | for (String securityType : wifiSecurityTypes) {
37 | if (scanResult.capabilities.contains(securityType)) {
38 | return true;
39 | }
40 | }
41 | return false;
42 | }
43 |
44 | @Override
45 | public boolean equals(Object o) {
46 | if (this == o) return true;
47 | if (o == null || getClass() != o.getClass()) return false;
48 |
49 | ScanResultNetwork that = (ScanResultNetwork) o;
50 |
51 | return getSsid() != null ? getSsid().equals(that.getSsid()) : that.getSsid() == null;
52 | }
53 |
54 | @Override
55 | public int hashCode() {
56 | return getSsid() != null ? getSsid().hashCode() : 0;
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/devicesetup/src/main/java/io/particle/android/sdk/devicesetup/model/WifiNetwork.java:
--------------------------------------------------------------------------------
1 | package io.particle.android.sdk.devicesetup.model;
2 |
3 | import io.particle.android.sdk.utils.SSID;
4 |
5 |
6 | public interface WifiNetwork {
7 |
8 | SSID getSsid();
9 |
10 | boolean isSecured();
11 | }
12 |
--------------------------------------------------------------------------------
/devicesetup/src/main/java/io/particle/android/sdk/devicesetup/setupsteps/CheckIfDeviceClaimedStep.java:
--------------------------------------------------------------------------------
1 | package io.particle.android.sdk.devicesetup.setupsteps;
2 |
3 |
4 | import java.util.List;
5 |
6 | import io.particle.android.sdk.cloud.ParticleCloud;
7 | import io.particle.android.sdk.cloud.exceptions.ParticleCloudException;
8 | import io.particle.android.sdk.cloud.ParticleDevice;
9 |
10 |
11 | public class CheckIfDeviceClaimedStep extends SetupStep {
12 |
13 | private final ParticleCloud sparkCloud;
14 | private final String deviceBeingConfiguredId;
15 | private boolean needToClaimDevice = true;
16 |
17 | CheckIfDeviceClaimedStep(StepConfig stepConfig, ParticleCloud sparkCloud,
18 | String deviceBeingConfiguredId) {
19 | super(stepConfig);
20 | this.sparkCloud = sparkCloud;
21 | this.deviceBeingConfiguredId = deviceBeingConfiguredId;
22 | }
23 |
24 | @Override
25 | protected void onRunStep() throws SetupStepException {
26 | List devices;
27 | try {
28 | devices = sparkCloud.getDevices();
29 | } catch (ParticleCloudException e) {
30 | throw new SetupStepException(e);
31 | }
32 |
33 | log.d("Got devices back from the cloud...");
34 | for (ParticleDevice device : devices) {
35 | if (deviceBeingConfiguredId.equalsIgnoreCase(device.getID())) {
36 | log.d("Success, device " + device.getID() + " claimed!");
37 | needToClaimDevice = false;
38 | return;
39 | }
40 | }
41 |
42 | // device not found in the loop
43 | throw new SetupStepException("Device " + deviceBeingConfiguredId + " still not claimed.");
44 | }
45 |
46 | @Override
47 | public boolean isStepFulfilled() {
48 | return !needToClaimDevice;
49 | }
50 |
51 | }
52 |
--------------------------------------------------------------------------------
/devicesetup/src/main/java/io/particle/android/sdk/devicesetup/setupsteps/ConfigureAPStep.java:
--------------------------------------------------------------------------------
1 | package io.particle.android.sdk.devicesetup.setupsteps;
2 |
3 |
4 | import java.io.IOException;
5 | import java.security.PublicKey;
6 |
7 | import io.particle.android.sdk.devicesetup.commands.CommandClient;
8 | import io.particle.android.sdk.devicesetup.commands.ConfigureApCommand;
9 | import io.particle.android.sdk.devicesetup.commands.ScanApCommand;
10 | import io.particle.android.sdk.devicesetup.commands.data.WifiSecurity;
11 | import io.particle.android.sdk.utils.Crypto;
12 |
13 |
14 | public class ConfigureAPStep extends SetupStep {
15 |
16 | private final CommandClient commandClient;
17 | private final SetupStepApReconnector workerThreadApConnector;
18 | private final ScanApCommand.Scan networkToConnectTo;
19 | private final String networkSecretPlaintext;
20 | private final PublicKey publicKey;
21 |
22 | private volatile boolean commandSent = false;
23 |
24 | ConfigureAPStep(StepConfig stepConfig, CommandClient commandClient,
25 | SetupStepApReconnector workerThreadApConnector,
26 | ScanApCommand.Scan networkToConnectTo, String networkSecretPlaintext,
27 | PublicKey publicKey) {
28 | super(stepConfig);
29 | this.commandClient = commandClient;
30 | this.workerThreadApConnector = workerThreadApConnector;
31 | this.networkToConnectTo = networkToConnectTo;
32 | this.networkSecretPlaintext = networkSecretPlaintext;
33 | this.publicKey = publicKey;
34 | }
35 |
36 | protected void onRunStep() throws SetupStepException {
37 | WifiSecurity wifiSecurity = WifiSecurity.fromInteger(networkToConnectTo.wifiSecurityType);
38 | ConfigureApCommand.Builder builder = ConfigureApCommand.newBuilder()
39 | .setSsid(networkToConnectTo.ssid)
40 | .setSecurityType(wifiSecurity)
41 | .setChannel(networkToConnectTo.channel)
42 | .setIdx(0);
43 | if (wifiSecurity != WifiSecurity.OPEN) {
44 | try {
45 | builder.setEncryptedPasswordHex(
46 | Crypto.encryptAndEncodeToHex(networkSecretPlaintext, publicKey));
47 | } catch (Crypto.CryptoException e) {
48 | // FIXME: try to throw a more specific exception here.
49 | // Don't throw SetupException here -- if this is failing, it's not
50 | // going to get any better by the running this SetupStep again, and
51 | // it can really only fail if the surrounding app code is doing something
52 | // wrong. To wit: you *want* the app to crash here (or at least
53 | // throw out a dialog saying "horrible thing happened! horrible error
54 | // code: ..." and then return to a safe "default" activity.
55 | throw new RuntimeException("Error encrypting network credentials", e);
56 | }
57 | }
58 | ConfigureApCommand command = builder.build();
59 |
60 | try {
61 | log.d("Ensuring connection to AP");
62 | workerThreadApConnector.ensureConnectionToSoftAp();
63 |
64 | ConfigureApCommand.Response response = commandClient.sendCommand(
65 | command, ConfigureApCommand.Response.class);
66 | if (!response.isOk()) {
67 | throw new SetupStepException("Error response code " + response.responseCode +
68 | " while configuring device");
69 | }
70 | log.d("Configure AP command returned: " + response.responseCode);
71 | commandSent = true;
72 |
73 | } catch (IOException e) {
74 | throw new SetupStepException(e);
75 | }
76 | }
77 |
78 | public boolean isStepFulfilled() {
79 | return commandSent;
80 | }
81 |
82 | }
83 |
--------------------------------------------------------------------------------
/devicesetup/src/main/java/io/particle/android/sdk/devicesetup/setupsteps/ConnectDeviceToNetworkStep.java:
--------------------------------------------------------------------------------
1 | package io.particle.android.sdk.devicesetup.setupsteps;
2 |
3 | import java.io.IOException;
4 |
5 | import io.particle.android.sdk.devicesetup.commands.CommandClient;
6 | import io.particle.android.sdk.devicesetup.commands.ConnectAPCommand;
7 |
8 |
9 | public class ConnectDeviceToNetworkStep extends SetupStep {
10 |
11 | private final CommandClient commandClient;
12 | private final SetupStepApReconnector workerThreadApConnector;
13 |
14 | private volatile boolean commandSent = false;
15 |
16 | ConnectDeviceToNetworkStep(StepConfig stepConfig, CommandClient commandClient,
17 | SetupStepApReconnector workerThreadApConnector) {
18 | super(stepConfig);
19 | this.commandClient = commandClient;
20 | this.workerThreadApConnector = workerThreadApConnector;
21 | }
22 |
23 | @Override
24 | protected void onRunStep() throws SetupStepException {
25 | try {
26 | log.d("Ensuring connection to AP");
27 | workerThreadApConnector.ensureConnectionToSoftAp();
28 |
29 | log.d("Sending connect-ap command");
30 | ConnectAPCommand.Response response = commandClient.sendCommand(
31 | // FIXME: is hard-coding zero here correct? If so, document why
32 | new ConnectAPCommand(0), ConnectAPCommand.Response.class);
33 | if (!response.isOK()) {
34 | throw new SetupStepException("ConnectAPCommand returned non-zero response code: " +
35 | response.responseCode);
36 | }
37 |
38 | commandSent = true;
39 |
40 | } catch (IOException e) {
41 | throw new SetupStepException(e);
42 | }
43 | }
44 |
45 | @Override
46 | public boolean isStepFulfilled() {
47 | return commandSent;
48 | }
49 |
50 | }
51 |
--------------------------------------------------------------------------------
/devicesetup/src/main/java/io/particle/android/sdk/devicesetup/setupsteps/EnsureSoftApNotVisible.java:
--------------------------------------------------------------------------------
1 | package io.particle.android.sdk.devicesetup.setupsteps;
2 |
3 | import java.util.List;
4 |
5 | import io.particle.android.sdk.devicesetup.SetupProcessException;
6 | import io.particle.android.sdk.utils.EZ;
7 | import io.particle.android.sdk.utils.Funcy;
8 | import io.particle.android.sdk.utils.Funcy.Predicate;
9 | import io.particle.android.sdk.utils.Preconditions;
10 | import io.particle.android.sdk.utils.SSID;
11 | import io.particle.android.sdk.utils.WifiFacade;
12 |
13 | import static io.particle.android.sdk.utils.Py.list;
14 |
15 |
16 | public class EnsureSoftApNotVisible extends SetupStep {
17 |
18 | private final WifiFacade wifiFacade;
19 | private final SSID softApName;
20 | private final Predicate matchesSoftApSSID;
21 |
22 | private boolean wasFulfilledOnce = false;
23 |
24 | EnsureSoftApNotVisible(StepConfig stepConfig, SSID softApSSID, WifiFacade wifiFacade) {
25 | super(stepConfig);
26 | Preconditions.checkNotNull(softApSSID, "softApSSID cannot be null.");
27 | this.wifiFacade = wifiFacade;
28 | this.softApName = softApSSID;
29 | this.matchesSoftApSSID = softApName::equals;
30 | }
31 |
32 | @Override
33 | public boolean isStepFulfilled() {
34 | return wasFulfilledOnce && !isSoftApVisible();
35 | }
36 |
37 | @Override
38 | protected void onRunStep() throws SetupStepException, SetupProcessException {
39 | if (!wasFulfilledOnce) {
40 | onStepNeverYetFulfilled();
41 |
42 | } else {
43 | onStepPreviouslyFulfilled();
44 | }
45 | }
46 |
47 | // Before the soft AP disappears for the FIRST time, be lenient in allowing for retries if
48 | // it fails to disappear, since we don't have a good idea of why it's failing, so just throw
49 | // SetupStepException. (But see onStepPreviouslyFulfilled())
50 | private void onStepNeverYetFulfilled() throws SetupStepException {
51 | for (int i = 0; i < 16; i++) {
52 | if (!isSoftApVisible()) {
53 | // it's gone!
54 | wasFulfilledOnce = true;
55 | return;
56 | }
57 |
58 | if (i % 6 == 0) {
59 | wifiFacade.startScan();
60 | }
61 |
62 | EZ.threadSleep(250);
63 | }
64 | throw new SetupStepException("Wi-Fi credentials appear to be incorrect or an error has occurred");
65 | }
66 |
67 | // If this step was previously fulfilled, i.e.: the soft AP was gone, and now it's visible again,
68 | // this almost certainly means the device was given invalid Wi-Fi credentials, so we should
69 | // fail the whole setup process immediately.
70 | private void onStepPreviouslyFulfilled() throws SetupProcessException {
71 | if (isSoftApVisible()) {
72 | throw new SetupProcessException(
73 | "Soft AP visible again; Wi-Fi credentials may be incorrect", this);
74 | }
75 | }
76 |
77 | private boolean isSoftApVisible() {
78 | List scansPlusConnectedSsid = list();
79 |
80 | SSID currentlyConnected = wifiFacade.getCurrentlyConnectedSSID();
81 | if (currentlyConnected != null) {
82 | scansPlusConnectedSsid.add(currentlyConnected);
83 | }
84 |
85 | scansPlusConnectedSsid.addAll(
86 | Funcy.transformList(wifiFacade.getScanResults(),
87 | Funcy.notNull(),
88 | SSID::from)
89 | );
90 |
91 | log.d("scansPlusConnectedSsid: " + scansPlusConnectedSsid);
92 | log.d("Soft AP we're looking for: " + softApName);
93 |
94 | SSID firstMatch = Funcy.findFirstMatch(scansPlusConnectedSsid, matchesSoftApSSID);
95 | log.d("Matching SSID result: '" + firstMatch + "'");
96 | return firstMatch != null;
97 | }
98 |
99 | }
100 |
--------------------------------------------------------------------------------
/devicesetup/src/main/java/io/particle/android/sdk/devicesetup/setupsteps/SetupStep.java:
--------------------------------------------------------------------------------
1 | package io.particle.android.sdk.devicesetup.setupsteps;
2 |
3 | import android.annotation.SuppressLint;
4 | import android.support.annotation.AnyThread;
5 | import android.support.annotation.WorkerThread;
6 |
7 | import io.particle.android.sdk.devicesetup.SetupProcessException;
8 | import io.particle.android.sdk.utils.TLog;
9 |
10 |
11 | @WorkerThread
12 | public abstract class SetupStep {
13 |
14 | protected final TLog log;
15 | private final StepConfig stepConfig;
16 | private volatile int numAttempts;
17 |
18 |
19 | public SetupStep(StepConfig stepConfig) {
20 | log = TLog.get(this.getClass());
21 | this.stepConfig = stepConfig;
22 | }
23 |
24 | protected abstract void onRunStep() throws SetupStepException, SetupProcessException;
25 |
26 | public abstract boolean isStepFulfilled();
27 |
28 | final void runStep() throws SetupStepException, SetupProcessException {
29 | if (isStepFulfilled()) {
30 | getLog().i("Step " + getStepName() + " already fulfilled, skipping...");
31 | return;
32 | }
33 | if (numAttempts > stepConfig.maxAttempts) {
34 | @SuppressLint("DefaultLocale")
35 | String msg = String.format("Exceeded limit of %d retries for step %s",
36 | stepConfig.maxAttempts, getStepName());
37 | throw new SetupProcessException(msg, this);
38 | } else {
39 | getLog().i("Running step " + getStepName());
40 | numAttempts++;
41 | onRunStep();
42 | }
43 | }
44 |
45 | public TLog getLog() {
46 | return log;
47 | }
48 |
49 | @AnyThread
50 | public StepConfig getStepConfig() {
51 | return this.stepConfig;
52 | }
53 |
54 | protected void resetAttemptsCount() {
55 | numAttempts = 0;
56 | }
57 |
58 | private String getStepName() {
59 | return this.getClass().getSimpleName();
60 | }
61 |
62 | }
63 |
--------------------------------------------------------------------------------
/devicesetup/src/main/java/io/particle/android/sdk/devicesetup/setupsteps/SetupStepApReconnector.java:
--------------------------------------------------------------------------------
1 | package io.particle.android.sdk.devicesetup.setupsteps;
2 |
3 |
4 | import android.net.wifi.WifiConfiguration;
5 | import android.os.Handler;
6 |
7 | import java.io.IOException;
8 | import java.util.concurrent.CountDownLatch;
9 | import java.util.concurrent.TimeUnit;
10 | import java.util.concurrent.atomic.AtomicBoolean;
11 |
12 | import io.particle.android.sdk.devicesetup.ApConnector;
13 | import io.particle.android.sdk.devicesetup.ApConnector.Client;
14 | import io.particle.android.sdk.utils.SSID;
15 | import io.particle.android.sdk.utils.WifiFacade;
16 |
17 |
18 | public class SetupStepApReconnector {
19 |
20 | private final WifiFacade wifiFacade;
21 | private final ApConnector apConnector;
22 | private final Handler mainThreadHandler;
23 | private final SSID softApSSID;
24 | private final WifiConfiguration config;
25 |
26 | public SetupStepApReconnector(WifiFacade wifiFacade, ApConnector apConnector,
27 | Handler mainThreadHandler, SSID softApSSID) {
28 | this.wifiFacade = wifiFacade;
29 | this.apConnector = apConnector;
30 | this.mainThreadHandler = mainThreadHandler;
31 | this.softApSSID = softApSSID;
32 | this.config = ApConnector.buildUnsecuredConfig(softApSSID);
33 | }
34 |
35 | void ensureConnectionToSoftAp() throws IOException {
36 | if (isConnectedToSoftAp()) {
37 | return;
38 | }
39 |
40 | final CountDownLatch latch = new CountDownLatch(1);
41 | final AtomicBoolean gotConnected = new AtomicBoolean(false);
42 |
43 | mainThread(() ->
44 | apConnector.connectToAP(config, new Client() {
45 | @Override
46 | public void onApConnectionSuccessful(WifiConfiguration config) {
47 | gotConnected.set(true);
48 | latch.countDown();
49 | }
50 |
51 | @Override
52 | public void onApConnectionFailed(WifiConfiguration config) {
53 | latch.countDown();
54 | }
55 | }));
56 |
57 | // 50ms is an arbitrary number; just give the ApConnector time to do its work and allow for
58 | // a slight delay for overhead, etc.
59 | awaitCountdown(latch, ApConnector.CONNECT_TO_DEVICE_TIMEOUT_MILLIS + 50);
60 |
61 | // throw if it didn't work, otherwise assume success
62 | if (!gotConnected.get()) {
63 | throw new IOException("ApConnector did not finish in time; could not reconnect to soft AP");
64 | }
65 | }
66 |
67 | private boolean isConnectedToSoftAp() {
68 | return softApSSID.equals(wifiFacade.getCurrentlyConnectedSSID());
69 | }
70 |
71 | private CountDownLatch mainThread(final Runnable runnable) {
72 | final CountDownLatch latch = new CountDownLatch(1);
73 | mainThreadHandler.post(() -> {
74 | runnable.run();
75 | latch.countDown();
76 | });
77 | return latch;
78 | }
79 |
80 | private boolean awaitCountdown(CountDownLatch latch, long awaitMs) {
81 | try {
82 | return latch.await(awaitMs, TimeUnit.MILLISECONDS);
83 | } catch (InterruptedException e) {
84 | e.printStackTrace();
85 | return false;
86 | }
87 | }
88 |
89 | }
90 |
--------------------------------------------------------------------------------
/devicesetup/src/main/java/io/particle/android/sdk/devicesetup/setupsteps/SetupStepException.java:
--------------------------------------------------------------------------------
1 | package io.particle.android.sdk.devicesetup.setupsteps;
2 |
3 |
4 | public class SetupStepException extends Exception {
5 |
6 | public SetupStepException(String msg, Throwable throwable) {
7 | super(msg, throwable);
8 | }
9 |
10 | public SetupStepException(String msg) {
11 | super(msg);
12 | }
13 |
14 | public SetupStepException(Throwable throwable) {
15 | super(throwable);
16 | }
17 |
18 | }
19 |
--------------------------------------------------------------------------------
/devicesetup/src/main/java/io/particle/android/sdk/devicesetup/setupsteps/SetupStepsRunnerTask.java:
--------------------------------------------------------------------------------
1 | package io.particle.android.sdk.devicesetup.setupsteps;
2 |
3 | import android.os.AsyncTask;
4 |
5 | import java.util.List;
6 |
7 | import io.particle.android.sdk.devicesetup.SetupProcessException;
8 | import io.particle.android.sdk.utils.EZ;
9 | import io.particle.android.sdk.utils.TLog;
10 |
11 |
12 | public abstract class SetupStepsRunnerTask extends
13 | AsyncTask {
14 |
15 | private final TLog log = TLog.get(getClass());
16 |
17 | private final List steps;
18 | private final int maxOverallAttempts;
19 |
20 | public SetupStepsRunnerTask(List steps, int maxOverallAttempts) {
21 | this.steps = steps;
22 | this.maxOverallAttempts = maxOverallAttempts;
23 | }
24 |
25 | @Override
26 | public SetupProcessException doInBackground(Void... voids) {
27 | int attempts = 0;
28 | // We should never hit this limit, but just in case, we want to
29 | // avoid an infinite loop
30 | while (attempts < maxOverallAttempts) {
31 | attempts++;
32 | try {
33 | runSteps();
34 | // we got all the way through the steps, break out of the loop!
35 | return null;
36 |
37 | } catch (SetupStepException e) {
38 | log.w("Setup step failed: " + e.getMessage());
39 |
40 | } catch (SetupProcessException e) {
41 | return e;
42 | }
43 | }
44 |
45 | return new SetupProcessException("(Unknown setup error)", null);
46 | }
47 |
48 | private void runSteps() throws SetupStepException, SetupProcessException {
49 | for (SetupStep step : steps) {
50 |
51 | throwIfCancelled();
52 |
53 | publishProgress(new StepProgress(
54 | step.getStepConfig().getStepId(),
55 | StepProgress.STARTING));
56 |
57 | try {
58 | EZ.threadSleep(1000);
59 | throwIfCancelled();
60 |
61 | step.runStep();
62 |
63 | } catch (SetupStepException e) {
64 | // give it a moment before trying again.
65 | EZ.threadSleep(2000);
66 | throw e;
67 | }
68 |
69 | publishProgress(new StepProgress(
70 | step.getStepConfig().getStepId(),
71 | StepProgress.SUCCEEDED));
72 | }
73 | }
74 |
75 | private void throwIfCancelled() {
76 | // FIXME: while it's good that we handle being cancelled, this doesn't seem like
77 | // an ideal way to do it...
78 | if (isCancelled()) {
79 | throw new RuntimeException("Task was cancelled");
80 | }
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/devicesetup/src/main/java/io/particle/android/sdk/devicesetup/setupsteps/StepConfig.java:
--------------------------------------------------------------------------------
1 | package io.particle.android.sdk.devicesetup.setupsteps;
2 |
3 | import io.particle.android.sdk.utils.Preconditions;
4 |
5 |
6 | public class StepConfig {
7 |
8 | final int maxAttempts;
9 | private final int stepId;
10 | public final int resultCode;
11 |
12 | private StepConfig(int maxAttempts, int stepId, int resultCode) {
13 | this.maxAttempts = maxAttempts;
14 | this.stepId = stepId;
15 | this.resultCode = resultCode;
16 | }
17 |
18 | public int getStepId() {
19 | return stepId;
20 | }
21 |
22 | static Builder newBuilder() {
23 | return new Builder();
24 | }
25 |
26 | public static class Builder {
27 |
28 | private int maxAttempts;
29 | private int stepId;
30 | private int resultCode;
31 |
32 | Builder setMaxAttempts(int maxAttempts) {
33 | this.maxAttempts = maxAttempts;
34 | return this;
35 | }
36 |
37 | Builder setStepId(int stepId) {
38 | this.stepId = stepId;
39 | return this;
40 | }
41 |
42 | Builder setResultCode(int resultCode) {
43 | this.resultCode = resultCode;
44 | return this;
45 | }
46 |
47 | public StepConfig build() {
48 | Preconditions.checkArgument(maxAttempts > 0, "Max attempts must be > 0");
49 | Preconditions.checkArgument(stepId != 0, "Step ID cannot be unset or set to 0");
50 | Preconditions.checkArgument(resultCode != 0, "Result code cannot be unset or set to 0");
51 | return new StepConfig(maxAttempts, stepId, resultCode);
52 | }
53 | }
54 |
55 | }
56 |
--------------------------------------------------------------------------------
/devicesetup/src/main/java/io/particle/android/sdk/devicesetup/setupsteps/StepProgress.java:
--------------------------------------------------------------------------------
1 | package io.particle.android.sdk.devicesetup.setupsteps;
2 |
3 |
4 | public class StepProgress {
5 |
6 | public static final int STARTING = 1;
7 | static final int SUCCEEDED = 2;
8 |
9 | public final int stepId;
10 | public final int status;
11 |
12 | StepProgress(int stepId, int status) {
13 | this.status = status;
14 | this.stepId = stepId;
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/devicesetup/src/main/java/io/particle/android/sdk/devicesetup/setupsteps/WaitForCloudConnectivityStep.java:
--------------------------------------------------------------------------------
1 | package io.particle.android.sdk.devicesetup.setupsteps;
2 |
3 |
4 | import android.content.Context;
5 | import android.net.ConnectivityManager;
6 | import android.net.NetworkInfo;
7 |
8 | import io.particle.android.sdk.utils.EZ;
9 |
10 |
11 | public class WaitForCloudConnectivityStep extends SetupStep {
12 |
13 | private static final int MAX_RETRIES_REACHABILITY = 1;
14 |
15 | private final Context ctx;
16 |
17 | WaitForCloudConnectivityStep(StepConfig stepConfig, Context ctx) {
18 | super(stepConfig);
19 | this.ctx = ctx;
20 | }
21 |
22 | @Override
23 | protected void onRunStep() throws SetupStepException {
24 | // Wait for just a couple seconds for a WifiFacade connection if possible, in case we
25 | // flip from the soft AP, to mobile data, and then to WifiFacade in rapid succession.
26 | EZ.threadSleep(2000);
27 | int reachabilityRetries = 0;
28 | boolean isAPIHostReachable = checkIsApiHostAvailable();
29 | while (!isAPIHostReachable && reachabilityRetries <= MAX_RETRIES_REACHABILITY) {
30 | EZ.threadSleep(2000);
31 | isAPIHostReachable = checkIsApiHostAvailable();
32 | log.d("Checked for reachability " + reachabilityRetries + " times");
33 | reachabilityRetries++;
34 | }
35 | if (!isAPIHostReachable) {
36 | throw new SetupStepException("Unable to reach API host");
37 | }
38 | }
39 |
40 | @Override
41 | public boolean isStepFulfilled() {
42 | return checkIsApiHostAvailable();
43 | }
44 |
45 | private boolean checkIsApiHostAvailable() {
46 | ConnectivityManager cm = (ConnectivityManager) ctx.getSystemService(
47 | Context.CONNECTIVITY_SERVICE);
48 | NetworkInfo activeNetworkInfo = null;
49 | if (cm != null) {
50 | activeNetworkInfo = cm.getActiveNetworkInfo();
51 | }
52 | if (activeNetworkInfo == null || !activeNetworkInfo.isConnected()) {
53 | return false;
54 | }
55 |
56 | // FIXME: why is this commented out? See what iOS does here now.
57 | // try {
58 | // cloud.getDevices();
59 | // } catch (Exception e) {
60 | // log.e("error checking availability: ", e);
61 | // // FIXME:
62 | // return false;
63 | // // At this stage we're technically OK with other types of errors
64 | // if (set(Kind.NETWORK, Kind.UNEXPECTED).contains(e.getKind())) {
65 | // return false;
66 | // }
67 | // }
68 |
69 | return true;
70 | }
71 |
72 | }
73 |
--------------------------------------------------------------------------------
/devicesetup/src/main/java/io/particle/android/sdk/devicesetup/setupsteps/WaitForDisconnectionFromDeviceStep.java:
--------------------------------------------------------------------------------
1 | package io.particle.android.sdk.devicesetup.setupsteps;
2 |
3 | import io.particle.android.sdk.devicesetup.SetupProcessException;
4 | import io.particle.android.sdk.devicesetup.ui.DeviceSetupState;
5 | import io.particle.android.sdk.utils.EZ;
6 | import io.particle.android.sdk.utils.Preconditions;
7 | import io.particle.android.sdk.utils.SSID;
8 | import io.particle.android.sdk.utils.WifiFacade;
9 |
10 |
11 | public class WaitForDisconnectionFromDeviceStep extends SetupStep {
12 |
13 | private final SSID softApName;
14 | private final WifiFacade wifiFacade;
15 |
16 | private boolean wasDisconnected = false;
17 |
18 | WaitForDisconnectionFromDeviceStep(StepConfig stepConfig, SSID softApSSID, WifiFacade wifiFacade) {
19 | super(stepConfig);
20 | Preconditions.checkNotNull(softApSSID, "softApSSID cannot be null.");
21 | this.softApName = softApSSID;
22 | this.wifiFacade = wifiFacade;
23 | }
24 |
25 | @Override
26 | public boolean isStepFulfilled() {
27 | return wasDisconnected;
28 | }
29 |
30 | @Override
31 | protected void onRunStep() throws SetupStepException, SetupProcessException {
32 | for (int i = 0; i <= 5; i++) {
33 | if (isConnectedToSoftAp()) {
34 | // wait and try again
35 | EZ.threadSleep(200);
36 | } else {
37 | EZ.threadSleep(1000);
38 | // success, no longer connected.
39 | wasDisconnected = true;
40 | if (EZ.isUsingOlderWifiStack()) {
41 | // for some reason Lollipop doesn't need this??
42 | reenablePreviousWifi();
43 | }
44 | return;
45 | }
46 | }
47 |
48 | // Still connected after the above completed: fail
49 | throw new SetupStepException("Not disconnected from soft AP");
50 | }
51 |
52 | private void reenablePreviousWifi() {
53 | SSID prevSSID = DeviceSetupState.previouslyConnectedWifiNetwork;
54 | wifiFacade.reenableNetwork(prevSSID);
55 | wifiFacade.reassociate();
56 | }
57 |
58 | private boolean isConnectedToSoftAp() {
59 | SSID currentlyConnectedSSID = wifiFacade.getCurrentlyConnectedSSID();
60 | log.d("Currently connected SSID: " + currentlyConnectedSSID);
61 | return softApName.equals(currentlyConnectedSSID);
62 | }
63 |
64 | }
65 |
--------------------------------------------------------------------------------
/devicesetup/src/main/java/io/particle/android/sdk/devicesetup/ui/ConnectToApFragment.java:
--------------------------------------------------------------------------------
1 | package io.particle.android.sdk.devicesetup.ui;
2 |
3 | import android.content.Context;
4 | import android.net.wifi.WifiConfiguration;
5 | import android.os.Bundle;
6 | import android.support.v4.app.FragmentActivity;
7 |
8 | import javax.inject.Inject;
9 |
10 | import io.particle.android.sdk.devicesetup.ApConnector;
11 | import io.particle.android.sdk.devicesetup.ApConnector.Client;
12 | import io.particle.android.sdk.devicesetup.ParticleDeviceSetupLibrary;
13 | import io.particle.android.sdk.di.ApModule;
14 | import io.particle.android.sdk.utils.EZ;
15 | import io.particle.android.sdk.utils.SSID;
16 | import io.particle.android.sdk.utils.WifiFacade;
17 | import io.particle.android.sdk.utils.WorkerFragment;
18 | import io.particle.android.sdk.utils.ui.Ui;
19 |
20 |
21 | // reconsider if this even needs to be a fragment at all
22 | public class ConnectToApFragment extends WorkerFragment {
23 |
24 | public static final String TAG = WorkerFragment.buildFragmentTag(ConnectToApFragment.class);
25 |
26 |
27 | public static ConnectToApFragment get(FragmentActivity activity) {
28 | return Ui.findFrag(activity, TAG);
29 | }
30 |
31 | public static ConnectToApFragment ensureAttached(FragmentActivity activity) {
32 | ConnectToApFragment frag = get(activity);
33 | if (frag == null) {
34 | frag = new ConnectToApFragment();
35 | WorkerFragment.addFragment(activity, frag, TAG);
36 | }
37 | return frag;
38 | }
39 |
40 | @Inject protected ApConnector apConnector;
41 | @Inject protected WifiFacade wifiFacade;
42 | private Client apConnectorClient;
43 |
44 | @Override
45 | public void onAttach(Context context) {
46 | super.onAttach(context);
47 | apConnectorClient = EZ.getCallbacksOrThrow(this, Client.class);
48 | }
49 |
50 | @Override
51 | public void onCreate(Bundle savedInstanceState) {
52 | super.onCreate(savedInstanceState);
53 | ParticleDeviceSetupLibrary.getInstance().getApplicationComponent().activityComponentBuilder()
54 | .apModule(new ApModule()).build().inject(this);
55 | }
56 |
57 | @Override
58 | public void onStop() {
59 | super.onStop();
60 | apConnector.stop();
61 | }
62 |
63 | /**
64 | * Connect this Android device to the specified AP.
65 | *
66 | * @param config the WifiConfiguration defining which AP to connect to
67 | * @return the SSID that was connected prior to calling this method. Will be null if
68 | * there was no network connected, or if already connected to the target network.
69 | */
70 | public SSID connectToAP(final WifiConfiguration config) {
71 | return apConnector.connectToAP(config, apConnectorClient);
72 | }
73 |
74 | }
75 |
--------------------------------------------------------------------------------
/devicesetup/src/main/java/io/particle/android/sdk/devicesetup/ui/DeviceSetupState.java:
--------------------------------------------------------------------------------
1 | package io.particle.android.sdk.devicesetup.ui;
2 |
3 |
4 | import java.security.PublicKey;
5 | import java.util.Set;
6 | import java.util.concurrent.ConcurrentSkipListSet;
7 |
8 | import io.particle.android.sdk.utils.SSID;
9 |
10 | // FIXME: Statically defined, global, mutable state... refactor this thing into oblivion soon.
11 | public class DeviceSetupState {
12 |
13 | static final Set claimedDeviceIds = new ConcurrentSkipListSet<>();
14 | public static volatile SSID previouslyConnectedWifiNetwork;
15 | static volatile String claimCode;
16 | static volatile PublicKey publicKey;
17 | static volatile String deviceToBeSetUpId;
18 |
19 | static void reset() {
20 | claimCode = null;
21 | claimedDeviceIds.clear();
22 | publicKey = null;
23 | deviceToBeSetUpId = null;
24 | previouslyConnectedWifiNetwork = null;
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/devicesetup/src/main/java/io/particle/android/sdk/devicesetup/ui/ManualNetworkEntryActivity.java:
--------------------------------------------------------------------------------
1 | package io.particle.android.sdk.devicesetup.ui;
2 |
3 | import android.content.Context;
4 | import android.content.Intent;
5 | import android.os.Bundle;
6 | import android.support.v4.app.LoaderManager;
7 | import android.support.v4.content.Loader;
8 | import android.view.View;
9 | import android.widget.CheckBox;
10 |
11 | import java.util.Set;
12 |
13 | import javax.inject.Inject;
14 |
15 | import butterknife.ButterKnife;
16 | import butterknife.OnCheckedChanged;
17 | import io.particle.android.sdk.devicesetup.ParticleDeviceSetupLibrary;
18 | import io.particle.android.sdk.devicesetup.R;
19 | import io.particle.android.sdk.devicesetup.R2;
20 | import io.particle.android.sdk.devicesetup.commands.CommandClientFactory;
21 | import io.particle.android.sdk.devicesetup.commands.ScanApCommand;
22 | import io.particle.android.sdk.devicesetup.commands.data.WifiSecurity;
23 | import io.particle.android.sdk.devicesetup.loaders.ScanApCommandLoader;
24 | import io.particle.android.sdk.devicesetup.model.ScanAPCommandResult;
25 | import io.particle.android.sdk.di.ApModule;
26 | import io.particle.android.sdk.ui.BaseActivity;
27 | import io.particle.android.sdk.utils.SEGAnalytics;
28 | import io.particle.android.sdk.utils.SSID;
29 | import io.particle.android.sdk.utils.WifiFacade;
30 | import io.particle.android.sdk.utils.ui.ParticleUi;
31 | import io.particle.android.sdk.utils.ui.Ui;
32 |
33 |
34 | public class ManualNetworkEntryActivity extends BaseActivity
35 | implements LoaderManager.LoaderCallbacks> {
36 |
37 |
38 | public static Intent buildIntent(Context ctx, SSID softApSSID) {
39 | return new Intent(ctx, ManualNetworkEntryActivity.class)
40 | .putExtra(EXTRA_SOFT_AP, softApSSID);
41 | }
42 |
43 |
44 | private static final String EXTRA_SOFT_AP = "EXTRA_SOFT_AP";
45 |
46 |
47 | @Inject protected WifiFacade wifiFacade;
48 | @Inject protected CommandClientFactory commandClientFactory;
49 | private SSID softApSSID;
50 | protected Integer wifiSecurityType = WifiSecurity.WPA2_AES_PSK.asInt();
51 |
52 | @OnCheckedChanged(R2.id.network_requires_password)
53 | protected void onSecureCheckedChange(boolean isChecked) {
54 | if (isChecked) {
55 | SEGAnalytics.track("Device Setup: Selected secured network");
56 | wifiSecurityType = WifiSecurity.WPA2_AES_PSK.asInt();
57 | } else {
58 | SEGAnalytics.track("Device Setup: Selected open network");
59 | wifiSecurityType = WifiSecurity.OPEN.asInt();
60 | }
61 | }
62 |
63 | @Override
64 | protected void onCreate(Bundle savedInstanceState) {
65 | super.onCreate(savedInstanceState);
66 | ParticleDeviceSetupLibrary.getInstance().getApplicationComponent().activityComponentBuilder()
67 | .apModule(new ApModule()).build().inject(this);
68 | SEGAnalytics.screen("Device Setup: Manual network entry screen");
69 | softApSSID = getIntent().getParcelableExtra(EXTRA_SOFT_AP);
70 |
71 | setContentView(R.layout.activity_manual_network_entry);
72 | ButterKnife.bind(this);
73 | ParticleUi.enableBrandLogoInverseVisibilityAgainstSoftKeyboard(this);
74 | }
75 |
76 | public void onConnectClicked(View view) {
77 | String ssid = Ui.getText(this, R.id.network_name, true);
78 | ScanApCommand.Scan scan = new ScanApCommand.Scan(ssid, wifiSecurityType, 0);
79 |
80 | CheckBox requiresPassword = Ui.findView(this, R.id.network_requires_password);
81 | if (requiresPassword.isChecked()) {
82 | startActivity(PasswordEntryActivity.buildIntent(this, softApSSID, scan));
83 | } else {
84 | startActivity(ConnectingActivity.buildIntent(this, softApSSID, scan));
85 | }
86 | }
87 |
88 | public void onCancelClicked(View view) {
89 | finish();
90 | }
91 |
92 | // FIXME: loader not currently used, see note in onLoadFinished()
93 | @Override
94 | public Loader> onCreateLoader(int id, Bundle args) {
95 | return new ScanApCommandLoader(this, commandClientFactory.newClientUsingDefaultsForDevices(wifiFacade, softApSSID));
96 | }
97 |
98 | @Override
99 | public void onLoadFinished(Loader> loader, Set data) {
100 | // FIXME: perform process described here?:
101 | // https://github.com/spark/mobile-sdk-ios/issues/56
102 | }
103 |
104 | @Override
105 | public void onLoaderReset(Loader> loader) {
106 | // no-op
107 | }
108 | }
109 |
--------------------------------------------------------------------------------
/devicesetup/src/main/java/io/particle/android/sdk/devicesetup/ui/RequiresWifiScansActivity.java:
--------------------------------------------------------------------------------
1 | package io.particle.android.sdk.devicesetup.ui;
2 |
3 | import android.Manifest.permission;
4 | import android.annotation.SuppressLint;
5 | import android.util.Log;
6 |
7 | import io.particle.android.sdk.ui.BaseActivity;
8 |
9 | // FIXME: doing this via Activities feels sketchy. Find a better way when refactoring
10 | // to use fragments (or similar)
11 | @SuppressLint("Registered")
12 | public class RequiresWifiScansActivity extends BaseActivity {
13 |
14 | @Override
15 | protected void onStart() {
16 | super.onStart();
17 | if (!PermissionsFragment.hasPermission(this, permission.ACCESS_COARSE_LOCATION)) {
18 | Log.d("RequiresWifiScans", "Location permission appears to have been revoked, finishing activity...");
19 | finish();
20 | }
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/devicesetup/src/main/java/io/particle/android/sdk/di/ActivityInjectorComponent.java:
--------------------------------------------------------------------------------
1 | package io.particle.android.sdk.di;
2 |
3 | import android.support.annotation.RestrictTo;
4 |
5 | import dagger.Subcomponent;
6 | import io.particle.android.sdk.accountsetup.LoginActivity;
7 | import io.particle.android.sdk.accountsetup.PasswordResetActivity;
8 | import io.particle.android.sdk.accountsetup.TwoFactorActivity;
9 | import io.particle.android.sdk.devicesetup.ui.ConnectToApFragment;
10 | import io.particle.android.sdk.devicesetup.ui.ConnectingActivity;
11 | import io.particle.android.sdk.devicesetup.ui.ConnectingProcessWorkerTask;
12 | import io.particle.android.sdk.devicesetup.ui.DiscoverDeviceActivity;
13 | import io.particle.android.sdk.devicesetup.ui.GetReadyActivity;
14 | import io.particle.android.sdk.devicesetup.ui.ManualNetworkEntryActivity;
15 | import io.particle.android.sdk.devicesetup.ui.PasswordEntryActivity;
16 | import io.particle.android.sdk.devicesetup.ui.SelectNetworkActivity;
17 | import io.particle.android.sdk.devicesetup.ui.SuccessActivity;
18 |
19 | @PerActivity
20 | @Subcomponent(modules = {ApModule.class})
21 | @RestrictTo({RestrictTo.Scope.LIBRARY})
22 | public interface ActivityInjectorComponent {
23 | void inject(GetReadyActivity activity);
24 |
25 | void inject(LoginActivity loginActivity);
26 |
27 | void inject(PasswordResetActivity passwordResetActivity);
28 |
29 | void inject(SuccessActivity successActivity);
30 |
31 | void inject(DiscoverDeviceActivity discoverDeviceActivity);
32 |
33 | void inject(ConnectingActivity connectingActivity);
34 |
35 | void inject(PasswordEntryActivity passwordEntryActivity);
36 |
37 | void inject(ManualNetworkEntryActivity manualNetworkEntryActivity);
38 |
39 | void inject(SelectNetworkActivity selectNetworkActivity);
40 |
41 | void inject(ConnectToApFragment connectToApFragment);
42 |
43 | void inject(ConnectingProcessWorkerTask connectingProcessWorkerTask);
44 |
45 | void inject(TwoFactorActivity twoFactorActivity);
46 |
47 | @Subcomponent.Builder
48 | interface Builder {
49 | Builder apModule(ApModule module);
50 |
51 | ActivityInjectorComponent build();
52 | }
53 |
54 | }
55 |
--------------------------------------------------------------------------------
/devicesetup/src/main/java/io/particle/android/sdk/di/ApModule.java:
--------------------------------------------------------------------------------
1 | package io.particle.android.sdk.di;
2 |
3 | import android.content.Context;
4 | import android.support.annotation.RestrictTo;
5 |
6 | import dagger.Module;
7 | import dagger.Provides;
8 | import io.particle.android.sdk.devicesetup.ApConnector;
9 | import io.particle.android.sdk.devicesetup.commands.CommandClientFactory;
10 | import io.particle.android.sdk.devicesetup.setupsteps.SetupStepsFactory;
11 | import io.particle.android.sdk.devicesetup.ui.DiscoverProcessWorker;
12 | import io.particle.android.sdk.utils.SoftAPConfigRemover;
13 | import io.particle.android.sdk.utils.WifiFacade;
14 |
15 | @Module
16 | @RestrictTo({RestrictTo.Scope.LIBRARY})
17 | public class ApModule {
18 |
19 | @Provides
20 | protected SoftAPConfigRemover providesSoftApConfigRemover(Context context, WifiFacade wifiFacade) {
21 | return new SoftAPConfigRemover(context, wifiFacade);
22 | }
23 |
24 | @Provides
25 | protected WifiFacade providesWifiFacade(Context context) {
26 | return WifiFacade.get(context);
27 | }
28 |
29 | @Provides
30 | protected DiscoverProcessWorker providesDiscoverProcessWorker() {
31 | return new DiscoverProcessWorker();
32 | }
33 |
34 | @Provides
35 | protected CommandClientFactory providesCommandClientFactory() {
36 | return new CommandClientFactory();
37 | }
38 |
39 | @Provides
40 | protected SetupStepsFactory providesSetupStepsFactory() {
41 | return new SetupStepsFactory();
42 | }
43 |
44 | @Provides
45 | protected ApConnector providesApConnector(Context context, SoftAPConfigRemover softAPConfigRemover,
46 | WifiFacade wifiFacade) {
47 | return new ApConnector(context, softAPConfigRemover, wifiFacade);
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/devicesetup/src/main/java/io/particle/android/sdk/di/ApplicationComponent.java:
--------------------------------------------------------------------------------
1 | package io.particle.android.sdk.di;
2 |
3 | import android.app.Application;
4 | import android.content.Context;
5 | import android.support.annotation.RestrictTo;
6 |
7 | import com.google.gson.Gson;
8 |
9 | import javax.inject.Singleton;
10 |
11 | import dagger.Component;
12 | import io.particle.android.sdk.cloud.ParticleCloud;
13 |
14 | @Singleton
15 | @Component(modules = {ApplicationModule.class, CloudModule.class})
16 | @RestrictTo({RestrictTo.Scope.LIBRARY})
17 | public interface ApplicationComponent {
18 | ActivityInjectorComponent.Builder activityComponentBuilder();
19 |
20 | Application getApplication();
21 |
22 | Context getContext();
23 |
24 | ParticleCloud getParticleCloud();
25 |
26 | Gson getGson();
27 | }
28 |
--------------------------------------------------------------------------------
/devicesetup/src/main/java/io/particle/android/sdk/di/ApplicationModule.java:
--------------------------------------------------------------------------------
1 | package io.particle.android.sdk.di;
2 |
3 | import android.app.Application;
4 | import android.content.Context;
5 | import android.support.annotation.RestrictTo;
6 |
7 | import javax.inject.Singleton;
8 |
9 | import dagger.Module;
10 | import dagger.Provides;
11 |
12 | @Module
13 | @RestrictTo({RestrictTo.Scope.LIBRARY})
14 | public class ApplicationModule {
15 | private Application application;
16 |
17 | @RestrictTo(RestrictTo.Scope.LIBRARY)
18 | public ApplicationModule(Application application) {
19 | this.application = application;
20 | }
21 |
22 | @Singleton
23 | @Provides
24 | protected Application providesApplication() {
25 | return application;
26 | }
27 |
28 | @Singleton
29 | @Provides
30 | protected Context providesContext() {
31 | return application;
32 | }
33 |
34 | }
35 |
--------------------------------------------------------------------------------
/devicesetup/src/main/java/io/particle/android/sdk/di/CloudModule.java:
--------------------------------------------------------------------------------
1 | package io.particle.android.sdk.di;
2 |
3 | import android.support.annotation.RestrictTo;
4 |
5 | import com.google.gson.Gson;
6 |
7 | import javax.inject.Singleton;
8 |
9 | import dagger.Module;
10 | import dagger.Provides;
11 | import io.particle.android.sdk.cloud.ParticleCloud;
12 | import io.particle.android.sdk.cloud.ParticleCloudSDK;
13 |
14 | @Module
15 | @RestrictTo({RestrictTo.Scope.LIBRARY})
16 | public class CloudModule {
17 |
18 | @Singleton
19 | @Provides
20 | protected ParticleCloud providesParticleCloud() {
21 | return ParticleCloudSDK.getCloud();
22 | }
23 |
24 | @Singleton
25 | @Provides
26 | protected Gson providesGson() {
27 | return new Gson();
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/devicesetup/src/main/java/io/particle/android/sdk/di/PerActivity.java:
--------------------------------------------------------------------------------
1 | package io.particle.android.sdk.di;
2 |
3 | import android.support.annotation.RestrictTo;
4 |
5 | import java.lang.annotation.Documented;
6 | import java.lang.annotation.Retention;
7 | import java.lang.annotation.RetentionPolicy;
8 |
9 | import javax.inject.Scope;
10 |
11 | @Scope
12 | @Documented
13 | @Retention(value = RetentionPolicy.RUNTIME)
14 | @RestrictTo({RestrictTo.Scope.LIBRARY})
15 | public @interface PerActivity {
16 | }
--------------------------------------------------------------------------------
/devicesetup/src/main/java/io/particle/android/sdk/ui/BaseActivity.java:
--------------------------------------------------------------------------------
1 | package io.particle.android.sdk.ui;
2 |
3 | import android.annotation.SuppressLint;
4 | import android.content.Context;
5 | import android.os.Bundle;
6 | import android.support.annotation.RestrictTo;
7 | import android.support.v7.app.AppCompatActivity;
8 |
9 | import io.particle.android.sdk.cloud.SDKGlobals;
10 | import io.particle.android.sdk.devicesetup.R;
11 | import io.particle.android.sdk.utils.SEGAnalytics;
12 | import uk.co.chrisjenx.calligraphy.CalligraphyConfig;
13 | import uk.co.chrisjenx.calligraphy.CalligraphyContextWrapper;
14 |
15 | /**
16 | * This class exists solely to avoid requiring SDK users to have to define
17 | * anything in an Application subclass. By (ab)using this custom Activity,
18 | * we can at least be sure that the custom fonts in the device setup screens
19 | * work correctly without any special instructions.
20 | */
21 | // this is a base activity, it shouldn't be registered.
22 | @SuppressLint("Registered")
23 | public class BaseActivity extends AppCompatActivity {
24 | @RestrictTo(RestrictTo.Scope.LIBRARY)
25 | public static boolean setupOnly = false;
26 | private static boolean customFontInitDone = false;
27 |
28 | @Override
29 | protected void onCreate(Bundle savedInstanceState) {
30 | super.onCreate(savedInstanceState);
31 | SEGAnalytics.initialize(getApplicationContext());
32 | }
33 |
34 | @Override
35 | protected void attachBaseContext(Context newBase) {
36 | if (!customFontInitDone) {
37 | // FIXME: make actually customizable via resources
38 | // (see file extension string formatting nonsense)
39 | CalligraphyConfig.initDefault(
40 | new CalligraphyConfig.Builder()
41 | .setDefaultFontPath(newBase.getString(R.string.normal_text_font_name))
42 | .setFontAttrId(R.attr.fontPath)
43 | .build());
44 | customFontInitDone = true;
45 | }
46 | super.attachBaseContext(CalligraphyContextWrapper.wrap(newBase));
47 | SDKGlobals.init(this);
48 | }
49 |
50 | }
51 |
--------------------------------------------------------------------------------
/devicesetup/src/main/java/io/particle/android/sdk/ui/NextActivitySelector.java:
--------------------------------------------------------------------------------
1 | package io.particle.android.sdk.ui;
2 |
3 | import android.content.Context;
4 | import android.content.Intent;
5 |
6 | import io.particle.android.sdk.accountsetup.CreateAccountActivity;
7 | import io.particle.android.sdk.accountsetup.LoginActivity;
8 | import io.particle.android.sdk.cloud.ParticleCloud;
9 | import io.particle.android.sdk.devicesetup.ParticleDeviceSetupLibrary;
10 | import io.particle.android.sdk.devicesetup.SetupCompleteIntentBuilder;
11 | import io.particle.android.sdk.devicesetup.SetupResult;
12 | import io.particle.android.sdk.persistance.SensitiveDataStorage;
13 | import io.particle.android.sdk.utils.Preconditions;
14 | import io.particle.android.sdk.utils.TLog;
15 |
16 | import static io.particle.android.sdk.utils.Py.any;
17 | import static io.particle.android.sdk.utils.Py.truthy;
18 |
19 | /**
20 | * Selects the next Activity in the workflow, up to the "GetReady" screen or main UI.
21 | */
22 | public class NextActivitySelector {
23 |
24 | private static final TLog log = TLog.get(NextActivitySelector.class);
25 |
26 |
27 | public static Intent getNextActivityIntent(Context ctx,
28 | ParticleCloud particleCloud,
29 | SensitiveDataStorage credStorage,
30 | SetupResult setupResult) {
31 | NextActivitySelector selector = new NextActivitySelector(particleCloud, credStorage,
32 | ParticleDeviceSetupLibrary.getInstance().getSetupCompleteIntentBuilder());
33 |
34 | return selector.buildIntentForNextActivity(ctx, setupResult);
35 | }
36 |
37 |
38 | private final ParticleCloud cloud;
39 | private final SensitiveDataStorage credStorage;
40 | private final SetupCompleteIntentBuilder setupCompleteIntentBuilder;
41 |
42 | private NextActivitySelector(ParticleCloud cloud,
43 | SensitiveDataStorage credStorage,
44 | SetupCompleteIntentBuilder setupCompleteIntentBuilder) {
45 | Preconditions.checkNotNull(setupCompleteIntentBuilder, "SetupCompleteIntentBuilder instance is null");
46 |
47 | this.cloud = cloud;
48 | this.credStorage = credStorage;
49 | this.setupCompleteIntentBuilder = setupCompleteIntentBuilder;
50 | }
51 |
52 | Intent buildIntentForNextActivity(Context ctx, SetupResult result) {
53 | if (!hasUserBeenLoggedInBefore() && !BaseActivity.setupOnly) {
54 | log.d("User has not been logged in before");
55 | return new Intent(ctx, CreateAccountActivity.class);
56 | }
57 |
58 | if (!isOAuthTokenPresent() && !BaseActivity.setupOnly) {
59 | log.d("No auth token present");
60 | return new Intent(ctx, LoginActivity.class);
61 | }
62 |
63 | log.d("Building setup complete activity...");
64 | Intent successActivity = setupCompleteIntentBuilder.buildIntent(ctx, result);
65 |
66 | log.d("Returning setup complete activity");
67 | return successActivity;
68 | }
69 |
70 | boolean hasUserBeenLoggedInBefore() {
71 | return any(credStorage.getUser(), credStorage.getToken());
72 | }
73 |
74 | boolean isOAuthTokenPresent() {
75 | return truthy(cloud.getAccessToken());
76 | }
77 |
78 | }
79 |
--------------------------------------------------------------------------------
/devicesetup/src/main/java/io/particle/android/sdk/utils/BetterAsyncTaskLoader.java:
--------------------------------------------------------------------------------
1 | package io.particle.android.sdk.utils;
2 |
3 | import android.content.Context;
4 | import android.support.v4.content.AsyncTaskLoader;
5 |
6 |
7 | public abstract class BetterAsyncTaskLoader extends AsyncTaskLoader {
8 |
9 |
10 | public abstract boolean hasContent();
11 |
12 | public abstract T getLoadedContent();
13 |
14 |
15 | public BetterAsyncTaskLoader(Context context) {
16 | super(context);
17 | }
18 |
19 | @Override
20 | protected void onStartLoading() {
21 | // How is this not on AsyncTaskLoader already?
22 | if (hasContent()) {
23 | deliverResult(getLoadedContent());
24 | }
25 | if (takeContentChanged() || !hasContent()) {
26 | forceLoad();
27 | }
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/devicesetup/src/main/java/io/particle/android/sdk/utils/CoreNameGenerator.java:
--------------------------------------------------------------------------------
1 | package io.particle.android.sdk.utils;
2 |
3 | import java.util.Random;
4 | import java.util.Set;
5 |
6 |
7 | public class CoreNameGenerator {
8 |
9 | private static final Random random = new Random();
10 |
11 | private static final String[] TROCHEES = new String[]{"aardvark", "bacon", "badger", "banjo",
12 | "bobcat", "boomer", "captain", "chicken", "cowboy", "maker", "splendid", "useful",
13 | "dentist", "doctor", "dozen", "easter", "ferret", "gerbil", "hacker", "hamster",
14 | "sparkling", "hobbit", "hoosier", "hunter", "jester", "jetpack", "kitty", "laser", "lawyer",
15 | "mighty", "monkey", "morphing", "mutant", "narwhal", "ninja", "normal", "penguin",
16 | "pirate", "pizza", "plumber", "power", "puppy", "ranger", "raptor", "robot", "scraper",
17 | "spark", "station", "tasty", "trochee", "turkey", "turtle", "vampire", "wombat",
18 | "zombie"};
19 |
20 |
21 | public static String generateUniqueName(Set existingNames) {
22 | String uniqueName = null;
23 | while (uniqueName == null) {
24 | String part1 = getRandomName();
25 | String part2 = getRandomName();
26 | String candidate = part1 + "_" + part2;
27 | if (!existingNames.contains(candidate) && !part1.equals(part2)) {
28 | uniqueName = candidate;
29 | }
30 | }
31 | return uniqueName;
32 | }
33 |
34 | private static String getRandomName() {
35 | int randomIndex = random.nextInt(TROCHEES.length);
36 | return TROCHEES[randomIndex];
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/devicesetup/src/main/java/io/particle/android/sdk/utils/ParticleDeviceSetupInternalStringUtils.java:
--------------------------------------------------------------------------------
1 | package io.particle.android.sdk.utils;
2 |
3 | /**
4 | * Methods copied from Apache commons-lang, so that I don't have to incur the 4k+ method overhead
5 | * of commons-lang itself.
6 | */
7 | public class ParticleDeviceSetupInternalStringUtils {
8 |
9 | private static final int INDEX_NOT_FOUND = -1;
10 |
11 |
12 | public static String remove(final String str, final String remove) {
13 | if (isEmpty(str) || isEmpty(remove)) {
14 | return str;
15 | }
16 | return replace(str, remove, "", -1);
17 | }
18 |
19 |
20 | public static String removeStart(final String str, final String remove) {
21 | if (isEmpty(str) || isEmpty(remove)) {
22 | return str;
23 | }
24 | if (str.startsWith(remove)){
25 | return str.substring(remove.length());
26 | }
27 | return str;
28 | }
29 |
30 |
31 | public static String removeEnd(final String str, final String remove) {
32 | if (isEmpty(str) || isEmpty(remove)) {
33 | return str;
34 | }
35 | if (str.endsWith(remove)) {
36 | return str.substring(0, str.length() - remove.length());
37 | }
38 | return str;
39 | }
40 |
41 |
42 | private static String replace(final String text, final String searchString,
43 | final String replacement, int max) {
44 | if (isEmpty(text) || isEmpty(searchString) || replacement == null || max == 0) {
45 | return text;
46 | }
47 | int start = 0;
48 | int end = text.indexOf(searchString, start);
49 | if (end == INDEX_NOT_FOUND) {
50 | return text;
51 | }
52 | final int replLength = searchString.length();
53 | int increase = replacement.length() - replLength;
54 | increase = increase < 0 ? 0 : increase;
55 | increase *= max < 0 ? 16 : max > 64 ? 64 : max;
56 | final StringBuilder buf = new StringBuilder(text.length() + increase);
57 | while (end != INDEX_NOT_FOUND) {
58 | buf.append(text.substring(start, end)).append(replacement);
59 | start = end + replLength;
60 | if (--max == 0) {
61 | break;
62 | }
63 | end = text.indexOf(searchString, start);
64 | }
65 | buf.append(text.substring(start));
66 | return buf.toString();
67 | }
68 |
69 | private static boolean isEmpty(final CharSequence cs) {
70 | return cs == null || cs.length() == 0;
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/devicesetup/src/main/java/io/particle/android/sdk/utils/SEGAnalytics.java:
--------------------------------------------------------------------------------
1 | package io.particle.android.sdk.utils;
2 |
3 | import android.annotation.SuppressLint;
4 | import android.content.Context;
5 | import android.support.annotation.RestrictTo;
6 |
7 | import com.segment.analytics.Analytics;
8 | import com.segment.analytics.Properties;
9 |
10 |
11 | /**
12 | * Created by Julius.
13 | */
14 | @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
15 | public class SEGAnalytics {
16 | public static String analyticsKey = "";
17 | public static boolean analyticsOptOut = true;
18 | @SuppressLint("StaticFieldLeak") private static Context context;
19 |
20 | public static void initialize(Context context) {
21 | SEGAnalytics.context = context.getApplicationContext();
22 | try {
23 | Analytics.with(context);
24 | } catch (IllegalArgumentException exception) {
25 | if (!analyticsKey.isEmpty()) {
26 | Analytics analytics = new Analytics.Builder(context, analyticsKey).build();
27 | analytics.optOut(analyticsOptOut);
28 | Analytics.setSingletonInstance(analytics);
29 | }
30 | }
31 | }
32 |
33 | public static void track(String track) {
34 | if (!analyticsKey.isEmpty()) {
35 | Analytics.with(context).track(track);
36 | }
37 | }
38 |
39 | public static void screen(String screen) {
40 | if (!analyticsKey.isEmpty()) {
41 | Analytics.with(context).track(screen);
42 | }
43 | }
44 |
45 | public static void track(String track, Properties analyticProperties) {
46 | if (!analyticsKey.isEmpty()) {
47 | Analytics.with(context).track(track, analyticProperties);
48 | }
49 | }
50 |
51 | public static void identify(String email) {
52 | if (!analyticsKey.isEmpty()) {
53 | Analytics.with(context).identify(email);
54 | }
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/devicesetup/src/main/java/io/particle/android/sdk/utils/SSID.java:
--------------------------------------------------------------------------------
1 | package io.particle.android.sdk.utils;
2 |
3 | import android.net.wifi.ScanResult;
4 | import android.net.wifi.WifiConfiguration;
5 | import android.net.wifi.WifiInfo;
6 | import android.os.Parcel;
7 | import android.os.Parcelable;
8 | import android.support.annotation.NonNull;
9 |
10 | import java.util.Locale;
11 |
12 |
13 | /**
14 | * Simple value wrapper for SSID strings. Eliminates case comparison issues and the quoting
15 | * nonsense introduced by {@link android.net.wifi.WifiConfiguration#SSID} (and potentially elsewhere)
16 | */
17 | public class SSID implements Comparable, Parcelable {
18 |
19 | public static SSID from(@NonNull String rawSsidString) {
20 | Preconditions.checkNotNull(rawSsidString);
21 | return new SSID(deQuotifySsid(rawSsidString));
22 | }
23 |
24 | public static SSID from(WifiInfo wifiInfo) {
25 | return from(wifiInfo.getSSID());
26 | }
27 |
28 | public static SSID from(WifiConfiguration wifiConfiguration) {
29 | return from(wifiConfiguration.SSID);
30 | }
31 |
32 | public static SSID from(ScanResult scanResult) {
33 | return SSID.from(scanResult.SSID);
34 | }
35 |
36 |
37 | private final String ssidString;
38 |
39 | private SSID(String ssidString) {
40 | this.ssidString = ssidString;
41 | }
42 |
43 | @Override
44 | public String toString() {
45 | return ssidString;
46 | }
47 |
48 | public String inQuotes() {
49 | return "\"" + ssidString + "\"";
50 | }
51 |
52 | @Override
53 | public boolean equals(Object o) {
54 | if (this == o) return true;
55 | if (o == null || getClass() != o.getClass()) return false;
56 |
57 | SSID ssid = (SSID) o;
58 |
59 | return ssidString.equalsIgnoreCase(ssid.ssidString);
60 | }
61 |
62 | @Override
63 | public int hashCode() {
64 | return ssidString.toLowerCase(Locale.ROOT).hashCode();
65 | }
66 |
67 | @Override
68 | public int compareTo(@NonNull SSID o) {
69 | return ssidString.compareToIgnoreCase(o.ssidString);
70 | }
71 |
72 | private static String deQuotifySsid(String SSID) {
73 | String quoteMark = "\"";
74 | SSID = ParticleDeviceSetupInternalStringUtils.removeStart(SSID, quoteMark);
75 | SSID = ParticleDeviceSetupInternalStringUtils.removeEnd(SSID, quoteMark);
76 | return SSID;
77 | }
78 |
79 |
80 | //region Parcelable noise
81 | @Override
82 | public int describeContents() {
83 | return 0;
84 | }
85 |
86 | @Override
87 | public void writeToParcel(Parcel dest, int flags) {
88 | dest.writeString(ssidString);
89 | }
90 |
91 | public static final Creator CREATOR = new Creator() {
92 | @Override
93 | public SSID createFromParcel(Parcel in) {
94 | return new SSID(in.readString());
95 | }
96 |
97 | @Override
98 | public SSID[] newArray(int size) {
99 | return new SSID[size];
100 | }
101 | };
102 | //endregion
103 | }
104 |
--------------------------------------------------------------------------------
/devicesetup/src/main/java/io/particle/android/sdk/utils/SoftAPConfigRemover.java:
--------------------------------------------------------------------------------
1 | package io.particle.android.sdk.utils;
2 |
3 | import android.annotation.SuppressLint;
4 | import android.content.Context;
5 | import android.content.SharedPreferences;
6 |
7 | import java.util.Set;
8 |
9 | import static io.particle.android.sdk.utils.Funcy.transformSet;
10 | import static io.particle.android.sdk.utils.Py.set;
11 |
12 |
13 | public class SoftAPConfigRemover {
14 |
15 | private static final TLog log = TLog.get(SoftAPConfigRemover.class);
16 |
17 |
18 | private static final String
19 | PREFS_SOFT_AP_NETWORK_REMOVER = "PREFS_SOFT_AP_NETWORK_REMOVER",
20 | KEY_SOFT_AP_SSIDS = "KEY_SOFT_AP_SSIDS",
21 | KEY_DISABLED_WIFI_SSIDS = "KEY_DISABLED_WIFI_SSIDS";
22 |
23 |
24 | private final SharedPreferences prefs;
25 | private final WifiFacade wifiFacade;
26 |
27 | public SoftAPConfigRemover(Context context, WifiFacade wifiFacade) {
28 | this.wifiFacade = wifiFacade;
29 | Context ctx = context.getApplicationContext();
30 | prefs = ctx.getSharedPreferences(PREFS_SOFT_AP_NETWORK_REMOVER, Context.MODE_PRIVATE);
31 | }
32 |
33 | public void onSoftApConfigured(SSID newSsid) {
34 | // make a defensive copy of what we get back
35 | Set ssids = set(loadSSIDsWithKey(KEY_SOFT_AP_SSIDS));
36 | ssids.add(newSsid);
37 | saveWithKey(KEY_SOFT_AP_SSIDS, ssids);
38 | }
39 |
40 | public void removeAllSoftApConfigs() {
41 | for (SSID ssid : loadSSIDsWithKey(KEY_SOFT_AP_SSIDS)) {
42 | wifiFacade.removeNetwork(ssid);
43 | }
44 | saveWithKey(KEY_SOFT_AP_SSIDS, set());
45 | }
46 |
47 | public void onWifiNetworkDisabled(SSID ssid) {
48 | log.v("onWifiNetworkDisabled() " + ssid);
49 | Set ssids = set(loadSSIDsWithKey(KEY_DISABLED_WIFI_SSIDS));
50 | ssids.add(ssid);
51 | saveWithKey(KEY_DISABLED_WIFI_SSIDS, ssids);
52 | }
53 |
54 | public void reenableWifiNetworks() {
55 | log.v("reenableWifiNetworks()");
56 | for (SSID ssid : loadSSIDsWithKey(KEY_DISABLED_WIFI_SSIDS)) {
57 | wifiFacade.reenableNetwork(ssid);
58 | }
59 | saveWithKey(KEY_DISABLED_WIFI_SSIDS, set());
60 | }
61 |
62 |
63 | private Set loadSSIDsWithKey(String key) {
64 | return Funcy.transformSet(prefs.getStringSet(key, set()), SSID::from);
65 | }
66 |
67 | @SuppressLint("CommitPrefEdits")
68 | private void saveWithKey(String key, Set ssids) {
69 | Set asStrings = transformSet(ssids, SSID::toString);
70 | prefs.edit()
71 | .putStringSet(key, asStrings)
72 | .apply();
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/devicesetup/src/main/java/io/particle/android/sdk/utils/WorkerFragment.java:
--------------------------------------------------------------------------------
1 | package io.particle.android.sdk.utils;
2 |
3 |
4 | import android.content.Context;
5 | import android.os.Bundle;
6 | import android.support.annotation.NonNull;
7 | import android.support.v4.app.Fragment;
8 | import android.support.v4.app.FragmentActivity;
9 | import android.view.LayoutInflater;
10 | import android.view.View;
11 | import android.view.ViewGroup;
12 |
13 | /**
14 | * A simple {@link Fragment} subclass used as a marker for "worker" fragments, while bundling in the
15 | * fundamental behavior that makes them worker fragments.
16 | */
17 | public class WorkerFragment extends Fragment {
18 |
19 | // produce a standardized, unique tag
20 | public static String buildFragmentTag(Class extends Fragment> fragClass) {
21 | // return a unique name
22 | return "FRAG_" + fragClass.getCanonicalName();
23 | }
24 |
25 | // Syntactic sugar for simply adding a WorkerFragment
26 | public static void addFragment(FragmentActivity activity, Fragment frag, String tag) {
27 | activity.getSupportFragmentManager()
28 | .beginTransaction()
29 | .add(frag, tag)
30 | .commit();
31 | }
32 |
33 |
34 | @Override
35 | public void onAttach(Context context) {
36 | super.onAttach(context);
37 | this.setRetainInstance(true);
38 | }
39 |
40 | @Override
41 | public final View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
42 | Bundle savedInstanceState) {
43 | return null;
44 | }
45 |
46 | }
47 |
--------------------------------------------------------------------------------
/devicesetup/src/main/java/io/particle/android/sdk/utils/ui/ParticleUi.java:
--------------------------------------------------------------------------------
1 | package io.particle.android.sdk.utils.ui;
2 |
3 | import android.support.v4.app.FragmentActivity;
4 | import android.view.View;
5 |
6 | import io.particle.android.sdk.devicesetup.R;
7 |
8 |
9 | public class ParticleUi {
10 |
11 | // since it's specific to the SDK UI, this method assumes that the id of the
12 | // progress spinner in the button layout is "R.id.button_progress_indicator"
13 | public static void showParticleButtonProgress(FragmentActivity activity, int buttonId,
14 | final boolean show) {
15 | Ui.fadeViewVisibility(activity, R.id.button_progress_indicator, show);
16 | Ui.findView(activity, buttonId).setEnabled(!show);
17 | }
18 |
19 |
20 | public static void enableBrandLogoInverseVisibilityAgainstSoftKeyboard(FragmentActivity activity) {
21 | SoftKeyboardVisibilityDetectingLinearLayout detectingLayout;
22 | detectingLayout = Ui.findView(activity, R.id.keyboard_change_detector_layout);
23 | detectingLayout.setOnSoftKeyboardVisibilityChangeListener(new BrandImageHeaderHider(activity));
24 | }
25 |
26 |
27 | public static class BrandImageHeaderHider
28 | implements SoftKeyboardVisibilityDetectingLinearLayout.SoftKeyboardVisibilityChangeListener {
29 |
30 | final View logoView;
31 |
32 | public BrandImageHeaderHider(FragmentActivity activity) {
33 | logoView = Ui.findView(activity, R.id.brand_image_header);
34 | }
35 |
36 | @Override
37 | public void onSoftKeyboardShown() {
38 | logoView.setVisibility(View.GONE);
39 | }
40 |
41 | @Override
42 | public void onSoftKeyboardHidden() {
43 | logoView.setVisibility(View.VISIBLE);
44 | }
45 |
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/devicesetup/src/main/java/io/particle/android/sdk/utils/ui/SoftKeyboardVisibilityDetectingLinearLayout.java:
--------------------------------------------------------------------------------
1 | package io.particle.android.sdk.utils.ui;
2 |
3 | import android.content.Context;
4 | import android.util.AttributeSet;
5 | import android.view.KeyEvent;
6 | import android.widget.LinearLayout;
7 |
8 | /**
9 | * How can Android have gone this long without this ability being built-in...?
10 | *
11 | * Derived from: https://gist.github.com/mrleolink/8823150
12 | *
13 | */
14 | public class SoftKeyboardVisibilityDetectingLinearLayout extends LinearLayout {
15 |
16 |
17 | public interface SoftKeyboardVisibilityChangeListener {
18 |
19 | void onSoftKeyboardShown();
20 |
21 | void onSoftKeyboardHidden();
22 |
23 | }
24 |
25 |
26 | private boolean isKeyboardShown;
27 | private SoftKeyboardVisibilityChangeListener listener;
28 |
29 | public SoftKeyboardVisibilityDetectingLinearLayout(Context context) {
30 | super(context);
31 | }
32 |
33 | public SoftKeyboardVisibilityDetectingLinearLayout(Context context, AttributeSet attrs) {
34 | super(context, attrs);
35 | }
36 |
37 | public SoftKeyboardVisibilityDetectingLinearLayout(Context context, AttributeSet attrs, int defStyle) {
38 | super(context, attrs, defStyle);
39 | }
40 |
41 |
42 | @Override
43 | public boolean dispatchKeyEventPreIme(KeyEvent event) {
44 | if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) {
45 | // Keyboard is hidden
46 | if (isKeyboardShown) {
47 | isKeyboardShown = false;
48 | listener.onSoftKeyboardHidden();
49 | }
50 | }
51 | return super.dispatchKeyEventPreIme(event);
52 | }
53 |
54 | @Override
55 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
56 | final int proposedheight = MeasureSpec.getSize(heightMeasureSpec);
57 | final int actualHeight = getHeight();
58 | if (actualHeight > proposedheight) {
59 | // Keyboard is shown
60 | if (!isKeyboardShown) {
61 | isKeyboardShown = true;
62 | listener.onSoftKeyboardShown();
63 | }
64 | } else {
65 | // Keyboard is hidden <<< this doesn't work sometimes, so I don't use it
66 | }
67 | super.onMeasure(widthMeasureSpec, heightMeasureSpec);
68 | }
69 |
70 | public void setOnSoftKeyboardVisibilityChangeListener(SoftKeyboardVisibilityChangeListener listener) {
71 | this.listener = listener;
72 | }
73 |
74 | }
75 |
--------------------------------------------------------------------------------
/devicesetup/src/main/java/io/particle/android/sdk/utils/ui/Toaster.java:
--------------------------------------------------------------------------------
1 | package io.particle.android.sdk.utils.ui;
2 |
3 | import android.content.Context;
4 | import android.support.v4.app.Fragment;
5 | import android.view.View;
6 | import android.widget.Toast;
7 |
8 |
9 | public class Toaster {
10 |
11 | public static void s(Context ctx, String msg) {
12 | Toast.makeText(ctx, msg, Toast.LENGTH_SHORT).show();
13 | }
14 |
15 | public static void l(Context ctx, String msg) {
16 | Toast.makeText(ctx, msg, Toast.LENGTH_LONG).show();
17 | }
18 |
19 | public static void s(Context ctx, String msg, int gravity) {
20 | Toast t = Toast.makeText(ctx, msg, Toast.LENGTH_SHORT);
21 | t.setGravity(gravity, 0, 0);
22 | t.show();
23 | }
24 |
25 | public static void l(Context ctx, String msg, int gravity) {
26 | Toast t = Toast.makeText(ctx, msg, Toast.LENGTH_LONG);
27 | t.setGravity(gravity, 0, 0);
28 | t.show();
29 | }
30 |
31 | public static void s(Fragment frag, String msg) {
32 | s(frag.getActivity(), msg);
33 | }
34 |
35 | public static void l(Fragment frag, String msg) {
36 | l(frag.getActivity(), msg);
37 | }
38 |
39 | public static void s(View view, String msg) {
40 | s(view.getContext(), msg);
41 | }
42 |
43 | public static void l(View view, String msg) {
44 | l(view.getContext(), msg);
45 | }
46 | }
47 |
48 |
49 |
--------------------------------------------------------------------------------
/devicesetup/src/main/java/io/particle/android/sdk/utils/ui/WebViewActivity.java:
--------------------------------------------------------------------------------
1 | package io.particle.android.sdk.utils.ui;
2 |
3 | import android.annotation.SuppressLint;
4 | import android.content.Context;
5 | import android.content.Intent;
6 | import android.net.Uri;
7 | import android.os.Bundle;
8 | import android.support.v7.app.AppCompatActivity;
9 | import android.support.v7.widget.Toolbar;
10 | import android.webkit.WebSettings;
11 | import android.webkit.WebView;
12 | import android.webkit.WebViewClient;
13 |
14 | import io.particle.android.sdk.devicesetup.R;
15 | import io.particle.android.sdk.utils.SEGAnalytics;
16 |
17 |
18 | public class WebViewActivity extends AppCompatActivity {
19 |
20 |
21 | private static final String EXTRA_CONTENT_URI = "EXTRA_CONTENT_URI";
22 | private static final String EXTRA_PAGE_TITLE = "EXTRA_PAGE_TITLE";
23 |
24 |
25 | public static Intent buildIntent(Context ctx, Uri uri) {
26 | return new Intent(ctx, WebViewActivity.class)
27 | .putExtra(EXTRA_CONTENT_URI, uri);
28 | }
29 |
30 |
31 | public static Intent buildIntent(Context ctx, Uri uri, CharSequence pageTitle) {
32 | return buildIntent(ctx, uri)
33 | .putExtra(EXTRA_PAGE_TITLE, pageTitle.toString());
34 | }
35 |
36 |
37 | @SuppressLint("SetJavaScriptEnabled")
38 | @Override
39 | protected void onCreate(Bundle savedInstanceState) {
40 | super.onCreate(savedInstanceState);
41 | setContentView(R.layout.activity_web_view);
42 | SEGAnalytics.track("Device Setup: Webview Screen");
43 | Toolbar toolbar = Ui.findView(this, R.id.toolbar);
44 | toolbar.setNavigationIcon(
45 | Ui.getTintedDrawable(this, R.drawable.ic_clear_black_24dp, R.color.element_tint_color));
46 |
47 | toolbar.setNavigationOnClickListener(view -> finish());
48 |
49 | if (getIntent().hasExtra(EXTRA_PAGE_TITLE)) {
50 | toolbar.setTitle(getIntent().getStringExtra(EXTRA_PAGE_TITLE));
51 | }
52 |
53 | WebView webView = Ui.findView(this, R.id.web_content);
54 |
55 | webView.setWebViewClient(new WebViewClient() {
56 | @Override
57 | public boolean shouldOverrideUrlLoading(WebView view, String url) {
58 | // handle redirects in the same view
59 | view.loadUrl(url);
60 | // return false to indicate that we do not want to leave the webview
61 | return false; // then it is not handled by default action
62 | }
63 | });
64 |
65 | WebSettings webSettings = webView.getSettings();
66 | // this has to be enabled or else some pages don't render *at all.*
67 | webSettings.setJavaScriptEnabled(true);
68 |
69 | Uri uri = getIntent().getParcelableExtra(EXTRA_CONTENT_URI);
70 | webView.loadUrl(uri.toString());
71 | }
72 |
73 | }
74 |
--------------------------------------------------------------------------------
/devicesetup/src/main/res/drawable-hdpi/ic_clear_black_24dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/particle-iot/spark-setup-android/60290536c1be65b78ac8ef0d475999eb468a24c0/devicesetup/src/main/res/drawable-hdpi/ic_clear_black_24dp.png
--------------------------------------------------------------------------------
/devicesetup/src/main/res/drawable-mdpi/ic_clear_black_24dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/particle-iot/spark-setup-android/60290536c1be65b78ac8ef0d475999eb468a24c0/devicesetup/src/main/res/drawable-mdpi/ic_clear_black_24dp.png
--------------------------------------------------------------------------------
/devicesetup/src/main/res/drawable-xhdpi/checkmark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/particle-iot/spark-setup-android/60290536c1be65b78ac8ef0d475999eb468a24c0/devicesetup/src/main/res/drawable-xhdpi/checkmark.png
--------------------------------------------------------------------------------
/devicesetup/src/main/res/drawable-xhdpi/ic_clear_black_24dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/particle-iot/spark-setup-android/60290536c1be65b78ac8ef0d475999eb468a24c0/devicesetup/src/main/res/drawable-xhdpi/ic_clear_black_24dp.png
--------------------------------------------------------------------------------
/devicesetup/src/main/res/drawable-xxhdpi/fail.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/particle-iot/spark-setup-android/60290536c1be65b78ac8ef0d475999eb468a24c0/devicesetup/src/main/res/drawable-xxhdpi/fail.png
--------------------------------------------------------------------------------
/devicesetup/src/main/res/drawable-xxhdpi/ic_clear_black_24dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/particle-iot/spark-setup-android/60290536c1be65b78ac8ef0d475999eb468a24c0/devicesetup/src/main/res/drawable-xxhdpi/ic_clear_black_24dp.png
--------------------------------------------------------------------------------
/devicesetup/src/main/res/drawable-xxhdpi/particle_vertical_blue.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/particle-iot/spark-setup-android/60290536c1be65b78ac8ef0d475999eb468a24c0/devicesetup/src/main/res/drawable-xxhdpi/particle_vertical_blue.png
--------------------------------------------------------------------------------
/devicesetup/src/main/res/drawable-xxhdpi/photon_vector.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/particle-iot/spark-setup-android/60290536c1be65b78ac8ef0d475999eb468a24c0/devicesetup/src/main/res/drawable-xxhdpi/photon_vector.png
--------------------------------------------------------------------------------
/devicesetup/src/main/res/drawable-xxhdpi/photon_vector_small.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/particle-iot/spark-setup-android/60290536c1be65b78ac8ef0d475999eb468a24c0/devicesetup/src/main/res/drawable-xxhdpi/photon_vector_small.png
--------------------------------------------------------------------------------
/devicesetup/src/main/res/drawable-xxhdpi/success.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/particle-iot/spark-setup-android/60290536c1be65b78ac8ef0d475999eb468a24c0/devicesetup/src/main/res/drawable-xxhdpi/success.png
--------------------------------------------------------------------------------
/devicesetup/src/main/res/drawable-xxhdpi/the_wifi.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/particle-iot/spark-setup-android/60290536c1be65b78ac8ef0d475999eb468a24c0/devicesetup/src/main/res/drawable-xxhdpi/the_wifi.png
--------------------------------------------------------------------------------
/devicesetup/src/main/res/drawable-xxhdpi/trianglifybackground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/particle-iot/spark-setup-android/60290536c1be65b78ac8ef0d475999eb468a24c0/devicesetup/src/main/res/drawable-xxhdpi/trianglifybackground.png
--------------------------------------------------------------------------------
/devicesetup/src/main/res/drawable-xxxhdpi/ic_clear_black_24dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/particle-iot/spark-setup-android/60290536c1be65b78ac8ef0d475999eb468a24c0/devicesetup/src/main/res/drawable-xxxhdpi/ic_clear_black_24dp.png
--------------------------------------------------------------------------------
/devicesetup/src/main/res/drawable-xxxhdpi/lock.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/particle-iot/spark-setup-android/60290536c1be65b78ac8ef0d475999eb468a24c0/devicesetup/src/main/res/drawable-xxxhdpi/lock.png
--------------------------------------------------------------------------------
/devicesetup/src/main/res/drawable-xxxhdpi/particle_horizontal_blue.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/particle-iot/spark-setup-android/60290536c1be65b78ac8ef0d475999eb468a24c0/devicesetup/src/main/res/drawable-xxxhdpi/particle_horizontal_blue.png
--------------------------------------------------------------------------------
/devicesetup/src/main/res/drawable-xxxhdpi/particle_horizontal_head.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/particle-iot/spark-setup-android/60290536c1be65b78ac8ef0d475999eb468a24c0/devicesetup/src/main/res/drawable-xxxhdpi/particle_horizontal_head.png
--------------------------------------------------------------------------------
/devicesetup/src/main/res/drawable/button_text_color_selector.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/devicesetup/src/main/res/drawable/link_text_selector.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/devicesetup/src/main/res/drawable/progress_indicator_graphic.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/particle-iot/spark-setup-android/60290536c1be65b78ac8ef0d475999eb468a24c0/devicesetup/src/main/res/drawable/progress_indicator_graphic.png
--------------------------------------------------------------------------------
/devicesetup/src/main/res/drawable/progress_spinner.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/devicesetup/src/main/res/layout/activity_discover_device.xml:
--------------------------------------------------------------------------------
1 |
9 |
10 |
11 |
12 |
21 |
22 |
30 |
31 |
37 |
38 |
45 |
46 |
57 |
58 |
67 |
68 |
77 |
78 |
83 |
84 |
89 |
90 |
91 |
92 |
93 |
--------------------------------------------------------------------------------
/devicesetup/src/main/res/layout/activity_get_ready.xml:
--------------------------------------------------------------------------------
1 |
10 |
11 |
12 |
13 |
22 |
23 |
31 |
32 |
40 |
41 |
50 |
51 |
54 |
55 |
59 |
60 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
--------------------------------------------------------------------------------
/devicesetup/src/main/res/layout/activity_manual_network_entry.xml:
--------------------------------------------------------------------------------
1 |
11 |
12 |
13 |
14 |
22 |
23 |
29 |
30 |
37 |
38 |
45 |
46 |
52 |
53 |
58 |
59 |
60 |
61 |
62 |
63 |
--------------------------------------------------------------------------------
/devicesetup/src/main/res/layout/activity_password_entry.xml:
--------------------------------------------------------------------------------
1 |
10 |
11 |
12 |
13 |
22 |
23 |
28 |
29 |
36 |
37 |
48 |
49 |
58 |
59 |
60 |
61 |
71 |
72 |
80 |
81 |
86 |
87 |
92 |
93 |
94 |
95 |
96 |
--------------------------------------------------------------------------------
/devicesetup/src/main/res/layout/activity_password_reset.xml:
--------------------------------------------------------------------------------
1 |
12 |
13 |
14 |
15 |
24 |
25 |
34 |
35 |
44 |
45 |
50 |
51 |
56 |
57 |
62 |
63 |
64 |
65 |
69 |
70 |
71 |
72 |
--------------------------------------------------------------------------------
/devicesetup/src/main/res/layout/activity_select_network.xml:
--------------------------------------------------------------------------------
1 |
10 |
11 |
12 |
13 |
22 |
23 |
28 |
29 |
37 |
38 |
45 |
46 |
49 |
50 |
55 |
56 |
61 |
62 |
63 |
64 |
65 |
66 |
--------------------------------------------------------------------------------
/devicesetup/src/main/res/layout/activity_success.xml:
--------------------------------------------------------------------------------
1 |
9 |
10 |
11 |
12 |
21 |
22 |
28 |
29 |
35 |
36 |
47 |
48 |
55 |
56 |
65 |
66 |
75 |
76 |
80 |
84 |
85 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
--------------------------------------------------------------------------------
/devicesetup/src/main/res/layout/activity_two_factor.xml:
--------------------------------------------------------------------------------
1 |
13 |
14 |
15 |
16 |
17 |
23 |
24 |
34 |
35 |
42 |
43 |
51 |
52 |
59 |
60 |
64 |
65 |
69 |
70 |
75 |
76 |
77 |
78 |
89 |
90 |
91 |
92 |
93 |
94 |
--------------------------------------------------------------------------------
/devicesetup/src/main/res/layout/activity_web_view.xml:
--------------------------------------------------------------------------------
1 |
6 |
7 |
12 |
13 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/devicesetup/src/main/res/layout/brand_image_header.xml:
--------------------------------------------------------------------------------
1 |
2 |
15 |
--------------------------------------------------------------------------------
/devicesetup/src/main/res/layout/row_wifi_scan_result.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
23 |
24 |
34 |
35 |
46 |
47 |
--------------------------------------------------------------------------------
/devicesetup/src/main/res/values-sw820dp/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 | 64dp
6 |
7 |
--------------------------------------------------------------------------------
/devicesetup/src/main/res/values-v19/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 12dp
4 | 24dp
5 |
--------------------------------------------------------------------------------
/devicesetup/src/main/res/values-v19/themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
11 |
12 |
--------------------------------------------------------------------------------
/devicesetup/src/main/res/values-v21/themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
11 |
12 |
--------------------------------------------------------------------------------
/devicesetup/src/main/res/values-xlarge/dimens_font_size.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 14sp
5 | 16sp
6 | 18sp
7 | 18sp
8 | 26sp
9 |
10 |
--------------------------------------------------------------------------------
/devicesetup/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #5222
4 | #E5FFFFFF
5 |
--------------------------------------------------------------------------------
/devicesetup/src/main/res/values/devicesetup_styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
12 |
13 |
14 |
18 |
19 |
27 |
28 |
35 |
36 |
49 |
50 |
60 |
61 |
62 |
--------------------------------------------------------------------------------
/devicesetup/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 144dp
4 |
5 |
6 | 24dp
7 | 24dp
8 | 0dp
9 | 0dp
10 |
11 | 320dip
12 |
--------------------------------------------------------------------------------
/devicesetup/src/main/res/values/dimens_font_size.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 10sp
5 | 12sp
6 | 14sp
7 | 18sp
8 | 22sp
9 |
10 | 112sp
11 | 56sp
12 | 45sp
13 | 34sp
14 | 24sp
15 | 20sp
16 | 16sp
17 | 14sp
18 | 12sp
19 |
20 |
--------------------------------------------------------------------------------
/devicesetup/src/main/res/values/ids.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/devicesetup/src/main/res/values/strings_activity_login.xml:
--------------------------------------------------------------------------------
1 |
2 | Log In
3 |
4 | first name
5 | last name
6 | company name
7 | This is a personal account
8 | This is a business account
9 | email
10 | password
11 | verify password
12 | Log In
13 | LOG IN
14 |
15 | This email address is invalid
16 | This password is too short
17 | This password is incorrect
18 | This activation code is invalid
19 | This field is required
20 |
21 |
22 |
--------------------------------------------------------------------------------
/devicesetup/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
11 |
12 |
--------------------------------------------------------------------------------
/devicesetup/src/main/res/values/success_failure_messages.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Setup completed successfully!
5 | Congrats! You\'ve successfully set up your {device_name}!
6 |
7 | Setup completed!
8 | Setup was successful, but you\'re not the primary owner so we can\'t check if the {device_name} connected to the Internet. If you see the LED breathing cyan this means it worked! If not, please restart the setup process.
9 |
10 | Oops!
11 | Setup process couldn\'t claim your {device_name}! If the {device_name} LED is blinking blue or green then you may have mistyped the Wi-Fi credentials and you should try setup again. If the LED is breathing cyan a server issue may have occurred - please contact support.
12 |
13 | Uh oh!
14 | Setup process couldn\'t disconnect from the {device_name} Wi-Fi network. This is an internal problem with the device, so please try running setup again after resetting your {device_name} and putting it back in blinking blue listen mode if needed.
15 |
16 | Error!
17 | Setup process couldn\'t configure the Wi-Fi credentials for your {device_name}, please try running setup again after resetting your {device_name} and putting it back in blinking blue listen mode if needed.
18 |
19 | Setup process lost connection to the {device_name} before being able to configure it to use the supplied Wi-fi credentials, please try running setup again after resetting your {device_name} and putting it back in listen mode if needed.
20 |
21 |
--------------------------------------------------------------------------------
/devicesetup/src/main/res/values/themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
28 |
29 |
33 |
34 |
39 |
40 |
43 |
44 |
48 |
49 |
52 |
53 |
63 |
64 |
--------------------------------------------------------------------------------
/exampleapp/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 |
3 | android {
4 | compileSdkVersion 27
5 | buildToolsVersion '27.0.3'
6 |
7 | defaultConfig {
8 | applicationId "io.particle.devicesetup.exampleapp"
9 | minSdkVersion 16
10 | targetSdkVersion 27
11 | versionCode 1
12 | versionName "1.0"
13 |
14 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
15 | }
16 |
17 | compileOptions {
18 | sourceCompatibility JavaVersion.VERSION_1_8
19 | targetCompatibility JavaVersion.VERSION_1_8
20 | }
21 |
22 | buildTypes {
23 | release {
24 | minifyEnabled false
25 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
26 | }
27 | }
28 |
29 | packagingOptions {
30 | exclude 'META-INF/LICENSE.txt'
31 | exclude 'META-INF/NOTICE.txt'
32 | }
33 | }
34 |
35 | dependencies {
36 | compile fileTree(include: ['*.jar'], dir: 'libs')
37 |
38 | // BY DEFAULT, BUILD APP AGAINST THE LOCAL SETUP LIB SOURCE REPO
39 | // (i.e.: you can make modifications to the device setup lib source in the local repo, and
40 | // the changes will appear in the example app just by rebuilding, as you'd expect)
41 | compile project(':devicesetup')
42 | //
43 | // *OR*
44 | //
45 | // comment out that above line, and
46 | // UNCOMMENT THE FOLLOWING TO USE A PUBLISHED VERSION OF THE SDK:
47 | // compile 'io.particle:devicesetup:0.2.0'
48 |
49 | implementation 'com.android.support:support-v4:27.1.1'
50 | implementation 'com.android.support:appcompat-v7:27.1.1'
51 | }
--------------------------------------------------------------------------------
/exampleapp/lint.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/exampleapp/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # By default, the flags in this file are appended to flags specified
3 | # in /Users/ido/Library/Android/sdk/tools/proguard/proguard-android.txt
4 | # You can edit the include path and order by changing the proguardFiles
5 | # directive in build.gradle.
6 | #
7 | # For more details, see
8 | # http://developer.android.com/guide/developing/tools/proguard.html
9 |
10 | # Add any project specific keep options here:
11 |
12 | # If your project uses WebView with JS, uncomment the following
13 | # and specify the fully qualified class name to the JavaScript interface
14 | # class:
15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
16 | # public *;
17 | #}
18 |
--------------------------------------------------------------------------------
/exampleapp/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
10 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/exampleapp/src/main/java/io/particle/devicesetup/exampleapp/ExampleSetupCompleteIntentBuilder.java:
--------------------------------------------------------------------------------
1 | package io.particle.devicesetup.exampleapp;
2 |
3 | import android.content.Context;
4 | import android.content.Intent;
5 |
6 | import io.particle.android.sdk.devicesetup.SetupCompleteIntentBuilder;
7 | import io.particle.android.sdk.devicesetup.SetupResult;
8 |
9 | public class ExampleSetupCompleteIntentBuilder implements SetupCompleteIntentBuilder {
10 | private final String setupLaunchedTime;
11 |
12 | ExampleSetupCompleteIntentBuilder(String setupLaunchedTime) {
13 | this.setupLaunchedTime = setupLaunchedTime;
14 | }
15 |
16 | @Override
17 | public Intent buildIntent(Context ctx, SetupResult result) {
18 | Intent intent = new Intent(ctx, MainActivity.class);
19 | intent.putExtra(MainActivity.EXTRA_SETUP_LAUNCHED_TIME, setupLaunchedTime);
20 |
21 | return intent;
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/exampleapp/src/main/java/io/particle/devicesetup/exampleapp/MainActivity.java:
--------------------------------------------------------------------------------
1 | package io.particle.devicesetup.exampleapp;
2 |
3 | import android.os.Bundle;
4 | import android.support.v7.app.AppCompatActivity;
5 | import android.widget.TextView;
6 |
7 | import java.util.Date;
8 |
9 | import io.particle.android.sdk.devicesetup.ParticleDeviceSetupLibrary;
10 | import io.particle.android.sdk.utils.ui.Ui;
11 |
12 | public class MainActivity extends AppCompatActivity {
13 | public final static String EXTRA_SETUP_LAUNCHED_TIME = "io.particle.devicesetup.exampleapp.SETUP_LAUNCHED_TIME";
14 |
15 | @Override
16 | protected void onCreate(Bundle savedInstanceState) {
17 | super.onCreate(savedInstanceState);
18 | setContentView(R.layout.activity_main);
19 | ParticleDeviceSetupLibrary.init(this.getApplicationContext());
20 |
21 | Ui.findView(this, R.id.start_setup_button).setOnClickListener(view -> invokeDeviceSetup());
22 | Ui.findView(this, R.id.start_setup_custom_intent_button).setOnClickListener(v -> invokeDeviceSetupWithCustomIntentBuilder());
23 |
24 | String setupLaunchTime = this.getIntent().getStringExtra(EXTRA_SETUP_LAUNCHED_TIME);
25 |
26 | if (setupLaunchTime != null) {
27 | TextView label = Ui.findView(this, R.id.textView);
28 |
29 | label.setText(String.format(getString(R.string.welcome_back), setupLaunchTime));
30 | }
31 | }
32 |
33 | public void invokeDeviceSetup() {
34 | ParticleDeviceSetupLibrary.startDeviceSetup(this, MainActivity.class);
35 | }
36 |
37 | private void invokeDeviceSetupWithCustomIntentBuilder() {
38 | final String setupLaunchedTime = new Date().toString();
39 |
40 | // Important: don't use an anonymous inner class to implement SetupCompleteIntentBuilder, otherwise you will cause a memory leak.
41 | ParticleDeviceSetupLibrary.startDeviceSetup(this, new ExampleSetupCompleteIntentBuilder(setupLaunchedTime));
42 | }
43 |
44 | }
45 |
--------------------------------------------------------------------------------
/exampleapp/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
10 |
11 |
16 |
17 |
24 |
25 |
33 |
34 |
35 |
--------------------------------------------------------------------------------
/exampleapp/src/main/res/menu/menu_main.xml:
--------------------------------------------------------------------------------
1 |
11 |
--------------------------------------------------------------------------------
/exampleapp/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/particle-iot/spark-setup-android/60290536c1be65b78ac8ef0d475999eb468a24c0/exampleapp/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/exampleapp/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/particle-iot/spark-setup-android/60290536c1be65b78ac8ef0d475999eb468a24c0/exampleapp/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/exampleapp/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/particle-iot/spark-setup-android/60290536c1be65b78ac8ef0d475999eb468a24c0/exampleapp/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/exampleapp/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/particle-iot/spark-setup-android/60290536c1be65b78ac8ef0d475999eb468a24c0/exampleapp/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/exampleapp/src/main/res/values-w820dp/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 | 64dp
6 |
7 |
--------------------------------------------------------------------------------
/exampleapp/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 16dp
4 | 16dp
5 |
6 |
--------------------------------------------------------------------------------
/exampleapp/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | Example app
3 |
4 | Hello world!
5 | Welcome back! You\'ve returned from setup, which started at %s.
6 |
7 | Settings
8 |
9 |
10 | FULL
11 |
12 |
--------------------------------------------------------------------------------
/exampleapp/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 | # IDE (e.g. Android Studio) users:
3 | # Gradle settings configured through the IDE *will override*
4 | # any settings specified in this file.
5 | # For more details on how to configure your build environment visit
6 | # http://www.gradle.org/docs/current/userguide/build_environment.html
7 | # Specifies the JVM arguments used for the daemon process.
8 | # The setting is particularly useful for tweaking memory settings.
9 | # Default value: -Xmx10248m -XX:MaxPermSize=256m
10 | # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
11 | #org.gradle.jvmargs=-Xms256m -Xmx1024m -XX:MaxPermSize=384m -XX:ReservedCodeCacheSize=128m
12 | org.gradle.jvmargs=-Xmx4096M -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
13 | # When configured, Gradle will run in incubating parallel mode.
14 | # This option should only be used with decoupled projects. More details, visit
15 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
16 | org.gradle.parallel=true
17 | # run in daemon mode instead of initializing a new gradle process every time; speeds up re-building
18 | org.gradle.daemon=true
19 | #Configure only relevant projects
20 | #org.gradle.configureondemand=true
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/particle-iot/spark-setup-android/60290536c1be65b78ac8ef0d475999eb468a24c0/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Mon Jul 23 00:25:48 EEST 2018
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-4.4-all.zip
7 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
12 | set DEFAULT_JVM_OPTS=
13 |
14 | set DIRNAME=%~dp0
15 | if "%DIRNAME%" == "" set DIRNAME=.
16 | set APP_BASE_NAME=%~n0
17 | set APP_HOME=%DIRNAME%
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windowz variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 | if "%@eval[2+2]" == "4" goto 4NT_args
53 |
54 | :win9xME_args
55 | @rem Slurp the command line arguments.
56 | set CMD_LINE_ARGS=
57 | set _SKIP=2
58 |
59 | :win9xME_args_slurp
60 | if "x%~1" == "x" goto execute
61 |
62 | set CMD_LINE_ARGS=%*
63 | goto execute
64 |
65 | :4NT_args
66 | @rem Get arguments from the 4NT Shell from JP Software
67 | set CMD_LINE_ARGS=%$
68 |
69 | :execute
70 | @rem Setup the command line
71 |
72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if "%ERRORLEVEL%"=="0" goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
85 | exit /b 1
86 |
87 | :mainEnd
88 | if "%OS%"=="Windows_NT" endlocal
89 |
90 | :omega
91 |
--------------------------------------------------------------------------------
/pom_generator_v1.gradle:
--------------------------------------------------------------------------------
1 | // lifted from: https://raw.githubusercontent.com/nuuneoi/JCenter/master/installv1.gradle
2 | // and copied into this repo to maintain a hermetic build
3 | apply plugin: 'com.github.dcendents.android-maven'
4 |
5 | group = publishedGroupId // Maven Group ID for the artifact
6 |
7 | install {
8 | repositories.mavenInstaller {
9 | // This generates POM.xml with proper parameters
10 | pom {
11 | project {
12 | packaging 'aar'
13 | groupId publishedGroupId
14 | artifactId artifact
15 |
16 | // Add your description here
17 | name libraryName
18 | description libraryDescription
19 | url siteUrl
20 |
21 | // Set your license
22 | licenses {
23 | license {
24 | name licenseName
25 | url licenseUrl
26 | }
27 | }
28 | developers {
29 | developer {
30 | id developerId
31 | name developerName
32 | email developerEmail
33 | }
34 | }
35 | scm {
36 | connection gitUrl
37 | developerConnection gitUrl
38 | url siteUrl
39 |
40 | }
41 | }
42 | }
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':devicesetup', ':exampleapp', ':testapp'
2 |
--------------------------------------------------------------------------------
/testapp/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/testapp/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 |
3 | android {
4 | compileSdkVersion 27
5 | buildToolsVersion "27.0.3"
6 |
7 | defaultConfig {
8 | applicationId "io.particle.devicesetup.testapp"
9 | minSdkVersion 15
10 | targetSdkVersion 27
11 | versionCode 1
12 | versionName "1.0"
13 |
14 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
15 | }
16 | buildTypes {
17 | release {
18 | minifyEnabled false
19 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
20 | }
21 | }
22 | compileOptions {
23 | sourceCompatibility JavaVersion.VERSION_1_8
24 | targetCompatibility JavaVersion.VERSION_1_8
25 | }
26 | }
27 |
28 | dependencies {
29 | compile fileTree(dir: 'libs', include: ['*.jar'])
30 | compile 'com.android.support:appcompat-v7:27.1.1'
31 | compile 'com.android.support.constraint:constraint-layout:1.1.2'
32 |
33 | compile project(':devicesetup')
34 |
35 | testCompile "junit:junit:4.12"
36 | testCompile 'com.github.fabioCollini:DaggerMock:0.7.0'
37 | testCompile "org.mockito:mockito-core:2.16.0"
38 | testCompile "com.google.dagger:dagger-compiler:2.15"
39 |
40 | androidTestCompile 'com.android.support:support-annotations:27.1.1'
41 | androidTestCompile "org.mockito:mockito-core:2.16.0"
42 | androidTestCompile "org.mockito:mockito-android:2.8.8"
43 | androidTestCompile "com.android.support.test:runner:1.0.2"
44 | androidTestCompile "com.android.support.test:rules:1.0.2"
45 | androidTestCompile('com.android.support.test.espresso:espresso-core:3.0.0', {
46 | exclude group: 'com.google.code.findbugs', module: 'jsr305'
47 | })
48 | androidTestCompile 'com.github.fabioCollini:DaggerMock:0.7.0'
49 | }
50 |
51 | repositories {
52 | maven {
53 | url 'https://maven.google.com'
54 | }
55 | maven { url "https://jitpack.io" }
56 | }
--------------------------------------------------------------------------------
/testapp/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # By default, the flags in this file are appended to flags specified
3 | # in /Users/azemar/Library/Android/sdk/tools/proguard/proguard-android.txt
4 | # You can edit the include path and order by changing the proguardFiles
5 | # directive in build.gradle.
6 | #
7 | # For more details, see
8 | # http://developer.android.com/guide/developing/tools/proguard.html
9 |
10 | # Add any project specific keep options here:
11 |
12 | # If your project uses WebView with JS, uncomment the following
13 | # and specify the fully qualified class name to the JavaScript interface
14 | # class:
15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
16 | # public *;
17 | #}
18 |
19 | # Uncomment this to preserve the line number information for
20 | # debugging stack traces.
21 | #-keepattributes SourceFile,LineNumberTable
22 |
23 | # If you keep the line number information, uncomment this to
24 | # hide the original source file name.
25 | #-renamesourcefileattribute SourceFile
26 |
--------------------------------------------------------------------------------
/testapp/src/androidTest/java/io/particle/devicesetup/testapp/ApplicationTest.java:
--------------------------------------------------------------------------------
1 | package io.particle.devicesetup.testapp;
2 |
3 | import android.app.Application;
4 | import android.test.ApplicationTestCase;
5 |
6 | /**
7 | * Testing Fundamentals
8 | */
9 | public class ApplicationTest extends ApplicationTestCase {
10 | public ApplicationTest() {
11 | super(Application.class);
12 | }
13 | }
--------------------------------------------------------------------------------
/testapp/src/androidTest/java/io/particle/devicesetup/testapp/CustomAndroidTestRunner.java:
--------------------------------------------------------------------------------
1 | package io.particle.devicesetup.testapp;
2 |
3 | import android.app.Application;
4 | import android.content.Context;
5 | import android.support.test.runner.AndroidJUnitRunner;
6 |
7 | public class CustomAndroidTestRunner extends AndroidJUnitRunner {
8 |
9 | @Override
10 | public Application newApplication(ClassLoader cl, String className, Context context) throws InstantiationException, IllegalAccessException, ClassNotFoundException {
11 | return super.newApplication(cl, CustomApplication.class.getName(), context);
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/testapp/src/androidTest/java/io/particle/devicesetup/testapp/CustomApplication.java:
--------------------------------------------------------------------------------
1 | package io.particle.devicesetup.testapp;
2 |
3 | import android.app.Application;
4 |
5 | import io.particle.android.sdk.cloud.ParticleCloudSDK;
6 |
7 | public class CustomApplication extends Application {
8 |
9 | @Override
10 | public void onCreate() {
11 | super.onCreate();
12 | ParticleCloudSDK.init(this);
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/testapp/src/androidTest/java/io/particle/devicesetup/testapp/EspressoDaggerMockRule.java:
--------------------------------------------------------------------------------
1 | package io.particle.devicesetup.testapp;
2 |
3 | import android.app.Application;
4 | import android.support.test.InstrumentationRegistry;
5 |
6 | import io.particle.android.sdk.devicesetup.ParticleDeviceSetupLibrary;
7 | import io.particle.android.sdk.di.ApplicationComponent;
8 | import io.particle.android.sdk.di.ApplicationModule;
9 | import it.cosenonjaviste.daggermock.DaggerMockRule;
10 |
11 | /**
12 | * Created by Julius.
13 | */
14 |
15 | public class EspressoDaggerMockRule extends DaggerMockRule {
16 | public EspressoDaggerMockRule() {
17 | super(ApplicationComponent.class, new ApplicationModule(getApp()));
18 | set(component -> {
19 | ParticleDeviceSetupLibrary.init(getApp());
20 | ParticleDeviceSetupLibrary.getInstance().setComponent(component);
21 | });
22 | }
23 |
24 | private static Application getApp() {
25 | return (Application) InstrumentationRegistry.getInstrumentation().getTargetContext().getApplicationContext();
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/testapp/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/testapp/src/main/java/io/particle/devicesetup/testapp/MainActivity.java:
--------------------------------------------------------------------------------
1 | package io.particle.devicesetup.testapp;
2 |
3 | import android.os.Bundle;
4 | import android.support.v7.app.AppCompatActivity;
5 |
6 | public class MainActivity extends AppCompatActivity {
7 |
8 | @Override
9 | protected void onCreate(Bundle savedInstanceState) {
10 | super.onCreate(savedInstanceState);
11 | setContentView(R.layout.activity_main);
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/testapp/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/testapp/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/particle-iot/spark-setup-android/60290536c1be65b78ac8ef0d475999eb468a24c0/testapp/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/testapp/src/main/res/mipmap-hdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/particle-iot/spark-setup-android/60290536c1be65b78ac8ef0d475999eb468a24c0/testapp/src/main/res/mipmap-hdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/testapp/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/particle-iot/spark-setup-android/60290536c1be65b78ac8ef0d475999eb468a24c0/testapp/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/testapp/src/main/res/mipmap-mdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/particle-iot/spark-setup-android/60290536c1be65b78ac8ef0d475999eb468a24c0/testapp/src/main/res/mipmap-mdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/testapp/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/particle-iot/spark-setup-android/60290536c1be65b78ac8ef0d475999eb468a24c0/testapp/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/testapp/src/main/res/mipmap-xhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/particle-iot/spark-setup-android/60290536c1be65b78ac8ef0d475999eb468a24c0/testapp/src/main/res/mipmap-xhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/testapp/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/particle-iot/spark-setup-android/60290536c1be65b78ac8ef0d475999eb468a24c0/testapp/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/testapp/src/main/res/mipmap-xxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/particle-iot/spark-setup-android/60290536c1be65b78ac8ef0d475999eb468a24c0/testapp/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/testapp/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/particle-iot/spark-setup-android/60290536c1be65b78ac8ef0d475999eb468a24c0/testapp/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/testapp/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/particle-iot/spark-setup-android/60290536c1be65b78ac8ef0d475999eb468a24c0/testapp/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/testapp/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #3F51B5
4 | #303F9F
5 | #FF4081
6 |
7 |
--------------------------------------------------------------------------------
/testapp/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | Testapp
3 |
4 |
--------------------------------------------------------------------------------
/testapp/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/testapp/src/test/java/io/particle/devicesetup/testapp/ExampleUnitTest.java:
--------------------------------------------------------------------------------
1 | package io.particle.devicesetup.testapp;
2 |
3 | import org.junit.Test;
4 |
5 | import static org.junit.Assert.assertEquals;
6 |
7 | /**
8 | * Example local unit test, which will execute on the development machine (host).
9 | *
10 | * @see Testing documentation
11 | */
12 | public class ExampleUnitTest {
13 | @Test
14 | public void addition_isCorrect() throws Exception {
15 | assertEquals(4, 2 + 2);
16 | }
17 | }
--------------------------------------------------------------------------------