├── .gitignore
├── .project
├── AndroidManifest.xml
├── README.md
├── build.xml
├── libs
├── android-websockets.jar
└── gson-2.2.3.jar
├── project.properties
├── res
├── drawable-hdpi
│ ├── ic_action_delete.png
│ ├── ic_action_new.png
│ └── ic_launcher.png
├── drawable-mdpi
│ └── ic_launcher.png
├── drawable-xhdpi
│ ├── ic_action_delete.png
│ ├── ic_action_new.png
│ └── ic_launcher.png
├── layout
│ ├── activity_main.xml
│ └── suggest_text.xml
└── values
│ └── strings.xml
└── src
└── net
└── schwiz
└── eecs780
├── NetworkReceiver.java
├── PullingActivity.java
├── PullingService.java
├── PushActivity.java
├── PushService.java
└── Response.java
/.gitignore:
--------------------------------------------------------------------------------
1 | bin
2 | gen
3 | local.properties
4 | assets
5 | .settings
6 | .classpath
7 |
--------------------------------------------------------------------------------
/.project:
--------------------------------------------------------------------------------
1 |
2 |
3 | EECS780
4 |
5 |
6 |
7 |
8 |
9 | com.android.ide.eclipse.adt.ResourceManagerBuilder
10 |
11 |
12 |
13 |
14 | com.android.ide.eclipse.adt.PreCompilerBuilder
15 |
16 |
17 |
18 |
19 | org.eclipse.jdt.core.javabuilder
20 |
21 |
22 |
23 |
24 | com.android.ide.eclipse.adt.ApkBuilder
25 |
26 |
27 |
28 |
29 |
30 | com.android.ide.eclipse.adt.AndroidNature
31 | org.eclipse.jdt.core.javanature
32 |
33 |
34 |
--------------------------------------------------------------------------------
/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
10 |
11 |
12 |
13 |
14 |
15 |
20 |
21 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Android Push Sync Example
2 | =========================
3 |
4 | This simple app uses websockets to sync with a webapp in real time. It runs in the background to deliver push notifications in real time with zero battery impact.
5 |
6 | ###Source for included jars
7 | Websocket Libary found [here](http://github.com/schwiz/android-websockets)
8 | GSON Library found [here](https://code.google.com/p/google-gson/)
9 |
10 |
11 | ###Source for companion server
12 | Server Project found [here](http://github.com/schwiz/websocket-server)
13 |
14 |
15 | Download and set up the companion server project. Replace the host strings in strings.xml with the info for your server.
16 |
17 |
18 | License
19 | =======
20 | Copyright 2013 Nathan Schwermann
21 |
22 | Licensed under the Apache License, Version 2.0 (the "License");
23 | you may not use this file except in compliance with the License.
24 | You may obtain a copy of the License at
25 |
26 | http://www.apache.org/licenses/LICENSE-2.0
27 |
28 | Unless required by applicable law or agreed to in writing, software
29 | distributed under the License is distributed on an "AS IS" BASIS,
30 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
31 | See the License for the specific language governing permissions and
32 | limitations under the License.
33 |
--------------------------------------------------------------------------------
/build.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
7 |
8 |
9 |
29 |
30 |
31 |
35 |
36 |
37 |
38 |
39 |
40 |
49 |
50 |
51 |
52 |
56 |
57 |
69 |
70 |
71 |
89 |
90 |
91 |
92 |
93 |
--------------------------------------------------------------------------------
/libs/android-websockets.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nschwermann/android-websocket-example/6929b7c01f5adfc4e75f0a5ec465c1c4d676b92e/libs/android-websockets.jar
--------------------------------------------------------------------------------
/libs/gson-2.2.3.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nschwermann/android-websocket-example/6929b7c01f5adfc4e75f0a5ec465c1c4d676b92e/libs/gson-2.2.3.jar
--------------------------------------------------------------------------------
/project.properties:
--------------------------------------------------------------------------------
1 | # This file is automatically generated by Android Tools.
2 | # Do not modify this file -- YOUR CHANGES WILL BE ERASED!
3 | #
4 | # This file must be checked in Version Control Systems.
5 | #
6 | # To customize properties used by the Ant build system edit
7 | # "ant.properties", and override values to adapt the script to your
8 | # project structure.
9 | #
10 | # To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home):
11 | #proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt
12 |
13 | # Project target.
14 | target=android-17
15 |
--------------------------------------------------------------------------------
/res/drawable-hdpi/ic_action_delete.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nschwermann/android-websocket-example/6929b7c01f5adfc4e75f0a5ec465c1c4d676b92e/res/drawable-hdpi/ic_action_delete.png
--------------------------------------------------------------------------------
/res/drawable-hdpi/ic_action_new.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nschwermann/android-websocket-example/6929b7c01f5adfc4e75f0a5ec465c1c4d676b92e/res/drawable-hdpi/ic_action_new.png
--------------------------------------------------------------------------------
/res/drawable-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nschwermann/android-websocket-example/6929b7c01f5adfc4e75f0a5ec465c1c4d676b92e/res/drawable-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/res/drawable-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nschwermann/android-websocket-example/6929b7c01f5adfc4e75f0a5ec465c1c4d676b92e/res/drawable-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/res/drawable-xhdpi/ic_action_delete.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nschwermann/android-websocket-example/6929b7c01f5adfc4e75f0a5ec465c1c4d676b92e/res/drawable-xhdpi/ic_action_delete.png
--------------------------------------------------------------------------------
/res/drawable-xhdpi/ic_action_new.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nschwermann/android-websocket-example/6929b7c01f5adfc4e75f0a5ec465c1c4d676b92e/res/drawable-xhdpi/ic_action_new.png
--------------------------------------------------------------------------------
/res/drawable-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nschwermann/android-websocket-example/6929b7c01f5adfc4e75f0a5ec465c1c4d676b92e/res/drawable-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
6 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/res/layout/suggest_text.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
--------------------------------------------------------------------------------
/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | EECS 780
5 | http://example.com
6 | ws://example.com:8888
7 |
8 |
--------------------------------------------------------------------------------
/src/net/schwiz/eecs780/NetworkReceiver.java:
--------------------------------------------------------------------------------
1 | package net.schwiz.eecs780;
2 |
3 | import android.app.AlarmManager;
4 | import android.app.PendingIntent;
5 | import android.content.BroadcastReceiver;
6 | import android.content.Context;
7 | import android.content.Intent;
8 | import android.net.ConnectivityManager;
9 | import android.net.NetworkInfo;
10 | import android.util.Log;
11 |
12 | public class NetworkReceiver extends BroadcastReceiver {
13 |
14 | @Override
15 | public void onReceive(Context context, Intent intent) {
16 | ConnectivityManager conn = (ConnectivityManager)context.getSystemService(Context.CONNECTIVITY_SERVICE);
17 | NetworkInfo networkInfo = conn.getActiveNetworkInfo();
18 |
19 | if (networkInfo != null && networkInfo.getDetailedState() == NetworkInfo.DetailedState.CONNECTED) {
20 | Log.i("EECS780", "connected");
21 | context.startService(PushService.startIntent(context.getApplicationContext()));
22 | }
23 | else if(networkInfo != null){
24 | NetworkInfo.DetailedState state = networkInfo.getDetailedState();
25 | Log.i("EECS780", state.name());
26 | }
27 | else {
28 | Log.i("EECS780", "lost connection");
29 | AlarmManager am = (AlarmManager)context.getSystemService(Context.ALARM_SERVICE);
30 | PendingIntent operation = PendingIntent.getService(context, 0, PushService.pingIntent(context), PendingIntent.FLAG_NO_CREATE);
31 | if(operation != null){
32 | am.cancel(operation);
33 | operation.cancel();
34 | }
35 | context.startService(PushService.closeIntent(context));
36 | }
37 | }
38 | }
--------------------------------------------------------------------------------
/src/net/schwiz/eecs780/PullingActivity.java:
--------------------------------------------------------------------------------
1 | package net.schwiz.eecs780;
2 |
3 | import android.os.Bundle;
4 | import android.os.SystemClock;
5 | import android.app.Activity;
6 | import android.app.AlarmManager;
7 | import android.app.PendingIntent;
8 | import android.app.PendingIntent.CanceledException;
9 | import android.content.Intent;
10 | import android.view.Menu;
11 | import android.view.View;
12 | import android.view.View.OnClickListener;
13 | import android.widget.Button;
14 |
15 | public class PullingActivity extends Activity {
16 |
17 | @Override
18 | protected void onCreate(Bundle savedInstanceState) {
19 | super.onCreate(savedInstanceState);
20 | setContentView(R.layout.activity_main);
21 | final Button pull = (Button)findViewById(R.id.pulling_button);
22 | if(PullingService.hasAlarm(this)){
23 | pull.setText("Stop Service");
24 | }
25 | else {
26 | pull.setText("Start Service");
27 | }
28 | pull.setOnClickListener(new OnClickListener() {
29 |
30 | @Override
31 | public void onClick(View v) {
32 | if(PullingService.hasAlarm(PullingActivity.this)){
33 | AlarmManager am = (AlarmManager)getSystemService(ALARM_SERVICE);
34 | PendingIntent pi = PullingService.createIntent(PullingActivity.this);
35 | am.cancel(pi);
36 | pi.cancel();
37 | pull.setText("Start Service");
38 | }
39 | else {
40 | Intent i = new Intent(PullingActivity.this, PullingService.class);
41 | startService(i);
42 | pull.setText("Stop Service");
43 | }
44 | }
45 | });
46 | }
47 |
48 | }
49 |
--------------------------------------------------------------------------------
/src/net/schwiz/eecs780/PullingService.java:
--------------------------------------------------------------------------------
1 | package net.schwiz.eecs780;
2 |
3 | import java.io.BufferedInputStream;
4 | import java.io.BufferedReader;
5 | import java.io.IOException;
6 | import java.io.InputStream;
7 | import java.io.InputStreamReader;
8 | import java.net.HttpURLConnection;
9 | import java.net.URL;
10 |
11 | import android.app.AlarmManager;
12 | import android.app.IntentService;
13 | import android.app.PendingIntent;
14 | import android.content.Context;
15 | import android.content.Intent;
16 | import android.os.PowerManager;
17 | import android.os.SystemClock;
18 | import android.os.PowerManager.WakeLock;
19 | import android.util.Log;
20 |
21 | public class PullingService extends IntentService {
22 |
23 | public static int PULL_TIME = 30*1000;
24 |
25 | public PullingService(){
26 | this("PullingService");
27 | }
28 |
29 | public PullingService(String name) {
30 | super(name);
31 | // TODO Auto-generated constructor stub
32 | }
33 |
34 | @Override
35 | protected void onHandleIntent(Intent intent) {
36 | PowerManager pm = (PowerManager)getSystemService(POWER_SERVICE);
37 | WakeLock wl = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "PullingService");
38 | wl.acquire();
39 | try {
40 | URL url = new URL(getString(R.string.api_host) + "/apis/list");
41 | Log.i("EECS780", "opening connection");
42 | HttpURLConnection connection = (HttpURLConnection) url.openConnection();
43 | BufferedInputStream is = new BufferedInputStream(connection.getInputStream());
44 | String ret = fromStream(is);
45 | Log.i("EECS780", ret);
46 | } catch (Exception e) {
47 | e.printStackTrace();
48 | } finally{
49 | wl.release();
50 | AlarmManager am = (AlarmManager) getSystemService(ALARM_SERVICE);
51 | am.cancel(createIntent(this));
52 | am.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, SystemClock.elapsedRealtime() + PULL_TIME, createIntent(this));
53 | }
54 | }
55 |
56 | public static PendingIntent createIntent(Context context){
57 | Intent i = new Intent(context, PullingService.class);
58 | PendingIntent pi = PendingIntent.getService(context, 0, i, PendingIntent.FLAG_UPDATE_CURRENT);
59 | return pi;
60 | }
61 |
62 | public static boolean hasAlarm(Context context){
63 | return PendingIntent.getService(context, 0, new Intent(context, PullingService.class), PendingIntent.FLAG_NO_CREATE) != null;
64 | }
65 |
66 | String fromStream(InputStream in) throws IOException{
67 | BufferedReader reader = new BufferedReader(new InputStreamReader(in));
68 | StringBuilder out = new StringBuilder();
69 | String line;
70 | while ((line = reader.readLine()) != null) {
71 | out.append(line);
72 | }
73 | return out.toString();
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/src/net/schwiz/eecs780/PushActivity.java:
--------------------------------------------------------------------------------
1 | package net.schwiz.eecs780;
2 |
3 | import java.lang.reflect.Type;
4 | import java.util.ArrayList;
5 | import java.util.HashSet;
6 | import java.util.Iterator;
7 |
8 | import com.codebutler.android_websockets.WebSocketClient.Listener;
9 | import com.google.gson.Gson;
10 | import com.google.gson.reflect.TypeToken;
11 |
12 | import android.app.Activity;
13 | import android.app.AlertDialog;
14 | import android.app.AlertDialog.Builder;
15 | import android.app.Dialog;
16 | import android.app.DialogFragment;
17 | import android.app.ListFragment;
18 | import android.content.ComponentName;
19 | import android.content.Context;
20 | import android.content.DialogInterface;
21 | import android.content.DialogInterface.OnClickListener;
22 | import android.content.ServiceConnection;
23 | import android.os.Bundle;
24 | import android.os.IBinder;
25 | import android.preference.PreferenceManager;
26 | import android.util.Log;
27 | import android.view.ActionMode;
28 | import android.view.Menu;
29 | import android.view.MenuInflater;
30 | import android.view.MenuItem;
31 | import android.view.View;
32 | import android.view.WindowManager;
33 | import android.widget.ArrayAdapter;
34 | import android.widget.AutoCompleteTextView;
35 | import android.widget.BaseAdapter;
36 | import android.widget.CheckedTextView;
37 | import android.widget.ListView;
38 | import android.widget.TextView;
39 | import android.widget.Toast;
40 |
41 | public class PushActivity extends Activity {
42 |
43 | private static final int MENU_ADD = Menu.FIRST + 1000;
44 | private static final int ACTION_DELETE = Menu.FIRST + 50;
45 |
46 | @Override
47 | protected void onCreate(Bundle savedInstanceState) {
48 | super.onCreate(savedInstanceState);
49 | if(savedInstanceState == null) {
50 | FragmentMyList list = new FragmentMyList();
51 | getFragmentManager().beginTransaction().add(android.R.id.content, list, FragmentMyList.class.getName()).commit();
52 | }
53 | }
54 |
55 | public static class FragmentMyList extends ListFragment implements PushService.PushListener{
56 |
57 | private int mSelection = -1;
58 | private ActionMode mActionMode;
59 | private ArrayList mSavedList;
60 |
61 | private PushService mService;
62 |
63 | private ServiceConnection mConnection = new ServiceConnection() {
64 |
65 | @Override
66 | public void onServiceDisconnected(ComponentName name) {
67 | mService = null;
68 | }
69 |
70 | @Override
71 | public void onServiceConnected(ComponentName name, IBinder service) {
72 | mService = ((PushService.Binder)service).getService();
73 | mService.onStartCommand(null, 0, 0);
74 | mSavedList = mService.getList();
75 | mService.setListener(FragmentMyList.this);
76 | ((ArrayAdapter)getListAdapter()).clear();
77 | ((ArrayAdapter)getListAdapter()).addAll(mSavedList);
78 | ((ArrayAdapter)getListAdapter()).notifyDataSetInvalidated();
79 | }
80 | };
81 |
82 | public FragmentMyList(){
83 | }
84 |
85 | @Override
86 | public void onCreate(Bundle savedInstanceState) {
87 | super.onCreate(savedInstanceState);
88 | setHasOptionsMenu(true);
89 | }
90 |
91 | @Override
92 | public void onPause() {
93 | super.onPause();
94 | if(mService != null) mService.setListener(null);
95 | }
96 |
97 | @Override
98 | public void onResume() {
99 | super.onResume();
100 | if(mService != null) {
101 | mSavedList = mService.getList();
102 | mService.setListener(this);
103 | ((ArrayAdapter)getListAdapter()).clear();
104 | ((ArrayAdapter)getListAdapter()).addAll(mSavedList);
105 | ((ArrayAdapter)getListAdapter()).notifyDataSetInvalidated();
106 | }
107 | }
108 |
109 | @Override
110 | public void onListItemClick(ListView l, View v, int position, long id) {
111 | if(mSelection == position) return;
112 | if(mActionMode != null){
113 | mActionMode.finish();
114 | mActionMode = null;
115 | }
116 | mSelection = position;
117 | ((CheckedTextView)v).setChecked(true);
118 | getActivity().startActionMode(new ActionModeString());
119 | }
120 |
121 | @Override
122 | public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
123 | super.onCreateOptionsMenu(menu, inflater);
124 | menu.add(3, MENU_ADD, 0, "New Item").setIcon(R.drawable.ic_action_new).setShowAsActionFlags(MenuItem.SHOW_AS_ACTION_IF_ROOM);
125 | }
126 |
127 | @Override
128 | public boolean onOptionsItemSelected(MenuItem item) {
129 | if(MENU_ADD == item.getItemId()){
130 | DialogAddItem dialog = new DialogAddItem();
131 | dialog.show(getFragmentManager(), DialogAddItem.class.getName());
132 | return true;
133 | }
134 | return super.onOptionsItemSelected(item);
135 | }
136 |
137 | @Override
138 | public void onActivityCreated(Bundle savedInstanceState) {
139 | super.onActivityCreated(savedInstanceState);
140 | getActivity().bindService(PushService.startIntent(getActivity().getApplicationContext()), mConnection, Context.BIND_IMPORTANT);
141 | if(mSavedList == null)mSavedList = new ArrayList();
142 | setListAdapter(new ArrayAdapter(getActivity(), android.R.layout.simple_list_item_checked));
143 | ((ArrayAdapter)getListAdapter()).addAll(mSavedList);
144 | ((ArrayAdapter)getListAdapter()).notifyDataSetInvalidated();
145 | }
146 |
147 | @Override
148 | public void onAttach(Activity activity) {
149 | super.onAttach(activity);
150 | activity.getApplicationContext().startService(PushService.startIntent(activity.getApplicationContext()));
151 | }
152 |
153 | @Override
154 | public void onDetach() {
155 | super.onDetach();
156 | if(mService != null) mService.setListener(null);
157 | getActivity().unbindService(mConnection);
158 | }
159 |
160 | @Override
161 | public void newResponse(Response response) {
162 | if("add".equals(response.getAction())){
163 | ((ArrayAdapter)getListAdapter()).add(response.getName());
164 | ((ArrayAdapter)getListAdapter()).notifyDataSetChanged();
165 | }
166 | else if("delete".equals(response.getAction())){
167 | ((ArrayAdapter)getListAdapter()).remove(response.getName());
168 | ((ArrayAdapter)getListAdapter()).notifyDataSetChanged();
169 | }
170 | else if("connect".equals(response.getAction())){
171 | ((ArrayAdapter)getListAdapter()).clear();
172 | ((ArrayAdapter)getListAdapter()).addAll(response.getList());
173 | ((ArrayAdapter)getListAdapter()).notifyDataSetInvalidated();
174 | }
175 | }
176 |
177 | private final class ActionModeString implements ActionMode.Callback{
178 |
179 | @Override
180 | public boolean onCreateActionMode(ActionMode mode, Menu menu) {
181 | mActionMode = mode;
182 | setMenu(mode, menu);
183 | return true;
184 | }
185 |
186 | @Override
187 | public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
188 | return false;
189 | }
190 |
191 | @Override
192 | public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
193 | if(item.getItemId() == ACTION_DELETE){
194 | if(mService != null && mService.isConnected()){
195 | String string = ((ArrayAdapter)getListAdapter()).getItem(mSelection);
196 | mService.removeItem(string);
197 | ((ArrayAdapter)getListAdapter()).remove(string);
198 | ((ArrayAdapter) getListAdapter()).notifyDataSetChanged();
199 | mode.finish();
200 | }
201 | else {
202 | Toast.makeText(getActivity(), "Not connected to push service please try again", Toast.LENGTH_SHORT);
203 | }
204 | }
205 | return true;
206 | }
207 |
208 | @Override
209 | public void onDestroyActionMode(ActionMode mode) {
210 | if(getListView().getChildAt(mSelection) != null){
211 | ((CheckedTextView)getListView().getChildAt(mSelection)).setChecked(false);
212 | }
213 | mSelection = -1;
214 | }
215 |
216 | private void setMenu(ActionMode mode, Menu menu){
217 | menu.add(0, ACTION_DELETE, 2, "Delete").setIcon(R.drawable.ic_action_delete).setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
218 | }
219 |
220 | }
221 |
222 | public static class DialogAddItem extends DialogFragment{
223 |
224 | FragmentMyList mParent;
225 | HashSet mSuggestions;
226 |
227 | public DialogAddItem() {
228 | }
229 |
230 | @Override
231 | public void onCreate(Bundle savedInstanceState) {
232 | super.onCreate(savedInstanceState);
233 | final String json = PreferenceManager.getDefaultSharedPreferences(getActivity()).getString("gsuggestions", null);
234 | if(json != null){
235 | final Gson g = new Gson();
236 | final Type type = new TypeToken>(){}.getType();
237 | mSuggestions = g.fromJson(json, type);
238 | }
239 | if(mSuggestions == null){
240 | mSuggestions = new HashSet();
241 | }
242 | }
243 |
244 | @Override
245 | public void onActivityCreated(Bundle arg0) {
246 | super.onActivityCreated(arg0);
247 | mParent = (FragmentMyList) getActivity().getFragmentManager().findFragmentByTag(FragmentMyList.class.getName());
248 | }
249 |
250 | @Override
251 | public void onSaveInstanceState(Bundle arg0) {
252 | super.onSaveInstanceState(arg0);
253 | arg0.putString("text", ((AutoCompleteTextView)getDialog().findViewById(R.id.suggestion_text)).getText().toString());
254 | }
255 |
256 | @Override
257 | public Dialog onCreateDialog(Bundle savedInstanceState) {
258 | final AutoCompleteTextView text = (AutoCompleteTextView) View.inflate(getActivity(), R.layout.suggest_text, null);
259 | final ArrayAdapter adapter = new ArrayAdapter(getActivity(), android.R.layout.simple_dropdown_item_1line);
260 | Iterator iterator = mSuggestions.iterator();
261 | while(iterator.hasNext()){
262 | adapter.add(iterator.next());
263 | }
264 | text.setAdapter(adapter);
265 | final AlertDialog.Builder builder = new Builder(getActivity());
266 | builder.setTitle("Add Item");
267 | builder.setView(text);
268 | builder.setPositiveButton("OK", new OnClickListener() {
269 |
270 | @Override
271 | public void onClick(DialogInterface dialog, int which) {
272 | if(text.getText().length() > 0){
273 | mSuggestions.add(text.getText().toString());
274 | final String item = text.getText().toString();
275 | if(mParent.mService != null && mParent.mService.isConnected()){
276 | mParent.mService.addItem(item);
277 | ((ArrayAdapter)mParent.getListAdapter()).add(item);
278 | ((ArrayAdapter)mParent.getListAdapter()).notifyDataSetChanged();
279 | dialog.dismiss();
280 | }
281 | else {
282 | Toast.makeText(getActivity(), "Not connected to push service please try again", Toast.LENGTH_SHORT);
283 | }
284 | }
285 | }
286 | });
287 | builder.setNegativeButton("Cancel", new OnClickListener() {
288 |
289 | @Override
290 | public void onClick(DialogInterface dialog, int which) {
291 | dialog.dismiss();
292 | }
293 | });
294 | text.requestFocus();
295 | AlertDialog dialog = builder.create();
296 | dialog.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE);
297 | return dialog;
298 | }
299 |
300 | }
301 |
302 | }
303 | }
304 |
--------------------------------------------------------------------------------
/src/net/schwiz/eecs780/PushService.java:
--------------------------------------------------------------------------------
1 | package net.schwiz.eecs780;
2 |
3 | import java.net.URI;
4 | import java.util.ArrayList;
5 | import java.util.Arrays;
6 | import java.util.HashSet;
7 | import java.util.LinkedList;
8 |
9 | import com.codebutler.android_websockets.WebSocketClient;
10 | import com.codebutler.android_websockets.WebSocketClient.Listener;
11 |
12 | import android.app.AlarmManager;
13 | import android.app.Notification;
14 | import android.app.NotificationManager;
15 | import android.app.PendingIntent;
16 | import android.app.Service;
17 | import android.content.Context;
18 | import android.content.Intent;
19 | import android.media.RingtoneManager;
20 | import android.os.Handler;
21 | import android.os.IBinder;
22 | import android.os.PowerManager;
23 | import android.os.PowerManager.WakeLock;
24 | import android.util.Log;
25 |
26 | public class PushService extends Service implements Listener {
27 |
28 | public static final String ACTION_PING = "edu.ku.eecs780.ACTION_PING";
29 | public static final String ACTION_CONNECT = "edu.ku.eecs780.ACTION_CONNECT";
30 | public static final String ACTION_SHUT_DOWN = "edu.edu.eecs780.ACTION_SHUT_DOWN";
31 |
32 | private WebSocketClient mClient;
33 | private final IBinder mBinder = new Binder();
34 | private HashSet mList = new HashSet();
35 | private boolean mShutDown = false;
36 | private PushListener mListener;
37 | private Handler mHandler;
38 |
39 | public static Intent startIntent(Context context){
40 | Intent i = new Intent(context, PushService.class);
41 | i.setAction(ACTION_CONNECT);
42 | return i;
43 | }
44 |
45 | public static Intent pingIntent(Context context){
46 | Intent i = new Intent(context, PushService.class);
47 | i.setAction(ACTION_PING);
48 | return i;
49 | }
50 |
51 | public static Intent closeIntent(Context context){
52 | Intent i = new Intent(context, PushService.class);
53 | i.setAction(ACTION_SHUT_DOWN);
54 | return i;
55 | }
56 |
57 | @Override
58 | public IBinder onBind(Intent intent) {
59 | return mBinder;
60 | }
61 |
62 | @Override
63 | public void onCreate() {
64 | super.onCreate();
65 | mHandler = new Handler();
66 | Log.d("EECS780", "Creating Service " + this.toString());
67 | }
68 |
69 | @Override
70 | public void onDestroy() {
71 | super.onDestroy();
72 | Log.d("EECS780", "Destroying Service " + this.toString());
73 | if(mClient != null && mClient.isConnected()) mClient.disconnect();
74 | }
75 |
76 | @Override
77 | public int onStartCommand(Intent intent, int flags, int startId) {
78 | WakeLock wakelock = ((PowerManager)getSystemService(POWER_SERVICE)).newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "EECS780 Service");
79 | wakelock.acquire();
80 | Log.i("EECS780", "PushService start command");
81 | if(intent != null) Log.i("EECS780", intent.toUri(0));
82 | mShutDown = false;
83 | if(mClient == null) {
84 | WakeLock clientlock = ((PowerManager)getSystemService(POWER_SERVICE)).newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "EECS780");
85 | mClient = new WebSocketClient(URI.create(getString(R.string.socket_host) + "/list/socket"), this, null, clientlock);
86 | }
87 |
88 | if(!mClient.isConnected()) mClient.connect();
89 |
90 | if(intent != null) {
91 | if(ACTION_PING.equals(intent.getAction())){
92 | if(mClient.isConnected()) mClient.send("{\"action\":\"ping\"}");
93 | }
94 | else if(ACTION_SHUT_DOWN.equals(intent.getAction())){
95 | mShutDown = true;
96 | if(mClient.isConnected()) mClient.disconnect();
97 | }
98 | }
99 |
100 | if(intent == null || !intent.getAction().equals(ACTION_SHUT_DOWN)){
101 | AlarmManager am = (AlarmManager)getSystemService(Context.ALARM_SERVICE);
102 | PendingIntent operation = PendingIntent.getService(this, 0, PushService.pingIntent(this), PendingIntent.FLAG_NO_CREATE);
103 | if(operation == null){
104 | operation = PendingIntent.getService(this, 0, PushService.pingIntent(this), PendingIntent.FLAG_UPDATE_CURRENT);
105 | am.setInexactRepeating(AlarmManager.RTC_WAKEUP, System.currentTimeMillis(), AlarmManager.INTERVAL_HALF_HOUR, operation);
106 | }
107 | }
108 |
109 | wakelock.release();
110 | return START_STICKY;
111 | }
112 |
113 | public class Binder extends android.os.Binder{
114 |
115 | PushService getService(){
116 | return PushService.this;
117 | }
118 | }
119 |
120 | public synchronized void setListener(PushListener listener){
121 | mListener = listener;
122 | }
123 |
124 | public synchronized void addItem(String item){
125 | if(mClient != null && mClient.isConnected()){
126 | Response res = new Response();
127 | res.setAction("add");
128 | res.setName(item);
129 | mClient.send(res.toString());
130 | mList.add(item);
131 | }
132 | }
133 |
134 | public synchronized void removeItem(String item){
135 | if(mClient != null && mClient.isConnected()){
136 | Response res = new Response();
137 | res.setAction("delete");
138 | res.setName(item);
139 | mClient.send(res.toString());
140 | mList.remove(item);
141 | }
142 | }
143 |
144 | public synchronized boolean isConnected() {
145 | return mClient != null && mClient.isConnected();
146 | }
147 |
148 | public synchronized ArrayList getList(){
149 | ArrayList ret = new ArrayList();
150 | ret.addAll(mList);
151 | return ret;
152 | }
153 |
154 | @Override
155 | public void onConnect() {
156 | Log.d("EECS780", "Connected to websocket");
157 | }
158 |
159 | @Override
160 | public synchronized void onDisconnect(int code, String reason) {
161 | Log.d("EECS780", String.format("Disconnected! Code: %d Reason: %s", code, reason));
162 | if(!mShutDown){
163 | startService(startIntent(this));
164 | }
165 | else{
166 | stopSelf();
167 | }
168 | }
169 |
170 | @Override
171 | public synchronized void onError(Exception arg0) {
172 | Log.e("EECS780", "PushService", arg0);
173 | startService(startIntent(this));
174 | }
175 |
176 | @Override
177 | public synchronized void onMessage(String arg0) {
178 | WakeLock wakelock = ((PowerManager)getSystemService(POWER_SERVICE)).newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "EECS780 Service");
179 | wakelock.acquire();
180 | Log.d("EECS780", arg0);
181 | final Response response = Response.deserializeList(arg0);
182 | if("connect".equals(response.getAction())){
183 | mList.addAll(response.getList());
184 | }
185 | else if("add".equals(response.getAction())){
186 | mList.add(response.getName());
187 | }
188 | else if("delete".equals(response.getAction())){
189 | mList.remove(response.getName());
190 | }
191 | else {
192 | wakelock.release();
193 | return;
194 | }
195 |
196 | mHandler.post(new Runnable() {
197 |
198 | @Override
199 | public void run() {
200 | if(mListener != null) mListener.newResponse(response);
201 | else sendNotification(response);
202 | }
203 | });
204 | wakelock.release();
205 | }
206 |
207 | private void sendNotification(Response response){
208 | Notification.Builder notBuilder = new Notification.Builder(this);
209 | StringBuilder stringBuilder = new StringBuilder();
210 | for(String s : mList) stringBuilder.append(s).append('\n');
211 | if(response.getAction().equals("connect")) {
212 | notBuilder.setContentTitle("Connected to list server");
213 | }
214 | else if(response.getAction().equals("add")) {
215 | notBuilder.setContentTitle("New item added");
216 | notBuilder.setContentText(response.getName());
217 | }
218 | else if(response.getAction().equals("delete")){
219 | notBuilder.setContentTitle("Item removed");
220 | notBuilder.setContentText(response.getName());
221 | }
222 | Intent intent = new Intent(this, PushActivity.class);
223 | intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK);
224 | PendingIntent pi = PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
225 | notBuilder.setContentIntent(pi);
226 | notBuilder.setSmallIcon(android.R.drawable.ic_notification_overlay);
227 | notBuilder.setSound(RingtoneManager.getActualDefaultRingtoneUri(this, RingtoneManager.TYPE_NOTIFICATION));
228 | notBuilder.setOnlyAlertOnce(false);
229 | notBuilder.setAutoCancel(true);
230 | Notification.BigTextStyle bst = new Notification.BigTextStyle(notBuilder);
231 | bst.bigText(stringBuilder.toString());
232 |
233 | NotificationManager nm = (NotificationManager)getSystemService(NOTIFICATION_SERVICE);
234 | nm.notify(0, bst.build());
235 | }
236 |
237 | @Override
238 | public synchronized void onMessage(byte[] arg0) {
239 | // TODO Auto-generated method stub
240 |
241 | }
242 |
243 | public interface PushListener{
244 | public void newResponse(Response response);
245 | }
246 | }
247 |
--------------------------------------------------------------------------------
/src/net/schwiz/eecs780/Response.java:
--------------------------------------------------------------------------------
1 | package net.schwiz.eecs780;
2 |
3 | import java.util.ArrayList;
4 |
5 | import com.google.gson.Gson;
6 |
7 | public class Response {
8 |
9 |
10 | private String action;
11 | private ArrayList list;
12 | private String name;
13 | private long last_update;
14 |
15 | public Response(){}
16 |
17 | public ArrayList getList() {
18 | return list;
19 | }
20 | public void setList(ArrayList list) {
21 | this.list = list;
22 | }
23 | public long getLast_update() {
24 | return last_update;
25 | }
26 | public void setLast_update(long last_update) {
27 | this.last_update = last_update;
28 | }
29 |
30 | public static Response deserializeList(String json){
31 | return new Gson().fromJson(json, Response.class);
32 | }
33 |
34 | public String getAction() {
35 | return action;
36 | }
37 |
38 | public void setAction(String action) {
39 | this.action = action;
40 | }
41 |
42 | public String getName() {
43 | return name;
44 | }
45 |
46 | public void setName(String name) {
47 | this.name = name;
48 | }
49 |
50 | @Override
51 | public String toString() {
52 | return new Gson().toJson(this);
53 | }
54 |
55 | }
56 |
--------------------------------------------------------------------------------