How to prevent config class from getting freed, when APP in background - android

I made an Android APP, which has an config structure, which contains data,and services I need throughout all activities.
I now face the issue, that if my APP is in the background for a while, my APP crash, because my config structure has been deleted.
In my config structure, I also have data, I can not easily recreate at runtime.
So in my first Activity, I create the config structure.
FreightWeightConfig config = new FreightWeightConfig(getApplicationContext()); // make sure our config is up and running
And the start of my config class looks like
public FreightWeightConfig(Context appContext) {
instance = this;
mApplicationContext = appContext;
tcBlue.setCallingContext(appContext);
tcBlueConfig = Config.getInstance(); // to make sure it is available straight away
mFirebaseAuth = FirebaseAuth.getInstance();
....
}
I have a second function in the config structure, which allows me to get the instance of my config class, which I need to get access to functions and interfaces in config and services.
public static synchronized FreightWeightConfig getInstance () {
//if (FreightWeightConfig.instance == null) {
// FreightWeightConfig.instance = new FreightWeightConfig(getApplication().getApplicationContext());
//}
if (FreightWeightConfig.instance == null){
FirebaseCrash.logcat(Log.ERROR, LOG_TAG, "Fatal Error : FreightWeightConfig.getInstance()==null. Try restarting the APP");
FirebaseCrash.logcat(Log.ERROR, LOG_TAG, "Fatal Error : Killing ourself, as we have no chance to go on");
//System.exit(0); // we are in a bad state
// Toast.makeText(mApplicationContext, "Fatal Error : FreightWeightConfig.getInstance()==null. Try restarting the APP", Toast.LENGTH_SHORT).show();
}
return FreightWeightConfig.instance;
}
In every Activity, I created a variable that hold a copy the instance. This is simply, because I thought it tells the system, I still need this class, do not kill it. Which does not seem to work.
I first thought, whenever I find my config class to be dead, I can recreate it. But it is not a simple task, as I need the APP context and need to recreate my services in the background. Also I store selections made, while navigating my APP
Anyone has a good Idea, how to solve the unloading / deleting my config class?
Based on the suggestion I extended Application like this:
public class FreightWeightApp extends Application implements DialogInterface.OnCancelListener {
private String LOG_TAG = "FreightWeightApp";
private FreightWeightConfig config;
public static int GOOGLE_PLAY_SERVIE_ABBORTED = 1001;
public void onCreate() {
super.onCreate();
// We first check if the google services are present, if not, better abort!!
int result = GoogleApiAvailability.getInstance().isGooglePlayServicesAvailable(this);
switch (result) {
case SUCCESS:
Log.d(LOG_TAG, "Google Services available");
break;
case SERVICE_MISSING:
Log.e(LOG_TAG, "Google Services missing, STOP");
googleServiceNotUpToDateDialog(result);
break;
case SERVICE_VERSION_UPDATE_REQUIRED:
Log.w(LOG_TAG, "Service update required");
googleServiceNotUpToDateDialog(result);
break;
case SERVICE_DISABLED:
Log.e(LOG_TAG, "Service disabled, STOP");
googleServiceNotUpToDateDialog(result);
break;
}
config = new FreightWeightConfig(getApplicationContext()); // make sure our config is up and running
}
private void googleServiceNotUpToDateDialog(int result) {
// Try to ask the user to update or finish off
// GoogleApiAvailability gaa = GoogleApiAvailability.getInstance();
// Dialog dialog = gaa.getErrorDialog(this, result, GOOGLE_PLAY_SERVIE_ABBORTED, this); //<==== Can not call this, as I have no Activity Context
// dialog.show();
}
#Override
public void onCancel(DialogInterface dialogInterface) {
// Now I should abbort the APP, or we will crash.
}
}
But now I have issues with verifying the GoogleService.
Dialog dialog = gaa.getErrorDialog(this, result, GOOGLE_PLAY_SERVIE_ABBORTED, this);
as I have no Activity Context.

Create your own implementation of Application, then initialize your config object in the onCreate() method(in this case the getInstance() method could also initialize the object).
public class MyApplication extends Application {
#Override
public void onCreate() {
super.onCreate();
FreightWeightConfig config = FreightWeightConfig.getInstance(getApplicationContext());
}
}
Declare your implementation in your app's module manifest:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="your.package">
<application
android:name="your.package.MyApplication"
android:icon="#mipmap/ic_launcher"
android:label="#string/app_name"
android:theme="#style/AppTheme">
...
</application>
</manifest>
Now you can use your config instance in the activities just like you do, calling getInstance(); the Application onCreate will do the load work. But there is no way to "unload the resources before the app process gets killed/cached", you just have to understand how Android components' lifecycle works.

