Android - webview opens extra activity - java

I have a android app, in which the main activity calls a function from a external library, which then opens a webview if neccesary.
Opening the webview is no problem. My problem start when people/users close the webview. It appears that the webview (I think, I am not sure though) has opened an extra activity or something on top of the original app that called the external library.
Now users have to close 2 windows, before they can continue in the original app.
Does anyone have experience with this, or knows what's happening here?
MainActivity
public class MainActivity extends Activity
{
private final String appKey = "Android.Lib.Test";
#Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
externalLib.Initialize(this, getIntent(), appKey);
}
#Override
public boolean onCreateOptionsMenu(Menu menu)
{
getMenuInflater().inflate(R.menu.activity_main, menu);
return true;
}
}
External Lib
public class externalLib
{
private static final String TAG = "externalLib";
private static int messageId;
public static void Initialize(Context context, Intent intent, String newAppKey)
{
Log.d(TAG, "initializing");
APPKEY = newAppKey;
if(intent.hasExtra("url"))
{
if(intent.getExtras().getString("url") != null)
{
Intent webViewIntent = new Intent(context, externalLibWebView.class);
webViewIntent.putExtra("url", intent.getExtras().getString("url"));
context.startActivity(webViewIntent);
}
}
if(intent.hasExtra("messageId"))
{
messageId = intent.getExtras().getInt("messageId");
Log.e(TAG, "messageId: " + messageId);
}
else
{
messageId = 0;
}
}
}

I found out what the problems was. I wanted the webview to load the url, but instead, the browser was opened leaving the webview empty and thus "creating" the extra acitvity. Now the webview loads the url.

Related

How to do google sign-in from an Android WebView and after successful sign-in redirect back to the home website?

I have an android app that uses a webview to render a website that has a google log-in and a facebook log-in. The problem is that when i do the google or facebook login, it opens a mobile browser and after successful login it does not go back to the app, it stays in the browser. I want it to go to the app. It doesnot matter the login page opens in the webview itself or it opens in the browser. But after successful login it should redirect back to the home website in the app. i have tried rendering the login page in the webview itself but after logging in it stucks in a page.
I have added the INTERNET permission i the manifest
public class MainActivity extends AppCompatActivity {
WebView myWebView;
WebView myPopedWebView;
WebSettings myWebSettings;
FrameLayout mContainer;
String HOME_URL = "https://www.something.com";
String HOME_URL_PREFIX = "www.something.com";
Context mContext;
#SuppressLint("SetJavaScriptEnabled")
#Override protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
CookieManager cm = CookieManager.getInstance();
cm.setAcceptCookie(true);
myWebView = findViewById(R.id.myWebView);
myWebSettings = myWebView.getSettings();
mContainer = findViewById(R.id.frame_layout);
myWebView.loadUrl(HOME_URL);
myWebView.setWebViewClient(new customWebViewClient());
myWebView.setWebChromeClient(new customWebChromeClient());
myWebSettings.setJavaScriptEnabled(true);
myWebSettings.setDomStorageEnabled(true);
myWebSettings.setJavaScriptCanOpenWindowsAutomatically(true);
myWebSettings.setAppCacheEnabled(true);
myWebSettings.setSupportMultipleWindows(false);
myWebSettings.setUserAgentString(myWebSettings.getUserAgentString().replace("; wv", ""));
mContext = this.getApplicationContext();
}
public class customWebViewClient extends WebViewClient {
#Override public boolean shouldOverrideUrlLoading(WebView view, String url) {
String host = Uri.parse(url).getHost();
Log.d("Loading url", url);
if (host.equals(HOME_URL_PREFIX)) {
if (myPopedWebView != null) {
myPopedWebView.setVisibility(View.GONE);
mContainer.removeView(myPopedWebView);
myPopedWebView = null;
}
return false;
}
if (host.equals("www.facebook.com") || host.equals("www.accounts.google.com")) {
return false;
}
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
startActivity(intent);
return false;
}
#Override public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
Log.d("onReceivedSslError", "onReceivedSslError");
}
}
public class customWebChromeClient extends WebChromeClient {
#SuppressLint("SetJavaScriptEnabled")
#Override public boolean onCreateWindow(WebView view, boolean isDialog, boolean isUserGesture, Message resultMsg) {
myPopedWebView = new WebView(mContext);
myPopedWebView.getSettings().setDomStorageEnabled(true);
myPopedWebView.getSettings().setJavaScriptEnabled(true);
myPopedWebView.setWebChromeClient(new customWebChromeClient());
myPopedWebView.setWebViewClient(new customWebViewClient());
myPopedWebView.setLayoutParams(new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
WebView.WebViewTransport transport = (WebView.WebViewTransport)resultMsg.obj;
transport.setWebView(myPopedWebView);
resultMsg.sendToTarget();
return true;
}
#Override public void onCloseWindow(WebView window) {
Log.d("onCloseWindow", "closed");
}
}
}
You don't have any code that requests the facebook (or google) account login with login callback. Here's what you can do:
Either code the login capability(along with login callback url configuration) in the website that has your login with facebook or google buttons.
or, you can add facebook and google sign-in (auth) sdks in your android app and handle the login and callback in your app (writing java/kotlin code).
You can choose either of the two approaches, you can easily find tutorials online on how to integrate facebook sdk in web (approach 1) or android (approach 2).
Happy Coding!!

