diff options
| -rw-r--r-- | cmds/stagefright/Android.mk | 3 | ||||
| -rw-r--r-- | cmds/stagefright/stagefright.cpp | 60 | ||||
| -rw-r--r-- | core/java/android/app/backup/BackupAgentHelper.java | 26 | ||||
| -rw-r--r-- | core/java/android/app/backup/BackupDataInputStream.java | 59 | ||||
| -rw-r--r-- | core/java/android/app/backup/BackupHelper.java | 52 | ||||
| -rw-r--r-- | core/java/android/server/BluetoothA2dpService.java | 6 | ||||
| -rw-r--r-- | core/java/android/webkit/WebView.java | 8 | ||||
| -rw-r--r-- | media/java/android/media/AudioService.java | 67 | ||||
| -rw-r--r-- | media/libstagefright/AudioPlayer.cpp | 2 | ||||
| -rw-r--r-- | media/libstagefright/AwesomePlayer.cpp | 16 | ||||
| -rw-r--r-- | media/libstagefright/OMXCodec.cpp | 2 | ||||
| -rw-r--r-- | packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java | 3 | ||||
| -rw-r--r-- | services/java/com/android/server/ThrottleService.java | 2 |
13 files changed, 258 insertions, 48 deletions
diff --git a/cmds/stagefright/Android.mk b/cmds/stagefright/Android.mk index 52f767e00e0e..34648b56fbcb 100644 --- a/cmds/stagefright/Android.mk +++ b/cmds/stagefright/Android.mk @@ -5,7 +5,8 @@ LOCAL_PATH:= $(call my-dir) include $(CLEAR_VARS) LOCAL_SRC_FILES:= \ - stagefright.cpp + stagefright.cpp \ + SineSource.cpp LOCAL_SHARED_LIBRARIES := \ libstagefright libmedia libutils libbinder diff --git a/cmds/stagefright/stagefright.cpp b/cmds/stagefright/stagefright.cpp index fec9e1a7f280..4405da6f81ad 100644 --- a/cmds/stagefright/stagefright.cpp +++ b/cmds/stagefright/stagefright.cpp @@ -20,9 +20,12 @@ #include <string.h> #include <unistd.h> +#include "SineSource.h" + #include <binder/IServiceManager.h> #include <binder/ProcessState.h> #include <media/IMediaPlayerService.h> +#include <media/stagefright/AudioPlayer.h> #include <media/stagefright/CachingDataSource.h> #include <media/stagefright/FileSource.h> #include <media/stagefright/HTTPDataSource.h> @@ -42,6 +45,7 @@ static long gNumRepetitions; static long gMaxNumFrames; // 0 means decode all available. static long gReproduceBug; // if not -1. static bool gPreferSoftwareCodec; +static bool gPlaybackAudio; static int64_t getNowUs() { struct timeval tv; @@ -73,7 +77,20 @@ static void playSource(OMXClient *client, const sp<MediaSource> &source) { rawSource->start(); - if (gReproduceBug >= 3 && gReproduceBug <= 5) { + if (gPlaybackAudio) { + AudioPlayer *player = new AudioPlayer(NULL); + player->setSource(rawSource); + + player->start(true /* sourceAlreadyStarted */); + + status_t finalStatus; + while (!player->reachedEOS(&finalStatus)) { + usleep(100000ll); + } + + delete player; + player = NULL; + } else if (gReproduceBug >= 3 && gReproduceBug <= 5) { int64_t durationUs; CHECK(meta->findInt64(kKeyDuration, &durationUs)); @@ -245,6 +262,7 @@ static void usage(const char *me) { fprintf(stderr, " -p(rofiles) dump decoder profiles supported\n"); fprintf(stderr, " -t(humbnail) extract video thumbnail or album art\n"); fprintf(stderr, " -s(oftware) prefer software codec\n"); + fprintf(stderr, " -o playback audio\n"); } int main(int argc, char **argv) { @@ -258,9 +276,10 @@ int main(int argc, char **argv) { gMaxNumFrames = 0; gReproduceBug = -1; gPreferSoftwareCodec = false; + gPlaybackAudio = false; int res; - while ((res = getopt(argc, argv, "han:lm:b:pts")) >= 0) { + while ((res = getopt(argc, argv, "han:lm:b:ptso")) >= 0) { switch (res) { case 'a': { @@ -314,6 +333,12 @@ int main(int argc, char **argv) { break; } + case 'o': + { + gPlaybackAudio = true; + break; + } + case '?': case 'h': default: @@ -325,6 +350,11 @@ int main(int argc, char **argv) { } } + if (gPlaybackAudio && !audioOnly) { + // This doesn't make any sense if we're decoding the video track. + gPlaybackAudio = false; + } + argc -= optind; argv += optind; @@ -456,6 +486,11 @@ int main(int argc, char **argv) { dataSource = new FileSource(filename); } + if (dataSource == NULL) { + fprintf(stderr, "Unable to create data source.\n"); + return 1; + } + bool isJPEG = false; size_t len = strlen(filename); @@ -467,10 +502,18 @@ int main(int argc, char **argv) { if (isJPEG) { mediaSource = new JPEGSource(dataSource); + } else if (!strncasecmp("sine:", filename, 5)) { + char *end; + long sampleRate = strtol(filename + 5, &end, 10); + + if (end == filename + 5) { + sampleRate = 44100; + } + mediaSource = new SineSource(sampleRate, 1); } else { sp<MediaExtractor> extractor = MediaExtractor::Create(dataSource); if (extractor == NULL) { - fprintf(stderr, "could not create data source\n"); + fprintf(stderr, "could not create extractor.\n"); return -1; } @@ -492,6 +535,17 @@ int main(int argc, char **argv) { if (!audioOnly && !strncasecmp(mime, "video/", 6)) { break; } + + meta = NULL; + } + + if (meta == NULL) { + fprintf(stderr, + "No suitable %s track found. The '-a' option will " + "target audio tracks only, the default is to target " + "video tracks only.\n", + audioOnly ? "audio" : "video"); + return -1; } int64_t thumbTimeUs; diff --git a/core/java/android/app/backup/BackupAgentHelper.java b/core/java/android/app/backup/BackupAgentHelper.java index 788b1b53d07d..6d73090f74db 100644 --- a/core/java/android/app/backup/BackupAgentHelper.java +++ b/core/java/android/app/backup/BackupAgentHelper.java @@ -21,16 +21,28 @@ import android.os.ParcelFileDescriptor; import java.io.IOException; /** - * A convenient BackupAgent wrapper class that automatically manages + * A convenient {@link BackupAgent} wrapper class that automatically manages * heterogeneous data sets within the backup data, each identified by a unique - * key prefix. An application will typically extend this class in their own - * backup agent. Then, within the agent's onBackup() and onRestore() methods, it - * will call {@link #addHelper(String, BackupHelper)} one or more times to - * specify the data sets, then invoke super.onBackup() or super.onRestore() to - * have the BackupAgentHelper implementation process the data. + * key prefix. When processing a backup or restore operation, the BackupAgentHelper + * dispatches to one or more installed {@link BackupHelper helpers} objects, each + * of which is responsible for a defined subset of the data being processed. * <p> - * STOPSHIP: document! + * An application will typically extend this class in their own + * backup agent. Then, within the agent's {@link BackupAgent#onCreate() onCreate()} + * method, it will call {@link #addHelper(String, BackupHelper)} one or more times to + * install the handlers for each kind of data it wishes to manage within its backups. + * <p> + * The Android framework currently provides two predefined {@link BackupHelper} classes: + * {@link FileBackupHelper}, which manages the backup and restore of entire files + * within an application's data directory hierarchy; and {@link SharedPreferencesBackupHelper}, + * which manages the backup and restore of an application's + * {@link android.content.SharedPreferences} data. + * <p> + * An application can also implement its own helper classes to work within the + * {@link BackupAgentHelper} framework. See the {@link BackupHelper} interface + * documentation for details. * + * @see BackupHelper * @see FileBackupHelper * @see SharedPreferencesBackupHelper */ diff --git a/core/java/android/app/backup/BackupDataInputStream.java b/core/java/android/app/backup/BackupDataInputStream.java index a7f4ba6388fb..465b3b6afef7 100644 --- a/core/java/android/app/backup/BackupDataInputStream.java +++ b/core/java/android/app/backup/BackupDataInputStream.java @@ -20,7 +20,21 @@ import java.io.InputStream; import java.io.IOException; /** - * STOPSHIP: document */ + * Used by {@link BackupHelper} classes within the {@link BackupAgentHelper} mechanism, + * this class provides an {@link java.io.InputStream}-like interface for accessing an + * entity's data during a restore operation. + * <p> + * When {@link BackupHelper#restoreEntity(BackupDataInputStream) BackupHelper.restoreEntity(BackupDataInputStream)} + * is called, the current entity's header has already been read from the underlying + * {@link BackupDataInput}. The entity's key string and total data size are available + * through this class's {@link #getKey()} and {@link #size()} methods, respectively. + * <p class="note"> + * <em>Note:</em> The caller should take care not to seek or close the underlying data + * source, or to read more than {@link #size()} bytes total from the stream.</p> + * + * @see BackupAgentHelper + * @see BackupHelper + */ public class BackupDataInputStream extends InputStream { String key; @@ -34,6 +48,13 @@ public class BackupDataInputStream extends InputStream { mData = data; } + /** + * Read one byte of entity data from the stream, returning it as + * an integer value. If more than {@link #size()} bytes of data + * are read from the stream, the output of this method is undefined. + * + * @return The byte read, or undefined if the end of the stream has been reached. + */ public int read() throws IOException { byte[] one = mOneByte; if (mOneByte == null) { @@ -43,18 +64,52 @@ public class BackupDataInputStream extends InputStream { return one[0]; } + /** + * Read up to {@code size} bytes of data into a byte array, beginning at position + * {@code offset} within the array. + * + * @param b Byte array into which the data will be read + * @param offset The data will be stored in {@code b} beginning at this index + * within the array. + * @param size The number of bytes to read in this operation. If insufficient + * data exists within the entity to fulfill this request, only as much data + * will be read as is available. + * @return The number of bytes of data read, or zero if all of the entity's + * data has already been read. + */ public int read(byte[] b, int offset, int size) throws IOException { return mData.readEntityData(b, offset, size); } + /** + * Read enough entity data into a byte array to fill the array. + * + * @param b Byte array to fill with data from the stream. If the stream does not + * have sufficient data to fill the array, then the contents of the remainder of + * the array will be undefined. + * @return The number of bytes of data read, or zero if all of the entity's + * data has already been read. + */ public int read(byte[] b) throws IOException { return mData.readEntityData(b, 0, b.length); } + /** + * Report the key string associated with this entity within the backup data set. + * + * @return The key string for this entity, equivalent to calling + * {@link BackupDataInput#getKey()} on the underlying {@link BackupDataInput}. + */ public String getKey() { return this.key; } - + + /** + * Report the total number of bytes of data available for the current entity. + * + * @return The number of data bytes available, equivalent to calling + * {@link BackupDataInput#getDataSize()} on the underlying {@link BackupDataInput}. + */ public int size() { return this.dataSize; } diff --git a/core/java/android/app/backup/BackupHelper.java b/core/java/android/app/backup/BackupHelper.java index 3f41ed24dfca..87b581b0cd43 100644 --- a/core/java/android/app/backup/BackupHelper.java +++ b/core/java/android/app/backup/BackupHelper.java @@ -19,11 +19,21 @@ package android.app.backup; import android.os.ParcelFileDescriptor; /** - * A convenient interface to be used with the - * {@link android.app.backup.BackupAgentHelper} class to implement backup and restore of - * arbitrary data types. + * This interface defines the calling interface that {@link BackupAgentHelper} uses + * when dispatching backup and restore operations to the installed helpers. + * Applications can define and install their own helpers as well as using those + * provided as part of the Android framework. * <p> - * STOPSHIP: document! + * Although multiple helper objects may be installed simultaneously, each helper + * is responsible only for handling its own data, and will not see entities + * created by other components within the backup system. Invocations of multiple + * helpers are performed sequentially by the {@link BackupAgentHelper}, with each + * helper given a chance to access its own saved state from within the state record + * produced during the previous backup operation. + * + * @see BackupAgentHelper + * @see FileBackupHelper + * @see SharedPreferencesBackupHelper */ public interface BackupHelper { /** @@ -31,24 +41,46 @@ public interface BackupHelper { * application's data directory need to be backed up, write them to * <code>data</code>, and fill in <code>newState</code> with the state as it * exists now. + * <p> + * Implementing this method is much like implementing + * {@link BackupAgent#onBackup(ParcelFileDescriptor, BackupDataOutput, ParcelFileDescriptor)} + * — the method parameters are the same. When this method is invoked the + * {@code oldState} descriptor points to the beginning of the state data + * written during this helper's previous backup operation, and the {@code newState} + * descriptor points to the file location at which the helper should write its + * new state after performing the backup operation. + * <p class="note"> + * <em>Note:</em> The helper should not close or seek either the {@code oldState} or + * the {@code newState} file descriptors.</p> */ public void performBackup(ParcelFileDescriptor oldState, BackupDataOutput data, ParcelFileDescriptor newState); /** * Called by {@link android.app.backup.BackupAgentHelper BackupAgentHelper} - * to restore one entity from the restore dataset. - * <p class=note> - * Do not close the <code>data</code> stream. Do not read more than - * <code>data.size()</code> bytes from <code>data</code>. + * to restore a single entity from the restore data set. This method will be + * called for each entity in the data set that belongs to this handler. + * <p class="note"> + * <em>Note:</em> Do not close the <code>data</code> stream. Do not read more than + * <code>data.size()</code> bytes from <code>data</code>.</p> */ public void restoreEntity(BackupDataInputStream data); /** * Called by {@link android.app.backup.BackupAgentHelper BackupAgentHelper} * after a restore operation to write the backup state file corresponding to - * the data as processed by the helper. + * the data as processed by the helper. The data written here will be + * available to the helper during the next call to its + * {@link #performBackup(ParcelFileDescriptor, BackupDataOutput, ParcelFileDescriptor)} + * method. + * <p> + * Note that this method will be called even if the handler's + * {@link #restoreEntity(BackupDataInputStream)} method was never invoked during + * the restore operation. + * <p class="note"> + * <em>Note:</em> The helper should not close or seek the {@code newState} + * file descriptor.</p> */ - public void writeNewStateDescription(ParcelFileDescriptor fd); + public void writeNewStateDescription(ParcelFileDescriptor newState); } diff --git a/core/java/android/server/BluetoothA2dpService.java b/core/java/android/server/BluetoothA2dpService.java index 08cd2f0ec420..893db2e748c6 100644 --- a/core/java/android/server/BluetoothA2dpService.java +++ b/core/java/android/server/BluetoothA2dpService.java @@ -487,12 +487,6 @@ public class BluetoothA2dpService extends IBluetoothA2dp.Stub { if (state != prevState) { if (state == BluetoothA2dp.STATE_DISCONNECTED || state == BluetoothA2dp.STATE_DISCONNECTING) { - if (prevState == BluetoothA2dp.STATE_CONNECTED || - prevState == BluetoothA2dp.STATE_PLAYING) { - // disconnecting or disconnected - Intent intent = new Intent(AudioManager.ACTION_AUDIO_BECOMING_NOISY); - mContext.sendBroadcast(intent); - } mSinkCount--; } else if (state == BluetoothA2dp.STATE_CONNECTED) { mSinkCount ++; diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java index 4f2a67bfb153..06a7a6f4c127 100644 --- a/core/java/android/webkit/WebView.java +++ b/core/java/android/webkit/WebView.java @@ -2150,11 +2150,15 @@ public class WebView extends AbsoluteLayout mScrollX = pinLocX(Math.round(sx)); mScrollY = pinLocY(Math.round(sy)); + // update webkit if (oldX != mScrollX || oldY != mScrollY) { onScrollChanged(mScrollX, mScrollY, oldX, oldY); + } else { + // the scroll position is adjusted at the beginning of the + // zoom animation. But we want to update the WebKit at the + // end of the zoom animation. See comments in onScaleEnd(). + sendOurVisibleRect(); } - - // update webkit sendViewSizeZoom(); } } diff --git a/media/java/android/media/AudioService.java b/media/java/android/media/AudioService.java index 87ccaafef639..9ab02f00b5d5 100644 --- a/media/java/android/media/AudioService.java +++ b/media/java/android/media/AudioService.java @@ -102,6 +102,9 @@ public class AudioService extends IAudioService.Stub { private static final int MSG_MEDIA_SERVER_DIED = 5; private static final int MSG_MEDIA_SERVER_STARTED = 6; private static final int MSG_PLAY_SOUND_EFFECT = 7; + private static final int MSG_BTA2DP_DOCK_TIMEOUT = 8; + + private static final int BTA2DP_DOCK_TIMEOUT_MILLIS = 8000; /** @see AudioSystemThread */ private AudioSystemThread mAudioSystemThread; @@ -1651,6 +1654,11 @@ public class AudioService extends IAudioService.Stub { case MSG_PLAY_SOUND_EFFECT: playSoundEffect(msg.arg1, msg.arg2); break; + + case MSG_BTA2DP_DOCK_TIMEOUT: + // msg.obj == address of BTA2DP device + makeA2dpDeviceUnavailableNow( (String) msg.obj ); + break; } } } @@ -1705,6 +1713,38 @@ public class AudioService extends IAudioService.Stub { } } + private void makeA2dpDeviceAvailable(String address) { + AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, + AudioSystem.DEVICE_STATE_AVAILABLE, + address); + // Reset A2DP suspend state each time a new sink is connected + AudioSystem.setParameters("A2dpSuspended=false"); + mConnectedDevices.put( new Integer(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP), + address); + } + + private void makeA2dpDeviceUnavailableNow(String address) { + Intent noisyIntent = new Intent(AudioManager.ACTION_AUDIO_BECOMING_NOISY); + mContext.sendBroadcast(noisyIntent); + AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, + AudioSystem.DEVICE_STATE_UNAVAILABLE, + address); + mConnectedDevices.remove(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP); + } + + private void makeA2dpDeviceUnavailableLater(String address) { + // the device will be made unavailable later, so consider it disconnected right away + mConnectedDevices.remove(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP); + // send the delayed message to make the device unavailable later + Message msg = mAudioHandler.obtainMessage(MSG_BTA2DP_DOCK_TIMEOUT, address); + mAudioHandler.sendMessageDelayed(msg, BTA2DP_DOCK_TIMEOUT_MILLIS); + + } + + private void cancelA2dpDeviceTimeout(String address) { + mAudioHandler.removeMessages(MSG_BTA2DP_DOCK_TIMEOUT); + } + /** * Receiver for misc intent broadcasts the Phone app cares about. */ @@ -1739,20 +1779,25 @@ public class AudioService extends IAudioService.Stub { if (isConnected && state != BluetoothA2dp.STATE_CONNECTED && state != BluetoothA2dp.STATE_PLAYING) { - AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, - AudioSystem.DEVICE_STATE_UNAVAILABLE, - address); - mConnectedDevices.remove(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP); + if (btDevice.isBluetoothDock()) { + if (state == BluetoothA2dp.STATE_DISCONNECTED) { + // introduction of a delay for transient disconnections of docks when + // power is rapidly turned off/on, this message will be canceled if + // we reconnect the dock under a preset delay + makeA2dpDeviceUnavailableLater(address); + // the next time isConnected is evaluated, it will be false for the dock + } + } else { + makeA2dpDeviceUnavailableNow(address); + } } else if (!isConnected && (state == BluetoothA2dp.STATE_CONNECTED || state == BluetoothA2dp.STATE_PLAYING)) { - AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, - AudioSystem.DEVICE_STATE_AVAILABLE, - address); - // Reset A2DP suspend state each time a new sink is connected - AudioSystem.setParameters("A2dpSuspended=false"); - mConnectedDevices.put( new Integer(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP), - address); + if (btDevice.isBluetoothDock()) { + // this could be a reconnection after a transient disconnection + cancelA2dpDeviceTimeout(address); + } + makeA2dpDeviceAvailable(address); } } else if (action.equals(BluetoothHeadset.ACTION_STATE_CHANGED)) { int state = intent.getIntExtra(BluetoothHeadset.EXTRA_STATE, diff --git a/media/libstagefright/AudioPlayer.cpp b/media/libstagefright/AudioPlayer.cpp index 005c64a0b748..bcf246397266 100644 --- a/media/libstagefright/AudioPlayer.cpp +++ b/media/libstagefright/AudioPlayer.cpp @@ -102,7 +102,7 @@ status_t AudioPlayer::start(bool sourceAlreadyStarted) { (numChannels == 2) ? AudioSystem::CHANNEL_OUT_STEREO : AudioSystem::CHANNEL_OUT_MONO, - 8192, 0, &AudioCallback, this, 0); + 0, 0, &AudioCallback, this, 0); if ((err = mAudioTrack->initCheck()) != OK) { delete mAudioTrack; diff --git a/media/libstagefright/AwesomePlayer.cpp b/media/libstagefright/AwesomePlayer.cpp index 2bc81399c389..b14a03ce9e08 100644 --- a/media/libstagefright/AwesomePlayer.cpp +++ b/media/libstagefright/AwesomePlayer.cpp @@ -1239,7 +1239,19 @@ status_t AwesomePlayer::suspend() { Mutex::Autolock autoLock(mLock); if (mSuspensionState != NULL) { - return INVALID_OPERATION; + if (mLastVideoBuffer == NULL) { + //go into here if video is suspended again + //after resuming without being played between + //them + SuspensionState *state = mSuspensionState; + mSuspensionState = NULL; + reset_l(); + mSuspensionState = state; + return OK; + } + + delete mSuspensionState; + mSuspensionState = NULL; } if (mFlags & PREPARING) { @@ -1344,7 +1356,7 @@ status_t AwesomePlayer::resume() { play_l(); } - delete state; + mSuspensionState = state; state = NULL; return OK; diff --git a/media/libstagefright/OMXCodec.cpp b/media/libstagefright/OMXCodec.cpp index b9d6fbc29215..873c2aa575b4 100644 --- a/media/libstagefright/OMXCodec.cpp +++ b/media/libstagefright/OMXCodec.cpp @@ -3040,7 +3040,7 @@ status_t QueryCodecs( continue; } - OMXCodec::setComponentRole(omx, node, queryDecoders, mime); + OMXCodec::setComponentRole(omx, node, !queryDecoders, mime); results->push(); CodecCapabilities *caps = &results->editItemAt(results->size() - 1); diff --git a/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java b/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java index 0c0bf93ac073..2b4714def1f8 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java @@ -789,7 +789,8 @@ public class DatabaseHelper extends SQLiteOpenHelper { + " VALUES(?,?);"); // Set the timeout to 30 minutes in milliseconds - loadIntegerSetting(stmt, Settings.System.SCREEN_OFF_TIMEOUT, 30 * 60 * 1000); + loadSetting(stmt, Settings.System.SCREEN_OFF_TIMEOUT, + Integer.toString(30 * 60 * 1000)); } finally { if (stmt != null) stmt.close(); } diff --git a/services/java/com/android/server/ThrottleService.java b/services/java/com/android/server/ThrottleService.java index 9d4e2263b902..08b003d10470 100644 --- a/services/java/com/android/server/ThrottleService.java +++ b/services/java/com/android/server/ThrottleService.java @@ -463,7 +463,7 @@ public class ThrottleService extends IThrottleManager.Stub { private void postNotification(int titleInt, int messageInt, int icon, int flags) { Intent intent = new Intent(); // TODO - fix up intent - intent.setClassName("com.android.settings", "com.android.settings.TetherSettings"); + intent.setClassName("com.android.phone", "com.android.phone.DataUsage"); intent.setFlags(Intent.FLAG_ACTIVITY_NO_HISTORY); PendingIntent pi = PendingIntent.getActivity(mContext, 0, intent, 0); |