Java tutorial
/* * Copyright (C) 2008 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package mobi.omegacentauri.ptimer; import android.app.Activity; import android.app.AlertDialog; import android.app.ProgressDialog; import android.content.ContentValues; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.content.SharedPreferences; import android.content.res.Configuration; import android.content.res.Resources; import android.database.Cursor; import android.media.AudioManager; import android.media.MediaPlayer.OnCompletionListener; import android.media.MediaPlayer.OnSeekCompleteListener; import android.media.MediaPlayer; import android.media.RingtoneManager; import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.os.Environment; import android.os.Handler; import android.os.Message; import android.provider.Contacts.People; import android.provider.MediaStore; import android.provider.Settings; import android.text.Editable; import android.text.method.LinkMovementMethod; import android.text.SpannableString; import android.text.TextWatcher; import android.text.util.Linkify; import android.util.DisplayMetrics; import android.util.Log; import android.view.KeyEvent; import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.view.View.OnClickListener; import android.widget.AbsoluteLayout; import android.widget.Button; import android.widget.ImageButton; import android.widget.ImageView; import android.widget.TextView; import android.widget.Toast; import mobi.omegacentauri.ptimer.soundfile.CheapSoundFile; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.InputStream; import java.io.OutputStream; import java.io.PrintWriter; import java.io.RandomAccessFile; import java.net.URLEncoder; import java.util.HashMap; import java.util.Random; import org.apache.http.HttpResponse; import org.apache.http.client.ClientProtocolException; import org.apache.http.client.HttpClient; import org.apache.http.client.methods.HttpPost; import org.apache.http.entity.ByteArrayEntity; import org.apache.http.impl.client.DefaultHttpClient; import org.apache.http.params.BasicHttpParams; import org.apache.http.params.HttpConnectionParams; import org.apache.http.params.HttpParams; /** * The activity for the Ringdroid main editor window. Keeps track of * the waveform display, current horizontal offset, marker handles, * start / end text boxes, and handles all of the buttons and controls. */ public class PTimerEditActivity extends Activity implements MarkerView.MarkerListener, WaveformView.WaveformListener { private long mLoadingStartTime; private long mLoadingLastUpdateTime; private boolean mLoadingKeepGoing; private ProgressDialog mProgressDialog; private CheapSoundFile mSoundFile; private File mFile; private String mFilename; private String mDstFilename; private String mArtist; private String mAlbum; private String mGenre; private String mTitle; private int mYear; private String mExtension; private String mRecordingFilename; private int mNewFileKind; private Uri mRecordingUri; private boolean mWasGetContentIntent; private WaveformView mWaveformView; private MarkerView mStartMarker; private MarkerView mEndMarker; private TextView mStartText; private TextView mEndText; private TextView mInfo; private ImageButton mPlayButton; private ImageButton mRewindButton; private ImageButton mFfwdButton; private ImageButton mZoomInButton; private ImageButton mZoomOutButton; private ImageButton mSaveButton; private boolean mKeyDown; private String mCaption = ""; private int mWidth; private int mMaxPos; private int mStartPos; private int mEndPos; private boolean mStartVisible; private boolean mEndVisible; private int mLastDisplayedStartPos; private int mLastDisplayedEndPos; private int mOffset; private int mOffsetGoal; private int mFlingVelocity; private int mPlayStartMsec; private int mPlayStartOffset; private int mPlayEndMsec; private Handler mHandler; private boolean mIsPlaying; private MediaPlayer mPlayer; private boolean mCanSeekAccurately; private boolean mTouchDragging; private float mTouchStart; private int mTouchInitialOffset; private int mTouchInitialStartPos; private int mTouchInitialEndPos; private long mWaveformTouchStartMsec; private float mDensity; private int mMarkerLeftInset; private int mMarkerRightInset; private int mMarkerTopOffset; private int mMarkerBottomOffset; // Menu commands private static final int CMD_SAVE = 1; private static final int CMD_RESET = 2; private static final int CMD_ABOUT = 3; // Result codes private static final int REQUEST_CODE_RECORD = 1; private static final int REQUEST_CODE_CHOOSE_CONTACT = 2; /** * This is a special intent action that means "edit a sound file". */ public static final String EDIT = "mobi.omegacentauri.ptimer.action.EDIT"; /** * Preference names */ public static final String PREF_SUCCESS_COUNT = "success_count"; public static final String PREF_STATS_SERVER_CHECK = "stats_server_check"; public static final String PREF_STATS_SERVER_ALLOWED = "stats_server_allowed"; public static final String PREF_ERROR_COUNT = "error_count"; public static final String PREF_ERR_SERVER_CHECK = "err_server_check"; public static final String PREF_ERR_SERVER_ALLOWED = "err_server_allowed"; public static final String PREF_UNIQUE_ID = "unique_id"; /** * Possible codes for PREF_*_SERVER_ALLOWED */ public static final int SERVER_ALLOWED_UNKNOWN = 0; public static final int SERVER_ALLOWED_NO = 1; public static final int SERVER_ALLOWED_YES = 2; /** * Server url */ public static final String STATS_SERVER_URL = "http://ringdroid.appspot.com/add"; public static final String ERR_SERVER_URL = "http://ringdroid.appspot.com/err"; // // Public methods and protected overrides // /** Called with the activity is first created. */ @Override public void onCreate(Bundle icicle) { super.onCreate(icicle); mRecordingFilename = null; mRecordingUri = null; mPlayer = null; mIsPlaying = false; Intent intent = getIntent(); if (intent.getBooleanExtra("privacy", false)) { showServerPrompt(true); return; } // If the Ringdroid media select activity was launched via a // GET_CONTENT intent, then we shouldn't display a "saved" // message when the user saves, we should just return whatever // they create. mWasGetContentIntent = intent.getBooleanExtra("was_get_content_intent", false); mFilename = intent.getData().toString(); mSoundFile = null; mKeyDown = false; if (mFilename.equals("record")) { try { Intent recordIntent = new Intent(MediaStore.Audio.Media.RECORD_SOUND_ACTION); startActivityForResult(recordIntent, REQUEST_CODE_RECORD); } catch (Exception e) { showFinalAlert(e, R.string.record_error); } } mHandler = new Handler(); loadGui(); mHandler.postDelayed(mTimerRunnable, 100); if (!mFilename.equals("record")) { loadFromFile(); } } /** Called with the activity is finally destroyed. */ @Override protected void onDestroy() { Log.i("Ringdroid", "EditActivity OnDestroy"); if (mPlayer != null && mPlayer.isPlaying()) { mPlayer.stop(); } mPlayer = null; if (mRecordingFilename != null) { try { if (!new File(mRecordingFilename).delete()) { showFinalAlert(new Exception(), R.string.delete_tmp_error); } getContentResolver().delete(mRecordingUri, null, null); } catch (SecurityException e) { showFinalAlert(e, R.string.delete_tmp_error); } } super.onDestroy(); } /** Called with an Activity we started with an Intent returns. */ @Override protected void onActivityResult(int requestCode, int resultCode, Intent dataIntent) { if (requestCode == REQUEST_CODE_CHOOSE_CONTACT) { // The user finished saving their ringtone and they're // just applying it to a contact. When they return here, // they're done. sendStatsToServerIfAllowedAndFinish(); return; } if (requestCode != REQUEST_CODE_RECORD) { return; } if (resultCode != RESULT_OK) { finish(); return; } if (dataIntent == null) { finish(); return; } // Get the recorded file and open it, but save the uri and // filename so that we can delete them when we exit; the // recorded file is only temporary and only the edited & saved // ringtone / other sound will stick around. mRecordingUri = dataIntent.getData(); mRecordingFilename = getFilenameFromUri(mRecordingUri); mFilename = mRecordingFilename; loadFromFile(); } /** * Called when the orientation changes and/or the keyboard is shown * or hidden. We don't need to recreate the whole activity in this * case, but we do need to redo our layout somewhat. */ @Override public void onConfigurationChanged(Configuration newConfig) { final int saveZoomLevel = mWaveformView.getZoomLevel(); super.onConfigurationChanged(newConfig); loadGui(); enableZoomButtons(); mHandler.postDelayed(new Runnable() { public void run() { mStartMarker.requestFocus(); markerFocus(mStartMarker); mWaveformView.setZoomLevel(saveZoomLevel); mWaveformView.recomputeHeights(mDensity); updateDisplay(); } }, 500); } @Override public boolean onCreateOptionsMenu(Menu menu) { super.onCreateOptionsMenu(menu); MenuItem item; item = menu.add(0, CMD_SAVE, 0, R.string.menu_save); item.setIcon(R.drawable.menu_save); item = menu.add(0, CMD_RESET, 0, R.string.menu_reset); item.setIcon(R.drawable.menu_reset); item = menu.add(0, CMD_ABOUT, 0, R.string.menu_about); item.setIcon(R.drawable.menu_about); return true; } @Override public boolean onPrepareOptionsMenu(Menu menu) { super.onPrepareOptionsMenu(menu); menu.findItem(CMD_SAVE).setVisible(true); menu.findItem(CMD_RESET).setVisible(true); menu.findItem(CMD_ABOUT).setVisible(true); return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case CMD_SAVE: onSave(); return true; case CMD_RESET: resetPositions(); mOffsetGoal = 0; updateDisplay(); return true; case CMD_ABOUT: onAbout(this); return true; default: return false; } } @Override public boolean onKeyDown(int keyCode, KeyEvent event) { if (keyCode == KeyEvent.KEYCODE_SPACE) { onPlay(mStartPos); return true; } return super.onKeyDown(keyCode, event); } // // WaveformListener // /** * Every time we get a message that our waveform drew, see if we need to * animate and trigger another redraw. */ public void waveformDraw() { mWidth = mWaveformView.getMeasuredWidth(); if (mOffsetGoal != mOffset && !mKeyDown) updateDisplay(); else if (mIsPlaying) { updateDisplay(); } else if (mFlingVelocity != 0) { updateDisplay(); } } public void waveformTouchStart(float x) { mTouchDragging = true; mTouchStart = x; mTouchInitialOffset = mOffset; mFlingVelocity = 0; mWaveformTouchStartMsec = System.currentTimeMillis(); } public void waveformTouchMove(float x) { mOffset = trap((int) (mTouchInitialOffset + (mTouchStart - x))); updateDisplay(); } public void waveformTouchEnd() { mTouchDragging = false; mOffsetGoal = mOffset; long elapsedMsec = System.currentTimeMillis() - mWaveformTouchStartMsec; if (elapsedMsec < 300) { if (mIsPlaying) { int seekMsec = mWaveformView.pixelsToMillisecs((int) (mTouchStart + mOffset)); if (seekMsec >= mPlayStartMsec && seekMsec < mPlayEndMsec) { mPlayer.seekTo(seekMsec - mPlayStartOffset); } else { handlePause(); } } else { onPlay((int) (mTouchStart + mOffset)); } } } public void waveformFling(float vx) { mTouchDragging = false; mOffsetGoal = mOffset; mFlingVelocity = (int) (-vx); updateDisplay(); } // // MarkerListener // public void markerDraw() { } public void markerTouchStart(MarkerView marker, float x) { mTouchDragging = true; mTouchStart = x; mTouchInitialStartPos = mStartPos; mTouchInitialEndPos = mEndPos; } public void markerTouchMove(MarkerView marker, float x) { float delta = x - mTouchStart; if (marker == mStartMarker) { mStartPos = trap((int) (mTouchInitialStartPos + delta)); mEndPos = trap((int) (mTouchInitialEndPos + delta)); } else { mEndPos = trap((int) (mTouchInitialEndPos + delta)); if (mEndPos < mStartPos) mEndPos = mStartPos; } updateDisplay(); } public void markerTouchEnd(MarkerView marker) { mTouchDragging = false; if (marker == mStartMarker) { setOffsetGoalStart(); } else { setOffsetGoalEnd(); } } public void markerLeft(MarkerView marker, int velocity) { mKeyDown = true; if (marker == mStartMarker) { int saveStart = mStartPos; mStartPos = trap(mStartPos - velocity); mEndPos = trap(mEndPos - (saveStart - mStartPos)); setOffsetGoalStart(); } if (marker == mEndMarker) { if (mEndPos == mStartPos) { mStartPos = trap(mStartPos - velocity); mEndPos = mStartPos; } else { mEndPos = trap(mEndPos - velocity); } setOffsetGoalEnd(); } updateDisplay(); } public void markerRight(MarkerView marker, int velocity) { mKeyDown = true; if (marker == mStartMarker) { int saveStart = mStartPos; mStartPos += velocity; if (mStartPos > mMaxPos) mStartPos = mMaxPos; mEndPos += (mStartPos - saveStart); if (mEndPos > mMaxPos) mEndPos = mMaxPos; setOffsetGoalStart(); } if (marker == mEndMarker) { mEndPos += velocity; if (mEndPos > mMaxPos) mEndPos = mMaxPos; setOffsetGoalEnd(); } updateDisplay(); } public void markerEnter(MarkerView marker) { } public void markerKeyUp() { mKeyDown = false; updateDisplay(); } public void markerFocus(MarkerView marker) { mKeyDown = false; if (marker == mStartMarker) { setOffsetGoalStartNoUpdate(); } else { setOffsetGoalEndNoUpdate(); } // Delay updaing the display because if this focus was in // response to a touch event, we want to receive the touch // event too before updating the display. mHandler.postDelayed(new Runnable() { public void run() { updateDisplay(); } }, 100); } // // Static About dialog method, also called from PTimerSelectActivity // public static void onAbout(final Activity activity) { new AlertDialog.Builder(activity).setTitle(R.string.about_title).setMessage(R.string.about_text) .setPositiveButton(R.string.alert_ok_button, null).setCancelable(false).show(); } // // Internal methods // /** * Called from both onCreate and onConfigurationChanged * (if the user switched layouts) */ private void loadGui() { // Inflate our UI from its XML layout description. setContentView(R.layout.editor); DisplayMetrics metrics = new DisplayMetrics(); getWindowManager().getDefaultDisplay().getMetrics(metrics); mDensity = metrics.density; mMarkerLeftInset = (int) (46 * mDensity); mMarkerRightInset = (int) (48 * mDensity); mMarkerTopOffset = (int) (10 * mDensity); mMarkerBottomOffset = (int) (10 * mDensity); mStartText = (TextView) findViewById(R.id.starttext); mStartText.addTextChangedListener(mTextWatcher); mEndText = (TextView) findViewById(R.id.endtext); mEndText.addTextChangedListener(mTextWatcher); mPlayButton = (ImageButton) findViewById(R.id.play); mPlayButton.setOnClickListener(mPlayListener); mRewindButton = (ImageButton) findViewById(R.id.rew); mRewindButton.setOnClickListener(mRewindListener); mFfwdButton = (ImageButton) findViewById(R.id.ffwd); mFfwdButton.setOnClickListener(mFfwdListener); mZoomInButton = (ImageButton) findViewById(R.id.zoom_in); mZoomInButton.setOnClickListener(mZoomInListener); mZoomOutButton = (ImageButton) findViewById(R.id.zoom_out); mZoomOutButton.setOnClickListener(mZoomOutListener); mSaveButton = (ImageButton) findViewById(R.id.save); mSaveButton.setOnClickListener(mSaveListener); TextView markStartButton = (TextView) findViewById(R.id.mark_start); markStartButton.setOnClickListener(mMarkStartListener); TextView markEndButton = (TextView) findViewById(R.id.mark_end); markEndButton.setOnClickListener(mMarkStartListener); enableDisableButtons(); mWaveformView = (WaveformView) findViewById(R.id.waveform); mWaveformView.setListener(this); mInfo = (TextView) findViewById(R.id.info); mInfo.setText(mCaption); mMaxPos = 0; mLastDisplayedStartPos = -1; mLastDisplayedEndPos = -1; if (mSoundFile != null) { mWaveformView.setSoundFile(mSoundFile); mWaveformView.recomputeHeights(mDensity); mMaxPos = mWaveformView.maxPos(); } mStartMarker = (MarkerView) findViewById(R.id.startmarker); mStartMarker.setListener(this); mStartMarker.setAlpha(255); mStartMarker.setFocusable(true); mStartMarker.setFocusableInTouchMode(true); mStartVisible = true; mEndMarker = (MarkerView) findViewById(R.id.endmarker); mEndMarker.setListener(this); mEndMarker.setAlpha(255); mEndMarker.setFocusable(true); mEndMarker.setFocusableInTouchMode(true); mEndVisible = true; updateDisplay(); } private void loadFromFile() { mFile = new File(mFilename); mExtension = getExtensionFromFilename(mFilename); SongMetadataReader metadataReader = new SongMetadataReader(this, mFilename); mTitle = metadataReader.mTitle; mArtist = metadataReader.mArtist; mAlbum = metadataReader.mAlbum; mYear = metadataReader.mYear; mGenre = metadataReader.mGenre; String titleLabel = mTitle; if (mArtist != null && mArtist.length() > 0) { titleLabel += " - " + mArtist; } setTitle(titleLabel); mLoadingStartTime = System.currentTimeMillis(); mLoadingLastUpdateTime = System.currentTimeMillis(); mLoadingKeepGoing = true; mProgressDialog = new ProgressDialog(PTimerEditActivity.this); mProgressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL); mProgressDialog.setTitle(R.string.progress_dialog_loading); mProgressDialog.setCancelable(true); mProgressDialog.setOnCancelListener(new DialogInterface.OnCancelListener() { public void onCancel(DialogInterface dialog) { mLoadingKeepGoing = false; } }); mProgressDialog.show(); final CheapSoundFile.ProgressListener listener = new CheapSoundFile.ProgressListener() { public boolean reportProgress(double fractionComplete) { long now = System.currentTimeMillis(); if (now - mLoadingLastUpdateTime > 100) { mProgressDialog.setProgress((int) (mProgressDialog.getMax() * fractionComplete)); mLoadingLastUpdateTime = now; } return mLoadingKeepGoing; } }; // Create the MediaPlayer in a background thread mCanSeekAccurately = false; new Thread() { public void run() { mCanSeekAccurately = SeekTest.CanSeekAccurately(getPreferences(Context.MODE_PRIVATE)); System.out.println("Seek test done, creating media player."); try { MediaPlayer player = new MediaPlayer(); player.setDataSource(mFile.getAbsolutePath()); player.setAudioStreamType(AudioManager.STREAM_MUSIC); player.prepare(); mPlayer = player; } catch (final java.io.IOException e) { Runnable runnable = new Runnable() { public void run() { handleFatalError("ReadError", getResources().getText(R.string.read_error), e); } }; mHandler.post(runnable); } ; } }.start(); // Load the sound file in a background thread new Thread() { public void run() { try { mSoundFile = CheapSoundFile.create(mFile.getAbsolutePath(), listener); if (mSoundFile == null) { mProgressDialog.dismiss(); String name = mFile.getName().toLowerCase(); String[] components = name.split("\\."); String err; if (components.length < 2) { err = getResources().getString(R.string.no_extension_error); } else { err = getResources().getString(R.string.bad_extension_error) + " " + components[components.length - 1]; } final String finalErr = err; Runnable runnable = new Runnable() { public void run() { handleFatalError("UnsupportedExtension", finalErr, new Exception()); } }; mHandler.post(runnable); return; } } catch (final Exception e) { mProgressDialog.dismiss(); e.printStackTrace(); mInfo.setText(e.toString()); Runnable runnable = new Runnable() { public void run() { handleFatalError("ReadError", getResources().getText(R.string.read_error), e); } }; mHandler.post(runnable); return; } mProgressDialog.dismiss(); if (mLoadingKeepGoing) { Runnable runnable = new Runnable() { public void run() { finishOpeningSoundFile(); } }; mHandler.post(runnable); } else { PTimerEditActivity.this.finish(); } } }.start(); } private void finishOpeningSoundFile() { mWaveformView.setSoundFile(mSoundFile); mWaveformView.recomputeHeights(mDensity); mMaxPos = mWaveformView.maxPos(); mLastDisplayedStartPos = -1; mLastDisplayedEndPos = -1; mTouchDragging = false; mOffset = 0; mOffsetGoal = 0; mFlingVelocity = 0; resetPositions(); if (mEndPos > mMaxPos) mEndPos = mMaxPos; mCaption = mSoundFile.getFiletype() + ", " + mSoundFile.getSampleRate() + " Hz, " + mSoundFile.getAvgBitrateKbps() + " kbps, " + formatTime(mMaxPos) + " " + getResources().getString(R.string.time_seconds); mInfo.setText(mCaption); updateDisplay(); } private synchronized void updateDisplay() { if (mIsPlaying) { int now = mPlayer.getCurrentPosition() + mPlayStartOffset; int frames = mWaveformView.millisecsToPixels(now); mWaveformView.setPlayback(frames); setOffsetGoalNoUpdate(frames - mWidth / 2); if (now >= mPlayEndMsec) { handlePause(); } } if (!mTouchDragging) { int offsetDelta; if (mFlingVelocity != 0) { float saveVel = mFlingVelocity; offsetDelta = mFlingVelocity / 30; if (mFlingVelocity > 80) { mFlingVelocity -= 80; } else if (mFlingVelocity < -80) { mFlingVelocity += 80; } else { mFlingVelocity = 0; } mOffset += offsetDelta; if (mOffset + mWidth / 2 > mMaxPos) { mOffset = mMaxPos - mWidth / 2; mFlingVelocity = 0; } if (mOffset < 0) { mOffset = 0; mFlingVelocity = 0; } mOffsetGoal = mOffset; } else { offsetDelta = mOffsetGoal - mOffset; if (offsetDelta > 10) offsetDelta = offsetDelta / 10; else if (offsetDelta > 0) offsetDelta = 1; else if (offsetDelta < -10) offsetDelta = offsetDelta / 10; else if (offsetDelta < 0) offsetDelta = -1; else offsetDelta = 0; mOffset += offsetDelta; } } mWaveformView.setParameters(mStartPos, mEndPos, mOffset); mWaveformView.invalidate(); mStartMarker .setContentDescription(getResources().getText(R.string.start_marker) + " " + formatTime(mStartPos)); mEndMarker.setContentDescription(getResources().getText(R.string.end_marker) + " " + formatTime(mEndPos)); int startX = mStartPos - mOffset - mMarkerLeftInset; if (startX + mStartMarker.getWidth() >= 0) { if (!mStartVisible) { // Delay this to avoid flicker mHandler.postDelayed(new Runnable() { public void run() { mStartVisible = true; mStartMarker.setAlpha(255); } }, 0); } } else { if (mStartVisible) { mStartMarker.setAlpha(0); mStartVisible = false; } startX = 0; } int endX = mEndPos - mOffset - mEndMarker.getWidth() + mMarkerRightInset; if (endX + mEndMarker.getWidth() >= 0) { if (!mEndVisible) { // Delay this to avoid flicker mHandler.postDelayed(new Runnable() { public void run() { mEndVisible = true; mEndMarker.setAlpha(255); } }, 0); } } else { if (mEndVisible) { mEndMarker.setAlpha(0); mEndVisible = false; } endX = 0; } mStartMarker.setLayoutParams(new AbsoluteLayout.LayoutParams(AbsoluteLayout.LayoutParams.WRAP_CONTENT, AbsoluteLayout.LayoutParams.WRAP_CONTENT, startX, mMarkerTopOffset)); mEndMarker.setLayoutParams(new AbsoluteLayout.LayoutParams(AbsoluteLayout.LayoutParams.WRAP_CONTENT, AbsoluteLayout.LayoutParams.WRAP_CONTENT, endX, mWaveformView.getMeasuredHeight() - mEndMarker.getHeight() - mMarkerBottomOffset)); } private Runnable mTimerRunnable = new Runnable() { public void run() { // Updating an EditText is slow on Android. Make sure // we only do the update if the text has actually changed. if (mStartPos != mLastDisplayedStartPos && !mStartText.hasFocus()) { mStartText.setText(formatTime(mStartPos)); mLastDisplayedStartPos = mStartPos; } if (mEndPos != mLastDisplayedEndPos && !mEndText.hasFocus()) { mEndText.setText(formatTime(mEndPos)); mLastDisplayedEndPos = mEndPos; } mHandler.postDelayed(mTimerRunnable, 100); } }; private void enableDisableButtons() { if (mIsPlaying) { mPlayButton.setImageResource(android.R.drawable.ic_media_pause); mPlayButton.setContentDescription(getResources().getText(R.string.stop)); } else { mPlayButton.setImageResource(android.R.drawable.ic_media_play); mPlayButton.setContentDescription(getResources().getText(R.string.play)); } } private void resetPositions() { mStartPos = mWaveformView.secondsToPixels(0.0); mEndPos = mWaveformView.secondsToPixels(15.0); } private int trap(int pos) { if (pos < 0) return 0; if (pos > mMaxPos) return mMaxPos; return pos; } private void setOffsetGoalStart() { setOffsetGoal(mStartPos - mWidth / 2); } private void setOffsetGoalStartNoUpdate() { setOffsetGoalNoUpdate(mStartPos - mWidth / 2); } private void setOffsetGoalEnd() { setOffsetGoal(mEndPos - mWidth / 2); } private void setOffsetGoalEndNoUpdate() { setOffsetGoalNoUpdate(mEndPos - mWidth / 2); } private void setOffsetGoal(int offset) { setOffsetGoalNoUpdate(offset); updateDisplay(); } private void setOffsetGoalNoUpdate(int offset) { if (mTouchDragging) { return; } mOffsetGoal = offset; if (mOffsetGoal + mWidth / 2 > mMaxPos) mOffsetGoal = mMaxPos - mWidth / 2; if (mOffsetGoal < 0) mOffsetGoal = 0; } private String formatTime(int pixels) { if (mWaveformView != null && mWaveformView.isInitialized()) { return formatDecimal(mWaveformView.pixelsToSeconds(pixels)); } else { return ""; } } private String formatDecimal(double x) { int xWhole = (int) x; int xFrac = (int) (100 * (x - xWhole) + 0.5); if (xFrac >= 100) { xWhole++; //Round up xFrac -= 100; //Now we need the remainder after the round up if (xFrac < 10) { xFrac *= 10; //we need a fraction that is 2 digits long } } if (xFrac < 10) return xWhole + ".0" + xFrac; else return xWhole + "." + xFrac; } private synchronized void handlePause() { if (mPlayer != null && mPlayer.isPlaying()) { mPlayer.pause(); } mWaveformView.setPlayback(-1); mIsPlaying = false; enableDisableButtons(); } private synchronized void onPlay(int startPosition) { if (mIsPlaying) { handlePause(); return; } if (mPlayer == null) { // Not initialized yet return; } try { mPlayStartMsec = mWaveformView.pixelsToMillisecs(startPosition); if (startPosition < mStartPos) { mPlayEndMsec = mWaveformView.pixelsToMillisecs(mStartPos); } else if (startPosition > mEndPos) { mPlayEndMsec = mWaveformView.pixelsToMillisecs(mMaxPos); } else { mPlayEndMsec = mWaveformView.pixelsToMillisecs(mEndPos); } mPlayStartOffset = 0; int startFrame = mWaveformView.secondsToFrames(mPlayStartMsec * 0.001); int endFrame = mWaveformView.secondsToFrames(mPlayEndMsec * 0.001); int startByte = mSoundFile.getSeekableFrameOffset(startFrame); int endByte = mSoundFile.getSeekableFrameOffset(endFrame); if (mCanSeekAccurately && startByte >= 0 && endByte >= 0) { try { mPlayer.reset(); mPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC); FileInputStream subsetInputStream = new FileInputStream(mFile.getAbsolutePath()); mPlayer.setDataSource(subsetInputStream.getFD(), startByte, endByte - startByte); mPlayer.prepare(); mPlayStartOffset = mPlayStartMsec; } catch (Exception e) { System.out.println("Exception trying to play file subset"); mPlayer.reset(); mPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC); mPlayer.setDataSource(mFile.getAbsolutePath()); mPlayer.prepare(); mPlayStartOffset = 0; } } mPlayer.setOnCompletionListener(new OnCompletionListener() { public synchronized void onCompletion(MediaPlayer arg0) { handlePause(); } }); mIsPlaying = true; if (mPlayStartOffset == 0) { mPlayer.seekTo(mPlayStartMsec); } mPlayer.start(); updateDisplay(); enableDisableButtons(); } catch (Exception e) { showFinalAlert(e, R.string.play_error); return; } } /** * Show a "final" alert dialog that will exit the activity * after the user clicks on the OK button. If an exception * is passed, it's assumed to be an error condition, and the * dialog is presented as an error, and the stack trace is * logged. If there's no exception, it's a success message. */ private void showFinalAlert(Exception e, CharSequence message) { CharSequence title; if (e != null) { Log.e("Ringdroid", "Error: " + message); Log.e("Ringdroid", getStackTrace(e)); title = getResources().getText(R.string.alert_title_failure); setResult(RESULT_CANCELED, new Intent()); } else { Log.i("Ringdroid", "Success: " + message); title = getResources().getText(R.string.alert_title_success); } new AlertDialog.Builder(PTimerEditActivity.this).setTitle(title).setMessage(message) .setPositiveButton(R.string.alert_ok_button, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int whichButton) { finish(); } }).setCancelable(false).show(); } private void showFinalAlert(Exception e, int messageResourceId) { showFinalAlert(e, getResources().getText(messageResourceId)); } private String makeRingtoneFilename(CharSequence title, String extension) { String parentdir; switch (mNewFileKind) { default: case FileSaveDialog.FILE_KIND_MUSIC: parentdir = "/sdcard/media/audio/music"; break; case FileSaveDialog.FILE_KIND_ALARM: parentdir = "/sdcard/media/audio/alarms"; break; case FileSaveDialog.FILE_KIND_NOTIFICATION: parentdir = "/sdcard/media/audio/notifications"; break; case FileSaveDialog.FILE_KIND_RINGTONE: parentdir = "/sdcard/media/audio/ringtones"; break; } // Create the parent directory File parentDirFile = new File(parentdir); parentDirFile.mkdirs(); // If we can't write to that special path, try just writing // directly to the sdcard if (!parentDirFile.isDirectory()) { parentdir = "/sdcard"; } // Turn the title into a filename String filename = ""; for (int i = 0; i < title.length(); i++) { if (Character.isLetterOrDigit(title.charAt(i))) { filename += title.charAt(i); } } // Try to make the filename unique String path = null; for (int i = 0; i < 100; i++) { String testPath; if (i > 0) testPath = parentdir + "/" + filename + i + extension; else testPath = parentdir + "/" + filename + extension; try { RandomAccessFile f = new RandomAccessFile(new File(testPath), "r"); } catch (Exception e) { // Good, the file didn't exist path = testPath; break; } } return path; } private void saveRingtone(final CharSequence title) { final String outPath = makeRingtoneFilename(title, mExtension); if (outPath == null) { showFinalAlert(new Exception(), R.string.no_unique_filename); return; } mDstFilename = outPath; double startTime = mWaveformView.pixelsToSeconds(mStartPos); double endTime = mWaveformView.pixelsToSeconds(mEndPos); final int startFrame = mWaveformView.secondsToFrames(startTime); final int endFrame = mWaveformView.secondsToFrames(endTime); final int duration = (int) (endTime - startTime + 0.5); // Create an indeterminate progress dialog mProgressDialog = new ProgressDialog(this); mProgressDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER); mProgressDialog.setTitle(R.string.progress_dialog_saving); mProgressDialog.setIndeterminate(true); mProgressDialog.setCancelable(false); mProgressDialog.show(); // Save the sound file in a background thread new Thread() { public void run() { final File outFile = new File(outPath); try { // Write the new file mSoundFile.WriteFile(outFile, startFrame, endFrame - startFrame); // Try to load the new file to make sure it worked final CheapSoundFile.ProgressListener listener = new CheapSoundFile.ProgressListener() { public boolean reportProgress(double frac) { // Do nothing - we're not going to try to // estimate when reloading a saved sound // since it's usually fast, but hard to // estimate anyway. return true; // Keep going } }; CheapSoundFile.create(outPath, listener); } catch (Exception e) { mProgressDialog.dismiss(); CharSequence errorMessage; if (e.getMessage().equals("No space left on device")) { errorMessage = getResources().getText(R.string.no_space_error); e = null; } else { errorMessage = getResources().getText(R.string.write_error); } final CharSequence finalErrorMessage = errorMessage; final Exception finalException = e; Runnable runnable = new Runnable() { public void run() { handleFatalError("WriteError", finalErrorMessage, finalException); } }; mHandler.post(runnable); return; } mProgressDialog.dismiss(); Runnable runnable = new Runnable() { public void run() { afterSavingRingtone(title, outPath, outFile, duration); } }; mHandler.post(runnable); } }.start(); } private void afterSavingRingtone(CharSequence title, String outPath, File outFile, int duration) { long length = outFile.length(); if (length <= 512) { outFile.delete(); new AlertDialog.Builder(this).setTitle(R.string.alert_title_failure) .setMessage(R.string.too_small_error).setPositiveButton(R.string.alert_ok_button, null) .setCancelable(false).show(); return; } // Create the database record, pointing to the existing file path long fileSize = outFile.length(); String mimeType = "audio/mpeg"; String artist = "" + getResources().getText(R.string.artist_name); ContentValues values = new ContentValues(); values.put(MediaStore.MediaColumns.DATA, outPath); values.put(MediaStore.MediaColumns.TITLE, title.toString()); values.put(MediaStore.MediaColumns.SIZE, fileSize); values.put(MediaStore.MediaColumns.MIME_TYPE, mimeType); values.put(MediaStore.Audio.Media.ARTIST, artist); values.put(MediaStore.Audio.Media.DURATION, duration); values.put(MediaStore.Audio.Media.IS_RINGTONE, mNewFileKind == FileSaveDialog.FILE_KIND_RINGTONE); values.put(MediaStore.Audio.Media.IS_NOTIFICATION, mNewFileKind == FileSaveDialog.FILE_KIND_NOTIFICATION); values.put(MediaStore.Audio.Media.IS_ALARM, mNewFileKind == FileSaveDialog.FILE_KIND_ALARM); values.put(MediaStore.Audio.Media.IS_MUSIC, mNewFileKind == FileSaveDialog.FILE_KIND_MUSIC); // Insert it into the database Uri uri = MediaStore.Audio.Media.getContentUriForPath(outPath); final Uri newUri = getContentResolver().insert(uri, values); setResult(RESULT_OK, new Intent().setData(newUri)); // Update a preference that counts how many times we've // successfully saved a ringtone or other audio SharedPreferences prefs = getPreferences(Context.MODE_PRIVATE); int successCount = prefs.getInt(PREF_SUCCESS_COUNT, 0); SharedPreferences.Editor prefsEditor = prefs.edit(); prefsEditor.putInt(PREF_SUCCESS_COUNT, successCount + 1); prefsEditor.commit(); // If Ringdroid was launched to get content, just return if (mWasGetContentIntent) { sendStatsToServerIfAllowedAndFinish(); return; } // There's nothing more to do with music or an alarm. Show a // success message and then quit. if (mNewFileKind == FileSaveDialog.FILE_KIND_MUSIC || mNewFileKind == FileSaveDialog.FILE_KIND_ALARM) { Toast.makeText(this, R.string.save_success_message, Toast.LENGTH_SHORT).show(); sendStatsToServerIfAllowedAndFinish(); return; } // If it's a notification, give the user the option of making // this their default notification. If they say no, we're finished. if (mNewFileKind == FileSaveDialog.FILE_KIND_NOTIFICATION) { new AlertDialog.Builder(PTimerEditActivity.this).setTitle(R.string.alert_title_success) .setMessage(R.string.set_default_notification) .setPositiveButton(R.string.alert_yes_button, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int whichButton) { RingtoneManager.setActualDefaultRingtoneUri(PTimerEditActivity.this, RingtoneManager.TYPE_NOTIFICATION, newUri); sendStatsToServerIfAllowedAndFinish(); } }).setNegativeButton(R.string.alert_no_button, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int whichButton) { sendStatsToServerIfAllowedAndFinish(); } }).setCancelable(false).show(); return; } // If we get here, that means the type is a ringtone. There are // three choices: make this your default ringtone, assign it to a // contact, or do nothing. final Handler handler = new Handler() { public void handleMessage(Message response) { int actionId = response.arg1; switch (actionId) { case R.id.button_make_default: RingtoneManager.setActualDefaultRingtoneUri(PTimerEditActivity.this, RingtoneManager.TYPE_RINGTONE, newUri); Toast.makeText(PTimerEditActivity.this, R.string.default_ringtone_success_message, Toast.LENGTH_SHORT).show(); sendStatsToServerIfAllowedAndFinish(); break; case R.id.button_choose_contact: chooseContactForRingtone(newUri); break; default: case R.id.button_do_nothing: sendStatsToServerIfAllowedAndFinish(); break; } } }; Message message = Message.obtain(handler); AfterSaveActionDialog dlog = new AfterSaveActionDialog(this, message); dlog.show(); } private void chooseContactForRingtone(Uri uri) { try { Intent intent = new Intent(Intent.ACTION_EDIT, uri); intent.setClassName("mobi.omegacentauri.ptimer", "mobi.omegacentauri.ptimer.ChooseContactActivity"); startActivityForResult(intent, REQUEST_CODE_CHOOSE_CONTACT); } catch (Exception e) { Log.e("Ringdroid", "Couldn't open Choose Contact window"); } } private void handleFatalError(final CharSequence errorInternalName, final CharSequence errorString, final Exception exception) { Log.i("Ringdroid", "handleFatalError"); SharedPreferences prefs = getPreferences(Context.MODE_PRIVATE); int failureCount = prefs.getInt(PREF_ERROR_COUNT, 0); final SharedPreferences.Editor prefsEditor = prefs.edit(); prefsEditor.putInt(PREF_ERROR_COUNT, failureCount + 1); prefsEditor.commit(); // Check if we already have a pref for whether or not we can // contact the server. int serverAllowed = prefs.getInt(PREF_ERR_SERVER_ALLOWED, SERVER_ALLOWED_UNKNOWN); if (serverAllowed == SERVER_ALLOWED_NO) { Log.i("Ringdroid", "ERR: SERVER_ALLOWED_NO"); // Just show a simple "write error" message showFinalAlert(exception, errorString); return; } if (serverAllowed == SERVER_ALLOWED_YES) { Log.i("Ringdroid", "SERVER_ALLOWED_YES"); new AlertDialog.Builder(PTimerEditActivity.this).setTitle(R.string.alert_title_failure) .setMessage(errorString) .setPositiveButton(R.string.alert_ok_button, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int whichButton) { sendErrToServerAndFinish(errorInternalName, exception); return; } }).setCancelable(false).show(); return; } // The number of times the user must have had a failure before // we'll ask them. Defaults to 1, and each time they click "Later" // we double and add 1. final int allowServerCheckIndex = prefs.getInt(PREF_ERR_SERVER_CHECK, 1); if (failureCount < allowServerCheckIndex) { Log.i("Ringdroid", "failureCount " + failureCount + " is less than " + allowServerCheckIndex); // Just show a simple "write error" message showFinalAlert(exception, errorString); return; } final SpannableString message = new SpannableString( errorString + ". " + getResources().getText(R.string.error_server_prompt)); Linkify.addLinks(message, Linkify.ALL); AlertDialog dialog = new AlertDialog.Builder(this).setTitle(R.string.alert_title_failure) .setMessage(message).setPositiveButton(R.string.server_yes, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int whichButton) { prefsEditor.putInt(PREF_ERR_SERVER_ALLOWED, SERVER_ALLOWED_YES); prefsEditor.commit(); sendErrToServerAndFinish(errorInternalName, exception); } }).setNeutralButton(R.string.server_later, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int whichButton) { prefsEditor.putInt(PREF_ERR_SERVER_CHECK, 1 + allowServerCheckIndex * 2); Log.i("Ringdroid", "Won't check again until " + (1 + allowServerCheckIndex * 2) + " errors."); prefsEditor.commit(); finish(); } }).setNegativeButton(R.string.server_never, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int whichButton) { prefsEditor.putInt(PREF_ERR_SERVER_ALLOWED, SERVER_ALLOWED_NO); prefsEditor.commit(); finish(); } }).setCancelable(false).show(); // Make links clicky ((TextView) dialog.findViewById(android.R.id.message)).setMovementMethod(LinkMovementMethod.getInstance()); } private void onSave() { if (mIsPlaying) { handlePause(); } final Handler handler = new Handler() { public void handleMessage(Message response) { CharSequence newTitle = (CharSequence) response.obj; mNewFileKind = response.arg1; saveRingtone(newTitle); } }; Message message = Message.obtain(handler); FileSaveDialog dlog = new FileSaveDialog(this, getResources(), mTitle, message); dlog.show(); } private void enableZoomButtons() { mZoomInButton.setEnabled(mWaveformView.canZoomIn()); mZoomOutButton.setEnabled(mWaveformView.canZoomOut()); } private OnClickListener mSaveListener = new OnClickListener() { public void onClick(View sender) { onSave(); } }; private OnClickListener mPlayListener = new OnClickListener() { public void onClick(View sender) { onPlay(mStartPos); } }; private OnClickListener mZoomInListener = new OnClickListener() { public void onClick(View sender) { mWaveformView.zoomIn(); mStartPos = mWaveformView.getStart(); mEndPos = mWaveformView.getEnd(); mMaxPos = mWaveformView.maxPos(); mOffset = mWaveformView.getOffset(); mOffsetGoal = mOffset; enableZoomButtons(); updateDisplay(); } }; private OnClickListener mZoomOutListener = new OnClickListener() { public void onClick(View sender) { mWaveformView.zoomOut(); mStartPos = mWaveformView.getStart(); mEndPos = mWaveformView.getEnd(); mMaxPos = mWaveformView.maxPos(); mOffset = mWaveformView.getOffset(); mOffsetGoal = mOffset; enableZoomButtons(); updateDisplay(); } }; private OnClickListener mRewindListener = new OnClickListener() { public void onClick(View sender) { if (mIsPlaying) { int newPos = mPlayer.getCurrentPosition() - 5000; if (newPos < mPlayStartMsec) newPos = mPlayStartMsec; mPlayer.seekTo(newPos); } else { mStartMarker.requestFocus(); markerFocus(mStartMarker); } } }; private OnClickListener mFfwdListener = new OnClickListener() { public void onClick(View sender) { if (mIsPlaying) { int newPos = 5000 + mPlayer.getCurrentPosition(); if (newPos > mPlayEndMsec) newPos = mPlayEndMsec; mPlayer.seekTo(newPos); } else { mEndMarker.requestFocus(); markerFocus(mEndMarker); } } }; private OnClickListener mMarkStartListener = new OnClickListener() { public void onClick(View sender) { if (mIsPlaying) { mStartPos = mWaveformView.millisecsToPixels(mPlayer.getCurrentPosition() + mPlayStartOffset); updateDisplay(); } } }; private OnClickListener mMarkEndListener = new OnClickListener() { public void onClick(View sender) { if (mIsPlaying) { mEndPos = mWaveformView.millisecsToPixels(mPlayer.getCurrentPosition() + mPlayStartOffset); updateDisplay(); handlePause(); } } }; private TextWatcher mTextWatcher = new TextWatcher() { public void beforeTextChanged(CharSequence s, int start, int count, int after) { } public void onTextChanged(CharSequence s, int start, int before, int count) { } public void afterTextChanged(Editable s) { if (mStartText.hasFocus()) { try { mStartPos = mWaveformView.secondsToPixels(Double.parseDouble(mStartText.getText().toString())); updateDisplay(); } catch (NumberFormatException e) { } } if (mEndText.hasFocus()) { try { mEndPos = mWaveformView.secondsToPixels(Double.parseDouble(mEndText.getText().toString())); updateDisplay(); } catch (NumberFormatException e) { } } } }; private String getStackTrace(Exception e) { ByteArrayOutputStream stream = new ByteArrayOutputStream(); PrintWriter writer = new PrintWriter(stream, true); e.printStackTrace(writer); return stream.toString(); } /** * Return extension including dot, like ".mp3" */ private String getExtensionFromFilename(String filename) { return filename.substring(filename.lastIndexOf('.'), filename.length()); } private String getFilenameFromUri(Uri uri) { Cursor c = managedQuery(uri, null, "", null, null); if (c.getCount() == 0) { return null; } c.moveToFirst(); int dataIndex = c.getColumnIndexOrThrow(MediaStore.Audio.Media.DATA); return c.getString(dataIndex); } private void sendStatsToServerIfAllowedAndFinish() { Log.i("Ringdroid", "sendStatsToServerIfAllowedAndFinish"); final SharedPreferences prefs = getPreferences(Context.MODE_PRIVATE); // Check if we already have a pref for whether or not we can // contact the server. int serverAllowed = prefs.getInt(PREF_STATS_SERVER_ALLOWED, SERVER_ALLOWED_UNKNOWN); if (serverAllowed == SERVER_ALLOWED_NO) { Log.i("Ringdroid", "SERVER_ALLOWED_NO"); finish(); return; } if (serverAllowed == SERVER_ALLOWED_YES) { Log.i("Ringdroid", "SERVER_ALLOWED_YES"); sendStatsToServerAndFinish(); return; } // Number of times the user has successfully saved a sound. int successCount = prefs.getInt(PREF_SUCCESS_COUNT, 0); // The number of times the user must have successfully saved // a sound before we'll ask them. Defaults to 2, and doubles // each time they click "Later". final int allowServerCheckIndex = prefs.getInt(PREF_STATS_SERVER_CHECK, 2); if (successCount < allowServerCheckIndex) { Log.i("Ringdroid", "successCount " + successCount + " is less than " + allowServerCheckIndex); finish(); return; } showServerPrompt(false); } void showServerPrompt(final boolean userInitiated) { final SharedPreferences prefs = getPreferences(Context.MODE_PRIVATE); final SpannableString message = new SpannableString(getResources().getText(R.string.server_prompt)); Linkify.addLinks(message, Linkify.ALL); final AlertDialog dialog = new AlertDialog.Builder(PTimerEditActivity.this).setTitle(R.string.server_title) .setMessage(message).setPositiveButton(R.string.server_yes, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int whichButton) { SharedPreferences.Editor prefsEditor = prefs.edit(); prefsEditor.putInt(PREF_STATS_SERVER_ALLOWED, SERVER_ALLOWED_YES); prefsEditor.commit(); if (userInitiated) { finish(); } else { sendStatsToServerAndFinish(); } } }).setNeutralButton(R.string.server_later, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int whichButton) { int allowServerCheckIndex = prefs.getInt(PREF_STATS_SERVER_CHECK, 2); int successCount = prefs.getInt(PREF_SUCCESS_COUNT, 0); SharedPreferences.Editor prefsEditor = prefs.edit(); if (userInitiated) { prefsEditor.putInt(PREF_STATS_SERVER_CHECK, successCount + 2); } else { prefsEditor.putInt(PREF_STATS_SERVER_CHECK, allowServerCheckIndex * 2); } prefsEditor.commit(); finish(); } }).setNegativeButton(R.string.server_never, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int whichButton) { SharedPreferences.Editor prefsEditor = prefs.edit(); prefsEditor.putInt(PREF_STATS_SERVER_ALLOWED, SERVER_ALLOWED_NO); if (userInitiated) { // If the user initiated, err on the safe side and disable // sending crash reports too. There's no way to turn them // back on now aside from clearing data from this app, but // it doesn't matter, we don't need error reports from every // user ever. prefsEditor.putInt(PREF_ERR_SERVER_ALLOWED, SERVER_ALLOWED_NO); } prefsEditor.commit(); finish(); } }).setCancelable(false).show(); // Make links clicky ((TextView) dialog.findViewById(android.R.id.message)).setMovementMethod(LinkMovementMethod.getInstance()); } void sendStatsToServerAndFinish() { Log.i("Ringdroid", "sendStatsToServerAndFinish"); new Thread() { public void run() { sendToServer(STATS_SERVER_URL, null, null); } }.start(); Log.i("Ringdroid", "sendStatsToServerAndFinish calling finish"); finish(); } void sendErrToServerAndFinish(final CharSequence errType, final Exception exception) { Log.i("Ringdroid", "sendErrToServerAndFinish"); new Thread() { public void run() { sendToServer(ERR_SERVER_URL, errType, exception); } }.start(); Log.i("Ringdroid", "sendErrToServerAndFinish calling finish"); finish(); } /** * Nothing nefarious about this; the purpose is just to * uniquely identify each user so we don't double-count the same * ringtone - without actually identifying the actual user. */ long getUniqueId() { SharedPreferences prefs = getPreferences(Context.MODE_PRIVATE); long uniqueId = prefs.getLong(PREF_UNIQUE_ID, 0); if (uniqueId == 0) { uniqueId = new Random().nextLong(); SharedPreferences.Editor prefsEditor = prefs.edit(); prefsEditor.putLong(PREF_UNIQUE_ID, uniqueId); prefsEditor.commit(); } return uniqueId; } /** * If the exception is not null, will send the stack trace. */ void sendToServer(String serverUrl, CharSequence errType, Exception exception) { if (mTitle == null) return; Log.i("Ringdroid", "sendStatsToServer"); boolean isSuccess = (exception == null); StringBuilder postMessage = new StringBuilder(); String ringdroidVersion = "unknown"; try { ringdroidVersion = getPackageManager().getPackageInfo(getPackageName(), -1).versionName; } catch (android.content.pm.PackageManager.NameNotFoundException e) { } postMessage.append("ringdroid_version="); postMessage.append(URLEncoder.encode(ringdroidVersion)); postMessage.append("&android_version="); postMessage.append(URLEncoder.encode(Build.VERSION.RELEASE)); postMessage.append("&unique_id="); postMessage.append(getUniqueId()); postMessage.append("&accurate_seek="); postMessage.append(mCanSeekAccurately); if (isSuccess) { postMessage.append("&title="); postMessage.append(URLEncoder.encode(mTitle)); if (mArtist != null) { postMessage.append("&artist="); postMessage.append(URLEncoder.encode(mArtist)); } if (mAlbum != null) { postMessage.append("&album="); postMessage.append(URLEncoder.encode(mAlbum)); } if (mGenre != null) { postMessage.append("&genre="); postMessage.append(URLEncoder.encode(mGenre)); } postMessage.append("&year="); postMessage.append(mYear); postMessage.append("&filename="); postMessage.append(URLEncoder.encode(mFilename)); // The user's real location is not actually sent, this is just // vestigial code from an old experiment. double latitude = 0.0; double longitude = 0.0; postMessage.append("&user_lat="); postMessage.append(URLEncoder.encode("" + latitude)); postMessage.append("&user_lon="); postMessage.append(URLEncoder.encode("" + longitude)); SharedPreferences prefs = getPreferences(Context.MODE_PRIVATE); int successCount = prefs.getInt(PREF_SUCCESS_COUNT, 0); postMessage.append("&success_count="); postMessage.append(URLEncoder.encode("" + successCount)); postMessage.append("&bitrate="); postMessage.append(URLEncoder.encode("" + mSoundFile.getAvgBitrateKbps())); postMessage.append("&channels="); postMessage.append(URLEncoder.encode("" + mSoundFile.getChannels())); String md5; try { md5 = mSoundFile.computeMd5OfFirst10Frames(); } catch (Exception e) { md5 = ""; } postMessage.append("&md5="); postMessage.append(URLEncoder.encode(md5)); } else { // Error case postMessage.append("&err_type="); postMessage.append(errType); postMessage.append("&err_str="); postMessage.append(URLEncoder.encode(getStackTrace(exception))); postMessage.append("&src_filename="); postMessage.append(URLEncoder.encode(mFilename)); if (mDstFilename != null) { postMessage.append("&dst_filename="); postMessage.append(URLEncoder.encode(mDstFilename)); } } if (mSoundFile != null) { double framesToSecs = 0.0; double sampleRate = mSoundFile.getSampleRate(); if (sampleRate > 0.0) { framesToSecs = mSoundFile.getSamplesPerFrame() * 1.0 / sampleRate; } double songLen = framesToSecs * mSoundFile.getNumFrames(); postMessage.append("&songlen="); postMessage.append(URLEncoder.encode("" + songLen)); postMessage.append("&sound_type="); postMessage.append(URLEncoder.encode(mSoundFile.getFiletype())); double clipStart = mStartPos * framesToSecs; double clipLen = (mEndPos - mStartPos) * framesToSecs; postMessage.append("&clip_start="); postMessage.append(URLEncoder.encode("" + clipStart)); postMessage.append("&clip_len="); postMessage.append(URLEncoder.encode("" + clipLen)); } String fileKindName = FileSaveDialog.KindToName(mNewFileKind); postMessage.append("&clip_kind="); postMessage.append(URLEncoder.encode(fileKindName)); Log.i("Ringdroid", postMessage.toString()); try { int TIMEOUT_MILLISEC = 10000; // = 10 seconds HttpParams httpParams = new BasicHttpParams(); HttpConnectionParams.setConnectionTimeout(httpParams, TIMEOUT_MILLISEC); HttpConnectionParams.setSoTimeout(httpParams, TIMEOUT_MILLISEC); HttpClient client = new DefaultHttpClient(httpParams); HttpPost request = new HttpPost(serverUrl); request.setEntity(new ByteArrayEntity(postMessage.toString().getBytes("UTF8"))); Log.i("Ringdroid", "Executing request"); HttpResponse response = client.execute(request); Log.i("Ringdroid", "Response: " + response.toString()); } catch (Exception e) { e.printStackTrace(); } } }