Agora RtcEngine always return null - java

I'm using agora to make voice calls in my android application , i have set up the code as per the documentation but whenever i try to join a call it crashes and says that rtcEngine is null even though it is initialized , any help would be appreciated , Thank you
Code
public class AudioCallActivity extends AppCompatActivity {
// An integer that identifies the local user.
private int uid = 0;
// Track the status of your connection
private boolean isJoined = false;
// Agora engine instance
private RtcEngine agoraEngine;
// UI elements
private TextView infoText;
private Button joinLeaveButton;
private static final int PERMISSION_REQ_ID = 22;
private static final String[] REQUESTED_PERMISSIONS = { Manifest.permission.RECORD_AUDIO};
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_audio_call);
// If all the permissions are granted, initialize the RtcEngine object and join a channel.
if (!checkSelfPermission()) {
ActivityCompat.requestPermissions(this, REQUESTED_PERMISSIONS, PERMISSION_REQ_ID);
}
setupVoiceSDKEngine();
// Set up access to the UI elements
joinLeaveButton = findViewById(R.id.joinLeaveButton);
infoText = findViewById(R.id.infoText);
}
private boolean checkSelfPermission() {
return ContextCompat.checkSelfPermission(this, REQUESTED_PERMISSIONS[0]) == PackageManager.PERMISSION_GRANTED;
}
void showMessage(String message) {
runOnUiThread(() -> Toast.makeText(getApplicationContext(), message, Toast.LENGTH_SHORT).show());
}
private void setupVoiceSDKEngine() {
try {
RtcEngineConfig config = new RtcEngineConfig();
config.mContext = getBaseContext();
config.mAppId = getString(R.string.app_id);
config.mEventHandler = mRtcEventHandler;
agoraEngine = RtcEngine.create(config);
} catch (Exception e) {
throw new RuntimeException("Check the error.");
}
}
private final IRtcEngineEventHandler mRtcEventHandler = new IRtcEngineEventHandler() {
#Override
// Listen for the remote user joining the channel.
public void onUserJoined(int uid, int elapsed) {
runOnUiThread(()->infoText.setText("Remote user joined: " + uid));
}
#Override
public void onJoinChannelSuccess(String channel, int uid, int elapsed) {
// Successfully joined a channel
isJoined = true;
showMessage("Joined Channel " + channel);
runOnUiThread(()->infoText.setText("Waiting for a remote user to join"));
}
#Override
public void onUserOffline(int uid, int reason) {
// Listen for remote users leaving the channel
showMessage("Remote user offline " + uid + " " + reason);
if (isJoined) runOnUiThread(()->infoText.setText("Waiting for a remote user to join"));
}
#Override
public void onLeaveChannel(RtcStats stats) {
// Listen for the local user leaving the channel
runOnUiThread(()->infoText.setText("Press the button to join a channel"));
isJoined = false;
}
};
private void joinChannel() {
ChannelMediaOptions options = new ChannelMediaOptions();
options.autoSubscribeAudio = true;
// Set both clients as the BROADCASTER.
options.clientRoleType = Constants.CLIENT_ROLE_BROADCASTER;
// Set the channel profile as BROADCASTING.
options.channelProfile = Constants.CHANNEL_PROFILE_LIVE_BROADCASTING;
// Join the channel with a temp token.
// You need to specify the user ID yourself, and ensure that it is unique in the channel.
agoraEngine.joinChannel(getString(R.string.agora_token), "ChannelOne", uid, options);
}
public void joinLeaveChannel(View view) {
try {
if (isJoined) {
agoraEngine.leaveChannel();
joinLeaveButton.setText("Join");
} else {
joinChannel();
joinLeaveButton.setText("Leave");
}
}catch (Exception e){
Log.d("TAG","Error is " + e.getMessage());
}
}
#Override
protected void onDestroy() {
agoraEngine.leaveChannel();
// Destroy the engine in a sub-thread to avoid congestion
new Thread(() -> {
RtcEngine.destroy();
agoraEngine = null;
}).start();
onDestroy();
}
}
Error Thrown
Attempt to invoke virtual method 'int io.agora.rtc2.RtcEngine.joinChannel(java.lang.String, java.lang.String, int, io.agora.rtc2.ChannelMediaOptions)' on a null object reference

Related

GRPC android chat Cancelled before half close

