├── .github └── workflows │ └── ci.yml ├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── app ├── build.gradle ├── libs │ └── ifttt-sdk-android-release.aar ├── proguard-rules.pro ├── release │ └── app-release.aab └── src │ └── main │ ├── AndroidManifest.xml │ ├── ic_launcher-web.png │ ├── java │ └── com │ │ └── ifttt │ │ └── groceryexpress │ │ ├── ApiHelper.kt │ │ ├── EmailPreferencesHelper.kt │ │ ├── FeatureView.kt │ │ ├── GroceryExpressApplication.kt │ │ ├── GroceryExpressCredentialsProvider.kt │ │ ├── LocationForegroundService.kt │ │ ├── MainActivity.kt │ │ ├── NotificationsHelper.kt │ │ └── UiHelper.kt │ └── res │ ├── drawable-nodpi │ └── demo_image.png │ ├── drawable │ ├── ic_calendar.xml │ ├── ic_gift.xml │ ├── ic_house.xml │ ├── ic_launcher_background.xml │ └── ic_launcher_foreground.xml │ ├── layout │ ├── activity_main.xml │ └── view_email.xml │ ├── menu │ └── menu.xml │ ├── mipmap-anydpi-v26 │ ├── ic_launcher.xml │ └── ic_launcher_round.xml │ ├── mipmap-hdpi │ ├── ic_launcher.png │ ├── ic_launcher_foreground.png │ └── ic_launcher_round.png │ ├── mipmap-mdpi │ ├── ic_launcher.png │ ├── ic_launcher_foreground.png │ └── ic_launcher_round.png │ ├── mipmap-xhdpi │ ├── ic_launcher.png │ ├── ic_launcher_foreground.png │ └── ic_launcher_round.png │ ├── mipmap-xxhdpi │ ├── ic_launcher.png │ ├── ic_launcher_foreground.png │ └── ic_launcher_round.png │ ├── mipmap-xxxhdpi │ ├── ic_launcher.png │ ├── ic_launcher_foreground.png │ └── ic_launcher_round.png │ ├── values │ ├── api_keys.xml │ ├── arrays.xml │ ├── colors.xml │ ├── dimens.xml │ ├── donottranslate-strings.xml │ ├── ic_launcher_background.xml │ └── styles.xml │ └── xml │ └── network_security_config.xml ├── build.gradle ├── connect-api ├── .gitignore ├── build.gradle ├── publish.gradle └── src │ ├── main │ ├── AndroidManifest.xml │ └── java │ │ └── com │ │ └── ifttt │ │ └── connect │ │ └── api │ │ ├── AnonymousId.java │ │ ├── ApiPendingResult.java │ │ ├── CheckBoxFieldValue.java │ │ ├── CheckBoxValue.java │ │ ├── CollectionFieldValue.java │ │ ├── Connection.java │ │ ├── ConnectionApi.java │ │ ├── ConnectionApiClient.java │ │ ├── ConnectionJsonAdapter.java │ │ ├── CoverImage.java │ │ ├── ErrorResponse.java │ │ ├── Feature.java │ │ ├── FeatureStep.java │ │ ├── FieldAreNonnullByDefault.java │ │ ├── HexColor.java │ │ ├── HexColorJsonAdapter.java │ │ ├── InviteCodeInterceptor.java │ │ ├── LocationFieldValue.java │ │ ├── PendingResult.java │ │ ├── RetrofitConnectionApi.java │ │ ├── SdkInfoInterceptor.java │ │ ├── Service.java │ │ ├── StringFieldValue.java │ │ ├── TokenInterceptor.java │ │ ├── User.java │ │ ├── UserFeature.java │ │ ├── UserFeatureField.java │ │ ├── UserFeatureStep.java │ │ ├── UserTokenJsonAdapter.java │ │ ├── UserTokenProvider.java │ │ ├── ValueProposition.java │ │ └── package-info.java │ └── test │ ├── java │ └── com │ │ └── ifttt │ │ └── connect │ │ └── api │ │ ├── ApiPendingResultTest.java │ │ ├── ConnectionApiClientTest.java │ │ ├── ConnectionTest.java │ │ ├── TestUtils.java │ │ ├── TokenInterceptorTest.java │ │ └── UserTokenJsonAdapterTest.java │ └── resources │ └── connection.json ├── connect-button ├── build.gradle ├── proguard-rules.pro ├── publish.gradle └── src │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── ifttt │ │ │ └── connect │ │ │ ├── analytics │ │ │ └── tape │ │ │ │ ├── FileObjectQueue.java │ │ │ │ ├── InMemoryObjectQueue.java │ │ │ │ ├── ObjectQueue.java │ │ │ │ ├── Private.java │ │ │ │ └── QueueFile.java │ │ │ └── ui │ │ │ ├── AboutIftttActivity.java │ │ │ ├── AbsActivityLifecycleCallbacks.java │ │ │ ├── AccountApi.java │ │ │ ├── AnalyticsApiHelper.java │ │ │ ├── AnalyticsEventPayload.java │ │ │ ├── AnalyticsEventUploader.java │ │ │ ├── AnalyticsLocation.java │ │ │ ├── AnalyticsManager.java │ │ │ ├── AnalyticsObject.java │ │ │ ├── AvenirTypefaceSpan.java │ │ │ ├── BaseConnectButton.java │ │ │ ├── ButtonApiHelper.java │ │ │ ├── ButtonParentView.java │ │ │ ├── ButtonStateChangeListener.java │ │ │ ├── ButtonUiHelper.java │ │ │ ├── CheckMarkDrawable.java │ │ │ ├── CheckMarkView.java │ │ │ ├── ConnectButton.java │ │ │ ├── ConnectButtonState.java │ │ │ ├── ConnectResult.java │ │ │ ├── CredentialsProvider.java │ │ │ ├── EmailAppsChecker.java │ │ │ ├── EventsList.java │ │ │ ├── ImageLoader.java │ │ │ ├── PendingResultLifecycleObserver.java │ │ │ ├── ProgressBackground.java │ │ │ ├── ProgressBackgroundJellyBean.java │ │ │ ├── ProgressBackgroundKitKat.java │ │ │ ├── ProgressView.java │ │ │ ├── RedirectPrepAsyncTask.java │ │ │ ├── Revertable.java │ │ │ ├── RevertableHandler.java │ │ │ ├── StartIconDrawable.java │ │ │ └── package-info.java │ └── res │ │ ├── anim │ │ ├── ifttt_helper_text_in.xml │ │ └── ifttt_helper_text_out.xml │ │ ├── drawable-v21 │ │ └── ifttt_about_button_background.xml │ │ ├── drawable │ │ ├── background_button.xml │ │ ├── button_background_default.xml │ │ ├── ic_close_black_24dp.xml │ │ ├── ic_google_play_store_download.xml │ │ ├── ic_ifttt_about_1.xml │ │ ├── ic_ifttt_about_2.xml │ │ ├── ic_ifttt_about_3.xml │ │ ├── ic_ifttt_about_4.xml │ │ ├── ic_ifttt_about_arrow.xml │ │ ├── ic_ifttt_logo_black.xml │ │ ├── ic_ifttt_logo_white.xml │ │ ├── ic_start_arrow.xml │ │ ├── ifttt_about_button_background.xml │ │ ├── ifttt_about_button_background_default.xml │ │ ├── ifttt_about_button_background_pressed.xml │ │ └── ifttt_button_border.xml │ │ ├── font │ │ ├── avenir_next_ltpro_bold.otf │ │ └── avenir_next_ltpro_demi.otf │ │ ├── layout │ │ ├── view_ifttt_about.xml │ │ ├── view_ifttt_connect.xml │ │ ├── view_ifttt_progress.xml │ │ └── view_ifttt_simple_connect_button.xml │ │ ├── values-cs │ │ └── strings.xml │ │ ├── values-da │ │ └── strings.xml │ │ ├── values-de │ │ └── strings.xml │ │ ├── values-en-rGB │ │ └── strings.xml │ │ ├── values-es-rUS │ │ └── strings.xml │ │ ├── values-es │ │ └── strings.xml │ │ ├── values-fi │ │ └── strings.xml │ │ ├── values-fr-rCA │ │ └── strings.xml │ │ ├── values-fr │ │ └── strings.xml │ │ ├── values-it │ │ └── strings.xml │ │ ├── values-ja │ │ └── strings.xml │ │ ├── values-ko │ │ └── strings.xml │ │ ├── values-nb │ │ └── strings.xml │ │ ├── values-nl │ │ └── strings.xml │ │ ├── values-pl │ │ └── strings.xml │ │ ├── values-pt-rBR │ │ └── strings.xml │ │ ├── values-pt-rPT │ │ └── strings.xml │ │ ├── values-ru │ │ └── strings.xml │ │ ├── values-sv │ │ └── strings.xml │ │ ├── values-v21 │ │ ├── colors.xml │ │ └── styles.xml │ │ ├── values-zh-rCN │ │ └── strings.xml │ │ ├── values-zh-rTW │ │ └── strings.xml │ │ └── values │ │ ├── colors.xml │ │ ├── dimens.xml │ │ ├── ids.xml │ │ ├── public.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── ifttt │ │ └── connect │ │ ├── api │ │ └── TestUtils.java │ │ └── ui │ │ ├── BaseConnectButtonTest.java │ │ ├── ButtonApiHelperTest.java │ │ ├── ConnectButtonTest.java │ │ ├── ConnectResultTest.java │ │ ├── DisableTrackingTest.java │ │ ├── LocalizationTest.java │ │ ├── QueueOperationsTest.java │ │ ├── StartIconDrawableTest.java │ │ └── TestActivity.java │ └── resources │ ├── connection.json │ └── connection_enabled.json ├── connect-location ├── .gitignore ├── README.md ├── build.gradle ├── publish.gradle └── src │ ├── main │ ├── AndroidManifest.xml │ └── java │ │ └── com │ │ └── ifttt │ │ └── location │ │ ├── AwarenessEnterReceiver.java │ │ ├── AwarenessExitReceiver.java │ │ ├── AwarenessGeofenceProvider.java │ │ ├── BackupGeofenceMonitor.java │ │ ├── Cache.java │ │ ├── CacheUserTokenProvider.java │ │ ├── ConnectLocation.java │ │ ├── ConnectionRefresher.java │ │ ├── GeofenceProvider.java │ │ ├── LocationEventAttributes.java │ │ ├── LocationEventHelper.java │ │ ├── LocationEventListener.java │ │ ├── LocationEventType.java │ │ ├── LocationEventUploadHelper.java │ │ ├── LocationEventUploader.java │ │ ├── LocationInfo.java │ │ ├── Logger.java │ │ ├── OnEventUploadListener.java │ │ ├── RebootBroadcastReceiver.java │ │ ├── RetrofitLocationApi.java │ │ ├── SharedPreferenceUserTokenCache.java │ │ ├── SharedPreferencesGeofenceCache.java │ │ └── package-info.java │ └── test │ ├── AndroidManifest.xml │ └── java │ └── com │ └── ifttt │ └── location │ ├── AwarenessGeofenceProviderTest.java │ ├── BackupGeofenceMonitorTest.java │ ├── CacheUserTokenProviderTest.java │ ├── ConnectLocationTest.java │ ├── ConnectionRefresherTest.java │ ├── LocationEventHelperTest.java │ ├── LocationEventUploadHelperTest.java │ └── TestActivity.java ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── publish-root.gradle └── settings.gradle /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: SDK unit tests 2 | 3 | on: [push] 4 | 5 | jobs: 6 | test: 7 | name: Run unit tests 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@v1 11 | - name: Setup JDK 11 12 | uses: actions/setup-java@v1 13 | with: 14 | java-version: '11' 15 | - name: Run unit tests 16 | run: bash ./gradlew testDebug 17 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # IntelliJ IDEA 2 | .idea 3 | *.iml 4 | 5 | # Gradle 6 | .gradle 7 | gradlew.bat 8 | build 9 | local.properties 10 | 11 | # Mac OS 12 | .DS_Store 13 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | ## ConnectButton 4 | ### v2.3.0 5 | #### Location SDK 6 | In v2.3.0 we added the Connect Location SDK, which is an add-on functionality to the ConnectButton SDK, to provide native [Location](https://ifttt.com/location) trigger functionality to the apps. 7 | 8 | ### v2.2.0 9 | #### Breaking API changes 10 | * Namespace changes: the following classes have been moved to `com.ifttt.connect.api`: 11 | * Connection 12 | * Service 13 | * User 14 | * PendingResult 15 | * ConnectionApiClient 16 | * API change: ConnectButton.Configuration has been reworked, use `ConnectButton.Configuration.newBuilder(String email, Uri redirectUri)` to instantiate a builder and follow the steps with appropriate parameters: 17 | * for connection information: use `withConnection(Connection connection)` or `withConnectionId(String id)` 18 | * for ConnectionApiClient: if you want to use your own ConnectionApiClient instance, use `withClient(ConnectionApiClient client, CredentialProvider)`; otherwise use `withCredentialProvider` 19 | * API change: `ConnectionApiClient#setUserToken` has been removed, you should use `UserTokenCredential` to set up API authorization. 20 | * API change: `ConnectionApiClient#Builder` has been reworked, now it requires a Context and a UserTokenProvider to instantiate. 21 | * API deprecation: `ValueProposition` and `Connection#valuePropositions` has been deprecated. Use `Feature` and `Connection#features` instead. 22 | * API addition: more Connect API data structure support has been added 23 | * `Feature`: representing a Feature for the connection 24 | * `UserFeature`: representing a user-enabled feature for the connection 25 | * `UserFeatureStep`: representing a trigger/action/query in the user-enabled feature 26 | * `UserFeatureField`: representing a user configured field in the enabled feature 27 | * `LocationFieldValue`, `CollectionFieldValue`, `StringArrayFieldValue`, `StringFieldValue`: representing different types of the configured field value 28 | * API addition: Added an "unknown" state as the default value for a ConnectButton. 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 IFTTT 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | apply plugin: 'kotlin-android' 3 | 4 | android { 5 | compileSdkVersion rootProject.compileSdkVersion 6 | 7 | defaultConfig { 8 | applicationId "com.ifttt.groceryexpress" 9 | minSdkVersion rootProject.minSdkVersion 10 | targetSdkVersion 31 11 | versionCode 17 12 | versionName "1.8.0" 13 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 14 | } 15 | buildTypes { 16 | release { 17 | minifyEnabled false 18 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 19 | } 20 | } 21 | 22 | compileOptions { 23 | sourceCompatibility JavaVersion.VERSION_11 24 | targetCompatibility JavaVersion.VERSION_11 25 | } 26 | namespace 'com.ifttt.groceryexpress' 27 | } 28 | 29 | dependencies { 30 | implementation project(':connect-button') 31 | implementation project(':connect-location') 32 | implementation "com.squareup.retrofit2:retrofit:$retrofitVersion" 33 | implementation "com.squareup.retrofit2:converter-moshi:$retrofitVersion" 34 | implementation "com.squareup.moshi:moshi:$moshiVersion" 35 | implementation "com.squareup.moshi:moshi-adapters:$moshiVersion" 36 | implementation "com.squareup.okhttp3:logging-interceptor:$okHttpVersion" 37 | implementation 'com.google.android.material:material:1.5.0' 38 | implementation "com.squareup.picasso:picasso:$picassoVersion" 39 | implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.4.1" 40 | 41 | implementation"org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" 42 | 43 | testImplementation 'junit:junit:4.12' 44 | androidTestImplementation('androidx.test.espresso:espresso-core:3.1.0', { 45 | exclude group: 'com.android.support', module: 'support-annotations' 46 | }) 47 | } 48 | -------------------------------------------------------------------------------- /app/libs/ifttt-sdk-android-release.aar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IFTTT/ConnectSDK-Android/30286987c6688f174d9b7eceaa23cb6bd273e869/app/libs/ifttt-sdk-android-release.aar -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /app/release/app-release.aab: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IFTTT/ConnectSDK-Android/30286987c6688f174d9b7eceaa23cb6bd273e869/app/release/app-release.aab -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 15 | 16 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 33 | 34 | 35 | 36 | 37 | 38 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /app/src/main/ic_launcher-web.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IFTTT/ConnectSDK-Android/30286987c6688f174d9b7eceaa23cb6bd273e869/app/src/main/ic_launcher-web.png -------------------------------------------------------------------------------- /app/src/main/java/com/ifttt/groceryexpress/ApiHelper.kt: -------------------------------------------------------------------------------- 1 | package com.ifttt.groceryexpress 2 | 3 | import android.net.Uri 4 | import com.squareup.moshi.Moshi 5 | import okhttp3.OkHttpClient 6 | import okhttp3.logging.HttpLoggingInterceptor 7 | import retrofit2.Call 8 | import retrofit2.Retrofit 9 | import retrofit2.converter.moshi.MoshiConverterFactory 10 | import retrofit2.http.Field 11 | import retrofit2.http.FormUrlEncoded 12 | import retrofit2.http.POST 13 | import java.io.IOException 14 | 15 | /** 16 | * This API helper represents your app's business logic with your backend server. We're using an example service here 17 | * to demonstrate the process of exchanging IFTTT user token. 18 | */ 19 | object ApiHelper { 20 | val REDIRECT_URI: Uri = Uri.parse("groceryexpress://connectcallback") 21 | 22 | private val tokenApi: TokenApi 23 | 24 | init { 25 | val client = 26 | OkHttpClient.Builder().addInterceptor(HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY)) 27 | .build() 28 | val moshi = Moshi.Builder().build() 29 | val retrofit = Retrofit.Builder().baseUrl("https://grocery-express.ifttt.com") 30 | .client(client) 31 | .addConverterFactory(MoshiConverterFactory.create(moshi)).build() 32 | tokenApi = retrofit.create(TokenApi::class.java) 33 | } 34 | 35 | /** 36 | * This method simulates the process of returning an IFTTT user token given a user's OAuth credential. This request 37 | * should happen between your application and your backend server, as it involves IFTTT service key. 38 | */ 39 | fun getUserToken(oAuthCode: String?): String? { 40 | if (oAuthCode == null) { 41 | return null 42 | } 43 | 44 | return try { 45 | val response = tokenApi.getUserToken(oAuthCode).execute() 46 | if (response.isSuccessful) { 47 | response.body()?.user_token 48 | } else { 49 | null 50 | } 51 | } catch (e: IOException) { 52 | null 53 | } 54 | } 55 | 56 | private interface TokenApi { 57 | @FormUrlEncoded 58 | @POST("/api/user_token") 59 | fun getUserToken(@Field("code") code: String): Call 60 | } 61 | 62 | private class Token(val type: String, val code: String?, val user_token: String?) 63 | } 64 | -------------------------------------------------------------------------------- /app/src/main/java/com/ifttt/groceryexpress/EmailPreferencesHelper.kt: -------------------------------------------------------------------------------- 1 | package com.ifttt.groceryexpress 2 | 3 | import android.content.Context 4 | 5 | /** 6 | * Helper class for storing email string in SharedPreferences. This simulates the login functionality. 7 | */ 8 | class EmailPreferencesHelper(context: Context) { 9 | 10 | private val sharedPreferences = context.getSharedPreferences(PREF_NAME, 0) 11 | 12 | fun getEmail(): String? = sharedPreferences.getString(EMAIL_KEY, null) 13 | 14 | fun setEmail(email: String) { 15 | sharedPreferences.edit().putString(EMAIL_KEY, email).apply() 16 | } 17 | 18 | fun clear() { 19 | sharedPreferences.edit().remove(EMAIL_KEY).apply() 20 | } 21 | 22 | private companion object { 23 | private const val PREF_NAME = "demo" 24 | private const val EMAIL_KEY = "email" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/src/main/java/com/ifttt/groceryexpress/FeatureView.kt: -------------------------------------------------------------------------------- 1 | package com.ifttt.groceryexpress 2 | 3 | import android.content.Context 4 | import android.graphics.Bitmap 5 | import android.graphics.drawable.BitmapDrawable 6 | import android.graphics.drawable.Drawable 7 | import android.util.AttributeSet 8 | import androidx.appcompat.widget.AppCompatTextView 9 | import com.squareup.picasso.Picasso 10 | import com.squareup.picasso.Target 11 | 12 | class FeatureView @JvmOverloads constructor( 13 | context: Context, 14 | attributeSet: AttributeSet? = null, 15 | defAttrStyle: Int = 0 16 | ) : AppCompatTextView(context, attributeSet, defAttrStyle), Target { 17 | 18 | override fun onPrepareLoad(placeHolderDrawable: Drawable?) { 19 | // We aren't setting a placeholder while loading the icon using picasso, so don't do anything here 20 | } 21 | 22 | override fun onBitmapFailed(e: Exception?, errorDrawable: Drawable?) { 23 | setCompoundDrawables(null, null, null, null) 24 | } 25 | 26 | override fun onBitmapLoaded(bitmap: Bitmap?, from: Picasso.LoadedFrom?) { 27 | if (bitmap != null) { 28 | val size = resources.getDimensionPixelSize(R.dimen.feature_icon_size) 29 | setCompoundDrawables(BitmapDrawable(resources, bitmap).apply { 30 | setBounds(0, 0, size, size) 31 | }, null, null, null) 32 | 33 | compoundDrawablePadding = resources.getDimensionPixelSize(R.dimen.feature_drawable_padding) 34 | } else { 35 | setCompoundDrawables(null, null, null, null) 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /app/src/main/java/com/ifttt/groceryexpress/GroceryExpressApplication.kt: -------------------------------------------------------------------------------- 1 | package com.ifttt.groceryexpress 2 | 3 | import android.app.Application 4 | import com.ifttt.groceryexpress.NotificationsHelper.sendNotification 5 | import com.ifttt.location.ConnectLocation 6 | 7 | class GroceryExpressApplication : Application() { 8 | 9 | /** 10 | * Initialize Location module here, so that it can set up polling and get the most updated location field values 11 | */ 12 | override fun onCreate() { 13 | super.onCreate() 14 | ConnectLocation.init(this, GroceryExpressCredentialsProvider(EmailPreferencesHelper(this))) 15 | with(ConnectLocation.getInstance()) { 16 | setLoggingEnabled(BuildConfig.DEBUG) 17 | setLocationEventListener { type, data -> 18 | sendNotification("$type $data") 19 | } 20 | } 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /app/src/main/java/com/ifttt/groceryexpress/GroceryExpressCredentialsProvider.kt: -------------------------------------------------------------------------------- 1 | package com.ifttt.groceryexpress 2 | 3 | import com.ifttt.connect.ui.CredentialsProvider 4 | 5 | class GroceryExpressCredentialsProvider(private val emailPreferencesHelper: EmailPreferencesHelper): 6 | CredentialsProvider { 7 | 8 | override fun getUserToken() = ApiHelper.getUserToken(emailPreferencesHelper.getEmail()) 9 | 10 | override fun getOAuthCode() = emailPreferencesHelper.getEmail() 11 | } 12 | -------------------------------------------------------------------------------- /app/src/main/java/com/ifttt/groceryexpress/NotificationsHelper.kt: -------------------------------------------------------------------------------- 1 | package com.ifttt.groceryexpress 2 | 3 | import android.app.NotificationChannel 4 | import android.app.NotificationManager 5 | import android.content.Context 6 | import android.graphics.Color 7 | import android.os.Build 8 | import androidx.core.app.NotificationCompat 9 | import androidx.core.app.NotificationManagerCompat 10 | 11 | /** 12 | * Extension functions for sending local notifications. 13 | */ 14 | object NotificationsHelper { 15 | 16 | private const val NOTIFICATION_CHANNEL_ID = "grocery_express_notifications" 17 | 18 | /** 19 | * Send a local notification via the generic app notification channel. 20 | */ 21 | fun Context.sendNotification(message: CharSequence) { 22 | ensureNotificationChannel() 23 | 24 | val notification = NotificationCompat.Builder(this, NOTIFICATION_CHANNEL_ID) 25 | .setSmallIcon(R.mipmap.ic_launcher) 26 | .setContentTitle(getString(R.string.app_name)) 27 | .setContentText(message) 28 | .setColor(Color.BLACK) 29 | .build() 30 | 31 | NotificationManagerCompat.from(this).notify(System.currentTimeMillis().toInt(), notification) 32 | } 33 | 34 | private fun Context.ensureNotificationChannel() { 35 | val notificationManager = NotificationManagerCompat.from(this) 36 | 37 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && 38 | notificationManager.getNotificationChannel(NOTIFICATION_CHANNEL_ID) == null 39 | ) { 40 | val notificationChannel = NotificationChannel( 41 | NOTIFICATION_CHANNEL_ID, 42 | getString(R.string.foreground_notification_channel_name), 43 | NotificationManager.IMPORTANCE_MIN 44 | ) 45 | notificationChannel.setShowBadge(false) 46 | notificationManager.createNotificationChannel(notificationChannel) 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /app/src/main/java/com/ifttt/groceryexpress/UiHelper.kt: -------------------------------------------------------------------------------- 1 | package com.ifttt.groceryexpress 2 | 3 | import android.content.Context 4 | import android.content.Intent 5 | import android.content.pm.PackageManager 6 | import android.net.Uri 7 | import android.provider.Settings 8 | import androidx.core.content.ContextCompat 9 | 10 | object UiHelper { 11 | 12 | /** 13 | * Helper extension function that builds an [Intent] to the app's settings screen. 14 | */ 15 | fun Context.appSettingsIntent(): Intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS).apply { 16 | data = Uri.fromParts("package", packageName, null) 17 | } 18 | 19 | /** 20 | * Helper extension function that checks a list of permissions to see if they are all granted. 21 | * 22 | * @return true if all permissions within the list are granted, false otherwise. 23 | */ 24 | fun Array.allPermissionsGranted(context: Context): Boolean = 25 | all { ContextCompat.checkSelfPermission(context, it) == PackageManager.PERMISSION_GRANTED } 26 | } 27 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-nodpi/demo_image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IFTTT/ConnectSDK-Android/30286987c6688f174d9b7eceaa23cb6bd273e869/app/src/main/res/drawable-nodpi/demo_image.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_calendar.xml: -------------------------------------------------------------------------------- 1 | 6 | 12 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_gift.xml: -------------------------------------------------------------------------------- 1 | 6 | 12 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_house.xml: -------------------------------------------------------------------------------- 1 | 6 | 12 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 6 | 8 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 10 | 11 | 14 | 15 | 20 | 21 | 34 | 35 | 40 | 41 | 42 | 43 | 47 | 48 | 55 | 56 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /app/src/main/res/layout/view_email.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /app/src/main/res/menu/menu.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IFTTT/ConnectSDK-Android/30286987c6688f174d9b7eceaa23cb6bd273e869/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IFTTT/ConnectSDK-Android/30286987c6688f174d9b7eceaa23cb6bd273e869/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IFTTT/ConnectSDK-Android/30286987c6688f174d9b7eceaa23cb6bd273e869/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IFTTT/ConnectSDK-Android/30286987c6688f174d9b7eceaa23cb6bd273e869/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IFTTT/ConnectSDK-Android/30286987c6688f174d9b7eceaa23cb6bd273e869/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IFTTT/ConnectSDK-Android/30286987c6688f174d9b7eceaa23cb6bd273e869/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IFTTT/ConnectSDK-Android/30286987c6688f174d9b7eceaa23cb6bd273e869/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IFTTT/ConnectSDK-Android/30286987c6688f174d9b7eceaa23cb6bd273e869/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IFTTT/ConnectSDK-Android/30286987c6688f174d9b7eceaa23cb6bd273e869/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IFTTT/ConnectSDK-Android/30286987c6688f174d9b7eceaa23cb6bd273e869/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IFTTT/ConnectSDK-Android/30286987c6688f174d9b7eceaa23cb6bd273e869/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IFTTT/ConnectSDK-Android/30286987c6688f174d9b7eceaa23cb6bd273e869/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IFTTT/ConnectSDK-Android/30286987c6688f174d9b7eceaa23cb6bd273e869/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IFTTT/ConnectSDK-Android/30286987c6688f174d9b7eceaa23cb6bd273e869/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IFTTT/ConnectSDK-Android/30286987c6688f174d9b7eceaa23cb6bd273e869/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/values/api_keys.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | YOUR_AWARENESS_API_KEY 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/values/arrays.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Connect Google Calendar 5 | Connect Location 6 | 7 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #3F51B5 4 | #303F9F 5 | #FF4081 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 300dp 4 | 32dp 5 | 16dp 6 | 24dp 7 | 24dp 8 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/values/donottranslate-strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Grocery Express 4 | User id 5 | Email 6 | Start 7 | User id cannot be empty 8 | Sign in 9 | User authentication failed. 10 | Network request failed. 11 | Disable 12 | Enable 13 | Turn on 14 | Configure 15 | Please enter your email 16 | Logout 17 | Login 18 | Select a connection. 19 | Skip configuration 20 | Geofences activated 21 | Geofences deactivated 22 | Location foreground service 23 | Location foreground service is running… 24 | Please select \"Allow all the time\" in app settings for Location permission, and then restart the app. 25 | OK 26 | 27 | -------------------------------------------------------------------------------- /app/src/main/res/values/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #D8D8D8 4 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/xml/network_security_config.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | buildscript { 3 | ext.kotlin_version = '1.6.21' 4 | repositories { 5 | google() 6 | mavenCentral() 7 | } 8 | dependencies { 9 | classpath 'com.android.tools.build:gradle:7.4.2' 10 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 11 | 12 | // NOTE: Do not place your application dependencies here; they belong 13 | // in the individual module build.gradle files 14 | } 15 | } 16 | 17 | plugins { 18 | id "io.github.gradle-nexus.publish-plugin" version "1.1.0" 19 | } 20 | 21 | allprojects { 22 | repositories { 23 | google() 24 | mavenCentral() 25 | flatDir { 26 | dirs 'libs' 27 | } 28 | } 29 | } 30 | 31 | task clean(type: Delete) { 32 | delete rootProject.buildDir 33 | } 34 | 35 | ext { 36 | libVersion = '2.5.3' 37 | okHttpVersion = '4.10.0' 38 | retrofitVersion = '2.9.0' 39 | moshiVersion = '1.8.0' 40 | workManagerVersion = '2.7.1' 41 | 42 | compileSdkVersion = 31 43 | minSdkVersion = 21 44 | buildToolsVersion = '30.0.2' 45 | awarenessVersion = '19.0.1' 46 | picassoVersion = '2.71828' 47 | robolectricVersion = '4.7.3' 48 | junitVersion = '4.13.2' 49 | androidXTestVersion = '1.4.0' 50 | androidXJunitVersion = '1.1.3' 51 | okhttpMockServerVersion = '4.4.0' 52 | } 53 | 54 | apply from: 'publish-root.gradle' 55 | -------------------------------------------------------------------------------- /connect-api/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /connect-api/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | 3 | android { 4 | compileSdkVersion rootProject.compileSdkVersion 5 | 6 | defaultConfig { 7 | minSdkVersion rootProject.minSdkVersion 8 | 9 | buildConfigField 'String', 'VERSION_NAME', "\"$libVersion\"" 10 | 11 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 12 | } 13 | 14 | buildTypes { 15 | release { 16 | minifyEnabled false 17 | } 18 | } 19 | 20 | compileOptions { 21 | sourceCompatibility JavaVersion.VERSION_1_8 22 | targetCompatibility JavaVersion.VERSION_1_8 23 | } 24 | namespace 'com.ifttt.connect.api' 25 | } 26 | 27 | dependencies { 28 | api 'com.google.code.findbugs:jsr305:3.0.2' 29 | 30 | api "com.squareup.okhttp3:okhttp:$okHttpVersion" 31 | api "com.squareup.retrofit2:retrofit:$retrofitVersion" 32 | api "com.squareup.retrofit2:converter-moshi:$retrofitVersion" 33 | api "com.squareup.moshi:moshi:$moshiVersion" 34 | api "com.squareup.moshi:moshi-adapters:$moshiVersion" 35 | api 'androidx.appcompat:appcompat:1.1.0' 36 | 37 | testImplementation "junit:junit:$junitVersion" 38 | testImplementation "org.robolectric:robolectric:$robolectricVersion" 39 | testImplementation "androidx.test:runner:$androidXTestVersion" 40 | testImplementation "androidx.test:core:$androidXTestVersion" 41 | testImplementation "androidx.test.ext:truth:$androidXTestVersion" 42 | testImplementation "androidx.test.ext:junit:$androidXJunitVersion" 43 | testImplementation "com.squareup.okhttp3:mockwebserver:$okhttpMockServerVersion" 44 | testImplementation "com.squareup.retrofit2:retrofit-mock:$retrofitVersion" 45 | } 46 | apply from: 'publish.gradle' 47 | -------------------------------------------------------------------------------- /connect-api/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /connect-api/src/main/java/com/ifttt/connect/api/AnonymousId.java: -------------------------------------------------------------------------------- 1 | package com.ifttt.connect.api; 2 | 3 | import android.annotation.SuppressLint; 4 | import android.content.Context; 5 | import android.content.SharedPreferences; 6 | import android.provider.Settings; 7 | import java.util.UUID; 8 | 9 | /** 10 | * Helper class to generate and maintain an anonymous Identifier for the installation. Used for analytics purposes. 11 | */ 12 | public final class AnonymousId { 13 | 14 | private static final String ANALYTICS_ANONYMOUS_ID_KEY = "anonymous_id"; 15 | 16 | @SuppressLint("HardwareIds") 17 | public static String get(Context context) { 18 | String androidId = Settings.Secure.getString(context.getContentResolver(), Settings.Secure.ANDROID_ID); 19 | if (androidId != null) { 20 | return androidId; 21 | } 22 | 23 | SharedPreferences preferences = context.getSharedPreferences("ifttt_connect_sdk_anonymous_id", 0);; 24 | if (!preferences.contains(ANALYTICS_ANONYMOUS_ID_KEY)) { 25 | SharedPreferences.Editor editor = preferences.edit(); 26 | editor.putString(ANALYTICS_ANONYMOUS_ID_KEY, UUID.randomUUID().toString()); 27 | editor.apply(); 28 | } 29 | 30 | return preferences.getString(ANALYTICS_ANONYMOUS_ID_KEY, null); 31 | } 32 | 33 | private AnonymousId() { 34 | throw new AssertionError(); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /connect-api/src/main/java/com/ifttt/connect/api/CheckBoxFieldValue.java: -------------------------------------------------------------------------------- 1 | package com.ifttt.connect.api; 2 | 3 | import android.os.Parcel; 4 | import android.os.Parcelable; 5 | 6 | import java.util.List; 7 | import java.util.Objects; 8 | 9 | /** 10 | * Data structure representing the checkbox field values. 11 | */ 12 | public final class CheckBoxFieldValue implements Parcelable { 13 | 14 | public final List value; 15 | 16 | public CheckBoxFieldValue(List value) { 17 | this.value = value; 18 | } 19 | 20 | protected CheckBoxFieldValue(Parcel in) { 21 | value = in.createTypedArrayList(CheckBoxValue.CREATOR); 22 | } 23 | 24 | public static final Creator CREATOR = new Creator() { 25 | @Override 26 | public CheckBoxFieldValue createFromParcel(Parcel in) { 27 | return new CheckBoxFieldValue(in); 28 | } 29 | 30 | @Override 31 | public CheckBoxFieldValue[] newArray(int size) { 32 | return new CheckBoxFieldValue[size]; 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 | CheckBoxFieldValue that = (CheckBoxFieldValue) o; 41 | return Objects.equals(value, that.value); 42 | } 43 | 44 | @Override 45 | public int hashCode() { 46 | return Objects.hash(value); 47 | } 48 | 49 | @Override 50 | public int describeContents() { 51 | return 0; 52 | } 53 | 54 | @Override 55 | public void writeToParcel(Parcel dest, int flags) { 56 | dest.writeTypedList(value); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /connect-api/src/main/java/com/ifttt/connect/api/CheckBoxValue.java: -------------------------------------------------------------------------------- 1 | package com.ifttt.connect.api; 2 | 3 | import android.os.Parcel; 4 | import android.os.Parcelable; 5 | 6 | import java.util.Objects; 7 | 8 | public final class CheckBoxValue implements Parcelable { 9 | public final String label; 10 | public final String value; 11 | 12 | public CheckBoxValue(String label, String value) { 13 | this.label = label; 14 | this.value = value; 15 | } 16 | 17 | protected CheckBoxValue(Parcel in) { 18 | label = in.readString(); 19 | value = in.readString(); 20 | } 21 | 22 | public static final Creator CREATOR = new Creator() { 23 | @Override 24 | public CheckBoxValue createFromParcel(Parcel in) { 25 | return new CheckBoxValue(in); 26 | } 27 | 28 | @Override 29 | public CheckBoxValue[] newArray(int size) { 30 | return new CheckBoxValue[size]; 31 | } 32 | }; 33 | 34 | @Override 35 | public boolean equals(Object o) { 36 | if (this == o) return true; 37 | if (o == null || getClass() != o.getClass()) return false; 38 | CheckBoxValue that = (CheckBoxValue) o; 39 | return Objects.equals(label, that.label) && Objects.equals(value, that.value); 40 | } 41 | 42 | @Override 43 | public int hashCode() { 44 | return Objects.hash(label, value); 45 | } 46 | 47 | @Override 48 | public int describeContents() { 49 | return 0; 50 | } 51 | 52 | @Override 53 | public void writeToParcel(Parcel dest, int flags) { 54 | dest.writeString(label); 55 | dest.writeString(value); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /connect-api/src/main/java/com/ifttt/connect/api/CollectionFieldValue.java: -------------------------------------------------------------------------------- 1 | package com.ifttt.connect.api; 2 | 3 | import android.os.Parcel; 4 | import android.os.Parcelable; 5 | 6 | /** 7 | * Data structure representing the dropdown or double dropdown field value. 8 | */ 9 | public final class CollectionFieldValue implements Parcelable { 10 | 11 | /** 12 | * Group identifier, this can be used to represent grouped values, such as a double dropdown field. 13 | */ 14 | public final String group; 15 | 16 | /** 17 | * User-friendly name for the value. 18 | */ 19 | public final String label; 20 | 21 | /** 22 | * Unique identifier for the value, this is usually only used in Connect API requests. 23 | */ 24 | public final String value; 25 | 26 | public CollectionFieldValue(String group, String label, String value) { 27 | this.group = group; 28 | this.label = label; 29 | this.value = value; 30 | } 31 | 32 | protected CollectionFieldValue(Parcel in) { 33 | group = in.readString(); 34 | label = in.readString(); 35 | value = in.readString(); 36 | } 37 | 38 | public static final Creator CREATOR = new Creator() { 39 | @Override 40 | public CollectionFieldValue createFromParcel(Parcel in) { 41 | return new CollectionFieldValue(in); 42 | } 43 | 44 | @Override 45 | public CollectionFieldValue[] newArray(int size) { 46 | return new CollectionFieldValue[size]; 47 | } 48 | }; 49 | 50 | @Override 51 | public int describeContents() { 52 | return 0; 53 | } 54 | 55 | @Override 56 | public void writeToParcel(Parcel dest, int flags) { 57 | dest.writeString(group); 58 | dest.writeString(label); 59 | dest.writeString(value); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /connect-api/src/main/java/com/ifttt/connect/api/ConnectionApi.java: -------------------------------------------------------------------------------- 1 | package com.ifttt.connect.api; 2 | 3 | /** 4 | * IFTTT API wrapper interface. You may use the instance from {@link ConnectionApiClient#api()} to make API calls 5 | * asynchronously. 6 | * 7 | * @see ConnectionApiClient#api() 8 | */ 9 | public interface ConnectionApi { 10 | 11 | /** 12 | * API for fetching a Connection's metadata. 13 | * 14 | * @param id Connection id. 15 | * @return A {@link PendingResult} for the API call execution. 16 | */ 17 | PendingResult showConnection(String id); 18 | 19 | /** 20 | * API for disabling a Connection. 21 | * 22 | * @param id Connection id. 23 | * @return A {@link PendingResult} for the API call execution. 24 | */ 25 | PendingResult disableConnection(String id); 26 | 27 | /** 28 | * API for re-enable a Connection 29 | * 30 | * @param id Connection id. 31 | * @return A {@link PendingResult} for the API call execution. 32 | */ 33 | PendingResult reenableConnection(String id); 34 | 35 | /** 36 | * API for retrieving information about the IFTTT user, as well as the authentication level of the current 37 | * ConnectionApiClient. 38 | * 39 | * @return A {@link PendingResult} for the API call execution. 40 | */ 41 | PendingResult user(); 42 | } 43 | -------------------------------------------------------------------------------- /connect-api/src/main/java/com/ifttt/connect/api/ErrorResponse.java: -------------------------------------------------------------------------------- 1 | package com.ifttt.connect.api; 2 | 3 | /** 4 | * Standardized error response from IFTTT API. 5 | */ 6 | public final class ErrorResponse { 7 | 8 | /** 9 | * A machine-readable string categorizing the failure. 10 | */ 11 | public final String code; 12 | 13 | /** 14 | * A human-readable error message describing the failure. 15 | */ 16 | public final String message; 17 | 18 | public ErrorResponse(String code, String message) { 19 | this.code = code; 20 | this.message = message; 21 | } 22 | 23 | @Override 24 | public String toString() { 25 | return "ErrorResponse{" + "code='" + code + '\'' + ", message='" + message + '\'' + '}'; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /connect-api/src/main/java/com/ifttt/connect/api/FeatureStep.java: -------------------------------------------------------------------------------- 1 | package com.ifttt.connect.api; 2 | 3 | /** 4 | * Data structure representing the 3 basic feature "steps": triggers, queries and actions. The 3 types of steps are 5 | * basic components that provides different functionality from a service. 6 | */ 7 | public final class FeatureStep { 8 | 9 | /** 10 | * Enum representation used to identify the type of the step. 11 | */ 12 | public enum StepType { 13 | /** 14 | * Trigger step type, more details see https://platform.ifttt.com/docs/connect_api#triggers 15 | */ 16 | Trigger, 17 | /** 18 | * Query step type, more details see https://platform.ifttt.com/docs/connect_api#actions 19 | */ 20 | Action, 21 | /** 22 | * Action step type, more details see https://platform.ifttt.com/docs/connect_api#queries 23 | */ 24 | Query 25 | } 26 | 27 | public final StepType stepType; 28 | 29 | /** 30 | * User-friendly name for the feature step. 31 | */ 32 | public final String label; 33 | 34 | /** 35 | * Unique identifier for the feature step. Between two different features, this id is unique even if they are from 36 | * the same step (trigger/query/action). 37 | */ 38 | public final String id; 39 | 40 | /** 41 | * Unique identifier for the step. Different from {@link #id}, this id is from the trigger, query or action, which 42 | * can be the same if two features have the same steps. 43 | */ 44 | public final String stepId; 45 | 46 | /** 47 | * Unique identifier for the service. If the steps (triggers/queries/actions) are from the same service, this value 48 | * is going to be the same among them. 49 | */ 50 | public final String serviceId; 51 | 52 | public FeatureStep(StepType stepType, String label, String id, String stepId, String serviceId) { 53 | this.stepType = stepType; 54 | this.label = label; 55 | this.id = id; 56 | this.stepId = stepId; 57 | this.serviceId = serviceId; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /connect-api/src/main/java/com/ifttt/connect/api/FieldAreNonnullByDefault.java: -------------------------------------------------------------------------------- 1 | package com.ifttt.connect.api; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import javax.annotation.Nonnull; 6 | import javax.annotation.meta.TypeQualifierDefault; 7 | 8 | import static java.lang.annotation.RetentionPolicy.RUNTIME; 9 | 10 | @Nonnull 11 | @TypeQualifierDefault(ElementType.FIELD) 12 | @Retention(RUNTIME) 13 | public @interface FieldAreNonnullByDefault { 14 | } 15 | -------------------------------------------------------------------------------- /connect-api/src/main/java/com/ifttt/connect/api/HexColor.java: -------------------------------------------------------------------------------- 1 | package com.ifttt.connect.api; 2 | 3 | import com.squareup.moshi.JsonQualifier; 4 | import java.lang.annotation.Retention; 5 | 6 | import static java.lang.annotation.RetentionPolicy.RUNTIME; 7 | 8 | /** 9 | * JSON qualifier annotation for integer representation of color values. This will be used in {@link Service} to 10 | * convert string representation of the color value into integers. 11 | */ 12 | @Retention(RUNTIME) 13 | @JsonQualifier 14 | @interface HexColor { 15 | } 16 | -------------------------------------------------------------------------------- /connect-api/src/main/java/com/ifttt/connect/api/HexColorJsonAdapter.java: -------------------------------------------------------------------------------- 1 | package com.ifttt.connect.api; 2 | 3 | import android.graphics.Color; 4 | import com.squareup.moshi.FromJson; 5 | import com.squareup.moshi.JsonWriter; 6 | import com.squareup.moshi.ToJson; 7 | import java.io.IOException; 8 | 9 | /** 10 | * Moshi JSON adapter for converting string representation of color values into integers. 11 | */ 12 | final class HexColorJsonAdapter { 13 | @ToJson 14 | void toJson(JsonWriter writer, @HexColor int color) throws IOException { 15 | throw new UnsupportedOperationException(); 16 | } 17 | 18 | @FromJson 19 | @HexColor 20 | int fromJson(String value) { 21 | return Color.parseColor(value); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /connect-api/src/main/java/com/ifttt/connect/api/InviteCodeInterceptor.java: -------------------------------------------------------------------------------- 1 | package com.ifttt.connect.api; 2 | 3 | import java.io.IOException; 4 | import okhttp3.Interceptor; 5 | import okhttp3.Response; 6 | 7 | /** 8 | * OkHttp {@link Interceptor} for setting and clearing invite code header. 9 | */ 10 | final class InviteCodeInterceptor implements Interceptor { 11 | private final String inviteCode; 12 | 13 | InviteCodeInterceptor(String inviteCode) { 14 | this.inviteCode = inviteCode; 15 | } 16 | 17 | @Override 18 | public Response intercept(Chain chain) throws IOException { 19 | return chain.proceed(chain.request().newBuilder().addHeader("IFTTT-Invite-Code", inviteCode).build()); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /connect-api/src/main/java/com/ifttt/connect/api/PendingResult.java: -------------------------------------------------------------------------------- 1 | package com.ifttt.connect.api; 2 | 3 | import retrofit2.Call; 4 | 5 | /** 6 | * Wrapper interface for the Retrofit {@link Call}. It is used to provide standardized error response we have in the 7 | * API through {@link ErrorResponse}. 8 | */ 9 | public interface PendingResult { 10 | 11 | /** 12 | * Callback interface for the API call results. 13 | */ 14 | interface ResultCallback { 15 | 16 | /** 17 | * Called when the API call was successful. 18 | * 19 | * @param result API response object. 20 | */ 21 | void onSuccess(T result); 22 | 23 | /** 24 | * Called when the API call was failed. 25 | * 26 | * @param errorResponse Formatted error responses from the API call. 27 | */ 28 | void onFailure(ErrorResponse errorResponse); 29 | } 30 | 31 | /** 32 | * @return the wrapped Retrofit Call object. This can be used to access the additional functionality that Call 33 | * have, e.g. synchronous execution. 34 | */ 35 | Call getCall(); 36 | 37 | /** 38 | * Execute the API call, and subscribe to its response. 39 | * 40 | * @param callback Callback that provides API call response status and response. 41 | */ 42 | void execute(ResultCallback callback); 43 | 44 | /** 45 | * Cancel the ongoing API call. 46 | */ 47 | void cancel(); 48 | } 49 | -------------------------------------------------------------------------------- /connect-api/src/main/java/com/ifttt/connect/api/RetrofitConnectionApi.java: -------------------------------------------------------------------------------- 1 | package com.ifttt.connect.api; 2 | 3 | import retrofit2.Call; 4 | import retrofit2.http.GET; 5 | import retrofit2.http.POST; 6 | import retrofit2.http.Path; 7 | 8 | /** 9 | * Connection API endpoints. 10 | */ 11 | interface RetrofitConnectionApi { 12 | 13 | @GET("/v2/connections/{id}") 14 | Call showConnection(@Path("id") String id); 15 | 16 | @POST("/v2/connections/{id}/disable") 17 | Call disableConnection(@Path("id") String id); 18 | 19 | @POST("/v2/connections/{id}/enable") 20 | Call reenableConnection(@Path("id") String id); 21 | 22 | @GET("/v2/me") 23 | Call user(); 24 | } 25 | -------------------------------------------------------------------------------- /connect-api/src/main/java/com/ifttt/connect/api/SdkInfoInterceptor.java: -------------------------------------------------------------------------------- 1 | package com.ifttt.connect.api; 2 | 3 | import java.io.IOException; 4 | import okhttp3.Interceptor; 5 | import okhttp3.Response; 6 | 7 | /** 8 | * Interceptor that adds common headers in the API call to IFTTT API. 9 | */ 10 | public final class SdkInfoInterceptor implements Interceptor { 11 | 12 | private final String anonymousId; 13 | 14 | public SdkInfoInterceptor(String anonymousId) { 15 | this.anonymousId = anonymousId; 16 | } 17 | 18 | @Override 19 | public Response intercept(Chain chain) throws IOException { 20 | return chain.proceed(chain.request() 21 | .newBuilder() 22 | .addHeader("IFTTT-SDK-Version", BuildConfig.VERSION_NAME) 23 | .addHeader("IFTTT-SDK-Platform", "android") 24 | .addHeader("IFTTT-SDK-Anonymous-Id", anonymousId) 25 | .build()); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /connect-api/src/main/java/com/ifttt/connect/api/StringFieldValue.java: -------------------------------------------------------------------------------- 1 | package com.ifttt.connect.api; 2 | 3 | import android.os.Parcel; 4 | import android.os.Parcelable; 5 | 6 | /** 7 | * Data structure for field types that have string values. 8 | */ 9 | public final class StringFieldValue implements Parcelable { 10 | public final String value; 11 | 12 | public StringFieldValue(String value) { 13 | this.value = value; 14 | } 15 | 16 | protected StringFieldValue(Parcel in) { 17 | value = in.readString(); 18 | } 19 | 20 | @Override 21 | public void writeToParcel(Parcel dest, int flags) { 22 | dest.writeString(value); 23 | } 24 | 25 | @Override 26 | public int describeContents() { 27 | return 0; 28 | } 29 | 30 | public static final Creator CREATOR = new Creator() { 31 | @Override 32 | public StringFieldValue createFromParcel(Parcel in) { 33 | return new StringFieldValue(in); 34 | } 35 | 36 | @Override 37 | public StringFieldValue[] newArray(int size) { 38 | return new StringFieldValue[size]; 39 | } 40 | }; 41 | } 42 | -------------------------------------------------------------------------------- /connect-api/src/main/java/com/ifttt/connect/api/TokenInterceptor.java: -------------------------------------------------------------------------------- 1 | package com.ifttt.connect.api; 2 | 3 | import java.io.IOException; 4 | import okhttp3.Interceptor; 5 | import okhttp3.Response; 6 | 7 | /** 8 | * {@link Interceptor} for setting user authentication header. 9 | */ 10 | final class TokenInterceptor implements Interceptor { 11 | private final UserTokenProvider userTokenProvider; 12 | 13 | private boolean isUserAuthenticated = false; 14 | 15 | TokenInterceptor(UserTokenProvider userTokenProvider) { 16 | this.userTokenProvider = userTokenProvider; 17 | } 18 | 19 | /** 20 | * @return true if the Interceptor has a valid user token, false otherwise. This is represented as whether the 21 | * most recent {@link UserTokenProvider#getUserToken()} returned a non-null value and the API response did not have 22 | * a 401 status code. 23 | */ 24 | boolean isUserAuthenticated() { 25 | return isUserAuthenticated; 26 | } 27 | 28 | @Override 29 | public Response intercept(Chain chain) throws IOException { 30 | String token; 31 | try { 32 | token = userTokenProvider.getUserToken(); 33 | if (token == null) { 34 | // If token is still null, proceed without it. 35 | isUserAuthenticated = false; 36 | return chain.proceed(chain.request()); 37 | } 38 | } catch (Exception e) { 39 | if (BuildConfig.DEBUG) { 40 | e.printStackTrace(); 41 | } 42 | 43 | isUserAuthenticated = false; 44 | return chain.proceed(chain.request()); 45 | } 46 | 47 | Response response = chain.proceed(chain.request() 48 | .newBuilder() 49 | .addHeader("Authorization", "Bearer " + token) 50 | .build()); 51 | 52 | // If we are getting 401, reset the user authentication flag. 53 | isUserAuthenticated = response.code() != 401; 54 | 55 | return response; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /connect-api/src/main/java/com/ifttt/connect/api/UserFeature.java: -------------------------------------------------------------------------------- 1 | package com.ifttt.connect.api; 2 | 3 | import android.os.Parcel; 4 | import android.os.Parcelable; 5 | import java.util.List; 6 | 7 | /** 8 | * Data structure representing an enabled instance of a {@link Feature}. This class contains information about the 9 | * configuration of the feature, stored in {@link #userFeatureSteps}. 10 | */ 11 | public final class UserFeature implements Parcelable { 12 | 13 | public final String id; 14 | public final String featureId; 15 | public final boolean enabled; 16 | public final List userFeatureSteps; 17 | 18 | public UserFeature(String id, String featureId, boolean enabled, List userFeatureSteps) { 19 | this.id = id; 20 | this.featureId = featureId; 21 | this.enabled = enabled; 22 | this.userFeatureSteps = userFeatureSteps; 23 | } 24 | 25 | protected UserFeature(Parcel in) { 26 | id = in.readString(); 27 | featureId = in.readString(); 28 | enabled = in.readInt() == 1; 29 | userFeatureSteps = in.createTypedArrayList(UserFeatureStep.CREATOR); 30 | } 31 | 32 | public static final Creator CREATOR = new Creator() { 33 | @Override 34 | public UserFeature createFromParcel(Parcel in) { 35 | return new UserFeature(in); 36 | } 37 | 38 | @Override 39 | public UserFeature[] newArray(int size) { 40 | return new UserFeature[size]; 41 | } 42 | }; 43 | 44 | @Override 45 | public int describeContents() { 46 | return 0; 47 | } 48 | 49 | @Override 50 | public void writeToParcel(Parcel dest, int flags) { 51 | dest.writeString(id); 52 | dest.writeString(featureId); 53 | dest.writeInt(enabled ? 1 : 0); 54 | dest.writeTypedList(userFeatureSteps); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /connect-api/src/main/java/com/ifttt/connect/api/UserFeatureField.java: -------------------------------------------------------------------------------- 1 | package com.ifttt.connect.api; 2 | 3 | import android.os.Parcel; 4 | import android.os.Parcelable; 5 | 6 | /** 7 | * Data structure representing one configuration field for a {@link UserFeature}. Currently, the supported value types 8 | * are 9 | * - {@link CollectionFieldValue} 10 | * - {@link CheckBoxFieldValue} 11 | * - {@link LocationFieldValue} 12 | * - {@link StringFieldValue} 13 | */ 14 | public final class UserFeatureField implements Parcelable { 15 | 16 | public final T value; 17 | public final String fieldType; 18 | public final String fieldId; 19 | 20 | public UserFeatureField(T value, String fieldType, String fieldId) { 21 | this.value = value; 22 | this.fieldType = fieldType; 23 | this.fieldId = fieldId; 24 | } 25 | 26 | protected UserFeatureField(Parcel in) { 27 | String className = in.readString(); 28 | T parceledValue = null; 29 | if (className != null) { 30 | try { 31 | parceledValue = in.readParcelable(Class.forName(className).getClassLoader()); 32 | } catch (ClassNotFoundException ignored) { 33 | } 34 | } 35 | 36 | value = parceledValue; 37 | fieldId = in.readString(); 38 | fieldType = in.readString(); 39 | } 40 | 41 | public static final Creator CREATOR = new Creator() { 42 | @Override 43 | public UserFeatureField createFromParcel(Parcel in) { 44 | return new UserFeatureField(in); 45 | } 46 | 47 | @Override 48 | public UserFeatureField[] newArray(int size) { 49 | return new UserFeatureField[size]; 50 | } 51 | }; 52 | 53 | @Override 54 | public int describeContents() { 55 | return 0; 56 | } 57 | 58 | @Override 59 | public void writeToParcel(Parcel dest, int flags) { 60 | dest.writeString(value.getClass().getName()); 61 | dest.writeParcelable(value, flags); 62 | dest.writeString(fieldId); 63 | dest.writeString(fieldType); 64 | } 65 | 66 | @Override 67 | public String toString() { 68 | return "UserFeatureField{" 69 | + "value=" 70 | + value 71 | + ", fieldType='" 72 | + fieldType 73 | + '\'' 74 | + ", fieldId='" 75 | + fieldId 76 | + '\'' 77 | + '}'; 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /connect-api/src/main/java/com/ifttt/connect/api/UserFeatureStep.java: -------------------------------------------------------------------------------- 1 | package com.ifttt.connect.api; 2 | 3 | import android.os.Parcel; 4 | import android.os.Parcelable; 5 | import java.util.List; 6 | import javax.annotation.Nullable; 7 | 8 | /** 9 | * Data structure representing an enabled instance of a {@link FeatureStep}. The configuration information for this step 10 | * can be found in the {@link UserFeatureField} list. 11 | */ 12 | public final class UserFeatureStep implements Parcelable { 13 | 14 | public final FeatureStep.StepType stepType; 15 | public final String stepId; 16 | public final List fields; 17 | 18 | @Nullable public final String id; 19 | 20 | public UserFeatureStep( 21 | FeatureStep.StepType stepType, @Nullable String id, String stepId, List fields 22 | ) { 23 | this.stepType = stepType; 24 | this.id = id; 25 | this.stepId = stepId; 26 | this.fields = fields; 27 | } 28 | 29 | protected UserFeatureStep(Parcel in) { 30 | stepType = (FeatureStep.StepType) in.readSerializable(); 31 | id = in.readString(); 32 | stepId = in.readString(); 33 | fields = in.createTypedArrayList(UserFeatureField.CREATOR); 34 | } 35 | 36 | public static final Creator CREATOR = new Creator() { 37 | @Override 38 | public UserFeatureStep createFromParcel(Parcel in) { 39 | return new UserFeatureStep(in); 40 | } 41 | 42 | @Override 43 | public UserFeatureStep[] newArray(int size) { 44 | return new UserFeatureStep[size]; 45 | } 46 | }; 47 | 48 | @Override 49 | public int describeContents() { 50 | return 0; 51 | } 52 | 53 | @Override 54 | public void writeToParcel(Parcel dest, int flags) { 55 | dest.writeSerializable(stepType); 56 | dest.writeString(id); 57 | dest.writeString(stepId); 58 | dest.writeTypedList(fields); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /connect-api/src/main/java/com/ifttt/connect/api/UserTokenJsonAdapter.java: -------------------------------------------------------------------------------- 1 | package com.ifttt.connect.api; 2 | 3 | import com.squareup.moshi.FromJson; 4 | import com.squareup.moshi.JsonQualifier; 5 | import com.squareup.moshi.JsonReader; 6 | import com.squareup.moshi.JsonWriter; 7 | import com.squareup.moshi.ToJson; 8 | import java.io.IOException; 9 | import java.lang.annotation.Retention; 10 | 11 | import static java.lang.annotation.RetentionPolicy.RUNTIME; 12 | 13 | /** 14 | * JSON adapter for parsing user token. 15 | */ 16 | final class UserTokenJsonAdapter { 17 | @Retention(RUNTIME) 18 | @JsonQualifier 19 | @interface UserTokenRequest { 20 | } 21 | 22 | @FromJson 23 | @UserTokenRequest 24 | String fromJson(JsonReader reader) throws IOException { 25 | throw new UnsupportedOperationException(); 26 | } 27 | 28 | @ToJson 29 | void toJson(JsonWriter writer, @UserTokenRequest String token) throws IOException { 30 | writer.beginObject(); 31 | writer.name("token").value(token); 32 | writer.endObject(); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /connect-api/src/main/java/com/ifttt/connect/api/UserTokenProvider.java: -------------------------------------------------------------------------------- 1 | package com.ifttt.connect.api; 2 | 3 | import androidx.annotation.Nullable; 4 | import androidx.annotation.WorkerThread; 5 | 6 | public interface UserTokenProvider { 7 | 8 | /** 9 | * @return Your users' IFTTT user token, once they have successfully authenticate your service on IFTTT. Because this 10 | * method is potentially going to be called for every API call, we recommend caching this value if possible to avoid 11 | * unnecessary operations. 12 | */ 13 | @Nullable 14 | @WorkerThread 15 | String getUserToken(); 16 | } 17 | -------------------------------------------------------------------------------- /connect-api/src/main/java/com/ifttt/connect/api/ValueProposition.java: -------------------------------------------------------------------------------- 1 | package com.ifttt.connect.api; 2 | 3 | import android.os.Parcel; 4 | import android.os.Parcelable; 5 | import com.squareup.moshi.Json; 6 | 7 | /** 8 | * Value proposition data structure, including the icon url and the description. 9 | * 10 | * @deprecated use {@link Feature} instead. 11 | */ 12 | @Deprecated 13 | public final class ValueProposition implements Parcelable { 14 | @Json(name = "icon_url") final String iconUrl; 15 | final String description; 16 | 17 | public ValueProposition(String iconUrl, String description) { 18 | this.iconUrl = iconUrl; 19 | this.description = description; 20 | } 21 | 22 | protected ValueProposition(Parcel in) { 23 | iconUrl = in.readString(); 24 | description = in.readString(); 25 | } 26 | 27 | public static final Creator CREATOR = new Creator() { 28 | @Override 29 | public ValueProposition createFromParcel(Parcel in) { 30 | return new ValueProposition(in); 31 | } 32 | 33 | @Override 34 | public ValueProposition[] newArray(int size) { 35 | return new ValueProposition[size]; 36 | } 37 | }; 38 | 39 | @Override 40 | public int describeContents() { 41 | return 0; 42 | } 43 | 44 | @Override 45 | public void writeToParcel(Parcel dest, int flags) { 46 | dest.writeString(iconUrl); 47 | dest.writeString(description); 48 | } 49 | 50 | @Override 51 | public String toString() { 52 | return "ValueProposition{" + "iconUrl='" + iconUrl + '\'' + ", description='" + description + '\'' + '}'; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /connect-api/src/main/java/com/ifttt/connect/api/package-info.java: -------------------------------------------------------------------------------- 1 | @ParametersAreNonnullByDefault 2 | package com.ifttt.connect.api; 3 | 4 | import javax.annotation.ParametersAreNonnullByDefault; 5 | -------------------------------------------------------------------------------- /connect-api/src/test/java/com/ifttt/connect/api/ApiPendingResultTest.java: -------------------------------------------------------------------------------- 1 | package com.ifttt.connect.api; 2 | 3 | import com.squareup.moshi.Moshi; 4 | import java.io.IOException; 5 | import java.util.concurrent.atomic.AtomicReference; 6 | import org.junit.Test; 7 | import org.junit.runner.RunWith; 8 | import org.junit.runners.JUnit4; 9 | import retrofit2.mock.Calls; 10 | 11 | import static com.google.common.truth.Truth.assertThat; 12 | import static junit.framework.Assert.fail; 13 | 14 | @RunWith(JUnit4.class) 15 | public final class ApiPendingResultTest { 16 | 17 | @Test 18 | public void testExecution() { 19 | ApiPendingResult pendingResult = new ApiPendingResult<>(Calls.failure(new IOException()), 20 | new Moshi.Builder().build().adapter(ErrorResponse.class)); 21 | final AtomicReference errorResponseAtomicReference = new AtomicReference<>(); 22 | pendingResult.execute(new PendingResult.ResultCallback() { 23 | @Override 24 | public void onSuccess(Connection result) { 25 | fail(); 26 | } 27 | 28 | @Override 29 | public void onFailure(ErrorResponse errorResponse) { 30 | errorResponseAtomicReference.set(errorResponse); 31 | } 32 | }); 33 | 34 | assertThat(errorResponseAtomicReference.get().code).isEqualTo("exception"); 35 | assertThat(errorResponseAtomicReference.get().message).isEqualTo("Unexpected error"); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /connect-api/src/test/java/com/ifttt/connect/api/ConnectionApiClientTest.java: -------------------------------------------------------------------------------- 1 | package com.ifttt.connect.api; 2 | 3 | import static com.google.common.truth.Truth.assertThat; 4 | 5 | import android.content.Context; 6 | 7 | import androidx.test.core.app.ApplicationProvider; 8 | import androidx.test.ext.junit.runners.AndroidJUnit4; 9 | 10 | import org.junit.Test; 11 | import org.junit.runner.RunWith; 12 | 13 | @RunWith(AndroidJUnit4.class) 14 | public final class ConnectionApiClientTest { 15 | 16 | @Test 17 | public void newBuilderShouldOverrideUserTokenProvider() { 18 | UserTokenProvider oldProvider = () -> null; 19 | Context context = ApplicationProvider.getApplicationContext(); 20 | ConnectionApiClient client = new ConnectionApiClient.Builder(context, oldProvider).build(); 21 | 22 | UserTokenProvider newProvider = () -> "token"; 23 | ConnectionApiClient newClient = client.newBuilder(newProvider).build(); 24 | 25 | assertThat(client).isNotSameInstanceAs(newClient); 26 | assertThat(client.userTokenProvider).isNotSameInstanceAs(newClient.userTokenProvider); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /connect-api/src/test/java/com/ifttt/connect/api/TestUtils.java: -------------------------------------------------------------------------------- 1 | package com.ifttt.connect.api; 2 | 3 | import com.squareup.moshi.JsonAdapter; 4 | import com.squareup.moshi.JsonReader; 5 | import com.squareup.moshi.Moshi; 6 | import com.squareup.moshi.adapters.Rfc3339DateJsonAdapter; 7 | import java.io.IOException; 8 | import java.io.InputStream; 9 | import java.util.Date; 10 | import okio.Okio; 11 | 12 | public final class TestUtils { 13 | 14 | private static final Moshi MOSHI = new Moshi.Builder().add(Date.class, new Rfc3339DateJsonAdapter().nullSafe()) 15 | .add(new ConnectionJsonAdapter()) 16 | .add(new HexColorJsonAdapter()) 17 | .build(); 18 | 19 | private static final JsonAdapter CONNECTION_ADAPTER = MOSHI.adapter(Connection.class); 20 | 21 | public static Connection loadConnection(ClassLoader classLoader) throws IOException { 22 | InputStream inputStream = classLoader.getResourceAsStream("connection.json"); 23 | JsonReader jsonReader = JsonReader.of(Okio.buffer(Okio.source(inputStream))); 24 | return CONNECTION_ADAPTER.fromJson(jsonReader); 25 | } 26 | 27 | private TestUtils() { 28 | throw new AssertionError(); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /connect-api/src/test/java/com/ifttt/connect/api/TokenInterceptorTest.java: -------------------------------------------------------------------------------- 1 | package com.ifttt.connect.api; 2 | 3 | import java.io.IOException; 4 | import okhttp3.OkHttpClient; 5 | import okhttp3.Request; 6 | import okhttp3.mockwebserver.MockResponse; 7 | import okhttp3.mockwebserver.MockWebServer; 8 | import org.junit.After; 9 | import org.junit.Before; 10 | import org.junit.Test; 11 | import org.junit.runner.RunWith; 12 | import org.junit.runners.JUnit4; 13 | 14 | import static com.google.common.truth.Truth.assertThat; 15 | 16 | @RunWith(JUnit4.class) 17 | public final class TokenInterceptorTest { 18 | 19 | private final MockWebServer server = new MockWebServer(); 20 | 21 | @Before 22 | public void setUp() throws Exception { 23 | server.start(); 24 | } 25 | 26 | @Test 27 | public void shouldCallProviderForApiCalls() throws IOException { 28 | TokenInterceptor interceptor = new TokenInterceptor(() -> "token"); 29 | 30 | OkHttpClient client = new OkHttpClient.Builder().addInterceptor(interceptor).build(); 31 | 32 | server.enqueue(new MockResponse()); 33 | client.newCall(TokenInterceptorTest.this.request()).execute(); 34 | assertThat(interceptor.isUserAuthenticated()).isTrue(); 35 | } 36 | 37 | @Test 38 | public void shouldInvalidateTokenIf401() throws IOException { 39 | TokenInterceptor interceptor = new TokenInterceptor(() -> "token"); 40 | 41 | OkHttpClient client = new OkHttpClient.Builder().addInterceptor(interceptor).build(); 42 | 43 | server.enqueue(new MockResponse()); 44 | server.enqueue(new MockResponse().setResponseCode(401)); 45 | client.newCall(request()).execute(); 46 | assertThat(interceptor.isUserAuthenticated()).isTrue(); 47 | 48 | client.newCall(request()).execute(); 49 | assertThat(interceptor.isUserAuthenticated()).isFalse(); 50 | } 51 | 52 | private Request request() { 53 | return new Request.Builder().url(server.url("")).build(); 54 | } 55 | 56 | @After 57 | public void tearDown() throws Exception { 58 | server.shutdown(); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /connect-api/src/test/java/com/ifttt/connect/api/UserTokenJsonAdapterTest.java: -------------------------------------------------------------------------------- 1 | package com.ifttt.connect.api; 2 | 3 | import com.squareup.moshi.JsonAdapter; 4 | import com.squareup.moshi.Moshi; 5 | import org.junit.Before; 6 | import org.junit.Test; 7 | import org.junit.runner.RunWith; 8 | import org.junit.runners.JUnit4; 9 | 10 | import static com.google.common.truth.Truth.assertThat; 11 | 12 | @RunWith(JUnit4.class) 13 | public class UserTokenJsonAdapterTest { 14 | 15 | JsonAdapter adapter; 16 | 17 | @Before 18 | public void setUp() throws Exception { 19 | Moshi moshi = new Moshi.Builder().add(new UserTokenJsonAdapter()).build(); 20 | adapter = moshi.adapter(String.class, UserTokenJsonAdapter.UserTokenRequest.class); 21 | } 22 | 23 | @Test 24 | public void toJson() { 25 | String value = adapter.toJson("my_token"); 26 | assertThat(value).isEqualTo("{\"token\":\"my_token\"}"); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /connect-button/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | 3 | android { 4 | compileSdkVersion rootProject.compileSdkVersion 5 | 6 | defaultConfig { 7 | minSdkVersion rootProject.minSdkVersion 8 | 9 | buildConfigField 'String', 'VERSION_NAME', "\"$libVersion\"" 10 | } 11 | 12 | buildTypes { 13 | release { 14 | minifyEnabled false 15 | } 16 | } 17 | 18 | compileOptions { 19 | sourceCompatibility JavaVersion.VERSION_1_8 20 | targetCompatibility JavaVersion.VERSION_1_8 21 | } 22 | 23 | testOptions.unitTests.includeAndroidResources = true 24 | namespace 'com.ifttt.connect' 25 | } 26 | 27 | dependencies { 28 | debugApi project(':connect-api') 29 | releaseApi "com.ifttt:connect-api:$libVersion" 30 | 31 | implementation 'androidx.browser:browser:1.4.0' 32 | implementation "androidx.work:work-runtime:$workManagerVersion" 33 | 34 | testImplementation "junit:junit:$junitVersion" 35 | testImplementation "org.robolectric:robolectric:$robolectricVersion" 36 | testImplementation "androidx.test:runner:$androidXTestVersion" 37 | testImplementation "androidx.test:core:$androidXTestVersion" 38 | testImplementation "androidx.test.ext:truth:$androidXTestVersion" 39 | testImplementation "androidx.test.ext:junit:$androidXJunitVersion" 40 | testImplementation "com.squareup.okhttp3:mockwebserver:$okhttpMockServerVersion" 41 | testImplementation "com.squareup.retrofit2:retrofit-mock:$retrofitVersion" 42 | testImplementation "androidx.work:work-testing:$workManagerVersion" 43 | } 44 | 45 | apply from: 'publish.gradle' 46 | -------------------------------------------------------------------------------- /connect-button/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | -dontobfuscate 2 | -keepparameternames 3 | -renamesourcefileattribute SourceFile 4 | -keepattributes Exceptions, InnerClasses, Signature, Deprecated, SourceFile, LineNumberTable, EnclosingMethod 5 | 6 | # Preserve all annotations. 7 | -keepattributes *Annotation* 8 | 9 | # Preserve all public classes, and their public and protected fields and 10 | # methods. 11 | -keep public class * { 12 | public protected *; 13 | } 14 | 15 | # Preserve all .class method names. 16 | -keepclassmembernames class * { 17 | java.lang.Class class$(java.lang.String); 18 | java.lang.Class class$(java.lang.String, boolean); 19 | } 20 | 21 | # Moshi 22 | # JSR 305 annotations are for embedding nullability information. 23 | -dontwarn javax.annotation.** 24 | 25 | -keepclasseswithmembers class * { 26 | @com.squareup.moshi.* ; 27 | } 28 | 29 | -keep @com.squareup.moshi.JsonQualifier interface * 30 | 31 | # Enum field names are used by the integrated EnumJsonAdapter. 32 | # Annotate enums with @JsonClass(generateAdapter = false) to use them with Moshi. 33 | -keepclassmembers @com.squareup.moshi.JsonClass class * extends java.lang.Enum { 34 | ; 35 | } 36 | 37 | # The name of @JsonClass types is used to look up the generated adapter. 38 | -keepnames @com.squareup.moshi.JsonClass class * 39 | -------------------------------------------------------------------------------- /connect-button/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /connect-button/src/main/java/com/ifttt/connect/analytics/tape/Private.java: -------------------------------------------------------------------------------- 1 | package com.ifttt.connect.analytics.tape; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | /** 9 | * Indicates that the given field or method has package visibility solely to prevent the creation of 10 | * a synthetic method. In practice, you should treat this field/method as if it were private.

