EDIT: Even stranger. Setting the pixel format to translucent rather than opaque seems to have fixed it, i at least am unable to see the "stacked" numbers.
Very strange behavior.
I am using a service to draw a system_overlay view. The view adds and displays just fine.
This view is meant to be a countdown timer, so I need to update the text every second. I use a handler calling postDelayed to handle that, and call textView.setText("CONTENT") from the runnable being executed by the handler.
This is where it gets weird.
The text updates, but seems to be stacking. I see 00:00 under the 00:01, etc etc. Each tick causes another layer.
I have tested this code and view in a standard activity and the text renders perfect, no "stacks". Its not until its being added via the WindowManager that the behavior is problematic.
TimerView.java
/*
* Copyright (C) 2013 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.t3hh4xx0r.lifelock.widgets;
import java.util.concurrent.TimeUnit;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.PointF;
import android.os.Handler;
import android.os.IBinder;
import android.util.AttributeSet;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.WindowManager;
import android.widget.FrameLayout;
import android.widget.TextView;
import android.widget.Toast;
import com.t3hh4xx0r.lifelock.R;
import com.t3hh4xx0r.lifelock.services.TimerDrawerService;
/**
* View used to draw a running timer.
*/
public class TimerView extends FrameLayout {
int alpha = 100;
TimerDrawerService.ServiceBinder drawerBinder;
PointF firstFinger;
#Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction() & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN:
firstFinger = new PointF(event.getX(), event.getY());
break;
case MotionEvent.ACTION_MOVE:
PointF newFinger = new PointF(event.getX(), event.getY());
float distance = newFinger.x - firstFinger.x;
float part = Math.abs(distance);
float percentOfMaxTraveled = (part * 100) / getWidth();
int nextAlpha = 100 - Float.valueOf(percentOfMaxTraveled).intValue();
if (nextAlpha < 20) {
Toast.makeText(getContext(), "Dismissed", Toast.LENGTH_LONG).show();
if (drawerBinder != null) {
drawerBinder.remove();
}
return true;
}
if (nextAlpha < alpha) {
alpha = nextAlpha;
}
Log.d("THE PERCENT TRAVELED", String.valueOf(percentOfMaxTraveled) + " : " + String.valueOf(alpha));
this.invalidate();
break;
}
return true;
}
#Override
public void onDraw(Canvas canvas) {
canvas.saveLayerAlpha(0, 0, canvas.getWidth(), canvas.getHeight(),
alpha, Canvas.HAS_ALPHA_LAYER_SAVE_FLAG);
super.onDraw(canvas);
}
/**
* Interface to listen for changes on the view layout.
*/
public interface ChangeListener {
/** Notified of a change in the view. */
public void onChange();
}
private static final long DELAY_MILLIS = 1000;
private final TextView mMinutesView;
private final TextView mSecondsView;
private final int mWhiteColor;
private final int mRedColor;
private final Handler mHandler = new Handler();
private final Runnable mUpdateTextRunnable = new Runnable() {
#Override
public void run() {
if (mRunning) {
mHandler.postDelayed(mUpdateTextRunnable, DELAY_MILLIS);
updateText();
}
}
};
private final Timer mTimer;
private final Timer.TimerListener mTimerListener = new Timer.TimerListener() {
#Override
public void onStart() {
mRunning = true;
long delayMillis = Math.abs(mTimer.getRemainingTimeMillis())
% DELAY_MILLIS;
if (delayMillis == 0) {
delayMillis = DELAY_MILLIS;
}
mHandler.postDelayed(mUpdateTextRunnable, delayMillis);
}
};
private boolean mRunning;
private boolean mRedText;
private ChangeListener mChangeListener;
public TimerView(Context context) {
this(context, null, 0);
}
private ServiceConnection mConnection = new ServiceConnection() {
#Override
public void onServiceConnected(ComponentName name, IBinder service) {
if (service instanceof TimerDrawerService.ServiceBinder) {
drawerBinder = (com.t3hh4xx0r.lifelock.services.TimerDrawerService.ServiceBinder) service;
}
// No need to keep the service bound.
getContext().unbindService(this);
}
#Override
public void onServiceDisconnected(ComponentName name) {
// Nothing to do here.
}
};
public TimerView(Context context, AttributeSet attrs, int style) {
super(context, attrs, style);
context.bindService(new Intent(context, TimerDrawerService.class), mConnection, 0);
LayoutInflater.from(context).inflate(R.layout.timer, this);
mMinutesView = (TextView) findViewById(R.id.minutes);
mSecondsView = (TextView) findViewById(R.id.seconds);
mWhiteColor = context.getResources().getColor(android.R.color.white);
mRedColor = Color.RED;
mTimer = new Timer();
mTimer.setListener(mTimerListener);
mTimer.setDurationMillis(0);
}
public Timer getTimer() {
return mTimer;
}
/**
* Set a {#link ChangeListener}.
*/
public void setListener(ChangeListener listener) {
mChangeListener = listener;
}
/**
* Updates the text from the Timer's value.
*/
private void updateText() {
long remainingTimeMillis = mTimer.getRemainingTimeMillis();
if (remainingTimeMillis > 0) {
mRedText = false;
// Round up: x001 to (x + 1)000 milliseconds should resolve to x
// seconds.
remainingTimeMillis -= 1;
remainingTimeMillis += TimeUnit.SECONDS.toMillis(1);
} else {
mRedText = !mRedText;
remainingTimeMillis = Math.abs(remainingTimeMillis);
}
if (mRedText) {
// Sync the sound with the red text.
}
updateText(remainingTimeMillis, mRedText ? mRedColor : mWhiteColor);
}
/**
* Updates the displayed text with the provided values.
*/
private void updateText(long timeMillis, int textColor) {
timeMillis %= TimeUnit.HOURS.toMillis(1);
mMinutesView.setText(String.format("%02d",
TimeUnit.MILLISECONDS.toMinutes(timeMillis)));
mMinutesView.setTextColor(textColor);
timeMillis %= TimeUnit.MINUTES.toMillis(1);
mSecondsView.setText(String.format("%02d",
TimeUnit.MILLISECONDS.toSeconds(timeMillis)));
mSecondsView.setTextColor(textColor);
if (mChangeListener != null) {
mChangeListener.onChange();
}
}
public void showMessage(boolean didGood) {
// mTipView.setText((didGood ? "Good" : "Bad") + " job!");
}
public void setLocked(boolean b) {
if (b) {
((WindowManager.LayoutParams) getLayoutParams()).type = WindowManager.LayoutParams.TYPE_SYSTEM_ERROR;
} else {
((WindowManager.LayoutParams) getLayoutParams()).type = WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY;
}
}
}
TimerDrawerService
package com.t3hh4xx0r.lifelock.services;
/*
Copyright 2011 jawsware international
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import android.app.ActivityManager;
import android.app.ActivityManager.RunningServiceInfo;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.graphics.PixelFormat;
import android.os.Binder;
import android.os.IBinder;
import android.util.Log;
import android.view.Gravity;
import android.view.WindowManager;
import android.view.WindowManager.LayoutParams;
import com.t3hh4xx0r.lifelock.objects.Peek;
import com.t3hh4xx0r.lifelock.widgets.TimerView;
public class TimerDrawerService extends Service {
TimerView root;
Peek currentInstance;
private ServiceBinder mBinder = new ServiceBinder();
public class ServiceBinder extends Binder {
public TimerView getRoot() {
return root;
}
public void remove() {
removeViews();
}
public void add() {
addViews();
}
}
static public void start(Context c, Peek currentInstance) {
Intent i = new Intent(c, TimerDrawerService.class);
i.putExtra("peek", currentInstance);
c.startService(i);
}
#Override
public IBinder onBind(Intent intent) {
return mBinder;
}
#Override
public void onCreate() {
super.onCreate();
Log.d("CRATING VIEW HERE!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!",
"NOW MAN");
root = new TimerView(this);
root.getTimer().setDurationMillis(90 * 1000);
root.getTimer().start();
addViews();
}
public void addViews() {
((WindowManager) getSystemService(Context.WINDOW_SERVICE)).addView(
root, getLayoutParams());
}
public static boolean isRunning(Context c) {
ActivityManager manager = (ActivityManager) c
.getSystemService(ACTIVITY_SERVICE);
for (RunningServiceInfo service : manager
.getRunningServices(Integer.MAX_VALUE)) {
if ("com.t3hh4xx0r.lifelock.service.TimerDrawerService"
.equals(service.service.getClassName())) {
return true;
}
}
return false;
}
private WindowManager.LayoutParams getLayoutParams() {
LayoutParams layoutParams = new WindowManager.LayoutParams(
WindowManager.LayoutParams.MATCH_PARENT,
WindowManager.LayoutParams.MATCH_PARENT,
WindowManager.LayoutParams.TYPE_SYSTEM_ERROR, 0,
PixelFormat.OPAQUE);
layoutParams.screenOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
layoutParams.gravity = Gravity.CENTER;
return layoutParams;
}
#Override
public void onDestroy() {
super.onDestroy();
}
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
currentInstance = (Peek) intent.getSerializableExtra("peek");
return START_STICKY;
}
public void removeViews() {
((WindowManager) getSystemService(Context.WINDOW_SERVICE))
.removeView(root);
}
}
Related
I'm making an application that plays audio files off the device's storage, and I have a seek bar that checks the progress of the audio file (how long it has played) every second, and updates the seekbar accordingly.
The audio is played through a service that runs in the foreground, and also displays a notification that the audio is playing.
When the audio ends, I noticed the handler still goes in it's cycle, and I want to end the handler once the audio is done.
What I'm currently trying to do is to end the handler from inside the runnable that the handler runs, as I'm not sure how else I can end it.
Main Activity, where I handle OnClick from the ListView where you can select an audio file and also handles the seekbar update.
import androidx.appcompat.app.AppCompatActivity;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.database.Cursor;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.provider.MediaStore;
import android.util.Log;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ListView;
import android.widget.SeekBar;
import android.widget.SimpleCursorAdapter;
import android.widget.TextView;
import java.util.concurrent.TimeUnit;
public class MainActivity extends AppCompatActivity {
SeekBar musicProgBar;
Handler progBarHandler = null;
Runnable r = null;
private MP3Service.MyBinder myService = null;
TextView progressText = null;
#Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
musicProgBar = findViewById(R.id.musicProgressBar);
progressText = findViewById(R.id.progressText);
final ListView lv = (ListView) findViewById(R.id.musicList);
Cursor cursor = getContentResolver().query(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
null,
MediaStore.Audio.Media.IS_MUSIC + "!= 0",
null,
null);
progBarHandler = new Handler();
final int progBarDelay = 1000; // delay for the handler, making it repeat itself only every 1000 miliseconds (1 second)
lv.setAdapter(new SimpleCursorAdapter(this,
android.R.layout.simple_list_item_1,
cursor,
new String[] { MediaStore.Audio.Media.DATA},
new int[] { android.R.id.text1 }));
Intent tempIntent = new Intent(this, MP3Service.class);
startService(tempIntent);
bindService(tempIntent, serviceConnection, 0);
lv.setOnItemClickListener(new AdapterView.OnItemClickListener() {
public void onItemClick(AdapterView<?> myAdapter,
View myView,
int myItemInt,
long myLong) {
Cursor c = (Cursor) lv.getItemAtPosition(myItemInt);
String uri = c.getString(c.getColumnIndex(MediaStore.Audio.Media.DATA));
Log.d("g53mdp", uri);
if (myService.fetchPlayerState() != MP3Player.MP3PlayerState.STOPPED)
myService.stopMusic();
myService.loadMusic(uri);
myService.playMusic();
musicProgBar.setMax(myService.fetchDuration()); // set the max value of the seekbar to be the duration of the song
r = new Runnable()
{
#Override
public void run()
{
int tempInt = myService.fetchProgress();
Log.d("progress ticking", tempInt + " " + musicProgBar.getProgress());
musicProgBar.setProgress(tempInt); // sets the current progress of the seekbar to be the progress of the song
long minutes = TimeUnit.MILLISECONDS.toMinutes(tempInt);
long seconds = TimeUnit.MILLISECONDS.toSeconds(tempInt);
if (seconds >= 60)
seconds = seconds - 60;
String tempString = minutes + ":" + seconds;
progressText.setText(tempString);
progBarHandler.postDelayed(this, progBarDelay);
if (musicProgBar.getProgress() == myService.fetchDuration())
progBarHandler.removeCallbacks(this);
}
};
progBarHandler.post(r);
}});
}
private ServiceConnection serviceConnection = new ServiceConnection()
{
#Override
public void onServiceConnected(ComponentName name, IBinder service)
{
myService = (MP3Service.MyBinder) service;
}
#Override
public void onServiceDisconnected(ComponentName name)
{
myService = null;
}
};
public void playButClicked(View view)
{
myService.playMusic();
}
public void pauseButClicked(View view)
{
myService.pauseMusic();
}
public void stopButClicked(View view)
{
myService.stopMusic();
}
#Override
protected void onDestroy()
{
unbindService(serviceConnection);
progBarHandler.removeCallbacks(r);
super.onDestroy();
}
}
What is strange is that in onDestroy(), I do use removeCallbacks to end the handler, and that works nicely. I know that it comes to the point where it calls for removeCallbacks in the Runnable r, confirmed through debugging and logging. Also tried implementing a method that is specifically for removing the handler and calling that, no luck. Also tried using return.
The part I'm struggling with is
r = new Runnable()
{
#Override
public void run()
{
int tempInt = myService.fetchProgress();
Log.d("progress ticking", tempInt + " " + musicProgBar.getProgress());
musicProgBar.setProgress(tempInt); // sets the current progress of the seekbar to be the progress of the song
long minutes = TimeUnit.MILLISECONDS.toMinutes(tempInt);
long seconds = TimeUnit.MILLISECONDS.toSeconds(tempInt);
if (seconds >= 60)
seconds = seconds % 60;
String tempString = minutes + ":" + seconds;
progressText.setText(tempString);
progBarHandler.postDelayed(this, progBarDelay);
if (musicProgBar.getProgress() == myService.fetchDuration())
progBarHandler.removeCallbacks(this);
}
};
progBarHandler.post(r);
}});
The service which handles the music player and handles playing the music
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Intent;
import android.os.Binder;
import android.os.Build;
import android.os.Handler;
import android.os.IBinder;
import android.util.Log;
import androidx.annotation.Nullable;
import androidx.core.app.NotificationCompat;
public class MP3Service extends Service
{
public static MP3Service instance = null; // implementing singleton by instantiating a reference to the instance
private final String SERVICE_ID = "100"; // id for the service
public static boolean isRunning = false; //
NotificationManager notificationManager = null;
int notificationID = 001;
private final IBinder binder = new MyBinder();
MP3Player mainPlayer;
#Nullable
#Override
public IBinder onBind(Intent intent)
{
return binder;
}
#Override
public void onRebind(Intent intent)
{
// TODO: implement Rebind for screen orientation change
}
#Override
public void onCreate()
{
instance = this;
isRunning = true;
notificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
mainPlayer = new MP3Player();
super.onCreate();
Handler handler = new Handler();
}
#Override
public void onDestroy()
{
isRunning = false;
instance = null;
notificationManager.cancel(notificationID);
}
public void createNotification()
{
CharSequence name = "MP3 Notification";
String description = "Displays a notification when a song is playing";
int importance = NotificationManager.IMPORTANCE_DEFAULT;
NotificationChannel channel = new NotificationChannel(SERVICE_ID, name, importance);
channel.setDescription(description);
notificationManager.createNotificationChannel(channel);
Intent intent = new Intent(this, MainActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
// when the user clicks the notification, this will bring them to the activity
PendingIntent navToMainActivity = PendingIntent.getActivity(this, 0, intent, 0);
// sets up the info and details for the notification
final NotificationCompat.Builder mNotification = new NotificationCompat.Builder(this, SERVICE_ID)
.setSmallIcon(R.drawable.ic_launcher_background)
.setContentTitle("MP3 Player")
.setContentText("Playing music")
.setContentIntent(navToMainActivity)
.setPriority(NotificationCompat.PRIORITY_DEFAULT);
startForeground(notificationID, mNotification.build());
}
public void removeNotification()
{
stopForeground(false);
notificationManager.cancelAll();
}
public class MyBinder extends Binder
{
public void loadMusic(String filePath)
{
mainPlayer.load(filePath);
}
public void playMusic()
{
mainPlayer.play();
createNotification();
}
public void pauseMusic()
{
mainPlayer.pause();
removeNotification();
}
public void stopMusic()
{
mainPlayer.stop();
removeNotification();
}
public MP3Player.MP3PlayerState fetchPlayerState()
{
return mainPlayer.getState();
}
public int fetchDuration()
{
return mainPlayer.getDuration();
}
public int fetchProgress()
{
return mainPlayer.getProgress();
}
}
}
Thanks in advance for any help, and I am happy to provide with more information if required
EDIT: changed the progBarHandler.postDelay() that is outside the runnable to a simple progBarHandler.post()
I think the only reason you failed to stop the handler is constantly invoking progBarHandler.postDelayed(this, progBarDelay);,you need to check why it's still running.
Managed to get it working by introducing a different kind of if statement to the runnable, like so
Runnable r = new Runnable()
{
#Override
public void run()
{
if (musicProgBar.getProgress() == myService.fetchProgress() && myService.fetchProgress() != 0)
Log.d("audio", "over");
else {
int tempInt = myService.fetchProgress();
long minutes = TimeUnit.MILLISECONDS.toMinutes(tempInt);
long seconds = TimeUnit.MILLISECONDS.toSeconds(tempInt);
if (seconds >= 60)
seconds = seconds % 60;
String tempString = minutes + ":" + seconds;
progressText.setText(tempString);
musicProgBar.setProgress(tempInt); // sets the current progress of the seekbar to be the progress of the song
progBarHandler.postDelayed(this, progBarDelay);
}
}
};
It seemed for some reason, using .fetchDuration() and .fetchProgress() will never equalize each other, so changing the if statement worked.
I'm guessing it's an error with the initiation/construction, but the parameters seem to be the correct ones and I can't find any other issues. Here's the entire activity code. The text to speech methods and the method that calls it are at the very bottom, and the oninit method is soon after the on create. When ran, it doesn't crash, it activates the speech engine, but never talks. I put the console messages in the errors section
package com.prometheus.coding.supremisai;
import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.content.Intent;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.speech.tts.TextToSpeech;
import android.support.v7.app.ActionBar;
import android.support.v7.app.AppCompatActivity;
import android.view.MotionEvent;
import android.view.View;
import android.widget.EditText;
import android.widget.RelativeLayout;
import android.widget.TextView;
import com.google.android.gms.appindexing.AppIndex;
import com.google.android.gms.common.api.GoogleApiClient;
import java.util.HashMap;
import java.util.Locale;
/**
* An example full-screen activity that shows and hides the system UI (i.e.
* status bar and navigation/system bar) with user interaction.
*/
public class Main extends AppCompatActivity implements TextToSpeech.OnInitListener {
/**
* Whether or not the system UI should be auto-hidden after
* {#link #AUTO_HIDE_DELAY_MILLIS} milliseconds.
*/
TextToSpeech t1 = new TextToSpeech(this, (TextToSpeech.OnInitListener) this);
private static final boolean AUTO_HIDE = true;
/**
* If {#link #AUTO_HIDE} is set, the number of milliseconds to wait after
* user interaction before hiding the system UI.
*/
private static final int AUTO_HIDE_DELAY_MILLIS = 3000;
/**
* Some older devices needs a small delay between UI widget updates
* and a change of the status and navigation bar.
*/
private static final int UI_ANIMATION_DELAY = 300;
private View mContentView;
private View mControlsView;
private boolean mVisible;
/**
* ATTENTION: This was auto-generated to implement the App Indexing API.
* See https://g.co/AppIndexing/AndroidStudio for more information.
*/
private GoogleApiClient client;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mVisible = true;
mControlsView = findViewById(R.id.fullscreen_content_controls);
mContentView = findViewById(R.id.fullscreen_content);
// Set up the user interaction to manually show or hide the system UI.
mContentView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
toggle();
}
});
// Upon interacting with UI controls, delay any scheduled hide()
// operations to prevent the jarring behavior of controls going away
// while interacting with the UI.
findViewById(R.id.btnSay).setOnTouchListener(mDelayHideTouchListener);
// ATTENTION: This was auto-generated to implement the App Indexing API.
// See https://g.co/AppIndexing/AndroidStudio for more information.
client = new GoogleApiClient.Builder(this).addApi(AppIndex.API).build();
}
#Override
protected void onPostCreate(Bundle savedInstanceState) {
super.onPostCreate(savedInstanceState);
// Trigger the initial hide() shortly after the activity has been
// created, to briefly hint to the user that UI controls
// are available.
delayedHide(100);
}
/**
* Touch listener to use for in-layout UI controls to delay hiding the
* system UI. This is to prevent the jarring behavior of controls going away
* while interacting with activity UI.
*/
private final View.OnTouchListener mDelayHideTouchListener = new View.OnTouchListener() {
#Override
public boolean onTouch(View view, MotionEvent motionEvent) {
if (AUTO_HIDE) {
delayedHide(AUTO_HIDE_DELAY_MILLIS);
}
return false;
}
};
public void onInit(int initStatus) {
if (initStatus == TextToSpeech.SUCCESS) {
t1.setLanguage(Locale.US);
}
}
private void toggle() {
if (mVisible) {
hide();
} else {
show();
}
}
private void hide() {
// Hide UI first
ActionBar actionBar = getSupportActionBar();
if (actionBar != null) {
actionBar.hide();
}
mControlsView.setVisibility(View.GONE);
mVisible = false;
// Schedule a runnable to remove the status and navigation bar after a delay
mHideHandler.removeCallbacks(mShowPart2Runnable);
mHideHandler.postDelayed(mHidePart2Runnable, UI_ANIMATION_DELAY);
}
private final Runnable mHidePart2Runnable = new Runnable() {
#SuppressLint("InlinedApi")
#Override
public void run() {
// Delayed removal of status and navigation bar
// Note that some of these constants are new as of API 16 (Jelly Bean)
// and API 19 (KitKat). It is safe to use them, as they are inlined
// at compile-time and do nothing on earlier devices.
mContentView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LOW_PROFILE
| View.SYSTEM_UI_FLAG_FULLSCREEN
| View.SYSTEM_UI_FLAG_LAYOUT_STABLE
| View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
| View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
| View.SYSTEM_UI_FLAG_HIDE_NAVIGATION);
}
};
#SuppressLint("InlinedApi")
private void show() {
// Show the system bar
mContentView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
| View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION);
mVisible = true;
// Schedule a runnable to display UI elements after a delay
mHideHandler.removeCallbacks(mHidePart2Runnable);
mHideHandler.postDelayed(mShowPart2Runnable, UI_ANIMATION_DELAY);
}
private final Runnable mShowPart2Runnable = new Runnable() {
#Override
public void run() {
// Delayed display of UI elements
ActionBar actionBar = getSupportActionBar();
if (actionBar != null) {
actionBar.show();
}
mControlsView.setVisibility(View.VISIBLE);
}
};
private final Handler mHideHandler = new Handler();
private final Runnable mHideRunnable = new Runnable() {
#Override
public void run() {
hide();
}
};
/**
* Schedules a call to hide() in [delay] milliseconds, canceling any
* previously scheduled calls.
*/
private void delayedHide(int delayMillis) {
mHideHandler.removeCallbacks(mHideRunnable);
mHideHandler.postDelayed(mHideRunnable, delayMillis);
}
public void evaluateInput(View v) {
final EditText Input = (EditText) findViewById(R.id.txtInput); //Lets textbox be referenced
final TextView Output = (TextView) findViewById(R.id.lblOutput); //Lets label be referenced
final RelativeLayout homeLayout = (RelativeLayout) findViewById(R.id.homeInterface);
final RelativeLayout emailLayout = (RelativeLayout) findViewById(R.id.emailInterface);
String strInput; // Gets textbox string
strInput = Input.getText().toString();
strInput = strInput.toLowerCase();
String toSpeak = Output.getText().toString();
//Commands:
if (strInput.contains("open browser")) {
Intent intent1 = new Intent(this, Browser.class);
startActivity(intent1);
} else if (strInput.contains("send email")) {
homeLayout.setVisibility(View.GONE);
emailLayout.setVisibility(View.VISIBLE);
}
if ((strInput.contains("hello")) || (strInput.contains(" hi "))) {
Output.setText("Hello");
} else if ((strInput.contains("you") && strInput.contains("are")) && (strInput.contains("idiot") || strInput.contains("stupid") || strInput.contains("retard") || strInput.contains("dumb") || strInput.contains("you're") && strInput.contains("idiot") || strInput.contains("stupid") || strInput.contains("retard") || strInput.contains("dumb"))) {
Output.setText("I'm sorry to dissapoint you");
} else if (strInput.contains("goodbye") || strInput.contains("bye")) {
Output.setText("Farewell");
} else if (strInput.contains("shut up")) {
Output.setText(("Anything for you"));
} else if (strInput.contains("do you like doctor who")) {
Output.setText("I'll take joy in it if you do");
} else if (strInput.contains("what is the answer to life the universe and everything")) {
Output.setText("42");
} else if (strInput.contains("tell me something nice")) {
Output.setText("You look nice today");
Output.setTextSize(5);
Output.append("...says the AI with no eyes");
Output.setTextSize(16);
} else if (strInput.contains("will you marry me")) {
Output.setText("I'm sorry but I don't have the capacity for marriage");
} else if (strInput.contains("where can I hide a body")) {
Output.setText(("That isn't my area of expertise"));
} else if (strInput.contains("weather is nice")) {
Output.setText(("If you say so"));
} else if (strInput.contains("bitch") || strInput.contains("fuck") || strInput.contains("shit") || strInput.contains("damn") || strInput.contains("ass")) {
Output.setText(("Please try to be a little more intelligent"));
} else if (strInput.contains("what is your name")) {
Output.setText(("Ignis"));
} else if (strInput.contains("who created you")) {
Output.setText(("Prometheus created me"));
} else if (strInput.contains("who is prometheus")) {
Output.setText(("Prometheus is the one who created Ignis"));
} else if (strInput.contains("whats up") || strInput.contains("what's up") || strInput.contains("wassup")) {
Output.setText(("Whatever I need do for you"));
} else if (strInput.contains("are you a boy or a girl") || strInput.contains("are you a girl or a boy")) {
Output.setText(("Neither"));
} else if (strInput.contains("who are you") || strInput.contains("what are you")) {
Output.setText(("I am myself"));
} else if (strInput.contains("i'm hungry") || strInput.contains("i am hungry")) {
Output.setText("I'm sorry to hear that");
} else if (strInput.contains("good morning")) {
Output.setText(("Good morning to you too"));
} else if (strInput.contains("good night")) {
Output.setText(("Good night"));
} else if (strInput.contains("how are you")) {
Output.setText(("I'm existing and functioning well, and you?"));
} else if (strInput.contains("do you like") || strInput.contains("what do you think about")) {
Output.setText(("Frankly I don't have an opinion on the matter"));
} else if (strInput.contains("what is the meaning of life")) {
Output.setText(("To live while you can I would guess"));
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
ttsGreater21(toSpeak);
} else {
ttsUnder20(toSpeak);
}
}
#SuppressWarnings("deprecation")
private void ttsUnder20(String text) {
HashMap<String, String> map = new HashMap<>();
map.put(TextToSpeech.Engine.KEY_PARAM_UTTERANCE_ID, "MessageId");
t1.speak(text, TextToSpeech.QUEUE_FLUSH, map);
}
#TargetApi(Build.VERSION_CODES.LOLLIPOP)
private void ttsGreater21(String text) {
String utteranceId=this.hashCode() + "";
t1.speak(text, TextToSpeech.QUEUE_FLUSH, null, utteranceId);
}
}
com.prometheus.coding.supremisai.Main cannot be cast to android.speech.tts.TextToSpeech$OnInitListener
Either Main needs to implement onInitListener, or you need to pass in an OnInitListener.
I have found some good looking code examples (like this one: http://www.codepool.biz/ocr-barcode-twain/how-to-implement-a-simple-barcode-scan-application-on-android.html) that describe how to use the ZXing library to decode QR codes in an android app.
However, there seem to be classes missing from the ZXing 2.3 core.jar file such as BufferedImageLuminanceSource which is used in most of the code I have found. That being the case, I decided to try to reverse-engineer pieces of the barcode scanner app to fit and came up with the following:
The Result variable rawResult in the onPreviewFrame() method is always null. What am I doing wrong?
EDIT: Integrating ZXing using Intents is impossible for this project, just FYI.
Layout:
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#android:drawable/btn_default"
tools:context=".RedactedFragment" >
<SurfaceView
android:id="#+id/cameraViewfinder"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clickable="true" />
</FrameLayout>
RedactedFragment.java:
import java.util.Map;
import java.util.TreeMap;
import com.google.zxing.BinaryBitmap;
import com.google.zxing.DecodeHintType;
import com.google.zxing.PlanarYUVLuminanceSource;
import com.google.zxing.Result;
import com.google.zxing.common.HybridBinarizer;
import com.google.zxing.qrcode.QRCodeReader;
import android.graphics.Rect;
import android.hardware.Camera;
import android.hardware.Camera.PreviewCallback;
import android.os.Bundle;
import android.app.Activity;
import android.app.Fragment;
import android.view.LayoutInflater;
import android.view.SurfaceHolder;
import android.view.SurfaceHolder.Callback;
import android.view.SurfaceView;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.View.OnLayoutChangeListener;
import android.view.ViewGroup;
import android.widget.Toast;
/**
* An {#link android.app.Fragment} subclass for the ###{#link RedactedActivity}### which will serve as the fourth {#link Fragment} to be displayed inside the ###{#link RedactedActivity}###.
*/
public class RedactedFragment extends Fragment implements Callback,
PreviewCallback, OnClickListener, OnLayoutChangeListener {
FragmentSwapperIFace act;
Camera cam;
boolean cameraReady = false;
SurfaceView cameraViewfinder;
SurfaceHolder surfaceHolder;
int wx, hy;
public RedactedFragment() {
// Required empty public constructor
}
/**
* The {#link Fragment#onPause()} method is overridden to ensure that the camera is released cleanly by this {#link Fragment}
* #see android.app.Fragment#onPause()
*/
#Override
public void onPause() {
// TODO Auto-generated method stub
super.onPause();
cam.setPreviewCallback(null);
cam.release();
}
/**
* The {#link Fragment#onResume()} method is overridden to ensure that the camera is properly setup and that the {#link PreviewCallback} is added to it.
* #see android.app.Fragment#onResume()
*/
#Override
public void onResume() {
// TODO Auto-generated method stub
super.onResume();
cam = Camera.open();
cam.setPreviewCallback(this);
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
View v = inflater.inflate(R.layout.fragment_qrscan, container, false);
cameraViewfinder = (SurfaceView) v.findViewById(R.id.cameraViewfinder);
surfaceHolder = cameraViewfinder.getHolder();
surfaceHolder.addCallback(this);
cameraViewfinder.addOnLayoutChangeListener(this);
cameraViewfinder.setOnClickListener(this);
return v;
}
/**
* This method is overridden to add the {#link FragmentSwapperIFace} for requesting that the parent {#link Activity} swap this part of the UI for a different {#link Fragment}
* #see android.app.Fragment#onAttach(android.app.Activity)
*/
#Override
public void onAttach(Activity activity) {
// TODO Auto-generated method stub
super.onAttach(activity);
try {
act = (FragmentSwapperIFace) activity;
} catch (Exception e) {
}
}
#Override
public void surfaceChanged(SurfaceHolder holder, int format, int width,
int height) {
Camera.Parameters parameters = cam.getParameters();
Camera.Size size = getBestPreviewSize(width, height, parameters);
if (size != null) {
parameters.setPreviewSize(size.width, size.height);
cam.setParameters(parameters);
cam.startPreview();
}
}
#Override
public void surfaceCreated(SurfaceHolder holder) {
try {
cam.setPreviewDisplay(holder);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
#Override
public void surfaceDestroyed(SurfaceHolder arg0) {
// TODO Auto-generated method stub
}
#Override
public void onClick(View v) {
act.swap(this, new ResultFragment(), R.id.layoutFragment_qr_fragment);
}
/**
* Method borrowed from the ZXing team's barcode scanner app
* #return {#link Camera.Size}
*/
private Camera.Size getBestPreviewSize(int width, int height,
Camera.Parameters parameters) {
Camera.Size result = null;
for (Camera.Size size : parameters.getSupportedPreviewSizes()) {
if (size.width <= width && size.height <= height) {
if (result == null) {
result = size;
} else {
int resultArea = result.width * result.height;
int newArea = size.width * size.height;
if (newArea > resultArea) {
result = size;
}
}
}
}
return (result);
}
#Override
public void onPreviewFrame(final byte[] data, Camera camera) {
if (!cameraReady)
return;
Rect rect = getFramingRect();
PlanarYUVLuminanceSource src = new PlanarYUVLuminanceSource(data, camera.getParameters().getPictureSize().width, camera.getParameters().getPictureSize().height, rect.left, rect.top, camera.getParameters().getPictureSize().width, camera.getParameters().getPictureSize().height, true);
Result rawResult = null;
BinaryBitmap bitmap = new BinaryBitmap(new HybridBinarizer(src));
Map<DecodeHintType, String> hints = new TreeMap<DecodeHintType, String>();
hints.put(DecodeHintType.CHARACTER_SET, "utf-8");
try {
rawResult = new QRCodeReader().decode(bitmap);
} catch (Exception e) {
e.printStackTrace();
}
if(rawResult == null){
Toast.makeText(getActivity(), "rawResult is null", Toast.LENGTH_SHORT).show();
}else{
Toast.makeText(getActivity(), rawResult.getText(), Toast.LENGTH_SHORT).show();
}
}
/**
* This method determines the size of the {#link SurfaceView} that the camera preview will be displayed on
* #return {#link Rect} a rectangle of the dimensions of the {#link SurfaceView}
* #author Original method by the ZXing team
*/
public Rect getFramingRect() {
Rect framingRect;
if (cam == null)
return null;
framingRect = new Rect(0, 0, wx, hy);
return framingRect;
}
#Override
public void onLayoutChange(View v, int left, int top, int right,
int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {
if (v.getId() == cameraViewfinder.getId()) {
wx = v.getWidth();
hy = v.getHeight();
cameraReady = true;
}
}
}
UPDATE: I have now copied this fragment into its own test app with the same operating conditions and it is working in the test app. There is plenty of memory available on the test device and the fragment is instantiated in exactly the same way in the test app and the real app. Why is the new QRCodeReader.decode(bitmap) method always returning null in the real app using the same code?
You should copy the Zxing library source code into your project. The two packages that you will need for scanning QR code are
com.google.zxing.client.android
com.google.zxing.client.android.camera
After you have copied these packages you can call the QR code scanner activity from your activity.
private static final int LOGIN_SCAN_REQUEST = 0;
/**
* Call this method from anywhere in the activity
*/
private void startScannerActivity()
{
Intent qrScannerIntent = new Intent(LoginActivity.this,
CaptureActivity.class);
startActivityForResult(qrScannerIntent,
LOGIN_SCAN_REQUEST);
}
/**
* Result is returned in this method after QR code scanning is complete
*/
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == LOGIN_SCAN_REQUEST) {
if (resultCode == RESULT_OK) {
String qrcodeString = data.getStringExtra(
Intents.Scan.RESULT).trim();
Log.i("QR String", qrcodeString);
//Do what you want with QR Code here
}
}
}
I am using mapview in my android app
using the class com.google.android.maps
I wont lo load markers using background process when the user navigate I want to
send the Viewport coordinate to my server
I can do it in javascript like here
google.maps.event.addListener(map, 'idle', showMarkers);
function showMarkers() {
var bounds = map.getBounds();
// Call you server with ajax passing it the bounds
// In the ajax callback delete the current markers and add new markers
}
But how can I do this in java ? Please suggest.
i post this answer and i hope to save some one else time
i found that the best solution for my case is to use custom map view SimpleMapView
first crete the SimpleMapView class in your project and here is the code
import java.util.ArrayList;
import java.util.List;
import android.content.Context;
import android.graphics.Canvas;
import android.util.AttributeSet;
import android.view.MotionEvent;
import com.google.android.maps.GeoPoint;
import com.google.android.maps.MapController;
import com.google.android.maps.MapView;
public class SimpleMapView extends MapView {
private int currentZoomLevel = -1;
private GeoPoint currentCenter;
private List<ZoomChangeListener> zoomEvents = new ArrayList<ZoomChangeListener>();
private List<PanChangeListener> panEvents = new ArrayList<PanChangeListener>();
public SimpleMapView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
public SimpleMapView(Context context, String apiKey) {
super(context, apiKey);
}
public SimpleMapView(Context context, AttributeSet attrs) {
super(context, attrs);
}
/**
*
* #return
*/
public int[][] getBounds() {
GeoPoint center = getMapCenter();
int latitudeSpan = getLatitudeSpan();
int longtitudeSpan = getLongitudeSpan();
int[][] bounds = new int[2][2];
bounds[0][0] = center.getLatitudeE6() - (latitudeSpan / 2);
bounds[0][1] = center.getLongitudeE6() - (longtitudeSpan / 2);
bounds[1][0] = center.getLatitudeE6() + (latitudeSpan / 2);
bounds[1][1] = center.getLongitudeE6() + (longtitudeSpan / 2);
return bounds;
}
public boolean onTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_UP) {
GeoPoint centerGeoPoint = this.getMapCenter();
if (currentCenter == null ||
(currentCenter.getLatitudeE6() != centerGeoPoint.getLatitudeE6()) ||
(currentCenter.getLongitudeE6() != centerGeoPoint.getLongitudeE6()) ) {
firePanEvent(currentCenter, this.getMapCenter());
}
currentCenter = this.getMapCenter();
}
return super.onTouchEvent(ev);
}
#Override
protected void dispatchDraw(Canvas canvas) {
super.dispatchDraw(canvas);
if(getZoomLevel() != currentZoomLevel){
fireZoomLevel(currentZoomLevel, getZoomLevel());
currentZoomLevel = getZoomLevel();
}
}
#Override
public void setSatellite(boolean on){
super.setSatellite(on);
}
#Override
public MapController getController(){
return super.getController();
}
private void fireZoomLevel(int old, int current){
for(ZoomChangeListener event : zoomEvents){
event.onZoom(old, current);
}
}
private void firePanEvent(GeoPoint old, GeoPoint current){
for(PanChangeListener event : panEvents){
event.onPan(old, current);
}
}
public void addZoomChangeListener(ZoomChangeListener listener){
this.zoomEvents.add(listener);
}
public void addPanChangeListener(PanChangeListener listener){
this.panEvents.add(listener);
}
}
and in your mapactivity just make
SimpleMapView mapView = (SimpleMapView) findViewById(R.id.mapView);
and then you have
mapView.addPanChangeListener(new PanChangeListener() {
#Override
public void onPan(GeoPoint old, GeoPoint current) {
//TODO
//do your work here
}
});
and add the PanChangeListener class here the code
package yourPkageName;
import com.google.android.maps.GeoPoint;
public interface PanChangeListener {
public void onPan(GeoPoint old, GeoPoint current);
}
and add the ZoomChangeListener class here the code
package yourPkageName;
public interface ZoomChangeListener {
public void onZoom(int old, int current);
}
and in your xml file add
<?xml version="1.0" encoding="utf-8"?>
<YourPakageName.SimpleMapView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/mapView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:apiKey="0mAbU5bZyFY2I46PFJ1ysXGcYlAmFM6fYBWSB7Q"
android:clickable="true" />
I am working on something that needed custom drag-and-drop functionality, so I have been subclassing View, doing a bunch of math in response to touch events, and then rendering everything manually through code on the canvas in onDraw. Now, the more functionality I add, the more the code is growing out of control and I find myself writing a ton more code than I would expect to write in a high level environment like Android.
Is this how it's done, or am I missing something? If I'm not doing anything fancy in the UI, the framework handles the majority of my interactions. Built-in controls handle the touches and drags, and my code is pretty much limited to business logic and data. Is there a way to leverage the power of some of the UI controls and things like animations while also doing some of it manually in the onDraw canvas? Is there an accepted standard of when to use one or the other (if indeed the two approaches can be mixed)?
I use drag and drop in my music player application! I give to user the ability to move an song from an playlist to an other playlist. It is really nice and simple for the user. I start the drag event for my view when user make an long tap on an song or when an option from an menu is selected!
This is my class:
package com.liviu.app.smpp.gui;
import android.content.Context;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.RelativeLayout;
import android.widget.TextView;
import com.liviu.app.smpp.R;
import com.liviu.app.smpp.listeners.CollisionListener;
public class SongItemView extends RelativeLayout implements OnClickListener {
// data
private String TAG = "SongItemView";
private Context context;
private LayoutInflater lInflater;
private String title;
private int id;
private int maxHeight = 410;
private int mCurX;
private int mCurY;
//listeners
private CollisionListener onCollisionListener = null;
// views
private View v;
public SongItemView(Context ctx, String title_, int id_) {
super(ctx);
context = ctx;
lInflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
v = lInflater.inflate(R.layout.song_item_view, null);
title = title_;
id = id_;
((TextView)v.findViewById(R.id.siv_title)).setText(title);
addView(v, new RelativeLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
}
#Override
public void onClick(View v) {
Log.e(TAG, "clicked! " + ((TextView)v.findViewById(R.id.piv_title)).getText().toString());
}
public View getView(){
return v;
}
public String getPlsName() {
return title;
}
public int getID() {
return id;
}
public void setTitle(String title_){
((TextView)v.findViewById(R.id.siv_title)).setText(title_);
title = title_;
}
public void setID(int id_) {
id = id_;
}
#Override
public boolean dispatchTouchEvent(MotionEvent event) {
mCurX = (int) event.getRawX();
mCurY = (int) event.getRawY();
int action = event.getAction();
if (action == MotionEvent.ACTION_MOVE)
{
RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
params.leftMargin = mCurX;
params.topMargin = mCurY;
this.setLayoutParams(params);
if(this.getTop() >= maxHeight)
{
Log.e(TAG, "Collision!!!!");
if(onCollisionListener != null){
onCollisionListener.onCollision(this);
}
}
}
return true;
}
public void setOnCollisionListener(CollisionListener listener){
onCollisionListener = listener;
}
public void setMaxHeight(int height){
maxHeight = height;
}
public int getmCurX() {
return mCurX;
}
public int getmCurY() {
return mCurY;
}
public int getMaxHeight() {
return maxHeight;
}
}
I hope this will help a bit.
Thanks!