I am trying to implement a spell checker service as described here called SampleSpellCheckerService but it seems the tutorial is incomplete and the source code for it does not seem to be available.
I am struggling with how to get a session from my spell checker service in the setSuggestionsFor() method of my activity, as highlighted here:
public class SpellCheckerSettingsActivity extends AppCompatActivity implements SpellCheckerSession.SpellCheckerSessionListener {
private static final String LOG_TAG = SpellCheckerSettingsActivity.class.getSimpleName();
private TextView textView = null;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_spell_checker_settings);
final EditText editText = (EditText)findViewById(R.id.editText);
textView = (TextView)findViewById(R.id.textView);
Button button = (Button)findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
fetchSuggestionsFor(editText.getText().toString());
}
});
startService(new Intent(this, SampleSpellCheckerService.class));
}
private void fetchSuggestionsFor(String input){
Log.d(LOG_TAG, "fetchSuggestionsFor(\"" + input + "\")");
/***************************************************
*
* This line is invalid. What do I replace it with?
*
***************************************************/
SpellCheckerSession session = SampleSpellCheckerService.getSession();
TextInfo[] textInfos = new TextInfo[]{ new TextInfo(input) };
int suggestionsLimit = 5;
session.getSentenceSuggestions(textInfos, suggestionsLimit);
}
#Override
public void onGetSuggestions(SuggestionsInfo[] results) {
Log.d(LOG_TAG, "onGetSuggestions(" + results + ")");
runOnUiThread(new Runnable() {
#Override
public void run() {
textView.setText("Suggestions obtained (TODO - get from results[])");
}
});
}
#Override
public void onGetSentenceSuggestions(SentenceSuggestionsInfo[] results) {
Log.d(LOG_TAG, "onGetSentenceSuggestions(" + results + ")");
if (results != null) {
final StringBuffer sb = new StringBuffer("");
for (SentenceSuggestionsInfo result : results) {
int n = result.getSuggestionsCount();
for (int i = 0; i < n; i++) {
int m = result.getSuggestionsInfoAt(i).getSuggestionsCount();
for (int k = 0; k < m; k++) {
sb.append(result.getSuggestionsInfoAt(i).getSuggestionAt(k))
.append("\n");
}
sb.append("\n");
}
}
runOnUiThread(new Runnable() {
#Override
public void run() {
textView.setText(sb.toString());
}
});
}
}
#Override
public void onDestroy() {
stopService(new Intent(this, SampleSpellCheckerService.class));
super.onDestroy();
}
}
So what is the correct way to get a session from SampleSpellCheckerService?
For completeness, here is my spell checker service class:
public class SampleSpellCheckerService extends SpellCheckerService {
public static final String LOG_TAG = SampleSpellCheckerService.class.getSimpleName();
public SampleSpellCheckerService() {
Log.d(LOG_TAG, "SampleSpellCheckerService");
}
#Override
public void onCreate() {
super.onCreate();
Log.d(LOG_TAG, "SampleSpellCheckerService.onCreate");
}
#Override
public Session createSession() {
Log.d(LOG_TAG, "createSession");
return new AndroidSpellCheckerSession();
}
private static class AndroidSpellCheckerSession extends SpellCheckerService.Session {
#Override
public void onCreate() {
Log.d(LOG_TAG, "AndroidSpellCheckerSession.onCreate");
}
#Override
public SentenceSuggestionsInfo[] onGetSentenceSuggestionsMultiple(TextInfo[] textInfos, int suggestionsLimit) {
Log.d(LOG_TAG, "onGetSentenceSuggestionsMultiple");
SentenceSuggestionsInfo[] suggestionsInfos = null;
//suggestionsInfo = new SuggestionsInfo();
//... // look up suggestions for TextInfo
return suggestionsInfos;
}
#Override
public SuggestionsInfo onGetSuggestions(TextInfo textInfo, int suggestionsLimit) {
Log.d(LOG_TAG, "onGetSuggestions");
SuggestionsInfo suggestionsInfo = null;
//suggestionsInfo = new SuggestionsInfo();
//... // look up suggestions for TextInfo
return suggestionsInfo;
}
#Override
public void onCancel() {
Log.d(LOG_TAG, "onCancel");
}
}
}
Here is my manifest:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example">
<permission android:name="android.permission.BIND_TEXT_SERVICE" />
<application
android:allowBackup="true"
android:icon="#mipmap/ic_launcher"
android:label="#string/app_name"
android:supportsRtl="true"
android:theme="#style/AppTheme">
<service
android:name="com.example.SampleSpellCheckerService"
android:label="#string/app_name"
android:enabled="true"
android:permission="android.permission.BIND_TEXT_SERVICE">
<intent-filter>
<action android:name="android.service.textservice.SpellCheckerService" />
</intent-filter>
<meta-data
android:name="android.view.textservice.scs"
android:resource="#xml/spellchecker" />
</service>
<activity android:name="com.example.SpellCheckerSettingsActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
And here is my spellchecker.xml:
<?xml version="1.0" encoding="utf-8"?>
<spell-checker
xmlns:android="http://schemas.android.com/apk/res/android"
android:label="#string/spellchecker_name"
android:settingsActivity="com.example.SpellCheckerSettingsActivity">
<subtype
android:label="#string/subtype_generic"
android:subtypeLocale="en" />
/>
<subtype
android:label="#string/subtype_generic"
android:subtypeLocale="en_GB" />
/>
</spell-checker>
NB - I am testing with a Samsung device.
As far as I can see from the docs and some sample code, there seems to be some misconception of the android Spell Checking API, that result in your error.
As far as I can tell you can't call your service directly since the APIs goal is for you to define a spellchecker that the user has to select from the system settings first. Basically you mixed up the settings activity (that is displayed for service related settings) with a test activity for your service.
Some better tutorials are written in the android dev blog and here, some sample code for a testing client and an rudimentary example service could be found between the mirrored android samples on github.
What you got so far is the sample service (though the linked samples provide some more code to see how the methods could be implemented), you have your spellchecker.xml needed for locale definition and the spellchecker name appearing in the settings, you already have a settings activity (as defined in your spellchecker.xml, but not needed as long as you don't need any preferences) and you have an activity implementing your SpellCheckerSessionListener (although you named it as settings activity).
What you'd still need to do, is go to your settings -> Language & keyboard -> activate Spell checker and choose your spell checker.
To get a session from that spellchecker you can then make a call to the API with
final TextServicesManager tsm = (TextServicesManager) getSystemService(
Context.TEXT_SERVICES_MANAGER_SERVICE);
mScs = tsm.newSpellCheckerSession(null, null, this, true);
as seen in the samples.
Edit:
if you don't need any settings for your service, you can remove the xml attribute from your xml:
android:settingsActivity="com.example.SpellCheckerSettingsActivity"
Related
I am trying to add dynamic feature support to my app, so I created a test app.
The test app has a main app part that loads a dynamic feature and try to execute it.
The feature module has MainActivityCalled as main activity.
What I get is that the feature loading process works because I get successful log messages, and I get the list of installed modules.
Note that the app is run on a virtual device and no real download happens, I thinks everything gets installed automatically by deployment procedure from AndroidStudio.
The fact is that I get this kind of error when trying to call the main activity of the module:
W/System.err: android.content.ActivityNotFoundException: Unable to find explicit activity class {com.example.dynamicfeature1/MainActivityCalled}; have you declared this activity in your AndroidManifest.xml?
W/System.err: at android.app.Instrumentation.checkStartActivityResult(Instrumentation.java:1805)
at android.app.Instrumentation.execStartActivity(Instrumentation.java:1523)
at android.app.Activity.startActivityForResult(Activity.java:4225)
at androidx.fragment.app.FragmentActivity.startActivityForResult(FragmentActivity.java:767)
at android.app.Activity.startActivityForResult(Activity.java:4183)
at androidx.fragment.app.FragmentActivity.startActivityForResult(FragmentActivity.java:754)
at android.app.Activity.startActivity(Activity.java:4522)
at android.app.Activity.startActivity(Activity.java:4490)
at com.example.mymodules.MainActivity$2.onClick(MainActivity.java:227)
at android.view.View.performClick(View.java:5637)
at android.view.View$PerformClick.run(View.java:22429)
at android.os.Handler.handleCallback(Handler.java:751)
at android.os.Handler.dispatchMessage(Handler.java:95)
Everything is done as in the documentation at https://developer.android.com/guide/app-bundle/playcore#java
except for the explicit call to the module activity that I guessed has to be performed with an intent (no example in the above linked page).
Important part of the code you find below are:
new OnSuccessListener<Integer>() {
#Override
public void onSuccess(Integer result) {
Log.d("request feature load","success "+result);
mySessionId=result;
Set<String> installedModules = splitInstallManager.getInstalledModules();
String[] modules = new String[installedModules.size()];
installedModules.toArray(modules);
for (int i=0;i<modules.length;i++)
{
Log.d("module",modules[i]);
}
}
})
that is OK.
Then
Button button2 = findViewById(R.id.fab2);
button2.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
Log.d("feature1","called");
Intent intent=new Intent();
intent.setClassName("com.example.dynamicfeature1","MainActivityCalled");
try{ startActivity(intent);}
catch (Exception e){
e.printStackTrace();
}
}
});
All main activities has this overidden method
#Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(base);
// Emulates installation of on demand modules using SplitCompat.
SplitCompat.installActivity(this);
Log.d("attachBaseContext",base.getPackageName().toString());
}
that is not called in the dynamic feature module when installed.
What is wrong with my code?
This is the Mainactivity of the app
package com.example.mymodules;
...imports...
public class MainActivity extends AppCompatActivity {
private static int MY_REQUEST_CODE=1;
Activity activity;
int mySessionId;
#Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(base);
// Emulates installation of future on demand modules using SplitCompat.
SplitCompat.install(this);
Log.d("attachBaseContext",base.getPackageName().toString());
}
#Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == MY_REQUEST_CODE) {
// Handle the user's decision. For example, if the user selects "Cancel",
// you may want to disable certain functionality that depends on the module.
}
}
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
activity = this;
setContentView(R.layout.activity_main);
Toolbar toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
Button button1 = findViewById(R.id.button1);
button1.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
// Creates an instance of SplitInstallManager.
final SplitInstallManager splitInstallManager =
SplitInstallManagerFactory.create(activity);
// Creates a request to install a module.
SplitInstallRequest request =
SplitInstallRequest
.newBuilder()
// You can download multiple on demand modules per
// request by invoking the following method for each
// module you want to install.
.addModule("dynamicfeature1")
.build();
// Creates a listener for request status updates.
SplitInstallStateUpdatedListener listener =new SplitInstallStateUpdatedListener() {
#Override
public void onStateUpdate(SplitInstallSessionState state) {
if (state.status() == SplitInstallSessionStatus.REQUIRES_USER_CONFIRMATION) {
// Displays a dialog for the user to either “Download”
// or “Cancel” the request.
try {
splitInstallManager.startConfirmationDialogForResult(
state,
/* activity = */ activity,
// You use this request code to later retrieve the user's decision.
/* requestCode = */ MY_REQUEST_CODE);
} catch (IntentSender.SendIntentException e) {
e.printStackTrace();
}
}
if (state.sessionId() == mySessionId) {
switch (state.status()) {
case SplitInstallSessionStatus.INSTALLED:
Context tempNewContext=null;
try {
tempNewContext = activity.createPackageContext(activity.getPackageName(), 0);
} catch (PackageManager.NameNotFoundException e) {
}
final Context newContext =tempNewContext;
// If you use AssetManager to access your app’s raw asset files, you’ll need
// to generate a new AssetManager instance from the updated context.
AssetManager am = newContext.getAssets();
if (BuildCompat.isAtLeastO()) {
// Updates the app’s context with the code and resources of the
// installed module.
SplitInstallHelper.updateAppInfo(newContext);
new Handler().post(new Runnable() {
#Override public void run() {
// Loads contents from the module using AssetManager
AssetManager am = newContext.getAssets();
}
});
} else
{SplitInstallHelper.updateAppInfo(newContext);}
}
}
}
} ;
splitInstallManager.registerListener(listener);
splitInstallManager
// Submits the request to install the module through the
// asynchronous startInstall() task. Your app needs to be
// in the foreground to submit the request.
.startInstall(request)
// You should also be able to gracefully handle
// request state changes and errors. To learn more, go to
// the section about how to Monitor the request state.
.addOnSuccessListener(new OnSuccessListener<Integer>() {
#Override
public void onSuccess(Integer result) {
Log.d("request feature load","success "+result);
mySessionId=result;
Set<String> installedModules = splitInstallManager.getInstalledModules();
String[] modules = new String[installedModules.size()];
installedModules.toArray(modules);
for (int i=0;i<modules.length;i++)
{
Log.d("module",modules[i]);
}
}
})
.addOnFailureListener(new OnFailureListener() {
void checkForActiveDownloads() {
splitInstallManager
// Returns a SplitInstallSessionState object for each active session as a List.
.getSessionStates()
.addOnCompleteListener(
new OnCompleteListener<List<SplitInstallSessionState>>() {
#Override
public void onComplete(Task<List<SplitInstallSessionState>> task) {
if (task.isSuccessful()) {
// Check for active sessions.
for (SplitInstallSessionState state : task.getResult()) {
if (state.status() == SplitInstallSessionStatus.DOWNLOADING) {
// Cancel the request, or request a deferred installation.
}
}
}
}
});
}
#Override
public void onFailure(Exception e) {
Log.d("request feature load","failure "+e.getMessage());
switch (((SplitInstallException) e).getErrorCode()) {
case SplitInstallErrorCode.NETWORK_ERROR:
// Display a message that requests the user to establish a
// network connection.
break;
case SplitInstallErrorCode.ACTIVE_SESSIONS_LIMIT_EXCEEDED:
checkForActiveDownloads();
}
}
});
}
});
Button button2 = findViewById(R.id.button2);
button2.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
Log.d("feature1","called");
Intent intent=new Intent();
intent.setClassName("com.example.dynamicfeature1","MainActivityCalled");
try{ startActivity(intent);}
catch (Exception e){
e.printStackTrace();
}
}
});
Button button3 = findViewById(R.id.button3);
button3.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
Log.d("feature2","called");
}
});
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.menu_main, menu);
return true;
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
int id = item.getItemId();
//noinspection SimplifiableIfStatement
if (id == R.id.action_settings) {
return true;
}
return super.onOptionsItemSelected(item);
}
}
here's the main app manifest
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:dist="http://schemas.android.com/apk/distribution"
package="com.example.mymodules">
<application
android:allowBackup="true"
android:icon="#mipmap/ic_launcher"
android:label="#string/app_name"
android:roundIcon="#mipmap/ic_launcher_round"
android:supportsRtl="true"
android:name="com.google.android.play.core.splitcompat.SplitCompatApplication"
android:theme="#style/AppTheme">
<activity
android:name=".MainActivity"
android:label="#string/app_name"
android:theme="#style/AppTheme.NoActionBar">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
Then there is the MainActivity of the dyamic feature module
package com.example.dynamicfeature1;
...imports...
public class MainActivityCalled extends AppCompatActivity {
#Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(base);
// Emulates installation of on demand modules using SplitCompat.
SplitCompat.installActivity(this);
Log.d("attachBaseContext",base.getPackageName().toString());
}
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Toolbar toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
FloatingActionButton fab = findViewById(R.id.fab);
fab.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
Snackbar.make(view, "1-Replace with your own action", Snackbar.LENGTH_LONG)
.setAction("Action", null).show();
}
});
}
}
and the module manifest
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:dist="http://schemas.android.com/apk/distribution"
package="com.example.dynamicfeature1">
<dist:module
dist:instant="false"
dist:title="#string/title_dynamicfeature1">
<dist:delivery>
<dist:on-demand />
</dist:delivery>
<dist:fusing dist:include="false" />
</dist:module>
<application>
<activity
android:name=".MainActivityCalled"
android:label="#string/title_activity_main"
android:theme="#style/AppTheme.NoActionBar"></activity>
</application>
</manifest>
The right instruction to call the activity is
intent. setClassName(BuildConfig.APPLICATION_ID, "com.example.dynamicfeature.MainActivityCalled");
It works, the activity gets called.
Note that BuildConfig.APPLICATION_ID is
com.example.mymodules
If this string is used in the dynamic modules too, you can make cross-calls:
-from one module to another
-from one module to the main app
but you do not want to use BuildConfig.APPLICATION_ID in modules because there it is a different value, so the main string value has to be put into a variable.
I have the following code that tells when the phone is connected to EDGE or UMTS network. It works on Android 6 (API 23) but when I test
on Android 7 shows the network type only when I launch the app for first time, but there is no update when the phone changes from 2G to 3G and vice versa.
What is needed in order to make this code works updating the newtwork type in the moment that happens for Android 6 and 7+?
public class MainActivity extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
TelephonyManager telephonyManager = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);
PhoneStateListener ConnectionStateListener = new PhoneStateListener() {
#Override
public void onDataConnectionStateChanged(int state, int networkType) {
super.onDataConnectionStateChanged(state, networkType);
String sState = "";
switch (networkType) {
case TelephonyManager.NETWORK_TYPE_EDGE: sState = "EDGE (2G)"; break;
case TelephonyManager.NETWORK_TYPE_UMTS: sState = "UMTS (3G)"; break;
}
Toast.makeText(getApplicationContext(), sState, Toast.LENGTH_SHORT).show();
}
};
telephonyManager.listen(ConnectionStateListener,PhoneStateListener.LISTEN_DATA_CONNECTION_STATE);
}
}
I've added to Manifest file the READ_PHONE_STATE permission as shown below.
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<application
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>
As Lucas pointed out this is most likely due to the ConnectionStateListener being garbage collected. You can fix it simply by moving your listener out of the function to make it a class field:
public class MainActivity extends AppCompatActivity {
private PhoneStateListener ConnectionStateListener = new PhoneStateListener() {
#Override
public void onDataConnectionStateChanged(int state, int networkType) {
super.onDataConnectionStateChanged(state, networkType);
String sState = "";
switch (networkType) {
case TelephonyManager.NETWORK_TYPE_EDGE:
sState = "EDGE (2G)";
break;
case TelephonyManager.NETWORK_TYPE_UMTS:
sState = "UMTS (3G)";
break;
}
Toast.makeText(getApplicationContext(), sState, Toast.LENGTH_SHORT).show();
}
};
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
TelephonyManager telephonyManager = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);
telephonyManager.listen(ConnectionStateListener, PhoneStateListener.LISTEN_DATA_CONNECTION_STATE);
}
}
This way you will keep the reference to the PhoneStateListener, preventing it from being garbage collected.
There was a change in PhoneStateListener in Android 7
I think your question is related to this Android Nougat PhoneStateListener is not triggered
You can use ConnectivityManagerCompat. Or you can also use below code
ConnectivityManager connectivityManager = (ConnectivityManager) context.getSystemService(
Context.CONNECTIVITY_SERVICE);
NetworkInfo activeNetwork = connectivityManager.getActiveNetworkInfo();
String subtype = activeNetwork.getSubtypeName();
if you want to keep observing it then register a dynamic broadcast receiver with intent filter "ConnectivityManager.CONNECTIVITY_ACTION". Please note static receiver might not work post android 6.0
For implementation details
mark this entry in manifest
<receiver
android:name=".receivers.ConnectivityBroadcastReceiver"
android:enabled="#bool/connectivity_change_receiver_enabled"
android:exported="false">
<intent-filter>
<action android:name="android.net.conn.CONNECTIVITY_CHANGE" />
</intent-filter>
And make sure that #bool/connectivity_change_receiver_enabled is true for <=6.0 and false for >=7.0
and then for 7.0 use below code in your Applicaiton class
Context.registerReceiver(new ConnectivityBroadcastReceiver(),
new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION))
After that you will get all the network related chagnes notified in onReceive of ConnectivityBroadcastReceiver
inside values folder define a bools.xml file with following content
<resources>
<bool name="connectivity_change_receiver_enabled">true</bool>
now create another folder named values-v24 parallel to values with following content
<resources>
<bool name="connectivity_change_receiver_enabled">false</bool>
Now you will be able to use the boolean in manifest and then put the dynamic registration of ConnectivityBroadcastReceiver inside API level check
Full code for ConnectivityBroadcastReceiver:
public class ConnectivityBroadcastReceiver extends BroadcastReceiver {
private static final String TAG = "ConnectivityReceiver";
/**
* Informs the service that we have network connectivity.
*
* #param context
* The application context
* #param intent
* An Intent carrying information about the event
*/
#Override
public void onReceive(final Context context, final Intent intent) {
Log.d(TAG, "inside method onReceive::ConnectivityBroadcastReceiver");
// if we are equal to or above API 23 then we can detect if device is in idle mode.
// so we will do nothing if device is in idle mode
if (Build.VERSION.SDK_INT >= 23) {
PowerManager powerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
if(powerManager.isDeviceIdleMode()) {
// log("returning since device is in idle/doze mode");
Log.d(TAG, "returning since device is in idle/doze mode");
return;
}
}
// Do nothing if this is received right after this broadcast is registered.
if (isInitialStickyBroadcast()) {
Log.d(TAG,"This is a initial sticky broadcast. Do nothing.");
return;
}
// either use conventional method
ConnectivityManager connectivityManager = (ConnectivityManager) context.getSystemService(
Context.CONNECTIVITY_SERVICE);
Intent notifyToActivity = new Intent("com.network.state.changed");
String netType = "unknown";
if(connectivityManager!= null) {
NetworkInfo activeNetwork = connectivityManager.getActiveNetworkInfo();
if(activeNetwork!= null) {
netType = activeNetwork.getSubtypeName();
Toast.makeText(context, "network state is " + netType, Toast.LENGTH_LONG).show();
}
}
notifyToActivity.putExtra("NET_TYPE",netType);
context.sendBroadcast(notifyToActivity);
}
}
Full Code for MainActivity:
ublic class MainActivity extends AppCompatActivity {
TextView textureView;
NetworkStateListener networkStateListener;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Toolbar toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
FloatingActionButton fab = findViewById(R.id.fab);
fab.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG)
.setAction("Action", null).show();
}
});
textureView = (TextView) findViewById(R.id.network_type);
networkStateListener = new NetworkStateListener();
this.registerReceiver(networkStateListener, new IntentFilter("com.network.state.changed"));
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.menu_main, menu);
return true;
}
#Override
protected void onDestroy() {
super.onDestroy();
if(networkStateListener!= null) {
this.unregisterReceiver(networkStateListener);
}
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
int id = item.getItemId();
//noinspection SimplifiableIfStatement
if (id == R.id.action_settings) {
return true;
}
return super.onOptionsItemSelected(item);
}
class NetworkStateListener extends BroadcastReceiver{
#Override
public void onReceive(Context context, Intent intent) {
if(intent!= null){
String type = intent.getExtras().getString("NET_TYPE");
textureView.setText(type);
}
}
}
}
Full Code for NetworkAppClass:
public class NetworkAppClass extends Application {
#Override
public void onCreate() {
super.onCreate();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
this.registerReceiver(new ConnectivityBroadcastReceiver(),
new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION));
}
}
}
Full code for Manifest:
<?xml version="1.0" encoding="utf-8"?>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<application
android:name=".NetworkAppClass"
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"
android:label="#string/app_name"
android:theme="#style/AppTheme.NoActionBar">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<receiver
android:name=".ConnectivityBroadcastReceiver"
android:enabled="#bool/connectivity_change_receiver_enabled"
android:exported="false">
<intent-filter>
<action android:name="android.net.conn.CONNECTIVITY_CHANGE" />
</intent-filter>
</receiver>
</application>
Screenshot to change network dynamically from emulator
I am trying to implement simple quick settings tile with the help of google docs,
but my tile appears to be there but greyed out(intent activity)- I can't click or do anything with it and cant remove it either without restarting my phone(one plus 3T/oreo8.0.0).
and the same thing goes with sample code google provided.
what things do i need to keep in mind/ how to do it?
is there anything I am missing?
I saw one similar question but it was a bit over my head.
MANIFEST
<application
android:icon="#mipmap/ic_launcher"
android:label="#string/app_name"
android:roundIcon="#mipmap/ic_launcher_round"
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>
<service
android:name=".QSIntentService"
android:icon="#drawable/ic_android_black_24dp"
android:label="#string/qs_intent_tile_label"
android:permission="android.permission.BIND_QUICK_SETTINGS_TILE">
<intent-filter>
<action android:name="android.service.quicksettings.action.QS_TILE" />
</intent-filter>
</service>
<activity
android:name=".ResultActivity"
android:label="#string/result_label"/>
</application>
JAVA (Main ACtivity)
public class MainActivity extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
}
QSintentservice.java
public class QSIntentService extends TileService{
private static final String SERVICE_STATUS_FLAG = "serviceStatus";
private static final String PREFERENCES_KEY = "com.google.android_quick_settings";
#Override
public void onClick() {
updateTile();
boolean isCurrentlyLocked = this.isLocked();
if (!isCurrentlyLocked) {
Resources resources = getApplication().getResources();
Tile tile = getQsTile();
String tileLabel = tile.getLabel().toString();
String tileState = (tile.getState() == Tile.STATE_ACTIVE) ?
resources.getString(R.string.service_active) :
resources.getString(R.string.service_inactive);
Intent intent = new Intent(getApplicationContext(),
ResultActivity.class);
intent.putExtra(ResultActivity.RESULT_ACTIVITY_NAME_KEY,
tileLabel);
intent.putExtra(ResultActivity.RESULT_ACTIVITY_INFO_KEY,
tileState);
startActivityAndCollapse(intent);
}
}
private void updateTile() {
Tile tile = this.getQsTile();
boolean isActive = getServiceStatus();
Icon newIcon;
String newLabel;
int newState;
if (isActive) {
newLabel = String.format(Locale.US,
"%s %s",
getString(R.string.tile_label),
getString(R.string.service_active));
newIcon = Icon.createWithResource(getApplicationContext(), ic_android_black_24dp);
newState = Tile.STATE_ACTIVE;
} else {
newLabel = String.format(Locale.US,
"%s %s",
getString(R.string.tile_label),
getString(R.string.service_inactive));
newIcon =
Icon.createWithResource(getApplicationContext(),
android.R.drawable.ic_dialog_alert);
newState = Tile.STATE_INACTIVE;
}
tile.setLabel(newLabel);
tile.setIcon(newIcon);
tile.setState(newState);
tile.updateTile();
}
private boolean getServiceStatus() {
SharedPreferences prefs =
getApplicationContext()
.getSharedPreferences(PREFERENCES_KEY,
MODE_PRIVATE);
boolean isActive = prefs.getBoolean(SERVICE_STATUS_FLAG, false);
isActive = !isActive;
prefs.edit().putBoolean(SERVICE_STATUS_FLAG, isActive).apply();
return isActive;
}
}
Result.java
public class ResultActivity extends AppCompatActivity {
public static final String RESULT_ACTIVITY_INFO_KEY = "resultActivityInfo";
public static final String RESULT_ACTIVITY_NAME_KEY = "resultActivityName";
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_result);
if (getIntent() != null) {
Bundle extras = getIntent().getExtras();
assert extras != null;
String tileState = extras.getString(RESULT_ACTIVITY_INFO_KEY);
String tileName = extras.getString(RESULT_ACTIVITY_NAME_KEY);
TextView outputText = findViewById(R.id.result_info);
outputText.setText(String.format(Locale.US,
getString(R.string.result_output),
tileName,
tileState));
TextView returnHome = findViewById(R.id.result_return_main);
returnHome.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
Intent goHome = new Intent(getApplicationContext(),
MainActivity.class);
startActivity(goHome);
}
});
}
}
}
This code works fine on other devices. However, there is an issue in one plus quick setting menu as its observed and brought to notice. Check the below link to verify,
https://forums.oneplus.net/threads/android-oreo-8-0-oxigenos-quick-settings-bug.690621/
I have a wearable app that has a couple of fragments created with FragmentGridPagerAdapter. One of the fragments has a couple of CircularButtons and I want to update the backcolor of the button when a message is received from handheld phone. I have no problems in receiving the message. However, button's color (or anything in UI) doesn't update. Do you know how can I fix this?
public class UIPageAdapter extends FragmentGridPagerAdapter {
private final Context mContext;
MainControlFragment[] mainControlFragments;
private List mRows;
uiChangeListener mUIChangeListener = new uiChangeListener();
public UIPageAdapter(Context ctx, FragmentManager fm) {
super(fm);
Log.i("pageAdapter", "constructor");
mContext = ctx;
mainControlFragments = new MainControlFragment[2];
mainControlFragments[0] = new MainControlFragment();
mainControlFragments[1] = new MainControlFragment();
LocalBroadcastManager.getInstance(ctx).registerReceiver(mUIChangeListener,new IntentFilter(Constants.BROADCAST_CONTROL_HOME));
}
#Override
public Fragment getFragment(int row, int col) {
Log.i("PageAdapter","Fragment #" + col +"is asked");
return mainControlFragments[col];
}
public void changeStatus(int button, boolean status) {
mainControlFragments[0].setStatus(button,status);
// notifyDataSetChanged();
}
public class uiChangeListener extends BroadcastReceiver{
#Override
public void onReceive(Context context, Intent intent) {
String act = intent.getAction();
if (act == Constants.BROADCAST_CONTROL_HOME) {
int key = intent.getIntExtra(Constants.CONTROL_HOME_KEY,-1);
String command = intent.getStringExtra(Constants.CONTROL_HOME_COMMAND);
changeStatus(key,command.equals("on"));
}
}
}
#Override
public int getRowCount() {
return 1;
}
#Override
public int getColumnCount(int i) {
return 2;
}
}
Basically when a message received from the handheld device a WearableListener class broadcasts an update message to the UIPageAdapter
This is the listener class
public class ListenerService extends WearableListenerService
{
String tag = "ListenerService";
#Override
public void onMessageReceived(MessageEvent messageEvent) {
final String message = (new String(messageEvent.getData()));
Log.i(tag,message);
LocalBroadcastManager.getInstance(this).sendBroadcast(new Intent(Constants.BROADCAST_CONTROL_HOME)
.putExtra(Constants.CONTROL_HOME_KEY, messageEvent.getPath())
.putExtra(Constants.CONTROL_HOME_COMMAND,Integer.parseInt(message.substring(1)))
.putExtra("caller",tag));
}
#Override
public void onCreate() {
super.onCreate();
Log.i(tag, "onCreate");
}
}
Manifest file
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="smartstuff.com.tr.myautomationtool" >
<uses-feature android:name="android.hardware.type.watch" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS"/>
<application
android:allowBackup="true"
android:icon="#mipmap/ic_launcher"
android:label="#string/app_name"
android:theme="#android:style/Theme.DeviceDefault" >
<uses-library
android:name="com.google.android.wearable"
android:required="false" />
<service android:name=".ListenerService">
<intent-filter>
<action android:name="com.google.android.gms.wearable.BIND_LISTENER" />
</intent-filter>
</service>
<activity
android:name=".MainActivity"
android:label="#string/app_name"
android:theme="#android:style/Theme.DeviceDefault.Light" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
Finally the custom fragment
public class MainControlFragment extends Fragment{
ViewGroup container;
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
Log.i("controlFragment","create");
this.container = container;
// Inflate the layout for this fragment
return inflater.inflate(R.layout.main_control, container, false);
}
public void setStatus(int button, boolean status) {
Log.i("controlFragment",button + " "+ status);
CircularButton[] btns = new CircularButton[4];
btns[0] = (CircularButton) container.findViewById(R.id.cbtnFront);
btns[1] = (CircularButton) container.findViewById(R.id.cbtnBack);
btns[2] = (CircularButton) container.findViewById(R.id.cbtnBed);
btns[3] = (CircularButton) container.findViewById(R.id.cbtnCoffee);
btns[button].setColor(status?Color.BLACK:Color.RED);
}
}
I also tried the notifyDataSetChanged(); method in UIPageAdapter however it it only calls onCreateView method in fragment. Any help is appreciated
I'm assuming you already resolved this but I had to add a call to invalidate() on the CircularButton after calling setColor():
_circularButton.setColor(ContextCompat.getColor(getActivity(), buttonColor));
_circularButton.invalidate();
Without the call to invalidate the UI only updated some of the time.
My problem is related to the back button and the backstack, ive got a app for reading nfc tags, so ive launch Activity A, then go for Activity B, that is declared SingleTop in the manifest.
I aproach the phone to the tags and it reads the tag, everything is working fine at this moment.
If i press the back button , it goes back to activity B,instead of going to the Activity A, and then if i pressed the backbutton again it goes to Activity A.
Like this:
A->B->read Tags->B->press back button ->B->press back button ->A->press back button ->close app.
and i want like this:
A->B->read Tags->B->press back button-> A->press back button ->close app.
I want only one instance of B.
I have tried single task , but the problem is i ve click in the app icon, and the activity b is launched, but the intent from reading tags is preserved.
public class B extends Activity {
private static final String KINVEY_KEY = YOUR_APP_KEY;
private static final String KINVEY_SECRET_KEY = 'YOUR_APP_SECRET_KEY';
private KCSClient kinveyClient;
private NfcAdapter mNfcAdapter;
private Button mEnableWriteButton;
private EditText mTextField;
private ProgressBar mProgressBar;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_tags);
mTextField = (EditText) findViewById(R.id.text_field);
mProgressBar = (ProgressBar) findViewById(R.id.progress_bar);
mProgressBar.setVisibility(View.GONE);
mEnableWriteButton = (Button) findViewById(R.id.enable_write_button);
mEnableWriteButton.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
setTagWriteReady(!isWriteReady);
mProgressBar.setVisibility(isWriteReady ? View.VISIBLE : View.GONE);
}
});
mNfcAdapter = NfcAdapter.getDefaultAdapter(this);
if (mNfcAdapter == null) {
Toast.makeText(this, 'Sorry, NFC is not available on this device', Toast.LENGTH_SHORT).show();
finish();
}
// Initialize Kinvey
KinveySettings settings = new KinveySettings(KINVEY_KEY, KINVEY_SECRET_KEY);
kinveyClient = KCSClient.getInstance(this.getApplicationContext(), settings);
}
private boolean isWriteReady = false;
public void setTagWriteReady(boolean isWriteReady) {
this.isWriteReady = isWriteReady;
if (isWriteReady) {
IntentFilter[] writeTagFilters = new IntentFilter[] { new IntentFilter(NfcAdapter.ACTION_TAG_DISCOVERED) };
mNfcAdapter.enableForegroundDispatch(TagsActivity.this, NfcUtils.getPendingIntent(TagsActivity.this),
writeTagFilters, null);
} else {
// Disable dispatch if not writing tags
mNfcAdapter.disableForegroundDispatch(TagsActivity.this);
}
mProgressBar.setVisibility(isWriteReady ? View.VISIBLE : View.GONE);
}
#Override
public void onNewIntent(Intent intent) {
// onResume gets called after this to handle the intent
setIntent(intent);
}
#Override
public void onResume() {
super.onResume();
if (isWriteReady && NfcAdapter.ACTION_TAG_DISCOVERED.equals(getIntent().getAction())) {
processWriteIntent(getIntent());
} else if (!isWriteReady
&& (NfcAdapter.ACTION_TAG_DISCOVERED.equals(getIntent().getAction()) || NfcAdapter.ACTION_NDEF_DISCOVERED
.equals(getIntent().getAction()))) {
processReadIntent(getIntent());
}
}
private static final String MIME_TYPE = 'application/com.tapped.nfc.tag';
public void processWriteIntent(Intent intent) {
if (isWriteReady && NfcAdapter.ACTION_TAG_DISCOVERED.equals(getIntent().getAction())) {
Tag detectedTag = getIntent().getParcelableExtra(NfcAdapter.EXTRA_TAG);
String tagWriteMessage = mTextField.getText().toString();
byte[] payload = new String(tagWriteMessage).getBytes();
if (detectedTag != null && NfcUtils.writeTag(
NfcUtils.createMessage(MIME_TYPE, payload), detectedTag)) {
Toast.makeText(this, "Wrote '" + tagWriteMessage + "' to a tag!",
Toast.LENGTH_LONG).show();
setTagWriteReady(false);
} else {
Toast.makeText(this, "Write failed. Please try again.", Toast.LENGTH_LONG).show();
}
}
}
public void processReadIntent(Intent intent) {
List<NdefMessage> intentMessages = NfcUtils.getMessagesFromIntent(intent);
List<String> payloadStrings = new ArrayList<String>(intentMessages.size());
for (NdefMessage message : intentMessages) {
for (NdefRecord record : message.getRecords()) {
byte[] payload = record.getPayload();
String payloadString = new String(payload);
if (!TextUtils.isEmpty(payloadString))
payloadStrings.add(payloadString);
}
}
if (!payloadStrings.isEmpty()) {
String content = TextUtils.join(",", payloadStrings);
Toast.makeText(TagsActivity.this, "Read from tag: " + content,
Toast.LENGTH_LONG).show();
saveTag(content);
}
}
private void saveTag(String tagMessage){
TagReadEntity tag = new TagReadEntity(UUID.randomUUID().toString(),
tagMessage, System.currentTimeMillis());
kinveyClient.mappeddata("tags").save(tag, new ScalarCallback<TagReadEntity>() {
#Override
public void onSuccess(TagReadEntity tag) {
Log.i("NFC Demo", "Saved tag!");
}
#Override
public void onFailure(Throwable e) {
Log.e("NFC Demo", "Error saving tag", e);
}
});
}
}
and the manifest:
<activity
android:name=".TagsActivity"
android:label="#string/title_activity_tags"
android:launchMode="singleTop"
android:screenOrientation="portrait" >
<intent-filter>
<action android:name="android.nfc.action.NDEF_DISCOVERED" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="application/com.tapped.nfc.tag" />
</intent-filter>
<intent-filter>
<action android:name="android.nfc.action.TAG_DISCOVERED" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="application/com.tapped.nfc.tag" />
</intent-filter>
</activity>
ive declared the task as singletask, it seems to work now.
android:launchMode="singleTask"
ive tried standart, and everytime ive read a nfc tags, a new activity was created, if a read 10 tags, i need ti press the back button 9times.
after ive tried singleTop, and now i was going from A to B, then no matter how many times i read the tags there was only 2 instances of B, so i need to press back once to go to activity B, and back again to close the app.