diff options
80 files changed, 4077 insertions, 672 deletions
diff --git a/cmds/pm/src/com/android/commands/pm/Pm.java b/cmds/pm/src/com/android/commands/pm/Pm.java index d764aa9..f2b6fce 100644 --- a/cmds/pm/src/com/android/commands/pm/Pm.java +++ b/cmds/pm/src/com/android/commands/pm/Pm.java @@ -30,14 +30,15 @@ import android.content.pm.InstrumentationInfo; import android.content.pm.PackageInfo; import android.content.pm.PackageItemInfo; import android.content.pm.PackageManager; +import android.content.pm.ParceledListSlice; import android.content.pm.PermissionGroupInfo; import android.content.pm.PermissionInfo; import android.content.res.AssetManager; import android.content.res.Resources; import android.net.Uri; +import android.os.Parcel; import android.os.RemoteException; import android.os.ServiceManager; -import android.provider.Settings; import java.io.File; import java.lang.reflect.Field; @@ -223,7 +224,7 @@ public final class Pm { String filter = nextArg(); try { - List<PackageInfo> packages = mPm.getInstalledPackages(getFlags); + final List<PackageInfo> packages = getInstalledPackages(mPm, getFlags); int count = packages.size(); for (int p = 0 ; p < count ; p++) { @@ -247,6 +248,22 @@ public final class Pm { } } + @SuppressWarnings("unchecked") + private List<PackageInfo> getInstalledPackages(IPackageManager pm, int flags) + throws RemoteException { + final List<PackageInfo> packageInfos = new ArrayList<PackageInfo>(); + PackageInfo lastItem = null; + ParceledListSlice<PackageInfo> slice; + + do { + final String lastKey = lastItem != null ? lastItem.packageName : null; + slice = pm.getInstalledPackages(flags, lastKey); + lastItem = slice.populateList(packageInfos, PackageInfo.CREATOR); + } while (!slice.isLastSlice()); + + return packageInfos; + } + /** * Lists all of the features supported by the current device. * diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java index 0e473c9..fe4bfcf 100644 --- a/core/java/android/app/ContextImpl.java +++ b/core/java/android/app/ContextImpl.java @@ -48,6 +48,7 @@ import android.content.pm.IPackageStatsObserver; import android.content.pm.InstrumentationInfo; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; +import android.content.pm.ParceledListSlice; import android.content.pm.PermissionGroupInfo; import android.content.pm.PermissionInfo; import android.content.pm.ProviderInfo; @@ -83,6 +84,7 @@ import android.os.Handler; import android.os.IBinder; import android.os.IPowerManager; import android.os.Looper; +import android.os.Parcel; import android.os.PowerManager; import android.os.Process; import android.os.RemoteException; @@ -1998,19 +2000,41 @@ class ContextImpl extends Context { throw new NameNotFoundException("No shared userid for user:"+sharedUserName); } + @SuppressWarnings("unchecked") @Override public List<PackageInfo> getInstalledPackages(int flags) { try { - return mPM.getInstalledPackages(flags); + final List<PackageInfo> packageInfos = new ArrayList<PackageInfo>(); + PackageInfo lastItem = null; + ParceledListSlice<PackageInfo> slice; + + do { + final String lastKey = lastItem != null ? lastItem.packageName : null; + slice = mPM.getInstalledPackages(flags, lastKey); + lastItem = slice.populateList(packageInfos, PackageInfo.CREATOR); + } while (!slice.isLastSlice()); + + return packageInfos; } catch (RemoteException e) { throw new RuntimeException("Package manager has died", e); } } + @SuppressWarnings("unchecked") @Override public List<ApplicationInfo> getInstalledApplications(int flags) { try { - return mPM.getInstalledApplications(flags); + final List<ApplicationInfo> applicationInfos = new ArrayList<ApplicationInfo>(); + ApplicationInfo lastItem = null; + ParceledListSlice<ApplicationInfo> slice; + + do { + final String lastKey = lastItem != null ? lastItem.packageName : null; + slice = mPM.getInstalledApplications(flags, lastKey); + lastItem = slice.populateList(applicationInfos, ApplicationInfo.CREATOR); + } while (!slice.isLastSlice()); + + return applicationInfos; } catch (RemoteException e) { throw new RuntimeException("Package manager has died", e); } diff --git a/core/java/android/bluetooth/BluetoothA2dp.java b/core/java/android/bluetooth/BluetoothA2dp.java index 7e5f858..8218c03 100644 --- a/core/java/android/bluetooth/BluetoothA2dp.java +++ b/core/java/android/bluetooth/BluetoothA2dp.java @@ -269,6 +269,22 @@ public final class BluetoothA2dp { } } + /** + * Allow or disallow incoming connection + * @param device Sink + * @param value True / False + * @return Success or Failure of the binder call. + */ + public boolean allowIncomingConnect(BluetoothDevice device, boolean value) { + if (DBG) log("allowIncomingConnect(" + device + ":" + value + ")"); + try { + return mService.allowIncomingConnect(device, value); + } catch (RemoteException e) { + Log.e(TAG, "", e); + return false; + } + } + /** Helper for converting a state to a string. * For debug use only - strings are not internationalized. * @hide diff --git a/core/java/android/bluetooth/BluetoothDevice.java b/core/java/android/bluetooth/BluetoothDevice.java index aee6ad8..e67ace0 100644 --- a/core/java/android/bluetooth/BluetoothDevice.java +++ b/core/java/android/bluetooth/BluetoothDevice.java @@ -276,6 +276,33 @@ public final class BluetoothDevice implements Parcelable { public static final String ACTION_PAIRING_CANCEL = "android.bluetooth.device.action.PAIRING_CANCEL"; + /** @hide */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_CONNECTION_ACCESS_REQUEST = + "android.bluetooth.device.action.CONNECTION_ACCESS_REQUEST"; + + /** @hide */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_CONNECTION_ACCESS_REPLY = + "android.bluetooth.device.action.CONNECTION_ACCESS_REPLY"; + + /** @hide */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_CONNECTION_ACCESS_CANCEL = + "android.bluetooth.device.action.CONNECTION_ACCESS_CANCEL"; + /** + * Used as an extra field in {@link #ACTION_CONNECTION_ACCESS_REPLY} intent. + * @hide + */ + public static final String EXTRA_CONNECTION_ACCESS_RESULT = + "android.bluetooth.device.extra.CONNECTION_ACCESS_RESULT"; + + /**@hide*/ + public static final int CONNECTION_ACCESS_YES = 1; + + /**@hide*/ + public static final int CONNECTION_ACCESS_NO = 2; + /** A bond attempt succeeded * @hide */ public static final int BOND_SUCCESS = 0; diff --git a/core/java/android/bluetooth/BluetoothDeviceProfileState.java b/core/java/android/bluetooth/BluetoothDeviceProfileState.java index 9d7e641..8896451 100644 --- a/core/java/android/bluetooth/BluetoothDeviceProfileState.java +++ b/core/java/android/bluetooth/BluetoothDeviceProfileState.java @@ -21,9 +21,11 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.os.Message; +import android.os.PowerManager; import android.server.BluetoothA2dpService; import android.server.BluetoothService; import android.util.Log; +import android.util.Pair; import com.android.internal.util.HierarchicalState; import com.android.internal.util.HierarchicalStateMachine; @@ -73,9 +75,17 @@ public final class BluetoothDeviceProfileState extends HierarchicalStateMachine public static final int AUTO_CONNECT_PROFILES = 101; public static final int TRANSITION_TO_STABLE = 102; public static final int CONNECT_OTHER_PROFILES = 103; + private static final int CONNECTION_ACCESS_REQUEST_REPLY = 104; + private static final int CONNECTION_ACCESS_REQUEST_EXPIRY = 105; private static final int AUTO_CONNECT_DELAY = 6000; // 6 secs private static final int CONNECT_OTHER_PROFILES_DELAY = 4000; // 4 secs + private static final int CONNECTION_ACCESS_REQUEST_EXPIRY_TIMEOUT = 7000; // 7 secs + private static final int CONNECTION_ACCESS_UNDEFINED = -1; + private static final long INIT_INCOMING_REJECT_TIMER = 1000; // 1 sec + private static final long MAX_INCOMING_REJECT_TIMER = 3600 * 1000 * 4; // 4 hours + + private static final String PREFS_NAME = "ConnectionAccess"; private BondedDevice mBondedDevice = new BondedDevice(); private OutgoingHandsfree mOutgoingHandsfree = new OutgoingHandsfree(); @@ -90,10 +100,16 @@ public final class BluetoothDeviceProfileState extends HierarchicalStateMachine private BluetoothPbap mPbapService; private boolean mHeadsetServiceConnected; private boolean mPbapServiceConnected; + private static final String BLUETOOTH_ADMIN_PERM = android.Manifest.permission.BLUETOOTH_ADMIN; private BluetoothDevice mDevice; private int mHeadsetState; private int mA2dpState; + private long mIncomingRejectTimer; + private boolean mConnectionAccessReplyReceived = false; + private Pair<Integer, String> mIncomingConnections; + private PowerManager.WakeLock mWakeLock; + private PowerManager mPowerManager; private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { @Override @@ -108,6 +124,10 @@ public final class BluetoothDeviceProfileState extends HierarchicalStateMachine int initiator = intent.getIntExtra( BluetoothHeadset.EXTRA_DISCONNECT_INITIATOR, BluetoothHeadset.LOCAL_DISCONNECT); + // We trust this device now + if (newState == BluetoothHeadset.STATE_CONNECTED) { + setTrust(BluetoothDevice.CONNECTION_ACCESS_YES); + } mHeadsetState = newState; if (newState == BluetoothHeadset.STATE_DISCONNECTED && initiator == BluetoothHeadset.REMOTE_DISCONNECT) { @@ -121,6 +141,10 @@ public final class BluetoothDeviceProfileState extends HierarchicalStateMachine int newState = intent.getIntExtra(BluetoothA2dp.EXTRA_SINK_STATE, 0); int oldState = intent.getIntExtra(BluetoothA2dp.EXTRA_PREVIOUS_SINK_STATE, 0); mA2dpState = newState; + // We trust this device now + if (newState == BluetoothA2dp.STATE_CONNECTED) { + setTrust(BluetoothDevice.CONNECTION_ACCESS_YES); + } if ((oldState == BluetoothA2dp.STATE_CONNECTED || oldState == BluetoothA2dp.STATE_PLAYING) && newState == BluetoothA2dp.STATE_DISCONNECTED) { @@ -134,6 +158,13 @@ public final class BluetoothDeviceProfileState extends HierarchicalStateMachine // This is technically not needed, but we can get stuck sometimes. // For example, if incoming A2DP fails, we are not informed by Bluez sendMessage(TRANSITION_TO_STABLE); + } else if (action.equals(BluetoothDevice.ACTION_CONNECTION_ACCESS_REPLY)) { + mWakeLock.release(); + int val = intent.getIntExtra(BluetoothDevice.EXTRA_CONNECTION_ACCESS_RESULT, + BluetoothDevice.CONNECTION_ACCESS_NO); + Message msg = obtainMessage(CONNECTION_ACCESS_REQUEST_REPLY); + msg.arg1 = val; + sendMessage(msg); } } }; @@ -174,11 +205,20 @@ public final class BluetoothDeviceProfileState extends HierarchicalStateMachine filter.addAction(BluetoothHeadset.ACTION_STATE_CHANGED); filter.addAction(BluetoothDevice.ACTION_ACL_CONNECTED); filter.addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED); + filter.addAction(BluetoothDevice.ACTION_CONNECTION_ACCESS_REPLY); mContext.registerReceiver(mBroadcastReceiver, filter); HeadsetServiceListener l = new HeadsetServiceListener(); PbapServiceListener p = new PbapServiceListener(); + + mIncomingConnections = mService.getIncomingState(address); + mIncomingRejectTimer = readTimerValue(); + mPowerManager = (PowerManager)mContext.getSystemService(Context.POWER_SERVICE); + mWakeLock = mPowerManager.newWakeLock(PowerManager.SCREEN_DIM_WAKE_LOCK | + PowerManager.ACQUIRE_CAUSES_WAKEUP | + PowerManager.ON_AFTER_RELEASE, TAG); + mWakeLock.setReferenceCounted(false); } private class HeadsetServiceListener implements BluetoothHeadset.ServiceListener { @@ -438,6 +478,24 @@ public final class BluetoothDeviceProfileState extends HierarchicalStateMachine // Ignore Log.e(TAG, "Error: Incoming connection with a pending incoming connection"); break; + case CONNECTION_ACCESS_REQUEST_REPLY: + int val = message.arg1; + mConnectionAccessReplyReceived = true; + boolean value = false; + if (val == BluetoothDevice.CONNECTION_ACCESS_YES) { + value = true; + } + setTrust(val); + + handleIncomingConnection(CONNECT_HFP_INCOMING, value); + break; + case CONNECTION_ACCESS_REQUEST_EXPIRY: + if (!mConnectionAccessReplyReceived) { + handleIncomingConnection(CONNECT_HFP_INCOMING, false); + sendConnectionAccessRemovalIntent(); + sendMessage(TRANSITION_TO_STABLE); + } + break; case CONNECT_A2DP_INCOMING: // Serialize the commands. deferMessage(message); @@ -607,6 +665,25 @@ public final class BluetoothDeviceProfileState extends HierarchicalStateMachine case CONNECT_A2DP_INCOMING: // ignore break; + case CONNECTION_ACCESS_REQUEST_REPLY: + int val = message.arg1; + mConnectionAccessReplyReceived = true; + boolean value = false; + if (val == BluetoothDevice.CONNECTION_ACCESS_YES) { + value = true; + } + setTrust(val); + handleIncomingConnection(CONNECT_A2DP_INCOMING, value); + break; + case CONNECTION_ACCESS_REQUEST_EXPIRY: + // The check protects the race condition between REQUEST_REPLY + // and the timer expiry. + if (!mConnectionAccessReplyReceived) { + handleIncomingConnection(CONNECT_A2DP_INCOMING, false); + sendConnectionAccessRemovalIntent(); + sendMessage(TRANSITION_TO_STABLE); + } + break; case CONNECT_A2DP_OUTGOING: // Defer message and retry deferMessage(message); @@ -662,8 +739,138 @@ public final class BluetoothDeviceProfileState extends HierarchicalStateMachine deferMessage(msg); } + private void updateIncomingAllowedTimer() { + // Not doing a perfect exponential backoff because + // we want two different rates. For all practical + // purposes, this is good enough. + if (mIncomingRejectTimer == 0) mIncomingRejectTimer = INIT_INCOMING_REJECT_TIMER; + + mIncomingRejectTimer *= 5; + if (mIncomingRejectTimer > MAX_INCOMING_REJECT_TIMER) { + mIncomingRejectTimer = MAX_INCOMING_REJECT_TIMER; + } + writeTimerValue(mIncomingRejectTimer); + } + + private boolean handleIncomingConnection(int command, boolean accept) { + boolean ret = false; + Log.i(TAG, "handleIncomingConnection:" + command + ":" + accept); + switch (command) { + case CONNECT_HFP_INCOMING: + if (!accept) { + ret = mHeadsetService.rejectIncomingConnect(mDevice); + sendMessage(TRANSITION_TO_STABLE); + updateIncomingAllowedTimer(); + } else if (mHeadsetState == BluetoothHeadset.STATE_CONNECTING) { + writeTimerValue(0); + ret = mHeadsetService.acceptIncomingConnect(mDevice); + } else if (mHeadsetState == BluetoothHeadset.STATE_DISCONNECTED) { + writeTimerValue(0); + handleConnectionOfOtherProfiles(command); + ret = mHeadsetService.createIncomingConnect(mDevice); + } + break; + case CONNECT_A2DP_INCOMING: + if (!accept) { + ret = mA2dpService.allowIncomingConnect(mDevice, false); + sendMessage(TRANSITION_TO_STABLE); + updateIncomingAllowedTimer(); + } else { + writeTimerValue(0); + ret = mA2dpService.allowIncomingConnect(mDevice, true); + handleConnectionOfOtherProfiles(command); + } + break; + default: + Log.e(TAG, "Waiting for incoming connection but state changed to:" + command); + break; + } + return ret; + } + + private void sendConnectionAccessIntent() { + mConnectionAccessReplyReceived = false; + + if (!mPowerManager.isScreenOn()) mWakeLock.acquire(); + + Intent intent = new Intent(BluetoothDevice.ACTION_CONNECTION_ACCESS_REQUEST); + intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mDevice); + mContext.sendBroadcast(intent, BLUETOOTH_ADMIN_PERM); + } + + private void sendConnectionAccessRemovalIntent() { + mWakeLock.release(); + Intent intent = new Intent(BluetoothDevice.ACTION_CONNECTION_ACCESS_CANCEL); + intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mDevice); + mContext.sendBroadcast(intent, BLUETOOTH_ADMIN_PERM); + } + + private int getTrust() { + String address = mDevice.getAddress(); + if (mIncomingConnections != null) return mIncomingConnections.first; + return CONNECTION_ACCESS_UNDEFINED; + } + + + private String getStringValue(long value) { + StringBuilder sbr = new StringBuilder(); + sbr.append(Long.toString(System.currentTimeMillis())); + sbr.append("-"); + sbr.append(Long.toString(value)); + return sbr.toString(); + } + + private void setTrust(int value) { + String second; + if (mIncomingConnections == null) { + second = getStringValue(INIT_INCOMING_REJECT_TIMER); + } else { + second = mIncomingConnections.second; + } + + mIncomingConnections = new Pair(value, second); + mService.writeIncomingConnectionState(mDevice.getAddress(), mIncomingConnections); + } + + private void writeTimerValue(long value) { + Integer first; + if (mIncomingConnections == null) { + first = CONNECTION_ACCESS_UNDEFINED; + } else { + first = mIncomingConnections.first; + } + mIncomingConnections = new Pair(first, getStringValue(value)); + mService.writeIncomingConnectionState(mDevice.getAddress(), mIncomingConnections); + } + + private long readTimerValue() { + if (mIncomingConnections == null) + return 0; + String value = mIncomingConnections.second; + String[] splits = value.split("-"); + if (splits != null && splits.length == 2) { + return Long.parseLong(splits[1]); + } + return 0; + } + + private boolean readIncomingAllowedValue() { + if (readTimerValue() == 0) return true; + String value = mIncomingConnections.second; + String[] splits = value.split("-"); + if (splits != null && splits.length == 2) { + long val1 = Long.parseLong(splits[0]); + long val2 = Long.parseLong(splits[1]); + if (val1 + val2 <= System.currentTimeMillis()) { + return true; + } + } + return false; + } + synchronized boolean processCommand(int command) { - Log.i(TAG, "Processing command:" + command); + Log.e(TAG, "Processing command:" + command); + Message msg; switch(command) { case CONNECT_HFP_OUTGOING: if (mHeadsetService != null) { @@ -673,11 +880,21 @@ public final class BluetoothDeviceProfileState extends HierarchicalStateMachine case CONNECT_HFP_INCOMING: if (!mHeadsetServiceConnected) { deferProfileServiceMessage(command); - } else if (mHeadsetState == BluetoothHeadset.STATE_CONNECTING) { - return mHeadsetService.acceptIncomingConnect(mDevice); - } else if (mHeadsetState == BluetoothHeadset.STATE_DISCONNECTED) { - handleConnectionOfOtherProfiles(command); - return mHeadsetService.createIncomingConnect(mDevice); + } else { + // Check if device is already trusted + int access = getTrust(); + if (access == BluetoothDevice.CONNECTION_ACCESS_YES) { + handleIncomingConnection(command, true); + } else if (access == BluetoothDevice.CONNECTION_ACCESS_NO && + !readIncomingAllowedValue()) { + handleIncomingConnection(command, false); + } else { + sendConnectionAccessIntent(); + msg = obtainMessage(CONNECTION_ACCESS_REQUEST_EXPIRY); + sendMessageDelayed(msg, + CONNECTION_ACCESS_REQUEST_EXPIRY_TIMEOUT); + } + return true; } break; case CONNECT_A2DP_OUTGOING: @@ -686,8 +903,19 @@ public final class BluetoothDeviceProfileState extends HierarchicalStateMachine } break; case CONNECT_A2DP_INCOMING: - handleConnectionOfOtherProfiles(command); - // ignore, Bluez takes care + // Check if device is already trusted + int access = getTrust(); + if (access == BluetoothDevice.CONNECTION_ACCESS_YES) { + handleIncomingConnection(command, true); + } else if (access == BluetoothDevice.CONNECTION_ACCESS_NO && + !readIncomingAllowedValue()) { + handleIncomingConnection(command, false); + } else { + sendConnectionAccessIntent(); + msg = obtainMessage(CONNECTION_ACCESS_REQUEST_EXPIRY); + sendMessageDelayed(msg, + CONNECTION_ACCESS_REQUEST_EXPIRY_TIMEOUT); + } return true; case DISCONNECT_HFP_OUTGOING: if (!mHeadsetServiceConnected) { @@ -728,6 +956,8 @@ public final class BluetoothDeviceProfileState extends HierarchicalStateMachine } break; case UNPAIR: + writeTimerValue(INIT_INCOMING_REJECT_TIMER); + setTrust(CONNECTION_ACCESS_UNDEFINED); return mService.removeBondInternal(mDevice.getAddress()); default: Log.e(TAG, "Error: Unknown Command"); diff --git a/core/java/android/bluetooth/BluetoothHeadset.java b/core/java/android/bluetooth/BluetoothHeadset.java index da1aa45..f64f826 100644 --- a/core/java/android/bluetooth/BluetoothHeadset.java +++ b/core/java/android/bluetooth/BluetoothHeadset.java @@ -494,6 +494,23 @@ public final class BluetoothHeadset { } /** + * Reject the incoming connection. + * @hide + */ + public boolean rejectIncomingConnect(BluetoothDevice device) { + if (DBG) log("rejectIncomingConnect"); + if (mService != null) { + try { + return mService.rejectIncomingConnect(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 diff --git a/core/java/android/bluetooth/IBluetoothA2dp.aidl b/core/java/android/bluetooth/IBluetoothA2dp.aidl index 40f1058..0c2cf1b 100644 --- a/core/java/android/bluetooth/IBluetoothA2dp.aidl +++ b/core/java/android/bluetooth/IBluetoothA2dp.aidl @@ -36,4 +36,6 @@ interface IBluetoothA2dp { boolean connectSinkInternal(in BluetoothDevice device); boolean disconnectSinkInternal(in BluetoothDevice device); + boolean allowIncomingConnect(in BluetoothDevice device, boolean value); + } diff --git a/core/java/android/bluetooth/IBluetoothHeadset.aidl b/core/java/android/bluetooth/IBluetoothHeadset.aidl index d96f0ca..62bceee 100644 --- a/core/java/android/bluetooth/IBluetoothHeadset.aidl +++ b/core/java/android/bluetooth/IBluetoothHeadset.aidl @@ -37,6 +37,7 @@ interface IBluetoothHeadset { boolean createIncomingConnect(in BluetoothDevice device); boolean acceptIncomingConnect(in BluetoothDevice device); + boolean rejectIncomingConnect(in BluetoothDevice device); boolean cancelConnectThread(); boolean connectHeadsetInternal(in BluetoothDevice device); boolean disconnectHeadsetInternal(in BluetoothDevice device); diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl index bfc9185..5df5eba 100644 --- a/core/java/android/content/pm/IPackageManager.aidl +++ b/core/java/android/content/pm/IPackageManager.aidl @@ -30,6 +30,7 @@ import android.content.pm.IPackageMoveObserver; import android.content.pm.IPackageStatsObserver; import android.content.pm.InstrumentationInfo; import android.content.pm.PackageInfo; +import android.content.pm.ParceledListSlice; import android.content.pm.ProviderInfo; import android.content.pm.PermissionGroupInfo; import android.content.pm.PermissionInfo; @@ -109,9 +110,21 @@ interface IPackageManager { List<ResolveInfo> queryIntentServices(in Intent intent, String resolvedType, int flags); - List<PackageInfo> getInstalledPackages(int flags); + /** + * This implements getInstalledPackages via a "last returned row" + * mechanism that is not exposed in the API. This is to get around the IPC + * limit that kicks in when flags are included that bloat up the data + * returned. + */ + ParceledListSlice getInstalledPackages(int flags, in String lastRead); - List<ApplicationInfo> getInstalledApplications(int flags); + /** + * This implements getInstalledApplications via a "last returned row" + * mechanism that is not exposed in the API. This is to get around the IPC + * limit that kicks in when flags are included that bloat up the data + * returned. + */ + ParceledListSlice getInstalledApplications(int flags, in String lastRead); /** * Retrieve all applications that are marked as persistent. diff --git a/core/java/android/content/pm/ParceledListSlice.aidl b/core/java/android/content/pm/ParceledListSlice.aidl new file mode 100755 index 0000000..c02cc6a --- /dev/null +++ b/core/java/android/content/pm/ParceledListSlice.aidl @@ -0,0 +1,19 @@ +/* + * Copyright 2011, 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.content.pm; + +parcelable ParceledListSlice; diff --git a/core/java/android/content/pm/ParceledListSlice.java b/core/java/android/content/pm/ParceledListSlice.java new file mode 100644 index 0000000..f3a98db --- /dev/null +++ b/core/java/android/content/pm/ParceledListSlice.java @@ -0,0 +1,170 @@ +/* + * Copyright (C) 2011 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.content.pm; + +import android.os.Parcel; +import android.os.Parcelable; + +import java.util.List; + +/** + * Builds up a parcel that is discarded when written to another parcel or + * written to a list. This is useful for API that sends huge lists across a + * Binder that may be larger than the IPC limit. + * + * @hide + */ +public class ParceledListSlice<T extends Parcelable> implements Parcelable { + /* + * TODO get this number from somewhere else. For now set it to a quarter of + * the 1MB limit. + */ + private static final int MAX_IPC_SIZE = 256 * 1024; + + private Parcel mParcel; + + private int mNumItems; + + private boolean mIsLastSlice; + + public ParceledListSlice() { + mParcel = Parcel.obtain(); + } + + private ParceledListSlice(Parcel p, int numItems, boolean lastSlice) { + mParcel = p; + mNumItems = numItems; + mIsLastSlice = lastSlice; + } + + @Override + public int describeContents() { + return 0; + } + + /** + * Write this to another Parcel. Note that this discards the internal Parcel + * and should not be used anymore. This is so we can pass this to a Binder + * where we won't have a chance to call recycle on this. + */ + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(mNumItems); + dest.writeInt(mIsLastSlice ? 1 : 0); + + if (mNumItems > 0) { + final int parcelSize = mParcel.dataSize(); + dest.writeInt(parcelSize); + dest.appendFrom(mParcel, 0, parcelSize); + } + + mNumItems = 0; + mParcel.recycle(); + mParcel = null; + } + + /** + * Appends a parcel to this list slice. + * + * @param item Parcelable item to append to this list slice + * @return true when the list slice is full and should not be appended to + * anymore + */ + public boolean append(T item) { + if (mParcel == null) { + throw new IllegalStateException("ParceledListSlice has already been recycled"); + } + + item.writeToParcel(mParcel, PARCELABLE_WRITE_RETURN_VALUE); + mNumItems++; + + return mParcel.dataSize() > MAX_IPC_SIZE; + } + + /** + * Populates a list and discards the internal state of the + * ParceledListSlice in the process. The instance should + * not be used anymore. + * + * @param list list to insert items from this slice. + * @param creator creator that knows how to unparcel the + * target object type. + * @return the last item inserted into the list or null if none. + */ + public T populateList(List<T> list, Creator<T> creator) { + mParcel.setDataPosition(0); + + T item = null; + for (int i = 0; i < mNumItems; i++) { + item = creator.createFromParcel(mParcel); + list.add(item); + } + + mParcel.recycle(); + mParcel = null; + + return item; + } + + /** + * Sets whether this is the last list slice in the series. + * + * @param lastSlice + */ + public void setLastSlice(boolean lastSlice) { + mIsLastSlice = lastSlice; + } + + /** + * Returns whether this is the last slice in a series of slices. + * + * @return true if this is the last slice in the series. + */ + public boolean isLastSlice() { + return mIsLastSlice; + } + + @SuppressWarnings("unchecked") + public static final Parcelable.Creator<ParceledListSlice> CREATOR = + new Parcelable.Creator<ParceledListSlice>() { + public ParceledListSlice createFromParcel(Parcel in) { + final int numItems = in.readInt(); + final boolean lastSlice = in.readInt() == 1; + + if (numItems > 0) { + final int parcelSize = in.readInt(); + + // Advance within this Parcel + int offset = in.dataPosition(); + in.setDataPosition(offset + parcelSize); + + Parcel p = Parcel.obtain(); + p.setDataPosition(0); + p.appendFrom(in, offset, parcelSize); + p.setDataPosition(0); + + return new ParceledListSlice(p, numItems, lastSlice); + } else { + return new ParceledListSlice(); + } + } + + public ParceledListSlice[] newArray(int size) { + return new ParceledListSlice[size]; + } + }; +} diff --git a/core/java/android/nfc/INfcAdapterExtras.aidl b/core/java/android/nfc/INfcAdapterExtras.aidl index 8677a50..0c2a2fd 100755 --- a/core/java/android/nfc/INfcAdapterExtras.aidl +++ b/core/java/android/nfc/INfcAdapterExtras.aidl @@ -16,7 +16,6 @@ package android.nfc; -import android.nfc.ApduList; import android.os.Bundle; @@ -29,6 +28,5 @@ interface INfcAdapterExtras { Bundle transceive(in byte[] data_in); int getCardEmulationRoute(); void setCardEmulationRoute(int route); - void registerTearDownApdus(String packageName, in ApduList apdu); - void unregisterTearDownApdus(String packageName); + void authenticate(in byte[] token); } diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 38c5dc4..75bdc2b 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -2600,27 +2600,6 @@ public final class Settings { public static final String WIFI_SAVED_STATE = "wifi_saved_state"; /** - * AP SSID - * - * @hide - */ - public static final String WIFI_AP_SSID = "wifi_ap_ssid"; - - /** - * AP security - * - * @hide - */ - public static final String WIFI_AP_SECURITY = "wifi_ap_security"; - - /** - * AP passphrase - * - * @hide - */ - public static final String WIFI_AP_PASSWD = "wifi_ap_passwd"; - - /** * The acceptable packet loss percentage (range 0 - 100) before trying * another AP on the same network. */ diff --git a/core/java/android/provider/Telephony.java b/core/java/android/provider/Telephony.java index 62f66b6..2e480e8 100755 --- a/core/java/android/provider/Telephony.java +++ b/core/java/android/provider/Telephony.java @@ -542,7 +542,7 @@ public final class Telephony { * values:</p> * * <ul> - * <li><em>pdus</em> - An Object[] od byte[]s containing the PDUs + * <li><em>pdus</em> - An Object[] of byte[]s containing the PDUs * that make up the message.</li> * </ul> * @@ -586,6 +586,46 @@ public final class Telephony { "android.provider.Telephony.WAP_PUSH_RECEIVED"; /** + * Broadcast Action: A new Cell Broadcast message has been received + * by the device. The intent will have the following extra + * values:</p> + * + * <ul> + * <li><em>pdus</em> - An Object[] of byte[]s containing the PDUs + * that make up the message.</li> + * </ul> + * + * <p>The extra values can be extracted using + * {@link #getMessagesFromIntent(Intent)}.</p> + * + * <p>If a BroadcastReceiver encounters an error while processing + * this intent it should set the result code appropriately.</p> + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String SMS_CB_RECEIVED_ACTION = + "android.provider.Telephony.SMS_CB_RECEIVED"; + + /** + * Broadcast Action: A new Emergency Broadcast message has been received + * by the device. The intent will have the following extra + * values:</p> + * + * <ul> + * <li><em>pdus</em> - An Object[] of byte[]s containing the PDUs + * that make up the message.</li> + * </ul> + * + * <p>The extra values can be extracted using + * {@link #getMessagesFromIntent(Intent)}.</p> + * + * <p>If a BroadcastReceiver encounters an error while processing + * this intent it should set the result code appropriately.</p> + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String SMS_EMERGENCY_CB_RECEIVED_ACTION = + "android.provider.Telephony.SMS_EMERGENCY_CB_RECEIVED"; + + /** * Broadcast Action: The SIM storage for SMS messages is full. If * space is not freed, messages targeted for the SIM (class 2) may * not be saved. @@ -618,7 +658,7 @@ public final class Telephony { * @param intent the intent to read from * @return an array of SmsMessages for the PDUs */ - public static final SmsMessage[] getMessagesFromIntent( + public static SmsMessage[] getMessagesFromIntent( Intent intent) { Object[] messages = (Object[]) intent.getSerializableExtra("pdus"); byte[][] pduObjs = new byte[messages.length][]; diff --git a/core/java/android/server/BluetoothA2dpService.java b/core/java/android/server/BluetoothA2dpService.java index 6e221c8..bea01f3 100644 --- a/core/java/android/server/BluetoothA2dpService.java +++ b/core/java/android/server/BluetoothA2dpService.java @@ -457,6 +457,22 @@ public class BluetoothA2dpService extends IBluetoothA2dp.Stub { Settings.Secure.getBluetoothA2dpSinkPriorityKey(device.getAddress()), priority); } + public synchronized boolean allowIncomingConnect(BluetoothDevice device, boolean value) { + mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, + "Need BLUETOOTH_ADMIN permission"); + String address = device.getAddress(); + if (!BluetoothAdapter.checkBluetoothAddress(address)) { + return false; + } + Integer data = mBluetoothService.getAuthorizationAgentRequestData(address); + if (data == null) { + Log.w(TAG, "allowIncomingConnect(" + device + ") called but no native data available"); + return false; + } + log("allowIncomingConnect: A2DP: " + device + ":" + value); + return mBluetoothService.setAuthorizationNative(address, value, data.intValue()); + } + private synchronized void onSinkPropertyChanged(String path, String []propValues) { if (!mBluetoothService.isEnabled()) { return; diff --git a/core/java/android/server/BluetoothEventLoop.java b/core/java/android/server/BluetoothEventLoop.java index 568d465..5cc9144 100644 --- a/core/java/android/server/BluetoothEventLoop.java +++ b/core/java/android/server/BluetoothEventLoop.java @@ -50,6 +50,7 @@ class BluetoothEventLoop { private boolean mInterrupted; private final HashMap<String, Integer> mPasskeyAgentRequestData; + private final HashMap<String, Integer> mAuthorizationAgentRequestData; private final BluetoothService mBluetoothService; private final BluetoothAdapter mAdapter; private final Context mContext; @@ -109,6 +110,7 @@ class BluetoothEventLoop { mBluetoothService = bluetoothService; mContext = context; mPasskeyAgentRequestData = new HashMap(); + mAuthorizationAgentRequestData = new HashMap<String, Integer>(); mAdapter = adapter; //WakeLock instantiation in BluetoothEventLoop class PowerManager pm = (PowerManager)context.getSystemService(Context.POWER_SERVICE); @@ -130,6 +132,10 @@ class BluetoothEventLoop { return mPasskeyAgentRequestData; } + /* package */ HashMap<String, Integer> getAuthorizationAgentRequestData() { + return mAuthorizationAgentRequestData; + } + /* package */ void start() { if (!isEventLoopRunningNative()) { @@ -518,27 +524,29 @@ class BluetoothEventLoop { mContext.sendBroadcast(intent, BLUETOOTH_ADMIN_PERM); } - private boolean onAgentAuthorize(String objectPath, String deviceUuid) { + private void onAgentAuthorize(String objectPath, String deviceUuid, int nativeData) { String address = mBluetoothService.getAddressFromObjectPath(objectPath); if (address == null) { Log.e(TAG, "Unable to get device address in onAuthAgentAuthorize"); - return false; + return; } boolean authorized = false; ParcelUuid uuid = ParcelUuid.fromString(deviceUuid); BluetoothA2dp a2dp = new BluetoothA2dp(mContext); + BluetoothDevice device = mAdapter.getRemoteDevice(address); + mAuthorizationAgentRequestData.put(address, new Integer(nativeData)); + // Bluez sends the UUID of the local service being accessed, _not_ the // remote service if (mBluetoothService.isEnabled() && (BluetoothUuid.isAudioSource(uuid) || BluetoothUuid.isAvrcpTarget(uuid) || BluetoothUuid.isAdvAudioDist(uuid)) && !isOtherSinkInNonDisconnectingState(address)) { - BluetoothDevice device = mAdapter.getRemoteDevice(address); authorized = a2dp.getSinkPriority(device) > BluetoothA2dp.PRIORITY_OFF; if (authorized) { - Log.i(TAG, "Allowing incoming A2DP / AVRCP connection from " + address); + Log.i(TAG, "First check pass for incoming A2DP / AVRCP connection from " + address); // Some headsets try to connect AVCTP before AVDTP - against the recommendation // If AVCTP connection fails, we get stuck in IncomingA2DP state in the state // machine. We don't handle AVCTP signals currently. We only send @@ -546,6 +554,8 @@ class BluetoothEventLoop { // some cases. For now, just don't move to incoming state in this case. if (!BluetoothUuid.isAvrcpTarget(uuid)) { mBluetoothService.notifyIncomingA2dpConnection(address); + } else { + a2dp.allowIncomingConnect(device, authorized); } } else { Log.i(TAG, "Rejecting incoming A2DP / AVRCP connection from " + address); @@ -554,7 +564,7 @@ class BluetoothEventLoop { Log.i(TAG, "Rejecting incoming " + deviceUuid + " connection from " + address); } log("onAgentAuthorize(" + objectPath + ", " + deviceUuid + ") = " + authorized); - return authorized; + if (!authorized) a2dp.allowIncomingConnect(device, authorized); } private boolean onAgentOutOfBandDataAvailable(String objectPath) { diff --git a/core/java/android/server/BluetoothService.java b/core/java/android/server/BluetoothService.java index 6144766..643581e 100755 --- a/core/java/android/server/BluetoothService.java +++ b/core/java/android/server/BluetoothService.java @@ -27,8 +27,8 @@ package android.server; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothClass; import android.bluetooth.BluetoothDevice; -import android.bluetooth.BluetoothHeadset; import android.bluetooth.BluetoothDeviceProfileState; +import android.bluetooth.BluetoothHeadset; import android.bluetooth.BluetoothProfileState; import android.bluetooth.BluetoothSocket; import android.bluetooth.BluetoothUuid; @@ -67,6 +67,7 @@ import java.io.FileWriter; import java.io.IOException; import java.io.InputStreamReader; import java.io.PrintWriter; +import java.io.RandomAccessFile; import java.io.UnsupportedEncodingException; import java.util.ArrayList; import java.util.Arrays; @@ -103,7 +104,6 @@ public class BluetoothService extends IBluetooth.Stub { private static final int MESSAGE_REGISTER_SDP_RECORDS = 1; private static final int MESSAGE_FINISH_DISABLE = 2; private static final int MESSAGE_UUID_INTENT = 3; - private static final int MESSAGE_DISCOVERABLE_TIMEOUT = 4; private static final int MESSAGE_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY = 5; // The time (in millisecs) to delay the pairing attempt after the first @@ -143,6 +143,11 @@ public class BluetoothService extends IBluetooth.Stub { private static String mDockAddress; private String mDockPin; + private static final String INCOMING_CONNECTION_FILE = + "/data/misc/bluetooth/incoming_connection.conf"; + private HashMap<String, Pair<Integer, String>> mIncomingConnections; + + private static class RemoteService { public String address; public ParcelUuid uuid; @@ -210,6 +215,7 @@ public class BluetoothService extends IBluetooth.Stub { filter.addAction(Intent.ACTION_DOCK_EVENT); mContext.registerReceiver(mReceiver, filter); + mIncomingConnections = new HashMap<String, Pair<Integer, String>>(); } public static synchronized String readDockBluetoothAddress() { @@ -501,15 +507,6 @@ public class BluetoothService extends IBluetooth.Stub { makeServiceChannelCallbacks(address); } break; - case MESSAGE_DISCOVERABLE_TIMEOUT: - int mode = msg.arg1; - if (isEnabledInternal()) { - // TODO: Switch back to the previous scan mode - // This is ok for now, because we only use - // CONNECTABLE and CONNECTABLE_DISCOVERABLE - setScanMode(BluetoothAdapter.SCAN_MODE_CONNECTABLE, -1); - } - break; case MESSAGE_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY: address = (String)msg.obj; if (address != null) { @@ -743,8 +740,6 @@ 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 + " (" + @@ -1078,21 +1073,17 @@ public class BluetoothService extends IBluetooth.Stub { switch (mode) { case BluetoothAdapter.SCAN_MODE_NONE: - mHandler.removeMessages(MESSAGE_DISCOVERABLE_TIMEOUT); pairable = false; discoverable = false; break; case BluetoothAdapter.SCAN_MODE_CONNECTABLE: - mHandler.removeMessages(MESSAGE_DISCOVERABLE_TIMEOUT); pairable = true; discoverable = false; break; case BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE: - mHandler.removeMessages(MESSAGE_DISCOVERABLE_TIMEOUT); + setDiscoverableTimeout(duration); pairable = true; discoverable = true; - Message msg = mHandler.obtainMessage(MESSAGE_DISCOVERABLE_TIMEOUT); - mHandler.sendMessageDelayed(msg, duration * 1000); if (DBG) Log.d(TAG, "BT Discoverable for " + duration + " seconds"); break; default: @@ -1326,6 +1317,8 @@ public class BluetoothService extends IBluetooth.Stub { } public synchronized boolean removeBondInternal(String address) { + // Unset the trusted device state and then unpair + setTrust(address, false); return removeDeviceNative(getObjectPathFromAddress(address)); } @@ -2175,10 +2168,6 @@ public class BluetoothService extends IBluetooth.Stub { return state; } - private void removeProfileState(String address) { - mDeviceProfileState.remove(address); - } - private void initProfileState() { String []bonds = null; String val = getPropertyInternal("Devices"); @@ -2227,6 +2216,11 @@ public class BluetoothService extends IBluetooth.Stub { mA2dpService = a2dpService; } + /*package*/ Integer getAuthorizationAgentRequestData(String address) { + Integer data = mEventLoop.getAuthorizationAgentRequestData().remove(address); + return data; + } + public void sendProfileStateMessage(int profile, int cmd) { Message msg = new Message(); msg.what = cmd; @@ -2237,6 +2231,116 @@ public class BluetoothService extends IBluetooth.Stub { } } + private void createIncomingConnectionStateFile() { + File f = new File(INCOMING_CONNECTION_FILE); + if (!f.exists()) { + try { + f.createNewFile(); + } catch (IOException e) { + Log.e(TAG, "IOException: cannot create file"); + } + } + } + + /** @hide */ + public Pair<Integer, String> getIncomingState(String address) { + if (mIncomingConnections.isEmpty()) { + createIncomingConnectionStateFile(); + readIncomingConnectionState(); + } + return mIncomingConnections.get(address); + } + + private void readIncomingConnectionState() { + synchronized(mIncomingConnections) { + FileInputStream fstream = null; + try { + fstream = new FileInputStream(INCOMING_CONNECTION_FILE); + DataInputStream in = new DataInputStream(fstream); + BufferedReader file = new BufferedReader(new InputStreamReader(in)); + String line; + while((line = file.readLine()) != null) { + line = line.trim(); + if (line.length() == 0) continue; + String[] value = line.split(","); + if (value != null && value.length == 3) { + Integer val1 = Integer.parseInt(value[1]); + Pair<Integer, String> val = new Pair(val1, value[2]); + mIncomingConnections.put(value[0], val); + } + } + } catch (FileNotFoundException e) { + log("FileNotFoundException: readIncomingConnectionState" + e.toString()); + } catch (IOException e) { + log("IOException: readIncomingConnectionState" + e.toString()); + } finally { + if (fstream != null) { + try { + fstream.close(); + } catch (IOException e) { + // Ignore + } + } + } + } + } + + private void truncateIncomingConnectionFile() { + RandomAccessFile r = null; + try { + r = new RandomAccessFile(INCOMING_CONNECTION_FILE, "rw"); + r.setLength(0); + } catch (FileNotFoundException e) { + log("FileNotFoundException: truncateIncomingConnectionState" + e.toString()); + } catch (IOException e) { + log("IOException: truncateIncomingConnectionState" + e.toString()); + } finally { + if (r != null) { + try { + r.close(); + } catch (IOException e) { + // ignore + } + } + } + } + + /** @hide */ + public void writeIncomingConnectionState(String address, Pair<Integer, String> data) { + synchronized(mIncomingConnections) { + mIncomingConnections.put(address, data); + + truncateIncomingConnectionFile(); + BufferedWriter out = null; + StringBuilder value = new StringBuilder(); + try { + out = new BufferedWriter(new FileWriter(INCOMING_CONNECTION_FILE, true)); + for (String devAddress: mIncomingConnections.keySet()) { + Pair<Integer, String> val = mIncomingConnections.get(devAddress); + value.append(devAddress); + value.append(","); + value.append(val.first.toString()); + value.append(","); + value.append(val.second); + value.append("\n"); + } + out.write(value.toString()); + } catch (FileNotFoundException e) { + log("FileNotFoundException: writeIncomingConnectionState" + e.toString()); + } catch (IOException e) { + log("IOException: writeIncomingConnectionState" + e.toString()); + } finally { + if (out != null) { + try { + out.close(); + } catch (IOException e) { + // Ignore + } + } + } + } + } + private static void log(String msg) { Log.d(TAG, msg); } @@ -2287,4 +2391,5 @@ public class BluetoothService extends IBluetooth.Stub { short channel); private native boolean removeServiceRecordNative(int handle); private native boolean setLinkTimeoutNative(String path, int num_slots); + native boolean setAuthorizationNative(String address, boolean value, int data); } diff --git a/core/jni/android_nfc_NdefMessage.cpp b/core/jni/android_nfc_NdefMessage.cpp index 9beef2a..aff8aa6 100644 --- a/core/jni/android_nfc_NdefMessage.cpp +++ b/core/jni/android_nfc_NdefMessage.cpp @@ -102,6 +102,19 @@ static jint android_nfc_NdefMessage_parseNdefMessage(JNIEnv *e, jobject o, } TRACE("phFriNfc_NdefRecord_Parse() returned 0x%04x", status); + // We don't exactly know what *is* a valid length, but a simple + // sanity check is to make sure that the length of the header + // plus all fields does not exceed raw_msg_size. The min length + // of the header is 3 bytes: TNF, Type Length, Payload Length + // (ID length field is optional!) + uint64_t indicatedMsgLength = 3 + record.TypeLength + record.IdLength + + (uint64_t)record.PayloadLength; + if (indicatedMsgLength > + (uint64_t)raw_msg_size) { + LOGE("phFri_NdefRecord_Parse: invalid length field"); + goto end; + } + type = e->NewByteArray(record.TypeLength); if (type == NULL) { LOGD("NFC_Set Record Type Error\n"); diff --git a/core/jni/android_server_BluetoothEventLoop.cpp b/core/jni/android_server_BluetoothEventLoop.cpp index de003df..73dfdbe 100644 --- a/core/jni/android_server_BluetoothEventLoop.cpp +++ b/core/jni/android_server_BluetoothEventLoop.cpp @@ -106,7 +106,7 @@ static void classInitNative(JNIEnv* env, jclass clazz) { "(Ljava/lang/String;Z)V"); method_onAgentAuthorize = env->GetMethodID(clazz, "onAgentAuthorize", - "(Ljava/lang/String;Ljava/lang/String;)Z"); + "(Ljava/lang/String;Ljava/lang/String;I)V"); method_onAgentOutOfBandDataAvailable = env->GetMethodID(clazz, "onAgentOutOfBandDataAvailable", "(Ljava/lang/String;)Z"); method_onAgentCancel = env->GetMethodID(clazz, "onAgentCancel", "()V"); @@ -917,29 +917,11 @@ DBusHandlerResult agent_event_filter(DBusConnection *conn, LOGV("... object_path = %s", object_path); LOGV("... uuid = %s", uuid); - bool auth_granted = - env->CallBooleanMethod(nat->me, method_onAgentAuthorize, - env->NewStringUTF(object_path), env->NewStringUTF(uuid)); + dbus_message_ref(msg); // increment refcount because we pass to java + env->CallBooleanMethod(nat->me, method_onAgentAuthorize, + env->NewStringUTF(object_path), env->NewStringUTF(uuid), + int(msg)); - // reply - if (auth_granted) { - DBusMessage *reply = dbus_message_new_method_return(msg); - if (!reply) { - LOGE("%s: Cannot create message reply\n", __FUNCTION__); - goto failure; - } - dbus_connection_send(nat->conn, reply, NULL); - dbus_message_unref(reply); - } else { - DBusMessage *reply = dbus_message_new_error(msg, - "org.bluez.Error.Rejected", "Authorization rejected"); - if (!reply) { - LOGE("%s: Cannot create message reply\n", __FUNCTION__); - goto failure; - } - dbus_connection_send(nat->conn, reply, NULL); - dbus_message_unref(reply); - } goto success; } else if (dbus_message_is_method_call(msg, "org.bluez.Agent", "OutOfBandAvailable")) { diff --git a/core/jni/android_server_BluetoothService.cpp b/core/jni/android_server_BluetoothService.cpp index 6cb8d4d..248b942 100644 --- a/core/jni/android_server_BluetoothService.cpp +++ b/core/jni/android_server_BluetoothService.cpp @@ -604,6 +604,35 @@ static jboolean setRemoteOutOfBandDataNative(JNIEnv *env, jobject object, jstrin return JNI_FALSE; } +static jboolean setAuthorizationNative(JNIEnv *env, jobject object, jstring address, + jboolean val, int nativeData) { +#ifdef HAVE_BLUETOOTH + LOGV(__FUNCTION__); + native_data_t *nat = get_native_data(env, object); + if (nat) { + DBusMessage *msg = (DBusMessage *)nativeData; + DBusMessage *reply; + if (val) { + reply = dbus_message_new_method_return(msg); + } else { + reply = dbus_message_new_error(msg, + "org.bluez.Error.Rejected", "Authorization rejected"); + } + if (!reply) { + LOGE("%s: Cannot create message reply D-Bus\n", __FUNCTION__); + dbus_message_unref(msg); + return JNI_FALSE; + } + + dbus_connection_send(nat->conn, reply, NULL); + dbus_message_unref(msg); + dbus_message_unref(reply); + return JNI_TRUE; + } +#endif + return JNI_FALSE; +} + static jboolean setPinNative(JNIEnv *env, jobject object, jstring address, jstring pin, int nativeData) { #ifdef HAVE_BLUETOOTH @@ -1034,6 +1063,7 @@ static JNINativeMethod sMethods[] = { (void *)setPairingConfirmationNative}, {"setPasskeyNative", "(Ljava/lang/String;II)Z", (void *)setPasskeyNative}, {"setRemoteOutOfBandDataNative", "(Ljava/lang/String;[B[BI)Z", (void *)setRemoteOutOfBandDataNative}, + {"setAuthorizationNative", "(Ljava/lang/String;ZI)Z", (void *)setAuthorizationNative}, {"setPinNative", "(Ljava/lang/String;Ljava/lang/String;I)Z", (void *)setPinNative}, {"cancelPairingUserInputNative", "(Ljava/lang/String;I)Z", (void *)cancelPairingUserInputNative}, diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index c582eee..55cb6c8 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -150,6 +150,15 @@ android:label="@string/permlab_receiveMms" android:description="@string/permdesc_receiveMms" /> + <!-- Allows an application to receive emergency cell broadcast messages, + to record or display them to the user. Reserved for system apps. + @hide Pending API council approval --> + <permission android:name="android.permission.RECEIVE_EMERGENCY_BROADCAST" + android:permissionGroup="android.permission-group.MESSAGES" + android:protectionLevel="signatureOrSystem" + android:label="@string/permlab_receiveEmergencyBroadcast" + android:description="@string/permdesc_receiveEmergencyBroadcast" /> + <!-- Allows an application to read SMS messages. --> <permission android:name="android.permission.READ_SMS" android:permissionGroup="android.permission-group.MESSAGES" diff --git a/core/res/res/values-hr/strings.xml b/core/res/res/values-hr/strings.xml index fdc3679..91cc3ed 100644 --- a/core/res/res/values-hr/strings.xml +++ b/core/res/res/values-hr/strings.xml @@ -183,6 +183,8 @@ <string name="permdesc_receiveSms" msgid="6298292335965966117">"Aplikaciji omogućuje primanje i obradu SMS poruka. Zlonamjerne aplikacije mogu pratiti vaše poruke ili ih izbrisati prije nego što ih vi vidite."</string> <string name="permlab_receiveMms" msgid="8894700916188083287">"primanje MMS-a"</string> <string name="permdesc_receiveMms" msgid="4563346832000174373">"Aplikaciji omogućuje primanje i obradu MMS poruka. Zlonamjerne aplikacije mogu pratiti vaše poruke ili ih izbrisati prije nego što ih vi vidite."</string> + <string name="permlab_receiveEmergencyBroadcast" msgid="1803477660846288089">"primanje hitnih odašiljanja"</string> + <string name="permdesc_receiveEmergencyBroadcast" msgid="7118393393716546131">"Omogućuje aplikaciji primanje i obradu poruka hitnih odašiljanja. Ta je dozvola dostupna samo aplikacijama sustava."</string> <string name="permlab_sendSms" msgid="5600830612147671529">"slanje SMS poruka"</string> <string name="permdesc_sendSms" msgid="1946540351763502120">"Aplikaciji omogućuje slanje SMS poruka. Zlonamjerne aplikacije mogu stvarati troškove slanjem poruka bez vaše potvrde."</string> <string name="permlab_readSms" msgid="4085333708122372256">"čitanje SMS-a ili MMS-a"</string> diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 916dd81..abc27e2 100755 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -125,6 +125,7 @@ <!-- Regex array of allowable upstream ifaces for tethering - for example if you want tethering on a new interface called "foo2" add <item>"foo\\d"</item> to the array --> + <!-- Interfaces will be prioritized according to the order listed --> <string-array translatable="false" name="config_tether_upstream_regexs"> </string-array> @@ -478,4 +479,15 @@ <string name="config_wimaxServiceClassname"></string> <!-- Name of the wimax state tracker clas --> <string name="config_wimaxStateTrackerClassname"></string> + + <!-- Set to true if the RSSI should always display CDMA signal strength + even on EVDO --> + <bool name="config_alwaysUseCdmaRssi">false</bool> + + <!-- If this value is true, duplicate Source/Destination port fields + in WDP header of some carriers OMADM wap push are supported. + ex: MSGTYPE-TotalSegments-CurrentSegment + -SourcePortDestPort-SourcePortDestPort-OMADM PDU + If false, not supported. --> + <bool name="config_duplicate_port_omadm_wappush">false</bool> </resources> diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index ae3cd02..9a88ab3 100755 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -427,6 +427,13 @@ your messages or delete them without showing them to you.</string> <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. --> + <string name="permlab_receiveEmergencyBroadcast">receive emergency broadcasts</string> + <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. --> + <string name="permdesc_receiveEmergencyBroadcast">Allows application to receive + and process emergency broadcast messages. This permission is only available + to system applications.</string> + + <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. --> <string name="permlab_sendSms">send SMS messages</string> <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. --> <string name="permdesc_sendSms">Allows application to send SMS diff --git a/include/utils/RefBase.h b/include/utils/RefBase.h index c24c0db..d92cfb0 100644 --- a/include/utils/RefBase.h +++ b/include/utils/RefBase.h @@ -112,10 +112,23 @@ public: getWeakRefs()->trackMe(enable, retain); } + // used to override the RefBase destruction. + class Destroyer { + friend class RefBase; + public: + virtual ~Destroyer(); + private: + virtual void destroy(RefBase const* base) = 0; + }; + + // Make sure to never acquire a strong reference from this function. The + // same restrictions than for destructors apply. + void setDestroyer(Destroyer* destroyer); + protected: RefBase(); virtual ~RefBase(); - + //! Flags for extendObjectLifetime() enum { OBJECT_LIFETIME_WEAK = 0x0001, diff --git a/libs/utils/RefBase.cpp b/libs/utils/RefBase.cpp index d28b751..545da7d 100644 --- a/libs/utils/RefBase.cpp +++ b/libs/utils/RefBase.cpp @@ -48,6 +48,11 @@ namespace android { // --------------------------------------------------------------------------- +RefBase::Destroyer::~Destroyer() { +} + +// --------------------------------------------------------------------------- + class RefBase::weakref_impl : public RefBase::weakref_type { public: @@ -55,7 +60,7 @@ public: volatile int32_t mWeak; RefBase* const mBase; volatile int32_t mFlags; - + Destroyer* mDestroyer; #if !DEBUG_REFS @@ -64,6 +69,7 @@ public: , mWeak(0) , mBase(base) , mFlags(0) + , mDestroyer(0) { } @@ -310,7 +316,11 @@ void RefBase::decStrong(const void* id) const if (c == 1) { const_cast<RefBase*>(this)->onLastStrongRef(id); if ((refs->mFlags&OBJECT_LIFETIME_WEAK) != OBJECT_LIFETIME_WEAK) { - delete this; + if (refs->mDestroyer) { + refs->mDestroyer->destroy(this); + } else { + delete this; + } } } refs->removeWeakRef(id); @@ -345,7 +355,9 @@ int32_t RefBase::getStrongCount() const return mRefs->mStrong; } - +void RefBase::setDestroyer(RefBase::Destroyer* destroyer) { + mRefs->mDestroyer = destroyer; +} RefBase* RefBase::weakref_type::refBase() const { @@ -369,16 +381,28 @@ void RefBase::weakref_type::decWeak(const void* id) if (c != 1) return; if ((impl->mFlags&OBJECT_LIFETIME_WEAK) != OBJECT_LIFETIME_WEAK) { - if (impl->mStrong == INITIAL_STRONG_VALUE) - delete impl->mBase; - else { -// LOGV("Freeing refs %p of old RefBase %p\n", this, impl->mBase); + if (impl->mStrong == INITIAL_STRONG_VALUE) { + if (impl->mBase) { + if (impl->mDestroyer) { + impl->mDestroyer->destroy(impl->mBase); + } else { + delete impl->mBase; + } + } + } else { + // LOGV("Freeing refs %p of old RefBase %p\n", this, impl->mBase); delete impl; } } else { impl->mBase->onLastWeakRef(id); if ((impl->mFlags&OBJECT_LIFETIME_FOREVER) != OBJECT_LIFETIME_FOREVER) { - delete impl->mBase; + if (impl->mBase) { + if (impl->mDestroyer) { + impl->mDestroyer->destroy(impl->mBase); + } else { + delete impl->mBase; + } + } } } } @@ -502,10 +526,10 @@ RefBase::RefBase() RefBase::~RefBase() { -// LOGV("Destroying RefBase %p (refs %p)\n", this, mRefs); - if (mRefs->mWeak == 0) { -// LOGV("Freeing refs %p of old RefBase %p\n", mRefs, this); - delete mRefs; + if ((mRefs->mFlags & OBJECT_LIFETIME_WEAK) == OBJECT_LIFETIME_WEAK) { + if (mRefs->mWeak == 0) { + delete mRefs; + } } } diff --git a/media/libstagefright/HTTPStream.cpp b/media/libstagefright/HTTPStream.cpp index 4ad1c12..42664b2 100644 --- a/media/libstagefright/HTTPStream.cpp +++ b/media/libstagefright/HTTPStream.cpp @@ -124,6 +124,83 @@ static status_t MyConnect( return result; } +// Apparently under our linux closing a socket descriptor from one thread +// will not unblock a pending send/recv on that socket on another thread. +static ssize_t MySendReceive( + int s, void *data, size_t size, int flags, bool sendData) { + ssize_t result = 0; + + if (s < 0) { + return -1; + } + while (size > 0) { + fd_set rs, ws, es; + FD_ZERO(&rs); + FD_ZERO(&ws); + FD_ZERO(&es); + FD_SET(s, sendData ? &ws : &rs); + FD_SET(s, &es); + + struct timeval tv; + tv.tv_sec = 0; + tv.tv_usec = 100000ll; + + int nfds = ::select( + s + 1, + sendData ? NULL : &rs, + sendData ? &ws : NULL, + &es, + &tv); + + if (nfds < 0) { + if (errno == EINTR) { + continue; + } + + result = -errno; + break; + } else if (nfds == 0) { + // timeout + + continue; + } + + CHECK_EQ(nfds, 1); + + ssize_t nbytes = + sendData ? send(s, data, size, flags) : recv(s, data, size, flags); + + if (nbytes < 0) { + if (errno == EINTR) { + continue; + } + + result = -errno; + break; + } else if (nbytes == 0) { + result = 0; + break; + } + + data = (uint8_t *)data + nbytes; + size -= nbytes; + + result = nbytes; + break; + } + + return result; +} + +static ssize_t MySend(int s, const void *data, size_t size, int flags) { + return MySendReceive( + s, const_cast<void *>(data), size, flags, true /* sendData */); +} + +static ssize_t MyReceive(int s, void *data, size_t size, int flags) { + return MySendReceive(s, data, size, flags, false /* sendData */); +} + status_t HTTPStream::connect(const char *server, int port) { Mutex::Autolock autoLock(mLock); @@ -220,16 +297,12 @@ status_t HTTPStream::send(const char *data, size_t size) { } while (size > 0) { - ssize_t n = ::send(mSocket, data, size, 0); + ssize_t n = MySend(mSocket, data, size, 0); if (n < 0) { - if (errno == EINTR) { - continue; - } - disconnect(); - return ERROR_IO; + return n; } else if (n == 0) { disconnect(); @@ -265,12 +338,8 @@ status_t HTTPStream::receive_line(char *line, size_t size) { for (;;) { char c; - ssize_t n = recv(mSocket, &c, 1, 0); + ssize_t n = MyReceive(mSocket, &c, 1, 0); if (n < 0) { - if (errno == EINTR) { - continue; - } - disconnect(); return ERROR_IO; @@ -383,14 +452,10 @@ status_t HTTPStream::receive_header(int *http_status) { ssize_t HTTPStream::receive(void *data, size_t size) { size_t total = 0; while (total < size) { - ssize_t n = recv(mSocket, (char *)data + total, size - total, 0); + ssize_t n = MyReceive(mSocket, (char *)data + total, size - total, 0); if (n < 0) { - if (errno == EINTR) { - continue; - } - - LOGE("recv failed, errno = %d (%s)", errno, strerror(errno)); + LOGE("recv failed, errno = %d (%s)", (int)n, strerror(-n)); disconnect(); return (ssize_t)ERROR_IO; diff --git a/nfc-extras/java/com/android/nfc_extras/NfcAdapterExtras.java b/nfc-extras/java/com/android/nfc_extras/NfcAdapterExtras.java index 6001be9..e0c38b1 100644 --- a/nfc-extras/java/com/android/nfc_extras/NfcAdapterExtras.java +++ b/nfc-extras/java/com/android/nfc_extras/NfcAdapterExtras.java @@ -18,7 +18,6 @@ package com.android.nfc_extras; import android.annotation.SdkConstant; import android.annotation.SdkConstant.SdkConstantType; -import android.nfc.ApduList; import android.nfc.INfcAdapterExtras; import android.nfc.NfcAdapter; import android.os.RemoteException; @@ -208,17 +207,18 @@ public final class NfcAdapterExtras { return sEmbeddedEe; } - public void registerTearDownApdus(String packageName, ApduList apdus) { - try { - sService.registerTearDownApdus(packageName, apdus); - } catch (RemoteException e) { - attemptDeadServiceRecovery(e); - } - } - - public void unregisterTearDownApdus(String packageName) { + /** + * Authenticate the client application. + * + * Some implementations of NFC Adapter Extras may require applications + * to authenticate with a token, before using other methods. + * + * @param a implementation specific token + * @throws a {@link java.lang.SecurityException} if authentication failed + */ + public void authenticate(byte[] token) { try { - sService.unregisterTearDownApdus(packageName); + sService.authenticate(token); } catch (RemoteException e) { attemptDeadServiceRecovery(e); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarPolicy.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarPolicy.java index 8c0fecf..b30a043 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarPolicy.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarPolicy.java @@ -468,6 +468,9 @@ public class StatusBarPolicy { ServiceState mServiceState; SignalStrength mSignalStrength; + // flag for signal strength behavior + private boolean mAlwaysUseCdmaRssi; + // data connection private boolean mDataIconVisible; private boolean mHspaDataDistinguishable; @@ -512,10 +515,10 @@ public class StatusBarPolicy { private static final int sWimaxDisconnectedImg = R.drawable.stat_sys_data_wimax_signal_disconnected; private static final int sWimaxIdleImg = R.drawable.stat_sys_data_wimax_signal_idle; - private boolean mIsWimaxConnected = false; private boolean mIsWimaxEnabled = false; private int mWimaxSignal = 0; private int mWimaxState = 0; + private int mWimaxExtraState = 0; // state of inet connection - 0 not connected, 100 connected private int mInetCondition = 0; @@ -600,6 +603,8 @@ public class StatusBarPolicy { mPhone = (TelephonyManager)context.getSystemService(Context.TELEPHONY_SERVICE); mPhoneSignalIconId = R.drawable.stat_sys_signal_null; mService.setIcon("phone_signal", mPhoneSignalIconId, 0); + mAlwaysUseCdmaRssi = mContext.getResources().getBoolean( + com.android.internal.R.bool.config_alwaysUseCdmaRssi); // register for phone state notifications. ((TelephonyManager)mContext.getSystemService(Context.TELEPHONY_SERVICE)) @@ -940,12 +945,6 @@ public class StatusBarPolicy { break; case ConnectivityManager.TYPE_WIMAX: mInetCondition = inetCondition; - if (info.isConnected()) { - mIsWimaxConnected = true; - mService.setIconVisibility("wimax", true); - } else { - mIsWimaxConnected = false; - } updateWiMAX(intent); break; } @@ -1084,7 +1083,8 @@ public class StatusBarPolicy { // If 3G(EV) and 1x network are available than 3G should be // displayed, displayed RSSI should be from the EV side. // If a voice call is made then RSSI should switch to 1x. - if ((mPhoneState == TelephonyManager.CALL_STATE_IDLE) && isEvdo()){ + if ((mPhoneState == TelephonyManager.CALL_STATE_IDLE) && isEvdo() + && !mAlwaysUseCdmaRssi) { iconLevel = getEvdoLevel(); if (false) { Slog.d(TAG, "use Evdo level=" + iconLevel + " to replace Cdma Level=" + getCdmaLevel()); @@ -1332,10 +1332,10 @@ public class StatusBarPolicy { final String action = intent.getAction(); int iconId = sWimaxDisconnectedImg; - if (action.equals(WimaxManagerConstants. WIMAX_ENABLED_STATUS_CHANGED)) { - int mWimaxStatus = intent.getIntExtra(WimaxManagerConstants.EXTRA_WIMAX_STATUS, + if (action.equals(WimaxManagerConstants.WIMAX_ENABLED_STATUS_CHANGED)) { + int wimaxStatus = intent.getIntExtra(WimaxManagerConstants.EXTRA_WIMAX_STATUS, WimaxManagerConstants.WIMAX_STATUS_DISABLED); - switch(mWimaxStatus) { + switch(wimaxStatus) { case WimaxManagerConstants.WIMAX_STATUS_ENABLED: mIsWimaxEnabled = true; break; @@ -1343,31 +1343,29 @@ public class StatusBarPolicy { mIsWimaxEnabled = false; break; } + mService.setIconVisibility("wimax", mIsWimaxEnabled); } else if (action.equals(WimaxManagerConstants.SIGNAL_LEVEL_CHANGED_ACTION)) { mWimaxSignal = intent.getIntExtra(WimaxManagerConstants.EXTRA_NEW_SIGNAL_LEVEL, 0); } else if (action.equals(WimaxManagerConstants.WIMAX_STATE_CHANGED_ACTION)) { mWimaxState = intent.getIntExtra(WimaxManagerConstants.EXTRA_WIMAX_STATE, WimaxManagerConstants.WIMAX_STATE_UNKNOWN); - int mExtraWimaxState = intent.getIntExtra( + mWimaxExtraState = intent.getIntExtra( WimaxManagerConstants.EXTRA_WIMAX_STATE_DETAIL, WimaxManagerConstants.WIMAX_DEREGISTRATION); - - switch(mWimaxState) { - case WimaxManagerConstants.WIMAX_STATE_DISCONNECTED: - iconId = sWimaxDisconnectedImg; - break; - case WimaxManagerConstants.WIMAX_STATE_CONNECTED: - if(mExtraWimaxState == WimaxManagerConstants.WIMAX_IDLE) { - iconId = sWimaxIdleImg; - } - else { - iconId = sWimaxSignalImages[mInetCondition][mWimaxSignal]; - } - break; - } - mService.setIcon("wimax", iconId, 0); } - mService.setIconVisibility("wimax", mIsWimaxEnabled); + switch(mWimaxState) { + case WimaxManagerConstants.WIMAX_STATE_DISCONNECTED: + iconId = sWimaxDisconnectedImg; + break; + case WimaxManagerConstants.WIMAX_STATE_CONNECTED: + if(mWimaxExtraState == WimaxManagerConstants.WIMAX_IDLE) { + iconId = sWimaxIdleImg; + } else { + iconId = sWimaxSignalImages[mInetCondition][mWimaxSignal]; + } + break; + } + if (mIsWimaxEnabled) mService.setIcon("wimax", iconId, 0); } private final void updateGps(Intent intent) { diff --git a/services/java/com/android/server/PackageManagerService.java b/services/java/com/android/server/PackageManagerService.java index 19abdc3..29b9ee4 100644 --- a/services/java/com/android/server/PackageManagerService.java +++ b/services/java/com/android/server/PackageManagerService.java @@ -55,6 +55,8 @@ import android.content.pm.PackageInfo; import android.content.pm.PackageInfoLite; import android.content.pm.PackageManager; import android.content.pm.PackageStats; +import android.content.pm.ParceledListSlice; + import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DEFAULT; import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED; import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_ENABLED; @@ -75,6 +77,7 @@ import android.os.IBinder; import android.os.Looper; import android.os.Message; import android.os.Parcel; +import android.os.Parcelable; import android.os.RemoteException; import android.os.Environment; import android.os.FileObserver; @@ -2323,63 +2326,110 @@ class PackageManagerService extends IPackageManager.Stub { } } - public List<PackageInfo> getInstalledPackages(int flags) { - ArrayList<PackageInfo> finalList = new ArrayList<PackageInfo>(); + private static final int getContinuationPoint(final String[] keys, final String key) { + final int index; + if (key == null) { + index = 0; + } else { + final int insertPoint = Arrays.binarySearch(keys, key); + if (insertPoint < 0) { + index = -insertPoint; + } else { + index = insertPoint + 1; + } + } + return index; + } + + public ParceledListSlice<PackageInfo> getInstalledPackages(int flags, String lastRead) { + final ParceledListSlice<PackageInfo> list = new ParceledListSlice<PackageInfo>(); + final boolean listUninstalled = (flags & PackageManager.GET_UNINSTALLED_PACKAGES) != 0; + final String[] keys; synchronized (mPackages) { - if((flags & PackageManager.GET_UNINSTALLED_PACKAGES) != 0) { - Iterator<PackageSetting> i = mSettings.mPackages.values().iterator(); - while (i.hasNext()) { - final PackageSetting ps = i.next(); - PackageInfo psPkg = generatePackageInfoFromSettingsLP(ps.name, flags); - if(psPkg != null) { - finalList.add(psPkg); - } - } - } - else { - Iterator<PackageParser.Package> i = mPackages.values().iterator(); - while (i.hasNext()) { - final PackageParser.Package p = i.next(); - if (p.applicationInfo != null) { - PackageInfo pi = generatePackageInfo(p, flags); - if(pi != null) { - finalList.add(pi); - } + if (listUninstalled) { + keys = mSettings.mPackages.keySet().toArray(new String[mSettings.mPackages.size()]); + } else { + keys = mPackages.keySet().toArray(new String[mPackages.size()]); + } + + Arrays.sort(keys); + int i = getContinuationPoint(keys, lastRead); + final int N = keys.length; + + while (i < N) { + final String packageName = keys[i++]; + + PackageInfo pi = null; + if (listUninstalled) { + final PackageSetting ps = mSettings.mPackages.get(packageName); + if (ps != null) { + pi = generatePackageInfoFromSettingsLP(ps.name, flags); + } + } else { + final PackageParser.Package p = mPackages.get(packageName); + if (p != null) { + pi = generatePackageInfo(p, flags); } } + + if (pi != null && !list.append(pi)) { + break; + } + } + + if (i == N) { + list.setLastSlice(true); } } - return finalList; + + return list; } - public List<ApplicationInfo> getInstalledApplications(int flags) { - ArrayList<ApplicationInfo> finalList = new ArrayList<ApplicationInfo>(); - synchronized(mPackages) { - if((flags & PackageManager.GET_UNINSTALLED_PACKAGES) != 0) { - Iterator<PackageSetting> i = mSettings.mPackages.values().iterator(); - while (i.hasNext()) { - final PackageSetting ps = i.next(); - ApplicationInfo ai = generateApplicationInfoFromSettingsLP(ps.name, flags); - if(ai != null) { - finalList.add(ai); - } - } - } - else { - Iterator<PackageParser.Package> i = mPackages.values().iterator(); - while (i.hasNext()) { - final PackageParser.Package p = i.next(); - if (p.applicationInfo != null) { - ApplicationInfo ai = PackageParser.generateApplicationInfo(p, flags); - if(ai != null) { - finalList.add(ai); - } + public ParceledListSlice<ApplicationInfo> getInstalledApplications(int flags, + String lastRead) { + final ParceledListSlice<ApplicationInfo> list = new ParceledListSlice<ApplicationInfo>(); + final boolean listUninstalled = (flags & PackageManager.GET_UNINSTALLED_PACKAGES) != 0; + final String[] keys; + + synchronized (mPackages) { + if (listUninstalled) { + keys = mSettings.mPackages.keySet().toArray(new String[mSettings.mPackages.size()]); + } else { + keys = mPackages.keySet().toArray(new String[mPackages.size()]); + } + + Arrays.sort(keys); + int i = getContinuationPoint(keys, lastRead); + final int N = keys.length; + + while (i < N) { + final String packageName = keys[i++]; + + ApplicationInfo ai = null; + if (listUninstalled) { + final PackageSetting ps = mSettings.mPackages.get(packageName); + if (ps != null) { + ai = generateApplicationInfoFromSettingsLP(ps.name, flags); + } + } else { + final PackageParser.Package p = mPackages.get(packageName); + if (p != null) { + ai = PackageParser.generateApplicationInfo(p, flags); } } + + if (ai != null && !list.append(ai)) { + break; + } + } + + if (i == N) { + list.setLastSlice(true); } } - return finalList; + + return list; } public List<ApplicationInfo> getPersistentApplications(int flags) { diff --git a/services/java/com/android/server/WifiService.java b/services/java/com/android/server/WifiService.java index 5aa0111..647f115 100644 --- a/services/java/com/android/server/WifiService.java +++ b/services/java/com/android/server/WifiService.java @@ -53,6 +53,7 @@ import android.net.NetworkStateTracker; import android.net.DhcpInfo; import android.net.NetworkUtils; import android.os.Binder; +import android.os.Environment; import android.os.Handler; import android.os.HandlerThread; import android.os.IBinder; @@ -68,6 +69,17 @@ import android.provider.Settings; import android.util.Slog; import android.text.TextUtils; +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.EOFException; +import java.io.FileDescriptor; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.PrintWriter; +import java.net.UnknownHostException; import java.util.ArrayList; import java.util.BitSet; import java.util.HashMap; @@ -76,9 +88,7 @@ import java.util.List; import java.util.Map; import java.util.Set; import java.util.regex.Pattern; -import java.io.FileDescriptor; -import java.io.PrintWriter; -import java.net.UnknownHostException; +import java.util.UUID; import com.android.internal.app.IBatteryStats; import android.app.backup.IBackupManager; @@ -176,6 +186,8 @@ public class WifiService extends IWifiManager.Stub { private static final int MESSAGE_START_SCAN = 10; private static final int MESSAGE_REPORT_WORKSOURCE = 11; private static final int MESSAGE_ENABLE_RSSI_POLLING = 12; + private static final int MESSAGE_WRITE_WIFI_AP_CONFIG = 13; + private static final int MESSAGE_READ_WIFI_AP_CONFIG = 14; private final WifiHandler mWifiHandler; @@ -218,6 +230,13 @@ public class WifiService extends IWifiManager.Stub { private static final String ACTION_DEVICE_IDLE = "com.android.server.WifiManager.action.DEVICE_IDLE"; + private static final String WIFIAP_CONFIG_FILE = Environment.getDataDirectory() + + "/misc/wifi/softap.conf"; + + private static final int WIFIAP_CONFIG_VERSION = 1; + private WifiConfiguration mWifiApConfig = new WifiConfiguration(); + private final Object mWifiApConfigLock = new Object(); + WifiService(Context context, WifiStateTracker tracker) { mContext = context; mWifiStateTracker = tracker; @@ -281,6 +300,9 @@ public class WifiService extends IWifiManager.Stub { } },new IntentFilter(ConnectivityManager.ACTION_TETHER_STATE_CHANGED)); + + //Initiate a read of Wifi Ap configuration + Message.obtain(mWifiHandler, MESSAGE_READ_WIFI_AP_CONFIG).sendToTarget(); } /** @@ -634,38 +656,103 @@ public class WifiService extends IWifiManager.Stub { public WifiConfiguration getWifiApConfiguration() { enforceAccessPermission(); - final ContentResolver cr = mContext.getContentResolver(); - WifiConfiguration wifiConfig = new WifiConfiguration(); - int authType; - try { - wifiConfig.SSID = Settings.Secure.getString(cr, Settings.Secure.WIFI_AP_SSID); - if (wifiConfig.SSID == null) - return null; - authType = Settings.Secure.getInt(cr, Settings.Secure.WIFI_AP_SECURITY); - wifiConfig.allowedKeyManagement.set(authType); - wifiConfig.preSharedKey = Settings.Secure.getString(cr, Settings.Secure.WIFI_AP_PASSWD); - return wifiConfig; - } catch (Settings.SettingNotFoundException e) { - Slog.e(TAG,"AP settings not found, returning"); - return null; + synchronized (mWifiApConfigLock) { + WifiConfiguration config = new WifiConfiguration(); + config.SSID = mWifiApConfig.SSID; + if (mWifiApConfig.allowedKeyManagement.get(KeyMgmt.WPA_PSK)) { + config.allowedKeyManagement.set(KeyMgmt.WPA_PSK); + config.preSharedKey = mWifiApConfig.preSharedKey; + } else { + config.allowedKeyManagement.set(KeyMgmt.NONE); + } + return config; } } public void setWifiApConfiguration(WifiConfiguration wifiConfig) { enforceChangePermission(); - final ContentResolver cr = mContext.getContentResolver(); - boolean isWpa; if (wifiConfig == null) return; - Settings.Secure.putString(cr, Settings.Secure.WIFI_AP_SSID, wifiConfig.SSID); - isWpa = wifiConfig.allowedKeyManagement.get(KeyMgmt.WPA_PSK); - Settings.Secure.putInt(cr, - Settings.Secure.WIFI_AP_SECURITY, - isWpa ? KeyMgmt.WPA_PSK : KeyMgmt.NONE); - if (isWpa) - Settings.Secure.putString(cr, Settings.Secure.WIFI_AP_PASSWD, wifiConfig.preSharedKey); + Message.obtain(mWifiHandler, MESSAGE_WRITE_WIFI_AP_CONFIG, wifiConfig).sendToTarget(); + } + + /* Generate a default WPA2 based configuration with a random password. + + We are changing the Wifi Ap configuration storage from secure settings to a + flat file accessible only by the system. A WPA2 based default configuration + will keep the device secure after the update */ + private void setDefaultWifiApConfiguration() { + synchronized (mWifiApConfigLock) { + mWifiApConfig.SSID = mContext.getString(R.string.wifi_tether_configure_ssid_default); + mWifiApConfig.allowedKeyManagement.set(KeyMgmt.WPA_PSK); + String randomUUID = UUID.randomUUID().toString(); + //first 12 chars from xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx + mWifiApConfig.preSharedKey = randomUUID.substring(0, 8) + randomUUID.substring(9,13); + } + } + + private void writeWifiApConfigBlocked(WifiConfiguration wifiConfig) { + DataOutputStream out = null; + try { + out = new DataOutputStream(new BufferedOutputStream( + new FileOutputStream(WIFIAP_CONFIG_FILE))); + + out.writeInt(WIFIAP_CONFIG_VERSION); + out.writeUTF(wifiConfig.SSID); + if(wifiConfig.allowedKeyManagement.get(KeyMgmt.WPA_PSK)) { + out.writeInt(KeyMgmt.WPA_PSK); + out.writeUTF(wifiConfig.preSharedKey); + } else { + out.writeInt(KeyMgmt.NONE); + } + synchronized (mWifiApConfigLock) { + mWifiApConfig = wifiConfig; + } + } catch (IOException e) { + Slog.e(TAG, "Error writing hotspot configuration" + e); + } finally { + if (out != null) { + try { + out.close(); + } catch (IOException e) {} + } + } } + private void readWifiApConfigBlocked() { + DataInputStream in = null; + try { + WifiConfiguration config = new WifiConfiguration(); + in = new DataInputStream(new BufferedInputStream(new FileInputStream( + WIFIAP_CONFIG_FILE))); + + int version = in.readInt(); + if (version != 1) { + Slog.e(TAG, "Bad version on hotspot configuration file, set defaults"); + setDefaultWifiApConfiguration(); + return; + } + config.SSID = in.readUTF(); + int authType = in.readInt(); + config.allowedKeyManagement.set(authType); + if (authType != KeyMgmt.NONE) { + config.preSharedKey = in.readUTF(); + } + synchronized (mWifiApConfigLock) { + mWifiApConfig = config; + } + } catch (IOException ignore) { + setDefaultWifiApConfiguration(); + } finally { + if (in != null) { + try { + in.close(); + } catch (IOException e) {} + } + } + } + + /** * Enables/disables Wi-Fi AP synchronously. The driver is loaded * and soft access point configured as a single operation. @@ -716,11 +803,7 @@ public class WifiService extends IWifiManager.Stub { if (enable) { /* Use default config if there is no existing config */ - if (wifiConfig == null && ((wifiConfig = getWifiApConfiguration()) == null)) { - wifiConfig = new WifiConfiguration(); - wifiConfig.SSID = mContext.getString(R.string.wifi_tether_configure_ssid_default); - wifiConfig.allowedKeyManagement.set(KeyMgmt.NONE); - } + if (wifiConfig == null) wifiConfig = getWifiApConfiguration(); if (!mWifiStateTracker.loadDriver()) { Slog.e(TAG, "Failed to load Wi-Fi driver for AP mode"); @@ -2038,6 +2121,12 @@ public class WifiService extends IWifiManager.Stub { case MESSAGE_ENABLE_RSSI_POLLING: mWifiStateTracker.enableRssiPolling(msg.arg1 == 1); break; + case MESSAGE_WRITE_WIFI_AP_CONFIG: + writeWifiApConfigBlocked((WifiConfiguration) msg.obj); + break; + case MESSAGE_READ_WIFI_AP_CONFIG: + readWifiApConfigBlocked(); + break; } } } diff --git a/services/java/com/android/server/connectivity/Tethering.java b/services/java/com/android/server/connectivity/Tethering.java index 1ec9b51..a430128 100644 --- a/services/java/com/android/server/connectivity/Tethering.java +++ b/services/java/com/android/server/connectivity/Tethering.java @@ -1170,8 +1170,8 @@ public class Tethering extends INetworkManagementEventObserver.Stub { return null; } - for (String iface : ifaces) { - for (String regex : mUpstreamIfaceRegexs) { + for (String regex : mUpstreamIfaceRegexs) { + for (String iface : ifaces) { if (iface.matches(regex)) { // verify it is active InterfaceConfiguration ifcg = null; diff --git a/services/surfaceflinger/Layer.cpp b/services/surfaceflinger/Layer.cpp index dfee803..da06f61 100644 --- a/services/surfaceflinger/Layer.cpp +++ b/services/surfaceflinger/Layer.cpp @@ -60,6 +60,7 @@ Layer::Layer(SurfaceFlinger* flinger, mWidth(0), mHeight(0), mNeedsScaling(false), mFixedSize(false), mBypassState(false) { + setDestroyer(this); } Layer::~Layer() @@ -76,6 +77,10 @@ Layer::~Layer() } } +void Layer::destroy(RefBase const* base) { + mFlinger->destroyLayer(static_cast<LayerBase const*>(base)); +} + status_t Layer::setToken(const sp<UserClient>& userClient, SharedClient* sharedClient, int32_t token) { @@ -123,22 +128,6 @@ sp<LayerBaseClient::Surface> Layer::createSurface() const return mSurface; } -status_t Layer::ditch() -{ - // NOTE: Called from the main UI thread - - // the layer is not on screen anymore. free as much resources as possible - mFreezeLock.clear(); - - EGLDisplay dpy(mFlinger->graphicPlane(0).getEGLDisplay()); - mBufferManager.destroy(dpy); - mSurface.clear(); - - Mutex::Autolock _l(mLock); - mWidth = mHeight = 0; - return NO_ERROR; -} - status_t Layer::setBuffers( uint32_t w, uint32_t h, PixelFormat format, uint32_t flags) { diff --git a/services/surfaceflinger/Layer.h b/services/surfaceflinger/Layer.h index 7bb207a..2c4f756 100644 --- a/services/surfaceflinger/Layer.h +++ b/services/surfaceflinger/Layer.h @@ -44,7 +44,7 @@ class UserClient; // --------------------------------------------------------------------------- -class Layer : public LayerBaseClient +class Layer : public LayerBaseClient, private RefBase::Destroyer { public: Layer(SurfaceFlinger* flinger, DisplayID display, @@ -78,7 +78,6 @@ public: virtual bool needsFiltering() const; virtual bool isSecure() const { return mSecure; } virtual sp<Surface> createSurface() const; - virtual status_t ditch(); virtual void onRemoved(); virtual bool setBypass(bool enable); @@ -95,6 +94,7 @@ public: return mFreezeLock; } protected: + virtual void destroy(RefBase const* base); virtual void dump(String8& result, char* scratch, size_t size) const; private: diff --git a/services/surfaceflinger/LayerBase.cpp b/services/surfaceflinger/LayerBase.cpp index 21c36e1..3986fde 100644 --- a/services/surfaceflinger/LayerBase.cpp +++ b/services/surfaceflinger/LayerBase.cpp @@ -583,10 +583,7 @@ LayerBaseClient::Surface::~Surface() */ // destroy client resources - sp<LayerBaseClient> layer = getOwner(); - if (layer != 0) { - mFlinger->destroySurface(layer); - } + mFlinger->destroySurface(mOwner); } sp<LayerBaseClient> LayerBaseClient::Surface::getOwner() const { diff --git a/services/surfaceflinger/LayerBase.h b/services/surfaceflinger/LayerBase.h index afc5ec8..e69cb6a 100644 --- a/services/surfaceflinger/LayerBase.h +++ b/services/surfaceflinger/LayerBase.h @@ -196,10 +196,6 @@ public: */ virtual bool isSecure() const { return false; } - /** Called from the main thread, when the surface is removed from the - * draw list */ - virtual status_t ditch() { return NO_ERROR; } - /** called with the state lock when the surface is removed from the * current list */ virtual void onRemoved() { }; @@ -264,7 +260,8 @@ protected: volatile int32_t mInvalidate; -protected: +public: + // called from class SurfaceFlinger virtual ~LayerBase(); private: diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp index c08e2c9..a93d756 100644 --- a/services/surfaceflinger/SurfaceFlinger.cpp +++ b/services/surfaceflinger/SurfaceFlinger.cpp @@ -358,6 +358,9 @@ bool SurfaceFlinger::threadLoop() { waitForEvent(); + // call Layer's destructor + handleDestroyLayers(); + // check for transactions if (UNLIKELY(mConsoleSignals)) { handleConsoleEvents(); @@ -366,7 +369,7 @@ bool SurfaceFlinger::threadLoop() if (LIKELY(mTransactionCount == 0)) { // if we're in a global transaction, don't do anything. const uint32_t mask = eTransactionNeeded | eTraversalNeeded; - uint32_t transactionFlags = getTransactionFlags(mask); + uint32_t transactionFlags = peekTransactionFlags(mask); if (LIKELY(transactionFlags)) { handleTransaction(transactionFlags); } @@ -467,37 +470,26 @@ void SurfaceFlinger::handleConsoleEvents() void SurfaceFlinger::handleTransaction(uint32_t transactionFlags) { - Vector< sp<LayerBase> > ditchedLayers; + Mutex::Autolock _l(mStateLock); + const nsecs_t now = systemTime(); + mDebugInTransaction = now; - /* - * Perform and commit the transaction - */ + // Here we're guaranteed that some transaction flags are set + // so we can call handleTransactionLocked() unconditionally. + // We call getTransactionFlags(), which will also clear the flags, + // with mStateLock held to guarantee that mCurrentState won't change + // until the transaction is committed. - { // scope for the lock - Mutex::Autolock _l(mStateLock); - const nsecs_t now = systemTime(); - mDebugInTransaction = now; - handleTransactionLocked(transactionFlags, ditchedLayers); - mLastTransactionTime = systemTime() - now; - mDebugInTransaction = 0; - // here the transaction has been committed - } + const uint32_t mask = eTransactionNeeded | eTraversalNeeded; + transactionFlags = getTransactionFlags(mask); + handleTransactionLocked(transactionFlags); - /* - * Clean-up all layers that went away - * (do this without the lock held) - */ - const size_t count = ditchedLayers.size(); - for (size_t i=0 ; i<count ; i++) { - if (ditchedLayers[i] != 0) { - //LOGD("ditching layer %p", ditchedLayers[i].get()); - ditchedLayers[i]->ditch(); - } - } + mLastTransactionTime = systemTime() - now; + mDebugInTransaction = 0; + // here the transaction has been committed } -void SurfaceFlinger::handleTransactionLocked( - uint32_t transactionFlags, Vector< sp<LayerBase> >& ditchedLayers) +void SurfaceFlinger::handleTransactionLocked(uint32_t transactionFlags) { const LayerVector& currentLayers(mCurrentState.layersSortedByZ); const size_t count = currentLayers.size(); @@ -569,7 +561,6 @@ void SurfaceFlinger::handleTransactionLocked( const sp<LayerBase>& layer(previousLayers[i]); if (currentLayers.indexOf( layer ) < 0) { // this layer is not visible anymore - ditchedLayers.add(layer); mDirtyRegionRemovedLayer.orSelf(layer->visibleRegionScreen); } } @@ -579,6 +570,31 @@ void SurfaceFlinger::handleTransactionLocked( commitTransaction(); } +void SurfaceFlinger::destroyLayer(LayerBase const* layer) +{ + Mutex::Autolock _l(mDestroyedLayerLock); + mDestroyedLayers.add(layer); + signalEvent(); +} + +void SurfaceFlinger::handleDestroyLayers() +{ + Vector<LayerBase const *> destroyedLayers; + + { // scope for the lock + Mutex::Autolock _l(mDestroyedLayerLock); + destroyedLayers = mDestroyedLayers; + mDestroyedLayers.clear(); + } + + // call destructors without a lock held + const size_t count = destroyedLayers.size(); + for (size_t i=0 ; i<count ; i++) { + //LOGD("destroying %s", destroyedLayers[i]->getName().string()); + delete destroyedLayers[i]; + } +} + sp<FreezeLock> SurfaceFlinger::getFreezeLock() const { return new FreezeLock(const_cast<SurfaceFlinger *>(this)); @@ -1030,15 +1046,15 @@ status_t SurfaceFlinger::addLayer_l(const sp<LayerBase>& layer) ssize_t SurfaceFlinger::addClientLayer(const sp<Client>& client, const sp<LayerBaseClient>& lbc) { - Mutex::Autolock _l(mStateLock); - // attach this layer to the client - ssize_t name = client->attachLayer(lbc); + size_t name = client->attachLayer(lbc); + + Mutex::Autolock _l(mStateLock); // add this layer to the current state list addLayer_l(lbc); - return name; + return ssize_t(name); } status_t SurfaceFlinger::removeLayer(const sp<LayerBase>& layer) @@ -1085,6 +1101,11 @@ status_t SurfaceFlinger::invalidateLayerVisibility(const sp<LayerBase>& layer) return NO_ERROR; } +uint32_t SurfaceFlinger::peekTransactionFlags(uint32_t flags) +{ + return android_atomic_release_load(&mTransactionFlags); +} + uint32_t SurfaceFlinger::getTransactionFlags(uint32_t flags) { return android_atomic_and(~flags, &mTransactionFlags) & flags; @@ -1314,38 +1335,18 @@ status_t SurfaceFlinger::removeSurface(const sp<Client>& client, SurfaceID sid) return err; } -status_t SurfaceFlinger::destroySurface(const sp<LayerBaseClient>& layer) +status_t SurfaceFlinger::destroySurface(const wp<LayerBaseClient>& layer) { // called by ~ISurface() when all references are gone - - class MessageDestroySurface : public MessageBase { - SurfaceFlinger* flinger; - sp<LayerBaseClient> layer; - public: - MessageDestroySurface( - SurfaceFlinger* flinger, const sp<LayerBaseClient>& layer) - : flinger(flinger), layer(layer) { } - virtual bool handler() { - sp<LayerBaseClient> l(layer); - layer.clear(); // clear it outside of the lock; - Mutex::Autolock _l(flinger->mStateLock); - /* - * remove the layer from the current list -- chances are that it's - * not in the list anyway, because it should have been removed - * already upon request of the client (eg: window manager). - * However, a buggy client could have not done that. - * Since we know we don't have any more clients, we don't need - * to use the purgatory. - */ - status_t err = flinger->removeLayer_l(l); - LOGE_IF(err<0 && err != NAME_NOT_FOUND, - "error removing layer=%p (%s)", l.get(), strerror(-err)); - return true; - } - }; - - postMessageAsync( new MessageDestroySurface(this, layer) ); - return NO_ERROR; + status_t err = NO_ERROR; + sp<LayerBaseClient> l(layer.promote()); + if (l != NULL) { + Mutex::Autolock _l(mStateLock); + err = removeLayer_l(l); + LOGE_IF(err<0 && err != NAME_NOT_FOUND, + "error removing layer=%p (%s)", l.get(), strerror(-err)); + } + return err; } status_t SurfaceFlinger::setClientState( @@ -2253,15 +2254,17 @@ status_t Client::initCheck() const { return NO_ERROR; } -ssize_t Client::attachLayer(const sp<LayerBaseClient>& layer) +size_t Client::attachLayer(const sp<LayerBaseClient>& layer) { - int32_t name = android_atomic_inc(&mNameGenerator); + Mutex::Autolock _l(mLock); + size_t name = mNameGenerator++; mLayers.add(name, layer); return name; } void Client::detachLayer(const LayerBaseClient* layer) { + Mutex::Autolock _l(mLock); // we do a linear search here, because this doesn't happen often const size_t count = mLayers.size(); for (size_t i=0 ; i<count ; i++) { @@ -2271,9 +2274,11 @@ void Client::detachLayer(const LayerBaseClient* layer) } } } -sp<LayerBaseClient> Client::getLayerUser(int32_t i) const { +sp<LayerBaseClient> Client::getLayerUser(int32_t i) const +{ + Mutex::Autolock _l(mLock); sp<LayerBaseClient> lbc; - const wp<LayerBaseClient>& layer(mLayers.valueFor(i)); + wp<LayerBaseClient> layer(mLayers.valueFor(i)); if (layer != 0) { lbc = layer.promote(); LOGE_IF(lbc==0, "getLayerUser(name=%d) is dead", int(i)); diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h index df1ca48..9fa98cf 100644 --- a/services/surfaceflinger/SurfaceFlinger.h +++ b/services/surfaceflinger/SurfaceFlinger.h @@ -66,7 +66,7 @@ public: status_t initCheck() const; // protected by SurfaceFlinger::mStateLock - ssize_t attachLayer(const sp<LayerBaseClient>& layer); + size_t attachLayer(const sp<LayerBaseClient>& layer); void detachLayer(const LayerBaseClient* layer); sp<LayerBaseClient> getLayerUser(int32_t i) const; @@ -82,9 +82,15 @@ private: virtual status_t destroySurface(SurfaceID surfaceId); virtual status_t setState(int32_t count, const layer_state_t* states); - DefaultKeyedVector< size_t, wp<LayerBaseClient> > mLayers; + // constant sp<SurfaceFlinger> mFlinger; - int32_t mNameGenerator; + + // protected by mLock + DefaultKeyedVector< size_t, wp<LayerBaseClient> > mLayers; + size_t mNameGenerator; + + // thread-safe + mutable Mutex mLock; }; class UserClient : public BnSurfaceComposerClient @@ -212,6 +218,7 @@ public: status_t removeLayer(const sp<LayerBase>& layer); status_t addLayer(const sp<LayerBase>& layer); status_t invalidateLayerVisibility(const sp<LayerBase>& layer); + void destroyLayer(LayerBase const* layer); sp<Layer> getLayer(const sp<ISurface>& sur) const; @@ -249,7 +256,7 @@ private: uint32_t w, uint32_t h, uint32_t flags); status_t removeSurface(const sp<Client>& client, SurfaceID sid); - status_t destroySurface(const sp<LayerBaseClient>& layer); + status_t destroySurface(const wp<LayerBaseClient>& layer); status_t setClientState(const sp<Client>& client, int32_t count, const layer_state_t* states); @@ -294,9 +301,8 @@ public: // hack to work around gcc 4.0.3 bug private: void handleConsoleEvents(); void handleTransaction(uint32_t transactionFlags); - void handleTransactionLocked( - uint32_t transactionFlags, - Vector< sp<LayerBase> >& ditchedLayers); + void handleTransactionLocked(uint32_t transactionFlags); + void handleDestroyLayers(); void computeVisibleRegions( LayerVector& currentLayers, @@ -319,6 +325,7 @@ private: status_t purgatorizeLayer_l(const sp<LayerBase>& layer); uint32_t getTransactionFlags(uint32_t flags); + uint32_t peekTransactionFlags(uint32_t flags); uint32_t setTransactionFlags(uint32_t flags); void commitTransaction(); @@ -415,6 +422,11 @@ private: // these are thread safe mutable Barrier mReadyToRunBarrier; + + // protected by mDestroyedLayerLock; + mutable Mutex mDestroyedLayerLock; + Vector<LayerBase const *> mDestroyedLayers; + // atomic variables enum { eConsoleReleased = 1, diff --git a/telephony/java/android/telephony/SmsCbConstants.java b/telephony/java/android/telephony/SmsCbConstants.java new file mode 100644 index 0000000..a1b4adf --- /dev/null +++ b/telephony/java/android/telephony/SmsCbConstants.java @@ -0,0 +1,117 @@ +/* + * Copyright (C) 2011 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.telephony; + +/** + * Constants used in SMS Cell Broadcast messages. + * + * {@hide} + */ +public interface SmsCbConstants { + /** Start of PWS Message Identifier range (includes ETWS and CMAS). */ + public static final int MESSAGE_ID_PWS_FIRST_IDENTIFIER = 0x1100; + + /** Bitmask for messages of ETWS type (including future extensions). */ + public static final int MESSAGE_ID_ETWS_TYPE_MASK = 0xFFF8; + + /** Value for messages of ETWS type after applying {@link #MESSAGE_ID_ETWS_TYPE_MASK}. */ + public static final int MESSAGE_ID_ETWS_TYPE = 0x1100; + + /** ETWS Message Identifier for earthquake warning message. */ + public static final int MESSAGE_ID_ETWS_EARTHQUAKE_WARNING = 0x1100; + + /** ETWS Message Identifier for tsunami warning message. */ + public static final int MESSAGE_ID_ETWS_TSUNAMI_WARNING = 0x1101; + + /** ETWS Message Identifier for earthquake and tsunami combined warning message. */ + public static final int MESSAGE_ID_ETWS_EARTHQUAKE_AND_TSUNAMI_WARNING = 0x1102; + + /** ETWS Message Identifier for test message. */ + public static final int MESSAGE_ID_ETWS_TEST_MESSAGE = 0x1103; + + /** ETWS Message Identifier for messages related to other emergency types. */ + public static final int MESSAGE_ID_ETWS_OTHER_EMERGENCY_TYPE = 0x1104; + + /** Start of CMAS Message Identifier range. */ + public static final int MESSAGE_ID_CMAS_FIRST_IDENTIFIER = 0x1112; + + /** CMAS Message Identifier for Presidential Level alerts. */ + public static final int MESSAGE_ID_CMAS_ALERT_PRESIDENTIAL_LEVEL = 0x1112; + + /** CMAS Message Identifier for Extreme alerts, Urgency=Immediate, Certainty=Observed. */ + public static final int MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_OBSERVED = 0x1113; + + /** CMAS Message Identifier for Extreme alerts, Urgency=Immediate, Certainty=Likely. */ + public static final int MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_LIKELY = 0x1114; + + /** CMAS Message Identifier for Extreme alerts, Urgency=Expected, Certainty=Observed. */ + public static final int MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_OBSERVED = 0x1115; + + /** CMAS Message Identifier for Extreme alerts, Urgency=Expected, Certainty=Likely. */ + public static final int MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_LIKELY = 0x1116; + + /** CMAS Message Identifier for Severe alerts, Urgency=Immediate, Certainty=Observed. */ + public static final int MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_OBSERVED = 0x1117; + + /** CMAS Message Identifier for Severe alerts, Urgency=Immediate, Certainty=Likely. */ + public static final int MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_LIKELY = 0x1118; + + /** CMAS Message Identifier for Severe alerts, Urgency=Expected, Certainty=Observed. */ + public static final int MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_OBSERVED = 0x1119; + + /** CMAS Message Identifier for Severe alerts, Urgency=Expected, Certainty=Likely. */ + public static final int MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_LIKELY = 0x111A; + + /** CMAS Message Identifier for Child Abduction Emergency (Amber Alert). */ + public static final int MESSAGE_ID_CMAS_ALERT_CHILD_ABDUCTION_EMERGENCY = 0x111B; + + /** CMAS Message Identifier for the Required Monthly Test. */ + public static final int MESSAGE_ID_CMAS_ALERT_REQUIRED_MONTHLY_TEST = 0x111C; + + /** CMAS Message Identifier for CMAS Exercise. */ + public static final int MESSAGE_ID_CMAS_ALERT_EXERCISE = 0x111D; + + /** CMAS Message Identifier for operator defined use. */ + public static final int MESSAGE_ID_CMAS_ALERT_OPERATOR_DEFINED_USE = 0x111E; + + /** End of CMAS Message Identifier range (including future extensions). */ + public static final int MESSAGE_ID_CMAS_LAST_IDENTIFIER = 0x112F; + + /** End of PWS Message Identifier range (includes ETWS, CMAS, and future extensions). */ + public static final int MESSAGE_ID_PWS_LAST_IDENTIFIER = 0x18FF; + + /** ETWS message code flag to activate the popup display. */ + public static final int MESSAGE_CODE_ETWS_ACTIVATE_POPUP = 0x100; + + /** ETWS message code flag to activate the emergency user alert. */ + public static final int MESSAGE_CODE_ETWS_EMERGENCY_USER_ALERT = 0x200; + + /** ETWS warning type value for earthquake. */ + public static final int ETWS_WARNING_TYPE_EARTHQUAKE = 0x00; + + /** ETWS warning type value for tsunami. */ + public static final int ETWS_WARNING_TYPE_TSUNAMI = 0x01; + + /** ETWS warning type value for earthquake and tsunami. */ + public static final int ETWS_WARNING_TYPE_EARTHQUAKE_AND_TSUNAMI = 0x02; + + /** ETWS warning type value for test broadcast. */ + public static final int ETWS_WARNING_TYPE_TEST = 0x03; + + /** ETWS warning type value for other notifications. */ + public static final int ETWS_WARNING_TYPE_OTHER = 0x04; +} diff --git a/telephony/java/android/telephony/SmsCbMessage.java b/telephony/java/android/telephony/SmsCbMessage.java index 5608402..383e0f9 100644 --- a/telephony/java/android/telephony/SmsCbMessage.java +++ b/telephony/java/android/telephony/SmsCbMessage.java @@ -16,9 +16,11 @@ package android.telephony; +import android.text.format.Time; import android.util.Log; import com.android.internal.telephony.GsmAlphabet; +import com.android.internal.telephony.IccUtils; import com.android.internal.telephony.gsm.SmsCbHeader; import java.io.UnsupportedEncodingException; @@ -93,9 +95,26 @@ public class SmsCbMessage { private String mBody; + /** Timestamp of ETWS primary notification with security. */ + private long mPrimaryNotificationTimestamp; + + /** 43 byte digital signature of ETWS primary notification with security. */ + private byte[] mPrimaryNotificationDigitalSignature; + private SmsCbMessage(byte[] pdu) throws IllegalArgumentException { mHeader = new SmsCbHeader(pdu); - parseBody(pdu); + if (mHeader.format == SmsCbHeader.FORMAT_ETWS_PRIMARY) { + mBody = "ETWS"; + // ETWS primary notification with security is 56 octets in length + if (pdu.length >= SmsCbHeader.PDU_LENGTH_ETWS) { + mPrimaryNotificationTimestamp = getTimestampMillis(pdu); + mPrimaryNotificationDigitalSignature = new byte[43]; + // digital signature starts after 6 byte header and 7 byte timestamp + System.arraycopy(pdu, 13, mPrimaryNotificationDigitalSignature, 0, 43); + } + } else { + parseBody(pdu); + } } /** @@ -157,6 +176,55 @@ public class SmsCbMessage { } /** + * Get the format of this message. + * @return {@link SmsCbHeader#FORMAT_GSM}, {@link SmsCbHeader#FORMAT_UMTS}, or + * {@link SmsCbHeader#FORMAT_ETWS_PRIMARY} + */ + public int getMessageFormat() { + return mHeader.format; + } + + /** + * For ETWS primary notifications, return the emergency user alert flag. + * @return true to notify terminal to activate emergency user alert; false otherwise + */ + public boolean getEtwsEmergencyUserAlert() { + return mHeader.etwsEmergencyUserAlert; + } + + /** + * For ETWS primary notifications, return the popup flag. + * @return true to notify terminal to activate display popup; false otherwise + */ + public boolean getEtwsPopup() { + return mHeader.etwsPopup; + } + + /** + * For ETWS primary notifications, return the warning type. + * @return a value such as {@link SmsCbConstants#ETWS_WARNING_TYPE_EARTHQUAKE} + */ + public int getEtwsWarningType() { + return mHeader.etwsWarningType; + } + + /** + * For ETWS primary notifications, return the Warning-Security-Information timestamp. + * @return a timestamp in System.currentTimeMillis() format. + */ + public long getEtwsSecurityTimestamp() { + return mPrimaryNotificationTimestamp; + } + + /** + * For ETWS primary notifications, return the 43 byte digital signature. + * @return a byte array containing a copy of the digital signature + */ + public byte[] getEtwsSecuritySignature() { + return mPrimaryNotificationDigitalSignature.clone(); + } + + /** * Parse and unpack the body text according to the encoding in the DCS. * After completing successfully this method will have assigned the body * text into mBody, and optionally the language code into mLanguage @@ -333,4 +401,59 @@ public class SmsCbMessage { return body; } + + /** + * Parses an ETWS primary notification timestamp and returns a currentTimeMillis()-style + * timestamp. Copied from com.android.internal.telephony.gsm.SmsMessage. + * @param pdu the ETWS primary notification PDU to decode + * @return the UTC timestamp from the Warning-Security-Information parameter + */ + private long getTimestampMillis(byte[] pdu) { + // Timestamp starts after CB header, in pdu[6] + int year = IccUtils.gsmBcdByteToInt(pdu[6]); + int month = IccUtils.gsmBcdByteToInt(pdu[7]); + int day = IccUtils.gsmBcdByteToInt(pdu[8]); + int hour = IccUtils.gsmBcdByteToInt(pdu[9]); + int minute = IccUtils.gsmBcdByteToInt(pdu[10]); + int second = IccUtils.gsmBcdByteToInt(pdu[11]); + + // For the timezone, the most significant bit of the + // least significant nibble is the sign byte + // (meaning the max range of this field is 79 quarter-hours, + // which is more than enough) + + byte tzByte = pdu[12]; + + // Mask out sign bit. + int timezoneOffset = IccUtils.gsmBcdByteToInt((byte) (tzByte & (~0x08))); + + timezoneOffset = ((tzByte & 0x08) == 0) ? timezoneOffset : -timezoneOffset; + + Time time = new Time(Time.TIMEZONE_UTC); + + // It's 2006. Should I really support years < 2000? + time.year = year >= 90 ? year + 1900 : year + 2000; + time.month = month - 1; + time.monthDay = day; + time.hour = hour; + time.minute = minute; + time.second = second; + + // Timezone offset is in quarter hours. + return time.toMillis(true) - (timezoneOffset * 15 * 60 * 1000); + } + + /** + * Append text to the message body. This is used to concatenate multi-page GSM broadcasts. + * @param body the text to append to this message + */ + public void appendToBody(String body) { + mBody = mBody + body; + } + + @Override + public String toString() { + return "SmsCbMessage{" + mHeader.toString() + ", language=" + mLanguage + + ", body=\"" + mBody + "\"}"; + } } diff --git a/telephony/java/android/telephony/SmsManager.java b/telephony/java/android/telephony/SmsManager.java index 0ecd854..480186c 100644 --- a/telephony/java/android/telephony/SmsManager.java +++ b/telephony/java/android/telephony/SmsManager.java @@ -21,7 +21,6 @@ import android.os.RemoteException; import android.os.ServiceManager; import android.text.TextUtils; -import com.android.internal.telephony.EncodeException; import com.android.internal.telephony.ISms; import com.android.internal.telephony.IccConstants; import com.android.internal.telephony.SmsRawData; @@ -348,7 +347,7 @@ public final class SmsManager { * message identifier. Note that if two different clients enable the same * message identifier, they must both disable it for the device to stop * receiving those messages. All received messages will be broadcast in an - * intent with the action "android.provider.telephony.SMS_CB_RECEIVED". + * intent with the action "android.provider.Telephony.SMS_CB_RECEIVED". * Note: This call is blocking, callers may want to avoid calling it from * the main thread of an application. * @@ -404,6 +403,68 @@ public final class SmsManager { } /** + * Enable reception of cell broadcast (SMS-CB) messages with the given + * message identifier range. Note that if two different clients enable the same + * message identifier, they must both disable it for the device to stop + * receiving those messages. All received messages will be broadcast in an + * intent with the action "android.provider.Telephony.SMS_CB_RECEIVED". + * Note: This call is blocking, callers may want to avoid calling it from + * the main thread of an application. + * + * @param startMessageId first message identifier as specified in TS 23.041 + * @param endMessageId last message identifier as specified in TS 23.041 + * @return true if successful, false otherwise + * @see #disableCellBroadcastRange(int, int) + * + * {@hide} + */ + public boolean enableCellBroadcastRange(int startMessageId, int endMessageId) { + boolean success = false; + + try { + ISms iccISms = ISms.Stub.asInterface(ServiceManager.getService("isms")); + if (iccISms != null) { + success = iccISms.enableCellBroadcastRange(startMessageId, endMessageId); + } + } catch (RemoteException ex) { + // ignore it + } + + return success; + } + + /** + * Disable reception of cell broadcast (SMS-CB) messages with the given + * message identifier range. Note that if two different clients enable the same + * message identifier, they must both disable it for the device to stop + * receiving those messages. + * Note: This call is blocking, callers may want to avoid calling it from + * the main thread of an application. + * + * @param startMessageId first message identifier as specified in TS 23.041 + * @param endMessageId last message identifier as specified in TS 23.041 + * @return true if successful, false otherwise + * + * @see #enableCellBroadcastRange(int, int) + * + * {@hide} + */ + public boolean disableCellBroadcastRange(int startMessageId, int endMessageId) { + boolean success = false; + + try { + ISms iccISms = ISms.Stub.asInterface(ServiceManager.getService("isms")); + if (iccISms != null) { + success = iccISms.disableCellBroadcastRange(startMessageId, endMessageId); + } + } catch (RemoteException ex) { + // ignore it + } + + return success; + } + + /** * Create a list of <code>SmsMessage</code>s from a list of RawSmsData * records returned by <code>getAllMessagesFromIcc()</code> * diff --git a/telephony/java/com/android/internal/telephony/GsmAlphabet.java b/telephony/java/com/android/internal/telephony/GsmAlphabet.java index 57d73e3..8ea1f8d 100644 --- a/telephony/java/com/android/internal/telephony/GsmAlphabet.java +++ b/telephony/java/com/android/internal/telephony/GsmAlphabet.java @@ -83,6 +83,8 @@ public class GsmAlphabet { * GSM_EXTENDED_ESCAPE if this character is in the extended table. * In this case, you must call charToGsmExtended() for the value * that should follow GSM_EXTENDED_ESCAPE in the GSM alphabet string. + * @param c the character to convert + * @return the GSM 7 bit table index for the specified character */ public static int charToGsm(char c) { @@ -96,12 +98,15 @@ public class GsmAlphabet { /** * Converts a char to a GSM 7 bit table index. + * Returns GSM_EXTENDED_ESCAPE if this character is in the extended table. + * In this case, you must call charToGsmExtended() for the value that + * should follow GSM_EXTENDED_ESCAPE in the GSM alphabet string. + * + * @param c the character to convert * @param throwException If true, throws EncodeException on invalid char. * If false, returns GSM alphabet ' ' char. - * - * Returns GSM_EXTENDED_ESCAPE if this character is in the extended table - * In this case, you must call charToGsmExtended() for the value that - * should follow GSM_EXTENDED_ESCAPE in the GSM alphabet string + * @throws EncodeException encode error when throwException is true + * @return the GSM 7 bit table index for the specified character */ public static int charToGsm(char c, boolean throwException) throws EncodeException { @@ -130,6 +135,8 @@ public class GsmAlphabet { * Converts a char to an extended GSM 7 bit table index. * Extended chars should be escaped with GSM_EXTENDED_ESCAPE. * Returns ' ' in GSM alphabet if there's no possible match. + * @param c the character to convert + * @return the GSM 7 bit extended table index for the specified character */ public static int charToGsmExtended(char c) { @@ -152,6 +159,9 @@ public class GsmAlphabet { * gsmExtendedToChar(). * * If an unmappable value is passed (one greater than 127), ' ' is returned. + * + * @param gsmChar the GSM 7 bit table index to convert + * @return the decoded character */ public static char gsmToChar(int gsmChar) { @@ -171,6 +181,9 @@ public class GsmAlphabet { * * If an unmappable value is passed, the character from the GSM 7 bit * default table will be used (table 6.2.1.1 of TS 23.038). + * + * @param gsmChar the GSM 7 bit extended table index to convert + * @return the decoded character */ public static char gsmExtendedToChar(int gsmChar) { @@ -241,6 +254,26 @@ public class GsmAlphabet { * septets. * * @param data the data string to encode + * @return the encoded string + * @throws EncodeException if String is too large to encode + */ + public static byte[] stringToGsm7BitPacked(String data) + throws EncodeException { + return stringToGsm7BitPacked(data, 0, true, 0, 0); + } + + /** + * Converts a String into a byte array containing + * the 7-bit packed GSM Alphabet representation of the string. + * + * Unencodable chars are encoded as spaces + * + * Byte 0 in the returned byte array is the count of septets used + * The returned byte array is the minimum size required to store + * the packed septets. The returned array cannot contain more than 255 + * septets. + * + * @param data the data string to encode * @param languageTable the 7 bit language table, or 0 for the default GSM alphabet * @param languageShiftTable the 7 bit single shift language table, or 0 for the default * GSM extension table @@ -446,6 +479,11 @@ public class GsmAlphabet { * * Field may be padded with trailing 0xff's. The decode stops * at the first 0xff encountered. + * + * @param data the byte array to decode + * @param offset array offset for the first character to decode + * @param length the number of bytes to decode + * @return the decoded string */ public static String gsm8BitUnpackedToString(byte[] data, int offset, int length) { @@ -494,6 +532,8 @@ public class GsmAlphabet { /** * Convert a string into an 8-bit unpacked GSM alphabet byte array. * Always uses GSM default 7-bit alphabet and extension table. + * @param s the string to encode + * @return the 8-bit GSM encoded byte array for the string */ public static byte[] stringToGsm8BitPacked(String s) { @@ -512,11 +552,13 @@ public class GsmAlphabet { /** * Write a String into a GSM 8-bit unpacked field of - * @param length size at @param offset in @param dest - * * Field is padded with 0xff's, string is truncated if necessary + * + * @param s the string to encode + * @param dest the destination byte array + * @param offset the starting offset for the encoded string + * @param length the maximum number of bytes to write */ - public static void stringToGsm8BitUnpackedField(String s, byte dest[], int offset, int length) { int outByteIndex = offset; @@ -558,6 +600,8 @@ public class GsmAlphabet { /** * Returns the count of 7-bit GSM alphabet characters * needed to represent this character. Counts unencodable char as 1 septet. + * @param c the character to examine + * @return the number of septets for this character */ public static int countGsmSeptets(char c) { @@ -572,8 +616,11 @@ public class GsmAlphabet { /** * Returns the count of 7-bit GSM alphabet characters * needed to represent this character using the default 7 bit GSM alphabet. + * @param c the character to examine * @param throwsException If true, throws EncodeException if unencodable - * char. Otherwise, counts invalid char as 1 septet + * char. Otherwise, counts invalid char as 1 septet. + * @return the number of septets for this character + * @throws EncodeException the character can't be encoded and throwsException is true */ public static int countGsmSeptets(char c, boolean throwsException) throws EncodeException { diff --git a/telephony/java/com/android/internal/telephony/ISms.aidl b/telephony/java/com/android/internal/telephony/ISms.aidl index 90de5e1..735f986 100644 --- a/telephony/java/com/android/internal/telephony/ISms.aidl +++ b/telephony/java/com/android/internal/telephony/ISms.aidl @@ -170,4 +170,32 @@ interface ISms { */ boolean disableCellBroadcast(int messageIdentifier); + /** + * Enable reception of cell broadcast (SMS-CB) messages with the given + * message identifier range. Note that if two different clients enable + * a message identifier range, they must both disable it for the device + * to stop receiving those messages. + * + * @param startMessageId first message identifier as specified in TS 23.041 + * @param endMessageId last message identifier as specified in TS 23.041 + * @return true if successful, false otherwise + * + * @see #disableCellBroadcastRange(int, int) + */ + boolean enableCellBroadcastRange(int startMessageId, int endMessageId); + + /** + * Disable reception of cell broadcast (SMS-CB) messages with the given + * message identifier range. Note that if two different clients enable + * a message identifier range, they must both disable it for the device + * to stop receiving those messages. + * + * @param startMessageId first message identifier as specified in TS 23.041 + * @param endMessageId last message identifier as specified in TS 23.041 + * @return true if successful, false otherwise + * + * @see #enableCellBroadcastRange(int, int) + */ + boolean disableCellBroadcastRange(int startMessageId, int endMessageId); + } diff --git a/telephony/java/com/android/internal/telephony/IccSmsInterfaceManagerProxy.java b/telephony/java/com/android/internal/telephony/IccSmsInterfaceManagerProxy.java index 5049249..54de508 100644 --- a/telephony/java/com/android/internal/telephony/IccSmsInterfaceManagerProxy.java +++ b/telephony/java/com/android/internal/telephony/IccSmsInterfaceManagerProxy.java @@ -76,4 +76,13 @@ public class IccSmsInterfaceManagerProxy extends ISms.Stub { return mIccSmsInterfaceManager.disableCellBroadcast(messageIdentifier); } + public boolean enableCellBroadcastRange(int startMessageId, int endMessageId) + throws android.os.RemoteException { + return mIccSmsInterfaceManager.enableCellBroadcastRange(startMessageId, endMessageId); + } + + public boolean disableCellBroadcastRange(int startMessageId, int endMessageId) + throws android.os.RemoteException { + return mIccSmsInterfaceManager.disableCellBroadcastRange(startMessageId, endMessageId); + } } diff --git a/telephony/java/com/android/internal/telephony/IntRangeManager.java b/telephony/java/com/android/internal/telephony/IntRangeManager.java new file mode 100644 index 0000000..970bc44 --- /dev/null +++ b/telephony/java/com/android/internal/telephony/IntRangeManager.java @@ -0,0 +1,568 @@ +/* + * Copyright (C) 2011 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.telephony; + +import java.util.ArrayList; +import java.util.Iterator; + +/** + * Clients can enable reception of SMS-CB messages for specific ranges of + * message identifiers (channels). This class keeps track of the currently + * enabled message identifiers and calls abstract methods to update the + * radio when the range of enabled message identifiers changes. + * + * An update is a call to {@link #startUpdate} followed by zero or more + * calls to {@link #addRange} followed by a call to {@link #finishUpdate}. + * Calls to {@link #enableRange} and {@link #disableRange} will perform + * an incremental update operation if the enabled ranges have changed. + * A full update operation (i.e. after a radio reset) can be performed + * by a call to {@link #updateRanges}. + * + * Clients are identified by String (the name associated with the User ID + * of the caller) so that a call to remove a range can be mapped to the + * client that enabled that range (or else rejected). + */ +public abstract class IntRangeManager { + + /** + * Initial capacity for IntRange clients array list. There will be + * few cell broadcast listeners on a typical device, so this can be small. + */ + private static final int INITIAL_CLIENTS_ARRAY_SIZE = 4; + + /** + * One or more clients forming the continuous range [startId, endId]. + * <p>When a client is added, the IntRange may merge with one or more + * adjacent IntRanges to form a single combined IntRange. + * <p>When a client is removed, the IntRange may divide into several + * non-contiguous IntRanges. + */ + private class IntRange { + int startId; + int endId; + // sorted by earliest start id + final ArrayList<ClientRange> clients; + + /** + * Create a new IntRange with a single client. + * @param startId the first id included in the range + * @param endId the last id included in the range + * @param client the client requesting the enabled range + */ + IntRange(int startId, int endId, String client) { + this.startId = startId; + this.endId = endId; + clients = new ArrayList<ClientRange>(INITIAL_CLIENTS_ARRAY_SIZE); + clients.add(new ClientRange(startId, endId, client)); + } + + /** + * Create a new IntRange for an existing ClientRange. + * @param clientRange the initial ClientRange to add + */ + IntRange(ClientRange clientRange) { + startId = clientRange.startId; + endId = clientRange.endId; + clients = new ArrayList<ClientRange>(INITIAL_CLIENTS_ARRAY_SIZE); + clients.add(clientRange); + } + + /** + * Create a new IntRange from an existing IntRange. This is used for + * removing a ClientRange, because new IntRanges may need to be created + * for any gaps that open up after the ClientRange is removed. A copy + * is made of the elements of the original IntRange preceding the element + * that is being removed. The following elements will be added to this + * IntRange or to a new IntRange when a gap is found. + * @param intRange the original IntRange to copy elements from + * @param numElements the number of elements to copy from the original + */ + IntRange(IntRange intRange, int numElements) { + this.startId = intRange.startId; + this.endId = intRange.endId; + this.clients = new ArrayList<ClientRange>(intRange.clients.size()); + for (int i=0; i < numElements; i++) { + this.clients.add(intRange.clients.get(i)); + } + } + + /** + * Insert new ClientRange in order by start id. + * <p>If the new ClientRange is known to be sorted before or after the + * existing ClientRanges, or at a particular index, it can be added + * to the clients array list directly, instead of via this method. + * <p>Note that this can be changed from linear to binary search if the + * number of clients grows large enough that it would make a difference. + * @param range the new ClientRange to insert + */ + void insert(ClientRange range) { + int len = clients.size(); + for (int i=0; i < len; i++) { + ClientRange nextRange = clients.get(i); + if (range.startId <= nextRange.startId) { + // ignore duplicate ranges from the same client + if (!range.equals(nextRange)) { + clients.add(i, range); + } + return; + } + } + clients.add(range); // append to end of list + } + } + + /** + * The message id range for a single client. + */ + private class ClientRange { + final int startId; + final int endId; + final String client; + + ClientRange(int startId, int endId, String client) { + this.startId = startId; + this.endId = endId; + this.client = client; + } + + @Override + public boolean equals(Object o) { + if (o != null && o instanceof ClientRange) { + ClientRange other = (ClientRange) o; + return startId == other.startId && + endId == other.endId && + client.equals(other.client); + } else { + return false; + } + } + + @Override + public int hashCode() { + return (startId * 31 + endId) * 31 + client.hashCode(); + } + } + + /** + * List of integer ranges, one per client, sorted by start id. + */ + private ArrayList<IntRange> mRanges = new ArrayList<IntRange>(); + + protected IntRangeManager() {} + + /** + * Enable a range for the specified client and update ranges + * if necessary. If {@link #finishUpdate} returns failure, + * false is returned and the range is not added. + * + * @param startId the first id included in the range + * @param endId the last id included in the range + * @param client the client requesting the enabled range + * @return true if successful, false otherwise + */ + public synchronized boolean enableRange(int startId, int endId, String client) { + int len = mRanges.size(); + + // empty range list: add the initial IntRange + if (len == 0) { + if (tryAddSingleRange(startId, endId, true)) { + mRanges.add(new IntRange(startId, endId, client)); + return true; + } else { + return false; // failed to update radio + } + } + + for (int startIndex = 0; startIndex < len; startIndex++) { + IntRange range = mRanges.get(startIndex); + if (startId < range.startId) { + // test if new range completely precedes this range + // note that [1, 4] and [5, 6] coalesce to [1, 6] + if ((endId + 1) < range.startId) { + // insert new int range before previous first range + if (tryAddSingleRange(startId, endId, true)) { + mRanges.add(startIndex, new IntRange(startId, endId, client)); + return true; + } else { + return false; // failed to update radio + } + } else if (endId <= range.endId) { + // extend the start of this range + if (tryAddSingleRange(startId, range.startId - 1, true)) { + range.startId = startId; + range.clients.add(0, new ClientRange(startId, endId, client)); + return true; + } else { + return false; // failed to update radio + } + } else { + // find last range that can coalesce into the new combined range + for (int endIndex = startIndex+1; endIndex < len; endIndex++) { + IntRange endRange = mRanges.get(endIndex); + if ((endId + 1) < endRange.startId) { + // try to add entire new range + if (tryAddSingleRange(startId, endId, true)) { + range.startId = startId; + range.endId = endId; + // insert new ClientRange before existing ranges + range.clients.add(0, new ClientRange(startId, endId, client)); + // coalesce range with following ranges up to endIndex-1 + // remove each range after adding its elements, so the index + // of the next range to join is always startIndex+1. + // i is the index if no elements were removed: we only care + // about the number of loop iterations, not the value of i. + int joinIndex = startIndex + 1; + for (int i = joinIndex; i < endIndex; i++) { + IntRange joinRange = mRanges.get(joinIndex); + range.clients.addAll(joinRange.clients); + mRanges.remove(joinRange); + } + return true; + } else { + return false; // failed to update radio + } + } else if (endId <= endRange.endId) { + // add range from start id to start of last overlapping range, + // values from endRange.startId to endId are already enabled + if (tryAddSingleRange(startId, endRange.startId - 1, true)) { + range.startId = startId; + range.endId = endRange.endId; + // insert new ClientRange before existing ranges + range.clients.add(0, new ClientRange(startId, endId, client)); + // coalesce range with following ranges up to endIndex + // remove each range after adding its elements, so the index + // of the next range to join is always startIndex+1. + // i is the index if no elements were removed: we only care + // about the number of loop iterations, not the value of i. + int joinIndex = startIndex + 1; + for (int i = joinIndex; i <= endIndex; i++) { + IntRange joinRange = mRanges.get(joinIndex); + range.clients.addAll(joinRange.clients); + mRanges.remove(joinRange); + } + return true; + } else { + return false; // failed to update radio + } + } + } + + // endId extends past all existing IntRanges: combine them all together + if (tryAddSingleRange(startId, endId, true)) { + range.startId = startId; + range.endId = endId; + // insert new ClientRange before existing ranges + range.clients.add(0, new ClientRange(startId, endId, client)); + // coalesce range with following ranges up to len-1 + // remove each range after adding its elements, so the index + // of the next range to join is always startIndex+1. + // i is the index if no elements were removed: we only care + // about the number of loop iterations, not the value of i. + int joinIndex = startIndex + 1; + for (int i = joinIndex; i < len; i++) { + IntRange joinRange = mRanges.get(joinIndex); + range.clients.addAll(joinRange.clients); + mRanges.remove(joinRange); + } + return true; + } else { + return false; // failed to update radio + } + } + } else if ((startId + 1) <= range.endId) { + if (endId <= range.endId) { + // completely contained in existing range; no radio changes + range.insert(new ClientRange(startId, endId, client)); + return true; + } else { + // find last range that can coalesce into the new combined range + int endIndex = startIndex; + for (int testIndex = startIndex+1; testIndex < len; testIndex++) { + IntRange testRange = mRanges.get(testIndex); + if ((endId + 1) < testRange.startId) { + break; + } else { + endIndex = testIndex; + } + } + // no adjacent IntRanges to combine + if (endIndex == startIndex) { + // add range from range.endId+1 to endId, + // values from startId to range.endId are already enabled + if (tryAddSingleRange(range.endId + 1, endId, true)) { + range.endId = endId; + range.insert(new ClientRange(startId, endId, client)); + return true; + } else { + return false; // failed to update radio + } + } + // get last range to coalesce into start range + IntRange endRange = mRanges.get(endIndex); + // Values from startId to range.endId have already been enabled. + // if endId > endRange.endId, then enable range from range.endId+1 to endId, + // else enable range from range.endId+1 to endRange.startId-1, because + // values from endRange.startId to endId have already been added. + int newRangeEndId = (endId <= endRange.endId) ? endRange.startId - 1 : endId; + if (tryAddSingleRange(range.endId + 1, newRangeEndId, true)) { + range.endId = endId; + // insert new ClientRange in place + range.insert(new ClientRange(startId, endId, client)); + // coalesce range with following ranges up to endIndex-1 + // remove each range after adding its elements, so the index + // of the next range to join is always startIndex+1 (joinIndex). + // i is the index if no elements had been removed: we only care + // about the number of loop iterations, not the value of i. + int joinIndex = startIndex + 1; + for (int i = joinIndex; i < endIndex; i++) { + IntRange joinRange = mRanges.get(joinIndex); + range.clients.addAll(joinRange.clients); + mRanges.remove(joinRange); + } + return true; + } else { + return false; // failed to update radio + } + } + } + } + + // append new range after existing IntRanges + if (tryAddSingleRange(startId, endId, true)) { + mRanges.add(new IntRange(startId, endId, client)); + return true; + } else { + return false; // failed to update radio + } + } + + /** + * Disable a range for the specified client and update ranges + * if necessary. If {@link #finishUpdate} returns failure, + * false is returned and the range is not removed. + * + * @param startId the first id included in the range + * @param endId the last id included in the range + * @param client the client requesting to disable the range + * @return true if successful, false otherwise + */ + public synchronized boolean disableRange(int startId, int endId, String client) { + int len = mRanges.size(); + + for (int i=0; i < len; i++) { + IntRange range = mRanges.get(i); + if (startId < range.startId) { + return false; // not found + } else if (endId <= range.endId) { + // found the IntRange that encloses the client range, if any + // search for it in the clients list + ArrayList<ClientRange> clients = range.clients; + + // handle common case of IntRange containing one ClientRange + int crLength = clients.size(); + if (crLength == 1) { + ClientRange cr = clients.get(0); + if (cr.startId == startId && cr.endId == endId && cr.client.equals(client)) { + // disable range in radio then remove the entire IntRange + if (tryAddSingleRange(startId, endId, false)) { + mRanges.remove(i); + return true; + } else { + return false; // failed to update radio + } + } else { + return false; // not found + } + } + + // several ClientRanges: remove one, potentially splitting into many IntRanges. + // Save the original start and end id for the original IntRange + // in case the radio update fails and we have to revert it. If the + // update succeeds, we remove the client range and insert the new IntRanges. + int largestEndId = Integer.MIN_VALUE; // largest end identifier found + boolean updateStarted = false; + + for (int crIndex=0; crIndex < crLength; crIndex++) { + ClientRange cr = clients.get(crIndex); + if (cr.startId == startId && cr.endId == endId && cr.client.equals(client)) { + // found the ClientRange to remove, check if it's the last in the list + if (crIndex == crLength - 1) { + if (range.endId == largestEndId) { + // no channels to remove from radio; return success + clients.remove(crIndex); + return true; + } else { + // disable the channels at the end and lower the end id + if (tryAddSingleRange(largestEndId + 1, range.endId, false)) { + clients.remove(crIndex); + range.endId = largestEndId; + return true; + } else { + return false; + } + } + } + + // copy the IntRange so that we can remove elements and modify the + // start and end id's in the copy, leaving the original unmodified + // until after the radio update succeeds + IntRange rangeCopy = new IntRange(range, crIndex); + + if (crIndex == 0) { + // removing the first ClientRange, so we may need to increase + // the start id of the IntRange. + // We know there are at least two ClientRanges in the list, + // so clients.get(1) should always succeed. + int nextStartId = clients.get(1).startId; + if (nextStartId != range.startId) { + startUpdate(); + updateStarted = true; + addRange(range.startId, nextStartId - 1, false); + rangeCopy.startId = nextStartId; + } + // init largestEndId + largestEndId = clients.get(1).endId; + } + + // go through remaining ClientRanges, creating new IntRanges when + // there is a gap in the sequence. After radio update succeeds, + // remove the original IntRange and append newRanges to mRanges. + // Otherwise, leave the original IntRange in mRanges and return false. + ArrayList<IntRange> newRanges = new ArrayList<IntRange>(); + + IntRange currentRange = rangeCopy; + for (int nextIndex = crIndex + 1; nextIndex < crLength; nextIndex++) { + ClientRange nextCr = clients.get(nextIndex); + if (nextCr.startId > largestEndId + 1) { + if (!updateStarted) { + startUpdate(); + updateStarted = true; + } + addRange(largestEndId + 1, nextCr.startId - 1, false); + currentRange.endId = largestEndId; + newRanges.add(currentRange); + currentRange = new IntRange(nextCr); + } else { + currentRange.clients.add(nextCr); + } + if (nextCr.endId > largestEndId) { + largestEndId = nextCr.endId; + } + } + + // remove any channels between largestEndId and endId + if (largestEndId < endId) { + if (!updateStarted) { + startUpdate(); + updateStarted = true; + } + addRange(largestEndId + 1, endId, false); + currentRange.endId = largestEndId; + } + newRanges.add(currentRange); + + if (updateStarted && !finishUpdate()) { + return false; // failed to update radio + } + + // replace the original IntRange with newRanges + mRanges.remove(i); + mRanges.addAll(i, newRanges); + return true; + } else { + // not the ClientRange to remove; save highest end ID seen so far + if (cr.endId > largestEndId) { + largestEndId = cr.endId; + } + } + } + } + } + + return false; // not found + } + + /** + * Perform a complete update operation (enable all ranges). Useful + * after a radio reset. Calls {@link #startUpdate}, followed by zero or + * more calls to {@link #addRange}, followed by {@link #finishUpdate}. + * @return true if successful, false otherwise + */ + public boolean updateRanges() { + startUpdate(); + Iterator<IntRange> iterator = mRanges.iterator(); + if (iterator.hasNext()) { + IntRange range = iterator.next(); + int start = range.startId; + int end = range.endId; + // accumulate ranges of [startId, endId] + while (iterator.hasNext()) { + IntRange nextNode = iterator.next(); + // [startIdA, endIdA], [endIdA + 1, endIdB] -> [startIdA, endIdB] + if (nextNode.startId <= (end + 1)) { + if (nextNode.endId > end) { + end = nextNode.endId; + } + } else { + addRange(start, end, true); + start = nextNode.startId; + end = nextNode.endId; + } + } + // add final range + addRange(start, end, true); + } + return finishUpdate(); + } + + /** + * Enable or disable a single range of message identifiers. + * @param startId the first id included in the range + * @param endId the last id included in the range + * @param selected true to enable range, false to disable range + * @return true if successful, false otherwise + */ + private boolean tryAddSingleRange(int startId, int endId, boolean selected) { + startUpdate(); + addRange(startId, endId, selected); + return finishUpdate(); + } + + /** + * Called when the list of enabled ranges has changed. This will be + * followed by zero or more calls to {@link #addRange} followed by + * a call to {@link #finishUpdate}. + */ + protected abstract void startUpdate(); + + /** + * Called after {@link #startUpdate} to indicate a range of enabled + * or disabled values. + * + * @param startId the first id included in the range + * @param endId the last id included in the range + * @param selected true to enable range, false to disable range + */ + protected abstract void addRange(int startId, int endId, boolean selected); + + /** + * Called to indicate the end of a range update started by the + * previous call to {@link #startUpdate}. + * @return true if successful, false otherwise + */ + protected abstract boolean finishUpdate(); +} diff --git a/telephony/java/com/android/internal/telephony/SMSDispatcher.java b/telephony/java/com/android/internal/telephony/SMSDispatcher.java index ad34550..69dd029 100755..100644 --- a/telephony/java/com/android/internal/telephony/SMSDispatcher.java +++ b/telephony/java/com/android/internal/telephony/SMSDispatcher.java @@ -32,11 +32,9 @@ import android.database.Cursor; import android.database.SQLException; import android.net.Uri; import android.os.AsyncResult; -import android.os.Environment; import android.os.Handler; import android.os.Message; import android.os.PowerManager; -import android.os.StatFs; import android.provider.Telephony; import android.provider.Telephony.Sms.Intents; import android.provider.Settings; @@ -878,37 +876,6 @@ public abstract class SMSDispatcher extends Handler { protected abstract void sendMultipartSms (SmsTracker tracker); /** - * Activate or deactivate cell broadcast SMS. - * - * @param activate - * 0 = activate, 1 = deactivate - * @param response - * Callback message is empty on completion - */ - protected abstract void activateCellBroadcastSms(int activate, Message response); - - /** - * Query the current configuration of cell broadcast SMS. - * - * @param response - * Callback message contains the configuration from the modem on completion - * @see #setCellBroadcastConfig - */ - protected abstract void getCellBroadcastSmsConfig(Message response); - - /** - * Configure cell broadcast SMS. - * - * @param configValuesArray - * The first element defines the number of triples that follow. - * A triple is made up of the service category, the language identifier - * and a boolean that specifies whether the category is set active. - * @param response - * Callback message is empty on completion - */ - protected abstract void setCellBroadcastConfig(int[] configValuesArray, Message response); - - /** * Send an acknowledge message. * @param success indicates that last message was successfully received. * @param result result code indicating any error @@ -1014,14 +981,21 @@ public abstract class SMSDispatcher extends Handler { protected abstract void handleBroadcastSms(AsyncResult ar); - protected void dispatchBroadcastPdus(byte[][] pdus) { - Intent intent = new Intent("android.provider.telephony.SMS_CB_RECEIVED"); - intent.putExtra("pdus", pdus); + protected void dispatchBroadcastPdus(byte[][] pdus, boolean isEmergencyMessage) { + if (isEmergencyMessage) { + Intent intent = new Intent(Intents.SMS_EMERGENCY_CB_RECEIVED_ACTION); + intent.putExtra("pdus", pdus); + if (Config.LOGD) + Log.d(TAG, "Dispatching " + pdus.length + " emergency SMS CB pdus"); - if (Config.LOGD) - Log.d(TAG, "Dispatching " + pdus.length + " SMS CB pdus"); + dispatch(intent, "android.permission.RECEIVE_EMERGENCY_BROADCAST"); + } else { + Intent intent = new Intent(Intents.SMS_CB_RECEIVED_ACTION); + intent.putExtra("pdus", pdus); + if (Config.LOGD) + Log.d(TAG, "Dispatching " + pdus.length + " SMS CB pdus"); - dispatch(intent, "android.permission.RECEIVE_SMS"); + dispatch(intent, "android.permission.RECEIVE_SMS"); + } } - } diff --git a/telephony/java/com/android/internal/telephony/WapPushOverSms.java b/telephony/java/com/android/internal/telephony/WapPushOverSms.java index 7704667..7704667 100644..100755 --- a/telephony/java/com/android/internal/telephony/WapPushOverSms.java +++ b/telephony/java/com/android/internal/telephony/WapPushOverSms.java diff --git a/telephony/java/com/android/internal/telephony/WspTypeDecoder.java b/telephony/java/com/android/internal/telephony/WspTypeDecoder.java index c8dd718..73260fb 100644..100755 --- a/telephony/java/com/android/internal/telephony/WspTypeDecoder.java +++ b/telephony/java/com/android/internal/telephony/WspTypeDecoder.java @@ -194,6 +194,7 @@ public class WspTypeDecoder { public static final String CONTENT_TYPE_B_PUSH_CO = "application/vnd.wap.coc"; public static final String CONTENT_TYPE_B_MMS = "application/vnd.wap.mms-message"; + public static final String CONTENT_TYPE_B_PUSH_SYNCML_NOTI = "application/vnd.syncml.notification"; byte[] wspData; int dataLength; diff --git a/telephony/java/com/android/internal/telephony/cdma/CDMAPhone.java b/telephony/java/com/android/internal/telephony/cdma/CDMAPhone.java index 3890a98..1efae21 100755 --- a/telephony/java/com/android/internal/telephony/cdma/CDMAPhone.java +++ b/telephony/java/com/android/internal/telephony/cdma/CDMAPhone.java @@ -1163,7 +1163,8 @@ public class CDMAPhone extends PhoneBase { * @param response Callback message is empty on completion */ public void activateCellBroadcastSms(int activate, Message response) { - mSMS.activateCellBroadcastSms(activate, response); + Log.e(LOG_TAG, "[CDMAPhone] activateCellBroadcastSms() is obsolete; use SmsManager"); + response.sendToTarget(); } /** @@ -1172,7 +1173,8 @@ public class CDMAPhone extends PhoneBase { * @param response Callback message is empty on completion */ public void getCellBroadcastSmsConfig(Message response) { - mSMS.getCellBroadcastSmsConfig(response); + Log.e(LOG_TAG, "[CDMAPhone] getCellBroadcastSmsConfig() is obsolete; use SmsManager"); + response.sendToTarget(); } /** @@ -1181,7 +1183,8 @@ public class CDMAPhone extends PhoneBase { * @param response Callback message is empty on completion */ public void setCellBroadcastSmsConfig(int[] configValuesArray, Message response) { - mSMS.setCellBroadcastConfig(configValuesArray, response); + Log.e(LOG_TAG, "[CDMAPhone] setCellBroadcastSmsConfig() is obsolete; use SmsManager"); + response.sendToTarget(); } private static final String IS683A_FEATURE_CODE = "*228"; diff --git a/telephony/java/com/android/internal/telephony/cdma/CdmaSMSDispatcher.java b/telephony/java/com/android/internal/telephony/cdma/CdmaSMSDispatcher.java index d6fc134..a04cafc 100644..100755 --- a/telephony/java/com/android/internal/telephony/cdma/CdmaSMSDispatcher.java +++ b/telephony/java/com/android/internal/telephony/cdma/CdmaSMSDispatcher.java @@ -42,6 +42,7 @@ import com.android.internal.telephony.SmsHeader; import com.android.internal.telephony.SmsMessageBase; import com.android.internal.telephony.SmsMessageBase.TextEncodingDetails; import com.android.internal.telephony.TelephonyProperties; +import com.android.internal.telephony.WspTypeDecoder; import com.android.internal.telephony.cdma.sms.SmsEnvelope; import com.android.internal.telephony.cdma.sms.UserData; import com.android.internal.util.HexDump; @@ -51,6 +52,8 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; +import android.content.res.Resources; + final class CdmaSMSDispatcher extends SMSDispatcher { private static final String TAG = "CDMA"; @@ -58,6 +61,9 @@ final class CdmaSMSDispatcher extends SMSDispatcher { private byte[] mLastDispatchedSmsFingerprint; private byte[] mLastAcknowledgedSmsFingerprint; + private boolean mCheckForDuplicatePortsInOmadmWapPush = Resources.getSystem().getBoolean( + com.android.internal.R.bool.config_duplicate_port_omadm_wappush); + CdmaSMSDispatcher(CDMAPhone phone) { super(phone); } @@ -245,6 +251,13 @@ final class CdmaSMSDispatcher extends SMSDispatcher { sourcePort |= 0xFF & pdu[index++]; destinationPort = (0xFF & pdu[index++]) << 8; destinationPort |= 0xFF & pdu[index++]; + // Some carriers incorrectly send duplicate port fields in omadm wap pushes. + // If configured, check for that here + if (mCheckForDuplicatePortsInOmadmWapPush) { + if (checkDuplicatePortOmadmWappush(pdu,index)) { + index = index + 4; // skip duplicate port fields + } + } } // Lookup all other related parts @@ -265,7 +278,7 @@ final class CdmaSMSDispatcher extends SMSDispatcher { if (cursorCount != totalSegments - 1) { // We don't have all the parts yet, store this one away ContentValues values = new ContentValues(); - values.put("date", new Long(0)); + values.put("date", (long) 0); values.put("pdu", HexDump.toHexString(pdu, index, pdu.length - index)); values.put("address", address); values.put("reference_number", referenceNumber); @@ -468,21 +481,6 @@ final class CdmaSMSDispatcher extends SMSDispatcher { } } - /** {@inheritDoc} */ - protected void activateCellBroadcastSms(int activate, Message response) { - mCm.setCdmaBroadcastActivation((activate == 0), response); - } - - /** {@inheritDoc} */ - protected void getCellBroadcastSmsConfig(Message response) { - mCm.getCdmaBroadcastConfig(response); - } - - /** {@inheritDoc} */ - protected void setCellBroadcastConfig(int[] configValuesArray, Message response) { - mCm.setCdmaBroadcastConfig(configValuesArray, response); - } - protected void handleBroadcastSms(AsyncResult ar) { // Not supported Log.e(TAG, "Error! Not implemented for CDMA."); @@ -503,4 +501,42 @@ final class CdmaSMSDispatcher extends SMSDispatcher { return CommandsInterface.CDMA_SMS_FAIL_CAUSE_ENCODING_PROBLEM; } } + + /** + * Optional check to see if the received WapPush is an OMADM notification with erroneous + * extra port fields. + * - Some carriers make this mistake. + * ex: MSGTYPE-TotalSegments-CurrentSegment + * -SourcePortDestPort-SourcePortDestPort-OMADM PDU + * @param origPdu The WAP-WDP PDU segment + * @param index Current Index while parsing the PDU. + * @return True if OrigPdu is OmaDM Push Message which has duplicate ports. + * False if OrigPdu is NOT OmaDM Push Message which has duplicate ports. + */ + private boolean checkDuplicatePortOmadmWappush(byte[] origPdu, int index) { + index += 4; + byte[] omaPdu = new byte[origPdu.length - index]; + System.arraycopy(origPdu, index, omaPdu, 0, omaPdu.length); + + WspTypeDecoder pduDecoder = new WspTypeDecoder(omaPdu); + int wspIndex = 2; + + // Process header length field + if (pduDecoder.decodeUintvarInteger(wspIndex) == false) { + return false; + } + + wspIndex += pduDecoder.getDecodedDataLength(); // advance to next field + + // Process content type field + if (pduDecoder.decodeContentType(wspIndex) == false) { + return false; + } + + String mimeType = pduDecoder.getValueString(); + if (mimeType != null && mimeType.equals(WspTypeDecoder.CONTENT_TYPE_B_PUSH_SYNCML_NOTI)) { + return true; + } + return false; + } } diff --git a/telephony/java/com/android/internal/telephony/cdma/RuimSmsInterfaceManager.java b/telephony/java/com/android/internal/telephony/cdma/RuimSmsInterfaceManager.java index f4a6d11..7b42b06 100644 --- a/telephony/java/com/android/internal/telephony/cdma/RuimSmsInterfaceManager.java +++ b/telephony/java/com/android/internal/telephony/cdma/RuimSmsInterfaceManager.java @@ -204,6 +204,18 @@ public class RuimSmsInterfaceManager extends IccSmsInterfaceManager { return false; } + public boolean enableCellBroadcastRange(int startMessageId, int endMessageId) { + // Not implemented + Log.e(LOG_TAG, "Error! Not implemented for CDMA."); + return false; + } + + public boolean disableCellBroadcastRange(int startMessageId, int endMessageId) { + // Not implemented + Log.e(LOG_TAG, "Error! Not implemented for CDMA."); + return false; + } + protected void log(String msg) { Log.d(LOG_TAG, "[RuimSmsInterfaceManager] " + msg); } diff --git a/telephony/java/com/android/internal/telephony/cdma/SmsMessage.java b/telephony/java/com/android/internal/telephony/cdma/SmsMessage.java index 54cf612..676a828 100644 --- a/telephony/java/com/android/internal/telephony/cdma/SmsMessage.java +++ b/telephony/java/com/android/internal/telephony/cdma/SmsMessage.java @@ -18,6 +18,7 @@ package com.android.internal.telephony.cdma; import android.os.Parcel; import android.os.SystemProperties; +import android.telephony.PhoneNumberUtils; import android.text.format.Time; import android.util.Config; import android.util.Log; @@ -811,7 +812,12 @@ public class SmsMessage extends SmsMessageBase { * mechanism, and avoid null pointer exceptions. */ - CdmaSmsAddress destAddr = CdmaSmsAddress.parse(destAddrStr); + /** + * North America Plus Code : + * Convert + code to 011 and dial out for international SMS + */ + CdmaSmsAddress destAddr = CdmaSmsAddress.parse( + PhoneNumberUtils.cdmaCheckAndProcessPlusCode(destAddrStr)); if (destAddr == null) return null; BearerData bearerData = new BearerData(); diff --git a/telephony/java/com/android/internal/telephony/gsm/GSMPhone.java b/telephony/java/com/android/internal/telephony/gsm/GSMPhone.java index 3959c67..c5be856 100644 --- a/telephony/java/com/android/internal/telephony/gsm/GSMPhone.java +++ b/telephony/java/com/android/internal/telephony/gsm/GSMPhone.java @@ -1471,16 +1471,35 @@ public class GSMPhone extends PhoneBase { return this.mIccFileHandler; } + /** + * Activate or deactivate cell broadcast SMS. + * + * @param activate 0 = activate, 1 = deactivate + * @param response Callback message is empty on completion + */ public void activateCellBroadcastSms(int activate, Message response) { - Log.e(LOG_TAG, "Error! This functionality is not implemented for GSM."); + Log.e(LOG_TAG, "[GSMPhone] activateCellBroadcastSms() is obsolete; use SmsManager"); + response.sendToTarget(); } + /** + * Query the current configuration of cdma cell broadcast SMS. + * + * @param response Callback message is empty on completion + */ public void getCellBroadcastSmsConfig(Message response) { - Log.e(LOG_TAG, "Error! This functionality is not implemented for GSM."); + Log.e(LOG_TAG, "[GSMPhone] getCellBroadcastSmsConfig() is obsolete; use SmsManager"); + response.sendToTarget(); } - public void setCellBroadcastSmsConfig(int[] configValuesArray, Message response){ - Log.e(LOG_TAG, "Error! This functionality is not implemented for GSM."); + /** + * Configure cdma cell broadcast SMS. + * + * @param response Callback message is empty on completion + */ + public void setCellBroadcastSmsConfig(int[] configValuesArray, Message response) { + Log.e(LOG_TAG, "[GSMPhone] setCellBroadcastSmsConfig() is obsolete; use SmsManager"); + response.sendToTarget(); } public boolean isCspPlmnEnabled() { diff --git a/telephony/java/com/android/internal/telephony/gsm/GsmSMSDispatcher.java b/telephony/java/com/android/internal/telephony/gsm/GsmSMSDispatcher.java index 14ad59a..de769d1 100755..100644 --- a/telephony/java/com/android/internal/telephony/gsm/GsmSMSDispatcher.java +++ b/telephony/java/com/android/internal/telephony/gsm/GsmSMSDispatcher.java @@ -350,6 +350,7 @@ final class GsmSMSDispatcher extends SMSDispatcher { * * @param tracker holds the multipart Sms tracker ready to be sent */ + @Override protected void sendMultipartSms (SmsTracker tracker) { ArrayList<String> parts; ArrayList<PendingIntent> sentIntents; @@ -370,6 +371,7 @@ final class GsmSMSDispatcher extends SMSDispatcher { } /** {@inheritDoc} */ + @Override protected void acknowledgeLastIncomingSms(boolean success, int result, Message response){ // FIXME unit test leaves cm == null. this should change if (mCm != null) { @@ -377,27 +379,6 @@ final class GsmSMSDispatcher extends SMSDispatcher { } } - /** {@inheritDoc} */ - protected void activateCellBroadcastSms(int activate, Message response) { - // Unless CBS is implemented for GSM, this point should be unreachable. - Log.e(TAG, "Error! The functionality cell broadcast sms is not implemented for GSM."); - response.recycle(); - } - - /** {@inheritDoc} */ - protected void getCellBroadcastSmsConfig(Message response){ - // Unless CBS is implemented for GSM, this point should be unreachable. - Log.e(TAG, "Error! The functionality cell broadcast sms is not implemented for GSM."); - response.recycle(); - } - - /** {@inheritDoc} */ - protected void setCellBroadcastConfig(int[] configValuesArray, Message response) { - // Unless CBS is implemented for GSM, this point should be unreachable. - Log.e(TAG, "Error! The functionality cell broadcast sms is not implemented for GSM."); - response.recycle(); - } - private int resultToCause(int rc) { switch (rc) { case Activity.RESULT_OK: @@ -489,9 +470,10 @@ final class GsmSMSDispatcher extends SMSDispatcher { } // This map holds incomplete concatenated messages waiting for assembly - private HashMap<SmsCbConcatInfo, byte[][]> mSmsCbPageMap = + private final HashMap<SmsCbConcatInfo, byte[][]> mSmsCbPageMap = new HashMap<SmsCbConcatInfo, byte[][]>(); + @Override protected void handleBroadcastSms(AsyncResult ar) { try { byte[][] pdus = null; @@ -503,9 +485,9 @@ final class GsmSMSDispatcher extends SMSDispatcher { for (int j = i; j < i + 8 && j < receivedPdu.length; j++) { int b = receivedPdu[j] & 0xff; if (b < 0x10) { - sb.append("0"); + sb.append('0'); } - sb.append(Integer.toHexString(b)).append(" "); + sb.append(Integer.toHexString(b)).append(' '); } Log.d(TAG, sb.toString()); } @@ -550,7 +532,8 @@ final class GsmSMSDispatcher extends SMSDispatcher { pdus[0] = receivedPdu; } - dispatchBroadcastPdus(pdus); + boolean isEmergencyMessage = SmsCbHeader.isEmergencyMessage(header.messageIdentifier); + dispatchBroadcastPdus(pdus, isEmergencyMessage); // Remove messages that are out of scope to prevent the map from // growing indefinitely, containing incomplete messages that were diff --git a/telephony/java/com/android/internal/telephony/gsm/SimSmsInterfaceManager.java b/telephony/java/com/android/internal/telephony/gsm/SimSmsInterfaceManager.java index e55596b..6e87f21 100644 --- a/telephony/java/com/android/internal/telephony/gsm/SimSmsInterfaceManager.java +++ b/telephony/java/com/android/internal/telephony/gsm/SimSmsInterfaceManager.java @@ -27,6 +27,7 @@ import android.util.Log; import com.android.internal.telephony.IccConstants; import com.android.internal.telephony.IccSmsInterfaceManager; import com.android.internal.telephony.IccUtils; +import com.android.internal.telephony.IntRangeManager; import com.android.internal.telephony.SMSDispatcher; import com.android.internal.telephony.SmsRawData; @@ -53,6 +54,9 @@ public class SimSmsInterfaceManager extends IccSmsInterfaceManager { private HashMap<Integer, HashSet<String>> mCellBroadcastSubscriptions = new HashMap<Integer, HashSet<String>>(); + private CellBroadcastRangeManager mCellBroadcastRangeManager = + new CellBroadcastRangeManager(); + private static final int EVENT_LOAD_DONE = 1; private static final int EVENT_UPDATE_DONE = 2; private static final int EVENT_SET_BROADCAST_ACTIVATION_DONE = 3; @@ -212,7 +216,15 @@ public class SimSmsInterfaceManager extends IccSmsInterfaceManager { } public boolean enableCellBroadcast(int messageIdentifier) { - if (DBG) log("enableCellBroadcast"); + return enableCellBroadcastRange(messageIdentifier, messageIdentifier); + } + + public boolean disableCellBroadcast(int messageIdentifier) { + return disableCellBroadcastRange(messageIdentifier, messageIdentifier); + } + + public boolean enableCellBroadcastRange(int startMessageId, int endMessageId) { + if (DBG) log("enableCellBroadcastRange"); Context context = mPhone.getContext(); @@ -222,30 +234,22 @@ public class SimSmsInterfaceManager extends IccSmsInterfaceManager { String client = context.getPackageManager().getNameForUid( Binder.getCallingUid()); - HashSet<String> clients = mCellBroadcastSubscriptions.get(messageIdentifier); - - if (clients == null) { - // This is a new message identifier - clients = new HashSet<String>(); - mCellBroadcastSubscriptions.put(messageIdentifier, clients); - if (!updateCellBroadcastConfig()) { - mCellBroadcastSubscriptions.remove(messageIdentifier); - return false; - } + if (!mCellBroadcastRangeManager.enableRange(startMessageId, endMessageId, client)) { + log("Failed to add cell broadcast subscription for MID range " + startMessageId + + " to " + endMessageId + " from client " + client); + return false; } - clients.add(client); - if (DBG) - log("Added cell broadcast subscription for MID " + messageIdentifier - + " from client " + client); + log("Added cell broadcast subscription for MID range " + startMessageId + + " to " + endMessageId + " from client " + client); return true; } - public boolean disableCellBroadcast(int messageIdentifier) { - if (DBG) log("disableCellBroadcast"); + public boolean disableCellBroadcastRange(int startMessageId, int endMessageId) { + if (DBG) log("disableCellBroadcastRange"); Context context = mPhone.getContext(); @@ -255,39 +259,56 @@ public class SimSmsInterfaceManager extends IccSmsInterfaceManager { String client = context.getPackageManager().getNameForUid( Binder.getCallingUid()); - HashSet<String> clients = mCellBroadcastSubscriptions.get(messageIdentifier); - if (clients != null && clients.remove(client)) { - if (DBG) - log("Removed cell broadcast subscription for MID " + messageIdentifier - + " from client " + client); - - if (clients.isEmpty()) { - mCellBroadcastSubscriptions.remove(messageIdentifier); - updateCellBroadcastConfig(); - } - return true; + if (!mCellBroadcastRangeManager.disableRange(startMessageId, endMessageId, client)) { + log("Failed to remove cell broadcast subscription for MID range " + startMessageId + + " to " + endMessageId + " from client " + client); + return false; } - return false; + if (DBG) + log("Removed cell broadcast subscription for MID range " + startMessageId + + " to " + endMessageId + " from client " + client); + + return true; } - private boolean updateCellBroadcastConfig() { - Set<Integer> messageIdentifiers = mCellBroadcastSubscriptions.keySet(); + class CellBroadcastRangeManager extends IntRangeManager { + private ArrayList<SmsBroadcastConfigInfo> mConfigList = + new ArrayList<SmsBroadcastConfigInfo>(); + + /** + * Called when the list of enabled ranges has changed. This will be + * followed by zero or more calls to {@link #addRange} followed by + * a call to {@link #finishUpdate}. + */ + protected void startUpdate() { + mConfigList.clear(); + } - if (messageIdentifiers.size() > 0) { - SmsBroadcastConfigInfo[] configs = - new SmsBroadcastConfigInfo[messageIdentifiers.size()]; - int i = 0; + /** + * Called after {@link #startUpdate} to indicate a range of enabled + * values. + * @param startId the first id included in the range + * @param endId the last id included in the range + */ + protected void addRange(int startId, int endId, boolean selected) { + mConfigList.add(new SmsBroadcastConfigInfo(startId, endId, + SMS_CB_CODE_SCHEME_MIN, SMS_CB_CODE_SCHEME_MAX, selected)); + } - for (int messageIdentifier : messageIdentifiers) { - configs[i++] = new SmsBroadcastConfigInfo(messageIdentifier, messageIdentifier, - SMS_CB_CODE_SCHEME_MIN, SMS_CB_CODE_SCHEME_MAX, true); + /** + * Called to indicate the end of a range update started by the + * previous call to {@link #startUpdate}. + */ + protected boolean finishUpdate() { + if (mConfigList.isEmpty()) { + return setCellBroadcastActivation(false); + } else { + SmsBroadcastConfigInfo[] configs = + mConfigList.toArray(new SmsBroadcastConfigInfo[mConfigList.size()]); + return setCellBroadcastConfig(configs) && setCellBroadcastActivation(true); } - - return setCellBroadcastConfig(configs) && setCellBroadcastActivation(true); - } else { - return setCellBroadcastActivation(false); } } @@ -313,7 +334,7 @@ public class SimSmsInterfaceManager extends IccSmsInterfaceManager { private boolean setCellBroadcastActivation(boolean activate) { if (DBG) - log("Calling setCellBroadcastActivation(" + activate + ")"); + log("Calling setCellBroadcastActivation(" + activate + ')'); synchronized (mLock) { Message response = mHandler.obtainMessage(EVENT_SET_BROADCAST_ACTIVATION_DONE); @@ -331,6 +352,7 @@ public class SimSmsInterfaceManager extends IccSmsInterfaceManager { return mSuccess; } + @Override protected void log(String msg) { Log.d(LOG_TAG, "[SimSmsInterfaceManager] " + msg); } diff --git a/telephony/java/com/android/internal/telephony/gsm/SmsBroadcastConfigInfo.java b/telephony/java/com/android/internal/telephony/gsm/SmsBroadcastConfigInfo.java index 45f50bc..66e7ce0 100644 --- a/telephony/java/com/android/internal/telephony/gsm/SmsBroadcastConfigInfo.java +++ b/telephony/java/com/android/internal/telephony/gsm/SmsBroadcastConfigInfo.java @@ -30,11 +30,11 @@ package com.android.internal.telephony.gsm; * and 9.4.4.2.3 for UMTS. * All other values can be treated as empty CBM data coding scheme. * - * selected false means message types specified in <fromServiceId, toServiceId> - * and <fromCodeScheme, toCodeScheme>are not accepted, while true means accepted. + * selected false means message types specified in {@code <fromServiceId, toServiceId>} + * and {@code <fromCodeScheme, toCodeScheme>} are not accepted, while true means accepted. * */ -public class SmsBroadcastConfigInfo { +public final class SmsBroadcastConfigInfo { private int fromServiceId; private int toServiceId; private int fromCodeScheme; @@ -46,11 +46,11 @@ public class SmsBroadcastConfigInfo { */ public SmsBroadcastConfigInfo(int fromId, int toId, int fromScheme, int toScheme, boolean selected) { - setFromServiceId(fromId); - setToServiceId(toId); - setFromCodeScheme(fromScheme); - setToCodeScheme(toScheme); - this.setSelected(selected); + fromServiceId = fromId; + toServiceId = toId; + fromCodeScheme = fromScheme; + toCodeScheme = toScheme; + this.selected = selected; } /** @@ -126,8 +126,8 @@ public class SmsBroadcastConfigInfo { @Override public String toString() { return "SmsBroadcastConfigInfo: Id [" + - getFromServiceId() + "," + getToServiceId() + "] Code [" + - getFromCodeScheme() + "," + getToCodeScheme() + "] " + - (isSelected() ? "ENABLED" : "DISABLED"); + fromServiceId + ',' + toServiceId + "] Code [" + + fromCodeScheme + ',' + toCodeScheme + "] " + + (selected ? "ENABLED" : "DISABLED"); } -}
\ No newline at end of file +} diff --git a/telephony/java/com/android/internal/telephony/gsm/SmsCbHeader.java b/telephony/java/com/android/internal/telephony/gsm/SmsCbHeader.java index 0945a38..8e6b79b 100644 --- a/telephony/java/com/android/internal/telephony/gsm/SmsCbHeader.java +++ b/telephony/java/com/android/internal/telephony/gsm/SmsCbHeader.java @@ -16,7 +16,9 @@ package com.android.internal.telephony.gsm; -public class SmsCbHeader { +import android.telephony.SmsCbConstants; + +public class SmsCbHeader implements SmsCbConstants { /** * Length of SMS-CB header */ @@ -33,6 +35,11 @@ public class SmsCbHeader { public static final int FORMAT_UMTS = 2; /** + * GSM pdu format, as defined in 3gpp TS 23.041, section 9.4.1.3 + */ + public static final int FORMAT_ETWS_PRIMARY = 3; + + /** * Message type value as defined in 3gpp TS 25.324, section 11.1. */ private static final int MESSAGE_TYPE_CBS_MESSAGE = 1; @@ -40,7 +47,12 @@ public class SmsCbHeader { /** * Length of GSM pdus */ - private static final int PDU_LENGTH_GSM = 88; + public static final int PDU_LENGTH_GSM = 88; + + /** + * Maximum length of ETWS primary message GSM pdus + */ + public static final int PDU_LENGTH_ETWS = 56; public final int geographicalScope; @@ -58,12 +70,30 @@ public class SmsCbHeader { public final int format; + public final boolean etwsEmergencyUserAlert; + + public final boolean etwsPopup; + + public final int etwsWarningType; + public SmsCbHeader(byte[] pdu) throws IllegalArgumentException { if (pdu == null || pdu.length < PDU_HEADER_LENGTH) { throw new IllegalArgumentException("Illegal PDU"); } - if (pdu.length <= PDU_LENGTH_GSM) { + if (pdu.length <= PDU_LENGTH_ETWS) { + format = FORMAT_ETWS_PRIMARY; + geographicalScope = -1; //not applicable + messageCode = -1; + updateNumber = -1; + messageIdentifier = ((pdu[2] & 0xff) << 8) | (pdu[3] & 0xff); + dataCodingScheme = -1; + pageIndex = -1; + nrOfPages = -1; + etwsEmergencyUserAlert = (pdu[4] & 0x1) != 0; + etwsPopup = (pdu[5] & 0x80) != 0; + etwsWarningType = (pdu[4] & 0xfe) >> 1; + } else if (pdu.length <= PDU_LENGTH_GSM) { // GSM pdus are no more than 88 bytes format = FORMAT_GSM; geographicalScope = (pdu[0] & 0xc0) >> 6; @@ -83,6 +113,9 @@ public class SmsCbHeader { this.pageIndex = pageIndex; this.nrOfPages = nrOfPages; + etwsEmergencyUserAlert = false; + etwsPopup = false; + etwsWarningType = -1; } else { // UMTS pdus are always at least 90 bytes since the payload includes // a number-of-pages octet and also one length octet per page @@ -105,6 +138,77 @@ public class SmsCbHeader { // actual payload may contain several pages. pageIndex = 1; nrOfPages = 1; + etwsEmergencyUserAlert = false; + etwsPopup = false; + etwsWarningType = -1; } } + + /** + * Return whether the specified message ID is an emergency (PWS) message type. + * This method is static and takes an argument so that it can be used by + * CellBroadcastReceiver, which stores message ID's in SQLite rather than PDU. + * @param id the message identifier to check + * @return true if the message is emergency type; false otherwise + */ + public static boolean isEmergencyMessage(int id) { + return id >= MESSAGE_ID_PWS_FIRST_IDENTIFIER && id <= MESSAGE_ID_PWS_LAST_IDENTIFIER; + } + + /** + * Return whether the specified message ID is an ETWS emergency message type. + * This method is static and takes an argument so that it can be used by + * CellBroadcastReceiver, which stores message ID's in SQLite rather than PDU. + * @param id the message identifier to check + * @return true if the message is ETWS emergency type; false otherwise + */ + public static boolean isEtwsMessage(int id) { + return (id & MESSAGE_ID_ETWS_TYPE_MASK) == MESSAGE_ID_ETWS_TYPE; + } + + /** + * Return whether the specified message ID is a CMAS emergency message type. + * This method is static and takes an argument so that it can be used by + * CellBroadcastReceiver, which stores message ID's in SQLite rather than PDU. + * @param id the message identifier to check + * @return true if the message is CMAS emergency type; false otherwise + */ + public static boolean isCmasMessage(int id) { + return id >= MESSAGE_ID_CMAS_FIRST_IDENTIFIER && id <= MESSAGE_ID_CMAS_LAST_IDENTIFIER; + } + + /** + * Return whether the specified message code indicates an ETWS popup alert. + * This method is static and takes an argument so that it can be used by + * CellBroadcastReceiver, which stores message codes in SQLite rather than PDU. + * This method assumes that the message ID has already been checked for ETWS type. + * + * @param messageCode the message code to check + * @return true if the message code indicates a popup alert should be displayed + */ + public static boolean isEtwsPopupAlert(int messageCode) { + return (messageCode & MESSAGE_CODE_ETWS_ACTIVATE_POPUP) != 0; + } + + /** + * Return whether the specified message code indicates an ETWS emergency user alert. + * This method is static and takes an argument so that it can be used by + * CellBroadcastReceiver, which stores message codes in SQLite rather than PDU. + * This method assumes that the message ID has already been checked for ETWS type. + * + * @param messageCode the message code to check + * @return true if the message code indicates an emergency user alert + */ + public static boolean isEtwsEmergencyUserAlert(int messageCode) { + return (messageCode & MESSAGE_CODE_ETWS_EMERGENCY_USER_ALERT) != 0; + } + + @Override + public String toString() { + return "SmsCbHeader{GS=" + geographicalScope + ", messageCode=0x" + + Integer.toHexString(messageCode) + ", updateNumber=" + updateNumber + + ", messageIdentifier=0x" + Integer.toHexString(messageIdentifier) + + ", DCS=0x" + Integer.toHexString(dataCodingScheme) + + ", page " + pageIndex + " of " + nrOfPages + '}'; + } } diff --git a/telephony/tests/telephonytests/src/com/android/internal/telephony/GsmSmsCbTest.java b/telephony/tests/telephonytests/src/com/android/internal/telephony/GsmSmsCbTest.java index b131a01..417aac4 100644 --- a/telephony/tests/telephonytests/src/com/android/internal/telephony/GsmSmsCbTest.java +++ b/telephony/tests/telephonytests/src/com/android/internal/telephony/GsmSmsCbTest.java @@ -18,6 +18,7 @@ package com.android.internal.telephony; import android.telephony.SmsCbMessage; import android.test.AndroidTestCase; +import android.util.Log; /** * Test cases for basic SmsCbMessage operations @@ -663,4 +664,49 @@ public class GsmSmsCbTest extends AndroidTestCase { assertEquals("Unexpected update number decoded", 5, msg.getUpdateNumber()); } + + /* ETWS Test message including header */ + private static final byte[] etwsMessageNormal = IccUtils.hexStringToBytes("000011001101" + + "0D0A5BAE57CE770C531790E85C716CBF3044573065B930675730" + + "9707767A751F30025F37304463FA308C306B5099304830664E0B30553044FF086C178C615E81FF09" + + "0000000000000000000000000000"); + + private static final byte[] etwsMessageCancel = IccUtils.hexStringToBytes("000011001101" + + "0D0A5148307B3069002800310030003A0035" + + "00320029306E7DCA602557309707901F5831309253D66D883057307E3059FF086C178C615E81FF09" + + "00000000000000000000000000000000000000000000"); + + private static final byte[] etwsMessageTest = IccUtils.hexStringToBytes("000011031101" + + "0D0A5BAE57CE770C531790E85C716CBF3044" + + "573065B9306757309707300263FA308C306B5099304830664E0B30553044FF086C178C615E81FF09" + + "00000000000000000000000000000000000000000000"); + + // FIXME: add example of ETWS primary notification PDU + + public void testEtwsMessageNormal() { + SmsCbMessage msg = SmsCbMessage.createFromPdu(etwsMessageNormal); + Log.d("GsmSmsCbTest", msg.toString()); + assertEquals("GS mismatch", 0, msg.getGeographicalScope()); + assertEquals("message code mismatch", 0, msg.getMessageCode()); + assertEquals("update number mismatch", 0, msg.getUpdateNumber()); + assertEquals("message ID mismatch", 0x1100, msg.getMessageIdentifier()); + } + + public void testEtwsMessageCancel() { + SmsCbMessage msg = SmsCbMessage.createFromPdu(etwsMessageCancel); + Log.d("GsmSmsCbTest", msg.toString()); + assertEquals("GS mismatch", 0, msg.getGeographicalScope()); + assertEquals("message code mismatch", 0, msg.getMessageCode()); + assertEquals("update number mismatch", 0, msg.getUpdateNumber()); + assertEquals("message ID mismatch", 0x1100, msg.getMessageIdentifier()); + } + + public void testEtwsMessageTest() { + SmsCbMessage msg = SmsCbMessage.createFromPdu(etwsMessageTest); + Log.d("GsmSmsCbTest", msg.toString()); + assertEquals("GS mismatch", 0, msg.getGeographicalScope()); + assertEquals("message code mismatch", 0, msg.getMessageCode()); + assertEquals("update number mismatch", 0, msg.getUpdateNumber()); + assertEquals("message ID mismatch", 0x1103, msg.getMessageIdentifier()); + } } diff --git a/telephony/tests/telephonytests/src/com/android/internal/telephony/IntRangeManagerTest.java b/telephony/tests/telephonytests/src/com/android/internal/telephony/IntRangeManagerTest.java new file mode 100644 index 0000000..79dca39 --- /dev/null +++ b/telephony/tests/telephonytests/src/com/android/internal/telephony/IntRangeManagerTest.java @@ -0,0 +1,374 @@ +/* + * Copyright (C) 2011 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.telephony; + +import android.test.AndroidTestCase; + +import com.android.internal.telephony.gsm.SmsBroadcastConfigInfo; + +import java.util.ArrayList; + +/** + * Test cases for the IntRangeManager class. + */ +public class IntRangeManagerTest extends AndroidTestCase { + + private static final int SMS_CB_CODE_SCHEME_MIN = 0; + private static final int SMS_CB_CODE_SCHEME_MAX = 255; + + private static final int FLAG_START_UPDATE_CALLED = 0x01; + private static final int FLAG_ADD_RANGE_CALLED = 0x02; + private static final int FLAG_FINISH_UPDATE_CALLED = 0x04; + + private static final int ALL_FLAGS_SET = FLAG_START_UPDATE_CALLED | FLAG_ADD_RANGE_CALLED | + FLAG_FINISH_UPDATE_CALLED; + + /** Dummy IntRangeManager for testing. */ + class TestIntRangeManager extends IntRangeManager { + ArrayList<SmsBroadcastConfigInfo> mConfigList = + new ArrayList<SmsBroadcastConfigInfo>(); + + int flags; + boolean finishUpdateReturnValue = true; + + /** + * Called when the list of enabled ranges has changed. This will be + * followed by zero or more calls to {@link #addRange} followed by + * a call to {@link #finishUpdate}. + */ + protected void startUpdate() { + mConfigList.clear(); + flags |= FLAG_START_UPDATE_CALLED; + } + + /** + * Called after {@link #startUpdate} to indicate a range of enabled + * values. + * @param startId the first id included in the range + * @param endId the last id included in the range + */ + protected void addRange(int startId, int endId, boolean selected) { + mConfigList.add(new SmsBroadcastConfigInfo(startId, endId, + SMS_CB_CODE_SCHEME_MIN, SMS_CB_CODE_SCHEME_MAX, selected)); + flags |= FLAG_ADD_RANGE_CALLED; + } + + /** + * Called to indicate the end of a range update started by the + * previous call to {@link #startUpdate}. + */ + protected boolean finishUpdate() { + flags |= FLAG_FINISH_UPDATE_CALLED; + return finishUpdateReturnValue; + } + + /** Reset the object for the next test case. */ + void reset() { + flags = 0; + mConfigList.clear(); + } + } + + public void testEmptyRangeManager() { + TestIntRangeManager testManager = new TestIntRangeManager(); + assertEquals("expecting empty configlist", 0, testManager.mConfigList.size()); + } + + private void checkConfigInfo(SmsBroadcastConfigInfo info, int fromServiceId, + int toServiceId, int fromCodeScheme, int toCodeScheme, boolean selected) { + assertEquals("fromServiceId", fromServiceId, info.getFromServiceId()); + assertEquals("toServiceId", toServiceId, info.getToServiceId()); + assertEquals("fromCodeScheme", fromCodeScheme, info.getFromCodeScheme()); + assertEquals("toCodeScheme", toCodeScheme, info.getToCodeScheme()); + assertEquals("selected", selected, info.isSelected()); + } + + public void testAddSingleChannel() { + TestIntRangeManager testManager = new TestIntRangeManager(); + assertEquals("flags before test", 0, testManager.flags); + assertTrue("enabling range", testManager.enableRange(123, 123, "client1")); + assertEquals("flags after test", ALL_FLAGS_SET, testManager.flags); + assertEquals("configlist size", 1, testManager.mConfigList.size()); + checkConfigInfo(testManager.mConfigList.get(0), 123, 123, SMS_CB_CODE_SCHEME_MIN, + SMS_CB_CODE_SCHEME_MAX, true); + testManager.reset(); + assertTrue("updating ranges", testManager.updateRanges()); + assertEquals("flags after test", ALL_FLAGS_SET, testManager.flags); + assertEquals("configlist size", 1, testManager.mConfigList.size()); + checkConfigInfo(testManager.mConfigList.get(0), 123, 123, SMS_CB_CODE_SCHEME_MIN, + SMS_CB_CODE_SCHEME_MAX, true); + } + + public void testRemoveSingleChannel() { + TestIntRangeManager testManager = new TestIntRangeManager(); + assertTrue("enabling range", testManager.enableRange(123, 123, "client1")); + assertEquals("flags after enable", ALL_FLAGS_SET, testManager.flags); + assertEquals("configlist size", 1, testManager.mConfigList.size()); + testManager.reset(); + assertTrue("disabling range", testManager.disableRange(123, 123, "client1")); + assertEquals("flags after test", ALL_FLAGS_SET, testManager.flags); + assertEquals("configlist size", 1, testManager.mConfigList.size()); + checkConfigInfo(testManager.mConfigList.get(0), 123, 123, SMS_CB_CODE_SCHEME_MIN, + SMS_CB_CODE_SCHEME_MAX, false); + testManager.reset(); + assertTrue("updating ranges", testManager.updateRanges()); + assertEquals("flags after test", FLAG_START_UPDATE_CALLED | FLAG_FINISH_UPDATE_CALLED, + testManager.flags); + assertEquals("configlist size", 0, testManager.mConfigList.size()); + } + + public void testRemoveBadChannel() { + TestIntRangeManager testManager = new TestIntRangeManager(); + assertFalse("disabling missing range", testManager.disableRange(123, 123, "client1")); + assertEquals("flags after test", 0, testManager.flags); + assertEquals("configlist size", 0, testManager.mConfigList.size()); + } + + public void testAddTwoChannels() { + TestIntRangeManager testManager = new TestIntRangeManager(); + assertEquals("flags before test", 0, testManager.flags); + assertTrue("enabling range 1", testManager.enableRange(100, 120, "client1")); + assertEquals("flags after test", ALL_FLAGS_SET, testManager.flags); + assertEquals("configlist size", 1, testManager.mConfigList.size()); + checkConfigInfo(testManager.mConfigList.get(0), 100, 120, SMS_CB_CODE_SCHEME_MIN, + SMS_CB_CODE_SCHEME_MAX, true); + testManager.reset(); + assertTrue("enabling range 2", testManager.enableRange(200, 250, "client2")); + assertEquals("flags after test", ALL_FLAGS_SET, testManager.flags); + assertEquals("configlist size", 1, testManager.mConfigList.size()); + checkConfigInfo(testManager.mConfigList.get(0), 200, 250, SMS_CB_CODE_SCHEME_MIN, + SMS_CB_CODE_SCHEME_MAX, true); + testManager.reset(); + assertTrue("updating ranges", testManager.updateRanges()); + assertEquals("flags after test", ALL_FLAGS_SET, testManager.flags); + assertEquals("configlist size", 2, testManager.mConfigList.size()); + checkConfigInfo(testManager.mConfigList.get(0), 100, 120, SMS_CB_CODE_SCHEME_MIN, + SMS_CB_CODE_SCHEME_MAX, true); + checkConfigInfo(testManager.mConfigList.get(1), 200, 250, SMS_CB_CODE_SCHEME_MIN, + SMS_CB_CODE_SCHEME_MAX, true); + } + + public void testOverlappingChannels() { + TestIntRangeManager testManager = new TestIntRangeManager(); + assertEquals("flags before test", 0, testManager.flags); + assertTrue("enabling range 1", testManager.enableRange(100, 200, "client1")); + assertEquals("flags after test", ALL_FLAGS_SET, testManager.flags); + assertEquals("configlist size", 1, testManager.mConfigList.size()); + checkConfigInfo(testManager.mConfigList.get(0), 100, 200, SMS_CB_CODE_SCHEME_MIN, + SMS_CB_CODE_SCHEME_MAX, true); + testManager.reset(); + assertTrue("enabling range 2", testManager.enableRange(150, 250, "client2")); + assertEquals("flags after test", ALL_FLAGS_SET, testManager.flags); + assertEquals("configlist size", 1, testManager.mConfigList.size()); + checkConfigInfo(testManager.mConfigList.get(0), 201, 250, SMS_CB_CODE_SCHEME_MIN, + SMS_CB_CODE_SCHEME_MAX, true); + testManager.reset(); + assertTrue("updating ranges", testManager.updateRanges()); + assertEquals("flags after test", ALL_FLAGS_SET, testManager.flags); + assertEquals("configlist size", 1, testManager.mConfigList.size()); + checkConfigInfo(testManager.mConfigList.get(0), 100, 250, SMS_CB_CODE_SCHEME_MIN, + SMS_CB_CODE_SCHEME_MAX, true); + testManager.reset(); + assertTrue("disabling range 1", testManager.disableRange(100, 200, "client1")); + assertEquals("flags after test", ALL_FLAGS_SET, testManager.flags); + assertEquals("configlist size", 1, testManager.mConfigList.size()); + checkConfigInfo(testManager.mConfigList.get(0), 100, 149, SMS_CB_CODE_SCHEME_MIN, + SMS_CB_CODE_SCHEME_MAX, false); + testManager.reset(); + assertTrue("disabling range 2", testManager.disableRange(150, 250, "client2")); + assertEquals("flags after test", ALL_FLAGS_SET, testManager.flags); + assertEquals("configlist size", 1, testManager.mConfigList.size()); + checkConfigInfo(testManager.mConfigList.get(0), 150, 250, SMS_CB_CODE_SCHEME_MIN, + SMS_CB_CODE_SCHEME_MAX, false); + testManager.reset(); + assertTrue("updating ranges", testManager.updateRanges()); + assertEquals("flags after test", FLAG_START_UPDATE_CALLED | FLAG_FINISH_UPDATE_CALLED, + testManager.flags); + assertEquals("configlist size", 0, testManager.mConfigList.size()); + } + + public void testOverlappingChannels2() { + TestIntRangeManager testManager = new TestIntRangeManager(); + assertEquals("flags before test", 0, testManager.flags); + assertTrue("enabling range 1", testManager.enableRange(100, 200, "client1")); + assertEquals("flags after test", ALL_FLAGS_SET, testManager.flags); + assertEquals("configlist size", 1, testManager.mConfigList.size()); + checkConfigInfo(testManager.mConfigList.get(0), 100, 200, SMS_CB_CODE_SCHEME_MIN, + SMS_CB_CODE_SCHEME_MAX, true); + testManager.reset(); + assertTrue("enabling range 2", testManager.enableRange(150, 250, "client2")); + assertEquals("flags after test", ALL_FLAGS_SET, testManager.flags); + assertEquals("configlist size", 1, testManager.mConfigList.size()); + checkConfigInfo(testManager.mConfigList.get(0), 201, 250, SMS_CB_CODE_SCHEME_MIN, + SMS_CB_CODE_SCHEME_MAX, true); + testManager.reset(); + assertTrue("disabling range 2", testManager.disableRange(150, 250, "client2")); + assertEquals("flags after test", ALL_FLAGS_SET, testManager.flags); + assertEquals("configlist size", 1, testManager.mConfigList.size()); + checkConfigInfo(testManager.mConfigList.get(0), 201, 250, SMS_CB_CODE_SCHEME_MIN, + SMS_CB_CODE_SCHEME_MAX, false); + testManager.reset(); + assertTrue("updating ranges", testManager.updateRanges()); + assertEquals("flags after test", ALL_FLAGS_SET, testManager.flags); + assertEquals("configlist size", 1, testManager.mConfigList.size()); + checkConfigInfo(testManager.mConfigList.get(0), 100, 200, SMS_CB_CODE_SCHEME_MIN, + SMS_CB_CODE_SCHEME_MAX, true); + testManager.reset(); + assertTrue("disabling range 1", testManager.disableRange(100, 200, "client1")); + assertEquals("flags after test", ALL_FLAGS_SET, testManager.flags); + assertEquals("configlist size", 1, testManager.mConfigList.size()); + checkConfigInfo(testManager.mConfigList.get(0), 100, 200, SMS_CB_CODE_SCHEME_MIN, + SMS_CB_CODE_SCHEME_MAX, false); + } + + public void testMultipleOverlappingChannels() { + TestIntRangeManager testManager = new TestIntRangeManager(); + assertEquals("flags before test", 0, testManager.flags); + assertTrue("enabling range 1", testManager.enableRange(67, 9999, "client1")); + assertEquals("flags after test", ALL_FLAGS_SET, testManager.flags); + assertEquals("configlist size", 1, testManager.mConfigList.size()); + checkConfigInfo(testManager.mConfigList.get(0), 67, 9999, SMS_CB_CODE_SCHEME_MIN, + SMS_CB_CODE_SCHEME_MAX, true); + testManager.reset(); + assertTrue("enabling range 2", testManager.enableRange(150, 250, "client2")); + assertEquals("flags after test", 0, testManager.flags); + assertEquals("configlist size", 0, testManager.mConfigList.size()); + testManager.reset(); + assertTrue("enabling range 3", testManager.enableRange(25, 75, "client3")); + assertEquals("flags after test", ALL_FLAGS_SET, testManager.flags); + assertEquals("configlist size", 1, testManager.mConfigList.size()); + checkConfigInfo(testManager.mConfigList.get(0), 25, 66, SMS_CB_CODE_SCHEME_MIN, + SMS_CB_CODE_SCHEME_MAX, true); + testManager.reset(); + assertTrue("enabling range 4", testManager.enableRange(12, 500, "client4")); + assertEquals("flags after test", ALL_FLAGS_SET, testManager.flags); + assertEquals("configlist size", 1, testManager.mConfigList.size()); + checkConfigInfo(testManager.mConfigList.get(0), 12, 24, SMS_CB_CODE_SCHEME_MIN, + SMS_CB_CODE_SCHEME_MAX, true); + testManager.reset(); + assertTrue("enabling range 5", testManager.enableRange(8000, 9998, "client5")); + assertEquals("flags after test", 0, testManager.flags); + assertEquals("configlist size", 0, testManager.mConfigList.size()); + testManager.reset(); + assertTrue("enabling range 6", testManager.enableRange(50000, 65535, "client6")); + assertEquals("flags after test", ALL_FLAGS_SET, testManager.flags); + assertEquals("configlist size", 1, testManager.mConfigList.size()); + checkConfigInfo(testManager.mConfigList.get(0), 50000, 65535, SMS_CB_CODE_SCHEME_MIN, + SMS_CB_CODE_SCHEME_MAX, true); + testManager.reset(); + assertTrue("updating ranges", testManager.updateRanges()); + assertEquals("flags after test", ALL_FLAGS_SET, testManager.flags); + assertEquals("configlist size", 2, testManager.mConfigList.size()); + checkConfigInfo(testManager.mConfigList.get(0), 12, 9999, SMS_CB_CODE_SCHEME_MIN, + SMS_CB_CODE_SCHEME_MAX, true); + checkConfigInfo(testManager.mConfigList.get(1), 50000, 65535, SMS_CB_CODE_SCHEME_MIN, + SMS_CB_CODE_SCHEME_MAX, true); + testManager.reset(); + assertTrue("disabling range 1", testManager.disableRange(67, 9999, "client1")); + assertEquals("flags after test", ALL_FLAGS_SET, testManager.flags); + assertEquals("configlist size", 2, testManager.mConfigList.size()); + checkConfigInfo(testManager.mConfigList.get(0), 501, 7999, SMS_CB_CODE_SCHEME_MIN, + SMS_CB_CODE_SCHEME_MAX, false); + checkConfigInfo(testManager.mConfigList.get(1), 9999, 9999, SMS_CB_CODE_SCHEME_MIN, + SMS_CB_CODE_SCHEME_MAX, false); + testManager.reset(); + assertTrue("updating ranges", testManager.updateRanges()); + assertEquals("flags after test", ALL_FLAGS_SET, testManager.flags); + assertEquals("configlist size", 3, testManager.mConfigList.size()); + checkConfigInfo(testManager.mConfigList.get(0), 12, 500, SMS_CB_CODE_SCHEME_MIN, + SMS_CB_CODE_SCHEME_MAX, true); + checkConfigInfo(testManager.mConfigList.get(1), 8000, 9998, SMS_CB_CODE_SCHEME_MIN, + SMS_CB_CODE_SCHEME_MAX, true); + checkConfigInfo(testManager.mConfigList.get(2), 50000, 65535, SMS_CB_CODE_SCHEME_MIN, + SMS_CB_CODE_SCHEME_MAX, true); + testManager.reset(); + assertTrue("disabling range 4", testManager.disableRange(12, 500, "client4")); + assertEquals("flags after test", ALL_FLAGS_SET, testManager.flags); + assertEquals("configlist size", 3, testManager.mConfigList.size()); + checkConfigInfo(testManager.mConfigList.get(0), 12, 24, SMS_CB_CODE_SCHEME_MIN, + SMS_CB_CODE_SCHEME_MAX, false); + checkConfigInfo(testManager.mConfigList.get(1), 76, 149, SMS_CB_CODE_SCHEME_MIN, + SMS_CB_CODE_SCHEME_MAX, false); + checkConfigInfo(testManager.mConfigList.get(2), 251, 500, SMS_CB_CODE_SCHEME_MIN, + SMS_CB_CODE_SCHEME_MAX, false); + testManager.reset(); + assertTrue("updating ranges", testManager.updateRanges()); + assertEquals("flags after test", ALL_FLAGS_SET, testManager.flags); + assertEquals("configlist size", 4, testManager.mConfigList.size()); + checkConfigInfo(testManager.mConfigList.get(0), 25, 75, SMS_CB_CODE_SCHEME_MIN, + SMS_CB_CODE_SCHEME_MAX, true); + checkConfigInfo(testManager.mConfigList.get(1), 150, 250, SMS_CB_CODE_SCHEME_MIN, + SMS_CB_CODE_SCHEME_MAX, true); + checkConfigInfo(testManager.mConfigList.get(2), 8000, 9998, SMS_CB_CODE_SCHEME_MIN, + SMS_CB_CODE_SCHEME_MAX, true); + checkConfigInfo(testManager.mConfigList.get(3), 50000, 65535, SMS_CB_CODE_SCHEME_MIN, + SMS_CB_CODE_SCHEME_MAX, true); + testManager.reset(); + assertTrue("disabling range 5", testManager.disableRange(8000, 9998, "client5")); + assertEquals("flags after test", ALL_FLAGS_SET, testManager.flags); + assertEquals("configlist size", 1, testManager.mConfigList.size()); + checkConfigInfo(testManager.mConfigList.get(0), 8000, 9998, SMS_CB_CODE_SCHEME_MIN, + SMS_CB_CODE_SCHEME_MAX, false); + testManager.reset(); + assertTrue("updating ranges", testManager.updateRanges()); + assertEquals("flags after test", ALL_FLAGS_SET, testManager.flags); + assertEquals("configlist size", 3, testManager.mConfigList.size()); + checkConfigInfo(testManager.mConfigList.get(0), 25, 75, SMS_CB_CODE_SCHEME_MIN, + SMS_CB_CODE_SCHEME_MAX, true); + checkConfigInfo(testManager.mConfigList.get(1), 150, 250, SMS_CB_CODE_SCHEME_MIN, + SMS_CB_CODE_SCHEME_MAX, true); + checkConfigInfo(testManager.mConfigList.get(2), 50000, 65535, SMS_CB_CODE_SCHEME_MIN, + SMS_CB_CODE_SCHEME_MAX, true); + testManager.reset(); + assertTrue("disabling range 6", testManager.disableRange(50000, 65535, "client6")); + assertEquals("flags after test", ALL_FLAGS_SET, testManager.flags); + assertEquals("configlist size", 1, testManager.mConfigList.size()); + checkConfigInfo(testManager.mConfigList.get(0), 50000, 65535, SMS_CB_CODE_SCHEME_MIN, + SMS_CB_CODE_SCHEME_MAX, false); + testManager.reset(); + assertTrue("updating ranges", testManager.updateRanges()); + assertEquals("flags after test", ALL_FLAGS_SET, testManager.flags); + assertEquals("configlist size", 2, testManager.mConfigList.size()); + checkConfigInfo(testManager.mConfigList.get(0), 25, 75, SMS_CB_CODE_SCHEME_MIN, + SMS_CB_CODE_SCHEME_MAX, true); + checkConfigInfo(testManager.mConfigList.get(1), 150, 250, SMS_CB_CODE_SCHEME_MIN, + SMS_CB_CODE_SCHEME_MAX, true); + testManager.reset(); + assertTrue("disabling range 2", testManager.disableRange(150, 250, "client2")); + assertEquals("flags after test", ALL_FLAGS_SET, testManager.flags); + assertEquals("configlist size", 1, testManager.mConfigList.size()); + checkConfigInfo(testManager.mConfigList.get(0), 150, 250, SMS_CB_CODE_SCHEME_MIN, + SMS_CB_CODE_SCHEME_MAX, false); + testManager.reset(); + assertTrue("updating ranges", testManager.updateRanges()); + assertEquals("flags after test", ALL_FLAGS_SET, testManager.flags); + assertEquals("configlist size", 1, testManager.mConfigList.size()); + checkConfigInfo(testManager.mConfigList.get(0), 25, 75, SMS_CB_CODE_SCHEME_MIN, + SMS_CB_CODE_SCHEME_MAX, true); + testManager.reset(); + assertTrue("disabling range 3", testManager.disableRange(25, 75, "client3")); + assertEquals("flags after test", ALL_FLAGS_SET, testManager.flags); + assertEquals("configlist size", 1, testManager.mConfigList.size()); + checkConfigInfo(testManager.mConfigList.get(0), 25, 75, SMS_CB_CODE_SCHEME_MIN, + SMS_CB_CODE_SCHEME_MAX, false); + testManager.reset(); + assertTrue("updating ranges", testManager.updateRanges()); + assertEquals("flags after test", FLAG_START_UPDATE_CALLED | FLAG_FINISH_UPDATE_CALLED, + testManager.flags); + assertEquals("configlist size", 0, testManager.mConfigList.size()); + } +} diff --git a/tools/layoutlib/bridge/src/android/graphics/Canvas_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Canvas_Delegate.java index 49c1e4b..5623526 100644 --- a/tools/layoutlib/bridge/src/android/graphics/Canvas_Delegate.java +++ b/tools/layoutlib/bridge/src/android/graphics/Canvas_Delegate.java @@ -443,7 +443,7 @@ public final class Canvas_Delegate { AffineTransform matrixTx = matrixDelegate.getAffineTransform(); // combine them so that the given matrix is applied after. - currentTx.preConcatenate(matrixTx); + currentTx.concatenate(matrixTx); // give it to the graphics2D as a new matrix replacing all previous transform snapshot.setTransform(currentTx); @@ -735,7 +735,7 @@ public final class Canvas_Delegate { /*package*/ static void native_drawCircle(int nativeCanvas, float cx, float cy, float radius, int paint) { native_drawOval(nativeCanvas, - new RectF(cx - radius, cy - radius, radius*2, radius*2), + new RectF(cx - radius, cy - radius, radius, radius), paint); } diff --git a/tools/layoutlib/bridge/src/android/os/HandlerThread_Delegate.java b/tools/layoutlib/bridge/src/android/os/HandlerThread_Delegate.java new file mode 100644 index 0000000..afbe97c --- /dev/null +++ b/tools/layoutlib/bridge/src/android/os/HandlerThread_Delegate.java @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2011 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 com.android.layoutlib.bridge.android.BridgeContext; +import com.android.layoutlib.bridge.impl.RenderAction; +import com.android.tools.layoutlib.annotations.LayoutlibDelegate; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Delegate overriding selected methods of android.os.HandlerThread + * + * Through the layoutlib_create tool, selected methods of Handler have been replaced + * by calls to methods of the same name in this delegate class. + * + * + */ +public class HandlerThread_Delegate { + + private static Map<BridgeContext, List<HandlerThread>> sThreads = + new HashMap<BridgeContext, List<HandlerThread>>(); + + public static void cleanUp(BridgeContext context) { + List<HandlerThread> list = sThreads.get(context); + if (list != null) { + for (HandlerThread thread : list) { + thread.quit(); + } + + list.clear(); + sThreads.remove(context); + } + } + + // -------- Delegate methods + + @LayoutlibDelegate + /*package*/ static void run(HandlerThread theThread) { + // record the thread so that it can be quit() on clean up. + BridgeContext context = RenderAction.getCurrentContext(); + List<HandlerThread> list = sThreads.get(context); + if (list == null) { + list = new ArrayList<HandlerThread>(); + sThreads.put(context, list); + } + + list.add(theThread); + + // ---- START DEFAULT IMPLEMENTATION. + + theThread.mTid = Process.myTid(); + Looper.prepare(); + synchronized (theThread) { + theThread.mLooper = Looper.myLooper(); + theThread.notifyAll(); + } + Process.setThreadPriority(theThread.mPriority); + theThread.onLooperPrepared(); + Looper.loop(); + theThread.mTid = -1; + } +} diff --git a/tools/layoutlib/bridge/src/android/view/LayoutInflater_Delegate.java b/tools/layoutlib/bridge/src/android/view/LayoutInflater_Delegate.java index d5266a5..ea7242c 100644 --- a/tools/layoutlib/bridge/src/android/view/LayoutInflater_Delegate.java +++ b/tools/layoutlib/bridge/src/android/view/LayoutInflater_Delegate.java @@ -43,6 +43,8 @@ import java.io.IOException; */ public class LayoutInflater_Delegate { + public static boolean sIsInInclude = false; + @LayoutlibDelegate /*package*/ static void parseInclude(LayoutInflater thisInflater, XmlPullParser parser, View parent, AttributeSet attrs) @@ -109,10 +111,22 @@ public class LayoutInflater_Delegate { // false means we need to rely on the included layout params. ViewGroup.LayoutParams params = null; try { + // ---- START CHANGES + sIsInInclude = true; + // ---- END CHANGES + params = group.generateLayoutParams(attrs); } catch (RuntimeException e) { + // ---- START CHANGES + sIsInInclude = false; + // ---- END CHANGES + params = group.generateLayoutParams(childAttrs); } finally { + // ---- START CHANGES + sIsInInclude = false; + // ---- END CHANGES + if (params != null) { view.setLayoutParams(params); } diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/Bridge.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/Bridge.java index b1a7824..c74eb33 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/Bridge.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/Bridge.java @@ -25,6 +25,7 @@ import com.android.ide.common.rendering.api.LayoutLog; import com.android.ide.common.rendering.api.RenderSession; import com.android.ide.common.rendering.api.Result; import com.android.ide.common.rendering.api.SessionParams; +import com.android.ide.common.rendering.api.Result.Status; import com.android.layoutlib.bridge.android.BridgeAssetManager; import com.android.layoutlib.bridge.impl.FontLoader; import com.android.layoutlib.bridge.impl.RenderDrawable; @@ -39,6 +40,9 @@ import android.graphics.Bitmap; import android.graphics.Typeface; import android.graphics.Typeface_Delegate; import android.os.Looper; +import android.view.View; +import android.view.ViewGroup; +import android.view.ViewParent; import java.io.File; import java.lang.ref.SoftReference; @@ -192,10 +196,11 @@ public final class Bridge extends com.android.ide.common.rendering.api.Bridge { Capability.UNBOUND_RENDERING, Capability.CUSTOM_BACKGROUND_COLOR, Capability.RENDER, - //Capability.LAYOUT_ONLY, // disable to run on ADT 10.0 which doesn't include this. + Capability.LAYOUT_ONLY, Capability.EMBEDDED_LAYOUT, - Capability.VIEW_MANIPULATION); - + Capability.VIEW_MANIPULATION, + Capability.ADAPTER_BINDING, + Capability.EXTENDED_VIEWINFO); BridgeAssetManager.initSystem(); @@ -367,6 +372,31 @@ public final class Bridge extends com.android.ide.common.rendering.api.Bridge { } } + @Override + public Result getViewParent(Object viewObject) { + if (viewObject instanceof View) { + return Status.SUCCESS.createResult(((View)viewObject).getParent()); + } + + throw new IllegalArgumentException("viewObject is not a View"); + } + + @Override + public Result getViewIndex(Object viewObject) { + if (viewObject instanceof View) { + View view = (View) viewObject; + ViewParent parentView = view.getParent(); + + if (parentView instanceof ViewGroup) { + Status.SUCCESS.createResult(((ViewGroup) parentView).indexOfChild(view)); + } + + return Status.SUCCESS.createResult(); + } + + throw new IllegalArgumentException("viewObject is not a View"); + } + /** * Returns the lock for the bridge */ diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeRenderSession.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeRenderSession.java index bf22c4d..e38b910 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeRenderSession.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeRenderSession.java @@ -22,12 +22,10 @@ import com.android.ide.common.rendering.api.RenderParams; import com.android.ide.common.rendering.api.RenderSession; import com.android.ide.common.rendering.api.Result; import com.android.ide.common.rendering.api.ViewInfo; -import com.android.ide.common.rendering.api.Result.Status; import com.android.layoutlib.bridge.impl.RenderSessionImpl; import android.view.View; import android.view.ViewGroup; -import android.view.ViewParent; import java.awt.image.BufferedImage; import java.util.List; @@ -83,31 +81,6 @@ public class BridgeRenderSession extends RenderSession { } @Override - public Result getViewParent(Object viewObject) { - if (viewObject instanceof View) { - return Status.SUCCESS.createResult(((View)viewObject).getParent()); - } - - throw new IllegalArgumentException("viewObject is not a View"); - } - - @Override - public Result getViewIndex(Object viewObject) { - if (viewObject instanceof View) { - View view = (View) viewObject; - ViewParent parentView = view.getParent(); - - if (parentView instanceof ViewGroup) { - Status.SUCCESS.createResult(((ViewGroup) parentView).indexOfChild(view)); - } - - return Status.SUCCESS.createResult(); - } - - throw new IllegalArgumentException("viewObject is not a View"); - } - - @Override public Result render(long timeout) { try { Bridge.prepareThread(); diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/MockView.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/MockView.java index 1ca3182..3d50b2a 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/MockView.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/MockView.java @@ -19,21 +19,23 @@ package com.android.layoutlib.bridge; import android.content.Context; import android.graphics.Canvas; import android.util.AttributeSet; +import android.view.Gravity; import android.widget.TextView; /** * Base class for mocked views. - * + * * TODO: implement onDraw and draw a rectangle in a random color with the name of the class * (or better the id of the view). */ public class MockView extends TextView { - + public MockView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); - + setText(this.getClass().getSimpleName()); setTextColor(0xFF000000); + setGravity(Gravity.CENTER); } @Override diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java index c4eee17..ea3d5d2 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java @@ -16,9 +16,11 @@ package com.android.layoutlib.bridge.android; +import com.android.ide.common.rendering.api.ILayoutPullParser; import com.android.ide.common.rendering.api.IProjectCallback; import com.android.ide.common.rendering.api.LayoutLog; import com.android.ide.common.rendering.api.RenderResources; +import com.android.ide.common.rendering.api.ResourceReference; import com.android.ide.common.rendering.api.ResourceValue; import com.android.ide.common.rendering.api.StyleResourceValue; import com.android.layoutlib.bridge.Bridge; @@ -27,6 +29,10 @@ import com.android.layoutlib.bridge.impl.Stack; import com.android.resources.ResourceType; import com.android.util.Pair; +import org.kxml2.io.KXmlParser; +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + import android.app.Activity; import android.content.BroadcastReceiver; import android.content.ComponentName; @@ -57,6 +63,7 @@ import android.util.DisplayMetrics; import android.util.TypedValue; import android.view.LayoutInflater; import android.view.View; +import android.view.ViewGroup; import java.io.File; import java.io.FileInputStream; @@ -265,6 +272,107 @@ public final class BridgeContext extends Activity { } + public ResourceReference resolveId(int id) { + // first get the String related to this id in the framework + Pair<ResourceType, String> resourceInfo = Bridge.resolveResourceId(id); + + if (resourceInfo != null) { + return new ResourceReference(resourceInfo.getSecond(), true); + } + + // didn't find a match in the framework? look in the project. + if (mProjectCallback != null) { + resourceInfo = mProjectCallback.resolveResourceId(id); + + if (resourceInfo != null) { + return new ResourceReference(resourceInfo.getSecond(), false); + } + } + + return null; + } + + public Pair<View, Boolean> inflateView(ResourceReference resource, ViewGroup parent, + boolean attachToRoot, boolean skipCallbackParser) { + String layoutName = resource.getName(); + boolean isPlatformLayout = resource.isFramework(); + + if (isPlatformLayout == false && skipCallbackParser == false) { + // check if the project callback can provide us with a custom parser. + ILayoutPullParser parser = mProjectCallback.getParser(layoutName); + if (parser != null) { + BridgeXmlBlockParser blockParser = new BridgeXmlBlockParser(parser, + this, resource.isFramework()); + try { + pushParser(blockParser); + return Pair.of( + mBridgeInflater.inflate(blockParser, parent, attachToRoot), + true); + } finally { + popParser(); + } + } + } + + ResourceValue resValue; + if (resource instanceof ResourceValue) { + resValue = (ResourceValue) resource; + } else { + if (isPlatformLayout) { + resValue = mRenderResources.getFrameworkResource(ResourceType.LAYOUT, + resource.getName()); + } else { + resValue = mRenderResources.getProjectResource(ResourceType.LAYOUT, + resource.getName()); + } + } + + if (resValue != null) { + + File xml = new File(resValue.getValue()); + if (xml.isFile()) { + // we need to create a pull parser around the layout XML file, and then + // give that to our XmlBlockParser + try { + KXmlParser parser = new KXmlParser(); + parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true); + parser.setInput(new FileInputStream(xml), "UTF-8"); //$NON-NLS-1$); + + // set the resource ref to have correct view cookies + mBridgeInflater.setResourceReference(resource); + + BridgeXmlBlockParser blockParser = new BridgeXmlBlockParser(parser, + this, resource.isFramework()); + try { + pushParser(blockParser); + return Pair.of( + mBridgeInflater.inflate(blockParser, parent, attachToRoot), + false); + } finally { + popParser(); + } + } catch (XmlPullParserException e) { + Bridge.getLog().error(LayoutLog.TAG_BROKEN, + "Failed to configure parser for " + xml, e, null /*data*/); + // we'll return null below. + } catch (FileNotFoundException e) { + // this shouldn't happen since we check above. + } finally { + mBridgeInflater.setResourceReference(null); + } + } else { + Bridge.getLog().error(LayoutLog.TAG_BROKEN, + String.format("File %s is missing!", xml), null); + } + } else { + Bridge.getLog().error(LayoutLog.TAG_BROKEN, + String.format("Layout %s%s does not exist.", isPlatformLayout ? "android:" : "", + layoutName), null); + } + + return Pair.of(null, false); + } + // ------------- Activity Methods @Override @@ -400,14 +508,13 @@ public final class BridgeContext extends Activity { BridgeTypedArray ta = ((BridgeResources) mSystemResources).newTypeArray(attrs.length, isPlatformFile); - // resolve the defStyleAttr value into a IStyleResourceValue - StyleResourceValue defStyleValues = null; - // look for a custom style. String customStyle = null; if (set != null) { customStyle = set.getAttributeValue(null /* namespace*/, "style"); } + + StyleResourceValue customStyleValues = null; if (customStyle != null) { ResourceValue item = mRenderResources.findResValue(customStyle, false /*forceFrameworkOnly*/); @@ -416,75 +523,76 @@ public final class BridgeContext extends Activity { item = mRenderResources.resolveResValue(item); if (item instanceof StyleResourceValue) { - defStyleValues = (StyleResourceValue)item; + customStyleValues = (StyleResourceValue)item; } } - if (defStyleValues == null) { - if (defStyleAttr != 0) { - // get the name from the int. - String defStyleName = searchAttr(defStyleAttr); + // resolve the defStyleAttr value into a IStyleResourceValue + StyleResourceValue defStyleValues = null; - if (defaultPropMap != null) { - defaultPropMap.put("style", defStyleName); - } + if (defStyleAttr != 0) { + // get the name from the int. + String defStyleName = searchAttr(defStyleAttr); - // look for the style in the current theme, and its parent: - ResourceValue item = mRenderResources.findItemInTheme(defStyleName); + if (defaultPropMap != null) { + defaultPropMap.put("style", defStyleName); + } - if (item != null) { - // item is a reference to a style entry. Search for it. - item = mRenderResources.findResValue(item.getValue(), - false /*forceFrameworkOnly*/); + // look for the style in the current theme, and its parent: + ResourceValue item = mRenderResources.findItemInTheme(defStyleName); - if (item instanceof StyleResourceValue) { - defStyleValues = (StyleResourceValue)item; - } - } else { - Bridge.getLog().error(null, - String.format( - "Failed to find style '%s' in current theme", defStyleName), - null /*data*/); - } - } else if (defStyleRes != 0) { - Pair<ResourceType, String> value = Bridge.resolveResourceId(defStyleRes); - if (value == null) { - value = mProjectCallback.resolveResourceId(defStyleRes); + if (item != null) { + // item is a reference to a style entry. Search for it. + item = mRenderResources.findResValue(item.getValue(), + false /*forceFrameworkOnly*/); + + if (item instanceof StyleResourceValue) { + defStyleValues = (StyleResourceValue)item; } + } else { + Bridge.getLog().error(null, + String.format( + "Failed to find style '%s' in current theme", defStyleName), + null /*data*/); + } + } else if (defStyleRes != 0) { + Pair<ResourceType, String> value = Bridge.resolveResourceId(defStyleRes); + if (value == null) { + value = mProjectCallback.resolveResourceId(defStyleRes); + } - if (value != null) { - if (value.getFirst() == ResourceType.STYLE) { - // look for the style in the current theme, and its parent: - ResourceValue item = mRenderResources.findItemInTheme(value.getSecond()); - if (item != null) { - if (item instanceof StyleResourceValue) { - if (defaultPropMap != null) { - defaultPropMap.put("style", item.getName()); - } - - defStyleValues = (StyleResourceValue)item; + if (value != null) { + if (value.getFirst() == ResourceType.STYLE) { + // look for the style in the current theme, and its parent: + ResourceValue item = mRenderResources.findItemInTheme(value.getSecond()); + if (item != null) { + if (item instanceof StyleResourceValue) { + if (defaultPropMap != null) { + defaultPropMap.put("style", item.getName()); } - } else { - Bridge.getLog().error(null, - String.format( - "Style with id 0x%x (resolved to '%s') does not exist.", - defStyleRes, value.getSecond()), - null /*data*/); + + defStyleValues = (StyleResourceValue)item; } } else { Bridge.getLog().error(null, String.format( - "Resouce id 0x%x is not of type STYLE (instead %s)", - defStyleRes, value.getFirst().toString()), + "Style with id 0x%x (resolved to '%s') does not exist.", + defStyleRes, value.getSecond()), null /*data*/); } } else { Bridge.getLog().error(null, String.format( - "Failed to find style with id 0x%x in current theme", - defStyleRes), + "Resouce id 0x%x is not of type STYLE (instead %s)", + defStyleRes, value.getFirst().toString()), null /*data*/); } + } else { + Bridge.getLog().error(null, + String.format( + "Failed to find style with id 0x%x in current theme", + defStyleRes), + null /*data*/); } } @@ -509,8 +617,13 @@ public final class BridgeContext extends Activity { if (value == null) { ResourceValue resValue = null; - // look for the value in the defStyle first (and its parent if needed) - if (defStyleValues != null) { + // look for the value in the custom style first (and its parent if needed) + if (customStyleValues != null) { + resValue = mRenderResources.findItemInStyle(customStyleValues, name); + } + + // then look for the value in the default Style (and its parent if needed) + if (resValue == null && defStyleValues != null) { resValue = mRenderResources.findItemInStyle(defStyleValues, name); } diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeInflater.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeInflater.java index cb8d8dd..edfe83e 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeInflater.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeInflater.java @@ -19,6 +19,7 @@ package com.android.layoutlib.bridge.android; import com.android.ide.common.rendering.api.IProjectCallback; import com.android.ide.common.rendering.api.LayoutLog; import com.android.ide.common.rendering.api.MergeCookie; +import com.android.ide.common.rendering.api.ResourceReference; import com.android.ide.common.rendering.api.ResourceValue; import com.android.layoutlib.bridge.Bridge; import com.android.resources.ResourceType; @@ -35,7 +36,7 @@ import android.view.View; import android.view.ViewGroup; import java.io.File; -import java.io.FileReader; +import java.io.FileInputStream; /** * Custom implementation of {@link LayoutInflater} to handle custom views. @@ -44,6 +45,7 @@ public final class BridgeInflater extends LayoutInflater { private final IProjectCallback mProjectCallback; private boolean mIsInMerge = false; + private ResourceReference mResourceReference; /** * List of class prefixes which are tried first by default. @@ -175,7 +177,7 @@ public final class BridgeInflater extends LayoutInflater { try { KXmlParser parser = new KXmlParser(); parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true); - parser.setInput(new FileReader(f)); + parser.setInput(new FileInputStream(f), "UTF-8"); //$NON-NLS-1$ BridgeXmlBlockParser bridgeParser = new BridgeXmlBlockParser( parser, bridgeContext, false); @@ -223,23 +225,33 @@ public final class BridgeInflater extends LayoutInflater { // get the view key Object viewKey = parser.getViewCookie(); - // if there's no view key and the depth is 1 (ie this is the first tag), or 2 - // (this is first item in included merge layout) - // look for a previous parser in the context, and check if this one has a viewkey. - int testDepth = mIsInMerge ? 2 : 1; - if (viewKey == null && parser.getDepth() == testDepth) { + if (viewKey == null) { + int currentDepth = parser.getDepth(); + + // test whether we are in an included file or in a adapter binding view. BridgeXmlBlockParser previousParser = bc.getPreviousParser(); if (previousParser != null) { - viewKey = previousParser.getViewCookie(); + // looks like we inside an embedded layout. + // only apply the cookie of the calling node (<include>) if we are at the + // top level of the embedded layout. If there is a merge tag, then + // skip it and look for the 2nd level + int testDepth = mIsInMerge ? 2 : 1; + if (currentDepth == testDepth) { + viewKey = previousParser.getViewCookie(); + // if we are in a merge, wrap the cookie in a MergeCookie. + if (viewKey != null && mIsInMerge) { + viewKey = new MergeCookie(viewKey); + } + } + } else if (mResourceReference != null && currentDepth == 1) { + // else if there's a resource reference, this means we are in an adapter + // binding case. Set the resource ref as the view cookie only for the top + // level view. + viewKey = mResourceReference; } } if (viewKey != null) { - if (testDepth == 2) { - // include-merge case - viewKey = new MergeCookie(viewKey); - } - bc.addViewKey(view, viewKey); } } @@ -250,6 +262,10 @@ public final class BridgeInflater extends LayoutInflater { mIsInMerge = isInMerge; } + public void setResourceReference(ResourceReference reference) { + mResourceReference = reference; + } + @Override public LayoutInflater cloneInContext(Context newContext) { return new BridgeInflater(this, newContext); diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeResources.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeResources.java index 5e5aeb1..345f053 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeResources.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeResources.java @@ -46,7 +46,6 @@ import android.view.ViewGroup.LayoutParams; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; -import java.io.FileReader; import java.io.InputStream; /** @@ -232,9 +231,8 @@ public final class BridgeResources extends Resources { try { // check if the current parser can provide us with a custom parser. - BridgeXmlBlockParser currentParser = mContext.getCurrentParser(); - if (currentParser != null) { - parser = currentParser.getParser(value.getName()); + if (mPlatformResourceFlag[0] == false) { + parser = mProjectCallback.getParser(value.getName()); } // create a new one manually if needed. @@ -245,7 +243,7 @@ public final class BridgeResources extends Resources { // give that to our XmlBlockParser parser = new KXmlParser(); parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true); - parser.setInput(new FileReader(xml)); + parser.setInput(new FileInputStream(xml), "UTF-8"); //$NON-NLS-1$); } } @@ -283,7 +281,7 @@ public final class BridgeResources extends Resources { // give that to our XmlBlockParser parser = new KXmlParser(); parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true); - parser.setInput(new FileReader(xml)); + parser.setInput(new FileInputStream(xml), "UTF-8"); //$NON-NLS-1$); return new BridgeXmlBlockParser(parser, mContext, mPlatformResourceFlag[0]); } @@ -502,7 +500,7 @@ public final class BridgeResources extends Resources { try { KXmlParser parser = new KXmlParser(); parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true); - parser.setInput(new FileReader(f)); + parser.setInput(new FileInputStream(f), "UTF-8"); //$NON-NLS-1$); return new BridgeXmlBlockParser(parser, mContext, mPlatformResourceFlag[0]); } catch (XmlPullParserException e) { @@ -537,7 +535,7 @@ public final class BridgeResources extends Resources { try { KXmlParser parser = new KXmlParser(); parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true); - parser.setInput(new FileReader(f)); + parser.setInput(new FileInputStream(f), "UTF-8"); //$NON-NLS-1$); return new BridgeXmlBlockParser(parser, mContext, mPlatformResourceFlag[0]); } catch (XmlPullParserException e) { diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeTypedArray.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeTypedArray.java index b9f769f..d4600a1 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeTypedArray.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeTypedArray.java @@ -36,10 +36,11 @@ import android.content.res.TypedArray; import android.graphics.drawable.Drawable; import android.util.DisplayMetrics; import android.util.TypedValue; +import android.view.LayoutInflater_Delegate; import android.view.ViewGroup.LayoutParams; import java.io.File; -import java.io.FileReader; +import java.io.FileInputStream; import java.util.Arrays; import java.util.Map; @@ -315,7 +316,7 @@ public final class BridgeTypedArray extends TypedArray { try { KXmlParser parser = new KXmlParser(); parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true); - parser.setInput(new FileReader(f)); + parser.setInput(new FileInputStream(f), "UTF-8"); //$NON-NLS-1$); BridgeXmlBlockParser blockParser = new BridgeXmlBlockParser( parser, mContext, resValue.isFramework()); @@ -471,40 +472,23 @@ public final class BridgeTypedArray extends TypedArray { */ @Override public int getDimensionPixelSize(int index, int defValue) { - if (mResourceData[index] == null) { - return defValue; - } + try { + return getDimension(index); + } catch (RuntimeException e) { + if (mResourceData[index] != null) { + String s = mResourceData[index].getValue(); - String s = mResourceData[index].getValue(); + if (s != null) { + // looks like we were unable to resolve the dimension value + Bridge.getLog().warning(LayoutLog.TAG_RESOURCES_FORMAT, + String.format( + "\"%1$s\" in attribute \"%2$s\" is not a valid format.", + s, mNames[index]), null /*data*/); + } + } - if (s == null) { - return defValue; - } else if (s.equals(BridgeConstants.MATCH_PARENT) || - s.equals(BridgeConstants.FILL_PARENT)) { - return LayoutParams.MATCH_PARENT; - } else if (s.equals(BridgeConstants.WRAP_CONTENT)) { - return LayoutParams.WRAP_CONTENT; - } else if (RenderResources.REFERENCE_NULL.equals(s)) { return defValue; } - - if (ResourceHelper.stringToFloat(s, mValue)) { - float f = mValue.getDimension(mBridgeResources.mMetrics); - - final int res = (int)(f+0.5f); - if (res != 0) return res; - if (f == 0) return 0; - if (f > 0) return 1; - return defValue; // this is basically unreachable. - } - - // looks like we were unable to resolve the dimension value - Bridge.getLog().warning(LayoutLog.TAG_RESOURCES_FORMAT, - String.format( - "\"%1$s\" in attribute \"%2$s\" is not a valid format.", - s, mNames[index]), null /*data*/); - - return defValue; } /** @@ -521,7 +505,20 @@ public final class BridgeTypedArray extends TypedArray { */ @Override public int getLayoutDimension(int index, String name) { - return getDimensionPixelSize(index, 0); + try { + // this will throw an exception + return getDimension(index); + } catch (RuntimeException e) { + + if (LayoutInflater_Delegate.sIsInInclude) { + throw new RuntimeException(); + } + + Bridge.getLog().warning(LayoutLog.TAG_RESOURCES_FORMAT, + "You must supply a " + name + " attribute.", null); + + return 0; + } } @Override @@ -529,6 +526,36 @@ public final class BridgeTypedArray extends TypedArray { return getDimensionPixelSize(index, defValue); } + private int getDimension(int index) { + if (mResourceData[index] == null) { + throw new RuntimeException(); + } + + String s = mResourceData[index].getValue(); + + if (s == null) { + throw new RuntimeException(); + } else if (s.equals(BridgeConstants.MATCH_PARENT) || + s.equals(BridgeConstants.FILL_PARENT)) { + return LayoutParams.MATCH_PARENT; + } else if (s.equals(BridgeConstants.WRAP_CONTENT)) { + return LayoutParams.WRAP_CONTENT; + } else if (RenderResources.REFERENCE_NULL.equals(s)) { + throw new RuntimeException(); + } + + if (ResourceHelper.stringToFloat(s, mValue)) { + float f = mValue.getDimension(mBridgeResources.mMetrics); + + final int res = (int)(f+0.5f); + if (res != 0) return res; + if (f == 0) return 0; + if (f > 0) return 1; + } + + throw new RuntimeException(); + } + /** * Retrieve a fractional unit attribute at <var>index</var>. * diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeXmlBlockParser.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeXmlBlockParser.java index 2f54ae6..70dbaa4 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeXmlBlockParser.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeXmlBlockParser.java @@ -69,14 +69,6 @@ public class BridgeXmlBlockParser implements XmlResourceParser { return mPlatformFile; } - public ILayoutPullParser getParser(String layoutName) { - if (mParser instanceof ILayoutPullParser) { - return ((ILayoutPullParser)mParser).getParser(layoutName); - } - - return null; - } - public Object getViewCookie() { if (mParser instanceof ILayoutPullParser) { return ((ILayoutPullParser)mParser).getViewCookie(); diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/CustomBar.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/CustomBar.java index 0c4b0d3..060e6ee 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/CustomBar.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/CustomBar.java @@ -73,7 +73,7 @@ abstract class CustomBar extends LinearLayout { parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true); parser.setInput( getClass().getResourceAsStream(layoutPath), - "UTF8"); + "UTF8"); //$NON-NLS-1$ BridgeXmlBlockParser bridgeParser = new BridgeXmlBlockParser( parser, (BridgeContext) context, false /*platformFile*/); diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderAction.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderAction.java index 8e80c21..6194f5d 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderAction.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderAction.java @@ -29,6 +29,7 @@ import com.android.layoutlib.bridge.Bridge; import com.android.layoutlib.bridge.android.BridgeContext; import com.android.resources.ResourceType; +import android.os.HandlerThread_Delegate; import android.util.DisplayMetrics; import java.util.concurrent.TimeUnit; @@ -228,6 +229,10 @@ public abstract class RenderAction<T extends RenderParams> extends FrameworkReso private void tearDown() { // Make sure to remove static references, otherwise we could not unload the lib mContext.disposeResources(); + + // quit HandlerThread created during this session. + HandlerThread_Delegate.cleanUp(sCurrentContext); + sCurrentContext = null; Bridge.setLog(null); diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java index 348c579..f29a9d0 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java @@ -22,12 +22,14 @@ import static com.android.ide.common.rendering.api.Result.Status.ERROR_UNKNOWN; import static com.android.ide.common.rendering.api.Result.Status.ERROR_VIEWGROUP_NO_CHILDREN; import static com.android.ide.common.rendering.api.Result.Status.SUCCESS; +import com.android.ide.common.rendering.api.AdapterBinding; import com.android.ide.common.rendering.api.IAnimationListener; import com.android.ide.common.rendering.api.ILayoutPullParser; import com.android.ide.common.rendering.api.IProjectCallback; import com.android.ide.common.rendering.api.RenderParams; import com.android.ide.common.rendering.api.RenderResources; import com.android.ide.common.rendering.api.RenderSession; +import com.android.ide.common.rendering.api.ResourceReference; import com.android.ide.common.rendering.api.ResourceValue; import com.android.ide.common.rendering.api.Result; import com.android.ide.common.rendering.api.SessionParams; @@ -44,6 +46,8 @@ import com.android.layoutlib.bridge.android.BridgeWindowSession; import com.android.layoutlib.bridge.android.BridgeXmlBlockParser; import com.android.layoutlib.bridge.bars.PhoneSystemBar; import com.android.layoutlib.bridge.bars.TitleBar; +import com.android.layoutlib.bridge.impl.binding.FakeAdapter; +import com.android.layoutlib.bridge.impl.binding.FakeExpandableAdapter; import com.android.resources.ResourceType; import com.android.resources.ScreenSize; import com.android.util.Pair; @@ -62,8 +66,14 @@ import android.view.ViewGroup; import android.view.View.AttachInfo; import android.view.View.MeasureSpec; import android.view.ViewGroup.LayoutParams; +import android.view.ViewGroup.MarginLayoutParams; +import android.widget.AbsListView; +import android.widget.AbsSpinner; +import android.widget.AdapterView; +import android.widget.ExpandableListView; import android.widget.FrameLayout; import android.widget.LinearLayout; +import android.widget.ListView; import android.widget.TabHost; import android.widget.TabWidget; import android.widget.TabHost.TabSpec; @@ -268,6 +278,9 @@ public class RenderSessionImpl extends RenderAction<SessionParams> { View view = mInflater.inflate(mBlockParser, mContentRoot); + // done with the parser, pop it. + context.popParser(); + // set the AttachInfo on the root view. AttachInfo info = new AttachInfo(new BridgeWindowSession(), new BridgeWindow(), new Handler(), null); @@ -453,7 +466,7 @@ public class RenderSessionImpl extends RenderAction<SessionParams> { mViewRoot.draw(mCanvas); } - mViewInfoList = startVisitingViews(mViewRoot, 0); + mViewInfoList = startVisitingViews(mViewRoot, 0, params.getExtendedViewInfoMode()); // success! return SUCCESS.createResult(); @@ -875,6 +888,75 @@ public class RenderSessionImpl extends RenderAction<SessionParams> { throws PostInflateException { if (view instanceof TabHost) { setupTabHost((TabHost)view, projectCallback); + } else if (view instanceof AdapterView<?>) { + // get the view ID. + int id = view.getId(); + + BridgeContext context = getContext(); + + // get a ResourceReference from the integer ID. + ResourceReference listRef = context.resolveId(id); + + if (listRef != null) { + SessionParams params = getParams(); + AdapterBinding binding = params.getAdapterBindings().get(listRef); + + // if there was no adapter binding, trying to get it from the call back. + if (binding == null) { + binding = params.getProjectCallback().getAdapterBinding(listRef, + context.getViewKey(view), view); + } + + if (binding != null) { + + if (view instanceof AbsListView) { + if ((binding.getFooterCount() > 0 || binding.getHeaderCount() > 0) && + view instanceof ListView) { + ListView list = (ListView) view; + + boolean skipCallbackParser = false; + + int count = binding.getHeaderCount(); + for (int i = 0 ; i < count ; i++) { + Pair<View, Boolean> pair = context.inflateView( + binding.getHeaderAt(i), + list, false /*attachToRoot*/, skipCallbackParser); + if (pair.getFirst() != null) { + list.addHeaderView(pair.getFirst()); + } + + skipCallbackParser |= pair.getSecond(); + } + + count = binding.getFooterCount(); + for (int i = 0 ; i < count ; i++) { + Pair<View, Boolean> pair = context.inflateView( + binding.getFooterAt(i), + list, false /*attachToRoot*/, skipCallbackParser); + if (pair.getFirst() != null) { + list.addFooterView(pair.getFirst()); + } + + skipCallbackParser |= pair.getSecond(); + } + } + + if (view instanceof ExpandableListView) { + ((ExpandableListView) view).setAdapter( + new FakeExpandableAdapter( + listRef, binding, params.getProjectCallback())); + } else { + ((AbsListView) view).setAdapter( + new FakeAdapter( + listRef, binding, params.getProjectCallback())); + } + } else if (view instanceof AbsSpinner) { + ((AbsSpinner) view).setAdapter( + new FakeAdapter( + listRef, binding, params.getProjectCallback())); + } + } + } } else if (view instanceof ViewGroup) { ViewGroup group = (ViewGroup)view; final int count = group.getChildCount(); @@ -959,7 +1041,7 @@ public class RenderSessionImpl extends RenderAction<SessionParams> { } } - private List<ViewInfo> startVisitingViews(View view, int offset) { + private List<ViewInfo> startVisitingViews(View view, int offset, boolean setExtendedInfo) { if (view == null) { return null; } @@ -968,7 +1050,7 @@ public class RenderSessionImpl extends RenderAction<SessionParams> { offset += view.getTop(); if (view == mContentRoot) { - return visitAllChildren(mContentRoot, offset); + return visitAllChildren(mContentRoot, offset, setExtendedInfo); } // otherwise, look for mContentRoot in the children @@ -976,7 +1058,8 @@ public class RenderSessionImpl extends RenderAction<SessionParams> { ViewGroup group = ((ViewGroup) view); for (int i = 0; i < group.getChildCount(); i++) { - List<ViewInfo> list = startVisitingViews(group.getChildAt(i), offset); + List<ViewInfo> list = startVisitingViews(group.getChildAt(i), offset, + setExtendedInfo); if (list != null) { return list; } @@ -991,8 +1074,9 @@ public class RenderSessionImpl extends RenderAction<SessionParams> { * bounds of all the views. * @param view the root View * @param offset an offset for the view bounds. + * @param setExtendedInfo whether to set the extended view info in the {@link ViewInfo} object. */ - private ViewInfo visit(View view, int offset) { + private ViewInfo visit(View view, int offset, boolean setExtendedInfo) { if (view == null) { return null; } @@ -1002,9 +1086,22 @@ public class RenderSessionImpl extends RenderAction<SessionParams> { view.getLeft(), view.getTop() + offset, view.getRight(), view.getBottom() + offset, view, view.getLayoutParams()); + if (setExtendedInfo) { + MarginLayoutParams marginParams = null; + LayoutParams params = view.getLayoutParams(); + if (params instanceof MarginLayoutParams) { + marginParams = (MarginLayoutParams) params; + } + result.setExtendedInfo(view.getBaseline(), + marginParams != null ? marginParams.leftMargin : 0, + marginParams != null ? marginParams.topMargin : 0, + marginParams != null ? marginParams.rightMargin : 0, + marginParams != null ? marginParams.bottomMargin : 0); + } + if (view instanceof ViewGroup) { ViewGroup group = ((ViewGroup) view); - result.setChildren(visitAllChildren(group, 0 /*offset*/)); + result.setChildren(visitAllChildren(group, 0 /*offset*/, setExtendedInfo)); } return result; @@ -1015,15 +1112,17 @@ public class RenderSessionImpl extends RenderAction<SessionParams> { * containing the bounds of all the views. * @param view the root View * @param offset an offset for the view bounds. + * @param setExtendedInfo whether to set the extended view info in the {@link ViewInfo} object. */ - private List<ViewInfo> visitAllChildren(ViewGroup viewGroup, int offset) { + private List<ViewInfo> visitAllChildren(ViewGroup viewGroup, int offset, + boolean setExtendedInfo) { if (viewGroup == null) { return null; } List<ViewInfo> children = new ArrayList<ViewInfo>(); for (int i = 0; i < viewGroup.getChildCount(); i++) { - children.add(visit(viewGroup.getChildAt(i), offset)); + children.add(visit(viewGroup.getChildAt(i), offset, setExtendedInfo)); } return children; } diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/ResourceHelper.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/ResourceHelper.java index 649160e..e5efa4e 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/ResourceHelper.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/ResourceHelper.java @@ -44,7 +44,6 @@ import android.util.TypedValue; import java.io.File; import java.io.FileInputStream; -import java.io.FileReader; import java.io.IOException; import java.io.InputStream; import java.net.MalformedURLException; @@ -115,7 +114,7 @@ public final class ResourceHelper { public static ColorStateList getColorStateList(ResourceValue resValue, BridgeContext context) { String value = resValue.getValue(); - if (value != null) { + if (value != null && RenderResources.REFERENCE_NULL.equals(value) == false) { // first check if the value is a file (xml most likely) File f = new File(value); if (f.isFile()) { @@ -124,7 +123,7 @@ public final class ResourceHelper { // providing an XmlPullParser KXmlParser parser = new KXmlParser(); parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true); - parser.setInput(new FileReader(f)); + parser.setInput(new FileInputStream(f), "UTF-8"); //$NON-NLS-1$); BridgeXmlBlockParser blockParser = new BridgeXmlBlockParser( parser, context, resValue.isFramework()); @@ -206,7 +205,7 @@ public final class ResourceHelper { // let the framework inflate the Drawable from the XML file. KXmlParser parser = new KXmlParser(); parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true); - parser.setInput(new FileReader(f)); + parser.setInput(new FileInputStream(f), "UTF-8"); //$NON-NLS-1$); BridgeXmlBlockParser blockParser = new BridgeXmlBlockParser( parser, context, value.isFramework()); diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/binding/BaseAdapter.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/binding/BaseAdapter.java new file mode 100644 index 0000000..e0414fe --- /dev/null +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/binding/BaseAdapter.java @@ -0,0 +1,247 @@ +/* + * Copyright (C) 2011 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.layoutlib.bridge.impl.binding; + +import com.android.ide.common.rendering.api.AdapterBinding; +import com.android.ide.common.rendering.api.DataBindingItem; +import com.android.ide.common.rendering.api.IProjectCallback; +import com.android.ide.common.rendering.api.LayoutLog; +import com.android.ide.common.rendering.api.ResourceReference; +import com.android.ide.common.rendering.api.IProjectCallback.ViewAttribute; +import com.android.layoutlib.bridge.Bridge; +import com.android.layoutlib.bridge.android.BridgeContext; +import com.android.layoutlib.bridge.impl.RenderAction; +import com.android.util.Pair; + +import android.database.DataSetObserver; +import android.view.View; +import android.view.ViewGroup; +import android.widget.AdapterView; +import android.widget.Checkable; +import android.widget.ImageView; +import android.widget.TextView; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * Base adapter to do fake data binding in {@link AdapterView} objects. + */ +public class BaseAdapter { + + /** + * This is the items provided by the adapter. They are dynamically generated. + */ + protected final static class AdapterItem { + private final DataBindingItem mItem; + private final int mType; + private final int mFullPosition; + private final int mPositionPerType; + private List<AdapterItem> mChildren; + + protected AdapterItem(DataBindingItem item, int type, int fullPosition, + int positionPerType) { + mItem = item; + mType = type; + mFullPosition = fullPosition; + mPositionPerType = positionPerType; + } + + void addChild(AdapterItem child) { + if (mChildren == null) { + mChildren = new ArrayList<AdapterItem>(); + } + + mChildren.add(child); + } + + List<AdapterItem> getChildren() { + if (mChildren != null) { + return mChildren; + } + + return Collections.emptyList(); + } + + int getType() { + return mType; + } + + int getFullPosition() { + return mFullPosition; + } + + int getPositionPerType() { + return mPositionPerType; + } + + DataBindingItem getDataBindingItem() { + return mItem; + } + } + + private final AdapterBinding mBinding; + private final IProjectCallback mCallback; + private final ResourceReference mAdapterRef; + private boolean mSkipCallbackParser = false; + + protected final List<AdapterItem> mItems = new ArrayList<AdapterItem>(); + + protected BaseAdapter(ResourceReference adapterRef, AdapterBinding binding, + IProjectCallback callback) { + mAdapterRef = adapterRef; + mBinding = binding; + mCallback = callback; + } + + // ------- Some Adapter method used by all children classes. + + public boolean areAllItemsEnabled() { + return true; + } + + public boolean hasStableIds() { + return true; + } + + public boolean isEmpty() { + return mItems.size() == 0; + } + + public void registerDataSetObserver(DataSetObserver observer) { + // pass + } + + public void unregisterDataSetObserver(DataSetObserver observer) { + // pass + } + + // ------- + + + protected AdapterBinding getBinding() { + return mBinding; + } + + protected View getView(AdapterItem item, AdapterItem parentItem, View convertView, + ViewGroup parent) { + // we don't care about recycling here because we never scroll. + DataBindingItem dataBindingItem = item.getDataBindingItem(); + + BridgeContext context = RenderAction.getCurrentContext(); + + Pair<View, Boolean> pair = context.inflateView(dataBindingItem.getViewReference(), + parent, false /*attachToRoot*/, mSkipCallbackParser); + + View view = pair.getFirst(); + mSkipCallbackParser |= pair.getSecond(); + + if (view != null) { + fillView(context, view, item, parentItem); + } else { + // create a text view to display an error. + TextView tv = new TextView(context); + tv.setText("Unable to find layout: " + dataBindingItem.getViewReference().getName()); + view = tv; + } + + return view; + } + + private void fillView(BridgeContext context, View view, AdapterItem item, + AdapterItem parentItem) { + if (view instanceof ViewGroup) { + ViewGroup group = (ViewGroup) view; + final int count = group.getChildCount(); + for (int i = 0 ; i < count ; i++) { + fillView(context, group.getChildAt(i), item, parentItem); + } + } else { + int id = view.getId(); + if (id != 0) { + ResourceReference resolvedRef = context.resolveId(id); + if (resolvedRef != null) { + int fullPosition = item.getFullPosition(); + int positionPerType = item.getPositionPerType(); + int fullParentPosition = parentItem != null ? parentItem.getFullPosition() : 0; + int parentPositionPerType = parentItem != null ? + parentItem.getPositionPerType() : 0; + + if (view instanceof TextView) { + TextView tv = (TextView) view; + Object value = mCallback.getAdapterItemValue( + mAdapterRef, context.getViewKey(view), + item.getDataBindingItem().getViewReference(), + fullPosition, positionPerType, + fullParentPosition, parentPositionPerType, + resolvedRef, ViewAttribute.TEXT, tv.getText().toString()); + if (value != null) { + if (value.getClass() != ViewAttribute.TEXT.getAttributeClass()) { + Bridge.getLog().error(LayoutLog.TAG_BROKEN, String.format( + "Wrong Adapter Item value class for TEXT. Expected String, got %s", + value.getClass().getName()), null); + } else { + tv.setText((String) value); + } + } + } + + if (view instanceof Checkable) { + Checkable cb = (Checkable) view; + + Object value = mCallback.getAdapterItemValue( + mAdapterRef, context.getViewKey(view), + item.getDataBindingItem().getViewReference(), + fullPosition, positionPerType, + fullParentPosition, parentPositionPerType, + resolvedRef, ViewAttribute.IS_CHECKED, cb.isChecked()); + if (value != null) { + if (value.getClass() != ViewAttribute.IS_CHECKED.getAttributeClass()) { + Bridge.getLog().error(LayoutLog.TAG_BROKEN, String.format( + "Wrong Adapter Item value class for TEXT. Expected Boolean, got %s", + value.getClass().getName()), null); + } else { + cb.setChecked((Boolean) value); + } + } + } + + if (view instanceof ImageView) { + ImageView iv = (ImageView) view; + + Object value = mCallback.getAdapterItemValue( + mAdapterRef, context.getViewKey(view), + item.getDataBindingItem().getViewReference(), + fullPosition, positionPerType, + fullParentPosition, parentPositionPerType, + resolvedRef, ViewAttribute.SRC, iv.getDrawable()); + if (value != null) { + if (value.getClass() != ViewAttribute.SRC.getAttributeClass()) { + Bridge.getLog().error(LayoutLog.TAG_BROKEN, String.format( + "Wrong Adapter Item value class for TEXT. Expected Boolean, got %s", + value.getClass().getName()), null); + } else { + // FIXME + } + } + } + } + } + } + } +} diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/binding/FakeAdapter.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/binding/FakeAdapter.java new file mode 100644 index 0000000..c9bb424 --- /dev/null +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/binding/FakeAdapter.java @@ -0,0 +1,113 @@ +/* + * Copyright (C) 2011 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.layoutlib.bridge.impl.binding; + +import com.android.ide.common.rendering.api.AdapterBinding; +import com.android.ide.common.rendering.api.DataBindingItem; +import com.android.ide.common.rendering.api.IProjectCallback; +import com.android.ide.common.rendering.api.ResourceReference; + +import android.view.View; +import android.view.ViewGroup; +import android.widget.AdapterView; +import android.widget.ListAdapter; +import android.widget.SpinnerAdapter; + +import java.util.ArrayList; +import java.util.List; + +/** + * Fake adapter to do fake data binding in {@link AdapterView} objects for {@link ListAdapter} + * and {@link SpinnerAdapter}. + * + */ +public class FakeAdapter extends BaseAdapter implements ListAdapter, SpinnerAdapter { + + // don't use a set because the order is important. + private final List<ResourceReference> mTypes = new ArrayList<ResourceReference>(); + + public FakeAdapter(ResourceReference adapterRef, AdapterBinding binding, + IProjectCallback callback) { + super(adapterRef, binding, callback); + + final int repeatCount = getBinding().getRepeatCount(); + final int itemCount = getBinding().getItemCount(); + + // Need an array to count for each type. + // This is likely too big, but is the max it can be. + int[] typeCount = new int[itemCount]; + + // We put several repeating sets. + for (int r = 0 ; r < repeatCount ; r++) { + // loop on the type of list items, and add however many for each type. + for (DataBindingItem dataBindingItem : getBinding()) { + ResourceReference viewRef = dataBindingItem.getViewReference(); + int typeIndex = mTypes.indexOf(viewRef); + if (typeIndex == -1) { + typeIndex = mTypes.size(); + mTypes.add(viewRef); + } + + int count = dataBindingItem.getCount(); + + int index = typeCount[typeIndex]; + typeCount[typeIndex] += count; + + for (int k = 0 ; k < count ; k++) { + mItems.add(new AdapterItem(dataBindingItem, typeIndex, mItems.size(), index++)); + } + } + } + } + + public boolean isEnabled(int position) { + return true; + } + + public int getCount() { + return mItems.size(); + } + + public Object getItem(int position) { + return mItems.get(position); + } + + public long getItemId(int position) { + return position; + } + + public int getItemViewType(int position) { + return mItems.get(position).getType(); + } + + public View getView(int position, View convertView, ViewGroup parent) { + // we don't care about recycling here because we never scroll. + AdapterItem item = mItems.get(position); + return getView(item, null /*parentGroup*/, convertView, parent); + } + + public int getViewTypeCount() { + return mTypes.size(); + } + + // ---- SpinnerAdapter + + public View getDropDownView(int position, View convertView, ViewGroup parent) { + // pass + return null; + } +} diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/binding/FakeExpandableAdapter.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/binding/FakeExpandableAdapter.java new file mode 100644 index 0000000..2c492e3 --- /dev/null +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/binding/FakeExpandableAdapter.java @@ -0,0 +1,179 @@ +/* + * Copyright (C) 2011 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.layoutlib.bridge.impl.binding; + +import com.android.ide.common.rendering.api.AdapterBinding; +import com.android.ide.common.rendering.api.DataBindingItem; +import com.android.ide.common.rendering.api.IProjectCallback; +import com.android.ide.common.rendering.api.ResourceReference; + +import android.view.View; +import android.view.ViewGroup; +import android.widget.ExpandableListAdapter; +import android.widget.HeterogeneousExpandableList; + +import java.util.ArrayList; +import java.util.List; + +public class FakeExpandableAdapter extends BaseAdapter implements ExpandableListAdapter, + HeterogeneousExpandableList { + + // don't use a set because the order is important. + private final List<ResourceReference> mGroupTypes = new ArrayList<ResourceReference>(); + private final List<ResourceReference> mChildrenTypes = new ArrayList<ResourceReference>(); + + public FakeExpandableAdapter(ResourceReference adapterRef, AdapterBinding binding, + IProjectCallback callback) { + super(adapterRef, binding, callback); + + createItems(binding, binding.getItemCount(), binding.getRepeatCount(), mGroupTypes, 1); + } + + private void createItems(Iterable<DataBindingItem> iterable, final int itemCount, + final int repeatCount, List<ResourceReference> types, int depth) { + // Need an array to count for each type. + // This is likely too big, but is the max it can be. + int[] typeCount = new int[itemCount]; + + // we put several repeating sets. + for (int r = 0 ; r < repeatCount ; r++) { + // loop on the type of list items, and add however many for each type. + for (DataBindingItem dataBindingItem : iterable) { + ResourceReference viewRef = dataBindingItem.getViewReference(); + int typeIndex = types.indexOf(viewRef); + if (typeIndex == -1) { + typeIndex = types.size(); + types.add(viewRef); + } + + List<DataBindingItem> children = dataBindingItem.getChildren(); + int count = dataBindingItem.getCount(); + + // if there are children, we use the count as a repeat count for the children. + if (children.size() > 0) { + count = 1; + } + + int index = typeCount[typeIndex]; + typeCount[typeIndex] += count; + + for (int k = 0 ; k < count ; k++) { + AdapterItem item = new AdapterItem(dataBindingItem, typeIndex, mItems.size(), + index++); + mItems.add(item); + + if (children.size() > 0) { + createItems(dataBindingItem, depth + 1); + } + } + } + } + } + + private void createItems(DataBindingItem item, int depth) { + if (depth == 2) { + createItems(item, item.getChildren().size(), item.getCount(), mChildrenTypes, depth); + } + } + + private AdapterItem getChildItem(int groupPosition, int childPosition) { + AdapterItem item = mItems.get(groupPosition); + + List<AdapterItem> children = item.getChildren(); + return children.get(childPosition); + } + + // ---- ExpandableListAdapter + + public int getGroupCount() { + return mItems.size(); + } + + public int getChildrenCount(int groupPosition) { + AdapterItem item = mItems.get(groupPosition); + return item.getChildren().size(); + } + + public Object getGroup(int groupPosition) { + return mItems.get(groupPosition); + } + + public Object getChild(int groupPosition, int childPosition) { + return getChildItem(groupPosition, childPosition); + } + + public View getGroupView(int groupPosition, boolean isExpanded, View convertView, + ViewGroup parent) { + // we don't care about recycling here because we never scroll. + AdapterItem item = mItems.get(groupPosition); + return getView(item, null /*parentItem*/, convertView, parent); + } + + public View getChildView(int groupPosition, int childPosition, boolean isLastChild, + View convertView, ViewGroup parent) { + // we don't care about recycling here because we never scroll. + AdapterItem parentItem = mItems.get(groupPosition); + AdapterItem item = getChildItem(groupPosition, childPosition); + return getView(item, parentItem, convertView, parent); + } + + public long getGroupId(int groupPosition) { + return groupPosition; + } + + public long getChildId(int groupPosition, int childPosition) { + return childPosition; + } + + public long getCombinedGroupId(long groupId) { + return groupId << 16 | 0x0000FFFF; + } + + public long getCombinedChildId(long groupId, long childId) { + return groupId << 16 | childId; + } + + public boolean isChildSelectable(int groupPosition, int childPosition) { + return true; + } + + public void onGroupCollapsed(int groupPosition) { + // pass + } + + public void onGroupExpanded(int groupPosition) { + // pass + } + + // ---- HeterogeneousExpandableList + + public int getChildType(int groupPosition, int childPosition) { + return getChildItem(groupPosition, childPosition).getType(); + } + + public int getChildTypeCount() { + return mChildrenTypes.size(); + } + + public int getGroupType(int groupPosition) { + return mItems.get(groupPosition).getType(); + } + + public int getGroupTypeCount() { + return mGroupTypes.size(); + } +} diff --git a/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/android/BridgeXmlBlockParserTest.java b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/android/BridgeXmlBlockParserTest.java index 3252fb4..70d5446 100644 --- a/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/android/BridgeXmlBlockParserTest.java +++ b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/android/BridgeXmlBlockParserTest.java @@ -44,7 +44,7 @@ public class BridgeXmlBlockParserTest extends TestCase { InputStream input = this.getClass().getClassLoader().getResourceAsStream( "com/android/layoutlib/testdata/layout1.xml"); - parser.setInput(input, null /*encoding*/); + parser.setInput(input, "UTF-8"); //$NON-NLS-1$ assertEquals(XmlPullParser.START_DOCUMENT, parser.next()); diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java index 85e6c3f..95506c6 100644 --- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java +++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java @@ -100,11 +100,11 @@ public final class CreateInfo implements ICreateInfo { "android.content.res.Resources$Theme#resolveAttribute", "android.graphics.BitmapFactory#finishDecode", "android.os.Handler#sendMessageAtTime", + "android.os.HandlerThread#run", "android.os.Build#getString", "android.view.LayoutInflater#parseInclude", "android.view.View#isInEditMode", "com.android.internal.util.XmlUtils#convertValueToInt", - // TODO: comment out once DelegateClass is working }; /** diff --git a/wifi/java/android/net/wifi/WifiStateTracker.java b/wifi/java/android/net/wifi/WifiStateTracker.java index 90295e0..fedf3bb 100644 --- a/wifi/java/android/net/wifi/WifiStateTracker.java +++ b/wifi/java/android/net/wifi/WifiStateTracker.java @@ -166,6 +166,9 @@ public class WifiStateTracker extends NetworkStateTracker { */ private static final int DEFAULT_MAX_DHCP_RETRIES = 9; + //Minimum dhcp lease duration for renewal + private static final int MIN_RENEWAL_TIME_SECS = 5 * 60; //5 minutes + private static final int DRIVER_POWER_MODE_AUTO = 0; private static final int DRIVER_POWER_MODE_ACTIVE = 1; @@ -2507,14 +2510,8 @@ public class WifiStateTracker extends NetworkStateTracker { if (NetworkUtils.runDhcp(mInterfaceName, mDhcpInfo)) { event = EVENT_INTERFACE_CONFIGURATION_SUCCEEDED; Log.d(TAG, "DHCP succeeded with lease: " + mDhcpInfo.leaseDuration); - //Do it a bit earlier than half the lease duration time - //to beat the native DHCP client and avoid extra packets - //48% for one hour lease time = 29 minutes - mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, - SystemClock.elapsedRealtime() + - mDhcpInfo.leaseDuration * 480, //in milliseconds - mDhcpRenewalIntent); - } else { + setDhcpRenewalAlarm(mDhcpInfo.leaseDuration); + } else { event = EVENT_INTERFACE_CONFIGURATION_FAILED; Log.e(TAG, "DHCP request failed: " + NetworkUtils.getDhcpError()); } @@ -2551,10 +2548,7 @@ public class WifiStateTracker extends NetworkStateTracker { msg.sendToTarget(); } - mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, - SystemClock.elapsedRealtime() + - mDhcpInfo.leaseDuration * 480, - mDhcpRenewalIntent); + setDhcpRenewalAlarm(mDhcpInfo.leaseDuration); } else { event = EVENT_INTERFACE_CONFIGURATION_FAILED; Log.d(TAG, "DHCP renewal failed: " + NetworkUtils.getDhcpError()); @@ -2602,6 +2596,19 @@ public class WifiStateTracker extends NetworkStateTracker { return state == BluetoothHeadset.STATE_DISCONNECTED; } + private void setDhcpRenewalAlarm(long leaseDuration) { + //Do it a bit earlier than half the lease duration time + //to beat the native DHCP client and avoid extra packets + //48% for one hour lease time = 29 minutes + if (leaseDuration < MIN_RENEWAL_TIME_SECS) { + leaseDuration = MIN_RENEWAL_TIME_SECS; + } + mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, + SystemClock.elapsedRealtime() + + leaseDuration * 480, //in milliseconds + mDhcpRenewalIntent); + } + } private void checkUseStaticIp() { |