package com.yotadevices.sdk;
import com.yotadevices.sdk.Constants.SystemBSFlags;
import com.yotadevices.sdk.Constants.VolumeButtonsEvent;
import com.yotadevices.sdk.exception.SuperNotCalledException;
import com.yotadevices.sdk.helper.HelperConstant;
import android.app.Service;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.os.Messenger;
import android.util.Log;
import android.view.KeyEvent;
import android.view.View;
import android.view.ViewGroup.LayoutParams;
import android.view.WindowManager;
public class BSActivity extends Service {
private String TAG;
private static final boolean DEBUG_BS_LIFECIRCLE = true;
/** Standard activity result: operation canceled. */
public static final int RESULT_CANCELED = 0;
/** Standard activity result: operation succeeded. */
public static final int RESULT_OK = -1;
/** Start of user-defined activity results. */
public static final int RESULT_FIRST_USER = 1;
private final Object mLockActive = new Object();
private Intent mIntent;
private BSDrawer mDrawer;
/** Flag indicating whether we have called bind on the service. */
boolean mIsBound;
boolean mCalled;
boolean isResumed;
boolean isFinishing;
boolean isAttached;
boolean isBSLock;
boolean dispatchOnHandleIntent;
private int mSystemUiVisibility = SystemBSFlags.SYSTEM_BS_UI_FLAG_VISIBLE;
private int mFeatureWindow = 0;
private int mResultCode;
private Intent mResultData;
private int mRequestCode;
/** Record inner state BSActivity */
private BSRecord mRecord;
private Handler mIncomingHandler;
private final Handler h = new Handler(); // for UI thread actions
private Drawer.Waveform mInitialWaveform = Drawer.Waveform.WAVEFORM_A2;
private Drawer.Dithering mInitialDithering = Drawer.Dithering.DITHER_ATKINSON_BINARY;
/**
* Target we publish for clients to send messages to IncomingHandler.
*/
private Messenger mMessenger;
/** Messenger for communicating with service. */
Messenger mService = null;
/**
* @hide
*/
@Override
public final void onCreate() {
super.onCreate();
TAG = getClass().getSimpleName();
mIncomingHandler = new BSAcivityIncomingMessagesHandler(this);
mMessenger = new Messenger(mIncomingHandler);
mDrawer = new BSDrawer(this);
mRecord = new BSRecord(getApplicationContext(), TAG);
}
private void cleanResource() {
mIncomingHandler = null;
mMessenger = null;
mDrawer = null;
}
synchronized void doBindService() {
if (!mIsBound) {
Log.d(TAG, "Start Binding.");
mIsBound = bindService(getFrameworkIntent(), mConnection, Context.BIND_AUTO_CREATE | Context.BIND_WAIVE_PRIORITY);
Log.d(TAG, "Binding..." + mIsBound);
}
}
synchronized void doUnbindService() {
if (mIsBound) {
unbindService(mConnection);
mIsBound = false;
Log.d(TAG, "Unbinding.");
}
}
private Intent getFrameworkIntent() {
return new Intent(HelperConstant.FRAMEWORK_SDK_ACTION);
}
/**
* @hide
*/
@Override
@Deprecated
public final void onStart(Intent intent, int startId) {
super.onStart(intent, startId);
}
/**
* @hide
*/
@Override
public final int onStartCommand(Intent intent, int flags, int startId) {
super.onStartCommand(intent, flags, startId);
mIntent = intent;
if (isResumed) {
handleCommand();
} else {
doBindService();
}
return Service.START_NOT_STICKY;
}
protected void onStartCommand(Intent intent) {
}
private void handleCommand() {
onStartCommand(mIntent);
onHandleIntent(mIntent);
sendRequest(InnerConstants.RequestFramework.REQUEST_SET_INTENT);
}
/**
* @hide
*/
@Override
public final IBinder onBind(Intent arg0) {
return null;
}
/**
* @hide
*/
@Override
public final void onTaskRemoved(Intent rootIntent) {
super.onTaskRemoved(rootIntent);
performFnishWithRequest(false);
doUnbindService();
performBSDestroy();
cleanResource();
onBSTaskRemoved(rootIntent);
}
/**
* @hide
*/
@Override
public final void onDestroy() {
super.onDestroy();
if (!isFinishing && isAttached) {
// user can stop bsActivity using stopService method
performFnishWithRequest(false);
}
doUnbindService();
if (isAttached) {
performBSDestroy();
}
cleanResource();
}
/**
* @hide
* Method is deprecated. Please to use {@link #onStartCommand}.
*
* @param intent
*/
@Deprecated
protected void onHandleIntent(Intent intent) {
Log.d(TAG, "onHandleIntent");
}
private void checkBSActivityRunning() {
if (mDrawer == null) {
throw new IllegalStateException("Is your BSActivity running?");
}
}
/**
* save inner state.
*/
private void saveInstanceState() {
performBSSaveInstanceState(mRecord);
mRecord.saveState();
}
private void restoreInstanceState() {
mRecord.restoreState();
performBSRestoreInstanceState(mRecord);
}
final void performBSSaveInstanceState(BSRecord record) {
onBSSaveInstanceState(record.getData());
if (DEBUG_BS_LIFECIRCLE) {
Log.v(TAG, "onBSSaveInstanceState " + TAG + " : " + record.getData());
}
}
final void performBSRestoreInstanceState(BSRecord record) {
onBSRestoreInstanceState(record.getData());
if (DEBUG_BS_LIFECIRCLE) {
Log.v(TAG, "onBSRestoreInstanceState " + TAG + " : " + record.getData());
}
}
final void performBSCreate() {
if (DEBUG_BS_LIFECIRCLE) {
Log.v(TAG, "onBSCreate.");
}
mDrawer.showBlankView();// TODO:
mCalled = false;
onBSCreate();
if (!mCalled) {
throw new SuperNotCalledException("BSActivity " + TAG + " did not call through to super.onBSCreate()");
}
sendRequest(InnerConstants.RequestFramework.REQUEST_SET_ACTIVE);
}
void performBSActivated(boolean isBsLock) {
performBSActivated(isBsLock, -1, RESULT_CANCELED, null);
}
void performBSActivated(boolean isBsLock, int requestCode, int resultCode, Intent data) {
if (!isFinishing) {
restoreInstanceState();
if (requestCode != -1) {
performOnBSActivityResult(requestCode, resultCode, data);
}
performBSResume(isBsLock);
handleCommand();
}
}
/** Disactivate current BSActivity : pause() -> stop() -> destroy() */
void performBSDisActivated() {
setFinishing(true);
performBSPause();
performBSStop(true);
}
void performOnBSActivityResult(int requestCode, int resultCode, Intent data) {
if (DEBUG_BS_LIFECIRCLE) {
Log.v(TAG, "onBSActivityResult");
}
onBSActivityResult(requestCode, resultCode, data);
}
void performBSResume(boolean isBSLocked) {
if (DEBUG_BS_LIFECIRCLE) {
Log.v(TAG, "onBSResume.");
}
isBSLock = isBSLocked;
mCalled = false;
onBSResume();
if (!mCalled) {
throw new SuperNotCalledException("BSActivity " + getClass().getSimpleName() + " did not call through to super.onBSResume()");
}
}
void performBSPause() {
if (DEBUG_BS_LIFECIRCLE) {
Log.v(TAG, "onBSPause.");
}
mCalled = false;
onBSPause();
if (!mCalled) {
throw new SuperNotCalledException("BSActivity " + getClass().getSimpleName() + " did not call through to super.onBSPause()");
}
}
void performBSStop(boolean stopped) {
if (DEBUG_BS_LIFECIRCLE) {
Log.v(TAG, "onBSStop.");
}
saveInstanceState();
mCalled = false;
onBSStop();
if (!mCalled) {
throw new SuperNotCalledException("BSActivity " + getClass().getSimpleName() + " did not call through to super.onBSStop()");
}
// self
if (stopped) {
stopSelf();
}
}
private final void performBSDestroy() {
if (DEBUG_BS_LIFECIRCLE) {
Log.v(TAG, "onBSDestroy.");
}
mCalled = false;
onBSDestroy();
if (!mCalled) {
throw new SuperNotCalledException("BSActivity " + getClass().getSimpleName() + " did not call through to super.onBSDestroy()");
}
}
void performBSLock() {
isBSLock = true;
onBSLock();
}
void performBSUnlock() {
isBSLock = false;
onBSUnlock();
}
final void performVolumeButtonsEvent(VolumeButtonsEvent event) {
onVolumeButtonsEvent(event);
}
final void performSystemUIChange() {
checkBSActivityRunning();
mDrawer.updateViewLayout(mSystemUiVisibility);
}
final void performKeyPress(int keyCode) {
String key = null;
boolean value = false;
switch (keyCode) {
case KeyEvent.KEYCODE_BACK:
key = InnerConstants.EXTRA_OVERRIDE_KEY_BACK;
value = onBackPressed();
break;
case KeyEvent.KEYCODE_HOME:
key = InnerConstants.EXTRA_OVERRIDE_KEY_HOME;
value = onHomePressed();
break;
default:
break;
}
Bundle bundle = getDefaultBundle();
if (key != null) {
bundle.putBoolean(key, value);
}
sendToFramework(InnerConstants.RequestFramework.HANDLE_ON_KEY_PRESS, bundle);
}
protected void onPrepareLayoutParams(WindowManager.LayoutParams lp) {
}
/**
* onBSSaveInstanceState - Save state before the instance is killed
*
* @param outState
* instance state.
*/
protected void onBSSaveInstanceState(Bundle outState) {
}
/**
* onBSRestoreInstanceState - Restores the state
*
* @param savedInstanceState
* saved instance state.
*/
protected void onBSRestoreInstanceState(Bundle savedInstanceState) {
}
/**
* onBSCreate - Called when BsDrawer is registered in the YotaPhoneitanium
* Manager (PM). In this state BsDrawer gains privileges to draw on BS but
* drawing is not permitted yet
*/
protected void onBSCreate() {
isResumed = false;
isFinishing = false;
dispatchOnHandleIntent = false;
isBSLock = false;
isAttached = true;
mCalled = true;
}
/**
* onBSResume - Called when BsDrawer is ready to draw on BS.
*/
protected void onBSResume() {
checkBSActivityRunning();
mDrawer.addBSParentView(mInitialWaveform, mInitialDithering);// show
// user UI
// on back
// screen
isResumed = true;
mCalled = true;
}
/**
* onBSStop and onBSPause: Called when BsDrawer loses privileges to draw on
* BS.
*/
protected void onBSPause() {
// checkBSActivityRunning();
if (mDrawer != null) {
mDrawer.removeBSParentView();
}
isResumed = false;
mCalled = true;
}
/**
* onBSStop and onBSPause: Called when BsDrawer loses privileges to draw on
* BS.
*/
protected void onBSStop() {
isFinishing = true;
mCalled = true;
}
/**
* onBSDestroy - Called when BsDrawer is unregistered from PM.
*/
protected void onBSDestroy() {
mCalled = true;
}
/**
* This is called if the service is currently running and the user has
* removed a task that comes from the service's application. If you have set
* ServiceInfo.FLAG_STOP_WITH_TASK then you will not receive this callback;
* instead, the service will simply be stopped.
*
* @param rootIntent
* The original root Intent that was used to launch the task that
* is being removed.
*/
protected void onBSTaskRemoved(Intent rootIntent) {
}
/**
* onBSLock - Called if back screen is locked. In this case BSActivity
* looses input from user.
*/
protected void onBSLock() {
}
/**
* onBSUnlock - Called if back screen is unlocked and application received
* the controls back.
*/
protected void onBSUnlock() {
}
/**
* onVolumeButtonsEvent - Called when Volume button event occurs.
*
* @param event
* Volume button event.
*/
protected void onVolumeButtonsEvent(VolumeButtonsEvent event) {
}
/**
* isBackScreenLocked - to be used to determine whether back screen is
* locked
*/
public boolean isBackScreenLocked() {
return isBSLock;
}
/**
* getIntent - Return the intent that started this BSActivity.
*
* @return Intent
*/
public Intent getIntent() {
return mIntent;
}
/**
* getBSDrawer - Returns instance of BSDrawer that should be used to draw on
* back screen.
*/
public BSDrawer getBSDrawer() {
return mDrawer;
}
public void setInitialWaveform(Drawer.Waveform initialWaveform) {
mInitialWaveform = initialWaveform;
}
public void setInitialDithering(Drawer.Dithering initialDithering) {
mInitialDithering = initialDithering;
}
/**
* Convenience for calling {@link com.yotadevices.sdk.BSDrawer#addViewToBS}
* .
*/
public void setBSContentView(View view) {
checkBSActivityRunning();
mDrawer.getParentView().removeAllViews();
mDrawer.addViewToBS(view);
}
/**
* Convenience for calling {@link com.yotadevices.sdk.BSDrawer#addViewToBS}
* .
*/
public void setBSContentView(View view, LayoutParams params) {
checkBSActivityRunning();
mDrawer.getParentView().removeAllViews();
mDrawer.addViewToBS(view, params);
}
/**
* Set the back screen activity content from a layout resource. The resource
* will be inflated, adding all top-level views to the back screen activity.
*
* @param layoutResID
* Resource ID to be inflated.
* @see #setBSContentView(android.view.View)
* @see #setBSContentView(android.view.View,
* android.view.ViewGroup.LayoutParams)
*/
public void setBSContentView(int layoutResID) {
checkBSActivityRunning();
mDrawer.getParentView().removeAllViews();
if (mDrawer.getBSLayoutInflater() != null) {
mDrawer.addViewToBS(getBSDrawer().getBSLayoutInflater().inflate(layoutResID, null));
}
}
/**
* Convenience for calling {@link com.yotadevices.sdk.BSDrawer#findViewById}
* .
*/
public View findViewById(int id) {
checkBSActivityRunning();
return mDrawer.findViewById(id);
}
/**
* Return application context
*
* @return getApplicationContext()
*/
public Context getContext() {
return getApplicationContext();
}
public void setFeature(int feature) {
if (mFeatureWindow != feature) {
mFeatureWindow = feature;
}
}
public void setSystemBSUiVisibility(int visibility) {
if (visibility != mSystemUiVisibility) {
mSystemUiVisibility = visibility;
if (isResumed) {
sendRequest(InnerConstants.RequestFramework.REQUEST_SET_SYSTEM_UI);
}
}
}
int getSytemBSUiVisibility() {
return mSystemUiVisibility;
}
/**
* Runs the specified action on the UI thread. If the current thread is the
* UI thread, then the action is executed immediately. If the current thread
* is not the UI thread, the action is posted to the event queue of the UI
* thread. Parameters: action the action to run on the UI thread
*
* @param action
*/
public void runOnUiThread(Runnable action) {
h.post(action);
}
/**
* Check to see whether this BSActivity is in the process of finishing,
* either because you called finish() on it or someone else has requested
* that it finished. This is often used in onBSPause() to determine whether
* the BSActivity is simply pausing or completely finishing.
*
* @return If the BSActivity is finishing, returns true; else returns false.
*/
public boolean isFinishing() {
return isFinishing;
}
void setFinishing(boolean finish) {
isFinishing = finish;
}
/**
* Call this when your BSActivity is done and should be closed
*/
public void finish() {
if (!isFinishing) {
performFnishWithRequest(true);
}
}
/**
* Call this to set the result that your bsactivity will return to its
* caller.
*
* @param resultCode
* The result code to propagate back to the originating activity,
* often RESULT_CANCELED or RESULT_OK
*/
public void setResult(int resultCode) {
synchronized (this) {
mResultCode = resultCode;
mResultData = null;
}
}
public void setResult(int resultCode, Intent data) {
synchronized (this) {
mResultCode = resultCode;
mResultData = data;
}
}
/**
* Same as {@link #startBSActivityForResult(Intent, int)} with no options
* specified.
*
* @param intent
* The intent to start.
*/
public void startBSActivity(Intent intent) {
startBSActivityForResult(intent, -1);
}
/**
* Launch an activity for which you would like a result when it finished.
* When this activity exits, your onBSActivityResult() method will be called
* with the given requestCode. Using a negative requestCode is the same as
* calling {@link #startBSActivity} (the activity is not launched as a
* sub-activity).
*
* @param intent
* The intent to start.
*
* @param requestCode
* If >= 0, this code will be returned in onBSActivityResult()
* when the activity exits.
*/
public void startBSActivityForResult(Intent intent, int requestCode) {
checkBSActivityRunning();
synchronized (this) {
mRequestCode = requestCode;
}
sendRequest(InnerConstants.RequestFramework.REQUEST_SET_ACTIVITY_RESULT);
startService(intent);
}
/**
* Called when an bs-activity you launched exits, giving you the requestCode
* you started it with, the resultCode it returned, and any additional data
* from it. The <var>resultCode</var> will be {@link #RESULT_CANCELED} if
* the activity explicitly returned that, didn't return any result, or
* crashed during its operation.
*
* <p>
* You will receive this call immediately before onResume() when your
* bs-activity is re-starting.
*
* @param requestCode
* The integer request code originally supplied to
* startBSActivityForResult(), allowing you to identify who this
* result came from.
* @param resultCode
* The integer result code returned by the child activity through
* its setResult().
* @param data
* An Intent, which can return result data to the caller (various
* data can be attached to Intent "extras").
*
* @see #startBSActivityForResult
* @see #setResult(int)
*/
protected void onBSActivityResult(int requestCode, int resultCode, Intent data) {
}
protected boolean onBackPressed() {
return false;
}
protected boolean onHomePressed() {
return false;
}
void performFnishWithRequest(boolean stopped) {
sendRequest(InnerConstants.RequestFramework.REQUEST_SET_FINISH);
performFnish(stopped);
}
void performFnish(boolean stopped) {
isFinishing = true;
if (isResumed) {
performBSPause();
}
performBSStop(stopped);
}
void sendRequest(int what) {
Bundle bundle = getDefaultBundle();
bundle.putInt(InnerConstants.EXTRA_SYSTEM_BS_UI_FLAG, mSystemUiVisibility);
bundle.putInt(InnerConstants.EXTRA_SYSTEM_FEATURE, mFeatureWindow);
sendToFramework(what, bundle);
}
private Bundle getDefaultBundle() {
Bundle bundle = new Bundle();
bundle.putString(InnerConstants.EXTRA_SERVICE_NAME, getClass().getName());
bundle.putString(InnerConstants.EXTRA_PACKAGE_NAME, getPackageName());
return bundle;
}
private void sendToFramework(int what, Bundle bundle) {
if (mService == null) {
return;
}
try {
Message msg = Message.obtain(null, what);
msg.arg1 = android.os.Process.myPid();
msg.arg2 = android.os.Process.myUid();
switch (what) {
case InnerConstants.RequestFramework.REQUEST_SET_ACTIVE:
case InnerConstants.RequestFramework.REQUEST_CAN_START:
msg.replyTo = mMessenger;
break;
case InnerConstants.RequestFramework.REQUEST_SET_INTENT:
bundle.putParcelable(InnerConstants.EXTRA_BS_ACTIVITY_INTENT, getIntent());
break;
case InnerConstants.RequestFramework.REQUEST_SET_ACTIVITY_RESULT:
int requestCode;
synchronized (this) {
requestCode = mRequestCode;
}
bundle.putInt(InnerConstants.EXTRA_REQUEST_CODE, requestCode);
break;
case InnerConstants.RequestFramework.REQUEST_SET_FINISH:
int resultCode;
Intent resultData;
synchronized (this) {
resultCode = mResultCode;
resultData = mResultData;
}
bundle.putInt(InnerConstants.EXTRA_RESULT_CODE, resultCode);
if (resultData != null) {
bundle.putParcelable(InnerConstants.EXTRA_RESULT_DATA, resultData);
}
break;
default:
break;
}
msg.setData(bundle);
mService.send(msg);
Log.d(TAG, "sending command to framework: " + what);
} catch (Exception e) {
Log.d(TAG, "Error while send msg", e);
if (!isFinishing) {
performFnish(true);
}
}
}
/**
* Class for interacting with the main interface of the service.
*/
private ServiceConnection mConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName className, IBinder service) {
if (mService == null) {
Log.d(TAG, "Attached.");
mService = new Messenger(service);
sendRequest(InnerConstants.RequestFramework.REQUEST_CAN_START);
}
}
@Override
public void onServiceDisconnected(ComponentName className) {
mService = null;
Log.d(TAG, "Disconnected.");
}
};
}