Related

Interprocess or adb communication with AndroidJUnit

I want to know, does any way exist to communicate with system during instrumentation test execution.
For example:
I have a phone with IR port on onboard & I can work with it through private SDK, also I can tune it with my application. In my Instrumentation test cases I want test app behavior based on external events which I want to configure before test separate test execution.
It's looks like
#Test
public void test() throws Exception {
setupExternalCondition(condition1_ON); // setup external transiver
assertNotNull(IR.read());
assertTrue(assertIR.write());
setupExternalCondition(condition1_OFF);
assertNotNull(IR.read());
assertFalse(IR.write());
}
It's very simple example but there is a lot of "conditions", and sdk updating frequencies to high. I can't do all of this verification manually, and can't ask "transiver&SDK team" make a mock states list for writing just a unit test for coverage. So I want somehow inject external component execution to TestRuner for receiving events(or testName before test case execution) on local machine(or CI machine) to setup external condition.
Simple solution(I think) to run a tcp server on appUnderTest and request external condition change - I am not sure does it possible, and not sure about stable connection(wifi), so may be it's possible to do over adb.
Any suggestions?
P.S: test device has root permissions.
So, find not bad but not ideal solution.
Still wait for better proposition, if not may be this answer will be helpful for someone;
For "building the bridge" between local machine and AndroidJUnitTest I add next class to tests:
class IPCServiceBridge extends BroadcastReceiver {
private static final String FILTER_ID = "IPC_SERVICE";
private static IPCServiceBridge sInstance;
private boolean mIsPermitted;
#Override
public void onReceive(Context context, Intent intent) {
if (intent.getAction().equals("ipc.service.action")) {
mIsPermitted = true;
}
}
public static IPCServiceBridge getInstance() {
if (sInstance == null) {
sInstance = new IPCServiceBridge();
IntentFilter filter = new IntentFilter();
filter.addAction("ipc.service.action");
Context context = InstrumentationRegistry.getContext();
context.registerReceiver(sInstance, filter);
}
return sInstance;
}
public void sendIpcCommand(String commandName) {
try {
int i = 30;
mIsPermitted = false;
while (i > 0) {
pub("request:" + commandName);
Thread.sleep(1000);
if (mIsPermitted) {
break;
}
i--;
}
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
if (!mIsPermitted) {
throw new RuntimeException("IPC service does not respond");
}
}
private static void pub(String msg) {
Log.e(FILTER_ID, msg);
}
}
Than I start adb logcat -s "filter_name", parse and check which condition should be applied for InsttUnit test. When conditions is ready i send back broadcast receiver with required action.
#Test
public void test2() throws Exception {
IPCServiceBridge.getInstance().sendIpcCommand("CONDITION#123");
}
Work good, but I'm not sure that it will be super stable.

Android: bindService always returning false (Expansion APK api)

I'm trying to use the APK Expansion extension from Google to download expansion files I have hosted with them. I'm also using the code from the SampleDownloadActivity to do this, albeit slightly modified to fit in my app.
My problem is that the download is never initiated. In my class that implements IDownloadClient, onStart() is called, but onServiceConnected() is not.
I have traced this down to this line in DownloaderClientMarshaller:
if( c.bindService(bindIntent, mConnection, Context.BIND_DEBUG_UNBIND) ) {
This always returns false, and therefore the service is not bound.
I'm using the calling activity within a TabHost, which has caused problems for other people. They were saying that you must not pass the TabHost context, rather that the Application context to the connect function. I've changed this by doing:
mDownloaderClientStub.connect(getApplicationContext());
instead of:
mDownloaderClientStub.connect(this);
but it doesn't help, I still get false. I'm doing all my testing on the Emulator if that makes a difference.
I'm really pulling my hair out on this one. If anyone has any ideas, I'd be extremely grateful!
In most cases, bindService() method returns false if the service was not declared in the application's Manifest file.
In my case, the problem was that I had given the wrong class object to the DownloaderClientMarshaller.CreateStub() method. I accidentally used DownloaderService.class instead of MyDownloaderService.class.
When using the downloader API, make sure to pass the correct class object that extends the base DownloaderService.
I recommend using the updated Downloader Library included in Better APK Expansion package. It has this and other issues fixed and also provides simplified API that minimizes chances to shoot yourself in the foot.
To receive the download progress, you will just have to extend the BroadcastDownloaderClient.
public class SampleDownloaderActivity extends AppCompatActivity {
private final DownloaderClient mClient = new DownloaderClient(this);
// ...
#Override
protected void onStart() {
super.onStart();
mClient.register(this);
}
#Override
protected void onStop() {
mClient.unregister(this);
super.onStop();
}
// ...
class DownloaderClient extends BroadcastDownloaderClient {
#Override
public void onDownloadStateChanged(int newState) {
if (newState == STATE_COMPLETED) {
// downloaded successfully...
} else if (newState >= 15) {
// failed
int message = Helpers.getDownloaderStringResourceIDFromState(newState);
Toast.makeText(this, message, Toast.LENGTH_SHORT).show();
}
}
#Override
public void onDownloadProgress(DownloadProgressInfo progress) {
if (progress.mOverallTotal > 0) {
// receive the download progress
// you can then display the progress in your activity
String progress = Helpers.getDownloadProgressPercent(
progress.mOverallProgress, progress.mOverallTotal);
Log.i("SampleDownloaderActivity", "downloading progress: " + progress);
}
}
}
}
Check the full documentation on the library's page.

get access to com.android.internal.telephony.Call

I need to get access to com.android.internal.telephony.Call.
doing so:
// Initialize the telephony framework
PhoneFactory.makeDefaultPhones (this);
// Get the default phone
Phone phone = PhoneFactory.getDefaultPhone ();
CallManager mCM = CallManager.getInstance ();
mCM.registerPhone (phone);
Call call = mCM.getFirstActiveBgCall();
but does not extend to initialize the framework.
Help me to initialize Call.
I need to read the state of the call like:
IDLE, ACTIVE, HOLDING, DIALING, ALERTING, INCOMING, WAITING, DISCONNECTED, DISCONNECTING.
You need to make use of PhoneStateListener
It will provide you the facility to have your application listen for different state of a phone call. You will need to put <uses-permission android:name="android.permission.READ_PHONE_STATE"/> in your manifest file
You can but there is a critical requirement: the application must be signed at system level, meaning you are the manufacturer.
Here is how you write a Service that will broadcast an intent for every change in the foreground call state.
/*
* This implementation uses the com.android.internal.telephony package: you have
* to extract the framework classes .jar file from the platform (or the
* emulator) to compile this code. Also, add the jar file to the external
* libraries in the Java Build Path/libraries of the android project. </p>
*
* The jar file must match the android version you are building the application
* for. Because this implementation is using the internal packages it cannot be
* guaranteed to operate on later versions of android.
*/
public class CallStateNotificationService extends Service {
private static final String LOG_TAG = CallStateNotificationService.class.getSimpleName();
private Handler mHandler = new Handler() {
#Override
public void handleMessage(Message msg) {
// TODO Auto-generated method stub
super.handleMessage(msg);
if (msg.what == 101) {
CallManager callManager = CallManager.getInstance();
Call.State state = callManager.getActiveFgCallState();
Intent intent = new Intent(PhoneIntents.ACTION_PRECISE_CALL_STATE);
intent.putExtra(PhoneIntents.PRECISE_CALL_STATE, state.name());
Context context = getApplicationContext();
context.sendBroadcast(intent);
}
}
};
#Override
public void onCreate() {
super.onCreate();
try {
CallManager callManager = CallManager.getInstance();
if (callManager != null) {
callManager.registerForPreciseCallStateChanged(mHandler, 101, null);
} else {
Log.w(LOG_TAG, "Can't resolve CallManager reference"); //$NON-NLS-1$
}
} catch (Exception e) {
e.printStackTrace();
}
}
#Override
public void onDestroy() {
super.onDestroy();
CallManager callManager = CallManager.getInstance();
if (callManager != null) {
callManager.unregisterForPreciseCallStateChanged(mHandler);
} else {
Log.w(LOG_TAG, "Can't resolve CallManager reference"); //$NON-NLS-1$
}
}
And here is the definition of the custom broadcasted intents.
/** Intent action and extra argument names for CallStateNotificationService */
public final class PhoneIntents {
public static final String ACTION_PRECISE_CALL_STATE = "com.myorg.myapp.CALL_STATE";
public static final String PRECISE_CALL_STATE = "precise_call_state";
}
To have this code compile and link, you of course need to either build the program as part of the android distribution itself or import the class-framework by a method explained elsewhere on the Internet.
All of this is currently in an app under production.

Android - security through inheritence

I want to extend a common security check to nearly every view of my application. To do this, I have made this class
public class ProtectedActivity extends ActivityBase {
boolean isAuthenticated = false;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Thread validationThread = new Thread()
{
#Override
public void run()
{
try
{
isAuthenticated = UserService.validateToken();
}
catch (FTNIServiceException e)
{
//eat it
}
finally
{
if (!isAuthenticated)
{
startActivity(new Intent(ProtectedActivity.this, SignInActivity.class));
finish();
}
}
}
};
validationThread.start();
}
}
The logic is simple. Validate the user against my restful api to make sure they are signed in. If they aren't, show them to the signin page.
This works great, because to add the security check, all I need to do is inherit from my ProtectedActivity.
public class MainMenuActivity extends ProtectedActivity{
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
}
The problem is, however, that I periodically receive View not attached to window manager errors. I understand why this is happening. I am starting a new intent in the parent class, and the child lives on. to attempt to alter it's view even though a new intent has started. What is a better way to handle this so that if a user is not authenticated (such as their session expires serverside), it won't error when sending the user to the sign in screen?
Don't you Thread. Use AsyncTask instead which should handle your references to windows correctly.
On a different note, I would change this to a different implementation. Why don't use the Preferences storage on the phone to store some kind token. If the token is not valid then request a new token and all the stuff you are doing currently. This way is better because you don't want to request a REST call every time.
I imagine something like this (pseudo code)
Check if credentials exist in Preference
if(valid) then do nothing
else use AsyncTask and pop up a loader screen "Waiting..."

SpeechToText and running the ACTION_CHECK_TTS_DATA intent

I've implemented the TextToSpeech integration exactly as mentioned in this blog post. After I've added it to my program it's now interfering with my other intents.
For example:
List item
User starts app
User invokes load activity
User picks a file to load, and activity returns fileanme to load in the intent
Main activity starts, and realizes it needs to load a filename so it starts doing so
The check for TTS needs to be done so I launch the ACTION_CHECK_TTS_DATA intent
This pauses the main activity again and the loading process gets interrupted
When the TTS check returns, the loading never happened.
When do I need this TTS check? Can I just do it once on application start up? It's causing my application to load slowly. I would like this load to be performed in a separate thread if possible.
Do the check once. Once the data is installed, it's very unlikely that the user will need to ever do it again. Once the data is installed, there's no way for the user to delete it, even if they wanted to.
Also, don't use the ACTION_CHECK_TTS_DATA Intent, that's awkward to use.
Instead, do the following:
Create TextToSpeech
OnInit, check isLanguageAvailable()
if it is, your app is all set.
if not, send the ACTION_INSTALL_TTS_DATA
Here's some code that initializes a TextToSpeech in the way I suggest. As a bonus, it sets the language as well.
public class DemoCreateTTS
{
private static final String TAG = "DemoCreateTTS";
private TextToSpeech tts;
public void createTextToSpeech(final Context context,
final Locale locale)
{
tts = new TextToSpeech(context, new OnInitListener()
{
#Override
public void onInit(int status)
{
if (status == TextToSpeech.SUCCESS)
{
Locale defaultOrPassedIn = locale;
if (locale == null)
{
defaultOrPassedIn = Locale.getDefault();
}
// check if language is available
switch (tts.isLanguageAvailable(defaultOrPassedIn))
{
case TextToSpeech.LANG_AVAILABLE:
case TextToSpeech.LANG_COUNTRY_AVAILABLE:
case TextToSpeech.LANG_COUNTRY_VAR_AVAILABLE:
Log.d(TAG, "SUPPORTED");
tts.setLanguage(locale);
//pass the tts back to the main
//activity for use
break;
case TextToSpeech.LANG_MISSING_DATA:
Log.d(TAG, "MISSING_DATA");
Log.d(TAG, "require data...");
Intent installIntent = new Intent();
installIntent.setAction(TextToSpeech.Engine.ACTION_INSTALL_TTS_DATA);
context.startActivity(installIntent);
break;
case TextToSpeech.LANG_NOT_SUPPORTED:
Log.d(TAG, "NOT SUPPORTED");
break;
}
}
}
});
}
}

Categories

Resources