diff options
Diffstat (limited to 'core/java')
49 files changed, 1615 insertions, 473 deletions
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java index 69cba78..e79e20c 100644 --- a/core/java/android/app/Activity.java +++ b/core/java/android/app/Activity.java @@ -108,6 +108,7 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.HashMap; +import java.util.List; /** * An activity is a single, focused thing that the user can do. Almost all @@ -706,8 +707,6 @@ public class Activity extends ContextThemeWrapper /*package*/ ActivityThread mMainThread; Activity mParent; boolean mCalled; - boolean mCheckedForLoaderManager; - boolean mLoadersStarted; /*package*/ boolean mResumed; private boolean mStopped; boolean mFinished; @@ -726,8 +725,8 @@ public class Activity extends ContextThemeWrapper static final class NonConfigurationInstances { Object activity; HashMap<String, Object> children; - ArrayList<Fragment> fragments; - ArrayMap<String, LoaderManagerImpl> loaders; + List<Fragment> fragments; + ArrayMap<String, LoaderManager> loaders; VoiceInteractor voiceInteractor; } /* package */ NonConfigurationInstances mLastNonConfigurationInstances; @@ -747,26 +746,13 @@ public class Activity extends ContextThemeWrapper private CharSequence mTitle; private int mTitleColor = 0; - final FragmentManagerImpl mFragments = new FragmentManagerImpl(); - final FragmentContainer mContainer = new FragmentContainer() { - @Override - @Nullable - public View findViewById(int id) { - return Activity.this.findViewById(id); - } - @Override - public boolean hasView() { - Window window = Activity.this.getWindow(); - return (window != null && window.peekDecorView() != null); - } - }; + // we must have a handler before the FragmentController is constructed + final Handler mHandler = new Handler(); + final FragmentController mFragments = FragmentController.createController(new HostCallbacks()); // Most recent call to requestVisibleBehind(). boolean mVisibleBehind; - ArrayMap<String, LoaderManagerImpl> mAllLoaderManagers; - LoaderManagerImpl mLoaderManager; - private static final class ManagedCursor { ManagedCursor(Cursor cursor) { mCursor = cursor; @@ -802,7 +788,6 @@ public class Activity extends ContextThemeWrapper private final Object mInstanceTracker = StrictMode.trackActivity(this); private Thread mUiThread; - final Handler mHandler = new Handler(); ActivityTransitionState mActivityTransitionState = new ActivityTransitionState(); SharedElementCallback mEnterTransitionListener = SharedElementCallback.NULL_CALLBACK; @@ -863,28 +848,7 @@ public class Activity extends ContextThemeWrapper * Return the LoaderManager for this activity, creating it if needed. */ public LoaderManager getLoaderManager() { - if (mLoaderManager != null) { - return mLoaderManager; - } - mCheckedForLoaderManager = true; - mLoaderManager = getLoaderManager("(root)", mLoadersStarted, true); - return mLoaderManager; - } - - LoaderManagerImpl getLoaderManager(String who, boolean started, boolean create) { - if (mAllLoaderManagers == null) { - mAllLoaderManagers = new ArrayMap<String, LoaderManagerImpl>(); - } - LoaderManagerImpl lm = mAllLoaderManagers.get(who); - if (lm == null) { - if (create) { - lm = new LoaderManagerImpl(who, this, started); - mAllLoaderManagers.put(who, lm); - } - } else { - lm.updateActivity(this); - } - return lm; + return mFragments.getLoaderManager(); } /** @@ -931,7 +895,7 @@ public class Activity extends ContextThemeWrapper protected void onCreate(@Nullable Bundle savedInstanceState) { if (DEBUG_LIFECYCLE) Slog.v(TAG, "onCreate " + this + ": " + savedInstanceState); if (mLastNonConfigurationInstances != null) { - mAllLoaderManagers = mLastNonConfigurationInstances.loaders; + mFragments.restoreLoaderNonConfig(mLastNonConfigurationInstances.loaders); } if (mActivityInfo.parentActivityName != null) { if (mActionBar == null) { @@ -1172,15 +1136,7 @@ public class Activity extends ContextThemeWrapper if (DEBUG_LIFECYCLE) Slog.v(TAG, "onStart " + this); mCalled = true; - if (!mLoadersStarted) { - mLoadersStarted = true; - if (mLoaderManager != null) { - mLoaderManager.doStart(); - } else if (!mCheckedForLoaderManager) { - mLoaderManager = getLoaderManager("(root)", mLoadersStarted, false); - } - mCheckedForLoaderManager = true; - } + mFragments.doLoaderStart(); getApplication().dispatchActivityStarted(this); } @@ -1873,27 +1829,9 @@ public class Activity extends ContextThemeWrapper NonConfigurationInstances retainNonConfigurationInstances() { Object activity = onRetainNonConfigurationInstance(); HashMap<String, Object> children = onRetainNonConfigurationChildInstances(); - ArrayList<Fragment> fragments = mFragments.retainNonConfig(); - boolean retainLoaders = false; - if (mAllLoaderManagers != null) { - // prune out any loader managers that were already stopped and so - // have nothing useful to retain. - final int N = mAllLoaderManagers.size(); - LoaderManagerImpl loaders[] = new LoaderManagerImpl[N]; - for (int i=N-1; i>=0; i--) { - loaders[i] = mAllLoaderManagers.valueAt(i); - } - for (int i=0; i<N; i++) { - LoaderManagerImpl lm = loaders[i]; - if (lm.mRetaining) { - retainLoaders = true; - } else { - lm.doDestroy(); - mAllLoaderManagers.remove(lm.mWho); - } - } - } - if (activity == null && children == null && fragments == null && !retainLoaders + List<Fragment> fragments = mFragments.retainNonConfig(); + ArrayMap<String, LoaderManager> loaders = mFragments.retainLoaderNonConfig(); + if (activity == null && children == null && fragments == null && loaders == null && mVoiceInteractor == null) { return null; } @@ -1902,7 +1840,7 @@ public class Activity extends ContextThemeWrapper nci.activity = activity; nci.children = children; nci.fragments = fragments; - nci.loaders = mAllLoaderManagers; + nci.loaders = loaders; nci.voiceInteractor = mVoiceInteractor; return nci; } @@ -1924,18 +1862,7 @@ public class Activity extends ContextThemeWrapper * with this activity. */ public FragmentManager getFragmentManager() { - return mFragments; - } - - void invalidateFragment(String who) { - //Log.v(TAG, "invalidateFragmentIndex: index=" + index); - if (mAllLoaderManagers != null) { - LoaderManagerImpl lm = mAllLoaderManagers.get(who); - if (lm != null && !lm.mRetaining) { - lm.doDestroy(); - mAllLoaderManagers.remove(who); - } - } + return mFragments.getFragmentManager(); } /** @@ -2518,7 +2445,7 @@ public class Activity extends ContextThemeWrapper return; } - if (!mFragments.popBackStackImmediate()) { + if (!mFragments.getFragmentManager().popBackStackImmediate()) { finishAfterTransition(); } } @@ -5518,21 +5445,13 @@ public class Activity extends ContextThemeWrapper writer.print(mResumed); writer.print(" mStopped="); writer.print(mStopped); writer.print(" mFinished="); writer.println(mFinished); - writer.print(innerPrefix); writer.print("mLoadersStarted="); - writer.println(mLoadersStarted); writer.print(innerPrefix); writer.print("mChangingConfigurations="); writer.println(mChangingConfigurations); writer.print(innerPrefix); writer.print("mCurrentConfig="); writer.println(mCurrentConfig); - if (mLoaderManager != null) { - writer.print(prefix); writer.print("Loader Manager "); - writer.print(Integer.toHexString(System.identityHashCode(mLoaderManager))); - writer.println(":"); - mLoaderManager.dump(prefix + " ", fd, writer, args); - } - - mFragments.dump(prefix, fd, writer, args); + mFragments.dumpLoaders(innerPrefix, fd, writer, args); + mFragments.getFragmentManager().dump(innerPrefix, fd, writer, args); if (getWindow() != null && getWindow().peekDecorView() != null && @@ -6128,7 +6047,7 @@ public class Activity extends ContextThemeWrapper Configuration config, String referrer, IVoiceInteractor voiceInteractor) { attachBaseContext(context); - mFragments.attachActivity(this, mContainer, null); + mFragments.attachHost(null /*parent*/); mWindow = new PhoneWindow(this); mWindow.setCallback(this); @@ -6211,18 +6130,7 @@ public class Activity extends ContextThemeWrapper " did not call through to super.onStart()"); } mFragments.dispatchStart(); - if (mAllLoaderManagers != null) { - final int N = mAllLoaderManagers.size(); - LoaderManagerImpl loaders[] = new LoaderManagerImpl[N]; - for (int i=N-1; i>=0; i--) { - loaders[i] = mAllLoaderManagers.valueAt(i); - } - for (int i=0; i<N; i++) { - LoaderManagerImpl lm = loaders[i]; - lm.finishRetain(); - lm.doReportStart(); - } - } + mFragments.reportLoaderStart(); mActivityTransitionState.enterReady(this); } @@ -6328,16 +6236,7 @@ public class Activity extends ContextThemeWrapper final void performStop() { mDoReportFullyDrawn = false; - if (mLoadersStarted) { - mLoadersStarted = false; - if (mLoaderManager != null) { - if (!mChangingConfigurations) { - mLoaderManager.doStop(); - } else { - mLoaderManager.doRetain(); - } - } - } + mFragments.doLoaderStop(mChangingConfigurations /*retain*/); if (!mStopped) { if (mWindow != null) { @@ -6379,9 +6278,7 @@ public class Activity extends ContextThemeWrapper mWindow.destroy(); mFragments.dispatchDestroy(); onDestroy(); - if (mLoaderManager != null) { - mLoaderManager.doDestroy(); - } + mFragments.doLoaderDestroy(); if (mVoiceInteractor != null) { mVoiceInteractor.detachActivity(); } @@ -6541,4 +6438,74 @@ public class Activity extends ContextThemeWrapper return intent != null && PackageManager.ACTION_REQUEST_PERMISSIONS.equals(intent.getAction()); } + + class HostCallbacks extends FragmentHostCallback<Activity> { + public HostCallbacks() { + super(Activity.this /*activity*/); + } + + @Override + public void onDump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) { + Activity.this.dump(prefix, fd, writer, args); + } + + @Override + public boolean onShouldSaveFragmentState(Fragment fragment) { + return !isFinishing(); + } + + @Override + public LayoutInflater onGetLayoutInflater() { + final LayoutInflater result = Activity.this.getLayoutInflater(); + if (onUseFragmentManagerInflaterFactory()) { + return result.cloneInContext(Activity.this); + } + return result; + } + + @Override + public boolean onUseFragmentManagerInflaterFactory() { + // Newer platform versions use the child fragment manager's LayoutInflaterFactory. + return getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.LOLLIPOP; + } + + @Override + public Activity onGetHost() { + return Activity.this; + } + + @Override + public void onInvalidateOptionsMenu() { + Activity.this.invalidateOptionsMenu(); + } + + @Override + public void onStartActivityFromFragment(Fragment fragment, Intent intent, int requestCode, + Bundle options) { + Activity.this.startActivityFromFragment(fragment, intent, requestCode, options); + } + + @Override + public boolean onHasWindowAnimations() { + return getWindow() != null; + } + + @Override + public int onGetWindowAnimations() { + final Window w = getWindow(); + return (w == null) ? 0 : w.getAttributes().windowAnimations; + } + + @Nullable + @Override + public View onFindViewById(int id) { + return Activity.this.findViewById(id); + } + + @Override + public boolean onHasView() { + final Window w = getWindow(); + return (w != null && w.peekDecorView() != null); + } + } } diff --git a/core/java/android/app/AlarmManager.java b/core/java/android/app/AlarmManager.java index 9d1d312..b0fda9c 100644 --- a/core/java/android/app/AlarmManager.java +++ b/core/java/android/app/AlarmManager.java @@ -26,6 +26,10 @@ import android.os.Parcelable; import android.os.RemoteException; import android.os.UserHandle; import android.os.WorkSource; +import android.text.TextUtils; +import libcore.util.ZoneInfoDB; + +import java.io.IOException; /** * This class provides access to the system alarm services. These allow you @@ -151,6 +155,7 @@ public class AlarmManager private final IAlarmManager mService; private final boolean mAlwaysExact; + private final int mTargetSdkVersion; /** @@ -159,8 +164,8 @@ public class AlarmManager AlarmManager(IAlarmManager service, Context ctx) { mService = service; - final int sdkVersion = ctx.getApplicationInfo().targetSdkVersion; - mAlwaysExact = (sdkVersion < Build.VERSION_CODES.KITKAT); + mTargetSdkVersion = ctx.getApplicationInfo().targetSdkVersion; + mAlwaysExact = (mTargetSdkVersion < Build.VERSION_CODES.KITKAT); } private long legacyExactLength() { @@ -585,12 +590,38 @@ public class AlarmManager } /** - * Set the system default time zone. - * Requires the permission android.permission.SET_TIME_ZONE. - * - * @param timeZone in the format understood by {@link java.util.TimeZone} + * Sets the system's persistent default time zone. This is the time zone for all apps, even + * after a reboot. Use {@link java.util.TimeZone#setDefault} if you just want to change the + * time zone within your app, and even then prefer to pass an explicit + * {@link java.util.TimeZone} to APIs that require it rather than changing the time zone for + * all threads. + * + * <p> On android M and above, it is an error to pass in a non-Olson timezone to this + * function. Note that this is a bad idea on all Android releases because POSIX and + * the {@code TimeZone} class have opposite interpretations of {@code '+'} and {@code '-'} + * in the same non-Olson ID. + * + * @param timeZone one of the Olson ids from the list returned by + * {@link java.util.TimeZone#getAvailableIDs} */ public void setTimeZone(String timeZone) { + if (TextUtils.isEmpty(timeZone)) { + return; + } + + // Reject this timezone if it isn't an Olson zone we recognize. + if (mTargetSdkVersion >= Build.VERSION_CODES.MNC) { + boolean hasTimeZone = false; + try { + hasTimeZone = ZoneInfoDB.getInstance().hasTimeZone(timeZone); + } catch (IOException ignored) { + } + + if (!hasTimeZone) { + throw new IllegalArgumentException("Timezone: " + timeZone + " is not an Olson ID"); + } + } + try { mService.setTimeZone(timeZone); } catch (RemoteException ex) { diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java index 1127436..8a3c9c8 100644 --- a/core/java/android/app/AppOpsManager.java +++ b/core/java/android/app/AppOpsManager.java @@ -217,8 +217,14 @@ public class AppOpsManager { public static final int OP_READ_PHONE_STATE = 51; /** @hide Add voicemail messages to the voicemail content provider. */ public static final int OP_ADD_VOICEMAIL = 52; + /** @hide Access APIs for SIP calling over VOIP or WiFi. */ + public static final int OP_USE_SIP = 53; + /** @hide Intercept outgoing calls. */ + public static final int OP_PROCESS_OUTGOING_CALLS = 54; + /** @hide User the fingerprint API. */ + public static final int OP_USE_FINGERPRINT = 55; /** @hide */ - public static final int _NUM_OP = 53; + public static final int _NUM_OP = 56; /** Access to coarse location information. */ public static final String OPSTR_COARSE_LOCATION = "android:coarse_location"; @@ -351,7 +357,10 @@ public class AppOpsManager { OP_ASSIST_STRUCTURE, OP_ASSIST_SCREENSHOT, OP_READ_PHONE_STATE, - OP_ADD_VOICEMAIL + OP_ADD_VOICEMAIL, + OP_USE_SIP, + OP_PROCESS_OUTGOING_CALLS, + OP_USE_FINGERPRINT }; /** @@ -411,6 +420,9 @@ public class AppOpsManager { null, null, null, + null, + null, + null, null }; @@ -471,7 +483,10 @@ public class AppOpsManager { "ASSIST_STRUCTURE", "ASSIST_SCREENSHOT", "OP_READ_PHONE_STATE", - "ADD_VOICEMAIL" + "ADD_VOICEMAIL", + "USE_SIP", + "PROCESS_OUTGOING_CALLS", + "USE_FINGERPRINT" }; /** @@ -531,7 +546,10 @@ public class AppOpsManager { null, // no permission for receiving assist structure null, // no permission for receiving assist screenshot Manifest.permission.READ_PHONE_STATE, - Manifest.permission.ADD_VOICEMAIL + Manifest.permission.ADD_VOICEMAIL, + Manifest.permission.USE_SIP, + Manifest.permission.PROCESS_OUTGOING_CALLS, + Manifest.permission.USE_FINGERPRINT }; /** @@ -592,7 +610,10 @@ public class AppOpsManager { null, // ASSIST_STRUCTURE null, // ASSIST_SCREENSHOT null, // READ_PHONE_STATE - null // ADD_VOICEMAIL + null, // ADD_VOICEMAIL + null, // USE_SIP + null, // PROCESS_OUTGOING_CALLS + null // USE_FINGERPRINT }; /** @@ -652,7 +673,10 @@ public class AppOpsManager { false, //ASSIST_STRUCTURE false, //ASSIST_SCREENSHOT false, //READ_PHONE_STATE - false //ADD_VOICEMAIL + false, //ADD_VOICEMAIL + false, // USE_SIP + false, // PROCESS_OUTGOING_CALLS + false // USE_FINGERPRINT }; /** @@ -711,6 +735,9 @@ public class AppOpsManager { AppOpsManager.MODE_ALLOWED, AppOpsManager.MODE_ALLOWED, AppOpsManager.MODE_ALLOWED, + AppOpsManager.MODE_ALLOWED, + AppOpsManager.MODE_ALLOWED, + AppOpsManager.MODE_ALLOWED, AppOpsManager.MODE_ALLOWED }; @@ -774,6 +801,9 @@ public class AppOpsManager { false, false, false, + false, + false, + false, false }; diff --git a/core/java/android/app/AssistStructure.java b/core/java/android/app/AssistStructure.java index 9946d79..3abbb5b 100644 --- a/core/java/android/app/AssistStructure.java +++ b/core/java/android/app/AssistStructure.java @@ -224,6 +224,7 @@ final public class AssistStructure implements Parcelable { static final int FLAGS_CHECKED = 0x00000200; static final int FLAGS_CLICKABLE = 0x00004000; static final int FLAGS_LONG_CLICKABLE = 0x00200000; + static final int FLAGS_STYLUS_BUTTON_PRESSABLE = 0x00400000; int mFlags; @@ -401,6 +402,10 @@ final public class AssistStructure implements Parcelable { return (mFlags&ViewNode.FLAGS_LONG_CLICKABLE) != 0; } + public boolean isStylusButtonPressable() { + return (mFlags&ViewNode.FLAGS_STYLUS_BUTTON_PRESSABLE) != 0; + } + public String getClassName() { return mClassName; } @@ -513,6 +518,12 @@ final public class AssistStructure implements Parcelable { } @Override + public void setStylusButtonPressable(boolean state) { + mNode.mFlags = (mNode.mFlags&~ViewNode.FLAGS_STYLUS_BUTTON_PRESSABLE) + | (state ? ViewNode.FLAGS_STYLUS_BUTTON_PRESSABLE : 0); + } + + @Override public void setFocusable(boolean state) { mNode.mFlags = (mNode.mFlags&~ViewNode.FLAGS_FOCUSABLE) | (state ? ViewNode.FLAGS_FOCUSABLE : 0); diff --git a/core/java/android/app/BackStackRecord.java b/core/java/android/app/BackStackRecord.java index 8fb048b..49644a7 100644 --- a/core/java/android/app/BackStackRecord.java +++ b/core/java/android/app/BackStackRecord.java @@ -416,14 +416,14 @@ final class BackStackRecord extends FragmentTransaction implements public CharSequence getBreadCrumbTitle() { if (mBreadCrumbTitleRes != 0) { - return mManager.mActivity.getText(mBreadCrumbTitleRes); + return mManager.mHost.getContext().getText(mBreadCrumbTitleRes); } return mBreadCrumbTitleText; } public CharSequence getBreadCrumbShortTitle() { if (mBreadCrumbShortTitleRes != 0) { - return mManager.mActivity.getText(mBreadCrumbShortTitleRes); + return mManager.mHost.getContext().getText(mBreadCrumbShortTitleRes); } return mBreadCrumbShortTitleText; } @@ -868,7 +868,7 @@ final class BackStackRecord extends FragmentTransaction implements */ private void calculateFragments(SparseArray<Fragment> firstOutFragments, SparseArray<Fragment> lastInFragments) { - if (!mManager.mContainer.hasView()) { + if (!mManager.mContainer.onHasView()) { return; // nothing to see, so no transitions } Op op = mHead; @@ -926,7 +926,7 @@ final class BackStackRecord extends FragmentTransaction implements */ public void calculateBackFragments(SparseArray<Fragment> firstOutFragments, SparseArray<Fragment> lastInFragments) { - if (!mManager.mContainer.hasView()) { + if (!mManager.mContainer.onHasView()) { return; // nothing to see, so no transitions } Op op = mHead; @@ -1002,7 +1002,7 @@ final class BackStackRecord extends FragmentTransaction implements // Adding a non-existent target view makes sure that the transitions don't target // any views by default. They'll only target the views we tell add. If we don't // add any, then no views will be targeted. - state.nonExistentView = new View(mManager.mActivity); + state.nonExistentView = new View(mManager.mHost.getContext()); // Go over all leaving fragments. for (int i = 0; i < firstOutFragments.size(); i++) { @@ -1275,7 +1275,7 @@ final class BackStackRecord extends FragmentTransaction implements */ private void configureTransitions(int containerId, TransitionState state, boolean isBack, SparseArray<Fragment> firstOutFragments, SparseArray<Fragment> lastInFragments) { - ViewGroup sceneRoot = (ViewGroup) mManager.mContainer.findViewById(containerId); + ViewGroup sceneRoot = (ViewGroup) mManager.mContainer.onFindViewById(containerId); if (sceneRoot != null) { Fragment inFragment = lastInFragments.get(containerId); Fragment outFragment = firstOutFragments.get(containerId); diff --git a/core/java/android/app/DialogFragment.java b/core/java/android/app/DialogFragment.java index bde5a61..2fb8cc2 100644 --- a/core/java/android/app/DialogFragment.java +++ b/core/java/android/app/DialogFragment.java @@ -410,7 +410,7 @@ public class DialogFragment extends Fragment return (LayoutInflater)mDialog.getContext().getSystemService( Context.LAYOUT_INFLATER_SERVICE); } - return (LayoutInflater)mActivity.getSystemService( + return (LayoutInflater) mHost.getContext().getSystemService( Context.LAYOUT_INFLATER_SERVICE); } diff --git a/core/java/android/app/Fragment.java b/core/java/android/app/Fragment.java index 4fdae7f..91d810e 100644 --- a/core/java/android/app/Fragment.java +++ b/core/java/android/app/Fragment.java @@ -26,7 +26,6 @@ import android.content.Intent; import android.content.res.Configuration; import android.content.res.Resources; import android.content.res.TypedArray; -import android.os.Build; import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; @@ -94,19 +93,20 @@ final class FragmentState implements Parcelable { mSavedFragmentState = in.readBundle(); } - public Fragment instantiate(Activity activity, Fragment parent) { + public Fragment instantiate(FragmentHostCallback host, Fragment parent) { if (mInstance != null) { return mInstance; } + final Context context = host.getContext(); if (mArguments != null) { - mArguments.setClassLoader(activity.getClassLoader()); + mArguments.setClassLoader(context.getClassLoader()); } - mInstance = Fragment.instantiate(activity, mClassName, mArguments); + mInstance = Fragment.instantiate(context, mClassName, mArguments); if (mSavedFragmentState != null) { - mSavedFragmentState.setClassLoader(activity.getClassLoader()); + mSavedFragmentState.setClassLoader(context.getClassLoader()); mInstance.mSavedFragmentState = mSavedFragmentState; } mInstance.setIndex(mIndex, parent); @@ -117,7 +117,7 @@ final class FragmentState implements Parcelable { mInstance.mTag = mTag; mInstance.mRetainInstance = mRetainInstance; mInstance.mDetached = mDetached; - mInstance.mFragmentManager = activity.mFragments; + mInstance.mFragmentManager = host.mFragmentManager; if (FragmentManagerImpl.DEBUG) Log.v(FragmentManagerImpl.TAG, "Instantiated fragment " + mInstance); @@ -425,7 +425,7 @@ public class Fragment implements ComponentCallbacks2, OnCreateContextMenuListene FragmentManagerImpl mFragmentManager; // Activity this fragment is attached to. - Activity mActivity; + FragmentHostCallback mHost; // Private fragment manager for child fragments inside of this one. FragmentManagerImpl mChildFragmentManager; @@ -775,20 +775,36 @@ public class Fragment implements ComponentCallbacks2, OnCreateContextMenuListene } /** + * Return the {@link Context} this fragment is currently associated with. + */ + public Context getContext() { + return mHost == null ? null : mHost.getContext(); + } + + /** * Return the Activity this fragment is currently associated with. */ final public Activity getActivity() { - return mActivity; + return mHost == null ? null : mHost.getActivity(); + } + + /** + * Return the host object of this fragment. May return {@code null} if the fragment + * isn't currently being hosted. + */ + @Nullable + final public Object getHost() { + return mHost == null ? null : mHost.onGetHost(); } /** * Return <code>getActivity().getResources()</code>. */ final public Resources getResources() { - if (mActivity == null) { + if (mHost == null) { throw new IllegalStateException("Fragment " + this + " not attached to Activity"); } - return mActivity.getResources(); + return mHost.getContext().getResources(); } /** @@ -870,7 +886,7 @@ public class Fragment implements ComponentCallbacks2, OnCreateContextMenuListene * Return true if the fragment is currently added to its activity. */ final public boolean isAdded() { - return mActivity != null && mAdded; + return mHost != null && mAdded; } /** @@ -1037,11 +1053,11 @@ public class Fragment implements ComponentCallbacks2, OnCreateContextMenuListene if (mLoaderManager != null) { return mLoaderManager; } - if (mActivity == null) { + if (mHost == null) { throw new IllegalStateException("Fragment " + this + " not attached to Activity"); } mCheckedForLoaderManager = true; - mLoaderManager = mActivity.getLoaderManager(mWho, mLoadersStarted, true); + mLoaderManager = mHost.getLoaderManager(mWho, mLoadersStarted, true); return mLoaderManager; } @@ -1065,15 +1081,15 @@ public class Fragment implements ComponentCallbacks2, OnCreateContextMenuListene * Context.startActivity(Intent, Bundle)} for more details. */ public void startActivity(Intent intent, Bundle options) { - if (mActivity == null) { + if (mHost == null) { throw new IllegalStateException("Fragment " + this + " not attached to Activity"); } if (options != null) { - mActivity.startActivityFromFragment(this, intent, -1, options); + mHost.onStartActivityFromFragment(this, intent, -1, options); } else { // Note we want to go through this call for compatibility with // applications that may have overridden the method. - mActivity.startActivityFromFragment(this, intent, -1); + mHost.onStartActivityFromFragment(this, intent, -1, null /*options*/); } } @@ -1090,10 +1106,10 @@ public class Fragment implements ComponentCallbacks2, OnCreateContextMenuListene * containing Activity. */ public void startActivityForResult(Intent intent, int requestCode, Bundle options) { - if (mActivity == null) { + if (mHost == null) { throw new IllegalStateException("Fragment " + this + " not attached to Activity"); } - mActivity.startActivityFromFragment(this, intent, requestCode, options); + mHost.onStartActivityFromFragment(this, intent, requestCode, options); } /** @@ -1181,11 +1197,12 @@ public class Fragment implements ComponentCallbacks2, OnCreateContextMenuListene * @see android.content.Context#checkSelfPermission(String) */ public final void requestPermissions(@NonNull String[] permissions, int requestCode) { - if (mActivity == null) { + if (mHost == null) { throw new IllegalStateException("Fragment " + this + " not attached to Activity"); } - Intent intent = mActivity.getPackageManager().buildRequestPermissionsIntent(permissions); - mActivity.startActivityFromFragment(this, intent, requestCode, null); + Intent intent = + mHost.getContext().getPackageManager().buildRequestPermissionsIntent(permissions); + mHost.onStartActivityFromFragment(this, intent, requestCode, null); } /** @@ -1211,19 +1228,16 @@ public class Fragment implements ComponentCallbacks2, OnCreateContextMenuListene * inflation. Maybe this should become a public API. Note sure. */ public LayoutInflater getLayoutInflater(Bundle savedInstanceState) { - // Newer platform versions use the child fragment manager's LayoutInflaterFactory. - if (mActivity.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.LOLLIPOP) { - LayoutInflater result = mActivity.getLayoutInflater().cloneInContext(mActivity); + final LayoutInflater result = mHost.onGetLayoutInflater(); + if (mHost.onUseFragmentManagerInflaterFactory()) { getChildFragmentManager(); // Init if needed; use raw implementation below. result.setPrivateFactory(mChildFragmentManager.getLayoutInflaterFactory()); - return result; - } else { - return mActivity.getLayoutInflater(); } + return result; } /** - * @deprecated Use {@link #onInflate(Activity, AttributeSet, Bundle)} instead. + * @deprecated Use {@link #onInflate(Context, AttributeSet, Bundle)} instead. */ @Deprecated public void onInflate(AttributeSet attrs, Bundle savedInstanceState) { @@ -1266,29 +1280,29 @@ public class Fragment implements ComponentCallbacks2, OnCreateContextMenuListene * {@sample development/samples/ApiDemos/src/com/example/android/apis/app/FragmentArguments.java * create} * - * @param activity The Activity that is inflating this fragment. + * @param context The Context that is inflating this fragment. * @param attrs The attributes at the tag where the fragment is * being created. * @param savedInstanceState If the fragment is being re-created from * a previous saved state, this is the state. */ - public void onInflate(Activity activity, AttributeSet attrs, Bundle savedInstanceState) { + public void onInflate(Context context, AttributeSet attrs, Bundle savedInstanceState) { onInflate(attrs, savedInstanceState); mCalled = true; - TypedArray a = activity.obtainStyledAttributes(attrs, + TypedArray a = context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.Fragment); - mEnterTransition = loadTransition(activity, a, mEnterTransition, null, + mEnterTransition = loadTransition(context, a, mEnterTransition, null, com.android.internal.R.styleable.Fragment_fragmentEnterTransition); - mReturnTransition = loadTransition(activity, a, mReturnTransition, USE_DEFAULT_TRANSITION, + mReturnTransition = loadTransition(context, a, mReturnTransition, USE_DEFAULT_TRANSITION, com.android.internal.R.styleable.Fragment_fragmentReturnTransition); - mExitTransition = loadTransition(activity, a, mExitTransition, null, + mExitTransition = loadTransition(context, a, mExitTransition, null, com.android.internal.R.styleable.Fragment_fragmentExitTransition); - mReenterTransition = loadTransition(activity, a, mReenterTransition, USE_DEFAULT_TRANSITION, + mReenterTransition = loadTransition(context, a, mReenterTransition, USE_DEFAULT_TRANSITION, com.android.internal.R.styleable.Fragment_fragmentReenterTransition); - mSharedElementEnterTransition = loadTransition(activity, a, mSharedElementEnterTransition, + mSharedElementEnterTransition = loadTransition(context, a, mSharedElementEnterTransition, null, com.android.internal.R.styleable.Fragment_fragmentSharedElementEnterTransition); - mSharedElementReturnTransition = loadTransition(activity, a, mSharedElementReturnTransition, + mSharedElementReturnTransition = loadTransition(context, a, mSharedElementReturnTransition, USE_DEFAULT_TRANSITION, com.android.internal.R.styleable.Fragment_fragmentSharedElementReturnTransition); if (mAllowEnterTransitionOverlap == null) { @@ -1303,9 +1317,30 @@ public class Fragment implements ComponentCallbacks2, OnCreateContextMenuListene } /** - * Called when a fragment is first attached to its activity. + * @deprecated Use {@link #onInflate(Context, AttributeSet, Bundle)} instead. + */ + @Deprecated + public void onInflate(Activity activity, AttributeSet attrs, Bundle savedInstanceState) { + mCalled = true; + } + + /** + * Called when a fragment is first attached to its context. * {@link #onCreate(Bundle)} will be called after this. */ + public void onAttach(Context context) { + mCalled = true; + final Activity hostActivity = mHost == null ? null : mHost.getActivity(); + if (hostActivity != null) { + mCalled = false; + onAttach(hostActivity); + } + } + + /** + * @deprecated Use {@link #onAttach(Context)} instead. + */ + @Deprecated public void onAttach(Activity activity) { mCalled = true; } @@ -1428,7 +1463,7 @@ public class Fragment implements ComponentCallbacks2, OnCreateContextMenuListene mLoadersStarted = true; if (!mCheckedForLoaderManager) { mCheckedForLoaderManager = true; - mLoaderManager = mActivity.getLoaderManager(mWho, mLoadersStarted, false); + mLoaderManager = mHost.getLoaderManager(mWho, mLoadersStarted, false); } if (mLoaderManager != null) { mLoaderManager.doStart(); @@ -1521,7 +1556,7 @@ public class Fragment implements ComponentCallbacks2, OnCreateContextMenuListene // + " mLoaderManager=" + mLoaderManager); if (!mCheckedForLoaderManager) { mCheckedForLoaderManager = true; - mLoaderManager = mActivity.getLoaderManager(mWho, mLoadersStarted, false); + mLoaderManager = mHost.getLoaderManager(mWho, mLoadersStarted, false); } if (mLoaderManager != null) { mLoaderManager.doDestroy(); @@ -1546,7 +1581,7 @@ public class Fragment implements ComponentCallbacks2, OnCreateContextMenuListene mBackStackNesting = 0; mFragmentManager = null; mChildFragmentManager = null; - mActivity = null; + mHost = null; mFragmentId = 0; mContainerId = 0; mTag = null; @@ -2034,9 +2069,9 @@ public class Fragment implements ComponentCallbacks2, OnCreateContextMenuListene writer.print(prefix); writer.print("mFragmentManager="); writer.println(mFragmentManager); } - if (mActivity != null) { - writer.print(prefix); writer.print("mActivity="); - writer.println(mActivity); + if (mHost != null) { + writer.print(prefix); writer.print("mHost="); + writer.println(mHost); } if (mParentFragment != null) { writer.print(prefix); writer.print("mParentFragment="); @@ -2094,10 +2129,10 @@ public class Fragment implements ComponentCallbacks2, OnCreateContextMenuListene void instantiateChildFragmentManager() { mChildFragmentManager = new FragmentManagerImpl(); - mChildFragmentManager.attachActivity(mActivity, new FragmentContainer() { + mChildFragmentManager.attachController(mHost, new FragmentContainer() { @Override @Nullable - public View findViewById(int id) { + public View onFindViewById(int id) { if (mView == null) { throw new IllegalStateException("Fragment does not have a view"); } @@ -2105,7 +2140,7 @@ public class Fragment implements ComponentCallbacks2, OnCreateContextMenuListene } @Override - public boolean hasView() { + public boolean onHasView() { return (mView != null); } }, this); @@ -2319,13 +2354,13 @@ public class Fragment implements ComponentCallbacks2, OnCreateContextMenuListene mLoadersStarted = false; if (!mCheckedForLoaderManager) { mCheckedForLoaderManager = true; - mLoaderManager = mActivity.getLoaderManager(mWho, mLoadersStarted, false); + mLoaderManager = mHost.getLoaderManager(mWho, mLoadersStarted, false); } if (mLoaderManager != null) { - if (mActivity == null || !mActivity.mChangingConfigurations) { - mLoaderManager.doStop(); - } else { + if (mRetaining) { mLoaderManager.doRetain(); + } else { + mLoaderManager.doStop(); } } } diff --git a/core/java/android/app/FragmentContainer.java b/core/java/android/app/FragmentContainer.java new file mode 100644 index 0000000..b2e0300 --- /dev/null +++ b/core/java/android/app/FragmentContainer.java @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2015 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.annotation.IdRes; +import android.annotation.Nullable; +import android.view.View; + +/** + * Callbacks to a {@link Fragment}'s container. + */ +public abstract class FragmentContainer { + /** + * Return the view with the given resource ID. May return {@code null} if the + * view is not a child of this container. + */ + @Nullable + public abstract View onFindViewById(@IdRes int id); + + /** + * Return {@code true} if the container holds any view. + */ + public abstract boolean onHasView(); +} diff --git a/core/java/android/app/FragmentController.java b/core/java/android/app/FragmentController.java new file mode 100644 index 0000000..28dadfa --- /dev/null +++ b/core/java/android/app/FragmentController.java @@ -0,0 +1,384 @@ +/* + * Copyright (C) 2015 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.annotation.Nullable; +import android.content.Context; +import android.content.res.Configuration; +import android.os.Bundle; +import android.os.Parcelable; +import android.util.ArrayMap; +import android.util.AttributeSet; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.View; + +import java.io.FileDescriptor; +import java.io.PrintWriter; +import java.util.List; + +/** + * Provides integration points with a {@link FragmentManager} for a fragment host. + * <p> + * It is the responsibility of the host to take care of the Fragment's lifecycle. + * The methods provided by {@link FragmentController} are for that purpose. + */ +public class FragmentController { + private final FragmentHostCallback<?> mHost; + + /** + * Returns a {@link FragmentController}. + */ + public static final FragmentController createController(FragmentHostCallback<?> callbacks) { + return new FragmentController(callbacks); + } + + private FragmentController(FragmentHostCallback<?> callbacks) { + mHost = callbacks; + } + + /** + * Returns a {@link FragmentManager} for this controller. + */ + public FragmentManager getFragmentManager() { + return mHost.getFragmentManagerImpl(); + } + + /** + * Returns a {@link LoaderManager}. + */ + public LoaderManager getLoaderManager() { + return mHost.getLoaderManagerImpl(); + } + + /** + * Returns a fragment with the given identifier. + */ + @Nullable + public Fragment findFragmentByWho(String who) { + return mHost.mFragmentManager.findFragmentByWho(who); + } + + /** + * Attaches the host to the FragmentManager for this controller. The host must be + * attached before the FragmentManager can be used to manage Fragments. + * */ + public void attachHost(Fragment parent) { + mHost.mFragmentManager.attachController( + mHost, mHost /*container*/, parent); + } + + /** + * Instantiates a Fragment's view. + * + * @param parent The parent that the created view will be placed + * in; <em>note that this may be null</em>. + * @param name Tag name to be inflated. + * @param context The context the view is being created in. + * @param attrs Inflation attributes as specified in XML file. + * + * @return view the newly created view + */ + public View onCreateView(View parent, String name, Context context, AttributeSet attrs) { + return mHost.mFragmentManager.onCreateView(parent, name, context, attrs); + } + + /** + * Marks the fragment state as unsaved. This allows for "state loss" detection. + */ + public void noteStateNotSaved() { + mHost.mFragmentManager.noteStateNotSaved(); + } + + /** + * Saves the state for all Fragments. + */ + public Parcelable saveAllState() { + return mHost.mFragmentManager.saveAllState(); + } + + /** + * Restores the saved state for all Fragments. The given Fragment list are Fragment + * instances retained across configuration changes. + * + * @see #retainNonConfig() + */ + public void restoreAllState(Parcelable state, List<Fragment> nonConfigList) { + mHost.mFragmentManager.restoreAllState(state, nonConfigList); + } + + /** + * Returns a list of Fragments that have opted to retain their instance across + * configuration changes. + */ + public List<Fragment> retainNonConfig() { + return mHost.mFragmentManager.retainNonConfig(); + } + + /** + * Moves all Fragments managed by the controller's FragmentManager + * into the create state. + * <p>Call when Fragments should be created. + * + * @see Fragment#onCreate(Bundle) + */ + public void dispatchCreate() { + mHost.mFragmentManager.dispatchCreate(); + } + + /** + * Moves all Fragments managed by the controller's FragmentManager + * into the activity created state. + * <p>Call when Fragments should be informed their host has been created. + * + * @see Fragment#onActivityCreated(Bundle) + */ + public void dispatchActivityCreated() { + mHost.mFragmentManager.dispatchActivityCreated(); + } + + /** + * Moves all Fragments managed by the controller's FragmentManager + * into the start state. + * <p>Call when Fragments should be started. + * + * @see Fragment#onStart() + */ + public void dispatchStart() { + mHost.mFragmentManager.dispatchStart(); + } + + /** + * Moves all Fragments managed by the controller's FragmentManager + * into the resume state. + * <p>Call when Fragments should be resumed. + * + * @see Fragment#onResume() + */ + public void dispatchResume() { + mHost.mFragmentManager.dispatchResume(); + } + + /** + * Moves all Fragments managed by the controller's FragmentManager + * into the pause state. + * <p>Call when Fragments should be paused. + * + * @see Fragment#onPause() + */ + public void dispatchPause() { + mHost.mFragmentManager.dispatchPause(); + } + + /** + * Moves all Fragments managed by the controller's FragmentManager + * into the stop state. + * <p>Call when Fragments should be stopped. + * + * @see Fragment#onStop() + */ + public void dispatchStop() { + mHost.mFragmentManager.dispatchStop(); + } + + /** + * Moves all Fragments managed by the controller's FragmentManager + * into the destroy view state. + * <p>Call when the Fragment's views should be destroyed. + * + * @see Fragment#onDestroyView() + */ + public void dispatchDestroyView() { + mHost.mFragmentManager.dispatchDestroyView(); + } + + /** + * Moves all Fragments managed by the controller's FragmentManager + * into the destroy state. + * <p>Call when Fragments should be destroyed. + * + * @see Fragment#onDestroy() + */ + public void dispatchDestroy() { + mHost.mFragmentManager.dispatchDestroy(); + } + + /** + * Lets all Fragments managed by the controller's FragmentManager + * know a configuration change occurred. + * <p>Call when there is a configuration change. + * + * @see Fragment#onConfigurationChanged(Configuration) + */ + public void dispatchConfigurationChanged(Configuration newConfig) { + mHost.mFragmentManager.dispatchConfigurationChanged(newConfig); + } + + /** + * Lets all Fragments managed by the controller's FragmentManager + * know the device is in a low memory condition. + * <p>Call when the device is low on memory and Fragment's should trim + * their memory usage. + * + * @see Fragment#onLowMemory() + */ + public void dispatchLowMemory() { + mHost.mFragmentManager.dispatchLowMemory(); + } + + /** + * Lets all Fragments managed by the controller's FragmentManager + * know they should trim their memory usage. + * <p>Call when the Fragment can release allocated memory [such as if + * the Fragment is in the background]. + * + * @see Fragment#onTrimMemory(int) + */ + public void dispatchTrimMemory(int level) { + mHost.mFragmentManager.dispatchTrimMemory(level); + } + + /** + * Lets all Fragments managed by the controller's FragmentManager + * know they should create an options menu. + * <p>Call when the Fragment should create an options menu. + * + * @return {@code true} if the options menu contains items to display + * @see Fragment#onCreateOptionsMenu(Menu, MenuInflater) + */ + public boolean dispatchCreateOptionsMenu(Menu menu, MenuInflater inflater) { + return mHost.mFragmentManager.dispatchCreateOptionsMenu(menu, inflater); + } + + /** + * Lets all Fragments managed by the controller's FragmentManager + * know they should prepare their options menu for display. + * <p>Call immediately before displaying the Fragment's options menu. + * + * @return {@code true} if the options menu contains items to display + * @see Fragment#onPrepareOptionsMenu(Menu) + */ + public boolean dispatchPrepareOptionsMenu(Menu menu) { + return mHost.mFragmentManager.dispatchPrepareOptionsMenu(menu); + } + + /** + * Sends an option item selection event to the Fragments managed by the + * controller's FragmentManager. Once the event has been consumed, + * no additional handling will be performed. + * <p>Call immediately after an options menu item has been selected + * + * @return {@code true} if the options menu selection event was consumed + * @see Fragment#onOptionsItemSelected(MenuItem) + */ + public boolean dispatchOptionsItemSelected(MenuItem item) { + return mHost.mFragmentManager.dispatchOptionsItemSelected(item); + } + + /** + * Sends a context item selection event to the Fragments managed by the + * controller's FragmentManager. Once the event has been consumed, + * no additional handling will be performed. + * <p>Call immediately after an options menu item has been selected + * + * @return {@code true} if the context menu selection event was consumed + * @see Fragment#onContextItemSelected(MenuItem) + */ + public boolean dispatchContextItemSelected(MenuItem item) { + return mHost.mFragmentManager.dispatchContextItemSelected(item); + } + + /** + * Lets all Fragments managed by the controller's FragmentManager + * know their options menu has closed. + * <p>Call immediately after closing the Fragment's options menu. + * + * @see Fragment#onOptionsMenuClosed(Menu) + */ + public void dispatchOptionsMenuClosed(Menu menu) { + mHost.mFragmentManager.dispatchOptionsMenuClosed(menu); + } + + /** + * Execute any pending actions for the Fragments managed by the + * controller's FragmentManager. + * <p>Call when queued actions can be performed [eg when the + * Fragment moves into a start or resume state]. + * @return {@code true} if queued actions were performed + */ + public boolean execPendingActions() { + return mHost.mFragmentManager.execPendingActions(); + } + + /** + * Starts the loaders. + */ + public void doLoaderStart() { + mHost.doLoaderStart(); + } + + /** + * Stops the loaders, optionally retaining their state. This is useful for keeping the + * loader state across configuration changes. + * + * @param retain When {@code true}, the loaders aren't stopped, but, their instances + * are retained in a started state + */ + public void doLoaderStop(boolean retain) { + mHost.doLoaderStop(retain); + } + + /** + * Destroys the loaders and, if their state is not being retained, removes them. + */ + public void doLoaderDestroy() { + mHost.doLoaderDestroy(); + } + + /** + * Lets the loaders know the host is ready to receive notifications. + */ + public void reportLoaderStart() { + mHost.reportLoaderStart(); + } + + /** + * Returns a list of LoaderManagers that have opted to retain their instance across + * configuration changes. + */ + public ArrayMap<String, LoaderManager> retainLoaderNonConfig() { + return mHost.retainLoaderNonConfig(); + } + + /** + * Restores the saved state for all LoaderManagers. The given LoaderManager list are + * LoaderManager instances retained across configuration changes. + * + * @see #retainLoaderNonConfig() + */ + public void restoreLoaderNonConfig(ArrayMap<String, LoaderManager> loaderManagers) { + mHost.restoreLoaderNonConfig(loaderManagers); + } + + /** + * Dumps the current state of the loaders. + */ + public void dumpLoaders(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) { + mHost.dumpLoaders(prefix, fd, writer, args); + } +} diff --git a/core/java/android/app/FragmentHostCallback.java b/core/java/android/app/FragmentHostCallback.java new file mode 100644 index 0000000..dad2c79 --- /dev/null +++ b/core/java/android/app/FragmentHostCallback.java @@ -0,0 +1,315 @@ +/* + * Copyright (C) 2015 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.annotation.Nullable; +import android.app.Activity; +import android.content.Context; +import android.content.Intent; +import android.os.Bundle; +import android.os.Handler; +import android.util.ArrayMap; +import android.util.AttributeSet; +import android.view.LayoutInflater; +import android.view.View; + +import java.io.FileDescriptor; +import java.io.PrintWriter; + +/** + * Integration points with the Fragment host. + * <p> + * Fragments may be hosted by any object; such as an {@link Activity}. In order to + * host fragments, implement {@link FragmentHostCallback}, overriding the methods + * applicable to the host. + */ +public abstract class FragmentHostCallback<E> extends FragmentContainer { + private final Activity mActivity; + final Context mContext; + private final Handler mHandler; + final int mWindowAnimations; + final FragmentManagerImpl mFragmentManager = new FragmentManagerImpl(); + private ArrayMap<String, LoaderManager> mAllLoaderManagers; + private LoaderManagerImpl mLoaderManager; + private boolean mCheckedForLoaderManager; + private boolean mLoadersStarted; + + public FragmentHostCallback(Context context, Handler handler, int windowAnimations) { + this(null /*activity*/, context, handler, windowAnimations); + } + + FragmentHostCallback(Activity activity) { + this(activity, activity /*context*/, activity.mHandler, 0 /*windowAnimations*/); + } + + FragmentHostCallback(Activity activity, Context context, Handler handler, + int windowAnimations) { + mActivity = activity; + mContext = context; + mHandler = handler; + mWindowAnimations = windowAnimations; + } + + /** + * Print internal state into the given stream. + * + * @param prefix Desired prefix to prepend at each line of output. + * @param fd The raw file descriptor that the dump is being sent to. + * @param writer The PrintWriter to which you should dump your state. This will be closed + * for you after you return. + * @param args additional arguments to the dump request. + */ + public void onDump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) { + } + + /** + * Return {@code true} if the fragment's state needs to be saved. + */ + public boolean onShouldSaveFragmentState(Fragment fragment) { + return true; + } + + /** + * Return a {@link LayoutInflater}. + * See {@link Activity#getLayoutInflater()}. + */ + public LayoutInflater onGetLayoutInflater() { + return (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE); + } + + /** + * Return {@code true} if the FragmentManager's LayoutInflaterFactory should be used. + */ + public boolean onUseFragmentManagerInflaterFactory() { + return false; + } + + /** + * Return the object that's currently hosting the fragment. If a {@link Fragment} + * is hosted by a {@link Activity}, the object returned here should be the same + * object returned from {@link Fragment#getActivity()}. + */ + @Nullable + public abstract E onGetHost(); + + /** + * Invalidates the activity's options menu. + * See {@link Activity#invalidateOptionsMenu()} + */ + public void onInvalidateOptionsMenu() { + } + + /** + * Starts a new {@link Activity} from the given fragment. + * See {@link Activity#startActivityForResult(Intent, int)}. + */ + public void onStartActivityFromFragment(Fragment fragment, Intent intent, int requestCode, + Bundle options) { + if (requestCode != -1) { + throw new IllegalStateException( + "Starting activity with a requestCode requires a FragmentActivity host"); + } + mContext.startActivity(intent); + } + + /** + * Return {@code true} if there are window animations. + */ + public boolean onHasWindowAnimations() { + return true; + } + + /** + * Return the window animations. + */ + public int onGetWindowAnimations() { + return mWindowAnimations; + } + + @Nullable + @Override + public View onFindViewById(int id) { + return null; + } + + @Override + public boolean onHasView() { + return true; + } + + Activity getActivity() { + return mActivity; + } + + Context getContext() { + return mContext; + } + + Handler getHandler() { + return mHandler; + } + + FragmentManagerImpl getFragmentManagerImpl() { + return mFragmentManager; + } + + LoaderManagerImpl getLoaderManagerImpl() { + if (mLoaderManager != null) { + return mLoaderManager; + } + mCheckedForLoaderManager = true; + mLoaderManager = getLoaderManager("(root)", mLoadersStarted, true /*create*/); + return mLoaderManager; + } + + void inactivateFragment(String who) { + //Log.v(TAG, "invalidateSupportFragment: who=" + who); + if (mAllLoaderManagers != null) { + LoaderManagerImpl lm = (LoaderManagerImpl) mAllLoaderManagers.get(who); + if (lm != null && !lm.mRetaining) { + lm.doDestroy(); + mAllLoaderManagers.remove(who); + } + } + } + + void onFragmentInflate(Fragment fragment, AttributeSet attrs, Bundle savedInstanceState) { + fragment.onInflate(mContext, attrs, savedInstanceState); + } + + void onFragmentAttach(Fragment fragment) { + fragment.onAttach(mContext); + } + + void doLoaderStart() { + if (mLoadersStarted) { + return; + } + mLoadersStarted = true; + + if (mLoaderManager != null) { + mLoaderManager.doStart(); + } else if (!mCheckedForLoaderManager) { + mLoaderManager = getLoaderManager("(root)", mLoadersStarted, false); + } + mCheckedForLoaderManager = true; + } + + void doLoaderStop(boolean retain) { + if (mLoaderManager == null) { + return; + } + + if (!mLoadersStarted) { + return; + } + mLoadersStarted = false; + + if (retain) { + mLoaderManager.doRetain(); + } else { + mLoaderManager.doStop(); + } + } + + void doLoaderRetain() { + if (mLoaderManager == null) { + return; + } + mLoaderManager.doRetain(); + } + + void doLoaderDestroy() { + if (mLoaderManager == null) { + return; + } + mLoaderManager.doDestroy(); + } + + void reportLoaderStart() { + if (mAllLoaderManagers != null) { + final int N = mAllLoaderManagers.size(); + LoaderManagerImpl loaders[] = new LoaderManagerImpl[N]; + for (int i=N-1; i>=0; i--) { + loaders[i] = (LoaderManagerImpl) mAllLoaderManagers.valueAt(i); + } + for (int i=0; i<N; i++) { + LoaderManagerImpl lm = loaders[i]; + lm.finishRetain(); + lm.doReportStart(); + } + } + } + + LoaderManagerImpl getLoaderManager(String who, boolean started, boolean create) { + if (mAllLoaderManagers == null) { + mAllLoaderManagers = new ArrayMap<String, LoaderManager>(); + } + LoaderManagerImpl lm = (LoaderManagerImpl) mAllLoaderManagers.get(who); + if (lm == null) { + if (create) { + lm = new LoaderManagerImpl(who, this, started); + mAllLoaderManagers.put(who, lm); + } + } else { + lm.updateHostController(this); + } + return lm; + } + + ArrayMap<String, LoaderManager> retainLoaderNonConfig() { + boolean retainLoaders = false; + if (mAllLoaderManagers != null) { + // prune out any loader managers that were already stopped and so + // have nothing useful to retain. + final int N = mAllLoaderManagers.size(); + LoaderManagerImpl loaders[] = new LoaderManagerImpl[N]; + for (int i=N-1; i>=0; i--) { + loaders[i] = (LoaderManagerImpl) mAllLoaderManagers.valueAt(i); + } + for (int i=0; i<N; i++) { + LoaderManagerImpl lm = loaders[i]; + if (lm.mRetaining) { + retainLoaders = true; + } else { + lm.doDestroy(); + mAllLoaderManagers.remove(lm.mWho); + } + } + } + + if (retainLoaders) { + return mAllLoaderManagers; + } + return null; + } + + void restoreLoaderNonConfig(ArrayMap<String, LoaderManager> loaderManagers) { + mAllLoaderManagers = loaderManagers; + } + + void dumpLoaders(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) { + writer.print(prefix); writer.print("mLoadersStarted="); + writer.println(mLoadersStarted); + if (mLoaderManager != null) { + writer.print(prefix); writer.print("Loader Manager "); + writer.print(Integer.toHexString(System.identityHashCode(mLoaderManager))); + writer.println(":"); + mLoaderManager.dump(prefix + " ", fd, writer, args); + } + } +} diff --git a/core/java/android/app/FragmentManager.java b/core/java/android/app/FragmentManager.java index 975b20d..62436e9 100644 --- a/core/java/android/app/FragmentManager.java +++ b/core/java/android/app/FragmentManager.java @@ -19,9 +19,7 @@ package android.app; import android.animation.Animator; import android.animation.AnimatorInflater; import android.animation.AnimatorListenerAdapter; -import android.annotation.Nullable; import android.content.Context; -import android.annotation.IdRes; import android.content.res.Configuration; import android.content.res.TypedArray; import android.os.Bundle; @@ -48,6 +46,7 @@ import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.ArrayList; import java.util.Arrays; +import java.util.List; /** * Interface for interacting with {@link Fragment} objects inside of an @@ -393,15 +392,6 @@ final class FragmentManagerState implements Parcelable { } /** - * Callbacks from FragmentManagerImpl to its container. - */ -interface FragmentContainer { - @Nullable - public View findViewById(@IdRes int id); - public boolean hasView(); -} - -/** * Container for fragments associated with an activity. */ final class FragmentManagerImpl extends FragmentManager implements LayoutInflater.Factory2 { @@ -430,7 +420,8 @@ final class FragmentManagerImpl extends FragmentManager implements LayoutInflate ArrayList<OnBackStackChangedListener> mBackStackChangeListeners; int mCurState = Fragment.INITIALIZING; - Activity mActivity; + FragmentHostCallback<?> mHost; + FragmentController mController; FragmentContainer mContainer; Fragment mParent; @@ -455,10 +446,10 @@ final class FragmentManagerImpl extends FragmentManager implements LayoutInflate Log.e(TAG, ex.getMessage()); LogWriter logw = new LogWriter(Log.ERROR, TAG); PrintWriter pw = new FastPrintWriter(logw, false, 1024); - if (mActivity != null) { + if (mHost != null) { Log.e(TAG, "Activity state:"); try { - mActivity.dump(" ", null, pw, new String[] { }); + mHost.onDump(" ", null, pw, new String[] { }); } catch (Exception e) { pw.flush(); Log.e(TAG, "Failed dumping state", e); @@ -490,7 +481,7 @@ final class FragmentManagerImpl extends FragmentManager implements LayoutInflate public void popBackStack() { enqueueAction(new Runnable() { @Override public void run() { - popBackStackState(mActivity.mHandler, null, -1, 0); + popBackStackState(mHost.getHandler(), null, -1, 0); } }, false); } @@ -499,14 +490,14 @@ final class FragmentManagerImpl extends FragmentManager implements LayoutInflate public boolean popBackStackImmediate() { checkStateLoss(); executePendingTransactions(); - return popBackStackState(mActivity.mHandler, null, -1, 0); + return popBackStackState(mHost.getHandler(), null, -1, 0); } @Override public void popBackStack(final String name, final int flags) { enqueueAction(new Runnable() { @Override public void run() { - popBackStackState(mActivity.mHandler, name, -1, flags); + popBackStackState(mHost.getHandler(), name, -1, flags); } }, false); } @@ -515,7 +506,7 @@ final class FragmentManagerImpl extends FragmentManager implements LayoutInflate public boolean popBackStackImmediate(String name, int flags) { checkStateLoss(); executePendingTransactions(); - return popBackStackState(mActivity.mHandler, name, -1, flags); + return popBackStackState(mHost.getHandler(), name, -1, flags); } @Override @@ -525,7 +516,7 @@ final class FragmentManagerImpl extends FragmentManager implements LayoutInflate } enqueueAction(new Runnable() { @Override public void run() { - popBackStackState(mActivity.mHandler, null, id, flags); + popBackStackState(mHost.getHandler(), null, id, flags); } }, false); } @@ -537,7 +528,7 @@ final class FragmentManagerImpl extends FragmentManager implements LayoutInflate if (id < 0) { throw new IllegalArgumentException("Bad id: " + id); } - return popBackStackState(mActivity.mHandler, null, id, flags); + return popBackStackState(mHost.getHandler(), null, id, flags); } @Override @@ -619,7 +610,7 @@ final class FragmentManagerImpl extends FragmentManager implements LayoutInflate if (mParent != null) { DebugUtils.buildShortClassTag(mParent, sb); } else { - DebugUtils.buildShortClassTag(mActivity, sb); + DebugUtils.buildShortClassTag(mHost, sb); } sb.append("}}"); return sb.toString(); @@ -716,7 +707,7 @@ final class FragmentManagerImpl extends FragmentManager implements LayoutInflate } writer.print(prefix); writer.println("FragmentManager misc state:"); - writer.print(prefix); writer.print(" mActivity="); writer.println(mActivity); + writer.print(prefix); writer.print(" mHost="); writer.println(mHost); writer.print(prefix); writer.print(" mContainer="); writer.println(mContainer); if (mParent != null) { writer.print(prefix); writer.print(" mParent="); writer.println(mParent); @@ -747,7 +738,7 @@ final class FragmentManagerImpl extends FragmentManager implements LayoutInflate } if (fragment.mNextAnim != 0) { - Animator anim = AnimatorInflater.loadAnimator(mActivity, fragment.mNextAnim); + Animator anim = AnimatorInflater.loadAnimator(mHost.getContext(), fragment.mNextAnim); if (anim != null) { return anim; } @@ -762,14 +753,14 @@ final class FragmentManagerImpl extends FragmentManager implements LayoutInflate return null; } - if (transitionStyle == 0 && mActivity.getWindow() != null) { - transitionStyle = mActivity.getWindow().getAttributes().windowAnimations; + if (transitionStyle == 0 && mHost.onHasWindowAnimations()) { + transitionStyle = mHost.onGetWindowAnimations(); } if (transitionStyle == 0) { return null; } - TypedArray attrs = mActivity.obtainStyledAttributes(transitionStyle, + TypedArray attrs = mHost.getContext().obtainStyledAttributes(transitionStyle, com.android.internal.R.styleable.FragmentAnimation); int anim = attrs.getResourceId(styleIndex, 0); attrs.recycle(); @@ -778,7 +769,7 @@ final class FragmentManagerImpl extends FragmentManager implements LayoutInflate return null; } - return AnimatorInflater.loadAnimator(mActivity, anim); + return AnimatorInflater.loadAnimator(mHost.getContext(), anim); } public void performPendingDeferredStart(Fragment f) { @@ -848,18 +839,18 @@ final class FragmentManagerImpl extends FragmentManager implements LayoutInflate } } } - f.mActivity = mActivity; + f.mHost = mHost; f.mParentFragment = mParent; f.mFragmentManager = mParent != null - ? mParent.mChildFragmentManager : mActivity.mFragments; + ? mParent.mChildFragmentManager : mHost.getFragmentManagerImpl(); f.mCalled = false; - f.onAttach(mActivity); + mHost.onFragmentAttach(f); if (!f.mCalled) { throw new SuperNotCalledException("Fragment " + f + " did not call through to super.onAttach()"); } if (f.mParentFragment == null) { - mActivity.onAttachFragment(f); + mHost.onFragmentAttach(f); } if (!f.mRetaining) { @@ -884,7 +875,7 @@ final class FragmentManagerImpl extends FragmentManager implements LayoutInflate if (!f.mFromLayout) { ViewGroup container = null; if (f.mContainerId != 0) { - container = (ViewGroup)mContainer.findViewById(f.mContainerId); + container = (ViewGroup)mContainer.onFindViewById(f.mContainerId); if (container == null && !f.mRestored) { throwException(new IllegalArgumentException( "No view found for id 0x" @@ -954,7 +945,7 @@ final class FragmentManagerImpl extends FragmentManager implements LayoutInflate if (f.mView != null) { // Need to save the current view state if not // done already. - if (!mActivity.isFinishing() && f.mSavedViewState == null) { + if (!mHost.onShouldSaveFragmentState(f) && f.mSavedViewState == null) { saveFragmentViewState(f); } } @@ -1030,7 +1021,7 @@ final class FragmentManagerImpl extends FragmentManager implements LayoutInflate if (!f.mRetaining) { makeInactive(f); } else { - f.mActivity = null; + f.mHost = null; f.mParentFragment = null; f.mFragmentManager = null; f.mChildFragmentManager = null; @@ -1053,7 +1044,7 @@ final class FragmentManagerImpl extends FragmentManager implements LayoutInflate } void moveToState(int newState, int transit, int transitStyle, boolean always) { - if (mActivity == null && newState != Fragment.INITIALIZING) { + if (mHost == null && newState != Fragment.INITIALIZING) { throw new IllegalStateException("No activity"); } @@ -1078,8 +1069,8 @@ final class FragmentManagerImpl extends FragmentManager implements LayoutInflate startPendingDeferredFragments(); } - if (mNeedMenuInvalidate && mActivity != null && mCurState == Fragment.RESUMED) { - mActivity.invalidateOptionsMenu(); + if (mNeedMenuInvalidate && mHost != null && mCurState == Fragment.RESUMED) { + mHost.onInvalidateOptionsMenu(); mNeedMenuInvalidate = false; } } @@ -1126,7 +1117,7 @@ final class FragmentManagerImpl extends FragmentManager implements LayoutInflate mAvailIndices = new ArrayList<Integer>(); } mAvailIndices.add(f.mIndex); - mActivity.invalidateFragment(f.mWho); + mHost.inactivateFragment(f.mWho); f.initState(); } @@ -1349,7 +1340,7 @@ final class FragmentManagerImpl extends FragmentManager implements LayoutInflate checkStateLoss(); } synchronized (this) { - if (mDestroyed || mActivity == null) { + if (mDestroyed || mHost == null) { throw new IllegalStateException("Activity has been destroyed"); } if (mPendingActions == null) { @@ -1357,8 +1348,8 @@ final class FragmentManagerImpl extends FragmentManager implements LayoutInflate } mPendingActions.add(action); if (mPendingActions.size() == 1) { - mActivity.mHandler.removeCallbacks(mExecCommit); - mActivity.mHandler.post(mExecCommit); + mHost.getHandler().removeCallbacks(mExecCommit); + mHost.getHandler().post(mExecCommit); } } } @@ -1427,7 +1418,7 @@ final class FragmentManagerImpl extends FragmentManager implements LayoutInflate throw new IllegalStateException("Recursive entry to executePendingTransactions"); } - if (Looper.myLooper() != mActivity.mHandler.getLooper()) { + if (Looper.myLooper() != mHost.getHandler().getLooper()) { throw new IllegalStateException("Must be called from main thread of process"); } @@ -1447,7 +1438,7 @@ final class FragmentManagerImpl extends FragmentManager implements LayoutInflate } mPendingActions.toArray(mTmpActions); mPendingActions.clear(); - mActivity.mHandler.removeCallbacks(mExecCommit); + mHost.getHandler().removeCallbacks(mExecCommit); } mExecutingActions = true; @@ -1737,7 +1728,7 @@ final class FragmentManagerImpl extends FragmentManager implements LayoutInflate return fms; } - void restoreAllState(Parcelable state, ArrayList<Fragment> nonConfig) { + void restoreAllState(Parcelable state, List<Fragment> nonConfig) { // If there is no saved state at all, then there can not be // any nonConfig fragments either, so that is that. if (state == null) return; @@ -1758,7 +1749,7 @@ final class FragmentManagerImpl extends FragmentManager implements LayoutInflate f.mAdded = false; f.mTarget = null; if (fs.mSavedFragmentState != null) { - fs.mSavedFragmentState.setClassLoader(mActivity.getClassLoader()); + fs.mSavedFragmentState.setClassLoader(mHost.getContext().getClassLoader()); f.mSavedViewState = fs.mSavedFragmentState.getSparseParcelableArray( FragmentManagerImpl.VIEW_STATE_TAG); f.mSavedFragmentState = fs.mSavedFragmentState; @@ -1775,7 +1766,7 @@ final class FragmentManagerImpl extends FragmentManager implements LayoutInflate for (int i=0; i<fms.mActive.length; i++) { FragmentState fs = fms.mActive[i]; if (fs != null) { - Fragment f = fs.instantiate(mActivity, mParent); + Fragment f = fs.instantiate(mHost, mParent); if (DEBUG) Log.v(TAG, "restoreAllState: active #" + i + ": " + f); mActive.add(f); // Now that the fragment is instantiated (or came from being @@ -1851,9 +1842,10 @@ final class FragmentManagerImpl extends FragmentManager implements LayoutInflate } } - public void attachActivity(Activity activity, FragmentContainer container, Fragment parent) { - if (mActivity != null) throw new IllegalStateException("Already attached"); - mActivity = activity; + public void attachController(FragmentHostCallback<?> host, FragmentContainer container, + Fragment parent) { + if (mHost != null) throw new IllegalStateException("Already attached"); + mHost = host; mContainer = container; mParent = parent; } @@ -1898,7 +1890,7 @@ final class FragmentManagerImpl extends FragmentManager implements LayoutInflate mDestroyed = true; execPendingActions(); moveToState(Fragment.INITIALIZING, false); - mActivity = null; + mHost = null; mContainer = null; mParent = null; } @@ -2024,8 +2016,8 @@ final class FragmentManagerImpl extends FragmentManager implements LayoutInflate @Override public void invalidateOptionsMenu() { - if (mActivity != null && mCurState == Fragment.RESUMED) { - mActivity.invalidateOptionsMenu(); + if (mHost != null && mCurState == Fragment.RESUMED) { + mHost.onInvalidateOptionsMenu(); } else { mNeedMenuInvalidate = true; } @@ -2115,7 +2107,7 @@ final class FragmentManagerImpl extends FragmentManager implements LayoutInflate fragment.mTag = tag; fragment.mInLayout = true; fragment.mFragmentManager = this; - fragment.onInflate(mActivity, attrs, fragment.mSavedFragmentState); + mHost.onFragmentInflate(fragment, attrs, fragment.mSavedFragmentState); addFragment(fragment, true); } else if (fragment.mInLayout) { // A fragment already exists and it is not one we restored from @@ -2132,7 +2124,7 @@ final class FragmentManagerImpl extends FragmentManager implements LayoutInflate // from last saved state), then give it the attributes to // initialize itself. if (!fragment.mRetaining) { - fragment.onInflate(mActivity, attrs, fragment.mSavedFragmentState); + mHost.onFragmentInflate(fragment, attrs, fragment.mSavedFragmentState); } } diff --git a/core/java/android/app/LoaderManager.java b/core/java/android/app/LoaderManager.java index b13b24a..f0e35c9 100644 --- a/core/java/android/app/LoaderManager.java +++ b/core/java/android/app/LoaderManager.java @@ -214,12 +214,12 @@ class LoaderManagerImpl extends LoaderManager { final String mWho; - Activity mActivity; boolean mStarted; boolean mRetaining; boolean mRetainingStarted; boolean mCreatingLoader; + private FragmentHostCallback mHost; final class LoaderInfo implements Loader.OnLoadCompleteListener<Object>, Loader.OnLoadCanceledListener<Object> { @@ -356,15 +356,15 @@ class LoaderManagerImpl extends LoaderManager { if (mCallbacks != null && mLoader != null && mHaveData && needReset) { if (DEBUG) Log.v(TAG, " Reseting: " + this); String lastBecause = null; - if (mActivity != null) { - lastBecause = mActivity.mFragments.mNoTransactionsBecause; - mActivity.mFragments.mNoTransactionsBecause = "onLoaderReset"; + if (mHost != null) { + lastBecause = mHost.mFragmentManager.mNoTransactionsBecause; + mHost.mFragmentManager.mNoTransactionsBecause = "onLoaderReset"; } try { mCallbacks.onLoaderReset(mLoader); } finally { - if (mActivity != null) { - mActivity.mFragments.mNoTransactionsBecause = lastBecause; + if (mHost != null) { + mHost.mFragmentManager.mNoTransactionsBecause = lastBecause; } } } @@ -465,25 +465,25 @@ class LoaderManagerImpl extends LoaderManager { mInactiveLoaders.remove(mId); } - if (mActivity != null && !hasRunningLoaders()) { - mActivity.mFragments.startPendingDeferredFragments(); + if (mHost != null && !hasRunningLoaders()) { + mHost.mFragmentManager.startPendingDeferredFragments(); } } void callOnLoadFinished(Loader<Object> loader, Object data) { if (mCallbacks != null) { String lastBecause = null; - if (mActivity != null) { - lastBecause = mActivity.mFragments.mNoTransactionsBecause; - mActivity.mFragments.mNoTransactionsBecause = "onLoadFinished"; + if (mHost != null) { + lastBecause = mHost.mFragmentManager.mNoTransactionsBecause; + mHost.mFragmentManager.mNoTransactionsBecause = "onLoadFinished"; } try { if (DEBUG) Log.v(TAG, " onLoadFinished in " + loader + ": " + loader.dataToString(data)); mCallbacks.onLoadFinished(loader, data); } finally { - if (mActivity != null) { - mActivity.mFragments.mNoTransactionsBecause = lastBecause; + if (mHost != null) { + mHost.mFragmentManager.mNoTransactionsBecause = lastBecause; } } mDeliveredData = true; @@ -530,14 +530,14 @@ class LoaderManagerImpl extends LoaderManager { } } - LoaderManagerImpl(String who, Activity activity, boolean started) { + LoaderManagerImpl(String who, FragmentHostCallback host, boolean started) { mWho = who; - mActivity = activity; + mHost = host; mStarted = started; } - void updateActivity(Activity activity) { - mActivity = activity; + void updateHostController(FragmentHostCallback host) { + mHost = host; } private LoaderInfo createLoader(int id, Bundle args, @@ -730,8 +730,8 @@ class LoaderManagerImpl extends LoaderManager { mInactiveLoaders.removeAt(idx); info.destroy(); } - if (mActivity != null && !hasRunningLoaders()) { - mActivity.mFragments.startPendingDeferredFragments(); + if (mHost != null && !hasRunningLoaders()) { + mHost.mFragmentManager.startPendingDeferredFragments(); } } @@ -849,7 +849,7 @@ class LoaderManagerImpl extends LoaderManager { sb.append("LoaderManager{"); sb.append(Integer.toHexString(System.identityHashCode(this))); sb.append(" in "); - DebugUtils.buildShortClassTag(mActivity, sb); + DebugUtils.buildShortClassTag(mHost, sb); sb.append("}}"); return sb.toString(); } diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index cf9813f..9f71ea5 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -2926,7 +2926,7 @@ public class DevicePolicyManager { * the user has already been set up. */ @SystemApi - public boolean setActiveProfileOwner(ComponentName admin, String ownerName) + public boolean setActiveProfileOwner(ComponentName admin, @Deprecated String ownerName) throws IllegalArgumentException { if (mService != null) { try { @@ -2992,8 +2992,8 @@ public class DevicePolicyManager { * @throws IllegalArgumentException if admin is null, the package isn't installed, or the * preconditions mentioned are not met. */ - public boolean setProfileOwner(ComponentName admin, String ownerName, int userHandle) - throws IllegalArgumentException { + public boolean setProfileOwner(ComponentName admin, @Deprecated String ownerName, + int userHandle) throws IllegalArgumentException { if (admin == null) { throw new NullPointerException("admin cannot be null"); } diff --git a/core/java/android/appwidget/AppWidgetManager.java b/core/java/android/appwidget/AppWidgetManager.java index 00248cc..1205708 100644 --- a/core/java/android/appwidget/AppWidgetManager.java +++ b/core/java/android/appwidget/AppWidgetManager.java @@ -1053,6 +1053,20 @@ public class AppWidgetManager { } } + /** + * @hide + */ + public boolean isBoundWidgetPackage(String packageName, int userId) { + if (mService == null) { + return false; + } + try { + return mService.isBoundWidgetPackage(packageName, userId); + } catch (RemoteException re) { + throw new RuntimeException("system server dead?", re); + } + } + private boolean bindAppWidgetIdIfAllowed(int appWidgetId, int profileId, ComponentName provider, Bundle options) { if (mService == null) { diff --git a/core/java/android/bluetooth/BluetoothAdapter.java b/core/java/android/bluetooth/BluetoothAdapter.java index 3044a94..9b4dcc6 100644 --- a/core/java/android/bluetooth/BluetoothAdapter.java +++ b/core/java/android/bluetooth/BluetoothAdapter.java @@ -34,6 +34,7 @@ import android.os.RemoteException; import android.os.ServiceManager; import android.app.ActivityThread; import android.os.SystemProperties; +import android.provider.Settings; import android.os.Binder; import android.util.Log; import android.util.Pair; @@ -389,7 +390,7 @@ public final class BluetoothAdapter { * @hide */ public static final String ACTION_BLE_STATE_CHANGED = - "anrdoid.bluetooth.adapter.action.BLE_STATE_CHANGED"; + "android.bluetooth.adapter.action.BLE_STATE_CHANGED"; /** * Broadcast Action: The notifys Bluetooth ACL connected event. This will be @@ -632,24 +633,6 @@ public final class BluetoothAdapter { } /** - * Returns true if LE only mode is enabled, that is apps - * have authorization to turn only BT ON and the calling - * app has privilage to do so - */ - private boolean isLEAlwaysOnEnabled() { - boolean ret = false; - if (SystemProperties.getBoolean("ro.bluetooth.blealwayson", true) == true) { - Log.v(TAG, "LE always on mode is enabled"); - // TODO: System API authorization check - ret = true; - } else { - Log.v(TAG, "LE always on mode is disabled"); - ret = false; - } - return ret; - } - - /** * Turns off Bluetooth LE which was earlier turned on by calling EnableBLE(). * * <p> If the internal Adapter state is STATE_BLE_ON, this would trigger the transition @@ -676,7 +659,7 @@ public final class BluetoothAdapter { * @hide */ public boolean disableBLE() { - if (isLEAlwaysOnEnabled() != true) return false; + if (!isBleScanAlwaysAvailable()) return false; int state = getLeState(); if (state == BluetoothAdapter.STATE_ON) { @@ -738,7 +721,7 @@ public final class BluetoothAdapter { * @hide */ public boolean enableBLE() { - if (isLEAlwaysOnEnabled() != true) return false; + if (!isBleScanAlwaysAvailable()) return false; if (isLeEnabled() == true) { if (DBG) Log.d(TAG, "enableBLE(): BT is already enabled..!"); @@ -1243,8 +1226,12 @@ public final class BluetoothAdapter { */ @SystemApi public boolean isBleScanAlwaysAvailable() { - // TODO: implement after Settings UI change. - return false; + try { + return mManagerService.isBleScanAlwaysAvailable(); + } catch (RemoteException e) { + Log.e(TAG, "remote expection when calling isBleScanAlwaysAvailable", e); + return false; + } } /** diff --git a/core/java/android/bluetooth/IBluetoothManager.aidl b/core/java/android/bluetooth/IBluetoothManager.aidl index 8d1ce99..0b81ee8 100644 --- a/core/java/android/bluetooth/IBluetoothManager.aidl +++ b/core/java/android/bluetooth/IBluetoothManager.aidl @@ -44,6 +44,8 @@ interface IBluetoothManager String getAddress(); String getName(); + + boolean isBleScanAlwaysAvailable(); int updateBleAppCount(IBinder b, boolean enable); boolean isBleAppPresent(); } diff --git a/core/java/android/bluetooth/le/ScanSettings.java b/core/java/android/bluetooth/le/ScanSettings.java index 123514e..dad486d 100644 --- a/core/java/android/bluetooth/le/ScanSettings.java +++ b/core/java/android/bluetooth/le/ScanSettings.java @@ -187,7 +187,7 @@ public final class ScanSettings implements Parcelable { mScanResultType = scanResultType; mReportDelayMillis = reportDelayMillis; mNumOfMatchesPerFilter = numOfMatchesPerFilter; - mMatchMode = numOfMatchesPerFilter; + mMatchMode = matchMode; } private ScanSettings(Parcel in) { diff --git a/core/java/android/content/pm/IntentFilterVerificationInfo.java b/core/java/android/content/pm/IntentFilterVerificationInfo.java index e50b0ff..96000dd 100644 --- a/core/java/android/content/pm/IntentFilterVerificationInfo.java +++ b/core/java/android/content/pm/IntentFilterVerificationInfo.java @@ -48,19 +48,18 @@ public final class IntentFilterVerificationInfo implements Parcelable { private static final String ATTR_PACKAGE_NAME = "packageName"; private static final String ATTR_STATUS = "status"; - private ArrayList<String> mDomains; + private ArraySet<String> mDomains = new ArraySet<>(); private String mPackageName; private int mMainStatus; public IntentFilterVerificationInfo() { mPackageName = null; - mDomains = new ArrayList<>(); mMainStatus = INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED; } public IntentFilterVerificationInfo(String packageName, ArrayList<String> domains) { mPackageName = packageName; - mDomains = domains; + mDomains.addAll(domains); mMainStatus = INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED; } @@ -73,14 +72,6 @@ public final class IntentFilterVerificationInfo implements Parcelable { readFromParcel(source); } - public ArrayList<String> getDomains() { - return mDomains; - } - - public ArraySet<String> getDomainsSet() { - return new ArraySet<>(mDomains); - } - public String getPackageName() { return mPackageName; } @@ -98,6 +89,14 @@ public final class IntentFilterVerificationInfo implements Parcelable { } } + public ArraySet<String> getDomains() { + return mDomains; + } + + public void setDomains(ArrayList<String> list) { + mDomains = new ArraySet<>(list); + } + public String getDomainsString() { StringBuilder sb = new StringBuilder(); for (String str : mDomains) { @@ -145,7 +144,6 @@ public final class IntentFilterVerificationInfo implements Parcelable { } mMainStatus = status; - mDomains = new ArrayList<>(); int outerDepth = parser.getDepth(); int type; while ((type=parser.next()) != XmlPullParser.END_DOCUMENT @@ -201,15 +199,16 @@ public final class IntentFilterVerificationInfo implements Parcelable { private void readFromParcel(Parcel source) { mPackageName = source.readString(); mMainStatus = source.readInt(); - mDomains = new ArrayList<>(); - source.readStringList(mDomains); + ArrayList<String> list = new ArrayList<>(); + source.readStringList(list); + mDomains.addAll(list); } @Override public void writeToParcel(Parcel dest, int flags) { dest.writeString(mPackageName); dest.writeInt(mMainStatus); - dest.writeStringList(mDomains); + dest.writeStringList(new ArrayList<>(mDomains)); } public static final Creator<IntentFilterVerificationInfo> CREATOR = @@ -221,5 +220,4 @@ public final class IntentFilterVerificationInfo implements Parcelable { return new IntentFilterVerificationInfo[size]; } }; - } diff --git a/core/java/android/hardware/camera2/CameraCaptureSession.java b/core/java/android/hardware/camera2/CameraCaptureSession.java index 0cf8df1..aeddf03 100644 --- a/core/java/android/hardware/camera2/CameraCaptureSession.java +++ b/core/java/android/hardware/camera2/CameraCaptureSession.java @@ -114,6 +114,11 @@ public abstract class CameraCaptureSession implements AutoCloseable { * the Surface provided to prepare must not be used as a target of a CaptureRequest submitted * to this session.</p> * + * <p>{@link android.hardware.camera2.CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY LEGACY} + * devices cannot pre-allocate output buffers; for those devices, + * {@link StateCallback#onSurfacePrepared} will be immediately called, and no preallocation is + * done.</p> + * * @param surface the output Surface for which buffers should be pre-allocated. Must be one of * the output Surfaces used to create this session. * diff --git a/core/java/android/hardware/camera2/CameraDevice.java b/core/java/android/hardware/camera2/CameraDevice.java index f6791a4..e9564b3 100644 --- a/core/java/android/hardware/camera2/CameraDevice.java +++ b/core/java/android/hardware/camera2/CameraDevice.java @@ -580,7 +580,8 @@ public abstract class CameraDevice implements AutoCloseable { * indicating that the camera device is in use already. * * <p> - * This error can be produced when opening the camera fails. + * This error can be produced when opening the camera fails due to the camera + * being used by a higher-priority camera API client. * </p> * * @see #onError @@ -678,7 +679,7 @@ public abstract class CameraDevice implements AutoCloseable { * {@link CameraAccessException}. The disconnection could be due to a * change in security policy or permissions; the physical disconnection * of a removable camera device; or the camera being needed for a - * higher-priority use case.</p> + * higher-priority camera API client.</p> * * <p>There may still be capture callbacks that are invoked * after this method is called, or new image buffers that are delivered @@ -688,8 +689,9 @@ public abstract class CameraDevice implements AutoCloseable { * about the disconnection.</p> * * <p>You should clean up the camera with {@link CameraDevice#close} after - * this happens, as it is not recoverable until opening the camera again - * after it becomes {@link CameraManager.AvailabilityCallback#onCameraAvailable available}. + * this happens, as it is not recoverable until the camera can be opened + * again. For most use cases, this will be when the camera again becomes + * {@link CameraManager.AvailabilityCallback#onCameraAvailable available}. * </p> * * @param camera the device that has been disconnected diff --git a/core/java/android/hardware/camera2/CameraManager.java b/core/java/android/hardware/camera2/CameraManager.java index 1a00a05..e2d2f61 100644 --- a/core/java/android/hardware/camera2/CameraManager.java +++ b/core/java/android/hardware/camera2/CameraManager.java @@ -77,8 +77,8 @@ public final class CameraManager { } /** - * Return the list of currently connected camera devices by - * identifier. + * Return the list of currently connected camera devices by identifier, including + * cameras that may be in use by other camera API clients. * * <p>Non-removable cameras use integers starting at 0 for their * identifiers, while removable cameras have a unique identifier for each @@ -103,6 +103,11 @@ public final class CameraManager { * <p>The first time a callback is registered, it is immediately called * with the availability status of all currently known camera devices.</p> * + * <p>{@link AvailabilityCallback#onCameraUnavailable(String)} will be called whenever a camera + * device is opened by any camera API client. As of API level 23, other camera API clients may + * still be able to open such a camera device, evicting the existing client if they have higher + * priority than the existing client of a camera device. See open() for more details.</p> + * * <p>Since this callback will be registered with the camera service, remember to unregister it * once it is no longer needed; otherwise the callback will continue to receive events * indefinitely and it may prevent other resources from being released. Specifically, the @@ -259,14 +264,14 @@ public final class CameraManager { } /** - * Helper for openning a connection to a camera with the given ID. + * Helper for opening a connection to a camera with the given ID. * * @param cameraId The unique identifier of the camera device to open * @param callback The callback for the camera. Must not be null. * @param handler The handler to invoke the callback on. Must not be null. * * @throws CameraAccessException if the camera is disabled by device policy, - * or too many camera devices are already open, or the cameraId does not match + * too many camera devices are already open, or the cameraId does not match * any currently available camera device. * * @throws SecurityException if the application does not have permission to @@ -330,7 +335,8 @@ public final class CameraManager { deviceImpl.setRemoteFailure(e); if (e.getReason() == CameraAccessException.CAMERA_DISABLED || - e.getReason() == CameraAccessException.CAMERA_DISCONNECTED) { + e.getReason() == CameraAccessException.CAMERA_DISCONNECTED || + e.getReason() == CameraAccessException.CAMERA_IN_USE) { // Per API docs, these failures call onError and throw throw e.asChecked(); } @@ -369,7 +375,19 @@ public final class CameraManager { * <p>Use {@link #getCameraIdList} to get the list of available camera * devices. Note that even if an id is listed, open may fail if the device * is disconnected between the calls to {@link #getCameraIdList} and - * {@link #openCamera}.</p> + * {@link #openCamera}, or if a higher-priority camera API client begins using the + * camera device.</p> + * + * <p>As of API level 23, devices for which the + * {@link AvailabilityCallback#onCameraUnavailable(String)} callback has been called due to the + * device being in use by a lower-priority, background camera API client can still potentially + * be opened by calling this method when the calling camera API client has a higher priority + * than the current camera API client using this device. In general, if the top, foreground + * activity is running within your application process, your process will be given the highest + * priority when accessing the camera, and this method will succeed even if the camera device is + * in use by another camera API client. Any lower-priority application that loses control of the + * camera in this way will receive an + * {@link android.hardware.camera2.CameraDevice.StateCallback#onDisconnected} callback.</p> * * <p>Once the camera is successfully opened, {@link CameraDevice.StateCallback#onOpened} will * be invoked with the newly opened {@link CameraDevice}. The camera device can then be set up @@ -401,7 +419,7 @@ public final class CameraManager { * {@code null} to use the current thread's {@link android.os.Looper looper}. * * @throws CameraAccessException if the camera is disabled by device policy, - * or the camera has become or was disconnected. + * has been disconnected, or is being used by a higher-priority camera API client. * * @throws IllegalArgumentException if cameraId or the callback was null, * or the cameraId does not match any currently or previously available @@ -477,8 +495,7 @@ public final class CameraManager { } /** - * A callback for camera devices becoming available or - * unavailable to open. + * A callback for camera devices becoming available or unavailable to open. * * <p>Cameras become available when they are no longer in use, or when a new * removable camera is connected. They become unavailable when some diff --git a/core/java/android/hardware/camera2/legacy/CameraDeviceUserShim.java b/core/java/android/hardware/camera2/legacy/CameraDeviceUserShim.java index abe26ea..edad00f 100644 --- a/core/java/android/hardware/camera2/legacy/CameraDeviceUserShim.java +++ b/core/java/android/hardware/camera2/legacy/CameraDeviceUserShim.java @@ -202,6 +202,7 @@ public class CameraDeviceUserShim implements ICameraDeviceUser { private static final int CAMERA_IDLE = 1; private static final int CAPTURE_STARTED = 2; private static final int RESULT_RECEIVED = 3; + private static final int PREPARED = 4; private final HandlerThread mHandlerThread; private Handler mHandler; @@ -253,7 +254,9 @@ public class CameraDeviceUserShim implements ICameraDeviceUser { @Override public void onPrepared(int streamId) { - // TODO + Message msg = getHandler().obtainMessage(PREPARED, + /*arg1*/ streamId, /*arg2*/ 0); + getHandler().sendMessage(msg); } @Override @@ -301,6 +304,11 @@ public class CameraDeviceUserShim implements ICameraDeviceUser { mCallbacks.onResultReceived(result, resultExtras); break; } + case PREPARED: { + int streamId = msg.arg1; + mCallbacks.onPrepared(streamId); + break; + } default: throw new IllegalArgumentException( "Unknown callback message " + msg.what); @@ -631,7 +639,9 @@ public class CameraDeviceUserShim implements ICameraDeviceUser { return CameraBinderDecorator.ENODEV; } - // TODO: Implement and fire callback + // LEGACY doesn't support actual prepare, just signal success right away + mCameraCallbacks.onPrepared(streamId); + return CameraBinderDecorator.NO_ERROR; } diff --git a/core/java/android/hardware/fingerprint/FingerprintManager.java b/core/java/android/hardware/fingerprint/FingerprintManager.java index 9f344ad..cf96145 100644 --- a/core/java/android/hardware/fingerprint/FingerprintManager.java +++ b/core/java/android/hardware/fingerprint/FingerprintManager.java @@ -434,7 +434,8 @@ public class FingerprintManager { mAuthenticationCallback = callback; mCryptoObject = crypto; long sessionId = crypto != null ? crypto.getOpId() : 0; - mService.authenticate(mToken, sessionId, userId, mServiceReceiver, flags); + mService.authenticate(mToken, sessionId, userId, mServiceReceiver, flags, + mContext.getOpPackageName()); } catch (RemoteException e) { Log.w(TAG, "Remote exception while authenticating: ", e); if (callback != null) { @@ -555,7 +556,7 @@ public class FingerprintManager { */ public List<Fingerprint> getEnrolledFingerprints(int userId) { if (mService != null) try { - return mService.getEnrolledFingerprints(userId); + return mService.getEnrolledFingerprints(userId, mContext.getOpPackageName()); } catch (RemoteException e) { Log.v(TAG, "Remote exception in getEnrolledFingerprints: ", e); } @@ -579,7 +580,8 @@ public class FingerprintManager { */ public boolean hasEnrolledFingerprints() { if (mService != null) try { - return mService.hasEnrolledFingerprints(UserHandle.myUserId()); + return mService.hasEnrolledFingerprints(UserHandle.myUserId(), + mContext.getOpPackageName()); } catch (RemoteException e) { Log.v(TAG, "Remote exception in getEnrolledFingerprints: ", e); } @@ -595,7 +597,7 @@ public class FingerprintManager { if (mService != null) { try { long deviceId = 0; /* TODO: plumb hardware id to FPMS */ - return mService.isHardwareDetected(deviceId); + return mService.isHardwareDetected(deviceId, mContext.getOpPackageName()); } catch (RemoteException e) { Log.v(TAG, "Remote exception in isFingerprintHardwareDetected(): ", e); } @@ -614,7 +616,7 @@ public class FingerprintManager { public long getAuthenticatorId() { if (mService != null) { try { - return mService.getAuthenticatorId(); + return mService.getAuthenticatorId(mContext.getOpPackageName()); } catch (RemoteException e) { Log.v(TAG, "Remote exception in getAuthenticatorId(): ", e); } @@ -624,7 +626,13 @@ public class FingerprintManager { return 0; } - private Handler mHandler = new Handler() { + private Handler mHandler; + + private class MyHandler extends Handler { + private MyHandler(Context context) { + super(context.getMainLooper()); + } + public void handleMessage(android.os.Message msg) { switch(msg.what) { case MSG_ENROLL_RESULT: @@ -709,6 +717,7 @@ public class FingerprintManager { if (mService == null) { Slog.v(TAG, "FingerprintManagerService was null"); } + mHandler = new MyHandler(context); } private int getCurrentUserId() { @@ -736,7 +745,7 @@ public class FingerprintManager { private void cancelAuthentication(CryptoObject cryptoObject) { if (mService != null) try { - mService.cancelAuthentication(mToken); + mService.cancelAuthentication(mToken, mContext.getOpPackageName()); } catch (RemoteException e) { if (DEBUG) Log.w(TAG, "Remote exception while canceling enrollment"); } diff --git a/core/java/android/hardware/fingerprint/IFingerprintService.aidl b/core/java/android/hardware/fingerprint/IFingerprintService.aidl index c5ec08c..0484806 100644 --- a/core/java/android/hardware/fingerprint/IFingerprintService.aidl +++ b/core/java/android/hardware/fingerprint/IFingerprintService.aidl @@ -27,10 +27,10 @@ import java.util.List; interface IFingerprintService { // Authenticate the given sessionId with a fingerprint void authenticate(IBinder token, long sessionId, int groupId, - IFingerprintServiceReceiver receiver, int flags); + IFingerprintServiceReceiver receiver, int flags, String opPackageName); // Cancel authentication for the given sessionId - void cancelAuthentication(IBinder token); + void cancelAuthentication(IBinder token, String opPackageName); // Start fingerprint enrollment void enroll(IBinder token, in byte [] cryptoToken, int groupId, IFingerprintServiceReceiver receiver, @@ -46,16 +46,16 @@ interface IFingerprintService { void rename(int fingerId, int groupId, String name); // Get a list of enrolled fingerprints in the given group. - List<Fingerprint> getEnrolledFingerprints(int groupId); + List<Fingerprint> getEnrolledFingerprints(int groupId, String opPackageName); // Determine if HAL is loaded and ready - boolean isHardwareDetected(long deviceId); + boolean isHardwareDetected(long deviceId, String opPackageName); // Get a pre-enrollment authentication token long preEnroll(IBinder token); // Determine if a user has at least one enrolled fingerprint - boolean hasEnrolledFingerprints(int groupId); + boolean hasEnrolledFingerprints(int groupId, String opPackageName); // Gets the number of hardware devices // int getHardwareDeviceCount(); @@ -64,5 +64,5 @@ interface IFingerprintService { // long getHardwareDevice(int i); // Gets the authenticator ID for fingerprint - long getAuthenticatorId(); + long getAuthenticatorId(String opPackageName); } diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java index c531e7e..2d63e3f 100644 --- a/core/java/android/net/ConnectivityManager.java +++ b/core/java/android/net/ConnectivityManager.java @@ -341,7 +341,8 @@ public class ConnectivityManager { * one. This is used by applications needing to talk to the carrier's * Multimedia Messaging Service servers. * - * @deprecated Applications should instead use {@link #requestNetwork} to request a network that + * @deprecated Applications should instead use + * {@link #requestNetwork(NetworkRequest, NetworkCallback)} to request a network that * provides the {@link NetworkCapabilities#NET_CAPABILITY_MMS} capability. */ public static final int TYPE_MOBILE_MMS = 2; @@ -351,7 +352,8 @@ public class ConnectivityManager { * one. This is used by applications needing to talk to the carrier's * Secure User Plane Location servers for help locating the device. * - * @deprecated Applications should instead use {@link #requestNetwork} to request a network that + * @deprecated Applications should instead use + * {@link #requestNetwork(NetworkRequest, NetworkCallback)} to request a network that * provides the {@link NetworkCapabilities#NET_CAPABILITY_SUPL} capability. */ public static final int TYPE_MOBILE_SUPL = 3; @@ -367,7 +369,8 @@ public class ConnectivityManager { * same network interface as {@link #TYPE_MOBILE} but the routing setup * is different. * - * @deprecated Applications should instead use {@link #requestNetwork} to request a network that + * @deprecated Applications should instead use + * {@link #requestNetwork(NetworkRequest, NetworkCallback)} to request a network that * uses the {@link NetworkCapabilities#TRANSPORT_CELLULAR} transport. */ public static final int TYPE_MOBILE_HIPRI = 5; @@ -910,7 +913,8 @@ public class ConnectivityManager { * implementation+feature combination, except that the value {@code -1} * always indicates failure. * - * @deprecated Deprecated in favor of the cleaner {@link #requestNetwork} api. + * @deprecated Deprecated in favor of the cleaner + * {@link #requestNetwork(NetworkRequest, NetworkCallback)} API. * @removed */ public int startUsingNetworkFeature(int networkType, String feature) { @@ -958,7 +962,7 @@ public class ConnectivityManager { * implementation+feature combination, except that the value {@code -1} * always indicates failure. * - * @deprecated Deprecated in favor of the cleaner {@link #requestNetwork} api. + * @deprecated Deprecated in favor of the cleaner {@link unregisterNetworkCallback} API. * @removed */ public int stopUsingNetworkFeature(int networkType, String feature) { @@ -1236,8 +1240,9 @@ public class ConnectivityManager { * @param hostAddress the IP address of the host to which the route is desired * @return {@code true} on success, {@code false} on failure * - * @deprecated Deprecated in favor of the {@link #requestNetwork}, - * {@link #bindProcessToNetwork} and {@link Network#getSocketFactory} api. + * @deprecated Deprecated in favor of the + * {@link #requestNetwork(NetworkRequest, NetworkCallback)}, + * {@link #bindProcessToNetwork} and {@link Network#getSocketFactory} API. * @removed */ public boolean requestRouteToHost(int networkType, int hostAddress) { @@ -1256,7 +1261,7 @@ public class ConnectivityManager { * @return {@code true} on success, {@code false} on failure * @hide * @deprecated Deprecated in favor of the {@link #requestNetwork} and - * {@link #bindProcessToNetwork} api. + * {@link #bindProcessToNetwork} API. * @removed */ public boolean requestRouteToHostAddress(int networkType, InetAddress hostAddress) { @@ -2144,14 +2149,22 @@ public class ConnectivityManager { public static final int CANCELED = 8; /** - * @hide - * Called whenever the framework connects to a network that it may use to - * satisfy this request + * Called when the framework connects to a new network to evaluate whether it satisfies this + * request. If evaluation succeeds, this callback may be followed by an {@link #onAvailable} + * callback. There is no guarantee that this new network will satisfy any requests, or that + * the network will stay connected for longer than the time necessary to evaluate it. + * <p> + * Most applications <b>should not</b> act on this callback, and should instead use + * {@link #onAvailable}. This callback is intended for use by applications that can assist + * the framework in properly evaluating the network — for example, an application that + * can automatically log in to a captive portal without user intervention. + * + * @param network The {@link Network} of the network that is being evaluated. */ public void onPreCheck(Network network) {} /** - * Called when the framework connects and has declared new network ready for use. + * Called when the framework connects and has declared a new network ready for use. * This callback may be called more than once if the {@link Network} that is * satisfying the request changes. * @@ -2251,116 +2264,82 @@ public class ConnectivityManager { @Override public void handleMessage(Message message) { Log.d(TAG, "CM callback handler got msg " + message.what); + NetworkRequest request = (NetworkRequest) getObject(message, NetworkRequest.class); + Network network = (Network) getObject(message, Network.class); switch (message.what) { case CALLBACK_PRECHECK: { - NetworkRequest request = (NetworkRequest)getObject(message, - NetworkRequest.class); - NetworkCallback callbacks = getCallbacks(request); - if (callbacks != null) { - callbacks.onPreCheck((Network)getObject(message, Network.class)); - } else { - Log.e(TAG, "callback not found for PRECHECK message"); + NetworkCallback callback = getCallback(request, "PRECHECK"); + if (callback != null) { + callback.onPreCheck(network); } break; } case CALLBACK_AVAILABLE: { - NetworkRequest request = (NetworkRequest)getObject(message, - NetworkRequest.class); - NetworkCallback callbacks = getCallbacks(request); - if (callbacks != null) { - callbacks.onAvailable((Network)getObject(message, Network.class)); - } else { - Log.e(TAG, "callback not found for AVAILABLE message"); + NetworkCallback callback = getCallback(request, "AVAILABLE"); + if (callback != null) { + callback.onAvailable(network); } break; } case CALLBACK_LOSING: { - NetworkRequest request = (NetworkRequest)getObject(message, - NetworkRequest.class); - NetworkCallback callbacks = getCallbacks(request); - if (callbacks != null) { - callbacks.onLosing((Network)getObject(message, Network.class), - message.arg1); - } else { - Log.e(TAG, "callback not found for LOSING message"); + NetworkCallback callback = getCallback(request, "LOSING"); + if (callback != null) { + callback.onLosing(network, message.arg1); } break; } case CALLBACK_LOST: { - NetworkRequest request = (NetworkRequest)getObject(message, - NetworkRequest.class); - - NetworkCallback callbacks = getCallbacks(request); - if (callbacks != null) { - callbacks.onLost((Network)getObject(message, Network.class)); - } else { - Log.e(TAG, "callback not found for LOST message"); + NetworkCallback callback = getCallback(request, "LOST"); + if (callback != null) { + callback.onLost(network); } break; } case CALLBACK_UNAVAIL: { - NetworkRequest request = (NetworkRequest)getObject(message, - NetworkRequest.class); - NetworkCallback callbacks = null; - synchronized(mCallbackMap) { - callbacks = mCallbackMap.get(request); - } - if (callbacks != null) { - callbacks.onUnavailable(); - } else { - Log.e(TAG, "callback not found for UNAVAIL message"); + NetworkCallback callback = getCallback(request, "UNAVAIL"); + if (callback != null) { + callback.onUnavailable(); } break; } case CALLBACK_CAP_CHANGED: { - NetworkRequest request = (NetworkRequest)getObject(message, - NetworkRequest.class); - NetworkCallback callbacks = getCallbacks(request); - if (callbacks != null) { - Network network = (Network)getObject(message, Network.class); + NetworkCallback callback = getCallback(request, "CAP_CHANGED"); + if (callback != null) { NetworkCapabilities cap = (NetworkCapabilities)getObject(message, NetworkCapabilities.class); - callbacks.onCapabilitiesChanged(network, cap); - } else { - Log.e(TAG, "callback not found for CAP_CHANGED message"); + callback.onCapabilitiesChanged(network, cap); } break; } case CALLBACK_IP_CHANGED: { - NetworkRequest request = (NetworkRequest)getObject(message, - NetworkRequest.class); - NetworkCallback callbacks = getCallbacks(request); - if (callbacks != null) { - Network network = (Network)getObject(message, Network.class); + NetworkCallback callback = getCallback(request, "IP_CHANGED"); + if (callback != null) { LinkProperties lp = (LinkProperties)getObject(message, LinkProperties.class); - callbacks.onLinkPropertiesChanged(network, lp); - } else { - Log.e(TAG, "callback not found for IP_CHANGED message"); + callback.onLinkPropertiesChanged(network, lp); } break; } case CALLBACK_RELEASED: { - NetworkRequest req = (NetworkRequest)getObject(message, NetworkRequest.class); - NetworkCallback callbacks = null; + NetworkCallback callback = null; synchronized(mCallbackMap) { - callbacks = mCallbackMap.remove(req); + callback = mCallbackMap.remove(request); } - if (callbacks != null) { + if (callback != null) { synchronized(mRefCount) { if (mRefCount.decrementAndGet() == 0) { getLooper().quit(); } } } else { - Log.e(TAG, "callback not found for CANCELED message"); + Log.e(TAG, "callback not found for RELEASED message"); } break; } case CALLBACK_EXIT: { - Log.d(TAG, "Listener quiting"); + Log.d(TAG, "Listener quitting"); getLooper().quit(); break; } @@ -2374,10 +2353,16 @@ public class ConnectivityManager { private Object getObject(Message msg, Class c) { return msg.getData().getParcelable(c.getSimpleName()); } - private NetworkCallback getCallbacks(NetworkRequest req) { + + private NetworkCallback getCallback(NetworkRequest req, String name) { + NetworkCallback callback; synchronized(mCallbackMap) { - return mCallbackMap.get(req); + callback = mCallbackMap.get(req); + } + if (callback == null) { + Log.e(TAG, "callback not found for " + name + " message"); } + return callback; } } @@ -2601,10 +2586,10 @@ public class ConnectivityManager { /** * Unregisters callbacks about and possibly releases networks originating from - * {@link #requestNetwork} and {@link #registerNetworkCallback} calls. If the - * given {@code NetworkCallback} had previously been used with {@code #requestNetwork}, - * any networks that had been connected to only to satisfy that request will be - * disconnected. + * {@link #requestNetwork(NetworkRequest, NetworkCallback)} and {@link #registerNetworkCallback} + * calls. If the given {@code NetworkCallback} had previously been used with + * {@code #requestNetwork}, any networks that had been connected to only to satisfy that request + * will be disconnected. * * @param networkCallback The {@link NetworkCallback} used when making the request. */ diff --git a/core/java/android/net/NetworkCapabilities.java b/core/java/android/net/NetworkCapabilities.java index 8c8bfab..ab70485 100644 --- a/core/java/android/net/NetworkCapabilities.java +++ b/core/java/android/net/NetworkCapabilities.java @@ -37,6 +37,11 @@ public final class NetworkCapabilities implements Parcelable { * @hide */ public NetworkCapabilities() { + clearAll(); + mNetworkCapabilities = + (1 << NET_CAPABILITY_NOT_RESTRICTED) | + (1 << NET_CAPABILITY_TRUSTED) | + (1 << NET_CAPABILITY_NOT_VPN); } public NetworkCapabilities(NetworkCapabilities nc) { @@ -50,11 +55,21 @@ public final class NetworkCapabilities implements Parcelable { } /** + * Completely clears the contents of this object, removing even the capabilities that are set + * by default when the object is constructed. + * @hide + */ + public void clearAll() { + mNetworkCapabilities = mTransportTypes = 0; + mLinkUpBandwidthKbps = mLinkDownBandwidthKbps = 0; + mNetworkSpecifier = null; + } + + /** * Represents the network's capabilities. If any are specified they will be satisfied * by any Network that matches all of them. */ - private long mNetworkCapabilities = (1 << NET_CAPABILITY_NOT_RESTRICTED) | - (1 << NET_CAPABILITY_TRUSTED) | (1 << NET_CAPABILITY_NOT_VPN); + private long mNetworkCapabilities; /** * Indicates this is a network that has the ability to reach the diff --git a/core/java/android/os/Environment.java b/core/java/android/os/Environment.java index e3e16eb..8e0584a 100644 --- a/core/java/android/os/Environment.java +++ b/core/java/android/os/Environment.java @@ -20,6 +20,7 @@ import android.app.admin.DevicePolicyManager; import android.content.Context; import android.os.storage.StorageManager; import android.os.storage.StorageVolume; +import android.text.TextUtils; import android.util.Log; import java.io.File; @@ -242,6 +243,15 @@ public class Environment { return DATA_DIRECTORY; } + /** {@hide} */ + public static File getDataAppDirectory(String volumeUuid) { + if (TextUtils.isEmpty(volumeUuid)) { + return new File("/data/app"); + } else { + return new File("/mnt/expand/" + volumeUuid + "/app"); + } + } + /** * Return the primary external storage directory. This directory may not * currently be accessible if it has been mounted by the user on their diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java index 8c1f44f..1273772 100644 --- a/core/java/android/os/Parcel.java +++ b/core/java/android/os/Parcel.java @@ -502,7 +502,25 @@ public final class Parcel { * {@SystemApi} */ public final void writeBlob(byte[] b) { - nativeWriteBlob(mNativePtr, b, 0, (b != null) ? b.length : 0); + writeBlob(b, 0, (b != null) ? b.length : 0); + } + + /** + * Write a blob of data into the parcel at the current {@link #dataPosition}, + * growing {@link #dataCapacity} if needed. + * @param b Bytes to place into the parcel. + * @param offset Index of first byte to be written. + * @param len Number of bytes to write. + * {@hide} + * {@SystemApi} + */ + public final void writeBlob(byte[] b, int offset, int len) { + if (b == null) { + writeInt(-1); + return; + } + Arrays.checkOffsetAndCount(b.length, offset, len); + nativeWriteBlob(mNativePtr, b, offset, len); } /** diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java index 355ec8c..009649f 100644 --- a/core/java/android/os/Process.java +++ b/core/java/android/os/Process.java @@ -634,6 +634,9 @@ public class Process { if ((debugFlags & Zygote.DEBUG_ENABLE_JIT) != 0) { argsForZygote.add("--enable-jit"); } + if ((debugFlags & Zygote.DEBUG_GENERATE_CFI) != 0) { + argsForZygote.add("--generate-cfi"); + } if ((debugFlags & Zygote.DEBUG_ENABLE_ASSERT) != 0) { argsForZygote.add("--enable-assert"); } diff --git a/core/java/android/security/keymaster/KeymasterDefs.java b/core/java/android/security/keymaster/KeymasterDefs.java index d8834fe..0e2b8ba 100644 --- a/core/java/android/security/keymaster/KeymasterDefs.java +++ b/core/java/android/security/keymaster/KeymasterDefs.java @@ -238,6 +238,8 @@ public final class KeymasterDefs { sErrorCodeToString.put(KM_ERROR_UNSUPPORTED_EC_FIELD, "Unsupported EC field"); sErrorCodeToString.put(KM_ERROR_MISSING_NONCE, "Required IV missing"); sErrorCodeToString.put(KM_ERROR_INVALID_NONCE, "Invalid IV"); + sErrorCodeToString.put(KM_ERROR_CALLER_NONCE_PROHIBITED, + "Caller-provided IV not permitted"); sErrorCodeToString.put(KM_ERROR_UNIMPLEMENTED, "Not implemented"); sErrorCodeToString.put(KM_ERROR_UNKNOWN_ERROR, "Unknown error"); } diff --git a/core/java/android/service/voice/VoiceInteractionServiceInfo.java b/core/java/android/service/voice/VoiceInteractionServiceInfo.java index 4bc97c9..997d586 100644 --- a/core/java/android/service/voice/VoiceInteractionServiceInfo.java +++ b/core/java/android/service/voice/VoiceInteractionServiceInfo.java @@ -43,7 +43,7 @@ public class VoiceInteractionServiceInfo { private String mSessionService; private String mRecognitionService; private String mSettingsActivity; - private boolean mSupportsAssistGesture; + private boolean mSupportsAssist; public VoiceInteractionServiceInfo(PackageManager pm, ComponentName comp) throws PackageManager.NameNotFoundException { @@ -95,8 +95,8 @@ public class VoiceInteractionServiceInfo { com.android.internal.R.styleable.VoiceInteractionService_recognitionService); mSettingsActivity = array.getString( com.android.internal.R.styleable.VoiceInteractionService_settingsActivity); - mSupportsAssistGesture = array.getBoolean( - com.android.internal.R.styleable.VoiceInteractionService_supportsAssistGesture, + mSupportsAssist = array.getBoolean( + com.android.internal.R.styleable.VoiceInteractionService_supportsAssist, false); array.recycle(); if (mSessionService == null) { @@ -145,7 +145,7 @@ public class VoiceInteractionServiceInfo { return mSettingsActivity; } - public boolean getSupportsAssistGesture() { - return mSupportsAssistGesture; + public boolean getSupportsAssist() { + return mSupportsAssist; } } diff --git a/core/java/android/view/HapticFeedbackConstants.java b/core/java/android/view/HapticFeedbackConstants.java index cc090ad..6651b83 100644 --- a/core/java/android/view/HapticFeedbackConstants.java +++ b/core/java/android/view/HapticFeedbackConstants.java @@ -52,6 +52,11 @@ public class HapticFeedbackConstants { public static final int CALENDAR_DATE = 5; /** + * The user has touched the screen with a stylus and pressed the stylus button. + */ + public static final int STYLUS_BUTTON_PRESS = 6; + + /** * This is a private constant. Feel free to renumber as desired. * @hide */ diff --git a/core/java/android/view/LayoutInflater.java b/core/java/android/view/LayoutInflater.java index 457d6ad..1503728 100644 --- a/core/java/android/view/LayoutInflater.java +++ b/core/java/android/view/LayoutInflater.java @@ -946,25 +946,21 @@ public abstract class LayoutInflater { attrs, R.styleable.Include); final int id = a.getResourceId(R.styleable.Include_id, View.NO_ID); final int visibility = a.getInt(R.styleable.Include_visibility, -1); - final boolean hasWidth = a.hasValue(R.styleable.Include_layout_width); - final boolean hasHeight = a.hasValue(R.styleable.Include_layout_height); a.recycle(); - // We try to load the layout params set in the <include /> tag. If - // they don't exist, we will rely on the layout params set in the - // included XML file. - // During a layoutparams generation, a runtime exception is thrown - // if either layout_width or layout_height is missing. We catch - // this exception and set localParams accordingly: true means we - // successfully loaded layout params from the <include /> tag, - // false means we need to rely on the included layout params. + // We try to load the layout params set in the <include /> tag. + // If the parent can't generate layout params (ex. missing width + // or height for the framework ViewGroups, though this is not + // necessarily true of all ViewGroups) then we expect it to throw + // a runtime exception. + // We catch this exception and set localParams accordingly: true + // means we successfully loaded layout params from the <include> + // tag, false means we need to rely on the included layout params. ViewGroup.LayoutParams params = null; - if (hasWidth && hasHeight) { - try { - params = group.generateLayoutParams(attrs); - } catch (RuntimeException e) { - // Ignore, just fail over to child attrs. - } + try { + params = group.generateLayoutParams(attrs); + } catch (RuntimeException e) { + // Ignore, just fail over to child attrs. } if (params == null) { params = group.generateLayoutParams(childAttrs); diff --git a/core/java/android/view/MotionEvent.java b/core/java/android/view/MotionEvent.java index 5e45c8f..5df596a 100644 --- a/core/java/android/view/MotionEvent.java +++ b/core/java/android/view/MotionEvent.java @@ -3180,6 +3180,18 @@ public final class MotionEvent extends InputEvent implements Parcelable { return (getButtonState() & button) == button; } + /** + * Checks if a stylus is being used and if the first stylus button is + * pressed. + * + * @return True if the tool is a stylus and if the first stylus button is + * pressed. + * @see #BUTTON_SECONDARY + */ + public final boolean isStylusButtonPressed() { + return (isButtonPressed(BUTTON_SECONDARY) && getToolType(0) == TOOL_TYPE_STYLUS); + } + public static final Parcelable.Creator<MotionEvent> CREATOR = new Parcelable.Creator<MotionEvent>() { public MotionEvent createFromParcel(Parcel in) { diff --git a/core/java/android/view/TextureView.java b/core/java/android/view/TextureView.java index 6db46e9..85b22fb 100644 --- a/core/java/android/view/TextureView.java +++ b/core/java/android/view/TextureView.java @@ -246,6 +246,9 @@ public class TextureView extends View { mSurface = null; mLayer = null; + // Make sure if/when new layer gets re-created, transform matrix will + // be re-applied. + mMatrixChanged = true; mHadSurface = true; } } diff --git a/core/java/android/view/ThreadedRenderer.java b/core/java/android/view/ThreadedRenderer.java index 87d5d9a..5017a38 100644 --- a/core/java/android/view/ThreadedRenderer.java +++ b/core/java/android/view/ThreadedRenderer.java @@ -360,7 +360,7 @@ public class ThreadedRenderer extends HardwareRenderer { @Override boolean copyLayerInto(final HardwareLayer layer, final Bitmap bitmap) { return nCopyLayerInto(mNativeProxy, - layer.getDeferredLayerUpdater(), bitmap.getSkBitmap()); + layer.getDeferredLayerUpdater(), bitmap); } @Override @@ -531,7 +531,7 @@ public class ThreadedRenderer extends HardwareRenderer { private static native long nCreateTextureLayer(long nativeProxy); private static native void nBuildLayer(long nativeProxy, long node); - private static native boolean nCopyLayerInto(long nativeProxy, long layer, long bitmap); + private static native boolean nCopyLayerInto(long nativeProxy, long layer, Bitmap bitmap); private static native void nPushLayerUpdate(long nativeProxy, long layer); private static native void nCancelLayerUpdate(long nativeProxy, long layer); private static native void nDetachSurfaceTexture(long nativeProxy, long layer); diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index b5b7f0f..ed0ceb2 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -987,6 +987,17 @@ public class View implements Drawable.Callback, KeyEvent.Callback, */ static final int DUPLICATE_PARENT_STATE = 0x00400000; + /** + * <p> + * Indicates this view can be stylus button pressed. When stylus button + * pressable, a View reacts to stylus button presses by notifiying + * the OnStylusButtonPressListener. + * </p> + * {@hide} + */ + static final int STYLUS_BUTTON_PRESSABLE = 0x00800000; + + /** @hide */ @IntDef({ SCROLLBARS_INSIDE_OVERLAY, @@ -3270,6 +3281,13 @@ public class View implements Drawable.Callback, KeyEvent.Callback, protected OnLongClickListener mOnLongClickListener; /** + * Listener used to dispatch stylus touch and button press events. This field should be made + * private, so it is hidden from the SDK. + * {@hide} + */ + protected OnStylusButtonPressListener mOnStylusButtonPressListener; + + /** * Listener used to build the context menu. * This field should be made private, so it is hidden from the SDK. * {@hide} @@ -3360,6 +3378,13 @@ public class View implements Drawable.Callback, KeyEvent.Callback, private boolean mHasPerformedLongPress; /** + * Whether the stylus button is currently pressed down. This is true when + * the stylus is touching the screen and the button has been pressed, this + * is false once the stylus has been lifted. + */ + private boolean mInStylusButtonPress = false; + + /** * The minimum height of the view. We'll try our best to have the height * of this view to at least this amount. */ @@ -3875,6 +3900,12 @@ public class View implements Drawable.Callback, KeyEvent.Callback, viewFlagMasks |= LONG_CLICKABLE; } break; + case com.android.internal.R.styleable.View_stylusButtonPressable: + if (a.getBoolean(attr, false)) { + viewFlagValues |= STYLUS_BUTTON_PRESSABLE; + viewFlagMasks |= STYLUS_BUTTON_PRESSABLE; + } + break; case com.android.internal.R.styleable.View_saveEnabled: if (!a.getBoolean(attr, true)) { viewFlagValues |= SAVE_DISABLED; @@ -4340,6 +4371,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, out.append((mViewFlags&SCROLLBARS_VERTICAL) != 0 ? 'V' : '.'); out.append((mViewFlags&CLICKABLE) != 0 ? 'C' : '.'); out.append((mViewFlags&LONG_CLICKABLE) != 0 ? 'L' : '.'); + out.append((mViewFlags & STYLUS_BUTTON_PRESSABLE) != 0 ? 'S' : '.'); out.append(' '); out.append((mPrivateFlags&PFLAG_IS_ROOT_NAMESPACE) != 0 ? 'R' : '.'); out.append((mPrivateFlags&PFLAG_FOCUSED) != 0 ? 'F' : '.'); @@ -4835,6 +4867,20 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } /** + * Register a callback to be invoked when this view is touched with a stylus and the button is + * pressed. + * + * @param l The callback that will run + * @see #setStylusButtonPressable(boolean) + */ + public void setOnStylusButtonPressListener(@Nullable OnStylusButtonPressListener l) { + if (!isStylusButtonPressable()) { + setStylusButtonPressable(true); + } + getListenerInfo().mOnStylusButtonPressListener = l; + } + + /** * Register a callback to be invoked when the context menu for this view is * being built. If this view is not long clickable, it becomes long clickable. * @@ -4912,6 +4958,46 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } /** + * Call this view's OnStylusButtonPressListener, if it is defined. + * + * @return True if there was an assigned OnStylusButtonPressListener that consumed the event, + * false otherwise. + */ + public boolean performStylusButtonPress() { + sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_STYLUS_BUTTON_PRESSED); + + boolean handled = false; + ListenerInfo li = mListenerInfo; + if (li != null && li.mOnStylusButtonPressListener != null) { + handled = li.mOnStylusButtonPressListener.onStylusButtonPress(View.this); + } + if (handled) { + performHapticFeedback(HapticFeedbackConstants.STYLUS_BUTTON_PRESS); + } + return handled; + } + + /** + * Checks for a stylus button press and calls the listener. + * + * @param event The event. + * @return True if the event was consumed. + */ + private boolean performStylusActionOnButtonPress(MotionEvent event) { + if (isStylusButtonPressable() && !mInStylusButtonPress + && !mHasPerformedLongPress && event.isStylusButtonPressed()) { + if (performStylusButtonPress()) { + mInStylusButtonPress = true; + setPressed(true, event.getX(), event.getY()); + removeTapCallback(); + removeLongPressCallback(); + return true; + } + } + return false; + } + + /** * Performs button-related actions during a touch down event. * * @param event The event. @@ -5701,6 +5787,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * <li>{@link AccessibilityNodeInfo#setFocused(boolean)},</li> * <li>{@link AccessibilityNodeInfo#setLongClickable(boolean)},</li> * <li>{@link AccessibilityNodeInfo#setSelected(boolean)},</li> + * <li>{@link AccessibilityNodeInfo#setStylusButtonPressable(boolean)}</li> * </ul> * <p> * Subclasses should override this method, call the super implementation, @@ -5852,6 +5939,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback, structure.setChecked(true); } } + if (isStylusButtonPressable()) { + structure.setStylusButtonPressable(true); + } structure.setClassName(getAccessibilityClassName().toString()); structure.setContentDescription(getContentDescription()); } @@ -5909,6 +5999,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback, structure.setChecked(true); } } + if (info.isStylusButtonPressable()) { + structure.setStylusButtonPressable(true); + } CharSequence cname = info.getClassName(); structure.setClassName(cname != null ? cname.toString() : null); structure.setContentDescription(info.getContentDescription()); @@ -6038,6 +6131,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, info.setAccessibilityFocused(isAccessibilityFocused()); info.setSelected(isSelected()); info.setLongClickable(isLongClickable()); + info.setStylusButtonPressable(isStylusButtonPressable()); info.setLiveRegion(getAccessibilityLiveRegion()); // TODO: These make sense only if we are in an AdapterView but all @@ -6068,6 +6162,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback, info.addAction(AccessibilityNodeInfo.ACTION_LONG_CLICK); } + if (isStylusButtonPressable() && isEnabled()) { + info.addAction(AccessibilityAction.ACTION_STYLUS_BUTTON_PRESS); + } + CharSequence text = getIterableTextForAccessibility(); if (text != null && text.length() > 0) { info.setTextSelection(getAccessibilitySelectionStart(), getAccessibilitySelectionEnd()); @@ -7442,6 +7540,31 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } /** + * Indicates whether this view reacts to stylus button press events or not. + * + * @return true if the view is stylus button pressable, false otherwise + * @see #setStylusButtonPressable(boolean) + * @attr ref android.R.styleable#View_stylusButtonPressable + */ + public boolean isStylusButtonPressable() { + return (mViewFlags & STYLUS_BUTTON_PRESSABLE) == STYLUS_BUTTON_PRESSABLE; + } + + /** + * Enables or disables stylus button press events for this view. When a view is stylus button + * pressable it reacts to the user touching the screen with a stylus and pressing the first + * stylus button. This event can launch the listener. + * + * @param stylusButtonPressable true to make the view react to a stylus button press, false + * otherwise + * @see #isStylusButtonPressable() + * @attr ref android.R.styleable#View_stylusButtonPressable + */ + public void setStylusButtonPressable(boolean stylusButtonPressable) { + setFlags(stylusButtonPressable ? STYLUS_BUTTON_PRESSABLE : 0, STYLUS_BUTTON_PRESSABLE); + } + + /** * Sets the pressed state for this view and provides a touch coordinate for * animation hinting. * @@ -7856,7 +7979,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback, public void addTouchables(ArrayList<View> views) { final int viewFlags = mViewFlags; - if (((viewFlags & CLICKABLE) == CLICKABLE || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) + if (((viewFlags & CLICKABLE) == CLICKABLE || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE + || (viewFlags & STYLUS_BUTTON_PRESSABLE) == STYLUS_BUTTON_PRESSABLE) && (viewFlags & ENABLED_MASK) == ENABLED) { views.add(this); } @@ -8571,6 +8695,12 @@ public class View implements Drawable.Callback, KeyEvent.Callback, return requestRectangleOnScreen(r, true); } } break; + case R.id.accessibilityActionStylusButtonPress: { + if (isStylusButtonPressable()) { + performStylusButtonPress(); + return true; + } + } break; } return false; } @@ -9728,7 +9858,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } return (viewFlags & CLICKABLE) == CLICKABLE - || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE; + || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE + || (viewFlags & STYLUS_BUTTON_PRESSABLE) == STYLUS_BUTTON_PRESSABLE; } /** @@ -9811,15 +9942,17 @@ public class View implements Drawable.Callback, KeyEvent.Callback, final float x = event.getX(); final float y = event.getY(); final int viewFlags = mViewFlags; + final int action = event.getAction(); if ((viewFlags & ENABLED_MASK) == DISABLED) { - if (event.getAction() == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) { + if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) { setPressed(false); } // A disabled view that is clickable still consumes the touch // events, it just doesn't respond to them. - return (((viewFlags & CLICKABLE) == CLICKABLE || - (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)); + return (((viewFlags & CLICKABLE) == CLICKABLE + || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) + || (viewFlags & STYLUS_BUTTON_PRESSABLE) == STYLUS_BUTTON_PRESSABLE); } if (mTouchDelegate != null) { @@ -9829,9 +9962,14 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } if (((viewFlags & CLICKABLE) == CLICKABLE || - (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) { - switch (event.getAction()) { + (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) || + (viewFlags & STYLUS_BUTTON_PRESSABLE) == STYLUS_BUTTON_PRESSABLE) { + switch (action) { case MotionEvent.ACTION_UP: + if (mInStylusButtonPress) { + mInStylusButtonPress = false; + mHasPerformedLongPress = false; + } boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0; if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) { // take focus if we don't have it already and we should in @@ -9885,6 +10023,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback, case MotionEvent.ACTION_DOWN: mHasPerformedLongPress = false; + mInStylusButtonPress = false; + + if (performStylusActionOnButtonPress(event)) { + break; + } if (performButtonActionOnTouchDown(event)) { break; @@ -9914,6 +10057,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback, setPressed(false); removeTapCallback(); removeLongPressCallback(); + if (mInStylusButtonPress) { + mInStylusButtonPress = false; + mHasPerformedLongPress = false; + } break; case MotionEvent.ACTION_MOVE: @@ -9929,6 +10076,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback, setPressed(false); } + } else if (performStylusActionOnButtonPress(event)) { + // Check for stylus button press if we're within the view. + break; } break; } @@ -10216,7 +10366,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback, if (accessibilityEnabled) { if ((changed & FOCUSABLE_MASK) != 0 || (changed & VISIBILITY_MASK) != 0 - || (changed & CLICKABLE) != 0 || (changed & LONG_CLICKABLE) != 0) { + || (changed & CLICKABLE) != 0 || (changed & LONG_CLICKABLE) != 0 + || (changed & STYLUS_BUTTON_PRESSABLE) != 0) { if (oldIncludeForAccessibility != includeForAccessibility()) { notifySubtreeAccessibilityStateChangedIfNeeded(); } else { @@ -10940,25 +11091,34 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } /** - * <p>Sets the opacity of the view. This is a value from 0 to 1, where 0 means the view is - * completely transparent and 1 means the view is completely opaque.</p> + * Sets the opacity of the view to a value from 0 to 1, where 0 means the view is + * completely transparent and 1 means the view is completely opaque. * - * <p> Note that setting alpha to a translucent value (0 < alpha < 1) can have significant - * performance implications, especially for large views. It is best to use the alpha property - * sparingly and transiently, as in the case of fading animations.</p> + * <p class="note"><strong>Note:</strong> setting alpha to a translucent value (0 < alpha < 1) + * can have significant performance implications, especially for large views. It is best to use + * the alpha property sparingly and transiently, as in the case of fading animations.</p> * * <p>For a view with a frequently changing alpha, such as during a fading animation, it is * strongly recommended for performance reasons to either override - * {@link #hasOverlappingRendering()} to return false if appropriate, or setting a - * {@link #setLayerType(int, android.graphics.Paint) layer type} on the view.</p> + * {@link #hasOverlappingRendering()} to return <code>false</code> if appropriate, or setting a + * {@link #setLayerType(int, android.graphics.Paint) layer type} on the view for the duration + * of the animation. On versions {@link android.os.Build.VERSION_CODES#MNC} and below, + * the default path for rendering an unlayered View with alpha could add multiple milliseconds + * of rendering cost, even for simple or small views. Starting with + * {@link android.os.Build.VERSION_CODES#MNC}, {@link #LAYER_TYPE_HARDWARE} is automatically + * applied to the view at the rendering level.</p> * * <p>If this view overrides {@link #onSetAlpha(int)} to return true, then this view is * responsible for applying the opacity itself.</p> * - * <p>Note that if the view is backed by a - * {@link #setLayerType(int, android.graphics.Paint) layer} and is associated with a - * {@link #setLayerPaint(android.graphics.Paint) layer paint}, setting an alpha value less than - * 1.0 will supersede the alpha of the layer paint.</p> + * <p>On versions {@link android.os.Build.VERSION_CODES#LOLLIPOP_MR1} and below, note that if + * the view is backed by a {@link #setLayerType(int, android.graphics.Paint) layer} and is + * associated with a {@link #setLayerPaint(android.graphics.Paint) layer paint}, setting an + * alpha value less than 1.0 will supersede the alpha of the layer paint.</p> + * + * <p>Starting with {@link android.os.Build.VERSION_CODES#MNC}, setting a translucent alpha + * value will clip a View to its bounds, unless the View returns <code>false</code> from + * {@link #hasOverlappingRendering}.</p> * * @param alpha The opacity of the view. * @@ -20782,6 +20942,20 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } /** + * Interface definition for a callback to be invoked when a view is touched with a stylus while + * the stylus button is pressed. + */ + public interface OnStylusButtonPressListener { + /** + * Called when a view is touched with a stylus while the stylus button is pressed. + * + * @param v The view that was touched. + * @return true if the callback consumed the stylus button press, false otherwise. + */ + boolean onStylusButtonPress(View v); + } + + /** * Interface definition for a callback to be invoked when the context menu * for this view is being built. */ diff --git a/core/java/android/view/ViewAssistStructure.java b/core/java/android/view/ViewAssistStructure.java index 346b8ec..fccfbb8 100644 --- a/core/java/android/view/ViewAssistStructure.java +++ b/core/java/android/view/ViewAssistStructure.java @@ -40,6 +40,8 @@ public abstract class ViewAssistStructure { public abstract void setLongClickable(boolean state); + public abstract void setStylusButtonPressable(boolean state); + public abstract void setFocusable(boolean state); public abstract void setFocused(boolean state); diff --git a/core/java/android/view/accessibility/AccessibilityEvent.java b/core/java/android/view/accessibility/AccessibilityEvent.java index 417e22c..b0dbeca 100644 --- a/core/java/android/view/accessibility/AccessibilityEvent.java +++ b/core/java/android/view/accessibility/AccessibilityEvent.java @@ -684,6 +684,11 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par public static final int TYPE_WINDOWS_CHANGED = 0x00400000; /** + * Represents the event of a stylus button press on a {@link android.view.View}. + */ + public static final int TYPE_VIEW_STYLUS_BUTTON_PRESSED = 0x00800000; + + /** * Change type for {@link #TYPE_WINDOW_CONTENT_CHANGED} event: * The type of change is not defined. */ @@ -731,6 +736,7 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par * @see #TYPE_TOUCH_INTERACTION_START * @see #TYPE_TOUCH_INTERACTION_END * @see #TYPE_WINDOWS_CHANGED + * @see #TYPE_VIEW_STYLUS_BUTTON_PRESSED */ public static final int TYPES_ALL_MASK = 0xFFFFFFFF; @@ -1396,6 +1402,14 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par builder.append("TYPE_WINDOWS_CHANGED"); eventTypeCount++; } break; + case TYPE_VIEW_STYLUS_BUTTON_PRESSED: { + if (eventTypeCount > 0) { + builder.append(", "); + } + builder.append("TYPE_VIEW_STYLUS_BUTTON_PRESSED"); + eventTypeCount++; + } + break; } } if (eventTypeCount > 1) { diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfo.java b/core/java/android/view/accessibility/AccessibilityNodeInfo.java index 0736ed8..bf4b7ae 100644 --- a/core/java/android/view/accessibility/AccessibilityNodeInfo.java +++ b/core/java/android/view/accessibility/AccessibilityNodeInfo.java @@ -520,6 +520,8 @@ public class AccessibilityNodeInfo implements Parcelable { private static final int BOOLEAN_PROPERTY_CONTENT_INVALID = 0x00010000; + private static final int BOOLEAN_PROPERTY_STYLUS_BUTTON_PRESSABLE = 0x00020000; + /** * Bits that provide the id of a virtual descendant of a view. */ @@ -1930,6 +1932,30 @@ public class AccessibilityNodeInfo implements Parcelable { } /** + * Gets whether this node is stylus button pressable. + * + * @return True if the node is stylus button pressable. + */ + public boolean isStylusButtonPressable() { + return getBooleanProperty(BOOLEAN_PROPERTY_STYLUS_BUTTON_PRESSABLE); + } + + /** + * Sets whether this node is stylus button pressable. + * <p> + * <strong>Note:</strong> Cannot be called from an + * {@link android.accessibilityservice.AccessibilityService}. This class is made immutable + * before being delivered to an AccessibilityService. + * </p> + * + * @param stylusButtonPressable True if the node is stylus button pressable. + * @throws IllegalStateException If called from an AccessibilityService. + */ + public void setStylusButtonPressable(boolean stylusButtonPressable) { + setBooleanProperty(BOOLEAN_PROPERTY_STYLUS_BUTTON_PRESSABLE, stylusButtonPressable); + } + + /** * Gets the node's live region mode. * <p> * A live region is a node that contains information that is important for @@ -3117,6 +3143,7 @@ public class AccessibilityNodeInfo implements Parcelable { builder.append("; selected: ").append(isSelected()); builder.append("; clickable: ").append(isClickable()); builder.append("; longClickable: ").append(isLongClickable()); + builder.append("; stylusButtonPressable: ").append(isStylusButtonPressable()); builder.append("; enabled: ").append(isEnabled()); builder.append("; password: ").append(isPassword()); builder.append("; scrollable: ").append(isScrollable()); @@ -3472,6 +3499,12 @@ public class AccessibilityNodeInfo implements Parcelable { public static final AccessibilityAction ACTION_SCROLL_TO_POSITION = new AccessibilityAction(R.id.accessibilityActionScrollToPosition, null); + /** + * Action that stylus button presses the node. + */ + public static final AccessibilityAction ACTION_STYLUS_BUTTON_PRESS = + new AccessibilityAction(R.id.accessibilityActionStylusButtonPress, null); + private static final ArraySet<AccessibilityAction> sStandardActions = new ArraySet<>(); static { sStandardActions.add(ACTION_FOCUS); @@ -3498,6 +3531,7 @@ public class AccessibilityNodeInfo implements Parcelable { sStandardActions.add(ACTION_SET_TEXT); sStandardActions.add(ACTION_SHOW_ON_SCREEN); sStandardActions.add(ACTION_SCROLL_TO_POSITION); + sStandardActions.add(ACTION_STYLUS_BUTTON_PRESS); } private final int mActionId; diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java index c9d9a8c..1df43d0 100644 --- a/core/java/android/widget/AbsListView.java +++ b/core/java/android/widget/AbsListView.java @@ -3638,11 +3638,8 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te startNestedScroll(SCROLL_AXIS_VERTICAL); - if (mFastScroll != null) { - boolean intercepted = mFastScroll.onTouchEvent(ev); - if (intercepted) { - return true; - } + if (mFastScroll != null && mFastScroll.onTouchEvent(ev)) { + return true; } initVelocityTrackerIfNotExists(); diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java index 35e7389..169aef9 100644 --- a/core/java/android/widget/Editor.java +++ b/core/java/android/widget/Editor.java @@ -578,7 +578,12 @@ public class Editor { } private void hideCursorControllers() { - if (mSuggestionsPopupWindow != null && !mSuggestionsPopupWindow.isShowingUp()) { + // When mTextView is not ExtractEditText, we need to distinguish two kinds of focus-lost. + // One is the true focus lost where suggestions pop-up (if any) should be dismissed, and the + // other is an side effect of showing the suggestions pop-up itself. We use isShowingUp() + // to distinguish one from the other. + if (mSuggestionsPopupWindow != null && ((mTextView instanceof ExtractEditText) || + !mSuggestionsPopupWindow.isShowingUp())) { // Should be done before hide insertion point controller since it triggers a show of it mSuggestionsPopupWindow.hide(); } diff --git a/core/java/android/widget/FastScroller.java b/core/java/android/widget/FastScroller.java index 552b274..f06f3c2 100644 --- a/core/java/android/widget/FastScroller.java +++ b/core/java/android/widget/FastScroller.java @@ -1389,7 +1389,8 @@ class FastScroller { // to intercept events. If it does, we will receive a CANCEL // event. if (!mList.isInScrollingContainer()) { - beginDrag(); + // This will get dispatched to onTouchEvent(). Start + // dragging there. return true; } @@ -1406,6 +1407,8 @@ class FastScroller { final float pos = getPosFromMotionEvent(mInitialTouchY); scrollTo(pos); + // This may get dispatched to onTouchEvent(), but it + // doesn't really matter since we'll already be in a drag. return onTouchEvent(ev); } break; @@ -1440,6 +1443,15 @@ class FastScroller { } switch (me.getActionMasked()) { + case MotionEvent.ACTION_DOWN: { + if (isPointInside(me.getX(), me.getY())) { + if (!mList.isInScrollingContainer()) { + beginDrag(); + return true; + } + } + } break; + case MotionEvent.ACTION_UP: { if (mPendingDrag >= 0) { // Allow a tap to scroll. diff --git a/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl b/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl index d149c5b..4c6db24 100644 --- a/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl +++ b/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl @@ -105,5 +105,5 @@ interface IVoiceInteractionManagerService { * Indicates whether the currently active voice interaction service is capable of handling the * assist gesture. */ - boolean activeServiceSupportsAssistGesture(); + boolean activeServiceSupportsAssist(); } diff --git a/core/java/com/android/internal/appwidget/IAppWidgetService.aidl b/core/java/com/android/internal/appwidget/IAppWidgetService.aidl index 008d38b..7d3db02 100644 --- a/core/java/com/android/internal/appwidget/IAppWidgetService.aidl +++ b/core/java/com/android/internal/appwidget/IAppWidgetService.aidl @@ -65,5 +65,6 @@ interface IAppWidgetService { in IBinder connection); void unbindRemoteViewsService(String callingPackage, int appWidgetId, in Intent intent); int[] getAppWidgetIds(in ComponentName providerComponent); + boolean isBoundWidgetPackage(String packageName, int userId); } diff --git a/core/java/com/android/internal/logging/MetricsLogger.java b/core/java/com/android/internal/logging/MetricsLogger.java index 3f96174..6173832 100644 --- a/core/java/com/android/internal/logging/MetricsLogger.java +++ b/core/java/com/android/internal/logging/MetricsLogger.java @@ -32,6 +32,7 @@ public class MetricsLogger implements MetricsConstants { public static final int NOTIFICATION_ZEN_MODE_EXTERNAL_RULE = 145; public static final int ACTION_BAN_APP_NOTES = 146; public static final int NOTIFICATION_ZEN_MODE_EVENT_RULE = 147; + public static final int ACTION_DISMISS_ALL_NOTES = 148; public static void visible(Context context, int category) throws IllegalArgumentException { if (Build.IS_DEBUGGABLE && category == VIEW_UNKNOWN) { diff --git a/core/java/com/android/internal/os/Zygote.java b/core/java/com/android/internal/os/Zygote.java index 75b6446..1e7ee5a 100644 --- a/core/java/com/android/internal/os/Zygote.java +++ b/core/java/com/android/internal/os/Zygote.java @@ -40,6 +40,8 @@ public final class Zygote { public static final int DEBUG_ENABLE_JNI_LOGGING = 1 << 4; /** enable the JIT compiler */ public static final int DEBUG_ENABLE_JIT = 1 << 5; + /** Force generation of CFI code */ + public static final int DEBUG_GENERATE_CFI = 1 << 6; /** No external storage should be mounted. */ public static final int MOUNT_EXTERNAL_NONE = 0; diff --git a/core/java/com/android/internal/os/ZygoteConnection.java b/core/java/com/android/internal/os/ZygoteConnection.java index 9106ccd..969d236 100644 --- a/core/java/com/android/internal/os/ZygoteConnection.java +++ b/core/java/com/android/internal/os/ZygoteConnection.java @@ -321,7 +321,7 @@ class ZygoteConnection { /** * From --enable-debugger, --enable-checkjni, --enable-assert, - * --enable-safemode, --enable-jit, and --enable-jni-logging. + * --enable-safemode, --enable-jit, --generate-cfi and --enable-jni-logging. */ int debugFlags; @@ -433,6 +433,8 @@ class ZygoteConnection { debugFlags |= Zygote.DEBUG_ENABLE_CHECKJNI; } else if (arg.equals("--enable-jit")) { debugFlags |= Zygote.DEBUG_ENABLE_JIT; + } else if (arg.equals("--generate-cfi")) { + debugFlags |= Zygote.DEBUG_GENERATE_CFI; } else if (arg.equals("--enable-jni-logging")) { debugFlags |= Zygote.DEBUG_ENABLE_JNI_LOGGING; } else if (arg.equals("--enable-assert")) { diff --git a/core/java/com/android/internal/widget/SwipeDismissLayout.java b/core/java/com/android/internal/widget/SwipeDismissLayout.java index 191662c..6d4e058 100644 --- a/core/java/com/android/internal/widget/SwipeDismissLayout.java +++ b/core/java/com/android/internal/widget/SwipeDismissLayout.java @@ -216,6 +216,8 @@ public class SwipeDismissLayout extends FrameLayout { if (mVelocityTracker == null) { return super.onTouchEvent(ev); } + // offset because the view is translated during swipe + ev.offsetLocation(mTranslationX, 0); switch (ev.getActionMasked()) { case MotionEvent.ACTION_UP: updateDismiss(ev); |