├── .gitignore ├── .idea ├── .name ├── compiler.xml ├── copyright │ └── profiles_settings.xml ├── encodings.xml ├── gradle.xml ├── misc.xml ├── modules.xml ├── runConfigurations.xml └── vcs.xml ├── OpenVBX.iml ├── README.md ├── app ├── app.iml ├── build.gradle └── src │ └── main │ ├── AndroidManifest.xml │ ├── ic_launcher-web.png │ ├── java │ └── org │ │ └── openvbx │ │ ├── AnnotationActivity.java │ │ ├── CallActivity.java │ │ ├── InboxActivity.java │ │ ├── LoginActivity.java │ │ ├── MessageActivity.java │ │ ├── OpenVBX.java │ │ ├── SmsActivity.java │ │ ├── adapters │ │ ├── AnnotationAdapter.java │ │ ├── CallerIDAdapter.java │ │ ├── MessagesAdapter.java │ │ └── UserAdapter.java │ │ └── models │ │ ├── Annotation.java │ │ ├── CallerID.java │ │ ├── ClientResult.java │ │ ├── Folder.java │ │ ├── Inbox.java │ │ ├── Message.java │ │ ├── MessageResult.java │ │ └── User.java │ └── res │ ├── drawable-hdpi │ ├── ic_archive_white_36dp.png │ ├── ic_call_white_48dp.png │ ├── ic_folder_white_48dp.png │ ├── ic_inbox_white_48dp.png │ ├── ic_message_white_48dp.png │ ├── ic_pause_white_48dp.png │ ├── ic_play_arrow_white_48dp.png │ ├── ic_reply_white_48dp.png │ ├── ic_speaker_notes_white_36dp.png │ ├── ic_textsms_white_36dp.png │ └── ic_voicemail_white_48dp.png │ ├── drawable-mdpi │ ├── ic_archive_white_36dp.png │ ├── ic_call_white_48dp.png │ ├── ic_folder_white_48dp.png │ ├── ic_inbox_white_48dp.png │ ├── ic_message_white_48dp.png │ ├── ic_pause_white_48dp.png │ ├── ic_play_arrow_white_48dp.png │ ├── ic_reply_white_48dp.png │ ├── ic_speaker_notes_white_36dp.png │ ├── ic_textsms_white_36dp.png │ └── ic_voicemail_white_48dp.png │ ├── drawable-xhdpi │ ├── ic_archive_white_36dp.png │ ├── ic_call_white_48dp.png │ ├── ic_folder_white_48dp.png │ ├── ic_inbox_white_48dp.png │ ├── ic_message_white_48dp.png │ ├── ic_pause_white_48dp.png │ ├── ic_play_arrow_white_48dp.png │ ├── ic_reply_white_48dp.png │ ├── ic_speaker_notes_white_36dp.png │ ├── ic_textsms_white_36dp.png │ └── ic_voicemail_white_48dp.png │ ├── drawable-xxhdpi │ ├── ic_archive_white_36dp.png │ ├── ic_call_white_48dp.png │ ├── ic_folder_white_48dp.png │ ├── ic_inbox_white_48dp.png │ ├── ic_message_white_48dp.png │ ├── ic_pause_white_48dp.png │ ├── ic_play_arrow_white_48dp.png │ ├── ic_reply_white_48dp.png │ ├── ic_speaker_notes_white_36dp.png │ ├── ic_textsms_white_36dp.png │ └── ic_voicemail_white_48dp.png │ ├── drawable-xxxhdpi │ ├── ic_archive_white_36dp.png │ ├── ic_call_white_48dp.png │ ├── ic_folder_white_48dp.png │ ├── ic_inbox_white_48dp.png │ ├── ic_message_white_48dp.png │ ├── ic_pause_white_48dp.png │ ├── ic_play_arrow_white_48dp.png │ ├── ic_reply_white_48dp.png │ ├── ic_speaker_notes_white_36dp.png │ ├── ic_textsms_white_36dp.png │ └── ic_voicemail_white_48dp.png │ ├── drawable │ ├── drawer_header.xml │ └── logo.png │ ├── layout │ ├── activity_annotation.xml │ ├── activity_call.xml │ ├── activity_inbox.xml │ ├── activity_login.xml │ ├── activity_message.xml │ ├── activity_sms.xml │ ├── dialog_settings.xml │ ├── drawer_header.xml │ ├── fragment_annotation_item.xml │ ├── fragment_inbox_item.xml │ ├── fragment_refreshable_list.xml │ └── fragment_toolbar.xml │ ├── menu │ ├── menu_drawer.xml │ ├── menu_inbox.xml │ └── menu_message.xml │ ├── mipmap-hdpi │ └── ic_launcher.png │ ├── mipmap-mdpi │ └── ic_launcher.png │ ├── mipmap-xhdpi │ └── ic_launcher.png │ ├── mipmap-xxhdpi │ └── ic_launcher.png │ ├── mipmap-xxxhdpi │ └── ic_launcher.png │ └── values │ ├── colors.xml │ ├── strings.xml │ └── themes.xml ├── build.gradle ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | .gradle 2 | /local.properties 3 | /.idea/workspace.xml 4 | /.idea/libraries 5 | .DS_Store 6 | /build 7 | /app/build 8 | -------------------------------------------------------------------------------- /.idea/.name: -------------------------------------------------------------------------------- 1 | OpenVBX-Android -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 22 | -------------------------------------------------------------------------------- /.idea/copyright/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 24 | 25 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 19 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 46 | 47 | 48 | 49 | 50 | 1.7 51 | 52 | 57 | 58 | 59 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /OpenVBX.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # OpenVBX for Android [![Google Play](http://developer.android.com/images/brand/en_generic_rgb_wo_45.png)](https://play.google.com/store/apps/details?id=org.openvbx) 2 | 3 | This repository contains the source code for the OpenVBX Android app. 4 | 5 | Please see the [issues](https://github.com/chadsmith/OpenVBX-Android/issues) section to report any bugs or feature requests and to see the list of known issues. 6 | 7 | ## Requirements 8 | 9 | * OpenVBX 0.90 or higher 10 | * Android 4.0.3 or higher 11 | 12 | ## Building in Android Studio 13 | 14 | `New > Import Project...` or `New > Project from Version Control > GitHub` should do the trick. 15 | 16 | ## Trademarks 17 | 18 | "OpenVBX" and "Twilio" are trademarks of Twilio, Inc., all rights reserved. If you want to redistribute this Android app, you must come up with your own product name. Use of the Twilio trademarks in your product name requires Twilio's written permission. This version of OpenVBX for Android was released with permission from Twilio, Inc. 19 | 20 | ## Acknowledgements 21 | 22 | This project uses open source libraries such as: 23 | 24 | * [RxAndroid](https://github.com/ReactiveX/RxAndroid) 25 | * [Retrofit](http://square.github.io/retrofit/) 26 | 27 | ## Contributing 28 | 29 | Please fork this repository and contribute back using [pull requests](https://github.com/chadsmith/OpenVBX-Android/pulls). 30 | 31 | Any contributions, large or small, major features, bug fixes, additional language translations, unit/integration tests are welcomed and appreciated. -------------------------------------------------------------------------------- /app/app.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 8 | 9 | 10 | 11 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 23 5 | buildToolsVersion "23.0.2" 6 | defaultConfig { 7 | applicationId "org.openvbx" 8 | minSdkVersion 15 9 | targetSdkVersion 23 10 | versionCode 3 11 | versionName "1.0.1" 12 | multiDexEnabled true 13 | } 14 | buildTypes { 15 | release { 16 | minifyEnabled false 17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt' 18 | } 19 | } 20 | compileOptions { 21 | sourceCompatibility JavaVersion.VERSION_1_7 22 | targetCompatibility JavaVersion.VERSION_1_7 23 | } 24 | productFlavors { 25 | } 26 | } 27 | 28 | dependencies { 29 | compile fileTree(dir: 'libs', include: ['*.jar']) 30 | compile 'com.google.android.gms:play-services-gcm:8.4.0' 31 | compile 'io.reactivex:rxandroid:1.1.0' 32 | compile 'com.squareup.okhttp3:okhttp:3.2.0' 33 | compile 'com.squareup.okhttp3:logging-interceptor:3.2.0' 34 | compile 'com.squareup.retrofit2:retrofit:2.0.2' 35 | compile 'com.squareup.retrofit2:converter-gson:2.0.2' 36 | compile 'com.squareup.retrofit2:converter-scalars:2.0.2' 37 | compile 'com.squareup.retrofit2:adapter-rxjava:2.0.2' 38 | compile 'com.android.support:multidex:1.0.1' 39 | compile 'com.android.support:support-v4:23.3.0' 40 | compile 'com.android.support:appcompat-v7:23.3.0' 41 | compile 'com.android.support:design:23.3.0' 42 | } -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 17 | 21 | 22 | 26 | 27 | 28 | 29 | 30 | 31 | 34 | 35 | 38 | 39 | 43 | 44 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /app/src/main/ic_launcher-web.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chadsmith/OpenVBX-Android/53aafb87aa3d404ecd98f37809562079aea8ddf0/app/src/main/ic_launcher-web.png -------------------------------------------------------------------------------- /app/src/main/java/org/openvbx/AnnotationActivity.java: -------------------------------------------------------------------------------- 1 | package org.openvbx; 2 | 3 | import android.os.Bundle; 4 | import android.support.v7.app.AppCompatActivity; 5 | import android.support.v7.widget.Toolbar; 6 | import android.view.MenuItem; 7 | import android.view.View; 8 | import android.widget.EditText; 9 | 10 | import org.openvbx.models.Message; 11 | 12 | import rx.functions.Action1; 13 | 14 | // TODO - move to dialog 15 | public class AnnotationActivity extends AppCompatActivity { 16 | 17 | private Message message; 18 | 19 | @Override 20 | public void onCreate(Bundle savedInstanceState) { 21 | super.onCreate(savedInstanceState); 22 | 23 | setContentView(R.layout.activity_annotation); 24 | 25 | Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); 26 | setSupportActionBar(toolbar); 27 | getSupportActionBar().setDisplayHomeAsUpEnabled(true); 28 | 29 | Bundle extras = getIntent().getExtras(); 30 | message = extras.getParcelable("message"); 31 | findViewById(R.id.add).setOnClickListener(new View.OnClickListener() { 32 | public void onClick(View view) { 33 | String description = ((EditText) findViewById(R.id.description)).getText().toString(); 34 | if (!description.isEmpty()) { 35 | OpenVBX.toast(R.string.saving); 36 | OpenVBX.API.addAnnotation(message.id, "noted", description) 37 | .compose(OpenVBX.defaultSchedulers()) 38 | .subscribe(new Action1() { 39 | @Override 40 | public void call(Void ignored) { 41 | finish(); 42 | } 43 | }); 44 | } 45 | } 46 | }); 47 | } 48 | 49 | @Override 50 | public boolean onOptionsItemSelected(MenuItem item) { 51 | int id = item.getItemId(); 52 | switch(id) { 53 | case android.R.id.home: 54 | finish(); 55 | break; 56 | } 57 | return super.onOptionsItemSelected(item); 58 | } 59 | 60 | } -------------------------------------------------------------------------------- /app/src/main/java/org/openvbx/CallActivity.java: -------------------------------------------------------------------------------- 1 | package org.openvbx; 2 | 3 | import android.app.Activity; 4 | import android.os.Bundle; 5 | import android.support.v7.app.AppCompatActivity; 6 | import android.support.v7.widget.Toolbar; 7 | import android.view.MenuItem; 8 | import android.view.View; 9 | import android.widget.EditText; 10 | import android.widget.Spinner; 11 | 12 | import org.openvbx.adapters.CallerIDAdapter; 13 | import org.openvbx.models.MessageResult; 14 | 15 | import rx.functions.Action1; 16 | 17 | public class CallActivity extends AppCompatActivity { 18 | 19 | private Activity mActivity; 20 | private Spinner spinner; 21 | private EditText toInput; 22 | private int message_id = -1; 23 | 24 | // TODO - switch call button to FAB 25 | // TODO - add custom dial pad 26 | @Override 27 | public void onCreate(Bundle savedInstanceState) { 28 | super.onCreate(savedInstanceState); 29 | 30 | mActivity = this; 31 | setContentView(R.layout.activity_call); 32 | 33 | Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); 34 | setSupportActionBar(toolbar); 35 | getSupportActionBar().setDisplayHomeAsUpEnabled(true); 36 | 37 | spinner = (Spinner) findViewById(R.id.callerid); 38 | CallerIDAdapter adapter = new CallerIDAdapter(this, OpenVBX.getCallerIDs("voice")); 39 | spinner.setAdapter(adapter); 40 | 41 | toInput = (EditText) findViewById(R.id.to); 42 | 43 | Bundle extras = getIntent().getExtras(); 44 | if(extras != null) { 45 | spinner.setSelection(adapter.indexOf(extras.getString("from"))); 46 | toInput.setText(extras.getString("to")); 47 | message_id = extras.getInt("message_id", -1); 48 | } 49 | 50 | findViewById(R.id.call).setOnClickListener(new View.OnClickListener() { 51 | public void onClick(View view) { 52 | String callerid = spinner.getSelectedItem().toString(); 53 | String to = toInput.getText().toString().trim(); 54 | if(!callerid.isEmpty() && !to.isEmpty()) { 55 | (message_id == -1 ? OpenVBX.API.sendCall(callerid, OpenVBX.device, to) : OpenVBX.API.sendCallback(message_id, callerid, OpenVBX.device, to)) 56 | .compose(OpenVBX.defaultSchedulers()) 57 | .subscribe(new Action1() { 58 | @Override 59 | public void call(MessageResult result) { 60 | if (result.error) 61 | OpenVBX.error(mActivity, result.message); 62 | else { 63 | OpenVBX.toast(R.string.calling); 64 | finish(); 65 | } 66 | } 67 | }); 68 | } 69 | } 70 | }); 71 | } 72 | 73 | @Override 74 | public boolean onOptionsItemSelected(MenuItem item) { 75 | int id = item.getItemId(); 76 | switch(id) { 77 | case android.R.id.home: 78 | finish(); 79 | break; 80 | } 81 | return super.onOptionsItemSelected(item); 82 | } 83 | 84 | } -------------------------------------------------------------------------------- /app/src/main/java/org/openvbx/InboxActivity.java: -------------------------------------------------------------------------------- 1 | package org.openvbx; 2 | 3 | import android.content.Context; 4 | import android.content.DialogInterface; 5 | import android.content.Intent; 6 | import android.os.Bundle; 7 | import android.support.design.widget.NavigationView; 8 | import android.support.v4.widget.DrawerLayout; 9 | import android.support.v4.widget.SwipeRefreshLayout; 10 | import android.support.v7.app.ActionBarDrawerToggle; 11 | import android.support.v7.app.AlertDialog; 12 | import android.support.v7.app.AppCompatActivity; 13 | import android.support.v7.widget.Toolbar; 14 | import android.view.LayoutInflater; 15 | import android.view.Menu; 16 | import android.view.MenuItem; 17 | import android.view.View; 18 | import android.widget.AdapterView; 19 | import android.widget.BaseAdapter; 20 | import android.widget.EditText; 21 | import android.widget.HeaderViewListAdapter; 22 | import android.widget.LinearLayout; 23 | import android.widget.ListView; 24 | import android.widget.TextView; 25 | 26 | import org.openvbx.adapters.MessagesAdapter; 27 | import org.openvbx.models.CallerID; 28 | import org.openvbx.models.Folder; 29 | import org.openvbx.models.Inbox; 30 | import org.openvbx.models.Message; 31 | 32 | import java.util.ArrayList; 33 | 34 | import rx.Observable; 35 | import rx.functions.Action1; 36 | import rx.functions.Func1; 37 | 38 | public class InboxActivity extends AppCompatActivity { 39 | 40 | private Context mContext; 41 | private DrawerLayout drawerLayout; 42 | private Toolbar toolbar; 43 | private NavigationView navigationView; 44 | private LinearLayout mainContent; 45 | private MessagesAdapter messagesAdapter; 46 | private LinearLayout progress; 47 | private SwipeRefreshLayout refreshView; 48 | 49 | private Inbox inbox; 50 | private Folder folder = new Folder(); 51 | 52 | @Override 53 | public void onCreate(Bundle savedInstanceState) { 54 | super.onCreate(savedInstanceState); 55 | setContentView(R.layout.activity_inbox); 56 | 57 | Bundle extras = getIntent().getExtras(); 58 | 59 | if(extras != null) 60 | inbox = extras.getParcelable("inbox"); 61 | 62 | mContext = getApplicationContext(); 63 | 64 | drawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout); 65 | toolbar = (Toolbar) findViewById(R.id.toolbar); 66 | setSupportActionBar(toolbar); 67 | 68 | mainContent = (LinearLayout) findViewById(R.id.main); 69 | 70 | ActionBarDrawerToggle drawerToggle = new ActionBarDrawerToggle(this, drawerLayout, toolbar, R.string.open, R.string.close) { 71 | public void onDrawerClosed(View view) { 72 | super.onDrawerClosed(view); 73 | } 74 | 75 | public void onDrawerOpened(View drawerView) { 76 | super.onDrawerOpened(drawerView); 77 | } 78 | 79 | public void onDrawerSlide(View drawerView, float slideOffset) { 80 | super.onDrawerSlide(drawerView, slideOffset); 81 | mainContent.setTranslationX(slideOffset * drawerView.getWidth()); 82 | drawerLayout.bringChildToFront(drawerView); 83 | drawerLayout.requestLayout(); 84 | } 85 | }; 86 | drawerLayout.setDrawerListener(drawerToggle); 87 | drawerToggle.syncState(); 88 | 89 | View headerView = LayoutInflater.from(this).inflate(R.layout.drawer_header, null); 90 | TextView currentUser = (TextView) headerView.findViewById(R.id.current_user); 91 | currentUser.setText(OpenVBX.email); 92 | 93 | navigationView = (NavigationView) findViewById(R.id.navigation_view); 94 | navigationView.addHeaderView(headerView); 95 | navigationView.setNavigationItemSelectedListener(new NavigationView.OnNavigationItemSelectedListener() { 96 | @Override 97 | public boolean onNavigationItemSelected(MenuItem menuItem) { 98 | int itemId = menuItem.getItemId(); 99 | switch (itemId) { 100 | case R.id.settings: 101 | requestDevice(); 102 | break; 103 | case R.id.logout: 104 | OpenVBX.signOut(); 105 | requireLogin(); 106 | break; 107 | default: 108 | selectFolder(itemId); 109 | menuItem.setChecked(true); 110 | } 111 | drawerLayout.closeDrawers(); 112 | return true; 113 | } 114 | }); 115 | 116 | progress = (LinearLayout) findViewById(R.id.progress); 117 | refreshView = (SwipeRefreshLayout) findViewById(R.id.refresh); 118 | ListView listView = (ListView) findViewById(R.id.list); 119 | 120 | messagesAdapter = new MessagesAdapter(this, R.layout.fragment_inbox_item, folder.messages.items); 121 | 122 | if(OpenVBX.endpoint == null || OpenVBX.email == null || OpenVBX.password == null) 123 | requireLogin(); 124 | else if(OpenVBX.device == null) 125 | requestDevice(); 126 | else 127 | getOutgoingCallerIDs(); 128 | 129 | listView.setAdapter(messagesAdapter); 130 | listView.setOnItemClickListener(new AdapterView.OnItemClickListener() { 131 | @Override 132 | public void onItemClick(AdapterView parent, View view, int position, long id) { 133 | Message message = messagesAdapter.get(position); 134 | Intent i = new Intent(mContext, MessageActivity.class); 135 | i.putExtra("message", message); 136 | startActivity(i); 137 | } 138 | }); 139 | refreshView.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() { 140 | @Override 141 | public void onRefresh() { 142 | refresh(); 143 | } 144 | }); 145 | progress.setVisibility(View.VISIBLE); 146 | findViewById(R.id.call).setOnClickListener(new View.OnClickListener() { 147 | @Override 148 | public void onClick(View v) { 149 | Intent i = new Intent(mContext, CallActivity.class); 150 | startActivity(i); 151 | } 152 | }); 153 | } 154 | 155 | public void requireLogin() { 156 | Intent i = new Intent(mContext, LoginActivity.class); 157 | startActivity(i); 158 | finish(); 159 | } 160 | 161 | public void requestDevice() { 162 | View dialogView = LayoutInflater.from(this).inflate(R.layout.dialog_settings, null); 163 | final EditText deviceInput = (EditText) dialogView.findViewById(R.id.device); 164 | deviceInput.setText(OpenVBX.device); 165 | new AlertDialog.Builder(this) 166 | .setView(dialogView) 167 | .setCancelable(false) 168 | .setPositiveButton(R.string.save, new DialogInterface.OnClickListener() { 169 | @Override 170 | public void onClick(DialogInterface dialog, int which) { 171 | String device = deviceInput.getText().toString(); 172 | if (!device.isEmpty()) { 173 | OpenVBX.saveDevice(device); 174 | dialog.dismiss(); 175 | } 176 | } 177 | }) 178 | .show(); 179 | } 180 | 181 | public void getOutgoingCallerIDs() { 182 | OpenVBX.API.getOutgoingCallerIDs() 183 | .compose(OpenVBX.>defaultSchedulers()) 184 | .subscribe(new Action1>() { 185 | @Override 186 | public void call(ArrayList callerIDs) { 187 | OpenVBX.callerIDs = callerIDs; 188 | } 189 | }); 190 | } 191 | 192 | public void refresh() { 193 | OpenVBX.API.getFolders() 194 | .flatMap(new Func1>() { 195 | @Override 196 | public Observable call(Inbox update) { 197 | inbox = update; 198 | if (folder.id != -1) 199 | return OpenVBX.API.getFolder(folder.id); 200 | else if (!inbox.folders.isEmpty()) 201 | return OpenVBX.API.getFolder(inbox.folders.get(0).id); 202 | else 203 | return Observable.just(new Folder()); 204 | } 205 | }) 206 | .compose(OpenVBX.defaultSchedulers()) 207 | .subscribe(new Action1() { 208 | @Override 209 | public void call(Folder selected) { 210 | folder = selected; 211 | updateNavigationMenu(); 212 | messagesAdapter.update(folder.messages.items); 213 | toolbar.setTitle(folder.name == null ? getString(R.string.app_name) : folder.name); 214 | progress.setVisibility(View.GONE); 215 | refreshView.setRefreshing(false); 216 | } 217 | }); 218 | } 219 | 220 | private void updateNavigationMenu() { 221 | Menu menu = navigationView.getMenu(); 222 | menu.removeGroup(R.id.primary_group); 223 | for(int i = 0; i < inbox.folders.size(); i++) { 224 | Folder folder = inbox.folders.get(i); 225 | MenuItem menuItem = menu.add(R.id.primary_group, i, i, folder.name); 226 | menuItem.setIcon("inbox".equals(folder.type) ? R.drawable.ic_inbox_white_48dp : R.drawable.ic_folder_white_48dp); 227 | } 228 | invalidateNavigationMenu(); 229 | } 230 | 231 | private void invalidateNavigationMenu() { 232 | for (int i = 0, count = navigationView.getChildCount(); i < count; i++) { 233 | final View child = navigationView.getChildAt(i); 234 | if (child != null && child instanceof ListView) { 235 | final ListView menuView = (ListView) child; 236 | final HeaderViewListAdapter adapter = (HeaderViewListAdapter) menuView.getAdapter(); 237 | final BaseAdapter wrapped = (BaseAdapter) adapter.getWrappedAdapter(); 238 | wrapped.notifyDataSetChanged(); 239 | } 240 | } 241 | } 242 | 243 | private void selectFolder(int folder_id) { 244 | if(folder.id != folder_id) 245 | progress.setVisibility(View.VISIBLE); 246 | OpenVBX.API.getFolder(folder_id) 247 | .compose(OpenVBX.defaultSchedulers()) 248 | .subscribe(new Action1() { 249 | @Override 250 | public void call(Folder selected) { 251 | folder = selected; 252 | messagesAdapter.update(folder.messages.items); 253 | toolbar.setTitle(folder.name); 254 | progress.setVisibility(View.GONE); 255 | } 256 | }); 257 | } 258 | 259 | @Override 260 | protected void onResume() { 261 | super.onResume(); 262 | refresh(); 263 | } 264 | 265 | @Override 266 | public boolean onCreateOptionsMenu(Menu menu) { 267 | getMenuInflater().inflate(R.menu.menu_inbox, menu); 268 | return true; 269 | } 270 | 271 | @Override 272 | public boolean onOptionsItemSelected(MenuItem item) { 273 | int id = item.getItemId(); 274 | Intent i; 275 | switch(id) { 276 | case android.R.id.home: 277 | finish(); 278 | break; 279 | case R.id.action_new_sms: 280 | i = new Intent(mContext, SmsActivity.class); 281 | startActivity(i); 282 | break; 283 | } 284 | return super.onOptionsItemSelected(item); 285 | } 286 | 287 | } -------------------------------------------------------------------------------- /app/src/main/java/org/openvbx/LoginActivity.java: -------------------------------------------------------------------------------- 1 | package org.openvbx; 2 | 3 | import android.Manifest; 4 | import android.app.Activity; 5 | import android.content.Context; 6 | import android.content.Intent; 7 | import android.content.pm.PackageManager; 8 | import android.os.Build; 9 | import android.os.Bundle; 10 | import android.support.v4.app.ActivityCompat; 11 | import android.support.v7.app.AppCompatActivity; 12 | import android.telephony.TelephonyManager; 13 | import android.view.View; 14 | import android.widget.EditText; 15 | import android.widget.LinearLayout; 16 | 17 | import org.openvbx.models.ClientResult; 18 | import org.openvbx.models.Inbox; 19 | 20 | import rx.Subscriber; 21 | 22 | public class LoginActivity extends AppCompatActivity implements ActivityCompat.OnRequestPermissionsResultCallback { 23 | 24 | private static final int PERMISSION_REQUEST_PHONE_STATE = 0; 25 | private Activity mActivity; 26 | private Context mContext; 27 | 28 | private LinearLayout setupView; 29 | private EditText endpointInput; 30 | private String endpoint; 31 | 32 | private LinearLayout loginView; 33 | private EditText emailInput; 34 | private EditText passwordInput; 35 | private String email; 36 | private String password; 37 | 38 | private Boolean canCheckPhoneState; 39 | 40 | @Override 41 | public void onCreate(Bundle savedInstanceState) { 42 | super.onCreate(savedInstanceState); 43 | setContentView(R.layout.activity_login); 44 | 45 | mActivity = this; 46 | mContext = getApplicationContext(); 47 | setupView = (LinearLayout) findViewById(R.id.setup); 48 | endpointInput = (EditText) findViewById(R.id.endpoint); 49 | 50 | findViewById(R.id.next).setOnClickListener(new View.OnClickListener() { 51 | public void onClick(View view) { 52 | endpoint = endpointInput.getText().toString().trim(); 53 | if(!endpoint.isEmpty()) { 54 | OpenVBX.setEndpoint(endpoint); 55 | checkEndpoint(); 56 | } 57 | } 58 | }); 59 | 60 | endpointInput.setText(OpenVBX.endpoint); 61 | 62 | if(ActivityCompat.checkSelfPermission(mActivity, Manifest.permission.READ_PHONE_STATE) == PackageManager.PERMISSION_GRANTED) 63 | canCheckPhoneState = true; 64 | else 65 | ActivityCompat.requestPermissions(this, new String[]{ Manifest.permission.READ_PHONE_STATE }, PERMISSION_REQUEST_PHONE_STATE); 66 | 67 | loginView = (LinearLayout) findViewById(R.id.sign_in); 68 | emailInput = (EditText) findViewById(R.id.email); 69 | passwordInput = (EditText) findViewById(R.id.password); 70 | 71 | findViewById(R.id.login).setOnClickListener(new View.OnClickListener() { 72 | public void onClick(View view) { 73 | email = emailInput.getText().toString().trim(); 74 | password = passwordInput.getText().toString(); 75 | if(!email.isEmpty() && !password.isEmpty()) { 76 | OpenVBX.setLogin(email, password); 77 | OpenVBX.API.getFolders() 78 | .compose(OpenVBX.defaultSchedulers()) 79 | .subscribe(new Subscriber() { 80 | @Override 81 | public void onCompleted() { 82 | } 83 | 84 | @Override 85 | public void onError(Throwable e) { 86 | OpenVBX.setLogin(email, null); 87 | passwordInput.setText(null); 88 | OpenVBX.error(mActivity, R.string.login_failed); 89 | } 90 | 91 | @Override 92 | public void onNext(Inbox inbox) { 93 | OpenVBX.saveLogin(); 94 | if (canCheckPhoneState) { 95 | TelephonyManager telephonyManager = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE); 96 | String device = telephonyManager.getLine1Number(); 97 | if (!device.isEmpty() && !Build.PRODUCT.equals("sdk") && !Build.PRODUCT.equals("google_sdk")) 98 | OpenVBX.saveDevice(device); 99 | } 100 | Intent i = new Intent(mContext, InboxActivity.class); 101 | i.putExtra("inbox", inbox); 102 | startActivity(i); 103 | finish(); 104 | } 105 | }); 106 | } 107 | } 108 | }); 109 | 110 | emailInput.setText(OpenVBX.email); 111 | passwordInput.setText(OpenVBX.password); 112 | } 113 | 114 | public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) { 115 | switch (requestCode) { 116 | case PERMISSION_REQUEST_PHONE_STATE: 117 | if (grantResults.length == 1 && grantResults[0] == PackageManager.PERMISSION_GRANTED) 118 | canCheckPhoneState = true; 119 | break; 120 | default: 121 | super.onRequestPermissionsResult(requestCode, permissions, grantResults); 122 | } 123 | } 124 | 125 | private void checkEndpoint() { 126 | OpenVBX.API.checkEndpoint() 127 | .compose(OpenVBX.defaultSchedulers()) 128 | .subscribe(new Subscriber() { 129 | @Override 130 | public void onCompleted() { 131 | } 132 | 133 | @Override 134 | public void onError(Throwable e) { 135 | if (endpoint.endsWith("index.php")) { 136 | OpenVBX.setEndpoint(null); 137 | endpointInput.setText(null); 138 | OpenVBX.error(mActivity, R.string.invalid_endpoint); 139 | } else { 140 | if (!endpoint.endsWith("/")) 141 | endpoint += "/"; 142 | endpoint += "index.php"; 143 | OpenVBX.setEndpoint(endpoint); 144 | checkEndpoint(); 145 | } 146 | } 147 | 148 | @Override 149 | public void onNext(ClientResult result) { 150 | OpenVBX.saveEndpoint(); 151 | setupView.setVisibility(View.GONE); 152 | loginView.setVisibility(View.VISIBLE); 153 | } 154 | }); 155 | } 156 | 157 | } -------------------------------------------------------------------------------- /app/src/main/java/org/openvbx/MessageActivity.java: -------------------------------------------------------------------------------- 1 | package org.openvbx; 2 | 3 | import android.content.Intent; 4 | import android.media.AudioManager; 5 | import android.media.MediaPlayer; 6 | import android.os.Bundle; 7 | import android.support.v7.app.AppCompatActivity; 8 | import android.support.v7.widget.Toolbar; 9 | import android.view.Menu; 10 | import android.view.MenuItem; 11 | import android.view.MotionEvent; 12 | import android.view.View; 13 | import android.view.View.OnTouchListener; 14 | import android.widget.AdapterView; 15 | import android.widget.AdapterView.OnItemSelectedListener; 16 | import android.widget.ArrayAdapter; 17 | import android.widget.ImageView; 18 | import android.widget.LinearLayout; 19 | import android.widget.ListView; 20 | import android.widget.SeekBar; 21 | import android.widget.Spinner; 22 | import android.widget.TextView; 23 | 24 | import org.openvbx.adapters.AnnotationAdapter; 25 | import org.openvbx.adapters.UserAdapter; 26 | import org.openvbx.models.Annotation; 27 | import org.openvbx.models.Message; 28 | import org.openvbx.models.User; 29 | 30 | import java.util.ArrayList; 31 | 32 | import rx.functions.Action1; 33 | 34 | public class MessageActivity extends AppCompatActivity { 35 | 36 | private Boolean isVoice = false; 37 | private MediaPlayer mediaPlayer = null; 38 | private SeekBar seekBar; 39 | private ImageView play; 40 | private Thread updateProgress = null; 41 | private String statuses[] = { "Open", "Closed", "Pending" }; 42 | private Spinner status; 43 | private Spinner assigned; 44 | private UserAdapter userAdapter; 45 | private AnnotationAdapter annotationAdapter; 46 | private Message message; 47 | private ListView annotationList; 48 | private LinearLayout progress; 49 | private Boolean isPaused = false; 50 | 51 | @Override 52 | public void onCreate(Bundle savedInstanceState) { 53 | super.onCreate(savedInstanceState); 54 | 55 | setContentView(R.layout.activity_message); 56 | 57 | Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); 58 | setSupportActionBar(toolbar); 59 | getSupportActionBar().setDisplayHomeAsUpEnabled(true); 60 | 61 | progress = (LinearLayout) findViewById(R.id.progress); 62 | 63 | Bundle extras = getIntent().getExtras(); 64 | message = extras.getParcelable("message"); 65 | 66 | annotationList = (ListView) findViewById(R.id.annotations); 67 | annotationAdapter = new AnnotationAdapter(this, R.layout.fragment_annotation_item, new ArrayList()); 68 | annotationList.setAdapter(annotationAdapter); 69 | 70 | status = (Spinner) findViewById(R.id.status); 71 | ArrayAdapter statusAdapter = new ArrayAdapter(this, android.R.layout.simple_spinner_item, statuses); 72 | statusAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); 73 | status.setAdapter(statusAdapter); 74 | status.setOnItemSelectedListener(new OnItemSelectedListener() { 75 | public void onItemSelected(AdapterView parent, View view, int pos, long id) { 76 | final String status = statuses[pos].toLowerCase(); 77 | OpenVBX.API.updateTicketStatus(message.id, status) 78 | .compose(OpenVBX.defaultSchedulers()) 79 | .subscribe(new Action1() { 80 | @Override 81 | public void call(Void ignored) { 82 | // TODO - refresh view 83 | } 84 | }); 85 | } 86 | 87 | public void onNothingSelected(AdapterView parent) { 88 | } 89 | }); 90 | 91 | assigned = (Spinner) findViewById(R.id.assigned); 92 | userAdapter = new UserAdapter(this, new ArrayList()); 93 | assigned.setAdapter(userAdapter); 94 | assigned.setOnItemSelectedListener(new OnItemSelectedListener() { 95 | public void onItemSelected(AdapterView parent, View view, int pos, long id) { 96 | final int user_id = message.active_users.get(pos).id; 97 | OpenVBX.API.updateAssignment(message.id, user_id) 98 | .compose(OpenVBX.defaultSchedulers()) 99 | .subscribe(new Action1() { 100 | @Override 101 | public void call(Void ignored) { 102 | // TODO - refresh view 103 | } 104 | }); 105 | } 106 | 107 | public void onNothingSelected(AdapterView parent) { 108 | } 109 | }); 110 | 111 | progress.setVisibility(View.VISIBLE); 112 | 113 | findViewById(R.id.reply).setOnClickListener(new View.OnClickListener() { 114 | public void onClick(View view) { 115 | Intent i = new Intent(getApplicationContext(), isVoice ? CallActivity.class : SmsActivity.class); 116 | i.putExtra("from", message.original_called); 117 | i.putExtra("to", message.original_caller); 118 | i.putExtra("message_id", message.id); 119 | startActivity(i); 120 | } 121 | }); 122 | } 123 | 124 | public void refresh() { 125 | OpenVBX.API.getMessage(message.id) 126 | .compose(OpenVBX.defaultSchedulers()) 127 | .subscribe(new Action1() { 128 | @Override 129 | public void call(Message update) { 130 | message = update; 131 | 132 | ((TextView) findViewById(R.id.caller)).setText(message.caller); 133 | ((TextView) findViewById(R.id.received_time)).setText(message.receivedTime()); 134 | 135 | TextView folder = (TextView) findViewById(R.id.folder); 136 | folder.setText(message.folder); 137 | if (message.folder.isEmpty()) 138 | folder.setVisibility(View.GONE); 139 | 140 | ((TextView) findViewById(R.id.summary)).setText(message.summary); 141 | 142 | if (message.isVoice()) { 143 | for (int i = 0; i < statuses.length; i++) 144 | if (statuses[i].toLowerCase().equals(message.ticket_status)) 145 | status.setSelection(i); 146 | status.setVisibility(View.VISIBLE); 147 | 148 | if (!message.folder.isEmpty()) { 149 | message.active_users.add(0, new User(0, "Select a", "user")); 150 | userAdapter.update(message.active_users); 151 | if (message.assigned != -1) 152 | assigned.setSelection(userAdapter.indexOf(message.assigned)); 153 | assigned.setVisibility(View.VISIBLE); 154 | } 155 | 156 | if (!message.annotations.items.isEmpty()) { 157 | annotationAdapter.update(message.annotations.items); 158 | annotationList.setVisibility(View.VISIBLE); 159 | } 160 | 161 | status.setVisibility(View.VISIBLE); 162 | play = (ImageView) findViewById(R.id.play); 163 | seekBar = (SeekBar) findViewById(R.id.seekbar); 164 | seekBar.setOnTouchListener(new OnTouchListener() { 165 | public boolean onTouch(View v, MotionEvent event) { 166 | if (mediaPlayer.isPlaying()) { 167 | SeekBar sb = (SeekBar) v; 168 | mediaPlayer.seekTo(sb.getProgress()); 169 | } 170 | return false; 171 | } 172 | }); 173 | play.setOnClickListener(new View.OnClickListener() { 174 | @Override 175 | public void onClick(View v) { 176 | if(mediaPlayer.isPlaying()) { 177 | isPaused = true; 178 | mediaPlayer.pause(); 179 | updateButtonState(false); 180 | } 181 | else { 182 | isPaused = false; 183 | int curr = seekBar.getProgress(); 184 | int total = mediaPlayer.getDuration(); 185 | seekBar.setMax(total); 186 | if(curr < total) 187 | mediaPlayer.seekTo(curr); 188 | else 189 | mediaPlayer.seekTo(0); 190 | mediaPlayer.start(); 191 | updateButtonState(true); 192 | updateProgress = new Thread(new Runnable() { 193 | public void run() { 194 | Thread currentThread = Thread.currentThread(); 195 | int curr = mediaPlayer.getCurrentPosition(); 196 | int total = mediaPlayer.getDuration(); 197 | seekBar.setProgress(curr); 198 | while (currentThread == updateProgress && mediaPlayer != null && mediaPlayer.isPlaying()) { 199 | try { 200 | curr = mediaPlayer.getCurrentPosition(); 201 | seekBar.setProgress(curr); 202 | Thread.sleep(100); 203 | } catch (InterruptedException e) { 204 | e.printStackTrace(); 205 | } 206 | } 207 | if(!isPaused) { 208 | seekBar.setProgress(total); 209 | updateButtonState(false); 210 | } 211 | } 212 | }); 213 | updateProgress.start(); 214 | } 215 | } 216 | }); 217 | seekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { 218 | @Override 219 | public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { 220 | if(fromUser) 221 | mediaPlayer.seekTo(progress); 222 | } 223 | 224 | @Override 225 | public void onStartTrackingTouch(SeekBar seekBar) { 226 | } 227 | 228 | @Override 229 | public void onStopTrackingTouch(SeekBar seekBar) { 230 | } 231 | }); 232 | mediaPlayer = new MediaPlayer(); 233 | mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC); 234 | try { 235 | mediaPlayer.setDataSource(message.recording_url); 236 | } catch (Exception e) { 237 | e.printStackTrace(); 238 | } 239 | mediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() { 240 | @Override 241 | public void onPrepared(MediaPlayer mp) { 242 | findViewById(R.id.audio).setVisibility(View.VISIBLE); 243 | } 244 | }); 245 | mediaPlayer.prepareAsync(); 246 | } 247 | 248 | invalidateOptionsMenu(); 249 | progress.setVisibility(View.GONE); 250 | } 251 | }); 252 | } 253 | 254 | public void updateButtonState(final Boolean showPause) { 255 | runOnUiThread(new Runnable() { 256 | @Override 257 | public void run() { 258 | play.setImageResource(showPause ? R.drawable.ic_pause_white_48dp : R.drawable.ic_play_arrow_white_48dp); 259 | } 260 | }); 261 | } 262 | 263 | @Override 264 | protected void onResume() { 265 | super.onResume(); 266 | refresh(); 267 | } 268 | 269 | @Override 270 | public boolean onCreateOptionsMenu(Menu menu) { 271 | getMenuInflater().inflate(R.menu.menu_message, menu); 272 | menu.findItem(R.id.action_add_note).setVisible(isVoice); 273 | return true; 274 | } 275 | 276 | @Override 277 | public boolean onOptionsItemSelected(MenuItem item) { 278 | int id = item.getItemId(); 279 | Intent i; 280 | switch(id) { 281 | case android.R.id.home: 282 | finish(); 283 | break; 284 | case R.id.action_add_note: 285 | i = new Intent(getApplicationContext(), AnnotationActivity.class); 286 | i.putExtra("message", message); 287 | startActivity(i); 288 | break; 289 | case R.id.action_archive: 290 | // TODO - confirm before deleting 291 | OpenVBX.API.archiveMessage(message.id, true) 292 | .compose(OpenVBX.defaultSchedulers()) 293 | .subscribe(new Action1() { 294 | @Override 295 | public void call(Void ignored) { 296 | OpenVBX.toast(R.string.message_archived); 297 | finish(); 298 | } 299 | }); 300 | break; 301 | } 302 | return super.onOptionsItemSelected(item); 303 | } 304 | 305 | @Override 306 | protected void onPause() { 307 | super.onPause(); 308 | updateProgress = null; 309 | if(mediaPlayer != null) { 310 | mediaPlayer.release(); 311 | mediaPlayer = null; 312 | } 313 | } 314 | } -------------------------------------------------------------------------------- /app/src/main/java/org/openvbx/OpenVBX.java: -------------------------------------------------------------------------------- 1 | package org.openvbx; 2 | 3 | import android.app.Activity; 4 | import android.app.Application; 5 | import android.content.Context; 6 | import android.content.SharedPreferences; 7 | import android.os.Handler; 8 | import android.os.Looper; 9 | import android.support.v7.app.AlertDialog; 10 | import android.util.Base64; 11 | import android.widget.Toast; 12 | 13 | import org.openvbx.models.CallerID; 14 | import org.openvbx.models.ClientResult; 15 | import org.openvbx.models.Folder; 16 | import org.openvbx.models.Inbox; 17 | import org.openvbx.models.Message; 18 | import org.openvbx.models.MessageResult; 19 | 20 | import java.io.IOException; 21 | import java.util.ArrayList; 22 | 23 | import okhttp3.Interceptor; 24 | import okhttp3.OkHttpClient; 25 | import okhttp3.Request; 26 | import okhttp3.Response; 27 | import okhttp3.logging.HttpLoggingInterceptor; 28 | import retrofit2.Retrofit; 29 | import retrofit2.adapter.rxjava.RxJavaCallAdapterFactory; 30 | import retrofit2.converter.gson.GsonConverterFactory; 31 | import retrofit2.http.Field; 32 | import retrofit2.http.FormUrlEncoded; 33 | import retrofit2.http.GET; 34 | import retrofit2.http.POST; 35 | import retrofit2.http.Path; 36 | import rx.Observable; 37 | import rx.android.schedulers.AndroidSchedulers; 38 | import rx.schedulers.Schedulers; 39 | 40 | public class OpenVBX extends Application { 41 | 42 | private static SharedPreferences settings; 43 | 44 | public static String endpoint = null; 45 | public static String email = null; 46 | public static String password = null; 47 | public static String device = null; 48 | public static Context mContext; 49 | 50 | public static VbxService API; 51 | 52 | public static ArrayList callerIDs = new ArrayList<>(); 53 | 54 | public void onCreate() { 55 | 56 | super.onCreate(); 57 | 58 | mContext = getApplicationContext(); 59 | settings = getSharedPreferences("settings", MODE_PRIVATE); 60 | endpoint = settings.getString("endpoint", null); 61 | email = settings.getString("email", null); 62 | password = settings.getString("password", null); 63 | device = settings.getString("device", null); 64 | 65 | if(endpoint != null && !endpoint.isEmpty()) 66 | API = getApiInstance(); 67 | } 68 | 69 | private static VbxService getApiInstance() { 70 | 71 | // Use HttpLoggingInterceptor.Level.NONE to disable logging 72 | HttpLoggingInterceptor logger = new HttpLoggingInterceptor(); 73 | logger.setLevel(HttpLoggingInterceptor.Level.BODY); 74 | 75 | Interceptor setAcceptType = new Interceptor() { 76 | @Override 77 | public Response intercept(Chain chain) throws IOException { 78 | Request request = chain.request(); 79 | Request typedRequest = request 80 | .newBuilder() 81 | .header("Accept", "application/json") 82 | .build(); 83 | return chain.proceed(typedRequest); 84 | } 85 | }; 86 | 87 | Interceptor setAuthorization = new Interceptor() { 88 | @Override 89 | public Response intercept(Chain chain) throws IOException { 90 | Request request = chain.request(); 91 | if(email != null && password != null) { 92 | String auth = Base64.encodeToString((email + ":" + password).getBytes(), Base64.NO_WRAP); 93 | Request typedRequest = request 94 | .newBuilder() 95 | .header("Authorization", String.format("Basic %s", auth)) 96 | .build(); 97 | return chain.proceed(typedRequest); 98 | } 99 | return chain.proceed(request); 100 | } 101 | }; 102 | 103 | OkHttpClient apiClient = new OkHttpClient.Builder() 104 | .addInterceptor(logger) 105 | .addInterceptor(setAcceptType) 106 | .addInterceptor(setAuthorization) 107 | .build(); 108 | 109 | return new Retrofit.Builder() 110 | .baseUrl(endpoint) 111 | .client(apiClient) 112 | .addCallAdapterFactory(RxJavaCallAdapterFactory.create()) 113 | .addConverterFactory(GsonConverterFactory.create()) 114 | .build() 115 | .create(VbxService.class); 116 | } 117 | 118 | public static ArrayList getCallerIDs(String type) { 119 | if("voice".equals(type)) 120 | return callerIDs; 121 | ArrayList result = new ArrayList<>(); 122 | for(CallerID callerID : callerIDs) 123 | if(callerID.hasSms()) 124 | result.add(callerID); 125 | return result; 126 | } 127 | 128 | public static void setEndpoint(String endpoint) { 129 | OpenVBX.endpoint = endpoint; 130 | API = getApiInstance(); 131 | } 132 | 133 | public static void saveEndpoint() { 134 | settings.edit().putString("endpoint", endpoint).apply(); 135 | } 136 | 137 | public static void setLogin(String email, String password) { 138 | OpenVBX.email = email; 139 | OpenVBX.password = password; 140 | } 141 | 142 | public static void saveLogin() { 143 | settings.edit().putString("email", email).putString("password", password).apply(); 144 | } 145 | 146 | public static void saveDevice(String device) { 147 | OpenVBX.device = device; 148 | settings.edit().putString("device", device).apply(); 149 | } 150 | 151 | public static void signOut() { 152 | settings.edit().clear().apply(); 153 | endpoint = null; 154 | email = null; 155 | password = null; 156 | device = null; 157 | } 158 | 159 | public static void error(Activity activity, String message) { 160 | new AlertDialog.Builder(activity) 161 | .setMessage(message) 162 | .setPositiveButton(R.string.ok, null) 163 | .show(); 164 | } 165 | 166 | public static void error(Activity activity, int message) { 167 | new AlertDialog.Builder(activity) 168 | .setMessage(message) 169 | .setPositiveButton(R.string.ok, null) 170 | .show(); 171 | } 172 | 173 | public static void toast(final String message) { 174 | new Handler(Looper.getMainLooper()).post(new Runnable() { 175 | @Override 176 | public void run() { 177 | Toast.makeText(mContext, message, Toast.LENGTH_SHORT).show(); 178 | } 179 | }); 180 | } 181 | 182 | public static void toast(int id) { 183 | toast(mContext.getString(id)); 184 | } 185 | 186 | public interface VbxService { 187 | 188 | @GET("/client") 189 | Observable checkEndpoint(); 190 | 191 | @GET("/messages/inbox") 192 | Observable getFolders(); 193 | 194 | @GET("/messages/inbox/{folder_id}") 195 | Observable getFolder(@Path("folder_id") int folder_id); 196 | 197 | @GET("/messages/details/{message_id}") 198 | Observable getMessage(@Path("message_id") int message_id); 199 | 200 | @FormUrlEncoded 201 | @POST("/messages/details/{message_id}") 202 | Observable archiveMessage(@Path("message_id") int message_id, @Field("archived") Boolean archived); 203 | 204 | @FormUrlEncoded 205 | @POST("/messages/details/{message_id}") 206 | Observable updateTicketStatus(@Path("message_id") int message_id, @Field("ticket_status") String status); 207 | 208 | @FormUrlEncoded 209 | @POST("/messages/details/{message_id}") 210 | Observable updateAssignment(@Path("message_id") int message_id, @Field("assigned") int user_id); 211 | 212 | @FormUrlEncoded 213 | @POST("/messages/details/{message_id}/annotations") 214 | Observable addAnnotation(@Path("message_id") int message_id, @Field("annotation_type") String annotation_type, @Field("description") String description); 215 | 216 | @FormUrlEncoded 217 | @POST("/messages/sms") 218 | Observable sendMessage(@Field("from") String from, @Field("to") String to, @Field("content") String content); 219 | 220 | @FormUrlEncoded 221 | @POST("/messages/sms/{message_id}") 222 | Observable sendReply(@Path("message_id") int message_id, @Field("from") String from, @Field("to") String to, @Field("content") String content); 223 | 224 | @FormUrlEncoded 225 | @POST("/messages/call") 226 | Observable sendCall(@Field("callerid") String callerid, @Field("from") String from, @Field("to") String to); 227 | 228 | @FormUrlEncoded 229 | @POST("/messages/call/{message_id}") 230 | Observable sendCallback(@Path("message_id") int message_id, @Field("callerid") String callerid, @Field("from") String from, @Field("to") String to); 231 | 232 | @GET("/numbers/outgoingcallerid") 233 | Observable> getOutgoingCallerIDs(); 234 | } 235 | 236 | public static Observable.Transformer defaultSchedulers() { 237 | return new Observable.Transformer() { 238 | @Override 239 | public Observable call(Observable observable) { 240 | return observable.subscribeOn(Schedulers.io()) 241 | .observeOn(AndroidSchedulers.mainThread()); 242 | } 243 | }; 244 | } 245 | 246 | } -------------------------------------------------------------------------------- /app/src/main/java/org/openvbx/SmsActivity.java: -------------------------------------------------------------------------------- 1 | package org.openvbx; 2 | 3 | import android.app.Activity; 4 | import android.os.Bundle; 5 | import android.support.v7.app.AppCompatActivity; 6 | import android.support.v7.widget.Toolbar; 7 | import android.view.MenuItem; 8 | import android.view.View; 9 | import android.widget.EditText; 10 | import android.widget.Spinner; 11 | 12 | import org.openvbx.adapters.CallerIDAdapter; 13 | import org.openvbx.models.MessageResult; 14 | 15 | import rx.functions.Action1; 16 | 17 | public class SmsActivity extends AppCompatActivity { 18 | 19 | private Activity mActivity; 20 | private Spinner spinner; 21 | private EditText toInput; 22 | private int message_id = -1; 23 | 24 | // TODO - switch send button to FAB 25 | @Override 26 | public void onCreate(Bundle savedInstanceState) { 27 | super.onCreate(savedInstanceState); 28 | 29 | mActivity = this; 30 | setContentView(R.layout.activity_sms); 31 | 32 | Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); 33 | setSupportActionBar(toolbar); 34 | getSupportActionBar().setDisplayHomeAsUpEnabled(true); 35 | 36 | spinner = (Spinner) findViewById(R.id.from); 37 | CallerIDAdapter adapter = new CallerIDAdapter(this, OpenVBX.getCallerIDs("sms")); 38 | spinner.setAdapter(adapter); 39 | 40 | toInput = (EditText) findViewById(R.id.to); 41 | 42 | Bundle extras = getIntent().getExtras(); 43 | if(extras != null) { 44 | spinner.setSelection(adapter.indexOf(extras.getString("from"))); 45 | toInput.setText(extras.getString("to")); 46 | message_id = extras.getInt("message_id", -1); 47 | } 48 | 49 | findViewById(R.id.send).setOnClickListener(new View.OnClickListener() { 50 | public void onClick(View view) { 51 | String from = spinner.getSelectedItem().toString(); 52 | String to = toInput.getText().toString().trim(); 53 | String content = ((EditText) findViewById(R.id.content)).getText().toString(); 54 | if (!from.isEmpty() && !to.isEmpty() && !content.isEmpty()) { 55 | (message_id == -1 ? OpenVBX.API.sendMessage(from, to, content) : OpenVBX.API.sendReply(message_id, from, to, content)) 56 | .compose(OpenVBX.defaultSchedulers()) 57 | .subscribe(new Action1() { 58 | @Override 59 | public void call(MessageResult result) { 60 | if (result.error) 61 | OpenVBX.error(mActivity, result.message); 62 | else { 63 | OpenVBX.toast(R.string.message_sent); 64 | finish(); 65 | } 66 | } 67 | }); 68 | } 69 | } 70 | }); 71 | } 72 | 73 | @Override 74 | public boolean onOptionsItemSelected(MenuItem item) { 75 | int id = item.getItemId(); 76 | switch(id) { 77 | case android.R.id.home: 78 | finish(); 79 | break; 80 | } 81 | return super.onOptionsItemSelected(item); 82 | } 83 | 84 | } -------------------------------------------------------------------------------- /app/src/main/java/org/openvbx/adapters/AnnotationAdapter.java: -------------------------------------------------------------------------------- 1 | package org.openvbx.adapters; 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.TextView; 9 | 10 | import org.openvbx.R; 11 | import org.openvbx.models.Annotation; 12 | 13 | import java.util.ArrayList; 14 | 15 | public class AnnotationAdapter extends ArrayAdapter { 16 | public ArrayList annotations; 17 | private int ViewResourceId; 18 | private LayoutInflater inflater; 19 | 20 | public AnnotationAdapter(Context context, int textViewResourceId, ArrayList annotations) { 21 | super(context, textViewResourceId, annotations); 22 | this.annotations = annotations; 23 | this.ViewResourceId = textViewResourceId; 24 | inflater = LayoutInflater.from(context); 25 | } 26 | 27 | @Override 28 | public View getView(int position, View convertView, ViewGroup parent) { 29 | View view = convertView; 30 | if (view == null) { 31 | view = inflater.inflate(ViewResourceId, null); 32 | } 33 | Annotation annotation = annotations.get(position); 34 | if (annotation != null) { 35 | TextView name = (TextView) view.findViewById(R.id.name); 36 | TextView description = (TextView) view.findViewById(R.id.description); 37 | name.setText(annotation.getName()); 38 | description.setText(annotation.description); 39 | } 40 | return view; 41 | } 42 | 43 | @Override 44 | public int getCount() { 45 | return annotations.size(); 46 | } 47 | 48 | public Annotation get(int position) { 49 | return annotations.get(position); 50 | } 51 | 52 | public void update(ArrayList annotations) { 53 | this.annotations = annotations; 54 | notifyDataSetChanged(); 55 | } 56 | } -------------------------------------------------------------------------------- /app/src/main/java/org/openvbx/adapters/CallerIDAdapter.java: -------------------------------------------------------------------------------- 1 | package org.openvbx.adapters; 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.BaseAdapter; 8 | import android.widget.TextView; 9 | 10 | import org.openvbx.models.CallerID; 11 | 12 | import java.util.ArrayList; 13 | 14 | public class CallerIDAdapter extends BaseAdapter { 15 | public ArrayList callerids; 16 | private LayoutInflater inflater; 17 | 18 | static class ViewHolder { 19 | TextView text; 20 | } 21 | 22 | public CallerIDAdapter(Context context, ArrayList callerids) { 23 | this.callerids = callerids; 24 | inflater = LayoutInflater.from(context); 25 | } 26 | 27 | public int getCount() { 28 | return callerids.size(); 29 | } 30 | 31 | public CallerID get(int position){ 32 | return callerids.get(position); 33 | } 34 | 35 | public void add(CallerID callerid){ 36 | callerids.add(callerid); 37 | } 38 | 39 | public String getItem(int position){ 40 | return get(position).name; 41 | } 42 | 43 | public long getItemId(int position) { 44 | return callerids.indexOf(get(position)); 45 | } 46 | 47 | public int indexOf(String number) { 48 | for(int i = 0; i < callerids.size(); i++) 49 | if(number.equals(callerids.get(i).phone_number)) 50 | return i; 51 | return -1; 52 | } 53 | 54 | public View getView(int position, View convertView, ViewGroup parent) { 55 | return createResource(position, convertView, parent, android.R.layout.simple_spinner_item); 56 | } 57 | 58 | public View getDropDownView(int position, View convertView, ViewGroup parent) { 59 | return createResource(position, convertView, parent, android.R.layout.simple_spinner_dropdown_item); 60 | } 61 | 62 | public View createResource(int position, View convertView, ViewGroup parent, int resource) { 63 | View v = convertView; 64 | ViewHolder vh; 65 | if (v == null) { 66 | v = inflater.inflate(resource, parent, false); 67 | vh = new ViewHolder(); 68 | vh.text = (TextView) v.findViewById(android.R.id.text1); 69 | v.setTag(vh); 70 | } 71 | else 72 | vh = (ViewHolder) v.getTag(); 73 | vh.text.setText(get(position).name); 74 | return v; 75 | } 76 | } -------------------------------------------------------------------------------- /app/src/main/java/org/openvbx/adapters/MessagesAdapter.java: -------------------------------------------------------------------------------- 1 | package org.openvbx.adapters; 2 | 3 | import android.content.Context; 4 | import android.graphics.Typeface; 5 | import android.view.LayoutInflater; 6 | import android.view.View; 7 | import android.view.ViewGroup; 8 | import android.widget.ArrayAdapter; 9 | import android.widget.ImageView; 10 | import android.widget.TextView; 11 | 12 | import org.openvbx.R; 13 | import org.openvbx.models.Message; 14 | 15 | import java.util.ArrayList; 16 | 17 | public class MessagesAdapter extends ArrayAdapter { 18 | public ArrayList messages; 19 | private int ViewResourceId; 20 | private LayoutInflater inflater; 21 | 22 | public MessagesAdapter(Context context, int textViewResourceId, ArrayList messages) { 23 | super(context, textViewResourceId, messages); 24 | this.messages = messages; 25 | this.ViewResourceId = textViewResourceId; 26 | inflater = LayoutInflater.from(context); 27 | } 28 | 29 | public int indexOf(int id) { 30 | for(int i = 0; i < messages.size(); i++) 31 | if(id == messages.get(i).id) 32 | return i; 33 | return -1; 34 | } 35 | 36 | @Override 37 | public View getView(int position, View convertView, ViewGroup parent) { 38 | View view = convertView; 39 | if (view == null) { 40 | view = inflater.inflate(ViewResourceId, null); 41 | } 42 | Message message = messages.get(position); 43 | if (message != null) { 44 | ImageView icon = (ImageView) view.findViewById(R.id.icon); 45 | TextView caller = (TextView) view.findViewById(R.id.caller); 46 | TextView folder = (TextView) view.findViewById(R.id.folder); 47 | TextView short_summary = (TextView) view.findViewById(R.id.short_summary); 48 | TextView received_time = (TextView) view.findViewById(R.id.received_time); 49 | icon.setImageResource("sms".equals(message.type) ? R.drawable.ic_message_white_48dp : R.drawable.ic_voicemail_white_48dp); 50 | caller.setText(message.caller); 51 | folder.setText(message.folder); 52 | if(message.folder.isEmpty()) 53 | folder.setVisibility(View.GONE); 54 | short_summary.setText(message.short_summary); 55 | if(message.short_summary.isEmpty()) 56 | short_summary.setVisibility(View.GONE); 57 | received_time.setText(message.receivedTime()); 58 | int typeface = message.unread ? Typeface.BOLD : Typeface.NORMAL; 59 | caller.setTypeface(null, typeface); 60 | folder.setTypeface(null, typeface); 61 | short_summary.setTypeface(null, typeface); 62 | received_time.setTypeface(null, typeface); 63 | } 64 | return view; 65 | } 66 | 67 | @Override 68 | public int getCount() { 69 | return messages.size(); 70 | } 71 | 72 | public Message get(int position) { 73 | return messages.get(position); 74 | } 75 | 76 | public void update(ArrayList messages) { 77 | this.messages = messages; 78 | notifyDataSetChanged(); 79 | } 80 | } -------------------------------------------------------------------------------- /app/src/main/java/org/openvbx/adapters/UserAdapter.java: -------------------------------------------------------------------------------- 1 | package org.openvbx.adapters; 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.BaseAdapter; 8 | import android.widget.TextView; 9 | 10 | import org.openvbx.models.User; 11 | 12 | import java.util.ArrayList; 13 | 14 | public class UserAdapter extends BaseAdapter { 15 | 16 | public ArrayList users; 17 | private LayoutInflater inflater; 18 | 19 | static class ViewHolder { 20 | TextView text; 21 | } 22 | 23 | public UserAdapter(Context context, ArrayList users) { 24 | this.users = users; 25 | inflater = LayoutInflater.from(context); 26 | } 27 | 28 | public int getCount() { 29 | return users.size(); 30 | } 31 | 32 | public User get(int position){ 33 | return users.get(position); 34 | } 35 | 36 | public void add(User user){ 37 | users.add(user); 38 | } 39 | 40 | public String getItem(int position){ 41 | return get(position).getName(); 42 | } 43 | 44 | public long getItemId(int position) { 45 | return users.indexOf(get(position)); 46 | } 47 | 48 | public int indexOf(int id) { 49 | for(int i = 0; i < users.size(); i++) 50 | if(id == users.get(i).id) 51 | return i; 52 | return -1; 53 | } 54 | 55 | public View getView(int position, View convertView, ViewGroup parent) { 56 | return createResource(position, convertView, parent, android.R.layout.simple_spinner_item); 57 | } 58 | 59 | public View getDropDownView(int position, View convertView, ViewGroup parent) { 60 | return createResource(position, convertView, parent, android.R.layout.simple_spinner_dropdown_item); 61 | } 62 | 63 | public View createResource(int position, View convertView, ViewGroup parent, int resource) { 64 | View v = convertView; 65 | ViewHolder vh; 66 | if (v == null) { 67 | v = inflater.inflate(resource, parent, false); 68 | vh = new ViewHolder(); 69 | vh.text = (TextView) v.findViewById(android.R.id.text1); 70 | v.setTag(vh); 71 | } 72 | else 73 | vh = (ViewHolder) v.getTag(); 74 | vh.text.setText(get(position).getName()); 75 | return v; 76 | } 77 | 78 | public void update(ArrayList users) { 79 | this.users = users; 80 | notifyDataSetChanged(); 81 | } 82 | } -------------------------------------------------------------------------------- /app/src/main/java/org/openvbx/models/Annotation.java: -------------------------------------------------------------------------------- 1 | package org.openvbx.models; 2 | 3 | import android.os.Parcel; 4 | import android.os.Parcelable; 5 | 6 | public class Annotation implements Parcelable { 7 | 8 | public int id; 9 | public String annotation_type = "noted"; 10 | public int message_id; 11 | public int user_id; 12 | public String description = ""; 13 | public String created; 14 | public int tenant_id; 15 | public String email; 16 | public String first_name = ""; 17 | public String last_name = ""; 18 | 19 | public String getName() { 20 | return first_name + " " + last_name; 21 | } 22 | 23 | protected Annotation(Parcel in) { 24 | id = in.readInt(); 25 | annotation_type = in.readString(); 26 | message_id = in.readInt(); 27 | user_id = in.readInt(); 28 | description = in.readString(); 29 | created = in.readString(); 30 | tenant_id = in.readInt(); 31 | email = in.readString(); 32 | first_name = in.readString(); 33 | last_name = in.readString(); 34 | } 35 | 36 | @Override 37 | public int describeContents() { 38 | return 0; 39 | } 40 | 41 | @Override 42 | public void writeToParcel(Parcel dest, int flags) { 43 | dest.writeInt(id); 44 | dest.writeString(annotation_type); 45 | dest.writeInt(message_id); 46 | dest.writeInt(user_id); 47 | dest.writeString(description); 48 | dest.writeString(created); 49 | dest.writeInt(tenant_id); 50 | dest.writeString(email); 51 | dest.writeString(first_name); 52 | dest.writeString(last_name); 53 | } 54 | 55 | @SuppressWarnings("unused") 56 | public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { 57 | @Override 58 | public Annotation createFromParcel(Parcel in) { 59 | return new Annotation(in); 60 | } 61 | 62 | @Override 63 | public Annotation[] newArray(int size) { 64 | return new Annotation[size]; 65 | } 66 | }; 67 | 68 | } -------------------------------------------------------------------------------- /app/src/main/java/org/openvbx/models/CallerID.java: -------------------------------------------------------------------------------- 1 | package org.openvbx.models; 2 | 3 | import android.os.Parcel; 4 | import android.os.Parcelable; 5 | 6 | import com.google.gson.annotations.SerializedName; 7 | 8 | public class CallerID implements Parcelable { 9 | 10 | public int flow_id; 11 | public String id; 12 | public String name; 13 | public String phone; 14 | public String phone_number; 15 | public String url; 16 | public String method; 17 | public String smsUrl; 18 | public String smsMethod; 19 | @SerializedName("capabilities") 20 | public Capabilities capabilities = new Capabilities(); 21 | public String voiceApplicationSid; 22 | public Boolean installed; 23 | 24 | public Boolean hasSms() { 25 | return capabilities != null && capabilities.sms; 26 | } 27 | 28 | protected CallerID(Parcel in) { 29 | flow_id = in.readInt(); 30 | id = in.readString(); 31 | name = in.readString(); 32 | phone = in.readString(); 33 | phone_number = in.readString(); 34 | url = in.readString(); 35 | method = in.readString(); 36 | smsUrl = in.readString(); 37 | smsMethod = in.readString(); 38 | capabilities = (Capabilities) in.readValue(Capabilities.class.getClassLoader()); 39 | voiceApplicationSid = in.readString(); 40 | byte installedVal = in.readByte(); 41 | installed = installedVal == 0x02 ? null : installedVal != 0x00; 42 | } 43 | 44 | @Override 45 | public int describeContents() { 46 | return 0; 47 | } 48 | 49 | @Override 50 | public void writeToParcel(Parcel dest, int flags) { 51 | dest.writeInt(flow_id); 52 | dest.writeString(id); 53 | dest.writeString(name); 54 | dest.writeString(phone); 55 | dest.writeString(phone_number); 56 | dest.writeString(url); 57 | dest.writeString(method); 58 | dest.writeString(smsUrl); 59 | dest.writeString(smsMethod); 60 | dest.writeValue(capabilities); 61 | dest.writeString(voiceApplicationSid); 62 | if (installed == null) { 63 | dest.writeByte((byte) (0x02)); 64 | } else { 65 | dest.writeByte((byte) (installed ? 0x01 : 0x00)); 66 | } 67 | } 68 | 69 | @SuppressWarnings("unused") 70 | public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { 71 | @Override 72 | public CallerID createFromParcel(Parcel in) { 73 | return new CallerID(in); 74 | } 75 | 76 | @Override 77 | public CallerID[] newArray(int size) { 78 | return new CallerID[size]; 79 | } 80 | }; 81 | 82 | public static class Capabilities implements Parcelable { 83 | 84 | public Boolean voice = false; 85 | public Boolean sms = false; 86 | public Boolean mms = false; 87 | 88 | public Capabilities() { 89 | } 90 | 91 | protected Capabilities(Parcel in) { 92 | byte voiceVal = in.readByte(); 93 | voice = voiceVal == 0x02 ? null : voiceVal != 0x00; 94 | byte smsVal = in.readByte(); 95 | sms = smsVal == 0x02 ? null : smsVal != 0x00; 96 | byte mmsVal = in.readByte(); 97 | mms = mmsVal == 0x02 ? null : mmsVal != 0x00; 98 | } 99 | 100 | @Override 101 | public int describeContents() { 102 | return 0; 103 | } 104 | 105 | @Override 106 | public void writeToParcel(Parcel dest, int flags) { 107 | if (voice == null) { 108 | dest.writeByte((byte) (0x02)); 109 | } else { 110 | dest.writeByte((byte) (voice ? 0x01 : 0x00)); 111 | } 112 | if (sms == null) { 113 | dest.writeByte((byte) (0x02)); 114 | } else { 115 | dest.writeByte((byte) (sms ? 0x01 : 0x00)); 116 | } 117 | if (mms == null) { 118 | dest.writeByte((byte) (0x02)); 119 | } else { 120 | dest.writeByte((byte) (mms ? 0x01 : 0x00)); 121 | } 122 | } 123 | 124 | @SuppressWarnings("unused") 125 | public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { 126 | @Override 127 | public Capabilities createFromParcel(Parcel in) { 128 | return new Capabilities(in); 129 | } 130 | 131 | @Override 132 | public Capabilities[] newArray(int size) { 133 | return new Capabilities[size]; 134 | } 135 | }; 136 | 137 | } 138 | } -------------------------------------------------------------------------------- /app/src/main/java/org/openvbx/models/ClientResult.java: -------------------------------------------------------------------------------- 1 | package org.openvbx.models; 2 | 3 | public class ClientResult { 4 | 5 | public Boolean error = false; 6 | public String message = null; 7 | public String version = null; 8 | public String theme = null; 9 | 10 | } 11 | -------------------------------------------------------------------------------- /app/src/main/java/org/openvbx/models/Folder.java: -------------------------------------------------------------------------------- 1 | package org.openvbx.models; 2 | 3 | import android.os.Parcel; 4 | import android.os.Parcelable; 5 | 6 | import com.google.gson.annotations.SerializedName; 7 | 8 | import java.util.ArrayList; 9 | 10 | public class Folder implements Parcelable { 11 | 12 | public int id = -1; 13 | public String name = null; 14 | public String type = null; 15 | public int archived = 0; 16 | @SerializedName("new") 17 | public int unread = 0; 18 | public int read = 0; 19 | public int total = 0; 20 | @SerializedName("messages") 21 | public Messages messages = new Messages(); 22 | 23 | public Folder() { 24 | } 25 | 26 | protected Folder(Parcel in) { 27 | id = in.readInt(); 28 | name = in.readString(); 29 | type = in.readString(); 30 | archived = in.readInt(); 31 | unread = in.readInt(); 32 | read = in.readInt(); 33 | total = in.readInt(); 34 | messages = (Messages) in.readValue(Messages.class.getClassLoader()); 35 | } 36 | 37 | @Override 38 | public int describeContents() { 39 | return 0; 40 | } 41 | 42 | @Override 43 | public void writeToParcel(Parcel dest, int flags) { 44 | dest.writeInt(id); 45 | dest.writeString(name); 46 | dest.writeString(type); 47 | dest.writeInt(archived); 48 | dest.writeInt(unread); 49 | dest.writeInt(read); 50 | dest.writeInt(total); 51 | dest.writeValue(messages); 52 | } 53 | 54 | @SuppressWarnings("unused") 55 | public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { 56 | @Override 57 | public Folder createFromParcel(Parcel in) { 58 | return new Folder(in); 59 | } 60 | 61 | @Override 62 | public Folder[] newArray(int size) { 63 | return new Folder[size]; 64 | } 65 | }; 66 | 67 | public static class Messages implements Parcelable { 68 | 69 | @SerializedName("items") 70 | public ArrayList items = new ArrayList<>(); 71 | public int total = 0; 72 | public Boolean offset = false; 73 | public int max = 20; 74 | 75 | public Messages() { 76 | } 77 | 78 | protected Messages(Parcel in) { 79 | if (in.readByte() == 0x01) { 80 | items = new ArrayList<>(); 81 | in.readList(items, Message.class.getClassLoader()); 82 | } else { 83 | items = null; 84 | } 85 | total = in.readInt(); 86 | byte offsetVal = in.readByte(); 87 | offset = offsetVal == 0x02 ? null : offsetVal != 0x00; 88 | max = in.readInt(); 89 | } 90 | 91 | @Override 92 | public int describeContents() { 93 | return 0; 94 | } 95 | 96 | @Override 97 | public void writeToParcel(Parcel dest, int flags) { 98 | if (items == null) { 99 | dest.writeByte((byte) (0x00)); 100 | } else { 101 | dest.writeByte((byte) (0x01)); 102 | dest.writeList(items); 103 | } 104 | dest.writeInt(total); 105 | if (offset == null) { 106 | dest.writeByte((byte) (0x02)); 107 | } else { 108 | dest.writeByte((byte) (offset ? 0x01 : 0x00)); 109 | } 110 | dest.writeInt(max); 111 | } 112 | 113 | @SuppressWarnings("unused") 114 | public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { 115 | @Override 116 | public Messages createFromParcel(Parcel in) { 117 | return new Messages(in); 118 | } 119 | 120 | @Override 121 | public Messages[] newArray(int size) { 122 | return new Messages[size]; 123 | } 124 | }; 125 | 126 | } 127 | 128 | } -------------------------------------------------------------------------------- /app/src/main/java/org/openvbx/models/Inbox.java: -------------------------------------------------------------------------------- 1 | package org.openvbx.models; 2 | 3 | import android.os.Parcel; 4 | import android.os.Parcelable; 5 | 6 | import com.google.gson.annotations.SerializedName; 7 | 8 | import java.util.ArrayList; 9 | 10 | public class Inbox implements Parcelable { 11 | 12 | public int max = 20; 13 | public Boolean offset = false; 14 | public int total = 0; 15 | @SerializedName("folders") 16 | public ArrayList folders = new ArrayList<>(); 17 | 18 | protected Inbox(Parcel in) { 19 | max = in.readInt(); 20 | byte offsetVal = in.readByte(); 21 | offset = offsetVal == 0x02 ? null : offsetVal != 0x00; 22 | total = in.readInt(); 23 | if (in.readByte() == 0x01) { 24 | folders = new ArrayList<>(); 25 | in.readList(folders, Folder.class.getClassLoader()); 26 | } else { 27 | folders = null; 28 | } 29 | } 30 | 31 | @Override 32 | public int describeContents() { 33 | return 0; 34 | } 35 | 36 | @Override 37 | public void writeToParcel(Parcel dest, int flags) { 38 | dest.writeInt(max); 39 | if (offset == null) { 40 | dest.writeByte((byte) (0x02)); 41 | } else { 42 | dest.writeByte((byte) (offset ? 0x01 : 0x00)); 43 | } 44 | dest.writeInt(total); 45 | if (folders == null) { 46 | dest.writeByte((byte) (0x00)); 47 | } else { 48 | dest.writeByte((byte) (0x01)); 49 | dest.writeList(folders); 50 | } 51 | } 52 | 53 | @SuppressWarnings("unused") 54 | public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { 55 | @Override 56 | public Inbox createFromParcel(Parcel in) { 57 | return new Inbox(in); 58 | } 59 | 60 | @Override 61 | public Inbox[] newArray(int size) { 62 | return new Inbox[size]; 63 | } 64 | }; 65 | 66 | } -------------------------------------------------------------------------------- /app/src/main/java/org/openvbx/models/Message.java: -------------------------------------------------------------------------------- 1 | package org.openvbx.models; 2 | 3 | 4 | import android.os.Parcel; 5 | import android.os.Parcelable; 6 | 7 | import com.google.gson.annotations.SerializedName; 8 | 9 | import java.text.SimpleDateFormat; 10 | import java.util.ArrayList; 11 | import java.util.Locale; 12 | 13 | public class Message implements Parcelable { 14 | 15 | public int id; 16 | public Boolean selected_folder = false; 17 | public Boolean selected_folder_id = false; 18 | public String status = "read"; 19 | public String folder = ""; 20 | public int folder_id; 21 | public String summary = ""; 22 | public String short_summary = ""; 23 | public int assigned = -1; 24 | public String type = ""; 25 | public int assigned_user; 26 | public String ticket_status; 27 | public Boolean archived = false; 28 | public Boolean unread = false; 29 | public String recording_url; 30 | public String recording_length; 31 | public String received_time; 32 | public String last_updated; 33 | public String called; 34 | public String caller; 35 | public String original_called; 36 | public String original_caller; 37 | public String owner_type; 38 | public String message_type; 39 | @SerializedName("active_users") 40 | public ArrayList active_users = new ArrayList<>(); 41 | @SerializedName("annotations") 42 | public Annotations annotations = new Annotations(); 43 | 44 | public String receivedTime() { 45 | try { 46 | return new SimpleDateFormat("M/d/yy h:mm a", Locale.getDefault()).format(new SimpleDateFormat("yyyy-MMM-dd'T'HH:mm:ssZ", Locale.getDefault()).parse(received_time)); 47 | } catch (Exception e) { 48 | e.printStackTrace(); 49 | return null; 50 | } 51 | } 52 | 53 | public Boolean isVoice() { 54 | return type != null && type.equals("voice"); 55 | } 56 | 57 | public Boolean isSms() { 58 | return type != null && type.equals("sms"); 59 | } 60 | 61 | protected Message(Parcel in) { 62 | id = in.readInt(); 63 | byte selected_folderVal = in.readByte(); 64 | selected_folder = selected_folderVal == 0x02 ? null : selected_folderVal != 0x00; 65 | byte selected_folder_idVal = in.readByte(); 66 | selected_folder_id = selected_folder_idVal == 0x02 ? null : selected_folder_idVal != 0x00; 67 | status = in.readString(); 68 | folder = in.readString(); 69 | folder_id = in.readInt(); 70 | summary = in.readString(); 71 | short_summary = in.readString(); 72 | assigned = in.readInt(); 73 | type = in.readString(); 74 | assigned_user = in.readInt(); 75 | ticket_status = in.readString(); 76 | byte archivedVal = in.readByte(); 77 | archived = archivedVal == 0x02 ? null : archivedVal != 0x00; 78 | byte unreadVal = in.readByte(); 79 | unread = unreadVal == 0x02 ? null : unreadVal != 0x00; 80 | recording_url = in.readString(); 81 | recording_length = in.readString(); 82 | received_time = in.readString(); 83 | last_updated = in.readString(); 84 | called = in.readString(); 85 | caller = in.readString(); 86 | original_called = in.readString(); 87 | original_caller = in.readString(); 88 | owner_type = in.readString(); 89 | message_type = in.readString(); 90 | if (in.readByte() == 0x01) { 91 | active_users = new ArrayList<>(); 92 | in.readList(active_users, User.class.getClassLoader()); 93 | } else { 94 | active_users = null; 95 | } 96 | annotations = (Annotations) in.readValue(Annotations.class.getClassLoader()); 97 | } 98 | 99 | @Override 100 | public int describeContents() { 101 | return 0; 102 | } 103 | 104 | @Override 105 | public void writeToParcel(Parcel dest, int flags) { 106 | dest.writeInt(id); 107 | if (selected_folder == null) { 108 | dest.writeByte((byte) (0x02)); 109 | } else { 110 | dest.writeByte((byte) (selected_folder ? 0x01 : 0x00)); 111 | } 112 | if (selected_folder_id == null) { 113 | dest.writeByte((byte) (0x02)); 114 | } else { 115 | dest.writeByte((byte) (selected_folder_id ? 0x01 : 0x00)); 116 | } 117 | dest.writeString(status); 118 | dest.writeString(folder); 119 | dest.writeInt(folder_id); 120 | dest.writeString(summary); 121 | dest.writeString(short_summary); 122 | dest.writeInt(assigned); 123 | dest.writeString(type); 124 | dest.writeInt(assigned_user); 125 | dest.writeString(ticket_status); 126 | if (archived == null) { 127 | dest.writeByte((byte) (0x02)); 128 | } else { 129 | dest.writeByte((byte) (archived ? 0x01 : 0x00)); 130 | } 131 | if (unread == null) { 132 | dest.writeByte((byte) (0x02)); 133 | } else { 134 | dest.writeByte((byte) (unread ? 0x01 : 0x00)); 135 | } 136 | dest.writeString(recording_url); 137 | dest.writeString(recording_length); 138 | dest.writeString(received_time); 139 | dest.writeString(last_updated); 140 | dest.writeString(called); 141 | dest.writeString(caller); 142 | dest.writeString(original_called); 143 | dest.writeString(original_caller); 144 | dest.writeString(owner_type); 145 | dest.writeString(message_type); 146 | if (active_users == null) { 147 | dest.writeByte((byte) (0x00)); 148 | } else { 149 | dest.writeByte((byte) (0x01)); 150 | dest.writeList(active_users); 151 | } 152 | dest.writeValue(annotations); 153 | } 154 | 155 | @SuppressWarnings("unused") 156 | public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { 157 | @Override 158 | public Message createFromParcel(Parcel in) { 159 | return new Message(in); 160 | } 161 | 162 | @Override 163 | public Message[] newArray(int size) { 164 | return new Message[size]; 165 | } 166 | }; 167 | 168 | public static class Annotations implements Parcelable { 169 | 170 | @SerializedName("items") 171 | public ArrayList items = new ArrayList<>(); 172 | public int max = 0; 173 | public int total = 0; 174 | 175 | public Annotations() { 176 | } 177 | 178 | protected Annotations(Parcel in) { 179 | if (in.readByte() == 0x01) { 180 | items = new ArrayList<>(); 181 | in.readList(items, Annotation.class.getClassLoader()); 182 | } else { 183 | items = null; 184 | } 185 | max = in.readInt(); 186 | total = in.readInt(); 187 | } 188 | 189 | @Override 190 | public int describeContents() { 191 | return 0; 192 | } 193 | 194 | @Override 195 | public void writeToParcel(Parcel dest, int flags) { 196 | if (items == null) { 197 | dest.writeByte((byte) (0x00)); 198 | } else { 199 | dest.writeByte((byte) (0x01)); 200 | dest.writeList(items); 201 | } 202 | dest.writeInt(max); 203 | dest.writeInt(total); 204 | } 205 | 206 | @SuppressWarnings("unused") 207 | public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { 208 | @Override 209 | public Annotations createFromParcel(Parcel in) { 210 | return new Annotations(in); 211 | } 212 | 213 | @Override 214 | public Annotations[] newArray(int size) { 215 | return new Annotations[size]; 216 | } 217 | }; 218 | 219 | } 220 | 221 | } -------------------------------------------------------------------------------- /app/src/main/java/org/openvbx/models/MessageResult.java: -------------------------------------------------------------------------------- 1 | package org.openvbx.models; 2 | 3 | public class MessageResult { 4 | 5 | public Boolean error; 6 | public String message = ""; 7 | 8 | } -------------------------------------------------------------------------------- /app/src/main/java/org/openvbx/models/User.java: -------------------------------------------------------------------------------- 1 | package org.openvbx.models; 2 | 3 | import android.os.Parcel; 4 | import android.os.Parcelable; 5 | 6 | public class User implements Parcelable { 7 | 8 | public int id; 9 | public String first_name; 10 | public String last_name; 11 | public String email; 12 | 13 | public String getName() { 14 | return first_name + " " + last_name; 15 | } 16 | 17 | public User(int id, String first_name, String last_name) { 18 | this.id = id; 19 | this.first_name = first_name; 20 | this.last_name = last_name; 21 | } 22 | 23 | protected User(Parcel in) { 24 | id = in.readInt(); 25 | first_name = in.readString(); 26 | last_name = in.readString(); 27 | email = in.readString(); 28 | } 29 | 30 | @Override 31 | public int describeContents() { 32 | return 0; 33 | } 34 | 35 | @Override 36 | public void writeToParcel(Parcel dest, int flags) { 37 | dest.writeInt(id); 38 | dest.writeString(first_name); 39 | dest.writeString(last_name); 40 | dest.writeString(email); 41 | } 42 | 43 | @SuppressWarnings("unused") 44 | public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { 45 | @Override 46 | public User createFromParcel(Parcel in) { 47 | return new User(in); 48 | } 49 | 50 | @Override 51 | public User[] newArray(int size) { 52 | return new User[size]; 53 | } 54 | }; 55 | 56 | } -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_archive_white_36dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chadsmith/OpenVBX-Android/53aafb87aa3d404ecd98f37809562079aea8ddf0/app/src/main/res/drawable-hdpi/ic_archive_white_36dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_call_white_48dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chadsmith/OpenVBX-Android/53aafb87aa3d404ecd98f37809562079aea8ddf0/app/src/main/res/drawable-hdpi/ic_call_white_48dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_folder_white_48dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chadsmith/OpenVBX-Android/53aafb87aa3d404ecd98f37809562079aea8ddf0/app/src/main/res/drawable-hdpi/ic_folder_white_48dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_inbox_white_48dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chadsmith/OpenVBX-Android/53aafb87aa3d404ecd98f37809562079aea8ddf0/app/src/main/res/drawable-hdpi/ic_inbox_white_48dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_message_white_48dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chadsmith/OpenVBX-Android/53aafb87aa3d404ecd98f37809562079aea8ddf0/app/src/main/res/drawable-hdpi/ic_message_white_48dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_pause_white_48dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chadsmith/OpenVBX-Android/53aafb87aa3d404ecd98f37809562079aea8ddf0/app/src/main/res/drawable-hdpi/ic_pause_white_48dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_play_arrow_white_48dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chadsmith/OpenVBX-Android/53aafb87aa3d404ecd98f37809562079aea8ddf0/app/src/main/res/drawable-hdpi/ic_play_arrow_white_48dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_reply_white_48dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chadsmith/OpenVBX-Android/53aafb87aa3d404ecd98f37809562079aea8ddf0/app/src/main/res/drawable-hdpi/ic_reply_white_48dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_speaker_notes_white_36dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chadsmith/OpenVBX-Android/53aafb87aa3d404ecd98f37809562079aea8ddf0/app/src/main/res/drawable-hdpi/ic_speaker_notes_white_36dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_textsms_white_36dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chadsmith/OpenVBX-Android/53aafb87aa3d404ecd98f37809562079aea8ddf0/app/src/main/res/drawable-hdpi/ic_textsms_white_36dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_voicemail_white_48dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chadsmith/OpenVBX-Android/53aafb87aa3d404ecd98f37809562079aea8ddf0/app/src/main/res/drawable-hdpi/ic_voicemail_white_48dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/ic_archive_white_36dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chadsmith/OpenVBX-Android/53aafb87aa3d404ecd98f37809562079aea8ddf0/app/src/main/res/drawable-mdpi/ic_archive_white_36dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/ic_call_white_48dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chadsmith/OpenVBX-Android/53aafb87aa3d404ecd98f37809562079aea8ddf0/app/src/main/res/drawable-mdpi/ic_call_white_48dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/ic_folder_white_48dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chadsmith/OpenVBX-Android/53aafb87aa3d404ecd98f37809562079aea8ddf0/app/src/main/res/drawable-mdpi/ic_folder_white_48dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/ic_inbox_white_48dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chadsmith/OpenVBX-Android/53aafb87aa3d404ecd98f37809562079aea8ddf0/app/src/main/res/drawable-mdpi/ic_inbox_white_48dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/ic_message_white_48dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chadsmith/OpenVBX-Android/53aafb87aa3d404ecd98f37809562079aea8ddf0/app/src/main/res/drawable-mdpi/ic_message_white_48dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/ic_pause_white_48dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chadsmith/OpenVBX-Android/53aafb87aa3d404ecd98f37809562079aea8ddf0/app/src/main/res/drawable-mdpi/ic_pause_white_48dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/ic_play_arrow_white_48dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chadsmith/OpenVBX-Android/53aafb87aa3d404ecd98f37809562079aea8ddf0/app/src/main/res/drawable-mdpi/ic_play_arrow_white_48dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/ic_reply_white_48dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chadsmith/OpenVBX-Android/53aafb87aa3d404ecd98f37809562079aea8ddf0/app/src/main/res/drawable-mdpi/ic_reply_white_48dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/ic_speaker_notes_white_36dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chadsmith/OpenVBX-Android/53aafb87aa3d404ecd98f37809562079aea8ddf0/app/src/main/res/drawable-mdpi/ic_speaker_notes_white_36dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/ic_textsms_white_36dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chadsmith/OpenVBX-Android/53aafb87aa3d404ecd98f37809562079aea8ddf0/app/src/main/res/drawable-mdpi/ic_textsms_white_36dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/ic_voicemail_white_48dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chadsmith/OpenVBX-Android/53aafb87aa3d404ecd98f37809562079aea8ddf0/app/src/main/res/drawable-mdpi/ic_voicemail_white_48dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_archive_white_36dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chadsmith/OpenVBX-Android/53aafb87aa3d404ecd98f37809562079aea8ddf0/app/src/main/res/drawable-xhdpi/ic_archive_white_36dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_call_white_48dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chadsmith/OpenVBX-Android/53aafb87aa3d404ecd98f37809562079aea8ddf0/app/src/main/res/drawable-xhdpi/ic_call_white_48dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_folder_white_48dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chadsmith/OpenVBX-Android/53aafb87aa3d404ecd98f37809562079aea8ddf0/app/src/main/res/drawable-xhdpi/ic_folder_white_48dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_inbox_white_48dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chadsmith/OpenVBX-Android/53aafb87aa3d404ecd98f37809562079aea8ddf0/app/src/main/res/drawable-xhdpi/ic_inbox_white_48dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_message_white_48dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chadsmith/OpenVBX-Android/53aafb87aa3d404ecd98f37809562079aea8ddf0/app/src/main/res/drawable-xhdpi/ic_message_white_48dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_pause_white_48dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chadsmith/OpenVBX-Android/53aafb87aa3d404ecd98f37809562079aea8ddf0/app/src/main/res/drawable-xhdpi/ic_pause_white_48dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_play_arrow_white_48dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chadsmith/OpenVBX-Android/53aafb87aa3d404ecd98f37809562079aea8ddf0/app/src/main/res/drawable-xhdpi/ic_play_arrow_white_48dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_reply_white_48dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chadsmith/OpenVBX-Android/53aafb87aa3d404ecd98f37809562079aea8ddf0/app/src/main/res/drawable-xhdpi/ic_reply_white_48dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_speaker_notes_white_36dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chadsmith/OpenVBX-Android/53aafb87aa3d404ecd98f37809562079aea8ddf0/app/src/main/res/drawable-xhdpi/ic_speaker_notes_white_36dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_textsms_white_36dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chadsmith/OpenVBX-Android/53aafb87aa3d404ecd98f37809562079aea8ddf0/app/src/main/res/drawable-xhdpi/ic_textsms_white_36dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_voicemail_white_48dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chadsmith/OpenVBX-Android/53aafb87aa3d404ecd98f37809562079aea8ddf0/app/src/main/res/drawable-xhdpi/ic_voicemail_white_48dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_archive_white_36dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chadsmith/OpenVBX-Android/53aafb87aa3d404ecd98f37809562079aea8ddf0/app/src/main/res/drawable-xxhdpi/ic_archive_white_36dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_call_white_48dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chadsmith/OpenVBX-Android/53aafb87aa3d404ecd98f37809562079aea8ddf0/app/src/main/res/drawable-xxhdpi/ic_call_white_48dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_folder_white_48dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chadsmith/OpenVBX-Android/53aafb87aa3d404ecd98f37809562079aea8ddf0/app/src/main/res/drawable-xxhdpi/ic_folder_white_48dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_inbox_white_48dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chadsmith/OpenVBX-Android/53aafb87aa3d404ecd98f37809562079aea8ddf0/app/src/main/res/drawable-xxhdpi/ic_inbox_white_48dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_message_white_48dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chadsmith/OpenVBX-Android/53aafb87aa3d404ecd98f37809562079aea8ddf0/app/src/main/res/drawable-xxhdpi/ic_message_white_48dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_pause_white_48dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chadsmith/OpenVBX-Android/53aafb87aa3d404ecd98f37809562079aea8ddf0/app/src/main/res/drawable-xxhdpi/ic_pause_white_48dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_play_arrow_white_48dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chadsmith/OpenVBX-Android/53aafb87aa3d404ecd98f37809562079aea8ddf0/app/src/main/res/drawable-xxhdpi/ic_play_arrow_white_48dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_reply_white_48dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chadsmith/OpenVBX-Android/53aafb87aa3d404ecd98f37809562079aea8ddf0/app/src/main/res/drawable-xxhdpi/ic_reply_white_48dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_speaker_notes_white_36dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chadsmith/OpenVBX-Android/53aafb87aa3d404ecd98f37809562079aea8ddf0/app/src/main/res/drawable-xxhdpi/ic_speaker_notes_white_36dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_textsms_white_36dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chadsmith/OpenVBX-Android/53aafb87aa3d404ecd98f37809562079aea8ddf0/app/src/main/res/drawable-xxhdpi/ic_textsms_white_36dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_voicemail_white_48dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chadsmith/OpenVBX-Android/53aafb87aa3d404ecd98f37809562079aea8ddf0/app/src/main/res/drawable-xxhdpi/ic_voicemail_white_48dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/ic_archive_white_36dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chadsmith/OpenVBX-Android/53aafb87aa3d404ecd98f37809562079aea8ddf0/app/src/main/res/drawable-xxxhdpi/ic_archive_white_36dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/ic_call_white_48dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chadsmith/OpenVBX-Android/53aafb87aa3d404ecd98f37809562079aea8ddf0/app/src/main/res/drawable-xxxhdpi/ic_call_white_48dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/ic_folder_white_48dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chadsmith/OpenVBX-Android/53aafb87aa3d404ecd98f37809562079aea8ddf0/app/src/main/res/drawable-xxxhdpi/ic_folder_white_48dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/ic_inbox_white_48dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chadsmith/OpenVBX-Android/53aafb87aa3d404ecd98f37809562079aea8ddf0/app/src/main/res/drawable-xxxhdpi/ic_inbox_white_48dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/ic_message_white_48dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chadsmith/OpenVBX-Android/53aafb87aa3d404ecd98f37809562079aea8ddf0/app/src/main/res/drawable-xxxhdpi/ic_message_white_48dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/ic_pause_white_48dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chadsmith/OpenVBX-Android/53aafb87aa3d404ecd98f37809562079aea8ddf0/app/src/main/res/drawable-xxxhdpi/ic_pause_white_48dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/ic_play_arrow_white_48dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chadsmith/OpenVBX-Android/53aafb87aa3d404ecd98f37809562079aea8ddf0/app/src/main/res/drawable-xxxhdpi/ic_play_arrow_white_48dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/ic_reply_white_48dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chadsmith/OpenVBX-Android/53aafb87aa3d404ecd98f37809562079aea8ddf0/app/src/main/res/drawable-xxxhdpi/ic_reply_white_48dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/ic_speaker_notes_white_36dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chadsmith/OpenVBX-Android/53aafb87aa3d404ecd98f37809562079aea8ddf0/app/src/main/res/drawable-xxxhdpi/ic_speaker_notes_white_36dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/ic_textsms_white_36dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chadsmith/OpenVBX-Android/53aafb87aa3d404ecd98f37809562079aea8ddf0/app/src/main/res/drawable-xxxhdpi/ic_textsms_white_36dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/ic_voicemail_white_48dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chadsmith/OpenVBX-Android/53aafb87aa3d404ecd98f37809562079aea8ddf0/app/src/main/res/drawable-xxxhdpi/ic_voicemail_white_48dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/drawer_header.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chadsmith/OpenVBX-Android/53aafb87aa3d404ecd98f37809562079aea8ddf0/app/src/main/res/drawable/logo.png -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_annotation.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 11 | 16 | 23 |