I've developed and android chatting app, through the gRPC protocol, problem is, after I send the first message to the server, the server propagates the message to the available clients but then it runs in to this Exception: "io.grpc.StatusRuntimeException: CANCELLED: cancelled before receiving half close"
I've scoured the web for a hint at what could be wrong or a fix but I've got pretty much nothing useful.
This is my server class function that deals with the clients messages
public StreamObserver<Messaging.Message> sendReceiveMessage(StreamObserver<Messaging.Message> responseObserver) {
// return super.sendReceiveMessage(responseObserver);
//While you are on the inside of the chat the bilateral stream will be a continue one
observers.add(responseObserver);
return new StreamObserver<Messaging.Message>() {
#Override
public void onNext(Messaging.Message message) {
//receiving data from client
System.out.println(String.format("Got a message from: '%s' : '%s'", message.getMessageOwnerId(), message.getMessage()));
observers.stream().forEach(o -> {
o.onNext(message);
});
}
#Override
public void onError(Throwable throwable) {
observers.remove(responseObserver);
}
#Override
public void onCompleted() {
observers.remove(responseObserver);
}
};
}
This is the protobuff for the messages
service MessagingService{
rpc GetMessagingHistory(MessageHistoryRequest) returns (stream Message);
rpc SendReceiveMessage(stream Message) returns (stream Message);
}
message MessageHistoryRequest{
int32 chat_id = 1;
int32 last_message_id = 2;
}
message Message{
int32 message_id = 1;
int32 chat_id = 2;
int32 message_owner_id = 3;
string message = 4;
}
and lastly the client side code:
private int CHAT_ID;
private ImageView profilePicture;
private TextView name;
private Button sendButton;
private EditText messageEditText;
private RecyclerView chat;
private MessageAdapter msgAdapter;
private Vector<Messaging.Message> messages = new Vector<>();
private ManagedChannel channel;
private MessagingServiceGrpc.MessagingServiceStub stub;
private StreamObserver<Messaging.Message> toServer;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_chat);
initViews();
}
private void createStub(){
channel = ManagedChannelBuilder.forAddress("10.0.2.2",8080)
.usePlaintext()
.build();
stub = MessagingServiceGrpc.newStub(channel);
toServer = stub.sendReceiveMessage(new StreamObserver<Messaging.Message>() {
#Override
public void onNext(Messaging.Message value) {
msgAdapter.receiveMessage(value);
Log.e("MYERROR",value.getMessageOwnerId() + " : " + value.getMessage() );
msgAdapter.notifyDataSetChanged();
}
#Override
public void onError(Throwable t) {
//nothing
t.printStackTrace();
}
#Override
public void onCompleted() {
Log.e("Completed:","Why");
}
});
}
private void initViews(){
profilePicture = findViewById(R.id.profilePictureChatMenu);
name = findViewById(R.id.chatNameTextView);
Intent intent = getIntent();
CHAT_ID = intent.getIntExtra("ID",-1);
name.setText(intent.getStringExtra("NAME"));
sendButton = findViewById(R.id.sendCommentButton);
messageEditText = findViewById(R.id.writingBarEditText);
chat = findViewById(R.id.chat);
chat.setLayoutManager(new LinearLayoutManager(getApplicationContext()));
msgAdapter = new MessageAdapter(getApplicationContext(),messages);
chat.setAdapter(msgAdapter);
createStub();
sendButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
Messaging.Message sentMessage = Messaging.Message.newBuilder()
.setChatId(CHAT_ID)
.setMessage(messageEditText.getText().toString())
.setMessageOwnerId(ApplicationController.getAccount().getId())
.build();
toServer.onNext(sentMessage);
messageEditText.setText("");
msgAdapter.receiveMessage(sentMessage);
msgAdapter.notifyDataSetChanged();
}
});
}
The message is just informing the server the client cancelled. There's nothing more for the server to do other than clean up resources.
The cancellation may have been triggered because of an I/O failure. The TCP connection may have been dead and sending a message on the connection exposed the fact the TCP connection died (TCP can only reliably detect breakages by performing a write).

How Best To Implement ViewModel( in AndroidX) So Data Survives Configuration Changes

