github.com/olivere/camlistore@v0.0.0-20140121221811-1b7ac2da0199/clients/android/src/org/camlistore/CamliActivity.java (about) 1 /* 2 Copyright 2011 Google Inc. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package org.camlistore; 18 19 import android.app.Activity; 20 import android.app.AlertDialog; 21 import android.content.ComponentName; 22 import android.content.Context; 23 import android.content.Intent; 24 import android.content.ServiceConnection; 25 import android.content.SharedPreferences; 26 import android.os.Bundle; 27 import android.os.Handler; 28 import android.os.IBinder; 29 import android.os.Looper; 30 import android.os.MessageQueue; 31 import android.os.RemoteException; 32 import android.util.Log; 33 import android.view.Menu; 34 import android.view.MenuItem; 35 import android.view.View; 36 import android.view.View.OnClickListener; 37 import android.widget.Button; 38 import android.widget.ProgressBar; 39 import android.widget.TextView; 40 import android.widget.Toast; 41 42 public class CamliActivity extends Activity { 43 private static final String TAG = "CamliActivity"; 44 private static final int MENU_SETTINGS = 1; 45 private static final int MENU_STOP = 2; 46 private static final int MENU_STOP_DIE = 3; 47 private static final int MENU_UPLOAD_ALL = 4; 48 private static final int MENU_VERSION = 5; 49 50 private IUploadService mServiceStub = null; 51 private IStatusCallback mCallback = null; 52 53 // Status text update state, since it updates too quickly to do it the naive way. 54 private long mLastStatusUpdate = 0; // time in millis we lasted updated the screen 55 private String mStatusTextCurrent = null; // what the screen says 56 private String mStatusTextWant = null; // what the service wants it to say 57 58 private final Handler mHandler = new Handler(); 59 60 private final MessageQueue.IdleHandler mIdleHandler = new MessageQueue.IdleHandler() { 61 @Override 62 public boolean queueIdle() { 63 if (mStatusTextCurrent != mStatusTextWant) { 64 TextView textStats = (TextView) findViewById(R.id.textStats); 65 mLastStatusUpdate = System.currentTimeMillis(); 66 mStatusTextCurrent = mStatusTextWant; 67 textStats.setText(mStatusTextWant); 68 } 69 return true; 70 } 71 }; 72 73 private final ServiceConnection mServiceConnection = new ServiceConnection() { 74 75 @Override 76 public void onServiceConnected(ComponentName name, IBinder service) { 77 mServiceStub = IUploadService.Stub.asInterface(service); 78 Log.d(TAG, "Service connected, registering callback " + mCallback); 79 80 try { 81 mServiceStub.registerCallback(mCallback); 82 } catch (RemoteException e) { 83 e.printStackTrace(); 84 } 85 } 86 87 @Override 88 public void onServiceDisconnected(ComponentName name) { 89 Log.d(TAG, "Service disconnected"); 90 mServiceStub = null; 91 }; 92 }; 93 94 @Override 95 public void onCreate(Bundle savedInstanceState) { 96 super.onCreate(savedInstanceState); 97 setContentView(R.layout.main); 98 99 Looper.myQueue().addIdleHandler(mIdleHandler); 100 final Button buttonToggle = (Button) findViewById(R.id.buttonToggle); 101 102 final TextView textStatus = (TextView) findViewById(R.id.textStatus); 103 final TextView textStats = (TextView) findViewById(R.id.textStats); 104 final TextView textBlobsRemain = (TextView) findViewById(R.id.textBlobsRemain); 105 final TextView textUploadStatus = (TextView) findViewById(R.id.textUploadStatus); 106 final ProgressBar progressBytes = (ProgressBar) findViewById(R.id.progressByteStatus); 107 final ProgressBar progressFile = (ProgressBar) findViewById(R.id.progressFileStatus); 108 109 buttonToggle.setOnClickListener(new OnClickListener() { 110 @Override 111 public void onClick(View btn) { 112 Log.d(TAG, "button click! text=" + buttonToggle.getText()); 113 if (getString(R.string.pause).equals(buttonToggle.getText())) { 114 try { 115 Log.d(TAG, "Pausing.."); 116 mServiceStub.pause(); 117 } catch (RemoteException e) { 118 } 119 } else if (getString(R.string.resume).equals(buttonToggle.getText())) { 120 try { 121 Log.d(TAG, "Resuming.."); 122 mServiceStub.resume(); 123 } catch (RemoteException e) { 124 } 125 } 126 } 127 }); 128 129 mCallback = new IStatusCallback.Stub() { 130 private volatile int mLastBlobsUploadRemain = 0; 131 private volatile int mLastBlobsDigestRemain = 0; 132 133 @Override 134 public void logToClient(String stuff) throws RemoteException { 135 // TODO Auto-generated method stub 136 } 137 138 @Override 139 public void setUploading(final boolean uploading) throws RemoteException { 140 mHandler.post(new Runnable() { 141 @Override 142 public void run() { 143 if (uploading) { 144 buttonToggle.setText(R.string.pause); 145 textStatus.setText(R.string.uploading); 146 } else if (mLastBlobsDigestRemain > 0) { 147 buttonToggle.setText(R.string.pause); 148 textStatus.setText(R.string.digesting); 149 } else { 150 buttonToggle.setText(R.string.resume); 151 int stepsRemain = mLastBlobsUploadRemain + mLastBlobsDigestRemain; 152 textStatus.setText(stepsRemain > 0 ? "Paused." : "Idle."); 153 } 154 } 155 }); 156 } 157 158 @Override 159 public void setFileStatus(final int done, final int inFlight, final int total) throws RemoteException { 160 mHandler.post(new Runnable() { 161 @Override 162 public void run() { 163 boolean finished = (done == total && mLastBlobsDigestRemain == 0); 164 buttonToggle.setEnabled(!finished); 165 progressFile.setMax(total); 166 progressFile.setProgress(done); 167 progressFile.setSecondaryProgress(done + inFlight); 168 if (finished) { 169 buttonToggle.setText(getString(R.string.pause_resume)); 170 } 171 172 StringBuilder sb = new StringBuilder(40); 173 sb.append("Files to upload: ").append(total - done); 174 textBlobsRemain.setText(sb.toString()); 175 } 176 }); 177 } 178 179 @Override 180 public void setByteStatus(final long done, final int inFlight, final long total) throws RemoteException { 181 mHandler.post(new Runnable() { 182 @Override 183 public void run() { 184 // setMax takes an (signed) int, but 2GB is a totally 185 // reasonable upload size, so use units of 1KB instead. 186 progressBytes.setMax((int) (total / 1024L)); 187 progressBytes.setProgress((int) (done / 1024L)); 188 progressBytes.setSecondaryProgress(progressBytes.getProgress() + inFlight / 1024); 189 } 190 }); 191 } 192 193 @Override 194 public void setUploadStatusText(final String text) throws RemoteException { 195 mHandler.post(new Runnable() { 196 @Override 197 public void run() { 198 textUploadStatus.setText(text); 199 } 200 }); 201 } 202 203 @Override 204 public void setUploadStatsText(final String text) throws RemoteException { 205 // We were getting these status updates so quickly that the calls to TextView.setText 206 // were consuming all CPU on the main thread and it was stalling the main thread 207 // for seconds. Ridiculous. So instead, only update this every 5 milliseconds, 208 // otherwise wait for the looper to be idle to update it. 209 mHandler.post(new Runnable() { 210 @Override 211 public void run() { 212 mStatusTextWant = text; 213 long now = System.currentTimeMillis(); 214 if (mLastStatusUpdate < now - 5) { 215 mStatusTextCurrent = mStatusTextWant; 216 textStats.setText(mStatusTextWant); 217 mLastStatusUpdate = System.currentTimeMillis(); 218 } 219 } 220 }); 221 } 222 }; 223 224 } 225 226 @Override 227 protected void onActivityResult(int requestCode, int resultCode, Intent data) { 228 super.onActivityResult(requestCode, resultCode, data); 229 230 // TODO: picking files/photos to upload? 231 } 232 233 @Override 234 protected void onDestroy() { 235 // TODO Auto-generated method stub 236 super.onDestroy(); 237 } 238 239 @Override 240 public boolean onCreateOptionsMenu(Menu menu) { 241 super.onCreateOptionsMenu(menu); 242 243 MenuItem uploadAll = menu.add(Menu.NONE, MENU_UPLOAD_ALL, 0, R.string.upload_all); 244 uploadAll.setIcon(android.R.drawable.ic_menu_upload); 245 246 MenuItem stop = menu.add(Menu.NONE, MENU_STOP, 0, R.string.stop); 247 stop.setIcon(android.R.drawable.ic_menu_close_clear_cancel); 248 249 MenuItem stopDie = menu.add(Menu.NONE, MENU_STOP_DIE, 0, R.string.stop_die); 250 stopDie.setIcon(android.R.drawable.ic_menu_close_clear_cancel); 251 252 MenuItem settings = menu.add(Menu.NONE, MENU_SETTINGS, 0, R.string.settings); 253 settings.setIcon(android.R.drawable.ic_menu_preferences); 254 255 menu.add(Menu.NONE, MENU_VERSION, 0, R.string.version); 256 return true; 257 } 258 259 @Override 260 public boolean onOptionsItemSelected(MenuItem item) { 261 switch (item.getItemId()) { 262 case MENU_STOP: 263 try { 264 if (mServiceStub != null) { 265 mServiceStub.stopEverything(); 266 } 267 } catch (RemoteException e) { 268 // Ignore. 269 } 270 break; 271 case MENU_STOP_DIE: 272 System.exit(1); 273 case MENU_SETTINGS: 274 SettingsActivity.show(this); 275 break; 276 case MENU_VERSION: 277 Toast.makeText(this, "camput version: " + ((UploadApplication) getApplication()).getCamputVersion(), Toast.LENGTH_LONG).show(); 278 break; 279 case MENU_UPLOAD_ALL: 280 Intent uploadAll = new Intent(UploadService.INTENT_UPLOAD_ALL); 281 uploadAll.setClass(this, UploadService.class); 282 Log.d(TAG, "Starting upload all..."); 283 startService(uploadAll); 284 Log.d(TAG, "Back from upload all..."); 285 break; 286 } 287 return true; 288 } 289 290 @Override 291 protected void onPause() { 292 super.onPause(); 293 try { 294 if (mServiceStub != null) 295 mServiceStub.unregisterCallback(mCallback); 296 } catch (RemoteException e) { 297 // Ignore. 298 } 299 if (mServiceConnection != null) { 300 unbindService(mServiceConnection); 301 } 302 } 303 304 @Override 305 protected void onResume() { 306 super.onResume(); 307 308 SharedPreferences sp = getSharedPreferences(Preferences.NAME, 0); 309 try { 310 HostPort hp = new HostPort(sp.getString(Preferences.HOST, "")); 311 if (!hp.isValid()) { 312 // Crashes oddly in some Android Instrumentation thing if 313 // uncommented: 314 // SettingsActivity.show(this); 315 // return; 316 } 317 } catch (NumberFormatException enf) { 318 AlertDialog.Builder builder = new AlertDialog.Builder(this); 319 builder.setMessage("Server should be of form [https://]<host[:port]>") 320 .setTitle("Invalid Setting"); 321 AlertDialog alert = builder.create(); 322 alert.show(); 323 } 324 325 bindService(new Intent(this, UploadService.class), mServiceConnection, Context.BIND_AUTO_CREATE); 326 327 Intent intent = getIntent(); 328 String action = intent.getAction(); 329 Log.d(TAG, "onResume; action=" + action); 330 331 if (Intent.ACTION_SEND.equals(action) || Intent.ACTION_SEND_MULTIPLE.equals(action)) { 332 Intent serviceIntent = new Intent(intent); 333 serviceIntent.setClass(this, UploadService.class); 334 startService(serviceIntent); 335 setIntent(new Intent(this, CamliActivity.class)); 336 } else { 337 Log.d(TAG, "Normal CamliActivity viewing."); 338 } 339 } 340 }