I have one Activity MainActivity. It contains a ViewPager hosting a fragment FragmentPlaceHolder. This fragment has a RecyclerView.
Now, on a click of a particular view, say button, inside a RecyclerView item, I want to open a chrome custom tab in new Activity. Can anyone help me with this one ? I am expecting a step wise implemention of Chrome Custom tabs.
Thanks.
So, I did it this way:
First of all, add this following dependency in build.gradle(app):
compile 'com.android.support:customtabs:23.1.1'
First create an activity WebviewActivity:
WebViewActivity.java
public class WebviewActivity extends AppCompatActivity {
public static final String EXTRA_URL = "extra.url";
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_webview);
String url = getIntent().getStringExtra(EXTRA_URL);
WebView webView = (WebView)findViewById(R.id.webview);
webView.setWebViewClient(new WebViewClient());
WebSettings webSettings = webView.getSettings();
webSettings.setJavaScriptEnabled(true);
setTitle(url);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
webView.loadUrl(url);
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
// Respond to the action bar's Up/Home button
case android.R.id.home:
finish();
return true;
}
return super.onOptionsItemSelected(item);
}
}
Now, say on button click you want to open chrome custom tab and load www.google.com, so, do it as below:
public void onButtonClick(View view){
CustomTabsIntent customTabsIntent = new CustomTabsIntent.Builder().build();
CustomTabActivityHelper.openCustomTab(
this,// activity
customTabsIntent,
Uri.parse("http://www.google.com"),
new WebviewFallback()
);
}
activity_webview.xml
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright 2015 Google Inc. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<WebView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/webview"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="#dimen/activity_vertical_margin"
android:paddingLeft="#dimen/activity_horizontal_margin"
android:paddingRight="#dimen/activity_horizontal_margin"
android:paddingTop="#dimen/activity_vertical_margin"
tools:context=".helpers.WebviewActivity" />
Then, simply craete the classes as below:
CustomTabActivityHelper.java
import android.app.Activity;
import android.net.Uri;
import android.os.Bundle;
import android.support.customtabs.CustomTabsClient;
import android.support.customtabs.CustomTabsIntent;
import android.support.customtabs.CustomTabsServiceConnection;
import android.support.customtabs.CustomTabsSession;
import java.util.List;
/**
* This is a helper class to manage the connection to the Custom Tabs Service.
*/
public class CustomTabActivityHelper implements ServiceConnectionCallback {
private CustomTabsSession mCustomTabsSession;
private CustomTabsClient mClient;
private CustomTabsServiceConnection mConnection;
private ConnectionCallback mConnectionCallback;
/**
* Opens the URL on a Custom Tab if possible. Otherwise fallsback to opening it on a WebView.
*
* #param activity The host activity.
* #param customTabsIntent a CustomTabsIntent to be used if Custom Tabs is available.
* #param uri the Uri to be opened.
* #param fallback a CustomTabFallback to be used if Custom Tabs is not available.
*/
public static void openCustomTab(Activity activity,
CustomTabsIntent customTabsIntent,
Uri uri,
CustomTabFallback fallback) {
String packageName = CustomTabsHelper.getPackageNameToUse(activity);
//If we cant find a package name, it means theres no browser that supports
//Chrome Custom Tabs installed. So, we fallback to the webview
if (packageName == null) {
if (fallback != null) {
fallback.openUri(activity, uri);
}
} else {
customTabsIntent.intent.setPackage(packageName);
customTabsIntent.launchUrl(activity, uri);
}
}
/**
* Unbinds the Activity from the Custom Tabs Service.
*
* #param activity the activity that is connected to the service.
*/
public void unbindCustomTabsService(Activity activity) {
if (mConnection == null) return;
activity.unbindService(mConnection);
mClient = null;
mCustomTabsSession = null;
mConnection = null;
}
/**
* Creates or retrieves an exiting CustomTabsSession.
*
* #return a CustomTabsSession.
*/
public CustomTabsSession getSession() {
if (mClient == null) {
mCustomTabsSession = null;
} else if (mCustomTabsSession == null) {
mCustomTabsSession = mClient.newSession(null);
}
return mCustomTabsSession;
}
/**
* Register a Callback to be called when connected or disconnected from the Custom Tabs Service.
*
* #param connectionCallback
*/
public void setConnectionCallback(ConnectionCallback connectionCallback) {
this.mConnectionCallback = connectionCallback;
}
/**
* Binds the Activity to the Custom Tabs Service.
*
* #param activity the activity to be binded to the service.
*/
public void bindCustomTabsService(Activity activity) {
if (mClient != null) return;
String packageName = CustomTabsHelper.getPackageNameToUse(activity);
if (packageName == null) return;
mConnection = new ServiceConnection(this);
CustomTabsClient.bindCustomTabsService(activity, packageName, mConnection);
}
/**
* #return true if call to mayLaunchUrl was accepted.
* #see {#link CustomTabsSession#mayLaunchUrl(Uri, Bundle, List)}.
*/
public boolean mayLaunchUrl(Uri uri, Bundle extras, List<Bundle> otherLikelyBundles) {
if (mClient == null) return false;
CustomTabsSession session = getSession();
if (session == null) return false;
return session.mayLaunchUrl(uri, extras, otherLikelyBundles);
}
#Override
public void onServiceConnected(CustomTabsClient client) {
mClient = client;
mClient.warmup(0L);
if (mConnectionCallback != null) mConnectionCallback.onCustomTabsConnected();
}
#Override
public void onServiceDisconnected() {
mClient = null;
mCustomTabsSession = null;
if (mConnectionCallback != null) mConnectionCallback.onCustomTabsDisconnected();
}
/**
* A Callback for when the service is connected or disconnected. Use those callbacks to
* handle UI changes when the service is connected or disconnected.
*/
public interface ConnectionCallback {
/**
* Called when the service is connected.
*/
void onCustomTabsConnected();
/**
* Called when the service is disconnected.
*/
void onCustomTabsDisconnected();
}
/**
* To be used as a fallback to open the Uri when Custom Tabs is not available.
*/
public interface CustomTabFallback {
/**
* #param activity The Activity that wants to open the Uri.
* #param uri The uri to be opened by the fallback.
*/
void openUri(Activity activity, Uri uri);
}
}
CustomTabsHelper.java
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.net.Uri;
import android.text.TextUtils;
import android.util.Log;
import java.util.ArrayList;
import java.util.List;
/**
* Helper class for Custom Tabs.
*/
public class CustomTabsHelper {
private static final String TAG = "CustomTabsHelper";
static final String STABLE_PACKAGE = "com.android.chrome";
static final String BETA_PACKAGE = "com.chrome.beta";
static final String DEV_PACKAGE = "com.chrome.dev";
static final String LOCAL_PACKAGE = "com.google.android.apps.chrome";
private static final String EXTRA_CUSTOM_TABS_KEEP_ALIVE =
"android.support.customtabs.extra.KEEP_ALIVE";
private static final String ACTION_CUSTOM_TABS_CONNECTION =
"android.support.customtabs.action.CustomTabsService";
private static String sPackageNameToUse;
private CustomTabsHelper() {}
public static void addKeepAliveExtra(Context context, Intent intent) {
Intent keepAliveIntent = new Intent().setClassName(
context.getPackageName(), KeepAliveService.class.getCanonicalName());
intent.putExtra(EXTRA_CUSTOM_TABS_KEEP_ALIVE, keepAliveIntent);
}
/**
* Goes through all apps that handle VIEW intents and have a warmup service. Picks
* the one chosen by the user if there is one, otherwise makes a best effort to return a
* valid package name.
*
* This is <strong>not</strong> threadsafe.
*
* #param context {#link Context} to use for accessing {#link PackageManager}.
* #return The package name recommended to use for connecting to custom tabs related components.
*/
public static String getPackageNameToUse(Context context) {
if (sPackageNameToUse != null) return sPackageNameToUse;
PackageManager pm = context.getPackageManager();
// Get default VIEW intent handler.
Intent activityIntent = new Intent(Intent.ACTION_VIEW, Uri.parse("http://www.example.com"));
ResolveInfo defaultViewHandlerInfo = pm.resolveActivity(activityIntent, 0);
String defaultViewHandlerPackageName = null;
if (defaultViewHandlerInfo != null) {
defaultViewHandlerPackageName = defaultViewHandlerInfo.activityInfo.packageName;
}
// Get all apps that can handle VIEW intents.
List<ResolveInfo> resolvedActivityList = pm.queryIntentActivities(activityIntent, 0);
List<String> packagesSupportingCustomTabs = new ArrayList<>();
for (ResolveInfo info : resolvedActivityList) {
Intent serviceIntent = new Intent();
serviceIntent.setAction(ACTION_CUSTOM_TABS_CONNECTION);
serviceIntent.setPackage(info.activityInfo.packageName);
if (pm.resolveService(serviceIntent, 0) != null) {
packagesSupportingCustomTabs.add(info.activityInfo.packageName);
}
}
// Now packagesSupportingCustomTabs contains all apps that can handle both VIEW intents
// and service calls.
if (packagesSupportingCustomTabs.isEmpty()) {
sPackageNameToUse = null;
} else if (packagesSupportingCustomTabs.size() == 1) {
sPackageNameToUse = packagesSupportingCustomTabs.get(0);
} else if (!TextUtils.isEmpty(defaultViewHandlerPackageName)
&& !hasSpecializedHandlerIntents(context, activityIntent)
&& packagesSupportingCustomTabs.contains(defaultViewHandlerPackageName)) {
sPackageNameToUse = defaultViewHandlerPackageName;
} else if (packagesSupportingCustomTabs.contains(STABLE_PACKAGE)) {
sPackageNameToUse = STABLE_PACKAGE;
} else if (packagesSupportingCustomTabs.contains(BETA_PACKAGE)) {
sPackageNameToUse = BETA_PACKAGE;
} else if (packagesSupportingCustomTabs.contains(DEV_PACKAGE)) {
sPackageNameToUse = DEV_PACKAGE;
} else if (packagesSupportingCustomTabs.contains(LOCAL_PACKAGE)) {
sPackageNameToUse = LOCAL_PACKAGE;
}
return sPackageNameToUse;
}
/**
* Used to check whether there is a specialized handler for a given intent.
* #param intent The intent to check with.
* #return Whether there is a specialized handler for the given intent.
*/
private static boolean hasSpecializedHandlerIntents(Context context, Intent intent) {
try {
PackageManager pm = context.getPackageManager();
List<ResolveInfo> handlers = pm.queryIntentActivities(
intent,
PackageManager.GET_RESOLVED_FILTER);
if (handlers == null || handlers.size() == 0) {
return false;
}
for (ResolveInfo resolveInfo : handlers) {
IntentFilter filter = resolveInfo.filter;
if (filter == null) continue;
if (filter.countDataAuthorities() == 0 || filter.countDataPaths() == 0) continue;
if (resolveInfo.activityInfo == null) continue;
return true;
}
} catch (RuntimeException e) {
Log.e(TAG, "Runtime exception while getting specialized handlers");
}
return false;
}
/**
* #return All possible chrome package names that provide custom tabs feature.
*/
public static String[] getPackages() {
return new String[]{"", STABLE_PACKAGE, BETA_PACKAGE, DEV_PACKAGE, LOCAL_PACKAGE};
}
}
ServiceConnection.java
import android.content.ComponentName;
import android.support.customtabs.CustomTabsClient;
import android.support.customtabs.CustomTabsServiceConnection;
import java.lang.ref.WeakReference;
/**
* Implementation for the CustomTabsServiceConnection that avoids leaking the
* ServiceConnectionCallback
*/
public class ServiceConnection extends CustomTabsServiceConnection {
// A weak reference to the ServiceConnectionCallback to avoid leaking it.
private WeakReference<ServiceConnectionCallback> mConnectionCallback;
public ServiceConnection(ServiceConnectionCallback connectionCallback) {
mConnectionCallback = new WeakReference<>(connectionCallback);
}
#Override
public void onCustomTabsServiceConnected(ComponentName name, CustomTabsClient client) {
ServiceConnectionCallback connectionCallback = mConnectionCallback.get();
if (connectionCallback != null) connectionCallback.onServiceConnected(client);
}
#Override
public void onServiceDisconnected(ComponentName name) {
ServiceConnectionCallback connectionCallback = mConnectionCallback.get();
if (connectionCallback != null) connectionCallback.onServiceDisconnected();
}
}
ServiceConnectionCallback.java
import android.support.customtabs.CustomTabsClient;
/**
* Callback for events when connecting and disconnecting from Custom Tabs Service.
*/
public interface ServiceConnectionCallback {
/**
* Called when the service is connected.
* #param client a CustomTabsClient
*/
void onServiceConnected(CustomTabsClient client);
/**
* Called when the service is disconnected.
*/
void onServiceDisconnected();
}
WebviewFallback.java
import android.app.Activity;
import android.content.Intent;
import android.net.Uri;
/**
* A Fallback that opens a Webview when Custom Tabs is not available
*/
public class WebviewFallback implements CustomTabActivityHelper.CustomTabFallback {
#Override
public void openUri(Activity activity, Uri uri) {
Intent intent = new Intent(activity, WebviewActivity.class);
intent.putExtra(WebviewActivity.EXTRA_URL, uri.toString());
activity.startActivity(intent);
}
}
By now, you should be able to open Chrome custom tab.
If found any issue with this, just let me know.
#Chintan Soni's answer is absolutely correct for Android SDK versions lower than 21.
For higher versions when querying activities that can handle the intent in getPackageNameToUse() function pass flag PackageManager.MATCH_ALL like this.
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.LOLLIPOP_MR1)
resolvedActivityList = pm.queryIntentActivities(activityIntent,
PackageManager.MATCH_ALL);
else
resolvedActivityList = pm.queryIntentActivities(activityIntent, 0);
add next string to tour code before WebSettings webSettings
webView.setWebViewClient(new WebViewClient());
Related
I got a use case where I had to use Broadcast receivers in my project and came to know that the class "LocalBroadcastManager" has been deprecated.
In the google docs they recommend using Livedate and I tried searching for a good example of it but I didn't see any that suits my case. Here's how I'm using broadcast receiver:
MainActivity.java
private WeekFilterStatus weekFilterStatus;
//Inside onCreate
weekFilterStatus = new WeekFilterStatus();
LocalBroadcastManager.getInstance(getContext()).registerReceiver(weekFilterStatus, new IntentFilter("Week_filter"));
//outside onCreate
private class WeekFilterStatus extends BroadcastReceiver{
#Override
public void onReceive(Context context, Intent intent) {
txtview.SetText("Week");
}
}
Dailog.java
go.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
Intent intent= new Intent("Week_filter");
LocalBroadcastManager.getInstance(getContext()).sendBroadcast(intent);
dismiss();
}
});
This is how I'm using in my project, I open a dialog and then hit go and the textview gets updated in main activity. And ya I have no issue with this as it is working as expected but I want to know other ways of doing exactly this with LiveData or any other methods.
If you use latest version of Android studio and try to call LocalBroadcastManager class, you may not be able to import it as that has been in my case so here is the class just in case anyone requires:
LocalBroadcastManager.java
/*
* Copyright 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package androidx.localbroadcastmanager.content;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.net.Uri;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import androidx.annotation.NonNull;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Set;
/**
* Helper to register for and send broadcasts of Intents to local objects
* within your process. This has a number of advantages over sending
* global broadcasts with {#link android.content.Context#sendBroadcast}:
* <ul>
* <li> You know that the data you are broadcasting won't leave your app, so
* don't need to worry about leaking private data.
* <li> It is not possible for other applications to send these broadcasts to
* your app, so you don't need to worry about having security holes they can
* exploit.
* <li> It is more efficient than sending a global broadcast through the
* system.
* </ul>
*/
public final class LocalBroadcastManager {
private static final class ReceiverRecord {
final IntentFilter filter;
final BroadcastReceiver receiver;
boolean broadcasting;
boolean dead;
ReceiverRecord(IntentFilter _filter, BroadcastReceiver _receiver) {
filter = _filter;
receiver = _receiver;
}
#Override
public String toString() {
StringBuilder builder = new StringBuilder(128);
builder.append("Receiver{");
builder.append(receiver);
builder.append(" filter=");
builder.append(filter);
if (dead) {
builder.append(" DEAD");
}
builder.append("}");
return builder.toString();
}
}
private static final class BroadcastRecord {
final Intent intent;
final ArrayList<ReceiverRecord> receivers;
BroadcastRecord(Intent _intent, ArrayList<ReceiverRecord> _receivers) {
intent = _intent;
receivers = _receivers;
}
}
private static final String TAG = "LocalBroadcastManager";
private static final boolean DEBUG = false;
private final Context mAppContext;
private final HashMap<BroadcastReceiver, ArrayList<ReceiverRecord>> mReceivers
= new HashMap<>();
private final HashMap<String, ArrayList<ReceiverRecord>> mActions = new HashMap<>();
private final ArrayList<BroadcastRecord> mPendingBroadcasts = new ArrayList<>();
static final int MSG_EXEC_PENDING_BROADCASTS = 1;
private final Handler mHandler;
private static final Object mLock = new Object();
private static LocalBroadcastManager mInstance;
#NonNull
public static LocalBroadcastManager getInstance(#NonNull Context context) {
synchronized (mLock) {
if (mInstance == null) {
mInstance = new LocalBroadcastManager(context.getApplicationContext());
}
return mInstance;
}
}
private LocalBroadcastManager(Context context) {
mAppContext = context;
mHandler = new Handler(context.getMainLooper()) {
#Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_EXEC_PENDING_BROADCASTS:
executePendingBroadcasts();
break;
default:
super.handleMessage(msg);
}
}
};
}
/**
* Register a receive for any local broadcasts that match the given IntentFilter.
*
* #param receiver The BroadcastReceiver to handle the broadcast.
* #param filter Selects the Intent broadcasts to be received.
*
* #see #unregisterReceiver
*/
public void registerReceiver(#NonNull BroadcastReceiver receiver,
#NonNull IntentFilter filter) {
synchronized (mReceivers) {
ReceiverRecord entry = new ReceiverRecord(filter, receiver);
ArrayList<ReceiverRecord> filters = mReceivers.get(receiver);
if (filters == null) {
filters = new ArrayList<>(1);
mReceivers.put(receiver, filters);
}
filters.add(entry);
for (int i=0; i<filter.countActions(); i++) {
String action = filter.getAction(i);
ArrayList<ReceiverRecord> entries = mActions.get(action);
if (entries == null) {
entries = new ArrayList<ReceiverRecord>(1);
mActions.put(action, entries);
}
entries.add(entry);
}
}
}
/**
* Unregister a previously registered BroadcastReceiver. <em>All</em>
* filters that have been registered for this BroadcastReceiver will be
* removed.
*
* #param receiver The BroadcastReceiver to unregister.
*
* #see #registerReceiver
*/
public void unregisterReceiver(#NonNull BroadcastReceiver receiver) {
synchronized (mReceivers) {
final ArrayList<ReceiverRecord> filters = mReceivers.remove(receiver);
if (filters == null) {
return;
}
for (int i=filters.size()-1; i>=0; i--) {
final ReceiverRecord filter = filters.get(i);
filter.dead = true;
for (int j=0; j<filter.filter.countActions(); j++) {
final String action = filter.filter.getAction(j);
final ArrayList<ReceiverRecord> receivers = mActions.get(action);
if (receivers != null) {
for (int k=receivers.size()-1; k>=0; k--) {
final ReceiverRecord rec = receivers.get(k);
if (rec.receiver == receiver) {
rec.dead = true;
receivers.remove(k);
}
}
if (receivers.size() <= 0) {
mActions.remove(action);
}
}
}
}
}
}
/**
* Broadcast the given intent to all interested BroadcastReceivers. This
* call is asynchronous; it returns immediately, and you will continue
* executing while the receivers are run.
*
* #param intent The Intent to broadcast; all receivers matching this
* Intent will receive the broadcast.
*
* #see #registerReceiver
*
* #return Returns true if the intent has been scheduled for delivery to one or more
* broadcast receivers. (Note tha delivery may not ultimately take place if one of those
* receivers is unregistered before it is dispatched.)
*/
public boolean sendBroadcast(#NonNull Intent intent) {
synchronized (mReceivers) {
final String action = intent.getAction();
final String type = intent.resolveTypeIfNeeded(
mAppContext.getContentResolver());
final Uri data = intent.getData();
final String scheme = intent.getScheme();
final Set<String> categories = intent.getCategories();
final boolean debug = DEBUG ||
((intent.getFlags() & Intent.FLAG_DEBUG_LOG_RESOLUTION) != 0);
if (debug) Log.v(
TAG, "Resolving type " + type + " scheme " + scheme
+ " of intent " + intent);
ArrayList<ReceiverRecord> entries = mActions.get(intent.getAction());
if (entries != null) {
if (debug) Log.v(TAG, "Action list: " + entries);
ArrayList<ReceiverRecord> receivers = null;
for (int i=0; i<entries.size(); i++) {
ReceiverRecord receiver = entries.get(i);
if (debug) Log.v(TAG, "Matching against filter " + receiver.filter);
if (receiver.broadcasting) {
if (debug) {
Log.v(TAG, " Filter's target already added");
}
continue;
}
int match = receiver.filter.match(action, type, scheme, data,
categories, "LocalBroadcastManager");
if (match >= 0) {
if (debug) Log.v(TAG, " Filter matched! match=0x" +
Integer.toHexString(match));
if (receivers == null) {
receivers = new ArrayList<ReceiverRecord>();
}
receivers.add(receiver);
receiver.broadcasting = true;
} else {
if (debug) {
String reason;
switch (match) {
case IntentFilter.NO_MATCH_ACTION: reason = "action"; break;
case IntentFilter.NO_MATCH_CATEGORY: reason = "category"; break;
case IntentFilter.NO_MATCH_DATA: reason = "data"; break;
case IntentFilter.NO_MATCH_TYPE: reason = "type"; break;
default: reason = "unknown reason"; break;
}
Log.v(TAG, " Filter did not match: " + reason);
}
}
}
if (receivers != null) {
for (int i=0; i<receivers.size(); i++) {
receivers.get(i).broadcasting = false;
}
mPendingBroadcasts.add(new BroadcastRecord(intent, receivers));
if (!mHandler.hasMessages(MSG_EXEC_PENDING_BROADCASTS)) {
mHandler.sendEmptyMessage(MSG_EXEC_PENDING_BROADCASTS);
}
return true;
}
}
}
return false;
}
/**
* Like {#link #sendBroadcast(Intent)}, but if there are any receivers for
* the Intent this function will block and immediately dispatch them before
* returning.
*/
public void sendBroadcastSync(#NonNull Intent intent) {
if (sendBroadcast(intent)) {
executePendingBroadcasts();
}
}
#SuppressWarnings("WeakerAccess") /* synthetic access */
void executePendingBroadcasts() {
while (true) {
final BroadcastRecord[] brs;
synchronized (mReceivers) {
final int N = mPendingBroadcasts.size();
if (N <= 0) {
return;
}
brs = new BroadcastRecord[N];
mPendingBroadcasts.toArray(brs);
mPendingBroadcasts.clear();
}
for (int i=0; i<brs.length; i++) {
final BroadcastRecord br = brs[i];
final int nbr = br.receivers.size();
for (int j=0; j<nbr; j++) {
final ReceiverRecord rec = br.receivers.get(j);
if (!rec.dead) {
rec.receiver.onReceive(mAppContext, br.intent);
}
}
}
}
}
}
Any help is appreciated. Thanks.
You can use LiveData by creating a ViewModel class as below
class MainViewModel : ViewModel() {
private val _text: MutableLiveData<String> = MutableLiveData()
val text: LiveData<String> get() = _text
fun setText(newText: String) {
_text.value = newText
}
}
And in MainActivity your code will be as
class MainActivity : AppCompatActivity() {
private val viewModel by viewModels<MainViewModel>()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val textview = findViewById<TextView>(R.id.text_view)
val go = findViewById<Button>(R.id.go)
viewModel.text.observe(this) {
textview.text = it
}
// todo
go.setOnClickListener {
viewModel.setText("Week")
}
}}
I used this android-vision project to scan a barcode. When the camera detects the barcode, I am currently required to manually tap to capture it. But, I want to change the code a little bit so that it is captured automatically when the barcode is detected. How can I do this? This is my BarcodeCaptureActivity class:
package com.example.fs.barcodescanner;
import android.Manifest;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.hardware.Camera;
import android.os.Build;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.design.widget.Snackbar;
import android.support.v4.app.ActivityCompat;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.ScaleGestureDetector;
import android.view.View;
import android.view.WindowId;
import android.widget.Toast;
import com.example.fs.barcodescanner.ui.camera.CameraSource;
import com.example.fs.barcodescanner.ui.camera.CameraSourcePreview;
import com.example.fs.barcodescanner.ui.camera.GraphicOverlay;
import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.GoogleApiAvailability;
import com.google.android.gms.common.api.CommonStatusCodes;
import com.google.android.gms.vision.MultiProcessor;
import com.google.android.gms.vision.barcode.Barcode;
import com.google.android.gms.vision.barcode.BarcodeDetector;
import java.io.IOException;
public final class BarcodeCaptureActivity extends AppCompatActivity {
private static final String TAG = "Barcode-reader";
// intent request code to handle updating play services if needed.
private static final int RC_HANDLE_GMS = 9001;
// permission request codes need to be < 256
private static final int RC_HANDLE_CAMERA_PERM = 2;
// constants used to pass extra data in the intent
public static final String AutoFocus = "AutoFocus";
public static final String UseFlash = "UseFlash";
public static final String BarcodeObject = "Barcode";
private CameraSource mCameraSource;
private CameraSourcePreview mPreview;
private GraphicOverlay<BarcodeGraphic> mGraphicOverlay;
// helper objects for detecting taps and pinches.
private ScaleGestureDetector scaleGestureDetector;
private GestureDetector gestureDetector;
private WindowId.FocusObserver fo;
/**
* Initializes the UI and creates the detector pipeline.
*/
#Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
setContentView(R.layout.activity_barcode_capture);
mPreview = (CameraSourcePreview) findViewById(R.id.preview);
mGraphicOverlay = (GraphicOverlay<BarcodeGraphic>) findViewById(R.id.graphicOverlay);
// read parameters from the intent used to launch the activity.
boolean autoFocus = getIntent().getBooleanExtra(AutoFocus, false);
boolean useFlash = getIntent().getBooleanExtra(UseFlash, false);
// Check for the camera permission before accessing the camera. If the
// permission is not granted yet, request permission.
int rc = ActivityCompat.checkSelfPermission(this, Manifest.permission.CAMERA);
if (rc == PackageManager.PERMISSION_GRANTED) {
createCameraSource(autoFocus, useFlash);
} else {
requestCameraPermission();
}
gestureDetector = new GestureDetector(this, new CaptureGestureListener());
scaleGestureDetector = new ScaleGestureDetector(this, new ScaleListener());
Snackbar.make(mGraphicOverlay, "Tap to capture. Pinch/Stretch to zoom",
Snackbar.LENGTH_LONG)
.show();
}
/**
* Handles the requesting of the camera permission. This includes
* showing a "Snackbar" message of why the permission is needed then
* sending the request.
*/
private void requestCameraPermission() {
Log.w(TAG, "Camera permission is not granted. Requesting permission");
final String[] permissions = new String[]{Manifest.permission.CAMERA};
if (!ActivityCompat.shouldShowRequestPermissionRationale(this,
Manifest.permission.CAMERA)) {
ActivityCompat.requestPermissions(this, permissions, RC_HANDLE_CAMERA_PERM);
return;
}
final Activity thisActivity = this;
View.OnClickListener listener = new View.OnClickListener() {
#Override
public void onClick(View view) {
ActivityCompat.requestPermissions(thisActivity, permissions,
RC_HANDLE_CAMERA_PERM);
}
};
Snackbar.make(mGraphicOverlay, R.string.permission_camera_rationale,
Snackbar.LENGTH_INDEFINITE)
.setAction(R.string.ok, listener)
.show();
}
#Override
public boolean onTouchEvent(MotionEvent e) {
boolean b = scaleGestureDetector.onTouchEvent(e);
System.out.println("scale gesture"+b);
boolean c = gestureDetector.onTouchEvent(e);
System.out.println("tap gesture"+c);
return b || c || super.onTouchEvent(e);
}
/**
* Creates and starts the camera. Note that this uses a higher resolution in comparison
* to other detection examples to enable the barcode detector to detect small barcodes
* at long distances.
*
* Suppressing InlinedApi since there is a check that the minimum version is met before using
* the constant.
*/
#SuppressLint("InlinedApi")
private void createCameraSource(boolean autoFocus, boolean useFlash) {
Context context = getApplicationContext();
// A barcode detector is created to track barcodes. An associated multi-processor instance
// is set to receive the barcode detection results, track the barcodes, and maintain
// graphics for each barcode on screen. The factory is used by the multi-processor to
// create a separate tracker instance for each barcode.
BarcodeDetector barcodeDetector = new BarcodeDetector.Builder(context).build();
BarcodeTrackerFactory barcodeFactory = new BarcodeTrackerFactory(mGraphicOverlay);
barcodeDetector.setProcessor(
new MultiProcessor.Builder<>(barcodeFactory).build());
if (!barcodeDetector.isOperational()) {
// Note: The first time that an app using the barcode or face API is installed on a
// device, GMS will download a native libraries to the device in order to do detection.
// Usually this completes before the app is run for the first time. But if that
// download has not yet completed, then the above call will not detect any barcodes
// and/or faces.
//
// isOperational() can be used to check if the required native libraries are currently
// available. The detectors will automatically become operational once the library
// downloads complete on device.
Log.w(TAG, "Detector dependencies are not yet available.");
// Check for low storage. If there is low storage, the native library will not be
// downloaded, so detection will not become operational.
IntentFilter lowstorageFilter = new IntentFilter(Intent.ACTION_DEVICE_STORAGE_LOW);
boolean hasLowStorage = registerReceiver(null, lowstorageFilter) != null;
if (hasLowStorage) {
Toast.makeText(this, R.string.low_storage_error, Toast.LENGTH_LONG).show();
Log.w(TAG, getString(R.string.low_storage_error));
}
}
// Creates and starts the camera. Note that this uses a higher resolution in comparison
// to other detection examples to enable the barcode detector to detect small barcodes
// at long distances.
CameraSource.Builder builder = new CameraSource.Builder(getApplicationContext(), barcodeDetector)
.setFacing(CameraSource.CAMERA_FACING_BACK)
.setRequestedPreviewSize(1600, 1024)
.setRequestedFps(15.0f);
// make sure that auto focus is an available option
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
builder = builder.setFocusMode(
autoFocus ? Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE : null);
}
mCameraSource = builder
.setFlashMode(useFlash ? Camera.Parameters.FLASH_MODE_TORCH : null)
.build();
}
/**
* Restarts the camera.
*/
#Override
protected void onResume() {
super.onResume();
startCameraSource();
}
/**
* Stops the camera.
*/
#Override
protected void onPause() {
super.onPause();
if (mPreview != null) {
mPreview.stop();
}
}
/**
* Releases the resources associated with the camera source, the associated detectors, and the
* rest of the processing pipeline.
*/
#Override
protected void onDestroy() {
super.onDestroy();
if (mPreview != null) {
mPreview.release();
}
}
/**
* Callback for the result from requesting permissions. This method
* is invoked for every call on {#link #requestPermissions(String[], int)}.
* <p>
* <strong>Note:</strong> It is possible that the permissions request interaction
* with the user is interrupted. In this case you will receive empty permissions
* and results arrays which should be treated as a cancellation.
* </p>
*
* #param requestCode The request code passed in {#link #requestPermissions(String[], int)}.
* #param permissions The requested permissions. Never null.
* #param grantResults The grant results for the corresponding permissions
* which is either {#link PackageManager#PERMISSION_GRANTED}
* or {#link PackageManager#PERMISSION_DENIED}. Never null.
* #see #requestPermissions(String[], int)
*/
#Override
public void onRequestPermissionsResult(int requestCode,
#NonNull String[] permissions,
#NonNull int[] grantResults) {
if (requestCode != RC_HANDLE_CAMERA_PERM) {
Log.d(TAG, "Got unexpected permission result: " + requestCode);
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
return;
}
if (grantResults.length != 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
Log.d(TAG, "Camera permission granted - initialize the camera source");
// we have permission, so create the camerasource
boolean autoFocus = getIntent().getBooleanExtra(AutoFocus,false);
boolean useFlash = getIntent().getBooleanExtra(UseFlash, false);
createCameraSource(autoFocus, useFlash);
return;
}
Log.e(TAG, "Permission not granted: results len = " + grantResults.length +
" Result code = " + (grantResults.length > 0 ? grantResults[0] : "(empty)"));
DialogInterface.OnClickListener listener = new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
finish();
}
};
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle("Multitracker sample")
.setMessage(R.string.no_camera_permission)
.setPositiveButton(R.string.ok, listener)
.show();
}
/**
* Starts or restarts the camera source, if it exists. If the camera source doesn't exist yet
* (e.g., because onResume was called before the camera source was created), this will be called
* again when the camera source is created.
*/
private void startCameraSource() throws SecurityException {
// check that the device has play services available.
int code = GoogleApiAvailability.getInstance().isGooglePlayServicesAvailable(
getApplicationContext());
if (code != ConnectionResult.SUCCESS) {
Dialog dlg =
GoogleApiAvailability.getInstance().getErrorDialog(this, code, RC_HANDLE_GMS);
dlg.show();
}
if (mCameraSource != null) {
try {
mPreview.start(mCameraSource, mGraphicOverlay);
} catch (IOException e) {
Log.e(TAG, "Unable to start camera source.", e);
mCameraSource.release();
mCameraSource = null;
}
}
}
/**
* onTap is called to capture the oldest barcode currently detected and
* return it to the caller.
*
* #param rawX - the raw position of the tap
* #param rawY - the raw position of the tap.
* #return true if the activity is ending.
*/
private boolean onTap(float rawX, float rawY) {
//TODO: use the tap position to select the barcode.
BarcodeGraphic graphic = mGraphicOverlay.getFirstGraphic();
Barcode barcode = null;
if (graphic != null) {
barcode = graphic.getBarcode();
if (barcode != null) {
Intent data = new Intent();
data.putExtra(BarcodeObject, barcode);
setResult(CommonStatusCodes.SUCCESS, data);
finish();
}
else {
Log.d(TAG, "barcode data is null");
}
}
else {
Log.d(TAG,"no barcode detected");
}
return barcode != null;
}
private class CaptureGestureListener extends GestureDetector.SimpleOnGestureListener {
#Override
public boolean onSingleTapConfirmed(MotionEvent e) {
System.out.println(e.getRawX());
System.out.println(e.getRawY());
return onTap(e.getRawX(), e.getRawY()) || super.onSingleTapConfirmed(e);
}
}
private class ScaleListener implements ScaleGestureDetector.OnScaleGestureListener {
/**
* Responds to scaling events for a gesture in progress.
* Reported by pointer motion.
*
* #param detector The detector reporting the event - use this to
* retrieve extended info about event state.
* #return Whether or not the detector should consider this event
* as handled. If an event was not handled, the detector
* will continue to accumulate movement until an event is
* handled. This can be useful if an application, for example,
* only wants to update scaling factors if the change is
* greater than 0.01.
*/
#Override
public boolean onScale(ScaleGestureDetector detector) {
return false;
}
/**
* Responds to the beginning of a scaling gesture. Reported by
* new pointers going down.
*
* #param detector The detector reporting the event - use this to
* retrieve extended info about event state.
* #return Whether or not the detector should continue recognizing
* this gesture. For example, if a gesture is beginning
* with a focal point outside of a region where it makes
* sense, onScaleBegin() may return false to ignore the
* rest of the gesture.
*/
#Override
public boolean onScaleBegin(ScaleGestureDetector detector) {
return true;
}
/**
* Responds to the end of a scale gesture. Reported by existing
* pointers going up.
* <p/>
* Once a scale has ended, {#link ScaleGestureDetector#getFocusX()}
* and {#link ScaleGestureDetector#getFocusY()} will return focal point
* of the pointers remaining on the screen.
*
* #param detector The detector reporting the event - use this to
* retrieve extended info about event state.
*/
#Override
public void onScaleEnd(ScaleGestureDetector detector) {
mCameraSource.doZoom(detector.getScaleFactor());
}
}
}
Use interfaces.
Steps to do:
Create interface, like this:
public interface QRCodeDetectedInterface {
void onQRCodeDetected();}
Implement it in BarcodeCaptureActivity, like this :
public final class BarcodeCaptureActivity extends AppCompatActivity implements QRCodeDetectedInterface{
#Override
public void onQRCodeDetected() {
BarcodeGraphic graphic = mGraphicOverlay.getFirstGraphic();
Barcode barcode = null;
if (graphic != null) {
barcode = graphic.getBarcode();
if (barcode != null) {
Intent data = new Intent();
data.putExtra(BarcodeObject, barcode);
setResult(CommonStatusCodes.SUCCESS, data);
finish();
}
else {
Log.d(TAG, "barcode data is null");
}
}
else {
Log.d(TAG,"no barcode detected");
}
}
}
And use it in BarcodeGraphic.java file, like this:
private Context context;
private QRCodeDetectedInterface mCallback;
BarcodeGraphic(GraphicOverlay overlay) {
super(overlay);
this.context = overlay.getContext();
mCallback = (BarcodeCaptureActivity) context;
}
And then:
#Override
public void draw(Canvas canvas) {
mCallback.onQRCodeDetected();}
As #Lynx said, you need to create an interface. I was in need of something like that. I created an interface to listen when the camera tracks the barcode and implemented that in the activity that will listen.
In BarcodeGraphicTracker.java class, This is the interface that I created.
public interface BarcodeDetectorListener{
//event call back
void onObjectDetected(Barcode data);
}
#Override
public void onNewItem(int id, Barcode item) {
mGraphic.setId(id);
if(mBarcodeDetectorListener == null) return;
mBarcodeDetectorListener.onObjectDetected(item);
mBarcodeDetectorListener = null;
}
in BarcodeScannerActivty
#Override
public void onObjectDetected(Barcode data) {
//do something with the barcode data here
Intent mIntent = new Intent();
mIntent.putExtra(BarcodeObject, data);
setResult(CommonStatusCodes.SUCCESS, mIntent);
finish();
}
I have uploaded a sample in github. Please find it here.
package com.example.fs.barcodescanner;
import android.Manifest;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.hardware.Camera;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.design.widget.Snackbar;
import android.support.v4.app.ActivityCompat;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.ScaleGestureDetector;
import android.view.View;
import android.widget.Toast;
import com.example.fs.barcodescanner.ui.camera.CameraSource;
import com.example.fs.barcodescanner.ui.camera.CameraSourcePreview;
import com.example.fs.barcodescanner.ui.camera.GraphicOverlay;
import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.GoogleApiAvailability;
import com.google.android.gms.common.api.CommonStatusCodes;
import com.google.android.gms.vision.MultiProcessor;
import com.google.android.gms.vision.barcode.Barcode;
import com.google.android.gms.vision.barcode.BarcodeDetector;
import java.io.IOException;
public final class BarcodeCaptureActivity extends AppCompatActivity {
private static final String TAG = "Barcode-reader";
// intent request code to handle updating play services if needed.
private static final int RC_HANDLE_GMS = 9001;
// permission request codes need to be < 256
private static final int RC_HANDLE_CAMERA_PERM = 2;
// constants used to pass extra data in the intent
public static final String AutoFocus = "AutoFocus";
public static final String UseFlash = "UseFlash";
public static final String BarcodeObject = "Barcode";
private CameraSource mCameraSource;
private CameraSourcePreview mPreview;
private GraphicOverlay<BarcodeGraphic> mGraphicOverlay;
// helper objects for detecting taps and pinches.
private ScaleGestureDetector scaleGestureDetector;
private GestureDetector gestureDetector;
/**
* Initializes the UI and creates the detector pipeline.
*/
#Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
setContentView(R.layout.activity_barcode_capture);
mPreview = (CameraSourcePreview) findViewById(R.id.preview);
mGraphicOverlay = (GraphicOverlay<BarcodeGraphic>) findViewById(R.id.graphicOverlay);
// read parameters from the intent used to launch the activity.
boolean autoFocus = getIntent().getBooleanExtra(AutoFocus, false);
boolean useFlash = getIntent().getBooleanExtra(UseFlash, false);
// Check for the camera permission before accessing the camera. If the
// permission is not granted yet, request permission.
int rc = ActivityCompat.checkSelfPermission(this, Manifest.permission.CAMERA);
if (rc == PackageManager.PERMISSION_GRANTED) {
createCameraSource();
} else {
requestCameraPermission();
}
gestureDetector = new GestureDetector(this, new CaptureGestureListener());
scaleGestureDetector = new ScaleGestureDetector(this, new ScaleListener());
Snackbar.make(mGraphicOverlay, "Tap to capture. Pinch/Stretch to zoom",
Snackbar.LENGTH_LONG)
.show();
}
/**
* Handles the requesting of the camera permission. This includes
* showing a "Snackbar" message of why the permission is needed then
* sending the request.
*/
private void requestCameraPermission() {
Log.w(TAG, "Camera permission is not granted. Requesting permission");
final String[] permissions = new String[]{Manifest.permission.CAMERA};
if (!ActivityCompat.shouldShowRequestPermissionRationale(this,
Manifest.permission.CAMERA)) {
ActivityCompat.requestPermissions(this, permissions, RC_HANDLE_CAMERA_PERM);
return;
}
final Activity thisActivity = this;
View.OnClickListener listener = new View.OnClickListener() {
#Override
public void onClick(View view) {
ActivityCompat.requestPermissions(thisActivity, permissions,
RC_HANDLE_CAMERA_PERM);
}
};
Snackbar.make(mGraphicOverlay, R.string.permission_camera_rationale,
Snackbar.LENGTH_INDEFINITE)
.setAction(R.string.ok, listener)
.show();
}
#Override
public boolean onTouchEvent(MotionEvent e) {
boolean b = scaleGestureDetector.onTouchEvent(e);
System.out.println("scale gesture" + b);
boolean c = gestureDetector.onTouchEvent(e);
System.out.println("tap gesture" + c);
return b || c || super.onTouchEvent(e);
}
/**
* Creates and starts the camera. Note that this uses a higher resolution in comparison
* to other detection examples to enable the barcode detector to detect small barcodes
* at long distances.
* <p>
* Suppressing InlinedApi since there is a check that the minimum version is met before using
* the constant.
*/
#SuppressLint("InlinedApi")
private void createCameraSource() {
Context context = getApplicationContext();
// A barcode detector is created to track barcodes. An associated multi-processor instance
// is set to receive the barcode detection results, track the barcodes, and maintain
// graphics for each barcode on screen. The factory is used by the multi-processor to
// create a separate tracker instance for each barcode.
BarcodeDetector barcodeDetector = new BarcodeDetector.Builder(context).build();
BarcodeTrackerFactory barcodeFactory = new BarcodeTrackerFactory(mGraphicOverlay);
barcodeDetector.setProcessor(
new MultiProcessor.Builder<>(barcodeFactory).build());
if (!barcodeDetector.isOperational()) {
// Note: The first time that an app using the barcode or face API is installed on a
// device, GMS will download a native libraries to the device in order to do detection.
// Usually this completes before the app is run for the first time. But if that
// download has not yet completed, then the above call will not detect any barcodes
// and/or faces.
//
// isOperational() can be used to check if the required native libraries are currently
// available. The detectors will automatically become operational once the library
// downloads complete on device.
Log.w(TAG, "Detector dependencies are not yet available.");
// Check for low storage. If there is low storage, the native library will not be
// downloaded, so detection will not become operational.
IntentFilter lowstorageFilter = new IntentFilter(Intent.ACTION_DEVICE_STORAGE_LOW);
boolean hasLowStorage = registerReceiver(null, lowstorageFilter) != null;
if (hasLowStorage) {
Toast.makeText(this, R.string.low_storage_error, Toast.LENGTH_LONG).show();
Log.w(TAG, getString(R.string.low_storage_error));
}
}
// Creates and starts the camera. Note that this uses a higher resolution in comparison
// to other detection examples to enable the barcode detector to detect small barcodes
// at long distances.
CameraSource.Builder builder = new CameraSource.Builder(getApplicationContext(), barcodeDetector)
.setFacing(CameraSource.CAMERA_FACING_BACK)
.setRequestedPreviewSize(1600, 1024)
.setRequestedFps(15.0f)
.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE);
// make sure that auto focus is an available option
// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
// builder = builder.setFocusMode(
// autoFocus ? Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE : null);
// }
mCameraSource = builder
//.setFlashMode(useFlash ? Camera.Parameters.FLASH_MODE_TORCH : null)
.build();
mCameraSource.autoFocus(new CameraSource.AutoFocusCallback() {
#Override
public void onAutoFocus(boolean success) {
System.out.println("Hello");
}
});
}
/**
* Restarts the camera.
*/
#Override
protected void onResume() {
super.onResume();
startCameraSource();
}
/**
* Stops the camera.
*/
#Override
protected void onPause() {
super.onPause();
if (mPreview != null) {
mPreview.stop();
}
}
/**
* Releases the resources associated with the camera source, the associated detectors, and the
* rest of the processing pipeline.
*/
#Override
protected void onDestroy() {
super.onDestroy();
if (mPreview != null) {
mPreview.release();
}
}
/**
* Callback for the result from requesting permissions. This method
* is invoked for every call on {#link #requestPermissions(String[], int)}.
* <p>
* <strong>Note:</strong> It is possible that the permissions request interaction
* with the user is interrupted. In this case you will receive empty permissions
* and results arrays which should be treated as a cancellation.
* </p>
*
* #param requestCode The request code passed in {#link #requestPermissions(String[], int)}.
* #param permissions The requested permissions. Never null.
* #param grantResults The grant results for the corresponding permissions
* which is either {#link PackageManager#PERMISSION_GRANTED}
* or {#link PackageManager#PERMISSION_DENIED}. Never null.
* #see #requestPermissions(String[], int)
*/
#Override
public void onRequestPermissionsResult(int requestCode,
#NonNull String[] permissions,
#NonNull int[] grantResults) {
if (requestCode != RC_HANDLE_CAMERA_PERM) {
Log.d(TAG, "Got unexpected permission result: " + requestCode);
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
return;
}
if (grantResults.length != 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
Log.d(TAG, "Camera permission granted - initialize the camera source");
// we have permission, so create the camerasource
boolean autoFocus = getIntent().getBooleanExtra(AutoFocus,false);
boolean useFlash = getIntent().getBooleanExtra(UseFlash, false);
createCameraSource();
return;
}
Log.e(TAG, "Permission not granted: results len = " + grantResults.length +
" Result code = " + (grantResults.length > 0 ? grantResults[0] : "(empty)"));
DialogInterface.OnClickListener listener = new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
finish();
}
};
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle("Multitracker sample")
.setMessage(R.string.no_camera_permission)
.setPositiveButton(R.string.ok, listener)
.show();
}
/**
* Starts or restarts the camera source, if it exists. If the camera source doesn't exist yet
* (e.g., because onResume was called before the camera source was created), this will be called
* again when the camera source is created.
*/
private void startCameraSource() throws SecurityException {
// check that the device has play services available.
int code = GoogleApiAvailability.getInstance().isGooglePlayServicesAvailable(
getApplicationContext());
if (code != ConnectionResult.SUCCESS) {
Dialog dlg =
GoogleApiAvailability.getInstance().getErrorDialog(this, code, RC_HANDLE_GMS);
dlg.show();
}
if (mCameraSource != null) {
try {
mPreview.start(mCameraSource, mGraphicOverlay);
} catch (IOException e) {
Log.e(TAG, "Unable to start camera source.", e);
mCameraSource.release();
mCameraSource = null;
}
}
}
/**
* onTap is called to capture the oldest barcode currently detected and
* return it to the caller.
*
* #param rawX - the raw position of the tap
* #param rawY - the raw position of the tap.
* #return true if the activity is ending.
*/
private boolean onTap(float rawX, float rawY) {
//TODO: use the tap position to select the barcode.
BarcodeGraphic graphic = mGraphicOverlay.getFirstGraphic();
Barcode barcode = null;
if (graphic != null) {
barcode = graphic.getBarcode();
if (barcode != null) {
Intent data = new Intent();
data.putExtra(BarcodeObject, barcode);
setResult(CommonStatusCodes.SUCCESS, data);
finish();
} else {
Log.d(TAG, "barcode data is null");
}
} else {
Log.d(TAG, "no barcode detected");
}
return barcode != null;
}
private class CaptureGestureListener extends GestureDetector.SimpleOnGestureListener {
#Override
public boolean onSingleTapConfirmed(MotionEvent e) {
System.out.println(e.getRawX());
System.out.println(e.getRawY());
return onTap(e.getRawX(), e.getRawY()) || super.onSingleTapConfirmed(e);
}
}
private class ScaleListener implements ScaleGestureDetector.OnScaleGestureListener {
/**
* Responds to scaling events for a gesture in progress.
* Reported by pointer motion.
*
* #param detector The detector reporting the event - use this to
* retrieve extended info about event state.
* #return Whether or not the detector should consider this event
* as handled. If an event was not handled, the detector
* will continue to accumulate movement until an event is
* handled. This can be useful if an application, for example,
* only wants to update scaling factors if the change is
* greater than 0.01.
*/
#Override
public boolean onScale(ScaleGestureDetector detector) {
return false;
}
/**
* Responds to the beginning of a scaling gesture. Reported by
* new pointers going down.
*
* #param detector The detector reporting the event - use this to
* retrieve extended info about event state.
* #return Whether or not the detector should continue recognizing
* this gesture. For example, if a gesture is beginning
* with a focal point outside of a region where it makes
* sense, onScaleBegin() may return false to ignore the
* rest of the gesture.
*/
#Override
public boolean onScaleBegin(ScaleGestureDetector detector) {
return true;
}
/**
* Responds to the end of a scale gesture. Reported by existing
* pointers going up.
* <p/>
* Once a scale has ended, {#link ScaleGestureDetector#getFocusX()}
* and {#link ScaleGestureDetector#getFocusY()} will return focal point
* of the pointers remaining on the screen.
*
* #param detector The detector reporting the event - use this to
* retrieve extended info about event state.
*/
#Override
public void onScaleEnd(ScaleGestureDetector detector) {
mCameraSource.doZoom(detector.getScaleFactor());
}
}
}
I am getting java.lang.NoClassDefFoundError in android studio ,when i add a new class in my project that class is not detected by the rest of the code(It is not giving any compile time errors by not working as expected).
In this case I have added a UtilDateTime class which is giving java.lang.NoClassDefFoundError. Please help me out this probably does not seem to be a coding problem but something related to the libraries I am using(Though i am not sure of the sorce of problem).
The Log is showing this :-
08-08 17:03:36.832 19979-20179/com.indiacom.salondemo E/AndroidRuntime﹕ FATAL EXCEPTION: AsyncTask #3
java.lang.RuntimeException: An error occured while executing doInBackground()
at android.os.AsyncTask$3.done(AsyncTask.java:299)
at java.util.concurrent.FutureTask.finishCompletion(FutureTask.java:352)
at java.util.concurrent.FutureTask.setException(FutureTask.java:219)
at java.util.concurrent.FutureTask.run(FutureTask.java:239)
at android.os.AsyncTask$SerialExecutor$1.run(AsyncTask.java:230)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1080)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:573)
at java.lang.Thread.run(Thread.java:838)
Caused by: java.lang.NoClassDefFoundError: com.indiacom.salondemo.utilities.UtilDateTime
at com.indiacom.salondemo.asynctask.CalendarApiAsyncTask.getDataFromApi(CalendarApiAsyncTask.java:86)
at com.indiacom.salondemo.asynctask.CalendarApiAsyncTask.doInBackground(CalendarApiAsyncTask.java:45)
at com.indiacom.salondemo.asynctask.CalendarApiAsyncTask.doInBackground(CalendarApiAsyncTask.java:22)
at android.os.AsyncTask$2.call(AsyncTask.java:287)
at java.util.concurrent.FutureTask.run(FutureTask.java:234)
at
android.os.AsyncTask$SerialExecutor$1.run(AsyncTask.java:230)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1080)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:573)
at java.lang.Thread.run(Thread.java:838)
This is my code :-
AppointmentActivity class:-
package com.indiacom.salondemo;
import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.GooglePlayServicesUtil;
import com.google.api.client.extensions.android.http.AndroidHttp;
import com.google.api.client.googleapis.extensions.android.gms.auth.GoogleAccountCredential;
import com.google.api.client.http.HttpTransport;
import com.google.api.client.json.JsonFactory;
import com.google.api.client.json.gson.GsonFactory;
import com.google.api.client.util.ExponentialBackOff;
import com.google.api.services.calendar.CalendarScopes;
import com.indiacom.salondemo.asynctask.CalendarApiAsyncTask;
import com.indiacom.salondemo.utilities.UtilDateTime;
import android.accounts.AccountManager;
import android.app.Activity;
import android.app.Dialog;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.graphics.Typeface;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.os.Bundle;
import android.text.TextUtils;
import android.text.method.ScrollingMovementMethod;
import android.util.Log;
import android.view.ViewGroup;
import android.widget.LinearLayout;
import android.widget.TextView;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class AppointmentActivity extends Activity {
/**
* A Google Calendar API service object used to access the API.
* Note: Do not confuse this class with API library's model classes, which
* represent specific data structures.
*/
public com.google.api.services.calendar.Calendar mService;
GoogleAccountCredential credential;
private TextView mStatusText;
private TextView mResultsText;
final HttpTransport transport = AndroidHttp.newCompatibleTransport();
final JsonFactory jsonFactory = GsonFactory.getDefaultInstance();
public static final int REQUEST_ACCOUNT_PICKER = 1000;
public static final int REQUEST_AUTHORIZATION = 1001;
static final int REQUEST_GOOGLE_PLAY_SERVICES = 1002;
private static final String PREF_ACCOUNT_NAME = "accountName";
private static final String[] SCOPES = { CalendarScopes.CALENDAR_READONLY };
/**
* Create the main activity.
* #param savedInstanceState previously saved instance data.
*/
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
LinearLayout activityLayout = new LinearLayout(this);
LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.MATCH_PARENT);
activityLayout.setLayoutParams(lp);
activityLayout.setOrientation(LinearLayout.VERTICAL);
activityLayout.setPadding(16, 16, 16, 16);
ViewGroup.LayoutParams tlp = new ViewGroup.LayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.WRAP_CONTENT);
mStatusText = new TextView(this);
mStatusText.setLayoutParams(tlp);
mStatusText.setTypeface(null, Typeface.BOLD);
mStatusText.setText("Retrieving data...");
activityLayout.addView(mStatusText);
mResultsText = new TextView(this);
mResultsText.setLayoutParams(tlp);
mResultsText.setPadding(16, 16, 16, 16);
mResultsText.setVerticalScrollBarEnabled(true);
mResultsText.setMovementMethod(new ScrollingMovementMethod());
activityLayout.addView(mResultsText);
setContentView(activityLayout);
// Initialize credentials and service object.
SharedPreferences settings = getPreferences(Context.MODE_PRIVATE);
credential = GoogleAccountCredential.usingOAuth2(
getApplicationContext(), Arrays.asList(SCOPES))
.setBackOff(new ExponentialBackOff())
.setSelectedAccountName(settings.getString(PREF_ACCOUNT_NAME, null));
mService = new com.google.api.services.calendar.Calendar.Builder(
transport, jsonFactory, credential)
.setApplicationName("Google Calendar API Android Quickstart")
.build();
}
/**
* Called whenever this activity is pushed to the foreground, such as after
* a call to onCreate().
*/
#Override
protected void onResume() {
super.onResume();
if (isGooglePlayServicesAvailable()) {
refreshResults();
} else {
mStatusText.setText("Google Play Services required: " +
"after installing, close and relaunch this app.");
}
}
/**
* Called when an activity launched here (specifically, AccountPicker
* and authorization) exits, giving you the requestCode you started it with,
* the resultCode it returned, and any additional data from it.
* #param requestCode code indicating which activity result is incoming.
* #param resultCode code indicating the result of the incoming
* activity result.
* #param data Intent (containing result data) returned by incoming
* activity result.
*/
#Override
protected void onActivityResult(
int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
switch(requestCode) {
case REQUEST_GOOGLE_PLAY_SERVICES:
if (resultCode != RESULT_OK) {
isGooglePlayServicesAvailable();
}
break;
case REQUEST_ACCOUNT_PICKER:
if (resultCode == RESULT_OK && data != null &&
data.getExtras() != null) {
String accountName =
data.getStringExtra(AccountManager.KEY_ACCOUNT_NAME);
if (accountName != null) {
credential.setSelectedAccountName(accountName);
SharedPreferences settings =
getPreferences(Context.MODE_PRIVATE);
SharedPreferences.Editor editor = settings.edit();
editor.putString(PREF_ACCOUNT_NAME, accountName);
editor.commit();
}
} else if (resultCode == RESULT_CANCELED) {
mStatusText.setText("Account unspecified.");
}
break;
case REQUEST_AUTHORIZATION:
if (resultCode != RESULT_OK) {
chooseAccount();
}
break;
}
super.onActivityResult(requestCode, resultCode, data);
}
/**
* Attempt to get a set of data from the Google Calendar API to display. If the
* email address isn't known yet, then call chooseAccount() method so the
* user can pick an account.
*/
private void refreshResults() {
if (credential.getSelectedAccountName() == null) {
chooseAccount();
} else {
if (isDeviceOnline()) {
new CalendarApiAsyncTask(this).execute();
} else {
mStatusText.setText("No network connection available.");
}
}
}
/**
* Clear any existing Google Calendar API data from the TextView and update
* the header message; called from background threads and async tasks
* that need to update the UI (in the UI thread).
*/
public void clearResultsText() {
runOnUiThread(new Runnable() {
#Override
public void run() {
mStatusText.setText("Retrieving data…");
mResultsText.setText("");
}
});
}
/**
* Fill the data TextView with the given List of Strings; called from
* background threads and async tasks that need to update the UI (in the
* UI thread).
* #param dataStrings a List of Strings to populate the main TextView with.
*/
public void updateResultsText(final ArrayList<UtilDateTime> dataStrings) {
runOnUiThread(new Runnable() {
#Override
public void run() {
if (dataStrings == null) {
mStatusText.setText("Error retrieving data!");
} else if (dataStrings.size() == 0) {
mStatusText.setText("No data found.");
}else{
mStatusText.setText("Data retrieved using" +
" the Google Calendar API:");
String sss=dataStrings.get(0).getmDate();
Log.d("Sample String:","String sss="+sss);
mResultsText.setText(sss);
}
}
});
}
/**
* Show a status message in the list header TextView; called from background
* threads and async tasks that need to update the UI (in the UI thread).
* #param message a String to display in the UI header TextView.
*/
public void updateStatus(final String message) {
runOnUiThread(new Runnable() {
#Override
public void run() {
mStatusText.setText(message);
}
});
}
/**
* Starts an activity in Google Play Services so the user can pick an
* account.
*/
private void chooseAccount() {
startActivityForResult(
credential.newChooseAccountIntent(), REQUEST_ACCOUNT_PICKER);
}
/**
* Checks whether the device currently has a network connection.
* #return true if the device has a network connection, false otherwise.
*/
private boolean isDeviceOnline() {
ConnectivityManager connMgr =
(ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo networkInfo = connMgr.getActiveNetworkInfo();
return (networkInfo != null && networkInfo.isConnected());
}
/**
* Check that Google Play services APK is installed and up to date. Will
* launch an error dialog for the user to update Google Play Services if
* possible.
* #return true if Google Play Services is available and up to
* date on this device; false otherwise.
*/
private boolean isGooglePlayServicesAvailable() {
final int connectionStatusCode =
GooglePlayServicesUtil.isGooglePlayServicesAvailable(this);
if (GooglePlayServicesUtil.isUserRecoverableError(connectionStatusCode)) {
showGooglePlayServicesAvailabilityErrorDialog(connectionStatusCode);
return false;
} else if (connectionStatusCode != ConnectionResult.SUCCESS ) {
return false;
}
return true;
}
/**
* Display an error dialog showing that Google Play Services is missing
* or out of date.
* #param connectionStatusCode code describing the presence (or lack of)
* Google Play Services on this device.
*/
public void showGooglePlayServicesAvailabilityErrorDialog(
final int connectionStatusCode) {
runOnUiThread(new Runnable() {
#Override
public void run() {
Dialog dialog = GooglePlayServicesUtil.getErrorDialog(
connectionStatusCode,
AppointmentActivity.this,
REQUEST_GOOGLE_PLAY_SERVICES);
dialog.show();
}
});
}
}
CalenderApiAsyncTask class :-
package com.indiacom.salondemo.asynctask;
import android.os.AsyncTask;
import android.util.Log;
import com.google.api.client.googleapis.extensions.android.gms.auth.GooglePlayServicesAvailabilityIOException;
import com.google.api.client.googleapis.extensions.android.gms.auth.UserRecoverableAuthIOException;
import com.google.api.client.util.DateTime;
import com.google.api.services.calendar.model.*;
import com.indiacom.salondemo.AppointmentActivity;
import com.indiacom.salondemo.AppointmentActivity;
import com.indiacom.salondemo.utilities.UtilDateTime;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
/**
* An asynchronous task that handles the Google Calendar API call.
* Placing the API calls in their own task ensures the UI stays responsive.
*/
public class CalendarApiAsyncTask extends AsyncTask<Void, Void, Void> {
private AppointmentActivity mActivity;
ArrayList<UtilDateTime> mArrList;
/**
* Constructor.
*
* #param activity MainActivity that spawned this task.
*/
public CalendarApiAsyncTask(AppointmentActivity activity) {
this.mActivity = activity;
}
/**
* Background task to call Google Calendar API.
*
* #param params no parameters needed for this task.
*/
#Override
protected Void doInBackground(Void... params) {
try {
mActivity.clearResultsText();
mActivity.updateResultsText(getDataFromApi());
} catch (final GooglePlayServicesAvailabilityIOException availabilityException) {
mActivity.showGooglePlayServicesAvailabilityErrorDialog(
availabilityException.getConnectionStatusCode());
} catch (UserRecoverableAuthIOException userRecoverableException) {
mActivity.startActivityForResult(
userRecoverableException.getIntent(),
AppointmentActivity.REQUEST_AUTHORIZATION);
} catch (Exception e) {
mActivity.updateStatus("The following error occurred:\n" +
e.getMessage());
}
return null;
}
/**
* Fetch a list of the next 10 events from the primary calendar.
*
* #return List of Strings describing returned events.
* #throws IOException
*/
private ArrayList<UtilDateTime> getDataFromApi() throws IOException {
// List the next 10 events from the primary calendar.
DateTime now = new DateTime(System.currentTimeMillis());
List<String> eventStrings = new ArrayList<String>();
Events events = mActivity.mService.events().list("primary")
.setMaxResults(10)
.setTimeMin(now)
.setOrderBy("startTime")
.setSingleEvents(true)
.execute();
List<Event> items = events.getItems();
mArrList= new ArrayList<UtilDateTime>();
for(int i=0;i<5;i++)
{
UtilDateTime objectForDateTime=new UtilDateTime();
objectForDateTime.setmDate("12-7-2015");
objectForDateTime.setmTime("12.00");
mArrList.add(objectForDateTime);
}
Log.e("arraylist::","list:"+mArrList.get(0).getmDate());
//
// for (Event event : items) {
// DateTime start = event.getStart().getDateTime();
// if (start == null) {
// // All-day events don't have start times, so just use
// // the start date.
// start = event.getStart().getDate();
// }
// eventStrings.add(
// String.format("%s (%s)", event.getSummary(), start));
// }
return mArrList;
}
}
UtilDateTime class :-
package com.indiacom.salondemo.utilities;
/**
* Created by cloud1 on 7/8/15.
*/
public class UtilDateTime {
String mDate, mTime;
public UtilDateTime()
{
}
public UtilDateTime(String mDate, String mTime) {
this.mDate = mDate;
this.mTime = mTime;
}
public String getmDate() {
return mDate;
}
public void setmDate(String mDate) {
this.mDate = mDate;
}
public String getmTime() {
return mTime;
}
public void setmTime(String mTime) {
this.mTime = mTime;
}
}
I am not able to download image using messenger in boundservice eventhough in android manifest showing service in separate process
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="edu.vuum.mocca"
android:versionCode="1"
android:versionName="1.0" >
<uses-permission android:name="android.permission.INTERNET"></uses-permission>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-sdk
android:minSdkVersion="14"
android:targetSdkVersion="19" />
<supports-screens android:smallScreens="false" android:xlargeScreens="true" android:normalScreens="true" android:largeScreens="true"/>
<application
android:allowBackup="true"
android:icon="#drawable/ic_launcher"
android:label="#string/app_name"
android:theme="#style/AppTheme"
android:uiOptions="splitActionBarWhenNarrow">
<activity
android:name="edu.vuum.mocca.DownloadActivity"
android:label="#string/app_name" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<service android:name="edu.vuum.mocca.DownloadService" android:process=":my_process"/>
</application>
</manifest>
Dowloadservice handler calling dowloadmessageandRespond it gives android.os.networkonmainthreadexception
But services handler runs in separate process so above execution shouldn't come?
If i do dowloadmessageandRespond method in separate thread then program runs fine which commented in dowloadmessageandRespond method.
package edu.vuum.mocca;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URL;
import android.app.Activity;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
import android.os.Messenger;
import android.os.RemoteException;
import android.util.Base64;
import android.util.Log;
/**
* #class DownloadService
*
* #brief DownloadService receives an Intent containing a URL (which
* is a type of URI) and a Messenger. It downloads the file at
* the URL, stores it on the file system, then returns the path
* name to the caller using the supplied Messenger.
*
* The DownloadService class implements the CommandProcessor
* pattern and The Messenger is used as part of the Active
* Object pattern.
*/
public class DownloadService extends Service
{
/**
* Used for debugging.
*/
private final String TAG = getClass().getName();
private final static int LOG_OP = 1;
/**
* Looper associated with the HandlerThread.
*/
// private volatile Looper mServiceLooper;
/**
* Implementation a Messenger that encapsulates the RequestHandler
* used to handle request Messages sent from the
* UniqueIDGeneratorActivity.
*/
private final static String MESSAGE_URL = "course.examples.Services.Logging.MESSAGE";
final Messenger mMessengerImpl =
new Messenger(new ServiceHandler());
/**
* Factory method to make the desired Intent.
*/
public static Intent makeIntent(Context context
) {
// Create the Intent that's associated to the DownloadService
// class.
Intent intent = new Intent(context,
DownloadService.class);
return intent;
}
/**
* #class ServiceHandler
*
* #brief An inner class that inherits from Handler and uses its
* handleMessage() hook method to process Messages sent to
* it from onStartCommnand() that indicate which images to
* download.
*/
private final class ServiceHandler extends Handler {
/**
* A factory method that creates a Message to return to the
* DownloadActivity with the pathname of the downloaded image.
*/
private Message makeReplyMessage(String pathname){
Message message = Message.obtain();
// Return the result to indicate whether the download
// succeeded or failed.
message.arg1 = pathname == null
? Activity.RESULT_CANCELED
: Activity.RESULT_OK;
Bundle bundle = new Bundle();
// Pathname for the downloaded image.
bundle.putString("PATHNAME",
pathname);
message.setData(bundle);
return message;
}
/**
* A factory method that creates a Message that contains
* information on the image to download and how to stop the
* Service.
*/
/* private Message makeDownloadMessage(Intent intent,
int startId){
Message message = Message.obtain();
// Include Intent & startId in Message to indicate which URI
// to retrieve and which request is being stopped when
// download completes.
message.obj = intent;
message.arg1 = startId;
return message;
}*/
/**
* Retrieves the designated image and reply to the
* DownloadActivity via the Messenger .
*/
private void downloadImageAndReply(Message message) {
final Messenger messenger = message.replyTo;
final String url=message.getData().getString(MESSAGE_URL);
// Download the requested image.
//final String pathname;
// new Thread( new Runnable() {
// public void run(){
String pathname = downloadImage(DownloadService.this,url);
Log.d(TAG, "pathname----- after downloadImage()"+pathname);
sendPath(messenger, pathname);
// }
// }).start();
}
/**
* Send the pathname back to the DownloadActivity via the
* messenger.
*/
private void sendPath(Messenger messenger,
String pathname) {
// Call factory method to create Message.
Message message = makeReplyMessage(pathname);
try {
if (messenger == null)
Log.d(TAG, "replyMessenger is null");
else {
Log.d(TAG, "sending key" + pathname);
// Send pathname to back to the DownloadActivity.
messenger.send(message);
}
} catch (RemoteException e) {
Log.e(getClass().getName(),
"Exception while sending.",
e);
}
}
/**
* Create a file to store the result of a download.
*
* #param context
* #param url
* #return
* #throws IOException
*/
private File getTemporaryFile(final Context context,
final String url) throws IOException {
return context.getFileStreamPath(Base64.encodeToString(url.getBytes(),
Base64.NO_WRAP)
+ System.currentTimeMillis());
}
/**
* Copy the contents of an InputStream into an OutputStream.
*
* #param in
* #param out
* #return
* #throws IOException
*/
private int copy(final InputStream in,
final OutputStream out) throws IOException {
final int BUFFER_LENGTH = 1024;
final byte[] buffer = new byte[BUFFER_LENGTH];
int totalRead = 0;
int read = 0;
while ((read = in.read(buffer)) != -1) {
out.write(buffer, 0, read);
totalRead += read;
}
return totalRead;
}
/**
* Download the requested image and return the local file path.
*
* #param context
* #param url
* #return
*/
public String downloadImage(final Context context,
final String url) {
try {
final File file = getTemporaryFile(context, url);
Log.d(getClass().getName(), " downloading to " + file);
final InputStream in = (InputStream)
new URL(url).getContent();
final OutputStream out =
new FileOutputStream(file);
copy(in, out);
in.close();
out.close();
return file.getAbsolutePath();
} catch (Exception e) {
Log.e(getClass().getName(),
"Exception while downloading. Returning null.");
Log.e(getClass().getName(),
e.toString());
e.printStackTrace();
return null;
}
}
/**
* Hook method that retrieves an image from a remote server.
*/
public void handleMessage( Message message) {
// Download the designated image and reply to the
// DownloadActivity via the Messenger sent with the
// Intent.
switch (message.what) {
case LOG_OP:
Log.i(TAG, message.getData().getString(MESSAGE_URL));
downloadImageAndReply(message);
break;
default:
super.handleMessage(message);
}
}
}
/**
* Hook method called when DownloadService is first launched by
* the Android ActivityManager.
*/
/* public void onCreate() {
super.onCreate();
// Create and start a background HandlerThread since by
// default a Service runs in the UI Thread, which we don't
// want to block.
HandlerThread thread =
new HandlerThread("DownloadService");
thread.start();
// Get the HandlerThread's Looper and use it for our Handler.
mServiceLooper = thread.getLooper();
mServiceHandler =
new ServiceHandler(mServiceLooper);
}*/
/**
* Hook method called each time a Started Service is sent an
* Intent via startService().
*/
/* public int onStartCommand(Intent intent,
int flags,
int startId) {
// Create a Message that will be sent to ServiceHandler to
// retrieve animagebased on the URI in the Intent.
Message message =
mServiceHandler.makeDownloadMessage(intent,
startId);
// Send the Message to ServiceHandler to retrieve an image
// based on contents of the Intent.
mServiceHandler.sendMessage(message);
// Don't restart the DownloadService automatically if its
// process is killed while it's running.
return Service.START_NOT_STICKY;
}*/
#Override
public IBinder onBind(Intent intent) {
return mMessengerImpl.getBinder();
}
}
downloadactivity downloadimage method Called when a user clicks the Download Image button to download image using the DownloadService
package edu.vuum.mocca;
import java.lang.ref.WeakReference;
import android.app.Activity;
import android.app.ProgressDialog;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.os.Messenger;
import android.os.RemoteException;
import android.util.Log;
import android.view.Menu;
import android.view.View;
import android.view.inputmethod.InputMethodManager;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.Toast;
/**
* #class DownloadActivity
*
* #brief A class that allows a user to download a bitmap image using
* a DownloadService.
*/
public class DownloadActivity extends Activity {
/**
* Used for debugging.
*/
private final String TAG = getClass().getName();
private final static int LOG_OP = 1;
private final static String MESSAGE_URL = "course.examples.Services.Logging.MESSAGE";
private boolean mIsBound;
/**
* User's selection of URL to download
*/
private EditText mUrlEditText;
/**
* Image that's been downloaded
*/
private ImageView mImageView;
/**
* Default URL.
*/
private String mDefaultUrl =
"http://www.dre.vanderbilt.edu/~schmidt/ka.png";
/**
* Display progress of download
*/
private ProgressDialog mProgressDialog;
/**
* Reference to the Messenger that's implemented in the
* UniqueIDGeneratorService.
*/
private Messenger mMessengerRef = null;
/**
* This ServiceConnection is used to receive a Messenger proxy
* after binding to the UniqueIDGeneratorService using bindService().
*/
private ServiceConnection mConnection = new ServiceConnection() {
/**
* Called after the UniqueIDGeneratorService is connected to
* convey the result returned from onBind().
*/
public void onServiceConnected(ComponentName className,
IBinder messenger) {
//Log.d(TAG, "ComponentName:" + className);
// Create a newq Messenger that encapsulates the
// returned IBinder object and store it for later use
// in mMessengerRef.
mMessengerRef = new Messenger(messenger);
mIsBound = true;
}
/**
* Called if the Service crashes and is no longer
* available. The ServiceConnection will remain bound,
* but the service will not respond to any requests.
*/
public void onServiceDisconnected(ComponentName className) {
mMessengerRef = null;
mIsBound = false;
}
};
/**
* Method that initializes the Activity when it is first created.
*
* #param savedInstanceState
* Activity's previously frozen state, if there was one.
*/
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
/**
* Sets the content view specified in the main.xml file.
*/
setContentView(R.layout.main);
/**
* Caches references to the EditText and ImageView objects in
* data members to optimize subsequent access.
*/
mUrlEditText = (EditText) findViewById(R.id.mUrlEditText);
mImageView = (ImageView) findViewById(R.id.mImageView);
}
/**
* Show a toast, notifying a user of an error when retrieving a
* bitmap.
*/
void showErrorToast(String errorString) {
Toast.makeText(this,
errorString,
Toast.LENGTH_LONG).show();
}
/**
* Display a downloaded bitmap image if it's non-null; otherwise,
* it reports an error via a Toast.
*
* #param image
* The bitmap image
*/
void displayImage(Bitmap image)
{
if (mImageView == null)
showErrorToast("Problem with Application,"
+ " please contact the Developer.");
else if (image != null)
mImageView.setImageBitmap(image);
else
showErrorToast("image is corrupted,"
+ " please check the requested URL.");
}
/**
* Called when a user clicks a button to reset an image to
* default.
*
* #param view
* The "Reset Image" button
*/
public void resetImage(View view) {
mImageView.setImageResource(R.drawable.default_image);
}
/**
* Called when a user clicks the Download Image button to download
* an image using the DownloadService
*
* #param view
* The "Download Image" button
*/
public void downloadImage(View view) {
// Obtain the requested URL from the user input.
if (mIsBound) {
String url = getUrlString();
Log.e(DownloadActivity.class.getSimpleName(),
"Downloading " + url);
hideKeyboard();
// Inform the user that the download is starting.
showDialog("downloading via startService()");
// Create a request Message that indicates the Service should
// send the reply back to ReplyHandler encapsulated by the
// Messenger.
Message msg = Message.obtain(null, LOG_OP);
Bundle bundle = new Bundle();
bundle.putString(MESSAGE_URL, url);
msg.setData(bundle);
// Message request = Message.obtain();
msg.replyTo = new Messenger(new DownloadHandler(this));
try {
if (mMessengerRef != null) {
Log.d(TAG, "sending message to service with url");
// Send the request Message to the
// UniqueIDGeneratorService.
mMessengerRef.send(msg);
}
} catch (RemoteException e) {
e.printStackTrace();
}
// Create an Intent to download an image in the background via
// a Service. The downloaded image is later diplayed in the
// UI Thread via the downloadHandler() method defined below.
// DownloadService.makeIntent(this, Uri.parse( getUrlString()));
}else{
Log.d(TAG, "service is not bounded restat again");
}
}
/**
* Hook method called by Android when this Activity becomes
* visible.
*/
#Override
protected void onStart() {
super.onStart();
Log.d(TAG, "onStart()");
Log.d(TAG, "calling bindService()");
if (mMessengerRef == null)
// Bind to the UniqueIDGeneratorService associated with this
// Intent.
bindService(DownloadService.makeIntent(this),
this.mConnection,
Context.BIND_AUTO_CREATE);
}
#Override
protected void onStop() {
super.onStop();
if (mIsBound)
unbindService(mConnection);
}
/**
* #class DownloadHandler
*
* #brief An inner class that inherits from Handler and uses its
* handleMessage() hook method to process Messages sent to
* it from the DownloadService.
*/
private static class DownloadHandler extends Handler {
/**
* Allows Activity to be garbage collected properly.
*/
private WeakReference<DownloadActivity> mActivity;
/**
* Class constructor constructs mActivity as weak reference
* to the activity
*
* #param activity
* The corresponding activity
*/
public DownloadHandler(DownloadActivity activity) {
mActivity = new WeakReference<DownloadActivity>(activity);
}
/**
/**
* This hook method is dispatched in response to receiving
* the pathname back from the DownloadService.
*/
public void handleMessage(Message msg) {
DownloadActivity activity = mActivity.get();
// Bail out of the DownloadActivity is gone.
if (activity == null)
return;
// Extract the data from Message, which is in the form
// of a Bundle that can be passed across processes.
Bundle data = msg.getData();
// Extract the pathname from the Bundle.
String pathname = data.getString("PATHNAME");
// See if things worked or not.
if (msg.arg1 != RESULT_OK || pathname == null)
activity.showDialog("failed download");
// Stop displaying the progress dialog.
activity.dismissDialog();
// Display the image in the UI Thread.
activity.displayImage(BitmapFactory.decodeFile(pathname));
}
};
/**
* Display the Dialog to the User.
*
* #param message
* The String to display what download method was used.
*/
public void showDialog(String message) {
mProgressDialog =
ProgressDialog.show(this,
"Download",
message,
true);
}
/**
* Dismiss the Dialog
*/
public void dismissDialog() {
if (mProgressDialog != null)
mProgressDialog.dismiss();
}
/**
* Hide the keyboard after a user has finished typing the url.
*/
private void hideKeyboard() {
InputMethodManager mgr =
(InputMethodManager) getSystemService
(Context.INPUT_METHOD_SERVICE);
mgr.hideSoftInputFromWindow(mUrlEditText.getWindowToken(),
0);
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.download, menu);
return true;
}
/**
* Read the URL EditText and return the String it contains.
*
* #return String value in mUrlEditText
*/
String getUrlString() {
String s = mUrlEditText.getText().toString();
if (s.equals(""))
s = mDefaultUrl;
return s;
}
}
Bound service with messenger ,if it is running in separate process doesn't need separate thread to do long running task.
I need to know why this service of downloadimage not working even though its running in separate process.
Thanks for helping .
As there is a NetworkOnMainThreadException the solution is to put the network code in an AsyncTask or thread.
I am facing a problem with an expansion file process, I do not know what the problem is. I hope if someone could help.
I have followed the steps by Google; yet I cannot assure I have done it correctly 100%.
/*
* Copyright (C) 2012 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*/
import com.android.vending.expansion.zipfile.ZipResourceFile;
import com.android.vending.expansion.zipfile.ZipResourceFile.ZipEntryRO;
import com.google.android.vending.expansion.downloader.Constants;
import com.google.android.vending.expansion.downloader.DownloadProgressInfo;
import com.google.android.vending.expansion.downloader.DownloaderClientMarshaller;
import com.google.android.vending.expansion.downloader.DownloaderServiceMarshaller;
import com.google.android.vending.expansion.downloader.Helpers;
import com.google.android.vending.expansion.downloader.IDownloaderClient;
import com.google.android.vending.expansion.downloader.IDownloaderService;
import com.google.android.vending.expansion.downloader.IStub;
import android.app.Activity;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager.NameNotFoundException;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Messenger;
import android.os.SystemClock;
import android.provider.Settings;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.ProgressBar;
import android.widget.TextView;
import java.io.DataInputStream;
import java.io.IOException;
import java.util.zip.CRC32;
/**
* This is sample code for a project built against the downloader library. It
* implements the IDownloaderClient that the client marshaler will talk to as
* messages are delivered from the DownloaderService.
*/
public class ExpansionFileDownloaderActivity extends Activity implements IDownloaderClient {
private static final String LOG_TAG = "LVLDownloader";
private ProgressBar mPB;
private TextView mStatusText;
private TextView mProgressFraction;
private TextView mProgressPercent;
private TextView mAverageSpeed;
private TextView mTimeRemaining;
private View mDashboard;
private View mCellMessage;
private Button mPauseButton;
private Button mWiFiSettingsButton;
private boolean mStatePaused;
private int mState;
private IDownloaderService mRemoteService;
private IStub mDownloaderClientStub;
private void setState(int newState) {
if (mState != newState) {
mState = newState;
mStatusText.setText(Helpers.getDownloaderStringResourceIDFromState(newState));
}
}
private void setButtonPausedState(boolean paused) {
mStatePaused = paused;
int stringResourceID = paused ? R.string.text_button_resume :
R.string.text_button_pause;
mPauseButton.setText(stringResourceID);
}
/**
* This is a little helper class that demonstrates simple testing of an
* Expansion APK file delivered by Market. You may not wish to hard-code
* things such as file lengths into your executable... and you may wish to
* turn this code off during application development.
*/
private static class XAPKFile {
public final boolean mIsMain;
public final int mFileVersion;
public final long mFileSize;
XAPKFile(boolean isMain, int fileVersion, long fileSize) {
mIsMain = isMain;
mFileVersion = fileVersion;
mFileSize = fileSize;
}
}
/**
* Here is where you place the data that the validator will use to determine
* if the file was delivered correctly. This is encoded in the source code
* so the application can easily determine whether the file has been
* properly delivered without having to talk to the server. If the
* application is using LVL for licensing, it may make sense to eliminate
* these checks and to just rely on the server.
*/
private static final XAPKFile[] xAPKS = {
new XAPKFile(
true, // true signifies a main file
1, // the version of the APK that the file was uploaded
// against
102459993L // the length of the file in bytes
)
};
/**
* Go through each of the APK Expansion files defined in the structure above
* and determine if the files are present and match the required size. Free
* applications should definitely consider doing this, as this allows the
* application to be launched for the first time without having a network
* connection present. Paid applications that use LVL should probably do at
* least one LVL check that requires the network to be present, so this is
* not as necessary.
*
* #return true if they are present.
*/
boolean expansionFilesDelivered() {
for (XAPKFile xf : xAPKS) {
String fileName = Helpers.getExpansionAPKFileName(this, xf.mIsMain, xf.mFileVersion);
if (!Helpers.doesFileExist(this, fileName, xf.mFileSize, false))
return false;
}
return true;
}
public static boolean expansionFilesDelivered(Context ctx) {
for (XAPKFile xf : xAPKS) {
String fileName = Helpers.getExpansionAPKFileName(ctx, xf.mIsMain, xf.mFileVersion);
if (!Helpers.doesFileExist(ctx, fileName, xf.mFileSize, false))
return false;
}
return true;
}
/**
* Calculating a moving average for the validation speed so we don't get
* jumpy calculations for time etc.
*/
static private final float SMOOTHING_FACTOR = 0.005f;
/**
* Used by the async task
*/
private boolean mCancelValidation;
/**
* Go through each of the Expansion APK files and open each as a zip file.
* Calculate the CRC for each file and return false if any fail to match.
*
* #return true if XAPKZipFile is successful
*/
void validateXAPKZipFiles() {
AsyncTask<Object, DownloadProgressInfo, Boolean> validationTask = new AsyncTask<Object, DownloadProgressInfo, Boolean>() {
#Override
protected void onPreExecute() {
mDashboard.setVisibility(View.VISIBLE);
mCellMessage.setVisibility(View.GONE);
mStatusText.setText(R.string.text_verifying_download);
mPauseButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
mCancelValidation = true;
}
});
mPauseButton.setText(R.string.text_button_cancel_verify);
super.onPreExecute();
}
#Override
protected Boolean doInBackground(Object... params) {
for (XAPKFile xf : xAPKS) {
String fileName = Helpers.getExpansionAPKFileName(
ExpansionFileDownloaderActivity.this,
xf.mIsMain, xf.mFileVersion);
if (!Helpers.doesFileExist(ExpansionFileDownloaderActivity.this, fileName,
xf.mFileSize, false))
return false;
fileName = Helpers
.generateSaveFileName(ExpansionFileDownloaderActivity.this, fileName);
ZipResourceFile zrf;
byte[] buf = new byte[1024 * 256];
try {
zrf = new ZipResourceFile(fileName);
ZipEntryRO[] entries = zrf.getAllEntries();
/**
* First calculate the total compressed length
*/
long totalCompressedLength = 0;
for (ZipEntryRO entry : entries) {
totalCompressedLength += entry.mCompressedLength;
}
float averageVerifySpeed = 0;
long totalBytesRemaining = totalCompressedLength;
long timeRemaining;
/**
* Then calculate a CRC for every file in the Zip file,
* comparing it to what is stored in the Zip directory.
* Note that for compressed Zip files we must extract
* the contents to do this comparison.
*/
for (ZipEntryRO entry : entries) {
if (-1 != entry.mCRC32) {
long length = entry.mUncompressedLength;
CRC32 crc = new CRC32();
DataInputStream dis = null;
try {
dis = new DataInputStream(
zrf.getInputStream(entry.mFileName));
long startTime = SystemClock.uptimeMillis();
while (length > 0) {
int seek = (int) (length > buf.length ? buf.length
: length);
dis.readFully(buf, 0, seek);
crc.update(buf, 0, seek);
length -= seek;
long currentTime = SystemClock.uptimeMillis();
long timePassed = currentTime - startTime;
if (timePassed > 0) {
float currentSpeedSample = (float) seek
/ (float) timePassed;
if (0 != averageVerifySpeed) {
averageVerifySpeed = SMOOTHING_FACTOR
* currentSpeedSample
+ (1 - SMOOTHING_FACTOR)
* averageVerifySpeed;
} else {
averageVerifySpeed = currentSpeedSample;
}
totalBytesRemaining -= seek;
timeRemaining = (long) (totalBytesRemaining / averageVerifySpeed);
this.publishProgress(
new DownloadProgressInfo(
totalCompressedLength,
totalCompressedLength
- totalBytesRemaining,
timeRemaining,
averageVerifySpeed
)
);
}
startTime = currentTime;
if (mCancelValidation)
return true;
}
if (crc.getValue() != entry.mCRC32) {
Log.e(Constants.TAG,
"CRC does not match for entry: "
+ entry.mFileName
);
Log.e(Constants.TAG,
"In file: " + entry.getZipFileName());
return false;
}
} finally {
if (null != dis) {
dis.close();
}
}
}
}
} catch (IOException e) {
e.printStackTrace();
return false;
}
}
return true;
}
#Override
protected void onProgressUpdate(DownloadProgressInfo... values) {
onDownloadProgress(values[0]);
super.onProgressUpdate(values);
}
#Override
protected void onPostExecute(Boolean result) {
if (result) {
mDashboard.setVisibility(View.VISIBLE);
mCellMessage.setVisibility(View.GONE);
mStatusText.setText(R.string.text_validation_complete);
mPauseButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
finish();
}
});
mPauseButton.setText(android.R.string.ok);
} else {
mDashboard.setVisibility(View.VISIBLE);
mCellMessage.setVisibility(View.GONE);
mStatusText.setText(R.string.text_validation_failed);
mPauseButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
finish();
}
});
mPauseButton.setText(android.R.string.cancel);
}
super.onPostExecute(result);
}
};
validationTask.execute(new Object());
}
/**
* If the download isn't present, we initialize the download UI. This ties
* all of the controls into the remote service calls.
*/
private void initializeDownloadUI() {
mDownloaderClientStub = DownloaderClientMarshaller.CreateStub
(this, SampleDownloaderService.class);
setContentView(R.layout.downloader_ui);
mPB = (ProgressBar) findViewById(R.id.progressBar);
mStatusText = (TextView) findViewById(R.id.statusText);
mProgressFraction = (TextView) findViewById(R.id.progressAsFraction);
mProgressPercent = (TextView) findViewById(R.id.progressAsPercentage);
mAverageSpeed = (TextView) findViewById(R.id.progressAverageSpeed);
mTimeRemaining = (TextView) findViewById(R.id.progressTimeRemaining);
mDashboard = findViewById(R.id.downloaderDashboard);
mCellMessage = findViewById(R.id.approveCellular);
mPauseButton = (Button) findViewById(R.id.pauseButton);
mWiFiSettingsButton = (Button) findViewById(R.id.wifiSettingsButton);
mPauseButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
if (mStatePaused) {
mRemoteService.requestContinueDownload();
} else {
mRemoteService.requestPauseDownload();
}
setButtonPausedState(!mStatePaused);
}
});
mWiFiSettingsButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
startActivity(new Intent(Settings.ACTION_WIFI_SETTINGS));
}
});
Button resumeOnCell = (Button) findViewById(R.id.resumeOverCellular);
resumeOnCell.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
mRemoteService.setDownloadFlags(IDownloaderService.FLAGS_DOWNLOAD_OVER_CELLULAR);
mRemoteService.requestContinueDownload();
mCellMessage.setVisibility(View.GONE);
}
});
}
/**
* Called when the activity is first create; we wouldn't create a layout in
* the case where we have the file and are moving to another activity
* without downloading.
*/
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
/**
* Both downloading and validation make use of the "download" UI
*/
initializeDownloadUI();
/**
* Before we do anything, are the files we expect already here and
* delivered (presumably by Market) For free titles, this is probably
* worth doing. (so no Market request is necessary)
*/
if (!expansionFilesDelivered()) {
try {
Intent launchIntent = ExpansionFileDownloaderActivity.this
.getIntent();
Intent intentToLaunchThisActivityFromNotification = new Intent(
ExpansionFileDownloaderActivity
.this, ((Object) this).getClass() //..... 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(
ExpansionFileDownloaderActivity.this,
0, intentToLaunchThisActivityFromNotification,
PendingIntent.FLAG_UPDATE_CURRENT);
// Request to start the download
int startResult = DownloaderClientMarshaller.startDownloadServiceIfRequired(this,
pendingIntent, SampleDownloaderService.class);
if (startResult != DownloaderClientMarshaller.NO_DOWNLOAD_REQUIRED) {
// The DownloaderService has started downloading the files,
// show progress
initializeDownloadUI();
return;
} // otherwise, download not needed so we fall through to
// starting the movie
} catch (NameNotFoundException e) {
Log.e(LOG_TAG, "Cannot find own package! MAYDAY!");
e.printStackTrace();
}
} else {
validateXAPKZipFiles();
}
}
/**
* Connect the stub to our service on start.
*/
#Override
protected void onStart() {
if (null != mDownloaderClientStub) {
mDownloaderClientStub.connect(this);
}
super.onStart();
}
/**
* Disconnect the stub from our service on stop
*/
#Override
protected void onStop() {
if (null != mDownloaderClientStub) {
mDownloaderClientStub.disconnect(this);
}
super.onStop();
}
/**
* 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());
}
/**
* The download state should trigger changes in the UI --- it may be useful
* to show the state as being indeterminate at times. This sample can be
* considered a guideline.
*/
#Override
public void onDownloadStateChanged(int newState) {
setState(newState);
boolean showDashboard = true;
boolean showCellMessage = false;
boolean paused;
boolean indeterminate;
switch (newState) {
case IDownloaderClient.STATE_IDLE:
// STATE_IDLE means the service is listening, so it's
// safe to start making calls via mRemoteService.
paused = false;
indeterminate = true;
break;
case IDownloaderClient.STATE_CONNECTING:
case IDownloaderClient.STATE_FETCHING_URL:
showDashboard = true;
paused = false;
indeterminate = true;
break;
case IDownloaderClient.STATE_DOWNLOADING:
paused = false;
showDashboard = true;
indeterminate = false;
break;
case IDownloaderClient.STATE_FAILED_CANCELED:
case IDownloaderClient.STATE_FAILED:
case IDownloaderClient.STATE_FAILED_FETCHING_URL:
case IDownloaderClient.STATE_FAILED_UNLICENSED:
paused = true;
showDashboard = false;
indeterminate = false;
break;
case IDownloaderClient.STATE_PAUSED_NEED_CELLULAR_PERMISSION:
case IDownloaderClient.STATE_PAUSED_WIFI_DISABLED_NEED_CELLULAR_PERMISSION:
showDashboard = false;
paused = true;
indeterminate = false;
showCellMessage = true;
break;
case IDownloaderClient.STATE_PAUSED_BY_REQUEST:
paused = true;
indeterminate = false;
break;
case IDownloaderClient.STATE_PAUSED_ROAMING:
case IDownloaderClient.STATE_PAUSED_SDCARD_UNAVAILABLE:
paused = true;
indeterminate = false;
break;
case IDownloaderClient.STATE_COMPLETED:
showDashboard = false;
paused = false;
indeterminate = false;
validateXAPKZipFiles();
return;
default:
paused = true;
indeterminate = true;
showDashboard = true;
}
int newDashboardVisibility = showDashboard ? View.VISIBLE : View.GONE;
if (mDashboard.getVisibility() != newDashboardVisibility) {
mDashboard.setVisibility(newDashboardVisibility);
}
int cellMessageVisibility = showCellMessage ? View.VISIBLE : View.GONE;
if (mCellMessage.getVisibility() != cellMessageVisibility) {
mCellMessage.setVisibility(cellMessageVisibility);
}
mPB.setIndeterminate(indeterminate);
setButtonPausedState(paused);
}
/**
* Sets the state of the various controls based on the progressinfo object
* sent from the downloader service.
*/
#Override
public void onDownloadProgress(DownloadProgressInfo progress) {
mAverageSpeed.setText(getString(R.string.kilobytes_per_second,
Helpers.getSpeedString(progress.mCurrentSpeed)));
mTimeRemaining.setText(getString(R.string.time_remaining,
Helpers.getTimeRemaining(progress.mTimeRemaining)));
progress.mOverallTotal = progress.mOverallTotal;
mPB.setMax((int) (progress.mOverallTotal >> 8));
mPB.setProgress((int) (progress.mOverallProgress >> 8));
mProgressPercent.setText(Long.toString(progress.mOverallProgress
* 100 /
progress.mOverallTotal) + "%");
mProgressFraction.setText(Helpers.getDownloadProgressString
(progress.mOverallProgress,
progress.mOverallTotal));
}
#Override
protected void onDestroy() {
this.mCancelValidation = true;
super.onDestroy();
}
}
this is the SampleDownloaderService class,
import com.google.android.vending.expansion.downloader.impl.DownloaderService;
/**
* Created by Abdulkarim Kanaan on 22-Mar-14.
*/
public class SampleDownloaderService extends DownloaderService {
// You must use the public key belonging to your publisher account
public static final String BASE64_PUBLIC_KEY = "<Place Key provided by Google Play Publisher>";
// You should also modify this salt
public static final byte[] SALT = new byte[] { 1, 42, -12, -1, 54, 98,
-100, -12, 43, 2, -8, -4, 9, 5, -106, -107, -33, 45, -1, 84
};
#Override
public String getPublicKey() {
return BASE64_PUBLIC_KEY;
}
#Override
public byte[] getSALT() {
return SALT;
}
#Override
public String getAlarmReceiverClassName() {
return SampleAlarmReceiver.class.getName();
}
}
Finally, this is the Main Activity creation method, where the check is carried out to see whether the expansion file exists or not.
public class MainActivity extends Activity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
String.valueOf(ExpansionFileDownloaderActivity.expansionFilesDelivered(this)));
if (!ExpansionFileDownloaderActivity.expansionFilesDelivered(this)){
startActivity(new Intent(this, ExpansionFileDownloaderActivity.class));
}
//startApp(); // Expansion files are available, start the app
}
the problem is that after uploading the expansion file to Google Play, when the program starts, it shows that downloading process is going to be carried out. After a short period, it stops saying that "Download failed because the resources could not be found". When I place the file (obb file) in the SD Card, the application works as expected. However, I am trying now to download the file from Google Play.
What is the problem?
Thank you in advance,
I was having the same problem and no matter what I tried it still didnt work.
Finally I managed to download the apk expansion file. What I did was the following.
*Make sure your file size is correct (file size not disk size).
*Make sure versionCode matches with your manifest's versionCode.
*Make sure you have changed the SALT bytes.
Step by step:
1- Create a new app in the developer console.
2- Obtain the LVL Licence Key and repace it in your code.
3- Sign and export your application and create a zip with your expansion file without compression (compression: "store" in win rar).
4-Upload your app to google play but upload it in the Beta Testing Section. (without a production version listed, this is very important you must not have a production version saved in draft).
5- Create a google group with the people you want to allow beta acess and under the Beta Testing tab you must add that group.
5- Now its the scary part, you must click on publish. Your app will not show in google play, it will only be shown to your testing groups, TRUST ME. It should look like this:
*Note: Make sure you don't promote the beta to production or it will be shown, also notice I don't have a production version listed.
6- Under Beta Testing tab there is a link that a you must click in order to use the beta.
After that just download the app from google play, you can just type your package name for example:
https://play.google.com/store/apps/details?id=com.mydomain.myapp
and the app will be installed along with the expansion package.
If you want to test the files download, install the app and then delete the obb file downloaded. This will start the obb download process.
Hope this helps, as it did for me!
Cheers.