I am trying to implement a ViewModel architecture for a RecyclerView in AndroidX, following the example as stated in enter link description here and enter link description here. Items in the recyclerView get selected on position clicked, but for some reason, the selected item de-select and revert to default after the device is rotated and configuration changed. I know there have been answers for questions like this in the past, but all I have seen are either not directly applicable in my case or are simply for deprecated cases.
CAN SOMEONE PLEASE TELL ME WHAT I AM DOING WRONG!
Below are snippets from my Code:
Dependencies added
dependencies {
def lifecycle_version = "2.2.0"
// ViewModel
implementation "androidx.lifecycle:lifecycle-viewmodel:$lifecycle_version"
// LiveData
implementation "androidx.lifecycle:lifecycle-livedata:$lifecycle_version"
// Saved state module for ViewModel
implementation "androidx.lifecycle:lifecycle-viewmodel-savedstate:$lifecycle_version"
// Annotation processor
annotationProcessor "androidx.lifecycle:lifecycle-compiler:$lifecycle_version"
implementation 'androidx.hilt:hilt-lifecycle-viewmodel:1.0.0-alpha02'
}
Repository Class:
public class TopicRepository {
private Application application;
private SharedPreferences sharedPreferences;
private ArrayList<RootTopic> topicGroupList;
private MutableLiveData<ArrayList<RootTopic>>topicGroupMLD;
public TopicRepository(Application application) {
this.application = application;
}
public LiveData<ArrayList<RootTopic>> getRootTopicLD(String subject){
if (topicGroupMLD == null){
topicGroupMLD = new MutableLiveData<ArrayList<RootTopic>>();
generateTopicGroup(subject);
}
return topicGroupMLD;
}
private void generateTopicGroup(final String subject){
Log.d(TAG, "generateTopicGroup: CALLED");
isRequestingMLD.postValue(true);
final String subjectTopicGroupList = subject + "TopicGroupList";
sharedPreferences = application.getSharedPreferences(AppConstant.Constants.PACKAGE_NAME, Context.MODE_PRIVATE);
String serializedTopicGroup = sharedPreferences.getString(subjectTopicGroupList, null);
if (serializedTopicGroup != null){
Gson gson = new Gson();
Type type = new TypeToken<ArrayList<RootTopic>>(){}.getType();
topicGroupList = gson.fromJson(serializedTopicGroup, type);
topicGroupMLD.postValue(topicGroupList);
}else {// - Not saved in SP
Log.d(TAG, "getTopicGroup: NOT IN SP");
new ActiveConnectionCheck(new ActiveConnectionCheck.Consumer() {
#Override
public void accept(Boolean internet) {
Log.d(TAG, "accept: CHECKED INTERNET");
if (internet){
Log.d(TAG, "accept: INTERNET CONNECTION = TRUE");
internetCheckMLD.postValue(AppConstant.Constants.IS_INTERNET_REQUEST_SUCCESS);
FirebaseFirestore fbFStore = FirebaseFirestore.getInstance();
CollectionReference lectureRef = fbFStore.collection(subject);
lectureRef.orderBy(AppConstant.Constants.POSITION, Query.Direction.ASCENDING)
.get().addOnSuccessListener(
new OnSuccessListener<QuerySnapshot>() {
#Override
public void onSuccess(QuerySnapshot queryDocumentSnapshots) {
ArrayList<Topic>topicList = new ArrayList<>();
ArrayList<String> rootTitleList = new ArrayList<>();
for (QueryDocumentSnapshot snapshot : queryDocumentSnapshots){
Topic topic = snapshot.toObject(Topic.class);
topicList.add(topic);
}
Log.d(TAG, "onSuccess: TopicListSize = " + topicList.size());
for (Topic topic : topicList){
String rootTopicString = topic.getRootTopic();
if (!rootTitleList.contains(rootTopicString)){
rootTitleList.add(rootTopicString);
}
}
Log.d(TAG, "onSuccess: RootTitleListSize = " + rootTitleList.size());
for (int x = 0; x < rootTitleList.size(); x ++){
RootTopic rootTopic = new RootTopic(rootTitleList.get(x), new ArrayList<Topic>());
topicGroupList = new ArrayList<>();
topicGroupList.add(rootTopic);
}
for (int e = 0; e < topicList.size(); e++){
addTopicToGroup(topicGroupList, topicList.get(e));
}
topicGroupMLD.postValue(topicGroupList);
Gson gson = new Gson();
String serializedTopicGroup = gson.toJson(topicGroupList);
sharedPreferences.edit().putString(subjectTopicGroupList, serializedTopicGroup).apply();
Log.d(TAG, "onSuccess: TOPICGROUPSIZE = " + topicGroupList.size());
Log.d(TAG, "onSuccess: SERIALIZED GROUP = " + serializedTopicGroup);
isRequestingMLD.postValue(false);
}
}
).addOnFailureListener(
new OnFailureListener() {
#Override
public void onFailure(#NonNull Exception e) {
isRequestingMLD.postValue(false);
Log.d(TAG, "onFailure: FAILED TO GET TOPICLIST e = " + e.toString());
}
}
);
}else {
internetCheckMLD.postValue(AppConstant.Constants.IS_INTERNET_REQUEST_FAIL);
Log.d(TAG, "accept: InternetCONECTION = " + false);
}
}
});
}
}
private void addTopicToGroup(ArrayList<RootTopic>rootGroup, Topic topic){
for (int x = 0; x < rootGroup.size(); x++){
RootTopic rootTopic = rootGroup.get(x);
if (rootTopic.getRootTopicName().equals(topic.getRootTopic())){
rootTopic.getTopicGroup().add(topic);
}
}
}
}
My ViewModel class
public class LectureViewModel extends AndroidViewModel {
public static final String TAG = AppConstant.Constants.GEN_TAG + ":LectureVM";
private Application application;
private TopicRepository topicRepository;
private ArrayList<RootTopic> topicGroupList;
public LectureViewModel(#NonNull Application application) {
super(application);
this.application = application;
topicRepository = new TopicRepository(application);
}
public LiveData<ArrayList<RootTopic>> getRootTopicListLD(String subject){
return topicRepository.getRootTopicLD(subject);
}
}
Activity Implementing ViewModel
public class LectureRoomActivity extends AppCompatActivity {
public static final String TAG = AppConstant.Constants.GEN_TAG + " LecRoom";
private LectureViewModel lectureRoomVM;
private String subject;
private RecyclerView mainRecyclerView;
private RootTopicAdapter rootTopicAdapter;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_lecture_room);
Intent intent = getIntent();
subject = intent.getStringExtra(AppConstant.Constants.SUBJECT);
mainRecyclerView = findViewById(R.id.recyclerView);
downloadVM = new ViewModelProvider(this).get(DownloadLectureViewModel.class);
lectureRoomVM = new ViewModelProvider(this).get(LectureViewModel.class);
lectureRoomVM.getRootTopicListLD(subject).observe(
this,
new Observer<ArrayList<RootTopic>>() {
#Override
public void onChanged(ArrayList<RootTopic> rootTopics) {
if (rootTopics != null){
currentTopic = lectureRoomVM.getCursorTopic(subject, rootTopics);
setUpRec(rootTopics, currentTopic);
}
}
});
}
private void setUpRec( ArrayList<RootTopic>topicGroup, CursorTopic currentTopic){
rootTopicAdapter = new RootTopicAdapter(topicGroup,
new ArrayList<String>(), currentTopic.getParentPosition(),
currentTopic.getCursorPosition());
LinearLayoutManager linearLayoutManager = new LinearLayoutManager(
this, RecyclerView.VERTICAL,false);
mainRecyclerView.setHasFixedSize(true);
mainRecyclerView.setLayoutManager(linearLayoutManager);
mainRecyclerView.setAdapter(rootTopicAdapter);
Log.d(TAG, "setUpRec: SETTING REC");
}
}
For saving and restoring UI related data you better use savedInstanceState Bundle to survive the last state. To achive this you simply override two methods in you UI activity. See the sample code snippet below.
In your RootTopicAdapter
// Add this where you detect the item click, probably in your adaptor class
private int lastRecyclerViewIndex; // define the variable to hold the last index
...
#Override
public void onClick(View v) {
lastRecyclerViewIndex = getLayoutPosition();
}
public int getLastIndex() {
return lastRecyclerViewIndex;
}
In your view model class
public class LectureViewModel extends AndroidViewModel {
public static final String TAG = AppConstant.Constants.GEN_TAG + ":LectureVM";
private Application application;
private TopicRepository topicRepository;
private ArrayList<RootTopic> topicGroupList;
public boolean mustRestore; // Is there any data to restore
public int lasIndexSelected;
public LectureViewModel(#NonNull Application application) {
super(application);
this.application = application;
topicRepository = new TopicRepository(application);
}
public LiveData<ArrayList<RootTopic>> getRootTopicListLD(String subject){
return topicRepository.getRootTopicLD(subject);
}
}
In you UI activity which uses the RecyclerView
public class LectureRoomActivity extends AppCompatActivity {
...
private LectureViewModel lectureRoomVM;
...
private RecyclerView mainRecyclerView;
private RootTopicAdapter rootTopicAdapter;
...
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_lecture_room);
Intent intent = getIntent();
subject = intent.getStringExtra(AppConstant.Constants.SUBJECT);
mainRecyclerView = findViewById(R.id.recyclerView);
downloadVM = new ViewModelProvider(this).get(DownloadLectureViewModel.class);
lectureRoomVM = new ViewModelProvider(this).get(LectureViewModel.class);
lectureRoomVM.getRootTopicListLD(subject).observe(
this,
new Observer<ArrayList<RootTopic>>() {
#Override
public void onChanged(ArrayList<RootTopic> rootTopics) {
if (rootTopics != null){
currentTopic = lectureRoomVM.getCursorTopic(subject, rootTopics);
setUpRec(rootTopics, currentTopic);
// Exactly here, after setting up the data get your index for example
if(lectureRoomVM.mustRestore){
// Check the item count in the adaptor to avoid crashes
if(mainRecyclerView.getAdapter().getItemCount >= lastRecyclerViewIndex){
mainRecyclerView.findViewHolderForAdapterPosition(lastRecyclerViewIndex).itemView.performClick();
}
// After the restoration set the mustRestore to false
lectureRoomVM.mustRestore = false;
}
}
}
});
}
#Override
protected void onDestroy() {
super.onDestroy();
Log.d(E, "onDestroy");
/*
* Here just set the mustRestore to true in order to be able to restore in onCreate method.
* If the application itself is not destroyed your data will still be live in the
* memory thanks to the ViewModel's life cycle awarness.
*/
lectureRoomVM.mustRestore = true;
}
}
There you go. Try this logic carefully without bugs. Then I think you will achive what you want to get.

