Is Android HandlerThread Looper reusable? - android

My Android app uses JobService to perform my App Widget Refresh.
Today I received a single crash report of:-
Fatal Exception: java.lang.OutOfMemoryError: Could not allocate JNI Env
at java.lang.Thread.nativeCreate(Thread.java)
at java.lang.Thread.start(Thread.java:731)
at com.research.app_widget.WidgetUpdateJobService.onStartJob(SourceFile:29)
at android.app.job.JobService$JobHandler.handleMessage(JobService.java:143)
at android.os.Handler.dispatchMessage(Handler.java:102)
at android.os.Looper.loop(Looper.java:154)
at android.app.ActivityThread.main(ActivityThread.java:6776)
at java.lang.reflect.Method.invoke(Method.java)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1496)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1386)
From the stack trace of this crash I have created over 500 instances of my HandlerThread
My onStartJob method resembles this:-
#Override
public boolean onStartJob(final JobParameters params) {
mServerHandlerThread = new HandlerThread("ServerApplication");
mServerHandlerThread.start();
mServerHandler = new Handler(mServerHandlerThread.getLooper(), new Handler.Callback() {
#Override
public boolean handleMessage(final Message msg) {
final int what = msg.what;
switch (what) {
case WHAT_WIDGET_REFRESH:
refreshWidget(params);
break;
default:
Log.e(TAG, "handleMessage: Unexpected WHAT = " + what);
}
return true;
}
});
mServerHandler.sendEmptyMessage(WHAT_WIDGET_REFRESH);
return true;
}
The refreshWidget(params) method is as follows:-
private void refreshWidget(final JobParameters params) {
final PersistableBundle persistableBundle = params.getExtras();
final int[] appWidgetIds = persistableBundle.getIntArray(AppWidgetManager.EXTRA_APPWIDGET_IDS);
if (appWidgetIds == null) {
Log.e(TAG, "refreshWidget: appWidgetIds array is null");
} else {
for (int appWidgetId : appWidgetIds) {
new WidgetRemoteViewsHelper().configureViews(this.getApplicationContext(), appWidgetId, isListEmpty(persistableBundle));
}
}
jobFinished(params, false);
}
What I don't understand is that theres an Android limit of starting 100 scheduledJobs, so how have I managed to start over 500 HandlerThreads?
What I would like to know is, is the following an acceptable solution?
e.g. reusing a single HandlerThread Looper instance?
private final HandlerThread mServerHandlerThread;
{
mServerHandlerThread = new HandlerThread("ServerApplication");
mServerHandlerThread.start();
}
#Override
public boolean onStartJob(final JobParameters params) {
mServerHandler = new Handler(mServerHandlerThread.getLooper(), new Handler.Callback() {
#Override
public boolean handleMessage(final Message msg) {
final int what = msg.what;
switch (what) {
case WHAT_WIDGET_REFRESH:
refreshWidget(params);
break;
default:
Log.e(TAG, "handleMessage: Unexpected WHAT = " + what);
}
return true;
}
});
mServerHandler.sendEmptyMessage(WHAT_WIDGET_REFRESH);
return true;
}
From my testing so far this approach seems to work as required. I just felt uneasy about reusing a single HandlerThread.

Related

Android JobScheduler rescheduling on failed with jobFinished(params, true)

