1

The following app upload video to Dropbox when it is start, I added finish() in the end of onCreate to exit the app when its completed the uploading, but I got "Unfortunately, DBRoulette has stopped.".

In the Eclipse LogCat:

Activity com.dropbox.android.sample.DBRoulette has leaked window  com.android.internal.policy.impl.PhoneWindow$DecorView@42a7ee38 that was originally added here
android.view.WindowLeaked: Activity com.dropbox.android.sample.DBRoulette has leaked window com.android.internal.policy.impl.PhoneWindow$DecorView@42a7ee38 that was originally added here

I have Progress viewer which may cause the problem but I don't know how to solve it! What I want to do is closing the app automatically when the uploading completed.

DBRoulette.java

@SuppressLint("SimpleDateFormat")
public class DBRoulette extends Activity {
private static final String TAG = "DBRoulette";

final static private String APP_KEY = "<My APP_KEY>";
final static private String APP_SECRET = "<My APP_SECRET>";

// You don't need to change these, leave them alone.
final static private String ACCOUNT_PREFS_NAME = "prefs";
final static private String ACCESS_KEY_NAME = "ACCESS_KEY";
final static private String ACCESS_SECRET_NAME = "ACCESS_SECRET";

private static final boolean USE_OAUTH1 = false;

DropboxAPI<AndroidAuthSession> mApi;

private boolean mLoggedIn;

// Android widgets
private Button mSubmit;
private RelativeLayout mDisplay;
private Button  mGallery;

private ImageView mImage;

private final String PHOTO_DIR = "/Motion/";

@SuppressWarnings("unused")
final static private int NEW_PICTURE = 50;
private String mCameraFileName;


@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    if (savedInstanceState != null) {
        mCameraFileName = savedInstanceState.getString("mCameraFileName");          
    }

    // We create a new AuthSession so that we can use the Dropbox API.
    AndroidAuthSession session = buildSession();
    mApi = new DropboxAPI<AndroidAuthSession>(session);

    // Basic Android widgets
    setContentView(R.layout.main);

    checkAppKeySetup();

    mSubmit = (Button) findViewById(R.id.auth_button);

    mSubmit.setOnClickListener(new OnClickListener() {
        @SuppressWarnings("deprecation")
        public void onClick(View v) {
            // This logs you out if you're logged in, or vice versa
            if (mLoggedIn) {
                logOut();
            } else {
                // Start the remote authentication
                if (USE_OAUTH1) {
                    mApi.getSession().startAuthentication(DBRoulette.this);
                } else {
                    mApi.getSession().startOAuth2Authentication(
                            DBRoulette.this);


                }
            }
        }
    });

    mDisplay = (RelativeLayout) findViewById(R.id.logged_in_display);

    // This is where a photo is displayed
    mImage = (ImageView) findViewById(R.id.image_view);


    File outFile = new File("/mnt/sdcard/ipwebcam_videos/video.mov");

    mCameraFileName = outFile.toString();
    UploadPicture upload = new UploadPicture(DBRoulette.this, mApi, PHOTO_DIR,outFile);
    upload.execute();



    // Display the proper UI state if logged in or not
    setLoggedIn(mApi.getSession().isLinked());
    finish();

}

@Override
protected void onSaveInstanceState(Bundle outState) {
    outState.putString("mCameraFileName", mCameraFileName);
    super.onSaveInstanceState(outState);
}

@Override
protected void onResume() {
    super.onResume();
    AndroidAuthSession session = mApi.getSession();
    if (session.authenticationSuccessful()) {
        try {
            session.finishAuthentication();
            storeAuth(session);
            setLoggedIn(true);
        } catch (IllegalStateException e) {
            showToast("Couldn't authenticate with Dropbox:"
                    + e.getLocalizedMessage());
            Log.i(TAG, "Error authenticating", e);
        }
    }
}


