(Android) On a music player, you update the seekbar as expected with this:
PRECISION_SEEKBAR = 100000;
((SeekBar) findViewById(R.id.seekBar2)).setMax(PRECISION_SEEKBAR);
timerSeekBarUpdate.scheduleAtFixedRate(new TimerTask() {
#Override
public void run() {
runOnUiThread(new Runnable() {
final SeekBar seekBar = (SeekBar) findViewById(R.id.seekBar);
#Override
public void run() {
if (control == null || player == null) {
cancel();
return;
}
seekBar.setProgress((int) (player.getCurrentPosition() * PRECISION_SEEKBAR / player.getDuration()));
...
However, if the focus is on the seek bar, talkback steadily and nonstop gives feedback for the progress. Like "seek control 25%", "seek control 25%", "seek control 25%", "seek control 26%", "seek control 26%", "seek control 27%"
I'm missing sth but couldnot solve the problem. I have set the contentDescription to other than #null. But then it reads the content description this time without stopping.
On Spotify client, I checked, it reads the progress as "xx percent" just once. Despite saving the focus on the seekbar.
When I edit the precision for 1 or 100, then you lose the precision on the seekbar. It looks like there are a few parts in the song. You either play one or another by swiping on the seekbar.
Has anybody experienced sth like this? I couldn't find anything on google docs, stack network or somewhere else.
You can just override sendAccessibilityEvent() so it ignores description updates:
#Override
public void sendAccessibilityEvent(int eventType) {
if (eventType != AccessibilityEvent.CONTENT_CHANGE_TYPE_CONTENT_DESCRIPTION) {
super.sendAccessibilityEvent(eventType);
}
}
As Altoyyr mentioned, this has the side effect of ignore ALL description updates, including scrolling with volume buttons. So you'll need add back sending the event for volume press actions:
#Override
public boolean performAccessibilityAction(int action, Bundle arguments) {
switch (action) {
case AccessibilityNodeInfo.ACTION_SCROLL_FORWARD:
case AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD: {
super.sendAccessibilityEvent(AccessibilityEvent.CONTENT_CHANGE_TYPE_CONTENT_DESCRIPTION);
}
}
return super.performAccessibilityAction(action, arguments);
}
I had the problem and found that SeekBar reads the percentage on every update.
It helped, that I update the SeekBar only when the percentage changes but still keep a high precision (in my case in ms).
#Override
public void updateSeekBar(final int currentPosInMillis, final int durationInMillis) {
long progressPercent = calculatePercent(currentPosInMillis, durationInMillis);
if (progressPercent != previousProgressPercent) {
seekBar.setMax(durationInMillis);
seekBar.setProgress(currentPosInMillis);
}
previousProgressPercent = progressPercent;
}
private int calculatePercent(int currentPosInMillis, int durationInMillis) {
if(durationInMillis == 0) {
return 0;
}
return (int) (((float)currentPosInMillis / durationInMillis) * 100);
}
previousProgressPercent is initialized to -1.
Please note that this solution is not the same as Spotify does it.
Spotify overrides the message announced by the system when the SeekBar gets selected.
This has following 2 effects:
Updates can be made as often as you want without the percentage beeing repeated
When the percentage changes while the SeekBar is selected then nothing gets announced
Point 2 might me a drawback depending on what you want to achieve.
Related
Scenario:
I ran into a strange issue while testing out threads in my fragment.
I have a fragment written in Kotlin with the following snippet in onResume():
override fun onResume() {
super.onResume()
val handlerThread = HandlerThread("Stuff")
handlerThread.start()
val handler = Handler(handlerThread.looper)
handler.post {
Thread.sleep(2000)
tv_name.setText("Something something : " + isMainThread())
}
}
is MainThread() is a function that checks if the current thread is the main thread like so:
private fun isMainThread(): Boolean = Looper.myLooper() == Looper.getMainLooper()
I am seeing my TextView get updated after 2 seconds with the text "Something something : false"
Seeing false tells me that this thread is currently not the UI/Main thread.
I thought this was strange so I created the same fragment but written in Java instead with the following snippet from onResume():
#Override
public void onResume() {
super.onResume();
HandlerThread handlerThread = new HandlerThread("stuff");
handlerThread.start();
new Handler(handlerThread.getLooper()).post(new Runnable() {
#Override
public void run() {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
textView.setText("Something something...");
}
});
}
The app crashes with the following exception as expected:
android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
at android.view.ViewRootImpl.checkThread(ViewRootImpl.java:7313)
at android.view.ViewRootImpl.requestLayout(ViewRootImpl.java:1161)
I did some research but I couldn't really find something that explains this. Also, please assume that my views are all inflated correctly.
Question:
Why does my app not crash when I modify my TextView in the runnable that's running off my UI thread in the Fragment written in Kotlin?
If there's something in some documentation somewhere that explains this, can someone please refer me to this?
I am not actually trying to modify my UI off the UI thread, I am just curious why this is happening.
Please let me know if you guys need any more information. Thanks a lot!
Update:
As per what #Hong Duan mentioned, requestLayout() was not getting called. This has nothing to do with Kotlin/Java but with the TextView itself.
I goofed and didn't realize that the TextView in my Kotlin fragment has a layout_width of "match_parent." Whereas the TextView in my Java fragment has a layout_width of "wrap_content."
TLDR: User error + requestLayout(), where thread checking doesn't always occur.
The CalledFromWrongThreadException only throws when necessary, but not always. In your cases, it throws when the ViewRootImpl.checkThread() is called during ViewRootImpl.requestLayout(), here is the code from ViewRootImpl.java:
#Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true;
scheduleTraversals();
}
}
void checkThread() {
if (mThread != Thread.currentThread()) {
throw new CalledFromWrongThreadException(
"Only the original thread that created a view hierarchy can touch its views.");
}
}
And for TextView, it's not always necessary to relayout when we update it's text, we can see the logic in the source code:
/**
* Check whether entirely new text requires a new view layout
* or merely a new text layout.
*/
private void checkForRelayout() {
// If we have a fixed width, we can just swap in a new text layout
// if the text height stays the same or if the view height is fixed.
if ((mLayoutParams.width != LayoutParams.WRAP_CONTENT
|| (mMaxWidthMode == mMinWidthMode && mMaxWidth == mMinWidth))
&& (mHint == null || mHintLayout != null)
&& (mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight() > 0)) {
// Static width, so try making a new text layout.
int oldht = mLayout.getHeight();
int want = mLayout.getWidth();
int hintWant = mHintLayout == null ? 0 : mHintLayout.getWidth();
/*
* No need to bring the text into view, since the size is not
* changing (unless we do the requestLayout(), in which case it
* will happen at measure).
*/
makeNewLayout(want, hintWant, UNKNOWN_BORING, UNKNOWN_BORING,
mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(),
false);
if (mEllipsize != TextUtils.TruncateAt.MARQUEE) {
// In a fixed-height view, so use our new text layout.
if (mLayoutParams.height != LayoutParams.WRAP_CONTENT
&& mLayoutParams.height != LayoutParams.MATCH_PARENT) {
autoSizeText();
invalidate();
return; // return with out relayout
}
// Dynamic height, but height has stayed the same,
// so use our new text layout.
if (mLayout.getHeight() == oldht
&& (mHintLayout == null || mHintLayout.getHeight() == oldht)) {
autoSizeText();
invalidate();
return; // return with out relayout
}
}
// We lose: the height has changed and we have a dynamic height.
// Request a new view layout using our new text layout.
requestLayout();
invalidate();
} else {
// Dynamic width, so we have no choice but to request a new
// view layout with a new text layout.
nullLayouts();
requestLayout();
invalidate();
}
}
As you can see, in some cases, the requestLayout() is not called, so the main thread check is not introduced.
So I think the key point is not about Kotlin or Java, it's about the TextViews' layout params which determined whether requestLayout() is called or not.
Most likely, in Kotlin case, there is some overhead in setText() which assures that it runs in UI thread.
I am trying to send light data from a light sensor using Bluetooth in Android Studio. Once the user presses the button, the sensor value should be constantly updating and sent to the other phone.
I've modified some Bluetooth code I found online; however, the light sensor value does not constantly update, it just shows the value of the sensor when the button was pressed.
I've tried putting in a timer that runs every 5 seconds after the start flag is set, so that a new value from the light sensor is constantly sent, but it does not work.
Is there a way I can sample the sensor and send the constantly updating sensor data after the button is pressed? Thanks in advance.
This is the code which gets the sensor readings and passes it onto the button listeners code:
#Override
public void onSensorChanged(SensorEvent sensorEvent) {
Sensor sensor = sensorEvent.sensor;
if (sensor.getType() == Sensor.TYPE_LIGHT) {
lightText.setText("Light = " + sensorEvent.values[0]);
float lightTemp = sensorEvent.values[0];
implementListeners(lightTemp);
}
}
Button listeners - listens for the user pressing the start button and then writes the sensor reading to the bluetooth part of the code (to then send to the other phone):
private void implementListeners(final float lightTemp) {
.
.
.
.
startBtn.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
int startFlag=1;
checkStartFlag(startFlag);
}
private void checkStartFlag(int startFlag) {
if (startFlag==1){
Timer timer = new Timer ();
TimerTask checkSec = new TimerTask () {
#Override
public void run () {
String string= String.valueOf(lightTemp);
sendReceive.write(string.getBytes());
}
};
timer.schedule (checkSec, 0l, 5000); // Check every 1 sec
}
}
});
}
There's a lot going on in your code here, so let me try to explain what's currently happening. When you get a new light value, you change the start button's click listener to start sending the value over bluetooth. However, since you're passing lightTemp into your implementListeners function, it won't get changed when onSensorChanged fires again. Thus, having a timer run every 5 seconds is likely working, but instead of sending a new value it's just sending the old one from before. So, what we need to do is have the data sent whenever the sensor value changes instead of when the button gets pressed.
The best way of going about this would likely be to have a state variable in your class that gets updated when you tap the start button.
private boolean sendData = false;
//...
#Override
protected void onCreate(Bundle savedInstanceState) {
// Your existing code in onCreate goes here
startBtn.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
sendData = !sendData; // Toggles whether we're sending data or not.
// You can also just set it to true if you don't
// want toggling behaviour.
}
});
}
Now, we need to actually send the data if we have enabled data sending. We can change your onSensorChanged method to check the state variable we defined before, then send data if our state is true:
#Override
public void onSensorChanged(SensorEvent sensorEvent) {
Sensor sensor = sensorEvent.sensor;
if (sensor.getType() == Sensor.TYPE_LIGHT) {
lightText.setText("Light = " + sensorEvent.values[0]);
if(sendData) { // check if we've enabed data sending
String lightTemp = String.valueOf(sensorEvent.values[0]); // get the sensor value as a string
sendReceive.write(lightTemp.getBytes()); // send the string over bluetooth
}
}
}
Your app should now send data over bluetooth every time the light sensor value updates after you tap the start button.
I have made the beginning of what will be a pretty cool wear app, for now it is just a metronome that vibrates on the tempo the user selects.
My problem is that when you exit the app or when the screen goes into "ambient mode" the vibration keeps going, which is great, but when you open the app again it opens a new "instance" (or however you say that) of the app so the vibration tempo previously selected keeps vibrating and when you select a new one you basically have 2 tempos vibrating.
Maybe this has something to do with the fact that I use a new Thread for the timing and that Thread keeps running. Is there a way to prevent this? Thank you!
public void buttonTempoOnClick(final View v) {
Running = !Running;
Button button = (Button) v;
Thread Timer = new Thread(new Runnable() {
public void run() {
while (Running == true) {
vibrate(150);
try {
Thread.sleep(60000 / Tempo);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
Timer.start();
(vibrate refers to a void I created in the class, which works.)
public void vibrate(int duration){
Vibrator vibrator = (Vibrator) getSystemService(Context.VIBRATOR_SERVICE);
vibrator.vibrate(duration);
}
Edit: I already have a public boolean Running that is false by default, when the "start' button is pressed it toggles the boolean and starts a new threat with a while(Running == true) loop in it. Stopping the vibration by pressing the button one more time works like a charm.
Try setting a variable in the class managing the thread, like
boolean isTempoOn = true;
In your main activity, in the onResume() function, check if it's already running. If so, don't start again.
Perhaps posting the applicable portion of code will help.
Hello Developers,
i am working with Btwebview here on long press we are avoiding the default selection functionality on long press and giving our own.The overriding of long press method is working perfectly till android 4.3 but with 4.4 the defalut selection also coming with actionbar.Below i am mentioning the sample code-
public class BTWebView extends WebView implements TextSelectionJavascriptInterfaceListener, OnTouchListener, OnLongClickListener,DragListener {
.......
public BTWebView(Context context) {
super(context);
this.ctx = context;
this.setup(context);
}
protected void setup(Context context)
{
this.setOnLongClickListener(this);
this.setOnTouchListener(this);
}
and on long press
#Override
public boolean onLongClick(View v)
{
......
return true;
}
}
Here after overriding the long click and return value as true so it avoid default selection till 4.3 so please tell me how to avoid either the complete default selection or atleast avoid the action bar comes on long press .thanks in advance
i found the solution for this question here on github ,it is-
if(event.getAction() == MotionEvent.ACTION_UP)
{
if(!mScrolling){
mScrolling = false;
endSelectionMode();
return false;
}
mScrollDiffX = 0;
mScrollDiffY = 0;
mScrolling = false;
// Fixes 4.4 double selection
return true;
}
here also you have to return the true,after that the default selection will not come .
The solution for KitKat and above proposed by Ravi Saini works to prevent the default selection interface to appear. However, return true for ACTION_UP event prevents fling gestures for scrolling the contents vertically quickly, so the use of WebView seems unnatural. I added isInSelectionMode() condition to prevent this, now fling gestures work fine, except when in selection mode. The code from WebViewMarker GitHub project with my change is as follows (in TextSelectionSupport.java module, onTouch() method):
case MotionEvent.ACTION_UP:
if (!mScrolling) {
endSelectionMode();
//
// Fixes 4.4 double selection
// See: http://stackoverflow.com/questions/20391783/how-to-avoid-default-selection-on-long-press-in-android-kitkat-4-4
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
return false;
}
}
mScrollDiffX = 0;
mScrollDiffY = 0;
mScrolling = false;
//
// Fixes 4.4 double selection
// See: http://stackoverflow.com/questions/20391783/how-to-avoid-default-selection-on-long-press-in-android-kitkat-4-4
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT && isInSelectionMode()) {
return true;
}
break;
Greg
If the onLongClick() method isn't getting called and the long press in the WebView is enabling the user selection (i.e. copy, cut, paste etc) then you could try the user-select CSS property
user-select:none;
Which will surpress that behaviour, although not sure if this will still fire the long click listener, or whether the WebView will continue to inherit the click events.
I need to get my screen turned on/off using SensorEventListener when
#Override
public final void onSensorChanged(SensorEvent event) {
if (event.values[0] == 0)
turnScreenOFF();
else if (event.values[0] == 5)
turnScreenON();
}
I have tried many sample code for it, but I can't get my screen turned ON again after it's turned OFF
There is the code to turn off the screen :
WindowManager.LayoutParams params = getWindow().getAttributes();
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
getWindow().addFlags(WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD);
getWindow().addFlags(WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON);
params.screenBrightness = 0.0f;
getWindow().setAttributes(params);
For screen on-off state, you can try with ACTION_SCREEN_ON and ACTION_SCREEN_OFF intents,which will come in nifty if you’re making an application that might need to save state or respond to the user’s screen going to sleep/waking up, etc.
Check out the Handling Screen ON/OFF.
EDITED:
Try out below:
private void unlockScreen() {
Window window = this.getWindow();
window.addFlags(LayoutParams.FLAG_DISMISS_KEYGUARD);
window.addFlags(LayoutParams.FLAG_SHOW_WHEN_LOCKED);
window.addFlags(LayoutParams.FLAG_TURN_SCREEN_ON);
}
And call this method from onResume().