I am having trouble with the JobScheduling API on android, when my job fails, I want it to be rescheduled according to the previously set back off policy.
The creation of the job looks like that :
try {
mJobSchedulerServiceComponent = new ComponentName(this.context, JobSchedulerService.class);
JobInfo.Builder builder = new JobInfo.Builder(Constant.AUTO_UPLOAD_JOB_ID, mJobSchedulerServiceComponent );
builder.setPersisted(true);
builder.setRequiresCharging(true);
builder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY);
builder.setBackoffCriteria(10000, JobInfo.BACKOFF_POLICY_LINEAR);
builder.setOverrideDeadline(600 * 1000);
builder.setMinimumLatency(60 * 1000);
PersistableBundle bundle = new PersistableBundle();
bundle.putString("action", Constant.JOB_SCHEDULER_AUTO_UPLOAD);
bundle.putString("url", Constant.API_URL_SEND_DATA);
builder.setExtras(bundle);
if( mJobScheduler.schedule( builder.build() ) <= 0 ) {
//If something goes wrong
logger.e(TAG, "Create auto upload job failed");
}
} catch (Exception e) {
logger.e(TAG, "createAutoUploadJob error " + e);
}
The JobService handling the jobs looks like that :
public class JobSchedulerService extends JobService {
private Handler handler;
protected Config config;
protected Logger logger;
protected static final String TAG = "MDM-JobSchedulerService";
private volatile ServiceThread thread = null;
#Override
public void onCreate() {
super.onCreate();
logger = Logger.getInstance(this);
logger.d(TAG, "JobSchedulerService start");
thread = new ServiceThread(this);
logger.d(TAG, "Starting thread");
thread.lock.lock();
thread.start();
logger.d(TAG, "waiting for init_done");
try {
thread.init_done.await();
}catch (InterruptedException e){
logger.i(TAG, "Got exception "+e.toString());
}
logger.d(TAG, "init_done");
thread.lock.unlock();
}
#Override
public void onDestroy() {
super.onDestroy();
logger.i(TAG, "JobSchedulerService destroyed");
}
#Override
public boolean onStartJob(JobParameters params) {
/*
* You'll notice that in the following code snippet, the onStartJob(JobParameters params) method returns true.
* This is because you're going to use a Handler instance to control your operation, which means that it could
* take longer to finish than the onStartJob(JobParameters params) method. By returning true, you're letting
* the application know that you will manually call the jobFinished(JobParameters params, boolean needsRescheduled) method
* */
if (handler != null) {
handler.sendMessage(Message.obtain(handler, params.getJobId(), params));
}else{
logger.e(TAG, "handler is null on onStartJob");
}
return true;
}
#Override
public boolean onStopJob(JobParameters params) {
handler.removeMessages(params.getJobId());
return true;
}
private class ServiceThread extends Thread{
private Context context;
protected Database.Helper db;
public Lock lock;
public Condition init_done;
// MAIN TREAD
public ServiceThread(Context context){
setContext(context);
config = Config.getInstance(context);
logger = Logger.getInstance(context);
db = Database.Helper.getInstance(context);
encryptor = new Encryption(context);
lock = new ReentrantLock();
init_done = lock.newCondition();
}
public void setContext(Context context) {
this.context = context;
}
//SERVICE THREAD
#Override
public void run() {
logger.i(TAG, "Thread started");
lock.lock();
Looper.prepare();
handler = new Handler( new Handler.Callback() {
#Override
public boolean handleMessage( Message msg ) {
logger.d(TAG, "New message");
JobParameters job_params = (JobParameters) msg.obj;
boolean success = handleJob(job_params);
if(!success) {
logger.d(TAG, "Auto upload job will be rescheduled (because upload failed)");
}
jobFinished( job_params, !success ); // Auto reschedule on failure following backoff policy
return true;
}
} );
logger.i(TAG, "Thread init_done");
init_done.signal();
lock.unlock();
Looper.loop();
}
private boolean httpPost(String url, String json){
logger.i(TAG, "Http post to : " + url);
return false; // Force fail to test rescheduling
}
public boolean handleJob(JobParameters params){
//Thread
// Return true to tell job successful, so it will not be retyied, false will retry
PersistableBundle extras = params.getExtras();
String action = extras.getString("action");
String json;
Boolean success;
logger.d(TAG, "Action = "+action);
switch (action){
case Constant.JOB_SCHEDULER_AUTO_UPLOAD:
success = httpPost(extras.getString("url"), json);
if(success){
db.storeEvent(System.currentTimeMillis(), Constant.JOB_SCHEDULER_AUTO_UPLOAD, "auto upload success");
return true;
}
db.storeEvent(System.currentTimeMillis(), Constant.JOB_SCHEDULER_AUTO_UPLOAD, "auto upload failed");
return false;
}
return false;
}
}
}
The job is launched properly when conditions are met, howerver it is never launch after failing, here are some logs related to the previous code :
10-18 15:06:51.857 19261-19261/fr.myapp.mdm D/MDM-JobSchedulerService: JobSchedulerService start
10-18 15:06:51.859 19261-19261/fr.myapp.mdm D/MDM-JobSchedulerService: Starting thread
10-18 15:06:51.860 19261-19261/fr.myapp.mdm D/MDM-JobSchedulerService: waiting for init_done
10-18 15:06:51.862 19261-19653/fr.myapp.mdm I/MDM-JobSchedulerService: Thread started
10-18 15:06:51.862 19261-19653/fr.myapp.mdm I/MDM-JobSchedulerService: Thread init_done
10-18 15:06:51.863 19261-19261/fr.myapp.mdm D/MDM-JobSchedulerService: init_done
10-18 15:06:51.869 19261-19653/fr.myapp.mdm D/MDM-JobSchedulerService: New message
10-18 15:06:51.870 19261-19653/fr.myapp.mdm D/MDM-JobSchedulerService: Action = JOB_SCHEDULER_AUTO_UPLOAD
10-18 15:06:51.870 19261-19653/fr.myapp.mdm I/MDM-JobSchedulerService: Auto upload job starting
10-18 15:06:52.029 19261-19653/fr.myapp.mdm I/MDM-JobSchedulerService: Http post to : http://xxxx
10-18 15:06:52.037 19261-19653/fr.myapp.mdm D/MDM-JobSchedulerService: Auto upload job will be rescheduled (because upload failed)
10-18 15:06:52.047 19261-19261/fr.myapp.mdm I/MDM-JobSchedulerService: JobSchedulerService destroyed
Thanks for your help

