├── .gitignore ├── Fontify ├── .gitignore ├── AndroidManifest.xml ├── pom.xml ├── project.properties ├── readme.md ├── res │ └── values │ │ └── attrs.xml └── src │ └── com │ └── danh32 │ └── fontify │ ├── AutoCompleteTextView.java │ ├── Button.java │ ├── CheckBox.java │ ├── CheckedTextView.java │ ├── Chronometer.java │ ├── CompoundButton.java │ ├── DigitalClock.java │ ├── EditText.java │ ├── ExtractEditText.java │ ├── FontManager.java │ ├── MultiAutoCompleteTextView.java │ ├── RadioButton.java │ ├── Switch.java │ ├── TextClock.java │ ├── TextView.java │ └── ToggleButton.java ├── LICENSE ├── README.md └── SparkCore ├── .classpath ├── .project ├── AndroidManifest.xml ├── assets └── fonts │ └── .gitkeep ├── ic_launcher-web.png ├── libs ├── android-support-v4.jar ├── commons-lang3-3.1.jar ├── gson-2.2.4.jar ├── guava-15.0.jar ├── licenses │ ├── android-support-v4-LICENSE.txt │ ├── commons-lang3-LICENSE.txt │ ├── gson-LICENSE.txt │ ├── guava-LICENSE.txt │ └── okhttp-LICENSE.txt └── okhttp-1.2.1-jar-with-dependencies.jar ├── proguard-project.txt ├── project.properties ├── res ├── animator │ ├── pin_background_end.xml │ ├── pin_background_go_dark.xml │ └── pin_background_start.xml ├── drawable-hdpi │ ├── ic_action_edit.png │ ├── ic_launcher.png │ ├── ic_wifi.png │ └── new_core.png ├── drawable-mdpi │ ├── ic_action_edit.png │ ├── ic_launcher.png │ ├── ic_menu_moreoverflow_normal_holo_dark.png │ ├── ic_wifi.png │ ├── log_in_background_bitmap.png │ ├── new_core.png │ ├── sign_up_background_bitmap.png │ └── smart_config_background_bitmap.png ├── drawable-xhdpi │ ├── ic_action_edit.png │ ├── ic_launcher.png │ ├── ic_wifi.png │ ├── log_in_background_bitmap.png │ ├── new_core.png │ ├── sign_up_background_bitmap.png │ └── smart_config_background_bitmap.png ├── drawable-xxhdpi │ └── ic_launcher.png ├── drawable-xxxhdpi │ └── ic_launcher.png ├── drawable │ ├── action_bar_dark_bg.xml │ ├── action_bar_layers.xml │ ├── action_bar_transparent_gradient.xml │ ├── aes_checkbox_checked_temp.png │ ├── aes_checkbox_unchecked_temp.png │ ├── blue_button_gradient.xml │ ├── blue_button_pressed_gradient.xml │ ├── blue_button_selector.xml │ ├── core_list_dot.xml │ ├── link_text_selector.xml │ ├── log_in_background.xml │ ├── log_in_button_gradient.xml │ ├── log_in_button_pressed_gradient.xml │ ├── log_in_button_selector.xml │ ├── point_at_d7_temp.png │ ├── progress_emerald.xml │ ├── progress_indicator_temp.png │ ├── progress_spinner.xml │ ├── progress_sunflower.xml │ ├── red_button_gradient.xml │ ├── red_button_pressed_gradient.xml │ ├── red_button_selector.xml │ ├── sign_up_background.xml │ ├── sign_up_button_gradient.xml │ ├── sign_up_button_pressed_gradient.xml │ ├── sign_up_button_selector.xml │ ├── smart_config_background.xml │ ├── spark_logo_temp.png │ ├── tinker_core_shadow_temp.png │ ├── tinker_logo_temp.png │ ├── tinker_pin.xml │ ├── tinker_pin_alizarin.xml │ ├── tinker_pin_cyan.xml │ ├── tinker_pin_emerald.xml │ ├── tinker_pin_muted.xml │ ├── tinker_pin_read_high.xml │ ├── tinker_pin_sunflower.xml │ ├── tinker_pin_write_high.xml │ └── white_circle.xml ├── layout │ ├── activity_core_list.xml │ ├── activity_log_in.xml │ ├── activity_sign_up.xml │ ├── activity_smart_config.xml │ ├── core_row.xml │ ├── dialog_rename.xml │ ├── fragment_core_list.xml │ ├── fragment_naming.xml │ ├── fragment_no_cores_found.xml │ ├── fragment_smart_config.xml │ ├── fragment_tinker.xml │ ├── tinker_analog_read_left.xml │ ├── tinker_analog_read_right.xml │ ├── tinker_analog_write_left.xml │ ├── tinker_analog_write_right.xml │ ├── tinker_digital_read.xml │ ├── tinker_digital_write.xml │ ├── tinker_instructions.xml │ └── tinker_select.xml ├── menu │ ├── account.xml │ ├── all_screens.xml │ ├── core_list.xml │ ├── core_row_overflow.xml │ └── tinker.xml ├── values-h600dp │ └── dimens.xml ├── values-large │ ├── layout_config.xml │ └── styles.xml ├── values-sw720dp-land │ └── dimens.xml └── values │ ├── appconfig.xml │ ├── colors.xml │ ├── dimens.xml │ ├── font_names.xml │ ├── ids.xml │ ├── layout_config.xml │ ├── strings.xml │ ├── strings_account_screens.xml │ ├── styles.xml │ ├── themes.xml │ └── uris.xml └── src ├── io └── spark │ └── core │ └── android │ ├── app │ ├── AppConfig.java │ ├── DeviceState.java │ └── SparkCoreApp.java │ ├── cloud │ ├── ApiFacade.java │ ├── ApiUrlHelper.java │ ├── WebHelpers.java │ ├── api │ │ ├── Device.java │ │ ├── ListDevicesResponse.java │ │ ├── SimpleResponse.java │ │ └── TinkerResponse.java │ ├── login │ │ ├── TokenRequest.java │ │ ├── TokenResponse.java │ │ └── TokenTool.java │ └── requestservice │ │ ├── ClearableIntentService.java │ │ └── SimpleSparkApiService.java │ ├── smartconfig │ ├── SmartConfigService.java │ └── SmartConfigState.java │ ├── storage │ ├── Prefs.java │ └── TinkerPrefs.java │ ├── ui │ ├── BaseActivity.java │ ├── BaseFragment.java │ ├── ErrorsDelegate.java │ ├── LoginActivity.java │ ├── SignUpActivity.java │ ├── assets │ │ └── Typefaces.java │ ├── corelist │ │ ├── CoreListActivity.java │ │ ├── CoreListFragment.java │ │ └── DeviceListAdapter.java │ ├── smartconfig │ │ ├── NamingActivity.java │ │ ├── NamingFragment.java │ │ ├── NoCoresFoundActivity.java │ │ ├── NoCoresFoundFragment.java │ │ ├── SmartConfigActivity.java │ │ └── SmartConfigFragment.java │ ├── tinker │ │ ├── BgColorLinearLayout.java │ │ ├── DigitalValue.java │ │ ├── Pin.java │ │ ├── PinAction.java │ │ ├── PinType.java │ │ ├── ReversedProgressBar.java │ │ ├── ReversedSeekBar.java │ │ └── TinkerFragment.java │ └── util │ │ ├── NamingHelper.java │ │ └── Ui.java │ └── util │ ├── CoreNameGenerator.java │ ├── NetConnectionHelper.java │ └── Strings.java └── org └── solemnsilence └── util ├── EZ.java ├── Py.java ├── TLog.java └── Toaster.java /.gitignore: -------------------------------------------------------------------------------- 1 | # built application files 2 | *.apk 3 | *.ap_ 4 | 5 | # files for the dex VM 6 | *.dex 7 | 8 | # Java class files 9 | *.class 10 | 11 | # generated files 12 | */bin/ 13 | */gen/ 14 | 15 | # Local configuration file (sdk path, etc) 16 | */local.properties 17 | 18 | 19 | # local build config items 20 | SparkCore/res/values/local_build.xml 21 | 22 | 23 | # Eclipse project files 24 | # COMMENTED OUT: we want these. 25 | #.classpath 26 | #.project 27 | 28 | */.metadata/ 29 | */.settings/ 30 | -------------------------------------------------------------------------------- /Fontify/.gitignore: -------------------------------------------------------------------------------- 1 | # Android Generated Files: 2 | bin 3 | gen 4 | lint.xml 5 | 6 | # Eclipse Files: 7 | .project 8 | .classpath 9 | .settings 10 | .checkstyle 11 | 12 | # IntelliJ IDEA Files: 13 | .idea 14 | *.iml 15 | *.ipr 16 | *.iws 17 | classes 18 | gen-external-apklibs 19 | 20 | # Maven Files: 21 | target 22 | release.properties 23 | pom.xml.* 24 | 25 | # Ant Files: 26 | ant.properties 27 | local.properties 28 | proguard.cfg 29 | proguard-project.txt 30 | 31 | # Other Files: 32 | .DS_Store 33 | tmp 34 | -------------------------------------------------------------------------------- /Fontify/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 5 | 6 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /Fontify/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | com.danh32.fontify 6 | fontify 7 | 1.0-SNAPSHOT 8 | apklib 9 | 10 | Fontify 11 | Android TextView subclasses with easy custom font capabilities. 12 | https://github.com/danh32/Fontify 13 | 14 | 15 | 16 | The Apache Software License, Version 2.0 17 | http://www.apache.org/licenses/LICENSE-2.0.txt 18 | repo 19 | 20 | 21 | 22 | 23 | https://github.com/danh32/Fontify 24 | 25 | 26 | 27 | UTF-8 28 | 4.1.1.4 29 | 30 | 3.5.3 31 | 32 | 33 | 34 | 35 | com.google.android 36 | android 37 | ${platform.version} 38 | provided 39 | 40 | 41 | 42 | 43 | 44 | 45 | com.jayway.maven.plugins.android.generation2 46 | android-maven-plugin 47 | ${android.plugin.version} 48 | true 49 | 50 | 51 | 17 52 | 53 | 54 | 55 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /Fontify/project.properties: -------------------------------------------------------------------------------- 1 | # This file is automatically generated by Android Tools. 2 | # Do not modify this file -- YOUR CHANGES WILL BE ERASED! 3 | # 4 | # This file must be checked in Version Control Systems. 5 | # 6 | # To customize properties used by the Ant build system edit 7 | # "ant.properties", and override values to adapt the script to your 8 | # project structure. 9 | # 10 | # To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home): 11 | #proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt 12 | 13 | # Project target. 14 | target=android-19 15 | android.library=true 16 | -------------------------------------------------------------------------------- /Fontify/readme.md: -------------------------------------------------------------------------------- 1 | #Fontify 2 | 3 | Fontify is an Android library project providing drop-in replacements for all Android TextView subclasses, allowing developers to apply custom fonts via xml layouts and/or styles. 4 | 5 | ##Set Up: 6 | 1) Clone Fontify locally 7 | 2) Import library project into Eclipse 8 | 3) Add Fontify to your app as a library project 9 | 10 | ##Usage: 11 | ###Getting fonts in place: 12 | 1) Put your custom font file in Android's assets folder (assets/fonts/helvetica.ttf) 13 | 2) Put the path to your file in your strings.xml for ease of use: 14 | 15 | fonts/helvetica.ttf 16 | 17 | ###Applying fonts: 18 | ####In styles/themes: 19 | 1) Declare your style in res/values/styles.xml: 20 | 21 | 24 | 2) Use the Fontify subclass of your TextView: 25 | 29 | 30 | ####In layouts: 31 | 1) Add new XML NameSpace to root element of layout: 32 | 33 | xmlns:fontify="http://schemas.android.com/apk/res-auto" 34 | 2) Use the Fontify subclass of your TextView: 35 | 36 | 40 | 41 | ####In Java: 42 | Use the textView.setFont(String fontPath) or textView.setFont(int resId) method: 43 | 44 | import com.danh32.fontify.TextView; 45 | 46 | public class CustomFontActivity extends Activity { 47 | @Override 48 | onCreate (Bundle savedInstanceState) { 49 | super(savedInstanceState); 50 | 51 | TextView tv = new TextView(this); 52 | tv.setFont(R.string.FONT_HELVETICA); 53 | } 54 | } 55 | 56 | ##License 57 | Copyright 2013 Daniel Hill 58 | 59 | Licensed under the Apache License, Version 2.0 (the "License"); 60 | you may not use this file except in compliance with the License. 61 | You may obtain a copy of the License at 62 | 63 | http://www.apache.org/licenses/LICENSE-2.0 64 | 65 | Unless required by applicable law or agreed to in writing, software 66 | distributed under the License is distributed on an "AS IS" BASIS, 67 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 68 | See the License for the specific language governing permissions and 69 | limitations under the License. 70 | -------------------------------------------------------------------------------- /Fontify/res/values/attrs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /Fontify/src/com/danh32/fontify/AutoCompleteTextView.java: -------------------------------------------------------------------------------- 1 | package com.danh32.fontify; 2 | 3 | 4 | import android.content.Context; 5 | import android.util.AttributeSet; 6 | 7 | 8 | public class AutoCompleteTextView extends android.widget.AutoCompleteTextView { 9 | public AutoCompleteTextView(Context context) { 10 | super(context); 11 | } 12 | 13 | public AutoCompleteTextView(Context context, AttributeSet attrs) { 14 | super(context, attrs); 15 | 16 | // return early for eclipse preview mode 17 | if (isInEditMode()) return; 18 | 19 | FontManager.getInstance().setFont(this, attrs); 20 | } 21 | 22 | public void setFont(String fontPath) { 23 | FontManager.getInstance().setFont(this, fontPath); 24 | } 25 | 26 | public void setFont(int resId) { 27 | String fontPath = getContext().getString(resId); 28 | setFont(fontPath); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Fontify/src/com/danh32/fontify/Button.java: -------------------------------------------------------------------------------- 1 | package com.danh32.fontify; 2 | 3 | 4 | import android.content.Context; 5 | import android.graphics.Paint; 6 | import android.util.AttributeSet; 7 | 8 | 9 | public class Button extends android.widget.Button { 10 | 11 | public Button(Context context) { 12 | super(context); 13 | setup(); 14 | } 15 | 16 | public Button(Context context, AttributeSet attrs) { 17 | super(context, attrs); 18 | setup(); 19 | // return early for eclipse preview mode 20 | if (isInEditMode()) 21 | return; 22 | 23 | FontManager.getInstance().setFont(this, attrs); 24 | } 25 | 26 | public void setFont(String fontPath) { 27 | FontManager.getInstance().setFont(this, fontPath); 28 | } 29 | 30 | public void setFont(int resId) { 31 | String fontPath = getContext().getString(resId); 32 | setFont(fontPath); 33 | } 34 | 35 | private void setup() { 36 | setPaintFlags(getPaintFlags() | Paint.SUBPIXEL_TEXT_FLAG | Paint.HINTING_ON); 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /Fontify/src/com/danh32/fontify/CheckBox.java: -------------------------------------------------------------------------------- 1 | package com.danh32.fontify; 2 | 3 | 4 | import android.content.Context; 5 | import android.graphics.Paint; 6 | import android.util.AttributeSet; 7 | 8 | 9 | public class CheckBox extends android.widget.CheckBox { 10 | 11 | public CheckBox(Context context) { 12 | super(context); 13 | setup(); 14 | } 15 | 16 | public CheckBox(Context context, AttributeSet attrs) { 17 | super(context, attrs); 18 | setup(); 19 | // return early for eclipse preview mode 20 | if (isInEditMode()) { 21 | return; 22 | 23 | } 24 | FontManager.getInstance().setFont(this, attrs); 25 | } 26 | 27 | public void setFont(String fontPath) { 28 | FontManager.getInstance().setFont(this, fontPath); 29 | } 30 | 31 | public void setFont(int resId) { 32 | String fontPath = getContext().getString(resId); 33 | setFont(fontPath); 34 | } 35 | 36 | private void setup() { 37 | setPaintFlags(getPaintFlags() | Paint.SUBPIXEL_TEXT_FLAG | Paint.HINTING_ON); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /Fontify/src/com/danh32/fontify/CheckedTextView.java: -------------------------------------------------------------------------------- 1 | package com.danh32.fontify; 2 | 3 | 4 | import android.content.Context; 5 | import android.util.AttributeSet; 6 | 7 | 8 | public class CheckedTextView extends android.widget.CheckedTextView { 9 | public CheckedTextView(Context context) { 10 | super(context); 11 | } 12 | 13 | public CheckedTextView(Context context, AttributeSet attrs) { 14 | super(context, attrs); 15 | 16 | // return early for eclipse preview mode 17 | if (isInEditMode()) return; 18 | 19 | FontManager.getInstance().setFont(this, attrs); 20 | } 21 | 22 | public void setFont(String fontPath) { 23 | FontManager.getInstance().setFont(this, fontPath); 24 | } 25 | 26 | public void setFont(int resId) { 27 | String fontPath = getContext().getString(resId); 28 | setFont(fontPath); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Fontify/src/com/danh32/fontify/Chronometer.java: -------------------------------------------------------------------------------- 1 | package com.danh32.fontify; 2 | 3 | 4 | import android.content.Context; 5 | import android.util.AttributeSet; 6 | 7 | 8 | public class Chronometer extends android.widget.Chronometer { 9 | public Chronometer(Context context) { 10 | super(context); 11 | } 12 | 13 | public Chronometer(Context context, AttributeSet attrs) { 14 | super(context, attrs); 15 | 16 | // return early for eclipse preview mode 17 | if (isInEditMode()) return; 18 | 19 | FontManager.getInstance().setFont(this, attrs); 20 | } 21 | 22 | public void setFont(String fontPath) { 23 | FontManager.getInstance().setFont(this, fontPath); 24 | } 25 | 26 | public void setFont(int resId) { 27 | String fontPath = getContext().getString(resId); 28 | setFont(fontPath); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Fontify/src/com/danh32/fontify/CompoundButton.java: -------------------------------------------------------------------------------- 1 | package com.danh32.fontify; 2 | 3 | 4 | import android.content.Context; 5 | import android.util.AttributeSet; 6 | 7 | 8 | public class CompoundButton extends android.widget.CompoundButton { 9 | public CompoundButton(Context context) { 10 | super(context); 11 | } 12 | 13 | public CompoundButton(Context context, AttributeSet attrs) { 14 | super(context, attrs); 15 | 16 | // return early for eclipse preview mode 17 | if (isInEditMode()) return; 18 | 19 | FontManager.getInstance().setFont(this, attrs); 20 | } 21 | 22 | public void setFont(String fontPath) { 23 | FontManager.getInstance().setFont(this, fontPath); 24 | } 25 | 26 | public void setFont(int resId) { 27 | String fontPath = getContext().getString(resId); 28 | setFont(fontPath); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Fontify/src/com/danh32/fontify/DigitalClock.java: -------------------------------------------------------------------------------- 1 | package com.danh32.fontify; 2 | 3 | 4 | import android.content.Context; 5 | import android.util.AttributeSet; 6 | 7 | 8 | @Deprecated 9 | public class DigitalClock extends android.widget.DigitalClock { 10 | public DigitalClock(Context context) { 11 | super(context); 12 | } 13 | 14 | public DigitalClock(Context context, AttributeSet attrs) { 15 | super(context, attrs); 16 | 17 | // return early for eclipse preview mode 18 | if (isInEditMode()) return; 19 | 20 | FontManager.getInstance().setFont(this, attrs); 21 | } 22 | 23 | public void setFont(String fontPath) { 24 | FontManager.getInstance().setFont(this, fontPath); 25 | } 26 | 27 | public void setFont(int resId) { 28 | String fontPath = getContext().getString(resId); 29 | setFont(fontPath); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Fontify/src/com/danh32/fontify/EditText.java: -------------------------------------------------------------------------------- 1 | package com.danh32.fontify; 2 | 3 | 4 | import android.content.Context; 5 | import android.util.AttributeSet; 6 | 7 | 8 | public class EditText extends android.widget.EditText { 9 | public EditText(Context context) { 10 | super(context); 11 | } 12 | 13 | public EditText(Context context, AttributeSet attrs) { 14 | super(context, attrs); 15 | 16 | // return early for eclipse preview mode 17 | if (isInEditMode()) return; 18 | 19 | FontManager.getInstance().setFont(this, attrs); 20 | } 21 | 22 | public void setFont(String fontPath) { 23 | FontManager.getInstance().setFont(this, fontPath); 24 | } 25 | 26 | public void setFont(int resId) { 27 | String fontPath = getContext().getString(resId); 28 | setFont(fontPath); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Fontify/src/com/danh32/fontify/ExtractEditText.java: -------------------------------------------------------------------------------- 1 | package com.danh32.fontify; 2 | 3 | 4 | import android.content.Context; 5 | import android.util.AttributeSet; 6 | 7 | 8 | public class ExtractEditText extends android.inputmethodservice.ExtractEditText { 9 | public ExtractEditText(Context context) { 10 | super(context); 11 | } 12 | 13 | public ExtractEditText(Context context, AttributeSet attrs) { 14 | super(context, attrs); 15 | 16 | // return early for eclipse preview mode 17 | if (isInEditMode()) return; 18 | 19 | FontManager.getInstance().setFont(this, attrs); 20 | } 21 | 22 | public void setFont(String fontPath) { 23 | FontManager.getInstance().setFont(this, fontPath); 24 | } 25 | 26 | public void setFont(int resId) { 27 | String fontPath = getContext().getString(resId); 28 | setFont(fontPath); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Fontify/src/com/danh32/fontify/FontManager.java: -------------------------------------------------------------------------------- 1 | package com.danh32.fontify; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | 6 | import android.content.Context; 7 | import android.content.res.TypedArray; 8 | import android.graphics.Typeface; 9 | import android.util.AttributeSet; 10 | import android.widget.TextView; 11 | 12 | 13 | public class FontManager { 14 | 15 | private static FontManager sInstance; 16 | private Map mCache; 17 | 18 | private FontManager() { 19 | mCache = new HashMap(); 20 | } 21 | 22 | public static FontManager getInstance() { 23 | if (sInstance == null) { 24 | sInstance = new FontManager(); 25 | } 26 | 27 | return sInstance; 28 | } 29 | 30 | public void setFont(TextView tv, AttributeSet attrs) { 31 | String fontName = getFontName(tv.getContext(), attrs); 32 | setFont(tv, fontName); 33 | } 34 | 35 | public void setFont(TextView tv, String fontName) { 36 | if (fontName == null) 37 | return; 38 | 39 | Typeface typeface = mCache.get(fontName); 40 | if (typeface == null) { 41 | typeface = Typeface.createFromAsset(tv.getContext().getAssets(), fontName); 42 | mCache.put(fontName, typeface); 43 | } 44 | 45 | tv.setTypeface(typeface); 46 | } 47 | 48 | public static String getFontName(Context c, AttributeSet attrs) { 49 | TypedArray arr = c.obtainStyledAttributes(attrs, R.styleable.Fontify); 50 | String fontName = arr.getString(R.styleable.Fontify_font); 51 | arr.recycle(); 52 | return fontName; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /Fontify/src/com/danh32/fontify/MultiAutoCompleteTextView.java: -------------------------------------------------------------------------------- 1 | package com.danh32.fontify; 2 | 3 | 4 | import android.content.Context; 5 | import android.util.AttributeSet; 6 | 7 | 8 | public class MultiAutoCompleteTextView extends android.widget.MultiAutoCompleteTextView { 9 | public MultiAutoCompleteTextView(Context context) { 10 | super(context); 11 | } 12 | 13 | public MultiAutoCompleteTextView(Context context, AttributeSet attrs) { 14 | super(context, attrs); 15 | 16 | // return early for eclipse preview mode 17 | if (isInEditMode()) return; 18 | 19 | FontManager.getInstance().setFont(this, attrs); 20 | } 21 | 22 | public void setFont(String fontPath) { 23 | FontManager.getInstance().setFont(this, fontPath); 24 | } 25 | 26 | public void setFont(int resId) { 27 | String fontPath = getContext().getString(resId); 28 | setFont(fontPath); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Fontify/src/com/danh32/fontify/RadioButton.java: -------------------------------------------------------------------------------- 1 | package com.danh32.fontify; 2 | 3 | 4 | import android.content.Context; 5 | import android.util.AttributeSet; 6 | 7 | 8 | public class RadioButton extends android.widget.RadioButton { 9 | public RadioButton(Context context) { 10 | super(context); 11 | } 12 | 13 | public RadioButton(Context context, AttributeSet attrs) { 14 | super(context, attrs); 15 | 16 | // return early for eclipse preview mode 17 | if (isInEditMode()) return; 18 | 19 | FontManager.getInstance().setFont(this, attrs); 20 | } 21 | 22 | public void setFont(String fontPath) { 23 | FontManager.getInstance().setFont(this, fontPath); 24 | } 25 | 26 | public void setFont(int resId) { 27 | String fontPath = getContext().getString(resId); 28 | setFont(fontPath); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Fontify/src/com/danh32/fontify/Switch.java: -------------------------------------------------------------------------------- 1 | package com.danh32.fontify; 2 | 3 | 4 | import android.annotation.TargetApi; 5 | import android.content.Context; 6 | import android.os.Build; 7 | import android.util.AttributeSet; 8 | 9 | 10 | @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH) 11 | public class Switch extends android.widget.Switch { 12 | public Switch(Context context) { 13 | super(context); 14 | } 15 | 16 | public Switch(Context context, AttributeSet attrs) { 17 | super(context, attrs); 18 | 19 | // return early for eclipse preview mode 20 | if (isInEditMode()) return; 21 | 22 | FontManager.getInstance().setFont(this, attrs); 23 | } 24 | 25 | public void setFont(String fontPath) { 26 | FontManager.getInstance().setFont(this, fontPath); 27 | } 28 | 29 | public void setFont(int resId) { 30 | String fontPath = getContext().getString(resId); 31 | setFont(fontPath); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Fontify/src/com/danh32/fontify/TextClock.java: -------------------------------------------------------------------------------- 1 | package com.danh32.fontify; 2 | 3 | 4 | import android.annotation.TargetApi; 5 | import android.content.Context; 6 | import android.os.Build; 7 | import android.util.AttributeSet; 8 | 9 | @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1) 10 | public class TextClock extends android.widget.TextClock { 11 | public TextClock(Context context) { 12 | super(context); 13 | } 14 | 15 | public TextClock(Context context, AttributeSet attrs) { 16 | super(context, attrs); 17 | 18 | // return early for eclipse preview mode 19 | if (isInEditMode()) return; 20 | 21 | FontManager.getInstance().setFont(this, attrs); 22 | } 23 | 24 | public void setFont(String fontPath) { 25 | FontManager.getInstance().setFont(this, fontPath); 26 | } 27 | 28 | public void setFont(int resId) { 29 | String fontPath = getContext().getString(resId); 30 | setFont(fontPath); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Fontify/src/com/danh32/fontify/TextView.java: -------------------------------------------------------------------------------- 1 | package com.danh32.fontify; 2 | 3 | 4 | import android.content.Context; 5 | import android.graphics.Paint; 6 | import android.util.AttributeSet; 7 | 8 | 9 | public class TextView extends android.widget.TextView { 10 | 11 | public TextView(Context context) { 12 | super(context); 13 | setup(); 14 | } 15 | 16 | public TextView(Context context, AttributeSet attrs) { 17 | super(context, attrs); 18 | setup(); 19 | // return early for eclipse preview mode 20 | if (isInEditMode()) 21 | return; 22 | 23 | FontManager.getInstance().setFont(this, attrs); 24 | } 25 | 26 | public void setFont(String fontPath) { 27 | FontManager.getInstance().setFont(this, fontPath); 28 | } 29 | 30 | public void setFont(int resId) { 31 | String fontPath = getContext().getString(resId); 32 | setFont(fontPath); 33 | } 34 | 35 | private void setup() { 36 | setPaintFlags(getPaintFlags() | Paint.SUBPIXEL_TEXT_FLAG | Paint.HINTING_ON); 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /Fontify/src/com/danh32/fontify/ToggleButton.java: -------------------------------------------------------------------------------- 1 | package com.danh32.fontify; 2 | 3 | 4 | import android.content.Context; 5 | import android.util.AttributeSet; 6 | 7 | 8 | public class ToggleButton extends android.widget.ToggleButton { 9 | public ToggleButton(Context context) { 10 | super(context); 11 | } 12 | 13 | public ToggleButton(Context context, AttributeSet attrs) { 14 | super(context, attrs); 15 | 16 | // return early for eclipse preview mode 17 | if (isInEditMode()) return; 18 | 19 | FontManager.getInstance().setFont(this, attrs); 20 | } 21 | 22 | public void setFont(String fontPath) { 23 | FontManager.getInstance().setFont(this, fontPath); 24 | } 25 | 26 | public void setFont(int resId) { 27 | String fontPath = getContext().getString(resId); 28 | setFont(fontPath); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2013 Spark Labs, Inc. 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Spark Android App 2 | 3 | This is the source repo for the official Spark app for Android, providing the Smart Config and Tinker features, and offering a starting point for your own Android apps to work with your Cores. 4 | 5 | 6 | ## Building 7 | 1. In Eclipse, go to File --> Import, and under the Android "folder", click "Existing Android Code into workspace", then click Next 8 | 2. Click Browse, select the dir where you cloned the repo, and click OK 9 | 3. You should now see two projects under the "Projects to Import" header: "SparkCore" and "Fontify". Click on Finish. 10 | 4. In the SparkCore app, create the file ```res/values/local_build.xml``` with the following contents: 11 | 12 | ``` 13 | 14 | 15 | spark:spark 16 | 17 | ``` 18 | _(You could actually put any valid HTTP Basic Auth string where it says ```spark:spark```; these values aren't currently used, but they must be present.)_ 19 | 20 | After this, you might also need to do a refresh and/or clean on the SparkCore project, because Eclipse. ;-) 21 | 22 | 23 | ## Required Fonts 24 | 25 | The Spark app distributed via Google Play uses several typefaces in the Gotham family. 26 | If you have a license to these, you can place the following 4 files in `SparkCore/assets/fonts`. 27 | 28 | * gotham_bold.otf 29 | * gotham_book.otf 30 | * gotham_light.otf 31 | * gotham_medium.otf 32 | 33 | Otherwise, in order to build a working app, you will need to either modify the app not to look for the fonts or put some other fonts in their place. 34 | 35 | 36 | ## Required TI SmartConfig Library 37 | 38 | You must add smartconfiglib.jar to the `SparkCore/libs` directory. 39 | 40 | To get the SmartConfig library, go to the 41 | [CC3000 Wi-Fi Downloads](http://processors.wiki.ti.com/index.php/CC3000_Wi-Fi_Downloads) 42 | page. Search the page for the Android SmartConfig Application. 43 | Download and unpack the app, which will require Windows. :-/ 44 | You can find smartconfiglib.jar in the libs directory of TI's app. 45 | 46 | ## Key Classes 47 | 48 | If you want to know where the action is in the app, look at: 49 | * SimpleSparkApiService: an IntentService which performs the actual HTTP calls to talk to the Spark Cloud 50 | * ApiFacade: A simple interface for making requests and handling responses from the Spark API. If you want to work with the Spark API from Android, this is the place to start. See examples below like nameCore(), digitalWrite(), etc, for templates to work from. 51 | * SparkCoreApp: There are a number of classes which rely on an initialization step during app startup. All of this happens in SparkCoreApp.onCreate(). 52 | 53 | 54 | ## Open Source Licenses 55 | 56 | Original code in this repository is licensed by Spark Labs, Inc. under the Apache License, Version 2.0. 57 | See LICENSE for more information. 58 | 59 | This app uses several Open Source libraries. See SparkCore/libs/licenses for more information. 60 | -------------------------------------------------------------------------------- /SparkCore/.classpath: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /SparkCore/.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | SparkCore 4 | 5 | 6 | 7 | 8 | 9 | com.android.ide.eclipse.adt.ResourceManagerBuilder 10 | 11 | 12 | 13 | 14 | com.android.ide.eclipse.adt.PreCompilerBuilder 15 | 16 | 17 | 18 | 19 | org.eclipse.jdt.core.javabuilder 20 | 21 | 22 | 23 | 24 | com.android.ide.eclipse.adt.ApkBuilder 25 | 26 | 27 | 28 | 29 | 30 | com.android.ide.eclipse.adt.AndroidNature 31 | org.eclipse.jdt.core.javanature 32 | 33 | 34 | -------------------------------------------------------------------------------- /SparkCore/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 41 | 46 | 49 | 50 | 55 | 58 | 59 | 64 | 67 | 68 | 72 | 73 | 74 | 77 | 80 | 81 | 82 | -------------------------------------------------------------------------------- /SparkCore/assets/fonts/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/particle-iot/android-app/bf2c751e33c60a44f19b674610d8d9ad3c495a16/SparkCore/assets/fonts/.gitkeep -------------------------------------------------------------------------------- /SparkCore/ic_launcher-web.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/particle-iot/android-app/bf2c751e33c60a44f19b674610d8d9ad3c495a16/SparkCore/ic_launcher-web.png -------------------------------------------------------------------------------- /SparkCore/libs/android-support-v4.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/particle-iot/android-app/bf2c751e33c60a44f19b674610d8d9ad3c495a16/SparkCore/libs/android-support-v4.jar -------------------------------------------------------------------------------- /SparkCore/libs/commons-lang3-3.1.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/particle-iot/android-app/bf2c751e33c60a44f19b674610d8d9ad3c495a16/SparkCore/libs/commons-lang3-3.1.jar -------------------------------------------------------------------------------- /SparkCore/libs/gson-2.2.4.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/particle-iot/android-app/bf2c751e33c60a44f19b674610d8d9ad3c495a16/SparkCore/libs/gson-2.2.4.jar -------------------------------------------------------------------------------- /SparkCore/libs/guava-15.0.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/particle-iot/android-app/bf2c751e33c60a44f19b674610d8d9ad3c495a16/SparkCore/libs/guava-15.0.jar -------------------------------------------------------------------------------- /SparkCore/libs/okhttp-1.2.1-jar-with-dependencies.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/particle-iot/android-app/bf2c751e33c60a44f19b674610d8d9ad3c495a16/SparkCore/libs/okhttp-1.2.1-jar-with-dependencies.jar -------------------------------------------------------------------------------- /SparkCore/proguard-project.txt: -------------------------------------------------------------------------------- 1 | # To enable ProGuard in your project, edit project.properties 2 | # to define the proguard.config property as described in that file. 3 | # 4 | # Add project specific ProGuard rules here. 5 | # By default, the flags in this file are appended to flags specified 6 | # in ${sdk.dir}/tools/proguard/proguard-android.txt 7 | # You can edit the include path and order by changing the ProGuard 8 | # include property in project.properties. 9 | # 10 | # For more details, see 11 | # http://developer.android.com/guide/developing/tools/proguard.html 12 | 13 | # Add any project specific keep options here: 14 | 15 | # If your project uses WebView with JS, uncomment the following 16 | # and specify the fully qualified class name to the JavaScript interface 17 | # class: 18 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 19 | # public *; 20 | #} 21 | -------------------------------------------------------------------------------- /SparkCore/project.properties: -------------------------------------------------------------------------------- 1 | # This file is automatically generated by Android Tools. 2 | # Do not modify this file -- YOUR CHANGES WILL BE ERASED! 3 | # 4 | # This file must be checked in Version Control Systems. 5 | # 6 | # To customize properties used by the Ant build system edit 7 | # "ant.properties", and override values to adapt the script to your 8 | # project structure. 9 | # 10 | # To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home): 11 | #proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt 12 | 13 | # Project target. 14 | target=android-19 15 | android.library.reference.1=../Fontify 16 | -------------------------------------------------------------------------------- /SparkCore/res/animator/pin_background_end.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | -------------------------------------------------------------------------------- /SparkCore/res/animator/pin_background_go_dark.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | -------------------------------------------------------------------------------- /SparkCore/res/animator/pin_background_start.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | -------------------------------------------------------------------------------- /SparkCore/res/drawable-hdpi/ic_action_edit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/particle-iot/android-app/bf2c751e33c60a44f19b674610d8d9ad3c495a16/SparkCore/res/drawable-hdpi/ic_action_edit.png -------------------------------------------------------------------------------- /SparkCore/res/drawable-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/particle-iot/android-app/bf2c751e33c60a44f19b674610d8d9ad3c495a16/SparkCore/res/drawable-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /SparkCore/res/drawable-hdpi/ic_wifi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/particle-iot/android-app/bf2c751e33c60a44f19b674610d8d9ad3c495a16/SparkCore/res/drawable-hdpi/ic_wifi.png -------------------------------------------------------------------------------- /SparkCore/res/drawable-hdpi/new_core.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/particle-iot/android-app/bf2c751e33c60a44f19b674610d8d9ad3c495a16/SparkCore/res/drawable-hdpi/new_core.png -------------------------------------------------------------------------------- /SparkCore/res/drawable-mdpi/ic_action_edit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/particle-iot/android-app/bf2c751e33c60a44f19b674610d8d9ad3c495a16/SparkCore/res/drawable-mdpi/ic_action_edit.png -------------------------------------------------------------------------------- /SparkCore/res/drawable-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/particle-iot/android-app/bf2c751e33c60a44f19b674610d8d9ad3c495a16/SparkCore/res/drawable-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /SparkCore/res/drawable-mdpi/ic_menu_moreoverflow_normal_holo_dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/particle-iot/android-app/bf2c751e33c60a44f19b674610d8d9ad3c495a16/SparkCore/res/drawable-mdpi/ic_menu_moreoverflow_normal_holo_dark.png -------------------------------------------------------------------------------- /SparkCore/res/drawable-mdpi/ic_wifi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/particle-iot/android-app/bf2c751e33c60a44f19b674610d8d9ad3c495a16/SparkCore/res/drawable-mdpi/ic_wifi.png -------------------------------------------------------------------------------- /SparkCore/res/drawable-mdpi/log_in_background_bitmap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/particle-iot/android-app/bf2c751e33c60a44f19b674610d8d9ad3c495a16/SparkCore/res/drawable-mdpi/log_in_background_bitmap.png -------------------------------------------------------------------------------- /SparkCore/res/drawable-mdpi/new_core.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/particle-iot/android-app/bf2c751e33c60a44f19b674610d8d9ad3c495a16/SparkCore/res/drawable-mdpi/new_core.png -------------------------------------------------------------------------------- /SparkCore/res/drawable-mdpi/sign_up_background_bitmap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/particle-iot/android-app/bf2c751e33c60a44f19b674610d8d9ad3c495a16/SparkCore/res/drawable-mdpi/sign_up_background_bitmap.png -------------------------------------------------------------------------------- /SparkCore/res/drawable-mdpi/smart_config_background_bitmap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/particle-iot/android-app/bf2c751e33c60a44f19b674610d8d9ad3c495a16/SparkCore/res/drawable-mdpi/smart_config_background_bitmap.png -------------------------------------------------------------------------------- /SparkCore/res/drawable-xhdpi/ic_action_edit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/particle-iot/android-app/bf2c751e33c60a44f19b674610d8d9ad3c495a16/SparkCore/res/drawable-xhdpi/ic_action_edit.png -------------------------------------------------------------------------------- /SparkCore/res/drawable-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/particle-iot/android-app/bf2c751e33c60a44f19b674610d8d9ad3c495a16/SparkCore/res/drawable-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /SparkCore/res/drawable-xhdpi/ic_wifi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/particle-iot/android-app/bf2c751e33c60a44f19b674610d8d9ad3c495a16/SparkCore/res/drawable-xhdpi/ic_wifi.png -------------------------------------------------------------------------------- /SparkCore/res/drawable-xhdpi/log_in_background_bitmap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/particle-iot/android-app/bf2c751e33c60a44f19b674610d8d9ad3c495a16/SparkCore/res/drawable-xhdpi/log_in_background_bitmap.png -------------------------------------------------------------------------------- /SparkCore/res/drawable-xhdpi/new_core.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/particle-iot/android-app/bf2c751e33c60a44f19b674610d8d9ad3c495a16/SparkCore/res/drawable-xhdpi/new_core.png -------------------------------------------------------------------------------- /SparkCore/res/drawable-xhdpi/sign_up_background_bitmap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/particle-iot/android-app/bf2c751e33c60a44f19b674610d8d9ad3c495a16/SparkCore/res/drawable-xhdpi/sign_up_background_bitmap.png -------------------------------------------------------------------------------- /SparkCore/res/drawable-xhdpi/smart_config_background_bitmap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/particle-iot/android-app/bf2c751e33c60a44f19b674610d8d9ad3c495a16/SparkCore/res/drawable-xhdpi/smart_config_background_bitmap.png -------------------------------------------------------------------------------- /SparkCore/res/drawable-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/particle-iot/android-app/bf2c751e33c60a44f19b674610d8d9ad3c495a16/SparkCore/res/drawable-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /SparkCore/res/drawable-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/particle-iot/android-app/bf2c751e33c60a44f19b674610d8d9ad3c495a16/SparkCore/res/drawable-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /SparkCore/res/drawable/action_bar_dark_bg.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /SparkCore/res/drawable/action_bar_layers.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /SparkCore/res/drawable/action_bar_transparent_gradient.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 10 | 11 | -------------------------------------------------------------------------------- /SparkCore/res/drawable/aes_checkbox_checked_temp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/particle-iot/android-app/bf2c751e33c60a44f19b674610d8d9ad3c495a16/SparkCore/res/drawable/aes_checkbox_checked_temp.png -------------------------------------------------------------------------------- /SparkCore/res/drawable/aes_checkbox_unchecked_temp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/particle-iot/android-app/bf2c751e33c60a44f19b674610d8d9ad3c495a16/SparkCore/res/drawable/aes_checkbox_unchecked_temp.png -------------------------------------------------------------------------------- /SparkCore/res/drawable/blue_button_gradient.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 10 | 11 | -------------------------------------------------------------------------------- /SparkCore/res/drawable/blue_button_pressed_gradient.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 10 | 11 | -------------------------------------------------------------------------------- /SparkCore/res/drawable/blue_button_selector.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /SparkCore/res/drawable/core_list_dot.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /SparkCore/res/drawable/link_text_selector.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /SparkCore/res/drawable/log_in_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | -------------------------------------------------------------------------------- /SparkCore/res/drawable/log_in_button_gradient.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 10 | 11 | -------------------------------------------------------------------------------- /SparkCore/res/drawable/log_in_button_pressed_gradient.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 10 | 11 | -------------------------------------------------------------------------------- /SparkCore/res/drawable/log_in_button_selector.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /SparkCore/res/drawable/point_at_d7_temp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/particle-iot/android-app/bf2c751e33c60a44f19b674610d8d9ad3c495a16/SparkCore/res/drawable/point_at_d7_temp.png -------------------------------------------------------------------------------- /SparkCore/res/drawable/progress_emerald.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /SparkCore/res/drawable/progress_indicator_temp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/particle-iot/android-app/bf2c751e33c60a44f19b674610d8d9ad3c495a16/SparkCore/res/drawable/progress_indicator_temp.png -------------------------------------------------------------------------------- /SparkCore/res/drawable/progress_spinner.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | -------------------------------------------------------------------------------- /SparkCore/res/drawable/progress_sunflower.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /SparkCore/res/drawable/red_button_gradient.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 10 | 11 | -------------------------------------------------------------------------------- /SparkCore/res/drawable/red_button_pressed_gradient.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 10 | 11 | -------------------------------------------------------------------------------- /SparkCore/res/drawable/red_button_selector.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /SparkCore/res/drawable/sign_up_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | -------------------------------------------------------------------------------- /SparkCore/res/drawable/sign_up_button_gradient.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 10 | 11 | -------------------------------------------------------------------------------- /SparkCore/res/drawable/sign_up_button_pressed_gradient.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 10 | 11 | -------------------------------------------------------------------------------- /SparkCore/res/drawable/sign_up_button_selector.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /SparkCore/res/drawable/smart_config_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | -------------------------------------------------------------------------------- /SparkCore/res/drawable/spark_logo_temp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/particle-iot/android-app/bf2c751e33c60a44f19b674610d8d9ad3c495a16/SparkCore/res/drawable/spark_logo_temp.png -------------------------------------------------------------------------------- /SparkCore/res/drawable/tinker_core_shadow_temp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/particle-iot/android-app/bf2c751e33c60a44f19b674610d8d9ad3c495a16/SparkCore/res/drawable/tinker_core_shadow_temp.png -------------------------------------------------------------------------------- /SparkCore/res/drawable/tinker_logo_temp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/particle-iot/android-app/bf2c751e33c60a44f19b674610d8d9ad3c495a16/SparkCore/res/drawable/tinker_logo_temp.png -------------------------------------------------------------------------------- /SparkCore/res/drawable/tinker_pin.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /SparkCore/res/drawable/tinker_pin_alizarin.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /SparkCore/res/drawable/tinker_pin_cyan.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /SparkCore/res/drawable/tinker_pin_emerald.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /SparkCore/res/drawable/tinker_pin_muted.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /SparkCore/res/drawable/tinker_pin_read_high.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /SparkCore/res/drawable/tinker_pin_sunflower.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /SparkCore/res/drawable/tinker_pin_write_high.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /SparkCore/res/drawable/white_circle.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /SparkCore/res/layout/activity_core_list.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 14 | 15 | 22 | 23 | -------------------------------------------------------------------------------- /SparkCore/res/layout/activity_log_in.xml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 12 | 13 | 19 | 20 | 28 | 29 | 36 | 37 | 38 | 39 | 40 | 50 | 51 | 55 | 56 | 62 | 63 | 68 | 69 | 70 | 75 | 76 | 82 | 83 | 84 | 85 | -------------------------------------------------------------------------------- /SparkCore/res/layout/activity_sign_up.xml: -------------------------------------------------------------------------------- 1 | 9 | 10 | 14 | 15 | 23 | 24 | 32 | 33 | 40 | 41 | 42 | 43 | 44 | 54 | 55 | 59 | 60 | 66 | 67 | 72 | 73 | 74 | 82 | 83 | 84 | 85 | 96 | 97 | -------------------------------------------------------------------------------- /SparkCore/res/layout/activity_smart_config.xml: -------------------------------------------------------------------------------- 1 | 10 | 11 | 15 | 16 | 21 | 22 | 27 | 28 | 32 | 33 | 34 | 35 | 47 | 48 | -------------------------------------------------------------------------------- /SparkCore/res/layout/core_row.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 15 | 16 | 29 | 30 | -------------------------------------------------------------------------------- /SparkCore/res/layout/dialog_rename.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 16 | 17 | -------------------------------------------------------------------------------- /SparkCore/res/layout/fragment_core_list.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 22 | 23 | 30 | 31 | 35 | 36 | 43 | 44 | 45 | 49 | 50 | 58 | 59 | 65 | 66 | 67 | -------------------------------------------------------------------------------- /SparkCore/res/layout/fragment_naming.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 19 | 20 | 28 | 29 | 36 | 37 | 48 | 49 | 52 | 53 | 58 | 59 | 64 | 65 | 66 | 67 | -------------------------------------------------------------------------------- /SparkCore/res/layout/fragment_no_cores_found.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 19 | 20 | 26 | 27 | 32 | 33 | -------------------------------------------------------------------------------- /SparkCore/res/layout/fragment_smart_config.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 15 | 16 | 24 | 25 | 34 | 35 | 39 | 40 | 46 | 47 | 53 | 54 | 63 | 64 | 70 | 71 | 77 | 78 | 83 | 84 | 85 | 86 | 87 | 91 | 92 | 102 | 103 | 109 | 110 | 118 | 119 | 130 | 131 | 132 | 144 | 145 | 146 | -------------------------------------------------------------------------------- /SparkCore/res/layout/fragment_tinker.xml: -------------------------------------------------------------------------------- 1 | 9 | 10 | 20 | 21 | 28 | 29 | 30 | 31 | 35 | 36 | 37 | 38 | 39 | 43 | 44 | 45 | 46 | 47 | 51 | 52 | 53 | 54 | 55 | 59 | 60 | 61 | 62 | 63 | 67 | 68 | 69 | 70 | 71 | 75 | 76 | 77 | 78 | 79 | 83 | 84 | 85 | 86 | 87 | 91 | 92 | 93 | 94 | 101 | 102 | 103 | 104 | 108 | 109 | 110 | 111 | 112 | 116 | 117 | 118 | 119 | 120 | 124 | 125 | 126 | 127 | 128 | 132 | 133 | 134 | 135 | 136 | 140 | 141 | 142 | 143 | 144 | 148 | 149 | 150 | 151 | 152 | 156 | 157 | 158 | 159 | 160 | 164 | 165 | 166 | 167 | 168 | 174 | 175 | 181 | 182 | -------------------------------------------------------------------------------- /SparkCore/res/layout/tinker_analog_read_left.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 16 | 17 | 24 | 25 | -------------------------------------------------------------------------------- /SparkCore/res/layout/tinker_analog_read_right.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 16 | 17 | 24 | 25 | -------------------------------------------------------------------------------- /SparkCore/res/layout/tinker_analog_write_left.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 16 | 17 | 24 | 25 | -------------------------------------------------------------------------------- /SparkCore/res/layout/tinker_analog_write_right.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 17 | 18 | 24 | 25 | -------------------------------------------------------------------------------- /SparkCore/res/layout/tinker_digital_read.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 19 | 20 | -------------------------------------------------------------------------------- /SparkCore/res/layout/tinker_digital_write.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 19 | 20 | -------------------------------------------------------------------------------- /SparkCore/res/layout/tinker_instructions.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 10 | 11 | 16 | 17 | 24 | 25 | 31 | 32 | 39 | 40 | 47 | 48 | 54 | 55 | 61 | 62 | 70 | 71 | 77 | 78 | 79 | 80 | -------------------------------------------------------------------------------- /SparkCore/res/layout/tinker_select.xml: -------------------------------------------------------------------------------- 1 | 9 | 10 | 19 | 20 | 25 | 26 | 30 | 31 | 35 | 36 | 39 | 40 | 41 | 44 | 45 | 48 | 49 | 53 | 54 | 55 | 56 | 60 | 61 | 65 | 66 | 70 | 71 | 74 | 75 | 76 | 79 | 80 | 83 | 84 | 88 | 89 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /SparkCore/res/menu/account.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /SparkCore/res/menu/all_screens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 13 | 14 | 17 | 20 | 23 | 26 | 29 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /SparkCore/res/menu/core_list.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 15 | 16 | -------------------------------------------------------------------------------- /SparkCore/res/menu/core_row_overflow.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 13 | 17 | 18 | -------------------------------------------------------------------------------- /SparkCore/res/menu/tinker.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 10 | -------------------------------------------------------------------------------- /SparkCore/res/values-h600dp/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 32dip 5 | 6 | -------------------------------------------------------------------------------- /SparkCore/res/values-large/layout_config.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | true 5 | 6 | -------------------------------------------------------------------------------- /SparkCore/res/values-large/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /SparkCore/res/values-sw720dp-land/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 128dp 8 | 9 | 10 | -------------------------------------------------------------------------------- /SparkCore/res/values/appconfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | api.spark.io 5 | staging.api.spark.io 6 | 7 | false 8 | 9 | 32 10 | 11 | v1 12 | access_token 13 | https 14 | 15 | 443 16 | 17 | 18 | 224.0.1.187 19 | 20 | 5683 21 | 19 22 | 23 | sparkdevices2013 24 | 25 | -------------------------------------------------------------------------------- /SparkCore/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | #00ACED 5 | #2ECC71 6 | #F1C40F 7 | #E74C3C 8 | 9 | 10 | #19FFFFFF 11 | #19000000 12 | #2E2F32 13 | #6F717D 14 | #201F24 15 | #ED0049 16 | 17 | 18 | #1ABC9C 19 | #16A085 20 | #2ECC71 21 | #27AE60 22 | #3498DB 23 | #2980B9 24 | #9B59B6 25 | #8E44AD 26 | #F1C40F 27 | #F39C12 28 | #E67E22 29 | #D35400 30 | #E74C3C 31 | #C0392B 32 | #ECF0F1 33 | #95A5A6 34 | #7F8C8D 35 | 36 | 37 | 38 | #A5000000 39 | #4C000000 40 | #B2000000 41 | #19000000 42 | #19FFFFFF 43 | #b2000000 44 | #7F000000 45 | #33000000 46 | #66000000 47 | #7f000000 48 | 49 | -------------------------------------------------------------------------------- /SparkCore/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 16dp 5 | 6 | 7 | 12sp 8 | 14sp 9 | 18sp 10 | 11 | 12 | 280dp 13 | 14 | 15 | 330dip 16 | 32dip 17 | 205dip 18 | 20sp 19 | 20 | -------------------------------------------------------------------------------- /SparkCore/res/values/font_names.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | fonts/gotham_light.otf 5 | fonts/gotham_medium.otf 6 | fonts/gotham_book.otf 7 | fonts/gotham_bold.otf 8 | 9 | -------------------------------------------------------------------------------- /SparkCore/res/values/ids.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /SparkCore/res/values/layout_config.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | true 5 | 6 | -------------------------------------------------------------------------------- /SparkCore/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Spark 6 | spark 7 | 8 | 9 | Spark could not be reached at this time. Please check your internet connection, and try again later. 10 | We have encountered an error. Please visit http://www.spark.io/support for more information.\n\nStatus code: 11 | We have encountered an error. Please check that your Core is online. 12 | 13 | 14 | Your Core is set up! 15 | Welcome to Tinker. 16 | Tinker is the fastest and easiest way to prototype and play with your Spark Core. 17 | Here you can access the basic input/output functions of the Spark Core without writing a line of code. 18 | Tap any pin to get started. Try digitalWrite on D7; there\'s an LED attached. 19 | Enjoy!\n-The Spark Team 20 | 21 | 22 | 23 | We found a Core!\nLet\'s name it. 24 | We found %d Cores!\nLet\'s name them. 25 | 26 | 27 | Settings 28 | Let\'s connect your Core. 29 | Password 30 | SSID 31 | Connect 32 | Custom AES Key 33 | You can change these names at any point. 34 | Log out 35 | Help… 36 | Spark can only connect a Core to the Wi-Fi network you’re currently associated with. To connect the Core to a different network, first connect your phone to that network, then try again. 37 | OK 38 | Cancel 39 | Name the Core that\'s shouting rainbows at you. 40 | No Cores found. 41 | Continue Anyway 42 | Try Again 43 | Report a bug 44 | Contribute 45 | Documentation 46 | Build your own Core app 47 | Spark homepage 48 | Support 49 | Connect a Core 50 | Reflash Tinker 51 | Rename Core 52 | Reconfigure Core 53 | cores 54 | Sorry, you’ve already got a Core by that name. Try another one. 55 | Rename your Core 56 | Rename 57 | Clear Tinker 58 | stop 59 | The Spark Core can only accept\npasswords up to 32 characters. 60 | Log out? 61 | Log Out 62 | (Unnamed Core) 63 | Logged out. 64 | Error communicating with server 65 | 66 | -------------------------------------------------------------------------------- /SparkCore/res/values/strings_account_screens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | email 5 | password 6 | Sign Up 7 | Log In 8 | <u>I forgot my password</u> 9 | This email address is invalid 10 | This password is too short 11 | This password is incorrect 12 | This field is required 13 | <u>I already have an account</u> 14 | Terms of Service and Privacy Policy.]]> 15 | <u>I don\'t have an account</u> 16 | 17 | -------------------------------------------------------------------------------- /SparkCore/res/values/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 10 | 11 | 14 | 15 | 18 | 19 | 22 | 23 | 26 | 27 | 36 | 37 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /SparkCore/res/values/uris.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | https://www.spark.io/support 5 | https://www.spark.io 6 | https://www.spark.io/build 7 | http://docs.spark.io 8 | http://spark.github.io 9 | http://www.github.com/spark/android-app/issues 10 | https://www.spark.io/forgot-password 11 | 12 | -------------------------------------------------------------------------------- /SparkCore/src/io/spark/core/android/app/AppConfig.java: -------------------------------------------------------------------------------- 1 | package io.spark.core.android.app; 2 | 3 | import io.spark.core.android.R; 4 | import android.content.Context; 5 | 6 | 7 | public class AppConfig { 8 | 9 | private static Context ctx; 10 | 11 | 12 | // Should be called when starting up the app, probably in 13 | // Application.onCreate() 14 | public static void initialize(Context context) { 15 | ctx = context.getApplicationContext(); 16 | } 17 | 18 | public static String getApiHostname() { 19 | int resId = useStaging() ? R.string.staging_hostname : R.string.prod_hostname; 20 | return ctx.getString(resId); 21 | } 22 | 23 | public static String getApiUrlScheme() { 24 | return ctx.getString(R.string.api_url_scheme); 25 | } 26 | 27 | public static int getApiHostPort() { 28 | return ctx.getResources().getInteger(R.integer.api_host_port); 29 | } 30 | 31 | public static String getSparkTokenCreationCredentials() { 32 | return ctx.getString(R.string.spark_token_creation_credentials); 33 | } 34 | 35 | public static boolean useStaging() { 36 | return ctx.getResources().getBoolean(R.bool.use_staging); 37 | } 38 | 39 | public static String getApiVersion() { 40 | return ctx.getString(R.string.api_version); 41 | } 42 | 43 | public static String getApiParamAccessToken() { 44 | return ctx.getString(R.string.api_param_access_token); 45 | } 46 | 47 | public static String getSmartConfigHelloListenAddress() { 48 | return ctx.getString(R.string.smart_config_hello_listen_address); 49 | } 50 | 51 | public static int getSmartConfigHelloListenPort() { 52 | return ctx.getResources().getInteger(R.integer.smart_config_hello_port); 53 | } 54 | 55 | public static int getSmartConfigHelloMessageLength() { 56 | return ctx.getResources().getInteger(R.integer.smart_config_hello_msg_length); 57 | } 58 | 59 | public static String getSmartConfigDefaultAesKey() { 60 | return ctx.getString(R.string.smart_config_default_aes_key); 61 | } 62 | 63 | } 64 | -------------------------------------------------------------------------------- /SparkCore/src/io/spark/core/android/app/DeviceState.java: -------------------------------------------------------------------------------- 1 | package io.spark.core.android.app; 2 | 3 | import static org.solemnsilence.util.Py.list; 4 | import static org.solemnsilence.util.Py.map; 5 | import static org.solemnsilence.util.Py.set; 6 | import static org.solemnsilence.util.Py.truthy; 7 | import io.spark.core.android.R; 8 | import io.spark.core.android.cloud.WebHelpers; 9 | import io.spark.core.android.cloud.api.Device; 10 | import io.spark.core.android.storage.Prefs; 11 | 12 | import java.lang.reflect.Type; 13 | import java.util.List; 14 | import java.util.Map; 15 | import java.util.Random; 16 | import java.util.Set; 17 | 18 | import org.solemnsilence.util.TLog; 19 | 20 | import android.content.Context; 21 | import android.content.res.TypedArray; 22 | import android.os.AsyncTask; 23 | 24 | import com.google.common.base.Function; 25 | import com.google.common.collect.Collections2; 26 | import com.google.common.collect.Lists; 27 | import com.google.gson.reflect.TypeToken; 28 | 29 | 30 | /** 31 | * Common access to {@link Device}s. 32 | * 33 | * Uses an in-memory cache, backed by on-disk storage using SharedPreferences. 34 | * 35 | */ 36 | public class DeviceState { 37 | 38 | static final TLog log = new TLog(DeviceState.class); 39 | 40 | 41 | private static final Map deviceMap = map(); 42 | private static final Random random = new Random(); 43 | 44 | private static Context appContext; 45 | 46 | 47 | public static synchronized void initialize(Context ctx) { 48 | appContext = ctx.getApplicationContext(); 49 | String coresJsonArray = Prefs.getInstance().getCoresJsonArray(); 50 | Type listType = new TypeToken>() { 51 | }.getType(); 52 | List devices = WebHelpers.getGson().fromJson(coresJsonArray, listType); 53 | updateAllKnownDevices(devices); 54 | } 55 | 56 | public synchronized static List getKnownDevices() { 57 | return Lists.newArrayList(deviceMap.values()); 58 | } 59 | 60 | public synchronized static Device getDeviceById(String deviceId) { 61 | return (deviceId == null) ? null : deviceMap.get(deviceId); 62 | } 63 | 64 | public synchronized static void updateAllKnownDevices(List updatedDevices) { 65 | log.d("Updating known devices with: " + updatedDevices); 66 | 67 | Set updatedDeviceIds = set(); 68 | for (Device updatedDevice : updatedDevices) { 69 | String updatedDeviceId = updateSingleDevice(updatedDevice, false); 70 | updatedDeviceIds.add(updatedDeviceId); 71 | } 72 | 73 | // now remove the devices which weren't in the update. 74 | for (String missingDeviceId : set(deviceMap.keySet()).getDifference(updatedDeviceIds)) { 75 | log.d("Removing device from local device store: " + missingDeviceId); 76 | deviceMap.remove(missingDeviceId); 77 | } 78 | 79 | saveDevices(); 80 | } 81 | 82 | // returns the ID of the updated device. 83 | public synchronized static String updateSingleDevice(Device updatedDevice, boolean save) { 84 | Device oldDevice = deviceMap.get(updatedDevice.id); 85 | Device.Builder toInsert = updatedDevice.toBuilder(); 86 | 87 | // ensure we never have a device with a 88 | if (oldDevice != null) { 89 | toInsert.fillInFalseyValuesFromOther(oldDevice); 90 | } 91 | 92 | if (!truthy(toInsert.getColor())) { 93 | toInsert.setColor(getRandomCoreColor()); 94 | } 95 | if (!truthy(toInsert.getName())) { 96 | // don't allow null or empty string names 97 | toInsert.setName(appContext.getString(R.string._unnamed_core_)); 98 | } 99 | 100 | Device built = toInsert.build(); 101 | deviceMap.put(built.id, built); 102 | 103 | if (save) { 104 | saveDevices(); 105 | } 106 | 107 | return updatedDevice.id; 108 | } 109 | 110 | public synchronized static void renameDevice(String coreId, String newName) { 111 | // Create a device with default values and let 'updateSingleDevice' do 112 | // the work. Kinda cheesy, but it works. 113 | Device device = deviceMap.get(coreId); 114 | if (device == null) { 115 | log.e("Cannot rename, no device found with ID: " + coreId); 116 | return; 117 | } 118 | updateSingleDevice(device.toBuilder() 119 | .setName(newName) 120 | .build(), 121 | true); 122 | } 123 | 124 | public synchronized static Set getExistingCoreNames() { 125 | return set(Collections2.transform(deviceMap.values(), 126 | new Function() { 127 | 128 | @Override 129 | public String apply(Device device) { 130 | return device.name; 131 | } 132 | })); 133 | } 134 | 135 | 136 | private static int getRandomCoreColor() { 137 | TypedArray colors = appContext.getResources().obtainTypedArray(R.array.core_colors); 138 | int max = colors.length() - 1; 139 | int min = 0; 140 | int randomIdx = random.nextInt((max - min) + 1) + min; 141 | int color = colors.getColor(randomIdx, 0); 142 | colors.recycle(); 143 | return color; 144 | } 145 | 146 | 147 | private synchronized static void saveDevices() { 148 | new DevicesSaver(list(deviceMap.values())) 149 | .executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); 150 | } 151 | 152 | 153 | private static class DevicesSaver extends AsyncTask { 154 | 155 | final Prefs prefs = Prefs.getInstance(); 156 | final List devices; 157 | 158 | public DevicesSaver(List devices) { 159 | this.devices = devices; 160 | } 161 | 162 | @Override 163 | protected Void doInBackground(Void... params) { 164 | Type listType = new TypeToken>() { 165 | }.getType(); 166 | String asJson = WebHelpers.getGson().toJson(devices, listType); 167 | prefs.saveCoresJsonArray(asJson); 168 | return null; 169 | } 170 | 171 | } 172 | 173 | 174 | } 175 | -------------------------------------------------------------------------------- /SparkCore/src/io/spark/core/android/app/SparkCoreApp.java: -------------------------------------------------------------------------------- 1 | package io.spark.core.android.app; 2 | 3 | import io.spark.core.android.cloud.WebHelpers; 4 | import io.spark.core.android.storage.Prefs; 5 | import io.spark.core.android.storage.TinkerPrefs; 6 | import android.app.Application; 7 | 8 | 9 | public class SparkCoreApp extends Application { 10 | 11 | @Override 12 | public void onCreate() { 13 | super.onCreate(); 14 | 15 | AppConfig.initialize(this); 16 | Prefs.initialize(this); 17 | TinkerPrefs.initialize(this); 18 | WebHelpers.initialize(this); 19 | DeviceState.initialize(this); 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /SparkCore/src/io/spark/core/android/cloud/ApiUrlHelper.java: -------------------------------------------------------------------------------- 1 | package io.spark.core.android.cloud; 2 | 3 | import static org.solemnsilence.util.Py.truthy; 4 | import io.spark.core.android.app.AppConfig; 5 | 6 | import java.net.MalformedURLException; 7 | import java.net.URL; 8 | 9 | import org.solemnsilence.util.TLog; 10 | 11 | import android.net.Uri; 12 | 13 | 14 | public class ApiUrlHelper { 15 | 16 | // all Uris just end up getting built from this one anyhow, no need to keep 17 | // reconstructing them completely 18 | private static Uri baseUri; 19 | 20 | 21 | public static Uri.Builder buildUri(String authToken, String... pathSegments) { 22 | Uri.Builder builder = getBaseUriBuilder().appendPath(AppConfig.getApiVersion()); 23 | for (String segment : pathSegments) { 24 | builder.appendPath(segment); 25 | } 26 | 27 | if (truthy(authToken)) { 28 | builder.appendQueryParameter(AppConfig.getApiParamAccessToken(), authToken); 29 | } 30 | return builder; 31 | } 32 | 33 | public synchronized static Uri.Builder getBaseUriBuilder() { 34 | if (baseUri == null) { 35 | baseUri = new Uri.Builder() 36 | .scheme(AppConfig.getApiUrlScheme()) 37 | .encodedAuthority(AppConfig.getApiHostname() + ":" + AppConfig.getApiHostPort()) 38 | .build(); 39 | } 40 | return baseUri.buildUpon(); 41 | } 42 | 43 | public static URL convertToURL(Uri.Builder uriBuilder) { 44 | Uri builtUri = uriBuilder.build(); 45 | try { 46 | return new URL(builtUri.toString()); 47 | } catch (MalformedURLException e) { 48 | // Not printing exception here since I don't know for sure if this 49 | // could ever include the URL itself, which in the case of a GET 50 | // request would include the token(!) 51 | log.e("Unable to build URL from Uri"); 52 | return null; 53 | } 54 | } 55 | 56 | 57 | private static final TLog log = new TLog(ApiUrlHelper.class); 58 | 59 | } 60 | -------------------------------------------------------------------------------- /SparkCore/src/io/spark/core/android/cloud/WebHelpers.java: -------------------------------------------------------------------------------- 1 | package io.spark.core.android.cloud; 2 | 3 | import io.spark.core.android.app.AppConfig; 4 | 5 | import java.security.SecureRandom; 6 | import java.security.cert.CertificateException; 7 | import java.security.cert.X509Certificate; 8 | 9 | import javax.net.ssl.HostnameVerifier; 10 | import javax.net.ssl.SSLContext; 11 | import javax.net.ssl.SSLSession; 12 | import javax.net.ssl.X509TrustManager; 13 | 14 | import org.solemnsilence.util.TLog; 15 | 16 | import android.content.Context; 17 | 18 | import com.google.gson.FieldNamingPolicy; 19 | import com.google.gson.Gson; 20 | import com.google.gson.GsonBuilder; 21 | import com.squareup.okhttp.OkHttpClient; 22 | 23 | 24 | public class WebHelpers { 25 | 26 | private static final TLog log = new TLog(WebHelpers.class); 27 | 28 | 29 | private static OkHttpClient okHttpClient; 30 | private static Gson gson; 31 | private static boolean initialized = false; 32 | 33 | 34 | // should be called during Application.onCreate() to ensure availability 35 | public static void initialize(Context ctx) { 36 | if (!initialized) { 37 | if (AppConfig.useStaging()) { 38 | okHttpClient = disableTLSforStaging(); 39 | } else { 40 | okHttpClient = new OkHttpClient(); 41 | } 42 | gson = new GsonBuilder() 43 | .setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES) 44 | .create(); 45 | initialized = true; 46 | } 47 | } 48 | 49 | public static Gson getGson() { 50 | return gson; 51 | } 52 | 53 | public static OkHttpClient getOkClient() { 54 | return okHttpClient; 55 | } 56 | 57 | 58 | private static OkHttpClient disableTLSforStaging() { 59 | 60 | log.e("WARNING: TLS DISABLED FOR STAGING!"); 61 | 62 | OkHttpClient client = new OkHttpClient(); 63 | client.setHostnameVerifier(new HostnameVerifier() { 64 | 65 | public boolean verify(String hostname, SSLSession session) { 66 | return true; 67 | } 68 | }); 69 | 70 | try { 71 | SSLContext context = SSLContext.getInstance("TLS"); 72 | context.init(null, new X509TrustManager[] { new X509TrustManager() { 73 | 74 | public void checkClientTrusted(X509Certificate[] chain, String authType) 75 | throws CertificateException { 76 | } 77 | 78 | public void checkServerTrusted(X509Certificate[] chain, String authType) 79 | throws CertificateException { 80 | } 81 | 82 | public X509Certificate[] getAcceptedIssuers() { 83 | return new X509Certificate[0]; 84 | } 85 | } }, new SecureRandom()); 86 | 87 | client.setSslSocketFactory(context.getSocketFactory()); 88 | 89 | } catch (Exception e) { 90 | e.printStackTrace(); 91 | } 92 | 93 | return client; 94 | } 95 | 96 | 97 | } 98 | -------------------------------------------------------------------------------- /SparkCore/src/io/spark/core/android/cloud/api/Device.java: -------------------------------------------------------------------------------- 1 | package io.spark.core.android.cloud.api; 2 | 3 | import static org.solemnsilence.util.Py.truthy; 4 | 5 | 6 | /** 7 | * Class representing a single Spark device (i.e.: a Core) 8 | */ 9 | public class Device { 10 | 11 | public final String id; 12 | public final String name; 13 | public final int color; 14 | 15 | private Device(String id, String name, int color) { 16 | this.id = id; 17 | this.name = name; 18 | this.color = color; 19 | } 20 | 21 | 22 | public static Builder newBuilder() { 23 | return new Builder(); 24 | } 25 | 26 | public Builder toBuilder() { 27 | return newBuilder() 28 | .setColor(color) 29 | .setId(id) 30 | .setName(name); 31 | } 32 | 33 | 34 | public static class Builder { 35 | 36 | private String id; 37 | private String name; 38 | private int color; 39 | 40 | private Builder() { 41 | } 42 | 43 | public Device build() { 44 | return new Device(id, name, color); 45 | } 46 | 47 | public Builder fillInFalseyValuesFromOther(Device other) { 48 | this.id = (truthy(this.id)) ? this.id : other.id; 49 | this.name = (truthy(this.name)) ? this.name : other.name; 50 | this.color = (truthy(this.color)) ? this.color : other.color; 51 | return this; 52 | } 53 | 54 | 55 | public String getId() { 56 | return id; 57 | } 58 | 59 | public Builder setId(String id) { 60 | this.id = id; 61 | return this; 62 | } 63 | 64 | public String getName() { 65 | return name; 66 | } 67 | 68 | public Builder setName(String name) { 69 | this.name = name; 70 | return this; 71 | } 72 | 73 | public int getColor() { 74 | return color; 75 | } 76 | 77 | public Builder setColor(int color) { 78 | this.color = color; 79 | return this; 80 | } 81 | } 82 | 83 | 84 | @Override 85 | public String toString() { 86 | return "Device [id=" + id + ", name=" + name + ", color=" + color + "]"; 87 | } 88 | 89 | // NOTE: device ID is the ONLY field being factored in to test equality 90 | @Override 91 | public boolean equals(Object obj) { 92 | if (this == obj) 93 | return true; 94 | if (obj == null) 95 | return false; 96 | if (getClass() != obj.getClass()) 97 | return false; 98 | Device other = (Device) obj; 99 | if (id == null) { 100 | if (other.id != null) 101 | return false; 102 | } else if (!id.equals(other.id)) 103 | return false; 104 | return true; 105 | } 106 | 107 | @Override 108 | public int hashCode() { 109 | final int prime = 31; 110 | int result = 1; 111 | result = prime * result + ((id == null) ? 0 : id.hashCode()); 112 | return result; 113 | } 114 | 115 | } 116 | -------------------------------------------------------------------------------- /SparkCore/src/io/spark/core/android/cloud/api/ListDevicesResponse.java: -------------------------------------------------------------------------------- 1 | package io.spark.core.android.cloud.api; 2 | 3 | import java.util.List; 4 | 5 | 6 | 7 | public class ListDevicesResponse extends SimpleResponse { 8 | 9 | 10 | public final List devices; 11 | 12 | 13 | public ListDevicesResponse(boolean ok, List errors, List devices) { 14 | super(ok, errors); 15 | this.devices = devices; 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /SparkCore/src/io/spark/core/android/cloud/api/SimpleResponse.java: -------------------------------------------------------------------------------- 1 | package io.spark.core.android.cloud.api; 2 | 3 | import java.util.List; 4 | 5 | 6 | public class SimpleResponse { 7 | 8 | public final boolean ok; 9 | public final List errors; 10 | 11 | public SimpleResponse(boolean ok, List errors) { 12 | super(); 13 | this.ok = ok; 14 | this.errors = errors; 15 | } 16 | 17 | @Override 18 | public String toString() { 19 | return "SimpleResponse [ok=" + ok + ", errors=" + errors + "]"; 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /SparkCore/src/io/spark/core/android/cloud/api/TinkerResponse.java: -------------------------------------------------------------------------------- 1 | package io.spark.core.android.cloud.api; 2 | 3 | import static org.solemnsilence.util.Py.truthy; 4 | import android.os.Parcel; 5 | import android.os.Parcelable; 6 | 7 | 8 | public class TinkerResponse implements Parcelable { 9 | 10 | public static final int RESPONSE_TYPE_DIGITAL = 1; 11 | public static final int RESPONSE_TYPE_ANALOG = 2; 12 | 13 | public static final int REQUEST_TYPE_READ = 3; 14 | public static final int REQUEST_TYPE_WRITE = 4; 15 | 16 | 17 | public final int requestType; 18 | public final String coreId; 19 | public final String pin; 20 | public final int responseValue; 21 | public final int responseType; 22 | public final boolean errorMakingRequest; 23 | 24 | 25 | public TinkerResponse(int requestType, String coreId, String pin, int responseType, 26 | int responseValue, boolean requestError) { 27 | this.requestType = requestType; 28 | this.coreId = coreId; 29 | this.pin = pin; 30 | this.responseType = responseType; 31 | this.responseValue = responseValue; 32 | this.errorMakingRequest = requestError; 33 | } 34 | 35 | public TinkerResponse(Parcel in) { 36 | this.requestType = in.readInt(); 37 | this.coreId = in.readString(); 38 | this.pin = in.readString(); 39 | this.responseType = in.readInt(); 40 | this.responseValue = in.readInt(); 41 | this.errorMakingRequest = truthy(in.readInt()); 42 | } 43 | 44 | @Override 45 | public String toString() { 46 | return "TinkerResponse [requestType=" + requestType + ", coreId=" + coreId + ", pin=" + pin 47 | + ", responseValue=" + responseValue + ", responseType=" + responseType 48 | + ", requestError=" + errorMakingRequest + "]"; 49 | } 50 | 51 | @Override 52 | public int describeContents() { 53 | return 0; 54 | } 55 | 56 | @Override 57 | public void writeToParcel(Parcel dest, int flags) { 58 | dest.writeInt(requestType); 59 | dest.writeString(coreId); 60 | dest.writeString(pin); 61 | dest.writeInt(responseType); 62 | dest.writeInt(responseValue); 63 | dest.writeInt((errorMakingRequest) ? 1 : 0); 64 | } 65 | 66 | 67 | public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { 68 | 69 | public TinkerResponse createFromParcel(Parcel in) { 70 | return new TinkerResponse(in); 71 | } 72 | 73 | public TinkerResponse[] newArray(int size) { 74 | return new TinkerResponse[size]; 75 | } 76 | }; 77 | 78 | } 79 | -------------------------------------------------------------------------------- /SparkCore/src/io/spark/core/android/cloud/login/TokenRequest.java: -------------------------------------------------------------------------------- 1 | package io.spark.core.android.cloud.login; 2 | 3 | import static org.solemnsilence.util.Py.list; 4 | import io.spark.core.android.util.Strings; 5 | 6 | import java.util.List; 7 | 8 | import org.apache.http.client.utils.URLEncodedUtils; 9 | import org.apache.http.message.BasicNameValuePair; 10 | import org.apache.http.protocol.HTTP; 11 | 12 | 13 | /** 14 | * Note: cannot be used with GSON like the other models, thus the placement in a 15 | * separate package * 16 | */ 17 | public class TokenRequest { 18 | 19 | public final String username; 20 | public final String password; 21 | 22 | 23 | public TokenRequest(String username, String password) { 24 | this.username = username; 25 | this.password = password; 26 | } 27 | 28 | 29 | @Override 30 | public String toString() { 31 | return "LoginRequest [username=" + username + ", " + 32 | "password=" + Strings.getRedacted(password) + "]"; 33 | } 34 | 35 | 36 | public String asFormEncodedData() { 37 | List pairs = list( 38 | new BasicNameValuePair("grant_type", "password"), 39 | new BasicNameValuePair("username", username), 40 | new BasicNameValuePair("password", password)); 41 | return URLEncodedUtils.format(pairs, HTTP.UTF_8); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /SparkCore/src/io/spark/core/android/cloud/login/TokenResponse.java: -------------------------------------------------------------------------------- 1 | package io.spark.core.android.cloud.login; 2 | 3 | import io.spark.core.android.util.Strings; 4 | 5 | 6 | public class TokenResponse { 7 | 8 | // only available when request is successful (HTTP 200) 9 | public final String accessToken; 10 | public final String tokenType; 11 | public final int expiresIn; 12 | 13 | // all(?) other responses 14 | public final String errorDescription; 15 | 16 | private int statusCode; 17 | 18 | 19 | public TokenResponse(String accessToken, String tokenType, int expiresIn, 20 | String errorDescription) { 21 | this.accessToken = accessToken; 22 | this.tokenType = tokenType; 23 | this.expiresIn = expiresIn; 24 | this.errorDescription = errorDescription; 25 | } 26 | 27 | public TokenResponse() { 28 | this(null, null, -1, null); 29 | } 30 | 31 | @Override 32 | public String toString() { 33 | return "LoginResponse [accessToken=" + Strings.getRedacted(accessToken) + ", tokenType=" 34 | + tokenType + ", expiresIn=" + expiresIn + ", errorDescription=" + errorDescription 35 | + "]"; 36 | } 37 | 38 | public int getStatusCode() { 39 | return statusCode; 40 | } 41 | 42 | public void setStatusCode(int statusCode) { 43 | this.statusCode = statusCode; 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /SparkCore/src/io/spark/core/android/cloud/login/TokenTool.java: -------------------------------------------------------------------------------- 1 | package io.spark.core.android.cloud.login; 2 | 3 | import io.spark.core.android.app.AppConfig; 4 | import io.spark.core.android.cloud.ApiUrlHelper; 5 | 6 | import java.io.BufferedInputStream; 7 | import java.io.BufferedOutputStream; 8 | import java.io.BufferedReader; 9 | import java.io.IOException; 10 | import java.io.InputStream; 11 | import java.io.InputStreamReader; 12 | import java.io.OutputStream; 13 | import java.io.UnsupportedEncodingException; 14 | import java.net.HttpURLConnection; 15 | import java.net.URL; 16 | 17 | import org.apache.http.protocol.HTTP; 18 | import org.solemnsilence.util.TLog; 19 | 20 | import android.net.Uri; 21 | import android.util.Base64; 22 | 23 | import com.google.gson.Gson; 24 | import com.squareup.okhttp.OkHttpClient; 25 | 26 | 27 | public class TokenTool { 28 | 29 | private static final TLog log = new TLog(TokenTool.class); 30 | 31 | private static final String[] PATH_SEGMENTS = new String[] { "oauth", "token" }; 32 | 33 | 34 | private final Gson gson; 35 | private final OkHttpClient okHttpclient; 36 | 37 | public TokenTool(Gson gson, OkHttpClient okHttpclient) { 38 | this.gson = gson; 39 | this.okHttpclient = okHttpclient; 40 | } 41 | 42 | 43 | public TokenResponse requestToken(TokenRequest tokenRequest) { 44 | // URL url = ApiUrlHelper.buildUrlNoVersion(PATH); 45 | Uri.Builder uriBuilder = ApiUrlHelper.getBaseUriBuilder(); 46 | for (String pathSegment : PATH_SEGMENTS) { 47 | uriBuilder.appendPath(pathSegment); 48 | } 49 | URL url = ApiUrlHelper.convertToURL(uriBuilder); 50 | HttpURLConnection urlConnection = null; 51 | try { 52 | urlConnection = okHttpclient.open(url); 53 | return requestTokenPrivate(urlConnection, tokenRequest); 54 | 55 | } catch (Exception e) { 56 | log.e("Error when logging in"); 57 | return null; 58 | 59 | } finally { 60 | if (urlConnection != null) { 61 | urlConnection.disconnect(); 62 | } 63 | } 64 | } 65 | 66 | private TokenResponse requestTokenPrivate(HttpURLConnection urlConnection, 67 | TokenRequest tokenRequest) { 68 | 69 | TokenResponse response = new TokenResponse(); 70 | int responseCode = -1; 71 | 72 | urlConnection.setDoOutput(true); 73 | urlConnection.setConnectTimeout(5000); 74 | urlConnection.setReadTimeout(15000); 75 | urlConnection.setRequestProperty("Authorization", getBasicAuthString()); 76 | 77 | try { 78 | OutputStream out = new BufferedOutputStream(urlConnection.getOutputStream()); 79 | out.write(tokenRequest.asFormEncodedData().getBytes(HTTP.UTF_8)); 80 | out.close(); 81 | 82 | responseCode = urlConnection.getResponseCode(); 83 | 84 | InputStream in = new BufferedInputStream(urlConnection.getInputStream()); 85 | String responseStr = readStream(in); 86 | in.close(); 87 | if (responseStr == null) { 88 | log.e("Error logging in, response was null. HTTP response: " + responseCode); 89 | return null; 90 | } else { 91 | response = gson.fromJson(responseStr, TokenResponse.class); 92 | } 93 | } catch (IOException e) { 94 | log.e("Error requesting token"); 95 | } 96 | 97 | response.setStatusCode(responseCode); 98 | return response; 99 | } 100 | 101 | private String getBasicAuthString() { 102 | try { 103 | byte[] asBytes = AppConfig.getSparkTokenCreationCredentials().getBytes(HTTP.UTF_8); 104 | return "Basic " + Base64.encodeToString(asBytes, Base64.NO_WRAP); 105 | } catch (UnsupportedEncodingException e) { 106 | log.e("Error encoding String as UTF-8 bytes: ", e); 107 | return ""; 108 | } 109 | } 110 | 111 | static String readStream(InputStream in) throws IOException { 112 | StringBuilder strBuilder = new StringBuilder(); 113 | try { 114 | BufferedReader reader = new BufferedReader(new InputStreamReader(in)); 115 | for (String line = reader.readLine(); line != null; line = reader.readLine()) { 116 | strBuilder.append(line).append("\n"); 117 | } 118 | return strBuilder.toString(); 119 | 120 | } finally { 121 | in.close(); 122 | } 123 | } 124 | 125 | } 126 | -------------------------------------------------------------------------------- /SparkCore/src/io/spark/core/android/cloud/requestservice/ClearableIntentService.java: -------------------------------------------------------------------------------- 1 | package io.spark.core.android.cloud.requestservice; 2 | 3 | 4 | /* 5 | * Copyright (C) 2008 The Android Open Source Project 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 8 | * use this file except in compliance with the License. You may obtain a copy of 9 | * the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 15 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 16 | * License for the specific language governing permissions and limitations under 17 | * the License. 18 | */ 19 | 20 | 21 | import android.app.Service; 22 | import android.content.Intent; 23 | import android.os.Handler; 24 | import android.os.HandlerThread; 25 | import android.os.IBinder; 26 | import android.os.Looper; 27 | import android.os.Message; 28 | 29 | 30 | /** 31 | * This is just a subclass of IntentService which can have its queue cleared 32 | * when by sending "ACTION_CLEAR_INTENT_QUEUE" 33 | * 34 | * 35 | * IntentService is a base class for {@link Service}s that handle asynchronous 36 | * requests (expressed as {@link Intent}s) on demand. Clients send requests 37 | * through {@link android.content.Context#startService(Intent)} calls; the 38 | * service is started as needed, handles each Intent in turn using a worker 39 | * thread, and stops itself when it runs out of work. 40 | * 41 | *