private void logOut() {
    // Remove credentials from the session
    mApi.getSession().unlink();
    clearKeys();
    // Change UI state to display logged out version
    setLoggedIn(false);
}
@SuppressWarnings("deprecation")
public String getRealPathFromURI(Uri contentUri) 
{
     String[] proj = { MediaStore.Audio.Media.DATA };
     Cursor cursor = managedQuery(contentUri, proj, null, null, null);
     int column_index = cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.DATA);
     cursor.moveToFirst();
     return cursor.getString(column_index);
}

private void setLoggedIn(boolean loggedIn) {
    mLoggedIn = loggedIn;
    if (loggedIn) {
        mSubmit.setText("Logout from Dropbox");
        mDisplay.setVisibility(View.VISIBLE);
    } else {
        mSubmit.setText("Login with Dropbox");
        mDisplay.setVisibility(View.GONE);
        mImage.setImageDrawable(null);
    }
}

private void checkAppKeySetup() {
    // Check to make sure that we have a valid app key
    if (APP_KEY.startsWith("CHANGE") || APP_SECRET.startsWith("CHANGE")) {
        showToast("You must apply for an app key and secret from developers.dropbox.com, and add them to the DBRoulette ap before trying it.");
        finish();
        return;
    }

    // Check if the app has set up its manifest properly.
    Intent testIntent = new Intent(Intent.ACTION_VIEW);
    String scheme = "db-" + APP_KEY;
    String uri = scheme + "://" + AuthActivity.AUTH_VERSION + "/test";
    testIntent.setData(Uri.parse(uri));
    PackageManager pm = getPackageManager();
    if (0 == pm.queryIntentActivities(testIntent, 0).size()) {
        showToast("URL scheme in your app's "
                + "manifest is not set up correctly. You should have a "
                + "com.dropbox.client2.android.AuthActivity with the "
                + "scheme: " + scheme);
        finish();
    }
}

private void showToast(String msg) {
    Toast error = Toast.makeText(this, msg, Toast.LENGTH_LONG);
    error.show();
}

/**
 * Shows keeping the access keys returned from Trusted Authenticator in a
 * local store, rather than storing user name & password, and
 * re-authenticating each time (which is not to be done, ever).
 */
private void loadAuth(AndroidAuthSession session) {
    SharedPreferences prefs = getSharedPreferences(ACCOUNT_PREFS_NAME, 0);
    String key = prefs.getString(ACCESS_KEY_NAME, null);
    String secret = prefs.getString(ACCESS_SECRET_NAME, null);
    if (key == null || secret == null || key.length() == 0
            || secret.length() == 0)
        return;

    if (key.equals("oauth2:")) {
        // If the key is set to "oauth2:", then we can assume the token is
        // for OAuth 2.
        session.setOAuth2AccessToken(secret);
    } else {
        // Still support using old OAuth 1 tokens.
        session.setAccessTokenPair(new AccessTokenPair(key, secret));
    }
}

/**
 * Shows keeping the access keys returned from Trusted Authenticator in a
 * local store, rather than storing user name & password, and
 * re-authenticating each time (which is not to be done, ever).
 */
private void storeAuth(AndroidAuthSession session) {
    // Store the OAuth 2 access token, if there is one.
    String oauth2AccessToken = session.getOAuth2AccessToken();
    if (oauth2AccessToken != null) {
        SharedPreferences prefs = getSharedPreferences(ACCOUNT_PREFS_NAME,
                0);
        Editor edit = prefs.edit();
        edit.putString(ACCESS_KEY_NAME, "oauth2:");
        edit.putString(ACCESS_SECRET_NAME, oauth2AccessToken);
        edit.commit();
        return;
    }
    // Store the OAuth 1 access token, if there is one. This is only
    // necessary if
    // you're still using OAuth 1.
    AccessTokenPair oauth1AccessToken = session.getAccessTokenPair();
    if (oauth1AccessToken != null) {
        SharedPreferences prefs = getSharedPreferences(ACCOUNT_PREFS_NAME,
                0);
        Editor edit = prefs.edit();
        edit.putString(ACCESS_KEY_NAME, oauth1AccessToken.key);
        edit.putString(ACCESS_SECRET_NAME, oauth1AccessToken.secret);
        edit.commit();
        return;
    }
}