Android - Cannot make a static reference to a non static method

I refactoring my threads in order to avoid memory leaks, and I got 2 errors regarding handler and startActivityForResult being called from within the thread ( dealing with GoogleDrive)
I have in my DownloadActivity :
public class DownloadActivity extends Activity {
....
private void getFolderId(){
getFolderIdThread = new GetFolderIdThread();
getFolderIdThread.start();
}
private static class GetFolderIdThread extends Thread {
private Boolean mRunning = false;
#Override
public void run() {
mRunning = true;
fResultList = new ArrayList<File>();
Files f1 = mService.files();
Files.List request = null;
aFolderId = null;
do {
try {
request = f1.list();
String aQuery = "'root' in parents and mimeType='application/vnd.google-apps.folder' and title='"+ aFolderName + "'";
request.setQ(aQuery);
FileList fileList = request.execute();
fResultList.addAll(fileList.getItems());
request.setPageToken(fileList.getNextPageToken());
} catch (UserRecoverableAuthIOException e) {
startActivityForResult(e.getIntent(), REQUEST_AUTHORIZATION); <= THIS RAISES THE ERROR
} catch (IOException e) {
e.printStackTrace();
if (request != null){
request.setPageToken(null);
}
}
} while (request.getPageToken() !=null && request.getPageToken().length() > 0);
if (fResultList.size() == 0) {
Log.d(TAG, "cannot find the training folder at root level");
Message msg = handler.obtainMessage(); <= THIS RAISES THE ERROR
Bundle bundle = new Bundle();
bundle.putInt("msgKey", DownloadActivity.NO_TRAININGS_FOLDER);
msg.setData(bundle);
handler.sendMessage(msg); <= THIS RAISES THE ERROR
} else {
File folder = fResultList.get(0);
aFolderId = folder.getId();
getFolderContents(); <= THIS RAISES THE ERROR
}
}
public void close() {
mRunning = false;
}
}
I have the handler defined in my activity
Handler handler = new Handler() {
#Override
public void handleMessage(Message msg) {
...
}
}
and the onActivityResult
protected void onActivityResult(final int requestCode, final int resultCode, final Intent data) {
switch (requestCode) {
case REQUEST_ACCOUNT_PICKER:
....
break;
}
}
what are my options to bypass this error ?
Your GetFolderIdThread class is static and a static nested class cannot reference non-static methods and fields in the instance of the outer class that created it. Such a nested class can only access static methods and fields in your Activity. Remove static from the class definition and I think your problem will resolve.
You also need to post your call to startActivityForResult on the UI thread. Of course you should be able to use your handler for that, something like:
handler.post(new Runnable()
{
public void run()
{
startActivityForResult(e.getIntent(), REQUEST_AUTHORIZATION);
}
});
Make sure your thread can gracefully complete as well when you do that because it will continue to run.

Android Handler.postDelayed interrupts sound playing

I am using this code to play a sound
final MediaPlayer mp = MediaPlayer.create(this, R.raw.sound);
mp.setOnCompletionListener(new OnCompletionListener() {
#Override
public void onCompletion(MediaPlayer mp) {
mp.release();
}
});
It works fine on its own, however there was a problem after I added an animation that extends ImageView, which refreshes(by calling handler.postDelayed) the image resource at an interval about 30ms to create animation. The problem is that when the animation starts, it terminates the the playing of the sound. Here is the code for the Runnable that refreshes the ImageView.
private Runnable runnable = new Runnable () {
public void run() {
String name = "frame_" + frameCount;
frameCount ++;
int resId = mContext.getResources().getIdentifier(name, "drawable", mContext.getPackageName());
imageView.setImageResource(resId);
if(frameCount < totalFrameCount) {
mHandler.postDelayed(runnable, interval);
}
}
};
I also tried to use a thread that calls the anmiationView.postInvalidate to do the animation, however it has the same problem. Please help. Thanks
Edit:
It looks like the problem is due to WHEN the animation is called. Previously I called it in the onActivityResult of the activity. Looks like this is not the right place to call. Now I put the animation view in a popupWindow and play it there, it works properly. Not sure exactly why.
in handler's comments :
"A Handler allows you to send and process {#link Message} and Runnable
objects associated with a thread's {#link MessageQueue}. Each Handler
instance is associated with a single thread and that thread's message
queue. When you create a new Handler, it is bound to the thread /
message queue of the thread that is creating it -- from that point on,
it will deliver messages and runnables to that message queue and execute
them as they come out of the message queue."
so, the problem may be caused by both of animation and media playing operations are in
the same message queue own by which thread create the handler (let's say the main thread).
if the animation loops for ever, then the media player will hardly get any chance to run.
you could take it a try with HandlerThread, the thread will contain a new looper for the
handler created from it, all the runnables added to that handler will be running in another
individual thread.
the animation thread and the media play thread should be running in the different threads not
scheduling in the same one.
hope, it helps.
the HandlerThread usage and some discuss looks like this :
How to create a Looper thread, then send it a message immediately?
maybe it is caused by your miss arranged codes, i take it a try on my nexus 4 with android version 4.4.2, even no any cache tech, the animation and music works like a charm...
here is the major codes :
public class MainActivity extends Activity implements View.OnClickListener {
protected static final String TAG = "test002" ;
protected static final int UPDATE_ANI = 0x0701;
protected static final int UPDATE_END = 0x0702;
protected static final int[] ANI_IMG_IDS = {R.raw.img1, R.raw.img2, R.raw.img3, R.raw.img4,
R.raw.img5, R.raw.img6, R.raw.img7};
protected static final int[] BTN_IDS = {R.id.btnStart, R.id.btnStop};
protected android.os.Handler aniHandler = null; // async update
protected boolean isAniRunning = false ;
protected int aniImgIndex = 0 ;
protected ImageView aniImgView = null ;
protected MediaPlayer mediaPly = null ;
// animation timer
class AniUpdateRunnable implements Runnable {
public void run() {
Message msg = null ;
while (!Thread.currentThread().isInterrupted() && isAniRunning) {
msg = new Message();
msg.what = UPDATE_ANI;
aniHandler.sendMessage(msg);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
break ;
}
}
msg = new Message() ;
msg.what = UPDATE_END ;
aniHandler.sendMessage(msg) ;
}
}
protected void prepareMediaPlayer(MediaPlayer mp, int resource_id) {
AssetFileDescriptor afd = getResources().openRawResourceFd(resource_id);
try {
mp.reset();
mp.setDataSource(afd.getFileDescriptor(), afd.getStartOffset(), afd.getDeclaredLength());
afd.close();
mp.prepare();
} catch (IllegalArgumentException e) {
Log.d(TAG, "IlleagalArgumentException happened - " + e.toString()) ;
} catch(IllegalStateException e) {
Log.d(TAG, "IllegalStateException happened - " + e.toString()) ;
} catch(IOException e) {
Log.d(TAG, "IOException happened - " + e.toString()) ;
}
}
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// init : buttons onclick callback
{
Button btn;
int i;
for (i = 0; i < BTN_IDS.length; i++) {
btn = (Button) findViewById(BTN_IDS[i]);
btn.setOnClickListener(this);
}
}
// init : update animation handler callback
{
aniHandler = new Handler() {
public void handleMessage(Message msg) {
switch (msg.what) {
case UPDATE_ANI:
updateAniImages();
break ;
case UPDATE_END:
updateAniEnd();
break ;
default:
break;
}
}
};
}
// init : prepare image view
{
aniImgView = (ImageView)findViewById(R.id.imgAni) ;
mediaPly = MediaPlayer.create(this, R.raw.happyny) ;
mediaPly.setLooping(true);
}
}
protected void updateAniImages() {
if(aniImgIndex >= ANI_IMG_IDS.length) {
aniImgIndex = 0 ;
}
InputStream is = getResources().openRawResource(ANI_IMG_IDS[aniImgIndex]) ;
Bitmap bmp = (Bitmap) BitmapFactory.decodeStream(is) ;
aniImgView.setImageBitmap(bmp);
aniImgIndex++ ;
}
protected void updateAniEnd() {
aniImgIndex = 0 ;
aniImgView.setImageBitmap(null);
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
int id = item.getItemId();
if (id == R.id.action_settings) {
return true;
}
return super.onOptionsItemSelected(item);
}
#Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.btnStart:
isAniRunning = true ;
// no re-enter protectiion, should not be used in real project
new Thread(new AniUpdateRunnable()).start();
mediaPly.start();
break;
case R.id.btnStop:
isAniRunning = false ;
mediaPly.stop();
prepareMediaPlayer(mediaPly, R.raw.happyny);
break;
default:
break;
}
}
}
the major project codes and test apk should be find here :
apk installer
source code

