I'm trying to create a plugin in java for Unity, I have already succeeded to fetch and get all connected midi devices, but now I'm stuck to get the pressed notes.
On java side I have theses classes :
public class UnityMidiAndroid {
private static UnityMidiAndroid _instance = new UnityMidiAndroid();
private static final String LOGTAG = "UnityMidiAndroid";
private MidiManager manager = null;
private MidiDeviceInfo[] midiDeviceInfos;
private MidiOutputPort midiOutputPort;
private final UnityMidiAndroidReceiver midiReceiver;
private MidiCallback midiCallback = null;
private UnityMidiAndroid()
{
Log.i(LOGTAG, "Ctor Created new UnityMidiAndroid");
midiReceiver = new UnityMidiAndroidReceiver();
}
public static UnityMidiAndroid getInstance()
{
return (_instance);
}
public void ctor(MidiCallback callback, Activity activity)
{
Context context = activity.getApplicationContext();
manager = (MidiManager) context.getSystemService(MIDI_SERVICE);
midiCallback = callback;
}
public String[] fetchDevices()
{
midiDeviceInfos = manager.getDevices();
String[] availableDevices = new String[midiDeviceInfos.length];
for (int i = 0; i < midiDeviceInfos.length; i++) {
Bundle property = midiDeviceInfos[i].getProperties();
String deviceName = property.getString(MidiDeviceInfo.PROPERTY_PRODUCT);
if (deviceName != null)
availableDevices[i] = deviceName;
}
return (availableDevices);
}
public void openDeviceAtIndex(int index)
{
if (midiDeviceInfos == null || midiDeviceInfos.length == 0)
midiDeviceInfos = manager.getDevices();
if (midiDeviceInfos.length == 0)
return;
openDevice(midiDeviceInfos[0]);
}
private void openDevice(MidiDeviceInfo deviceInfo)
{
if (deviceInfo == null)
return;
manager.openDevice(deviceInfo, device -> {
if (device == null)
Log.e(LOGTAG, "couldn't open " + deviceInfo.toString());
else {
midiOutputPort = device.openOutputPort(0);
if (midiOutputPort == null)
Log.e(LOGTAG, "couln't open input port on " + device);
midiOutputPort.connect(midiReceiver);
}
}, null);
}
private class UnityMidiAndroidReceiver extends MidiReceiver {
#Override
public void onSend(byte[] msg, int offset, int count, long timestamp) throws IOException {
if (midiCallback == null)
throw new IOException("midiCallback = null");
midiCallback.MidiMessage(0, msg);
midiReceiver.send(msg, offset, count);
}
}
public interface MidiCallback {
void MidiMessage(int index, byte[] data);
}
On Unity side I have these classes :
public class UnityMidiAndroidCallBack : AndroidJavaProxy
{
public UnityMidiAndroidCallBack() : base("com.test.unitymidianrdoid.MidiCallback")
{
}
public void MidiMessage(int index, byte[] data)
{
UnityMidiAndroidInputs.str += "NewNote__"; //static property for debug when new note is pressed
}
}
public class UnityMidiAndroid
{
private const string PluginName = "com.test.unitymidianrdoid.UnityMidiAndroid";
private const string UnityPlayer = "com.unity3d.player.UnityPlayer";
private static AndroidJavaClass _pluginClass;
private static AndroidJavaObject _pluginInstance;
private static UnityMidiAndroidCallBack _callBack;
public UnityMidiAndroid()
{
_callBack = new UnityMidiAndroidCallBack();
AndroidJavaClass unityPlayer = new AndroidJavaClass(UnityPlayer);
AndroidJavaObject activity = unityPlayer.GetStatic<AndroidJavaObject>("currentActivity");
PluginInstance.Call("ctor", _callBack, activity);
PluginInstance.Call("openDeviceAtIndex", 0);
}
private static AndroidJavaClass PluginClass
{
get { return _pluginClass ??= new AndroidJavaClass(PluginName); }
}
private static AndroidJavaObject PluginInstance
{
get { return _pluginInstance ??= PluginClass.CallStatic<AndroidJavaObject>("getInstance"); }
}
public string[] GetAvailableDevices()
{
return (PluginInstance.Call<string[]>("fetchDevices"));
}
}
The problem is (I think) the callback function is never triggered, but I don't know why..
For the moment I don't want to know which note is pressed, I just want to trigger the callback when a note is pressed
PS: Sorry for my bad english
Related
I am trying to update the UI depending on whether the data is being loaded or has loaded but it is not working properly. I am using enum class for different states.
Initially the error was
Attempt to invoke virtual method 'void androidx.lifecycle.LiveData.observe(androidx.lifecycle.LifecycleOwner, androidx.lifecycle.Observer)' on a null object reference
Then I passed an empty new MutableLiveData()<>. Now, it doesn't crashes the application, however, the getDataStatus() observer isn't working correctly. Kindly look at my implementations and see if they are right.
DataSource
public class ArticlesDataSource extends PageKeyedDataSource<Integer, NewsItem> {
private static final int FIRST_PAGE = 1;
private static final String TAG = "ArticlesDataSource";
public static final String SORT_ORDER = "publishedAt";
public static final String LANGUAGE = "en";
public static final String API_KEY = Utils.API_KEY;
public static final int PAGE_SIZE = 10;
private String mKeyword;
private MutableLiveData<DataStatus> dataStatusMutableLiveData = new MutableLiveData<>();
public ArticlesDataSource(String keyword) {
mKeyword = keyword;
dataStatusMutableLiveData = new MutableLiveData<>();
}
public MutableLiveData<DataStatus> getDataStatusMutableLiveData() {
return dataStatusMutableLiveData;
}
#Override
public void loadInitial(#NonNull LoadInitialParams<Integer> params, #NonNull LoadInitialCallback<Integer, NewsItem> callback) {
dataStatusMutableLiveData.postValue(DataStatus.LOADING);
NewsAPI newsAPI = ServiceGenerator.createService(NewsAPI.class);
Call<RootJsonData> call = newsAPI.searchArticlesByKeyWord(mKeyword, SORT_ORDER, LANGUAGE, API_KEY, FIRST_PAGE, PAGE_SIZE);
call.enqueue(new Callback<RootJsonData>() {
#Override
public void onResponse(Call<RootJsonData> call, Response<RootJsonData> response) {
if (response.body() != null) {
callback.onResult(response.body().getNewsItems(), null, FIRST_PAGE + 1);
dataStatusMutableLiveData.postValue(DataStatus.LOADED);
}
}
#Override
public void onFailure(Call<RootJsonData> call, Throwable t) {
Log.d(TAG, "onFailure: " + t.getMessage());
dataStatusMutableLiveData.postValue(DataStatus.ERROR);
}
});
}
#Override
public void loadBefore(#NonNull LoadParams<Integer> params, #NonNull LoadCallback<Integer, NewsItem> callback) {
NewsAPI newsAPI = ServiceGenerator.createService(NewsAPI.class);
Call<RootJsonData> call = newsAPI.searchArticlesByKeyWord(mKeyword, SORT_ORDER, LANGUAGE, API_KEY, FIRST_PAGE, PAGE_SIZE);
call.enqueue(new Callback<RootJsonData>() {
#Override
public void onResponse(Call<RootJsonData> call, Response<RootJsonData> response) {
// if the current page is greater than one
// we are decrementing the page number
// else there is no previous page
Integer adjacentKey = (params.key > 1) ? params.key - 1 : null;
if (response.body() != null) {
// passing the loaded data
// and the previous page key
callback.onResult(response.body().getNewsItems(), adjacentKey);
}
}
#Override
public void onFailure(Call<RootJsonData> call, Throwable t) {
Log.d(TAG, "onFailure: " + t.getMessage());
}
});
}
#Override
public void loadAfter(#NonNull LoadParams<Integer> params, #NonNull LoadCallback<Integer, NewsItem> callback) {
NewsAPI newsAPI = ServiceGenerator.createService(NewsAPI.class);
Call<RootJsonData> call = newsAPI.searchArticlesByKeyWord(mKeyword, SORT_ORDER, LANGUAGE, API_KEY, params.key, PAGE_SIZE);
call.enqueue(new Callback<RootJsonData>() {
#Override
public void onResponse(Call<RootJsonData> call, Response<RootJsonData> response) {
dataStatusMutableLiveData.postValue(DataStatus.LOADED);
if (response.code() == 429) {
// no more results
List<NewsItem> emptyList = new ArrayList<>();
callback.onResult(emptyList, null);
}
if (response.body() != null) {
// if the response has next page
// incrementing the next page number
Integer key = params.key + 1;
// passing the loaded data and next page value
if (!response.body().getNewsItems().isEmpty()) {
callback.onResult(response.body().getNewsItems(), key);
}
}
}
#Override
public void onFailure(Call<RootJsonData> call, Throwable t) {
Log.d(TAG, "onFailure: " + t.getMessage());
dataStatusMutableLiveData.postValue(DataStatus.ERROR);
}
});
}
}
DataSourceFactory
public class ArticlesDataSourceFactory extends DataSource.Factory {
private final MutableLiveData<ArticlesDataSource> itemLiveDataSource;
private String mQuery;
private final LiveData<DataStatus> dataStatusLiveData = Transformations.switchMap(itemLiveDataSource, (itemDataSource) -> {
return itemDataSource.getDataStatusMutableLiveData();
});
public ArticlesDataSourceFactory() {
mQuery = "news";
itemLiveDataSource = new MutableLiveData<>();
}
#Override
public DataSource<Integer, NewsItem> create() {
ArticlesDataSource itemDataSource = new ArticlesDataSource(mQuery);
itemLiveDataSource.postValue(itemDataSource);
// dataStatusMutableLiveData = itemDataSource.getDataStatusMutableLiveData();
return itemDataSource;
}
public MutableLiveData<ArticlesDataSource> getArticlesLiveDataSource() {
return itemLiveDataSource;
}
public void setQuery(String query) {
mQuery = query;
}
public MutableLiveData<DataStatus> getDataStatusMutableLiveData() {
return dataStatusMutableLiveData;
}
public void setDataStatusMutableLiveData(DataStatus dataStatus){
dataStatusMutableLiveData.postValue(dataStatus);
}
public LiveData<DataStatus> getDataStatusLiveData() {
return dataStatusLiveData;
}
}
ViewModel
public class ArticlesViewModel extends ViewModel {
public LiveData<PagedList<NewsItem>> itemPagedList;
private MutableLiveData<ArticlesDataSource> liveDataSource;
private ArticlesDataSourceFactory articlesDataSourceFactory;
private LiveData dataStatus = new MutableLiveData<>();
public ArticlesViewModel() {
articlesDataSourceFactory = new ArticlesDataSourceFactory();
liveDataSource = articlesDataSourceFactory.getArticlesLiveDataSource();
dataStatus = articlesDataSourceFactory.getDataStatusMutableLiveData();
PagedList.Config pagedListConfig =
(new PagedList.Config.Builder())
.setEnablePlaceholders(false)
.setPageSize(10).build();
itemPagedList = (new LivePagedListBuilder(articlesDataSourceFactory, pagedListConfig)).build();
}
public void setKeyword(String query) {
if (query.equals("") || query.length() == 0)
articlesDataSourceFactory.setDataStatusMutableLiveData(DataStatus.EMPTY);
else {
articlesDataSourceFactory.setQuery(query);
refreshData();
}
}
void refreshData() {
if (itemPagedList.getValue() != null) {
itemPagedList.getValue().getDataSource().invalidate();
}
}
public LiveData<DataStatus> getDataStatus() {
return dataStatus;
}
}
Fragment
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
View rootView = inflater.inflate(R.layout.fragment_articles, container, false);
mContext = getActivity();
progressBar = rootView.findViewById(R.id.progress_circular);
emptyStateTextView = rootView.findViewById(R.id.empty_view);
swipeRefreshLayout = rootView.findViewById(R.id.swipe_refresh);
textViewTitle = rootView.findViewById(R.id.text_view_top_headlines);
recyclerView = rootView.findViewById(R.id.recycler_view);
if (savedInstanceState != null) {
keyword = savedInstanceState.getString("keyword");
}
initEmptyRecyclerView();
articlesViewModel = ViewModelProviders.of(this).get(ArticlesViewModel.class);
articlesViewModel.itemPagedList.observe(getViewLifecycleOwner(), new Observer<PagedList<NewsItem>>() {
#Override
public void onChanged(PagedList<NewsItem> newsItems) {
adapter.submitList(newsItems);
// TODO: Handle UI changes
// handleUIChanges(newsItems);
}
});
articlesViewModel.getDataStatus().observe(getViewLifecycleOwner(), new Observer<DataStatus>() {
#Override
public void onChanged(DataStatus dataStatus) {
switch (dataStatus) {
case LOADED:
progressBar.setVisibility(View.GONE);
emptyStateTextView.setVisibility(View.INVISIBLE);
swipeRefreshLayout.setRefreshing(false);
textViewTitle.setVisibility(View.VISIBLE);
break;
case LOADING:
progressBar.setVisibility(View.VISIBLE);
swipeRefreshLayout.setRefreshing(true);
textViewTitle.setVisibility(View.INVISIBLE);
emptyStateTextView.setVisibility(View.INVISIBLE);
break;
case EMPTY:
progressBar.setVisibility(View.GONE);
swipeRefreshLayout.setRefreshing(false);
textViewTitle.setVisibility(View.INVISIBLE);
emptyStateTextView.setVisibility(View.VISIBLE);
emptyStateTextView.setText(R.string.no_news_found);
break;
case ERROR:
progressBar.setVisibility(View.GONE);
swipeRefreshLayout.setRefreshing(false);
textViewTitle.setVisibility(View.INVISIBLE);
emptyStateTextView.setVisibility(View.VISIBLE);
emptyStateTextView.setText(R.string.no_internet_connection);
break;
}
}
});
swipeRefreshLayout.setOnRefreshListener(() -> {
articlesViewModel.setKeyword(keyword);
});
setHasOptionsMenu(true);
return rootView;
}
DataStatus
public enum DataStatus {
ERROR,
LOADING,
LOADED,
EMPTY
}
When you call invalidate(), a new datasource will be created by the factory. However, you are directly exposing the data status liveData of the "current" created datasource, without taking into consideration that more will be created in the future.
The solution is to store the current data source in the factory in a MutableLiveData, and expose the "most recent current data status" using switchMap.
public class ArticlesDataSourceFactory extends DataSource.Factory {
private final MutableLiveData<ArticlesDataSource> itemLiveDataSource = new MutableLiveData<>();
private String mQuery = "news";
private final LiveData<DataStatus> dataStatusLiveData = Transformations.switchMap(itemLiveDataSource, (itemDataSource) -> {
return itemDataSource.getDataStatusMutableLiveData();
});
public ArticlesDataSourceFactory() {
}
#Override
public DataSource<Integer, NewsItem> create() {
ArticlesDataSource itemDataSource = new ArticlesDataSource(mQuery);
itemLiveDataSource.postValue(itemDataSource);
...
public LiveData<DataStatus> getDataStatusLiveData() {
return dataStatusLiveData;
}
I have 2 model classes(Data,Title) which contain the same field:
String dataID. I want to get both of this IDs with interface implementation.
I am passing Title model through Bundle to another Activity, passing Data model through Bundle in that same activity(just creating new instance of the activity and resetting information).
I want both of my model classes to implement SharedID interface, with method String getSharedId();
How can I get different ids but from different models? I need to put only one parameter and it should be String in my ViewModelFactory constructor.
public class Data implements SharedId,Parcelable {
private String text;
private String textHeader;
private int viewType;
private String mainId;
private String dataID;
public Data() { }
public String getDataID() {
return dataID;
}
public void setDataID(String dataID) {
this.dataID = dataID;
}
public String getText() {return (String) trimTrailingWhitespace(text); }
public void setText(String text) {
this.text = (String) trimTrailingWhitespace(text);
}
public String getTextHeader() {
return (String) trimTrailingWhitespace(textHeader);
}
public void setTextHeader(String textHeader) {
this.textHeader = textHeader;
}
public int getViewType() {
return viewType;
}
public void setViewType(int viewType) {
this.viewType = viewType;
}
public String getMainId() {
return mainId;
}
public void setMainId(String mainId) {
this.mainId = mainId;
}
protected Data(Parcel in) {
text = in.readString();
textHeader = in.readString();
viewType = in.readInt();
mainId = in.readString();
dataID = in.readString();
}
#Override
public String toString() {
return "Data{" +
"order=" +
", text='" + text + '\'' +
", textHeader='" + textHeader + '\'' +
", viewType=" + viewType +
'}';
}
#SuppressWarnings("StatementWithEmptyBody")
public static CharSequence trimTrailingWhitespace(CharSequence source) {
if (source == null) {
return "";
}
int i = source.length();
// loop back to the first non-whitespace character
while (--i >= 0 && Character.isWhitespace(source.charAt(i))) {
}
return source.subSequence(0, i + 1);
}
public static final Creator<Data> CREATOR = new Creator<Data>() {
#Override
public Data createFromParcel(Parcel in) {
return new Data(in);
}
#Override
public Data[] newArray(int size) {
return new Data[size];
}
};
#Override
public int describeContents() {
return 0;
}
#Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(text);
dest.writeString(textHeader);
dest.writeInt(viewType);
dest.writeString(mainId);
dest.writeString(dataID);
}
#Override
public String getSharedDataId() {
return getDataID();
}
}
public class Title implements SharedId,Parcelable {
private String dataID;
private String title;
public Title() { }
protected Title(Parcel in) {
dataID = in.readString();
title = in.readString();
}
public String getDataID() {
return dataID;
}
public void setDataID(String dataID) {
this.dataID = dataID;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public static final Creator<Title> CREATOR = new Creator<Title>() {
#Override
public Title createFromParcel(Parcel in) {
return new Title(in);
}
#Override
public Title[] newArray(int size) {
return new Title[size];
}
};
#Override
public int describeContents() {
return 0;
}
#Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(dataID);
dest.writeString(title);
}
#NonNull
#Override
public String toString() {
return "Title{" +
"dataID='" + dataID + '\'' +
", titleOrder=" +
", title='" + title + '\'' +
'}';
}
#Override
public String getSharedDataId() {
return getDataID();
}
}
And My DetailActivity code, I already succeeded with the mission of passing id, but i need to do this trough interfaces :( So help me out friends, would really appreciate it!
public class DetailActivity extends AppCompatActivity implements
DetailAdapter.OnDialogClickListener,
DetailAdapter.OnDetailClickListener {
private static String id;
private String parentId;
private Data data;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_detail);
TextView tvToolbarTitle = findViewById(R.id.title_toolbar_detail);
tvToolbarTitle.setSelected(true);
findViewById(R.id.btn_back).setOnClickListener(v -> finish());
ArrayList<SharedId> sharedIds = new ArrayList<>();
sharedIds.add(new Title());
sharedIds.add(new Data());
for (SharedId sharedId : sharedIds){
System.out.println(sharedId.getSharedDataId());
}
Bundle bundle = getIntent().getExtras();
if (bundle != null) {
Title model = bundle.containsKey("ID") ? bundle.getParcelable("ID") : null;
Data childModel = bundle.containsKey("idDetail") ? bundle.getParcelable("idDetail") : null;
}
if (bundle != null) {
Title model = bundle.containsKey("ID") ? bundle.getParcelable("ID") : null;
Data childModel = bundle.containsKey("idDetail") ? bundle.getParcelable("idDetail") : null;
String parentId = bundle.getString("mainScreenId");
if (parentId != null) {
this.parentId = parentId;
}
if (model != null) {
this.id = model.getDataID();
tvToolbarTitle.setText(model.getTitle());
}
if (childModel != null) {
this.id = childModel.getDataID();
tvToolbarTitle.setText(childModel.getTextHeader());
}
}
RecyclerView recyclerView = findViewById(R.id.rv_detail);
DetailAdapter adapter = new DetailAdapter(this, this);
recyclerView.setAdapter(adapter);
// TODO: 3/1/19 change it to single ID // DetailViewModelFactory(); // id != null ? id : parentId
DetailViewModelFactory detailViewModelFactory = new DetailViewModelFactory(id != null ? id : parentId);
DetailActivityViewModel viewModel = ViewModelProviders.of(this, detailViewModelFactory).get(DetailActivityViewModel.class);
FirebaseListLiveData<Data> liveData = viewModel.getLiveDataQuery();
liveData.observe(this, adapter::setNewData);
}
#Override
public void onDialogClicked(#NonNull String text) {
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setMessage(HtmlCompat.fromHtml(text, 0, null, new HandlerHtml()));
builder.setPositiveButton("Ok", null);
builder.show();
}
#Override
public void onDetailClicked(Data data) {
Intent intent = new Intent();
DetailActivity.open(DetailActivity.this);
intent.putExtra("idDetail", data);
intent.putExtra("mainScreenId", id);
startActivity(intent);
}
public static void open(#NonNull Context context) {
context.startActivity(new Intent(context, InfoActivity.class));
}
}
I found a bit different, but working solution!
I create an interface
public interface SharedId {
String getSharedDataId();
String getHeader();
}
Both of my model classes Data + Title implemented Interface and methods from it.
In DetailActivity i created 2 Strings.
private String mainId;
private String detailId;
And then passed ids with my model classes with bundle
`SharedId mainId = new Title();
SharedId detailId = new Data();
Bundle bundle = getIntent().getExtras();
if (bundle != null) {
mainId = bundle.containsKey("ID") ? bundle.getParcelable("ID") : null;
detailId = bundle.containsKey("idDetail") ?
bundle.getParcelable("idDetail") : null;
}
if (mainId != null) {
this.detailId = mainId.getSharedDataId();
tvToolbarTitle.setText(mainId.getHeader());
}
if (detailId != null) {
this.mainId = detailId.getSharedDataId();
tvToolbarTitle.setText(detailId.getHeader());
}
And passed in my ViewmodelFactory
DetailViewModelFactory detailViewModelFactory =
new DetailViewModelFactory(this.detailId != null ?
this.detailId : this.mainId);
Trying to run MIDI on my Android app. I'm following the midisuite example to configure my app and it works fine with the exception of aftertouch. Whenever I try to trigger aftertouch, I run into a threading exception type
InteruptedException. How should I prevent this threading issue? My knowledge on multithreading isn't the best or else I would've figured this out already. All I can really tell right now is that the message is sending too fast and the thread hasn't woken up yet from its sleep call.
I followed the github repo with my code as follows:
MidiReceiver subclass:
#TargetApi(Build.VERSION_CODES.M)
public class MidiEngine extends MidiReceiver {
public AudioActivity activity;
private MidiEventScheduler eventScheduler;
private MidiFramer midiFramer;
private MidiReceiver midiReceiver = new MyReceiver();
private Thread mThread;
private boolean go;
private int mProgram;
public MidiEngine() {
this(new AudioActivity());
}
public MidiEngine(AudioActivity activity) {
this.activity = activity;
midiReceiver = new MyReceiver();
midiFramer = new MidiFramer(midiReceiver);
}
public AudioActivity getActivity() {
return this.activity;
}
/* This will be called when MIDI data arrives. */
#Override
public void onSend(byte[] data, int offset, int count, long timestamp)
throws IOException {
if (eventScheduler != null) {
if (!MidiConstants.isAllActiveSensing(data, offset, count)) {
eventScheduler.getReceiver().send(data, offset, count,
timestamp);
}
}
}
// Custom Listener to send to correct methods
private class MyReceiver extends MidiReceiver {
#Override
public void onSend(byte[] msg, int offset, int count, long timestamp) throws IOException {
byte command = (byte)(msg[0] & MidiConstants.STATUS_COMMAND_MASK);
int channel = (byte)(msg[0] & MidiConstants.STATUS_CHANNEL_MASK);
switch (command) {
case MidiConstants.STATUS_NOTE_ON:
activity.keyDown(i, msg[1], msg[2]);
break;
case MidiConstants.STATUS_NOTE_OFF:
activity.keyUp(channel, msg[1]);
break;
case MidiConstants.STATUS_POLYPHONIC_AFTERTOUCH:
activity.keyDown(channel, msg[1], msg[2]);
break;
case MidiConstants.STATUS_PITCH_BEND:
activity.pitchBendAction(channel, (msg[2] << 7) + msg[1]);
break;
case MidiConstants.STATUS_CONTROL_CHANGE:
activity.ccAction(channel, msg[1], msg[2]);
break;
case MidiConstants.STATUS_PROGRAM_CHANGE:
mProgram = msg[1];
break;
default:
break;
}
}
}
class MyRunnable implements Runnable {
#Override
public void run() {
do {
try {
activity.runOnUiThread(new Runnable() {
#Override
public void run() {
try {
processMidiEvents();
}
catch (Exception e) {
Log.e("Java", "SynthEngine background thread exception.", e);
}
}
});
Thread.sleep(100);
}
catch (InterruptedException e) {
Log.e("Java", "Threading exception", e);
}
}
while (go);
}
}
/**
* #throws IOException
*
*/
private void processMidiEvents() throws IOException {
long now = System.nanoTime();
MidiEventScheduler.MidiEvent event = (MidiEventScheduler.MidiEvent) eventScheduler.getNextEvent(now);
while (event != null) {
midiFramer.send(event.data, 0, event.count, event.getTimestamp());
eventScheduler.addEventToPool(event);
event = (MidiEventScheduler.MidiEvent) eventScheduler.getNextEvent(now);
}
}
public void start() {
stop();
go = true;
mThread = new Thread(new MyRunnable());
mThread.setPriority(6);
eventScheduler = new MidiEventScheduler();
mThread.start();
}
public void stop() {
go = false;
if (mThread != null) {
try {
mThread.interrupt();
mThread.join(500);
}
catch (Exception e) {
}
mThread = null;
eventScheduler = null;
}
}
}
Stack Trace Error (line 154 refers to the Thread.sleep part in my custom Runnable class):
Java: Threading exception
java.lang.InterruptedException
at java.lang.Thread.sleep(Native Method)
at java.lang.Thread.sleep(Thread.java:1031)
at java.lang.Thread.sleep(Thread.java:985)
at com.rfoo.midiapp.communication.MidiEngineInput$MyRunnable.run(MidiEngineInput.java:154)
at java.lang.Thread.run(Thread.java:818)
Thanks!
EDIT: Thread start
Midi Device Service subclass (thread will start whenever a device has connected or disconnected).
#TargetApi(Build.VERSION_CODES.M)
public class MidiSynthDeviceService extends MidiDeviceService {
private static final String TAG = "MidiSynthDeviceService";
private boolean midiStarted = false;
#Override
public void onCreate() {
super.onCreate();
}
#Override
public void onDestroy() {
AudioActivity.midiEngine.stop();
super.onDestroy();
}
#Override
// Declare the receivers associated with your input ports.
public MidiReceiver[] onGetInputPortReceivers() {
return new MidiReceiver[] { AudioActivity.midiEngine };
}
/**
* This will get called when clients connect or disconnect.
* You can use it to turn on your synth only when needed.
*/
#Override
public void onDeviceStatusChanged(MidiDeviceStatus status) {
if (status.isInputPortOpen(0) && !midiStarted) {
AudioActivity.midiEngine.start();
midiStarted = true;
} else if (!status.isInputPortOpen(0) && midiStarted){
AudioActivity.midiEngine.stop();
midiStarted = false;
}
}
}
Activity class:
public class AudioActivity extends AppCompatActivity {
private Thread thread;
public static MidiEngine midiEngine;
#Override
protected void onCreate(Bundle savedInstanceState) {
// Layout inits
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,WindowManager.LayoutParams.FLAG_FULLSCREEN);
// Setup MIDI:
if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_MIDI)) {
Toast.makeText(this, "MIDI not supported!", Toast.LENGTH_LONG).show();
}
else {
midiEngine = new MidiEngine(this);
setupMidi();
}
// Setup audio thread:
if (thread == null) {
thread = new Thread() {
public void run() {
setPriority(Thread.MAX_PRIORITY);
// Runs an Open SL audio thread (C++)
// This generates a waveform.
// AudioEngine is a wrapper class connecting C++ to Java
AudioEngine.runProcess();
}
}
}
}
public void setupMidi() {
if (activity == null) activity = (AudioActivity) getContext();
mMidiManager = (MidiManager) activity.getSystemService(AudioActivity.MIDI_SERVICE);
if (mMidiManager == null) {
Toast.makeText(activity, "MidiManager is null!", Toast.LENGTH_LONG).show();
return;
}
// Get Device Info
MidiDeviceInfo deviceInfo = MidiTools.findDevice(mMidiManager, "RFOO", "AudioApp");
// MIDI Input
portIndex = 0;
inputPortSelector = new MidiOutputPortConnectionSelector(mMidiManager, activity, R.id
.inputListView, deviceInfo, portIndex);
inputPortSelector.setConnectedListener(new MyPortsConnectedListener());
midi_ch_input = 0;
midi_ch_output = 0;
}
// Bunch of UI code here....
}
Hi guys ive just implemented the open source project Auto Update apk.. Because my app is not on play store and it was getting very silly waiting for people to download new updates.. ive had no problems getting it to work it was pretty straight forward to implement.. My problem is the amount of data allowance they give u on a free account... ive used all my quote for the month in less than aweek.. what want to know is how i can change the code so it checks aweb address of my choosing instead of theres.. ive read that it would involve a .txt file that my app would read with new version number inside..
any ideas of what to change in the auto update apk class to achive this? also what would the txt file need to contain. thanks guys n girls
this is the main code for the auto updateapk project
public class AutoUpdateApk extends Observable {
// this class is supposed to be instantiated in any of your activities or,
// better yet, in Application subclass. Something along the lines of:
//
// private AutoUpdateApk aua; <-- you need to add this line of code
//
// public void onCreate(Bundle savedInstanceState) {
// super.onCreate(savedInstanceState);
// setContentView(R.layout.main);
//
// aua = new AutoUpdateApk(getApplicationContext()); <-- and add this line too
//
public AutoUpdateApk(Context ctx) {
setupVariables(ctx);
}
// set icon for notification popup (default = application icon)
//
public static void setIcon( int icon ) {
appIcon = icon;
}
// set name to display in notification popup (default = application label)
//
public static void setName( String name ) {
appName = name;
}
// set update interval (in milliseconds)
//
// there are nice constants in this file: MINUTES, HOURS, DAYS
// you may use them to specify update interval like: 5 * DAYS
//
// please, don't specify update interval below 1 hour, this might
// be considered annoying behaviour and result in service suspension
//
public void setUpdateInterval(long interval) {
if( interval > 60 * MINUTES ) {
UPDATE_INTERVAL = interval;
} else {
Log_e(TAG, "update interval is too short (less than 1 hour)");
}
}
// software updates will use WiFi/Ethernet only (default mode)
//
public static void disableMobileUpdates() {
mobile_updates = false;
}
// software updates will use any internet connection, including mobile
// might be a good idea to have 'unlimited' plan on your 3.75G connection
//
public static void enableMobileUpdates() {
mobile_updates = true;
}
// call this if you want to perform update on demand
// (checking for updates more often than once an hour is not recommended
// and polling server every few minutes might be a reason for suspension)
//
public void checkUpdatesManually() {
checkUpdates(true); // force update check
}
public static final String AUTOUPDATE_CHECKING = "autoupdate_checking";
public static final String AUTOUPDATE_NO_UPDATE = "autoupdate_no_update";
public static final String AUTOUPDATE_GOT_UPDATE = "autoupdate_got_update";
public static final String AUTOUPDATE_HAVE_UPDATE = "autoupdate_have_update";
public void clearSchedule() {
schedule.clear();
}
public void addSchedule(int start, int end) {
schedule.add(new ScheduleEntry(start,end));
}
//
// ---------- everything below this line is private and does not belong to the public API ----------
//
protected final static String TAG = "AutoUpdateApk";
private final static String ANDROID_PACKAGE = "application/vnd.android.package-archive";
// private final static String API_URL = "http://auto-update-apk.appspot.com/check";
private final static String API_URL = "http://www.auto-update-apk.com/check";
protected static Context context = null;
protected static SharedPreferences preferences;
private final static String LAST_UPDATE_KEY = "last_update";
private static long last_update = 0;
private static int appIcon = R.mipmap.ic_launcher;
private static int versionCode = 0; // as low as it gets
private static String packageName;
private static String appName;
private static int device_id;
public static final long MINUTES = 60 * 1000;
public static final long HOURS = 60 * MINUTES;
public static final long DAYS = 24 * HOURS;
// 3-4 hours in dev.mode, 1-2 days for stable releases
private static long UPDATE_INTERVAL = 48 * DAYS; // how often to check
private static boolean mobile_updates = false; // download updates over wifi only
private final static Handler updateHandler = new Handler();
protected final static String UPDATE_FILE = "update_file";
protected final static String SILENT_FAILED = "silent_failed";
private final static String MD5_TIME = "md5_time";
private final static String MD5_KEY = "md5";
private static int NOTIFICATION_ID = 0xBEEF;
private static long WAKEUP_INTERVAL = 15 * MINUTES;
private class ScheduleEntry {
public int start;
public int end;
public ScheduleEntry(int start, int end) {
this.start = start;
this.end = end;
}
}
private static ArrayList<ScheduleEntry> schedule = new
ArrayList<ScheduleEntry>();
private Runnable periodicUpdate = new Runnable() {
#Override
public void run() {
checkUpdates(false);
updateHandler.removeCallbacks(periodicUpdate); // remove whatever others may have posted
updateHandler.postDelayed(this, WAKEUP_INTERVAL);
}
};
private BroadcastReceiver connectivity_receiver = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
NetworkInfo currentNetworkInfo = (NetworkInfo) intent.getParcelableExtra(ConnectivityManager.EXTRA_NETWORK_INFO);
// do application-specific task(s) based on the current network state, such
// as enabling queuing of HTTP requests when currentNetworkInfo is connected etc.
boolean not_mobile = currentNetworkInfo.getTypeName().equalsIgnoreCase("MOBILE") ? false : true;
if( currentNetworkInfo.isConnected() && (mobile_updates || not_mobile) ) {
checkUpdates(false);
updateHandler.postDelayed(periodicUpdate, UPDATE_INTERVAL);
} else {
updateHandler.removeCallbacks(periodicUpdate); // no network anyway
}
}
};
private void setupVariables(Context ctx) {
context = ctx;
packageName = context.getPackageName();
preferences = context.getSharedPreferences( packageName + "_" + TAG, Context.MODE_PRIVATE);
device_id = crc32(Secure.getString( context.getContentResolver(), Secure.ANDROID_ID));
last_update = preferences.getLong("last_update", 0);
NOTIFICATION_ID += crc32(packageName);
// schedule.add(new ScheduleEntry(0,24));
ApplicationInfo appinfo = context.getApplicationInfo();
if( appinfo.icon != 0 ) {
appIcon = appinfo.icon;
} else {
Log_w(TAG, "unable to find application icon");
}
if( appinfo.labelRes != 0 ) {
appName = context.getString(appinfo.labelRes);
} else {
Log_w(TAG, "unable to find application label");
}
if( new File(appinfo.sourceDir).lastModified() > preferences.getLong(MD5_TIME, 0) ) {
preferences.edit().putString( MD5_KEY, MD5Hex(appinfo.sourceDir)).commit();
preferences.edit().putLong( MD5_TIME, System.currentTimeMillis()).commit();
String update_file = preferences.getString(UPDATE_FILE, "");
if( update_file.length() > 0 ) {
if( new File( context.getFilesDir().getAbsolutePath() + "/" + update_file ).delete() ) {
preferences.edit().remove(UPDATE_FILE).remove(SILENT_FAILED).commit();
}
}
}
raise_notification();
if( haveInternetPermissions() ) {
context.registerReceiver( connectivity_receiver,
new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION));
}
}
private boolean checkSchedule() {
if( schedule.size() == 0 ) return true; // empty schedule always fits
int now = Calendar.getInstance().get(Calendar.HOUR_OF_DAY);
for( ScheduleEntry e : schedule ) {
if( now >= e.start && now < e.end ) return true;
}
return false;
}
// required in order to prevent issues in earlier Android version.
private static void disableConnectionReuseIfNecessary() {
// see HttpURLConnection API doc
if (Integer.parseInt(Build.VERSION.SDK) < Build.VERSION_CODES.FROYO) {
System.setProperty("http.keepAlive", "false");
}
}
private static ArrayList<ScheduleEntry> schedule = new ArrayList<ScheduleEntry>();
private Runnable periodicUpdate = new Runnable() {
#Override
public void run() {
checkUpdates(false);
updateHandler.removeCallbacks(periodicUpdate); // remove whatever others may have posted
updateHandler.postDelayed(this, WAKEUP_INTERVAL);
}
};
private BroadcastReceiver connectivity_receiver = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
NetworkInfo currentNetworkInfo = (NetworkInfo) intent.getParcelableExtra(ConnectivityManager.EXTRA_NETWORK_INFO);
// do application-specific task(s) based on the current network state, such
// as enabling queuing of HTTP requests when currentNetworkInfo is connected etc.
boolean not_mobile = currentNetworkInfo.getTypeName().equalsIgnoreCase("MOBILE") ? false : true;
if( currentNetworkInfo.isConnected() && (mobile_updates || not_mobile) ) {
checkUpdates(false);
updateHandler.postDelayed(periodicUpdate, UPDATE_INTERVAL);
} else {
updateHandler.removeCallbacks(periodicUpdate); // no network anyway
}
}
};
private void setupVariables(Context ctx) {
context = ctx;
packageName = context.getPackageName();
preferences = context.getSharedPreferences( packageName + "_" + TAG, Context.MODE_PRIVATE);
device_id = crc32(Secure.getString( context.getContentResolver(), Secure.ANDROID_ID));
last_update = preferences.getLong("last_update", 0);
NOTIFICATION_ID += crc32(packageName);
// schedule.add(new ScheduleEntry(0,24));
ApplicationInfo appinfo = context.getApplicationInfo();
if( appinfo.icon != 0 ) {
appIcon = appinfo.icon;
} else {
Log_w(TAG, "unable to find application icon");
}
if( appinfo.labelRes != 0 ) {
appName = context.getString(appinfo.labelRes);
} else {
Log_w(TAG, "unable to find application label");
}
if( new File(appinfo.sourceDir).lastModified() > preferences.getLong(MD5_TIME, 0) ) {
preferences.edit().putString( MD5_KEY, MD5Hex(appinfo.sourceDir)).commit();
preferences.edit().putLong( MD5_TIME, System.currentTimeMillis()).commit();
String update_file = preferences.getString(UPDATE_FILE, "");
if( update_file.length() > 0 ) {
if( new File( context.getFilesDir().getAbsolutePath() + "/" + update_file ).delete() ) {
preferences.edit().remove(UPDATE_FILE).remove(SILENT_FAILED).commit();
}
}
}
raise_notification();
if( haveInternetPermissions() ) {
context.registerReceiver( connectivity_receiver,
new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION));
}
}
private boolean checkSchedule() {
if( schedule.size() == 0 ) return true; // empty schedule always fits
int now = Calendar.getInstance().get(Calendar.HOUR_OF_DAY);
for( ScheduleEntry e : schedule ) {
if( now >= e.start && now < e.end ) return true;
}
return false;
}
// required in order to prevent issues in earlier Android version.
private static void disableConnectionReuseIfNecessary() {
// see HttpURLConnection API doc
if (Integer.parseInt(Build.VERSION.SDK) < Build.VERSION_CODES.FROYO) {
System.setProperty("http.keepAlive", "false");
}
}
I have an activity that calls a service on its onCreate , however when I try yo run the project I keep getting an error saying the service has leaked and longer bound on the activity that called/registered it .
"Activity com.xera.deviceinsight.home.DataUsageActivity has leaked ServiceConnection com.xera.deviceinsight.home.DataUsageActivity$3#42676a48 that was originally bound here" I am assuming this might have something to do with the lifecycle of the activity . I have both the activity and the service in question below
myActivity
public class DataUsageActivity extends AppCompatActivity implements MonitorService.ServiceCallback
{
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
TinyDB settings = new TinyDB(this);
if (settings.getBoolean(AppPreferences.HAS_LOGGED_IN))
{
this.bindService(
new Intent(this, MonitorService.class),
serviceConnection,
Context.BIND_AUTO_CREATE);
return;
}
}
public void sendResults(int resultCode, Bundle b)
{
// adapter.notifyDataSetChanged();
}
private ServiceConnection serviceConnection = new ServiceConnection()
{
#Override
public void onServiceConnected(ComponentName className, IBinder service)
{
MonitorService.LocalBinder binder = (MonitorService.LocalBinder)service;
backgroundService = binder.getService();
backgroundService.setCallback(DataUsageActivity.this);
backgroundService.start();
}
#Override
public void onServiceDisconnected(ComponentName className)
{
backgroundService = null;
}
};
#Override
public void onResume()
{
super.onResume();
if(backgroundService != null)
{
backgroundService.setCallback(this);
}
}
#Override
public void onPause()
{
super.onPause();
if(backgroundService != null)
{
backgroundService.setCallback(null);
}
}
}
**myService**
public class MonitorService extends Service
{
private boolean initialized = false;
private final IBinder mBinder = new LocalBinder();
private ServiceCallback callback = null;
private Timer timer = null;
private final Handler mHandler = new Handler();
private String foreground = null;
private ArrayList<HashMap<String,Object>> processList;
private ArrayList<String> packages;
private Date split = null;
// private Date startTime = null;
public int timeCheckVariable = 0 ;
public static int SERVICE_PERIOD = 5000; // TODO: customize (this is for scan every 5 seconds)
private final ProcessList pl = new ProcessList(this)
{
#Override
protected boolean isFilteredByName(String pack)
{
// TODO: filter processes by names, return true to skip the process
// always return false (by default) to monitor all processes
return false;
}
};
public interface ServiceCallback
{
void sendResults(int resultCode, Bundle b);
}
public class LocalBinder extends Binder
{
MonitorService getService()
{
// Return this instance of the service so clients can call public methods
return MonitorService.this;
}
}
#Override
public void onCreate()
{
super.onCreate();
initialized = true;
processList = ((DeviceInsightApp)getApplication()).getProcessList();
packages = ((DeviceInsightApp)getApplication()).getPackages();
}
#Override
public IBinder onBind(Intent intent)
{
if(initialized)
{
return mBinder;
}
return null;
}
public void setCallback(ServiceCallback callback)
{
this.callback = callback;
}
// private boolean addToStatistics(String target , Long startTime)
private boolean addToStatistics(String target )
{
boolean changed = false;
Date now = new Date();
if(!TextUtils.isEmpty(target))
{
if(!target.equals(foreground))
{
int i;
// timeCheckVariable = i ;
if(foreground != null && split != null)
{
// TODO: calculate time difference from current moment
// to the moment when previous foreground process was activated
i = packages.indexOf(foreground);
timeCheckVariable = i ;
long delta = (now.getTime() - split.getTime()) / 1000;
Long time = (Long)processList.get(i).get(ProcessList.COLUMN_PROCESS_TIME);
if(time != null)
{
// TODO: add the delta to statistics of 'foreground'
time += delta;
}
else
{
time = new Long(delta);
}
processList.get(i).put(ProcessList.COLUMN_PROCESS_TIME, time);
//String applicationName = (String)processList.get(i).get(ProcessList.COLUMN_PROCESS_NAME);
// DatabaseHandler db = new DatabaseHandler(this);
// int x = time.intValue( );
// db.addAppRecord(new AppUsageClass(applicationName , x));
// db.getApplicationCount();
// List<AppUsageClass> appUsageClass = db.getAllApplications();
// db.getApplicationCount();
// for (AppUsageClass cn : appUsageClass) {
//String log = "Id: " + cn.getID() + " ,ApplicationName : " + cn.getName() + " ,TimeSpent: " + cn.getTimeSpent();
// Log.d("Name: ", log);
//}
}
//update count of process activation for new 'target'
i = packages.indexOf(target);
Integer count = (Integer)processList.get(i).get(ProcessList.COLUMN_PROCESS_COUNT);
if(count != null) count++;
else
{
count = new Integer(1);
}
processList.get(i).put(ProcessList.COLUMN_PROCESS_COUNT, count);
foreground = target;
split = now;
changed = true;
}
}
//Long checkTimeNow = (Long)processList.get(timeCheckVariable).get(ProcessList.COLUMN_PROCESS_TIME);
return changed;
}
public void start()
{
if(timer == null)
{
timer = new Timer();
timer.schedule(new MonitoringTimerTask(), 500, SERVICE_PERIOD);
}
// TODO: startForeground(srvcid, createNotification(null));
}
public void stop()
{
timer.cancel();
timer.purge();
timer = null;
}
private class MonitoringTimerTask extends TimerTask
{
#Override
public void run()
{
fillProcessList();
ActivityManager activityManager = (ActivityManager)MonitorService.this.getSystemService(ACTIVITY_SERVICE);
List<ActivityManager.RunningTaskInfo> taskInfo = activityManager.getRunningTasks(1);
String current = taskInfo.get(0).topActivity.getPackageName(); // gets the application which is in the foreground
int i = packages.indexOf(current);
Long timecheck = (Long)processList.get(i).get(ProcessList.COLUMN_PROCESS_TIME);
if(addToStatistics(current)&& callback != null)
{
final Bundle b = new Bundle();
// TODO: pass necessary info to UI via bundle
mHandler.post(new Runnable()
{
public void run()
{
callback.sendResults(1, b);
}
});
}
}
}
private void fillProcessList()
{
pl.fillProcessList(processList, packages);
}
The problem is that you don't unbind from you service in .onPause() or in .onDestroy(), so if you Activity is destroyed, connection still last, so there is leaked connection. If you want you service to run all the time, you should start it by .startService() and then bind to it. In .onStop() or .onDestroy() unbind from that service