private void clearKeys() {
    SharedPreferences prefs = getSharedPreferences(ACCOUNT_PREFS_NAME, 0);
    Editor edit = prefs.edit();
    edit.clear();
    edit.commit();
}

private AndroidAuthSession buildSession() {
    AppKeyPair appKeyPair = new AppKeyPair(APP_KEY, APP_SECRET);

    AndroidAuthSession session = new AndroidAuthSession(appKeyPair);
    loadAuth(session);
    return session;
}
}

UploadPicture.java

public class UploadPicture extends AsyncTask<Void, Long, Boolean> {

private DropboxAPI<?> mApi;
private String mPath;
private File mFile;

private long mFileLen;
private UploadRequest mRequest;
private Context mContext;
private final ProgressDialog mDialog;

private String mErrorMsg;
private File outFiles;


public UploadPicture(Context context, DropboxAPI<?> api, String dropboxPath,
        File file) {
    // We set the context this way so we don't accidentally leak activities
    mContext = context.getApplicationContext();

    mFileLen = file.length();
    mApi = api;
    mPath = dropboxPath;
    mFile = file;
    Date dates = new Date();
    DateFormat dfs = new SimpleDateFormat("yyyyMMdd-kkmmss");

    String newPicFiles = dfs.format(dates) + ".mov";
    String outPaths = new File(Environment
            .getExternalStorageDirectory(), newPicFiles).getPath();
    outFiles = new File(outPaths);
    mDialog = new ProgressDialog(context);
    mDialog.setMax(100);
    mDialog.setMessage("Uploading " + outFiles.getName());
    mDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
    mDialog.setProgress(0);
    mDialog.setButton(ProgressDialog.BUTTON_POSITIVE, "Cancel", new OnClickListener() {
        @Override
        public void onClick(DialogInterface dialog, int which) {
            // This will cancel the putFile operation
            mRequest.abort();
        }
    });
    mDialog.show();
}

@Override
protected Boolean doInBackground(Void... params) {
    try {
        // By creating a request, we get a handle to the putFile operation,
        // so we can cancel it later if we want to
        FileInputStream fis = new FileInputStream(mFile);
        String path = mPath + outFiles.getName();
        mRequest = mApi.putFileOverwriteRequest(path, fis, mFile.length(),
                new ProgressListener() {
            @Override
            public long progressInterval() {
                // Update the progress bar every half-second or so
                return 500;
            }

            @Override
            public void onProgress(long bytes, long total) {
                publishProgress(bytes);
            }
        });

        if (mRequest != null) {
            mRequest.upload();
            return true;
        }

    } catch (DropboxUnlinkedException e) {
        // This session wasn't authenticated properly or user unlinked
        mErrorMsg = "This app wasn't authenticated properly.";
    } catch (DropboxFileSizeException e) {
        // File size too big to upload via the API
        mErrorMsg = "This file is too big to upload";
    } catch (DropboxPartialFileException e) {
        // We canceled the operation
        mErrorMsg = "Upload canceled";
    } catch (DropboxServerException e) {
        // Server-side exception.  These are examples of what could happen,
        // but we don't do anything special with them here.
        if (e.error == DropboxServerException._401_UNAUTHORIZED) {
            // Unauthorized, so we should unlink them.  You may want to
            // automatically log the user out in this case.
        } else if (e.error == DropboxServerException._403_FORBIDDEN) {
            // Not allowed to access this
        } else if (e.error == DropboxServerException._404_NOT_FOUND) {
            // path not found (or if it was the thumbnail, can't be
            // thumbnailed)
        } else if (e.error == DropboxServerException._507_INSUFFICIENT_STORAGE) {
            // user is over quota
        } else {
            // Something else
        }
        // This gets the Dropbox error, translated into the user's language
        mErrorMsg = e.body.userError;
        if (mErrorMsg == null) {
            mErrorMsg = e.body.error;
        }
    } catch (DropboxIOException e) {
        // Happens all the time, probably want to retry automatically.
        mErrorMsg = "Network error.  Try again.";
    } catch (DropboxParseException e) {
        // Probably due to Dropbox server restarting, should retry
        mErrorMsg = "Dropbox error.  Try again.";
    } catch (DropboxException e) {
        // Unknown error
        mErrorMsg = "Unknown error.  Try again.";
    } catch (FileNotFoundException e) {
        e.printStackTrace();
    }
    return false;
}

@Override
protected void onProgressUpdate(Long... progress) {
    int percent = (int)(100.0*(double)progress[0]/mFileLen + 0.5);
    mDialog.setProgress(percent);
}

@Override
protected void onPostExecute(Boolean result) {
    mDialog.dismiss();
    if (result) {
        showToast("Image successfully uploaded");
    } else {
        showToast(mErrorMsg);
    }
}

private void showToast(String msg) {
    Toast error = Toast.makeText(mContext, msg, Toast.LENGTH_LONG);
    error.show();
}
}
Majid ff
  • 249
  • 7
  • 22