Duplicate handler message?

when I call addAdapter() several times in background, sometimes I got some duplicate message. e.g. when I call addAdapter(item1, item2, item3...), it prints item1, item2, item2...
ExaminationItem currentAddItem = null;
private void addAdapter(ExaminationItem item)
{
currentAddItem = item;
addhandler.sendEmptyMessage(1);
}
private Handler addhandler = new Handler() {
#Override
public void handleMessage(Message msg)
{
switch (msg.what) {
case 1:
if (currentAddItem != null) {
_adapter.add(currentAddItem);
Log.i(getClass().getName(), "---------------------------addhandler: currentAddItem._itemName = " + currentAddItem._itemName);
}
break;
default:
break;
}
}
};
That isn't surprising. Every time you call sendEmptyMessage(), you're adding a message to the thread's message queue. You're not adding your item to the queue, you're just sending a message to the Handler to access whatever the value of currentAddItem is at the time that the Handler processes the message. It doesn't get to see what the value was at the time that you sent the message. So you're likely to see both skipped items and duplicate items.
private void addAdapter(ExaminationItem item)
{
Message message = addhandler.obtainMessage();
message.what = 1;
message.obj = item;
addhandler.sendMessage(message);
}
private Handler addhandler = new Handler() {
public void handleMessage(Message msg)
{
switch (msg.what) {
case 1:
if (msg.obj != null) {
_adapter.add((ExaminationItem) msg.obj);
examination_scanner_detail_tv.setText("detect to keep fit.");
Log.i(getClass().getName(), "addhandler: msg.obj = " + msg.obj);
}
break;
default:
break;
}
}
};

