diff options
Diffstat (limited to 'core/java')
73 files changed, 4626 insertions, 829 deletions
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java index b277efb..7207e29 100644 --- a/core/java/android/app/Activity.java +++ b/core/java/android/app/Activity.java @@ -29,6 +29,8 @@ import android.content.Intent; import android.content.IntentSender; import android.content.SharedPreferences; import android.content.pm.ActivityInfo; +import android.content.pm.PackageManager; +import android.content.pm.PackageManager.NameNotFoundException; import android.content.res.Configuration; import android.content.res.Resources; import android.content.res.TypedArray; @@ -65,13 +67,13 @@ import android.view.MenuInflater; import android.view.MenuItem; import android.view.MotionEvent; import android.view.View; -import android.view.WindowManagerImpl; import android.view.View.OnCreateContextMenuListener; import android.view.ViewGroup; import android.view.ViewGroup.LayoutParams; import android.view.ViewManager; import android.view.Window; import android.view.WindowManager; +import android.view.WindowManagerImpl; import android.view.accessibility.AccessibilityEvent; import android.widget.AdapterView; @@ -704,6 +706,7 @@ public class Activity extends ContextThemeWrapper /*package*/ boolean mVisibleFromServer = false; /*package*/ boolean mVisibleFromClient = true; /*package*/ ActionBarImpl mActionBar = null; + private boolean mEnableDefaultActionBarUp; private CharSequence mTitle; private int mTitleColor = 0; @@ -865,6 +868,13 @@ public class Activity extends ContextThemeWrapper if (mLastNonConfigurationInstances != null) { mAllLoaderManagers = mLastNonConfigurationInstances.loaders; } + if (mActivityInfo.parentActivityName != null) { + if (mActionBar == null) { + mEnableDefaultActionBarUp = true; + } else { + mActionBar.setDefaultDisplayHomeAsUpEnabled(true); + } + } if (savedInstanceState != null) { Parcelable p = savedInstanceState.getParcelable(FRAGMENTS_TAG); mFragments.restoreAllState(p, mLastNonConfigurationInstances != null @@ -1829,6 +1839,7 @@ public class Activity extends ContextThemeWrapper } mActionBar = new ActionBarImpl(this); + mActionBar.setDefaultDisplayHomeAsUpEnabled(mEnableDefaultActionBarUp); } /** @@ -2511,7 +2522,19 @@ public class Activity extends ContextThemeWrapper if (onOptionsItemSelected(item)) { return true; } - return mFragments.dispatchOptionsItemSelected(item); + if (mFragments.dispatchOptionsItemSelected(item)) { + return true; + } + if (item.getItemId() == android.R.id.home && mActionBar != null && + (mActionBar.getDisplayOptions() & ActionBar.DISPLAY_HOME_AS_UP) != 0) { + if (mParent == null) { + onNavigateUp(); + } else { + mParent.onNavigateUpFromChild(this); + } + return true; + } + return false; case Window.FEATURE_CONTEXT_MENU: EventLog.writeEvent(50000, 1, item.getTitleCondensed()); @@ -2630,7 +2653,7 @@ public class Activity extends ContextThemeWrapper * facilities. * * <p>Derived classes should call through to the base class for it to - * perform the default menu handling. + * perform the default menu handling.</p> * * @param item The menu item that was selected. * @@ -2647,6 +2670,92 @@ public class Activity extends ContextThemeWrapper } /** + * This method is called whenever the user chooses to navigate Up within your application's + * activity hierarchy from the action bar. + * + * <p>If the attribute {@link android.R.attr#parentActivityName parentActivityName} + * was specified in the manifest for this activity or an activity-alias to it, + * default Up navigation will be handled automatically. If any activity + * along the parent chain requires extra Intent arguments, the Activity subclass + * should override the method {@link #onPrepareNavigateUpTaskStack(TaskStackBuilder)} + * to supply those arguments.</p> + * + * <p>See <a href="{@docRoot}guide/topics/fundamentals/tasks-and-back-stack.html">Tasks and Back Stack</a> + * from the developer guide and <a href="{@docRoot}design/patterns/navigation.html">Navigation</a> + * from the design guide for more information about navigating within your app.</p> + * + * <p>See the {@link TaskStackBuilder} class and the Activity methods + * {@link #getParentActivityIntent()}, {@link #shouldUpRecreateTask(Intent)}, and + * {@link #navigateUpTo(Intent)} for help implementing custom Up navigation. + * The AppNavigation sample application in the Android SDK is also available for reference.</p> + * + * @return true if Up navigation completed successfully and this Activity was finished, + * false otherwise. + */ + public boolean onNavigateUp() { + // Automatically handle hierarchical Up navigation if the proper + // metadata is available. + Intent upIntent = getParentActivityIntent(); + if (upIntent != null) { + if (shouldUpRecreateTask(upIntent)) { + TaskStackBuilder b = TaskStackBuilder.from(this); + onCreateNavigateUpTaskStack(b); + onPrepareNavigateUpTaskStack(b); + b.startActivities(); + finish(); + } else { + navigateUpTo(upIntent); + } + return true; + } + return false; + } + + /** + * This is called when a child activity of this one attempts to navigate up. + * The default implementation simply calls onNavigateUp() on this activity (the parent). + * + * @param child The activity making the call. + */ + public boolean onNavigateUpFromChild(Activity child) { + return onNavigateUp(); + } + + /** + * Define the synthetic task stack that will be generated during Up navigation from + * a different task. + * + * <p>The default implementation of this method adds the parent chain of this activity + * as specified in the manifest to the supplied {@link TaskStackBuilder}. Applications + * may choose to override this method to construct the desired task stack in a different + * way.</p> + * + * <p>Applications that wish to supply extra Intent parameters to the parent stack defined + * by the manifest should override {@link #onPrepareNavigateUpTaskStack(TaskStackBuilder)}.</p> + * + * @param builder An empty TaskStackBuilder - the application should add intents representing + * the desired task stack + */ + public void onCreateNavigateUpTaskStack(TaskStackBuilder builder) { + builder.addParentStack(this); + } + + /** + * Prepare the synthetic task stack that will be generated during Up navigation + * from a different task. + * + * <p>This method receives the {@link TaskStackBuilder} with the constructed series of + * Intents as generated by {@link #onCreateNavigateUpTaskStack(TaskStackBuilder)}. + * If any extra data should be added to these intents before launching the new task, + * the application should override this method and add that data here.</p> + * + * @param builder A TaskStackBuilder that has been populated with Intents by + * onCreateNavigateUpTaskStack. + */ + public void onPrepareNavigateUpTaskStack(TaskStackBuilder builder) { + } + + /** * This hook is called whenever the options menu is being closed (either by the user canceling * the menu with the back/menu button, or when an item is selected). * @@ -4658,6 +4767,122 @@ public class Activity extends ContextThemeWrapper public void onActionModeFinished(ActionMode mode) { } + /** + * Returns true if the app should recreate the task when navigating 'up' from this activity + * by using targetIntent. + * + * <p>If this method returns false the app can trivially call + * {@link #navigateUpTo(Intent)} using the same parameters to correctly perform + * up navigation. If this method returns false, the app should synthesize a new task stack + * by using {@link TaskStackBuilder} or another similar mechanism to perform up navigation.</p> + * + * @param targetIntent An intent representing the target destination for up navigation + * @return true if navigating up should recreate a new task stack, false if the same task + * should be used for the destination + */ + public boolean shouldUpRecreateTask(Intent targetIntent) { + try { + PackageManager pm = getPackageManager(); + ComponentName cn = targetIntent.getComponent(); + if (cn == null) { + cn = targetIntent.resolveActivity(pm); + } + ActivityInfo info = pm.getActivityInfo(cn, 0); + if (info.taskAffinity == null) { + return false; + } + return !ActivityManagerNative.getDefault() + .targetTaskAffinityMatchesActivity(mToken, info.taskAffinity); + } catch (RemoteException e) { + return false; + } catch (NameNotFoundException e) { + return false; + } + } + + /** + * Navigate from this activity to the activity specified by upIntent, finishing this activity + * in the process. If the activity indicated by upIntent already exists in the task's history, + * this activity and all others before the indicated activity in the history stack will be + * finished. If the indicated activity does not appear in the history stack, this is equivalent + * to simply calling finish() on this activity. + * + * <p>This method should be used when performing up navigation from within the same task + * as the destination. If up navigation should cross tasks in some cases, see + * {@link #shouldUpRecreateTask(Intent)}.</p> + * + * @param upIntent An intent representing the target destination for up navigation + * + * @return true if up navigation successfully reached the activity indicated by upIntent and + * upIntent was delivered to it. false if an instance of the indicated activity could + * not be found and this activity was simply finished normally. + */ + public boolean navigateUpTo(Intent upIntent) { + if (mParent == null) { + ComponentName destInfo = upIntent.getComponent(); + if (destInfo == null) { + destInfo = upIntent.resolveActivity(getPackageManager()); + if (destInfo == null) { + return false; + } + upIntent = new Intent(upIntent); + upIntent.setComponent(destInfo); + } + int resultCode; + Intent resultData; + synchronized (this) { + resultCode = mResultCode; + resultData = mResultData; + } + if (resultData != null) { + resultData.setAllowFds(false); + } + try { + return ActivityManagerNative.getDefault().navigateUpTo(mToken, upIntent, + resultCode, resultData); + } catch (RemoteException e) { + return false; + } + } else { + return mParent.navigateUpToFromChild(this, upIntent); + } + } + + /** + * This is called when a child activity of this one calls its + * {@link #navigateUpTo} method. The default implementation simply calls + * navigateUpTo(upIntent) on this activity (the parent). + * + * @param child The activity making the call. + * @param upIntent An intent representing the target destination for up navigation + * + * @return true if up navigation successfully reached the activity indicated by upIntent and + * upIntent was delivered to it. false if an instance of the indicated activity could + * not be found and this activity was simply finished normally. + */ + public boolean navigateUpToFromChild(Activity child, Intent upIntent) { + return navigateUpTo(upIntent); + } + + /** + * Obtain an {@link Intent} that will launch an explicit target activity specified by + * this activity's logical parent. The logical parent is named in the application's manifest + * by the {@link android.R.attr#parentActivityName parentActivityName} attribute. + * Activity subclasses may override this method to modify the Intent returned by + * super.getParentActivityIntent() or to implement a different mechanism of retrieving + * the parent intent entirely. + * + * @return a new Intent targeting the defined parent of this activity or null if + * there is no valid parent. + */ + public Intent getParentActivityIntent() { + final String parentName = mActivityInfo.parentActivityName; + if (TextUtils.isEmpty(parentName)) { + return null; + } + return new Intent().setClassName(this, parentName); + } + // ------------------ Internal API ------------------ final void setParent(Activity parent) { diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java index 531a695..11b4c3a 100644 --- a/core/java/android/app/ActivityManager.java +++ b/core/java/android/app/ActivityManager.java @@ -1774,5 +1774,4 @@ public class ActivityManager { return false; } } - } diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java index 5917cbf..a3fdf3e 100644 --- a/core/java/android/app/ActivityManagerNative.java +++ b/core/java/android/app/ActivityManagerNative.java @@ -17,10 +17,10 @@ package android.app; import android.content.ComponentName; +import android.content.IIntentReceiver; +import android.content.IIntentSender; import android.content.Intent; import android.content.IntentFilter; -import android.content.IIntentSender; -import android.content.IIntentReceiver; import android.content.IntentSender; import android.content.pm.ApplicationInfo; import android.content.pm.ConfigurationInfo; @@ -32,11 +32,11 @@ import android.net.Uri; import android.os.Binder; import android.os.Bundle; import android.os.Debug; -import android.os.Parcelable; -import android.os.ParcelFileDescriptor; -import android.os.RemoteException; import android.os.IBinder; import android.os.Parcel; +import android.os.ParcelFileDescriptor; +import android.os.Parcelable; +import android.os.RemoteException; import android.os.ServiceManager; import android.os.StrictMode; import android.text.TextUtils; @@ -867,6 +867,16 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM return true; } + case GET_UID_FOR_INTENT_SENDER_TRANSACTION: { + data.enforceInterface(IActivityManager.descriptor); + IIntentSender r = IIntentSender.Stub.asInterface( + data.readStrongBinder()); + int res = getUidForIntentSender(r); + reply.writeNoException(); + reply.writeInt(res); + return true; + } + case SET_PROCESS_LIMIT_TRANSACTION: { data.enforceInterface(IActivityManager.descriptor); int max = data.readInt(); @@ -1605,6 +1615,31 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM return true; } + case TARGET_TASK_AFFINITY_MATCHES_ACTIVITY_TRANSACTION: { + data.enforceInterface(IActivityManager.descriptor); + IBinder token = data.readStrongBinder(); + String destAffinity = data.readString(); + boolean res = targetTaskAffinityMatchesActivity(token, destAffinity); + reply.writeNoException(); + reply.writeInt(res ? 1 : 0); + return true; + } + + case NAVIGATE_UP_TO_TRANSACTION: { + data.enforceInterface(IActivityManager.descriptor); + IBinder token = data.readStrongBinder(); + Intent target = Intent.CREATOR.createFromParcel(data); + int resultCode = data.readInt(); + Intent resultData = null; + if (data.readInt() != 0) { + resultData = Intent.CREATOR.createFromParcel(data); + } + boolean res = navigateUpTo(token, target, resultCode, resultData); + reply.writeNoException(); + reply.writeInt(res ? 1 : 0); + return true; + } + } return super.onTransact(code, data, reply, flags); @@ -2689,6 +2724,18 @@ class ActivityManagerProxy implements IActivityManager reply.recycle(); return res; } + public int getUidForIntentSender(IIntentSender sender) throws RemoteException { + Parcel data = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + data.writeInterfaceToken(IActivityManager.descriptor); + data.writeStrongBinder(sender.asBinder()); + mRemote.transact(GET_UID_FOR_INTENT_SENDER_TRANSACTION, data, reply, 0); + reply.readException(); + int res = reply.readInt(); + data.recycle(); + reply.recycle(); + return res; + } public void setProcessLimit(int max) throws RemoteException { Parcel data = Parcel.obtain(); @@ -3662,5 +3709,42 @@ class ActivityManagerProxy implements IActivityManager reply.recycle(); } + public boolean targetTaskAffinityMatchesActivity(IBinder token, String destAffinity) + throws RemoteException { + Parcel data = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + data.writeInterfaceToken(IActivityManager.descriptor); + data.writeStrongBinder(token); + data.writeString(destAffinity); + mRemote.transact(TARGET_TASK_AFFINITY_MATCHES_ACTIVITY_TRANSACTION, data, reply, 0); + reply.readException(); + boolean result = reply.readInt() != 0; + data.recycle(); + reply.recycle(); + return result; + } + + public boolean navigateUpTo(IBinder token, Intent target, int resultCode, Intent resultData) + throws RemoteException { + Parcel data = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + data.writeInterfaceToken(IActivityManager.descriptor); + data.writeStrongBinder(token); + target.writeToParcel(data, 0); + data.writeInt(resultCode); + if (resultData != null) { + data.writeInt(1); + resultData.writeToParcel(data, 0); + } else { + data.writeInt(0); + } + mRemote.transact(NAVIGATE_UP_TO_TRANSACTION, data, reply, 0); + reply.readException(); + boolean result = reply.readInt() != 0; + data.recycle(); + reply.recycle(); + return result; + } + private IBinder mRemote; } diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java index d758eca..c5d7b91 100644 --- a/core/java/android/app/ContextImpl.java +++ b/core/java/android/app/ContextImpl.java @@ -45,6 +45,8 @@ import android.graphics.drawable.Drawable; import android.hardware.ISerialManager; import android.hardware.SensorManager; import android.hardware.SerialManager; +import android.hardware.input.IInputManager; +import android.hardware.input.InputManager; import android.hardware.usb.IUsbManager; import android.hardware.usb.UsbManager; import android.location.CountryDetector; @@ -59,6 +61,8 @@ import android.net.NetworkPolicyManager; import android.net.ThrottleManager; import android.net.IThrottleManager; import android.net.Uri; +import android.net.nsd.INsdManager; +import android.net.nsd.NsdManager; import android.net.wifi.IWifiManager; import android.net.wifi.WifiManager; import android.net.wifi.p2p.IWifiP2pManager; @@ -321,6 +325,11 @@ class ContextImpl extends Context { return createDropBoxManager(); }}); + registerService(INPUT_SERVICE, new ServiceFetcher() { + public Object createService(ContextImpl ctx) { + return new InputManager(ctx); + }}); + registerService(INPUT_METHOD_SERVICE, new ServiceFetcher() { public Object createService(ContextImpl ctx) { return InputMethodManager.getInstance(ctx); @@ -372,6 +381,14 @@ class ContextImpl extends Context { ctx.mMainThread.getHandler()); }}); + registerService(NSD_SERVICE, new ServiceFetcher() { + @Override + public Object createService(ContextImpl ctx) { + IBinder b = ServiceManager.getService(NSD_SERVICE); + INsdManager service = INsdManager.Stub.asInterface(b); + return new NsdManager(service); + }}); + // Note: this was previously cached in a static variable, but // constructed using mMainThread.getHandler(), so converting // it to be a regular Context-cached service... diff --git a/core/java/android/app/DownloadManager.java b/core/java/android/app/DownloadManager.java index ad8d41f..dd58397 100644 --- a/core/java/android/app/DownloadManager.java +++ b/core/java/android/app/DownloadManager.java @@ -1097,6 +1097,18 @@ public class DownloadManager { } } + /** {@hide} */ + public static boolean isActiveNetworkExpensive(Context context) { + // TODO: connect to NetworkPolicyManager + return false; + } + + /** {@hide} */ + public static long getActiveNetworkWarningBytes(Context context) { + // TODO: connect to NetworkPolicyManager + return -1; + } + /** * Adds a file to the downloads database system, so it could appear in Downloads App * (and thus become eligible for management by the Downloads App). diff --git a/core/java/android/app/IActivityManager.java b/core/java/android/app/IActivityManager.java index 2b1eb43..c71b186 100644 --- a/core/java/android/app/IActivityManager.java +++ b/core/java/android/app/IActivityManager.java @@ -175,6 +175,7 @@ public interface IActivityManager extends IInterface { public boolean clearApplicationUserData(final String packageName, final IPackageDataObserver observer, int userId) throws RemoteException; public String getPackageForIntentSender(IIntentSender sender) throws RemoteException; + public int getUidForIntentSender(IIntentSender sender) throws RemoteException; public void setProcessLimit(int max) throws RemoteException; public int getProcessLimit() throws RemoteException; @@ -341,6 +342,12 @@ public interface IActivityManager extends IInterface { public void dismissKeyguardOnNextActivity() throws RemoteException; + public boolean targetTaskAffinityMatchesActivity(IBinder token, String destAffinity) + throws RemoteException; + + public boolean navigateUpTo(IBinder token, Intent target, int resultCode, Intent resultData) + throws RemoteException; + /* * Private non-Binder interfaces */ @@ -525,6 +532,7 @@ public interface IActivityManager extends IInterface { int START_BACKUP_AGENT_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+89; int BACKUP_AGENT_CREATED_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+90; int UNBIND_BACKUP_AGENT_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+91; + int GET_UID_FOR_INTENT_SENDER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+92; int START_ACTIVITY_IN_PACKAGE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+94; @@ -578,4 +586,6 @@ public interface IActivityManager extends IInterface { int GET_MY_MEMORY_STATE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+142; int KILL_PROCESSES_BELOW_FOREGROUND_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+143; int GET_CURRENT_USER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+144; + int TARGET_TASK_AFFINITY_MATCHES_ACTIVITY_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+145; + int NAVIGATE_UP_TO_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+146; } diff --git a/core/java/android/app/Instrumentation.java b/core/java/android/app/Instrumentation.java index e4f7950..f955713 100644 --- a/core/java/android/app/Instrumentation.java +++ b/core/java/android/app/Instrumentation.java @@ -23,6 +23,7 @@ import android.content.Intent; import android.content.IntentFilter; import android.content.pm.ActivityInfo; import android.content.res.Configuration; +import android.hardware.input.InputManager; import android.os.Bundle; import android.os.Debug; import android.os.IBinder; @@ -35,6 +36,7 @@ import android.os.SystemClock; import android.util.AndroidRuntimeException; import android.util.Log; import android.view.IWindowManager; +import android.view.InputDevice; import android.view.KeyCharacterMap; import android.view.KeyEvent; import android.view.MotionEvent; @@ -859,11 +861,30 @@ public class Instrumentation { */ public void sendKeySync(KeyEvent event) { validateNotAppThread(); - try { - (IWindowManager.Stub.asInterface(ServiceManager.getService("window"))) - .injectKeyEvent(event, true); - } catch (RemoteException e) { + + long downTime = event.getDownTime(); + long eventTime = event.getEventTime(); + int action = event.getAction(); + int code = event.getKeyCode(); + int repeatCount = event.getRepeatCount(); + int metaState = event.getMetaState(); + int deviceId = event.getDeviceId(); + int scancode = event.getScanCode(); + int source = event.getSource(); + int flags = event.getFlags(); + if (source == InputDevice.SOURCE_UNKNOWN) { + source = InputDevice.SOURCE_KEYBOARD; + } + if (eventTime == 0) { + eventTime = SystemClock.uptimeMillis(); + } + if (downTime == 0) { + downTime = eventTime; } + KeyEvent newEvent = new KeyEvent(downTime, eventTime, action, code, repeatCount, metaState, + deviceId, scancode, flags | KeyEvent.FLAG_FROM_SYSTEM, source); + InputManager.injectInputEvent(newEvent, + InputManager.INJECT_INPUT_EVENT_MODE_WAIT_FOR_FINISH); } /** @@ -902,11 +923,10 @@ public class Instrumentation { */ public void sendPointerSync(MotionEvent event) { validateNotAppThread(); - try { - (IWindowManager.Stub.asInterface(ServiceManager.getService("window"))) - .injectPointerEvent(event, true); - } catch (RemoteException e) { + if ((event.getSource() & InputDevice.SOURCE_CLASS_POINTER) == 0) { + event.setSource(InputDevice.SOURCE_TOUCHSCREEN); } + InputManager.injectInputEvent(event, InputManager.INJECT_INPUT_EVENT_MODE_WAIT_FOR_FINISH); } /** @@ -922,11 +942,10 @@ public class Instrumentation { */ public void sendTrackballEventSync(MotionEvent event) { validateNotAppThread(); - try { - (IWindowManager.Stub.asInterface(ServiceManager.getService("window"))) - .injectTrackballEvent(event, true); - } catch (RemoteException e) { + if ((event.getSource() & InputDevice.SOURCE_CLASS_TRACKBALL) == 0) { + event.setSource(InputDevice.SOURCE_TRACKBALL); } + InputManager.injectInputEvent(event, InputManager.INJECT_INPUT_EVENT_MODE_WAIT_FOR_FINISH); } /** diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index 096af93..04c64a0 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -26,6 +26,7 @@ import android.os.Bundle; import android.os.IBinder; import android.os.Parcel; import android.os.Parcelable; +import android.os.SystemClock; import android.text.TextUtils; import android.util.IntProperty; import android.util.Log; @@ -808,6 +809,7 @@ public class Notification implements Parcelable @Deprecated public void setLatestEventInfo(Context context, CharSequence contentTitle, CharSequence contentText, PendingIntent contentIntent) { + // TODO: rewrite this to use Builder RemoteViews contentView = new RemoteViews(context.getPackageName(), R.layout.notification_template_base); if (this.icon != 0) { @@ -820,6 +822,7 @@ public class Notification implements Parcelable contentView.setTextViewText(R.id.text, contentText); } if (this.when != 0) { + contentView.setViewVisibility(R.id.time, View.VISIBLE); contentView.setLong(R.id.time, "setTime", when); } @@ -942,6 +945,7 @@ public class Notification implements Parcelable private ArrayList<Action> mActions = new ArrayList<Action>(3); private boolean mCanHasIntruder; private boolean mIntruderActionsShowText; + private boolean mUseChronometer; /** * Constructs a new Builder with the defaults: @@ -983,6 +987,18 @@ public class Notification implements Parcelable } /** + * @hide + * + * Show the {@link Notification#when} field as a countdown (or count-up) timer instead of a timestamp. + * + * @see Notification#when + */ + public Builder setUsesChronometer(boolean b) { + mUseChronometer = b; + return this; + } + + /** * Set the small icon resource, which will be used to represent the notification in the * status bar. * @@ -1434,7 +1450,15 @@ public class Notification implements Parcelable } } if (mWhen != 0) { - contentView.setLong(R.id.time, "setTime", mWhen); + if (mUseChronometer) { + contentView.setViewVisibility(R.id.chronometer, View.VISIBLE); + contentView.setLong(R.id.chronometer, "setBase", + mWhen + (SystemClock.elapsedRealtime() - System.currentTimeMillis())); + contentView.setBoolean(R.id.chronometer, "setStarted", true); + } else { + contentView.setViewVisibility(R.id.time, View.VISIBLE); + contentView.setLong(R.id.time, "setTime", mWhen); + } } contentView.setViewVisibility(R.id.line3, hasLine3 ? View.VISIBLE : View.GONE); return contentView; diff --git a/core/java/android/app/TaskStackBuilder.java b/core/java/android/app/TaskStackBuilder.java new file mode 100644 index 0000000..7fd4747 --- /dev/null +++ b/core/java/android/app/TaskStackBuilder.java @@ -0,0 +1,220 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.app; + +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.pm.ActivityInfo; +import android.content.pm.PackageManager; +import android.content.pm.PackageManager.NameNotFoundException; +import android.util.Log; + +import java.util.ArrayList; +import java.util.Iterator; + +/** + * Utility class for constructing synthetic back stacks for cross-task navigation + * on Android 3.0 and newer. + * + * <p>In API level 11 (Android 3.0/Honeycomb) the recommended conventions for + * app navigation using the back key changed. The back key's behavior is local + * to the current task and does not capture navigation across different tasks. + * Navigating across tasks and easily reaching the previous task is accomplished + * through the "recents" UI, accessible through the software-provided Recents key + * on the navigation or system bar. On devices with the older hardware button configuration + * the recents UI can be accessed with a long press on the Home key.</p> + * + * <p>When crossing from one task stack to another post-Android 3.0, + * the application should synthesize a back stack/history for the new task so that + * the user may navigate out of the new task and back to the Launcher by repeated + * presses of the back key. Back key presses should not navigate across task stacks.</p> + * + * <p>TaskStackBuilder provides a way to obey the correct conventions + * around cross-task navigation.</p> + * + * <div class="special reference"> + * <h3>About Navigation</h3> + * For more detailed information about tasks, the back stack, and navigation design guidelines, + * please read + * <a href="{@docRoot}guide/topics/fundamentals/tasks-and-back-stack.html">Tasks and Back Stack</a> + * from the developer guide and <a href="{@docRoot}design/patterns/navigation.html">Navigation</a> + * from the design guide. + * </div> + */ +public class TaskStackBuilder implements Iterable<Intent> { + private static final String TAG = "TaskStackBuilder"; + + private final ArrayList<Intent> mIntents = new ArrayList<Intent>(); + private final Context mSourceContext; + + private TaskStackBuilder(Context a) { + mSourceContext = a; + } + + /** + * Return a new TaskStackBuilder for launching a fresh task stack consisting + * of a series of activities. + * + * @param context The context that will launch the new task stack or generate a PendingIntent + * @return A new TaskStackBuilder + */ + public static TaskStackBuilder from(Context context) { + return new TaskStackBuilder(context); + } + + /** + * Add a new Intent to the task stack. The most recently added Intent will invoke + * the Activity at the top of the final task stack. + * + * @param nextIntent Intent for the next Activity in the synthesized task stack + * @return This TaskStackBuilder for method chaining + */ + public TaskStackBuilder addNextIntent(Intent nextIntent) { + mIntents.add(nextIntent); + return this; + } + + /** + * Add the activity parent chain as specified by the + * {@link android.R.attr#parentActivityName parentActivityName} attribute of the activity + * (or activity-alias) element in the application's manifest to the task stack builder. + * + * @param sourceActivity All parents of this activity will be added + * @return This TaskStackBuilder for method chaining + */ + public TaskStackBuilder addParentStack(Activity sourceActivity) { + final int insertAt = mIntents.size(); + Intent parent = sourceActivity.getParentActivityIntent(); + PackageManager pm = sourceActivity.getPackageManager(); + while (parent != null) { + mIntents.add(insertAt, parent); + try { + ActivityInfo info = pm.getActivityInfo(parent.getComponent(), 0); + String parentActivity = info.parentActivityName; + if (parentActivity != null) { + parent = new Intent().setComponent( + new ComponentName(mSourceContext, parentActivity)); + } else { + parent = null; + } + } catch (NameNotFoundException e) { + Log.e(TAG, "Bad ComponentName while traversing activity parent metadata"); + throw new IllegalArgumentException(e); + } + } + return this; + } + + /** + * Add the activity parent chain as specified by the + * {@link android.R.attr#parentActivityName parentActivityName} attribute of the activity + * (or activity-alias) element in the application's manifest to the task stack builder. + * + * @param sourceActivityClass All parents of this activity will be added + * @return This TaskStackBuilder for method chaining + */ + public TaskStackBuilder addParentStack(Class<?> sourceActivityClass) { + final int insertAt = mIntents.size(); + PackageManager pm = mSourceContext.getPackageManager(); + try { + ActivityInfo info = pm.getActivityInfo( + new ComponentName(mSourceContext, sourceActivityClass), 0); + String parentActivity = info.parentActivityName; + Intent parent = new Intent().setComponent( + new ComponentName(mSourceContext, parentActivity)); + while (parent != null) { + mIntents.add(insertAt, parent); + info = pm.getActivityInfo(parent.getComponent(), 0); + parentActivity = info.parentActivityName; + if (parentActivity != null) { + parent = new Intent().setComponent( + new ComponentName(mSourceContext, parentActivity)); + } else { + parent = null; + } + } + } catch (NameNotFoundException e) { + Log.e(TAG, "Bad ComponentName while traversing activity parent metadata"); + throw new IllegalArgumentException(e); + } + return this; + } + + /** + * @return the number of intents added so far. + */ + public int getIntentCount() { + return mIntents.size(); + } + + /** + * Get the intent at the specified index. + * Useful if you need to modify the flags or extras of an intent that was previously added, + * for example with {@link #addParentStack(Activity)}. + * + * @param index Index from 0-getIntentCount() + * @return the intent at position index + */ + public Intent getIntent(int index) { + return mIntents.get(index); + } + + public Iterator<Intent> iterator() { + return mIntents.iterator(); + } + + /** + * Start the task stack constructed by this builder. + */ + public void startActivities() { + if (mIntents.isEmpty()) { + throw new IllegalStateException( + "No intents added to TaskStackBuilder; cannot startActivities"); + } + + Intent[] intents = mIntents.toArray(new Intent[mIntents.size()]); + intents[0].addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | + Intent.FLAG_ACTIVITY_CLEAR_TASK | + Intent.FLAG_ACTIVITY_TASK_ON_HOME); + mSourceContext.startActivities(intents); + } + + /** + * Obtain a {@link PendingIntent} for launching the task constructed by this builder so far. + * + * @param requestCode Private request code for the sender + * @param flags May be {@link PendingIntent#FLAG_ONE_SHOT}, + * {@link PendingIntent#FLAG_NO_CREATE}, {@link PendingIntent#FLAG_CANCEL_CURRENT}, + * {@link PendingIntent#FLAG_UPDATE_CURRENT}, or any of the flags supported by + * {@link Intent#fillIn(Intent, int)} to control which unspecified parts of the + * intent that can be supplied when the actual send happens. + * @return The obtained PendingIntent + */ + public PendingIntent getPendingIntent(int requestCode, int flags) { + if (mIntents.isEmpty()) { + throw new IllegalStateException( + "No intents added to TaskStackBuilder; cannot getPendingIntent"); + } + + Intent[] intents = mIntents.toArray(new Intent[mIntents.size()]); + intents[0].addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | + Intent.FLAG_ACTIVITY_CLEAR_TASK | + Intent.FLAG_ACTIVITY_TASK_ON_HOME); + return PendingIntent.getActivities(mSourceContext, requestCode, intents, flags); + } +} diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index 2902504..36638f9 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -1769,6 +1769,18 @@ public abstract class Context { public static final String WIFI_P2P_SERVICE = "wifip2p"; /** + * Use with {@link #getSystemService} to retrieve a {@link + * android.net.NsdManager} for handling management of network service + * discovery + * + * @hide + * @see #getSystemService + * @see android.net.NsdManager + */ + public static final String NSD_SERVICE = "servicediscovery"; + + + /** * Use with {@link #getSystemService} to retrieve a * {@link android.media.AudioManager} for handling management of volume, * ringer modes and audio routing. @@ -1907,6 +1919,15 @@ public abstract class Context { public static final String SERIAL_SERVICE = "serial"; /** + * Use with {@link #getSystemService} to retrieve a + * {@link android.hardware.input.InputManager} for interacting with input devices. + * + * @see #getSystemService + * @see android.hardware.input.InputManager + */ + public static final String INPUT_SERVICE = "input"; + + /** * Determine whether the given permission is allowed for a particular * process and user ID running in the system. * diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java index 2a9f1af..18d682d 100644 --- a/core/java/android/content/Intent.java +++ b/core/java/android/content/Intent.java @@ -2000,8 +2000,8 @@ public class Intent implements Parcelable, Cloneable { * @hide */ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) - public static final String ACTION_USB_ANLG_HEADSET_PLUG = - "android.intent.action.USB_ANLG_HEADSET_PLUG"; + public static final String ACTION_ANALOG_AUDIO_DOCK_PLUG = + "android.intent.action.ANALOG_AUDIO_DOCK_PLUG"; /** * Broadcast Action: A digital audio speaker/headset plugged in or unplugged. @@ -2015,8 +2015,8 @@ public class Intent implements Parcelable, Cloneable { * @hide */ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) - public static final String ACTION_USB_DGTL_HEADSET_PLUG = - "android.intent.action.USB_DGTL_HEADSET_PLUG"; + public static final String ACTION_DIGITAL_AUDIO_DOCK_PLUG = + "android.intent.action.DIGITAL_AUDIO_DOCK_PLUG"; /** * Broadcast Action: A HMDI cable was plugged or unplugged @@ -2034,6 +2034,38 @@ public class Intent implements Parcelable, Cloneable { "android.intent.action.HDMI_AUDIO_PLUG"; /** + * Broadcast Action: A USB audio accessory was plugged in or unplugged. + * + * <p>The intent will have the following extra values: + * <ul> + * <li><em>state</em> - 0 for unplugged, 1 for plugged. </li> + * <li><em>card</em> - ALSA card number (integer) </li> + * <li><em>device</em> - ALSA device number (integer) </li> + * </ul> + * </ul> + * @hide + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_USB_AUDIO_ACCESSORY_PLUG = + "android.intent.action.USB_AUDIO_ACCESSORY_PLUG"; + + /** + * Broadcast Action: A USB audio device was plugged in or unplugged. + * + * <p>The intent will have the following extra values: + * <ul> + * <li><em>state</em> - 0 for unplugged, 1 for plugged. </li> + * <li><em>card</em> - ALSA card number (integer) </li> + * <li><em>device</em> - ALSA device number (integer) </li> + * </ul> + * </ul> + * @hide + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_USB_AUDIO_DEVICE_PLUG = + "android.intent.action.USB_AUDIO_DEVICE_PLUG"; + + /** * <p>Broadcast Action: The user has switched on advanced settings in the settings app:</p> * <ul> * <li><em>state</em> - A boolean value indicating whether the settings is on or off.</li> diff --git a/core/java/android/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java index 0e6694d..6b16e74 100644 --- a/core/java/android/content/pm/ActivityInfo.java +++ b/core/java/android/content/pm/ActivityInfo.java @@ -450,6 +450,11 @@ public class ActivityInfo extends ComponentInfo */ public static final int UIOPTION_SPLIT_ACTION_BAR_WHEN_NARROW = 1; + /** + * If defined, the activity named here is the logical parent of this activity. + */ + public String parentActivityName; + public ActivityInfo() { } @@ -465,6 +470,7 @@ public class ActivityInfo extends ComponentInfo configChanges = orig.configChanges; softInputMode = orig.softInputMode; uiOptions = orig.uiOptions; + parentActivityName = orig.parentActivityName; } /** @@ -524,6 +530,7 @@ public class ActivityInfo extends ComponentInfo dest.writeInt(configChanges); dest.writeInt(softInputMode); dest.writeInt(uiOptions); + dest.writeString(parentActivityName); } public static final Parcelable.Creator<ActivityInfo> CREATOR @@ -548,5 +555,6 @@ public class ActivityInfo extends ComponentInfo configChanges = source.readInt(); softInputMode = source.readInt(); uiOptions = source.readInt(); + parentActivityName = source.readString(); } } diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java index a79b86a..7571993 100644 --- a/core/java/android/content/pm/PackageParser.java +++ b/core/java/android/content/pm/PackageParser.java @@ -94,10 +94,12 @@ public class PackageParser { public static class SplitPermissionInfo { public final String rootPerm; public final String[] newPerms; + public final int targetSdk; - public SplitPermissionInfo(String rootPerm, String[] newPerms) { + public SplitPermissionInfo(String rootPerm, String[] newPerms, int targetSdk) { this.rootPerm = rootPerm; this.newPerms = newPerms; + this.targetSdk = targetSdk; } } @@ -126,7 +128,14 @@ public class PackageParser { public static final PackageParser.SplitPermissionInfo SPLIT_PERMISSIONS[] = new PackageParser.SplitPermissionInfo[] { new PackageParser.SplitPermissionInfo(android.Manifest.permission.WRITE_EXTERNAL_STORAGE, - new String[] { android.Manifest.permission.READ_EXTERNAL_STORAGE }) + new String[] { android.Manifest.permission.READ_EXTERNAL_STORAGE }, + android.os.Build.VERSION_CODES.CUR_DEVELOPMENT+1), + new PackageParser.SplitPermissionInfo(android.Manifest.permission.READ_CONTACTS, + new String[] { android.Manifest.permission.READ_CALL_LOG }, + android.os.Build.VERSION_CODES.JELLY_BEAN), + new PackageParser.SplitPermissionInfo(android.Manifest.permission.WRITE_CONTACTS, + new String[] { android.Manifest.permission.WRITE_CALL_LOG }, + android.os.Build.VERSION_CODES.JELLY_BEAN) }; private String mArchiveSourcePath; @@ -1293,8 +1302,9 @@ public class PackageParser { for (int is=0; is<NS; is++) { final PackageParser.SplitPermissionInfo spi = PackageParser.SPLIT_PERMISSIONS[is]; - if (!pkg.requestedPermissions.contains(spi.rootPerm)) { - break; + if (pkg.applicationInfo.targetSdkVersion >= spi.targetSdk + || !pkg.requestedPermissions.contains(spi.rootPerm)) { + continue; } for (int in=0; in<spi.newPerms.length; in++) { final String perm = spi.newPerms[in]; @@ -2030,6 +2040,19 @@ public class PackageParser { com.android.internal.R.styleable.AndroidManifestActivity_uiOptions, a.info.applicationInfo.uiOptions); + String parentName = sa.getNonConfigurationString( + com.android.internal.R.styleable.AndroidManifestActivity_parentActivityName, 0); + if (parentName != null) { + String parentClassName = buildClassName(a.info.packageName, parentName, outError); + if (outError[0] == null) { + a.info.parentActivityName = parentClassName; + } else { + Log.e(TAG, "Activity " + a.info.name + " specified invalid parentActivityName " + + parentName); + outError[0] = null; + } + } + String str; str = sa.getNonConfigurationString( com.android.internal.R.styleable.AndroidManifestActivity_permission, 0); @@ -2274,6 +2297,7 @@ public class PackageParser { info.theme = target.info.theme; info.softInputMode = target.info.softInputMode; info.uiOptions = target.info.uiOptions; + info.parentActivityName = target.info.parentActivityName; Activity a = new Activity(mParseActivityAliasArgs, info); if (outError[0] != null) { @@ -2295,6 +2319,20 @@ public class PackageParser { a.info.permission = str.length() > 0 ? str.toString().intern() : null; } + String parentName = sa.getNonConfigurationString( + com.android.internal.R.styleable.AndroidManifestActivityAlias_parentActivityName, + 0); + if (parentName != null) { + String parentClassName = buildClassName(a.info.packageName, parentName, outError); + if (outError[0] == null) { + a.info.parentActivityName = parentClassName; + } else { + Log.e(TAG, "Activity alias " + a.info.name + + " specified invalid parentActivityName " + parentName); + outError[0] = null; + } + } + sa.recycle(); if (outError[0] != null) { diff --git a/core/java/android/hardware/input/IInputManager.aidl b/core/java/android/hardware/input/IInputManager.aidl new file mode 100644 index 0000000..c2abce5 --- /dev/null +++ b/core/java/android/hardware/input/IInputManager.aidl @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2012 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.hardware.input; + +import android.view.InputDevice; +import android.view.InputEvent; + +/** @hide */ +interface IInputManager { + // Gets input device information. + InputDevice getInputDevice(int deviceId); + int[] getInputDeviceIds(); + + // Reports whether the hardware supports the given keys; returns true if successful + boolean hasKeys(int deviceId, int sourceMask, in int[] keyCodes, out boolean[] keyExists); + + // Temporarily changes the pointer speed. + void tryPointerSpeed(int speed); + + // Injects an input event into the system. To inject into windows owned by other + // applications, the caller must have the INJECT_EVENTS permission. + boolean injectInputEvent(in InputEvent ev, int mode); +} diff --git a/core/java/android/hardware/input/InputManager.java b/core/java/android/hardware/input/InputManager.java new file mode 100755 index 0000000..5ead1f4 --- /dev/null +++ b/core/java/android/hardware/input/InputManager.java @@ -0,0 +1,651 @@ +/* + * Copyright (C) 2012 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.hardware.input; + +import com.android.internal.util.XmlUtils; + +import android.annotation.SdkConstant; +import android.annotation.SdkConstant.SdkConstantType; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.pm.ActivityInfo; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.content.pm.PackageManager.NameNotFoundException; +import android.content.res.Resources; +import android.content.res.TypedArray; +import android.content.res.XmlResourceParser; +import android.os.Bundle; +import android.os.IBinder; +import android.os.Parcel; +import android.os.Parcelable; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.provider.Settings; +import android.provider.Settings.SettingNotFoundException; +import android.util.Log; +import android.view.InputDevice; +import android.view.InputEvent; +import android.view.KeyCharacterMap; +import android.view.KeyCharacterMap.UnavailableException; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; + +/** + * Provides information about input devices and available key layouts. + * <p> + * Get an instance of this class by calling + * {@link android.content.Context#getSystemService(java.lang.String) + * Context.getSystemService()} with the argument + * {@link android.content.Context#INPUT_SERVICE}. + * </p> + */ +public final class InputManager { + private static final String TAG = "InputManager"; + + private static final IInputManager sIm; + + private final Context mContext; + + // Used to simulate a persistent data store. + // TODO: Replace with the real thing. + private static final HashMap<String, String> mFakeRegistry = new HashMap<String, String>(); + + /** + * Broadcast Action: Query available keyboard layouts. + * <p> + * The input manager service locates available keyboard layouts + * by querying broadcast receivers that are registered for this action. + * An application can offer additional keyboard layouts to the user + * by declaring a suitable broadcast receiver in its manifest. + * </p><p> + * Here is an example broadcast receiver declaration that an application + * might include in its AndroidManifest.xml to advertise keyboard layouts. + * The meta-data specifies a resource that contains a description of each keyboard + * layout that is provided by the application. + * <pre><code> + * <receiver android:name=".InputDeviceReceiver"> + * <intent-filter> + * <action android:name="android.hardware.input.action.QUERY_KEYBOARD_LAYOUTS" /> + * </intent-filter> + * <meta-data android:name="android.hardware.input.metadata.KEYBOARD_LAYOUTS" + * android:resource="@xml/keyboard_layouts" /> + * </receiver> + * </code></pre> + * </p><p> + * In the above example, the <code>@xml/keyboard_layouts</code> resource refers to + * an XML resource whose root element is <code><keyboard-layouts></code> that + * contains zero or more <code><keyboard-layout></code> elements. + * Each <code><keyboard-layout></code> element specifies the name, label, and location + * of a key character map for a particular keyboard layout. + * <pre></code> + * <?xml version="1.0" encoding="utf-8"?> + * <keyboard-layouts xmlns:android="http://schemas.android.com/apk/res/android"> + * <keyboard-layout android:name="keyboard_layout_english_us" + * android:label="@string/keyboard_layout_english_us_label" + * android:kcm="@raw/keyboard_layout_english_us" /> + * </keyboard-layouts> + * </p><p> + * The <code>android:name</code> attribute specifies an identifier by which + * the keyboard layout will be known in the package. + * The <code>android:label</code> attributes specifies a human-readable descriptive + * label to describe the keyboard layout in the user interface, such as "English (US)". + * The <code>android:kcm</code> attribute refers to a + * <a href="http://source.android.com/tech/input/key-character-map-files.html"> + * key character map</a> resource that defines the keyboard layout. + * </p> + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_QUERY_KEYBOARD_LAYOUTS = + "android.hardware.input.action.QUERY_KEYBOARD_LAYOUTS"; + + /** + * Metadata Key: Keyboard layout metadata associated with + * {@link #ACTION_QUERY_KEYBOARD_LAYOUTS}. + * <p> + * Specifies the resource id of a XML resource that describes the keyboard + * layouts that are provided by the application. + * </p> + */ + public static final String META_DATA_KEYBOARD_LAYOUTS = + "android.hardware.input.metadata.KEYBOARD_LAYOUTS"; + + /** + * Pointer Speed: The minimum (slowest) pointer speed (-7). + * @hide + */ + public static final int MIN_POINTER_SPEED = -7; + + /** + * Pointer Speed: The maximum (fastest) pointer speed (7). + * @hide + */ + public static final int MAX_POINTER_SPEED = 7; + + /** + * Pointer Speed: The default pointer speed (0). + * @hide + */ + public static final int DEFAULT_POINTER_SPEED = 0; + + /** + * Input Event Injection Synchronization Mode: None. + * Never blocks. Injection is asynchronous and is assumed always to be successful. + * @hide + */ + public static final int INJECT_INPUT_EVENT_MODE_ASYNC = 0; // see InputDispatcher.h + + /** + * Input Event Injection Synchronization Mode: Wait for result. + * Waits for previous events to be dispatched so that the input dispatcher can + * determine whether input event injection will be permitted based on the current + * input focus. Does not wait for the input event to finish being handled + * by the application. + * @hide + */ + public static final int INJECT_INPUT_EVENT_MODE_WAIT_FOR_RESULT = 1; // see InputDispatcher.h + + /** + * Input Event Injection Synchronization Mode: Wait for finish. + * Waits for the event to be delivered to the application and handled. + * @hide + */ + public static final int INJECT_INPUT_EVENT_MODE_WAIT_FOR_FINISH = 2; // see InputDispatcher.h + + static { + IBinder b = ServiceManager.getService(Context.INPUT_SERVICE); + sIm = IInputManager.Stub.asInterface(b); + } + + /** @hide */ + public InputManager(Context context) { + mContext = context; + } + + /** + * Gets information about all supported keyboard layouts. + * <p> + * The input manager consults the built-in keyboard layouts as well + * as all keyboard layouts advertised by applications using a + * {@link #ACTION_QUERY_KEYBOARD_LAYOUTS} broadcast receiver. + * </p> + * + * @return A list of all supported keyboard layouts. + * @hide + */ + public List<KeyboardLayout> getKeyboardLayouts() { + ArrayList<KeyboardLayout> list = new ArrayList<KeyboardLayout>(); + + final PackageManager pm = mContext.getPackageManager(); + Intent intent = new Intent(ACTION_QUERY_KEYBOARD_LAYOUTS); + for (ResolveInfo resolveInfo : pm.queryBroadcastReceivers(intent, + PackageManager.GET_META_DATA)) { + loadKeyboardLayouts(pm, resolveInfo.activityInfo, list, null); + } + return list; + } + + /** + * Gets the keyboard layout with the specified descriptor. + * + * @param keyboardLayoutDescriptor The keyboard layout descriptor, as returned by + * {@link KeyboardLayout#getDescriptor()}. + * @return The keyboard layout, or null if it could not be loaded. + * + * @hide + */ + public KeyboardLayout getKeyboardLayout(String keyboardLayoutDescriptor) { + if (keyboardLayoutDescriptor == null) { + throw new IllegalArgumentException("keyboardLayoutDescriptor must not be null"); + } + + KeyboardLayoutDescriptor d = parseKeyboardLayoutDescriptor(keyboardLayoutDescriptor); + if (d == null) { + return null; + } + + final PackageManager pm = mContext.getPackageManager(); + try { + ActivityInfo receiver = pm.getReceiverInfo( + new ComponentName(d.packageName, d.receiverName), + PackageManager.GET_META_DATA); + return loadKeyboardLayouts(pm, receiver, null, d.keyboardLayoutName); + } catch (NameNotFoundException ex) { + Log.w(TAG, "Could not load keyboard layout '" + d.keyboardLayoutName + + "' from receiver " + d.packageName + "/" + d.receiverName, ex); + return null; + } + } + + /** + * Gets the keyboard layout descriptor for the specified input device. + * + * @param inputDeviceDescriptor The input device descriptor. + * @return The keyboard layout descriptor, or null if unknown or if the default + * keyboard layout will be used. + * + * @hide + */ + public String getInputDeviceKeyboardLayoutDescriptor(String inputDeviceDescriptor) { + if (inputDeviceDescriptor == null) { + throw new IllegalArgumentException("inputDeviceDescriptor must not be null"); + } + + return mFakeRegistry.get(inputDeviceDescriptor); + } + + /** + * Sets the keyboard layout descriptor for the specified input device. + * <p> + * This method may have the side-effect of causing the input device in question + * to be reconfigured. + * </p> + * + * @param inputDeviceDescriptor The input device descriptor. + * @param keyboardLayoutDescriptor The keyboard layout descriptor, or null to remove + * the mapping so that the default keyboard layout will be used for the input device. + * + * @hide + */ + public void setInputDeviceKeyboardLayoutDescriptor(String inputDeviceDescriptor, + String keyboardLayoutDescriptor) { + if (inputDeviceDescriptor == null) { + throw new IllegalArgumentException("inputDeviceDescriptor must not be null"); + } + + mFakeRegistry.put(inputDeviceDescriptor, keyboardLayoutDescriptor); + } + + private KeyboardLayout loadKeyboardLayouts( + PackageManager pm, ActivityInfo receiver, + List<KeyboardLayout> list, String keyboardName) { + Bundle metaData = receiver.metaData; + if (metaData == null) { + return null; + } + + int configResId = metaData.getInt(META_DATA_KEYBOARD_LAYOUTS); + if (configResId == 0) { + Log.w(TAG, "Missing meta-data '" + META_DATA_KEYBOARD_LAYOUTS + "' on receiver " + + receiver.packageName + "/" + receiver.name); + return null; + } + + try { + Resources resources = pm.getResourcesForApplication(receiver.applicationInfo); + XmlResourceParser parser = resources.getXml(configResId); + try { + XmlUtils.beginDocument(parser, "keyboard-layouts"); + + for (;;) { + XmlUtils.nextElement(parser); + String element = parser.getName(); + if (element == null) { + break; + } + if (element.equals("keyboard-layout")) { + TypedArray a = resources.obtainAttributes( + parser, com.android.internal.R.styleable.KeyboardLayout); + try { + String name = a.getString( + com.android.internal.R.styleable.KeyboardLayout_name); + String label = a.getString( + com.android.internal.R.styleable.KeyboardLayout_label); + int kcmResId = a.getResourceId( + com.android.internal.R.styleable.KeyboardLayout_kcm, 0); + if (name == null || label == null || kcmResId == 0) { + Log.w(TAG, "Missing required 'name', 'label' or 'kcm' " + + "attributes in keyboard layout " + + "resource from receiver " + + receiver.packageName + "/" + receiver.name); + } else { + String descriptor = makeKeyboardLayoutDescriptor( + receiver.packageName, receiver.name, name); + KeyboardLayout c = new KeyboardLayout( + descriptor, label, kcmResId); + if (keyboardName != null && name.equals(keyboardName)) { + return c; + } + if (list != null) { + list.add(c); + } + } + } finally { + a.recycle(); + } + } else { + Log.w(TAG, "Skipping unrecognized element '" + element + + "' in keyboard layout resource from receiver " + + receiver.packageName + "/" + receiver.name); + } + } + } finally { + parser.close(); + } + } catch (Exception ex) { + Log.w(TAG, "Could not load keyboard layout resource from receiver " + + receiver.packageName + "/" + receiver.name, ex); + return null; + } + if (keyboardName != null) { + Log.w(TAG, "Could not load keyboard layout '" + keyboardName + + "' from receiver " + receiver.packageName + "/" + receiver.name + + " because it was not declared in the keyboard layout resource."); + } + return null; + } + + /** + * Gets the mouse pointer speed. + * <p> + * Only returns the permanent mouse pointer speed. Ignores any temporary pointer + * speed set by {@link #tryPointerSpeed}. + * </p> + * + * @return The pointer speed as a value between {@link #MIN_POINTER_SPEED} and + * {@link #MAX_POINTER_SPEED}, or the default value {@link #DEFAULT_POINTER_SPEED}. + * + * @hide + */ + public int getPointerSpeed() { + int speed = DEFAULT_POINTER_SPEED; + try { + speed = Settings.System.getInt(mContext.getContentResolver(), + Settings.System.POINTER_SPEED); + } catch (SettingNotFoundException snfe) { + } + return speed; + } + + /** + * Sets the mouse pointer speed. + * <p> + * Requires {@link android.Manifest.permissions.WRITE_SETTINGS}. + * </p> + * + * @param speed The pointer speed as a value between {@link #MIN_POINTER_SPEED} and + * {@link #MAX_POINTER_SPEED}, or the default value {@link #DEFAULT_POINTER_SPEED}. + * + * @hide + */ + public void setPointerSpeed(int speed) { + if (speed < MIN_POINTER_SPEED || speed > MAX_POINTER_SPEED) { + throw new IllegalArgumentException("speed out of range"); + } + + Settings.System.putInt(mContext.getContentResolver(), + Settings.System.POINTER_SPEED, speed); + } + + /** + * Changes the mouse pointer speed temporarily, but does not save the setting. + * <p> + * Requires {@link android.Manifest.permission.SET_POINTER_SPEED}. + * </p> + * + * @param speed The pointer speed as a value between {@link #MIN_POINTER_SPEED} and + * {@link #MAX_POINTER_SPEED}, or the default value {@link #DEFAULT_POINTER_SPEED}. + * + * @hide + */ + public void tryPointerSpeed(int speed) { + if (speed < MIN_POINTER_SPEED || speed > MAX_POINTER_SPEED) { + throw new IllegalArgumentException("speed out of range"); + } + + try { + sIm.tryPointerSpeed(speed); + } catch (RemoteException ex) { + Log.w(TAG, "Could not set temporary pointer speed.", ex); + } + } + + /** + * Gets information about the input device with the specified id. + * @param id The device id. + * @return The input device or null if not found. + * + * @hide + */ + public static InputDevice getInputDevice(int id) { + try { + return sIm.getInputDevice(id); + } catch (RemoteException ex) { + throw new RuntimeException("Could not get input device information.", ex); + } + } + + /** + * Gets the ids of all input devices in the system. + * @return The input device ids. + * + * @hide + */ + public static int[] getInputDeviceIds() { + try { + return sIm.getInputDeviceIds(); + } catch (RemoteException ex) { + throw new RuntimeException("Could not get input device ids.", ex); + } + } + + /** + * Queries the framework about whether any physical keys exist on the + * any keyboard attached to the device that are capable of producing the given + * array of key codes. + * + * @param keyCodes The array of key codes to query. + * @return A new array of the same size as the key codes array whose elements + * are set to true if at least one attached keyboard supports the corresponding key code + * at the same index in the key codes array. + * + * @hide + */ + public static boolean[] deviceHasKeys(int[] keyCodes) { + boolean[] ret = new boolean[keyCodes.length]; + try { + sIm.hasKeys(-1, InputDevice.SOURCE_ANY, keyCodes, ret); + } catch (RemoteException e) { + // no fallback; just return the empty array + } + return ret; + } + + /** + * Injects an input event into the event system on behalf of an application. + * The synchronization mode determines whether the method blocks while waiting for + * input injection to proceed. + * <p> + * Requires {@link android.Manifest.permission.INJECT_EVENTS} to inject into + * windows that are owned by other applications. + * </p><p> + * Make sure you correctly set the event time and input source of the event + * before calling this method. + * </p> + * + * @param event The event to inject. + * @param mode The synchronization mode. One of: + * {@link #INJECT_INPUT_EVENT_MODE_ASYNC}, + * {@link #INJECT_INPUT_EVENT_MODE_WAIT_FOR_RESULT}, or + * {@link #INJECT_INPUT_EVENT_MODE_WAIT_FOR_FINISH}. + * @return True if input event injection succeeded. + * + * @hide + */ + public static boolean injectInputEvent(InputEvent event, int mode) { + if (event == null) { + throw new IllegalArgumentException("event must not be null"); + } + if (mode != INJECT_INPUT_EVENT_MODE_ASYNC + && mode != INJECT_INPUT_EVENT_MODE_WAIT_FOR_FINISH + && mode != INJECT_INPUT_EVENT_MODE_WAIT_FOR_RESULT) { + throw new IllegalArgumentException("mode is invalid"); + } + + try { + return sIm.injectInputEvent(event, mode); + } catch (RemoteException ex) { + return false; + } + } + + private static String makeKeyboardLayoutDescriptor(String packageName, + String receiverName, String keyboardName) { + return packageName + "/" + receiverName + "/" + keyboardName; + } + + private static KeyboardLayoutDescriptor parseKeyboardLayoutDescriptor(String descriptor) { + int pos = descriptor.indexOf('/'); + if (pos < 0 || pos + 1 == descriptor.length()) { + return null; + } + int pos2 = descriptor.indexOf('/', pos + 1); + if (pos2 < pos + 2 || pos2 + 1 == descriptor.length()) { + return null; + } + + KeyboardLayoutDescriptor result = new KeyboardLayoutDescriptor(); + result.packageName = descriptor.substring(0, pos); + result.receiverName = descriptor.substring(pos + 1, pos2); + result.keyboardLayoutName = descriptor.substring(pos2 + 1); + return result; + } + + /** + * Describes a keyboard layout. + * + * @hide + */ + public static final class KeyboardLayout implements Parcelable, + Comparable<KeyboardLayout> { + private final String mDescriptor; + private final String mLabel; + private final int mKeyCharacterMapResId; + + private KeyCharacterMap mKeyCharacterMap; + + public static final Parcelable.Creator<KeyboardLayout> CREATOR = + new Parcelable.Creator<KeyboardLayout>() { + public KeyboardLayout createFromParcel(Parcel source) { + return new KeyboardLayout(source); + } + public KeyboardLayout[] newArray(int size) { + return new KeyboardLayout[size]; + } + }; + + private KeyboardLayout(String descriptor, + String label, int keyCharacterMapResId) { + mDescriptor = descriptor; + mLabel = label; + mKeyCharacterMapResId = keyCharacterMapResId; + } + + private KeyboardLayout(Parcel source) { + mDescriptor = source.readString(); + mLabel = source.readString(); + mKeyCharacterMapResId = source.readInt(); + } + + /** + * Gets the keyboard layout descriptor, which can be used to retrieve + * the keyboard layout again later using + * {@link InputManager#getKeyboardLayout(String)}. + * + * @return The keyboard layout descriptor. + */ + public String getDescriptor() { + return mDescriptor; + } + + /** + * Gets the keyboard layout descriptive label to show in the user interface. + * @return The keyboard layout descriptive label. + */ + public String getLabel() { + return mLabel; + } + + /** + * Loads the key character map associated with the keyboard layout. + * + * @param pm The package manager. + * @return The key character map, or null if it could not be loaded for any reason. + */ + public KeyCharacterMap loadKeyCharacterMap(PackageManager pm) { + if (pm == null) { + throw new IllegalArgumentException("pm must not be null"); + } + + if (mKeyCharacterMap == null) { + KeyboardLayoutDescriptor d = parseKeyboardLayoutDescriptor(mDescriptor); + if (d == null) { + Log.e(TAG, "Could not load key character map '" + mDescriptor + + "' because the descriptor could not be parsed."); + return null; + } + + CharSequence cs = pm.getText(d.packageName, mKeyCharacterMapResId, null); + if (cs == null) { + Log.e(TAG, "Could not load key character map '" + mDescriptor + + "' because its associated resource could not be loaded."); + return null; + } + + try { + mKeyCharacterMap = KeyCharacterMap.load(cs); + } catch (UnavailableException ex) { + Log.e(TAG, "Could not load key character map '" + mDescriptor + + "' due to an error while parsing.", ex); + return null; + } + } + return mKeyCharacterMap; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(mDescriptor); + dest.writeString(mLabel); + dest.writeInt(mKeyCharacterMapResId); + } + + @Override + public int compareTo(KeyboardLayout another) { + return mLabel.compareToIgnoreCase(another.mLabel); + } + + @Override + public String toString() { + return mLabel; + } + } + + private static final class KeyboardLayoutDescriptor { + public String packageName; + public String receiverName; + public String keyboardLayoutName; + } +} diff --git a/core/java/android/hardware/usb/UsbManager.java b/core/java/android/hardware/usb/UsbManager.java index 93f93c7..c40504a 100644 --- a/core/java/android/hardware/usb/UsbManager.java +++ b/core/java/android/hardware/usb/UsbManager.java @@ -66,6 +66,8 @@ public class UsbManager { * PTP function is enabled * <li> {@link #USB_FUNCTION_PTP} boolean extra indicating whether the * accessory function is enabled + * <li> {@link #USB_FUNCTION_AUDIO_SOURCE} boolean extra indicating whether the + * audio source function is enabled * </ul> * * {@hide} @@ -178,6 +180,14 @@ public class UsbManager { public static final String USB_FUNCTION_PTP = "ptp"; /** + * Name of the audio source USB function. + * Used in extras for the {@link #ACTION_USB_STATE} broadcast + * + * {@hide} + */ + public static final String USB_FUNCTION_AUDIO_SOURCE = "audio_source"; + + /** * Name of the Accessory USB function. * Used in extras for the {@link #ACTION_USB_STATE} broadcast * diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java index 2eef8f4..de16985 100644 --- a/core/java/android/net/ConnectivityManager.java +++ b/core/java/android/net/ConnectivityManager.java @@ -20,6 +20,7 @@ import static com.android.internal.util.Preconditions.checkNotNull; import android.annotation.SdkConstant; import android.annotation.SdkConstant.SdkConstantType; +import android.content.Context; import android.os.Binder; import android.os.Build.VERSION_CODES; import android.os.RemoteException; @@ -610,6 +611,11 @@ public class ConnectivityManager { mService = checkNotNull(service, "missing IConnectivityManager"); } + /** {@hide} */ + public static ConnectivityManager from(Context context) { + return (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); + } + /** * {@hide} */ diff --git a/core/java/android/net/INetworkStatsService.aidl b/core/java/android/net/INetworkStatsService.aidl index 0e883cf..b4f6367 100644 --- a/core/java/android/net/INetworkStatsService.aidl +++ b/core/java/android/net/INetworkStatsService.aidl @@ -16,6 +16,7 @@ package android.net; +import android.net.INetworkStatsSession; import android.net.NetworkStats; import android.net.NetworkStatsHistory; import android.net.NetworkTemplate; @@ -23,15 +24,11 @@ import android.net.NetworkTemplate; /** {@hide} */ interface INetworkStatsService { - /** Return historical network layer stats for traffic that matches template. */ - NetworkStatsHistory getHistoryForNetwork(in NetworkTemplate template, int fields); - /** Return historical network layer stats for specific UID traffic that matches template. */ - NetworkStatsHistory getHistoryForUid(in NetworkTemplate template, int uid, int set, int tag, int fields); + /** Start a statistics query session. */ + INetworkStatsSession openSession(); - /** Return network layer usage summary for traffic that matches template. */ - NetworkStats getSummaryForNetwork(in NetworkTemplate template, long start, long end); - /** Return network layer usage summary per UID for traffic that matches template. */ - NetworkStats getSummaryForAllUid(in NetworkTemplate template, long start, long end, boolean includeTags); + /** Return network layer usage total for traffic that matches template. */ + long getNetworkTotalBytes(in NetworkTemplate template, long start, long end); /** Return data layer snapshot of UID network usage. */ NetworkStats getDataLayerSnapshotForUid(int uid); diff --git a/core/java/android/net/INetworkStatsSession.aidl b/core/java/android/net/INetworkStatsSession.aidl new file mode 100644 index 0000000..1596fa2 --- /dev/null +++ b/core/java/android/net/INetworkStatsSession.aidl @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2012 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.net; + +import android.net.NetworkStats; +import android.net.NetworkStatsHistory; +import android.net.NetworkTemplate; + +/** {@hide} */ +interface INetworkStatsSession { + + /** Return network layer usage summary for traffic that matches template. */ + NetworkStats getSummaryForNetwork(in NetworkTemplate template, long start, long end); + /** Return historical network layer stats for traffic that matches template. */ + NetworkStatsHistory getHistoryForNetwork(in NetworkTemplate template, int fields); + + /** Return network layer usage summary per UID for traffic that matches template. */ + NetworkStats getSummaryForAllUid(in NetworkTemplate template, long start, long end, boolean includeTags); + /** Return historical network layer stats for specific UID traffic that matches template. */ + NetworkStatsHistory getHistoryForUid(in NetworkTemplate template, int uid, int set, int tag, int fields); + + void close(); + +} diff --git a/core/java/android/net/NetworkIdentity.java b/core/java/android/net/NetworkIdentity.java index ee12989..4ac5e76 100644 --- a/core/java/android/net/NetworkIdentity.java +++ b/core/java/android/net/NetworkIdentity.java @@ -16,9 +16,13 @@ package android.net; +import static android.net.ConnectivityManager.TYPE_WIFI; +import static android.net.ConnectivityManager.getNetworkTypeName; import static android.net.ConnectivityManager.isNetworkTypeMobile; import android.content.Context; +import android.net.wifi.WifiInfo; +import android.net.wifi.WifiManager; import android.os.Build; import android.telephony.TelephonyManager; @@ -42,18 +46,21 @@ public class NetworkIdentity { final int mType; final int mSubType; final String mSubscriberId; + final String mNetworkId; final boolean mRoaming; - public NetworkIdentity(int type, int subType, String subscriberId, boolean roaming) { - this.mType = type; - this.mSubType = COMBINE_SUBTYPE_ENABLED ? SUBTYPE_COMBINED : subType; - this.mSubscriberId = subscriberId; - this.mRoaming = roaming; + public NetworkIdentity( + int type, int subType, String subscriberId, String networkId, boolean roaming) { + mType = type; + mSubType = COMBINE_SUBTYPE_ENABLED ? SUBTYPE_COMBINED : subType; + mSubscriberId = subscriberId; + mNetworkId = networkId; + mRoaming = roaming; } @Override public int hashCode() { - return Objects.hashCode(mType, mSubType, mSubscriberId, mRoaming); + return Objects.hashCode(mType, mSubType, mSubscriberId, mNetworkId, mRoaming); } @Override @@ -61,27 +68,34 @@ public class NetworkIdentity { if (obj instanceof NetworkIdentity) { final NetworkIdentity ident = (NetworkIdentity) obj; return mType == ident.mType && mSubType == ident.mSubType && mRoaming == ident.mRoaming - && Objects.equal(mSubscriberId, ident.mSubscriberId); + && Objects.equal(mSubscriberId, ident.mSubscriberId) + && Objects.equal(mNetworkId, ident.mNetworkId); } return false; } @Override public String toString() { - final String typeName = ConnectivityManager.getNetworkTypeName(mType); - final String subTypeName; + final StringBuilder builder = new StringBuilder("["); + builder.append("type=").append(getNetworkTypeName(mType)); + builder.append(", subType="); if (COMBINE_SUBTYPE_ENABLED) { - subTypeName = "COMBINED"; + builder.append("COMBINED"); } else if (ConnectivityManager.isNetworkTypeMobile(mType)) { - subTypeName = TelephonyManager.getNetworkTypeName(mSubType); + builder.append(TelephonyManager.getNetworkTypeName(mSubType)); } else { - subTypeName = Integer.toString(mSubType); + builder.append(mSubType); } - - final String scrubSubscriberId = scrubSubscriberId(mSubscriberId); - final String roaming = mRoaming ? ", ROAMING" : ""; - return "[type=" + typeName + ", subType=" + subTypeName + ", subscriberId=" - + scrubSubscriberId + roaming + "]"; + if (mSubscriberId != null) { + builder.append(", subscriberId=").append(scrubSubscriberId(mSubscriberId)); + } + if (mNetworkId != null) { + builder.append(", networkId=").append(mNetworkId); + } + if (mRoaming) { + builder.append(", ROAMING"); + } + return builder.append("]").toString(); } public int getType() { @@ -96,6 +110,10 @@ public class NetworkIdentity { return mSubscriberId; } + public String getNetworkId() { + return mNetworkId; + } + public boolean getRoaming() { return mRoaming; } @@ -106,8 +124,11 @@ public class NetworkIdentity { public static String scrubSubscriberId(String subscriberId) { if ("eng".equals(Build.TYPE)) { return subscriberId; + } else if (subscriberId != null) { + // TODO: parse this as MCC+MNC instead of hard-coding + return subscriberId.substring(0, Math.min(6, subscriberId.length())) + "..."; } else { - return subscriberId != null ? "valid" : "null"; + return "null"; } } @@ -122,8 +143,10 @@ public class NetworkIdentity { // TODO: consider moving subscriberId over to LinkCapabilities, so it // comes from an authoritative source. - final String subscriberId; - final boolean roaming; + String subscriberId = null; + String networkId = null; + boolean roaming = false; + if (isNetworkTypeMobile(type)) { final TelephonyManager telephony = (TelephonyManager) context.getSystemService( Context.TELEPHONY_SERVICE); @@ -133,10 +156,13 @@ public class NetworkIdentity { } else { subscriberId = telephony.getSubscriberId(); } - } else { - subscriberId = null; - roaming = false; + + } else if (type == TYPE_WIFI) { + final WifiManager wifi = (WifiManager) context.getSystemService(Context.WIFI_SERVICE); + final WifiInfo info = wifi.getConnectionInfo(); + networkId = info != null ? info.getSSID() : null; } - return new NetworkIdentity(type, subType, subscriberId, roaming); + + return new NetworkIdentity(type, subType, subscriberId, networkId, roaming); } } diff --git a/core/java/android/net/NetworkPolicy.java b/core/java/android/net/NetworkPolicy.java index c1f58a3..441db7a 100644 --- a/core/java/android/net/NetworkPolicy.java +++ b/core/java/android/net/NetworkPolicy.java @@ -30,6 +30,7 @@ import com.android.internal.util.Objects; * @hide */ public class NetworkPolicy implements Parcelable, Comparable<NetworkPolicy> { + public static final int CYCLE_NONE = -1; public static final long WARNING_DISABLED = -1; public static final long LIMIT_DISABLED = -1; public static final long SNOOZE_NEVER = -1; @@ -123,6 +124,13 @@ public class NetworkPolicy implements Parcelable, Comparable<NetworkPolicy> { lastLimitSnooze = SNOOZE_NEVER; } + /** + * Test if this policy has a cycle defined, after which usage should reset. + */ + public boolean hasCycle() { + return cycleDay != CYCLE_NONE; + } + @Override public int compareTo(NetworkPolicy another) { if (another == null || another.limitBytes == LIMIT_DISABLED) { @@ -159,10 +167,17 @@ public class NetworkPolicy implements Parcelable, Comparable<NetworkPolicy> { @Override public String toString() { - return "NetworkPolicy[" + template + "]: cycleDay=" + cycleDay + ", cycleTimezone=" - + cycleTimezone + ", warningBytes=" + warningBytes + ", limitBytes=" + limitBytes - + ", lastWarningSnooze=" + lastWarningSnooze + ", lastLimitSnooze=" - + lastLimitSnooze + ", metered=" + metered + ", inferred=" + inferred; + final StringBuilder builder = new StringBuilder("NetworkPolicy"); + builder.append("[").append(template).append("]:"); + builder.append(" cycleDay=").append(cycleDay); + builder.append(", cycleTimezone=").append(cycleTimezone); + builder.append(", warningBytes=").append(warningBytes); + builder.append(", limitBytes=").append(limitBytes); + builder.append(", lastWarningSnooze=").append(lastWarningSnooze); + builder.append(", lastLimitSnooze=").append(lastLimitSnooze); + builder.append(", metered=").append(metered); + builder.append(", inferred=").append(inferred); + return builder.toString(); } public static final Creator<NetworkPolicy> CREATOR = new Creator<NetworkPolicy>() { diff --git a/core/java/android/net/NetworkPolicyManager.java b/core/java/android/net/NetworkPolicyManager.java index c09c676..2b36131 100644 --- a/core/java/android/net/NetworkPolicyManager.java +++ b/core/java/android/net/NetworkPolicyManager.java @@ -17,6 +17,7 @@ package android.net; import static android.content.pm.PackageManager.GET_SIGNATURES; +import static android.net.NetworkPolicy.CYCLE_NONE; import static android.text.format.Time.MONTH_DAY; import android.content.Context; @@ -66,27 +67,10 @@ public class NetworkPolicyManager { mService = service; } - public static NetworkPolicyManager getSystemService(Context context) { + public static NetworkPolicyManager from(Context context) { return (NetworkPolicyManager) context.getSystemService(Context.NETWORK_POLICY_SERVICE); } - /** {@hide} */ - public void setNetworkPolicies(NetworkPolicy[] policies) { - try { - mService.setNetworkPolicies(policies); - } catch (RemoteException e) { - } - } - - /** {@hide} */ - public NetworkPolicy[] getNetworkPolicies() { - try { - return mService.getNetworkPolicies(); - } catch (RemoteException e) { - return null; - } - } - /** * Set policy flags for specific application. * @@ -122,6 +106,36 @@ public class NetworkPolicyManager { } } + public void setNetworkPolicies(NetworkPolicy[] policies) { + try { + mService.setNetworkPolicies(policies); + } catch (RemoteException e) { + } + } + + public NetworkPolicy[] getNetworkPolicies() { + try { + return mService.getNetworkPolicies(); + } catch (RemoteException e) { + return null; + } + } + + public void setRestrictBackground(boolean restrictBackground) { + try { + mService.setRestrictBackground(restrictBackground); + } catch (RemoteException e) { + } + } + + public boolean getRestrictBackground() { + try { + return mService.getRestrictBackground(); + } catch (RemoteException e) { + return false; + } + } + /** * Compute the last cycle boundary for the given {@link NetworkPolicy}. For * example, if cycle day is 20th, and today is June 15th, it will return May @@ -131,6 +145,10 @@ public class NetworkPolicyManager { * @hide */ public static long computeLastCycleBoundary(long currentTime, NetworkPolicy policy) { + if (policy.cycleDay == CYCLE_NONE) { + throw new IllegalArgumentException("Unable to compute boundary without cycleDay"); + } + final Time now = new Time(policy.cycleTimezone); now.set(currentTime); @@ -157,6 +175,10 @@ public class NetworkPolicyManager { /** {@hide} */ public static long computeNextCycleBoundary(long currentTime, NetworkPolicy policy) { + if (policy.cycleDay == CYCLE_NONE) { + throw new IllegalArgumentException("Unable to compute boundary without cycleDay"); + } + final Time now = new Time(policy.cycleTimezone); now.set(currentTime); diff --git a/core/java/android/net/NetworkQuotaInfo.java b/core/java/android/net/NetworkQuotaInfo.java index 6535256..1725ed7 100644 --- a/core/java/android/net/NetworkQuotaInfo.java +++ b/core/java/android/net/NetworkQuotaInfo.java @@ -57,12 +57,12 @@ public class NetworkQuotaInfo implements Parcelable { return mHardLimitBytes; } - /** {@inheritDoc} */ + @Override public int describeContents() { return 0; } - /** {@inheritDoc} */ + @Override public void writeToParcel(Parcel out, int flags) { out.writeLong(mEstimatedBytes); out.writeLong(mSoftLimitBytes); @@ -70,10 +70,12 @@ public class NetworkQuotaInfo implements Parcelable { } public static final Creator<NetworkQuotaInfo> CREATOR = new Creator<NetworkQuotaInfo>() { + @Override public NetworkQuotaInfo createFromParcel(Parcel in) { return new NetworkQuotaInfo(in); } + @Override public NetworkQuotaInfo[] newArray(int size) { return new NetworkQuotaInfo[size]; } diff --git a/core/java/android/net/NetworkState.java b/core/java/android/net/NetworkState.java index 704111b..2fc69ad 100644 --- a/core/java/android/net/NetworkState.java +++ b/core/java/android/net/NetworkState.java @@ -52,12 +52,12 @@ public class NetworkState implements Parcelable { subscriberId = in.readString(); } - /** {@inheritDoc} */ + @Override public int describeContents() { return 0; } - /** {@inheritDoc} */ + @Override public void writeToParcel(Parcel out, int flags) { out.writeParcelable(networkInfo, flags); out.writeParcelable(linkProperties, flags); @@ -66,10 +66,12 @@ public class NetworkState implements Parcelable { } public static final Creator<NetworkState> CREATOR = new Creator<NetworkState>() { + @Override public NetworkState createFromParcel(Parcel in) { return new NetworkState(in); } + @Override public NetworkState[] newArray(int size) { return new NetworkState[size]; } diff --git a/core/java/android/net/NetworkStats.java b/core/java/android/net/NetworkStats.java index 7a1ef66..844d055 100644 --- a/core/java/android/net/NetworkStats.java +++ b/core/java/android/net/NetworkStats.java @@ -155,7 +155,7 @@ public class NetworkStats implements Parcelable { operations = parcel.createLongArray(); } - /** {@inheritDoc} */ + @Override public void writeToParcel(Parcel dest, int flags) { dest.writeLong(elapsedRealtime); dest.writeInt(size); @@ -352,10 +352,9 @@ public class NetworkStats implements Parcelable { * on matching {@link #uid} and {@link #tag} rows. Ignores {@link #iface}, * since operation counts are at data layer. */ - @Deprecated public void spliceOperationsFrom(NetworkStats stats) { for (int i = 0; i < size; i++) { - final int j = stats.findIndex(IFACE_ALL, uid[i], set[i], tag[i]); + final int j = stats.findIndex(iface[i], uid[i], set[i], tag[i]); if (j == -1) { operations[i] = 0; } else { @@ -663,16 +662,18 @@ public class NetworkStats implements Parcelable { return writer.toString(); } - /** {@inheritDoc} */ + @Override public int describeContents() { return 0; } public static final Creator<NetworkStats> CREATOR = new Creator<NetworkStats>() { + @Override public NetworkStats createFromParcel(Parcel in) { return new NetworkStats(in); } + @Override public NetworkStats[] newArray(int size) { return new NetworkStats[size]; } diff --git a/core/java/android/net/NetworkStatsHistory.java b/core/java/android/net/NetworkStatsHistory.java index faf8a3f..0003c6e 100644 --- a/core/java/android/net/NetworkStatsHistory.java +++ b/core/java/android/net/NetworkStatsHistory.java @@ -130,7 +130,7 @@ public class NetworkStatsHistory implements Parcelable { totalBytes = in.readLong(); } - /** {@inheritDoc} */ + @Override public void writeToParcel(Parcel out, int flags) { out.writeLong(bucketDuration); writeLongArray(out, bucketStart, bucketCount); @@ -191,7 +191,7 @@ public class NetworkStatsHistory implements Parcelable { writeVarLongArray(out, operations, bucketCount); } - /** {@inheritDoc} */ + @Override public int describeContents() { return 0; } @@ -586,10 +586,12 @@ public class NetworkStatsHistory implements Parcelable { } public static final Creator<NetworkStatsHistory> CREATOR = new Creator<NetworkStatsHistory>() { + @Override public NetworkStatsHistory createFromParcel(Parcel in) { return new NetworkStatsHistory(in); } + @Override public NetworkStatsHistory[] newArray(int size) { return new NetworkStatsHistory[size]; } diff --git a/core/java/android/net/NetworkTemplate.java b/core/java/android/net/NetworkTemplate.java index e1fbdcc..50432a1 100644 --- a/core/java/android/net/NetworkTemplate.java +++ b/core/java/android/net/NetworkTemplate.java @@ -43,15 +43,10 @@ import com.android.internal.util.Objects; */ public class NetworkTemplate implements Parcelable { - /** {@hide} */ public static final int MATCH_MOBILE_ALL = 1; - /** {@hide} */ public static final int MATCH_MOBILE_3G_LOWER = 2; - /** {@hide} */ public static final int MATCH_MOBILE_4G = 3; - /** {@hide} */ public static final int MATCH_WIFI = 4; - /** {@hide} */ public static final int MATCH_ETHERNET = 5; /** @@ -65,37 +60,50 @@ public class NetworkTemplate implements Parcelable { } /** - * Template to combine all {@link ConnectivityManager#TYPE_MOBILE} style - * networks together. Only uses statistics for requested IMSI. + * Template to match {@link ConnectivityManager#TYPE_MOBILE} networks with + * the given IMSI. */ public static NetworkTemplate buildTemplateMobileAll(String subscriberId) { - return new NetworkTemplate(MATCH_MOBILE_ALL, subscriberId); + return new NetworkTemplate(MATCH_MOBILE_ALL, subscriberId, null); } /** - * Template to combine all {@link ConnectivityManager#TYPE_MOBILE} style - * networks together that roughly meet a "3G" definition, or lower. Only - * uses statistics for requested IMSI. + * Template to match {@link ConnectivityManager#TYPE_MOBILE} networks with + * the given IMSI that roughly meet a "3G" definition, or lower. */ + @Deprecated public static NetworkTemplate buildTemplateMobile3gLower(String subscriberId) { - return new NetworkTemplate(MATCH_MOBILE_3G_LOWER, subscriberId); + return new NetworkTemplate(MATCH_MOBILE_3G_LOWER, subscriberId, null); } /** - * Template to combine all {@link ConnectivityManager#TYPE_MOBILE} style - * networks together that meet a "4G" definition. Only uses statistics for - * requested IMSI. + * Template to match {@link ConnectivityManager#TYPE_MOBILE} networks with + * the given IMSI that roughly meet a "4G" definition. */ + @Deprecated public static NetworkTemplate buildTemplateMobile4g(String subscriberId) { - return new NetworkTemplate(MATCH_MOBILE_4G, subscriberId); + return new NetworkTemplate(MATCH_MOBILE_4G, subscriberId, null); } /** - * Template to combine all {@link ConnectivityManager#TYPE_WIFI} style - * networks together. + * Template to match all {@link ConnectivityManager#TYPE_WIFI} networks, + * regardless of SSID. */ + public static NetworkTemplate buildTemplateWifiWildcard() { + return new NetworkTemplate(MATCH_WIFI, null, null); + } + + @Deprecated public static NetworkTemplate buildTemplateWifi() { - return new NetworkTemplate(MATCH_WIFI, null); + return buildTemplateWifiWildcard(); + } + + /** + * Template to match {@link ConnectivityManager#TYPE_WIFI} networks with the + * given SSID. + */ + public static NetworkTemplate buildTemplateWifi(String networkId) { + return new NetworkTemplate(MATCH_WIFI, null, networkId); } /** @@ -103,44 +111,53 @@ public class NetworkTemplate implements Parcelable { * networks together. */ public static NetworkTemplate buildTemplateEthernet() { - return new NetworkTemplate(MATCH_ETHERNET, null); + return new NetworkTemplate(MATCH_ETHERNET, null, null); } private final int mMatchRule; private final String mSubscriberId; + private final String mNetworkId; - /** {@hide} */ - public NetworkTemplate(int matchRule, String subscriberId) { - this.mMatchRule = matchRule; - this.mSubscriberId = subscriberId; + public NetworkTemplate(int matchRule, String subscriberId, String networkId) { + mMatchRule = matchRule; + mSubscriberId = subscriberId; + mNetworkId = networkId; } private NetworkTemplate(Parcel in) { mMatchRule = in.readInt(); mSubscriberId = in.readString(); + mNetworkId = in.readString(); } - /** {@inheritDoc} */ + @Override public void writeToParcel(Parcel dest, int flags) { dest.writeInt(mMatchRule); dest.writeString(mSubscriberId); + dest.writeString(mNetworkId); } - /** {@inheritDoc} */ + @Override public int describeContents() { return 0; } @Override public String toString() { - final String scrubSubscriberId = scrubSubscriberId(mSubscriberId); - return "NetworkTemplate: matchRule=" + getMatchRuleName(mMatchRule) + ", subscriberId=" - + scrubSubscriberId; + final StringBuilder builder = new StringBuilder("NetworkTemplate: "); + builder.append("matchRule=").append(getMatchRuleName(mMatchRule)); + if (mSubscriberId != null) { + builder.append(", subscriberId=").append(scrubSubscriberId(mSubscriberId)); + } + if (mNetworkId != null) { + builder.append(", networkId=").append(mNetworkId); + } + return builder.toString(); } @Override public int hashCode() { - return Objects.hashCode(mMatchRule, mSubscriberId); + return Objects.hashCode(mMatchRule, mSubscriberId, mNetworkId); } @Override @@ -148,21 +165,24 @@ public class NetworkTemplate implements Parcelable { if (obj instanceof NetworkTemplate) { final NetworkTemplate other = (NetworkTemplate) obj; return mMatchRule == other.mMatchRule - && Objects.equal(mSubscriberId, other.mSubscriberId); + && Objects.equal(mSubscriberId, other.mSubscriberId) + && Objects.equal(mNetworkId, other.mNetworkId); } return false; } - /** {@hide} */ public int getMatchRule() { return mMatchRule; } - /** {@hide} */ public String getSubscriberId() { return mSubscriberId; } + public String getNetworkId() { + return mNetworkId; + } + /** * Test if given {@link NetworkIdentity} matches this template. */ @@ -237,8 +257,13 @@ public class NetworkTemplate implements Parcelable { private boolean matchesWifi(NetworkIdentity ident) { switch (ident.mType) { case TYPE_WIFI: + if (mNetworkId == null) { + return true; + } else { + return Objects.equal(mNetworkId, ident.mNetworkId); + } case TYPE_WIFI_P2P: - return true; + return mNetworkId == null; default: return false; } @@ -279,10 +304,12 @@ public class NetworkTemplate implements Parcelable { } public static final Creator<NetworkTemplate> CREATOR = new Creator<NetworkTemplate>() { + @Override public NetworkTemplate createFromParcel(Parcel in) { return new NetworkTemplate(in); } + @Override public NetworkTemplate[] newArray(int size) { return new NetworkTemplate[size]; } diff --git a/core/java/android/net/TrafficStats.java b/core/java/android/net/TrafficStats.java index 973fac1..ee3e165 100644 --- a/core/java/android/net/TrafficStats.java +++ b/core/java/android/net/TrafficStats.java @@ -238,6 +238,19 @@ public class TrafficStats { } } + /** {@hide} */ + public static void closeQuietly(INetworkStatsSession session) { + // TODO: move to NetworkStatsService once it exists + if (session != null) { + try { + session.close(); + } catch (RuntimeException rethrown) { + throw rethrown; + } catch (Exception ignored) { + } + } + } + /** * Get the total number of packets transmitted through the mobile interface. * diff --git a/core/java/android/net/nsd/DnsSdServiceInfo.java b/core/java/android/net/nsd/DnsSdServiceInfo.java new file mode 100644 index 0000000..47d6ec6 --- /dev/null +++ b/core/java/android/net/nsd/DnsSdServiceInfo.java @@ -0,0 +1,136 @@ +/* + * Copyright (C) 2012 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.net.nsd; + +import android.os.Parcelable; +import android.os.Parcel; + +/** + * Defines a service based on DNS service discovery + * {@hide} + */ +public class DnsSdServiceInfo implements NetworkServiceInfo, Parcelable { + + private String mServiceName; + + private String mRegistrationType; + + private DnsSdTxtRecord mTxtRecord; + + private String mHostname; + + private int mPort; + + DnsSdServiceInfo() { + } + + DnsSdServiceInfo(String sn, String rt, DnsSdTxtRecord tr) { + mServiceName = sn; + mRegistrationType = rt; + mTxtRecord = tr; + } + + @Override + /** @hide */ + public String getServiceName() { + return mServiceName; + } + + @Override + /** @hide */ + public void setServiceName(String s) { + mServiceName = s; + } + + @Override + /** @hide */ + public String getServiceType() { + return mRegistrationType; + } + + @Override + /** @hide */ + public void setServiceType(String s) { + mRegistrationType = s; + } + + public DnsSdTxtRecord getTxtRecord() { + return mTxtRecord; + } + + public void setTxtRecord(DnsSdTxtRecord t) { + mTxtRecord = new DnsSdTxtRecord(t); + } + + public String getHostName() { + return mHostname; + } + + public void setHostName(String s) { + mHostname = s; + } + + public int getPort() { + return mPort; + } + + public void setPort(int p) { + mPort = p; + } + + public String toString() { + StringBuffer sb = new StringBuffer(); + + sb.append("name: ").append(mServiceName). + append("type: ").append(mRegistrationType). + append("txtRecord: ").append(mTxtRecord); + return sb.toString(); + } + + /** Implement the Parcelable interface */ + public int describeContents() { + return 0; + } + + /** Implement the Parcelable interface */ + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(mServiceName); + dest.writeString(mRegistrationType); + dest.writeParcelable(mTxtRecord, flags); + dest.writeString(mHostname); + dest.writeInt(mPort); + } + + /** Implement the Parcelable interface */ + public static final Creator<DnsSdServiceInfo> CREATOR = + new Creator<DnsSdServiceInfo>() { + public DnsSdServiceInfo createFromParcel(Parcel in) { + DnsSdServiceInfo info = new DnsSdServiceInfo(); + info.mServiceName = in.readString(); + info.mRegistrationType = in.readString(); + info.mTxtRecord = in.readParcelable(null); + info.mHostname = in.readString(); + info.mPort = in.readInt(); + return info; + } + + public DnsSdServiceInfo[] newArray(int size) { + return new DnsSdServiceInfo[size]; + } + }; + +} diff --git a/core/java/android/net/nsd/DnsSdTxtRecord.java b/core/java/android/net/nsd/DnsSdTxtRecord.java new file mode 100644 index 0000000..6d4342c --- /dev/null +++ b/core/java/android/net/nsd/DnsSdTxtRecord.java @@ -0,0 +1,305 @@ +/* -*- Mode: Java; tab-width: 4 -*- + * + * Copyright (c) 2004 Apple Computer, Inc. All rights reserved. + * + * 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. + + To do: + - implement remove() + - fix set() to replace existing values + */ + +package android.net.nsd; + +import android.os.Parcelable; +import android.os.Parcel; + +/** + * This class handles TXT record data for DNS based service discovery as specified at + * http://tools.ietf.org/html/draft-cheshire-dnsext-dns-sd-11 + * + * DNS-SD specifies that a TXT record corresponding to an SRV record consist of + * a packed array of bytes, each preceded by a length byte. Each string + * is an attribute-value pair. + * + * The DnsSdTxtRecord object stores the entire TXT data as a single byte array, traversing it + * as need be to implement its various methods. + * + * @hide + */ +public class DnsSdTxtRecord implements Parcelable { + private static final byte mSeperator = '='; + + private byte[] mData; + + /** Constructs a new, empty TXT record. */ + public DnsSdTxtRecord() { + mData = new byte[0]; + } + + /** Constructs a new TXT record from a byte array in the standard format. */ + public DnsSdTxtRecord(byte[] data) { + mData = (byte[]) data.clone(); + } + + /** Copy constructor */ + public DnsSdTxtRecord(DnsSdTxtRecord src) { + if (src != null && src.mData != null) { + mData = (byte[]) src.mData.clone(); + } + } + + /** + * Set a key/value pair. Setting an existing key will replace its value. + * @param key Must be ascii with no '=' + * @param value matching value to key + */ + public void set(String key, String value) { + byte[] keyBytes; + byte[] valBytes; + int valLen; + + if (value != null) { + valBytes = value.getBytes(); + valLen = valBytes.length; + } else { + valBytes = null; + valLen = 0; + } + + try { + keyBytes = key.getBytes("US-ASCII"); + } + catch (java.io.UnsupportedEncodingException e) { + throw new IllegalArgumentException("key should be US-ASCII"); + } + + for (int i = 0; i < keyBytes.length; i++) { + if (keyBytes[i] == '=') { + throw new IllegalArgumentException("= is not a valid character in key"); + } + } + + if (keyBytes.length + valLen >= 255) { + throw new IllegalArgumentException("Key and Value length cannot exceed 255 bytes"); + } + + int currentLoc = remove(key); + if (currentLoc == -1) + currentLoc = keyCount(); + + insert(keyBytes, valBytes, currentLoc); + } + + /** + * Get a value for a key + * + * @param key + * @return The value associated with the key + */ + public String get(String key) { + byte[] val = this.getValue(key); + return val != null ? new String(val) : null; + } + + /** Remove a key/value pair. If found, returns the index or -1 if not found */ + public int remove(String key) { + int avStart = 0; + + for (int i=0; avStart < mData.length; i++) { + int avLen = mData[avStart]; + if (key.length() <= avLen && + (key.length() == avLen || mData[avStart + key.length() + 1] == mSeperator)) { + String s = new String(mData, avStart + 1, key.length()); + if (0 == key.compareToIgnoreCase(s)) { + byte[] oldBytes = mData; + mData = new byte[oldBytes.length - avLen - 1]; + System.arraycopy(oldBytes, 0, mData, 0, avStart); + System.arraycopy(oldBytes, avStart + avLen + 1, mData, avStart, + oldBytes.length - avStart - avLen - 1); + return i; + } + } + avStart += (0xFF & (avLen + 1)); + } + return -1; + } + + /** Return the count of keys */ + public int keyCount() { + int count = 0, nextKey; + for (nextKey = 0; nextKey < mData.length; count++) { + nextKey += (0xFF & (mData[nextKey] + 1)); + } + return count; + } + + /** Return true if key is present, false if not. */ + public boolean contains(String key) { + String s = null; + for (int i = 0; null != (s = this.getKey(i)); i++) { + if (0 == key.compareToIgnoreCase(s)) return true; + } + return false; + } + + /* Gets the size in bytes */ + public int size() { + return mData.length; + } + + /* Gets the raw data in bytes */ + public byte[] getRawData() { + return mData; + } + + private void insert(byte[] keyBytes, byte[] value, int index) { + byte[] oldBytes = mData; + int valLen = (value != null) ? value.length : 0; + int insertion = 0; + int newLen, avLen; + + for (int i = 0; i < index && insertion < mData.length; i++) { + insertion += (0xFF & (mData[insertion] + 1)); + } + + avLen = keyBytes.length + valLen + (value != null ? 1 : 0); + newLen = avLen + oldBytes.length + 1; + + mData = new byte[newLen]; + System.arraycopy(oldBytes, 0, mData, 0, insertion); + int secondHalfLen = oldBytes.length - insertion; + System.arraycopy(oldBytes, insertion, mData, newLen - secondHalfLen, secondHalfLen); + mData[insertion] = (byte) avLen; + System.arraycopy(keyBytes, 0, mData, insertion + 1, keyBytes.length); + if (value != null) { + mData[insertion + 1 + keyBytes.length] = mSeperator; + System.arraycopy(value, 0, mData, insertion + keyBytes.length + 2, valLen); + } + } + + /** Return a key in the TXT record by zero-based index. Returns null if index exceeds the total number of keys. */ + private String getKey(int index) { + int avStart = 0; + + for (int i=0; i < index && avStart < mData.length; i++) { + avStart += mData[avStart] + 1; + } + + if (avStart < mData.length) { + int avLen = mData[avStart]; + int aLen = 0; + + for (aLen=0; aLen < avLen; aLen++) { + if (mData[avStart + aLen + 1] == mSeperator) break; + } + return new String(mData, avStart + 1, aLen); + } + return null; + } + + /** + * Look up a key in the TXT record by zero-based index and return its value. + * Returns null if index exceeds the total number of keys. + * Returns null if the key is present with no value. + */ + private byte[] getValue(int index) { + int avStart = 0; + byte[] value = null; + + for (int i=0; i < index && avStart < mData.length; i++) { + avStart += mData[avStart] + 1; + } + + if (avStart < mData.length) { + int avLen = mData[avStart]; + int aLen = 0; + + for (aLen=0; aLen < avLen; aLen++) { + if (mData[avStart + aLen + 1] == mSeperator) { + value = new byte[avLen - aLen - 1]; + System.arraycopy(mData, avStart + aLen + 2, value, 0, avLen - aLen - 1); + break; + } + } + } + return value; + } + + private String getValueAsString(int index) { + byte[] value = this.getValue(index); + return value != null ? new String(value) : null; + } + + private byte[] getValue(String forKey) { + String s = null; + int i; + + for (i = 0; null != (s = this.getKey(i)); i++) { + if (0 == forKey.compareToIgnoreCase(s)) { + return this.getValue(i); + } + } + + return null; + } + + /** + * Return a string representation. + * Example : {key1=value1},{key2=value2}.. + * + * For a key say like "key3" with null value + * {key1=value1},{key2=value2}{key3} + */ + public String toString() { + String a, result = null; + + for (int i = 0; null != (a = this.getKey(i)); i++) { + String av = "{" + a; + String val = this.getValueAsString(i); + if (val != null) + av += "=" + val + "}"; + else + av += "}"; + if (result == null) + result = av; + else + result = result + ", " + av; + } + return result != null ? result : ""; + } + + /** Implement the Parcelable interface */ + public int describeContents() { + return 0; + } + + /** Implement the Parcelable interface */ + public void writeToParcel(Parcel dest, int flags) { + dest.writeByteArray(mData); + } + + /** Implement the Parcelable interface */ + public static final Creator<DnsSdTxtRecord> CREATOR = + new Creator<DnsSdTxtRecord>() { + public DnsSdTxtRecord createFromParcel(Parcel in) { + DnsSdTxtRecord info = new DnsSdTxtRecord(); + in.readByteArray(info.mData); + return info; + } + + public DnsSdTxtRecord[] newArray(int size) { + return new DnsSdTxtRecord[size]; + } + }; +} diff --git a/core/java/android/net/nsd/INsdManager.aidl b/core/java/android/net/nsd/INsdManager.aidl new file mode 100644 index 0000000..077a675 --- /dev/null +++ b/core/java/android/net/nsd/INsdManager.aidl @@ -0,0 +1,29 @@ +/** + * Copyright (c) 2012, 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.net.nsd; + +import android.os.Messenger; + +/** + * Interface that NsdService implements + * + * {@hide} + */ +interface INsdManager +{ + Messenger getMessenger(); +} diff --git a/core/java/android/net/nsd/NetworkServiceInfo.java b/core/java/android/net/nsd/NetworkServiceInfo.java new file mode 100644 index 0000000..34d83d1 --- /dev/null +++ b/core/java/android/net/nsd/NetworkServiceInfo.java @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2012 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.net.nsd; + +/** + * Interface for a network service. + * + * {@hide} + */ +public interface NetworkServiceInfo { + + String getServiceName(); + void setServiceName(String s); + + String getServiceType(); + void setServiceType(String s); + +} diff --git a/core/java/android/net/nsd/NsdManager.java b/core/java/android/net/nsd/NsdManager.java new file mode 100644 index 0000000..a109a98 --- /dev/null +++ b/core/java/android/net/nsd/NsdManager.java @@ -0,0 +1,394 @@ +/* + * Copyright (C) 2012 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.net.nsd; + +import android.content.Context; +import android.os.Binder; +import android.os.IBinder; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import android.os.RemoteException; +import android.os.Messenger; +import android.util.Log; + +import com.android.internal.util.AsyncChannel; +import com.android.internal.util.Protocol; + +/** + * The Network Service Discovery Manager class provides the API for service + * discovery. Service discovery enables applications to discover and connect with services + * on a network. Example applications include a game application discovering another instance + * of the game application or a printer application discovering other printers on a network. + * + * <p> The API is asynchronous and responses to requests from an application are on listener + * callbacks provided by the application. The application needs to do an initialization with + * {@link #initialize} before doing any operation. + * + * <p> Android currently supports DNS based service discovery and it is limited to a local + * network with the use of multicast DNS. In future, this class will be + * extended to support other service discovery mechanisms. + * + * Get an instance of this class by calling {@link android.content.Context#getSystemService(String) + * Context.getSystemService(Context.NSD_SERVICE)}. + * @hide + * + */ +public class NsdManager { + private static final String TAG = "NsdManager"; + INsdManager mService; + + private static final int BASE = Protocol.BASE_NSD_MANAGER; + + /** @hide */ + public static final int DISCOVER_SERVICES = BASE + 1; + /** @hide */ + public static final int DISCOVER_SERVICES_STARTED = BASE + 2; + /** @hide */ + public static final int DISCOVER_SERVICES_FAILED = BASE + 3; + /** @hide */ + public static final int SERVICE_FOUND = BASE + 4; + /** @hide */ + public static final int SERVICE_LOST = BASE + 5; + + /** @hide */ + public static final int STOP_DISCOVERY = BASE + 6; + /** @hide */ + public static final int STOP_DISCOVERY_FAILED = BASE + 7; + /** @hide */ + public static final int STOP_DISCOVERY_SUCCEEDED = BASE + 8; + + /** @hide */ + public static final int REGISTER_SERVICE = BASE + 9; + /** @hide */ + public static final int REGISTER_SERVICE_FAILED = BASE + 10; + /** @hide */ + public static final int REGISTER_SERVICE_SUCCEEDED = BASE + 11; + + /** @hide */ + public static final int UPDATE_SERVICE = BASE + 12; + /** @hide */ + public static final int UPDATE_SERVICE_FAILED = BASE + 13; + /** @hide */ + public static final int UPDATE_SERVICE_SUCCEEDED = BASE + 14; + + /** @hide */ + public static final int RESOLVE_SERVICE = BASE + 15; + /** @hide */ + public static final int RESOLVE_SERVICE_FAILED = BASE + 16; + /** @hide */ + public static final int RESOLVE_SERVICE_SUCCEEDED = BASE + 17; + + /** + * Create a new Nsd instance. Applications use + * {@link android.content.Context#getSystemService Context.getSystemService()} to retrieve + * {@link android.content.Context#NSD_SERVICE Context.NSD_SERVICE}. + * @param service the Binder interface + * @hide - hide this because it takes in a parameter of type INsdManager, which + * is a system private class. + */ + public NsdManager(INsdManager service) { + mService = service; + } + + /** + * Indicates that the operation failed due to an internal error. + */ + public static final int ERROR = 0; + + /** + * Indicates that the operation failed because service discovery is unsupported on the device. + */ + public static final int UNSUPPORTED = 1; + + /** + * Indicates that the operation failed because the framework is busy and + * unable to service the request + */ + public static final int BUSY = 2; + + /** Interface for callback invocation when framework channel is connected or lost */ + public interface ChannelListener { + public void onChannelConnected(Channel c); + /** + * The channel to the framework has been disconnected. + * Application could try re-initializing using {@link #initialize} + */ + public void onChannelDisconnected(); + } + + public interface ActionListener { + + public void onFailure(int errorCode); + + public void onSuccess(); + } + + public interface DnsSdDiscoveryListener { + + public void onFailure(int errorCode); + + public void onStarted(String registrationType); + + public void onServiceFound(DnsSdServiceInfo serviceInfo); + + public void onServiceLost(DnsSdServiceInfo serviceInfo); + + } + + public interface DnsSdRegisterListener { + + public void onFailure(int errorCode); + + public void onServiceRegistered(int registeredId, DnsSdServiceInfo serviceInfo); + } + + public interface DnsSdUpdateRegistrationListener { + + public void onFailure(int errorCode); + + public void onServiceUpdated(int registeredId, DnsSdTxtRecord txtRecord); + } + + public interface DnsSdResolveListener { + + public void onFailure(int errorCode); + + public void onServiceResolved(DnsSdServiceInfo serviceInfo); + } + + /** + * A channel that connects the application to the NetworkService framework. + * Most service operations require a Channel as an argument. An instance of Channel is obtained + * by doing a call on {@link #initialize} + */ + public static class Channel { + Channel(Looper looper, ChannelListener l) { + mAsyncChannel = new AsyncChannel(); + mHandler = new ServiceHandler(looper); + mChannelListener = l; + } + private ChannelListener mChannelListener; + private DnsSdDiscoveryListener mDnsSdDiscoveryListener; + private ActionListener mDnsSdStopDiscoveryListener; + private DnsSdRegisterListener mDnsSdRegisterListener; + private DnsSdUpdateRegistrationListener mDnsSdUpdateListener; + private DnsSdResolveListener mDnsSdResolveListener; + + AsyncChannel mAsyncChannel; + ServiceHandler mHandler; + class ServiceHandler extends Handler { + ServiceHandler(Looper looper) { + super(looper); + } + + @Override + public void handleMessage(Message message) { + switch (message.what) { + case AsyncChannel.CMD_CHANNEL_HALF_CONNECTED: + mAsyncChannel.sendMessage(AsyncChannel.CMD_CHANNEL_FULL_CONNECTION); + break; + case AsyncChannel.CMD_CHANNEL_FULLY_CONNECTED: + if (mChannelListener != null) { + mChannelListener.onChannelConnected(Channel.this); + } + break; + case AsyncChannel.CMD_CHANNEL_DISCONNECTED: + if (mChannelListener != null) { + mChannelListener.onChannelDisconnected(); + mChannelListener = null; + } + break; + case DISCOVER_SERVICES_STARTED: + if (mDnsSdDiscoveryListener != null) { + mDnsSdDiscoveryListener.onStarted((String) message.obj); + } + break; + case DISCOVER_SERVICES_FAILED: + if (mDnsSdDiscoveryListener != null) { + mDnsSdDiscoveryListener.onFailure(message.arg1); + } + break; + case SERVICE_FOUND: + if (mDnsSdDiscoveryListener != null) { + mDnsSdDiscoveryListener.onServiceFound( + (DnsSdServiceInfo) message.obj); + } + break; + case SERVICE_LOST: + if (mDnsSdDiscoveryListener != null) { + mDnsSdDiscoveryListener.onServiceLost( + (DnsSdServiceInfo) message.obj); + } + break; + case STOP_DISCOVERY_FAILED: + if (mDnsSdStopDiscoveryListener != null) { + mDnsSdStopDiscoveryListener.onFailure(message.arg1); + } + break; + case STOP_DISCOVERY_SUCCEEDED: + if (mDnsSdStopDiscoveryListener != null) { + mDnsSdStopDiscoveryListener.onSuccess(); + } + break; + case REGISTER_SERVICE_FAILED: + if (mDnsSdRegisterListener != null) { + mDnsSdRegisterListener.onFailure(message.arg1); + } + break; + case REGISTER_SERVICE_SUCCEEDED: + if (mDnsSdRegisterListener != null) { + mDnsSdRegisterListener.onServiceRegistered(message.arg1, + (DnsSdServiceInfo) message.obj); + } + break; + case UPDATE_SERVICE_FAILED: + if (mDnsSdUpdateListener != null) { + mDnsSdUpdateListener.onFailure(message.arg1); + } + break; + case UPDATE_SERVICE_SUCCEEDED: + if (mDnsSdUpdateListener != null) { + mDnsSdUpdateListener.onServiceUpdated(message.arg1, + (DnsSdTxtRecord) message.obj); + } + break; + case RESOLVE_SERVICE_FAILED: + if (mDnsSdResolveListener != null) { + mDnsSdResolveListener.onFailure(message.arg1); + } + break; + case RESOLVE_SERVICE_SUCCEEDED: + if (mDnsSdResolveListener != null) { + mDnsSdResolveListener.onServiceResolved( + (DnsSdServiceInfo) message.obj); + } + break; + default: + Log.d(TAG, "Ignored " + message); + break; + } + } + } + } + + /** + * Registers the application with the service discovery framework. This function + * must be the first to be called before any other operations are performed. No service + * discovery operations must be performed until the ChannelListener callback notifies + * that the channel is connected + * + * @param srcContext is the context of the source + * @param srcLooper is the Looper on which the callbacks are receivied + * @param listener for callback at loss of framework communication. + */ + public void initialize(Context srcContext, Looper srcLooper, ChannelListener listener) { + Messenger messenger = getMessenger(); + if (messenger == null) throw new RuntimeException("Failed to initialize"); + if (listener == null) throw new IllegalArgumentException("ChannelListener cannot be null"); + + Channel c = new Channel(srcLooper, listener); + c.mAsyncChannel.connect(srcContext, c.mHandler, messenger); + } + + /** + * Set the listener for service discovery. Can be null. + */ + public void setDiscoveryListener(Channel c, DnsSdDiscoveryListener b) { + if (c == null) throw new IllegalArgumentException("Channel needs to be initialized"); + c.mDnsSdDiscoveryListener = b; + } + + /** + * Set the listener for stop service discovery. Can be null. + */ + public void setStopDiscoveryListener(Channel c, ActionListener a) { + if (c == null) throw new IllegalArgumentException("Channel needs to be initialized"); + c.mDnsSdStopDiscoveryListener = a; + } + + /** + * Set the listener for service registration. Can be null. + */ + public void setRegisterListener(Channel c, DnsSdRegisterListener b) { + if (c == null) throw new IllegalArgumentException("Channel needs to be initialized"); + c.mDnsSdRegisterListener = b; + } + + /** + * Set the listener for service registration. Can be null. + */ + public void setUpdateRegistrationListener(Channel c, DnsSdUpdateRegistrationListener b) { + if (c == null) throw new IllegalArgumentException("Channel needs to be initialized"); + c.mDnsSdUpdateListener = b; + } + + /** + * Set the listener for service resolution. Can be null. + */ + public void setResolveListener(Channel c, DnsSdResolveListener b) { + if (c == null) throw new IllegalArgumentException("Channel needs to be initialized"); + c.mDnsSdResolveListener = b; + } + + public void registerService(Channel c, DnsSdServiceInfo serviceInfo) { + if (c == null) throw new IllegalArgumentException("Channel needs to be initialized"); + if (serviceInfo == null) throw new IllegalArgumentException("Null serviceInfo"); + c.mAsyncChannel.sendMessage(REGISTER_SERVICE, serviceInfo); + } + + public void updateService(Channel c, int registeredId, DnsSdTxtRecord txtRecord) { + if (c == null) throw new IllegalArgumentException("Channel needs to be initialized"); + c.mAsyncChannel.sendMessage(UPDATE_SERVICE, registeredId, 0, txtRecord); + } + + public void discoverServices(Channel c, String serviceType) { + if (c == null) throw new IllegalArgumentException("Channel needs to be initialized"); + if (c.mDnsSdDiscoveryListener == null) throw new + IllegalStateException("Discovery listener needs to be set first"); + DnsSdServiceInfo s = new DnsSdServiceInfo(); + s.setServiceType(serviceType); + c.mAsyncChannel.sendMessage(DISCOVER_SERVICES, s); + } + + public void stopServiceDiscovery(Channel c) { + if (c == null) throw new IllegalArgumentException("Channel needs to be initialized"); + c.mAsyncChannel.sendMessage(STOP_DISCOVERY); + } + + public void resolveService(Channel c, DnsSdServiceInfo serviceInfo) { + if (c == null) throw new IllegalArgumentException("Channel needs to be initialized"); + if (serviceInfo == null) throw new IllegalArgumentException("Null serviceInfo"); + if (c.mDnsSdResolveListener == null) throw new + IllegalStateException("Resolve listener needs to be set first"); + c.mAsyncChannel.sendMessage(RESOLVE_SERVICE, serviceInfo); + } + + /** + * Get a reference to NetworkService handler. This is used to establish + * an AsyncChannel communication with the service + * + * @return Messenger pointing to the NetworkService handler + */ + private Messenger getMessenger() { + try { + return mService.getMessenger(); + } catch (RemoteException e) { + return null; + } + } +} diff --git a/core/java/android/nfc/INdefPushCallback.aidl b/core/java/android/nfc/INdefPushCallback.aidl index e60a5b0..4e79822 100644 --- a/core/java/android/nfc/INdefPushCallback.aidl +++ b/core/java/android/nfc/INdefPushCallback.aidl @@ -17,6 +17,7 @@ package android.nfc; import android.nfc.NdefMessage; +import android.net.Uri; /** * @hide @@ -24,5 +25,7 @@ import android.nfc.NdefMessage; interface INdefPushCallback { NdefMessage createMessage(); + Uri getUri(); + String getMimeType(); void onNdefPushComplete(); } diff --git a/core/java/android/nfc/INfcTag.aidl b/core/java/android/nfc/INfcTag.aidl index 2223255..3ac1dcc 100644 --- a/core/java/android/nfc/INfcTag.aidl +++ b/core/java/android/nfc/INfcTag.aidl @@ -45,4 +45,5 @@ interface INfcTag void resetTimeouts(); boolean canMakeReadOnly(int ndefType); int getMaxTransceiveLength(int technology); + boolean getExtendedLengthApdusSupported(); } diff --git a/core/java/android/nfc/NfcActivityManager.java b/core/java/android/nfc/NfcActivityManager.java index 2c73056..f80dae4 100644 --- a/core/java/android/nfc/NfcActivityManager.java +++ b/core/java/android/nfc/NfcActivityManager.java @@ -18,6 +18,7 @@ package android.nfc; import android.app.Activity; import android.app.Application; +import android.net.Uri; import android.os.Bundle; import android.os.RemoteException; import android.util.Log; @@ -107,10 +108,16 @@ public final class NfcActivityManager extends INdefPushCallback.Stub NdefMessage ndefMessage = null; // static NDEF message NfcAdapter.CreateNdefMessageCallback ndefMessageCallback = null; NfcAdapter.OnNdefPushCompleteCallback onNdefPushCompleteCallback = null; + Uri uri = null; + String mimeType = null; public NfcActivityState(Activity activity) { if (activity.getWindow().isDestroyed()) { throw new IllegalStateException("activity is already destroyed"); } + // Check if activity is resumed right now, as we will not + // immediately get a callback for that. + resumed = activity.isResumed(); + this.activity = activity; registerApplication(activity.getApplication()); } @@ -121,12 +128,14 @@ public final class NfcActivityManager extends INdefPushCallback.Stub ndefMessage = null; ndefMessageCallback = null; onNdefPushCompleteCallback = null; + uri = null; + mimeType = null; } @Override public String toString() { StringBuilder s = new StringBuilder("[").append(" "); s.append(ndefMessage).append(" ").append(ndefMessageCallback).append(" "); - s.append(onNdefPushCompleteCallback).append("]"); + s.append(onNdefPushCompleteCallback).append(" ").append(uri).append("]"); return s.toString(); } } @@ -175,6 +184,19 @@ public final class NfcActivityManager extends INdefPushCallback.Stub mDefaultEvent = new NfcEvent(mAdapter); } + public void setNdefPushContentUri(Activity activity, String mimeType, Uri uri) { + boolean isResumed; + synchronized (NfcActivityManager.this) { + NfcActivityState state = getActivityState(activity); + state.uri = uri; + state.mimeType = mimeType; + isResumed = state.resumed; + } + if (isResumed) { + requestNfcServiceCallback(true); + } + } + public void setNdefPushMessage(Activity activity, NdefMessage message) { boolean isResumed; synchronized (NfcActivityManager.this) { @@ -249,6 +271,26 @@ public final class NfcActivityManager extends INdefPushCallback.Stub /** Callback from NFC service, usually on binder thread */ @Override + public Uri getUri() { + synchronized (NfcActivityManager.this) { + NfcActivityState state = findResumedActivityState(); + if (state == null) return null; + + return state.uri; + } + } + /** Callback from NFC service, usually on binder thread */ + @Override + public String getMimeType() { + synchronized (NfcActivityManager.this) { + NfcActivityState state = findResumedActivityState(); + if (state == null) return null; + + return state.mimeType; + } + } + /** Callback from NFC service, usually on binder thread */ + @Override public void onNdefPushComplete() { NfcAdapter.OnNdefPushCompleteCallback callback; synchronized (NfcActivityManager.this) { diff --git a/core/java/android/nfc/NfcAdapter.java b/core/java/android/nfc/NfcAdapter.java index b7a7bd5..917751c 100644 --- a/core/java/android/nfc/NfcAdapter.java +++ b/core/java/android/nfc/NfcAdapter.java @@ -28,6 +28,7 @@ import android.content.Context; import android.content.IntentFilter; import android.content.pm.IPackageManager; import android.content.pm.PackageManager; +import android.net.Uri; import android.nfc.tech.MifareClassic; import android.nfc.tech.Ndef; import android.nfc.tech.NfcA; @@ -555,6 +556,18 @@ public final class NfcAdapter { } } + //TODO: Consider a callback alternative + //TOOD: See if we get rid of mimeType + //TODO: make sure NFC service has permission for URI + //TODO: javadoc + /** @hide */ + public void setBeamPushUri(String mimeType, Uri uri, Activity activity) { + if (activity == null) { + throw new NullPointerException("activity cannot be null"); + } + mNfcActivityManager.setNdefPushContentUri(activity, mimeType, uri); + } + /** * Set a static {@link NdefMessage} to send using Android Beam (TM). * @@ -580,7 +593,18 @@ public final class NfcAdapter { * and/or {@link #setNdefPushMessageCallback} is called with a null callback, * then NDEF push will be completely disabled for the specified activity(s). * This also disables any default NDEF message the Android OS would have - * otherwise sent on your behalf. + * otherwise sent on your behalf for those activity(s). + * + * <p>If you want to prevent the Android OS from sending default NDEF + * messages completely (for all activities), you can include a + * <code><meta-data></code> element inside the <code><application></code> + * element of your AndroidManifest.xml file, like this: + * <pre>{@code + * <application ...> + * <meta-data android:name="android.nfc.disable_beam_default" + * android:value="true" /> + * </application> + * }</pre> * * <p>The API allows for multiple activities to be specified at a time, * but it is strongly recommended to just register one at a time, @@ -664,7 +688,18 @@ public final class NfcAdapter { * and/or {@link #setNdefPushMessageCallback} is called with a null callback, * then NDEF push will be completely disabled for the specified activity(s). * This also disables any default NDEF message the Android OS would have - * otherwise sent on your behalf. + * otherwise sent on your behalf for those activity(s). + * + * <p>If you want to prevent the Android OS from sending default NDEF + * messages completely (for all activities), you can include a + * <code><meta-data></code> element inside the <code><application></code> + * element of your AndroidManifest.xml file, like this: + * <pre>{@code + * <application ...> + * <meta-data android:name="android.nfc.disable_beam_default" + * android:value="true" /> + * </application> + * }</pre> * * <p>The API allows for multiple activities to be specified at a time, * but it is strongly recommended to just register one at a time, diff --git a/core/java/android/nfc/tech/IsoDep.java b/core/java/android/nfc/tech/IsoDep.java index 1859877..089b159 100644 --- a/core/java/android/nfc/tech/IsoDep.java +++ b/core/java/android/nfc/tech/IsoDep.java @@ -179,4 +179,27 @@ public final class IsoDep extends BasicTagTechnology { public int getMaxTransceiveLength() { return getMaxTransceiveLengthInternal(); } + + /** + * <p>Standard APDUs have a 1-byte length field, allowing a maximum of + * 255 payload bytes, which results in a maximum APDU length of 261 bytes. + * + * <p>Extended length APDUs have a 3-byte length field, allowing 65535 + * payload bytes. + * + * <p>Some NFC adapters, like the one used in the Nexus S and the Galaxy Nexus + * do not support extended length APDUs. They are expected to be well-supported + * in the future though. Use this method to check for extended length APDU + * support. + * + * @return whether the NFC adapter on this device supports extended length APDUs. + */ + public boolean isExtendedLengthApduSupported() { + try { + return mTag.getTagService().getExtendedLengthApdusSupported(); + } catch (RemoteException e) { + Log.e(TAG, "NFC service dead", e); + return false; + } + } } diff --git a/core/java/android/nfc/tech/MifareClassic.java b/core/java/android/nfc/tech/MifareClassic.java index 9d1e6a1..8c92288 100644 --- a/core/java/android/nfc/tech/MifareClassic.java +++ b/core/java/android/nfc/tech/MifareClassic.java @@ -150,6 +150,7 @@ public final class MifareClassic extends BasicTagTechnology { mIsEmulated = false; switch (a.getSak()) { + case 0x01: case 0x08: mType = TYPE_CLASSIC; mSize = SIZE_1K; diff --git a/core/java/android/preference/Preference.java b/core/java/android/preference/Preference.java index 74a376d..8df4339 100644 --- a/core/java/android/preference/Preference.java +++ b/core/java/android/preference/Preference.java @@ -497,27 +497,30 @@ public class Preference implements Comparable<Preference>, OnDependencyChangeLis * @see #onCreateView(ViewGroup) */ protected void onBindView(View view) { - TextView textView = (TextView) view.findViewById(com.android.internal.R.id.title); - if (textView != null) { - textView.setText(getTitle()); + final TextView titleView = (TextView) view.findViewById( + com.android.internal.R.id.title); + if (titleView != null) { + final CharSequence title = getTitle(); + if (!TextUtils.isEmpty(title)) { + titleView.setText(title); + titleView.setVisibility(View.VISIBLE); + } else { + titleView.setVisibility(View.GONE); + } } - - textView = (TextView) view.findViewById(com.android.internal.R.id.summary); - if (textView != null) { + + final TextView summaryView = (TextView) view.findViewById( + com.android.internal.R.id.summary); + if (summaryView != null) { final CharSequence summary = getSummary(); if (!TextUtils.isEmpty(summary)) { - if (textView.getVisibility() != View.VISIBLE) { - textView.setVisibility(View.VISIBLE); - } - - textView.setText(getSummary()); + summaryView.setText(summary); + summaryView.setVisibility(View.VISIBLE); } else { - if (textView.getVisibility() != View.GONE) { - textView.setVisibility(View.GONE); - } + summaryView.setVisibility(View.GONE); } } - + ImageView imageView = (ImageView) view.findViewById(com.android.internal.R.id.icon); if (imageView != null) { if (mIconResId != 0 || mIcon != null) { diff --git a/core/java/android/provider/ContactsContract.java b/core/java/android/provider/ContactsContract.java index d724d56..0e9306b 100644 --- a/core/java/android/provider/ContactsContract.java +++ b/core/java/android/provider/ContactsContract.java @@ -4531,8 +4531,6 @@ public final class ContactsContract { /** * The phone number's E164 representation. * <P>Type: TEXT</P> - * - * @hide */ public static final String NORMALIZED_NUMBER = "normalized_number"; } @@ -5408,10 +5406,10 @@ public final class ContactsContract { public static final String NUMBER = DATA; /** - * The phone number's E164 representation. + * The phone number's E164 representation. This value can be omitted in which + * case the provider will try to automatically infer it. If present, {@link #NUMBER} + * has to be set as well (it will be ignored otherwise). * <P>Type: TEXT</P> - * - * @hide */ public static final String NORMALIZED_NUMBER = DATA4; diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index d74ccb8..2aaf548 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -36,14 +36,20 @@ import android.net.Uri; import android.net.wifi.WifiManager; import android.os.BatteryManager; import android.os.Bundle; +import android.os.IBinder; +import android.os.Process; import android.os.RemoteException; +import android.os.ServiceManager; import android.os.SystemProperties; +import android.os.UserId; import android.speech.tts.TextToSpeech; import android.text.TextUtils; import android.util.AndroidException; import android.util.Log; import android.view.WindowOrientationListener; +import com.android.internal.widget.ILockSettings; + import java.net.URISyntaxException; import java.util.HashMap; import java.util.HashSet; @@ -1506,13 +1512,22 @@ public final class Settings { public static final String VOLUME_MASTER = "volume_master"; /** - * Whether the notifications should use the ring volume (value of 1) or a separate - * notification volume (value of 0). In most cases, users will have this enabled so the - * notification and ringer volumes will be the same. However, power users can disable this - * and use the separate notification volume control. + * Master volume mute (int 1 = mute, 0 = not muted). + * + * @hide + */ + public static final String VOLUME_MASTER_MUTE = "volume_master_mute"; + + /** + * Whether the notifications should use the ring volume (value of 1) or + * a separate notification volume (value of 0). In most cases, users + * will have this enabled so the notification and ringer volumes will be + * the same. However, power users can disable this and use the separate + * notification volume control. * <p> - * Note: This is a one-off setting that will be removed in the future when there is profile - * support. For this reason, it is kept hidden from the public APIs. + * Note: This is a one-off setting that will be removed in the future + * when there is profile support. For this reason, it is kept hidden + * from the public APIs. * * @hide * @deprecated @@ -2244,6 +2259,17 @@ public final class Settings { // Populated lazily, guarded by class object: private static NameValueCache sNameValueCache = null; + private static ILockSettings sLockSettings = null; + + private static boolean sIsSystemProcess; + private static final HashSet<String> MOVED_TO_LOCK_SETTINGS; + static { + MOVED_TO_LOCK_SETTINGS = new HashSet<String>(3); + MOVED_TO_LOCK_SETTINGS.add(Secure.LOCK_PATTERN_ENABLED); + MOVED_TO_LOCK_SETTINGS.add(Secure.LOCK_PATTERN_VISIBLE); + MOVED_TO_LOCK_SETTINGS.add(Secure.LOCK_PATTERN_TACTILE_FEEDBACK_ENABLED); + } + /** * Look up a name in the database. * @param resolver to access the database with @@ -2255,6 +2281,21 @@ public final class Settings { sNameValueCache = new NameValueCache(SYS_PROP_SETTING_VERSION, CONTENT_URI, CALL_METHOD_GET_SECURE); } + + if (sLockSettings == null) { + sLockSettings = ILockSettings.Stub.asInterface( + (IBinder) ServiceManager.getService("lock_settings")); + sIsSystemProcess = Process.myUid() == Process.SYSTEM_UID; + } + if (sLockSettings != null && !sIsSystemProcess + && MOVED_TO_LOCK_SETTINGS.contains(name)) { + try { + return sLockSettings.getString(name, "0", UserId.getCallingUserId()); + } catch (RemoteException re) { + // Fall through + } + } + return sNameValueCache.getString(resolver, name); } diff --git a/core/java/android/text/SpannableStringBuilder.java b/core/java/android/text/SpannableStringBuilder.java index f7a7eb8..ae9042c 100644 --- a/core/java/android/text/SpannableStringBuilder.java +++ b/core/java/android/text/SpannableStringBuilder.java @@ -125,28 +125,24 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable } private void resizeFor(int size) { - int newlen = ArrayUtils.idealCharArraySize(size + 1); - char[] newtext = new char[newlen]; + final int oldLength = mText.length; + final int newLength = ArrayUtils.idealCharArraySize(size + 1); + final int after = oldLength - (mGapStart + mGapLength); - int after = mText.length - (mGapStart + mGapLength); + char[] newText = new char[newLength]; + System.arraycopy(mText, 0, newText, 0, mGapStart); + System.arraycopy(mText, oldLength - after, newText, newLength - after, after); + mText = newText; - System.arraycopy(mText, 0, newtext, 0, mGapStart); - System.arraycopy(mText, mText.length - after, - newtext, newlen - after, after); + final int delta = newLength - oldLength; + mGapLength += delta; + if (mGapLength < 1) + new Exception("mGapLength < 1").printStackTrace(); for (int i = 0; i < mSpanCount; i++) { - if (mSpanStarts[i] > mGapStart) - mSpanStarts[i] += newlen - mText.length; - if (mSpanEnds[i] > mGapStart) - mSpanEnds[i] += newlen - mText.length; + if (mSpanStarts[i] > mGapStart) mSpanStarts[i] += delta; + if (mSpanEnds[i] > mGapStart) mSpanEnds[i] += delta; } - - int oldlen = mText.length; - mText = newtext; - mGapLength += mText.length - oldlen; - - if (mGapLength < 1) - new Exception("mGapLength < 1").printStackTrace(); } private void moveGapTo(int where) { @@ -157,14 +153,10 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable if (where < mGapStart) { int overlap = mGapStart - where; - - System.arraycopy(mText, where, - mText, mGapStart + mGapLength - overlap, overlap); + System.arraycopy(mText, where, mText, mGapStart + mGapLength - overlap, overlap); } else /* where > mGapStart */ { int overlap = where - mGapStart; - - System.arraycopy(mText, where + mGapLength - overlap, - mText, mGapStart, overlap); + System.arraycopy(mText, where + mGapLength - overlap, mText, mGapStart, overlap); } // XXX be more clever @@ -340,18 +332,17 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable boolean atEnd = (mGapStart + mGapLength == mText.length); for (int i = mSpanCount - 1; i >= 0; i--) { - if (mSpanStarts[i] >= start && - mSpanStarts[i] < mGapStart + mGapLength) { + if (mSpanStarts[i] >= start && mSpanStarts[i] < mGapStart + mGapLength) { int flag = (mSpanFlags[i] & START_MASK) >> START_SHIFT; - if (flag == POINT || (flag == PARAGRAPH && atEnd)) - mSpanStarts[i] = mGapStart + mGapLength; - else - mSpanStarts[i] = start; + if (flag == POINT || (flag == PARAGRAPH && atEnd)) { + mSpanStarts[i] = mGapStart + mGapLength; + } else { + mSpanStarts[i] = start; + } } - if (mSpanEnds[i] >= start && - mSpanEnds[i] < mGapStart + mGapLength) { + if (mSpanEnds[i] >= start && mSpanEnds[i] < mGapStart + mGapLength) { int flag = (mSpanFlags[i] & END_MASK); if (flag == POINT || (flag == PARAGRAPH && atEnd)) @@ -360,7 +351,8 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable mSpanEnds[i] = start; } - // remove 0-length SPAN_EXCLUSIVE_EXCLUSIVE + // remove 0-length SPAN_EXCLUSIVE_EXCLUSIVE, which are POINT_MARK and could + // get their boundaries swapped by the above code if (mSpanEnds[i] < mSpanStarts[i]) { removeSpan(i); } @@ -520,6 +512,11 @@ public class SpannableStringBuilder implements CharSequence, GetChars, Spannable } } + if (flags == Spanned.SPAN_EXCLUSIVE_EXCLUSIVE && start == end) { + throw new IllegalArgumentException( + "SPAN_EXCLUSIVE_EXCLUSIVE spans cannot have a zero length"); + } + if (start > mGapStart) { start += mGapLength; } else if (start == mGapStart) { diff --git a/core/java/android/view/GLES20TextureLayer.java b/core/java/android/view/GLES20TextureLayer.java index cbb908b..16a13cf 100644 --- a/core/java/android/view/GLES20TextureLayer.java +++ b/core/java/android/view/GLES20TextureLayer.java @@ -42,6 +42,12 @@ class GLES20TextureLayer extends GLES20Layer { } } + GLES20TextureLayer(SurfaceTexture surface, boolean isOpaque) { + this(isOpaque); + mSurface = surface; + mSurface.attachToGLContext(mTexture); + } + @Override boolean isValid() { return mLayer != 0 && mTexture != 0; @@ -72,6 +78,14 @@ class GLES20TextureLayer extends GLES20Layer { return mSurface; } + void setSurfaceTexture(SurfaceTexture surfaceTexture) { + if (mSurface != null) { + mSurface.release(); + } + mSurface = surfaceTexture; + mSurface.attachToGLContext(mTexture); + } + @Override void update(int width, int height, boolean isOpaque) { super.update(width, height, isOpaque); diff --git a/core/java/android/view/HardwareRenderer.java b/core/java/android/view/HardwareRenderer.java index 9ef2621..b0399fd 100644 --- a/core/java/android/view/HardwareRenderer.java +++ b/core/java/android/view/HardwareRenderer.java @@ -87,7 +87,7 @@ public abstract class HardwareRenderer { /** * System property used to enable or disable hardware rendering profiling. * The default value of this property is assumed to be false. - * + * * When profiling is enabled, the adb shell dumpsys gfxinfo command will * output extra information about the time taken to execute by the last * frames. @@ -99,6 +99,20 @@ public abstract class HardwareRenderer { static final String PROFILE_PROPERTY = "hwui.profile"; /** + * System property used to specify the number of frames to be used + * when doing hardware rendering profiling. + * The default value of this property is #PROFILE_MAX_FRAMES. + * + * When profiling is enabled, the adb shell dumpsys gfxinfo command will + * output extra information about the time taken to execute by the last + * frames. + * + * Possible values: + * "60", to set the limit of frames to 60 + */ + static final String PROFILE_MAXFRAMES_PROPERTY = "hwui.profile.maxframes"; + + /** * System property used to debug EGL configuration choice. * * Possible values: @@ -134,7 +148,7 @@ public abstract class HardwareRenderer { /** * Number of frames to profile. */ - private static final int PROFILE_MAX_FRAMES = 120; + private static final int PROFILE_MAX_FRAMES = 64; /** * Number of floats per profiled frame. @@ -377,9 +391,9 @@ public abstract class HardwareRenderer { * @param isOpaque Whether the layer should be opaque or not * * @return A hardware layer - */ + */ abstract HardwareLayer createHardwareLayer(boolean isOpaque); - + /** * Creates a new hardware layer. * @@ -403,6 +417,15 @@ public abstract class HardwareRenderer { abstract SurfaceTexture createSurfaceTexture(HardwareLayer layer); /** + * Sets the {@link android.graphics.SurfaceTexture} that will be used to + * render into the specified hardware layer. + * + * @param layer The layer to render into using a {@link android.graphics.SurfaceTexture} + * @param surfaceTexture The {@link android.graphics.SurfaceTexture} to use for the layer + */ + abstract void setSurfaceTexture(HardwareLayer layer, SurfaceTexture surfaceTexture); + + /** * Initializes the hardware renderer for the specified surface and setup the * renderer for drawing, if needed. This is invoked when the ViewAncestor has * potentially lost the hardware renderer. The hardware renderer should be @@ -503,7 +526,7 @@ public abstract class HardwareRenderer { static final int SURFACE_STATE_SUCCESS = 1; static final int SURFACE_STATE_UPDATED = 2; - static final int FUNCTOR_PROCESS_DELAY = 2; + static final int FUNCTOR_PROCESS_DELAY = 4; static EGL10 sEgl; static EGLDisplay sEglDisplay; @@ -579,7 +602,13 @@ public abstract class HardwareRenderer { } if (mProfileEnabled) { - mProfileData = new float[PROFILE_MAX_FRAMES * PROFILE_FRAME_DATA_COUNT]; + property = SystemProperties.get(PROFILE_MAXFRAMES_PROPERTY, + Integer.toString(PROFILE_MAX_FRAMES)); + int maxProfileFrames = Integer.valueOf(property); + mProfileData = new float[maxProfileFrames * PROFILE_FRAME_DATA_COUNT]; + for (int i = 0; i < mProfileData.length; i += PROFILE_FRAME_DATA_COUNT) { + mProfileData[i] = mProfileData[i + 1] = mProfileData[i + 2] = -1; + } } else { mProfileData = null; } @@ -596,9 +625,14 @@ public abstract class HardwareRenderer { if (mProfileEnabled) { pw.printf("\n\tDraw\tProcess\tExecute\n"); for (int i = 0; i < mProfileData.length; i += PROFILE_FRAME_DATA_COUNT) { + if (mProfileData[i] < 0) { + break; + } pw.printf("\t%3.2f\t%3.2f\t%3.2f\n", mProfileData[i], mProfileData[i + 1], mProfileData[i + 2]); + mProfileData[i] = mProfileData[i + 1] = mProfileData[i + 2] = -1; } + mProfileCurrentFrame = mProfileData.length; } } @@ -1320,6 +1354,11 @@ public abstract class HardwareRenderer { } @Override + void setSurfaceTexture(HardwareLayer layer, SurfaceTexture surfaceTexture) { + ((GLES20TextureLayer) layer).setSurfaceTexture(surfaceTexture); + } + + @Override void destroyLayers(View view) { if (view != null && isEnabled() && checkCurrent() != SURFACE_STATE_ERROR) { destroyHardwareLayer(view); diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl index 14cd48f..8fe8e40 100644 --- a/core/java/android/view/IWindowManager.aidl +++ b/core/java/android/view/IWindowManager.aidl @@ -62,18 +62,9 @@ interface IWindowManager void setForcedDisplaySize(int longDimen, int shortDimen); void clearForcedDisplaySize(); - // Is device configured with a hideable status bar or a tablet system bar? - boolean canStatusBarHide(); - - // These can only be called when injecting events to your own window, - // or by holding the INJECT_EVENTS permission. These methods may block - // until pending input events are finished being dispatched even when 'sync' is false. - // Avoid calling these methods on your UI thread or use the 'NoWait' version instead. - boolean injectKeyEvent(in KeyEvent ev, boolean sync); - boolean injectPointerEvent(in MotionEvent ev, boolean sync); - boolean injectTrackballEvent(in MotionEvent ev, boolean sync); - boolean injectInputEventNoWait(in InputEvent ev); - + // Is the device configured to have a full system bar for larger screens? + boolean hasSystemNavBar(); + // These can only be called when holding the MANAGE_APP_TOKENS permission. void pauseKeyDispatching(IBinder token); void resumeKeyDispatching(IBinder token); @@ -128,26 +119,6 @@ interface IWindowManager void setAnimationScale(int which, float scale); void setAnimationScales(in float[] scales); - // These require the READ_INPUT_STATE permission. - int getSwitchState(int sw); - int getSwitchStateForDevice(int devid, int sw); - int getScancodeState(int sw); - int getScancodeStateForDevice(int devid, int sw); - int getTrackballScancodeState(int sw); - int getDPadScancodeState(int sw); - int getKeycodeState(int sw); - int getKeycodeStateForDevice(int devid, int sw); - int getTrackballKeycodeState(int sw); - int getDPadKeycodeState(int sw); - InputChannel monitorInput(String inputChannelName); - - // Report whether the hardware supports the given keys; returns true if successful - boolean hasKeys(in int[] keycodes, inout boolean[] keyExists); - - // Get input device information. - InputDevice getInputDevice(int deviceId); - int[] getInputDeviceIds(); - // For testing void setInTouchMode(boolean showFocus); @@ -171,8 +142,10 @@ interface IWindowManager * @param alwaysSendConfiguration Flag to force a new configuration to * be evaluated. This can be used when there are other parameters in * configuration that are changing. + * @param forceRelayout If true, the window manager will always do a relayout + * of its windows even if the rotation hasn't changed. */ - void updateRotation(boolean alwaysSendConfiguration); + void updateRotation(boolean alwaysSendConfiguration, boolean forceRelayout); /** * Retrieve the current screen orientation, constants as per @@ -218,11 +191,6 @@ interface IWindowManager void statusBarVisibilityChanged(int visibility); /** - * Called by the settings application to temporarily set the pointer speed. - */ - void setPointerSpeed(int speed); - - /** * Block until the given window has been drawn to the screen. */ void waitForWindowDrawn(IBinder token, in IRemoteCallback callback); diff --git a/core/java/android/view/InputDevice.java b/core/java/android/view/InputDevice.java index 8115b36..6f8d09b 100755 --- a/core/java/android/view/InputDevice.java +++ b/core/java/android/view/InputDevice.java @@ -16,9 +16,9 @@ package android.view; +import android.hardware.input.InputManager; import android.os.Parcel; import android.os.Parcelable; -import android.os.RemoteException; import java.util.ArrayList; import java.util.List; @@ -26,7 +26,7 @@ import java.util.List; /** * Describes the capabilities of a particular input device. * <p> - * Each input device may support multiple classes of input. For example, a multifunction + * Each input device may support multiple classes of input. For example, a multi-function * keyboard may compose the capabilities of a standard keyboard together with a track pad mouse * or other pointing device. * </p><p> @@ -41,6 +41,7 @@ import java.util.List; public final class InputDevice implements Parcelable { private int mId; private String mName; + private String mDescriptor; private int mSources; private int mKeyboardType; private String mKeyCharacterMapFile; @@ -118,7 +119,11 @@ public final class InputDevice implements Parcelable { /** * The input source is a keyboard. - * + * + * This source indicates pretty much anything that has buttons. Use + * {@link #getKeyboardType()} to determine whether the keyboard has alphabetic keys + * and can be used to enter text. + * * @see #SOURCE_CLASS_BUTTON */ public static final int SOURCE_KEYBOARD = 0x00000100 | SOURCE_CLASS_BUTTON; @@ -297,13 +302,7 @@ public final class InputDevice implements Parcelable { * @return The input device or null if not found. */ public static InputDevice getDevice(int id) { - IWindowManager wm = Display.getWindowManager(); - try { - return wm.getInputDevice(id); - } catch (RemoteException ex) { - throw new RuntimeException( - "Could not get input device information from Window Manager.", ex); - } + return InputManager.getInputDevice(id); } /** @@ -311,23 +310,51 @@ public final class InputDevice implements Parcelable { * @return The input device ids. */ public static int[] getDeviceIds() { - IWindowManager wm = Display.getWindowManager(); - try { - return wm.getInputDeviceIds(); - } catch (RemoteException ex) { - throw new RuntimeException( - "Could not get input device ids from Window Manager.", ex); - } + return InputManager.getInputDeviceIds(); } - + /** * Gets the input device id. + * <p> + * Each input device receives a unique id when it is first configured + * by the system. The input device id may change when the system is restarted or if the + * input device is disconnected, reconnected or reconfigured at any time. + * If you require a stable identifier for a device that persists across + * boots and reconfigurations, use {@link #getDescriptor()}. + * </p> + * * @return The input device id. */ public int getId() { return mId; } - + + /** + * Gets the input device descriptor, which is a stable identifier for an input device. + * <p> + * An input device descriptor uniquely identifies an input device. Its value + * is intended to be persistent across system restarts, and should not change even + * if the input device is disconnected, reconnected or reconfigured at any time. + * </p><p> + * It is possible for there to be multiple {@link InputDevice} instances that have the + * same input device descriptor. This might happen in situations where a single + * human input device registers multiple {@link InputDevice} instances (HID collections) + * that describe separate features of the device, such as a keyboard that also + * has a trackpad. Alternately, it may be that the input devices are simply + * indistinguishable, such as two keyboards made by the same manufacturer. + * </p><p> + * The input device descriptor returned by {@link #getDescriptor} should only bt + * used when an application needs to remember settings associated with a particular + * input device. For all other purposes when referring to a logical + * {@link InputDevice} instance at runtime use the id returned by {@link #getId()}. + * </p> + * + * @return The input device descriptor. + */ + public String getDescriptor() { + return mDescriptor; + } + /** * Gets the name of this input device. * @return The input device name. @@ -534,6 +561,7 @@ public final class InputDevice implements Parcelable { private void readFromParcel(Parcel in) { mId = in.readInt(); mName = in.readString(); + mDescriptor = in.readString(); mSources = in.readInt(); mKeyboardType = in.readInt(); mKeyCharacterMapFile = in.readString(); @@ -552,6 +580,7 @@ public final class InputDevice implements Parcelable { public void writeToParcel(Parcel out, int flags) { out.writeInt(mId); out.writeString(mName); + out.writeString(mDescriptor); out.writeInt(mSources); out.writeInt(mKeyboardType); out.writeString(mKeyCharacterMapFile); @@ -578,7 +607,8 @@ public final class InputDevice implements Parcelable { public String toString() { StringBuilder description = new StringBuilder(); description.append("Input Device ").append(mId).append(": ").append(mName).append("\n"); - + description.append(" Descriptor: ").append(mDescriptor).append("\n"); + description.append(" Keyboard Type: "); switch (mKeyboardType) { case KEYBOARD_TYPE_NONE: diff --git a/core/java/android/view/KeyCharacterMap.java b/core/java/android/view/KeyCharacterMap.java index 575af3b..b03f086 100644 --- a/core/java/android/view/KeyCharacterMap.java +++ b/core/java/android/view/KeyCharacterMap.java @@ -19,7 +19,7 @@ package android.view; import android.text.method.MetaKeyKeyListener; import android.util.AndroidRuntimeException; import android.util.SparseIntArray; -import android.os.RemoteException; +import android.hardware.input.InputManager; import android.util.SparseArray; import java.lang.Character; @@ -196,6 +196,14 @@ public class KeyCharacterMap { } /** + * TODO implement this + * @hide + */ + public static KeyCharacterMap load(CharSequence contents) { + return null; + } + + /** * Gets the Unicode character generated by the specified key and meta * key state combination. * <p> @@ -456,7 +464,8 @@ public class KeyCharacterMap { /** * Gets the keyboard type. - * Returns {@link #NUMERIC}, {@link #PREDICTIVE}, {@link #ALPHA} or {@link #FULL}. + * Returns {@link #NUMERIC}, {@link #PREDICTIVE}, {@link #ALPHA}, {@link #FULL} + * or {@link #SPECIAL_FUNCTION}. * <p> * Different keyboard types have different semantics. Refer to the documentation * associated with the keyboard type constants for details. @@ -518,10 +527,7 @@ public class KeyCharacterMap { * @return True if at least one attached keyboard supports the specified key code. */ public static boolean deviceHasKey(int keyCode) { - int[] codeArray = new int[1]; - codeArray[0] = keyCode; - boolean[] ret = deviceHasKeys(codeArray); - return ret[0]; + return InputManager.deviceHasKeys(new int[] { keyCode })[0]; } /** @@ -535,14 +541,7 @@ public class KeyCharacterMap { * at the same index in the key codes array. */ public static boolean[] deviceHasKeys(int[] keyCodes) { - boolean[] ret = new boolean[keyCodes.length]; - IWindowManager wm = Display.getWindowManager(); - try { - wm.hasKeys(keyCodes, ret); - } catch (RemoteException e) { - // no fallback; just return the empty array - } - return ret; + return InputManager.deviceHasKeys(keyCodes); } /** diff --git a/core/java/android/view/TextureView.java b/core/java/android/view/TextureView.java index 83999a1..3cd8b71 100644 --- a/core/java/android/view/TextureView.java +++ b/core/java/android/view/TextureView.java @@ -115,6 +115,7 @@ public class TextureView extends View { private final Object[] mLock = new Object[0]; private boolean mUpdateLayer; + private boolean mUpdateSurface; private SurfaceTexture.OnFrameAvailableListener mUpdateListener; @@ -208,6 +209,8 @@ public class TextureView extends View { private void destroySurface() { if (mLayer != null) { + mSurface.detachFromGLContext(); + boolean shouldRelease = true; if (mListener != null) { shouldRelease = mListener.onSurfaceTextureDestroyed(mSurface); @@ -322,9 +325,13 @@ public class TextureView extends View { } mLayer = mAttachInfo.mHardwareRenderer.createHardwareLayer(mOpaque); - mSurface = mAttachInfo.mHardwareRenderer.createSurfaceTexture(mLayer); + if (!mUpdateSurface) { + // We already have a SurfaceTexture to use, and we will pass it + // to mLayer below. + mSurface = mAttachInfo.mHardwareRenderer.createSurfaceTexture(mLayer); + } nSetDefaultBufferSize(mSurface, getWidth(), getHeight()); - nCreateNativeWindow(mSurface); + nCreateNativeWindow(mSurface); mUpdateListener = new SurfaceTexture.OnFrameAvailableListener() { @Override @@ -344,6 +351,15 @@ public class TextureView extends View { } } + if (mUpdateSurface) { + // Someone has requested that we use a specific SurfaceTexture, so + // tell mLayer about it and set the SurfaceTexture to use the + // current view size. + mUpdateSurface = false; + mAttachInfo.mHardwareRenderer.setSurfaceTexture(mLayer, mSurface); + nSetDefaultBufferSize(mSurface, getWidth(), getHeight()); + } + applyUpdate(); applyTransformMatrix(); @@ -371,7 +387,7 @@ public class TextureView extends View { mUpdateLayer = true; invalidate(); } - + private void applyUpdate() { if (mLayer == null) { return; @@ -636,6 +652,32 @@ public class TextureView extends View { } /** + * Set the {@link SurfaceTexture} for this view to use. If a {@link + * SurfaceTexture} is already being used by this view, it is immediately + * released and not be usable any more. The {@link + * SurfaceTextureListener#onSurfaceTextureDestroyed} callback is <b>not</b> + * called. + * + * The {@link SurfaceTexture} object must be detached from all OpenGL ES + * contexts prior to calling this method. + * + * @param surfaceTexture The {@link SurfaceTexture} that the view should use. + * @see SurfaceTexture#detachFromGLContext() + * @hide + */ + public void setSurfaceTexture(SurfaceTexture surfaceTexture) { + if (surfaceTexture == null) { + throw new NullPointerException("surfaceTexture must not be null"); + } + if (mSurface != null) { + mSurface.release(); + } + mSurface = surfaceTexture; + mUpdateSurface = true; + invalidateParentIfNeeded(); + } + + /** * Returns the {@link SurfaceTextureListener} currently associated with this * texture view. * diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index c40a7d5..d62e32f 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -71,6 +71,7 @@ import android.view.inputmethod.InputMethodManager; import android.widget.ScrollBarDrawable; import static android.os.Build.VERSION_CODES.*; +import static java.lang.Math.max; import com.android.internal.R; import com.android.internal.util.Predicate; @@ -126,7 +127,7 @@ import java.util.concurrent.CopyOnWriteArrayList; * example, all views will let you set a listener to be notified when the view * gains or loses focus. You can register such a listener using * {@link #setOnFocusChangeListener(android.view.View.OnFocusChangeListener)}. - * Other view subclasses offer more specialized listeners. For example, a Button + * Other view subclasses offer more specialized listeners. For example, a Button * exposes a listener to notify clients when the button is clicked.</li> * <li><strong>Set visibility:</strong> You can hide or show views using * {@link #setVisibility(int)}.</li> @@ -579,6 +580,7 @@ import java.util.concurrent.CopyOnWriteArrayList; * @attr ref android.R.styleable#View_duplicateParentState * @attr ref android.R.styleable#View_id * @attr ref android.R.styleable#View_requiresFadingEdge + * @attr ref android.R.styleable#View_fadeScrollbars * @attr ref android.R.styleable#View_fadingEdgeLength * @attr ref android.R.styleable#View_filterTouchesWhenObscured * @attr ref android.R.styleable#View_fitsSystemWindows @@ -624,6 +626,7 @@ import java.util.concurrent.CopyOnWriteArrayList; * @attr ref android.R.styleable#View_scrollbarAlwaysDrawVerticalTrack * @attr ref android.R.styleable#View_soundEffectsEnabled * @attr ref android.R.styleable#View_tag + * @attr ref android.R.styleable#View_textAlignment * @attr ref android.R.styleable#View_transformPivotX * @attr ref android.R.styleable#View_transformPivotY * @attr ref android.R.styleable#View_translationX @@ -1827,15 +1830,15 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal public static final int TEXT_DIRECTION_LOCALE = 5; /** - * Bit shift to get the horizontal layout direction. (bits after LAYOUT_DIRECTION_RESOLVED) - * @hide + * Default text direction is inherited */ - static final int TEXT_DIRECTION_MASK_SHIFT = 6; + protected static int TEXT_DIRECTION_DEFAULT = TEXT_DIRECTION_INHERIT; /** - * Default text direction is inherited + * Bit shift to get the horizontal layout direction. (bits after LAYOUT_DIRECTION_RESOLVED) + * @hide */ - protected static int TEXT_DIRECTION_DEFAULT = TEXT_DIRECTION_INHERIT; + static final int TEXT_DIRECTION_MASK_SHIFT = 6; /** * Mask for use with private flags indicating bits used for text direction. @@ -1882,6 +1885,113 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal static final int TEXT_DIRECTION_RESOLVED_DEFAULT = TEXT_DIRECTION_FIRST_STRONG << TEXT_DIRECTION_RESOLVED_MASK_SHIFT; + /* + * Default text alignment. The text alignment of this View is inherited from its parent. + * Use with {@link #setTextAlignment(int)} + */ + public static final int TEXT_ALIGNMENT_INHERIT = 0; + + /** + * Default for the root view. The gravity determines the text alignment, ALIGN_NORMAL, + * ALIGN_CENTER, or ALIGN_OPPOSITE, which are relative to each paragraph’s text direction. + * + * Use with {@link #setTextAlignment(int)} + */ + public static final int TEXT_ALIGNMENT_GRAVITY = 1; + + /** + * Align to the start of the paragraph, e.g. ALIGN_NORMAL. + * + * Use with {@link #setTextAlignment(int)} + */ + public static final int TEXT_ALIGNMENT_TEXT_START = 2; + + /** + * Align to the end of the paragraph, e.g. ALIGN_OPPOSITE. + * + * Use with {@link #setTextAlignment(int)} + */ + public static final int TEXT_ALIGNMENT_TEXT_END = 3; + + /** + * Center the paragraph, e.g. ALIGN_CENTER. + * + * Use with {@link #setTextAlignment(int)} + */ + public static final int TEXT_ALIGNMENT_CENTER = 4; + + /** + * Align to the start of the view, which is ALIGN_LEFT if the view’s resolved + * layoutDirection is LTR, and ALIGN_RIGHT otherwise. + * + * Use with {@link #setTextAlignment(int)} + */ + public static final int TEXT_ALIGNMENT_VIEW_START = 5; + + /** + * Align to the end of the view, which is ALIGN_RIGHT if the view’s resolved + * layoutDirection is LTR, and ALIGN_LEFT otherwise. + * + * Use with {@link #setTextAlignment(int)} + */ + public static final int TEXT_ALIGNMENT_VIEW_END = 6; + + /** + * Default text alignment is inherited + */ + protected static int TEXT_ALIGNMENT_DEFAULT = TEXT_ALIGNMENT_GRAVITY; + + /** + * Bit shift to get the horizontal layout direction. (bits after DRAG_HOVERED) + * @hide + */ + static final int TEXT_ALIGNMENT_MASK_SHIFT = 13; + + /** + * Mask for use with private flags indicating bits used for text alignment. + * @hide + */ + static final int TEXT_ALIGNMENT_MASK = 0x00000007 << TEXT_ALIGNMENT_MASK_SHIFT; + + /** + * Array of text direction flags for mapping attribute "textAlignment" to correct + * flag value. + * @hide + */ + private static final int[] TEXT_ALIGNMENT_FLAGS = { + TEXT_ALIGNMENT_INHERIT << TEXT_ALIGNMENT_MASK_SHIFT, + TEXT_ALIGNMENT_GRAVITY << TEXT_ALIGNMENT_MASK_SHIFT, + TEXT_ALIGNMENT_TEXT_START << TEXT_ALIGNMENT_MASK_SHIFT, + TEXT_ALIGNMENT_TEXT_END << TEXT_ALIGNMENT_MASK_SHIFT, + TEXT_ALIGNMENT_CENTER << TEXT_ALIGNMENT_MASK_SHIFT, + TEXT_ALIGNMENT_VIEW_START << TEXT_ALIGNMENT_MASK_SHIFT, + TEXT_ALIGNMENT_VIEW_END << TEXT_ALIGNMENT_MASK_SHIFT + }; + + /** + * Indicates whether the view text alignment has been resolved. + * @hide + */ + static final int TEXT_ALIGNMENT_RESOLVED = 0x00000008 << TEXT_ALIGNMENT_MASK_SHIFT; + + /** + * Bit shift to get the resolved text alignment. + * @hide + */ + static final int TEXT_ALIGNMENT_RESOLVED_MASK_SHIFT = 17; + + /** + * Mask for use with private flags indicating bits used for text alignment. + * @hide + */ + static final int TEXT_ALIGNMENT_RESOLVED_MASK = 0x00000007 << TEXT_ALIGNMENT_RESOLVED_MASK_SHIFT; + + /** + * Indicates whether if the view text alignment has been resolved to gravity + */ + public static final int TEXT_ALIGNMENT_RESOLVED_DEFAULT = + TEXT_ALIGNMENT_GRAVITY << TEXT_ALIGNMENT_RESOLVED_MASK_SHIFT; + /* End of masks for mPrivateFlags2 */ @@ -1926,7 +2036,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * system UI to enter an unobtrusive "low profile" mode. * * <p>This is for use in games, book readers, video players, or any other - * "immersive" application where the usual system chrome is deemed too distracting. + * "immersive" application where the usual system chrome is deemed too distracting. * * <p>In low profile mode, the status bar and/or navigation icons may dim. * @@ -1942,7 +2052,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * {@link #SYSTEM_UI_FLAG_LOW_PROFILE}; on devices that draw essential navigation controls * (Home, Back, and the like) on screen, <code>SYSTEM_UI_FLAG_HIDE_NAVIGATION</code> will cause * those to disappear. This is useful (in conjunction with the - * {@link android.view.WindowManager.LayoutParams#FLAG_FULLSCREEN FLAG_FULLSCREEN} and + * {@link android.view.WindowManager.LayoutParams#FLAG_FULLSCREEN FLAG_FULLSCREEN} and * {@link android.view.WindowManager.LayoutParams#FLAG_LAYOUT_IN_SCREEN FLAG_LAYOUT_IN_SCREEN} * window flags) for displaying content using every last pixel on the display. * @@ -2339,7 +2449,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal */ private int mPrevWidth = -1; private int mPrevHeight = -1; - + /** * The degrees rotation around the vertical axis through the pivot point. */ @@ -2546,7 +2656,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal */ int mOldHeightMeasureSpec = Integer.MIN_VALUE; - private Drawable mBGDrawable; + private Drawable mBackground; private int mBackgroundResource; private boolean mBackgroundSizeChanged; @@ -2620,7 +2730,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal /** * Set to true when drawing cache is enabled and cannot be created. - * + * * @hide */ public boolean mCachingFailed; @@ -2841,7 +2951,8 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal mViewFlags = SOUND_EFFECTS_ENABLED | HAPTIC_FEEDBACK_ENABLED; // Set layout and text direction defaults mPrivateFlags2 = (LAYOUT_DIRECTION_DEFAULT << LAYOUT_DIRECTION_MASK_SHIFT) | - (TEXT_DIRECTION_DEFAULT << TEXT_DIRECTION_MASK_SHIFT); + (TEXT_DIRECTION_DEFAULT << TEXT_DIRECTION_MASK_SHIFT) | + (TEXT_ALIGNMENT_DEFAULT << TEXT_ALIGNMENT_MASK_SHIFT); mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop(); setOverScrollMode(OVER_SCROLL_IF_CONTENT_SCROLLS); mUserPaddingStart = -1; @@ -3222,6 +3333,13 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal mPrivateFlags2 |= TEXT_DIRECTION_FLAGS[textDirection]; } break; + case R.styleable.View_textAlignment: + // Clear any text alignment flag already set + mPrivateFlags2 &= ~TEXT_ALIGNMENT_MASK; + // Set the text alignment flag depending on the value of the attribute + final int textAlignment = a.getInt(attr, TEXT_ALIGNMENT_DEFAULT); + mPrivateFlags2 |= TEXT_ALIGNMENT_FLAGS[textAlignment]; + break; } } @@ -3230,7 +3348,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal setOverScrollMode(overScrollMode); if (background != null) { - setBackgroundDrawable(background); + setBackground(background); } // Cache user padding as we cannot fully resolve padding here (we dont have yet the resolved @@ -3494,6 +3612,11 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal } } + private ScrollabilityCache getScrollCache() { + initScrollCache(); + return mScrollCache; + } + /** * Set the position of the vertical scroll bar. Should be one of * {@link #SCROLLBAR_POSITION_DEFAULT}, {@link #SCROLLBAR_POSITION_LEFT} or @@ -3909,8 +4032,9 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * <p> * <strong>Note:</strong> When a View clears focus the framework is trying * to give focus to the first focusable View from the top. Hence, if this - * View is the first from the top that can take focus, then its focus will - * not be cleared nor will the focus change callback be invoked. + * View is the first from the top that can take focus, then all callbacks + * related to clearing focus will be invoked after wich the framework will + * give focus to this view. * </p> */ public void clearFocus() { @@ -3927,25 +4051,15 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal onFocusChanged(false, 0, null); refreshDrawableState(); + + ensureInputFocusOnFirstFocusable(); } } - /** - * Called to clear the focus of a view that is about to be removed. - * Doesn't call clearChildFocus, which prevents this view from taking - * focus again before it has been removed from the parent - */ - void clearFocusForRemoval() { - if ((mPrivateFlags & FOCUSED) != 0) { - mPrivateFlags &= ~FOCUSED; - - onFocusChanged(false, 0, null); - refreshDrawableState(); - - // The view cleared focus and invoked the callbacks, so now is the - // time to give focus to the the first focusable from the top to - // ensure that the gain focus is announced after clear focus. - getRootView().requestFocus(FOCUS_FORWARD); + void ensureInputFocusOnFirstFocusable() { + View root = getRootView(); + if (root != null) { + root.requestFocus(FOCUS_FORWARD); } } @@ -4562,11 +4676,26 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal } /** + * Indicates whether this view is one of the set of scrollable containers in + * its window. + * + * @return whether this view is one of the set of scrollable containers in + * its window + * + * @attr ref android.R.styleable#View_isScrollContainer + */ + public boolean isScrollContainer() { + return (mPrivateFlags & SCROLL_CONTAINER_ADDED) != 0; + } + + /** * Change whether this view is one of the set of scrollable containers in * its window. This will be used to determine whether the window can * resize or must pan when a soft input area is open -- scrollable * containers allow the window to use resize mode since the container * will appropriately shrink. + * + * @attr ref android.R.styleable#View_isScrollContainer */ public void setScrollContainer(boolean isScrollContainer) { if (isScrollContainer) { @@ -4898,7 +5027,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal @RemotableViewMethod public void setVisibility(int visibility) { setFlags(visibility, VISIBILITY_MASK); - if (mBGDrawable != null) mBGDrawable.setVisible(visibility == VISIBLE, false); + if (mBackground != null) mBackground.setVisible(visibility == VISIBLE, false); } /** @@ -5279,7 +5408,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * {@link #setPressed(boolean)} is explicitly called, only clickable views can enter * the pressed state. * - * @see #setPressed(boolean) + * @see #setPressed(boolean) * @see #isClickable() * @see #setClickable(boolean) * @@ -5965,7 +6094,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal /** * Dispatch a hover event. * <p> - * Do not call this method directly. + * Do not call this method directly. * Call {@link #dispatchGenericMotionEvent(MotionEvent)} instead. * </p> * @@ -6152,7 +6281,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * * @param visibility The new visibility of the window. * - * @see #onWindowVisibilityChanged(int) + * @see #onWindowVisibilityChanged(int) */ public void dispatchWindowVisibilityChanged(int visibility) { onWindowVisibilityChanged(visibility); @@ -6228,7 +6357,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * * @param newConfig The new resource configuration. * - * @see #onConfigurationChanged(android.content.res.Configuration) + * @see #onConfigurationChanged(android.content.res.Configuration) */ public void dispatchConfigurationChanged(Configuration newConfig) { onConfigurationChanged(newConfig); @@ -7096,7 +7225,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal if ((changed & DRAW_MASK) != 0) { if ((mViewFlags & WILL_NOT_DRAW) != 0) { - if (mBGDrawable != null) { + if (mBackground != null) { mPrivateFlags &= ~SKIP_DRAW; mPrivateFlags |= ONLY_DRAWS_BACKGROUND; } else { @@ -7484,39 +7613,39 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * views are drawn) from the camera to this view. The camera's distance * affects 3D transformations, for instance rotations around the X and Y * axis. If the rotationX or rotationY properties are changed and this view is - * large (more than half the size of the screen), it is recommended to always + * large (more than half the size of the screen), it is recommended to always * use a camera distance that's greater than the height (X axis rotation) or * the width (Y axis rotation) of this view.</p> - * + * * <p>The distance of the camera from the view plane can have an affect on the * perspective distortion of the view when it is rotated around the x or y axis. * For example, a large distance will result in a large viewing angle, and there * will not be much perspective distortion of the view as it rotates. A short - * distance may cause much more perspective distortion upon rotation, and can + * distance may cause much more perspective distortion upon rotation, and can * also result in some drawing artifacts if the rotated view ends up partially * behind the camera (which is why the recommendation is to use a distance at * least as far as the size of the view, if the view is to be rotated.)</p> - * + * * <p>The distance is expressed in "depth pixels." The default distance depends * on the screen density. For instance, on a medium density display, the * default distance is 1280. On a high density display, the default distance * is 1920.</p> - * + * * <p>If you want to specify a distance that leads to visually consistent * results across various densities, use the following formula:</p> * <pre> * float scale = context.getResources().getDisplayMetrics().density; * view.setCameraDistance(distance * scale); * </pre> - * + * * <p>The density scale factor of a high density display is 1.5, * and 1920 = 1280 * 1.5.</p> - * + * * @param distance The distance in "depth pixels", if negative the opposite * value is used - * - * @see #setRotationX(float) - * @see #setRotationY(float) + * + * @see #setRotationX(float) + * @see #setRotationY(float) */ public void setCameraDistance(float distance) { invalidateViewProperty(true, false); @@ -7541,10 +7670,10 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal /** * The degrees that the view is rotated around the pivot point. * - * @see #setRotation(float) + * @see #setRotation(float) * @see #getPivotX() * @see #getPivotY() - * + * * @return The degrees of rotation. */ @ViewDebug.ExportedProperty(category = "drawing") @@ -7557,12 +7686,12 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * result in clockwise rotation. * * @param rotation The degrees of rotation. - * - * @see #getRotation() + * + * @see #getRotation() * @see #getPivotX() * @see #getPivotY() - * @see #setRotationX(float) - * @see #setRotationY(float) + * @see #setRotationX(float) + * @see #setRotationY(float) * * @attr ref android.R.styleable#View_rotation */ @@ -7586,8 +7715,8 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * * @see #getPivotX() * @see #getPivotY() - * @see #setRotationY(float) - * + * @see #setRotationY(float) + * * @return The degrees of Y rotation. */ @ViewDebug.ExportedProperty(category = "drawing") @@ -7599,18 +7728,18 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * Sets the degrees that the view is rotated around the vertical axis through the pivot point. * Increasing values result in counter-clockwise rotation from the viewpoint of looking * down the y axis. - * + * * When rotating large views, it is recommended to adjust the camera distance * accordingly. Refer to {@link #setCameraDistance(float)} for more information. * * @param rotationY The degrees of Y rotation. - * - * @see #getRotationY() + * + * @see #getRotationY() * @see #getPivotX() * @see #getPivotY() * @see #setRotation(float) - * @see #setRotationX(float) - * @see #setCameraDistance(float) + * @see #setRotationX(float) + * @see #setCameraDistance(float) * * @attr ref android.R.styleable#View_rotationY */ @@ -7633,8 +7762,8 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * * @see #getPivotX() * @see #getPivotY() - * @see #setRotationX(float) - * + * @see #setRotationX(float) + * * @return The degrees of X rotation. */ @ViewDebug.ExportedProperty(category = "drawing") @@ -7646,18 +7775,18 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * Sets the degrees that the view is rotated around the horizontal axis through the pivot point. * Increasing values result in clockwise rotation from the viewpoint of looking down the * x axis. - * + * * When rotating large views, it is recommended to adjust the camera distance * accordingly. Refer to {@link #setCameraDistance(float)} for more information. * * @param rotationX The degrees of X rotation. - * - * @see #getRotationX() + * + * @see #getRotationX() * @see #getPivotX() * @see #getPivotY() * @see #setRotation(float) - * @see #setRotationY(float) - * @see #setCameraDistance(float) + * @see #setRotationY(float) + * @see #setCameraDistance(float) * * @attr ref android.R.styleable#View_rotationX */ @@ -7762,6 +7891,8 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * @see #getScaleY() * @see #getPivotY() * @return The x location of the pivot point. + * + * @attr ref android.R.styleable#View_transformPivotX */ @ViewDebug.ExportedProperty(category = "drawing") public float getPivotX() { @@ -7807,6 +7938,8 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * @see #getScaleY() * @see #getPivotY() * @return The y location of the pivot point. + * + * @attr ref android.R.styleable#View_transformPivotY */ @ViewDebug.ExportedProperty(category = "drawing") public float getPivotY() { @@ -9022,7 +9155,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal // - Background is opaque // - Doesn't have scrollbars or scrollbars are inside overlay - if (mBGDrawable != null && mBGDrawable.getOpacity() == PixelFormat.OPAQUE) { + if (mBackground != null && mBackground.getOpacity() == PixelFormat.OPAQUE) { mPrivateFlags |= OPAQUE_BACKGROUND; } else { mPrivateFlags &= ~OPAQUE_BACKGROUND; @@ -9070,7 +9203,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal /** * <p>Causes the Runnable to be added to the message queue. * The runnable will be run on the user interface thread.</p> - * + * * <p>This method can be invoked from outside of the UI thread * only when this View is attached to a window.</p> * @@ -9094,7 +9227,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * <p>Causes the Runnable to be added to the message queue, to be run * after the specified amount of time elapses. * The runnable will be run on the user interface thread.</p> - * + * * <p>This method can be invoked from outside of the UI thread * only when this View is attached to a window.</p> * @@ -9168,7 +9301,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal /** * <p>Removes the specified Runnable from the message queue.</p> - * + * * <p>This method can be invoked from outside of the UI thread * only when this View is attached to a window.</p> * @@ -9200,7 +9333,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * * <p>This method can be invoked from outside of the UI thread * only when this View is attached to a window.</p> - * + * * @see #invalidate() */ public void postInvalidate() { @@ -9210,7 +9343,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal /** * <p>Cause an invalidate of the specified area to happen on a subsequent cycle * through the event loop. Use this to invalidate the View from a non-UI thread.</p> - * + * * <p>This method can be invoked from outside of the UI thread * only when this View is attached to a window.</p> * @@ -9229,7 +9362,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal /** * <p>Cause an invalidate to happen on a subsequent cycle through the event * loop. Waits for the specified amount of time.</p> - * + * * <p>This method can be invoked from outside of the UI thread * only when this View is attached to a window.</p> * @@ -9248,7 +9381,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal /** * <p>Cause an invalidate of the specified area to happen on a subsequent cycle * through the event loop. Waits for the specified amount of time.</p> - * + * * <p>This method can be invoked from outside of the UI thread * only when this View is attached to a window.</p> * @@ -9358,6 +9491,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * otherwise * * @see #setHorizontalFadingEdgeEnabled(boolean) + * * @attr ref android.R.styleable#View_requiresFadingEdge */ public boolean isHorizontalFadingEdgeEnabled() { @@ -9373,6 +9507,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * horizontally * * @see #isHorizontalFadingEdgeEnabled() + * * @attr ref android.R.styleable#View_requiresFadingEdge */ public void setHorizontalFadingEdgeEnabled(boolean horizontalFadingEdgeEnabled) { @@ -9393,6 +9528,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * otherwise * * @see #setVerticalFadingEdgeEnabled(boolean) + * * @attr ref android.R.styleable#View_requiresFadingEdge */ public boolean isVerticalFadingEdgeEnabled() { @@ -9408,6 +9544,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * vertically * * @see #isVerticalFadingEdgeEnabled() + * * @attr ref android.R.styleable#View_requiresFadingEdge */ public void setVerticalFadingEdgeEnabled(boolean verticalFadingEdgeEnabled) { @@ -9550,6 +9687,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * * @param fadeScrollbars wheter to enable fading * + * @attr ref android.R.styleable#View_fadeScrollbars */ public void setScrollbarFadingEnabled(boolean fadeScrollbars) { initScrollCache(); @@ -9567,12 +9705,86 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * Returns true if scrollbars will fade when this view is not scrolling * * @return true if scrollbar fading is enabled + * + * @attr ref android.R.styleable#View_fadeScrollbars */ public boolean isScrollbarFadingEnabled() { return mScrollCache != null && mScrollCache.fadeScrollBars; } /** + * + * Returns the delay before scrollbars fade. + * + * @return the delay before scrollbars fade + * + * @attr ref android.R.styleable#View_scrollbarDefaultDelayBeforeFade + */ + public int getScrollBarDefaultDelayBeforeFade() { + return mScrollCache == null ? ViewConfiguration.getScrollDefaultDelay() : + mScrollCache.scrollBarDefaultDelayBeforeFade; + } + + /** + * Define the delay before scrollbars fade. + * + * @param scrollBarDefaultDelayBeforeFade - the delay before scrollbars fade + * + * @attr ref android.R.styleable#View_scrollbarDefaultDelayBeforeFade + */ + public void setScrollBarDefaultDelayBeforeFade(int scrollBarDefaultDelayBeforeFade) { + getScrollCache().scrollBarDefaultDelayBeforeFade = scrollBarDefaultDelayBeforeFade; + } + + /** + * + * Returns the scrollbar fade duration. + * + * @return the scrollbar fade duration + * + * @attr ref android.R.styleable#View_scrollbarFadeDuration + */ + public int getScrollBarFadeDuration() { + return mScrollCache == null ? ViewConfiguration.getScrollBarFadeDuration() : + mScrollCache.scrollBarFadeDuration; + } + + /** + * Define the scrollbar fade duration. + * + * @param scrollBarFadeDuration - the scrollbar fade duration + * + * @attr ref android.R.styleable#View_scrollbarFadeDuration + */ + public void setScrollBarFadeDuration(int scrollBarFadeDuration) { + getScrollCache().scrollBarFadeDuration = scrollBarFadeDuration; + } + + /** + * + * Returns the scrollbar size. + * + * @return the scrollbar size + * + * @attr ref android.R.styleable#View_scrollbarSize + */ + public int getScrollBarSize() { + return mScrollCache == null ? ViewConfiguration.getScrollBarSize() : + mScrollCache.scrollBarSize; + } + + /** + * Define the scrollbar size. + * + * @param scrollBarSize - the scrollbar size + * + * @attr ref android.R.styleable#View_scrollbarSize + */ + public void setScrollBarSize(int scrollBarSize) { + getScrollCache().scrollBarSize = scrollBarSize; + } + + /** * <p>Specify the style of the scrollbars. The scrollbars can be overlaid or * inset. When inset, they add to the padding of the view. And the scrollbars * can be drawn inside the padding area or on the edge of the view. For example, @@ -9588,6 +9800,8 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * @see #SCROLLBARS_INSIDE_INSET * @see #SCROLLBARS_OUTSIDE_OVERLAY * @see #SCROLLBARS_OUTSIDE_INSET + * + * @attr ref android.R.styleable#View_scrollbarStyle */ public void setScrollBarStyle(int style) { if (style != (mViewFlags & SCROLLBARS_STYLE_MASK)) { @@ -9604,6 +9818,8 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * @see #SCROLLBARS_INSIDE_INSET * @see #SCROLLBARS_OUTSIDE_OVERLAY * @see #SCROLLBARS_OUTSIDE_INSET + * + * @attr ref android.R.styleable#View_scrollbarStyle */ @ViewDebug.ExportedProperty(mapping = { @ViewDebug.IntToString(from = SCROLLBARS_INSIDE_OVERLAY, to = "INSIDE_OVERLAY"), @@ -9989,6 +10205,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal resolveLayoutDirection(); resolvePadding(); resolveTextDirection(); + resolveTextAlignment(); if (isFocused()) { InputMethodManager imm = InputMethodManager.peekInstance(); imm.focusIn(this); @@ -10218,6 +10435,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal mCurrentAnimation = null; resetResolvedLayoutDirection(); + resetResolvedTextAlignment(); } /** @@ -10348,9 +10566,9 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * * @param container The SparseArray in which to save the view's state. * - * @see #restoreHierarchyState(android.util.SparseArray) - * @see #dispatchSaveInstanceState(android.util.SparseArray) - * @see #onSaveInstanceState() + * @see #restoreHierarchyState(android.util.SparseArray) + * @see #dispatchSaveInstanceState(android.util.SparseArray) + * @see #onSaveInstanceState() */ public void saveHierarchyState(SparseArray<Parcelable> container) { dispatchSaveInstanceState(container); @@ -10363,9 +10581,9 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * * @param container The SparseArray in which to save the view's state. * - * @see #dispatchRestoreInstanceState(android.util.SparseArray) - * @see #saveHierarchyState(android.util.SparseArray) - * @see #onSaveInstanceState() + * @see #dispatchRestoreInstanceState(android.util.SparseArray) + * @see #saveHierarchyState(android.util.SparseArray) + * @see #onSaveInstanceState() */ protected void dispatchSaveInstanceState(SparseArray<Parcelable> container) { if (mID != NO_ID && (mViewFlags & SAVE_DISABLED_MASK) == 0) { @@ -10399,9 +10617,9 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * @return Returns a Parcelable object containing the view's current dynamic * state, or null if there is nothing interesting to save. The * default implementation returns null. - * @see #onRestoreInstanceState(android.os.Parcelable) - * @see #saveHierarchyState(android.util.SparseArray) - * @see #dispatchSaveInstanceState(android.util.SparseArray) + * @see #onRestoreInstanceState(android.os.Parcelable) + * @see #saveHierarchyState(android.util.SparseArray) + * @see #dispatchSaveInstanceState(android.util.SparseArray) * @see #setSaveEnabled(boolean) */ protected Parcelable onSaveInstanceState() { @@ -10414,9 +10632,9 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * * @param container The SparseArray which holds previously frozen states. * - * @see #saveHierarchyState(android.util.SparseArray) - * @see #dispatchRestoreInstanceState(android.util.SparseArray) - * @see #onRestoreInstanceState(android.os.Parcelable) + * @see #saveHierarchyState(android.util.SparseArray) + * @see #dispatchRestoreInstanceState(android.util.SparseArray) + * @see #onRestoreInstanceState(android.os.Parcelable) */ public void restoreHierarchyState(SparseArray<Parcelable> container) { dispatchRestoreInstanceState(container); @@ -10430,9 +10648,9 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * * @param container The SparseArray which holds previously saved state. * - * @see #dispatchSaveInstanceState(android.util.SparseArray) - * @see #restoreHierarchyState(android.util.SparseArray) - * @see #onRestoreInstanceState(android.os.Parcelable) + * @see #dispatchSaveInstanceState(android.util.SparseArray) + * @see #restoreHierarchyState(android.util.SparseArray) + * @see #onRestoreInstanceState(android.os.Parcelable) */ protected void dispatchRestoreInstanceState(SparseArray<Parcelable> container) { if (mID != NO_ID) { @@ -10458,9 +10676,9 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * @param state The frozen state that had previously been returned by * {@link #onSaveInstanceState}. * - * @see #onSaveInstanceState() - * @see #restoreHierarchyState(android.util.SparseArray) - * @see #dispatchRestoreInstanceState(android.util.SparseArray) + * @see #onSaveInstanceState() + * @see #restoreHierarchyState(android.util.SparseArray) + * @see #dispatchRestoreInstanceState(android.util.SparseArray) */ protected void onRestoreInstanceState(Parcelable state) { mPrivateFlags |= SAVE_STATE_CALLED; @@ -10614,7 +10832,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * {@link #LAYER_TYPE_HARDWARE} * * @see #setLayerType(int, android.graphics.Paint) - * @see #buildLayer() + * @see #buildLayer() * @see #LAYER_TYPE_NONE * @see #LAYER_TYPE_SOFTWARE * @see #LAYER_TYPE_HARDWARE @@ -10627,14 +10845,14 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * Forces this view's layer to be created and this view to be rendered * into its layer. If this view's layer type is set to {@link #LAYER_TYPE_NONE}, * invoking this method will have no effect. - * + * * This method can for instance be used to render a view into its layer before * starting an animation. If this view is complex, rendering into the layer * before starting the animation will avoid skipping frames. - * + * * @throws IllegalStateException If this view is not attached to a window - * - * @see #setLayerType(int, android.graphics.Paint) + * + * @see #setLayerType(int, android.graphics.Paint) */ public void buildLayer() { if (mLayerType == LAYER_TYPE_NONE) return; @@ -10656,7 +10874,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal break; } } - + // Make sure the HardwareRenderer.validate() was invoked before calling this method void flushLayer() { if (mLayerType == LAYER_TYPE_HARDWARE && mHardwareLayer != null) { @@ -10675,7 +10893,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal !mAttachInfo.mHardwareRenderer.isEnabled()) { return null; } - + if (!mAttachInfo.mHardwareRenderer.validate()) return null; final int width = mRight - mLeft; @@ -10709,10 +10927,10 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal /** * Destroys this View's hardware layer if possible. - * + * * @return True if the layer was destroyed, false otherwise. - * - * @see #setLayerType(int, android.graphics.Paint) + * + * @see #setLayerType(int, android.graphics.Paint) * @see #LAYER_TYPE_HARDWARE */ boolean destroyLayer(boolean valid) { @@ -10736,11 +10954,11 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * Destroys all hardware rendering resources. This method is invoked * when the system needs to reclaim resources. Upon execution of this * method, you should free any OpenGL resources created by the view. - * + * * Note: you <strong>must</strong> call * <code>super.destroyHardwareResources()</code> when overriding * this method. - * + * * @hide */ protected void destroyHardwareResources() { @@ -11451,17 +11669,17 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal if (offsetRequired) top += getTopPaddingOffset(); return top; } - + /** * @hide * @param offsetRequired */ protected int getFadeHeight(boolean offsetRequired) { int padding = mPaddingTop; - if (offsetRequired) padding += getTopPaddingOffset(); + if (offsetRequired) padding += getTopPaddingOffset(); return mBottom - mTop - mPaddingBottom - padding; } - + /** * <p>Indicates whether this view is attached to a hardware accelerated * window or not.</p> @@ -11962,7 +12180,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal int saveCount; if (!dirtyOpaque) { - final Drawable background = mBGDrawable; + final Drawable background = mBackground; if (background != null) { final int scrollX = mScrollX; final int scrollY = mScrollY; @@ -12036,7 +12254,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal } final ScrollabilityCache scrollabilityCache = mScrollCache; - final float fadeHeight = scrollabilityCache.fadingEdgeLength; + final float fadeHeight = scrollabilityCache.fadingEdgeLength; int length = (int) fadeHeight; // clip the fade length if top and bottom fades overlap @@ -12143,8 +12361,8 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * optimize the drawing of the fading edges. If you do return a non-zero color, the alpha * should be set to 0xFF. * - * @see #setVerticalFadingEdgeEnabled(boolean) - * @see #setHorizontalFadingEdgeEnabled(boolean) + * @see #setVerticalFadingEdgeEnabled(boolean) + * @see #setHorizontalFadingEdgeEnabled(boolean) * * @return The known solid color background for this view, or 0 if the color may vary */ @@ -12493,7 +12711,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * @param who the Drawable to query */ public int getResolvedLayoutDirection(Drawable who) { - return (who == mBGDrawable) ? getResolvedLayoutDirection() : LAYOUT_DIRECTION_DEFAULT; + return (who == mBackground) ? getResolvedLayoutDirection() : LAYOUT_DIRECTION_DEFAULT; } /** @@ -12512,11 +12730,11 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * @return boolean If true than the Drawable is being displayed in the * view; else false and it is not allowed to animate. * - * @see #unscheduleDrawable(android.graphics.drawable.Drawable) - * @see #drawableStateChanged() + * @see #unscheduleDrawable(android.graphics.drawable.Drawable) + * @see #drawableStateChanged() */ protected boolean verifyDrawable(Drawable who) { - return who == mBGDrawable; + return who == mBackground; } /** @@ -12526,10 +12744,10 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * <p>Be sure to call through to the superclass when overriding this * function. * - * @see Drawable#setState(int[]) + * @see Drawable#setState(int[]) */ protected void drawableStateChanged() { - Drawable d = mBGDrawable; + Drawable d = mBackground; if (d != null && d.isStateful()) { d.setState(getDrawableState()); } @@ -12559,9 +12777,9 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * * @return The current drawable state * - * @see Drawable#setState(int[]) - * @see #drawableStateChanged() - * @see #onCreateDrawableState(int) + * @see Drawable#setState(int[]) + * @see #drawableStateChanged() + * @see #onCreateDrawableState(int) */ public final int[] getDrawableState() { if ((mDrawableState != null) && ((mPrivateFlags & DRAWABLE_STATE_DIRTY) == 0)) { @@ -12586,7 +12804,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * @return Returns an array holding the current {@link Drawable} state of * the view. * - * @see #mergeDrawableStates(int[], int[]) + * @see #mergeDrawableStates(int[], int[]) */ protected int[] onCreateDrawableState(int extraSpace) { if ((mViewFlags & DUPLICATE_PARENT_STATE) == DUPLICATE_PARENT_STATE && @@ -12662,7 +12880,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * @return As a convenience, the <var>baseState</var> array you originally * passed into the function is returned. * - * @see #onCreateDrawableState(int) + * @see #onCreateDrawableState(int) */ protected static int[] mergeDrawableStates(int[] baseState, int[] additionalState) { final int N = baseState.length; @@ -12679,8 +12897,8 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * on all Drawable objects associated with this view. */ public void jumpDrawablesToCurrentState() { - if (mBGDrawable != null) { - mBGDrawable.jumpToCurrentState(); + if (mBackground != null) { + mBackground.jumpToCurrentState(); } } @@ -12690,10 +12908,10 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal */ @RemotableViewMethod public void setBackgroundColor(int color) { - if (mBGDrawable instanceof ColorDrawable) { - ((ColorDrawable) mBGDrawable).setColor(color); + if (mBackground instanceof ColorDrawable) { + ((ColorDrawable) mBackground).setColor(color); } else { - setBackgroundDrawable(new ColorDrawable(color)); + setBackground(new ColorDrawable(color)); } } @@ -12701,6 +12919,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * Set the background to a given resource. The resource should refer to * a Drawable object or 0 to remove the background. * @param resid The identifier of the resource. + * * @attr ref android.R.styleable#View_background */ @RemotableViewMethod @@ -12713,7 +12932,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal if (resid != 0) { d = mResources.getDrawable(resid); } - setBackgroundDrawable(d); + setBackground(d); mBackgroundResource = resid; } @@ -12725,11 +12944,19 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * touched. If setting the padding is desired, please use * {@link #setPadding(int, int, int, int)}. * - * @param d The Drawable to use as the background, or null to remove the + * @param background The Drawable to use as the background, or null to remove the * background */ - public void setBackgroundDrawable(Drawable d) { - if (d == mBGDrawable) { + public void setBackground(Drawable background) { + setBackgroundDrawable(background); + } + + /** + * @deprecated use {@link #setBackground(Drawable)} instead + */ + @Deprecated + public void setBackgroundDrawable(Drawable background) { + if (background == mBackground) { return; } @@ -12741,19 +12968,19 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * Regardless of whether we're setting a new background or not, we want * to clear the previous drawable. */ - if (mBGDrawable != null) { - mBGDrawable.setCallback(null); - unscheduleDrawable(mBGDrawable); + if (mBackground != null) { + mBackground.setCallback(null); + unscheduleDrawable(mBackground); } - if (d != null) { + if (background != null) { Rect padding = sThreadLocal.get(); if (padding == null) { padding = new Rect(); sThreadLocal.set(padding); } - if (d.getPadding(padding)) { - switch (d.getResolvedLayoutDirectionSelf()) { + if (background.getPadding(padding)) { + switch (background.getResolvedLayoutDirectionSelf()) { case LAYOUT_DIRECTION_RTL: setPadding(padding.right, padding.top, padding.left, padding.bottom); break; @@ -12765,17 +12992,17 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal // Compare the minimum sizes of the old Drawable and the new. If there isn't an old or // if it has a different minimum size, we should layout again - if (mBGDrawable == null || mBGDrawable.getMinimumHeight() != d.getMinimumHeight() || - mBGDrawable.getMinimumWidth() != d.getMinimumWidth()) { + if (mBackground == null || mBackground.getMinimumHeight() != background.getMinimumHeight() || + mBackground.getMinimumWidth() != background.getMinimumWidth()) { requestLayout = true; } - d.setCallback(this); - if (d.isStateful()) { - d.setState(getDrawableState()); + background.setCallback(this); + if (background.isStateful()) { + background.setState(getDrawableState()); } - d.setVisible(getVisibility() == VISIBLE, false); - mBGDrawable = d; + background.setVisible(getVisibility() == VISIBLE, false); + mBackground = background; if ((mPrivateFlags & SKIP_DRAW) != 0) { mPrivateFlags &= ~SKIP_DRAW; @@ -12784,7 +13011,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal } } else { /* Remove the background */ - mBGDrawable = null; + mBackground = null; if ((mPrivateFlags & ONLY_DRAWS_BACKGROUND) != 0) { /* @@ -12820,10 +13047,15 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal /** * Gets the background drawable + * * @return The drawable used as the background for this view, if any. + * + * @see #setBackground(Drawable) + * + * @attr ref android.R.styleable#View_background */ public Drawable getBackground() { - return mBGDrawable; + return mBackground; } /** @@ -13364,8 +13596,8 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * number. * * @see #NO_ID - * @see #getId() - * @see #findViewById(int) + * @see #getId() + * @see #findViewById(int) * * @param id a number used to identify the view * @@ -13404,8 +13636,8 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * @return a positive integer used to identify the view or {@link #NO_ID} * if the view has no ID * - * @see #setId(int) - * @see #findViewById(int) + * @see #setId(int) + * @see #findViewById(int) * @attr ref android.R.styleable#View_id */ @ViewDebug.CapturedViewProperty @@ -13917,16 +14149,8 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * @return The suggested minimum height of the view. */ protected int getSuggestedMinimumHeight() { - int suggestedMinHeight = mMinHeight; + return (mBackground == null) ? mMinHeight : max(mMinHeight, mBackground.getMinimumHeight()); - if (mBGDrawable != null) { - final int bgMinHeight = mBGDrawable.getMinimumHeight(); - if (suggestedMinHeight < bgMinHeight) { - suggestedMinHeight = bgMinHeight; - } - } - - return suggestedMinHeight; } /** @@ -13941,16 +14165,20 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * @return The suggested minimum width of the view. */ protected int getSuggestedMinimumWidth() { - int suggestedMinWidth = mMinWidth; - - if (mBGDrawable != null) { - final int bgMinWidth = mBGDrawable.getMinimumWidth(); - if (suggestedMinWidth < bgMinWidth) { - suggestedMinWidth = bgMinWidth; - } - } + return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth()); + } - return suggestedMinWidth; + /** + * Returns the minimum height of the view. + * + * @return the minimum height the view will try to be. + * + * @see #setMinimumHeight(int) + * + * @attr ref android.R.styleable#View_minHeight + */ + public int getMinimumHeight() { + return mMinHeight; } /** @@ -13959,9 +14187,27 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * constrains it with less available height). * * @param minHeight The minimum height the view will try to be. + * + * @see #getMinimumHeight() + * + * @attr ref android.R.styleable#View_minHeight */ public void setMinimumHeight(int minHeight) { mMinHeight = minHeight; + requestLayout(); + } + + /** + * Returns the minimum width of the view. + * + * @return the minimum width the view will try to be. + * + * @see #setMinimumWidth(int) + * + * @attr ref android.R.styleable#View_minWidth + */ + public int getMinimumWidth() { + return mMinWidth; } /** @@ -13970,9 +14216,15 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * constrains it with less available width). * * @param minWidth The minimum width the view will try to be. + * + * @see #getMinimumWidth() + * + * @attr ref android.R.styleable#View_minWidth */ public void setMinimumWidth(int minWidth) { mMinWidth = minWidth; + requestLayout(); + } /** @@ -14091,11 +14343,11 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal getLocationInWindow(location); region.op(location[0], location[1], location[0] + mRight - mLeft, location[1] + mBottom - mTop, Region.Op.DIFFERENCE); - } else if ((pflags & ONLY_DRAWS_BACKGROUND) != 0 && mBGDrawable != null) { + } else if ((pflags & ONLY_DRAWS_BACKGROUND) != 0 && mBackground != null) { // The ONLY_DRAWS_BACKGROUND flag IS set and the background drawable // exists, so we remove the background drawable's non-transparent // parts from this transparent region. - applyDrawableToTransparentRegion(mBGDrawable, region); + applyDrawableToTransparentRegion(mBackground, region); } } return true; @@ -14162,9 +14414,48 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal } /** - * Request that the visibility of the status bar be changed. - * @param visibility Bitwise-or of flags {@link #SYSTEM_UI_FLAG_LOW_PROFILE} or - * {@link #SYSTEM_UI_FLAG_HIDE_NAVIGATION}. + * Request that the visibility of the status bar or other screen/window + * decorations be changed. + * + * <p>This method is used to put the over device UI into temporary modes + * where the user's attention is focused more on the application content, + * by dimming or hiding surrounding system affordances. This is typically + * used in conjunction with {@link Window#FEATURE_ACTION_BAR_OVERLAY + * Window.FEATURE_ACTION_BAR_OVERLAY}, allowing the applications content + * to be placed behind the action bar (and with these flags other system + * affordances) so that smooth transitions between hiding and showing them + * can be done. + * + * <p>Two representative examples of the use of system UI visibility is + * implementing a content browsing application (like a magazine reader) + * and a video playing application. + * + * <p>The first code shows a typical implementation of a View in a content + * browsing application. In this implementation, the application goes + * into a content-oriented mode by hiding the status bar and action bar, + * and putting the navigation elements into lights out mode. The user can + * then interact with content while in this mode. Such an application should + * provide an easy way for the user to toggle out of the mode (such as to + * check information in the status bar or access notifications). In the + * implementation here, this is done simply by tapping on the content. + * + * {@sample development/samples/ApiDemos/src/com/example/android/apis/view/ContentBrowserActivity.java + * content} + * + * <p>This second code sample shows a typical implementation of a View + * in a video playing application. In this situation, while the video is + * playing the application would like to go into a complete full-screen mode, + * to use as much of the display as possible for the video. When in this state + * the user can not interact with the application; the system intercepts + * touching on the screen to pop the UI out of full screen mode. + * + * {@sample development/samples/ApiDemos/src/com/example/android/apis/view/VideoPlayerActivity.java + * content} + * + * @param visibility Bitwise-or of flags {@link #SYSTEM_UI_FLAG_LOW_PROFILE}, + * {@link #SYSTEM_UI_FLAG_HIDE_NAVIGATION}, {@link #SYSTEM_UI_FLAG_FULLSCREEN}, + * {@link #SYSTEM_UI_FLAG_LAYOUT_STABLE}, {@link #SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION}, + * and {@link #SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN}. */ public void setSystemUiVisibility(int visibility) { if (visibility != mSystemUiVisibility) { @@ -14176,9 +14467,11 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal } /** - * Returns the status bar visibility that this view has requested. - * @return Bitwise-or of flags {@link #SYSTEM_UI_FLAG_LOW_PROFILE} or - * {@link #SYSTEM_UI_FLAG_HIDE_NAVIGATION}. + * Returns the last {@link #setSystemUiVisibility(int) that this view has requested. + * @return Bitwise-or of flags {@link #SYSTEM_UI_FLAG_LOW_PROFILE}, + * {@link #SYSTEM_UI_FLAG_HIDE_NAVIGATION}, {@link #SYSTEM_UI_FLAG_FULLSCREEN}, + * {@link #SYSTEM_UI_FLAG_LAYOUT_STABLE}, {@link #SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION}, + * and {@link #SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN}. */ public int getSystemUiVisibility() { return mSystemUiVisibility; @@ -14766,7 +15059,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * {@link #TEXT_DIRECTION_ANY_RTL}, * {@link #TEXT_DIRECTION_LTR}, * {@link #TEXT_DIRECTION_RTL}, - * {@link #TEXT_DIRECTION_LOCALE}, + * {@link #TEXT_DIRECTION_LOCALE} */ @ViewDebug.ExportedProperty(category = "text", mapping = { @ViewDebug.IntToString(from = TEXT_DIRECTION_INHERIT, to = "INHERIT"), @@ -14790,7 +15083,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * {@link #TEXT_DIRECTION_ANY_RTL}, * {@link #TEXT_DIRECTION_LTR}, * {@link #TEXT_DIRECTION_RTL}, - * {@link #TEXT_DIRECTION_LOCALE}, + * {@link #TEXT_DIRECTION_LOCALE} */ public void setTextDirection(int textDirection) { if (getTextDirection() != textDirection) { @@ -14799,6 +15092,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal resetResolvedTextDirection(); // Set the new text direction mPrivateFlags2 |= ((textDirection << TEXT_DIRECTION_MASK_SHIFT) & TEXT_DIRECTION_MASK); + // Refresh requestLayout(); invalidate(true); } @@ -14818,7 +15112,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * {@link #TEXT_DIRECTION_ANY_RTL}, * {@link #TEXT_DIRECTION_LTR}, * {@link #TEXT_DIRECTION_RTL}, - * {@link #TEXT_DIRECTION_LOCALE}, + * {@link #TEXT_DIRECTION_LOCALE} */ public int getResolvedTextDirection() { // The text direction will be resolved only if needed @@ -14927,6 +15221,199 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal public void onResolvedTextDirectionReset() { } + /** + * Return the value specifying the text alignment or policy that was set with + * {@link #setTextAlignment(int)}. + * + * @return the defined text alignment. It can be one of: + * + * {@link #TEXT_ALIGNMENT_INHERIT}, + * {@link #TEXT_ALIGNMENT_GRAVITY}, + * {@link #TEXT_ALIGNMENT_CENTER}, + * {@link #TEXT_ALIGNMENT_TEXT_START}, + * {@link #TEXT_ALIGNMENT_TEXT_END}, + * {@link #TEXT_ALIGNMENT_VIEW_START}, + * {@link #TEXT_ALIGNMENT_VIEW_END} + */ + @ViewDebug.ExportedProperty(category = "text", mapping = { + @ViewDebug.IntToString(from = TEXT_ALIGNMENT_INHERIT, to = "INHERIT"), + @ViewDebug.IntToString(from = TEXT_ALIGNMENT_GRAVITY, to = "GRAVITY"), + @ViewDebug.IntToString(from = TEXT_ALIGNMENT_TEXT_START, to = "TEXT_START"), + @ViewDebug.IntToString(from = TEXT_ALIGNMENT_TEXT_END, to = "TEXT_END"), + @ViewDebug.IntToString(from = TEXT_ALIGNMENT_CENTER, to = "CENTER"), + @ViewDebug.IntToString(from = TEXT_ALIGNMENT_VIEW_START, to = "VIEW_START"), + @ViewDebug.IntToString(from = TEXT_ALIGNMENT_VIEW_END, to = "VIEW_END") + }) + public int getTextAlignment() { + return (mPrivateFlags2 & TEXT_ALIGNMENT_MASK) >> TEXT_ALIGNMENT_MASK_SHIFT; + } + + /** + * Set the text alignment. + * + * @param textAlignment The text alignment to set. Should be one of + * + * {@link #TEXT_ALIGNMENT_INHERIT}, + * {@link #TEXT_ALIGNMENT_GRAVITY}, + * {@link #TEXT_ALIGNMENT_CENTER}, + * {@link #TEXT_ALIGNMENT_TEXT_START}, + * {@link #TEXT_ALIGNMENT_TEXT_END}, + * {@link #TEXT_ALIGNMENT_VIEW_START}, + * {@link #TEXT_ALIGNMENT_VIEW_END} + * + * @attr ref android.R.styleable#View_textAlignment + */ + public void setTextAlignment(int textAlignment) { + if (textAlignment != getTextAlignment()) { + // Reset the current and resolved text alignment + mPrivateFlags2 &= ~TEXT_ALIGNMENT_MASK; + resetResolvedTextAlignment(); + // Set the new text alignment + mPrivateFlags2 |= ((textAlignment << TEXT_ALIGNMENT_MASK_SHIFT) & TEXT_ALIGNMENT_MASK); + // Refresh + requestLayout(); + invalidate(true); + } + } + + /** + * Return the resolved text alignment. + * + * The resolved text alignment. This needs resolution if the value is + * TEXT_ALIGNMENT_INHERIT. The resolution matches {@link #setTextAlignment(int)} if it is + * not TEXT_ALIGNMENT_INHERIT, otherwise resolution proceeds up the parent chain of the view. + * + * @return the resolved text alignment. Returns one of: + * + * {@link #TEXT_ALIGNMENT_GRAVITY}, + * {@link #TEXT_ALIGNMENT_CENTER}, + * {@link #TEXT_ALIGNMENT_TEXT_START}, + * {@link #TEXT_ALIGNMENT_TEXT_END}, + * {@link #TEXT_ALIGNMENT_VIEW_START}, + * {@link #TEXT_ALIGNMENT_VIEW_END} + */ + @ViewDebug.ExportedProperty(category = "text", mapping = { + @ViewDebug.IntToString(from = TEXT_ALIGNMENT_INHERIT, to = "INHERIT"), + @ViewDebug.IntToString(from = TEXT_ALIGNMENT_GRAVITY, to = "GRAVITY"), + @ViewDebug.IntToString(from = TEXT_ALIGNMENT_TEXT_START, to = "TEXT_START"), + @ViewDebug.IntToString(from = TEXT_ALIGNMENT_TEXT_END, to = "TEXT_END"), + @ViewDebug.IntToString(from = TEXT_ALIGNMENT_CENTER, to = "CENTER"), + @ViewDebug.IntToString(from = TEXT_ALIGNMENT_VIEW_START, to = "VIEW_START"), + @ViewDebug.IntToString(from = TEXT_ALIGNMENT_VIEW_END, to = "VIEW_END") + }) + public int getResolvedTextAlignment() { + // If text alignment is not resolved, then resolve it + if ((mPrivateFlags2 & TEXT_ALIGNMENT_RESOLVED) != TEXT_ALIGNMENT_RESOLVED) { + resolveTextAlignment(); + } + return (mPrivateFlags2 & TEXT_ALIGNMENT_RESOLVED_MASK) >> TEXT_ALIGNMENT_RESOLVED_MASK_SHIFT; + } + + /** + * Resolve the text alignment. Will call {@link View#onResolvedTextAlignmentChanged} when + * resolution is done. + */ + public void resolveTextAlignment() { + // Reset any previous text alignment resolution + mPrivateFlags2 &= ~(TEXT_ALIGNMENT_RESOLVED | TEXT_ALIGNMENT_RESOLVED_MASK); + + if (hasRtlSupport()) { + // Set resolved text alignment flag depending on text alignment flag + final int textAlignment = getTextAlignment(); + switch (textAlignment) { + case TEXT_ALIGNMENT_INHERIT: + // Check if we can resolve the text alignment + if (canResolveLayoutDirection() && mParent instanceof View) { + View view = (View) mParent; + + final int parentResolvedTextAlignment = view.getResolvedTextAlignment(); + switch (parentResolvedTextAlignment) { + case TEXT_ALIGNMENT_GRAVITY: + case TEXT_ALIGNMENT_TEXT_START: + case TEXT_ALIGNMENT_TEXT_END: + case TEXT_ALIGNMENT_CENTER: + case TEXT_ALIGNMENT_VIEW_START: + case TEXT_ALIGNMENT_VIEW_END: + // Resolved text alignment is the same as the parent resolved + // text alignment + mPrivateFlags2 |= + (parentResolvedTextAlignment << TEXT_ALIGNMENT_RESOLVED_MASK_SHIFT); + break; + default: + // Use default resolved text alignment + mPrivateFlags2 |= TEXT_ALIGNMENT_RESOLVED_DEFAULT; + } + } + else { + // We cannot do the resolution if there is no parent so use the default + mPrivateFlags2 |= TEXT_ALIGNMENT_RESOLVED_DEFAULT; + } + break; + case TEXT_ALIGNMENT_GRAVITY: + case TEXT_ALIGNMENT_TEXT_START: + case TEXT_ALIGNMENT_TEXT_END: + case TEXT_ALIGNMENT_CENTER: + case TEXT_ALIGNMENT_VIEW_START: + case TEXT_ALIGNMENT_VIEW_END: + // Resolved text alignment is the same as text alignment + mPrivateFlags2 |= (textAlignment << TEXT_ALIGNMENT_RESOLVED_MASK_SHIFT); + break; + default: + // Use default resolved text alignment + mPrivateFlags2 |= TEXT_ALIGNMENT_RESOLVED_DEFAULT; + } + } else { + // Use default resolved text alignment + mPrivateFlags2 |= TEXT_ALIGNMENT_RESOLVED_DEFAULT; + } + + // Set the resolved + mPrivateFlags2 |= TEXT_ALIGNMENT_RESOLVED; + onResolvedTextAlignmentChanged(); + } + + /** + * Check if text alignment resolution can be done. + * + * @return true if text alignment resolution can be done otherwise return false. + */ + public boolean canResolveTextAlignment() { + switch (getTextAlignment()) { + case TEXT_DIRECTION_INHERIT: + return (mParent != null); + default: + return true; + } + } + + /** + * Called when text alignment has been resolved. Subclasses that care about text alignment + * resolution should override this method. + * + * The default implementation does nothing. + */ + public void onResolvedTextAlignmentChanged() { + } + + /** + * Reset resolved text alignment. Text alignment can be resolved with a call to + * getResolvedTextAlignment(). Will call {@link View#onResolvedTextAlignmentReset} when + * reset is done. + */ + public void resetResolvedTextAlignment() { + // Reset any previous text alignment resolution + mPrivateFlags2 &= ~(TEXT_ALIGNMENT_RESOLVED | TEXT_ALIGNMENT_RESOLVED_MASK); + onResolvedTextAlignmentReset(); + } + + /** + * Called when text alignment is reset. Subclasses that care about text alignment reset should + * override this method and do a reset of the text alignment of their children. The default + * implementation does nothing. + */ + public void onResolvedTextAlignmentReset() { + } + // // Properties // @@ -15419,7 +15906,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * visibility. This reports <strong>global</strong> changes to the system UI * state, not just what the application is requesting. * - * @see View#setOnSystemUiVisibilityChangeListener(android.view.View.OnSystemUiVisibilityChangeListener) + * @see View#setOnSystemUiVisibilityChangeListener(android.view.View.OnSystemUiVisibilityChangeListener) */ public interface OnSystemUiVisibilityChangeListener { /** diff --git a/core/java/android/view/ViewConfiguration.java b/core/java/android/view/ViewConfiguration.java index b9924c7..9d06145 100644 --- a/core/java/android/view/ViewConfiguration.java +++ b/core/java/android/view/ViewConfiguration.java @@ -315,7 +315,7 @@ public class ViewConfiguration { if (!sHasPermanentMenuKeySet) { IWindowManager wm = Display.getWindowManager(); try { - sHasPermanentMenuKey = wm.canStatusBarHide() && !wm.hasNavigationBar(); + sHasPermanentMenuKey = !wm.hasSystemNavBar() && !wm.hasNavigationBar(); sHasPermanentMenuKeySet = true; } catch (RemoteException ex) { sHasPermanentMenuKey = false; diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java index d5c783f..121b544 100644 --- a/core/java/android/view/ViewGroup.java +++ b/core/java/android/view/ViewGroup.java @@ -3375,7 +3375,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager boolean clearChildFocus = false; if (view == mFocused) { - view.clearFocusForRemoval(); + view.unFocus(); clearChildFocus = true; } @@ -3398,6 +3398,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager if (clearChildFocus) { clearChildFocus(view); + ensureInputFocusOnFirstFocusable(); } } @@ -3450,7 +3451,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager } if (view == focused) { - view.clearFocusForRemoval(); + view.unFocus(); clearChildFocus = view; } @@ -3474,6 +3475,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager if (clearChildFocus != null) { clearChildFocus(clearChildFocus); + ensureInputFocusOnFirstFocusable(); } } @@ -3519,7 +3521,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager } if (view == focused) { - view.clearFocusForRemoval(); + view.unFocus(); clearChildFocus = view; } @@ -3542,6 +3544,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager if (clearChildFocus != null) { clearChildFocus(clearChildFocus); + ensureInputFocusOnFirstFocusable(); } } @@ -5056,6 +5059,18 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager } } + @Override + public void onResolvedTextAlignmentReset() { + // Take care of resetting the children resolution too + final int count = getChildCount(); + for (int i = 0; i < count; i++) { + final View child = getChildAt(i); + if (child.getTextAlignment() == TEXT_ALIGNMENT_INHERIT) { + child.resetResolvedTextAlignment(); + } + } + } + /** * Return true if the pressed state should be delayed for children or descendants of this * ViewGroup. Generally, this should be done for containers that can scroll, such as a List. diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index d72f3b7..899fb32 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -2039,9 +2039,10 @@ public final class ViewRootImpl implements ViewParent, scrollToRectOrFocus(null, false); - if (mAttachInfo.mViewScrollChanged) { - mAttachInfo.mViewScrollChanged = false; - mAttachInfo.mTreeObserver.dispatchOnScrollChanged(); + final AttachInfo attachInfo = mAttachInfo; + if (attachInfo.mViewScrollChanged) { + attachInfo.mViewScrollChanged = false; + attachInfo.mTreeObserver.dispatchOnScrollChanged(); } int yoff; @@ -2056,8 +2057,8 @@ public final class ViewRootImpl implements ViewParent, fullRedrawNeeded = true; } - final float appScale = mAttachInfo.mApplicationScale; - final boolean scalingRequired = mAttachInfo.mScalingRequired; + final float appScale = attachInfo.mApplicationScale; + final boolean scalingRequired = attachInfo.mScalingRequired; int resizeAlpha = 0; if (mResizeBuffer != null) { @@ -2086,7 +2087,7 @@ public final class ViewRootImpl implements ViewParent, } if (fullRedrawNeeded) { - mAttachInfo.mIgnoreDirtyState = true; + attachInfo.mIgnoreDirtyState = true; dirty.set(0, 0, (int) (mWidth * appScale + 0.5f), (int) (mHeight * appScale + 0.5f)); } @@ -2099,8 +2100,10 @@ public final class ViewRootImpl implements ViewParent, appScale + ", width=" + mWidth + ", height=" + mHeight); } + attachInfo.mTreeObserver.dispatchOnDraw(); + if (!dirty.isEmpty() || mIsAnimating) { - if (mAttachInfo.mHardwareRenderer != null && mAttachInfo.mHardwareRenderer.isEnabled()) { + if (attachInfo.mHardwareRenderer != null && attachInfo.mHardwareRenderer.isEnabled()) { // Draw with hardware renderer. mIsAnimating = false; mHardwareYOffset = yoff; @@ -2111,154 +2114,164 @@ public final class ViewRootImpl implements ViewParent, mPreviousDirty.set(dirty); dirty.setEmpty(); - if (mAttachInfo.mHardwareRenderer.draw(mView, mAttachInfo, this, + if (attachInfo.mHardwareRenderer.draw(mView, attachInfo, this, animating ? null : mCurrentDirty)) { mPreviousDirty.set(0, 0, mWidth, mHeight); } - } else { - // Draw with software renderer. - Canvas canvas; - try { - int left = dirty.left; - int top = dirty.top; - int right = dirty.right; - int bottom = dirty.bottom; - - final long lockCanvasStartTime; - if (ViewDebug.DEBUG_LATENCY) { - lockCanvasStartTime = System.nanoTime(); - } - - canvas = mSurface.lockCanvas(dirty); + } else if (!drawSoftware(surface, attachInfo, yoff, scalingRequired, dirty)) { + return; + } + } - if (ViewDebug.DEBUG_LATENCY) { - long now = System.nanoTime(); - Log.d(ViewDebug.DEBUG_LATENCY_TAG, "- lockCanvas() took " - + ((now - lockCanvasStartTime) * 0.000001f) + "ms"); - } + if (animating) { + mFullRedrawNeeded = true; + scheduleTraversals(); + } + } - if (left != dirty.left || top != dirty.top || right != dirty.right || - bottom != dirty.bottom) { - mAttachInfo.mIgnoreDirtyState = true; - } + /** + * @return true if drawing was succesfull, false if an error occurred + */ + private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int yoff, + boolean scalingRequired, Rect dirty) { - // TODO: Do this in native - canvas.setDensity(mDensity); - } catch (Surface.OutOfResourcesException e) { - Log.e(TAG, "OutOfResourcesException locking surface", e); - try { - if (!sWindowSession.outOfMemory(mWindow)) { - Slog.w(TAG, "No processes killed for memory; killing self"); - Process.killProcess(Process.myPid()); - } - } catch (RemoteException ex) { - } - mLayoutRequested = true; // ask wm for a new surface next time. - return; - } catch (IllegalArgumentException e) { - Log.e(TAG, "IllegalArgumentException locking surface", e); - // Don't assume this is due to out of memory, it could be - // something else, and if it is something else then we could - // kill stuff (or ourself) for no reason. - mLayoutRequested = true; // ask wm for a new surface next time. - return; - } + // Draw with software renderer. + Canvas canvas; + try { + int left = dirty.left; + int top = dirty.top; + int right = dirty.right; + int bottom = dirty.bottom; - try { - if (DEBUG_ORIENTATION || DEBUG_DRAW) { - Log.v(TAG, "Surface " + surface + " drawing to bitmap w=" - + canvas.getWidth() + ", h=" + canvas.getHeight()); - //canvas.drawARGB(255, 255, 0, 0); - } + final long lockCanvasStartTime; + if (ViewDebug.DEBUG_LATENCY) { + lockCanvasStartTime = System.nanoTime(); + } - long startTime = 0L; - if (ViewDebug.DEBUG_PROFILE_DRAWING) { - startTime = SystemClock.elapsedRealtime(); - } + canvas = mSurface.lockCanvas(dirty); - // If this bitmap's format includes an alpha channel, we - // need to clear it before drawing so that the child will - // properly re-composite its drawing on a transparent - // background. This automatically respects the clip/dirty region - // or - // If we are applying an offset, we need to clear the area - // where the offset doesn't appear to avoid having garbage - // left in the blank areas. - if (!canvas.isOpaque() || yoff != 0) { - canvas.drawColor(0, PorterDuff.Mode.CLEAR); - } + if (ViewDebug.DEBUG_LATENCY) { + long now = System.nanoTime(); + Log.d(ViewDebug.DEBUG_LATENCY_TAG, "- lockCanvas() took " + + ((now - lockCanvasStartTime) * 0.000001f) + "ms"); + } - dirty.setEmpty(); - mIsAnimating = false; - mAttachInfo.mDrawingTime = SystemClock.uptimeMillis(); - mView.mPrivateFlags |= View.DRAWN; + if (left != dirty.left || top != dirty.top || right != dirty.right || + bottom != dirty.bottom) { + attachInfo.mIgnoreDirtyState = true; + } - if (DEBUG_DRAW) { - Context cxt = mView.getContext(); - Log.i(TAG, "Drawing: package:" + cxt.getPackageName() + - ", metrics=" + cxt.getResources().getDisplayMetrics() + - ", compatibilityInfo=" + cxt.getResources().getCompatibilityInfo()); - } - try { - canvas.translate(0, -yoff); - if (mTranslator != null) { - mTranslator.translateCanvas(canvas); - } - canvas.setScreenDensity(scalingRequired - ? DisplayMetrics.DENSITY_DEVICE : 0); - mAttachInfo.mSetIgnoreDirtyState = false; + // TODO: Do this in native + canvas.setDensity(mDensity); + } catch (Surface.OutOfResourcesException e) { + Log.e(TAG, "OutOfResourcesException locking surface", e); + try { + if (!sWindowSession.outOfMemory(mWindow)) { + Slog.w(TAG, "No processes killed for memory; killing self"); + Process.killProcess(Process.myPid()); + } + } catch (RemoteException ex) { + } + mLayoutRequested = true; // ask wm for a new surface next time. + return false; + } catch (IllegalArgumentException e) { + Log.e(TAG, "IllegalArgumentException locking surface", e); + // Don't assume this is due to out of memory, it could be + // something else, and if it is something else then we could + // kill stuff (or ourself) for no reason. + mLayoutRequested = true; // ask wm for a new surface next time. + return false; + } - final long drawStartTime; - if (ViewDebug.DEBUG_LATENCY) { - drawStartTime = System.nanoTime(); - } + try { + if (DEBUG_ORIENTATION || DEBUG_DRAW) { + Log.v(TAG, "Surface " + surface + " drawing to bitmap w=" + + canvas.getWidth() + ", h=" + canvas.getHeight()); + //canvas.drawARGB(255, 255, 0, 0); + } - mView.draw(canvas); + long startTime = 0L; + if (ViewDebug.DEBUG_PROFILE_DRAWING) { + startTime = SystemClock.elapsedRealtime(); + } - if (ViewDebug.DEBUG_LATENCY) { - long now = System.nanoTime(); - Log.d(ViewDebug.DEBUG_LATENCY_TAG, "- draw() took " - + ((now - drawStartTime) * 0.000001f) + "ms"); - } - } finally { - if (!mAttachInfo.mSetIgnoreDirtyState) { - // Only clear the flag if it was not set during the mView.draw() call - mAttachInfo.mIgnoreDirtyState = false; - } - } + // If this bitmap's format includes an alpha channel, we + // need to clear it before drawing so that the child will + // properly re-composite its drawing on a transparent + // background. This automatically respects the clip/dirty region + // or + // If we are applying an offset, we need to clear the area + // where the offset doesn't appear to avoid having garbage + // left in the blank areas. + if (!canvas.isOpaque() || yoff != 0) { + canvas.drawColor(0, PorterDuff.Mode.CLEAR); + } - if (false && ViewDebug.consistencyCheckEnabled) { - mView.dispatchConsistencyCheck(ViewDebug.CONSISTENCY_DRAWING); - } + dirty.setEmpty(); + mIsAnimating = false; + attachInfo.mDrawingTime = SystemClock.uptimeMillis(); + mView.mPrivateFlags |= View.DRAWN; - if (ViewDebug.DEBUG_PROFILE_DRAWING) { - EventLog.writeEvent(60000, SystemClock.elapsedRealtime() - startTime); - } - } finally { - final long unlockCanvasAndPostStartTime; - if (ViewDebug.DEBUG_LATENCY) { - unlockCanvasAndPostStartTime = System.nanoTime(); - } + if (DEBUG_DRAW) { + Context cxt = mView.getContext(); + Log.i(TAG, "Drawing: package:" + cxt.getPackageName() + + ", metrics=" + cxt.getResources().getDisplayMetrics() + + ", compatibilityInfo=" + cxt.getResources().getCompatibilityInfo()); + } + try { + canvas.translate(0, -yoff); + if (mTranslator != null) { + mTranslator.translateCanvas(canvas); + } + canvas.setScreenDensity(scalingRequired + ? DisplayMetrics.DENSITY_DEVICE : 0); + attachInfo.mSetIgnoreDirtyState = false; - surface.unlockCanvasAndPost(canvas); + final long drawStartTime; + if (ViewDebug.DEBUG_LATENCY) { + drawStartTime = System.nanoTime(); + } - if (ViewDebug.DEBUG_LATENCY) { - long now = System.nanoTime(); - Log.d(ViewDebug.DEBUG_LATENCY_TAG, "- unlockCanvasAndPost() took " - + ((now - unlockCanvasAndPostStartTime) * 0.000001f) + "ms"); - } + mView.draw(canvas); - if (LOCAL_LOGV) { - Log.v(TAG, "Surface " + surface + " unlockCanvasAndPost"); - } + if (ViewDebug.DEBUG_LATENCY) { + long now = System.nanoTime(); + Log.d(ViewDebug.DEBUG_LATENCY_TAG, "- draw() took " + + ((now - drawStartTime) * 0.000001f) + "ms"); + } + } finally { + if (!attachInfo.mSetIgnoreDirtyState) { + // Only clear the flag if it was not set during the mView.draw() call + attachInfo.mIgnoreDirtyState = false; } } - } - if (animating) { - mFullRedrawNeeded = true; - scheduleTraversals(); + if (false && ViewDebug.consistencyCheckEnabled) { + mView.dispatchConsistencyCheck(ViewDebug.CONSISTENCY_DRAWING); + } + + if (ViewDebug.DEBUG_PROFILE_DRAWING) { + EventLog.writeEvent(60000, SystemClock.elapsedRealtime() - startTime); + } + } finally { + final long unlockCanvasAndPostStartTime; + if (ViewDebug.DEBUG_LATENCY) { + unlockCanvasAndPostStartTime = System.nanoTime(); + } + + surface.unlockCanvasAndPost(canvas); + + if (ViewDebug.DEBUG_LATENCY) { + long now = System.nanoTime(); + Log.d(ViewDebug.DEBUG_LATENCY_TAG, "- unlockCanvasAndPost() took " + + ((now - unlockCanvasAndPostStartTime) * 0.000001f) + "ms"); + } + + if (LOCAL_LOGV) { + Log.v(TAG, "Surface " + surface + " unlockCanvasAndPost"); + } } + return true; } void invalidateDisplayLists() { @@ -3830,30 +3843,33 @@ public final class ViewRootImpl implements ViewParent, if (LOCAL_LOGV) Log.v(TAG, "DIE in " + this + " of " + mSurface); synchronized (this) { if (mAdded) { - mAdded = false; dispatchDetachedFromWindow(); } if (mAdded && !mFirst) { destroyHardwareRenderer(); - int viewVisibility = mView.getVisibility(); - boolean viewVisibilityChanged = mViewVisibility != viewVisibility; - if (mWindowAttributesChanged || viewVisibilityChanged) { - // If layout params have been changed, first give them - // to the window manager to make sure it has the correct - // animation info. - try { - if ((relayoutWindow(mWindowAttributes, viewVisibility, false) - & WindowManagerImpl.RELAYOUT_RES_FIRST_TIME) != 0) { - sWindowSession.finishDrawing(mWindow); + if (mView != null) { + int viewVisibility = mView.getVisibility(); + boolean viewVisibilityChanged = mViewVisibility != viewVisibility; + if (mWindowAttributesChanged || viewVisibilityChanged) { + // If layout params have been changed, first give them + // to the window manager to make sure it has the correct + // animation info. + try { + if ((relayoutWindow(mWindowAttributes, viewVisibility, false) + & WindowManagerImpl.RELAYOUT_RES_FIRST_TIME) != 0) { + sWindowSession.finishDrawing(mWindow); + } + } catch (RemoteException e) { } - } catch (RemoteException e) { } + + mSurface.release(); } - - mSurface.release(); } + + mAdded = false; } } diff --git a/core/java/android/view/ViewTreeObserver.java b/core/java/android/view/ViewTreeObserver.java index 7fd3389..1c5d436 100644 --- a/core/java/android/view/ViewTreeObserver.java +++ b/core/java/android/view/ViewTreeObserver.java @@ -38,6 +38,7 @@ public final class ViewTreeObserver { private CopyOnWriteArrayList<OnComputeInternalInsetsListener> mOnComputeInternalInsetsListeners; private CopyOnWriteArrayList<OnScrollChangedListener> mOnScrollChangedListeners; private ArrayList<OnPreDrawListener> mOnPreDrawListeners; + private ArrayList<OnDrawListener> mOnDrawListeners; private boolean mAlive = true; @@ -90,6 +91,27 @@ public final class ViewTreeObserver { } /** + * Interface definition for a callback to be invoked when the view tree is about to be drawn. + */ + public interface OnDrawListener { + /** + * <p>Callback method to be invoked when the view tree is about to be drawn. At this point, + * views cannot be modified in any way.</p> + * + * <p>Unlike with {@link OnPreDrawListener}, this method cannot be used to cancel the + * current drawing pass.</p> + * + * <p>An {@link OnDrawListener} listener <strong>cannot be added or removed</strong> + * from this method.</p> + * + * @see android.view.View#onMeasure + * @see android.view.View#onLayout + * @see android.view.View#onDraw + */ + public void onDraw(); + } + + /** * Interface definition for a callback to be invoked when the touch mode changes. */ public interface OnTouchModeChangeListener { @@ -171,11 +193,7 @@ public final class ViewTreeObserver { public void setTouchableInsets(int val) { mTouchableInsets = val; } - - public int getTouchableInsets() { - return mTouchableInsets; - } - + int mTouchableInsets; void reset() { @@ -184,29 +202,28 @@ public final class ViewTreeObserver { touchableRegion.setEmpty(); mTouchableInsets = TOUCHABLE_INSETS_FRAME; } - + + @Override + public int hashCode() { + int result = contentInsets != null ? contentInsets.hashCode() : 0; + result = 31 * result + (visibleInsets != null ? visibleInsets.hashCode() : 0); + result = 31 * result + (touchableRegion != null ? touchableRegion.hashCode() : 0); + result = 31 * result + mTouchableInsets; + return result; + } + @Override public boolean equals(Object o) { - try { - if (o == null) { - return false; - } - InternalInsetsInfo other = (InternalInsetsInfo)o; - if (mTouchableInsets != other.mTouchableInsets) { - return false; - } - if (!contentInsets.equals(other.contentInsets)) { - return false; - } - if (!visibleInsets.equals(other.visibleInsets)) { - return false; - } - return touchableRegion.equals(other.touchableRegion); - } catch (ClassCastException e) { - return false; - } + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + InternalInsetsInfo other = (InternalInsetsInfo)o; + return mTouchableInsets == other.mTouchableInsets && + contentInsets.equals(other.contentInsets) && + visibleInsets.equals(other.visibleInsets) && + touchableRegion.equals(other.touchableRegion); } - + void set(InternalInsetsInfo other) { contentInsets.set(other.contentInsets); visibleInsets.set(other.visibleInsets); @@ -420,6 +437,44 @@ public final class ViewTreeObserver { } /** + * <p>Register a callback to be invoked when the view tree is about to be drawn.</p> + * <p><strong>Note:</strong> this method <strong>cannot</strong> be invoked from + * {@link android.view.ViewTreeObserver.OnDrawListener#onDraw()}.</p> + * + * @param listener The callback to add + * + * @throws IllegalStateException If {@link #isAlive()} returns false + */ + public void addOnDrawListener(OnDrawListener listener) { + checkIsAlive(); + + if (mOnDrawListeners == null) { + mOnDrawListeners = new ArrayList<OnDrawListener>(); + } + + mOnDrawListeners.add(listener); + } + + /** + * <p>Remove a previously installed pre-draw callback.</p> + * <p><strong>Note:</strong> this method <strong>cannot</strong> be invoked from + * {@link android.view.ViewTreeObserver.OnDrawListener#onDraw()}.</p> + * + * @param victim The callback to remove + * + * @throws IllegalStateException If {@link #isAlive()} returns false + * + * @see #addOnDrawListener(OnDrawListener) + */ + public void removeOnDrawListener(OnDrawListener victim) { + checkIsAlive(); + if (mOnDrawListeners == null) { + return; + } + mOnDrawListeners.remove(victim); + } + + /** * Register a callback to be invoked when a view has been scrolled. * * @param listener The callback to add @@ -601,6 +656,7 @@ public final class ViewTreeObserver { * * @return True if the current draw should be canceled and resceduled, false otherwise. */ + @SuppressWarnings("unchecked") public final boolean dispatchOnPreDraw() { // NOTE: we *must* clone the listener list to perform the dispatching. // The clone is a safe guard against listeners that @@ -619,6 +675,19 @@ public final class ViewTreeObserver { } /** + * Notifies registered listeners that the drawing pass is about to start. + */ + public final void dispatchOnDraw() { + if (mOnDrawListeners != null) { + final ArrayList<OnDrawListener> listeners = mOnDrawListeners; + int numListeners = listeners.size(); + for (int i = 0; i < numListeners; ++i) { + listeners.get(i).onDraw(); + } + } + } + + /** * Notifies registered listeners that the touch mode has changed. * * @param inTouchMode True if the touch mode is now enabled, false otherwise. diff --git a/core/java/android/view/WindowManagerPolicy.java b/core/java/android/view/WindowManagerPolicy.java index 75267bb..491cd67 100644 --- a/core/java/android/view/WindowManagerPolicy.java +++ b/core/java/android/view/WindowManagerPolicy.java @@ -326,6 +326,11 @@ public interface WindowManagerPolicy { * Returns true if {@link #hideLw} was last called for the window. */ public boolean showLw(boolean doAnimation); + + /** + * Check whether the process hosting this window is currently alive. + */ + public boolean isAlive(); } /** @@ -344,6 +349,10 @@ public interface WindowManagerPolicy { * between it and the policy. */ public interface WindowManagerFuncs { + public static final int LID_ABSENT = -1; + public static final int LID_CLOSED = 0; + public static final int LID_OPEN = 1; + /** * Ask the window manager to re-evaluate the system UI flags. */ @@ -357,6 +366,16 @@ public interface WindowManagerPolicy { InputEventReceiver.Factory inputEventReceiverFactory, String name, int windowType, int layoutParamsFlags, boolean canReceiveKeys, boolean hasFocus, boolean touchFullscreen); + + /** + * Returns a code that describes the current state of the lid switch. + */ + public int getLidState(); + + /** + * Creates an input channel that will receive all input from the input dispatcher. + */ + public InputChannel monitorInput(String name); } /** @@ -447,7 +466,7 @@ public interface WindowManagerPolicy { * Called by window manager once it has the initial, default native * display dimensions. */ - public void setInitialDisplaySize(int width, int height); + public void setInitialDisplaySize(Display display, int width, int height); /** * Check permissions when adding a window. @@ -514,10 +533,10 @@ public interface WindowManagerPolicy { public int getMaxWallpaperLayer(); /** - * Return true if the policy allows the status bar to hide. Otherwise, - * it is a tablet-style system bar. + * Return true if the policy desires a full unified system nav bar. Otherwise, + * it is a phone-style status bar with optional nav bar. */ - public boolean canStatusBarHide(); + public boolean hasSystemNavBar(); /** * Return the display width available after excluding any screen @@ -938,10 +957,10 @@ public interface WindowManagerPolicy { public void setRotationLw(int rotation); /** - * Called when the system is mostly done booting to determine whether + * Called when the system is mostly done booting to set whether * the system should go into safe mode. */ - public boolean detectSafeMode(); + public void setSafeMode(boolean safeMode); /** * Called when the system is mostly done booting. diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java index 17dbde8..067be39 100644 --- a/core/java/android/view/inputmethod/InputMethodManager.java +++ b/core/java/android/view/inputmethod/InputMethodManager.java @@ -405,7 +405,9 @@ public final class InputMethodManager { } // Check focus again in case that "onWindowFocus" is called before // handling this message. - checkFocus(mHasBeenInactive); + if (mServedView != null && mServedView.hasWindowFocus()) { + checkFocus(mHasBeenInactive); + } } } return; @@ -1202,7 +1204,9 @@ public final class InputMethodManager { } if (DEBUG) Log.v(TAG, "checkFocus: view=" + mServedView + " next=" + mNextServedView - + " forceNewFocus=" + forceNewFocus); + + " forceNewFocus=" + forceNewFocus + + " package=" + + (mServedView != null ? mServedView.getContext().getPackageName() : "<none>")); if (mNextServedView == null) { finishInputLocked(); diff --git a/core/java/android/view/textservice/SpellCheckerInfo.java b/core/java/android/view/textservice/SpellCheckerInfo.java index 137743a..d05c1af 100644 --- a/core/java/android/view/textservice/SpellCheckerInfo.java +++ b/core/java/android/view/textservice/SpellCheckerInfo.java @@ -45,6 +45,7 @@ public final class SpellCheckerInfo implements Parcelable { private final ResolveInfo mService; private final String mId; private final int mLabel; + private final boolean mSupportsSentenceSpellCheck; /** * The spell checker setting activity's name, used by the system settings to @@ -97,6 +98,9 @@ public final class SpellCheckerInfo implements Parcelable { label = sa.getResourceId(com.android.internal.R.styleable.SpellChecker_label, 0); settingsActivityComponent = sa.getString( com.android.internal.R.styleable.SpellChecker_settingsActivity); + mSupportsSentenceSpellCheck = sa.getBoolean( + com.android.internal.R.styleable.SpellChecker_supportsSentenceSpellCheck, + false); sa.recycle(); final int depth = parser.getDepth(); @@ -138,6 +142,7 @@ public final class SpellCheckerInfo implements Parcelable { */ public SpellCheckerInfo(Parcel source) { mLabel = source.readInt(); + mSupportsSentenceSpellCheck = source.readInt() != 0; mId = source.readString(); mSettingsActivityName = source.readString(); mService = ResolveInfo.CREATOR.createFromParcel(source); @@ -152,6 +157,12 @@ public final class SpellCheckerInfo implements Parcelable { return mId; } + /** + * @hide + */ + public boolean isSentenceSpellCheckSupported() { + return mSupportsSentenceSpellCheck; + } /** * Return the component of the service that implements. @@ -177,6 +188,7 @@ public final class SpellCheckerInfo implements Parcelable { @Override public void writeToParcel(Parcel dest, int flags) { dest.writeInt(mLabel); + dest.writeInt(mSupportsSentenceSpellCheck ? 1 : 0); dest.writeString(mId); dest.writeString(mSettingsActivityName); mService.writeToParcel(dest, flags); diff --git a/core/java/android/view/textservice/SpellCheckerSession.java b/core/java/android/view/textservice/SpellCheckerSession.java index 35940ba..9dc05e4 100644 --- a/core/java/android/view/textservice/SpellCheckerSession.java +++ b/core/java/android/view/textservice/SpellCheckerSession.java @@ -436,15 +436,15 @@ public class SpellCheckerSession { */ public interface SpellCheckerSessionListener { /** - * Callback for {@link SpellCheckerSession#getSuggestions(TextInfo[], int, boolean)} + * Callback for {@link SpellCheckerSession#getSuggestions(TextInfo, int)} + * and {@link SpellCheckerSession#getSuggestions(TextInfo[], int, boolean)} * @param results an array of {@link SuggestionsInfo}s. * These results are suggestions for {@link TextInfo}s queried by - * {@link SpellCheckerSession#getSuggestions(TextInfo[], int, boolean)}. + * {@link SpellCheckerSession#getSuggestions(TextInfo, int)} or + * {@link SpellCheckerSession#getSuggestions(TextInfo[], int, boolean)} */ public void onGetSuggestions(SuggestionsInfo[] results); - // TODO: Remove @hide as soon as the sample spell checker client gets fixed. /** - * @hide * Callback for {@link SpellCheckerSession#getSentenceSuggestions(TextInfo[], int)} * @param results an array of {@link SentenceSuggestionsInfo}s. * These results are suggestions for {@link TextInfo}s @@ -494,7 +494,7 @@ public class SpellCheckerSession { } /** - * @hide + * @return true if the spell checker supports sentence level spell checking APIs */ public boolean isSentenceSpellCheckSupported() { return mSubtype.containsExtraValueKey(SUPPORT_SENTENCE_SPELL_CHECK); diff --git a/core/java/android/webkit/FindActionModeCallback.java b/core/java/android/webkit/FindActionModeCallback.java index 6c331ac..6b7263c 100644 --- a/core/java/android/webkit/FindActionModeCallback.java +++ b/core/java/android/webkit/FindActionModeCallback.java @@ -148,8 +148,8 @@ class FindActionModeCallback implements ActionMode.Callback, TextWatcher, mInput.showSoftInput(mEditText, 0); } - public void updateMatchCount(int matchIndex, int matchCount, boolean isNewFind) { - if (!isNewFind) { + public void updateMatchCount(int matchIndex, int matchCount, boolean isEmptyFind) { + if (!isEmptyFind) { mNumberOfMatches = matchCount; mActiveMatchIndex = matchIndex; updateMatchesString(); diff --git a/core/java/android/webkit/WebSettingsClassic.java b/core/java/android/webkit/WebSettingsClassic.java index 94b46fc..aa3d8d3 100644 --- a/core/java/android/webkit/WebSettingsClassic.java +++ b/core/java/android/webkit/WebSettingsClassic.java @@ -33,7 +33,7 @@ import java.util.Locale; */ public class WebSettingsClassic extends WebSettings { // TODO: Keep this up to date - private static final String PREVIOUS_VERSION = "4.0.3"; + private static final String PREVIOUS_VERSION = "4.0.4"; // WebView associated with this WebSettings. private WebViewClassic mWebView; diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java index 9492e38..84632c6 100644 --- a/core/java/android/webkit/WebView.java +++ b/core/java/android/webkit/WebView.java @@ -313,7 +313,6 @@ public class WebView extends AbsoluteLayout /** * Interface to listen for find results. - * @hide */ public interface FindListener { /** @@ -1249,8 +1248,7 @@ public class WebView extends AbsoluteLayout * Register the listener to be notified as find-on-page operations progress. * This will replace the current listener. * - * @param listener An implementation of {@link WebView#FindListener}. - * @hide + * @param listener An implementation of {@link FindListener}. */ public void setFindListener(FindListener listener) { checkThread(); @@ -1258,11 +1256,15 @@ public class WebView extends AbsoluteLayout } /** - * Highlight and scroll to the next occurance of String in findAll. - * Wraps the page infinitely, and scrolls. Must be called after - * calling findAll. + * Highlight and scroll to the next match found by {@link #findAll} or + * {@link #findAllAsync}, wrapping around page boundaries as necessary. + * Notifies any registered {@link FindListener}. If neither + * {@link #findAll} nor {@link #findAllAsync(String)} has been called yet, + * or if {@link #clearMatches} has been called since the last find + * operation, this function does nothing. * * @param forward Direction to search. + * @see #setFindListener */ public void findNext(boolean forward) { checkThread(); @@ -1271,10 +1273,13 @@ public class WebView extends AbsoluteLayout /** * Find all instances of find on the page and highlight them. + * Notifies any registered {@link FindListener}. * * @param find String to find. * @return int The number of occurances of the String "find" * that were found. + * @deprecated {@link #findAllAsync} is preferred. + * @see #setFindListener */ public int findAll(String find) { checkThread(); @@ -1283,10 +1288,12 @@ public class WebView extends AbsoluteLayout /** * Find all instances of find on the page and highlight them, - * asynchronously. + * asynchronously. Notifies any registered {@link FindListener}. + * Successive calls to this or {@link #findAll} will cancel any + * pending searches. * * @param find String to find. - * @hide + * @see #setFindListener */ public void findAllAsync(String find) { checkThread(); @@ -1333,8 +1340,9 @@ public class WebView extends AbsoluteLayout return getFactory().getStatics().findAddress(addr); } - /* - * Clear the highlighting surrounding text matches created by findAll. + /** + * Clear the highlighting surrounding text matches created by + * {@link #findAll} or {@link #findAllAsync}. */ public void clearMatches() { checkThread(); diff --git a/core/java/android/webkit/WebViewClassic.java b/core/java/android/webkit/WebViewClassic.java index 4c118ac..586fcb1 100644 --- a/core/java/android/webkit/WebViewClassic.java +++ b/core/java/android/webkit/WebViewClassic.java @@ -3588,7 +3588,9 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc @Override public void findNext(boolean forward) { if (0 == mNativeClass) return; // client isn't initialized - mWebViewCore.sendMessage(EventHub.FIND_NEXT, forward ? 1 : 0); + if (mFindRequest != null) { + mWebViewCore.sendMessage(EventHub.FIND_NEXT, forward ? 1 : 0, mFindRequest); + } } /** @@ -3605,28 +3607,26 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc private int findAllBody(String find, boolean isAsync) { if (0 == mNativeClass) return 0; // client isn't initialized - mLastFind = find; + mFindRequest = null; if (find == null) return 0; mWebViewCore.removeMessages(EventHub.FIND_ALL); - WebViewCore.FindAllRequest request = new - WebViewCore.FindAllRequest(find); + mFindRequest = new WebViewCore.FindAllRequest(find); if (isAsync) { - mWebViewCore.sendMessage(EventHub.FIND_ALL, request); + mWebViewCore.sendMessage(EventHub.FIND_ALL, mFindRequest); return 0; // no need to wait for response } - synchronized(request) { + synchronized(mFindRequest) { try { - mWebViewCore.sendMessageAtFrontOfQueue(EventHub.FIND_ALL, - request); - while (request.mMatchCount == -1) { - request.wait(); + mWebViewCore.sendMessageAtFrontOfQueue(EventHub.FIND_ALL, mFindRequest); + while (mFindRequest.mMatchCount == -1) { + mFindRequest.wait(); } } catch (InterruptedException e) { return 0; } + return mFindRequest.mMatchCount; } - return request.mMatchCount; } /** @@ -3657,7 +3657,7 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc return true; } if (text == null) { - text = mLastFind; + text = mFindRequest == null ? null : mFindRequest.mSearchText; } if (text != null) { mFindCallback.setText(text); @@ -3683,9 +3683,8 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc // or not we draw the highlights for matches. private boolean mFindIsUp; - // Keep track of the last string sent, so we can search again when find is - // reopened. - private String mLastFind; + // Keep track of the last find request sent. + private WebViewCore.FindAllRequest mFindRequest = null; /** * Return the first substring consisting of the address of a physical @@ -8476,13 +8475,27 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc } case UPDATE_MATCH_COUNT: { - boolean isNewFind = mLastFind == null || !mLastFind.equals(msg.obj); - if (mFindCallback != null) - mFindCallback.updateMatchCount(msg.arg1, msg.arg2, isNewFind); - if (mFindListener != null) - mFindListener.onFindResultReceived(msg.arg1, msg.arg2, true); + WebViewCore.FindAllRequest request = (WebViewCore.FindAllRequest)msg.obj; + if (request == null) { + if (mFindCallback != null) { + mFindCallback.updateMatchCount(0, 0, true); + } + } else if (request == mFindRequest) { + int matchCount, matchIndex; + synchronized (mFindRequest) { + matchCount = request.mMatchCount; + matchIndex = request.mMatchIndex; + } + if (mFindCallback != null) { + mFindCallback.updateMatchCount(matchIndex, matchCount, false); + } + if (mFindListener != null) { + mFindListener.onFindResultReceived(matchIndex, matchCount, true); + } + } break; } + case CLEAR_CARET_HANDLE: selectionDone(); break; diff --git a/core/java/android/webkit/WebViewCore.java b/core/java/android/webkit/WebViewCore.java index b4ebc09..5549d89 100644 --- a/core/java/android/webkit/WebViewCore.java +++ b/core/java/android/webkit/WebViewCore.java @@ -1041,9 +1041,11 @@ public final class WebViewCore { public FindAllRequest(String text) { mSearchText = text; mMatchCount = -1; + mMatchIndex = -1; } - public String mSearchText; + public final String mSearchText; public int mMatchCount; + public int mMatchIndex; } /** @@ -1777,21 +1779,32 @@ public final class WebViewCore { nativeSelectAll(mNativeClass); break; case FIND_ALL: { - FindAllRequest request = (FindAllRequest) msg.obj; - if (request == null) { - nativeFindAll(mNativeClass, null); - } else { - request.mMatchCount = nativeFindAll( - mNativeClass, request.mSearchText); - synchronized(request) { + FindAllRequest request = (FindAllRequest)msg.obj; + if (request != null) { + int matchCount = nativeFindAll(mNativeClass, request.mSearchText); + int matchIndex = nativeFindNext(mNativeClass, true); + synchronized (request) { + request.mMatchCount = matchCount; + request.mMatchIndex = matchIndex; request.notify(); } + } else { + nativeFindAll(mNativeClass, null); } + Message.obtain(mWebViewClassic.mPrivateHandler, + WebViewClassic.UPDATE_MATCH_COUNT, request).sendToTarget(); break; } - case FIND_NEXT: - nativeFindNext(mNativeClass, msg.arg1 != 0); + case FIND_NEXT: { + FindAllRequest request = (FindAllRequest)msg.obj; + int matchIndex = nativeFindNext(mNativeClass, msg.arg1 != 0); + synchronized (request) { + request.mMatchIndex = matchIndex; + } + Message.obtain(mWebViewClassic.mPrivateHandler, + WebViewClassic.UPDATE_MATCH_COUNT, request).sendToTarget(); break; + } } } }; @@ -2825,17 +2838,6 @@ public final class WebViewCore { .sendToTarget(); } - // called by JNI - private void updateMatchCount(int matchIndex, int matchCount, - String findText) { - if (mWebViewClassic == null) { - return; - } - Message.obtain(mWebViewClassic.mPrivateHandler, - WebViewClassic.UPDATE_MATCH_COUNT, matchIndex, matchCount, - findText).sendToTarget(); - } - private native void nativeRevealSelection(int nativeClass); private native String nativeRequestLabel(int nativeClass, int framePtr, int nodePtr); @@ -3086,7 +3088,7 @@ public final class WebViewCore { private native void nativeAutoFillForm(int nativeClass, int queryId); private native void nativeScrollLayer(int nativeClass, int layer, Rect rect); private native int nativeFindAll(int nativeClass, String text); - private native void nativeFindNext(int nativeClass, boolean forward); + private native int nativeFindNext(int nativeClass, boolean forward); /** * Deletes editable text between two points. Note that the selection may diff --git a/core/java/android/widget/Chronometer.java b/core/java/android/widget/Chronometer.java index 0370049..b7a126e 100644 --- a/core/java/android/widget/Chronometer.java +++ b/core/java/android/widget/Chronometer.java @@ -25,6 +25,7 @@ import android.os.SystemClock; import android.text.format.DateUtils; import android.util.AttributeSet; import android.util.Log; +import android.util.Slog; import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityNodeInfo; import android.widget.RemoteViews.RemoteView; diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java index 880dc34..cbff58c 100644 --- a/core/java/android/widget/Editor.java +++ b/core/java/android/widget/Editor.java @@ -1609,7 +1609,6 @@ public class Editor { private boolean mCancelled; public void run() { - Log.d("GILLES", "blinking !!!"); if (mCancelled) { return; } diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index d2a1755..9867e47 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -5340,24 +5340,63 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener physicalWidth, false); } + @Override + public void onResolvedLayoutDirectionReset() { + if (mLayoutAlignment != null) { + int resolvedTextAlignment = getResolvedTextAlignment(); + if (resolvedTextAlignment == TEXT_ALIGNMENT_VIEW_START || + resolvedTextAlignment == TEXT_ALIGNMENT_VIEW_END) { + mLayoutAlignment = null; + } + } + } + private Layout.Alignment getLayoutAlignment() { if (mLayoutAlignment == null) { - switch (mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK) { - case Gravity.START: + int textAlign = getResolvedTextAlignment(); + switch (textAlign) { + case TEXT_ALIGNMENT_GRAVITY: + switch (mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK) { + case Gravity.START: + mLayoutAlignment = Layout.Alignment.ALIGN_NORMAL; + break; + case Gravity.END: + mLayoutAlignment = Layout.Alignment.ALIGN_OPPOSITE; + break; + case Gravity.LEFT: + mLayoutAlignment = Layout.Alignment.ALIGN_LEFT; + break; + case Gravity.RIGHT: + mLayoutAlignment = Layout.Alignment.ALIGN_RIGHT; + break; + case Gravity.CENTER_HORIZONTAL: + mLayoutAlignment = Layout.Alignment.ALIGN_CENTER; + break; + default: + mLayoutAlignment = Layout.Alignment.ALIGN_NORMAL; + break; + } + break; + case TEXT_ALIGNMENT_TEXT_START: mLayoutAlignment = Layout.Alignment.ALIGN_NORMAL; break; - case Gravity.END: + case TEXT_ALIGNMENT_TEXT_END: mLayoutAlignment = Layout.Alignment.ALIGN_OPPOSITE; break; - case Gravity.LEFT: - mLayoutAlignment = Layout.Alignment.ALIGN_LEFT; + case TEXT_ALIGNMENT_CENTER: + mLayoutAlignment = Layout.Alignment.ALIGN_CENTER; break; - case Gravity.RIGHT: - mLayoutAlignment = Layout.Alignment.ALIGN_RIGHT; + case TEXT_ALIGNMENT_VIEW_START: + mLayoutAlignment = (getResolvedLayoutDirection() == LAYOUT_DIRECTION_RTL) ? + Layout.Alignment.ALIGN_RIGHT : Layout.Alignment.ALIGN_LEFT; break; - case Gravity.CENTER_HORIZONTAL: - mLayoutAlignment = Layout.Alignment.ALIGN_CENTER; + case TEXT_ALIGNMENT_VIEW_END: + mLayoutAlignment = (getResolvedLayoutDirection() == LAYOUT_DIRECTION_RTL) ? + Layout.Alignment.ALIGN_LEFT : Layout.Alignment.ALIGN_RIGHT; break; + case TEXT_ALIGNMENT_INHERIT: + // This should never happen as we have already resolved the text alignment + // but better safe than sorry so we just fall through default: mLayoutAlignment = Layout.Alignment.ALIGN_NORMAL; break; diff --git a/core/java/com/android/internal/app/ActionBarImpl.java b/core/java/com/android/internal/app/ActionBarImpl.java index 3115eff..f1dffa1 100644 --- a/core/java/com/android/internal/app/ActionBarImpl.java +++ b/core/java/com/android/internal/app/ActionBarImpl.java @@ -85,6 +85,8 @@ public class ActionBarImpl extends ActionBar { private TabImpl mSelectedTab; private int mSavedTabPosition = INVALID_POSITION; + private boolean mDisplayHomeAsUpSet; + ActionModeImpl mActionMode; ActionMode mDeferredDestroyActionMode; ActionMode.Callback mDeferredModeDestroyCallback; @@ -375,11 +377,17 @@ public class ActionBarImpl extends ActionBar { } public void setDisplayOptions(int options) { + if ((options & DISPLAY_HOME_AS_UP) != 0) { + mDisplayHomeAsUpSet = true; + } mActionView.setDisplayOptions(options); } public void setDisplayOptions(int options, int mask) { final int current = mActionView.getDisplayOptions(); + if ((mask & DISPLAY_HOME_AS_UP) != 0) { + mDisplayHomeAsUpSet = true; + } mActionView.setDisplayOptions((options & mask) | (current & ~mask)); } @@ -1072,4 +1080,10 @@ public class ActionBarImpl extends ActionBar { public void setLogo(Drawable logo) { mActionView.setLogo(logo); } + + public void setDefaultDisplayHomeAsUpEnabled(boolean enable) { + if (!mDisplayHomeAsUpSet) { + setDisplayHomeAsUpEnabled(enable); + } + } } diff --git a/core/java/com/android/internal/net/NetworkStatsFactory.java b/core/java/com/android/internal/net/NetworkStatsFactory.java index ccd2763..d59585f 100644 --- a/core/java/com/android/internal/net/NetworkStatsFactory.java +++ b/core/java/com/android/internal/net/NetworkStatsFactory.java @@ -16,7 +16,7 @@ package com.android.internal.net; -import static android.net.NetworkStats.SET_DEFAULT; +import static android.net.NetworkStats.SET_ALL; import static android.net.NetworkStats.TAG_NONE; import static android.net.NetworkStats.UID_ALL; import static com.android.server.NetworkManagementSocketTagger.kernelToTag; @@ -101,7 +101,7 @@ public class NetworkStatsFactory { while (reader.hasMoreData()) { entry.iface = reader.nextString(); entry.uid = UID_ALL; - entry.set = SET_DEFAULT; + entry.set = SET_ALL; entry.tag = TAG_NONE; final boolean active = reader.nextInt() != 0; @@ -165,7 +165,7 @@ public class NetworkStatsFactory { entry.iface = iface; entry.uid = UID_ALL; - entry.set = SET_DEFAULT; + entry.set = SET_ALL; entry.tag = TAG_NONE; entry.rxBytes = readSingleLongFromFile(new File(ifacePath, "rx_bytes")); entry.rxPackets = readSingleLongFromFile(new File(ifacePath, "rx_packets")); @@ -193,7 +193,7 @@ public class NetworkStatsFactory { try { entry.iface = values.get(0); entry.uid = UID_ALL; - entry.set = SET_DEFAULT; + entry.set = SET_ALL; entry.tag = TAG_NONE; entry.rxBytes = Long.parseLong(values.get(1)); entry.rxPackets = Long.parseLong(values.get(2)); diff --git a/core/java/com/android/internal/util/Protocol.java b/core/java/com/android/internal/util/Protocol.java index d462d7f..7c2b1b5 100644 --- a/core/java/com/android/internal/util/Protocol.java +++ b/core/java/com/android/internal/util/Protocol.java @@ -49,7 +49,7 @@ public class Protocol { public static final int BASE_DATA_CONNECTION = 0x00040000; public static final int BASE_DATA_CONNECTION_AC = 0x00041000; public static final int BASE_DATA_CONNECTION_TRACKER = 0x00042000; - public static final int BASE_DNS_PINGER = 0x00050000; + public static final int BASE_NSD_MANAGER = 0x00060000; //TODO: define all used protocols } diff --git a/core/java/com/android/internal/widget/ActionBarOverlayLayout.java b/core/java/com/android/internal/widget/ActionBarOverlayLayout.java index 8521481..d1652df 100644 --- a/core/java/com/android/internal/widget/ActionBarOverlayLayout.java +++ b/core/java/com/android/internal/widget/ActionBarOverlayLayout.java @@ -18,14 +18,11 @@ package com.android.internal.widget; import com.android.internal.app.ActionBarImpl; -import android.animation.LayoutTransition; import android.content.Context; import android.content.res.TypedArray; import android.graphics.Rect; import android.util.AttributeSet; -import android.util.Log; import android.view.View; -import android.view.ViewTreeObserver; import android.widget.FrameLayout; /** diff --git a/core/java/com/android/internal/widget/ILockSettings.aidl b/core/java/com/android/internal/widget/ILockSettings.aidl new file mode 100644 index 0000000..c72c770 --- /dev/null +++ b/core/java/com/android/internal/widget/ILockSettings.aidl @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2012 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.widget; + +/** {@hide} */ +interface ILockSettings { + void setBoolean(in String key, in boolean value, in int userId); + void setLong(in String key, in long value, in int userId); + void setString(in String key, in String value, in int userId); + boolean getBoolean(in String key, in boolean defaultValue, in int userId); + long getLong(in String key, in long defaultValue, in int userId); + String getString(in String key, in String defaultValue, in int userId); + void setLockPattern(in byte[] hash, int userId); + boolean checkPattern(in byte[] hash, int userId); + void setLockPassword(in byte[] hash, int userId); + boolean checkPassword(in byte[] hash, int userId); + boolean havePattern(int userId); + boolean havePassword(int userId); + void removeUser(int userId); +} diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java index 93f90f6..4d308dd 100644 --- a/core/java/com/android/internal/widget/LockPatternUtils.java +++ b/core/java/com/android/internal/widget/LockPatternUtils.java @@ -21,15 +21,20 @@ import com.android.internal.telephony.ITelephony; import com.google.android.collect.Lists; import android.app.admin.DevicePolicyManager; +import android.content.BroadcastReceiver; import android.content.ContentResolver; import android.content.Context; import android.content.Intent; +import android.content.IntentFilter; import android.content.pm.PackageManager; +import android.os.Binder; import android.os.FileObserver; import android.os.IBinder; +import android.os.Process; import android.os.RemoteException; import android.os.ServiceManager; import android.os.SystemClock; +import android.os.UserId; import android.os.storage.IMountService; import android.provider.Settings; import android.security.KeyStore; @@ -59,10 +64,6 @@ public class LockPatternUtils { private static final String TAG = "LockPatternUtils"; - private static final String SYSTEM_DIRECTORY = "/system/"; - private static final String LOCK_PATTERN_FILE = "gesture.key"; - private static final String LOCK_PASSWORD_FILE = "password.key"; - /** * The maximum number of incorrect attempts before the user is prevented * from trying again for {@link #FAILED_ATTEMPT_TIMEOUT_MS}. @@ -111,14 +112,14 @@ public class LockPatternUtils { */ public static final int FLAG_BIOMETRIC_WEAK_LIVELINESS = 0x1; - private final static String LOCKOUT_PERMANENT_KEY = "lockscreen.lockedoutpermanently"; - private final static String LOCKOUT_ATTEMPT_DEADLINE = "lockscreen.lockoutattemptdeadline"; - private final static String PATTERN_EVER_CHOSEN_KEY = "lockscreen.patterneverchosen"; + protected final static String LOCKOUT_PERMANENT_KEY = "lockscreen.lockedoutpermanently"; + protected final static String LOCKOUT_ATTEMPT_DEADLINE = "lockscreen.lockoutattemptdeadline"; + protected final static String PATTERN_EVER_CHOSEN_KEY = "lockscreen.patterneverchosen"; public final static String PASSWORD_TYPE_KEY = "lockscreen.password_type"; public static final String PASSWORD_TYPE_ALTERNATE_KEY = "lockscreen.password_type_alternate"; - private final static String LOCK_PASSWORD_SALT_KEY = "lockscreen.password_salt"; - private final static String DISABLE_LOCKSCREEN_KEY = "lockscreen.disabled"; - private final static String LOCKSCREEN_OPTIONS = "lockscreen.options"; + protected final static String LOCK_PASSWORD_SALT_KEY = "lockscreen.password_salt"; + protected final static String DISABLE_LOCKSCREEN_KEY = "lockscreen.disabled"; + protected final static String LOCKSCREEN_OPTIONS = "lockscreen.options"; public final static String LOCKSCREEN_BIOMETRIC_WEAK_FALLBACK = "lockscreen.biometric_weak_fallback"; public final static String BIOMETRIC_WEAK_EVER_CHOSEN_KEY @@ -126,35 +127,13 @@ public class LockPatternUtils { public final static String LOCKSCREEN_POWER_BUTTON_INSTANTLY_LOCKS = "lockscreen.power_button_instantly_locks"; - private final static String PASSWORD_HISTORY_KEY = "lockscreen.passwordhistory"; + protected final static String PASSWORD_HISTORY_KEY = "lockscreen.passwordhistory"; private final Context mContext; private final ContentResolver mContentResolver; private DevicePolicyManager mDevicePolicyManager; - private static String sLockPatternFilename; - private static String sLockPasswordFilename; - - private static final AtomicBoolean sHaveNonZeroPatternFile = new AtomicBoolean(false); - private static final AtomicBoolean sHaveNonZeroPasswordFile = new AtomicBoolean(false); - - private static FileObserver sPasswordObserver; - - private static class PasswordFileObserver extends FileObserver { - public PasswordFileObserver(String path, int mask) { - super(path, mask); - } - - @Override - public void onEvent(int event, String path) { - if (LOCK_PATTERN_FILE.equals(path)) { - Log.d(TAG, "lock pattern file changed"); - sHaveNonZeroPatternFile.set(new File(sLockPatternFilename).length() > 0); - } else if (LOCK_PASSWORD_FILE.equals(path)) { - Log.d(TAG, "lock password file changed"); - sHaveNonZeroPasswordFile.set(new File(sLockPasswordFilename).length() > 0); - } - } - } + private ILockSettings mLockSettingsService; + private int mCurrentUserId = 0; public DevicePolicyManager getDevicePolicyManager() { if (mDevicePolicyManager == null) { @@ -167,34 +146,27 @@ public class LockPatternUtils { } return mDevicePolicyManager; } + /** * @param contentResolver Used to look up and save settings. */ public LockPatternUtils(Context context) { mContext = context; mContentResolver = context.getContentResolver(); + } - // Initialize the location of gesture & PIN lock files - if (sLockPatternFilename == null) { - String dataSystemDirectory = - android.os.Environment.getDataDirectory().getAbsolutePath() + - SYSTEM_DIRECTORY; - sLockPatternFilename = dataSystemDirectory + LOCK_PATTERN_FILE; - sLockPasswordFilename = dataSystemDirectory + LOCK_PASSWORD_FILE; - sHaveNonZeroPatternFile.set(new File(sLockPatternFilename).length() > 0); - sHaveNonZeroPasswordFile.set(new File(sLockPasswordFilename).length() > 0); - int fileObserverMask = FileObserver.CLOSE_WRITE | FileObserver.DELETE | - FileObserver.MOVED_TO | FileObserver.CREATE; - sPasswordObserver = new PasswordFileObserver(dataSystemDirectory, fileObserverMask); - sPasswordObserver.startWatching(); + private ILockSettings getLockSettings() { + if (mLockSettingsService == null) { + mLockSettingsService = ILockSettings.Stub.asInterface( + (IBinder) ServiceManager.getService("lock_settings")); } + return mLockSettingsService; } public int getRequestedMinimumPasswordLength() { return getDevicePolicyManager().getPasswordMinimumLength(null); } - /** * Gets the device policy password mode. If the mode is non-specific, returns * MODE_PATTERN which allows the user to choose anything. @@ -243,6 +215,33 @@ public class LockPatternUtils { getDevicePolicyManager().reportSuccessfulPasswordAttempt(); } + public void setCurrentUser(int userId) { + if (Process.myUid() == Process.SYSTEM_UID) { + mCurrentUserId = userId; + } else { + throw new SecurityException("Only the system process can set the current user"); + } + } + + public void removeUser(int userId) { + if (Process.myUid() == Process.SYSTEM_UID) { + try { + getLockSettings().removeUser(userId); + } catch (RemoteException re) { + Log.e(TAG, "Couldn't remove lock settings for user " + userId); + } + } + } + + private int getCurrentOrCallingUserId() { + int callingUid = Binder.getCallingUid(); + if (callingUid == android.os.Process.SYSTEM_UID) { + return mCurrentUserId; + } else { + return UserId.getUserId(callingUid); + } + } + /** * Check to see if a pattern matches the saved pattern. If no pattern exists, * always returns true. @@ -250,20 +249,10 @@ public class LockPatternUtils { * @return Whether the pattern matches the stored one. */ public boolean checkPattern(List<LockPatternView.Cell> pattern) { + int userId = getCurrentOrCallingUserId(); try { - // Read all the bytes from the file - RandomAccessFile raf = new RandomAccessFile(sLockPatternFilename, "r"); - final byte[] stored = new byte[(int) raf.length()]; - int got = raf.read(stored, 0, stored.length); - raf.close(); - if (got <= 0) { - return true; - } - // Compare the hash from the file with the entered pattern's hash - return Arrays.equals(stored, LockPatternUtils.patternToHash(pattern)); - } catch (FileNotFoundException fnfe) { - return true; - } catch (IOException ioe) { + return getLockSettings().checkPattern(patternToHash(pattern), userId); + } catch (RemoteException re) { return true; } } @@ -275,20 +264,10 @@ public class LockPatternUtils { * @return Whether the password matches the stored one. */ public boolean checkPassword(String password) { + int userId = getCurrentOrCallingUserId(); try { - // Read all the bytes from the file - RandomAccessFile raf = new RandomAccessFile(sLockPasswordFilename, "r"); - final byte[] stored = new byte[(int) raf.length()]; - int got = raf.read(stored, 0, stored.length); - raf.close(); - if (got <= 0) { - return true; - } - // Compare the hash from the file with the entered password's hash - return Arrays.equals(stored, passwordToHash(password)); - } catch (FileNotFoundException fnfe) { - return true; - } catch (IOException ioe) { + return getLockSettings().checkPassword(passwordToHash(password), userId); + } catch (RemoteException re) { return true; } } @@ -325,7 +304,11 @@ public class LockPatternUtils { * @return Whether a saved pattern exists. */ public boolean savedPatternExists() { - return sHaveNonZeroPatternFile.get(); + try { + return getLockSettings().havePattern(getCurrentOrCallingUserId()); + } catch (RemoteException re) { + return false; + } } /** @@ -333,7 +316,11 @@ public class LockPatternUtils { * @return Whether a saved pattern exists. */ public boolean savedPasswordExists() { - return sHaveNonZeroPasswordFile.get(); + try { + return getLockSettings().havePassword(getCurrentOrCallingUserId()); + } catch (RemoteException re) { + return false; + } } /** @@ -471,15 +458,7 @@ public class LockPatternUtils { // Compute the hash final byte[] hash = LockPatternUtils.patternToHash(pattern); try { - // Write the hash to file - RandomAccessFile raf = new RandomAccessFile(sLockPatternFilename, "rw"); - // Truncate the file if pattern is null, to clear the lock - if (pattern == null) { - raf.setLength(0); - } else { - raf.write(hash, 0, hash.length); - } - raf.close(); + getLockSettings().setLockPattern(hash, getCurrentOrCallingUserId()); DevicePolicyManager dpm = getDevicePolicyManager(); KeyStore keyStore = KeyStore.getInstance(); if (pattern != null) { @@ -505,13 +484,8 @@ public class LockPatternUtils { dpm.setActivePasswordState(DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED, 0, 0, 0, 0, 0, 0, 0); } - } catch (FileNotFoundException fnfe) { - // Cant do much, unless we want to fail over to using the settings - // provider - Log.e(TAG, "Unable to save lock pattern to " + sLockPatternFilename); - } catch (IOException ioe) { - // Cant do much - Log.e(TAG, "Unable to save lock pattern to " + sLockPatternFilename); + } catch (RemoteException re) { + Log.e(TAG, "Couldn't save lock pattern " + re); } } @@ -586,15 +560,7 @@ public class LockPatternUtils { // Compute the hash final byte[] hash = passwordToHash(password); try { - // Write the hash to file - RandomAccessFile raf = new RandomAccessFile(sLockPasswordFilename, "rw"); - // Truncate the file if pattern is null, to clear the lock - if (password == null) { - raf.setLength(0); - } else { - raf.write(hash, 0, hash.length); - } - raf.close(); + getLockSettings().setLockPassword(hash, getCurrentOrCallingUserId()); DevicePolicyManager dpm = getDevicePolicyManager(); KeyStore keyStore = KeyStore.getInstance(); if (password != null) { @@ -676,12 +642,9 @@ public class LockPatternUtils { dpm.setActivePasswordState( DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED, 0, 0, 0, 0, 0, 0, 0); } - } catch (FileNotFoundException fnfe) { - // Cant do much, unless we want to fail over to using the settings provider - Log.e(TAG, "Unable to save lock pattern to " + sLockPasswordFilename); - } catch (IOException ioe) { + } catch (RemoteException re) { // Cant do much - Log.e(TAG, "Unable to save lock pattern to " + sLockPasswordFilename); + Log.e(TAG, "Unable to save lock password " + re); } } @@ -1013,30 +976,57 @@ public class LockPatternUtils { } private boolean getBoolean(String secureSettingKey, boolean defaultValue) { - return 1 == - android.provider.Settings.Secure.getInt(mContentResolver, secureSettingKey, - defaultValue ? 1 : 0); + try { + return getLockSettings().getBoolean(secureSettingKey, defaultValue, + getCurrentOrCallingUserId()); + } catch (RemoteException re) { + return defaultValue; + } } private void setBoolean(String secureSettingKey, boolean enabled) { - android.provider.Settings.Secure.putInt(mContentResolver, secureSettingKey, - enabled ? 1 : 0); + try { + getLockSettings().setBoolean(secureSettingKey, enabled, getCurrentOrCallingUserId()); + } catch (RemoteException re) { + // What can we do? + Log.e(TAG, "Couldn't write boolean " + secureSettingKey + re); + } } - private long getLong(String secureSettingKey, long def) { - return android.provider.Settings.Secure.getLong(mContentResolver, secureSettingKey, def); + private long getLong(String secureSettingKey, long defaultValue) { + try { + return getLockSettings().getLong(secureSettingKey, defaultValue, + getCurrentOrCallingUserId()); + } catch (RemoteException re) { + return defaultValue; + } } private void setLong(String secureSettingKey, long value) { - android.provider.Settings.Secure.putLong(mContentResolver, secureSettingKey, value); + try { + getLockSettings().setLong(secureSettingKey, value, getCurrentOrCallingUserId()); + } catch (RemoteException re) { + // What can we do? + Log.e(TAG, "Couldn't write long " + secureSettingKey + re); + } } private String getString(String secureSettingKey) { - return android.provider.Settings.Secure.getString(mContentResolver, secureSettingKey); + try { + return getLockSettings().getString(secureSettingKey, null, + getCurrentOrCallingUserId()); + } catch (RemoteException re) { + return null; + } } private void setString(String secureSettingKey, String value) { - android.provider.Settings.Secure.putString(mContentResolver, secureSettingKey, value); + try { + getLockSettings().setString(secureSettingKey, value, getCurrentOrCallingUserId()); + } catch (RemoteException re) { + // What can we do? + Log.e(TAG, "Couldn't write string " + secureSettingKey + re); + } } public boolean isSecure() { diff --git a/core/java/com/android/internal/widget/LockSettingsService.java b/core/java/com/android/internal/widget/LockSettingsService.java new file mode 100644 index 0000000..24c7161 --- /dev/null +++ b/core/java/com/android/internal/widget/LockSettingsService.java @@ -0,0 +1,388 @@ +/* + * Copyright (C) 2012 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.widget; + +import android.content.ContentResolver; +import android.content.ContentValues; +import android.content.Context; +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteOpenHelper; +import android.os.Binder; +import android.os.RemoteException; +import android.os.UserId; +import android.provider.Settings; +import android.provider.Settings.Secure; +import android.text.TextUtils; +import android.util.Slog; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.RandomAccessFile; +import java.util.Arrays; + +/** + * Keeps the lock pattern/password data and related settings for each user. + * Used by LockPatternUtils. Needs to be a service because Settings app also needs + * to be able to save lockscreen information for secondary users. + * @hide + */ +public class LockSettingsService extends ILockSettings.Stub { + + private final DatabaseHelper mOpenHelper; + private static final String TAG = "LockSettingsService"; + + private static final String TABLE = "locksettings"; + private static final String COLUMN_KEY = "name"; + private static final String COLUMN_USERID = "user"; + private static final String COLUMN_VALUE = "value"; + + private static final String[] COLUMNS_FOR_QUERY = { + COLUMN_VALUE + }; + + private static final String SYSTEM_DIRECTORY = "/system/"; + private static final String LOCK_PATTERN_FILE = "gesture.key"; + private static final String LOCK_PASSWORD_FILE = "password.key"; + + private final Context mContext; + + public LockSettingsService(Context context) { + mContext = context; + // Open the database + mOpenHelper = new DatabaseHelper(mContext); + } + + public void systemReady() { + migrateOldData(); + } + + private void migrateOldData() { + try { + if (getString("migrated", null, 0) != null) { + // Already migrated + return; + } + + final ContentResolver cr = mContext.getContentResolver(); + for (String validSetting : VALID_SETTINGS) { + String value = Settings.Secure.getString(cr, validSetting); + if (value != null) { + setString(validSetting, value, 0); + } + } + // No need to move the password / pattern files. They're already in the right place. + setString("migrated", "true", 0); + Slog.i(TAG, "Migrated lock settings to new location"); + } catch (RemoteException re) { + Slog.e(TAG, "Unable to migrate old data"); + } + } + + private static final void checkWritePermission(int userId) { + final int callingUid = Binder.getCallingUid(); + if (UserId.getAppId(callingUid) != android.os.Process.SYSTEM_UID) { + throw new SecurityException("uid=" + callingUid + + " not authorized to write lock settings"); + } + } + + private static final void checkPasswordReadPermission(int userId) { + final int callingUid = Binder.getCallingUid(); + if (UserId.getAppId(callingUid) != android.os.Process.SYSTEM_UID) { + throw new SecurityException("uid=" + callingUid + + " not authorized to read lock password"); + } + } + + private static final void checkReadPermission(int userId) { + final int callingUid = Binder.getCallingUid(); + if (UserId.getAppId(callingUid) != android.os.Process.SYSTEM_UID + && UserId.getUserId(callingUid) != userId) { + throw new SecurityException("uid=" + callingUid + + " not authorized to read settings of user " + userId); + } + } + + @Override + public void setBoolean(String key, boolean value, int userId) throws RemoteException { + checkWritePermission(userId); + + writeToDb(key, value ? "1" : "0", userId); + } + + @Override + public void setLong(String key, long value, int userId) throws RemoteException { + checkWritePermission(userId); + + writeToDb(key, Long.toString(value), userId); + } + + @Override + public void setString(String key, String value, int userId) throws RemoteException { + checkWritePermission(userId); + + writeToDb(key, value, userId); + } + + @Override + public boolean getBoolean(String key, boolean defaultValue, int userId) throws RemoteException { + //checkReadPermission(userId); + + String value = readFromDb(key, null, userId); + return TextUtils.isEmpty(value) ? + defaultValue : (value.equals("1") || value.equals("true")); + } + + @Override + public long getLong(String key, long defaultValue, int userId) throws RemoteException { + //checkReadPermission(userId); + + String value = readFromDb(key, null, userId); + return TextUtils.isEmpty(value) ? defaultValue : Long.parseLong(value); + } + + @Override + public String getString(String key, String defaultValue, int userId) throws RemoteException { + //checkReadPermission(userId); + + return readFromDb(key, defaultValue, userId); + } + + private String getLockPatternFilename(int userId) { + String dataSystemDirectory = + android.os.Environment.getDataDirectory().getAbsolutePath() + + SYSTEM_DIRECTORY; + if (userId == 0) { + // Leave it in the same place for user 0 + return dataSystemDirectory + LOCK_PATTERN_FILE; + } else { + return dataSystemDirectory + "users/" + userId + "/" + LOCK_PATTERN_FILE; + } + } + + private String getLockPasswordFilename(int userId) { + String dataSystemDirectory = + android.os.Environment.getDataDirectory().getAbsolutePath() + + SYSTEM_DIRECTORY; + if (userId == 0) { + // Leave it in the same place for user 0 + return dataSystemDirectory + LOCK_PASSWORD_FILE; + } else { + return dataSystemDirectory + "users/" + userId + "/" + LOCK_PASSWORD_FILE; + } + } + + @Override + public boolean havePassword(int userId) throws RemoteException { + // Do we need a permissions check here? + + return new File(getLockPasswordFilename(userId)).length() > 0; + } + + @Override + public boolean havePattern(int userId) throws RemoteException { + // Do we need a permissions check here? + + return new File(getLockPatternFilename(userId)).length() > 0; + } + + @Override + public void setLockPattern(byte[] hash, int userId) throws RemoteException { + checkWritePermission(userId); + + writeFile(getLockPatternFilename(userId), hash); + } + + @Override + public boolean checkPattern(byte[] hash, int userId) throws RemoteException { + checkPasswordReadPermission(userId); + try { + // Read all the bytes from the file + RandomAccessFile raf = new RandomAccessFile(getLockPatternFilename(userId), "r"); + final byte[] stored = new byte[(int) raf.length()]; + int got = raf.read(stored, 0, stored.length); + raf.close(); + if (got <= 0) { + return true; + } + // Compare the hash from the file with the entered pattern's hash + return Arrays.equals(stored, hash); + } catch (FileNotFoundException fnfe) { + Slog.e(TAG, "Cannot read file " + fnfe); + return true; + } catch (IOException ioe) { + Slog.e(TAG, "Cannot read file " + ioe); + return true; + } + } + + @Override + public void setLockPassword(byte[] hash, int userId) throws RemoteException { + checkWritePermission(userId); + + writeFile(getLockPasswordFilename(userId), hash); + } + + @Override + public boolean checkPassword(byte[] hash, int userId) throws RemoteException { + checkPasswordReadPermission(userId); + + try { + // Read all the bytes from the file + RandomAccessFile raf = new RandomAccessFile(getLockPasswordFilename(userId), "r"); + final byte[] stored = new byte[(int) raf.length()]; + int got = raf.read(stored, 0, stored.length); + raf.close(); + if (got <= 0) { + return true; + } + // Compare the hash from the file with the entered password's hash + return Arrays.equals(stored, hash); + } catch (FileNotFoundException fnfe) { + Slog.e(TAG, "Cannot read file " + fnfe); + return true; + } catch (IOException ioe) { + Slog.e(TAG, "Cannot read file " + ioe); + return true; + } + } + + @Override + public void removeUser(int userId) { + checkWritePermission(userId); + + SQLiteDatabase db = mOpenHelper.getWritableDatabase(); + try { + File file = new File(getLockPasswordFilename(userId)); + if (file.exists()) { + file.delete(); + } + file = new File(getLockPatternFilename(userId)); + if (file.exists()) { + file.delete(); + } + + db.beginTransaction(); + db.delete(TABLE, COLUMN_USERID + "='" + userId + "'", null); + db.setTransactionSuccessful(); + } finally { + db.endTransaction(); + } + } + + private void writeFile(String name, byte[] hash) { + try { + // Write the hash to file + RandomAccessFile raf = new RandomAccessFile(name, "rw"); + // Truncate the file if pattern is null, to clear the lock + if (hash == null || hash.length == 0) { + raf.setLength(0); + } else { + raf.write(hash, 0, hash.length); + } + raf.close(); + } catch (IOException ioe) { + Slog.e(TAG, "Error writing to file " + ioe); + } + } + + private void writeToDb(String key, String value, int userId) { + ContentValues cv = new ContentValues(); + cv.put(COLUMN_KEY, key); + cv.put(COLUMN_USERID, userId); + cv.put(COLUMN_VALUE, value); + + SQLiteDatabase db = mOpenHelper.getWritableDatabase(); + db.beginTransaction(); + try { + db.delete(TABLE, COLUMN_KEY + "=? AND " + COLUMN_USERID + "=?", + new String[] {key, Integer.toString(userId)}); + db.insert(TABLE, null, cv); + db.setTransactionSuccessful(); + } finally { + db.endTransaction(); + } + } + + private String readFromDb(String key, String defaultValue, int userId) { + Cursor cursor; + String result = defaultValue; + SQLiteDatabase db = mOpenHelper.getReadableDatabase(); + if ((cursor = db.query(TABLE, COLUMNS_FOR_QUERY, + COLUMN_USERID + "=? AND " + COLUMN_KEY + "=?", + new String[] { Integer.toString(userId), key }, + null, null, null)) != null) { + if (cursor.moveToFirst()) { + result = cursor.getString(0); + } + cursor.close(); + } + return result; + } + + class DatabaseHelper extends SQLiteOpenHelper { + private static final String TAG = "LockSettingsDB"; + private static final String DATABASE_NAME = "locksettings.db"; + + private static final int DATABASE_VERSION = 1; + + public DatabaseHelper(Context context) { + super(context, DATABASE_NAME, null, DATABASE_VERSION); + setWriteAheadLoggingEnabled(true); + } + + private void createTable(SQLiteDatabase db) { + db.execSQL("CREATE TABLE " + TABLE + " (" + + "_id INTEGER PRIMARY KEY AUTOINCREMENT," + + COLUMN_KEY + " TEXT," + + COLUMN_USERID + " INTEGER," + + COLUMN_VALUE + " TEXT" + + ");"); + } + + @Override + public void onCreate(SQLiteDatabase db) { + createTable(db); + } + + @Override + public void onUpgrade(SQLiteDatabase db, int oldVersion, int currentVersion) { + // Nothing yet + } + } + + private static final String[] VALID_SETTINGS = new String[] { + LockPatternUtils.LOCKOUT_PERMANENT_KEY, + LockPatternUtils.LOCKOUT_ATTEMPT_DEADLINE, + LockPatternUtils.PATTERN_EVER_CHOSEN_KEY, + LockPatternUtils.PASSWORD_TYPE_KEY, + LockPatternUtils.PASSWORD_TYPE_ALTERNATE_KEY, + LockPatternUtils.LOCK_PASSWORD_SALT_KEY, + LockPatternUtils.DISABLE_LOCKSCREEN_KEY, + LockPatternUtils.LOCKSCREEN_OPTIONS, + LockPatternUtils.LOCKSCREEN_BIOMETRIC_WEAK_FALLBACK, + LockPatternUtils.BIOMETRIC_WEAK_EVER_CHOSEN_KEY, + LockPatternUtils.LOCKSCREEN_POWER_BUTTON_INSTANTLY_LOCKS, + LockPatternUtils.PASSWORD_HISTORY_KEY, + Secure.LOCK_PATTERN_ENABLED, + Secure.LOCK_BIOMETRIC_WEAK_FLAGS, + Secure.LOCK_PATTERN_VISIBLE, + Secure.LOCK_PATTERN_TACTILE_FEEDBACK_ENABLED + }; +} |
