1 /******************************************************************************* 2 * This file is part of BOINC. 3 * http://boinc.berkeley.edu 4 * Copyright (C) 2012 University of California 5 * 6 * BOINC is free software; you can redistribute it and/or modify it 7 * under the terms of the GNU Lesser General Public License 8 * as published by the Free Software Foundation, 9 * either version 3 of the License, or (at your option) any later version. 10 * 11 * BOINC is distributed in the hope that it will be useful, 12 * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 14 * See the GNU Lesser General Public License for more details. 15 * 16 * You should have received a copy of the GNU Lesser General Public License 17 * along with BOINC. If not, see <http://www.gnu.org/licenses/>. 18 ******************************************************************************/ 19 package edu.berkeley.boinc; 20 21 import android.app.Dialog; 22 import android.app.Service; 23 import android.content.BroadcastReceiver; 24 import android.content.ComponentName; 25 import android.content.Context; 26 import android.content.Intent; 27 import android.content.IntentFilter; 28 import android.content.ServiceConnection; 29 import android.content.pm.PackageManager.NameNotFoundException; 30 import android.content.res.Configuration; 31 import android.net.Uri; 32 import android.os.AsyncTask; 33 import android.os.Bundle; 34 import android.os.IBinder; 35 import android.os.RemoteException; 36 import android.util.Log; 37 import android.view.KeyEvent; 38 import android.view.Menu; 39 import android.view.MenuInflater; 40 import android.view.MenuItem; 41 import android.view.View; 42 import android.view.View.OnClickListener; 43 import android.view.Window; 44 import android.widget.AdapterView; 45 import android.widget.Button; 46 import android.widget.ListView; 47 import android.widget.TextView; 48 import android.support.v4.app.ActionBarDrawerToggle; 49 import android.support.v4.app.Fragment; 50 import android.support.v4.app.FragmentTransaction; 51 import android.support.v4.widget.DrawerLayout; 52 import android.support.v7.app.ActionBarActivity; 53 import edu.berkeley.boinc.adapter.NavDrawerListAdapter; 54 import edu.berkeley.boinc.adapter.NavDrawerListAdapter.NavDrawerItem; 55 import edu.berkeley.boinc.attach.SelectionListActivity; 56 import edu.berkeley.boinc.client.ClientStatus; 57 import edu.berkeley.boinc.client.Monitor; 58 import edu.berkeley.boinc.client.IMonitor; 59 import edu.berkeley.boinc.rpc.Project; 60 import edu.berkeley.boinc.utils.BOINCDefs; 61 import edu.berkeley.boinc.utils.Logging; 62 import java.util.ArrayList; 63 64 public class BOINCActivity extends ActionBarActivity { 65 66 public static IMonitor monitor; 67 private Integer clientComputingStatus = -1; 68 private Integer numberProjectsInNavList = 0; 69 static Boolean mIsBound = false; 70 71 // app title (changes with nav bar selection) 72 private CharSequence mTitle; 73 // nav drawer title 74 private CharSequence mDrawerTitle; 75 76 private DrawerLayout mDrawerLayout; 77 private ListView mDrawerList; 78 private ActionBarDrawerToggle mDrawerToggle; 79 private NavDrawerListAdapter mDrawerListAdapter; 80 81 private ServiceConnection mConnection = new ServiceConnection() { 82 public void onServiceConnected(ComponentName className, IBinder service) { 83 // This is called when the connection with the service has been established, getService returns 84 // the Monitor object that is needed to call functions. 85 monitor = IMonitor.Stub.asInterface(service); 86 mIsBound = true; 87 determineStatus(); 88 } 89 90 public void onServiceDisconnected(ComponentName className) { 91 // This should not happen 92 monitor = null; 93 mIsBound = false; 94 95 Log.e(Logging.TAG, "BOINCActivity onServiceDisconnected"); 96 } 97 }; 98 99 private BroadcastReceiver mClientStatusChangeRec = new BroadcastReceiver() { 100 @Override 101 public void onReceive(Context context,Intent intent) { 102 if(Logging.VERBOSE) Log.d(Logging.TAG, "BOINCActivity ClientStatusChange - onReceive()"); 103 determineStatus(); 104 } 105 }; 106 private IntentFilter ifcsc = new IntentFilter("edu.berkeley.boinc.clientstatuschange"); 107 108 @Override onCreate(Bundle savedInstanceState)109 public void onCreate(Bundle savedInstanceState) { 110 if(Logging.DEBUG) Log.d(Logging.TAG, "BOINCActivity onCreate()"); 111 super.onCreate(savedInstanceState); 112 setContentView(R.layout.main); 113 114 // setup navigation bar 115 mTitle = mDrawerTitle = getTitle(); 116 mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout); 117 mDrawerList = (ListView) findViewById(R.id.list_slidermenu); 118 mDrawerList.setOnItemClickListener(new ListView.OnItemClickListener(){ 119 @Override 120 public void onItemClick(AdapterView<?> parent, View view, int position, long id) { 121 // display view for selected nav drawer item 122 dispatchNavBarOnClick(mDrawerListAdapter.getItem(position),false); 123 }}); 124 mDrawerListAdapter = new NavDrawerListAdapter(getApplicationContext()); 125 mDrawerList.setAdapter(mDrawerListAdapter); 126 // enabling action bar app icon and behaving it as toggle button 127 getSupportActionBar().setDisplayHomeAsUpEnabled(true); 128 getSupportActionBar().setHomeButtonEnabled(true); 129 130 mDrawerToggle = new ActionBarDrawerToggle(this, mDrawerLayout, 131 R.drawable.ic_drawer, //nav menu toggle icon 132 R.string.app_name, // nav drawer open - description for accessibility 133 R.string.app_name // nav drawer close - description for accessibility 134 ) { 135 public void onDrawerClosed(View view) { 136 getSupportActionBar().setTitle(mTitle); 137 // calling onPrepareOptionsMenu() to show action bar icons 138 supportInvalidateOptionsMenu(); 139 } 140 141 public void onDrawerOpened(View drawerView) { 142 getSupportActionBar().setTitle(mDrawerTitle); 143 mDrawerListAdapter.notifyDataSetChanged(); // force redraw of all items (adapter.getView()) in order to adapt changing icons or number of tasks/notices 144 // calling onPrepareOptionsMenu() to hide action bar icons 145 supportInvalidateOptionsMenu(); 146 } 147 }; 148 mDrawerLayout.setDrawerListener(mDrawerToggle); 149 150 151 // pre-select fragment 152 // 1. check if explicitly requested fragment present 153 // e.g. after initial project attach. 154 int targetFragId = getIntent().getIntExtra("targetFragment", -1); 155 156 // 2. if no explicit request, try to restore previous selection 157 if(targetFragId < 0 && savedInstanceState != null) 158 targetFragId = savedInstanceState.getInt("navBarSelectionId"); 159 160 NavDrawerItem item = null; 161 if(targetFragId < 0) { 162 // if non of the above, go to default 163 item = mDrawerListAdapter.getItem(0); 164 } else item = mDrawerListAdapter.getItemForId(targetFragId); 165 166 if(item != null) dispatchNavBarOnClick(item, true); 167 else if(Logging.WARNING) Log.w(Logging.TAG, "onCreate: fragment selection returned null"); 168 169 //bind monitor service 170 doBindService(); 171 } 172 173 @Override onSaveInstanceState(Bundle outState)174 protected void onSaveInstanceState(Bundle outState) { 175 outState.putInt("navBarSelectionId", mDrawerListAdapter.selectedMenuId); 176 super.onSaveInstanceState(outState); 177 } 178 179 @Override onDestroy()180 protected void onDestroy() { 181 if(Logging.DEBUG) Log.d(Logging.TAG, "BOINCActivity onDestroy()"); 182 doUnbindService(); 183 super.onDestroy(); 184 } 185 186 @Override onNewIntent(Intent intent)187 protected void onNewIntent(Intent intent) { 188 if(Logging.DEBUG) Log.d(Logging.TAG, "BOINCActivity onNewIntent()"); 189 // onNewIntent gets called if activity is brought to front via intent, but was still alive, so onCreate is not called again 190 // getIntent always returns the intent activity was created of, so this method is the only hook to receive an updated intent 191 // e.g. after (not initial) project attach 192 super.onNewIntent(intent); 193 // navigate to explicitly requested fragment (e.g. after project attach) 194 int id = intent.getIntExtra("targetFragment", -1); 195 if(Logging.DEBUG) Log.d(Logging.TAG, "BOINCActivity onNewIntent() for target fragment: " + id); 196 NavDrawerItem item = mDrawerListAdapter.getItemForId(id); 197 if(item != null) dispatchNavBarOnClick(item,false); 198 else if(Logging.WARNING) Log.w(Logging.TAG, "onNewIntent: requested target fragment is null, for id: " + id); 199 } 200 201 @Override onResume()202 protected void onResume() { // gets called by system every time activity comes to front. after onCreate upon first creation 203 super.onResume(); 204 registerReceiver(mClientStatusChangeRec, ifcsc); 205 determineStatus(); 206 } 207 208 @Override onPause()209 protected void onPause() { // gets called by system every time activity loses focus. 210 if(Logging.VERBOSE) Log.v(Logging.TAG, "BOINCActivity onPause()"); 211 super.onPause(); 212 unregisterReceiver(mClientStatusChangeRec); 213 } 214 doBindService()215 private void doBindService() { 216 // start service to allow setForeground later on... 217 startService(new Intent(this, Monitor.class)); 218 // Establish a connection with the service, onServiceConnected gets called when 219 bindService(new Intent(this, Monitor.class), mConnection, Service.BIND_AUTO_CREATE); 220 } 221 doUnbindService()222 private void doUnbindService() { 223 if (mIsBound) { 224 // Detach existing connection. 225 unbindService(mConnection); 226 mIsBound = false; 227 } 228 } 229 /* 230 public IMonitor getMonitorService() { 231 if(!mIsBound) if(Logging.WARNING) Log.w(Logging.TAG, "Fragment trying to obtain serive reference, but Monitor not bound in BOINCActivity"); 232 return monitor; 233 }*/ 234 startAttachProjectListActivity()235 public void startAttachProjectListActivity() { 236 if(Logging.DEBUG) Log.d(Logging.TAG, "BOINCActivity attempt to start "); 237 startActivity(new Intent(this,SelectionListActivity.class)); 238 } 239 240 /** 241 * React to selection of nav bar item 242 * @param item 243 * @param position 244 * @param init 245 */ dispatchNavBarOnClick(NavDrawerItem item, boolean init)246 private void dispatchNavBarOnClick(NavDrawerItem item, boolean init) { 247 // update the main content by replacing fragments 248 if(item == null) { 249 if(Logging.WARNING) Log.w(Logging.TAG, "dispatchNavBarOnClick returns, item null."); 250 return; 251 } 252 if(Logging.DEBUG) Log.d(Logging.TAG, "dispatchNavBarOnClick for item with id: " + item.getId() + " title: " + item.getTitle() + " is project? " + item.isProjectItem()); 253 254 FragmentTransaction ft = getSupportFragmentManager().beginTransaction(); 255 Boolean fragmentChanges = false; 256 if(init) { 257 // if init, setup status fragment 258 ft.replace(R.id.status_container, new StatusFragment()); 259 } 260 if(!item.isProjectItem()) { 261 switch (item.getId()) { 262 case R.string.tab_tasks: 263 ft.replace(R.id.frame_container, new TasksFragment()); 264 fragmentChanges = true; 265 break; 266 case R.string.tab_notices: 267 ft.replace(R.id.frame_container, new NoticesFragment()); 268 fragmentChanges = true; 269 break; 270 case R.string.tab_projects: 271 ft.replace(R.id.frame_container, new ProjectsFragment()); 272 fragmentChanges = true; 273 break; 274 case R.string.menu_help: 275 Intent i = new Intent(Intent.ACTION_VIEW, Uri.parse("http://boinc.berkeley.edu/wiki/BOINC_Help")); 276 startActivity(i); 277 break; 278 case R.string.menu_about: 279 final Dialog dialog = new Dialog(this); 280 dialog.requestWindowFeature(Window.FEATURE_NO_TITLE); 281 dialog.setContentView(R.layout.dialog_about); 282 Button returnB = (Button) dialog.findViewById(R.id.returnB); 283 TextView tvVersion = (TextView)dialog.findViewById(R.id.version); 284 try { 285 tvVersion.setText(getString(R.string.about_version) + " " 286 + getPackageManager().getPackageInfo(getPackageName(), 0).versionName); 287 } catch (NameNotFoundException e) {if(Logging.WARNING) Log.w(Logging.TAG, "version name not found.");} 288 289 returnB.setOnClickListener(new OnClickListener() { 290 @Override 291 public void onClick(View v) { 292 dialog.dismiss(); 293 } 294 }); 295 dialog.show(); 296 break; 297 case R.string.menu_eventlog: 298 startActivity(new Intent(this,EventLogActivity.class)); 299 break; 300 case R.string.projects_add: 301 startActivity(new Intent(this, SelectionListActivity.class)); 302 break; 303 case R.string.tab_preferences: 304 ft.replace(R.id.frame_container, new PrefsFragment()); 305 fragmentChanges = true; 306 break; 307 308 default: 309 if(Logging.ERROR) Log.d(Logging.TAG, "dispatchNavBarOnClick() could not find corresponding fragment for " + item.getTitle()); 310 break; 311 } 312 313 } else { 314 // ProjectDetailsFragment. Data shown based on given master URL 315 Bundle args = new Bundle(); 316 args.putString("url", item.getProjectMasterUrl()); 317 Fragment frag = new ProjectDetailsFragment(); 318 frag.setArguments(args); 319 ft.replace(R.id.frame_container, frag); 320 fragmentChanges = true; 321 } 322 323 mDrawerLayout.closeDrawer(mDrawerList); 324 325 if(fragmentChanges) { 326 ft.commit(); 327 setTitle(item.getTitle()); 328 mDrawerListAdapter.selectedMenuId = item.getId(); //highlight item persistently 329 mDrawerListAdapter.notifyDataSetChanged(); // force redraw 330 } 331 332 if(Logging.DEBUG) Log.d(Logging.TAG, "displayFragmentForNavDrawer() " + item.getTitle()); 333 } 334 335 // tests whether status is available and whether it changed since the last event. determineStatus()336 private void determineStatus() { 337 try { 338 if(mIsBound) { 339 Integer newComputingStatus = monitor.getComputingStatus(); 340 if(newComputingStatus != clientComputingStatus) { 341 // computing status has changed, update and invalidate to force adaption of action items 342 clientComputingStatus = newComputingStatus; 343 supportInvalidateOptionsMenu(); 344 } 345 if(numberProjectsInNavList != monitor.getProjects().size()) 346 numberProjectsInNavList = mDrawerListAdapter.compareAndAddProjects((ArrayList<Project>)monitor.getProjects()); 347 //setAppTitle(); 348 } 349 } catch (Exception e) {} 350 } 351 onKeyDown(final int keyCode, final KeyEvent keyEvent)352 public final boolean onKeyDown(final int keyCode, final KeyEvent keyEvent) { 353 if (keyCode == KeyEvent.KEYCODE_MENU) { 354 if (this.mDrawerLayout.isDrawerOpen(this.mDrawerList)) { 355 this.mDrawerLayout.closeDrawer(this.mDrawerList); 356 } else { 357 this.mDrawerLayout.openDrawer(this.mDrawerList); 358 } 359 return true; 360 } 361 return super.onKeyDown(keyCode, keyEvent); 362 } 363 364 @Override onCreateOptionsMenu(Menu menu)365 public boolean onCreateOptionsMenu(Menu menu) { 366 if(Logging.DEBUG) Log.d(Logging.TAG, "BOINCActivity onCreateOptionsMenu()"); 367 368 MenuInflater inflater = getMenuInflater(); 369 inflater.inflate(R.menu.main_menu, menu); 370 return true; 371 } 372 373 @Override onPrepareOptionsMenu(Menu menu)374 public boolean onPrepareOptionsMenu(Menu menu) { 375 if(Logging.DEBUG) Log.d(Logging.TAG, "BOINCActivity onPrepareOptionsMenu()"); 376 377 // run mode, set title and icon based on status 378 MenuItem runMode = menu.findItem(R.id.run_mode); 379 if(clientComputingStatus == ClientStatus.COMPUTING_STATUS_NEVER) { 380 // display play button 381 runMode.setTitle(R.string.menu_run_mode_enable); 382 runMode.setIcon(R.drawable.playw); 383 } else { 384 // display stop button 385 runMode.setTitle(R.string.menu_run_mode_disable); 386 runMode.setIcon(R.drawable.pausew); 387 } 388 389 return super.onPrepareOptionsMenu(menu); 390 } 391 392 @Override onOptionsItemSelected(MenuItem item)393 public boolean onOptionsItemSelected(MenuItem item) { 394 if(Logging.DEBUG) Log.d(Logging.TAG, "BOINCActivity onOptionsItemSelected()"); 395 396 // toggle drawer 397 if (mDrawerToggle.onOptionsItemSelected(item)) { 398 return true; 399 } 400 401 switch (item.getItemId()) { 402 case R.id.run_mode: 403 if(item.getTitle().equals(getApplication().getString(R.string.menu_run_mode_disable))) { 404 if(Logging.DEBUG) Log.d(Logging.TAG,"run mode: disable"); 405 new WriteClientModeAsync().execute(BOINCDefs.RUN_MODE_NEVER); 406 } else if (item.getTitle().equals(getApplication().getString(R.string.menu_run_mode_enable))) { 407 if(Logging.DEBUG) Log.d(Logging.TAG,"run mode: enable"); 408 new WriteClientModeAsync().execute(BOINCDefs.RUN_MODE_AUTO); 409 } else if(Logging.DEBUG) Log.d(Logging.TAG,"run mode: unrecognized command"); 410 return true; 411 case R.id.projects_add: 412 startActivity(new Intent(this, SelectionListActivity.class)); 413 return true; 414 default: 415 return super.onOptionsItemSelected(item); 416 } 417 } 418 419 @Override onPostCreate(Bundle savedInstanceState)420 protected void onPostCreate(Bundle savedInstanceState) { 421 super.onPostCreate(savedInstanceState); 422 // Sync the toggle state after onRestoreInstanceState has occurred. 423 mDrawerToggle.syncState(); 424 } 425 426 @Override onConfigurationChanged(Configuration newConfig)427 public void onConfigurationChanged(Configuration newConfig) { 428 super.onConfigurationChanged(newConfig); 429 // Pass any configuration change to the drawer toggls 430 mDrawerToggle.onConfigurationChanged(newConfig); 431 } 432 433 @Override setTitle(CharSequence title)434 public void setTitle(CharSequence title) { 435 mTitle = title; 436 getSupportActionBar().setTitle(mTitle); 437 } 438 439 private final class WriteClientModeAsync extends AsyncTask<Integer, Void, Boolean> { 440 441 @Override doInBackground(Integer... params)442 protected Boolean doInBackground(Integer... params) { 443 // setting provided mode for both, CPU computation and network. 444 Boolean runMode; 445 try { 446 runMode = monitor.setRunMode(params[0]); 447 } catch (RemoteException e) { 448 runMode = false; 449 } 450 Boolean networkMode; 451 try { 452 networkMode = monitor.setNetworkMode(params[0]); 453 } catch (RemoteException e) { 454 networkMode = false; 455 } 456 return runMode && networkMode; 457 } 458 459 @Override onPostExecute(Boolean success)460 protected void onPostExecute(Boolean success) { 461 if(success) 462 try { 463 monitor.forceRefresh(); 464 } catch (RemoteException e) {} 465 else if(Logging.WARNING) Log.w(Logging.TAG,"setting run and network mode failed"); 466 } 467 } 468 } 469