Is my activity being leaked - and is it the ActionBar that's doing it?

I have an Activity named PhotoSelectorActivity. It inherits from a BaseActivity that looks like this:
public class BaseActivity
extends ActionBarActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d(this.getClass().getSimpleName(),
"onCreate("+Integer.toHexString(System.identityHashCode(this))+")");
}
#Override
protected void onDestroy() {
super.onDestroy();
getSupportActionBar().setCustomView(null);
Log.d(this.getClass().getSimpleName(),
"onDestroy("+Integer.toHexString(System.identityHashCode(this))+")");
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
return onNavigateUp(item);
case R.id.menu_item_settings:
startActivity(new Intent(this, PreferencesActivity.class));
return true;
default:
return super.onOptionsItemSelected(item);
}
}
#TargetApi(Build.VERSION_CODES.HONEYCOMB)
protected ActionBar setupActionBar(boolean enableBackButton) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
getSupportActionBar().setDisplayHomeAsUpEnabled(enableBackButton);
}
ActionBar actionBar = getSupportActionBar();
actionBar.setCustomView(R.layout.action_bar);
actionBar.setDisplayShowTitleEnabled(false);
actionBar.setDisplayShowCustomEnabled(true);
return null;
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
}
The purpose of this BaseActivity is to provide the same menu and actionbar to each one of my activities. You'll notice the getSupportActionBar().setCustomView(null) in the onDestroy() method, that's there to try and combat the problem that I may be having.
When i get an orientation change event, i notice in DDMS that i end up with 2 instances of my activity. One of them may be leaking, but I'm not certain. Here's a screen shot from DDMS:
So the object at the top is the Activity in question: PhotoSelectorActivity. The instance shown here is the previous instance (onDestroy() has already been called on it). Yet it remains in memory even after a forced GC via DDMS.
Another bit of information is that this only seems to happen after using a dialog. That is, when the Activity is initially displayed and before the user performs and action I can do back to back orientation changes without the # of activities climbing above 1. After I've used the following dialog i seem to get the extra Activity in memory:
public class PhotoSourceDialog
extends DialogFragment
implements DialogInterface.OnClickListener {
public static interface PhotoSourceDialogListener {
void onPhotoSourceSelected(String result);
}
private PhotoSourceDialogListener listener;
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
if (!PhotoSourceDialogListener.class.isInstance(activity)) {
throw new IllegalStateException(
"Activity must implement PhotoSourceDialogListener");
}
listener = PhotoSourceDialogListener.class.cast(activity);
}
#Override
public void onDetach() {
super.onDetach();
listener = null;
}
#Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
return new AlertDialog.Builder(getActivity())
.setTitle(R.string.photo_source)
.setItems(R.array.photo_sources, this).create();
}
#Override
public void onClick(DialogInterface dialog, int which) {
String choice = getResources().getStringArray(
R.array.photo_sources)[which];
if (listener!=null) {
listener.onPhotoSourceSelected(choice);
}
}
}
and to invoke it i do this in my activity:
PhotoSourceDialog dialog = new PhotoSourceDialog();
dialog.show(getSupportFragmentManager(), PhotoSourceDialog.class.getName());
So my question is this: Should I be worried? Is this just something that is hanging around for a bit but will eventually be GCd? I would think that if there was a leak it would grow higher than 2.
I'm closing this question. Someone at google has responded with the following:
OK, in that case then it's not an AppCompat bug since the standard
Action Bar implementation is used on ICS+.
Looking at that MAT screenshot, the framework's ActionMenuItemView is
being referenced from a clipboard event which is being finalized,
hence about to be GC'd. The LayoutInflater is probably the
LayoutInflater that the Activity keeps itself (getLayoutInflater()).

