I am currently using android-demo-app/ObjectDetection/ On Temi Robot, the preloaded images are working so far but when I press "live" to go to live object detection screen, it is rotated 90 degrees to the right.
Temi robot only have a front facing camera on the same side of the screen.
I have tried changing textureView.setTransform() imageAnalysisConfig.Builder().setTargetRotation() imageAnalysis.setTargetRotation() but to no avail
Also tried changing AndroidManifest.xml screenOrientation under activity tag to fullSenor or Landscape but nothing changed.
I have been looking up and down on the Android Developer CameraX page for an answer first link second link but I can't find any. Maybe I am not smart enough to find the solution here.
Any help is much appreciated!
AbstactCameraXActivity.java
private void setupCameraX() {
final TextureView textureView = getCameraPreviewTextureView();
final PreviewConfig previewConfig = new PreviewConfig.Builder().build();
final Preview preview = new Preview(previewConfig);
// Matrix m = new Matrix();
// m.postRotate(180);
// textureView.setTransform(m); //not working
preview.setOnPreviewOutputUpdateListener(output -> textureView.setSurfaceTexture(output.getSurfaceTexture()));
final var imageAnalysisConfig =
new ImageAnalysisConfig.Builder()
.setTargetResolution(new Size(500, 500))
.setCallbackHandler(mBackgroundHandler)
.setImageReaderMode(ImageAnalysis.ImageReaderMode.ACQUIRE_LATEST_IMAGE)
//.setTargetRotation(Surface.ROTATION_0) // not working
.build();
imageAnalysis = new ImageAnalysis(imageAnalysisConfig);
imageAnalysis.setAnalyzer((image, rotationDegrees) -> {
if (SystemClock.elapsedRealtime() - mLastAnalysisResultTime < 500) {
return;
}
final R2 result = analyzeImage(image, rotationDegrees);
if (result != null) {
mLastAnalysisResultTime = SystemClock.elapsedRealtime();
runOnUiThread(() -> applyToUiAnalyzeImageResult(result));
}
});
//imageAnalysis.setTargetRotation(Surface.ROTATION_180); // not working
CameraX.bindToLifecycle(this, preview, imageAnalysis);
}
ObjectDetectionActivity.java
#Override
#WorkerThread
#Nullable
protected AnalysisResult analyzeImage(ImageProxy image, int rotationDegrees) {
try {
if (mModule == null) {
mModule = LiteModuleLoader.load(MainActivity.assetFilePath(getApplicationContext(), "yolov5s.torchscript.ptl"));
}
} catch (IOException e) {
Log.e("Object Detection", "Error reading assets", e);
return null;
}
Bitmap bitmap = imgToBitmap(Objects.requireNonNull(image.getImage()));
Matrix matrix = new Matrix();
matrix.postRotate(90.0f);
bitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true);
Bitmap resizedBitmap = Bitmap.createScaledBitmap(bitmap, PrePostProcessor.mInputWidth, PrePostProcessor.mInputHeight, true);
final Tensor inputTensor = TensorImageUtils.bitmapToFloat32Tensor(resizedBitmap, PrePostProcessor.NO_MEAN_RGB, PrePostProcessor.NO_STD_RGB);
IValue[] outputTuple = mModule.forward(IValue.from(inputTensor)).toTuple();
final Tensor outputTensor = outputTuple[0].toTensor();
final float[] outputs = outputTensor.getDataAsFloatArray();
float imgScaleX = (float)bitmap.getWidth() / PrePostProcessor.mInputWidth;
float imgScaleY = (float)bitmap.getHeight() / PrePostProcessor.mInputHeight;
float ivScaleX = (float)mResultView.getWidth() / bitmap.getWidth();
float ivScaleY = (float)mResultView.getHeight() / bitmap.getHeight();
final ArrayList<Result> results = PrePostProcessor.outputsToNMSPredictions(outputs, imgScaleX, imgScaleY, ivScaleX, ivScaleY, 0, 0);
return new AnalysisResult(results);
}
AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="org.pytorch.demo.objectdetection">
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.CAMERA" />
<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"
android:configChanges="orientation"
android:screenOrientation="fullSensor">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name=".ObjectDetectionActivity"
android:configChanges="orientation"
android:screenOrientation="fullSensor">
</activity>
</application>
</manifest>
Update
I think I may know the problem now. In ObjectDetectionActivity's setupCameraX() method, I should manipulate the textureView and manupilating the pivot of the matrix transform is what I need. I began to see some of the cameraView on screen. However I don't know what is the x and y needed in this parameter...
final TextureView textureView = getCameraPreviewTextureView();
final PreviewConfig previewConfig = new PreviewConfig.Builder().build();
final Preview preview = new Preview(previewConfig);
Matrix m = new Matrix();
m.postRotate(180,x,y);//potential solution here.
textureView.setTransform(m); //not working
preview.setOnPreviewOutputUpdateListener(output -> textureView.setSurfaceTexture(output.getSurfaceTexture()));
I have changed the cameraX version to 1.0.0 from 1.0.0-alpha5
private void setupCameraX() {
ListenableFuture<ProcessCameraProvider> cameraProviderFuture =
ProcessCameraProvider.getInstance(this);
cameraProviderFuture.addListener(() -> {
try {
ProcessCameraProvider cameraProvider = cameraProviderFuture.get();
PreviewView previewView = getCameraPreviewTextureView();
final Preview preview = new Preview.Builder()
.setTargetRotation(Surface.ROTATION_270)//working nicely
.build();
//TODO: Check if result_view can render over preview_view
CameraSelector cameraSelector = new CameraSelector
.Builder()
.requireLensFacing(CameraSelector.LENS_FACING_FRONT)
.build();
preview.setSurfaceProvider(previewView.getSurfaceProvider());
executor = Executors.newSingleThreadExecutor();
imageAnalysis = new ImageAnalysis.Builder()
.setTargetResolution(new Size(500, 500))
.setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
.build();
imageAnalysis.setAnalyzer(executor,
image -> {
Log.d("image analyzer","Entered Analyse method");
if (SystemClock.elapsedRealtime() - mLastAnalysisResultTime < 500) {
return;
}
final T result = analyzeImage(image, 90);
if (result != null) {
mLastAnalysisResultTime = SystemClock.elapsedRealtime();
runOnUiThread(() -> applyToUiAnalyzeImageResult(result));
}
});
camera = cameraProvider.bindToLifecycle(
this,
cameraSelector,
imageAnalysis,
preview);
} catch (InterruptedException | ExecutionException e) {
new AlertDialog
.Builder(this)
.setTitle("Camera setup error")
.setMessage(e.getMessage())
.setPositiveButton("Ok",
(dialog, which) -> {
})
.show();
}
}, ContextCompat.getMainExecutor(this));
Note: getCameraPreviewTextureView() is methods that inflate a ViewStub. I am just following a pytorch android example.
#Override
protected PreviewView getCameraPreviewTextureView() {
mResultView = findViewById(R.id.resultView);
//
return ((ViewStub) findViewById(R.id.preview_view_stub))
.inflate()
.findViewById(R.id.preview_view);
}
Related
I copy a source follow underlink
How to install Android apk from code in unity
but if I play in phone, it is show an error
Error:android.content.ActivityNotFoundException: No Activity found to handle Intent{act=android.intent.action.VIEW dat=content://com.mycompany.productname.fileprovider/files_root/files/data/ApkDownload.apk typ=applcation/vnd.android.package-archive flg=0x10000001}
Code:
public class AutoUpdateNew : MonoBehaviour {
public string downloadurl;
void Start()
{
StartCoroutine(downLoadFromServer());
}
IEnumerator downLoadFromServer()
{
//downloadurl = string url;
string url = "https://drive.google.com/uc?export=download&id=1SUqRAITK-8ezVA7t6ORuuS8X9f69ei8v";
string savePath = Path.Combine(Application.persistentDataPath, "data");
savePath = Path.Combine(savePath, "ApkDownload.apk");
Dictionary<string, string> header = new Dictionary<string, string>();
string userAgent = "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36";
header.Add("User-Agent", userAgent);
WWW www = new WWW(url, null, header);
while (!www.isDone)
{
//Must yield below/wait for a frame
GameObject.Find("TextDebug").GetComponent<Text>().text = "Stat: " + www.progress;
yield return null;
}
byte[] yourBytes = www.bytes;
GameObject.Find("TextDebug").GetComponent<Text>().text = "Done downloading. Size: " + yourBytes.Length;
//Create Directory if it does not exist
if (!Directory.Exists(Path.GetDirectoryName(savePath)))
{
Directory.CreateDirectory(Path.GetDirectoryName(savePath));
GameObject.Find("TextDebug").GetComponent<Text>().text = "Created Dir";
}
try
{
//Now Save it
System.IO.File.WriteAllBytes(savePath, yourBytes);
Debug.Log("Saved Data to: " + savePath.Replace("/", "\\"));
GameObject.Find("TextDebug").GetComponent<Text>().text = "Saved Data";
}
catch (Exception e)
{
Debug.LogWarning("Failed To Save Data to: " + savePath.Replace("/", "\\"));
Debug.LogWarning("Error: " + e.Message);
GameObject.Find("TextDebug").GetComponent<Text>().text = "Error Saving Data";
}
//Install APK
installApp(savePath);
}
/*
public bool installApp(string apkPath)
{
try
{
AndroidJavaClass intentObj = new AndroidJavaClass("android.content.Intent");
string ACTION_VIEW = intentObj.GetStatic<string>("ACTION_VIEW");
int FLAG_ACTIVITY_NEW_TASK = intentObj.GetStatic<int>("FLAG_ACTIVITY_NEW_TASK");
AndroidJavaObject intent = new AndroidJavaObject("android.content.Intent", ACTION_VIEW);
AndroidJavaObject fileObj = new AndroidJavaObject("java.io.File", apkPath);
AndroidJavaClass uriObj = new AndroidJavaClass("android.net.Uri");
AndroidJavaObject uri = uriObj.CallStatic<AndroidJavaObject>("fromFile", fileObj);
intent.Call<AndroidJavaObject>("setDataAndType", uri, "application/vnd.android.package-archive");
intent.Call<AndroidJavaObject>("addFlags", FLAG_ACTIVITY_NEW_TASK);
intent.Call<AndroidJavaObject>("setClassName","com.mycompany.productname","com.mycompany.productname.PackageInstallerActivity");
AndroidJavaClass unityPlayer = new AndroidJavaClass("com.unity3d.player.UnityPlayer");
AndroidJavaObject currentActivity = unityPlayer.GetStatic<AndroidJavaObject>("currentActivity");
currentActivity.Call("startActivity", intent);
GameObject.Find("TextDebug").GetComponent<Text>().text = "Success";
return true;
}
catch (System.Exception e)
{
GameObject.Find("TextDebug").GetComponent<Text>().text = "Error: " + e.Message;
return false;
}
}*/
private bool installApp(string apkPath)
{
bool success = true;
GameObject.Find("TextDebug").GetComponent<Text>().text = "Installing App";
try
{
//Get Activity then Context
AndroidJavaClass unityPlayer = new AndroidJavaClass("com.unity3d.player.UnityPlayer");
AndroidJavaObject currentActivity = unityPlayer.GetStatic<AndroidJavaObject>("currentActivity");
AndroidJavaObject unityContext = currentActivity.Call<AndroidJavaObject>("getApplicationContext");
//Get the package Name
string packageName = unityContext.Call<string>("getPackageName");
string authority = packageName + ".fileprovider";
AndroidJavaClass intentObj = new AndroidJavaClass("android.content.Intent");
string ACTION_VIEW = intentObj.GetStatic<string>("ACTION_VIEW");
AndroidJavaObject intent = new AndroidJavaObject("android.content.Intent", ACTION_VIEW);
int FLAG_ACTIVITY_NEW_TASK = intentObj.GetStatic<int>("FLAG_ACTIVITY_NEW_TASK");
int FLAG_GRANT_READ_URI_PERMISSION = intentObj.GetStatic<int>("FLAG_GRANT_READ_URI_PERMISSION");
//File fileObj = new File(String pathname);
AndroidJavaObject fileObj = new AndroidJavaObject("java.io.File", apkPath);
//FileProvider object that will be used to call it static function
AndroidJavaClass fileProvider = new AndroidJavaClass("android.support.v4.content.FileProvider");
//getUriForFile(Context context, String authority, File file)
AndroidJavaObject uri = fileProvider.CallStatic<AndroidJavaObject>("getUriForFile", unityContext, authority, fileObj);
intent.Call<AndroidJavaObject>("setDataAndType", uri, "application/vnd.android.package-archive");
intent.Call<AndroidJavaObject>("addFlags", FLAG_ACTIVITY_NEW_TASK);
intent.Call<AndroidJavaObject>("addFlags", FLAG_GRANT_READ_URI_PERMISSION);
currentActivity.Call("startActivity", intent);
GameObject.Find("TextDebug").GetComponent<Text>().text = "Success";
}
catch (System.Exception e)
{
GameObject.Find("TextDebug").GetComponent<Text>().text = "Error: " + e.Message;
success = false;
}
return success;
}
}
AndroidManifest.xml:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.mycompany.productname" xmlns:tools="http://schemas.android.com/tools"
android:installLocation="preferExternal" android:versionName="1.0" android:versionCode="1">
<supports-screens android:smallScreens="true" android:normalScreens="true" android:largeScreens="true" android:xlargeScreens="true" android:anyDensity="true" />
<application android:theme="#style/UnityThemeSelector" android:icon="#drawable/app_icon" android:label="#string/app_name" android:debuggable="true">
<activity android:name="com.unity3d.player.UnityPlayerActivity" android:label="#string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<meta-data android:name="unityplayer.UnityActivity" android:value="true" />
</activity>
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="com.success.apkupdateTest.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="#xml/provider_paths"/>
</provider>
<activity android:name=".PackageInstallerActivity">
</activity>
</application>
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="27" />
</manifest>
In the link you mentioned in your question, the author said:
For Android API 24 and above, this requires a different code since
the API changed.
So you have to check the version of your device using methods like this and then call the appropriate function.
The game starts from MainActivity class (that extends Activity) and then calls new GamePanel that extends SurfaceView, and there are many classes that update the game, so there is a class (GameplayScene) that checks the touch and check what happened to the game (if the user win or lose), so there is an update method that check if the player wins or lose and if the player win or lose then I want to start a new activity that have a button to restart the game,
The issue is: After starting the new activity (by using Intent from a class that does not extends anything) the app does not respond (but it displays the new activity correctly)
the class that starts the new activity from () :
public class GameplayScene implements Scene {
public GameplayScene() {
....
#Override
public void recieveTouch(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
if (!youWin && !gameOver && player.getRectangle().contains((int) event.getX(), (int) event.getY()))
movingPlayer = true;
forBegin = false;
if (gameOver && System.currentTimeMillis() - gameOverTime >= 2000) {
reset();
gameOver = false;
}
if (youWin && System.currentTimeMillis() - winTime >= 2000) {
reset();
youWin = false;
}
if(!youWin && !gameOver && !movingPlayer) {
if(!forBegin && System.currentTimeMillis() - fireTime >= 200) {
touchFire = true;
count = 1;
fireTime = System.currentTimeMillis();
}
}
break;
case MotionEvent.ACTION_MOVE:
if (!youWin && !gameOver && movingPlayer)
playerPoint.set((int) event.getX(), (int) event.getY());
break;
case MotionEvent.ACTION_UP:
movingPlayer = false;
touchFire = false;
break;
}
}
//#Override
public void draw(Canvas canvas) {
canvas.drawColor(Color.BLACK);
//Drawable d = getDrawable(getResources(), R.drawable.bgi);
//d.setBounds(0, 0,canvas.getWidth(), canvas.getHeight());
//d.draw(canvas);
//canvas.drawBitmap(bgi, null, new Rect(0, 0, canvas.getWidth(), canvas.getHeight()),new Paint());
player.draw(canvas);
obstacleManager.draw(canvas);
if(gameOver) {
Context contX = Constants.CONTEXT;
Intent intent = new Intent(contX , result.class);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_NEW_TASK);
intent.putExtra("toWR",0);
contX.startActivity(intent);
}
if(youWin) {
Context contX = Constants.CONTEXT;
Intent intent = new Intent(contX , result.class);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_NEW_TASK);
intent.putExtra("toWR",1);
contX.startActivity(intent);
}
}
}
Android manifest :
<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=".main_menu"
android:screenOrientation="portrait" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity android:name=".MainActivity"
android:screenOrientation="portrait" />
<SurfaceView android:name=".GamePanel"
android:screenOrientation="portrait" />
<activity android:name=".result"
android:screenOrientation="portrait" />
</application>
I'm assuming you have some kind of DrawingThread(...) that is delegating the draw(...) to this Scene. If that is the case, then you're probably aiming for 60 updates per second... which means this intent will try to launch 60 times per second. That's probably what is causing your app to seem like it isn't responding.
Instead of launching an intent from in here, change the state of your game (or reuse the gameOver state) and interrupt your game loop (drawing thread). Then, start your activity once outside of the game loop, but before your thread finishes.
There is something I don't know why I have set
android:theme="#style/AppTheme"
In AndroidManifest.xml
<application
android:allowBackup="true"
android:icon="#mipmap/ic_launcher"
android:label="#string/app_name"
android:theme="#style/AppTheme">
<activity
android:name=".MainActivity"
android:label="#string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name=".DetailActivity"
android:parentActivityName=".MainActivity">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value=".MainActivity" />
</activity>
<activity android:name=".LineChartActivity1"></activity>
<activity android:name=".RealtimeLineChartActivity">
<!--android:theme="#style/AppTheme"-->
</activity>
<activity android:label="#string/app_name"
android:theme="#style/AppTheme"
android:name=".Add_arg_activity"/>
</application>
And the RealtimeLineChartActivity's xml is
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:theme="#style/AppTheme">
<com.github.mikephil.charting.charts.LineChart
android:id="#+id/chart1"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
But!!!the screen is like this... I'm really sorry about the image, I don't have enough points in stackoverflow.
The main activity screen shot
The second activity screen shot(RealtimeLineChartActivity)
As you can see the color in the pic, the second one is white and the first one is what i want,which has applied in
#style/AppTheme
I don't why. Please, give me something useful, I have checked lots of information.
This is the second one's activity code:
public class RealtimeLineChartActivity extends DemoBase implements
OnChartValueSelectedListener {
private LineChart mChart;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,WindowManager.LayoutParams.FLAG_FULLSCREEN);
setContentView(R.layout.activity_realtime_linechart);
SystemBarTintManager tintManager = new SystemBarTintManager(this);
tintManager.setStatusBarTintResource(R.color.dark_grey);
tintManager.setStatusBarTintEnabled(true);
mChart = (LineChart) findViewById(R.id.chart1);
mChart.setOnChartValueSelectedListener(this);
// no description text
mChart.setDescription("");
mChart.setNoDataTextDescription("You need to provide data for the chart.");
// enable touch gestures
mChart.setTouchEnabled(true);
// enable scaling and dragging
mChart.setDragEnabled(true);
mChart.setScaleEnabled(true);
mChart.setDrawGridBackground(false);
// if disabled, scaling can be done on x- and y-axis separately
mChart.setPinchZoom(true);
// set an alternative background color
mChart.setBackgroundColor(Color.LTGRAY);
LineData data = new LineData();
data.setValueTextColor(Color.WHITE);
// add empty data
mChart.setData(data);
Typeface tf = Typeface.createFromAsset(getAssets(), "OpenSans-Regular.ttf");
// get the legend (only possible after setting data)
Legend l = mChart.getLegend();
// modify the legend ...
// l.setPosition(LegendPosition.LEFT_OF_CHART);
l.setForm(LegendForm.LINE);
l.setTypeface(tf);
l.setTextColor(Color.WHITE);
XAxis xl = mChart.getXAxis();
xl.setTypeface(tf);
xl.setTextColor(Color.WHITE);
xl.setDrawGridLines(false);
xl.setAvoidFirstLastClipping(true);
xl.setSpaceBetweenLabels(5);
xl.setEnabled(true);
YAxis leftAxis = mChart.getAxisLeft();
leftAxis.setTypeface(tf);
leftAxis.setTextColor(Color.WHITE);
leftAxis.setAxisMaxValue(100 f);
leftAxis.setAxisMinValue(0 f);
leftAxis.setDrawGridLines(true);
YAxis rightAxis = mChart.getAxisRight();
rightAxis.setEnabled(false);
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.realtime, menu);
return true;
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.actionAdd:
{
addEntry();
break;
}
case R.id.actionClear:
{
mChart.clearValues();
Toast.makeText(this, "Chart cleared!", Toast.LENGTH_SHORT).show();
break;
}
case R.id.actionFeedMultiple:
{
feedMultiple();
break;
}
}
return true;
}
private int year = 2015;
private void addEntry() {
LineData data = mChart.getData();
if (data != null) {
ILineDataSet set = data.getDataSetByIndex(0);
// set.addEntry(...); // can be called as well
if (set == null) {
set = createSet();
data.addDataSet(set);
}
// add a new x-value first
data.addXValue(mMonths[data.getXValCount() % 12] + " " + (year + data.getXValCount() / 12));
data.addEntry(new Entry((float)(Math.random() * 40) + 30 f, set.getEntryCount()), 0);
// let the chart know it's data has changed
mChart.notifyDataSetChanged();
// limit the number of visible entries
mChart.setVisibleXRangeMaximum(120);
// mChart.setVisibleYRange(30, AxisDependency.LEFT);
// move to the latest entry
mChart.moveViewToX(data.getXValCount() - 121);
// this automatically refreshes the chart (calls invalidate())
// mChart.moveViewTo(data.getXValCount()-7, 55f,
// AxisDependency.LEFT);
}
}
private LineDataSet createSet() {
LineDataSet set = new LineDataSet(null, "Dynamic Data");
set.setAxisDependency(AxisDependency.LEFT);
set.setColor(ColorTemplate.getHoloBlue());
set.setCircleColor(Color.WHITE);
set.setLineWidth(2 f);
set.setCircleRadius(4 f);
set.setFillAlpha(65);
set.setFillColor(ColorTemplate.getHoloBlue());
set.setHighLightColor(Color.rgb(244, 117, 117));
set.setValueTextColor(Color.WHITE);
set.setValueTextSize(9 f);
set.setDrawValues(false);
return set;
}
private void feedMultiple() {
new Thread(new Runnable() {
#Override
public void run() {
for (int i = 0; i < 500; i++) {
runOnUiThread(new Runnable() {
#Override
public void run() {
addEntry();
}
});
try {
Thread.sleep(35);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}).start();
}
#Override
public void onValueSelected(Entry e, int dataSetIndex, Highlight h) {
Log.i("Entry selected", e.toString());
}
#Override
public void onNothingSelected() {
Log.i("Nothing selected", "Nothing selected.");
}
}
Here is my styles.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="AppTheme" parent="AppTheme.Base">
<item name="android:windowDrawsSystemBarBackgrounds">true</item>
<item name="colorPrimary">#3F51B5</item>
<!-- Light Indigo -->
<item name="colorPrimaryDark">#3949AB</item>
<!-- Dark Indigo -->
<item name="colorAccent">#00B0FF</item>
<!-- Blue -->
<item name="android:statusBarColor">#android:color/transparent</item>
</style>
There are two styles.xml in my project.The first is styles.xml the other one is styles-v21.xml.I did't set the item in styles-v21.xml.I use the android version is 6.0 API-23,So I copy the same item in styles.xml.It turns OK.My fault!
The First
The Second
I have a simple music player app (source) which has had playback issues in Lollipop when using headphones. Music will play normally for anywhere from 30 seconds to 5 minutes, then will pause for ~2-4 seconds, then resume.
The behavior seems to generally occur while the screen is off, but acquiring a CPU wakelock didn't help.
The frequency of the pauses seems to accelerate over time. At first it's once per hour, but then the time between pauses decreases by about half each time, until it's pausing almost every minute.
I've observed this behavior with iTunes encoded aac files, others have observed it with mp3s.
This has only been observed while playing over wired headphones. I have never experienced this behavior on a Bluetooth headset.
What could be causing this? It seems like a process priority issue, but I don't know how to address that kind of problem.
I haven't experienced this on Android 4.x.
Here's the Github ticket for this issue.
Here are some relevant bits of source code:
Manifest
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.smithdtyler.prettygoodmusicplayer"
android:versionCode="65"
android:versionName="3.2.14" >
<uses-sdk
android:minSdkVersion="16"
android:targetSdkVersion="19" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<application
android:allowBackup="true"
android:icon="#drawable/ic_pgmp_launcher"
android:label="#string/app_name"
android:theme="#style/AppBaseTheme" >
<!-- Set the artist list to launch mode single task to prevent multiple instances -->
<!-- This fixes an error where exiting the application just brings up another instance -->
<!-- See https://developer.android.com/guide/topics/manifest/activity-element.html#lmode -->
<activity
android:name="com.smithdtyler.prettygoodmusicplayer.ArtistList"
android:label="#string/app_name"
android:launchMode="singleTask" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
<category android:name="android.intent.category.CATEGORY_APP_MUSIC " />
</intent-filter>
</activity>
<activity
android:name="com.smithdtyler.prettygoodmusicplayer.SettingsActivity"
android:label="#string/title_activity_settings" >
</activity>
<activity
android:name="com.smithdtyler.prettygoodmusicplayer.AlbumList"
android:label="#string/title_activity_album_list" >
</activity>
<activity
android:name="com.smithdtyler.prettygoodmusicplayer.SongList"
android:label="#string/title_activity_song_list" >
</activity>
<activity
android:name="com.smithdtyler.prettygoodmusicplayer.NowPlaying"
android:exported="true"
android:label="#string/title_activity_now_playing" >
</activity>
<!--
The service has android:exported="true" because that's needed for
control from the notification. Not sure why it causes a warning...
-->
<service
android:name="com.smithdtyler.prettygoodmusicplayer.MusicPlaybackService"
android:exported="true"
android:icon="#drawable/ic_pgmp_launcher" >
</service>
<receiver
android:name="com.smithdtyler.prettygoodmusicplayer.MusicBroadcastReceiver"
android:enabled="true" >
<intent-filter android:priority="2147483647" >
<action android:name="android.intent.action.MEDIA_BUTTON" />
</intent-filter>
</receiver>
</application>
</manifest>
MusicPlaybackService.onCreate()
#Override
public synchronized void onCreate() {
Log.i(TAG, "Music Playback Service Created!");
isRunning = true;
sharedPref = PreferenceManager.getDefaultSharedPreferences(this);
powerManager =(PowerManager) getSystemService(POWER_SERVICE);
wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
"PGMPWakeLock");
random = new Random();
mp = new MediaPlayer();
mp.setOnCompletionListener(new OnCompletionListener() {
#Override
public void onCompletion(MediaPlayer mp) {
Log.i(TAG, "Song complete");
next();
}
});
// https://developer.android.com/training/managing-audio/audio-focus.html
audioFocusListener = new PrettyGoodAudioFocusChangeListener();
// Get permission to play audio
am = (AudioManager) getBaseContext().getSystemService(
Context.AUDIO_SERVICE);
HandlerThread thread = new HandlerThread("ServiceStartArguments");
thread.start();
// Get the HandlerThread's Looper and use it for our Handler
mServiceLooper = thread.getLooper();
mServiceHandler = new ServiceHandler(mServiceLooper);
// https://stackoverflow.com/questions/19474116/the-constructor-notification-is-deprecated
// https://stackoverflow.com/questions/6406730/updating-an-ongoing-notification-quietly/15538209#15538209
Intent resultIntent = new Intent(this, NowPlaying.class);
resultIntent.putExtra("From_Notification", true);
resultIntent.putExtra(AlbumList.ALBUM_NAME, album);
resultIntent.putExtra(ArtistList.ARTIST_NAME, artist);
resultIntent.putExtra(ArtistList.ARTIST_ABS_PATH_NAME, artistAbsPath);
// Use the FLAG_ACTIVITY_CLEAR_TOP to prevent launching a second
// NowPlaying if one already exists.
resultIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0,
resultIntent, 0);
Builder builder = new NotificationCompat.Builder(
this.getApplicationContext());
String contentText = getResources().getString(R.string.ticker_text);
if (songFile != null) {
contentText = Utils.getPrettySongName(songFile);
}
Notification notification = builder
.setContentText(contentText)
.setSmallIcon(R.drawable.ic_pgmp_launcher)
.setWhen(System.currentTimeMillis())
.setContentIntent(pendingIntent)
.setContentTitle(
getResources().getString(R.string.notification_title))
.build();
startForeground(uniqueid, notification);
timer = new Timer();
timer.scheduleAtFixedRate(new TimerTask() {
public void run() {
onTimerTick();
}
}, 0, 500L);
Log.i(TAG, "Registering event receiver");
mAudioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
// Apparently audio registration is persistent across lots of things...
// restarts, installs, etc.
mAudioManager.registerMediaButtonEventReceiver(cn);
// I tried to register this in the manifest, but it doesn't seen to
// accept it, so I'll do it this way.
getApplicationContext().registerReceiver(receiver, filter);
headphoneReceiver = new HeadphoneBroadcastReceiver();
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction("android.intent.action.HEADSET_PLUG");
registerReceiver(headphoneReceiver, filter);
}
MusicPlaybackService.startPlayingFile()
private synchronized void startPlayingFile(int songProgress) {
// Have we loaded a file yet?
if (mp.getDuration() > 0) {
pause();
mp.stop();
mp.reset();
}
// open the file, pass it into the mp
try {
fis = new FileInputStream(songFile);
mp.setDataSource(fis.getFD());
mp.prepare();
if(songProgress > 0){
mp.seekTo(songProgress);
}
wakeLock.acquire();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (IllegalStateException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
MusicPlaybackService Timer Task
private void onTimerTick() {
long currentTime = System.currentTimeMillis();
if (pauseTime < currentTime) {
pause();
}
updateResumePosition();
sendUpdateToClients();
}
private void updateResumePosition(){
long currentTime = System.currentTimeMillis();
if(currentTime - 10000 > lastResumeUpdateTime){
if(mp != null && songFile != null && mp.isPlaying()){
int pos = mp.getCurrentPosition();
SharedPreferences prefs = getSharedPreferences("PrettyGoodMusicPlayer", MODE_PRIVATE);
Log.i(TAG,
"Preferences update success: "
+ prefs.edit()
.putString(songFile.getParentFile().getAbsolutePath(),songFile.getName() + "~" + pos)
.commit());
}
lastResumeUpdateTime = currentTime;
}
}
private void sendUpdateToClients() {
List<Messenger> toRemove = new ArrayList<Messenger>();
synchronized (mClients) {
for (Messenger client : mClients) {
Message msg = Message.obtain(null, MSG_SERVICE_STATUS);
Bundle b = new Bundle();
if (songFile != null) {
b.putString(PRETTY_SONG_NAME,
Utils.getPrettySongName(songFile));
b.putString(PRETTY_ALBUM_NAME, songFile.getParentFile()
.getName());
b.putString(PRETTY_ARTIST_NAME, songFile.getParentFile()
.getParentFile().getName());
} else {
// songFile can be null while we're shutting down.
b.putString(PRETTY_SONG_NAME, " ");
b.putString(PRETTY_ALBUM_NAME, " ");
b.putString(PRETTY_ARTIST_NAME, " ");
}
b.putBoolean(IS_SHUFFLING, this._shuffle);
if (mp.isPlaying()) {
b.putInt(PLAYBACK_STATE, PlaybackState.PLAYING.ordinal());
} else {
b.putInt(PLAYBACK_STATE, PlaybackState.PAUSED.ordinal());
}
// We might not be able to send the position right away if mp is
// still being created
// so instead let's send the last position we knew about.
if (mp.isPlaying()) {
lastDuration = mp.getDuration();
lastPosition = mp.getCurrentPosition();
}
b.putInt(TRACK_DURATION, lastDuration);
b.putInt(TRACK_POSITION, lastPosition);
msg.setData(b);
try {
client.send(msg);
} catch (RemoteException e) {
e.printStackTrace();
toRemove.add(client);
}
}
for (Messenger remove : toRemove) {
mClients.remove(remove);
}
}
}
I got a really helpful response from the developer of the Vanilla Music Player:
We use a separated thread to read-ahead the currently playing file:
-> The thread reads the file with about 256kb/s, so it will read the file faster than mediaserver does
-> This gives the file a very good chance to stay in the page/disk cache
-> ..and this minimizes the chance for 'drop outs' due to funky sd-cards or other IO-pauses.
The code is located here: https://github.com/vanilla-music/vanilla/blob/master/src/ch/blinkenlights/android/vanilla/ReadaheadThread.java
The code does not depend on any parts of vanilla music: if you would like to give it a try, just drop it into your project and do something like:
onCreate {
...
mReadaheadThread = new ReadaheadThread()
...
}
...
mMediaPlayer.setDataSource(path);
mReadaheadThread.setDataSource(path);
...
Since implementing this change I haven't encountered the problem.
I am implementing CreateNdefMessageCallback and OnNdefPushCompleteCallback. For some reason the callback methods are NEVER touched, no errors on the log either.
I do hear the sound from the API though, the phone that I am debugging on is a Nexus S running version 4.0.4.
Here is my activity:
public class TestActivity extends Activity implements CreateNdefMessageCallback, OnNdefPushCompleteCallback
{
private static SoundHelper soundHelper;
private PowerManager.WakeLock wakeLock;
private NfcAdapter nfcAdapter;
private PendingIntent pendingIntent = null;
private IntentFilter[] intentFiltersArray;
private String[][] techListsArray;
private TextView onScreenLog;
private List<String> uniqueTagsRead = new ArrayList<String>();
/** handler stuff */
private static final int MESSAGE_SENT = 1;
private final Handler handler = new Handler()
{
#Override
public void handleMessage(Message msg)
{
switch (msg.what)
{
case MESSAGE_SENT:
if (soundHelper != null)
{
soundHelper.playSound(R.raw.smw_coin);
}
updateTagCount();
break;
}
}
};
/** Called when the activity is first created. */
#Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.test);
soundHelper = new SoundHelper(this);
onScreenLog = (TextView) findViewById(R.id.log);
// nfc adapter
nfcAdapter = NfcAdapter.getDefaultAdapter(this);
if (nfcAdapter != null)
{
// callbacks
nfcAdapter.setNdefPushMessageCallback(this, this);
nfcAdapter.setOnNdefPushCompleteCallback(this, this);
// other stuff
nfcAdapter = NfcAdapter.getDefaultAdapter(this);
pendingIntent = PendingIntent.getActivity(this, 0, new Intent(this, getClass()).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP), 0);
IntentFilter ndef = new IntentFilter(NfcAdapter.ACTION_NDEF_DISCOVERED);
try
{
ndef.addDataType("*/*");
}
catch (MalformedMimeTypeException e)
{
throw new RuntimeException("fail", e);
}
intentFiltersArray = new IntentFilter[] {ndef, };
techListsArray = new String[][] {
new String[] { IsoDep.class.getName() },
new String[] { NfcA.class.getName() },
new String[] { NfcB.class.getName() },
new String[] { NfcF.class.getName() },
new String[] { NfcV.class.getName() },
new String[] { Ndef.class.getName() },
new String[] { NdefFormatable.class.getName() },
new String[] { MifareClassic.class.getName() },
new String[] { MifareUltralight.class.getName() },
};
}
else
{
onScreenLog.setText("NFC is not available on this device. :(");
}
}
public void onPause()
{
super.onPause();
// end wake lock
wakeLock.release();
nfcAdapter.disableForegroundDispatch(this);
}
public void onResume()
{
super.onResume();
// start wake lock
PowerManager powerManager = (PowerManager) getSystemService(Context.POWER_SERVICE);
wakeLock = powerManager.newWakeLock(PowerManager.FULL_WAKE_LOCK, "DoNotDimScreen");
wakeLock.acquire();
nfcAdapter.enableForegroundDispatch(this, pendingIntent, intentFiltersArray, techListsArray);
}
private void updateTagCount()
{
String newCount = String.valueOf(uniqueTagsRead.size());
String text = getString(R.string.format_count);
text = getString(R.string.format_count).replace("0", newCount);
onScreenLog.setText(text);
}
#Override
public NdefMessage createNdefMessage(NfcEvent event)
{
String message = "This is NFC message";
NdefRecord mimeRecord = createMimeRecord("application/param.android.sample.beam",
message.getBytes());
NdefRecord appRecord = NdefRecord.createApplicationRecord("param.android.sample.beam");
NdefRecord[] ndefRecords = new NdefRecord[] {
mimeRecord,
appRecord
};
NdefMessage ndefMessage = new NdefMessage(ndefRecords);
return ndefMessage;
/*
String mimeType = "text/plain"; // "text/plain";
NdefRecord[] data = {createMimeRecord(mimeType, TEXT_TO_WRITE.getBytes())};
// data[data.length - 1] = NdefRecord.createApplicationRecord(); // com.test.nfc.application.activities.
return new NdefMessage(data);
*/
}
/**
* Creates a custom MIME type encapsulated in an NDEF record
*
* #param mimeType
*/
public NdefRecord createMimeRecord(String mimeType, byte[] payload)
{
byte[] mimeBytes = mimeType.getBytes(Charset.forName("US-ASCII"));
NdefRecord mimeRecord = new NdefRecord(NdefRecord.TNF_MIME_MEDIA, mimeBytes, new byte[0], payload);
return mimeRecord;
}
#Override
public void onNdefPushComplete(NfcEvent event)
{
handler.obtainMessage(MESSAGE_SENT).sendToTarget();
}
}
manifest:
<uses-sdk android:minSdkVersion="14" />
<supports-screens android:anyDensity="true" />
<uses-permission android:name="android.permission.NFC"/>
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-feature android:name="android.hardware.nfc" />
<application android:name="com.test.nfc.application.Application"
android:icon="#drawable/icon_launcher_nfc_droid_hdpi"
android:theme="#android:style/Theme.Light"
android:label="#string/app_name">
<activity
android:label="#string/app_name"
android:name=".application.activities.MainActivity"
android:configChanges="orientation|keyboardHidden">
<intent-filter >
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:label="#string/test"
android:name=".application.activities.TestActivity"
android:configChanges="orientation|keyboardHidden"
android:launchMode="singleTop">
<intent-filter>
<action android:name="android.nfc.action.TECH_DISCOVERED"/>
</intent-filter>
<meta-data android:name="android.nfc.action.TECH_DISCOVERED" android:resource="#xml/nfc_tech_list" />
</activity>
</application>
</manifest>
techlist
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<tech-list>
<tech>android.nfc.tech.IsoDep</tech>
<tech>android.nfc.tech.NfcA</tech>
<tech>android.nfc.tech.NfcB</tech>
<tech>android.nfc.tech.NfcF</tech>
<tech>android.nfc.tech.NfcV</tech>
<tech>android.nfc.tech.Ndef</tech>
<tech>android.nfc.tech.NdefFormatable</tech>
<tech>android.nfc.tech.MifareClassic</tech>
<tech>android.nfc.tech.MifareUltralight</tech>
</tech-list>
</resources>
From your question and example code it is not entirely clear to me whether you want to receive NDEF messages, send them or both.
When using NfcAdapter.enableForegroundDispatch(), your Activity will be notified about new NFC intents by a call to onNewIntent(), so you should override that method in your Activity to receive the intents.
NfcAdapter.CreateNdefMessageCallback and NfcAdapter.OnNdefPushCompleteCallback are used to send NDEF data via Android Beam to another NFC device. The user needs to tap the screen to activate sending the NDEF message, which will cause calls to createNdefMessage() and onNdefPushComplete().
One more remark: if you pass null for the filters and techLists parameters to NfcAdapter.enableForegroundDispatch() that will act as a wild card (so you don't need to declare a complete list of technologies, as you are doing now).
It looks like you are getting the default NFC adapter twice?
nfcAdapter = NfcAdapter.getDefaultAdapter(this);
You do it once before your check for null on nfcAdapter, then in your if statement you do it again. This might have some weird effects. I'm not sure though. Also it looks like you are declaring intent filters at runtime. Do this in the manifest to debug if you still have problems. It's just easier to be certain something is filtering intents correctly that way.
See this sample code and the Android Beam sample in the SDK for more examples:
http://developer.android.com/guide/topics/nfc/nfc.html#p2p