diff options
Diffstat (limited to 'core/java')
97 files changed, 4148 insertions, 521 deletions
diff --git a/core/java/android/accounts/AccountManagerService.java b/core/java/android/accounts/AccountManagerService.java index 1cd7aa7..1d9e0f1 100644 --- a/core/java/android/accounts/AccountManagerService.java +++ b/core/java/android/accounts/AccountManagerService.java @@ -1483,7 +1483,13 @@ public class AccountManagerService } private static String getDatabaseName() { - return DATABASE_NAME; + if(Environment.isEncryptedFilesystemEnabled()) { + // Hard-coded path in case of encrypted file system + return Environment.getSystemSecureDirectory().getPath() + File.separator + DATABASE_NAME; + } else { + // Regular path in case of non-encrypted file system + return DATABASE_NAME; + } } private class DatabaseHelper extends SQLiteOpenHelper { diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java index c9096cf..7f95bf5 100644 --- a/core/java/android/app/ActivityManager.java +++ b/core/java/android/app/ActivityManager.java @@ -727,11 +727,25 @@ public class ActivityManager { /** * Constant for {@link #importance}: this process is running something - * that is considered to be actively visible to the user. + * that is actively visible to the user, though not in the immediate + * foreground. */ public static final int IMPORTANCE_VISIBLE = 200; /** + * Constant for {@link #importance}: this process is running something + * that is considered to be actively perceptible to the user. An + * example would be an application performing background music playback. + */ + public static final int IMPORTANCE_PERCEPTIBLE = 130; + + /** + * Constant for {@link #importance}: this process is running a + * heavy-weight application and thus should not be killed. + */ + public static final int IMPORTANCE_HEAVY_WEIGHT = 170; + + /** * Constant for {@link #importance}: this process is contains services * that should remain running. */ diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java index f694285..b4c7edc 100644 --- a/core/java/android/app/ActivityManagerNative.java +++ b/core/java/android/app/ActivityManagerNative.java @@ -1062,6 +1062,15 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM return true; } + case HANDLE_APPLICATION_STRICT_MODE_VIOLATION_TRANSACTION: { + data.enforceInterface(IActivityManager.descriptor); + IBinder app = data.readStrongBinder(); + ApplicationErrorReport.CrashInfo ci = new ApplicationErrorReport.CrashInfo(data); + handleApplicationStrictModeViolation(app, ci); + reply.writeNoException(); + return true; + } + case SIGNAL_PERSISTENT_PROCESSES_TRANSACTION: { data.enforceInterface(IActivityManager.descriptor); int sig = data.readInt(); @@ -1251,6 +1260,13 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM reply.writeNoException(); return true; } + + case FINISH_HEAVY_WEIGHT_APP_TRANSACTION: { + data.enforceInterface(IActivityManager.descriptor); + finishHeavyWeightApp(); + reply.writeNoException(); + return true; + } } return super.onTransact(code, data, reply, flags); @@ -2516,6 +2532,7 @@ class ActivityManagerProxy implements IActivityManager reply.recycle(); data.recycle(); } + public boolean handleApplicationWtf(IBinder app, String tag, ApplicationErrorReport.CrashInfo crashInfo) throws RemoteException { @@ -2533,6 +2550,20 @@ class ActivityManagerProxy implements IActivityManager return res; } + public void handleApplicationStrictModeViolation(IBinder app, + ApplicationErrorReport.CrashInfo crashInfo) throws RemoteException + { + Parcel data = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + data.writeInterfaceToken(IActivityManager.descriptor); + data.writeStrongBinder(app); + crashInfo.writeToParcel(data, 0); + mRemote.transact(HANDLE_APPLICATION_STRICT_MODE_VIOLATION_TRANSACTION, data, reply, 0); + reply.readException(); + reply.recycle(); + data.recycle(); + } + public void signalPersistentProcesses(int sig) throws RemoteException { Parcel data = Parcel.obtain(); Parcel reply = Parcel.obtain(); @@ -2758,5 +2789,15 @@ class ActivityManagerProxy implements IActivityManager return res; } + public void finishHeavyWeightApp() throws RemoteException { + Parcel data = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + data.writeInterfaceToken(IActivityManager.descriptor); + mRemote.transact(FINISH_HEAVY_WEIGHT_APP_TRANSACTION, data, reply, 0); + reply.readException(); + data.recycle(); + reply.recycle(); + } + private IBinder mRemote; } diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index 773c344..49f1a8f 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -42,6 +42,7 @@ import android.database.sqlite.SQLiteDebug; import android.database.sqlite.SQLiteDebug.DbStats; import android.graphics.Bitmap; import android.graphics.Canvas; +import android.os.Build; import android.os.Bundle; import android.os.Debug; import android.os.Handler; @@ -53,6 +54,7 @@ import android.os.ParcelFileDescriptor; import android.os.Process; import android.os.RemoteException; import android.os.ServiceManager; +import android.os.StrictMode; import android.os.SystemClock; import android.util.AndroidRuntimeException; import android.util.Config; @@ -4132,6 +4134,20 @@ public final class ActivityThread { data.info = getPackageInfoNoCheck(data.appInfo); /** + * For system applications on userdebug/eng builds, log stack + * traces of disk and network access to dropbox for analysis. + */ + if ((data.appInfo.flags&ApplicationInfo.FLAG_SYSTEM) != 0 && + !"user".equals(Build.TYPE)) { + StrictMode.setDropBoxManager(ContextImpl.createDropBoxManager()); + StrictMode.setThreadBlockingPolicy( + StrictMode.DISALLOW_DISK_WRITE | + StrictMode.DISALLOW_DISK_READ | + StrictMode.DISALLOW_NETWORK | + StrictMode.PENALTY_DROPBOX); + } + + /** * Switch this process to density compatibility mode if needed. */ if ((data.appInfo.flags&ApplicationInfo.FLAG_SUPPORTS_SCREEN_DENSITIES) diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java index f471f57..bcdfe59 100644 --- a/core/java/android/app/ContextImpl.java +++ b/core/java/android/app/ContextImpl.java @@ -1157,12 +1157,16 @@ class ContextImpl extends Context { return mAudioManager; } + /* package */ static DropBoxManager createDropBoxManager() { + IBinder b = ServiceManager.getService(DROPBOX_SERVICE); + IDropBoxManagerService service = IDropBoxManagerService.Stub.asInterface(b); + return new DropBoxManager(service); + } + private DropBoxManager getDropBoxManager() { synchronized (mSync) { if (mDropBoxManager == null) { - IBinder b = ServiceManager.getService(DROPBOX_SERVICE); - IDropBoxManagerService service = IDropBoxManagerService.Stub.asInterface(b); - mDropBoxManager = new DropBoxManager(service); + mDropBoxManager = createDropBoxManager(); } } return mDropBoxManager; @@ -2173,6 +2177,39 @@ class ContextImpl extends Context { throws NameNotFoundException { return getApplicationIcon(getApplicationInfo(packageName, 0)); } + + @Override + public Drawable getActivityLogo(ComponentName activityName) + throws NameNotFoundException { + return getActivityInfo(activityName, 0).loadLogo(this); + } + + @Override + public Drawable getActivityLogo(Intent intent) + throws NameNotFoundException { + if (intent.getComponent() != null) { + return getActivityLogo(intent.getComponent()); + } + + ResolveInfo info = resolveActivity( + intent, PackageManager.MATCH_DEFAULT_ONLY); + if (info != null) { + return info.activityInfo.loadLogo(this); + } + + throw new NameNotFoundException(intent.toUri(0)); + } + + @Override + public Drawable getApplicationLogo(ApplicationInfo info) { + return info.loadLogo(this); + } + + @Override + public Drawable getApplicationLogo(String packageName) + throws NameNotFoundException { + return getApplicationLogo(getApplicationInfo(packageName, 0)); + } @Override public Resources getResourcesForActivity( ComponentName activityName) throws NameNotFoundException { diff --git a/core/java/android/app/Dialog.java b/core/java/android/app/Dialog.java index 0235599..da8c9e5 100644 --- a/core/java/android/app/Dialog.java +++ b/core/java/android/app/Dialog.java @@ -138,7 +138,7 @@ public class Dialog implements DialogInterface, Window.Callback, 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"); + mWindowManager = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE); Window w = PolicyManager.makeNewWindow(mContext); mWindow = w; w.setCallback(this); diff --git a/core/java/android/app/IActivityManager.java b/core/java/android/app/IActivityManager.java index 31f0a63..ca09290 100644 --- a/core/java/android/app/IActivityManager.java +++ b/core/java/android/app/IActivityManager.java @@ -256,7 +256,9 @@ public interface IActivityManager extends IInterface { ApplicationErrorReport.CrashInfo crashInfo) throws RemoteException; public boolean handleApplicationWtf(IBinder app, String tag, ApplicationErrorReport.CrashInfo crashInfo) throws RemoteException; - + public void handleApplicationStrictModeViolation(IBinder app, + ApplicationErrorReport.CrashInfo crashInfo) throws RemoteException; + /* * This will deliver the specified signal to all the persistent processes. Currently only * SIGUSR1 is delivered. All others are ignored. @@ -303,6 +305,8 @@ public interface IActivityManager extends IInterface { public boolean isUserAMonkey() throws RemoteException; + public void finishHeavyWeightApp() throws RemoteException; + /* * Private non-Binder interfaces */ @@ -513,4 +517,6 @@ public interface IActivityManager extends IInterface { int WILL_ACTIVITY_BE_VISIBLE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+105; int START_ACTIVITY_WITH_CONFIG_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+106; int GET_RUNNING_EXTERNAL_APPLICATIONS_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+107; + int FINISH_HEAVY_WEIGHT_APP_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+108; + int HANDLE_APPLICATION_STRICT_MODE_VIOLATION_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+109; } diff --git a/core/java/android/app/ListActivity.java b/core/java/android/app/ListActivity.java index 84a57b5..4bf5518 100644 --- a/core/java/android/app/ListActivity.java +++ b/core/java/android/app/ListActivity.java @@ -18,9 +18,7 @@ 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; @@ -68,7 +66,7 @@ import android.widget.ListView; * android:layout_weight="1" * android:drawSelectorOnTop="false"/> * - * <TextView id="@id/android:empty" + * <TextView android:id="@id/android:empty" * android:layout_width="match_parent" * android:layout_height="match_parent" * android:background="#FF0000" @@ -316,7 +314,7 @@ public class ListActivity extends Activity { } private AdapterView.OnItemClickListener mOnClickListener = new AdapterView.OnItemClickListener() { - public void onItemClick(AdapterView parent, View v, int position, long id) + public void onItemClick(AdapterView<?> parent, View v, int position, long id) { onListItemClick((ListView)parent, v, position, id); } diff --git a/core/java/android/app/NativeActivity.java b/core/java/android/app/NativeActivity.java new file mode 100644 index 0000000..fd20b71 --- /dev/null +++ b/core/java/android/app/NativeActivity.java @@ -0,0 +1,141 @@ +package android.app; + +import dalvik.system.PathClassLoader; + +import android.content.pm.ActivityInfo; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.os.Bundle; +import android.view.SurfaceHolder; + +import java.io.File; + +/** + * Convenience for implementing an activity that will be implemented + * purely in native code. That is, a game (or game-like thing). + */ +public class NativeActivity extends Activity implements SurfaceHolder.Callback { + public static final String META_DATA_LIB_NAME = "android.app.lib_name"; + + private int mNativeHandle; + + private native int loadNativeCode(String path); + private native void unloadNativeCode(int handle); + + private native void onStartNative(int handle); + private native void onResumeNative(int handle); + private native void onSaveInstanceStateNative(int handle); + private native void onPauseNative(int handle); + private native void onStopNative(int handle); + private native void onLowMemoryNative(int handle); + private native void onWindowFocusChangedNative(int handle, boolean focused); + private native void onSurfaceCreatedNative(int handle, SurfaceHolder holder); + private native void onSurfaceChangedNative(int handle, SurfaceHolder holder, + int format, int width, int height); + private native void onSurfaceDestroyedNative(int handle, SurfaceHolder holder); + + @Override + protected void onCreate(Bundle savedInstanceState) { + String libname = "main"; + ActivityInfo ai; + + getWindow().takeSurface(this); + + try { + ai = getPackageManager().getActivityInfo( + getIntent().getComponent(), PackageManager.GET_META_DATA); + if (ai.metaData != null) { + String ln = ai.metaData.getString(META_DATA_LIB_NAME); + if (ln != null) libname = ln; + } + } catch (PackageManager.NameNotFoundException e) { + throw new RuntimeException("Error getting activity info", e); + } + + String path = null; + + if ((ai.applicationInfo.flags&ApplicationInfo.FLAG_HAS_CODE) == 0) { + // If the application does not have (Java) code, then no ClassLoader + // has been set up for it. We will need to do our own search for + // the native code. + path = ai.applicationInfo.dataDir + "/lib/" + System.mapLibraryName(libname); + if (!(new File(path)).exists()) { + path = null; + } + } + + if (path == null) { + path = ((PathClassLoader)getClassLoader()).findLibrary(libname); + } + + if (path == null) { + throw new IllegalArgumentException("Unable to find native library: " + libname); + } + + mNativeHandle = loadNativeCode(path); + if (mNativeHandle == 0) { + throw new IllegalArgumentException("Unable to load native library: " + path); + } + super.onCreate(savedInstanceState); + } + + @Override + protected void onDestroy() { + unloadNativeCode(mNativeHandle); + super.onDestroy(); + } + + @Override + protected void onPause() { + super.onPause(); + onPauseNative(mNativeHandle); + } + + @Override + protected void onResume() { + super.onResume(); + onResumeNative(mNativeHandle); + } + + @Override + protected void onSaveInstanceState(Bundle outState) { + super.onSaveInstanceState(outState); + onSaveInstanceStateNative(mNativeHandle); + } + + @Override + protected void onStart() { + super.onStart(); + onStartNative(mNativeHandle); + } + + @Override + protected void onStop() { + super.onStop(); + onStopNative(mNativeHandle); + } + + @Override + public void onLowMemory() { + super.onLowMemory(); + onLowMemoryNative(mNativeHandle); + } + + @Override + public void onWindowFocusChanged(boolean hasFocus) { + super.onWindowFocusChanged(hasFocus); + onWindowFocusChangedNative(mNativeHandle, hasFocus); + } + + public void surfaceCreated(SurfaceHolder holder) { + onSurfaceCreatedNative(mNativeHandle, holder); + } + + public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { + onSurfaceChangedNative(mNativeHandle, holder, format, width, height); + } + + public void surfaceDestroyed(SurfaceHolder holder) { + onSurfaceDestroyedNative(mNativeHandle, holder); + } +} diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index 4d72f73..739aca3 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -341,6 +341,44 @@ public class Notification implements Parcelable iconLevel = parcel.readInt(); } + public Notification clone() { + Notification that = new Notification(); + + that.when = this.when; + that.icon = this.icon; + that.number = this.number; + + // PendingIntents are global, so there's no reason (or way) to clone them. + that.contentIntent = this.contentIntent; + that.deleteIntent = this.deleteIntent; + + if (this.tickerText != null) { + that.tickerText = this.tickerText.toString(); + } + if (this.contentView != null) { + that.contentView = this.contentView.clone(); + } + that.iconLevel = that.iconLevel; + that.sound = this.sound; // android.net.Uri is immutable + that.audioStreamType = this.audioStreamType; + + final long[] vibrate = this.vibrate; + if (vibrate != null) { + final int N = vibrate.length; + final long[] vib = that.vibrate = new long[N]; + System.arraycopy(vibrate, 0, vib, 0, N); + } + + that.ledARGB = this.ledARGB; + that.ledOnMS = this.ledOnMS; + that.ledOffMS = this.ledOffMS; + that.defaults = this.defaults; + + that.flags = this.flags; + + return that; + } + public int describeContents() { return 0; } diff --git a/core/java/android/app/StatusBarManager.java b/core/java/android/app/StatusBarManager.java index 72ec616..de544fb 100644 --- a/core/java/android/app/StatusBarManager.java +++ b/core/java/android/app/StatusBarManager.java @@ -23,6 +23,8 @@ import android.os.RemoteException; import android.os.IBinder; import android.os.ServiceManager; +import com.android.internal.statusbar.IStatusBarService; + /** * Allows an app to control the status bar. * @@ -58,12 +60,12 @@ public class StatusBarManager { public static final int DISABLE_NONE = 0x00000000; private Context mContext; - private IStatusBar mService; + private IStatusBarService mService; private IBinder mToken = new Binder(); StatusBarManager(Context context) { mContext = context; - mService = IStatusBar.Stub.asInterface( + mService = IStatusBarService.Stub.asInterface( ServiceManager.getService(Context.STATUS_BAR_SERVICE)); } @@ -85,7 +87,7 @@ public class StatusBarManager { */ public void expand() { try { - mService.activate(); + mService.expand(); } catch (RemoteException ex) { // system process is dead anyway. throw new RuntimeException(ex); @@ -97,46 +99,34 @@ public class StatusBarManager { */ 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(); + mService.collapse(); } catch (RemoteException ex) { // system process is dead anyway. throw new RuntimeException(ex); } } - public IBinder addIcon(String slot, int iconId, int iconLevel) { + public void setIcon(String slot, int iconId, int iconLevel) { try { - return mService.addIcon(slot, mContext.getPackageName(), iconId, iconLevel); + mService.setIcon(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) { + public void removeIcon(String slot) { try { - mService.updateIcon(key, slot, mContext.getPackageName(), iconId, iconLevel); + mService.removeIcon(slot); } catch (RemoteException ex) { // system process is dead anyway. throw new RuntimeException(ex); } } - public void removeIcon(IBinder key) { + public void setIconVisibility(String slot, boolean visible) { try { - mService.removeIcon(key); + mService.setIconVisibility(slot, visible); } catch (RemoteException ex) { // system process is dead anyway. throw new RuntimeException(ex); diff --git a/core/java/android/bluetooth/BluetoothDeviceProfileState.java b/core/java/android/bluetooth/BluetoothDeviceProfileState.java new file mode 100644 index 0000000..8e655e2 --- /dev/null +++ b/core/java/android/bluetooth/BluetoothDeviceProfileState.java @@ -0,0 +1,665 @@ +/* + * Copyright (C) 2010 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.bluetooth; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.os.Message; +import android.server.BluetoothA2dpService; +import android.server.BluetoothService; +import android.util.Log; + +import com.android.internal.util.HierarchicalState; +import com.android.internal.util.HierarchicalStateMachine; + +/** + * This class is the Profile connection state machine associated with a remote + * device. When the device bonds an instance of this class is created. + * This tracks incoming and outgoing connections of all the profiles. Incoming + * connections are preferred over outgoing connections and HFP preferred over + * A2DP. When the device is unbonded, the instance is removed. + * + * States: + * {@link BondedDevice}: This state represents a bonded device. When in this + * state none of the profiles are in transition states. + * + * {@link OutgoingHandsfree}: Handsfree profile connection is in a transition + * state because of a outgoing Connect or Disconnect. + * + * {@link IncomingHandsfree}: Handsfree profile connection is in a transition + * state because of a incoming Connect or Disconnect. + * + * {@link IncomingA2dp}: A2dp profile connection is in a transition + * state because of a incoming Connect or Disconnect. + * + * {@link OutgoingA2dp}: A2dp profile connection is in a transition + * state because of a outgoing Connect or Disconnect. + * + * Todo(): Write tests for this class, when the Android Mock support is completed. + * @hide + */ +public final class BluetoothDeviceProfileState extends HierarchicalStateMachine { + private static final String TAG = "BluetoothDeviceProfileState"; + private static final boolean DBG = true; //STOPSHIP - Change to false + + public static final int CONNECT_HFP_OUTGOING = 1; + public static final int CONNECT_HFP_INCOMING = 2; + public static final int CONNECT_A2DP_OUTGOING = 3; + public static final int CONNECT_A2DP_INCOMING = 4; + + public static final int DISCONNECT_HFP_OUTGOING = 5; + private static final int DISCONNECT_HFP_INCOMING = 6; + public static final int DISCONNECT_A2DP_OUTGOING = 7; + public static final int DISCONNECT_A2DP_INCOMING = 8; + + public static final int UNPAIR = 9; + public static final int AUTO_CONNECT_PROFILES = 10; + public static final int TRANSITION_TO_STABLE = 11; + + private static final int AUTO_CONNECT_DELAY = 6000; // 6 secs + + private BondedDevice mBondedDevice = new BondedDevice(); + private OutgoingHandsfree mOutgoingHandsfree = new OutgoingHandsfree(); + private IncomingHandsfree mIncomingHandsfree = new IncomingHandsfree(); + private IncomingA2dp mIncomingA2dp = new IncomingA2dp(); + private OutgoingA2dp mOutgoingA2dp = new OutgoingA2dp(); + + private Context mContext; + private BluetoothService mService; + private BluetoothA2dpService mA2dpService; + private BluetoothHeadset mHeadsetService; + private boolean mHeadsetServiceConnected; + + private BluetoothDevice mDevice; + private int mHeadsetState; + private int mA2dpState; + + private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + String action = intent.getAction(); + BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); + if (!device.equals(mDevice)) return; + + if (action.equals(BluetoothHeadset.ACTION_STATE_CHANGED)) { + int newState = intent.getIntExtra(BluetoothHeadset.EXTRA_STATE, 0); + int oldState = intent.getIntExtra(BluetoothHeadset.EXTRA_PREVIOUS_STATE, 0); + int initiator = intent.getIntExtra( + BluetoothHeadset.EXTRA_DISCONNECT_INITIATOR, + BluetoothHeadset.LOCAL_DISCONNECT); + mHeadsetState = newState; + if (newState == BluetoothHeadset.STATE_DISCONNECTED && + initiator == BluetoothHeadset.REMOTE_DISCONNECT) { + sendMessage(DISCONNECT_HFP_INCOMING); + } + if (newState == BluetoothHeadset.STATE_CONNECTED || + newState == BluetoothHeadset.STATE_DISCONNECTED) { + sendMessage(TRANSITION_TO_STABLE); + } + } else if (action.equals(BluetoothA2dp.ACTION_SINK_STATE_CHANGED)) { + int newState = intent.getIntExtra(BluetoothA2dp.EXTRA_SINK_STATE, 0); + int oldState = intent.getIntExtra(BluetoothA2dp.EXTRA_PREVIOUS_SINK_STATE, 0); + mA2dpState = newState; + if ((oldState == BluetoothA2dp.STATE_CONNECTED || + oldState == BluetoothA2dp.STATE_PLAYING) && + newState == BluetoothA2dp.STATE_DISCONNECTED) { + sendMessage(DISCONNECT_A2DP_INCOMING); + } + if (newState == BluetoothA2dp.STATE_CONNECTED || + newState == BluetoothA2dp.STATE_DISCONNECTED) { + sendMessage(TRANSITION_TO_STABLE); + } + } else if (action.equals(BluetoothDevice.ACTION_ACL_CONNECTED)) { + if (!getCurrentState().equals(mBondedDevice)) { + Log.e(TAG, "State is: " + getCurrentState()); + return; + } + Message msg = new Message(); + msg.what = AUTO_CONNECT_PROFILES; + sendMessageDelayed(msg, AUTO_CONNECT_DELAY); + } + } + }; + + private boolean isPhoneDocked(BluetoothDevice autoConnectDevice) { + // This works only because these broadcast intents are "sticky" + Intent i = mContext.registerReceiver(null, new IntentFilter(Intent.ACTION_DOCK_EVENT)); + if (i != null) { + int state = i.getIntExtra(Intent.EXTRA_DOCK_STATE, Intent.EXTRA_DOCK_STATE_UNDOCKED); + if (state != Intent.EXTRA_DOCK_STATE_UNDOCKED) { + BluetoothDevice device = i.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); + if (device != null && autoConnectDevice.equals(device)) { + return true; + } + } + } + return false; + } + + public BluetoothDeviceProfileState(Context context, String address, + BluetoothService service, BluetoothA2dpService a2dpService) { + super(address); + mContext = context; + mDevice = new BluetoothDevice(address); + mService = service; + mA2dpService = a2dpService; + + addState(mBondedDevice); + addState(mOutgoingHandsfree); + addState(mIncomingHandsfree); + addState(mIncomingA2dp); + addState(mOutgoingA2dp); + setInitialState(mBondedDevice); + + IntentFilter filter = new IntentFilter(); + // Fine-grained state broadcasts + filter.addAction(BluetoothA2dp.ACTION_SINK_STATE_CHANGED); + filter.addAction(BluetoothHeadset.ACTION_STATE_CHANGED); + filter.addAction(BluetoothDevice.ACTION_ACL_CONNECTED); + + mContext.registerReceiver(mBroadcastReceiver, filter); + + HeadsetServiceListener l = new HeadsetServiceListener(); + } + + private class HeadsetServiceListener implements BluetoothHeadset.ServiceListener { + public HeadsetServiceListener() { + mHeadsetService = new BluetoothHeadset(mContext, this); + } + public void onServiceConnected() { + synchronized(BluetoothDeviceProfileState.this) { + mHeadsetServiceConnected = true; + } + } + public void onServiceDisconnected() { + synchronized(BluetoothDeviceProfileState.this) { + mHeadsetServiceConnected = false; + } + } + } + + private class BondedDevice extends HierarchicalState { + @Override + protected void enter() { + log("Entering ACL Connected state with: " + getCurrentMessage().what); + Message m = new Message(); + m.copyFrom(getCurrentMessage()); + sendMessageAtFrontOfQueue(m); + } + @Override + protected boolean processMessage(Message message) { + log("ACL Connected State -> Processing Message: " + message.what); + switch(message.what) { + case CONNECT_HFP_OUTGOING: + case DISCONNECT_HFP_OUTGOING: + transitionTo(mOutgoingHandsfree); + break; + case CONNECT_HFP_INCOMING: + transitionTo(mIncomingHandsfree); + break; + case DISCONNECT_HFP_INCOMING: + transitionTo(mIncomingHandsfree); + break; + case CONNECT_A2DP_OUTGOING: + case DISCONNECT_A2DP_OUTGOING: + transitionTo(mOutgoingA2dp); + break; + case CONNECT_A2DP_INCOMING: + case DISCONNECT_A2DP_INCOMING: + transitionTo(mIncomingA2dp); + break; + case UNPAIR: + if (mHeadsetState != BluetoothHeadset.STATE_DISCONNECTED) { + sendMessage(DISCONNECT_HFP_OUTGOING); + deferMessage(message); + break; + } else if (mA2dpState != BluetoothA2dp.STATE_DISCONNECTED) { + sendMessage(DISCONNECT_A2DP_OUTGOING); + deferMessage(message); + break; + } + processCommand(UNPAIR); + break; + case AUTO_CONNECT_PROFILES: + if (isPhoneDocked(mDevice)) { + // Don't auto connect to docks. + break; + } else if (!mHeadsetServiceConnected) { + deferMessage(message); + } else { + if (mHeadsetService.getPriority(mDevice) == + BluetoothHeadset.PRIORITY_AUTO_CONNECT && + !mHeadsetService.isConnected(mDevice)) { + mHeadsetService.connectHeadset(mDevice); + } + if (mA2dpService != null && + mA2dpService.getSinkPriority(mDevice) == + BluetoothA2dp.PRIORITY_AUTO_CONNECT && + mA2dpService.getConnectedSinks().length == 0) { + mA2dpService.connectSink(mDevice); + } + } + break; + case TRANSITION_TO_STABLE: + // ignore. + break; + default: + return NOT_HANDLED; + } + return HANDLED; + } + } + + private class OutgoingHandsfree extends HierarchicalState { + private boolean mStatus = false; + private int mCommand; + + @Override + protected void enter() { + log("Entering OutgoingHandsfree state with: " + getCurrentMessage().what); + mCommand = getCurrentMessage().what; + if (mCommand != CONNECT_HFP_OUTGOING && + mCommand != DISCONNECT_HFP_OUTGOING) { + Log.e(TAG, "Error: OutgoingHandsfree state with command:" + mCommand); + } + mStatus = processCommand(mCommand); + if (!mStatus) sendMessage(TRANSITION_TO_STABLE); + } + + @Override + protected boolean processMessage(Message message) { + log("OutgoingHandsfree State -> Processing Message: " + message.what); + Message deferMsg = new Message(); + int command = message.what; + switch(command) { + case CONNECT_HFP_OUTGOING: + if (command != mCommand) { + // Disconnect followed by a connect - defer + deferMessage(message); + } + break; + case CONNECT_HFP_INCOMING: + if (mCommand == CONNECT_HFP_OUTGOING) { + // Cancel outgoing connect, accept incoming + cancelCommand(CONNECT_HFP_OUTGOING); + transitionTo(mIncomingHandsfree); + } else { + // We have done the disconnect but we are not + // sure which state we are in at this point. + deferMessage(message); + } + break; + case CONNECT_A2DP_INCOMING: + // accept incoming A2DP, retry HFP_OUTGOING + transitionTo(mIncomingA2dp); + + if (mStatus) { + deferMsg.what = mCommand; + deferMessage(deferMsg); + } + break; + case CONNECT_A2DP_OUTGOING: + deferMessage(message); + break; + case DISCONNECT_HFP_OUTGOING: + if (mCommand == CONNECT_HFP_OUTGOING) { + // Cancel outgoing connect + cancelCommand(CONNECT_HFP_OUTGOING); + processCommand(DISCONNECT_HFP_OUTGOING); + } + // else ignore + break; + case DISCONNECT_HFP_INCOMING: + // When this happens the socket would be closed and the headset + // state moved to DISCONNECTED, cancel the outgoing thread. + // if it still is in CONNECTING state + cancelCommand(CONNECT_HFP_OUTGOING); + break; + case DISCONNECT_A2DP_OUTGOING: + deferMessage(message); + break; + case DISCONNECT_A2DP_INCOMING: + // Bluez will handle the disconnect. If because of this the outgoing + // handsfree connection has failed, then retry. + if (mStatus) { + deferMsg.what = mCommand; + deferMessage(deferMsg); + } + break; + case UNPAIR: + case AUTO_CONNECT_PROFILES: + deferMessage(message); + break; + case TRANSITION_TO_STABLE: + transitionTo(mBondedDevice); + break; + default: + return NOT_HANDLED; + } + return HANDLED; + } + } + + private class IncomingHandsfree extends HierarchicalState { + private boolean mStatus = false; + private int mCommand; + + @Override + protected void enter() { + log("Entering IncomingHandsfree state with: " + getCurrentMessage().what); + mCommand = getCurrentMessage().what; + if (mCommand != CONNECT_HFP_INCOMING && + mCommand != DISCONNECT_HFP_INCOMING) { + Log.e(TAG, "Error: IncomingHandsfree state with command:" + mCommand); + } + mStatus = processCommand(mCommand); + if (!mStatus) sendMessage(TRANSITION_TO_STABLE); + } + + @Override + protected boolean processMessage(Message message) { + log("IncomingHandsfree State -> Processing Message: " + message.what); + switch(message.what) { + case CONNECT_HFP_OUTGOING: + deferMessage(message); + break; + case CONNECT_HFP_INCOMING: + // Ignore + Log.e(TAG, "Error: Incoming connection with a pending incoming connection"); + break; + case CONNECT_A2DP_INCOMING: + // Serialize the commands. + deferMessage(message); + break; + case CONNECT_A2DP_OUTGOING: + deferMessage(message); + break; + case DISCONNECT_HFP_OUTGOING: + // We don't know at what state we are in the incoming HFP connection state. + // We can be changing from DISCONNECTED to CONNECTING, or + // from CONNECTING to CONNECTED, so serializing this command is + // the safest option. + deferMessage(message); + break; + case DISCONNECT_HFP_INCOMING: + // Nothing to do here, we will already be DISCONNECTED + // by this point. + break; + case DISCONNECT_A2DP_OUTGOING: + deferMessage(message); + break; + case DISCONNECT_A2DP_INCOMING: + // Bluez handles incoming A2DP disconnect. + // If this causes incoming HFP to fail, it is more of a headset problem + // since both connections are incoming ones. + break; + case UNPAIR: + case AUTO_CONNECT_PROFILES: + deferMessage(message); + break; + case TRANSITION_TO_STABLE: + transitionTo(mBondedDevice); + break; + default: + return NOT_HANDLED; + } + return HANDLED; + } + } + + private class OutgoingA2dp extends HierarchicalState { + private boolean mStatus = false; + private int mCommand; + + @Override + protected void enter() { + log("Entering OutgoingA2dp state with: " + getCurrentMessage().what); + mCommand = getCurrentMessage().what; + if (mCommand != CONNECT_A2DP_OUTGOING && + mCommand != DISCONNECT_A2DP_OUTGOING) { + Log.e(TAG, "Error: OutgoingA2DP state with command:" + mCommand); + } + mStatus = processCommand(mCommand); + if (!mStatus) sendMessage(TRANSITION_TO_STABLE); + } + + @Override + protected boolean processMessage(Message message) { + log("OutgoingA2dp State->Processing Message: " + message.what); + Message deferMsg = new Message(); + switch(message.what) { + case CONNECT_HFP_OUTGOING: + processCommand(CONNECT_HFP_OUTGOING); + + // Don't cancel A2DP outgoing as there is no guarantee it + // will get canceled. + // It might already be connected but we might not have got the + // A2DP_SINK_STATE_CHANGE. Hence, no point disconnecting here. + // The worst case, the connection will fail, retry. + // The same applies to Disconnecting an A2DP connection. + if (mStatus) { + deferMsg.what = mCommand; + deferMessage(deferMsg); + } + break; + case CONNECT_HFP_INCOMING: + processCommand(CONNECT_HFP_INCOMING); + + // Don't cancel A2DP outgoing as there is no guarantee + // it will get canceled. + // The worst case, the connection will fail, retry. + if (mStatus) { + deferMsg.what = mCommand; + deferMessage(deferMsg); + } + break; + case CONNECT_A2DP_INCOMING: + // Bluez will take care of conflicts between incoming and outgoing + // connections. + transitionTo(mIncomingA2dp); + break; + case CONNECT_A2DP_OUTGOING: + // Ignore + break; + case DISCONNECT_HFP_OUTGOING: + deferMessage(message); + break; + case DISCONNECT_HFP_INCOMING: + // At this point, we are already disconnected + // with HFP. Sometimes A2DP connection can + // fail due to the disconnection of HFP. So add a retry + // for the A2DP. + if (mStatus) { + deferMsg.what = mCommand; + deferMessage(deferMsg); + } + break; + case DISCONNECT_A2DP_OUTGOING: + processCommand(DISCONNECT_A2DP_OUTGOING); + break; + case DISCONNECT_A2DP_INCOMING: + // Ignore, will be handled by Bluez + break; + case UNPAIR: + case AUTO_CONNECT_PROFILES: + deferMessage(message); + break; + case TRANSITION_TO_STABLE: + transitionTo(mBondedDevice); + break; + default: + return NOT_HANDLED; + } + return HANDLED; + } + } + + private class IncomingA2dp extends HierarchicalState { + private boolean mStatus = false; + private int mCommand; + + @Override + protected void enter() { + log("Entering IncomingA2dp state with: " + getCurrentMessage().what); + mCommand = getCurrentMessage().what; + if (mCommand != CONNECT_A2DP_INCOMING && + mCommand != DISCONNECT_A2DP_INCOMING) { + Log.e(TAG, "Error: IncomingA2DP state with command:" + mCommand); + } + mStatus = processCommand(mCommand); + if (!mStatus) sendMessage(TRANSITION_TO_STABLE); + } + + @Override + protected boolean processMessage(Message message) { + log("IncomingA2dp State->Processing Message: " + message.what); + Message deferMsg = new Message(); + switch(message.what) { + case CONNECT_HFP_OUTGOING: + deferMessage(message); + break; + case CONNECT_HFP_INCOMING: + // Shouldn't happen, but serialize the commands. + deferMessage(message); + break; + case CONNECT_A2DP_INCOMING: + // ignore + break; + case CONNECT_A2DP_OUTGOING: + // Defer message and retry + deferMessage(message); + break; + case DISCONNECT_HFP_OUTGOING: + deferMessage(message); + break; + case DISCONNECT_HFP_INCOMING: + // Shouldn't happen but if does, we can handle it. + // Depends if the headset can handle it. + // Incoming A2DP will be handled by Bluez, Disconnect HFP + // the socket would have already been closed. + // ignore + break; + case DISCONNECT_A2DP_OUTGOING: + deferMessage(message); + break; + case DISCONNECT_A2DP_INCOMING: + // Ignore, will be handled by Bluez + break; + case UNPAIR: + case AUTO_CONNECT_PROFILES: + deferMessage(message); + break; + case TRANSITION_TO_STABLE: + transitionTo(mBondedDevice); + break; + default: + return NOT_HANDLED; + } + return HANDLED; + } + } + + + + synchronized void cancelCommand(int command) { + if (command == CONNECT_HFP_OUTGOING ) { + // Cancel the outgoing thread. + if (mHeadsetServiceConnected) { + mHeadsetService.cancelConnectThread(); + } + // HeadsetService is down. Phone process most likely crashed. + // The thread would have got killed. + } + } + + synchronized void deferHeadsetMessage(int command) { + Message msg = new Message(); + msg.what = command; + deferMessage(msg); + } + + synchronized boolean processCommand(int command) { + log("Processing command:" + command); + switch(command) { + case CONNECT_HFP_OUTGOING: + if (mHeadsetService != null) { + return mHeadsetService.connectHeadsetInternal(mDevice); + } + break; + case CONNECT_HFP_INCOMING: + if (!mHeadsetServiceConnected) { + deferHeadsetMessage(command); + } else if (mHeadsetState == BluetoothHeadset.STATE_CONNECTING) { + return mHeadsetService.acceptIncomingConnect(mDevice); + } else if (mHeadsetState == BluetoothHeadset.STATE_DISCONNECTED) { + return mHeadsetService.createIncomingConnect(mDevice); + } + break; + case CONNECT_A2DP_OUTGOING: + if (mA2dpService != null) { + return mA2dpService.connectSinkInternal(mDevice); + } + break; + case CONNECT_A2DP_INCOMING: + // ignore, Bluez takes care + return true; + case DISCONNECT_HFP_OUTGOING: + if (!mHeadsetServiceConnected) { + deferHeadsetMessage(command); + } else { + if (mHeadsetService.getPriority(mDevice) == + BluetoothHeadset.PRIORITY_AUTO_CONNECT) { + mHeadsetService.setPriority(mDevice, BluetoothHeadset.PRIORITY_ON); + } + return mHeadsetService.disconnectHeadsetInternal(mDevice); + } + break; + case DISCONNECT_HFP_INCOMING: + // ignore + return true; + case DISCONNECT_A2DP_INCOMING: + // ignore + return true; + case DISCONNECT_A2DP_OUTGOING: + if (mA2dpService != null) { + if (mA2dpService.getSinkPriority(mDevice) == + BluetoothA2dp.PRIORITY_AUTO_CONNECT) { + mA2dpService.setSinkPriority(mDevice, BluetoothHeadset.PRIORITY_ON); + } + return mA2dpService.disconnectSinkInternal(mDevice); + } + break; + case UNPAIR: + return mService.removeBondInternal(mDevice.getAddress()); + default: + Log.e(TAG, "Error: Unknown Command"); + } + return false; + } + + /*package*/ BluetoothDevice getDevice() { + return mDevice; + } + + private void log(String message) { + if (DBG) { + Log.i(TAG, "Device:" + mDevice + " Message:" + message); + } + } +} diff --git a/core/java/android/bluetooth/BluetoothHeadset.java b/core/java/android/bluetooth/BluetoothHeadset.java index 95e61b6..4a91a8c 100644 --- a/core/java/android/bluetooth/BluetoothHeadset.java +++ b/core/java/android/bluetooth/BluetoothHeadset.java @@ -189,11 +189,11 @@ public final class BluetoothHeadset { * @return One of the STATE_ return codes, or STATE_ERROR if this proxy * object is currently not connected to the Headset service. */ - public int getState() { + public int getState(BluetoothDevice device) { if (DBG) log("getState()"); if (mService != null) { try { - return mService.getState(); + return mService.getState(device); } catch (RemoteException e) {Log.e(TAG, e.toString());} } else { Log.w(TAG, "Proxy not attached to service"); @@ -271,11 +271,11 @@ public final class BluetoothHeadset { * be made asynchornous. Returns false if this proxy object is * not currently connected to the Headset service. */ - public boolean disconnectHeadset() { + public boolean disconnectHeadset(BluetoothDevice device) { if (DBG) log("disconnectHeadset()"); if (mService != null) { try { - mService.disconnectHeadset(); + mService.disconnectHeadset(device); return true; } catch (RemoteException e) {Log.e(TAG, e.toString());} } else { @@ -395,7 +395,6 @@ public final class BluetoothHeadset { } return -1; } - /** * Indicates if current platform supports voice dialing over bluetooth SCO. * @return true if voice dialing over bluetooth is supported, false otherwise. @@ -406,6 +405,92 @@ public final class BluetoothHeadset { com.android.internal.R.bool.config_bluetooth_sco_off_call); } + /** + * Cancel the outgoing connection. + * @hide + */ + public boolean cancelConnectThread() { + if (DBG) log("cancelConnectThread"); + if (mService != null) { + try { + return mService.cancelConnectThread(); + } catch (RemoteException e) {Log.e(TAG, e.toString());} + } else { + Log.w(TAG, "Proxy not attached to service"); + if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable())); + } + return false; + } + + /** + * Accept the incoming connection. + * @hide + */ + public boolean acceptIncomingConnect(BluetoothDevice device) { + if (DBG) log("acceptIncomingConnect"); + if (mService != null) { + try { + return mService.acceptIncomingConnect(device); + } catch (RemoteException e) {Log.e(TAG, e.toString());} + } else { + Log.w(TAG, "Proxy not attached to service"); + if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable())); + } + return false; + } + + /** + * Create the connect thread the incoming connection. + * @hide + */ + public boolean createIncomingConnect(BluetoothDevice device) { + if (DBG) log("createIncomingConnect"); + if (mService != null) { + try { + return mService.createIncomingConnect(device); + } catch (RemoteException e) {Log.e(TAG, e.toString());} + } else { + Log.w(TAG, "Proxy not attached to service"); + if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable())); + } + return false; + } + + /** + * Connect to a Bluetooth Headset. + * Note: This is an internal function and shouldn't be exposed + * @hide + */ + public boolean connectHeadsetInternal(BluetoothDevice device) { + if (DBG) log("connectHeadsetInternal"); + if (mService != null) { + try { + return mService.connectHeadsetInternal(device); + } catch (RemoteException e) {Log.e(TAG, e.toString());} + } else { + Log.w(TAG, "Proxy not attached to service"); + if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable())); + } + return false; + } + + /** + * Disconnect a Bluetooth Headset. + * Note: This is an internal function and shouldn't be exposed + * @hide + */ + public boolean disconnectHeadsetInternal(BluetoothDevice device) { + if (DBG) log("disconnectHeadsetInternal"); + if (mService != null) { + try { + return mService.disconnectHeadsetInternal(device); + } catch (RemoteException e) {Log.e(TAG, e.toString());} + } else { + Log.w(TAG, "Proxy not attached to service"); + if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable())); + } + return false; + } private ServiceConnection mConnection = new ServiceConnection() { public void onServiceConnected(ComponentName className, IBinder service) { if (DBG) Log.d(TAG, "Proxy object connected"); diff --git a/core/java/android/bluetooth/BluetoothProfileState.java b/core/java/android/bluetooth/BluetoothProfileState.java new file mode 100644 index 0000000..946dcaa --- /dev/null +++ b/core/java/android/bluetooth/BluetoothProfileState.java @@ -0,0 +1,144 @@ +/* + * Copyright (C) 2010 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.bluetooth; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.os.Message; +import android.util.Log; + +import com.android.internal.util.HierarchicalState; +import com.android.internal.util.HierarchicalStateMachine; + +/** + * This state machine is used to serialize the connections + * to a particular profile. Currently, we only allow one device + * to be connected to a particular profile. + * States: + * {@link StableState} : No pending commands. Send the + * command to the appropriate remote device specific state machine. + * + * {@link PendingCommandState} : A profile connection / disconnection + * command is being executed. This will result in a profile state + * change. Defer all commands. + * @hide + */ + +public class BluetoothProfileState extends HierarchicalStateMachine { + private static final boolean DBG = true; // STOPSHIP - change to false. + private static final String TAG = "BluetoothProfileState"; + + public static int HFP = 0; + public static int A2DP = 1; + + private static int TRANSITION_TO_STABLE = 100; + + private int mProfile; + private BluetoothDevice mPendingDevice; + private PendingCommandState mPendingCommandState = new PendingCommandState(); + private StableState mStableState = new StableState(); + + private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + String action = intent.getAction(); + + if (action.equals(BluetoothHeadset.ACTION_STATE_CHANGED)) { + int newState = intent.getIntExtra(BluetoothHeadset.EXTRA_STATE, 0); + if (mProfile == HFP && (newState == BluetoothHeadset.STATE_CONNECTED || + newState == BluetoothHeadset.STATE_DISCONNECTED)) { + sendMessage(TRANSITION_TO_STABLE); + } + } else if (action.equals(BluetoothA2dp.ACTION_SINK_STATE_CHANGED)) { + int newState = intent.getIntExtra(BluetoothA2dp.EXTRA_SINK_STATE, 0); + if (mProfile == A2DP && (newState == BluetoothA2dp.STATE_CONNECTED || + newState == BluetoothA2dp.STATE_DISCONNECTED)) { + sendMessage(TRANSITION_TO_STABLE); + } + } + } + }; + + public BluetoothProfileState(Context context, int profile) { + super("BluetoothProfileState:" + profile); + mProfile = profile; + addState(mStableState); + addState(mPendingCommandState); + setInitialState(mStableState); + + IntentFilter filter = new IntentFilter(); + filter.addAction(BluetoothA2dp.ACTION_SINK_STATE_CHANGED); + filter.addAction(BluetoothHeadset.ACTION_STATE_CHANGED); + context.registerReceiver(mBroadcastReceiver, filter); + } + + private class StableState extends HierarchicalState { + @Override + protected void enter() { + log("Entering Stable State"); + mPendingDevice = null; + } + + @Override + protected boolean processMessage(Message msg) { + if (msg.what != TRANSITION_TO_STABLE) { + transitionTo(mPendingCommandState); + } + return true; + } + } + + private class PendingCommandState extends HierarchicalState { + @Override + protected void enter() { + log("Entering PendingCommandState State"); + dispatchMessage(getCurrentMessage()); + } + + @Override + protected boolean processMessage(Message msg) { + if (msg.what == TRANSITION_TO_STABLE) { + transitionTo(mStableState); + } else { + dispatchMessage(msg); + } + return true; + } + + private void dispatchMessage(Message msg) { + BluetoothDeviceProfileState deviceProfileMgr = + (BluetoothDeviceProfileState)msg.obj; + int cmd = msg.arg1; + if (mPendingDevice == null || mPendingDevice.equals(deviceProfileMgr.getDevice())) { + mPendingDevice = deviceProfileMgr.getDevice(); + deviceProfileMgr.sendMessage(cmd); + } else { + Message deferMsg = new Message(); + deferMsg.arg1 = cmd; + deferMsg.obj = deviceProfileMgr; + deferMessage(deferMsg); + } + } + } + + private void log(String message) { + if (DBG) { + Log.i(TAG, "Message:" + message); + } + } +} diff --git a/core/java/android/bluetooth/IBluetooth.aidl b/core/java/android/bluetooth/IBluetooth.aidl index 0868779..ea71034 100644 --- a/core/java/android/bluetooth/IBluetooth.aidl +++ b/core/java/android/bluetooth/IBluetooth.aidl @@ -68,4 +68,8 @@ interface IBluetooth int addRfcommServiceRecord(in String serviceName, in ParcelUuid uuid, int channel, IBinder b); void removeServiceRecord(int handle); + + boolean connectHeadset(String address); + boolean disconnectHeadset(String address); + boolean notifyIncomingConnection(String address); } diff --git a/core/java/android/bluetooth/IBluetoothA2dp.aidl b/core/java/android/bluetooth/IBluetoothA2dp.aidl index 168fe3b..40f1058 100644 --- a/core/java/android/bluetooth/IBluetoothA2dp.aidl +++ b/core/java/android/bluetooth/IBluetoothA2dp.aidl @@ -33,4 +33,7 @@ interface IBluetoothA2dp { int getSinkState(in BluetoothDevice device); boolean setSinkPriority(in BluetoothDevice device, int priority); int getSinkPriority(in BluetoothDevice device); + + boolean connectSinkInternal(in BluetoothDevice device); + boolean disconnectSinkInternal(in BluetoothDevice device); } diff --git a/core/java/android/bluetooth/IBluetoothHeadset.aidl b/core/java/android/bluetooth/IBluetoothHeadset.aidl index 6cccd50..d96f0ca 100644 --- a/core/java/android/bluetooth/IBluetoothHeadset.aidl +++ b/core/java/android/bluetooth/IBluetoothHeadset.aidl @@ -24,14 +24,20 @@ import android.bluetooth.BluetoothDevice; * {@hide} */ interface IBluetoothHeadset { - int getState(); + int getState(in BluetoothDevice device); BluetoothDevice getCurrentHeadset(); boolean connectHeadset(in BluetoothDevice device); - void disconnectHeadset(); + void disconnectHeadset(in BluetoothDevice device); boolean isConnected(in BluetoothDevice device); boolean startVoiceRecognition(); boolean stopVoiceRecognition(); boolean setPriority(in BluetoothDevice device, int priority); int getPriority(in BluetoothDevice device); int getBatteryUsageHint(); + + boolean createIncomingConnect(in BluetoothDevice device); + boolean acceptIncomingConnect(in BluetoothDevice device); + boolean cancelConnectThread(); + boolean connectHeadsetInternal(in BluetoothDevice device); + boolean disconnectHeadsetInternal(in BluetoothDevice device); } diff --git a/core/java/android/content/ContentService.java b/core/java/android/content/ContentService.java index 377e383..fc2dfc0 100644 --- a/core/java/android/content/ContentService.java +++ b/core/java/android/content/ContentService.java @@ -537,6 +537,9 @@ public final class ContentService extends IContentService.Stub { // Look to see if the proper child already exists String segment = getUriSegment(uri, index); + if (segment == null) { + throw new IllegalArgumentException("Invalid Uri (" + uri + ") used for observer"); + } int N = mChildren.size(); for (int i = 0; i < N; i++) { ObserverNode node = mChildren.get(i); diff --git a/core/java/android/content/IntentSender.java b/core/java/android/content/IntentSender.java index e182021..007a715 100644 --- a/core/java/android/content/IntentSender.java +++ b/core/java/android/content/IntentSender.java @@ -16,6 +16,7 @@ package android.content; +import android.app.ActivityManagerNative; import android.content.Context; import android.content.Intent; import android.content.IIntentSender; @@ -170,6 +171,25 @@ public class IntentSender implements Parcelable { } /** + * Return the package name of the application that created this + * IntentSender, 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 IntentSender objects, such that true * is returned then they both represent the same operation from the * same package. diff --git a/core/java/android/content/SyncStorageEngine.java b/core/java/android/content/SyncStorageEngine.java index 98a4993..6413cec 100644 --- a/core/java/android/content/SyncStorageEngine.java +++ b/core/java/android/content/SyncStorageEngine.java @@ -317,7 +317,9 @@ public class SyncStorageEngine extends Handler { if (sSyncStorageEngine != null) { return; } - File dataDir = Environment.getDataDirectory(); + // This call will return the correct directory whether Encrypted File Systems is + // enabled or not. + File dataDir = Environment.getSecureDataDirectory(); sSyncStorageEngine = new SyncStorageEngine(context, dataDir); } diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java index 1577f9e..7901b155 100644 --- a/core/java/android/content/pm/ApplicationInfo.java +++ b/core/java/android/content/pm/ApplicationInfo.java @@ -174,7 +174,7 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { * Value for {@link #flags}: true when the application's window can be * increased in size for larger screens. Corresponds to * {@link android.R.styleable#AndroidManifestSupportsScreens_largeScreens - * android:smallScreens}. + * android:largeScreens}. */ public static final int FLAG_SUPPORTS_LARGE_SCREENS = 1<<11; @@ -252,6 +252,9 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { public static final int FLAG_RESTORE_ANY_VERSION = 1<<17; /** + * Value for {@link #flags}: Set to true if the application has been + * installed using the forward lock option. + * * Value for {@link #flags}: Set to true if the application is * currently installed on external/removable/unprotected storage. Such * applications may not be available if their storage is not currently @@ -262,12 +265,41 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { public static final int FLAG_EXTERNAL_STORAGE = 1<<18; /** + * Value for {@link #flags}: true when the application's window can be + * increased in size for extra large screens. Corresponds to + * {@link android.R.styleable#AndroidManifestSupportsScreens_xlargeScreens + * android:xlargeScreens}. + */ + public static final int FLAG_SUPPORTS_XLARGE_SCREENS = 1<<19; + + /** + * Value for {@link #flags}: set to <code>true</code> if the application + * has reported that it is heavy-weight, and thus can not participate in + * the normal application lifecycle. + * + * <p>Comes from the + * {@link android.R.styleable#AndroidManifestApplication_heavyWeight android:heavyWeight} + * attribute of the <application> tag. + */ + public static final int FLAG_HEAVY_WEIGHT = 1<<20; + + /** + * Value for {@link #flags}: this is true if the application has set + * its android:neverEncrypt to true, false otherwise. It is used to specify + * that this package specifically "opts-out" of a secured file system solution, + * and will always store its data in-the-clear. + * + * {@hide} + */ + public static final int FLAG_NEVER_ENCRYPT = 1<<30; + + /** * Value for {@link #flags}: Set to true if the application has been * installed using the forward lock option. * * {@hide} */ - public static final int FLAG_FORWARD_LOCK = 1<<20; + public static final int FLAG_FORWARD_LOCK = 1<<29; /** * Value for {@link #flags}: Set to true if the application is @@ -275,7 +307,7 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { * * {@hide} */ - public static final int FLAG_NATIVE_DEBUGGABLE = 1<<21; + public static final int FLAG_NATIVE_DEBUGGABLE = 1<<28; /** * Flags associated with the application. Any combination of @@ -285,7 +317,8 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { * {@link #FLAG_ALLOW_CLEAR_USER_DATA}, {@link #FLAG_UPDATED_SYSTEM_APP}, * {@link #FLAG_TEST_ONLY}, {@link #FLAG_SUPPORTS_SMALL_SCREENS}, * {@link #FLAG_SUPPORTS_NORMAL_SCREENS}, - * {@link #FLAG_SUPPORTS_LARGE_SCREENS}, {@link #FLAG_RESIZEABLE_FOR_SCREENS}, + * {@link #FLAG_SUPPORTS_LARGE_SCREENS}, {@link #FLAG_SUPPORTS_XLARGE_SCREENS}, + * {@link #FLAG_RESIZEABLE_FOR_SCREENS}, * {@link #FLAG_SUPPORTS_SCREEN_DENSITIES}, {@link #FLAG_VM_SAFE_MODE} */ public int flags = 0; @@ -517,7 +550,7 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { public void disableCompatibilityMode() { flags |= (FLAG_SUPPORTS_LARGE_SCREENS | FLAG_SUPPORTS_NORMAL_SCREENS | FLAG_SUPPORTS_SMALL_SCREENS | FLAG_RESIZEABLE_FOR_SCREENS | - FLAG_SUPPORTS_SCREEN_DENSITIES); + FLAG_SUPPORTS_SCREEN_DENSITIES | FLAG_SUPPORTS_XLARGE_SCREENS); } /** diff --git a/core/java/android/content/pm/ComponentInfo.java b/core/java/android/content/pm/ComponentInfo.java index cafe372..f16c4ef 100644 --- a/core/java/android/content/pm/ComponentInfo.java +++ b/core/java/android/content/pm/ComponentInfo.java @@ -157,6 +157,14 @@ public class ComponentInfo extends PackageItemInfo { /** * @hide */ + @Override + protected Drawable loadDefaultLogo(PackageManager pm) { + return applicationInfo.loadLogo(pm); + } + + /** + * @hide + */ @Override protected ApplicationInfo getApplicationInfo() { return applicationInfo; } diff --git a/core/java/android/content/pm/PackageItemInfo.java b/core/java/android/content/pm/PackageItemInfo.java index 14c0680..d73aaf6 100644 --- a/core/java/android/content/pm/PackageItemInfo.java +++ b/core/java/android/content/pm/PackageItemInfo.java @@ -67,6 +67,14 @@ public class PackageItemInfo { public int icon; /** + * A drawable resource identifier (in the package's resources) of this + * component's logo. Logos may be larger/wider than icons and are + * displayed by certain UI elements in place of a name or name/icon + * combination. From the "logo" attribute or, if not set, 0. + */ + public int logo; + + /** * Additional meta-data associated with this component. This field * will only be filled in if you set the * {@link PackageManager#GET_META_DATA} flag when requesting the info. @@ -84,6 +92,7 @@ public class PackageItemInfo { nonLocalizedLabel = orig.nonLocalizedLabel; if (nonLocalizedLabel != null) nonLocalizedLabel = nonLocalizedLabel.toString().trim(); icon = orig.icon; + logo = orig.logo; metaData = orig.metaData; } @@ -152,6 +161,42 @@ public class PackageItemInfo { } /** + * Retrieve the current graphical logo associated with this item. This + * will call back on the given PackageManager to load the logo from + * the application. + * + * @param pm A PackageManager from which the logo can be loaded; usually + * the PackageManager from which you originally retrieved this item. + * + * @return Returns a Drawable containing the item's logo. If the item + * does not have a logo, this method will return null. + */ + public Drawable loadLogo(PackageManager pm) { + if (logo != 0) { + Drawable d = pm.getDrawable(packageName, logo, getApplicationInfo()); + if (d != null) { + return d; + } + } + return loadDefaultLogo(pm); + } + + /** + * Retrieve the default graphical logo associated with this item. + * + * @param pm A PackageManager from which the logo can be loaded; usually + * the PackageManager from which you originally retrieved this item. + * + * @return Returns a Drawable containing the item's default logo + * or null if no default logo is available. + * + * @hide + */ + protected Drawable loadDefaultLogo(PackageManager pm) { + return null; + } + + /** * Load an XML resource attached to the meta-data of this item. This will * retrieved the name meta-data entry, and if defined call back on the * given PackageManager to load its XML file from the application. @@ -196,6 +241,7 @@ public class PackageItemInfo { dest.writeInt(labelRes); TextUtils.writeToParcel(nonLocalizedLabel, dest, parcelableFlags); dest.writeInt(icon); + dest.writeInt(logo); dest.writeBundle(metaData); } @@ -206,6 +252,7 @@ public class PackageItemInfo { nonLocalizedLabel = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source); icon = source.readInt(); + logo = source.readInt(); metaData = source.readBundle(); } diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java index 68b44e7..196f508 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -1597,6 +1597,79 @@ public abstract class PackageManager { throws NameNotFoundException; /** + * Retrieve the logo associated with an activity. Given the full name of + * an activity, retrieves the information about it and calls + * {@link ComponentInfo#loadLogo ComponentInfo.loadLogo()} to return its logo. + * If the activity can not be found, NameNotFoundException is thrown. + * + * @param activityName Name of the activity whose logo is to be retrieved. + * + * @return Returns the image of the logo or null if the activity has no + * logo specified. + * + * @throws NameNotFoundException Thrown if the resources for the given + * activity could not be loaded. + * + * @see #getActivityLogo(Intent) + */ + public abstract Drawable getActivityLogo(ComponentName activityName) + throws NameNotFoundException; + + /** + * Retrieve the logo associated with an Intent. If intent.getClassName() is + * set, this simply returns the result of + * getActivityLogo(intent.getClassName()). Otherwise it resolves the intent's + * component and returns the logo associated with the resolved component. + * If intent.getClassName() can not be found or the Intent can not be resolved + * to a component, NameNotFoundException is thrown. + * + * @param intent The intent for which you would like to retrieve a logo. + * + * @return Returns the image of the logo, or null if the activity has no + * logo specified. + * + * @throws NameNotFoundException Thrown if the resources for application + * matching the given intent could not be loaded. + * + * @see #getActivityLogo(ComponentName) + */ + public abstract Drawable getActivityLogo(Intent intent) + throws NameNotFoundException; + + /** + * Retrieve the logo associated with an application. If it has not specified + * a logo, this method returns null. + * + * @param info Information about application being queried. + * + * @return Returns the image of the logo, or null if no logo is specified + * by the application. + * + * @see #getApplicationLogo(String) + */ + public abstract Drawable getApplicationLogo(ApplicationInfo info); + + /** + * Retrieve the logo associated with an application. Given the name of the + * application's package, retrieves the information about it and calls + * getApplicationLogo() to return its logo. If the application can not be + * found, NameNotFoundException is thrown. + * + * @param packageName Name of the package whose application logo is to be + * retrieved. + * + * @return Returns the image of the logo, or null if no application logo + * has been specified. + * + * @throws NameNotFoundException Thrown if the resources for the given + * application could not be loaded. + * + * @see #getApplicationLogo(ApplicationInfo) + */ + public abstract Drawable getApplicationLogo(String packageName) + throws NameNotFoundException; + + /** * Retrieve text from a package. This is a low-level API used by * the various package manager info structures (such as * {@link ComponentInfo} to implement retrieval of their associated diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java index 2a20a2d..a5f5acc 100644 --- a/core/java/android/content/pm/PackageParser.java +++ b/core/java/android/content/pm/PackageParser.java @@ -105,17 +105,19 @@ public class PackageParser { final int nameRes; final int labelRes; final int iconRes; + final int logoRes; String tag; TypedArray sa; ParsePackageItemArgs(Package _owner, String[] _outError, - int _nameRes, int _labelRes, int _iconRes) { + int _nameRes, int _labelRes, int _iconRes, int _logoRes) { owner = _owner; outError = _outError; nameRes = _nameRes; labelRes = _labelRes; iconRes = _iconRes; + logoRes = _logoRes; } } @@ -127,10 +129,10 @@ public class PackageParser { int flags; ParseComponentArgs(Package _owner, String[] _outError, - int _nameRes, int _labelRes, int _iconRes, + int _nameRes, int _labelRes, int _iconRes, int _logoRes, String[] _sepProcesses, int _processRes, int _descriptionRes, int _enabledRes) { - super(_owner, _outError, _nameRes, _labelRes, _iconRes); + super(_owner, _outError, _nameRes, _labelRes, _iconRes, _logoRes); sepProcesses = _sepProcesses; processRes = _processRes; descriptionRes = _descriptionRes; @@ -789,6 +791,7 @@ public class PackageParser { int supportsSmallScreens = 1; int supportsNormalScreens = 1; int supportsLargeScreens = 1; + int supportsXLargeScreens = 1; int resizeable = 1; int anyDensity = 1; @@ -996,9 +999,12 @@ public class PackageParser { supportsLargeScreens = sa.getInteger( com.android.internal.R.styleable.AndroidManifestSupportsScreens_largeScreens, supportsLargeScreens); + supportsXLargeScreens = sa.getInteger( + com.android.internal.R.styleable.AndroidManifestSupportsScreens_xlargeScreens, + supportsXLargeScreens); resizeable = sa.getInteger( com.android.internal.R.styleable.AndroidManifestSupportsScreens_resizeable, - supportsLargeScreens); + resizeable); anyDensity = sa.getInteger( com.android.internal.R.styleable.AndroidManifestSupportsScreens_anyDensity, anyDensity); @@ -1132,6 +1138,11 @@ public class PackageParser { >= android.os.Build.VERSION_CODES.DONUT)) { pkg.applicationInfo.flags |= ApplicationInfo.FLAG_SUPPORTS_LARGE_SCREENS; } + if (supportsXLargeScreens < 0 || (supportsXLargeScreens > 0 + && pkg.applicationInfo.targetSdkVersion + >= android.os.Build.VERSION_CODES.GINGERBREAD)) { + pkg.applicationInfo.flags |= ApplicationInfo.FLAG_SUPPORTS_XLARGE_SCREENS; + } if (resizeable < 0 || (resizeable > 0 && pkg.applicationInfo.targetSdkVersion >= android.os.Build.VERSION_CODES.DONUT)) { @@ -1241,7 +1252,8 @@ public class PackageParser { "<permission-group>", sa, com.android.internal.R.styleable.AndroidManifestPermissionGroup_name, com.android.internal.R.styleable.AndroidManifestPermissionGroup_label, - com.android.internal.R.styleable.AndroidManifestPermissionGroup_icon)) { + com.android.internal.R.styleable.AndroidManifestPermissionGroup_icon, + com.android.internal.R.styleable.AndroidManifestPermissionGroup_logo)) { sa.recycle(); mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED; return null; @@ -1276,7 +1288,8 @@ public class PackageParser { "<permission>", sa, com.android.internal.R.styleable.AndroidManifestPermission_name, com.android.internal.R.styleable.AndroidManifestPermission_label, - com.android.internal.R.styleable.AndroidManifestPermission_icon)) { + com.android.internal.R.styleable.AndroidManifestPermission_icon, + com.android.internal.R.styleable.AndroidManifestPermission_logo)) { sa.recycle(); mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED; return null; @@ -1329,7 +1342,8 @@ public class PackageParser { "<permission-tree>", sa, com.android.internal.R.styleable.AndroidManifestPermissionTree_name, com.android.internal.R.styleable.AndroidManifestPermissionTree_label, - com.android.internal.R.styleable.AndroidManifestPermissionTree_icon)) { + com.android.internal.R.styleable.AndroidManifestPermissionTree_icon, + com.android.internal.R.styleable.AndroidManifestPermissionTree_logo)) { sa.recycle(); mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED; return null; @@ -1373,7 +1387,8 @@ public class PackageParser { mParseInstrumentationArgs = new ParsePackageItemArgs(owner, outError, com.android.internal.R.styleable.AndroidManifestInstrumentation_name, com.android.internal.R.styleable.AndroidManifestInstrumentation_label, - com.android.internal.R.styleable.AndroidManifestInstrumentation_icon); + com.android.internal.R.styleable.AndroidManifestInstrumentation_icon, + com.android.internal.R.styleable.AndroidManifestInstrumentation_logo); mParseInstrumentationArgs.tag = "<instrumentation>"; } @@ -1485,6 +1500,8 @@ public class PackageParser { ai.icon = sa.getResourceId( com.android.internal.R.styleable.AndroidManifestApplication_icon, 0); + ai.logo = sa.getResourceId( + com.android.internal.R.styleable.AndroidManifestApplication_logo, 0); ai.theme = sa.getResourceId( com.android.internal.R.styleable.AndroidManifestApplication_theme, 0); ai.descriptionRes = sa.getResourceId( @@ -1542,6 +1559,12 @@ public class PackageParser { ai.flags |= ApplicationInfo.FLAG_TEST_ONLY; } + if (sa.getBoolean( + com.android.internal.R.styleable.AndroidManifestApplication_neverEncrypt, + false)) { + ai.flags |= ApplicationInfo.FLAG_NEVER_ENCRYPT; + } + String str; str = sa.getNonConfigurationString( com.android.internal.R.styleable.AndroidManifestApplication_permission, 0); @@ -1577,6 +1600,18 @@ public class PackageParser { ai.enabled = sa.getBoolean( com.android.internal.R.styleable.AndroidManifestApplication_enabled, true); + + if (sa.getBoolean( + com.android.internal.R.styleable.AndroidManifestApplication_heavyWeight, + false)) { + ai.flags |= ApplicationInfo.FLAG_HEAVY_WEIGHT; + + // A heavy-weight application can not be in a custom process. + // We can do direct compare because we intern all strings. + if (ai.processName != null && ai.processName != ai.packageName) { + outError[0] = "Heavy-weight applications can not use custom processes"; + } + } } sa.recycle(); @@ -1705,7 +1740,7 @@ public class PackageParser { private boolean parsePackageItemInfo(Package owner, PackageItemInfo outInfo, String[] outError, String tag, TypedArray sa, - int nameRes, int labelRes, int iconRes) { + int nameRes, int labelRes, int iconRes, int logoRes) { String name = sa.getNonConfigurationString(nameRes, 0); if (name == null) { outError[0] = tag + " does not specify android:name"; @@ -1723,6 +1758,11 @@ public class PackageParser { outInfo.icon = iconVal; outInfo.nonLocalizedLabel = null; } + + int logoVal = sa.getResourceId(logoRes, 0); + if (logoVal != 0) { + outInfo.logo = logoVal; + } TypedValue v = sa.peekValue(labelRes); if (v != null && (outInfo.labelRes=v.resourceId) == 0) { @@ -1745,6 +1785,7 @@ public class PackageParser { com.android.internal.R.styleable.AndroidManifestActivity_name, com.android.internal.R.styleable.AndroidManifestActivity_label, com.android.internal.R.styleable.AndroidManifestActivity_icon, + com.android.internal.R.styleable.AndroidManifestActivity_logo, mSeparateProcesses, com.android.internal.R.styleable.AndroidManifestActivity_process, com.android.internal.R.styleable.AndroidManifestActivity_description, @@ -1860,6 +1901,14 @@ public class PackageParser { sa.recycle(); + if (receiver && (owner.applicationInfo.flags&ApplicationInfo.FLAG_HEAVY_WEIGHT) != 0) { + // A heavy-weight application can not have receives in its main process + // We can do direct compare because we intern all strings. + if (a.info.processName == owner.packageName) { + outError[0] = "Heavy-weight applications can not have receivers in main process"; + } + } + if (outError[0] != null) { return null; } @@ -1947,6 +1996,7 @@ public class PackageParser { com.android.internal.R.styleable.AndroidManifestActivityAlias_name, com.android.internal.R.styleable.AndroidManifestActivityAlias_label, com.android.internal.R.styleable.AndroidManifestActivityAlias_icon, + com.android.internal.R.styleable.AndroidManifestActivityAlias_logo, mSeparateProcesses, 0, com.android.internal.R.styleable.AndroidManifestActivityAlias_description, @@ -1980,6 +2030,7 @@ public class PackageParser { info.configChanges = target.info.configChanges; info.flags = target.info.flags; info.icon = target.info.icon; + info.logo = target.info.logo; info.labelRes = target.info.labelRes; info.nonLocalizedLabel = target.info.nonLocalizedLabel; info.launchMode = target.info.launchMode; @@ -2074,6 +2125,7 @@ public class PackageParser { com.android.internal.R.styleable.AndroidManifestProvider_name, com.android.internal.R.styleable.AndroidManifestProvider_label, com.android.internal.R.styleable.AndroidManifestProvider_icon, + com.android.internal.R.styleable.AndroidManifestProvider_logo, mSeparateProcesses, com.android.internal.R.styleable.AndroidManifestProvider_process, com.android.internal.R.styleable.AndroidManifestProvider_description, @@ -2139,6 +2191,15 @@ public class PackageParser { sa.recycle(); + if ((owner.applicationInfo.flags&ApplicationInfo.FLAG_HEAVY_WEIGHT) != 0) { + // A heavy-weight application can not have providers in its main process + // We can do direct compare because we intern all strings. + if (p.info.processName == owner.packageName) { + outError[0] = "Heavy-weight applications can not have providers in main process"; + return null; + } + } + if (cpname == null) { outError[0] = "<provider> does not incude authorities attribute"; return null; @@ -2337,6 +2398,7 @@ public class PackageParser { com.android.internal.R.styleable.AndroidManifestService_name, com.android.internal.R.styleable.AndroidManifestService_label, com.android.internal.R.styleable.AndroidManifestService_icon, + com.android.internal.R.styleable.AndroidManifestService_logo, mSeparateProcesses, com.android.internal.R.styleable.AndroidManifestService_process, com.android.internal.R.styleable.AndroidManifestService_description, @@ -2370,6 +2432,15 @@ public class PackageParser { sa.recycle(); + if ((owner.applicationInfo.flags&ApplicationInfo.FLAG_HEAVY_WEIGHT) != 0) { + // A heavy-weight application can not have services in its main process + // We can do direct compare because we intern all strings. + if (s.info.processName == owner.packageName) { + outError[0] = "Heavy-weight applications can not have services in main process"; + return null; + } + } + int outerDepth = parser.getDepth(); int type; while ((type=parser.next()) != XmlPullParser.END_DOCUMENT @@ -2540,6 +2611,9 @@ public class PackageParser { outInfo.icon = sa.getResourceId( com.android.internal.R.styleable.AndroidManifestIntentFilter_icon, 0); + + outInfo.logo = sa.getResourceId( + com.android.internal.R.styleable.AndroidManifestIntentFilter_logo, 0); sa.recycle(); @@ -2801,6 +2875,11 @@ public class PackageParser { outInfo.icon = iconVal; outInfo.nonLocalizedLabel = null; } + + int logoVal = args.sa.getResourceId(args.logoRes, 0); + if (logoVal != 0) { + outInfo.logo = logoVal; + } TypedValue v = args.sa.peekValue(args.labelRes); if (v != null && (outInfo.labelRes=v.resourceId) == 0) { @@ -3135,6 +3214,7 @@ public class PackageParser { public int labelRes; public CharSequence nonLocalizedLabel; public int icon; + public int logo; } public final static class ActivityIntentInfo extends IntentInfo { diff --git a/core/java/android/content/res/CompatibilityInfo.java b/core/java/android/content/res/CompatibilityInfo.java index 11c67cc..d0ba590 100644 --- a/core/java/android/content/res/CompatibilityInfo.java +++ b/core/java/android/content/res/CompatibilityInfo.java @@ -99,7 +99,22 @@ public class CompatibilityInfo { */ private static final int CONFIGURED_LARGE_SCREENS = 16; - private static final int SCALING_EXPANDABLE_MASK = SCALING_REQUIRED | EXPANDABLE | LARGE_SCREENS; + /** + * A flag mask to indicates that the application supports xlarge screens. + * The flag is set to true if + * 1) Application declares it supports xlarge screens in manifest file using <supports-screens> or + * 2) The screen size is not xlarge + * {@see compatibilityFlag} + */ + private static final int XLARGE_SCREENS = 32; + + /** + * A flag mask to tell if the application supports xlarge screens. This differs + * from XLARGE_SCREENS in that the application that does not support xlarge + * screens will be marked as supporting them if the current screen is not + * xlarge. + */ + private static final int CONFIGURED_XLARGE_SCREENS = 64; /** * The effective screen density we have selected for this application. @@ -127,6 +142,9 @@ public class CompatibilityInfo { if ((appInfo.flags & ApplicationInfo.FLAG_SUPPORTS_LARGE_SCREENS) != 0) { mCompatibilityFlags |= LARGE_SCREENS | CONFIGURED_LARGE_SCREENS; } + if ((appInfo.flags & ApplicationInfo.FLAG_SUPPORTS_XLARGE_SCREENS) != 0) { + mCompatibilityFlags |= XLARGE_SCREENS | CONFIGURED_XLARGE_SCREENS; + } if ((appInfo.flags & ApplicationInfo.FLAG_RESIZEABLE_FOR_SCREENS) != 0) { mCompatibilityFlags |= EXPANDABLE | CONFIGURED_EXPANDABLE; } @@ -157,6 +175,7 @@ public class CompatibilityInfo { this(ApplicationInfo.FLAG_SUPPORTS_SMALL_SCREENS | ApplicationInfo.FLAG_SUPPORTS_NORMAL_SCREENS | ApplicationInfo.FLAG_SUPPORTS_LARGE_SCREENS + | ApplicationInfo.FLAG_SUPPORTS_XLARGE_SCREENS | ApplicationInfo.FLAG_RESIZEABLE_FOR_SCREENS, EXPANDABLE | CONFIGURED_EXPANDABLE, DisplayMetrics.DENSITY_DEVICE, @@ -196,6 +215,17 @@ public class CompatibilityInfo { } /** + * Sets large screen bit in the compatibility flag. + */ + public void setXLargeScreens(boolean expandable) { + if (expandable) { + mCompatibilityFlags |= CompatibilityInfo.XLARGE_SCREENS; + } else { + mCompatibilityFlags &= ~CompatibilityInfo.XLARGE_SCREENS; + } + } + + /** * @return true if the application is configured to be expandable. */ public boolean isConfiguredExpandable() { @@ -210,6 +240,13 @@ public class CompatibilityInfo { } /** + * @return true if the application is configured to be expandable. + */ + public boolean isConfiguredXLargeScreens() { + return (mCompatibilityFlags & CompatibilityInfo.CONFIGURED_XLARGE_SCREENS) != 0; + } + + /** * @return true if the scaling is required */ public boolean isScalingRequired() { diff --git a/core/java/android/content/res/Configuration.java b/core/java/android/content/res/Configuration.java index 1a0c867..02956ba 100644 --- a/core/java/android/content/res/Configuration.java +++ b/core/java/android/content/res/Configuration.java @@ -62,6 +62,7 @@ public final class Configuration implements Parcelable, Comparable<Configuration public static final int SCREENLAYOUT_SIZE_SMALL = 0x01; public static final int SCREENLAYOUT_SIZE_NORMAL = 0x02; public static final int SCREENLAYOUT_SIZE_LARGE = 0x03; + public static final int SCREENLAYOUT_SIZE_XLARGE = 0x04; public static final int SCREENLAYOUT_LONG_MASK = 0x30; public static final int SCREENLAYOUT_LONG_UNDEFINED = 0x00; @@ -82,7 +83,7 @@ public final class Configuration implements Parcelable, Comparable<Configuration * <p>The {@link #SCREENLAYOUT_SIZE_MASK} bits define the overall size * of the screen. They may be one of * {@link #SCREENLAYOUT_SIZE_SMALL}, {@link #SCREENLAYOUT_SIZE_NORMAL}, - * or {@link #SCREENLAYOUT_SIZE_LARGE}. + * {@link #SCREENLAYOUT_SIZE_LARGE}, or {@link #SCREENLAYOUT_SIZE_XLARGE}. * * <p>The {@link #SCREENLAYOUT_LONG_MASK} defines whether the screen * is wider/taller than normal. They may be one of diff --git a/core/java/android/database/Cursor.java b/core/java/android/database/Cursor.java index 79178f4..6539156 100644 --- a/core/java/android/database/Cursor.java +++ b/core/java/android/database/Cursor.java @@ -25,6 +25,9 @@ import java.util.Map; /** * This interface provides random read-write access to the result set returned * by a database query. + * + * Cursor implementations are not required to be synchronized so code using a Cursor from multiple + * threads should perform its own synchronization when using the Cursor. */ public interface Cursor { /** diff --git a/core/java/android/database/sqlite/SQLiteCursor.java b/core/java/android/database/sqlite/SQLiteCursor.java index 6e5b3e1..c7e58fa 100644 --- a/core/java/android/database/sqlite/SQLiteCursor.java +++ b/core/java/android/database/sqlite/SQLiteCursor.java @@ -36,6 +36,9 @@ import java.util.concurrent.locks.ReentrantLock; /** * A Cursor implementation that exposes results from a query on a * {@link SQLiteDatabase}. + * + * SQLiteCursor is not internally synchronized so code using a SQLiteCursor from multiple + * threads should perform its own synchronization when using the SQLiteCursor. */ public class SQLiteCursor extends AbstractWindowedCursor { static final String TAG = "Cursor"; diff --git a/core/java/android/database/sqlite/SQLiteDatabase.java b/core/java/android/database/sqlite/SQLiteDatabase.java index fb5507d..0e798dc 100644 --- a/core/java/android/database/sqlite/SQLiteDatabase.java +++ b/core/java/android/database/sqlite/SQLiteDatabase.java @@ -33,6 +33,8 @@ import android.util.EventLog; import android.util.Log; import android.util.Pair; +import dalvik.system.BlockGuard; + import java.io.File; import java.lang.ref.WeakReference; import java.text.SimpleDateFormat; @@ -1134,7 +1136,8 @@ public class SQLiteDatabase extends SQLiteClosable { * * @param sql The raw SQL statement, may contain ? for unknown values to be * bound later. - * @return a pre-compiled statement object. + * @return A pre-compiled {@link SQLiteStatement} object. Note that + * {@link SQLiteStatement}s are not synchronized, see the documentation for more details. */ public SQLiteStatement compileStatement(String sql) throws SQLException { lock(); @@ -1175,7 +1178,8 @@ public class SQLiteDatabase extends SQLiteClosable { * default sort order, which may be unordered. * @param limit Limits the number of rows returned by the query, * formatted as LIMIT clause. Passing null denotes no LIMIT clause. - * @return A Cursor object, which is positioned before the first entry + * @return A {@link Cursor} object, which is positioned before the first entry. Note that + * {@link Cursor}s are not synchronized, see the documentation for more details. * @see Cursor */ public Cursor query(boolean distinct, String table, String[] columns, @@ -1213,7 +1217,8 @@ public class SQLiteDatabase extends SQLiteClosable { * default sort order, which may be unordered. * @param limit Limits the number of rows returned by the query, * formatted as LIMIT clause. Passing null denotes no LIMIT clause. - * @return A Cursor object, which is positioned before the first entry + * @return A {@link Cursor} object, which is positioned before the first entry. Note that + * {@link Cursor}s are not synchronized, see the documentation for more details. * @see Cursor */ public Cursor queryWithFactory(CursorFactory cursorFactory, @@ -1254,7 +1259,8 @@ public class SQLiteDatabase extends SQLiteClosable { * @param orderBy How to order the rows, formatted as an SQL ORDER BY clause * (excluding the ORDER BY itself). Passing null will use the * default sort order, which may be unordered. - * @return A {@link Cursor} object, which is positioned before the first entry + * @return A {@link Cursor} object, which is positioned before the first entry. Note that + * {@link Cursor}s are not synchronized, see the documentation for more details. * @see Cursor */ public Cursor query(String table, String[] columns, String selection, @@ -1291,7 +1297,8 @@ public class SQLiteDatabase extends SQLiteClosable { * default sort order, which may be unordered. * @param limit Limits the number of rows returned by the query, * formatted as LIMIT clause. Passing null denotes no LIMIT clause. - * @return A {@link Cursor} object, which is positioned before the first entry + * @return A {@link Cursor} object, which is positioned before the first entry. Note that + * {@link Cursor}s are not synchronized, see the documentation for more details. * @see Cursor */ public Cursor query(String table, String[] columns, String selection, @@ -1309,7 +1316,8 @@ public class SQLiteDatabase extends SQLiteClosable { * @param selectionArgs You may include ?s in where clause in the query, * which will be replaced by the values from selectionArgs. The * values will be bound as Strings. - * @return A {@link Cursor} object, which is positioned before the first entry + * @return A {@link Cursor} object, which is positioned before the first entry. Note that + * {@link Cursor}s are not synchronized, see the documentation for more details. */ public Cursor rawQuery(String sql, String[] selectionArgs) { return rawQueryWithFactory(null, sql, selectionArgs, null); @@ -1324,7 +1332,8 @@ public class SQLiteDatabase extends SQLiteClosable { * which will be replaced by the values from selectionArgs. The * values will be bound as Strings. * @param editTable the name of the first table, which is editable - * @return A {@link Cursor} object, which is positioned before the first entry + * @return A {@link Cursor} object, which is positioned before the first entry. Note that + * {@link Cursor}s are not synchronized, see the documentation for more details. */ public Cursor rawQueryWithFactory( CursorFactory cursorFactory, String sql, String[] selectionArgs, @@ -1332,6 +1341,7 @@ public class SQLiteDatabase extends SQLiteClosable { if (!isOpen()) { throw new IllegalStateException("database not open"); } + BlockGuard.getThreadPolicy().onReadFromDisk(); long timeStart = 0; if (Config.LOGV || mSlowQueryThreshold != -1) { @@ -1379,7 +1389,8 @@ public class SQLiteDatabase extends SQLiteClosable { * values will be bound as Strings. * @param initialRead set the initial count of items to read from the cursor * @param maxRead set the count of items to read on each iteration after the first - * @return A {@link Cursor} object, which is positioned before the first entry + * @return A {@link Cursor} object, which is positioned before the first entry. Note that + * {@link Cursor}s are not synchronized, see the documentation for more details. * * This work is incomplete and not fully tested or reviewed, so currently * hidden. @@ -1489,6 +1500,7 @@ public class SQLiteDatabase extends SQLiteClosable { */ public long insertWithOnConflict(String table, String nullColumnHack, ContentValues initialValues, int conflictAlgorithm) { + BlockGuard.getThreadPolicy().onWriteToDisk(); if (!isOpen()) { throw new IllegalStateException("database not open"); } @@ -1580,6 +1592,7 @@ public class SQLiteDatabase extends SQLiteClosable { * whereClause. */ public int delete(String table, String whereClause, String[] whereArgs) { + BlockGuard.getThreadPolicy().onWriteToDisk(); lock(); if (!isOpen()) { throw new IllegalStateException("database not open"); @@ -1635,6 +1648,7 @@ public class SQLiteDatabase extends SQLiteClosable { */ public int updateWithOnConflict(String table, ContentValues values, String whereClause, String[] whereArgs, int conflictAlgorithm) { + BlockGuard.getThreadPolicy().onWriteToDisk(); if (values == null || values.size() == 0) { throw new IllegalArgumentException("Empty values"); } @@ -1717,6 +1731,7 @@ public class SQLiteDatabase extends SQLiteClosable { * @throws SQLException If the SQL string is invalid for some reason */ public void execSQL(String sql) throws SQLException { + BlockGuard.getThreadPolicy().onWriteToDisk(); long timeStart = SystemClock.uptimeMillis(); lock(); if (!isOpen()) { @@ -1752,6 +1767,7 @@ public class SQLiteDatabase extends SQLiteClosable { * @throws SQLException If the SQL string is invalid for some reason */ public void execSQL(String sql, Object[] bindArgs) throws SQLException { + BlockGuard.getThreadPolicy().onWriteToDisk(); if (bindArgs == null) { throw new IllegalArgumentException("Empty bindArgs"); } diff --git a/core/java/android/database/sqlite/SQLiteOpenHelper.java b/core/java/android/database/sqlite/SQLiteOpenHelper.java index 52aac3a..aefbabc 100644 --- a/core/java/android/database/sqlite/SQLiteOpenHelper.java +++ b/core/java/android/database/sqlite/SQLiteOpenHelper.java @@ -28,6 +28,12 @@ import android.util.Log; * Transactions are used to make sure the database is always in a sensible state. * <p>For an example, see the NotePadProvider class in the NotePad sample application, * in the <em>samples/</em> directory of the SDK.</p> + * + * <p class="note"><strong>Note:</strong> this class assumes + * monotonically increasing version numbers for upgrades. Also, there + * is no concept of a database downgrade; installing a new version of + * your app which uses a lower version number than a + * previously-installed version will result in undefined behavior.</p> */ public abstract class SQLiteOpenHelper { private static final String TAG = SQLiteOpenHelper.class.getSimpleName(); @@ -105,6 +111,10 @@ public abstract class SQLiteOpenHelper { if (version == 0) { onCreate(db); } else { + if (version > mNewVersion) { + Log.wtf(TAG, "Can't downgrade read-only database from version " + + version + " to " + mNewVersion + ": " + db.getPath()); + } onUpgrade(db, version, mNewVersion); } db.setVersion(mNewVersion); diff --git a/core/java/android/database/sqlite/SQLiteProgram.java b/core/java/android/database/sqlite/SQLiteProgram.java index 89a5f0d..4d96f12 100644 --- a/core/java/android/database/sqlite/SQLiteProgram.java +++ b/core/java/android/database/sqlite/SQLiteProgram.java @@ -20,6 +20,9 @@ import android.util.Log; /** * A base class for compiled SQLite programs. + * + * SQLiteProgram is not internally synchronized so code using a SQLiteProgram from multiple + * threads should perform its own synchronization when using the SQLiteProgram. */ public abstract class SQLiteProgram extends SQLiteClosable { diff --git a/core/java/android/database/sqlite/SQLiteQuery.java b/core/java/android/database/sqlite/SQLiteQuery.java index 43d2fac..905b66b 100644 --- a/core/java/android/database/sqlite/SQLiteQuery.java +++ b/core/java/android/database/sqlite/SQLiteQuery.java @@ -23,6 +23,9 @@ import android.util.Log; /** * A SQLite program that represents a query that reads the resulting rows into a CursorWindow. * This class is used by SQLiteCursor and isn't useful itself. + * + * SQLiteQuery is not internally synchronized so code using a SQLiteQuery from multiple + * threads should perform its own synchronization when using the SQLiteQuery. */ public class SQLiteQuery extends SQLiteProgram { private static final String TAG = "Cursor"; diff --git a/core/java/android/database/sqlite/SQLiteStatement.java b/core/java/android/database/sqlite/SQLiteStatement.java index 98da414..9e425c3 100644 --- a/core/java/android/database/sqlite/SQLiteStatement.java +++ b/core/java/android/database/sqlite/SQLiteStatement.java @@ -18,11 +18,16 @@ package android.database.sqlite; import android.os.SystemClock; +import dalvik.system.BlockGuard; + /** * A pre-compiled statement against a {@link SQLiteDatabase} that can be reused. * The statement cannot return multiple rows, but 1x1 result sets are allowed. * Don't use SQLiteStatement constructor directly, please use * {@link SQLiteDatabase#compileStatement(String)} + * + * SQLiteStatement is not internally synchronized so code using a SQLiteStatement from multiple + * threads should perform its own synchronization when using the SQLiteStatement. */ public class SQLiteStatement extends SQLiteProgram { @@ -44,6 +49,7 @@ public class SQLiteStatement extends SQLiteProgram * some reason */ public void execute() { + BlockGuard.getThreadPolicy().onWriteToDisk(); if (!mDatabase.isOpen()) { throw new IllegalStateException("database " + mDatabase.getPath() + " already closed"); } @@ -70,6 +76,7 @@ public class SQLiteStatement extends SQLiteProgram * some reason */ public long executeInsert() { + BlockGuard.getThreadPolicy().onWriteToDisk(); if (!mDatabase.isOpen()) { throw new IllegalStateException("database " + mDatabase.getPath() + " already closed"); } @@ -96,6 +103,7 @@ public class SQLiteStatement extends SQLiteProgram * @throws android.database.sqlite.SQLiteDoneException if the query returns zero rows */ public long simpleQueryForLong() { + BlockGuard.getThreadPolicy().onReadFromDisk(); if (!mDatabase.isOpen()) { throw new IllegalStateException("database " + mDatabase.getPath() + " already closed"); } @@ -122,6 +130,7 @@ public class SQLiteStatement extends SQLiteProgram * @throws android.database.sqlite.SQLiteDoneException if the query returns zero rows */ public String simpleQueryForString() { + BlockGuard.getThreadPolicy().onReadFromDisk(); if (!mDatabase.isOpen()) { throw new IllegalStateException("database " + mDatabase.getPath() + " already closed"); } diff --git a/core/java/android/hardware/Camera.java b/core/java/android/hardware/Camera.java index 8687a89..7640cc1 100644 --- a/core/java/android/hardware/Camera.java +++ b/core/java/android/hardware/Camera.java @@ -58,7 +58,7 @@ import android.os.Message; public class Camera { private static final String TAG = "Camera"; - // These match the enums in frameworks/base/include/ui/Camera.h + // These match the enums in frameworks/base/include/camera/Camera.h private static final int CAMERA_MSG_ERROR = 0x001; private static final int CAMERA_MSG_SHUTTER = 0x002; private static final int CAMERA_MSG_FOCUS = 0x004; @@ -84,13 +84,71 @@ public class Camera { private boolean mWithBuffer; /** + * Returns the number of Cameras available. + * @hide + */ + public native static int getNumberOfCameras(); + + /** + * Returns the information about the camera. + * If {@link #getNumberOfCameras()} returns N, the valid id is 0 to N-1. + * @hide + */ + public native static void getCameraInfo(int cameraId, CameraInfo cameraInfo); + + /** + * Information about a camera + * @hide + */ + public static class CameraInfo { + public static final int CAMERA_FACING_BACK = 0; + public static final int CAMERA_FACING_FRONT = 1; + + /** + * The direction that the camera faces to. It should be + * CAMERA_FACING_BACK or CAMERA_FACING_FRONT. + */ + public int mFacing; + + /** + * The orientation of the camera image. The value is the angle that the + * camera image needs to be rotated clockwise so it shows correctly on + * the display in its natural orientation. It should be 0, 90, 180, or 270. + * + * For example, suppose a device has a naturally tall screen, but the camera + * sensor is mounted in landscape. If the top side of the camera sensor is + * aligned with the right edge of the display in natural orientation, the + * value should be 90. + * + * @see #setDisplayOrientation(int) + */ + public int mOrientation; + }; + + /** * Returns a new Camera object. + * If {@link #getNumberOfCameras()} returns N, the valid id is 0 to N-1. + * The id 0 is the default camera. + * @hide + */ + public static Camera open(int cameraId) { + return new Camera(cameraId); + } + + /** + * The id for the default camera. + * @hide + */ + public static int CAMERA_ID_DEFAULT = 0; + + /** + * Returns a new Camera object. This returns the default camera. */ public static Camera open() { - return new Camera(); + return new Camera(CAMERA_ID_DEFAULT); } - Camera() { + Camera(int cameraId) { mShutterCallback = null; mRawImageCallback = null; mJpegCallback = null; @@ -107,14 +165,14 @@ public class Camera { mEventHandler = null; } - native_setup(new WeakReference<Camera>(this)); + native_setup(new WeakReference<Camera>(this), cameraId); } protected void finalize() { native_release(); } - private native final void native_setup(Object camera_this); + private native final void native_setup(Object camera_this, int cameraId); private native final void native_release(); @@ -565,6 +623,18 @@ public class Camera { * {@link PreviewCallback#onPreviewFrame}. This method is not allowed to * be called during preview. * + * If you want to make the camera image show in the same orientation as + * the display, you can use <p> + * <pre> + * android.view.Display display; + * android.hardware.Camera.CameraInfo cameraInfo; + * + * int rotation = getWindowManager().getDefaultDisplay().getRotation(); + * android.hardware.Camera.getCameraInfo(id, cameraInfo); + * int degrees = (cameraInfo.mOrientation - rotation + 360) % 360; + * + * setDisplayOrientation(degrees); + * </pre> * @param degrees the angle that the picture will be rotated clockwise. * Valid values are 0, 90, 180, and 270. The starting * position is 0 (landscape). @@ -746,6 +816,9 @@ public class Camera { private static final String KEY_ZOOM_RATIOS = "zoom-ratios"; private static final String KEY_ZOOM_SUPPORTED = "zoom-supported"; private static final String KEY_SMOOTH_ZOOM_SUPPORTED = "smooth-zoom-supported"; + private static final String KEY_FOCUS_DISTANCES = "focus-distances"; + private static final String KEY_METERING_MODE = "metering-mode"; + // Parameter key suffix for supported values. private static final String SUPPORTED_VALUES_SUFFIX = "-values"; @@ -807,21 +880,81 @@ public class Camera { */ public static final String FLASH_MODE_TORCH = "torch"; - // Values for scene mode settings. + /** + * Scene mode is off. + */ public static final String SCENE_MODE_AUTO = "auto"; + + /** + * Take photos of fast moving objects. Same as {@link + * #SCENE_MODE_SPORTS}. + */ public static final String SCENE_MODE_ACTION = "action"; + + /** + * Take people pictures. + */ public static final String SCENE_MODE_PORTRAIT = "portrait"; + + /** + * Take pictures on distant objects. + */ public static final String SCENE_MODE_LANDSCAPE = "landscape"; + + /** + * Take photos at night. + */ public static final String SCENE_MODE_NIGHT = "night"; + + /** + * Take people pictures at night. + */ public static final String SCENE_MODE_NIGHT_PORTRAIT = "night-portrait"; + + /** + * Take photos in a theater. Flash light is off. + */ public static final String SCENE_MODE_THEATRE = "theatre"; + + /** + * Take pictures on the beach. + */ public static final String SCENE_MODE_BEACH = "beach"; + + /** + * Take pictures on the snow. + */ public static final String SCENE_MODE_SNOW = "snow"; + + /** + * Take sunset photos. + */ public static final String SCENE_MODE_SUNSET = "sunset"; + + /** + * Avoid blurry pictures (for example, due to hand shake). + */ public static final String SCENE_MODE_STEADYPHOTO = "steadyphoto"; + + /** + * For shooting firework displays. + */ public static final String SCENE_MODE_FIREWORKS = "fireworks"; + + /** + * Take photos of fast moving objects. Same as {@link + * #SCENE_MODE_ACTION}. + */ public static final String SCENE_MODE_SPORTS = "sports"; + + /** + * Take indoor low-light shot. + */ public static final String SCENE_MODE_PARTY = "party"; + + /** + * Capture the naturally warm color of scenes lit by candles. + */ public static final String SCENE_MODE_CANDLELIGHT = "candlelight"; /** @@ -858,6 +991,53 @@ public class Camera { */ public static final String FOCUS_MODE_EDOF = "edof"; + // Indices for focus distance array. + /** + * The array index of near focus distance for use with + * {@link #getFocusDistances(float[])}. + */ + public static final int FOCUS_DISTANCE_NEAR_INDEX = 0; + + /** + * The array index of optimal focus distance for use with + * {@link #getFocusDistances(float[])}. + */ + public static final int FOCUS_DISTANCE_OPTIMAL_INDEX = 1; + + /** + * The array index of far focus distance for use with + * {@link #getFocusDistances(float[])}. + */ + public static final int FOCUS_DISTANCE_FAR_INDEX = 2; + + /** + * Continuous focus mode. The camera continuously tries to focus. This + * is ideal for shooting video or shooting photo of moving object. + * Continuous focus starts when {@link #autoFocus(AutoFocusCallback)} is + * called. Continuous focus stops when {@link #cancelAutoFocus()} is + * called. AutoFocusCallback will be only called once as soon as the + * picture is in focus. + */ + public static final String FOCUS_MODE_CONTINUOUS = "continuous"; + + /** + * The camera determines the exposure by giving more weight to the + * central part of the scene. + */ + public static final String METERING_MODE_CENTER_WEIGHTED = "center-weighted"; + + /** + * The camera determines the exposure by averaging the entire scene, + * giving no weighting to any particular area. + */ + public static final String METERING_MODE_FRAME_AVERAGE = "frame-average"; + + /** + * The camera determines the exposure by a very small area of the scene, + * typically the center. + */ + public static final String METERING_MODE_SPOT = "spot"; + // Formats for setPreviewFormat and setPictureFormat. private static final String PIXEL_FORMAT_YUV422SP = "yuv422sp"; private static final String PIXEL_FORMAT_YUV420SP = "yuv420sp"; @@ -1788,6 +1968,76 @@ public class Camera { return TRUE.equals(str); } + /** + * Gets the distances from the camera to where an object appears to be + * in focus. The object is sharpest at the optimal focus distance. The + * depth of field is the far focus distance minus near focus distance. + * + * Focus distances may change after calling {@link + * #autoFocus(AutoFocusCallback)}, {@link #cancelAutoFocus}, or {@link + * #startPreview()}. Applications can call {@link #getParameters()} + * and this method anytime to get the latest focus distances. If the + * focus mode is FOCUS_MODE_CONTINUOUS and autofocus has started, focus + * distances may change from time to time. + * + * Far focus distance >= optimal focus distance >= near focus distance. + * If the focus distance is infinity, the value will be + * Float.POSITIVE_INFINITY. + * + * @param output focus distances in meters. output must be a float + * array with three elements. Near focus distance, optimal focus + * distance, and far focus distance will be filled in the array. + * @see #FOCUS_DISTANCE_NEAR_INDEX + * @see #FOCUS_DISTANCE_OPTIMAL_INDEX + * @see #FOCUS_DISTANCE_FAR_INDEX + */ + public void getFocusDistances(float[] output) { + if (output == null || output.length != 3) { + throw new IllegalArgumentException( + "output must be an float array with three elements."); + } + List<Float> distances = splitFloat(get(KEY_FOCUS_DISTANCES)); + output[0] = distances.get(0); + output[1] = distances.get(1); + output[2] = distances.get(2); + } + + /** + * Gets the supported metering modes. + * + * @return a list of supported metering modes. null if metering mode + * setting is not supported. + * @see #getMeteringMode() + */ + public List<String> getSupportedMeteringModes() { + String str = get(KEY_METERING_MODE + SUPPORTED_VALUES_SUFFIX); + return split(str); + } + + /** + * Gets the current metering mode, which affects how camera determines + * exposure. + * + * @return current metering mode. If the camera does not support + * metering setting, this should return null. + * @see #METERING_MODE_CENTER_WEIGHTED + * @see #METERING_MODE_FRAME_AVERAGE + * @see #METERING_MODE_SPOT + */ + public String getMeteringMode() { + return get(KEY_METERING_MODE); + } + + /** + * Sets the metering mode. + * + * @param value metering mode. + * @see #getMeteringMode() + */ + public void setMeteringMode(String value) { + set(KEY_METERING_MODE, value); + } + // Splits a comma delimited string to an ArrayList of String. // Return null if the passing string is null or the size is 0. private ArrayList<String> split(String str) { @@ -1817,6 +2067,21 @@ public class Camera { return substrings; } + // Splits a comma delimited string to an ArrayList of Float. + // Return null if the passing string is null or the size is 0. + private ArrayList<Float> splitFloat(String str) { + if (str == null) return null; + + StringTokenizer tokenizer = new StringTokenizer(str, ","); + ArrayList<Float> substrings = new ArrayList<Float>(); + while (tokenizer.hasMoreElements()) { + String token = tokenizer.nextToken(); + substrings.add(Float.parseFloat(token)); + } + if (substrings.size() == 0) return null; + return substrings; + } + // Returns the value of a float parameter. private float getFloat(String key, float defaultValue) { try { diff --git a/core/java/android/inputmethodservice/IInputMethodSessionWrapper.java b/core/java/android/inputmethodservice/IInputMethodSessionWrapper.java index 80e9865..44f30f7 100644 --- a/core/java/android/inputmethodservice/IInputMethodSessionWrapper.java +++ b/core/java/android/inputmethodservice/IInputMethodSessionWrapper.java @@ -47,9 +47,10 @@ class IInputMethodSessionWrapper extends IInputMethodSession.Stub private static final int DO_UPDATE_CURSOR = 95; private static final int DO_APP_PRIVATE_COMMAND = 100; private static final int DO_TOGGLE_SOFT_INPUT = 105; - - final HandlerCaller mCaller; - final InputMethodSession mInputMethodSession; + private static final int DO_FINISH_SESSION = 110; + + HandlerCaller mCaller; + InputMethodSession mInputMethodSession; // NOTE: we should have a cache of these. static class InputMethodEventCallbackWrapper implements InputMethodSession.EventCallback { @@ -127,6 +128,10 @@ class IInputMethodSessionWrapper extends IInputMethodSession.Stub mInputMethodSession.toggleSoftInput(msg.arg1, msg.arg2); return; } + case DO_FINISH_SESSION: { + mInputMethodSession = null; + return; + } } Log.w(TAG, "Unhandled message code: " + msg.what); } @@ -174,4 +179,8 @@ class IInputMethodSessionWrapper extends IInputMethodSession.Stub public void toggleSoftInput(int showFlags, int hideFlags) { mCaller.executeOrSendMessage(mCaller.obtainMessageII(DO_TOGGLE_SOFT_INPUT, showFlags, hideFlags)); } + + public void finishSession() { + mCaller.executeOrSendMessage(mCaller.obtainMessage(DO_FINISH_SESSION)); + } } diff --git a/core/java/android/inputmethodservice/IInputMethodWrapper.java b/core/java/android/inputmethodservice/IInputMethodWrapper.java index bfa82ee..35fd46f 100644 --- a/core/java/android/inputmethodservice/IInputMethodWrapper.java +++ b/core/java/android/inputmethodservice/IInputMethodWrapper.java @@ -39,6 +39,7 @@ import android.view.inputmethod.InputMethodSession; import java.io.FileDescriptor; import java.io.PrintWriter; +import java.lang.ref.WeakReference; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; @@ -64,9 +65,9 @@ class IInputMethodWrapper extends IInputMethod.Stub private static final int DO_SHOW_SOFT_INPUT = 60; private static final int DO_HIDE_SOFT_INPUT = 70; - final AbstractInputMethodService mTarget; + final WeakReference<AbstractInputMethodService> mTarget; final HandlerCaller mCaller; - final InputMethod mInputMethod; + final WeakReference<InputMethod> mInputMethod; static class Notifier { boolean notified; @@ -96,21 +97,32 @@ class IInputMethodWrapper extends IInputMethod.Stub public IInputMethodWrapper(AbstractInputMethodService context, InputMethod inputMethod) { - mTarget = context; - mCaller = new HandlerCaller(context, this); - mInputMethod = inputMethod; + mTarget = new WeakReference<AbstractInputMethodService>(context); + mCaller = new HandlerCaller(context.getApplicationContext(), this); + mInputMethod = new WeakReference<InputMethod>(inputMethod); } public InputMethod getInternalInputMethod() { - return mInputMethod; + return mInputMethod.get(); } public void executeMessage(Message msg) { + InputMethod inputMethod = mInputMethod.get(); + // Need a valid reference to the inputMethod for everything except a dump. + if (inputMethod == null && msg.what != DO_DUMP) { + Log.w(TAG, "Input method reference was null, ignoring message: " + msg.what); + return; + } + switch (msg.what) { case DO_DUMP: { + AbstractInputMethodService target = mTarget.get(); + if (target == null) { + return; + } HandlerCaller.SomeArgs args = (HandlerCaller.SomeArgs)msg.obj; try { - mTarget.dump((FileDescriptor)args.arg1, + target.dump((FileDescriptor)args.arg1, (PrintWriter)args.arg2, (String[])args.arg3); } catch (RuntimeException e) { ((PrintWriter)args.arg2).println("Exception: " + e); @@ -122,22 +134,22 @@ class IInputMethodWrapper extends IInputMethod.Stub } case DO_ATTACH_TOKEN: { - mInputMethod.attachToken((IBinder)msg.obj); + inputMethod.attachToken((IBinder)msg.obj); return; } case DO_SET_INPUT_CONTEXT: { - mInputMethod.bindInput((InputBinding)msg.obj); + inputMethod.bindInput((InputBinding)msg.obj); return; } case DO_UNSET_INPUT_CONTEXT: - mInputMethod.unbindInput(); + inputMethod.unbindInput(); return; case DO_START_INPUT: { HandlerCaller.SomeArgs args = (HandlerCaller.SomeArgs)msg.obj; IInputContext inputContext = (IInputContext)args.arg1; InputConnection ic = inputContext != null ? new InputConnectionWrapper(inputContext) : null; - mInputMethod.startInput(ic, (EditorInfo)args.arg2); + inputMethod.startInput(ic, (EditorInfo)args.arg2); return; } case DO_RESTART_INPUT: { @@ -145,33 +157,37 @@ class IInputMethodWrapper extends IInputMethod.Stub IInputContext inputContext = (IInputContext)args.arg1; InputConnection ic = inputContext != null ? new InputConnectionWrapper(inputContext) : null; - mInputMethod.restartInput(ic, (EditorInfo)args.arg2); + inputMethod.restartInput(ic, (EditorInfo)args.arg2); return; } case DO_CREATE_SESSION: { - mInputMethod.createSession(new InputMethodSessionCallbackWrapper( + inputMethod.createSession(new InputMethodSessionCallbackWrapper( mCaller.mContext, (IInputMethodCallback)msg.obj)); return; } case DO_SET_SESSION_ENABLED: - mInputMethod.setSessionEnabled((InputMethodSession)msg.obj, + inputMethod.setSessionEnabled((InputMethodSession)msg.obj, msg.arg1 != 0); return; case DO_REVOKE_SESSION: - mInputMethod.revokeSession((InputMethodSession)msg.obj); + inputMethod.revokeSession((InputMethodSession)msg.obj); return; case DO_SHOW_SOFT_INPUT: - mInputMethod.showSoftInput(msg.arg1, (ResultReceiver)msg.obj); + inputMethod.showSoftInput(msg.arg1, (ResultReceiver)msg.obj); return; case DO_HIDE_SOFT_INPUT: - mInputMethod.hideSoftInput(msg.arg1, (ResultReceiver)msg.obj); + inputMethod.hideSoftInput(msg.arg1, (ResultReceiver)msg.obj); return; } Log.w(TAG, "Unhandled message code: " + msg.what); } @Override protected void dump(FileDescriptor fd, PrintWriter fout, String[] args) { - if (mTarget.checkCallingOrSelfPermission(android.Manifest.permission.DUMP) + AbstractInputMethodService target = mTarget.get(); + if (target == null) { + return; + } + if (target.checkCallingOrSelfPermission(android.Manifest.permission.DUMP) != PackageManager.PERMISSION_GRANTED) { fout.println("Permission Denial: can't dump InputMethodManager from from pid=" diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java index 8c8d3e5..1a261d3 100644 --- a/core/java/android/inputmethodservice/InputMethodService.java +++ b/core/java/android/inputmethodservice/InputMethodService.java @@ -1988,15 +1988,19 @@ public class InputMethodService extends AbstractInputMethodService { ei.inputType != InputType.TYPE_NULL); if (hasAction) { mExtractAccessories.setVisibility(View.VISIBLE); - if (ei.actionLabel != null) { - mExtractAction.setText(ei.actionLabel); - } else { - mExtractAction.setText(getTextForImeAction(ei.imeOptions)); + if (mExtractAction != null) { + if (ei.actionLabel != null) { + mExtractAction.setText(ei.actionLabel); + } else { + mExtractAction.setText(getTextForImeAction(ei.imeOptions)); + } + mExtractAction.setOnClickListener(mActionClickListener); } - mExtractAction.setOnClickListener(mActionClickListener); } else { mExtractAccessories.setVisibility(View.GONE); - mExtractAction.setOnClickListener(null); + if (mExtractAction != null) { + mExtractAction.setOnClickListener(null); + } } } diff --git a/core/java/android/net/MobileDataStateTracker.java b/core/java/android/net/MobileDataStateTracker.java index 98f32b3..214510d 100644 --- a/core/java/android/net/MobileDataStateTracker.java +++ b/core/java/android/net/MobileDataStateTracker.java @@ -310,6 +310,9 @@ public class MobileDataStateTracker extends NetworkStateTracker { case TelephonyManager.NETWORK_TYPE_EVDO_A: networkTypeStr = "evdo"; break; + case TelephonyManager.NETWORK_TYPE_EVDO_B: + networkTypeStr = "evdo"; + break; } return "net.tcp.buffersize." + networkTypeStr; } diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java index d114bff..4adeaeb 100644 --- a/core/java/android/os/BatteryStats.java +++ b/core/java/android/os/BatteryStats.java @@ -51,50 +51,36 @@ public abstract class BatteryStats implements Parcelable { /** * A constant indicating a sensor timer. - * - * {@hide} */ public static final int SENSOR = 3; /** * A constant indicating a a wifi turn on timer - * - * {@hide} */ public static final int WIFI_TURNED_ON = 4; /** * A constant indicating a full wifi lock timer - * - * {@hide} */ public static final int FULL_WIFI_LOCK = 5; /** * A constant indicating a scan wifi lock timer - * - * {@hide} */ public static final int SCAN_WIFI_LOCK = 6; /** * A constant indicating a wifi multicast timer - * - * {@hide} */ public static final int WIFI_MULTICAST_ENABLED = 7; /** * A constant indicating an audio turn on timer - * - * {@hide} */ public static final int AUDIO_TURNED_ON = 7; /** * A constant indicating a video turn on timer - * - * {@hide} */ public static final int VIDEO_TURNED_ON = 8; @@ -391,6 +377,61 @@ public abstract class BatteryStats implements Parcelable { } } + public final class BatteryHistoryRecord implements Parcelable { + public BatteryHistoryRecord next; + + public long time; + public byte batteryLevel; + + public static final int STATE_SCREEN_MASK = 0x000000f; + public static final int STATE_SCREEN_SHIFT = 0; + public static final int STATE_SIGNAL_STRENGTH_MASK = 0x00000f0; + public static final int STATE_SIGNAL_STRENGTH_SHIFT = 4; + public static final int STATE_PHONE_STATE_MASK = 0x0000f00; + public static final int STATE_PHONE_STATE_SHIFT = 8; + public static final int STATE_DATA_CONNECTION_MASK = 0x000f000; + public static final int STATE_DATA_CONNECTION_SHIFT = 12; + + public static final int STATE_BATTERY_PLUGGED_FLAG = 1<<30; + public static final int STATE_SCREEN_ON_FLAG = 1<<29; + public static final int STATE_GPS_ON_FLAG = 1<<28; + public static final int STATE_PHONE_ON_FLAG = 1<<27; + public static final int STATE_WIFI_ON_FLAG = 1<<26; + public static final int STATE_WIFI_RUNNING_FLAG = 1<<25; + public static final int STATE_WIFI_FULL_LOCK_FLAG = 1<<24; + public static final int STATE_WIFI_SCAN_LOCK_FLAG = 1<<23; + public static final int STATE_WIFI_MULTICAST_ON_FLAG = 1<<22; + public static final int STATE_BLUETOOTH_ON_FLAG = 1<<21; + public static final int STATE_AUDIO_ON_FLAG = 1<<20; + public static final int STATE_VIDEO_ON_FLAG = 1<<19; + + public int states; + + public BatteryHistoryRecord() { + } + + public BatteryHistoryRecord(long time, Parcel src) { + this.time = time; + batteryLevel = (byte)src.readInt(); + states = src.readInt(); + } + + public int describeContents() { + return 0; + } + + public void writeToParcel(Parcel dest, int flags) { + dest.writeLong(time); + dest.writeInt(batteryLevel); + dest.writeInt(states); + } + } + + /** + * Return the current history of battery state changes. + */ + public abstract BatteryHistoryRecord getHistory(); + /** * Returns the number of times the device has been started. */ @@ -1443,6 +1484,20 @@ public abstract class BatteryStats implements Parcelable { */ @SuppressWarnings("unused") public void dumpLocked(PrintWriter pw) { + BatteryHistoryRecord rec = getHistory(); + if (rec != null) { + pw.println("Battery History:"); + while (rec != null) { + pw.print(" "); + pw.print(rec.time); + pw.print(" "); + pw.print(rec.batteryLevel); + pw.print(" "); + pw.println(Integer.toHexString(rec.states)); + rec = rec.next; + } + } + pw.println("Total Statistics (Current and Historic):"); pw.println(" System starts: " + getStartCount() + ", currently on battery: " + getIsOnBattery()); diff --git a/core/java/android/os/Build.java b/core/java/android/os/Build.java index 3e9fd42..9d1a634 100644 --- a/core/java/android/os/Build.java +++ b/core/java/android/os/Build.java @@ -180,6 +180,10 @@ public class Build { public static final int ECLAIR_MR1 = 7; public static final int FROYO = 8; + + public static final int KRAKEN = CUR_DEVELOPMENT; + + public static final int GINGERBREAD = CUR_DEVELOPMENT; } /** The type of build, like "user" or "eng". */ diff --git a/core/java/android/os/Environment.java b/core/java/android/os/Environment.java index 812391c..c7cbed6 100644 --- a/core/java/android/os/Environment.java +++ b/core/java/android/os/Environment.java @@ -28,6 +28,8 @@ public class Environment { private static final File ROOT_DIRECTORY = getDirectory("ANDROID_ROOT", "/system"); + private static final String SYSTEM_PROPERTY_EFS_ENABLED = "persist.security.efs.enabled"; + private static IMountService mMntSvc = null; /** @@ -37,9 +39,55 @@ public class Environment { return ROOT_DIRECTORY; } + /** + * Gets the system directory available for secure storage. + * If Encrypted File system is enabled, it returns an encrypted directory (/data/secure/system). + * Otherwise, it returns the unencrypted /data/system directory. + * @return File object representing the secure storage system directory. + * @hide + */ + public static File getSystemSecureDirectory() { + if (isEncryptedFilesystemEnabled()) { + return new File(SECURE_DATA_DIRECTORY, "system"); + } else { + return new File(DATA_DIRECTORY, "system"); + } + } + + /** + * Gets the data directory for secure storage. + * If Encrypted File system is enabled, it returns an encrypted directory (/data/secure). + * Otherwise, it returns the unencrypted /data directory. + * @return File object representing the data directory for secure storage. + * @hide + */ + public static File getSecureDataDirectory() { + if (isEncryptedFilesystemEnabled()) { + return SECURE_DATA_DIRECTORY; + } else { + return DATA_DIRECTORY; + } + } + + /** + * Returns whether the Encrypted File System feature is enabled on the device or not. + * @return <code>true</code> if Encrypted File System feature is enabled, <code>false</code> + * if disabled. + * @hide + */ + public static boolean isEncryptedFilesystemEnabled() { + return SystemProperties.getBoolean(SYSTEM_PROPERTY_EFS_ENABLED, false); + } + private static final File DATA_DIRECTORY = getDirectory("ANDROID_DATA", "/data"); + /** + * @hide + */ + private static final File SECURE_DATA_DIRECTORY + = getDirectory("ANDROID_SECURE_DATA", "/data/secure"); + private static final File EXTERNAL_STORAGE_DIRECTORY = getDirectory("EXTERNAL_STORAGE", "/sdcard"); diff --git a/core/java/android/os/MessageQueue.java b/core/java/android/os/MessageQueue.java index bc653d6..d394a46 100644 --- a/core/java/android/os/MessageQueue.java +++ b/core/java/android/os/MessageQueue.java @@ -36,8 +36,9 @@ public class MessageQueue { Message mMessages; private final ArrayList mIdleHandlers = new ArrayList(); private boolean mQuiting = false; + private int mObject = 0; // used by native code boolean mQuitAllowed = true; - + /** * Callback interface for discovering when a thread is going to block * waiting for more messages. @@ -85,16 +86,49 @@ public class MessageQueue { } } + // Add an input pipe to the set being selected over. If token is + // negative, remove 'handler's entry from the current set and forget + // about it. + void setInputToken(int token, int region, Handler handler) { + if (token >= 0) nativeRegisterInputStream(token, region, handler); + else nativeUnregisterInputStream(token); + } + MessageQueue() { + nativeInit(); } + private native void nativeInit(); + + /** + * @param token fd of the readable end of the input stream + * @param region fd of the ashmem region used for data transport alongside the 'token' fd + * @param handler Handler from which to make input messages based on data read from the fd + */ + private native void nativeRegisterInputStream(int token, int region, Handler handler); + private native void nativeUnregisterInputStream(int token); + private native void nativeSignal(); + + /** + * Wait until the designated time for new messages to arrive. + * + * @param when Timestamp in SystemClock.uptimeMillis() base of the next message in the queue. + * If 'when' is zero, the method will check for incoming messages without blocking. If + * 'when' is negative, the method will block forever waiting for the next message. + * @return + */ + private native int nativeWaitForNext(long when); final Message next() { boolean tryIdle = true; + // when we start out, we'll just touch the input pipes and then go from there + long timeToNextEventMillis = 0; while (true) { long now; Object[] idlers = null; - + + nativeWaitForNext(timeToNextEventMillis); + // Try to retrieve the next message, returning if found. synchronized (this) { now = SystemClock.uptimeMillis(); @@ -135,20 +169,17 @@ public class MessageQueue { synchronized (this) { // No messages, nobody to tell about it... time to wait! - try { - if (mMessages != null) { - if (mMessages.when-now > 0) { - Binder.flushPendingCommands(); - this.wait(mMessages.when-now); - } - } else { + if (mMessages != null) { + if (mMessages.when - now > 0) { Binder.flushPendingCommands(); - this.wait(); + timeToNextEventMillis = mMessages.when - now; } - } - catch (InterruptedException e) { + } else { + Binder.flushPendingCommands(); + timeToNextEventMillis = -1; } } + // loop to the while(true) and do the appropriate nativeWait(when) } } @@ -190,7 +221,6 @@ public class MessageQueue { if (p == null || when == 0 || when < p.when) { msg.next = p; mMessages = msg; - this.notify(); } else { Message prev = null; while (p != null && p.when <= when) { @@ -199,8 +229,8 @@ public class MessageQueue { } msg.next = prev.next; prev.next = msg; - this.notify(); } + nativeSignal(); } return true; } @@ -321,7 +351,7 @@ public class MessageQueue { void poke() { synchronized (this) { - this.notify(); + nativeSignal(); } } } diff --git a/core/java/android/os/ParcelFileDescriptor.java b/core/java/android/os/ParcelFileDescriptor.java index 0a3b2cf..d26f066 100644 --- a/core/java/android/os/ParcelFileDescriptor.java +++ b/core/java/android/os/ParcelFileDescriptor.java @@ -179,7 +179,7 @@ public class ParcelFileDescriptor implements Parcelable { /** * An InputStream you can create on a ParcelFileDescriptor, which will * take care of calling {@link ParcelFileDescriptor#close - * ParcelFileDescritor.close()} for you when the stream is closed. + * ParcelFileDescriptor.close()} for you when the stream is closed. */ public static class AutoCloseInputStream extends FileInputStream { private final ParcelFileDescriptor mFd; @@ -198,7 +198,7 @@ public class ParcelFileDescriptor implements Parcelable { /** * An OutputStream you can create on a ParcelFileDescriptor, which will * take care of calling {@link ParcelFileDescriptor#close - * ParcelFileDescritor.close()} for you when the stream is closed. + * ParcelFileDescriptor.close()} for you when the stream is closed. */ public static class AutoCloseOutputStream extends FileOutputStream { private final ParcelFileDescriptor mFd; diff --git a/core/java/android/os/RecoverySystem.java b/core/java/android/os/RecoverySystem.java index b6dc1b5..5fea6fe 100644 --- a/core/java/android/os/RecoverySystem.java +++ b/core/java/android/os/RecoverySystem.java @@ -348,6 +348,23 @@ public class RecoverySystem { } /** + * Reboot into the recovery system to wipe the /data partition and toggle + * Encrypted File Systems on/off. + * @param extras to add to the RECOVERY_COMPLETED intent after rebooting. + * @throws IOException if something goes wrong. + * + * @hide + */ + public static void rebootToggleEFS(Context context, boolean efsEnabled) + throws IOException { + if (efsEnabled) { + bootCommand(context, "--set_encrypted_filesystem=on"); + } else { + bootCommand(context, "--set_encrypted_filesystem=off"); + } + } + + /** * Reboot into the recovery system with the supplied argument. * @param arg to pass to the recovery utility. * @throws IOException if something goes wrong. diff --git a/core/java/android/os/StrictMode.java b/core/java/android/os/StrictMode.java new file mode 100644 index 0000000..876ec39 --- /dev/null +++ b/core/java/android/os/StrictMode.java @@ -0,0 +1,219 @@ +/* + * Copyright (C) 2010 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.os; + +import android.app.ActivityManagerNative; +import android.app.ApplicationErrorReport; +import android.util.Log; + +import com.android.internal.os.RuntimeInit; + +import dalvik.system.BlockGuard; + +/** + * <p>StrictMode lets you impose stricter rules under which your + * application runs.</p> + */ +public final class StrictMode { + private static final String TAG = "StrictMode"; + + private StrictMode() {} + + public static final int DISALLOW_DISK_WRITE = 0x01; + public static final int DISALLOW_DISK_READ = 0x02; + public static final int DISALLOW_NETWORK = 0x04; + + /** @hide */ + public static final int DISALLOW_MASK = + DISALLOW_DISK_WRITE | DISALLOW_DISK_READ | DISALLOW_NETWORK; + + /** + * Flag to log to the system log. + */ + public static final int PENALTY_LOG = 0x10; // normal android.util.Log + + /** + * Show an annoying dialog to the user. Will be rate-limited to be only + * a little annoying. + */ + public static final int PENALTY_DIALOG = 0x20; + + /** + * Crash hard if policy is violated. + */ + public static final int PENALTY_DEATH = 0x40; + + /** + * Log a stacktrace to the DropBox on policy violation. + */ + public static final int PENALTY_DROPBOX = 0x80; + + /** @hide */ + public static final int PENALTY_MASK = + PENALTY_LOG | PENALTY_DIALOG | + PENALTY_DROPBOX | PENALTY_DEATH; + + /** + * Sets the policy for what actions the current thread is denied, + * as well as the penalty for violating the policy. + * + * @param policyMask a bitmask of DISALLOW_* and PENALTY_* values. + */ + public static void setThreadBlockingPolicy(final int policyMask) { + BlockGuard.Policy policy = BlockGuard.getThreadPolicy(); + if (!(policy instanceof AndroidBlockGuardPolicy)) { + BlockGuard.setThreadPolicy(new AndroidBlockGuardPolicy(policyMask)); + } else { + AndroidBlockGuardPolicy androidPolicy = (AndroidBlockGuardPolicy) policy; + androidPolicy.setPolicyMask(policyMask); + } + } + + /** + * Returns the bitmask of the current thread's blocking policy. + * + * @return the bitmask of all the DISALLOW_* and PENALTY_* bits currently enabled + */ + public static int getThreadBlockingPolicy() { + return BlockGuard.getThreadPolicy().getPolicyMask(); + } + + /** @hide */ + public static void setDropBoxManager(DropBoxManager dropBoxManager) { + BlockGuard.Policy policy = BlockGuard.getThreadPolicy(); + if (!(policy instanceof AndroidBlockGuardPolicy)) { + policy = new AndroidBlockGuardPolicy(0); + BlockGuard.setThreadPolicy(policy); + } + ((AndroidBlockGuardPolicy) policy).setDropBoxManager(dropBoxManager); + } + + private static class AndroidBlockGuardPolicy implements BlockGuard.Policy { + private int mPolicyMask; + private DropBoxManager mDropBoxManager = null; + + public AndroidBlockGuardPolicy(final int policyMask) { + mPolicyMask = policyMask; + } + + // Part of BlockGuard.Policy interface: + public int getPolicyMask() { + return mPolicyMask; + } + + // Part of BlockGuard.Policy interface: + public void onWriteToDisk() { + if ((mPolicyMask & DISALLOW_DISK_WRITE) == 0) { + return; + } + handleViolation(DISALLOW_DISK_WRITE); + } + + // Part of BlockGuard.Policy interface: + public void onReadFromDisk() { + if ((mPolicyMask & DISALLOW_DISK_READ) == 0) { + return; + } + handleViolation(DISALLOW_DISK_READ); + } + + // Part of BlockGuard.Policy interface: + public void onNetwork() { + if ((mPolicyMask & DISALLOW_NETWORK) == 0) { + return; + } + handleViolation(DISALLOW_NETWORK); + } + + public void setPolicyMask(int policyMask) { + mPolicyMask = policyMask; + } + + public void setDropBoxManager(DropBoxManager dropBoxManager) { + mDropBoxManager = dropBoxManager; + } + + private void handleViolation(int violationBit) { + final BlockGuard.BlockGuardPolicyException violation = + new BlockGuard.BlockGuardPolicyException(mPolicyMask, violationBit); + violation.fillInStackTrace(); + + Looper looper = Looper.myLooper(); + if (looper == null) { + // Without a Looper, we're unable to time how long the + // violation takes place. This case should be rare, + // as most users will care about timing violations + // that happen on their main UI thread. + handleViolationWithTime(violation, -1L /* no time */); + } else { + MessageQueue queue = Looper.myQueue(); + final long violationTime = SystemClock.uptimeMillis(); + queue.addIdleHandler(new MessageQueue.IdleHandler() { + public boolean queueIdle() { + long afterViolationTime = SystemClock.uptimeMillis(); + handleViolationWithTime(violation, afterViolationTime - violationTime); + return false; // remove this idle handler from the array + } + }); + } + } + + private void handleViolationWithTime( + BlockGuard.BlockGuardPolicyException violation, + long durationMillis) { + + // It's possible (even quite likely) that mPolicyMask has + // changed from the time the violation fired and now + // (after the violating code ran) due to people who + // push/pop temporary policy in regions of code. So use + // the old policy here. + int policy = violation.getPolicy(); + + if ((policy & PENALTY_LOG) != 0) { + if (durationMillis != -1) { + Log.d(TAG, "StrictMode policy violation; ~duration=" + durationMillis + " ms", + violation); + } else { + Log.d(TAG, "StrictMode policy violation.", violation); + } + } + + if ((policy & PENALTY_DIALOG) != 0) { + // Currently this is just used for the dialog. + try { + ActivityManagerNative.getDefault().handleApplicationStrictModeViolation( + RuntimeInit.getApplicationObject(), + new ApplicationErrorReport.CrashInfo(violation)); + } catch (RemoteException e) { + Log.e(TAG, "RemoteException trying to open strict mode dialog", e); + } + } + + if ((policy & PENALTY_DROPBOX) != 0) { + // TODO: call into ActivityManagerNative to do the dropboxing. + // But do the first-layer signature dup-checking first client-side. + // This conditional should be combined with the above, too, along + // with PENALTY_DEATH below. + } + + if ((policy & PENALTY_DEATH) != 0) { + System.err.println("StrictMode policy violation with POLICY_DEATH; shutting down."); + Process.killProcess(Process.myPid()); + System.exit(10); + } + } + } +} diff --git a/core/java/android/pim/vcard/VCardBuilder.java b/core/java/android/pim/vcard/VCardBuilder.java index 0a6415d..1da6d7a 100644 --- a/core/java/android/pim/vcard/VCardBuilder.java +++ b/core/java/android/pim/vcard/VCardBuilder.java @@ -642,22 +642,18 @@ public class VCardBuilder { if (TextUtils.isEmpty(phoneNumber)) { continue; } - int type = (typeAsObject != null ? typeAsObject : DEFAULT_PHONE_TYPE); - if (type == Phone.TYPE_PAGER) { + + // PAGER number needs unformatted "phone number". + final int type = (typeAsObject != null ? typeAsObject : DEFAULT_PHONE_TYPE); + if (type == Phone.TYPE_PAGER || + VCardConfig.refrainPhoneNumberFormatting(mVCardType)) { phoneLineExists = true; if (!phoneSet.contains(phoneNumber)) { phoneSet.add(phoneNumber); appendTelLine(type, label, phoneNumber, isPrimary); } } else { - // The entry "may" have several phone numbers when the contact entry is - // corrupted because of its original source. - // - // e.g. I encountered the entry like the following. - // "111-222-3333 (Miami)\n444-555-6666 (Broward; 305-653-6796 (Miami); ..." - // This kind of entry is not able to be inserted via Android devices, but - // possible if the source of the data is already corrupted. - List<String> phoneNumberList = splitIfSeveralPhoneNumbersExist(phoneNumber); + final List<String> phoneNumberList = splitAndTrimPhoneNumbers(phoneNumber); if (phoneNumberList.isEmpty()) { continue; } @@ -670,7 +666,7 @@ public class VCardBuilder { phoneSet.add(actualPhoneNumber); appendTelLine(type, label, formattedPhoneNumber, isPrimary); } - } + } // for (String actualPhoneNumber : phoneNumberList) { } } } @@ -682,15 +678,38 @@ public class VCardBuilder { return this; } - private List<String> splitIfSeveralPhoneNumbersExist(final String phoneNumber) { - List<String> phoneList = new ArrayList<String>(); + /** + * <p> + * Splits a given string expressing phone numbers into several strings, and remove + * unnecessary characters inside them. The size of a returned list becomes 1 when + * no split is needed. + * </p> + * <p> + * The given number "may" have several phone numbers when the contact entry is corrupted + * because of its original source. + * e.g. "111-222-3333 (Miami)\n444-555-6666 (Broward; 305-653-6796 (Miami)" + * </p> + * <p> + * This kind of "phone numbers" will not be created with Android vCard implementation, + * but we may encounter them if the source of the input data has already corrupted + * implementation. + * </p> + * <p> + * To handle this case, this method first splits its input into multiple parts + * (e.g. "111-222-3333 (Miami)", "444-555-6666 (Broward", and 305653-6796 (Miami)") and + * removes unnecessary strings like "(Miami)". + * </p> + * <p> + * Do not call this method when trimming is inappropriate for its receivers. + * </p> + */ + private List<String> splitAndTrimPhoneNumbers(final String phoneNumber) { + final List<String> phoneList = new ArrayList<String>(); StringBuilder builder = new StringBuilder(); final int length = phoneNumber.length(); for (int i = 0; i < length; i++) { final char ch = phoneNumber.charAt(i); - // TODO: add a test case for string with '+', and care the other possible issues - // which may happen by ignoring non-digits other than '+'. if (Character.isDigit(ch) || ch == '+') { builder.append(ch); } else if ((ch == ';' || ch == '\n') && builder.length() > 0) { diff --git a/core/java/android/pim/vcard/VCardConfig.java b/core/java/android/pim/vcard/VCardConfig.java index 3409be6..8219840 100644 --- a/core/java/android/pim/vcard/VCardConfig.java +++ b/core/java/android/pim/vcard/VCardConfig.java @@ -15,6 +15,7 @@ */ package android.pim.vcard; +import android.telephony.PhoneNumberUtils; import android.util.Log; import java.util.HashMap; @@ -190,6 +191,30 @@ public class VCardConfig { */ public static final int FLAG_REFRAIN_IMAGE_EXPORT = 0x02000000; + /** + * <P> + * The flag indicating the vCard composer does touch nothing toward phone number Strings + * but leave it as is. + * </P> + * <P> + * The vCard specifications mention nothing toward phone numbers, while some devices + * do (wrongly, but with innevitable reasons). + * For example, there's a possibility Japanese mobile phones are expected to have + * just numbers, hypens, plus, etc. but not usual alphabets, while US mobile phones + * should get such characters. To make exported vCard simple for external parsers, + * we have used {@link PhoneNumberUtils#formatNumber(String)} during export, and + * removed unnecessary characters inside the number (e.g. "111-222-3333 (Miami)" + * becomes "111-222-3333"). + * Unfortunate side effect of that use was some control characters used in the other + * areas may be badly affected by the formatting. + * </P> + * <P> + * This flag disables that formatting, affecting both importer and exporter. + * If the user is aware of some side effects due to the implicit formatting, use this flag. + * </P> + */ + public static final int FLAG_REFRAIN_PHONE_NUMBER_FORMATTING = 0x02000000; + //// The followings are VCard types available from importer/exporter. //// /** @@ -431,6 +456,10 @@ public class VCardConfig { return sJapaneseMobileTypeSet.contains(vcardType); } + /* package */ static boolean refrainPhoneNumberFormatting(final int vcardType) { + return ((vcardType & FLAG_REFRAIN_PHONE_NUMBER_FORMATTING) != 0); + } + public static boolean needsToConvertPhoneticString(final int vcardType) { return ((vcardType & FLAG_CONVERT_PHONETIC_NAME_STRINGS) != 0); } @@ -445,4 +474,4 @@ public class VCardConfig { private VCardConfig() { } -}
\ No newline at end of file +} diff --git a/core/java/android/pim/vcard/VCardEntry.java b/core/java/android/pim/vcard/VCardEntry.java index 1327770..7c7e9b8 100644 --- a/core/java/android/pim/vcard/VCardEntry.java +++ b/core/java/android/pim/vcard/VCardEntry.java @@ -488,7 +488,7 @@ public class VCardEntry { final StringBuilder builder = new StringBuilder(); final String trimed = data.trim(); final String formattedNumber; - if (type == Phone.TYPE_PAGER) { + if (type == Phone.TYPE_PAGER || VCardConfig.refrainPhoneNumberFormatting(mVCardType)) { formattedNumber = trimed; } else { final int length = trimed.length(); @@ -500,8 +500,7 @@ public class VCardEntry { } // Use NANP in default when there's no information about locale. - final int formattingType = (VCardConfig.isJapaneseDevice(mVCardType) ? - PhoneNumberUtils.FORMAT_JAPAN : PhoneNumberUtils.FORMAT_NANP); + final int formattingType = VCardUtils.getPhoneNumberFormat(mVCardType); formattedNumber = PhoneNumberUtils.formatNumber(builder.toString(), formattingType); } PhoneData phoneData = new PhoneData(type, formattedNumber, label, isPrimary); diff --git a/core/java/android/preference/DialogPreference.java b/core/java/android/preference/DialogPreference.java index cc48aeb..bbad2b6 100644 --- a/core/java/android/preference/DialogPreference.java +++ b/core/java/android/preference/DialogPreference.java @@ -33,7 +33,6 @@ import android.view.LayoutInflater; import android.view.View; import android.view.Window; import android.view.WindowManager; -import android.view.inputmethod.InputMethodManager; import android.widget.TextView; /** @@ -275,7 +274,7 @@ public abstract class DialogPreference extends Preference implements protected void showDialog(Bundle state) { Context context = getContext(); - mWhichButtonClicked = DialogInterface.BUTTON2; + mWhichButtonClicked = DialogInterface.BUTTON_NEGATIVE; mBuilder = new AlertDialog.Builder(context) .setTitle(mDialogTitle) diff --git a/core/java/android/provider/MediaStore.java b/core/java/android/provider/MediaStore.java index c9d125b..40ed980 100644 --- a/core/java/android/provider/MediaStore.java +++ b/core/java/android/provider/MediaStore.java @@ -1819,4 +1819,12 @@ public final class MediaStore { * Name of current volume being scanned by the media scanner. */ public static final String MEDIA_SCANNER_VOLUME = "volume"; + + /** + * Name of the file signaling the media scanner to ignore media in the containing directory + * and its subdirectories. Developers should use this to avoid application graphics showing + * up in the Gallery and likewise prevent application sounds and music from showing up in + * the Music app. + */ + public static final String MEDIA_IGNORE_FILENAME = ".nomedia"; } diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index e12dfb0..7bb89f5 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -355,6 +355,21 @@ public final class Settings { "android.settings.MANAGE_APPLICATIONS_SETTINGS"; /** + * Activity Action: Show screen of details about a particular application. + * <p> + * In some cases, a matching Activity may not exist, so ensure you + * safeguard against this. + * <p> + * Input: The Intent's data URI specifies the application package name + * to be shown, with the "package" scheme. That is "package:com.my.app". + * <p> + * Output: Nothing. + */ + @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) + public static final String ACTION_APPLICATION_DETAILS_SETTINGS = + "android.settings.APPLICATION_DETAILS_SETTINGS"; + + /** * Activity Action: Show settings for system update functionality. * <p> * In some cases, a matching Activity may not exist, so ensure you diff --git a/core/java/android/server/BluetoothA2dpService.java b/core/java/android/server/BluetoothA2dpService.java index 893db2e..a52a221 100644 --- a/core/java/android/server/BluetoothA2dpService.java +++ b/core/java/android/server/BluetoothA2dpService.java @@ -27,7 +27,6 @@ import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothUuid; import android.bluetooth.IBluetoothA2dp; -import android.os.ParcelUuid; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; @@ -35,6 +34,7 @@ import android.content.IntentFilter; import android.media.AudioManager; import android.os.Handler; import android.os.Message; +import android.os.ParcelUuid; import android.provider.Settings; import android.util.Log; @@ -55,8 +55,6 @@ public class BluetoothA2dpService extends IBluetoothA2dp.Stub { private static final String BLUETOOTH_ENABLED = "bluetooth_enabled"; - private static final int MESSAGE_CONNECT_TO = 1; - private static final String PROPERTY_STATE = "State"; private static final String SINK_STATE_DISCONNECTED = "disconnected"; @@ -73,6 +71,7 @@ public class BluetoothA2dpService extends IBluetoothA2dp.Stub { private final BluetoothService mBluetoothService; private final BluetoothAdapter mAdapter; private int mTargetA2dpState; + private boolean mAdjustedPriority = false; private final BroadcastReceiver mReceiver = new BroadcastReceiver() { @Override @@ -104,16 +103,6 @@ public class BluetoothA2dpService extends IBluetoothA2dp.Stub { setSinkPriority(device, BluetoothA2dp.PRIORITY_UNDEFINED); break; } - } else if (action.equals(BluetoothDevice.ACTION_ACL_CONNECTED)) { - if (getSinkPriority(device) == BluetoothA2dp.PRIORITY_AUTO_CONNECT && - isSinkDevice(device)) { - // This device is a preferred sink. Make an A2DP connection - // after a delay. We delay to avoid connection collisions, - // and to give other profiles such as HFP a chance to - // connect first. - Message msg = Message.obtain(mHandler, MESSAGE_CONNECT_TO, device); - mHandler.sendMessageDelayed(msg, 6000); - } } else if (action.equals(BluetoothDevice.ACTION_ACL_DISCONNECTED)) { synchronized (this) { if (mAudioDevices.containsKey(device)) { @@ -187,6 +176,7 @@ public class BluetoothA2dpService extends IBluetoothA2dp.Stub { if (mBluetoothService.isEnabled()) onBluetoothEnable(); mTargetA2dpState = -1; + mBluetoothService.setA2dpService(this); } @Override @@ -198,29 +188,6 @@ public class BluetoothA2dpService extends IBluetoothA2dp.Stub { } } - private final Handler mHandler = new Handler() { - @Override - public void handleMessage(Message msg) { - switch (msg.what) { - case MESSAGE_CONNECT_TO: - BluetoothDevice device = (BluetoothDevice) msg.obj; - // check bluetooth is still on, device is still preferred, and - // nothing is currently connected - if (mBluetoothService.isEnabled() && - getSinkPriority(device) == BluetoothA2dp.PRIORITY_AUTO_CONNECT && - lookupSinksMatchingStates(new int[] { - BluetoothA2dp.STATE_CONNECTING, - BluetoothA2dp.STATE_CONNECTED, - BluetoothA2dp.STATE_PLAYING, - BluetoothA2dp.STATE_DISCONNECTING}).size() == 0) { - log("Auto-connecting A2DP to sink " + device); - connectSink(device); - } - break; - } - } - }; - private int convertBluezSinkStringtoState(String value) { if (value.equalsIgnoreCase("disconnected")) return BluetoothA2dp.STATE_DISCONNECTED; @@ -308,13 +275,37 @@ public class BluetoothA2dpService extends IBluetoothA2dp.Stub { mAudioManager.setParameters(BLUETOOTH_ENABLED + "=false"); } + private synchronized boolean isConnectSinkFeasible(BluetoothDevice device) { + if (!mBluetoothService.isEnabled() || !isSinkDevice(device) || + getSinkPriority(device) == BluetoothA2dp.PRIORITY_OFF) { + return false; + } + + if (mAudioDevices.get(device) == null && !addAudioSink(device)) { + return false; + } + + String path = mBluetoothService.getObjectPathFromAddress(device.getAddress()); + if (path == null) { + return false; + } + return true; + } + public synchronized boolean connectSink(BluetoothDevice device) { mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission"); if (DBG) log("connectSink(" + device + ")"); + if (!isConnectSinkFeasible(device)) return false; + return mBluetoothService.connectSink(device.getAddress()); + } + + public synchronized boolean connectSinkInternal(BluetoothDevice device) { if (!mBluetoothService.isEnabled()) return false; + int state = mAudioDevices.get(device); + // ignore if there are any active sinks if (lookupSinksMatchingStates(new int[] { BluetoothA2dp.STATE_CONNECTING, @@ -324,11 +315,6 @@ public class BluetoothA2dpService extends IBluetoothA2dp.Stub { return false; } - if (mAudioDevices.get(device) == null && !addAudioSink(device)) - return false; - - int state = mAudioDevices.get(device); - switch (state) { case BluetoothA2dp.STATE_CONNECTED: case BluetoothA2dp.STATE_PLAYING: @@ -339,8 +325,6 @@ public class BluetoothA2dpService extends IBluetoothA2dp.Stub { } String path = mBluetoothService.getObjectPathFromAddress(device.getAddress()); - if (path == null) - return false; // State is DISCONNECTED handleSinkStateChange(device, state, BluetoothA2dp.STATE_CONNECTING); @@ -353,11 +337,7 @@ public class BluetoothA2dpService extends IBluetoothA2dp.Stub { return true; } - public synchronized boolean disconnectSink(BluetoothDevice device) { - mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, - "Need BLUETOOTH_ADMIN permission"); - if (DBG) log("disconnectSink(" + device + ")"); - + private synchronized boolean isDisconnectSinkFeasible(BluetoothDevice device) { String path = mBluetoothService.getObjectPathFromAddress(device.getAddress()); if (path == null) { return false; @@ -370,6 +350,20 @@ public class BluetoothA2dpService extends IBluetoothA2dp.Stub { case BluetoothA2dp.STATE_DISCONNECTING: return true; } + return true; + } + + public synchronized boolean disconnectSink(BluetoothDevice device) { + mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, + "Need BLUETOOTH_ADMIN permission"); + if (DBG) log("disconnectSink(" + device + ")"); + if (!isDisconnectSinkFeasible(device)) return false; + return mBluetoothService.disconnectSink(device.getAddress()); + } + + public synchronized boolean disconnectSinkInternal(BluetoothDevice device) { + int state = getSinkState(device); + String path = mBluetoothService.getObjectPathFromAddress(device.getAddress()); // State is CONNECTING or CONNECTED or PLAYING handleSinkStateChange(device, state, BluetoothA2dp.STATE_DISCONNECTING); @@ -504,6 +498,12 @@ public class BluetoothA2dpService extends IBluetoothA2dp.Stub { setSinkPriority(device, BluetoothA2dp.PRIORITY_AUTO_CONNECT); } + if (state == BluetoothA2dp.STATE_CONNECTED) { + // We will only have 1 device with AUTO_CONNECT priority + // To be backward compatible set everyone else to have PRIORITY_ON + adjustOtherSinkPriorities(device); + } + Intent intent = new Intent(BluetoothA2dp.ACTION_SINK_STATE_CHANGED); intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device); intent.putExtra(BluetoothA2dp.EXTRA_PREVIOUS_SINK_STATE, prevState); @@ -514,6 +514,18 @@ public class BluetoothA2dpService extends IBluetoothA2dp.Stub { } } + private void adjustOtherSinkPriorities(BluetoothDevice connectedDevice) { + if (!mAdjustedPriority) { + for (BluetoothDevice device : mAdapter.getBondedDevices()) { + if (getSinkPriority(device) >= BluetoothA2dp.PRIORITY_AUTO_CONNECT && + !device.equals(connectedDevice)) { + setSinkPriority(device, BluetoothA2dp.PRIORITY_ON); + } + } + mAdjustedPriority = true; + } + } + private synchronized Set<BluetoothDevice> lookupSinksMatchingStates(int[] states) { Set<BluetoothDevice> sinks = new HashSet<BluetoothDevice>(); if (mAudioDevices.isEmpty()) { @@ -554,6 +566,7 @@ public class BluetoothA2dpService extends IBluetoothA2dp.Stub { if (!result) { if (deviceObjectPath != null) { String address = mBluetoothService.getAddressFromObjectPath(deviceObjectPath); + if (address == null) return; BluetoothDevice device = mAdapter.getRemoteDevice(address); int state = getSinkState(device); handleSinkStateChange(device, state, BluetoothA2dp.STATE_DISCONNECTED); diff --git a/core/java/android/server/BluetoothEventLoop.java b/core/java/android/server/BluetoothEventLoop.java index c0e4600..e1d3f13 100644 --- a/core/java/android/server/BluetoothEventLoop.java +++ b/core/java/android/server/BluetoothEventLoop.java @@ -566,6 +566,7 @@ class BluetoothEventLoop { authorized = a2dp.getSinkPriority(device) > BluetoothA2dp.PRIORITY_OFF; if (authorized) { Log.i(TAG, "Allowing incoming A2DP / AVRCP connection from " + address); + mBluetoothService.notifyIncomingA2dpConnection(address); } else { Log.i(TAG, "Rejecting incoming A2DP / AVRCP connection from " + address); } diff --git a/core/java/android/server/BluetoothService.java b/core/java/android/server/BluetoothService.java index c0affd3..31e5a7b 100644 --- a/core/java/android/server/BluetoothService.java +++ b/core/java/android/server/BluetoothService.java @@ -28,6 +28,8 @@ import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothClass; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothHeadset; +import android.bluetooth.BluetoothDeviceProfileState; +import android.bluetooth.BluetoothProfileState; import android.bluetooth.BluetoothSocket; import android.bluetooth.BluetoothUuid; import android.bluetooth.IBluetooth; @@ -112,7 +114,7 @@ public class BluetoothService extends IBluetooth.Stub { BluetoothUuid.HSP, BluetoothUuid.ObexObjectPush }; - + // TODO(): Optimize all these string handling private final Map<String, String> mAdapterProperties; private final HashMap<String, Map<String, String>> mDeviceProperties; @@ -122,6 +124,11 @@ public class BluetoothService extends IBluetooth.Stub { private final HashMap<Integer, Integer> mServiceRecordToPid; + private final HashMap<String, BluetoothDeviceProfileState> mDeviceProfileState; + private final BluetoothProfileState mA2dpProfileState; + private final BluetoothProfileState mHfpProfileState; + + private BluetoothA2dpService mA2dpService; private static String mDockAddress; private String mDockPin; @@ -179,6 +186,12 @@ public class BluetoothService extends IBluetooth.Stub { mUuidIntentTracker = new ArrayList<String>(); mUuidCallbackTracker = new HashMap<RemoteService, IBluetoothCallback>(); mServiceRecordToPid = new HashMap<Integer, Integer>(); + mDeviceProfileState = new HashMap<String, BluetoothDeviceProfileState>(); + mA2dpProfileState = new BluetoothProfileState(mContext, BluetoothProfileState.A2DP); + mHfpProfileState = new BluetoothProfileState(mContext, BluetoothProfileState.HFP); + + mHfpProfileState.start(); + mA2dpProfileState.start(); IntentFilter filter = new IntentFilter(); registerForAirplaneMode(filter); @@ -187,7 +200,7 @@ public class BluetoothService extends IBluetooth.Stub { mContext.registerReceiver(mReceiver, filter); } - public static synchronized String readDockBluetoothAddress() { + public static synchronized String readDockBluetoothAddress() { if (mDockAddress != null) return mDockAddress; BufferedInputStream file = null; @@ -534,6 +547,7 @@ public class BluetoothService extends IBluetooth.Stub { mIsDiscovering = false; mBondState.readAutoPairingData(); mBondState.loadBondState(); + initProfileState(); mHandler.sendMessageDelayed( mHandler.obtainMessage(MESSAGE_REGISTER_SDP_RECORDS, 1, -1), 3000); @@ -648,6 +662,12 @@ public class BluetoothService extends IBluetooth.Stub { } } + if (state == BluetoothDevice.BOND_BONDED) { + addProfileState(address); + } else if (state == BluetoothDevice.BOND_NONE) { + removeProfileState(address); + } + if (DBG) log(address + " bond state " + oldState + " -> " + state + " (" + reason + ")"); Intent intent = new Intent(BluetoothDevice.ACTION_BOND_STATE_CHANGED); @@ -1167,6 +1187,16 @@ public class BluetoothService extends IBluetooth.Stub { if (!BluetoothAdapter.checkBluetoothAddress(address)) { return false; } + BluetoothDeviceProfileState state = mDeviceProfileState.get(address); + if (state != null) { + state.sendMessage(BluetoothDeviceProfileState.UNPAIR); + return true; + } else { + return false; + } + } + + public synchronized boolean removeBondInternal(String address) { return removeDeviceNative(getObjectPathFromAddress(address)); } @@ -1836,7 +1866,7 @@ public class BluetoothService extends IBluetooth.Stub { // Rather not do this from here, but no-where else and I need this // dump pw.println("\n--Headset Service--"); - switch (headset.getState()) { + switch (headset.getState(headset.getCurrentHeadset())) { case BluetoothHeadset.STATE_DISCONNECTED: pw.println("getState() = STATE_DISCONNECTED"); break; @@ -1919,6 +1949,116 @@ public class BluetoothService extends IBluetooth.Stub { if (!result) log("Set Link Timeout to:" + num_slots + " slots failed"); } + public boolean connectHeadset(String address) { + BluetoothDeviceProfileState state = mDeviceProfileState.get(address); + if (state != null) { + Message msg = new Message(); + msg.arg1 = BluetoothDeviceProfileState.CONNECT_HFP_OUTGOING; + msg.obj = state; + mHfpProfileState.sendMessage(msg); + return true; + } + return false; + } + + public boolean disconnectHeadset(String address) { + BluetoothDeviceProfileState state = mDeviceProfileState.get(address); + if (state != null) { + Message msg = new Message(); + msg.arg1 = BluetoothDeviceProfileState.DISCONNECT_HFP_OUTGOING; + msg.obj = state; + mHfpProfileState.sendMessage(msg); + return true; + } + return false; + } + + public boolean connectSink(String address) { + BluetoothDeviceProfileState state = mDeviceProfileState.get(address); + if (state != null) { + Message msg = new Message(); + msg.arg1 = BluetoothDeviceProfileState.CONNECT_A2DP_OUTGOING; + msg.obj = state; + mA2dpProfileState.sendMessage(msg); + return true; + } + return false; + } + + public boolean disconnectSink(String address) { + BluetoothDeviceProfileState state = mDeviceProfileState.get(address); + if (state != null) { + Message msg = new Message(); + msg.arg1 = BluetoothDeviceProfileState.DISCONNECT_A2DP_OUTGOING; + msg.obj = state; + mA2dpProfileState.sendMessage(msg); + return true; + } + return false; + } + + private BluetoothDeviceProfileState addProfileState(String address) { + BluetoothDeviceProfileState state = mDeviceProfileState.get(address); + if (state != null) return state; + + state = new BluetoothDeviceProfileState(mContext, address, this, mA2dpService); + mDeviceProfileState.put(address, state); + state.start(); + return state; + } + + private void removeProfileState(String address) { + mDeviceProfileState.remove(address); + } + + private void initProfileState() { + String []bonds = null; + String val = getPropertyInternal("Devices"); + if (val != null) { + bonds = val.split(","); + } + if (bonds == null) { + return; + } + + for (String path : bonds) { + String address = getAddressFromObjectPath(path); + BluetoothDeviceProfileState state = addProfileState(address); + // Allow 8 secs for SDP records to get registered. + Message msg = new Message(); + msg.what = BluetoothDeviceProfileState.AUTO_CONNECT_PROFILES; + state.sendMessageDelayed(msg, 8000); + } + } + + public boolean notifyIncomingConnection(String address) { + BluetoothDeviceProfileState state = + mDeviceProfileState.get(address); + if (state != null) { + Message msg = new Message(); + msg.what = BluetoothDeviceProfileState.CONNECT_HFP_INCOMING; + state.sendMessage(msg); + return true; + } + return false; + } + + /*package*/ boolean notifyIncomingA2dpConnection(String address) { + BluetoothDeviceProfileState state = + mDeviceProfileState.get(address); + if (state != null) { + Message msg = new Message(); + msg.what = BluetoothDeviceProfileState.CONNECT_A2DP_INCOMING; + state.sendMessage(msg); + return true; + } + return false; + } + + /*package*/ void setA2dpService(BluetoothA2dpService a2dpService) { + mA2dpService = a2dpService; + } + private static void log(String msg) { Log.d(TAG, msg); } diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java index 3d1d7d6..2ade44e 100644 --- a/core/java/android/service/wallpaper/WallpaperService.java +++ b/core/java/android/service/wallpaper/WallpaperService.java @@ -522,20 +522,14 @@ public abstract class WallpaperService extends Service { } try { - SurfaceHolder.Callback callbacks[] = null; - synchronized (mSurfaceHolder.mCallbacks) { - final int N = mSurfaceHolder.mCallbacks.size(); - if (N > 0) { - callbacks = new SurfaceHolder.Callback[N]; - mSurfaceHolder.mCallbacks.toArray(callbacks); - } - } + mSurfaceHolder.ungetCallbacks(); if (surfaceCreating) { mIsCreating = true; if (DEBUG) Log.v(TAG, "onSurfaceCreated(" + mSurfaceHolder + "): " + this); onSurfaceCreated(mSurfaceHolder); + SurfaceHolder.Callback callbacks[] = mSurfaceHolder.getCallbacks(); if (callbacks != null) { for (SurfaceHolder.Callback c : callbacks) { c.surfaceCreated(mSurfaceHolder); @@ -557,6 +551,7 @@ public abstract class WallpaperService extends Service { + "): " + this); onSurfaceChanged(mSurfaceHolder, mFormat, mCurWidth, mCurHeight); + SurfaceHolder.Callback callbacks[] = mSurfaceHolder.getCallbacks(); if (callbacks != null) { for (SurfaceHolder.Callback c : callbacks) { c.surfaceChanged(mSurfaceHolder, mFormat, @@ -698,14 +693,12 @@ public abstract class WallpaperService extends Service { void reportSurfaceDestroyed() { if (mSurfaceCreated) { mSurfaceCreated = false; - SurfaceHolder.Callback callbacks[]; - synchronized (mSurfaceHolder.mCallbacks) { - callbacks = new SurfaceHolder.Callback[ - mSurfaceHolder.mCallbacks.size()]; - mSurfaceHolder.mCallbacks.toArray(callbacks); - } - for (SurfaceHolder.Callback c : callbacks) { - c.surfaceDestroyed(mSurfaceHolder); + mSurfaceHolder.ungetCallbacks(); + SurfaceHolder.Callback callbacks[] = mSurfaceHolder.getCallbacks(); + if (callbacks != null) { + for (SurfaceHolder.Callback c : callbacks) { + c.surfaceDestroyed(mSurfaceHolder); + } } if (DEBUG) Log.v(TAG, "onSurfaceDestroyed(" + mSurfaceHolder + "): " + this); diff --git a/core/java/android/text/util/Rfc822Tokenizer.java b/core/java/android/text/util/Rfc822Tokenizer.java index 952d833..69cf93c 100644 --- a/core/java/android/text/util/Rfc822Tokenizer.java +++ b/core/java/android/text/util/Rfc822Tokenizer.java @@ -84,8 +84,10 @@ public class Rfc822Tokenizer implements MultiAutoCompleteTextView.Tokenizer { if (c == '"') { i++; break; - } else if (c == '\\' && i + 1 < cursor) { - name.append(text.charAt(i + 1)); + } else if (c == '\\') { + if (i + 1 < cursor) { + name.append(text.charAt(i + 1)); + } i += 2; } else { name.append(c); @@ -110,8 +112,10 @@ public class Rfc822Tokenizer implements MultiAutoCompleteTextView.Tokenizer { comment.append(c); level++; i++; - } else if (c == '\\' && i + 1 < cursor) { - comment.append(text.charAt(i + 1)); + } else if (c == '\\') { + if (i + 1 < cursor) { + comment.append(text.charAt(i + 1)); + } i += 2; } else { comment.append(c); diff --git a/core/java/android/util/DisplayMetrics.java b/core/java/android/util/DisplayMetrics.java index 2628eb4..76d8106 100644 --- a/core/java/android/util/DisplayMetrics.java +++ b/core/java/android/util/DisplayMetrics.java @@ -135,6 +135,7 @@ public class DisplayMetrics { int screenLayout) { boolean expandable = compatibilityInfo.isConfiguredExpandable(); boolean largeScreens = compatibilityInfo.isConfiguredLargeScreens(); + boolean xlargeScreens = compatibilityInfo.isConfiguredXLargeScreens(); // Note: this assume that configuration is updated before calling // updateMetrics method. @@ -157,8 +158,18 @@ public class DisplayMetrics { compatibilityInfo.setLargeScreens(false); } } + if (!xlargeScreens) { + if ((screenLayout&Configuration.SCREENLAYOUT_SIZE_MASK) + != Configuration.SCREENLAYOUT_SIZE_XLARGE) { + xlargeScreens = true; + // the current screen size is not large. + compatibilityInfo.setXLargeScreens(true); + } else { + compatibilityInfo.setXLargeScreens(false); + } + } - if (!expandable || !largeScreens) { + if (!expandable || (!largeScreens && !xlargeScreens)) { // This is a larger screen device and the app is not // compatible with large screens, so diddle it. diff --git a/core/java/android/util/Log.java b/core/java/android/util/Log.java index e111669..d577b74 100644 --- a/core/java/android/util/Log.java +++ b/core/java/android/util/Log.java @@ -88,6 +88,21 @@ public final class Log { TerribleFailure(String msg, Throwable cause) { super(msg, cause); } } + /** + * Interface to handle terrible failures from {@link #wtf()}. + * + * @hide + */ + public interface TerribleFailureHandler { + void onTerribleFailure(String tag, TerribleFailure what); + } + + private static TerribleFailureHandler sWtfHandler = new TerribleFailureHandler() { + public void onTerribleFailure(String tag, TerribleFailure what) { + RuntimeInit.wtf(tag, what); + } + }; + private Log() { } @@ -257,13 +272,29 @@ public final class Log { * @param tr An exception to log. May be null. */ public static int wtf(String tag, String msg, Throwable tr) { - tr = new TerribleFailure(msg, tr); + TerribleFailure what = new TerribleFailure(msg, tr); int bytes = println_native(LOG_ID_MAIN, ASSERT, tag, getStackTraceString(tr)); - RuntimeInit.wtf(tag, tr); + sWtfHandler.onTerribleFailure(tag, what); return bytes; } /** + * Sets the terrible failure handler, for testing. + * + * @return the old handler + * + * @hide + */ + public static TerribleFailureHandler setWtfHandler(TerribleFailureHandler handler) { + if (handler == null) { + throw new NullPointerException("handler == null"); + } + TerribleFailureHandler oldHandler = sWtfHandler; + sWtfHandler = handler; + return oldHandler; + } + + /** * Handy function to get a loggable stack trace from a Throwable * @param tr An exception to log */ diff --git a/core/java/android/view/KeyEvent.java b/core/java/android/view/KeyEvent.java index d4f9787..9aa16b5 100644..100755 --- a/core/java/android/view/KeyEvent.java +++ b/core/java/android/view/KeyEvent.java @@ -120,6 +120,10 @@ public class KeyEvent implements Parcelable { public static final int KEYCODE_MEDIA_REWIND = 89; public static final int KEYCODE_MEDIA_FAST_FORWARD = 90; public static final int KEYCODE_MUTE = 91; + public static final int KEYCODE_PAGE_UP = 92; + public static final int KEYCODE_PAGE_DOWN = 93; + public static final int KEYCODE_PICTSYMBOLS = 94; // switch symbol-sets (Emoji,Kao-moji) + public static final int KEYCODE_SWITCH_CHARSET = 95; // switch char-sets (Kanji,Katakana) // NOTE: If you add a new keycode here you must also add it to: // isSystem() @@ -135,7 +139,7 @@ public class KeyEvent implements Parcelable { // those new codes. This is intended to maintain a consistent // set of key code definitions across all Android devices. - private static final int LAST_KEYCODE = KEYCODE_MUTE; + private static final int LAST_KEYCODE = KEYCODE_SWITCH_CHARSET; /** * @deprecated There are now more than MAX_KEYCODE keycodes. @@ -692,6 +696,8 @@ public class KeyEvent implements Parcelable { case KEYCODE_CAMERA: case KEYCODE_FOCUS: case KEYCODE_SEARCH: + case KEYCODE_PICTSYMBOLS: + case KEYCODE_SWITCH_CHARSET: return true; default: return false; diff --git a/core/java/android/view/MotionEvent.java b/core/java/android/view/MotionEvent.java index d648e96..eefbf7a 100644 --- a/core/java/android/view/MotionEvent.java +++ b/core/java/android/view/MotionEvent.java @@ -255,17 +255,19 @@ public final class MotionEvent implements Parcelable { } static private MotionEvent obtain() { + final MotionEvent ev; synchronized (gRecyclerLock) { if (gRecyclerTop == null) { return new MotionEvent(); } - MotionEvent ev = gRecyclerTop; + ev = gRecyclerTop; gRecyclerTop = ev.mNext; gRecyclerUsed--; - ev.mRecycledLocation = null; - ev.mRecycled = false; - return ev; } + ev.mRecycledLocation = null; + ev.mRecycled = false; + ev.mNext = null; + return ev; } /** @@ -620,11 +622,14 @@ public final class MotionEvent implements Parcelable { throw new RuntimeException(toString() + " recycled twice!", mRecycledLocation); } mRecycledLocation = new RuntimeException("Last recycled here"); - } else if (mRecycled) { - throw new RuntimeException(toString() + " recycled twice!"); + //Log.w("MotionEvent", "Recycling event " + this, mRecycledLocation); + } else { + if (mRecycled) { + throw new RuntimeException(toString() + " recycled twice!"); + } + mRecycled = true; } - //Log.w("MotionEvent", "Recycling event " + this, mRecycledLocation); synchronized (gRecyclerLock) { if (gRecyclerUsed < MAX_RECYCLED) { gRecyclerUsed++; diff --git a/core/java/android/view/Surface.java b/core/java/android/view/Surface.java index 83ef8ba..cd0ae3b 100644 --- a/core/java/android/view/Surface.java +++ b/core/java/android/view/Surface.java @@ -140,13 +140,13 @@ public class Surface implements Parcelable { public static final int FLAGS_ORIENTATION_ANIMATION_DISABLE = 0x000000001; @SuppressWarnings("unused") - private int mSurface; - @SuppressWarnings("unused") private int mSurfaceControl; @SuppressWarnings("unused") private int mSaveCount; @SuppressWarnings("unused") private Canvas mCanvas; + @SuppressWarnings("unused") + private int mNativeSurface; private String mName; // The display metrics used to provide the pseudo canvas size for applications @@ -422,13 +422,13 @@ public class Surface implements Parcelable { /* no user serviceable parts here ... */ @Override protected void finalize() throws Throwable { - if (mSurface != 0 || mSurfaceControl != 0) { + if (mNativeSurface != 0 || mSurfaceControl != 0) { if (DEBUG_RELEASE) { Log.w(LOG_TAG, "Surface.finalize() has work. You should have called release() (" - + mSurface + ", " + mSurfaceControl + ")", mCreationStack); + + mNativeSurface + ", " + mSurfaceControl + ")", mCreationStack); } else { Log.w(LOG_TAG, "Surface.finalize() has work. You should have called release() (" - + mSurface + ", " + mSurfaceControl + ")"); + + mNativeSurface + ", " + mSurfaceControl + ")"); } } release(); diff --git a/core/java/android/view/SurfaceHolder.java b/core/java/android/view/SurfaceHolder.java index 64a10d1..34e4638 100644 --- a/core/java/android/view/SurfaceHolder.java +++ b/core/java/android/view/SurfaceHolder.java @@ -182,7 +182,6 @@ public interface SurfaceHolder { /** * Enable or disable option to keep the screen turned on while this * surface is displayed. The default is false, allowing it to turn off. - * Enabling the option effectivelty. * This is safe to call from any thread. * * @param screenOn Supply to true to force the screen to stay on, false diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java index 53f0c2e..0f0cf60 100644 --- a/core/java/android/view/SurfaceView.java +++ b/core/java/android/view/SurfaceView.java @@ -141,7 +141,10 @@ public class SurfaceView extends View { boolean mViewVisibility = false; int mRequestedWidth = -1; int mRequestedHeight = -1; - int mRequestedFormat = PixelFormat.OPAQUE; + /* Set SurfaceView's format to 565 by default to maintain backward + * compatibility with applications assuming this format. + */ + int mRequestedFormat = PixelFormat.RGB_565; int mRequestedType = -1; boolean mHaveFrame = false; @@ -164,16 +167,20 @@ public class SurfaceView extends View { public SurfaceView(Context context) { super(context); - setWillNotDraw(true); + init(); } public SurfaceView(Context context, AttributeSet attrs) { super(context, attrs); - setWillNotDraw(true); + init(); } public SurfaceView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); + init(); + } + + private void init() { setWillNotDraw(true); } diff --git a/core/java/android/view/VelocityTracker.java b/core/java/android/view/VelocityTracker.java index aab76c4..e69b807 100644 --- a/core/java/android/view/VelocityTracker.java +++ b/core/java/android/view/VelocityTracker.java @@ -206,7 +206,7 @@ public final class VelocityTracker implements Poolable<VelocityTracker> { final long oldestTime = pastTime[oldestTouch]; float accumX = 0; float accumY = 0; - float N = (lastTouch - oldestTouch + NUM_PAST) % NUM_PAST + 1; + int N = (lastTouch - oldestTouch + NUM_PAST) % NUM_PAST + 1; // Skip the last received event, since it is probably pretty noisy. if (N > 3) N--; diff --git a/core/java/android/view/ViewRoot.java b/core/java/android/view/ViewRoot.java index 03efea9..aa124e6 100644 --- a/core/java/android/view/ViewRoot.java +++ b/core/java/android/view/ViewRoot.java @@ -16,8 +16,10 @@ package android.view; +import com.android.internal.view.BaseSurfaceHolder; import com.android.internal.view.IInputMethodCallback; import com.android.internal.view.IInputMethodSession; +import com.android.internal.view.RootViewSurfaceTaker; import android.graphics.Canvas; import android.graphics.PixelFormat; @@ -26,12 +28,12 @@ import android.graphics.Rect; import android.graphics.Region; import android.os.*; import android.os.Process; -import android.os.SystemProperties; import android.util.AndroidRuntimeException; import android.util.Config; import android.util.DisplayMetrics; import android.util.Log; import android.util.EventLog; +import android.util.Slog; import android.util.SparseArray; import android.view.View.MeasureSpec; import android.view.accessibility.AccessibilityEvent; @@ -50,6 +52,7 @@ import android.Manifest; import android.media.AudioManager; import java.lang.ref.WeakReference; +import java.io.FileDescriptor; import java.io.IOException; import java.io.OutputStream; import java.util.ArrayList; @@ -76,6 +79,7 @@ public final class ViewRoot extends Handler implements ViewParent, /** @noinspection PointlessBooleanExpression*/ private static final boolean DEBUG_DRAW = false || LOCAL_LOGV; private static final boolean DEBUG_LAYOUT = false || LOCAL_LOGV; + private static final boolean DEBUG_INPUT = true || LOCAL_LOGV; private static final boolean DEBUG_INPUT_RESIZE = false || LOCAL_LOGV; private static final boolean DEBUG_ORIENTATION = false || LOCAL_LOGV; private static final boolean DEBUG_TRACKBALL = false || LOCAL_LOGV; @@ -133,6 +137,11 @@ public final class ViewRoot extends Handler implements ViewParent, int mViewVisibility; boolean mAppVisible = true; + SurfaceHolder.Callback mSurfaceHolderCallback; + BaseSurfaceHolder mSurfaceHolder; + boolean mIsCreating; + boolean mDrawingAllowed; + final Region mTransparentRegion; final Region mPreviousTransparentRegion; @@ -425,6 +434,9 @@ public final class ViewRoot extends Handler implements ViewParent, } } + // fd [0] is the receiver, [1] is the sender + private native int[] makeInputChannel(); + /** * We have one child */ @@ -435,6 +447,13 @@ public final class ViewRoot extends Handler implements ViewParent, mView = view; mWindowAttributes.copyFrom(attrs); attrs = mWindowAttributes; + if (view instanceof RootViewSurfaceTaker) { + mSurfaceHolderCallback = + ((RootViewSurfaceTaker)view).willYouTakeTheSurface(); + if (mSurfaceHolderCallback != null) { + mSurfaceHolder = new TakenSurfaceHolder(); + } + } Resources resources = mView.getContext().getResources(); CompatibilityInfo compatibilityInfo = resources.getCompatibilityInfo(); mTranslator = compatibilityInfo.getTranslator(); @@ -469,6 +488,14 @@ public final class ViewRoot extends Handler implements ViewParent, mAdded = true; int res; /* = WindowManagerImpl.ADD_OKAY; */ + // Set up the input event channel + if (false) { + int[] fds = makeInputChannel(); + if (DEBUG_INPUT) { + Log.v(TAG, "makeInputChannel() returned " + fds); + } + } + // Schedule the first layout -before- adding to the window // manager, to make sure we do the relayout before receiving // any other events from the system. @@ -682,6 +709,7 @@ public final class ViewRoot extends Handler implements ViewParent, boolean windowResizesToFitContent = false; boolean fullRedrawNeeded = mFullRedrawNeeded; boolean newSurface = false; + boolean surfaceChanged = false; WindowManager.LayoutParams lp = mWindowAttributes; int desiredWindowWidth; @@ -700,6 +728,7 @@ public final class ViewRoot extends Handler implements ViewParent, WindowManager.LayoutParams params = null; if (mWindowAttributesChanged) { mWindowAttributesChanged = false; + surfaceChanged = true; params = lp; } Rect frame = mWinFrame; @@ -886,11 +915,18 @@ public final class ViewRoot extends Handler implements ViewParent, } } + if (mSurfaceHolder != null) { + mSurfaceHolder.mSurfaceLock.lock(); + mDrawingAllowed = true; + lp.format = mSurfaceHolder.getRequestedFormat(); + lp.type = mSurfaceHolder.getRequestedType(); + } + boolean initialized = false; boolean contentInsetsChanged = false; boolean visibleInsetsChanged; + boolean hadSurface = mSurface.isValid(); try { - boolean hadSurface = mSurface.isValid(); int fl = 0; if (params != null) { fl = params.flags; @@ -965,6 +1001,7 @@ public final class ViewRoot extends Handler implements ViewParent, } } catch (RemoteException e) { } + if (DEBUG_ORIENTATION) Log.v( "ViewRoot", "Relayout returned: frame=" + frame + ", surface=" + mSurface); @@ -977,6 +1014,57 @@ public final class ViewRoot extends Handler implements ViewParent, mWidth = frame.width(); mHeight = frame.height(); + if (mSurfaceHolder != null) { + // The app owns the surface; tell it about what is going on. + if (mSurface.isValid()) { + // XXX .copyFrom() doesn't work! + //mSurfaceHolder.mSurface.copyFrom(mSurface); + mSurfaceHolder.mSurface = mSurface; + } + mSurfaceHolder.mSurfaceLock.unlock(); + if (mSurface.isValid()) { + if (!hadSurface) { + mSurfaceHolder.ungetCallbacks(); + + mIsCreating = true; + mSurfaceHolderCallback.surfaceCreated(mSurfaceHolder); + SurfaceHolder.Callback callbacks[] = mSurfaceHolder.getCallbacks(); + if (callbacks != null) { + for (SurfaceHolder.Callback c : callbacks) { + c.surfaceCreated(mSurfaceHolder); + } + } + surfaceChanged = true; + } + if (surfaceChanged) { + mSurfaceHolderCallback.surfaceChanged(mSurfaceHolder, + lp.format, mWidth, mHeight); + SurfaceHolder.Callback callbacks[] = mSurfaceHolder.getCallbacks(); + if (callbacks != null) { + for (SurfaceHolder.Callback c : callbacks) { + c.surfaceChanged(mSurfaceHolder, lp.format, + mWidth, mHeight); + } + } + } + mIsCreating = false; + } else if (hadSurface) { + mSurfaceHolder.ungetCallbacks(); + SurfaceHolder.Callback callbacks[] = mSurfaceHolder.getCallbacks(); + mSurfaceHolderCallback.surfaceDestroyed(mSurfaceHolder); + if (callbacks != null) { + for (SurfaceHolder.Callback c : callbacks) { + c.surfaceDestroyed(mSurfaceHolder); + } + } + mSurfaceHolder.mSurfaceLock.lock(); + // Make surface invalid. + //mSurfaceHolder.mSurface.copyFrom(mSurface); + mSurfaceHolder.mSurface = new Surface(); + mSurfaceHolder.mSurfaceLock.unlock(); + } + } + if (initialized) { mGlCanvas.setViewport((int) (mWidth * appScale + 0.5f), (int) (mHeight * appScale + 0.5f)); @@ -1268,6 +1356,12 @@ public final class ViewRoot extends Handler implements ViewParent, boolean scalingRequired = mAttachInfo.mScalingRequired; Rect dirty = mDirty; + if (mSurfaceHolder != null) { + // The app owns the surface, we won't draw. + dirty.setEmpty(); + return; + } + if (mUseGL) { if (!dirty.isEmpty()) { Canvas canvas = mGlCanvas; @@ -1332,103 +1426,105 @@ public final class ViewRoot extends Handler implements ViewParent, appScale + ", width=" + mWidth + ", height=" + mHeight); } - Canvas canvas; - try { - int left = dirty.left; - int top = dirty.top; - int right = dirty.right; - int bottom = dirty.bottom; - canvas = surface.lockCanvas(dirty); - - if (left != dirty.left || top != dirty.top || right != dirty.right || - bottom != dirty.bottom) { - mAttachInfo.mIgnoreDirtyState = true; - } - - // TODO: Do this in native - canvas.setDensity(mDensity); - } catch (Surface.OutOfResourcesException e) { - Log.e("ViewRoot", "OutOfResourcesException locking surface", e); - // TODO: we should ask the window manager to do something! - // for now we just do nothing - return; - } catch (IllegalArgumentException e) { - Log.e("ViewRoot", "IllegalArgumentException locking surface", e); - // TODO: we should ask the window manager to do something! - // for now we just do nothing - return; - } + if (!dirty.isEmpty() || mIsAnimating) { + Canvas canvas; + try { + int left = dirty.left; + int top = dirty.top; + int right = dirty.right; + int bottom = dirty.bottom; + canvas = surface.lockCanvas(dirty); + + if (left != dirty.left || top != dirty.top || right != dirty.right || + bottom != dirty.bottom) { + mAttachInfo.mIgnoreDirtyState = true; + } - try { - if (!dirty.isEmpty() || mIsAnimating) { - long startTime = 0L; + // TODO: Do this in native + canvas.setDensity(mDensity); + } catch (Surface.OutOfResourcesException e) { + Log.e("ViewRoot", "OutOfResourcesException locking surface", e); + // TODO: we should ask the window manager to do something! + // for now we just do nothing + return; + } catch (IllegalArgumentException e) { + Log.e("ViewRoot", "IllegalArgumentException locking surface", e); + // TODO: we should ask the window manager to do something! + // for now we just do nothing + return; + } - if (DEBUG_ORIENTATION || DEBUG_DRAW) { - Log.v("ViewRoot", "Surface " + surface + " drawing to bitmap w=" - + canvas.getWidth() + ", h=" + canvas.getHeight()); - //canvas.drawARGB(255, 255, 0, 0); - } + try { + if (!dirty.isEmpty() || mIsAnimating) { + long startTime = 0L; - if (Config.DEBUG && ViewDebug.profileDrawing) { - startTime = SystemClock.elapsedRealtime(); - } + if (DEBUG_ORIENTATION || DEBUG_DRAW) { + Log.v("ViewRoot", "Surface " + surface + " drawing to bitmap w=" + + canvas.getWidth() + ", h=" + canvas.getHeight()); + //canvas.drawARGB(255, 255, 0, 0); + } - // If this bitmap's format includes an alpha channel, we - // need to clear it before drawing so that the child will - // properly re-composite its drawing on a transparent - // background. This automatically respects the clip/dirty region - // or - // If we are applying an offset, we need to clear the area - // where the offset doesn't appear to avoid having garbage - // left in the blank areas. - if (!canvas.isOpaque() || yoff != 0) { - canvas.drawColor(0, PorterDuff.Mode.CLEAR); - } + if (Config.DEBUG && ViewDebug.profileDrawing) { + startTime = SystemClock.elapsedRealtime(); + } - dirty.setEmpty(); - mIsAnimating = false; - mAttachInfo.mDrawingTime = SystemClock.uptimeMillis(); - mView.mPrivateFlags |= View.DRAWN; + // If this bitmap's format includes an alpha channel, we + // need to clear it before drawing so that the child will + // properly re-composite its drawing on a transparent + // background. This automatically respects the clip/dirty region + // or + // If we are applying an offset, we need to clear the area + // where the offset doesn't appear to avoid having garbage + // left in the blank areas. + if (!canvas.isOpaque() || yoff != 0) { + canvas.drawColor(0, PorterDuff.Mode.CLEAR); + } - if (DEBUG_DRAW) { - Context cxt = mView.getContext(); - Log.i(TAG, "Drawing: package:" + cxt.getPackageName() + - ", metrics=" + cxt.getResources().getDisplayMetrics() + - ", compatibilityInfo=" + cxt.getResources().getCompatibilityInfo()); - } - int saveCount = canvas.save(Canvas.MATRIX_SAVE_FLAG); - try { - canvas.translate(0, -yoff); - if (mTranslator != null) { - mTranslator.translateCanvas(canvas); + dirty.setEmpty(); + mIsAnimating = false; + mAttachInfo.mDrawingTime = SystemClock.uptimeMillis(); + mView.mPrivateFlags |= View.DRAWN; + + if (DEBUG_DRAW) { + Context cxt = mView.getContext(); + Log.i(TAG, "Drawing: package:" + cxt.getPackageName() + + ", metrics=" + cxt.getResources().getDisplayMetrics() + + ", compatibilityInfo=" + cxt.getResources().getCompatibilityInfo()); + } + int saveCount = canvas.save(Canvas.MATRIX_SAVE_FLAG); + try { + canvas.translate(0, -yoff); + if (mTranslator != null) { + mTranslator.translateCanvas(canvas); + } + canvas.setScreenDensity(scalingRequired + ? DisplayMetrics.DENSITY_DEVICE : 0); + mView.draw(canvas); + } finally { + mAttachInfo.mIgnoreDirtyState = false; + canvas.restoreToCount(saveCount); } - canvas.setScreenDensity(scalingRequired - ? DisplayMetrics.DENSITY_DEVICE : 0); - mView.draw(canvas); - } finally { - mAttachInfo.mIgnoreDirtyState = false; - canvas.restoreToCount(saveCount); - } - if (Config.DEBUG && ViewDebug.consistencyCheckEnabled) { - mView.dispatchConsistencyCheck(ViewDebug.CONSISTENCY_DRAWING); - } + if (Config.DEBUG && ViewDebug.consistencyCheckEnabled) { + mView.dispatchConsistencyCheck(ViewDebug.CONSISTENCY_DRAWING); + } - if (SHOW_FPS || Config.DEBUG && ViewDebug.showFps) { - int now = (int)SystemClock.elapsedRealtime(); - if (sDrawTime != 0) { - nativeShowFPS(canvas, now - sDrawTime); + if (SHOW_FPS || Config.DEBUG && ViewDebug.showFps) { + int now = (int)SystemClock.elapsedRealtime(); + if (sDrawTime != 0) { + nativeShowFPS(canvas, now - sDrawTime); + } + sDrawTime = now; } - sDrawTime = now; - } - if (Config.DEBUG && ViewDebug.profileDrawing) { - EventLog.writeEvent(60000, SystemClock.elapsedRealtime() - startTime); + if (Config.DEBUG && ViewDebug.profileDrawing) { + EventLog.writeEvent(60000, SystemClock.elapsedRealtime() - startTime); + } } - } - } finally { - surface.unlockCanvasAndPost(canvas); + } finally { + surface.unlockCanvasAndPost(canvas); + } } if (LOCAL_LOGV) { @@ -2813,6 +2909,46 @@ public final class ViewRoot extends Handler implements ViewParent, return scrollToRectOrFocus(rectangle, immediate); } + class TakenSurfaceHolder extends BaseSurfaceHolder { + @Override + public boolean onAllowLockCanvas() { + return mDrawingAllowed; + } + + @Override + public void onRelayoutContainer() { + // Not currently interesting -- from changing between fixed and layout size. + } + + public void setFormat(int format) { + ((RootViewSurfaceTaker)mView).setSurfaceFormat(format); + } + + public void setType(int type) { + ((RootViewSurfaceTaker)mView).setSurfaceType(type); + } + + @Override + public void onUpdateSurface() { + // We take care of format and type changes on our own. + throw new IllegalStateException("Shouldn't be here"); + } + + public boolean isCreating() { + return mIsCreating; + } + + @Override + public void setFixedSize(int width, int height) { + throw new UnsupportedOperationException( + "Currently only support sizing from layout"); + } + + public void setKeepScreenOn(boolean screenOn) { + ((RootViewSurfaceTaker)mView).setSurfaceKeepScreenOn(screenOn); + } + } + static class InputMethodCallback extends IInputMethodCallback.Stub { private WeakReference<ViewRoot> mViewRoot; diff --git a/core/java/android/view/Window.java b/core/java/android/view/Window.java index 7dd5085..234deba 100644 --- a/core/java/android/view/Window.java +++ b/core/java/android/view/Window.java @@ -473,6 +473,14 @@ public abstract class Window { } /** + * Take ownership of this window's surface. The window's view hierarchy + * will no longer draw into the surface, though it will otherwise continue + * to operate (such as for receiving input events). The given SurfaceHolder + * callback will be used to tell you about state changes to the surface. + */ + public abstract void takeSurface(SurfaceHolder.Callback callback); + + /** * Return whether this window is being displayed with a floating style * (based on the {@link android.R.attr#windowIsFloating} attribute in * the style/theme). diff --git a/core/java/android/webkit/MimeTypeMap.java b/core/java/android/webkit/MimeTypeMap.java index 034c88a..ca9ad53 100644 --- a/core/java/android/webkit/MimeTypeMap.java +++ b/core/java/android/webkit/MimeTypeMap.java @@ -67,7 +67,7 @@ public class MimeTypeMap { // if the filename contains special characters, we don't // consider it valid for our matching purposes: if (filename.length() > 0 && - Pattern.matches("[a-zA-Z_0-9\\.\\-\\(\\)]+", filename)) { + Pattern.matches("[a-zA-Z_0-9\\.\\-\\(\\)\\%]+", filename)) { int dotPos = filename.lastIndexOf('.'); if (0 <= dotPos) { return filename.substring(dotPos + 1); diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java index 921d0f5..bf751f5 100644 --- a/core/java/android/webkit/WebView.java +++ b/core/java/android/webkit/WebView.java @@ -238,7 +238,7 @@ import junit.framework.Assert; * * <p>The screen density of a device is based on the screen resolution. A screen with low density * has fewer available pixels per inch, where a screen with high density - * has more — sometimes significantly more — pixels per inch. The density of a + * has more — sometimes significantly more — pixels per inch. The density of a * screen is important because, other things being equal, a UI element (such as a button) whose * height and width are defined in terms of screen pixels will appear larger on the lower density * screen and smaller on the higher density screen. @@ -1380,16 +1380,23 @@ public class WebView extends AbsoluteLayout final File temp = new File(dest.getPath() + ".writing"); new Thread(new Runnable() { public void run() { + FileOutputStream out = null; try { - FileOutputStream out = new FileOutputStream(temp); + out = new FileOutputStream(temp); p.writeToStream(out); - out.close(); // Writing the picture succeeded, rename the temporary file // to the destination. temp.renameTo(dest); } catch (Exception e) { // too late to do anything about it. } finally { + if (out != null) { + try { + out.close(); + } catch (Exception e) { + // Can't do anything about that + } + } temp.delete(); } } @@ -1442,20 +1449,23 @@ public class WebView extends AbsoluteLayout final Bundle copy = new Bundle(b); new Thread(new Runnable() { public void run() { - final Picture p = Picture.createFromStream(in); - if (p != null) { - // Post a runnable on the main thread to update the - // history picture fields. - mPrivateHandler.post(new Runnable() { - public void run() { - restoreHistoryPictureFields(p, copy); - } - }); - } try { - in.close(); - } catch (Exception e) { - // Nothing we can do now. + final Picture p = Picture.createFromStream(in); + if (p != null) { + // Post a runnable on the main thread to update the + // history picture fields. + mPrivateHandler.post(new Runnable() { + public void run() { + restoreHistoryPictureFields(p, copy); + } + }); + } + } finally { + try { + in.close(); + } catch (Exception e) { + // Nothing we can do now. + } } } }).start(); diff --git a/core/java/android/widget/DatePicker.java b/core/java/android/widget/DatePicker.java index b3d5f1a..1fc23ab 100644 --- a/core/java/android/widget/DatePicker.java +++ b/core/java/android/widget/DatePicker.java @@ -328,6 +328,7 @@ public class DatePicker extends FrameLayout { mYear = ss.getYear(); mMonth = ss.getMonth(); mDay = ss.getDay(); + updateSpinners(); } /** diff --git a/core/java/android/widget/Gallery.java b/core/java/android/widget/Gallery.java index f34823c..1ed6b16 100644 --- a/core/java/android/widget/Gallery.java +++ b/core/java/android/widget/Gallery.java @@ -1087,7 +1087,7 @@ public class Gallery extends AbsSpinner implements GestureDetector.OnGestureList @Override public boolean dispatchKeyEvent(KeyEvent event) { // Gallery steals all key events - return event.dispatch(this); + return event.dispatch(this, null, null); } /** diff --git a/core/java/android/widget/MediaController.java b/core/java/android/widget/MediaController.java index c246c247..39b1377 100644 --- a/core/java/android/widget/MediaController.java +++ b/core/java/android/widget/MediaController.java @@ -123,7 +123,7 @@ public class MediaController extends FrameLayout { } private void initFloatingWindow() { - mWindowManager = (WindowManager)mContext.getSystemService("window"); + mWindowManager = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE); mWindow = PolicyManager.makeNewWindow(mContext); mWindow.setWindowManager(mWindowManager, null, null); mWindow.requestFeature(Window.FEATURE_NO_TITLE); diff --git a/core/java/android/widget/ProgressBar.java b/core/java/android/widget/ProgressBar.java index 202e658..8e9eb05 100644 --- a/core/java/android/widget/ProgressBar.java +++ b/core/java/android/widget/ProgressBar.java @@ -947,4 +947,20 @@ public class ProgressBar extends View { setProgress(ss.progress); setSecondaryProgress(ss.secondaryProgress); } + + @Override + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + if (mIndeterminate) { + startAnimation(); + } + } + + @Override + protected void onDetachedFromWindow() { + super.onDetachedFromWindow(); + if (mIndeterminate) { + stopAnimation(); + } + } } diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java index 3003580..7a70c80 100644 --- a/core/java/android/widget/RemoteViews.java +++ b/core/java/android/widget/RemoteViews.java @@ -100,6 +100,7 @@ public class RemoteViews implements Parcelable, Filter { * Base class for all actions that can be performed on an * inflated view. * + * SUBCLASSES MUST BE IMMUTABLE SO CLONE WORKS!!!!! */ private abstract static class Action implements Parcelable { public abstract void apply(View root) throws ActionException; @@ -568,6 +569,14 @@ public class RemoteViews implements Parcelable, Filter { } } + public RemoteViews clone() { + final RemoteViews that = new RemoteViews(mPackage, mLayoutId); + if (mActions != null) { + that.mActions = (ArrayList<Action>)mActions.clone(); + } + return that; + } + public String getPackage() { return mPackage; } diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index 64c9c99..950012c 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -4547,6 +4547,10 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener outText.text = TextUtils.substring(content, partialStartOffset, partialEndOffset); } + } else { + outText.partialStartOffset = 0; + outText.partialEndOffset = 0; + outText.text = ""; } outText.flags = 0; if (MetaKeyKeyListener.getMetaState(mText, MetaKeyKeyListener.META_SELECTING) != 0) { diff --git a/core/java/com/android/internal/app/ExternalMediaFormatActivity.java b/core/java/com/android/internal/app/ExternalMediaFormatActivity.java index 7e9bbd1..98dcb8b 100644 --- a/core/java/com/android/internal/app/ExternalMediaFormatActivity.java +++ b/core/java/com/android/internal/app/ExternalMediaFormatActivity.java @@ -23,13 +23,10 @@ import android.content.DialogInterface; import android.content.Intent; import android.content.IntentFilter; import android.os.Bundle; -import android.os.Handler; import android.os.storage.IMountService; -import android.os.Message; import android.os.RemoteException; import android.os.ServiceManager; import android.os.Environment; -import android.widget.Toast; import android.util.Log; /** @@ -38,7 +35,7 @@ import android.util.Log; */ public class ExternalMediaFormatActivity extends AlertActivity implements DialogInterface.OnClickListener { - private static final int POSITIVE_BUTTON = AlertDialog.BUTTON1; + private static final int POSITIVE_BUTTON = AlertDialog.BUTTON_POSITIVE; /** Used to detect when the media state changes, in case we need to call finish() */ private BroadcastReceiver mStorageReceiver = new BroadcastReceiver() { diff --git a/core/java/com/android/internal/app/HeavyWeightSwitcherActivity.java b/core/java/com/android/internal/app/HeavyWeightSwitcherActivity.java new file mode 100644 index 0000000..ada7f36 --- /dev/null +++ b/core/java/com/android/internal/app/HeavyWeightSwitcherActivity.java @@ -0,0 +1,156 @@ +/* + * Copyright (C) 2010 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 com.android.internal.app; + +import com.android.internal.R; + +import android.app.Activity; +import android.app.ActivityManagerNative; +import android.content.Intent; +import android.content.IntentSender; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.graphics.drawable.Drawable; +import android.os.Bundle; +import android.os.RemoteException; +import android.util.Log; +import android.view.View; +import android.view.Window; +import android.view.View.OnClickListener; +import android.widget.Button; +import android.widget.ImageView; +import android.widget.TextView; + +/** + * This activity is displayed when the system attempts to start an Intent for + * which there is more than one matching activity, allowing the user to decide + * which to go to. It is not normally used directly by application developers. + */ +public class HeavyWeightSwitcherActivity extends Activity { + /** The PendingIntent of the new activity being launched. */ + public static final String KEY_INTENT = "intent"; + /** Set if the caller is requesting a result. */ + public static final String KEY_HAS_RESULT = "has_result"; + /** Package of current heavy-weight app. */ + public static final String KEY_CUR_APP = "cur_app"; + /** Task that current heavy-weight activity is running in. */ + public static final String KEY_CUR_TASK = "cur_task"; + /** Package of newly requested heavy-weight app. */ + public static final String KEY_NEW_APP = "new_app"; + + IntentSender mStartIntent; + boolean mHasResult; + String mCurApp; + int mCurTask; + String mNewApp; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + requestWindowFeature(Window.FEATURE_LEFT_ICON); + + mStartIntent = (IntentSender)getIntent().getParcelableExtra(KEY_INTENT); + mHasResult = getIntent().getBooleanExtra(KEY_HAS_RESULT, false); + mCurApp = getIntent().getStringExtra(KEY_CUR_APP); + mCurTask = getIntent().getIntExtra(KEY_CUR_TASK, 0); + mNewApp = getIntent().getStringExtra(KEY_NEW_APP); + + setContentView(com.android.internal.R.layout.heavy_weight_switcher); + + setIconAndText(R.id.old_app_icon, R.id.old_app_action, R.id.old_app_description, + mCurApp, R.string.old_app_action, R.string.old_app_description); + setIconAndText(R.id.new_app_icon, R.id.new_app_action, R.id.new_app_description, + mNewApp, R.string.new_app_action, R.string.new_app_description); + + View button = findViewById((R.id.switch_old)); + button.setOnClickListener(mSwitchOldListener); + button = findViewById((R.id.switch_new)); + button.setOnClickListener(mSwitchNewListener); + button = findViewById((R.id.cancel)); + button.setOnClickListener(mCancelListener); + + getWindow().setFeatureDrawableResource(Window.FEATURE_LEFT_ICON, + android.R.drawable.ic_dialog_alert); + } + + void setText(int id, CharSequence text) { + ((TextView)findViewById(id)).setText(text); + } + + void setDrawable(int id, Drawable dr) { + if (dr != null) { + ((ImageView)findViewById(id)).setImageDrawable(dr); + } + } + + void setIconAndText(int iconId, int actionId, int descriptionId, + String packageName, int actionStr, int descriptionStr) { + CharSequence appName = ""; + Drawable appIcon = null; + if (mCurApp != null) { + try { + ApplicationInfo info = getPackageManager().getApplicationInfo( + packageName, 0); + appName = info.loadLabel(getPackageManager()); + appIcon = info.loadIcon(getPackageManager()); + } catch (PackageManager.NameNotFoundException e) { + } + } + + setDrawable(iconId, appIcon); + setText(actionId, getString(actionStr, appName)); + setText(descriptionId, getText(descriptionStr)); + } + + private OnClickListener mSwitchOldListener = new OnClickListener() { + public void onClick(View v) { + try { + ActivityManagerNative.getDefault().moveTaskToFront(mCurTask); + } catch (RemoteException e) { + } + finish(); + } + }; + + private OnClickListener mSwitchNewListener = new OnClickListener() { + public void onClick(View v) { + try { + ActivityManagerNative.getDefault().finishHeavyWeightApp(); + } catch (RemoteException e) { + } + try { + if (mHasResult) { + startIntentSenderForResult(mStartIntent, -1, null, + Intent.FLAG_ACTIVITY_FORWARD_RESULT, + Intent.FLAG_ACTIVITY_FORWARD_RESULT, 0); + } else { + startIntentSenderForResult(mStartIntent, -1, null, 0, 0, 0); + } + } catch (IntentSender.SendIntentException ex) { + Log.w("HeavyWeightSwitcherActivity", "Failure starting", ex); + } + finish(); + } + }; + + private OnClickListener mCancelListener = new OnClickListener() { + public void onClick(View v) { + finish(); + } + }; +} diff --git a/core/java/com/android/internal/app/NetInitiatedActivity.java b/core/java/com/android/internal/app/NetInitiatedActivity.java index 24818a8..36f45b2 100755 --- a/core/java/com/android/internal/app/NetInitiatedActivity.java +++ b/core/java/com/android/internal/app/NetInitiatedActivity.java @@ -23,14 +23,9 @@ import android.content.DialogInterface; import android.content.Intent; import android.content.IntentFilter; import android.os.Bundle; -import android.os.Handler; -import android.os.Message; -import android.os.RemoteException; -import android.os.ServiceManager; import android.widget.Toast; import android.util.Log; import android.location.LocationManager; -import com.android.internal.location.GpsLocationProvider; import com.android.internal.location.GpsNetInitiatedHandler; /** @@ -44,8 +39,8 @@ public class NetInitiatedActivity extends AlertActivity implements DialogInterfa private static final boolean DEBUG = true; private static final boolean VERBOSE = false; - private static final int POSITIVE_BUTTON = AlertDialog.BUTTON1; - private static final int NEGATIVE_BUTTON = AlertDialog.BUTTON2; + private static final int POSITIVE_BUTTON = AlertDialog.BUTTON_POSITIVE; + private static final int NEGATIVE_BUTTON = AlertDialog.BUTTON_NEGATIVE; // Dialog button text public static final String BUTTON_TEXT_ACCEPT = "Accept"; diff --git a/core/java/com/android/internal/app/RingtonePickerActivity.java b/core/java/com/android/internal/app/RingtonePickerActivity.java index ddddabe..5569ffe 100644 --- a/core/java/com/android/internal/app/RingtonePickerActivity.java +++ b/core/java/com/android/internal/app/RingtonePickerActivity.java @@ -223,7 +223,7 @@ public final class RingtonePickerActivity extends AlertActivity implements * On click of Ok/Cancel buttons */ public void onClick(DialogInterface dialog, int which) { - boolean positiveResult = which == BUTTON1; + boolean positiveResult = which == DialogInterface.BUTTON_POSITIVE; // Stop playing the previous ringtone mRingtoneManager.stopPreviousRingtone(); diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java index aadb576..3833725 100644 --- a/core/java/com/android/internal/os/BatteryStatsImpl.java +++ b/core/java/com/android/internal/os/BatteryStatsImpl.java @@ -56,12 +56,13 @@ import java.util.concurrent.atomic.AtomicInteger; public final class BatteryStatsImpl extends BatteryStats { private static final String TAG = "BatteryStatsImpl"; private static final boolean DEBUG = false; - + private static final boolean DEBUG_HISTORY = false; + // In-memory Parcel magic number, used to detect attempts to unmarshall bad data private static final int MAGIC = 0xBA757475; // 'BATSTATS' // Current on-disk Parcel version - private static final int VERSION = 43; + private static final int VERSION = 44; // The maximum number of names wakelocks we will keep track of // per uid; once the limit is reached, we batch the remaining wakelocks @@ -94,6 +95,11 @@ public final class BatteryStatsImpl extends BatteryStats { // is unplugged from power. final ArrayList<Unpluggable> mUnpluggables = new ArrayList<Unpluggable>(); + BatteryHistoryRecord mHistory; + BatteryHistoryRecord mHistoryEnd; + BatteryHistoryRecord mHistoryCache; + final BatteryHistoryRecord mHistoryCur = new BatteryHistoryRecord(); + int mStartCount; long mBatteryUptime; @@ -1042,6 +1048,37 @@ public final class BatteryStatsImpl extends BatteryStats { mBtHeadset = headset; } + void addHistoryRecord(long curTime) { + BatteryHistoryRecord rec = mHistoryCache; + if (rec != null) { + mHistoryCache = rec.next; + } else { + rec = new BatteryHistoryRecord(); + } + rec.time = curTime; + rec.batteryLevel = mHistoryCur.batteryLevel; + rec.states = mHistoryCur.states; + addHistoryRecord(rec); + } + + void addHistoryRecord(BatteryHistoryRecord rec) { + rec.next = null; + if (mHistoryEnd != null) { + mHistoryEnd.next = rec; + mHistoryEnd = rec; + } else { + mHistory = mHistoryEnd = rec; + } + } + + void clearHistory() { + if (mHistory != null) { + mHistoryEnd.next = mHistoryCache; + mHistoryCache = mHistory; + mHistory = mHistoryEnd = null; + } + } + public void doUnplug(long batteryUptime, long batteryRealtime) { for (int iu = mUidStats.size() - 1; iu >= 0; iu--) { Uid u = mUidStats.valueAt(iu); @@ -1094,16 +1131,37 @@ public final class BatteryStatsImpl extends BatteryStats { mBluetoothPingStart = -1; } + int mGpsNesting; + public void noteStartGps(int uid) { + if (mGpsNesting == 0) { + mHistoryCur.states |= BatteryHistoryRecord.STATE_GPS_ON_FLAG; + if (DEBUG_HISTORY) Slog.v(TAG, "Start GPS to: " + + Integer.toHexString(mHistoryCur.states)); + addHistoryRecord(SystemClock.elapsedRealtime()); + } + mGpsNesting++; getUidStatsLocked(uid).noteStartGps(); } public void noteStopGps(int uid) { + mGpsNesting--; + if (mGpsNesting == 0) { + mHistoryCur.states &= ~BatteryHistoryRecord.STATE_GPS_ON_FLAG; + if (DEBUG_HISTORY) Slog.v(TAG, "Stop GPS to: " + + Integer.toHexString(mHistoryCur.states)); + addHistoryRecord(SystemClock.elapsedRealtime()); + } getUidStatsLocked(uid).noteStopGps(); } public void noteScreenOnLocked() { if (!mScreenOn) { + mHistoryCur.states |= BatteryHistoryRecord.STATE_SCREEN_ON_FLAG; + if (DEBUG_HISTORY) Slog.v(TAG, "Screen on to: " + + Integer.toHexString(mHistoryCur.states)); + addHistoryRecord(SystemClock.elapsedRealtime()); + mGpsNesting++; mScreenOn = true; mScreenOnTimer.startRunningLocked(this); if (mScreenBrightnessBin >= 0) { @@ -1114,6 +1172,10 @@ public final class BatteryStatsImpl extends BatteryStats { public void noteScreenOffLocked() { if (mScreenOn) { + mHistoryCur.states &= ~BatteryHistoryRecord.STATE_SCREEN_ON_FLAG; + if (DEBUG_HISTORY) Slog.v(TAG, "Screen off to: " + + Integer.toHexString(mHistoryCur.states)); + addHistoryRecord(SystemClock.elapsedRealtime()); mScreenOn = false; mScreenOnTimer.stopRunningLocked(this); if (mScreenBrightnessBin >= 0) { @@ -1128,6 +1190,11 @@ public final class BatteryStatsImpl extends BatteryStats { if (bin < 0) bin = 0; else if (bin >= NUM_SCREEN_BRIGHTNESS_BINS) bin = NUM_SCREEN_BRIGHTNESS_BINS-1; if (mScreenBrightnessBin != bin) { + mHistoryCur.states = (mHistoryCur.states&~BatteryHistoryRecord.STATE_SCREEN_MASK) + | (bin << BatteryHistoryRecord.STATE_SCREEN_SHIFT); + if (DEBUG_HISTORY) Slog.v(TAG, "Screen brightness " + bin + " to: " + + Integer.toHexString(mHistoryCur.states)); + addHistoryRecord(SystemClock.elapsedRealtime()); if (mScreenOn) { if (mScreenBrightnessBin >= 0) { mScreenBrightnessTimer[mScreenBrightnessBin].stopRunningLocked(this); @@ -1148,6 +1215,10 @@ public final class BatteryStatsImpl extends BatteryStats { public void notePhoneOnLocked() { if (!mPhoneOn) { + mHistoryCur.states |= BatteryHistoryRecord.STATE_PHONE_ON_FLAG; + if (DEBUG_HISTORY) Slog.v(TAG, "Phone on to: " + + Integer.toHexString(mHistoryCur.states)); + addHistoryRecord(SystemClock.elapsedRealtime()); mPhoneOn = true; mPhoneOnTimer.startRunningLocked(this); } @@ -1155,6 +1226,10 @@ public final class BatteryStatsImpl extends BatteryStats { public void notePhoneOffLocked() { if (mPhoneOn) { + mHistoryCur.states &= ~BatteryHistoryRecord.STATE_PHONE_ON_FLAG; + if (DEBUG_HISTORY) Slog.v(TAG, "Phone off to: " + + Integer.toHexString(mHistoryCur.states)); + addHistoryRecord(SystemClock.elapsedRealtime()); mPhoneOn = false; mPhoneOnTimer.stopRunningLocked(this); } @@ -1195,7 +1270,15 @@ public final class BatteryStatsImpl extends BatteryStats { mPhoneSignalScanningTimer.startRunningLocked(this); } } - mPhoneServiceState = state; + + if (mPhoneServiceState != state) { + mHistoryCur.states = (mHistoryCur.states&~BatteryHistoryRecord.STATE_PHONE_STATE_MASK) + | (state << BatteryHistoryRecord.STATE_PHONE_STATE_SHIFT); + if (DEBUG_HISTORY) Slog.v(TAG, "Phone state " + bin + " to: " + + Integer.toHexString(mHistoryCur.states)); + addHistoryRecord(SystemClock.elapsedRealtime()); + mPhoneServiceState = state; + } } public void notePhoneSignalStrengthLocked(SignalStrength signalStrength) { @@ -1222,6 +1305,11 @@ public final class BatteryStatsImpl extends BatteryStats { else bin = SIGNAL_STRENGTH_POOR; } if (mPhoneSignalStrengthBin != bin) { + mHistoryCur.states = (mHistoryCur.states&~BatteryHistoryRecord.STATE_SIGNAL_STRENGTH_MASK) + | (bin << BatteryHistoryRecord.STATE_SIGNAL_STRENGTH_SHIFT); + if (DEBUG_HISTORY) Slog.v(TAG, "Signal strength " + bin + " to: " + + Integer.toHexString(mHistoryCur.states)); + addHistoryRecord(SystemClock.elapsedRealtime()); if (mPhoneSignalStrengthBin >= 0) { mPhoneSignalStrengthsTimer[mPhoneSignalStrengthBin].stopRunningLocked(this); } @@ -1250,6 +1338,11 @@ public final class BatteryStatsImpl extends BatteryStats { } if (DEBUG) Log.i(TAG, "Phone Data Connection -> " + dataType + " = " + hasData); if (mPhoneDataConnectionType != bin) { + mHistoryCur.states = (mHistoryCur.states&~BatteryHistoryRecord.STATE_DATA_CONNECTION_MASK) + | (bin << BatteryHistoryRecord.STATE_DATA_CONNECTION_SHIFT); + if (DEBUG_HISTORY) Slog.v(TAG, "Data connection " + bin + " to: " + + Integer.toHexString(mHistoryCur.states)); + addHistoryRecord(SystemClock.elapsedRealtime()); if (mPhoneDataConnectionType >= 0) { mPhoneDataConnectionsTimer[mPhoneDataConnectionType].stopRunningLocked(this); } @@ -1260,6 +1353,10 @@ public final class BatteryStatsImpl extends BatteryStats { public void noteWifiOnLocked(int uid) { if (!mWifiOn) { + mHistoryCur.states |= BatteryHistoryRecord.STATE_WIFI_ON_FLAG; + if (DEBUG_HISTORY) Slog.v(TAG, "WIFI on to: " + + Integer.toHexString(mHistoryCur.states)); + addHistoryRecord(SystemClock.elapsedRealtime()); mWifiOn = true; mWifiOnTimer.startRunningLocked(this); } @@ -1274,6 +1371,10 @@ public final class BatteryStatsImpl extends BatteryStats { public void noteWifiOffLocked(int uid) { if (mWifiOn) { + mHistoryCur.states &= ~BatteryHistoryRecord.STATE_WIFI_ON_FLAG; + if (DEBUG_HISTORY) Slog.v(TAG, "WIFI off to: " + + Integer.toHexString(mHistoryCur.states)); + addHistoryRecord(SystemClock.elapsedRealtime()); mWifiOn = false; mWifiOnTimer.stopRunningLocked(this); } @@ -1285,6 +1386,10 @@ public final class BatteryStatsImpl extends BatteryStats { public void noteAudioOnLocked(int uid) { if (!mAudioOn) { + mHistoryCur.states |= BatteryHistoryRecord.STATE_AUDIO_ON_FLAG; + if (DEBUG_HISTORY) Slog.v(TAG, "Audio on to: " + + Integer.toHexString(mHistoryCur.states)); + addHistoryRecord(SystemClock.elapsedRealtime()); mAudioOn = true; mAudioOnTimer.startRunningLocked(this); } @@ -1293,6 +1398,10 @@ public final class BatteryStatsImpl extends BatteryStats { public void noteAudioOffLocked(int uid) { if (mAudioOn) { + mHistoryCur.states &= ~BatteryHistoryRecord.STATE_AUDIO_ON_FLAG; + if (DEBUG_HISTORY) Slog.v(TAG, "Audio off to: " + + Integer.toHexString(mHistoryCur.states)); + addHistoryRecord(SystemClock.elapsedRealtime()); mAudioOn = false; mAudioOnTimer.stopRunningLocked(this); } @@ -1301,6 +1410,10 @@ public final class BatteryStatsImpl extends BatteryStats { public void noteVideoOnLocked(int uid) { if (!mVideoOn) { + mHistoryCur.states |= BatteryHistoryRecord.STATE_VIDEO_ON_FLAG; + if (DEBUG_HISTORY) Slog.v(TAG, "Video on to: " + + Integer.toHexString(mHistoryCur.states)); + addHistoryRecord(SystemClock.elapsedRealtime()); mVideoOn = true; mVideoOnTimer.startRunningLocked(this); } @@ -1309,6 +1422,10 @@ public final class BatteryStatsImpl extends BatteryStats { public void noteVideoOffLocked(int uid) { if (mVideoOn) { + mHistoryCur.states &= ~BatteryHistoryRecord.STATE_VIDEO_ON_FLAG; + if (DEBUG_HISTORY) Slog.v(TAG, "Video off to: " + + Integer.toHexString(mHistoryCur.states)); + addHistoryRecord(SystemClock.elapsedRealtime()); mVideoOn = false; mVideoOnTimer.stopRunningLocked(this); } @@ -1317,6 +1434,10 @@ public final class BatteryStatsImpl extends BatteryStats { public void noteWifiRunningLocked() { if (!mWifiRunning) { + mHistoryCur.states |= BatteryHistoryRecord.STATE_WIFI_RUNNING_FLAG; + if (DEBUG_HISTORY) Slog.v(TAG, "WIFI running to: " + + Integer.toHexString(mHistoryCur.states)); + addHistoryRecord(SystemClock.elapsedRealtime()); mWifiRunning = true; mWifiRunningTimer.startRunningLocked(this); } @@ -1324,6 +1445,10 @@ public final class BatteryStatsImpl extends BatteryStats { public void noteWifiStoppedLocked() { if (mWifiRunning) { + mHistoryCur.states &= ~BatteryHistoryRecord.STATE_WIFI_RUNNING_FLAG; + if (DEBUG_HISTORY) Slog.v(TAG, "WIFI stopped to: " + + Integer.toHexString(mHistoryCur.states)); + addHistoryRecord(SystemClock.elapsedRealtime()); mWifiRunning = false; mWifiRunningTimer.stopRunningLocked(this); } @@ -1331,6 +1456,10 @@ public final class BatteryStatsImpl extends BatteryStats { public void noteBluetoothOnLocked() { if (!mBluetoothOn) { + mHistoryCur.states |= BatteryHistoryRecord.STATE_BLUETOOTH_ON_FLAG; + if (DEBUG_HISTORY) Slog.v(TAG, "Bluetooth on to: " + + Integer.toHexString(mHistoryCur.states)); + addHistoryRecord(SystemClock.elapsedRealtime()); mBluetoothOn = true; mBluetoothOnTimer.startRunningLocked(this); } @@ -1338,32 +1467,84 @@ public final class BatteryStatsImpl extends BatteryStats { public void noteBluetoothOffLocked() { if (mBluetoothOn) { + mHistoryCur.states &= ~BatteryHistoryRecord.STATE_BLUETOOTH_ON_FLAG; + if (DEBUG_HISTORY) Slog.v(TAG, "Bluetooth off to: " + + Integer.toHexString(mHistoryCur.states)); + addHistoryRecord(SystemClock.elapsedRealtime()); mBluetoothOn = false; mBluetoothOnTimer.stopRunningLocked(this); } } + int mWifiFullLockNesting = 0; + public void noteFullWifiLockAcquiredLocked(int uid) { + if (mWifiFullLockNesting == 0) { + mHistoryCur.states |= BatteryHistoryRecord.STATE_WIFI_FULL_LOCK_FLAG; + if (DEBUG_HISTORY) Slog.v(TAG, "WIFI full lock on to: " + + Integer.toHexString(mHistoryCur.states)); + addHistoryRecord(SystemClock.elapsedRealtime()); + } + mWifiFullLockNesting++; getUidStatsLocked(uid).noteFullWifiLockAcquiredLocked(); } public void noteFullWifiLockReleasedLocked(int uid) { + mWifiFullLockNesting--; + if (mWifiFullLockNesting == 0) { + mHistoryCur.states &= ~BatteryHistoryRecord.STATE_WIFI_FULL_LOCK_FLAG; + if (DEBUG_HISTORY) Slog.v(TAG, "WIFI full lock off to: " + + Integer.toHexString(mHistoryCur.states)); + addHistoryRecord(SystemClock.elapsedRealtime()); + } getUidStatsLocked(uid).noteFullWifiLockReleasedLocked(); } + int mWifiScanLockNesting = 0; + public void noteScanWifiLockAcquiredLocked(int uid) { + if (mWifiScanLockNesting == 0) { + mHistoryCur.states |= BatteryHistoryRecord.STATE_WIFI_SCAN_LOCK_FLAG; + if (DEBUG_HISTORY) Slog.v(TAG, "WIFI scan lock on to: " + + Integer.toHexString(mHistoryCur.states)); + addHistoryRecord(SystemClock.elapsedRealtime()); + } + mWifiScanLockNesting++; getUidStatsLocked(uid).noteScanWifiLockAcquiredLocked(); } public void noteScanWifiLockReleasedLocked(int uid) { + mWifiScanLockNesting--; + if (mWifiScanLockNesting == 0) { + mHistoryCur.states &= ~BatteryHistoryRecord.STATE_WIFI_SCAN_LOCK_FLAG; + if (DEBUG_HISTORY) Slog.v(TAG, "WIFI scan lock off to: " + + Integer.toHexString(mHistoryCur.states)); + addHistoryRecord(SystemClock.elapsedRealtime()); + } getUidStatsLocked(uid).noteScanWifiLockReleasedLocked(); } + int mWifiMulticastNesting = 0; + public void noteWifiMulticastEnabledLocked(int uid) { + if (mWifiMulticastNesting == 0) { + mHistoryCur.states |= BatteryHistoryRecord.STATE_WIFI_MULTICAST_ON_FLAG; + if (DEBUG_HISTORY) Slog.v(TAG, "WIFI multicast on to: " + + Integer.toHexString(mHistoryCur.states)); + addHistoryRecord(SystemClock.elapsedRealtime()); + } + mWifiMulticastNesting++; getUidStatsLocked(uid).noteWifiMulticastEnabledLocked(); } public void noteWifiMulticastDisabledLocked(int uid) { + mWifiMulticastNesting--; + if (mWifiMulticastNesting == 0) { + mHistoryCur.states &= ~BatteryHistoryRecord.STATE_WIFI_MULTICAST_ON_FLAG; + if (DEBUG_HISTORY) Slog.v(TAG, "WIFI multicast off to: " + + Integer.toHexString(mHistoryCur.states)); + addHistoryRecord(SystemClock.elapsedRealtime()); + } getUidStatsLocked(uid).noteWifiMulticastDisabledLocked(); } @@ -2766,6 +2947,11 @@ public final class BatteryStatsImpl extends BatteryStats { } @Override + public BatteryHistoryRecord getHistory() { + return mHistory; + } + + @Override public int getStartCount() { return mStartCount; } @@ -2784,6 +2970,12 @@ public final class BatteryStatsImpl extends BatteryStats { long mSecRealtime = SystemClock.elapsedRealtime(); long realtime = mSecRealtime * 1000; if (onBattery) { + clearHistory(); + mHistoryCur.batteryLevel = (byte)level; + mHistoryCur.states &= ~BatteryHistoryRecord.STATE_BATTERY_PLUGGED_FLAG; + if (DEBUG_HISTORY) Slog.v(TAG, "Battery unplugged to: " + + Integer.toHexString(mHistoryCur.states)); + addHistoryRecord(mSecRealtime); mTrackBatteryUptimeStart = uptime; mTrackBatteryRealtimeStart = realtime; mUnpluggedBatteryUptime = getBatteryUptimeLocked(uptime); @@ -2791,6 +2983,11 @@ public final class BatteryStatsImpl extends BatteryStats { mDischargeCurrentLevel = mDischargeStartLevel = level; doUnplug(mUnpluggedBatteryUptime, mUnpluggedBatteryRealtime); } else { + mHistoryCur.batteryLevel = (byte)level; + mHistoryCur.states |= BatteryHistoryRecord.STATE_BATTERY_PLUGGED_FLAG; + if (DEBUG_HISTORY) Slog.v(TAG, "Battery plugged to: " + + Integer.toHexString(mHistoryCur.states)); + addHistoryRecord(mSecRealtime); mTrackBatteryPastUptime += uptime - mTrackBatteryUptimeStart; mTrackBatteryPastRealtime += realtime - mTrackBatteryRealtimeStart; mDischargeCurrentLevel = level; @@ -2806,7 +3003,11 @@ public final class BatteryStatsImpl extends BatteryStats { } public void recordCurrentLevel(int level) { - mDischargeCurrentLevel = level; + if (mDischargeCurrentLevel != level) { + mDischargeCurrentLevel = level; + mHistoryCur.batteryLevel = (byte)level; + addHistoryRecord(SystemClock.elapsedRealtime()); + } } public void updateKernelWakelocksLocked() { @@ -3146,6 +3347,24 @@ public final class BatteryStatsImpl extends BatteryStats { return 0; } + void readHistory(Parcel in) { + mHistory = mHistoryEnd = mHistoryCache = null; + long time; + while ((time=in.readLong()) >= 0) { + BatteryHistoryRecord rec = new BatteryHistoryRecord(time, in); + addHistoryRecord(rec); + } + } + + void writeHistory(Parcel out) { + BatteryHistoryRecord rec = mHistory; + while (rec != null) { + if (rec.time >= 0) rec.writeToParcel(out, 0); + rec = rec.next; + } + out.writeLong(-1); + } + private void readSummaryFromParcel(Parcel in) { final int version = in.readInt(); if (version != VERSION) { @@ -3154,6 +3373,8 @@ public final class BatteryStatsImpl extends BatteryStats { return; } + readHistory(in); + mStartCount = in.readInt(); mBatteryUptime = in.readLong(); mBatteryLastUptime = in.readLong(); @@ -3325,6 +3546,8 @@ public final class BatteryStatsImpl extends BatteryStats { out.writeInt(VERSION); + writeHistory(out); + out.writeInt(mStartCount); out.writeLong(computeBatteryUptime(NOW_SYS, STATS_TOTAL)); out.writeLong(computeBatteryUptime(NOW_SYS, STATS_CURRENT)); @@ -3493,6 +3716,8 @@ public final class BatteryStatsImpl extends BatteryStats { throw new ParcelFormatException("Bad magic number"); } + readHistory(in); + mStartCount = in.readInt(); mBatteryUptime = in.readLong(); mBatteryLastUptime = in.readLong(); @@ -3591,6 +3816,9 @@ public final class BatteryStatsImpl extends BatteryStats { final long batteryRealtime = getBatteryRealtimeLocked(uSecRealtime); out.writeInt(MAGIC); + + writeHistory(out); + out.writeInt(mStartCount); out.writeLong(mBatteryUptime); out.writeLong(mBatteryLastUptime); diff --git a/core/java/com/android/internal/os/RuntimeInit.java b/core/java/com/android/internal/os/RuntimeInit.java index 599a7fe..59600dc 100644 --- a/core/java/com/android/internal/os/RuntimeInit.java +++ b/core/java/com/android/internal/os/RuntimeInit.java @@ -342,6 +342,10 @@ public class RuntimeInit { mApplicationObject = app; } + public static final IBinder getApplicationObject() { + return mApplicationObject; + } + /** * Enable debugging features. */ diff --git a/core/java/com/android/internal/statusbar/IStatusBar.aidl b/core/java/com/android/internal/statusbar/IStatusBar.aidl new file mode 100644 index 0000000..4501bd7 --- /dev/null +++ b/core/java/com/android/internal/statusbar/IStatusBar.aidl @@ -0,0 +1,34 @@ +/** + * 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 com.android.internal.statusbar; + +import com.android.internal.statusbar.StatusBarIcon; +import com.android.internal.statusbar.StatusBarNotification; + +/** @hide */ +oneway interface IStatusBar +{ + void setIcon(int index, in StatusBarIcon icon); + void removeIcon(int index); + void addNotification(IBinder key, in StatusBarNotification notification); + void updateNotification(IBinder key, in StatusBarNotification notification); + void removeNotification(IBinder key); + void disable(int state); + void animateExpand(); + void animateCollapse(); +} + diff --git a/core/java/com/android/internal/statusbar/IStatusBarService.aidl b/core/java/com/android/internal/statusbar/IStatusBarService.aidl new file mode 100644 index 0000000..045c24f --- /dev/null +++ b/core/java/com/android/internal/statusbar/IStatusBarService.aidl @@ -0,0 +1,42 @@ +/** + * 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 com.android.internal.statusbar; + +import com.android.internal.statusbar.IStatusBar; +import com.android.internal.statusbar.StatusBarIcon; +import com.android.internal.statusbar.StatusBarIconList; +import com.android.internal.statusbar.StatusBarNotification; + +/** @hide */ +interface IStatusBarService +{ + void expand(); + void collapse(); + void disable(int what, IBinder token, String pkg); + void setIcon(String slot, String iconPackage, int iconId, int iconLevel); + void setIconVisibility(String slot, boolean visible); + void removeIcon(String slot); + + // ---- Methods below are for use by the status bar policy services ---- + // You need the STATUS_BAR_SERVICE permission + void registerStatusBar(IStatusBar callbacks, out StatusBarIconList iconList, + out List<IBinder> notificationKeys, out List<StatusBarNotification> notifications); + void onPanelRevealed(); + void onNotificationClick(String pkg, String tag, int id); + void onNotificationError(String pkg, String tag, int id, String message); + void onClearAllNotifications(); +} diff --git a/core/java/android/app/IStatusBar.aidl b/core/java/com/android/internal/statusbar/StatusBarIcon.aidl index c64fa50..311a077 100644 --- a/core/java/android/app/IStatusBar.aidl +++ b/core/java/com/android/internal/statusbar/StatusBarIcon.aidl @@ -1,5 +1,5 @@ -/** - * Copyright (c) 2007, The Android Open Source Project +/* + * Copyright (c) 2010, 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. @@ -13,17 +13,8 @@ * 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); -} +package com.android.internal.statusbar; + +parcelable StatusBarIcon; + diff --git a/core/java/com/android/internal/statusbar/StatusBarIcon.java b/core/java/com/android/internal/statusbar/StatusBarIcon.java new file mode 100644 index 0000000..ae2cac2 --- /dev/null +++ b/core/java/com/android/internal/statusbar/StatusBarIcon.java @@ -0,0 +1,105 @@ +/* + * Copyright (C) 2010 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 com.android.internal.statusbar; + +import android.os.Parcel; +import android.os.Parcelable; + +/** + * @hide + */ +public class StatusBarIcon implements Parcelable { + public String iconPackage; + public int iconId; + public int iconLevel; + public boolean visible = true; + public int number; + + private StatusBarIcon() { + } + + public StatusBarIcon(String iconPackage, int iconId, int iconLevel) { + this.iconPackage = iconPackage; + this.iconId = iconId; + this.iconLevel = iconLevel; + } + + public StatusBarIcon(String iconPackage, int iconId, int iconLevel, int number) { + this.iconPackage = iconPackage; + this.iconId = iconId; + this.iconLevel = iconLevel; + this.number = number; + } + + public String toString() { + return "StatusBarIcon(pkg=" + this.iconPackage + " id=0x" + Integer.toHexString(this.iconId) + + " level=" + this.iconLevel + " visible=" + visible + + " num=" + this.number + " )"; + } + + public StatusBarIcon clone() { + StatusBarIcon that = new StatusBarIcon(this.iconPackage, this.iconId, this.iconLevel); + that.visible = this.visible; + that.number = this.number; + return that; + } + + /** + * Unflatten the StatusBarIcon from a parcel. + */ + public StatusBarIcon(Parcel in) { + readFromParcel(in); + } + + public void readFromParcel(Parcel in) { + this.iconPackage = in.readString(); + this.iconId = in.readInt(); + this.iconLevel = in.readInt(); + this.visible = in.readInt() != 0; + this.number = in.readInt(); + } + + public void writeToParcel(Parcel out, int flags) { + out.writeString(this.iconPackage); + out.writeInt(this.iconId); + out.writeInt(this.iconLevel); + out.writeInt(this.visible ? 1 : 0); + out.writeInt(this.number); + } + + public int describeContents() { + return 0; + } + + /** + * Parcelable.Creator that instantiates StatusBarIcon objects + */ + public static final Parcelable.Creator<StatusBarIcon> CREATOR + = new Parcelable.Creator<StatusBarIcon>() + { + public StatusBarIcon createFromParcel(Parcel parcel) + { + return new StatusBarIcon(parcel); + } + + public StatusBarIcon[] newArray(int size) + { + return new StatusBarIcon[size]; + } + }; +} + diff --git a/core/java/com/android/internal/statusbar/StatusBarIconList.aidl b/core/java/com/android/internal/statusbar/StatusBarIconList.aidl new file mode 100644 index 0000000..c745120 --- /dev/null +++ b/core/java/com/android/internal/statusbar/StatusBarIconList.aidl @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2010, 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 com.android.internal.statusbar; + +parcelable StatusBarIconList; + diff --git a/core/java/com/android/internal/statusbar/StatusBarIconList.java b/core/java/com/android/internal/statusbar/StatusBarIconList.java new file mode 100644 index 0000000..478d245 --- /dev/null +++ b/core/java/com/android/internal/statusbar/StatusBarIconList.java @@ -0,0 +1,161 @@ +/* + * 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 com.android.internal.statusbar; + +import android.os.Parcel; +import android.os.Parcelable; + +import java.io.PrintWriter; + +public class StatusBarIconList implements Parcelable { + private String[] mSlots; + private StatusBarIcon[] mIcons; + + public StatusBarIconList() { + } + + public StatusBarIconList(Parcel in) { + readFromParcel(in); + } + + public void readFromParcel(Parcel in) { + this.mSlots = in.readStringArray(); + final int N = in.readInt(); + if (N < 0) { + mIcons = null; + } else { + mIcons = new StatusBarIcon[N]; + for (int i=0; i<N; i++) { + if (in.readInt() != 0) { + mIcons[i] = new StatusBarIcon(in); + } + } + } + } + + public void writeToParcel(Parcel out, int flags) { + out.writeStringArray(mSlots); + if (mIcons == null) { + out.writeInt(-1); + } else { + final int N = mIcons.length; + out.writeInt(N); + for (int i=0; i<N; i++) { + StatusBarIcon ic = mIcons[i]; + if (ic == null) { + out.writeInt(0); + } else { + out.writeInt(1); + ic.writeToParcel(out, flags); + } + } + } + } + + public int describeContents() { + return 0; + } + + /** + * Parcelable.Creator that instantiates StatusBarIconList objects + */ + public static final Parcelable.Creator<StatusBarIconList> CREATOR + = new Parcelable.Creator<StatusBarIconList>() + { + public StatusBarIconList createFromParcel(Parcel parcel) + { + return new StatusBarIconList(parcel); + } + + public StatusBarIconList[] newArray(int size) + { + return new StatusBarIconList[size]; + } + }; + + public void defineSlots(String[] slots) { + final int N = slots.length; + String[] s = mSlots = new String[N]; + for (int i=0; i<N; i++) { + s[i] = slots[i]; + } + mIcons = new StatusBarIcon[N]; + } + + public int getSlotIndex(String slot) { + final int N = mSlots.length; + for (int i=0; i<N; i++) { + if (slot.equals(mSlots[i])) { + return i; + } + } + return -1; + } + + public int size() { + return mSlots.length; + } + + public void setIcon(int index, StatusBarIcon icon) { + mIcons[index] = icon.clone(); + } + + public void removeIcon(int index) { + mIcons[index] = null; + } + + public String getSlot(int index) { + return mSlots[index]; + } + + public StatusBarIcon getIcon(int index) { + return mIcons[index]; + } + + public int getViewIndex(int index) { + int count = 0; + for (int i=0; i<index; i++) { + if (mIcons[i] != null) { + count++; + } + } + return count; + } + + public void copyFrom(StatusBarIconList that) { + if (that.mSlots == null) { + this.mSlots = null; + this.mIcons = null; + } else { + final int N = that.mSlots.length; + this.mSlots = new String[N]; + this.mIcons = new StatusBarIcon[N]; + for (int i=0; i<N; i++) { + this.mSlots[i] = that.mSlots[i]; + this.mIcons[i] = that.mIcons[i] != null ? that.mIcons[i].clone() : null; + } + } + } + + public void dump(PrintWriter pw) { + final int N = mSlots.length; + pw.println("Icon list:"); + for (int i=0; i<N; i++) { + pw.printf(" %2d: (%s) %s\n", i, mSlots[i], mIcons[i]); + } + } +} diff --git a/core/java/com/android/internal/statusbar/StatusBarNotification.aidl b/core/java/com/android/internal/statusbar/StatusBarNotification.aidl new file mode 100644 index 0000000..bd9e89c --- /dev/null +++ b/core/java/com/android/internal/statusbar/StatusBarNotification.aidl @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2010, 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 com.android.internal.statusbar; + +parcelable StatusBarNotification; + diff --git a/core/java/com/android/internal/statusbar/StatusBarNotification.java b/core/java/com/android/internal/statusbar/StatusBarNotification.java new file mode 100644 index 0000000..5499676 --- /dev/null +++ b/core/java/com/android/internal/statusbar/StatusBarNotification.java @@ -0,0 +1,116 @@ +/* + * 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 com.android.internal.statusbar; + +import android.app.Notification; +import android.os.Parcel; +import android.os.Parcelable; +import android.widget.RemoteViews; + + +/* +boolean clearable = !n.ongoingEvent && ((notification.flags & Notification.FLAG_NO_CLEAR) == 0); + + +// TODO: make this restriction do something smarter like never fill +// more than two screens. "Why would anyone need more than 80 characters." :-/ +final int maxTickerLen = 80; +if (truncatedTicker != null && truncatedTicker.length() > maxTickerLen) { + truncatedTicker = truncatedTicker.subSequence(0, maxTickerLen); +} +*/ + +public class StatusBarNotification implements Parcelable { + public String pkg; + public int id; + public String tag; + public Notification notification; + + public StatusBarNotification() { + } + + public StatusBarNotification(String pkg, int id, String tag, Notification notification) { + if (pkg == null) throw new NullPointerException(); + if (notification == null) throw new NullPointerException(); + + this.pkg = pkg; + this.id = id; + this.tag = tag; + this.notification = notification; + } + + public StatusBarNotification(Parcel in) { + readFromParcel(in); + } + + public void readFromParcel(Parcel in) { + this.pkg = in.readString(); + this.id = in.readInt(); + if (in.readInt() != 0) { + this.tag = in.readString(); + } else { + this.tag = null; + } + this.notification = new Notification(in); + } + + public void writeToParcel(Parcel out, int flags) { + out.writeString(this.pkg); + out.writeInt(this.id); + if (this.tag != null) { + out.writeInt(1); + out.writeString(this.tag); + } else { + out.writeInt(0); + } + this.notification.writeToParcel(out, flags); + } + + public int describeContents() { + return 0; + } + + public static final Parcelable.Creator<StatusBarNotification> CREATOR + = new Parcelable.Creator<StatusBarNotification>() + { + public StatusBarNotification createFromParcel(Parcel parcel) + { + return new StatusBarNotification(parcel); + } + + public StatusBarNotification[] newArray(int size) + { + return new StatusBarNotification[size]; + } + }; + + public StatusBarNotification clone() { + return new StatusBarNotification(this.pkg, this.id, this.tag, this.notification.clone()); + } + + public String toString() { + return "StatusBarNotification(package=" + pkg + " id=" + id + " tag=" + tag + + " notification=" + notification + ")"; + } + + public boolean isOngoing() { + return (notification.flags & Notification.FLAG_ONGOING_EVENT) != 0; + } + +} + + diff --git a/core/java/com/android/internal/statusbar/StatusBarNotificationList.aidl b/core/java/com/android/internal/statusbar/StatusBarNotificationList.aidl new file mode 100644 index 0000000..10abeee --- /dev/null +++ b/core/java/com/android/internal/statusbar/StatusBarNotificationList.aidl @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2010, 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 com.android.internal.statusbar; + +parcelable StatusBarNotificationList; + diff --git a/core/java/com/android/internal/util/HierarchicalStateMachine.java b/core/java/com/android/internal/util/HierarchicalStateMachine.java index 9911f48..c599d68 100644 --- a/core/java/com/android/internal/util/HierarchicalStateMachine.java +++ b/core/java/com/android/internal/util/HierarchicalStateMachine.java @@ -51,7 +51,7 @@ import java.util.HashMap; mS2 mS1 ----> initial state </code> * After the state machine is created and started, messages are sent to a state - * machine using <code>sendMessage</code and the messages are created using + * machine using <code>sendMessage</code> and the messages are created using * <code>obtainMessage</code>. When the state machine receives a message the * current state's <code>processMessage</code> is invoked. In the above example * mS1.processMessage will be invoked first. The state may use <code>transitionTo</code> @@ -59,9 +59,9 @@ import java.util.HashMap; * * Each state in the state machine may have a zero or one parent states and if * a child state is unable to handle a message it may have the message processed - * by its parent by returning false. If a message is never processed <code>unhandledMessage</code> - * will be invoked to give one last chance for the state machine to process - * the message. + * by its parent by returning false or NOT_HANDLED. If a message is never processed + * <code>unhandledMessage</code> will be invoked to give one last chance for the state machine + * to process the message. * * When all processing is completed a state machine may choose to call * <code>transitionToHaltingState</code>. When the current <code>processingMessage</code> @@ -95,7 +95,7 @@ import java.util.HashMap; * any other messages that are on the queue or might be added later. Both of * these are protected and may only be invoked from within a state machine. * - * To illustrate some of these properties we'll use state machine with 8 + * To illustrate some of these properties we'll use state machine with an 8 * state hierarchy: <code> mP0 @@ -109,44 +109,19 @@ import java.util.HashMap; * * After starting mS5 the list of active states is mP0, mP1, mS1 and mS5. * So the order of calling processMessage when a message is received is mS5, - * mS1, mP1, mP0 assuming each processMessage indicates it can't handle this - * message by returning false. + * mS1, mP1, mP0 assuming each processMessage indicates it can't handle this + * message by returning false or NOT_HANDLED. * * Now assume mS5.processMessage receives a message it can handle, and during - * the handling determines the machine should changes states. It would call - * transitionTo(mS4) and return true. Immediately after returning from + * the handling determines the machine should change states. It could call + * transitionTo(mS4) and return true or HANDLED. Immediately after returning from * processMessage the state machine runtime will find the common parent, * which is mP1. It will then call mS5.exit, mS1.exit, mS2.enter and then * mS4.enter. The new list of active states is mP0, mP1, mS2 and mS4. So * when the next message is received mS4.processMessage will be invoked. * - * To assist in describing an HSM a simple grammar has been created which - * is informally defined here and a formal EBNF description is at the end - * of the class comment. - * - * An HSM starts with the name and includes a set of hierarchical states. - * A state is preceeded by one or more plus signs (+), to indicate its - * depth and a hash (#) if its the initial state. Child states follow their - * parents and have one more plus sign then their parent. Inside a state - * are a series of messages, the actions they perform and if the processing - * is complete ends with a period (.). If processing isn't complete and - * the parent should process the message it ends with a caret (^). The - * actions include send a message ($MESSAGE), defer a message (%MESSAGE), - * transition to a new state (>MESSAGE) and an if statement - * (if ( expression ) { list of actions }.) - * - * The Hsm HelloWorld could documented as: - * - * HelloWorld { - * + # mState1. - * } - * - * and interpreted as HSM HelloWorld: - * - * mState1 a root state (single +) and initial state (#) which - * processes all messages completely, the period (.). - * - * The implementation is: + * Now for some concrete examples, here is the canonical HelloWorld as an HSM. + * It responds with "Hello World" being printed to the log for every message. <code> class HelloWorld extends HierarchicalStateMachine { Hsm1(String name) { @@ -164,7 +139,7 @@ class HelloWorld extends HierarchicalStateMachine { class State1 extends HierarchicalState { @Override public boolean processMessage(Message message) { Log.d(TAG, "Hello World"); - return true; + return HANDLED; } } State1 mState1 = new State1(); @@ -176,7 +151,7 @@ void testHelloWorld() { } </code> * - * A more interesting state machine is one of four states + * A more interesting state machine is one with four states * with two independent parent states. <code> mP1 mP2 @@ -184,45 +159,68 @@ void testHelloWorld() { mS2 mS1 </code> * - * documented as: + * Here is a description of this state machine using pseudo code. * - * Hsm1 { - * + mP1 { - * CMD_2 { - * $CMD_3 - * %CMD_2 - * >mS2 - * }. - * } - * ++ # mS1 { CMD_1{ >mS1 }^ } - * ++ mS2 { - * CMD_2{$CMD_4}. - * CMD_3{%CMD_3 ; >mP2}. - * } * - * + mP2 e($CMD_5) { - * CMD_3, CMD_4. - * CMD_5{>HALT}. - * } + * state mP1 { + * enter { log("mP1.enter"); } + * exit { log("mP1.exit"); } + * on msg { + * CMD_2 { + * send(CMD_3); + * defer(msg); + * transitonTo(mS2); + * return HANDLED; + * } + * return NOT_HANDLED; + * } * } * - * and interpreted as HierarchicalStateMachine Hsm1: - * - * mP1 a root state. - * processes message CMD_2 which sends CMD_3, defers CMD_2, and transitions to mS2 - * - * mS1 a child of mP1 is the initial state: - * processes message CMD_1 which transitions to itself and returns false to let mP1 handle it. + * INITIAL + * state mS1 parent mP1 { + * enter { log("mS1.enter"); } + * exit { log("mS1.exit"); } + * on msg { + * CMD_1 { + * transitionTo(mS1); + * return HANDLED; + * } + * return NOT_HANDLED; + * } + * } * - * mS2 a child of mP1: - * processes message CMD_2 which send CMD_4 - * processes message CMD_3 which defers CMD_3 and transitions to mP2 + * state mS2 parent mP1 { + * enter { log("mS2.enter"); } + * exit { log("mS2.exit"); } + * on msg { + * CMD_2 { + * send(CMD_4); + * return HANDLED; + * } + * CMD_3 { + * defer(msg); + * transitionTo(mP2); + * return HANDLED; + * } + * return NOT_HANDLED; + * } + * } * - * mP2 a root state. - * on enter it sends CMD_5 - * processes message CMD_3 - * processes message CMD_4 - * processes message CMD_5 which transitions to halt state + * state mP2 { + * enter { + * log("mP2.enter"); + * send(CMD_5); + * } + * exit { log("mP2.exit"); } + * on msg { + * CMD_3, CMD_4 { return HANDLED; } + * CMD_5 { + * transitionTo(HaltingState); + * return HANDLED; + * } + * return NOT_HANDLED; + * } + * } * * The implementation is below and also in HierarchicalStateMachineTest: <code> @@ -271,11 +269,11 @@ class Hsm1 extends HierarchicalStateMachine { sendMessage(obtainMessage(CMD_3)); deferMessage(message); transitionTo(mS2); - retVal = true; + retVal = HANDLED; break; default: // Any message we don't understand in this state invokes unhandledMessage - retVal = false; + retVal = NOT_HANDLED; break; } return retVal; @@ -294,10 +292,10 @@ class Hsm1 extends HierarchicalStateMachine { if (message.what == CMD_1) { // Transition to ourself to show that enter/exit is called transitionTo(mS1); - return true; + return HANDLED; } else { // Let parent process all other messages - return false; + return NOT_HANDLED; } } @Override public void exit() { @@ -315,15 +313,15 @@ class Hsm1 extends HierarchicalStateMachine { switch(message.what) { case(CMD_2): sendMessage(obtainMessage(CMD_4)); - retVal = true; + retVal = HANDLED; break; case(CMD_3): deferMessage(message); transitionTo(mP2); - retVal = true; + retVal = HANDLED; break; default: - retVal = false; + retVal = NOT_HANDLED; break; } return retVal; @@ -349,7 +347,7 @@ class Hsm1 extends HierarchicalStateMachine { transitionToHaltingState(); break; } - return true; + return HANDLED; } @Override public void exit() { Log.d(TAG, "mP2.exit"); @@ -357,7 +355,7 @@ class Hsm1 extends HierarchicalStateMachine { } @Override - protected void halting() { + void halting() { Log.d(TAG, "halting"); synchronized (this) { this.notifyAll(); @@ -413,53 +411,32 @@ class Hsm1 extends HierarchicalStateMachine { * D/hsm1 ( 1999): mP2.exit * D/hsm1 ( 1999): halting * - * Here is the HSM a BNF grammar, this is a first stab at creating an - * HSM description language, suggestions corrections or alternatives - * would be much appreciated. - * - * Legend: - * {} ::= zero or more - * {}+ ::= one or more - * [] ::= zero or one - * () ::= define a group with "or" semantics. - * - * HSM EBNF: - * HSM = HSM_NAME "{" { STATE }+ "}" ; - * HSM_NAME = alpha_numeric_name ; - * STATE = INTRODUCE_STATE [ ENTER | [ ENTER EXIT ] "{" [ MESSAGES ] "}" [ EXIT ] ; - * INTRODUCE_STATE = { STATE_DEPTH }+ [ INITIAL_STATE_INDICATOR ] STATE_NAME ; - * STATE_DEPTH = "+" ; - * INITIAL_STATE_INDICATOR = "#" - * ENTER = "e(" SEND_ACTION | TRANSITION_ACTION | HALT_ACTION ")" ; - * MESSAGES = { MSG_LIST MESSAGE_ACTIONS } ; - * MSG_LIST = { MSG_NAME { "," MSG_NAME } }; - * EXIT = "x(" SEND_ACTION | TRANSITION_ACTION | HALT_ACTION ")" ; - * PROCESS_COMPLETION = PROCESS_IN_PARENT_OR_COMPLETE | PROCESS_COMPLETE ; - * SEND_ACTION = "$" MSG_NAME ; - * DEFER_ACTION = "%" MSG_NAME ; - * TRANSITION_ACTION = ">" STATE_NAME ; - * HALT_ACTION = ">" HALT ; - * MESSAGE_ACTIONS = { "{" ACTION_LIST "}" } [ PROCESS_COMPLETION ] ; - * ACTION_LIST = ACTION { (";" | "\n") ACTION } ; - * ACTION = IF_ACTION | SEND_ACTION | DEFER_ACTION | TRANSITION_ACTION | HALT_ACTION ; - * IF_ACTION = "if(" boolean_expression ")" "{" ACTION_LIST "}" - * PROCESS_IN_PARENT_OR_COMPLETE = "^" ; - * PROCESS_COMPLETE = "." ; - * STATE_NAME = alpha_numeric_name ; - * MSG_NAME = alpha_numeric_name | ALL_OTHER_MESSAGES ; - * ALL_OTHER_MESSAGES = "*" ; - * EXP = boolean_expression ; - * - * Idioms: - * * { %* }. ::= All other messages will be deferred. */ public class HierarchicalStateMachine { private static final String TAG = "HierarchicalStateMachine"; private String mName; + /** Message.what value when quitting */ public static final int HSM_QUIT_CMD = -1; + /** Message.what value when initializing */ + public static final int HSM_INIT_CMD = -1; + + /** + * Convenience constant that maybe returned by processMessage + * to indicate the the message was processed and is not to be + * processed by parent states + */ + public static final boolean HANDLED = true; + + /** + * Convenience constant that maybe returned by processMessage + * to indicate the the message was NOT processed and is to be + * processed by parent states + */ + public static final boolean NOT_HANDLED = false; + private static class HsmHandler extends Handler { /** The debug flag */ @@ -468,6 +445,12 @@ public class HierarchicalStateMachine { /** The quit object */ private static final Object mQuitObj = new Object(); + /** The initialization message */ + private static final Message mInitMsg = null; + + /** The current message */ + private Message mMsg; + /** A list of messages that this state machine has processed */ private ProcessedMessages mProcessedMessages = new ProcessedMessages(); @@ -550,8 +533,7 @@ public class HierarchicalStateMachine { private class QuittingState extends HierarchicalState { @Override public boolean processMessage(Message msg) { - // Ignore - return false; + return NOT_HANDLED; } } @@ -565,6 +547,9 @@ public class HierarchicalStateMachine { public final void handleMessage(Message msg) { if (mDbg) Log.d(TAG, "handleMessage: E msg.what=" + msg.what); + /** Save the current message */ + mMsg = msg; + /** * Check that construction was completed */ @@ -679,6 +664,7 @@ public class HierarchicalStateMachine { * starting at the first entry. */ mIsConstructionCompleted = true; + mMsg = obtainMessage(HSM_INIT_CMD); invokeEnterMethods(0); /** @@ -855,6 +841,13 @@ public class HierarchicalStateMachine { } /** + * @return current message + */ + private final Message getCurrentMessage() { + return mMsg; + } + + /** * @return current state */ private final HierarchicalState getCurrentState() { @@ -1025,6 +1018,14 @@ public class HierarchicalStateMachine { protected final void addState(HierarchicalState state, HierarchicalState parent) { mHsmHandler.addState(state, parent); } + + /** + * @return current message + */ + protected final Message getCurrentMessage() { + return mHsmHandler.getCurrentMessage(); + } + /** * @return current state */ @@ -1032,7 +1033,6 @@ public class HierarchicalStateMachine { return mHsmHandler.getCurrentState(); } - /** * Add a new state to the state machine, parent will be null * @param state to add diff --git a/core/java/com/android/internal/view/BaseSurfaceHolder.java b/core/java/com/android/internal/view/BaseSurfaceHolder.java index e0d3a5f..3a04993 100644 --- a/core/java/com/android/internal/view/BaseSurfaceHolder.java +++ b/core/java/com/android/internal/view/BaseSurfaceHolder.java @@ -33,9 +33,11 @@ public abstract class BaseSurfaceHolder implements SurfaceHolder { public final ArrayList<SurfaceHolder.Callback> mCallbacks = new ArrayList<SurfaceHolder.Callback>(); - + SurfaceHolder.Callback[] mGottenCallbacks; + boolean mHaveGottenCallbacks; + public final ReentrantLock mSurfaceLock = new ReentrantLock(); - public final Surface mSurface = new Surface(); + public Surface mSurface = new Surface(); int mRequestedWidth = -1; int mRequestedHeight = -1; @@ -83,6 +85,31 @@ public abstract class BaseSurfaceHolder implements SurfaceHolder { } } + public SurfaceHolder.Callback[] getCallbacks() { + if (mHaveGottenCallbacks) { + return mGottenCallbacks; + } + + synchronized (mCallbacks) { + final int N = mCallbacks.size(); + if (N > 0) { + if (mGottenCallbacks == null || mGottenCallbacks.length != N) { + mGottenCallbacks = new SurfaceHolder.Callback[N]; + } + mCallbacks.toArray(mGottenCallbacks); + } else { + mGottenCallbacks = null; + } + mHaveGottenCallbacks = true; + } + + return mGottenCallbacks; + } + + public void ungetCallbacks() { + mHaveGottenCallbacks = false; + } + public void setFixedSize(int width, int height) { if (mRequestedWidth != width || mRequestedHeight != height) { mRequestedWidth = width; diff --git a/core/java/com/android/internal/view/IInputMethodSession.aidl b/core/java/com/android/internal/view/IInputMethodSession.aidl index a05ff14..338dcaa 100644 --- a/core/java/com/android/internal/view/IInputMethodSession.aidl +++ b/core/java/com/android/internal/view/IInputMethodSession.aidl @@ -48,4 +48,6 @@ oneway interface IInputMethodSession { void appPrivateCommand(String action, in Bundle data); void toggleSoftInput(int showFlags, int hideFlags); + + void finishSession(); } diff --git a/core/java/com/android/internal/view/RootViewSurfaceTaker.java b/core/java/com/android/internal/view/RootViewSurfaceTaker.java new file mode 100644 index 0000000..fcb1645 --- /dev/null +++ b/core/java/com/android/internal/view/RootViewSurfaceTaker.java @@ -0,0 +1,11 @@ +package com.android.internal.view; + +import android.view.SurfaceHolder; + +/** hahahah */ +public interface RootViewSurfaceTaker { + SurfaceHolder.Callback willYouTakeTheSurface(); + void setSurfaceType(int type); + void setSurfaceFormat(int format); + void setSurfaceKeepScreenOn(boolean keepOn); +} diff --git a/core/java/com/android/internal/widget/DigitalClock.java b/core/java/com/android/internal/widget/DigitalClock.java index 23e2277..fa47ff6 100644 --- a/core/java/com/android/internal/widget/DigitalClock.java +++ b/core/java/com/android/internal/widget/DigitalClock.java @@ -30,7 +30,7 @@ import android.provider.Settings; import android.text.format.DateFormat; import android.util.AttributeSet; import android.view.View; -import android.widget.RelativeLayout; +import android.widget.LinearLayout; import android.widget.TextView; import java.text.DateFormatSymbols; @@ -39,7 +39,7 @@ import java.util.Calendar; /** * Displays the time */ -public class DigitalClock extends RelativeLayout { +public class DigitalClock extends LinearLayout { private final static String M12 = "h:mm"; private final static String M24 = "kk:mm"; diff --git a/core/java/com/google/android/mms/ContentType.java b/core/java/com/google/android/mms/ContentType.java index 94bc9fd..b066fad 100644 --- a/core/java/com/google/android/mms/ContentType.java +++ b/core/java/com/google/android/mms/ContentType.java @@ -26,6 +26,7 @@ public class ContentType { public static final String MMS_GENERIC = "application/vnd.wap.mms-generic"; public static final String MULTIPART_MIXED = "application/vnd.wap.multipart.mixed"; public static final String MULTIPART_RELATED = "application/vnd.wap.multipart.related"; + public static final String MULTIPART_ALTERNATIVE = "application/vnd.wap.multipart.alternative"; public static final String TEXT_PLAIN = "text/plain"; public static final String TEXT_HTML = "text/html"; diff --git a/core/java/com/google/android/mms/pdu/PduParser.java b/core/java/com/google/android/mms/pdu/PduParser.java index d465c5a..1cd118b 100644 --- a/core/java/com/google/android/mms/pdu/PduParser.java +++ b/core/java/com/google/android/mms/pdu/PduParser.java @@ -200,7 +200,18 @@ public class PduParser { PduHeaders headers = new PduHeaders(); while (keepParsing && (pduDataStream.available() > 0)) { + pduDataStream.mark(1); int headerField = extractByteValue(pduDataStream); + /* parse custom text header */ + if ((headerField >= TEXT_MIN) && (headerField <= TEXT_MAX)) { + pduDataStream.reset(); + byte [] bVal = parseWapString(pduDataStream, TYPE_TEXT_STRING); + if (LOCAL_LOGV) { + Log.v(LOG_TAG, "TextHeader: " + new String(bVal)); + } + /* we should ignore it at the moment */ + continue; + } switch (headerField) { case PduHeaders.MESSAGE_TYPE: { @@ -778,26 +789,34 @@ public class PduParser { /* get part's data */ if (dataLength > 0) { byte[] partData = new byte[dataLength]; + String partContentType = new String(part.getContentType()); pduDataStream.read(partData, 0, dataLength); - // Check Content-Transfer-Encoding. - byte[] partDataEncoding = part.getContentTransferEncoding(); - if (null != partDataEncoding) { - String encoding = new String(partDataEncoding); - if (encoding.equalsIgnoreCase(PduPart.P_BASE64)) { - // Decode "base64" into "binary". - partData = Base64.decodeBase64(partData); - } else if (encoding.equalsIgnoreCase(PduPart.P_QUOTED_PRINTABLE)) { - // Decode "quoted-printable" into "binary". - partData = QuotedPrintable.decodeQuotedPrintable(partData); - } else { - // "binary" is the default encoding. + if (partContentType.equalsIgnoreCase(ContentType.MULTIPART_ALTERNATIVE)) { + // parse "multipart/vnd.wap.multipart.alternative". + PduBody childBody = parseParts(new ByteArrayInputStream(partData)); + // take the first part of children. + part = childBody.getPart(0); + } else { + // Check Content-Transfer-Encoding. + byte[] partDataEncoding = part.getContentTransferEncoding(); + if (null != partDataEncoding) { + String encoding = new String(partDataEncoding); + if (encoding.equalsIgnoreCase(PduPart.P_BASE64)) { + // Decode "base64" into "binary". + partData = Base64.decodeBase64(partData); + } else if (encoding.equalsIgnoreCase(PduPart.P_QUOTED_PRINTABLE)) { + // Decode "quoted-printable" into "binary". + partData = QuotedPrintable.decodeQuotedPrintable(partData); + } else { + // "binary" is the default encoding. + } } + if (null == partData) { + log("Decode part data error!"); + return null; + } + part.setData(partData); } - if (null == partData) { - log("Decode part data error!"); - return null; - } - part.setData(partData); } /* add this part to body */ |