Android Setting Up Splash Screen(Activity) Like Iphone Part1

I have three images with me and i want them to appear on first layout xml like a splash view so that they can be viewed only once i.e that activity will be called only once when app get's installed or if app get's a new update otherwise app should always start from the Second activity, i don't know how should i begin with this :
Can any one tell me any idea how this can be done.
To show splash for only once.
Next part of this question is here
Coding will be much appreciated.
Save a flag in the Preferences when you start up the application, after you've done the welcome screen stuff. Check for this flag before you show the welcome screen. If the flag is present (in other words, if it's not the first time), don't show it.
In your activity:
SharedPreferences mPrefs;
final String welcomeScreenShownPref = "welcomeScreenShown";
/** Called when the activity is first created. */
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
mPrefs = PreferenceManager.getDefaultSharedPreferences(this);
// second argument is the default to use if the preference can't be found
Boolean welcomeScreenShown = mPrefs.getBoolean(welcomeScreenShownPref, false);
if (!welcomeScreenShown) {
// here you can launch another activity if you like
// the code below will display a popup
String whatsNewTitle = getResources().getString(R.string.whatsNewTitle);
String whatsNewText = getResources().getString(R.string.whatsNewText);
new AlertDialog.Builder(this).setIcon(android.R.drawable.ic_dialog_alert).setTitle(whatsNewTitle).setMessage(whatsNewText).setPositiveButton(
R.string.ok, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
}
}).show();
SharedPreferences.Editor editor = mPrefs.edit();
editor.putBoolean(welcomeScreenShownPref, true);
editor.commit(); // Very important to save the preference
}
}
Try this :
public class MainActivity extends Activity {
private Thread mSplashThread;
/** Called when the activity is first created. */
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
try {
requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.splash);
final MainActivity sPlashScreen = this;
mSplashThread = new Thread() {
#Override
public void run() {
try {
synchronized (this) {
wait(4000);
}
} catch (InterruptedException ex) {
}
finish();
Intent intent = new Intent();
intent.setClass(sPlashScreen, StartNewActivity.class);// <-- Activity you want to start after Splash
startActivity(intent);
}
};
mSplashThread.start();
} catch (Exception e) {
}
}
#Override
public boolean onTouchEvent(MotionEvent evt) {
try {
if (evt.getAction() == MotionEvent.ACTION_DOWN) {
synchronized (mSplashThread) {
mSplashThread.notifyAll();
}
}
} catch (Exception e) {
}
return true;
}
}
you put an Image in splash.xml to show
to do this you have to detect the first launch of your application. To do so you can store a boolean value as #Nirav suggested.
And for the splash screen, You can consider using Fragments and ViewPager to create an activity which will only be shown for the first time

Shared preferences don't take effect until Preferences clicked

