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>
Related
Below is the code code of Android manifest
<application
android:name="com.example.m1.parsedemo.MainActivity" // this line show error
android:allowBackup="true"
android:icon="#mipmap/ic_launcher"
android:label="#string/app_name"
android:roundIcon="#mipmap/ic_launcher_round"
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>
and the main activity java file is
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Parse.enableLocalDatastore(this);
Parse.initialize(new Parse.Configuration.Builder(this)
.applicationId("31e1ed2c6158fa0d01c3a5201a15e4af3f228a5b")
// if define
.clientKey("7d930e5059ba7ca44ce7083a6708b99eb70d9b4b")
.server("http://35.154.249.137:80/parse/")
.build()
);
ParseUser.enableRevocableSessionInBackground();
ParseObject gameScore = new ParseObject("Manish");
gameScore.put("score", 1337);
gameScore.put("playerName", "Manish");
gameScore.put("cheatMode", false);
gameScore.saveInBackground(new SaveCallback() {
#Override
public void done(ParseException e) {
if(e == null) {
Log.i("sucess","sucess");
} else {
Log.i("error",e.toString());
}
}
});
}
I am following this guideline -> parse
Removing
android:name=""
shows no error, but cant connect to the server and adding it, show error. I am stuck here cant connect to parse server.
And also please tell me is there any wrong code in main activity java file.
Thanx
You won't need this or you're referencing the wrong file in here:
android:name="com.example.m1.parsedemo.MainActivity" // this line show error
Which you already have:
<activity android:name=".MainActivity"
If you're trying to reference the Application class of your project, use a different name and create another class like calling it App, then:
android:name=".App"
In your AndroidManifest.xml application tag and these codes inside the onCreate() of the App class:
import com.parse.Parse;
import android.app.Application;
public class App extends Application {
#Override
public void onCreate() {
super.onCreate();
Parse.initialize(new Parse.Configuration.Builder(this)
.applicationId("31e1ed2c6158fa0d01c3a5201a15e4af3f228a5b")
// if define
.clientKey("7d930e5059ba7ca44ce7083a6708b99eb70d9b4b")
.server("http://35.154.249.137:80/parse/")
.build()
);
}
}
Just like the documentation.
This should be name of your application class, not the activity class
<application
android:name="com.example.m1.parsedemo.MainActivity" // this line show error
</application
correct it with the application class which looks like this
public class App extends Application {
#Override
public void onCreate() {
super.onCreate();
Parse.initialize(new Parse.Configuration.Builder(this)
.applicationId("YOUR_APP_ID")
// if defined
.clientKey("YOUR_CLIENT_KEY")
.server("http://localhost:1337/parse/")
.build()
);
}
}
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 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!
When I launch my app from Eclipse to my phone, it launches the Launcher first and then my main activity. It successfully passes a variable to the main activity. I check that the username has been logged with a toast.
When I launch my app from my phone directly, it goes straight to the main activity; the main activity registers the variable as null.
I created a test app that performs EXACTLY the same function as the launcher in this one; their manifests are identical except for the activity names; and that test app functions correclty from the phone and when I install it from Eclipse.
this is a real brain-teaser.
Here is the code for the starter activity:
public class SecureAppStarter extends Activity {
TextView report;
WebView input;
Context thisContext = this;
String thisValue, url;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.starter);
initialize();
WebViewClient rclient = new WebViewClient(){
#Override
public boolean shouldOverrideUrlLoading(WebView view, String url){
return false;
}
#Override
public void onLoadResource(WebView view, String url){
input.getSettings().setJavaScriptEnabled(true);
input.addJavascriptInterface(new CustomJavaScriptInterface(thisContext), "Android");
}
#Override
public void onPageFinished(WebView view, String url){
report.setText(""+thisValue);
if (thisValue!= null){
Intent passOn = new Intent("arbuckle.app.MainActivity");
passOn.putExtra("username", thisValue);
startActivity(passOn);
}
}
};
rclient.onPageFinished(input, url);
input.setWebViewClient(rclient);
input.loadUrl(url);
}
public class CustomJavaScriptInterface {
Context mContext;
CustomJavaScriptInterface(Context context) {
mContext = context;
}
public void getValue(String value){
thisValue = value;
}
}
private void initialize() {
report = (TextView) findViewById(R.id.tvViewName);
input = (WebView) findViewById(R.id.wbWebAuth);
url = "http://URL.of.data.com";
}
}
And here is the manifest:
<application
android:icon="#drawable/ic_launcher"
android:label="#string/app_name" >
<activity
android:name="SecureAppStarter"
android:label="#string/app_name"
android:configChanges="keyboardHidden|orientation">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name=".ArbuckleAppActivity"
android:label="#string/app_name"
android:configChanges="keyboardHidden|orientation">
<intent-filter>
<action android:name="arbuckle.app.ArbuckleAppActivity" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
</application>
Well I figured it out.
I have been installing/uninstalling versions of the app for weeks where the main activity was the launcher. clearly that gets kept somewhere in Android cache and it was choosing the main activity to launch even though it wasn't the launcher anymore.
So I uninstalled the app completely; and reinstalled. Now it works.
Does anyone think I hacked at the problem instead of solving it? Should I expect further problems down the road?