Why onPurchasesUpdated isn't being called after launchBillingFlow

I'm in the process of implementing in app billing for Android and have got to the point where I can retrieve a list of products from the store. And can activate the Google purchase dialog via calling the launchBillingFlow() method. The documentation indicates that once this has been called, the onPurchasesUpdated is then called with the result. However this isn't happening for me.
The logging confirms that the purchase is requested (from within my method: startPurchaseFlow()). My onPurchasesUpdated() is also called when the activity first runs and provides a OK result (0) to confirm connection set up.
But why isn't it being called after launchBillingFlow()?
Class that holds purchase mechanics:
public class BillingManager implements PurchasesUpdatedListener {
private final BillingClient mBillingClient; // Billing client used to interface with Google Play
private final Store mActivity; // Referenced in constructor
// Structure to hold the details of SKUs returned from querying store
private static final HashMap<String, List<String>> SKUS;
static
{
SKUS = new HashMap<>();
SKUS.put(BillingClient.SkuType.INAPP, Arrays.asList("com.identifier.unlock")); // Strings for in app permanent products
}
public List<String> getSkus(#BillingClient.SkuType String type) {
return SKUS.get(type);
}
// Constructor
public BillingManager(Store activity) {
mActivity = activity;
mBillingClient = BillingClient.newBuilder(mActivity).setListener(this).build(); // Initialise billing client and set listener
mBillingClient.startConnection(new BillingClientStateListener() { // Start connection via billing client
#Override
public void onBillingSetupFinished(#BillingClient.BillingResponse int billingResponse) { // Actions to complete when connection is set up
if (billingResponse == BillingClient.BillingResponse.OK) {
Log.i("dev", "onBillingSetupFinished() response: " + billingResponse);
mActivity.getProducts();
} else {
Log.w("dev", "onBillingSetupFinished() error code: " + billingResponse);
}
}
#Override
public void onBillingServiceDisconnected() { // Called when the connection is disconnected
Log.w("dev", "onBillingServiceDisconnected()");
}
});
}
// Receives callbacks on updates regarding future purchases
#Override
public void onPurchasesUpdated(#BillingClient.BillingResponse int responseCode,
List<Purchase> purchases) {
Log.d(TAG, "onPurchasesUpdated() response: " + responseCode);
if (responseCode == 0 && !purchases.isEmpty()) {
String purchaseToken;
for (Purchase element : purchases) {
purchaseToken = element.getPurchaseToken();
mBillingClient.consumeAsync(purchaseToken, null); // Test to 'undo' the purchase TEST
}
}
}
// Used to query store and get details of products args include products to query including type and list of SKUs and a listener for response
public void querySkuDetailsAsync(#BillingClient.SkuType final String itemType,
final List<String> skuList, final SkuDetailsResponseListener listener) {
// Create a SkuDetailsParams instance containing args
SkuDetailsParams skuDetailsParams = SkuDetailsParams.newBuilder()
.setSkusList(skuList).setType(itemType).build();
//Query the billing client using the SkuDetailsParams object as an arg
mBillingClient.querySkuDetailsAsync(skuDetailsParams,
new SkuDetailsResponseListener() {
// Override the response to use the listener provided originally in args
#Override
public void onSkuDetailsResponse(int responseCode,
List<SkuDetails> skuDetailsList) {
listener.onSkuDetailsResponse(responseCode, skuDetailsList);
}
});
}
// Start purchase flow with retry option
public void startPurchaseFlow(final String skuId, final String billingType) {
Log.i("dev", "Starting purchaseflow...");
// Specify a runnable to start when connection to Billing client is established
Runnable executeOnConnectedService = new Runnable() {
#Override
public void run() {
BillingFlowParams billingFlowParams = BillingFlowParams.newBuilder()
.setType(billingType)
.setSku(skuId)
.build();
mBillingClient.launchBillingFlow(mActivity, billingFlowParams);
Log.i("dev", "Just called launchBillingFlow..." + skuId);
}
};
// If Billing client was disconnected, we retry 1 time
// and if success, execute the query
startServiceConnectionIfNeeded(executeOnConnectedService);
}
// Starts connection with reconnect try
private void startServiceConnectionIfNeeded(final Runnable executeOnSuccess) {
if (mBillingClient.isReady()) {
if (executeOnSuccess != null) {
executeOnSuccess.run();
}
} else {
mBillingClient.startConnection(new BillingClientStateListener() {
#Override
public void onBillingSetupFinished(#BillingClient.BillingResponse int billingResponse) {
if (billingResponse == BillingClient.BillingResponse.OK) {
Log.i(TAG, "onBillingSetupFinished() response: " + billingResponse);
if (executeOnSuccess != null) {
executeOnSuccess.run();
}
} else {
Log.w(TAG, "onBillingSetupFinished() error code: " + billingResponse);
}
}
#Override
public void onBillingServiceDisconnected() {
Log.w(TAG, "onBillingServiceDisconnected()");
}
});
}
}
} // End of class
Class that implements interface and initiates request for purchases and displays product information:
public class Store extends AppCompatActivity {
SharedPreferences prefs; // used to access and update the pro value
BillingManager billingManager; // Used to process purchases
// Following are used to store local details about unlock product from the play store
String productSku = "Loading"; // Holds SKU details
String productBillingType = "Loading";
String productTitle = "Loading"; // Will be used to display product title in the store activity
String productPrice = "Loading"; // Used to display product price
String productDescription = "Loading"; // Used to display the product description
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_store);
// Set up toolbar
Toolbar myToolbar = (Toolbar) findViewById(R.id.my_toolbar);
setSupportActionBar(myToolbar);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
getSupportActionBar().setDisplayShowHomeEnabled(true);
// Create billing manager instance
billingManager = new BillingManager(this);
// Set up the shared preferences variable
prefs = this.getSharedPreferences(
"com.identifier", Context.MODE_PRIVATE); // Initiate the preferences
// set up buttons
final Button btnBuy = findViewById(R.id.btnBuy);
btnBuy.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
billingManager.startPurchaseFlow(/*productSku*/ "android.test.purchased", productBillingType); // Amended for TEST
}
});
final Button btnPro = findViewById(R.id.btnPro);
btnPro.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
getProducts();
}
});
getProducts();
updateDisplay();
} // End of onCreate
// Used to unlock the app
public void unlock() {
Log.d("dev", "in unlock(), about to set to true");
prefs.edit().putBoolean("pro", true).apply();
MainActivity.pro = true;
}
// Go back if back/home pressed
#Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
// Respond to the action bar's Up/Home button
case android.R.id.home:
NavUtils.navigateUpFromSameTask(this);
return true;
}
return super.onOptionsItemSelected(item);
}
// Used to request details of products from the store from this class
public void getProducts() {
List<String> inAppSkus = billingManager.getSkus(BillingClient.SkuType.INAPP); // Create local list of Skus for query
billingManager.querySkuDetailsAsync(BillingClient.SkuType.INAPP, inAppSkus, new SkuDetailsResponseListener() {
#Override
public void onSkuDetailsResponse(int responseCode, List<SkuDetails> skuDetailsList) {
if (responseCode == BillingClient.BillingResponse.OK && skuDetailsList != null) {
for (SkuDetails details : skuDetailsList) {
productSku = details.getSku();
productTitle = details.getTitle();
productDescription = details.getDescription();
productPrice = details.getPrice();
productBillingType = details.getType();
}
updateDisplay();
}
}
});
}
// Helper method to update the display with strings
private void updateDisplay() {
final TextView titleText = findViewById(R.id.txtTitle);
final TextView descriptionText = findViewById(R.id.txtDescription);
final TextView priceText = findViewById(R.id.txtPrice);
titleText.setText(productTitle);
descriptionText.setText(productDescription);
priceText.setText(productPrice);
}
}
Ok, so this (replacing the onPurchasesUpdated method above) is now working/responding as expected. Why, I don't know, but it is.
#Override
public void onPurchasesUpdated(#BillingClient.BillingResponse int responseCode,
List<Purchase> purchases) {
if (responseCode == BillingClient.BillingResponse.OK
&& purchases != null) {
for (Purchase purchase : purchases) {
Log.d(TAG, "onPurchasesUpdated() response: " + responseCode);
Log.i("dev", "successful purchase...");
String purchasedSku = purchase.getSku();
Log.i("dev", "Purchased SKU: " + purchasedSku);
String purchaseToken = purchase.getPurchaseToken();
mBillingClient.consumeAsync(purchaseToken, null); // Test to 'undo' the purchase TEST
mActivity.unlock();
}
} else if (responseCode == BillingClient.BillingResponse.USER_CANCELED) {
// Handle an error caused by a user cancelling the purchase flow.
Log.d(TAG, "onPurchasesUpdated() response: User cancelled" + responseCode);
} else {
// Handle any other error codes.
}
}