I'm writing an app for the Sony Smartwatch, using their SDK. Here's part of the main activity:
class SmartTickerActivity extends ControlExtension {
private Handler mHandler;
SmartTickerActivity(final String hostAppPackageName, final Context context, Handler handler) {
super(context, hostAppPackageName);
if (handler == null) {
throw new IllegalArgumentException("handler == null");
}
}
#Override
public void onStart() {
//do some stuff
PreferenceManager.setDefaultValues(mContext, R.xml.preference, false);
}
The problem is that the saved preferences aren't being applied on the Smartwatch when the application launches. Nor are the default preference values from XML. However, if I click on any of the app's preferences on the phone, the saved preference values are immediately applied to the Smartwatch.
Note that the main class has no onCreate() method, and that's throwing me for a loop.
Here's part of the Preference activity:
public class MyPreferenceActivity extends PreferenceActivity {
private OnSharedPreferenceChangeListener mListener = new OnSharedPreferenceChangeListener() {
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
Preference pref = findPreference(key);
if (pref instanceof ListPreference) {
ListPreference listPref = (ListPreference) pref;
pref.setSummary(listPref.getEntry().toString());
}
if (pref instanceof EditTextPreference) {
EditTextPreference editTextPref = (EditTextPreference) pref;
pref.setSummary(editTextPref.getText().toString());
}
}
};
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Load the preferences from an XML resource
addPreferencesFromResource(R.xml.preference);
setSummaries();
setTypeface(SmartTickerActivity.mainLayout);
if (previewLayout != null) setTypeface(previewLayout);
// Handle read me
Preference readMe = findPreference(getText(R.string.preference_key_read_me));
readMe.setOnPreferenceClickListener(new OnPreferenceClickListener() {
public boolean onPreferenceClick(Preference readMe) {
showDialog(DIALOG_READ_ME);
return true;
}
});
// Handle about
Preference about = findPreference(getText(R.string.preference_key_about));
about.setOnPreferenceClickListener(new OnPreferenceClickListener() {
public boolean onPreferenceClick(Preference about) {
showDialog(DIALOG_ABOUT);
return true;
}
});
// Handle preview
Preference preview = findPreference(getText(R.string.preference_key_preview_dialog));
preview.setOnPreferenceClickListener(new OnPreferenceClickListener() {
public boolean onPreferenceClick(Preference preview) {
showDialog(DIALOG_PREVIEW);
return true;
}
});
}
I'm rather inexperienced at Android development, so the problem might very well have nothing to do whatsoever with the Sony SDK. Can anyone help?
You are correct, the preferences of the official sample extensions are not loaded until the PreferenceActivity is shown for the first time. If you use correct default values when accessing the preferences, this should not be a problem.
If you would like for the preferences to be loaded when the extension is initiated the first time, you could extend the android.app.Application class, and the onCreate method.
For example:
public class MySmartWatchApplication extends Application {
#Override
public void onCreate() {
super.onCreate();
PreferenceManager.setDefaultValues(this, R.xml.app_preferences, false);
}
}

Is there a way to call a class function from another class directly without an instance of it in Java?

