diff options
author | The Android Open Source Project <initial-contribution@android.com> | 2009-03-03 19:31:44 -0800 |
---|---|---|
committer | The Android Open Source Project <initial-contribution@android.com> | 2009-03-03 19:31:44 -0800 |
commit | 9066cfe9886ac131c34d59ed0e2d287b0e3c0087 (patch) | |
tree | d88beb88001f2482911e3d28e43833b50e4b4e97 /core/java/android/app | |
parent | d83a98f4ce9cfa908f5c54bbd70f03eec07e7553 (diff) | |
download | frameworks_base-9066cfe9886ac131c34d59ed0e2d287b0e3c0087.zip frameworks_base-9066cfe9886ac131c34d59ed0e2d287b0e3c0087.tar.gz frameworks_base-9066cfe9886ac131c34d59ed0e2d287b0e3c0087.tar.bz2 |
auto import from //depot/cupcake/@135843
Diffstat (limited to 'core/java/android/app')
51 files changed, 26304 insertions, 0 deletions
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java new file mode 100644 index 0000000..849a37d --- /dev/null +++ b/core/java/android/app/Activity.java @@ -0,0 +1,3598 @@ +/* + * Copyright (C) 2006 The Android Open Source Project + * + * 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 android.app; + +import android.content.ComponentCallbacks; +import android.content.ComponentName; +import android.content.ContentResolver; +import android.content.Context; +import android.content.Intent; +import android.content.SharedPreferences; +import android.content.pm.ActivityInfo; +import android.content.res.Configuration; +import android.content.res.Resources; +import android.database.Cursor; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.drawable.Drawable; +import android.media.AudioManager; +import android.net.Uri; +import android.os.Bundle; +import android.os.RemoteException; +import android.os.Handler; +import android.os.IBinder; +import android.text.Selection; +import android.text.SpannableStringBuilder; +import android.text.method.TextKeyListener; +import android.util.AttributeSet; +import android.util.Config; +import android.util.EventLog; +import android.util.Log; +import android.util.SparseArray; +import android.view.ContextMenu; +import android.view.ContextThemeWrapper; +import android.view.KeyEvent; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewGroup; +import android.view.ViewManager; +import android.view.Window; +import android.view.WindowManager; +import android.view.ContextMenu.ContextMenuInfo; +import android.view.View.OnCreateContextMenuListener; +import android.widget.AdapterView; + +import com.android.internal.policy.PolicyManager; + +import java.util.ArrayList; +import java.util.HashMap; + +/** + * An activity is a single, focused thing that the user can do. Almost all + * activities interact with the user, so the Activity class takes care of + * creating a window for you in which you can place your UI with + * {@link #setContentView}. While activities are often presented to the user + * as full-screen windows, they can also be used in other ways: as floating + * windows (via a theme with {@link android.R.attr#windowIsFloating} set) + * or embedded inside of another activity (using {@link ActivityGroup}). + * + * There are two methods almost all subclasses of Activity will implement: + * + * <ul> + * <li> {@link #onCreate} is where you initialize your activity. Most + * importantly, here you will usually call {@link #setContentView(int)} + * with a layout resource defining your UI, and using {@link #findViewById} + * to retrieve the widgets in that UI that you need to interact with + * programmatically. + * + * <li> {@link #onPause} is where you deal with the user leaving your + * activity. Most importantly, any changes made by the user should at this + * point be committed (usually to the + * {@link android.content.ContentProvider} holding the data). + * </ul> + * + * <p>To be of use with {@link android.content.Context#startActivity Context.startActivity()}, all + * activity classes must have a corresponding + * {@link android.R.styleable#AndroidManifestActivity <activity>} + * declaration in their package's <code>AndroidManifest.xml</code>.</p> + * + * <p>The Activity class is an important part of an application's overall lifecycle, + * and the way activities are launched and put together is a fundamental + * part of the platform's application model. For a detailed perspective on the structure of + * Android applications and lifecycles, please read the <em>Dev Guide</em> document on + * <a href="{@docRoot}guide/topics/fundamentals.html">Application Fundamentals</a>.</p> + * + * <p>Topics covered here: + * <ol> + * <li><a href="#ActivityLifecycle">Activity Lifecycle</a> + * <li><a href="#ConfigurationChanges">Configuration Changes</a> + * <li><a href="#StartingActivities">Starting Activities and Getting Results</a> + * <li><a href="#SavingPersistentState">Saving Persistent State</a> + * <li><a href="#Permissions">Permissions</a> + * <li><a href="#ProcessLifecycle">Process Lifecycle</a> + * </ol> + * + * <a name="ActivityLifecycle"></a> + * <h3>Activity Lifecycle</h3> + * + * <p>Activities in the system are managed as an <em>activity stack</em>. + * When a new activity is started, it is placed on the top of the stack + * and becomes the running activity -- the previous activity always remains + * below it in the stack, and will not come to the foreground again until + * the new activity exits.</p> + * + * <p>An activity has essentially four states:</p> + * <ul> + * <li> If an activity in the foreground of the screen (at the top of + * the stack), + * it is <em>active</em> or <em>running</em>. </li> + * <li>If an activity has lost focus but is still visible (that is, a new non-full-sized + * or transparent activity has focus on top of your activity), it + * is <em>paused</em>. A paused activity is completely alive (it + * maintains all state and member information and remains attached to + * the window manager), but can be killed by the system in extreme + * low memory situations. + * <li>If an activity is completely obscured by another activity, + * it is <em>stopped</em>. It still retains all state and member information, + * however, it is no longer visible to the user so its window is hidden + * and it will often be killed by the system when memory is needed + * elsewhere.</li> + * <li>If an activity is paused or stopped, the system can drop the activity + * from memory by either asking it to finish, or simply killing its + * process. When it is displayed again to the user, it must be + * completely restarted and restored to its previous state.</li> + * </ul> + * + * <p>The following diagram shows the important state paths of an Activity. + * The square rectangles represent callback methods you can implement to + * perform operations when the Activity moves between states. The colored + * ovals are major states the Activity can be in.</p> + * + * <p><img src="../../../images/activity_lifecycle.png" + * alt="State diagram for an Android Activity Lifecycle." border="0" /></p> + * + * <p>There are three key loops you may be interested in monitoring within your + * activity: + * + * <ul> + * <li>The <b>entire lifetime</b> of an activity happens between the first call + * to {@link android.app.Activity#onCreate} through to a single final call + * to {@link android.app.Activity#onDestroy}. An activity will do all setup + * of "global" state in onCreate(), and release all remaining resources in + * onDestroy(). For example, if it has a thread running in the background + * to download data from the network, it may create that thread in onCreate() + * and then stop the thread in onDestroy(). + * + * <li>The <b>visible lifetime</b> of an activity happens between a call to + * {@link android.app.Activity#onStart} until a corresponding call to + * {@link android.app.Activity#onStop}. During this time the user can see the + * activity on-screen, though it may not be in the foreground and interacting + * with the user. Between these two methods you can maintain resources that + * are needed to show the activity to the user. For example, you can register + * a {@link android.content.BroadcastReceiver} in onStart() to monitor for changes + * that impact your UI, and unregister it in onStop() when the user an no + * longer see what you are displaying. The onStart() and onStop() methods + * can be called multiple times, as the activity becomes visible and hidden + * to the user. + * + * <li>The <b>foreground lifetime</b> of an activity happens between a call to + * {@link android.app.Activity#onResume} until a corresponding call to + * {@link android.app.Activity#onPause}. During this time the activity is + * in front of all other activities and interacting with the user. An activity + * can frequently go between the resumed and paused states -- for example when + * the device goes to sleep, when an activity result is delivered, when a new + * intent is delivered -- so the code in these methods should be fairly + * lightweight. + * </ul> + * + * <p>The entire lifecycle of an activity is defined by the following + * Activity methods. All of these are hooks that you can override + * to do appropriate work when the activity changes state. All + * activities will implement {@link android.app.Activity#onCreate} + * to do their initial setup; many will also implement + * {@link android.app.Activity#onPause} to commit changes to data and + * otherwise prepare to stop interacting with the user. You should always + * call up to your superclass when implementing these methods.</p> + * + * </p> + * <pre class="prettyprint"> + * public class Activity extends ApplicationContext { + * protected void onCreate(Bundle savedInstanceState); + * + * protected void onStart(); + * + * protected void onRestart(); + * + * protected void onResume(); + * + * protected void onPause(); + * + * protected void onStop(); + * + * protected void onDestroy(); + * } + * </pre> + * + * <p>In general the movement through an activity's lifecycle looks like + * this:</p> + * + * <table border="2" width="85%" align="center" frame="hsides" rules="rows"> + * <colgroup align="left" span="3" /> + * <colgroup align="left" /> + * <colgroup align="center" /> + * <colgroup align="center" /> + * + * <thead> + * <tr><th colspan="3">Method</th> <th>Description</th> <th>Killable?</th> <th>Next</th></tr> + * </thead> + * + * <tbody> + * <tr><th colspan="3" align="left" border="0">{@link android.app.Activity#onCreate onCreate()}</th> + * <td>Called when the activity is first created. + * This is where you should do all of your normal static set up: + * create views, bind data to lists, etc. This method also + * provides you with a Bundle containing the activity's previously + * frozen state, if there was one. + * <p>Always followed by <code>onStart()</code>.</td> + * <td align="center">No</td> + * <td align="center"><code>onStart()</code></td> + * </tr> + * + * <tr><td rowspan="5" style="border-left: none; border-right: none;"> </td> + * <th colspan="2" align="left" border="0">{@link android.app.Activity#onRestart onRestart()}</th> + * <td>Called after your activity has been stopped, prior to it being + * started again. + * <p>Always followed by <code>onStart()</code></td> + * <td align="center">No</td> + * <td align="center"><code>onStart()</code></td> + * </tr> + * + * <tr><th colspan="2" align="left" border="0">{@link android.app.Activity#onStart onStart()}</th> + * <td>Called when the activity is becoming visible to the user. + * <p>Followed by <code>onResume()</code> if the activity comes + * to the foreground, or <code>onStop()</code> if it becomes hidden.</td> + * <td align="center">No</td> + * <td align="center"><code>onResume()</code> or <code>onStop()</code></td> + * </tr> + * + * <tr><td rowspan="2" style="border-left: none;"> </td> + * <th align="left" border="0">{@link android.app.Activity#onResume onResume()}</th> + * <td>Called when the activity will start + * interacting with the user. At this point your activity is at + * the top of the activity stack, with user input going to it. + * <p>Always followed by <code>onPause()</code>.</td> + * <td align="center">No</td> + * <td align="center"><code>onPause()</code></td> + * </tr> + * + * <tr><th align="left" border="0">{@link android.app.Activity#onPause onPause()}</th> + * <td>Called when the system is about to start resuming a previous + * activity. This is typically used to commit unsaved changes to + * persistent data, stop animations and other things that may be consuming + * CPU, etc. Implementations of this method must be very quick because + * the next activity will not be resumed until this method returns. + * <p>Followed by either <code>onResume()</code> if the activity + * returns back to the front, or <code>onStop()</code> if it becomes + * invisible to the user.</td> + * <td align="center"><font color="#800000"><strong>Yes</strong></font></td> + * <td align="center"><code>onResume()</code> or<br> + * <code>onStop()</code></td> + * </tr> + * + * <tr><th colspan="2" align="left" border="0">{@link android.app.Activity#onStop onStop()}</th> + * <td>Called when the activity is no longer visible to the user, because + * another activity has been resumed and is covering this one. This + * may happen either because a new activity is being started, an existing + * one is being brought in front of this one, or this one is being + * destroyed. + * <p>Followed by either <code>onRestart()</code> if + * this activity is coming back to interact with the user, or + * <code>onDestroy()</code> if this activity is going away.</td> + * <td align="center"><font color="#800000"><strong>Yes</strong></font></td> + * <td align="center"><code>onRestart()</code> or<br> + * <code>onDestroy()</code></td> + * </tr> + * + * <tr><th colspan="3" align="left" border="0">{@link android.app.Activity#onDestroy onDestroy()}</th> + * <td>The final call you receive before your + * activity is destroyed. This can happen either because the + * activity is finishing (someone called {@link Activity#finish} on + * it, or because the system is temporarily destroying this + * instance of the activity to save space. You can distinguish + * between these two scenarios with the {@link + * Activity#isFinishing} method.</td> + * <td align="center"><font color="#800000"><strong>Yes</strong></font></td> + * <td align="center"><em>nothing</em></td> + * </tr> + * </tbody> + * </table> + * + * <p>Note the "Killable" column in the above table -- for those methods that + * are marked as being killable, after that method returns the process hosting the + * activity may killed by the system <em>at any time</em> without another line + * of its code being executed. Because of this, you should use the + * {@link #onPause} method to write any persistent data (such as user edits) + * to storage. In addition, the method + * {@link #onSaveInstanceState(Bundle)} is called before placing the activity + * in such a background state, allowing you to save away any dynamic instance + * state in your activity into the given Bundle, to be later received in + * {@link #onCreate} if the activity needs to be re-created. + * See the <a href="#ProcessLifecycle">Process Lifecycle</a> + * section for more information on how the lifecycle of a process is tied + * to the activities it is hosting. Note that it is important to save + * persistent data in {@link #onPause} instead of {@link #onSaveInstanceState} + * because the later is not part of the lifecycle callbacks, so will not + * be called in every situation as described in its documentation.</p> + * + * <p>For those methods that are not marked as being killable, the activity's + * process will not be killed by the system starting from the time the method + * is called and continuing after it returns. Thus an activity is in the killable + * state, for example, between after <code>onPause()</code> to the start of + * <code>onResume()</code>.</p> + * + * <a name="ConfigurationChanges"></a> + * <h3>Configuration Changes</h3> + * + * <p>If the configuration of the device (as defined by the + * {@link Configuration Resources.Configuration} class) changes, + * then anything displaying a user interface will need to update to match that + * configuration. Because Activity is the primary mechanism for interacting + * with the user, it includes special support for handling configuration + * changes.</p> + * + * <p>Unless you specify otherwise, a configuration change (such as a change + * in screen orientation, language, input devices, etc) will cause your + * current activity to be <em>destroyed</em>, going through the normal activity + * lifecycle process of {@link #onPause}, + * {@link #onStop}, and {@link #onDestroy} as appropriate. If the activity + * had been in the foreground or visible to the user, once {@link #onDestroy} is + * called in that instance then a new instance of the activity will be + * created, with whatever savedInstanceState the previous instance had generated + * from {@link #onSaveInstanceState}.</p> + * + * <p>This is done because any application resource, + * including layout files, can change based on any configuration value. Thus + * the only safe way to handle a configuration change is to re-retrieve all + * resources, including layouts, drawables, and strings. Because activities + * must already know how to save their state and re-create themselves from + * that state, this is a convenient way to have an activity restart itself + * with a new configuration.</p> + * + * <p>In some special cases, you may want to bypass restarting of your + * activity based on one or more types of configuration changes. This is + * done with the {@link android.R.attr#configChanges android:configChanges} + * attribute in its manifest. For any types of configuration changes you say + * that you handle there, you will receive a call to your current activity's + * {@link #onConfigurationChanged} method instead of being restarted. If + * a configuration change involves any that you do not handle, however, the + * activity will still be restarted and {@link #onConfigurationChanged} + * will not be called.</p> + * + * <a name="StartingActivities"></a> + * <h3>Starting Activities and Getting Results</h3> + * + * <p>The {@link android.app.Activity#startActivity} + * method is used to start a + * new activity, which will be placed at the top of the activity stack. It + * takes a single argument, an {@link android.content.Intent Intent}, + * which describes the activity + * to be executed.</p> + * + * <p>Sometimes you want to get a result back from an activity when it + * ends. For example, you may start an activity that lets the user pick + * a person in a list of contacts; when it ends, it returns the person + * that was selected. To do this, you call the + * {@link android.app.Activity#startActivityForResult(Intent, int)} + * version with a second integer parameter identifying the call. The result + * will come back through your {@link android.app.Activity#onActivityResult} + * method.</p> + * + * <p>When an activity exits, it can call + * {@link android.app.Activity#setResult(int)} + * to return data back to its parent. It must always supply a result code, + * which can be the standard results RESULT_CANCELED, RESULT_OK, or any + * custom values starting at RESULT_FIRST_USER. In addition, it can optionally + * return back an Intent containing any additional data it wants. All of this + * information appears back on the + * parent's <code>Activity.onActivityResult()</code>, along with the integer + * identifier it originally supplied.</p> + * + * <p>If a child activity fails for any reason (such as crashing), the parent + * activity will receive a result with the code RESULT_CANCELED.</p> + * + * <pre class="prettyprint"> + * public class MyActivity extends Activity { + * ... + * + * static final int PICK_CONTACT_REQUEST = 0; + * + * protected boolean onKeyDown(int keyCode, KeyEvent event) { + * if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER) { + * // When the user center presses, let them pick a contact. + * startActivityForResult( + * new Intent(Intent.ACTION_PICK, + * new Uri("content://contacts")), + * PICK_CONTACT_REQUEST); + * return true; + * } + * return false; + * } + * + * protected void onActivityResult(int requestCode, int resultCode, + * Intent data) { + * if (requestCode == PICK_CONTACT_REQUEST) { + * if (resultCode == RESULT_OK) { + * // A contact was picked. Here we will just display it + * // to the user. + * startActivity(new Intent(Intent.ACTION_VIEW, data)); + * } + * } + * } + * } + * </pre> + * + * <a name="SavingPersistentState"></a> + * <h3>Saving Persistent State</h3> + * + * <p>There are generally two kinds of persistent state than an activity + * will deal with: shared document-like data (typically stored in a SQLite + * database using a {@linkplain android.content.ContentProvider content provider}) + * and internal state such as user preferences.</p> + * + * <p>For content provider data, we suggest that activities use a + * "edit in place" user model. That is, any edits a user makes are effectively + * made immediately without requiring an additional confirmation step. + * Supporting this model is generally a simple matter of following two rules:</p> + * + * <ul> + * <li> <p>When creating a new document, the backing database entry or file for + * it is created immediately. For example, if the user chooses to write + * a new e-mail, a new entry for that e-mail is created as soon as they + * start entering data, so that if they go to any other activity after + * that point this e-mail will now appear in the list of drafts.</p> + * <li> <p>When an activity's <code>onPause()</code> method is called, it should + * commit to the backing content provider or file any changes the user + * has made. This ensures that those changes will be seen by any other + * activity that is about to run. You will probably want to commit + * your data even more aggressively at key times during your + * activity's lifecycle: for example before starting a new + * activity, before finishing your own activity, when the user + * switches between input fields, etc.</p> + * </ul> + * + * <p>This model is designed to prevent data loss when a user is navigating + * between activities, and allows the system to safely kill an activity (because + * system resources are needed somewhere else) at any time after it has been + * paused. Note this implies + * that the user pressing BACK from your activity does <em>not</em> + * mean "cancel" -- it means to leave the activity with its current contents + * saved away. Cancelling edits in an activity must be provided through + * some other mechanism, such as an explicit "revert" or "undo" option.</p> + * + * <p>See the {@linkplain android.content.ContentProvider content package} for + * more information about content providers. These are a key aspect of how + * different activities invoke and propagate data between themselves.</p> + * + * <p>The Activity class also provides an API for managing internal persistent state + * associated with an activity. This can be used, for example, to remember + * the user's preferred initial display in a calendar (day view or week view) + * or the user's default home page in a web browser.</p> + * + * <p>Activity persistent state is managed + * with the method {@link #getPreferences}, + * allowing you to retrieve and + * modify a set of name/value pairs associated with the activity. To use + * preferences that are shared across multiple application components + * (activities, receivers, services, providers), you can use the underlying + * {@link Context#getSharedPreferences Context.getSharedPreferences()} method + * to retrieve a preferences + * object stored under a specific name. + * (Note that it is not possible to share settings data across application + * packages -- for that you will need a content provider.)</p> + * + * <p>Here is an excerpt from a calendar activity that stores the user's + * preferred view mode in its persistent settings:</p> + * + * <pre class="prettyprint"> + * public class CalendarActivity extends Activity { + * ... + * + * static final int DAY_VIEW_MODE = 0; + * static final int WEEK_VIEW_MODE = 1; + * + * private SharedPreferences mPrefs; + * private int mCurViewMode; + * + * protected void onCreate(Bundle savedInstanceState) { + * super.onCreate(savedInstanceState); + * + * SharedPreferences mPrefs = getSharedPreferences(); + * mCurViewMode = mPrefs.getInt("view_mode" DAY_VIEW_MODE); + * } + * + * protected void onPause() { + * super.onPause(); + * + * SharedPreferences.Editor ed = mPrefs.edit(); + * ed.putInt("view_mode", mCurViewMode); + * ed.commit(); + * } + * } + * </pre> + * + * <a name="Permissions"></a> + * <h3>Permissions</h3> + * + * <p>The ability to start a particular Activity can be enforced when it is + * declared in its + * manifest's {@link android.R.styleable#AndroidManifestActivity <activity>} + * tag. By doing so, other applications will need to declare a corresponding + * {@link android.R.styleable#AndroidManifestUsesPermission <uses-permission>} + * element in their own manifest to be able to start that activity. + * + * <p>See the <a href="{@docRoot}guide/topics/security/security.html">Security and Permissions</a> + * document for more information on permissions and security in general. + * + * <a name="ProcessLifecycle"></a> + * <h3>Process Lifecycle</h3> + * + * <p>The Android system attempts to keep application process around for as + * long as possible, but eventually will need to remove old processes when + * memory runs low. As described in <a href="#ActivityLifecycle">Activity + * Lifecycle</a>, the decision about which process to remove is intimately + * tied to the state of the user's interaction with it. In general, there + * are four states a process can be in based on the activities running in it, + * listed here in order of importance. The system will kill less important + * processes (the last ones) before it resorts to killing more important + * processes (the first ones). + * + * <ol> + * <li> <p>The <b>foreground activity</b> (the activity at the top of the screen + * that the user is currently interacting with) is considered the most important. + * Its process will only be killed as a last resort, if it uses more memory + * than is available on the device. Generally at this point the device has + * reached a memory paging state, so this is required in order to keep the user + * interface responsive. + * <li> <p>A <b>visible activity</b> (an activity that is visible to the user + * but not in the foreground, such as one sitting behind a foreground dialog) + * is considered extremely important and will not be killed unless that is + * required to keep the foreground activity running. + * <li> <p>A <b>background activity</b> (an activity that is not visible to + * the user and has been paused) is no longer critical, so the system may + * safely kill its process to reclaim memory for other foreground or + * visible processes. If its process needs to be killed, when the user navigates + * back to the activity (making it visible on the screen again), its + * {@link #onCreate} method will be called with the savedInstanceState it had previously + * supplied in {@link #onSaveInstanceState} so that it can restart itself in the same + * state as the user last left it. + * <li> <p>An <b>empty process</b> is one hosting no activities or other + * application components (such as {@link Service} or + * {@link android.content.BroadcastReceiver} classes). These are killed very + * quickly by the system as memory becomes low. For this reason, any + * background operation you do outside of an activity must be executed in the + * context of an activity BroadcastReceiver or Service to ensure that the system + * knows it needs to keep your process around. + * </ol> + * + * <p>Sometimes an Activity may need to do a long-running operation that exists + * independently of the activity lifecycle itself. An example may be a camera + * application that allows you to upload a picture to a web site. The upload + * may take a long time, and the application should allow the user to leave + * the application will it is executing. To accomplish this, your Activity + * should start a {@link Service} in which the upload takes place. This allows + * the system to properly prioritize your process (considering it to be more + * important than other non-visible applications) for the duration of the + * upload, independent of whether the original activity is paused, stopped, + * or finished. + */ +public class Activity extends ContextThemeWrapper + implements LayoutInflater.Factory, + Window.Callback, KeyEvent.Callback, + OnCreateContextMenuListener, ComponentCallbacks { + private static final String TAG = "Activity"; + + /** Standard activity result: operation canceled. */ + public static final int RESULT_CANCELED = 0; + /** Standard activity result: operation succeeded. */ + public static final int RESULT_OK = -1; + /** Start of user-defined activity results. */ + public static final int RESULT_FIRST_USER = 1; + + private static long sInstanceCount = 0; + + private static final String WINDOW_HIERARCHY_TAG = "android:viewHierarchyState"; + private static final String SAVED_DIALOG_IDS_KEY = "android:savedDialogIds"; + private static final String SAVED_DIALOGS_TAG = "android:savedDialogs"; + private static final String SAVED_DIALOG_KEY_PREFIX = "android:dialog_"; + private static final String SAVED_SEARCH_DIALOG_KEY = "android:search_dialog"; + + private SparseArray<Dialog> mManagedDialogs; + + // set by the thread after the constructor and before onCreate(Bundle savedInstanceState) is called. + private Instrumentation mInstrumentation; + private IBinder mToken; + /*package*/ String mEmbeddedID; + private Application mApplication; + private Intent mIntent; + private ComponentName mComponent; + /*package*/ ActivityInfo mActivityInfo; + /*package*/ ActivityThread mMainThread; + /*package*/ Object mLastNonConfigurationInstance; + /*package*/ HashMap<String,Object> mLastNonConfigurationChildInstances; + Activity mParent; + boolean mCalled; + private boolean mResumed; + private boolean mStopped; + boolean mFinished; + boolean mStartedActivity; + /*package*/ int mConfigChangeFlags; + /*package*/ Configuration mCurrentConfig; + + private Window mWindow; + + private WindowManager mWindowManager; + /*package*/ View mDecor = null; + /*package*/ boolean mWindowAdded = false; + /*package*/ boolean mVisibleFromServer = false; + /*package*/ boolean mVisibleFromClient = true; + + private CharSequence mTitle; + private int mTitleColor = 0; + + private static final class ManagedCursor { + ManagedCursor(Cursor cursor) { + mCursor = cursor; + mReleased = false; + mUpdated = false; + } + + private final Cursor mCursor; + private boolean mReleased; + private boolean mUpdated; + } + private final ArrayList<ManagedCursor> mManagedCursors = + new ArrayList<ManagedCursor>(); + + // protected by synchronized (this) + int mResultCode = RESULT_CANCELED; + Intent mResultData = null; + + private boolean mTitleReady = false; + + private int mDefaultKeyMode = DEFAULT_KEYS_DISABLE; + private SpannableStringBuilder mDefaultKeySsb = null; + + protected static final int[] FOCUSED_STATE_SET = {com.android.internal.R.attr.state_focused}; + + private Thread mUiThread; + private final Handler mHandler = new Handler(); + + public Activity() { + ++sInstanceCount; + } + + + @Override + protected void finalize() throws Throwable { + super.finalize(); + --sInstanceCount; + } + + public static long getInstanceCount() { + return sInstanceCount; + } + + /** Return the intent that started this activity. */ + public Intent getIntent() { + return mIntent; + } + + /** + * Change the intent returned by {@link #getIntent}. This holds a + * reference to the given intent; it does not copy it. Often used in + * conjunction with {@link #onNewIntent}. + * + * @param newIntent The new Intent object to return from getIntent + * + * @see #getIntent + * @see #onNewIntent + */ + public void setIntent(Intent newIntent) { + mIntent = newIntent; + } + + /** Return the application that owns this activity. */ + public final Application getApplication() { + return mApplication; + } + + /** Is this activity embedded inside of another activity? */ + public final boolean isChild() { + return mParent != null; + } + + /** Return the parent activity if this view is an embedded child. */ + public final Activity getParent() { + return mParent; + } + + /** Retrieve the window manager for showing custom windows. */ + public WindowManager getWindowManager() { + return mWindowManager; + } + + /** + * Retrieve the current {@link android.view.Window} for the activity. + * This can be used to directly access parts of the Window API that + * are not available through Activity/Screen. + * + * @return Window The current window, or null if the activity is not + * visual. + */ + public Window getWindow() { + return mWindow; + } + + /** + * Calls {@link android.view.Window#getCurrentFocus} on the + * Window of this Activity to return the currently focused view. + * + * @return View The current View with focus or null. + * + * @see #getWindow + * @see android.view.Window#getCurrentFocus + */ + public View getCurrentFocus() { + return mWindow != null ? mWindow.getCurrentFocus() : null; + } + + @Override + public int getWallpaperDesiredMinimumWidth() { + int width = super.getWallpaperDesiredMinimumWidth(); + return width <= 0 ? getWindowManager().getDefaultDisplay().getWidth() : width; + } + + @Override + public int getWallpaperDesiredMinimumHeight() { + int height = super.getWallpaperDesiredMinimumHeight(); + return height <= 0 ? getWindowManager().getDefaultDisplay().getHeight() : height; + } + + /** + * Called when the activity is starting. This is where most initialization + * should go: calling {@link #setContentView(int)} to inflate the + * activity's UI, using {@link #findViewById} to programmatically interact + * with widgets in the UI, calling + * {@link #managedQuery(android.net.Uri , String[], String, String[], String)} to retrieve + * cursors for data being displayed, etc. + * + * <p>You can call {@link #finish} from within this function, in + * which case onDestroy() will be immediately called without any of the rest + * of the activity lifecycle ({@link #onStart}, {@link #onResume}, + * {@link #onPause}, etc) executing. + * + * <p><em>Derived classes must call through to the super class's + * implementation of this method. If they do not, an exception will be + * thrown.</em></p> + * + * @param savedInstanceState If the activity is being re-initialized after + * previously being shut down then this Bundle contains the data it most + * recently supplied in {@link #onSaveInstanceState}. <b><i>Note: Otherwise it is null.</i></b> + * + * @see #onStart + * @see #onSaveInstanceState + * @see #onRestoreInstanceState + * @see #onPostCreate + */ + protected void onCreate(Bundle savedInstanceState) { + mVisibleFromClient = mWindow.getWindowStyle().getBoolean( + com.android.internal.R.styleable.Window_windowNoDisplay, true); + mCalled = true; + } + + /** + * The hook for {@link ActivityThread} to restore the state of this activity. + * + * Calls {@link #onSaveInstanceState(android.os.Bundle)} and + * {@link #restoreManagedDialogs(android.os.Bundle)}. + * + * @param savedInstanceState contains the saved state + */ + final void performRestoreInstanceState(Bundle savedInstanceState) { + onRestoreInstanceState(savedInstanceState); + restoreManagedDialogs(savedInstanceState); + + // Also restore the state of a search dialog (if any) + // TODO more generic than just this manager + SearchManager searchManager = + (SearchManager) getSystemService(Context.SEARCH_SERVICE); + searchManager.restoreSearchDialog(savedInstanceState, SAVED_SEARCH_DIALOG_KEY); + } + + /** + * This method is called after {@link #onStart} when the activity is + * being re-initialized from a previously saved state, given here in + * <var>state</var>. Most implementations will simply use {@link #onCreate} + * to restore their state, but it is sometimes convenient to do it here + * after all of the initialization has been done or to allow subclasses to + * decide whether to use your default implementation. The default + * implementation of this method performs a restore of any view state that + * had previously been frozen by {@link #onSaveInstanceState}. + * + * <p>This method is called between {@link #onStart} and + * {@link #onPostCreate}. + * + * @param savedInstanceState the data most recently supplied in {@link #onSaveInstanceState}. + * + * @see #onCreate + * @see #onPostCreate + * @see #onResume + * @see #onSaveInstanceState + */ + protected void onRestoreInstanceState(Bundle savedInstanceState) { + if (mWindow != null) { + Bundle windowState = savedInstanceState.getBundle(WINDOW_HIERARCHY_TAG); + if (windowState != null) { + mWindow.restoreHierarchyState(windowState); + } + } + } + + /** + * Restore the state of any saved managed dialogs. + * + * @param savedInstanceState The bundle to restore from. + */ + private void restoreManagedDialogs(Bundle savedInstanceState) { + final Bundle b = savedInstanceState.getBundle(SAVED_DIALOGS_TAG); + if (b == null) { + return; + } + + final int[] ids = b.getIntArray(SAVED_DIALOG_IDS_KEY); + final int numDialogs = ids.length; + mManagedDialogs = new SparseArray<Dialog>(numDialogs); + for (int i = 0; i < numDialogs; i++) { + final Integer dialogId = ids[i]; + Bundle dialogState = b.getBundle(savedDialogKeyFor(dialogId)); + if (dialogState != null) { + final Dialog dialog = onCreateDialog(dialogId); + dialog.onRestoreInstanceState(dialogState); + mManagedDialogs.put(dialogId, dialog); + } + } + } + + private String savedDialogKeyFor(int key) { + return SAVED_DIALOG_KEY_PREFIX + key; + } + + + /** + * Called when activity start-up is complete (after {@link #onStart} + * and {@link #onRestoreInstanceState} have been called). Applications will + * generally not implement this method; it is intended for system + * classes to do final initialization after application code has run. + * + * <p><em>Derived classes must call through to the super class's + * implementation of this method. If they do not, an exception will be + * thrown.</em></p> + * + * @param savedInstanceState If the activity is being re-initialized after + * previously being shut down then this Bundle contains the data it most + * recently supplied in {@link #onSaveInstanceState}. <b><i>Note: Otherwise it is null.</i></b> + * @see #onCreate + */ + protected void onPostCreate(Bundle savedInstanceState) { + if (!isChild()) { + mTitleReady = true; + onTitleChanged(getTitle(), getTitleColor()); + } + mCalled = true; + } + + /** + * Called after {@link #onCreate} — or after {@link #onRestart} when + * the activity had been stopped, but is now again being displayed to the + * user. It will be followed by {@link #onResume}. + * + * <p><em>Derived classes must call through to the super class's + * implementation of this method. If they do not, an exception will be + * thrown.</em></p> + * + * @see #onCreate + * @see #onStop + * @see #onResume + */ + protected void onStart() { + mCalled = true; + } + + /** + * Called after {@link #onStop} when the current activity is being + * re-displayed to the user (the user has navigated back to it). It will + * be followed by {@link #onStart} and then {@link #onResume}. + * + * <p>For activities that are using raw {@link Cursor} objects (instead of + * creating them through + * {@link #managedQuery(android.net.Uri , String[], String, String[], String)}, + * this is usually the place + * where the cursor should be requeried (because you had deactivated it in + * {@link #onStop}. + * + * <p><em>Derived classes must call through to the super class's + * implementation of this method. If they do not, an exception will be + * thrown.</em></p> + * + * @see #onStop + * @see #onStart + * @see #onResume + */ + protected void onRestart() { + mCalled = true; + } + + /** + * Called after {@link #onRestoreInstanceState}, {@link #onRestart}, or + * {@link #onPause}, for your activity to start interacting with the user. + * This is a good place to begin animations, open exclusive-access devices + * (such as the camera), etc. + * + * <p>Keep in mind that onResume is not the best indicator that your activity + * is visible to the user; a system window such as the keyguard may be in + * front. Use {@link #onWindowFocusChanged} to know for certain that your + * activity is visible to the user (for example, to resume a game). + * + * <p><em>Derived classes must call through to the super class's + * implementation of this method. If they do not, an exception will be + * thrown.</em></p> + * + * @see #onRestoreInstanceState + * @see #onRestart + * @see #onPostResume + * @see #onPause + */ + protected void onResume() { + mCalled = true; + } + + /** + * Called when activity resume is complete (after {@link #onResume} has + * been called). Applications will generally not implement this method; + * it is intended for system classes to do final setup after application + * resume code has run. + * + * <p><em>Derived classes must call through to the super class's + * implementation of this method. If they do not, an exception will be + * thrown.</em></p> + * + * @see #onResume + */ + protected void onPostResume() { + final Window win = getWindow(); + if (win != null) win.makeActive(); + mCalled = true; + } + + /** + * This is called for activities that set launchMode to "singleTop" in + * their package, or if a client used the {@link Intent#FLAG_ACTIVITY_SINGLE_TOP} + * flag when calling {@link #startActivity}. In either case, when the + * activity is re-launched while at the top of the activity stack instead + * of a new instance of the activity being started, onNewIntent() will be + * called on the existing instance with the Intent that was used to + * re-launch it. + * + * <p>An activity will always be paused before receiving a new intent, so + * you can count on {@link #onResume} being called after this method. + * + * <p>Note that {@link #getIntent} still returns the original Intent. You + * can use {@link #setIntent} to update it to this new Intent. + * + * @param intent The new intent that was started for the activity. + * + * @see #getIntent + * @see #setIntent + * @see #onResume + */ + protected void onNewIntent(Intent intent) { + } + + /** + * The hook for {@link ActivityThread} to save the state of this activity. + * + * Calls {@link #onSaveInstanceState(android.os.Bundle)} + * and {@link #saveManagedDialogs(android.os.Bundle)}. + * + * @param outState The bundle to save the state to. + */ + final void performSaveInstanceState(Bundle outState) { + onSaveInstanceState(outState); + saveManagedDialogs(outState); + + // Also save the state of a search dialog (if any) + // TODO more generic than just this manager + SearchManager searchManager = + (SearchManager) getSystemService(Context.SEARCH_SERVICE); + searchManager.saveSearchDialog(outState, SAVED_SEARCH_DIALOG_KEY); + } + + /** + * Called to retrieve per-instance state from an activity before being killed + * so that the state can be restored in {@link #onCreate} or + * {@link #onRestoreInstanceState} (the {@link Bundle} populated by this method + * will be passed to both). + * + * <p>This method is called before an activity may be killed so that when it + * comes back some time in the future it can restore its state. For example, + * if activity B is launched in front of activity A, and at some point activity + * A is killed to reclaim resources, activity A will have a chance to save the + * current state of its user interface via this method so that when the user + * returns to activity A, the state of the user interface can be restored + * via {@link #onCreate} or {@link #onRestoreInstanceState}. + * + * <p>Do not confuse this method with activity lifecycle callbacks such as + * {@link #onPause}, which is always called when an activity is being placed + * in the background or on its way to destruction, or {@link #onStop} which + * is called before destruction. One example of when {@link #onPause} and + * {@link #onStop} is called and not this method is when a user navigates back + * from activity B to activity A: there is no need to call {@link #onSaveInstanceState} + * on B because that particular instance will never be restored, so the + * system avoids calling it. An example when {@link #onPause} is called and + * not {@link #onSaveInstanceState} is when activity B is launched in front of activity A: + * the system may avoid calling {@link #onSaveInstanceState} on activity A if it isn't + * killed during the lifetime of B since the state of the user interface of + * A will stay intact. + * + * <p>The default implementation takes care of most of the UI per-instance + * state for you by calling {@link android.view.View#onSaveInstanceState()} on each + * view in the hierarchy that has an id, and by saving the id of the currently + * focused view (all of which is restored by the default implementation of + * {@link #onRestoreInstanceState}). If you override this method to save additional + * information not captured by each individual view, you will likely want to + * call through to the default implementation, otherwise be prepared to save + * all of the state of each view yourself. + * + * <p>If called, this method will occur before {@link #onStop}. There are + * no guarantees about whether it will occur before or after {@link #onPause}. + * + * @param outState Bundle in which to place your saved state. + * + * @see #onCreate + * @see #onRestoreInstanceState + * @see #onPause + */ + protected void onSaveInstanceState(Bundle outState) { + outState.putBundle(WINDOW_HIERARCHY_TAG, mWindow.saveHierarchyState()); + } + + /** + * Save the state of any managed dialogs. + * + * @param outState place to store the saved state. + */ + private void saveManagedDialogs(Bundle outState) { + if (mManagedDialogs == null) { + return; + } + + final int numDialogs = mManagedDialogs.size(); + if (numDialogs == 0) { + return; + } + + Bundle dialogState = new Bundle(); + + int[] ids = new int[mManagedDialogs.size()]; + + // save each dialog's bundle, gather the ids + for (int i = 0; i < numDialogs; i++) { + final int key = mManagedDialogs.keyAt(i); + ids[i] = key; + final Dialog dialog = mManagedDialogs.valueAt(i); + dialogState.putBundle(savedDialogKeyFor(key), dialog.onSaveInstanceState()); + } + + dialogState.putIntArray(SAVED_DIALOG_IDS_KEY, ids); + outState.putBundle(SAVED_DIALOGS_TAG, dialogState); + } + + + /** + * Called as part of the activity lifecycle when an activity is going into + * the background, but has not (yet) been killed. The counterpart to + * {@link #onResume}. + * + * <p>When activity B is launched in front of activity A, this callback will + * be invoked on A. B will not be created until A's {@link #onPause} returns, + * so be sure to not do anything lengthy here. + * + * <p>This callback is mostly used for saving any persistent state the + * activity is editing, to present a "edit in place" model to the user and + * making sure nothing is lost if there are not enough resources to start + * the new activity without first killing this one. This is also a good + * place to do things like stop animations and other things that consume a + * noticeable mount of CPU in order to make the switch to the next activity + * as fast as possible, or to close resources that are exclusive access + * such as the camera. + * + * <p>In situations where the system needs more memory it may kill paused + * processes to reclaim resources. Because of this, you should be sure + * that all of your state is saved by the time you return from + * this function. In general {@link #onSaveInstanceState} is used to save + * per-instance state in the activity and this method is used to store + * global persistent data (in content providers, files, etc.) + * + * <p>After receiving this call you will usually receive a following call + * to {@link #onStop} (after the next activity has been resumed and + * displayed), however in some cases there will be a direct call back to + * {@link #onResume} without going through the stopped state. + * + * <p><em>Derived classes must call through to the super class's + * implementation of this method. If they do not, an exception will be + * thrown.</em></p> + * + * @see #onResume + * @see #onSaveInstanceState + * @see #onStop + */ + protected void onPause() { + mCalled = true; + } + + /** + * Called as part of the activity lifecycle when an activity is about to go + * into the background as the result of user choice. For example, when the + * user presses the Home key, {@link #onUserLeaveHint} will be called, but + * when an incoming phone call causes the in-call Activity to be automatically + * brought to the foreground, {@link #onUserLeaveHint} will not be called on + * the activity being interrupted. In cases when it is invoked, this method + * is called right before the activity's {@link #onPause} callback. + * + * <p>This callback and {@link #onUserInteraction} are intended to help + * activities manage status bar notifications intelligently; specifically, + * for helping activities determine the proper time to cancel a notfication. + * + * @see #onUserInteraction() + */ + protected void onUserLeaveHint() { + } + + /** + * Generate a new thumbnail for this activity. This method is called before + * pausing the activity, and should draw into <var>outBitmap</var> the + * imagery for the desired thumbnail in the dimensions of that bitmap. It + * can use the given <var>canvas</var>, which is configured to draw into the + * bitmap, for rendering if desired. + * + * <p>The default implementation renders the Screen's current view + * hierarchy into the canvas to generate a thumbnail. + * + * <p>If you return false, the bitmap will be filled with a default + * thumbnail. + * + * @param outBitmap The bitmap to contain the thumbnail. + * @param canvas Can be used to render into the bitmap. + * + * @return Return true if you have drawn into the bitmap; otherwise after + * you return it will be filled with a default thumbnail. + * + * @see #onCreateDescription + * @see #onSaveInstanceState + * @see #onPause + */ + public boolean onCreateThumbnail(Bitmap outBitmap, Canvas canvas) { + final View view = mDecor; + if (view == null) { + return false; + } + + final int vw = view.getWidth(); + final int vh = view.getHeight(); + final int dw = outBitmap.getWidth(); + final int dh = outBitmap.getHeight(); + + canvas.save(); + canvas.scale(((float)dw)/vw, ((float)dh)/vh); + view.draw(canvas); + canvas.restore(); + + return true; + } + + /** + * Generate a new description for this activity. This method is called + * before pausing the activity and can, if desired, return some textual + * description of its current state to be displayed to the user. + * + * <p>The default implementation returns null, which will cause you to + * inherit the description from the previous activity. If all activities + * return null, generally the label of the top activity will be used as the + * description. + * + * @return A description of what the user is doing. It should be short and + * sweet (only a few words). + * + * @see #onCreateThumbnail + * @see #onSaveInstanceState + * @see #onPause + */ + public CharSequence onCreateDescription() { + return null; + } + + /** + * Called when you are no longer visible to the user. You will next + * receive either {@link #onRestart}, {@link #onDestroy}, or nothing, + * depending on later user activity. + * + * <p>Note that this method may never be called, in low memory situations + * where the system does not have enough memory to keep your activity's + * process running after its {@link #onPause} method is called. + * + * <p><em>Derived classes must call through to the super class's + * implementation of this method. If they do not, an exception will be + * thrown.</em></p> + * + * @see #onRestart + * @see #onResume + * @see #onSaveInstanceState + * @see #onDestroy + */ + protected void onStop() { + mCalled = true; + } + + /** + * Perform any final cleanup before an activity is destroyed. This can + * happen either because the activity is finishing (someone called + * {@link #finish} on it, or because the system is temporarily destroying + * this instance of the activity to save space. You can distinguish + * between these two scenarios with the {@link #isFinishing} method. + * + * <p><em>Note: do not count on this method being called as a place for + * saving data! For example, if an activity is editing data in a content + * provider, those edits should be committed in either {@link #onPause} or + * {@link #onSaveInstanceState}, not here.</em> This method is usually implemented to + * free resources like threads that are associated with an activity, so + * that a destroyed activity does not leave such things around while the + * rest of its application is still running. There are situations where + * the system will simply kill the activity's hosting process without + * calling this method (or any others) in it, so it should not be used to + * do things that are intended to remain around after the process goes + * away. + * + * <p><em>Derived classes must call through to the super class's + * implementation of this method. If they do not, an exception will be + * thrown.</em></p> + * + * @see #onPause + * @see #onStop + * @see #finish + * @see #isFinishing + */ + protected void onDestroy() { + mCalled = true; + + // dismiss any dialogs we are managing. + if (mManagedDialogs != null) { + + final int numDialogs = mManagedDialogs.size(); + for (int i = 0; i < numDialogs; i++) { + final Dialog dialog = mManagedDialogs.valueAt(i); + if (dialog.isShowing()) { + dialog.dismiss(); + } + } + } + + // also dismiss search dialog if showing + // TODO more generic than just this manager + SearchManager searchManager = + (SearchManager) getSystemService(Context.SEARCH_SERVICE); + searchManager.stopSearch(); + + // close any cursors we are managing. + int numCursors = mManagedCursors.size(); + for (int i = 0; i < numCursors; i++) { + ManagedCursor c = mManagedCursors.get(i); + if (c != null) { + c.mCursor.close(); + } + } + } + + /** + * Called by the system when the device configuration changes while your + * activity is running. Note that this will <em>only</em> be called if + * you have selected configurations you would like to handle with the + * {@link android.R.attr#configChanges} attribute in your manifest. If + * any configuration change occurs that is not selected to be reported + * by that attribute, then instead of reporting it the system will stop + * and restart the activity (to have it launched with the new + * configuration). + * + * <p>At the time that this function has been called, your Resources + * object will have been updated to return resource values matching the + * new configuration. + * + * @param newConfig The new device configuration. + */ + public void onConfigurationChanged(Configuration newConfig) { + mCalled = true; + + // also update search dialog if showing + // TODO more generic than just this manager + SearchManager searchManager = + (SearchManager) getSystemService(Context.SEARCH_SERVICE); + searchManager.onConfigurationChanged(newConfig); + + if (mWindow != null) { + // Pass the configuration changed event to the window + mWindow.onConfigurationChanged(newConfig); + } + } + + /** + * If this activity is being destroyed because it can not handle a + * configuration parameter being changed (and thus its + * {@link #onConfigurationChanged(Configuration)} method is + * <em>not</em> being called), then you can use this method to discover + * the set of changes that have occurred while in the process of being + * destroyed. Note that there is no guarantee that these will be + * accurate (other changes could have happened at any time), so you should + * only use this as an optimization hint. + * + * @return Returns a bit field of the configuration parameters that are + * changing, as defined by the {@link android.content.res.Configuration} + * class. + */ + public int getChangingConfigurations() { + return mConfigChangeFlags; + } + + /** + * Retrieve the non-configuration instance data that was previously + * returned by {@link #onRetainNonConfigurationInstance()}. This will + * be available from the initial {@link #onCreate} and + * {@link #onStart} calls to the new instance, allowing you to extract + * any useful dynamic state from the previous instance. + * + * <p>Note that the data you retrieve here should <em>only</em> be used + * as an optimization for handling configuration changes. You should always + * be able to handle getting a null pointer back, and an activity must + * still be able to restore itself to its previous state (through the + * normal {@link #onSaveInstanceState(Bundle)} mechanism) even if this + * function returns null. + * + * @return Returns the object previously returned by + * {@link #onRetainNonConfigurationInstance()}. + */ + public Object getLastNonConfigurationInstance() { + return mLastNonConfigurationInstance; + } + + /** + * Called by the system, as part of destroying an + * activity due to a configuration change, when it is known that a new + * instance will immediately be created for the new configuration. You + * can return any object you like here, including the activity instance + * itself, which can later be retrieved by calling + * {@link #getLastNonConfigurationInstance()} in the new activity + * instance. + * + * <p>This function is called purely as an optimization, and you must + * not rely on it being called. When it is called, a number of guarantees + * will be made to help optimize configuration switching: + * <ul> + * <li> The function will be called between {@link #onStop} and + * {@link #onDestroy}. + * <li> A new instance of the activity will <em>always</em> be immediately + * created after this one's {@link #onDestroy()} is called. + * <li> The object you return here will <em>always</em> be available from + * the {@link #getLastNonConfigurationInstance()} method of the following + * activity instance as described there. + * </ul> + * + * <p>These guarantees are designed so that an activity can use this API + * to propagate extensive state from the old to new activity instance, from + * loaded bitmaps, to network connections, to evenly actively running + * threads. Note that you should <em>not</em> propagate any data that + * may change based on the configuration, including any data loaded from + * resources such as strings, layouts, or drawables. + * + * @return Return any Object holding the desired state to propagate to the + * next activity instance. + */ + public Object onRetainNonConfigurationInstance() { + return null; + } + + /** + * Retrieve the non-configuration instance data that was previously + * returned by {@link #onRetainNonConfigurationChildInstances()}. This will + * be available from the initial {@link #onCreate} and + * {@link #onStart} calls to the new instance, allowing you to extract + * any useful dynamic state from the previous instance. + * + * <p>Note that the data you retrieve here should <em>only</em> be used + * as an optimization for handling configuration changes. You should always + * be able to handle getting a null pointer back, and an activity must + * still be able to restore itself to its previous state (through the + * normal {@link #onSaveInstanceState(Bundle)} mechanism) even if this + * function returns null. + * + * @return Returns the object previously returned by + * {@link #onRetainNonConfigurationChildInstances()} + */ + HashMap<String,Object> getLastNonConfigurationChildInstances() { + return mLastNonConfigurationChildInstances; + } + + /** + * This method is similar to {@link #onRetainNonConfigurationInstance()} except that + * it should return either a mapping from child activity id strings to arbitrary objects, + * or null. This method is intended to be used by Activity framework subclasses that control a + * set of child activities, such as ActivityGroup. The same guarantees and restrictions apply + * as for {@link #onRetainNonConfigurationInstance()}. The default implementation returns null. + */ + HashMap<String,Object> onRetainNonConfigurationChildInstances() { + return null; + } + + public void onLowMemory() { + mCalled = true; + } + + /** + * Wrapper around + * {@link ContentResolver#query(android.net.Uri , String[], String, String[], String)} + * that gives the resulting {@link Cursor} to call + * {@link #startManagingCursor} so that the activity will manage its + * lifecycle for you. + * + * @param uri The URI of the content provider to query. + * @param projection List of columns to return. + * @param selection SQL WHERE clause. + * @param sortOrder SQL ORDER BY clause. + * + * @return The Cursor that was returned by query(). + * + * @see ContentResolver#query(android.net.Uri , String[], String, String[], String) + * @see #startManagingCursor + * @hide + */ + public final Cursor managedQuery(Uri uri, + String[] projection, + String selection, + String sortOrder) + { + Cursor c = getContentResolver().query(uri, projection, selection, null, sortOrder); + if (c != null) { + startManagingCursor(c); + } + return c; + } + + /** + * Wrapper around + * {@link ContentResolver#query(android.net.Uri , String[], String, String[], String)} + * that gives the resulting {@link Cursor} to call + * {@link #startManagingCursor} so that the activity will manage its + * lifecycle for you. + * + * @param uri The URI of the content provider to query. + * @param projection List of columns to return. + * @param selection SQL WHERE clause. + * @param selectionArgs The arguments to selection, if any ?s are pesent + * @param sortOrder SQL ORDER BY clause. + * + * @return The Cursor that was returned by query(). + * + * @see ContentResolver#query(android.net.Uri , String[], String, String[], String) + * @see #startManagingCursor + */ + public final Cursor managedQuery(Uri uri, + String[] projection, + String selection, + String[] selectionArgs, + String sortOrder) + { + Cursor c = getContentResolver().query(uri, projection, selection, selectionArgs, sortOrder); + if (c != null) { + startManagingCursor(c); + } + return c; + } + + /** + * Wrapper around {@link Cursor#commitUpdates()} that takes care of noting + * that the Cursor needs to be requeried. You can call this method in + * {@link #onPause} or {@link #onStop} to have the system call + * {@link Cursor#requery} for you if the activity is later resumed. This + * allows you to avoid determing when to do the requery yourself (which is + * required for the Cursor to see any data changes that were committed with + * it). + * + * @param c The Cursor whose changes are to be committed. + * + * @see #managedQuery(android.net.Uri , String[], String, String[], String) + * @see #startManagingCursor + * @see Cursor#commitUpdates() + * @see Cursor#requery + * @hide + */ + @Deprecated + public void managedCommitUpdates(Cursor c) { + synchronized (mManagedCursors) { + final int N = mManagedCursors.size(); + for (int i=0; i<N; i++) { + ManagedCursor mc = mManagedCursors.get(i); + if (mc.mCursor == c) { + c.commitUpdates(); + mc.mUpdated = true; + return; + } + } + throw new RuntimeException( + "Cursor " + c + " is not currently managed"); + } + } + + /** + * This method allows the activity to take care of managing the given + * {@link Cursor}'s lifecycle for you based on the activity's lifecycle. + * That is, when the activity is stopped it will automatically call + * {@link Cursor#deactivate} on the given Cursor, and when it is later restarted + * it will call {@link Cursor#requery} for you. When the activity is + * destroyed, all managed Cursors will be closed automatically. + * + * @param c The Cursor to be managed. + * + * @see #managedQuery(android.net.Uri , String[], String, String[], String) + * @see #stopManagingCursor + */ + public void startManagingCursor(Cursor c) { + synchronized (mManagedCursors) { + mManagedCursors.add(new ManagedCursor(c)); + } + } + + /** + * Given a Cursor that was previously given to + * {@link #startManagingCursor}, stop the activity's management of that + * cursor. + * + * @param c The Cursor that was being managed. + * + * @see #startManagingCursor + */ + public void stopManagingCursor(Cursor c) { + synchronized (mManagedCursors) { + final int N = mManagedCursors.size(); + for (int i=0; i<N; i++) { + ManagedCursor mc = mManagedCursors.get(i); + if (mc.mCursor == c) { + mManagedCursors.remove(i); + break; + } + } + } + } + + /** + * Control whether this activity is required to be persistent. By default + * activities are not persistent; setting this to true will prevent the + * system from stopping this activity or its process when running low on + * resources. + * + * <p><em>You should avoid using this method</em>, it has severe negative + * consequences on how well the system can manage its resources. A better + * approach is to implement an application service that you control with + * {@link Context#startService} and {@link Context#stopService}. + * + * @param isPersistent Control whether the current activity must be + * persistent, true if so, false for the normal + * behavior. + */ + public void setPersistent(boolean isPersistent) { + if (mParent == null) { + try { + ActivityManagerNative.getDefault() + .setPersistent(mToken, isPersistent); + } catch (RemoteException e) { + // Empty + } + } else { + throw new RuntimeException("setPersistent() not yet supported for embedded activities"); + } + } + + /** + * Finds a view that was identified by the id attribute from the XML that + * was processed in {@link #onCreate}. + * + * @return The view if found or null otherwise. + */ + public View findViewById(int id) { + return getWindow().findViewById(id); + } + + /** + * Set the activity content from a layout resource. The resource will be + * inflated, adding all top-level views to the activity. + * + * @param layoutResID Resource ID to be inflated. + */ + public void setContentView(int layoutResID) { + getWindow().setContentView(layoutResID); + } + + /** + * Set the activity content to an explicit view. This view is placed + * directly into the activity's view hierarchy. It can itself be a complex + * view hierarhcy. + * + * @param view The desired content to display. + */ + public void setContentView(View view) { + getWindow().setContentView(view); + } + + /** + * Set the activity content to an explicit view. This view is placed + * directly into the activity's view hierarchy. It can itself be a complex + * view hierarhcy. + * + * @param view The desired content to display. + * @param params Layout parameters for the view. + */ + public void setContentView(View view, ViewGroup.LayoutParams params) { + getWindow().setContentView(view, params); + } + + /** + * Add an additional content view to the activity. Added after any existing + * ones in the activity -- existing views are NOT removed. + * + * @param view The desired content to display. + * @param params Layout parameters for the view. + */ + public void addContentView(View view, ViewGroup.LayoutParams params) { + getWindow().addContentView(view, params); + } + + /** + * Use with {@link #setDefaultKeyMode} to turn off default handling of + * keys. + * + * @see #setDefaultKeyMode + */ + static public final int DEFAULT_KEYS_DISABLE = 0; + /** + * Use with {@link #setDefaultKeyMode} to launch the dialer during default + * key handling. + * + * @see #setDefaultKeyMode + */ + static public final int DEFAULT_KEYS_DIALER = 1; + /** + * Use with {@link #setDefaultKeyMode} to execute a menu shortcut in + * default key handling. + * + * <p>That is, the user does not need to hold down the menu key to execute menu shortcuts. + * + * @see #setDefaultKeyMode + */ + static public final int DEFAULT_KEYS_SHORTCUT = 2; + /** + * Use with {@link #setDefaultKeyMode} to specify that unhandled keystrokes + * will start an application-defined search. (If the application or activity does not + * actually define a search, the the keys will be ignored.) + * + * <p>See {@link android.app.SearchManager android.app.SearchManager} for more details. + * + * @see #setDefaultKeyMode + */ + static public final int DEFAULT_KEYS_SEARCH_LOCAL = 3; + + /** + * Use with {@link #setDefaultKeyMode} to specify that unhandled keystrokes + * will start a global search (typically web search, but some platforms may define alternate + * methods for global search) + * + * <p>See {@link android.app.SearchManager android.app.SearchManager} for more details. + * + * @see #setDefaultKeyMode + */ + static public final int DEFAULT_KEYS_SEARCH_GLOBAL = 4; + + /** + * Select the default key handling for this activity. This controls what + * will happen to key events that are not otherwise handled. The default + * mode ({@link #DEFAULT_KEYS_DISABLE}) will simply drop them on the + * floor. Other modes allow you to launch the dialer + * ({@link #DEFAULT_KEYS_DIALER}), execute a shortcut in your options + * menu without requiring the menu key be held down + * ({@link #DEFAULT_KEYS_SHORTCUT}), or launch a search ({@link #DEFAULT_KEYS_SEARCH_LOCAL} + * and {@link #DEFAULT_KEYS_SEARCH_GLOBAL}). + * + * <p>Note that the mode selected here does not impact the default + * handling of system keys, such as the "back" and "menu" keys, and your + * activity and its views always get a first chance to receive and handle + * all application keys. + * + * @param mode The desired default key mode constant. + * + * @see #DEFAULT_KEYS_DISABLE + * @see #DEFAULT_KEYS_DIALER + * @see #DEFAULT_KEYS_SHORTCUT + * @see #DEFAULT_KEYS_SEARCH_LOCAL + * @see #DEFAULT_KEYS_SEARCH_GLOBAL + * @see #onKeyDown + */ + public final void setDefaultKeyMode(int mode) { + mDefaultKeyMode = mode; + + // Some modes use a SpannableStringBuilder to track & dispatch input events + // This list must remain in sync with the switch in onKeyDown() + switch (mode) { + case DEFAULT_KEYS_DISABLE: + case DEFAULT_KEYS_SHORTCUT: + mDefaultKeySsb = null; // not used in these modes + break; + case DEFAULT_KEYS_DIALER: + case DEFAULT_KEYS_SEARCH_LOCAL: + case DEFAULT_KEYS_SEARCH_GLOBAL: + mDefaultKeySsb = new SpannableStringBuilder(); + Selection.setSelection(mDefaultKeySsb,0); + break; + default: + throw new IllegalArgumentException(); + } + } + + /** + * Called when a key was pressed down and not handled by any of the views + * inside of the activity. So, for example, key presses while the cursor + * is inside a TextView will not trigger the event (unless it is a navigation + * to another object) because TextView handles its own key presses. + * + * <p>If the focused view didn't want this event, this method is called. + * + * <p>The default implementation handles KEYCODE_BACK to stop the activity + * and go back, and other default key handling if configured with {@link #setDefaultKeyMode}. + * + * @return Return <code>true</code> to prevent this event from being propagated + * further, or <code>false</code> to indicate that you have not handled + * this event and it should continue to be propagated. + * @see #onKeyUp + * @see android.view.KeyEvent + */ + public boolean onKeyDown(int keyCode, KeyEvent event) { + if (keyCode == KeyEvent.KEYCODE_BACK && event.getRepeatCount() == 0) { + finish(); + return true; + } + + if (mDefaultKeyMode == DEFAULT_KEYS_DISABLE) { + return false; + } else if (mDefaultKeyMode == DEFAULT_KEYS_SHORTCUT) { + return getWindow().performPanelShortcut(Window.FEATURE_OPTIONS_PANEL, + keyCode, event, Menu.FLAG_ALWAYS_PERFORM_CLOSE); + } else { + // Common code for DEFAULT_KEYS_DIALER & DEFAULT_KEYS_SEARCH_* + boolean clearSpannable = false; + boolean handled; + if ((event.getRepeatCount() != 0) || event.isSystem()) { + clearSpannable = true; + handled = false; + } else { + handled = TextKeyListener.getInstance().onKeyDown(null, mDefaultKeySsb, + keyCode, event); + if (handled && mDefaultKeySsb.length() > 0) { + // something useable has been typed - dispatch it now. + + final String str = mDefaultKeySsb.toString(); + clearSpannable = true; + + switch (mDefaultKeyMode) { + case DEFAULT_KEYS_DIALER: + Intent intent = new Intent(Intent.ACTION_DIAL, Uri.parse("tel:" + str)); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + startActivity(intent); + break; + case DEFAULT_KEYS_SEARCH_LOCAL: + startSearch(str, false, null, false); + break; + case DEFAULT_KEYS_SEARCH_GLOBAL: + startSearch(str, false, null, true); + break; + } + } + } + if (clearSpannable) { + mDefaultKeySsb.clear(); + mDefaultKeySsb.clearSpans(); + Selection.setSelection(mDefaultKeySsb,0); + } + return handled; + } + } + + /** + * Called when a key was released and not handled by any of the views + * inside of the activity. So, for example, key presses while the cursor + * is inside a TextView will not trigger the event (unless it is a navigation + * to another object) because TextView handles its own key presses. + * + * @return Return <code>true</code> to prevent this event from being propagated + * further, or <code>false</code> to indicate that you have not handled + * this event and it should continue to be propagated. + * @see #onKeyDown + * @see KeyEvent + */ + public boolean onKeyUp(int keyCode, KeyEvent event) { + return false; + } + + /** + * Default implementation of {@link KeyEvent.Callback#onKeyMultiple(int, int, KeyEvent) + * KeyEvent.Callback.onKeyMultiple()}: always returns false (doesn't handle + * the event). + */ + public boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event) { + return false; + } + + /** + * Called when a touch screen event was not handled by any of the views + * under it. This is most useful to process touch events that happen + * outside of your window bounds, where there is no view to receive it. + * + * @param event The touch screen event being processed. + * + * @return Return true if you have consumed the event, false if you haven't. + * The default implementation always returns false. + */ + public boolean onTouchEvent(MotionEvent event) { + return false; + } + + /** + * Called when the trackball was moved and not handled by any of the + * views inside of the activity. So, for example, if the trackball moves + * while focus is on a button, you will receive a call here because + * buttons do not normally do anything with trackball events. The call + * here happens <em>before</em> trackball movements are converted to + * DPAD key events, which then get sent back to the view hierarchy, and + * will be processed at the point for things like focus navigation. + * + * @param event The trackball event being processed. + * + * @return Return true if you have consumed the event, false if you haven't. + * The default implementation always returns false. + */ + public boolean onTrackballEvent(MotionEvent event) { + return false; + } + + /** + * Called whenever a key, touch, or trackball event is dispatched to the + * activity. Implement this method if you wish to know that the user has + * interacted with the device in some way while your activity is running. + * This callback and {@link #onUserLeaveHint} are intended to help + * activities manage status bar notifications intelligently; specifically, + * for helping activities determine the proper time to cancel a notfication. + * + * <p>All calls to your activity's {@link #onUserLeaveHint} callback will + * be accompanied by calls to {@link #onUserInteraction}. This + * ensures that your activity will be told of relevant user activity such + * as pulling down the notification pane and touching an item there. + * + * <p>Note that this callback will be invoked for the touch down action + * that begins a touch gesture, but may not be invoked for the touch-moved + * and touch-up actions that follow. + * + * @see #onUserLeaveHint() + */ + public void onUserInteraction() { + } + + public void onWindowAttributesChanged(WindowManager.LayoutParams params) { + // Update window manager if: we have a view, that view is + // attached to its parent (which will be a RootView), and + // this activity is not embedded. + if (mParent == null) { + View decor = mDecor; + if (decor != null && decor.getParent() != null) { + getWindowManager().updateViewLayout(decor, params); + } + } + } + + public void onContentChanged() { + } + + /** + * Called when the current {@link Window} of the activity gains or loses + * focus. This is the best indicator of whether this activity is visible + * to the user. + * + * <p>Note that this provides information what global focus state, which + * is managed independently of activity lifecycles. As such, while focus + * changes will generally have some relation to lifecycle changes (an + * activity that is stopped will not generally get window focus), you + * should not rely on any particular order between the callbacks here and + * those in the other lifecycle methods such as {@link #onResume}. + * + * <p>As a general rule, however, a resumed activity will have window + * focus... unless it has displayed other dialogs or popups that take + * input focus, in which case the activity itself will not have focus + * when the other windows have it. Likewise, the system may display + * system-level windows (such as the status bar notification panel or + * a system alert) which will temporarily take window input focus without + * pausing the foreground activity. + * + * @param hasFocus Whether the window of this activity has focus. + * + * @see #hasWindowFocus() + * @see #onResume + */ + public void onWindowFocusChanged(boolean hasFocus) { + } + + /** + * Returns true if this activity's <em>main</em> window currently has window focus. + * Note that this is not the same as the view itself having focus. + * + * @return True if this activity's main window currently has window focus. + * + * @see #onWindowAttributesChanged(android.view.WindowManager.LayoutParams) + */ + public boolean hasWindowFocus() { + Window w = getWindow(); + if (w != null) { + View d = w.getDecorView(); + if (d != null) { + return d.hasWindowFocus(); + } + } + return false; + } + + /** + * Called to process key events. You can override this to intercept all + * key events before they are dispatched to the window. Be sure to call + * this implementation for key events that should be handled normally. + * + * @param event The key event. + * + * @return boolean Return true if this event was consumed. + */ + public boolean dispatchKeyEvent(KeyEvent event) { + onUserInteraction(); + if (getWindow().superDispatchKeyEvent(event)) { + return true; + } + return event.dispatch(this); + } + + /** + * Called to process touch screen events. You can override this to + * intercept all touch screen events before they are dispatched to the + * window. Be sure to call this implementation for touch screen events + * that should be handled normally. + * + * @param ev The touch screen event. + * + * @return boolean Return true if this event was consumed. + */ + public boolean dispatchTouchEvent(MotionEvent ev) { + if (ev.getAction() == MotionEvent.ACTION_DOWN) { + onUserInteraction(); + } + if (getWindow().superDispatchTouchEvent(ev)) { + return true; + } + return onTouchEvent(ev); + } + + /** + * Called to process trackball events. You can override this to + * intercept all trackball events before they are dispatched to the + * window. Be sure to call this implementation for trackball events + * that should be handled normally. + * + * @param ev The trackball event. + * + * @return boolean Return true if this event was consumed. + */ + public boolean dispatchTrackballEvent(MotionEvent ev) { + onUserInteraction(); + if (getWindow().superDispatchTrackballEvent(ev)) { + return true; + } + return onTrackballEvent(ev); + } + + /** + * Default implementation of + * {@link android.view.Window.Callback#onCreatePanelView} + * for activities. This + * simply returns null so that all panel sub-windows will have the default + * menu behavior. + */ + public View onCreatePanelView(int featureId) { + return null; + } + + /** + * Default implementation of + * {@link android.view.Window.Callback#onCreatePanelMenu} + * for activities. This calls through to the new + * {@link #onCreateOptionsMenu} method for the + * {@link android.view.Window#FEATURE_OPTIONS_PANEL} panel, + * so that subclasses of Activity don't need to deal with feature codes. + */ + public boolean onCreatePanelMenu(int featureId, Menu menu) { + if (featureId == Window.FEATURE_OPTIONS_PANEL) { + return onCreateOptionsMenu(menu); + } + return false; + } + + /** + * Default implementation of + * {@link android.view.Window.Callback#onPreparePanel} + * for activities. This + * calls through to the new {@link #onPrepareOptionsMenu} method for the + * {@link android.view.Window#FEATURE_OPTIONS_PANEL} + * panel, so that subclasses of + * Activity don't need to deal with feature codes. + */ + public boolean onPreparePanel(int featureId, View view, Menu menu) { + if (featureId == Window.FEATURE_OPTIONS_PANEL && menu != null) { + boolean goforit = onPrepareOptionsMenu(menu); + return goforit && menu.hasVisibleItems(); + } + return true; + } + + /** + * {@inheritDoc} + * + * @return The default implementation returns true. + */ + public boolean onMenuOpened(int featureId, Menu menu) { + return true; + } + + /** + * Default implementation of + * {@link android.view.Window.Callback#onMenuItemSelected} + * for activities. This calls through to the new + * {@link #onOptionsItemSelected} method for the + * {@link android.view.Window#FEATURE_OPTIONS_PANEL} + * panel, so that subclasses of + * Activity don't need to deal with feature codes. + */ + public boolean onMenuItemSelected(int featureId, MenuItem item) { + switch (featureId) { + case Window.FEATURE_OPTIONS_PANEL: + // Put event logging here so it gets called even if subclass + // doesn't call through to superclass's implmeentation of each + // of these methods below + EventLog.writeEvent(50000, 0, item.getTitleCondensed()); + return onOptionsItemSelected(item); + + case Window.FEATURE_CONTEXT_MENU: + EventLog.writeEvent(50000, 1, item.getTitleCondensed()); + return onContextItemSelected(item); + + default: + return false; + } + } + + /** + * Default implementation of + * {@link android.view.Window.Callback#onPanelClosed(int, Menu)} for + * activities. This calls through to {@link #onOptionsMenuClosed(Menu)} + * method for the {@link android.view.Window#FEATURE_OPTIONS_PANEL} panel, + * so that subclasses of Activity don't need to deal with feature codes. + * For context menus ({@link Window#FEATURE_CONTEXT_MENU}), the + * {@link #onContextMenuClosed(Menu)} will be called. + */ + public void onPanelClosed(int featureId, Menu menu) { + switch (featureId) { + case Window.FEATURE_OPTIONS_PANEL: + onOptionsMenuClosed(menu); + break; + + case Window.FEATURE_CONTEXT_MENU: + onContextMenuClosed(menu); + break; + } + } + + /** + * Initialize the contents of the Activity's standard options menu. You + * should place your menu items in to <var>menu</var>. + * + * <p>This is only called once, the first time the options menu is + * displayed. To update the menu every time it is displayed, see + * {@link #onPrepareOptionsMenu}. + * + * <p>The default implementation populates the menu with standard system + * menu items. These are placed in the {@link Menu#CATEGORY_SYSTEM} group so that + * they will be correctly ordered with application-defined menu items. + * Deriving classes should always call through to the base implementation. + * + * <p>You can safely hold on to <var>menu</var> (and any items created + * from it), making modifications to it as desired, until the next + * time onCreateOptionsMenu() is called. + * + * <p>When you add items to the menu, you can implement the Activity's + * {@link #onOptionsItemSelected} method to handle them there. + * + * @param menu The options menu in which you place your items. + * + * @return You must return true for the menu to be displayed; + * if you return false it will not be shown. + * + * @see #onPrepareOptionsMenu + * @see #onOptionsItemSelected + */ + public boolean onCreateOptionsMenu(Menu menu) { + if (mParent != null) { + return mParent.onCreateOptionsMenu(menu); + } + return true; + } + + /** + * Prepare the Screen's standard options menu to be displayed. This is + * called right before the menu is shown, every time it is shown. You can + * use this method to efficiently enable/disable items or otherwise + * dynamically modify the contents. + * + * <p>The default implementation updates the system menu items based on the + * activity's state. Deriving classes should always call through to the + * base class implementation. + * + * @param menu The options menu as last shown or first initialized by + * onCreateOptionsMenu(). + * + * @return You must return true for the menu to be displayed; + * if you return false it will not be shown. + * + * @see #onCreateOptionsMenu + */ + public boolean onPrepareOptionsMenu(Menu menu) { + if (mParent != null) { + return mParent.onPrepareOptionsMenu(menu); + } + return true; + } + + /** + * This hook is called whenever an item in your options menu is selected. + * The default implementation simply returns false to have the normal + * processing happen (calling the item's Runnable or sending a message to + * its Handler as appropriate). You can use this method for any items + * for which you would like to do processing without those other + * facilities. + * + * <p>Derived classes should call through to the base class for it to + * perform the default menu handling. + * + * @param item The menu item that was selected. + * + * @return boolean Return false to allow normal menu processing to + * proceed, true to consume it here. + * + * @see #onCreateOptionsMenu + */ + public boolean onOptionsItemSelected(MenuItem item) { + if (mParent != null) { + return mParent.onOptionsItemSelected(item); + } + return false; + } + + /** + * This hook is called whenever the options menu is being closed (either by the user canceling + * the menu with the back/menu button, or when an item is selected). + * + * @param menu The options menu as last shown or first initialized by + * onCreateOptionsMenu(). + */ + public void onOptionsMenuClosed(Menu menu) { + if (mParent != null) { + mParent.onOptionsMenuClosed(menu); + } + } + + /** + * Programmatically opens the options menu. If the options menu is already + * open, this method does nothing. + */ + public void openOptionsMenu() { + mWindow.openPanel(Window.FEATURE_OPTIONS_PANEL, null); + } + + /** + * Progammatically closes the options menu. If the options menu is already + * closed, this method does nothing. + */ + public void closeOptionsMenu() { + mWindow.closePanel(Window.FEATURE_OPTIONS_PANEL); + } + + /** + * Called when a context menu for the {@code view} is about to be shown. + * Unlike {@link #onCreateOptionsMenu(Menu)}, this will be called every + * time the context menu is about to be shown and should be populated for + * the view (or item inside the view for {@link AdapterView} subclasses, + * this can be found in the {@code menuInfo})). + * <p> + * Use {@link #onContextItemSelected(android.view.MenuItem)} to know when an + * item has been selected. + * <p> + * It is not safe to hold onto the context menu after this method returns. + * {@inheritDoc} + */ + public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) { + } + + /** + * Registers a context menu to be shown for the given view (multiple views + * can show the context menu). This method will set the + * {@link OnCreateContextMenuListener} on the view to this activity, so + * {@link #onCreateContextMenu(ContextMenu, View, ContextMenuInfo)} will be + * called when it is time to show the context menu. + * + * @see #unregisterForContextMenu(View) + * @param view The view that should show a context menu. + */ + public void registerForContextMenu(View view) { + view.setOnCreateContextMenuListener(this); + } + + /** + * Prevents a context menu to be shown for the given view. This method will remove the + * {@link OnCreateContextMenuListener} on the view. + * + * @see #registerForContextMenu(View) + * @param view The view that should stop showing a context menu. + */ + public void unregisterForContextMenu(View view) { + view.setOnCreateContextMenuListener(null); + } + + /** + * Programmatically opens the context menu for a particular {@code view}. + * The {@code view} should have been added via + * {@link #registerForContextMenu(View)}. + * + * @param view The view to show the context menu for. + */ + public void openContextMenu(View view) { + view.showContextMenu(); + } + + /** + * Programmatically closes the most recently opened context menu, if showing. + */ + public void closeContextMenu() { + mWindow.closePanel(Window.FEATURE_CONTEXT_MENU); + } + + /** + * This hook is called whenever an item in a context menu is selected. The + * default implementation simply returns false to have the normal processing + * happen (calling the item's Runnable or sending a message to its Handler + * as appropriate). You can use this method for any items for which you + * would like to do processing without those other facilities. + * <p> + * Use {@link MenuItem#getMenuInfo()} to get extra information set by the + * View that added this menu item. + * <p> + * Derived classes should call through to the base class for it to perform + * the default menu handling. + * + * @param item The context menu item that was selected. + * @return boolean Return false to allow normal context menu processing to + * proceed, true to consume it here. + */ + public boolean onContextItemSelected(MenuItem item) { + if (mParent != null) { + return mParent.onContextItemSelected(item); + } + return false; + } + + /** + * This hook is called whenever the context menu is being closed (either by + * the user canceling the menu with the back/menu button, or when an item is + * selected). + * + * @param menu The context menu that is being closed. + */ + public void onContextMenuClosed(Menu menu) { + if (mParent != null) { + mParent.onContextMenuClosed(menu); + } + } + + /** + * Callback for creating dialogs that are managed (saved and restored) for you + * by the activity. + * + * If you use {@link #showDialog(int)}, the activity will call through to + * this method the first time, and hang onto it thereafter. Any dialog + * that is created by this method will automatically be saved and restored + * for you, including whether it is showing. + * + * If you would like the activity to manage the saving and restoring dialogs + * for you, you should override this method and handle any ids that are + * passed to {@link #showDialog}. + * + * If you would like an opportunity to prepare your dialog before it is shown, + * override {@link #onPrepareDialog(int, Dialog)}. + * + * @param id The id of the dialog. + * @return The dialog + * + * @see #onPrepareDialog(int, Dialog) + * @see #showDialog(int) + * @see #dismissDialog(int) + * @see #removeDialog(int) + */ + protected Dialog onCreateDialog(int id) { + return null; + } + + /** + * Provides an opportunity to prepare a managed dialog before it is being + * shown. + * <p> + * Override this if you need to update a managed dialog based on the state + * of the application each time it is shown. For example, a time picker + * dialog might want to be updated with the current time. You should call + * through to the superclass's implementation. The default implementation + * will set this Activity as the owner activity on the Dialog. + * + * @param id The id of the managed dialog. + * @param dialog The dialog. + * @see #onCreateDialog(int) + * @see #showDialog(int) + * @see #dismissDialog(int) + * @see #removeDialog(int) + */ + protected void onPrepareDialog(int id, Dialog dialog) { + dialog.setOwnerActivity(this); + } + + /** + * Show a dialog managed by this activity. A call to {@link #onCreateDialog(int)} + * will be made with the same id the first time this is called for a given + * id. From thereafter, the dialog will be automatically saved and restored. + * + * Each time a dialog is shown, {@link #onPrepareDialog(int, Dialog)} will + * be made to provide an opportunity to do any timely preparation. + * + * @param id The id of the managed dialog. + * + * @see #onCreateDialog(int) + * @see #onPrepareDialog(int, Dialog) + * @see #dismissDialog(int) + * @see #removeDialog(int) + */ + public final void showDialog(int id) { + if (mManagedDialogs == null) { + mManagedDialogs = new SparseArray<Dialog>(); + } + Dialog dialog = mManagedDialogs.get(id); + if (dialog == null) { + dialog = onCreateDialog(id); + if (dialog == null) { + throw new IllegalArgumentException("Activity#onCreateDialog did " + + "not create a dialog for id " + id); + } + dialog.dispatchOnCreate(null); + mManagedDialogs.put(id, dialog); + } + + onPrepareDialog(id, dialog); + dialog.show(); + } + + /** + * Dismiss a dialog that was previously shown via {@link #showDialog(int)}. + * + * @param id The id of the managed dialog. + * + * @throws IllegalArgumentException if the id was not previously shown via + * {@link #showDialog(int)}. + * + * @see #onCreateDialog(int) + * @see #onPrepareDialog(int, Dialog) + * @see #showDialog(int) + * @see #removeDialog(int) + */ + public final void dismissDialog(int id) { + if (mManagedDialogs == null) { + throw missingDialog(id); + + } + final Dialog dialog = mManagedDialogs.get(id); + if (dialog == null) { + throw missingDialog(id); + } + dialog.dismiss(); + } + + /** + * Creates an exception to throw if a user passed in a dialog id that is + * unexpected. + */ + private IllegalArgumentException missingDialog(int id) { + return new IllegalArgumentException("no dialog with id " + id + " was ever " + + "shown via Activity#showDialog"); + } + + /** + * Removes any internal references to a dialog managed by this Activity. + * If the dialog is showing, it will dismiss it as part of the clean up. + * + * This can be useful if you know that you will never show a dialog again and + * want to avoid the overhead of saving and restoring it in the future. + * + * @param id The id of the managed dialog. + * + * @see #onCreateDialog(int) + * @see #onPrepareDialog(int, Dialog) + * @see #showDialog(int) + * @see #dismissDialog(int) + */ + public final void removeDialog(int id) { + + if (mManagedDialogs == null) { + return; + } + + final Dialog dialog = mManagedDialogs.get(id); + if (dialog == null) { + return; + } + + dialog.dismiss(); + mManagedDialogs.remove(id); + } + + /** + * This hook is called when the user signals the desire to start a search. + * + * <p>You can use this function as a simple way to launch the search UI, in response to a + * menu item, search button, or other widgets within your activity. Unless overidden, + * calling this function is the same as calling: + * <p>The default implementation simply calls + * {@link #startSearch startSearch(null, false, null, false)}, launching a local search. + * + * <p>You can override this function to force global search, e.g. in response to a dedicated + * search key, or to block search entirely (by simply returning false). + * + * @return Returns true if search launched, false if activity blocks it + * + * @see android.app.SearchManager + */ + public boolean onSearchRequested() { + startSearch(null, false, null, false); + return true; + } + + /** + * This hook is called to launch the search UI. + * + * <p>It is typically called from onSearchRequested(), either directly from + * Activity.onSearchRequested() or from an overridden version in any given + * Activity. If your goal is simply to activate search, it is preferred to call + * onSearchRequested(), which may have been overriden elsewhere in your Activity. If your goal + * is to inject specific data such as context data, it is preferred to <i>override</i> + * onSearchRequested(), so that any callers to it will benefit from the override. + * + * @param initialQuery Any non-null non-empty string will be inserted as + * pre-entered text in the search query box. + * @param selectInitialQuery If true, the intial query will be preselected, which means that + * any further typing will replace it. This is useful for cases where an entire pre-formed + * query is being inserted. If false, the selection point will be placed at the end of the + * inserted query. This is useful when the inserted query is text that the user entered, + * and the user would expect to be able to keep typing. <i>This parameter is only meaningful + * if initialQuery is a non-empty string.</i> + * @param appSearchData An application can insert application-specific + * context here, in order to improve quality or specificity of its own + * searches. This data will be returned with SEARCH intent(s). Null if + * no extra data is required. + * @param globalSearch If false, this will only launch the search that has been specifically + * defined by the application (which is usually defined as a local search). If no default + * search is defined in the current application or activity, no search will be launched. + * If true, this will always launch a platform-global (e.g. web-based) search instead. + * + * @see android.app.SearchManager + * @see #onSearchRequested + */ + public void startSearch(String initialQuery, boolean selectInitialQuery, + Bundle appSearchData, boolean globalSearch) { + // activate the search manager and start it up! + SearchManager searchManager = (SearchManager) + getSystemService(Context.SEARCH_SERVICE); + searchManager.startSearch(initialQuery, selectInitialQuery, getComponentName(), + appSearchData, globalSearch); + } + + /** + * Request that key events come to this activity. Use this if your + * activity has no views with focus, but the activity still wants + * a chance to process key events. + * + * @see android.view.Window#takeKeyEvents + */ + public void takeKeyEvents(boolean get) { + getWindow().takeKeyEvents(get); + } + + /** + * Enable extended window features. This is a convenience for calling + * {@link android.view.Window#requestFeature getWindow().requestFeature()}. + * + * @param featureId The desired feature as defined in + * {@link android.view.Window}. + * @return Returns true if the requested feature is supported and now + * enabled. + * + * @see android.view.Window#requestFeature + */ + public final boolean requestWindowFeature(int featureId) { + return getWindow().requestFeature(featureId); + } + + /** + * Convenience for calling + * {@link android.view.Window#setFeatureDrawableResource}. + */ + public final void setFeatureDrawableResource(int featureId, int resId) { + getWindow().setFeatureDrawableResource(featureId, resId); + } + + /** + * Convenience for calling + * {@link android.view.Window#setFeatureDrawableUri}. + */ + public final void setFeatureDrawableUri(int featureId, Uri uri) { + getWindow().setFeatureDrawableUri(featureId, uri); + } + + /** + * Convenience for calling + * {@link android.view.Window#setFeatureDrawable(int, Drawable)}. + */ + public final void setFeatureDrawable(int featureId, Drawable drawable) { + getWindow().setFeatureDrawable(featureId, drawable); + } + + /** + * Convenience for calling + * {@link android.view.Window#setFeatureDrawableAlpha}. + */ + public final void setFeatureDrawableAlpha(int featureId, int alpha) { + getWindow().setFeatureDrawableAlpha(featureId, alpha); + } + + /** + * Convenience for calling + * {@link android.view.Window#getLayoutInflater}. + */ + public LayoutInflater getLayoutInflater() { + return getWindow().getLayoutInflater(); + } + + /** + * Returns a {@link MenuInflater} with this context. + */ + public MenuInflater getMenuInflater() { + return new MenuInflater(this); + } + + @Override + protected void onApplyThemeResource(Resources.Theme theme, + int resid, + boolean first) + { + if (mParent == null) { + super.onApplyThemeResource(theme, resid, first); + } else { + try { + theme.setTo(mParent.getTheme()); + } catch (Exception e) { + // Empty + } + theme.applyStyle(resid, false); + } + } + + /** + * Launch an activity for which you would like a result when it finished. + * When this activity exits, your + * onActivityResult() method will be called with the given requestCode. + * Using a negative requestCode is the same as calling + * {@link #startActivity} (the activity is not launched as a sub-activity). + * + * <p>Note that this method should only be used with Intent protocols + * that are defined to return a result. In other protocols (such as + * {@link Intent#ACTION_MAIN} or {@link Intent#ACTION_VIEW}), you may + * not get the result when you expect. For example, if the activity you + * are launching uses the singleTask launch mode, it will not run in your + * task and thus you will immediately receive a cancel result. + * + * <p>As a special case, if you call startActivityForResult() with a requestCode + * >= 0 during the initial onCreate(Bundle savedInstanceState)/onResume() of your + * activity, then your window will not be displayed until a result is + * returned back from the started activity. This is to avoid visible + * flickering when redirecting to another activity. + * + * <p>This method throws {@link android.content.ActivityNotFoundException} + * if there was no Activity found to run the given Intent. + * + * @param intent The intent to start. + * @param requestCode If >= 0, this code will be returned in + * onActivityResult() when the activity exits. + * + * @throws android.content.ActivityNotFoundException + * + * @see #startActivity + */ + public void startActivityForResult(Intent intent, int requestCode) { + if (mParent == null) { + Instrumentation.ActivityResult ar = + mInstrumentation.execStartActivity( + this, mMainThread.getApplicationThread(), mToken, this, + intent, requestCode); + if (ar != null) { + mMainThread.sendActivityResult( + mToken, mEmbeddedID, requestCode, ar.getResultCode(), + ar.getResultData()); + } + if (requestCode >= 0) { + // If this start is requesting a result, we can avoid making + // the activity visible until the result is received. Setting + // this code during onCreate(Bundle savedInstanceState) or onResume() will keep the + // activity hidden during this time, to avoid flickering. + // This can only be done when a result is requested because + // that guarantees we will get information back when the + // activity is finished, no matter what happens to it. + mStartedActivity = true; + } + } else { + mParent.startActivityFromChild(this, intent, requestCode); + } + } + + /** + * Launch a new activity. You will not receive any information about when + * the activity exits. This implementation overrides the base version, + * providing information about + * the activity performing the launch. Because of this additional + * information, the {@link Intent#FLAG_ACTIVITY_NEW_TASK} launch flag is not + * required; if not specified, the new activity will be added to the + * task of the caller. + * + * <p>This method throws {@link android.content.ActivityNotFoundException} + * if there was no Activity found to run the given Intent. + * + * @param intent The intent to start. + * + * @throws android.content.ActivityNotFoundException + * + * @see #startActivityForResult + */ + @Override + public void startActivity(Intent intent) { + startActivityForResult(intent, -1); + } + + /** + * A special variation to launch an activity only if a new activity + * instance is needed to handle the given Intent. In other words, this is + * just like {@link #startActivityForResult(Intent, int)} except: if you are + * using the {@link Intent#FLAG_ACTIVITY_SINGLE_TOP} flag, or + * singleTask or singleTop + * {@link android.R.styleable#AndroidManifestActivity_launchMode launchMode}, + * and the activity + * that handles <var>intent</var> is the same as your currently running + * activity, then a new instance is not needed. In this case, instead of + * the normal behavior of calling {@link #onNewIntent} this function will + * return and you can handle the Intent yourself. + * + * <p>This function can only be called from a top-level activity; if it is + * called from a child activity, a runtime exception will be thrown. + * + * @param intent The intent to start. + * @param requestCode If >= 0, this code will be returned in + * onActivityResult() when the activity exits, as described in + * {@link #startActivityForResult}. + * + * @return If a new activity was launched then true is returned; otherwise + * false is returned and you must handle the Intent yourself. + * + * @see #startActivity + * @see #startActivityForResult + */ + public boolean startActivityIfNeeded(Intent intent, int requestCode) { + if (mParent == null) { + int result = IActivityManager.START_RETURN_INTENT_TO_CALLER; + try { + result = ActivityManagerNative.getDefault() + .startActivity(mMainThread.getApplicationThread(), + intent, intent.resolveTypeIfNeeded( + getContentResolver()), + null, 0, + mToken, mEmbeddedID, requestCode, true, false); + } catch (RemoteException e) { + // Empty + } + + Instrumentation.checkStartActivityResult(result, intent); + + if (requestCode >= 0) { + // If this start is requesting a result, we can avoid making + // the activity visible until the result is received. Setting + // this code during onCreate(Bundle savedInstanceState) or onResume() will keep the + // activity hidden during this time, to avoid flickering. + // This can only be done when a result is requested because + // that guarantees we will get information back when the + // activity is finished, no matter what happens to it. + mStartedActivity = true; + } + return result != IActivityManager.START_RETURN_INTENT_TO_CALLER; + } + + throw new UnsupportedOperationException( + "startActivityIfNeeded can only be called from a top-level activity"); + } + + /** + * Special version of starting an activity, for use when you are replacing + * other activity components. You can use this to hand the Intent off + * to the next Activity that can handle it. You typically call this in + * {@link #onCreate} with the Intent returned by {@link #getIntent}. + * + * @param intent The intent to dispatch to the next activity. For + * correct behavior, this must be the same as the Intent that started + * your own activity; the only changes you can make are to the extras + * inside of it. + * + * @return Returns a boolean indicating whether there was another Activity + * to start: true if there was a next activity to start, false if there + * wasn't. In general, if true is returned you will then want to call + * finish() on yourself. + */ + public boolean startNextMatchingActivity(Intent intent) { + if (mParent == null) { + try { + return ActivityManagerNative.getDefault() + .startNextMatchingActivity(mToken, intent); + } catch (RemoteException e) { + // Empty + } + return false; + } + + throw new UnsupportedOperationException( + "startNextMatchingActivity can only be called from a top-level activity"); + } + + /** + * This is called when a child activity of this one calls its + * {@link #startActivity} or {@link #startActivityForResult} method. + * + * <p>This method throws {@link android.content.ActivityNotFoundException} + * if there was no Activity found to run the given Intent. + * + * @param child The activity making the call. + * @param intent The intent to start. + * @param requestCode Reply request code. < 0 if reply is not requested. + * + * @throws android.content.ActivityNotFoundException + * + * @see #startActivity + * @see #startActivityForResult + */ + public void startActivityFromChild(Activity child, Intent intent, + int requestCode) { + Instrumentation.ActivityResult ar = + mInstrumentation.execStartActivity( + this, mMainThread.getApplicationThread(), mToken, child, + intent, requestCode); + if (ar != null) { + mMainThread.sendActivityResult( + mToken, child.mEmbeddedID, requestCode, + ar.getResultCode(), ar.getResultData()); + } + } + + /** + * Call this to set the result that your activity will return to its + * caller. + * + * @param resultCode The result code to propagate back to the originating + * activity, often RESULT_CANCELED or RESULT_OK + * + * @see #RESULT_CANCELED + * @see #RESULT_OK + * @see #RESULT_FIRST_USER + * @see #setResult(int, Intent) + */ + public final void setResult(int resultCode) { + synchronized (this) { + mResultCode = resultCode; + mResultData = null; + } + } + + /** + * Call this to set the result that your activity will return to its + * caller. + * + * @param resultCode The result code to propagate back to the originating + * activity, often RESULT_CANCELED or RESULT_OK + * @param data The data to propagate back to the originating activity. + * + * @see #RESULT_CANCELED + * @see #RESULT_OK + * @see #RESULT_FIRST_USER + * @see #setResult(int) + */ + public final void setResult(int resultCode, Intent data) { + synchronized (this) { + mResultCode = resultCode; + mResultData = data; + } + } + + /** + * Return the name of the package that invoked this activity. This is who + * the data in {@link #setResult setResult()} will be sent to. You can + * use this information to validate that the recipient is allowed to + * receive the data. + * + * <p>Note: if the calling activity is not expecting a result (that is it + * did not use the {@link #startActivityForResult} + * form that includes a request code), then the calling package will be + * null. + * + * @return The package of the activity that will receive your + * reply, or null if none. + */ + public String getCallingPackage() { + try { + return ActivityManagerNative.getDefault().getCallingPackage(mToken); + } catch (RemoteException e) { + return null; + } + } + + /** + * Return the name of the activity that invoked this activity. This is + * who the data in {@link #setResult setResult()} will be sent to. You + * can use this information to validate that the recipient is allowed to + * receive the data. + * + * <p>Note: if the calling activity is not expecting a result (that is it + * did not use the {@link #startActivityForResult} + * form that includes a request code), then the calling package will be + * null. + * + * @return String The full name of the activity that will receive your + * reply, or null if none. + */ + public ComponentName getCallingActivity() { + try { + return ActivityManagerNative.getDefault().getCallingActivity(mToken); + } catch (RemoteException e) { + return null; + } + } + + /** + * Control whether this activity's main window is visible. This is intended + * only for the special case of an activity that is not going to show a + * UI itself, but can't just finish prior to onResume() because it needs + * to wait for a service binding or such. Setting this to false allows + * you to prevent your UI from being shown during that time. + * + * <p>The default value for this is taken from the + * {@link android.R.attr#windowNoDisplay} attribute of the activity's theme. + */ + public void setVisible(boolean visible) { + if (mVisibleFromClient != visible) { + mVisibleFromClient = visible; + if (mVisibleFromServer) { + if (visible) makeVisible(); + else mDecor.setVisibility(View.INVISIBLE); + } + } + } + + void makeVisible() { + if (!mWindowAdded) { + ViewManager wm = getWindowManager(); + wm.addView(mDecor, getWindow().getAttributes()); + mWindowAdded = true; + } + mDecor.setVisibility(View.VISIBLE); + } + + /** + * Check to see whether this activity is in the process of finishing, + * either because you called {@link #finish} on it or someone else + * has requested that it finished. This is often used in + * {@link #onPause} to determine whether the activity is simply pausing or + * completely finishing. + * + * @return If the activity is finishing, returns true; else returns false. + * + * @see #finish + */ + public boolean isFinishing() { + return mFinished; + } + + /** + * Call this when your activity is done and should be closed. The + * ActivityResult is propagated back to whoever launched you via + * onActivityResult(). + */ + public void finish() { + if (mParent == null) { + int resultCode; + Intent resultData; + synchronized (this) { + resultCode = mResultCode; + resultData = mResultData; + } + if (Config.LOGV) Log.v(TAG, "Finishing self: token=" + mToken); + try { + if (ActivityManagerNative.getDefault() + .finishActivity(mToken, resultCode, resultData)) { + mFinished = true; + } + } catch (RemoteException e) { + // Empty + } + } else { + mParent.finishFromChild(this); + } + } + + /** + * This is called when a child activity of this one calls its + * {@link #finish} method. The default implementation simply calls + * finish() on this activity (the parent), finishing the entire group. + * + * @param child The activity making the call. + * + * @see #finish + */ + public void finishFromChild(Activity child) { + finish(); + } + + /** + * Force finish another activity that you had previously started with + * {@link #startActivityForResult}. + * + * @param requestCode The request code of the activity that you had + * given to startActivityForResult(). If there are multiple + * activities started with this request code, they + * will all be finished. + */ + public void finishActivity(int requestCode) { + if (mParent == null) { + try { + ActivityManagerNative.getDefault() + .finishSubActivity(mToken, mEmbeddedID, requestCode); + } catch (RemoteException e) { + // Empty + } + } else { + mParent.finishActivityFromChild(this, requestCode); + } + } + + /** + * This is called when a child activity of this one calls its + * finishActivity(). + * + * @param child The activity making the call. + * @param requestCode Request code that had been used to start the + * activity. + */ + public void finishActivityFromChild(Activity child, int requestCode) { + try { + ActivityManagerNative.getDefault() + .finishSubActivity(mToken, child.mEmbeddedID, requestCode); + } catch (RemoteException e) { + // Empty + } + } + + /** + * Called when an activity you launched exits, giving you the requestCode + * you started it with, the resultCode it returned, and any additional + * data from it. The <var>resultCode</var> will be + * {@link #RESULT_CANCELED} if the activity explicitly returned that, + * didn't return any result, or crashed during its operation. + * + * <p>You will receive this call immediately before onResume() when your + * activity is re-starting. + * + * @param requestCode The integer request code originally supplied to + * startActivityForResult(), allowing you to identify who this + * result came from. + * @param resultCode The integer result code returned by the child activity + * through its setResult(). + * @param data An Intent, which can return result data to the caller + * (various data can be attached to Intent "extras"). + * + * @see #startActivityForResult + * @see #createPendingResult + * @see #setResult(int) + */ + protected void onActivityResult(int requestCode, int resultCode, + Intent data) { + } + + /** + * Create a new PendingIntent object which you can hand to others + * for them to use to send result data back to your + * {@link #onActivityResult} callback. The created object will be either + * one-shot (becoming invalid after a result is sent back) or multiple + * (allowing any number of results to be sent through it). + * + * @param requestCode Private request code for the sender that will be + * associated with the result data when it is returned. The sender can not + * modify this value, allowing you to identify incoming results. + * @param data Default data to supply in the result, which may be modified + * by the sender. + * @param flags May be {@link PendingIntent#FLAG_ONE_SHOT PendingIntent.FLAG_ONE_SHOT}, + * {@link PendingIntent#FLAG_NO_CREATE PendingIntent.FLAG_NO_CREATE}, + * {@link PendingIntent#FLAG_CANCEL_CURRENT PendingIntent.FLAG_CANCEL_CURRENT}, + * {@link PendingIntent#FLAG_UPDATE_CURRENT PendingIntent.FLAG_UPDATE_CURRENT}, + * or any of the flags as supported by + * {@link Intent#fillIn Intent.fillIn()} to control which unspecified parts + * of the intent that can be supplied when the actual send happens. + * + * @return Returns an existing or new PendingIntent matching the given + * parameters. May return null only if + * {@link PendingIntent#FLAG_NO_CREATE PendingIntent.FLAG_NO_CREATE} has been + * supplied. + * + * @see PendingIntent + */ + public PendingIntent createPendingResult(int requestCode, Intent data, + int flags) { + String packageName = getPackageName(); + try { + IIntentSender target = + ActivityManagerNative.getDefault().getIntentSender( + IActivityManager.INTENT_SENDER_ACTIVITY_RESULT, packageName, + mParent == null ? mToken : mParent.mToken, + mEmbeddedID, requestCode, data, null, flags); + return target != null ? new PendingIntent(target) : null; + } catch (RemoteException e) { + // Empty + } + return null; + } + + /** + * Change the desired orientation of this activity. If the activity + * is currently in the foreground or otherwise impacting the screen + * orientation, the screen will immediately be changed (possibly causing + * the activity to be restarted). Otherwise, this will be used the next + * time the activity is visible. + * + * @param requestedOrientation An orientation constant as used in + * {@link ActivityInfo#screenOrientation ActivityInfo.screenOrientation}. + */ + public void setRequestedOrientation(int requestedOrientation) { + if (mParent == null) { + try { + ActivityManagerNative.getDefault().setRequestedOrientation( + mToken, requestedOrientation); + } catch (RemoteException e) { + // Empty + } + } else { + mParent.setRequestedOrientation(requestedOrientation); + } + } + + /** + * Return the current requested orientation of the activity. This will + * either be the orientation requested in its component's manifest, or + * the last requested orientation given to + * {@link #setRequestedOrientation(int)}. + * + * @return Returns an orientation constant as used in + * {@link ActivityInfo#screenOrientation ActivityInfo.screenOrientation}. + */ + public int getRequestedOrientation() { + if (mParent == null) { + try { + return ActivityManagerNative.getDefault() + .getRequestedOrientation(mToken); + } catch (RemoteException e) { + // Empty + } + } else { + return mParent.getRequestedOrientation(); + } + return ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED; + } + + /** + * Return the identifier of the task this activity is in. This identifier + * will remain the same for the lifetime of the activity. + * + * @return Task identifier, an opaque integer. + */ + public int getTaskId() { + try { + return ActivityManagerNative.getDefault() + .getTaskForActivity(mToken, false); + } catch (RemoteException e) { + return -1; + } + } + + /** + * Return whether this activity is the root of a task. The root is the + * first activity in a task. + * + * @return True if this is the root activity, else false. + */ + public boolean isTaskRoot() { + try { + return ActivityManagerNative.getDefault() + .getTaskForActivity(mToken, true) >= 0; + } catch (RemoteException e) { + return false; + } + } + + /** + * Move the task containing this activity to the back of the activity + * stack. The activity's order within the task is unchanged. + * + * @param nonRoot If false then this only works if the activity is the root + * of a task; if true it will work for any activity in + * a task. + * + * @return If the task was moved (or it was already at the + * back) true is returned, else false. + */ + public boolean moveTaskToBack(boolean nonRoot) { + try { + return ActivityManagerNative.getDefault().moveActivityTaskToBack( + mToken, nonRoot); + } catch (RemoteException e) { + // Empty + } + return false; + } + + /** + * Returns class name for this activity with the package prefix removed. + * This is the default name used to read and write settings. + * + * @return The local class name. + */ + public String getLocalClassName() { + final String pkg = getPackageName(); + final String cls = mComponent.getClassName(); + int packageLen = pkg.length(); + if (!cls.startsWith(pkg) || cls.length() <= packageLen + || cls.charAt(packageLen) != '.') { + return cls; + } + return cls.substring(packageLen+1); + } + + /** + * Returns complete component name of this activity. + * + * @return Returns the complete component name for this activity + */ + public ComponentName getComponentName() + { + return mComponent; + } + + /** + * Retrieve a {@link SharedPreferences} object for accessing preferences + * that are private to this activity. This simply calls the underlying + * {@link #getSharedPreferences(String, int)} method by passing in this activity's + * class name as the preferences name. + * + * @param mode Operating mode. Use {@link #MODE_PRIVATE} for the default + * operation, {@link #MODE_WORLD_READABLE} and + * {@link #MODE_WORLD_WRITEABLE} to control permissions. + * + * @return Returns the single SharedPreferences instance that can be used + * to retrieve and modify the preference values. + */ + public SharedPreferences getPreferences(int mode) { + return getSharedPreferences(getLocalClassName(), mode); + } + + @Override + public Object getSystemService(String name) { + if (getBaseContext() == null) { + throw new IllegalStateException( + "System services not available to Activities before onCreate()"); + } + + if (WINDOW_SERVICE.equals(name)) { + return mWindowManager; + } + return super.getSystemService(name); + } + + /** + * Change the title associated with this activity. If this is a + * top-level activity, the title for its window will change. If it + * is an embedded activity, the parent can do whatever it wants + * with it. + */ + public void setTitle(CharSequence title) { + mTitle = title; + onTitleChanged(title, mTitleColor); + + if (mParent != null) { + mParent.onChildTitleChanged(this, title); + } + } + + /** + * Change the title associated with this activity. If this is a + * top-level activity, the title for its window will change. If it + * is an embedded activity, the parent can do whatever it wants + * with it. + */ + public void setTitle(int titleId) { + setTitle(getText(titleId)); + } + + public void setTitleColor(int textColor) { + mTitleColor = textColor; + onTitleChanged(mTitle, textColor); + } + + public final CharSequence getTitle() { + return mTitle; + } + + public final int getTitleColor() { + return mTitleColor; + } + + protected void onTitleChanged(CharSequence title, int color) { + if (mTitleReady) { + final Window win = getWindow(); + if (win != null) { + win.setTitle(title); + if (color != 0) { + win.setTitleColor(color); + } + } + } + } + + protected void onChildTitleChanged(Activity childActivity, CharSequence title) { + } + + /** + * Sets the visibility of the progress bar in the title. + * <p> + * In order for the progress bar to be shown, the feature must be requested + * via {@link #requestWindowFeature(int)}. + * + * @param visible Whether to show the progress bars in the title. + */ + public final void setProgressBarVisibility(boolean visible) { + getWindow().setFeatureInt(Window.FEATURE_PROGRESS, visible ? Window.PROGRESS_VISIBILITY_ON : + Window.PROGRESS_VISIBILITY_OFF); + } + + /** + * Sets the visibility of the indeterminate progress bar in the title. + * <p> + * In order for the progress bar to be shown, the feature must be requested + * via {@link #requestWindowFeature(int)}. + * + * @param visible Whether to show the progress bars in the title. + */ + public final void setProgressBarIndeterminateVisibility(boolean visible) { + getWindow().setFeatureInt(Window.FEATURE_INDETERMINATE_PROGRESS, + visible ? Window.PROGRESS_VISIBILITY_ON : Window.PROGRESS_VISIBILITY_OFF); + } + + /** + * Sets whether the horizontal progress bar in the title should be indeterminate (the circular + * is always indeterminate). + * <p> + * In order for the progress bar to be shown, the feature must be requested + * via {@link #requestWindowFeature(int)}. + * + * @param indeterminate Whether the horizontal progress bar should be indeterminate. + */ + public final void setProgressBarIndeterminate(boolean indeterminate) { + getWindow().setFeatureInt(Window.FEATURE_PROGRESS, + indeterminate ? Window.PROGRESS_INDETERMINATE_ON : Window.PROGRESS_INDETERMINATE_OFF); + } + + /** + * Sets the progress for the progress bars in the title. + * <p> + * In order for the progress bar to be shown, the feature must be requested + * via {@link #requestWindowFeature(int)}. + * + * @param progress The progress for the progress bar. Valid ranges are from + * 0 to 10000 (both inclusive). If 10000 is given, the progress + * bar will be completely filled and will fade out. + */ + public final void setProgress(int progress) { + getWindow().setFeatureInt(Window.FEATURE_PROGRESS, progress + Window.PROGRESS_START); + } + + /** + * Sets the secondary progress for the progress bar in the title. This + * progress is drawn between the primary progress (set via + * {@link #setProgress(int)} and the background. It can be ideal for media + * scenarios such as showing the buffering progress while the default + * progress shows the play progress. + * <p> + * In order for the progress bar to be shown, the feature must be requested + * via {@link #requestWindowFeature(int)}. + * + * @param secondaryProgress The secondary progress for the progress bar. Valid ranges are from + * 0 to 10000 (both inclusive). + */ + public final void setSecondaryProgress(int secondaryProgress) { + getWindow().setFeatureInt(Window.FEATURE_PROGRESS, + secondaryProgress + Window.PROGRESS_SECONDARY_START); + } + + /** + * Suggests an audio stream whose volume should be changed by the hardware + * volume controls. + * <p> + * The suggested audio stream will be tied to the window of this Activity. + * If the Activity is switched, the stream set here is no longer the + * suggested stream. The client does not need to save and restore the old + * suggested stream value in onPause and onResume. + * + * @param streamType The type of the audio stream whose volume should be + * changed by the hardware volume controls. It is not guaranteed that + * the hardware volume controls will always change this stream's + * volume (for example, if a call is in progress, its stream's volume + * may be changed instead). To reset back to the default, use + * {@link AudioManager#USE_DEFAULT_STREAM_TYPE}. + */ + public final void setVolumeControlStream(int streamType) { + getWindow().setVolumeControlStream(streamType); + } + + /** + * Gets the suggested audio stream whose volume should be changed by the + * harwdare volume controls. + * + * @return The suggested audio stream type whose volume should be changed by + * the hardware volume controls. + * @see #setVolumeControlStream(int) + */ + public final int getVolumeControlStream() { + return getWindow().getVolumeControlStream(); + } + + /** + * Runs the specified action on the UI thread. If the current thread is the UI + * thread, then the action is executed immediately. If the current thread is + * not the UI thread, the action is posted to the event queue of the UI thread. + * + * @param action the action to run on the UI thread + */ + public final void runOnUiThread(Runnable action) { + if (Thread.currentThread() != mUiThread) { + mHandler.post(action); + } else { + action.run(); + } + } + + /** + * Stub implementation of {@link android.view.LayoutInflater.Factory#onCreateView} used when + * inflating with the LayoutInflater returned by {@link #getSystemService}. This + * implementation simply returns null for all view names. + * + * @see android.view.LayoutInflater#createView + * @see android.view.Window#getLayoutInflater + */ + public View onCreateView(String name, Context context, AttributeSet attrs) { + return null; + } + + // ------------------ Internal API ------------------ + + final void setParent(Activity parent) { + mParent = parent; + } + + final void attach(Context context, ActivityThread aThread, Instrumentation instr, IBinder token, + Application application, Intent intent, ActivityInfo info, CharSequence title, + Activity parent, String id, Object lastNonConfigurationInstance, + Configuration config) { + attach(context, aThread, instr, token, application, intent, info, title, parent, id, + lastNonConfigurationInstance, null, config); + } + + final void attach(Context context, ActivityThread aThread, Instrumentation instr, IBinder token, + Application application, Intent intent, ActivityInfo info, CharSequence title, + Activity parent, String id, Object lastNonConfigurationInstance, + HashMap<String,Object> lastNonConfigurationChildInstances, Configuration config) { + attachBaseContext(context); + + mWindow = PolicyManager.makeNewWindow(this); + mWindow.setCallback(this); + if (info.softInputMode != WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED) { + mWindow.setSoftInputMode(info.softInputMode); + } + mUiThread = Thread.currentThread(); + + mMainThread = aThread; + mInstrumentation = instr; + mToken = token; + mApplication = application; + mIntent = intent; + mComponent = intent.getComponent(); + mActivityInfo = info; + mTitle = title; + mParent = parent; + mEmbeddedID = id; + mLastNonConfigurationInstance = lastNonConfigurationInstance; + mLastNonConfigurationChildInstances = lastNonConfigurationChildInstances; + + mWindow.setWindowManager(null, mToken, mComponent.flattenToString()); + if (mParent != null) { + mWindow.setContainer(mParent.getWindow()); + } + mWindowManager = mWindow.getWindowManager(); + mCurrentConfig = config; + } + + final IBinder getActivityToken() { + return mParent != null ? mParent.getActivityToken() : mToken; + } + + final void performStart() { + mCalled = false; + mInstrumentation.callActivityOnStart(this); + if (!mCalled) { + throw new SuperNotCalledException( + "Activity " + mComponent.toShortString() + + " did not call through to super.onStart()"); + } + } + + final void performRestart() { + final int N = mManagedCursors.size(); + for (int i=0; i<N; i++) { + ManagedCursor mc = mManagedCursors.get(i); + if (mc.mReleased || mc.mUpdated) { + mc.mCursor.requery(); + mc.mReleased = false; + mc.mUpdated = false; + } + } + + if (mStopped) { + mStopped = false; + mCalled = false; + mInstrumentation.callActivityOnRestart(this); + if (!mCalled) { + throw new SuperNotCalledException( + "Activity " + mComponent.toShortString() + + " did not call through to super.onRestart()"); + } + performStart(); + } + } + + final void performResume() { + performRestart(); + + mLastNonConfigurationInstance = null; + + // First call onResume() -before- setting mResumed, so we don't + // send out any status bar / menu notifications the client makes. + mCalled = false; + mInstrumentation.callActivityOnResume(this); + if (!mCalled) { + throw new SuperNotCalledException( + "Activity " + mComponent.toShortString() + + " did not call through to super.onResume()"); + } + + // Now really resume, and install the current status bar and menu. + mResumed = true; + mCalled = false; + onPostResume(); + if (!mCalled) { + throw new SuperNotCalledException( + "Activity " + mComponent.toShortString() + + " did not call through to super.onPostResume()"); + } + } + + final void performPause() { + onPause(); + } + + final void performUserLeaving() { + onUserInteraction(); + onUserLeaveHint(); + } + + final void performStop() { + if (!mStopped) { + if (mWindow != null) { + mWindow.closeAllPanels(); + } + + mCalled = false; + mInstrumentation.callActivityOnStop(this); + if (!mCalled) { + throw new SuperNotCalledException( + "Activity " + mComponent.toShortString() + + " did not call through to super.onStop()"); + } + + final int N = mManagedCursors.size(); + for (int i=0; i<N; i++) { + ManagedCursor mc = mManagedCursors.get(i); + if (!mc.mReleased) { + mc.mCursor.deactivate(); + mc.mReleased = true; + } + } + + mStopped = true; + } + mResumed = false; + } + + final boolean isResumed() { + return mResumed; + } + + void dispatchActivityResult(String who, int requestCode, + int resultCode, Intent data) { + if (Config.LOGV) Log.v( + TAG, "Dispatching result: who=" + who + ", reqCode=" + requestCode + + ", resCode=" + resultCode + ", data=" + data); + if (who == null) { + onActivityResult(requestCode, resultCode, data); + } + } +} diff --git a/core/java/android/app/ActivityGroup.java b/core/java/android/app/ActivityGroup.java new file mode 100644 index 0000000..f1216f9 --- /dev/null +++ b/core/java/android/app/ActivityGroup.java @@ -0,0 +1,127 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * 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 android.app; + +import java.util.HashMap; + +import android.content.Intent; +import android.os.Bundle; +import android.util.Log; + +/** + * A screen that contains and runs multiple embedded activities. + */ +public class ActivityGroup extends Activity { + private static final String TAG = "ActivityGroup"; + private static final String STATES_KEY = "android:states"; + static final String PARENT_NON_CONFIG_INSTANCE_KEY = "android:parent_non_config_instance"; + + /** + * This field should be made private, so it is hidden from the SDK. + * {@hide} + */ + protected LocalActivityManager mLocalActivityManager; + + public ActivityGroup() { + this(true); + } + + public ActivityGroup(boolean singleActivityMode) { + mLocalActivityManager = new LocalActivityManager(this, singleActivityMode); + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + Bundle states = savedInstanceState != null + ? (Bundle) savedInstanceState.getBundle(STATES_KEY) : null; + mLocalActivityManager.dispatchCreate(states); + } + + @Override + protected void onResume() { + super.onResume(); + mLocalActivityManager.dispatchResume(); + } + + @Override + protected void onSaveInstanceState(Bundle outState) { + super.onSaveInstanceState(outState); + Bundle state = mLocalActivityManager.saveInstanceState(); + if (state != null) { + outState.putBundle(STATES_KEY, state); + } + } + + @Override + protected void onPause() { + super.onPause(); + mLocalActivityManager.dispatchPause(isFinishing()); + } + + @Override + protected void onStop() { + super.onStop(); + mLocalActivityManager.dispatchStop(); + } + + @Override + protected void onDestroy() { + super.onDestroy(); + mLocalActivityManager.dispatchDestroy(isFinishing()); + } + + /** + * Returns a HashMap mapping from child activity ids to the return values + * from calls to their onRetainNonConfigurationInstance methods. + * + * {@hide} + */ + @Override + public HashMap<String,Object> onRetainNonConfigurationChildInstances() { + return mLocalActivityManager.dispatchRetainNonConfigurationInstance(); + } + + public Activity getCurrentActivity() { + return mLocalActivityManager.getCurrentActivity(); + } + + public final LocalActivityManager getLocalActivityManager() { + return mLocalActivityManager; + } + + @Override + void dispatchActivityResult(String who, int requestCode, int resultCode, + Intent data) { + if (who != null) { + Activity act = mLocalActivityManager.getActivity(who); + /* + if (Config.LOGV) Log.v( + TAG, "Dispatching result: who=" + who + ", reqCode=" + requestCode + + ", resCode=" + resultCode + ", data=" + data + + ", rec=" + rec); + */ + if (act != null) { + act.onActivityResult(requestCode, resultCode, data); + return; + } + } + super.dispatchActivityResult(who, requestCode, resultCode, data); + } +} + + diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java new file mode 100644 index 0000000..07520c9d --- /dev/null +++ b/core/java/android/app/ActivityManager.java @@ -0,0 +1,761 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * 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 android.app; + +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.pm.ConfigurationInfo; +import android.content.pm.IPackageDataObserver; +import android.graphics.Bitmap; +import android.os.RemoteException; +import android.os.Handler; +import android.os.Parcel; +import android.os.Parcelable; +import android.text.TextUtils; +import java.util.List; + +/** + * Interact with the overall activities running in the system. + */ +public class ActivityManager { + private static String TAG = "ActivityManager"; + private static boolean DEBUG = false; + private static boolean localLOGV = DEBUG || android.util.Config.LOGV; + + private final Context mContext; + private final Handler mHandler; + + /*package*/ ActivityManager(Context context, Handler handler) { + mContext = context; + mHandler = handler; + } + + /** + * Information you can retrieve about tasks that the user has most recently + * started or visited. + */ + public static class RecentTaskInfo implements Parcelable { + /** + * If this task is currently running, this is the identifier for it. + * If it is not running, this will be -1. + */ + public int id; + + /** + * The original Intent used to launch the task. You can use this + * Intent to re-launch the task (if it is no longer running) or bring + * the current task to the front. + */ + public Intent baseIntent; + + /** + * If this task was started from an alias, this is the actual + * activity component that was initially started; the component of + * the baseIntent in this case is the name of the actual activity + * implementation that the alias referred to. Otherwise, this is null. + */ + public ComponentName origActivity; + + public RecentTaskInfo() { + } + + public int describeContents() { + return 0; + } + + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(id); + if (baseIntent != null) { + dest.writeInt(1); + baseIntent.writeToParcel(dest, 0); + } else { + dest.writeInt(0); + } + ComponentName.writeToParcel(origActivity, dest); + } + + public void readFromParcel(Parcel source) { + id = source.readInt(); + if (source.readInt() != 0) { + baseIntent = Intent.CREATOR.createFromParcel(source); + } else { + baseIntent = null; + } + origActivity = ComponentName.readFromParcel(source); + } + + public static final Creator<RecentTaskInfo> CREATOR + = new Creator<RecentTaskInfo>() { + public RecentTaskInfo createFromParcel(Parcel source) { + return new RecentTaskInfo(source); + } + public RecentTaskInfo[] newArray(int size) { + return new RecentTaskInfo[size]; + } + }; + + private RecentTaskInfo(Parcel source) { + readFromParcel(source); + } + } + + /** + * Flag for use with {@link #getRecentTasks}: return all tasks, even those + * that have set their + * {@link android.content.Intent#FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS} flag. + */ + public static final int RECENT_WITH_EXCLUDED = 0x0001; + + /** + * Return a list of the tasks that the user has recently launched, with + * the most recent being first and older ones after in order. + * + * @param maxNum The maximum number of entries to return in the list. The + * actual number returned may be smaller, depending on how many tasks the + * user has started and the maximum number the system can remember. + * + * @return Returns a list of RecentTaskInfo records describing each of + * the recent tasks. + * + * @throws SecurityException Throws SecurityException if the caller does + * not hold the {@link android.Manifest.permission#GET_TASKS} permission. + */ + public List<RecentTaskInfo> getRecentTasks(int maxNum, int flags) + throws SecurityException { + try { + return ActivityManagerNative.getDefault().getRecentTasks(maxNum, + flags); + } catch (RemoteException e) { + // System dead, we will be dead too soon! + return null; + } + } + + /** + * Information you can retrieve about a particular task that is currently + * "running" in the system. Note that a running task does not mean the + * given task actual has a process it is actively running in; it simply + * means that the user has gone to it and never closed it, but currently + * the system may have killed its process and is only holding on to its + * last state in order to restart it when the user returns. + */ + public static class RunningTaskInfo implements Parcelable { + /** + * A unique identifier for this task. + */ + public int id; + + /** + * The component launched as the first activity in the task. This can + * be considered the "application" of this task. + */ + public ComponentName baseActivity; + + /** + * The activity component at the top of the history stack of the task. + * This is what the user is currently doing. + */ + public ComponentName topActivity; + + /** + * Thumbnail representation of the task's current state. + */ + public Bitmap thumbnail; + + /** + * Description of the task's current state. + */ + public CharSequence description; + + /** + * Number of activities in this task. + */ + public int numActivities; + + /** + * Number of activities that are currently running (not stopped + * and persisted) in this task. + */ + public int numRunning; + + public RunningTaskInfo() { + } + + public int describeContents() { + return 0; + } + + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(id); + ComponentName.writeToParcel(baseActivity, dest); + ComponentName.writeToParcel(topActivity, dest); + if (thumbnail != null) { + dest.writeInt(1); + thumbnail.writeToParcel(dest, 0); + } else { + dest.writeInt(0); + } + TextUtils.writeToParcel(description, dest, + Parcelable.PARCELABLE_WRITE_RETURN_VALUE); + dest.writeInt(numActivities); + dest.writeInt(numRunning); + } + + public void readFromParcel(Parcel source) { + id = source.readInt(); + baseActivity = ComponentName.readFromParcel(source); + topActivity = ComponentName.readFromParcel(source); + if (source.readInt() != 0) { + thumbnail = Bitmap.CREATOR.createFromParcel(source); + } else { + thumbnail = null; + } + description = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source); + numActivities = source.readInt(); + numRunning = source.readInt(); + } + + public static final Creator<RunningTaskInfo> CREATOR = new Creator<RunningTaskInfo>() { + public RunningTaskInfo createFromParcel(Parcel source) { + return new RunningTaskInfo(source); + } + public RunningTaskInfo[] newArray(int size) { + return new RunningTaskInfo[size]; + } + }; + + private RunningTaskInfo(Parcel source) { + readFromParcel(source); + } + } + + /** + * Return a list of the tasks that are currently running, with + * the most recent being first and older ones after in order. Note that + * "running" does not mean any of the task's code is currently loaded or + * activity -- the task may have been frozen by the system, so that it + * can be restarted in its previous state when next brought to the + * foreground. + * + * @param maxNum The maximum number of entries to return in the list. The + * actual number returned may be smaller, depending on how many tasks the + * user has started. + * + * @return Returns a list of RunningTaskInfo records describing each of + * the running tasks. + * + * @throws SecurityException Throws SecurityException if the caller does + * not hold the {@link android.Manifest.permission#GET_TASKS} permission. + */ + public List<RunningTaskInfo> getRunningTasks(int maxNum) + throws SecurityException { + try { + return (List<RunningTaskInfo>)ActivityManagerNative.getDefault() + .getTasks(maxNum, 0, null); + } catch (RemoteException e) { + // System dead, we will be dead too soon! + return null; + } + } + + /** + * Information you can retrieve about a particular Service that is + * currently running in the system. + */ + public static class RunningServiceInfo implements Parcelable { + /** + * The service component. + */ + public ComponentName service; + + /** + * If non-zero, this is the process the service is running in. + */ + public int pid; + + /** + * The name of the process this service runs in. + */ + public String process; + + /** + * Set to true if the service has asked to run as a foreground process. + */ + public boolean foreground; + + /** + * The time when the service was first made activity, either by someone + * starting or binding to it. + */ + public long activeSince; + + /** + * Set to true if this service has been explicitly started. + */ + public boolean started; + + /** + * Number of clients connected to the service. + */ + public int clientCount; + + /** + * Number of times the service's process has crashed while the service + * is running. + */ + public int crashCount; + + /** + * The time when there was last activity in the service (either + * explicit requests to start it or clients binding to it). + */ + public long lastActivityTime; + + /** + * If non-zero, this service is not currently running, but scheduled to + * restart at the given time. + */ + public long restarting; + + public RunningServiceInfo() { + } + + public int describeContents() { + return 0; + } + + public void writeToParcel(Parcel dest, int flags) { + ComponentName.writeToParcel(service, dest); + dest.writeInt(pid); + dest.writeString(process); + dest.writeInt(foreground ? 1 : 0); + dest.writeLong(activeSince); + dest.writeInt(started ? 1 : 0); + dest.writeInt(clientCount); + dest.writeInt(crashCount); + dest.writeLong(lastActivityTime); + dest.writeLong(restarting); + } + + public void readFromParcel(Parcel source) { + service = ComponentName.readFromParcel(source); + pid = source.readInt(); + process = source.readString(); + foreground = source.readInt() != 0; + activeSince = source.readLong(); + started = source.readInt() != 0; + clientCount = source.readInt(); + crashCount = source.readInt(); + lastActivityTime = source.readLong(); + restarting = source.readLong(); + } + + public static final Creator<RunningServiceInfo> CREATOR = new Creator<RunningServiceInfo>() { + public RunningServiceInfo createFromParcel(Parcel source) { + return new RunningServiceInfo(source); + } + public RunningServiceInfo[] newArray(int size) { + return new RunningServiceInfo[size]; + } + }; + + private RunningServiceInfo(Parcel source) { + readFromParcel(source); + } + } + + /** + * Return a list of the services that are currently running. + * + * @param maxNum The maximum number of entries to return in the list. The + * actual number returned may be smaller, depending on how many services + * are running. + * + * @return Returns a list of RunningServiceInfo records describing each of + * the running tasks. + */ + public List<RunningServiceInfo> getRunningServices(int maxNum) + throws SecurityException { + try { + return (List<RunningServiceInfo>)ActivityManagerNative.getDefault() + .getServices(maxNum, 0); + } catch (RemoteException e) { + // System dead, we will be dead too soon! + return null; + } + } + + /** + * Information you can retrieve about the available memory through + * {@link ActivityManager#getMemoryInfo}. + */ + public static class MemoryInfo implements Parcelable { + /** + * The total available memory on the system. This number should not + * be considered absolute: due to the nature of the kernel, a significant + * portion of this memory is actually in use and needed for the overall + * system to run well. + */ + public long availMem; + + /** + * The threshold of {@link #availMem} at which we consider memory to be + * low and start killing background services and other non-extraneous + * processes. + */ + public long threshold; + + /** + * Set to true if the system considers itself to currently be in a low + * memory situation. + */ + public boolean lowMemory; + + public MemoryInfo() { + } + + public int describeContents() { + return 0; + } + + public void writeToParcel(Parcel dest, int flags) { + dest.writeLong(availMem); + dest.writeLong(threshold); + dest.writeInt(lowMemory ? 1 : 0); + } + + public void readFromParcel(Parcel source) { + availMem = source.readLong(); + threshold = source.readLong(); + lowMemory = source.readInt() != 0; + } + + public static final Creator<MemoryInfo> CREATOR + = new Creator<MemoryInfo>() { + public MemoryInfo createFromParcel(Parcel source) { + return new MemoryInfo(source); + } + public MemoryInfo[] newArray(int size) { + return new MemoryInfo[size]; + } + }; + + private MemoryInfo(Parcel source) { + readFromParcel(source); + } + } + + public void getMemoryInfo(MemoryInfo outInfo) { + try { + ActivityManagerNative.getDefault().getMemoryInfo(outInfo); + } catch (RemoteException e) { + } + } + + /** + * @hide + */ + public boolean clearApplicationUserData(String packageName, IPackageDataObserver observer) { + try { + return ActivityManagerNative.getDefault().clearApplicationUserData(packageName, + observer); + } catch (RemoteException e) { + return false; + } + } + + /** + * Information you can retrieve about any processes that are in an error condition. + */ + public static class ProcessErrorStateInfo implements Parcelable { + /** + * Condition codes + */ + public static final int NO_ERROR = 0; + public static final int CRASHED = 1; + public static final int NOT_RESPONDING = 2; + + /** + * The condition that the process is in. + */ + public int condition; + + /** + * The process name in which the crash or error occurred. + */ + public String processName; + + /** + * The pid of this process; 0 if none + */ + public int pid; + + /** + * The kernel user-ID that has been assigned to this process; + * currently this is not a unique ID (multiple applications can have + * the same uid). + */ + public int uid; + + /** + * The tag that was provided when the process crashed. + */ + public String tag; + + /** + * A short message describing the error condition. + */ + public String shortMsg; + + /** + * A long message describing the error condition. + */ + public String longMsg; + + /** + * Raw data about the crash (typically a stack trace). + */ + public byte[] crashData; + + public ProcessErrorStateInfo() { + } + + public int describeContents() { + return 0; + } + + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(condition); + dest.writeString(processName); + dest.writeInt(pid); + dest.writeInt(uid); + dest.writeString(tag); + dest.writeString(shortMsg); + dest.writeString(longMsg); + dest.writeInt(crashData == null ? -1 : crashData.length); + dest.writeByteArray(crashData); + } + + public void readFromParcel(Parcel source) { + condition = source.readInt(); + processName = source.readString(); + pid = source.readInt(); + uid = source.readInt(); + tag = source.readString(); + shortMsg = source.readString(); + longMsg = source.readString(); + int cdLen = source.readInt(); + if (cdLen == -1) { + crashData = null; + } else { + crashData = new byte[cdLen]; + source.readByteArray(crashData); + } + } + + public static final Creator<ProcessErrorStateInfo> CREATOR = + new Creator<ProcessErrorStateInfo>() { + public ProcessErrorStateInfo createFromParcel(Parcel source) { + return new ProcessErrorStateInfo(source); + } + public ProcessErrorStateInfo[] newArray(int size) { + return new ProcessErrorStateInfo[size]; + } + }; + + private ProcessErrorStateInfo(Parcel source) { + readFromParcel(source); + } + } + + /** + * Returns a list of any processes that are currently in an error condition. The result + * will be null if all processes are running properly at this time. + * + * @return Returns a list of ProcessErrorStateInfo records, or null if there are no + * current error conditions (it will not return an empty list). This list ordering is not + * specified. + */ + public List<ProcessErrorStateInfo> getProcessesInErrorState() { + try { + return ActivityManagerNative.getDefault().getProcessesInErrorState(); + } catch (RemoteException e) { + return null; + } + } + + /** + * Information you can retrieve about a running process. + */ + public static class RunningAppProcessInfo implements Parcelable { + /** + * The name of the process that this object is associated with + */ + public String processName; + + /** + * The pid of this process; 0 if none + */ + public int pid; + + public String pkgList[]; + + /** + * Constant for {@link #importance}: this process is running the + * foreground UI. + */ + public static final int IMPORTANCE_FOREGROUND = 100; + + /** + * Constant for {@link #importance}: this process is running something + * that is considered to be actively visible to the user. + */ + public static final int IMPORTANCE_VISIBLE = 200; + + /** + * Constant for {@link #importance}: this process is contains services + * that should remain running. + */ + public static final int IMPORTANCE_SERVICE = 300; + + /** + * Constant for {@link #importance}: this process process contains + * background code that is expendable. + */ + public static final int IMPORTANCE_BACKGROUND = 400; + + /** + * Constant for {@link #importance}: this process is empty of any + * actively running code. + */ + public static final int IMPORTANCE_EMPTY = 500; + + /** + * The relative importance level that the system places on this + * process. May be one of {@link #IMPORTANCE_FOREGROUND}, + * {@link #IMPORTANCE_VISIBLE}, {@link #IMPORTANCE_SERVICE}, + * {@link #IMPORTANCE_BACKGROUND}, or {@link #IMPORTANCE_EMPTY}. These + * constants are numbered so that "more important" values are always + * smaller than "less important" values. + */ + public int importance; + + /** + * An additional ordering within a particular {@link #importance} + * category, providing finer-grained information about the relative + * utility of processes within a category. This number means nothing + * except that a smaller values are more recently used (and thus + * more important). Currently an LRU value is only maintained for + * the {@link #IMPORTANCE_BACKGROUND} category, though others may + * be maintained in the future. + */ + public int lru; + + public RunningAppProcessInfo() { + importance = IMPORTANCE_FOREGROUND; + } + + public RunningAppProcessInfo(String pProcessName, int pPid, String pArr[]) { + processName = pProcessName; + pid = pPid; + pkgList = pArr; + } + + public int describeContents() { + return 0; + } + + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(processName); + dest.writeInt(pid); + dest.writeStringArray(pkgList); + dest.writeInt(importance); + dest.writeInt(lru); + } + + public void readFromParcel(Parcel source) { + processName = source.readString(); + pid = source.readInt(); + pkgList = source.readStringArray(); + importance = source.readInt(); + lru = source.readInt(); + } + + public static final Creator<RunningAppProcessInfo> CREATOR = + new Creator<RunningAppProcessInfo>() { + public RunningAppProcessInfo createFromParcel(Parcel source) { + return new RunningAppProcessInfo(source); + } + public RunningAppProcessInfo[] newArray(int size) { + return new RunningAppProcessInfo[size]; + } + }; + + private RunningAppProcessInfo(Parcel source) { + readFromParcel(source); + } + } + + /** + * Returns a list of application processes that are running on the device. + * + * @return Returns a list of RunningAppProcessInfo records, or null if there are no + * running processes (it will not return an empty list). This list ordering is not + * specified. + */ + public List<RunningAppProcessInfo> getRunningAppProcesses() { + try { + return ActivityManagerNative.getDefault().getRunningAppProcesses(); + } catch (RemoteException e) { + return null; + } + } + + /** + * Have the system perform a force stop of everything associated with + * the given application package. All processes that share its uid + * will be killed, all services it has running stopped, all activities + * removed, etc. In addition, a {@link Intent#ACTION_PACKAGE_RESTARTED} + * broadcast will be sent, so that any of its registered alarms can + * be stopped, notifications removed, etc. + * + * <p>You must hold the permission + * {@link android.Manifest.permission#RESTART_PACKAGES} to be able to + * call this method. + * + * @param packageName The name of the package to be stopped. + */ + public void restartPackage(String packageName) { + try { + ActivityManagerNative.getDefault().restartPackage(packageName); + } catch (RemoteException e) { + } + } + + /** + * Get the device configuration attributes. + */ + public ConfigurationInfo getDeviceConfigurationInfo() { + try { + return ActivityManagerNative.getDefault().getDeviceConfigurationInfo(); + } catch (RemoteException e) { + } + return null; + } + +} diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java new file mode 100644 index 0000000..f11dbec --- /dev/null +++ b/core/java/android/app/ActivityManagerNative.java @@ -0,0 +1,2135 @@ +/* + * Copyright (C) 2006 The Android Open Source Project + * + * 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 android.app; + +import android.content.ComponentName; +import android.content.ContentResolver; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.pm.ConfigurationInfo; +import android.content.pm.IPackageDataObserver; +import android.content.res.Configuration; +import android.graphics.Bitmap; +import android.net.Uri; +import android.os.Binder; +import android.os.Bundle; +import android.os.Parcelable; +import android.os.ParcelFileDescriptor; +import android.os.RemoteException; +import android.os.IBinder; +import android.os.Parcel; +import android.os.ServiceManager; +import android.text.TextUtils; +import android.util.Config; +import android.util.Log; + +import java.io.FileNotFoundException; +import java.io.FileDescriptor; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +/** {@hide} */ +public abstract class ActivityManagerNative extends Binder implements IActivityManager +{ + /** + * Cast a Binder object into an activity manager interface, generating + * a proxy if needed. + */ + static public IActivityManager asInterface(IBinder obj) + { + if (obj == null) { + return null; + } + IActivityManager in = + (IActivityManager)obj.queryLocalInterface(descriptor); + if (in != null) { + return in; + } + + return new ActivityManagerProxy(obj); + } + + /** + * Retrieve the system's default/global activity manager. + */ + static public IActivityManager getDefault() + { + if (gDefault != null) { + //if (Config.LOGV) Log.v( + // "ActivityManager", "returning cur default = " + gDefault); + return gDefault; + } + IBinder b = ServiceManager.getService("activity"); + if (Config.LOGV) Log.v( + "ActivityManager", "default service binder = " + b); + gDefault = asInterface(b); + if (Config.LOGV) Log.v( + "ActivityManager", "default service = " + gDefault); + return gDefault; + } + + /** + * Convenience for checking whether the system is ready. For internal use only. + */ + static public boolean isSystemReady() { + if (!sSystemReady) { + sSystemReady = getDefault().testIsSystemReady(); + } + return sSystemReady; + } + static boolean sSystemReady = false; + + /** + * Convenience for sending a sticky broadcast. For internal use only. + * If you don't care about permission, use null. + */ + static public void broadcastStickyIntent(Intent intent, String permission) + { + try { + getDefault().broadcastIntent( + null, intent, null, null, Activity.RESULT_OK, null, null, + null /*permission*/, false, true); + } catch (RemoteException ex) { + } + } + + static public void noteWakeupAlarm(PendingIntent ps) { + try { + getDefault().noteWakeupAlarm(ps.getTarget()); + } catch (RemoteException ex) { + } + } + + public ActivityManagerNative() + { + attachInterface(this, descriptor); + } + + public boolean onTransact(int code, Parcel data, Parcel reply, int flags) + throws RemoteException { + switch (code) { + case START_ACTIVITY_TRANSACTION: + { + data.enforceInterface(IActivityManager.descriptor); + IBinder b = data.readStrongBinder(); + IApplicationThread app = ApplicationThreadNative.asInterface(b); + Intent intent = Intent.CREATOR.createFromParcel(data); + String resolvedType = data.readString(); + Uri[] grantedUriPermissions = data.createTypedArray(Uri.CREATOR); + int grantedMode = data.readInt(); + IBinder resultTo = data.readStrongBinder(); + String resultWho = data.readString(); + int requestCode = data.readInt(); + boolean onlyIfNeeded = data.readInt() != 0; + boolean debug = data.readInt() != 0; + int result = startActivity(app, intent, resolvedType, + grantedUriPermissions, grantedMode, resultTo, resultWho, + requestCode, onlyIfNeeded, debug); + reply.writeNoException(); + reply.writeInt(result); + return true; + } + + case START_NEXT_MATCHING_ACTIVITY_TRANSACTION: + { + data.enforceInterface(IActivityManager.descriptor); + IBinder callingActivity = data.readStrongBinder(); + Intent intent = Intent.CREATOR.createFromParcel(data); + boolean result = startNextMatchingActivity(callingActivity, intent); + reply.writeNoException(); + reply.writeInt(result ? 1 : 0); + return true; + } + + case FINISH_ACTIVITY_TRANSACTION: { + data.enforceInterface(IActivityManager.descriptor); + IBinder token = data.readStrongBinder(); + Intent resultData = null; + int resultCode = data.readInt(); + if (data.readInt() != 0) { + resultData = Intent.CREATOR.createFromParcel(data); + } + boolean res = finishActivity(token, resultCode, resultData); + reply.writeNoException(); + reply.writeInt(res ? 1 : 0); + return true; + } + + case FINISH_SUB_ACTIVITY_TRANSACTION: { + data.enforceInterface(IActivityManager.descriptor); + IBinder token = data.readStrongBinder(); + String resultWho = data.readString(); + int requestCode = data.readInt(); + finishSubActivity(token, resultWho, requestCode); + reply.writeNoException(); + return true; + } + + case REGISTER_RECEIVER_TRANSACTION: + { + data.enforceInterface(IActivityManager.descriptor); + IBinder b = data.readStrongBinder(); + IApplicationThread app = + b != null ? ApplicationThreadNative.asInterface(b) : null; + b = data.readStrongBinder(); + IIntentReceiver rec + = b != null ? IIntentReceiver.Stub.asInterface(b) : null; + IntentFilter filter = IntentFilter.CREATOR.createFromParcel(data); + String perm = data.readString(); + Intent intent = registerReceiver(app, rec, filter, perm); + reply.writeNoException(); + if (intent != null) { + reply.writeInt(1); + intent.writeToParcel(reply, 0); + } else { + reply.writeInt(0); + } + return true; + } + + case UNREGISTER_RECEIVER_TRANSACTION: + { + data.enforceInterface(IActivityManager.descriptor); + IBinder b = data.readStrongBinder(); + if (b == null) { + return true; + } + IIntentReceiver rec = IIntentReceiver.Stub.asInterface(b); + unregisterReceiver(rec); + reply.writeNoException(); + return true; + } + + case BROADCAST_INTENT_TRANSACTION: + { + data.enforceInterface(IActivityManager.descriptor); + IBinder b = data.readStrongBinder(); + IApplicationThread app = + b != null ? ApplicationThreadNative.asInterface(b) : null; + Intent intent = Intent.CREATOR.createFromParcel(data); + String resolvedType = data.readString(); + b = data.readStrongBinder(); + IIntentReceiver resultTo = + b != null ? IIntentReceiver.Stub.asInterface(b) : null; + int resultCode = data.readInt(); + String resultData = data.readString(); + Bundle resultExtras = data.readBundle(); + String perm = data.readString(); + boolean serialized = data.readInt() != 0; + boolean sticky = data.readInt() != 0; + int res = broadcastIntent(app, intent, resolvedType, resultTo, + resultCode, resultData, resultExtras, perm, + serialized, sticky); + reply.writeNoException(); + reply.writeInt(res); + return true; + } + + case UNBROADCAST_INTENT_TRANSACTION: + { + data.enforceInterface(IActivityManager.descriptor); + IBinder b = data.readStrongBinder(); + IApplicationThread app = b != null ? ApplicationThreadNative.asInterface(b) : null; + Intent intent = Intent.CREATOR.createFromParcel(data); + unbroadcastIntent(app, intent); + reply.writeNoException(); + return true; + } + + case FINISH_RECEIVER_TRANSACTION: { + data.enforceInterface(IActivityManager.descriptor); + IBinder who = data.readStrongBinder(); + int resultCode = data.readInt(); + String resultData = data.readString(); + Bundle resultExtras = data.readBundle(); + boolean resultAbort = data.readInt() != 0; + if (who != null) { + finishReceiver(who, resultCode, resultData, resultExtras, resultAbort); + } + reply.writeNoException(); + return true; + } + + case SET_PERSISTENT_TRANSACTION: { + data.enforceInterface(IActivityManager.descriptor); + IBinder token = data.readStrongBinder(); + boolean isPersistent = data.readInt() != 0; + if (token != null) { + setPersistent(token, isPersistent); + } + reply.writeNoException(); + return true; + } + + case ATTACH_APPLICATION_TRANSACTION: { + data.enforceInterface(IActivityManager.descriptor); + IApplicationThread app = ApplicationThreadNative.asInterface( + data.readStrongBinder()); + if (app != null) { + attachApplication(app); + } + reply.writeNoException(); + return true; + } + + case ACTIVITY_IDLE_TRANSACTION: { + data.enforceInterface(IActivityManager.descriptor); + IBinder token = data.readStrongBinder(); + if (token != null) { + activityIdle(token); + } + reply.writeNoException(); + return true; + } + + case ACTIVITY_PAUSED_TRANSACTION: { + data.enforceInterface(IActivityManager.descriptor); + IBinder token = data.readStrongBinder(); + Bundle map = data.readBundle(); + activityPaused(token, map); + reply.writeNoException(); + return true; + } + + case ACTIVITY_STOPPED_TRANSACTION: { + data.enforceInterface(IActivityManager.descriptor); + IBinder token = data.readStrongBinder(); + Bitmap thumbnail = data.readInt() != 0 + ? Bitmap.CREATOR.createFromParcel(data) : null; + CharSequence description = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(data); + activityStopped(token, thumbnail, description); + reply.writeNoException(); + return true; + } + + case ACTIVITY_DESTROYED_TRANSACTION: { + data.enforceInterface(IActivityManager.descriptor); + IBinder token = data.readStrongBinder(); + activityDestroyed(token); + reply.writeNoException(); + return true; + } + + case GET_CALLING_PACKAGE_TRANSACTION: { + data.enforceInterface(IActivityManager.descriptor); + IBinder token = data.readStrongBinder(); + String res = token != null ? getCallingPackage(token) : null; + reply.writeNoException(); + reply.writeString(res); + return true; + } + + case GET_CALLING_ACTIVITY_TRANSACTION: { + data.enforceInterface(IActivityManager.descriptor); + IBinder token = data.readStrongBinder(); + ComponentName cn = getCallingActivity(token); + reply.writeNoException(); + ComponentName.writeToParcel(cn, reply); + return true; + } + + case GET_TASKS_TRANSACTION: { + data.enforceInterface(IActivityManager.descriptor); + int maxNum = data.readInt(); + int fl = data.readInt(); + IBinder receiverBinder = data.readStrongBinder(); + IThumbnailReceiver receiver = receiverBinder != null + ? IThumbnailReceiver.Stub.asInterface(receiverBinder) + : null; + List list = getTasks(maxNum, fl, receiver); + reply.writeNoException(); + int N = list != null ? list.size() : -1; + reply.writeInt(N); + int i; + for (i=0; i<N; i++) { + ActivityManager.RunningTaskInfo info = + (ActivityManager.RunningTaskInfo)list.get(i); + info.writeToParcel(reply, 0); + } + return true; + } + + case GET_RECENT_TASKS_TRANSACTION: { + data.enforceInterface(IActivityManager.descriptor); + int maxNum = data.readInt(); + int fl = data.readInt(); + List<ActivityManager.RecentTaskInfo> list = getRecentTasks(maxNum, + fl); + reply.writeNoException(); + reply.writeTypedList(list); + return true; + } + + case GET_SERVICES_TRANSACTION: { + data.enforceInterface(IActivityManager.descriptor); + int maxNum = data.readInt(); + int fl = data.readInt(); + List list = getServices(maxNum, fl); + reply.writeNoException(); + int N = list != null ? list.size() : -1; + reply.writeInt(N); + int i; + for (i=0; i<N; i++) { + ActivityManager.RunningServiceInfo info = + (ActivityManager.RunningServiceInfo)list.get(i); + info.writeToParcel(reply, 0); + } + return true; + } + + case GET_PROCESSES_IN_ERROR_STATE_TRANSACTION: { + data.enforceInterface(IActivityManager.descriptor); + List<ActivityManager.ProcessErrorStateInfo> list = getProcessesInErrorState(); + reply.writeNoException(); + reply.writeTypedList(list); + return true; + } + + case GET_RUNNING_APP_PROCESSES_TRANSACTION: { + data.enforceInterface(IActivityManager.descriptor); + List<ActivityManager.RunningAppProcessInfo> list = getRunningAppProcesses(); + reply.writeNoException(); + reply.writeTypedList(list); + return true; + } + + case MOVE_TASK_TO_FRONT_TRANSACTION: { + data.enforceInterface(IActivityManager.descriptor); + int task = data.readInt(); + moveTaskToFront(task); + reply.writeNoException(); + return true; + } + + case MOVE_TASK_TO_BACK_TRANSACTION: { + data.enforceInterface(IActivityManager.descriptor); + int task = data.readInt(); + moveTaskToBack(task); + reply.writeNoException(); + return true; + } + + case MOVE_ACTIVITY_TASK_TO_BACK_TRANSACTION: { + data.enforceInterface(IActivityManager.descriptor); + IBinder token = data.readStrongBinder(); + boolean nonRoot = data.readInt() != 0; + boolean res = moveActivityTaskToBack(token, nonRoot); + reply.writeNoException(); + reply.writeInt(res ? 1 : 0); + return true; + } + + case MOVE_TASK_BACKWARDS_TRANSACTION: { + data.enforceInterface(IActivityManager.descriptor); + int task = data.readInt(); + moveTaskBackwards(task); + reply.writeNoException(); + return true; + } + + case GET_TASK_FOR_ACTIVITY_TRANSACTION: { + data.enforceInterface(IActivityManager.descriptor); + IBinder token = data.readStrongBinder(); + boolean onlyRoot = data.readInt() != 0; + int res = token != null + ? getTaskForActivity(token, onlyRoot) : -1; + reply.writeNoException(); + reply.writeInt(res); + return true; + } + + case FINISH_OTHER_INSTANCES_TRANSACTION: { + data.enforceInterface(IActivityManager.descriptor); + IBinder token = data.readStrongBinder(); + ComponentName className = ComponentName.readFromParcel(data); + finishOtherInstances(token, className); + reply.writeNoException(); + return true; + } + + case REPORT_THUMBNAIL_TRANSACTION: { + data.enforceInterface(IActivityManager.descriptor); + IBinder token = data.readStrongBinder(); + Bitmap thumbnail = data.readInt() != 0 + ? Bitmap.CREATOR.createFromParcel(data) : null; + CharSequence description = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(data); + reportThumbnail(token, thumbnail, description); + reply.writeNoException(); + return true; + } + + case GET_CONTENT_PROVIDER_TRANSACTION: { + data.enforceInterface(IActivityManager.descriptor); + IBinder b = data.readStrongBinder(); + IApplicationThread app = ApplicationThreadNative.asInterface(b); + String name = data.readString(); + ContentProviderHolder cph = getContentProvider(app, name); + reply.writeNoException(); + if (cph != null) { + reply.writeInt(1); + cph.writeToParcel(reply, 0); + } else { + reply.writeInt(0); + } + return true; + } + + case PUBLISH_CONTENT_PROVIDERS_TRANSACTION: { + data.enforceInterface(IActivityManager.descriptor); + IBinder b = data.readStrongBinder(); + IApplicationThread app = ApplicationThreadNative.asInterface(b); + ArrayList<ContentProviderHolder> providers = + data.createTypedArrayList(ContentProviderHolder.CREATOR); + publishContentProviders(app, providers); + reply.writeNoException(); + return true; + } + + case REMOVE_CONTENT_PROVIDER_TRANSACTION: { + data.enforceInterface(IActivityManager.descriptor); + IBinder b = data.readStrongBinder(); + IApplicationThread app = ApplicationThreadNative.asInterface(b); + String name = data.readString(); + removeContentProvider(app, name); + reply.writeNoException(); + return true; + } + + case START_SERVICE_TRANSACTION: { + data.enforceInterface(IActivityManager.descriptor); + IBinder b = data.readStrongBinder(); + IApplicationThread app = ApplicationThreadNative.asInterface(b); + Intent service = Intent.CREATOR.createFromParcel(data); + String resolvedType = data.readString(); + ComponentName cn = startService(app, service, resolvedType); + reply.writeNoException(); + ComponentName.writeToParcel(cn, reply); + return true; + } + + case STOP_SERVICE_TRANSACTION: { + data.enforceInterface(IActivityManager.descriptor); + IBinder b = data.readStrongBinder(); + IApplicationThread app = ApplicationThreadNative.asInterface(b); + Intent service = Intent.CREATOR.createFromParcel(data); + String resolvedType = data.readString(); + int res = stopService(app, service, resolvedType); + reply.writeNoException(); + reply.writeInt(res); + return true; + } + + case STOP_SERVICE_TOKEN_TRANSACTION: { + data.enforceInterface(IActivityManager.descriptor); + ComponentName className = ComponentName.readFromParcel(data); + IBinder token = data.readStrongBinder(); + int startId = data.readInt(); + boolean res = stopServiceToken(className, token, startId); + reply.writeNoException(); + reply.writeInt(res ? 1 : 0); + return true; + } + + case SET_SERVICE_FOREGROUND_TRANSACTION: { + data.enforceInterface(IActivityManager.descriptor); + ComponentName className = ComponentName.readFromParcel(data); + IBinder token = data.readStrongBinder(); + boolean isForeground = data.readInt() != 0; + setServiceForeground(className, token, isForeground); + reply.writeNoException(); + return true; + } + + case BIND_SERVICE_TRANSACTION: { + data.enforceInterface(IActivityManager.descriptor); + IBinder b = data.readStrongBinder(); + IApplicationThread app = ApplicationThreadNative.asInterface(b); + IBinder token = data.readStrongBinder(); + Intent service = Intent.CREATOR.createFromParcel(data); + String resolvedType = data.readString(); + b = data.readStrongBinder(); + int fl = data.readInt(); + IServiceConnection conn = IServiceConnection.Stub.asInterface(b); + int res = bindService(app, token, service, resolvedType, conn, fl); + reply.writeNoException(); + reply.writeInt(res); + return true; + } + + case UNBIND_SERVICE_TRANSACTION: { + data.enforceInterface(IActivityManager.descriptor); + IBinder b = data.readStrongBinder(); + IServiceConnection conn = IServiceConnection.Stub.asInterface(b); + boolean res = unbindService(conn); + reply.writeNoException(); + reply.writeInt(res ? 1 : 0); + return true; + } + + case PUBLISH_SERVICE_TRANSACTION: { + data.enforceInterface(IActivityManager.descriptor); + IBinder token = data.readStrongBinder(); + Intent intent = Intent.CREATOR.createFromParcel(data); + IBinder service = data.readStrongBinder(); + publishService(token, intent, service); + reply.writeNoException(); + return true; + } + + case UNBIND_FINISHED_TRANSACTION: { + data.enforceInterface(IActivityManager.descriptor); + IBinder token = data.readStrongBinder(); + Intent intent = Intent.CREATOR.createFromParcel(data); + boolean doRebind = data.readInt() != 0; + unbindFinished(token, intent, doRebind); + reply.writeNoException(); + return true; + } + + case SERVICE_DONE_EXECUTING_TRANSACTION: { + data.enforceInterface(IActivityManager.descriptor); + IBinder token = data.readStrongBinder(); + serviceDoneExecuting(token); + reply.writeNoException(); + return true; + } + + case START_INSTRUMENTATION_TRANSACTION: { + data.enforceInterface(IActivityManager.descriptor); + ComponentName className = ComponentName.readFromParcel(data); + String profileFile = data.readString(); + int fl = data.readInt(); + Bundle arguments = data.readBundle(); + IBinder b = data.readStrongBinder(); + IInstrumentationWatcher w = IInstrumentationWatcher.Stub.asInterface(b); + boolean res = startInstrumentation(className, profileFile, fl, arguments, w); + reply.writeNoException(); + reply.writeInt(res ? 1 : 0); + return true; + } + + + case FINISH_INSTRUMENTATION_TRANSACTION: { + data.enforceInterface(IActivityManager.descriptor); + IBinder b = data.readStrongBinder(); + IApplicationThread app = ApplicationThreadNative.asInterface(b); + int resultCode = data.readInt(); + Bundle results = data.readBundle(); + finishInstrumentation(app, resultCode, results); + reply.writeNoException(); + return true; + } + + case GET_CONFIGURATION_TRANSACTION: { + data.enforceInterface(IActivityManager.descriptor); + Configuration config = getConfiguration(); + reply.writeNoException(); + config.writeToParcel(reply, 0); + return true; + } + + case UPDATE_CONFIGURATION_TRANSACTION: { + data.enforceInterface(IActivityManager.descriptor); + Configuration config = Configuration.CREATOR.createFromParcel(data); + updateConfiguration(config); + reply.writeNoException(); + return true; + } + + case SET_REQUESTED_ORIENTATION_TRANSACTION: { + data.enforceInterface(IActivityManager.descriptor); + IBinder token = data.readStrongBinder(); + int requestedOrientation = data.readInt(); + setRequestedOrientation(token, requestedOrientation); + reply.writeNoException(); + return true; + } + + case GET_REQUESTED_ORIENTATION_TRANSACTION: { + data.enforceInterface(IActivityManager.descriptor); + IBinder token = data.readStrongBinder(); + int req = getRequestedOrientation(token); + reply.writeNoException(); + reply.writeInt(req); + return true; + } + + case GET_ACTIVITY_CLASS_FOR_TOKEN_TRANSACTION: { + data.enforceInterface(IActivityManager.descriptor); + IBinder token = data.readStrongBinder(); + ComponentName cn = getActivityClassForToken(token); + reply.writeNoException(); + ComponentName.writeToParcel(cn, reply); + return true; + } + + case GET_PACKAGE_FOR_TOKEN_TRANSACTION: { + data.enforceInterface(IActivityManager.descriptor); + IBinder token = data.readStrongBinder(); + reply.writeNoException(); + reply.writeString(getPackageForToken(token)); + return true; + } + + case GET_INTENT_SENDER_TRANSACTION: { + data.enforceInterface(IActivityManager.descriptor); + int type = data.readInt(); + String packageName = data.readString(); + IBinder token = data.readStrongBinder(); + String resultWho = data.readString(); + int requestCode = data.readInt(); + Intent requestIntent = data.readInt() != 0 + ? Intent.CREATOR.createFromParcel(data) : null; + String requestResolvedType = data.readString(); + int fl = data.readInt(); + IIntentSender res = getIntentSender(type, packageName, token, + resultWho, requestCode, requestIntent, + requestResolvedType, fl); + reply.writeNoException(); + reply.writeStrongBinder(res != null ? res.asBinder() : null); + return true; + } + + case CANCEL_INTENT_SENDER_TRANSACTION: { + data.enforceInterface(IActivityManager.descriptor); + IIntentSender r = IIntentSender.Stub.asInterface( + data.readStrongBinder()); + cancelIntentSender(r); + reply.writeNoException(); + return true; + } + + case GET_PACKAGE_FOR_INTENT_SENDER_TRANSACTION: { + data.enforceInterface(IActivityManager.descriptor); + IIntentSender r = IIntentSender.Stub.asInterface( + data.readStrongBinder()); + String res = getPackageForIntentSender(r); + reply.writeNoException(); + reply.writeString(res); + return true; + } + + case SET_PROCESS_LIMIT_TRANSACTION: { + data.enforceInterface(IActivityManager.descriptor); + int max = data.readInt(); + setProcessLimit(max); + reply.writeNoException(); + return true; + } + + case GET_PROCESS_LIMIT_TRANSACTION: { + data.enforceInterface(IActivityManager.descriptor); + int limit = getProcessLimit(); + reply.writeNoException(); + reply.writeInt(limit); + return true; + } + + case SET_PROCESS_FOREGROUND_TRANSACTION: { + data.enforceInterface(IActivityManager.descriptor); + IBinder token = data.readStrongBinder(); + int pid = data.readInt(); + boolean isForeground = data.readInt() != 0; + setProcessForeground(token, pid, isForeground); + reply.writeNoException(); + return true; + } + + case CHECK_PERMISSION_TRANSACTION: { + data.enforceInterface(IActivityManager.descriptor); + String perm = data.readString(); + int pid = data.readInt(); + int uid = data.readInt(); + int res = checkPermission(perm, pid, uid); + reply.writeNoException(); + reply.writeInt(res); + return true; + } + + case CHECK_URI_PERMISSION_TRANSACTION: { + data.enforceInterface(IActivityManager.descriptor); + Uri uri = Uri.CREATOR.createFromParcel(data); + int pid = data.readInt(); + int uid = data.readInt(); + int mode = data.readInt(); + int res = checkUriPermission(uri, pid, uid, mode); + reply.writeNoException(); + reply.writeInt(res); + return true; + } + + case CLEAR_APP_DATA_TRANSACTION: { + data.enforceInterface(IActivityManager.descriptor); + String packageName = data.readString(); + IPackageDataObserver observer = IPackageDataObserver.Stub.asInterface( + data.readStrongBinder()); + boolean res = clearApplicationUserData(packageName, observer); + reply.writeNoException(); + reply.writeInt(res ? 1 : 0); + return true; + } + + case GRANT_URI_PERMISSION_TRANSACTION: { + data.enforceInterface(IActivityManager.descriptor); + IBinder b = data.readStrongBinder(); + IApplicationThread app = ApplicationThreadNative.asInterface(b); + String targetPkg = data.readString(); + Uri uri = Uri.CREATOR.createFromParcel(data); + int mode = data.readInt(); + grantUriPermission(app, targetPkg, uri, mode); + reply.writeNoException(); + return true; + } + + case REVOKE_URI_PERMISSION_TRANSACTION: { + data.enforceInterface(IActivityManager.descriptor); + IBinder b = data.readStrongBinder(); + IApplicationThread app = ApplicationThreadNative.asInterface(b); + Uri uri = Uri.CREATOR.createFromParcel(data); + int mode = data.readInt(); + revokeUriPermission(app, uri, mode); + reply.writeNoException(); + return true; + } + + case SHOW_WAITING_FOR_DEBUGGER_TRANSACTION: { + data.enforceInterface(IActivityManager.descriptor); + IBinder b = data.readStrongBinder(); + IApplicationThread app = ApplicationThreadNative.asInterface(b); + boolean waiting = data.readInt() != 0; + showWaitingForDebugger(app, waiting); + reply.writeNoException(); + return true; + } + + case GET_MEMORY_INFO_TRANSACTION: { + data.enforceInterface(IActivityManager.descriptor); + ActivityManager.MemoryInfo mi = new ActivityManager.MemoryInfo(); + getMemoryInfo(mi); + reply.writeNoException(); + mi.writeToParcel(reply, 0); + return true; + } + + case UNHANDLED_BACK_TRANSACTION: { + data.enforceInterface(IActivityManager.descriptor); + unhandledBack(); + reply.writeNoException(); + return true; + } + + case OPEN_CONTENT_URI_TRANSACTION: { + data.enforceInterface(IActivityManager.descriptor); + Uri uri = Uri.parse(data.readString()); + ParcelFileDescriptor pfd = openContentUri(uri); + reply.writeNoException(); + if (pfd != null) { + reply.writeInt(1); + pfd.writeToParcel(reply, Parcelable.PARCELABLE_WRITE_RETURN_VALUE); + } else { + reply.writeInt(0); + } + return true; + } + + case GOING_TO_SLEEP_TRANSACTION: { + data.enforceInterface(IActivityManager.descriptor); + goingToSleep(); + reply.writeNoException(); + return true; + } + + case WAKING_UP_TRANSACTION: { + data.enforceInterface(IActivityManager.descriptor); + wakingUp(); + reply.writeNoException(); + return true; + } + + case SET_DEBUG_APP_TRANSACTION: { + data.enforceInterface(IActivityManager.descriptor); + String pn = data.readString(); + boolean wfd = data.readInt() != 0; + boolean per = data.readInt() != 0; + setDebugApp(pn, wfd, per); + reply.writeNoException(); + return true; + } + + case SET_ALWAYS_FINISH_TRANSACTION: { + data.enforceInterface(IActivityManager.descriptor); + boolean enabled = data.readInt() != 0; + setAlwaysFinish(enabled); + reply.writeNoException(); + return true; + } + + case SET_ACTIVITY_WATCHER_TRANSACTION: { + data.enforceInterface(IActivityManager.descriptor); + IActivityWatcher watcher = IActivityWatcher.Stub.asInterface( + data.readStrongBinder()); + setActivityWatcher(watcher); + return true; + } + + case ENTER_SAFE_MODE_TRANSACTION: { + data.enforceInterface(IActivityManager.descriptor); + enterSafeMode(); + reply.writeNoException(); + return true; + } + + case NOTE_WAKEUP_ALARM_TRANSACTION: { + data.enforceInterface(IActivityManager.descriptor); + IIntentSender is = IIntentSender.Stub.asInterface( + data.readStrongBinder()); + noteWakeupAlarm(is); + reply.writeNoException(); + return true; + } + + case KILL_PIDS_FOR_MEMORY_TRANSACTION: { + data.enforceInterface(IActivityManager.descriptor); + int[] pids = data.createIntArray(); + boolean res = killPidsForMemory(pids); + reply.writeNoException(); + reply.writeInt(res ? 1 : 0); + return true; + } + + case REPORT_PSS_TRANSACTION: { + data.enforceInterface(IActivityManager.descriptor); + IBinder b = data.readStrongBinder(); + IApplicationThread app = ApplicationThreadNative.asInterface(b); + int pss = data.readInt(); + reportPss(app, pss); + reply.writeNoException(); + return true; + } + + case START_RUNNING_TRANSACTION: { + data.enforceInterface(IActivityManager.descriptor); + String pkg = data.readString(); + String cls = data.readString(); + String action = data.readString(); + String indata = data.readString(); + startRunning(pkg, cls, action, indata); + reply.writeNoException(); + return true; + } + + case SYSTEM_READY_TRANSACTION: { + data.enforceInterface(IActivityManager.descriptor); + systemReady(); + reply.writeNoException(); + return true; + } + + case HANDLE_APPLICATION_ERROR_TRANSACTION: { + data.enforceInterface(IActivityManager.descriptor); + IBinder app = data.readStrongBinder(); + int fl = data.readInt(); + String tag = data.readString(); + String shortMsg = data.readString(); + String longMsg = data.readString(); + byte[] crashData = data.createByteArray(); + int res = handleApplicationError(app, fl, tag, shortMsg, longMsg, + crashData); + reply.writeNoException(); + reply.writeInt(res); + return true; + } + + case SIGNAL_PERSISTENT_PROCESSES_TRANSACTION: { + data.enforceInterface(IActivityManager.descriptor); + int sig = data.readInt(); + signalPersistentProcesses(sig); + reply.writeNoException(); + return true; + } + + case RESTART_PACKAGE_TRANSACTION: { + data.enforceInterface(IActivityManager.descriptor); + String packageName = data.readString(); + restartPackage(packageName); + reply.writeNoException(); + return true; + } + + case GET_DEVICE_CONFIGURATION_TRANSACTION: { + data.enforceInterface(IActivityManager.descriptor); + ConfigurationInfo config = getDeviceConfigurationInfo(); + reply.writeNoException(); + config.writeToParcel(reply, 0); + return true; + } + + case PEEK_SERVICE_TRANSACTION: { + data.enforceInterface(IActivityManager.descriptor); + Intent service = Intent.CREATOR.createFromParcel(data); + String resolvedType = data.readString(); + IBinder binder = peekService(service, resolvedType); + reply.writeNoException(); + reply.writeStrongBinder(binder); + return true; + } + } + + return super.onTransact(code, data, reply, flags); + } + + public IBinder asBinder() + { + return this; + } + + private static IActivityManager gDefault; +} + +class ActivityManagerProxy implements IActivityManager +{ + public ActivityManagerProxy(IBinder remote) + { + mRemote = remote; + } + + public IBinder asBinder() + { + return mRemote; + } + + public int startActivity(IApplicationThread caller, Intent intent, + String resolvedType, Uri[] grantedUriPermissions, int grantedMode, + IBinder resultTo, String resultWho, + int requestCode, boolean onlyIfNeeded, + boolean debug) throws RemoteException { + Parcel data = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + data.writeInterfaceToken(IActivityManager.descriptor); + data.writeStrongBinder(caller != null ? caller.asBinder() : null); + intent.writeToParcel(data, 0); + data.writeString(resolvedType); + data.writeTypedArray(grantedUriPermissions, 0); + data.writeInt(grantedMode); + data.writeStrongBinder(resultTo); + data.writeString(resultWho); + data.writeInt(requestCode); + data.writeInt(onlyIfNeeded ? 1 : 0); + data.writeInt(debug ? 1 : 0); + mRemote.transact(START_ACTIVITY_TRANSACTION, data, reply, 0); + reply.readException(); + int result = reply.readInt(); + reply.recycle(); + data.recycle(); + return result; + } + public boolean startNextMatchingActivity(IBinder callingActivity, + Intent intent) throws RemoteException { + Parcel data = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + data.writeInterfaceToken(IActivityManager.descriptor); + data.writeStrongBinder(callingActivity); + intent.writeToParcel(data, 0); + mRemote.transact(START_NEXT_MATCHING_ACTIVITY_TRANSACTION, data, reply, 0); + reply.readException(); + int result = reply.readInt(); + reply.recycle(); + data.recycle(); + return result != 0; + } + public boolean finishActivity(IBinder token, int resultCode, Intent resultData) + throws RemoteException { + Parcel data = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + data.writeInterfaceToken(IActivityManager.descriptor); + data.writeStrongBinder(token); + data.writeInt(resultCode); + if (resultData != null) { + data.writeInt(1); + resultData.writeToParcel(data, 0); + } else { + data.writeInt(0); + } + mRemote.transact(FINISH_ACTIVITY_TRANSACTION, data, reply, 0); + reply.readException(); + boolean res = reply.readInt() != 0; + data.recycle(); + reply.recycle(); + return res; + } + public void finishSubActivity(IBinder token, String resultWho, int requestCode) throws RemoteException + { + Parcel data = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + data.writeInterfaceToken(IActivityManager.descriptor); + data.writeStrongBinder(token); + data.writeString(resultWho); + data.writeInt(requestCode); + mRemote.transact(FINISH_SUB_ACTIVITY_TRANSACTION, data, reply, 0); + reply.readException(); + data.recycle(); + reply.recycle(); + } + public Intent registerReceiver(IApplicationThread caller, + IIntentReceiver receiver, + IntentFilter filter, String perm) throws RemoteException + { + Parcel data = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + data.writeInterfaceToken(IActivityManager.descriptor); + data.writeStrongBinder(caller != null ? caller.asBinder() : null); + data.writeStrongBinder(receiver != null ? receiver.asBinder() : null); + filter.writeToParcel(data, 0); + data.writeString(perm); + mRemote.transact(REGISTER_RECEIVER_TRANSACTION, data, reply, 0); + reply.readException(); + Intent intent = null; + int haveIntent = reply.readInt(); + if (haveIntent != 0) { + intent = Intent.CREATOR.createFromParcel(reply); + } + reply.recycle(); + data.recycle(); + return intent; + } + public void unregisterReceiver(IIntentReceiver receiver) throws RemoteException + { + Parcel data = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + data.writeInterfaceToken(IActivityManager.descriptor); + data.writeStrongBinder(receiver.asBinder()); + mRemote.transact(UNREGISTER_RECEIVER_TRANSACTION, data, reply, 0); + reply.readException(); + data.recycle(); + reply.recycle(); + } + public int broadcastIntent(IApplicationThread caller, + Intent intent, String resolvedType, IIntentReceiver resultTo, + int resultCode, String resultData, Bundle map, + String requiredPermission, boolean serialized, + boolean sticky) throws RemoteException + { + Parcel data = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + data.writeInterfaceToken(IActivityManager.descriptor); + data.writeStrongBinder(caller != null ? caller.asBinder() : null); + intent.writeToParcel(data, 0); + data.writeString(resolvedType); + data.writeStrongBinder(resultTo != null ? resultTo.asBinder() : null); + data.writeInt(resultCode); + data.writeString(resultData); + data.writeBundle(map); + data.writeString(requiredPermission); + data.writeInt(serialized ? 1 : 0); + data.writeInt(sticky ? 1 : 0); + mRemote.transact(BROADCAST_INTENT_TRANSACTION, data, reply, 0); + reply.readException(); + int res = reply.readInt(); + reply.recycle(); + data.recycle(); + return res; + } + public void unbroadcastIntent(IApplicationThread caller, Intent intent) throws RemoteException + { + Parcel data = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + data.writeInterfaceToken(IActivityManager.descriptor); + data.writeStrongBinder(caller != null ? caller.asBinder() : null); + intent.writeToParcel(data, 0); + mRemote.transact(UNBROADCAST_INTENT_TRANSACTION, data, reply, 0); + reply.readException(); + data.recycle(); + reply.recycle(); + } + public void finishReceiver(IBinder who, int resultCode, String resultData, Bundle map, boolean abortBroadcast) throws RemoteException + { + Parcel data = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + data.writeInterfaceToken(IActivityManager.descriptor); + data.writeStrongBinder(who); + data.writeInt(resultCode); + data.writeString(resultData); + data.writeBundle(map); + data.writeInt(abortBroadcast ? 1 : 0); + mRemote.transact(FINISH_RECEIVER_TRANSACTION, data, reply, IBinder.FLAG_ONEWAY); + reply.readException(); + data.recycle(); + reply.recycle(); + } + public void setPersistent(IBinder token, boolean isPersistent) throws RemoteException + { + Parcel data = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + data.writeInterfaceToken(IActivityManager.descriptor); + data.writeStrongBinder(token); + data.writeInt(isPersistent ? 1 : 0); + mRemote.transact(SET_PERSISTENT_TRANSACTION, data, reply, 0); + reply.readException(); + data.recycle(); + reply.recycle(); + } + public void attachApplication(IApplicationThread app) throws RemoteException + { + Parcel data = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + data.writeInterfaceToken(IActivityManager.descriptor); + data.writeStrongBinder(app.asBinder()); + mRemote.transact(ATTACH_APPLICATION_TRANSACTION, data, reply, 0); + reply.readException(); + data.recycle(); + reply.recycle(); + } + public void activityIdle(IBinder token) throws RemoteException + { + Parcel data = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + data.writeInterfaceToken(IActivityManager.descriptor); + data.writeStrongBinder(token); + mRemote.transact(ACTIVITY_IDLE_TRANSACTION, data, reply, IBinder.FLAG_ONEWAY); + reply.readException(); + data.recycle(); + reply.recycle(); + } + public void activityPaused(IBinder token, Bundle state) throws RemoteException + { + Parcel data = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + data.writeInterfaceToken(IActivityManager.descriptor); + data.writeStrongBinder(token); + data.writeBundle(state); + mRemote.transact(ACTIVITY_PAUSED_TRANSACTION, data, reply, 0); + reply.readException(); + data.recycle(); + reply.recycle(); + } + public void activityStopped(IBinder token, + Bitmap thumbnail, CharSequence description) throws RemoteException + { + Parcel data = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + data.writeInterfaceToken(IActivityManager.descriptor); + data.writeStrongBinder(token); + if (thumbnail != null) { + data.writeInt(1); + thumbnail.writeToParcel(data, 0); + } else { + data.writeInt(0); + } + TextUtils.writeToParcel(description, data, 0); + mRemote.transact(ACTIVITY_STOPPED_TRANSACTION, data, reply, IBinder.FLAG_ONEWAY); + reply.readException(); + data.recycle(); + reply.recycle(); + } + public void activityDestroyed(IBinder token) throws RemoteException + { + Parcel data = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + data.writeInterfaceToken(IActivityManager.descriptor); + data.writeStrongBinder(token); + mRemote.transact(ACTIVITY_DESTROYED_TRANSACTION, data, reply, IBinder.FLAG_ONEWAY); + reply.readException(); + data.recycle(); + reply.recycle(); + } + public String getCallingPackage(IBinder token) throws RemoteException + { + Parcel data = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + data.writeInterfaceToken(IActivityManager.descriptor); + data.writeStrongBinder(token); + mRemote.transact(GET_CALLING_PACKAGE_TRANSACTION, data, reply, 0); + reply.readException(); + String res = reply.readString(); + data.recycle(); + reply.recycle(); + return res; + } + public ComponentName getCallingActivity(IBinder token) + throws RemoteException { + Parcel data = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + data.writeInterfaceToken(IActivityManager.descriptor); + data.writeStrongBinder(token); + mRemote.transact(GET_CALLING_ACTIVITY_TRANSACTION, data, reply, 0); + reply.readException(); + ComponentName res = ComponentName.readFromParcel(reply); + data.recycle(); + reply.recycle(); + return res; + } + public List getTasks(int maxNum, int flags, + IThumbnailReceiver receiver) throws RemoteException { + Parcel data = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + data.writeInterfaceToken(IActivityManager.descriptor); + data.writeInt(maxNum); + data.writeInt(flags); + data.writeStrongBinder(receiver != null ? receiver.asBinder() : null); + mRemote.transact(GET_TASKS_TRANSACTION, data, reply, 0); + reply.readException(); + ArrayList list = null; + int N = reply.readInt(); + if (N >= 0) { + list = new ArrayList(); + while (N > 0) { + ActivityManager.RunningTaskInfo info = + ActivityManager.RunningTaskInfo.CREATOR + .createFromParcel(reply); + list.add(info); + N--; + } + } + data.recycle(); + reply.recycle(); + return list; + } + public List<ActivityManager.RecentTaskInfo> getRecentTasks(int maxNum, + int flags) throws RemoteException { + Parcel data = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + data.writeInterfaceToken(IActivityManager.descriptor); + data.writeInt(maxNum); + data.writeInt(flags); + mRemote.transact(GET_RECENT_TASKS_TRANSACTION, data, reply, 0); + reply.readException(); + ArrayList<ActivityManager.RecentTaskInfo> list + = reply.createTypedArrayList(ActivityManager.RecentTaskInfo.CREATOR); + data.recycle(); + reply.recycle(); + return list; + } + public List getServices(int maxNum, int flags) throws RemoteException { + Parcel data = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + data.writeInterfaceToken(IActivityManager.descriptor); + data.writeInt(maxNum); + data.writeInt(flags); + mRemote.transact(GET_SERVICES_TRANSACTION, data, reply, 0); + reply.readException(); + ArrayList list = null; + int N = reply.readInt(); + if (N >= 0) { + list = new ArrayList(); + while (N > 0) { + ActivityManager.RunningServiceInfo info = + ActivityManager.RunningServiceInfo.CREATOR + .createFromParcel(reply); + list.add(info); + N--; + } + } + data.recycle(); + reply.recycle(); + return list; + } + public List<ActivityManager.ProcessErrorStateInfo> getProcessesInErrorState() + throws RemoteException { + Parcel data = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + data.writeInterfaceToken(IActivityManager.descriptor); + mRemote.transact(GET_PROCESSES_IN_ERROR_STATE_TRANSACTION, data, reply, 0); + reply.readException(); + ArrayList<ActivityManager.ProcessErrorStateInfo> list + = reply.createTypedArrayList(ActivityManager.ProcessErrorStateInfo.CREATOR); + data.recycle(); + reply.recycle(); + return list; + } + public List<ActivityManager.RunningAppProcessInfo> getRunningAppProcesses() + throws RemoteException { + Parcel data = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + data.writeInterfaceToken(IActivityManager.descriptor); + mRemote.transact(GET_RUNNING_APP_PROCESSES_TRANSACTION, data, reply, 0); + reply.readException(); + ArrayList<ActivityManager.RunningAppProcessInfo> list + = reply.createTypedArrayList(ActivityManager.RunningAppProcessInfo.CREATOR); + data.recycle(); + reply.recycle(); + return list; + } + public void moveTaskToFront(int task) throws RemoteException + { + Parcel data = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + data.writeInterfaceToken(IActivityManager.descriptor); + data.writeInt(task); + mRemote.transact(MOVE_TASK_TO_FRONT_TRANSACTION, data, reply, 0); + reply.readException(); + data.recycle(); + reply.recycle(); + } + public void moveTaskToBack(int task) throws RemoteException + { + Parcel data = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + data.writeInterfaceToken(IActivityManager.descriptor); + data.writeInt(task); + mRemote.transact(MOVE_TASK_TO_BACK_TRANSACTION, data, reply, 0); + reply.readException(); + data.recycle(); + reply.recycle(); + } + public boolean moveActivityTaskToBack(IBinder token, boolean nonRoot) + throws RemoteException { + Parcel data = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + data.writeInterfaceToken(IActivityManager.descriptor); + data.writeStrongBinder(token); + data.writeInt(nonRoot ? 1 : 0); + mRemote.transact(MOVE_ACTIVITY_TASK_TO_BACK_TRANSACTION, data, reply, 0); + reply.readException(); + boolean res = reply.readInt() != 0; + data.recycle(); + reply.recycle(); + return res; + } + public void moveTaskBackwards(int task) throws RemoteException + { + Parcel data = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + data.writeInterfaceToken(IActivityManager.descriptor); + data.writeInt(task); + mRemote.transact(MOVE_TASK_BACKWARDS_TRANSACTION, data, reply, 0); + reply.readException(); + data.recycle(); + reply.recycle(); + } + public int getTaskForActivity(IBinder token, boolean onlyRoot) throws RemoteException + { + Parcel data = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + data.writeInterfaceToken(IActivityManager.descriptor); + data.writeStrongBinder(token); + data.writeInt(onlyRoot ? 1 : 0); + mRemote.transact(GET_TASK_FOR_ACTIVITY_TRANSACTION, data, reply, 0); + reply.readException(); + int res = reply.readInt(); + data.recycle(); + reply.recycle(); + return res; + } + public void finishOtherInstances(IBinder token, ComponentName className) throws RemoteException + { + Parcel data = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + data.writeInterfaceToken(IActivityManager.descriptor); + data.writeStrongBinder(token); + ComponentName.writeToParcel(className, data); + mRemote.transact(FINISH_OTHER_INSTANCES_TRANSACTION, data, reply, 0); + reply.readException(); + data.recycle(); + reply.recycle(); + } + public void reportThumbnail(IBinder token, + Bitmap thumbnail, CharSequence description) throws RemoteException + { + Parcel data = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + data.writeInterfaceToken(IActivityManager.descriptor); + data.writeStrongBinder(token); + if (thumbnail != null) { + data.writeInt(1); + thumbnail.writeToParcel(data, 0); + } else { + data.writeInt(0); + } + TextUtils.writeToParcel(description, data, 0); + mRemote.transact(REPORT_THUMBNAIL_TRANSACTION, data, reply, IBinder.FLAG_ONEWAY); + reply.readException(); + data.recycle(); + reply.recycle(); + } + public ContentProviderHolder getContentProvider(IApplicationThread caller, + String name) throws RemoteException + { + Parcel data = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + data.writeInterfaceToken(IActivityManager.descriptor); + data.writeStrongBinder(caller != null ? caller.asBinder() : null); + data.writeString(name); + mRemote.transact(GET_CONTENT_PROVIDER_TRANSACTION, data, reply, 0); + reply.readException(); + int res = reply.readInt(); + ContentProviderHolder cph = null; + if (res != 0) { + cph = ContentProviderHolder.CREATOR.createFromParcel(reply); + } + data.recycle(); + reply.recycle(); + return cph; + } + public void publishContentProviders(IApplicationThread caller, + List<ContentProviderHolder> providers) throws RemoteException + { + Parcel data = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + data.writeInterfaceToken(IActivityManager.descriptor); + data.writeStrongBinder(caller != null ? caller.asBinder() : null); + data.writeTypedList(providers); + mRemote.transact(PUBLISH_CONTENT_PROVIDERS_TRANSACTION, data, reply, 0); + reply.readException(); + data.recycle(); + reply.recycle(); + } + + public void removeContentProvider(IApplicationThread caller, + String name) throws RemoteException { + Parcel data = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + data.writeInterfaceToken(IActivityManager.descriptor); + data.writeStrongBinder(caller != null ? caller.asBinder() : null); + data.writeString(name); + mRemote.transact(REMOVE_CONTENT_PROVIDER_TRANSACTION, data, reply, 0); + reply.readException(); + data.recycle(); + reply.recycle(); + } + + public ComponentName startService(IApplicationThread caller, Intent service, + String resolvedType) throws RemoteException + { + Parcel data = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + data.writeInterfaceToken(IActivityManager.descriptor); + data.writeStrongBinder(caller != null ? caller.asBinder() : null); + service.writeToParcel(data, 0); + data.writeString(resolvedType); + mRemote.transact(START_SERVICE_TRANSACTION, data, reply, 0); + reply.readException(); + ComponentName res = ComponentName.readFromParcel(reply); + data.recycle(); + reply.recycle(); + return res; + } + public int stopService(IApplicationThread caller, Intent service, + String resolvedType) throws RemoteException + { + Parcel data = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + data.writeInterfaceToken(IActivityManager.descriptor); + data.writeStrongBinder(caller != null ? caller.asBinder() : null); + service.writeToParcel(data, 0); + data.writeString(resolvedType); + mRemote.transact(STOP_SERVICE_TRANSACTION, data, reply, 0); + reply.readException(); + int res = reply.readInt(); + reply.recycle(); + data.recycle(); + return res; + } + public boolean stopServiceToken(ComponentName className, IBinder token, + int startId) throws RemoteException { + Parcel data = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + data.writeInterfaceToken(IActivityManager.descriptor); + ComponentName.writeToParcel(className, data); + data.writeStrongBinder(token); + data.writeInt(startId); + mRemote.transact(STOP_SERVICE_TOKEN_TRANSACTION, data, reply, 0); + reply.readException(); + boolean res = reply.readInt() != 0; + data.recycle(); + reply.recycle(); + return res; + } + public void setServiceForeground(ComponentName className, IBinder token, + boolean isForeground) throws RemoteException { + Parcel data = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + data.writeInterfaceToken(IActivityManager.descriptor); + ComponentName.writeToParcel(className, data); + data.writeStrongBinder(token); + data.writeInt(isForeground ? 1 : 0); + mRemote.transact(SET_SERVICE_FOREGROUND_TRANSACTION, data, reply, 0); + reply.readException(); + data.recycle(); + reply.recycle(); + } + public int bindService(IApplicationThread caller, IBinder token, + Intent service, String resolvedType, IServiceConnection connection, + int flags) throws RemoteException { + Parcel data = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + data.writeInterfaceToken(IActivityManager.descriptor); + data.writeStrongBinder(caller != null ? caller.asBinder() : null); + data.writeStrongBinder(token); + service.writeToParcel(data, 0); + data.writeString(resolvedType); + data.writeStrongBinder(connection.asBinder()); + data.writeInt(flags); + mRemote.transact(BIND_SERVICE_TRANSACTION, data, reply, 0); + reply.readException(); + int res = reply.readInt(); + data.recycle(); + reply.recycle(); + return res; + } + public boolean unbindService(IServiceConnection connection) throws RemoteException + { + Parcel data = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + data.writeInterfaceToken(IActivityManager.descriptor); + data.writeStrongBinder(connection.asBinder()); + mRemote.transact(UNBIND_SERVICE_TRANSACTION, data, reply, 0); + reply.readException(); + boolean res = reply.readInt() != 0; + data.recycle(); + reply.recycle(); + return res; + } + + public void publishService(IBinder token, + Intent intent, IBinder service) throws RemoteException { + Parcel data = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + data.writeInterfaceToken(IActivityManager.descriptor); + data.writeStrongBinder(token); + intent.writeToParcel(data, 0); + data.writeStrongBinder(service); + mRemote.transact(PUBLISH_SERVICE_TRANSACTION, data, reply, 0); + reply.readException(); + data.recycle(); + reply.recycle(); + } + + public void unbindFinished(IBinder token, Intent intent, boolean doRebind) + throws RemoteException { + Parcel data = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + data.writeInterfaceToken(IActivityManager.descriptor); + data.writeStrongBinder(token); + intent.writeToParcel(data, 0); + data.writeInt(doRebind ? 1 : 0); + mRemote.transact(UNBIND_FINISHED_TRANSACTION, data, reply, 0); + reply.readException(); + data.recycle(); + reply.recycle(); + } + + public void serviceDoneExecuting(IBinder token) throws RemoteException { + Parcel data = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + data.writeInterfaceToken(IActivityManager.descriptor); + data.writeStrongBinder(token); + mRemote.transact(SERVICE_DONE_EXECUTING_TRANSACTION, data, reply, IBinder.FLAG_ONEWAY); + reply.readException(); + data.recycle(); + reply.recycle(); + } + + public IBinder peekService(Intent service, String resolvedType) throws RemoteException { + Parcel data = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + data.writeInterfaceToken(IActivityManager.descriptor); + service.writeToParcel(data, 0); + data.writeString(resolvedType); + mRemote.transact(PEEK_SERVICE_TRANSACTION, data, reply, 0); + reply.readException(); + IBinder binder = reply.readStrongBinder(); + reply.recycle(); + data.recycle(); + return binder; + } + + public boolean startInstrumentation(ComponentName className, String profileFile, + int flags, Bundle arguments, IInstrumentationWatcher watcher) + throws RemoteException { + Parcel data = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + data.writeInterfaceToken(IActivityManager.descriptor); + ComponentName.writeToParcel(className, data); + data.writeString(profileFile); + data.writeInt(flags); + data.writeBundle(arguments); + data.writeStrongBinder(watcher != null ? watcher.asBinder() : null); + mRemote.transact(START_INSTRUMENTATION_TRANSACTION, data, reply, 0); + reply.readException(); + boolean res = reply.readInt() != 0; + reply.recycle(); + data.recycle(); + return res; + } + + public void finishInstrumentation(IApplicationThread target, + int resultCode, Bundle results) throws RemoteException { + Parcel data = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + data.writeInterfaceToken(IActivityManager.descriptor); + data.writeStrongBinder(target != null ? target.asBinder() : null); + data.writeInt(resultCode); + data.writeBundle(results); + mRemote.transact(FINISH_INSTRUMENTATION_TRANSACTION, data, reply, 0); + reply.readException(); + data.recycle(); + reply.recycle(); + } + public Configuration getConfiguration() throws RemoteException + { + Parcel data = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + data.writeInterfaceToken(IActivityManager.descriptor); + mRemote.transact(GET_CONFIGURATION_TRANSACTION, data, reply, 0); + reply.readException(); + Configuration res = Configuration.CREATOR.createFromParcel(reply); + reply.recycle(); + data.recycle(); + return res; + } + public void updateConfiguration(Configuration values) throws RemoteException + { + Parcel data = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + data.writeInterfaceToken(IActivityManager.descriptor); + values.writeToParcel(data, 0); + mRemote.transact(UPDATE_CONFIGURATION_TRANSACTION, data, reply, 0); + reply.readException(); + data.recycle(); + reply.recycle(); + } + public void setRequestedOrientation(IBinder token, int requestedOrientation) + throws RemoteException { + Parcel data = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + data.writeInterfaceToken(IActivityManager.descriptor); + data.writeStrongBinder(token); + data.writeInt(requestedOrientation); + mRemote.transact(SET_REQUESTED_ORIENTATION_TRANSACTION, data, reply, 0); + reply.readException(); + data.recycle(); + reply.recycle(); + } + public int getRequestedOrientation(IBinder token) throws RemoteException { + Parcel data = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + data.writeInterfaceToken(IActivityManager.descriptor); + data.writeStrongBinder(token); + mRemote.transact(GET_REQUESTED_ORIENTATION_TRANSACTION, data, reply, 0); + reply.readException(); + int res = reply.readInt(); + data.recycle(); + reply.recycle(); + return res; + } + public ComponentName getActivityClassForToken(IBinder token) + throws RemoteException { + Parcel data = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + data.writeInterfaceToken(IActivityManager.descriptor); + data.writeStrongBinder(token); + mRemote.transact(GET_ACTIVITY_CLASS_FOR_TOKEN_TRANSACTION, data, reply, 0); + reply.readException(); + ComponentName res = ComponentName.readFromParcel(reply); + data.recycle(); + reply.recycle(); + return res; + } + public String getPackageForToken(IBinder token) throws RemoteException + { + Parcel data = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + data.writeInterfaceToken(IActivityManager.descriptor); + data.writeStrongBinder(token); + mRemote.transact(GET_PACKAGE_FOR_TOKEN_TRANSACTION, data, reply, 0); + reply.readException(); + String res = reply.readString(); + data.recycle(); + reply.recycle(); + return res; + } + public IIntentSender getIntentSender(int type, + String packageName, IBinder token, String resultWho, + int requestCode, Intent intent, String resolvedType, int flags) + throws RemoteException { + Parcel data = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + data.writeInterfaceToken(IActivityManager.descriptor); + data.writeInt(type); + data.writeString(packageName); + data.writeStrongBinder(token); + data.writeString(resultWho); + data.writeInt(requestCode); + if (intent != null) { + data.writeInt(1); + intent.writeToParcel(data, 0); + } else { + data.writeInt(0); + } + data.writeString(resolvedType); + data.writeInt(flags); + mRemote.transact(GET_INTENT_SENDER_TRANSACTION, data, reply, 0); + reply.readException(); + IIntentSender res = IIntentSender.Stub.asInterface( + reply.readStrongBinder()); + data.recycle(); + reply.recycle(); + return res; + } + public void cancelIntentSender(IIntentSender sender) throws RemoteException { + Parcel data = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + data.writeInterfaceToken(IActivityManager.descriptor); + data.writeStrongBinder(sender.asBinder()); + mRemote.transact(CANCEL_INTENT_SENDER_TRANSACTION, data, reply, 0); + reply.readException(); + data.recycle(); + reply.recycle(); + } + public String getPackageForIntentSender(IIntentSender sender) throws RemoteException { + Parcel data = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + data.writeInterfaceToken(IActivityManager.descriptor); + data.writeStrongBinder(sender.asBinder()); + mRemote.transact(GET_PACKAGE_FOR_INTENT_SENDER_TRANSACTION, data, reply, 0); + reply.readException(); + String res = reply.readString(); + data.recycle(); + reply.recycle(); + return res; + } + public void setProcessLimit(int max) throws RemoteException + { + Parcel data = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + data.writeInterfaceToken(IActivityManager.descriptor); + data.writeInt(max); + mRemote.transact(SET_PROCESS_LIMIT_TRANSACTION, data, reply, 0); + reply.readException(); + data.recycle(); + reply.recycle(); + } + public int getProcessLimit() throws RemoteException + { + Parcel data = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + data.writeInterfaceToken(IActivityManager.descriptor); + mRemote.transact(GET_PROCESS_LIMIT_TRANSACTION, data, reply, 0); + reply.readException(); + int res = reply.readInt(); + data.recycle(); + reply.recycle(); + return res; + } + public void setProcessForeground(IBinder token, int pid, + boolean isForeground) throws RemoteException { + Parcel data = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + data.writeInterfaceToken(IActivityManager.descriptor); + data.writeStrongBinder(token); + data.writeInt(pid); + data.writeInt(isForeground ? 1 : 0); + mRemote.transact(SET_PROCESS_FOREGROUND_TRANSACTION, data, reply, 0); + reply.readException(); + data.recycle(); + reply.recycle(); + } + public int checkPermission(String permission, int pid, int uid) + throws RemoteException { + Parcel data = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + data.writeInterfaceToken(IActivityManager.descriptor); + data.writeString(permission); + data.writeInt(pid); + data.writeInt(uid); + mRemote.transact(CHECK_PERMISSION_TRANSACTION, data, reply, 0); + reply.readException(); + int res = reply.readInt(); + data.recycle(); + reply.recycle(); + return res; + } + public boolean clearApplicationUserData(final String packageName, + final IPackageDataObserver observer) throws RemoteException { + Parcel data = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + data.writeInterfaceToken(IActivityManager.descriptor); + data.writeString(packageName); + data.writeStrongBinder(observer.asBinder()); + mRemote.transact(CLEAR_APP_DATA_TRANSACTION, data, reply, 0); + reply.readException(); + boolean res = reply.readInt() != 0; + data.recycle(); + reply.recycle(); + return res; + } + public int checkUriPermission(Uri uri, int pid, int uid, int mode) + throws RemoteException { + Parcel data = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + data.writeInterfaceToken(IActivityManager.descriptor); + uri.writeToParcel(data, 0); + data.writeInt(pid); + data.writeInt(uid); + data.writeInt(mode); + mRemote.transact(CHECK_URI_PERMISSION_TRANSACTION, data, reply, 0); + reply.readException(); + int res = reply.readInt(); + data.recycle(); + reply.recycle(); + return res; + } + public void grantUriPermission(IApplicationThread caller, String targetPkg, + Uri uri, int mode) throws RemoteException { + Parcel data = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + data.writeInterfaceToken(IActivityManager.descriptor); + data.writeStrongBinder(caller.asBinder()); + data.writeString(targetPkg); + uri.writeToParcel(data, 0); + data.writeInt(mode); + mRemote.transact(GRANT_URI_PERMISSION_TRANSACTION, data, reply, 0); + reply.readException(); + data.recycle(); + reply.recycle(); + } + public void revokeUriPermission(IApplicationThread caller, Uri uri, + int mode) throws RemoteException { + Parcel data = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + data.writeInterfaceToken(IActivityManager.descriptor); + data.writeStrongBinder(caller.asBinder()); + uri.writeToParcel(data, 0); + data.writeInt(mode); + mRemote.transact(REVOKE_URI_PERMISSION_TRANSACTION, data, reply, 0); + reply.readException(); + data.recycle(); + reply.recycle(); + } + public void showWaitingForDebugger(IApplicationThread who, boolean waiting) + throws RemoteException { + Parcel data = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + data.writeInterfaceToken(IActivityManager.descriptor); + data.writeStrongBinder(who.asBinder()); + data.writeInt(waiting ? 1 : 0); + mRemote.transact(SHOW_WAITING_FOR_DEBUGGER_TRANSACTION, data, reply, 0); + reply.readException(); + data.recycle(); + reply.recycle(); + } + public void getMemoryInfo(ActivityManager.MemoryInfo outInfo) throws RemoteException { + Parcel data = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + data.writeInterfaceToken(IActivityManager.descriptor); + mRemote.transact(GET_MEMORY_INFO_TRANSACTION, data, reply, 0); + reply.readException(); + outInfo.readFromParcel(reply); + data.recycle(); + reply.recycle(); + } + public void unhandledBack() throws RemoteException + { + Parcel data = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + data.writeInterfaceToken(IActivityManager.descriptor); + mRemote.transact(UNHANDLED_BACK_TRANSACTION, data, reply, 0); + reply.readException(); + data.recycle(); + reply.recycle(); + } + public ParcelFileDescriptor openContentUri(Uri uri) throws RemoteException + { + Parcel data = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + data.writeInterfaceToken(IActivityManager.descriptor); + mRemote.transact(OPEN_CONTENT_URI_TRANSACTION, data, reply, 0); + reply.readException(); + ParcelFileDescriptor pfd = null; + if (reply.readInt() != 0) { + pfd = ParcelFileDescriptor.CREATOR.createFromParcel(reply); + } + data.recycle(); + reply.recycle(); + return pfd; + } + public void goingToSleep() throws RemoteException + { + Parcel data = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + data.writeInterfaceToken(IActivityManager.descriptor); + mRemote.transact(GOING_TO_SLEEP_TRANSACTION, data, reply, 0); + reply.readException(); + data.recycle(); + reply.recycle(); + } + public void wakingUp() throws RemoteException + { + Parcel data = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + data.writeInterfaceToken(IActivityManager.descriptor); + mRemote.transact(WAKING_UP_TRANSACTION, data, reply, 0); + reply.readException(); + data.recycle(); + reply.recycle(); + } + public void setDebugApp( + String packageName, boolean waitForDebugger, boolean persistent) + throws RemoteException + { + Parcel data = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + data.writeInterfaceToken(IActivityManager.descriptor); + data.writeString(packageName); + data.writeInt(waitForDebugger ? 1 : 0); + data.writeInt(persistent ? 1 : 0); + mRemote.transact(SET_DEBUG_APP_TRANSACTION, data, reply, 0); + reply.readException(); + data.recycle(); + reply.recycle(); + } + public void setAlwaysFinish(boolean enabled) throws RemoteException + { + Parcel data = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + data.writeInterfaceToken(IActivityManager.descriptor); + data.writeInt(enabled ? 1 : 0); + mRemote.transact(SET_ALWAYS_FINISH_TRANSACTION, data, reply, 0); + reply.readException(); + data.recycle(); + reply.recycle(); + } + public void setActivityWatcher(IActivityWatcher watcher) throws RemoteException + { + Parcel data = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + data.writeInterfaceToken(IActivityManager.descriptor); + data.writeStrongBinder(watcher != null ? watcher.asBinder() : null); + mRemote.transact(SET_ACTIVITY_WATCHER_TRANSACTION, data, reply, 0); + reply.readException(); + data.recycle(); + reply.recycle(); + } + public void enterSafeMode() throws RemoteException { + Parcel data = Parcel.obtain(); + data.writeInterfaceToken(IActivityManager.descriptor); + mRemote.transact(ENTER_SAFE_MODE_TRANSACTION, data, null, 0); + data.recycle(); + } + public void noteWakeupAlarm(IIntentSender sender) throws RemoteException { + Parcel data = Parcel.obtain(); + data.writeStrongBinder(sender.asBinder()); + data.writeInterfaceToken(IActivityManager.descriptor); + mRemote.transact(NOTE_WAKEUP_ALARM_TRANSACTION, data, null, 0); + data.recycle(); + } + public boolean killPidsForMemory(int[] pids) throws RemoteException { + Parcel data = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + data.writeInterfaceToken(IActivityManager.descriptor); + data.writeIntArray(pids); + mRemote.transact(KILL_PIDS_FOR_MEMORY_TRANSACTION, data, reply, 0); + boolean res = reply.readInt() != 0; + data.recycle(); + reply.recycle(); + return res; + } + public void reportPss(IApplicationThread caller, int pss) throws RemoteException { + Parcel data = Parcel.obtain(); + data.writeInterfaceToken(IActivityManager.descriptor); + data.writeStrongBinder(caller.asBinder()); + data.writeInt(pss); + mRemote.transact(REPORT_PSS_TRANSACTION, data, null, 0); + data.recycle(); + } + public void startRunning(String pkg, String cls, String action, + String indata) throws RemoteException { + Parcel data = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + data.writeInterfaceToken(IActivityManager.descriptor); + data.writeString(pkg); + data.writeString(cls); + data.writeString(action); + data.writeString(indata); + mRemote.transact(START_RUNNING_TRANSACTION, data, reply, 0); + reply.readException(); + data.recycle(); + reply.recycle(); + } + public void systemReady() throws RemoteException + { + Parcel data = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + data.writeInterfaceToken(IActivityManager.descriptor); + mRemote.transact(SYSTEM_READY_TRANSACTION, data, reply, 0); + reply.readException(); + data.recycle(); + reply.recycle(); + } + public boolean testIsSystemReady() + { + /* this base class version is never called */ + return true; + } + public int handleApplicationError(IBinder app, int flags, + String tag, String shortMsg, String longMsg, + byte[] crashData) throws RemoteException + { + Parcel data = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + data.writeInterfaceToken(IActivityManager.descriptor); + data.writeStrongBinder(app); + data.writeInt(flags); + data.writeString(tag); + data.writeString(shortMsg); + data.writeString(longMsg); + data.writeByteArray(crashData); + mRemote.transact(HANDLE_APPLICATION_ERROR_TRANSACTION, data, reply, 0); + reply.readException(); + int res = reply.readInt(); + reply.recycle(); + data.recycle(); + return res; + } + + public void signalPersistentProcesses(int sig) throws RemoteException { + Parcel data = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + data.writeInterfaceToken(IActivityManager.descriptor); + data.writeInt(sig); + mRemote.transact(SIGNAL_PERSISTENT_PROCESSES_TRANSACTION, data, reply, 0); + reply.readException(); + data.recycle(); + reply.recycle(); + } + + public void restartPackage(String packageName) throws RemoteException { + Parcel data = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + data.writeInterfaceToken(IActivityManager.descriptor); + data.writeString(packageName); + mRemote.transact(RESTART_PACKAGE_TRANSACTION, data, reply, 0); + reply.readException(); + data.recycle(); + reply.recycle(); + } + + public ConfigurationInfo getDeviceConfigurationInfo() throws RemoteException + { + Parcel data = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + data.writeInterfaceToken(IActivityManager.descriptor); + mRemote.transact(GET_DEVICE_CONFIGURATION_TRANSACTION, data, reply, 0); + reply.readException(); + ConfigurationInfo res = ConfigurationInfo.CREATOR.createFromParcel(reply); + reply.recycle(); + data.recycle(); + return res; + } + private IBinder mRemote; +} diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java new file mode 100644 index 0000000..bf5616e --- /dev/null +++ b/core/java/android/app/ActivityThread.java @@ -0,0 +1,3916 @@ +/* + * Copyright (C) 2006 The Android Open Source Project + * + * 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 android.app; + +import android.content.BroadcastReceiver; +import android.content.ComponentCallbacks; +import android.content.ComponentName; +import android.content.ContentProvider; +import android.content.Context; +import android.content.IContentProvider; +import android.content.Intent; +import android.content.ServiceConnection; +import android.content.pm.ActivityInfo; +import android.content.pm.ApplicationInfo; +import android.content.pm.IPackageManager; +import android.content.pm.InstrumentationInfo; +import android.content.pm.PackageManager; +import android.content.pm.ProviderInfo; +import android.content.pm.ServiceInfo; +import android.content.res.AssetManager; +import android.content.res.Configuration; +import android.content.res.Resources; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteDebug; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.net.http.AndroidHttpClient; +import android.os.Bundle; +import android.os.Debug; +import android.os.Handler; +import android.os.IBinder; +import android.os.Looper; +import android.os.Message; +import android.os.MessageQueue; +import android.os.Process; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.os.SystemClock; +import android.util.AndroidRuntimeException; +import android.util.Config; +import android.util.DisplayMetrics; +import android.util.EventLog; +import android.util.Log; +import android.view.Display; +import android.view.View; +import android.view.ViewDebug; +import android.view.ViewManager; +import android.view.Window; +import android.view.WindowManager; +import android.view.WindowManagerImpl; + +import com.android.internal.os.BinderInternal; +import com.android.internal.os.RuntimeInit; +import com.android.internal.util.ArrayUtils; + +import org.apache.harmony.xnet.provider.jsse.OpenSSLSocketImpl; + +import java.io.File; +import java.io.FileDescriptor; +import java.io.FileOutputStream; +import java.io.PrintWriter; +import java.lang.ref.WeakReference; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.TimeZone; +import java.util.regex.Pattern; + +final class IntentReceiverLeaked extends AndroidRuntimeException { + public IntentReceiverLeaked(String msg) { + super(msg); + } +} + +final class ServiceConnectionLeaked extends AndroidRuntimeException { + public ServiceConnectionLeaked(String msg) { + super(msg); + } +} + +final class SuperNotCalledException extends AndroidRuntimeException { + public SuperNotCalledException(String msg) { + super(msg); + } +} + +/** + * This manages the execution of the main thread in an + * application process, scheduling and executing activities, + * broadcasts, and other operations on it as the activity + * manager requests. + * + * {@hide} + */ +public final class ActivityThread { + private static final String TAG = "ActivityThread"; + private static final boolean DEBUG = false; + private static final boolean localLOGV = DEBUG ? Config.LOGD : Config.LOGV; + private static final boolean DEBUG_BROADCAST = false; + private static final long MIN_TIME_BETWEEN_GCS = 5*1000; + private static final Pattern PATTERN_SEMICOLON = Pattern.compile(";"); + private static final int SQLITE_MEM_RELEASED_EVENT_LOG_TAG = 75003; + private static final int LOG_ON_PAUSE_CALLED = 30021; + private static final int LOG_ON_RESUME_CALLED = 30022; + + + public static final ActivityThread currentActivityThread() { + return (ActivityThread)sThreadLocal.get(); + } + + public static final String currentPackageName() + { + ActivityThread am = currentActivityThread(); + return (am != null && am.mBoundApplication != null) + ? am.mBoundApplication.processName : null; + } + + public static IPackageManager getPackageManager() { + if (sPackageManager != null) { + //Log.v("PackageManager", "returning cur default = " + sPackageManager); + return sPackageManager; + } + IBinder b = ServiceManager.getService("package"); + //Log.v("PackageManager", "default service binder = " + b); + sPackageManager = IPackageManager.Stub.asInterface(b); + //Log.v("PackageManager", "default service = " + sPackageManager); + return sPackageManager; + } + + DisplayMetrics getDisplayMetricsLocked(boolean forceUpdate) { + if (mDisplayMetrics != null && !forceUpdate) { + return mDisplayMetrics; + } + if (mDisplay == null) { + WindowManager wm = WindowManagerImpl.getDefault(); + mDisplay = wm.getDefaultDisplay(); + } + DisplayMetrics metrics = mDisplayMetrics = new DisplayMetrics(); + mDisplay.getMetrics(metrics); + //Log.i("foo", "New metrics: w=" + metrics.widthPixels + " h=" + // + metrics.heightPixels + " den=" + metrics.density + // + " xdpi=" + metrics.xdpi + " ydpi=" + metrics.ydpi); + return metrics; + } + + Resources getTopLevelResources(String appDir) { + synchronized (mPackages) { + //Log.w(TAG, "getTopLevelResources: " + appDir); + WeakReference<Resources> wr = mActiveResources.get(appDir); + Resources r = wr != null ? wr.get() : null; + if (r != null && r.getAssets().isUpToDate()) { + //Log.w(TAG, "Returning cached resources " + r + " " + appDir); + return r; + } + + //if (r != null) { + // Log.w(TAG, "Throwing away out-of-date resources!!!! " + // + r + " " + appDir); + //} + + AssetManager assets = new AssetManager(); + if (assets.addAssetPath(appDir) == 0) { + return null; + } + DisplayMetrics metrics = getDisplayMetricsLocked(false); + r = new Resources(assets, metrics, getConfiguration()); + //Log.i(TAG, "Created app resources " + r + ": " + r.getConfiguration()); + // XXX need to remove entries when weak references go away + mActiveResources.put(appDir, new WeakReference<Resources>(r)); + return r; + } + } + + final Handler getHandler() { + return mH; + } + + public final static class PackageInfo { + + private final ActivityThread mActivityThread; + private final ApplicationInfo mApplicationInfo; + private final String mPackageName; + private final String mAppDir; + private final String mResDir; + private final String[] mSharedLibraries; + private final String mDataDir; + private final File mDataDirFile; + private final ClassLoader mBaseClassLoader; + private final boolean mSecurityViolation; + private final boolean mIncludeCode; + private Resources mResources; + private ClassLoader mClassLoader; + private Application mApplication; + private final HashMap<Context, HashMap<BroadcastReceiver, ReceiverDispatcher>> mReceivers + = new HashMap<Context, HashMap<BroadcastReceiver, ReceiverDispatcher>>(); + private final HashMap<Context, HashMap<BroadcastReceiver, ReceiverDispatcher>> mUnregisteredReceivers + = new HashMap<Context, HashMap<BroadcastReceiver, ReceiverDispatcher>>(); + private final HashMap<Context, HashMap<ServiceConnection, ServiceDispatcher>> mServices + = new HashMap<Context, HashMap<ServiceConnection, ServiceDispatcher>>(); + private final HashMap<Context, HashMap<ServiceConnection, ServiceDispatcher>> mUnboundServices + = new HashMap<Context, HashMap<ServiceConnection, ServiceDispatcher>>(); + + int mClientCount = 0; + + public PackageInfo(ActivityThread activityThread, ApplicationInfo aInfo, + ActivityThread mainThread, ClassLoader baseLoader, + boolean securityViolation, boolean includeCode) { + mActivityThread = activityThread; + mApplicationInfo = aInfo; + mPackageName = aInfo.packageName; + mAppDir = aInfo.sourceDir; + mResDir = aInfo.uid == Process.myUid() ? aInfo.sourceDir + : aInfo.publicSourceDir; + mSharedLibraries = aInfo.sharedLibraryFiles; + mDataDir = aInfo.dataDir; + mDataDirFile = mDataDir != null ? new File(mDataDir) : null; + mBaseClassLoader = baseLoader; + mSecurityViolation = securityViolation; + mIncludeCode = includeCode; + + if (mAppDir == null) { + if (mSystemContext == null) { + mSystemContext = + ApplicationContext.createSystemContext(mainThread); + mSystemContext.getResources().updateConfiguration( + mainThread.getConfiguration(), + mainThread.getDisplayMetricsLocked(false)); + //Log.i(TAG, "Created system resources " + // + mSystemContext.getResources() + ": " + // + mSystemContext.getResources().getConfiguration()); + } + mClassLoader = mSystemContext.getClassLoader(); + mResources = mSystemContext.getResources(); + } + } + + public PackageInfo(ActivityThread activityThread, String name, + Context systemContext) { + mActivityThread = activityThread; + mApplicationInfo = new ApplicationInfo(); + mApplicationInfo.packageName = name; + mPackageName = name; + mAppDir = null; + mResDir = null; + mSharedLibraries = null; + mDataDir = null; + mDataDirFile = null; + mBaseClassLoader = null; + mSecurityViolation = false; + mIncludeCode = true; + mClassLoader = systemContext.getClassLoader(); + mResources = systemContext.getResources(); + } + + public String getPackageName() { + return mPackageName; + } + + public boolean isSecurityViolation() { + return mSecurityViolation; + } + + /** + * Gets the array of shared libraries that are listed as + * used by the given package. + * + * @param packageName the name of the package (note: not its + * file name) + * @return null-ok; the array of shared libraries, each one + * a fully-qualified path + */ + private static String[] getLibrariesFor(String packageName) { + ApplicationInfo ai = null; + try { + ai = getPackageManager().getApplicationInfo(packageName, + PackageManager.GET_SHARED_LIBRARY_FILES); + } catch (RemoteException e) { + throw new AssertionError(e); + } + + if (ai == null) { + return null; + } + + return ai.sharedLibraryFiles; + } + + /** + * Combines two arrays (of library names) such that they are + * concatenated in order but are devoid of duplicates. The + * result is a single string with the names of the libraries + * separated by colons, or <code>null</code> if both lists + * were <code>null</code> or empty. + * + * @param list1 null-ok; the first list + * @param list2 null-ok; the second list + * @return null-ok; the combination + */ + private static String combineLibs(String[] list1, String[] list2) { + StringBuilder result = new StringBuilder(300); + boolean first = true; + + if (list1 != null) { + for (String s : list1) { + if (first) { + first = false; + } else { + result.append(':'); + } + result.append(s); + } + } + + // Only need to check for duplicates if list1 was non-empty. + boolean dupCheck = !first; + + if (list2 != null) { + for (String s : list2) { + if (dupCheck && ArrayUtils.contains(list1, s)) { + continue; + } + + if (first) { + first = false; + } else { + result.append(':'); + } + result.append(s); + } + } + + return result.toString(); + } + + public ClassLoader getClassLoader() { + synchronized (this) { + if (mClassLoader != null) { + return mClassLoader; + } + + if (mIncludeCode && !mPackageName.equals("android")) { + String zip = mAppDir; + + /* + * The following is a bit of a hack to inject + * instrumentation into the system: If the app + * being started matches one of the instrumentation names, + * then we combine both the "instrumentation" and + * "instrumented" app into the path, along with the + * concatenation of both apps' shared library lists. + */ + + String instrumentationAppDir = + mActivityThread.mInstrumentationAppDir; + String instrumentationAppPackage = + mActivityThread.mInstrumentationAppPackage; + String instrumentedAppDir = + mActivityThread.mInstrumentedAppDir; + String[] instrumentationLibs = null; + + if (mAppDir.equals(instrumentationAppDir) + || mAppDir.equals(instrumentedAppDir)) { + zip = instrumentationAppDir + ":" + instrumentedAppDir; + if (! instrumentedAppDir.equals(instrumentationAppDir)) { + instrumentationLibs = + getLibrariesFor(instrumentationAppPackage); + } + } + + if ((mSharedLibraries != null) || + (instrumentationLibs != null)) { + zip = + combineLibs(mSharedLibraries, instrumentationLibs) + + ':' + zip; + } + + /* + * With all the combination done (if necessary, actually + * create the class loader. + */ + + if (localLOGV) Log.v(TAG, "Class path: " + zip); + + mClassLoader = + ApplicationLoaders.getDefault().getClassLoader( + zip, mDataDir, mBaseClassLoader); + } else { + if (mBaseClassLoader == null) { + mClassLoader = ClassLoader.getSystemClassLoader(); + } else { + mClassLoader = mBaseClassLoader; + } + } + return mClassLoader; + } + } + + public String getAppDir() { + return mAppDir; + } + + public String getResDir() { + return mResDir; + } + + public String getDataDir() { + return mDataDir; + } + + public File getDataDirFile() { + return mDataDirFile; + } + + public AssetManager getAssets(ActivityThread mainThread) { + return getResources(mainThread).getAssets(); + } + + public Resources getResources(ActivityThread mainThread) { + if (mResources == null) { + mResources = mainThread.getTopLevelResources(mResDir); + } + return mResources; + } + + public Application makeApplication() { + if (mApplication != null) { + return mApplication; + } + + Application app = null; + + String appClass = mApplicationInfo.className; + if (appClass == null) { + appClass = "android.app.Application"; + } + + try { + java.lang.ClassLoader cl = getClassLoader(); + ApplicationContext appContext = new ApplicationContext(); + appContext.init(this, null, mActivityThread); + app = mActivityThread.mInstrumentation.newApplication( + cl, appClass, appContext); + appContext.setOuterContext(app); + } catch (Exception e) { + if (!mActivityThread.mInstrumentation.onException(app, e)) { + throw new RuntimeException( + "Unable to instantiate application " + appClass + + ": " + e.toString(), e); + } + } + mActivityThread.mAllApplications.add(app); + return mApplication = app; + } + + public void removeContextRegistrations(Context context, + String who, String what) { + HashMap<BroadcastReceiver, ReceiverDispatcher> rmap = + mReceivers.remove(context); + if (rmap != null) { + Iterator<ReceiverDispatcher> it = rmap.values().iterator(); + while (it.hasNext()) { + ReceiverDispatcher rd = it.next(); + IntentReceiverLeaked leak = new IntentReceiverLeaked( + what + " " + who + " has leaked IntentReceiver " + + rd.getIntentReceiver() + " that was " + + "originally registered here. Are you missing a " + + "call to unregisterReceiver()?"); + leak.setStackTrace(rd.getLocation().getStackTrace()); + Log.e(TAG, leak.getMessage(), leak); + try { + ActivityManagerNative.getDefault().unregisterReceiver( + rd.getIIntentReceiver()); + } catch (RemoteException e) { + // system crashed, nothing we can do + } + } + } + mUnregisteredReceivers.remove(context); + //Log.i(TAG, "Receiver registrations: " + mReceivers); + HashMap<ServiceConnection, ServiceDispatcher> smap = + mServices.remove(context); + if (smap != null) { + Iterator<ServiceDispatcher> it = smap.values().iterator(); + while (it.hasNext()) { + ServiceDispatcher sd = it.next(); + ServiceConnectionLeaked leak = new ServiceConnectionLeaked( + what + " " + who + " has leaked ServiceConnection " + + sd.getServiceConnection() + " that was originally bound here"); + leak.setStackTrace(sd.getLocation().getStackTrace()); + Log.e(TAG, leak.getMessage(), leak); + try { + ActivityManagerNative.getDefault().unbindService( + sd.getIServiceConnection()); + } catch (RemoteException e) { + // system crashed, nothing we can do + } + sd.doForget(); + } + } + mUnboundServices.remove(context); + //Log.i(TAG, "Service registrations: " + mServices); + } + + public IIntentReceiver getReceiverDispatcher(BroadcastReceiver r, + Context context, Handler handler, + Instrumentation instrumentation, boolean registered) { + synchronized (mReceivers) { + ReceiverDispatcher rd = null; + HashMap<BroadcastReceiver, ReceiverDispatcher> map = null; + if (registered) { + map = mReceivers.get(context); + if (map != null) { + rd = map.get(r); + } + } + if (rd == null) { + rd = new ReceiverDispatcher(r, context, handler, + instrumentation, registered); + if (registered) { + if (map == null) { + map = new HashMap<BroadcastReceiver, ReceiverDispatcher>(); + mReceivers.put(context, map); + } + map.put(r, rd); + } + } else { + rd.validate(context, handler); + } + return rd.getIIntentReceiver(); + } + } + + public IIntentReceiver forgetReceiverDispatcher(Context context, + BroadcastReceiver r) { + synchronized (mReceivers) { + HashMap<BroadcastReceiver, ReceiverDispatcher> map = mReceivers.get(context); + ReceiverDispatcher rd = null; + if (map != null) { + rd = map.get(r); + if (rd != null) { + map.remove(r); + if (map.size() == 0) { + mReceivers.remove(context); + } + if (r.getDebugUnregister()) { + HashMap<BroadcastReceiver, ReceiverDispatcher> holder + = mUnregisteredReceivers.get(context); + if (holder == null) { + holder = new HashMap<BroadcastReceiver, ReceiverDispatcher>(); + mUnregisteredReceivers.put(context, holder); + } + RuntimeException ex = new IllegalArgumentException( + "Originally unregistered here:"); + ex.fillInStackTrace(); + rd.setUnregisterLocation(ex); + holder.put(r, rd); + } + return rd.getIIntentReceiver(); + } + } + HashMap<BroadcastReceiver, ReceiverDispatcher> holder + = mUnregisteredReceivers.get(context); + if (holder != null) { + rd = holder.get(r); + if (rd != null) { + RuntimeException ex = rd.getUnregisterLocation(); + throw new IllegalArgumentException( + "Unregistering Receiver " + r + + " that was already unregistered", ex); + } + } + if (context == null) { + throw new IllegalStateException("Unbinding Receiver " + r + + " from Context that is no longer in use: " + context); + } else { + throw new IllegalArgumentException("Receiver not registered: " + r); + } + + } + } + + static final class ReceiverDispatcher { + + final static class InnerReceiver extends IIntentReceiver.Stub { + final WeakReference<ReceiverDispatcher> mDispatcher; + final ReceiverDispatcher mStrongRef; + + InnerReceiver(ReceiverDispatcher rd, boolean strong) { + mDispatcher = new WeakReference<ReceiverDispatcher>(rd); + mStrongRef = strong ? rd : null; + } + public void performReceive(Intent intent, int resultCode, + String data, Bundle extras, boolean ordered) { + ReceiverDispatcher rd = mDispatcher.get(); + if (DEBUG_BROADCAST) { + int seq = intent.getIntExtra("seq", -1); + Log.i(TAG, "Receiving broadcast " + intent.getAction() + " seq=" + seq + + " to " + rd); + } + if (rd != null) { + rd.performReceive(intent, resultCode, data, extras, ordered); + } + } + } + + final IIntentReceiver.Stub mIIntentReceiver; + final BroadcastReceiver mReceiver; + final Context mContext; + final Handler mActivityThread; + final Instrumentation mInstrumentation; + final boolean mRegistered; + final IntentReceiverLeaked mLocation; + RuntimeException mUnregisterLocation; + + final class Args implements Runnable { + private Intent mCurIntent; + private int mCurCode; + private String mCurData; + private Bundle mCurMap; + private boolean mCurOrdered; + + public void run() { + BroadcastReceiver receiver = mReceiver; + if (DEBUG_BROADCAST) { + int seq = mCurIntent.getIntExtra("seq", -1); + Log.i(TAG, "Dispathing broadcast " + mCurIntent.getAction() + " seq=" + seq + + " to " + mReceiver); + } + if (receiver == null) { + return; + } + + IActivityManager mgr = ActivityManagerNative.getDefault(); + Intent intent = mCurIntent; + mCurIntent = null; + try { + ClassLoader cl = mReceiver.getClass().getClassLoader(); + intent.setExtrasClassLoader(cl); + if (mCurMap != null) { + mCurMap.setClassLoader(cl); + } + receiver.setOrderedHint(true); + receiver.setResult(mCurCode, mCurData, mCurMap); + receiver.clearAbortBroadcast(); + receiver.setOrderedHint(mCurOrdered); + receiver.onReceive(mContext, intent); + } catch (Exception e) { + if (mRegistered && mCurOrdered) { + try { + mgr.finishReceiver(mIIntentReceiver, + mCurCode, mCurData, mCurMap, false); + } catch (RemoteException ex) { + } + } + if (mInstrumentation == null || + !mInstrumentation.onException(mReceiver, e)) { + throw new RuntimeException( + "Error receiving broadcast " + intent + + " in " + mReceiver, e); + } + } + if (mRegistered && mCurOrdered) { + try { + mgr.finishReceiver(mIIntentReceiver, + receiver.getResultCode(), + receiver.getResultData(), + receiver.getResultExtras(false), + receiver.getAbortBroadcast()); + } catch (RemoteException ex) { + } + } + } + } + + ReceiverDispatcher(BroadcastReceiver receiver, Context context, + Handler activityThread, Instrumentation instrumentation, + boolean registered) { + if (activityThread == null) { + throw new NullPointerException("Handler must not be null"); + } + + mIIntentReceiver = new InnerReceiver(this, !registered); + mReceiver = receiver; + mContext = context; + mActivityThread = activityThread; + mInstrumentation = instrumentation; + mRegistered = registered; + mLocation = new IntentReceiverLeaked(null); + mLocation.fillInStackTrace(); + } + + void validate(Context context, Handler activityThread) { + if (mContext != context) { + throw new IllegalStateException( + "Receiver " + mReceiver + + " registered with differing Context (was " + + mContext + " now " + context + ")"); + } + if (mActivityThread != activityThread) { + throw new IllegalStateException( + "Receiver " + mReceiver + + " registered with differing handler (was " + + mActivityThread + " now " + activityThread + ")"); + } + } + + IntentReceiverLeaked getLocation() { + return mLocation; + } + + BroadcastReceiver getIntentReceiver() { + return mReceiver; + } + + IIntentReceiver getIIntentReceiver() { + return mIIntentReceiver; + } + + void setUnregisterLocation(RuntimeException ex) { + mUnregisterLocation = ex; + } + + RuntimeException getUnregisterLocation() { + return mUnregisterLocation; + } + + public void performReceive(Intent intent, int resultCode, + String data, Bundle extras, boolean ordered) { + if (DEBUG_BROADCAST) { + int seq = intent.getIntExtra("seq", -1); + Log.i(TAG, "Enqueueing broadcast " + intent.getAction() + " seq=" + seq + + " to " + mReceiver); + } + Args args = new Args(); + args.mCurIntent = intent; + args.mCurCode = resultCode; + args.mCurData = data; + args.mCurMap = extras; + args.mCurOrdered = ordered; + if (!mActivityThread.post(args)) { + if (mRegistered) { + IActivityManager mgr = ActivityManagerNative.getDefault(); + try { + mgr.finishReceiver(mIIntentReceiver, args.mCurCode, + args.mCurData, args.mCurMap, false); + } catch (RemoteException ex) { + } + } + } + } + + } + + public final IServiceConnection getServiceDispatcher(ServiceConnection c, + Context context, Handler handler, int flags) { + synchronized (mServices) { + ServiceDispatcher sd = null; + HashMap<ServiceConnection, ServiceDispatcher> map = mServices.get(context); + if (map != null) { + sd = map.get(c); + } + if (sd == null) { + sd = new ServiceDispatcher(c, context, handler, flags); + if (map == null) { + map = new HashMap<ServiceConnection, ServiceDispatcher>(); + mServices.put(context, map); + } + map.put(c, sd); + } else { + sd.validate(context, handler); + } + return sd.getIServiceConnection(); + } + } + + public final IServiceConnection forgetServiceDispatcher(Context context, + ServiceConnection c) { + synchronized (mServices) { + HashMap<ServiceConnection, ServiceDispatcher> map + = mServices.get(context); + ServiceDispatcher sd = null; + if (map != null) { + sd = map.get(c); + if (sd != null) { + map.remove(c); + sd.doForget(); + if (map.size() == 0) { + mServices.remove(context); + } + if ((sd.getFlags()&Context.BIND_DEBUG_UNBIND) != 0) { + HashMap<ServiceConnection, ServiceDispatcher> holder + = mUnboundServices.get(context); + if (holder == null) { + holder = new HashMap<ServiceConnection, ServiceDispatcher>(); + mUnboundServices.put(context, holder); + } + RuntimeException ex = new IllegalArgumentException( + "Originally unbound here:"); + ex.fillInStackTrace(); + sd.setUnbindLocation(ex); + holder.put(c, sd); + } + return sd.getIServiceConnection(); + } + } + HashMap<ServiceConnection, ServiceDispatcher> holder + = mUnboundServices.get(context); + if (holder != null) { + sd = holder.get(c); + if (sd != null) { + RuntimeException ex = sd.getUnbindLocation(); + throw new IllegalArgumentException( + "Unbinding Service " + c + + " that was already unbound", ex); + } + } + if (context == null) { + throw new IllegalStateException("Unbinding Service " + c + + " from Context that is no longer in use: " + context); + } else { + throw new IllegalArgumentException("Service not registered: " + c); + } + } + } + + static final class ServiceDispatcher { + private final InnerConnection mIServiceConnection; + private final ServiceConnection mConnection; + private final Context mContext; + private final Handler mActivityThread; + private final ServiceConnectionLeaked mLocation; + private final int mFlags; + + private RuntimeException mUnbindLocation; + + private boolean mDied; + + private static class ConnectionInfo { + IBinder binder; + IBinder.DeathRecipient deathMonitor; + } + + private static class InnerConnection extends IServiceConnection.Stub { + final WeakReference<ServiceDispatcher> mDispatcher; + + InnerConnection(ServiceDispatcher sd) { + mDispatcher = new WeakReference<ServiceDispatcher>(sd); + } + + public void connected(ComponentName name, IBinder service) throws RemoteException { + ServiceDispatcher sd = mDispatcher.get(); + if (sd != null) { + sd.connected(name, service); + } + } + } + + private final HashMap<ComponentName, ConnectionInfo> mActiveConnections + = new HashMap<ComponentName, ConnectionInfo>(); + + ServiceDispatcher(ServiceConnection conn, + Context context, Handler activityThread, int flags) { + mIServiceConnection = new InnerConnection(this); + mConnection = conn; + mContext = context; + mActivityThread = activityThread; + mLocation = new ServiceConnectionLeaked(null); + mLocation.fillInStackTrace(); + mFlags = flags; + } + + void validate(Context context, Handler activityThread) { + if (mContext != context) { + throw new RuntimeException( + "ServiceConnection " + mConnection + + " registered with differing Context (was " + + mContext + " now " + context + ")"); + } + if (mActivityThread != activityThread) { + throw new RuntimeException( + "ServiceConnection " + mConnection + + " registered with differing handler (was " + + mActivityThread + " now " + activityThread + ")"); + } + } + + void doForget() { + synchronized(this) { + Iterator<ConnectionInfo> it = mActiveConnections.values().iterator(); + while (it.hasNext()) { + ConnectionInfo ci = it.next(); + ci.binder.unlinkToDeath(ci.deathMonitor, 0); + } + mActiveConnections.clear(); + } + } + + ServiceConnectionLeaked getLocation() { + return mLocation; + } + + ServiceConnection getServiceConnection() { + return mConnection; + } + + IServiceConnection getIServiceConnection() { + return mIServiceConnection; + } + + int getFlags() { + return mFlags; + } + + void setUnbindLocation(RuntimeException ex) { + mUnbindLocation = ex; + } + + RuntimeException getUnbindLocation() { + return mUnbindLocation; + } + + public void connected(ComponentName name, IBinder service) { + if (mActivityThread != null) { + mActivityThread.post(new RunConnection(name, service, 0)); + } else { + doConnected(name, service); + } + } + + public void death(ComponentName name, IBinder service) { + ConnectionInfo old; + + synchronized (this) { + mDied = true; + old = mActiveConnections.remove(name); + if (old == null || old.binder != service) { + // Death for someone different than who we last + // reported... just ignore it. + return; + } + old.binder.unlinkToDeath(old.deathMonitor, 0); + } + + if (mActivityThread != null) { + mActivityThread.post(new RunConnection(name, service, 1)); + } else { + doDeath(name, service); + } + } + + public void doConnected(ComponentName name, IBinder service) { + ConnectionInfo old; + ConnectionInfo info; + + synchronized (this) { + old = mActiveConnections.get(name); + if (old != null && old.binder == service) { + // Huh, already have this one. Oh well! + return; + } + + if (service != null) { + // A new service is being connected... set it all up. + mDied = false; + info = new ConnectionInfo(); + info.binder = service; + info.deathMonitor = new DeathMonitor(name, service); + try { + service.linkToDeath(info.deathMonitor, 0); + mActiveConnections.put(name, info); + } catch (RemoteException e) { + // This service was dead before we got it... just + // don't do anything with it. + mActiveConnections.remove(name); + return; + } + + } else { + // The named service is being disconnected... clean up. + mActiveConnections.remove(name); + } + + if (old != null) { + old.binder.unlinkToDeath(old.deathMonitor, 0); + } + } + + // If there was an old service, it is not disconnected. + if (old != null) { + mConnection.onServiceDisconnected(name); + } + // If there is a new service, it is now connected. + if (service != null) { + mConnection.onServiceConnected(name, service); + } + } + + public void doDeath(ComponentName name, IBinder service) { + mConnection.onServiceDisconnected(name); + } + + private final class RunConnection implements Runnable { + RunConnection(ComponentName name, IBinder service, int command) { + mName = name; + mService = service; + mCommand = command; + } + + public void run() { + if (mCommand == 0) { + doConnected(mName, mService); + } else if (mCommand == 1) { + doDeath(mName, mService); + } + } + + final ComponentName mName; + final IBinder mService; + final int mCommand; + } + + private final class DeathMonitor implements IBinder.DeathRecipient + { + DeathMonitor(ComponentName name, IBinder service) { + mName = name; + mService = service; + } + + public void binderDied() { + death(mName, mService); + } + + final ComponentName mName; + final IBinder mService; + } + } + } + + private static ApplicationContext mSystemContext = null; + + private static final class ActivityRecord { + IBinder token; + Intent intent; + Bundle state; + Activity activity; + Window window; + Activity parent; + String embeddedID; + Object lastNonConfigurationInstance; + HashMap<String,Object> lastNonConfigurationChildInstances; + boolean paused; + boolean stopped; + boolean hideForNow; + Configuration newConfig; + ActivityRecord nextIdle; + + ActivityInfo activityInfo; + PackageInfo packageInfo; + + List<ResultInfo> pendingResults; + List<Intent> pendingIntents; + + boolean startsNotResumed; + boolean isForward; + + ActivityRecord() { + parent = null; + embeddedID = null; + paused = false; + stopped = false; + hideForNow = false; + nextIdle = null; + } + + public String toString() { + ComponentName componentName = intent.getComponent(); + return "ActivityRecord{" + + Integer.toHexString(System.identityHashCode(this)) + + " token=" + token + " " + (componentName == null + ? "no component name" : componentName.toShortString()) + + "}"; + } + } + + private final class ProviderRecord implements IBinder.DeathRecipient { + final String mName; + final IContentProvider mProvider; + final ContentProvider mLocalProvider; + + ProviderRecord(String name, IContentProvider provider, + ContentProvider localProvider) { + mName = name; + mProvider = provider; + mLocalProvider = localProvider; + } + + public void binderDied() { + removeDeadProvider(mName, mProvider); + } + } + + private static final class NewIntentData { + List<Intent> intents; + IBinder token; + public String toString() { + return "NewIntentData{intents=" + intents + " token=" + token + "}"; + } + } + + private static final class ReceiverData { + Intent intent; + ActivityInfo info; + int resultCode; + String resultData; + Bundle resultExtras; + boolean sync; + boolean resultAbort; + public String toString() { + return "ReceiverData{intent=" + intent + " packageName=" + + info.packageName + " resultCode=" + resultCode + + " resultData=" + resultData + " resultExtras=" + resultExtras + "}"; + } + } + + private static final class CreateServiceData { + IBinder token; + ServiceInfo info; + Intent intent; + public String toString() { + return "CreateServiceData{token=" + token + " className=" + + info.name + " packageName=" + info.packageName + + " intent=" + intent + "}"; + } + } + + private static final class BindServiceData { + IBinder token; + Intent intent; + boolean rebind; + public String toString() { + return "BindServiceData{token=" + token + " intent=" + intent + "}"; + } + } + + private static final class ServiceArgsData { + IBinder token; + int startId; + Intent args; + public String toString() { + return "ServiceArgsData{token=" + token + " startId=" + startId + + " args=" + args + "}"; + } + } + + private static final class AppBindData { + PackageInfo info; + String processName; + ApplicationInfo appInfo; + List<ProviderInfo> providers; + ComponentName instrumentationName; + String profileFile; + Bundle instrumentationArgs; + IInstrumentationWatcher instrumentationWatcher; + int debugMode; + Configuration config; + boolean handlingProfiling; + public String toString() { + return "AppBindData{appInfo=" + appInfo + "}"; + } + } + + private static final class DumpServiceInfo { + FileDescriptor fd; + IBinder service; + String[] args; + boolean dumped; + } + + private static final class ResultData { + IBinder token; + List<ResultInfo> results; + public String toString() { + return "ResultData{token=" + token + " results" + results + "}"; + } + } + + private static final class ContextCleanupInfo { + ApplicationContext context; + String what; + String who; + } + + private final class ApplicationThread extends ApplicationThreadNative { + private static final String HEAP_COLUMN = "%17s %8s %8s %8s %8s"; + private static final String ONE_COUNT_COLUMN = "%17s %8d"; + private static final String TWO_COUNT_COLUMNS = "%17s %8d %17s %8d"; + + // Formatting for checkin service - update version if row format changes + private static final int ACTIVITY_THREAD_CHECKIN_VERSION = 1; + + public final void schedulePauseActivity(IBinder token, boolean finished, + boolean userLeaving, int configChanges) { + queueOrSendMessage( + finished ? H.PAUSE_ACTIVITY_FINISHING : H.PAUSE_ACTIVITY, + token, + (userLeaving ? 1 : 0), + configChanges); + } + + public final void scheduleStopActivity(IBinder token, boolean showWindow, + int configChanges) { + queueOrSendMessage( + showWindow ? H.STOP_ACTIVITY_SHOW : H.STOP_ACTIVITY_HIDE, + token, 0, configChanges); + } + + public final void scheduleWindowVisibility(IBinder token, boolean showWindow) { + queueOrSendMessage( + showWindow ? H.SHOW_WINDOW : H.HIDE_WINDOW, + token); + } + + public final void scheduleResumeActivity(IBinder token, boolean isForward) { + queueOrSendMessage(H.RESUME_ACTIVITY, token, isForward ? 1 : 0); + } + + public final void scheduleSendResult(IBinder token, List<ResultInfo> results) { + ResultData res = new ResultData(); + res.token = token; + res.results = results; + queueOrSendMessage(H.SEND_RESULT, res); + } + + // we use token to identify this activity without having to send the + // activity itself back to the activity manager. (matters more with ipc) + public final void scheduleLaunchActivity(Intent intent, IBinder token, + ActivityInfo info, Bundle state, List<ResultInfo> pendingResults, + List<Intent> pendingNewIntents, boolean notResumed, boolean isForward) { + ActivityRecord r = new ActivityRecord(); + + r.token = token; + r.intent = intent; + r.activityInfo = info; + r.state = state; + + r.pendingResults = pendingResults; + r.pendingIntents = pendingNewIntents; + + r.startsNotResumed = notResumed; + r.isForward = isForward; + + queueOrSendMessage(H.LAUNCH_ACTIVITY, r); + } + + public final void scheduleRelaunchActivity(IBinder token, + List<ResultInfo> pendingResults, List<Intent> pendingNewIntents, + int configChanges, boolean notResumed) { + ActivityRecord r = new ActivityRecord(); + + r.token = token; + r.pendingResults = pendingResults; + r.pendingIntents = pendingNewIntents; + r.startsNotResumed = notResumed; + + synchronized (mRelaunchingActivities) { + mRelaunchingActivities.add(r); + } + + queueOrSendMessage(H.RELAUNCH_ACTIVITY, r, configChanges); + } + + public final void scheduleNewIntent(List<Intent> intents, IBinder token) { + NewIntentData data = new NewIntentData(); + data.intents = intents; + data.token = token; + + queueOrSendMessage(H.NEW_INTENT, data); + } + + public final void scheduleDestroyActivity(IBinder token, boolean finishing, + int configChanges) { + queueOrSendMessage(H.DESTROY_ACTIVITY, token, finishing ? 1 : 0, + configChanges); + } + + public final void scheduleReceiver(Intent intent, ActivityInfo info, + int resultCode, String data, Bundle extras, boolean sync) { + ReceiverData r = new ReceiverData(); + + r.intent = intent; + r.info = info; + r.resultCode = resultCode; + r.resultData = data; + r.resultExtras = extras; + r.sync = sync; + + queueOrSendMessage(H.RECEIVER, r); + } + + public final void scheduleCreateService(IBinder token, + ServiceInfo info) { + CreateServiceData s = new CreateServiceData(); + s.token = token; + s.info = info; + + queueOrSendMessage(H.CREATE_SERVICE, s); + } + + public final void scheduleBindService(IBinder token, Intent intent, + boolean rebind) { + BindServiceData s = new BindServiceData(); + s.token = token; + s.intent = intent; + s.rebind = rebind; + + queueOrSendMessage(H.BIND_SERVICE, s); + } + + public final void scheduleUnbindService(IBinder token, Intent intent) { + BindServiceData s = new BindServiceData(); + s.token = token; + s.intent = intent; + + queueOrSendMessage(H.UNBIND_SERVICE, s); + } + + public final void scheduleServiceArgs(IBinder token, int startId, + Intent args) { + ServiceArgsData s = new ServiceArgsData(); + s.token = token; + s.startId = startId; + s.args = args; + + queueOrSendMessage(H.SERVICE_ARGS, s); + } + + public final void scheduleStopService(IBinder token) { + queueOrSendMessage(H.STOP_SERVICE, token); + } + + public final void bindApplication(String processName, + ApplicationInfo appInfo, List<ProviderInfo> providers, + ComponentName instrumentationName, String profileFile, + Bundle instrumentationArgs, IInstrumentationWatcher instrumentationWatcher, + int debugMode, Configuration config, + Map<String, IBinder> services) { + Process.setArgV0(processName); + + if (services != null) { + // Setup the service cache in the ServiceManager + ServiceManager.initServiceCache(services); + } + + AppBindData data = new AppBindData(); + data.processName = processName; + data.appInfo = appInfo; + data.providers = providers; + data.instrumentationName = instrumentationName; + data.profileFile = profileFile; + data.instrumentationArgs = instrumentationArgs; + data.instrumentationWatcher = instrumentationWatcher; + data.debugMode = debugMode; + data.config = config; + queueOrSendMessage(H.BIND_APPLICATION, data); + } + + public final void scheduleExit() { + queueOrSendMessage(H.EXIT_APPLICATION, null); + } + + public void requestThumbnail(IBinder token) { + queueOrSendMessage(H.REQUEST_THUMBNAIL, token); + } + + public void scheduleConfigurationChanged(Configuration config) { + synchronized (mRelaunchingActivities) { + mPendingConfiguration = config; + } + queueOrSendMessage(H.CONFIGURATION_CHANGED, config); + } + + public void updateTimeZone() { + TimeZone.setDefault(null); + } + + public void processInBackground() { + mH.removeMessages(H.GC_WHEN_IDLE); + mH.sendMessage(mH.obtainMessage(H.GC_WHEN_IDLE)); + } + + public void dumpService(FileDescriptor fd, IBinder servicetoken, String[] args) { + DumpServiceInfo data = new DumpServiceInfo(); + data.fd = fd; + data.service = servicetoken; + data.args = args; + data.dumped = false; + queueOrSendMessage(H.DUMP_SERVICE, data); + synchronized (data) { + while (!data.dumped) { + try { + data.wait(); + } catch (InterruptedException e) { + // no need to do anything here, we will keep waiting until + // dumped is set + } + } + } + } + + // This function exists to make sure all receiver dispatching is + // correctly ordered, since these are one-way calls and the binder driver + // applies transaction ordering per object for such calls. + public void scheduleRegisteredReceiver(IIntentReceiver receiver, Intent intent, + int resultCode, String dataStr, Bundle extras, boolean ordered) + throws RemoteException { + receiver.performReceive(intent, resultCode, dataStr, extras, ordered); + } + + public void scheduleLowMemory() { + queueOrSendMessage(H.LOW_MEMORY, null); + } + + public void scheduleActivityConfigurationChanged(IBinder token) { + queueOrSendMessage(H.ACTIVITY_CONFIGURATION_CHANGED, token); + } + + public void requestPss() { + try { + ActivityManagerNative.getDefault().reportPss(this, + (int)Process.getPss(Process.myPid())); + } catch (RemoteException e) { + } + } + + @Override + protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + long nativeMax = Debug.getNativeHeapSize() / 1024; + long nativeAllocated = Debug.getNativeHeapAllocatedSize() / 1024; + long nativeFree = Debug.getNativeHeapFreeSize() / 1024; + + Debug.MemoryInfo memInfo = new Debug.MemoryInfo(); + Debug.getMemoryInfo(memInfo); + + final int nativeShared = memInfo.nativeSharedDirty; + final int dalvikShared = memInfo.dalvikSharedDirty; + final int otherShared = memInfo.otherSharedDirty; + + final int nativePrivate = memInfo.nativePrivateDirty; + final int dalvikPrivate = memInfo.dalvikPrivateDirty; + final int otherPrivate = memInfo.otherPrivateDirty; + + Runtime runtime = Runtime.getRuntime(); + + long dalvikMax = runtime.totalMemory() / 1024; + long dalvikFree = runtime.freeMemory() / 1024; + long dalvikAllocated = dalvikMax - dalvikFree; + long viewInstanceCount = ViewDebug.getViewInstanceCount(); + long viewRootInstanceCount = ViewDebug.getViewRootInstanceCount(); + long appContextInstanceCount = ApplicationContext.getInstanceCount(); + long activityInstanceCount = Activity.getInstanceCount(); + int globalAssetCount = AssetManager.getGlobalAssetCount(); + int globalAssetManagerCount = AssetManager.getGlobalAssetManagerCount(); + int binderLocalObjectCount = Debug.getBinderLocalObjectCount(); + int binderProxyObjectCount = Debug.getBinderProxyObjectCount(); + int binderDeathObjectCount = Debug.getBinderDeathObjectCount(); + int openSslSocketCount = OpenSSLSocketImpl.getInstanceCount(); + long sqliteAllocated = SQLiteDebug.getHeapAllocatedSize() / 1024; + SQLiteDebug.PagerStats stats = new SQLiteDebug.PagerStats(); + SQLiteDebug.getPagerStats(stats); + + // Check to see if we were called by checkin server. If so, print terse format. + boolean doCheckinFormat = false; + if (args != null) { + for (String arg : args) { + if ("-c".equals(arg)) doCheckinFormat = true; + } + } + + // For checkin, we print one long comma-separated list of values + if (doCheckinFormat) { + // NOTE: if you change anything significant below, also consider changing + // ACTIVITY_THREAD_CHECKIN_VERSION. + String processName = (mBoundApplication != null) + ? mBoundApplication.processName : "unknown"; + + // Header + pw.print(ACTIVITY_THREAD_CHECKIN_VERSION); pw.print(','); + pw.print(Process.myPid()); pw.print(','); + pw.print(processName); pw.print(','); + + // Heap info - max + pw.print(nativeMax); pw.print(','); + pw.print(dalvikMax); pw.print(','); + pw.print("N/A,"); + pw.print(nativeMax + dalvikMax); pw.print(','); + + // Heap info - allocated + pw.print(nativeAllocated); pw.print(','); + pw.print(dalvikAllocated); pw.print(','); + pw.print("N/A,"); + pw.print(nativeAllocated + dalvikAllocated); pw.print(','); + + // Heap info - free + pw.print(nativeFree); pw.print(','); + pw.print(dalvikFree); pw.print(','); + pw.print("N/A,"); + pw.print(nativeFree + dalvikFree); pw.print(','); + + // Heap info - proportional set size + pw.print(memInfo.nativePss); pw.print(','); + pw.print(memInfo.dalvikPss); pw.print(','); + pw.print(memInfo.otherPss); pw.print(','); + pw.print(memInfo.nativePss + memInfo.dalvikPss + memInfo.otherPss); pw.print(','); + + // Heap info - shared + pw.print(nativeShared); pw.print(','); + pw.print(dalvikShared); pw.print(','); + pw.print(otherShared); pw.print(','); + pw.print(nativeShared + dalvikShared + otherShared); pw.print(','); + + // Heap info - private + pw.print(nativePrivate); pw.print(','); + pw.print(dalvikPrivate); pw.print(','); + pw.print(otherPrivate); pw.print(','); + pw.print(nativePrivate + dalvikPrivate + otherPrivate); pw.print(','); + + // Object counts + pw.print(viewInstanceCount); pw.print(','); + pw.print(viewRootInstanceCount); pw.print(','); + pw.print(appContextInstanceCount); pw.print(','); + pw.print(activityInstanceCount); pw.print(','); + + pw.print(globalAssetCount); pw.print(','); + pw.print(globalAssetManagerCount); pw.print(','); + pw.print(binderLocalObjectCount); pw.print(','); + pw.print(binderProxyObjectCount); pw.print(','); + + pw.print(binderDeathObjectCount); pw.print(','); + pw.print(openSslSocketCount); pw.print(','); + + // SQL + pw.print(sqliteAllocated); pw.print(','); + pw.print(stats.databaseBytes / 1024); pw.print(','); + pw.print(stats.numPagers); pw.print(','); + pw.print((stats.totalBytes - stats.referencedBytes) / 1024); pw.print(','); + pw.print(stats.referencedBytes / 1024); pw.print('\n'); + + return; + } + + // otherwise, show human-readable format + printRow(pw, HEAP_COLUMN, "", "native", "dalvik", "other", "total"); + printRow(pw, HEAP_COLUMN, "size:", nativeMax, dalvikMax, "N/A", nativeMax + dalvikMax); + printRow(pw, HEAP_COLUMN, "allocated:", nativeAllocated, dalvikAllocated, "N/A", + nativeAllocated + dalvikAllocated); + printRow(pw, HEAP_COLUMN, "free:", nativeFree, dalvikFree, "N/A", + nativeFree + dalvikFree); + + printRow(pw, HEAP_COLUMN, "(Pss):", memInfo.nativePss, memInfo.dalvikPss, + memInfo.otherPss, memInfo.nativePss + memInfo.dalvikPss + memInfo.otherPss); + + printRow(pw, HEAP_COLUMN, "(shared dirty):", nativeShared, dalvikShared, otherShared, + nativeShared + dalvikShared + otherShared); + printRow(pw, HEAP_COLUMN, "(priv dirty):", nativePrivate, dalvikPrivate, otherPrivate, + nativePrivate + dalvikPrivate + otherPrivate); + + pw.println(" "); + pw.println(" Objects"); + printRow(pw, TWO_COUNT_COLUMNS, "Views:", viewInstanceCount, "ViewRoots:", + viewRootInstanceCount); + + printRow(pw, TWO_COUNT_COLUMNS, "AppContexts:", appContextInstanceCount, + "Activities:", activityInstanceCount); + + printRow(pw, TWO_COUNT_COLUMNS, "Assets:", globalAssetCount, + "AssetManagers:", globalAssetManagerCount); + + printRow(pw, TWO_COUNT_COLUMNS, "Local Binders:", binderLocalObjectCount, + "Proxy Binders:", binderProxyObjectCount); + printRow(pw, ONE_COUNT_COLUMN, "Death Recipients:", binderDeathObjectCount); + + printRow(pw, ONE_COUNT_COLUMN, "OpenSSL Sockets:", openSslSocketCount); + + // SQLite mem info + pw.println(" "); + pw.println(" SQL"); + printRow(pw, TWO_COUNT_COLUMNS, "heap:", sqliteAllocated, "dbFiles:", + stats.databaseBytes / 1024); + printRow(pw, TWO_COUNT_COLUMNS, "numPagers:", stats.numPagers, "inactivePageKB:", + (stats.totalBytes - stats.referencedBytes) / 1024); + printRow(pw, ONE_COUNT_COLUMN, "activePageKB:", stats.referencedBytes / 1024); + } + + private void printRow(PrintWriter pw, String format, Object...objs) { + pw.println(String.format(format, objs)); + } + } + + private final class H extends Handler { + public static final int LAUNCH_ACTIVITY = 100; + public static final int PAUSE_ACTIVITY = 101; + public static final int PAUSE_ACTIVITY_FINISHING= 102; + public static final int STOP_ACTIVITY_SHOW = 103; + public static final int STOP_ACTIVITY_HIDE = 104; + public static final int SHOW_WINDOW = 105; + public static final int HIDE_WINDOW = 106; + public static final int RESUME_ACTIVITY = 107; + public static final int SEND_RESULT = 108; + public static final int DESTROY_ACTIVITY = 109; + public static final int BIND_APPLICATION = 110; + public static final int EXIT_APPLICATION = 111; + public static final int NEW_INTENT = 112; + public static final int RECEIVER = 113; + public static final int CREATE_SERVICE = 114; + public static final int SERVICE_ARGS = 115; + public static final int STOP_SERVICE = 116; + public static final int REQUEST_THUMBNAIL = 117; + public static final int CONFIGURATION_CHANGED = 118; + public static final int CLEAN_UP_CONTEXT = 119; + public static final int GC_WHEN_IDLE = 120; + public static final int BIND_SERVICE = 121; + public static final int UNBIND_SERVICE = 122; + public static final int DUMP_SERVICE = 123; + public static final int LOW_MEMORY = 124; + public static final int ACTIVITY_CONFIGURATION_CHANGED = 125; + public static final int RELAUNCH_ACTIVITY = 126; + String codeToString(int code) { + if (localLOGV) { + switch (code) { + case LAUNCH_ACTIVITY: return "LAUNCH_ACTIVITY"; + case PAUSE_ACTIVITY: return "PAUSE_ACTIVITY"; + case PAUSE_ACTIVITY_FINISHING: return "PAUSE_ACTIVITY_FINISHING"; + case STOP_ACTIVITY_SHOW: return "STOP_ACTIVITY_SHOW"; + case STOP_ACTIVITY_HIDE: return "STOP_ACTIVITY_HIDE"; + case SHOW_WINDOW: return "SHOW_WINDOW"; + case HIDE_WINDOW: return "HIDE_WINDOW"; + case RESUME_ACTIVITY: return "RESUME_ACTIVITY"; + case SEND_RESULT: return "SEND_RESULT"; + case DESTROY_ACTIVITY: return "DESTROY_ACTIVITY"; + case BIND_APPLICATION: return "BIND_APPLICATION"; + case EXIT_APPLICATION: return "EXIT_APPLICATION"; + case NEW_INTENT: return "NEW_INTENT"; + case RECEIVER: return "RECEIVER"; + case CREATE_SERVICE: return "CREATE_SERVICE"; + case SERVICE_ARGS: return "SERVICE_ARGS"; + case STOP_SERVICE: return "STOP_SERVICE"; + case REQUEST_THUMBNAIL: return "REQUEST_THUMBNAIL"; + case CONFIGURATION_CHANGED: return "CONFIGURATION_CHANGED"; + case CLEAN_UP_CONTEXT: return "CLEAN_UP_CONTEXT"; + case GC_WHEN_IDLE: return "GC_WHEN_IDLE"; + case BIND_SERVICE: return "BIND_SERVICE"; + case UNBIND_SERVICE: return "UNBIND_SERVICE"; + case DUMP_SERVICE: return "DUMP_SERVICE"; + case LOW_MEMORY: return "LOW_MEMORY"; + case ACTIVITY_CONFIGURATION_CHANGED: return "ACTIVITY_CONFIGURATION_CHANGED"; + case RELAUNCH_ACTIVITY: return "RELAUNCH_ACTIVITY"; + } + } + return "(unknown)"; + } + public void handleMessage(Message msg) { + switch (msg.what) { + case LAUNCH_ACTIVITY: { + ActivityRecord r = (ActivityRecord)msg.obj; + + r.packageInfo = getPackageInfoNoCheck( + r.activityInfo.applicationInfo); + handleLaunchActivity(r); + } break; + case RELAUNCH_ACTIVITY: { + ActivityRecord r = (ActivityRecord)msg.obj; + handleRelaunchActivity(r, msg.arg1); + } break; + case PAUSE_ACTIVITY: + handlePauseActivity((IBinder)msg.obj, false, msg.arg1 != 0, msg.arg2); + break; + case PAUSE_ACTIVITY_FINISHING: + handlePauseActivity((IBinder)msg.obj, true, msg.arg1 != 0, msg.arg2); + break; + case STOP_ACTIVITY_SHOW: + handleStopActivity((IBinder)msg.obj, true, msg.arg2); + break; + case STOP_ACTIVITY_HIDE: + handleStopActivity((IBinder)msg.obj, false, msg.arg2); + break; + case SHOW_WINDOW: + handleWindowVisibility((IBinder)msg.obj, true); + break; + case HIDE_WINDOW: + handleWindowVisibility((IBinder)msg.obj, false); + break; + case RESUME_ACTIVITY: + handleResumeActivity((IBinder)msg.obj, true, + msg.arg1 != 0); + break; + case SEND_RESULT: + handleSendResult((ResultData)msg.obj); + break; + case DESTROY_ACTIVITY: + handleDestroyActivity((IBinder)msg.obj, msg.arg1 != 0, + msg.arg2, false); + break; + case BIND_APPLICATION: + AppBindData data = (AppBindData)msg.obj; + handleBindApplication(data); + break; + case EXIT_APPLICATION: + if (mInitialApplication != null) { + mInitialApplication.onTerminate(); + } + Looper.myLooper().quit(); + break; + case NEW_INTENT: + handleNewIntent((NewIntentData)msg.obj); + break; + case RECEIVER: + handleReceiver((ReceiverData)msg.obj); + break; + case CREATE_SERVICE: + handleCreateService((CreateServiceData)msg.obj); + break; + case BIND_SERVICE: + handleBindService((BindServiceData)msg.obj); + break; + case UNBIND_SERVICE: + handleUnbindService((BindServiceData)msg.obj); + break; + case SERVICE_ARGS: + handleServiceArgs((ServiceArgsData)msg.obj); + break; + case STOP_SERVICE: + handleStopService((IBinder)msg.obj); + break; + case REQUEST_THUMBNAIL: + handleRequestThumbnail((IBinder)msg.obj); + break; + case CONFIGURATION_CHANGED: + handleConfigurationChanged((Configuration)msg.obj); + break; + case CLEAN_UP_CONTEXT: + ContextCleanupInfo cci = (ContextCleanupInfo)msg.obj; + cci.context.performFinalCleanup(cci.who, cci.what); + break; + case GC_WHEN_IDLE: + scheduleGcIdler(); + break; + case DUMP_SERVICE: + handleDumpService((DumpServiceInfo)msg.obj); + break; + case LOW_MEMORY: + handleLowMemory(); + break; + case ACTIVITY_CONFIGURATION_CHANGED: + handleActivityConfigurationChanged((IBinder)msg.obj); + break; + } + } + } + + private final class Idler implements MessageQueue.IdleHandler { + public final boolean queueIdle() { + ActivityRecord a = mNewActivities; + if (a != null) { + mNewActivities = null; + IActivityManager am = ActivityManagerNative.getDefault(); + ActivityRecord prev; + do { + if (localLOGV) Log.v( + TAG, "Reporting idle of " + a + + " finished=" + + (a.activity != null ? a.activity.mFinished : false)); + if (a.activity != null && !a.activity.mFinished) { + try { + am.activityIdle(a.token); + } catch (RemoteException ex) { + } + } + prev = a; + a = a.nextIdle; + prev.nextIdle = null; + } while (a != null); + } + return false; + } + } + + final class GcIdler implements MessageQueue.IdleHandler { + public final boolean queueIdle() { + doGcIfNeeded(); + return false; + } + } + + static IPackageManager sPackageManager; + + final ApplicationThread mAppThread = new ApplicationThread(); + final Looper mLooper = Looper.myLooper(); + final H mH = new H(); + final HashMap<IBinder, ActivityRecord> mActivities + = new HashMap<IBinder, ActivityRecord>(); + // List of new activities (via ActivityRecord.nextIdle) that should + // be reported when next we idle. + ActivityRecord mNewActivities = null; + // Number of activities that are currently visible on-screen. + int mNumVisibleActivities = 0; + final HashMap<IBinder, Service> mServices + = new HashMap<IBinder, Service>(); + AppBindData mBoundApplication; + Configuration mConfiguration; + Application mInitialApplication; + final ArrayList<Application> mAllApplications + = new ArrayList<Application>(); + static final ThreadLocal sThreadLocal = new ThreadLocal(); + Instrumentation mInstrumentation; + String mInstrumentationAppDir = null; + String mInstrumentationAppPackage = null; + String mInstrumentedAppDir = null; + boolean mSystemThread = false; + + /** + * Activities that are enqueued to be relaunched. This list is accessed + * by multiple threads, so you must synchronize on it when accessing it. + */ + final ArrayList<ActivityRecord> mRelaunchingActivities + = new ArrayList<ActivityRecord>(); + Configuration mPendingConfiguration = null; + + // These can be accessed by multiple threads; mPackages is the lock. + // XXX For now we keep around information about all packages we have + // seen, not removing entries from this map. + final HashMap<String, WeakReference<PackageInfo>> mPackages + = new HashMap<String, WeakReference<PackageInfo>>(); + final HashMap<String, WeakReference<PackageInfo>> mResourcePackages + = new HashMap<String, WeakReference<PackageInfo>>(); + Display mDisplay = null; + DisplayMetrics mDisplayMetrics = null; + HashMap<String, WeakReference<Resources> > mActiveResources + = new HashMap<String, WeakReference<Resources> >(); + + // The lock of mProviderMap protects the following variables. + final HashMap<String, ProviderRecord> mProviderMap + = new HashMap<String, ProviderRecord>(); + final HashMap<IBinder, ProviderRefCount> mProviderRefCountMap + = new HashMap<IBinder, ProviderRefCount>(); + final HashMap<IBinder, ProviderRecord> mLocalProviders + = new HashMap<IBinder, ProviderRecord>(); + + final GcIdler mGcIdler = new GcIdler(); + boolean mGcIdlerScheduled = false; + + public final PackageInfo getPackageInfo(String packageName, int flags) { + synchronized (mPackages) { + WeakReference<PackageInfo> ref; + if ((flags&Context.CONTEXT_INCLUDE_CODE) != 0) { + ref = mPackages.get(packageName); + } else { + ref = mResourcePackages.get(packageName); + } + PackageInfo packageInfo = ref != null ? ref.get() : null; + //Log.i(TAG, "getPackageInfo " + packageName + ": " + packageInfo); + if (packageInfo != null && (packageInfo.mResources == null + || packageInfo.mResources.getAssets().isUpToDate())) { + if (packageInfo.isSecurityViolation() + && (flags&Context.CONTEXT_IGNORE_SECURITY) == 0) { + throw new SecurityException( + "Requesting code from " + packageName + + " to be run in process " + + mBoundApplication.processName + + "/" + mBoundApplication.appInfo.uid); + } + return packageInfo; + } + } + + ApplicationInfo ai = null; + try { + ai = getPackageManager().getApplicationInfo(packageName, + PackageManager.GET_SHARED_LIBRARY_FILES); + } catch (RemoteException e) { + } + + if (ai != null) { + return getPackageInfo(ai, flags); + } + + return null; + } + + public final PackageInfo getPackageInfo(ApplicationInfo ai, int flags) { + boolean includeCode = (flags&Context.CONTEXT_INCLUDE_CODE) != 0; + boolean securityViolation = includeCode && ai.uid != 0 + && ai.uid != Process.SYSTEM_UID && (mBoundApplication != null + ? ai.uid != mBoundApplication.appInfo.uid : true); + if ((flags&(Context.CONTEXT_INCLUDE_CODE + |Context.CONTEXT_IGNORE_SECURITY)) + == Context.CONTEXT_INCLUDE_CODE) { + if (securityViolation) { + String msg = "Requesting code from " + ai.packageName + + " (with uid " + ai.uid + ")"; + if (mBoundApplication != null) { + msg = msg + " to be run in process " + + mBoundApplication.processName + " (with uid " + + mBoundApplication.appInfo.uid + ")"; + } + throw new SecurityException(msg); + } + } + return getPackageInfo(ai, null, securityViolation, includeCode); + } + + public final PackageInfo getPackageInfoNoCheck(ApplicationInfo ai) { + return getPackageInfo(ai, null, false, true); + } + + private final PackageInfo getPackageInfo(ApplicationInfo aInfo, + ClassLoader baseLoader, boolean securityViolation, boolean includeCode) { + synchronized (mPackages) { + WeakReference<PackageInfo> ref; + if (includeCode) { + ref = mPackages.get(aInfo.packageName); + } else { + ref = mResourcePackages.get(aInfo.packageName); + } + PackageInfo packageInfo = ref != null ? ref.get() : null; + if (packageInfo == null || (packageInfo.mResources != null + && !packageInfo.mResources.getAssets().isUpToDate())) { + if (localLOGV) Log.v(TAG, (includeCode ? "Loading code package " + : "Loading resource-only package ") + aInfo.packageName + + " (in " + (mBoundApplication != null + ? mBoundApplication.processName : null) + + ")"); + packageInfo = + new PackageInfo(this, aInfo, this, baseLoader, + securityViolation, includeCode && + (aInfo.flags&ApplicationInfo.FLAG_HAS_CODE) != 0); + if (includeCode) { + mPackages.put(aInfo.packageName, + new WeakReference<PackageInfo>(packageInfo)); + } else { + mResourcePackages.put(aInfo.packageName, + new WeakReference<PackageInfo>(packageInfo)); + } + } + return packageInfo; + } + } + + public final boolean hasPackageInfo(String packageName) { + synchronized (mPackages) { + WeakReference<PackageInfo> ref; + ref = mPackages.get(packageName); + if (ref != null && ref.get() != null) { + return true; + } + ref = mResourcePackages.get(packageName); + if (ref != null && ref.get() != null) { + return true; + } + return false; + } + } + + ActivityThread() { + } + + public ApplicationThread getApplicationThread() + { + return mAppThread; + } + + public Instrumentation getInstrumentation() + { + return mInstrumentation; + } + + public Configuration getConfiguration() { + return mConfiguration; + } + + public boolean isProfiling() { + return mBoundApplication != null && mBoundApplication.profileFile != null; + } + + public String getProfileFilePath() { + return mBoundApplication.profileFile; + } + + public Looper getLooper() { + return mLooper; + } + + public Application getApplication() { + return mInitialApplication; + } + + public ApplicationContext getSystemContext() { + synchronized (this) { + if (mSystemContext == null) { + ApplicationContext context = + ApplicationContext.createSystemContext(this); + PackageInfo info = new PackageInfo(this, "android", context); + context.init(info, null, this); + context.getResources().updateConfiguration( + getConfiguration(), getDisplayMetricsLocked(false)); + mSystemContext = context; + //Log.i(TAG, "Created system resources " + context.getResources() + // + ": " + context.getResources().getConfiguration()); + } + } + return mSystemContext; + } + + void scheduleGcIdler() { + if (!mGcIdlerScheduled) { + mGcIdlerScheduled = true; + Looper.myQueue().addIdleHandler(mGcIdler); + } + mH.removeMessages(H.GC_WHEN_IDLE); + } + + void unscheduleGcIdler() { + if (mGcIdlerScheduled) { + mGcIdlerScheduled = false; + Looper.myQueue().removeIdleHandler(mGcIdler); + } + mH.removeMessages(H.GC_WHEN_IDLE); + } + + void doGcIfNeeded() { + mGcIdlerScheduled = false; + final long now = SystemClock.uptimeMillis(); + //Log.i(TAG, "**** WE MIGHT WANT TO GC: then=" + Binder.getLastGcTime() + // + "m now=" + now); + if ((BinderInternal.getLastGcTime()+MIN_TIME_BETWEEN_GCS) < now) { + //Log.i(TAG, "**** WE DO, WE DO WANT TO GC!"); + BinderInternal.forceGc("bg"); + } + } + + public final ActivityInfo resolveActivityInfo(Intent intent) { + ActivityInfo aInfo = intent.resolveActivityInfo( + mInitialApplication.getPackageManager(), PackageManager.GET_SHARED_LIBRARY_FILES); + if (aInfo == null) { + // Throw an exception. + Instrumentation.checkStartActivityResult( + IActivityManager.START_CLASS_NOT_FOUND, intent); + } + return aInfo; + } + + public final Activity startActivityNow(Activity parent, String id, + Intent intent, IBinder token, Bundle state) { + ActivityInfo aInfo = resolveActivityInfo(intent); + return startActivityNow(parent, id, intent, aInfo, token, state); + } + + public final Activity startActivityNow(Activity parent, String id, + Intent intent, ActivityInfo activityInfo, IBinder token, Bundle state) { + return startActivityNow(parent, id, intent, activityInfo, token, state, null); + } + + public final Activity startActivityNow(Activity parent, String id, + Intent intent, ActivityInfo activityInfo, IBinder token, Bundle state, + Object lastNonConfigurationInstance) { + ActivityRecord r = new ActivityRecord(); + r.token = token; + r.intent = intent; + r.state = state; + r.parent = parent; + r.embeddedID = id; + r.activityInfo = activityInfo; + r.lastNonConfigurationInstance = lastNonConfigurationInstance; + if (localLOGV) { + ComponentName compname = intent.getComponent(); + String name; + if (compname != null) { + name = compname.toShortString(); + } else { + name = "(Intent " + intent + ").getComponent() returned null"; + } + Log.v(TAG, "Performing launch: action=" + intent.getAction() + + ", comp=" + name + + ", token=" + token); + } + return performLaunchActivity(r); + } + + public final Activity getActivity(IBinder token) { + return mActivities.get(token).activity; + } + + public final void sendActivityResult( + IBinder token, String id, int requestCode, + int resultCode, Intent data) { + ArrayList<ResultInfo> list = new ArrayList<ResultInfo>(); + list.add(new ResultInfo(id, requestCode, resultCode, data)); + mAppThread.scheduleSendResult(token, list); + } + + // if the thread hasn't started yet, we don't have the handler, so just + // save the messages until we're ready. + private final void queueOrSendMessage(int what, Object obj) { + queueOrSendMessage(what, obj, 0, 0); + } + + private final void queueOrSendMessage(int what, Object obj, int arg1) { + queueOrSendMessage(what, obj, arg1, 0); + } + + private final void queueOrSendMessage(int what, Object obj, int arg1, int arg2) { + synchronized (this) { + if (localLOGV) Log.v( + TAG, "SCHEDULE " + what + " " + mH.codeToString(what) + + ": " + arg1 + " / " + obj); + Message msg = Message.obtain(); + msg.what = what; + msg.obj = obj; + msg.arg1 = arg1; + msg.arg2 = arg2; + mH.sendMessage(msg); + } + } + + final void scheduleContextCleanup(ApplicationContext context, String who, + String what) { + ContextCleanupInfo cci = new ContextCleanupInfo(); + cci.context = context; + cci.who = who; + cci.what = what; + queueOrSendMessage(H.CLEAN_UP_CONTEXT, cci); + } + + private final Activity performLaunchActivity(ActivityRecord r) { + // System.out.println("##### [" + System.currentTimeMillis() + "] ActivityThread.performLaunchActivity(" + r + ")"); + + ActivityInfo aInfo = r.activityInfo; + if (r.packageInfo == null) { + r.packageInfo = getPackageInfo(aInfo.applicationInfo, + Context.CONTEXT_INCLUDE_CODE); + } + + ComponentName component = r.intent.getComponent(); + if (component == null) { + component = r.intent.resolveActivity( + mInitialApplication.getPackageManager()); + r.intent.setComponent(component); + } + + if (r.activityInfo.targetActivity != null) { + component = new ComponentName(r.activityInfo.packageName, + r.activityInfo.targetActivity); + } + + Activity activity = null; + try { + java.lang.ClassLoader cl = r.packageInfo.getClassLoader(); + activity = mInstrumentation.newActivity( + cl, component.getClassName(), r.intent); + r.intent.setExtrasClassLoader(cl); + if (r.state != null) { + r.state.setClassLoader(cl); + } + } catch (Exception e) { + if (!mInstrumentation.onException(activity, e)) { + throw new RuntimeException( + "Unable to instantiate activity " + component + + ": " + e.toString(), e); + } + } + + try { + Application app = r.packageInfo.makeApplication(); + + if (localLOGV) Log.v(TAG, "Performing launch of " + r); + if (localLOGV) Log.v( + TAG, r + ": app=" + app + + ", appName=" + app.getPackageName() + + ", pkg=" + r.packageInfo.getPackageName() + + ", comp=" + r.intent.getComponent().toShortString() + + ", dir=" + r.packageInfo.getAppDir()); + + if (activity != null) { + ApplicationContext appContext = new ApplicationContext(); + appContext.init(r.packageInfo, r.token, this); + appContext.setOuterContext(activity); + CharSequence title = r.activityInfo.loadLabel(appContext.getPackageManager()); + Configuration config = new Configuration(mConfiguration); + activity.attach(appContext, this, getInstrumentation(), r.token, app, + r.intent, r.activityInfo, title, r.parent, r.embeddedID, + r.lastNonConfigurationInstance, r.lastNonConfigurationChildInstances, + config); + + r.lastNonConfigurationInstance = null; + r.lastNonConfigurationChildInstances = null; + activity.mStartedActivity = false; + int theme = r.activityInfo.getThemeResource(); + if (theme != 0) { + activity.setTheme(theme); + } + + activity.mCalled = false; + mInstrumentation.callActivityOnCreate(activity, r.state); + if (!activity.mCalled) { + throw new SuperNotCalledException( + "Activity " + r.intent.getComponent().toShortString() + + " did not call through to super.onCreate()"); + } + r.activity = activity; + r.stopped = true; + if (!r.activity.mFinished) { + activity.performStart(); + r.stopped = false; + } + if (!r.activity.mFinished) { + if (r.state != null) { + mInstrumentation.callActivityOnRestoreInstanceState(activity, r.state); + } + } + if (!r.activity.mFinished) { + activity.mCalled = false; + mInstrumentation.callActivityOnPostCreate(activity, r.state); + if (!activity.mCalled) { + throw new SuperNotCalledException( + "Activity " + r.intent.getComponent().toShortString() + + " did not call through to super.onPostCreate()"); + } + } + r.state = null; + } + r.paused = true; + + mActivities.put(r.token, r); + + } catch (SuperNotCalledException e) { + throw e; + + } catch (Exception e) { + if (!mInstrumentation.onException(activity, e)) { + throw new RuntimeException( + "Unable to start activity " + component + + ": " + e.toString(), e); + } + } + + return activity; + } + + private final void handleLaunchActivity(ActivityRecord r) { + // If we are getting ready to gc after going to the background, well + // we are back active so skip it. + unscheduleGcIdler(); + + if (localLOGV) Log.v( + TAG, "Handling launch of " + r); + Activity a = performLaunchActivity(r); + + if (a != null) { + handleResumeActivity(r.token, false, r.isForward); + + if (!r.activity.mFinished && r.startsNotResumed) { + // The activity manager actually wants this one to start out + // paused, because it needs to be visible but isn't in the + // foreground. We accomplish this by going through the + // normal startup (because activities expect to go through + // onResume() the first time they run, before their window + // is displayed), and then pausing it. However, in this case + // we do -not- need to do the full pause cycle (of freezing + // and such) because the activity manager assumes it can just + // retain the current state it has. + try { + r.activity.mCalled = false; + mInstrumentation.callActivityOnPause(r.activity); + if (!r.activity.mCalled) { + throw new SuperNotCalledException( + "Activity " + r.intent.getComponent().toShortString() + + " did not call through to super.onPause()"); + } + + } catch (SuperNotCalledException e) { + throw e; + + } catch (Exception e) { + if (!mInstrumentation.onException(r.activity, e)) { + throw new RuntimeException( + "Unable to pause activity " + + r.intent.getComponent().toShortString() + + ": " + e.toString(), e); + } + } + r.paused = true; + } + } else { + // If there was an error, for any reason, tell the activity + // manager to stop us. + try { + ActivityManagerNative.getDefault() + .finishActivity(r.token, Activity.RESULT_CANCELED, null); + } catch (RemoteException ex) { + } + } + } + + private final void deliverNewIntents(ActivityRecord r, + List<Intent> intents) { + final int N = intents.size(); + for (int i=0; i<N; i++) { + Intent intent = intents.get(i); + intent.setExtrasClassLoader(r.activity.getClassLoader()); + mInstrumentation.callActivityOnNewIntent(r.activity, intent); + } + } + + public final void performNewIntents(IBinder token, + List<Intent> intents) { + ActivityRecord r = mActivities.get(token); + if (r != null) { + final boolean resumed = !r.paused; + if (resumed) { + mInstrumentation.callActivityOnPause(r.activity); + } + deliverNewIntents(r, intents); + if (resumed) { + mInstrumentation.callActivityOnResume(r.activity); + } + } + } + + private final void handleNewIntent(NewIntentData data) { + performNewIntents(data.token, data.intents); + } + + private final void handleReceiver(ReceiverData data) { + // If we are getting ready to gc after going to the background, well + // we are back active so skip it. + unscheduleGcIdler(); + + String component = data.intent.getComponent().getClassName(); + + PackageInfo packageInfo = getPackageInfoNoCheck( + data.info.applicationInfo); + + IActivityManager mgr = ActivityManagerNative.getDefault(); + + BroadcastReceiver receiver = null; + try { + java.lang.ClassLoader cl = packageInfo.getClassLoader(); + data.intent.setExtrasClassLoader(cl); + if (data.resultExtras != null) { + data.resultExtras.setClassLoader(cl); + } + receiver = (BroadcastReceiver)cl.loadClass(component).newInstance(); + } catch (Exception e) { + try { + mgr.finishReceiver(mAppThread.asBinder(), data.resultCode, + data.resultData, data.resultExtras, data.resultAbort); + } catch (RemoteException ex) { + } + throw new RuntimeException( + "Unable to instantiate receiver " + component + + ": " + e.toString(), e); + } + + try { + Application app = packageInfo.makeApplication(); + + if (localLOGV) Log.v( + TAG, "Performing receive of " + data.intent + + ": app=" + app + + ", appName=" + app.getPackageName() + + ", pkg=" + packageInfo.getPackageName() + + ", comp=" + data.intent.getComponent().toShortString() + + ", dir=" + packageInfo.getAppDir()); + + ApplicationContext context = (ApplicationContext)app.getBaseContext(); + receiver.setOrderedHint(true); + receiver.setResult(data.resultCode, data.resultData, + data.resultExtras); + receiver.setOrderedHint(data.sync); + receiver.onReceive(context.getReceiverRestrictedContext(), + data.intent); + } catch (Exception e) { + try { + mgr.finishReceiver(mAppThread.asBinder(), data.resultCode, + data.resultData, data.resultExtras, data.resultAbort); + } catch (RemoteException ex) { + } + if (!mInstrumentation.onException(receiver, e)) { + throw new RuntimeException( + "Unable to start receiver " + component + + ": " + e.toString(), e); + } + } + + try { + if (data.sync) { + mgr.finishReceiver( + mAppThread.asBinder(), receiver.getResultCode(), + receiver.getResultData(), receiver.getResultExtras(false), + receiver.getAbortBroadcast()); + } else { + mgr.finishReceiver(mAppThread.asBinder(), 0, null, null, false); + } + } catch (RemoteException ex) { + } + } + + private final void handleCreateService(CreateServiceData data) { + // If we are getting ready to gc after going to the background, well + // we are back active so skip it. + unscheduleGcIdler(); + + PackageInfo packageInfo = getPackageInfoNoCheck( + data.info.applicationInfo); + Service service = null; + try { + java.lang.ClassLoader cl = packageInfo.getClassLoader(); + service = (Service) cl.loadClass(data.info.name).newInstance(); + } catch (Exception e) { + if (!mInstrumentation.onException(service, e)) { + throw new RuntimeException( + "Unable to instantiate service " + data.info.name + + ": " + e.toString(), e); + } + } + + try { + if (localLOGV) Log.v(TAG, "Creating service " + data.info.name); + + ApplicationContext context = new ApplicationContext(); + context.init(packageInfo, null, this); + + Application app = packageInfo.makeApplication(); + context.setOuterContext(service); + service.attach(context, this, data.info.name, data.token, app, + ActivityManagerNative.getDefault()); + service.onCreate(); + mServices.put(data.token, service); + try { + ActivityManagerNative.getDefault().serviceDoneExecuting(data.token); + } catch (RemoteException e) { + // nothing to do. + } + } catch (Exception e) { + if (!mInstrumentation.onException(service, e)) { + throw new RuntimeException( + "Unable to create service " + data.info.name + + ": " + e.toString(), e); + } + } + } + + private final void handleBindService(BindServiceData data) { + Service s = mServices.get(data.token); + if (s != null) { + try { + data.intent.setExtrasClassLoader(s.getClassLoader()); + try { + if (!data.rebind) { + IBinder binder = s.onBind(data.intent); + ActivityManagerNative.getDefault().publishService( + data.token, data.intent, binder); + } else { + s.onRebind(data.intent); + ActivityManagerNative.getDefault().serviceDoneExecuting( + data.token); + } + } catch (RemoteException ex) { + } + } catch (Exception e) { + if (!mInstrumentation.onException(s, e)) { + throw new RuntimeException( + "Unable to bind to service " + s + + " with " + data.intent + ": " + e.toString(), e); + } + } + } + } + + private final void handleUnbindService(BindServiceData data) { + Service s = mServices.get(data.token); + if (s != null) { + try { + data.intent.setExtrasClassLoader(s.getClassLoader()); + boolean doRebind = s.onUnbind(data.intent); + try { + if (doRebind) { + ActivityManagerNative.getDefault().unbindFinished( + data.token, data.intent, doRebind); + } else { + ActivityManagerNative.getDefault().serviceDoneExecuting( + data.token); + } + } catch (RemoteException ex) { + } + } catch (Exception e) { + if (!mInstrumentation.onException(s, e)) { + throw new RuntimeException( + "Unable to unbind to service " + s + + " with " + data.intent + ": " + e.toString(), e); + } + } + } + } + + private void handleDumpService(DumpServiceInfo info) { + try { + Service s = mServices.get(info.service); + if (s != null) { + PrintWriter pw = new PrintWriter(new FileOutputStream(info.fd)); + s.dump(info.fd, pw, info.args); + pw.close(); + } + } finally { + synchronized (info) { + info.dumped = true; + info.notifyAll(); + } + } + } + + private final void handleServiceArgs(ServiceArgsData data) { + Service s = mServices.get(data.token); + if (s != null) { + try { + if (data.args != null) { + data.args.setExtrasClassLoader(s.getClassLoader()); + } + s.onStart(data.args, data.startId); + try { + ActivityManagerNative.getDefault().serviceDoneExecuting(data.token); + } catch (RemoteException e) { + // nothing to do. + } + } catch (Exception e) { + if (!mInstrumentation.onException(s, e)) { + throw new RuntimeException( + "Unable to start service " + s + + " with " + data.args + ": " + e.toString(), e); + } + } + } + } + + private final void handleStopService(IBinder token) { + Service s = mServices.remove(token); + if (s != null) { + try { + if (localLOGV) Log.v(TAG, "Destroying service " + s); + s.onDestroy(); + Context context = s.getBaseContext(); + if (context instanceof ApplicationContext) { + final String who = s.getClassName(); + ((ApplicationContext) context).scheduleFinalCleanup(who, "Service"); + } + try { + ActivityManagerNative.getDefault().serviceDoneExecuting(token); + } catch (RemoteException e) { + // nothing to do. + } + } catch (Exception e) { + if (!mInstrumentation.onException(s, e)) { + throw new RuntimeException( + "Unable to stop service " + s + + ": " + e.toString(), e); + } + } + } + //Log.i(TAG, "Running services: " + mServices); + } + + public final ActivityRecord performResumeActivity(IBinder token, + boolean clearHide) { + ActivityRecord r = mActivities.get(token); + if (localLOGV) Log.v(TAG, "Performing resume of " + r + + " finished=" + r.activity.mFinished); + if (r != null && !r.activity.mFinished) { + if (clearHide) { + r.hideForNow = false; + r.activity.mStartedActivity = false; + } + try { + if (r.pendingIntents != null) { + deliverNewIntents(r, r.pendingIntents); + r.pendingIntents = null; + } + if (r.pendingResults != null) { + deliverResults(r, r.pendingResults); + r.pendingResults = null; + } + r.activity.performResume(); + + EventLog.writeEvent(LOG_ON_RESUME_CALLED, + r.activity.getComponentName().getClassName()); + + r.paused = false; + r.stopped = false; + if (r.activity.mStartedActivity) { + r.hideForNow = true; + } + r.state = null; + } catch (Exception e) { + if (!mInstrumentation.onException(r.activity, e)) { + throw new RuntimeException( + "Unable to resume activity " + + r.intent.getComponent().toShortString() + + ": " + e.toString(), e); + } + } + } + return r; + } + + final void handleResumeActivity(IBinder token, boolean clearHide, boolean isForward) { + // If we are getting ready to gc after going to the background, well + // we are back active so skip it. + unscheduleGcIdler(); + + ActivityRecord r = performResumeActivity(token, clearHide); + + if (r != null) { + final Activity a = r.activity; + + if (localLOGV) Log.v( + TAG, "Resume " + r + " started activity: " + + a.mStartedActivity + ", hideForNow: " + r.hideForNow + + ", finished: " + a.mFinished); + + final int forwardBit = isForward ? + WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION : 0; + + // If the window hasn't yet been added to the window manager, + // and this guy didn't finish itself or start another activity, + // then go ahead and add the window. + if (r.window == null && !a.mFinished && !a.mStartedActivity) { + r.window = r.activity.getWindow(); + View decor = r.window.getDecorView(); + decor.setVisibility(View.INVISIBLE); + ViewManager wm = a.getWindowManager(); + WindowManager.LayoutParams l = r.window.getAttributes(); + a.mDecor = decor; + l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION; + l.softInputMode |= forwardBit; + if (a.mVisibleFromClient) { + a.mWindowAdded = true; + wm.addView(decor, l); + } + + // If the window has already been added, but during resume + // we started another activity, then don't yet make the + // window visisble. + } else if (a.mStartedActivity) { + if (localLOGV) Log.v( + TAG, "Launch " + r + " mStartedActivity set"); + r.hideForNow = true; + } + + // The window is now visible if it has been added, we are not + // simply finishing, and we are not starting another activity. + if (!r.activity.mFinished && r.activity.mDecor != null + && !r.hideForNow) { + if (r.newConfig != null) { + performConfigurationChanged(r.activity, r.newConfig); + r.newConfig = null; + } + if (localLOGV) Log.v(TAG, "Resuming " + r + " with isForward=" + + isForward); + WindowManager.LayoutParams l = r.window.getAttributes(); + if ((l.softInputMode + & WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) + != forwardBit) { + l.softInputMode = (l.softInputMode + & (~WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION)) + | forwardBit; + ViewManager wm = a.getWindowManager(); + View decor = r.window.getDecorView(); + wm.updateViewLayout(decor, l); + } + r.activity.mVisibleFromServer = true; + mNumVisibleActivities++; + if (r.activity.mVisibleFromClient) { + r.activity.makeVisible(); + } + } + + r.nextIdle = mNewActivities; + mNewActivities = r; + if (localLOGV) Log.v( + TAG, "Scheduling idle handler for " + r); + Looper.myQueue().addIdleHandler(new Idler()); + + } else { + // If an exception was thrown when trying to resume, then + // just end this activity. + try { + ActivityManagerNative.getDefault() + .finishActivity(token, Activity.RESULT_CANCELED, null); + } catch (RemoteException ex) { + } + } + } + + private int mThumbnailWidth = -1; + private int mThumbnailHeight = -1; + + private final Bitmap createThumbnailBitmap(ActivityRecord r) { + Bitmap thumbnail = null; + try { + int w = mThumbnailWidth; + int h; + if (w < 0) { + Resources res = r.activity.getResources(); + mThumbnailHeight = h = + res.getDimensionPixelSize(com.android.internal.R.dimen.thumbnail_height); + + mThumbnailWidth = w = + res.getDimensionPixelSize(com.android.internal.R.dimen.thumbnail_width); + } else { + h = mThumbnailHeight; + } + + // XXX Only set hasAlpha if needed? + thumbnail = Bitmap.createBitmap(w, h, Bitmap.Config.RGB_565); + thumbnail.eraseColor(0); + Canvas cv = new Canvas(thumbnail); + if (!r.activity.onCreateThumbnail(thumbnail, cv)) { + thumbnail = null; + } + } catch (Exception e) { + if (!mInstrumentation.onException(r.activity, e)) { + throw new RuntimeException( + "Unable to create thumbnail of " + + r.intent.getComponent().toShortString() + + ": " + e.toString(), e); + } + thumbnail = null; + } + + return thumbnail; + } + + private final void handlePauseActivity(IBinder token, boolean finished, + boolean userLeaving, int configChanges) { + ActivityRecord r = mActivities.get(token); + if (r != null) { + //Log.v(TAG, "userLeaving=" + userLeaving + " handling pause of " + r); + if (userLeaving) { + performUserLeavingActivity(r); + } + + r.activity.mConfigChangeFlags |= configChanges; + Bundle state = performPauseActivity(token, finished, true); + + // Tell the activity manager we have paused. + try { + ActivityManagerNative.getDefault().activityPaused(token, state); + } catch (RemoteException ex) { + } + } + } + + final void performUserLeavingActivity(ActivityRecord r) { + mInstrumentation.callActivityOnUserLeaving(r.activity); + } + + final Bundle performPauseActivity(IBinder token, boolean finished, + boolean saveState) { + ActivityRecord r = mActivities.get(token); + return r != null ? performPauseActivity(r, finished, saveState) : null; + } + + final Bundle performPauseActivity(ActivityRecord r, boolean finished, + boolean saveState) { + if (r.paused) { + if (r.activity.mFinished) { + // If we are finishing, we won't call onResume() in certain cases. + // So here we likewise don't want to call onPause() if the activity + // isn't resumed. + return null; + } + RuntimeException e = new RuntimeException( + "Performing pause of activity that is not resumed: " + + r.intent.getComponent().toShortString()); + Log.e(TAG, e.getMessage(), e); + } + Bundle state = null; + if (finished) { + r.activity.mFinished = true; + } + try { + // Next have the activity save its current state and managed dialogs... + if (!r.activity.mFinished && saveState) { + state = new Bundle(); + mInstrumentation.callActivityOnSaveInstanceState(r.activity, state); + r.state = state; + } + // Now we are idle. + r.activity.mCalled = false; + mInstrumentation.callActivityOnPause(r.activity); + EventLog.writeEvent(LOG_ON_PAUSE_CALLED, r.activity.getComponentName().getClassName()); + if (!r.activity.mCalled) { + throw new SuperNotCalledException( + "Activity " + r.intent.getComponent().toShortString() + + " did not call through to super.onPause()"); + } + + } catch (SuperNotCalledException e) { + throw e; + + } catch (Exception e) { + if (!mInstrumentation.onException(r.activity, e)) { + throw new RuntimeException( + "Unable to pause activity " + + r.intent.getComponent().toShortString() + + ": " + e.toString(), e); + } + } + r.paused = true; + return state; + } + + final void performStopActivity(IBinder token) { + ActivityRecord r = mActivities.get(token); + performStopActivityInner(r, null, false); + } + + private static class StopInfo { + Bitmap thumbnail; + CharSequence description; + } + + private final class ProviderRefCount { + public int count; + ProviderRefCount(int pCount) { + count = pCount; + } + } + + private final void performStopActivityInner(ActivityRecord r, + StopInfo info, boolean keepShown) { + if (localLOGV) Log.v(TAG, "Performing stop of " + r); + if (r != null) { + if (!keepShown && r.stopped) { + if (r.activity.mFinished) { + // If we are finishing, we won't call onResume() in certain + // cases. So here we likewise don't want to call onStop() + // if the activity isn't resumed. + return; + } + RuntimeException e = new RuntimeException( + "Performing stop of activity that is not resumed: " + + r.intent.getComponent().toShortString()); + Log.e(TAG, e.getMessage(), e); + } + + if (info != null) { + try { + // First create a thumbnail for the activity... + //info.thumbnail = createThumbnailBitmap(r); + info.description = r.activity.onCreateDescription(); + } catch (Exception e) { + if (!mInstrumentation.onException(r.activity, e)) { + throw new RuntimeException( + "Unable to save state of activity " + + r.intent.getComponent().toShortString() + + ": " + e.toString(), e); + } + } + } + + if (!keepShown) { + try { + // Now we are idle. + r.activity.performStop(); + } catch (Exception e) { + if (!mInstrumentation.onException(r.activity, e)) { + throw new RuntimeException( + "Unable to stop activity " + + r.intent.getComponent().toShortString() + + ": " + e.toString(), e); + } + } + r.stopped = true; + } + + r.paused = true; + } + } + + private final void updateVisibility(ActivityRecord r, boolean show) { + View v = r.activity.mDecor; + if (v != null) { + if (show) { + if (!r.activity.mVisibleFromServer) { + r.activity.mVisibleFromServer = true; + mNumVisibleActivities++; + if (r.activity.mVisibleFromClient) { + r.activity.makeVisible(); + } + } + if (r.newConfig != null) { + performConfigurationChanged(r.activity, r.newConfig); + r.newConfig = null; + } + } else { + if (r.activity.mVisibleFromServer) { + r.activity.mVisibleFromServer = false; + mNumVisibleActivities--; + v.setVisibility(View.INVISIBLE); + } + } + } + } + + private final void handleStopActivity(IBinder token, boolean show, int configChanges) { + ActivityRecord r = mActivities.get(token); + r.activity.mConfigChangeFlags |= configChanges; + + StopInfo info = new StopInfo(); + performStopActivityInner(r, info, show); + + if (localLOGV) Log.v( + TAG, "Finishing stop of " + r + ": show=" + show + + " win=" + r.window); + + updateVisibility(r, show); + + // Tell activity manager we have been stopped. + try { + ActivityManagerNative.getDefault().activityStopped( + r.token, info.thumbnail, info.description); + } catch (RemoteException ex) { + } + } + + final void performRestartActivity(IBinder token) { + ActivityRecord r = mActivities.get(token); + if (r.stopped) { + r.activity.performRestart(); + r.stopped = false; + } + } + + private final void handleWindowVisibility(IBinder token, boolean show) { + ActivityRecord r = mActivities.get(token); + if (!show && !r.stopped) { + performStopActivityInner(r, null, show); + } else if (show && r.stopped) { + // If we are getting ready to gc after going to the background, well + // we are back active so skip it. + unscheduleGcIdler(); + + r.activity.performRestart(); + r.stopped = false; + } + if (r.activity.mDecor != null) { + if (Config.LOGV) Log.v( + TAG, "Handle window " + r + " visibility: " + show); + updateVisibility(r, show); + } + } + + private final void deliverResults(ActivityRecord r, List<ResultInfo> results) { + final int N = results.size(); + for (int i=0; i<N; i++) { + ResultInfo ri = results.get(i); + try { + if (ri.mData != null) { + ri.mData.setExtrasClassLoader(r.activity.getClassLoader()); + } + r.activity.dispatchActivityResult(ri.mResultWho, + ri.mRequestCode, ri.mResultCode, ri.mData); + } catch (Exception e) { + if (!mInstrumentation.onException(r.activity, e)) { + throw new RuntimeException( + "Failure delivering result " + ri + " to activity " + + r.intent.getComponent().toShortString() + + ": " + e.toString(), e); + } + } + } + } + + private final void handleSendResult(ResultData res) { + ActivityRecord r = mActivities.get(res.token); + if (localLOGV) Log.v(TAG, "Handling send result to " + r); + if (r != null) { + final boolean resumed = !r.paused; + if (!r.activity.mFinished && r.activity.mDecor != null + && r.hideForNow && resumed) { + // We had hidden the activity because it started another + // one... we have gotten a result back and we are not + // paused, so make sure our window is visible. + updateVisibility(r, true); + } + if (resumed) { + try { + // Now we are idle. + r.activity.mCalled = false; + mInstrumentation.callActivityOnPause(r.activity); + if (!r.activity.mCalled) { + throw new SuperNotCalledException( + "Activity " + r.intent.getComponent().toShortString() + + " did not call through to super.onPause()"); + } + } catch (SuperNotCalledException e) { + throw e; + } catch (Exception e) { + if (!mInstrumentation.onException(r.activity, e)) { + throw new RuntimeException( + "Unable to pause activity " + + r.intent.getComponent().toShortString() + + ": " + e.toString(), e); + } + } + } + deliverResults(r, res.results); + if (resumed) { + mInstrumentation.callActivityOnResume(r.activity); + } + } + } + + public final ActivityRecord performDestroyActivity(IBinder token, boolean finishing) { + return performDestroyActivity(token, finishing, 0, false); + } + + private final ActivityRecord performDestroyActivity(IBinder token, boolean finishing, + int configChanges, boolean getNonConfigInstance) { + ActivityRecord r = mActivities.get(token); + if (localLOGV) Log.v(TAG, "Performing finish of " + r); + if (r != null) { + r.activity.mConfigChangeFlags |= configChanges; + if (finishing) { + r.activity.mFinished = true; + } + if (!r.paused) { + try { + r.activity.mCalled = false; + mInstrumentation.callActivityOnPause(r.activity); + EventLog.writeEvent(LOG_ON_PAUSE_CALLED, + r.activity.getComponentName().getClassName()); + if (!r.activity.mCalled) { + throw new SuperNotCalledException( + "Activity " + r.intent.getComponent().toShortString() + + " did not call through to super.onPause()"); + } + } catch (SuperNotCalledException e) { + throw e; + } catch (Exception e) { + if (!mInstrumentation.onException(r.activity, e)) { + throw new RuntimeException( + "Unable to pause activity " + + r.intent.getComponent().toShortString() + + ": " + e.toString(), e); + } + } + r.paused = true; + } + if (!r.stopped) { + try { + r.activity.performStop(); + } catch (SuperNotCalledException e) { + throw e; + } catch (Exception e) { + if (!mInstrumentation.onException(r.activity, e)) { + throw new RuntimeException( + "Unable to stop activity " + + r.intent.getComponent().toShortString() + + ": " + e.toString(), e); + } + } + r.stopped = true; + } + if (getNonConfigInstance) { + try { + r.lastNonConfigurationInstance + = r.activity.onRetainNonConfigurationInstance(); + } catch (Exception e) { + if (!mInstrumentation.onException(r.activity, e)) { + throw new RuntimeException( + "Unable to retain activity " + + r.intent.getComponent().toShortString() + + ": " + e.toString(), e); + } + } + try { + r.lastNonConfigurationChildInstances + = r.activity.onRetainNonConfigurationChildInstances(); + } catch (Exception e) { + if (!mInstrumentation.onException(r.activity, e)) { + throw new RuntimeException( + "Unable to retain child activities " + + r.intent.getComponent().toShortString() + + ": " + e.toString(), e); + } + } + + } + try { + r.activity.mCalled = false; + r.activity.onDestroy(); + if (!r.activity.mCalled) { + throw new SuperNotCalledException( + "Activity " + r.intent.getComponent().toShortString() + + " did not call through to super.onDestroy()"); + } + if (r.window != null) { + r.window.closeAllPanels(); + } + } catch (SuperNotCalledException e) { + throw e; + } catch (Exception e) { + if (!mInstrumentation.onException(r.activity, e)) { + throw new RuntimeException( + "Unable to destroy activity " + + r.intent.getComponent().toShortString() + + ": " + e.toString(), e); + } + } + } + mActivities.remove(token); + + return r; + } + + private final void handleDestroyActivity(IBinder token, boolean finishing, + int configChanges, boolean getNonConfigInstance) { + ActivityRecord r = performDestroyActivity(token, finishing, + configChanges, getNonConfigInstance); + if (r != null) { + WindowManager wm = r.activity.getWindowManager(); + View v = r.activity.mDecor; + if (v != null) { + if (r.activity.mVisibleFromServer) { + mNumVisibleActivities--; + } + IBinder wtoken = v.getWindowToken(); + if (r.activity.mWindowAdded) { + wm.removeViewImmediate(v); + } + if (wtoken != null) { + WindowManagerImpl.getDefault().closeAll(wtoken, + r.activity.getClass().getName(), "Activity"); + } + r.activity.mDecor = null; + } + WindowManagerImpl.getDefault().closeAll(token, + r.activity.getClass().getName(), "Activity"); + + // Mocked out contexts won't be participating in the normal + // process lifecycle, but if we're running with a proper + // ApplicationContext we need to have it tear down things + // cleanly. + Context c = r.activity.getBaseContext(); + if (c instanceof ApplicationContext) { + ((ApplicationContext) c).scheduleFinalCleanup( + r.activity.getClass().getName(), "Activity"); + } + } + if (finishing) { + try { + ActivityManagerNative.getDefault().activityDestroyed(token); + } catch (RemoteException ex) { + // If the system process has died, it's game over for everyone. + } + } + } + + private final void handleRelaunchActivity(ActivityRecord tmp, int configChanges) { + // If we are getting ready to gc after going to the background, well + // we are back active so skip it. + unscheduleGcIdler(); + + Configuration changedConfig = null; + + // First: make sure we have the most recent configuration and most + // recent version of the activity, or skip it if some previous call + // had taken a more recent version. + synchronized (mRelaunchingActivities) { + int N = mRelaunchingActivities.size(); + IBinder token = tmp.token; + tmp = null; + for (int i=0; i<N; i++) { + ActivityRecord r = mRelaunchingActivities.get(i); + if (r.token == token) { + tmp = r; + mRelaunchingActivities.remove(i); + i--; + N--; + } + } + + if (tmp == null) { + return; + } + + if (mPendingConfiguration != null) { + changedConfig = mPendingConfiguration; + mPendingConfiguration = null; + } + } + + // If there was a pending configuration change, execute it first. + if (changedConfig != null) { + handleConfigurationChanged(changedConfig); + } + + ActivityRecord r = mActivities.get(tmp.token); + if (localLOGV) Log.v(TAG, "Handling relaunch of " + r); + if (r == null) { + return; + } + + r.activity.mConfigChangeFlags |= configChanges; + + Bundle savedState = null; + if (!r.paused) { + savedState = performPauseActivity(r.token, false, true); + } + + handleDestroyActivity(r.token, false, configChanges, true); + + r.activity = null; + r.window = null; + r.hideForNow = false; + r.nextIdle = null; + r.pendingResults = tmp.pendingResults; + r.pendingIntents = tmp.pendingIntents; + r.startsNotResumed = tmp.startsNotResumed; + if (savedState != null) { + r.state = savedState; + } + + handleLaunchActivity(r); + } + + private final void handleRequestThumbnail(IBinder token) { + ActivityRecord r = mActivities.get(token); + Bitmap thumbnail = createThumbnailBitmap(r); + CharSequence description = null; + try { + description = r.activity.onCreateDescription(); + } catch (Exception e) { + if (!mInstrumentation.onException(r.activity, e)) { + throw new RuntimeException( + "Unable to create description of activity " + + r.intent.getComponent().toShortString() + + ": " + e.toString(), e); + } + } + //System.out.println("Reporting top thumbnail " + thumbnail); + try { + ActivityManagerNative.getDefault().reportThumbnail( + token, thumbnail, description); + } catch (RemoteException ex) { + } + } + + ArrayList<ComponentCallbacks> collectComponentCallbacksLocked( + boolean allActivities, Configuration newConfig) { + ArrayList<ComponentCallbacks> callbacks + = new ArrayList<ComponentCallbacks>(); + + if (mActivities.size() > 0) { + Iterator<ActivityRecord> it = mActivities.values().iterator(); + while (it.hasNext()) { + ActivityRecord ar = it.next(); + Activity a = ar.activity; + if (a != null) { + if (!ar.activity.mFinished && (allActivities || + (a != null && !ar.paused))) { + // If the activity is currently resumed, its configuration + // needs to change right now. + callbacks.add(a); + } else if (newConfig != null) { + // Otherwise, we will tell it about the change + // the next time it is resumed or shown. Note that + // the activity manager may, before then, decide the + // activity needs to be destroyed to handle its new + // configuration. + ar.newConfig = newConfig; + } + } + } + } + if (mServices.size() > 0) { + Iterator<Service> it = mServices.values().iterator(); + while (it.hasNext()) { + callbacks.add(it.next()); + } + } + synchronized (mProviderMap) { + if (mLocalProviders.size() > 0) { + Iterator<ProviderRecord> it = mLocalProviders.values().iterator(); + while (it.hasNext()) { + callbacks.add(it.next().mLocalProvider); + } + } + } + final int N = mAllApplications.size(); + for (int i=0; i<N; i++) { + callbacks.add(mAllApplications.get(i)); + } + + return callbacks; + } + + private final void performConfigurationChanged( + ComponentCallbacks cb, Configuration config) { + // Only for Activity objects, check that they actually call up to their + // superclass implementation. ComponentCallbacks is an interface, so + // we check the runtime type and act accordingly. + Activity activity = (cb instanceof Activity) ? (Activity) cb : null; + if (activity != null) { + activity.mCalled = false; + } + + boolean shouldChangeConfig = false; + if ((activity == null) || (activity.mCurrentConfig == null)) { + shouldChangeConfig = true; + } else { + + // If the new config is the same as the config this Activity + // is already running with then don't bother calling + // onConfigurationChanged + int diff = activity.mCurrentConfig.diff(config); + if (diff != 0) { + + // If this activity doesn't handle any of the config changes + // then don't bother calling onConfigurationChanged as we're + // going to destroy it. + if ((~activity.mActivityInfo.configChanges & diff) == 0) { + shouldChangeConfig = true; + } + } + } + + if (shouldChangeConfig) { + cb.onConfigurationChanged(config); + + if (activity != null) { + if (!activity.mCalled) { + throw new SuperNotCalledException( + "Activity " + activity.getLocalClassName() + + " did not call through to super.onConfigurationChanged()"); + } + activity.mConfigChangeFlags = 0; + activity.mCurrentConfig = new Configuration(config); + } + } + } + + final void handleConfigurationChanged(Configuration config) { + + synchronized (mRelaunchingActivities) { + if (mPendingConfiguration != null) { + config = mPendingConfiguration; + mPendingConfiguration = null; + } + } + + ArrayList<ComponentCallbacks> callbacks + = new ArrayList<ComponentCallbacks>(); + + synchronized(mPackages) { + if (mConfiguration == null) { + mConfiguration = new Configuration(); + } + mConfiguration.updateFrom(config); + DisplayMetrics dm = getDisplayMetricsLocked(true); + + // set it for java, this also affects newly created Resources + if (config.locale != null) { + Locale.setDefault(config.locale); + } + + Resources.updateSystemConfiguration(config, null); + + ApplicationContext.ApplicationPackageManager.configurationChanged(); + //Log.i(TAG, "Configuration changed in " + currentPackageName()); + { + Iterator<WeakReference<Resources>> it = + mActiveResources.values().iterator(); + //Iterator<Map.Entry<String, WeakReference<Resources>>> it = + // mActiveResources.entrySet().iterator(); + while (it.hasNext()) { + WeakReference<Resources> v = it.next(); + Resources r = v.get(); + if (r != null) { + r.updateConfiguration(config, dm); + //Log.i(TAG, "Updated app resources " + v.getKey() + // + " " + r + ": " + r.getConfiguration()); + } else { + //Log.i(TAG, "Removing old resources " + v.getKey()); + it.remove(); + } + } + } + + callbacks = collectComponentCallbacksLocked(false, config); + } + + final int N = callbacks.size(); + for (int i=0; i<N; i++) { + performConfigurationChanged(callbacks.get(i), config); + } + } + + final void handleActivityConfigurationChanged(IBinder token) { + ActivityRecord r = mActivities.get(token); + if (r == null || r.activity == null) { + return; + } + + performConfigurationChanged(r.activity, mConfiguration); + } + + final void handleLowMemory() { + ArrayList<ComponentCallbacks> callbacks + = new ArrayList<ComponentCallbacks>(); + + synchronized(mPackages) { + callbacks = collectComponentCallbacksLocked(true, null); + } + + final int N = callbacks.size(); + for (int i=0; i<N; i++) { + callbacks.get(i).onLowMemory(); + } + + // Ask SQLite to free up as much memory as it can, mostly from it's page caches + int sqliteReleased = SQLiteDatabase.releaseMemory(); + EventLog.writeEvent(SQLITE_MEM_RELEASED_EVENT_LOG_TAG, sqliteReleased); + + BinderInternal.forceGc("mem"); + } + + private final void handleBindApplication(AppBindData data) { + mBoundApplication = data; + mConfiguration = new Configuration(data.config); + + // We now rely on this being set by zygote. + //Process.setGid(data.appInfo.gid); + //Process.setUid(data.appInfo.uid); + + // send up app name; do this *before* waiting for debugger + android.ddm.DdmHandleAppName.setAppName(data.processName); + + /* + * Before spawning a new process, reset the time zone to be the system time zone. + * This needs to be done because the system time zone could have changed after the + * the spawning of this process. Without doing this this process would have the incorrect + * system time zone. + */ + TimeZone.setDefault(null); + + /* + * Initialize the default locale in this process for the reasons we set the time zone. + */ + Locale.setDefault(data.config.locale); + + data.info = getPackageInfoNoCheck(data.appInfo); + + if (data.debugMode != IApplicationThread.DEBUG_OFF) { + // XXX should have option to change the port. + Debug.changeDebugPort(8100); + if (data.debugMode == IApplicationThread.DEBUG_WAIT) { + Log.w(TAG, "Application " + data.info.getPackageName() + + " is waiting for the debugger on port 8100..."); + + IActivityManager mgr = ActivityManagerNative.getDefault(); + try { + mgr.showWaitingForDebugger(mAppThread, true); + } catch (RemoteException ex) { + } + + Debug.waitForDebugger(); + + try { + mgr.showWaitingForDebugger(mAppThread, false); + } catch (RemoteException ex) { + } + + } else { + Log.w(TAG, "Application " + data.info.getPackageName() + + " can be debugged on port 8100..."); + } + } + + if (data.instrumentationName != null) { + ApplicationContext appContext = new ApplicationContext(); + appContext.init(data.info, null, this); + InstrumentationInfo ii = null; + try { + ii = appContext.getPackageManager(). + getInstrumentationInfo(data.instrumentationName, 0); + } catch (PackageManager.NameNotFoundException e) { + } + if (ii == null) { + throw new RuntimeException( + "Unable to find instrumentation info for: " + + data.instrumentationName); + } + + mInstrumentationAppDir = ii.sourceDir; + mInstrumentationAppPackage = ii.packageName; + mInstrumentedAppDir = data.info.getAppDir(); + + ApplicationInfo instrApp = new ApplicationInfo(); + instrApp.packageName = ii.packageName; + instrApp.sourceDir = ii.sourceDir; + instrApp.publicSourceDir = ii.publicSourceDir; + instrApp.dataDir = ii.dataDir; + PackageInfo pi = getPackageInfo(instrApp, + appContext.getClassLoader(), false, true); + ApplicationContext instrContext = new ApplicationContext(); + instrContext.init(pi, null, this); + + try { + java.lang.ClassLoader cl = instrContext.getClassLoader(); + mInstrumentation = (Instrumentation) + cl.loadClass(data.instrumentationName.getClassName()).newInstance(); + } catch (Exception e) { + throw new RuntimeException( + "Unable to instantiate instrumentation " + + data.instrumentationName + ": " + e.toString(), e); + } + + mInstrumentation.init(this, instrContext, appContext, + new ComponentName(ii.packageName, ii.name), data.instrumentationWatcher); + + if (data.profileFile != null && !ii.handleProfiling) { + data.handlingProfiling = true; + File file = new File(data.profileFile); + file.getParentFile().mkdirs(); + Debug.startMethodTracing(file.toString(), 8 * 1024 * 1024); + } + + try { + mInstrumentation.onCreate(data.instrumentationArgs); + } + catch (Exception e) { + throw new RuntimeException( + "Exception thrown in onCreate() of " + + data.instrumentationName + ": " + e.toString(), e); + } + + } else { + mInstrumentation = new Instrumentation(); + } + + Application app = data.info.makeApplication(); + mInitialApplication = app; + + List<ProviderInfo> providers = data.providers; + if (providers != null) { + installContentProviders(app, providers); + } + + try { + mInstrumentation.callApplicationOnCreate(app); + } catch (Exception e) { + if (!mInstrumentation.onException(app, e)) { + throw new RuntimeException( + "Unable to create application " + app.getClass().getName() + + ": " + e.toString(), e); + } + } + } + + /*package*/ final void finishInstrumentation(int resultCode, Bundle results) { + IActivityManager am = ActivityManagerNative.getDefault(); + if (mBoundApplication.profileFile != null && mBoundApplication.handlingProfiling) { + Debug.stopMethodTracing(); + } + //Log.i(TAG, "am: " + ActivityManagerNative.getDefault() + // + ", app thr: " + mAppThread); + try { + am.finishInstrumentation(mAppThread, resultCode, results); + } catch (RemoteException ex) { + } + } + + private final void installContentProviders( + Context context, List<ProviderInfo> providers) { + final ArrayList<IActivityManager.ContentProviderHolder> results = + new ArrayList<IActivityManager.ContentProviderHolder>(); + + Iterator<ProviderInfo> i = providers.iterator(); + while (i.hasNext()) { + ProviderInfo cpi = i.next(); + StringBuilder buf = new StringBuilder(128); + buf.append("Publishing provider "); + buf.append(cpi.authority); + buf.append(": "); + buf.append(cpi.name); + Log.i(TAG, buf.toString()); + IContentProvider cp = installProvider(context, null, cpi, false); + if (cp != null) { + IActivityManager.ContentProviderHolder cph = + new IActivityManager.ContentProviderHolder(cpi); + cph.provider = cp; + results.add(cph); + // Don't ever unload this provider from the process. + synchronized(mProviderMap) { + mProviderRefCountMap.put(cp.asBinder(), new ProviderRefCount(10000)); + } + } + } + + try { + ActivityManagerNative.getDefault().publishContentProviders( + getApplicationThread(), results); + } catch (RemoteException ex) { + } + } + + private final IContentProvider getProvider(Context context, String name) { + synchronized(mProviderMap) { + final ProviderRecord pr = mProviderMap.get(name); + if (pr != null) { + return pr.mProvider; + } + } + + IActivityManager.ContentProviderHolder holder = null; + try { + holder = ActivityManagerNative.getDefault().getContentProvider( + getApplicationThread(), name); + } catch (RemoteException ex) { + } + if (holder == null) { + Log.e(TAG, "Failed to find provider info for " + name); + return null; + } + if (holder.permissionFailure != null) { + throw new SecurityException("Permission " + holder.permissionFailure + + " required for provider " + name); + } + + IContentProvider prov = installProvider(context, holder.provider, + holder.info, true); + //Log.i(TAG, "noReleaseNeeded=" + holder.noReleaseNeeded); + if (holder.noReleaseNeeded || holder.provider == null) { + // We are not going to release the provider if it is an external + // provider that doesn't care about being released, or if it is + // a local provider running in this process. + //Log.i(TAG, "*** NO RELEASE NEEDED"); + synchronized(mProviderMap) { + mProviderRefCountMap.put(prov.asBinder(), new ProviderRefCount(10000)); + } + } + return prov; + } + + public final IContentProvider acquireProvider(Context c, String name) { + IContentProvider provider = getProvider(c, name); + if(provider == null) + return null; + IBinder jBinder = provider.asBinder(); + synchronized(mProviderMap) { + ProviderRefCount prc = mProviderRefCountMap.get(jBinder); + if(prc == null) { + mProviderRefCountMap.put(jBinder, new ProviderRefCount(1)); + } else { + prc.count++; + } //end else + } //end synchronized + return provider; + } + + public final boolean releaseProvider(IContentProvider provider) { + if(provider == null) { + return false; + } + IBinder jBinder = provider.asBinder(); + synchronized(mProviderMap) { + ProviderRefCount prc = mProviderRefCountMap.get(jBinder); + if(prc == null) { + if(localLOGV) Log.v(TAG, "releaseProvider::Weird shouldnt be here"); + return false; + } else { + prc.count--; + if(prc.count == 0) { + mProviderRefCountMap.remove(jBinder); + //invoke removeProvider to dereference provider + removeProviderLocked(provider); + } //end if + } //end else + } //end synchronized + return true; + } + + public final void removeProviderLocked(IContentProvider provider) { + if (provider == null) { + return; + } + IBinder providerBinder = provider.asBinder(); + boolean amRemoveFlag = false; + + // remove the provider from mProviderMap + Iterator<ProviderRecord> iter = mProviderMap.values().iterator(); + while (iter.hasNext()) { + ProviderRecord pr = iter.next(); + IBinder myBinder = pr.mProvider.asBinder(); + if (myBinder == providerBinder) { + //find if its published by this process itself + if(pr.mLocalProvider != null) { + if(localLOGV) Log.i(TAG, "removeProvider::found local provider returning"); + return; + } + if(localLOGV) Log.v(TAG, "removeProvider::Not local provider Unlinking " + + "death recipient"); + //content provider is in another process + myBinder.unlinkToDeath(pr, 0); + iter.remove(); + //invoke remove only once for the very first name seen + if(!amRemoveFlag) { + try { + if(localLOGV) Log.v(TAG, "removeProvider::Invoking " + + "ActivityManagerNative.removeContentProvider("+pr.mName); + ActivityManagerNative.getDefault().removeContentProvider(getApplicationThread(), pr.mName); + amRemoveFlag = true; + } catch (RemoteException e) { + //do nothing content provider object is dead any way + } //end catch + } + } //end if myBinder + } //end while iter + } + + final void removeDeadProvider(String name, IContentProvider provider) { + synchronized(mProviderMap) { + ProviderRecord pr = mProviderMap.get(name); + if (pr.mProvider.asBinder() == provider.asBinder()) { + Log.i(TAG, "Removing dead content provider: " + name); + mProviderMap.remove(name); + } + } + } + + final void removeDeadProviderLocked(String name, IContentProvider provider) { + ProviderRecord pr = mProviderMap.get(name); + if (pr.mProvider.asBinder() == provider.asBinder()) { + Log.i(TAG, "Removing dead content provider: " + name); + mProviderMap.remove(name); + } + } + + private final IContentProvider installProvider(Context context, + IContentProvider provider, ProviderInfo info, boolean noisy) { + ContentProvider localProvider = null; + if (provider == null) { + if (noisy) { + Log.d(TAG, "Loading provider " + info.authority + ": " + + info.name); + } + Context c = null; + ApplicationInfo ai = info.applicationInfo; + if (context.getPackageName().equals(ai.packageName)) { + c = context; + } else if (mInitialApplication != null && + mInitialApplication.getPackageName().equals(ai.packageName)) { + c = mInitialApplication; + } else { + try { + c = context.createPackageContext(ai.packageName, + Context.CONTEXT_INCLUDE_CODE); + } catch (PackageManager.NameNotFoundException e) { + } + } + if (c == null) { + Log.w(TAG, "Unable to get context for package " + + ai.packageName + + " while loading content provider " + + info.name); + return null; + } + try { + final java.lang.ClassLoader cl = c.getClassLoader(); + localProvider = (ContentProvider)cl. + loadClass(info.name).newInstance(); + provider = localProvider.getIContentProvider(); + if (provider == null) { + Log.e(TAG, "Failed to instantiate class " + + info.name + " from sourceDir " + + info.applicationInfo.sourceDir); + return null; + } + if (Config.LOGV) Log.v( + TAG, "Instantiating local provider " + info.name); + // XXX Need to create the correct context for this provider. + localProvider.attachInfo(c, info); + } catch (java.lang.Exception e) { + if (!mInstrumentation.onException(null, e)) { + throw new RuntimeException( + "Unable to get provider " + info.name + + ": " + e.toString(), e); + } + return null; + } + } else if (localLOGV) { + Log.v(TAG, "Installing external provider " + info.authority + ": " + + info.name); + } + + synchronized (mProviderMap) { + // Cache the pointer for the remote provider. + String names[] = PATTERN_SEMICOLON.split(info.authority); + for (int i=0; i<names.length; i++) { + ProviderRecord pr = new ProviderRecord(names[i], provider, + localProvider); + try { + provider.asBinder().linkToDeath(pr, 0); + mProviderMap.put(names[i], pr); + } catch (RemoteException e) { + return null; + } + } + if (localProvider != null) { + mLocalProviders.put(provider.asBinder(), + new ProviderRecord(null, provider, localProvider)); + } + } + + return provider; + } + + private final void attach(boolean system) { + sThreadLocal.set(this); + mSystemThread = system; + AndroidHttpClient.setThreadBlocked(true); + if (!system) { + android.ddm.DdmHandleAppName.setAppName("<pre-initialized>"); + RuntimeInit.setApplicationObject(mAppThread.asBinder()); + IActivityManager mgr = ActivityManagerNative.getDefault(); + try { + mgr.attachApplication(mAppThread); + } catch (RemoteException ex) { + } + } else { + // Don't set application object here -- if the system crashes, + // we can't display an alert, we just want to die die die. + android.ddm.DdmHandleAppName.setAppName("system_process"); + try { + mInstrumentation = new Instrumentation(); + ApplicationContext context = new ApplicationContext(); + context.init(getSystemContext().mPackageInfo, null, this); + Application app = Instrumentation.newApplication(Application.class, context); + mAllApplications.add(app); + mInitialApplication = app; + app.onCreate(); + } catch (Exception e) { + throw new RuntimeException( + "Unable to instantiate Application():" + e.toString(), e); + } + } + } + + private final void detach() + { + AndroidHttpClient.setThreadBlocked(false); + sThreadLocal.set(null); + } + + public static final ActivityThread systemMain() { + ActivityThread thread = new ActivityThread(); + thread.attach(true); + return thread; + } + + public final void installSystemProviders(List providers) { + if (providers != null) { + installContentProviders(mInitialApplication, + (List<ProviderInfo>)providers); + } + } + + public static final void main(String[] args) { + Process.setArgV0("<pre-initialized>"); + + Looper.prepareMainLooper(); + + ActivityThread thread = new ActivityThread(); + thread.attach(false); + + Looper.loop(); + + if (Process.supportsProcesses()) { + throw new RuntimeException("Main thread loop unexpectedly exited"); + } + + thread.detach(); + String name; + if (thread.mInitialApplication != null) name = thread.mInitialApplication.getPackageName(); + else name = "<unknown>"; + Log.i(TAG, "Main thread of " + name + " is now exiting"); + } +} diff --git a/core/java/android/app/AlarmManager.java b/core/java/android/app/AlarmManager.java new file mode 100644 index 0000000..b4c0e31 --- /dev/null +++ b/core/java/android/app/AlarmManager.java @@ -0,0 +1,276 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * 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 android.app; + +import android.content.Context; +import android.content.Intent; +import android.os.RemoteException; +import android.os.ServiceManager; + +/** + * This class provides access to the system alarm services. These allow you + * to schedule your application to be run at some point in the future. When + * an alarm goes off, the {@link Intent} that had been registered for it + * is broadcast by the system, automatically starting the target application + * if it is not already running. Registered alarms are retained while the + * device is asleep (and can optionally wake the device up if they go off + * during that time), but will be cleared if it is turned off and rebooted. + * + * <p><b>Note: The Alarm Manager is intended for cases where you want to have + * your application code run at a specific time, even if your application is + * not currently running. For normal timing operations (ticks, timeouts, + * etc) it is easier and much more efficient to use + * {@link android.os.Handler}.</b> + * + * <p>You do not + * instantiate this class directly; instead, retrieve it through + * {@link android.content.Context#getSystemService + * Context.getSystemService(Context.ALARM_SERVICE)}. + */ +public class AlarmManager +{ + /** + * Alarm time in {@link System#currentTimeMillis System.currentTimeMillis()} + * (wall clock time in UTC), which will wake up the device when + * it goes off. + */ + public static final int RTC_WAKEUP = 0; + /** + * Alarm time in {@link System#currentTimeMillis System.currentTimeMillis()} + * (wall clock time in UTC). This alarm does not wake the + * device up; if it goes off while the device is asleep, it will not be + * delivered until the next time the device wakes up. + */ + public static final int RTC = 1; + /** + * Alarm time in {@link android.os.SystemClock#elapsedRealtime + * SystemClock.elapsedRealtime()} (time since boot, including sleep), + * which will wake up the device when it goes off. + */ + public static final int ELAPSED_REALTIME_WAKEUP = 2; + /** + * Alarm time in {@link android.os.SystemClock#elapsedRealtime + * SystemClock.elapsedRealtime()} (time since boot, including sleep). + * This alarm does not wake the device up; if it goes off while the device + * is asleep, it will not be delivered until the next time the device + * wakes up. + */ + public static final int ELAPSED_REALTIME = 3; + + private final IAlarmManager mService; + + /** + * package private on purpose + */ + AlarmManager(IAlarmManager service) { + mService = service; + } + + /** + * Schedule an alarm. <b>Note: for timing operations (ticks, timeouts, + * etc) it is easier and much more efficient to use + * {@link android.os.Handler}.</b> If there is already an alarm scheduled + * for the same IntentSender, it will first be canceled. + * + * <p>If the time occurs in the past, the alarm will be triggered + * immediately. If there is already an alarm for this Intent + * scheduled (with the equality of two intents being defined by + * {@link Intent#filterEquals}), then it will be removed and replaced by + * this one. + * + * <p> + * The alarm is an intent broadcast that goes to a broadcast receiver that + * you registered with {@link android.content.Context#registerReceiver} + * or through the <receiver> tag in an AndroidManifest.xml file. + * + * <p> + * Alarm intents are delivered with a data extra of type int called + * {@link Intent#EXTRA_ALARM_COUNT Intent.EXTRA_ALARM_COUNT} that indicates + * how many past alarm events have been accumulated into this intent + * broadcast. Recurring alarms that have gone undelivered because the + * phone was asleep may have a count greater than one when delivered. + * + * @param type One of ELAPSED_REALTIME, ELAPSED_REALTIME_WAKEUP, RTC or + * RTC_WAKEUP. + * @param triggerAtTime Time the alarm should go off, using the + * appropriate clock (depending on the alarm type). + * @param operation Action to perform when the alarm goes off; + * typically comes from {@link PendingIntent#getBroadcast + * IntentSender.getBroadcast()}. + * + * @see android.os.Handler + * @see #setRepeating + * @see #cancel + * @see android.content.Context#sendBroadcast + * @see android.content.Context#registerReceiver + * @see android.content.Intent#filterEquals + * @see #ELAPSED_REALTIME + * @see #ELAPSED_REALTIME_WAKEUP + * @see #RTC + * @see #RTC_WAKEUP + */ + public void set(int type, long triggerAtTime, PendingIntent operation) { + try { + mService.set(type, triggerAtTime, operation); + } catch (RemoteException ex) { + } + } + + /** + * Schedule a repeating alarm. <b>Note: for timing operations (ticks, + * timeouts, etc) it is easier and much more efficient to use + * {@link android.os.Handler}.</b> If there is already an alarm scheduled + * for the same IntentSender, it will first be canceled. + * + * <p>Like {@link #set}, except you can also + * supply a rate at which the alarm will repeat. This alarm continues + * repeating until explicitly removed with {@link #cancel}. If the time + * occurs in the past, the alarm will be triggered immediately, with an + * alarm count depending on how far in the past the trigger time is relative + * to the repeat interval. + * + * <p>If an alarm is delayed (by system sleep, for example, for non + * _WAKEUP alarm types), a skipped repeat will be delivered as soon as + * possible. After that, future alarms will be delivered according to the + * original schedule; they do not drift over time. For example, if you have + * set a recurring alarm for the top of every hour but the phone was asleep + * from 7:45 until 8:45, an alarm will be sent as soon as the phone awakens, + * then the next alarm will be sent at 9:00. + * + * <p>If your application wants to allow the delivery times to drift in + * order to guarantee that at least a certain time interval always elapses + * between alarms, then the approach to take is to use one-time alarms, + * scheduling the next one yourself when handling each alarm delivery. + * + * @param type One of ELAPSED_REALTIME, ELAPSED_REALTIME_WAKEUP}, RTC or + * RTC_WAKEUP. + * @param triggerAtTime Time the alarm should first go off, using the + * appropriate clock (depending on the alarm type). + * @param interval Interval between subsequent repeats of the alarm. + * @param operation Action to perform when the alarm goes off; + * typically comes from {@link PendingIntent#getBroadcast + * IntentSender.getBroadcast()}. + * + * @see android.os.Handler + * @see #set + * @see #cancel + * @see android.content.Context#sendBroadcast + * @see android.content.Context#registerReceiver + * @see android.content.Intent#filterEquals + * @see #ELAPSED_REALTIME + * @see #ELAPSED_REALTIME_WAKEUP + * @see #RTC + * @see #RTC_WAKEUP + */ + public void setRepeating(int type, long triggerAtTime, long interval, + PendingIntent operation) { + try { + mService.setRepeating(type, triggerAtTime, interval, operation); + } catch (RemoteException ex) { + } + } + + /** + * Available inexact recurrence intervals recognized by + * {@link #setInexactRepeating(int, long, long, PendingIntent)} + */ + public static final long INTERVAL_FIFTEEN_MINUTES = 15 * 60 * 1000; + public static final long INTERVAL_HALF_HOUR = 2*INTERVAL_FIFTEEN_MINUTES; + public static final long INTERVAL_HOUR = 2*INTERVAL_HALF_HOUR; + public static final long INTERVAL_HALF_DAY = 12*INTERVAL_HOUR; + public static final long INTERVAL_DAY = 2*INTERVAL_HALF_DAY; + + /** + * Schedule a repeating alarm that has inexact trigger time requirements; + * for example, an alarm that repeats every hour, but not necessarily at + * the top of every hour. These alarms are more power-efficient than + * the strict recurrences supplied by {@link #setRepeating}, since the + * system can adjust alarms' phase to cause them to fire simultaneously, + * avoiding waking the device from sleep more than necessary. + * + * <p>Your alarm's first trigger will not be before the requested time, + * but it might not occur for almost a full interval after that time. In + * addition, while the overall period of the repeating alarm will be as + * requested, the time between any two successive firings of the alarm + * may vary. If your application demands very low jitter, use + * {@link #setRepeating} instead. + * + * @param type One of ELAPSED_REALTIME, ELAPSED_REALTIME_WAKEUP}, RTC or + * RTC_WAKEUP. + * @param triggerAtTime Time the alarm should first go off, using the + * appropriate clock (depending on the alarm type). This + * is inexact: the alarm will not fire before this time, + * but there may be a delay of almost an entire alarm + * interval before the first invocation of the alarm. + * @param interval Interval between subsequent repeats of the alarm. If + * this is one of INTERVAL_FIFTEEN_MINUTES, INTERVAL_HALF_HOUR, + * INTERVAL_HOUR, INTERVAL_HALF_DAY, or INTERVAL_DAY then the + * alarm will be phase-aligned with other alarms to reduce + * the number of wakeups. Otherwise, the alarm will be set + * as though the application had called {@link #setRepeating}. + * @param operation Action to perform when the alarm goes off; + * typically comes from {@link PendingIntent#getBroadcast + * IntentSender.getBroadcast()}. + * + * @see android.os.Handler + * @see #set + * @see #cancel + * @see android.content.Context#sendBroadcast + * @see android.content.Context#registerReceiver + * @see android.content.Intent#filterEquals + * @see #ELAPSED_REALTIME + * @see #ELAPSED_REALTIME_WAKEUP + * @see #RTC + * @see #RTC_WAKEUP + * @see #INTERVAL_FIFTEEN_MINUTES + * @see #INTERVAL_HALF_HOUR + * @see #INTERVAL_HOUR + * @see #INTERVAL_HALF_DAY + * @see #INTERVAL_DAY + */ + public void setInexactRepeating(int type, long triggerAtTime, long interval, + PendingIntent operation) { + try { + mService.setInexactRepeating(type, triggerAtTime, interval, operation); + } catch (RemoteException ex) { + } + } + + /** + * Remove any alarms with a matching {@link Intent}. + * Any alarm, of any type, whose Intent matches this one (as defined by + * {@link Intent#filterEquals}), will be canceled. + * + * @param operation IntentSender which matches a previously added + * IntentSender. + * + * @see #set + */ + public void cancel(PendingIntent operation) { + try { + mService.remove(operation); + } catch (RemoteException ex) { + } + } + + public void setTimeZone(String timeZone) { + try { + mService.setTimeZone(timeZone); + } catch (RemoteException ex) { + } + } +} diff --git a/core/java/android/app/AlertDialog.java b/core/java/android/app/AlertDialog.java new file mode 100644 index 0000000..f2b89c3 --- /dev/null +++ b/core/java/android/app/AlertDialog.java @@ -0,0 +1,795 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * 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 android.app; + +import android.content.Context; +import android.content.DialogInterface; +import android.database.Cursor; +import android.graphics.drawable.Drawable; +import android.os.Bundle; +import android.os.Message; +import android.view.KeyEvent; +import android.view.View; +import android.view.WindowManager; +import android.widget.AdapterView; +import android.widget.Button; +import android.widget.ListAdapter; +import android.widget.ListView; + +import com.android.internal.app.AlertController; + +/** + * A subclass of Dialog that can display one, two or three buttons. If you only want to + * display a String in this dialog box, use the setMessage() method. If you + * want to display a more complex view, look up the FrameLayout called "body" + * and add your view to it: + * + * <pre> + * FrameLayout fl = (FrameLayout) findViewById(R.id.body); + * fl.add(myView, new LayoutParams(FILL_PARENT, WRAP_CONTENT)); + * </pre> + * + * <p>The AlertDialog class takes care of automatically setting + * {@link WindowManager.LayoutParams#FLAG_ALT_FOCUSABLE_IM + * WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM} for you based on whether + * any views in the dialog return true from {@link View#onCheckIsTextEditor() + * View.onCheckIsTextEditor()}. Generally you want this set for a Dialog + * without text editors, so that it will be placed on top of the current + * input method UI. You can modify this behavior by forcing the flag to your + * desired mode after calling {@link #onCreate}. + */ +public class AlertDialog extends Dialog implements DialogInterface { + private AlertController mAlert; + + protected AlertDialog(Context context) { + this(context, com.android.internal.R.style.Theme_Dialog_Alert); + } + + protected AlertDialog(Context context, int theme) { + super(context, theme); + mAlert = new AlertController(context, this, getWindow()); + } + + protected AlertDialog(Context context, boolean cancelable, OnCancelListener cancelListener) { + super(context, com.android.internal.R.style.Theme_Dialog_Alert); + setCancelable(cancelable); + setOnCancelListener(cancelListener); + mAlert = new AlertController(context, this, getWindow()); + } + + /** + * Gets one of the buttons used in the dialog. + * <p> + * If a button does not exist in the dialog, null will be returned. + * + * @param whichButton The identifier of the button that should be returned. + * For example, this can be + * {@link DialogInterface#BUTTON_POSITIVE}. + * @return The button from the dialog, or null if a button does not exist. + */ + public Button getButton(int whichButton) { + return mAlert.getButton(whichButton); + } + + /** + * Gets the list view used in the dialog. + * + * @return The {@link ListView} from the dialog. + */ + public ListView getListView() { + return mAlert.getListView(); + } + + @Override + public void setTitle(CharSequence title) { + super.setTitle(title); + mAlert.setTitle(title); + } + + /** + * @see Builder#setCustomTitle(View) + */ + public void setCustomTitle(View customTitleView) { + mAlert.setCustomTitle(customTitleView); + } + + public void setMessage(CharSequence message) { + mAlert.setMessage(message); + } + + /** + * Set the view to display in that dialog. + */ + public void setView(View view) { + mAlert.setView(view); + } + + /** + * Set the view to display in that dialog, specifying the spacing to appear around that + * view. + * + * @param view The view to show in the content area of the dialog + * @param viewSpacingLeft Extra space to appear to the left of {@code view} + * @param viewSpacingTop Extra space to appear above {@code view} + * @param viewSpacingRight Extra space to appear to the right of {@code view} + * @param viewSpacingBottom Extra space to appear below {@code view} + */ + public void setView(View view, int viewSpacingLeft, int viewSpacingTop, int viewSpacingRight, + int viewSpacingBottom) { + mAlert.setView(view, viewSpacingLeft, viewSpacingTop, viewSpacingRight, viewSpacingBottom); + } + + /** + * Set a message to be sent when a button is pressed. + * + * @param whichButton Which button to set the message for, can be one of + * {@link DialogInterface#BUTTON_POSITIVE}, + * {@link DialogInterface#BUTTON_NEGATIVE}, or + * {@link DialogInterface#BUTTON_NEUTRAL} + * @param text The text to display in positive button. + * @param msg The {@link Message} to be sent when clicked. + */ + public void setButton(int whichButton, CharSequence text, Message msg) { + mAlert.setButton(whichButton, text, null, msg); + } + + /** + * Set a listener to be invoked when the positive button of the dialog is pressed. + * + * @param whichButton Which button to set the listener on, can be one of + * {@link DialogInterface#BUTTON_POSITIVE}, + * {@link DialogInterface#BUTTON_NEGATIVE}, or + * {@link DialogInterface#BUTTON_NEUTRAL} + * @param text The text to display in positive button. + * @param listener The {@link DialogInterface.OnClickListener} to use. + */ + public void setButton(int whichButton, CharSequence text, OnClickListener listener) { + mAlert.setButton(whichButton, text, listener, null); + } + + /** + * @deprecated Use {@link #setButton(int, CharSequence, Message)} with + * {@link DialogInterface#BUTTON_POSITIVE}. + */ + @Deprecated + public void setButton(CharSequence text, Message msg) { + setButton(BUTTON_POSITIVE, text, msg); + } + + /** + * @deprecated Use {@link #setButton(int, CharSequence, Message)} with + * {@link DialogInterface#BUTTON_NEGATIVE}. + */ + @Deprecated + public void setButton2(CharSequence text, Message msg) { + setButton(BUTTON_NEGATIVE, text, msg); + } + + /** + * @deprecated Use {@link #setButton(int, CharSequence, Message)} with + * {@link DialogInterface#BUTTON_NEUTRAL}. + */ + @Deprecated + public void setButton3(CharSequence text, Message msg) { + setButton(BUTTON_NEUTRAL, text, msg); + } + + /** + * Set a listener to be invoked when button 1 of the dialog is pressed. + * + * @param text The text to display in button 1. + * @param listener The {@link DialogInterface.OnClickListener} to use. + * @deprecated Use + * {@link #setButton(int, CharSequence, android.content.DialogInterface.OnClickListener)} + * with {@link DialogInterface#BUTTON_POSITIVE} + */ + @Deprecated + public void setButton(CharSequence text, final OnClickListener listener) { + setButton(BUTTON_POSITIVE, text, listener); + } + + /** + * Set a listener to be invoked when button 2 of the dialog is pressed. + * @param text The text to display in button 2. + * @param listener The {@link DialogInterface.OnClickListener} to use. + * @deprecated Use + * {@link #setButton(int, CharSequence, android.content.DialogInterface.OnClickListener)} + * with {@link DialogInterface#BUTTON_NEGATIVE} + */ + @Deprecated + public void setButton2(CharSequence text, final OnClickListener listener) { + setButton(BUTTON_NEGATIVE, text, listener); + } + + /** + * Set a listener to be invoked when button 3 of the dialog is pressed. + * @param text The text to display in button 3. + * @param listener The {@link DialogInterface.OnClickListener} to use. + * @deprecated Use + * {@link #setButton(int, CharSequence, android.content.DialogInterface.OnClickListener)} + * with {@link DialogInterface#BUTTON_POSITIVE} + */ + @Deprecated + public void setButton3(CharSequence text, final OnClickListener listener) { + setButton(BUTTON_NEUTRAL, text, listener); + } + + /** + * Set resId to 0 if you don't want an icon. + * @param resId the resourceId of the drawable to use as the icon or 0 + * if you don't want an icon. + */ + public void setIcon(int resId) { + mAlert.setIcon(resId); + } + + public void setIcon(Drawable icon) { + mAlert.setIcon(icon); + } + + public void setInverseBackgroundForced(boolean forceInverseBackground) { + mAlert.setInverseBackgroundForced(forceInverseBackground); + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + mAlert.installContent(); + } + + @Override + public boolean onKeyDown(int keyCode, KeyEvent event) { + if (mAlert.onKeyDown(keyCode, event)) return true; + return super.onKeyDown(keyCode, event); + } + + @Override + public boolean onKeyUp(int keyCode, KeyEvent event) { + if (mAlert.onKeyUp(keyCode, event)) return true; + return super.onKeyUp(keyCode, event); + } + + public static class Builder { + private final AlertController.AlertParams P; + + /** + * Constructor using a context for this builder and the {@link AlertDialog} it creates. + */ + public Builder(Context context) { + P = new AlertController.AlertParams(context); + } + + /** + * Set the title using the given resource id. + * + * @return This Builder object to allow for chaining of calls to set methods + */ + public Builder setTitle(int titleId) { + P.mTitle = P.mContext.getText(titleId); + return this; + } + + /** + * Set the title displayed in the {@link Dialog}. + * + * @return This Builder object to allow for chaining of calls to set methods + */ + public Builder setTitle(CharSequence title) { + P.mTitle = title; + return this; + } + + /** + * Set the title using the custom view {@code customTitleView}. The + * methods {@link #setTitle(int)} and {@link #setIcon(int)} should be + * sufficient for most titles, but this is provided if the title needs + * more customization. Using this will replace the title and icon set + * via the other methods. + * + * @param customTitleView The custom view to use as the title. + * + * @return This Builder object to allow for chaining of calls to set methods + */ + public Builder setCustomTitle(View customTitleView) { + P.mCustomTitleView = customTitleView; + return this; + } + + /** + * Set the message to display using the given resource id. + * + * @return This Builder object to allow for chaining of calls to set methods + */ + public Builder setMessage(int messageId) { + P.mMessage = P.mContext.getText(messageId); + return this; + } + + /** + * Set the message to display. + * + * @return This Builder object to allow for chaining of calls to set methods + */ + public Builder setMessage(CharSequence message) { + P.mMessage = message; + return this; + } + + /** + * Set the resource id of the {@link Drawable} to be used in the title. + * + * @return This Builder object to allow for chaining of calls to set methods + */ + public Builder setIcon(int iconId) { + P.mIconId = iconId; + return this; + } + + /** + * Set the {@link Drawable} to be used in the title. + * + * @return This Builder object to allow for chaining of calls to set methods + */ + public Builder setIcon(Drawable icon) { + P.mIcon = icon; + return this; + } + + /** + * Set a listener to be invoked when the positive button of the dialog is pressed. + * @param textId The resource id of the text to display in the positive button + * @param listener The {@link DialogInterface.OnClickListener} to use. + * + * @return This Builder object to allow for chaining of calls to set methods + */ + public Builder setPositiveButton(int textId, final OnClickListener listener) { + P.mPositiveButtonText = P.mContext.getText(textId); + P.mPositiveButtonListener = listener; + return this; + } + + /** + * Set a listener to be invoked when the positive button of the dialog is pressed. + * @param text The text to display in the positive button + * @param listener The {@link DialogInterface.OnClickListener} to use. + * + * @return This Builder object to allow for chaining of calls to set methods + */ + public Builder setPositiveButton(CharSequence text, final OnClickListener listener) { + P.mPositiveButtonText = text; + P.mPositiveButtonListener = listener; + return this; + } + + /** + * Set a listener to be invoked when the negative button of the dialog is pressed. + * @param textId The resource id of the text to display in the negative button + * @param listener The {@link DialogInterface.OnClickListener} to use. + * + * @return This Builder object to allow for chaining of calls to set methods + */ + public Builder setNegativeButton(int textId, final OnClickListener listener) { + P.mNegativeButtonText = P.mContext.getText(textId); + P.mNegativeButtonListener = listener; + return this; + } + + /** + * Set a listener to be invoked when the negative button of the dialog is pressed. + * @param text The text to display in the negative button + * @param listener The {@link DialogInterface.OnClickListener} to use. + * + * @return This Builder object to allow for chaining of calls to set methods + */ + public Builder setNegativeButton(CharSequence text, final OnClickListener listener) { + P.mNegativeButtonText = text; + P.mNegativeButtonListener = listener; + return this; + } + + /** + * Set a listener to be invoked when the neutral button of the dialog is pressed. + * @param textId The resource id of the text to display in the neutral button + * @param listener The {@link DialogInterface.OnClickListener} to use. + * + * @return This Builder object to allow for chaining of calls to set methods + */ + public Builder setNeutralButton(int textId, final OnClickListener listener) { + P.mNeutralButtonText = P.mContext.getText(textId); + P.mNeutralButtonListener = listener; + return this; + } + + /** + * Set a listener to be invoked when the neutral button of the dialog is pressed. + * @param text The text to display in the neutral button + * @param listener The {@link DialogInterface.OnClickListener} to use. + * + * @return This Builder object to allow for chaining of calls to set methods + */ + public Builder setNeutralButton(CharSequence text, final OnClickListener listener) { + P.mNeutralButtonText = text; + P.mNeutralButtonListener = listener; + return this; + } + + /** + * Sets whether the dialog is cancelable or not default is true. + * + * @return This Builder object to allow for chaining of calls to set methods + */ + public Builder setCancelable(boolean cancelable) { + P.mCancelable = cancelable; + return this; + } + + /** + * Sets the callback that will be called if the dialog is canceled. + * @see #setCancelable(boolean) + * + * @return This Builder object to allow for chaining of calls to set methods + */ + public Builder setOnCancelListener(OnCancelListener onCancelListener) { + P.mOnCancelListener = onCancelListener; + return this; + } + + /** + * Sets the callback that will be called if a key is dispatched to the dialog. + * + * @return This Builder object to allow for chaining of calls to set methods + */ + public Builder setOnKeyListener(OnKeyListener onKeyListener) { + P.mOnKeyListener = onKeyListener; + return this; + } + + /** + * Set a list of items to be displayed in the dialog as the content, you will be notified of the + * selected item via the supplied listener. This should be an array type i.e. R.array.foo + * + * @return This Builder object to allow for chaining of calls to set methods + */ + public Builder setItems(int itemsId, final OnClickListener listener) { + P.mItems = P.mContext.getResources().getTextArray(itemsId); + P.mOnClickListener = listener; + return this; + } + + /** + * Set a list of items to be displayed in the dialog as the content, you will be notified of the + * selected item via the supplied listener. + * + * @return This Builder object to allow for chaining of calls to set methods + */ + public Builder setItems(CharSequence[] items, final OnClickListener listener) { + P.mItems = items; + P.mOnClickListener = listener; + return this; + } + + /** + * Set a list of items, which are supplied by the given {@link ListAdapter}, to be + * displayed in the dialog as the content, you will be notified of the + * selected item via the supplied listener. + * + * @param adapter The {@link ListAdapter} to supply the list of items + * @param listener The listener that will be called when an item is clicked. + * + * @return This Builder object to allow for chaining of calls to set methods + */ + public Builder setAdapter(final ListAdapter adapter, final OnClickListener listener) { + P.mAdapter = adapter; + P.mOnClickListener = listener; + return this; + } + + /** + * Set a list of items, which are supplied by the given {@link Cursor}, to be + * displayed in the dialog as the content, you will be notified of the + * selected item via the supplied listener. + * + * @param cursor The {@link Cursor} to supply the list of items + * @param listener The listener that will be called when an item is clicked. + * @param labelColumn The column name on the cursor containing the string to display + * in the label. + * + * @return This Builder object to allow for chaining of calls to set methods + */ + public Builder setCursor(final Cursor cursor, final OnClickListener listener, + String labelColumn) { + P.mCursor = cursor; + P.mLabelColumn = labelColumn; + P.mOnClickListener = listener; + return this; + } + + /** + * Set a list of items to be displayed in the dialog as the content, + * you will be notified of the selected item via the supplied listener. + * This should be an array type, e.g. R.array.foo. The list will have + * a check mark displayed to the right of the text for each checked + * item. Clicking on an item in the list will not dismiss the dialog. + * Clicking on a button will dismiss the dialog. + * + * @param itemsId the resource id of an array i.e. R.array.foo + * @param checkedItems specifies which items are checked. It should be null in which case no + * items are checked. If non null it must be exactly the same length as the array of + * items. + * @param listener notified when an item on the list is clicked. The dialog will not be + * dismissed when an item is clicked. It will only be dismissed if clicked on a + * button, if no buttons are supplied it's up to the user to dismiss the dialog. + * + * @return This Builder object to allow for chaining of calls to set methods + */ + public Builder setMultiChoiceItems(int itemsId, boolean[] checkedItems, + final OnMultiChoiceClickListener listener) { + P.mItems = P.mContext.getResources().getTextArray(itemsId); + P.mOnCheckboxClickListener = listener; + P.mCheckedItems = checkedItems; + P.mIsMultiChoice = true; + return this; + } + + /** + * Set a list of items to be displayed in the dialog as the content, + * you will be notified of the selected item via the supplied listener. + * The list will have a check mark displayed to the right of the text + * for each checked item. Clicking on an item in the list will not + * dismiss the dialog. Clicking on a button will dismiss the dialog. + * + * @param items the text of the items to be displayed in the list. + * @param checkedItems specifies which items are checked. It should be null in which case no + * items are checked. If non null it must be exactly the same length as the array of + * items. + * @param listener notified when an item on the list is clicked. The dialog will not be + * dismissed when an item is clicked. It will only be dismissed if clicked on a + * button, if no buttons are supplied it's up to the user to dismiss the dialog. + * + * @return This Builder object to allow for chaining of calls to set methods + */ + public Builder setMultiChoiceItems(CharSequence[] items, boolean[] checkedItems, + final OnMultiChoiceClickListener listener) { + P.mItems = items; + P.mOnCheckboxClickListener = listener; + P.mCheckedItems = checkedItems; + P.mIsMultiChoice = true; + return this; + } + + /** + * Set a list of items to be displayed in the dialog as the content, + * you will be notified of the selected item via the supplied listener. + * The list will have a check mark displayed to the right of the text + * for each checked item. Clicking on an item in the list will not + * dismiss the dialog. Clicking on a button will dismiss the dialog. + * + * @param cursor the cursor used to provide the items. + * @param isCheckedColumn specifies the column name on the cursor to use to determine + * whether a checkbox is checked or not. It must return an integer value where 1 + * means checked and 0 means unchecked. + * @param labelColumn The column name on the cursor containing the string to display in the + * label. + * @param listener notified when an item on the list is clicked. The dialog will not be + * dismissed when an item is clicked. It will only be dismissed if clicked on a + * button, if no buttons are supplied it's up to the user to dismiss the dialog. + * + * @return This Builder object to allow for chaining of calls to set methods + */ + public Builder setMultiChoiceItems(Cursor cursor, String isCheckedColumn, String labelColumn, + final OnMultiChoiceClickListener listener) { + P.mCursor = cursor; + P.mOnCheckboxClickListener = listener; + P.mIsCheckedColumn = isCheckedColumn; + P.mLabelColumn = labelColumn; + P.mIsMultiChoice = true; + return this; + } + + /** + * Set a list of items to be displayed in the dialog as the content, you will be notified of + * the selected item via the supplied listener. This should be an array type i.e. + * R.array.foo The list will have a check mark displayed to the right of the text for the + * checked item. Clicking on an item in the list will not dismiss the dialog. Clicking on a + * button will dismiss the dialog. + * + * @param itemsId the resource id of an array i.e. R.array.foo + * @param checkedItem specifies which item is checked. If -1 no items are checked. + * @param listener notified when an item on the list is clicked. The dialog will not be + * dismissed when an item is clicked. It will only be dismissed if clicked on a + * button, if no buttons are supplied it's up to the user to dismiss the dialog. + * + * @return This Builder object to allow for chaining of calls to set methods + */ + public Builder setSingleChoiceItems(int itemsId, int checkedItem, + final OnClickListener listener) { + P.mItems = P.mContext.getResources().getTextArray(itemsId); + P.mOnClickListener = listener; + P.mCheckedItem = checkedItem; + P.mIsSingleChoice = true; + return this; + } + + /** + * Set a list of items to be displayed in the dialog as the content, you will be notified of + * the selected item via the supplied listener. The list will have a check mark displayed to + * the right of the text for the checked item. Clicking on an item in the list will not + * dismiss the dialog. Clicking on a button will dismiss the dialog. + * + * @param cursor the cursor to retrieve the items from. + * @param checkedItem specifies which item is checked. If -1 no items are checked. + * @param labelColumn The column name on the cursor containing the string to display in the + * label. + * @param listener notified when an item on the list is clicked. The dialog will not be + * dismissed when an item is clicked. It will only be dismissed if clicked on a + * button, if no buttons are supplied it's up to the user to dismiss the dialog. + * + * @return This Builder object to allow for chaining of calls to set methods + */ + public Builder setSingleChoiceItems(Cursor cursor, int checkedItem, String labelColumn, + final OnClickListener listener) { + P.mCursor = cursor; + P.mOnClickListener = listener; + P.mCheckedItem = checkedItem; + P.mLabelColumn = labelColumn; + P.mIsSingleChoice = true; + return this; + } + + /** + * Set a list of items to be displayed in the dialog as the content, you will be notified of + * the selected item via the supplied listener. The list will have a check mark displayed to + * the right of the text for the checked item. Clicking on an item in the list will not + * dismiss the dialog. Clicking on a button will dismiss the dialog. + * + * @param items the items to be displayed. + * @param checkedItem specifies which item is checked. If -1 no items are checked. + * @param listener notified when an item on the list is clicked. The dialog will not be + * dismissed when an item is clicked. It will only be dismissed if clicked on a + * button, if no buttons are supplied it's up to the user to dismiss the dialog. + * + * @return This Builder object to allow for chaining of calls to set methods + */ + public Builder setSingleChoiceItems(CharSequence[] items, int checkedItem, final OnClickListener listener) { + P.mItems = items; + P.mOnClickListener = listener; + P.mCheckedItem = checkedItem; + P.mIsSingleChoice = true; + return this; + } + + /** + * Set a list of items to be displayed in the dialog as the content, you will be notified of + * the selected item via the supplied listener. The list will have a check mark displayed to + * the right of the text for the checked item. Clicking on an item in the list will not + * dismiss the dialog. Clicking on a button will dismiss the dialog. + * + * @param adapter The {@link ListAdapter} to supply the list of items + * @param checkedItem specifies which item is checked. If -1 no items are checked. + * @param listener notified when an item on the list is clicked. The dialog will not be + * dismissed when an item is clicked. It will only be dismissed if clicked on a + * button, if no buttons are supplied it's up to the user to dismiss the dialog. + * + * @return This Builder object to allow for chaining of calls to set methods + */ + public Builder setSingleChoiceItems(ListAdapter adapter, int checkedItem, final OnClickListener listener) { + P.mAdapter = adapter; + P.mOnClickListener = listener; + P.mCheckedItem = checkedItem; + P.mIsSingleChoice = true; + return this; + } + + /** + * Sets a listener to be invoked when an item in the list is selected. + * + * @param listener The listener to be invoked. + * @see AdapterView#setOnItemSelectedListener(android.widget.AdapterView.OnItemSelectedListener) + * + * @return This Builder object to allow for chaining of calls to set methods + */ + public Builder setOnItemSelectedListener(final AdapterView.OnItemSelectedListener listener) { + P.mOnItemSelectedListener = listener; + return this; + } + + /** + * Set a custom view to be the contents of the Dialog. If the supplied view is an instance + * of a {@link ListView} the light background will be used. + * + * @param view The view to use as the contents of the Dialog. + * + * @return This Builder object to allow for chaining of calls to set methods + */ + public Builder setView(View view) { + P.mView = view; + P.mViewSpacingSpecified = false; + return this; + } + + /** + * Set a custom view to be the contents of the Dialog, specifying the + * spacing to appear around that view. If the supplied view is an + * instance of a {@link ListView} the light background will be used. + * + * @param view The view to use as the contents of the Dialog. + * @param viewSpacingLeft Spacing between the left edge of the view and + * the dialog frame + * @param viewSpacingTop Spacing between the top edge of the view and + * the dialog frame + * @param viewSpacingRight Spacing between the right edge of the view + * and the dialog frame + * @param viewSpacingBottom Spacing between the bottom edge of the view + * and the dialog frame + * @return This Builder object to allow for chaining of calls to set + * methods + * + * @hide pending API review + */ + public Builder setView(View view, int viewSpacingLeft, int viewSpacingTop, + int viewSpacingRight, int viewSpacingBottom) { + P.mView = view; + P.mViewSpacingSpecified = true; + P.mViewSpacingLeft = viewSpacingLeft; + P.mViewSpacingTop = viewSpacingTop; + P.mViewSpacingRight = viewSpacingRight; + P.mViewSpacingBottom = viewSpacingBottom; + return this; + } + + /** + * Sets the Dialog to use the inverse background, regardless of what the + * contents is. + * + * @param useInverseBackground Whether to use the inverse background + * + * @return This Builder object to allow for chaining of calls to set methods + */ + public Builder setInverseBackgroundForced(boolean useInverseBackground) { + P.mForceInverseBackground = useInverseBackground; + return this; + } + + /** + * Creates a {@link AlertDialog} with the arguments supplied to this builder. It does not + * {@link Dialog#show()} the dialog. This allows the user to do any extra processing + * before displaying the dialog. Use {@link #show()} if you don't have any other processing + * to do and want this to be created and displayed. + */ + public AlertDialog create() { + final AlertDialog dialog = new AlertDialog(P.mContext); + P.apply(dialog.mAlert); + dialog.setCancelable(P.mCancelable); + dialog.setOnCancelListener(P.mOnCancelListener); + if (P.mOnKeyListener != null) { + dialog.setOnKeyListener(P.mOnKeyListener); + } + return dialog; + } + + /** + * Creates a {@link AlertDialog} with the arguments supplied to this builder and + * {@link Dialog#show()}'s the dialog. + */ + public AlertDialog show() { + AlertDialog dialog = create(); + dialog.show(); + return dialog; + } + } + +} diff --git a/core/java/android/app/AliasActivity.java b/core/java/android/app/AliasActivity.java new file mode 100644 index 0000000..4f91e02 --- /dev/null +++ b/core/java/android/app/AliasActivity.java @@ -0,0 +1,123 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * 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 android.app; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + +import android.content.Intent; +import android.content.pm.ActivityInfo; +import android.content.pm.PackageManager; +import android.content.res.XmlResourceParser; +import android.os.Bundle; +import android.util.AttributeSet; +import android.util.Xml; +import com.android.internal.util.XmlUtils; + +import java.io.IOException; + +/** + * Stub activity that launches another activity (and then finishes itself) + * based on information in its component's manifest meta-data. This is a + * simple way to implement an alias-like mechanism. + * + * To use this activity, you should include in the manifest for the associated + * component an entry named "android.app.alias". It is a reference to an XML + * resource describing an intent that launches the real application. + */ +public class AliasActivity extends Activity { + /** + * This is the name under which you should store in your component the + * meta-data information about the alias. It is a reference to an XML + * resource describing an intent that launches the real application. + * {@hide} + */ + public final String ALIAS_META_DATA = "android.app.alias"; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + XmlResourceParser parser = null; + try { + ActivityInfo ai = getPackageManager().getActivityInfo( + getComponentName(), PackageManager.GET_META_DATA); + parser = ai.loadXmlMetaData(getPackageManager(), + ALIAS_META_DATA); + if (parser == null) { + throw new RuntimeException("Alias requires a meta-data field " + + ALIAS_META_DATA); + } + + Intent intent = parseAlias(parser); + if (intent == null) { + throw new RuntimeException( + "No <intent> tag found in alias description"); + } + + startActivity(intent); + finish(); + + } catch (PackageManager.NameNotFoundException e) { + throw new RuntimeException("Error parsing alias", e); + } catch (XmlPullParserException e) { + throw new RuntimeException("Error parsing alias", e); + } catch (IOException e) { + throw new RuntimeException("Error parsing alias", e); + } finally { + if (parser != null) parser.close(); + } + } + + private Intent parseAlias(XmlPullParser parser) + throws XmlPullParserException, IOException { + AttributeSet attrs = Xml.asAttributeSet(parser); + + Intent intent = null; + + int type; + while ((type=parser.next()) != XmlPullParser.END_DOCUMENT + && type != XmlPullParser.START_TAG) { + } + + String nodeName = parser.getName(); + if (!"alias".equals(nodeName)) { + throw new RuntimeException( + "Alias meta-data must start with <alias> tag; found" + + nodeName + " at " + parser.getPositionDescription()); + } + + int outerDepth = parser.getDepth(); + while ((type=parser.next()) != XmlPullParser.END_DOCUMENT + && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) { + if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) { + continue; + } + + nodeName = parser.getName(); + if ("intent".equals(nodeName)) { + Intent gotIntent = Intent.parseIntent(getResources(), parser, attrs); + if (intent == null) intent = gotIntent; + } else { + XmlUtils.skipCurrentTag(parser); + } + } + + return intent; + } + +} diff --git a/core/java/android/app/Application.java b/core/java/android/app/Application.java new file mode 100644 index 0000000..45ce860 --- /dev/null +++ b/core/java/android/app/Application.java @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2006 The Android Open Source Project + * + * 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 android.app; + +import android.content.ComponentCallbacks; +import android.content.Context; +import android.content.ContextWrapper; +import android.content.res.Configuration; + +/** + * Base class for those who need to maintain global application state. You can + * provide your own implementation by specifying its name in your + * AndroidManifest.xml's <application> tag, which will cause that class + * to be instantiated for you when the process for your application/package is + * created. + */ +public class Application extends ContextWrapper implements ComponentCallbacks { + + public Application() { + super(null); + } + + /** + * Called when the application is starting, before any other application + * objects have been created. Implementations should be as quick as + * possible (for example using lazy initialization of state) since the time + * spent in this function directly impacts the performance of starting the + * first activity, service, or receiver in a process. + * If you override this method, be sure to call super.onCreate(). + */ + public void onCreate() { + } + + /** + * Called when the application is stopping. There are no more application + * objects running and the process will exit. <em>Note: never depend on + * this method being called; in many cases an unneeded application process + * will simply be killed by the kernel without executing any application + * code.</em> + * If you override this method, be sure to call super.onTerminate(). + */ + public void onTerminate() { + } + + public void onConfigurationChanged(Configuration newConfig) { + } + + public void onLowMemory() { + } + + // ------------------ Internal API ------------------ + + /** + * @hide + */ + /* package */ final void attach(Context context) { + attachBaseContext(context); + } + +} diff --git a/core/java/android/app/ApplicationContext.java b/core/java/android/app/ApplicationContext.java new file mode 100644 index 0000000..3b5ad86 --- /dev/null +++ b/core/java/android/app/ApplicationContext.java @@ -0,0 +1,2765 @@ +/* + * Copyright (C) 2006 The Android Open Source Project + * + * 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 android.app; + +import com.google.android.collect.Maps; +import com.android.internal.util.XmlUtils; + +import android.bluetooth.BluetoothDevice; +import android.bluetooth.IBluetoothDevice; +import android.content.BroadcastReceiver; +import android.content.ComponentName; +import android.content.ContentResolver; +import android.content.Context; +import android.content.ContextWrapper; +import android.content.IContentProvider; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.ReceiverCallNotAllowedException; +import android.content.ServiceConnection; +import android.content.SharedPreferences; +import android.content.pm.ActivityInfo; +import android.content.pm.ApplicationInfo; +import android.content.pm.ComponentInfo; +import android.content.pm.IPackageDataObserver; +import android.content.pm.IPackageDeleteObserver; +import android.content.pm.IPackageStatsObserver; +import android.content.pm.IPackageInstallObserver; +import android.content.pm.IPackageManager; +import android.content.pm.InstrumentationInfo; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.content.pm.PermissionGroupInfo; +import android.content.pm.PermissionInfo; +import android.content.pm.ProviderInfo; +import android.content.pm.ResolveInfo; +import android.content.pm.ServiceInfo; +import android.content.res.AssetManager; +import android.content.res.Resources; +import android.content.res.XmlResourceParser; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteDatabase.CursorFactory; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.Drawable; +import android.hardware.SensorManager; +import android.location.ILocationManager; +import android.location.LocationManager; +import android.media.AudioManager; +import android.net.ConnectivityManager; +import android.net.IConnectivityManager; +import android.net.Uri; +import android.net.wifi.IWifiManager; +import android.net.wifi.WifiManager; +import android.os.Binder; +import android.os.Bundle; +import android.os.Looper; +import android.os.RemoteException; +import android.os.FileUtils; +import android.os.Handler; +import android.os.IBinder; +import android.os.IPowerManager; +import android.os.ParcelFileDescriptor; +import android.os.PowerManager; +import android.os.Process; +import android.os.ServiceManager; +import android.os.Vibrator; +import android.os.FileUtils.FileStatus; +import android.telephony.TelephonyManager; +import android.text.ClipboardManager; +import android.util.AndroidRuntimeException; +import android.util.Log; +import android.view.ContextThemeWrapper; +import android.view.LayoutInflater; +import android.view.WindowManagerImpl; +import android.view.inputmethod.InputMethodManager; + +import com.android.internal.policy.PolicyManager; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.lang.ref.WeakReference; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; + +import org.xmlpull.v1.XmlPullParserException; + +class ReceiverRestrictedContext extends ContextWrapper { + ReceiverRestrictedContext(Context base) { + super(base); + } + + @Override + public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter) { + return registerReceiver(receiver, filter, null, null); + } + + @Override + public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter, + String broadcastPermission, Handler scheduler) { + throw new ReceiverCallNotAllowedException( + "IntentReceiver components are not allowed to register to receive intents"); + //ex.fillInStackTrace(); + //Log.e("IntentReceiver", ex.getMessage(), ex); + //return mContext.registerReceiver(receiver, filter, broadcastPermission, + // scheduler); + } + + @Override + public boolean bindService(Intent service, ServiceConnection conn, int flags) { + throw new ReceiverCallNotAllowedException( + "IntentReceiver components are not allowed to bind to services"); + //ex.fillInStackTrace(); + //Log.e("IntentReceiver", ex.getMessage(), ex); + //return mContext.bindService(service, interfaceName, conn, flags); + } +} + +/** + * Common implementation of Context API, which Activity and other application + * classes inherit. + */ +class ApplicationContext extends Context { + private final static String TAG = "ApplicationContext"; + private final static boolean DEBUG_ICONS = false; + + private static final Object sSync = new Object(); + private static AlarmManager sAlarmManager; + private static PowerManager sPowerManager; + private static ConnectivityManager sConnectivityManager; + private static WifiManager sWifiManager; + private static LocationManager sLocationManager; + private static boolean sIsBluetoothDeviceCached = false; + private static BluetoothDevice sBluetoothDevice; + private static IWallpaperService sWallpaperService; + private static final HashMap<File, SharedPreferencesImpl> sSharedPrefs = + new HashMap<File, SharedPreferencesImpl>(); + + private AudioManager mAudioManager; + /*package*/ ActivityThread.PackageInfo mPackageInfo; + private Resources mResources; + /*package*/ ActivityThread mMainThread; + private Context mOuterContext; + private IBinder mActivityToken = null; + private ApplicationContentResolver mContentResolver; + private int mThemeResource = 0; + private Resources.Theme mTheme = null; + private PackageManager mPackageManager; + private NotificationManager mNotificationManager = null; + private ActivityManager mActivityManager = null; + private Context mReceiverRestrictedContext = null; + private SearchManager mSearchManager = null; + private SensorManager mSensorManager = null; + private Vibrator mVibrator = null; + private LayoutInflater mLayoutInflater = null; + private StatusBarManager mStatusBarManager = null; + private TelephonyManager mTelephonyManager = null; + private ClipboardManager mClipboardManager = null; + + private final Object mSync = new Object(); + + private File mDatabasesDir; + private File mPreferencesDir; + private File mFilesDir; + + + private File mCacheDir; + + private Drawable mWallpaper; + private IWallpaperServiceCallback mWallpaperCallback = null; + + private static long sInstanceCount = 0; + + private static final String[] EMPTY_FILE_LIST = {}; + + @Override + protected void finalize() throws Throwable { + super.finalize(); + --sInstanceCount; + } + + public static long getInstanceCount() { + return sInstanceCount; + } + + @Override + public AssetManager getAssets() { + return mResources.getAssets(); + } + + @Override + public Resources getResources() { + return mResources; + } + + @Override + public PackageManager getPackageManager() { + if (mPackageManager != null) { + return mPackageManager; + } + + IPackageManager pm = ActivityThread.getPackageManager(); + if (pm != null) { + // Doesn't matter if we make more than one instance. + return (mPackageManager = new ApplicationPackageManager(this, pm)); + } + + return null; + } + + @Override + public ContentResolver getContentResolver() { + return mContentResolver; + } + + @Override + public Looper getMainLooper() { + return mMainThread.getLooper(); + } + + @Override + public Context getApplicationContext() { + return mMainThread.getApplication(); + } + + @Override + public void setTheme(int resid) { + mThemeResource = resid; + } + + @Override + public Resources.Theme getTheme() { + if (mTheme == null) { + if (mThemeResource == 0) { + mThemeResource = com.android.internal.R.style.Theme; + } + mTheme = mResources.newTheme(); + mTheme.applyStyle(mThemeResource, true); + } + return mTheme; + } + + @Override + public ClassLoader getClassLoader() { + return mPackageInfo != null ? + mPackageInfo.getClassLoader() : ClassLoader.getSystemClassLoader(); + } + + @Override + public String getPackageName() { + if (mPackageInfo != null) { + return mPackageInfo.getPackageName(); + } + throw new RuntimeException("Not supported in system context"); + } + + @Override + public String getPackageResourcePath() { + if (mPackageInfo != null) { + return mPackageInfo.getResDir(); + } + throw new RuntimeException("Not supported in system context"); + } + + @Override + public String getPackageCodePath() { + if (mPackageInfo != null) { + return mPackageInfo.getAppDir(); + } + throw new RuntimeException("Not supported in system context"); + } + + private static File makeBackupFile(File prefsFile) { + return new File(prefsFile.getPath() + ".bak"); + } + + @Override + public SharedPreferences getSharedPreferences(String name, int mode) { + SharedPreferencesImpl sp; + File f = makeFilename(getPreferencesDir(), name + ".xml"); + synchronized (sSharedPrefs) { + sp = sSharedPrefs.get(f); + if (sp != null && !sp.hasFileChanged()) { + //Log.i(TAG, "Returning existing prefs " + name + ": " + sp); + return sp; + } + } + + FileInputStream str = null; + File backup = makeBackupFile(f); + if (backup.exists()) { + f.delete(); + backup.renameTo(f); + } + + // Debugging + if (f.exists() && !f.canRead()) { + Log.w(TAG, "Attempt to read preferences file " + f + " without permission"); + } + + Map map = null; + if (f.exists() && f.canRead()) { + try { + str = new FileInputStream(f); + map = XmlUtils.readMapXml(str); + str.close(); + } catch (org.xmlpull.v1.XmlPullParserException e) { + Log.w(TAG, "getSharedPreferences", e); + } catch (FileNotFoundException e) { + Log.w(TAG, "getSharedPreferences", e); + } catch (IOException e) { + Log.w(TAG, "getSharedPreferences", e); + } + } + + synchronized (sSharedPrefs) { + if (sp != null) { + //Log.i(TAG, "Updating existing prefs " + name + " " + sp + ": " + map); + sp.replace(map); + } else { + sp = sSharedPrefs.get(f); + if (sp == null) { + sp = new SharedPreferencesImpl(f, mode, map); + sSharedPrefs.put(f, sp); + } + } + return sp; + } + } + + private File getPreferencesDir() { + synchronized (mSync) { + if (mPreferencesDir == null) { + mPreferencesDir = new File(getDataDirFile(), "shared_prefs"); + } + return mPreferencesDir; + } + } + + @Override + public FileInputStream openFileInput(String name) + throws FileNotFoundException { + File f = makeFilename(getFilesDir(), name); + return new FileInputStream(f); + } + + @Override + public FileOutputStream openFileOutput(String name, int mode) + throws FileNotFoundException { + final boolean append = (mode&MODE_APPEND) != 0; + File f = makeFilename(getFilesDir(), name); + try { + FileOutputStream fos = new FileOutputStream(f, append); + setFilePermissionsFromMode(f.getPath(), mode, 0); + return fos; + } catch (FileNotFoundException e) { + } + + File parent = f.getParentFile(); + parent.mkdir(); + FileUtils.setPermissions( + parent.getPath(), + FileUtils.S_IRWXU|FileUtils.S_IRWXG|FileUtils.S_IXOTH, + -1, -1); + FileOutputStream fos = new FileOutputStream(f, append); + setFilePermissionsFromMode(f.getPath(), mode, 0); + return fos; + } + + @Override + public boolean deleteFile(String name) { + File f = makeFilename(getFilesDir(), name); + return f.delete(); + } + + @Override + public File getFilesDir() { + synchronized (mSync) { + if (mFilesDir == null) { + mFilesDir = new File(getDataDirFile(), "files"); + } + if (!mFilesDir.exists()) { + if(!mFilesDir.mkdirs()) { + Log.w(TAG, "Unable to create files directory"); + return null; + } + FileUtils.setPermissions( + mFilesDir.getPath(), + FileUtils.S_IRWXU|FileUtils.S_IRWXG|FileUtils.S_IXOTH, + -1, -1); + } + return mFilesDir; + } + } + + @Override + public File getCacheDir() { + synchronized (mSync) { + if (mCacheDir == null) { + mCacheDir = new File(getDataDirFile(), "cache"); + } + if (!mCacheDir.exists()) { + if(!mCacheDir.mkdirs()) { + Log.w(TAG, "Unable to create cache directory"); + return null; + } + FileUtils.setPermissions( + mCacheDir.getPath(), + FileUtils.S_IRWXU|FileUtils.S_IRWXG|FileUtils.S_IXOTH, + -1, -1); + } + } + return mCacheDir; + } + + + @Override + public File getFileStreamPath(String name) { + return makeFilename(getFilesDir(), name); + } + + @Override + public String[] fileList() { + final String[] list = getFilesDir().list(); + return (list != null) ? list : EMPTY_FILE_LIST; + } + + @Override + public SQLiteDatabase openOrCreateDatabase(String name, int mode, CursorFactory factory) { + File dir = getDatabasesDir(); + if (!dir.isDirectory() && dir.mkdir()) { + FileUtils.setPermissions(dir.getPath(), + FileUtils.S_IRWXU|FileUtils.S_IRWXG|FileUtils.S_IXOTH, + -1, -1); + } + + File f = makeFilename(dir, name); + SQLiteDatabase db = SQLiteDatabase.openOrCreateDatabase(f, factory); + setFilePermissionsFromMode(f.getPath(), mode, 0); + return db; + } + + @Override + public boolean deleteDatabase(String name) { + try { + File f = makeFilename(getDatabasesDir(), name); + return f.delete(); + } catch (Exception e) { + } + return false; + } + + @Override + public File getDatabasePath(String name) { + return makeFilename(getDatabasesDir(), name); + } + + @Override + public String[] databaseList() { + final String[] list = getDatabasesDir().list(); + return (list != null) ? list : EMPTY_FILE_LIST; + } + + + private File getDatabasesDir() { + synchronized (mSync) { + if (mDatabasesDir == null) { + mDatabasesDir = new File(getDataDirFile(), "databases"); + } + if (mDatabasesDir.getPath().equals("databases")) { + mDatabasesDir = new File("/data/system"); + } + return mDatabasesDir; + } + } + + @Override + public Drawable getWallpaper() { + Drawable dr = peekWallpaper(); + return dr != null ? dr : getResources().getDrawable( + com.android.internal.R.drawable.default_wallpaper); + } + + @Override + public synchronized Drawable peekWallpaper() { + if (mWallpaper != null) { + return mWallpaper; + } + mWallpaperCallback = new WallpaperCallback(this); + mWallpaper = getCurrentWallpaperLocked(); + return mWallpaper; + } + + private Drawable getCurrentWallpaperLocked() { + try { + ParcelFileDescriptor fd = getWallpaperService().getWallpaper(mWallpaperCallback); + if (fd != null) { + Bitmap bm = BitmapFactory.decodeFileDescriptor(fd.getFileDescriptor()); + if (bm != null) { + return new BitmapDrawable(bm); + } + } + } catch (RemoteException e) { + } + return null; + } + + @Override + public int getWallpaperDesiredMinimumWidth() { + try { + return getWallpaperService().getWidthHint(); + } catch (RemoteException e) { + // Shouldn't happen! + return 0; + } + } + + @Override + public int getWallpaperDesiredMinimumHeight() { + try { + return getWallpaperService().getHeightHint(); + } catch (RemoteException e) { + // Shouldn't happen! + return 0; + } + } + + @Override + public void setWallpaper(Bitmap bitmap) throws IOException { + try { + ParcelFileDescriptor fd = getWallpaperService().setWallpaper(); + if (fd == null) { + return; + } + FileOutputStream fos = null; + try { + fos = new ParcelFileDescriptor.AutoCloseOutputStream(fd); + bitmap.compress(Bitmap.CompressFormat.PNG, 90, fos); + } finally { + if (fos != null) { + fos.close(); + } + } + } catch (RemoteException e) { + } + } + + @Override + public void setWallpaper(InputStream data) throws IOException { + try { + ParcelFileDescriptor fd = getWallpaperService().setWallpaper(); + if (fd == null) { + return; + } + FileOutputStream fos = null; + try { + fos = new ParcelFileDescriptor.AutoCloseOutputStream(fd); + setWallpaper(data, fos); + } finally { + if (fos != null) { + fos.close(); + } + } + } catch (RemoteException e) { + } + } + + private void setWallpaper(InputStream data, FileOutputStream fos) + throws IOException { + byte[] buffer = new byte[32768]; + int amt; + while ((amt=data.read(buffer)) > 0) { + fos.write(buffer, 0, amt); + } + } + + @Override + public void clearWallpaper() throws IOException { + try { + /* Set the wallpaper to the default values */ + ParcelFileDescriptor fd = getWallpaperService().setWallpaper(); + if (fd != null) { + FileOutputStream fos = null; + try { + fos = new ParcelFileDescriptor.AutoCloseOutputStream(fd); + setWallpaper(getResources().openRawResource( + com.android.internal.R.drawable.default_wallpaper), + fos); + } finally { + if (fos != null) { + fos.close(); + } + } + } + } catch (RemoteException e) { + } + } + + @Override + public void startActivity(Intent intent) { + if ((intent.getFlags()&Intent.FLAG_ACTIVITY_NEW_TASK) == 0) { + throw new AndroidRuntimeException( + "Calling startActivity() from outside of an Activity " + + " context requires the FLAG_ACTIVITY_NEW_TASK flag." + + " Is this really what you want?"); + } + mMainThread.getInstrumentation().execStartActivity( + getOuterContext(), mMainThread.getApplicationThread(), null, null, intent, -1); + } + + @Override + public void sendBroadcast(Intent intent) { + String resolvedType = intent.resolveTypeIfNeeded(getContentResolver()); + try { + ActivityManagerNative.getDefault().broadcastIntent( + mMainThread.getApplicationThread(), intent, resolvedType, null, + Activity.RESULT_OK, null, null, null, false, false); + } catch (RemoteException e) { + } + } + + @Override + public void sendBroadcast(Intent intent, String receiverPermission) { + String resolvedType = intent.resolveTypeIfNeeded(getContentResolver()); + try { + ActivityManagerNative.getDefault().broadcastIntent( + mMainThread.getApplicationThread(), intent, resolvedType, null, + Activity.RESULT_OK, null, null, receiverPermission, false, false); + } catch (RemoteException e) { + } + } + + @Override + public void sendOrderedBroadcast(Intent intent, + String receiverPermission) { + String resolvedType = intent.resolveTypeIfNeeded(getContentResolver()); + try { + ActivityManagerNative.getDefault().broadcastIntent( + mMainThread.getApplicationThread(), intent, resolvedType, null, + Activity.RESULT_OK, null, null, receiverPermission, true, false); + } catch (RemoteException e) { + } + } + + @Override + public void sendOrderedBroadcast(Intent intent, + String receiverPermission, BroadcastReceiver resultReceiver, + Handler scheduler, int initialCode, String initialData, + Bundle initialExtras) { + IIntentReceiver rd = null; + if (resultReceiver != null) { + if (mPackageInfo != null) { + if (scheduler == null) { + scheduler = mMainThread.getHandler(); + } + rd = mPackageInfo.getReceiverDispatcher( + resultReceiver, getOuterContext(), scheduler, + mMainThread.getInstrumentation(), false); + } else { + if (scheduler == null) { + scheduler = mMainThread.getHandler(); + } + rd = new ActivityThread.PackageInfo.ReceiverDispatcher( + resultReceiver, getOuterContext(), scheduler, null, false).getIIntentReceiver(); + } + } + String resolvedType = intent.resolveTypeIfNeeded(getContentResolver()); + try { + ActivityManagerNative.getDefault().broadcastIntent( + mMainThread.getApplicationThread(), intent, resolvedType, rd, + initialCode, initialData, initialExtras, receiverPermission, + true, false); + } catch (RemoteException e) { + } + } + + @Override + public void sendStickyBroadcast(Intent intent) { + String resolvedType = intent.resolveTypeIfNeeded(getContentResolver()); + try { + ActivityManagerNative.getDefault().broadcastIntent( + mMainThread.getApplicationThread(), intent, resolvedType, null, + Activity.RESULT_OK, null, null, null, false, true); + } catch (RemoteException e) { + } + } + + @Override + public void removeStickyBroadcast(Intent intent) { + String resolvedType = intent.resolveTypeIfNeeded(getContentResolver()); + if (resolvedType != null) { + intent = new Intent(intent); + intent.setDataAndType(intent.getData(), resolvedType); + } + try { + ActivityManagerNative.getDefault().unbroadcastIntent( + mMainThread.getApplicationThread(), intent); + } catch (RemoteException e) { + } + } + + @Override + public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter) { + return registerReceiver(receiver, filter, null, null); + } + + @Override + public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter, + String broadcastPermission, Handler scheduler) { + return registerReceiverInternal(receiver, filter, broadcastPermission, + scheduler, getOuterContext()); + } + + private Intent registerReceiverInternal(BroadcastReceiver receiver, + IntentFilter filter, String broadcastPermission, + Handler scheduler, Context context) { + IIntentReceiver rd = null; + if (receiver != null) { + if (mPackageInfo != null && context != null) { + if (scheduler == null) { + scheduler = mMainThread.getHandler(); + } + rd = mPackageInfo.getReceiverDispatcher( + receiver, context, scheduler, + mMainThread.getInstrumentation(), true); + } else { + if (scheduler == null) { + scheduler = mMainThread.getHandler(); + } + rd = new ActivityThread.PackageInfo.ReceiverDispatcher( + receiver, context, scheduler, null, false).getIIntentReceiver(); + } + } + try { + return ActivityManagerNative.getDefault().registerReceiver( + mMainThread.getApplicationThread(), + rd, filter, broadcastPermission); + } catch (RemoteException e) { + return null; + } + } + + @Override + public void unregisterReceiver(BroadcastReceiver receiver) { + if (mPackageInfo != null) { + IIntentReceiver rd = mPackageInfo.forgetReceiverDispatcher( + getOuterContext(), receiver); + try { + ActivityManagerNative.getDefault().unregisterReceiver(rd); + } catch (RemoteException e) { + } + } else { + throw new RuntimeException("Not supported in system context"); + } + } + + @Override + public ComponentName startService(Intent service) { + try { + ComponentName cn = ActivityManagerNative.getDefault().startService( + mMainThread.getApplicationThread(), service, + service.resolveTypeIfNeeded(getContentResolver())); + if (cn != null && cn.getPackageName().equals("!")) { + throw new SecurityException( + "Not allowed to start service " + service + + " without permission " + cn.getClassName()); + } + return cn; + } catch (RemoteException e) { + return null; + } + } + + @Override + public boolean stopService(Intent service) { + try { + int res = ActivityManagerNative.getDefault().stopService( + mMainThread.getApplicationThread(), service, + service.resolveTypeIfNeeded(getContentResolver())); + if (res < 0) { + throw new SecurityException( + "Not allowed to stop service " + service); + } + return res != 0; + } catch (RemoteException e) { + return false; + } + } + + @Override + public boolean bindService(Intent service, ServiceConnection conn, + int flags) { + IServiceConnection sd; + if (mPackageInfo != null) { + sd = mPackageInfo.getServiceDispatcher(conn, getOuterContext(), + mMainThread.getHandler(), flags); + } else { + throw new RuntimeException("Not supported in system context"); + } + try { + int res = ActivityManagerNative.getDefault().bindService( + mMainThread.getApplicationThread(), getActivityToken(), + service, service.resolveTypeIfNeeded(getContentResolver()), + sd, flags); + if (res < 0) { + throw new SecurityException( + "Not allowed to bind to service " + service); + } + return res != 0; + } catch (RemoteException e) { + return false; + } + } + + @Override + public void unbindService(ServiceConnection conn) { + if (mPackageInfo != null) { + IServiceConnection sd = mPackageInfo.forgetServiceDispatcher( + getOuterContext(), conn); + try { + ActivityManagerNative.getDefault().unbindService(sd); + } catch (RemoteException e) { + } + } else { + throw new RuntimeException("Not supported in system context"); + } + } + + @Override + public boolean startInstrumentation(ComponentName className, + String profileFile, Bundle arguments) { + try { + return ActivityManagerNative.getDefault().startInstrumentation( + className, profileFile, 0, arguments, null); + } catch (RemoteException e) { + // System has crashed, nothing we can do. + } + return false; + } + + @Override + public Object getSystemService(String name) { + if (WINDOW_SERVICE.equals(name)) { + return WindowManagerImpl.getDefault(); + } else if (LAYOUT_INFLATER_SERVICE.equals(name)) { + synchronized (mSync) { + LayoutInflater inflater = mLayoutInflater; + if (inflater != null) { + return inflater; + } + mLayoutInflater = inflater = + PolicyManager.makeNewLayoutInflater(getOuterContext()); + return inflater; + } + } else if (ACTIVITY_SERVICE.equals(name)) { + return getActivityManager(); + } else if (ALARM_SERVICE.equals(name)) { + return getAlarmManager(); + } else if (POWER_SERVICE.equals(name)) { + return getPowerManager(); + } else if (CONNECTIVITY_SERVICE.equals(name)) { + return getConnectivityManager(); + } else if (WIFI_SERVICE.equals(name)) { + return getWifiManager(); + } else if (NOTIFICATION_SERVICE.equals(name)) { + return getNotificationManager(); + } else if (KEYGUARD_SERVICE.equals(name)) { + return new KeyguardManager(); + } else if (LOCATION_SERVICE.equals(name)) { + return getLocationManager(); + } else if (SEARCH_SERVICE.equals(name)) { + return getSearchManager(); + } else if ( SENSOR_SERVICE.equals(name)) { + return getSensorManager(); + } else if (BLUETOOTH_SERVICE.equals(name)) { + return getBluetoothDevice(); + } else if (VIBRATOR_SERVICE.equals(name)) { + return getVibrator(); + } else if (STATUS_BAR_SERVICE.equals(name)) { + synchronized (mSync) { + if (mStatusBarManager == null) { + mStatusBarManager = new StatusBarManager(getOuterContext()); + } + return mStatusBarManager; + } + } else if (AUDIO_SERVICE.equals(name)) { + return getAudioManager(); + } else if (TELEPHONY_SERVICE.equals(name)) { + return getTelephonyManager(); + } else if (CLIPBOARD_SERVICE.equals(name)) { + return getClipboardManager(); + } else if (INPUT_METHOD_SERVICE.equals(name)) { + return InputMethodManager.getInstance(this); + } + + return null; + } + + private ActivityManager getActivityManager() { + synchronized (mSync) { + if (mActivityManager == null) { + mActivityManager = new ActivityManager(getOuterContext(), + mMainThread.getHandler()); + } + } + return mActivityManager; + } + + private AlarmManager getAlarmManager() { + synchronized (sSync) { + if (sAlarmManager == null) { + IBinder b = ServiceManager.getService(ALARM_SERVICE); + IAlarmManager service = IAlarmManager.Stub.asInterface(b); + sAlarmManager = new AlarmManager(service); + } + } + return sAlarmManager; + } + + private PowerManager getPowerManager() { + synchronized (sSync) { + if (sPowerManager == null) { + IBinder b = ServiceManager.getService(POWER_SERVICE); + IPowerManager service = IPowerManager.Stub.asInterface(b); + sPowerManager = new PowerManager(service, mMainThread.getHandler()); + } + } + return sPowerManager; + } + + private ConnectivityManager getConnectivityManager() + { + synchronized (sSync) { + if (sConnectivityManager == null) { + IBinder b = ServiceManager.getService(CONNECTIVITY_SERVICE); + IConnectivityManager service = IConnectivityManager.Stub.asInterface(b); + sConnectivityManager = new ConnectivityManager(service); + } + } + return sConnectivityManager; + } + + private WifiManager getWifiManager() + { + synchronized (sSync) { + if (sWifiManager == null) { + IBinder b = ServiceManager.getService(WIFI_SERVICE); + IWifiManager service = IWifiManager.Stub.asInterface(b); + sWifiManager = new WifiManager(service, mMainThread.getHandler()); + } + } + return sWifiManager; + } + + private NotificationManager getNotificationManager() + { + synchronized (mSync) { + if (mNotificationManager == null) { + mNotificationManager = new NotificationManager( + new ContextThemeWrapper(getOuterContext(), com.android.internal.R.style.Theme_Dialog), + mMainThread.getHandler()); + } + } + return mNotificationManager; + } + + private TelephonyManager getTelephonyManager() { + synchronized (mSync) { + if (mTelephonyManager == null) { + mTelephonyManager = new TelephonyManager(getOuterContext()); + } + } + return mTelephonyManager; + } + + private ClipboardManager getClipboardManager() { + synchronized (mSync) { + if (mClipboardManager == null) { + mClipboardManager = new ClipboardManager(getOuterContext(), + mMainThread.getHandler()); + } + } + return mClipboardManager; + } + + private LocationManager getLocationManager() { + synchronized (sSync) { + if (sLocationManager == null) { + IBinder b = ServiceManager.getService(LOCATION_SERVICE); + ILocationManager service = ILocationManager.Stub.asInterface(b); + sLocationManager = new LocationManager(service); + } + } + return sLocationManager; + } + + private SearchManager getSearchManager() { + // This is only useable in Activity Contexts + if (getActivityToken() == null) { + throw new AndroidRuntimeException( + "Acquiring SearchManager objects only valid in Activity Contexts."); + } + synchronized (mSync) { + if (mSearchManager == null) { + mSearchManager = new SearchManager(getOuterContext(), mMainThread.getHandler()); + } + } + return mSearchManager; + } + + private BluetoothDevice getBluetoothDevice() { + if (sIsBluetoothDeviceCached) { + return sBluetoothDevice; + } + synchronized (sSync) { + IBinder b = ServiceManager.getService(BLUETOOTH_SERVICE); + if (b == null) { + sBluetoothDevice = null; + } else { + IBluetoothDevice service = IBluetoothDevice.Stub.asInterface(b); + sBluetoothDevice = new BluetoothDevice(service); + } + sIsBluetoothDeviceCached = true; + } + return sBluetoothDevice; + } + + private SensorManager getSensorManager() { + synchronized (mSync) { + if (mSensorManager == null) { + mSensorManager = new SensorManager(mMainThread.getHandler().getLooper()); + } + } + return mSensorManager; + } + + private Vibrator getVibrator() { + synchronized (mSync) { + if (mVibrator == null) { + mVibrator = new Vibrator(); + } + } + return mVibrator; + } + + private IWallpaperService getWallpaperService() { + synchronized (sSync) { + if (sWallpaperService == null) { + IBinder b = ServiceManager.getService(WALLPAPER_SERVICE); + sWallpaperService = IWallpaperService.Stub.asInterface(b); + } + } + return sWallpaperService; + } + + private AudioManager getAudioManager() + { + if (mAudioManager == null) { + mAudioManager = new AudioManager(this); + } + return mAudioManager; + } + + @Override + public int checkPermission(String permission, int pid, int uid) { + if (permission == null) { + throw new IllegalArgumentException("permission is null"); + } + + if (!Process.supportsProcesses()) { + return PackageManager.PERMISSION_GRANTED; + } + try { + return ActivityManagerNative.getDefault().checkPermission( + permission, pid, uid); + } catch (RemoteException e) { + return PackageManager.PERMISSION_DENIED; + } + } + + @Override + public int checkCallingPermission(String permission) { + if (permission == null) { + throw new IllegalArgumentException("permission is null"); + } + + if (!Process.supportsProcesses()) { + return PackageManager.PERMISSION_GRANTED; + } + int pid = Binder.getCallingPid(); + if (pid != Process.myPid()) { + return checkPermission(permission, pid, + Binder.getCallingUid()); + } + return PackageManager.PERMISSION_DENIED; + } + + @Override + public int checkCallingOrSelfPermission(String permission) { + if (permission == null) { + throw new IllegalArgumentException("permission is null"); + } + + return checkPermission(permission, Binder.getCallingPid(), + Binder.getCallingUid()); + } + + private void enforce( + String permission, int resultOfCheck, + boolean selfToo, int uid, String message) { + if (resultOfCheck != PackageManager.PERMISSION_GRANTED) { + throw new SecurityException( + (message != null ? (message + ": ") : "") + + (selfToo + ? "Neither user " + uid + " nor current process has " + : "User " + uid + " does not have ") + + permission + + "."); + } + } + + public void enforcePermission( + String permission, int pid, int uid, String message) { + enforce(permission, + checkPermission(permission, pid, uid), + false, + uid, + message); + } + + public void enforceCallingPermission(String permission, String message) { + enforce(permission, + checkCallingPermission(permission), + false, + Binder.getCallingUid(), + message); + } + + public void enforceCallingOrSelfPermission( + String permission, String message) { + enforce(permission, + checkCallingOrSelfPermission(permission), + true, + Binder.getCallingUid(), + message); + } + + @Override + public void grantUriPermission(String toPackage, Uri uri, int modeFlags) { + try { + ActivityManagerNative.getDefault().grantUriPermission( + mMainThread.getApplicationThread(), toPackage, uri, + modeFlags); + } catch (RemoteException e) { + } + } + + @Override + public void revokeUriPermission(Uri uri, int modeFlags) { + try { + ActivityManagerNative.getDefault().revokeUriPermission( + mMainThread.getApplicationThread(), uri, + modeFlags); + } catch (RemoteException e) { + } + } + + @Override + public int checkUriPermission(Uri uri, int pid, int uid, int modeFlags) { + if (!Process.supportsProcesses()) { + return PackageManager.PERMISSION_GRANTED; + } + try { + return ActivityManagerNative.getDefault().checkUriPermission( + uri, pid, uid, modeFlags); + } catch (RemoteException e) { + return PackageManager.PERMISSION_DENIED; + } + } + + @Override + public int checkCallingUriPermission(Uri uri, int modeFlags) { + if (!Process.supportsProcesses()) { + return PackageManager.PERMISSION_GRANTED; + } + int pid = Binder.getCallingPid(); + if (pid != Process.myPid()) { + return checkUriPermission(uri, pid, + Binder.getCallingUid(), modeFlags); + } + return PackageManager.PERMISSION_DENIED; + } + + @Override + public int checkCallingOrSelfUriPermission(Uri uri, int modeFlags) { + return checkUriPermission(uri, Binder.getCallingPid(), + Binder.getCallingUid(), modeFlags); + } + + @Override + public int checkUriPermission(Uri uri, String readPermission, + String writePermission, int pid, int uid, int modeFlags) { + if (false) { + Log.i("foo", "checkUriPermission: uri=" + uri + "readPermission=" + + readPermission + " writePermission=" + writePermission + + " pid=" + pid + " uid=" + uid + " mode" + modeFlags); + } + if ((modeFlags&Intent.FLAG_GRANT_READ_URI_PERMISSION) != 0) { + if (readPermission == null + || checkPermission(readPermission, pid, uid) + == PackageManager.PERMISSION_GRANTED) { + return PackageManager.PERMISSION_GRANTED; + } + } + if ((modeFlags&Intent.FLAG_GRANT_WRITE_URI_PERMISSION) != 0) { + if (writePermission == null + || checkPermission(writePermission, pid, uid) + == PackageManager.PERMISSION_GRANTED) { + return PackageManager.PERMISSION_GRANTED; + } + } + return uri != null ? checkUriPermission(uri, pid, uid, modeFlags) + : PackageManager.PERMISSION_DENIED; + } + + private String uriModeFlagToString(int uriModeFlags) { + switch (uriModeFlags) { + case Intent.FLAG_GRANT_READ_URI_PERMISSION | + Intent.FLAG_GRANT_WRITE_URI_PERMISSION: + return "read and write"; + case Intent.FLAG_GRANT_READ_URI_PERMISSION: + return "read"; + case Intent.FLAG_GRANT_WRITE_URI_PERMISSION: + return "write"; + } + throw new IllegalArgumentException( + "Unknown permission mode flags: " + uriModeFlags); + } + + private void enforceForUri( + int modeFlags, int resultOfCheck, boolean selfToo, + int uid, Uri uri, String message) { + if (resultOfCheck != PackageManager.PERMISSION_GRANTED) { + throw new SecurityException( + (message != null ? (message + ": ") : "") + + (selfToo + ? "Neither user " + uid + " nor current process has " + : "User " + uid + " does not have ") + + uriModeFlagToString(modeFlags) + + " permission on " + + uri + + "."); + } + } + + public void enforceUriPermission( + Uri uri, int pid, int uid, int modeFlags, String message) { + enforceForUri( + modeFlags, checkUriPermission(uri, pid, uid, modeFlags), + false, uid, uri, message); + } + + public void enforceCallingUriPermission( + Uri uri, int modeFlags, String message) { + enforceForUri( + modeFlags, checkCallingUriPermission(uri, modeFlags), + false, Binder.getCallingUid(), uri, message); + } + + public void enforceCallingOrSelfUriPermission( + Uri uri, int modeFlags, String message) { + enforceForUri( + modeFlags, + checkCallingOrSelfUriPermission(uri, modeFlags), true, + Binder.getCallingUid(), uri, message); + } + + public void enforceUriPermission( + Uri uri, String readPermission, String writePermission, + int pid, int uid, int modeFlags, String message) { + enforceForUri(modeFlags, + checkUriPermission( + uri, readPermission, writePermission, pid, uid, + modeFlags), + false, + uid, + uri, + message); + } + + @Override + public Context createPackageContext(String packageName, int flags) + throws PackageManager.NameNotFoundException { + if (packageName.equals("system") || packageName.equals("android")) { + return new ApplicationContext(mMainThread.getSystemContext()); + } + + ActivityThread.PackageInfo pi = + mMainThread.getPackageInfo(packageName, flags); + if (pi != null) { + ApplicationContext c = new ApplicationContext(); + c.init(pi, null, mMainThread); + if (c.mResources != null) { + return c; + } + } + + // Should be a better exception. + throw new PackageManager.NameNotFoundException( + "Application package " + packageName + " not found"); + } + + private File getDataDirFile() { + if (mPackageInfo != null) { + return mPackageInfo.getDataDirFile(); + } + throw new RuntimeException("Not supported in system context"); + } + + @Override + public File getDir(String name, int mode) { + name = "app_" + name; + File file = makeFilename(getDataDirFile(), name); + if (!file.exists()) { + file.mkdir(); + setFilePermissionsFromMode(file.getPath(), mode, + FileUtils.S_IRWXU|FileUtils.S_IRWXG|FileUtils.S_IXOTH); + } + return file; + } + + static ApplicationContext createSystemContext(ActivityThread mainThread) { + ApplicationContext context = new ApplicationContext(); + context.init(Resources.getSystem(), mainThread); + return context; + } + + ApplicationContext() { + ++sInstanceCount; + mOuterContext = this; + } + + /** + * Create a new ApplicationContext from an existing one. The new one + * works and operates the same as the one it is copying. + * + * @param context Existing application context. + */ + public ApplicationContext(ApplicationContext context) { + ++sInstanceCount; + mPackageInfo = context.mPackageInfo; + mResources = context.mResources; + mMainThread = context.mMainThread; + mContentResolver = context.mContentResolver; + mOuterContext = this; + } + + final void init(ActivityThread.PackageInfo packageInfo, + IBinder activityToken, ActivityThread mainThread) { + mPackageInfo = packageInfo; + mResources = mPackageInfo.getResources(mainThread); + mMainThread = mainThread; + mContentResolver = new ApplicationContentResolver(this, mainThread); + + setActivityToken(activityToken); + } + + final void init(Resources resources, ActivityThread mainThread) { + mPackageInfo = null; + mResources = resources; + mMainThread = mainThread; + mContentResolver = new ApplicationContentResolver(this, mainThread); + } + + final void scheduleFinalCleanup(String who, String what) { + mMainThread.scheduleContextCleanup(this, who, what); + } + + final void performFinalCleanup(String who, String what) { + //Log.i(TAG, "Cleanup up context: " + this); + mPackageInfo.removeContextRegistrations(getOuterContext(), who, what); + } + + final Context getReceiverRestrictedContext() { + if (mReceiverRestrictedContext != null) { + return mReceiverRestrictedContext; + } + return mReceiverRestrictedContext = new ReceiverRestrictedContext(getOuterContext()); + } + + final void setActivityToken(IBinder token) { + mActivityToken = token; + } + + final void setOuterContext(Context context) { + mOuterContext = context; + } + + final Context getOuterContext() { + return mOuterContext; + } + + final IBinder getActivityToken() { + return mActivityToken; + } + + private static void setFilePermissionsFromMode(String name, int mode, + int extraPermissions) { + int perms = FileUtils.S_IRUSR|FileUtils.S_IWUSR + |FileUtils.S_IRGRP|FileUtils.S_IWGRP + |extraPermissions; + if ((mode&MODE_WORLD_READABLE) != 0) { + perms |= FileUtils.S_IROTH; + } + if ((mode&MODE_WORLD_WRITEABLE) != 0) { + perms |= FileUtils.S_IWOTH; + } + if (false) { + Log.i(TAG, "File " + name + ": mode=0x" + Integer.toHexString(mode) + + ", perms=0x" + Integer.toHexString(perms)); + } + FileUtils.setPermissions(name, perms, -1, -1); + } + + private File makeFilename(File base, String name) { + if (name.indexOf(File.separatorChar) < 0) { + return new File(base, name); + } + throw new IllegalArgumentException( + "File " + name + " contains a path separator"); + } + + // ---------------------------------------------------------------------- + // ---------------------------------------------------------------------- + // ---------------------------------------------------------------------- + + private static final class ApplicationContentResolver extends ContentResolver { + public ApplicationContentResolver(Context context, + ActivityThread mainThread) + { + super(context); + mMainThread = mainThread; + } + + @Override + protected IContentProvider acquireProvider(Context context, String name) + { + return mMainThread.acquireProvider(context, name); + } + + @Override + public boolean releaseProvider(IContentProvider provider) + { + return mMainThread.releaseProvider(provider); + } + + private final ActivityThread mMainThread; + } + + // ---------------------------------------------------------------------- + // ---------------------------------------------------------------------- + // ---------------------------------------------------------------------- + + /*package*/ + static final class ApplicationPackageManager extends PackageManager { + @Override + public PackageInfo getPackageInfo(String packageName, int flags) + throws NameNotFoundException { + try { + PackageInfo pi = mPM.getPackageInfo(packageName, flags); + if (pi != null) { + return pi; + } + } catch (RemoteException e) { + throw new RuntimeException("Package manager has died", e); + } + + throw new NameNotFoundException(packageName); + } + + public Intent getLaunchIntentForPackage(String packageName) + throws NameNotFoundException { + // First see if the package has an INFO activity; the existence of + // such an activity is implied to be the desired front-door for the + // overall package (such as if it has multiple launcher entries). + Intent intent = getLaunchIntentForPackageCategory(this, packageName, + Intent.CATEGORY_INFO); + if (intent != null) { + return intent; + } + + // Otherwise, try to find a main launcher activity. + return getLaunchIntentForPackageCategory(this, packageName, + Intent.CATEGORY_LAUNCHER); + } + + // XXX This should be implemented as a call to the package manager, + // to reduce the work needed. + static Intent getLaunchIntentForPackageCategory(PackageManager pm, + String packageName, String category) { + Intent intent = new Intent(Intent.ACTION_MAIN); + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + Intent intentToResolve = new Intent(Intent.ACTION_MAIN, null); + intentToResolve.addCategory(category); + final List<ResolveInfo> apps = + pm.queryIntentActivities(intentToResolve, 0); + // I wish there were a way to directly get the "main" activity of a + // package but ... + for (ResolveInfo app : apps) { + if (app.activityInfo.packageName.equals(packageName)) { + intent.setClassName(packageName, app.activityInfo.name); + return intent; + } + } + return null; + } + + @Override + public int[] getPackageGids(String packageName) + throws NameNotFoundException { + try { + int[] gids = mPM.getPackageGids(packageName); + if (gids == null || gids.length > 0) { + return gids; + } + } catch (RemoteException e) { + throw new RuntimeException("Package manager has died", e); + } + + throw new NameNotFoundException(packageName); + } + + @Override + public PermissionInfo getPermissionInfo(String name, int flags) + throws NameNotFoundException { + try { + PermissionInfo pi = mPM.getPermissionInfo(name, flags); + if (pi != null) { + return pi; + } + } catch (RemoteException e) { + throw new RuntimeException("Package manager has died", e); + } + + throw new NameNotFoundException(name); + } + + @Override + public List<PermissionInfo> queryPermissionsByGroup(String group, int flags) + throws NameNotFoundException { + try { + List<PermissionInfo> pi = mPM.queryPermissionsByGroup(group, flags); + if (pi != null) { + return pi; + } + } catch (RemoteException e) { + throw new RuntimeException("Package manager has died", e); + } + + throw new NameNotFoundException(group); + } + + @Override + public PermissionGroupInfo getPermissionGroupInfo(String name, + int flags) throws NameNotFoundException { + try { + PermissionGroupInfo pgi = mPM.getPermissionGroupInfo(name, flags); + if (pgi != null) { + return pgi; + } + } catch (RemoteException e) { + throw new RuntimeException("Package manager has died", e); + } + + throw new NameNotFoundException(name); + } + + @Override + public List<PermissionGroupInfo> getAllPermissionGroups(int flags) { + try { + return mPM.getAllPermissionGroups(flags); + } catch (RemoteException e) { + throw new RuntimeException("Package manager has died", e); + } + } + + @Override + public ApplicationInfo getApplicationInfo(String packageName, int flags) + throws NameNotFoundException { + try { + ApplicationInfo ai = mPM.getApplicationInfo(packageName, flags); + if (ai != null) { + return ai; + } + } catch (RemoteException e) { + throw new RuntimeException("Package manager has died", e); + } + + throw new NameNotFoundException(packageName); + } + + @Override + public ActivityInfo getActivityInfo(ComponentName className, int flags) + throws NameNotFoundException { + try { + ActivityInfo ai = mPM.getActivityInfo(className, flags); + if (ai != null) { + return ai; + } + } catch (RemoteException e) { + throw new RuntimeException("Package manager has died", e); + } + + throw new NameNotFoundException(className.toString()); + } + + @Override + public ActivityInfo getReceiverInfo(ComponentName className, int flags) + throws NameNotFoundException { + try { + ActivityInfo ai = mPM.getReceiverInfo(className, flags); + if (ai != null) { + return ai; + } + } catch (RemoteException e) { + throw new RuntimeException("Package manager has died", e); + } + + throw new NameNotFoundException(className.toString()); + } + + @Override + public ServiceInfo getServiceInfo(ComponentName className, int flags) + throws NameNotFoundException { + try { + ServiceInfo si = mPM.getServiceInfo(className, flags); + if (si != null) { + return si; + } + } catch (RemoteException e) { + throw new RuntimeException("Package manager has died", e); + } + + throw new NameNotFoundException(className.toString()); + } + + @Override + public String[] getSystemSharedLibraryNames() { + try { + return mPM.getSystemSharedLibraryNames(); + } catch (RemoteException e) { + throw new RuntimeException("Package manager has died", e); + } + } + + @Override + public int checkPermission(String permName, String pkgName) { + try { + return mPM.checkPermission(permName, pkgName); + } catch (RemoteException e) { + throw new RuntimeException("Package manager has died", e); + } + } + + @Override + public boolean addPermission(PermissionInfo info) { + try { + return mPM.addPermission(info); + } catch (RemoteException e) { + throw new RuntimeException("Package manager has died", e); + } + } + + @Override + public void removePermission(String name) { + try { + mPM.removePermission(name); + } catch (RemoteException e) { + throw new RuntimeException("Package manager has died", e); + } + } + + @Override + public int checkSignatures(String pkg1, String pkg2) { + try { + return mPM.checkSignatures(pkg1, pkg2); + } catch (RemoteException e) { + throw new RuntimeException("Package manager has died", e); + } + } + + @Override + public String[] getPackagesForUid(int uid) { + try { + return mPM.getPackagesForUid(uid); + } catch (RemoteException e) { + throw new RuntimeException("Package manager has died", e); + } + } + + @Override + public String getNameForUid(int uid) { + try { + return mPM.getNameForUid(uid); + } catch (RemoteException e) { + throw new RuntimeException("Package manager has died", e); + } + } + + @Override + public int getUidForSharedUser(String sharedUserName) + throws NameNotFoundException { + try { + int uid = mPM.getUidForSharedUser(sharedUserName); + if(uid != -1) { + return uid; + } + } catch (RemoteException e) { + throw new RuntimeException("Package manager has died", e); + } + throw new NameNotFoundException("No shared userid for user:"+sharedUserName); + } + + @Override + public List<PackageInfo> getInstalledPackages(int flags) { + try { + return mPM.getInstalledPackages(flags); + } catch (RemoteException e) { + throw new RuntimeException("Package manager has died", e); + } + } + + @Override + public List<ApplicationInfo> getInstalledApplications(int flags) { + try { + return mPM.getInstalledApplications(flags); + } catch (RemoteException e) { + throw new RuntimeException("Package manager has died", e); + } + } + + @Override + public ResolveInfo resolveActivity(Intent intent, int flags) { + try { + return mPM.resolveIntent( + intent, + intent.resolveTypeIfNeeded(mContext.getContentResolver()), + flags); + } catch (RemoteException e) { + throw new RuntimeException("Package manager has died", e); + } + } + + @Override + public List<ResolveInfo> queryIntentActivities(Intent intent, + int flags) { + try { + return mPM.queryIntentActivities( + intent, + intent.resolveTypeIfNeeded(mContext.getContentResolver()), + flags); + } catch (RemoteException e) { + throw new RuntimeException("Package manager has died", e); + } + } + + @Override + public List<ResolveInfo> queryIntentActivityOptions( + ComponentName caller, Intent[] specifics, Intent intent, + int flags) { + final ContentResolver resolver = mContext.getContentResolver(); + + String[] specificTypes = null; + if (specifics != null) { + final int N = specifics.length; + for (int i=0; i<N; i++) { + Intent sp = specifics[i]; + if (sp != null) { + String t = sp.resolveTypeIfNeeded(resolver); + if (t != null) { + if (specificTypes == null) { + specificTypes = new String[N]; + } + specificTypes[i] = t; + } + } + } + } + + try { + return mPM.queryIntentActivityOptions(caller, specifics, + specificTypes, intent, intent.resolveTypeIfNeeded(resolver), + flags); + } catch (RemoteException e) { + throw new RuntimeException("Package manager has died", e); + } + } + + @Override + public List<ResolveInfo> queryBroadcastReceivers(Intent intent, int flags) { + try { + return mPM.queryIntentReceivers( + intent, + intent.resolveTypeIfNeeded(mContext.getContentResolver()), + flags); + } catch (RemoteException e) { + throw new RuntimeException("Package manager has died", e); + } + } + + @Override + public ResolveInfo resolveService(Intent intent, int flags) { + try { + return mPM.resolveService( + intent, + intent.resolveTypeIfNeeded(mContext.getContentResolver()), + flags); + } catch (RemoteException e) { + throw new RuntimeException("Package manager has died", e); + } + } + + @Override + public List<ResolveInfo> queryIntentServices(Intent intent, int flags) { + try { + return mPM.queryIntentServices( + intent, + intent.resolveTypeIfNeeded(mContext.getContentResolver()), + flags); + } catch (RemoteException e) { + throw new RuntimeException("Package manager has died", e); + } + } + + @Override + public ProviderInfo resolveContentProvider(String name, + int flags) { + try { + return mPM.resolveContentProvider(name, flags); + } catch (RemoteException e) { + throw new RuntimeException("Package manager has died", e); + } + } + + @Override + public List<ProviderInfo> queryContentProviders(String processName, + int uid, int flags) { + try { + return mPM.queryContentProviders(processName, uid, flags); + } catch (RemoteException e) { + throw new RuntimeException("Package manager has died", e); + } + } + + @Override + public InstrumentationInfo getInstrumentationInfo( + ComponentName className, int flags) + throws NameNotFoundException { + try { + InstrumentationInfo ii = mPM.getInstrumentationInfo( + className, flags); + if (ii != null) { + return ii; + } + } catch (RemoteException e) { + throw new RuntimeException("Package manager has died", e); + } + + throw new NameNotFoundException(className.toString()); + } + + @Override + public List<InstrumentationInfo> queryInstrumentation( + String targetPackage, int flags) { + try { + return mPM.queryInstrumentation(targetPackage, flags); + } catch (RemoteException e) { + throw new RuntimeException("Package manager has died", e); + } + } + + @Override public Drawable getDrawable(String packageName, int resid, + ApplicationInfo appInfo) { + ResourceName name = new ResourceName(packageName, resid); + Drawable dr = getCachedIcon(name); + if (dr != null) { + return dr; + } + if (appInfo == null) { + try { + appInfo = getApplicationInfo(packageName, 0); + } catch (NameNotFoundException e) { + return null; + } + } + try { + Resources r = getResourcesForApplication(appInfo); + dr = r.getDrawable(resid); + if (DEBUG_ICONS) Log.v(TAG, "Getting drawable 0x" + + Integer.toHexString(resid) + " from " + r + + ": " + dr); + putCachedIcon(name, dr); + return dr; + } catch (NameNotFoundException e) { + Log.w("PackageManager", "Failure retrieving resources for" + + appInfo.packageName); + } catch (RuntimeException e) { + // If an exception was thrown, fall through to return + // default icon. + Log.w("PackageManager", "Failure retrieving icon 0x" + + Integer.toHexString(resid) + " in package " + + packageName, e); + } + return null; + } + + @Override public Drawable getActivityIcon(ComponentName activityName) + throws NameNotFoundException { + return getActivityInfo(activityName, 0).loadIcon(this); + } + + @Override public Drawable getActivityIcon(Intent intent) + throws NameNotFoundException { + if (intent.getComponent() != null) { + return getActivityIcon(intent.getComponent()); + } + + ResolveInfo info = resolveActivity( + intent, PackageManager.MATCH_DEFAULT_ONLY); + if (info != null) { + return info.activityInfo.loadIcon(this); + } + + throw new NameNotFoundException(intent.toURI()); + } + + @Override public Drawable getDefaultActivityIcon() { + return Resources.getSystem().getDrawable( + com.android.internal.R.drawable.sym_def_app_icon); + } + + @Override public Drawable getApplicationIcon(ApplicationInfo info) { + final int icon = info.icon; + if (icon != 0) { + ResourceName name = new ResourceName(info, icon); + Drawable dr = getCachedIcon(name); + if (dr != null) { + return dr; + } + try { + Resources r = getResourcesForApplication(info); + dr = r.getDrawable(icon); + if (DEBUG_ICONS) Log.v(TAG, "Getting drawable 0x" + + Integer.toHexString(icon) + " from " + r + + ": " + dr); + putCachedIcon(name, dr); + return dr; + } catch (NameNotFoundException e) { + Log.w("PackageManager", "Failure retrieving resources for" + + info.packageName); + } catch (RuntimeException e) { + // If an exception was thrown, fall through to return + // default icon. + Log.w("PackageManager", "Failure retrieving app icon", e); + } + } + return getDefaultActivityIcon(); + } + + @Override public Drawable getApplicationIcon(String packageName) + throws NameNotFoundException { + return getApplicationIcon(getApplicationInfo(packageName, 0)); + } + + @Override public Resources getResourcesForActivity( + ComponentName activityName) throws NameNotFoundException { + return getResourcesForApplication( + getActivityInfo(activityName, 0).applicationInfo); + } + + @Override public Resources getResourcesForApplication( + ApplicationInfo app) throws NameNotFoundException { + if (app.packageName.equals("system")) { + return mContext.mMainThread.getSystemContext().getResources(); + } + Resources r = mContext.mMainThread.getTopLevelResources( + app.uid == Process.myUid() ? app.sourceDir + : app.publicSourceDir); + if (r != null) { + return r; + } + throw new NameNotFoundException("Unable to open " + app.publicSourceDir); + } + + @Override public Resources getResourcesForApplication( + String appPackageName) throws NameNotFoundException { + return getResourcesForApplication( + getApplicationInfo(appPackageName, 0)); + } + + int mCachedSafeMode = -1; + @Override public boolean isSafeMode() { + try { + if (mCachedSafeMode < 0) { + mCachedSafeMode = mPM.isSafeMode() ? 1 : 0; + } + return mCachedSafeMode != 0; + } catch (RemoteException e) { + throw new RuntimeException("Package manager has died", e); + } + } + + static void configurationChanged() { + synchronized (sSync) { + sIconCache.clear(); + sStringCache.clear(); + } + } + + ApplicationPackageManager(ApplicationContext context, + IPackageManager pm) { + mContext = context; + mPM = pm; + } + + private Drawable getCachedIcon(ResourceName name) { + synchronized (sSync) { + WeakReference<Drawable> wr = sIconCache.get(name); + if (DEBUG_ICONS) Log.v(TAG, "Get cached weak drawable ref for " + + name + ": " + wr); + if (wr != null) { // we have the activity + Drawable dr = wr.get(); + if (dr != null) { + if (DEBUG_ICONS) Log.v(TAG, "Get cached drawable for " + + name + ": " + dr); + return dr; + } + // our entry has been purged + sIconCache.remove(name); + } + } + return null; + } + + private void establishPackageRemovedReceiver() { + // mContext.registerReceiverInternal() winds up acquiring the + // main ActivityManagerService.this lock. If we hold our usual + // sSync global lock at the same time, we impose a required ordering + // on those two locks, which is not good for deadlock prevention. + // Use a dedicated lock around initialization of + // sPackageRemovedReceiver to avoid this. + synchronized (sPackageRemovedSync) { + if (sPackageRemovedReceiver == null) { + sPackageRemovedReceiver = new PackageRemovedReceiver(); + IntentFilter filter = new IntentFilter( + Intent.ACTION_PACKAGE_REMOVED); + filter.addDataScheme("package"); + mContext.registerReceiverInternal(sPackageRemovedReceiver, + filter, null, null, null); + } + } + } + + private void putCachedIcon(ResourceName name, Drawable dr) { + establishPackageRemovedReceiver(); + + synchronized (sSync) { + sIconCache.put(name, new WeakReference<Drawable>(dr)); + if (DEBUG_ICONS) Log.v(TAG, "Added cached drawable for " + + name + ": " + dr); + } + } + + private static final class PackageRemovedReceiver extends BroadcastReceiver { + @Override + public void onReceive(Context context, Intent intent) { + Uri data = intent.getData(); + String ssp; + if (data != null && (ssp=data.getSchemeSpecificPart()) != null) { + boolean needCleanup = false; + synchronized (sSync) { + Iterator<ResourceName> it = sIconCache.keySet().iterator(); + while (it.hasNext()) { + ResourceName nm = it.next(); + if (nm.packageName.equals(ssp)) { + //Log.i(TAG, "Removing cached drawable for " + nm); + it.remove(); + needCleanup = true; + } + } + it = sStringCache.keySet().iterator(); + while (it.hasNext()) { + ResourceName nm = it.next(); + if (nm.packageName.equals(ssp)) { + //Log.i(TAG, "Removing cached string for " + nm); + it.remove(); + needCleanup = true; + } + } + } + if (needCleanup || ActivityThread.currentActivityThread().hasPackageInfo(ssp)) { + ActivityThread.currentActivityThread().scheduleGcIdler(); + } + } + } + } + + private static final class ResourceName { + final String packageName; + final int iconId; + + ResourceName(String _packageName, int _iconId) { + packageName = _packageName; + iconId = _iconId; + } + + ResourceName(ApplicationInfo aInfo, int _iconId) { + this(aInfo.packageName, _iconId); + } + + ResourceName(ComponentInfo cInfo, int _iconId) { + this(cInfo.applicationInfo.packageName, _iconId); + } + + ResourceName(ResolveInfo rInfo, int _iconId) { + this(rInfo.activityInfo.applicationInfo.packageName, _iconId); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + ResourceName that = (ResourceName) o; + + if (iconId != that.iconId) return false; + return !(packageName != null ? + !packageName.equals(that.packageName) : that.packageName != null); + + } + + @Override + public int hashCode() { + int result; + result = packageName.hashCode(); + result = 31 * result + iconId; + return result; + } + + @Override + public String toString() { + return "{ResourceName " + packageName + " / " + iconId + "}"; + } + } + + private CharSequence getCachedString(ResourceName name) { + synchronized (sSync) { + WeakReference<CharSequence> wr = sStringCache.get(name); + if (wr != null) { // we have the activity + CharSequence cs = wr.get(); + if (cs != null) { + return cs; + } + // our entry has been purged + sStringCache.remove(name); + } + } + return null; + } + + private void putCachedString(ResourceName name, CharSequence cs) { + establishPackageRemovedReceiver(); + + synchronized (sSync) { + sStringCache.put(name, new WeakReference<CharSequence>(cs)); + } + } + + private CharSequence getLabel(ResourceName name, ApplicationInfo app, int id) { + CharSequence cs = getCachedString(name); + if (cs != null) { + return cs; + } + try { + Resources r = getResourcesForApplication(app); + cs = r.getText(id); + putCachedString(name, cs); + } catch (NameNotFoundException e) { + Log.w("PackageManager", "Failure retrieving resources for" + + app.packageName); + } catch (RuntimeException e) { + // If an exception was thrown, fall through to return null + Log.w("ApplicationInfo", "Failure retrieving activity name", e); + } + return cs; + } + + @Override + public CharSequence getText(String packageName, int resid, + ApplicationInfo appInfo) { + ResourceName name = new ResourceName(packageName, resid); + CharSequence text = getCachedString(name); + if (text != null) { + return text; + } + if (appInfo == null) { + try { + appInfo = getApplicationInfo(packageName, 0); + } catch (NameNotFoundException e) { + return null; + } + } + try { + Resources r = getResourcesForApplication(appInfo); + text = r.getText(resid); + putCachedString(name, text); + return text; + } catch (NameNotFoundException e) { + Log.w("PackageManager", "Failure retrieving resources for" + + appInfo.packageName); + } catch (RuntimeException e) { + // If an exception was thrown, fall through to return + // default icon. + Log.w("PackageManager", "Failure retrieving text 0x" + + Integer.toHexString(resid) + " in package " + + packageName, e); + } + return null; + } + + @Override + public XmlResourceParser getXml(String packageName, int resid, + ApplicationInfo appInfo) { + if (appInfo == null) { + try { + appInfo = getApplicationInfo(packageName, 0); + } catch (NameNotFoundException e) { + return null; + } + } + try { + Resources r = getResourcesForApplication(appInfo); + return r.getXml(resid); + } catch (RuntimeException e) { + // If an exception was thrown, fall through to return + // default icon. + Log.w("PackageManager", "Failure retrieving xml 0x" + + Integer.toHexString(resid) + " in package " + + packageName, e); + } catch (NameNotFoundException e) { + Log.w("PackageManager", "Failure retrieving resources for" + + appInfo.packageName); + } + return null; + } + + @Override + public CharSequence getApplicationLabel(ApplicationInfo info) { + if (info.nonLocalizedLabel != null) { + return info.nonLocalizedLabel; + } + final int id = info.labelRes; + if (id != 0) { + CharSequence cs = getLabel(new ResourceName(info, id), info, id); + if (cs != null) { + return cs; + } + } + return info.packageName; + } + + @Override + public void installPackage(Uri packageURI, IPackageInstallObserver observer, int flags) { + try { + mPM.installPackage(packageURI, observer, flags); + } catch (RemoteException e) { + // Should never happen! + } + } + + @Override + public void deletePackage(String packageName, IPackageDeleteObserver observer, int flags) { + try { + mPM.deletePackage(packageName, observer, flags); + } catch (RemoteException e) { + // Should never happen! + } + } + @Override + public void clearApplicationUserData(String packageName, + IPackageDataObserver observer) { + try { + mPM.clearApplicationUserData(packageName, observer); + } catch (RemoteException e) { + // Should never happen! + } + } + @Override + public void deleteApplicationCacheFiles(String packageName, + IPackageDataObserver observer) { + try { + mPM.deleteApplicationCacheFiles(packageName, observer); + } catch (RemoteException e) { + // Should never happen! + } + } + @Override + public void freeStorageAndNotify(long idealStorageSize, IPackageDataObserver observer) { + try { + mPM.freeStorageAndNotify(idealStorageSize, observer); + } catch (RemoteException e) { + // Should never happen! + } + } + + @Override + public void freeStorage(long idealStorageSize, PendingIntent opFinishedIntent) { + try { + mPM.freeStorage(idealStorageSize, opFinishedIntent); + } catch (RemoteException e) { + // Should never happen! + } + } + + @Override + public void getPackageSizeInfo(String packageName, + IPackageStatsObserver observer) { + try { + mPM.getPackageSizeInfo(packageName, observer); + } catch (RemoteException e) { + // Should never happen! + } + } + @Override + public void addPackageToPreferred(String packageName) { + try { + mPM.addPackageToPreferred(packageName); + } catch (RemoteException e) { + // Should never happen! + } + } + + @Override + public void removePackageFromPreferred(String packageName) { + try { + mPM.removePackageFromPreferred(packageName); + } catch (RemoteException e) { + // Should never happen! + } + } + + @Override + public List<PackageInfo> getPreferredPackages(int flags) { + try { + return mPM.getPreferredPackages(flags); + } catch (RemoteException e) { + // Should never happen! + } + return new ArrayList<PackageInfo>(); + } + + @Override + public void addPreferredActivity(IntentFilter filter, + int match, ComponentName[] set, ComponentName activity) { + try { + mPM.addPreferredActivity(filter, match, set, activity); + } catch (RemoteException e) { + // Should never happen! + } + } + + @Override + public void clearPackagePreferredActivities(String packageName) { + try { + mPM.clearPackagePreferredActivities(packageName); + } catch (RemoteException e) { + // Should never happen! + } + } + + @Override + public int getPreferredActivities(List<IntentFilter> outFilters, + List<ComponentName> outActivities, String packageName) { + try { + return mPM.getPreferredActivities(outFilters, outActivities, packageName); + } catch (RemoteException e) { + // Should never happen! + } + return 0; + } + + @Override + public void setComponentEnabledSetting(ComponentName componentName, + int newState, int flags) { + try { + mPM.setComponentEnabledSetting(componentName, newState, flags); + } catch (RemoteException e) { + // Should never happen! + } + } + + @Override + public int getComponentEnabledSetting(ComponentName componentName) { + try { + return mPM.getComponentEnabledSetting(componentName); + } catch (RemoteException e) { + // Should never happen! + } + return PackageManager.COMPONENT_ENABLED_STATE_DEFAULT; + } + + @Override + public void setApplicationEnabledSetting(String packageName, + int newState, int flags) { + try { + mPM.setApplicationEnabledSetting(packageName, newState, flags); + } catch (RemoteException e) { + // Should never happen! + } + } + + @Override + public int getApplicationEnabledSetting(String packageName) { + try { + return mPM.getApplicationEnabledSetting(packageName); + } catch (RemoteException e) { + // Should never happen! + } + return PackageManager.COMPONENT_ENABLED_STATE_DEFAULT; + } + + private final ApplicationContext mContext; + private final IPackageManager mPM; + + private static final Object sSync = new Object(); + private static final Object sPackageRemovedSync = new Object(); + private static BroadcastReceiver sPackageRemovedReceiver; + private static HashMap<ResourceName, WeakReference<Drawable> > sIconCache + = new HashMap<ResourceName, WeakReference<Drawable> >(); + private static HashMap<ResourceName, WeakReference<CharSequence> > sStringCache + = new HashMap<ResourceName, WeakReference<CharSequence> >(); + } + + // ---------------------------------------------------------------------- + // ---------------------------------------------------------------------- + // ---------------------------------------------------------------------- + + private static final class SharedPreferencesImpl implements SharedPreferences { + + private final File mFile; + private final File mBackupFile; + private final int mMode; + private Map mMap; + private final FileStatus mFileStatus = new FileStatus(); + private long mTimestamp; + + private List<OnSharedPreferenceChangeListener> mListeners; + + SharedPreferencesImpl( + File file, int mode, Map initialContents) { + mFile = file; + mBackupFile = makeBackupFile(file); + mMode = mode; + mMap = initialContents != null ? initialContents : new HashMap(); + if (FileUtils.getFileStatus(file.getPath(), mFileStatus)) { + mTimestamp = mFileStatus.mtime; + } + mListeners = new ArrayList<OnSharedPreferenceChangeListener>(); + } + + public boolean hasFileChanged() { + synchronized (this) { + if (!FileUtils.getFileStatus(mFile.getPath(), mFileStatus)) { + return true; + } + return mTimestamp != mFileStatus.mtime; + } + } + + public void replace(Map newContents) { + if (newContents != null) { + synchronized (this) { + mMap = newContents; + } + } + } + + public void registerOnSharedPreferenceChangeListener(OnSharedPreferenceChangeListener listener) { + synchronized(this) { + if (!mListeners.contains(listener)) { + mListeners.add(listener); + } + } + } + + public void unregisterOnSharedPreferenceChangeListener(OnSharedPreferenceChangeListener listener) { + synchronized(this) { + mListeners.remove(listener); + } + } + + public Map<String, ?> getAll() { + synchronized(this) { + //noinspection unchecked + return new HashMap(mMap); + } + } + + public String getString(String key, String defValue) { + synchronized (this) { + String v = (String)mMap.get(key); + return v != null ? v : defValue; + } + } + + public int getInt(String key, int defValue) { + synchronized (this) { + Integer v = (Integer)mMap.get(key); + return v != null ? v : defValue; + } + } + public long getLong(String key, long defValue) { + synchronized (this) { + Long v = (Long) mMap.get(key); + return v != null ? v : defValue; + } + } + public float getFloat(String key, float defValue) { + synchronized (this) { + Float v = (Float)mMap.get(key); + return v != null ? v : defValue; + } + } + public boolean getBoolean(String key, boolean defValue) { + synchronized (this) { + Boolean v = (Boolean)mMap.get(key); + return v != null ? v : defValue; + } + } + + public boolean contains(String key) { + synchronized (this) { + return mMap.containsKey(key); + } + } + + public final class EditorImpl implements Editor { + private final Map<String, Object> mModified = Maps.newHashMap(); + private boolean mClear = false; + + public Editor putString(String key, String value) { + synchronized (this) { + mModified.put(key, value); + return this; + } + } + public Editor putInt(String key, int value) { + synchronized (this) { + mModified.put(key, value); + return this; + } + } + public Editor putLong(String key, long value) { + synchronized (this) { + mModified.put(key, value); + return this; + } + } + public Editor putFloat(String key, float value) { + synchronized (this) { + mModified.put(key, value); + return this; + } + } + public Editor putBoolean(String key, boolean value) { + synchronized (this) { + mModified.put(key, value); + return this; + } + } + + public Editor remove(String key) { + synchronized (this) { + mModified.put(key, this); + return this; + } + } + + public Editor clear() { + synchronized (this) { + mClear = true; + return this; + } + } + + public boolean commit() { + boolean returnValue; + + boolean hasListeners; + List<String> keysModified = null; + List<OnSharedPreferenceChangeListener> listeners = null; + + synchronized (SharedPreferencesImpl.this) { + hasListeners = mListeners.size() > 0; + if (hasListeners) { + keysModified = new ArrayList<String>(); + listeners = new ArrayList<OnSharedPreferenceChangeListener>(mListeners); + } + + synchronized (this) { + if (mClear) { + mMap.clear(); + mClear = false; + } + + Iterator<Entry<String, Object>> it = mModified.entrySet().iterator(); + while (it.hasNext()) { + Map.Entry<String, Object> e = it.next(); + String k = e.getKey(); + Object v = e.getValue(); + if (v == this) { + mMap.remove(k); + } else { + mMap.put(k, v); + } + + if (hasListeners) { + keysModified.add(k); + } + } + + mModified.clear(); + } + + returnValue = writeFileLocked(); + } + + if (hasListeners) { + for (int i = keysModified.size() - 1; i >= 0; i--) { + final String key = keysModified.get(i); + // Call in the order they were registered + final int listenersSize = listeners.size(); + for (int j = 0; j < listenersSize; j++) { + listeners.get(j).onSharedPreferenceChanged(SharedPreferencesImpl.this, key); + } + } + } + + return returnValue; + } + } + + public Editor edit() { + return new EditorImpl(); + } + + private FileOutputStream createFileOutputStream(File file) { + FileOutputStream str = null; + try { + str = new FileOutputStream(file); + } catch (FileNotFoundException e) { + File parent = file.getParentFile(); + if (!parent.mkdir()) { + Log.e(TAG, "Couldn't create directory for SharedPreferences file " + file); + return null; + } + FileUtils.setPermissions( + parent.getPath(), + FileUtils.S_IRWXU|FileUtils.S_IRWXG|FileUtils.S_IXOTH, + -1, -1); + try { + str = new FileOutputStream(file); + } catch (FileNotFoundException e2) { + Log.e(TAG, "Couldn't create SharedPreferences file " + file, e2); + } + } + return str; + } + + private boolean writeFileLocked() { + // Rename the current file so it may be used as a backup during the next read + if (mFile.exists()) { + if (!mFile.renameTo(mBackupFile)) { + Log.e(TAG, "Couldn't rename file " + mFile + " to backup file " + mBackupFile); + } + } + + // Attempt to write the file, delete the backup and return true as atomically as + // possible. If any exception occurs, delete the new file; next time we will restore + // from the backup. + try { + FileOutputStream str = createFileOutputStream(mFile); + if (str == null) { + return false; + } + XmlUtils.writeMapXml(mMap, str); + str.close(); + setFilePermissionsFromMode(mFile.getPath(), mMode, 0); + if (FileUtils.getFileStatus(mFile.getPath(), mFileStatus)) { + mTimestamp = mFileStatus.mtime; + } + + // Writing was successful, delete the backup file + if (!mBackupFile.delete()) { + Log.e(TAG, "Couldn't delete new backup file " + mBackupFile); + } + return true; + } catch (XmlPullParserException e) { + Log.w(TAG, "writeFileLocked: Got exception:", e); + } catch (IOException e) { + Log.w(TAG, "writeFileLocked: Got exception:", e); + } + // Clean up an unsuccessfully written file + if (mFile.exists()) { + if (!mFile.delete()) { + Log.e(TAG, "Couldn't clean up partially-written file " + mFile); + } + } + return false; + } + } + + private static class WallpaperCallback extends IWallpaperServiceCallback.Stub { + private WeakReference<ApplicationContext> mContext; + + public WallpaperCallback(ApplicationContext context) { + mContext = new WeakReference<ApplicationContext>(context); + } + + public synchronized void onWallpaperChanged() { + + /* The wallpaper has changed but we shouldn't eagerly load the + * wallpaper as that would be inefficient. Reset the cached wallpaper + * to null so if the user requests the wallpaper again then we'll + * fetch it. + */ + final ApplicationContext applicationContext = mContext.get(); + if (applicationContext != null) { + applicationContext.mWallpaper = null; + } + } + } +} diff --git a/core/java/android/app/ApplicationLoaders.java b/core/java/android/app/ApplicationLoaders.java new file mode 100644 index 0000000..2e301c9 --- /dev/null +++ b/core/java/android/app/ApplicationLoaders.java @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2006 The Android Open Source Project + * + * 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 android.app; + +import dalvik.system.PathClassLoader; + +import java.util.HashMap; + +class ApplicationLoaders +{ + public static ApplicationLoaders getDefault() + { + return gApplicationLoaders; + } + + public ClassLoader getClassLoader(String zip, String appDataDir, + ClassLoader parent) + { + /* + * This is the parent we use if they pass "null" in. In theory + * this should be the "system" class loader; in practice we + * don't use that and can happily (and more efficiently) use the + * bootstrap class loader. + */ + ClassLoader baseParent = ClassLoader.getSystemClassLoader().getParent(); + + synchronized (mLoaders) { + if (parent == null) { + parent = baseParent; + } + + /* + * If we're one step up from the base class loader, find + * something in our cache. Otherwise, we create a whole + * new ClassLoader for the zip archive. + */ + if (parent == baseParent) { + ClassLoader loader = (ClassLoader)mLoaders.get(zip); + if (loader != null) { + return loader; + } + + PathClassLoader pathClassloader = + new PathClassLoader(zip, appDataDir + "/lib", parent); + + mLoaders.put(zip, pathClassloader); + return pathClassloader; + } + + return new PathClassLoader(zip, parent); + } + } + + private final HashMap mLoaders = new HashMap(); + + private static final ApplicationLoaders gApplicationLoaders + = new ApplicationLoaders(); +} diff --git a/core/java/android/app/ApplicationThreadNative.java b/core/java/android/app/ApplicationThreadNative.java new file mode 100644 index 0000000..d2cf55a --- /dev/null +++ b/core/java/android/app/ApplicationThreadNative.java @@ -0,0 +1,658 @@ +/* + * Copyright (C) 2006 The Android Open Source Project + * + * 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 android.app; + +import android.content.ComponentName; +import android.content.Intent; +import android.content.pm.ActivityInfo; +import android.content.pm.ApplicationInfo; +import android.content.pm.ProviderInfo; +import android.content.pm.ServiceInfo; +import android.content.res.Configuration; +import android.os.Binder; +import android.os.Bundle; +import android.os.RemoteException; +import android.os.IBinder; +import android.os.Parcel; +import android.os.ParcelFileDescriptor; + +import java.io.FileDescriptor; +import java.io.IOException; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** {@hide} */ +public abstract class ApplicationThreadNative extends Binder + implements IApplicationThread { + /** + * Cast a Binder object into an application thread interface, generating + * a proxy if needed. + */ + static public IApplicationThread asInterface(IBinder obj) { + if (obj == null) { + return null; + } + IApplicationThread in = + (IApplicationThread)obj.queryLocalInterface(descriptor); + if (in != null) { + return in; + } + + return new ApplicationThreadProxy(obj); + } + + public ApplicationThreadNative() { + attachInterface(this, descriptor); + } + + @Override + public boolean onTransact(int code, Parcel data, Parcel reply, int flags) + throws RemoteException { + switch (code) { + case SCHEDULE_PAUSE_ACTIVITY_TRANSACTION: + { + data.enforceInterface(IApplicationThread.descriptor); + IBinder b = data.readStrongBinder(); + boolean finished = data.readInt() != 0; + boolean userLeaving = data.readInt() != 0; + int configChanges = data.readInt(); + schedulePauseActivity(b, finished, userLeaving, configChanges); + return true; + } + + case SCHEDULE_STOP_ACTIVITY_TRANSACTION: + { + data.enforceInterface(IApplicationThread.descriptor); + IBinder b = data.readStrongBinder(); + boolean show = data.readInt() != 0; + int configChanges = data.readInt(); + scheduleStopActivity(b, show, configChanges); + return true; + } + + case SCHEDULE_WINDOW_VISIBILITY_TRANSACTION: + { + data.enforceInterface(IApplicationThread.descriptor); + IBinder b = data.readStrongBinder(); + boolean show = data.readInt() != 0; + scheduleWindowVisibility(b, show); + return true; + } + + case SCHEDULE_RESUME_ACTIVITY_TRANSACTION: + { + data.enforceInterface(IApplicationThread.descriptor); + IBinder b = data.readStrongBinder(); + boolean isForward = data.readInt() != 0; + scheduleResumeActivity(b, isForward); + return true; + } + + case SCHEDULE_SEND_RESULT_TRANSACTION: + { + data.enforceInterface(IApplicationThread.descriptor); + IBinder b = data.readStrongBinder(); + List<ResultInfo> ri = data.createTypedArrayList(ResultInfo.CREATOR); + scheduleSendResult(b, ri); + return true; + } + + case SCHEDULE_LAUNCH_ACTIVITY_TRANSACTION: + { + data.enforceInterface(IApplicationThread.descriptor); + Intent intent = Intent.CREATOR.createFromParcel(data); + IBinder b = data.readStrongBinder(); + ActivityInfo info = ActivityInfo.CREATOR.createFromParcel(data); + Bundle state = data.readBundle(); + List<ResultInfo> ri = data.createTypedArrayList(ResultInfo.CREATOR); + List<Intent> pi = data.createTypedArrayList(Intent.CREATOR); + boolean notResumed = data.readInt() != 0; + boolean isForward = data.readInt() != 0; + scheduleLaunchActivity(intent, b, info, state, ri, pi, notResumed, isForward); + return true; + } + + case SCHEDULE_RELAUNCH_ACTIVITY_TRANSACTION: + { + data.enforceInterface(IApplicationThread.descriptor); + IBinder b = data.readStrongBinder(); + List<ResultInfo> ri = data.createTypedArrayList(ResultInfo.CREATOR); + List<Intent> pi = data.createTypedArrayList(Intent.CREATOR); + int configChanges = data.readInt(); + boolean notResumed = data.readInt() != 0; + scheduleRelaunchActivity(b, ri, pi, configChanges, notResumed); + return true; + } + + case SCHEDULE_NEW_INTENT_TRANSACTION: + { + data.enforceInterface(IApplicationThread.descriptor); + List<Intent> pi = data.createTypedArrayList(Intent.CREATOR); + IBinder b = data.readStrongBinder(); + scheduleNewIntent(pi, b); + return true; + } + + case SCHEDULE_FINISH_ACTIVITY_TRANSACTION: + { + data.enforceInterface(IApplicationThread.descriptor); + IBinder b = data.readStrongBinder(); + boolean finishing = data.readInt() != 0; + int configChanges = data.readInt(); + scheduleDestroyActivity(b, finishing, configChanges); + return true; + } + + case SCHEDULE_RECEIVER_TRANSACTION: + { + data.enforceInterface(IApplicationThread.descriptor); + Intent intent = Intent.CREATOR.createFromParcel(data); + ActivityInfo info = ActivityInfo.CREATOR.createFromParcel(data); + int resultCode = data.readInt(); + String resultData = data.readString(); + Bundle resultExtras = data.readBundle(); + boolean sync = data.readInt() != 0; + scheduleReceiver(intent, info, resultCode, resultData, + resultExtras, sync); + return true; + } + + case SCHEDULE_CREATE_SERVICE_TRANSACTION: { + data.enforceInterface(IApplicationThread.descriptor); + IBinder token = data.readStrongBinder(); + ServiceInfo info = ServiceInfo.CREATOR.createFromParcel(data); + scheduleCreateService(token, info); + return true; + } + + case SCHEDULE_BIND_SERVICE_TRANSACTION: { + data.enforceInterface(IApplicationThread.descriptor); + IBinder token = data.readStrongBinder(); + Intent intent = Intent.CREATOR.createFromParcel(data); + boolean rebind = data.readInt() != 0; + scheduleBindService(token, intent, rebind); + return true; + } + + case SCHEDULE_UNBIND_SERVICE_TRANSACTION: { + data.enforceInterface(IApplicationThread.descriptor); + IBinder token = data.readStrongBinder(); + Intent intent = Intent.CREATOR.createFromParcel(data); + scheduleUnbindService(token, intent); + return true; + } + + case SCHEDULE_SERVICE_ARGS_TRANSACTION: + { + data.enforceInterface(IApplicationThread.descriptor); + IBinder token = data.readStrongBinder(); + int startId = data.readInt(); + Intent args = Intent.CREATOR.createFromParcel(data); + scheduleServiceArgs(token, startId, args); + return true; + } + + case SCHEDULE_STOP_SERVICE_TRANSACTION: + { + data.enforceInterface(IApplicationThread.descriptor); + IBinder token = data.readStrongBinder(); + scheduleStopService(token); + return true; + } + + case BIND_APPLICATION_TRANSACTION: + { + data.enforceInterface(IApplicationThread.descriptor); + String packageName = data.readString(); + ApplicationInfo info = + ApplicationInfo.CREATOR.createFromParcel(data); + List<ProviderInfo> providers = + data.createTypedArrayList(ProviderInfo.CREATOR); + ComponentName testName = (data.readInt() != 0) + ? new ComponentName(data) : null; + String profileName = data.readString(); + Bundle testArgs = data.readBundle(); + IBinder binder = data.readStrongBinder(); + IInstrumentationWatcher testWatcher = IInstrumentationWatcher.Stub.asInterface(binder); + int testMode = data.readInt(); + Configuration config = Configuration.CREATOR.createFromParcel(data); + HashMap<String, IBinder> services = data.readHashMap(null); + bindApplication(packageName, info, + providers, testName, profileName, + testArgs, testWatcher, testMode, config, services); + return true; + } + + case SCHEDULE_EXIT_TRANSACTION: + { + data.enforceInterface(IApplicationThread.descriptor); + scheduleExit(); + return true; + } + + case REQUEST_THUMBNAIL_TRANSACTION: + { + data.enforceInterface(IApplicationThread.descriptor); + IBinder b = data.readStrongBinder(); + requestThumbnail(b); + return true; + } + + case SCHEDULE_CONFIGURATION_CHANGED_TRANSACTION: + { + data.enforceInterface(IApplicationThread.descriptor); + Configuration config = Configuration.CREATOR.createFromParcel(data); + scheduleConfigurationChanged(config); + return true; + } + + case UPDATE_TIME_ZONE_TRANSACTION: { + data.enforceInterface(IApplicationThread.descriptor); + updateTimeZone(); + return true; + } + + case PROCESS_IN_BACKGROUND_TRANSACTION: { + data.enforceInterface(IApplicationThread.descriptor); + processInBackground(); + return true; + } + + case DUMP_SERVICE_TRANSACTION: { + data.enforceInterface(IApplicationThread.descriptor); + ParcelFileDescriptor fd = data.readFileDescriptor(); + final IBinder service = data.readStrongBinder(); + final String[] args = data.readStringArray(); + if (fd != null) { + dumpService(fd.getFileDescriptor(), service, args); + try { + fd.close(); + } catch (IOException e) { + } + } + return true; + } + + case SCHEDULE_REGISTERED_RECEIVER_TRANSACTION: { + data.enforceInterface(IApplicationThread.descriptor); + IIntentReceiver receiver = IIntentReceiver.Stub.asInterface( + data.readStrongBinder()); + Intent intent = Intent.CREATOR.createFromParcel(data); + int resultCode = data.readInt(); + String dataStr = data.readString(); + Bundle extras = data.readBundle(); + boolean ordered = data.readInt() != 0; + scheduleRegisteredReceiver(receiver, intent, + resultCode, dataStr, extras, ordered); + return true; + } + + case SCHEDULE_LOW_MEMORY_TRANSACTION: + { + scheduleLowMemory(); + return true; + } + + case SCHEDULE_ACTIVITY_CONFIGURATION_CHANGED_TRANSACTION: + { + data.enforceInterface(IApplicationThread.descriptor); + IBinder b = data.readStrongBinder(); + scheduleActivityConfigurationChanged(b); + return true; + } + + case REQUEST_PSS_TRANSACTION: + { + data.enforceInterface(IApplicationThread.descriptor); + requestPss(); + return true; + } + } + + return super.onTransact(code, data, reply, flags); + } + + public IBinder asBinder() + { + return this; + } +} + +class ApplicationThreadProxy implements IApplicationThread { + private final IBinder mRemote; + + public ApplicationThreadProxy(IBinder remote) { + mRemote = remote; + } + + public final IBinder asBinder() { + return mRemote; + } + + public final void schedulePauseActivity(IBinder token, boolean finished, + boolean userLeaving, int configChanges) throws RemoteException { + Parcel data = Parcel.obtain(); + data.writeInterfaceToken(IApplicationThread.descriptor); + data.writeStrongBinder(token); + data.writeInt(finished ? 1 : 0); + data.writeInt(userLeaving ? 1 :0); + data.writeInt(configChanges); + mRemote.transact(SCHEDULE_PAUSE_ACTIVITY_TRANSACTION, data, null, + IBinder.FLAG_ONEWAY); + data.recycle(); + } + + public final void scheduleStopActivity(IBinder token, boolean showWindow, + int configChanges) throws RemoteException { + Parcel data = Parcel.obtain(); + data.writeInterfaceToken(IApplicationThread.descriptor); + data.writeStrongBinder(token); + data.writeInt(showWindow ? 1 : 0); + data.writeInt(configChanges); + mRemote.transact(SCHEDULE_STOP_ACTIVITY_TRANSACTION, data, null, + IBinder.FLAG_ONEWAY); + data.recycle(); + } + + public final void scheduleWindowVisibility(IBinder token, + boolean showWindow) throws RemoteException { + Parcel data = Parcel.obtain(); + data.writeInterfaceToken(IApplicationThread.descriptor); + data.writeStrongBinder(token); + data.writeInt(showWindow ? 1 : 0); + mRemote.transact(SCHEDULE_WINDOW_VISIBILITY_TRANSACTION, data, null, + IBinder.FLAG_ONEWAY); + data.recycle(); + } + + public final void scheduleResumeActivity(IBinder token, boolean isForward) + throws RemoteException { + Parcel data = Parcel.obtain(); + data.writeInterfaceToken(IApplicationThread.descriptor); + data.writeStrongBinder(token); + data.writeInt(isForward ? 1 : 0); + mRemote.transact(SCHEDULE_RESUME_ACTIVITY_TRANSACTION, data, null, + IBinder.FLAG_ONEWAY); + data.recycle(); + } + + public final void scheduleSendResult(IBinder token, List<ResultInfo> results) + throws RemoteException { + Parcel data = Parcel.obtain(); + data.writeInterfaceToken(IApplicationThread.descriptor); + data.writeStrongBinder(token); + data.writeTypedList(results); + mRemote.transact(SCHEDULE_SEND_RESULT_TRANSACTION, data, null, + IBinder.FLAG_ONEWAY); + data.recycle(); + } + + public final void scheduleLaunchActivity(Intent intent, IBinder token, + ActivityInfo info, Bundle state, List<ResultInfo> pendingResults, + List<Intent> pendingNewIntents, boolean notResumed, boolean isForward) + throws RemoteException { + Parcel data = Parcel.obtain(); + data.writeInterfaceToken(IApplicationThread.descriptor); + intent.writeToParcel(data, 0); + data.writeStrongBinder(token); + info.writeToParcel(data, 0); + data.writeBundle(state); + data.writeTypedList(pendingResults); + data.writeTypedList(pendingNewIntents); + data.writeInt(notResumed ? 1 : 0); + data.writeInt(isForward ? 1 : 0); + mRemote.transact(SCHEDULE_LAUNCH_ACTIVITY_TRANSACTION, data, null, + IBinder.FLAG_ONEWAY); + data.recycle(); + } + + public final void scheduleRelaunchActivity(IBinder token, + List<ResultInfo> pendingResults, List<Intent> pendingNewIntents, + int configChanges, boolean notResumed) throws RemoteException { + Parcel data = Parcel.obtain(); + data.writeInterfaceToken(IApplicationThread.descriptor); + data.writeStrongBinder(token); + data.writeTypedList(pendingResults); + data.writeTypedList(pendingNewIntents); + data.writeInt(configChanges); + data.writeInt(notResumed ? 1 : 0); + mRemote.transact(SCHEDULE_RELAUNCH_ACTIVITY_TRANSACTION, data, null, + IBinder.FLAG_ONEWAY); + data.recycle(); + } + + public void scheduleNewIntent(List<Intent> intents, IBinder token) + throws RemoteException { + Parcel data = Parcel.obtain(); + data.writeInterfaceToken(IApplicationThread.descriptor); + data.writeTypedList(intents); + data.writeStrongBinder(token); + mRemote.transact(SCHEDULE_NEW_INTENT_TRANSACTION, data, null, + IBinder.FLAG_ONEWAY); + data.recycle(); + } + + public final void scheduleDestroyActivity(IBinder token, boolean finishing, + int configChanges) throws RemoteException { + Parcel data = Parcel.obtain(); + data.writeInterfaceToken(IApplicationThread.descriptor); + data.writeStrongBinder(token); + data.writeInt(finishing ? 1 : 0); + data.writeInt(configChanges); + mRemote.transact(SCHEDULE_FINISH_ACTIVITY_TRANSACTION, data, null, + IBinder.FLAG_ONEWAY); + data.recycle(); + } + + public final void scheduleReceiver(Intent intent, ActivityInfo info, + int resultCode, String resultData, + Bundle map, boolean sync) throws RemoteException { + Parcel data = Parcel.obtain(); + data.writeInterfaceToken(IApplicationThread.descriptor); + intent.writeToParcel(data, 0); + info.writeToParcel(data, 0); + data.writeInt(resultCode); + data.writeString(resultData); + data.writeBundle(map); + data.writeInt(sync ? 1 : 0); + mRemote.transact(SCHEDULE_RECEIVER_TRANSACTION, data, null, + IBinder.FLAG_ONEWAY); + data.recycle(); + } + + public final void scheduleCreateService(IBinder token, ServiceInfo info) + throws RemoteException { + Parcel data = Parcel.obtain(); + data.writeInterfaceToken(IApplicationThread.descriptor); + data.writeStrongBinder(token); + info.writeToParcel(data, 0); + mRemote.transact(SCHEDULE_CREATE_SERVICE_TRANSACTION, data, null, + IBinder.FLAG_ONEWAY); + data.recycle(); + } + + public final void scheduleBindService(IBinder token, Intent intent, boolean rebind) + throws RemoteException { + Parcel data = Parcel.obtain(); + data.writeInterfaceToken(IApplicationThread.descriptor); + data.writeStrongBinder(token); + intent.writeToParcel(data, 0); + data.writeInt(rebind ? 1 : 0); + mRemote.transact(SCHEDULE_BIND_SERVICE_TRANSACTION, data, null, + IBinder.FLAG_ONEWAY); + data.recycle(); + } + + public final void scheduleUnbindService(IBinder token, Intent intent) + throws RemoteException { + Parcel data = Parcel.obtain(); + data.writeInterfaceToken(IApplicationThread.descriptor); + data.writeStrongBinder(token); + intent.writeToParcel(data, 0); + mRemote.transact(SCHEDULE_UNBIND_SERVICE_TRANSACTION, data, null, + IBinder.FLAG_ONEWAY); + data.recycle(); + } + + public final void scheduleServiceArgs(IBinder token, int startId, + Intent args) throws RemoteException { + Parcel data = Parcel.obtain(); + data.writeInterfaceToken(IApplicationThread.descriptor); + data.writeStrongBinder(token); + data.writeInt(startId); + args.writeToParcel(data, 0); + mRemote.transact(SCHEDULE_SERVICE_ARGS_TRANSACTION, data, null, + IBinder.FLAG_ONEWAY); + data.recycle(); + } + + public final void scheduleStopService(IBinder token) + throws RemoteException { + Parcel data = Parcel.obtain(); + data.writeInterfaceToken(IApplicationThread.descriptor); + data.writeStrongBinder(token); + mRemote.transact(SCHEDULE_STOP_SERVICE_TRANSACTION, data, null, + IBinder.FLAG_ONEWAY); + data.recycle(); + } + + public final void bindApplication(String packageName, ApplicationInfo info, + List<ProviderInfo> providers, ComponentName testName, + String profileName, Bundle testArgs, IInstrumentationWatcher testWatcher, int debugMode, + Configuration config, Map<String, IBinder> services) throws RemoteException { + Parcel data = Parcel.obtain(); + data.writeInterfaceToken(IApplicationThread.descriptor); + data.writeString(packageName); + info.writeToParcel(data, 0); + data.writeTypedList(providers); + if (testName == null) { + data.writeInt(0); + } else { + data.writeInt(1); + testName.writeToParcel(data, 0); + } + data.writeString(profileName); + data.writeBundle(testArgs); + data.writeStrongInterface(testWatcher); + data.writeInt(debugMode); + config.writeToParcel(data, 0); + data.writeMap(services); + mRemote.transact(BIND_APPLICATION_TRANSACTION, data, null, + IBinder.FLAG_ONEWAY); + data.recycle(); + } + + public final void scheduleExit() throws RemoteException { + Parcel data = Parcel.obtain(); + data.writeInterfaceToken(IApplicationThread.descriptor); + mRemote.transact(SCHEDULE_EXIT_TRANSACTION, data, null, + IBinder.FLAG_ONEWAY); + data.recycle(); + } + + public final void requestThumbnail(IBinder token) + throws RemoteException { + Parcel data = Parcel.obtain(); + data.writeInterfaceToken(IApplicationThread.descriptor); + data.writeStrongBinder(token); + mRemote.transact(REQUEST_THUMBNAIL_TRANSACTION, data, null, + IBinder.FLAG_ONEWAY); + data.recycle(); + } + + public final void scheduleConfigurationChanged(Configuration config) + throws RemoteException { + Parcel data = Parcel.obtain(); + data.writeInterfaceToken(IApplicationThread.descriptor); + config.writeToParcel(data, 0); + mRemote.transact(SCHEDULE_CONFIGURATION_CHANGED_TRANSACTION, data, null, + IBinder.FLAG_ONEWAY); + data.recycle(); + } + + public void updateTimeZone() throws RemoteException { + Parcel data = Parcel.obtain(); + data.writeInterfaceToken(IApplicationThread.descriptor); + mRemote.transact(UPDATE_TIME_ZONE_TRANSACTION, data, null, + IBinder.FLAG_ONEWAY); + data.recycle(); + } + + public void processInBackground() throws RemoteException { + Parcel data = Parcel.obtain(); + data.writeInterfaceToken(IApplicationThread.descriptor); + mRemote.transact(PROCESS_IN_BACKGROUND_TRANSACTION, data, null, + IBinder.FLAG_ONEWAY); + data.recycle(); + } + + public void dumpService(FileDescriptor fd, IBinder token, String[] args) + throws RemoteException { + Parcel data = Parcel.obtain(); + data.writeInterfaceToken(IApplicationThread.descriptor); + data.writeFileDescriptor(fd); + data.writeStrongBinder(token); + data.writeStringArray(args); + mRemote.transact(DUMP_SERVICE_TRANSACTION, data, null, 0); + data.recycle(); + } + + public void scheduleRegisteredReceiver(IIntentReceiver receiver, Intent intent, + int resultCode, String dataStr, Bundle extras, boolean ordered) + throws RemoteException { + Parcel data = Parcel.obtain(); + data.writeInterfaceToken(IApplicationThread.descriptor); + data.writeStrongBinder(receiver.asBinder()); + intent.writeToParcel(data, 0); + data.writeInt(resultCode); + data.writeString(dataStr); + data.writeBundle(extras); + data.writeInt(ordered ? 1 : 0); + mRemote.transact(SCHEDULE_REGISTERED_RECEIVER_TRANSACTION, data, null, + IBinder.FLAG_ONEWAY); + data.recycle(); + } + + public final void scheduleLowMemory() throws RemoteException { + Parcel data = Parcel.obtain(); + data.writeInterfaceToken(IApplicationThread.descriptor); + mRemote.transact(SCHEDULE_LOW_MEMORY_TRANSACTION, data, null, + IBinder.FLAG_ONEWAY); + data.recycle(); + } + + public final void scheduleActivityConfigurationChanged( + IBinder token) throws RemoteException { + Parcel data = Parcel.obtain(); + data.writeInterfaceToken(IApplicationThread.descriptor); + data.writeStrongBinder(token); + mRemote.transact(SCHEDULE_ACTIVITY_CONFIGURATION_CHANGED_TRANSACTION, data, null, + IBinder.FLAG_ONEWAY); + data.recycle(); + } + + public final void requestPss() throws RemoteException { + Parcel data = Parcel.obtain(); + data.writeInterfaceToken(IApplicationThread.descriptor); + mRemote.transact(REQUEST_PSS_TRANSACTION, data, null, + IBinder.FLAG_ONEWAY); + data.recycle(); + } + +} + diff --git a/core/java/android/app/DatePickerDialog.java b/core/java/android/app/DatePickerDialog.java new file mode 100644 index 0000000..ee5e0d5 --- /dev/null +++ b/core/java/android/app/DatePickerDialog.java @@ -0,0 +1,185 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * 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 android.app; + +import android.content.Context; +import android.content.DialogInterface; +import android.content.DialogInterface.OnClickListener; +import android.os.Bundle; +import android.text.TextUtils.TruncateAt; +import android.text.format.DateFormat; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.DatePicker; +import android.widget.TextView; +import android.widget.DatePicker.OnDateChangedListener; + +import com.android.internal.R; + +import java.text.DateFormatSymbols; +import java.util.Calendar; + +/** + * A simple dialog containing an {@link android.widget.DatePicker}. + */ +public class DatePickerDialog extends AlertDialog implements OnClickListener, + OnDateChangedListener { + + private static final String YEAR = "year"; + private static final String MONTH = "month"; + private static final String DAY = "day"; + + private final DatePicker mDatePicker; + private final OnDateSetListener mCallBack; + private final Calendar mCalendar; + private final java.text.DateFormat mDateFormat; + private final String[] mWeekDays; + + private int mInitialYear; + private int mInitialMonth; + private int mInitialDay; + + /** + * The callback used to indicate the user is done filling in the date. + */ + public interface OnDateSetListener { + + /** + * @param view The view associated with this listener. + * @param year The year that was set. + * @param monthOfYear The month that was set (0-11) for compatibility + * with {@link java.util.Calendar}. + * @param dayOfMonth The day of the month that was set. + */ + void onDateSet(DatePicker view, int year, int monthOfYear, int dayOfMonth); + } + + /** + * @param context The context the dialog is to run in. + * @param callBack How the parent is notified that the date is set. + * @param year The initial year of the dialog. + * @param monthOfYear The initial month of the dialog. + * @param dayOfMonth The initial day of the dialog. + */ + public DatePickerDialog(Context context, + OnDateSetListener callBack, + int year, + int monthOfYear, + int dayOfMonth) { + this(context, com.android.internal.R.style.Theme_Dialog_Alert, + callBack, year, monthOfYear, dayOfMonth); + } + + /** + * @param context The context the dialog is to run in. + * @param theme the theme to apply to this dialog + * @param callBack How the parent is notified that the date is set. + * @param year The initial year of the dialog. + * @param monthOfYear The initial month of the dialog. + * @param dayOfMonth The initial day of the dialog. + */ + public DatePickerDialog(Context context, + int theme, + OnDateSetListener callBack, + int year, + int monthOfYear, + int dayOfMonth) { + super(context, theme); + + mCallBack = callBack; + mInitialYear = year; + mInitialMonth = monthOfYear; + mInitialDay = dayOfMonth; + DateFormatSymbols symbols = new DateFormatSymbols(); + mWeekDays = symbols.getShortWeekdays(); + + mDateFormat = DateFormat.getMediumDateFormat(context); + mCalendar = Calendar.getInstance(); + updateTitle(mInitialYear, mInitialMonth, mInitialDay); + + setButton(context.getText(R.string.date_time_set), this); + setButton2(context.getText(R.string.cancel), (OnClickListener) null); + setIcon(R.drawable.ic_dialog_time); + + LayoutInflater inflater = + (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); + View view = inflater.inflate(R.layout.date_picker_dialog, null); + setView(view); + mDatePicker = (DatePicker) view.findViewById(R.id.datePicker); + mDatePicker.init(mInitialYear, mInitialMonth, mInitialDay, this); + } + + @Override + public void show() { + super.show(); + + /* Sometimes the full month is displayed causing the title + * to be very long, in those cases ensure it doesn't wrap to + * 2 lines (as that looks jumpy) and ensure we ellipsize the end. + */ + TextView title = (TextView) findViewById(R.id.alertTitle); + title.setSingleLine(); + title.setEllipsize(TruncateAt.END); + } + + public void onClick(DialogInterface dialog, int which) { + if (mCallBack != null) { + mDatePicker.clearFocus(); + mCallBack.onDateSet(mDatePicker, mDatePicker.getYear(), + mDatePicker.getMonth(), mDatePicker.getDayOfMonth()); + } + } + + public void onDateChanged(DatePicker view, int year, + int month, int day) { + updateTitle(year, month, day); + } + + public void updateDate(int year, int monthOfYear, int dayOfMonth) { + mInitialYear = year; + mInitialMonth = monthOfYear; + mInitialDay = dayOfMonth; + mDatePicker.updateDate(year, monthOfYear, dayOfMonth); + } + + private void updateTitle(int year, int month, int day) { + mCalendar.set(Calendar.YEAR, year); + mCalendar.set(Calendar.MONTH, month); + mCalendar.set(Calendar.DAY_OF_MONTH, day); + String weekday = mWeekDays[mCalendar.get(Calendar.DAY_OF_WEEK)]; + setTitle(weekday + ", " + mDateFormat.format(mCalendar.getTime())); + } + + @Override + public Bundle onSaveInstanceState() { + Bundle state = super.onSaveInstanceState(); + state.putInt(YEAR, mDatePicker.getYear()); + state.putInt(MONTH, mDatePicker.getMonth()); + state.putInt(DAY, mDatePicker.getDayOfMonth()); + return state; + } + + @Override + public void onRestoreInstanceState(Bundle savedInstanceState) { + super.onRestoreInstanceState(savedInstanceState); + int year = savedInstanceState.getInt(YEAR); + int month = savedInstanceState.getInt(MONTH); + int day = savedInstanceState.getInt(DAY); + mDatePicker.init(year, month, day, this); + updateTitle(year, month, day); + } +} diff --git a/core/java/android/app/Dialog.java b/core/java/android/app/Dialog.java new file mode 100644 index 0000000..b09a57f --- /dev/null +++ b/core/java/android/app/Dialog.java @@ -0,0 +1,954 @@ +/* + * Copyright (C) 2006 The Android Open Source Project + * + * 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 android.app; + +import android.content.Context; +import android.content.DialogInterface; +import android.graphics.drawable.Drawable; +import android.net.Uri; +import android.os.Handler; +import android.os.Message; +import android.os.Bundle; +import android.util.Config; +import android.util.Log; +import android.view.ContextMenu; +import android.view.ContextThemeWrapper; +import android.view.Gravity; +import android.view.KeyEvent; +import android.view.Menu; +import android.view.MenuItem; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewConfiguration; +import android.view.ViewGroup; +import android.view.LayoutInflater; +import android.view.Window; +import android.view.WindowManager; +import android.view.ContextMenu.ContextMenuInfo; +import android.view.View.OnCreateContextMenuListener; + +import com.android.internal.policy.PolicyManager; + +import java.lang.ref.WeakReference; + +/** + * Base class for Dialogs. + * + * <p>Note: Activities provide a facility to manage the creation, saving and + * restoring of dialogs. See {@link Activity#onCreateDialog(int)}, + * {@link Activity#onPrepareDialog(int, Dialog)}, + * {@link Activity#showDialog(int)}, and {@link Activity#dismissDialog(int)}. If + * these methods are used, {@link #getOwnerActivity()} will return the Activity + * that managed this dialog. + * + * <p>Often you will want to have a Dialog display on top of the current + * input method, because there is no reason for it to accept text. You can + * do this by setting the {@link WindowManager.LayoutParams#FLAG_ALT_FOCUSABLE_IM + * WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM} window flag (assuming + * your Dialog takes input focus, as it the default) with the following code: + * + * <pre> + * getWindow().setFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM, + * WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM); + * </pre> + */ +public class Dialog implements DialogInterface, Window.Callback, + KeyEvent.Callback, OnCreateContextMenuListener { + private static final String LOG_TAG = "Dialog"; + + private Activity mOwnerActivity; + + final Context mContext; + final WindowManager mWindowManager; + Window mWindow; + View mDecor; + /** + * This field should be made private, so it is hidden from the SDK. + * {@hide} + */ + protected boolean mCancelable = true; + private Message mCancelMessage; + private Message mDismissMessage; + + /** + * Whether to cancel the dialog when a touch is received outside of the + * window's bounds. + */ + private boolean mCanceledOnTouchOutside = false; + + private OnKeyListener mOnKeyListener; + + private boolean mCreated = false; + private boolean mShowing = false; + + private final Thread mUiThread; + private final Handler mHandler = new Handler(); + + private final Runnable mDismissAction = new Runnable() { + public void run() { + dismissDialog(); + } + }; + + /** + * Create a Dialog window that uses the default dialog frame style. + * + * @param context The Context the Dialog is to run it. In particular, it + * uses the window manager and theme in this context to + * present its UI. + */ + public Dialog(Context context) { + this(context, 0); + } + + /** + * Create a Dialog window that uses a custom dialog style. + * + * @param context The Context in which the Dialog should run. In particular, it + * uses the window manager and theme from this context to + * present its UI. + * @param theme A style resource describing the theme to use for the + * window. See <a href="{@docRoot}guide/topics/resources/available-resources.html#stylesandthemes">Style + * and Theme Resources</a> for more information about defining and using + * styles. This theme is applied on top of the current theme in + * <var>context</var>. If 0, the default dialog theme will be used. + */ + public Dialog(Context context, int theme) { + mContext = new ContextThemeWrapper( + context, theme == 0 ? com.android.internal.R.style.Theme_Dialog : theme); + mWindowManager = (WindowManager)context.getSystemService("window"); + Window w = PolicyManager.makeNewWindow(mContext); + mWindow = w; + w.setCallback(this); + w.setWindowManager(mWindowManager, null, null); + w.setGravity(Gravity.CENTER); + mUiThread = Thread.currentThread(); + mDismissCancelHandler = new DismissCancelHandler(this); + } + + /** + * @deprecated + * @hide + */ + @Deprecated + protected Dialog(Context context, boolean cancelable, + Message cancelCallback) { + this(context); + mCancelable = cancelable; + mCancelMessage = cancelCallback; + } + + protected Dialog(Context context, boolean cancelable, + OnCancelListener cancelListener) { + this(context); + mCancelable = cancelable; + setOnCancelListener(cancelListener); + } + + /** + * Retrieve the Context this Dialog is running in. + * + * @return Context The Context that was supplied to the constructor. + */ + public final Context getContext() { + return mContext; + } + + /** + * Sets the Activity that owns this dialog. An example use: This Dialog will + * use the suggested volume control stream of the Activity. + * + * @param activity The Activity that owns this dialog. + */ + public final void setOwnerActivity(Activity activity) { + mOwnerActivity = activity; + + getWindow().setVolumeControlStream(mOwnerActivity.getVolumeControlStream()); + } + + /** + * Returns the Activity that owns this Dialog. For example, if + * {@link Activity#showDialog(int)} is used to show this Dialog, that + * Activity will be the owner (by default). Depending on how this dialog was + * created, this may return null. + * + * @return The Activity that owns this Dialog. + */ + public final Activity getOwnerActivity() { + return mOwnerActivity; + } + + /** + * @return Whether the dialog is currently showing. + */ + public boolean isShowing() { + return mShowing; + } + + /** + * Start the dialog and display it on screen. The window is placed in the + * application layer and opaque. Note that you should not override this + * method to do initialization when the dialog is shown, instead implement + * that in {@link #onStart}. + */ + public void show() { + if (mShowing) { + if (Config.LOGV) Log.v(LOG_TAG, + "[Dialog] start: already showing, ignore"); + if (mDecor != null) mDecor.setVisibility(View.VISIBLE); + return; + } + + if (!mCreated) { + dispatchOnCreate(null); + } + + onStart(); + mDecor = mWindow.getDecorView(); + WindowManager.LayoutParams l = mWindow.getAttributes(); + if ((l.softInputMode + & WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) == 0) { + WindowManager.LayoutParams nl = new WindowManager.LayoutParams(); + nl.copyFrom(l); + nl.softInputMode |= + WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION; + l = nl; + } + mWindowManager.addView(mDecor, l); + mShowing = true; + } + + /** + * Hide the dialog, but do not dismiss it. + */ + public void hide() { + if (mDecor != null) mDecor.setVisibility(View.GONE); + } + + /** + * Dismiss this dialog, removing it from the screen. This method can be + * invoked safely from any thread. Note that you should not override this + * method to do cleanup when the dialog is dismissed, instead implement + * that in {@link #onStop}. + */ + public void dismiss() { + if (Thread.currentThread() != mUiThread) { + mHandler.post(mDismissAction); + } else { + mDismissAction.run(); + } + } + + private void dismissDialog() { + if (mDecor == null) { + if (Config.LOGV) Log.v(LOG_TAG, + "[Dialog] dismiss: already dismissed, ignore"); + return; + } + if (!mShowing) { + if (Config.LOGV) Log.v(LOG_TAG, + "[Dialog] dismiss: not showing, ignore"); + return; + } + + mWindowManager.removeView(mDecor); + mDecor = null; + mWindow.closeAllPanels(); + onStop(); + mShowing = false; + + sendDismissMessage(); + } + + private void sendDismissMessage() { + if (mDismissMessage != null) { + // Obtain a new message so this dialog can be re-used + Message.obtain(mDismissMessage).sendToTarget(); + } + } + + // internal method to make sure mcreated is set properly without requiring + // users to call through to super in onCreate + void dispatchOnCreate(Bundle savedInstanceState) { + onCreate(savedInstanceState); + mCreated = true; + } + + /** + * Similar to {@link Activity#onCreate}, you should initialized your dialog + * in this method, including calling {@link #setContentView}. + * @param savedInstanceState If this dialog is being reinitalized after a + * the hosting activity was previously shut down, holds the result from + * the most recent call to {@link #onSaveInstanceState}, or null if this + * is the first time. + */ + protected void onCreate(Bundle savedInstanceState) { + } + + /** + * Called when the dialog is starting. + */ + protected void onStart() { + } + + /** + * Called to tell you that you're stopping. + */ + protected void onStop() { + } + + private static final String DIALOG_SHOWING_TAG = "android:dialogShowing"; + private static final String DIALOG_HIERARCHY_TAG = "android:dialogHierarchy"; + + /** + * Saves the state of the dialog into a bundle. + * + * The default implementation saves the state of its view hierarchy, so you'll + * likely want to call through to super if you override this to save additional + * state. + * @return A bundle with the state of the dialog. + */ + public Bundle onSaveInstanceState() { + Bundle bundle = new Bundle(); + bundle.putBoolean(DIALOG_SHOWING_TAG, mShowing); + if (mCreated) { + bundle.putBundle(DIALOG_HIERARCHY_TAG, mWindow.saveHierarchyState()); + } + return bundle; + } + + /** + * Restore the state of the dialog from a previously saved bundle. + * + * The default implementation restores the state of the dialog's view + * hierarchy that was saved in the default implementation of {@link #onSaveInstanceState()}, + * so be sure to call through to super when overriding unless you want to + * do all restoring of state yourself. + * @param savedInstanceState The state of the dialog previously saved by + * {@link #onSaveInstanceState()}. + */ + public void onRestoreInstanceState(Bundle savedInstanceState) { + final Bundle dialogHierarchyState = savedInstanceState.getBundle(DIALOG_HIERARCHY_TAG); + if (dialogHierarchyState == null) { + // dialog has never been shown, or onCreated, nothing to restore. + return; + } + dispatchOnCreate(savedInstanceState); + mWindow.restoreHierarchyState(dialogHierarchyState); + if (savedInstanceState.getBoolean(DIALOG_SHOWING_TAG)) { + show(); + } + } + + /** + * Retrieve the current Window for the activity. This can be used to + * directly access parts of the Window API that are not available + * through Activity/Screen. + * + * @return Window The current window, or null if the activity is not + * visual. + */ + public Window getWindow() { + return mWindow; + } + + /** + * Call {@link android.view.Window#getCurrentFocus} on the + * Window if this Activity to return the currently focused view. + * + * @return View The current View with focus or null. + * + * @see #getWindow + * @see android.view.Window#getCurrentFocus + */ + public View getCurrentFocus() { + return mWindow != null ? mWindow.getCurrentFocus() : null; + } + + /** + * Finds a view that was identified by the id attribute from the XML that + * was processed in {@link #onStart}. + * + * @param id the identifier of the view to find + * @return The view if found or null otherwise. + */ + public View findViewById(int id) { + return mWindow.findViewById(id); + } + + /** + * Set the screen content from a layout resource. The resource will be + * inflated, adding all top-level views to the screen. + * + * @param layoutResID Resource ID to be inflated. + */ + public void setContentView(int layoutResID) { + mWindow.setContentView(layoutResID); + } + + /** + * Set the screen content to an explicit view. This view is placed + * directly into the screen's view hierarchy. It can itself be a complex + * view hierarhcy. + * + * @param view The desired content to display. + */ + public void setContentView(View view) { + mWindow.setContentView(view); + } + + /** + * Set the screen content to an explicit view. This view is placed + * directly into the screen's view hierarchy. It can itself be a complex + * view hierarhcy. + * + * @param view The desired content to display. + * @param params Layout parameters for the view. + */ + public void setContentView(View view, ViewGroup.LayoutParams params) { + mWindow.setContentView(view, params); + } + + /** + * Add an additional content view to the screen. Added after any existing + * ones in the screen -- existing views are NOT removed. + * + * @param view The desired content to display. + * @param params Layout parameters for the view. + */ + public void addContentView(View view, ViewGroup.LayoutParams params) { + mWindow.addContentView(view, params); + } + + /** + * Set the title text for this dialog's window. + * + * @param title The new text to display in the title. + */ + public void setTitle(CharSequence title) { + mWindow.setTitle(title); + mWindow.getAttributes().setTitle(title); + } + + /** + * Set the title text for this dialog's window. The text is retrieved + * from the resources with the supplied identifier. + * + * @param titleId the title's text resource identifier + */ + public void setTitle(int titleId) { + setTitle(mContext.getText(titleId)); + } + + /** + * A key was pressed down. + * + * <p>If the focused view didn't want this event, this method is called. + * + * <p>The default implementation handles KEYCODE_BACK to close the + * dialog. + * + * @see #onKeyUp + * @see android.view.KeyEvent + */ + public boolean onKeyDown(int keyCode, KeyEvent event) { + if (keyCode == KeyEvent.KEYCODE_BACK) { + if (mCancelable) { + cancel(); + } + return true; + } + + return false; + } + + /** + * A key was released. + * + * @see #onKeyDown + * @see KeyEvent + */ + public boolean onKeyUp(int keyCode, KeyEvent event) { + return false; + } + + /** + * Default implementation of {@link KeyEvent.Callback#onKeyMultiple(int, int, KeyEvent) + * KeyEvent.Callback.onKeyMultiple()}: always returns false (doesn't handle + * the event). + */ + public boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event) { + return false; + } + + /** + * Called when a touch screen event was not handled by any of the views + * under it. This is most useful to process touch events that happen outside + * of your window bounds, where there is no view to receive it. + * + * @param event The touch screen event being processed. + * @return Return true if you have consumed the event, false if you haven't. + * The default implementation will cancel the dialog when a touch + * happens outside of the window bounds. + */ + public boolean onTouchEvent(MotionEvent event) { + if (mCancelable && mCanceledOnTouchOutside && event.getAction() == MotionEvent.ACTION_DOWN + && isOutOfBounds(event)) { + cancel(); + return true; + } + + return false; + } + + private boolean isOutOfBounds(MotionEvent event) { + final int x = (int) event.getX(); + final int y = (int) event.getY(); + final int slop = ViewConfiguration.get(mContext).getScaledWindowTouchSlop(); + final View decorView = getWindow().getDecorView(); + return (x < -slop) || (y < -slop) + || (x > (decorView.getWidth()+slop)) + || (y > (decorView.getHeight()+slop)); + } + + /** + * Called when the trackball was moved and not handled by any of the + * views inside of the activity. So, for example, if the trackball moves + * while focus is on a button, you will receive a call here because + * buttons do not normally do anything with trackball events. The call + * here happens <em>before</em> trackball movements are converted to + * DPAD key events, which then get sent back to the view hierarchy, and + * will be processed at the point for things like focus navigation. + * + * @param event The trackball event being processed. + * + * @return Return true if you have consumed the event, false if you haven't. + * The default implementation always returns false. + */ + public boolean onTrackballEvent(MotionEvent event) { + return false; + } + + public void onWindowAttributesChanged(WindowManager.LayoutParams params) { + if (mDecor != null) { + mWindowManager.updateViewLayout(mDecor, params); + } + } + + public void onContentChanged() { + } + + public void onWindowFocusChanged(boolean hasFocus) { + } + + /** + * Called to process key events. You can override this to intercept all + * key events before they are dispatched to the window. Be sure to call + * this implementation for key events that should be handled normally. + * + * @param event The key event. + * + * @return boolean Return true if this event was consumed. + */ + public boolean dispatchKeyEvent(KeyEvent event) { + if ((mOnKeyListener != null) && (mOnKeyListener.onKey(this, event.getKeyCode(), event))) { + return true; + } + if (mWindow.superDispatchKeyEvent(event)) { + return true; + } + return event.dispatch(this); + } + + /** + * Called to process touch screen events. You can override this to + * intercept all touch screen events before they are dispatched to the + * window. Be sure to call this implementation for touch screen events + * that should be handled normally. + * + * @param ev The touch screen event. + * + * @return boolean Return true if this event was consumed. + */ + public boolean dispatchTouchEvent(MotionEvent ev) { + if (mWindow.superDispatchTouchEvent(ev)) { + return true; + } + return onTouchEvent(ev); + } + + /** + * Called to process trackball events. You can override this to + * intercept all trackball events before they are dispatched to the + * window. Be sure to call this implementation for trackball events + * that should be handled normally. + * + * @param ev The trackball event. + * + * @return boolean Return true if this event was consumed. + */ + public boolean dispatchTrackballEvent(MotionEvent ev) { + if (mWindow.superDispatchTrackballEvent(ev)) { + return true; + } + return onTrackballEvent(ev); + } + + /** + * @see Activity#onCreatePanelView(int) + */ + public View onCreatePanelView(int featureId) { + return null; + } + + /** + * @see Activity#onCreatePanelMenu(int, Menu) + */ + public boolean onCreatePanelMenu(int featureId, Menu menu) { + if (featureId == Window.FEATURE_OPTIONS_PANEL) { + return onCreateOptionsMenu(menu); + } + + return false; + } + + /** + * @see Activity#onPreparePanel(int, View, Menu) + */ + public boolean onPreparePanel(int featureId, View view, Menu menu) { + if (featureId == Window.FEATURE_OPTIONS_PANEL && menu != null) { + boolean goforit = onPrepareOptionsMenu(menu); + return goforit && menu.hasVisibleItems(); + } + return true; + } + + /** + * @see Activity#onMenuOpened(int, Menu) + */ + public boolean onMenuOpened(int featureId, Menu menu) { + return true; + } + + /** + * @see Activity#onMenuItemSelected(int, MenuItem) + */ + public boolean onMenuItemSelected(int featureId, MenuItem item) { + return false; + } + + /** + * @see Activity#onPanelClosed(int, Menu) + */ + public void onPanelClosed(int featureId, Menu menu) { + } + + /** + * It is usually safe to proxy this call to the owner activity's + * {@link Activity#onCreateOptionsMenu(Menu)} if the client desires the same + * menu for this Dialog. + * + * @see Activity#onCreateOptionsMenu(Menu) + * @see #getOwnerActivity() + */ + public boolean onCreateOptionsMenu(Menu menu) { + return true; + } + + /** + * It is usually safe to proxy this call to the owner activity's + * {@link Activity#onPrepareOptionsMenu(Menu)} if the client desires the + * same menu for this Dialog. + * + * @see Activity#onPrepareOptionsMenu(Menu) + * @see #getOwnerActivity() + */ + public boolean onPrepareOptionsMenu(Menu menu) { + return true; + } + + /** + * @see Activity#onOptionsItemSelected(MenuItem) + */ + public boolean onOptionsItemSelected(MenuItem item) { + return false; + } + + /** + * @see Activity#onOptionsMenuClosed(Menu) + */ + public void onOptionsMenuClosed(Menu menu) { + } + + /** + * @see Activity#openOptionsMenu() + */ + public void openOptionsMenu() { + mWindow.openPanel(Window.FEATURE_OPTIONS_PANEL, null); + } + + /** + * @see Activity#closeOptionsMenu() + */ + public void closeOptionsMenu() { + mWindow.closePanel(Window.FEATURE_OPTIONS_PANEL); + } + + /** + * @see Activity#onCreateContextMenu(ContextMenu, View, ContextMenuInfo) + */ + public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) { + } + + /** + * @see Activity#registerForContextMenu(View) + */ + public void registerForContextMenu(View view) { + view.setOnCreateContextMenuListener(this); + } + + /** + * @see Activity#unregisterForContextMenu(View) + */ + public void unregisterForContextMenu(View view) { + view.setOnCreateContextMenuListener(null); + } + + /** + * @see Activity#openContextMenu(View) + */ + public void openContextMenu(View view) { + view.showContextMenu(); + } + + /** + * @see Activity#onContextItemSelected(MenuItem) + */ + public boolean onContextItemSelected(MenuItem item) { + return false; + } + + /** + * @see Activity#onContextMenuClosed(Menu) + */ + public void onContextMenuClosed(Menu menu) { + } + + /** + * This hook is called when the user signals the desire to start a search. + */ + public boolean onSearchRequested() { + // not during dialogs, no. + return false; + } + + + /** + * Request that key events come to this dialog. Use this if your + * dialog has no views with focus, but the dialog still wants + * a chance to process key events. + * + * @param get true if the dialog should receive key events, false otherwise + * @see android.view.Window#takeKeyEvents + */ + public void takeKeyEvents(boolean get) { + mWindow.takeKeyEvents(get); + } + + /** + * Enable extended window features. This is a convenience for calling + * {@link android.view.Window#requestFeature getWindow().requestFeature()}. + * + * @param featureId The desired feature as defined in + * {@link android.view.Window}. + * @return Returns true if the requested feature is supported and now + * enabled. + * + * @see android.view.Window#requestFeature + */ + public final boolean requestWindowFeature(int featureId) { + return getWindow().requestFeature(featureId); + } + + /** + * Convenience for calling + * {@link android.view.Window#setFeatureDrawableResource}. + */ + public final void setFeatureDrawableResource(int featureId, int resId) { + getWindow().setFeatureDrawableResource(featureId, resId); + } + + /** + * Convenience for calling + * {@link android.view.Window#setFeatureDrawableUri}. + */ + public final void setFeatureDrawableUri(int featureId, Uri uri) { + getWindow().setFeatureDrawableUri(featureId, uri); + } + + /** + * Convenience for calling + * {@link android.view.Window#setFeatureDrawable(int, Drawable)}. + */ + public final void setFeatureDrawable(int featureId, Drawable drawable) { + getWindow().setFeatureDrawable(featureId, drawable); + } + + /** + * Convenience for calling + * {@link android.view.Window#setFeatureDrawableAlpha}. + */ + public final void setFeatureDrawableAlpha(int featureId, int alpha) { + getWindow().setFeatureDrawableAlpha(featureId, alpha); + } + + public LayoutInflater getLayoutInflater() { + return getWindow().getLayoutInflater(); + } + + /** + * Sets whether this dialog is cancelable with the + * {@link KeyEvent#KEYCODE_BACK BACK} key. + */ + public void setCancelable(boolean flag) { + mCancelable = flag; + } + + /** + * Sets whether this dialog is canceled when touched outside the window's + * bounds. If setting to true, the dialog is set to be cancelable if not + * already set. + * + * @param cancel Whether the dialog should be canceled when touched outside + * the window. + */ + public void setCanceledOnTouchOutside(boolean cancel) { + if (cancel && !mCancelable) { + mCancelable = true; + } + + mCanceledOnTouchOutside = cancel; + } + + /** + * Cancel the dialog. This is essentially the same as calling {@link #dismiss()}, but it will + * also call your {@link DialogInterface.OnCancelListener} (if registered). + */ + public void cancel() { + if (mCancelMessage != null) { + + // Obtain a new message so this dialog can be re-used + Message.obtain(mCancelMessage).sendToTarget(); + } + dismiss(); + } + + /** + * Set a listener to be invoked when the dialog is canceled. + * <p> + * This will only be invoked when the dialog is canceled, if the creator + * needs to know when it is dismissed in general, use + * {@link #setOnDismissListener}. + * + * @param listener The {@link DialogInterface.OnCancelListener} to use. + */ + public void setOnCancelListener(final OnCancelListener listener) { + if (listener != null) { + mCancelMessage = mDismissCancelHandler.obtainMessage(CANCEL, listener); + } else { + mCancelMessage = null; + } + } + + /** + * Set a message to be sent when the dialog is canceled. + * @param msg The msg to send when the dialog is canceled. + * @see #setOnCancelListener(android.content.DialogInterface.OnCancelListener) + */ + public void setCancelMessage(final Message msg) { + mCancelMessage = msg; + } + + /** + * Set a listener to be invoked when the dialog is dismissed. + * @param listener The {@link DialogInterface.OnDismissListener} to use. + */ + public void setOnDismissListener(final OnDismissListener listener) { + if (listener != null) { + mDismissMessage = mDismissCancelHandler.obtainMessage(DISMISS, listener); + } else { + mDismissMessage = null; + } + } + + /** + * Set a message to be sent when the dialog is dismissed. + * @param msg The msg to send when the dialog is dismissed. + */ + public void setDismissMessage(final Message msg) { + mDismissMessage = msg; + } + + /** + * By default, this will use the owner Activity's suggested stream type. + * + * @see Activity#setVolumeControlStream(int) + * @see #setOwnerActivity(Activity) + */ + public final void setVolumeControlStream(int streamType) { + getWindow().setVolumeControlStream(streamType); + } + + /** + * @see Activity#getVolumeControlStream() + */ + public final int getVolumeControlStream() { + return getWindow().getVolumeControlStream(); + } + + /** + * Sets the callback that will be called if a key is dispatched to the dialog. + */ + public void setOnKeyListener(final OnKeyListener onKeyListener) { + mOnKeyListener = onKeyListener; + } + + private static final int DISMISS = 0x43; + private static final int CANCEL = 0x44; + + private Handler mDismissCancelHandler; + + private static final class DismissCancelHandler extends Handler { + private WeakReference<DialogInterface> mDialog; + + public DismissCancelHandler(Dialog dialog) { + mDialog = new WeakReference<DialogInterface>(dialog); + } + + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case DISMISS: + ((OnDismissListener) msg.obj).onDismiss(mDialog.get()); + break; + case CANCEL: + ((OnCancelListener) msg.obj).onCancel(mDialog.get()); + break; + } + } + } +} diff --git a/core/java/android/app/ExpandableListActivity.java b/core/java/android/app/ExpandableListActivity.java new file mode 100644 index 0000000..a2e048f --- /dev/null +++ b/core/java/android/app/ExpandableListActivity.java @@ -0,0 +1,324 @@ +/* + * Copyright (C) 2006 The Android Open Source Project + * + * 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 android.app; + +import android.database.Cursor; +import android.os.Bundle; +import java.util.List; +import android.view.ContextMenu; +import android.view.View; +import android.view.ContextMenu.ContextMenuInfo; +import android.view.View.OnCreateContextMenuListener; +import android.widget.ExpandableListAdapter; +import android.widget.ExpandableListView; +import android.widget.SimpleCursorTreeAdapter; +import android.widget.SimpleExpandableListAdapter; +import android.widget.AdapterView.AdapterContextMenuInfo; + +import java.util.Map; + +/** + * An activity that displays an expandable list of items by binding to a data + * source implementing the ExpandableListAdapter, and exposes event handlers + * when the user selects an item. + * <p> + * ExpandableListActivity hosts a + * {@link android.widget.ExpandableListView ExpandableListView} object that can + * be bound to different data sources that provide a two-levels of data (the + * top-level is group, and below each group are children). Binding, screen + * layout, and row layout are discussed in the following sections. + * <p> + * <strong>Screen Layout</strong> + * </p> + * <p> + * ExpandableListActivity has a default layout that consists of a single, + * full-screen, centered expandable list. However, if you desire, you can + * customize the screen layout by setting your own view layout with + * setContentView() in onCreate(). To do this, your own view MUST contain an + * ExpandableListView object with the id "@android:id/list" (or + * {@link android.R.id#list} if it's in code) + * <p> + * Optionally, your custom view can contain another view object of any type to + * display when the list view is empty. This "empty list" notifier must have an + * id "android:empty". Note that when an empty view is present, the expandable + * list view will be hidden when there is no data to display. + * <p> + * The following code demonstrates an (ugly) custom screen layout. It has a list + * with a green background, and an alternate red "no data" message. + * </p> + * + * <pre> + * <?xml version="1.0" encoding="UTF-8"?> + * <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + * android:orientation="vertical" + * android:layout_width="fill_parent" + * android:layout_height="fill_parent" + * android:paddingLeft="8dp" + * android:paddingRight="8dp"> + * + * <ExpandableListView android:id="@id/android:list" + * android:layout_width="fill_parent" + * android:layout_height="fill_parent" + * android:background="#00FF00" + * android:layout_weight="1" + * android:drawSelectorOnTop="false"/> + * + * <TextView android:id="@id/android:empty" + * android:layout_width="fill_parent" + * android:layout_height="fill_parent" + * android:background="#FF0000" + * android:text="No data"/> + * </LinearLayout> + * </pre> + * + * <p> + * <strong>Row Layout</strong> + * </p> + * The {@link ExpandableListAdapter} set in the {@link ExpandableListActivity} + * via {@link #setListAdapter(ExpandableListAdapter)} provides the {@link View}s + * for each row. This adapter has separate methods for providing the group + * {@link View}s and child {@link View}s. There are a couple provided + * {@link ExpandableListAdapter}s that simplify use of adapters: + * {@link SimpleCursorTreeAdapter} and {@link SimpleExpandableListAdapter}. + * <p> + * With these, you can specify the layout of individual rows for groups and + * children in the list. These constructor takes a few parameters that specify + * layout resources for groups and children. It also has additional parameters + * that let you specify which data field to associate with which object in the + * row layout resource. The {@link SimpleCursorTreeAdapter} fetches data from + * {@link Cursor}s and the {@link SimpleExpandableListAdapter} fetches data + * from {@link List}s of {@link Map}s. + * </p> + * <p> + * Android provides some standard row layout resources. These are in the + * {@link android.R.layout} class, and have names such as simple_list_item_1, + * simple_list_item_2, and two_line_list_item. The following layout XML is the + * source for the resource two_line_list_item, which displays two data + * fields,one above the other, for each list row. + * </p> + * + * <pre> + * <?xml version="1.0" encoding="utf-8"?> + * <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + * android:layout_width="fill_parent" + * android:layout_height="wrap_content" + * android:orientation="vertical"> + * + * <TextView android:id="@+id/text1" + * android:textSize="16sp" + * android:textStyle="bold" + * android:layout_width="fill_parent" + * android:layout_height="wrap_content"/> + * + * <TextView android:id="@+id/text2" + * android:textSize="16sp" + * android:layout_width="fill_parent" + * android:layout_height="wrap_content"/> + * </LinearLayout> + * </pre> + * + * <p> + * You must identify the data bound to each TextView object in this layout. The + * syntax for this is discussed in the next section. + * </p> + * <p> + * <strong>Binding to Data</strong> + * </p> + * <p> + * You bind the ExpandableListActivity's ExpandableListView object to data using + * a class that implements the + * {@link android.widget.ExpandableListAdapter ExpandableListAdapter} interface. + * Android provides two standard list adapters: + * {@link android.widget.SimpleExpandableListAdapter SimpleExpandableListAdapter} + * for static data (Maps), and + * {@link android.widget.SimpleCursorTreeAdapter SimpleCursorTreeAdapter} for + * Cursor query results. + * </p> + * + * @see #setListAdapter + * @see android.widget.ExpandableListView + */ +public class ExpandableListActivity extends Activity implements + OnCreateContextMenuListener, + ExpandableListView.OnChildClickListener, ExpandableListView.OnGroupCollapseListener, + ExpandableListView.OnGroupExpandListener { + ExpandableListAdapter mAdapter; + ExpandableListView mList; + boolean mFinishedStart = false; + + /** + * Override this to populate the context menu when an item is long pressed. menuInfo + * will contain an {@link android.widget.ExpandableListView.ExpandableListContextMenuInfo} + * whose packedPosition is a packed position + * that should be used with {@link ExpandableListView#getPackedPositionType(long)} and + * the other similar methods. + * <p> + * {@inheritDoc} + */ + @Override + public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) { + } + + /** + * Override this for receiving callbacks when a child has been clicked. + * <p> + * {@inheritDoc} + */ + public boolean onChildClick(ExpandableListView parent, View v, int groupPosition, + int childPosition, long id) { + return false; + } + + /** + * Override this for receiving callbacks when a group has been collapsed. + */ + public void onGroupCollapse(int groupPosition) { + } + + /** + * Override this for receiving callbacks when a group has been expanded. + */ + public void onGroupExpand(int groupPosition) { + } + + /** + * Ensures the expandable list view has been created before Activity restores all + * of the view states. + * + *@see Activity#onRestoreInstanceState(Bundle) + */ + @Override + protected void onRestoreInstanceState(Bundle state) { + ensureList(); + super.onRestoreInstanceState(state); + } + + /** + * Updates the screen state (current list and other views) when the + * content changes. + * + * @see Activity#onContentChanged() + */ + @Override + public void onContentChanged() { + super.onContentChanged(); + View emptyView = findViewById(com.android.internal.R.id.empty); + mList = (ExpandableListView)findViewById(com.android.internal.R.id.list); + if (mList == null) { + throw new RuntimeException( + "Your content must have a ExpandableListView whose id attribute is " + + "'android.R.id.list'"); + } + if (emptyView != null) { + mList.setEmptyView(emptyView); + } + mList.setOnChildClickListener(this); + mList.setOnGroupExpandListener(this); + mList.setOnGroupCollapseListener(this); + + if (mFinishedStart) { + setListAdapter(mAdapter); + } + mFinishedStart = true; + } + + /** + * Provide the adapter for the expandable list. + */ + public void setListAdapter(ExpandableListAdapter adapter) { + synchronized (this) { + ensureList(); + mAdapter = adapter; + mList.setAdapter(adapter); + } + } + + /** + * Get the activity's expandable list view widget. This can be used to get the selection, + * set the selection, and many other useful functions. + * + * @see ExpandableListView + */ + public ExpandableListView getExpandableListView() { + ensureList(); + return mList; + } + + /** + * Get the ExpandableListAdapter associated with this activity's + * ExpandableListView. + */ + public ExpandableListAdapter getExpandableListAdapter() { + return mAdapter; + } + + private void ensureList() { + if (mList != null) { + return; + } + setContentView(com.android.internal.R.layout.expandable_list_content); + } + + /** + * Gets the ID of the currently selected group or child. + * + * @return The ID of the currently selected group or child. + */ + public long getSelectedId() { + return mList.getSelectedId(); + } + + /** + * Gets the position (in packed position representation) of the currently + * selected group or child. Use + * {@link ExpandableListView#getPackedPositionType}, + * {@link ExpandableListView#getPackedPositionGroup}, and + * {@link ExpandableListView#getPackedPositionChild} to unpack the returned + * packed position. + * + * @return A packed position representation containing the currently + * selected group or child's position and type. + */ + public long getSelectedPosition() { + return mList.getSelectedPosition(); + } + + /** + * Sets the selection to the specified child. If the child is in a collapsed + * group, the group will only be expanded and child subsequently selected if + * shouldExpandGroup is set to true, otherwise the method will return false. + * + * @param groupPosition The position of the group that contains the child. + * @param childPosition The position of the child within the group. + * @param shouldExpandGroup Whether the child's group should be expanded if + * it is collapsed. + * @return Whether the selection was successfully set on the child. + */ + public boolean setSelectedChild(int groupPosition, int childPosition, boolean shouldExpandGroup) { + return mList.setSelectedChild(groupPosition, childPosition, shouldExpandGroup); + } + + /** + * Sets the selection to the specified group. + * @param groupPosition The position of the group that should be selected. + */ + public void setSelectedGroup(int groupPosition) { + mList.setSelectedGroup(groupPosition); + } + +} + diff --git a/core/java/android/app/IActivityManager.java b/core/java/android/app/IActivityManager.java new file mode 100644 index 0000000..cd3701f --- /dev/null +++ b/core/java/android/app/IActivityManager.java @@ -0,0 +1,368 @@ +/* + * Copyright (C) 2006 The Android Open Source Project + * + * 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 android.app; + +import android.app.ActivityManager.MemoryInfo; +import android.content.ComponentName; +import android.content.ContentProviderNative; +import android.content.IContentProvider; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.pm.ConfigurationInfo; +import android.content.pm.IPackageDataObserver; +import android.content.pm.ProviderInfo; +import android.content.res.Configuration; +import android.graphics.Bitmap; +import android.net.Uri; +import android.os.RemoteException; +import android.os.IBinder; +import android.os.IInterface; +import android.os.Parcel; +import android.os.Parcelable; +import android.os.ParcelFileDescriptor; +import android.text.TextUtils; +import android.os.Bundle; + +import java.util.List; + +/** + * System private API for talking with the activity manager service. This + * provides calls from the application back to the activity manager. + * + * {@hide} + */ +public interface IActivityManager extends IInterface { + public static final int START_DELIVERED_TO_TOP = 3; + public static final int START_TASK_TO_FRONT = 2; + public static final int START_RETURN_INTENT_TO_CALLER = 1; + public static final int START_SUCCESS = 0; + public static final int START_INTENT_NOT_RESOLVED = -1; + public static final int START_CLASS_NOT_FOUND = -2; + public static final int START_FORWARD_AND_REQUEST_CONFLICT = -3; + public static final int START_PERMISSION_DENIED = -4; + public int startActivity(IApplicationThread caller, + Intent intent, String resolvedType, Uri[] grantedUriPermissions, + int grantedMode, IBinder resultTo, String resultWho, int requestCode, + boolean onlyIfNeeded, boolean debug) throws RemoteException; + public boolean startNextMatchingActivity(IBinder callingActivity, + Intent intent) throws RemoteException; + public boolean finishActivity(IBinder token, int code, Intent data) + throws RemoteException; + public void finishSubActivity(IBinder token, String resultWho, int requestCode) throws RemoteException; + public Intent registerReceiver(IApplicationThread caller, + IIntentReceiver receiver, IntentFilter filter, + String requiredPermission) throws RemoteException; + public void unregisterReceiver(IIntentReceiver receiver) throws RemoteException; + public static final int BROADCAST_SUCCESS = 0; + public static final int BROADCAST_STICKY_CANT_HAVE_PERMISSION = -1; + public int broadcastIntent(IApplicationThread caller, Intent intent, + String resolvedType, IIntentReceiver resultTo, int resultCode, + String resultData, Bundle map, String requiredPermission, + boolean serialized, boolean sticky) throws RemoteException; + public void unbroadcastIntent(IApplicationThread caller, Intent intent) throws RemoteException; + /* oneway */ + public void finishReceiver(IBinder who, int resultCode, String resultData, Bundle map, boolean abortBroadcast) throws RemoteException; + public void setPersistent(IBinder token, boolean isPersistent) throws RemoteException; + public void attachApplication(IApplicationThread app) throws RemoteException; + /* oneway */ + public void activityIdle(IBinder token) throws RemoteException; + public void activityPaused(IBinder token, Bundle state) throws RemoteException; + /* oneway */ + public void activityStopped(IBinder token, + Bitmap thumbnail, CharSequence description) throws RemoteException; + /* oneway */ + public void activityDestroyed(IBinder token) throws RemoteException; + public String getCallingPackage(IBinder token) throws RemoteException; + public ComponentName getCallingActivity(IBinder token) throws RemoteException; + public List getTasks(int maxNum, int flags, + IThumbnailReceiver receiver) throws RemoteException; + public List<ActivityManager.RecentTaskInfo> getRecentTasks(int maxNum, + int flags) throws RemoteException; + public List getServices(int maxNum, int flags) throws RemoteException; + public List<ActivityManager.ProcessErrorStateInfo> getProcessesInErrorState() + throws RemoteException; + public void moveTaskToFront(int task) throws RemoteException; + public void moveTaskToBack(int task) throws RemoteException; + public boolean moveActivityTaskToBack(IBinder token, boolean nonRoot) throws RemoteException; + public void moveTaskBackwards(int task) throws RemoteException; + public int getTaskForActivity(IBinder token, boolean onlyRoot) throws RemoteException; + public void finishOtherInstances(IBinder token, ComponentName className) throws RemoteException; + /* oneway */ + public void reportThumbnail(IBinder token, + Bitmap thumbnail, CharSequence description) throws RemoteException; + public ContentProviderHolder getContentProvider(IApplicationThread caller, + String name) throws RemoteException; + public void removeContentProvider(IApplicationThread caller, + String name) throws RemoteException; + public void publishContentProviders(IApplicationThread caller, + List<ContentProviderHolder> providers) throws RemoteException; + public ComponentName startService(IApplicationThread caller, Intent service, + String resolvedType) throws RemoteException; + public int stopService(IApplicationThread caller, Intent service, + String resolvedType) throws RemoteException; + public boolean stopServiceToken(ComponentName className, IBinder token, + int startId) throws RemoteException; + public void setServiceForeground(ComponentName className, IBinder token, + boolean isForeground) throws RemoteException; + public int bindService(IApplicationThread caller, IBinder token, + Intent service, String resolvedType, + IServiceConnection connection, int flags) throws RemoteException; + public boolean unbindService(IServiceConnection connection) throws RemoteException; + public void publishService(IBinder token, + Intent intent, IBinder service) throws RemoteException; + public void unbindFinished(IBinder token, Intent service, + boolean doRebind) throws RemoteException; + /* oneway */ + public void serviceDoneExecuting(IBinder token) throws RemoteException; + public IBinder peekService(Intent service, String resolvedType) throws RemoteException; + + public boolean startInstrumentation(ComponentName className, String profileFile, + int flags, Bundle arguments, IInstrumentationWatcher watcher) + throws RemoteException; + public void finishInstrumentation(IApplicationThread target, + int resultCode, Bundle results) throws RemoteException; + + public Configuration getConfiguration() throws RemoteException; + public void updateConfiguration(Configuration values) throws RemoteException; + public void setRequestedOrientation(IBinder token, + int requestedOrientation) throws RemoteException; + public int getRequestedOrientation(IBinder token) throws RemoteException; + + public ComponentName getActivityClassForToken(IBinder token) throws RemoteException; + public String getPackageForToken(IBinder token) throws RemoteException; + + public static final int INTENT_SENDER_BROADCAST = 1; + public static final int INTENT_SENDER_ACTIVITY = 2; + public static final int INTENT_SENDER_ACTIVITY_RESULT = 3; + public static final int INTENT_SENDER_SERVICE = 4; + public IIntentSender getIntentSender(int type, + String packageName, IBinder token, String resultWho, + int requestCode, Intent intent, String resolvedType, int flags) throws RemoteException; + public void cancelIntentSender(IIntentSender sender) throws RemoteException; + public boolean clearApplicationUserData(final String packageName, + final IPackageDataObserver observer) throws RemoteException; + public String getPackageForIntentSender(IIntentSender sender) throws RemoteException; + + public void setProcessLimit(int max) throws RemoteException; + public int getProcessLimit() throws RemoteException; + + public void setProcessForeground(IBinder token, int pid, boolean isForeground) throws RemoteException; + + public int checkPermission(String permission, int pid, int uid) + throws RemoteException; + + public int checkUriPermission(Uri uri, int pid, int uid, int mode) + throws RemoteException; + public void grantUriPermission(IApplicationThread caller, String targetPkg, + Uri uri, int mode) throws RemoteException; + public void revokeUriPermission(IApplicationThread caller, Uri uri, + int mode) throws RemoteException; + + public void showWaitingForDebugger(IApplicationThread who, boolean waiting) + throws RemoteException; + + public void getMemoryInfo(ActivityManager.MemoryInfo outInfo) throws RemoteException; + + public void restartPackage(final String packageName) throws RemoteException; + + // Note: probably don't want to allow applications access to these. + public void goingToSleep() throws RemoteException; + public void wakingUp() throws RemoteException; + + public void unhandledBack() throws RemoteException; + public ParcelFileDescriptor openContentUri(Uri uri) throws RemoteException; + public void setDebugApp( + String packageName, boolean waitForDebugger, boolean persistent) + throws RemoteException; + public void setAlwaysFinish(boolean enabled) throws RemoteException; + public void setActivityWatcher(IActivityWatcher watcher) + throws RemoteException; + + public void enterSafeMode() throws RemoteException; + + public void noteWakeupAlarm(IIntentSender sender) throws RemoteException; + + public boolean killPidsForMemory(int[] pids) throws RemoteException; + + public void reportPss(IApplicationThread caller, int pss) throws RemoteException; + + // Special low-level communication with activity manager. + public void startRunning(String pkg, String cls, String action, + String data) throws RemoteException; + public void systemReady() throws RemoteException; + // Returns 1 if the user wants to debug. + public int handleApplicationError(IBinder app, + int flags, /* 1 == can debug */ + String tag, String shortMsg, String longMsg, + byte[] crashData) throws RemoteException; + + /* + * This will deliver the specified signal to all the persistent processes. Currently only + * SIGUSR1 is delivered. All others are ignored. + */ + public void signalPersistentProcesses(int signal) throws RemoteException; + // Retrieve running application processes in the system + public List<ActivityManager.RunningAppProcessInfo> getRunningAppProcesses() + throws RemoteException; + // Get device configuration + public ConfigurationInfo getDeviceConfigurationInfo() throws RemoteException; + + /* + * Private non-Binder interfaces + */ + /* package */ boolean testIsSystemReady(); + + /** Information you can retrieve about a particular application. */ + public static class ContentProviderHolder implements Parcelable { + public final ProviderInfo info; + public final String permissionFailure; + public IContentProvider provider; + public boolean noReleaseNeeded; + + public ContentProviderHolder(ProviderInfo _info) { + info = _info; + permissionFailure = null; + } + + public ContentProviderHolder(ProviderInfo _info, + String _permissionFailure) { + info = _info; + permissionFailure = _permissionFailure; + } + + public int describeContents() { + return 0; + } + + public void writeToParcel(Parcel dest, int flags) { + info.writeToParcel(dest, 0); + dest.writeString(permissionFailure); + if (provider != null) { + dest.writeStrongBinder(provider.asBinder()); + } else { + dest.writeStrongBinder(null); + } + dest.writeInt(noReleaseNeeded ? 1:0); + } + + public static final Parcelable.Creator<ContentProviderHolder> CREATOR + = new Parcelable.Creator<ContentProviderHolder>() { + public ContentProviderHolder createFromParcel(Parcel source) { + return new ContentProviderHolder(source); + } + + public ContentProviderHolder[] newArray(int size) { + return new ContentProviderHolder[size]; + } + }; + + private ContentProviderHolder(Parcel source) { + info = ProviderInfo.CREATOR.createFromParcel(source); + permissionFailure = source.readString(); + provider = ContentProviderNative.asInterface( + source.readStrongBinder()); + noReleaseNeeded = source.readInt() != 0; + } + }; + + String descriptor = "android.app.IActivityManager"; + + // Please keep these transaction codes the same -- they are also + // sent by C++ code. + int START_RUNNING_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION; + int HANDLE_APPLICATION_ERROR_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+1; + int START_ACTIVITY_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+2; + int UNHANDLED_BACK_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+3; + int OPEN_CONTENT_URI_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+4; + + // Remaining non-native transaction codes. + int FINISH_ACTIVITY_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+10; + int REGISTER_RECEIVER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+11; + int UNREGISTER_RECEIVER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+12; + int BROADCAST_INTENT_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+13; + int UNBROADCAST_INTENT_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+14; + int FINISH_RECEIVER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+15; + int ATTACH_APPLICATION_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+16; + int ACTIVITY_IDLE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+17; + int ACTIVITY_PAUSED_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+18; + int ACTIVITY_STOPPED_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+19; + int GET_CALLING_PACKAGE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+20; + int GET_CALLING_ACTIVITY_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+21; + int GET_TASKS_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+22; + int MOVE_TASK_TO_FRONT_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+23; + int MOVE_TASK_TO_BACK_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+24; + int MOVE_TASK_BACKWARDS_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+25; + int GET_TASK_FOR_ACTIVITY_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+26; + int REPORT_THUMBNAIL_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+27; + int GET_CONTENT_PROVIDER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+28; + int PUBLISH_CONTENT_PROVIDERS_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+29; + int SET_PERSISTENT_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+30; + int FINISH_SUB_ACTIVITY_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+31; + int SYSTEM_READY_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+32; + int START_SERVICE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+33; + int STOP_SERVICE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+34; + int BIND_SERVICE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+35; + int UNBIND_SERVICE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+36; + int PUBLISH_SERVICE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+37; + int FINISH_OTHER_INSTANCES_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+38; + int GOING_TO_SLEEP_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+39; + int WAKING_UP_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+40; + int SET_DEBUG_APP_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+41; + int SET_ALWAYS_FINISH_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+42; + int START_INSTRUMENTATION_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+43; + int FINISH_INSTRUMENTATION_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+44; + int GET_CONFIGURATION_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+45; + int UPDATE_CONFIGURATION_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+46; + int STOP_SERVICE_TOKEN_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+47; + int GET_ACTIVITY_CLASS_FOR_TOKEN_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+48; + int GET_PACKAGE_FOR_TOKEN_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+49; + int SET_PROCESS_LIMIT_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+50; + int GET_PROCESS_LIMIT_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+51; + int CHECK_PERMISSION_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+52; + int CHECK_URI_PERMISSION_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+53; + int GRANT_URI_PERMISSION_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+54; + int REVOKE_URI_PERMISSION_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+55; + int SET_ACTIVITY_WATCHER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+56; + int SHOW_WAITING_FOR_DEBUGGER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+57; + int SIGNAL_PERSISTENT_PROCESSES_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+58; + int GET_RECENT_TASKS_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+59; + int SERVICE_DONE_EXECUTING_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+60; + int ACTIVITY_DESTROYED_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+61; + int GET_INTENT_SENDER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+62; + int CANCEL_INTENT_SENDER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+63; + int GET_PACKAGE_FOR_INTENT_SENDER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+64; + int ENTER_SAFE_MODE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+65; + int START_NEXT_MATCHING_ACTIVITY_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+66; + int NOTE_WAKEUP_ALARM_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+67; + int REMOVE_CONTENT_PROVIDER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+68; + int SET_REQUESTED_ORIENTATION_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+69; + int GET_REQUESTED_ORIENTATION_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+70; + int UNBIND_FINISHED_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+71; + int SET_PROCESS_FOREGROUND_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+72; + int SET_SERVICE_FOREGROUND_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+73; + int MOVE_ACTIVITY_TASK_TO_BACK_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+74; + int GET_MEMORY_INFO_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+75; + int GET_PROCESSES_IN_ERROR_STATE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+76; + int CLEAR_APP_DATA_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+77; + int RESTART_PACKAGE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+78; + int KILL_PIDS_FOR_MEMORY_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+79; + int GET_SERVICES_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+80; + int REPORT_PSS_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+81; + int GET_RUNNING_APP_PROCESSES_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+82; + int GET_DEVICE_CONFIGURATION_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+83; + int PEEK_SERVICE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+84; +} diff --git a/core/java/android/app/IActivityPendingResult.aidl b/core/java/android/app/IActivityPendingResult.aidl new file mode 100644 index 0000000..e8eebf1 --- /dev/null +++ b/core/java/android/app/IActivityPendingResult.aidl @@ -0,0 +1,27 @@ +/* //device/java/android/android/app/IActivityPendingResult.aidl +** +** Copyright 2007, The Android Open Source Project +** +** 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 android.app; + +import android.os.Bundle; + +/** @hide */ +interface IActivityPendingResult +{ + boolean sendResult(int code, String data, in Bundle ex); +} + diff --git a/core/java/android/app/IActivityWatcher.aidl b/core/java/android/app/IActivityWatcher.aidl new file mode 100644 index 0000000..f13a385 --- /dev/null +++ b/core/java/android/app/IActivityWatcher.aidl @@ -0,0 +1,55 @@ +/* //device/java/android/android/app/IInstrumentationWatcher.aidl +** +** Copyright 2007, The Android Open Source Project +** +** 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 android.app; + +import android.content.Intent; + +/** + * Testing interface to monitor what is happening in the activity manager + * while tests are running. Not for normal application development. + * {@hide} + */ +interface IActivityWatcher +{ + /** + * The system is trying to start an activity. Return true to allow + * it to be started as normal, or false to cancel/reject this activity. + */ + boolean activityStarting(in Intent intent, String pkg); + + /** + * The system is trying to return to an activity. Return true to allow + * it to be resumed as normal, or false to cancel/reject this activity. + */ + boolean activityResuming(String pkg); + + /** + * An application process has crashed (in Java). Return true for the + * normal error recovery (app crash dialog) to occur, false to kill + * it immediately. + */ + boolean appCrashed(String processName, int pid, String shortMsg, + String longMsg, in byte[] crashData); + + /** + * An application process is not responding. Return 0 to show the "app + * not responding" dialog, 1 to continue waiting, or -1 to kill it + * immediately. + */ + int appNotResponding(String processName, int pid, String processStats); +} diff --git a/core/java/android/app/IAlarmManager.aidl b/core/java/android/app/IAlarmManager.aidl new file mode 100755 index 0000000..cb42236 --- /dev/null +++ b/core/java/android/app/IAlarmManager.aidl @@ -0,0 +1,34 @@ +/* //device/java/android/android/app/IAlarmManager.aidl +** +** Copyright 2006, The Android Open Source Project +** +** 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 android.app; + +import android.app.PendingIntent; + +/** + * System private API for talking with the alarm manager service. + * + * {@hide} + */ +interface IAlarmManager { + void set(int type, long triggerAtTime, in PendingIntent operation); + void setRepeating(int type, long triggerAtTime, long interval, in PendingIntent operation); + void setInexactRepeating(int type, long triggerAtTime, long interval, in PendingIntent operation); + void setTimeZone(String zone); + void remove(in PendingIntent operation); +} + + diff --git a/core/java/android/app/IApplicationThread.java b/core/java/android/app/IApplicationThread.java new file mode 100644 index 0000000..47476b5 --- /dev/null +++ b/core/java/android/app/IApplicationThread.java @@ -0,0 +1,118 @@ +/* + * Copyright (C) 2006 The Android Open Source Project + * + * 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 android.app; + +import android.content.ComponentName; +import android.content.Intent; +import android.content.pm.ActivityInfo; +import android.content.pm.ApplicationInfo; +import android.content.pm.ProviderInfo; +import android.content.pm.ServiceInfo; +import android.content.res.Configuration; +import android.os.Bundle; +import android.os.RemoteException; +import android.os.IBinder; +import android.os.IInterface; + +import java.io.FileDescriptor; +import java.util.List; +import java.util.Map; + +/** + * System private API for communicating with the application. This is given to + * the activity manager by an application when it starts up, for the activity + * manager to tell the application about things it needs to do. + * + * {@hide} + */ +public interface IApplicationThread extends IInterface { + void schedulePauseActivity(IBinder token, boolean finished, boolean userLeaving, + int configChanges) throws RemoteException; + void scheduleStopActivity(IBinder token, boolean showWindow, + int configChanges) throws RemoteException; + void scheduleWindowVisibility(IBinder token, boolean showWindow) throws RemoteException; + void scheduleResumeActivity(IBinder token, boolean isForward) throws RemoteException; + void scheduleSendResult(IBinder token, List<ResultInfo> results) throws RemoteException; + void scheduleLaunchActivity(Intent intent, IBinder token, + ActivityInfo info, Bundle state, List<ResultInfo> pendingResults, + List<Intent> pendingNewIntents, boolean notResumed, boolean isForward) + throws RemoteException; + void scheduleRelaunchActivity(IBinder token, List<ResultInfo> pendingResults, + List<Intent> pendingNewIntents, int configChanges, + boolean notResumed) throws RemoteException; + void scheduleNewIntent(List<Intent> intent, IBinder token) throws RemoteException; + void scheduleDestroyActivity(IBinder token, boolean finished, + int configChanges) throws RemoteException; + void scheduleReceiver(Intent intent, ActivityInfo info, int resultCode, + String data, Bundle extras, boolean sync) throws RemoteException; + void scheduleCreateService(IBinder token, ServiceInfo info) throws RemoteException; + void scheduleBindService(IBinder token, + Intent intent, boolean rebind) throws RemoteException; + void scheduleUnbindService(IBinder token, + Intent intent) throws RemoteException; + void scheduleServiceArgs(IBinder token, int startId, Intent args) throws RemoteException; + void scheduleStopService(IBinder token) throws RemoteException; + static final int DEBUG_OFF = 0; + static final int DEBUG_ON = 1; + static final int DEBUG_WAIT = 2; + void bindApplication(String packageName, ApplicationInfo info, List<ProviderInfo> providers, + ComponentName testName, String profileName, Bundle testArguments, + IInstrumentationWatcher testWatcher, int debugMode, Configuration config, Map<String, + IBinder> services) throws RemoteException; + void scheduleExit() throws RemoteException; + void requestThumbnail(IBinder token) throws RemoteException; + void scheduleConfigurationChanged(Configuration config) throws RemoteException; + void updateTimeZone() throws RemoteException; + void processInBackground() throws RemoteException; + void dumpService(FileDescriptor fd, IBinder servicetoken, String[] args) + throws RemoteException; + void scheduleRegisteredReceiver(IIntentReceiver receiver, Intent intent, + int resultCode, String data, Bundle extras, boolean ordered) + throws RemoteException; + void scheduleLowMemory() throws RemoteException; + void scheduleActivityConfigurationChanged(IBinder token) throws RemoteException; + void requestPss() throws RemoteException; + + String descriptor = "android.app.IApplicationThread"; + + int SCHEDULE_PAUSE_ACTIVITY_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION; + int SCHEDULE_STOP_ACTIVITY_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+2; + int SCHEDULE_WINDOW_VISIBILITY_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+3; + int SCHEDULE_RESUME_ACTIVITY_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+4; + int SCHEDULE_SEND_RESULT_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+5; + int SCHEDULE_LAUNCH_ACTIVITY_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+6; + int SCHEDULE_NEW_INTENT_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+7; + int SCHEDULE_FINISH_ACTIVITY_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+8; + int SCHEDULE_RECEIVER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+9; + int SCHEDULE_CREATE_SERVICE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+10; + int SCHEDULE_STOP_SERVICE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+11; + int BIND_APPLICATION_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+12; + int SCHEDULE_EXIT_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+13; + int REQUEST_THUMBNAIL_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+14; + int SCHEDULE_CONFIGURATION_CHANGED_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+15; + int SCHEDULE_SERVICE_ARGS_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+16; + int UPDATE_TIME_ZONE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+17; + int PROCESS_IN_BACKGROUND_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+18; + int SCHEDULE_BIND_SERVICE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+19; + int SCHEDULE_UNBIND_SERVICE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+20; + int DUMP_SERVICE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+21; + int SCHEDULE_REGISTERED_RECEIVER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+22; + int SCHEDULE_LOW_MEMORY_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+23; + int SCHEDULE_ACTIVITY_CONFIGURATION_CHANGED_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+24; + int SCHEDULE_RELAUNCH_ACTIVITY_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+25; + int REQUEST_PSS_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+26; +} diff --git a/core/java/android/app/IInstrumentationWatcher.aidl b/core/java/android/app/IInstrumentationWatcher.aidl new file mode 100644 index 0000000..405a3d8 --- /dev/null +++ b/core/java/android/app/IInstrumentationWatcher.aidl @@ -0,0 +1,31 @@ +/* //device/java/android/android/app/IInstrumentationWatcher.aidl +** +** Copyright 2007, The Android Open Source Project +** +** 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 android.app; + +import android.content.ComponentName; +import android.os.Bundle; + +/** @hide */ +oneway interface IInstrumentationWatcher +{ + void instrumentationStatus(in ComponentName name, int resultCode, + in Bundle results); + void instrumentationFinished(in ComponentName name, int resultCode, + in Bundle results); +} + diff --git a/core/java/android/app/IIntentReceiver.aidl b/core/java/android/app/IIntentReceiver.aidl new file mode 100755 index 0000000..5f5d0eb --- /dev/null +++ b/core/java/android/app/IIntentReceiver.aidl @@ -0,0 +1,33 @@ +/* +** +** Copyright 2006, The Android Open Source Project +** +** 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 android.app; + +import android.content.Intent; +import android.os.Bundle; + +/** + * System private API for dispatching intent broadcasts. This is given to the + * activity manager as part of registering for an intent broadcasts, and is + * called when it receives intents. + * + * {@hide} + */ +oneway interface IIntentReceiver { + void performReceive(in Intent intent, int resultCode, + String data, in Bundle extras, boolean ordered); +} + diff --git a/core/java/android/app/IIntentSender.aidl b/core/java/android/app/IIntentSender.aidl new file mode 100644 index 0000000..53e135a --- /dev/null +++ b/core/java/android/app/IIntentSender.aidl @@ -0,0 +1,27 @@ +/* //device/java/android/android/app/IActivityPendingResult.aidl +** +** Copyright 2007, The Android Open Source Project +** +** 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 android.app; + +import android.app.IIntentReceiver; +import android.content.Intent; + +/** @hide */ +interface IIntentSender { + int send(int code, in Intent intent, String resolvedType, + IIntentReceiver finishedReceiver); +} diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl new file mode 100644 index 0000000..c1035b6 --- /dev/null +++ b/core/java/android/app/INotificationManager.aidl @@ -0,0 +1,34 @@ +/* //device/java/android/android/app/INotificationManager.aidl +** +** Copyright 2007, The Android Open Source Project +** +** 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 android.app; + +import android.app.ITransientNotification; +import android.app.Notification; +import android.content.Intent; + +/** {@hide} */ +interface INotificationManager +{ + void enqueueNotification(String pkg, int id, in Notification notification, inout int[] idReceived); + void cancelNotification(String pkg, int id); + void cancelAllNotifications(String pkg); + + void enqueueToast(String pkg, ITransientNotification callback, int duration); + void cancelToast(String pkg, ITransientNotification callback); +} + diff --git a/core/java/android/app/ISearchManager.aidl b/core/java/android/app/ISearchManager.aidl new file mode 100644 index 0000000..6c3617a --- /dev/null +++ b/core/java/android/app/ISearchManager.aidl @@ -0,0 +1,25 @@ +/** + * Copyright (c) 2007, The Android Open Source Project + * + * 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 android.app; + +import android.content.ComponentName; +import android.server.search.SearchableInfo; + +/** @hide */ +interface ISearchManager { + SearchableInfo getSearchableInfo(in ComponentName launchActivity, boolean globalSearch); +} diff --git a/core/java/android/app/IServiceConnection.aidl b/core/java/android/app/IServiceConnection.aidl new file mode 100644 index 0000000..6804071 --- /dev/null +++ b/core/java/android/app/IServiceConnection.aidl @@ -0,0 +1,26 @@ +/* //device/java/android/android/app/IServiceConnection.aidl +** +** Copyright 2007, The Android Open Source Project +** +** 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 android.app; + +import android.content.ComponentName; + +/** @hide */ +oneway interface IServiceConnection { + void connected(in ComponentName name, IBinder service); +} + diff --git a/core/java/android/app/IStatusBar.aidl b/core/java/android/app/IStatusBar.aidl new file mode 100644 index 0000000..c64fa50 --- /dev/null +++ b/core/java/android/app/IStatusBar.aidl @@ -0,0 +1,29 @@ +/** + * Copyright (c) 2007, The Android Open Source Project + * + * 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 android.app; + +/** @hide */ +interface IStatusBar +{ + void activate(); + void deactivate(); + void toggle(); + void disable(int what, IBinder token, String pkg); + IBinder addIcon(String slot, String iconPackage, int iconId, int iconLevel); + void updateIcon(IBinder key, String slot, String iconPackage, int iconId, int iconLevel); + void removeIcon(IBinder key); +} diff --git a/core/java/android/app/IThumbnailReceiver.aidl b/core/java/android/app/IThumbnailReceiver.aidl new file mode 100755 index 0000000..7943f2c --- /dev/null +++ b/core/java/android/app/IThumbnailReceiver.aidl @@ -0,0 +1,30 @@ +/* //device/java/android/android/app/IThumbnailReceiver.aidl +** +** Copyright 2006, The Android Open Source Project +** +** 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 android.app; + +import android.graphics.Bitmap; + +/** + * System private API for receiving updated thumbnails from a checkpoint. + * + * {@hide} + */ +oneway interface IThumbnailReceiver { + void newThumbnail(int id, in Bitmap thumbnail, CharSequence description); + void finished(); +} + diff --git a/core/java/android/app/ITransientNotification.aidl b/core/java/android/app/ITransientNotification.aidl new file mode 100644 index 0000000..35b53a4 --- /dev/null +++ b/core/java/android/app/ITransientNotification.aidl @@ -0,0 +1,25 @@ +/* //device/java/android/android/app/ITransientNotification.aidl +** +** Copyright 2007, The Android Open Source Project +** +** 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 android.app; + +/** @hide */ +oneway interface ITransientNotification { + void show(); + void hide(); +} + diff --git a/core/java/android/app/IWallpaperService.aidl b/core/java/android/app/IWallpaperService.aidl new file mode 100644 index 0000000..a332b1a --- /dev/null +++ b/core/java/android/app/IWallpaperService.aidl @@ -0,0 +1,55 @@ +/** + * Copyright (c) 2008, The Android Open Source Project + * + * 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 android.app; + +import android.os.ParcelFileDescriptor; +import android.app.IWallpaperServiceCallback; + +/** @hide */ +interface IWallpaperService { + + /** + * Set the wallpaper. + */ + ParcelFileDescriptor setWallpaper(); + + /** + * Get the wallpaper. + */ + ParcelFileDescriptor getWallpaper(IWallpaperServiceCallback cb); + + /** + * Clear the wallpaper. + */ + void clearWallpaper(); + + /** + * Sets the dimension hint for the wallpaper. These hints indicate the desired + * minimum width and height for the wallpaper. + */ + void setDimensionHints(in int width, in int height); + + /** + * Returns the desired minimum width for the wallpaper. + */ + int getWidthHint(); + + /** + * Returns the desired minimum height for the wallpaper. + */ + int getHeightHint(); +} diff --git a/core/java/android/app/IWallpaperServiceCallback.aidl b/core/java/android/app/IWallpaperServiceCallback.aidl new file mode 100644 index 0000000..6086f40 --- /dev/null +++ b/core/java/android/app/IWallpaperServiceCallback.aidl @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * 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 android.app; + +/** + * Callback interface used by IWallpaperService to send asynchronous + * notifications back to its clients. Note that this is a + * one-way interface so the server does not block waiting for the client. + * + * @hide + */ +oneway interface IWallpaperServiceCallback { + /** + * Called when the wallpaper has changed + */ + void onWallpaperChanged(); +} diff --git a/core/java/android/app/Instrumentation.java b/core/java/android/app/Instrumentation.java new file mode 100644 index 0000000..f6a28b2 --- /dev/null +++ b/core/java/android/app/Instrumentation.java @@ -0,0 +1,1613 @@ +/* + * Copyright (C) 2006 The Android Open Source Project + * + * 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 android.app; + +import android.content.ActivityNotFoundException; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.pm.ActivityInfo; +import android.content.res.Configuration; +import android.os.Bundle; +import android.os.RemoteException; +import android.os.Debug; +import android.os.IBinder; +import android.os.MessageQueue; +import android.os.Process; +import android.os.SystemClock; +import android.os.ServiceManager; +import android.util.AndroidRuntimeException; +import android.util.Config; +import android.util.Log; +import android.view.IWindowManager; +import android.view.KeyCharacterMap; +import android.view.KeyEvent; +import android.view.MotionEvent; +import android.view.ViewConfiguration; +import android.view.Window; +import android.view.inputmethod.InputMethodManager; + +import java.io.File; +import java.util.ArrayList; +import java.util.List; + + +/** + * Base class for implementing application instrumentation code. When running + * with instrumentation turned on, this class will be instantiated for you + * before any of the application code, allowing you to monitor all of the + * interaction the system has with the application. An Instrumentation + * implementation is described to the system through an AndroidManifest.xml's + * <instrumentation> tag. + */ +public class Instrumentation { + /** + * If included in the status or final bundle sent to an IInstrumentationWatcher, this key + * identifies the class that is writing the report. This can be used to provide more structured + * logging or reporting capabilities in the IInstrumentationWatcher. + */ + public static final String REPORT_KEY_IDENTIFIER = "id"; + /** + * If included in the status or final bundle sent to an IInstrumentationWatcher, this key + * identifies a string which can simply be printed to the output stream. Using these streams + * provides a "pretty printer" version of the status & final packets. Any bundles including + * this key should also include the complete set of raw key/value pairs, so that the + * instrumentation can also be launched, and results collected, by an automated system. + */ + public static final String REPORT_KEY_STREAMRESULT = "stream"; + + private static final String TAG = "Instrumentation"; + + private final Object mSync = new Object(); + private ActivityThread mThread = null; + private MessageQueue mMessageQueue = null; + private Context mInstrContext; + private Context mAppContext; + private ComponentName mComponent; + private Thread mRunner; + private List<ActivityWaiter> mWaitingActivities; + private List<ActivityMonitor> mActivityMonitors; + private IInstrumentationWatcher mWatcher; + private long mPreCpuTime; + private long mStart; + private boolean mAutomaticPerformanceSnapshots = false; + private Bundle mPrePerfMetrics = new Bundle(); + private Bundle mPerfMetrics = new Bundle(); + + public Instrumentation() { + } + + /** + * Called when the instrumentation is starting, before any application code + * has been loaded. Usually this will be implemented to simply call + * {@link #start} to begin the instrumentation thread, which will then + * continue execution in {@link #onStart}. + * + * <p>If you do not need your own thread -- that is you are writing your + * instrumentation to be completely asynchronous (returning to the event + * loop so that the application can run), you can simply begin your + * instrumentation here, for example call {@link Context#startActivity} to + * begin the appropriate first activity of the application. + * + * @param arguments Any additional arguments that were supplied when the + * instrumentation was started. + */ + public void onCreate(Bundle arguments) { + } + + /** + * Create and start a new thread in which to run instrumentation. This new + * thread will call to {@link #onStart} where you can implement the + * instrumentation. + */ + public void start() { + if (mRunner != null) { + throw new RuntimeException("Instrumentation already started"); + } + mRunner = new InstrumentationThread("Instr: " + getClass().getName()); + mRunner.start(); + } + + /** + * Method where the instrumentation thread enters execution. This allows + * you to run your instrumentation code in a separate thread than the + * application, so that it can perform blocking operation such as + * {@link #sendKeySync} or {@link #startActivitySync}. + * + * <p>You will typically want to call finish() when this function is done, + * to end your instrumentation. + */ + public void onStart() { + } + + /** + * This is called whenever the system captures an unhandled exception that + * was thrown by the application. The default implementation simply + * returns false, allowing normal system handling of the exception to take + * place. + * + * @param obj The client object that generated the exception. May be an + * Application, Activity, BroadcastReceiver, Service, or null. + * @param e The exception that was thrown. + * + * @return To allow normal system exception process to occur, return false. + * If true is returned, the system will proceed as if the exception + * didn't happen. + */ + public boolean onException(Object obj, Throwable e) { + return false; + } + + /** + * Provide a status report about the application. + * + * @param resultCode Current success/failure of instrumentation. + * @param results Any results to send back to the code that started the instrumentation. + */ + public void sendStatus(int resultCode, Bundle results) { + if (mWatcher != null) { + try { + mWatcher.instrumentationStatus(mComponent, resultCode, results); + } + catch (RemoteException e) { + mWatcher = null; + } + } + } + + /** + * Terminate instrumentation of the application. This will cause the + * application process to exit, removing this instrumentation from the next + * time the application is started. + * + * @param resultCode Overall success/failure of instrumentation. + * @param results Any results to send back to the code that started the + * instrumentation. + */ + public void finish(int resultCode, Bundle results) { + if (mAutomaticPerformanceSnapshots) { + endPerformanceSnapshot(); + } + if (mPerfMetrics != null) { + results.putAll(mPerfMetrics); + } + mThread.finishInstrumentation(resultCode, results); + } + + public void setAutomaticPerformanceSnapshots() { + mAutomaticPerformanceSnapshots = true; + } + + public void startPerformanceSnapshot() { + mStart = 0; + if (!isProfiling()) { + // Add initial binder counts + Bundle binderCounts = getBinderCounts(); + for (String key: binderCounts.keySet()) { + addPerfMetricLong("pre_" + key, binderCounts.getLong(key)); + } + + // Force a GC and zero out the performance counters. Do this + // before reading initial CPU/wall-clock times so we don't include + // the cost of this setup in our final metrics. + startAllocCounting(); + + // Record CPU time up to this point, and start timing. Note: this + // must happen at the end of this method, otherwise the timing will + // include noise. + mStart = SystemClock.uptimeMillis(); + mPreCpuTime = Process.getElapsedCpuTime(); + } + } + + public void endPerformanceSnapshot() { + if (!isProfiling()) { + // Stop the timing. This must be done first before any other counting is stopped. + long cpuTime = Process.getElapsedCpuTime(); + long duration = SystemClock.uptimeMillis(); + + stopAllocCounting(); + + long nativeMax = Debug.getNativeHeapSize() / 1024; + long nativeAllocated = Debug.getNativeHeapAllocatedSize() / 1024; + long nativeFree = Debug.getNativeHeapFreeSize() / 1024; + + Debug.MemoryInfo memInfo = new Debug.MemoryInfo(); + Debug.getMemoryInfo(memInfo); + + Runtime runtime = Runtime.getRuntime(); + + long dalvikMax = runtime.totalMemory() / 1024; + long dalvikFree = runtime.freeMemory() / 1024; + long dalvikAllocated = dalvikMax - dalvikFree; + + // Add final binder counts + Bundle binderCounts = getBinderCounts(); + for (String key: binderCounts.keySet()) { + addPerfMetricLong(key, binderCounts.getLong(key)); + } + + // Add alloc counts + Bundle allocCounts = getAllocCounts(); + for (String key: allocCounts.keySet()) { + addPerfMetricLong(key, allocCounts.getLong(key)); + } + + addPerfMetricLong("execution_time", duration - mStart); + addPerfMetricLong("pre_cpu_time", mPreCpuTime); + addPerfMetricLong("cpu_time", cpuTime - mPreCpuTime); + + addPerfMetricLong("native_size", nativeMax); + addPerfMetricLong("native_allocated", nativeAllocated); + addPerfMetricLong("native_free", nativeFree); + addPerfMetricInt("native_pss", memInfo.nativePss); + addPerfMetricInt("native_private_dirty", memInfo.nativePrivateDirty); + addPerfMetricInt("native_shared_dirty", memInfo.nativeSharedDirty); + + addPerfMetricLong("java_size", dalvikMax); + addPerfMetricLong("java_allocated", dalvikAllocated); + addPerfMetricLong("java_free", dalvikFree); + addPerfMetricInt("java_pss", memInfo.dalvikPss); + addPerfMetricInt("java_private_dirty", memInfo.dalvikPrivateDirty); + addPerfMetricInt("java_shared_dirty", memInfo.dalvikSharedDirty); + + addPerfMetricInt("other_pss", memInfo.otherPss); + addPerfMetricInt("other_private_dirty", memInfo.otherPrivateDirty); + addPerfMetricInt("other_shared_dirty", memInfo.otherSharedDirty); + + } + } + + private void addPerfMetricLong(String key, long value) { + mPerfMetrics.putLong("performance." + key, value); + } + + private void addPerfMetricInt(String key, int value) { + mPerfMetrics.putInt("performance." + key, value); + } + + /** + * Called when the instrumented application is stopping, after all of the + * normal application cleanup has occurred. + */ + public void onDestroy() { + } + + /** + * Return the Context of this instrumentation's package. Note that this is + * often different than the Context of the application being + * instrumentated, since the instrumentation code often lives is a + * different package than that of the application it is running against. + * See {@link #getTargetContext} to retrieve a Context for the target + * application. + * + * @return The instrumentation's package context. + * + * @see #getTargetContext + */ + public Context getContext() { + return mInstrContext; + } + + /** + * Returns complete component name of this instrumentation. + * + * @return Returns the complete component name for this instrumentation. + */ + public ComponentName getComponentName() { + return mComponent; + } + + /** + * Return a Context for the target application being instrumented. Note + * that this is often different than the Context of the instrumentation + * code, since the instrumentation code often lives is a different package + * than that of the application it is running against. See + * {@link #getContext} to retrieve a Context for the instrumentation code. + * + * @return A Context in the target application. + * + * @see #getContext + */ + public Context getTargetContext() { + return mAppContext; + } + + /** + * Check whether this instrumentation was started with profiling enabled. + * + * @return Returns true if profiling was enabled when starting, else false. + */ + public boolean isProfiling() { + return mThread.isProfiling(); + } + + /** + * This method will start profiling if isProfiling() returns true. You should + * only call this method if you set the handleProfiling attribute in the + * manifest file for this Instrumentation to true. + */ + public void startProfiling() { + if (mThread.isProfiling()) { + File file = new File(mThread.getProfileFilePath()); + file.getParentFile().mkdirs(); + Debug.startMethodTracing(file.toString(), 8 * 1024 * 1024); + } + } + + /** + * Stops profiling if isProfiling() returns true. + */ + public void stopProfiling() { + if (mThread.isProfiling()) { + Debug.stopMethodTracing(); + } + } + + /** + * Force the global system in or out of touch mode. This can be used if + * your instrumentation relies on the UI being in one more or the other + * when it starts. + * + * @param inTouch Set to true to be in touch mode, false to be in + * focus mode. + */ + public void setInTouchMode(boolean inTouch) { + try { + IWindowManager.Stub.asInterface( + ServiceManager.getService("window")).setInTouchMode(inTouch); + } catch (RemoteException e) { + // Shouldn't happen! + } + } + + /** + * Schedule a callback for when the application's main thread goes idle + * (has no more events to process). + * + * @param recipient Called the next time the thread's message queue is + * idle. + */ + public void waitForIdle(Runnable recipient) { + mMessageQueue.addIdleHandler(new Idler(recipient)); + mThread.getHandler().post(new EmptyRunnable()); + } + + /** + * Synchronously wait for the application to be idle. Can not be called + * from the main application thread -- use {@link #start} to execute + * instrumentation in its own thread. + */ + public void waitForIdleSync() { + validateNotAppThread(); + Idler idler = new Idler(null); + mMessageQueue.addIdleHandler(idler); + mThread.getHandler().post(new EmptyRunnable()); + idler.waitForIdle(); + } + + /** + * Execute a call on the application's main thread, blocking until it is + * complete. Useful for doing things that are not thread-safe, such as + * looking at or modifying the view hierarchy. + * + * @param runner The code to run on the main thread. + */ + public void runOnMainSync(Runnable runner) { + validateNotAppThread(); + SyncRunnable sr = new SyncRunnable(runner); + mThread.getHandler().post(sr); + sr.waitForComplete(); + } + + /** + * Start a new activity and wait for it to begin running before returning. + * In addition to being synchronous, this method as some semantic + * differences from the standard {@link Context#startActivity} call: the + * activity component is resolved before talking with the activity manager + * (its class name is specified in the Intent that this method ultimately + * starts), and it does not allow you to start activities that run in a + * different process. In addition, if the given Intent resolves to + * multiple activities, instead of displaying a dialog for the user to + * select an activity, an exception will be thrown. + * + * <p>The function returns as soon as the activity goes idle following the + * call to its {@link Activity#onCreate}. Generally this means it has gone + * through the full initialization including {@link Activity#onResume} and + * drawn and displayed its initial window. + * + * @param intent Description of the activity to start. + * + * @see Context#startActivity + */ + public Activity startActivitySync(Intent intent) { + validateNotAppThread(); + + synchronized (mSync) { + intent = new Intent(intent); + + ActivityInfo ai = intent.resolveActivityInfo( + getTargetContext().getPackageManager(), 0); + if (ai == null) { + throw new RuntimeException("Unable to resolve activity for: " + intent); + } + if (!ai.applicationInfo.processName.equals( + getTargetContext().getPackageName())) { + // todo: if this intent is ambiguous, look here to see if + // there is a single match that is in our package. + throw new RuntimeException("Intent resolved to different package " + + ai.applicationInfo.packageName + ": " + + intent); + } + + intent.setComponent(new ComponentName( + ai.applicationInfo.packageName, ai.name)); + final ActivityWaiter aw = new ActivityWaiter(intent); + + if (mWaitingActivities == null) { + mWaitingActivities = new ArrayList(); + } + mWaitingActivities.add(aw); + + getTargetContext().startActivity(intent); + + do { + try { + mSync.wait(); + } catch (InterruptedException e) { + } + } while (mWaitingActivities.contains(aw)); + + return aw.activity; + } + } + + /** + * Information about a particular kind of Intent that is being monitored. + * An instance of this class is added to the + * current instrumentation through {@link #addMonitor}; after being added, + * when a new activity is being started the monitor will be checked and, if + * matching, its hit count updated and (optionally) the call stopped and a + * canned result returned. + * + * <p>An ActivityMonitor can also be used to look for the creation of an + * activity, through the {@link #waitForActivity} method. This will return + * after a matching activity has been created with that activity object. + */ + public static class ActivityMonitor { + private final IntentFilter mWhich; + private final String mClass; + private final ActivityResult mResult; + private final boolean mBlock; + + + // This is protected by 'Instrumentation.this.mSync'. + /*package*/ int mHits = 0; + + // This is protected by 'this'. + /*package*/ Activity mLastActivity = null; + + /** + * Create a new ActivityMonitor that looks for a particular kind of + * intent to be started. + * + * @param which The set of intents this monitor is responsible for. + * @param result A canned result to return if the monitor is hit; can + * be null. + * @param block Controls whether the monitor should block the activity + * start (returning its canned result) or let the call + * proceed. + * + * @see Instrumentation#addMonitor + */ + public ActivityMonitor( + IntentFilter which, ActivityResult result, boolean block) { + mWhich = which; + mClass = null; + mResult = result; + mBlock = block; + } + + /** + * Create a new ActivityMonitor that looks for a specific activity + * class to be started. + * + * @param cls The activity class this monitor is responsible for. + * @param result A canned result to return if the monitor is hit; can + * be null. + * @param block Controls whether the monitor should block the activity + * start (returning its canned result) or let the call + * proceed. + * + * @see Instrumentation#addMonitor + */ + public ActivityMonitor( + String cls, ActivityResult result, boolean block) { + mWhich = null; + mClass = cls; + mResult = result; + mBlock = block; + } + + /** + * Retrieve the filter associated with this ActivityMonitor. + */ + public final IntentFilter getFilter() { + return mWhich; + } + + /** + * Retrieve the result associated with this ActivityMonitor, or null if + * none. + */ + public final ActivityResult getResult() { + return mResult; + } + + /** + * Check whether this monitor blocks activity starts (not allowing the + * actual activity to run) or allows them to execute normally. + */ + public final boolean isBlocking() { + return mBlock; + } + + /** + * Retrieve the number of times the monitor has been hit so far. + */ + public final int getHits() { + return mHits; + } + + /** + * Retrieve the most recent activity class that was seen by this + * monitor. + */ + public final Activity getLastActivity() { + return mLastActivity; + } + + /** + * Block until an Activity is created that matches this monitor, + * returning the resulting activity. + * + * @return Activity + */ + public final Activity waitForActivity() { + synchronized (this) { + while (mLastActivity == null) { + try { + wait(); + } catch (InterruptedException e) { + } + } + Activity res = mLastActivity; + mLastActivity = null; + return res; + } + } + + /** + * Block until an Activity is created that matches this monitor, + * returning the resulting activity or till the timeOut period expires. + * If the timeOut expires before the activity is started, return null. + * + * @param timeOut Time to wait before the activity is created. + * + * @return Activity + */ + public final Activity waitForActivityWithTimeout(long timeOut) { + synchronized (this) { + try { + wait(timeOut); + } catch (InterruptedException e) { + } + if (mLastActivity == null) { + return null; + } else { + Activity res = mLastActivity; + mLastActivity = null; + return res; + } + } + } + + final boolean match(Context who, + Activity activity, + Intent intent) { + synchronized (this) { + if (mWhich != null + && mWhich.match(who.getContentResolver(), intent, + true, "Instrumentation") < 0) { + return false; + } + if (mClass != null) { + String cls = null; + if (activity != null) { + cls = activity.getClass().getName(); + } else if (intent.getComponent() != null) { + cls = intent.getComponent().getClassName(); + } + if (cls == null || !mClass.equals(cls)) { + return false; + } + } + if (activity != null) { + mLastActivity = activity; + notifyAll(); + } + return true; + } + } + } + + /** + * Add a new {@link ActivityMonitor} that will be checked whenever an + * activity is started. The monitor is added + * after any existing ones; the monitor will be hit only if none of the + * existing monitors can themselves handle the Intent. + * + * @param monitor The new ActivityMonitor to see. + * + * @see #addMonitor(IntentFilter, ActivityResult, boolean) + * @see #checkMonitorHit + */ + public void addMonitor(ActivityMonitor monitor) { + synchronized (mSync) { + if (mActivityMonitors == null) { + mActivityMonitors = new ArrayList(); + } + mActivityMonitors.add(monitor); + } + } + + /** + * A convenience wrapper for {@link #addMonitor(ActivityMonitor)} that + * creates an intent filter matching {@link ActivityMonitor} for you and + * returns it. + * + * @param filter The set of intents this monitor is responsible for. + * @param result A canned result to return if the monitor is hit; can + * be null. + * @param block Controls whether the monitor should block the activity + * start (returning its canned result) or let the call + * proceed. + * + * @return The newly created and added activity monitor. + * + * @see #addMonitor(ActivityMonitor) + * @see #checkMonitorHit + */ + public ActivityMonitor addMonitor( + IntentFilter filter, ActivityResult result, boolean block) { + ActivityMonitor am = new ActivityMonitor(filter, result, block); + addMonitor(am); + return am; + } + + /** + * A convenience wrapper for {@link #addMonitor(ActivityMonitor)} that + * creates a class matching {@link ActivityMonitor} for you and returns it. + * + * @param cls The activity class this monitor is responsible for. + * @param result A canned result to return if the monitor is hit; can + * be null. + * @param block Controls whether the monitor should block the activity + * start (returning its canned result) or let the call + * proceed. + * + * @return The newly created and added activity monitor. + * + * @see #addMonitor(ActivityMonitor) + * @see #checkMonitorHit + */ + public ActivityMonitor addMonitor( + String cls, ActivityResult result, boolean block) { + ActivityMonitor am = new ActivityMonitor(cls, result, block); + addMonitor(am); + return am; + } + + /** + * Test whether an existing {@link ActivityMonitor} has been hit. If the + * monitor has been hit at least <var>minHits</var> times, then it will be + * removed from the activity monitor list and true returned. Otherwise it + * is left as-is and false is returned. + * + * @param monitor The ActivityMonitor to check. + * @param minHits The minimum number of hits required. + * + * @return True if the hit count has been reached, else false. + * + * @see #addMonitor + */ + public boolean checkMonitorHit(ActivityMonitor monitor, int minHits) { + waitForIdleSync(); + synchronized (mSync) { + if (monitor.getHits() < minHits) { + return false; + } + mActivityMonitors.remove(monitor); + } + return true; + } + + /** + * Wait for an existing {@link ActivityMonitor} to be hit. Once the + * monitor has been hit, it is removed from the activity monitor list and + * the first created Activity object that matched it is returned. + * + * @param monitor The ActivityMonitor to wait for. + * + * @return The Activity object that matched the monitor. + */ + public Activity waitForMonitor(ActivityMonitor monitor) { + Activity activity = monitor.waitForActivity(); + synchronized (mSync) { + mActivityMonitors.remove(monitor); + } + return activity; + } + + /** + * Wait for an existing {@link ActivityMonitor} to be hit till the timeout + * expires. Once the monitor has been hit, it is removed from the activity + * monitor list and the first created Activity object that matched it is + * returned. If the timeout expires, a null object is returned. + * + * @param monitor The ActivityMonitor to wait for. + * @param timeOut The timeout value in secs. + * + * @return The Activity object that matched the monitor. + */ + public Activity waitForMonitorWithTimeout(ActivityMonitor monitor, long timeOut) { + Activity activity = monitor.waitForActivityWithTimeout(timeOut); + synchronized (mSync) { + mActivityMonitors.remove(monitor); + } + return activity; + } + + /** + * Remove an {@link ActivityMonitor} that was previously added with + * {@link #addMonitor}. + * + * @param monitor The monitor to remove. + * + * @see #addMonitor + */ + public void removeMonitor(ActivityMonitor monitor) { + synchronized (mSync) { + mActivityMonitors.remove(monitor); + } + } + + /** + * Execute a particular menu item. + * + * @param targetActivity The activity in question. + * @param id The identifier associated with the menu item. + * @param flag Additional flags, if any. + * @return Whether the invocation was successful (for example, it could be + * false if item is disabled). + */ + public boolean invokeMenuActionSync(Activity targetActivity, + int id, int flag) { + class MenuRunnable implements Runnable { + private final Activity activity; + private final int identifier; + private final int flags; + boolean returnValue; + + public MenuRunnable(Activity _activity, int _identifier, + int _flags) { + activity = _activity; + identifier = _identifier; + flags = _flags; + } + + public void run() { + Window win = activity.getWindow(); + + returnValue = win.performPanelIdentifierAction( + Window.FEATURE_OPTIONS_PANEL, + identifier, + flags); + } + + } + MenuRunnable mr = new MenuRunnable(targetActivity, id, flag); + runOnMainSync(mr); + return mr.returnValue; + } + + /** + * Show the context menu for the currently focused view and executes a + * particular context menu item. + * + * @param targetActivity The activity in question. + * @param id The identifier associated with the context menu item. + * @param flag Additional flags, if any. + * @return Whether the invocation was successful (for example, it could be + * false if item is disabled). + */ + public boolean invokeContextMenuAction(Activity targetActivity, int id, int flag) { + validateNotAppThread(); + + // Bring up context menu for current focus. + // It'd be nice to do this through code, but currently ListView depends on + // long press to set metadata for its selected child + + final KeyEvent downEvent = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DPAD_CENTER); + sendKeySync(downEvent); + + // Need to wait for long press + waitForIdleSync(); + try { + Thread.sleep(ViewConfiguration.getLongPressTimeout()); + } catch (InterruptedException e) { + Log.e(TAG, "Could not sleep for long press timeout", e); + return false; + } + + final KeyEvent upEvent = new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_DPAD_CENTER); + sendKeySync(upEvent); + + // Wait for context menu to appear + waitForIdleSync(); + + class ContextMenuRunnable implements Runnable { + private final Activity activity; + private final int identifier; + private final int flags; + boolean returnValue; + + public ContextMenuRunnable(Activity _activity, int _identifier, + int _flags) { + activity = _activity; + identifier = _identifier; + flags = _flags; + } + + public void run() { + Window win = activity.getWindow(); + returnValue = win.performContextMenuIdentifierAction( + identifier, + flags); + } + + } + + ContextMenuRunnable cmr = new ContextMenuRunnable(targetActivity, id, flag); + runOnMainSync(cmr); + return cmr.returnValue; + } + + /** + * Sends the key events corresponding to the text to the app being + * instrumented. + * + * @param text The text to be sent. + */ + public void sendStringSync(String text) { + if (text == null) { + return; + } + KeyCharacterMap keyCharacterMap = + KeyCharacterMap.load(KeyCharacterMap.BUILT_IN_KEYBOARD); + + KeyEvent[] events = keyCharacterMap.getEvents(text.toCharArray()); + + if (events != null) { + for (int i = 0; i < events.length; i++) { + sendKeySync(events[i]); + } + } + } + + /** + * Send a key event to the currently focused window/view and wait for it to + * be processed. Finished at some point after the recipient has returned + * from its event processing, though it may <em>not</em> have completely + * finished reacting from the event -- for example, if it needs to update + * its display as a result, it may still be in the process of doing that. + * + * @param event The event to send to the current focus. + */ + public void sendKeySync(KeyEvent event) { + validateNotAppThread(); + try { + (IWindowManager.Stub.asInterface(ServiceManager.getService("window"))) + .injectKeyEvent(event, true); + } catch (RemoteException e) { + } + } + + /** + * Sends an up and down key event sync to the currently focused window. + * + * @param key The integer keycode for the event. + */ + public void sendKeyDownUpSync(int key) { + sendKeySync(new KeyEvent(KeyEvent.ACTION_DOWN, key)); + sendKeySync(new KeyEvent(KeyEvent.ACTION_UP, key)); + } + + /** + * Higher-level method for sending both the down and up key events for a + * particular character key code. Equivalent to creating both KeyEvent + * objects by hand and calling {@link #sendKeySync}. The event appears + * as if it came from keyboard 0, the built in one. + * + * @param keyCode The key code of the character to send. + */ + public void sendCharacterSync(int keyCode) { + sendKeySync(new KeyEvent(KeyEvent.ACTION_DOWN, keyCode)); + sendKeySync(new KeyEvent(KeyEvent.ACTION_UP, keyCode)); + } + + /** + * Dispatch a pointer event. Finished at some point after the recipient has + * returned from its event processing, though it may <em>not</em> have + * completely finished reacting from the event -- for example, if it needs + * to update its display as a result, it may still be in the process of + * doing that. + * + * @param event A motion event describing the pointer action. (As noted in + * {@link MotionEvent#obtain(long, long, int, float, float, int)}, be sure to use + * {@link SystemClock#uptimeMillis()} as the timebase. + */ + public void sendPointerSync(MotionEvent event) { + validateNotAppThread(); + try { + (IWindowManager.Stub.asInterface(ServiceManager.getService("window"))) + .injectPointerEvent(event, true); + } catch (RemoteException e) { + } + } + + /** + * Dispatch a trackball event. Finished at some point after the recipient has + * returned from its event processing, though it may <em>not</em> have + * completely finished reacting from the event -- for example, if it needs + * to update its display as a result, it may still be in the process of + * doing that. + * + * @param event A motion event describing the trackball action. (As noted in + * {@link MotionEvent#obtain(long, long, int, float, float, int)}, be sure to use + * {@link SystemClock#uptimeMillis()} as the timebase. + */ + public void sendTrackballEventSync(MotionEvent event) { + validateNotAppThread(); + try { + (IWindowManager.Stub.asInterface(ServiceManager.getService("window"))) + .injectTrackballEvent(event, true); + } catch (RemoteException e) { + } + } + + /** + * Perform instantiation of the process's {@link Application} object. The + * default implementation provides the normal system behavior. + * + * @param cl The ClassLoader with which to instantiate the object. + * @param className The name of the class implementing the Application + * object. + * @param context The context to initialize the application with + * + * @return The newly instantiated Application object. + */ + public Application newApplication(ClassLoader cl, String className, Context context) + throws InstantiationException, IllegalAccessException, + ClassNotFoundException { + return newApplication(cl.loadClass(className), context); + } + + /** + * Perform instantiation of the process's {@link Application} object. The + * default implementation provides the normal system behavior. + * + * @param clazz The class used to create an Application object from. + * @param context The context to initialize the application with + * + * @return The newly instantiated Application object. + */ + static public Application newApplication(Class<?> clazz, Context context) + throws InstantiationException, IllegalAccessException, + ClassNotFoundException { + Application app = (Application)clazz.newInstance(); + app.attach(context); + return app; + } + + /** + * Perform calling of the application's {@link Application#onCreate} + * method. The default implementation simply calls through to that method. + * + * @param app The application being created. + */ + public void callApplicationOnCreate(Application app) { + app.onCreate(); + } + + /** + * Perform instantiation of an {@link Activity} object. This method is intended for use with + * unit tests, such as android.test.ActivityUnitTestCase. The activity will be useable + * locally but will be missing some of the linkages necessary for use within the sytem. + * + * @param clazz The Class of the desired Activity + * @param context The base context for the activity to use + * @param token The token for this activity to communicate with + * @param application The application object (if any) + * @param intent The intent that started this Activity + * @param info ActivityInfo from the manifest + * @param title The title, typically retrieved from the ActivityInfo record + * @param parent The parent Activity (if any) + * @param id The embedded Id (if any) + * @param lastNonConfigurationInstance Arbitrary object that will be + * available via {@link Activity#getLastNonConfigurationInstance() + * Activity.getLastNonConfigurationInstance()}. + * @return Returns the instantiated activity + * @throws InstantiationException + * @throws IllegalAccessException + */ + public Activity newActivity(Class<?> clazz, Context context, + IBinder token, Application application, Intent intent, ActivityInfo info, + CharSequence title, Activity parent, String id, + Object lastNonConfigurationInstance) throws InstantiationException, + IllegalAccessException { + Activity activity = (Activity)clazz.newInstance(); + ActivityThread aThread = null; + activity.attach(context, aThread, this, token, application, intent, info, title, + parent, id, lastNonConfigurationInstance, new Configuration()); + return activity; + } + + /** + * Perform instantiation of the process's {@link Activity} object. The + * default implementation provides the normal system behavior. + * + * @param cl The ClassLoader with which to instantiate the object. + * @param className The name of the class implementing the Activity + * object. + * @param intent The Intent object that specified the activity class being + * instantiated. + * + * @return The newly instantiated Activity object. + */ + public Activity newActivity(ClassLoader cl, String className, + Intent intent) + throws InstantiationException, IllegalAccessException, + ClassNotFoundException { + return (Activity)cl.loadClass(className).newInstance(); + } + + /** + * Perform calling of an activity's {@link Activity#onCreate} + * method. The default implementation simply calls through to that method. + * + * @param activity The activity being created. + * @param icicle The previously frozen state (or null) to pass through to + * onCreate(). + */ + public void callActivityOnCreate(Activity activity, Bundle icicle) { + if (mWaitingActivities != null) { + synchronized (mSync) { + final int N = mWaitingActivities.size(); + for (int i=0; i<N; i++) { + final ActivityWaiter aw = mWaitingActivities.get(i); + final Intent intent = aw.intent; + if (intent.filterEquals(activity.getIntent())) { + aw.activity = activity; + mMessageQueue.addIdleHandler(new ActivityGoing(aw)); + } + } + } + } + + activity.onCreate(icicle); + + if (mActivityMonitors != null) { + synchronized (mSync) { + final int N = mActivityMonitors.size(); + for (int i=0; i<N; i++) { + final ActivityMonitor am = mActivityMonitors.get(i); + am.match(activity, activity, activity.getIntent()); + } + } + } + } + + public void callActivityOnDestroy(Activity activity) { + if (mWaitingActivities != null) { + synchronized (mSync) { + final int N = mWaitingActivities.size(); + for (int i=0; i<N; i++) { + final ActivityWaiter aw = mWaitingActivities.get(i); + final Intent intent = aw.intent; + if (intent.filterEquals(activity.getIntent())) { + aw.activity = activity; + mMessageQueue.addIdleHandler(new ActivityGoing(aw)); + } + } + } + } + + activity.onDestroy(); + + if (mActivityMonitors != null) { + synchronized (mSync) { + final int N = mActivityMonitors.size(); + for (int i=0; i<N; i++) { + final ActivityMonitor am = mActivityMonitors.get(i); + am.match(activity, activity, activity.getIntent()); + } + } + } + } + + /** + * Perform calling of an activity's {@link Activity#onRestoreInstanceState} + * method. The default implementation simply calls through to that method. + * + * @param activity The activity being restored. + * @param savedInstanceState The previously saved state being restored. + */ + public void callActivityOnRestoreInstanceState(Activity activity, Bundle savedInstanceState) { + activity.performRestoreInstanceState(savedInstanceState); + } + + /** + * Perform calling of an activity's {@link Activity#onPostCreate} method. + * The default implementation simply calls through to that method. + * + * @param activity The activity being created. + * @param icicle The previously frozen state (or null) to pass through to + * onPostCreate(). + */ + public void callActivityOnPostCreate(Activity activity, Bundle icicle) { + activity.onPostCreate(icicle); + } + + /** + * Perform calling of an activity's {@link Activity#onNewIntent} + * method. The default implementation simply calls through to that method. + * + * @param activity The activity receiving a new Intent. + * @param intent The new intent being received. + */ + public void callActivityOnNewIntent(Activity activity, Intent intent) { + activity.onNewIntent(intent); + } + + /** + * Perform calling of an activity's {@link Activity#onStart} + * method. The default implementation simply calls through to that method. + * + * @param activity The activity being started. + */ + public void callActivityOnStart(Activity activity) { + activity.onStart(); + } + + /** + * Perform calling of an activity's {@link Activity#onRestart} + * method. The default implementation simply calls through to that method. + * + * @param activity The activity being restarted. + */ + public void callActivityOnRestart(Activity activity) { + activity.onRestart(); + } + + /** + * Perform calling of an activity's {@link Activity#onResume} method. The + * default implementation simply calls through to that method. + * + * @param activity The activity being resumed. + */ + public void callActivityOnResume(Activity activity) { + activity.onResume(); + + if (mActivityMonitors != null) { + synchronized (mSync) { + final int N = mActivityMonitors.size(); + for (int i=0; i<N; i++) { + final ActivityMonitor am = mActivityMonitors.get(i); + am.match(activity, activity, activity.getIntent()); + } + } + } + } + + /** + * Perform calling of an activity's {@link Activity#onStop} + * method. The default implementation simply calls through to that method. + * + * @param activity The activity being stopped. + */ + public void callActivityOnStop(Activity activity) { + activity.onStop(); + } + + /** + * Perform calling of an activity's {@link Activity#onPause} method. The + * default implementation simply calls through to that method. + * + * @param activity The activity being saved. + * @param outState The bundle to pass to the call. + */ + public void callActivityOnSaveInstanceState(Activity activity, Bundle outState) { + activity.performSaveInstanceState(outState); + } + + /** + * Perform calling of an activity's {@link Activity#onPause} method. The + * default implementation simply calls through to that method. + * + * @param activity The activity being paused. + */ + public void callActivityOnPause(Activity activity) { + activity.performPause(); + } + + /** + * Perform calling of an activity's {@link Activity#onUserLeaveHint} method. + * The default implementation simply calls through to that method. + * + * @param activity The activity being notified that the user has navigated away + */ + public void callActivityOnUserLeaving(Activity activity) { + activity.performUserLeaving(); + } + + /* + * Starts allocation counting. This triggers a gc and resets the counts. + */ + public void startAllocCounting() { + // Before we start trigger a GC and reset the debug counts. Run the + // finalizers and another GC before starting and stopping the alloc + // counts. This will free up any objects that were just sitting around + // waiting for their finalizers to be run. + Runtime.getRuntime().gc(); + Runtime.getRuntime().runFinalization(); + Runtime.getRuntime().gc(); + + Debug.resetAllCounts(); + + // start the counts + Debug.startAllocCounting(); + } + + /* + * Stops allocation counting. + */ + public void stopAllocCounting() { + Runtime.getRuntime().gc(); + Runtime.getRuntime().runFinalization(); + Runtime.getRuntime().gc(); + Debug.stopAllocCounting(); + } + + /** + * If Results already contains Key, it appends Value to the key's ArrayList + * associated with the key. If the key doesn't already exist in results, it + * adds the key/value pair to results. + */ + private void addValue(String key, int value, Bundle results) { + if (results.containsKey(key)) { + List<Integer> list = results.getIntegerArrayList(key); + if (list != null) { + list.add(value); + } + } else { + ArrayList<Integer> list = new ArrayList<Integer>(); + list.add(value); + results.putIntegerArrayList(key, list); + } + } + + /** + * Returns a bundle with the current results from the allocation counting. + */ + public Bundle getAllocCounts() { + Bundle results = new Bundle(); + results.putLong("global_alloc_count", Debug.getGlobalAllocCount()); + results.putLong("global_alloc_size", Debug.getGlobalAllocSize()); + results.putLong("global_freed_count", Debug.getGlobalFreedCount()); + results.putLong("global_freed_size", Debug.getGlobalFreedSize()); + results.putLong("gc_invocation_count", Debug.getGlobalGcInvocationCount()); + return results; + } + + /** + * Returns a bundle with the counts for various binder counts for this process. Currently the only two that are + * reported are the number of send and the number of received transactions. + */ + public Bundle getBinderCounts() { + Bundle results = new Bundle(); + results.putLong("sent_transactions", Debug.getBinderSentTransactions()); + results.putLong("received_transactions", Debug.getBinderReceivedTransactions()); + return results; + } + + /** + * Description of a Activity execution result to return to the original + * activity. + */ + public static final class ActivityResult { + /** + * Create a new activity result. See {@link Activity#setResult} for + * more information. + * + * @param resultCode The result code to propagate back to the + * originating activity, often RESULT_CANCELED or RESULT_OK + * @param resultData The data to propagate back to the originating + * activity. + */ + public ActivityResult(int resultCode, Intent resultData) { + mResultCode = resultCode; + mResultData = resultData; + } + + /** + * Retrieve the result code contained in this result. + */ + public int getResultCode() { + return mResultCode; + } + + /** + * Retrieve the data contained in this result. + */ + public Intent getResultData() { + return mResultData; + } + + private final int mResultCode; + private final Intent mResultData; + } + + /** + * Execute a startActivity call made by the application. The default + * implementation takes care of updating any active {@link ActivityMonitor} + * objects and dispatches this call to the system activity manager; you can + * override this to watch for the application to start an activity, and + * modify what happens when it does. + * + * <p>This method returns an {@link ActivityResult} object, which you can + * use when intercepting application calls to avoid performing the start + * activity action but still return the result the application is + * expecting. To do this, override this method to catch the call to start + * activity so that it returns a new ActivityResult containing the results + * you would like the application to see, and don't call up to the super + * class. Note that an application is only expecting a result if + * <var>requestCode</var> is >= 0. + * + * <p>This method throws {@link android.content.ActivityNotFoundException} + * if there was no Activity found to run the given Intent. + * + * @param who The Context from which the activity is being started. + * @param contextThread The main thread of the Context from which the activity + * is being started. + * @param token Internal token identifying to the system who is starting + * the activity; may be null. + * @param target Which activity is perform the start (and thus receiving + * any result); may be null if this call is not being made + * from an activity. + * @param intent The actual Intent to start. + * @param requestCode Identifier for this request's result; less than zero + * if the caller is not expecting a result. + * + * @return To force the return of a particular result, return an + * ActivityResult object containing the desired data; otherwise + * return null. The default implementation always returns null. + * + * @throws android.content.ActivityNotFoundException + * + * @see Activity#startActivity(Intent) + * @see Activity#startActivityForResult(Intent, int) + * @see Activity#startActivityFromChild + * + * {@hide} + */ + public ActivityResult execStartActivity( + Context who, IBinder contextThread, IBinder token, Activity target, + Intent intent, int requestCode) { + IApplicationThread whoThread = (IApplicationThread) contextThread; + if (mActivityMonitors != null) { + synchronized (mSync) { + final int N = mActivityMonitors.size(); + for (int i=0; i<N; i++) { + final ActivityMonitor am = mActivityMonitors.get(i); + if (am.match(who, null, intent)) { + am.mHits++; + if (am.isBlocking()) { + return requestCode >= 0 ? am.getResult() : null; + } + break; + } + } + } + } + try { + int result = ActivityManagerNative.getDefault() + .startActivity(whoThread, intent, + intent.resolveTypeIfNeeded(who.getContentResolver()), + null, 0, token, target != null ? target.mEmbeddedID : null, + requestCode, false, false); + checkStartActivityResult(result, intent); + } catch (RemoteException e) { + } + return null; + } + + /*package*/ final void init(ActivityThread thread, + Context instrContext, Context appContext, ComponentName component, + IInstrumentationWatcher watcher) { + mThread = thread; + mMessageQueue = mThread.getLooper().myQueue(); + mInstrContext = instrContext; + mAppContext = appContext; + mComponent = component; + mWatcher = watcher; + } + + /*package*/ static void checkStartActivityResult(int res, Intent intent) { + if (res >= IActivityManager.START_SUCCESS) { + return; + } + + switch (res) { + case IActivityManager.START_INTENT_NOT_RESOLVED: + case IActivityManager.START_CLASS_NOT_FOUND: + if (intent.getComponent() != null) + throw new ActivityNotFoundException( + "Unable to find explicit activity class " + + intent.getComponent().toShortString() + + "; have you declared this activity in your AndroidManifest.xml?"); + throw new ActivityNotFoundException( + "No Activity found to handle " + intent); + case IActivityManager.START_PERMISSION_DENIED: + throw new SecurityException("Not allowed to start activity " + + intent); + case IActivityManager.START_FORWARD_AND_REQUEST_CONFLICT: + throw new AndroidRuntimeException( + "FORWARD_RESULT_FLAG used while also requesting a result"); + default: + throw new AndroidRuntimeException("Unknown error code " + + res + " when starting " + intent); + } + } + + private final void validateNotAppThread() { + if (ActivityThread.currentActivityThread() != null) { + throw new RuntimeException( + "This method can not be called from the main application thread"); + } + } + + private final class InstrumentationThread extends Thread { + public InstrumentationThread(String name) { + super(name); + } + public void run() { + IActivityManager am = ActivityManagerNative.getDefault(); + try { + Process.setThreadPriority(Process.THREAD_PRIORITY_URGENT_DISPLAY); + } catch (RuntimeException e) { + Log.w(TAG, "Exception setting priority of instrumentation thread " + + Process.myTid(), e); + } + if (mAutomaticPerformanceSnapshots) { + startPerformanceSnapshot(); + } + onStart(); + } + } + + private static final class EmptyRunnable implements Runnable { + public void run() { + } + } + + private static final class SyncRunnable implements Runnable { + private final Runnable mTarget; + private boolean mComplete; + + public SyncRunnable(Runnable target) { + mTarget = target; + } + + public void run() { + mTarget.run(); + synchronized (this) { + mComplete = true; + notifyAll(); + } + } + + public void waitForComplete() { + synchronized (this) { + while (!mComplete) { + try { + wait(); + } catch (InterruptedException e) { + } + } + } + } + } + + private static final class ActivityWaiter { + public final Intent intent; + public Activity activity; + + public ActivityWaiter(Intent _intent) { + intent = _intent; + } + } + + private final class ActivityGoing implements MessageQueue.IdleHandler { + private final ActivityWaiter mWaiter; + + public ActivityGoing(ActivityWaiter waiter) { + mWaiter = waiter; + } + + public final boolean queueIdle() { + synchronized (mSync) { + mWaitingActivities.remove(mWaiter); + mSync.notifyAll(); + } + return false; + } + } + + private static final class Idler implements MessageQueue.IdleHandler { + private final Runnable mCallback; + private boolean mIdle; + + public Idler(Runnable callback) { + mCallback = callback; + mIdle = false; + } + + public final boolean queueIdle() { + if (mCallback != null) { + mCallback.run(); + } + synchronized (this) { + mIdle = true; + notifyAll(); + } + return false; + } + + public void waitForIdle() { + synchronized (this) { + while (!mIdle) { + try { + wait(); + } catch (InterruptedException e) { + } + } + } + } + } +} diff --git a/core/java/android/app/IntentService.java b/core/java/android/app/IntentService.java new file mode 100644 index 0000000..2b12a2a --- /dev/null +++ b/core/java/android/app/IntentService.java @@ -0,0 +1,74 @@ +package android.app; + +import android.content.Intent; +import android.os.Handler; +import android.os.HandlerThread; +import android.os.IBinder; +import android.os.Looper; +import android.os.Message; + +/** + * An abstract {@link Service} that serializes the handling of the Intents passed upon service + * start and handles them on a handler thread. + * + * <p>To use this class extend it and implement {@link #onHandleIntent}. The {@link Service} will + * automatically be stopped when the last enqueued {@link Intent} is handled. + */ +public abstract class IntentService extends Service { + private volatile Looper mServiceLooper; + private volatile ServiceHandler mServiceHandler; + private String mName; + + private final class ServiceHandler extends Handler { + public ServiceHandler(Looper looper) { + super(looper); + } + + @Override + public void handleMessage(Message msg) { + onHandleIntent((Intent)msg.obj); + stopSelf(msg.arg1); + } + } + + public IntentService(String name) { + super(); + mName = name; + } + + @Override + public void onCreate() { + super.onCreate(); + HandlerThread thread = new HandlerThread("IntentService[" + mName + "]"); + thread.start(); + + mServiceLooper = thread.getLooper(); + mServiceHandler = new ServiceHandler(mServiceLooper); + } + + @Override + public void onStart(Intent intent, int startId) { + super.onStart(intent, startId); + Message msg = mServiceHandler.obtainMessage(); + msg.arg1 = startId; + msg.obj = intent; + mServiceHandler.sendMessage(msg); + } + + @Override + public void onDestroy() { + mServiceLooper.quit(); + } + + @Override + public IBinder onBind(Intent intent) { + return null; + } + + /** + * Invoked on the Handler thread with the {@link Intent} that is passed to {@link #onStart}. + * Note that this will be invoked from a different thread than the one that handles the + * {@link #onStart} call. + */ + protected abstract void onHandleIntent(Intent intent); +} diff --git a/core/java/android/app/KeyguardManager.java b/core/java/android/app/KeyguardManager.java new file mode 100644 index 0000000..0c07553 --- /dev/null +++ b/core/java/android/app/KeyguardManager.java @@ -0,0 +1,155 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * 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 android.app; + +import android.content.Context; +import android.os.Binder; +import android.os.RemoteException; +import android.os.IBinder; +import android.os.ServiceManager; +import android.view.IWindowManager; +import android.view.IOnKeyguardExitResult; + +/** + * Class that can be used to lock and unlock the keyboard. Get an instance of this + * class by calling {@link android.content.Context#getSystemService(java.lang.String)} + * with argument {@link android.content.Context#KEYGUARD_SERVICE}. The + * Actual class to control the keyboard locking is + * {@link android.app.KeyguardManager.KeyguardLock}. + */ +public class KeyguardManager { + private IWindowManager mWM; + + /** + * Handle returned by {@link KeyguardManager#newKeyguardLock} that allows + * you to disable / reenable the keyguard. + */ + public class KeyguardLock { + private IBinder mToken = new Binder(); + private String mTag; + + KeyguardLock(String tag) { + mTag = tag; + } + + /** + * Disable the keyguard from showing. If the keyguard is currently + * showing, hide it. The keyguard will be prevented from showing again + * until {@link #reenableKeyguard()} is called. + * + * A good place to call this is from {@link android.app.Activity#onResume()} + * + * @see #reenableKeyguard() + */ + public void disableKeyguard() { + try { + mWM.disableKeyguard(mToken, mTag); + } catch (RemoteException ex) { + } + } + + /** + * Reenable the keyguard. The keyguard will reappear if the previous + * call to {@link #disableKeyguard()} caused it it to be hidden. + * + * A good place to call this is from {@link android.app.Activity#onPause()} + * + * @see #disableKeyguard() + */ + public void reenableKeyguard() { + try { + mWM.reenableKeyguard(mToken); + } catch (RemoteException ex) { + } + } + } + + /** + * Callback passed to {@link KeyguardManager#exitKeyguardSecurely} to notify + * caller of result. + */ + public interface OnKeyguardExitResult { + + /** + * @param success True if the user was able to authenticate, false if + * not. + */ + void onKeyguardExitResult(boolean success); + } + + + KeyguardManager() { + mWM = IWindowManager.Stub.asInterface(ServiceManager.getService(Context.WINDOW_SERVICE)); + } + + /** + * Enables you to lock or unlock the keyboard. Get an instance of this class by + * calling {@link android.content.Context#getSystemService(java.lang.String) Context.getSystemService()}. + * This class is wrapped by {@link android.app.KeyguardManager KeyguardManager}. + * @param tag A tag that informally identifies who you are (for debugging who + * is disabling he keyguard). + * + * @return A {@link KeyguardLock} handle to use to disable and reenable the + * keyguard. + */ + public KeyguardLock newKeyguardLock(String tag) { + return new KeyguardLock(tag); + } + + /** + * If keyguard screen is showing or in restricted key input mode (i.e. in + * keyguard password emergency screen). When in such mode, certain keys, + * such as the Home key and the right soft keys, don't work. + * + * @return true if in keyguard restricted input mode. + * + * @see android.view.WindowManagerPolicy#inKeyguardRestrictedKeyInputMode + */ + public boolean inKeyguardRestrictedInputMode() { + try { + return mWM.inKeyguardRestrictedInputMode(); + } catch (RemoteException ex) { + return false; + } + } + + /** + * Exit the keyguard securely. The use case for this api is that, after + * disabling the keyguard, your app, which was granted permission to + * disable the keyguard and show a limited amount of information deemed + * safe without the user getting past the keyguard, needs to navigate to + * something that is not safe to view without getting past the keyguard. + * + * This will, if the keyguard is secure, bring up the unlock screen of + * the keyguard. + * + * @param callback Let's you know whether the operation was succesful and + * it is safe to launch anything that would normally be considered safe + * once the user has gotten past the keyguard. + */ + public void exitKeyguardSecurely(final OnKeyguardExitResult callback) { + try { + mWM.exitKeyguardSecurely(new IOnKeyguardExitResult.Stub() { + public void onKeyguardExitResult(boolean success) throws RemoteException { + callback.onKeyguardExitResult(success); + } + }); + } catch (RemoteException e) { + + } + } +} diff --git a/core/java/android/app/LauncherActivity.java b/core/java/android/app/LauncherActivity.java new file mode 100644 index 0000000..d6fcbb1 --- /dev/null +++ b/core/java/android/app/LauncherActivity.java @@ -0,0 +1,380 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * 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 android.app; + +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.content.res.Resources; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.PaintFlagsDrawFilter; +import android.graphics.PixelFormat; +import android.graphics.Rect; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.Drawable; +import android.graphics.drawable.PaintDrawable; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.view.Window; +import android.widget.BaseAdapter; +import android.widget.Filter; +import android.widget.Filterable; +import android.widget.ListView; +import android.widget.TextView; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + + +/** + * Displays a list of all activities which can be performed + * for a given intent. Launches when clicked. + * + */ +public abstract class LauncherActivity extends ListActivity { + + Intent mIntent; + PackageManager mPackageManager; + + /** + * An item in the list + */ + public static class ListItem { + public CharSequence label; + //public CharSequence description; + public Drawable icon; + public String packageName; + public String className; + + ListItem(PackageManager pm, ResolveInfo resolveInfo, IconResizer resizer) { + label = resolveInfo.loadLabel(pm); + if (label == null && resolveInfo.activityInfo != null) { + label = resolveInfo.activityInfo.name; + } + + /* + if (resolveInfo.activityInfo != null && + resolveInfo.activityInfo.applicationInfo != null) { + description = resolveInfo.activityInfo.applicationInfo.loadDescription(pm); + } + */ + + icon = resizer.createIconThumbnail(resolveInfo.loadIcon(pm)); + packageName = resolveInfo.activityInfo.applicationInfo.packageName; + className = resolveInfo.activityInfo.name; + } + + public ListItem() { + } + } + + /** + * Adapter which shows the set of activities that can be performed for a given intent. + */ + private class ActivityAdapter extends BaseAdapter implements Filterable { + private final Object lock = new Object(); + private ArrayList<ListItem> mOriginalValues; + + protected final LayoutInflater mInflater; + + protected List<ListItem> mActivitiesList; + + private Filter mFilter; + + public ActivityAdapter() { + mInflater = (LayoutInflater) LauncherActivity.this.getSystemService( + Context.LAYOUT_INFLATER_SERVICE); + mActivitiesList = makeListItems(); + } + + public Intent intentForPosition(int position) { + if (mActivitiesList == null) { + return null; + } + + Intent intent = new Intent(mIntent); + ListItem item = mActivitiesList.get(position); + intent.setClassName(item.packageName, item.className); + return intent; + } + + public int getCount() { + return mActivitiesList != null ? mActivitiesList.size() : 0; + } + + public Object getItem(int position) { + return position; + } + + public long getItemId(int position) { + return position; + } + + public View getView(int position, View convertView, ViewGroup parent) { + View view; + if (convertView == null) { + view = mInflater.inflate( + com.android.internal.R.layout.activity_list_item_2, parent, false); + } else { + view = convertView; + } + bindView(view, mActivitiesList.get(position)); + return view; + } + + private void bindView(View view, ListItem item) { + TextView text = (TextView) view; + text.setText(item.label); + text.setCompoundDrawablesWithIntrinsicBounds(item.icon, null, null, null); + } + + public Filter getFilter() { + if (mFilter == null) { + mFilter = new ArrayFilter(); + } + return mFilter; + } + + /** + * An array filters constrains the content of the array adapter with a prefix. Each + * item that does not start with the supplied prefix is removed from the list. + */ + private class ArrayFilter extends Filter { + @Override + protected FilterResults performFiltering(CharSequence prefix) { + FilterResults results = new FilterResults(); + + if (mOriginalValues == null) { + synchronized (lock) { + mOriginalValues = new ArrayList<ListItem>(mActivitiesList); + } + } + + if (prefix == null || prefix.length() == 0) { + synchronized (lock) { + ArrayList<ListItem> list = new ArrayList<ListItem>(mOriginalValues); + results.values = list; + results.count = list.size(); + } + } else { + final String prefixString = prefix.toString().toLowerCase(); + + ArrayList<ListItem> values = mOriginalValues; + int count = values.size(); + + ArrayList<ListItem> newValues = new ArrayList<ListItem>(count); + + for (int i = 0; i < count; i++) { + ListItem item = values.get(i); + + String[] words = item.label.toString().toLowerCase().split(" "); + int wordCount = words.length; + + for (int k = 0; k < wordCount; k++) { + final String word = words[k]; + + if (word.startsWith(prefixString)) { + newValues.add(item); + break; + } + } + } + + results.values = newValues; + results.count = newValues.size(); + } + + return results; + } + + @Override + protected void publishResults(CharSequence constraint, FilterResults results) { + //noinspection unchecked + mActivitiesList = (List<ListItem>) results.values; + if (results.count > 0) { + notifyDataSetChanged(); + } else { + notifyDataSetInvalidated(); + } + } + } + } + + /** + * Utility class to resize icons to match default icon size. + */ + public class IconResizer { + // Code is borrowed from com.android.launcher.Utilities. + private int mIconWidth = -1; + private int mIconHeight = -1; + + private final Rect mOldBounds = new Rect(); + private Canvas mCanvas = new Canvas(); + + public IconResizer() { + mCanvas.setDrawFilter(new PaintFlagsDrawFilter(Paint.DITHER_FLAG, + Paint.FILTER_BITMAP_FLAG)); + + final Resources resources = LauncherActivity.this.getResources(); + mIconWidth = mIconHeight = (int) resources.getDimension( + android.R.dimen.app_icon_size); + } + + /** + * Returns a Drawable representing the thumbnail of the specified Drawable. + * The size of the thumbnail is defined by the dimension + * android.R.dimen.launcher_application_icon_size. + * + * This method is not thread-safe and should be invoked on the UI thread only. + * + * @param icon The icon to get a thumbnail of. + * + * @return A thumbnail for the specified icon or the icon itself if the + * thumbnail could not be created. + */ + public Drawable createIconThumbnail(Drawable icon) { + int width = mIconWidth; + int height = mIconHeight; + + final int iconWidth = icon.getIntrinsicWidth(); + final int iconHeight = icon.getIntrinsicHeight(); + + if (icon instanceof PaintDrawable) { + PaintDrawable painter = (PaintDrawable) icon; + painter.setIntrinsicWidth(width); + painter.setIntrinsicHeight(height); + } + + if (width > 0 && height > 0) { + if (width < iconWidth || height < iconHeight) { + final float ratio = (float) iconWidth / iconHeight; + + if (iconWidth > iconHeight) { + height = (int) (width / ratio); + } else if (iconHeight > iconWidth) { + width = (int) (height * ratio); + } + + final Bitmap.Config c = icon.getOpacity() != PixelFormat.OPAQUE ? + Bitmap.Config.ARGB_8888 : Bitmap.Config.RGB_565; + final Bitmap thumb = Bitmap.createBitmap(mIconWidth, mIconHeight, c); + final Canvas canvas = mCanvas; + canvas.setBitmap(thumb); + // Copy the old bounds to restore them later + // If we were to do oldBounds = icon.getBounds(), + // the call to setBounds() that follows would + // change the same instance and we would lose the + // old bounds + mOldBounds.set(icon.getBounds()); + final int x = (mIconWidth - width) / 2; + final int y = (mIconHeight - height) / 2; + icon.setBounds(x, y, x + width, y + height); + icon.draw(canvas); + icon.setBounds(mOldBounds); + icon = new BitmapDrawable(thumb); + } else if (iconWidth < width && iconHeight < height) { + final Bitmap.Config c = Bitmap.Config.ARGB_8888; + final Bitmap thumb = Bitmap.createBitmap(mIconWidth, mIconHeight, c); + final Canvas canvas = mCanvas; + canvas.setBitmap(thumb); + mOldBounds.set(icon.getBounds()); + final int x = (width - iconWidth) / 2; + final int y = (height - iconHeight) / 2; + icon.setBounds(x, y, x + iconWidth, y + iconHeight); + icon.draw(canvas); + icon.setBounds(mOldBounds); + icon = new BitmapDrawable(thumb); + } + } + + return icon; + } + } + + @Override + protected void onCreate(Bundle icicle) { + super.onCreate(icicle); + + mPackageManager = getPackageManager(); + + requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS); + setProgressBarIndeterminateVisibility(true); + setContentView(com.android.internal.R.layout.activity_list); + + + mIntent = new Intent(getTargetIntent()); + mIntent.setComponent(null); + mAdapter = new ActivityAdapter(); + + setListAdapter(mAdapter); + getListView().setTextFilterEnabled(true); + + setProgressBarIndeterminateVisibility(false); + } + + @Override + protected void onListItemClick(ListView l, View v, int position, long id) { + Intent intent = ((ActivityAdapter)mAdapter).intentForPosition(position); + + startActivity(intent); + } + + /** + * Return the actual Intent for a specific position in our + * {@link android.widget.ListView}. + * @param position The item whose Intent to return + */ + protected Intent intentForPosition(int position) { + ActivityAdapter adapter = (ActivityAdapter) mAdapter; + return adapter.intentForPosition(position); + } + + /** + * Get the base intent to use when running + * {@link PackageManager#queryIntentActivities(Intent, int)}. + */ + protected Intent getTargetIntent() { + return new Intent(); + } + + /** + * Perform the query to determine which results to show and return a list of them. + */ + public List<ListItem> makeListItems() { + // Load all matching activities and sort correctly + List<ResolveInfo> list = mPackageManager.queryIntentActivities(mIntent, + /* no flags */ 0); + Collections.sort(list, new ResolveInfo.DisplayNameComparator(mPackageManager)); + + IconResizer resizer = new IconResizer(); + + ArrayList<ListItem> result = new ArrayList<ListItem>(list.size()); + int listSize = list.size(); + for (int i = 0; i < listSize; i++) { + ResolveInfo resolveInfo = list.get(i); + result.add(new ListItem(mPackageManager, resolveInfo, resizer)); + } + + return result; + } +} diff --git a/core/java/android/app/ListActivity.java b/core/java/android/app/ListActivity.java new file mode 100644 index 0000000..5523c18 --- /dev/null +++ b/core/java/android/app/ListActivity.java @@ -0,0 +1,316 @@ +/* + * Copyright (C) 2006 The Android Open Source Project + * + * 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 android.app; + +import android.os.Bundle; +import android.os.Handler; +import android.view.KeyEvent; +import android.view.View; +import android.widget.Adapter; +import android.widget.AdapterView; +import android.widget.ListAdapter; +import android.widget.ListView; + +/** + * An activity that displays a list of items by binding to a data source such as + * an array or Cursor, and exposes event handlers when the user selects an item. + * <p> + * ListActivity hosts a {@link android.widget.ListView ListView} object that can + * be bound to different data sources, typically either an array or a Cursor + * holding query results. Binding, screen layout, and row layout are discussed + * in the following sections. + * <p> + * <strong>Screen Layout</strong> + * </p> + * <p> + * ListActivity has a default layout that consists of a single, full-screen list + * in the center of the screen. However, if you desire, you can customize the + * screen layout by setting your own view layout with setContentView() in + * onCreate(). To do this, your own view MUST contain a ListView object with the + * id "@android:id/list" (or {@link android.R.id#list} if it's in code) + * <p> + * Optionally, your custom view can contain another view object of any type to + * display when the list view is empty. This "empty list" notifier must have an + * id "android:empty". Note that when an empty view is present, the list view + * will be hidden when there is no data to display. + * <p> + * The following code demonstrates an (ugly) custom screen layout. It has a list + * with a green background, and an alternate red "no data" message. + * </p> + * + * <pre> + * <?xml version="1.0" encoding="utf-8"?> + * <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + * android:orientation="vertical" + * android:layout_width="fill_parent" + * android:layout_height="fill_parent" + * android:paddingLeft="8dp" + * android:paddingRight="8dp"> + * + * <ListView android:id="@id/android:list" + * android:layout_width="fill_parent" + * android:layout_height="fill_parent" + * android:background="#00FF00" + * android:layout_weight="1" + * android:drawSelectorOnTop="false"/> + * + * <TextView id="@id/android:empty" + * android:layout_width="fill_parent" + * android:layout_height="fill_parent" + * android:background="#FF0000" + * android:text="No data"/> + * </LinearLayout> + * </pre> + * + * <p> + * <strong>Row Layout</strong> + * </p> + * <p> + * You can specify the layout of individual rows in the list. You do this by + * specifying a layout resource in the ListAdapter object hosted by the activity + * (the ListAdapter binds the ListView to the data; more on this later). + * <p> + * A ListAdapter constructor takes a parameter that specifies a layout resource + * for each row. It also has two additional parameters that let you specify + * which data field to associate with which object in the row layout resource. + * These two parameters are typically parallel arrays. + * </p> + * <p> + * Android provides some standard row layout resources. These are in the + * {@link android.R.layout} class, and have names such as simple_list_item_1, + * simple_list_item_2, and two_line_list_item. The following layout XML is the + * source for the resource two_line_list_item, which displays two data + * fields,one above the other, for each list row. + * </p> + * + * <pre> + * <?xml version="1.0" encoding="utf-8"?> + * <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + * android:layout_width="fill_parent" + * android:layout_height="wrap_content" + * android:orientation="vertical"> + * + * <TextView android:id="@+id/text1" + * android:textSize="16sp" + * android:textStyle="bold" + * android:layout_width="fill_parent" + * android:layout_height="wrap_content"/> + * + * <TextView android:id="@+id/text2" + * android:textSize="16sp" + * android:layout_width="fill_parent" + * android:layout_height="wrap_content"/> + * </LinearLayout> + * </pre> + * + * <p> + * You must identify the data bound to each TextView object in this layout. The + * syntax for this is discussed in the next section. + * </p> + * <p> + * <strong>Binding to Data</strong> + * </p> + * <p> + * You bind the ListActivity's ListView object to data using a class that + * implements the {@link android.widget.ListAdapter ListAdapter} interface. + * Android provides two standard list adapters: + * {@link android.widget.SimpleAdapter SimpleAdapter} for static data (Maps), + * and {@link android.widget.SimpleCursorAdapter SimpleCursorAdapter} for Cursor + * query results. + * </p> + * <p> + * The following code from a custom ListActivity demonstrates querying the + * Contacts provider for all contacts, then binding the Name and Company fields + * to a two line row layout in the activity's ListView. + * </p> + * + * <pre> + * public class MyListAdapter extends ListActivity { + * + * @Override + * protected void onCreate(Bundle savedInstanceState){ + * super.onCreate(savedInstanceState); + * + * // We'll define a custom screen layout here (the one shown above), but + * // typically, you could just use the standard ListActivity layout. + * setContentView(R.layout.custom_list_activity_view); + * + * // Query for all people contacts using the {@link android.provider.Contacts.People} convenience class. + * // Put a managed wrapper around the retrieved cursor so we don't have to worry about + * // requerying or closing it as the activity changes state. + * mCursor = People.query(this.getContentResolver(), null); + * startManagingCursor(mCursor); + * + * // Now create a new list adapter bound to the cursor. + * // SimpleListAdapter is designed for binding to a Cursor. + * ListAdapter adapter = new SimpleCursorAdapter( + * this, // Context. + * android.R.layout.two_line_list_item, // Specify the row template to use (here, two columns bound to the two retrieved cursor + * rows). + * mCursor, // Pass in the cursor to bind to. + * new String[] {People.NAME, People.COMPANY}, // Array of cursor columns to bind to. + * new int[]); // Parallel array of which template objects to bind to those columns. + * + * // Bind to our new adapter. + * setListAdapter(adapter); + * } + * } + * </pre> + * + * @see #setListAdapter + * @see android.widget.ListView + */ +public class ListActivity extends Activity { + /** + * This field should be made private, so it is hidden from the SDK. + * {@hide} + */ + protected ListAdapter mAdapter; + /** + * This field should be made private, so it is hidden from the SDK. + * {@hide} + */ + protected ListView mList; + + private Handler mHandler = new Handler(); + private boolean mFinishedStart = false; + + private Runnable mRequestFocus = new Runnable() { + public void run() { + mList.focusableViewAvailable(mList); + } + }; + + /** + * This method will be called when an item in the list is selected. + * Subclasses should override. Subclasses can call + * getListView().getItemAtPosition(position) if they need to access the + * data associated with the selected item. + * + * @param l The ListView where the click happened + * @param v The view that was clicked within the ListView + * @param position The position of the view in the list + * @param id The row id of the item that was clicked + */ + protected void onListItemClick(ListView l, View v, int position, long id) { + } + + /** + * Ensures the list view has been created before Activity restores all + * of the view states. + * + *@see Activity#onRestoreInstanceState(Bundle) + */ + @Override + protected void onRestoreInstanceState(Bundle state) { + ensureList(); + super.onRestoreInstanceState(state); + } + + /** + * Updates the screen state (current list and other views) when the + * content changes. + * + * @see Activity#onContentChanged() + */ + @Override + public void onContentChanged() { + super.onContentChanged(); + View emptyView = findViewById(com.android.internal.R.id.empty); + mList = (ListView)findViewById(com.android.internal.R.id.list); + if (mList == null) { + throw new RuntimeException( + "Your content must have a ListView whose id attribute is " + + "'android.R.id.list'"); + } + if (emptyView != null) { + mList.setEmptyView(emptyView); + } + mList.setOnItemClickListener(mOnClickListener); + if (mFinishedStart) { + setListAdapter(mAdapter); + } + mHandler.post(mRequestFocus); + mFinishedStart = true; + } + + /** + * Provide the cursor for the list view. + */ + public void setListAdapter(ListAdapter adapter) { + synchronized (this) { + ensureList(); + mAdapter = adapter; + mList.setAdapter(adapter); + } + } + + /** + * Set the currently selected list item to the specified + * position with the adapter's data + * + * @param position + */ + public void setSelection(int position) { + mList.setSelection(position); + } + + /** + * Get the position of the currently selected list item. + */ + public int getSelectedItemPosition() { + return mList.getSelectedItemPosition(); + } + + /** + * Get the cursor row ID of the currently selected list item. + */ + public long getSelectedItemId() { + return mList.getSelectedItemId(); + } + + /** + * Get the activity's list view widget. + */ + public ListView getListView() { + ensureList(); + return mList; + } + + /** + * Get the ListAdapter associated with this activity's ListView. + */ + public ListAdapter getListAdapter() { + return mAdapter; + } + + private void ensureList() { + if (mList != null) { + return; + } + setContentView(com.android.internal.R.layout.list_content); + + } + + private AdapterView.OnItemClickListener mOnClickListener = new AdapterView.OnItemClickListener() { + public void onItemClick(AdapterView parent, View v, int position, long id) + { + onListItemClick((ListView)parent, v, position, id); + } + }; +} + diff --git a/core/java/android/app/LocalActivityManager.java b/core/java/android/app/LocalActivityManager.java new file mode 100644 index 0000000..a24fcae --- /dev/null +++ b/core/java/android/app/LocalActivityManager.java @@ -0,0 +1,627 @@ +/* + * Copyright (C) 2006 The Android Open Source Project + * + * 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 android.app; + +import android.content.Intent; +import android.content.pm.ActivityInfo; +import android.os.Binder; +import android.os.Bundle; +import android.util.Config; +import android.util.Log; +import android.view.Window; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; + +/** + * Helper class for managing multiple running embedded activities in the same + * process. This class is not normally used directly, but rather created for + * you as part of the {@link android.app.ActivityGroup} implementation. + * + * @see ActivityGroup + */ +public class LocalActivityManager { + private static final String TAG = "LocalActivityManager"; + private static final boolean localLOGV = false || Config.LOGV; + + // Internal token for an Activity being managed by LocalActivityManager. + private static class LocalActivityRecord extends Binder { + LocalActivityRecord(String _id, Intent _intent) { + id = _id; + intent = _intent; + } + + final String id; // Unique name of this record. + Intent intent; // Which activity to run here. + ActivityInfo activityInfo; // Package manager info about activity. + Activity activity; // Currently instantiated activity. + Window window; // Activity's top-level window. + Bundle instanceState; // Last retrieved freeze state. + int curState = RESTORED; // Current state the activity is in. + } + + static final int RESTORED = 0; // State restored, but no startActivity(). + static final int INITIALIZING = 1; // Ready to launch (after startActivity()). + static final int CREATED = 2; // Created, not started or resumed. + static final int STARTED = 3; // Created and started, not resumed. + static final int RESUMED = 4; // Created started and resumed. + static final int DESTROYED = 5; // No longer with us. + + /** Thread our activities are running in. */ + private final ActivityThread mActivityThread; + /** The containing activity that owns the activities we create. */ + private final Activity mParent; + + /** The activity that is currently resumed. */ + private LocalActivityRecord mResumed; + /** id -> record of all known activities. */ + private final Map<String, LocalActivityRecord> mActivities + = new HashMap<String, LocalActivityRecord>(); + /** array of all known activities for easy iterating. */ + private final ArrayList<LocalActivityRecord> mActivityArray + = new ArrayList<LocalActivityRecord>(); + + /** True if only one activity can be resumed at a time */ + private boolean mSingleMode; + + /** Set to true once we find out the container is finishing. */ + private boolean mFinishing; + + /** Current state the owner (ActivityGroup) is in */ + private int mCurState = INITIALIZING; + + /** String ids of running activities starting with least recently used. */ + // TODO: put back in stopping of activities. + //private List<LocalActivityRecord> mLRU = new ArrayList(); + + /** + * Create a new LocalActivityManager for holding activities running within + * the given <var>parent</var>. + * + * @param parent the host of the embedded activities + * @param singleMode True if the LocalActivityManger should keep a maximum + * of one activity resumed + */ + public LocalActivityManager(Activity parent, boolean singleMode) { + mActivityThread = ActivityThread.currentActivityThread(); + mParent = parent; + mSingleMode = singleMode; + } + + private void moveToState(LocalActivityRecord r, int desiredState) { + if (r.curState == RESTORED || r.curState == DESTROYED) { + // startActivity() has not yet been called, so nothing to do. + return; + } + + if (r.curState == INITIALIZING) { + // Get the lastNonConfigurationInstance for the activity + HashMap<String,Object> lastNonConfigurationInstances = + mParent.getLastNonConfigurationChildInstances(); + Object instance = null; + if (lastNonConfigurationInstances != null) { + instance = lastNonConfigurationInstances.get(r.id); + } + + // We need to have always created the activity. + if (localLOGV) Log.v(TAG, r.id + ": starting " + r.intent); + if (r.activityInfo == null) { + r.activityInfo = mActivityThread.resolveActivityInfo(r.intent); + } + r.activity = mActivityThread.startActivityNow( + mParent, r.id, r.intent, r.activityInfo, r, r.instanceState, instance); + if (r.activity == null) { + return; + } + r.window = r.activity.getWindow(); + r.instanceState = null; + r.curState = STARTED; + + if (desiredState == RESUMED) { + if (localLOGV) Log.v(TAG, r.id + ": resuming"); + mActivityThread.performResumeActivity(r, true); + r.curState = RESUMED; + } + + // Don't do anything more here. There is an important case: + // if this is being done as part of onCreate() of the group, then + // the launching of the activity gets its state a little ahead + // of our own (it is now STARTED, while we are only CREATED). + // If we just leave things as-is, we'll deal with it as the + // group's state catches up. + return; + } + + switch (r.curState) { + case CREATED: + if (desiredState == STARTED) { + if (localLOGV) Log.v(TAG, r.id + ": restarting"); + mActivityThread.performRestartActivity(r); + r.curState = STARTED; + } + if (desiredState == RESUMED) { + if (localLOGV) Log.v(TAG, r.id + ": restarting and resuming"); + mActivityThread.performRestartActivity(r); + mActivityThread.performResumeActivity(r, true); + r.curState = RESUMED; + } + return; + + case STARTED: + if (desiredState == RESUMED) { + // Need to resume it... + if (localLOGV) Log.v(TAG, r.id + ": resuming"); + mActivityThread.performResumeActivity(r, true); + r.instanceState = null; + r.curState = RESUMED; + } + if (desiredState == CREATED) { + if (localLOGV) Log.v(TAG, r.id + ": stopping"); + mActivityThread.performStopActivity(r); + r.curState = CREATED; + } + return; + + case RESUMED: + if (desiredState == STARTED) { + if (localLOGV) Log.v(TAG, r.id + ": pausing"); + performPause(r, mFinishing); + r.curState = STARTED; + } + if (desiredState == CREATED) { + if (localLOGV) Log.v(TAG, r.id + ": pausing"); + performPause(r, mFinishing); + if (localLOGV) Log.v(TAG, r.id + ": stopping"); + mActivityThread.performStopActivity(r); + r.curState = CREATED; + } + return; + } + } + + private void performPause(LocalActivityRecord r, boolean finishing) { + boolean needState = r.instanceState == null; + Bundle instanceState = mActivityThread.performPauseActivity(r, + finishing, needState); + if (needState) { + r.instanceState = instanceState; + } + } + + /** + * Start a new activity running in the group. Every activity you start + * must have a unique string ID associated with it -- this is used to keep + * track of the activity, so that if you later call startActivity() again + * on it the same activity object will be retained. + * + * <p>When there had previously been an activity started under this id, + * it may either be destroyed and a new one started, or the current + * one re-used, based on these conditions, in order:</p> + * + * <ul> + * <li> If the Intent maps to a different activity component than is + * currently running, the current activity is finished and a new one + * started. + * <li> If the current activity uses a non-multiple launch mode (such + * as singleTop), or the Intent has the + * {@link Intent#FLAG_ACTIVITY_SINGLE_TOP} flag set, then the current + * activity will remain running and its + * {@link Activity#onNewIntent(Intent) Activity.onNewIntent()} method + * called. + * <li> If the new Intent is the same (excluding extras) as the previous + * one, and the new Intent does not have the + * {@link Intent#FLAG_ACTIVITY_CLEAR_TOP} set, then the current activity + * will remain running as-is. + * <li> Otherwise, the current activity will be finished and a new + * one started. + * </ul> + * + * <p>If the given Intent can not be resolved to an available Activity, + * this method throws {@link android.content.ActivityNotFoundException}. + * + * <p>Warning: There is an issue where, if the Intent does not + * include an explicit component, we can restore the state for a different + * activity class than was previously running when the state was saved (if + * the set of available activities changes between those points). + * + * @param id Unique identifier of the activity to be started + * @param intent The Intent describing the activity to be started + * + * @return Returns the window of the activity. The caller needs to take + * care of adding this window to a view hierarchy, and likewise dealing + * with removing the old window if the activity has changed. + * + * @throws android.content.ActivityNotFoundException + */ + public Window startActivity(String id, Intent intent) { + if (mCurState == INITIALIZING) { + throw new IllegalStateException( + "Activities can't be added until the containing group has been created."); + } + + boolean adding = false; + boolean sameIntent = false; + + ActivityInfo aInfo = null; + + // Already have information about the new activity id? + LocalActivityRecord r = mActivities.get(id); + if (r == null) { + // Need to create it... + r = new LocalActivityRecord(id, intent); + adding = true; + } else if (r.intent != null) { + sameIntent = r.intent.filterEquals(intent); + if (sameIntent) { + // We are starting the same activity. + aInfo = r.activityInfo; + } + } + if (aInfo == null) { + aInfo = mActivityThread.resolveActivityInfo(intent); + } + + // Pause the currently running activity if there is one and only a single + // activity is allowed to be running at a time. + if (mSingleMode) { + LocalActivityRecord old = mResumed; + + // If there was a previous activity, and it is not the current + // activity, we need to stop it. + if (old != null && old != r && mCurState == RESUMED) { + moveToState(old, STARTED); + } + } + + if (adding) { + // It's a brand new world. + mActivities.put(id, r); + mActivityArray.add(r); + } else if (r.activityInfo != null) { + // If the new activity is the same as the current one, then + // we may be able to reuse it. + if (aInfo == r.activityInfo || + (aInfo.name.equals(r.activityInfo.name) && + aInfo.packageName.equals(r.activityInfo.packageName))) { + if (aInfo.launchMode != ActivityInfo.LAUNCH_MULTIPLE || + (intent.getFlags()&Intent.FLAG_ACTIVITY_SINGLE_TOP) != 0) { + // The activity wants onNewIntent() called. + ArrayList<Intent> intents = new ArrayList<Intent>(1); + intents.add(intent); + if (localLOGV) Log.v(TAG, r.id + ": new intent"); + mActivityThread.performNewIntents(r, intents); + r.intent = intent; + moveToState(r, mCurState); + if (mSingleMode) { + mResumed = r; + } + return r.window; + } + if (sameIntent && + (intent.getFlags()&Intent.FLAG_ACTIVITY_CLEAR_TOP) == 0) { + // We are showing the same thing, so this activity is + // just resumed and stays as-is. + r.intent = intent; + moveToState(r, mCurState); + if (mSingleMode) { + mResumed = r; + } + return r.window; + } + } + + // The new activity is different than the current one, or it + // is a multiple launch activity, so we need to destroy what + // is currently there. + performDestroy(r, true); + } + + r.intent = intent; + r.curState = INITIALIZING; + r.activityInfo = aInfo; + + moveToState(r, mCurState); + + // When in single mode keep track of the current activity + if (mSingleMode) { + mResumed = r; + } + return r.window; + } + + private Window performDestroy(LocalActivityRecord r, boolean finish) { + Window win = null; + win = r.window; + if (r.curState == RESUMED && !finish) { + performPause(r, finish); + } + if (localLOGV) Log.v(TAG, r.id + ": destroying"); + mActivityThread.performDestroyActivity(r, finish); + r.activity = null; + r.window = null; + if (finish) { + r.instanceState = null; + } + r.curState = DESTROYED; + return win; + } + + /** + * Destroy the activity associated with a particular id. This activity + * will go through the normal lifecycle events and fine onDestroy(), and + * then the id removed from the group. + * + * @param id Unique identifier of the activity to be destroyed + * @param finish If true, this activity will be finished, so its id and + * all state are removed from the group. + * + * @return Returns the window that was used to display the activity, or + * null if there was none. + */ + public Window destroyActivity(String id, boolean finish) { + LocalActivityRecord r = mActivities.get(id); + Window win = null; + if (r != null) { + win = performDestroy(r, finish); + if (finish) { + mActivities.remove(r); + } + } + return win; + } + + /** + * Retrieve the Activity that is currently running. + * + * @return the currently running (resumed) Activity, or null if there is + * not one + * + * @see #startActivity + * @see #getCurrentId + */ + public Activity getCurrentActivity() { + return mResumed != null ? mResumed.activity : null; + } + + /** + * Retrieve the ID of the activity that is currently running. + * + * @return the ID of the currently running (resumed) Activity, or null if + * there is not one + * + * @see #startActivity + * @see #getCurrentActivity + */ + public String getCurrentId() { + return mResumed != null ? mResumed.id : null; + } + + /** + * Return the Activity object associated with a string ID. + * + * @see #startActivity + * + * @return the associated Activity object, or null if the id is unknown or + * its activity is not currently instantiated + */ + public Activity getActivity(String id) { + LocalActivityRecord r = mActivities.get(id); + return r != null ? r.activity : null; + } + + /** + * Restore a state that was previously returned by {@link #saveInstanceState}. This + * adds to the activity group information about all activity IDs that had + * previously been saved, even if they have not been started yet, so if the + * user later navigates to them the correct state will be restored. + * + * <p>Note: This does <b>not</b> change the current running activity, or + * start whatever activity was previously running when the state was saved. + * That is up to the client to do, in whatever way it thinks is best. + * + * @param state a previously saved state; does nothing if this is null + * + * @see #saveInstanceState + */ + public void dispatchCreate(Bundle state) { + if (state != null) { + final Iterator<String> i = state.keySet().iterator(); + while (i.hasNext()) { + try { + final String id = i.next(); + final Bundle astate = state.getBundle(id); + LocalActivityRecord r = mActivities.get(id); + if (r != null) { + r.instanceState = astate; + } else { + r = new LocalActivityRecord(id, null); + r.instanceState = astate; + mActivities.put(id, r); + mActivityArray.add(r); + } + } catch (Exception e) { + // Recover from -all- app errors. + Log.e(TAG, + "Exception thrown when restoring LocalActivityManager state", + e); + } + } + } + + mCurState = CREATED; + } + + /** + * Retrieve the state of all activities known by the group. For + * activities that have previously run and are now stopped or finished, the + * last saved state is used. For the current running activity, its + * {@link Activity#onSaveInstanceState} is called to retrieve its current state. + * + * @return a Bundle holding the newly created state of all known activities + * + * @see #dispatchCreate + */ + public Bundle saveInstanceState() { + Bundle state = null; + + // FIXME: child activities will freeze as part of onPaused. Do we + // need to do this here? + final int N = mActivityArray.size(); + for (int i=0; i<N; i++) { + final LocalActivityRecord r = mActivityArray.get(i); + if (state == null) { + state = new Bundle(); + } + if ((r.instanceState != null || r.curState == RESUMED) + && r.activity != null) { + // We need to save the state now, if we don't currently + // already have it or the activity is currently resumed. + final Bundle childState = new Bundle(); + r.activity.onSaveInstanceState(childState); + r.instanceState = childState; + } + if (r.instanceState != null) { + state.putBundle(r.id, r.instanceState); + } + } + + return state; + } + + /** + * Called by the container activity in its {@link Activity#onResume} so + * that LocalActivityManager can perform the corresponding action on the + * activities it holds. + * + * @see Activity#onResume + */ + public void dispatchResume() { + mCurState = RESUMED; + if (mSingleMode) { + if (mResumed != null) { + moveToState(mResumed, RESUMED); + } + } else { + final int N = mActivityArray.size(); + for (int i=0; i<N; i++) { + moveToState(mActivityArray.get(i), RESUMED); + } + } + } + + /** + * Called by the container activity in its {@link Activity#onPause} so + * that LocalActivityManager can perform the corresponding action on the + * activities it holds. + * + * @param finishing set to true if the parent activity has been finished; + * this can be determined by calling + * Activity.isFinishing() + * + * @see Activity#onPause + * @see Activity#isFinishing + */ + public void dispatchPause(boolean finishing) { + if (finishing) { + mFinishing = true; + } + mCurState = STARTED; + if (mSingleMode) { + if (mResumed != null) { + moveToState(mResumed, STARTED); + } + } else { + final int N = mActivityArray.size(); + for (int i=0; i<N; i++) { + LocalActivityRecord r = mActivityArray.get(i); + if (r.curState == RESUMED) { + moveToState(r, STARTED); + } + } + } + } + + /** + * Called by the container activity in its {@link Activity#onStop} so + * that LocalActivityManager can perform the corresponding action on the + * activities it holds. + * + * @see Activity#onStop + */ + public void dispatchStop() { + mCurState = CREATED; + final int N = mActivityArray.size(); + for (int i=0; i<N; i++) { + LocalActivityRecord r = mActivityArray.get(i); + moveToState(r, CREATED); + } + } + + /** + * Call onRetainNonConfigurationInstance on each child activity and store the + * results in a HashMap by id. Only construct the HashMap if there is a non-null + * object to store. Note that this does not support nested ActivityGroups. + * + * {@hide} + */ + public HashMap<String,Object> dispatchRetainNonConfigurationInstance() { + HashMap<String,Object> instanceMap = null; + + final int N = mActivityArray.size(); + for (int i=0; i<N; i++) { + LocalActivityRecord r = mActivityArray.get(i); + if ((r != null) && (r.activity != null)) { + Object instance = r.activity.onRetainNonConfigurationInstance(); + if (instance != null) { + if (instanceMap == null) { + instanceMap = new HashMap<String,Object>(); + } + instanceMap.put(r.id, instance); + } + } + } + return instanceMap; + } + + /** + * Remove all activities from this LocalActivityManager, performing an + * {@link Activity#onDestroy} on any that are currently instantiated. + */ + public void removeAllActivities() { + dispatchDestroy(true); + } + + /** + * Called by the container activity in its {@link Activity#onDestroy} so + * that LocalActivityManager can perform the corresponding action on the + * activities it holds. + * + * @see Activity#onDestroy + */ + public void dispatchDestroy(boolean finishing) { + final int N = mActivityArray.size(); + for (int i=0; i<N; i++) { + LocalActivityRecord r = mActivityArray.get(i); + if (localLOGV) Log.v(TAG, r.id + ": destroying"); + mActivityThread.performDestroyActivity(r, finishing); + } + mActivities.clear(); + mActivityArray.clear(); + } +} diff --git a/core/java/android/app/Notification.aidl b/core/java/android/app/Notification.aidl new file mode 100644 index 0000000..9d8129c --- /dev/null +++ b/core/java/android/app/Notification.aidl @@ -0,0 +1,19 @@ +/** + * Copyright (c) 2007, The Android Open Source Project + * + * 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 android.app; + +parcelable Notification; diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java new file mode 100644 index 0000000..51fddb1 --- /dev/null +++ b/core/java/android/app/Notification.java @@ -0,0 +1,483 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * 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 android.app; + +import java.util.Date; + +import android.app.PendingIntent; +import android.content.Context; +import android.content.Intent; +import android.media.AudioManager; +import android.net.Uri; +import android.os.Parcel; +import android.os.Parcelable; +import android.text.TextUtils; +import android.text.format.DateFormat; +import android.text.format.DateUtils; +import android.widget.RemoteViews; + +/** + * A class that represents how a persistent notification is to be presented to + * the user using the {@link android.app.NotificationManager}. + * + */ +public class Notification implements Parcelable +{ + /** + * Use all default values (where applicable). + */ + public static final int DEFAULT_ALL = ~0; + + /** + * Use the default notification sound. This will ignore any given + * {@link #sound}. + * + * @see #defaults + */ + public static final int DEFAULT_SOUND = 1; + + /** + * Use the default notification vibrate. This will ignore any given + * {@link #vibrate}. + * + * @see #defaults + */ + public static final int DEFAULT_VIBRATE = 2; + + /** + * Use the default notification lights. This will ignore the + * {@link #FLAG_SHOW_LIGHTS} bit, and {@link #ledARGB}, {@link #ledOffMS}, or + * {@link #ledOnMS}. + * + * @see #defaults + */ + public static final int DEFAULT_LIGHTS = 4; + + /** + * The timestamp for the notification. The icons and expanded views + * are sorted by this key. + */ + public long when; + + /** + * The resource id of a drawable to use as the icon in the status bar. + */ + public int icon; + + /** + * The number of events that this notification represents. For example, if this is the + * new mail notification, this would be the number of unread messages. This number is + * be superimposed over the icon in the status bar. If the number is 0 or negative, it + * is not shown in the status bar. + */ + public int number; + + /** + * The intent to execute when the expanded status entry is clicked. If + * this is an activity, it must include the + * {@link android.content.Intent#FLAG_ACTIVITY_NEW_TASK} flag, which requires + * that you take care of task management as described in the <em>Activities and Tasks</em> + * section of the <a href="{@docRoot}guide/topics/fundamentals.html#acttask">Application + * Fundamentals</a> document. + */ + public PendingIntent contentIntent; + + /** + * The intent to execute when the status entry is deleted by the user + * with the "Clear All Notifications" button. This probably shouldn't + * be launching an activity since several of those will be sent at the + * same time. + */ + public PendingIntent deleteIntent; + + /** + * Text to scroll across the screen when this item is added to + * the status bar. + */ + public CharSequence tickerText; + + /** + * The view that shows when this notification is shown in the expanded status bar. + */ + public RemoteViews contentView; + + /** + * If the icon in the status bar is to have more than one level, you can set this. Otherwise, + * leave it at its default value of 0. + * + * @see android.widget.ImageView#setImageLevel + * @see android.graphics.drawable#setLevel + */ + public int iconLevel; + + /** + * The sound to play. + * + * <p> + * To play the default notification sound, see {@link #defaults}. + * </p> + */ + public Uri sound; + + /** + * Use this constant as the value for audioStreamType to request that + * the default stream type for notifications be used. Currently the + * default stream type is STREAM_RING. + */ + public static final int STREAM_DEFAULT = -1; + + /** + * The audio stream type to use when playing the sound. + * Should be one of the STREAM_ constants from + * {@link android.media.AudioManager}. + */ + public int audioStreamType = STREAM_DEFAULT; + + + /** + * The pattern with which to vibrate. This pattern will repeat if {@link + * #FLAG_INSISTENT} bit is set in the {@link #flags} field. + * + * <p> + * To vibrate the default pattern, see {@link #defaults}. + * </p> + * + * @see android.os.Vibrator#vibrate(long[],int) + */ + public long[] vibrate; + + /** + * The color of the led. The hardware will do its best approximation. + * + * @see #FLAG_SHOW_LIGHTS + * @see #flags + */ + public int ledARGB; + + /** + * The number of milliseconds for the LED to be on while it's flashing. + * The hardware will do its best approximation. + * + * @see #FLAG_SHOW_LIGHTS + * @see #flags + */ + public int ledOnMS; + + /** + * The number of milliseconds for the LED to be off while it's flashing. + * The hardware will do its best approximation. + * + * @see #FLAG_SHOW_LIGHTS + * @see #flags + */ + public int ledOffMS; + + /** + * Specifies which values should be taken from the defaults. + * <p> + * To set, OR the desired from {@link #DEFAULT_SOUND}, + * {@link #DEFAULT_VIBRATE}, {@link #DEFAULT_LIGHTS}. For all default + * values, use {@link #DEFAULT_ALL}. + * </p> + */ + public int defaults; + + + /** + * Bit to be bitwise-ored into the {@link #flags} field that should be + * set if you want the LED on for this notification. + * <ul> + * <li>To turn the LED off, pass 0 in the alpha channel for colorARGB + * or 0 for both ledOnMS and ledOffMS.</li> + * <li>To turn the LED on, pass 1 for ledOnMS and 0 for ledOffMS.</li> + * <li>To flash the LED, pass the number of milliseconds that it should + * be on and off to ledOnMS and ledOffMS.</li> + * </ul> + * <p> + * Since hardware varies, you are not guaranteed that any of the values + * you pass are honored exactly. Use the system defaults (TODO) if possible + * because they will be set to values that work on any given hardware. + * <p> + * The alpha channel must be set for forward compatibility. + * + */ + public static final int FLAG_SHOW_LIGHTS = 0x00000001; + + /** + * Bit to be bitwise-ored into the {@link #flags} field that should be + * set if this notification is in reference to something that is ongoing, + * like a phone call. It should not be set if this notification is in + * reference to something that happened at a particular point in time, + * like a missed phone call. + */ + public static final int FLAG_ONGOING_EVENT = 0x00000002; + + /** + * Bit to be bitwise-ored into the {@link #flags} field that if set, + * the audio and vibration will be repeated until the notification is + * cancelled. + * + * <p> + * NOTE: This notion will change when we have decided exactly + * what the UI will be. + * </p> + */ + public static final int FLAG_INSISTENT = 0x00000004; + + /** + * Bit to be bitwise-ored into the {@link #flags} field that should be + * set if you want the sound and/or vibration play each time the + * notification is sent, even if it has not been canceled before that. + */ + public static final int FLAG_ONLY_ALERT_ONCE = 0x00000008; + + /** + * Bit to be bitwise-ored into the {@link #flags} field that should be + * set if the notification should be canceled when it is clicked by the + * user. + */ + public static final int FLAG_AUTO_CANCEL = 0x00000010; + + /** + * Bit to be bitwise-ored into the {@link #flags} field that should be + * set if the notification should not be canceled when the user clicks + * the Clear all button. + */ + public static final int FLAG_NO_CLEAR = 0x00000020; + + public int flags; + + /** + * Constructs a Notification object with everything set to 0. + */ + public Notification() + { + this.when = System.currentTimeMillis(); + } + + /** + * @deprecated use {@link #Notification(int,CharSequence,long)} and {@link #setLatestEventInfo}. + * @hide + */ + public Notification(Context context, int icon, CharSequence tickerText, long when, + CharSequence contentTitle, CharSequence contentText, Intent contentIntent) + { + this.when = when; + this.icon = icon; + this.tickerText = tickerText; + setLatestEventInfo(context, contentTitle, contentText, + PendingIntent.getActivity(context, 0, contentIntent, 0)); + } + + /** + * Constructs a Notification object with the information needed to + * have a status bar icon without the standard expanded view. + * + * @param icon The resource id of the icon to put in the status bar. + * @param tickerText The text that flows by in the status bar when the notification first + * activates. + * @param when The time to show in the time field. In the System.currentTimeMillis + * timebase. + */ + public Notification(int icon, CharSequence tickerText, long when) + { + this.icon = icon; + this.tickerText = tickerText; + this.when = when; + } + + /** + * Unflatten the notification from a parcel. + */ + public Notification(Parcel parcel) + { + int version = parcel.readInt(); + + when = parcel.readLong(); + icon = parcel.readInt(); + number = parcel.readInt(); + if (parcel.readInt() != 0) { + contentIntent = PendingIntent.CREATOR.createFromParcel(parcel); + } + if (parcel.readInt() != 0) { + deleteIntent = PendingIntent.CREATOR.createFromParcel(parcel); + } + if (parcel.readInt() != 0) { + tickerText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(parcel); + } + if (parcel.readInt() != 0) { + contentView = RemoteViews.CREATOR.createFromParcel(parcel); + } + defaults = parcel.readInt(); + flags = parcel.readInt(); + if (parcel.readInt() != 0) { + sound = Uri.CREATOR.createFromParcel(parcel); + } + + audioStreamType = parcel.readInt(); + vibrate = parcel.createLongArray(); + ledARGB = parcel.readInt(); + ledOnMS = parcel.readInt(); + ledOffMS = parcel.readInt(); + iconLevel = parcel.readInt(); + } + + public int describeContents() { + return 0; + } + + /** + * Flatten this notification from a parcel. + */ + public void writeToParcel(Parcel parcel, int flags) + { + parcel.writeInt(1); + + parcel.writeLong(when); + parcel.writeInt(icon); + parcel.writeInt(number); + if (contentIntent != null) { + parcel.writeInt(1); + contentIntent.writeToParcel(parcel, 0); + } else { + parcel.writeInt(0); + } + if (deleteIntent != null) { + parcel.writeInt(1); + deleteIntent.writeToParcel(parcel, 0); + } else { + parcel.writeInt(0); + } + if (tickerText != null) { + parcel.writeInt(1); + TextUtils.writeToParcel(tickerText, parcel, flags); + } else { + parcel.writeInt(0); + } + if (contentView != null) { + parcel.writeInt(1); + contentView.writeToParcel(parcel, 0); + } else { + parcel.writeInt(0); + } + + parcel.writeInt(defaults); + parcel.writeInt(this.flags); + + if (sound != null) { + parcel.writeInt(1); + sound.writeToParcel(parcel, 0); + } else { + parcel.writeInt(0); + } + parcel.writeInt(audioStreamType); + parcel.writeLongArray(vibrate); + parcel.writeInt(ledARGB); + parcel.writeInt(ledOnMS); + parcel.writeInt(ledOffMS); + parcel.writeInt(iconLevel); + } + + /** + * Parcelable.Creator that instantiates Notification objects + */ + public static final Parcelable.Creator<Notification> CREATOR + = new Parcelable.Creator<Notification>() + { + public Notification createFromParcel(Parcel parcel) + { + return new Notification(parcel); + } + + public Notification[] newArray(int size) + { + return new Notification[size]; + } + }; + + /** + * Sets the {@link #contentView} field to be a view with the standard "Latest Event" + * layout. + * + * <p>Uses the {@link #icon} and {@link #when} fields to set the icon and time fields + * in the view.</p> + * @param context The context for your application / activity. + * @param contentTitle The title that goes in the expanded entry. + * @param contentText The text that goes in the expanded entry. + * @param contentIntent The intent to launch when the user clicks the expanded notification. + * If this is an activity, it must include the + * {@link android.content.Intent#FLAG_ACTIVITY_NEW_TASK} flag, which requires + * that you take care of task management as described in + * <a href="{@docRoot}guide/topics/fundamentals.html#lcycles">Application Fundamentals: Activities and Tasks</a>. + */ + public void setLatestEventInfo(Context context, + CharSequence contentTitle, CharSequence contentText, PendingIntent contentIntent) { + RemoteViews contentView = new RemoteViews(context.getPackageName(), + com.android.internal.R.layout.status_bar_latest_event_content); + if (this.icon != 0) { + contentView.setImageViewResource(com.android.internal.R.id.icon, this.icon); + } + if (contentTitle != null) { + contentView.setTextViewText(com.android.internal.R.id.title, contentTitle); + } + if (contentText != null) { + contentView.setTextViewText(com.android.internal.R.id.text, contentText); + } + if (this.when != 0) { + Date date = new Date(when); + CharSequence str = + DateUtils.isToday(when) ? DateFormat.getTimeFormat(context).format(date) + : DateFormat.getDateFormat(context).format(date); + contentView.setTextViewText(com.android.internal.R.id.time, str); + } + + this.contentView = contentView; + this.contentIntent = contentIntent; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("Notification(vibrate="); + if (this.vibrate != null) { + int N = this.vibrate.length-1; + sb.append("["); + for (int i=0; i<N; i++) { + sb.append(this.vibrate[i]); + sb.append(','); + } + sb.append(this.vibrate[N]); + sb.append("]"); + } else if ((this.defaults & DEFAULT_VIBRATE) != 0) { + sb.append("default"); + } else { + sb.append("null"); + } + sb.append(",sound="); + if (this.sound != null) { + sb.append(this.sound.toString()); + } else if ((this.defaults & DEFAULT_SOUND) != 0) { + sb.append("default"); + } else { + sb.append("null"); + } + sb.append(",defaults=0x"); + sb.append(Integer.toHexString(this.defaults)); + sb.append(")"); + return sb.toString(); + } +} diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java new file mode 100644 index 0000000..39edab7 --- /dev/null +++ b/core/java/android/app/NotificationManager.java @@ -0,0 +1,134 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * 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 android.app; + +import android.content.Context; +import android.os.RemoteException; +import android.os.Handler; +import android.os.IBinder; +import android.os.ServiceManager; +import android.util.Log; + +/** + * Class to notify the user of events that happen. This is how you tell + * the user that something has happened in the background. {@more} + * + * Notifications can take different forms: + * <ul> + * <li>A persistent icon that goes in the status bar and is accessible + * through the launcher, (when the user selects it, a designated Intent + * can be launched),</li> + * <li>Turning on or flashing LEDs on the device, or</li> + * <li>Alerting the user by flashing the backlight, playing a sound, + * or vibrating.</li> + * </ul> + * + * <p> + * Each of the notify methods takes an int id parameter. This id identifies + * this notification from your app to the system, so that id should be unique + * within your app. If you call one of the notify methods with an id that is + * currently active and a new set of notification parameters, it will be + * updated. For example, if you pass a new status bar icon, the old icon in + * the status bar will be replaced with the new one. This is also the same + * id you pass to the {@link #cancel} method to clear this notification. + * + * <p> + * You do not instantiate this class directly; instead, retrieve it through + * {@link android.content.Context#getSystemService}. + * + * @see android.app.Notification + * @see android.content.Context#getSystemService + */ +public class NotificationManager +{ + private static String TAG = "NotificationManager"; + private static boolean DEBUG = false; + private static boolean localLOGV = DEBUG || android.util.Config.LOGV; + + private static INotificationManager sService; + + static private INotificationManager getService() + { + if (sService != null) { + return sService; + } + IBinder b = ServiceManager.getService("notification"); + sService = INotificationManager.Stub.asInterface(b); + return sService; + } + + /*package*/ NotificationManager(Context context, Handler handler) + { + mContext = context; + } + + /** + * Persistent notification on the status bar, + * + * @param id An identifier for this notification unique within your + * application. + * @param notification A {@link Notification} object describing how to + * notify the user, other than the view you're providing. Must not be null. + */ + public void notify(int id, Notification notification) + { + int[] idOut = new int[1]; + INotificationManager service = getService(); + String pkg = mContext.getPackageName(); + if (localLOGV) Log.v(TAG, pkg + ": notify(" + id + ", " + notification + ")"); + try { + service.enqueueNotification(pkg, id, notification, idOut); + if (id != idOut[0]) { + Log.w(TAG, "notify: id corrupted: sent " + id + ", got back " + idOut[0]); + } + } catch (RemoteException e) { + } + } + + /** + * Cancel a previously shown notification. If it's transient, the view + * will be hidden. If it's persistent, it will be removed from the status + * bar. + */ + public void cancel(int id) + { + INotificationManager service = getService(); + String pkg = mContext.getPackageName(); + if (localLOGV) Log.v(TAG, pkg + ": cancel(" + id + ")"); + try { + service.cancelNotification(pkg, id); + } catch (RemoteException e) { + } + } + + /** + * Cancel all previously shown notifications. See {@link #cancel} for the + * detailed behavior. + */ + public void cancelAll() + { + INotificationManager service = getService(); + String pkg = mContext.getPackageName(); + if (localLOGV) Log.v(TAG, pkg + ": cancelAll()"); + try { + service.cancelAllNotifications(pkg); + } catch (RemoteException e) { + } + } + + private Context mContext; +} diff --git a/core/java/android/app/PendingIntent.aidl b/core/java/android/app/PendingIntent.aidl new file mode 100644 index 0000000..f0d530c --- /dev/null +++ b/core/java/android/app/PendingIntent.aidl @@ -0,0 +1,20 @@ +/* //device/java/android/android/content/Intent.aidl +** +** Copyright 2007, The Android Open Source Project +** +** 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 android.app; + +parcelable PendingIntent; diff --git a/core/java/android/app/PendingIntent.java b/core/java/android/app/PendingIntent.java new file mode 100644 index 0000000..1bed706 --- /dev/null +++ b/core/java/android/app/PendingIntent.java @@ -0,0 +1,508 @@ +/* + * Copyright (C) 2006 The Android Open Source Project + * + * 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 android.app; + +import android.content.Context; +import android.content.Intent; +import android.os.Bundle; +import android.os.RemoteException; +import android.os.Handler; +import android.os.IBinder; +import android.os.Parcel; +import android.os.Parcelable; +import android.util.AndroidException; + +/** + * A description of an Intent and target action to perform with it. Instances + * of this class are created with {@link #getActivity}, + * {@link #getBroadcast}, {@link #getService}; the returned object can be + * handed to other applications so that they can perform the action you + * described on your behalf at a later time. + * + * <p>By giving a PendingIntent to another application, + * you are granting it the right to perform the operation you have specified + * as if the other application was yourself (with the same permissions and + * identity). As such, you should be careful about how you build the PendingIntent: + * often, for example, the base Intent you supply will have the component + * name explicitly set to one of your own components, to ensure it is ultimately + * sent there and nowhere else. + * + * <p>A PendingIntent itself is simply a reference to a token maintained by + * the system describing the original data used to retrieve it. This means + * that, even if its owning application's process is killed, the + * PendingIntent itself will remain usable from other processes that + * have been given it. If the creating application later re-retrieves the + * same kind of PendingIntent (same operation, same Intent action, data, + * categories, and components, and same flags), it will receive a PendingIntent + * representing the same token if that is still valid, and can thus call + * {@link #cancel} to remove it. + */ +public final class PendingIntent implements Parcelable { + private final IIntentSender mTarget; + + /** + * Flag for use with {@link #getActivity}, {@link #getBroadcast}, and + * {@link #getService}: this + * PendingIntent can only be used once. If set, after + * {@link #send()} is called on it, it will be automatically + * canceled for you and any future attempt to send through it will fail. + */ + public static final int FLAG_ONE_SHOT = 1<<30; + /** + * Flag for use with {@link #getActivity}, {@link #getBroadcast}, and + * {@link #getService}: if the described PendingIntent does not already + * exist, then simply return null instead of creating it. + */ + public static final int FLAG_NO_CREATE = 1<<29; + /** + * Flag for use with {@link #getActivity}, {@link #getBroadcast}, and + * {@link #getService}: if the described PendingIntent already exists, + * the current one is canceled before generating a new one. You can use + * this to retrieve a new PendingIntent when you are only changing the + * extra data in the Intent; by canceling the previous pending intent, + * this ensures that only entities given the new data will be able to + * launch it. If this assurance is not an issue, consider + * {@link #FLAG_UPDATE_CURRENT}. + */ + public static final int FLAG_CANCEL_CURRENT = 1<<28; + /** + * Flag for use with {@link #getActivity}, {@link #getBroadcast}, and + * {@link #getService}: if the described PendingIntent already exists, + * then keep it but its replace its extra data with what is in this new + * Intent. This can be used if you are creating intents where only the + * extras change, and don't care that any entities that received your + * previous PendingIntent will be able to launch it with your new + * extras even if they are not explicitly given to it. + */ + public static final int FLAG_UPDATE_CURRENT = 1<<27; + + /** + * Exception thrown when trying to send through a PendingIntent that + * has been canceled or is otherwise no longer able to execute the request. + */ + public static class CanceledException extends AndroidException { + public CanceledException() { + } + + public CanceledException(String name) { + super(name); + } + + public CanceledException(Exception cause) { + super(cause); + } + }; + + /** + * Callback interface for discovering when a send operation has + * completed. Primarily for use with a PendingIntent that is + * performing a broadcast, this provides the same information as + * calling {@link Context#sendOrderedBroadcast(Intent, String, + * android.content.BroadcastReceiver, Handler, int, String, Bundle) + * Context.sendBroadcast()} with a final BroadcastReceiver. + */ + public interface OnFinished { + /** + * Called when a send operation as completed. + * + * @param pendingIntent The PendingIntent this operation was sent through. + * @param intent The original Intent that was sent. + * @param resultCode The final result code determined by the send. + * @param resultData The final data collected by a broadcast. + * @param resultExtras The final extras collected by a broadcast. + */ + void onSendFinished(PendingIntent pendingIntent, Intent intent, + int resultCode, String resultData, Bundle resultExtras); + } + + private static class FinishedDispatcher extends IIntentReceiver.Stub + implements Runnable { + private final PendingIntent mPendingIntent; + private final OnFinished mWho; + private final Handler mHandler; + private Intent mIntent; + private int mResultCode; + private String mResultData; + private Bundle mResultExtras; + FinishedDispatcher(PendingIntent pi, OnFinished who, Handler handler) { + mPendingIntent = pi; + mWho = who; + mHandler = handler; + } + public void performReceive(Intent intent, int resultCode, + String data, Bundle extras, boolean serialized) { + mIntent = intent; + mResultCode = resultCode; + mResultData = data; + mResultExtras = extras; + if (mHandler == null) { + run(); + } else { + mHandler.post(this); + } + } + public void run() { + mWho.onSendFinished(mPendingIntent, mIntent, mResultCode, + mResultData, mResultExtras); + } + } + + /** + * Retrieve a PendingIntent that will start a new activity, like calling + * {@link Context#startActivity(Intent) Context.startActivity(Intent)}. + * Note that the activity will be started outside of the context of an + * existing activity, so you must use the {@link Intent#FLAG_ACTIVITY_NEW_TASK + * Intent.FLAG_ACTIVITY_NEW_TASK} launch flag in the Intent. + * + * @param context The Context in which this PendingIntent should start + * the activity. + * @param requestCode Private request code for the sender (currently + * not used). + * @param intent Intent of the activity to be launched. + * @param flags May be {@link #FLAG_ONE_SHOT}, {@link #FLAG_NO_CREATE}, + * {@link #FLAG_CANCEL_CURRENT}, {@link #FLAG_UPDATE_CURRENT}, + * or any of the flags as supported by + * {@link Intent#fillIn Intent.fillIn()} to control which unspecified parts + * of the intent that can be supplied when the actual send happens. + * + * @return Returns an existing or new PendingIntent matching the given + * parameters. May return null only if {@link #FLAG_NO_CREATE} has been + * supplied. + */ + public static PendingIntent getActivity(Context context, int requestCode, + Intent intent, int flags) { + String packageName = context.getPackageName(); + String resolvedType = intent != null ? intent.resolveTypeIfNeeded( + context.getContentResolver()) : null; + try { + IIntentSender target = + ActivityManagerNative.getDefault().getIntentSender( + IActivityManager.INTENT_SENDER_ACTIVITY, packageName, + null, null, requestCode, intent, resolvedType, flags); + return target != null ? new PendingIntent(target) : null; + } catch (RemoteException e) { + } + return null; + } + + /** + * Retrieve a PendingIntent that will perform a broadcast, like calling + * {@link Context#sendBroadcast(Intent) Context.sendBroadcast()}. + * + * @param context The Context in which this PendingIntent should perform + * the broadcast. + * @param requestCode Private request code for the sender (currently + * not used). + * @param intent The Intent to be broadcast. + * @param flags May be {@link #FLAG_ONE_SHOT}, {@link #FLAG_NO_CREATE}, + * {@link #FLAG_CANCEL_CURRENT}, {@link #FLAG_UPDATE_CURRENT}, + * or any of the flags as supported by + * {@link Intent#fillIn Intent.fillIn()} to control which unspecified parts + * of the intent that can be supplied when the actual send happens. + * + * @return Returns an existing or new PendingIntent matching the given + * parameters. May return null only if {@link #FLAG_NO_CREATE} has been + * supplied. + */ + public static PendingIntent getBroadcast(Context context, int requestCode, + Intent intent, int flags) { + String packageName = context.getPackageName(); + String resolvedType = intent != null ? intent.resolveTypeIfNeeded( + context.getContentResolver()) : null; + try { + IIntentSender target = + ActivityManagerNative.getDefault().getIntentSender( + IActivityManager.INTENT_SENDER_BROADCAST, packageName, + null, null, requestCode, intent, resolvedType, flags); + return target != null ? new PendingIntent(target) : null; + } catch (RemoteException e) { + } + return null; + } + + /** + * Retrieve a PendingIntent that will start a service, like calling + * {@link Context#startService Context.startService()}. The start + * arguments given to the service will come from the extras of the Intent. + * + * @param context The Context in which this PendingIntent should start + * the service. + * @param requestCode Private request code for the sender (currently + * not used). + * @param intent An Intent describing the service to be started. + * @param flags May be {@link #FLAG_ONE_SHOT}, {@link #FLAG_NO_CREATE}, + * {@link #FLAG_CANCEL_CURRENT}, {@link #FLAG_UPDATE_CURRENT}, + * or any of the flags as supported by + * {@link Intent#fillIn Intent.fillIn()} to control which unspecified parts + * of the intent that can be supplied when the actual send happens. + * + * @return Returns an existing or new PendingIntent matching the given + * parameters. May return null only if {@link #FLAG_NO_CREATE} has been + * supplied. + */ + public static PendingIntent getService(Context context, int requestCode, + Intent intent, int flags) { + String packageName = context.getPackageName(); + String resolvedType = intent != null ? intent.resolveTypeIfNeeded( + context.getContentResolver()) : null; + try { + IIntentSender target = + ActivityManagerNative.getDefault().getIntentSender( + IActivityManager.INTENT_SENDER_SERVICE, packageName, + null, null, requestCode, intent, resolvedType, flags); + return target != null ? new PendingIntent(target) : null; + } catch (RemoteException e) { + } + return null; + } + + /** + * Cancel a currently active PendingIntent. Only the original application + * owning an PendingIntent can cancel it. + */ + public void cancel() { + try { + ActivityManagerNative.getDefault().cancelIntentSender(mTarget); + } catch (RemoteException e) { + } + } + + /** + * Perform the operation associated with this PendingIntent. + * + * @see #send(Context, int, Intent, android.app.PendingIntent.OnFinished, Handler) + * + * @throws CanceledException Throws CanceledException if the PendingIntent + * is no longer allowing more intents to be sent through it. + */ + public void send() throws CanceledException { + send(null, 0, null, null, null); + } + + /** + * Perform the operation associated with this PendingIntent. + * + * @param code Result code to supply back to the PendingIntent's target. + * + * @see #send(Context, int, Intent, android.app.PendingIntent.OnFinished, Handler) + * + * @throws CanceledException Throws CanceledException if the PendingIntent + * is no longer allowing more intents to be sent through it. + */ + public void send(int code) throws CanceledException { + send(null, code, null, null, null); + } + + /** + * Perform the operation associated with this PendingIntent, allowing the + * caller to specify information about the Intent to use. + * + * @param context The Context of the caller. + * @param code Result code to supply back to the PendingIntent's target. + * @param intent Additional Intent data. See {@link Intent#fillIn + * Intent.fillIn()} for information on how this is applied to the + * original Intent. + * + * @see #send(Context, int, Intent, android.app.PendingIntent.OnFinished, Handler) + * + * @throws CanceledException Throws CanceledException if the PendingIntent + * is no longer allowing more intents to be sent through it. + */ + public void send(Context context, int code, Intent intent) + throws CanceledException { + send(context, code, intent, null, null); + } + + /** + * Perform the operation associated with this PendingIntent, allowing the + * caller to be notified when the send has completed. + * + * @param code Result code to supply back to the PendingIntent's target. + * @param onFinished The object to call back on when the send has + * completed, or null for no callback. + * @param handler Handler identifying the thread on which the callback + * should happen. If null, the callback will happen from the thread + * pool of the process. + * + * @see #send(Context, int, Intent, android.app.PendingIntent.OnFinished, Handler) + * + * @throws CanceledException Throws CanceledException if the PendingIntent + * is no longer allowing more intents to be sent through it. + */ + public void send(int code, OnFinished onFinished, Handler handler) + throws CanceledException { + send(null, code, null, onFinished, handler); + } + + /** + * Perform the operation associated with this PendingIntent, allowing the + * caller to specify information about the Intent to use and be notified + * when the send has completed. + * + * <p>For the intent parameter, a PendingIntent + * often has restrictions on which fields can be supplied here, based on + * how the PendingIntent was retrieved in {@link #getActivity}, + * {@link #getBroadcast}, or {@link #getService}. + * + * @param context The Context of the caller. This may be null if + * <var>intent</var> is also null. + * @param code Result code to supply back to the PendingIntent's target. + * @param intent Additional Intent data. See {@link Intent#fillIn + * Intent.fillIn()} for information on how this is applied to the + * original Intent. Use null to not modify the original Intent. + * @param onFinished The object to call back on when the send has + * completed, or null for no callback. + * @param handler Handler identifying the thread on which the callback + * should happen. If null, the callback will happen from the thread + * pool of the process. + * + * @see #send() + * @see #send(int) + * @see #send(Context, int, Intent) + * @see #send(int, android.app.PendingIntent.OnFinished, Handler) + * + * @throws CanceledException Throws CanceledException if the PendingIntent + * is no longer allowing more intents to be sent through it. + */ + public void send(Context context, int code, Intent intent, + OnFinished onFinished, Handler handler) throws CanceledException { + try { + String resolvedType = intent != null ? + intent.resolveTypeIfNeeded(context.getContentResolver()) + : null; + int res = mTarget.send(code, intent, resolvedType, + onFinished != null + ? new FinishedDispatcher(this, onFinished, handler) + : null); + if (res < 0) { + throw new CanceledException(); + } + } catch (RemoteException e) { + throw new CanceledException(e); + } + } + + /** + * Return the package name of the application that created this + * PendingIntent, that is the identity under which you will actually be + * sending the Intent. The returned string is supplied by the system, so + * that an application can not spoof its package. + * + * @return The package name of the PendingIntent, or null if there is + * none associated with it. + */ + public String getTargetPackage() { + try { + return ActivityManagerNative.getDefault() + .getPackageForIntentSender(mTarget); + } catch (RemoteException e) { + // Should never happen. + return null; + } + } + + /** + * Comparison operator on two PendingIntent objects, such that true + * is returned then they both represent the same operation from the + * same package. This allows you to use {@link #getActivity}, + * {@link #getBroadcast}, or {@link #getService} multiple times (even + * across a process being killed), resulting in different PendingIntent + * objects but whose equals() method identifies them as being the same + * operation. + */ + @Override + public boolean equals(Object otherObj) { + if (otherObj instanceof PendingIntent) { + return mTarget.asBinder().equals(((PendingIntent)otherObj) + .mTarget.asBinder()); + } + return false; + } + + @Override + public int hashCode() { + return mTarget.asBinder().hashCode(); + } + + @Override + public String toString() { + return "PendingIntent{" + + Integer.toHexString(System.identityHashCode(this)) + + " target " + (mTarget != null ? mTarget.asBinder() : null) + "}"; + } + + public int describeContents() { + return 0; + } + + public void writeToParcel(Parcel out, int flags) { + out.writeStrongBinder(mTarget.asBinder()); + } + + public static final Parcelable.Creator<PendingIntent> CREATOR + = new Parcelable.Creator<PendingIntent>() { + public PendingIntent createFromParcel(Parcel in) { + IBinder target = in.readStrongBinder(); + return target != null ? new PendingIntent(target) : null; + } + + public PendingIntent[] newArray(int size) { + return new PendingIntent[size]; + } + }; + + /** + * Convenience function for writing either a PendingIntent or null pointer to + * a Parcel. You must use this with {@link #readPendingIntentOrNullFromParcel} + * for later reading it. + * + * @param sender The PendingIntent to write, or null. + * @param out Where to write the PendingIntent. + */ + public static void writePendingIntentOrNullToParcel(PendingIntent sender, + Parcel out) { + out.writeStrongBinder(sender != null ? sender.mTarget.asBinder() + : null); + } + + /** + * Convenience function for reading either a Messenger or null pointer from + * a Parcel. You must have previously written the Messenger with + * {@link #writePendingIntentOrNullToParcel}. + * + * @param in The Parcel containing the written Messenger. + * + * @return Returns the Messenger read from the Parcel, or null if null had + * been written. + */ + public static PendingIntent readPendingIntentOrNullFromParcel(Parcel in) { + IBinder b = in.readStrongBinder(); + return b != null ? new PendingIntent(b) : null; + } + + /*package*/ PendingIntent(IIntentSender target) { + mTarget = target; + } + + /*package*/ PendingIntent(IBinder target) { + mTarget = IIntentSender.Stub.asInterface(target); + } + + /*package*/ IIntentSender getTarget() { + return mTarget; + } +} diff --git a/core/java/android/app/ProgressDialog.java b/core/java/android/app/ProgressDialog.java new file mode 100644 index 0000000..c87e398 --- /dev/null +++ b/core/java/android/app/ProgressDialog.java @@ -0,0 +1,301 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * 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 android.app; + +import android.content.Context; +import android.graphics.drawable.Drawable; +import android.os.Bundle; +import android.os.Handler; +import android.os.Message; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.ProgressBar; +import android.widget.TextView; + +import com.android.internal.R; + +import java.text.NumberFormat; + +/** + * <p>A dialog showing a progress indicator and an optional text message or view. + * Only a text message or a view can be used at the same time.</p> + * <p>The dialog can be made cancelable on back key press.</p> + * <p>The progress range is 0..10000.</p> + */ +public class ProgressDialog extends AlertDialog { + + /** Creates a ProgressDialog with a ciruclar, spinning progress + * bar. This is the default. + */ + public static final int STYLE_SPINNER = 0; + + /** Creates a ProgressDialog with a horizontal progress bar. + */ + public static final int STYLE_HORIZONTAL = 1; + + private ProgressBar mProgress; + private TextView mMessageView; + + private int mProgressStyle = STYLE_SPINNER; + private TextView mProgressNumber; + private TextView mProgressPercent; + private NumberFormat mProgressPercentFormat; + + private int mMax; + private int mProgressVal; + private int mSecondaryProgressVal; + private int mIncrementBy; + private int mIncrementSecondaryBy; + private Drawable mProgressDrawable; + private Drawable mIndeterminateDrawable; + private CharSequence mMessage; + private boolean mIndeterminate; + + private boolean mHasStarted; + private Handler mViewUpdateHandler; + + public ProgressDialog(Context context) { + this(context, com.android.internal.R.style.Theme_Dialog_Alert); + } + + public ProgressDialog(Context context, int theme) { + super(context, theme); + } + + public static ProgressDialog show(Context context, CharSequence title, + CharSequence message) { + return show(context, title, message, false); + } + + public static ProgressDialog show(Context context, CharSequence title, + CharSequence message, boolean indeterminate) { + return show(context, title, message, indeterminate, false, null); + } + + public static ProgressDialog show(Context context, CharSequence title, + CharSequence message, boolean indeterminate, boolean cancelable) { + return show(context, title, message, indeterminate, cancelable, null); + } + + public static ProgressDialog show(Context context, CharSequence title, + CharSequence message, boolean indeterminate, + boolean cancelable, OnCancelListener cancelListener) { + ProgressDialog dialog = new ProgressDialog(context); + dialog.setTitle(title); + dialog.setMessage(message); + dialog.setIndeterminate(indeterminate); + dialog.setCancelable(cancelable); + dialog.setOnCancelListener(cancelListener); + dialog.show(); + return dialog; + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + LayoutInflater inflater = LayoutInflater.from(mContext); + if (mProgressStyle == STYLE_HORIZONTAL) { + + /* Use a separate handler to update the text views as they + * must be updated on the same thread that created them. + */ + mViewUpdateHandler = new Handler() { + @Override + public void handleMessage(Message msg) { + super.handleMessage(msg); + + /* Update the number and percent */ + int progress = mProgress.getProgress(); + int max = mProgress.getMax(); + double percent = (double) progress / (double) max; + mProgressNumber.setText(progress + "/" + max); + mProgressPercent.setText(mProgressPercentFormat.format(percent)); + } + }; + View view = inflater.inflate(R.layout.alert_dialog_progress, null); + mProgress = (ProgressBar) view.findViewById(R.id.progress); + mProgressNumber = (TextView) view.findViewById(R.id.progress_number); + mProgressPercent = (TextView) view.findViewById(R.id.progress_percent); + mProgressPercentFormat = NumberFormat.getPercentInstance(); + mProgressPercentFormat.setMaximumFractionDigits(0); + setView(view); + } else { + View view = inflater.inflate(R.layout.progress_dialog, null); + mProgress = (ProgressBar) view.findViewById(R.id.progress); + mMessageView = (TextView) view.findViewById(R.id.message); + setView(view); + } + if (mMax > 0) { + setMax(mMax); + } + if (mProgressVal > 0) { + setProgress(mProgressVal); + } + if (mSecondaryProgressVal > 0) { + setSecondaryProgress(mSecondaryProgressVal); + } + if (mIncrementBy > 0) { + incrementProgressBy(mIncrementBy); + } + if (mIncrementSecondaryBy > 0) { + incrementSecondaryProgressBy(mIncrementSecondaryBy); + } + if (mProgressDrawable != null) { + setProgressDrawable(mProgressDrawable); + } + if (mIndeterminateDrawable != null) { + setIndeterminateDrawable(mIndeterminateDrawable); + } + if (mMessage != null) { + setMessage(mMessage); + } + setIndeterminate(mIndeterminate); + onProgressChanged(); + super.onCreate(savedInstanceState); + } + + @Override + public void onStart() { + super.onStart(); + mHasStarted = true; + } + + @Override + protected void onStop() { + super.onStop(); + mHasStarted = false; + } + + public void setProgress(int value) { + if (mHasStarted) { + mProgress.setProgress(value); + onProgressChanged(); + } else { + mProgressVal = value; + } + } + + public void setSecondaryProgress(int secondaryProgress) { + if (mProgress != null) { + mProgress.setSecondaryProgress(secondaryProgress); + onProgressChanged(); + } else { + mSecondaryProgressVal = secondaryProgress; + } + } + + public int getProgress() { + if (mProgress != null) { + return mProgress.getProgress(); + } + return mProgressVal; + } + + public int getSecondaryProgress() { + if (mProgress != null) { + return mProgress.getSecondaryProgress(); + } + return mSecondaryProgressVal; + } + + public int getMax() { + if (mProgress != null) { + return mProgress.getMax(); + } + return mMax; + } + + public void setMax(int max) { + if (mProgress != null) { + mProgress.setMax(max); + onProgressChanged(); + } else { + mMax = max; + } + } + + public void incrementProgressBy(int diff) { + if (mProgress != null) { + mProgress.incrementProgressBy(diff); + onProgressChanged(); + } else { + mIncrementBy += diff; + } + } + + public void incrementSecondaryProgressBy(int diff) { + if (mProgress != null) { + mProgress.incrementSecondaryProgressBy(diff); + onProgressChanged(); + } else { + mIncrementSecondaryBy += diff; + } + } + + public void setProgressDrawable(Drawable d) { + if (mProgress != null) { + mProgress.setProgressDrawable(d); + } else { + mProgressDrawable = d; + } + } + + public void setIndeterminateDrawable(Drawable d) { + if (mProgress != null) { + mProgress.setIndeterminateDrawable(d); + } else { + mIndeterminateDrawable = d; + } + } + + public void setIndeterminate(boolean indeterminate) { + if (mProgress != null) { + mProgress.setIndeterminate(indeterminate); + } else { + mIndeterminate = indeterminate; + } + } + + public boolean isIndeterminate() { + if (mProgress != null) { + return mProgress.isIndeterminate(); + } + return mIndeterminate; + } + + @Override + public void setMessage(CharSequence message) { + if (mProgress != null) { + if (mProgressStyle == STYLE_HORIZONTAL) { + super.setMessage(message); + } else { + mMessageView.setText(message); + } + } else { + mMessage = message; + } + } + + public void setProgressStyle(int style) { + mProgressStyle = style; + } + + private void onProgressChanged() { + if (mProgressStyle == STYLE_HORIZONTAL) { + mViewUpdateHandler.sendEmptyMessage(0); + } + } +} diff --git a/core/java/android/app/ResultInfo.java b/core/java/android/app/ResultInfo.java new file mode 100644 index 0000000..48a0fc2 --- /dev/null +++ b/core/java/android/app/ResultInfo.java @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2006 The Android Open Source Project + * + * 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 android.app; + +import android.content.Intent; +import android.os.IBinder; +import android.os.Parcel; +import android.os.Parcelable; +import android.os.Bundle; + +import java.util.Map; + +/** + * {@hide} + */ +public class ResultInfo implements Parcelable { + public final String mResultWho; + public final int mRequestCode; + public final int mResultCode; + public final Intent mData; + + public ResultInfo(String resultWho, int requestCode, int resultCode, + Intent data) { + mResultWho = resultWho; + mRequestCode = requestCode; + mResultCode = resultCode; + mData = data; + } + + public String toString() { + return "ResultInfo{who=" + mResultWho + ", request=" + mRequestCode + + ", result=" + mResultCode + ", data=" + mData + "}"; + } + + public int describeContents() { + return 0; + } + + public void writeToParcel(Parcel out, int flags) { + out.writeString(mResultWho); + out.writeInt(mRequestCode); + out.writeInt(mResultCode); + if (mData != null) { + out.writeInt(1); + mData.writeToParcel(out, 0); + } else { + out.writeInt(0); + } + } + + public static final Parcelable.Creator<ResultInfo> CREATOR + = new Parcelable.Creator<ResultInfo>() { + public ResultInfo createFromParcel(Parcel in) { + return new ResultInfo(in); + } + + public ResultInfo[] newArray(int size) { + return new ResultInfo[size]; + } + }; + + public ResultInfo(Parcel in) { + mResultWho = in.readString(); + mRequestCode = in.readInt(); + mResultCode = in.readInt(); + if (in.readInt() != 0) { + mData = Intent.CREATOR.createFromParcel(in); + } else { + mData = null; + } + } +} diff --git a/core/java/android/app/SearchDialog.java b/core/java/android/app/SearchDialog.java new file mode 100644 index 0000000..d447eb2 --- /dev/null +++ b/core/java/android/app/SearchDialog.java @@ -0,0 +1,1593 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * 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 android.app; + +import android.content.ActivityNotFoundException; +import android.content.BroadcastReceiver; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.content.res.Configuration; +import android.content.res.Resources; +import android.content.res.Resources.NotFoundException; +import android.database.Cursor; +import android.graphics.drawable.Drawable; +import android.net.Uri; +import android.os.Bundle; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.os.SystemClock; +import android.server.search.SearchableInfo; +import android.speech.RecognizerIntent; +import android.text.Editable; +import android.text.InputType; +import android.text.TextUtils; +import android.text.TextWatcher; +import android.util.AttributeSet; +import android.util.Log; +import android.view.Gravity; +import android.view.KeyEvent; +import android.view.View; +import android.view.ViewGroup; +import android.view.Window; +import android.view.WindowManager; +import android.widget.AdapterView; +import android.widget.AutoCompleteTextView; +import android.widget.Button; +import android.widget.CursorAdapter; +import android.widget.ImageButton; +import android.widget.ImageView; +import android.widget.ListView; +import android.widget.SimpleCursorAdapter; +import android.widget.TextView; +import android.widget.WrapperListAdapter; +import android.widget.AdapterView.OnItemClickListener; +import android.widget.AdapterView.OnItemSelectedListener; + +import java.lang.ref.WeakReference; +import java.util.concurrent.atomic.AtomicLong; + +/** + * In-application-process implementation of Search Bar. This is still controlled by the + * SearchManager, but it runs in the current activity's process to keep things lighter weight. + * + * @hide + */ +public class SearchDialog extends Dialog implements OnItemClickListener, OnItemSelectedListener { + + // Debugging support + final static String LOG_TAG = "SearchDialog"; + private static final int DBG_LOG_TIMING = 0; + final static int DBG_JAM_THREADING = 0; + + // interaction with runtime + IntentFilter mCloseDialogsFilter; + IntentFilter mPackageFilter; + + private static final String INSTANCE_KEY_COMPONENT = "comp"; + private static final String INSTANCE_KEY_APPDATA = "data"; + private static final String INSTANCE_KEY_GLOBALSEARCH = "glob"; + private static final String INSTANCE_KEY_DISPLAY_QUERY = "dQry"; + private static final String INSTANCE_KEY_DISPLAY_SEL_START = "sel1"; + private static final String INSTANCE_KEY_DISPLAY_SEL_END = "sel2"; + private static final String INSTANCE_KEY_USER_QUERY = "uQry"; + private static final String INSTANCE_KEY_SUGGESTION_QUERY = "sQry"; + private static final String INSTANCE_KEY_SELECTED_ELEMENT = "slEl"; + private static final int INSTANCE_SELECTED_BUTTON = -2; + private static final int INSTANCE_SELECTED_QUERY = -1; + + // views & widgets + private TextView mBadgeLabel; + private AutoCompleteTextView mSearchTextField; + private Button mGoButton; + private ImageButton mVoiceButton; + + // interaction with searchable application + private ComponentName mLaunchComponent; + private Bundle mAppSearchData; + private boolean mGlobalSearchMode; + private Context mActivityContext; + + // interaction with the search manager service + private SearchableInfo mSearchable; + + // support for suggestions + private String mUserQuery = null; + private int mUserQuerySelStart; + private int mUserQuerySelEnd; + private boolean mLeaveJammedQueryOnRefocus = false; + private String mPreviousSuggestionQuery = null; + private int mPresetSelection = -1; + private String mSuggestionAction = null; + private Uri mSuggestionData = null; + private String mSuggestionQuery = null; + + // For voice searching + private Intent mVoiceWebSearchIntent; + private Intent mVoiceAppSearchIntent; + + // support for AutoCompleteTextView suggestions display + private SuggestionsAdapter mSuggestionsAdapter; + + + /** + * Constructor - fires it up and makes it look like the search UI. + * + * @param context Application Context we can use for system acess + */ + public SearchDialog(Context context) { + super(context, com.android.internal.R.style.Theme_SearchBar); + } + + /** + * We create the search dialog just once, and it stays around (hidden) + * until activated by the user. + */ + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + Window theWindow = getWindow(); + theWindow.setGravity(Gravity.TOP|Gravity.FILL_HORIZONTAL); + + setContentView(com.android.internal.R.layout.search_bar); + + theWindow.setLayout(ViewGroup.LayoutParams.FILL_PARENT, + ViewGroup.LayoutParams.WRAP_CONTENT); + WindowManager.LayoutParams lp = theWindow.getAttributes(); + lp.setTitle("Search Dialog"); + lp.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE; + theWindow.setAttributes(lp); + + // get the view elements for local access + mBadgeLabel = (TextView) findViewById(com.android.internal.R.id.search_badge); + mSearchTextField = (AutoCompleteTextView) + findViewById(com.android.internal.R.id.search_src_text); + mGoButton = (Button) findViewById(com.android.internal.R.id.search_go_btn); + mVoiceButton = (ImageButton) findViewById(com.android.internal.R.id.search_voice_btn); + + // attach listeners + mSearchTextField.addTextChangedListener(mTextWatcher); + mSearchTextField.setOnKeyListener(mTextKeyListener); + mGoButton.setOnClickListener(mGoButtonClickListener); + mGoButton.setOnKeyListener(mButtonsKeyListener); + mVoiceButton.setOnClickListener(mVoiceButtonClickListener); + mVoiceButton.setOnKeyListener(mButtonsKeyListener); + + // pre-hide all the extraneous elements + mBadgeLabel.setVisibility(View.GONE); + + // Additional adjustments to make Dialog work for Search + + // Touching outside of the search dialog will dismiss it + setCanceledOnTouchOutside(true); + + // Set up broadcast filters + mCloseDialogsFilter = new IntentFilter(Intent.ACTION_CLOSE_SYSTEM_DIALOGS); + mPackageFilter = new IntentFilter(); + mPackageFilter.addAction(Intent.ACTION_PACKAGE_ADDED); + mPackageFilter.addAction(Intent.ACTION_PACKAGE_REMOVED); + mPackageFilter.addAction(Intent.ACTION_PACKAGE_CHANGED); + mPackageFilter.addDataScheme("package"); + + // Save voice intent for later queries/launching + mVoiceWebSearchIntent = new Intent(RecognizerIntent.ACTION_WEB_SEARCH); + mVoiceWebSearchIntent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL, + RecognizerIntent.LANGUAGE_MODEL_WEB_SEARCH); + + mVoiceAppSearchIntent = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH); + } + + /** + * Set up the search dialog + * + * @param Returns true if search dialog launched, false if not + */ + public boolean show(String initialQuery, boolean selectInitialQuery, + ComponentName componentName, Bundle appSearchData, boolean globalSearch) { + if (isShowing()) { + // race condition - already showing but not handling events yet. + // in this case, just discard the "show" request + return true; + } + + // Get searchable info from search manager and use to set up other elements of UI + // Do this first so we can get out quickly if there's nothing to search + ISearchManager sms; + sms = ISearchManager.Stub.asInterface(ServiceManager.getService(Context.SEARCH_SERVICE)); + try { + mSearchable = sms.getSearchableInfo(componentName, globalSearch); + } catch (RemoteException e) { + mSearchable = null; + } + if (mSearchable == null) { + // unfortunately, we can't log here. it would be logspam every time the user + // clicks the "search" key on a non-search app + return false; + } + + // OK, we're going to show ourselves + super.show(); + + setupSearchableInfo(); + + mLaunchComponent = componentName; + mAppSearchData = appSearchData; + mGlobalSearchMode = globalSearch; + + // receive broadcasts + getContext().registerReceiver(mBroadcastReceiver, mCloseDialogsFilter); + getContext().registerReceiver(mBroadcastReceiver, mPackageFilter); + + // configure the autocomplete aspects of the input box + mSearchTextField.setOnItemClickListener(this); + mSearchTextField.setOnItemSelectedListener(this); + + // This conversion is necessary to force a preload of the EditText and thus force + // suggestions to be presented (even for an empty query) + if (initialQuery == null) { + initialQuery = ""; // This forces the preload to happen, triggering suggestions + } + + // attach the suggestions adapter, if suggestions are available + // The existence of a suggestions authority is the proxy for "suggestions available here" + if (mSearchable.getSuggestAuthority() == null) { + mSuggestionsAdapter = null; + mSearchTextField.setAdapter(mSuggestionsAdapter); + mSearchTextField.setText(initialQuery); + } else { + mSuggestionsAdapter = new SuggestionsAdapter(getContext(), mSearchable); + mSearchTextField.setAdapter(mSuggestionsAdapter); + + // finally, load the user's initial text (which may trigger suggestions) + mSuggestionsAdapter.setNonUserQuery(false); + mSearchTextField.setText(initialQuery); + } + + if (selectInitialQuery) { + mSearchTextField.selectAll(); + } else { + mSearchTextField.setSelection(initialQuery.length()); + } + return true; + } + + /** + * The default show() for this Dialog is not supported. + */ + @Override + public void show() { + return; + } + + /** + * The search dialog is being dismissed, so handle all of the local shutdown operations. + * + * This function is designed to be idempotent so that dismiss() can be safely called at any time + * (even if already closed) and more likely to really dump any memory. No leaks! + */ + @Override + public void onStop() { + super.onStop(); + + setOnCancelListener(null); + setOnDismissListener(null); + + // stop receiving broadcasts (throws exception if none registered) + try { + getContext().unregisterReceiver(mBroadcastReceiver); + } catch (RuntimeException e) { + // This is OK - it just means we didn't have any registered + } + + // close any leftover cursor + if (mSuggestionsAdapter != null) { + mSuggestionsAdapter.changeCursor(null); + } + + // dump extra memory we're hanging on to + mLaunchComponent = null; + mAppSearchData = null; + mSearchable = null; + mSuggestionAction = null; + mSuggestionData = null; + mSuggestionQuery = null; + mActivityContext = null; + mPreviousSuggestionQuery = null; + mUserQuery = null; + } + + /** + * Save the minimal set of data necessary to recreate the search + * + * @return A bundle with the state of the dialog. + */ + @Override + public Bundle onSaveInstanceState() { + Bundle bundle = new Bundle(); + + // setup info so I can recreate this particular search + bundle.putParcelable(INSTANCE_KEY_COMPONENT, mLaunchComponent); + bundle.putBundle(INSTANCE_KEY_APPDATA, mAppSearchData); + bundle.putBoolean(INSTANCE_KEY_GLOBALSEARCH, mGlobalSearchMode); + + // UI state + bundle.putString(INSTANCE_KEY_DISPLAY_QUERY, mSearchTextField.getText().toString()); + bundle.putInt(INSTANCE_KEY_DISPLAY_SEL_START, mSearchTextField.getSelectionStart()); + bundle.putInt(INSTANCE_KEY_DISPLAY_SEL_END, mSearchTextField.getSelectionEnd()); + bundle.putString(INSTANCE_KEY_USER_QUERY, mUserQuery); + bundle.putString(INSTANCE_KEY_SUGGESTION_QUERY, mPreviousSuggestionQuery); + + int selectedElement = INSTANCE_SELECTED_QUERY; + if (mGoButton.isFocused()) { + selectedElement = INSTANCE_SELECTED_BUTTON; + } else if (mSearchTextField.isPopupShowing()) { + selectedElement = 0; // TODO mSearchTextField.getListSelection() // 0..n + } + bundle.putInt(INSTANCE_KEY_SELECTED_ELEMENT, selectedElement); + + return bundle; + } + + /** + * Restore the state of the dialog from a previously saved bundle. + * + * @param savedInstanceState The state of the dialog previously saved by + * {@link #onSaveInstanceState()}. + */ + @Override + public void onRestoreInstanceState(Bundle savedInstanceState) { + // Get the launch info + ComponentName launchComponent = savedInstanceState.getParcelable(INSTANCE_KEY_COMPONENT); + Bundle appSearchData = savedInstanceState.getBundle(INSTANCE_KEY_APPDATA); + boolean globalSearch = savedInstanceState.getBoolean(INSTANCE_KEY_GLOBALSEARCH); + + // get the UI state + String displayQuery = savedInstanceState.getString(INSTANCE_KEY_DISPLAY_QUERY); + int querySelStart = savedInstanceState.getInt(INSTANCE_KEY_DISPLAY_SEL_START, -1); + int querySelEnd = savedInstanceState.getInt(INSTANCE_KEY_DISPLAY_SEL_END, -1); + String userQuery = savedInstanceState.getString(INSTANCE_KEY_USER_QUERY); + int selectedElement = savedInstanceState.getInt(INSTANCE_KEY_SELECTED_ELEMENT); + String suggestionQuery = savedInstanceState.getString(INSTANCE_KEY_SUGGESTION_QUERY); + + // show the dialog. skip any show/hide animation, we want to go fast. + // send the text that actually generates the suggestions here; we'll replace the display + // text as necessary in a moment. + if (!show(suggestionQuery, false, launchComponent, appSearchData, globalSearch)) { + // for some reason, we couldn't re-instantiate + return; + } + + if (mSuggestionsAdapter != null) { + mSuggestionsAdapter.setNonUserQuery(true); + } + mSearchTextField.setText(displayQuery); + // TODO because the new query is (not) processed in another thread, we can't just + // take away this flag (yet). The better solution here is going to require a new API + // in AutoCompleteTextView which allows us to change the text w/o changing the suggestions. +// mSuggestionsAdapter.setNonUserQuery(false); + + // clean up the selection state + switch (selectedElement) { + case INSTANCE_SELECTED_BUTTON: + mGoButton.setEnabled(true); + mGoButton.setFocusable(true); + mGoButton.requestFocus(); + break; + case INSTANCE_SELECTED_QUERY: + if (querySelStart >= 0 && querySelEnd >= 0) { + mSearchTextField.requestFocus(); + mSearchTextField.setSelection(querySelStart, querySelEnd); + } + break; + default: + // defer selecting a list element until suggestion list appears + mPresetSelection = selectedElement; + // TODO mSearchTextField.setListSelection(selectedElement) + break; + } + } + + /** + * Hook for updating layout on a rotation + * + */ + public void onConfigurationChanged(Configuration newConfig) { + if (isShowing()) { + // Redraw (resources may have changed) + updateSearchButton(); + updateSearchBadge(); + updateQueryHint(); + } + } + + /** + * Use SearchableInfo record (from search manager service) to preconfigure the UI in various + * ways. + */ + private void setupSearchableInfo() { + if (mSearchable != null) { + mActivityContext = mSearchable.getActivityContext(getContext()); + + updateSearchButton(); + updateSearchBadge(); + updateQueryHint(); + updateVoiceButton(); + + // In order to properly configure the input method (if one is being used), we + // need to let it know if we'll be providing suggestions. Although it would be + // difficult/expensive to know if every last detail has been configured properly, we + // can at least see if a suggestions provider has been configured, and use that + // as our trigger. + int inputType = mSearchable.getInputType(); + // We only touch this if the input type is set up for text (which it almost certainly + // should be, in the case of search!) + if ((inputType & InputType.TYPE_MASK_CLASS) == InputType.TYPE_CLASS_TEXT) { + // The existence of a suggestions authority is the proxy for "suggestions + // are available here" + inputType &= ~InputType.TYPE_TEXT_FLAG_AUTO_COMPLETE; + if (mSearchable.getSuggestAuthority() != null) { + inputType |= InputType.TYPE_TEXT_FLAG_AUTO_COMPLETE; + } + } + mSearchTextField.setInputType(inputType); + mSearchTextField.setImeOptions(mSearchable.getImeOptions()); + } + } + + /** + * The list of installed packages has just changed. This means that our current context + * may no longer be valid. This would only happen if a package is installed/removed exactly + * when the search bar is open. So for now we're just going to close the search + * bar. + * + * Anything fancier would require some checks to see if the user's context was still valid. + * Which would be messier. + */ + public void onPackageListChange() { + cancel(); + } + + /** + * Update the text in the search button. Note: This is deprecated functionality, for + * 1.0 compatibility only. + */ + private void updateSearchButton() { + String textLabel = null; + Drawable iconLabel = null; + int textId = mSearchable.getSearchButtonText(); + if (textId != 0) { + textLabel = mActivityContext.getResources().getString(textId); + } else { + iconLabel = getContext().getResources(). + getDrawable(com.android.internal.R.drawable.ic_btn_search); + } + mGoButton.setText(textLabel); + mGoButton.setCompoundDrawablesWithIntrinsicBounds(iconLabel, null, null, null); + } + + /** + * Setup the search "Badge" if request by mode flags. + */ + private void updateSearchBadge() { + // assume both hidden + int visibility = View.GONE; + Drawable icon = null; + String text = null; + + // optionally show one or the other. + if (mSearchable.mBadgeIcon) { + icon = mActivityContext.getResources().getDrawable(mSearchable.getIconId()); + visibility = View.VISIBLE; + } else if (mSearchable.mBadgeLabel) { + text = mActivityContext.getResources().getText(mSearchable.getLabelId()).toString(); + visibility = View.VISIBLE; + } + + mBadgeLabel.setCompoundDrawablesWithIntrinsicBounds(icon, null, null, null); + mBadgeLabel.setText(text); + mBadgeLabel.setVisibility(visibility); + } + + /** + * Update the hint in the query text field. + */ + private void updateQueryHint() { + if (isShowing()) { + String hint = null; + if (mSearchable != null) { + int hintId = mSearchable.getHintId(); + if (hintId != 0) { + hint = mActivityContext.getString(hintId); + } + } + mSearchTextField.setHint(hint); + } + } + + /** + * Update the visibility of the voice button. There are actually two voice search modes, + * either of which will activate the button. + */ + private void updateVoiceButton() { + int visibility = View.GONE; + if (mSearchable.getVoiceSearchEnabled()) { + Intent testIntent = null; + if (mSearchable.getVoiceSearchLaunchWebSearch()) { + testIntent = mVoiceWebSearchIntent; + } else if (mSearchable.getVoiceSearchLaunchRecognizer()) { + testIntent = mVoiceAppSearchIntent; + } + if (testIntent != null) { + ResolveInfo ri = getContext().getPackageManager(). + resolveActivity(testIntent, PackageManager.MATCH_DEFAULT_ONLY); + if (ri != null) { + visibility = View.VISIBLE; + } + } + } + mVoiceButton.setVisibility(visibility); + } + + /** + * Listeners of various types + */ + + /** + * Dialog's OnKeyListener implements various search-specific functionality + * + * @param keyCode This is the keycode of the typed key, and is the same value as + * found in the KeyEvent parameter. + * @param event The complete event record for the typed key + * + * @return Return true if the event was handled here, or false if not. + */ + @Override + public boolean onKeyDown(int keyCode, KeyEvent event) { + switch (keyCode) { + case KeyEvent.KEYCODE_BACK: + cancel(); + return true; + case KeyEvent.KEYCODE_SEARCH: + if (TextUtils.getTrimmedLength(mSearchTextField.getText()) != 0) { + launchQuerySearch(KeyEvent.KEYCODE_UNKNOWN, null); + } else { + cancel(); + } + return true; + default: + SearchableInfo.ActionKeyInfo actionKey = mSearchable.findActionKey(keyCode); + if ((actionKey != null) && (actionKey.mQueryActionMsg != null)) { + launchQuerySearch(keyCode, actionKey.mQueryActionMsg); + return true; + } + break; + } + return false; + } + + /** + * Callback to watch the textedit field for empty/non-empty + */ + private TextWatcher mTextWatcher = new TextWatcher() { + + public void beforeTextChanged(CharSequence s, int start, int + before, int after) { } + + public void onTextChanged(CharSequence s, int start, + int before, int after) { + if (DBG_LOG_TIMING == 1) { + dbgLogTiming("onTextChanged()"); + } + updateWidgetState(); + // Only do suggestions if actually typed by user + if ((mSuggestionsAdapter != null) && !mSuggestionsAdapter.getNonUserQuery()) { + mPreviousSuggestionQuery = s.toString(); + mUserQuery = mSearchTextField.getText().toString(); + mUserQuerySelStart = mSearchTextField.getSelectionStart(); + mUserQuerySelEnd = mSearchTextField.getSelectionEnd(); + } + } + + public void afterTextChanged(Editable s) { } + }; + + /** + * Enable/Disable the cancel button based on edit text state (any text?) + */ + private void updateWidgetState() { + // enable the button if we have one or more non-space characters + boolean enabled = + TextUtils.getTrimmedLength(mSearchTextField.getText()) != 0; + + mGoButton.setEnabled(enabled); + mGoButton.setFocusable(enabled); + } + + private final static String[] ONE_LINE_FROM = {SearchManager.SUGGEST_COLUMN_TEXT_1 }; + private final static String[] ONE_LINE_ICONS_FROM = {SearchManager.SUGGEST_COLUMN_TEXT_1, + SearchManager.SUGGEST_COLUMN_ICON_1, + SearchManager.SUGGEST_COLUMN_ICON_2}; + private final static String[] TWO_LINE_FROM = {SearchManager.SUGGEST_COLUMN_TEXT_1, + SearchManager.SUGGEST_COLUMN_TEXT_2 }; + private final static String[] TWO_LINE_ICONS_FROM = {SearchManager.SUGGEST_COLUMN_TEXT_1, + SearchManager.SUGGEST_COLUMN_TEXT_2, + SearchManager.SUGGEST_COLUMN_ICON_1, + SearchManager.SUGGEST_COLUMN_ICON_2 }; + + private final static int[] ONE_LINE_TO = {com.android.internal.R.id.text1}; + private final static int[] ONE_LINE_ICONS_TO = {com.android.internal.R.id.text1, + com.android.internal.R.id.icon1, + com.android.internal.R.id.icon2}; + private final static int[] TWO_LINE_TO = {com.android.internal.R.id.text1, + com.android.internal.R.id.text2}; + private final static int[] TWO_LINE_ICONS_TO = {com.android.internal.R.id.text1, + com.android.internal.R.id.text2, + com.android.internal.R.id.icon1, + com.android.internal.R.id.icon2}; + + /** + * Safely retrieve the suggestions cursor adapter from the ListView + * + * @param adapterView The ListView containing our adapter + * @result The CursorAdapter that we installed, or null if not set + */ + private static CursorAdapter getSuggestionsAdapter(AdapterView<?> adapterView) { + CursorAdapter result = null; + if (adapterView != null) { + Object ad = adapterView.getAdapter(); + if (ad instanceof CursorAdapter) { + result = (CursorAdapter) ad; + } else if (ad instanceof WrapperListAdapter) { + result = (CursorAdapter) ((WrapperListAdapter)ad).getWrappedAdapter(); + } + } + return result; + } + + /** + * React to typing in the GO search button by refocusing to EditText. + * Continue typing the query. + */ + View.OnKeyListener mButtonsKeyListener = new View.OnKeyListener() { + public boolean onKey(View v, int keyCode, KeyEvent event) { + // also guard against possible race conditions (late arrival after dismiss) + if (mSearchable != null) { + return refocusingKeyListener(v, keyCode, event); + } + return false; + } + }; + + /** + * React to a click in the GO button by launching a search. + */ + View.OnClickListener mGoButtonClickListener = new View.OnClickListener() { + public void onClick(View v) { + // also guard against possible race conditions (late arrival after dismiss) + if (mSearchable != null) { + launchQuerySearch(KeyEvent.KEYCODE_UNKNOWN, null); + } + } + }; + + /** + * React to a click in the voice search button. + */ + View.OnClickListener mVoiceButtonClickListener = new View.OnClickListener() { + public void onClick(View v) { + try { + if (mSearchable.getVoiceSearchLaunchWebSearch()) { + getContext().startActivity(mVoiceWebSearchIntent); + dismiss(); + } else if (mSearchable.getVoiceSearchLaunchRecognizer()) { + Intent appSearchIntent = createVoiceAppSearchIntent(mVoiceAppSearchIntent); + getContext().startActivity(appSearchIntent); + dismiss(); + } + } catch (ActivityNotFoundException e) { + // Should not happen, since we check the availability of + // voice search before showing the button. But just in case... + Log.w(LOG_TAG, "Could not find voice search activity"); + } + } + }; + + /** + * Create and return an Intent that can launch the voice search activity, perform a specific + * voice transcription, and forward the results to the searchable activity. + * + * @param baseIntent The voice app search intent to start from + * @return A completely-configured intent ready to send to the voice search activity + */ + private Intent createVoiceAppSearchIntent(Intent baseIntent) { + // create the necessary intent to set up a search-and-forward operation + // in the voice search system. We have to keep the bundle separate, + // because it becomes immutable once it enters the PendingIntent + Intent queryIntent = new Intent(Intent.ACTION_SEARCH); + queryIntent.setComponent(mSearchable.mSearchActivity); + PendingIntent pending = PendingIntent.getActivity( + getContext(), 0, queryIntent, PendingIntent.FLAG_ONE_SHOT); + + // Now set up the bundle that will be inserted into the pending intent + // when it's time to do the search. We always build it here (even if empty) + // because the voice search activity will always need to insert "QUERY" into + // it anyway. + Bundle queryExtras = new Bundle(); + if (mAppSearchData != null) { + queryExtras.putBundle(SearchManager.APP_DATA, mAppSearchData); + } + + // Now build the intent to launch the voice search. Add all necessary + // extras to launch the voice recognizer, and then all the necessary extras + // to forward the results to the searchable activity + Intent voiceIntent = new Intent(baseIntent); + + // Add all of the configuration options supplied by the searchable's metadata + String languageModel = RecognizerIntent.LANGUAGE_MODEL_FREE_FORM; + String prompt = null; + String language = null; + int maxResults = 1; + Resources resources = mActivityContext.getResources(); + if (mSearchable.getVoiceLanguageModeId() != 0) { + languageModel = resources.getString(mSearchable.getVoiceLanguageModeId()); + } + if (mSearchable.getVoicePromptTextId() != 0) { + prompt = resources.getString(mSearchable.getVoicePromptTextId()); + } + if (mSearchable.getVoiceLanguageId() != 0) { + language = resources.getString(mSearchable.getVoiceLanguageId()); + } + if (mSearchable.getVoiceMaxResults() != 0) { + maxResults = mSearchable.getVoiceMaxResults(); + } + voiceIntent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL, languageModel); + voiceIntent.putExtra(RecognizerIntent.EXTRA_PROMPT, prompt); + voiceIntent.putExtra(RecognizerIntent.EXTRA_LANGUAGE, language); + voiceIntent.putExtra(RecognizerIntent.EXTRA_MAX_RESULTS, maxResults); + + // Add the values that configure forwarding the results + voiceIntent.putExtra(RecognizerIntent.EXTRA_RESULTS_PENDINGINTENT, pending); + voiceIntent.putExtra(RecognizerIntent.EXTRA_RESULTS_PENDINGINTENT_BUNDLE, queryExtras); + + return voiceIntent; + } + + /** + * React to the user typing "enter" or other hardwired keys while typing in the search box. + * This handles these special keys while the edit box has focus. + */ + View.OnKeyListener mTextKeyListener = new View.OnKeyListener() { + public boolean onKey(View v, int keyCode, KeyEvent event) { + if (keyCode == KeyEvent.KEYCODE_BACK) { + cancel(); + return true; + } + // also guard against possible race conditions (late arrival after dismiss) + if (mSearchable != null && + TextUtils.getTrimmedLength(mSearchTextField.getText()) > 0) { + if (DBG_LOG_TIMING == 1) { + dbgLogTiming("doTextKey()"); + } + // dispatch "typing in the list" first + if (mSearchTextField.isPopupShowing() && + mSearchTextField.getListSelection() != ListView.INVALID_POSITION) { + return onSuggestionsKey(v, keyCode, event); + } + // otherwise, dispatch an "edit view" key + switch (keyCode) { + case KeyEvent.KEYCODE_ENTER: + if (event.getAction() == KeyEvent.ACTION_UP) { + v.cancelLongPress(); + launchQuerySearch(KeyEvent.KEYCODE_UNKNOWN, null); + return true; + } + break; + case KeyEvent.KEYCODE_DPAD_DOWN: + // capture the EditText state, so we can restore the user entry later + mUserQuery = mSearchTextField.getText().toString(); + mUserQuerySelStart = mSearchTextField.getSelectionStart(); + mUserQuerySelEnd = mSearchTextField.getSelectionEnd(); + // pass through - we're just watching here + break; + default: + if (event.getAction() == KeyEvent.ACTION_DOWN) { + SearchableInfo.ActionKeyInfo actionKey = mSearchable.findActionKey(keyCode); + if ((actionKey != null) && (actionKey.mQueryActionMsg != null)) { + launchQuerySearch(keyCode, actionKey.mQueryActionMsg); + return true; + } + } + break; + } + } + return false; + } + }; + + /** + * React to the user typing while the suggestions are focused. First, check for action + * keys. If not handled, try refocusing regular characters into the EditText. In this case, + * replace the query text (start typing fresh text). + */ + private boolean onSuggestionsKey(View v, int keyCode, KeyEvent event) { + boolean handled = false; + // also guard against possible race conditions (late arrival after dismiss) + if (mSearchable != null) { + handled = doSuggestionsKey(v, keyCode, event); + } + return handled; + } + + /** + * Per UI design, we're going to "steer" any typed keystrokes back into the EditText + * box, even if the user has navigated the focus to the dropdown or to the GO button. + * + * @param v The view into which the keystroke was typed + * @param keyCode keyCode of entered key + * @param event Full KeyEvent record of entered key + */ + private boolean refocusingKeyListener(View v, int keyCode, KeyEvent event) { + boolean handled = false; + + if (!event.isSystem() && + (keyCode != KeyEvent.KEYCODE_DPAD_UP) && + (keyCode != KeyEvent.KEYCODE_DPAD_DOWN) && + (keyCode != KeyEvent.KEYCODE_DPAD_LEFT) && + (keyCode != KeyEvent.KEYCODE_DPAD_RIGHT) && + (keyCode != KeyEvent.KEYCODE_DPAD_CENTER)) { + // restore focus and give key to EditText ... + // but don't replace the user's query + mLeaveJammedQueryOnRefocus = true; + if (mSearchTextField.requestFocus()) { + handled = mSearchTextField.dispatchKeyEvent(event); + } + mLeaveJammedQueryOnRefocus = false; + } + return handled; + } + + /** + * Update query text based on transitions in and out of suggestions list. + */ + /* + * TODO - figure out if this logic is required for the autocomplete text view version + + OnFocusChangeListener mSuggestFocusListener = new OnFocusChangeListener() { + public void onFocusChange(View v, boolean hasFocus) { + // also guard against possible race conditions (late arrival after dismiss) + if (mSearchable == null) { + return; + } + // Update query text based on navigation in to/out of the suggestions list + if (hasFocus) { + // Entering the list view - record selection point from user's query + mUserQuery = mSearchTextField.getText().toString(); + mUserQuerySelStart = mSearchTextField.getSelectionStart(); + mUserQuerySelEnd = mSearchTextField.getSelectionEnd(); + // then update the query to match the entered selection + jamSuggestionQuery(true, mSuggestionsList, + mSuggestionsList.getSelectedItemPosition()); + } else { + // Exiting the list view + + if (mSuggestionsList.getSelectedItemPosition() < 0) { + // Direct exit - Leave new suggestion in place (do nothing) + } else { + // Navigation exit - restore user's query text + if (!mLeaveJammedQueryOnRefocus) { + jamSuggestionQuery(false, null, -1); + } + } + } + + } + }; + */ + + /** + * This is the listener for the ACTION_CLOSE_SYSTEM_DIALOGS intent. It's an indication that + * we should close ourselves immediately, in order to allow a higher-priority UI to take over + * (e.g. phone call received). + */ + private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + String action = intent.getAction(); + if (Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(action)) { + cancel(); + } else if (Intent.ACTION_PACKAGE_ADDED.equals(action) + || Intent.ACTION_PACKAGE_REMOVED.equals(action) + || Intent.ACTION_PACKAGE_CHANGED.equals(action)) { + onPackageListChange(); + } + } + }; + + /** + * Various ways to launch searches + */ + + /** + * React to the user clicking the "GO" button. Hide the UI and launch a search. + * + * @param actionKey Pass a keycode if the launch was triggered by an action key. Pass + * KeyEvent.KEYCODE_UNKNOWN for no actionKey code. + * @param actionMsg Pass the suggestion-provided message if the launch was triggered by an + * action key. Pass null for no actionKey message. + */ + private void launchQuerySearch(int actionKey, final String actionMsg) { + final String query = mSearchTextField.getText().toString(); + final Bundle appData = mAppSearchData; + final SearchableInfo si = mSearchable; // cache briefly (dismiss() nulls it) + dismiss(); + sendLaunchIntent(Intent.ACTION_SEARCH, null, query, appData, actionKey, actionMsg, si); + } + + /** + * React to the user typing an action key while in the suggestions list + */ + private boolean doSuggestionsKey(View v, int keyCode, KeyEvent event) { + // Exit early in case of race condition + if (mSuggestionsAdapter == null) { + return false; + } + if (event.getAction() == KeyEvent.ACTION_DOWN) { + if (DBG_LOG_TIMING == 1) { + dbgLogTiming("doSuggestionsKey()"); + } + + // First, check for enter or search (both of which we'll treat as a "click") + if (keyCode == KeyEvent.KEYCODE_ENTER || keyCode == KeyEvent.KEYCODE_SEARCH) { + int position = mSearchTextField.getListSelection(); + return launchSuggestion(mSuggestionsAdapter, position); + } + + // Next, check for left/right moves, which we use to "return" the user to the edit view + if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT || keyCode == KeyEvent.KEYCODE_DPAD_RIGHT) { + // give "focus" to text editor, but don't restore the user's original query + int selPoint = (keyCode == KeyEvent.KEYCODE_DPAD_LEFT) ? + 0 : mSearchTextField.length(); + mSearchTextField.setSelection(selPoint); + mSearchTextField.setListSelection(0); + mSearchTextField.clearListSelection(); + return true; + } + + // Next, check for an "up and out" move + if (keyCode == KeyEvent.KEYCODE_DPAD_UP && 0 == mSearchTextField.getListSelection()) { + jamSuggestionQuery(false, null, -1); + // let ACTV complete the move + return false; + } + + // Next, check for an "action key" + SearchableInfo.ActionKeyInfo actionKey = mSearchable.findActionKey(keyCode); + if ((actionKey != null) && + ((actionKey.mSuggestActionMsg != null) || + (actionKey.mSuggestActionMsgColumn != null))) { + // launch suggestion using action key column + int position = mSearchTextField.getListSelection(); + if (position >= 0) { + Cursor c = mSuggestionsAdapter.getCursor(); + if (c.moveToPosition(position)) { + final String actionMsg = getActionKeyMessage(c, actionKey); + if (actionMsg != null && (actionMsg.length() > 0)) { + // shut down search bar and launch the activity + // cache everything we need because dismiss releases mems + setupSuggestionIntent(c, mSearchable); + final String query = mSearchTextField.getText().toString(); + final Bundle appData = mAppSearchData; + SearchableInfo si = mSearchable; + String suggestionAction = mSuggestionAction; + Uri suggestionData = mSuggestionData; + String suggestionQuery = mSuggestionQuery; + dismiss(); + sendLaunchIntent(suggestionAction, suggestionData, + suggestionQuery, appData, + keyCode, actionMsg, si); + return true; + } + } + } + } + } + return false; + } + + /** + * Set or reset the user query to follow the selections in the suggestions + * + * @param jamQuery True means to set the query, false means to reset it to the user's choice + */ + private void jamSuggestionQuery(boolean jamQuery, AdapterView<?> parent, int position) { + // quick check against race conditions + if (mSearchable == null) { + return; + } + + mSuggestionsAdapter.setNonUserQuery(true); // disables any suggestions processing + if (jamQuery) { + CursorAdapter ca = getSuggestionsAdapter(parent); + Cursor c = ca.getCursor(); + if (c.moveToPosition(position)) { + setupSuggestionIntent(c, mSearchable); + String jamText = null; + + // Simple heuristic for selecting text with which to rewrite the query. + if (mSuggestionQuery != null) { + jamText = mSuggestionQuery; + } else if (mSearchable.mQueryRewriteFromData && (mSuggestionData != null)) { + jamText = mSuggestionData.toString(); + } else if (mSearchable.mQueryRewriteFromText) { + try { + int column = c.getColumnIndexOrThrow(SearchManager.SUGGEST_COLUMN_TEXT_1); + jamText = c.getString(column); + } catch (RuntimeException e) { + // no work here, jamText is null + } + } + if (jamText != null) { + mSearchTextField.setText(jamText); + /* mSearchTextField.selectAll(); */ // this didn't work anyway in the old UI + // TODO this is only needed in the model where we have a selection in the ACTV + // and in the dropdown at the same time. + mSearchTextField.setSelection(jamText.length()); + } + } + } else { + // reset user query + mSearchTextField.setText(mUserQuery); + try { + mSearchTextField.setSelection(mUserQuerySelStart, mUserQuerySelEnd); + } catch (IndexOutOfBoundsException e) { + // In case of error, just select all + Log.e(LOG_TAG, "Caught IndexOutOfBoundsException while setting selection. " + + "start=" + mUserQuerySelStart + " end=" + mUserQuerySelEnd + + " text=\"" + mUserQuery + "\""); + mSearchTextField.selectAll(); + } + } + // TODO because the new query is (not) processed in another thread, we can't just + // take away this flag (yet). The better solution here is going to require a new API + // in AutoCompleteTextView which allows us to change the text w/o changing the suggestions. +// mSuggestionsAdapter.setNonUserQuery(false); + } + + /** + * Assemble a search intent and send it. + * + * @param action The intent to send, typically Intent.ACTION_SEARCH + * @param data The data for the intent + * @param query The user text entered (so far) + * @param appData The app data bundle (if supplied) + * @param actionKey If the intent was triggered by an action key, e.g. KEYCODE_CALL, it will + * be sent here. Pass KeyEvent.KEYCODE_UNKNOWN for no actionKey code. + * @param actionMsg If the intent was triggered by an action key, e.g. KEYCODE_CALL, the + * corresponding tag message will be sent here. Pass null for no actionKey message. + * @param si Reference to the current SearchableInfo. Passed here so it can be used even after + * we've called dismiss(), which attempts to null mSearchable. + */ + private void sendLaunchIntent(final String action, final Uri data, final String query, + final Bundle appData, int actionKey, final String actionMsg, final SearchableInfo si) { + Intent launcher = new Intent(action); + + if (query != null) { + launcher.putExtra(SearchManager.QUERY, query); + } + + if (data != null) { + launcher.setData(data); + } + + if (appData != null) { + launcher.putExtra(SearchManager.APP_DATA, appData); + } + + // add launch info (action key, etc.) + if (actionKey != KeyEvent.KEYCODE_UNKNOWN) { + launcher.putExtra(SearchManager.ACTION_KEY, actionKey); + launcher.putExtra(SearchManager.ACTION_MSG, actionMsg); + } + + // attempt to enforce security requirement (no 3rd-party intents) + launcher.setComponent(si.mSearchActivity); + + getContext().startActivity(launcher); + } + + /** + * Shared code for launching a query from a suggestion. + * @param ca The cursor adapter containing the suggestions + * @param position The suggestion we'll be launching from + * @return true if a successful launch, false if could not (e.g. bad position) + */ + private boolean launchSuggestion(CursorAdapter ca, int position) { + Cursor c = ca.getCursor(); + if ((c != null) && c.moveToPosition(position)) { + setupSuggestionIntent(c, mSearchable); + + final Bundle appData = mAppSearchData; + SearchableInfo si = mSearchable; + String suggestionAction = mSuggestionAction; + Uri suggestionData = mSuggestionData; + String suggestionQuery = mSuggestionQuery; + dismiss(); + sendLaunchIntent(suggestionAction, suggestionData, suggestionQuery, appData, + KeyEvent.KEYCODE_UNKNOWN, null, si); + return true; + } + return false; + } + + /** + * When a particular suggestion has been selected, perform the various lookups required + * to use the suggestion. This includes checking the cursor for suggestion-specific data, + * and/or falling back to the XML for defaults; It also creates REST style Uri data when + * the suggestion includes a data id. + * + * NOTE: Return values are in member variables mSuggestionAction & mSuggestionData. + * + * @param c The suggestions cursor, moved to the row of the user's selection + * @param si The searchable activity's info record + */ + void setupSuggestionIntent(Cursor c, SearchableInfo si) { + try { + // use specific action if supplied, or default action if supplied, or fixed default + mSuggestionAction = null; + int mColumn = c.getColumnIndex(SearchManager.SUGGEST_COLUMN_INTENT_ACTION); + if (mColumn >= 0) { + final String action = c.getString(mColumn); + if (action != null) { + mSuggestionAction = action; + } + } + if (mSuggestionAction == null) { + mSuggestionAction = si.getSuggestIntentAction(); + } + if (mSuggestionAction == null) { + mSuggestionAction = Intent.ACTION_SEARCH; + } + + // use specific data if supplied, or default data if supplied + String data = null; + mColumn = c.getColumnIndex(SearchManager.SUGGEST_COLUMN_INTENT_DATA); + if (mColumn >= 0) { + final String rowData = c.getString(mColumn); + if (rowData != null) { + data = rowData; + } + } + if (data == null) { + data = si.getSuggestIntentData(); + } + + // then, if an ID was provided, append it. + if (data != null) { + mColumn = c.getColumnIndex(SearchManager.SUGGEST_COLUMN_INTENT_DATA_ID); + if (mColumn >= 0) { + final String id = c.getString(mColumn); + if (id != null) { + data = data + "/" + Uri.encode(id); + } + } + } + mSuggestionData = (data == null) ? null : Uri.parse(data); + + mSuggestionQuery = null; + mColumn = c.getColumnIndex(SearchManager.SUGGEST_COLUMN_QUERY); + if (mColumn >= 0) { + final String query = c.getString(mColumn); + if (query != null) { + mSuggestionQuery = query; + } + } + } catch (RuntimeException e ) { + int rowNum; + try { // be really paranoid now + rowNum = c.getPosition(); + } catch (RuntimeException e2 ) { + rowNum = -1; + } + Log.w(LOG_TAG, "Search Suggestions cursor at row " + rowNum + + " returned exception" + e.toString()); + } + } + + /** + * For a given suggestion and a given cursor row, get the action message. If not provided + * by the specific row/column, also check for a single definition (for the action key). + * + * @param c The cursor providing suggestions + * @param actionKey The actionkey record being examined + * + * @return Returns a string, or null if no action key message for this suggestion + */ + private String getActionKeyMessage(Cursor c, final SearchableInfo.ActionKeyInfo actionKey) { + String result = null; + // check first in the cursor data, for a suggestion-specific message + final String column = actionKey.mSuggestActionMsgColumn; + if (column != null) { + try { + int colId = c.getColumnIndexOrThrow(column); + result = c.getString(colId); + } catch (RuntimeException e) { + // OK - result is already null + } + } + // If the cursor didn't give us a message, see if there's a single message defined + // for the actionkey (for all suggestions) + if (result == null) { + result = actionKey.mSuggestActionMsg; + } + return result; + } + + /** + * Local subclass for AutoCompleteTextView + * + * This exists entirely to override the threshold method. Otherwise we just use the class + * as-is. + */ + public static class SearchAutoComplete extends AutoCompleteTextView { + + public SearchAutoComplete(Context context) { + super(null); + } + + public SearchAutoComplete(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public SearchAutoComplete(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + } + + /** + * We never allow ACTV to automatically replace the text, since we use "jamSuggestionQuery" + * to do that. There's no point in letting ACTV do this here, because in the search UI, + * as soon as we click a suggestion, we're going to start shutting things down. + */ + @Override + public void replaceText(CharSequence text) { + } + + /** + * We always return true, so that the effective threshold is "zero". This allows us + * to provide "null" suggestions such as "just show me some recent entries". + */ + @Override + public boolean enoughToFilter() { + return true; + } + } + + /** + * Support for AutoCompleteTextView-based suggestions + */ + /** + * This class provides the filtering-based interface to suggestions providers. + * It is hardwired in a couple of places to support GoogleSearch - for example, it supports + * two-line suggestions, but it does not support icons. + */ + private static class SuggestionsAdapter extends SimpleCursorAdapter { + private final String TAG = "SuggestionsAdapter"; + + SearchableInfo mSearchable; + private Resources mProviderResources; + + // These private variables are shared by the filter thread and must be protected + private WeakReference<Cursor> mRecentCursor = new WeakReference<Cursor>(null); + private boolean mNonUserQuery = false; + + public SuggestionsAdapter(Context context, SearchableInfo searchable) { + super(context, -1, null, null, null); + mSearchable = searchable; + + // set up provider resources (gives us icons, etc.) + Context activityContext = mSearchable.getActivityContext(mContext); + Context providerContext = mSearchable.getProviderContext(mContext, activityContext); + mProviderResources = providerContext.getResources(); + } + + /** + * Set this field (temporarily!) to disable suggestions updating. This allows us + * to change the string in the text view without changing the suggestions list. + */ + public void setNonUserQuery(boolean nonUserQuery) { + synchronized (this) { + mNonUserQuery = nonUserQuery; + } + } + + public boolean getNonUserQuery() { + synchronized (this) { + return mNonUserQuery; + } + } + + /** + * Use the search suggestions provider to obtain a live cursor. This will be called + * in a worker thread, so it's OK if the query is slow (e.g. round trip for suggestions). + * The results will be processed in the UI thread and changeCursor() will be called. + * + * In order to provide the Search Mgr functionality of seeing your query change as you + * scroll through the list, we have to be able to jam new text into the string without + * retriggering the suggestions. We do that here via the "nonUserQuery" flag. In that + * case we simply return the existing cursor. + * + * TODO: Dianne suggests that this should simply be promoted into an AutoCompleteTextView + * behavior (perhaps optionally). + * + * TODO: The "nonuserquery" logic has a race condition because it happens in another thread. + * This also needs to be fixed. + */ + @Override + public Cursor runQueryOnBackgroundThread(CharSequence constraint) { + String query = (constraint == null) ? "" : constraint.toString(); + Cursor c = null; + synchronized (this) { + if (mNonUserQuery) { + c = mRecentCursor.get(); + mNonUserQuery = false; + } + } + if (c == null) { + c = getSuggestions(mSearchable, query); + synchronized (this) { + mRecentCursor = new WeakReference<Cursor>(c); + } + } + return c; + } + + /** + * Overriding changeCursor() allows us to change not only the cursor, but by sampling + * the cursor's columns, the actual display characteristics of the list. + */ + @Override + public void changeCursor(Cursor c) { + + // first, check for various conditions that disqualify this cursor + if ((c == null) || (c.getCount() == 0)) { + // no cursor, or cursor with no data + changeCursorAndColumns(null, null, null); + if (c != null) { + c.close(); + } + return; + } + + // check cursor before trying to create list views from it + int colId = c.getColumnIndex("_id"); + int col1 = c.getColumnIndex(SearchManager.SUGGEST_COLUMN_TEXT_1); + int col2 = c.getColumnIndex(SearchManager.SUGGEST_COLUMN_TEXT_2); + int colIc1 = c.getColumnIndex(SearchManager.SUGGEST_COLUMN_ICON_1); + int colIc2 = c.getColumnIndex(SearchManager.SUGGEST_COLUMN_ICON_2); + + boolean minimal = (colId >= 0) && (col1 >= 0); + boolean hasIcons = (colIc1 >= 0) && (colIc2 >= 0); + boolean has2Lines = col2 >= 0; + + if (minimal) { + int layout; + String[] from; + int[] to; + + if (hasIcons) { + if (has2Lines) { + layout = com.android.internal.R.layout.search_dropdown_item_icons_2line; + from = TWO_LINE_ICONS_FROM; + to = TWO_LINE_ICONS_TO; + } else { + layout = com.android.internal.R.layout.search_dropdown_item_icons_1line; + from = ONE_LINE_ICONS_FROM; + to = ONE_LINE_ICONS_TO; + } + } else { + if (has2Lines) { + layout = com.android.internal.R.layout.search_dropdown_item_2line; + from = TWO_LINE_FROM; + to = TWO_LINE_TO; + } else { + layout = com.android.internal.R.layout.search_dropdown_item_1line; + from = ONE_LINE_FROM; + to = ONE_LINE_TO; + } + } + // Now actually set up the cursor, columns, and the list view + changeCursorAndColumns(c, from, to); + setViewResource(layout); + } else { + // Provide some help for developers instead of just silently discarding + Log.w(LOG_TAG, "Suggestions cursor discarded due to missing required columns."); + changeCursorAndColumns(null, null, null); + c.close(); + } + if ((colIc1 >= 0) != (colIc2 >= 0)) { + Log.w(LOG_TAG, "Suggestion icon column(s) discarded, must be 0 or 2 columns."); + } + } + + /** + * Overriding this allows us to write the selected query back into the box. + * NOTE: This is a vastly simplified version of SearchDialog.jamQuery() and does + * not universally support the search API. But it is sufficient for Google Search. + */ + @Override + public CharSequence convertToString(Cursor cursor) { + CharSequence result = null; + if (cursor != null) { + int column = cursor.getColumnIndex(SearchManager.SUGGEST_COLUMN_QUERY); + if (column >= 0) { + final String query = cursor.getString(column); + if (query != null) { + result = query; + } + } + } + return result; + } + + /** + * Get the query cursor for the search suggestions. + * + * TODO this is functionally identical to the version in SearchDialog.java. Perhaps it + * could be hoisted into SearchableInfo or some other shared spot. + * + * @param query The search text entered (so far) + * @return Returns a cursor with suggestions, or null if no suggestions + */ + private Cursor getSuggestions(final SearchableInfo searchable, final String query) { + Cursor cursor = null; + if (searchable.getSuggestAuthority() != null) { + try { + StringBuilder uriStr = new StringBuilder("content://"); + uriStr.append(searchable.getSuggestAuthority()); + + // if content path provided, insert it now + final String contentPath = searchable.getSuggestPath(); + if (contentPath != null) { + uriStr.append('/'); + uriStr.append(contentPath); + } + + // append standard suggestion query path + uriStr.append('/' + SearchManager.SUGGEST_URI_PATH_QUERY); + + // inject query, either as selection args or inline + String[] selArgs = null; + if (searchable.getSuggestSelection() != null) { // use selection if provided + selArgs = new String[] {query}; + } else { + uriStr.append('/'); // no sel, use REST pattern + uriStr.append(Uri.encode(query)); + } + + // finally, make the query + cursor = mContext.getContentResolver().query( + Uri.parse(uriStr.toString()), null, + searchable.getSuggestSelection(), selArgs, + null); + } catch (RuntimeException e) { + Log.w(TAG, "Search Suggestions query returned exception " + e.toString()); + cursor = null; + } + } + + return cursor; + } + + /** + * Overriding this allows us to affect the way that an icon is loaded. Specifically, + * we can be more controlling about the resource path (and allow icons to come from other + * packages). + * + * TODO: This is 100% identical to the version in SearchDialog.java + * + * @param v ImageView to receive an image + * @param value the value retrieved from the cursor + */ + @Override + public void setViewImage(ImageView v, String value) { + int resID; + Drawable img = null; + + try { + resID = Integer.parseInt(value); + if (resID != 0) { + img = mProviderResources.getDrawable(resID); + } + } catch (NumberFormatException nfe) { + // img = null; + } catch (NotFoundException e2) { + // img = null; + } + + // finally, set the image to whatever we've gotten + v.setImageDrawable(img); + } + + /** + * This method is overridden purely to provide a bit of protection against + * flaky content providers. + * + * TODO: This is 100% identical to the version in SearchDialog.java + * + * @see android.widget.ListAdapter#getView(int, View, ViewGroup) + */ + @Override + public View getView(int position, View convertView, ViewGroup parent) { + try { + return super.getView(position, convertView, parent); + } catch (RuntimeException e) { + Log.w(TAG, "Search Suggestions cursor returned exception " + e.toString()); + // what can I return here? + View v = newView(mContext, mCursor, parent); + if (v != null) { + TextView tv = (TextView) v.findViewById(com.android.internal.R.id.text1); + tv.setText(e.toString()); + } + return v; + } + } + + } + + /** + * Implements OnItemClickListener + */ + public void onItemClick(AdapterView<?> parent, View view, int position, long id) { + // Log.d(LOG_TAG, "onItemClick() position " + position); + launchSuggestion(mSuggestionsAdapter, position); + } + + /** + * Implements OnItemSelectedListener + */ + public void onItemSelected(AdapterView<?> parent, View view, int position, long id) { + // Log.d(LOG_TAG, "onItemSelected() position " + position); + jamSuggestionQuery(true, parent, position); + } + + /** + * Implements OnItemSelectedListener + */ + public void onNothingSelected(AdapterView<?> parent) { + // Log.d(LOG_TAG, "onNothingSelected()"); + } + + /** + * Debugging Support + */ + + /** + * For debugging only, sample the millisecond clock and log it. + * Uses AtomicLong so we can use in multiple threads + */ + private AtomicLong mLastLogTime = new AtomicLong(SystemClock.uptimeMillis()); + private void dbgLogTiming(final String caller) { + long millis = SystemClock.uptimeMillis(); + long oldTime = mLastLogTime.getAndSet(millis); + long delta = millis - oldTime; + final String report = millis + " (+" + delta + ") ticks for Search keystroke in " + caller; + Log.d(LOG_TAG,report); + } +} diff --git a/core/java/android/app/SearchManager.java b/core/java/android/app/SearchManager.java new file mode 100644 index 0000000..c1d66f4 --- /dev/null +++ b/core/java/android/app/SearchManager.java @@ -0,0 +1,1385 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * 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 android.app; + +import android.content.ComponentName; +import android.content.Context; +import android.content.DialogInterface; +import android.content.res.Configuration; +import android.os.Bundle; +import android.os.Handler; +import android.os.ServiceManager; +import android.view.KeyEvent; + +/** + * This class provides access to the system search services. + * + * <p>In practice, you won't interact with this class directly, as search + * services are provided through methods in {@link android.app.Activity Activity} + * methods and the the {@link android.content.Intent#ACTION_SEARCH ACTION_SEARCH} + * {@link android.content.Intent Intent}. This class does provide a basic + * overview of search services and how to integrate them with your activities. + * If you do require direct access to the Search Manager, do not instantiate + * this class directly; instead, retrieve it through + * {@link android.content.Context#getSystemService + * context.getSystemService(Context.SEARCH_SERVICE)}. + * + * <p>Topics covered here: + * <ol> + * <li><a href="#DeveloperGuide">Developer Guide</a> + * <li><a href="#HowSearchIsInvoked">How Search Is Invoked</a> + * <li><a href="#QuerySearchApplications">Query-Search Applications</a> + * <li><a href="#FilterSearchApplications">Filter-Search Applications</a> + * <li><a href="#Suggestions">Search Suggestions</a> + * <li><a href="#ActionKeys">Action Keys</a> + * <li><a href="#SearchabilityMetadata">Searchability Metadata</a> + * <li><a href="#PassingSearchContext">Passing Search Context</a> + * <li><a href="#ProtectingUserPrivacy">Protecting User Privacy</a> + * </ol> + * + * <a name="DeveloperGuide"></a> + * <h3>Developer Guide</h3> + * + * <p>The ability to search for user, system, or network based data is considered to be + * a core user-level feature of the android platform. At any time, the user should be + * able to use a familiar command, button, or keystroke to invoke search, and the user + * should be able to search any data which is available to them. The goal is to make search + * appear to the user as a seamless, system-wide feature. + * + * <p>In terms of implementation, there are three broad classes of Applications: + * <ol> + * <li>Applications that are not inherently searchable</li> + * <li>Query-Search Applications</li> + * <li>Filter-Search Applications</li> + * </ol> + * <p>These categories, as well as related topics, are discussed in + * the sections below. + * + * <p>Even if your application is not <i>searchable</i>, it can still support the invocation of + * search. Please review the section <a href="#HowSearchIsInvoked">How Search Is Invoked</a> + * for more information on how to support this. + * + * <p>Many applications are <i>searchable</i>. These are + * the applications which can convert a query string into a list of results. + * Within this subset, applications can be grouped loosely into two families: + * <ul><li><i>Query Search</i> applications perform batch-mode searches - each query string is + * converted to a list of results.</li> + * <li><i>Filter Search</i> applications provide live filter-as-you-type searches.</li></ul> + * <p>Generally speaking, you would use query search for network-based data, and filter + * search for local data, but this is not a hard requirement and applications + * are free to use the model that fits them best (or invent a new model). + * <p>It should be clear that the search implementation decouples "search + * invocation" from "searchable". This satisfies the goal of making search appear + * to be "universal". The user should be able to launch any search from + * almost any context. + * + * <a name="HowSearchIsInvoked"></a> + * <h3>How Search Is Invoked</h3> + * + * <p>Unless impossible or inapplicable, all applications should support + * invoking the search UI. This means that when the user invokes the search command, + * a search UI will be presented to them. The search command is currently defined as a menu + * item called "Search" (with an alphabetic shortcut key of "S"), or on some devices, a dedicated + * search button key. + * <p>If your application is not inherently searchable, you can also allow the search UI + * to be invoked in a "web search" mode. If the user enters a search term and clicks the + * "Search" button, this will bring the browser to the front and will launch a web-based + * search. The user will be able to click the "Back" button and return to your application. + * <p>In general this is implemented by your activity, or the {@link android.app.Activity Activity} + * base class, which captures the search command and invokes the Search Manager to + * display and operate the search UI. You can also cause the search UI to be presented in response + * to user keystrokes in your activity (for example, to instantly start filter searching while + * viewing a list and typing any key). + * <p>The search UI is presented as a floating + * window and does not cause any change in the activity stack. If the user + * cancels search, the previous activity re-emerges. If the user launches a + * search, this will be done by sending a search {@link android.content.Intent Intent} (see below), + * and the normal intent-handling sequence will take place (your activity will pause, + * etc.) + * <p><b>What you need to do:</b> First, you should consider the way in which you want to + * handle invoking search. There are four broad (and partially overlapping) categories for + * you to choose from. + * <ul><li>You can capture the search command yourself, by including a <i>search</i> + * button or menu item - and invoking the search UI directly.</li> + * <li>You can provide a <i>type-to-search</i> feature, in which search is invoked automatically + * when the user enters any characters.</li> + * <li>Even if your application is not inherently searchable, you can allow web search, + * via the search key (or even via a search menu item). + * <li>You can disable search entirely. This should only be used in very rare circumstances, + * as search is a system-wide feature and users will expect it to be available in all contexts.</li> + * </ul> + * + * <p><b>How to define a search menu.</b> The system provides the following resources which may + * be useful when adding a search item to your menu: + * <ul><li>android.R.drawable.ic_search_category_default is an icon you can use in your menu.</li> + * <li>{@link #MENU_KEY SearchManager.MENU_KEY} is the recommended alphabetic shortcut.</li> + * </ul> + * + * <p><b>How to invoke search directly.</b> In order to invoke search directly, from a button + * or menu item, you can launch a generic search by calling + * {@link android.app.Activity#onSearchRequested onSearchRequested} as shown: + * <pre class="prettyprint"> + * onSearchRequested();</pre> + * + * <p><b>How to implement type-to-search.</b> While setting up your activity, call + * {@link android.app.Activity#setDefaultKeyMode setDefaultKeyMode}: + * <pre class="prettyprint"> + * setDefaultKeyMode(DEFAULT_KEYS_SEARCH_LOCAL); // search within your activity + * setDefaultKeyMode(DEFAULT_KEYS_SEARCH_GLOBAL); // search using platform global search</pre> + * + * <p><b>How to enable web-based search.</b> In addition to searching within your activity or + * application, you can also use the Search Manager to invoke a platform-global search, typically + * a web search. There are two ways to do this: + * <ul><li>You can simply define "search" within your application or activity to mean global search. + * This is described in more detail in the + * <a href="#SearchabilityMetadata">Searchability Metadata</a> section. Briefly, you will + * add a single meta-data entry to your manifest, declaring that the default search + * for your application is "*". This indicates to the system that no application-specific + * search activity is provided, and that it should launch web-based search instead.</li> + * <li>You can specify this at invocation time via default keys (see above), overriding + * {@link android.app.Activity#onSearchRequested}, or via a direct call to + * {@link android.app.Activity#startSearch}. This is most useful if you wish to provide local + * searchability <i>and</i> access to global search.</li></ul> + * + * <p><b>How to disable search from your activity.</b> search is a system-wide feature and users + * will expect it to be available in all contexts. If your UI design absolutely precludes + * launching search, override {@link android.app.Activity#onSearchRequested onSearchRequested} + * as shown: + * <pre class="prettyprint"> + * @Override + * public boolean onSearchRequested() { + * return false; + * }</pre> + * + * <p><b>Managing focus and knowing if Search is active.</b> The search UI is not a separate + * activity, and when the UI is invoked or dismissed, your activity will not typically be paused, + * resumed, or otherwise notified by the methods defined in + * <a href="{@docRoot}guide/topics/fundamentals.html#actlife">Application Fundamentals: + * Activity Lifecycle</a>. The search UI is + * handled in the same way as other system UI elements which may appear from time to time, such as + * notifications, screen locks, or other system alerts: + * <p>When the search UI appears, your activity will lose input focus. + * <p>When the search activity is dismissed, there are three possible outcomes: + * <ul><li>If the user simply canceled the search UI, your activity will regain input focus and + * proceed as before. See {@link #setOnDismissListener} and {@link #setOnCancelListener} if you + * required direct notification of search dialog dismissals.</li> + * <li>If the user launched a search, and this required switching to another activity to receive + * and process the search {@link android.content.Intent Intent}, your activity will receive the + * normal sequence of activity pause or stop notifications.</li> + * <li>If the user launched a search, and the current activity is the recipient of the search + * {@link android.content.Intent Intent}, you will receive notification via the + * {@link android.app.Activity#onNewIntent onNewIntent()} method.</li></ul> + * <p>This list is provided in order to clarify the ways in which your activities will interact with + * the search UI. More details on searchable activities and search intents are provided in the + * sections below. + * + * <a name="QuerySearchApplications"></a> + * <h3>Query-Search Applications</h3> + * + * <p>Query-search applications are those that take a single query (e.g. a search + * string) and present a set of results that may fit. Primary examples include + * web queries, map lookups, or email searches (with the common thread being + * network query dispatch). It may also be the case that certain local searches + * are treated this way. It's up to the application to decide. + * + * <p><b>What you need to do:</b> The following steps are necessary in order to + * implement query search. + * <ul> + * <li>Implement search invocation as described above. (Strictly speaking, + * these are decoupled, but it would make little sense to be "searchable" but not + * "search-invoking".)</li> + * <li>Your application should have an activity that takes a search string and + * converts it to a list of results. This could be your primary display activity + * or it could be a dedicated search results activity. This is your <i>searchable</i> + * activity and every query-search application must have one.</li> + * <li>In the searchable activity, in onCreate(), you must receive and handle the + * {@link android.content.Intent#ACTION_SEARCH ACTION_SEARCH} + * {@link android.content.Intent Intent}. The text to search (query string) for is provided by + * calling + * {@link #QUERY getStringExtra(SearchManager.QUERY)}.</li> + * <li>To identify and support your searchable activity, you'll need to + * provide an XML file providing searchability configuration parameters, a reference to that + * in your searchable activity's <a href="{@docRoot}guide/topics/manifest/manifest-intro.html">manifest</a> + * entry, and an intent-filter declaring that you can + * receive ACTION_SEARCH intents. This is described in more detail in the + * <a href="#SearchabilityMetadata">Searchability Metadata</a> section.</li> + * <li>Your <a href="{@docRoot}guide/topics/manifest/manifest-intro.html">manifest</a> also needs a metadata entry + * providing a global reference to the searchable activity. This is the "glue" directing the search + * UI, when invoked from any of your <i>other</i> activities, to use your application as the + * default search context. This is also described in more detail in the + * <a href="#SearchabilityMetadata">Searchability Metadata</a> section.</li> + * <li>Finally, you may want to define your search results activity as with the + * {@link android.R.attr#launchMode singleTop} launchMode flag. This allows the system + * to launch searches from/to the same activity without creating a pile of them on the + * activity stack. If you do this, be sure to also override + * {@link android.app.Activity#onNewIntent onNewIntent} to handle the + * updated intents (with new queries) as they arrive.</li> + * </ul> + * + * <p>Code snippet showing handling of intents in your search activity: + * <pre class="prettyprint"> + * @Override + * protected void onCreate(Bundle icicle) { + * super.onCreate(icicle); + * + * final Intent queryIntent = getIntent(); + * final String queryAction = queryIntent.getAction(); + * if (Intent.ACTION_SEARCH.equals(queryAction)) { + * doSearchWithIntent(queryIntent); + * } + * } + * + * private void doSearchWithIntent(final Intent queryIntent) { + * final String queryString = queryIntent.getStringExtra(SearchManager.QUERY); + * doSearchWithQuery(queryString); + * }</pre> + * + * <a name="FilterSearchApplications"></a> + * <h3>Filter-Search Applications</h3> + * + * <p>Filter-search applications are those that use live text entry (e.g. keystrokes)) to + * display and continuously update a list of results. Primary examples include applications + * that use locally-stored data. + * + * <p>Filter search is not directly supported by the Search Manager. Most filter search + * implementations will use variants of {@link android.widget.Filterable}, such as a + * {@link android.widget.ListView} bound to a {@link android.widget.SimpleCursorAdapter}. However, + * you may find it useful to mix them together, by declaring your filtered view searchable. With + * this configuration, you can still present the standard search dialog in all activities + * within your application, but transition to a filtered search when you enter the activity + * and display the results. + * + * <a name="Suggestions"></a> + * <h3>Search Suggestions</h3> + * + * <p>A powerful feature of the Search Manager is the ability of any application to easily provide + * live "suggestions" in order to prompt the user. Each application implements suggestions in a + * different, unique, and appropriate way. Suggestions be drawn from many sources, including but + * not limited to: + * <ul> + * <li>Actual searchable results (e.g. names in the address book)</li> + * <li>Recently entered queries</li> + * <li>Recently viewed data or results</li> + * <li>Contextually appropriate queries or results</li> + * <li>Summaries of possible results</li> + * </ul> + * + * <p>Another feature of suggestions is that they can expose queries or results before the user + * ever visits the application. This reduces the amount of context switching required, and helps + * the user access their data quickly and with less context shifting. In order to provide this + * capability, suggestions are accessed via a + * {@link android.content.ContentProvider Content Provider}. + * + * <p>The primary form of suggestions is known as <i>queried suggestions</i> and is based on query + * text that the user has already typed. This would generally be based on partial matches in + * the available data. In certain situations - for example, when no query text has been typed yet - + * an application may also opt to provide <i>zero-query suggestions</i>. + * These would typically be drawn from the same data source, but because no partial query text is + * available, they should be weighted based on other factors - for example, most recent queries + * or most recent results. + * + * <p><b>Overview of how suggestions are provided.</b> When the search manager identifies a + * particular activity as searchable, it will check for certain metadata which indicates that + * there is also a source of suggestions. If suggestions are provided, the following steps are + * taken. + * <ul><li>Using formatting information found in the metadata, the user's query text (whatever + * has been typed so far) will be formatted into a query and sent to the suggestions + * {@link android.content.ContentProvider Content Provider}.</li> + * <li>The suggestions {@link android.content.ContentProvider Content Provider} will create a + * {@link android.database.Cursor Cursor} which can iterate over the possible suggestions.</li> + * <li>The search manager will populate a list using display data found in each row of the cursor, + * and display these suggestions to the user.</li> + * <li>If the user types another key, or changes the query in any way, the above steps are repeated + * and the suggestions list is updated or repopulated.</li> + * <li>If the user clicks or touches the "GO" button, the suggestions are ignored and the search is + * launched using the normal {@link android.content.Intent#ACTION_SEARCH ACTION_SEARCH} type of + * {@link android.content.Intent Intent}.</li> + * <li>If the user uses the directional controls to navigate the focus into the suggestions list, + * the query text will be updated while the user navigates from suggestion to suggestion. The user + * can then click or touch the updated query and edit it further. If the user navigates back to + * the edit field, the original typed query is restored.</li> + * <li>If the user clicks or touches a particular suggestion, then a combination of data from the + * cursor and + * values found in the metadata are used to synthesize an Intent and send it to the application. + * Depending on the design of the activity and the way it implements search, this might be a + * {@link android.content.Intent#ACTION_SEARCH ACTION_SEARCH} (in order to launch a query), or it + * might be a {@link android.content.Intent#ACTION_VIEW ACTION_VIEW}, in order to proceed directly + * to display of specific data.</li> + * </ul> + * + * <p><b>Simple Recent-Query-Based Suggestions.</b> The Android framework provides a simple Search + * Suggestions provider, which simply records and replays recent queries. For many applications, + * this will be sufficient. The basic steps you will need to + * do, in order to use the built-in recent queries suggestions provider, are as follows: + * <ul> + * <li>Implement and test query search, as described in the previous sections.</li> + * <li>Create a Provider within your application by extending + * {@link android.content.SearchRecentSuggestionsProvider}.</li> + * <li>Create a manifest entry describing your provider.</li> + * <li>Update your searchable activity's XML configuration file with information about your + * provider.</li> + * <li>In your searchable activities, capture any user-generated queries and record them + * for future searches by calling {@link android.provider.SearchRecentSuggestions#saveRecentQuery}. + * </li> + * </ul> + * <p>For complete implementation details, please refer to + * {@link android.content.SearchRecentSuggestionsProvider}. The rest of the information in this + * section should not be necessary, as it refers to custom suggestions providers. + * + * <p><b>Creating a Customized Suggestions Provider:</b> In order to create more sophisticated + * suggestion providers, you'll need to take the following steps: + * <ul> + * <li>Implement and test query search, as described in the previous sections.</li> + * <li>Decide how you wish to <i>receive</i> suggestions. Just like queries that the user enters, + * suggestions will be delivered to your searchable activity as + * {@link android.content.Intent Intent} messages; Unlike simple queries, you have quite a bit of + * flexibility in forming those intents. A query search application will probably + * wish to continue receiving the {@link android.content.Intent#ACTION_SEARCH ACTION_SEARCH} + * {@link android.content.Intent Intent}, which will launch a query search using query text as + * provided by the suggestion. A filter search application will probably wish to + * receive the {@link android.content.Intent#ACTION_VIEW ACTION_VIEW} + * {@link android.content.Intent Intent}, which will take the user directly to a selected entry. + * Other interesting suggestions, including hybrids, are possible, and the suggestion provider + * can easily mix-and-match results to provide a richer set of suggestions for the user. Finally, + * you'll need to update your searchable activity (or other activities) to receive the intents + * as you've defined them.</li> + * <li>Implement a Content Provider that provides suggestions. If you already have one, and it + * has access to your suggestions data. If not, you'll have to create one. + * You'll also provide information about your Content Provider in your + * package's <a href="{@docRoot}guide/topics/manifest/manifest-intro.html">manifest</a>.</li> + * <li>Update your searchable activity's XML configuration file. There are two categories of + * information used for suggestions: + * <ul><li>The first is (required) data that the search manager will + * use to format the queries which are sent to the Content Provider.</li> + * <li>The second is (optional) parameters to configure structure + * if intents generated by suggestions.</li></li> + * </ul> + * </ul> + * + * <p><b>Configuring your Content Provider to Receive Suggestion Queries.</b> The basic job of + * a search suggestions {@link android.content.ContentProvider Content Provider} is to provide + * "live" (while-you-type) conversion of the user's query text into a set of zero or more + * suggestions. Each application is free to define the conversion, and as described above there are + * many possible solutions. This section simply defines how to communicate with the suggestion + * provider. + * + * <p>The Search Manager must first determine if your package provides suggestions. This is done + * by examination of your searchable meta-data XML file. The android:searchSuggestAuthority + * attribute, if provided, is the signal to obtain & display suggestions. + * + * <p>Every query includes a Uri, and the Search Manager will format the Uri as shown: + * <p><pre class="prettyprint"> + * content:// your.suggest.authority / your.suggest.path / SearchManager.SUGGEST_URI_PATH_QUERY</pre> + * + * <p>Your Content Provider can receive the query text in one of two ways. + * <ul> + * <li><b>Query provided as a selection argument.</b> If you define the attribute value + * android:searchSuggestSelection and include a string, this string will be passed as the + * <i>selection</i> parameter to your Content Provider's query function. You must define a single + * selection argument, using the '?' character. The user's query text will be passed to you + * as the first element of the selection arguments array.</li> + * <li><b>Query provided with Data Uri.</b> If you <i>do not</i> define the attribute value + * android:searchSuggestSelection, then the Search Manager will append another "/" followed by + * the user's query to the query Uri. The query will be encoding using Uri encoding rules - don't + * forget to decode it. (See {@link android.net.Uri#getPathSegments} and + * {@link android.net.Uri#getLastPathSegment} for helpful utilities you can use here.)</li> + * </ul> + * + * <p><b>Handling empty queries.</b> Your application should handle the "empty query" + * (no user text entered) case properly, and generate useful suggestions in this case. There are a + * number of ways to do this; Two are outlined here: + * <ul><li>For a simple filter search of local data, you could simply present the entire dataset, + * unfiltered. (example: People)</li> + * <li>For a query search, you could simply present the most recent queries. This allows the user + * to quickly repeat a recent search.</li></ul> + * + * <p><b>The Format of Individual Suggestions.</b> Your suggestions are communicated back to the + * Search Manager by way of a {@link android.database.Cursor Cursor}. The Search Manager will + * usually pass a null Projection, which means that your provider can simply return all appropriate + * columns for each suggestion. The columns currently defined are: + * + * <table border="2" width="85%" align="center" frame="hsides" rules="rows"> + * + * <thead> + * <tr><th>Column Name</th> <th>Description</th> <th>Required?</th></tr> + * </thead> + * + * <tbody> + * <tr><th>{@link #SUGGEST_COLUMN_FORMAT}</th> + * <td><i>Unused - can be null.</i></td> + * <td align="center">No</td> + * </tr> + * + * <tr><th>{@link #SUGGEST_COLUMN_TEXT_1}</th> + * <td>This is the line of text that will be presented to the user as the suggestion.</td> + * <td align="center">Yes</td> + * </tr> + * + * <tr><th>{@link #SUGGEST_COLUMN_TEXT_2}</th> + * <td>If your cursor includes this column, then all suggestions will be provided in a + * two-line format. The data in this column will be displayed as a second, smaller + * line of text below the primary suggestion, or it can be null or empty to indicate no + * text in this row's suggestion.</td> + * <td align="center">No</td> + * </tr> + * + * <tr><th>{@link #SUGGEST_COLUMN_ICON_1}</th> + * <td>If your cursor includes this column, then all suggestions will be provided in an + * icons+text format. This value should be a reference (resource ID) of the icon to + * draw on the left side, or it can be null or zero to indicate no icon in this row. + * You must provide both cursor columns, or neither. + * </td> + * <td align="center">No, but required if you also have {@link #SUGGEST_COLUMN_ICON_2}</td> + * </tr> + * + * <tr><th>{@link #SUGGEST_COLUMN_ICON_2}</th> + * <td>If your cursor includes this column, then all suggestions will be provided in an + * icons+text format. This value should be a reference (resource ID) of the icon to + * draw on the right side, or it can be null or zero to indicate no icon in this row. + * You must provide both cursor columns, or neither. + * </td> + * <td align="center">No, but required if you also have {@link #SUGGEST_COLUMN_ICON_1}</td> + * </tr> + * + * <tr><th>{@link #SUGGEST_COLUMN_INTENT_ACTION}</th> + * <td>If this column exists <i>and</i> this element exists at the given row, this is the + * action that will be used when forming the suggestion's intent. If the element is + * not provided, the action will be taken from the android:searchSuggestIntentAction + * field in your XML metadata. <i>At least one of these must be present for the + * suggestion to generate an intent.</i> Note: If your action is the same for all + * suggestions, it is more efficient to specify it using XML metadata and omit it from + * the cursor.</td> + * <td align="center">No</td> + * </tr> + * + * <tr><th>{@link #SUGGEST_COLUMN_INTENT_DATA}</th> + * <td>If this column exists <i>and</i> this element exists at the given row, this is the + * data that will be used when forming the suggestion's intent. If the element is not + * provided, the data will be taken from the android:searchSuggestIntentData field in + * your XML metadata. If neither source is provided, the Intent's data field will be + * null. Note: If your data is the same for all suggestions, or can be described + * using a constant part and a specific ID, it is more efficient to specify it using + * XML metadata and omit it from the cursor.</td> + * <td align="center">No</td> + * </tr> + * + * <tr><th>{@link #SUGGEST_COLUMN_INTENT_DATA_ID}</th> + * <td>If this column exists <i>and</i> this element exists at the given row, then "/" and + * this value will be appended to the data field in the Intent. This should only be + * used if the data field has already been set to an appropriate base string.</td> + * <td align="center">No</td> + * </tr> + * + * <tr><th>{@link #SUGGEST_COLUMN_QUERY}</th> + * <td>If this column exists <i>and</i> this element exists at the given row, this is the + * data that will be used when forming the suggestion's query.</td> + * <td align="center">Required if suggestion's action is + * {@link android.content.Intent#ACTION_SEARCH ACTION_SEARCH}, optional otherwise.</td> + * </tr> + * + * <tr><th><i>Other Columns</i></th> + * <td>Finally, if you have defined any <a href="#ActionKeys">Action Keys</a> and you wish + * for them to have suggestion-specific definitions, you'll need to define one + * additional column per action key. The action key will only trigger if the + * currently-selection suggestion has a non-empty string in the corresponding column. + * See the section on <a href="#ActionKeys">Action Keys</a> for additional details and + * implementation steps.</td> + * <td align="center">No</td> + * </tr> + * + * </tbody> + * </table> + * + * <p>Clearly there are quite a few permutations of your suggestion data, but in the next section + * we'll look at a few simple combinations that you'll select from. + * + * <p><b>The Format Of Intents Sent By Search Suggestions.</b> Although there are many ways to + * configure these intents, this document will provide specific information on just a few of them. + * <ul><li><b>Launch a query.</b> In this model, each suggestion represents a query that your + * searchable activity can perform, and the {@link android.content.Intent Intent} will be formatted + * exactly like those sent when the user enters query text and clicks the "GO" button: + * <ul> + * <li><b>Action:</b> {@link android.content.Intent#ACTION_SEARCH ACTION_SEARCH} provided + * using your XML metadata (android:searchSuggestIntentAction).</li> + * <li><b>Data:</b> empty (not used).</li> + * <li><b>Query:</b> query text supplied by the cursor.</li> + * </ul> + * </li> + * <li><b>Go directly to a result, using a complete Data Uri.</b> In this model, the user will be + * taken directly to a specific result. + * <ul> + * <li><b>Action:</b> {@link android.content.Intent#ACTION_VIEW ACTION_VIEW}</li> + * <li><b>Data:</b> a complete Uri, supplied by the cursor, that identifies the desired data.</li> + * <li><b>Query:</b> query text supplied with the suggestion (probably ignored)</li> + * </ul> + * </li> + * <li><b>Go directly to a result, using a synthesized Data Uri.</b> This has the same result + * as the previous suggestion, but provides the Data Uri in a different way. + * <ul> + * <li><b>Action:</b> {@link android.content.Intent#ACTION_VIEW ACTION_VIEW}</li> + * <li><b>Data:</b> The search manager will assemble a Data Uri using the following elements: + * a Uri fragment provided in your XML metadata (android:searchSuggestIntentData), followed by + * a single "/", followed by the value found in the {@link #SUGGEST_COLUMN_INTENT_DATA_ID} + * entry in your cursor.</li> + * <li><b>Query:</b> query text supplied with the suggestion (probably ignored)</li> + * </ul> + * </li> + * </ul> + * <p>This list is not meant to be exhaustive. Applications should feel free to define other types + * of suggestions. For example, you could reduce long lists of results to summaries, and use one + * of the above intents (or one of your own) with specially formatted Data Uri's to display more + * detailed results. Or you could display textual shortcuts as suggestions, but launch a display + * in a more data-appropriate format such as media artwork. + * + * <p><b>Suggestion Rewriting.</b> If the user navigates through the suggestions list, the UI + * may temporarily rewrite the user's query with a query that matches the currently selected + * suggestion. This enables the user to see what query is being suggested, and also allows the user + * to click or touch in the entry EditText element and make further edits to the query before + * dispatching it. In order to perform this correctly, the Search UI needs to know exactly what + * text to rewrite the query with. + * + * <p>For each suggestion, the following logic is used to select a new query string: + * <ul><li>If the suggestion provides an explicit value in the {@link #SUGGEST_COLUMN_QUERY} + * column, this value will be used.</li> + * <li>If the metadata includes the queryRewriteFromData flag, and the suggestion provides an + * explicit value for the intent Data field, this Uri will be used. Note that this should only be + * used with Uri's that are intended to be user-visible, such as HTTP. Internal Uri schemes should + * not be used in this way.</li> + * <li>If the metadata includes the queryRewriteFromText flag, the text in + * {@link #SUGGEST_COLUMN_TEXT_1} will be used. This should be used for suggestions in which no + * query text is provided and the SUGGEST_COLUMN_INTENT_DATA values are not suitable for user + * inspection and editing.</li></ul> + * + * <a name="ActionKeys"></a> + * <h3>Action Keys</h3> + * + * <p>Searchable activities may also wish to provide shortcuts based on the various action keys + * available on the device. The most basic example of this is the contacts app, which enables the + * green "dial" key for quick access during searching. Not all action keys are available on + * every device, and not all are allowed to be overriden in this way. (For example, the "Home" + * key must always return to the home screen, with no exceptions.) + * + * <p>In order to define action keys for your searchable application, you must do two things. + * + * <ul> + * <li>You'll add one or more <i>actionkey</i> elements to your searchable metadata configuration + * file. Each element defines one of the keycodes you are interested in, + * defines the conditions under which they are sent, and provides details + * on how to communicate the action key event back to your searchable activity.</li> + * <li>In your broadcast receiver, if you wish, you can check for action keys by checking the + * extras field of the {@link android.content.Intent Intent}.</li> + * </ul> + * + * <p><b>Updating metadata.</b> For each keycode of interest, you must add an <actionkey> + * element. Within this element you must define two or three attributes. The first attribute, + * <android:keycode>, is required; It is the key code of the action key event, as defined in + * {@link android.view.KeyEvent}. The remaining two attributes define the value of the actionkey's + * <i>message</i>, which will be passed to your searchable activity in the + * {@link android.content.Intent Intent} (see below for more details). Although each of these + * attributes is optional, you must define one or both for the action key to have any effect. + * <android:queryActionMsg> provides the message that will be sent if the action key is + * pressed while the user is simply entering query text. <android:suggestActionMsgColumn> + * is used when action keys are tied to specific suggestions. This attribute provides the name + * of a <i>column</i> in your suggestion cursor; The individual suggestion, in that column, + * provides the message. (If the cell is empty or null, that suggestion will not work with that + * action key.) + * <p>See the <a href="#SearchabilityMetadata">Searchability Metadata</a> section for more details + * and examples. + * + * <p><b>Receiving Action Keys</b> Intents launched by action keys will be specially marked + * using a combination of values. This enables your searchable application to examine the intent, + * if necessary, and perform special processing. For example, clicking a suggested contact might + * simply display them; Selecting a suggested contact and clicking the dial button might + * immediately call them. + * + * <p>When a search {@link android.content.Intent Intent} is launched by an action key, two values + * will be added to the extras field. + * <ul> + * <li>To examine the key code, use {@link android.content.Intent#getIntExtra + * getIntExtra(SearchManager.ACTION_KEY)}.</li> + * <li>To examine the message string, use {@link android.content.Intent#getStringExtra + * getStringExtra(SearchManager.ACTION_MSG)}</li> + * </ul> + * + * <a name="SearchabilityMetadata"></a> + * <h3>Searchability Metadata</h3> + * + * <p>Every activity that is searchable must provide a small amount of additional information + * in order to properly configure the search system. This controls the way that your search + * is presented to the user, and controls for the various modalities described previously. + * + * <p>If your application is not searchable, + * then you do not need to provide any search metadata, and you can skip the rest of this section. + * When this search metadata cannot be found, the search manager will assume that the activity + * does not implement search. (Note: to implement web-based search, you will need to add + * the android.app.default_searchable metadata to your manifest, as shown below.) + * + * <p>Values you supply in metadata apply only to each local searchable activity. Each + * searchable activity can define a completely unique search experience relevant to its own + * capabilities and user experience requirements, and a single application can even define multiple + * searchable activities. + * + * <p><b>Metadata for searchable activity.</b> As with your search implementations described + * above, you must first identify which of your activities is searchable. In the + * <a href="{@docRoot}guide/topics/manifest/manifest-intro.html">manifest</a> entry for this activity, you must + * provide two elements: + * <ul><li>An intent-filter specifying that you can receive and process the + * {@link android.content.Intent#ACTION_SEARCH ACTION_SEARCH} {@link android.content.Intent Intent}. + * </li> + * <li>A reference to a small XML file (typically called "searchable.xml") which contains the + * remaining configuration information for how your application implements search.</li></ul> + * + * <p>Here is a snippet showing the necessary elements in the + * <a href="{@docRoot}guide/topics/manifest/manifest-intro.html">manifest</a> entry for your searchable activity. + * <pre class="prettyprint"> + * <!-- Search Activity - searchable --> + * <activity android:name="MySearchActivity" + * android:label="Search" + * android:launchMode="singleTop"> + * <intent-filter> + * <action android:name="android.intent.action.SEARCH" /> + * <category android:name="android.intent.category.DEFAULT" /> + * </intent-filter> + * <meta-data android:name="android.app.searchable" + * android:resource="@xml/searchable" /> + * </activity></pre> + * + * <p>Next, you must provide the rest of the searchability configuration in + * the small XML file, stored in the ../xml/ folder in your build. The XML file is a + * simple enumeration of the search configuration parameters for searching within this activity, + * application, or package. Here is a sample XML file (named searchable.xml, for use with + * the above manifest) for a query-search activity. + * + * <pre class="prettyprint"> + * <searchable xmlns:android="http://schemas.android.com/apk/res/android" + * android:label="@string/search_label" + * android:hint="@string/search_hint" > + * </searchable></pre> + * + * <p>Note that all user-visible strings <i>must</i> be provided in the form of "@string" + * references. Hard-coded strings, which cannot be localized, will not work properly in search + * metadata. + * + * <p>Attributes you can set in search metadata: + * <table border="2" width="85%" align="center" frame="hsides" rules="rows"> + * + * <thead> + * <tr><th>Attribute</th> <th>Description</th> <th>Required?</th></tr> + * </thead> + * + * <tbody> + * <tr><th>android:label</th> + * <td>This is the name for your application that will be presented to the user in a + * list of search targets, or in the search box as a label.</td> + * <td align="center">Yes</td> + * </tr> + * + * <tr><th>android:icon</th> + * <td>If provided, this icon will be used <i>in place</i> of the label string. This + * is provided in order to present logos or other non-textual banners.</td> + * <td align="center">No</td> + * </tr> + * + * <tr><th>android:hint</th> + * <td>This is the text to display in the search text field when no user text has been + * entered.</td> + * <td align="center">No</td> + * </tr> + * + * <tr><th>android:searchButtonText</th> + * <td>If provided, this text will replace the default text in the "Search" button.</td> + * <td align="center">No</td> + * </tr> + * + * <tr><th>android:searchMode</th> + * <td>If provided and non-zero, sets additional modes for control of the search + * presentation. The following mode bits are defined: + * <table border="2" align="center" frame="hsides" rules="rows"> + * <tbody> + * <tr><th>showSearchLabelAsBadge</th> + * <td>If set, this flag enables the display of the search target (label) + * within the search bar. If this flag and showSearchIconAsBadge + * (see below) are both not set, no badge will be shown.</td> + * </tr> + * <tr><th>showSearchIconAsBadge</th> + * <td>If set, this flag enables the display of the search target (icon) within + * the search bar. If this flag and showSearchLabelAsBadge + * (see above) are both not set, no badge will be shown. If both flags + * are set, showSearchIconAsBadge has precedence and the icon will be + * shown.</td> + * </tr> + * <tr><th>queryRewriteFromData</th> + * <td>If set, this flag causes the suggestion column SUGGEST_COLUMN_INTENT_DATA + * to be considered as the text for suggestion query rewriting. This should + * only be used when the values in SUGGEST_COLUMN_INTENT_DATA are suitable + * for user inspection and editing - typically, HTTP/HTTPS Uri's.</td> + * </tr> + * <tr><th>queryRewriteFromText</th> + * <td>If set, this flag causes the suggestion column SUGGEST_COLUMN_TEXT_1 to + * be considered as the text for suggestion query rewriting. This should + * be used for suggestions in which no query text is provided and the + * SUGGEST_COLUMN_INTENT_DATA values are not suitable for user inspection + * and editing.</td> + * </tr> + * </tbody> + * </table></td> + * <td align="center">No</td> + * </tr> + * + * <tr><th>android:inputType</th> + * <td>If provided, supplies a hint about the type of search text the user will be + * entering. For most searches, in which free form text is expected, this attribute + * need not be provided. Suitable values for this attribute are described in the + * <a href="../R.attr.html#inputType">inputType</a> attribute.</td> + * <td align="center">No</td> + * </tr> + * <tr><th>android:imeOptions</th> + * <td>If provided, supplies additional options for the input method. + * For most searches, in which free form text is expected, this attribute + * need not be provided, and will default to "actionSearch". + * Suitable values for this attribute are described in the + * <a href="../R.attr.html#imeOptions">imeOptions</a> attribute.</td> + * <td align="center">No</td> + * </tr> + * + * </tbody> + * </table> + * + * <p><b>Styleable Resources in your Metadata.</b> It's possible to provide alternate strings + * for your searchable application, in order to provide localization and/or to better visual + * presentation on different device configurations. Each searchable activity has a single XML + * metadata file, but any resource references can be replaced at runtime based on device + * configuration, language setting, and other system inputs. + * + * <p>A concrete example is the "hint" text you supply using the android:searchHint attribute. + * In portrait mode you'll have less screen space and may need to provide a shorter string, but + * in landscape mode you can provide a longer, more descriptive hint. To do this, you'll need to + * define two or more strings.xml files, in the following directories: + * <ul><li>.../res/values-land/strings.xml</li> + * <li>.../res/values-port/strings.xml</li> + * <li>.../res/values/strings.xml</li></ul> + * + * <p>For more complete documentation on this capability, see + * <a href="{@docRoot}guide/topics/resources/resources-i18n.html#AlternateResources">Resources and + * Internationalization: Alternate Resources</a>. + * + * <p><b>Metadata for non-searchable activities.</b> Activities which are part of a searchable + * application, but don't implement search itself, require a bit of "glue" in order to cause + * them to invoke search using your searchable activity as their primary context. If this is not + * provided, then searches from these activities will use the system default search context. + * + * <p>The simplest way to specify this is to add a <i>search reference</i> element to the + * application entry in the <a href="{@docRoot}guide/topics/manifest/manifest-intro.html">manifest</a> file. + * The value of this reference can be either of: + * <ul><li>The name of your searchable activity. + * It is typically prefixed by '.' to indicate that it's in the same package.</li> + * <li>A "*" indicates that the system may select a default searchable activity, in which + * case it will typically select web-based search.</li> + * </ul> + * + * <p>Here is a snippet showing the necessary addition to the manifest entry for your + * non-searchable activities. + * <pre class="prettyprint"> + * <application> + * <meta-data android:name="android.app.default_searchable" + * android:value=".MySearchActivity" /> + * + * <!-- followed by activities, providers, etc... --> + * </application></pre> + * + * <p>You can also specify android.app.default_searchable on a per-activity basis, by including + * the meta-data element (as shown above) in one or more activity sections. If found, these will + * override the reference in the application section. The only reason to configure your application + * this way would be if you wish to partition it into separate sections with different search + * behaviors; Otherwise this configuration is not recommended. + * + * <p><b>Additional Metadata for search suggestions.</b> If you have defined a content provider + * to generate search suggestions, you'll need to publish it to the system, and you'll need to + * provide a bit of additional XML metadata in order to configure communications with it. + * + * <p>First, in your <a href="{@docRoot}guide/topics/manifest/manifest-intro.html">manifest</a>, you'll add the + * following lines. + * <pre class="prettyprint"> + * <!-- Content provider for search suggestions --> + * <provider android:name="YourSuggestionProviderClass" + * android:authorities="your.suggestion.authority" /></pre> + * + * <p>Next, you'll add a few lines to your XML metadata file, as shown: + * <pre class="prettyprint"> + * <!-- Required attribute for any suggestions provider --> + * android:searchSuggestAuthority="your.suggestion.authority" + * + * <!-- Optional attribute for configuring queries --> + * android:searchSuggestSelection="field =?" + * + * <!-- Optional attributes for configuring intent construction --> + * android:searchSuggestIntentAction="intent action string" + * android:searchSuggestIntentData="intent data Uri" /></pre> + * + * <p>Elements of search metadata that support suggestions: + * <table border="2" width="85%" align="center" frame="hsides" rules="rows"> + * + * <thead> + * <tr><th>Attribute</th> <th>Description</th> <th>Required?</th></tr> + * </thead> + * + * <tbody> + * <tr><th>android:searchSuggestAuthority</th> + * <td>This value must match the authority string provided in the <i>provider</i> section + * of your <a href="{@docRoot}guide/topics/manifest/manifest-intro.html">manifest</a>.</td> + * <td align="center">Yes</td> + * </tr> + * + * <tr><th>android:searchSuggestPath</th> + * <td>If provided, this will be inserted in the suggestions query Uri, after the authority + * you have provide but before the standard suggestions path. This is only required if + * you have a single content provider issuing different types of suggestions (e.g. for + * different data types) and you need a way to disambiguate the suggestions queries + * when they are received.</td> + * <td align="center">No</td> + * </tr> + * + * <tr><th>android:searchSuggestSelection</th> + * <td>If provided, this value will be passed into your query function as the + * <i>selection</i> parameter. Typically this will be a WHERE clause for your database, + * and will contain a single question mark, which represents the actual query string + * that has been typed by the user. However, you can also use any non-null value + * to simply trigger the delivery of the query text (via selection arguments), and then + * use the query text in any way appropriate for your provider (ignoring the actual + * text of the selection parameter.)</td> + * <td align="center">No</td> + * </tr> + * + * <tr><th>android:searchSuggestIntentAction</th> + * <td>If provided, and not overridden by the selected suggestion, this value will be + * placed in the action field of the {@link android.content.Intent Intent} when the + * user clicks a suggestion.</td> + * <td align="center">No</td> + * + * <tr><th>android:searchSuggestIntentData</th> + * <td>If provided, and not overridden by the selected suggestion, this value will be + * placed in the data field of the {@link android.content.Intent Intent} when the user + * clicks a suggestion.</td> + * <td align="center">No</td> + * </tr> + * + * </tbody> + * </table> + * + * <p><b>Additional Metadata for search action keys.</b> For each action key that you would like to + * define, you'll need to add an additional element defining that key, and using the attributes + * discussed in <a href="#ActionKeys">Action Keys</a>. A simple example is shown here: + * + * <pre class="prettyprint"><actionkey + * android:keycode="KEYCODE_CALL" + * android:queryActionMsg="call" + * android:suggestActionMsg="call" + * android:suggestActionMsgColumn="call_column" /></pre> + * + * <p>Elements of search metadata that support search action keys. Note that although each of the + * action message elements are marked as <i>optional</i>, at least one must be present for the + * action key to have any effect. + * + * <table border="2" width="85%" align="center" frame="hsides" rules="rows"> + * + * <thead> + * <tr><th>Attribute</th> <th>Description</th> <th>Required?</th></tr> + * </thead> + * + * <tbody> + * <tr><th>android:keycode</th> + * <td>This attribute denotes the action key you wish to respond to. Note that not + * all action keys are actually supported using this mechanism, as many of them are + * used for typing, navigation, or system functions. This will be added to the + * {@link android.content.Intent#ACTION_SEARCH ACTION_SEARCH} intent that is passed to + * your searchable activity. To examine the key code, use + * {@link android.content.Intent#getIntExtra getIntExtra(SearchManager.ACTION_KEY)}. + * <p>Note, in addition to the keycode, you must also provide one or more of the action + * specifier attributes.</td> + * <td align="center">Yes</td> + * </tr> + * + * <tr><th>android:queryActionMsg</th> + * <td>If you wish to handle an action key during normal search query entry, you + * must define an action string here. This will be added to the + * {@link android.content.Intent#ACTION_SEARCH ACTION_SEARCH} intent that is passed to your + * searchable activity. To examine the string, use + * {@link android.content.Intent#getStringExtra + * getStringExtra(SearchManager.ACTION_MSG)}.</td> + * <td align="center">No</td> + * </tr> + * + * <tr><th>android:suggestActionMsg</th> + * <td>If you wish to handle an action key while a suggestion is being displayed <i>and + * selected</i>, there are two ways to handle this. If <i>all</i> of your suggestions + * can handle the action key, you can simply define the action message using this + * attribute. This will be added to the + * {@link android.content.Intent#ACTION_SEARCH ACTION_SEARCH} intent that is passed to + * your searchable activity. To examine the string, use + * {@link android.content.Intent#getStringExtra + * getStringExtra(SearchManager.ACTION_MSG)}.</td> + * <td align="center">No</td> + * </tr> + * + * <tr><th>android:suggestActionMsgColumn</th> + * <td>If you wish to handle an action key while a suggestion is being displayed <i>and + * selected</i>, but you do not wish to enable this action key for every suggestion, + * then you can use this attribute to control it on a suggestion-by-suggestion basis. + * First, you must define a column (and name it here) where your suggestions will + * include the action string. Then, in your content provider, you must provide this + * column, and when desired, provide data in this column. + * The search manager will look at your suggestion cursor, using the string + * provided here in order to select a column, and will use that to select a string from + * the cursor. That string will be added to the + * {@link android.content.Intent#ACTION_SEARCH ACTION_SEARCH} intent that is passed to + * your searchable activity. To examine the string, use + * {@link android.content.Intent#getStringExtra + * getStringExtra(SearchManager.ACTION_MSG)}. <i>If the data does not exist for the + * selection suggestion, the action key will be ignored.</i></td> + * <td align="center">No</td> + * </tr> + * + * </tbody> + * </table> + * + * <a name="PassingSearchContext"></a> + * <h3>Passing Search Context</h3> + * + * <p>In order to improve search experience, an application may wish to specify + * additional data along with the search, such as local history or context. For + * example, a maps search would be improved by including the current location. + * In order to simplify the structure of your activities, this can be done using + * the search manager. + * + * <p>Any data can be provided at the time the search is launched, as long as it + * can be stored in a {@link android.os.Bundle Bundle} object. + * + * <p>To pass application data into the Search Manager, you'll need to override + * {@link android.app.Activity#onSearchRequested onSearchRequested} as follows: + * + * <pre class="prettyprint"> + * @Override + * public boolean onSearchRequested() { + * Bundle appData = new Bundle(); + * appData.put...(); + * appData.put...(); + * startSearch(null, false, appData); + * return true; + * }</pre> + * + * <p>To receive application data from the Search Manager, you'll extract it from + * the {@link android.content.Intent#ACTION_SEARCH ACTION_SEARCH} + * {@link android.content.Intent Intent} as follows: + * + * <pre class="prettyprint"> + * final Bundle appData = queryIntent.getBundleExtra(SearchManager.APP_DATA); + * if (appData != null) { + * appData.get...(); + * appData.get...(); + * }</pre> + * + * <a name="ProtectingUserPrivacy"></a> + * <h3>Protecting User Privacy</h3> + * + * <p>Many users consider their activities on the phone, including searches, to be private + * information. Applications that implement search should take steps to protect users' privacy + * wherever possible. This section covers two areas of concern, but you should consider your search + * design carefully and take any additional steps necessary. + * + * <p><b>Don't send personal information to servers, and if you do, don't log it.</b> + * "Personal information" is information that can personally identify your users, such as name, + * email address or billing information, or other data which can be reasonably linked to such + * information. If your application implements search with the assistance of a server, try to + * avoid sending personal information with your searches. For example, if you are searching for + * businesses near a zip code, you don't need to send the user ID as well - just send the zip code + * to the server. If you do need to send personal information, you should take steps to avoid + * logging it. If you must log it, you should protect that data very carefully, and erase it as + * soon as possible. + * + * <p><b>Provide the user with a way to clear their search history.</b> The Search Manager helps + * your application provide context-specific suggestions. Sometimes these suggestions are based + * on previous searches, or other actions taken by the user in an earlier session. A user may not + * wish for previous searches to be revealed to other users, for instance if they share their phone + * with a friend. If your application provides suggestions that can reveal previous activities, + * you should implement a "Clear History" menu, preference, or button. If you are using + * {@link android.provider.SearchRecentSuggestions}, you can simply call its + * {@link android.provider.SearchRecentSuggestions#clearHistory() clearHistory()} method from + * your "Clear History" UI. If you are implementing your own form of recent suggestions, you'll + * need to provide a similar a "clear history" API in your provider, and call it from your + * "Clear History" UI. + */ +public class SearchManager + implements DialogInterface.OnDismissListener, DialogInterface.OnCancelListener +{ + /** + * This is a shortcut definition for the default menu key to use for invoking search. + * + * See Menu.Item.setAlphabeticShortcut() for more information. + */ + public final static char MENU_KEY = 's'; + + /** + * This is a shortcut definition for the default menu key to use for invoking search. + * + * See Menu.Item.setAlphabeticShortcut() for more information. + */ + public final static int MENU_KEYCODE = KeyEvent.KEYCODE_S; + + /** + * Intent extra data key: Use this key with + * {@link android.content.Intent#getStringExtra + * content.Intent.getStringExtra()} + * to obtain the query string from Intent.ACTION_SEARCH. + */ + public final static String QUERY = "query"; + + /** + * Intent extra data key: Use this key with Intent.ACTION_SEARCH and + * {@link android.content.Intent#getBundleExtra + * content.Intent.getBundleExtra()} + * to obtain any additional app-specific data that was inserted by the + * activity that launched the search. + */ + public final static String APP_DATA = "app_data"; + + /** + * Intent app_data bundle key: Use this key with the bundle from + * {@link android.content.Intent#getBundleExtra + * content.Intent.getBundleExtra(APP_DATA)} to obtain the source identifier + * set by the activity that launched the search. + * + * @hide + */ + public final static String SOURCE = "source"; + + /** + * Intent extra data key: Use this key with Intent.ACTION_SEARCH and + * {@link android.content.Intent#getIntExtra content.Intent.getIntExtra()} + * to obtain the keycode that the user used to trigger this query. It will be zero if the + * user simply pressed the "GO" button on the search UI. This is primarily used in conjunction + * with the keycode attribute in the actionkey element of your searchable.xml configuration + * file. + */ + public final static String ACTION_KEY = "action_key"; + + /** + * Intent extra data key: Use this key with Intent.ACTION_SEARCH and + * {@link android.content.Intent#getStringExtra content.Intent.getStringExtra()} + * to obtain the action message that was defined for a particular search action key and/or + * suggestion. It will be null if the search was launched by typing "enter", touched the the + * "GO" button, or other means not involving any action key. + */ + public final static String ACTION_MSG = "action_msg"; + + /** + * Uri path for queried suggestions data. This is the path that the search manager + * will use when querying your content provider for suggestions data based on user input + * (e.g. looking for partial matches). + * Typically you'll use this with a URI matcher. + */ + public final static String SUGGEST_URI_PATH_QUERY = "search_suggest_query"; + + /** + * MIME type for suggestions data. You'll use this in your suggestions content provider + * in the getType() function. + */ + public final static String SUGGEST_MIME_TYPE = + "vnd.android.cursor.dir/vnd.android.search.suggest"; + + /** + * Column name for suggestions cursor. <i>Unused - can be null or column can be omitted.</i> + */ + public final static String SUGGEST_COLUMN_FORMAT = "suggest_format"; + /** + * Column name for suggestions cursor. <i>Required.</i> This is the primary line of text that + * will be presented to the user as the suggestion. + */ + public final static String SUGGEST_COLUMN_TEXT_1 = "suggest_text_1"; + /** + * Column name for suggestions cursor. <i>Optional.</i> If your cursor includes this column, + * then all suggestions will be provided in a two-line format. The second line of text is in + * a much smaller appearance. + */ + public final static String SUGGEST_COLUMN_TEXT_2 = "suggest_text_2"; + /** + * Column name for suggestions cursor. <i>Optional.</i> If your cursor includes this column, + * then all suggestions will be provided in format that includes space for two small icons, + * one at the left and one at the right of each suggestion. The data in the column must + * be a a resource ID for the icon you wish to have displayed. If you include this column, + * you must also include {@link #SUGGEST_COLUMN_ICON_2}. + */ + public final static String SUGGEST_COLUMN_ICON_1 = "suggest_icon_1"; + /** + * Column name for suggestions cursor. <i>Optional.</i> If your cursor includes this column, + * then all suggestions will be provided in format that includes space for two small icons, + * one at the left and one at the right of each suggestion. The data in the column must + * be a a resource ID for the icon you wish to have displayed. If you include this column, + * you must also include {@link #SUGGEST_COLUMN_ICON_1}. + */ + public final static String SUGGEST_COLUMN_ICON_2 = "suggest_icon_2"; + /** + * Column name for suggestions cursor. <i>Optional.</i> If this column exists <i>and</i> + * this element exists at the given row, this is the action that will be used when + * forming the suggestion's intent. If the element is not provided, the action will be taken + * from the android:searchSuggestIntentAction field in your XML metadata. <i>At least one of + * these must be present for the suggestion to generate an intent.</i> Note: If your action is + * the same for all suggestions, it is more efficient to specify it using XML metadata and omit + * it from the cursor. + */ + public final static String SUGGEST_COLUMN_INTENT_ACTION = "suggest_intent_action"; + /** + * Column name for suggestions cursor. <i>Optional.</i> If this column exists <i>and</i> + * this element exists at the given row, this is the data that will be used when + * forming the suggestion's intent. If the element is not provided, the data will be taken + * from the android:searchSuggestIntentData field in your XML metadata. If neither source + * is provided, the Intent's data field will be null. Note: If your data is + * the same for all suggestions, or can be described using a constant part and a specific ID, + * it is more efficient to specify it using XML metadata and omit it from the cursor. + */ + public final static String SUGGEST_COLUMN_INTENT_DATA = "suggest_intent_data"; + /** + * Column name for suggestions cursor. <i>Optional.</i> If this column exists <i>and</i> + * this element exists at the given row, then "/" and this value will be appended to the data + * field in the Intent. This should only be used if the data field has already been set to an + * appropriate base string. + */ + public final static String SUGGEST_COLUMN_INTENT_DATA_ID = "suggest_intent_data_id"; + /** + * Column name for suggestions cursor. <i>Required if action is + * {@link android.content.Intent#ACTION_SEARCH ACTION_SEARCH}, optional otherwise.</i> If this + * column exists <i>and</i> this element exists at the given row, this is the data that will be + * used when forming the suggestion's query. + */ + public final static String SUGGEST_COLUMN_QUERY = "suggest_intent_query"; + + + private final Context mContext; + private final Handler mHandler; + + private SearchDialog mSearchDialog; + + private OnDismissListener mDismissListener = null; + private OnCancelListener mCancelListener = null; + + /*package*/ SearchManager(Context context, Handler handler) { + mContext = context; + mHandler = handler; + } + private static ISearchManager mService; + + static { + mService = ISearchManager.Stub.asInterface( + ServiceManager.getService(Context.SEARCH_SERVICE)); + } + + /** + * Launch search UI. + * + * <p>The search manager will open a search widget in an overlapping + * window, and the underlying activity may be obscured. The search + * entry state will remain in effect until one of the following events: + * <ul> + * <li>The user completes the search. In most cases this will launch + * a search intent.</li> + * <li>The user uses the back, home, or other keys to exit the search.</li> + * <li>The application calls the {@link #stopSearch} + * method, which will hide the search window and return focus to the + * activity from which it was launched.</li> + * + * <p>Most applications will <i>not</i> use this interface to invoke search. + * The primary method for invoking search is to call + * {@link android.app.Activity#onSearchRequested Activity.onSearchRequested()} or + * {@link android.app.Activity#startSearch Activity.startSearch()}. + * + * @param initialQuery A search string can be pre-entered here, but this + * is typically null or empty. + * @param selectInitialQuery If true, the intial query will be preselected, which means that + * any further typing will replace it. This is useful for cases where an entire pre-formed + * query is being inserted. If false, the selection point will be placed at the end of the + * inserted query. This is useful when the inserted query is text that the user entered, + * and the user would expect to be able to keep typing. <i>This parameter is only meaningful + * if initialQuery is a non-empty string.</i> + * @param launchActivity The ComponentName of the activity that has launched this search. + * @param appSearchData An application can insert application-specific + * context here, in order to improve quality or specificity of its own + * searches. This data will be returned with SEARCH intent(s). Null if + * no extra data is required. + * @param globalSearch If false, this will only launch the search that has been specifically + * defined by the application (which is usually defined as a local search). If no default + * search is defined in the current application or activity, no search will be launched. + * If true, this will always launch a platform-global (e.g. web-based) search instead. + * + * @see android.app.Activity#onSearchRequested + * @see #stopSearch + */ + public void startSearch(String initialQuery, + boolean selectInitialQuery, + ComponentName launchActivity, + Bundle appSearchData, + boolean globalSearch) { + + if (mSearchDialog == null) { + mSearchDialog = new SearchDialog(mContext); + } + + // activate the search manager and start it up! + mSearchDialog.show(initialQuery, selectInitialQuery, launchActivity, appSearchData, + globalSearch); + + mSearchDialog.setOnCancelListener(this); + mSearchDialog.setOnDismissListener(this); + } + + /** + * Terminate search UI. + * + * <p>Typically the user will terminate the search UI by launching a + * search or by canceling. This function allows the underlying application + * or activity to cancel the search prematurely (for any reason). + * + * <p>This function can be safely called at any time (even if no search is active.) + * + * @see #startSearch + */ + public void stopSearch() { + if (mSearchDialog != null) { + mSearchDialog.cancel(); + } + } + + /** + * Determine if the Search UI is currently displayed. + * + * This is provided primarily for application test purposes. + * + * @return Returns true if the search UI is currently displayed. + * + * @hide + */ + public boolean isVisible() { + if (mSearchDialog != null) { + return mSearchDialog.isShowing(); + } + return false; + } + + /** + * See {@link #setOnDismissListener} for configuring your activity to monitor search UI state. + */ + public interface OnDismissListener { + /** + * This method will be called when the search UI is dismissed. To make use if it, you must + * implement this method in your activity, and call {@link #setOnDismissListener} to + * register it. + */ + public void onDismiss(); + } + + /** + * See {@link #setOnCancelListener} for configuring your activity to monitor search UI state. + */ + public interface OnCancelListener { + /** + * This method will be called when the search UI is canceled. To make use if it, you must + * implement this method in your activity, and call {@link #setOnCancelListener} to + * register it. + */ + public void onCancel(); + } + + /** + * Set or clear the callback that will be invoked whenever the search UI is dismissed. + * + * @param listener The {@link OnDismissListener} to use, or null. + */ + public void setOnDismissListener(final OnDismissListener listener) { + mDismissListener = listener; + } + + /** + * The callback from the search dialog when dismissed + * @hide + */ + public void onDismiss(DialogInterface dialog) { + if (dialog == mSearchDialog) { + if (mDismissListener != null) { + mDismissListener.onDismiss(); + } + } + } + + /** + * Set or clear the callback that will be invoked whenever the search UI is canceled. + * + * @param listener The {@link OnCancelListener} to use, or null. + */ + public void setOnCancelListener(final OnCancelListener listener) { + mCancelListener = listener; + } + + + /** + * The callback from the search dialog when canceled + * @hide + */ + public void onCancel(DialogInterface dialog) { + if (dialog == mSearchDialog) { + if (mCancelListener != null) { + mCancelListener.onCancel(); + } + } + } + + /** + * Save instance state so we can recreate after a rotation. + * + * @hide + */ + void saveSearchDialog(Bundle outState, String key) { + if (mSearchDialog != null && mSearchDialog.isShowing()) { + Bundle searchDialogState = mSearchDialog.onSaveInstanceState(); + outState.putBundle(key, searchDialogState); + } + } + + /** + * Restore instance state after a rotation. + * + * @hide + */ + void restoreSearchDialog(Bundle inState, String key) { + Bundle searchDialogState = inState.getBundle(key); + if (searchDialogState != null) { + if (mSearchDialog == null) { + mSearchDialog = new SearchDialog(mContext); + } + mSearchDialog.onRestoreInstanceState(searchDialogState); + } + } + + /** + * Hook for updating layout on a rotation + * + * @hide + */ + void onConfigurationChanged(Configuration newConfig) { + if (mSearchDialog != null && mSearchDialog.isShowing()) { + mSearchDialog.onConfigurationChanged(newConfig); + } + } + +} diff --git a/core/java/android/app/Service.java b/core/java/android/app/Service.java new file mode 100644 index 0000000..a6a436f --- /dev/null +++ b/core/java/android/app/Service.java @@ -0,0 +1,378 @@ +/* + * Copyright (C) 2006 The Android Open Source Project + * + * 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 android.app; + +import android.content.ComponentCallbacks; +import android.content.ComponentName; +import android.content.Intent; +import android.content.ContextWrapper; +import android.content.Context; +import android.content.res.Configuration; +import android.os.RemoteException; +import android.os.IBinder; + +import java.io.FileDescriptor; +import java.io.PrintWriter; + +/** + * A Service is an application component that runs in the background, not + * interacting with the user, for an indefinite period of time. Each service + * class must have a corresponding + * {@link android.R.styleable#AndroidManifestService <service>} + * declaration in its package's <code>AndroidManifest.xml</code>. Services + * can be started with + * {@link android.content.Context#startService Context.startService()} and + * {@link android.content.Context#bindService Context.bindService()}. + * + * <p>Note that services, like other application objects, run in the main + * thread of their hosting process. This means that, if your service is going + * to do any CPU intensive (such as MP3 playback) or blocking (such as + * networking) operations, it should spawn its own thread in which to do that + * work. More information on this can be found in + * <a href="{@docRoot}guide/topics/fundamentals.html#procthread">Application Fundamentals: + * Processes and Threads</a>.</p> + * + * <p>The Service class is an important part of an + * <a href="{@docRoot}guide/topics/fundamentals.html#lcycles">application's overall lifecycle</a>.</p> + * + * <p>Topics covered here: + * <ol> + * <li><a href="#ServiceLifecycle">Service Lifecycle</a> + * <li><a href="#Permissions">Permissions</a> + * <li><a href="#ProcessLifecycle">Process Lifecycle</a> + * </ol> + * + * <a name="ServiceLifecycle"></a> + * <h3>Service Lifecycle</h3> + * + * <p>There are two reasons that a service can be run by the system. If someone + * calls {@link android.content.Context#startService Context.startService()} then the system will + * retrieve the service (creating it and calling its {@link #onCreate} method + * if needed) and then call its {@link #onStart} method with the + * arguments supplied by the client. The service will at this point continue + * running until {@link android.content.Context#stopService Context.stopService()} or + * {@link #stopSelf()} is called. Note that multiple calls to + * Context.startService() do not nest (though they do result in multiple corresponding + * calls to onStart()), so no matter how many times it is started a service + * will be stopped once Context.stopService() or stopSelf() is called. + * + * <p>Clients can also use {@link android.content.Context#bindService Context.bindService()} to + * obtain a persistent connection to a service. This likewise creates the + * service if it is not already running (calling {@link #onCreate} while + * doing so), but does not call onStart(). The client will receive the + * {@link android.os.IBinder} object that the service returns from its + * {@link #onBind} method, allowing the client to then make calls back + * to the service. The service will remain running as long as the connection + * is established (whether or not the client retains a reference on the + * service's IBinder). Usually the IBinder returned is for a complex + * interface that has been <a href="{@docRoot}guide/developing/tools/aidl.html">written + * in aidl</a>. + * + * <p>A service can be both started and have connections bound to it. In such + * a case, the system will keep the service running as long as either it is + * started <em>or</em> there are one or more connections to it with the + * {@link android.content.Context#BIND_AUTO_CREATE Context.BIND_AUTO_CREATE} + * flag. Once neither + * of these situations hold, the service's {@link #onDestroy} method is called + * and the service is effectively terminated. All cleanup (stopping threads, + * unregistering receivers) should be complete upon returning from onDestroy(). + * + * <a name="Permissions"></a> + * <h3>Permissions</h3> + * + * <p>Global access to a service can be enforced when it is declared in its + * manifest's {@link android.R.styleable#AndroidManifestService <service>} + * tag. By doing so, other applications will need to declare a corresponding + * {@link android.R.styleable#AndroidManifestUsesPermission <uses-permission>} + * element in their own manifest to be able to start, stop, or bind to + * the service. + * + * <p>In addition, a service can protect individual IPC calls into it with + * permissions, by calling the + * {@link #checkCallingPermission} + * method before executing the implementation of that call. + * + * <p>See the <a href="{@docRoot}guide/topics/security/security.html">Security and Permissions</a> + * document for more information on permissions and security in general. + * + * <a name="ProcessLifecycle"></a> + * <h3>Process Lifecycle</h3> + * + * <p>The Android system will attempt to keep the process hosting a service + * around as long as the service has been started or has clients bound to it. + * When running low on memory and needing to kill existing processes, the + * priority of a process hosting the service will be the higher of the + * following possibilities: + * + * <ul> + * <li><p>If the service is currently executing code in its + * {@link #onCreate onCreate()}, {@link #onStart onStart()}, + * or {@link #onDestroy onDestroy()} methods, then the hosting process will + * be a foreground process to ensure this code can execute without + * being killed. + * <li><p>If the service has been started, then its hosting process is considered + * to be less important than any processes that are currently visible to the + * user on-screen, but more important than any process not visible. Because + * only a few processes are generally visible to the user, this means that + * the service should not be killed except in extreme low memory conditions. + * <li><p>If there are clients bound to the service, then the service's hosting + * process is never less important than the most important client. That is, + * if one of its clients is visible to the user, then the service itself is + * considered to be visible. + * </ul> + * + * <p>Note this means that most of the time your service is running, it may + * be killed by the system if it is under heavy memory pressure. If this + * happens, the system will later try to restart the service. An important + * consequence of this is that if you implement {@link #onStart onStart()} + * to schedule work to be done asynchronously or in another thread, then you + * may want to write information about that work into persistent storage + * during the onStart() call so that it does not get lost if the service later + * gets killed. + * + * <p>Other application components running in the same process as the service + * (such as an {@link android.app.Activity}) can, of course, increase the + * importance of the overall + * process beyond just the importance of the service itself. + */ +public abstract class Service extends ContextWrapper implements ComponentCallbacks { + private static final String TAG = "Service"; + + public Service() { + super(null); + } + + /** Return the application that owns this service. */ + public final Application getApplication() { + return mApplication; + } + + /** + * Called by the system when the service is first created. Do not call this method directly. + */ + public void onCreate() { + } + + /** + * Called by the system every time a client explicitly starts the service by calling + * {@link android.content.Context#startService}, providing the arguments it supplied and a + * unique integer token representing the start request. Do not call this method directly. + * + * @param intent The Intent supplied to {@link android.content.Context#startService}, + * as given. + * @param startId A unique integer representing this specific request to + * start. Use with {@link #stopSelfResult(int)}. + * + * @see #stopSelfResult(int) + */ + public void onStart(Intent intent, int startId) { + } + + /** + * Called by the system to notify a Service that it is no longer used and is being removed. The + * service should clean up an resources it holds (threads, registered + * receivers, etc) at this point. Upon return, there will be no more calls + * in to this Service object and it is effectively dead. Do not call this method directly. + */ + public void onDestroy() { + } + + public void onConfigurationChanged(Configuration newConfig) { + } + + public void onLowMemory() { + } + + /** + * Return the communication channel to the service. May return null if + * clients can not bind to the service. The returned + * {@link android.os.IBinder} is usually for a complex interface + * that has been <a href="{@docRoot}guide/developing/tools/aidl.html">described using + * aidl</a>. + * + * <p><em>Note that unlike other application components, calls on to the + * IBinder interface returned here may not happen on the main thread + * of the process</em>. More information about this can be found + * in <a href="{@docRoot}guide/topics/fundamentals.html#procthread">Application Fundamentals: + * Processes and Threads</a>.</p> + * + * @param intent The Intent that was used to bind to this service, + * as given to {@link android.content.Context#bindService + * Context.bindService}. Note that any extras that were included with + * the Intent at that point will <em>not</em> be seen here. + * + * @return Return an IBinder through which clients can call on to the + * service. + */ + public abstract IBinder onBind(Intent intent); + + /** + * Called when all clients have disconnected from a particular interface + * published by the service. The default implementation does nothing and + * returns false. + * + * @param intent The Intent that was used to bind to this service, + * as given to {@link android.content.Context#bindService + * Context.bindService}. Note that any extras that were included with + * the Intent at that point will <em>not</em> be seen here. + * + * @return Return true if you would like to have the service's + * {@link #onRebind} method later called when new clients bind to it. + */ + public boolean onUnbind(Intent intent) { + return false; + } + + /** + * Called when new clients have connected to the service, after it had + * previously been notified that all had disconnected in its + * {@link #onUnbind}. This will only be called if the implementation + * of {@link #onUnbind} was overridden to return true. + * + * @param intent The Intent that was used to bind to this service, + * as given to {@link android.content.Context#bindService + * Context.bindService}. Note that any extras that were included with + * the Intent at that point will <em>not</em> be seen here. + */ + public void onRebind(Intent intent) { + } + + /** + * Stop the service, if it was previously started. This is the same as + * calling {@link android.content.Context#stopService} for this particular service. + * + * @see #stopSelfResult(int) + */ + public final void stopSelf() { + stopSelf(-1); + } + + /** + * Old version of {@link #stopSelfResult} that doesn't return a result. + * + * @see #stopSelfResult + */ + public final void stopSelf(int startId) { + if (mActivityManager == null) { + return; + } + try { + mActivityManager.stopServiceToken( + new ComponentName(this, mClassName), mToken, startId); + } catch (RemoteException ex) { + } + } + + /** + * Stop the service, if the most recent time it was started was + * <var>startId</var>. This is the same as calling {@link + * android.content.Context#stopService} for this particular service but allows you to + * safely avoid stopping if there is a start request from a client that you + * haven't yet see in {@link #onStart}. + * + * @param startId The most recent start identifier received in {@link + * #onStart}. + * @return Returns true if the startId matches the last start request + * and the service will be stopped, else false. + * + * @see #stopSelf() + */ + public final boolean stopSelfResult(int startId) { + if (mActivityManager == null) { + return false; + } + try { + return mActivityManager.stopServiceToken( + new ComponentName(this, mClassName), mToken, startId); + } catch (RemoteException ex) { + } + return false; + } + + /** + * Control whether this service is considered to be a foreground service. + * By default services are background, meaning that if the system needs to + * kill them to reclaim more memory (such as to display a large page in a + * web browser), they can be killed without too much harm. You can set this + * flag if killing your service would be disruptive to the user: such as + * if your service is performing background music playback, so the user + * would notice if their music stopped playing. + * + * @param isForeground Determines whether this service is considered to + * be foreground (true) or background (false). + */ + public final void setForeground(boolean isForeground) { + if (mActivityManager == null) { + return; + } + try { + mActivityManager.setServiceForeground( + new ComponentName(this, mClassName), mToken, isForeground); + } catch (RemoteException ex) { + } + } + + /** + * Print the Service's state into the given stream. This gets invoked if + * you run "adb shell dumpsys activity service <yourservicename>". + * This is distinct from "dumpsys <servicename>", which only works for + * named system services and which invokes the {@link IBinder#dump} method + * on the {@link IBinder} interface registered with ServiceManager. + * + * @param fd The raw file descriptor that the dump is being sent to. + * @param writer The PrintWriter to which you should dump your state. This will be + * closed for you after you return. + * @param args additional arguments to the dump request. + */ + protected void dump(FileDescriptor fd, PrintWriter writer, String[] args) { + writer.println("nothing to dump"); + } + + @Override + protected void finalize() throws Throwable { + super.finalize(); + //Log.i("Service", "Finalizing Service: " + this); + } + + // ------------------ Internal API ------------------ + + /** + * @hide + */ + public final void attach( + Context context, + ActivityThread thread, String className, IBinder token, + Application application, Object activityManager) { + attachBaseContext(context); + mThread = thread; // NOTE: unused - remove? + mClassName = className; + mToken = token; + mApplication = application; + mActivityManager = (IActivityManager)activityManager; + } + + final String getClassName() { + return mClassName; + } + + // set by the thread after the constructor and before onCreate(Bundle icicle) is called. + private ActivityThread mThread = null; + private String mClassName = null; + private IBinder mToken = null; + private Application mApplication = null; + private IActivityManager mActivityManager = null; +} diff --git a/core/java/android/app/StatusBarManager.java b/core/java/android/app/StatusBarManager.java new file mode 100644 index 0000000..51d7393 --- /dev/null +++ b/core/java/android/app/StatusBarManager.java @@ -0,0 +1,139 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * 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 android.app; + +import android.content.Context; +import android.os.Binder; +import android.os.RemoteException; +import android.os.IBinder; +import android.os.ServiceManager; + +/** + * Allows an app to control the status bar. + * + * @hide + */ +public class StatusBarManager { + /** + * Flag for {@link #disable} to make the status bar not expandable. Unless you also + * set {@link #DISABLE_NOTIFICATIONS}, new notifications will continue to show. + */ + public static final int DISABLE_EXPAND = 0x00000001; + + /** + * Flag for {@link #disable} to hide notification icons and ticker text. + */ + public static final int DISABLE_NOTIFICATION_ICONS = 0x00000002; + + /** + * Flag for {@link #disable} to disable incoming notification alerts. This will not block + * icons, but it will block sound, vibrating and other visual or aural notifications. + */ + public static final int DISABLE_NOTIFICATION_ALERTS = 0x00000004; + + /** + * Re-enable all of the status bar features that you've disabled. + */ + public static final int DISABLE_NONE = 0x00000000; + + private Context mContext; + private IStatusBar mService; + private IBinder mToken = new Binder(); + + StatusBarManager(Context context) { + mContext = context; + mService = IStatusBar.Stub.asInterface( + ServiceManager.getService(Context.STATUS_BAR_SERVICE)); + } + + /** + * Disable some features in the status bar. Pass the bitwise-or of the DISABLE_* flags. + * To re-enable everything, pass {@link #DISABLE_NONE}. + */ + public void disable(int what) { + try { + mService.disable(what, mToken, mContext.getPackageName()); + } catch (RemoteException ex) { + // system process is dead anyway. + throw new RuntimeException(ex); + } + } + + /** + * Expand the status bar. + */ + public void expand() { + try { + mService.activate(); + } catch (RemoteException ex) { + // system process is dead anyway. + throw new RuntimeException(ex); + } + } + + /** + * Collapse the status bar. + */ + public void collapse() { + try { + mService.deactivate(); + } catch (RemoteException ex) { + // system process is dead anyway. + throw new RuntimeException(ex); + } + } + + /** + * Toggle the status bar. + */ + public void toggle() { + try { + mService.toggle(); + } catch (RemoteException ex) { + // system process is dead anyway. + throw new RuntimeException(ex); + } + } + + public IBinder addIcon(String slot, int iconId, int iconLevel) { + try { + return mService.addIcon(slot, mContext.getPackageName(), iconId, iconLevel); + } catch (RemoteException ex) { + // system process is dead anyway. + throw new RuntimeException(ex); + } + } + + public void updateIcon(IBinder key, String slot, int iconId, int iconLevel) { + try { + mService.updateIcon(key, slot, mContext.getPackageName(), iconId, iconLevel); + } catch (RemoteException ex) { + // system process is dead anyway. + throw new RuntimeException(ex); + } + } + + public void removeIcon(IBinder key) { + try { + mService.removeIcon(key); + } catch (RemoteException ex) { + // system process is dead anyway. + throw new RuntimeException(ex); + } + } +} diff --git a/core/java/android/app/TabActivity.java b/core/java/android/app/TabActivity.java new file mode 100644 index 0000000..033fa0c --- /dev/null +++ b/core/java/android/app/TabActivity.java @@ -0,0 +1,148 @@ +/* + * Copyright (C) 2006 The Android Open Source Project + * + * 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 android.app; + +import android.os.Bundle; +import android.view.View; +import android.widget.TabHost; +import android.widget.TabWidget; +import android.widget.TextView; + +/** + * An activity that contains and runs multiple embedded activities or views. + */ +public class TabActivity extends ActivityGroup { + private TabHost mTabHost; + private String mDefaultTab = null; + private int mDefaultTabIndex = -1; + + public TabActivity() { + } + + /** + * Sets the default tab that is the first tab highlighted. + * + * @param tag the name of the default tab + */ + public void setDefaultTab(String tag) { + mDefaultTab = tag; + mDefaultTabIndex = -1; + } + + /** + * Sets the default tab that is the first tab highlighted. + * + * @param index the index of the default tab + */ + public void setDefaultTab(int index) { + mDefaultTab = null; + mDefaultTabIndex = index; + } + + @Override + protected void onRestoreInstanceState(Bundle state) { + super.onRestoreInstanceState(state); + ensureTabHost(); + String cur = state.getString("currentTab"); + if (cur != null) { + mTabHost.setCurrentTabByTag(cur); + } + if (mTabHost.getCurrentTab() < 0) { + if (mDefaultTab != null) { + mTabHost.setCurrentTabByTag(mDefaultTab); + } else if (mDefaultTabIndex >= 0) { + mTabHost.setCurrentTab(mDefaultTabIndex); + } + } + } + + @Override + protected void onPostCreate(Bundle icicle) { + super.onPostCreate(icicle); + + ensureTabHost(); + + if (mTabHost.getCurrentTab() == -1) { + mTabHost.setCurrentTab(0); + } + } + + @Override + protected void onSaveInstanceState(Bundle outState) { + super.onSaveInstanceState(outState); + String currentTabTag = mTabHost.getCurrentTabTag(); + if (currentTabTag != null) { + outState.putString("currentTab", currentTabTag); + } + } + + /** + * Updates the screen state (current list and other views) when the + * content changes. + * + *@see Activity#onContentChanged() + */ + @Override + public void onContentChanged() { + super.onContentChanged(); + mTabHost = (TabHost) findViewById(com.android.internal.R.id.tabhost); + + if (mTabHost == null) { + throw new RuntimeException( + "Your content must have a TabHost whose id attribute is " + + "'android.R.id.tabhost'"); + } + mTabHost.setup(getLocalActivityManager()); + } + + private void ensureTabHost() { + if (mTabHost == null) { + this.setContentView(com.android.internal.R.layout.tab_content); + } + } + + @Override + protected void + onChildTitleChanged(Activity childActivity, CharSequence title) { + // Dorky implementation until we can have multiple activities running. + if (getLocalActivityManager().getCurrentActivity() == childActivity) { + View tabView = mTabHost.getCurrentTabView(); + if (tabView != null && tabView instanceof TextView) { + ((TextView) tabView).setText(title); + } + } + } + + /** + * Returns the {@link TabHost} the activity is using to host its tabs. + * + * @return the {@link TabHost} the activity is using to host its tabs. + */ + public TabHost getTabHost() { + ensureTabHost(); + return mTabHost; + } + + /** + * Returns the {@link TabWidget} the activity is using to draw the actual tabs. + * + * @return the {@link TabWidget} the activity is using to draw the actual tabs. + */ + public TabWidget getTabWidget() { + return mTabHost.getTabWidget(); + } +} diff --git a/core/java/android/app/TimePickerDialog.java b/core/java/android/app/TimePickerDialog.java new file mode 100644 index 0000000..002b01f --- /dev/null +++ b/core/java/android/app/TimePickerDialog.java @@ -0,0 +1,162 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * 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 android.app; + +import android.content.Context; +import android.content.DialogInterface; +import android.content.DialogInterface.OnClickListener; +import android.os.Bundle; +import android.text.format.DateFormat; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.TimePicker; +import android.widget.TimePicker.OnTimeChangedListener; + +import com.android.internal.R; + +import java.util.Calendar; + +/** + * A dialog that prompts the user for the time of day using a {@link TimePicker}. + */ +public class TimePickerDialog extends AlertDialog implements OnClickListener, + OnTimeChangedListener { + + /** + * The callback interface used to indicate the user is done filling in + * the time (they clicked on the 'Set' button). + */ + public interface OnTimeSetListener { + + /** + * @param view The view associated with this listener. + * @param hourOfDay The hour that was set. + * @param minute The minute that was set. + */ + void onTimeSet(TimePicker view, int hourOfDay, int minute); + } + + private static final String HOUR = "hour"; + private static final String MINUTE = "minute"; + private static final String IS_24_HOUR = "is24hour"; + + private final TimePicker mTimePicker; + private final OnTimeSetListener mCallback; + private final Calendar mCalendar; + private final java.text.DateFormat mDateFormat; + + int mInitialHourOfDay; + int mInitialMinute; + boolean mIs24HourView; + + /** + * @param context Parent. + * @param callBack How parent is notified. + * @param hourOfDay The initial hour. + * @param minute The initial minute. + * @param is24HourView Whether this is a 24 hour view, or AM/PM. + */ + public TimePickerDialog(Context context, + OnTimeSetListener callBack, + int hourOfDay, int minute, boolean is24HourView) { + this(context, com.android.internal.R.style.Theme_Dialog_Alert, + callBack, hourOfDay, minute, is24HourView); + } + + /** + * @param context Parent. + * @param theme the theme to apply to this dialog + * @param callBack How parent is notified. + * @param hourOfDay The initial hour. + * @param minute The initial minute. + * @param is24HourView Whether this is a 24 hour view, or AM/PM. + */ + public TimePickerDialog(Context context, + int theme, + OnTimeSetListener callBack, + int hourOfDay, int minute, boolean is24HourView) { + super(context, theme); + mCallback = callBack; + mInitialHourOfDay = hourOfDay; + mInitialMinute = minute; + mIs24HourView = is24HourView; + + mDateFormat = DateFormat.getTimeFormat(context); + mCalendar = Calendar.getInstance(); + updateTitle(mInitialHourOfDay, mInitialMinute); + + setButton(context.getText(R.string.date_time_set), this); + setButton2(context.getText(R.string.cancel), (OnClickListener) null); + setIcon(R.drawable.ic_dialog_time); + + LayoutInflater inflater = + (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); + View view = inflater.inflate(R.layout.time_picker_dialog, null); + setView(view); + mTimePicker = (TimePicker) view.findViewById(R.id.timePicker); + + // initialize state + mTimePicker.setCurrentHour(mInitialHourOfDay); + mTimePicker.setCurrentMinute(mInitialMinute); + mTimePicker.setIs24HourView(mIs24HourView); + mTimePicker.setOnTimeChangedListener(this); + } + + public void onClick(DialogInterface dialog, int which) { + if (mCallback != null) { + mTimePicker.clearFocus(); + mCallback.onTimeSet(mTimePicker, mTimePicker.getCurrentHour(), + mTimePicker.getCurrentMinute()); + } + } + + public void onTimeChanged(TimePicker view, int hourOfDay, int minute) { + updateTitle(hourOfDay, minute); + } + + public void updateTime(int hourOfDay, int minutOfHour) { + mTimePicker.setCurrentHour(hourOfDay); + mTimePicker.setCurrentMinute(minutOfHour); + } + + private void updateTitle(int hour, int minute) { + mCalendar.set(Calendar.HOUR_OF_DAY, hour); + mCalendar.set(Calendar.MINUTE, minute); + setTitle(mDateFormat.format(mCalendar.getTime())); + } + + @Override + public Bundle onSaveInstanceState() { + Bundle state = super.onSaveInstanceState(); + state.putInt(HOUR, mTimePicker.getCurrentHour()); + state.putInt(MINUTE, mTimePicker.getCurrentMinute()); + state.putBoolean(IS_24_HOUR, mTimePicker.is24HourView()); + return state; + } + + @Override + public void onRestoreInstanceState(Bundle savedInstanceState) { + super.onRestoreInstanceState(savedInstanceState); + int hour = savedInstanceState.getInt(HOUR); + int minute = savedInstanceState.getInt(MINUTE); + mTimePicker.setCurrentHour(hour); + mTimePicker.setCurrentMinute(minute); + mTimePicker.setIs24HourView(savedInstanceState.getBoolean(IS_24_HOUR)); + mTimePicker.setOnTimeChangedListener(this); + updateTitle(hour, minute); + } +} diff --git a/core/java/android/app/package.html b/core/java/android/app/package.html new file mode 100644 index 0000000..048ee93 --- /dev/null +++ b/core/java/android/app/package.html @@ -0,0 +1,72 @@ +<html> +<head> +<script type="text/javascript" src="http://www.corp.google.com/style/prettify.js"></script> +<script src="http://www.corp.google.com/eng/techpubs/include/navbar.js" type="text/javascript"></script> +</head> + +<body> + +<p>High-level classes encapsulating the overall Android application model. +The central class is {@link android.app.Activity}, with other top-level +application components being defined by {@link android.app.Service} and, +from the {@link android.content} package, {@link android.content.BroadcastReceiver} +and {@link android.content.ContentProvider}. It also includes application +tools, such as dialogs and notifications.</p> + +<p>This package builds on top of the lower-level Android packages +{@link android.widget}, {@link android.view}, {@link android.content}, +{@link android.text}, {@link android.graphics}, {@link android.os}, and +{@link android.util}.</p> + +<p>An {@link android.app.Activity Activity} is a specific operation the +user can perform, generally corresponding +to one screen in the user interface. +It is the basic building block of an Android application. +Examples of activities are "view the +list of people," "view the details of a person," "edit information about +a person," "view an image," etc. Switching from one activity to another +generally implies adding a new entry on the navigation history; that is, +going "back" means moving to the previous activity you were doing.</p> + +<p>A set of related activities can be grouped together as a "task". Until +a new task is explicitly specified, all activites you start are considered +to be part of the current task. While the only way to navigate between +individual activities is by going "back" in the history stack, the group +of activities in a task can be moved in relation to other tasks: for example +to the front or the back of the history stack. This mechanism can be used +to present to the user a list of things they have been doing, moving +between them without disrupting previous work. +</p> + +<p>A complete "application" is a set of activities that allow the user to do a +cohesive group of operations -- such as working with contacts, working with a +calendar, messaging, etc. Though there can be a custom application object +associated with a set of activities, in many cases this is not needed -- +each activity provides a particular path into one of the various kinds of +functionality inside of the application, serving as its on self-contained +"mini application". +</p> + +<p>This approach allows an application to be broken into pieces, which +can be reused and replaced in a variety of ways. Consider, for example, +a "camera application." There are a number of things this application +must do, each of which is provided by a separate activity: take a picture +(creating a new image), browse through the existing images, display a +specific image, etc. If the "contacts application" then wants to let the +user associate an image with a person, it can simply launch the existing +"take a picture" or "select an image" activity that is part of the camera +application and attach the picture it gets back. +</p> + +<p>Note that there is no hard relationship between tasks the user sees and +applications the developer writes. A task can be composed of activities from +multiple applications (such as the contact application using an activity in +the camera application to get a picture for a person), and multiple active +tasks may be running for the same application (such as editing e-mail messages +to two different people). The way tasks are organized is purely a UI policy +decided by the system; for example, typically a new task is started when the +user goes to the application launcher and selects an application. +</p> + +</body> +</html> |