onServiceConnected sometimes not called after bindService on some devices

I've looked at a number of other threads with similar titles, and none seem to cover my problem. So, here goes.
I'm using the Google market expansion files (apkx) library and sample code, with a few modifications. This code relies on receiving callbacks from a service which handles background downloading, licence checks etc.
I have a bug where the service doesn't get correctly attached, which results in a softlock. To make this more unhelpful, this bug never happens on some devices, but occurs about two thirds of the time on other devices. I believe it to be independent of Android version, certainly I have two devices running 2.3.4, one of which (a Nexus S) doesn't have the problem, the other (an HTC Evo 3D) does.
To attempt to connect to the service, bindService is called and returns true. OnBind then gets called as expected and returns a sensible value but (when the bug occurs) onServiceConnected doesn't happen (I've waited 20 minutes just in case).
Has anyone else seen anything like this? If not, any guesses for what I might have done to cause such behaviour? If no-one has any thoughts, I'll post some code tomorrow.
EDIT: Here's the relevant code. If I've missed anything, please ask.
Whilst adding this code, I found a minor bug. Fixing it caused the frequency of the problem I'm trying to solve to change from 2 times in 3 to about 1 time in 6 on the phone I'm testing it on; no idea about effects on other phones. This continues to suggest to me a race condition or similar, but I've no idea what with.
OurDownloaderActivity.java (copied and changed from Google sample code)
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
...
//Test the licence is up to date
//if (current stored licence has expired)
{
startLicenceCheck();
initializeDownloadUI();
return;
}
...
}
#Override
protected void onResume() {
if (null != mDownloaderClientStub) {
mDownloaderClientStub.connect(this);
}
super.onResume();
}
private void startLicenceCheck()
{
Intent launchIntent = OurDownloaderActivity.this
.getIntent();
Intent intentToLaunchThisActivityFromNotification = new Intent(OurDownloaderActivity
.this, OurDownloaderActivity.this.getClass());
intentToLaunchThisActivityFromNotification.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
Intent.FLAG_ACTIVITY_CLEAR_TOP);
intentToLaunchThisActivityFromNotification.setAction(launchIntent.getAction());
if (launchIntent.getCategories() != null) {
for (String category : launchIntent.getCategories()) {
intentToLaunchThisActivityFromNotification.addCategory(category);
}
}
// Build PendingIntent used to open this activity from Notification
PendingIntent pendingIntent = PendingIntent.getActivity(OurDownloaderActivity.this,
0, intentToLaunchThisActivityFromNotification,
PendingIntent.FLAG_UPDATE_CURRENT);
DownloaderService.startLicenceCheck(this, pendingIntent, OurDownloaderService.class);
}
initializeDownloadUI()
{
mDownloaderClientStub = DownloaderClientMarshaller.CreateStub
(this, OurDownloaderService.class);
//do a load of UI setup
...
}
//This should be called by the Stub's onServiceConnected method
/**
* Critical implementation detail. In onServiceConnected we create the
* remote service and marshaler. This is how we pass the client information
* back to the service so the client can be properly notified of changes. We
* must do this every time we reconnect to the service.
*/
#Override
public void onServiceConnected(Messenger m) {
mRemoteService = DownloaderServiceMarshaller.CreateProxy(m);
mRemoteService.onClientUpdated(mDownloaderClientStub.getMessenger());
}
DownloaderService.java (in Google market expansion library but somewhat edited )
//this is the onBind call that happens fine; the value it returns is definitely not null
#Override
public IBinder onBind(Intent paramIntent) {
return this.mServiceMessenger.getBinder();
}
final private IStub mServiceStub = DownloaderServiceMarshaller.CreateStub(this);
final private Messenger mServiceMessenger = mServiceStub.getMessenger();
//MY CODE, derived from Google's code
//I have seen the bug occur with a service started by Google's code too,
//but this code happens more often so is more repeatably related to the problem
public static void startLicenceCheck(Context context, PendingIntent pendingIntent, Class<?> serviceClass)
{
String packageName = serviceClass.getPackage().getName();
String className = serviceClass.getName();
Intent fileIntent = new Intent();
fileIntent.setClassName(packageName, className);
fileIntent.putExtra(EXTRA_LICENCE_EXPIRED, true);
fileIntent.putExtra(EXTRA_PENDING_INTENT, pendingIntent);
context.startService(fileIntent);
}
#Override
protected void onHandleIntent(Intent intent) {
setServiceRunning(true);
try {
final PendingIntent pendingIntent = (PendingIntent) intent
.getParcelableExtra(EXTRA_PENDING_INTENT);
if (null != pendingIntent)
{
mNotification.setClientIntent(pendingIntent);
mPendingIntent = pendingIntent;
} else if (null != mPendingIntent) {
mNotification.setClientIntent(mPendingIntent);
} else {
Log.e(LOG_TAG, "Downloader started in bad state without notification intent.");
return;
}
if(intent.getBooleanExtra(EXTRA_LICENCE_EXPIRED, false))
{
//we are here due to startLicenceCheck
updateExpiredLVL(this);
return;
}
...
}
}
//MY CODE, based on Google's, again
public void updateExpiredLVL(final Context context) {
Context c = context.getApplicationContext();
Handler h = new Handler(c.getMainLooper());
h.post(new LVLExpiredUpdateRunnable(c));
}
private class LVLExpiredUpdateRunnable implements Runnable
{
LVLExpiredUpdateRunnable(Context context) {
mContext = context;
}
final Context mContext;
#Override
public void run() {
setServiceRunning(true);
mNotification.onDownloadStateChanged(IDownloaderClient.STATE_LVL_UPDATING);
String deviceId = getDeviceId(mContext);
final APKExpansionPolicy aep = new APKExpansionPolicy(mContext,
new AESObfuscator(getSALT(), mContext.getPackageName(), deviceId));
// Construct the LicenseChecker with a Policy.
final LicenseChecker checker = new LicenseChecker(mContext, aep,
getPublicKey() // Your public licensing key.
);
checker.checkAccess(new LicenseCheckerCallback() {
...
});
}
}
DownloaderClientMarshaller.java (in Google market expansion library)
public static IStub CreateStub(IDownloaderClient itf, Class<?> downloaderService) {
return new Stub(itf, downloaderService);
}
and the Stub class from the same file:
private static class Stub implements IStub {
private IDownloaderClient mItf = null;
private Class<?> mDownloaderServiceClass;
private boolean mBound;
private Messenger mServiceMessenger;
private Context mContext;
/**
* Target we publish for clients to send messages to IncomingHandler.
*/
final Messenger mMessenger = new Messenger(new Handler() {
#Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_ONDOWNLOADPROGRESS:
Bundle bun = msg.getData();
if ( null != mContext ) {
bun.setClassLoader(mContext.getClassLoader());
DownloadProgressInfo dpi = (DownloadProgressInfo) msg.getData()
.getParcelable(PARAM_PROGRESS);
mItf.onDownloadProgress(dpi);
}
break;
case MSG_ONDOWNLOADSTATE_CHANGED:
mItf.onDownloadStateChanged(msg.getData().getInt(PARAM_NEW_STATE));
break;
case MSG_ONSERVICECONNECTED:
mItf.onServiceConnected(
(Messenger) msg.getData().getParcelable(PARAM_MESSENGER));
break;
}
}
});
public Stub(IDownloaderClient itf, Class<?> downloaderService) {
mItf = itf;
mDownloaderServiceClass = downloaderService;
}
/**
* Class for interacting with the main interface of the service.
*/
private ServiceConnection mConnection = new ServiceConnection() {
//this is the critical call that never happens
public void onServiceConnected(ComponentName className, IBinder service) {
// This is called when the connection with the service has been
// established, giving us the object we can use to
// interact with the service. We are communicating with the
// service using a Messenger, so here we get a client-side
// representation of that from the raw IBinder object.
mServiceMessenger = new Messenger(service);
mItf.onServiceConnected(
mServiceMessenger);
mBound = true;
}
public void onServiceDisconnected(ComponentName className) {
// This is called when the connection with the service has been
// unexpectedly disconnected -- that is, its process crashed.
mServiceMessenger = null;
mBound = false;
}
};
#Override
public void connect(Context c) {
mContext = c;
Intent bindIntent = new Intent(c, mDownloaderServiceClass);
bindIntent.putExtra(PARAM_MESSENGER, mMessenger);
if ( !c.bindService(bindIntent, mConnection, 0) ) {
if ( Constants.LOGVV ) {
Log.d(Constants.TAG, "Service Unbound");
}
}
}
#Override
public void disconnect(Context c) {
if (mBound) {
c.unbindService(mConnection);
mBound = false;
}
mContext = null;
}
#Override
public Messenger getMessenger() {
return mMessenger;
}
}
DownloaderServiceMarshaller.java (in Google market expansion library, unchanged)
private static class Proxy implements IDownloaderService {
private Messenger mMsg;
private void send(int method, Bundle params) {
Message m = Message.obtain(null, method);
m.setData(params);
try {
mMsg.send(m);
} catch (RemoteException e) {
e.printStackTrace();
}
}
public Proxy(Messenger msg) {
mMsg = msg;
}
#Override
public void requestAbortDownload() {
send(MSG_REQUEST_ABORT_DOWNLOAD, new Bundle());
}
#Override
public void requestPauseDownload() {
send(MSG_REQUEST_PAUSE_DOWNLOAD, new Bundle());
}
#Override
public void setDownloadFlags(int flags) {
Bundle params = new Bundle();
params.putInt(PARAMS_FLAGS, flags);
send(MSG_SET_DOWNLOAD_FLAGS, params);
}
#Override
public void requestContinueDownload() {
send(MSG_REQUEST_CONTINUE_DOWNLOAD, new Bundle());
}
#Override
public void requestDownloadStatus() {
send(MSG_REQUEST_DOWNLOAD_STATE, new Bundle());
}
#Override
public void onClientUpdated(Messenger clientMessenger) {
Bundle bundle = new Bundle(1);
bundle.putParcelable(PARAM_MESSENGER, clientMessenger);
send(MSG_REQUEST_CLIENT_UPDATE, bundle);
}
}
private static class Stub implements IStub {
private IDownloaderService mItf = null;
final Messenger mMessenger = new Messenger(new Handler() {
#Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_REQUEST_ABORT_DOWNLOAD:
mItf.requestAbortDownload();
break;
case MSG_REQUEST_CONTINUE_DOWNLOAD:
mItf.requestContinueDownload();
break;
case MSG_REQUEST_PAUSE_DOWNLOAD:
mItf.requestPauseDownload();
break;
case MSG_SET_DOWNLOAD_FLAGS:
mItf.setDownloadFlags(msg.getData().getInt(PARAMS_FLAGS));
break;
case MSG_REQUEST_DOWNLOAD_STATE:
mItf.requestDownloadStatus();
break;
case MSG_REQUEST_CLIENT_UPDATE:
mItf.onClientUpdated((Messenger) msg.getData().getParcelable(
PARAM_MESSENGER));
break;
}
}
});
public Stub(IDownloaderService itf) {
mItf = itf;
}
#Override
public Messenger getMessenger() {
return mMessenger;
}
#Override
public void connect(Context c) {
}
#Override
public void disconnect(Context c) {
}
}
/**
* Returns a proxy that will marshall calls to IDownloaderService methods
*
* #param ctx
* #return
*/
public static IDownloaderService CreateProxy(Messenger msg) {
return new Proxy(msg);
}
/**
* Returns a stub object that, when connected, will listen for marshalled
* IDownloaderService methods and translate them into calls to the supplied
* interface.
*
* #param itf An implementation of IDownloaderService that will be called
* when remote method calls are unmarshalled.
* #return
*/
public static IStub CreateStub(IDownloaderService itf) {
return new Stub(itf);
}

Categories

Resources