11 | * 12 | * When a private method is called from an inner class, the Java compiler generates a simple package 13 | * private shim method that the class generated from the inner class can call. This results in 14 | * unnecessary bloat and runtime method call overhead. It also gets us closer to the dex method 15 | * count limit.

16 | * 17 | * If you'd like to see warnings for these synthetic methods in IntelliJ, turn on the inspections 18 | * "Private method only used from inner class" and "Private member access between outer and inner 19 | * classes".

20 | */ 21 | @Retention(RetentionPolicy.SOURCE) 22 | @Target({ElementType.METHOD, ElementType.FIELD, ElementType.CONSTRUCTOR, ElementType.TYPE}) 23 | @interface Private { 24 | } 25 | 26 | -------------------------------------------------------------------------------- /connect-button/src/main/java/com/ifttt/connect/ui/AbsActivityLifecycleCallbacks.java: -------------------------------------------------------------------------------- 1 | package com.ifttt.connect.ui; 2 | 3 | import android.app.Activity; 4 | import android.app.Application; 5 | import android.os.Bundle; 6 | 7 | /** 8 | * Convenient abstract class for using {@link Application.ActivityLifecycleCallbacks}. 9 | */ 10 | abstract class AbsActivityLifecycleCallbacks implements Application.ActivityLifecycleCallbacks { 11 | @Override 12 | public void onActivityCreated(Activity activity, Bundle savedInstanceState) { 13 | } 14 | 15 | @Override 16 | public void onActivityStarted(Activity activity) { 17 | } 18 | 19 | @Override 20 | public void onActivityResumed(Activity activity) { 21 | } 22 | 23 | @Override 24 | public void onActivityPaused(Activity activity) { 25 | } 26 | 27 | @Override 28 | public void onActivityStopped(Activity activity) { 29 | } 30 | 31 | @Override 32 | public void onActivitySaveInstanceState(Activity activity, Bundle outState) { 33 | } 34 | 35 | @Override 36 | public void onActivityDestroyed(Activity activity) { 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /connect-button/src/main/java/com/ifttt/connect/ui/AccountApi.java: -------------------------------------------------------------------------------- 1 | package com.ifttt.connect.ui; 2 | 3 | import retrofit2.Call; 4 | import retrofit2.http.GET; 5 | import retrofit2.http.Query; 6 | 7 | /** 8 | * API used for trying to match an existing IFTTT account with an email. 9 | */ 10 | interface AccountApi { 11 | @GET("/v2/account/find") 12 | Call findAccount(@Query("email") String email); 13 | } 14 | -------------------------------------------------------------------------------- /connect-button/src/main/java/com/ifttt/connect/ui/AnalyticsApiHelper.java: -------------------------------------------------------------------------------- 1 | package com.ifttt.connect.ui; 2 | 3 | import com.ifttt.connect.api.SdkInfoInterceptor; 4 | import okhttp3.OkHttpClient; 5 | import retrofit2.Call; 6 | import retrofit2.Retrofit; 7 | import retrofit2.converter.moshi.MoshiConverterFactory; 8 | import retrofit2.http.Body; 9 | import retrofit2.http.POST; 10 | 11 | final class AnalyticsApiHelper { 12 | 13 | private static AnalyticsApiHelper INSTANCE; 14 | 15 | private final EventsApi eventsApi; 16 | 17 | private AnalyticsApiHelper(String anonymousId) { 18 | OkHttpClient.Builder builder = new OkHttpClient.Builder(); 19 | OkHttpClient okHttpClient = builder.addInterceptor(new SdkInfoInterceptor(anonymousId)).build(); 20 | 21 | Retrofit retrofit = new Retrofit.Builder().baseUrl("https://connect.ifttt.com") 22 | .addConverterFactory(MoshiConverterFactory.create()) 23 | .client(okHttpClient) 24 | .build(); 25 | 26 | eventsApi = retrofit.create(EventsApi.class); 27 | } 28 | 29 | static synchronized AnalyticsApiHelper get(String anonymousId) { 30 | if (INSTANCE == null) { 31 | INSTANCE = new AnalyticsApiHelper(anonymousId); 32 | } 33 | return INSTANCE; 34 | } 35 | 36 | Call submitEvents(EventsList events) { 37 | return eventsApi.postEvents(events); 38 | } 39 | 40 | private interface EventsApi { 41 | @POST("/v2/sdk/events") 42 | Call postEvents(@Body EventsList events); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /connect-button/src/main/java/com/ifttt/connect/ui/AnalyticsEventPayload.java: -------------------------------------------------------------------------------- 1 | package com.ifttt.connect.ui; 2 | 3 | import java.util.Map; 4 | 5 | final class AnalyticsEventPayload { 6 | 7 | private final String name; 8 | 9 | private final String timestamp; 10 | private final Map properties; 11 | 12 | AnalyticsEventPayload(String name, String timestamp, Map properties) { 13 | this.name = name; 14 | this.timestamp = timestamp; 15 | this.properties = properties; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /connect-button/src/main/java/com/ifttt/connect/ui/AnalyticsEventUploader.java: -------------------------------------------------------------------------------- 1 | package com.ifttt.connect.ui; 2 | 3 | import android.content.Context; 4 | import androidx.annotation.NonNull; 5 | import androidx.work.Worker; 6 | import androidx.work.WorkerParameters; 7 | import com.ifttt.connect.api.AnonymousId; 8 | import java.io.IOException; 9 | import java.util.List; 10 | import retrofit2.Response; 11 | 12 | /* 13 | * Schedules a one time work request to read from the queue, make an api call to submit events and remove them from queue. 14 | * */ 15 | public final class AnalyticsEventUploader extends Worker { 16 | 17 | private final AnalyticsManager analyticsManager; 18 | private final AnalyticsApiHelper apiHelper; 19 | 20 | private static final int MAX_RETRY_COUNT = 3; 21 | 22 | public AnalyticsEventUploader(Context context, WorkerParameters params) { 23 | super(context, params); 24 | analyticsManager = AnalyticsManager.getInstance(context.getApplicationContext()); 25 | apiHelper = AnalyticsApiHelper.get(AnonymousId.get(context)); 26 | } 27 | 28 | @Override 29 | @NonNull 30 | public Result doWork() { 31 | try { 32 | List list = analyticsManager.performRead(); 33 | 34 | if (list != null && !list.isEmpty()) { 35 | Response response = apiHelper 36 | .submitEvents(new EventsList(list)) 37 | .execute(); 38 | 39 | if (response.isSuccessful()) { 40 | analyticsManager.performRemove(list.size()); 41 | return Result.success(); 42 | } 43 | } else { 44 | return Result.success(); 45 | } 46 | } catch (IOException e) { 47 | } 48 | 49 | if (getRunAttemptCount() < MAX_RETRY_COUNT) { 50 | return Result.retry(); 51 | } else { 52 | return Result.failure(); 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /connect-button/src/main/java/com/ifttt/connect/ui/AnalyticsLocation.java: -------------------------------------------------------------------------------- 1 | package com.ifttt.connect.ui; 2 | 3 | import android.content.Context; 4 | import androidx.annotation.VisibleForTesting; 5 | 6 | final class AnalyticsLocation { 7 | 8 | final String id; 9 | final String type; 10 | 11 | private static final String TYPE_CONNECT_BUTTON = "connect_button"; 12 | 13 | static final AnalyticsLocation WORKS_WITH_IFTTT = new AnalyticsLocation("", TYPE_CONNECT_BUTTON); 14 | 15 | @VisibleForTesting 16 | AnalyticsLocation(String id, String type) { 17 | this.id = id; 18 | this.type = type; 19 | } 20 | 21 | static AnalyticsLocation fromConnectButton(Context context) { 22 | return new AnalyticsLocation(context.getPackageName(),TYPE_CONNECT_BUTTON); 23 | } 24 | 25 | static AnalyticsLocation fromConnectButtonWithId(String connectionId) { 26 | return new AnalyticsLocation(connectionId, TYPE_CONNECT_BUTTON); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /connect-button/src/main/java/com/ifttt/connect/ui/AnalyticsObject.java: -------------------------------------------------------------------------------- 1 | package com.ifttt.connect.ui; 2 | 3 | import androidx.annotation.VisibleForTesting; 4 | import com.ifttt.connect.api.Connection; 5 | 6 | class AnalyticsObject { 7 | 8 | final String id; 9 | final String type; 10 | 11 | private static String TYPE_CONNECTION = "connection"; 12 | private static String TYPE_BUTTON = "button"; 13 | private static String TYPE_MODAL = "modal"; 14 | 15 | private static String ID_WORKS_WITH_IFTTT = "works_with_ifttt"; 16 | private static String ID_CONNECT_INFORMATION = "connect_information"; 17 | private static String ID_PRIVACY_POLICY = "privacy_policy"; 18 | private static String ID_MANAGE = "manage"; 19 | private static String ID_CONNECTION_NAME = "connection_name"; 20 | 21 | static final AnalyticsObject WORKS_WITH_IFTTT = new AnalyticsObject(ID_WORKS_WITH_IFTTT, TYPE_BUTTON); 22 | static final AnalyticsObject CONNECT_INFORMATION_MODAL = new AnalyticsObject(ID_CONNECT_INFORMATION, TYPE_MODAL); 23 | static final AnalyticsObject PRIVACY_POLICY = new AnalyticsObject(ID_PRIVACY_POLICY, TYPE_BUTTON); 24 | static final AnalyticsObject MANAGE_CONNECTION = new AnalyticsObject(ID_MANAGE, TYPE_BUTTON); 25 | static final AnalyticsObject CONNECTION_NAME = new AnalyticsObject(ID_CONNECTION_NAME, TYPE_BUTTON); 26 | 27 | @VisibleForTesting 28 | AnalyticsObject(String id, String type) { 29 | this.id = id; 30 | this.type = type; 31 | } 32 | 33 | static final class ConnectionAnalyticsObject extends AnalyticsObject { 34 | String status; 35 | 36 | ConnectionAnalyticsObject(String id, String status) { 37 | super(id, TYPE_CONNECTION); 38 | this.status = status; 39 | } 40 | 41 | static ConnectionAnalyticsObject fromConnection(Connection connection) { 42 | return new ConnectionAnalyticsObject(connection.id, connection.status.toString()); 43 | } 44 | } 45 | 46 | static AnalyticsObject fromService(String serviceModuleName) { 47 | return new AnalyticsObject(serviceModuleName.concat("_").concat("service_icon"), TYPE_BUTTON); 48 | } 49 | 50 | static AnalyticsObject fromConnectionEmail(String connectionId) { 51 | return new AnalyticsObject(connectionId, "connection_email"); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /connect-button/src/main/java/com/ifttt/connect/ui/AvenirTypefaceSpan.java: -------------------------------------------------------------------------------- 1 | package com.ifttt.connect.ui; 2 | 3 | import android.graphics.Paint; 4 | import android.graphics.Typeface; 5 | import android.text.TextPaint; 6 | import android.text.style.TypefaceSpan; 7 | 8 | final class AvenirTypefaceSpan extends TypefaceSpan { 9 | 10 | private final Typeface newType; 11 | 12 | AvenirTypefaceSpan(Typeface type) { 13 | super("san-serif"); 14 | newType = type; 15 | } 16 | 17 | @Override 18 | public void updateDrawState(TextPaint ds) { 19 | applyCustomTypeFace(ds, newType); 20 | } 21 | 22 | @Override 23 | public void updateMeasureState(TextPaint paint) { 24 | applyCustomTypeFace(paint, newType); 25 | } 26 | 27 | private void applyCustomTypeFace(Paint paint, Typeface tf) { 28 | int oldStyle; 29 | Typeface old = paint.getTypeface(); 30 | if (old == null) { 31 | oldStyle = 0; 32 | } else { 33 | oldStyle = old.getStyle(); 34 | } 35 | int fake = oldStyle & ~tf.getStyle(); 36 | if ((fake & Typeface.BOLD) != 0) { 37 | paint.setFakeBoldText(true); 38 | } 39 | if ((fake & Typeface.ITALIC) != 0) { 40 | paint.setTextSkewX(-0.25f); 41 | } 42 | paint.setTypeface(tf); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /connect-button/src/main/java/com/ifttt/connect/ui/ButtonStateChangeListener.java: -------------------------------------------------------------------------------- 1 | package com.ifttt.connect.ui; 2 | 3 | import com.ifttt.connect.api.Connection; 4 | import com.ifttt.connect.api.ErrorResponse; 5 | 6 | /** 7 | * Callback interface for listening to state changes of the {@link BaseConnectButton}. 8 | */ 9 | public interface ButtonStateChangeListener { 10 | /** 11 | * Called when there is a state change, either successful or failed, on the IftttConnectButton. 12 | * 13 | * @param currentState Current state of the button. 14 | * @param previousState Previous state of the button. 15 | * @param connection Connection instance. 16 | * If currentState = Enabled, this param will reflect the refreshed Connection instance with updated feature fields. 17 | */ 18 | void onStateChanged(ConnectButtonState currentState, ConnectButtonState previousState, Connection connection); 19 | 20 | /** 21 | * Called when the button state change encounters an errorResponse. 22 | * 23 | * @param errorResponse ErrorResponse messages from the button or underlying API calls, or null for a successful state 24 | * change. 25 | */ 26 | void onError(ErrorResponse errorResponse); 27 | } 28 | -------------------------------------------------------------------------------- /connect-button/src/main/java/com/ifttt/connect/ui/ConnectButtonState.java: -------------------------------------------------------------------------------- 1 | package com.ifttt.connect.ui; 2 | 3 | public enum ConnectButtonState { 4 | /** 5 | * A button state for displaying an Connection in its initial state, the user has never authenticated this Connection 6 | * before. 7 | */ 8 | Initial, 9 | 10 | /** 11 | * A button state for the create account authentication step. In this step, the user is going to be redirected 12 | * to web to create an account and continue with service connection. 13 | */ 14 | CreateAccount, 15 | 16 | /** 17 | * A button state for the login authentication step. In this step, the user is going to be redirected to web 18 | * to login to IFTTT. 19 | */ 20 | Login, 21 | 22 | /** 23 | * A button state for displaying a Connection that is enabled. 24 | */ 25 | Enabled, 26 | 27 | /** 28 | * A button stat for displaying a Connection that is disabled. 29 | */ 30 | Disabled, 31 | 32 | /** 33 | * Default button state. 34 | */ 35 | Unknown 36 | } 37 | -------------------------------------------------------------------------------- /connect-button/src/main/java/com/ifttt/connect/ui/CredentialsProvider.java: -------------------------------------------------------------------------------- 1 | package com.ifttt.connect.ui; 2 | 3 | import androidx.annotation.WorkerThread; 4 | import com.ifttt.connect.api.Connection; 5 | import com.ifttt.connect.api.UserTokenProvider; 6 | 7 | /** 8 | * Interface that defines APIs for providing credentials used during the service authentication process for a 9 | * {@link Connection}. 10 | */ 11 | public interface CredentialsProvider extends UserTokenProvider { 12 | 13 | /** 14 | * @return Your users' OAuth code for your service. This is to be used to automatically authenticate the 15 | * user to your service on IFTTT during the connection enable flow. 16 | */ 17 | @WorkerThread 18 | String getOAuthCode(); 19 | } 20 | -------------------------------------------------------------------------------- /connect-button/src/main/java/com/ifttt/connect/ui/EmailAppsChecker.java: -------------------------------------------------------------------------------- 1 | package com.ifttt.connect.ui; 2 | 3 | import android.content.pm.PackageManager; 4 | import java.util.ArrayList; 5 | import java.util.Collections; 6 | import java.util.List; 7 | import javax.annotation.CheckReturnValue; 8 | 9 | /** 10 | * Helper class that facilitates the returning user flow: check the installed email apps on the device, which will be 11 | * used by the web view to prompt users to open the email app to check for sign-in email. 12 | */ 13 | final class EmailAppsChecker { 14 | 15 | private static final List APP_LIST; 16 | 17 | static { 18 | ArrayList list = new ArrayList<>(4); 19 | list.add("com.google.android.gm"); 20 | list.add("com.microsoft.office.outlook"); 21 | list.add("com.samsung.android.email.provider"); 22 | list.add("com.yahoo.mobile.client.android.mail"); 23 | APP_LIST = Collections.unmodifiableList(list); 24 | } 25 | 26 | private final PackageManager packageManager; 27 | 28 | EmailAppsChecker(PackageManager packageManager) { 29 | this.packageManager = packageManager; 30 | } 31 | 32 | @CheckReturnValue 33 | List detectEmailApps() { 34 | ArrayList detected = new ArrayList<>(); 35 | for (String app : APP_LIST) { 36 | try { 37 | packageManager.getApplicationInfo(app, 0); 38 | detected.add(app); 39 | } catch (PackageManager.NameNotFoundException e) { 40 | // Could not find email app installed. 41 | } 42 | } 43 | 44 | return detected; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /connect-button/src/main/java/com/ifttt/connect/ui/EventsList.java: -------------------------------------------------------------------------------- 1 | package com.ifttt.connect.ui; 2 | 3 | import java.util.List; 4 | 5 | final class EventsList{ 6 | private List events; 7 | 8 | EventsList(List events) { 9 | this.events = events; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /connect-button/src/main/java/com/ifttt/connect/ui/PendingResultLifecycleObserver.java: -------------------------------------------------------------------------------- 1 | package com.ifttt.connect.ui; 2 | 3 | import androidx.lifecycle.Lifecycle; 4 | import androidx.lifecycle.LifecycleObserver; 5 | import androidx.lifecycle.OnLifecycleEvent; 6 | import com.ifttt.connect.api.PendingResult; 7 | 8 | final class PendingResultLifecycleObserver implements LifecycleObserver { 9 | 10 | private final PendingResult pendingResult; 11 | 12 | PendingResultLifecycleObserver(PendingResult pendingResult) { 13 | this.pendingResult = pendingResult; 14 | } 15 | 16 | @OnLifecycleEvent(Lifecycle.Event.ON_STOP) 17 | public void onStop() { 18 | pendingResult.cancel(); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /connect-button/src/main/java/com/ifttt/connect/ui/ProgressBackground.java: -------------------------------------------------------------------------------- 1 | package com.ifttt.connect.ui; 2 | 3 | import androidx.annotation.ColorInt; 4 | import androidx.annotation.FloatRange; 5 | 6 | interface ProgressBackground { 7 | 8 | /** 9 | * Set the current progress that should be rendered. 10 | * 11 | * @param progress a progress value ranging from 0 to 1. 12 | */ 13 | void setProgress(@FloatRange(from = 0.0f, to = 1.0f) float progress); 14 | 15 | /** 16 | * Set up the drawable as a progress bar. 17 | * 18 | * @param primaryColor Primary color of the drawable. 19 | * @param progressColor Progress bar color of the drawable. 20 | */ 21 | void setColor(@ColorInt int primaryColor, @ColorInt int progressColor); 22 | } 23 | -------------------------------------------------------------------------------- /connect-button/src/main/java/com/ifttt/connect/ui/Revertable.java: -------------------------------------------------------------------------------- 1 | package com.ifttt.connect.ui; 2 | 3 | /** 4 | * Interface that represents an action that can be reverted. 5 | */ 6 | interface Revertable { 7 | 8 | void run(); 9 | 10 | /** 11 | * Represents an action that reverts the action done by the {@link #run()} function. 12 | */ 13 | void revert(); 14 | } 15 | -------------------------------------------------------------------------------- /connect-button/src/main/java/com/ifttt/connect/ui/RevertableHandler.java: -------------------------------------------------------------------------------- 1 | package com.ifttt.connect.ui; 2 | 3 | import android.os.Handler; 4 | import android.os.Looper; 5 | import java.util.ArrayList; 6 | import java.util.List; 7 | 8 | /** 9 | * A {@link Handler} wrapper that works with {@link Revertable} to automatically schedule a revert action for any 10 | * running Revertable. 11 | */ 12 | final class RevertableHandler { 13 | 14 | private final List revertables = new ArrayList<>(); 15 | private final Handler handler = new Handler(Looper.getMainLooper()); 16 | 17 | /** 18 | * Run a {@link Revertable}, and automatically schedule a Runnable to call {@link Revertable#revert()} after a 19 | * delay. 20 | * 21 | * @param revertable Revertable object to run. 22 | * @param delay Delay in milliseconds that the revert function will be called. 23 | */ 24 | void run(Revertable revertable, long delay) { 25 | Runnable toRevert = new Runnable() { 26 | @Override 27 | public void run() { 28 | revertables.remove(this); 29 | revertable.revert(); 30 | } 31 | }; 32 | revertables.add(toRevert); 33 | 34 | revertable.run(); 35 | handler.postDelayed(toRevert, delay); 36 | } 37 | 38 | /** 39 | * Revert all of the scheduled revert actions. 40 | */ 41 | void revertAll() { 42 | for (Runnable revertable : revertables) { 43 | handler.removeCallbacks(revertable); 44 | revertable.run(); 45 | } 46 | 47 | revertables.clear(); 48 | } 49 | 50 | /** 51 | * Remove all of the scheduled revert actions. 52 | */ 53 | void clear() { 54 | for (Runnable revertable : revertables) { 55 | handler.removeCallbacks(revertable); 56 | } 57 | 58 | revertables.clear(); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /connect-button/src/main/java/com/ifttt/connect/ui/package-info.java: -------------------------------------------------------------------------------- 1 | @ParametersAreNonnullByDefault 2 | package com.ifttt.connect.ui; 3 | 4 | import javax.annotation.ParametersAreNonnullByDefault; 5 | -------------------------------------------------------------------------------- /connect-button/src/main/res/anim/ifttt_helper_text_in.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /connect-button/src/main/res/anim/ifttt_helper_text_out.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /connect-button/src/main/res/drawable-v21/ifttt_about_button_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /connect-button/src/main/res/drawable/background_button.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /connect-button/src/main/res/drawable/button_background_default.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /connect-button/src/main/res/drawable/ic_close_black_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /connect-button/src/main/res/drawable/ic_ifttt_about_1.xml: -------------------------------------------------------------------------------- 1 | 7 | 13 | 19 | 20 | -------------------------------------------------------------------------------- /connect-button/src/main/res/drawable/ic_ifttt_about_2.xml: -------------------------------------------------------------------------------- 1 | 7 | 13 | 19 | 20 | -------------------------------------------------------------------------------- /connect-button/src/main/res/drawable/ic_ifttt_about_3.xml: -------------------------------------------------------------------------------- 1 | 7 | 15 | 22 | 29 | 30 | -------------------------------------------------------------------------------- /connect-button/src/main/res/drawable/ic_ifttt_about_4.xml: -------------------------------------------------------------------------------- 1 | 7 | 15 | 21 | 27 | 35 | 36 | -------------------------------------------------------------------------------- /connect-button/src/main/res/drawable/ic_ifttt_about_arrow.xml: -------------------------------------------------------------------------------- 1 | 6 | 12 | 20 | 21 | -------------------------------------------------------------------------------- /connect-button/src/main/res/drawable/ic_ifttt_logo_black.xml: -------------------------------------------------------------------------------- 1 | 6 | 12 | 18 | 24 | 30 | 36 | 42 | 48 | 54 | 60 | 66 | 67 | -------------------------------------------------------------------------------- /connect-button/src/main/res/drawable/ic_ifttt_logo_white.xml: -------------------------------------------------------------------------------- 1 | 6 | 12 | 18 | 24 | 30 | 36 | 42 | 48 | 54 | 60 | 66 | 67 | -------------------------------------------------------------------------------- /connect-button/src/main/res/drawable/ic_start_arrow.xml: -------------------------------------------------------------------------------- 1 | 6 | 12 | 13 | -------------------------------------------------------------------------------- /connect-button/src/main/res/drawable/ifttt_about_button_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /connect-button/src/main/res/drawable/ifttt_about_button_background_default.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /connect-button/src/main/res/drawable/ifttt_about_button_background_pressed.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /connect-button/src/main/res/drawable/ifttt_button_border.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /connect-button/src/main/res/font/avenir_next_ltpro_bold.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IFTTT/ConnectSDK-Android/30286987c6688f174d9b7eceaa23cb6bd273e869/connect-button/src/main/res/font/avenir_next_ltpro_bold.otf -------------------------------------------------------------------------------- /connect-button/src/main/res/font/avenir_next_ltpro_demi.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IFTTT/ConnectSDK-Android/30286987c6688f174d9b7eceaa23cb6bd273e869/connect-button/src/main/res/font/avenir_next_ltpro_demi.otf -------------------------------------------------------------------------------- /connect-button/src/main/res/layout/view_ifttt_progress.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 13 | 14 | 24 | 25 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /connect-button/src/main/res/layout/view_ifttt_simple_connect_button.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 10 | 11 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /connect-button/src/main/res/values-cs/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Chraňte a monitorujte své údaje 4 | Odpojování... 5 | Ikona zahájení 6 | Zkontrolujte, že má váš telefon přístup k internetu 7 | Odpojeno 8 | Zadejte platný e-mail 9 | Můžete kdykoli odpojit 10 | Nový účet IFTTT pro %1$s 11 | Další informace 12 | Zkusit znovu 13 | Připojit 14 | Přechod na %1$s... 15 | Ikona služby %1$s 16 | cs 17 | FUNGUJE S IFTTT 18 | Odpojit 19 | Načítání… 20 | Vytváření účtu... 21 | Spravovat 22 | Připojeno 23 | Využíváním tohoto propojení vyjadřujete souhlas Ochrana soukromí a smluvní podmínky 24 | Ochrana soukromí a smluvní podmínky 25 | IFTTT propojuje %1$s s %2$s 26 | E-mail 27 | Připojování 28 | Připojit %1$s 29 | Ovládejte, které služby mají přístup k vašim údajům a jak je využívají 30 | Zabezpečeno pomocí IFTTT. Další informace 31 | Získejte nové funkce, kdy budou vaše oblíbené věci spolupracovat 32 | 33 | -------------------------------------------------------------------------------- /connect-button/src/main/res/values-da/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Beskyt og overvåg dine data 4 | Afbryder forbindelsen... 5 | Start-ikon 6 | Sørg for, at din telefon har internetforbindelse 7 | Forbindelsen er afbrudt. 8 | Indtast gyldig e-mail 9 | Du kan afbryde forbindelsen når som helst 10 | Ny IFTTT-konto til %1$s 11 | Få flere oplysninger 12 | Nyt forsøg 13 | Opret forbindelse 14 | Går til %1$s... 15 | Ikon for %1$s-tjenesten 16 | da 17 | VIRKER MED IFTTT 18 | Afbryd forbindelse 19 | Indlæser... 20 | Opretter konto... 21 | Administrer 22 | Tilsluttede 23 | Ved at bruge denne forbindelse accepterer du vores Fortrolighedspolitik og vilkår. 24 | Fortrolighedspolitik og vilkår 25 | IFTTT opretter forbindelse mellem %1$s og %2$s 26 | E-mail 27 | Tilslutning 28 | Opret forbindelse til %1$s 29 | Styr, hvilke tjenester der har adgang til dine data, og hvordan de bruger dem 30 | Sikret med IFTTT. Få flere oplysninger 31 | Lås op for nye funktioner, når dine foretrukne ting fungerer sammen 32 | 33 | -------------------------------------------------------------------------------- /connect-button/src/main/res/values-de/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Schützen und überwachen Sie Ihre Daten. 4 | Wird getrennt ... 5 | Startsymbol 6 | Stellen Sie sicher, dass Ihr Telefon über eine Internetverbindung verfügt. 7 | Verbindung getrennt 8 | Geben Sie eine gültige E-Mail-Adresse ein. 9 | Trennen jederzeit möglich 10 | Neues IFTTT-Konto für %1$s 11 | Weitere Informationen 12 | Wiederholen 13 | Verbinden 14 | Navigieren zu %1$s ... 15 | %1$s-Service-Symbol 16 | de 17 | FUNKTIONIERT MIT IFTTT 18 | Verbindung trennen 19 | Laden… 20 | Konto wird erstellt ... 21 | Verwalten 22 | verbunden 23 | Durch die Nutzung dieser Verbindung stimmen Sie unseren Datenschutzbestimmungen zu. 24 | Datenschutzbestimmungen 25 | IFTTT verbindet %1$s mit %2$s. 26 | E-Mail 27 | Wird verbunden 28 | Verbinden %1$s 29 | Steuern Sie, welche Dienste auf Ihre Daten zugreifen und wie sie diese verwenden. 30 | Mit IFTTT gesichert Weitere Informationen 31 | Schalten Sie neue Funktion frei, indem Sie Ihre Lieblingsapps zusammenarbeiten lassen. 32 | 33 | -------------------------------------------------------------------------------- /connect-button/src/main/res/values-en-rGB/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Protect and monitor your data 4 | Disconnecting... 5 | Start icon 6 | Make sure your phone has internet connection 7 | Disconnected 8 | Enter valid email 9 | Unplug any time 10 | New IFTTT account for %1$s 11 | Learn more 12 | Retry 13 | Connect 14 | Going to %1$s... 15 | %1$s service icon 16 | en-GB 17 | WORKS WITH IFTTT 18 | Disconnect 19 | Loading... 20 | Creating account... 21 | Manage 22 | Connected 23 | By using this connection, you agree to our Privacy & Terms 24 | Privacy & Terms 25 | IFTTT connects %1$s to %2$s 26 | Email 27 | Connecting... 28 | Connect %1$s 29 | Control which services access your data and how they use it 30 | Secured with IFTTT. Learn More 31 | Unlock new features when your favourite things work together 32 | 33 | -------------------------------------------------------------------------------- /connect-button/src/main/res/values-es-rUS/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Proteja y monitoree sus datos 4 | Desconectando... 5 | Icono de inicio 6 | Asegúrese de que su teléfono tenga conexión a internet 7 | Desconectado 8 | Ingrese un correo electrónico válido 9 | Desconéctese en cualquier momento 10 | Nueva cuenta IFTTT para %1$s 11 | Más información 12 | Intentar nuevamente 13 | Conectarse 14 | Yendo a %1$s... 15 | Icono de servicio de %1$s 16 | es-419 17 | FUNCIONA CON IFTTT 18 | Desconectar 19 | Cargando... 20 | Creando cuenta... 21 | Administrar 22 | Se conectó con 23 | Al usar esta conexión, acepta nuestros Términos y privacidad 24 | Términos y privacidad 25 | IFTTT conecta %1$s con %2$s 26 | Correo electrónico 27 | Conectando 28 | Conectar %1$s 29 | Controle qué servicios acceden a sus datos y cómo los usan 30 | Protegido con IFTTT. Más información 31 | Desbloquee nuevas funciones cuando sus cosas favoritas funcionan juntas 32 | 33 | -------------------------------------------------------------------------------- /connect-button/src/main/res/values-es/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Protege y supervisa tus datos 4 | Desconectando... 5 | Icono de inicio 6 | Asegúrate de que tu teléfono tenga conexión a Internet 7 | Desconectado 8 | Introduce un correo electrónico válido 9 | Desconecta en cualquier momento 10 | Nueva cuenta IFTTT para %1$s 11 | Más información 12 | Reintentar 13 | Conectar 14 | Yendo a %1$s ... 15 | Icono de servicio de %1$s 16 | es 17 | FUNCIONA CON IFTTT 18 | Desconectar 19 | Cargando... 20 | Creando cuenta... 21 | Gestionar 22 | Conectado 23 | Al usar esta conexión, aceptas nuestros Privacidad y términos 24 | Privacidad y términos 25 | IFTTT conecta %1$s con %2$s 26 | Correo electrónico 27 | Conectando 28 | Conectar %1$s 29 | Controla qué servicios acceden a tus datos y cómo los usan 30 | Asegurado con IFTTT. Más información 31 | Desbloquea nuevas funciones cuando tus dispositivos favoritos trabajen juntos 32 | 33 | -------------------------------------------------------------------------------- /connect-button/src/main/res/values-fi/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Suojaa ja tarkastele tietojasi 4 | Yhteys katkaistaan... 5 | Käynnistyskuvake 6 | Varmista, että puhelimesi on yhteydessä internetiin 7 | Yhteys katkaistu 8 | Anna kelvollinen sähköpostiosoite 9 | Poista käytöstä koska tahansa 10 | Uusi IFTTT-tili käyttäjälle %1$s 11 | Lisätietoja 12 | Yritä uudelleen 13 | Yhdistä 14 | Siirrytään kohteeseen %1$s... 15 | palvelukuvake %1$s 16 | fi 17 | TOIMII IFTTT:n kanssa 18 | Katkaise yhteys 19 | Ladataan... 20 | Tiliä luodaan... 21 | Hallinnoi 22 | Yhdistetty: 23 | Käyttämällä tätä yhteyttä hyväksyt Yksityisyys ja ehdot 24 | Yksityisyys ja ehdot 25 | IFTTT yhdistää kohteen %1$s kohteeseen %2$s 26 | Sähköposti 27 | Yhdistetään 28 | Yhdistä %1$s 29 | Määritä, mitkä palvelut voivat käyttää tietojasi ja miten 30 | IFTTT-suojattu. Lisätietoja 31 | Kun lempiasiasi toimivat yhdessä, voit hyödyntää uusia ominaisuuksia 32 | 33 | -------------------------------------------------------------------------------- /connect-button/src/main/res/values-fr-rCA/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Protégez et surveillez vos données 4 | Déconnexion... 5 | Icône de démarrage 6 | Assurez-vous que votre téléphone dispose d\'une connexion Internet 7 | Déconnecté 8 | Saisir une adresse courriel valide 9 | Débranchez à tout moment 10 | Nouveau compte IFTTT pour %1$s 11 | En savoir plus 12 | Réessayer 13 | Connecter 14 | Aller à %1$s... 15 | icône de service %1$s 16 | fr-CA 17 | FONCTIONNE AVEC IFTTT 18 | Se déconnecter 19 | Chargement... 20 | Création du compte... 21 | Gérer 22 | Connecté 23 | En utilisant cette connexion, vous acceptez nos Confidentialité et modalités 24 | Confidentialité et modalités 25 | IFTTT connecte %1$s à %2$s 26 | Courriel 27 | Connexion 28 | Connecter %1$s 29 | Contrôlez quels services accèdent à vos données et comment ils les utilisent 30 | Sécurisé avec IFTTT. En savoir plus 31 | Déverrouillez de nouvelles fonctionnalités lorsque vos choses préférées fonctionnent ensemble 32 | 33 | -------------------------------------------------------------------------------- /connect-button/src/main/res/values-fr/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Protégez et surveillez vos données 4 | Déconnexion... 5 | Icône de démarrage 6 | Assurez-vous que votre téléphone dispose d\'une connexion Internet 7 | Déconnecté 8 | Entrez une adresse e-mail valide 9 | Débranchez à tout moment 10 | Nouveau compte IFTTT pour %1$s 11 | En savoir plus 12 | Réessayer 13 | Connecter 14 | Déplacement vers %1$s... 15 | Icône de service %1$s 16 | fr 17 | FONCTIONNE AVEC IFTTT 18 | Déconnexion 19 | Chargement... 20 | Création du compte... 21 | Gérer 22 | Connecté 23 | En utilisant cette connexion, vous acceptez nos Confidentialité et conditions 24 | Confidentialité et conditions 25 | IFTTT connecte %1$s à %2$s 26 | Adresse e-mail 27 | Connexion 28 | Connecter %1$s 29 | Contrôlez quels services accèdent à vos données et comment ils les utilisent 30 | Sécurisé avec IFTTT. En savoir plus 31 | Déverrouillez de nouvelles fonctionnalités lorsque vos objets préférés fonctionnent ensemble 32 | 33 | -------------------------------------------------------------------------------- /connect-button/src/main/res/values-it/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Proteggi e monitora i tuoi dati 4 | Scollegamento in corso... 5 | Icona di avvio 6 | Assicurati che il tuo telefono sia collegato a Internet 7 | Scollegato 8 | Inserisci un indirizzo e-mail valido 9 | Scollegati quando vuoi 10 | Nuovo account IFTTT per %1$s 11 | Per saperne di più 12 | Riprova 13 | Collega 14 | Spostamento verso %1$s... 15 | icona del servizio %1$s 16 | it 17 | FUNZIONA CON IFTTT 18 | Disconnetti 19 | Caricamento... 20 | Creazione dell\'account... 21 | Gestisci 22 | Collegato a 23 | Utilizzando questo collegamento, l\'utente accetta la nostra Informativa sulla privacy e termini 24 | Informativa sulla privacy e termini 25 | IFTTT si collega a %1$s per %2$s 26 | E-mail 27 | Connessione 28 | Collega %1$s 29 | Controlla quali servizi accedono ai tuoi dati e come li utilizzano 30 | Protetto con IFTTT. Per saperne di più 31 | Sblocca nuove funzioni quando i tuoi elementi preferiti funzionano assieme 32 | 33 | -------------------------------------------------------------------------------- /connect-button/src/main/res/values-ja/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | データを保護し、監視します 4 | 接続解除中... 5 | スタートアイコン 6 | スマートフォンがインターネットに接続されていることを確認してください 7 | 切断済み 8 | 有効な電子メールの入力 9 | いつでも停止可能 10 | %1$sの新規アカウント 11 | 詳細を確認する 12 | 再試行 13 | 接続 14 | %1$sに移動中... 15 | %1$sサービスアイコン 16 | ja 17 | IFTTTと連携 18 | 切断 19 | 読み込み中… 20 | アカウントを作成中... 21 | 管理 22 | 接続済み 23 | この接続を使用すると、プライバシーと利用規約に同意したことになります 24 | プライバシーと利用規約 25 | IFTTTは%1$sを%2$sに接続します 26 | メール 27 | 接続中 28 | %1$sに接続 29 | データにアクセスするサービスとその使用方法を制御します 30 | IFTTTで保護 詳細を確認する 31 | お気に入りのものを連携すると、新しい機能を利用できます 32 | 33 | -------------------------------------------------------------------------------- /connect-button/src/main/res/values-ko/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 데이터 보호 및 모니터링 4 | 연결을 해제하는 중... 5 | 시작 아이콘 6 | 전화가 인터넷에 연결되었는지 확인 7 | 연결 해제됨 8 | 올바른 이메일 입력 9 | 언제든지 플러그 분리 10 | %1$s을(를) 위한 새 IFTTT 계정 11 | 자세히 알아보기 12 | 재시도 13 | 연결 14 | %1$s(으)로 이동하는 중... 15 | %1$s 서비스 아이콘 16 | ko 17 | IFTTT와 함께 사용 가능 18 | 연결 해제 19 | 로드 중... 20 | 계정을 만드는 중... 21 | 관리 22 | 연결된 23 | 이 연결 사용 시 당사의 개인정보 개인정보 및 약관 24 | 개인정보 및 약관 25 | IFTTT는 %1$s을(를) %2$s에 연결합니다. 26 | 이메일 27 | 연결 중 28 | %1$s에 연결 29 | 데이터에 액세스하는 서비스와 사용하는 방법 제어 30 | IFTTT로 보안 처리되었습니다. 자세히 알아보기 31 | 즐겨 사용하는 장치를 함께 사용할 때 새 기능 잠금 해제 32 | 33 | -------------------------------------------------------------------------------- /connect-button/src/main/res/values-nb/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Beskytt og overvåk dataene dine 4 | Kobler fra ... 5 | Start-ikon 6 | Kontroller at telefonen har tilkobling til Internett 7 | Frakoblet 8 | Angi en gyldig e-postadresse 9 | Koble ut når som helst 10 | Ny IFTTT-konto for %1$s 11 | Finn ut mer 12 | Prøv igjen 13 | Koble til 14 | Går til %1$s ... 15 | %1$s-tjenesteikon 16 | nb 17 | FUNGERER MED IFTTT 18 | Koble fra 19 | Laster inn ... 20 | Oppretter konto ... 21 | Administrer 22 | Tilkoblet 23 | Ved å bruke denne koblingen, godtar du våre retningslinjer for Personvern og vilkår 24 | Personvern og vilkår 25 | IFTTT kobler %1$s til %2$s 26 | E-post 27 | Kobler til 28 | Koble til %1$s 29 | Kontroller hvilke tjenester som har tilgang til dataene dine og hvordan de bruker dem 30 | Sikret med IFTTT. Finn ut mer 31 | Lås opp nye funksjoner når favorittingene dine samarbeider 32 | 33 | -------------------------------------------------------------------------------- /connect-button/src/main/res/values-nl/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Uw gegevens beschermen en controleren 4 | Verbinding verbreken... 5 | Startpictogram 6 | Zorg dat uw telefoon een internetverbinding heeft 7 | Verbinding verbroken 8 | Vul een geldig e-mailadres in 9 | Op elk gewenst moment loskoppelen 10 | Nieuw IFTTT-account voor %1$s 11 | Meer informatie 12 | Opnieuw proberen 13 | Verbinden 14 | Gaat %1$s naar... 15 | %1$s-servicepictogram 16 | nl 17 | WERKT MET IFTTT 18 | Verbinding verbreken 19 | Bezig met laden... 20 | Account maken... 21 | Beheren 22 | Verbonden 23 | Door deze verbinding te gebruiken, gaat u akkoord met onze Privacy en voorwaarden 24 | Privacy en voorwaarden 25 | IFTTT verbindt %1$s met %2$s 26 | E-mail 27 | Verbinding maken 28 | Verbinden met %1$s 29 | Bepaal welke services toegang hebben tot uw gegevens en hoe ze deze gebruiken 30 | Beveiligd met IFTTT. Meer informatie 31 | Ontgrendel nieuwe functies wanneer uw favoriete dingen samenwerken 32 | 33 | -------------------------------------------------------------------------------- /connect-button/src/main/res/values-pl/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Monitoruj i chroń swoje dane 4 | Rozłączanie... 5 | Ikona Start 6 | Upewnij się, że Twój telefon ma połączenie z Internetem 7 | Rozłączono 8 | Wprowadź prawidłowy e-mail 9 | Możesz odłączyć w dowolnym momencie 10 | Nowe konto IFTTT dla %1$s 11 | Dowiedz się więcej 12 | Spróbuj ponownie 13 | Połącz 14 | Przechodzenie do %1$s... 15 | Ikona usługi %1$s 16 | pl 17 | DZIAŁA Z IFTTT 18 | Rozłącz 19 | Wczytywanie... 20 | Tworzenie konta... 21 | Zarządzaj 22 | Połączony 23 | Korzystanie z tego połączenia jest równoznaczne z wyrażeniem zgody na Warunki i Polityka prywatności 24 | Warunki i Polityka prywatności 25 | IFTTT łączy %1$s z %2$s 26 | E-mail 27 | Łączenie 28 | Połącz %1$s 29 | Kontroluj, które usługi mogą mieć dostęp do Twoich danych, i jak mogą je wykorzystywać 30 | Zabezpieczone za pomocą IFTTT. Dowiedz się więcej 31 | Odkrywaj nowe funkcje, gdy ulubione elementy współpracują ze sobą 32 | 33 | -------------------------------------------------------------------------------- /connect-button/src/main/res/values-pt-rBR/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Proteja e monitore seus dados 4 | Desconectando... 5 | Ícone de início 6 | Verifique se o telefone tem conexão com a Internet 7 | Desconectado 8 | Insira um e-mail válido 9 | Desconecte a qualquer momento 10 | Nova conta do IFTTT para %1$s 11 | Saiba mais 12 | Tentar novamente 13 | Conectar-se 14 | Indo para %1$s... 15 | Ícone de serviço do %1$s 16 | pt-BR 17 | TAREFAS COM O IFTTT 18 | Desconectar 19 | Carregando... 20 | Criando conta... 21 | Gerenciar 22 | Conectado 23 | Ao usar esta conexão, você concorda com nossa Privacidade e nossos Termos 24 | Privacidade e nossos Termos 25 | O IFTTT conecta %1$s a %2$s 26 | E-mail 27 | Conectando 28 | Conectar %1$s 29 | Controle quais serviços acessam seus dados e como eles os usam 30 | Seguro com o IFTTT. Saiba mais 31 | Libere novos recursos quando seus produtos favoritos funcionam em conjunto 32 | 33 | -------------------------------------------------------------------------------- /connect-button/src/main/res/values-pt-rPT/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Proteja e monitorize os seus dados 4 | A desligar... 5 | Ícone de iniciar 6 | Certifique-se de que o seu telefone tem ligação à Internet 7 | Desligado 8 | Introduza um e-mail válido 9 | Desligue em qualquer altura 10 | Nova conta IFTTT para %1$s 11 | Mais informações 12 | Repetir 13 | Estabelecer ligação 14 | A ir para %1$s... 15 | Ícone de serviço %1$s 16 | pt-PT 17 | FUNCIONA COM IFTTT 18 | Desligar 19 | A carregar 20 | A criar conta... 21 | Gerir 22 | ligado 23 | A utilizar esta ligação, concorda com os nossos Termos e Privacidade 24 | Termos e Privacidade 25 | IFTTT liga %1$s para %2$s 26 | E-mail 27 | A ligar 28 | Estabelecer ligação a %1$s 29 | Controle que serviços acedem aos seus dados e como utilizá-los 30 | Protegido com IFTTT. Mais informações 31 | Desbloqueie novas funcionalidades quando os seus equipamentos favoritos trabalham em conjunto 32 | 33 | -------------------------------------------------------------------------------- /connect-button/src/main/res/values-ru/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Защищайте и отслеживайте свои данные 4 | Отключение... 5 | Значок запуска 6 | Убедитесь, что телефон подключен к Интернету 7 | Отключено 8 | Введите действительный адрес 9 | Вы можете отключиться в любой момент! 10 | Новая учетная запись IFTTT для %1$s 11 | Узнайте больше 12 | Повторить попытку 13 | Подключиться 14 | Переход к %1$s... 15 | Значок службы %1$s 16 | ru 17 | РАБОТАЕТ С IFTTT 18 | Отключиться 19 | Загрузка... 20 | Создание учетной записи... 21 | Управление 22 | Подключено: 23 | Используя это подключение, вы принимаете наши Политику конфиденциальности и Условия 24 | конфиденциальности и Условия 25 | IFTTT подключает %1$s к %2$s 26 | Адрес электронной почты 27 | Подключение 28 | Подключить %1$s 29 | Контролируйте доступ различных служб к вашим данным и их использование 30 | Под защитой IFTTT. Узнайте больше 31 | Откройте новые возможности благодаря взаимодействию ваших любимых вещей 32 | 33 | -------------------------------------------------------------------------------- /connect-button/src/main/res/values-sv/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Skydda och övervaka dina uppgifter 4 | Kopplar från… 5 | Startsymbol 6 | Kontrollera att din telefon har en Internetanslutning 7 | Frånkopplad 8 | Ange en giltig e-postadress 9 | Koppla ur när som helst 10 | Nytt IFTTT-konto för %1$s 11 | Läs mer 12 | Försök igen 13 | Anslut 14 | Går till %1$s… 15 | Servicesymbol för %1$s 16 | sv 17 | FUNGERAR MED IFTTT 18 | Koppla från 19 | Läser in ... 20 | Skapar konto… 21 | Hantera 22 | Ansluten till 23 | Genom att använda den här anslutningen accepterar du vår Sekretesspolicy och våra villkor 24 | Sekretesspolicy och våra villkor 25 | IFTTT ansluter %1$s till %2$s 26 | E-post 27 | Ansluter 28 | Anslut %1$s 29 | Styr vilka tjänster som kommer åt dina uppgifter och hur de använder dem 30 | Skyddad med IFTTT. Läs mer 31 | Upptäck nya funktioner när dina favoritsaker arbetar tillsammans 32 | 33 | -------------------------------------------------------------------------------- /connect-button/src/main/res/values-v21/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #666666 4 | 5 | -------------------------------------------------------------------------------- /connect-button/src/main/res/values-v21/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | -------------------------------------------------------------------------------- /connect-button/src/main/res/values-zh-rCN/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 保护和监控您的数据 4 | 正在断开连接... 5 | 开始图标 6 | 确保您的手机已连接互联网 7 | 已断开连接 8 | 输入有效的邮箱 9 | 可随时断电 10 | 为 %1$s 新建 IFTTT 帐户 11 | 了解更多 12 | 重试 13 | 连接 14 | 正在转到 %1$s... 15 | %1$s 服务图标 16 | zh-Hans 17 | 使用 IFTTT 18 | 断开连接 19 | 正在加载... 20 | 正在创建帐户... 21 | 管理 22 | 已连接 23 | 使用此连接,即表示您同意我们的隐私条款 24 | 隐私条款 25 | IFTTT 将 %1$s 连接至 %2$s 26 | 电子邮箱 27 | 正在连接 28 | 连接 %1$s 29 | 控制可访问您数据的服务,并管理其使用方式 30 | IFTTT 提供保护。了解更多 31 | 常用工具齐登场,助力解锁新功能。 32 | 33 | -------------------------------------------------------------------------------- /connect-button/src/main/res/values-zh-rTW/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 保護並監控您的資料 4 | 正在中斷連線... 5 | 開始圖示 6 | 請確定您的手機有網際網路連線 7 | 已中斷連線 8 | 輸入有效的電子郵件 9 | 隨時斷開 10 | %1$s​ 的 IFTTT 新帳戶 11 | 了解更多 12 | 重試 13 | 連線 14 | 正在前往 %1$s... 15 | %1$s 服務圖示 16 | zh-Hant 17 | 搭配使用 IFTTT 18 | 中斷連線 19 | 正在載入... 20 | 正在建立帳戶... 21 | 管理 22 | 已連線 23 | 使用此連線,即表示您同意我們的隱私權與條款 24 | 隱私權與條款 25 | IFTTT 會將 %1$s 連線至 %2$s 26 | 電子郵件 27 | 正在連線 28 | 連線 %1$s 29 | 控制哪些服務可存取您的資料,以及使用資料的方式 30 | 透過 IFTTT 保護。了解更多 31 | 讓您喜愛的事物彼此合作,就能發掘出新功能 32 | 33 | -------------------------------------------------------------------------------- /connect-button/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #EEEEEE 4 | #CBCBCB 5 | #222222 6 | #444444 7 | #41FFFFFF 8 | #222222 9 | #666666 10 | #FFFFFF 11 | #FF2121 12 | #4DFFFFFF 13 | #4D000000 14 | #4CFFFFFF 15 | 16 | -------------------------------------------------------------------------------- /connect-button/src/main/res/values/ids.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /connect-button/src/main/res/values/public.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /connect-button/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Connect %1$s 4 | Connect 5 | Email 6 | IFTTT connects %1$s to %2$s 7 | Learn more 8 | WORKS WITH IFTTT 9 | Unlock new features when your favorite things work together 10 | Control which services access your data and how they use it 11 | Protect and monitor your data 12 | Unplug any time 13 | Creating account… 14 | Going to %1$s… 15 | Connected 16 | Connecting… 17 | Disconnecting… 18 | Disconnect 19 | Please enter a valid email address 20 | By using this connection you agree to our Privacy & Terms 21 | Privacy & Terms 22 | Secured with IFTTT Learn more 23 | Manage 24 | Loading… 25 | Make sure your phone has internet connection 26 | New IFTTT account for %1$s 27 | Retry 28 | 29 | 30 | 31 | %1$s service icon 32 | Start icon 33 | Disconnected 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /connect-button/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 9 | 10 | 13 | 14 | -------------------------------------------------------------------------------- /connect-button/src/test/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /connect-button/src/test/java/com/ifttt/connect/api/TestUtils.java: -------------------------------------------------------------------------------- 1 | package com.ifttt.connect.api; 2 | 3 | import android.content.Context; 4 | import com.squareup.moshi.JsonAdapter; 5 | import com.squareup.moshi.JsonReader; 6 | import com.squareup.moshi.Moshi; 7 | import com.squareup.moshi.adapters.Rfc3339DateJsonAdapter; 8 | import java.io.IOException; 9 | import java.io.InputStream; 10 | import java.util.Date; 11 | import okhttp3.mockwebserver.MockWebServer; 12 | import okio.Okio; 13 | 14 | public final class TestUtils { 15 | 16 | private static final Moshi MOSHI = new Moshi.Builder().add(Date.class, new Rfc3339DateJsonAdapter().nullSafe()).add( 17 | new HexColorJsonAdapter()).add(new ConnectionJsonAdapter()).build(); 18 | 19 | private static final JsonAdapter CONNECTION_ADAPTER = MOSHI.adapter(Connection.class); 20 | 21 | public static Connection loadConnection(ClassLoader classLoader) throws IOException { 22 | InputStream inputStream = classLoader.getResourceAsStream("connection.json"); 23 | JsonReader jsonReader = JsonReader.of(Okio.buffer(Okio.source(inputStream))); 24 | return CONNECTION_ADAPTER.fromJson(jsonReader); 25 | } 26 | 27 | public static ConnectionApiClient getMockConnectionApiClient( 28 | Context context, MockWebServer server, UserTokenProvider provider 29 | ) { 30 | return new ConnectionApiClient.Builder(context, provider).buildWithBaseUrl(server.url("").toString()); 31 | } 32 | 33 | private TestUtils() { 34 | throw new AssertionError(); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /connect-button/src/test/java/com/ifttt/connect/ui/ConnectResultTest.java: -------------------------------------------------------------------------------- 1 | package com.ifttt.connect.ui; 2 | 3 | import android.content.Intent; 4 | import android.net.Uri; 5 | import org.junit.Test; 6 | import org.junit.runner.RunWith; 7 | import org.robolectric.RobolectricTestRunner; 8 | 9 | import static com.google.common.truth.Truth.assertThat; 10 | 11 | @RunWith(RobolectricTestRunner.class) 12 | public final class ConnectResultTest { 13 | 14 | @Test 15 | public void fromInvalidServiceConnection() { 16 | Intent intent = new Intent().setData(Uri.parse("test://url?next_step=service_connection")); 17 | ConnectResult result = ConnectResult.fromIntent(intent); 18 | 19 | assertThat(result.nextStep).isEqualTo(ConnectResult.NextStep.Unknown); 20 | assertThat(result.errorType).isNull(); 21 | } 22 | 23 | @Test 24 | public void fromComplete() { 25 | Intent intent = new Intent().setData(Uri.parse("test://url?next_step=complete")); 26 | ConnectResult result = ConnectResult.fromIntent(intent); 27 | 28 | assertThat(result.nextStep).isEqualTo(ConnectResult.NextStep.Complete); 29 | assertThat(result.errorType).isNull(); 30 | } 31 | 32 | @Test 33 | public void fromUnknownState() { 34 | Intent intent = new Intent().setData(Uri.parse("test://url?")); 35 | ConnectResult result = ConnectResult.fromIntent(intent); 36 | 37 | assertThat(result.nextStep).isEqualTo(ConnectResult.NextStep.Unknown); 38 | assertThat(result.errorType).isNull(); 39 | } 40 | 41 | @Test 42 | public void fromError() { 43 | Intent intent = new Intent().setData(Uri.parse("test://url?next_step=error&error_type=account_creation")); 44 | ConnectResult result = ConnectResult.fromIntent(intent); 45 | 46 | assertThat(result.nextStep).isEqualTo(ConnectResult.NextStep.Error); 47 | assertThat(result.errorType).isEqualTo("account_creation"); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /connect-button/src/test/java/com/ifttt/connect/ui/DisableTrackingTest.java: -------------------------------------------------------------------------------- 1 | package com.ifttt.connect.ui; 2 | 3 | import static com.google.common.truth.Truth.assertThat; 4 | 5 | import android.content.Context; 6 | 7 | import androidx.test.core.app.ApplicationProvider; 8 | import androidx.test.ext.junit.runners.AndroidJUnit4; 9 | import androidx.work.Configuration; 10 | import androidx.work.testing.SynchronousExecutor; 11 | import androidx.work.testing.WorkManagerTestInitHelper; 12 | 13 | import org.junit.Before; 14 | import org.junit.Test; 15 | import org.junit.runner.RunWith; 16 | import org.robolectric.annotation.LooperMode; 17 | 18 | @RunWith(AndroidJUnit4.class) 19 | @LooperMode(LooperMode.Mode.PAUSED) 20 | public final class DisableTrackingTest { 21 | 22 | private AnalyticsManager analyticsManager; 23 | 24 | @Before 25 | public void setup() { 26 | Context context = ApplicationProvider.getApplicationContext(); 27 | Configuration config = new Configuration.Builder() 28 | .setExecutor(new SynchronousExecutor()) 29 | .build(); 30 | WorkManagerTestInitHelper.initializeTestWorkManager(context, config); 31 | 32 | analyticsManager = AnalyticsManager.getInstance(context); 33 | } 34 | 35 | @Test 36 | public void testAnalyticsDisabled() { 37 | analyticsManager.clearQueue(); 38 | assertThat(analyticsManager.performRead().size()).isEqualTo(0); 39 | 40 | analyticsManager.disableTracking(); 41 | analyticsManager.trackUiClick(new AnalyticsObject("obj_id", "obj_type"), new AnalyticsLocation("loc_id", "loc_type")); 42 | 43 | assertThat(analyticsManager.performRead().size()).isEqualTo(0); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /connect-button/src/test/java/com/ifttt/connect/ui/QueueOperationsTest.java: -------------------------------------------------------------------------------- 1 | package com.ifttt.connect.ui; 2 | 3 | import static com.google.common.truth.Truth.assertThat; 4 | 5 | import androidx.test.ext.junit.runners.AndroidJUnit4; 6 | 7 | import com.ifttt.connect.R; 8 | 9 | import org.junit.Before; 10 | import org.junit.Test; 11 | import org.junit.runner.RunWith; 12 | import org.robolectric.Robolectric; 13 | import org.robolectric.android.controller.ActivityController; 14 | import org.robolectric.annotation.Config; 15 | 16 | import java.util.HashMap; 17 | 18 | @RunWith(AndroidJUnit4.class) 19 | @Config(sdk = 28) 20 | public final class QueueOperationsTest { 21 | private AnalyticsManager analyticsManager; 22 | 23 | @Before 24 | public void setup() { 25 | ActivityController controller = Robolectric.buildActivity(TestActivity.class); 26 | controller.get().setTheme(R.style.Base_Theme_AppCompat); 27 | controller.create().start(); 28 | 29 | analyticsManager = AnalyticsManager.getInstance(controller.get()); 30 | analyticsManager.clearQueue(); 31 | } 32 | 33 | @Test 34 | public void testEnqueueAndRead() { 35 | assertThat(analyticsManager.performRead().size()).isEqualTo(0); 36 | 37 | analyticsManager.performAdd(new AnalyticsEventPayload("event1", "", new HashMap<>())); 38 | analyticsManager.performAdd(new AnalyticsEventPayload("event2", "", new HashMap<>())); 39 | analyticsManager.performAdd(new AnalyticsEventPayload("event3", "", new HashMap<>())); 40 | 41 | assertThat(analyticsManager.performRead().size()).isEqualTo(3); 42 | } 43 | 44 | @Test 45 | public void testRemove() { 46 | assertThat(analyticsManager.performRead().size()).isEqualTo(0); 47 | 48 | analyticsManager.performAdd(new AnalyticsEventPayload("event1", "", new HashMap<>())); 49 | analyticsManager.performAdd(new AnalyticsEventPayload("event2", "", new HashMap<>())); 50 | analyticsManager.performAdd(new AnalyticsEventPayload("event3", "", new HashMap<>())); 51 | 52 | analyticsManager.performRemove(3); 53 | assertThat(analyticsManager.performRead().size()).isEqualTo(0); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /connect-button/src/test/java/com/ifttt/connect/ui/StartIconDrawableTest.java: -------------------------------------------------------------------------------- 1 | package com.ifttt.connect.ui; 2 | 3 | import org.junit.Test; 4 | import org.junit.runner.RunWith; 5 | import org.robolectric.RobolectricTestRunner; 6 | 7 | import static android.graphics.Color.parseColor; 8 | import static com.google.common.truth.Truth.assertThat; 9 | import static com.ifttt.connect.ui.StartIconDrawable.isDarkColor; 10 | 11 | @RunWith(RobolectricTestRunner.class) 12 | public final class StartIconDrawableTest { 13 | 14 | @Test 15 | public void testIsDarkColor() { 16 | String[] colors = new String[] { 17 | "#333333", "#3B579D", "#E4405F", "#0099FF", "#4D4E4D", "#1ED760", "#000000", "#1E2023", "#FFFFFF" 18 | }; 19 | 20 | assertThat(isDarkColor(parseColor(colors[0]))).isTrue(); 21 | assertThat(isDarkColor(parseColor(colors[1]))).isFalse(); 22 | assertThat(isDarkColor(parseColor(colors[2]))).isFalse(); 23 | assertThat(isDarkColor(parseColor(colors[3]))).isFalse(); 24 | assertThat(isDarkColor(parseColor(colors[4]))).isFalse(); 25 | assertThat(isDarkColor(parseColor(colors[5]))).isFalse(); 26 | assertThat(isDarkColor(parseColor(colors[6]))).isTrue(); 27 | assertThat(isDarkColor(parseColor(colors[7]))).isTrue(); 28 | assertThat(isDarkColor(parseColor(colors[8]))).isFalse(); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /connect-button/src/test/java/com/ifttt/connect/ui/TestActivity.java: -------------------------------------------------------------------------------- 1 | package com.ifttt.connect.ui; 2 | 3 | import android.app.Activity; 4 | import android.os.Bundle; 5 | import android.widget.FrameLayout; 6 | import androidx.annotation.Nullable; 7 | import androidx.work.Configuration; 8 | import androidx.work.testing.SynchronousExecutor; 9 | import androidx.work.testing.WorkManagerTestInitHelper; 10 | import com.ifttt.connect.R; 11 | 12 | import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; 13 | import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; 14 | 15 | public final class TestActivity extends Activity { 16 | 17 | @Override 18 | protected void onCreate(@Nullable Bundle savedInstanceState) { 19 | super.onCreate(savedInstanceState); 20 | 21 | final Configuration config = new Configuration.Builder() 22 | .setExecutor(new SynchronousExecutor()) 23 | .build(); 24 | WorkManagerTestInitHelper.initializeTestWorkManager( 25 | this, config); 26 | 27 | BaseConnectButton button = new BaseConnectButton(this); 28 | button.setId(R.id.ifttt_connect_button_test); 29 | button.setLayoutParams(new FrameLayout.LayoutParams(MATCH_PARENT, WRAP_CONTENT)); 30 | 31 | setContentView(button); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /connect-location/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /connect-location/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | 3 | android { 4 | compileSdkVersion rootProject.compileSdkVersion 5 | 6 | defaultConfig { 7 | minSdkVersion rootProject.minSdkVersion 8 | 9 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 10 | } 11 | 12 | buildTypes { 13 | release { 14 | minifyEnabled false 15 | } 16 | } 17 | 18 | compileOptions { 19 | sourceCompatibility JavaVersion.VERSION_1_8 20 | targetCompatibility JavaVersion.VERSION_1_8 21 | } 22 | 23 | testOptions.unitTests.includeAndroidResources = true 24 | namespace 'com.ifttt.location' 25 | } 26 | 27 | dependencies { 28 | debugImplementation project(':connect-button') 29 | releaseImplementation "com.ifttt:connect-button:$libVersion" 30 | releaseImplementation "com.ifttt:connect-api:$libVersion" 31 | 32 | implementation "com.google.android.gms:play-services-awareness:$awarenessVersion" 33 | implementation "androidx.work:work-runtime:$workManagerVersion" 34 | 35 | testImplementation "junit:junit:$junitVersion" 36 | testImplementation "org.robolectric:robolectric:$robolectricVersion" 37 | testImplementation "androidx.test:runner:$androidXTestVersion" 38 | testImplementation "androidx.test:core:$androidXTestVersion" 39 | testImplementation "androidx.test.ext:truth:$androidXTestVersion" 40 | testImplementation "androidx.test.ext:junit:$androidXJunitVersion" 41 | testImplementation "com.squareup.retrofit2:retrofit-mock:$retrofitVersion" 42 | testImplementation "androidx.work:work-testing:$workManagerVersion" 43 | } 44 | 45 | apply from: 'publish.gradle' 46 | -------------------------------------------------------------------------------- /connect-location/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /connect-location/src/main/java/com/ifttt/location/AwarenessEnterReceiver.java: -------------------------------------------------------------------------------- 1 | package com.ifttt.location; 2 | 3 | import android.content.BroadcastReceiver; 4 | import android.content.Context; 5 | import android.content.Intent; 6 | import com.google.android.gms.awareness.fence.FenceState; 7 | 8 | import static com.ifttt.location.LocationEventAttributes.LocationDataSource.Awareness; 9 | import static com.ifttt.location.LocationEventUploader.EventType.Entry; 10 | 11 | /** 12 | * BroadcastReceiver that listens to all "enter" geo-fence events. 13 | */ 14 | public final class AwarenessEnterReceiver extends BroadcastReceiver { 15 | 16 | @Override 17 | public void onReceive(Context context, Intent intent) { 18 | FenceState fenceState = FenceState.extract(intent); 19 | if (fenceState.getCurrentState() != FenceState.TRUE) { 20 | Logger.error("Geo-fence enter event, fence state: " + fenceState.getCurrentState()); 21 | return; 22 | } 23 | 24 | BackupGeofenceMonitor monitor = BackupGeofenceMonitor.get(context); 25 | if (BackupGeofenceMonitor.MonitoredGeofence.GeofenceState.Entered 26 | == monitor.getState(fenceState.getFenceKey())) { 27 | return; 28 | } 29 | 30 | Logger.log("Geo-fence enter event"); 31 | LocationEventHelper.logEventReported(ConnectLocation.getInstance(), Entry, Awareness); 32 | 33 | String stepId = LocationEventUploadHelper.extractStepId(fenceState.getFenceKey()); 34 | LocationEventUploader.schedule(context, Entry, Awareness, stepId); 35 | monitor.setState(fenceState.getFenceKey(), BackupGeofenceMonitor.MonitoredGeofence.GeofenceState.Entered); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /connect-location/src/main/java/com/ifttt/location/AwarenessExitReceiver.java: -------------------------------------------------------------------------------- 1 | package com.ifttt.location; 2 | 3 | import android.content.BroadcastReceiver; 4 | import android.content.Context; 5 | import android.content.Intent; 6 | import com.google.android.gms.awareness.fence.FenceState; 7 | 8 | import static com.ifttt.location.LocationEventAttributes.LocationDataSource.Awareness; 9 | import static com.ifttt.location.LocationEventUploader.EventType.Exit; 10 | 11 | /** 12 | * BroadcastReceiver that listens to all "exit" geo-fence events. 13 | */ 14 | public final class AwarenessExitReceiver extends BroadcastReceiver { 15 | 16 | @Override 17 | public void onReceive(Context context, Intent intent) { 18 | FenceState fenceState = FenceState.extract(intent); 19 | if (fenceState.getCurrentState() != FenceState.TRUE) { 20 | Logger.error("Geo-fence exit event, fence state: " + fenceState.getCurrentState()); 21 | return; 22 | } 23 | 24 | BackupGeofenceMonitor monitor = BackupGeofenceMonitor.get(context); 25 | if (BackupGeofenceMonitor.MonitoredGeofence.GeofenceState.Exited 26 | == monitor.getState(fenceState.getFenceKey())) { 27 | return; 28 | } 29 | 30 | Logger.log("Geo-fence exit event"); 31 | LocationEventHelper.logEventReported(ConnectLocation.getInstance(), Exit, Awareness); 32 | 33 | String stepId = LocationEventUploadHelper.extractStepId(fenceState.getFenceKey()); 34 | LocationEventUploader.schedule(context, Exit, Awareness, stepId); 35 | monitor.setState(fenceState.getFenceKey(), BackupGeofenceMonitor.MonitoredGeofence.GeofenceState.Exited); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /connect-location/src/main/java/com/ifttt/location/Cache.java: -------------------------------------------------------------------------------- 1 | package com.ifttt.location; 2 | 3 | /** 4 | * Interface representing cached data within ConnectLocation module. 5 | * 6 | * @see SharedPreferencesGeofenceCache 7 | * @see SharedPreferenceUserTokenCache 8 | */ 9 | interface Cache { 10 | void write(T t); 11 | 12 | T read(); 13 | 14 | void clear(); 15 | } 16 | -------------------------------------------------------------------------------- /connect-location/src/main/java/com/ifttt/location/CacheUserTokenProvider.java: -------------------------------------------------------------------------------- 1 | package com.ifttt.location; 2 | 3 | import androidx.annotation.Nullable; 4 | import com.ifttt.connect.api.UserTokenProvider; 5 | 6 | class CacheUserTokenProvider implements UserTokenProvider { 7 | 8 | private final Cache cache; 9 | @Nullable private final UserTokenProvider delegate; 10 | 11 | CacheUserTokenProvider(Cache cache, @Nullable UserTokenProvider delegate) { 12 | this.cache = cache; 13 | this.delegate = delegate; 14 | } 15 | 16 | @Nullable 17 | @Override 18 | public String getUserToken() { 19 | String token; 20 | if (delegate != null) { 21 | token = delegate.getUserToken(); 22 | if (token != null && token.length() > 0) { 23 | cache.write(token); 24 | } 25 | } else { 26 | token = cache.read(); 27 | } 28 | 29 | return token; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /connect-location/src/main/java/com/ifttt/location/GeofenceProvider.java: -------------------------------------------------------------------------------- 1 | package com.ifttt.location; 2 | 3 | import android.Manifest; 4 | import androidx.annotation.Nullable; 5 | import androidx.annotation.RequiresPermission; 6 | import com.ifttt.connect.api.Connection; 7 | import com.ifttt.location.ConnectLocation.LocationStatusCallback; 8 | import java.util.Arrays; 9 | import java.util.HashSet; 10 | import java.util.Set; 11 | 12 | /** 13 | * Abstract type for a geofence functionality provider. The type is responsbile for setting up geofences given a 14 | * {@link Connection}. 15 | */ 16 | interface GeofenceProvider { 17 | 18 | String FIELD_TYPE_LOCATION_ENTER = "LOCATION_ENTER"; 19 | String FIELD_TYPE_LOCATION_EXIT = "LOCATION_EXIT"; 20 | String FIELD_TYPE_LOCATION_ENTER_EXIT = "LOCATION_ENTER_OR_EXIT"; 21 | 22 | Set LOCATION_FIELD_TYPES_LIST = new HashSet<>(Arrays.asList(FIELD_TYPE_LOCATION_ENTER, 23 | FIELD_TYPE_LOCATION_EXIT, 24 | FIELD_TYPE_LOCATION_ENTER_EXIT 25 | )); 26 | 27 | @RequiresPermission(Manifest.permission.ACCESS_FINE_LOCATION) 28 | void updateGeofences(final Connection connection, @Nullable LocationStatusCallback locationStatusCallback); 29 | 30 | void removeGeofences(@Nullable LocationStatusCallback locationStatusCallback); 31 | } 32 | -------------------------------------------------------------------------------- /connect-location/src/main/java/com/ifttt/location/LocationEventAttributes.java: -------------------------------------------------------------------------------- 1 | package com.ifttt.location; 2 | 3 | /** 4 | * Attributes and meta-data for background location events reported by {@link LocationEventListener}. 5 | */ 6 | public final class LocationEventAttributes { 7 | 8 | /** 9 | * Location change event types, represented by {@link LocationEventUploader.EventType}. 10 | */ 11 | public static final String LOCATION_EVENT_EVENT_TYPE = "eventType"; 12 | 13 | /** 14 | * Unique location event upload job ID. 15 | */ 16 | public static final String LOCATION_EVENT_JOB_ID = "jobId"; 17 | 18 | /** 19 | * Error types encountered during location event upload, represented by {@link ErrorType}. 20 | */ 21 | public static final String LOCATION_EVENT_ERROR_TYPE = "error"; 22 | 23 | /** 24 | * Free-form text from the errors as additional data. 25 | */ 26 | public static final String LOCATION_EVENT_ERROR_MESSAGE = "errorMessage"; 27 | 28 | /** 29 | * Timestamp representing the lapsed time between a location event is reported and it is to be uploaded. 30 | */ 31 | public static final String LOCATION_EVENT_DELAY_TO_UPLOAD = "delayToUpload"; 32 | 33 | /** 34 | * Timestamp representing the time it takes to upload a location event. 35 | */ 36 | public static final String LOCATION_EVENT_DELAY_TO_COMPLETE = "delayToComplete"; 37 | 38 | /** 39 | * Location change events' source types, represented by {@link LocationDataSource}. 40 | */ 41 | public static final String LOCATION_EVENT_SOURCE = "source"; 42 | 43 | public enum ErrorType { 44 | Network, 45 | Sdk 46 | } 47 | 48 | public enum LocationDataSource { 49 | LocationReport, Awareness 50 | } 51 | 52 | private LocationEventAttributes() { 53 | throw new AssertionError(); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /connect-location/src/main/java/com/ifttt/location/LocationEventListener.java: -------------------------------------------------------------------------------- 1 | package com.ifttt.location; 2 | 3 | import java.util.Map; 4 | 5 | /** 6 | * Listener interface that reports background location events and their attributes 7 | * whenever a location change happens on any of the registered geofences. 8 | */ 9 | public interface LocationEventListener { 10 | 11 | void onLocationEventReported(LocationEventType type, Map data); 12 | } 13 | -------------------------------------------------------------------------------- /connect-location/src/main/java/com/ifttt/location/LocationEventType.java: -------------------------------------------------------------------------------- 1 | package com.ifttt.location; 2 | 3 | /** 4 | * Representations of the background location events that {@link LocationEventListener} listens to. 5 | */ 6 | public enum LocationEventType { 7 | /** 8 | * A location change event (entering or exiting a geofence) is reported to the SDK. 9 | */ 10 | EventReported, 11 | 12 | /** 13 | * A location change event is scheduled to be uploaded to IFTTT. 14 | */ 15 | EventUploadAttempted, 16 | 17 | /** 18 | * A location change event is uploaded successfully to IFTTT. 19 | */ 20 | EventUploadSuccessful, 21 | 22 | /** 23 | * A location change event could not be uploaded to IFTTT. 24 | */ 25 | EventUploadFailed 26 | } 27 | -------------------------------------------------------------------------------- /connect-location/src/main/java/com/ifttt/location/LocationInfo.java: -------------------------------------------------------------------------------- 1 | package com.ifttt.location; 2 | 3 | import com.squareup.moshi.Json; 4 | import java.text.DateFormat; 5 | import java.text.SimpleDateFormat; 6 | import java.util.Locale; 7 | import java.util.UUID; 8 | 9 | final class LocationInfo { 10 | 11 | private static final DateFormat LOCATION_EVENT_DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssXXX", 12 | Locale.US 13 | ); 14 | 15 | @Json(name = "channel_id") final String channelId = "941030000"; 16 | @Json(name = "trigger_subscription_id") final String triggerSubscriptionId; 17 | @Json(name = "record_id") final String recordId = UUID.randomUUID().toString(); 18 | @Json(name = "occurred_at") final String occurredAt; 19 | @Json(name = "event_type") final String eventType; 20 | @Json(name = "region_type") final String regionType = "geo"; 21 | @Json(name = "installation_id") final String installationId; 22 | 23 | private LocationInfo( 24 | String triggerSubscriptionId, String occurredAt, String eventType, String installationId 25 | ) { 26 | this.triggerSubscriptionId = triggerSubscriptionId; 27 | this.occurredAt = occurredAt; 28 | this.eventType = eventType; 29 | this.installationId = installationId; 30 | } 31 | 32 | static LocationInfo entry(String triggerSubscriptionId, String installationId) { 33 | return new LocationInfo(triggerSubscriptionId, 34 | LOCATION_EVENT_DATE_FORMAT.format(System.currentTimeMillis()), 35 | "entry", 36 | installationId 37 | ); 38 | } 39 | 40 | static LocationInfo exit(String triggerSubscriptionId, String installationId) { 41 | return new LocationInfo(triggerSubscriptionId, 42 | LOCATION_EVENT_DATE_FORMAT.format(System.currentTimeMillis()), 43 | "exit", 44 | installationId 45 | ); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /connect-location/src/main/java/com/ifttt/location/Logger.java: -------------------------------------------------------------------------------- 1 | package com.ifttt.location; 2 | 3 | import android.util.Log; 4 | 5 | final class Logger { 6 | 7 | private static final String TAG = "ConnectLocation"; 8 | private static Boolean canLogEvent = false; 9 | 10 | static void setLoggingEnabled(Boolean enabled) { 11 | canLogEvent = enabled; 12 | } 13 | 14 | static void log(String message) { 15 | if (canLogEvent) { 16 | Log.d(TAG, message); 17 | } 18 | } 19 | static void warning(String message) { 20 | if (canLogEvent) { 21 | Log.w(TAG, message); 22 | } 23 | } 24 | static void error(String message) { 25 | if (canLogEvent) { 26 | Log.e(TAG, message); 27 | } 28 | } 29 | 30 | private Logger() { 31 | throw new AssertionError(); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /connect-location/src/main/java/com/ifttt/location/OnEventUploadListener.java: -------------------------------------------------------------------------------- 1 | package com.ifttt.location; 2 | 3 | import android.content.Context; 4 | 5 | /** 6 | * A listener interface for geo-fence uploads from {@link ConnectLocation#reportEvent(Context, double, double, OnEventUploadListener)} 7 | * that status of any given method call. You can use this interface to listen to whether a reported event was uploaded 8 | * or skipped. 9 | */ 10 | public interface OnEventUploadListener { 11 | 12 | void onUploadEvent(String fenceKey, LocationEventUploader.EventType eventType); 13 | 14 | void onUploadSkipped(String fenceKey, String reason); 15 | } 16 | -------------------------------------------------------------------------------- /connect-location/src/main/java/com/ifttt/location/RebootBroadcastReceiver.java: -------------------------------------------------------------------------------- 1 | package com.ifttt.location; 2 | 3 | import android.content.BroadcastReceiver; 4 | import android.content.Context; 5 | import android.content.Intent; 6 | 7 | /** 8 | * A {@link BroadcastReceiver} that listens to system's {@link Intent#ACTION_BOOT_COMPLETED} broadcast, and make an 9 | * attempt to re-register geo-fences. 10 | */ 11 | public final class RebootBroadcastReceiver extends BroadcastReceiver { 12 | 13 | @Override 14 | public void onReceive(Context context, Intent intent) { 15 | if (!intent.getAction().equals(Intent.ACTION_BOOT_COMPLETED)) { 16 | return; 17 | } 18 | 19 | ConnectionRefresher.executeIfExists(context); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /connect-location/src/main/java/com/ifttt/location/RetrofitLocationApi.java: -------------------------------------------------------------------------------- 1 | package com.ifttt.location; 2 | 3 | import java.util.List; 4 | import okhttp3.Interceptor; 5 | import okhttp3.OkHttpClient; 6 | import retrofit2.Call; 7 | import retrofit2.Retrofit; 8 | import retrofit2.converter.moshi.MoshiConverterFactory; 9 | import retrofit2.http.Body; 10 | import retrofit2.http.POST; 11 | 12 | interface RetrofitLocationApi { 13 | @POST("/v1/location_events") 14 | Call upload(@Body List locationInfoList); 15 | 16 | final class Client { 17 | 18 | final RetrofitLocationApi api; 19 | 20 | Client(Interceptor tokenInterceptor) { 21 | OkHttpClient client = new OkHttpClient.Builder().addInterceptor(tokenInterceptor).build(); 22 | Retrofit retrofit = new Retrofit.Builder().client(client) 23 | .baseUrl("https://connectapi.ifttt.com") 24 | .addConverterFactory(MoshiConverterFactory.create()) 25 | .build(); 26 | api = retrofit.create(RetrofitLocationApi.class); 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /connect-location/src/main/java/com/ifttt/location/SharedPreferenceUserTokenCache.java: -------------------------------------------------------------------------------- 1 | package com.ifttt.location; 2 | 3 | import android.content.Context; 4 | import android.content.SharedPreferences; 5 | import androidx.annotation.Nullable; 6 | 7 | final class SharedPreferenceUserTokenCache implements Cache { 8 | 9 | private static final String SHARED_PREF_NAME = "ifttt_user_token_store"; 10 | private static final String PREF_KEY_USER_TOKEN = "ifttt_key_user_token"; 11 | 12 | private final SharedPreferences sharedPreferences; 13 | 14 | SharedPreferenceUserTokenCache(Context context) { 15 | sharedPreferences = context.getSharedPreferences(SHARED_PREF_NAME, Context.MODE_PRIVATE); 16 | } 17 | 18 | @Override 19 | public void write(String userToken) { 20 | sharedPreferences.edit().putString(PREF_KEY_USER_TOKEN, userToken).apply(); 21 | } 22 | 23 | @Override 24 | @Nullable 25 | public String read() { 26 | return sharedPreferences.getString(PREF_KEY_USER_TOKEN, null); 27 | } 28 | 29 | @Override 30 | public void clear() { 31 | sharedPreferences.edit().remove(PREF_KEY_USER_TOKEN).apply(); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /connect-location/src/main/java/com/ifttt/location/SharedPreferencesGeofenceCache.java: -------------------------------------------------------------------------------- 1 | package com.ifttt.location; 2 | 3 | import android.content.Context; 4 | import android.content.SharedPreferences; 5 | import com.ifttt.location.BackupGeofenceMonitor.MonitoredGeofence; 6 | import com.squareup.moshi.JsonAdapter; 7 | import com.squareup.moshi.Moshi; 8 | import com.squareup.moshi.Types; 9 | import java.io.IOException; 10 | import java.util.Collections; 11 | import java.util.Map; 12 | 13 | final class SharedPreferencesGeofenceCache implements Cache> { 14 | 15 | private static final String PREFS_GEOFENCE_MONITOR = "ifttt_geofence_monitor"; 16 | private static final String PREF_KEY_MONITORED_GEOFENCES = "monitored_geofences"; 17 | private static final Moshi MOSHI_INSTANCE = new Moshi.Builder().build(); 18 | 19 | private final SharedPreferences sharedPreferences; 20 | private final JsonAdapter> jsonAdapter; 21 | 22 | SharedPreferencesGeofenceCache(Context context) { 23 | jsonAdapter = MOSHI_INSTANCE.adapter(Types.newParameterizedType( 24 | Map.class, 25 | String.class, 26 | MonitoredGeofence.class 27 | )); 28 | sharedPreferences = context.getSharedPreferences(PREFS_GEOFENCE_MONITOR, Context.MODE_PRIVATE); 29 | } 30 | 31 | @Override 32 | public void write(Map data) { 33 | String geofencesString = jsonAdapter.toJson(data); 34 | sharedPreferences.edit().putString(PREF_KEY_MONITORED_GEOFENCES, geofencesString).apply(); 35 | } 36 | 37 | @Override 38 | public Map read() { 39 | String monitoredGeofencesString = sharedPreferences.getString(PREF_KEY_MONITORED_GEOFENCES, null); 40 | if (monitoredGeofencesString == null) { 41 | return Collections.emptyMap(); 42 | } 43 | 44 | try { 45 | return jsonAdapter.fromJson(monitoredGeofencesString); 46 | } catch (IOException e) { 47 | return Collections.emptyMap(); 48 | } 49 | } 50 | 51 | @Override 52 | public void clear() { 53 | sharedPreferences.edit().remove(PREF_KEY_MONITORED_GEOFENCES).apply(); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /connect-location/src/main/java/com/ifttt/location/package-info.java: -------------------------------------------------------------------------------- 1 | @ParametersAreNonnullByDefault 2 | package com.ifttt.location; 3 | 4 | import javax.annotation.ParametersAreNonnullByDefault; 5 | -------------------------------------------------------------------------------- /connect-location/src/test/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /connect-location/src/test/java/com/ifttt/location/CacheUserTokenProviderTest.java: -------------------------------------------------------------------------------- 1 | package com.ifttt.location; 2 | 3 | import static com.google.common.truth.Truth.assertThat; 4 | 5 | import android.content.Context; 6 | 7 | import androidx.test.core.app.ApplicationProvider; 8 | import androidx.test.ext.junit.runners.AndroidJUnit4; 9 | 10 | import org.junit.Before; 11 | import org.junit.Test; 12 | import org.junit.runner.RunWith; 13 | 14 | @RunWith(AndroidJUnit4.class) 15 | public final class CacheUserTokenProviderTest { 16 | 17 | private Context context; 18 | 19 | @Before 20 | public void setUp() { 21 | context = ApplicationProvider.getApplicationContext(); 22 | } 23 | 24 | @Test 25 | public void shouldPreferDelegate() { 26 | Cache cache = new SharedPreferenceUserTokenCache(context); 27 | cache.write("cached"); 28 | CacheUserTokenProvider provider = new CacheUserTokenProvider(cache, () -> "delegate"); 29 | 30 | assertThat(provider.getUserToken()).isEqualTo("delegate"); 31 | } 32 | 33 | @Test 34 | public void shouldReturnCachedForNullDelegate() { 35 | Cache cache = new SharedPreferenceUserTokenCache(context); 36 | cache.write("cached"); 37 | CacheUserTokenProvider provider = new CacheUserTokenProvider(cache, null); 38 | 39 | assertThat(provider.getUserToken()).isEqualTo("cached"); 40 | } 41 | 42 | @Test 43 | public void shouldCacheNonNullToken() { 44 | Cache cache = new SharedPreferenceUserTokenCache(context); 45 | CacheUserTokenProvider provider = new CacheUserTokenProvider(cache, () -> "delegate"); 46 | provider.getUserToken(); 47 | 48 | assertThat(cache.read()).isEqualTo("delegate"); 49 | } 50 | 51 | @Test 52 | public void shouldNotCacheNullToken() { 53 | Cache cache = new SharedPreferenceUserTokenCache(context); 54 | CacheUserTokenProvider provider = new CacheUserTokenProvider(cache, () -> null); 55 | provider.getUserToken(); 56 | 57 | assertThat(cache.read()).isNull(); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /connect-location/src/test/java/com/ifttt/location/TestActivity.java: -------------------------------------------------------------------------------- 1 | package com.ifttt.location; 2 | 3 | import android.app.Activity; 4 | import android.os.Bundle; 5 | import androidx.annotation.Nullable; 6 | import androidx.work.Configuration; 7 | import androidx.work.testing.SynchronousExecutor; 8 | import androidx.work.testing.WorkManagerTestInitHelper; 9 | 10 | public final class TestActivity extends Activity { 11 | 12 | @Override 13 | protected void onCreate(@Nullable Bundle savedInstanceState) { 14 | super.onCreate(savedInstanceState); 15 | 16 | final Configuration config = new Configuration.Builder() 17 | .setExecutor(new SynchronousExecutor()) 18 | .build(); 19 | WorkManagerTestInitHelper.initializeTestWorkManager( 20 | this, config); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | 3 | # IDE (e.g. Android Studio) users: 4 | # Gradle settings configured through the IDE *will override* 5 | # any settings specified in this file. 6 | 7 | # For more details on how to configure your build environment visit 8 | # http://www.gradle.org/docs/current/userguide/build_environment.html 9 | 10 | # Specifies the JVM arguments used for the daemon process. 11 | # The setting is particularly useful for tweaking memory settings. 12 | org.gradle.jvmargs=-Xmx1536m 13 | 14 | # When configured, Gradle will run in incubating parallel mode. 15 | # This option should only be used with decoupled projects. More details, visit 16 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 17 | # org.gradle.parallel=true 18 | 19 | android.useAndroidX=true 20 | android.enableJetifier=true 21 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IFTTT/ConnectSDK-Android/30286987c6688f174d9b7eceaa23cb6bd273e869/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Sun Nov 01 19:25:57 PST 2020 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-7.5-bin.zip 7 | -------------------------------------------------------------------------------- /publish-root.gradle: -------------------------------------------------------------------------------- 1 | nexusPublishing { 2 | Properties properties = new Properties() 3 | if (project.rootProject.file('local.properties').canRead()) { 4 | properties.load(project.rootProject.file('local.properties').newDataInputStream()) 5 | } 6 | 7 | repositories { 8 | sonatype { 9 | stagingProfileId = properties.getProperty('mavenCentral.sonatypeStagingProfileId') 10 | username = properties.getProperty('mavenCentral.ossrhUsername') 11 | password = properties.getProperty('mavenCentral.ossrhPassword') 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | include ':connect-api', ':connect-button', ':connect-location' 3 | --------------------------------------------------------------------------------