├── LICENSE ├── README.md ├── android ├── .gitignore ├── DashboardRedux │ ├── DashboardRedux.iml │ ├── build.gradle │ ├── libs │ │ ├── README.txt │ │ └── RootTools-3.4.jar │ └── src │ │ └── main │ │ ├── AndroidManifest.xml │ │ ├── assets │ │ ├── README.txt │ │ └── dashboard.pbw │ │ ├── java │ │ ├── activity │ │ │ ├── AboutActivity.java │ │ │ ├── Landing.java │ │ │ └── LogViewer.java │ │ ├── adapter │ │ │ └── ToggleSelector.java │ │ ├── background │ │ │ ├── App.java │ │ │ ├── BootPackageChangeReceiver.java │ │ │ ├── DeviceAdmin.java │ │ │ ├── FindPhone.java │ │ │ ├── HandlerService.java │ │ │ ├── KeepAliveReceiver.java │ │ │ ├── PSLCallback.java │ │ │ ├── PebbleReceiver.java │ │ │ └── SignalListener.java │ │ ├── cl_toolkit │ │ │ ├── Contact.java │ │ │ ├── FileMap.java │ │ │ ├── Logger.java │ │ │ ├── Meta.java │ │ │ ├── Platform.java │ │ │ ├── Radios.java │ │ │ ├── Root.java │ │ │ ├── Storage.java │ │ │ ├── UserInterface.java │ │ │ └── Web.java │ │ ├── config │ │ │ ├── Build.java │ │ │ ├── Keys.java │ │ │ └── Runtime.java │ │ ├── fragment │ │ │ └── About.java │ │ └── util │ │ │ ├── PebbleUtils.java │ │ │ └── VersionCheck.java │ │ └── res │ │ ├── anim │ │ └── slide_in_bottom.xml │ │ ├── drawable-hdpi │ │ └── ic_launcher_notif.png │ │ ├── drawable-mdpi │ │ └── ic_launcher_notif.png │ │ ├── drawable-xhdpi │ │ ├── ap.png │ │ ├── brightness.png │ │ ├── bt.png │ │ ├── button.9.png │ │ ├── data.png │ │ ├── empty.png │ │ ├── framed.png │ │ ├── ic_launcher.png │ │ ├── ic_launcher_notif.png │ │ ├── ic_launcher_notif_large.png │ │ ├── lock.png │ │ ├── loud.png │ │ ├── mailsrc.png │ │ ├── paypal.png │ │ ├── phone.png │ │ ├── priority.png │ │ ├── questionmark.png │ │ ├── rate.png │ │ ├── silent.png │ │ ├── sync.png │ │ ├── tweet.png │ │ ├── vibrate.png │ │ ├── wifi.png │ │ └── wordpress.png │ │ ├── layout │ │ ├── activity_landing.xml │ │ ├── activity_log_viewer.xml │ │ ├── settings_fragment.xml │ │ └── toggle_spinner_item.xml │ │ ├── menu │ │ ├── menu_landing.xml │ │ └── menu_log_viewer.xml │ │ ├── values-v11 │ │ └── styles.xml │ │ ├── values-v14 │ │ └── styles.xml │ │ ├── values-v21 │ │ └── styles.xml │ │ ├── values-w820dp │ │ └── dimens.xml │ │ ├── values │ │ ├── colours.xml │ │ ├── dimens.xml │ │ ├── strings.xml │ │ └── styles.xml │ │ └── xml │ │ ├── actionbar_tab_indicator.xml │ │ ├── admin.xml │ │ └── preferences.xml ├── PebbleHomescreen.iml ├── android.iml ├── build.gradle ├── gradle.properties ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle ├── assets ├── listing │ ├── app_logo.png │ ├── blogbanner.png │ ├── icon-large.png │ ├── icon-small.png │ ├── icons-original-size │ │ ├── ap.png │ │ ├── brightness.png │ │ ├── bt.png │ │ ├── data.png │ │ ├── lock.png │ │ ├── loud.png │ │ ├── phone.png │ │ ├── priority.png │ │ ├── silent.png │ │ ├── sync.png │ │ ├── vibrate.png │ │ └── wifi.png │ ├── marketing-aplite.png │ ├── marketing-basalt.png │ ├── marketing-chalk.png │ ├── marketing-gplay.png │ ├── marketing-src-aplite.xcf │ ├── marketing-src-basalt.xcf │ ├── marketing-src-chalk.xcf │ └── marketing-src-gplay.xcf ├── releases │ ├── Dashboard-release-1.13.apk │ ├── Dashboard-release-1.14.apk │ ├── Dashboard-release-1.15.apk │ ├── Dashboard-release-1.16.apk │ ├── Dashboard-release-1.17.apk │ ├── Dashboard-release-1.19.apk │ ├── Dashboard-release-1.20.apk │ ├── Dashboard-release-3.0.apk │ ├── Dashboard-release-3.1.apk │ ├── Dashboard-release-3.2.apk │ ├── Dashboard-release-4.0.apk │ ├── Dashboard-release-4.1.apk │ ├── Dashboard-release-4.10.apk │ ├── Dashboard-release-4.11.apk │ ├── Dashboard-release-4.12.apk │ ├── Dashboard-release-4.14.apk │ ├── Dashboard-release-4.15.apk │ ├── Dashboard-release-4.16.apk │ ├── Dashboard-release-4.17.apk │ ├── Dashboard-release-4.2.apk │ ├── Dashboard-release-4.3.apk │ ├── Dashboard-release-4.4.apk │ ├── Dashboard-release-4.5.apk │ ├── Dashboard-release-4.6.apk │ ├── Dashboard-release-4.7.apk │ ├── Dashboard-release-4.8.apk │ ├── Dashboard-release-4.9.apk │ ├── dashboard-4.0.pbw │ ├── dashboard-4.1.pbw │ ├── dashboard-4.2.pbw │ ├── dashboard-4.3.pbw │ ├── dashboard-4.5.pbw │ ├── dashboard-4.6.pbw │ └── dashboard-4.8.pbw └── screenshots │ ├── Screenshot_20160504-164142.png │ ├── Screenshot_20160504-164150.png │ ├── aplite1.png │ ├── aplite2.png │ ├── aplite3.png │ ├── aplite4.png │ ├── basalt1.png │ ├── basalt2.png │ ├── basalt3.png │ ├── basalt4.png │ ├── chalk1.png │ ├── chalk2.png │ ├── chalk3.png │ └── chalk4.png └── pebble ├── package.json ├── resources ├── icons │ ├── ap.png │ ├── brightness.png │ ├── bt.png │ ├── data.png │ ├── lock.png │ ├── loud.png │ ├── phone.png │ ├── priority.png │ ├── silent.png │ ├── sync.png │ ├── vibrate.png │ └── wifi.png └── images │ ├── app_icon.png │ ├── down.png │ ├── logo.png │ ├── select.png │ ├── tick.png │ └── up.png ├── src └── c │ ├── config.h │ ├── lib │ └── new_number_window │ │ ├── new_number_window.c │ │ └── new_number_window.h │ ├── main.c │ ├── modules │ ├── comm.c │ ├── comm.h │ ├── data.c │ ├── data.h │ ├── icons.c │ ├── icons.h │ ├── sync_timer.c │ └── sync_timer.h │ ├── types.h │ ├── util │ ├── animation.c │ ├── animation.h │ ├── test.c │ └── test.h │ └── windows │ ├── dialog_window.c │ ├── dialog_window.h │ ├── list_window.c │ ├── list_window.h │ ├── schedule_window.c │ ├── schedule_window.h │ ├── splash_window.c │ ├── splash_window.h │ ├── unified_window.c │ ├── unified_window.h │ ├── wakeup_window.c │ └── wakeup_window.h └── wscript /LICENSE: -------------------------------------------------------------------------------- 1 | (The MIT License) 2 | 3 | Copyright (c) 2016 Chris Lewis 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | 'Software'), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 21 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 22 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # dashboard 2 | 3 | Source code for the Dashboard app. Includes code for both the Pebble watchapp and the Android companion app. 4 | 5 | See `assets/releases` for sideloadable Android and Pebble apps files. 6 | 7 | **Latest Versions** 8 | 9 | - Watchapp: 4.8 10 | 11 | - Android app: 4.16 12 | 13 | 14 | **Install** 15 | 16 | - [Appstore](https://apps.rebble.io/en_US/application/53ec8d840c3036447e000109) 17 | 18 | - [Google Play](https://play.google.com/store/apps/details?id=com.wordpress.ninedof.dashboard) 19 | 20 | 21 | > Don't try this at home! See [Dashboard API Status](https://github.com/C-D-Lewis/dashboard-api-status). 22 | 23 | -------------------------------------------------------------------------------- /android/.gitignore: -------------------------------------------------------------------------------- 1 | .gradle 2 | /local.properties 3 | /.idea/workspace.xml 4 | .DS_Store 5 | /.idea 6 | **/build/ -------------------------------------------------------------------------------- /android/DashboardRedux/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 29 5 | 6 | defaultConfig { 7 | applicationId "com.wordpress.ninedof.dashboard" 8 | minSdkVersion 18 9 | targetSdkVersion 29 10 | } 11 | 12 | buildTypes { 13 | release { 14 | minifyEnabled false 15 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt' 16 | } 17 | } 18 | sourceSets { main { java.srcDirs = ['src/main/java', 'src/main/java/receiver'] } } 19 | } 20 | 21 | dependencies { 22 | implementation 'androidx.legacy:legacy-support-v4:1.0.0' 23 | implementation 'androidx.cardview:cardview:1.0.0' 24 | implementation 'com.google.android.gms:play-services-location:17.1.0' 25 | implementation 'com.getpebble:pebblekit:3.0.0' 26 | compile files('libs/RootTools-3.4.jar') 27 | } 28 | -------------------------------------------------------------------------------- /android/DashboardRedux/libs/README.txt: -------------------------------------------------------------------------------- 1 | Stericson RootTools repo: https://github.com/Stericson/RootTools 2 | -------------------------------------------------------------------------------- /android/DashboardRedux/libs/RootTools-3.4.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/C-D-Lewis/dashboard/76439ff18b98bf13ec317ec0ab7913a3c546c41c/android/DashboardRedux/libs/RootTools-3.4.jar -------------------------------------------------------------------------------- /android/DashboardRedux/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 51 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 65 | 66 | 67 | 68 | 69 | 72 | 73 | 74 | 75 | 76 | 77 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 100 | 101 | 102 | 103 | 104 | 107 | 108 | 109 | 110 | 111 | 112 | 115 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | -------------------------------------------------------------------------------- /android/DashboardRedux/src/main/assets/README.txt: -------------------------------------------------------------------------------- 1 | Make sure this is the latest compatible appstore version! 2 | -------------------------------------------------------------------------------- /android/DashboardRedux/src/main/assets/dashboard.pbw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/C-D-Lewis/dashboard/76439ff18b98bf13ec317ec0ab7913a3c546c41c/android/DashboardRedux/src/main/assets/dashboard.pbw -------------------------------------------------------------------------------- /android/DashboardRedux/src/main/java/activity/AboutActivity.java: -------------------------------------------------------------------------------- 1 | package activity; 2 | 3 | import android.app.ActionBar; 4 | import android.app.Activity; 5 | import android.graphics.drawable.ColorDrawable; 6 | import android.os.Bundle; 7 | 8 | import com.wordpress.ninedof.dashboard.R; 9 | 10 | import fragment.About; 11 | 12 | public class AboutActivity extends Activity { 13 | 14 | @Override 15 | protected void onCreate(Bundle savedInstanceState) { 16 | super.onCreate(savedInstanceState); 17 | setContentView(R.layout.settings_fragment); 18 | 19 | ActionBar ab = getActionBar(); 20 | ab.setTitle("About Dashboard"); 21 | ab.setBackgroundDrawable(new ColorDrawable(getResources().getColor(R.color.main_colour))); 22 | ab.setHomeButtonEnabled(true); 23 | } 24 | 25 | @Override 26 | protected void onResume() { 27 | super.onResume(); 28 | 29 | getFragmentManager() 30 | .beginTransaction() 31 | .replace(R.id.fragment_layout, new fragment.About()) 32 | .commit(); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /android/DashboardRedux/src/main/java/activity/LogViewer.java: -------------------------------------------------------------------------------- 1 | package activity; 2 | 3 | import android.app.ActionBar; 4 | import android.app.Activity; 5 | import android.content.Context; 6 | import android.content.DialogInterface; 7 | import android.content.Intent; 8 | import android.graphics.drawable.ColorDrawable; 9 | import android.net.Uri; 10 | import android.os.Bundle; 11 | import android.util.Log; 12 | import android.view.Menu; 13 | import android.view.MenuItem; 14 | import android.view.View; 15 | import android.widget.ScrollView; 16 | import android.widget.TextView; 17 | import android.widget.Toast; 18 | 19 | import com.wordpress.ninedof.dashboard.R; 20 | 21 | import java.io.BufferedReader; 22 | import java.io.File; 23 | import java.io.FileReader; 24 | import java.io.IOException; 25 | import java.text.SimpleDateFormat; 26 | import java.util.Calendar; 27 | import java.util.Timer; 28 | import java.util.TimerTask; 29 | 30 | import cl_toolkit.Logger; 31 | import cl_toolkit.Storage; 32 | import cl_toolkit.UserInterface; 33 | import config.Build; 34 | import config.Runtime; 35 | 36 | public class LogViewer extends Activity { 37 | 38 | private static final String TAG = LogViewer.class.getName(); 39 | 40 | private ActionBar actionBar; 41 | private TextView textView; 42 | private ScrollView scrollView; 43 | 44 | @Override 45 | protected void onCreate(Bundle savedInstanceState) { 46 | super.onCreate(savedInstanceState); 47 | setContentView(R.layout.activity_log_viewer); 48 | 49 | //Setup ActionBar 50 | actionBar = getActionBar(); 51 | actionBar.setTitle("Debug Log"); 52 | actionBar.setBackgroundDrawable(new ColorDrawable(getResources().getColor(R.color.main_colour))); 53 | 54 | // Get UI members 55 | textView = (TextView)findViewById(R.id.log_view); 56 | scrollView = (ScrollView)findViewById(R.id.scroll_view); 57 | 58 | //Fill view 59 | refreshLog(); 60 | beginTimer(); 61 | } 62 | 63 | private void beginTimer() { 64 | Timer timer = new Timer(); 65 | timer.scheduleAtFixedRate(new TimerTask() { 66 | 67 | @Override 68 | public void run() { 69 | runOnUiThread(new Runnable() { 70 | 71 | @Override 72 | public void run() { 73 | SimpleDateFormat format = new SimpleDateFormat("HH:mm:ss"); 74 | String time = format.format(Calendar.getInstance().getTime()); 75 | actionBar.setSubtitle(time); 76 | } 77 | 78 | }); 79 | } 80 | 81 | }, 0, 1000); 82 | } 83 | 84 | private void refreshLog() { 85 | new Thread(new Runnable() { 86 | 87 | @Override 88 | public void run() { 89 | Context context = getApplicationContext(); 90 | 91 | try { 92 | readFile(); 93 | } catch(Exception e) { 94 | Log.e(TAG, e.getMessage()); 95 | 96 | //There may not be one yet 97 | Runtime.log(context, TAG, "Began new log file.", Logger.INFO); 98 | try { 99 | readFile(); 100 | } catch(Exception e1) { 101 | Runtime.log(context, TAG, "That REALLY went wrong...", Logger.ERROR); 102 | Runtime.logStackTrace(context, e1); 103 | } 104 | } 105 | 106 | } 107 | 108 | }).start(); 109 | } 110 | 111 | private void readFile() throws IOException { 112 | File logFile = new File(Storage.getAppStorage(getApplicationContext()) + "/" + Build.DEBUG_LOG_NAME); 113 | BufferedReader br = new BufferedReader(new FileReader(logFile)); 114 | String next = br.readLine(); 115 | StringBuilder content = new StringBuilder(Build.DEBUG_LOG_MAX_SIZE_BYTES); 116 | while(next != null) { 117 | content.append(next + "\n"); 118 | next = br.readLine(); 119 | } 120 | br.close(); 121 | 122 | //Show it 123 | final String newString = content.toString(); 124 | runOnUiThread(new Runnable() { 125 | 126 | @Override 127 | public void run() { 128 | textView.setText(newString); 129 | scrollView.post(new Runnable() { 130 | 131 | @Override 132 | public void run() { 133 | scrollView.fullScroll(View.FOCUS_DOWN); 134 | } 135 | }); 136 | } 137 | 138 | }); 139 | } 140 | 141 | @Override 142 | public boolean onCreateOptionsMenu(Menu menu) { 143 | getMenuInflater().inflate(R.menu.menu_log_viewer, menu); 144 | return true; 145 | } 146 | 147 | @Override 148 | public boolean onOptionsItemSelected(MenuItem item) { 149 | switch(item.getItemId()) { 150 | case R.id.action_refresh: 151 | refreshLog(); 152 | break; 153 | case R.id.action_report: 154 | UserInterface.showDialog(this, 155 | "Log Reporting", 156 | "You are about to send a copy of this log to the developer to help fix a problem.\n\nPlease include a description of the problem you are experiencing.\n\nThanks for your co-operation!", 157 | "Send Log", 158 | new DialogInterface.OnClickListener() { 159 | 160 | @Override 161 | public void onClick(DialogInterface dialog, int which) { 162 | sendDevMail(); 163 | } 164 | }, 165 | "Cancel", 166 | new DialogInterface.OnClickListener() { 167 | 168 | @Override 169 | public void onClick(DialogInterface dialog, int which) { 170 | dialog.dismiss(); 171 | } 172 | } 173 | ); 174 | break; 175 | } 176 | 177 | //Finally 178 | return super.onOptionsItemSelected(item); 179 | } 180 | 181 | private void sendDevMail() { 182 | //Use the actual one 183 | File attachment = new File(Storage.getStorage() + "/" + Build.DEBUG_LOG_NAME); 184 | 185 | //Open email with attachment 186 | if(attachment != null && attachment.canRead()) { 187 | Intent emailIntent = new Intent(Intent.ACTION_SENDTO, Uri.fromParts("mailto","bonsitm@gmail.com", null)); 188 | emailIntent.putExtra(Intent.EXTRA_SUBJECT, "Dashboard Debug Log File"); 189 | emailIntent.putExtra(Intent.EXTRA_STREAM, Uri.fromFile(attachment)); 190 | startActivity(Intent.createChooser(emailIntent, "Send email...")); 191 | } else { 192 | Toast.makeText(this, "Could not add attachment.", Toast.LENGTH_LONG).show(); 193 | } 194 | } 195 | 196 | } 197 | -------------------------------------------------------------------------------- /android/DashboardRedux/src/main/java/adapter/ToggleSelector.java: -------------------------------------------------------------------------------- 1 | package adapter; 2 | 3 | import android.content.Context; 4 | import android.view.LayoutInflater; 5 | import android.view.View; 6 | import android.view.ViewGroup; 7 | import android.widget.ArrayAdapter; 8 | import android.widget.ImageView; 9 | import android.widget.TextView; 10 | 11 | import com.wordpress.ninedof.dashboard.R; 12 | 13 | public class ToggleSelector extends ArrayAdapter { 14 | 15 | public static final String[] items = { 16 | "Wi-Fi", 17 | "Data", 18 | "BT", 19 | "Ringer", 20 | "Auto Sync", 21 | "Hotspot", 22 | "Find Phone", 23 | "Lock Phone", 24 | "Auto Brightness" 25 | }; 26 | 27 | public static final int[] iconIds = { 28 | R.drawable.wifi, 29 | R.drawable.data, 30 | R.drawable.bt, 31 | R.drawable.loud, 32 | R.drawable.sync, 33 | R.drawable.ap, 34 | R.drawable.phone, 35 | R.drawable.lock, 36 | R.drawable.brightness 37 | }; 38 | 39 | private Context context; 40 | 41 | public ToggleSelector(Context context) { 42 | super(context, R.layout.toggle_spinner_item, items); 43 | 44 | this.context = context; 45 | } 46 | 47 | @Override 48 | public View getDropDownView(int position, View convertView, ViewGroup parent) { 49 | return inflateView(position, parent); 50 | } 51 | 52 | @Override 53 | public View getView(int position, View convertView, ViewGroup parent) { 54 | return inflateView(position, parent); 55 | } 56 | 57 | public View inflateView(int position, ViewGroup parent) { 58 | //Inflate view 59 | LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); 60 | View rootView = inflater.inflate(R.layout.toggle_spinner_item, parent, false); 61 | 62 | //Get items 63 | ImageView icon = (ImageView)rootView.findViewById(R.id.icon); 64 | TextView name = (TextView)rootView.findViewById(R.id.name); 65 | 66 | icon.setImageResource(iconIds[position]); 67 | name.setText(items[position]); 68 | return rootView; 69 | } 70 | 71 | } 72 | -------------------------------------------------------------------------------- /android/DashboardRedux/src/main/java/background/App.java: -------------------------------------------------------------------------------- 1 | package background; 2 | 3 | import android.app.Application; 4 | import android.content.Context; 5 | import android.content.Intent; 6 | import android.content.SharedPreferences; 7 | import android.preference.PreferenceManager; 8 | 9 | import cl_toolkit.Logger; 10 | import config.Build; 11 | import config.Keys; 12 | import config.Runtime; 13 | 14 | public class App extends Application { 15 | 16 | private static final String TAG = App.class.getName(); 17 | 18 | @Override 19 | public void onCreate() { 20 | super.onCreate(); 21 | Context context = getApplicationContext(); 22 | 23 | if (!Build.RELEASE) { 24 | Runtime.log(context, TAG, "Application onCreate", Logger.DEBUG); 25 | } 26 | 27 | // Start KeepAlive alarms here, since on first install we won't get PACKAGE_ADDED intent broadcast, and it won't be boot time 28 | SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); 29 | if(prefs.getBoolean(Keys.PREF_FIRST_RUN, true)) { 30 | Intent i = new Intent(context, BootPackageChangeReceiver.class); 31 | i.setAction(BootPackageChangeReceiver.ACTION_START_BATTERY_MONITORING); 32 | sendBroadcast(i); 33 | 34 | // DON'T CLEAR FIRST RUN FLAG, Landing.java will do this 35 | } 36 | 37 | PebbleReceiver.registerReceiver(context); 38 | } 39 | 40 | @Override 41 | public void onTerminate() { 42 | Context context = getApplicationContext(); 43 | PebbleReceiver.unregisterReceiver(context); 44 | 45 | super.onTerminate(); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /android/DashboardRedux/src/main/java/background/BootPackageChangeReceiver.java: -------------------------------------------------------------------------------- 1 | package background; 2 | 3 | import android.app.AlarmManager; 4 | import android.app.PendingIntent; 5 | import android.content.BroadcastReceiver; 6 | import android.content.Context; 7 | import android.content.Intent; 8 | import android.content.SharedPreferences; 9 | import android.preference.PreferenceManager; 10 | 11 | import cl_toolkit.Logger; 12 | import config.Build; 13 | import config.Keys; 14 | import config.Runtime; 15 | 16 | /** 17 | * I really love Android APIs sometimes... 18 | * http://stackoverflow.com/questions/11277302/i-cant-receive-broadcast-on-battery-state-change/11277524#11277524 19 | */ 20 | public class BootPackageChangeReceiver extends BroadcastReceiver { 21 | 22 | private static final String TAG = BootPackageChangeReceiver.class.getName(); 23 | 24 | public static final String ACTION_START_BATTERY_MONITORING = "com.wordpress.ninedof.dashboard.action.START_BATTERY_MONITORING"; 25 | 26 | private static final int 27 | MINS = 30, 28 | REQUEST_CODE = 1; 29 | 30 | private boolean thisApp(Intent intent) { 31 | return intent.getData().getEncodedSchemeSpecificPart().equals(Build.PACKAGE_NAME); 32 | } 33 | 34 | @Override 35 | public void onReceive(Context context, Intent intent) { 36 | // Do our best to be alive for future background usage... 37 | PebbleReceiver.launchHandlerService(context, ""); 38 | 39 | SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); 40 | if(!prefs.getBoolean(Keys.PREF_KEY_CHARGE_NOTIFICATION_ENABLED, false)) { 41 | return; 42 | } 43 | 44 | String action = intent.getAction(); 45 | Runtime.log(context, TAG, "Starting battery monitor due to...", Logger.INFO); // TODO sometimes this is never followed up 46 | 47 | // Boot completed 48 | if(action.equals(Intent.ACTION_BOOT_COMPLETED)) { 49 | Runtime.log(context, TAG, "Boot completed", Logger.INFO); 50 | } 51 | 52 | // Launched by me 53 | else if(action.equals(ACTION_START_BATTERY_MONITORING)) { 54 | Runtime.log(context, TAG, "Requested by Dashboard", Logger.INFO); 55 | } 56 | 57 | // App updated or installed 58 | else if(action.equals(Intent.ACTION_PACKAGE_REPLACED) 59 | || action.equals(Intent.ACTION_PACKAGE_ADDED)) { 60 | if(!thisApp(intent)) { 61 | return; 62 | } 63 | Runtime.log(context, TAG, "Dashboard package added or replaced", Logger.INFO); 64 | } 65 | 66 | else { 67 | Runtime.log(context, TAG, "Some other reason: " + action, Logger.INFO); 68 | } 69 | 70 | startAlarms(context); 71 | } 72 | 73 | private static PendingIntent getIdenticalPendingIntent(Context context) { 74 | Intent i = new Intent(context, KeepAliveReceiver.class); 75 | return PendingIntent.getBroadcast(context, REQUEST_CODE, i, 0); 76 | } 77 | 78 | public static void startAlarms(Context context) { 79 | clearAlarms(context); 80 | 81 | AlarmManager alarmManager = (AlarmManager)context.getSystemService(Context.ALARM_SERVICE); 82 | 83 | PendingIntent pendingIntent = getIdenticalPendingIntent(context); 84 | alarmManager.setRepeating(AlarmManager.RTC_WAKEUP, System.currentTimeMillis() + 1000, 1000 * 60 * MINS, pendingIntent); // Millisec * Second * Minute 85 | 86 | Runtime.log(context, TAG, "AM start", Logger.DEBUG); 87 | } 88 | 89 | public static void clearAlarms(Context context) { 90 | AlarmManager alarmManager = (AlarmManager)context.getSystemService(Context.ALARM_SERVICE); 91 | PendingIntent pendingIntent = getIdenticalPendingIntent(context); 92 | alarmManager.cancel(pendingIntent); // Works because the pendingIntent is identical to that which was registered 93 | 94 | Runtime.log(context, TAG, "AM cancel", Logger.INFO); 95 | } 96 | 97 | } 98 | -------------------------------------------------------------------------------- /android/DashboardRedux/src/main/java/background/DeviceAdmin.java: -------------------------------------------------------------------------------- 1 | package background; 2 | 3 | import android.app.admin.DeviceAdminReceiver; 4 | 5 | public class DeviceAdmin extends DeviceAdminReceiver { 6 | // Must have an 'implementation' to use the API, apparently 7 | // But we don't want anything special here 8 | } -------------------------------------------------------------------------------- /android/DashboardRedux/src/main/java/background/FindPhone.java: -------------------------------------------------------------------------------- 1 | package background; 2 | 3 | import android.app.Notification; 4 | import android.app.NotificationChannel; 5 | import android.app.NotificationManager; 6 | import android.app.Service; 7 | import android.content.Context; 8 | import android.content.Intent; 9 | import android.content.SharedPreferences; 10 | import android.media.AudioManager; 11 | import android.media.MediaPlayer; 12 | import android.media.RingtoneManager; 13 | import android.net.Uri; 14 | import android.os.IBinder; 15 | import android.preference.PreferenceManager; 16 | import androidx.annotation.RequiresApi; 17 | import androidx.core.app.NotificationCompat; 18 | import android.widget.Toast; 19 | 20 | import com.getpebble.android.kit.PebbleKit; 21 | import com.getpebble.android.kit.util.PebbleDictionary; 22 | import com.wordpress.ninedof.dashboard.R; 23 | 24 | import java.io.IOException; 25 | 26 | import cl_toolkit.Logger; 27 | import config.Build; 28 | import config.Keys; 29 | import config.Runtime; 30 | 31 | public class FindPhone extends Service { 32 | 33 | private static final String TAG = FindPhone.class.getName(); 34 | private static final int ID_FIND_PHONE = 347; 35 | 36 | private static MediaPlayer mediaPlayer; // Exist forever pls (or at least while the sound is on) 37 | private static int userVolume; 38 | 39 | @Override 40 | public int onStartCommand(Intent intent, int flags, int startId) { 41 | Context context = getApplicationContext(); 42 | 43 | SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); 44 | Runtime.log(context, TAG, "FindPhone onStartCommand", Logger.INFO); 45 | 46 | boolean isRunning = prefs.getBoolean(Keys.PREF_FIND_PHONE_RUNNING, false); 47 | if(isRunning) { 48 | Runtime.log(context, TAG, "Stopping find phone...", Logger.INFO); 49 | 50 | //Stop media 51 | final AudioManager audioManager = (AudioManager)getSystemService(Context.AUDIO_SERVICE); 52 | audioManager.setStreamVolume(AudioManager.STREAM_ALARM, userVolume, AudioManager.FLAG_PLAY_SOUND); 53 | 54 | if(mediaPlayer != null) { 55 | mediaPlayer.stop(); 56 | mediaPlayer.reset(); 57 | } else { 58 | Runtime.log(context, TAG, "mediaPlayer null when trying to stop Find Phone sound!", Logger.ERROR); 59 | } 60 | 61 | PebbleDictionary dict = new PebbleDictionary(); 62 | dict.addInt8(Keys.AppKeyToggleFindPhone, (byte) Keys.ToggleStateOff); 63 | PebbleKit.sendDataToPebble(context, Build.WATCH_APP_UUID, dict); 64 | 65 | SharedPreferences.Editor ed = prefs.edit(); 66 | ed.putBoolean(Keys.PREF_FIND_PHONE_RUNNING, false); 67 | ed.commit(); 68 | 69 | // Stop service 70 | stopSelf(); 71 | } else { 72 | Runtime.log(context, TAG, "Starting find phone...", Logger.INFO); 73 | String message = "Playing Find Phone sound. Use Find Phone toggle again to cancel."; 74 | String channelId = "find_phone_notification"; 75 | 76 | if (android.os.Build.VERSION.SDK_INT > android.os.Build.VERSION_CODES.O) { 77 | createNotificationChannel(channelId, "Find My Phone"); 78 | } else { 79 | // Start 80 | NotificationCompat.Builder builder = new NotificationCompat.Builder(context, channelId); 81 | builder.setSmallIcon(R.drawable.ic_launcher_notif); 82 | builder.setContentTitle("Dashboard"); 83 | builder.setStyle(new NotificationCompat.BigTextStyle().bigText(message)); 84 | builder.setColor(getResources().getColor(R.color.main_colour)); 85 | startForeground(ID_FIND_PHONE, builder.build()); 86 | } 87 | 88 | // Choose user's choice or default 89 | String uriString = prefs.getString(Keys.PREF_KEY_FIND_PHONE_FILE, Keys.PREF_VALUE_FIND_PHONE_DEFAULT); 90 | Uri uri; 91 | if(uriString.equals(Keys.PREF_VALUE_FIND_PHONE_DEFAULT)) { 92 | uri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION); 93 | Runtime.log(context, TAG, "Used Notification tone.", Logger.INFO); 94 | Toast.makeText(context, "No file chosen, or was unavailable. Used Notification sound.", Toast.LENGTH_SHORT).show(); 95 | } else { 96 | uri = Uri.parse(uriString); 97 | Runtime.log(context, TAG, "Using loaded Uri: " + uriString, Logger.INFO); 98 | } 99 | 100 | playSound(uri); 101 | } 102 | 103 | Runtime.log(context, TAG, "FindPhone onStartCommand finished", Logger.INFO); 104 | 105 | // Finally 106 | return super.onStartCommand(intent, flags, startId); 107 | } 108 | 109 | private void playSound(Uri uri) { 110 | Context context = getApplicationContext(); 111 | final AudioManager audioManager = (AudioManager)getSystemService(Context.AUDIO_SERVICE); 112 | 113 | if(mediaPlayer != null) { 114 | // Stop current playback 115 | mediaPlayer.stop(); 116 | mediaPlayer.reset(); 117 | 118 | // Reset volume 119 | audioManager.setStreamVolume(AudioManager.STREAM_ALARM, userVolume, AudioManager.FLAG_PLAY_SOUND); 120 | } else { 121 | // Create new one 122 | mediaPlayer = new MediaPlayer(); 123 | } 124 | 125 | try { 126 | if(!mediaPlayer.isPlaying()) { 127 | // Get current volume 128 | userVolume = audioManager.getStreamVolume(AudioManager.STREAM_ALARM); 129 | 130 | // Boost volume 131 | audioManager.setStreamVolume(AudioManager.STREAM_ALARM, audioManager.getStreamMaxVolume(AudioManager.STREAM_ALARM), AudioManager.FLAG_PLAY_SOUND); 132 | 133 | mediaPlayer.setDataSource(context, uri); 134 | mediaPlayer.setAudioStreamType(AudioManager.STREAM_ALARM); 135 | mediaPlayer.setLooping(true); 136 | mediaPlayer.prepare(); 137 | mediaPlayer.start(); 138 | Runtime.log(context, TAG, "Sound started.", Logger.INFO); 139 | 140 | PebbleDictionary out = new PebbleDictionary(); 141 | out.addInt8(Keys.AppKeyToggleFindPhone, (byte) Keys.ToggleStateOn); 142 | PebbleKit.sendDataToPebble(context, Build.WATCH_APP_UUID, out); 143 | 144 | SharedPreferences.Editor ed = PreferenceManager.getDefaultSharedPreferences(context).edit(); 145 | ed.putBoolean(Keys.PREF_FIND_PHONE_RUNNING, true); 146 | ed.commit(); 147 | 148 | Runtime.log(context, TAG, "Sound started.", Logger.INFO); 149 | } 150 | } catch (IOException e) { 151 | Toast.makeText(context, "Your Find Phone sound was unavailable. Resetting to default...", Toast.LENGTH_LONG).show(); 152 | SharedPreferences.Editor ed = PreferenceManager.getDefaultSharedPreferences(context).edit(); 153 | ed.putString(Keys.PREF_KEY_FIND_PHONE_FILE, Keys.PREF_VALUE_FIND_PHONE_DEFAULT); 154 | ed.commit(); 155 | } 156 | audioManager.setStreamVolume(AudioManager.STREAM_ALARM, audioManager.getStreamMaxVolume(AudioManager.STREAM_ALARM), AudioManager.FLAG_PLAY_SOUND); 157 | } 158 | 159 | @Override 160 | public IBinder onBind(Intent intent) { 161 | return null; 162 | } 163 | 164 | @RequiresApi(android.os.Build.VERSION_CODES.O) 165 | private String createNotificationChannel(String channelId, String channelName){ 166 | NotificationChannel channel = new NotificationChannel(channelId, channelName, NotificationManager.IMPORTANCE_NONE); 167 | channel.setLightColor(getResources().getColor(R.color.main_colour)); 168 | channel.setLockscreenVisibility(Notification.VISIBILITY_PRIVATE); 169 | NotificationManager manager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); 170 | manager.createNotificationChannel(channel); 171 | 172 | return channelId; 173 | } 174 | } 175 | -------------------------------------------------------------------------------- /android/DashboardRedux/src/main/java/background/KeepAliveReceiver.java: -------------------------------------------------------------------------------- 1 | package background; 2 | 3 | import android.app.NotificationManager; 4 | import android.content.BroadcastReceiver; 5 | import android.content.Context; 6 | import android.content.Intent; 7 | import android.content.IntentFilter; 8 | import android.content.SharedPreferences; 9 | import android.os.BatteryManager; 10 | import android.preference.PreferenceManager; 11 | import androidx.core.app.NotificationCompat; 12 | 13 | import com.wordpress.ninedof.dashboard.R; 14 | 15 | import cl_toolkit.Logger; 16 | import cl_toolkit.Platform; 17 | import config.Build; 18 | import config.Keys; 19 | import config.Runtime; 20 | 21 | /** 22 | * AKA 'how hard can it be to notify when fully charged??' 23 | */ 24 | public class KeepAliveReceiver extends BroadcastReceiver { 25 | 26 | private static final String TAG = KeepAliveReceiver.class.getName(); 27 | 28 | public static int NOTIFICATION_ID = 543897; 29 | 30 | private static BroadcastReceiver receiver; 31 | 32 | @Override 33 | public void onReceive(Context context, Intent intent) { 34 | if(!Build.RELEASE) { 35 | Runtime.log(context, TAG, "KeepAliveReceiver onReceive()", Logger.DEBUG); 36 | } 37 | 38 | if(receiver == null) { 39 | Runtime.log(context, TAG, "Battery receiver was null, replacing it. Next unregister will fail.", Logger.INFO); 40 | } 41 | registerReceiver(context.getApplicationContext()); 42 | } 43 | 44 | public static void registerReceiver(Context context) { 45 | unregisterReceiver(context); 46 | receiver = new BroadcastReceiver() { 47 | 48 | @Override 49 | public void onReceive(final Context context, final Intent batteryStatus) { 50 | handleChange(context, batteryStatus); 51 | } 52 | 53 | }; 54 | IntentFilter filter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED); 55 | context.getApplicationContext().registerReceiver(receiver, filter); 56 | 57 | Runtime.log(context, TAG, "Registered new battery level receiver", Logger.INFO); 58 | if(!Build.RELEASE) { 59 | notify(context, "Receiver registered."); 60 | } 61 | } 62 | 63 | public static void unregisterReceiver(Context context) { 64 | if(receiver == null) { 65 | Runtime.log(context, TAG, "Failed to unregister battery receiver - it was null", Logger.INFO); 66 | return; 67 | } 68 | 69 | try { 70 | context.getApplicationContext().unregisterReceiver(receiver); 71 | if(!Build.RELEASE) { 72 | Runtime.log(context, TAG, "Unregistered battery receiver", Logger.INFO); 73 | } 74 | } catch(Exception e) { 75 | Runtime.log(context, TAG, "Exception unregistering receiver - it may have already been unregistered", Logger.ERROR); 76 | Runtime.logStackTrace(context, e); 77 | } 78 | } 79 | 80 | private static boolean isPluggedIn(Intent batteryState) { 81 | int plugged = batteryState.getIntExtra(BatteryManager.EXTRA_PLUGGED, -1); 82 | return plugged == BatteryManager.BATTERY_PLUGGED_AC || plugged == BatteryManager.BATTERY_PLUGGED_USB; 83 | } 84 | 85 | private static void handleChange(Context c, Intent batteryStatus) { 86 | Context context = c.getApplicationContext(); 87 | SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); 88 | 89 | // Get new level 90 | int level = batteryStatus.getIntExtra(BatteryManager.EXTRA_LEVEL, -1); 91 | int scale = batteryStatus.getIntExtra(BatteryManager.EXTRA_SCALE, -1); 92 | int status = batteryStatus.getIntExtra(BatteryManager.EXTRA_STATUS, -1); 93 | float batteryPct = (float) level / (float) scale; 94 | int currentLevel = Math.round(batteryPct * 100.0F); 95 | boolean isOnCharge = (status == BatteryManager.BATTERY_STATUS_CHARGING || 96 | status == BatteryManager.BATTERY_STATUS_FULL); 97 | 98 | if(!Build.RELEASE) { 99 | Runtime.log(context, TAG, "Battery level now " + currentLevel + " (" + batteryPct + ")", Logger.DEBUG); 100 | } 101 | 102 | // Ratchet - Are we going up from 99%? 103 | int lastLevel = prefs.getInt(Keys.PREF_KEY_CHARGE_NOTIFICATION_LAST_VALUE, -1); 104 | if(lastLevel == 99 && currentLevel == 100) { 105 | // Does the user care? 106 | boolean monitorIsEnabled = prefs.getBoolean(Keys.PREF_KEY_CHARGE_NOTIFICATION_ENABLED, false); 107 | if(monitorIsEnabled && isOnCharge) { 108 | notify(context, "Your phone is now fully charged!"); 109 | Runtime.log(context, TAG, "Fully charged, notifying.", Logger.INFO); 110 | } else { 111 | Runtime.log(context, TAG, "Fully charged, but notification is not enabled. Ignoring.", Logger.INFO); 112 | } 113 | } 114 | 115 | // Dismiss on unplug 116 | if(!isPluggedIn(batteryStatus)) { 117 | try { 118 | NotificationManager mNotifyMgr = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); 119 | mNotifyMgr.cancel(NOTIFICATION_ID); 120 | Runtime.log(context, TAG, "Dismissed on unplug", Logger.INFO); 121 | } catch (Exception e) { 122 | Runtime.log(context, TAG, "Exception dismissing notification, was it not present?", Logger.INFO); 123 | Runtime.logStackTrace(context, e); 124 | } 125 | } 126 | 127 | // Remember last value 128 | SharedPreferences.Editor ed = prefs.edit(); 129 | ed.putInt(Keys.PREF_KEY_CHARGE_NOTIFICATION_LAST_VALUE, currentLevel); 130 | ed.commit(); 131 | } 132 | 133 | private static void notify(Context context, String content) { 134 | NotificationCompat.Builder builder = new NotificationCompat.Builder(context); 135 | if(Platform.isLollipopOrAbove()) { 136 | builder.setSmallIcon(R.drawable.ic_launcher_notif); 137 | } else { 138 | builder.setSmallIcon(R.drawable.ic_launcher); 139 | } 140 | builder.setContentTitle("Dashboard"); 141 | builder.setContentText(content); 142 | builder.setColor(context.getResources().getColor(R.color.main_colour)); 143 | 144 | NotificationManager mNotifyMgr = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); 145 | mNotifyMgr.notify(NOTIFICATION_ID, builder.build()); 146 | } 147 | 148 | } 149 | -------------------------------------------------------------------------------- /android/DashboardRedux/src/main/java/background/PSLCallback.java: -------------------------------------------------------------------------------- 1 | package background; 2 | 3 | /** 4 | * Simpler PSL interface for my brain 5 | */ 6 | public abstract class PSLCallback { 7 | 8 | public abstract void onPercentKnown(int perc); 9 | 10 | } -------------------------------------------------------------------------------- /android/DashboardRedux/src/main/java/background/PebbleReceiver.java: -------------------------------------------------------------------------------- 1 | package background; 2 | 3 | import android.content.BroadcastReceiver; 4 | import android.content.Context; 5 | import android.content.Intent; 6 | import android.content.IntentFilter; 7 | import android.util.Log; 8 | 9 | import com.getpebble.android.kit.PebbleKit; 10 | 11 | import cl_toolkit.Logger; 12 | import config.Build; 13 | import config.Runtime; 14 | import util.PebbleUtils; 15 | 16 | import static com.getpebble.android.kit.Constants.MSG_DATA; 17 | import static com.getpebble.android.kit.Constants.TRANSACTION_ID; 18 | 19 | public class PebbleReceiver extends BroadcastReceiver { 20 | //Configuration 21 | public final static String TAG = PebbleReceiver.class.getName(); 22 | 23 | private static PebbleReceiver receiver; 24 | 25 | @Override 26 | public void onReceive(Context context, Intent intent) { 27 | try { 28 | if(PebbleUtils.isThisApp(intent, Build.WATCH_APP_UUID)) { 29 | Runtime.startNewSession(context); 30 | Runtime.log(context, TAG, "Dashboard received Intent with matching UUID.", Logger.INFO); 31 | 32 | //Ack 33 | int transactionId = intent.getIntExtra(TRANSACTION_ID, -1); 34 | PebbleKit.sendAckToPebble(context, transactionId); 35 | 36 | //Get dictionary 37 | String jsonData = intent.getStringExtra(MSG_DATA); 38 | if (jsonData != null && !jsonData.isEmpty()) { 39 | //Launch service w/dictionary JSON 40 | launchHandlerService(context, jsonData); 41 | 42 | } else { 43 | Runtime.log(context, TAG, "jsonData was null or empty!", Logger.ERROR); 44 | } 45 | } 46 | } catch(Exception e) { 47 | Runtime.log(context, TAG, "Receiver threw exception: " + e.getLocalizedMessage(), Logger.ERROR); 48 | Runtime.logStackTrace(context, e); 49 | } 50 | } 51 | 52 | public static void launchHandlerService(Context context, String jsonData) { 53 | Intent service = new Intent(context, HandlerService.class); 54 | service.putExtra("json", jsonData); 55 | 56 | // Background apps are cached inactive after API 28 (Oreo) :( 57 | final int sdkInt = android.os.Build.VERSION.SDK_INT; 58 | if (sdkInt >= android.os.Build.VERSION_CODES.O) { 59 | Runtime.log(context, TAG, "startForegroundService on version " + sdkInt, Logger.INFO); 60 | context.startForegroundService(service); 61 | } else { 62 | Runtime.log(context, TAG, "startService on version " + sdkInt, Logger.INFO); 63 | context.startService(service); 64 | } 65 | } 66 | 67 | public static void registerReceiver(Context context) { 68 | unregisterReceiver(context); 69 | receiver = new PebbleReceiver(); 70 | 71 | context.getApplicationContext().registerReceiver(receiver, new IntentFilter("com.getpebble.action.app.RECEIVE")); 72 | 73 | Runtime.log(context, TAG, "Registered new Pebble receiver", Logger.INFO); 74 | } 75 | 76 | public static void unregisterReceiver(Context context) { 77 | if(receiver == null) { 78 | Runtime.log(context, TAG, "Failed to unregister Pebble receiver - it was null", Logger.INFO); 79 | return; 80 | } 81 | 82 | try { 83 | context.getApplicationContext().unregisterReceiver(receiver); 84 | if(!Build.RELEASE) { 85 | Runtime.log(context, TAG, "Unregistered Pebble receiver", Logger.INFO); 86 | } 87 | } catch(Exception e) { 88 | Runtime.log(context, TAG, "Exception unregistering receiver - it may have already been unregistered", Logger.ERROR); 89 | Runtime.logStackTrace(context, e); 90 | } 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /android/DashboardRedux/src/main/java/background/SignalListener.java: -------------------------------------------------------------------------------- 1 | package background; 2 | 3 | import android.content.Context; 4 | import android.telephony.PhoneStateListener; 5 | import android.telephony.SignalStrength; 6 | 7 | import cl_toolkit.Logger; 8 | import config.Runtime; 9 | 10 | // Get the phone GSM signal strength, approximately 11 | public class SignalListener extends PhoneStateListener { 12 | 13 | private static final String TAG = SignalListener.class.getName(); 14 | 15 | private static final int 16 | UNKNOWN_CODE = 99, 17 | MAX_SIGNAL_DBM_VALUE = 31; 18 | 19 | private PSLCallback callback; 20 | private Context context; 21 | 22 | public SignalListener(Context context, PSLCallback callback) { 23 | this.callback = callback; 24 | this.context = context; 25 | } 26 | 27 | private int calculateSignalStrengthInPercent(int signalStrength) { 28 | Runtime.log(context, TAG, "Raw GSM strength: " + signalStrength + "/31", Logger.INFO); 29 | return (int) ((float) signalStrength / MAX_SIGNAL_DBM_VALUE * 100); 30 | } 31 | 32 | @Override 33 | public void onSignalStrengthsChanged(SignalStrength signalStrength) { 34 | super.onSignalStrengthsChanged(signalStrength); 35 | 36 | int perc; 37 | if(signalStrength != null && signalStrength.getGsmSignalStrength() != UNKNOWN_CODE) { 38 | perc = calculateSignalStrengthInPercent(signalStrength.getGsmSignalStrength()); 39 | Runtime.log(context, TAG, "Phone signal strength percent = " + perc, Logger.INFO); 40 | } else { 41 | Runtime.log(context, TAG, "Unable to get phone signal strength!", Logger.INFO); 42 | perc = 0; 43 | } 44 | 45 | callback.onPercentKnown(perc); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /android/DashboardRedux/src/main/java/cl_toolkit/Contact.java: -------------------------------------------------------------------------------- 1 | package cl_toolkit; 2 | 3 | import android.content.ContentResolver; 4 | import android.content.Context; 5 | import android.database.Cursor; 6 | import android.net.Uri; 7 | import android.provider.ContactsContract.PhoneLookup; 8 | 9 | /** 10 | * Common Android Contacts helpers 11 | * @author Chris Lewis 12 | */ 13 | public class Contact { 14 | 15 | /** 16 | * Get the last Contact who sent the user an SMS 17 | * @param context Context object 18 | * @return Name of last Contact or if unknown, the phone number 19 | */ 20 | public static String getLastSMSName(Context context, int maxLength) { 21 | try { 22 | Cursor cursor = context.getContentResolver().query(Uri.parse("content://sms/inbox"), null, null, null, null); 23 | cursor.moveToFirst(); 24 | 25 | String sender = ""; 26 | for(int idx=0;idx maxLength) { 102 | return contactName.substring(0, maxLength); 103 | } else { 104 | return contactName; 105 | } 106 | } else { 107 | return phoneNumber; 108 | } 109 | } catch(Exception e) { 110 | e.printStackTrace(); 111 | return phoneNumber != null ? phoneNumber : "GCNOTFOUND"; 112 | } 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /android/DashboardRedux/src/main/java/cl_toolkit/FileMap.java: -------------------------------------------------------------------------------- 1 | package cl_toolkit; 2 | 3 | import java.io.BufferedReader; 4 | import java.io.File; 5 | import java.io.FileReader; 6 | import java.io.FileWriter; 7 | import java.util.HashMap; 8 | 9 | /** 10 | * Key:Value database in a file, safe and easy to use. Nothing should be uncommitted. 11 | * @author Chris Lewis 12 | */ 13 | public class FileMap { 14 | 15 | private final String DELIMITER = ":"; 16 | 17 | private File file; 18 | 19 | /** 20 | * Default constructor 21 | */ 22 | public FileMap(String path) { 23 | try { 24 | file = new File(path); 25 | 26 | //Make a new one 27 | if(!file.exists()) { 28 | FileWriter fw = new FileWriter(file); 29 | fw.flush(); 30 | fw.close(); 31 | } 32 | } catch(Exception e) { 33 | System.err.println("Error opening file " + path); 34 | e.printStackTrace(); 35 | } 36 | } 37 | 38 | /** 39 | * Put a value in the db 40 | * @param key Key String 41 | * @param value Value String 42 | * @return true if successful, else false 43 | */ 44 | public boolean put(String key, String value) { 45 | try { 46 | //Load into hashmap 47 | HashMap map = new HashMap(); 48 | BufferedReader br = new BufferedReader(new FileReader(file)); 49 | String next = br.readLine(); 50 | while(next != null) { 51 | String currentKey = next.substring(0, next.indexOf(DELIMITER)); 52 | String currentValue = next.substring(next.indexOf(DELIMITER) + 1); 53 | 54 | //Add existing to map 55 | map.put(currentKey, currentValue); 56 | 57 | //Finally 58 | next = br.readLine(); 59 | } 60 | 61 | //Add new 62 | map.put(key, value); 63 | 64 | //Save all 65 | br.close(); 66 | 67 | FileWriter fw = new FileWriter(file); 68 | for(String k : map.keySet()) { 69 | fw.write(k + DELIMITER + map.get(k)); 70 | fw.write("\n"); 71 | } 72 | 73 | //Finally 74 | fw.flush(); 75 | fw.close(); 76 | return true; 77 | } catch(Exception e) { 78 | System.err.println("Error putting " + file.getAbsolutePath()); 79 | e.printStackTrace(); 80 | return false; 81 | } 82 | } 83 | 84 | /** 85 | * Get a value 86 | * @param key Key String 87 | * @return Value if found, else null 88 | */ 89 | public String get(String key) { 90 | try { 91 | //Open file 92 | BufferedReader br = new BufferedReader(new FileReader(file)); 93 | 94 | //Find the key 95 | String next = br.readLine(); 96 | while(next != null) { 97 | //If it is found 98 | if(next.contains(key)) { 99 | br.close(); 100 | return next.substring(next.indexOf(key + DELIMITER) + (key.length() + 1)); 101 | } 102 | 103 | //Get next 104 | next = br.readLine(); 105 | } 106 | 107 | //Failed 108 | // System.err.println("Could not find " + key + " in file " + file.getAbsolutePath()); 109 | br.close(); 110 | return null; 111 | } catch(Exception e) { 112 | System.err.println("Error getting " + key + " from " + file.getAbsolutePath()); 113 | e.printStackTrace(); 114 | return null; 115 | } 116 | } 117 | 118 | /** 119 | * Get a HashMap of all contained keys and values 120 | * @return HashMap of all contained keys and values 121 | */ 122 | public HashMap getHashMap() { 123 | HashMap result = new HashMap(); 124 | 125 | try { 126 | //Open file 127 | BufferedReader br = new BufferedReader(new FileReader(file)); 128 | 129 | //Find the key 130 | String next = br.readLine(); 131 | while(next != null) { 132 | //Split and add 133 | result.put(next.substring(0, next.indexOf(DELIMITER)), next.substring(next.indexOf(DELIMITER) + 1)); 134 | 135 | //Get next 136 | next = br.readLine(); 137 | } 138 | 139 | br.close(); 140 | } catch(Exception e) { 141 | System.err.println("Error getting hashmap from " + file.getAbsolutePath()); 142 | e.printStackTrace(); 143 | return null; 144 | } 145 | 146 | //Finally 147 | return result; 148 | } 149 | 150 | } -------------------------------------------------------------------------------- /android/DashboardRedux/src/main/java/cl_toolkit/Logger.java: -------------------------------------------------------------------------------- 1 | package cl_toolkit; 2 | 3 | import android.annotation.SuppressLint; 4 | import android.util.Log; 5 | 6 | import java.io.File; 7 | import java.io.FileOutputStream; 8 | import java.io.FileWriter; 9 | import java.io.PrintWriter; 10 | import java.sql.Date; 11 | import java.text.SimpleDateFormat; 12 | 13 | /** 14 | * File system synchronised logger 15 | * @author Chris Lewis 16 | */ 17 | public class Logger { 18 | 19 | private static final String TAG = Logger.class.getName(); 20 | 21 | /** 22 | * Levels available 23 | */ 24 | public static final String 25 | INFO = "[I]", 26 | ERROR = "[E]", 27 | DEBUG = "[D]"; 28 | 29 | private File file; 30 | 31 | /** 32 | * Create a new log 33 | * @param path Path to output file location 34 | * @param maxSizeBytes Max size in bytes 35 | */ 36 | public Logger(String path, int maxSizeBytes) { 37 | file = new File(path); 38 | 39 | //Delete when too large 40 | if(file.exists()) { 41 | long length = file.length(); 42 | if(length > maxSizeBytes) { 43 | file.delete(); 44 | file = new File(path); 45 | log(TAG, "Fresh new log file! (Old one was " + length + "B", Logger.INFO); 46 | } 47 | } 48 | } 49 | 50 | /** 51 | * Start a new session in the log file 52 | */ 53 | public void startNewSession() { 54 | try { 55 | FileWriter fw = new FileWriter(file, true); 56 | 57 | fw.write("\n" + "===== New AppMessage session: " + getTimestamp() + " =====\n"); 58 | fw.write("Size is: " + file.length() + "B " + "\n"); 59 | 60 | fw.close(); 61 | } catch(Exception e) { 62 | System.err.println("Error starting new log session!"); 63 | e.printStackTrace(); 64 | } 65 | } 66 | 67 | /** 68 | * Add a log event to the log file 69 | * @param TAG TAG of the logging class 70 | * @param message Message to log 71 | * @param levelConstant Constant value to show the level 72 | */ 73 | public void log(String TAG, String message, String levelConstant) { 74 | try { 75 | FileWriter fw = new FileWriter(file, true); 76 | 77 | String complete = "[" + getTimestamp() + "] " + levelConstant + " [" + TAG + "] " + message; 78 | 79 | //Log to file 80 | fw.write(complete + "\n"); 81 | 82 | //Log to ADB 83 | if(levelConstant.equals(INFO)) { 84 | Log.i(TAG, complete); 85 | } else if(levelConstant.equals(DEBUG)) { 86 | Log.d(TAG, complete); 87 | } else if(levelConstant.equals(ERROR)) { 88 | Log.e(TAG, complete); 89 | } 90 | 91 | //Finally 92 | fw.flush(); 93 | fw.close(); 94 | } catch(Exception e) { 95 | System.err.println("Error writing to log file!"); 96 | e.printStackTrace(); 97 | } 98 | } 99 | 100 | /** 101 | * Log a stack trace 102 | * @param e The Exception to print 103 | */ 104 | public void logStackTrace(Exception e) { 105 | PrintWriter pw; 106 | try { 107 | pw = new PrintWriter(new FileOutputStream(file, true)); 108 | e.printStackTrace(pw); 109 | pw.flush(); 110 | pw.close(); 111 | } catch (Exception e1) { 112 | e1.printStackTrace(); 113 | } 114 | } 115 | 116 | /** 117 | * Get a timestamp string 118 | * @return time as a String 119 | */ 120 | @SuppressLint("SimpleDateFormat") 121 | private String getTimestamp() { 122 | Date date = new Date(System.currentTimeMillis()); 123 | SimpleDateFormat sdf = new SimpleDateFormat("dd/MM HH:mm:ss.SSS"); 124 | return sdf.format(date); 125 | } 126 | 127 | } 128 | -------------------------------------------------------------------------------- /android/DashboardRedux/src/main/java/cl_toolkit/Meta.java: -------------------------------------------------------------------------------- 1 | package cl_toolkit; 2 | 3 | public class Meta { 4 | 5 | // Version of CL Toolkit 6 | public static final String VERSION = "1.10.0"; 7 | 8 | } 9 | -------------------------------------------------------------------------------- /android/DashboardRedux/src/main/java/cl_toolkit/Platform.java: -------------------------------------------------------------------------------- 1 | package cl_toolkit; 2 | 3 | public class Platform { 4 | 5 | public static boolean isLollipopOrAbove() { 6 | return android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP; 7 | } 8 | 9 | } 10 | -------------------------------------------------------------------------------- /android/DashboardRedux/src/main/java/cl_toolkit/Radios.java: -------------------------------------------------------------------------------- 1 | package cl_toolkit; 2 | 3 | import android.Manifest; 4 | import android.bluetooth.BluetoothAdapter; 5 | import android.content.Context; 6 | import android.content.pm.PackageManager; 7 | import android.net.ConnectivityManager; 8 | import android.net.NetworkInfo; 9 | import android.net.wifi.WifiConfiguration; 10 | import android.net.wifi.WifiManager; 11 | import android.os.Handler; 12 | import androidx.annotation.RequiresApi; 13 | import androidx.core.content.ContextCompat; 14 | import android.util.Log; 15 | 16 | import java.lang.reflect.Method; 17 | 18 | /** 19 | * Methods for working with device radios 20 | * 21 | * An exercise in relying on maaany reflected APIs that could vanish at the drop of a hat 22 | * > Don't do this. 23 | * 24 | * @author Chris Lewis 25 | */ 26 | public class Radios { 27 | 28 | public static final int 29 | WIFI_AP_STATE_UNKNOWN = -1, 30 | WIFI_AP_STATE_DISABLING = 0, 31 | WIFI_AP_STATE_DISABLED = 1, 32 | WIFI_AP_STATE_ENABLING = 2, 33 | WIFI_AP_STATE_ENABLED = 3, 34 | WIFI_AP_STATE_FAILED = 4; 35 | 36 | private static final String TAG = Radios.class.getName(); 37 | 38 | /** 39 | * Get the current WiFi state 40 | * @return true if WiFi is enabled 41 | */ 42 | public static boolean getWiFiEnabled(Context context) { 43 | return ((WifiManager)context.getSystemService(Context.WIFI_SERVICE)).isWifiEnabled(); 44 | } 45 | 46 | /** 47 | * Set the WiFi state 48 | * @param newState Desired new state 49 | */ 50 | public static boolean setWiFiState(Context context, boolean newState) { 51 | return ((WifiManager)context.getSystemService(Context.WIFI_SERVICE)).setWifiEnabled(newState); 52 | } 53 | 54 | /** 55 | * Get the current data network state 56 | * @return true if the network radio is connected or connecting 57 | */ 58 | public static boolean getMobileDataEnabled(Context context) { 59 | NetworkInfo nInfo = ((ConnectivityManager)context.getSystemService(Context.CONNECTIVITY_SERVICE)) 60 | .getNetworkInfo(ConnectivityManager.TYPE_MOBILE); 61 | return nInfo.getState() == NetworkInfo.State.CONNECTED 62 | || nInfo.getState() == NetworkInfo.State.CONNECTING; 63 | } 64 | 65 | /** 66 | * Set new Mobile Data state 67 | * 68 | * Adapted from source: 69 | * http://stackoverflow.com/questions/12535101/how-can-i-turn-off-3g-data-programmatically-on-android 70 | * 71 | * @param newState The new state 72 | */ 73 | public static boolean setMobileDataEnabled(Context context, boolean newState) { 74 | try { 75 | ConnectivityManager dataManager = (ConnectivityManager)context.getSystemService(Context.CONNECTIVITY_SERVICE); 76 | Method dataMtd = ConnectivityManager.class.getDeclaredMethod("setMobileDataEnabled", boolean.class); 77 | 78 | dataMtd.setAccessible(true); 79 | dataMtd.invoke(dataManager, newState); 80 | return true; 81 | } catch (Exception e) { 82 | e.printStackTrace(); 83 | return false; 84 | } 85 | } 86 | 87 | public static boolean setMobileDataEnabledL(Context context, boolean newState) { 88 | try { 89 | // root required ??? 90 | return true; 91 | } catch(Exception e) { 92 | e.printStackTrace(); 93 | return false; 94 | } 95 | } 96 | 97 | /** 98 | * Get current BT state 99 | * @return true if Bluetooth is enabled 100 | */ 101 | public static boolean getBluetoothState() { 102 | return BluetoothAdapter.getDefaultAdapter().isEnabled(); 103 | } 104 | 105 | /** 106 | * Set the current BT state 107 | * @param newState The desired state 108 | */ 109 | public static boolean setBluetoothState(boolean newState) { 110 | BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); 111 | 112 | if(newState) { 113 | return adapter.enable(); 114 | } else { 115 | return adapter.disable(); 116 | } 117 | } 118 | 119 | /** 120 | * Set the WiFi Access Point mode state 121 | */ 122 | public static boolean setWifiApEnabled(Context context, boolean newState) { 123 | try { 124 | WifiManager manager = (WifiManager)context.getSystemService(Context.WIFI_SERVICE); 125 | 126 | 127 | if (android.os.Build.VERSION.SDK_INT > android.os.Build.VERSION_CODES.O) { 128 | if (ContextCompat.checkSelfPermission(context, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) 129 | { 130 | return setHotspot(manager, newState); 131 | } else { 132 | return false; 133 | } 134 | } else { 135 | if(newState) { 136 | //Disable WiFi first 137 | manager.setWifiEnabled(false); 138 | } 139 | 140 | Method method = manager.getClass().getMethod("setWifiApEnabled", WifiConfiguration.class, boolean.class); 141 | return (Boolean) method.invoke(manager, getWifiApConfiguration(context), newState); 142 | } 143 | } catch (Exception e) { 144 | Log.e(TAG, "Error setting Wifi AP state"); 145 | e.printStackTrace(); 146 | return false; 147 | } 148 | } 149 | 150 | /** 151 | * Get the WiFi AP state 152 | */ 153 | private static int getWifiApState(Context context) { 154 | try { 155 | WifiManager manager = (WifiManager)context.getSystemService(Context.WIFI_SERVICE); 156 | Method method = manager.getClass().getMethod("getWifiApState"); 157 | 158 | int stateNow = (Integer)method.invoke(manager); 159 | if(stateNow > 10) { 160 | stateNow -= 10; // Changes in Android 4.0 for some strange reason 161 | } 162 | 163 | return stateNow; 164 | } catch (Exception e) { 165 | Log.e(TAG, "Error getting Wifi AP state"); 166 | e.printStackTrace(); 167 | return -1; 168 | } 169 | } 170 | 171 | /** 172 | * Simply get ON or OFF state 173 | * If in doubt, use getWifiApState and check int value 174 | */ 175 | public static boolean getWifiApEnabled(Context context) { 176 | int state = getWifiApState(context); 177 | if(state == WIFI_AP_STATE_ENABLED || state == WIFI_AP_STATE_ENABLING) { 178 | return true; 179 | } else if(state == WIFI_AP_STATE_DISABLED || state == WIFI_AP_STATE_DISABLING) { 180 | return false; 181 | } else { 182 | Log.e(TAG, "Wifi AP error state: " + state); 183 | return false; 184 | } 185 | } 186 | 187 | /** 188 | * Get the Wifi AP Configuration 189 | */ 190 | private static WifiConfiguration getWifiApConfiguration(Context context) { 191 | try { 192 | WifiManager manager = (WifiManager)context.getSystemService(Context.WIFI_SERVICE); 193 | Method method = manager.getClass().getMethod("getWifiApConfiguration"); 194 | return (WifiConfiguration)method.invoke(manager); 195 | } catch (Exception e) { 196 | Log.e(TAG, "Error getting Wifi AP Configuration"); 197 | e.printStackTrace(); 198 | return null; 199 | } 200 | } 201 | 202 | @RequiresApi(android.os.Build.VERSION_CODES.O) 203 | private static boolean setHotspot(WifiManager wifiManager, boolean state) { 204 | if (state) { 205 | wifiManager.startLocalOnlyHotspot(new WifiManager.LocalOnlyHotspotCallback() { 206 | @Override 207 | public void onStarted(WifiManager.LocalOnlyHotspotReservation reservation) { 208 | super.onStarted(reservation); 209 | Log.v(TAG, "WiFi AP Started"); 210 | } 211 | 212 | @Override 213 | public void onStopped() { 214 | super.onStopped(); 215 | Log.v(TAG, "WiFi AP Stopped"); 216 | } 217 | 218 | @Override 219 | public void onFailed(int reason) { 220 | super.onFailed(reason); 221 | Log.v(TAG, "WiFi AP failed to start"); 222 | } 223 | }, new Handler()); 224 | } else { 225 | try { 226 | Method method = wifiManager.getClass().getDeclaredMethod("cancelLocalOnlyHotspotRequest"); 227 | method.invoke(wifiManager); 228 | } catch (Exception e) { 229 | Log.v(TAG, "WiFi AP failed to stop"); 230 | } 231 | } 232 | return true; 233 | } 234 | } 235 | -------------------------------------------------------------------------------- /android/DashboardRedux/src/main/java/cl_toolkit/Root.java: -------------------------------------------------------------------------------- 1 | package cl_toolkit; 2 | 3 | import android.content.Context; 4 | import android.util.Log; 5 | 6 | import java.io.DataOutputStream; 7 | import java.lang.reflect.Field; 8 | 9 | public class Root { 10 | 11 | public static final String TAG = Root.class.getName(); 12 | 13 | public static final String 14 | CMD_DATA_ENABLE = "settings put global mobile_data 1", 15 | CMD_DATA_DISABLE = "settings put global mobile_data 0"; 16 | 17 | /** 18 | * Big thanks: 19 | * http://stackoverflow.com/a/27011670 20 | */ 21 | public static boolean rootSetMobileDataEnabled(Context context, boolean newState) { 22 | try { 23 | Process process = java.lang.Runtime.getRuntime().exec("su"); 24 | DataOutputStream out = new DataOutputStream(process.getOutputStream()); 25 | 26 | // The magic recipe! 27 | String cmd = "service call phone "; 28 | int rootInt = getTransactionCode("com.android.internal.telephony.ITelephony", "setDataEnabled"); 29 | cmd += rootInt; 30 | if(newState) { 31 | cmd += " i32 1"; 32 | } else { 33 | cmd += " i32 0"; 34 | } 35 | 36 | Log.d(TAG, "Root int: " + rootInt); 37 | 38 | out.writeBytes(cmd + "\n"); 39 | out.writeBytes("exit\n"); 40 | out.flush(); 41 | int retVal = -99; // Remember this is uninit value! 42 | synchronized (process) { 43 | retVal = process.waitFor(); 44 | } 45 | 46 | Log.d(TAG, "Root mobileDataEnabled exited code " + retVal); 47 | 48 | if(retVal == 1) { 49 | // User must grant root permissions? 50 | return false; 51 | } 52 | 53 | return true; 54 | } catch(Exception e) { 55 | e.printStackTrace(); 56 | return false; 57 | } 58 | } 59 | 60 | /** 61 | * Thanks to Cygery from XDA 62 | * @param baseClassName 63 | * @param targetFieldName 64 | * @return 65 | * @throws Exception 66 | */ 67 | public static int getTransactionCode(String baseClassName, String targetFieldName) throws Exception { 68 | Class classToInvestigate = Class.forName(baseClassName); 69 | Class[] innerClasses = classToInvestigate.getDeclaredClasses(); 70 | for (Class c : innerClasses) { 71 | Field[] aClassFields = c.getDeclaredFields(); 72 | for (Field f : aClassFields) { 73 | String fieldName = f.getName(); 74 | if (fieldName != null && fieldName.equals("TRANSACTION_" + targetFieldName)) { 75 | f.setAccessible(true); // that's important 76 | return f.getInt(f); 77 | } 78 | } 79 | } 80 | 81 | // Not found 82 | throw new Exception("TRANSACTION Field not found for query { " + baseClassName + ", " + targetFieldName + " }"); 83 | } 84 | 85 | } 86 | -------------------------------------------------------------------------------- /android/DashboardRedux/src/main/java/cl_toolkit/Storage.java: -------------------------------------------------------------------------------- 1 | package cl_toolkit; 2 | 3 | import android.content.Context; 4 | import android.os.Environment; 5 | import android.util.Log; 6 | 7 | import java.io.File; 8 | import java.io.InputStream; 9 | 10 | /** 11 | * Common Android storage related helpers 12 | * @author Chris Lewis 13 | */ 14 | public class Storage { 15 | 16 | private static final String TAG = Storage.class.getName(); 17 | 18 | /** 19 | * Get user storage directory on all devices 20 | * @return String to the directory 21 | */ 22 | public static String getStorage() { 23 | return Environment.getExternalStorageDirectory().getAbsolutePath(); 24 | } 25 | 26 | /** 27 | * Get the provite (data/data) directory for storage 28 | * @param context Context object for the current application 29 | * @return String in that directory 30 | */ 31 | public static String getPrivateStorage(Context context) { 32 | return context.getFilesDir().getAbsolutePath(); 33 | } 34 | 35 | /** 36 | * Get a directory in storage/Android/data/[app]/files/* 37 | * @param context Context object for the current application 38 | * @return String path to the location, or getExternalStorageDirectory if context or result is null 39 | */ 40 | public static String getAppStorage(Context context) { 41 | String result = context.getExternalFilesDir(null).getAbsolutePath(); 42 | 43 | if(result != null) { 44 | return result; 45 | } else { 46 | Log.e(TAG, "getAppStorage() returned null!"); 47 | return Environment.getExternalStorageDirectory().getAbsolutePath(); 48 | } 49 | } 50 | 51 | /** 52 | * Convenience method to create a new foler 53 | * @param pathToFolder Path to the new folder 54 | */ 55 | public static void createFolder(String pathToFolder) { 56 | File test = new File(pathToFolder); 57 | if(!test.canRead() || !test.canWrite()) { 58 | test.mkdirs(); 59 | } 60 | } 61 | 62 | /** 63 | * Get an input stream for a file in the assets folder 64 | * @param context Application context 65 | * @param assetName Name of the asset file 66 | * @return Stream of that file. Remember to .close()! 67 | */ 68 | public static InputStream getAssetStream(Context context, String assetName) { 69 | try { 70 | return context.getAssets().open(assetName); 71 | } catch(Exception e) { 72 | e.printStackTrace(); 73 | return null; 74 | } 75 | } 76 | 77 | } 78 | -------------------------------------------------------------------------------- /android/DashboardRedux/src/main/java/cl_toolkit/UserInterface.java: -------------------------------------------------------------------------------- 1 | package cl_toolkit; 2 | 3 | import android.app.Activity; 4 | import android.app.AlertDialog; 5 | import android.content.ContentResolver; 6 | import android.content.Context; 7 | import android.content.DialogInterface; 8 | import android.provider.Settings; 9 | 10 | /** 11 | * Convenience methods for Activity GUI 12 | * @author Chris Lewis 13 | */ 14 | public class UserInterface { 15 | 16 | public static final int 17 | BRIGHTNESS_MODE_MANUAL = 0, 18 | BRIGHTNESS_MODE_AUTO = 1; 19 | 20 | /** 21 | * Show a simple yes-no dialogue 22 | * @param context Activity context 23 | * @param title Title of the dialog 24 | * @param message Main message or question 25 | * @param positiveLabel Label for the positive button 26 | * @param positiveListener Listener for when the positive button is pressed 27 | * @param negativeLabel Label for the negative button 28 | * @param negativeListener Listener for when the negative button is pressed 29 | */ 30 | public static void showDialog(Context context, String title, String message, 31 | String positiveLabel, DialogInterface.OnClickListener positiveListener, 32 | String negativeLabel, DialogInterface.OnClickListener negativeListener) { 33 | AlertDialog.Builder builder = new AlertDialog.Builder(context); 34 | builder.setTitle(title); 35 | builder.setMessage(message); 36 | 37 | if(negativeListener != null) 38 | builder.setNegativeButton(negativeLabel, negativeListener); 39 | 40 | if(positiveListener != null) 41 | builder.setPositiveButton(positiveLabel, positiveListener); 42 | 43 | AlertDialog dialog = builder.create(); 44 | dialog.show(); 45 | } 46 | 47 | public static void uiDelayed(final Activity a, final Runnable r, final long delay) { 48 | new Thread(new Runnable() { 49 | 50 | @Override 51 | public void run() { 52 | try { 53 | Thread.sleep(delay); 54 | a.runOnUiThread(r); 55 | } catch(Exception e) { 56 | e.printStackTrace(); 57 | } 58 | } 59 | 60 | }).start(); 61 | } 62 | 63 | /** 64 | * Set the state of Auto/Adaptive brightness 65 | * @param context 66 | * @param state True for auto, false for manual. Last state appears to be restored for manual 67 | */ 68 | public static void setAutoBrightnessEnabled(Context context, boolean state) { 69 | ContentResolver resolver = context.getContentResolver(); 70 | if(state) { 71 | Settings.System.putInt(resolver, Settings.System.SCREEN_BRIGHTNESS_MODE, BRIGHTNESS_MODE_AUTO); 72 | } else { 73 | Settings.System.putInt(resolver, Settings.System.SCREEN_BRIGHTNESS_MODE, BRIGHTNESS_MODE_MANUAL); 74 | } 75 | } 76 | 77 | /** 78 | * Get whether Auto Brightness is enabled by the user. 79 | * @param context 80 | * @return true if set to Auto/Adaptive. False otherwise. 81 | */ 82 | public static boolean getAutoBrightnessEnabled(Context context) { 83 | ContentResolver resolver = context.getContentResolver(); 84 | int mode; 85 | try { 86 | mode = Settings.System.getInt(resolver, Settings.System.SCREEN_BRIGHTNESS_MODE); 87 | return mode == BRIGHTNESS_MODE_AUTO ? true : false; 88 | } catch (Exception e) { 89 | System.err.println("Exception getting brightness! Returning false."); 90 | return false; 91 | } 92 | } 93 | 94 | } 95 | -------------------------------------------------------------------------------- /android/DashboardRedux/src/main/java/cl_toolkit/Web.java: -------------------------------------------------------------------------------- 1 | package cl_toolkit; 2 | 3 | import org.json.JSONException; 4 | import org.json.JSONObject; 5 | 6 | import java.io.BufferedReader; 7 | import java.io.InputStreamReader; 8 | import java.net.HttpURLConnection; 9 | import java.net.URL; 10 | 11 | /** 12 | * Web data related helpers 13 | * @author Chris Lewis 14 | */ 15 | public class Web { 16 | 17 | /** 18 | * Download a JSON file and create it as an object 19 | * @param inUrl URL of the .json file 20 | * @return The file as a JSONObject 21 | */ 22 | public static JSONObject downloadJSON(String inUrl) { 23 | try { 24 | URL url = new URL(inUrl); 25 | HttpURLConnection urlConnection = (HttpURLConnection)url.openConnection(); 26 | 27 | //Read file 28 | BufferedReader br = new BufferedReader(new InputStreamReader(urlConnection.getInputStream())); 29 | StringBuilder builder = new StringBuilder(); 30 | String buffer = br.readLine(); 31 | while(buffer != null) { 32 | builder.append(buffer); 33 | buffer = br.readLine(); 34 | } 35 | 36 | //Parse results 37 | return new JSONObject(builder.toString()); 38 | } catch(Exception e) { 39 | e.printStackTrace(); 40 | 41 | JSONObject errObj = new JSONObject(); 42 | try { 43 | errObj.put("error", "Error downloading JSON!"); 44 | } catch (JSONException e1) { 45 | e1.printStackTrace(); 46 | } 47 | return errObj; 48 | } 49 | } 50 | 51 | public static String downloadHTML(String inUrl) { 52 | try { 53 | URL url = new URL(inUrl); 54 | HttpURLConnection urlConnection = (HttpURLConnection)url.openConnection(); 55 | 56 | //Read file 57 | BufferedReader br = new BufferedReader(new InputStreamReader(urlConnection.getInputStream())); 58 | StringBuilder builder = new StringBuilder(); 59 | String buffer = br.readLine(); 60 | while(buffer != null) { 61 | builder.append(buffer); 62 | 63 | buffer = br.readLine(); 64 | } 65 | 66 | //Parse results 67 | return builder.toString(); 68 | } catch(Exception e) { 69 | e.printStackTrace(); 70 | return "error"; 71 | } 72 | } 73 | 74 | } 75 | -------------------------------------------------------------------------------- /android/DashboardRedux/src/main/java/config/Build.java: -------------------------------------------------------------------------------- 1 | package config; 2 | 3 | import java.util.UUID; 4 | 5 | /** 6 | * Build-time data 7 | */ 8 | public class Build { 9 | 10 | /** Android app version string */ 11 | public static final String VERSION = "4.17"; 12 | 13 | /** Pebble watchapp UUID */ 14 | public static final UUID WATCH_APP_UUID = UUID.fromString("d522bc8e-65f3-4edf-9651-05e1e4567021"); 15 | /** Log filename */ 16 | public static final String DEBUG_LOG_NAME = "Dashboard-log.txt"; 17 | /** Android store package name */ 18 | public static final String PACKAGE_NAME = "com.wordpress.ninedof.dashboard"; 19 | /** Max size of debug log before re-creating */ 20 | public static final int DEBUG_LOG_MAX_SIZE_BYTES = 100000; // 100kB 21 | /** Most recent compatible watchapp version */ 22 | public static final String WATCH_APP_COMPATIBLE_VERSION = "4.8"; 23 | /** Hide some log details like the protocol in production */ 24 | public static final boolean RELEASE = true; 25 | /** Number of toggles - used for safe iteration over all configuration spinners (NEVER CHANGE THIS AGAIN, may cause problems with saved config on both sides) */ 26 | public static final int NUM_TOGGLES = 9; 27 | 28 | } 29 | -------------------------------------------------------------------------------- /android/DashboardRedux/src/main/java/config/Keys.java: -------------------------------------------------------------------------------- 1 | package config; 2 | 3 | public class Keys { 4 | 5 | // Preference keys 6 | public static final String 7 | PREF_FIRST_RUN = "first-run", 8 | PREF_CHANGELOG_PREFIX = "changelog", 9 | PREF_CONFIGURE_BASE = "conf", 10 | PREF_FIND_PHONE_RUNNING = "find-phone-running", 11 | // PREF_INVERT = "invert", // Removed in v2.0 12 | // PREF_ROOT_DISABLE = "pref_key_root_disable", // Removed when PREF_KEY_DATA_ENABLED was introduced to make data toggle explicit opt-in 13 | PREF_KEY_ADMIN = "pref_key_admin", 14 | PREF_KEY_VERSION = "pref_key_version", 15 | PREF_KEY_DATA_ENABLED = "pref_key_data_enabled", 16 | PREF_KEY_ROOT_ACCESS = "pref_key_root_access", 17 | PREF_KEY_QUICK_LAUNCH_TYPE = "pref_key_quick_launch_type", 18 | PREF_KEY_QUICK_LAUNCH_ENABLED = "pref_key_quick_launch_enabled", 19 | PREF_KEY_CHARGE_NOTIFICATION_ENABLED = "pref_key_charge_notification_enabled", 20 | PREF_KEY_CHARGE_NOTIFICATION_LAST_VALUE = "pref_key_charge_notification_last_value", 21 | PREF_KEY_FIND_PHONE_FILE = "pref_key_find_phone_file", 22 | PREF_KEY_FIND_PHONE_TITLE = "pref_key_find_phone_title", 23 | PREF_VALUE_FIND_PHONE_DEFAULT = "pref_value_find_phone_no_file"; 24 | 25 | 26 | // AppMessage keys - mirror pebble/src/c/types.h 27 | public static final int 28 | AppKeyGSMName = 0, 29 | AppKeyGSMPercent = 1, 30 | AppKeyWifiName = 2, 31 | AppKeyBatteryPercent = 3, 32 | AppKeyStorageFreeGBMajor = 4, 33 | AppKeyStorageFreeGBMinor = 5, 34 | AppKeyToggleWifi = 6, 35 | AppKeyToggleData = 7, 36 | AppKeyToggleBluetooth = 8, 37 | AppKeyToggleRinger = 9, 38 | AppKeyToggleSync = 10, 39 | AppKeyToggleWifiAP = 11, 40 | AppKeyToggleFindPhone = 12, 41 | AppKeyToggleLockPhone = 13, 42 | AppKeyToggleAutoBrightness = 14, 43 | AppKeyToggleOrderString = 15, 44 | AppKeyIsLollipop = 16, 45 | AppKeyStoragePercent = 17, 46 | AppKeyQuickLaunchEnabled = 18, 47 | AppKeyQuickLaunchType = 19, 48 | AppKeyVersion = 20, 49 | 50 | ToggleTypeWifi = 0, 51 | ToggleTypeData = 1, 52 | ToggleTypeBluetooth = 2, 53 | ToggleTypeRinger = 3, 54 | ToggleTypeSync = 4, 55 | ToggleTypeWifiAP = 5, 56 | ToggleTypeFindPhone = 6, 57 | ToggleTypeLockPhone = 7, 58 | ToggleTypeAutoBrightness = 8, 59 | 60 | ToggleStateWaiting = 0, 61 | ToggleStateOff = 1, 62 | ToggleStateOn = 2, 63 | ToggleStateLoud = 3, 64 | ToggleStateVibrate = 4, 65 | ToggleStateSilent = 5, 66 | ToggleStateBrightnessAutomatic = 6, 67 | ToggleStateBrightnessManual = 7, 68 | ToggleStateLocked = 8, 69 | 70 | ErrorCodeNoRoot = 0, 71 | ErrorCodeNoDeviceAdmin = 1, 72 | ErrorCodeLockSuccess = 2, 73 | ErrorCodeDataNotEnabled = 3, 74 | ErrorCodeWrongVersion = 40, // 4 clashes with AppKeyStorageFreeGBMajor 75 | 76 | MessageTypeRequestAll = 543, 77 | MessageTypeToggle = 544; 78 | 79 | } 80 | -------------------------------------------------------------------------------- /android/DashboardRedux/src/main/java/config/Runtime.java: -------------------------------------------------------------------------------- 1 | package config; 2 | 3 | import android.content.Context; 4 | 5 | import cl_toolkit.Logger; 6 | import cl_toolkit.Storage; 7 | 8 | /** 9 | * Run-time data 10 | * @author Chris Lewis 11 | */ 12 | public class Runtime { 13 | 14 | private static Logger getLogger(Context context) { 15 | return new Logger(Storage.getAppStorage(context) + "/" + Build.DEBUG_LOG_NAME, Build.DEBUG_LOG_MAX_SIZE_BYTES); 16 | } 17 | 18 | public static void log(Context context, String TAG, String message, String level) { 19 | getLogger(context).log(TAG, message, level); 20 | } 21 | 22 | public static void logStackTrace(Context context, Exception e) { 23 | getLogger(context).logStackTrace(e); 24 | } 25 | 26 | public static void startNewSession(Context context) { 27 | getLogger(context).startNewSession(); 28 | } 29 | 30 | } -------------------------------------------------------------------------------- /android/DashboardRedux/src/main/java/fragment/About.java: -------------------------------------------------------------------------------- 1 | package fragment; 2 | 3 | import android.content.Context; 4 | import android.content.Intent; 5 | import android.os.Bundle; 6 | import android.preference.CheckBoxPreference; 7 | import android.preference.Preference; 8 | import android.preference.Preference.OnPreferenceClickListener; 9 | import android.preference.PreferenceCategory; 10 | import android.preference.PreferenceFragment; 11 | import android.preference.PreferenceScreen; 12 | import android.widget.Toast; 13 | 14 | import com.stericson.RootTools.RootTools; 15 | import com.wordpress.ninedof.dashboard.R; 16 | 17 | import activity.LogViewer; 18 | import cl_toolkit.Logger; 19 | import cl_toolkit.Platform; 20 | import config.Build; 21 | import config.Keys; 22 | import config.Runtime; 23 | 24 | public class About extends PreferenceFragment { 25 | 26 | private static final String TAG = About.class.getName(); 27 | 28 | @Override 29 | public void onCreate(Bundle savedInstanceState) { 30 | super.onCreate(savedInstanceState); 31 | 32 | final Context context = getActivity().getApplicationContext(); 33 | addPreferencesFromResource(R.xml.preferences); 34 | 35 | // Set version string 36 | Preference about = (Preference)findPreference("pref_key_version"); 37 | about.setTitle("Dashboard v" + Build.VERSION + " by Chris Lewis"); 38 | 39 | //Load prefs 40 | if(Platform.isLollipopOrAbove()) { 41 | // Enable Data toggle 42 | CheckBoxPreference dataPref = (CheckBoxPreference)findPreference(Keys.PREF_KEY_DATA_ENABLED); 43 | if(dataPref != null) { 44 | dataPref.setOnPreferenceClickListener(new OnPreferenceClickListener() { 45 | 46 | @Override 47 | public boolean onPreferenceClick(Preference preference) { 48 | if (((CheckBoxPreference)preference).isChecked()) { 49 | if(!testRoot()) { 50 | Toast.makeText(context, "Enabled, but root access is not available. The Data toggle will not work.", Toast.LENGTH_LONG).show(); 51 | } 52 | } 53 | return true; 54 | } 55 | 56 | }); 57 | } 58 | 59 | Preference rootTestPref = findPreference(Keys.PREF_KEY_ROOT_ACCESS); 60 | if (rootTestPref != null) { 61 | rootTestPref.setOnPreferenceClickListener(new OnPreferenceClickListener() { 62 | 63 | @Override 64 | public boolean onPreferenceClick(Preference preference) { 65 | boolean success = testRoot(); 66 | Toast.makeText(context, "Root access test " + 67 | (success ? "succeeded!" : "failed! Device may not be rooted, or access was not granted."), Toast.LENGTH_LONG).show(); 68 | return true; 69 | } 70 | 71 | }); 72 | } else { 73 | Runtime.log(context, TAG, "rootTestPref null!", Logger.ERROR); 74 | } 75 | } else { 76 | // Not required, remove root stuff 77 | PreferenceScreen advancedScreen = (PreferenceScreen)findPreference("pref_screen"); 78 | PreferenceCategory advancedCategory = (PreferenceCategory)findPreference("pref_category_advanced"); 79 | advancedScreen.removePreference(advancedCategory); 80 | } 81 | 82 | //Debug log 83 | Preference logPref = findPreference(Keys.PREF_KEY_VERSION); 84 | logPref.setOnPreferenceClickListener(new OnPreferenceClickListener() { 85 | 86 | @Override 87 | public boolean onPreferenceClick(Preference preference) { 88 | // Show log viewer 89 | Intent i = new Intent(getActivity(), LogViewer.class); 90 | startActivity(i); 91 | return true; 92 | } 93 | 94 | }); 95 | } 96 | 97 | private boolean testRoot() { 98 | Context context = getActivity().getApplicationContext(); 99 | 100 | // Test root access 101 | boolean success; 102 | try { 103 | success = RootTools.isAccessGiven(); 104 | } catch(Exception e) { 105 | Runtime.log(context, TAG, "Failed to get root access", Logger.ERROR); 106 | Runtime.logStackTrace(context, e); 107 | success = false; 108 | } 109 | 110 | Runtime.log(context, TAG, "Root test result: " + success, Logger.INFO); 111 | return success; 112 | } 113 | 114 | } -------------------------------------------------------------------------------- /android/DashboardRedux/src/main/java/util/PebbleUtils.java: -------------------------------------------------------------------------------- 1 | package util; 2 | 3 | import android.app.AlertDialog; 4 | import android.content.Context; 5 | import android.content.DialogInterface; 6 | import android.content.Intent; 7 | import android.net.Uri; 8 | import android.widget.Toast; 9 | 10 | import com.getpebble.android.kit.util.PebbleDictionary; 11 | 12 | import java.io.File; 13 | import java.io.FileOutputStream; 14 | import java.io.IOException; 15 | import java.io.InputStream; 16 | import java.io.OutputStream; 17 | import java.util.UUID; 18 | 19 | public class PebbleUtils { 20 | 21 | /** 22 | * Appstore install for 2.0 use (deep link reliability may vary between offical Pebble app releases...) 23 | * @param appStoreUID UID from dev-portal.getpebble.com 24 | */ 25 | public static void appStoreInstall(Context context, String appStoreUID) { 26 | Intent intent = new Intent(Intent.ACTION_VIEW); 27 | intent.setData(Uri.parse("pebble://appstore/" + appStoreUID)); 28 | intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK); 29 | intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 30 | context.startActivity(intent); 31 | } 32 | 33 | /** 34 | * Alternative sideloading method 35 | * Source: http://forums.getpebble.com/discussion/comment/103733/#Comment_103733 36 | * @param assetFilename File name of the asset to sideload 37 | */ 38 | public static void sideloadInstall(Context ctx, String assetFilename) { 39 | try { 40 | Intent intent = new Intent(Intent.ACTION_VIEW); 41 | File file = new File(ctx.getExternalFilesDir(null), assetFilename); 42 | InputStream is = ctx.getResources().getAssets().open(assetFilename); 43 | OutputStream os = new FileOutputStream(file); 44 | byte[] pbw = new byte[is.available()]; 45 | is.read(pbw); 46 | os.write(pbw); 47 | is.close(); 48 | os.close(); 49 | intent.setDataAndType(Uri.fromFile(file), "application/pbw"); 50 | intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 51 | ctx.startActivity(intent); 52 | } catch (IOException e) { 53 | Toast.makeText(ctx, "App install failed: " + e.getLocalizedMessage(), Toast.LENGTH_LONG).show(); 54 | } 55 | } 56 | 57 | /** 58 | * Let user choose install source for 2.0 use 59 | * @param pbwFilename File name for .pbw option 60 | * @param appstoreUid UID from dev-portal.getpebble.com 61 | */ 62 | public static void installEither(final Context context, final String pbwFilename, final String appstoreUid) { 63 | AlertDialog.Builder dialog = new AlertDialog.Builder(context); 64 | dialog.setTitle("Install Watchapp"); 65 | dialog.setMessage("Choose your preferred method of installation."); 66 | dialog.setPositiveButton("Local .pbw", new DialogInterface.OnClickListener() { 67 | 68 | @Override 69 | public void onClick(DialogInterface dialog, int which) { 70 | sideloadInstall(context, pbwFilename); 71 | } 72 | 73 | }); 74 | dialog.setNeutralButton("Pebble Appstore", new DialogInterface.OnClickListener() { 75 | 76 | @Override 77 | public void onClick(DialogInterface dialog, int which) { 78 | try { 79 | appStoreInstall(context, appstoreUid); 80 | } catch (Exception e) { 81 | Toast.makeText(context, "Pebble Appstore not found! Try 'Local.pbw' instead.", Toast.LENGTH_SHORT).show(); 82 | } 83 | } 84 | 85 | }); 86 | dialog.setNegativeButton("Cancel", new DialogInterface.OnClickListener() { 87 | 88 | @Override 89 | public void onClick(DialogInterface dialog, int which) { 90 | dialog.dismiss(); 91 | } 92 | 93 | }); 94 | dialog.show(); 95 | } 96 | 97 | /** 98 | * See if the Pebble Dictionary has a TupletInteger 99 | * @param dict Dictionary that may contain the key 100 | * @param key The key itself to look for data. 101 | * @return true if a K-V pair is there 102 | */ 103 | public static boolean hasInt(PebbleDictionary dict, int key) { 104 | return dict.getInteger(key) != null; 105 | } 106 | 107 | /** 108 | * Get a TupletInteger from the dictionary 109 | * @param dict Dictionary that contains the key 110 | * @param key The key to look for 111 | * @return the value associated with the key 112 | */ 113 | public static int getInt(PebbleDictionary dict, int key) { 114 | return dict.getInteger(key).intValue(); 115 | } 116 | 117 | /** 118 | * See if the Pebble Dictionary has a TupletCString 119 | * @param dict Dictionary that may contain the key 120 | * @param key The key itself to look for data. 121 | * @return true if a K-V pair is there 122 | */ 123 | public static boolean hasString(PebbleDictionary dict, int key) { 124 | return dict.getString(key) != null; 125 | } 126 | 127 | /** 128 | * Check a Receiver's intent to see if it is for this app 129 | * @param intent Broadcast intent containing Pebble data 130 | * @param thisAppUUID UUID of this app 131 | * @return true if the two match 132 | */ 133 | public static boolean isThisApp(Intent intent, UUID thisAppUUID) { 134 | return intent.getSerializableExtra("uuid").equals(thisAppUUID); 135 | } 136 | 137 | } 138 | -------------------------------------------------------------------------------- /android/DashboardRedux/src/main/java/util/VersionCheck.java: -------------------------------------------------------------------------------- 1 | package util; 2 | 3 | import java.util.UUID; 4 | 5 | import android.content.Context; 6 | import android.util.Log; 7 | 8 | import com.getpebble.android.kit.PebbleKit; 9 | import com.getpebble.android.kit.util.PebbleDictionary; 10 | 11 | /** 12 | * Check watchapp version against a known compatible version on Android 13 | * 1. Call version_check(char *version, void(*callback)(bool)); from watchapp 14 | * 2. On reception of that dictionary, call check() below to issue response and trigger callback 15 | * 16 | * @author Chris Lewis 17 | */ 18 | public class VersionCheck { 19 | 20 | private static final String TAG = VersionCheck.class.getName(); 21 | 22 | public static final int 23 | KEY_VERSION_CHECK_VERSION = 58283, 24 | KEY_VERSION_CHECK_SUCCESS = 58278, 25 | KEY_VERSION_CHECK_FAILURE = 58232; 26 | 27 | /** 28 | * To be called from a PebbleDataReceiver implementation 29 | * @param context Context of the Android app 30 | * @param uuid UUID of the watchapp 31 | * @param dict Incoming dictionary 32 | * @param localVersion Compatible version with this Android app version 33 | */ 34 | public static void check(Context context, UUID uuid, PebbleDictionary dict, String localVersion) { 35 | if(dict.getString(KEY_VERSION_CHECK_VERSION) != null) { 36 | String remoteVersion = dict.getString(KEY_VERSION_CHECK_VERSION).toString(); 37 | 38 | //Let the watch know 39 | PebbleDictionary response = new PebbleDictionary(); 40 | 41 | if(localVersion.equals(remoteVersion)) { 42 | response.addInt32(KEY_VERSION_CHECK_SUCCESS, 0); 43 | Log.d(TAG, "Version check passed!"); 44 | } else { 45 | response.addInt32(KEY_VERSION_CHECK_FAILURE, 0); 46 | Log.d(TAG, "Version check failed!"); 47 | } 48 | 49 | PebbleKit.sendDataToPebble(context, uuid, response); 50 | } 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /android/DashboardRedux/src/main/res/anim/slide_in_bottom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 7 | -------------------------------------------------------------------------------- /android/DashboardRedux/src/main/res/drawable-hdpi/ic_launcher_notif.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/C-D-Lewis/dashboard/76439ff18b98bf13ec317ec0ab7913a3c546c41c/android/DashboardRedux/src/main/res/drawable-hdpi/ic_launcher_notif.png -------------------------------------------------------------------------------- /android/DashboardRedux/src/main/res/drawable-mdpi/ic_launcher_notif.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/C-D-Lewis/dashboard/76439ff18b98bf13ec317ec0ab7913a3c546c41c/android/DashboardRedux/src/main/res/drawable-mdpi/ic_launcher_notif.png -------------------------------------------------------------------------------- /android/DashboardRedux/src/main/res/drawable-xhdpi/ap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/C-D-Lewis/dashboard/76439ff18b98bf13ec317ec0ab7913a3c546c41c/android/DashboardRedux/src/main/res/drawable-xhdpi/ap.png -------------------------------------------------------------------------------- /android/DashboardRedux/src/main/res/drawable-xhdpi/brightness.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/C-D-Lewis/dashboard/76439ff18b98bf13ec317ec0ab7913a3c546c41c/android/DashboardRedux/src/main/res/drawable-xhdpi/brightness.png -------------------------------------------------------------------------------- /android/DashboardRedux/src/main/res/drawable-xhdpi/bt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/C-D-Lewis/dashboard/76439ff18b98bf13ec317ec0ab7913a3c546c41c/android/DashboardRedux/src/main/res/drawable-xhdpi/bt.png -------------------------------------------------------------------------------- /android/DashboardRedux/src/main/res/drawable-xhdpi/button.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/C-D-Lewis/dashboard/76439ff18b98bf13ec317ec0ab7913a3c546c41c/android/DashboardRedux/src/main/res/drawable-xhdpi/button.9.png -------------------------------------------------------------------------------- /android/DashboardRedux/src/main/res/drawable-xhdpi/data.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/C-D-Lewis/dashboard/76439ff18b98bf13ec317ec0ab7913a3c546c41c/android/DashboardRedux/src/main/res/drawable-xhdpi/data.png -------------------------------------------------------------------------------- /android/DashboardRedux/src/main/res/drawable-xhdpi/empty.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/C-D-Lewis/dashboard/76439ff18b98bf13ec317ec0ab7913a3c546c41c/android/DashboardRedux/src/main/res/drawable-xhdpi/empty.png -------------------------------------------------------------------------------- /android/DashboardRedux/src/main/res/drawable-xhdpi/framed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/C-D-Lewis/dashboard/76439ff18b98bf13ec317ec0ab7913a3c546c41c/android/DashboardRedux/src/main/res/drawable-xhdpi/framed.png -------------------------------------------------------------------------------- /android/DashboardRedux/src/main/res/drawable-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/C-D-Lewis/dashboard/76439ff18b98bf13ec317ec0ab7913a3c546c41c/android/DashboardRedux/src/main/res/drawable-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/DashboardRedux/src/main/res/drawable-xhdpi/ic_launcher_notif.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/C-D-Lewis/dashboard/76439ff18b98bf13ec317ec0ab7913a3c546c41c/android/DashboardRedux/src/main/res/drawable-xhdpi/ic_launcher_notif.png -------------------------------------------------------------------------------- /android/DashboardRedux/src/main/res/drawable-xhdpi/ic_launcher_notif_large.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/C-D-Lewis/dashboard/76439ff18b98bf13ec317ec0ab7913a3c546c41c/android/DashboardRedux/src/main/res/drawable-xhdpi/ic_launcher_notif_large.png -------------------------------------------------------------------------------- /android/DashboardRedux/src/main/res/drawable-xhdpi/lock.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/C-D-Lewis/dashboard/76439ff18b98bf13ec317ec0ab7913a3c546c41c/android/DashboardRedux/src/main/res/drawable-xhdpi/lock.png -------------------------------------------------------------------------------- /android/DashboardRedux/src/main/res/drawable-xhdpi/loud.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/C-D-Lewis/dashboard/76439ff18b98bf13ec317ec0ab7913a3c546c41c/android/DashboardRedux/src/main/res/drawable-xhdpi/loud.png -------------------------------------------------------------------------------- /android/DashboardRedux/src/main/res/drawable-xhdpi/mailsrc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/C-D-Lewis/dashboard/76439ff18b98bf13ec317ec0ab7913a3c546c41c/android/DashboardRedux/src/main/res/drawable-xhdpi/mailsrc.png -------------------------------------------------------------------------------- /android/DashboardRedux/src/main/res/drawable-xhdpi/paypal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/C-D-Lewis/dashboard/76439ff18b98bf13ec317ec0ab7913a3c546c41c/android/DashboardRedux/src/main/res/drawable-xhdpi/paypal.png -------------------------------------------------------------------------------- /android/DashboardRedux/src/main/res/drawable-xhdpi/phone.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/C-D-Lewis/dashboard/76439ff18b98bf13ec317ec0ab7913a3c546c41c/android/DashboardRedux/src/main/res/drawable-xhdpi/phone.png -------------------------------------------------------------------------------- /android/DashboardRedux/src/main/res/drawable-xhdpi/priority.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/C-D-Lewis/dashboard/76439ff18b98bf13ec317ec0ab7913a3c546c41c/android/DashboardRedux/src/main/res/drawable-xhdpi/priority.png -------------------------------------------------------------------------------- /android/DashboardRedux/src/main/res/drawable-xhdpi/questionmark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/C-D-Lewis/dashboard/76439ff18b98bf13ec317ec0ab7913a3c546c41c/android/DashboardRedux/src/main/res/drawable-xhdpi/questionmark.png -------------------------------------------------------------------------------- /android/DashboardRedux/src/main/res/drawable-xhdpi/rate.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/C-D-Lewis/dashboard/76439ff18b98bf13ec317ec0ab7913a3c546c41c/android/DashboardRedux/src/main/res/drawable-xhdpi/rate.png -------------------------------------------------------------------------------- /android/DashboardRedux/src/main/res/drawable-xhdpi/silent.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/C-D-Lewis/dashboard/76439ff18b98bf13ec317ec0ab7913a3c546c41c/android/DashboardRedux/src/main/res/drawable-xhdpi/silent.png -------------------------------------------------------------------------------- /android/DashboardRedux/src/main/res/drawable-xhdpi/sync.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/C-D-Lewis/dashboard/76439ff18b98bf13ec317ec0ab7913a3c546c41c/android/DashboardRedux/src/main/res/drawable-xhdpi/sync.png -------------------------------------------------------------------------------- /android/DashboardRedux/src/main/res/drawable-xhdpi/tweet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/C-D-Lewis/dashboard/76439ff18b98bf13ec317ec0ab7913a3c546c41c/android/DashboardRedux/src/main/res/drawable-xhdpi/tweet.png -------------------------------------------------------------------------------- /android/DashboardRedux/src/main/res/drawable-xhdpi/vibrate.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/C-D-Lewis/dashboard/76439ff18b98bf13ec317ec0ab7913a3c546c41c/android/DashboardRedux/src/main/res/drawable-xhdpi/vibrate.png -------------------------------------------------------------------------------- /android/DashboardRedux/src/main/res/drawable-xhdpi/wifi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/C-D-Lewis/dashboard/76439ff18b98bf13ec317ec0ab7913a3c546c41c/android/DashboardRedux/src/main/res/drawable-xhdpi/wifi.png -------------------------------------------------------------------------------- /android/DashboardRedux/src/main/res/drawable-xhdpi/wordpress.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/C-D-Lewis/dashboard/76439ff18b98bf13ec317ec0ab7913a3c546c41c/android/DashboardRedux/src/main/res/drawable-xhdpi/wordpress.png -------------------------------------------------------------------------------- /android/DashboardRedux/src/main/res/layout/activity_log_viewer.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 14 | 15 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /android/DashboardRedux/src/main/res/layout/settings_fragment.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /android/DashboardRedux/src/main/res/layout/toggle_spinner_item.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 13 | 14 | 19 | 20 | -------------------------------------------------------------------------------- /android/DashboardRedux/src/main/res/menu/menu_landing.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 10 | -------------------------------------------------------------------------------- /android/DashboardRedux/src/main/res/menu/menu_log_viewer.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 10 | 11 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /android/DashboardRedux/src/main/res/values-v11/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /android/DashboardRedux/src/main/res/values-v14/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /android/DashboardRedux/src/main/res/values-v21/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /android/DashboardRedux/src/main/res/values-w820dp/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 64dp 9 | 10 | 11 | -------------------------------------------------------------------------------- /android/DashboardRedux/src/main/res/values/colours.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #AA0000 4 | #880000 5 | #990000 6 | 7 | -------------------------------------------------------------------------------- /android/DashboardRedux/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 16dp 5 | 16dp 6 | 7 | 8 | -------------------------------------------------------------------------------- /android/DashboardRedux/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Dashboard 5 | Settings 6 | LandingL 7 | Hello world! 8 | 9 | 10 | -------------------------------------------------------------------------------- /android/DashboardRedux/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 14 | 15 | 16 | 20 | 21 | 22 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /android/DashboardRedux/src/main/res/xml/actionbar_tab_indicator.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 10 | 13 | 14 | 15 | 16 | 17 | 20 | 23 | 24 | -------------------------------------------------------------------------------- /android/DashboardRedux/src/main/res/xml/admin.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /android/DashboardRedux/src/main/res/xml/preferences.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 8 | 9 | 12 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 31 | 34 | 35 | 40 | 43 | 44 | 45 | 50 | 53 | 54 | 59 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | -------------------------------------------------------------------------------- /android/PebbleHomescreen.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /android/android.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | buildscript { 4 | repositories { 5 | jcenter() 6 | google() 7 | } 8 | dependencies { 9 | classpath 'com.android.tools.build:gradle:4.0.2' 10 | 11 | // NOTE: Do not place your application dependencies here; they belong 12 | // in the individual module build.gradle files 13 | } 14 | } 15 | 16 | allprojects { 17 | repositories { 18 | jcenter() 19 | google() 20 | } 21 | } 22 | 23 | task clean(type: Delete) { 24 | delete rootProject.buildDir 25 | } 26 | -------------------------------------------------------------------------------- /android/gradle.properties: -------------------------------------------------------------------------------- 1 | android.enableJetifier=true 2 | android.useAndroidX=true -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/C-D-Lewis/dashboard/76439ff18b98bf13ec317ec0ab7913a3c546c41c/android/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Sat Oct 10 17:00:42 BST 2020 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.1.1-all.zip 7 | -------------------------------------------------------------------------------- /android/gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn () { 37 | echo "$*" 38 | } 39 | 40 | die () { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Escape application args 158 | save () { 159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 160 | echo " " 161 | } 162 | APP_ARGS=$(save "$@") 163 | 164 | # Collect all arguments for the java command, following the shell quoting and substitution rules 165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 166 | 167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 169 | cd "$(dirname "$0")" 170 | fi 171 | 172 | exec "$JAVACMD" "$@" 173 | -------------------------------------------------------------------------------- /android/gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /android/settings.gradle: -------------------------------------------------------------------------------- 1 | include ':DashboardRedux' 2 | -------------------------------------------------------------------------------- /assets/listing/app_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/C-D-Lewis/dashboard/76439ff18b98bf13ec317ec0ab7913a3c546c41c/assets/listing/app_logo.png -------------------------------------------------------------------------------- /assets/listing/blogbanner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/C-D-Lewis/dashboard/76439ff18b98bf13ec317ec0ab7913a3c546c41c/assets/listing/blogbanner.png -------------------------------------------------------------------------------- /assets/listing/icon-large.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/C-D-Lewis/dashboard/76439ff18b98bf13ec317ec0ab7913a3c546c41c/assets/listing/icon-large.png -------------------------------------------------------------------------------- /assets/listing/icon-small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/C-D-Lewis/dashboard/76439ff18b98bf13ec317ec0ab7913a3c546c41c/assets/listing/icon-small.png -------------------------------------------------------------------------------- /assets/listing/icons-original-size/ap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/C-D-Lewis/dashboard/76439ff18b98bf13ec317ec0ab7913a3c546c41c/assets/listing/icons-original-size/ap.png -------------------------------------------------------------------------------- /assets/listing/icons-original-size/brightness.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/C-D-Lewis/dashboard/76439ff18b98bf13ec317ec0ab7913a3c546c41c/assets/listing/icons-original-size/brightness.png -------------------------------------------------------------------------------- /assets/listing/icons-original-size/bt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/C-D-Lewis/dashboard/76439ff18b98bf13ec317ec0ab7913a3c546c41c/assets/listing/icons-original-size/bt.png -------------------------------------------------------------------------------- /assets/listing/icons-original-size/data.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/C-D-Lewis/dashboard/76439ff18b98bf13ec317ec0ab7913a3c546c41c/assets/listing/icons-original-size/data.png -------------------------------------------------------------------------------- /assets/listing/icons-original-size/lock.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/C-D-Lewis/dashboard/76439ff18b98bf13ec317ec0ab7913a3c546c41c/assets/listing/icons-original-size/lock.png -------------------------------------------------------------------------------- /assets/listing/icons-original-size/loud.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/C-D-Lewis/dashboard/76439ff18b98bf13ec317ec0ab7913a3c546c41c/assets/listing/icons-original-size/loud.png -------------------------------------------------------------------------------- /assets/listing/icons-original-size/phone.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/C-D-Lewis/dashboard/76439ff18b98bf13ec317ec0ab7913a3c546c41c/assets/listing/icons-original-size/phone.png -------------------------------------------------------------------------------- /assets/listing/icons-original-size/priority.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/C-D-Lewis/dashboard/76439ff18b98bf13ec317ec0ab7913a3c546c41c/assets/listing/icons-original-size/priority.png -------------------------------------------------------------------------------- /assets/listing/icons-original-size/silent.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/C-D-Lewis/dashboard/76439ff18b98bf13ec317ec0ab7913a3c546c41c/assets/listing/icons-original-size/silent.png -------------------------------------------------------------------------------- /assets/listing/icons-original-size/sync.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/C-D-Lewis/dashboard/76439ff18b98bf13ec317ec0ab7913a3c546c41c/assets/listing/icons-original-size/sync.png -------------------------------------------------------------------------------- /assets/listing/icons-original-size/vibrate.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/C-D-Lewis/dashboard/76439ff18b98bf13ec317ec0ab7913a3c546c41c/assets/listing/icons-original-size/vibrate.png -------------------------------------------------------------------------------- /assets/listing/icons-original-size/wifi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/C-D-Lewis/dashboard/76439ff18b98bf13ec317ec0ab7913a3c546c41c/assets/listing/icons-original-size/wifi.png -------------------------------------------------------------------------------- /assets/listing/marketing-aplite.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/C-D-Lewis/dashboard/76439ff18b98bf13ec317ec0ab7913a3c546c41c/assets/listing/marketing-aplite.png -------------------------------------------------------------------------------- /assets/listing/marketing-basalt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/C-D-Lewis/dashboard/76439ff18b98bf13ec317ec0ab7913a3c546c41c/assets/listing/marketing-basalt.png -------------------------------------------------------------------------------- /assets/listing/marketing-chalk.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/C-D-Lewis/dashboard/76439ff18b98bf13ec317ec0ab7913a3c546c41c/assets/listing/marketing-chalk.png -------------------------------------------------------------------------------- /assets/listing/marketing-gplay.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/C-D-Lewis/dashboard/76439ff18b98bf13ec317ec0ab7913a3c546c41c/assets/listing/marketing-gplay.png -------------------------------------------------------------------------------- /assets/listing/marketing-src-aplite.xcf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/C-D-Lewis/dashboard/76439ff18b98bf13ec317ec0ab7913a3c546c41c/assets/listing/marketing-src-aplite.xcf -------------------------------------------------------------------------------- /assets/listing/marketing-src-basalt.xcf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/C-D-Lewis/dashboard/76439ff18b98bf13ec317ec0ab7913a3c546c41c/assets/listing/marketing-src-basalt.xcf -------------------------------------------------------------------------------- /assets/listing/marketing-src-chalk.xcf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/C-D-Lewis/dashboard/76439ff18b98bf13ec317ec0ab7913a3c546c41c/assets/listing/marketing-src-chalk.xcf -------------------------------------------------------------------------------- /assets/listing/marketing-src-gplay.xcf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/C-D-Lewis/dashboard/76439ff18b98bf13ec317ec0ab7913a3c546c41c/assets/listing/marketing-src-gplay.xcf -------------------------------------------------------------------------------- /assets/releases/Dashboard-release-1.13.apk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/C-D-Lewis/dashboard/76439ff18b98bf13ec317ec0ab7913a3c546c41c/assets/releases/Dashboard-release-1.13.apk -------------------------------------------------------------------------------- /assets/releases/Dashboard-release-1.14.apk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/C-D-Lewis/dashboard/76439ff18b98bf13ec317ec0ab7913a3c546c41c/assets/releases/Dashboard-release-1.14.apk -------------------------------------------------------------------------------- /assets/releases/Dashboard-release-1.15.apk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/C-D-Lewis/dashboard/76439ff18b98bf13ec317ec0ab7913a3c546c41c/assets/releases/Dashboard-release-1.15.apk -------------------------------------------------------------------------------- /assets/releases/Dashboard-release-1.16.apk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/C-D-Lewis/dashboard/76439ff18b98bf13ec317ec0ab7913a3c546c41c/assets/releases/Dashboard-release-1.16.apk -------------------------------------------------------------------------------- /assets/releases/Dashboard-release-1.17.apk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/C-D-Lewis/dashboard/76439ff18b98bf13ec317ec0ab7913a3c546c41c/assets/releases/Dashboard-release-1.17.apk -------------------------------------------------------------------------------- /assets/releases/Dashboard-release-1.19.apk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/C-D-Lewis/dashboard/76439ff18b98bf13ec317ec0ab7913a3c546c41c/assets/releases/Dashboard-release-1.19.apk -------------------------------------------------------------------------------- /assets/releases/Dashboard-release-1.20.apk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/C-D-Lewis/dashboard/76439ff18b98bf13ec317ec0ab7913a3c546c41c/assets/releases/Dashboard-release-1.20.apk -------------------------------------------------------------------------------- /assets/releases/Dashboard-release-3.0.apk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/C-D-Lewis/dashboard/76439ff18b98bf13ec317ec0ab7913a3c546c41c/assets/releases/Dashboard-release-3.0.apk -------------------------------------------------------------------------------- /assets/releases/Dashboard-release-3.1.apk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/C-D-Lewis/dashboard/76439ff18b98bf13ec317ec0ab7913a3c546c41c/assets/releases/Dashboard-release-3.1.apk -------------------------------------------------------------------------------- /assets/releases/Dashboard-release-3.2.apk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/C-D-Lewis/dashboard/76439ff18b98bf13ec317ec0ab7913a3c546c41c/assets/releases/Dashboard-release-3.2.apk -------------------------------------------------------------------------------- /assets/releases/Dashboard-release-4.0.apk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/C-D-Lewis/dashboard/76439ff18b98bf13ec317ec0ab7913a3c546c41c/assets/releases/Dashboard-release-4.0.apk -------------------------------------------------------------------------------- /assets/releases/Dashboard-release-4.1.apk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/C-D-Lewis/dashboard/76439ff18b98bf13ec317ec0ab7913a3c546c41c/assets/releases/Dashboard-release-4.1.apk -------------------------------------------------------------------------------- /assets/releases/Dashboard-release-4.10.apk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/C-D-Lewis/dashboard/76439ff18b98bf13ec317ec0ab7913a3c546c41c/assets/releases/Dashboard-release-4.10.apk -------------------------------------------------------------------------------- /assets/releases/Dashboard-release-4.11.apk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/C-D-Lewis/dashboard/76439ff18b98bf13ec317ec0ab7913a3c546c41c/assets/releases/Dashboard-release-4.11.apk -------------------------------------------------------------------------------- /assets/releases/Dashboard-release-4.12.apk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/C-D-Lewis/dashboard/76439ff18b98bf13ec317ec0ab7913a3c546c41c/assets/releases/Dashboard-release-4.12.apk -------------------------------------------------------------------------------- /assets/releases/Dashboard-release-4.14.apk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/C-D-Lewis/dashboard/76439ff18b98bf13ec317ec0ab7913a3c546c41c/assets/releases/Dashboard-release-4.14.apk -------------------------------------------------------------------------------- /assets/releases/Dashboard-release-4.15.apk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/C-D-Lewis/dashboard/76439ff18b98bf13ec317ec0ab7913a3c546c41c/assets/releases/Dashboard-release-4.15.apk -------------------------------------------------------------------------------- /assets/releases/Dashboard-release-4.16.apk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/C-D-Lewis/dashboard/76439ff18b98bf13ec317ec0ab7913a3c546c41c/assets/releases/Dashboard-release-4.16.apk -------------------------------------------------------------------------------- /assets/releases/Dashboard-release-4.17.apk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/C-D-Lewis/dashboard/76439ff18b98bf13ec317ec0ab7913a3c546c41c/assets/releases/Dashboard-release-4.17.apk -------------------------------------------------------------------------------- /assets/releases/Dashboard-release-4.2.apk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/C-D-Lewis/dashboard/76439ff18b98bf13ec317ec0ab7913a3c546c41c/assets/releases/Dashboard-release-4.2.apk -------------------------------------------------------------------------------- /assets/releases/Dashboard-release-4.3.apk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/C-D-Lewis/dashboard/76439ff18b98bf13ec317ec0ab7913a3c546c41c/assets/releases/Dashboard-release-4.3.apk -------------------------------------------------------------------------------- /assets/releases/Dashboard-release-4.4.apk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/C-D-Lewis/dashboard/76439ff18b98bf13ec317ec0ab7913a3c546c41c/assets/releases/Dashboard-release-4.4.apk -------------------------------------------------------------------------------- /assets/releases/Dashboard-release-4.5.apk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/C-D-Lewis/dashboard/76439ff18b98bf13ec317ec0ab7913a3c546c41c/assets/releases/Dashboard-release-4.5.apk -------------------------------------------------------------------------------- /assets/releases/Dashboard-release-4.6.apk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/C-D-Lewis/dashboard/76439ff18b98bf13ec317ec0ab7913a3c546c41c/assets/releases/Dashboard-release-4.6.apk -------------------------------------------------------------------------------- /assets/releases/Dashboard-release-4.7.apk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/C-D-Lewis/dashboard/76439ff18b98bf13ec317ec0ab7913a3c546c41c/assets/releases/Dashboard-release-4.7.apk -------------------------------------------------------------------------------- /assets/releases/Dashboard-release-4.8.apk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/C-D-Lewis/dashboard/76439ff18b98bf13ec317ec0ab7913a3c546c41c/assets/releases/Dashboard-release-4.8.apk -------------------------------------------------------------------------------- /assets/releases/Dashboard-release-4.9.apk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/C-D-Lewis/dashboard/76439ff18b98bf13ec317ec0ab7913a3c546c41c/assets/releases/Dashboard-release-4.9.apk -------------------------------------------------------------------------------- /assets/releases/dashboard-4.0.pbw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/C-D-Lewis/dashboard/76439ff18b98bf13ec317ec0ab7913a3c546c41c/assets/releases/dashboard-4.0.pbw -------------------------------------------------------------------------------- /assets/releases/dashboard-4.1.pbw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/C-D-Lewis/dashboard/76439ff18b98bf13ec317ec0ab7913a3c546c41c/assets/releases/dashboard-4.1.pbw -------------------------------------------------------------------------------- /assets/releases/dashboard-4.2.pbw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/C-D-Lewis/dashboard/76439ff18b98bf13ec317ec0ab7913a3c546c41c/assets/releases/dashboard-4.2.pbw -------------------------------------------------------------------------------- /assets/releases/dashboard-4.3.pbw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/C-D-Lewis/dashboard/76439ff18b98bf13ec317ec0ab7913a3c546c41c/assets/releases/dashboard-4.3.pbw -------------------------------------------------------------------------------- /assets/releases/dashboard-4.5.pbw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/C-D-Lewis/dashboard/76439ff18b98bf13ec317ec0ab7913a3c546c41c/assets/releases/dashboard-4.5.pbw -------------------------------------------------------------------------------- /assets/releases/dashboard-4.6.pbw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/C-D-Lewis/dashboard/76439ff18b98bf13ec317ec0ab7913a3c546c41c/assets/releases/dashboard-4.6.pbw -------------------------------------------------------------------------------- /assets/releases/dashboard-4.8.pbw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/C-D-Lewis/dashboard/76439ff18b98bf13ec317ec0ab7913a3c546c41c/assets/releases/dashboard-4.8.pbw -------------------------------------------------------------------------------- /assets/screenshots/Screenshot_20160504-164142.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/C-D-Lewis/dashboard/76439ff18b98bf13ec317ec0ab7913a3c546c41c/assets/screenshots/Screenshot_20160504-164142.png -------------------------------------------------------------------------------- /assets/screenshots/Screenshot_20160504-164150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/C-D-Lewis/dashboard/76439ff18b98bf13ec317ec0ab7913a3c546c41c/assets/screenshots/Screenshot_20160504-164150.png -------------------------------------------------------------------------------- /assets/screenshots/aplite1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/C-D-Lewis/dashboard/76439ff18b98bf13ec317ec0ab7913a3c546c41c/assets/screenshots/aplite1.png -------------------------------------------------------------------------------- /assets/screenshots/aplite2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/C-D-Lewis/dashboard/76439ff18b98bf13ec317ec0ab7913a3c546c41c/assets/screenshots/aplite2.png -------------------------------------------------------------------------------- /assets/screenshots/aplite3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/C-D-Lewis/dashboard/76439ff18b98bf13ec317ec0ab7913a3c546c41c/assets/screenshots/aplite3.png -------------------------------------------------------------------------------- /assets/screenshots/aplite4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/C-D-Lewis/dashboard/76439ff18b98bf13ec317ec0ab7913a3c546c41c/assets/screenshots/aplite4.png -------------------------------------------------------------------------------- /assets/screenshots/basalt1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/C-D-Lewis/dashboard/76439ff18b98bf13ec317ec0ab7913a3c546c41c/assets/screenshots/basalt1.png -------------------------------------------------------------------------------- /assets/screenshots/basalt2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/C-D-Lewis/dashboard/76439ff18b98bf13ec317ec0ab7913a3c546c41c/assets/screenshots/basalt2.png -------------------------------------------------------------------------------- /assets/screenshots/basalt3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/C-D-Lewis/dashboard/76439ff18b98bf13ec317ec0ab7913a3c546c41c/assets/screenshots/basalt3.png -------------------------------------------------------------------------------- /assets/screenshots/basalt4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/C-D-Lewis/dashboard/76439ff18b98bf13ec317ec0ab7913a3c546c41c/assets/screenshots/basalt4.png -------------------------------------------------------------------------------- /assets/screenshots/chalk1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/C-D-Lewis/dashboard/76439ff18b98bf13ec317ec0ab7913a3c546c41c/assets/screenshots/chalk1.png -------------------------------------------------------------------------------- /assets/screenshots/chalk2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/C-D-Lewis/dashboard/76439ff18b98bf13ec317ec0ab7913a3c546c41c/assets/screenshots/chalk2.png -------------------------------------------------------------------------------- /assets/screenshots/chalk3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/C-D-Lewis/dashboard/76439ff18b98bf13ec317ec0ab7913a3c546c41c/assets/screenshots/chalk3.png -------------------------------------------------------------------------------- /assets/screenshots/chalk4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/C-D-Lewis/dashboard/76439ff18b98bf13ec317ec0ab7913a3c546c41c/assets/screenshots/chalk4.png -------------------------------------------------------------------------------- /pebble/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Dashboard", 3 | "version": "4.8.0", 4 | "author": "Chris Lewis", 5 | "private": true, 6 | "dependencies": {}, 7 | "keywords": [ 8 | "pebble-app" 9 | ], 10 | "pebble": { 11 | "sdkVersion": "3", 12 | "resources": { 13 | "media": [ 14 | { 15 | "type": "bitmap", 16 | "name": "LOGO", 17 | "file": "images/logo.png" 18 | }, 19 | { 20 | "type": "bitmap", 21 | "name": "TICK", 22 | "file": "images/tick.png" 23 | }, 24 | { 25 | "type": "bitmap", 26 | "name": "UP", 27 | "file": "images/up.png" 28 | }, 29 | { 30 | "type": "bitmap", 31 | "name": "SELECT", 32 | "file": "images/select.png" 33 | }, 34 | { 35 | "type": "bitmap", 36 | "name": "DOWN", 37 | "file": "images/down.png" 38 | }, 39 | { 40 | "type": "bitmap", 41 | "name": "WIFI", 42 | "file": "icons/wifi.png" 43 | }, 44 | { 45 | "type": "bitmap", 46 | "name": "DATA", 47 | "file": "icons/data.png" 48 | }, 49 | { 50 | "type": "bitmap", 51 | "name": "BLUETOOTH", 52 | "file": "icons/bt.png" 53 | }, 54 | { 55 | "type": "bitmap", 56 | "name": "RINGER_LOUD", 57 | "file": "icons/loud.png" 58 | }, 59 | { 60 | "type": "bitmap", 61 | "name": "RINGER_VIBRATE", 62 | "file": "icons/vibrate.png" 63 | }, 64 | { 65 | "type": "bitmap", 66 | "name": "RINGER_SILENT", 67 | "file": "icons/silent.png" 68 | }, 69 | { 70 | "type": "bitmap", 71 | "name": "RINGER_PRIORITY", 72 | "file": "icons/priority.png" 73 | }, 74 | { 75 | "type": "bitmap", 76 | "name": "SYNC", 77 | "file": "icons/sync.png" 78 | }, 79 | { 80 | "type": "bitmap", 81 | "name": "WIFI_AP", 82 | "file": "icons/ap.png" 83 | }, 84 | { 85 | "type": "bitmap", 86 | "name": "FIND_PHONE", 87 | "file": "icons/phone.png" 88 | }, 89 | { 90 | "type": "bitmap", 91 | "name": "LOCK_PHONE", 92 | "file": "icons/lock.png" 93 | }, 94 | { 95 | "type": "bitmap", 96 | "name": "BRIGHTNESS", 97 | "file": "icons/brightness.png" 98 | }, 99 | { 100 | "menuIcon": true, 101 | "type": "bitmap", 102 | "name": "APP_ICON", 103 | "file": "images/app_icon.png" 104 | } 105 | ] 106 | }, 107 | "projectType": "native", 108 | "uuid": "d522bc8e-65f3-4edf-9651-05e1e4567021", 109 | "messageKeys": { 110 | "dummy": 0 111 | }, 112 | "enableMultiJS": true, 113 | "displayName": "Dashboard", 114 | "watchapp": { 115 | "onlyShownOnCommunication": false, 116 | "hiddenApp": false, 117 | "watchface": false 118 | }, 119 | "targetPlatforms": [ 120 | "aplite", 121 | "basalt", 122 | "chalk", 123 | "diorite" 124 | ], 125 | "capabilities": [] 126 | } 127 | } -------------------------------------------------------------------------------- /pebble/resources/icons/ap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/C-D-Lewis/dashboard/76439ff18b98bf13ec317ec0ab7913a3c546c41c/pebble/resources/icons/ap.png -------------------------------------------------------------------------------- /pebble/resources/icons/brightness.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/C-D-Lewis/dashboard/76439ff18b98bf13ec317ec0ab7913a3c546c41c/pebble/resources/icons/brightness.png -------------------------------------------------------------------------------- /pebble/resources/icons/bt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/C-D-Lewis/dashboard/76439ff18b98bf13ec317ec0ab7913a3c546c41c/pebble/resources/icons/bt.png -------------------------------------------------------------------------------- /pebble/resources/icons/data.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/C-D-Lewis/dashboard/76439ff18b98bf13ec317ec0ab7913a3c546c41c/pebble/resources/icons/data.png -------------------------------------------------------------------------------- /pebble/resources/icons/lock.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/C-D-Lewis/dashboard/76439ff18b98bf13ec317ec0ab7913a3c546c41c/pebble/resources/icons/lock.png -------------------------------------------------------------------------------- /pebble/resources/icons/loud.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/C-D-Lewis/dashboard/76439ff18b98bf13ec317ec0ab7913a3c546c41c/pebble/resources/icons/loud.png -------------------------------------------------------------------------------- /pebble/resources/icons/phone.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/C-D-Lewis/dashboard/76439ff18b98bf13ec317ec0ab7913a3c546c41c/pebble/resources/icons/phone.png -------------------------------------------------------------------------------- /pebble/resources/icons/priority.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/C-D-Lewis/dashboard/76439ff18b98bf13ec317ec0ab7913a3c546c41c/pebble/resources/icons/priority.png -------------------------------------------------------------------------------- /pebble/resources/icons/silent.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/C-D-Lewis/dashboard/76439ff18b98bf13ec317ec0ab7913a3c546c41c/pebble/resources/icons/silent.png -------------------------------------------------------------------------------- /pebble/resources/icons/sync.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/C-D-Lewis/dashboard/76439ff18b98bf13ec317ec0ab7913a3c546c41c/pebble/resources/icons/sync.png -------------------------------------------------------------------------------- /pebble/resources/icons/vibrate.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/C-D-Lewis/dashboard/76439ff18b98bf13ec317ec0ab7913a3c546c41c/pebble/resources/icons/vibrate.png -------------------------------------------------------------------------------- /pebble/resources/icons/wifi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/C-D-Lewis/dashboard/76439ff18b98bf13ec317ec0ab7913a3c546c41c/pebble/resources/icons/wifi.png -------------------------------------------------------------------------------- /pebble/resources/images/app_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/C-D-Lewis/dashboard/76439ff18b98bf13ec317ec0ab7913a3c546c41c/pebble/resources/images/app_icon.png -------------------------------------------------------------------------------- /pebble/resources/images/down.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/C-D-Lewis/dashboard/76439ff18b98bf13ec317ec0ab7913a3c546c41c/pebble/resources/images/down.png -------------------------------------------------------------------------------- /pebble/resources/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/C-D-Lewis/dashboard/76439ff18b98bf13ec317ec0ab7913a3c546c41c/pebble/resources/images/logo.png -------------------------------------------------------------------------------- /pebble/resources/images/select.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/C-D-Lewis/dashboard/76439ff18b98bf13ec317ec0ab7913a3c546c41c/pebble/resources/images/select.png -------------------------------------------------------------------------------- /pebble/resources/images/tick.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/C-D-Lewis/dashboard/76439ff18b98bf13ec317ec0ab7913a3c546c41c/pebble/resources/images/tick.png -------------------------------------------------------------------------------- /pebble/resources/images/up.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/C-D-Lewis/dashboard/76439ff18b98bf13ec317ec0ab7913a3c546c41c/pebble/resources/images/up.png -------------------------------------------------------------------------------- /pebble/src/c/config.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #define TEST false // Use local mock data 6 | #define DEBUG false 7 | 8 | #define VERSION "4.8" 9 | 10 | #define BUFFER_SIZE_IN 256 11 | #define BUFFER_SIZE_OUT 64 12 | #define INITIAL_DELAY 100 13 | #define NETWORK_MAX_LENGTH 32 14 | #define COMM_TIMEOUT_MS 1000 15 | #define SYNC_TIMER_INTERVAL_S 30 16 | #define TRANSITION_DURATION 200 17 | #define MAX_WAKEUPS 8 -------------------------------------------------------------------------------- /pebble/src/c/lib/new_number_window/new_number_window.c: -------------------------------------------------------------------------------- 1 | #include "new_number_window.h" 2 | 3 | static Window *s_window; 4 | static TextLayer *s_title_layer, *s_value_layer; 5 | static ActionBarLayer *s_action_bar; 6 | static StatusBarLayer *s_status_bar; 7 | 8 | static GBitmap *s_up_bitmap, *s_select_bitmap, *s_down_bitmap; 9 | static NewNumberWindowCallback *s_callback; 10 | static char s_title_buffer[32]; 11 | static int s_min, s_max, s_step, s_value; 12 | 13 | static void set_value() { 14 | static char s_buff[8]; 15 | snprintf(s_buff, sizeof(s_buff), "%d", s_value); 16 | text_layer_set_text(s_value_layer, s_buff); 17 | } 18 | 19 | static void select_click_handler(ClickRecognizerRef recognizer, void *context) { 20 | // Accept value 21 | window_stack_pop(true); 22 | s_callback(s_value); 23 | } 24 | 25 | static void up_click_handler(ClickRecognizerRef recognizer, void *context) { 26 | if(s_value < s_max) { 27 | s_value += s_step; 28 | } 29 | set_value(); 30 | } 31 | 32 | static void down_click_handler(ClickRecognizerRef recognizer, void *context) { 33 | if(s_value > s_min) { 34 | s_value -= s_step; 35 | } 36 | set_value(); 37 | } 38 | 39 | static void click_config_provider(void *context) { 40 | window_single_click_subscribe(BUTTON_ID_SELECT, select_click_handler); 41 | window_single_repeating_click_subscribe(BUTTON_ID_UP, 200, up_click_handler); 42 | window_single_repeating_click_subscribe(BUTTON_ID_DOWN, 200, down_click_handler); 43 | } 44 | 45 | static void window_load(Window *window) { 46 | Layer *window_layer = window_get_root_layer(window); 47 | GRect bounds = layer_get_bounds(window_layer); 48 | 49 | s_up_bitmap = gbitmap_create_with_resource(RESOURCE_ID_UP); 50 | s_select_bitmap = gbitmap_create_with_resource(RESOURCE_ID_SELECT); 51 | s_down_bitmap = gbitmap_create_with_resource(RESOURCE_ID_DOWN); 52 | 53 | s_action_bar = action_bar_layer_create(); 54 | action_bar_layer_set_icon(s_action_bar, BUTTON_ID_UP, s_up_bitmap); 55 | action_bar_layer_set_icon(s_action_bar, BUTTON_ID_SELECT, s_select_bitmap); 56 | action_bar_layer_set_icon(s_action_bar, BUTTON_ID_DOWN, s_down_bitmap); 57 | action_bar_layer_set_click_config_provider(s_action_bar, click_config_provider); 58 | action_bar_layer_add_to_window(s_action_bar, window); 59 | 60 | const int x_margin = PBL_IF_ROUND_ELSE(0, ACTION_BAR_WIDTH); 61 | const int y_margin = PBL_IF_ROUND_ELSE(35, 25); 62 | s_title_layer = text_layer_create(grect_inset(bounds, GEdgeInsets(y_margin, x_margin, 0, 0))); 63 | text_layer_set_font(s_title_layer, fonts_get_system_font(FONT_KEY_GOTHIC_24_BOLD)); 64 | text_layer_set_background_color(s_title_layer, GColorClear); 65 | text_layer_set_text_alignment(s_title_layer, GTextAlignmentCenter); 66 | text_layer_set_overflow_mode(s_title_layer, GTextOverflowModeWordWrap); 67 | layer_add_child(window_layer, text_layer_get_layer(s_title_layer)); 68 | 69 | const int text_size = 50; 70 | const int y_centered = (bounds.size.h - text_size) / 2; 71 | s_value_layer = text_layer_create(grect_inset(bounds, GEdgeInsets(y_centered, x_margin, 0, 0))); 72 | text_layer_set_font(s_value_layer, fonts_get_system_font(FONT_KEY_BITHAM_42_MEDIUM_NUMBERS)); 73 | text_layer_set_background_color(s_value_layer, GColorClear); 74 | text_layer_set_text_alignment(s_value_layer, GTextAlignmentCenter); 75 | text_layer_set_overflow_mode(s_value_layer, GTextOverflowModeWordWrap); 76 | layer_add_child(window_layer, text_layer_get_layer(s_value_layer)); 77 | 78 | s_status_bar = status_bar_layer_create(); 79 | status_bar_layer_set_colors(s_status_bar, GColorBlack, GColorWhite); 80 | layer_add_child(window_layer, status_bar_layer_get_layer(s_status_bar)); 81 | } 82 | 83 | static void window_appear(Window *window) { 84 | // Reset state 85 | s_value = s_min; 86 | text_layer_set_text(s_title_layer, s_title_buffer); 87 | set_value(); 88 | } 89 | 90 | static void window_unload(Window *window) { 91 | text_layer_destroy(s_title_layer); 92 | text_layer_destroy(s_value_layer); 93 | action_bar_layer_destroy(s_action_bar); 94 | status_bar_layer_destroy(s_status_bar); 95 | 96 | gbitmap_destroy(s_up_bitmap); 97 | gbitmap_destroy(s_select_bitmap); 98 | gbitmap_destroy(s_down_bitmap); 99 | 100 | window_destroy(s_window); 101 | s_window = NULL; 102 | } 103 | 104 | void new_number_window_push(char *title, int min, int max, int step, NewNumberWindowCallback callback) { 105 | s_callback = callback; 106 | s_min = min; 107 | s_max = max; 108 | s_step = step; 109 | snprintf(s_title_buffer, sizeof(s_title_buffer), "%s", title); 110 | 111 | if(!s_window) { 112 | s_window = window_create(); 113 | window_set_window_handlers(s_window, (WindowHandlers) { 114 | .load = window_load, 115 | .unload = window_unload, 116 | .appear = window_appear 117 | }); 118 | } 119 | window_stack_push(s_window, true); 120 | 121 | // APP_LOG(APP_LOG_LEVEL_DEBUG, "Heap free: %d", (int)heap_bytes_free()); // Pushing the limits of Aplite 122 | } 123 | -------------------------------------------------------------------------------- /pebble/src/c/lib/new_number_window/new_number_window.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | typedef void(NewNumberWindowCallback)(int); 6 | 7 | void new_number_window_push(char *title, int min, int max, int step, NewNumberWindowCallback callback); 8 | -------------------------------------------------------------------------------- /pebble/src/c/main.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "config.h" 4 | 5 | #include "modules/data.h" 6 | #include "modules/comm.h" 7 | #include "modules/sync_timer.h" 8 | 9 | #include "windows/splash_window.h" 10 | #include "windows/list_window.h" 11 | 12 | // --------------------------------- AppGlance --------------------------------- 13 | 14 | #if PBL_API_EXISTS(app_glance_reload) 15 | static void app_glance_callback(AppGlanceReloadSession *session, size_t limit, void *context) { 16 | if(limit < 1) { 17 | return; 18 | } 19 | 20 | // Find next upcoming Event 21 | Event events[MAX_WAKEUPS]; 22 | int next_index = -1; 23 | time_t next_timestamp = 2147483647; // Max int32 24 | for(int i = 0; i < MAX_WAKEUPS; i++) { 25 | if(!persist_exists(i)) { 26 | continue; 27 | } 28 | 29 | persist_read_data(i, &events[i], sizeof(Event)); 30 | time_t timestamp; 31 | wakeup_query(events[i].wakeup_id, ×tamp); 32 | 33 | if(timestamp < next_timestamp) { 34 | next_timestamp = timestamp; 35 | next_index = i; 36 | } 37 | } 38 | 39 | char buffer[64]; 40 | if(next_index == -1) { 41 | // No events found 42 | snprintf(buffer, sizeof(buffer), "No upcoming events"); 43 | } else { 44 | int minute = events[next_index].minute; 45 | char min_buff[8]; 46 | if(minute < 10) { 47 | snprintf(min_buff, sizeof(min_buff), "0%d", minute); 48 | } else { 49 | snprintf(min_buff, sizeof(min_buff), "%d", minute); 50 | } 51 | snprintf(buffer, sizeof(buffer), "Next event: %s %s at %d:%s", 52 | list_window_get_type_string(events[next_index].type), 53 | wakeup_window_get_state_string(events[next_index].state), 54 | events[next_index].hour, min_buff); 55 | 56 | time_t timestamp; 57 | wakeup_query(events[next_index].wakeup_id, ×tamp); 58 | } 59 | const AppGlanceSlice slice = (AppGlanceSlice) { 60 | .layout = { 61 | .subtitle_template_string = &buffer[0] 62 | }, 63 | .expiration_time = APP_GLANCE_SLICE_NO_EXPIRATION 64 | }; 65 | app_glance_add_slice(session, slice); 66 | } 67 | #endif 68 | 69 | // ---------------------------------- Wakeup ----------------------------------- 70 | 71 | static void wakeup_handler(WakeupId wakeup_id, int32_t cookie) { 72 | vibes_double_pulse(); 73 | 74 | // Load event 75 | Event e; 76 | persist_read_data(cookie, &e, sizeof(Event)); 77 | 78 | // Wakeup found, carry out action 79 | if(comm_check_bt("Dashboard event failed: watch was disconnected.")) { 80 | comm_request_toggle(e.type, e.state); 81 | } 82 | 83 | // Re-schedule for same time tomorrow 84 | time_t timestamp = clock_to_timestamp(schedule_window_get_tomorrow_weekday(), e.hour, e.minute); 85 | int id = wakeup_schedule(timestamp, cookie, false); 86 | 87 | // Check 88 | if(wakeup_query(id, ×tamp)) { 89 | // Store backing data 90 | e.wakeup_id = id; 91 | persist_write_data(cookie, &e, sizeof(Event)); 92 | } else { 93 | APP_LOG(APP_LOG_LEVEL_ERROR, "Wakeup recall failed %d", id); 94 | dialog_window_push("Failed to re-schedule wakeup for tomorrow"); 95 | } 96 | 97 | // Show UI 98 | wakeup_window_push(e.type, e.state); 99 | } 100 | 101 | static void begin_handler(void *context) { 102 | if(TEST) { 103 | test_populate_data(); 104 | return; 105 | } 106 | 107 | if(!comm_check_bt("Watch disconnected. Reconnect to continue.")) { 108 | return; 109 | } 110 | 111 | // Connect to real device 112 | sync_timer_begin(SYNC_TIMER_INTERVAL_S); // Update regularly while open 113 | comm_request_all(); // Response will contain everything, and use comm 114 | // module timeout mechanism 115 | } 116 | 117 | // ------------------------------------ App ------------------------------------ 118 | 119 | static void init() { 120 | comm_init(BUFFER_SIZE_IN, BUFFER_SIZE_OUT); 121 | data_init(); 122 | wakeup_service_subscribe(wakeup_handler); 123 | 124 | if(launch_reason() == APP_LAUNCH_WAKEUP) { 125 | // The app was started by a wakeup 126 | WakeupId id = 0; 127 | int32_t reason = 0; 128 | 129 | // Get details and handle the wakeup 130 | wakeup_get_launch_event(&id, &reason); 131 | wakeup_handler(id, reason); 132 | } else { 133 | // No wakeup, launch normally 134 | splash_window_push(); 135 | app_timer_register(INITIAL_DELAY, begin_handler, NULL); 136 | } 137 | } 138 | 139 | static void deinit() { 140 | data_deinit(); 141 | 142 | #if PBL_API_EXISTS(app_glance_reload) 143 | app_glance_reload(app_glance_callback, NULL); 144 | #endif 145 | } 146 | 147 | int main() { 148 | APP_LOG(APP_LOG_LEVEL_INFO, "Dashboard v%s", VERSION); 149 | 150 | init(); 151 | app_event_loop(); 152 | deinit(); 153 | } 154 | -------------------------------------------------------------------------------- /pebble/src/c/modules/comm.c: -------------------------------------------------------------------------------- 1 | #include "comm.h" 2 | 3 | static AppTimer *s_timeout_timer; 4 | static bool s_quick_started = false; 5 | 6 | static void inbox_received_handler(DictionaryIterator *iter, void *context) { 7 | if(s_timeout_timer) { 8 | app_timer_cancel(s_timeout_timer); 9 | s_timeout_timer = NULL; 10 | } 11 | 12 | int size = (int)iter->end - (int)iter->dictionary; 13 | if(DEBUG) APP_LOG(APP_LOG_LEVEL_DEBUG, "Dict size: %d", size); 14 | 15 | // Wrong version gate 16 | if(dict_find(iter, ErrorCodeWrongVersion) != NULL) { 17 | APP_LOG(APP_LOG_LEVEL_ERROR, "Wrong version error!"); 18 | 19 | dialog_window_push( 20 | PBL_IF_ROUND_ELSE("Update\nwatchapp using the Dashboard Android app!", 21 | "Update watchapp using the Dashboard Android app!") 22 | ); 23 | splash_window_remove_from_stack(); 24 | return; 25 | } 26 | 27 | // Process the incoming update 28 | if(dict_find(iter, MessageTypeRequestAll) != NULL) { 29 | data_set_gsm_name(dict_find(iter, AppKeyGSMName)->value->cstring); 30 | data_set_wifi_name(dict_find(iter, AppKeyWifiName)->value->cstring); 31 | data_set_gsm_percent(dict_find(iter, AppKeyGSMPercent)->value->int8); 32 | data_set_battery_percent(dict_find(iter, AppKeyBatteryPercent)->value->int8); 33 | data_set_storage_gb_major(dict_find(iter, AppKeyStorageFreeGBMajor)->value->int8); 34 | data_set_storage_gb_minor(dict_find(iter, AppKeyStorageFreeGBMinor)->value->int8); 35 | data_set_storage_percent(dict_find(iter, AppKeyStoragePercent)->value->int8); 36 | data_set_toggle_order(dict_find(iter, AppKeyToggleOrderString)->value->cstring); 37 | data_set_is_lollipop(dict_find(iter, AppKeyIsLollipop)->value->int8 == 1); 38 | 39 | // 'Request all' response 40 | data_set_toggle_state(ToggleTypeWifi, (dict_find(iter, AppKeyToggleWifi)->value->int8 == 1) ? ToggleStateOn : ToggleStateOff); 41 | data_set_toggle_state(ToggleTypeData, (dict_find(iter, AppKeyToggleData)->value->int8 == 1) ? ToggleStateOn : ToggleStateOff); 42 | data_set_toggle_state(ToggleTypeBluetooth, (dict_find(iter, AppKeyToggleBluetooth)->value->int8 == 1) ? ToggleStateOn : ToggleStateOff); 43 | data_set_toggle_state(ToggleTypeWifiAP, (dict_find(iter, AppKeyToggleWifiAP)->value->int8 == 1) ? ToggleStateOn : ToggleStateOff); 44 | data_set_toggle_state(ToggleTypeSync, (dict_find(iter, AppKeyToggleSync)->value->int8 == 1) ? ToggleStateOn : ToggleStateOff); 45 | data_set_toggle_state(ToggleTypeFindPhone, (dict_find(iter, AppKeyToggleFindPhone)->value->int8 == 1) ? ToggleStateOn : ToggleStateOff); 46 | data_set_toggle_state(ToggleTypeRinger, dict_find(iter, AppKeyToggleRinger)->value->int8); // Uses toggle state in encoding 47 | data_set_toggle_state(ToggleTypeAutoBrightness, dict_find(iter, AppKeyToggleAutoBrightness)->value->int8); 48 | data_set_toggle_state(ToggleTypeLockPhone, ToggleStateOff); // Always 49 | 50 | unified_window_push(); 51 | unified_window_set_interaction_enabled(true); 52 | splash_window_remove_from_stack(); 53 | 54 | // Quick launch? 55 | Tuple *quick_launch_enabled_t = dict_find(iter, AppKeyQuickLaunchEnabled); 56 | if(quick_launch_enabled_t) { 57 | bool enabled = quick_launch_enabled_t->value->int8 == 1; 58 | int type = dict_find(iter, AppKeyQuickLaunchType)->value->int8; 59 | 60 | if(enabled && !s_quick_started) { 61 | s_quick_started = true; 62 | unified_window_jump_to(type); 63 | } 64 | } 65 | } 66 | 67 | // 'Toggle' response 68 | else { 69 | // If response for WiFi AP, request another full sync 70 | for(int key = AppKeyToggleWifi; key < AppKeyToggleOrderString; key++) { // Flaky if something extends the linear list of keys here 71 | Tuple *t = dict_find(iter, key); 72 | if(t) { 73 | int type = key - AppKeyToggleWifi; // Also enum dependent 74 | int8_t new_state = t->value->int8; 75 | if(DEBUG) APP_LOG(APP_LOG_LEVEL_DEBUG, "Received type %d set to %d", type, new_state); 76 | 77 | switch(type) { 78 | // new state encoded 79 | case ToggleTypeData: 80 | if(new_state == ErrorCodeNoRoot) { 81 | dialog_window_push(PBL_IF_ROUND_ELSE( 82 | "Data toggle\nnot available without root.", 83 | "Data toggle not available without root.")); 84 | data_set_toggle_state(ToggleTypeData, ToggleStateOff); 85 | } else if(new_state == ErrorCodeDataNotEnabled) { 86 | dialog_window_push(PBL_IF_ROUND_ELSE( 87 | "Data toggle\nnot enabled in Android app.", 88 | "Data toggle not enabled in Android app.")); 89 | data_set_toggle_state(ToggleTypeData, ToggleStateOff); 90 | } 91 | break; 92 | case ToggleTypeWifi: 93 | case ToggleTypeSync: 94 | case ToggleTypeWifiAP: 95 | case ToggleTypeFindPhone: 96 | case ToggleTypeAutoBrightness: 97 | case ToggleTypeRinger: 98 | data_set_toggle_state(type, new_state); 99 | break; 100 | // Off only 101 | case ToggleTypeBluetooth: 102 | dialog_window_push("Phone is now disconnected."); 103 | unified_window_remove_from_stack(); 104 | splash_window_remove_from_stack(); 105 | data_set_toggle_state(type, ToggleStateOff); 106 | break; 107 | case ToggleTypeLockPhone: 108 | if(new_state == ErrorCodeNoDeviceAdmin) { 109 | dialog_window_push("Unavailable without Device Admin permission."); 110 | data_set_toggle_state(type, ToggleStateOff); 111 | } else if(new_state == ErrorCodeLockSuccess) { 112 | data_set_toggle_state(type, ToggleStateLocked); 113 | } 114 | break; 115 | } 116 | } else { 117 | if(DEBUG) APP_LOG(APP_LOG_LEVEL_ERROR, "AppKey %d was not present", key); 118 | } 119 | } 120 | } 121 | 122 | unified_window_reload(); 123 | } 124 | 125 | static void timeout_handler(void *context) { 126 | APP_LOG(APP_LOG_LEVEL_ERROR, "Timed out. Retrying..."); 127 | comm_request_all(); 128 | } 129 | 130 | static void init_timeout_timer() { 131 | s_timeout_timer = app_timer_register(COMM_TIMEOUT_MS, timeout_handler, NULL); 132 | } 133 | 134 | bool comm_check_bt(char *failed_notice) { 135 | if(!connection_service_peek_pebble_app_connection()) { 136 | dialog_window_push(failed_notice); 137 | splash_window_remove_from_stack(); 138 | unified_window_remove_from_stack(); 139 | return false; 140 | } else { 141 | return true; 142 | } 143 | } 144 | 145 | void comm_request_toggle(int type, int state) { 146 | if(TEST) { 147 | dialog_window_push("Cannot toggle in test mode"); 148 | return; 149 | } 150 | 151 | if(!comm_check_bt("Watch disconnected. Reconnect to continue.")) { 152 | return; 153 | } 154 | 155 | // Transform type from type enum value to key enum value (they are linear) 156 | type += AppKeyToggleWifi; 157 | 158 | DictionaryIterator *iter; 159 | AppMessageResult result = app_message_outbox_begin(&iter); 160 | if(result != APP_MSG_OK) { 161 | APP_LOG(APP_LOG_LEVEL_ERROR, "Failed to open outbox: %d", (int)result); 162 | } 163 | 164 | const int dummy = 0; 165 | dict_write_int(iter, MessageTypeToggle, &dummy, sizeof(int), true); 166 | dict_write_int(iter, type, &state, sizeof(int), true); 167 | 168 | result = app_message_outbox_send(); 169 | if(result != APP_MSG_OK) { 170 | APP_LOG(APP_LOG_LEVEL_ERROR, "Error sending outbox: %d", (int)result); 171 | } 172 | } 173 | 174 | void comm_request_all() { 175 | if(TEST) { 176 | return; 177 | } 178 | 179 | if(!comm_check_bt("Watch disconnected. Reconnect to continue.")) { 180 | return; 181 | } 182 | 183 | // Just in case, but first time should be immediately after version check 184 | app_message_register_inbox_received(inbox_received_handler); 185 | 186 | // Can't use pebble-packet due to low memory on Aplite 187 | DictionaryIterator *iter; 188 | AppMessageResult result = app_message_outbox_begin(&iter); 189 | if(result != APP_MSG_OK) { 190 | APP_LOG(APP_LOG_LEVEL_ERROR, "Failed to open outbox: %d", (int)result); 191 | return; 192 | } 193 | 194 | const int dummy = 0; 195 | dict_write_int(iter, MessageTypeRequestAll, &dummy, sizeof(int), true); 196 | 197 | // Add version instead of version_check library 198 | dict_write_cstring(iter, AppKeyVersion, VERSION); 199 | 200 | result = app_message_outbox_send(); 201 | if(result != APP_MSG_OK) { 202 | APP_LOG(APP_LOG_LEVEL_ERROR, "Error sending outbox: %d", (int)result); 203 | } else { 204 | init_timeout_timer(); 205 | } 206 | } 207 | 208 | void comm_init(uint32_t inbox, uint32_t outbox) { 209 | app_message_open(inbox, outbox); 210 | } 211 | -------------------------------------------------------------------------------- /pebble/src/c/modules/comm.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "../types.h" 6 | #include "../config.h" 7 | 8 | #include "../util/test.h" 9 | 10 | #include "../windows/dialog_window.h" 11 | #include "../windows/splash_window.h" 12 | #include "../windows/unified_window.h" 13 | 14 | void comm_init(uint32_t inbox, uint32_t outbox); 15 | 16 | void comm_request_all(); 17 | 18 | void comm_request_toggle(int type, int state); 19 | 20 | bool comm_check_bt(char *failed_notice); 21 | -------------------------------------------------------------------------------- /pebble/src/c/modules/data.c: -------------------------------------------------------------------------------- 1 | #include "data.h" 2 | 3 | // Data model 4 | typedef struct { 5 | int type; 6 | int state; 7 | } Toggle; 8 | 9 | static Toggle s_toggles[ToggleTypeCount]; 10 | static char s_gsm_name[NETWORK_MAX_LENGTH]; 11 | static char s_wifi_name[NETWORK_MAX_LENGTH]; 12 | static char s_order[ToggleTypeCount]; 13 | static int s_gsm_percent, s_battery_percent, s_storage_gb_major, s_storage_gb_minor, s_storage_percent; 14 | static bool s_is_lollipop; 15 | 16 | void data_init() { 17 | for(int i = 0; i < ToggleTypeCount; i++) { 18 | s_toggles[i].type = i; 19 | s_toggles[i].state = ToggleStateWaiting; 20 | } 21 | 22 | snprintf(s_gsm_name, sizeof(s_gsm_name), "-"); 23 | snprintf(s_wifi_name, sizeof(s_wifi_name), "-"); 24 | } 25 | 26 | void data_deinit() { } 27 | 28 | void data_set_toggle_type_at_index(int index, int type) { 29 | s_toggles[index].type = type; 30 | } 31 | 32 | int data_get_toggle_type_at_index(int index) { 33 | return s_toggles[index].type; 34 | } 35 | 36 | void data_set_toggle_state(int type, int state) { 37 | int index = 0; 38 | while(s_toggles[index].type != type) { 39 | index++; 40 | 41 | if(index == ToggleTypeCount) { 42 | APP_LOG(APP_LOG_LEVEL_ERROR, "No toggle found with type %d!", type); 43 | } 44 | } 45 | s_toggles[index].state = state; 46 | } 47 | 48 | int data_get_toggle_state(int type) { 49 | int index = 0; 50 | while(s_toggles[index].type != type) { 51 | index++; 52 | 53 | if(index == ToggleTypeCount) { 54 | APP_LOG(APP_LOG_LEVEL_ERROR, "No toggle found with type %d!", type); 55 | } 56 | } 57 | return s_toggles[index].state; 58 | } 59 | 60 | void data_set_gsm_name(char *name) { 61 | snprintf(s_gsm_name, sizeof(s_gsm_name), "%s", name); 62 | } 63 | 64 | char* data_get_gsm_name() { 65 | return &s_gsm_name[0]; 66 | } 67 | 68 | void data_set_wifi_name(char *name) { 69 | snprintf(s_wifi_name, sizeof(s_wifi_name), "%s", name); 70 | } 71 | 72 | char* data_get_wifi_name() { 73 | return &s_wifi_name[0]; 74 | } 75 | 76 | void data_set_gsm_percent(int percent) { 77 | s_gsm_percent = percent; 78 | } 79 | 80 | int data_get_gsm_percent() { 81 | return s_gsm_percent; 82 | } 83 | 84 | void data_set_battery_percent(int percent) { 85 | s_battery_percent = percent; 86 | } 87 | 88 | int data_get_battery_percent() { 89 | return s_battery_percent; 90 | } 91 | 92 | void data_set_storage_gb_major(int value) { 93 | s_storage_gb_major = value; 94 | } 95 | 96 | int data_get_storage_gb_major() { 97 | return s_storage_gb_major; 98 | } 99 | 100 | void data_set_storage_gb_minor(int value) { 101 | s_storage_gb_minor = value; 102 | } 103 | 104 | int data_get_storage_gb_minor() { 105 | return s_storage_gb_minor; 106 | } 107 | 108 | void data_set_is_lollipop(bool is_lollipop) { 109 | s_is_lollipop = is_lollipop; 110 | persist_write_bool(PersistKeyIsLollipop, is_lollipop); 111 | } 112 | 113 | bool data_get_is_lollipop() { 114 | return false; // Ignore for now. Is it even a thing anymore? 115 | // return s_is_lollipop; 116 | } 117 | 118 | static int char2int(char c) { 119 | return (int)(c - 48); 120 | } 121 | 122 | void data_set_toggle_order(char *order) { 123 | snprintf(s_order, sizeof(s_order), "%s", order); 124 | for(int i = 0; i < ToggleTypeCount; i++) { 125 | s_toggles[i].type = char2int(order[i]); 126 | s_toggles[i].state = ToggleStateWaiting; 127 | 128 | if(DEBUG) APP_LOG(APP_LOG_LEVEL_DEBUG, "Set s_toggles[%d] to type %d", i, s_toggles[i].type); 129 | } 130 | } 131 | 132 | char* data_get_toggle_order() { 133 | return &s_order[0]; 134 | } 135 | 136 | void data_set_storage_percent(int perc) { 137 | s_storage_percent = perc; 138 | } 139 | 140 | int data_get_storage_percent() { 141 | return s_storage_percent; 142 | } 143 | 144 | int data_get_toggle_index_with_type(int type) { 145 | int index = 0; 146 | while(s_toggles[index].type != type) { 147 | index++; 148 | 149 | if(index == ToggleTypeCount) { 150 | APP_LOG(APP_LOG_LEVEL_ERROR, "No toggle found with type %d!", type); 151 | } 152 | } 153 | return index; 154 | } 155 | -------------------------------------------------------------------------------- /pebble/src/c/modules/data.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "../config.h" 6 | #include "../types.h" 7 | 8 | void data_init(); 9 | void data_deinit(); 10 | 11 | void data_set_toggle_state(int type, int state); 12 | int data_get_toggle_state(int type); 13 | 14 | void data_set_toggle_type_at_index(int index, int type); 15 | int data_get_toggle_type_at_index(int index); 16 | 17 | int data_get_toggle_index_with_type(int type); 18 | 19 | void data_set_gsm_name(char *name); 20 | char* data_get_gsm_name(); 21 | 22 | void data_set_wifi_name(char *name); 23 | char* data_get_wifi_name(); 24 | 25 | void data_set_gsm_percent(int percent); 26 | int data_get_gsm_percent(); 27 | 28 | void data_set_battery_percent(int percent); 29 | int data_get_battery_percent(); 30 | 31 | void data_set_storage_gb_major(int value); 32 | int data_get_storage_gb_major(); 33 | 34 | void data_set_storage_gb_minor(int value); 35 | int data_get_storage_gb_minor(); 36 | 37 | void data_set_is_lollipop(bool is_lollipop); 38 | bool data_get_is_lollipop(); 39 | 40 | void data_set_toggle_order(char *order); 41 | char* data_get_toggle_order(); 42 | 43 | void data_set_storage_percent(int perc); 44 | int data_get_storage_percent(); 45 | -------------------------------------------------------------------------------- /pebble/src/c/modules/icons.c: -------------------------------------------------------------------------------- 1 | #include "icons.h" 2 | 3 | #define NUM_ICONS 11 4 | 5 | static GBitmap *s_bitmaps[NUM_ICONS]; 6 | 7 | void icons_load() { 8 | s_bitmaps[0] = gbitmap_create_with_resource(RESOURCE_ID_WIFI); 9 | s_bitmaps[1] = gbitmap_create_with_resource(RESOURCE_ID_DATA); 10 | s_bitmaps[2] = gbitmap_create_with_resource(RESOURCE_ID_BLUETOOTH); 11 | s_bitmaps[3] = gbitmap_create_with_resource(RESOURCE_ID_RINGER_LOUD); 12 | s_bitmaps[4] = gbitmap_create_with_resource(RESOURCE_ID_RINGER_VIBRATE); 13 | 14 | if(data_get_is_lollipop()) { 15 | s_bitmaps[5] = gbitmap_create_with_resource(RESOURCE_ID_RINGER_PRIORITY); 16 | } else { 17 | s_bitmaps[5] = gbitmap_create_with_resource(RESOURCE_ID_RINGER_SILENT); 18 | } 19 | 20 | s_bitmaps[6] = gbitmap_create_with_resource(RESOURCE_ID_SYNC); 21 | s_bitmaps[7] = gbitmap_create_with_resource(RESOURCE_ID_WIFI_AP); 22 | s_bitmaps[8] = gbitmap_create_with_resource(RESOURCE_ID_FIND_PHONE); 23 | s_bitmaps[9] = gbitmap_create_with_resource(RESOURCE_ID_LOCK_PHONE); 24 | s_bitmaps[10] = gbitmap_create_with_resource(RESOURCE_ID_BRIGHTNESS); 25 | 26 | // Check integrity 27 | for(int i = 0; i < NUM_ICONS; i++) { 28 | if(s_bitmaps[i] == NULL) { 29 | APP_LOG(APP_LOG_LEVEL_ERROR, "Failed to allocate bitmap %d", i); 30 | } 31 | } 32 | } 33 | 34 | void icons_unload() { 35 | for(int i = 0; i < NUM_ICONS; i++) { 36 | if(s_bitmaps[i]) { 37 | gbitmap_destroy(s_bitmaps[i]); 38 | } 39 | } 40 | } 41 | 42 | GBitmap* icons_get_with_type(int type) { 43 | switch(type) { 44 | case ToggleTypeWifi: return s_bitmaps[0]; 45 | case ToggleTypeData: return s_bitmaps[1]; 46 | case ToggleTypeBluetooth: return s_bitmaps[2]; 47 | case ToggleTypeRinger: { 48 | int ringer_state = data_get_toggle_state(ToggleTypeRinger); 49 | switch(ringer_state) { 50 | case ToggleStateLoud: return s_bitmaps[3]; 51 | case ToggleStateVibrate: return s_bitmaps[4]; 52 | case ToggleStateSilent: return s_bitmaps[5]; 53 | default: 54 | APP_LOG(APP_LOG_LEVEL_ERROR, "Unknown icons_get_with_type() for ringer state %d", ringer_state); 55 | return s_bitmaps[8]; // '?' 56 | } 57 | } break; 58 | case ToggleTypeSync: return s_bitmaps[6]; 59 | case ToggleTypeWifiAP: return s_bitmaps[7]; 60 | case ToggleTypeFindPhone: return s_bitmaps[8]; 61 | case ToggleTypeLockPhone: return s_bitmaps[9]; 62 | case ToggleTypeAutoBrightness: return s_bitmaps[10]; 63 | 64 | default: 65 | APP_LOG(APP_LOG_LEVEL_ERROR, "Unknown icons_get_with_type() for type %d", type); 66 | return NULL; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /pebble/src/c/modules/icons.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "data.h" 6 | 7 | void icons_load(); 8 | 9 | void icons_unload(); 10 | 11 | GBitmap* icons_get_with_type(int type); 12 | -------------------------------------------------------------------------------- /pebble/src/c/modules/sync_timer.c: -------------------------------------------------------------------------------- 1 | #include "sync_timer.h" 2 | 3 | static AppTimer *s_timer; 4 | static int s_interval_ms; 5 | 6 | static void timer_handler(void *context); 7 | 8 | static void re_register() { 9 | s_timer = app_timer_register(s_interval_ms, timer_handler, NULL); 10 | } 11 | 12 | static void timer_handler(void *context) { 13 | s_timer = NULL; 14 | comm_request_all(); 15 | re_register(); 16 | if(DEBUG) APP_LOG(APP_LOG_LEVEL_DEBUG, "Resynchronizing..."); 17 | } 18 | 19 | void sync_timer_begin(int interval_s) { 20 | s_interval_ms = interval_s * 1000; 21 | re_register(); 22 | } 23 | -------------------------------------------------------------------------------- /pebble/src/c/modules/sync_timer.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "comm.h" 6 | 7 | void sync_timer_begin(int interval_s); 8 | -------------------------------------------------------------------------------- /pebble/src/c/types.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | // TODO: Pebble packet schema and callbacks library 6 | typedef enum { 7 | AppKeyGSMName = 0, 8 | AppKeyGSMPercent, 9 | AppKeyWifiName, 10 | AppKeyBatteryPercent, 11 | AppKeyStorageFreeGBMajor, 12 | AppKeyStorageFreeGBMinor, 13 | AppKeyToggleWifi, // Begin iterable sequence 14 | AppKeyToggleData, // 15 | AppKeyToggleBluetooth, // 16 | AppKeyToggleRinger, // 17 | AppKeyToggleSync, // 18 | AppKeyToggleWifiAP, // 19 | AppKeyToggleFindPhone, // 20 | AppKeyToggleLockPhone, // 21 | AppKeyToggleAutoBrightness, // 22 | AppKeyToggleOrderString, // End iterable sequence (non-inclusive) 23 | AppKeyIsLollipop, 24 | AppKeyStoragePercent, 25 | AppKeyQuickLaunchEnabled, 26 | AppKeyQuickLaunchType, 27 | AppKeyVersion 28 | } AppKey; // KEEP IN ORDER 29 | 30 | typedef enum { 31 | MessageTypeRequestAll = 543, 32 | MessageTypeToggle 33 | } MessageType; 34 | 35 | // Must stay linear for data_init()! 36 | typedef enum { 37 | ToggleTypeWifi = 0, 38 | ToggleTypeData, 39 | ToggleTypeBluetooth, 40 | ToggleTypeRinger, 41 | ToggleTypeSync, 42 | ToggleTypeWifiAP, 43 | ToggleTypeFindPhone, 44 | ToggleTypeLockPhone, 45 | ToggleTypeAutoBrightness, 46 | 47 | ToggleTypeCount 48 | } ToggleType; 49 | 50 | typedef enum { 51 | ToggleStateWaiting = 0, 52 | ToggleStateOff, 53 | ToggleStateOn, 54 | ToggleStateLoud, 55 | ToggleStateVibrate, 56 | ToggleStateSilent, 57 | ToggleStateBrightnessAutomatic, 58 | ToggleStateBrightnessManual, 59 | ToggleStateLocked 60 | } ToggleState; 61 | 62 | typedef enum { 63 | ErrorCodeNoRoot = 0, 64 | ErrorCodeNoDeviceAdmin, 65 | ErrorCodeLockSuccess, 66 | ErrorCodeDataNotEnabled, 67 | ErrorCodeWrongVersion = 40 68 | } ErrorCode; 69 | 70 | typedef struct { 71 | int type; 72 | int state; 73 | int hour; 74 | int minute; 75 | int set; 76 | int wakeup_id; 77 | } Event; 78 | // List row keys are row index 79 | 80 | typedef enum { 81 | PersistKeyIsLollipop = 453786 82 | } PersistKey; -------------------------------------------------------------------------------- /pebble/src/c/util/animation.c: -------------------------------------------------------------------------------- 1 | #include "animation.h" 2 | 3 | static bool s_locked; 4 | 5 | void animation_animate_layer(Layer *layer, GRect start, GRect finish, int duration_ms, AnimationHandlers *handlers) { 6 | PropertyAnimation *prop_anim = property_animation_create_layer_frame(layer, &start, &finish); 7 | Animation *anim = property_animation_get_animation(prop_anim); 8 | animation_set_duration(anim, duration_ms); 9 | if(handlers) { 10 | animation_set_handlers(anim, (*handlers), NULL); 11 | } 12 | animation_schedule(anim); 13 | } 14 | 15 | void animation_animate_delta_y(Layer *layer, int dy, int duration_ms, AnimationHandlers *handlers) { 16 | GRect start = layer_get_frame(layer); 17 | GRect finish = GRect(start.origin.x, start.origin.y + dy, start.size.w, start.size.h); 18 | animation_animate_layer(layer, start, finish, duration_ms, handlers); 19 | } 20 | 21 | void animation_lock() { 22 | s_locked = true; 23 | } 24 | 25 | void animation_unlock() { 26 | s_locked = false; 27 | } 28 | 29 | bool animation_is_locked() { 30 | return s_locked; 31 | } -------------------------------------------------------------------------------- /pebble/src/c/util/animation.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | void animation_animate_layer(Layer *layer, GRect start, GRect finish, int duration, AnimationHandlers *handlers); 6 | 7 | void animation_animate_delta_y(Layer *layer, int dy, int duration, AnimationHandlers *handlers); 8 | 9 | void animation_lock(); 10 | 11 | void animation_unlock(); 12 | 13 | bool animation_is_locked(); 14 | -------------------------------------------------------------------------------- /pebble/src/c/util/test.c: -------------------------------------------------------------------------------- 1 | #include "test.h" 2 | 3 | // Use local data in emulator until PKA is emulated 4 | void test_populate_data() { 5 | data_set_toggle_order("012345678"); 6 | data_set_is_lollipop(true); 7 | 8 | data_set_toggle_state(ToggleTypeWifi, ToggleStateOn); 9 | data_set_toggle_state(ToggleTypeData, ToggleStateOff); 10 | data_set_toggle_state(ToggleTypeBluetooth, ToggleStateOn); 11 | data_set_toggle_state(ToggleTypeRinger, ToggleStateSilent); 12 | data_set_toggle_state(ToggleTypeSync, ToggleStateOn); 13 | data_set_toggle_state(ToggleTypeWifiAP, ToggleStateOn); 14 | data_set_toggle_state(ToggleTypeFindPhone, ToggleStateOn); 15 | data_set_toggle_state(ToggleTypeLockPhone, ToggleStateWaiting); 16 | data_set_toggle_state(ToggleTypeAutoBrightness, ToggleStateBrightnessAutomatic); 17 | 18 | data_set_gsm_name("T-Mobile"); 19 | data_set_wifi_name("BTHub3-NCNR"); 20 | data_set_gsm_percent(50); 21 | data_set_battery_percent(75); 22 | data_set_storage_gb_major(16); 23 | data_set_storage_gb_minor(2); 24 | data_set_storage_percent(65); 25 | 26 | unified_window_push(); 27 | unified_window_set_interaction_enabled(true); 28 | splash_window_remove_from_stack(); 29 | } 30 | -------------------------------------------------------------------------------- /pebble/src/c/util/test.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "../modules/data.h" 6 | 7 | #include "../windows/splash_window.h" 8 | #include "../windows/unified_window.h" 9 | 10 | void test_populate_data(); 11 | -------------------------------------------------------------------------------- /pebble/src/c/windows/dialog_window.c: -------------------------------------------------------------------------------- 1 | // TODO: Make pebble package 2 | #include "dialog_window.h" 3 | 4 | static Window *s_window; 5 | static TextLayer *s_text_layer; 6 | static ActionBarLayer *s_action_bar_layer; 7 | 8 | static GBitmap *s_tick_bitmap; 9 | 10 | #define MAX_LENGTH 128 11 | static char s_text_buffer[MAX_LENGTH]; 12 | 13 | static void update_layout() { 14 | Layer *window_layer = window_get_root_layer(s_window); 15 | GRect bounds = layer_get_bounds(window_layer); 16 | 17 | const int margin = 5; 18 | const int y_margin = PBL_IF_ROUND_ELSE(5 * margin, margin); 19 | GRect text_bounds = grect_inset(bounds, GEdgeInsets(y_margin, ACTION_BAR_WIDTH + margin, margin, margin)); 20 | layer_set_frame(text_layer_get_layer(s_text_layer), text_bounds); 21 | } 22 | 23 | static void select_click_handler(ClickRecognizerRef recognizer, void *context) { 24 | window_stack_pop(true); 25 | } 26 | 27 | static void click_config_provider(void *context) { 28 | window_single_click_subscribe(BUTTON_ID_SELECT, select_click_handler); 29 | } 30 | 31 | static void window_load(Window *window) { 32 | Layer *window_layer = window_get_root_layer(window); 33 | GRect bounds = layer_get_bounds(window_layer); 34 | 35 | s_text_layer = text_layer_create(bounds); 36 | text_layer_set_background_color(s_text_layer, GColorClear); 37 | text_layer_set_text_alignment(s_text_layer, PBL_IF_ROUND_ELSE(GTextAlignmentRight, GTextAlignmentLeft)); 38 | text_layer_set_font(s_text_layer, fonts_get_system_font(FONT_KEY_GOTHIC_24_BOLD)); 39 | layer_add_child(window_layer, text_layer_get_layer(s_text_layer)); 40 | 41 | s_tick_bitmap = gbitmap_create_with_resource(RESOURCE_ID_TICK); 42 | 43 | s_action_bar_layer = action_bar_layer_create(); 44 | action_bar_layer_set_icon(s_action_bar_layer, BUTTON_ID_SELECT, s_tick_bitmap); 45 | action_bar_layer_set_click_config_provider(s_action_bar_layer, click_config_provider); 46 | action_bar_layer_add_to_window(s_action_bar_layer, window); 47 | } 48 | 49 | static void window_unload(Window *window) { 50 | text_layer_destroy(s_text_layer); 51 | action_bar_layer_destroy(s_action_bar_layer); 52 | gbitmap_destroy(s_tick_bitmap); 53 | 54 | window_destroy(s_window); 55 | s_window = NULL; 56 | APP_LOG(APP_LOG_LEVEL_DEBUG, "Dialog window exited with heap free %d", (int)heap_bytes_free()); 57 | } 58 | 59 | /************************************ API *************************************/ 60 | 61 | // Text should be per-platform newlined as appropriate 62 | void dialog_window_push(char *text) { 63 | if(!s_window) { 64 | s_window = window_create(); 65 | window_set_window_handlers(s_window, (WindowHandlers) { 66 | .load = window_load, 67 | .unload = window_unload 68 | }); 69 | } 70 | window_stack_push(s_window, true); 71 | 72 | window_set_background_color(s_window, PBL_IF_COLOR_ELSE(GColorLightGray, GColorWhite)); 73 | text_layer_set_text_color(s_text_layer, GColorBlack); 74 | 75 | snprintf(s_text_buffer, sizeof(s_text_buffer), "%s", text); 76 | text_layer_set_text(s_text_layer, s_text_buffer); 77 | 78 | update_layout(); 79 | } 80 | -------------------------------------------------------------------------------- /pebble/src/c/windows/dialog_window.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | void dialog_window_push(char *text); 6 | -------------------------------------------------------------------------------- /pebble/src/c/windows/list_window.c: -------------------------------------------------------------------------------- 1 | #include "list_window.h" 2 | 3 | #define BUFFER_LENGTH 32 4 | #define ALL_EVENTS -1 5 | 6 | static Window *s_window; 7 | static MenuLayer *s_menu_layer; 8 | static StatusBarLayer *s_status_layer; 9 | 10 | static Event events[MAX_WAKEUPS]; 11 | static char list_buffers[MAX_WAKEUPS][BUFFER_LENGTH]; 12 | 13 | char* list_window_get_type_string(int type) { 14 | switch(type) { 15 | case ToggleTypeWifi: return "Wi-Fi"; 16 | case ToggleTypeData: return "Data"; 17 | case ToggleTypeBluetooth: return "Bluetooth"; 18 | case ToggleTypeRinger: return "Ringer"; 19 | case ToggleTypeSync: return "Auto Sync"; 20 | case ToggleTypeWifiAP: return "Hotspot"; 21 | case ToggleTypeFindPhone: return "Find Phone"; 22 | case ToggleTypeAutoBrightness: return "Auto Bright."; 23 | case ToggleTypeLockPhone: return "Lock Phone"; 24 | default: 25 | APP_LOG(APP_LOG_LEVEL_ERROR, "Unknown type for list_window_get_type_string: %d", type); 26 | return "Unknown"; 27 | } 28 | } 29 | 30 | static void reload_events(int specific) { 31 | if(specific != ALL_EVENTS) { 32 | // Reload just one 33 | if(persist_exists(specific)) { 34 | persist_read_data(specific, &events[specific], sizeof(Event)); 35 | } else { 36 | events[specific].set = 0; // Not valid 37 | } 38 | } else { 39 | // Reload them all 40 | for(int i = 0; i < MAX_WAKEUPS; i++) { 41 | // If this row's event exists... 42 | if(persist_exists(i)) { 43 | // Load it 44 | persist_read_data(i, &events[i], sizeof(Event)); 45 | 46 | if(DEBUG) { 47 | // Log it 48 | time_t timestamp; 49 | APP_LOG(APP_LOG_LEVEL_DEBUG, "Read index %d, %d: %s (%d:%d): valid: %s", 50 | i, events[i].type, wakeup_window_get_state_string(events[i].state), events[i].hour, 51 | events[i].minute, (wakeup_query(events[i].wakeup_id, ×tamp) ? "true" : "false") 52 | ); 53 | } 54 | } else { 55 | // Does not yet exist 56 | events[i].set = 0; 57 | if(DEBUG) APP_LOG(APP_LOG_LEVEL_DEBUG, "Event %d is not set or does not exist", i); 58 | } 59 | } 60 | } 61 | } 62 | 63 | static int get_total_occupied_rows() { 64 | int result = 0; 65 | for(int i = 0; i < MAX_WAKEUPS; i++) { 66 | result += events[i].set; // 1 or 0, neat 67 | } 68 | return result; 69 | } 70 | 71 | /*********************************** MenuLayer ********************************/ 72 | 73 | static void draw_row_callback(GContext *ctx, const Layer *cell_layer, MenuIndex *cell_index, void *context) { 74 | GRect bounds = layer_get_bounds(cell_layer); 75 | int row = cell_index->row; 76 | 77 | if(events[row].set == 0) { 78 | // Not yet set, unused 79 | if(menu_layer_is_index_selected(s_menu_layer, cell_index)) { 80 | menu_cell_basic_draw(ctx, cell_layer, "Unused", "Click to add event...", NULL);\ 81 | } else { 82 | menu_cell_basic_draw(ctx, cell_layer, "Unused", NULL, NULL);\ 83 | } 84 | } else { 85 | // Used, load the data 86 | int type = events[row].type; 87 | int state = events[row].state; 88 | int hour = events[row].hour; 89 | int minute = events[row].minute; 90 | 91 | if(menu_layer_is_index_selected(s_menu_layer, cell_index)) { 92 | // Show all the details (TODO: padding function) 93 | if(hour < 10 && minute < 10) { 94 | snprintf(list_buffers[row], BUFFER_LENGTH, "%s: %s\n(0%d:0%d)", list_window_get_type_string(type), 95 | wakeup_window_get_state_string(state), hour, minute); 96 | } else if(hour < 10 && minute > 9) { 97 | snprintf(list_buffers[row], BUFFER_LENGTH, "%s: %s\n(0%d:%d)", list_window_get_type_string(type), 98 | wakeup_window_get_state_string(state), hour, minute); 99 | } else if(hour > 9 && minute < 10) { 100 | snprintf(list_buffers[row], BUFFER_LENGTH, "%s: %s\n(%d:0%d)", list_window_get_type_string(type), 101 | wakeup_window_get_state_string(state), hour, minute); 102 | } else { 103 | snprintf(list_buffers[row], BUFFER_LENGTH, "%s: %s\n(%d:%d)", list_window_get_type_string(type), 104 | wakeup_window_get_state_string(state), hour, minute); 105 | } 106 | } else { 107 | // Show minimal detail 108 | snprintf(list_buffers[row], BUFFER_LENGTH, "%s: %s", list_window_get_type_string(type), 109 | wakeup_window_get_state_string(state)); 110 | } 111 | 112 | int y_margin = 0; 113 | GRect text_rect = grect_inset(bounds, GEdgeInsets(y_margin, 0, 0, PBL_IF_ROUND_ELSE(0, 5))); 114 | graphics_draw_text(ctx, list_buffers[row], fonts_get_system_font(FONT_KEY_GOTHIC_18_BOLD), text_rect, 115 | GTextOverflowModeWordWrap, PBL_IF_ROUND_ELSE(GTextAlignmentCenter, GTextAlignmentLeft), NULL); 116 | text_rect.origin.y += 37; 117 | graphics_draw_text(ctx, "Click to delete...", fonts_get_system_font(FONT_KEY_GOTHIC_18), text_rect, 118 | GTextOverflowModeWordWrap, PBL_IF_ROUND_ELSE(GTextAlignmentCenter, GTextAlignmentLeft), NULL); 119 | } 120 | } 121 | 122 | static int16_t get_cell_height_callback(struct MenuLayer *menu_layer, MenuIndex *cell_index, void *context) { 123 | return menu_layer_is_index_selected(menu_layer, cell_index) ? 124 | (3 * MENU_CELL_ROUND_FOCUSED_TALL_CELL_HEIGHT) / 4 : MENU_CELL_ROUND_UNFOCUSED_TALL_CELL_HEIGHT; 125 | } 126 | 127 | static uint16_t num_rows_callback(struct MenuLayer *menu_layer, uint16_t section_index, void *context) { 128 | return MAX_WAKEUPS; // TODO revisit collapsible list structure 129 | } 130 | 131 | static void select_click_callback(struct MenuLayer *menu_layer, MenuIndex *cell_index, void *context) { 132 | int row = cell_index->row; 133 | 134 | // Event for this row? 135 | if(persist_read_data(row, &events[row], sizeof(Event)) <= 0) { 136 | // Does not exist, add it with user input 137 | schedule_window_push(row); 138 | } else { 139 | // Exists, remove wakeup and delete data 140 | wakeup_cancel(events[row].wakeup_id); 141 | persist_delete(row); 142 | dialog_window_push("Event deleted."); 143 | } 144 | 145 | // If none remain 146 | if(get_total_occupied_rows() == 0) { 147 | // Cancel All Wakeups 148 | wakeup_cancel_all(); 149 | if(DEBUG) APP_LOG(APP_LOG_LEVEL_DEBUG, "Cancelling all wakeups..."); 150 | 151 | for(int i = 0; i < MAX_WAKEUPS; i++) { 152 | if(persist_read_data(i, &events[i], sizeof(Event)) > 0) { 153 | // Save and reload only this event 154 | persist_delete(i); 155 | } 156 | } 157 | } 158 | 159 | // Update view 160 | reload_events(ALL_EVENTS); 161 | menu_layer_reload_data(menu_layer); 162 | } 163 | 164 | static uint16_t get_num_sections_callback(struct MenuLayer *menu_layer, void *context) { 165 | const int num_sections = 1; 166 | return num_sections; 167 | } 168 | 169 | /************************************ Window **********************************/ 170 | 171 | static void window_appear(Window *window) { 172 | // Load once all events 173 | reload_events(ALL_EVENTS); 174 | menu_layer_reload_data(s_menu_layer); 175 | } 176 | 177 | static void window_load(Window *window) { 178 | Layer *window_layer = window_get_root_layer(window); 179 | GRect bounds = layer_get_bounds(window_layer); 180 | 181 | s_menu_layer = menu_layer_create(grect_inset(bounds, GEdgeInsets(PBL_IF_ROUND_ELSE(0, STATUS_BAR_LAYER_HEIGHT), 0, 0, 0))); 182 | menu_layer_set_center_focused(s_menu_layer, true); 183 | menu_layer_pad_bottom_enable(s_menu_layer, false); 184 | #if defined(PBL_COLOR) 185 | menu_layer_set_normal_colors(s_menu_layer, GColorBlack, GColorWhite); 186 | menu_layer_set_highlight_colors(s_menu_layer, GColorDarkCandyAppleRed, GColorWhite); 187 | #endif 188 | menu_layer_set_click_config_onto_window(s_menu_layer, window); 189 | menu_layer_set_callbacks(s_menu_layer, NULL, (MenuLayerCallbacks) { 190 | .draw_row = draw_row_callback, 191 | .get_cell_height = get_cell_height_callback, 192 | .get_num_rows = num_rows_callback, 193 | .select_click = select_click_callback, 194 | .get_num_sections = get_num_sections_callback 195 | }); 196 | layer_add_child(window_layer, menu_layer_get_layer(s_menu_layer)); 197 | 198 | s_status_layer = status_bar_layer_create(); 199 | status_bar_layer_set_separator_mode(s_status_layer, StatusBarLayerSeparatorModeDotted); 200 | status_bar_layer_set_colors(s_status_layer, 201 | PBL_IF_COLOR_ELSE(GColorDarkCandyAppleRed, GColorWhite), PBL_IF_COLOR_ELSE(GColorWhite, GColorBlack)); 202 | layer_add_child(window_layer, status_bar_layer_get_layer(s_status_layer)); 203 | } 204 | 205 | static void window_unload(Window *window) { 206 | menu_layer_destroy(s_menu_layer); 207 | status_bar_layer_destroy(s_status_layer); 208 | 209 | window_destroy(window); 210 | s_window = NULL; 211 | } 212 | 213 | void list_window_push() { 214 | if(!s_window) { 215 | s_window = window_create(); 216 | window_set_background_color(s_window, PBL_IF_COLOR_ELSE(GColorBlack, GColorWhite)); 217 | window_set_window_handlers(s_window, (WindowHandlers) { 218 | .load = window_load, 219 | .unload = window_unload, 220 | .appear = window_appear 221 | }); 222 | } 223 | window_stack_push(s_window, true); 224 | } 225 | -------------------------------------------------------------------------------- /pebble/src/c/windows/list_window.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "dialog_window.h" 4 | #include "schedule_window.h" 5 | #include "wakeup_window.h" 6 | 7 | #include "../config.h" 8 | #include "../types.h" 9 | 10 | char* list_window_get_type_string(int type); // TODO Decouple and move to types.h 11 | 12 | void list_window_push(); 13 | -------------------------------------------------------------------------------- /pebble/src/c/windows/schedule_window.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "dialog_window.h" 6 | #include "list_window.h" 7 | 8 | #include "../config.h" 9 | #include "../types.h" 10 | 11 | #include "../modules/data.h" 12 | 13 | #include "../lib/new_number_window/new_number_window.h" 14 | 15 | WeekDay schedule_window_get_tomorrow_weekday(); // TODO move to types.h 16 | 17 | void schedule_window_push(int position); 18 | -------------------------------------------------------------------------------- /pebble/src/c/windows/splash_window.c: -------------------------------------------------------------------------------- 1 | #include "splash_window.h" 2 | 3 | #define INTERVAL_MS 150 // Animation frame interval 4 | 5 | static Window *s_window; 6 | static BitmapLayer *s_logo_layer; 7 | 8 | static GBitmap *s_logo_bitmap; 9 | 10 | /*********************************** Window ***********************************/ 11 | 12 | static void window_load(Window *window) { 13 | Layer *window_layer = window_get_root_layer(window); 14 | GRect bounds = layer_get_bounds(window_layer); 15 | 16 | s_logo_bitmap = gbitmap_create_with_resource(RESOURCE_ID_LOGO); 17 | 18 | s_logo_layer = bitmap_layer_create(bounds); 19 | bitmap_layer_set_bitmap(s_logo_layer, s_logo_bitmap); 20 | bitmap_layer_set_compositing_mode(s_logo_layer, GCompOpSet); 21 | layer_add_child(window_layer, bitmap_layer_get_layer(s_logo_layer)); 22 | } 23 | 24 | static void window_unload(Window *window) { 25 | bitmap_layer_destroy(s_logo_layer); 26 | gbitmap_destroy(s_logo_bitmap); 27 | 28 | window_destroy(s_window); 29 | s_window = NULL; 30 | } 31 | 32 | void splash_window_push() { 33 | if(!s_window) { 34 | s_window = window_create(); 35 | window_set_background_color(s_window, GColorBlack); 36 | window_set_window_handlers(s_window, (WindowHandlers) { 37 | .load = window_load, 38 | .unload = window_unload 39 | }); 40 | } 41 | window_stack_push(s_window, true); 42 | } 43 | 44 | void splash_window_remove_from_stack() { 45 | if(s_window) { 46 | window_stack_remove(s_window, true); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /pebble/src/c/windows/splash_window.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "../config.h" 6 | 7 | void splash_window_push(); 8 | 9 | void splash_window_remove_from_stack(); 10 | -------------------------------------------------------------------------------- /pebble/src/c/windows/unified_window.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "dialog_window.h" 6 | #include "list_window.h" 7 | #include "wakeup_window.h" 8 | 9 | #include "../types.h" 10 | 11 | #include "../modules/comm.h" 12 | #include "../modules/icons.h" 13 | 14 | #include "../util/animation.h" 15 | 16 | void unified_window_push(); 17 | 18 | void unified_window_remove_from_stack(); 19 | 20 | void unified_window_reload(); 21 | 22 | void unified_window_jump_to(int type); 23 | 24 | void unified_window_set_interaction_enabled(bool b); 25 | -------------------------------------------------------------------------------- /pebble/src/c/windows/wakeup_window.c: -------------------------------------------------------------------------------- 1 | #include "wakeup_window.h" 2 | 3 | static Window *s_window; 4 | static TextLayer *s_title_layer, *s_details_layer; 5 | 6 | static int s_type; 7 | static int s_state; 8 | 9 | static void timer_handler(void *context) { 10 | window_stack_pop_all(true); // Quit wakeup 11 | } 12 | 13 | char* wakeup_window_get_state_string(int state) { 14 | switch(state) { 15 | case ToggleStateWaiting: return "WAITING..."; 16 | case ToggleStateOff: return "OFF"; 17 | case ToggleStateOn: return "ON"; 18 | case ToggleStateLoud: return "LOUD"; 19 | case ToggleStateVibrate: return "VIBRATE"; 20 | case ToggleStateSilent: return "SILENT"; 21 | case ToggleStateBrightnessManual: return "MANUAL"; 22 | case ToggleStateBrightnessAutomatic: return "AUTO"; 23 | case ToggleStateLocked: return "NOW LOCKED"; 24 | default: 25 | APP_LOG(APP_LOG_LEVEL_ERROR, "Unknown state for wakeup_window_get_state_string: %d", state); 26 | return "UNKNOWN"; 27 | } 28 | } 29 | 30 | static void window_load(Window *window) { 31 | Layer *window_layer = window_get_root_layer(window); 32 | GRect bounds = layer_get_bounds(window_layer); 33 | 34 | s_title_layer = text_layer_create(GRect(0, PBL_IF_ROUND_ELSE(20, 0), bounds.size.w, 60)); 35 | text_layer_set_font(s_title_layer, fonts_get_system_font(FONT_KEY_GOTHIC_24_BOLD)); 36 | text_layer_set_text_alignment(s_title_layer, GTextAlignmentCenter); 37 | text_layer_set_text_color(s_title_layer, GColorWhite); 38 | text_layer_set_background_color(s_title_layer, GColorClear); 39 | text_layer_set_text(s_title_layer, "Dashboard Event"); 40 | layer_add_child(window_layer, text_layer_get_layer(s_title_layer)); 41 | 42 | s_details_layer = text_layer_create(GRect(0, PBL_IF_ROUND_ELSE(80, 70), bounds.size.w, bounds.size.h)); 43 | text_layer_set_font(s_details_layer, fonts_get_system_font(FONT_KEY_GOTHIC_24_BOLD)); 44 | text_layer_set_text_color(s_details_layer, GColorWhite); 45 | text_layer_set_background_color(s_details_layer, GColorClear); 46 | text_layer_set_text_alignment(s_details_layer, GTextAlignmentCenter); 47 | layer_add_child(window_layer, text_layer_get_layer(s_details_layer)); 48 | 49 | static char s_buffer[128]; 50 | snprintf(s_buffer, sizeof(s_buffer), "Attempting %s to %s\n(Back to dismiss)", 51 | list_window_get_type_string (s_type), wakeup_window_get_state_string(s_state)); 52 | text_layer_set_text(s_details_layer, s_buffer); 53 | 54 | // Two minutes 55 | const int two_mins_ms = 120000; 56 | app_timer_register(two_mins_ms, timer_handler, NULL); 57 | } 58 | 59 | static void window_unload(Window *window) { 60 | text_layer_destroy(s_details_layer); 61 | 62 | // Finally 63 | window_destroy(window); 64 | s_window = NULL; 65 | window_stack_pop_all(true); 66 | } 67 | 68 | void wakeup_window_push(int type, int state) { 69 | s_type = type; 70 | s_state = state; 71 | 72 | if(!s_window) { 73 | s_window = window_create(); 74 | window_set_background_color(s_window, GColorBlack); 75 | window_set_window_handlers(s_window, (WindowHandlers) { 76 | .load = window_load, 77 | .unload = window_unload 78 | }); 79 | } 80 | window_stack_push(s_window, true); 81 | } 82 | -------------------------------------------------------------------------------- /pebble/src/c/windows/wakeup_window.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "schedule_window.h" 4 | 5 | #include "../types.h" 6 | #include "../modules/comm.h" 7 | 8 | char* wakeup_window_get_state_string(int state); // TODO decouple and move to types.h 9 | 10 | void wakeup_window_push(int type, int state); 11 | -------------------------------------------------------------------------------- /pebble/wscript: -------------------------------------------------------------------------------- 1 | # 2 | # This file is the default set of rules to compile a Pebble application. 3 | # 4 | # Feel free to customize this to your needs. 5 | # 6 | import os.path 7 | 8 | top = '.' 9 | out = 'build' 10 | 11 | 12 | def options(ctx): 13 | ctx.load('pebble_sdk') 14 | 15 | 16 | def configure(ctx): 17 | """ 18 | This method is used to configure your build. ctx.load(`pebble_sdk`) automatically configures 19 | a build for each valid platform in `targetPlatforms`. Platform-specific configuration: add your 20 | change after calling ctx.load('pebble_sdk') and make sure to set the correct environment first. 21 | Universal configuration: add your change prior to calling ctx.load('pebble_sdk'). 22 | """ 23 | ctx.load('pebble_sdk') 24 | 25 | 26 | def build(ctx): 27 | ctx.load('pebble_sdk') 28 | 29 | build_worker = os.path.exists('worker_src') 30 | binaries = [] 31 | 32 | cached_env = ctx.env 33 | for platform in ctx.env.TARGET_PLATFORMS: 34 | ctx.env = ctx.all_envs[platform] 35 | ctx.set_group(ctx.env.PLATFORM_NAME) 36 | app_elf = '{}/pebble-app.elf'.format(ctx.env.BUILD_DIR) 37 | ctx.pbl_build(source=ctx.path.ant_glob('src/c/**/*.c'), target=app_elf, bin_type='app') 38 | 39 | if build_worker: 40 | worker_elf = '{}/pebble-worker.elf'.format(ctx.env.BUILD_DIR) 41 | binaries.append({'platform': platform, 'app_elf': app_elf, 'worker_elf': worker_elf}) 42 | ctx.pbl_build(source=ctx.path.ant_glob('worker_src/c/**/*.c'), 43 | target=worker_elf, 44 | bin_type='worker') 45 | else: 46 | binaries.append({'platform': platform, 'app_elf': app_elf}) 47 | ctx.env = cached_env 48 | 49 | ctx.set_group('bundle') 50 | ctx.pbl_bundle(binaries=binaries, 51 | js=ctx.path.ant_glob(['src/pkjs/**/*.js', 52 | 'src/pkjs/**/*.json', 53 | 'src/common/**/*.js']), 54 | js_entry_file='src/pkjs/index.js') 55 | --------------------------------------------------------------------------------