I'm trying to add a custom quick settings tile for my application. I've followed the sample code/documentation from Google but I'm running into some issues. After searching for some time I couldn't find any solutions.
When I run my app, the tile is visible in the quick settings tray, but it remains in an unavailable state.
I need a two-way flow of communication with the tile, i.e., when the user selects the tile, the app responds and when the user does a certain action within the app, the tile UI is toggled.
The problem seems to come from attempting to bind my custom TileService class to my MainActivity -- whenever I bind it is when the tile goes into a consistently unavailable state. I can't seem to figure out why though because it's being bound successfully. If I don't bind it (i.e. just have the one way communication of the tile commanding the app), the tile is active and the app responds to selecting it.
Ultimately I do the binding to attain a reference to my custom TileService class to be able to call it's method toggleTileUI(). I'm not looking to use a singleton or static member variable as a solution to getting a reference to my service.
Here's what I have in my AndroidManifest.xml:
<service
android:name=".ConnectionQuickSettingsService"
android:label="#string/quick_setting_tile_connect"
android:icon="#drawable/tile_icon"
android:permission="android.permission.BIND_QUICK_SETTINGS_TILE">
<intent-filter>
<action android:name="android.service.quicksettings.action.QS_TILE" />
</intent-filter>
</service>
Here's my ConnectionQuickSettingsService.java:
#TargetApi(24)
public class ConnectionQuickSettingsService extends TileService {
private static final String TAG = "ConnectionQuickSettingsService";
private final IBinder mBinder = new LocalBinder();
public class LocalBinder extends Binder {
ConnectionQuickSettingsService getService() {
return ConnectionQuickSettingsService.this;
}
}
#Override
public IBinder onBind(Intent i) {
return mBinder;
}
#Override
public void onTileAdded() {
L.d(TAG, "onTileAdded()");
}
#Override
public void onStartListening() {
L.d(TAG, "onStartListening()");
}
#Override
public void onClick() {
L.d(TAG, "Quick Settings tile selected");
toggleInAppSwitch();
toggleTileUI();
}
private void toggleInAppSwitch() {
doStuff();
}
public void toggleTileUI() {
Tile tile = this.getQsTile();
doStuffWithTile();
}
}
and lastly (the relevant parts of) my MainActivity.java:
#Override
protected void onStart() {
super.onStart();
Intent i = new Intent(this, ConnectionQuickSettingsService.class);
bindService(i, mConnection, Context.BIND_AUTO_CREATE);
}
#Override
protected void onStop() {
super.onStop();
if (mBound) {
unbindService(mConnection);
mBound = false;
}
}
private ServiceConnection mConnection = new ServiceConnection() {
#Override
public void onServiceConnected(ComponentName className,
IBinder service) {
ConnectionQuickSettingsService.LocalBinder binder = (ConnectionQuickSettingsService.LocalBinder) service;
mQSService = binder.getService();
mBound = true;
L.d(TAG, "Bound to QS service successfully");
}
#Override
public void onServiceDisconnected(ComponentName arg0) {
mBound = false;
L.d(TAG, "Disconnected from QS service");
}
};
public void onOnOff(){
L.d(TAG, "On/Off switch toggled");
if (mBound) {
mQSService.toggleTileUI();
}
}
Any input would be greatly appreciated, thanks!
Part of your problem is that you are overriding onBind(), which clobbers the TileService implementation of onBind(), preventing Android from working with your ConnectionQuickSettingsService. I would expect that there would be error messages in LogCat from a system process complaining about this.
The other part of your problem is that you assume that getQsTile() works at arbitrary points in time. It doesn't.
I recommend finding some other interaction pattern with your tile, such as using META_DATA_ACTIVE_TILE and requestListeningState(), and getting rid of your Binder and onBind() method.
I don't think you need all the service connection. I would add the meta data META_DATA_ACTIVE_TILE to the tile service manifest declaration, and then you can call TileService.requestListeningState(context, new ComponentName(context, TileService.class)); which will trigger onStartListening().
After that onStartListening() could call a method to update the Tile state/label/icon.
Also in your Tile service method to update the tile
I would add a check if (getQsTile !=null)
Related
Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 5 years ago.
Improve this question
I have made an App in android in which i have a TCP Client but now i want to send a message to the server only when the app is going to be closed, i've trying to add the openConnection (opening connection with TCP Client) and sendMessage (Sending message to the TCP server) action in onDestroy method but that didn't worked.
The TCP Client i've used is in this guide, actually i need to send this message for communicate the server that the communication with the device is closed and send message "Device is Offline" and just then close the app.
Method 1: You can use ActivityLifecycleCallbacks to achieve this. There's an example with some logs below.
public class MyApplication extends Application {
private static final String TAG = MyApplication.class.getSimpleName();
private int mVisibleCount;
private boolean mInBackground;
#Override public void onCreate() {
super.onCreate();
registerActivityLifecycleCallbacks(new ActivityLifecycleCallbacks() {
#Override public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
}
#Override public void onActivityStarted(Activity activity) {
mVisibleCount++;
if (mInBackground && mVisibleCount > 0) {
mInBackground = false;
Log.i(TAG, "App in foreground");
}
}
#Override public void onActivityResumed(Activity activity) {
}
#Override public void onActivityPaused(Activity activity) {
}
#Override public void onActivityStopped(Activity activity) {
mVisibleCount--;
if (mVisibleCount == 0) {
if (activity.isFinishing()) {
Log.i(TAG, "App is finishing");
} else {
mInBackground = true;
Log.i(TAG, "App in background");
}
}
}
#Override public void onActivitySaveInstanceState(Activity activity, Bundle outState) {
}
#Override public void onActivityDestroyed(Activity activity) {
}
});
}
public boolean isAppInBackground() {
return mInBackground;
}
public boolean isAppVisible() {
return mVisibleCount > 0;
}
public int getVisibleCount() {
return mVisibleCount;
}
}
Method 2: There's another method using Service to detect if application is terminated. See link
Following method call on diff action
Minimize the Application using home button --> this calls
onPause()
onStop()
Remove the app from Task Manager ->
then onDestroy() is called for that MainActivity (launcher).
So Make an parent Activity suppose named BaseActivity and override its
onPause(), onDestroy() and onStop() method and call your
implementation from here and then extend this Activity from your
another Activities
You should not rely on the onDestroy method. As per the official android documentation it is not called always.
Try to put the same code in the onStop() method of your activity.
Additionally you can have a parent activity which is extended by all other activities.
You can then override the onPause() and onStop() method in your particular child activities and handle the specific scenarios.
I had previously managed to get my service to continue running. But today after I went back to the application to keep working with the app I noticed that the service gets disconnected after the app is closed which was not happening before.
I start the service in my main activity like this:
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// setContentView(R.layout.activity_main);
if(savedInstanceState == null) {
setContentView(R.layout.activity_main);
Intent i = new Intent(MainActivity.this, SystemWebService.class);
SystemWebService.setMain(this);
MainActivity.this.startService(i);
}
The code in the SystemWebService class looks like this:
public class SystemWebService extends Service {
private static WebView webdemo;
private static MainActivity ma;
//Context dex;
#Nullable
#Override
public IBinder onBind(Intent intent) {
return null;
}
#Override
public int onStartCommand(Intent intent, int flags, int startId){
// let the service continue until stopped
Toast.makeText(this, "Service has started", Toast.LENGTH_SHORT).show();
return START_STICKY;
}
#Override
public void onDestroy(){
super.onDestroy();
Toast.makeText(this, "Service has been destroyed", Toast.LENGTH_SHORT).show();
}
#Override
public void onCreate() {
super.onCreate();
}
public static void setMain(MainActivity a) {
ma = a;
SystemWebService sws = new SystemWebService();
sws.setView();
}
}
This is the code that sets the webview
public void setView() {
webdemo = (WebView) ma.findViewById(R.id.webdemo);
webdemo.addJavascriptInterface(new SystemWebService.WebAppInterface(this), "Android");
webdemo.setWebViewClient(new WebViewClient(){
#Override
public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest url){
return false;
}
});
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
webdemo.getSettings().setJavaScriptEnabled(true);
webdemo.getSettings().setDomStorageEnabled(true);
webdemo.getSettings().setAllowFileAccessFromFileURLs(true);
webdemo.getSettings().setAllowUniversalAccessFromFileURLs(true);
}
webdemo.loadUrl("file:///android_asset/_wv.html");
}
The webview establishes a connection to our server and I would normally see data display on the logs. Before I would continue to see the flux of data passing even after the application was closed but as of right not I am not seeing anything, only the message "Application terminated".
I can't seem to understand what is happening.
Passing a reference of your activity to the service, and keeping a static instance of it is not the recommended way of development. It can lead to unexpected behaviour during the life cycle of your activity or the service.
Use the Bound Services instead. Check the documentation here : https://developer.android.com/guide/components/bound-services.html
I'm working on an App where i want to stream a specific video, which is located on my mobile device. I want to stream this video with the help of ChromeCast to a projector. I've done the Google Chromecast Tutorials (https://codelabs.developers.google.com/codelabs/cast-videos-android/#0 for example) and already looked for an answer to my Question but there wasn't the perfect answer to solve my problem in my opinion.
There are a few solutions in other coding languages but not especially for java.
My problem with the Google Cast API is: I want to select my ChromeCast without pressing the Cast Button in the OptionMenu, which appears to select one of the ChromeCasts in the Network.
I just found out, that the setRouteSelector-Methode from the class CastButtonFactory is a part to select the Device. But i can't find out, how to select the device automatically.
Here is my Code where the CastButton appears (created in the OnCreateOptionsMenu-Method)(It's just the Code to Connect to the ChromeCast. I didn't implement the Code for selecting and streaming the video yet):
Can anyone help me?
Code:
public class MainActivity extends AppCompatActivity {
private CastSession mCastSession;
private CastContext mCastContext;
private SessionManager mSessionManager;
private final SessionManagerListener mSessionManagerListener = new SessionManagerListenerImpl();
private class SessionManagerListenerImpl implements SessionManagerListener{
#Override
public void onSessionStarting(Session session) {}
#Override
public void onSessionStarted(Session session, String sessionId) {
invalidateOptionsMenu();
}
#Override
public void onSessionStartFailed(Session session, int i) {}
#Override
public void onSessionEnding(Session session) {}
#Override
public void onSessionEnded(Session session, int error) {
finish();
}
#Override
public void onSessionResuming(Session session, String s) {}
#Override
public void onSessionResumed(Session session, boolean wasSuspended) {
invalidateOptionsMenu();
}
#Override
public void onSessionResumeFailed(Session session, int i) {}
#Override
public void onSessionSuspended(Session session, int i) {}
}
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mCastContext = CastContext.getSharedInstance(this);
mCastContext.registerLifecycleCallbacksBeforeIceCreamSandwich(this, savedInstanceState);
mSessionManager = CastContext.getSharedInstance(this).getSessionManager();
}
#Override
public void onResume(){
mCastSession = mCastContext.getSessionManager().getCurrentCastSession();
mSessionManager.addSessionManagerListener(mSessionManagerListener);
super.onResume();
}
#Override
public void onPause(){
super.onPause();
mSessionManager.removeSessionManagerListener(mSessionManagerListener);
mCastSession = null;
}
#Override
public void onStart(){
super.onStart();
}
#Override public boolean onCreateOptionsMenu(Menu menu) {
super.onCreateOptionsMenu(menu);
getMenuInflater().inflate(R.menu.menu, menu);
CastButtonFactory.setUpMediaRouteButton(getApplicationContext(),
menu,
R.id.media_route_menu_item);
return true;
}}
This is possible with SDK v2. Check out the source code for MediaRouteChooserDialogFragment, the UI element that lets users select a Cast device. You'll want to create a MediaRouter.Callback, and override onRouteAdded(MediaRouter router, MediaRouter.RouteInfo newRoute). If the new route is not the default route, select it with mMediaRouter.selectRoute(newRoute). This will connect you to the receiver device without any user interaction.
This should work for testing, but please don't do this in a production app. #AliNaddaf correctly notes this is against the UX guidelines and Google Cast terms of service (you can imagine the potential for abuse if apps are permitted to start casting without the user's consent).
I'm making a litle game in android studio where I want background music to play when the user is interacting with the application (and have it turned on in the settings). For this I used a service for playing backgroundmusic. As you can see below
import android.app.Service;
import android.content.Intent;
import android.media.MediaPlayer;
import android.os.IBinder;
public class BackgroundMusicService extends Service {
MediaPlayer player;
public IBinder onBind(Intent arg0) {
return null;
}
#Override
public void onCreate() {
super.onCreate();
player = MediaPlayer.create(this, R.raw.backgroundmusic);
player.setLooping(true); // Set looping
}
public int onStartCommand(Intent intent, int flags, int startId) {
player.start();
return 1;
}
#Override
public void onDestroy() {
player.stop();
player.release();
}
#Override
public void onLowMemory() {
}
}
I want my backgroundmusic to play on all my activities, but stop when the user leaves the application, as it is impossible to detect a menu button press or a home button press in android. I tried to solve it like this.
import java.util.Timer;
import java.util.TimerTask;
public class LeaveAppDetector extends BackgroundMusicService{
private boolean Deactivated;
private int Time = 700;
private int wait = 0;
private Timer timer = new Timer();
public void Activate() {
Deactivated = false;
timer.schedule(new TimerTask() {
#Override
public void run() {
if (wait >= 1) {
if (!getDeactivate()) {
//app has been closed
StopPlaying();
StopTimer();
} else {StopTimer();} //app has not been closed
}
wait++;
}
}, Time);
}
public void Deactivate() { //call at the start of each activity
Deactivated = true;
}
private boolean getDeactivate() {
return Deactivated;
}
private void StopTimer() {
if (timer != null) {
timer.cancel();
timer.purge();
}
}
}
The idea is that when an activity starts, it calls Deactivate(); and when it closes, it calls Activate(); so that the value of Activated is updated after a short period of time. I added the following method to my BackgrondMusicService class in order to be able to turn it off remotely
public void StopPlaying() {
player.stop();
player.release();
}
Now the problem, it gives me the error that the reference to the mediaplayers in the method I just showed, is one referring to a null object, and I don't know why or why it won't work. Can someone help?
You need to use a Bound Service like this answer suggest:
https://stackoverflow.com/a/2660696/3742122
If you only want the service running while activities are using it, consider getting rid of startService(). Instead, use bindService() in the onStart() methods of the activities that need the service, and call unbindService() in their corresponding onStop() methods. You can use BIND_AUTO_CREATE to have the service be lazy-started when needed, and Android will automatically stop the service after all connections have been unbound.
I'm relatively new to Android,
I have read related articles on detecting network connectivity changes and have implemented this BroadcastReceiver subclass, made the necessary additions to AndroidManifest.xml and I receive the requisite state change broadcasts as expected:
public class NetworkStateReceiver extends BroadcastReceiver {
#Override
public void onReceive(Context context, Intent intent) {
}
}
Question is: how can I receive or forward these notifications in/to my Activity subclasses? Apparently creating an instance of NetworkStateReceiver in my Activity subclass and overriding onReceive there doesn't do the trick.
Thanks in advance for any pointers...
Edit:
I ended up broadcasting an Intent from onReceive above like so:
Intent target = new Intent(CONNECTIVITY_EVENT);
target.putExtra(CONNECTIVITY_STATE, networkInfo.isConnected());
context.sendBroadcast(target);
And receiving that in my Activity like so:
#Override
protected String[] notifyStrings() {
return ArrayUtils.addAll(super.notifyStrings(), new String[] {NetworkStateReceiver.CONNECTIVITY_EVENT});
}
#Override
protected void notifyEvent(Intent intent, String action) {
super.notifyEvent(intent, action);
if (action != null) {
if (action.equalsIgnoreCase(NetworkStateReceiver.CONNECTIVITY_EVENT)) {
boolean isConnected = intent.getBooleanExtra(NetworkStateReceiver.CONNECTIVITY_STATE, true);
// Do something...
}
}
}
I would recommend using either
1) An interface approach. So declare an interface that has a networkChanged() method, and have the class which owns this BroadcastReceiver keep a list of classes who want to be notified of network changes with a local List<InterfaceName>
2) Skip the interface creating and use a subscription utility. My two favorites are
https://github.com/greenrobot/EventBus
and
https://gist.github.com/bclymer/6708819 (smaller, less used, also disclaimer: I wrote this)
With these you would create event classes with properties, and then subscribe and post instances of those classes.
In your activity
#Override
public void onCreate() {
...
EventBus.getInstance().subscribe(this, MyType.class);
}
#Override
public void onDestroy() {
...
EventBus.getInstance().unsubscribe(this, MyType.class);
}
#Override
public void newEvent(Object event) {
if (event instanceOf MyType) {
// do stuff
}
}
And then in your BroadcastReceiver
#Override
public void onReceive(Context context, Intent intent) {
EventBus.post(new MyType(true));
}
Example MyType
public class MyType {
public boolean networkEnabled;
public MyType(boolean networkEnabled) {
this.networkEnabled = networkEnabled;
}
}
This examples use the 2nd subscription utility (mine).