Android MIDI Threading InteruptedException - Aftertouch Messages

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....
}

Pass Latitude and Longitude to Google API Places Search in Android

I have literally been searching for this for weeks. I am a novice java programmer but I have been able to piece together an app that can use a double latitude and longitude hard coded in the same class. It will show a list of current places surrounding those points. I have another separate class with a method that is able to get the current location based on the gps/network but I can't pass the variables created from this second class to the PlaceRequest class. I have looked through all of the tutorials on the above subjects but there isn't anything combining current location and place search results. I have two getters declared but can't call the variables in these. Again sort of a rookie so may be an easy fix. Any ideas?
Update - Here is my code so far:
GooglePlaceActivity.java
public class GooglePlaceActivity extends Activity {
/** Called when the activity is first created. */
Button btn1;
TextView txt1;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
setContentView(R.layout.main);
btn1 = (Button)findViewById(R.id.button1);
txt1 = (TextView)findViewById(R.id.textView1);
btn1.setOnClickListener(l);
}
private class SearchSrv extends AsyncTask<Void, Void, PlacesList>{
#Override
protected PlacesList doInBackground(Void... params) {
PlacesList pl = null;
try {
pl = new PlaceRequest().performSearch();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return pl;
}
#Override
protected void onPostExecute(PlacesList result) {
String text = "Result \n";
if (result!=null){
for(Place place: result.results){
text = text + place.name +"\n";
}
txt1.setText(text);
}
setProgressBarIndeterminateVisibility(false);
}
}
View.OnClickListener l = new View.OnClickListener() {
#Override
public void onClick(View v) {
// TODO Auto-generated method stub
SearchSrv srv = new SearchSrv();
setProgressBarIndeterminateVisibility(true);
srv.execute();
}
};
}
//////////////////////
PlaceRequest.java
public class PlaceRequest {
private static final HttpTransport transport = new ApacheHttpTransport();
private static final String API_KEY = "keyhere";
private static final String LOG_KEY = "GGPlace";
// The different Places API endpoints.
private static final String PLACES_SEARCH_URL = "https://maps.googleapis.com/maps/api/place/search/json?";
private static final String PLACES_AUTOCOMPLETE_URL = "https://maps.googleapis.com/maps/api/place/autocomplete/json?";
private static final String PLACES_DETAILS_URL = "https://maps.googleapis.com/maps/api/place/details/json?";
private static final boolean PRINT_AS_STRING = true;
//double latitude;
//double longitude;
CurrentLocation clo = new CurrentLocation(null);
//clo.onLocationChanged(latitude);
//double longitude = CurrentLocation.getLongitude();
//double latitude = CurrentLocation.getLatitude();
double longi = clo.getLongitude();
double lat = clo.getLatitude();
public PlacesList performSearch() throws Exception {
try {
//CurrentLocation currlo = new CurrentLocation();
//double lat = currlo.getLatitude();
//double longi = currlo.getLongitude();
Log.v(LOG_KEY, "Start Search");
GenericUrl reqUrl = new GenericUrl(PLACES_SEARCH_URL);
reqUrl.put("key", API_KEY);
//reqUrl.put("location", latitude + "," + longitude);
//reqUrl.put("location", getLatitude(latitude) + "," + getLongitude());
reqUrl.put("location", lat + "," + longi);
reqUrl.put("radius", 1600);
reqUrl.put("types", "food");
reqUrl.put("sensor", "false");
Log.v(LOG_KEY, "url= " + reqUrl);
HttpRequestFactory httpRequestFactory = createRequestFactory(transport);
HttpRequest request = httpRequestFactory.buildGetRequest(reqUrl);
Log.v(LOG_KEY, request.execute().parseAsString());
PlacesList places = request.execute().parseAs(PlacesList.class);
Log.v(LOG_KEY, "STATUS = " + places.status);
for (Place place : places.results) {
Log.v(LOG_KEY, place.name);
}
return places;
} catch (HttpResponseException e) {
Log.v(LOG_KEY, e.getResponse().parseAsString());
throw e;
}
catch (IOException e) {
// TODO: handle exception
throw e;
}
}
public static HttpRequestFactory createRequestFactory(final HttpTransport transport) {
return transport.createRequestFactory(new HttpRequestInitializer() {
public void initialize(HttpRequest request) {
GoogleHeaders headers = new GoogleHeaders();
headers.setApplicationName("Google-Places-DemoApp");
request.setHeaders(headers);
JsonHttpParser parser = new JsonHttpParser(new JacksonFactory()) ;
//JsonHttpParser.builder(new JacksonFactory());
//parser.jsonFactory = new JacksonFactory();
request.addParser(parser);
}
});
}
}
/////////////
CurrentLocation.java
public class CurrentLocation {
private static final long MINIMUM_DISTANCE_CHANGE_FOR_UPDATES = 1; // in Meters
private static final long MINIMUM_TIME_BETWEEN_UPDATES = 1000; // in Milliseconds
LocationManager locationManager ;
double latitude=0;
double longitude=0;
public CurrentLocation(Context ctxt) {
super();
locationManager = (LocationManager) ctxt.getSystemService(Context.LOCATION_SERVICE);
// Register the listener with the Location Manager to receive location updates
locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER,
MINIMUM_TIME_BETWEEN_UPDATES,
MINIMUM_DISTANCE_CHANGE_FOR_UPDATES,
new LocationListener() {
#Override
public void onStatusChanged(String provider, int status, Bundle extras) {}
#Override
public void onProviderEnabled(String provider) {}
#Override
public void onProviderDisabled(String provider) {}
#Override
public void onLocationChanged(Location location) {
longitude = location.getLongitude();
latitude = location.getLatitude();
}
});
}
public double getLatitude() {
return latitude;
}
public double getLongitude() {
return longitude;
}
}
Edit: After looking your complete code, I see a few fundamental design flaws so I'm going to show you how I did it and you can adapt it to your program flow. Please keep in mind that this example is vastly simplified from my original, but it should be enough to get you going.
First, the CurrentLocation.java file. My design decision for wrapping this in a Future was so that I can re-use it in multiple activities with the added bonus of killing it when necessary.
public class CurrentLocation implements Callable<Location> {
private static final String TAG = "CurrentLocation";
private Context context;
private LocationManager lm;
private Criteria criteria;
private Location bestResult;
private boolean locationListenerWorking;
public CurrentLocation(Context context) {
lm = (LocationManager)context.getSystemService(Context.LOCATION_SERVICE);
this.context = context;
criteria = new Criteria();
criteria.setAccuracy(Criteria.ACCURACY_FINE);
bestResult = null;
locationListenerWorking = false;
}
public Location call() {
return getLoc();
}
private Location getLoc() {
String provider = lm.getBestProvider(criteria, true);
if (provider != null) {
Log.d(TAG, "Using provider: " +provider);
locationListenerWorking = true;
lm.requestLocationUpdates(provider,
0,
0,
singeUpdateListener,
context.getMainLooper());
} else {
Log.w(TAG, "Couldn't find a location provider");
return null;
}
while (locationListenerWorking) {
// Check for the interrupt signal - terminate if necessary
if (Thread.currentThread().isInterrupted()) {
Log.i(TAG, "User initiated interrupt (cancel signal)");
cleanup();
break;
}
try {
// ghetto implementation of a busy wait...
Thread.sleep(500); // Sleep for half a second
} catch (Exception e) {
Log.d(TAG, "Thread interrupted..");
cleanup();
break;
}
}
return bestResult;
}
private void cleanup() {
if (lm != null) {
Log.d(TAG, "Location manager not null - cleaning up");
lm.removeUpdates(singeUpdateListener);
} else {
Log.d(TAG, "Location manager was NULL - no cleanup necessary");
}
}
/**
* This one-off {#link LocationListener} simply listens for a single location
* update before unregistering itself. The one-off location update is
* returned via the {#link LocationListener} specified in {#link
* setChangedLocationListener}.
*/
private LocationListener singeUpdateListener = new LocationListener() {
public void onLocationChanged(Location location) {
Log.d(TAG, "Got a location update");
if (location == null) {
Log.d(TAG, "Seems as if we got a null location");
} else {
bestResult = location;
}
cleanup();
locationListenerWorking = false;
}
public void onStatusChanged(String provider, int status, Bundle extras) {}
public void onProviderEnabled(String provider) {}
public void onProviderDisabled(String provider) {}
};
}
Then in your calling class (i.e. where you need the lat/lon coordinates - you want to do this from an Activity):
private class GetLocationTask extends AsyncTask <Void, Void, Location> {
private Future<Location> future;
private ExecutorService executor = new ScheduledThreadPoolExecutor(5);
private boolean cancelTriggered = false;
protected void onPreExecute() {
Log.d(TAG, "Starting location get...");
}
public Location doInBackground(Void... arg) {
CurrentLocation currLoc = new CurrentLocation(getApplicationContext());
future = executor.submit(currLoc);
long LOCATION_TIMEOUT = 20000; // ms = 20 sec
try {
// return future.get(Constants.LOCATION_TIMEOUT, TimeUnit.MILLISECONDS);
return future.get(LOCATION_TIMEOUT, TimeUnit.MILLISECONDS);
} catch (Exception e) {
Log.w(TAG, "Location get timed out");
future.cancel(true);
return null;
}
}
public boolean killTask() {
cancelTriggered = true;
boolean futureCancelRes = future.cancel(true);
this.cancel(true);
Log.d(TAG, "Result of cancelling task: " +futureCancelRes);
return futureCancelRes;
}
protected void onPostExecute(Location res) {
if (cancelTriggered) {
Log.d(TAG, "User initiated cancel - this is okay");
cancelTriggered = false;
} else if (res == null) {
Log.d(TAG, "Could not get a location result");
} else {
double lat = res.getLatitude();
double lon = res.getLongitude();
Log.d(TAG, "Latitude: " +lat);
Log.d(TAG, "Longitude: " +lon);
}
}
}
Finally to wrap things up, here's how you call it:
GetLocationTask t = new GetLocationTask();
t.execute();
And if you need to kill the location update for whatever reason (if your user switches out of your activity, etc), this will kill the AsyncTask as well as the associated Future task.
t.killTask();
P.S. You may want to get your API keys changed and edit it out of your post.

Categories