1 Answers1

1

Your problem is that you are trying to update an Activity's UI after the activity has finished.

First, you kick of an AsyncTask which posts progress updates to the UI thread. Then, before the task completes, you call finish(). Any updates to the Activity's UI after finish() is called are liable to throw exceptions, cause window leak issues, etc.

If you want to have any UI behavior as you perform your AsyncTask, you do not want to finish() the Activity until it the task is completed.

To achieve this, you could include a callback in the onPostExecute which tells the activity it is OK to finish once the AsyncTask completes.

This is how I would do it:

  1. Change the signature of UploadPicture:

    final Activity callingActivity;
    public UploadPicture(final Activity callingActivity, DropboxAPI api, String dropboxPath, File file) {
        Context mContext = callingActivity.getApplicationContext();
        this.callingActivity = callingActivity;
  2. Add the finish call to onPostExecute:

    @Override
    protected void onPostExecute(Boolean result) {
        mDialog.dismiss();
        if (result) {
            showToast("Image successfully uploaded");
        } else {
            showToast(mErrorMsg);
        }
        callingActivity.finish(); //Finish activity only once you are done
    }
emerssso
  • 2,376
  • 18
  • 24
  • Bingo! You need to wait until the thread is done before calling ' finish(); '. – spartygw Sep 25 '14 at 20:13
  • Added some code snippets, let me know if you want more clarification. – emerssso Sep 25 '14 at 20:32
  • emersson, I did the change as you described above but still have the same problem, and the exception say: java.lang.RuntimeException: Unable to start activity ComponentInfo{com.dropbox.android.sample/com.dropbox.android.sample.DBRoulette}: android.view.WindowManager$BadTokenException: Unable to add window -- token null is not for an application – Majid ff Sep 25 '14 at 20:44
  • 1
    Based on [this question](http://stackoverflow.com/questions/5796611/dialog-throwing-unable-to-add-window-token-null-is-not-for-an-application-wi), it looks like this is due to the use of Application context. Try just using the `callingActivity` as your context instead. – emerssso Sep 25 '14 at 20:54
  • In this time I got : FATAL EXCEPTION: main java.lang.NullPointerException – Majid ff Sep 25 '14 at 21:11
  • When I remove onProgressUpdate, onPostExecute and showToast the app work fine and exit correctly but without showing any viewer (UI) and at the moment that ok for me. Thank you so much emerssso for trying to solve the problem. – Majid ff Sep 25 '14 at 21:17
  • What line was your NPE on? – emerssso Sep 25 '14 at 22:05