I am trying to get basic reporting using ACRA in Android Studio in my test app (Lollipop).
So far, I have implemented following:
added dependancy in gradle
compile 'ch.acra:acra:4.6.2'
added MyApplication which extends Application and added ReportsCrashes annotation to it:
#ReportsCrashes(
resNotifTickerText = R.string.crash_notification_ticker_text,
resNotifTitle = R.string.crash_notification_title,
resNotifText = R.string.crash_notification_text,
resNotifIcon = R.mipmap.error );
public class MyApplication extends Application {
private static final String TAG = MyApplication.class.getSimpleName();
#Override
public void onCreate(){
super.onCreate();
ACRA.init(this);
}
}
(BTW, sorry for code formatting above, but StackOverflow refused to format it properly for some reason)
This is based on ACRA documentation provided in github https://github.com/ACRA/acra/wiki/BasicSetup
added application name and INTERNET permission in AndroidManifest
<!-- add INTERNET permission -->
<uses-permission android:name="android.permission.INTERNET" />
<!-- add application name -->
<application
android:name="MyApplication"
android:allowBackup="true"
android:icon="#mipmap/ic_launcher"
android:label="#string/app_name"
android:supportsRtl="true"
android:theme="#style/AppTheme" >
<activity android:name=".MainActivity" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
My main activity has just one button, when clicked, it will crash app when it attempts to do division by zero
public class MainActivity extends AppCompatActivity {
public final static String TAG = MainActivity.class.getSimpleName();
private Button btnError;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
btnError = (Button) findViewById(R.id.btnError);
btnError.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
Toast.makeText(getApplicationContext(), getString(R.string.toast_app_crash), Toast.LENGTH_SHORT).show();
Runnable r = new Runnable() {
#Override
public void run() {
// this will crash your app throwing Arithmetic Exception
int number = 7 / 0;
}
};
Handler h = new Handler();
h.postDelayed(r, 2000);
}
});
}
}
I am expecting to see some kind of notification and some kind of report to get generated but I dont get any. My app simply crashes at the spot where division by zero is attempted.
I am not sure what is that I am doing wrong.
Thanks,
The type of notification you should select as
mode = ReportingInteractionMode.TOAST,
//Available : Dialog,Notification,Toast and Silent
resToastText = R.string.crash_text_toast
Here is the sample report parameter what i have used in my app.
#ReportsCrashes(
formUri="",
formUriBasicAuthLogin = "CloundantAuthLogin",
formUriBasicAuthPassword = "CloundantAuthKeyPassword",
reportType = org.acra.sender.HttpSender.Type.JSON,
httpMethod = org.acra.sender.HttpSender.Method.PUT,
customReportContent = { ReportField.APP_VERSION_NAME, ReportField.ANDROID_VERSION, ReportField.PHONE_MODEL,ReportField.DEVICE_FEATURES,
ReportField.USER_APP_START_DATE,ReportField.USER_CRASH_DATE,ReportField.TOTAL_MEM_SIZE,ReportField.USER_COMMENT,
ReportField.THREAD_DETAILS, ReportField.STACK_TRACE },
mode = ReportingInteractionMode.DIALOG,
includeDropBoxSystemTags = true,
resToastText = R.string.crash_toast_text, // optional, displayed as soon as the crash occurs, before collecting data which can take a few seconds
resDialogText = R.string.crash_dialog_text,
resDialogIcon = android.R.drawable.ic_dialog_info, //optional. default is a warning sign
resDialogTitle = R.string.crash_dialog_title, // optional. default is your application name
resDialogCommentPrompt = R.string.crash_dialog_comment_prompt, // optional. when defined, adds a user text field input with this text resource as a label
resDialogOkToast = R.string.crash_dialog_ok_toast // optional. displays a Toast message when the user accepts to send a report.
)
Library used : acra-4.6.2
The best tutorial till date available here : http://www.toptal.com/android/automated-android-crash-reports-with-acra-and-cloudant
Related
I am developing a Flutter plugin that allows me to log in to the Ethereum based wallet Torus through a process called direct auth: https://docs.tor.us/direct-auth/what-is-directauth.
Here is the plugin class file:
/** TorusDirect */
public class TorusDirectPlugin implements FlutterPlugin, MethodCallHandler, ActivityAware {
/// The MethodChannel that will the communication between Flutter and native Android
///
/// This local reference serves to register the plugin with the Flutter Engine and unregister it
/// when the Flutter Engine is detached from the Activity
private Context context;
private Activity activity;
private MethodChannel channel;
private TorusDirectSdk torusDirectSDK;
private SubVerifierDetails subVerifierDetails;
public void onDetachedFromActivity() {
System.out.println("onDetachedFromActivity called");
}
public void onReattachedToActivityForConfigChanges(ActivityPluginBinding binding) {
System.out.println("onReattachedToActivityForConfigChanges called");
}
public void onAttachedToActivity(ActivityPluginBinding binding) {
activity = binding.getActivity();
}
public void onDetachedFromActivityForConfigChanges() {
System.out.println("onDetachedFromActivityForConfigChanges called");
}
#Override
public void onAttachedToEngine(#NonNull FlutterPluginBinding flutterPluginBinding) {
System.out.println("onAttachedToEngine called");
channel = new MethodChannel(flutterPluginBinding.getFlutterEngine().getDartExecutor(), "torus.flutter.dev/torus-direct");
channel.setMethodCallHandler(this);
this.context = flutterPluginBinding.getApplicationContext();
}
// This static function is optional and equivalent to onAttachedToEngine. It supports the old
// pre-Flutter-1.12 Android projects. You are encouraged to continue supporting
// plugin registration via this function while apps migrate to use the new Android APIs
// post-flutter-1.12 via https://flutter.dev/go/android-project-migration.
//
// It is encouraged to share logic between onAttachedToEngine and registerWith to keep
// them functionally equivalent. Only one of onAttachedToEngine or registerWith will be called
// depending on the user's project. onAttachedToEngine or registerWith must both be defined
// in the same class.
public static void registerWith(Registrar registrar) {
System.out.println("registerWith called");
final MethodChannel channel = new MethodChannel(registrar.messenger(), "torus.flutter.dev/torus-direct");
channel.setMethodCallHandler(new TorusDirectPlugin());
}
#Override
public void onMethodCall(#NonNull MethodCall call, #NonNull Result result) {
switch (call.method) {
case "setVerifierDetails":
System.out.println(call.arguments);
HashMap<String, String> args = (HashMap<String, String> ) call.arguments;
String verifierTypeString = args.get("verifierType");
String loginProviderString = args.get("loginProvider");
String clientId = args.get("clientId");
String verifierName = args.get("verifierName");
String redirectURL = args.get("redirectURL");
Log.d(TorusDirectPlugin.class.getSimpleName(), "Verifier Type: " + verifierTypeString);
this.subVerifierDetails = new SubVerifierDetails(
LoginType.valueOf(loginProviderString.toUpperCase()),
clientId,
verifierName,
new Auth0ClientOptions.Auth0ClientOptionsBuilder("").build());
DirectSdkArgs directSdkArgs = new DirectSdkArgs("torusapp://io.flutter.app.FlutterApplication/redirect", TorusNetwork.TESTNET, "");
this.torusDirectSDK = new TorusDirectSdk(directSdkArgs, this.context);
result.success(true);
case "triggerLogin":
Executors.newFixedThreadPool(10).submit(() -> {
try {
CompletableFuture<TorusLoginResponse> torusLoginResponseCompletableFuture = this.torusDirectSDK.triggerLogin(new SubVerifierDetails(LoginType.GOOGLE,
"",
"",
new Auth0ClientOptions.Auth0ClientOptionsBuilder("").build()));
TorusLoginResponse torusLoginResponse = torusLoginResponseCompletableFuture.get();
TorusVerifierUnionResponse userInfo = torusLoginResponse.getUserInfo();
Log.d(TorusDirectPlugin.class.getSimpleName(), "Private Key: " + torusLoginResponse.getPrivateKey());
Log.d(TorusDirectPlugin.class.getSimpleName(), "Public Address: " + torusLoginResponse.getPublicAddress());
HashMap<String, String> torusLoginInfoMap = (HashMap<String, String> ) new HashMap<String,String>();
torusLoginInfoMap.put("email",userInfo.getEmail());
torusLoginInfoMap.put("name",userInfo.getName());
torusLoginInfoMap.put("id",userInfo.getVerifierId());
torusLoginInfoMap.put("profileImage",userInfo.getProfileImage());
torusLoginInfoMap.put("privateKey", torusLoginResponse.getPrivateKey());
torusLoginInfoMap.put("publicAddress", torusLoginResponse.getPublicAddress());
result.success(torusLoginInfoMap);
} catch (ExecutionException | InterruptedException e) {
e.printStackTrace();
}
});
}
}
#Override
public void onDetachedFromEngine(#NonNull FlutterPluginBinding binding) {
channel.setMethodCallHandler(null);
}
}
I set options for logging in with "setVerifierDetails" and I launch a login window with those details with "triggerLogin". Trigger login calls this function to use the app context and activity to launch a login Window in a browser:
#Override
public CompletableFuture<LoginWindowResponse> handleLoginWindow(Context context) {
if (StartUpActivity.loginHandler != null && StartUpActivity.loginHandler.get() == null) {
StartUpActivity.loginHandler.set(this);
}
Intent intent = new Intent(context, StartUpActivity.class).putExtra(StartUpActivity.URL, finalURL);
context.startActivity(intent);
return loginWindowResponseCompletableFuture;
}
I am able to launch the intent and login, however, when I redirect back to the application the screen is blank. I'm thinking the Flutter process is being killed when launching a broswer to login, but I'm not entirely sure.
Here is the AndroidManifest.xml:
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.torus_direct_example">
<!-- io.flutter.app.FlutterApplication is an android.app.Application that
calls FlutterMain.startInitialization(this); in its onCreate method.
In most cases you can leave this as-is, but you if you want to provide
additional functionality it is fine to subclass or reimplement
FlutterApplication and put your custom class here. -->
<application
android:name="io.flutter.app.FlutterApplication"
android:label="torus_direct_example"
android:icon="#mipmap/ic_launcher"
android:theme="#style/Theme.AppCompat.Light">
<activity
android:name=".MainActivity"
android:launchMode="singleTop"
android:theme="#style/LaunchTheme"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize">
<!-- Specifies an Android theme to apply to this Activity as soon as
the Android process has started. This theme is visible to the user
while the Flutter UI initializes. After that, this theme continues
to determine the Window background behind the Flutter UI. -->
<meta-data
android:name="io.flutter.embedding.android.NormalTheme"
android:resource="#style/NormalTheme"
/>
<!-- Displays an Android View that continues showing the launch screen
Drawable until Flutter paints its first frame, then this splash
screen fades out. A splash screen is useful to avoid any visual
gap between the end of Android's launch screen and the painting of
Flutter's first frame. -->
<meta-data
android:name="io.flutter.embedding.android.SplashScreenDrawable"
android:resource="#drawable/launch_background"
/>
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<activity android:name="com.torusresearch.torusdirect.activity.StartUpActivity"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize"
android:theme="#style/Theme.AppCompat.Light.NoActionBar"
android:launchMode="singleTop">
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
<data android:scheme="torusapp"
android:host="io.flutter.app.FlutterApplication"
android:pathPattern="/*"
android:pathPrefix="/redirect"/>
</intent-filter>
</activity>
<!-- Don't delete the meta-data below.
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
<meta-data
android:name="flutterEmbedding"
android:value="2" />
</application>
</manifest>
I followed this tutorial to scan a bar code and display in the text view everything works fine but the scanned bar code is not displayed in the text view.As from the below you can see the handledata is never called when i scan the code through TC70 zebra device.As i expected the below code to create a new intent and call the handledata from new Intent method.
AndroidManifest.xml
<uses-permission android:name="com.symbol.emdk.permission.EMDK"/>
<application
android:allowBackup="true"
android:icon="#mipmap/ic_launcher"
android:label="#string/app_name"
android:launchMode="singleTask"
android:roundIcon="#mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="#style/AppTheme">
<uses-library android:name="com.symbol.emdk"/>
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<intent-filter>
<action android:name="com.lisec.emdktest.intentsample.RECVR"/>
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
</activity>
</application>
MainActivity.java
package com.lisec.emdktest.intentsample;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.content.Intent;
import android.widget.TextView;
import com.symbol.emdk.EMDKManager;
import com.symbol.emdk.EMDKResults;
import com.symbol.emdk.ProfileManager;
public class MainActivity extends AppCompatActivity implements EMDKManager.EMDKListener {
//Assign the profile name used in EMDKConfig.xml
private String profileName = "NewDataCapture";
//Declare a variable to store ProfileManager object
private ProfileManager mProfileManager = null;
//Declare a variable to store EMDKManager object
private EMDKManager emdkManager = null;
private TextView textViewBarcode = null;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//The EMDKManager object will be created and returned in the callback.
EMDKResults results = EMDKManager.getEMDKManager(getApplicationContext(), this);
//Check the return status of getEMDKManager
if(results.statusCode == EMDKResults.STATUS_CODE.FAILURE)
{
//Failed to create EMDKManager object
}
textViewBarcode = (TextView) findViewById(R.id.textViewBarcode);
Intent i = getIntent();
handleDecodeData(i);
}
//This function is responsible for getting
the data from the
intent
private void handleDecodeData(Intent i)
{
//Check the intent action is for us
if (i.getAction().contentEquals
("com.lisec.emdktest.intentsample.RECVR") ) {
String source =
i.getStringExtra
("com.motorolasolutions.emdk.datawedge.source");
//Check if the data has come from the Barcode scanner
if(source.equalsIgnoreCase("scanner"))
{
//Get the data from the intent
String data =
i.getStringExtra
("com.motorolasolutions.emdk.datawedge.data_string");
//Check that we have received data
if(data != null && data.length() > 0)
{
textViewBarcode.setText("Data = " + data);
}
}
}
}
#Override
public void onNewIntent(Intent i) {
handleDecodeData(i);
}
#Override
public void onOpened(EMDKManager emdkManager) {
this.emdkManager = emdkManager;
//Get the ProfileManager object to process the profiles
mProfileManager = (ProfileManager)
emdkManager.getInstance
(EMDKManager.FEATURE_TYPE.PROFILE);
if(mProfileManager != null)
{
try{
String[] modifyData = new String[1];
//Call processPrfoile with profile name
and SET flag to create
the profile.
The modifyData can be null.
EMDKResults results = mProfileManager.
processProfile(profileName,
ProfileManager.PROFILE_FLAG.SET, modifyData);
if(results.statusCode == EMDKResults.STATUS_CODE.FAILURE)
{
//Failed to set profile
}
}catch (Exception ex){
// Handle any exception
}
}
}
#Override
public void onClosed() {
}
#Override
protected void onDestroy() {
// TODO Auto-generated method stub
super.onDestroy();
//Clean up the objects created by EMDK manager
emdkManager.release();
}
}
I think you are confusing the different ways to retrieve scanned data. Intents are only sent from the DataWedge service (http://techdocs.zebra.com/datawedge/6-0/guide/about/) but you are also initialising the EMDK library (http://techdocs.zebra.com/emdk-for-android/6-0/guide/gettingstarted/). EMDK returns its data via callback.
If you choose the DataWedge route, I have an application which listens for DataWedge intents that might help: https://github.com/darryncampbell/DataWedge-API-Exerciser
If you choose the EMDK route, there are samples on Zebra's own site: http://techdocs.zebra.com/emdk-for-android/6-0/samples/barcode/
If you use the EMDK in your application it will automatically take priority over DataWedge so your application would never receive data via intents unless you delete the EMDK code.
I'm trying to use SharedPreferencesBackupHelper to save my SharedPreferences value to cloud.
AndroidManifest.xml
<application
android:allowBackup="true"
android:backupAgent=".DataBackupAgent"
android:icon="#mipmap/ic_launcher"
android:label="#string/app_name"
android:theme="#style/AppTheme" >
<meta-data android:name="com.google.android.backup.api_key" android:value="AEdPqrEAAAAIXMH86OqosQlXYuS0QbfyOaZT8fUadY1QUDzo2w" />
<activity
android:name=".MainActivity"
android:label="#string/app_name" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
DataBackupAgent.java:
public class DataBackupAgent extends BackupAgentHelper {
public static final String PREFS = "data_prefs";
public static final String PREFS_BACKUP_KEY = "myprefs";
#Override
public void onCreate() {
SharedPreferencesBackupHelper helper = new SharedPreferencesBackupHelper(this, PREFS);
addHelper(PREFS_BACKUP_KEY, helper);
}
}
MainActivity.java
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
backupManager = new BackupManager(this);
prefs = getSharedPreferences(DataBackupAgent.PREFS, Context.MODE_PRIVATE);
edit = prefs.edit();
text = (EditText)findViewById(R.id.editText);
String value = prefs.getString(DataBackupAgent.PREFS_BACKUP_KEY,"");
text.setText(value);
Button btnBackup = (Button)findViewById(R.id.button);
btnBackup.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
edit.putString(DataBackupAgent.PREFS_BACKUP_KEY,text.getText().toString());
edit.commit();
backupManager.dataChanged();
}
});
}
My steps:
Write something at the EditText, click Backup button
Close app and open again. The saved value will be shown in EditText
Uninstall the app and reinstall again. The saved value is not shown in EditText at all.
Edit at 27/02/2015:
I added the following code to restore manually:
backupManager.requestRestore(new RestoreObserver() {
#Override
public void restoreFinished(int error) {
super.restoreFinished(error);
String value = prefs.getString(DataBackupAgent.PREFS_BACKUP_KEY,"");
text.setText(value);
}
#Override
public void restoreStarting(int numPackages) {
super.restoreStarting(numPackages);
}
#Override
public void onUpdate(int nowBeingRestored, String currentPackage) {
super.onUpdate(nowBeingRestored, currentPackage);
}
});
Unfortunately no callback functions are called.
This means back or auto restore doesn't work at all. Any idea? Thanks
My steps:
1. Write something at the EditText, click Backup button
2. Close app and open again. The saved value will be shown in EditText
3. Uninstall the app and reinstall again. The saved value is not shown in EditText at all.
To test your implementation there are others steps related to the use of bmgras we can see here.
Nevertheless I implemented this feature some days ago and following the steps in the documentation using a real device - Samsung SII - the automatic restore doesn't happen BUT using the emulator all was fine.
Logcat will show you all the operation output details.
IMO, the Android Data Backup feature is not reliable today. We can see some discussion about the implementation problems here and here.
Hope it helps!
How to implement acra error report on activity or something else? I know it must be on class that extends application, but is it possible to add acra to activity?
I'm getting the following error
cannot be cast to android.app.application
This is my code
#ReportsCrashes(
formUri = "http://test.com/cekErr",
formUriBasicAuthLogin = "GENERATED_USERNAME_WITH_WRITE_PERMISSIONS",
formUriBasicAuthPassword = "GENERATED_PASSWORD",
formKey = "",
customReportContent = {
ReportField.APP_VERSION_CODE,
ReportField.APP_VERSION_NAME,
ReportField.ANDROID_VERSION,
ReportField.PACKAGE_NAME,
ReportField.REPORT_ID,
ReportField.BUILD,
ReportField.STACK_TRACE
},
resToastText = R.string.app_name
)
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ACRAConfiguration config = ACRA.getNewDefaultConfig(this.getApplication());
config.setResToastText(R.string.app_name);
ACRA.setConfig(config);
ACRA.init(this.getApplication());
Map<ReportField, String> mapping = new HashMap<ReportField, String>();
mapping.put(ReportField.APP_VERSION_CODE, "myAppVerCode");
mapping.put(ReportField.APP_VERSION_NAME, "myAppVerName");
mapping.put(ReportField.LOGCAT, "myAppErr");
// ...
mapping.put(ReportField.USER_EMAIL, "userEmail");
// remove any default report sender
ACRA.getErrorReporter().removeAllReportSenders();
// create your own instance with your specific mapping
ACRA.getErrorReporter().addReportSender(
new HttpPostSender
("http://test.com/cekErr"
, mapping));
}
You don't need to add acra to and activity you need to configure to an application class level.
MyApplication.java
import org.acra.*;
import org.acra.annotation.*;
#ReportsCrashes(
formKey = "", // This is required for backward compatibility but not used
formUri = "http://www.backendofyourchoice.com/reportpath"
)
public class MyApplication extends Application {
#Override
public void onCreate() {
super.onCreate();
// The following line triggers the initialization of ACRA
ACRA.init(this);
}
}
Application
<application android:icon="#drawable/icon" android:label="#string/app_name" android:name="MyApplication">
Permition
<uses-permission android:name="android.permission.INTERNET"></uses-permission>
follow this basic setup
No. ACRA is added to your entire application.
If you don't already have an Application class just create one that extends from Application.
As colleagues already have told you, ACRA is added to entire application. So, add ACRA to your app, but use it only in desired Activity.
I try to implement low-frequency Live Card using the instruction provided in the GDK guides.
I have a layout that I want to render and the LiveCard service class (that extends Service). I also have the Menu: activity to handle menu callbacks, making the menu transparent, and provide a PendingIntent for the card's action using setAction() in the LiveCard service.
I also got a successful message when loading the app into Glass but it doesn't show up in my Glass.
I'm not sure what else is missing.
[2014-04-22 00:45:01 - MyApp] Installing MyApp.apk...
[2014-04-22 00:45:04 - MyApp] Success!
[2014-04-22 00:45:04 - MyApp] /MyApp/bin/MyApp.apk installed on device
[2014-04-22 00:45:04 - MyApp] Done!
Below is my LiveCardService:
public class LiveCardService extends Service {
private ArrayList<FeedItem> feedItems = new ArrayList<FeedItem>();
//private FeedAdapter feedAdapter = null;
// NYC: 40.758895, -73.985131
private double latitude = 0;
private double longitude = 0;
private LocationManager mlocManager;
private LocationListener mlocListener;
/******/
private static final String LIVE_CARD_TAG = "LiveCardDemo";
//private TimelineManager mTimelineManager;
private LiveCard mLiveCard;
private RemoteViews mLiveCardView;
private int homeScore, awayScore;
private Random mPointsGenerator;
private final Handler mHandler = new Handler();
private final UpdateLiveCardRunnable mUpdateLiveCardRunnable =
new UpdateLiveCardRunnable();
private static final long DELAY_MILLIS = 30000;
#Override
public void onCreate() {
super.onCreate();
//mTimelineManager = TimelineManager.from(this);
mPointsGenerator = new Random();
}
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
if (mLiveCard == null) {
// Get an instance of a live card
// mLiveCard = mTimelineManager.createLiveCard(LIVE_CARD_TAG);
mLiveCard = new LiveCard(this, LIVE_CARD_TAG);
// Inflate a layout into a remote view
mLiveCardView = new RemoteViews(getPackageName(),
R.layout.score);
mLiveCard.setViews(mLiveCardView); // !!!
// Set up initial RemoteViews values
homeScore = 0;
awayScore = 0;
/**/
mLiveCardView.setTextViewText(R.id.homeTeamNameTextView,
/*getString(R.id.home_team)*/"HOME TEAM");
mLiveCardView.setTextViewText(R.id.awayTeamNameTextView,
/*getString(R.id.away_team)*/"AWAY TEAM NAME");
mLiveCardView.setTextViewText(R.id.footer_text,
/*getString(R.id.game_quarter)*/"FOOTER_TEXT");
/**/
// Set up the live card's action with a pending intent
// to show a menu when tapped
Intent menuIntent = new Intent(this, MenuActivity.class);
menuIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
Intent.FLAG_ACTIVITY_CLEAR_TASK);
mLiveCard.setAction(PendingIntent.getActivity(
this, 0, menuIntent, 0));
// Publish the live card
mLiveCard.publish(PublishMode.REVEAL);
// Queue the update text runnable
mHandler.post(mUpdateLiveCardRunnable);
}
return START_STICKY;
}
#Override
public void onDestroy() {
if (mLiveCard != null && mLiveCard.isPublished()) {
//Stop the handler from queuing more Runnable jobs
mUpdateLiveCardRunnable.setStop(true);
mLiveCard.unpublish();
mLiveCard = null;
}
super.onDestroy();
}
/**
* Runnable that updates live card contents
*/
private class UpdateLiveCardRunnable implements Runnable{
private boolean mIsStopped = false;
/*
* Updates the card with a fake score every 30 seconds as a demonstration.
* You also probably want to display something useful in your live card.
*
* If you are executing a long running task to get data to update a
* live card(e.g, making a web call), do this in another thread or
* AsyncTask.
*/
public void run(){
if(!isStopped()){
// Generate fake points.
homeScore += mPointsGenerator.nextInt(3);
awayScore += mPointsGenerator.nextInt(3);
// Update the remote view with the new scores.
mLiveCardView.setTextViewText(R.id.home_score_text_view,
String.valueOf(homeScore));
mLiveCardView.setTextViewText(R.id.away_score_text_view,
String.valueOf(awayScore));
// Always call setViews() to update the live card's RemoteViews.
mLiveCard.setViews(mLiveCardView);
// Queue another score update in 30 seconds.
mHandler.postDelayed(mUpdateLiveCardRunnable, DELAY_MILLIS);
}
}
public boolean isStopped() {
return mIsStopped;
}
public void setStop(boolean isStopped) {
this.mIsStopped = isStopped;
}
}
#Override
public IBinder onBind(Intent intent) {
/*
* If you need to set up interprocess communication
* (activity to a service, for instance), return a binder object
* so that the client can receive and modify data in this service.
*
* A typical use is to give a menu activity access to a binder object
* if it is trying to change a setting that is managed by the live card
* service. The menu activity in this sample does not require any
* of these capabilities, so this just returns null.
*/
return null;
}
and here is my Manifest file
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.coeverywhere.google_glass"
android:versionCode="5"
android:versionName="1.0" >
<uses-sdk
android:minSdkVersion="15"
android:targetSdkVersion="15" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="com.google.android.glass.permission.DEVELOPMENT" />
<application
android:allowBackup="true"
android:icon="#drawable/mylogo"
android:label="#string/app_name">
<activity
android:name="com.myapp.google_glass.MenuActivity"
android:theme="#style/MenuTheme"
android:enabled="true"
>
</activity>
<!-- android:icon="#drawable/ic_lap" -->
<service
android:name="com.myapp.google_glass.LiveCardService"
android:label="#string/app_name"
android:enabled="true"
android:exported="true">
<intent-filter>
<action android:name="com.google.android.glass.action.VOICE_TRIGGER" />
</intent-filter>
<meta-data
android:name="com.google.android.glass.VoiceTrigger"
android:resource="#xml/voice_trigger_start" />
</service>
</application>
</manifest>
and here's my voice_trigger_start.xml. I put them under res/xml
<?xml version="1.0" encoding="utf-8"?>
<trigger command = "basketball" />
Below is original answer, I have since made a reference project that takes the Google documentation and updates it for XE16 using the StopWatch project as a reference. Check the code and commit history to learn a lot more:
https://github.com/mscheel/GoogleGlass-XE16-LowFrequencyLiveCardBasketballScore
One way is to declare a voice trigger to start the service/livecard.
This is mentioned in this pattern explanation:
https://developers.google.com/glass/develop/patterns/ongoing-task
The technique is described here:
https://developers.google.com/glass/develop/gdk/starting-glassware#unlisted_commands
I tested it out with your code and it worked if the manifest has these items (modify for your package name of course) ... this first one goes inside the application tag:
<service
android:name="com.example.lowfrequencylivecardexample.LiveCardService"
android:enabled="true"
android:exported="true"
android:label="#string/app_name" >
<intent-filter>
<action android:name="com.google.android.glass.action.VOICE_TRIGGER" />
</intent-filter>
<meta-data
android:name="com.google.android.glass.VoiceTrigger"
android:resource="#xml/voice_trigger" />
</service>
You also need:
<uses-permission android:name="com.google.android.glass.permission.DEVELOPMENT" />
You will also need this file ... res/xml/voice_trigger.xml:
<?xml version="1.0" encoding="utf-8"?>
<trigger keyword="basketball" />
I found the Timer project to be helpful in coming up with this, here is its manifest:
https://github.com/googleglass/gdk-timer-sample/blob/master/AndroidManifest.xml
Optionally you can make the home team the Celtics and the away team the Lakers. Larry Legend forever!