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!
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 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
I created some little apps without icon and which are not launchable by the user directly in the app menu of Android. To dot that, i deleted the intent-filter part of the apps :
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
Now, i want to start these little apps from a big one (i have a listView listing all the little apps). When the user click on one of the apps, i start the activity of the corresponding app. But when i do that with the packageName of the little app, nothing happen.
I really want to keep this modularity by having a lot of little apps which are invisible for the user and only start it from a big app.
How can i do that if it's possible.
Thanks
public class MainActivity extends ListActivity {
/**
* This class describes an individual SoftFunction (the function title, and the activity class that
* demonstrates this function).
*/
private class SoftFunction {
private CharSequence title;
private String packageName;
public SoftFunction(int titleResId, int appPackageResId) {
this.title = getResources().getString(titleResId);
this.packageName = getResources().getString(appPackageResId);
}
#Override
public String toString() {
return title.toString();
}
}
/**
* The collection of all Soft Functions in the app. This gets instantiated in {#link
* #onCreate(android.os.Bundle)} because the {#link Sample} constructor needs access to {#link
* android.content.res.Resources}.
*/
private static SoftFunction[] mSoftFunctions;
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// Instantiate the list of samples.
mSoftFunctions = new SoftFunction[]{
new SoftFunction(R.string.title_app_test1, R.string.app_test1_package_name),
new SoftFunction(R.string.title_app_test2, R.string.app_test2_package_name),
new SoftFunction(R.string.title_app_test3, R.string.app_test3_package_name),
new SoftFunction(R.string.title_app_test4, R.string.app_test4_package_name),
};
setListAdapter(new ArrayAdapter<SoftFunction>(this,
android.R.layout.simple_list_item_1,
android.R.id.text1,
mSoftFunctions));
}
#Override
protected void onListItemClick(ListView listView, View view, int position, long id) {
// Launch the sample associated with this list position.
Intent i = getPackageManager().getLaunchIntentForPackage(mSoftFunctions[position].packageName);
if (i != null)
{
startActivity(i);
}
}
}
You have to set android:exported="true" for your "little app" activities. This is because by default activities without intent filter(s) are not exported.
Like this:
<activity
android:name=".YourActivity"
...
android:exported="true" />
Then you can launch this activity from external apps using package name and activity name.
Intent intent=new Intent();
intent.setComponent(new ComponentName("com.your.package.name", "com.your.package.name.YourActivity"));
startActivity(intent);
I'm trying to implement Data Backup into my application. I build Android 2.2 project, and run in Galaxy s2 4.0.3.
I try to use: BackupManagerTest to save preferences to the cloud
This is my code :
AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="net.amdroid.backuptest"
android:versionCode="1"
android:versionName="1.0" >
<uses-permission android:name="android.permission.INTERNET" />
<uses-sdk
android:minSdkVersion="8"
android:targetSdkVersion="8" />
<application
android:allowBackup="true"
android:backupAgent="net.amdroid.backuptest.MyBackupAgent"
android:icon="#drawable/icon"
android:label="#string/app_name" >
<activity
android:name=".BackupManagerTestActivity"
android:label="#string/app_name" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<meta-data
android:name="com.google.android.backup.api_key"
android:value="AEdPqrEAAAAI7_yf1xqlpltWZPZiKMHVlDgn3nMfgotjUweSUg" />
</application>
</manifest>
MyBackupAgent.java
public class MyBackupAgent extends BackupAgentHelper {
// The names of the SharedPreferences groups that the application maintains. These
// are the same strings that are passed to getSharedPreferences(String, int).
static final String PREFS_TEST = "testprefs";
// An arbitrary string used within the BackupAgentHelper implementation to
// identify the SharedPreferenceBackupHelper's data.
static final String MY_PREFS_BACKUP_KEY = "myprefs";
// Simply allocate a helper and install it
#Override
public void onCreate() {
SharedPreferencesBackupHelper helper =
new SharedPreferencesBackupHelper(this, PREFS_TEST);
addHelper(MY_PREFS_BACKUP_KEY, helper);
Log.d("Test", "Adding backupagent...");
}
}
My Activity
public class BackupManagerTestActivity extends Activity {
private SharedPreferences prefs;
private Editor edit;
private BackupManager backupManager;
private EditText text;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
backupManager = new BackupManager(getBaseContext());
prefs = getSharedPreferences(MyBackupAgent.PREFS_TEST, Context.MODE_PRIVATE);
edit = prefs.edit();
text = (EditText) findViewById(R.id.editName);
String nome = prefs.getString("KEY_NAME", "");
text.setText(nome);
Button button = (Button) findViewById(R.id.buttonSave);
button.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
edit.putString("KEY_NAME", text.getText().toString());
edit.commit();
Log.d("Test", "Calling backup...");
backupManager.dataChanged();
}
});
}
}
So MyBackupAgent never called. I don't know the reason.
When you call backupManager.dataChanged(), it merely schedules your app for backup. It does not mean your backup helper is called right away.
From http://developer.android.com/guide/topics/data/backup.html:
You can request a backup operation at any time by calling dataChanged(). This method notifies the Backup Manager that you'd like to backup your data using your backup agent. The Backup Manager then calls your backup agent's onBackup() method at an opportune time in the future. Typically, you should request a backup each time your data changes (such as when the user changes an application preference that you'd like to back up). If you call dataChanged() several times consecutively, before the Backup Manager requests a backup from your agent, your agent still receives just one call to onBackup().
Note: While developing your application, you can request a backup and initiate an immediate backup operation with the bmgr tool.
Instructions for the bmgr tool can be found at:
http://developer.android.com/tools/help/bmgr.html
To force all pending backup operations to run immediately, use:
adb shell bmgr run