42 | * This "work queue processor" pattern is commonly used to offload tasks from an 43 | * application's main thread. The IntentService class exists to simplify this 44 | * pattern and take care of the mechanics. To use it, extend IntentService and 45 | * implement {@link #onHandleIntent(Intent)}. IntentService will receive the 46 | * Intents, launch a worker thread, and stop the service as appropriate. 47 | * 48 | *

49 | * All requests are handled on a single worker thread -- they may take as long 50 | * as necessary (and will not block the application's main loop), but only one 51 | * request will be processed at a time. 52 | * 53 | *

54 | *

Developer Guides

55 | *

56 | * For a detailed discussion about how to create services, read the Services 58 | * developer guide. 59 | *

60 | *
61 | * 62 | * @see android.os.AsyncTask 63 | */ 64 | public abstract class ClearableIntentService extends Service { 65 | 66 | 67 | public static final String ACTION_CLEAR_INTENT_QUEUE = "ACTION_CLEAR_INTENT_QUEUE"; 68 | 69 | 70 | private volatile Looper mServiceLooper; 71 | private volatile ServiceHandler mServiceHandler; 72 | private String mName; 73 | private boolean mRedelivery; 74 | 75 | 76 | private final class ServiceHandler extends Handler { 77 | 78 | public ServiceHandler(Looper looper) { 79 | super(looper); 80 | } 81 | 82 | @Override 83 | public void handleMessage(Message msg) { 84 | onHandleIntent((Intent) msg.obj); 85 | stopSelf(msg.arg1); 86 | } 87 | } 88 | 89 | /** 90 | * Creates an IntentService. Invoked by your subclass's constructor. 91 | * 92 | * @param name 93 | * Used to name the worker thread, important only for debugging. 94 | */ 95 | public ClearableIntentService(String name) { 96 | super(); 97 | mName = name; 98 | } 99 | 100 | /** 101 | * Sets intent redelivery preferences. Usually called from the constructor 102 | * with your preferred semantics. 103 | * 104 | *

105 | * If enabled is true, {@link #onStartCommand(Intent, int, int)} will return 106 | * {@link Service#START_REDELIVER_INTENT}, so if this process dies before 107 | * {@link #onHandleIntent(Intent)} returns, the process will be restarted 108 | * and the intent redelivered. If multiple Intents have been sent, only the 109 | * most recent one is guaranteed to be redelivered. 110 | * 111 | *

112 | * If enabled is false (the default), 113 | * {@link #onStartCommand(Intent, int, int)} will return 114 | * {@link Service#START_NOT_STICKY}, and if the process dies, the Intent 115 | * dies along with it. 116 | */ 117 | public void setIntentRedelivery(boolean enabled) { 118 | mRedelivery = enabled; 119 | } 120 | 121 | @Override 122 | public void onCreate() { 123 | super.onCreate(); 124 | HandlerThread thread = new HandlerThread("IntentService[" + mName + "]"); 125 | thread.start(); 126 | 127 | mServiceLooper = thread.getLooper(); 128 | mServiceHandler = new ServiceHandler(mServiceLooper); 129 | } 130 | 131 | @Override 132 | public void onStart(Intent intent, int startId) { 133 | if (intent.getAction().equals(ACTION_CLEAR_INTENT_QUEUE)) { 134 | mServiceHandler.removeMessages(0); 135 | stopSelf(startId); 136 | } else { 137 | Message msg = mServiceHandler.obtainMessage(); 138 | msg.arg1 = startId; 139 | msg.obj = intent; 140 | mServiceHandler.sendMessage(msg); 141 | } 142 | } 143 | 144 | /** 145 | * You should not override this method for your IntentService. Instead, 146 | * override {@link #onHandleIntent}, which the system calls when the 147 | * IntentService receives a start request. 148 | * 149 | * @see android.app.Service#onStartCommand 150 | */ 151 | @Override 152 | public int onStartCommand(Intent intent, int flags, int startId) { 153 | onStart(intent, startId); 154 | return mRedelivery ? START_REDELIVER_INTENT : START_NOT_STICKY; 155 | } 156 | 157 | @Override 158 | public void onDestroy() { 159 | mServiceLooper.quit(); 160 | } 161 | 162 | /** 163 | * Unless you provide binding for your service, you don't need to implement 164 | * this method, because the default implementation returns null. 165 | * 166 | * @see android.app.Service#onBind 167 | */ 168 | @Override 169 | public IBinder onBind(Intent intent) { 170 | return null; 171 | } 172 | 173 | /** 174 | * This method is invoked on the worker thread with a request to process. 175 | * Only one Intent is processed at a time, but the processing happens on a 176 | * worker thread that runs independently from other application logic. So, 177 | * if this code takes a long time, it will hold up other requests to the 178 | * same IntentService, but it will not hold up anything else. When all 179 | * requests have been handled, the IntentService stops itself, so you should 180 | * not call {@link #stopSelf}. 181 | * 182 | * @param intent 183 | * The value passed to 184 | * {@link android.content.Context#startService(Intent)}. 185 | */ 186 | protected abstract void onHandleIntent(Intent intent); 187 | } 188 | -------------------------------------------------------------------------------- /SparkCore/src/io/spark/core/android/smartconfig/SmartConfigState.java: -------------------------------------------------------------------------------- 1 | package io.spark.core.android.smartconfig; 2 | 3 | import static org.solemnsilence.util.Py.set; 4 | 5 | import java.util.Set; 6 | 7 | import com.google.common.collect.Sets; 8 | 9 | 10 | public class SmartConfigState { 11 | 12 | 13 | private static final Set smartConfigFoundDeviceIds = set(); 14 | private static final Set claimedButPossiblyUnnamedDeviceIds = set(); 15 | 16 | 17 | public synchronized static Set getSmartConfigFoundDeviceIds() { 18 | return Sets.newHashSet(smartConfigFoundDeviceIds); 19 | } 20 | 21 | public synchronized static void addSmartConfigFoundId(String newId) { 22 | smartConfigFoundDeviceIds.add(newId); 23 | } 24 | 25 | public synchronized static void removeSmartConfigFoundDeviceId(String newId) { 26 | smartConfigFoundDeviceIds.remove(newId); 27 | } 28 | 29 | public synchronized static Set getClaimedButPossiblyUnnamedDeviceIds() { 30 | return Sets.newHashSet(claimedButPossiblyUnnamedDeviceIds); 31 | } 32 | 33 | public synchronized static void addClaimedButPossiblyUnnamedDeviceId(String newId) { 34 | claimedButPossiblyUnnamedDeviceIds.add(newId); 35 | } 36 | 37 | public synchronized static void removeClaimedButPossiblyUnnamedDeviceIds(String newId) { 38 | claimedButPossiblyUnnamedDeviceIds.remove(newId); 39 | } 40 | 41 | public synchronized static void clearSmartConfigData() { 42 | claimedButPossiblyUnnamedDeviceIds.clear(); 43 | smartConfigFoundDeviceIds.clear(); 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /SparkCore/src/io/spark/core/android/storage/Prefs.java: -------------------------------------------------------------------------------- 1 | package io.spark.core.android.storage; 2 | 3 | import static org.solemnsilence.util.Py.list; 4 | import io.spark.core.android.ui.tinker.PinAction; 5 | 6 | import java.util.List; 7 | 8 | import android.content.Context; 9 | import android.content.SharedPreferences; 10 | 11 | 12 | public class Prefs { 13 | 14 | 15 | private static final String BUCKET_NAME = "defaultPrefsBucket"; 16 | 17 | private static final String KEY_USERNAME = "username"; 18 | private static final String KEY_TOKEN = "token"; 19 | private static final String KEY_COMPLETED_FIRST_LOGIN = "completedFirstLogin"; 20 | private static final String KEY_CORES_JSON_ARRAY = "coresJsonArray"; 21 | private static final String KEY_PIN_CONFIG_TEMPLATE = "corePinConfig_core-$1%s_pin-$2%s"; 22 | 23 | private static Prefs instance = null; 24 | 25 | // making this a singleton to avoid having to pass around Context everywhere 26 | // that this info is needed. Kind of cheating, but in practice, it will be 27 | // fine here. 28 | public static Prefs getInstance() { 29 | return instance; 30 | } 31 | 32 | public static void initialize(Context ctx) { 33 | instance = new Prefs(ctx); 34 | } 35 | 36 | 37 | private final SharedPreferences prefs; 38 | 39 | 40 | private Prefs(Context context) { 41 | prefs = context.getApplicationContext() 42 | .getSharedPreferences(BUCKET_NAME, Context.MODE_PRIVATE); 43 | } 44 | 45 | 46 | public String getUsername() { 47 | return prefs.getString(KEY_USERNAME, null); 48 | } 49 | 50 | public void saveUsername(String username) { 51 | saveString(KEY_USERNAME, username); 52 | } 53 | 54 | public String getToken() { 55 | return prefs.getString(KEY_TOKEN, null); 56 | } 57 | 58 | public void saveToken(String token) { 59 | saveString(KEY_TOKEN, token); 60 | } 61 | 62 | public boolean getCompletedFirstLogin() { 63 | return prefs.getBoolean(KEY_COMPLETED_FIRST_LOGIN, false); 64 | } 65 | 66 | public void saveCompletedFirstLogin(boolean value) { 67 | prefs.edit().putBoolean(KEY_COMPLETED_FIRST_LOGIN, value).commit(); 68 | } 69 | 70 | public String getCoresJsonArray() { 71 | return prefs.getString(KEY_CORES_JSON_ARRAY, "[]"); 72 | } 73 | 74 | public void saveCoresJsonArray(String coresJson) { 75 | saveString(KEY_CORES_JSON_ARRAY, coresJson); 76 | } 77 | 78 | 79 | public PinAction getPinFunction(String coreId, String pinName) { 80 | String key = String.format(KEY_PIN_CONFIG_TEMPLATE, coreId, pinName); 81 | return PinAction.valueOf( 82 | prefs.getString(key, PinAction.NONE.name())); 83 | } 84 | 85 | public void savePinFunction(String coreId, String pinName, PinAction function) { 86 | String key = String.format(KEY_PIN_CONFIG_TEMPLATE, coreId, pinName); 87 | applyString(key, function.name()); 88 | } 89 | 90 | public void clearTinker(String coreId) { 91 | List pinNames = list("A0", "A1", "A2", "A3", "A4", "A5", "A6", "A7", "D0", "D1", 92 | "D2", "D3", "D4", "D5", "D6", "D7"); 93 | for (String pinName : pinNames) { 94 | savePinFunction(coreId, pinName, PinAction.NONE); 95 | } 96 | } 97 | 98 | public void clear() { 99 | boolean completed = getCompletedFirstLogin(); 100 | String username = getUsername(); 101 | prefs.edit().clear().commit(); 102 | saveCompletedFirstLogin(completed); 103 | saveUsername(username); 104 | } 105 | 106 | private void saveString(String key, String value) { 107 | prefs.edit().putString(key, value).commit(); 108 | } 109 | 110 | private void applyString(String key, String value) { 111 | prefs.edit().putString(key, value).apply(); 112 | } 113 | 114 | } 115 | -------------------------------------------------------------------------------- /SparkCore/src/io/spark/core/android/storage/TinkerPrefs.java: -------------------------------------------------------------------------------- 1 | package io.spark.core.android.storage; 2 | 3 | import android.content.Context; 4 | import android.content.SharedPreferences; 5 | 6 | 7 | public class TinkerPrefs { 8 | 9 | private static final String BUCKET_NAME = "tinkerPrefsBucket"; 10 | 11 | private static final String KEY_IS_VISITED = "isVisited"; 12 | 13 | private static TinkerPrefs instance = null; 14 | 15 | // making this a singleton to avoid having to pass around Context everywhere 16 | // that this info is needed. Kind of cheating, but in practice, it will be 17 | // fine here. 18 | public static TinkerPrefs getInstance() { 19 | return instance; 20 | } 21 | 22 | public static void initialize(Context ctx) { 23 | instance = new TinkerPrefs(ctx); 24 | } 25 | 26 | 27 | private final SharedPreferences prefs; 28 | 29 | 30 | private TinkerPrefs(Context context) { 31 | prefs = context.getApplicationContext() 32 | .getSharedPreferences(BUCKET_NAME, Context.MODE_PRIVATE); 33 | } 34 | 35 | public boolean isFirstVisit() { 36 | return !prefs.getBoolean(KEY_IS_VISITED, false); 37 | } 38 | 39 | public void setVisited(boolean isVisited) { 40 | prefs.edit().putBoolean(KEY_IS_VISITED, isVisited).commit(); 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /SparkCore/src/io/spark/core/android/ui/BaseFragment.java: -------------------------------------------------------------------------------- 1 | package io.spark.core.android.ui; 2 | 3 | import io.spark.core.android.cloud.ApiFacade; 4 | import io.spark.core.android.storage.Prefs; 5 | import io.spark.core.android.ui.util.Ui; 6 | import android.animation.Animator; 7 | import android.animation.AnimatorListenerAdapter; 8 | import android.app.Activity; 9 | import android.app.Fragment; 10 | import android.os.Bundle; 11 | import android.support.v4.content.LocalBroadcastManager; 12 | import android.view.LayoutInflater; 13 | import android.view.View; 14 | import android.view.ViewGroup; 15 | 16 | 17 | public abstract class BaseFragment extends Fragment { 18 | 19 | public abstract int getContentViewLayoutId(); 20 | 21 | 22 | protected Prefs prefs; 23 | protected ApiFacade api; 24 | protected LocalBroadcastManager broadcastMgr; 25 | 26 | 27 | @Override 28 | public void onAttach(Activity activity) { 29 | super.onAttach(activity); 30 | prefs = Prefs.getInstance(); 31 | api = ApiFacade.getInstance(activity); 32 | broadcastMgr = LocalBroadcastManager.getInstance(activity); 33 | } 34 | 35 | @Override 36 | public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { 37 | return (ViewGroup) inflater.inflate(getContentViewLayoutId(), container, false); 38 | } 39 | 40 | /** 41 | * Shows & hides the progress spinner and hides the login form. 42 | */ 43 | protected void showProgress(int viewId, final boolean show) { 44 | // Fade-in the progress spinner. 45 | int shortAnimTime = getResources().getInteger(android.R.integer.config_shortAnimTime); 46 | final View progressView = Ui.findView(this, viewId); 47 | progressView.setVisibility(View.VISIBLE); 48 | progressView.animate() 49 | .setDuration(shortAnimTime) 50 | .alpha(show ? 1 : 0) 51 | .setListener(new AnimatorListenerAdapter() { 52 | 53 | @Override 54 | public void onAnimationEnd(Animator animation) { 55 | progressView.setVisibility(show ? View.VISIBLE : View.GONE); 56 | } 57 | }); 58 | } 59 | 60 | 61 | } 62 | -------------------------------------------------------------------------------- /SparkCore/src/io/spark/core/android/ui/ErrorsDelegate.java: -------------------------------------------------------------------------------- 1 | package io.spark.core.android.ui; 2 | 3 | import io.spark.core.android.R; 4 | import io.spark.core.android.cloud.ApiFacade; 5 | 6 | import org.solemnsilence.util.TLog; 7 | 8 | import android.app.Activity; 9 | import android.app.AlertDialog; 10 | import android.content.BroadcastReceiver; 11 | import android.content.Context; 12 | import android.content.DialogInterface; 13 | import android.content.DialogInterface.OnClickListener; 14 | import android.content.Intent; 15 | import android.content.IntentFilter; 16 | import android.support.v4.content.LocalBroadcastManager; 17 | 18 | 19 | public class ErrorsDelegate { 20 | 21 | private static final TLog log = new TLog(ErrorsDelegate.class); 22 | 23 | private static final long MIN_DELAY_BETWEEN_DIALOGS_MILLIS = 10 * 1000; 24 | 25 | 26 | private final Activity activity; 27 | private final LocalBroadcastManager broadcastMgr; 28 | private final ErrorReceiver errorReceiver; 29 | 30 | private long lastShownUnreachableDialog = 0; 31 | private long lastShownHttpErrorDialog = 0; 32 | private long lastShownTinkerDialog = 0; 33 | 34 | 35 | public ErrorsDelegate(Activity activity) { 36 | this.activity = activity; 37 | this.errorReceiver = new ErrorReceiver(); 38 | this.broadcastMgr = LocalBroadcastManager.getInstance(activity); 39 | } 40 | 41 | public void showCloudUnreachableDialog() { 42 | if (!canShowAnotherDialog(lastShownUnreachableDialog)) { 43 | log.d("Refusing to show another cloud unreachable dialog -- too soon since last one."); 44 | return; 45 | } 46 | lastShownUnreachableDialog = System.currentTimeMillis(); 47 | showDialog(activity.getString(R.string.cloud_unreachable_msg)); 48 | } 49 | 50 | public void showHttpErrorDialog(int statusCode) { 51 | if (!canShowAnotherDialog(lastShownHttpErrorDialog)) { 52 | log.d("Refusing to show another http error dialog -- too soon since last one."); 53 | return; 54 | } 55 | lastShownHttpErrorDialog = System.currentTimeMillis(); 56 | showDialog(activity.getString(R.string.api_error_msg) + statusCode); 57 | } 58 | 59 | public void showTinkerError() { 60 | if (!canShowAnotherDialog(lastShownTinkerDialog)) { 61 | log.d("Refusing to show another tinker error dialog -- too soon since last one."); 62 | return; 63 | } 64 | lastShownTinkerDialog = System.currentTimeMillis(); 65 | showDialog(activity.getString(R.string.tinker_error)); 66 | } 67 | 68 | 69 | public void startListeningForErrors() { 70 | broadcastMgr.registerReceiver(errorReceiver, errorReceiver.getFilter()); 71 | } 72 | 73 | public void stopListeningForErrors() { 74 | broadcastMgr.unregisterReceiver(errorReceiver); 75 | } 76 | 77 | 78 | private void showDialog(String message) { 79 | new AlertDialog.Builder(activity) 80 | .setMessage(message) 81 | .setPositiveButton(R.string.ok, new OnClickListener() { 82 | 83 | @Override 84 | public void onClick(DialogInterface dialog, int which) { 85 | dialog.dismiss(); 86 | } 87 | }) 88 | .create() 89 | .show(); 90 | } 91 | 92 | private boolean canShowAnotherDialog(long lastShownTime) { 93 | return (System.currentTimeMillis() - MIN_DELAY_BETWEEN_DIALOGS_MILLIS > lastShownTime); 94 | } 95 | 96 | 97 | private class ErrorReceiver extends BroadcastReceiver { 98 | 99 | IntentFilter getFilter() { 100 | return new IntentFilter(ApiFacade.BROADCAST_SERVICE_API_ERROR); 101 | } 102 | 103 | @Override 104 | public void onReceive(Context context, Intent intent) { 105 | int errorCode = intent.getIntExtra( 106 | ApiFacade.EXTRA_ERROR_CODE, ApiFacade.REQUEST_FAILURE_CODE); 107 | if (errorCode == ApiFacade.REQUEST_FAILURE_CODE || errorCode < 300) { 108 | showCloudUnreachableDialog(); 109 | } else { 110 | showHttpErrorDialog(errorCode); 111 | } 112 | } 113 | } 114 | 115 | } 116 | -------------------------------------------------------------------------------- /SparkCore/src/io/spark/core/android/ui/assets/Typefaces.java: -------------------------------------------------------------------------------- 1 | package io.spark.core.android.ui.assets; 2 | 3 | import static org.solemnsilence.util.Py.map; 4 | 5 | import java.util.Map; 6 | 7 | import android.content.Context; 8 | import android.graphics.Typeface; 9 | 10 | 11 | public class Typefaces { 12 | 13 | // NOTE: this is tightly coupled to the filenames in assets/fonts 14 | public static enum Style { 15 | BOLD("gotham_bold.otf"), 16 | BOLD_ITALIC("gotham_bold_ita.otf"), 17 | BOOK("gotham_book.otf"), 18 | BOOK_ITALIC("gotham_book_ita.otf"), 19 | LIGHT("gotham_light.otf"), 20 | LIGHT_ITALIC("gotham_light_ita.otf"), 21 | MEDIUM("gotham_medium.otf"), 22 | MEDIUM_ITALIC("gotham_medium_ita.otf"); 23 | 24 | public final String fileName; 25 | 26 | private Style(String name) { 27 | fileName = name; 28 | } 29 | } 30 | 31 | 32 | private static final Map typefaces = map(); 33 | 34 | 35 | public static Typeface getTypeface(Context ctx, Style style) { 36 | Typeface face = typefaces.get(style); 37 | if (face == null) { 38 | face = Typeface.createFromAsset(ctx.getAssets(), "fonts/" + style.fileName); 39 | typefaces.put(style, face); 40 | } 41 | return face; 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /SparkCore/src/io/spark/core/android/ui/corelist/CoreListFragment.java: -------------------------------------------------------------------------------- 1 | package io.spark.core.android.ui.corelist; 2 | 3 | import io.spark.core.android.R; 4 | import io.spark.core.android.app.DeviceState; 5 | import io.spark.core.android.cloud.ApiFacade; 6 | import io.spark.core.android.cloud.api.Device; 7 | import io.spark.core.android.ui.tinker.TinkerFragment; 8 | import io.spark.core.android.ui.util.Ui; 9 | 10 | import org.solemnsilence.util.EZ; 11 | import org.solemnsilence.util.Py; 12 | import org.solemnsilence.util.Py.Ranger.IntValue; 13 | import org.solemnsilence.util.TLog; 14 | 15 | import android.app.Activity; 16 | import android.app.ListFragment; 17 | import android.content.BroadcastReceiver; 18 | import android.content.Context; 19 | import android.content.Intent; 20 | import android.content.IntentFilter; 21 | import android.os.Bundle; 22 | import android.support.v4.content.LocalBroadcastManager; 23 | import android.view.LayoutInflater; 24 | import android.view.Menu; 25 | import android.view.MenuInflater; 26 | import android.view.View; 27 | import android.view.ViewGroup; 28 | import android.widget.ListView; 29 | 30 | 31 | 32 | /** 33 | * A list fragment representing a list of Cores. This fragment also supports 34 | * tablet devices by allowing list items to be given an 'activated' state upon 35 | * selection. This helps indicate which item is currently being viewed in a 36 | * {@link TinkerFragment}. 37 | *

38 | * Activities containing this fragment MUST implement the {@link Callbacks} 39 | * interface. 40 | */ 41 | public class CoreListFragment extends ListFragment { 42 | 43 | 44 | private static final TLog log = new TLog(CoreListFragment.class); 45 | 46 | 47 | /** 48 | * The fragment's current callback object, which is notified of list item 49 | * clicks. 50 | */ 51 | private Callbacks mCallbacks = sDummyCallbacks; 52 | 53 | /** 54 | * The current activated item position. Only used on tablets. 55 | */ 56 | private int mActivatedPosition = ListView.INVALID_POSITION; 57 | 58 | /** 59 | * A callback interface that all activities containing this fragment must 60 | * implement. This mechanism allows activities to be notified of item 61 | * selections. 62 | */ 63 | public interface Callbacks { 64 | 65 | /** 66 | * Callback for when an item has been selected. 67 | */ 68 | public void onItemSelected(String id); 69 | } 70 | 71 | /** 72 | * A dummy implementation of the {@link Callbacks} interface that does 73 | * nothing. Used only when this fragment is not attached to an activity. 74 | */ 75 | private static Callbacks sDummyCallbacks = new Callbacks() { 76 | 77 | @Override 78 | public void onItemSelected(String id) { 79 | } 80 | }; 81 | 82 | DeviceListAdapter deviceAdapter; 83 | DevicesUpdatedReceiver updatesReceiver; 84 | LocalBroadcastManager broadcastMgr; 85 | ApiFacade api; 86 | 87 | String selectedDeviceId = null; 88 | 89 | /** 90 | * Mandatory empty constructor for the fragment manager to instantiate the 91 | * fragment (e.g. upon screen orientation changes). 92 | */ 93 | public CoreListFragment() { 94 | } 95 | 96 | 97 | @Override 98 | public void onAttach(Activity activity) { 99 | super.onAttach(activity); 100 | 101 | // Activities containing this fragment must implement its callbacks. 102 | if (!(activity instanceof Callbacks)) { 103 | throw new IllegalStateException("Activity must implement fragment's callbacks."); 104 | } 105 | 106 | mCallbacks = (Callbacks) activity; 107 | broadcastMgr = LocalBroadcastManager.getInstance(activity); 108 | api = ApiFacade.getInstance(activity); 109 | 110 | } 111 | 112 | @Override 113 | public void onCreate(Bundle savedInstanceState) { 114 | super.onCreate(savedInstanceState); 115 | 116 | updatesReceiver = new DevicesUpdatedReceiver(); 117 | deviceAdapter = new DeviceListAdapter(getActivity()); 118 | setListAdapter(deviceAdapter); 119 | } 120 | 121 | @Override 122 | public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { 123 | return inflater.inflate(R.layout.fragment_core_list, container, false); 124 | } 125 | 126 | @Override 127 | public void onViewCreated(View view, Bundle savedInstanceState) { 128 | super.onViewCreated(view, savedInstanceState); 129 | 130 | updateDevicesList(); 131 | } 132 | 133 | @Override 134 | public void onStart() { 135 | super.onStart(); 136 | broadcastMgr.registerReceiver(updatesReceiver, updatesReceiver.getIntentFilter()); 137 | } 138 | 139 | @Override 140 | public void onStop() { 141 | broadcastMgr.unregisterReceiver(updatesReceiver); 142 | super.onStop(); 143 | } 144 | 145 | @Override 146 | public void onDetach() { 147 | super.onDetach(); 148 | // Reset the active callbacks interface to the dummy implementation. 149 | mCallbacks = sDummyCallbacks; 150 | } 151 | 152 | @Override 153 | public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { 154 | super.onCreateOptionsMenu(menu, inflater); 155 | inflater.inflate(R.menu.core_list, menu); 156 | } 157 | 158 | @Override 159 | public void onListItemClick(ListView listView, View view, int position, long id) { 160 | super.onListItemClick(listView, view, position, id); 161 | 162 | Device selectedDevice = deviceAdapter.getItem(position); 163 | // Notify the active callbacks interface (the activity, if the 164 | // fragment is attached to one) that an item has been selected. 165 | mCallbacks.onItemSelected(selectedDevice.id); 166 | mActivatedPosition = position; 167 | } 168 | 169 | public void setActivatedItem(String id) { 170 | Device device = DeviceState.getDeviceById(id); 171 | if (device == null) { 172 | log.w("Device was null when trying to set active list item??"); 173 | return; 174 | } 175 | selectedDeviceId = device.id; 176 | mActivatedPosition = deviceAdapter.getPosition(device); 177 | setActiveCoreView(); 178 | } 179 | 180 | private void updateDevicesList() { 181 | log.d("updateDevicesList()"); 182 | deviceAdapter.clear(); 183 | deviceAdapter.addAll(DeviceState.getKnownDevices()); 184 | if (selectedDeviceId != null) { 185 | setActivatedItem(selectedDeviceId); 186 | setActiveCoreView(); 187 | } 188 | } 189 | 190 | private void setActiveCoreView() { 191 | // This is just *ugly*, but all of this seems to be necessary to 192 | // *always* show the colored stripe next to the selected core. 193 | for (IntValue intValue : Py.range(getListView().getChildCount())) { 194 | View child = getListView().getChildAt(intValue.value); 195 | if (intValue.value == mActivatedPosition) { 196 | Ui.findView(child, R.id.active_core_stripe).setVisibility(View.VISIBLE); 197 | child.setActivated(true); 198 | } else { 199 | Ui.findView(child, R.id.active_core_stripe).setVisibility(View.INVISIBLE); 200 | } 201 | } 202 | 203 | EZ.runOnMainThread(new Runnable() { 204 | 205 | @Override 206 | public void run() { 207 | ListView lv = null; 208 | try { 209 | lv = getListView(); 210 | if (getActivity() == null || getActivity().isFinishing() || lv == null) { 211 | return; 212 | } 213 | } catch (IllegalStateException e) { 214 | return; 215 | } 216 | for (IntValue intValue : Py.range(lv.getChildCount())) { 217 | View child = lv.getChildAt(intValue.value); 218 | if (intValue.value == mActivatedPosition) { 219 | Ui.findView(child, R.id.active_core_stripe).setVisibility(View.VISIBLE); 220 | child.setActivated(true); 221 | } else { 222 | Ui.findView(child, R.id.active_core_stripe).setVisibility(View.INVISIBLE); 223 | } 224 | } 225 | } 226 | }); 227 | } 228 | 229 | class DevicesUpdatedReceiver extends BroadcastReceiver { 230 | 231 | IntentFilter getIntentFilter() { 232 | return new IntentFilter(ApiFacade.BROADCAST_DEVICES_UPDATED); 233 | } 234 | 235 | @Override 236 | public void onReceive(Context context, Intent intent) { 237 | updateDevicesList(); 238 | } 239 | } 240 | 241 | } 242 | -------------------------------------------------------------------------------- /SparkCore/src/io/spark/core/android/ui/corelist/DeviceListAdapter.java: -------------------------------------------------------------------------------- 1 | package io.spark.core.android.ui.corelist; 2 | 3 | import io.spark.core.android.R; 4 | import io.spark.core.android.cloud.api.Device; 5 | import io.spark.core.android.ui.util.Ui; 6 | 7 | import org.solemnsilence.util.TLog; 8 | 9 | import android.content.Context; 10 | import android.graphics.drawable.Drawable; 11 | import android.graphics.drawable.GradientDrawable; 12 | import android.view.LayoutInflater; 13 | import android.view.View; 14 | import android.view.ViewGroup; 15 | import android.widget.ArrayAdapter; 16 | import android.widget.TextView; 17 | 18 | 19 | public class DeviceListAdapter extends ArrayAdapter { 20 | 21 | Device selectedForPopupActions; 22 | 23 | public DeviceListAdapter(Context ctx) { 24 | super(ctx, R.layout.core_row); 25 | } 26 | 27 | @Override 28 | public View getView(int position, View convertView, ViewGroup parent) { 29 | if (convertView == null) { 30 | LayoutInflater inflater = (LayoutInflater) getContext().getSystemService( 31 | Context.LAYOUT_INFLATER_SERVICE); 32 | convertView = inflater.inflate(R.layout.core_row, parent, false); 33 | 34 | ViewHolder holder = new ViewHolder(); 35 | holder.activeStripe = Ui.findView(convertView, R.id.active_core_stripe); 36 | holder.coreName = Ui.findView(convertView, R.id.core_name); 37 | 38 | convertView.setTag(holder); 39 | } 40 | 41 | ViewHolder holder = (ViewHolder) convertView.getTag(); 42 | final Device device = getItem(position); 43 | 44 | holder.coreName.setText(device.name); 45 | 46 | Drawable[] compoundDrawables = holder.coreName.getCompoundDrawables(); 47 | GradientDrawable dotDrawable = (GradientDrawable) compoundDrawables[0]; 48 | dotDrawable.setColor(device.color); 49 | 50 | holder.activeStripe.setBackgroundColor(device.color); 51 | if (convertView.isActivated()) { 52 | holder.activeStripe.setVisibility(View.VISIBLE); 53 | } else { 54 | holder.activeStripe.setVisibility(View.INVISIBLE); 55 | } 56 | 57 | return convertView; 58 | } 59 | 60 | 61 | static class ViewHolder { 62 | 63 | TextView coreName; 64 | View activeStripe; 65 | } 66 | 67 | 68 | static final TLog log = new TLog(DeviceListAdapter.class); 69 | 70 | } 71 | -------------------------------------------------------------------------------- /SparkCore/src/io/spark/core/android/ui/smartconfig/NamingActivity.java: -------------------------------------------------------------------------------- 1 | package io.spark.core.android.ui.smartconfig; 2 | 3 | import io.spark.core.android.R; 4 | import io.spark.core.android.ui.BaseActivity; 5 | import io.spark.core.android.ui.corelist.CoreListActivity; 6 | import io.spark.core.android.ui.util.Ui; 7 | import android.content.Intent; 8 | import android.os.Bundle; 9 | import android.support.v4.app.NavUtils; 10 | import android.view.MenuItem; 11 | import android.widget.TextView; 12 | 13 | 14 | public class NamingActivity extends BaseActivity { 15 | 16 | 17 | @Override 18 | protected void onCreate(Bundle savedInstanceState) { 19 | super.onCreate(savedInstanceState); 20 | setContentView(R.layout.activity_smart_config); 21 | getFragmentManager() 22 | .beginTransaction() 23 | .add(R.id.smart_config_frag, new NamingFragment()) 24 | .commit(); 25 | 26 | TextView finePrint = Ui.findView(this, R.id.fine_print); 27 | finePrint.setText(R.string.you_can_change_these_names_at_any_point); 28 | } 29 | 30 | @Override 31 | public boolean onOptionsItemSelected(MenuItem item) { 32 | switch (item.getItemId()) { 33 | case android.R.id.home: 34 | NavUtils.navigateUpTo(this, new Intent(this, CoreListActivity.class)); 35 | return true; 36 | } 37 | return super.onOptionsItemSelected(item); 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /SparkCore/src/io/spark/core/android/ui/smartconfig/NamingFragment.java: -------------------------------------------------------------------------------- 1 | package io.spark.core.android.ui.smartconfig; 2 | 3 | import io.spark.core.android.R; 4 | import io.spark.core.android.app.DeviceState; 5 | import io.spark.core.android.cloud.ApiFacade; 6 | import io.spark.core.android.cloud.api.Device; 7 | import io.spark.core.android.cloud.requestservice.SimpleSparkApiService; 8 | import io.spark.core.android.smartconfig.SmartConfigService; 9 | import io.spark.core.android.smartconfig.SmartConfigState; 10 | import io.spark.core.android.ui.BaseFragment; 11 | import io.spark.core.android.ui.corelist.CoreListActivity; 12 | import io.spark.core.android.ui.util.NamingHelper; 13 | import io.spark.core.android.ui.util.Ui; 14 | import io.spark.core.android.util.CoreNameGenerator; 15 | 16 | import java.util.Iterator; 17 | 18 | import org.apache.http.HttpStatus; 19 | import org.solemnsilence.util.TLog; 20 | 21 | import android.content.BroadcastReceiver; 22 | import android.content.Context; 23 | import android.content.Intent; 24 | import android.content.IntentFilter; 25 | import android.content.res.Resources; 26 | import android.os.Bundle; 27 | import android.view.KeyEvent; 28 | import android.view.View; 29 | import android.view.inputmethod.EditorInfo; 30 | import android.widget.Button; 31 | import android.widget.EditText; 32 | import android.widget.TextView; 33 | 34 | 35 | public class NamingFragment extends BaseFragment { 36 | 37 | private static final TLog log = new TLog(NamingFragment.class); 38 | 39 | 40 | private static final String STATE_CURRENT_RENAME_ATTEMPT_DEVICE_ID = "STATE_CURRENT_RENAME_ATTEMPT_DEVICE_ID"; 41 | 42 | EditText coreNameText; 43 | Button okButton; 44 | 45 | SmartConfigFoundSomethingReceiver foundSomethingReceiver; 46 | MoreCoresClaimedReceiver moreCoresClaimedReceiver; 47 | 48 | String currentRenameAttemptHexId; 49 | 50 | 51 | @Override 52 | public int getContentViewLayoutId() { 53 | return R.layout.fragment_naming; 54 | } 55 | 56 | @Override 57 | public void onCreate(Bundle savedInstanceState) { 58 | super.onCreate(savedInstanceState); 59 | if (savedInstanceState != null 60 | && savedInstanceState.containsKey(STATE_CURRENT_RENAME_ATTEMPT_DEVICE_ID)) { 61 | currentRenameAttemptHexId = savedInstanceState.getString( 62 | STATE_CURRENT_RENAME_ATTEMPT_DEVICE_ID); 63 | } 64 | foundSomethingReceiver = new SmartConfigFoundSomethingReceiver(); 65 | moreCoresClaimedReceiver = new MoreCoresClaimedReceiver(); 66 | } 67 | 68 | @Override 69 | public void onViewCreated(View view, Bundle savedInstanceState) { 70 | super.onViewCreated(view, savedInstanceState); 71 | 72 | coreNameText = Ui.findView(this, R.id.core_name); 73 | coreNameText.setOnEditorActionListener(new TextView.OnEditorActionListener() { 74 | 75 | @Override 76 | public boolean onEditorAction(TextView textView, int id, KeyEvent keyEvent) { 77 | if (id == R.id.name_core || id == EditorInfo.IME_NULL) { 78 | nameCurrentCore(); 79 | return true; 80 | } 81 | return false; 82 | } 83 | }); 84 | okButton = Ui.findView(this, R.id.ok_button); 85 | okButton.setOnClickListener(new View.OnClickListener() { 86 | 87 | @Override 88 | public void onClick(View v) { 89 | nameCurrentCore(); 90 | } 91 | }); 92 | 93 | if (currentRenameAttemptHexId == null) { 94 | startNaming(); 95 | } 96 | } 97 | 98 | @Override 99 | public void onSaveInstanceState(Bundle outState) { 100 | super.onSaveInstanceState(outState); 101 | outState.putString(STATE_CURRENT_RENAME_ATTEMPT_DEVICE_ID, currentRenameAttemptHexId); 102 | } 103 | 104 | @Override 105 | public void onStart() { 106 | super.onStart(); 107 | broadcastMgr.registerReceiver(foundSomethingReceiver, foundSomethingReceiver.getFilter()); 108 | broadcastMgr.registerReceiver(moreCoresClaimedReceiver, 109 | moreCoresClaimedReceiver.getFilter()); 110 | setHeaderCoreCountMessage(); 111 | } 112 | 113 | @Override 114 | public void onStop() { 115 | broadcastMgr.unregisterReceiver(foundSomethingReceiver); 116 | broadcastMgr.unregisterReceiver(moreCoresClaimedReceiver); 117 | SmartConfigService.stopSmartConfig(getActivity()); 118 | super.onStop(); 119 | } 120 | 121 | private String getNextFoundId() { 122 | Iterator iter = SmartConfigState.getClaimedButPossiblyUnnamedDeviceIds().iterator(); 123 | if (iter.hasNext()) { 124 | return iter.next(); 125 | } else { 126 | return null; 127 | } 128 | } 129 | 130 | private void populateName() { 131 | Device device = DeviceState.getDeviceById(currentRenameAttemptHexId); 132 | String name = null; 133 | if (device != null) { 134 | // use existing name 135 | name = device.name; 136 | } 137 | 138 | // it could still be null if the device was never named 139 | if (name == null) { 140 | name = CoreNameGenerator.generateUniqueName(DeviceState.getExistingCoreNames()); 141 | } 142 | 143 | coreNameText.setText(name); 144 | } 145 | 146 | private void startNaming() { 147 | String nextId = getNextFoundId(); 148 | if (nextId == null) { 149 | stopNaming(); 150 | } else { 151 | currentRenameAttemptHexId = nextId; 152 | api.startSignalling(currentRenameAttemptHexId); 153 | showProgress(false); 154 | setHeaderCoreCountMessage(); 155 | populateName(); 156 | } 157 | } 158 | 159 | private void setHeaderCoreCountMessage() { 160 | int count = SmartConfigState.getClaimedButPossiblyUnnamedDeviceIds().size(); 161 | Resources res = getResources(); 162 | String letsNameCoresMsg = res.getQuantityString(R.plurals.foundCoresMsg, count, count); 163 | Ui.setText(this, R.id.header_text, letsNameCoresMsg); 164 | } 165 | 166 | private void nameCurrentCore() { 167 | String name = coreNameText.getText().toString(); 168 | Device existingDevice = DeviceState.getDeviceById(currentRenameAttemptHexId); 169 | 170 | // is this the name of an existing device? 171 | if (DeviceState.getExistingCoreNames().contains(name)) { 172 | // is it the current device? 173 | if (existingDevice == null || !name.equals(existingDevice.name)) { 174 | // No, it's not, which means this name belongs to another device 175 | // for this user, so it's a dupe name -- don't allow it. 176 | new NamingHelper(getActivity(), api).showDupeNameDialog(null); 177 | return; 178 | } 179 | } 180 | api.nameCore(currentRenameAttemptHexId, name); 181 | showProgress(true); 182 | } 183 | 184 | 185 | private void stopNaming() { 186 | Intent intent = new Intent(getActivity(), CoreListActivity.class) 187 | .putExtra(CoreListActivity.ARG_SELECT_DEVICE_ID, currentRenameAttemptHexId) 188 | .setFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK 189 | | Intent.FLAG_ACTIVITY_SINGLE_TOP 190 | | Intent.FLAG_ACTIVITY_CLEAR_TOP); 191 | startActivity(intent); 192 | getActivity().finish(); 193 | } 194 | 195 | private void showProgress(final boolean show) { 196 | showProgress(R.id.progress_indicator, show); 197 | okButton.setEnabled(!show); 198 | } 199 | 200 | private void namingRequestFinished(boolean previousWasSuccessful, String errorMsg) { 201 | if (SmartConfigState.getClaimedButPossiblyUnnamedDeviceIds().isEmpty()) { 202 | // we're done 203 | stopNaming(); 204 | return; 205 | } 206 | 207 | startNaming(); 208 | } 209 | 210 | 211 | class MoreCoresClaimedReceiver extends BroadcastReceiver { 212 | 213 | IntentFilter getFilter() { 214 | return new IntentFilter(ApiFacade.BROADCAST_CORE_CLAIMED); 215 | } 216 | 217 | @Override 218 | public void onReceive(Context context, Intent intent) { 219 | log.i("Received BROADCAST_CORE_CLAIMED."); 220 | setHeaderCoreCountMessage(); 221 | } 222 | } 223 | 224 | 225 | class SmartConfigFoundSomethingReceiver extends BroadcastReceiver { 226 | 227 | IntentFilter getFilter() { 228 | return new IntentFilter(ApiFacade.BROADCAST_CORE_NAMING_REQUEST_COMPLETE); 229 | } 230 | 231 | @Override 232 | public void onReceive(Context context, Intent intent) { 233 | log.i("Received BROADCAST_CORE_NAMING_REQUEST_COMPLETE."); 234 | namingRequestFinished((ApiFacade.getResultCode(intent) == HttpStatus.SC_OK), 235 | intent.getStringExtra(SimpleSparkApiService.EXTRA_ERROR_MSG)); 236 | } 237 | } 238 | 239 | } 240 | -------------------------------------------------------------------------------- /SparkCore/src/io/spark/core/android/ui/smartconfig/NoCoresFoundActivity.java: -------------------------------------------------------------------------------- 1 | package io.spark.core.android.ui.smartconfig; 2 | 3 | import io.spark.core.android.R; 4 | import io.spark.core.android.ui.BaseActivity; 5 | import io.spark.core.android.ui.util.Ui; 6 | import android.content.Intent; 7 | import android.os.Bundle; 8 | import android.support.v4.app.NavUtils; 9 | import android.view.MenuItem; 10 | import android.view.View; 11 | import android.widget.TextView; 12 | 13 | 14 | public class NoCoresFoundActivity extends BaseActivity { 15 | 16 | 17 | @Override 18 | protected void onCreate(Bundle savedInstanceState) { 19 | super.onCreate(savedInstanceState); 20 | setContentView(R.layout.activity_smart_config); 21 | View border = Ui.findView(this, R.id.header_border); 22 | border.setBackgroundColor(getResources().getColor(R.color.failure_red)); 23 | 24 | TextView finePrint = Ui.findView(this, R.id.fine_print); 25 | finePrint.setText(""); 26 | 27 | getFragmentManager() 28 | .beginTransaction() 29 | .add(R.id.smart_config_frag, new NoCoresFoundFragment()) 30 | .commit(); 31 | } 32 | 33 | @Override 34 | public boolean onOptionsItemSelected(MenuItem item) { 35 | switch (item.getItemId()) { 36 | case android.R.id.home: 37 | NavUtils.navigateUpTo(this, new Intent(this, SmartConfigActivity.class)); 38 | return true; 39 | } 40 | return super.onOptionsItemSelected(item); 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /SparkCore/src/io/spark/core/android/ui/smartconfig/NoCoresFoundFragment.java: -------------------------------------------------------------------------------- 1 | package io.spark.core.android.ui.smartconfig; 2 | 3 | import io.spark.core.android.R; 4 | import io.spark.core.android.app.DeviceState; 5 | import io.spark.core.android.ui.BaseFragment; 6 | import io.spark.core.android.ui.corelist.CoreListActivity; 7 | import io.spark.core.android.ui.util.Ui; 8 | import android.content.Intent; 9 | import android.os.Bundle; 10 | import android.support.v4.app.NavUtils; 11 | import android.view.View; 12 | import android.view.View.OnClickListener; 13 | import android.widget.Button; 14 | 15 | 16 | public class NoCoresFoundFragment extends BaseFragment { 17 | 18 | 19 | @Override 20 | public int getContentViewLayoutId() { 21 | return R.layout.fragment_no_cores_found; 22 | } 23 | 24 | 25 | @Override 26 | public void onViewCreated(View view, Bundle savedInstanceState) { 27 | super.onViewCreated(view, savedInstanceState); 28 | 29 | Button tryAgain = Ui.findView(this, R.id.try_again_button); 30 | tryAgain.setOnClickListener(new OnClickListener() { 31 | 32 | @Override 33 | public void onClick(View v) { 34 | getActivity().startActivity(new Intent(getActivity(), SmartConfigActivity.class)); 35 | getActivity().finish(); 36 | } 37 | }); 38 | 39 | Button continueAnyway = Ui.findView(this, R.id.continue_anyway_button); 40 | if (DeviceState.getKnownDevices().isEmpty()) { 41 | continueAnyway.setVisibility(View.GONE); 42 | } else { 43 | continueAnyway.setOnClickListener(new OnClickListener() { 44 | 45 | @Override 46 | public void onClick(View v) { 47 | NavUtils.navigateUpTo(getActivity(), 48 | new Intent(getActivity(), CoreListActivity.class)); 49 | } 50 | }); 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /SparkCore/src/io/spark/core/android/ui/smartconfig/SmartConfigActivity.java: -------------------------------------------------------------------------------- 1 | package io.spark.core.android.ui.smartconfig; 2 | 3 | import io.spark.core.android.R; 4 | import io.spark.core.android.ui.BaseActivity; 5 | import io.spark.core.android.ui.corelist.CoreListActivity; 6 | import android.content.Intent; 7 | import android.os.Bundle; 8 | import android.support.v4.app.NavUtils; 9 | import android.view.MenuItem; 10 | 11 | 12 | public class SmartConfigActivity extends BaseActivity { 13 | 14 | @Override 15 | protected void onCreate(Bundle savedInstanceState) { 16 | super.onCreate(savedInstanceState); 17 | setContentView(R.layout.activity_smart_config); 18 | getFragmentManager() 19 | .beginTransaction() 20 | .add(R.id.smart_config_frag, new SmartConfigFragment()) 21 | .commit(); 22 | } 23 | 24 | 25 | @Override 26 | public boolean onOptionsItemSelected(MenuItem item) { 27 | switch (item.getItemId()) { 28 | case android.R.id.home: 29 | NavUtils.navigateUpTo(this, new Intent(this, CoreListActivity.class)); 30 | return true; 31 | } 32 | return super.onOptionsItemSelected(item); 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /SparkCore/src/io/spark/core/android/ui/tinker/BgColorLinearLayout.java: -------------------------------------------------------------------------------- 1 | package io.spark.core.android.ui.tinker; 2 | 3 | import android.content.Context; 4 | import android.graphics.drawable.ColorDrawable; 5 | import android.graphics.drawable.Drawable; 6 | import android.util.AttributeSet; 7 | import android.widget.LinearLayout; 8 | 9 | 10 | public class BgColorLinearLayout extends LinearLayout { 11 | 12 | public BgColorLinearLayout(Context context) { 13 | super(context); 14 | } 15 | 16 | public BgColorLinearLayout(Context context, AttributeSet attrs) { 17 | super(context, attrs); 18 | } 19 | 20 | public BgColorLinearLayout(Context context, AttributeSet attrs, int defStyle) { 21 | super(context, attrs, defStyle); 22 | } 23 | 24 | public int getBackgroundColor() { 25 | Drawable d = getBackground(); 26 | if (d == null || !(d instanceof ColorDrawable)) { 27 | return 0x00000000; 28 | } else { 29 | return ((ColorDrawable) getBackground()).getColor(); 30 | } 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /SparkCore/src/io/spark/core/android/ui/tinker/DigitalValue.java: -------------------------------------------------------------------------------- 1 | package io.spark.core.android.ui.tinker; 2 | 3 | public enum DigitalValue { 4 | 5 | HIGH(1), 6 | LOW(0), 7 | NONE(-1); 8 | 9 | 10 | private final int intValue; 11 | 12 | private DigitalValue(int intValue) { 13 | this.intValue = intValue; 14 | } 15 | 16 | 17 | public int asInt() { 18 | return intValue; 19 | } 20 | 21 | 22 | public static DigitalValue fromInt(int value) { 23 | switch (value) { 24 | case 1: 25 | return HIGH; 26 | case 0: 27 | return LOW; 28 | default: 29 | return NONE; 30 | } 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /SparkCore/src/io/spark/core/android/ui/tinker/PinAction.java: -------------------------------------------------------------------------------- 1 | package io.spark.core.android.ui.tinker; 2 | 3 | public enum PinAction { 4 | 5 | ANALOG_READ, 6 | ANALOG_WRITE, 7 | DIGITAL_READ, 8 | DIGITAL_WRITE, 9 | NONE; 10 | } 11 | -------------------------------------------------------------------------------- /SparkCore/src/io/spark/core/android/ui/tinker/PinType.java: -------------------------------------------------------------------------------- 1 | package io.spark.core.android.ui.tinker; 2 | 3 | public enum PinType { 4 | A, 5 | D; 6 | } 7 | -------------------------------------------------------------------------------- /SparkCore/src/io/spark/core/android/ui/tinker/ReversedProgressBar.java: -------------------------------------------------------------------------------- 1 | package io.spark.core.android.ui.tinker; 2 | 3 | import android.content.Context; 4 | import android.graphics.Canvas; 5 | import android.util.AttributeSet; 6 | import android.widget.ProgressBar; 7 | 8 | 9 | public class ReversedProgressBar extends ProgressBar { 10 | 11 | public ReversedProgressBar(Context context, AttributeSet attrs) { 12 | super(context, attrs); 13 | } 14 | 15 | protected void onDraw(Canvas c) { 16 | float px = this.getWidth() / 2.0f; 17 | float py = this.getHeight() / 2.0f; 18 | 19 | c.scale(-1, 1, px, py); 20 | 21 | super.onDraw(c); 22 | } 23 | // 24 | // public boolean onTouchEvent(MotionEvent event) { 25 | // event.setLocation(this.getWidth() - event.getX(), event.getY()); 26 | // return super.onTouchEvent(event); 27 | // } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /SparkCore/src/io/spark/core/android/ui/tinker/ReversedSeekBar.java: -------------------------------------------------------------------------------- 1 | package io.spark.core.android.ui.tinker; 2 | 3 | import android.content.Context; 4 | import android.graphics.Canvas; 5 | import android.util.AttributeSet; 6 | import android.view.MotionEvent; 7 | import android.widget.SeekBar; 8 | 9 | 10 | public class ReversedSeekBar extends SeekBar { 11 | 12 | public ReversedSeekBar(Context context, AttributeSet attrs) { 13 | super(context, attrs); 14 | } 15 | 16 | protected void onDraw(Canvas c) { 17 | float px = this.getWidth() / 2.0f; 18 | float py = this.getHeight() / 2.0f; 19 | 20 | c.scale(-1, 1, px, py); 21 | 22 | super.onDraw(c); 23 | } 24 | 25 | public boolean onTouchEvent(MotionEvent event) { 26 | event.setLocation(this.getWidth() - event.getX(), event.getY()); 27 | return super.onTouchEvent(event); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /SparkCore/src/io/spark/core/android/ui/util/NamingHelper.java: -------------------------------------------------------------------------------- 1 | package io.spark.core.android.ui.util; 2 | 3 | import io.spark.core.android.R; 4 | import io.spark.core.android.app.DeviceState; 5 | import io.spark.core.android.cloud.ApiFacade; 6 | import io.spark.core.android.cloud.api.Device; 7 | import io.spark.core.android.util.CoreNameGenerator; 8 | import android.app.Activity; 9 | import android.app.AlertDialog; 10 | import android.content.DialogInterface; 11 | import android.content.DialogInterface.OnClickListener; 12 | import android.content.Intent; 13 | import android.support.v4.content.LocalBroadcastManager; 14 | import android.view.View; 15 | import android.widget.EditText; 16 | 17 | 18 | public class NamingHelper { 19 | 20 | public final static String BROADCAST_NEW_NAME_FOUND = "BROADCAST_NEW_NAME_FOUND"; 21 | public final static String EXTRA_NEW_NAME = "EXTRA_NEW_NAME"; 22 | 23 | 24 | private final Activity activity; 25 | private final ApiFacade api; 26 | private final LocalBroadcastManager broadcastMgr; 27 | 28 | public NamingHelper(Activity activity, ApiFacade api) { 29 | this.activity = activity; 30 | this.api = api; 31 | this.broadcastMgr = LocalBroadcastManager.getInstance(activity.getApplicationContext()); 32 | } 33 | 34 | public void renameCore(Device device, String newName, Runnable runOnDupeName) { 35 | if (DeviceState.getExistingCoreNames().contains(newName) && !newName.equals(device.name)) { 36 | showDupeNameDialog(runOnDupeName); 37 | } else { 38 | Intent intent = new Intent(BROADCAST_NEW_NAME_FOUND). 39 | putExtra(EXTRA_NEW_NAME, newName); 40 | broadcastMgr.sendBroadcast(intent); 41 | DeviceState.renameDevice(device.id, newName); 42 | api.nameCore(device.id, newName); 43 | } 44 | } 45 | 46 | public void showRenameDialog(final Device device) { 47 | String suggestedName = CoreNameGenerator.generateUniqueName( 48 | DeviceState.getExistingCoreNames()); 49 | 50 | View dialogRoot = activity.getLayoutInflater().inflate(R.layout.dialog_rename, null); 51 | final EditText nameView = (EditText) dialogRoot.findViewById(R.id.new_name); 52 | nameView.setText(suggestedName); 53 | 54 | new AlertDialog.Builder(activity) 55 | .setView(dialogRoot) 56 | .setTitle(R.string.rename_your_core) 57 | .setPositiveButton(R.string.rename, new OnClickListener() { 58 | 59 | @Override 60 | public void onClick(DialogInterface dialog, int which) { 61 | String newName = nameView.getText().toString(); 62 | dialog.dismiss(); 63 | Runnable onDupeName = new Runnable() { 64 | 65 | @Override 66 | public void run() { 67 | showRenameDialog(device); 68 | } 69 | 70 | }; 71 | renameCore(device, newName, onDupeName); 72 | } 73 | }) 74 | .setNegativeButton(R.string.cancel, new OnClickListener() { 75 | 76 | @Override 77 | public void onClick(DialogInterface dialog, int which) { 78 | dialog.dismiss(); 79 | } 80 | }) 81 | .create() 82 | .show(); 83 | } 84 | 85 | 86 | public void showDupeNameDialog(final Runnable runOnDupeName) { 87 | new AlertDialog.Builder(activity) 88 | .setMessage(R.string.sorry_you_ve_already_got_a_core_by_that_name_try_another_one_) 89 | .setPositiveButton(R.string.ok, new OnClickListener() { 90 | 91 | @Override 92 | public void onClick(DialogInterface dialog, int which) { 93 | dialog.dismiss(); 94 | if (runOnDupeName != null) { 95 | runOnDupeName.run(); 96 | } 97 | } 98 | }) 99 | .create() 100 | .show(); 101 | } 102 | 103 | 104 | } 105 | -------------------------------------------------------------------------------- /SparkCore/src/io/spark/core/android/ui/util/Ui.java: -------------------------------------------------------------------------------- 1 | package io.spark.core.android.ui.util; 2 | 3 | import android.app.Activity; 4 | import android.app.Dialog; 5 | import android.app.Fragment; 6 | import android.text.Html; 7 | import android.view.View; 8 | import android.widget.TextView; 9 | 10 | 11 | public class Ui { 12 | 13 | @SuppressWarnings("unchecked") 14 | public static T findView(Activity activity, int id) { 15 | return (T) activity.findViewById(id); 16 | } 17 | 18 | @SuppressWarnings("unchecked") 19 | public static T findView(View enclosingView, int id) { 20 | return (T) enclosingView.findViewById(id); 21 | } 22 | 23 | @SuppressWarnings("unchecked") 24 | public static T findView(Fragment frag, int id) { 25 | return (T) frag.getActivity().findViewById(id); 26 | } 27 | 28 | @SuppressWarnings("unchecked") 29 | public static T findView(Dialog dialog, int id) { 30 | return (T) dialog.findViewById(id); 31 | } 32 | 33 | @SuppressWarnings("unchecked") 34 | public static T findFrag(Activity activity, int id) { 35 | return (T) activity.getFragmentManager().findFragmentById(id); 36 | } 37 | 38 | 39 | public static TextView setText(Activity activity, int textViewId, CharSequence text) { 40 | TextView textView = findView(activity, textViewId); 41 | textView.setText(text); 42 | return textView; 43 | } 44 | 45 | public static TextView setText(Fragment frag, int textViewId, CharSequence text) { 46 | TextView textView = findView(frag, textViewId); 47 | textView.setText(text); 48 | return textView; 49 | } 50 | 51 | public static String getText(Activity activity, int textViewId, boolean trim) { 52 | TextView textView = findView(activity, textViewId); 53 | String text = textView.getText().toString(); 54 | return trim ? text.trim() : text; 55 | } 56 | 57 | public static String getText(Fragment frag, int textViewId, boolean trim) { 58 | TextView textView = findView(frag, textViewId); 59 | String text = textView.getText().toString(); 60 | return trim ? text.trim() : text; 61 | } 62 | 63 | public static TextView setTextFromHtml(Activity activity, int textViewId, int htmlStringId) { 64 | TextView tv = Ui.findView(activity, textViewId); 65 | tv.setText(Html.fromHtml(activity.getString(htmlStringId)), TextView.BufferType.SPANNABLE); 66 | return tv; 67 | } 68 | 69 | } 70 | -------------------------------------------------------------------------------- /SparkCore/src/io/spark/core/android/util/CoreNameGenerator.java: -------------------------------------------------------------------------------- 1 | package io.spark.core.android.util; 2 | 3 | import java.util.Random; 4 | import java.util.Set; 5 | 6 | 7 | public class CoreNameGenerator { 8 | 9 | private static final Random random = new Random(); 10 | 11 | private static final String[] TROCHEES = new String[] { "aardvark", "bacon", "badger", "banjo", 12 | "bobcat", "boomer", "captain", "chicken", "cowboy", "cracker", "cranky", "crazy", 13 | "dentist", "doctor", "dozen", "easter", "ferret", "gerbil", "hacker", "hamster", 14 | "hindu", "hobo", "hoosier", "hunter", "jester", "jetpack", "kitty", "laser", "lawyer", 15 | "mighty", "monkey", "morphing", "mutant", "narwhal", "ninja", "normal", "penguin", 16 | "pirate", "pizza", "plumber", "power", "puppy", "ranger", "raptor", "robot", "scraper", 17 | "scrapple", "station", "tasty", "trochee", "turkey", "turtle", "vampire", "wombat", 18 | "zombie" }; 19 | 20 | 21 | public static String generateUniqueName(Set existingNames) { 22 | String uniqueName = null; 23 | while (uniqueName == null) { 24 | String part1 = getRandomName(); 25 | String part2 = getRandomName(); 26 | String candidate = part1 + "_" + part2; 27 | if (!existingNames.contains(candidate) && !part1.equals(part2)) { 28 | uniqueName = candidate; 29 | } 30 | } 31 | return uniqueName; 32 | } 33 | 34 | private static String getRandomName() { 35 | int randomIndex = random.nextInt(TROCHEES.length); 36 | return TROCHEES[randomIndex]; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /SparkCore/src/io/spark/core/android/util/NetConnectionHelper.java: -------------------------------------------------------------------------------- 1 | package io.spark.core.android.util; 2 | 3 | import static org.solemnsilence.util.Py.truthy; 4 | import android.content.Context; 5 | import android.net.ConnectivityManager; 6 | import android.net.DhcpInfo; 7 | import android.net.NetworkInfo; 8 | import android.net.wifi.WifiInfo; 9 | import android.net.wifi.WifiManager; 10 | import android.os.Build; 11 | import android.text.format.Formatter; 12 | 13 | 14 | public class NetConnectionHelper { 15 | 16 | final Context context; 17 | final WifiManager wifiManager; 18 | final ConnectivityManager connManager; 19 | 20 | public NetConnectionHelper(Context context) { 21 | // avoid retaining any context but the application context unless truly 22 | // necessary 23 | this.context = context.getApplicationContext(); 24 | this.wifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE); 25 | this.connManager = (ConnectivityManager) context 26 | .getSystemService(Context.CONNECTIVITY_SERVICE); 27 | } 28 | 29 | public String getSSID() { 30 | WifiInfo wifiInfo = getWifiInfo(); 31 | if (wifiInfo == null || !isConnectedViaWifi()) { 32 | return ""; 33 | } 34 | 35 | return removeQuotes(wifiInfo.getSSID()); 36 | } 37 | 38 | public boolean hasDataConnection() { 39 | NetworkInfo activeNetworkInfo = connManager.getActiveNetworkInfo(); 40 | if (activeNetworkInfo != null) { 41 | return activeNetworkInfo.isConnected(); 42 | } else { 43 | return false; 44 | } 45 | } 46 | 47 | public boolean isConnectedViaWifi() { 48 | NetworkInfo networkInfo = connManager.getNetworkInfo(ConnectivityManager.TYPE_WIFI); 49 | if (networkInfo == null) { 50 | return false; 51 | } else { 52 | return networkInfo.isConnected(); 53 | } 54 | } 55 | 56 | private WifiInfo getWifiInfo() { 57 | return wifiManager.getConnectionInfo(); 58 | } 59 | 60 | public String getGatewayIp() { 61 | DhcpInfo dhcpInfo = wifiManager.getDhcpInfo(); 62 | if (dhcpInfo == null) { 63 | return null; 64 | } else { 65 | return Formatter.formatIpAddress(dhcpInfo.gateway); 66 | } 67 | } 68 | 69 | // in Jellybean, SSIDs can have quotes around them 70 | private static String removeQuotes(String ssid) { 71 | if (!truthy(ssid)) { 72 | return ""; 73 | } 74 | 75 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { 76 | if (ssid.startsWith("\"") && ssid.endsWith("\"")) { 77 | ssid = ssid.substring(1, ssid.length() - 1); 78 | } 79 | } 80 | return ssid; 81 | } 82 | 83 | 84 | } 85 | -------------------------------------------------------------------------------- /SparkCore/src/io/spark/core/android/util/Strings.java: -------------------------------------------------------------------------------- 1 | package io.spark.core.android.util; 2 | 3 | 4 | public class Strings { 5 | 6 | public static String getRedacted(String value) { 7 | return (value == null) 8 | ? "" 9 | : String.format("[ REDACTED (length: %d) ]", value.length()); 10 | } 11 | 12 | } 13 | -------------------------------------------------------------------------------- /SparkCore/src/org/solemnsilence/util/EZ.java: -------------------------------------------------------------------------------- 1 | package org.solemnsilence.util; 2 | 3 | import android.os.Handler; 4 | import android.os.Looper; 5 | 6 | 7 | // Just some painkiller/"macro" type methods that are easier to remember than 8 | // what they implement. 9 | public class EZ { 10 | 11 | public static void runOnMainThread(Runnable runnable) { 12 | new Handler(Looper.getMainLooper()).post(runnable); 13 | } 14 | 15 | 16 | public static void runOnMainThreadDelayed(Runnable runnable, long delayMillis) { 17 | new Handler(Looper.getMainLooper()).postDelayed(runnable, delayMillis); 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /SparkCore/src/org/solemnsilence/util/TLog.java: -------------------------------------------------------------------------------- 1 | package org.solemnsilence.util; 2 | 3 | import android.util.Log; 4 | 5 | 6 | // "Tagged logger" -- stop having to include the log tag every time (and 7 | // [kind of] abstract away using Android's logger directly) 8 | public class TLog { 9 | 10 | private final String tag; 11 | 12 | 13 | public TLog(String tag) { 14 | this.tag = tag; 15 | } 16 | 17 | public TLog(Class clazz) { 18 | this.tag = clazz.getSimpleName(); 19 | } 20 | 21 | public void e(String msg) { 22 | Log.e(tag, msg); 23 | } 24 | 25 | public void e(String msg, Throwable tr) { 26 | Log.e(tag, msg, tr); 27 | } 28 | 29 | 30 | public void w(String msg) { 31 | Log.w(tag, msg); 32 | } 33 | 34 | public void w(String msg, Throwable tr) { 35 | Log.w(tag, msg, tr); 36 | } 37 | 38 | 39 | public void i(String msg) { 40 | Log.i(tag, msg); 41 | } 42 | 43 | public void i(String msg, Throwable tr) { 44 | Log.i(tag, msg, tr); 45 | } 46 | 47 | 48 | public void d(String msg) { 49 | Log.d(tag, msg); 50 | } 51 | 52 | public void d(String msg, Throwable tr) { 53 | Log.d(tag, msg, tr); 54 | } 55 | 56 | 57 | public void v(String msg) { 58 | Log.v(tag, msg); 59 | } 60 | 61 | public void v(String msg, Throwable tr) { 62 | Log.v(tag, msg, tr); 63 | } 64 | 65 | 66 | public void wtf(String msg) { 67 | Log.wtf(tag, msg); 68 | } 69 | 70 | public void wtf(String msg, Throwable tr) { 71 | Log.wtf(tag, msg, tr); 72 | } 73 | 74 | } 75 | -------------------------------------------------------------------------------- /SparkCore/src/org/solemnsilence/util/Toaster.java: -------------------------------------------------------------------------------- 1 | package org.solemnsilence.util; 2 | 3 | import android.app.Fragment; 4 | import android.content.Context; 5 | import android.widget.Toast; 6 | 7 | 8 | public class Toaster { 9 | 10 | public static void s(Context ctx, String msg) { 11 | Toast.makeText(ctx, msg, Toast.LENGTH_SHORT).show(); 12 | } 13 | 14 | public static void l(Context ctx, String msg) { 15 | Toast.makeText(ctx, msg, Toast.LENGTH_LONG).show(); 16 | } 17 | 18 | public static void s(Fragment frag, String msg) { 19 | s(frag.getActivity(), msg); 20 | } 21 | 22 | public static void l(Fragment frag, String msg) { 23 | l(frag.getActivity(), msg); 24 | } 25 | 26 | 27 | } 28 | --------------------------------------------------------------------------------