I added truecaller login for phone number verification I installed its working fine in the signed release apk if I generate a signed app bundle it's not working its calling onfailuerLister() error code 3 partnerKey error, it working fine if I have taken debug SHA1 if it's working in debug apk but not working in debug app-bundle, if I have taken SHA1 from release then release apk working fine but not working in the release app bundle, totally app-bundle not working at all, all other feature like location everything working fine in app-bundle except truecaller login
Mainactivity.java
package com.amitor.kotlintrue;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.EditText;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
import com.truecaller.android.sdk.ITrueCallback;
import com.truecaller.android.sdk.TrueError;
import com.truecaller.android.sdk.TrueProfile;
import com.truecaller.android.sdk.TruecallerSDK;
import com.truecaller.android.sdk.TruecallerSdkScope;
public class MainActivity extends AppCompatActivity {
private static final String TAG = MainActivity.class.getSimpleName();
EditText editTextName, mobileNumber;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
editTextName = findViewById(R.id.editTextName);
mobileNumber = findViewById(R.id.mobileNumberText);
(findViewById(R.id.button)).setOnClickListener((View v) -> {
//check if TrueCaller SDk is usable
if(TruecallerSDK.getInstance().isUsable()){
TruecallerSDK.getInstance().getUserProfile(this);
}else{
AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(this);
dialogBuilder.setMessage("Truecaller App not installed.");
dialogBuilder.setPositiveButton("OK", (dialog, which) -> {
Log.d(TAG, "onClick: Closing dialog");
dialog.dismiss();
}
);
dialogBuilder.setIcon(R.drawable.ic_launcher_background);
dialogBuilder.setTitle(" ");
AlertDialog alertDialog = dialogBuilder.create();
alertDialog.show();
}
});
// customisation of TrueCaller function like color , text can be done here
TruecallerSdkScope trueScope = new TruecallerSdkScope.Builder(this, sdkCallback)
.consentMode(TruecallerSdkScope.CONSENT_MODE_BOTTOMSHEET)
.loginTextPrefix(TruecallerSdkScope.LOGIN_TEXT_PREFIX_TO_GET_STARTED)
.loginTextSuffix(TruecallerSdkScope.LOGIN_TEXT_SUFFIX_PLEASE_VERIFY_MOBILE_NO)
.ctaTextPrefix(TruecallerSdkScope.CTA_TEXT_PREFIX_USE)
.buttonShapeOptions(TruecallerSdkScope.BUTTON_SHAPE_ROUNDED)
.privacyPolicyUrl("https://tagle.in")
.termsOfServiceUrl("https://tagle.in")
.footerType(TruecallerSdkScope.FOOTER_TYPE_NONE)
.consentTitleOption(TruecallerSdkScope.SDK_CONSENT_TITLE_LOG_IN)
.build();
TruecallerSDK.init(trueScope);
}
private final ITrueCallback sdkCallback = new ITrueCallback() {
#Override
public void onSuccessProfileShared(#NonNull final TrueProfile trueProfile) {
Toast.makeText(MainActivity.this,trueProfile.firstName, Toast.LENGTH_LONG ).show();
Log.i(TAG, trueProfile.firstName + " " + trueProfile.lastName);
launchHome(trueProfile);
}
#Override
public void onFailureProfileShared(#NonNull final TrueError trueError) {
Toast.makeText(MainActivity.this,TrueError.ERROR_TYPE, Toast.LENGTH_LONG ).show();
// Log.i(TAG, trueError.toString());
}
#Override
public void onVerificationRequired(#Nullable final TrueError trueError) {
Toast.makeText(MainActivity.this,trueError.getErrorType(), Toast.LENGTH_LONG ).show();
Log.i(TAG, "onVerificationRequired");
}
};
#Override
protected void onActivityResult(int requestCode, int resultCode, #Nullable Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == TruecallerSDK.SHARE_PROFILE_REQUEST_CODE) {
TruecallerSDK.getInstance().onActivityResultObtained(this, requestCode, resultCode, data);
} }
private void launchHome(TrueProfile trueProfile) {
editTextName.setText(trueProfile.firstName);
mobileNumber.setText(trueProfile.phoneNumber);
// startActivity(new Intent(getApplicationContext(), HomeActivity.class)
// .putExtra("profile", trueProfile));
// finish();
}
}
build.gradle
plugins {
id 'com.android.application'
}
android {
signingConfigs {
debug {
storeFile file('/Users/moden/key_store.jks')
storePassword '189556'
keyAlias 'key0'
keyPassword '189556'
}
release {
storeFile file('/Users/moden/key_store.jks')
storePassword '123456'
keyAlias 'key0'
keyPassword '123456'
}
}
namespace 'com.amitor.kotlintrue'
compileSdk 33
defaultConfig {
applicationId "com.amitor.kotlintrue"
minSdk 21
targetSdk 33
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
signingConfig signingConfigs.release
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}
dependencies {
implementation 'com.android.support:appcompat-v7:28.0.0'
implementation 'com.android.support.constraint:constraint-layout:2.0.4'
implementation 'com.truecaller.android.sdk:truecaller-sdk:2.7.0'
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'com.android.support.test:runner:1.0.2'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
}
AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<uses-sdk tools:overrideLibrary="com.truecaller.android.sdk" />
<uses-permission android:name="android.permission.READ_CALL_LOG"/>
<uses-permission android:name="android.permission.READ_PHONE_STATE"/>
<uses-permission android:name="android.permission.ANSWER_PHONE_CALLS"/>
<uses-permission android:name="android.permission.INTERNET" />
<application
android:allowBackup="true"
android:dataExtractionRules="#xml/data_extraction_rules"
android:fullBackupContent="#xml/backup_rules"
android:icon="#mipmap/ic_launcher"
android:label="#string/app_name"
android:roundIcon="#mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="#style/Theme.Kotlintrue"
tools:targetApi="31">
<activity
android:name=".MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<meta-data
android:name="android.app.lib_name"
android:value="" />
</activity>
<meta-data
android:name="com.truecaller.android.sdk.PartnerKey"
android:value="3sBRy9fe254fbac0541b5988fb6b085190ed4" />
</application>
</manifest>
I changed many partners kyes changed dependency I tried every solution which I got but not working at all
If you are using your bundle tool to create an apk for testing then you can use the following command to generate a universal apk.
bundletool build-apks --bundle=<bundle_file_location> --output=universal.apks --mode=universal --ks=<keystore_name> --ks-pass='pass:<keystore_passowrd>' --ks-key-alias=<keystore_alias_name> --key-pass='pass:key_password'
Then extract the apk using following command
unzip universal.apks -d apks
For playstore apps you have to update your SHA-1 with the SHA-1 from playstore App Integrity --> App Signing tab in truecaller page.
So Truecaller SDK gets a bit tricky when you are generating bundles. Let's say you have a current Truecaller key which you have made for your release/debug builds and when you run the application it runs fine but when you make a bundle (.aab) of it, it doesn't work.
Let's see how to fix this :
If you read the Truecaller SDK docs carefully ( Link - https://docs.truecaller.com/truecaller-sdk/android/getting-release-ready/google-play-app-signing )
you will find that you would need to create a new Truecaller Partner key but this time with the SHA1 from your Google Play console.
Steps:
Go to your app on Play console.
Find out the SHA1 of your app generated by Play Console ( It's different from the one you provided via Android Studio )
Use that SHA1 to generate a new Truecaller Partner key
Use this partner key when you are making bundles for releasing on Play Store.
If you want to directly install on your device this key won't work so you will have to use your previous key only.
Tip : You can keep both the keys in your strings.xml and use the key generated via SHA1 from Play Console when you are making bundles otherwise use your normal key for debugging.
Related
I am making a cleaner version of a research drone android app that utilises DJI SDK/Hardware and attempting to structure the app more appropriately such that it can be expanded later. The application allows user selection of hardware (currently mock/test, DJI and ToDo) from a config pop up in the main display (class UserInterface extends AppCompatActivity). This is to allow the app to be used without registering or check perms when it is not being used for DJI. Previously I registered the DJI SDK when the app first opens, but it was my first real app and was a mess to follow its flow and didn't manage screen real estate very well.
When the DJI option is actioned from the pop up "config" window on the main interface from UI class, the UI class saves the config to a SettingsManager instance and calls the EventsManager instance with method runSettings(). The method actions the relevant SDK by try/catch{new DJI SDK class} and passes both the instances of EventsManager and UI.
The difference of this major version compared to my first version (which functions correctly) is the API key (changed package name, so new key), I am not doing the SDK registration in the first main activity and the SDK registration is not a viewable class, it only forwards strings to run on UI thread for showToast method in the UI class.
App does not return any errors.
showToast for messages in the DJI SDK class appear on the display but nothing inside the
SDKManagerCallback occurs.
Unsure of constructor in a class with extends AppCompatActivity, context methods seem unavailable when
tried with different class extensions.
I have tried creating two DJI developer keys, no difference.
Unsure of DJI class listing in Manifest as it is not an activity started by an intent with bundle but
does require context.
DJI SDK class is extracted from DJISDKDemo (I am fairly new to android app development, not sure I
could write my own version from the limited explanation on DJI dev site).
Thank you greatly for your help and any other tips appreciated.
The SDKRegistration class:
package com.research.droneapp; // mock package name for SO
import android.Manifest;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import dji.common.error.DJIError;
import dji.common.error.DJISDKError;
import dji.sdk.base.BaseComponent;
import dji.sdk.base.BaseProduct;
import dji.sdk.sdkmanager.DJISDKInitEvent;
import dji.sdk.sdkmanager.DJISDKManager;
public class SoftwareDevelopmentKitDJI extends AppCompatActivity {
private static final String TAG = "SoftwareDevelopmentKit";
public UserInterface userInterface;
public EventsManager eventsManager;
public static final String FLAG_CONNECTION_CHANGE = "dji_sdk_connection_change";
private static BaseProduct mProduct;
private Handler mHandler;
public boolean isConnected = false; // Variable for start up flag
private static final String[] REQUIRED_PERMISSION_LIST = new String[]{
Manifest.permission.VIBRATE,
Manifest.permission.INTERNET,
Manifest.permission.ACCESS_WIFI_STATE,
Manifest.permission.WAKE_LOCK,
Manifest.permission.ACCESS_COARSE_LOCATION,
Manifest.permission.ACCESS_NETWORK_STATE,
Manifest.permission.ACCESS_FINE_LOCATION,
Manifest.permission.CHANGE_WIFI_STATE,
Manifest.permission.WRITE_EXTERNAL_STORAGE,
Manifest.permission.BLUETOOTH,
Manifest.permission.BLUETOOTH_ADMIN,
Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.READ_PHONE_STATE,
};
private List<String> missingPermission = new ArrayList<>();
private AtomicBoolean isRegistrationInProgress = new AtomicBoolean(false);
private static final int REQUEST_PERMISSION_CODE = 12345;
public SoftwareDevelopmentKitDJI(UserInterface userInterface, EventsManager eventsManager) {
Log.d(TAG, "SoftwareDevelopmentKitDJI");
this.userInterface = userInterface;
this.eventsManager = eventsManager;
// Permission checked true actions SDK registration
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
checkAndRequestPermissions();
}
// Handle DJI SDK hardware changes in background thread??
// ToDo: Receive hardware changes in EventsManager
mHandler = new Handler(Looper.getMainLooper());
}
/**
* Checks if there is any missing permissions, and
* requests runtime permission if needed.
*/
private void checkAndRequestPermissions() {
Log.d(TAG, "checkAndRequestPermissions: S");
// Check for permissions
for (String eachPermission : REQUIRED_PERMISSION_LIST) {
if (ContextCompat.checkSelfPermission(userInterface, eachPermission) !=
PackageManager.PERMISSION_GRANTED) {
missingPermission.add(eachPermission);
}
}
// Request for missing permissions
if (missingPermission.isEmpty()) {
Log.d(TAG, "notMissingPerms");
startSDKRegistration();
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
Log.d(TAG, "missingPerms");
passToastToUI("Need permissions!");
ActivityCompat.requestPermissions(userInterface,
missingPermission.toArray(new String[missingPermission.size()]),
REQUEST_PERMISSION_CODE);
}
Log.d(TAG, "checkAndRequestPermissions: E");
}
/**
* Result of runtime permission request
*/
#Override
public void onRequestPermissionsResult(int requestCode,
#NonNull String[] permissions,
#NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
// Check for granted permission and remove from missing list
if (requestCode == REQUEST_PERMISSION_CODE) {
for (int i = grantResults.length - 1; i >= 0; i--) {
if (grantResults[i] == PackageManager.PERMISSION_GRANTED) {
missingPermission.remove(permissions[i]);
}
}
}
// If no missing permission, start SDK registration
if (missingPermission.isEmpty()) {
startSDKRegistration();
} else {
passToastToUI("Missing permissions!!!");
}
}
private void startSDKRegistration() {
Log.d(TAG, "startSDKRegistration: S");
if (isRegistrationInProgress.compareAndSet(false, true)) {
AsyncTask.execute(new Runnable() {
#Override
public void run() {
passToastToUI("registering, pls wait...");
Log.d(TAG, "startSDKRegistration: run");
// ToDO: Investigate why SDKManagerCallback's don't occur
// (is getApplicationContext() correct?)
DJISDKManager.getInstance().registerApp(getApplicationContext(),
new DJISDKManager.SDKManagerCallback() {
#Override
public void onRegister(DJIError djiError) {
Log.d(TAG, "onRegister: S");
if (djiError == DJISDKError.REGISTRATION_SUCCESS) {
passToastToUI("Register Success");
DJISDKManager.getInstance().startConnectionToProduct();
}
else {
passToastToUI("Register sdk failed!");
}
Log.v(TAG, djiError.getDescription());
Log.d(TAG, "onRegister: E");
}
#Override
public void onProductDisconnect() {
Log.d(TAG, "onProductDisconnect");
passToastToUI("Product Disconnected");
notifyStatusChange();
isConnected = false; // Set hardware connection flag
}
#Override
public void onProductConnect(BaseProduct baseProduct) {
Log.d(TAG, String.format("onProductConnect newProduct:%s", baseProduct));
passToastToUI("Product Connected");
notifyStatusChange();
isConnected = true; // Set hardware connection flag
}
#Override
public void onComponentChange(BaseProduct.ComponentKey componentKey, BaseComponent oldComponent,
BaseComponent newComponent) {
if (newComponent != null) {
newComponent.setComponentListener(new BaseComponent.ComponentListener() {
#Override
public void onConnectivityChange(boolean isConnected) {
Log.d(TAG, "onComponentConnectivityChanged: " + isConnected);
notifyStatusChange();
}
});
}
Log.d(TAG, String.format(
"onComponentChange key:%s, oldComponent:%s, newComponent:%s",
componentKey, oldComponent, newComponent));
}
#Override
public void onInitProcess(DJISDKInitEvent djisdkInitEvent, int i) {
Log.d(TAG, "startSDKRegistration: onInitProcess");
}
#Override
public void onDatabaseDownloadProgress(long l, long l1) {
Log.d(TAG, "startSDKRegistration: onDatabaseDownloadProgress");
}
});
}
});
}
Log.d(TAG, "startSDKRegistration: E");
}
private void notifyStatusChange() {
mHandler.removeCallbacks(updateRunnable);
mHandler.postDelayed(updateRunnable, 500);
}
private Runnable updateRunnable = () -> {
Intent intent = new Intent(FLAG_CONNECTION_CHANGE);
sendBroadcast(intent);
};
private void passToastToUI(String toastMsg) {
runOnUiThread(() -> {
userInterface.showToast(toastMsg);
});
}
}
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.research.droneapp"> <!-- mock package name for SO -->
<!-- Permissions and features -->
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
<uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"
tools:ignore="ProtectedPermissions" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-feature android:name="android.hardware.camera" />
<uses-feature android:name="android.hardware.camera.autofocus" />
<uses-feature
android:name="android.hardware.usb.host"
android:required="false" />
<uses-feature
android:name="android.hardware.usb.accessory"
android:required="true" />
<!-- App Activity Process -->
<application
android:name="com.research.droneapp.MApplication"
android:allowBackup="true"
android:icon="#mipmap/hmu_icon"
android:label="#string/app_name"
android:roundIcon="#mipmap/hmu_icon_round"
android:supportsRtl="true"
android:theme="#style/Theme.AppCompat.NoActionBar">
<!-- Main Display -->
<activity android:name="com.research.droneapp.UserInterface" />
<!-- DJI SDK -->
<uses-library android:name="com.android.future.usb.accessory" />
<meta-data
android:name="com.dji.sdk.API_KEY"
android:value="*************" /> <!-- removed for SO -->
<activity
android:name="dji.sdk.sdkmanager.DJIAoaControllerActivity"
android:theme="#android:style/Theme.Translucent">
<intent-filter>
<action android:name="android.hardware.usb.action.USB_ACCESSORY_ATTACHED" />
</intent-filter>
<meta-data
android:name="android.hardware.usb.action.USB_ACCESSORY_ATTACHED"
android:resource="#xml/accessory_filter" />
</activity>
<service android:name="dji.sdk.sdkmanager.DJIGlobalService">
</service>
<activity android:name="com.research.droneapp.SoftwareDevelopmentKitDJI" />
<!-- Splash Screen at Launch -->
<activity android:name="com.research.droneapp.Splash" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
The method that starts the SDK class:
/**
* Settings changes to start relevant classes
*/
public void runSettings() {
Log.d(TAG, "runSettings: S");
// Get hardware selection from settings
int hardwarePosition = settingsManager.getHardwareInt();
/*ToDo: Set Test Environment*/
if (hardwarePosition == 0) { // Operate settings for test environment
Log.d(TAG, "runSettings: hardPos Test");
userInterface.showToast("runSetting: TEST");
}
else if (hardwarePosition == 1) { // Operate settings for DJI
Log.d(TAG, "runSettings: hardPos MavP2");
try {
this.softwareDevelopmentKitDJI = new SoftwareDevelopmentKitDJI(
userInterface, this);
Log.d(TAG, "runSettings: DJI Launched");
userInterface.showToast("runSetting: DJI");
} catch (Exception e) {
Log.d(TAG, "runSettings: DJI Error: "+ e.toString());
userInterface.showToast("runSetting: Error");
}
} // Operate settings for...?
else if (hardwarePosition == 2) { /*ToDo*/
Log.d(TAG, "runSettings: hardPos ToDo");
userInterface.showToast("runSetting: ToDo");
}
else { // Unknown hardware
Log.d(TAG, "runSettings: Error");
userInterface.showToast("runSetting:Error");
}
Log.d(TAG, "runSettings: E");
}
The config window click listener for action changes button, within the method of UI class for config pop up window:
// Action changes
Button btnAction = popupView.findViewById(R.id.btnAction);
btnAction.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
Log.d(TAG, "displayConfigOverlay: onClick -> configAction");
// Update hardware setting
String hardware =
(btnTest.isChecked())?settingsManager.HARDWARE_OPTIONS[0]:
(btnMavP2.isChecked())?settingsManager.HARDWARE_OPTIONS[1]:
(btnToDo.isChecked())?settingsManager.HARDWARE_OPTIONS[2]:
"NoSelection!";
settingsManager.setHardware(hardware);
// Update port number
String port = serverPortEdit.getText().toString();
settingsManager.setServerPort(port);
// Update WSS launch setting
boolean autoLunchWSS = swAutoWSS.isChecked();
settingsManager.setAutoLaunchWSS(autoLunchWSS);
// Display to user
showToast("Hardware: " + hardware + "\nPort: " + port + "\nAutoWSS: " +
autoLunchWSS);
// Push settings
eventsManager.runSettings();
// Close config pop up
popupConfig.dismiss();
}
});
Apologises for the lack of and mixed commenting styles. Again any tips appreciated, still new to android and java.
Min API23 setting in gradle allowed code to run but did not allow SDK to download. Unsure why errors not returned.
Solved by changing gradle to following:
...
android {
compileSdkVersion 29
buildToolsVersion '28.0.3'
useLibrary 'org.apache.http.legacy'
defaultConfig {
minSdkVersion 19
targetSdkVersion 29
multiDexEnabled true
ndk {
abiFilters 'armeabi-v7a', 'x86', 'arm64-v8a'
}
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
debug {
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
...
Additionally found a potential context issue within layout, tools:context="..." was not set to the Activity interacting with this layout.
...
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/mainConstraintLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".UserInterface" >
...
I have an issue with my Android App. I am asking for permissions to the user to access the READ_EXTERNAL_STORAGE but the bug "permission denial" still makes my app crash.
I have spent hours on it and I have no clue how to fix it.
Here's my logcat:
03-07 15:02:12.387 6800-8893/? E/DatabaseUtils: Writing exception to parcel
java.lang.SecurityException: Permission Denial: reading com.android.providers.media.MediaProvider uri content://media/external/images/media from pid=8108, uid=10089 requires android.permission.READ_EXTERNAL_STORAGE, or grantUriPermission()
at android.content.ContentProvider.enforceReadPermissionInner(ContentProvider.java:605)
at android.content.ContentProvider$Transport.enforceReadPermission(ContentProvider.java:480)
at android.content.ContentProvider$Transport.query(ContentProvider.java:211)
at android.content.ContentProviderNative.onTransact(ContentProviderNative.java:112)
at android.os.Binder.execTransact(Binder.java:453)
03-07 15:02:12.391 8108-11729/? E/iu.UploadsManager: Insufficient permissions to process media
java.lang.SecurityException: Permission Denial: reading com.android.providers.media.MediaProvider uri content://media/external/images/media from pid=8108, uid=10089 requires android.permission.READ_EXTERNAL_STORAGE, or grantUriPermission()
at android.os.Parcel.readException(Parcel.java:1602)
at android.database.DatabaseUtils.readExceptionFromParcel(DatabaseUtils.java:183)
at android.database.DatabaseUtils.readExceptionFromParcel(DatabaseUtils.java:135)
at android.content.ContentProviderProxy.query(ContentProviderNative.java:421)
at android.content.ContentResolver.query(ContentResolver.java:502)
at android.content.ContentResolver.query(ContentResolver.java:438)
at lqt.a(PG:14)
at com.google.android.libraries.social.autobackup.FingerprintScannerIntentService.onHandleIntent(PG:15)
at android.app.IntentService$ServiceHandler.handleMessage(IntentService.java:66)
at android.os.Handler.dispatchMessage(Handler.java:102)
at android.os.Looper.loop(Looper.java:150)
at android.os.HandlerThread.run(HandlerThread.java:61)
My Main activity is:
package com.example.arjufy;
import android.app.Activity;
import android.content.Context;
import android.content.DialogInterface;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AlertDialog;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.Manifest;
import android.support.v4.app.ActivityCompat;
import android.widget.Toast;
import android.content.Intent;
import android.support.v4.app.Fragment;
import android.support.v7.widget.Toolbar;
import android.view.Menu;
import android.view.MenuItem;
import android.widget.TextView;
import com.google.firebase.auth.FirebaseAuth;
import com.google.firebase.auth.FirebaseUser;
import com.google.firebase.database.DatabaseReference;
import com.google.firebase.database.FirebaseDatabase;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Environment;
public class MainActivity extends AppCompatActivity implements MarketPlaceFragment.OnListFragmentInteractionListener {
private FirebaseAuth mFirebaseAuth;
private FirebaseUser mFirebaseUser;
private DatabaseReference mDatabaseReference;
private String mUsername;
private String mPhotoUrl;
public static final int MY_PERMISSIONS_REQUEST_READ_EXTERNAL_STORAGE = 123;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (checkPermissionREAD_EXTERNAL_STORAGE(this)) {
// do your stuff..
// Initialize Firebase Auth
mFirebaseAuth = FirebaseAuth.getInstance();
mFirebaseUser = mFirebaseAuth.getCurrentUser();
if (mFirebaseUser == null) {
// Not signed in, launch the Sign In activity
startActivity(new Intent(this, SplashScreenActivity.class));
finish();
return;
} else {
mUsername = mFirebaseUser.getEmail();
}
mDatabaseReference = FirebaseDatabase.getInstance().getReference();
setContentView(R.layout.activity_main);
Fragment fragment = new MarketPlaceFragment();
getSupportFragmentManager().beginTransaction().replace(R.id.theFragmentFrame, fragment).addToBackStack("MarketPlaceFragment").commit();
}
}
public boolean checkPermissionREAD_EXTERNAL_STORAGE(
final Context context) {
int currentAPIVersion = Build.VERSION.SDK_INT;
if (currentAPIVersion >= android.os.Build.VERSION_CODES.M) {
if (ContextCompat.checkSelfPermission(context,
Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
if (ActivityCompat.shouldShowRequestPermissionRationale(
(Activity) context,
Manifest.permission.READ_EXTERNAL_STORAGE)) {
showDialog("External storage", context,
Manifest.permission.READ_EXTERNAL_STORAGE);
} else {
ActivityCompat
.requestPermissions(
(Activity) context,
new String[] { Manifest.permission.READ_EXTERNAL_STORAGE },
MY_PERMISSIONS_REQUEST_READ_EXTERNAL_STORAGE);
}
return false;
} else {
return true;
}
} else {
return true;
}
}
public void showDialog(final String msg, final Context context,
final String permission) {
AlertDialog.Builder alertBuilder = new AlertDialog.Builder(context);
alertBuilder.setCancelable(true);
alertBuilder.setTitle("Permission necessary");
alertBuilder.setMessage(msg + " permission is necessary");
alertBuilder.setPositiveButton(android.R.string.yes,
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
ActivityCompat.requestPermissions((Activity) context,
new String[] { permission },
MY_PERMISSIONS_REQUEST_READ_EXTERNAL_STORAGE);
}
});
AlertDialog alert = alertBuilder.create();
alert.show();
}
#Override
public void onRequestPermissionsResult(int requestCode,
String[] permissions, int[] grantResults) {
switch (requestCode) {
case MY_PERMISSIONS_REQUEST_READ_EXTERNAL_STORAGE:
if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
// do your stuff
} else {
Toast.makeText(MainActivity.this, "GET_ACCOUNTS Denied",
Toast.LENGTH_SHORT).show();
}
break;
default:
super.onRequestPermissionsResult(requestCode, permissions,
grantResults);
}
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.main_menu, menu);
return true;
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
int id = item.getItemId();
switch (id) {
case R.id.logoff:
FirebaseAuth.getInstance().signOut();
Intent intent1 = new Intent(this, LoginActivity.class);
startActivity(intent1);
return true;
case R.id.profile:
Intent intent2 = new Intent(this, MyProfileActivity.class);
startActivity(intent2);
return true;
}
return super.onOptionsItemSelected(item);
}
#Override
public void onListFragmentInteraction(DatabaseReference reference) {
Intent intent = new Intent(this, ProductDetailViewActivity.class);
intent.putExtra("Product reference", reference.toString());
startActivity(intent);
}
}
My manifest is:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.arjufy">
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-feature
android:name="android.hardware.camera"
android:required="true" />
<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">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>``
<activity android:name=".LoginActivity" />
<activity android:name=".SplashScr
eenActivity" />
<activity android:name=".AddProductActivity" />
<activity android:name=".SignUpActivity" />
<activity android:name=".MyProfileActivity" />
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="com.example.arjufy"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="#xml/path_files" />
</provider>
<activity android:name=".ProductDetailViewActivity" />
</application>
</manifest>
and my gradle is:
apply plugin: 'com.android.application'
apply plugin: 'com.google.gms.google-services'
android {
compileSdkVersion 28
defaultConfig {
applicationId "com.example.arjufy"
minSdkVersion 23
targetSdkVersion 28
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'com.android.support:appcompat-v7:28.0.0'
implementation 'com.android.support.constraint:constraint-layout:1.1.3'
implementation 'com.google.firebase:firebase-auth:16.1.0'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.2'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
implementation 'com.android.support:support-v4:28.0.0'
implementation 'com.android.support:recyclerview-v7:28.0.0'
implementation 'com.android.support:design:28.0.0'
implementation 'com.google.android.gms:play-services-auth:16.0.1'
implementation 'com.google.firebase:firebase-storage:16.1.0'
implementation 'com.google.firebase:firebase-database:16.1.0'
implementation 'com.firebaseui:firebase-ui:0.5.3'
implementation 'me.relex:circleindicator:2.1.0#aar'
implementation 'com.getbase:floatingactionbutton:1.10.1'
implementation 'de.hdodenhof:circleimageview:2.2.0'
implementation 'com.squareup.picasso:picasso:2.71828'
implementation 'com.google.firebase:firebase-core:16.0.7'
}
Do you have any idea where it might come from?
Thanks a lot for your help.
Very much appreciated.
From Android Developer Documentation
If your app needs a dangerous permission, you must check whether you have that permission every time you perform an operation that requires that permission. Beginning with Android 6.0 (API level 23), users can revoke permissions from any app at any time, even if the app targets a lower API level.
So you need to request permission like this
#TargetApi(23)
public void enableRunTimePermisstion() {
if ((Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) &&
(getActivity().checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) !=
PackageManager.PERMISSION_GRANTED))
if (getActivity().shouldShowRequestPermissionRationale
(Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
Toast.makeText(getActivity(), "Write storage permission is need for app"
, Toast.LENGTH_LONG).show();
requestPermissions(new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, 1);
} else {
requestPermissions(new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, 1);
Toast.makeText(getActivity(), "request permission"
, Toast.LENGTH_LONG).show();
}
}
#Override
public void onRequestPermissionsResult(int requestCode, #NonNull String[] permissions, #NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode == 1) {
if (permissions[0].equals(Manifest.permission.WRITE_EXTERNAL_STORAGE) &&
grantResults[0] == PackageManager.PERMISSION_GRANTED) {
Toast.makeText(context, "Write external storage granted", Toast.LENGTH_LONG).show();
// >> here you call the method that need the permission
}
} else {
Toast.makeText(getActivity(), "Write external permission denied", Toast.LENGTH_SHORT).show();
enableRunTimePermisstion(); // >> When user request the permission we calling the previous method again.
}
}
Runtime permission has actually taken a new shape in the recent times ... like you need to use a fileprovider, provide Uri and the likes but here is a good library that i have used in the recent times. It works well and hassle free. You can check it out on codepath.
https://guides.codepath.com/android/Managing-Runtime-Permissions-with-PermissionsDispatcher
Cheers.
Im trying to send a SMS message from my own app using 'sendTextMessage' or 'sendMultipartTextMessage'. For phones higher then API 19 (KitKat) this message is then saved to the sent folder. However on my Android 8.0 Oreo Phone it is not saving to the sent items.
I have created a very basic Test Application for purposes of posting here. This App will simply check permissions and send a text when the Resume function fires for the MainActivity. Here is the code.
Manifest:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.focus4software.www.myapplicationtest">
<uses-permission android:name="android.permission.SEND_SMS" />
<uses-permission android:name="android.permission.WRITE_SMS" />
<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">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
Build.Grade
apply plugin: 'com.android.application'
android {
compileSdkVersion 28
defaultConfig {
applicationId "com.focus4software.www.myapplicationtest2"
minSdkVersion 14
targetSdkVersion 28
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'com.android.support:appcompat-v7:28.0.0-alpha3'
implementation 'com.android.support.constraint:constraint-layout:1.1.2'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.2'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
}
MainActivity:
package com.focus4software.www.myapplicationtest;
import android.Manifest;
import android.content.ContentValues;
import android.content.Context;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Build;
import android.provider.Telephony;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.telephony.SmsManager;
import android.widget.Toast;
public class MainActivity extends AppCompatActivity {
private static final int REQUEST_RESULTCODE = 1;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
#Override
public void onResume(){
super.onResume();
//Check Permissions first
if (android.os.Build.VERSION.SDK_INT >= 23) {
if (ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.SEND_SMS) != PackageManager.PERMISSION_GRANTED) {
//Permissions not found.. request them
ActivityCompat.requestPermissions(MainActivity.this, new String[]{Manifest.permission.SEND_SMS}, REQUEST_RESULTCODE);
}
else {
this.SendSMS();
}
}
else {
this.SendSMS();
}
}
#Override
public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) {
switch (requestCode) {
case REQUEST_RESULTCODE: {
if (grantResults.length == 1) {
//Make sure none of the permissions were denied
boolean somethingDenied = false;
for (int result : grantResults){
if (result != PackageManager.PERMISSION_GRANTED){
somethingDenied = true;
}
}
if (somethingDenied){
//a permission was denied
Toast.makeText(getApplicationContext(), "Failed to Send The TEST SMS, Permission was denied", Toast.LENGTH_SHORT).show();
}
else {
//turn the app on.. permissions accepted
this.SendSMS();
}
}
else {
Toast.makeText(getApplicationContext(), "Failed to Send The TEST SMS, incorrect amount of permissions returned.", Toast.LENGTH_SHORT).show();
}
return;
}
}
}
private void SendSMS (){
String phone = "[INSERT PHONE NUMBER]";
String message = "InCodeTestExtra";
//send sms
SmsManager sms = SmsManager.getDefault();
sms.sendTextMessage(phone, null, message, null, null);
//Show we got here
Toast.makeText(getApplicationContext(), "Code Executed... SMS Passed on.", Toast.LENGTH_SHORT).show();
//Save SMS
//this.SaveSMS(getApplicationContext(), phone, message);
}
private void SaveSMS(Context inContext, String inAddress, String inBody) {
try {
ContentValues values = new ContentValues();
values.put("address", inAddress);
values.put("body", inBody);
values.put("read", 1);
values.put("date", System.currentTimeMillis());
//values.put("status", delivered);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
Uri uri = Telephony.Sms.Sent.CONTENT_URI;
inContext.getApplicationContext().getContentResolver().insert(uri, values);
}
else {
inContext.getApplicationContext().getContentResolver().insert(Uri.parse("content://sms/sent"), values);
}
//notify of the save
Toast.makeText(getApplicationContext(), "SMS SAVED (Maybe)", Toast.LENGTH_SHORT).show();
} catch (Exception ex) {
//notify of the Failure
Toast.makeText(getApplicationContext(), "SMS Failed to save (" + ex.getMessage() + ")", Toast.LENGTH_SHORT).show();
}
}
}
As Mentioned this is not saving the Message to the Send folder for my Android Oreo Phone.
According the the Android documentation this is meant to happen.
Note: Beginning with Android 4.4 (API level 19), if and only if an app
is not selected as the default SMS app, the system automatically
writes messages sent using this method to the SMS Provider (the
default SMS app is always responsible for writing its sent messages to
the SMS Provider). For information about how to behave as the default
SMS app, see Telephony.
As a workaround I tried to save the message manually. At the bottom of the SendSMS function this is commented out. However running this code did not cause an exception but did not save the SMS to the send folder either. This also works fine on older phones. I am unsure if this is a related issue.
Am I missing anything here?? Can anyone help? :)
Just thought id update this thread to what i found.
It SEEMS that Mike M was correct in that this is specific to certain models. I was using the Honor 9 and another stack overflow user had similar issues with the p20 lite.. both Hwawei. I did many many tests on many many models that were not Hwawei and never managed to replicate the issue.
As mentioned in my question, using code to manually save the SMS if it is not present also failed to save the sms… Perhaps this works for other users in this situation.
The only workaround I found that worked was to show the SMS Activity and let the user send the sms themselves. Downside is it requires user input and you simply populate the SMS Activity for them to send the message.
Heres a usefull Link on how to do that:
launch sms application with an intent
For people with similar issues it could be possible to only Popup the SMS Activity Certain models with some funky code.. or detect if this issue is present by trying to save a dummy sms and seeing if it worked (remember to remove it if it did work).. and if it failed show the SMS activity ….
IMO every workaround is uncomfortably messy and i'm open to any suggestions anyone has. : )
I am following the Spotify tutorial on android sdk beta22-noconnect-2.20b to just get the groundwork on an app that I want to to write and I am having trouble getting my application to authenticate. I am sure that I have signed my app and inserted the correct fingerprint into the "My Applications" tab on spotify. My URI is correct. I am also fairly certain that I followed the tutorial 100% properly. Every time I run the program I get this error.
I/OpenGLRenderer: Initialized EGL, version 1.4
D/OpenGLRenderer: Swap behavior 1
D/com.spotify.sdk.android.authentication.LoginActivity: https://accounts.spotify.com/authorize?client_id=7c59d84c8b7f4e35b3c85a7a5289db1b&response_type=token&redirect_uri=spotifymixer%3A%2F%2Fcallback&show_dialog=true&scope=user-read-private%20streaming
D/SpotifyAuthHandler: start
D/com.spotify.sdk.android.authentication.LoginActivity: Error authenticating
D/SpotifyAuthHandler: stop
I don't know how to proceed. Could I have used an incorrect signature for the program?
My MainActivity.java:
package com.example.mammo.spotifyplayer;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.app.Activity;
import android.content.Intent;
import android.util.Log;
import android.view.View;
import com.spotify.sdk.android.authentication.AuthenticationClient;
import com.spotify.sdk.android.authentication.AuthenticationRequest;
import com.spotify.sdk.android.authentication.AuthenticationResponse;
import com.spotify.sdk.android.player.Config;
import com.spotify.sdk.android.player.ConnectionStateCallback;
import com.spotify.sdk.android.player.Error;
import com.spotify.sdk.android.player.Player;
import com.spotify.sdk.android.player.PlayerEvent;
import com.spotify.sdk.android.player.Spotify;
import com.spotify.sdk.android.player.SpotifyPlayer;
public class MainActivity extends Activity implements
SpotifyPlayer.NotificationCallback, ConnectionStateCallback
{
// TODO: Replace with your client ID
private static final String CLIENT_ID = "7c59d84c8b7f4e35b3c85a7a5289db1b";
// TODO: Replace with your redirect URI
private static final String REDIRECT_URI = "spotifymixer://callback";
private Player mPlayer;
private static final int REQUEST_CODE = 1337;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
AuthenticationRequest.Builder builder = new AuthenticationRequest.Builder(CLIENT_ID, AuthenticationResponse.Type.TOKEN, REDIRECT_URI);
builder.setScopes(new String[]{"user-read-private", "streaming"});
AuthenticationRequest request = builder.build();
AuthenticationClient.openLoginActivity(this, REQUEST_CODE, request);
}
public void tryAgain(View view){
AuthenticationRequest.Builder builder = new AuthenticationRequest.Builder(CLIENT_ID, AuthenticationResponse.Type.TOKEN, REDIRECT_URI);
builder.setScopes(new String[]{"user-read-private", "streaming"});
AuthenticationRequest request = builder.build();
AuthenticationClient.openLoginActivity(this, REQUEST_CODE, request);
}
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent intent) {
super.onActivityResult(requestCode, resultCode, intent);
if(requestCode == REQUEST_CODE){
AuthenticationResponse response = AuthenticationClient.getResponse(resultCode, intent);
if(response.getType() == AuthenticationResponse.Type.TOKEN) {
Config playerConfig = new Config(this, response.getAccessToken(), CLIENT_ID);
Spotify.getPlayer(playerConfig, this, new SpotifyPlayer.InitializationObserver() {
#Override
public void onInitialized(SpotifyPlayer spotifyPlayer) {
mPlayer = spotifyPlayer;
mPlayer.addConnectionStateCallback(MainActivity.this);
mPlayer.addNotificationCallback(MainActivity.this);
}
#Override
public void onError(Throwable throwable) {
Log.e("MainActivity", "Could not initialize player: " + throwable.getMessage());
}
});
}
}
}
#Override
protected void onDestroy() {
Spotify.destroyPlayer(this);
super.onDestroy();
}
#Override
public void onPlaybackEvent(PlayerEvent playerEvent) {
Log.d("MainActivity", "Playback event received: " + playerEvent.name());
switch (playerEvent) {
// Handle event type as necessary
default:
break;
}
}
#Override
public void onPlaybackError(Error error) {
Log.d("MainActivity", "Playback error received: " + error.name());
switch (error) {
// Handle error type as necessary
default:
break;
}
}
#Override
public void onLoggedIn() {
Log.d("MainActivity", "User logged in");
mPlayer.playUri(null, "spotify:track:2TpxZ7JUBn3uw46aR7qd6V", 0, 0);
}
#Override
public void onLoggedOut() {
Log.d("MainActivity", "User logged out");
}
#Override
public void onLoginFailed(int i) {
Log.d("MainActivity", "Login failed");
}
#Override
public void onTemporaryError() {
Log.d("MainActivity", "Temporary error occurred");
}
#Override
public void onConnectionMessage(String message) {
Log.d("MainActivity", "Received connection message: " + message);
}
}
My build.gradle (Module:app):
apply plugin: 'com.android.application'
android {
compileSdkVersion 24
buildToolsVersion "24.0.2"
defaultConfig {
applicationId "com.example.mammo.spotifyplayer"
minSdkVersion 15
targetSdkVersion 24
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
repositories {
mavenCentral()
flatDir {
dirs 'libs'
}
}
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
exclude group: 'com.android.support', module: 'support-annotations'
})
// This library handles authentication and authorization
compile 'com.spotify.sdk:spotify-auth:beta22-noconnect-2.20b#aar'
// This library handles music playback
compile 'com.spotify.sdk:spotify-player:beta22-noconnect-2.20b#aar'
compile 'com.android.support:appcompat-v7:24.2.1'
testCompile 'junit:junit:4.12'
}
And my AndroidManifest.xml:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.mammo.spotifyplayer">
<uses-permission android:name="android.permission.INTERNET"/>
<application
android:allowBackup="true"
android:icon="#mipmap/ic_launcher"
android:label="#string/app_name"
android:supportsRtl="true"
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>
<!-- Needed for LoginActivity to work -->
<activity
android:name="com.spotify.sdk.android.authentication.LoginActivity"
android:theme="#android:style/Theme.Translucent.NoTitleBar"/>
</application>
</manifest>
I want to add leak canary to a test project. I created a project and did the steps from this video: https://www.youtube.com/watch?v=2VKBjlHtKMY
When I'm trying wrongWay(), the message "Dumping Memory app will freeze" is appearing in the emulator Nexus_5X_API_23. When I'm trying rightWay(), the same message is appearing too. There is no message "Dumping Memory app will freeze" in video when author using rightWay().
I cant understand why?
Where can I find a good tutorial about Leak Canary or other Leak Memory library.
So build.gradle is:
apply plugin: 'com.android.application'
android {
compileSdkVersion 24
buildToolsVersion "24.0.1"
defaultConfig {
applicationId "com.example.vopolski.myleakcanary"
minSdkVersion 15
targetSdkVersion 24
versionCode 1
versionName "1.0"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
testCompile 'junit:junit:4.12'
compile 'com.android.support:appcompat-v7:24.2.0'
debugCompile 'com.squareup.leakcanary:leakcanary-android:1.3.1'
releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.3.1'
testCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.3.1'
}
AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.vopolski.myleakcanary">
<application
android:allowBackup="true"
android:icon="#mipmap/ic_launcher"
android:label="#string/app_name"
android:supportsRtl="true"
android:theme="#style/AppTheme"
android:name=".LeakCanaryApplication">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
LeakCanaryApplication.java
package com.example.vopolski.myleakcanary;
import android.app.Application;
import android.content.Context;
import android.os.SystemClock;
import com.squareup.leakcanary.LeakCanary;
import com.squareup.leakcanary.RefWatcher;
public class LeakCanaryApplication extends Application {
private RefWatcher refWatcher;
#Override
public void onCreate() {
super.onCreate();
refWatcher = LeakCanary.install(this);
}
public static RefWatcher getRefWatcher(Context context) {
LeakCanaryApplication application =
(LeakCanaryApplication) context.getApplicationContext();
return application.refWatcher;
}
}
MainActivity.java
package com.example.vopolski.myleakcanary;
import android.os.SystemClock;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import com.squareup.leakcanary.LeakCanary;
import com.squareup.leakcanary.RefWatcher;
import android.app.Application;
public class MainActivity extends AppCompatActivity {
private String msg;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
rightWay();
}
private void rightWay() {
new MyThread().start();
}
private class MyThread extends Thread {
#Override
public void run() {
while (true) {
SystemClock.sleep(1000);
}
}
}
private void wrongWay() {
new Thread() {
#Override
public void run() {
while (true){
SystemClock.sleep(1000);
}
}
}.start();
}
}
I think you should add the following to your Activity onDestroy method
RefWatcher refWatcher = MyApplication.getRefWatcher(this);
refWatcher.watch(this);
You can refer to following URL https://github.com/square/leakcanary/wiki/FAQ
I think you're doing the rightWay() wrong.
MyThread should be static, otherwise it's a non-static inner class, and implicitly references the outer class Activity. The thread runs forever, so the activity won't be GCed. This is a memory leak.