I have an application that I have taking a picture, and then it is supposed to send the data from the picture to another activity using an intent.
I am trying to call the intent inside the jpegCallback, but the problem is I also need to release the camera through the preview class before calling the intent. However, I can not get to the original preview object from inside the callback, so I need a way to call MainActivity.doPictureResults() from inside the callback. Or I need a way to have a listener that fires after all of the picture callbacks are done.
Here is my MainActivity class which holds an instance of Preview class in the mPreview variable. The jpegCallback is at the bottom, and I want to call the doPictureResults from inside that, or setup another callback for after that function is done.
public class MainActivity extends Activity {
private final String TAG = "MainActivity";
private Preview mPreview;
Camera mCamera;
int numberOfCameras;
int cameraCurrentlyLocked;
//The first rear facing camera
int defaultCameraId;
/**
* Constructor
* #param savedInstanceState
*/
#Override
protected void onCreate(Bundle savedInstanceState) {Log.e(TAG, "onCreate");
super.onCreate(savedInstanceState);
//Hide the window title.
requestWindowFeature(Window.FEATURE_NO_TITLE);
getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
//Create a RelativeLayout container that will hold a SurfaceView,
//and set it as the content of our activity.
this.mPreview = new Preview(this);
setContentView(this.mPreview);
//Find the total number of cameras available
this.numberOfCameras = Camera.getNumberOfCameras();
//Find the ID of the default camera
CameraInfo cameraInfo = new CameraInfo();
for(int i = 0; i < this.numberOfCameras; i++) {
Camera.getCameraInfo(i, cameraInfo);
if(cameraInfo.facing == CameraInfo.CAMERA_FACING_BACK) {
this.defaultCameraId = i;
}
}
}
#Override
protected void onResume() {Log.e(TAG, "onResume");
super.onResume();
//Open the default i.e. the first rear facing camera.
this.mCamera = Camera.open();
this.cameraCurrentlyLocked = this.defaultCameraId;
this.mPreview.setCamera(mCamera);
}
#Override
protected void onPause() {Log.e(TAG, "onPause");
super.onPause();
//Because the Camera object is a shared resource, it's very
//Important to release it when the activity is paused.
this.mPreview.releaseCamera();
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
//Inflate our menu which can gather user input for switching camera
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.camera_menu, menu);
return true;
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
//Handle item selection
switch (item.getItemId()) {
case R.id.switchCam:
//Check for availability of multiple cameras
if(this.numberOfCameras == 1) {
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setMessage(this.getString(R.string.camera_alert)).setNeutralButton("Close", null);
AlertDialog alert = builder.create();
alert.show();
return true;
}
//OK, we have multiple cameras.
//Release this camera -> cameraCurrentlyLocked
this.mPreview.releaseCamera();
//Acquire the next camera and request Preview to reconfigure parameters.
this.mCamera = Camera.open((this.cameraCurrentlyLocked + 1) % this.numberOfCameras);
this.cameraCurrentlyLocked = (this.cameraCurrentlyLocked + 1) % this.numberOfCameras;
this.mPreview.switchCamera(mCamera);
//Start the preview
this.mCamera.startPreview();
return true;
case R.id.takePicture:
this.mCamera.takePicture(null, null, jpegCallback);
return true;
default:
return super.onOptionsItemSelected(item);
}
}
public void doPictureResults(byte[] data) {
this.mPreview.releaseCamera();
//Release the camera and send the results of the image to the GetResults view
Intent resultsIntent = new Intent(MainActivity.this, ImageProcessorActivity.class);
resultsIntent.putExtra("image_data", data);
startActivity(resultsIntent);
}
/**
* Handles data for jpeg picture when the picture is taken
*/
PictureCallback jpegCallback = new PictureCallback() {
public void onPictureTaken(byte[] data, Camera mCamera) {Log.e(TAG, "jpegCallback");
String baseExternalDir = Environment.getExternalStorageDirectory().getAbsolutePath();
String fileName = String.format("Assist/%d.jpg", System.currentTimeMillis());
FileOutputStream outStream = null;
try {
//Create the directory if needed
File assistDirectory = new File(baseExternalDir + File.separator + "Assist");
assistDirectory.mkdirs();
// Write to SD Card
outStream = new FileOutputStream(baseExternalDir + File.separator + fileName);
outStream.write(data);
outStream.close();
}
catch (FileNotFoundException e) {
Log.e(TAG, "IOException caused by PictureCallback()", e);
}
catch (IOException e) {
Log.e(TAG, "IOException caused by PictureCallback()", e);
}
//This is the type of thing I WANT to do....but its not possible.
MainActivity.doPictureResults();
}
};
}
One options would be to create a PictureCallback implementation that saved the information was required in doPictureResults. It's not clear if doPictureResults will be called anywhere else; if it's not, this is clean and isolates the functionality.
Another would be to have the activity itself implement PictureCallback so you have direct access to all the member variables without having to do any work at all. This allows doPictureResults to be called from other places.
public class MainActivity extends Activity implements PictureCallback {
...
#Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
....
case R.id.takePicture:
this.mCamera.takePicture(null, null, this);
return true;
default:
return super.onOptionsItemSelected(item);
}
}
...
public void onPictureTaken(byte[] data, Camera mCamera) {
Log.d(TAG, "jpegCallback");
String baseExternalDir = Environment.getExternalStorageDirectory().getAbsolutePath();
String fileName = String.format("%d.jpg", System.currentTimeMillis());
...
doPictureResults();
}
}
The only methods you can call on a class without an instance are static methods, but I don't know if that will do what you want here.

Categories