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