Merge "Don't require apps to call showLights to use default lights."
diff --git a/Android.mk b/Android.mk
index ad164e20..d68da97 100644
--- a/Android.mk
+++ b/Android.mk
@@ -136,6 +136,7 @@
core/java/android/content/ISyncStatusObserver.aidl \
core/java/android/content/pm/ILauncherApps.aidl \
core/java/android/content/pm/IOnAppsChangedListener.aidl \
+ core/java/android/content/pm/IOtaDexopt.aidl \
core/java/android/content/pm/IPackageDataObserver.aidl \
core/java/android/content/pm/IPackageDeleteObserver.aidl \
core/java/android/content/pm/IPackageDeleteObserver2.aidl \
diff --git a/core/java/android/content/pm/IOtaDexopt.aidl b/core/java/android/content/pm/IOtaDexopt.aidl
new file mode 100644
index 0000000..8f38d6f
--- /dev/null
+++ b/core/java/android/content/pm/IOtaDexopt.aidl
@@ -0,0 +1,49 @@
+/*
+**
+** Copyright 2016, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+
+package android.content.pm;
+
+/**
+ * A/B OTA dexopting service.
+ *
+ * {@hide}
+ */
+interface IOtaDexopt {
+ /**
+ * Prepare for A/B OTA dexopt. Initialize internal structures.
+ *
+ * Calls to the other methods are only valid after a call to prepare. You may not call
+ * prepare twice without a cleanup call.
+ */
+ void prepare();
+
+ /**
+ * Clean up all internal state.
+ */
+ void cleanup();
+
+ /**
+ * Check whether all updates have been performed.
+ */
+ boolean isDone();
+
+ /**
+ * Optimize the next package. Note: this command is synchronous, that is, only returns after
+ * the package has been dexopted (or dexopting failed).
+ */
+ void dexoptNextPackage();
+}
diff --git a/core/java/android/os/storage/VolumeInfo.java b/core/java/android/os/storage/VolumeInfo.java
index 5997515..4b70649 100644
--- a/core/java/android/os/storage/VolumeInfo.java
+++ b/core/java/android/os/storage/VolumeInfo.java
@@ -438,8 +438,11 @@
final Intent intent = new Intent(DocumentsContract.ACTION_BROWSE);
intent.addCategory(Intent.CATEGORY_DEFAULT);
intent.setData(uri);
+
+ // note that docsui treats this as *force* show advanced. So sending
+ // false permits advanced to be shown based on user preferences.
+ intent.putExtra(DocumentsContract.EXTRA_SHOW_ADVANCED, isPrimary());
intent.putExtra(DocumentsContract.EXTRA_SHOW_FILESIZE, true);
- intent.putExtra(DocumentsContract.EXTRA_SHOW_ADVANCED, true);
return intent;
}
diff --git a/core/java/android/print/PrintAttributes.java b/core/java/android/print/PrintAttributes.java
index 8892e34..e9c196d 100644
--- a/core/java/android/print/PrintAttributes.java
+++ b/core/java/android/print/PrintAttributes.java
@@ -27,6 +27,7 @@
import android.os.Parcelable;
import android.text.TextUtils;
import android.util.ArrayMap;
+import android.util.ArraySet;
import android.util.Log;
import com.android.internal.R;
@@ -872,6 +873,23 @@
mPackageName = null;
}
+ /**
+ * Get the Id of all predefined media sizes beside the {@link #UNKNOWN_PORTRAIT} and
+ * {@link #UNKNOWN_LANDSCAPE}.
+ *
+ * @return List of all predefined media sizes
+ *
+ * @hide
+ */
+ public static @NonNull ArraySet<MediaSize> getAllPredefinedSizes() {
+ ArraySet<MediaSize> definedMediaSizes = new ArraySet<>(sIdToMediaSizeMap.values());
+
+ definedMediaSizes.remove(UNKNOWN_PORTRAIT);
+ definedMediaSizes.remove(UNKNOWN_LANDSCAPE);
+
+ return definedMediaSizes;
+ }
+
/** @hide */
public MediaSize(String id, String label, String packageName,
int widthMils, int heightMils, int labelResId) {
diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java
index 2733391..cf13a13 100644
--- a/core/java/com/android/internal/app/ChooserActivity.java
+++ b/core/java/com/android/internal/app/ChooserActivity.java
@@ -25,6 +25,7 @@
import android.content.IntentSender;
import android.content.IntentSender.SendIntentException;
import android.content.ServiceConnection;
+import android.content.SharedPreferences;
import android.content.pm.ActivityInfo;
import android.content.pm.LabeledIntent;
import android.content.pm.PackageManager;
@@ -35,6 +36,7 @@
import android.graphics.drawable.Drawable;
import android.graphics.drawable.Icon;
import android.os.Bundle;
+import android.os.Environment;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
@@ -68,6 +70,7 @@
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.MetricsProto.MetricsEvent;
+import java.io.File;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
@@ -91,6 +94,11 @@
private ChooserListAdapter mChooserListAdapter;
private ChooserRowAdapter mChooserRowAdapter;
+ private SharedPreferences mPinnedSharedPrefs;
+ private static final float PINNED_TARGET_SCORE_BOOST = 1000.f;
+ private static final String PINNED_SHARED_PREFS_NAME = "chooser_pin_settings";
+ private static final String TARGET_DETAILS_FRAGMENT_TAG = "targetDetailsFragment";
+
private final List<ChooserTargetServiceConnection> mServiceConnections = new ArrayList<>();
private static final int CHOOSER_TARGET_SERVICE_RESULT = 1;
@@ -207,12 +215,30 @@
mRefinementIntentSender = intent.getParcelableExtra(
Intent.EXTRA_CHOOSER_REFINEMENT_INTENT_SENDER);
setSafeForwardingMode(true);
+
+ mPinnedSharedPrefs = getPinnedSharedPrefs(this);
super.onCreate(savedInstanceState, target, title, defaultTitleRes, initialIntents,
null, false);
MetricsLogger.action(this, MetricsEvent.ACTION_ACTIVITY_CHOOSER_SHOWN);
}
+ static SharedPreferences getPinnedSharedPrefs(Context context) {
+ // The code below is because in the android:ui process, no one can hear you scream.
+ // The package info in the context isn't initialized in the way it is for normal apps,
+ // so the standard, name-based context.getSharedPreferences doesn't work. Instead, we
+ // build the path manually below using the same policy that appears in ContextImpl.
+ // This fails silently under the hood if there's a problem, so if we find ourselves in
+ // the case where we don't have access to credential encrypted storage we just won't
+ // have our pinned target info.
+ final File prefsFile = new File(new File(
+ Environment.getDataUserCredentialEncryptedPackageDirectory(null,
+ context.getUserId(), context.getPackageName()),
+ "shared_prefs"),
+ PINNED_SHARED_PREFS_NAME + ".xml");
+ return context.getSharedPreferences(prefsFile, MODE_PRIVATE);
+ }
+
@Override
protected void onDestroy() {
super.onDestroy();
@@ -243,7 +269,7 @@
}
@Override
- void onActivityStarted(TargetInfo cti) {
+ public void onActivityStarted(TargetInfo cti) {
if (mChosenComponentSender != null) {
final ComponentName target = cti.getResolvedComponentName();
if (target != null) {
@@ -259,7 +285,7 @@
}
@Override
- void onPrepareAdapterView(AbsListView adapterView, ResolveListAdapter adapter,
+ public void onPrepareAdapterView(AbsListView adapterView, ResolveListAdapter adapter,
boolean alwaysUseOption) {
final ListView listView = adapterView instanceof ListView ? (ListView) adapterView : null;
mChooserListAdapter = (ChooserListAdapter) adapter;
@@ -272,17 +298,17 @@
}
@Override
- int getLayoutResource() {
+ public int getLayoutResource() {
return R.layout.chooser_grid;
}
@Override
- boolean shouldGetActivityMetadata() {
+ public boolean shouldGetActivityMetadata() {
return true;
}
@Override
- boolean shouldAutoLaunchSingleChoice(TargetInfo target) {
+ public boolean shouldAutoLaunchSingleChoice(TargetInfo target) {
final Intent intent = target.getResolvedIntent();
final ResolveInfo resolve = target.getResolveInfo();
@@ -299,6 +325,16 @@
return false;
}
+ @Override
+ public void showTargetDetails(ResolveInfo ri) {
+ ComponentName name = ri.activityInfo.getComponentName();
+ boolean pinned = mPinnedSharedPrefs.getBoolean(name.flattenToString(), false);
+ ResolverTargetActionsDialogFragment f =
+ new ResolverTargetActionsDialogFragment(ri.loadLabel(getPackageManager()),
+ name, pinned);
+ f.show(getFragmentManager(), TARGET_DETAILS_FRAGMENT_TAG);
+ }
+
private void modifyTargetIntent(Intent in) {
final String action = in.getAction();
if (Intent.ACTION_SEND.equals(action) ||
@@ -340,7 +376,7 @@
}
@Override
- void startSelected(int which, boolean always, boolean filtered) {
+ public void startSelected(int which, boolean always, boolean filtered) {
super.startSelected(which, always, filtered);
if (mChooserListAdapter != null) {
@@ -471,7 +507,7 @@
mChooserHandler.removeMessages(CHOOSER_TARGET_SERVICE_WATCHDOG_TIMEOUT);
}
- void onSetupVoiceInteraction() {
+ public void onSetupVoiceInteraction() {
// Do nothing. We'll send the voice stuff ourselves.
}
@@ -543,7 +579,7 @@
}
@Override
- ResolveListAdapter createAdapter(Context context, List<Intent> payloadIntents,
+ public ResolveListAdapter createAdapter(Context context, List<Intent> payloadIntents,
Intent[] initialIntents, List<ResolveInfo> rList, int launchedFromUid,
boolean filterLastUsed) {
final ChooserListAdapter adapter = new ChooserListAdapter(context, payloadIntents,
@@ -711,6 +747,11 @@
}
return results;
}
+
+ @Override
+ public boolean isPinned() {
+ return mSourceInfo != null ? mSourceInfo.isPinned() : false;
+ }
}
public class ChooserListAdapter extends ResolveListAdapter {
@@ -777,6 +818,20 @@
}
@Override
+ public boolean isComponentPinned(ComponentName name) {
+ return mPinnedSharedPrefs.getBoolean(name.flattenToString(), false);
+ }
+
+ @Override
+ public float getScore(DisplayResolveInfo target) {
+ float score = super.getScore(target);
+ if (target.isPinned()) {
+ score += PINNED_TARGET_SCORE_BOOST;
+ }
+ return score;
+ }
+
+ @Override
public View onCreateView(ViewGroup parent) {
return mInflater.inflate(
com.android.internal.R.layout.resolve_grid_item, parent, false);
@@ -1121,7 +1176,7 @@
v.setOnLongClickListener(new OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
- showAppDetails(
+ showTargetDetails(
mChooserListAdapter.resolveInfoForPosition(
holder.itemIndices[column], true));
return true;
diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java
index ec148c55..53e329d 100644
--- a/core/java/com/android/internal/app/ResolverActivity.java
+++ b/core/java/com/android/internal/app/ResolverActivity.java
@@ -22,6 +22,7 @@
import android.app.VoiceInteractor.PickOptionRequest;
import android.app.VoiceInteractor.PickOptionRequest.Option;
import android.app.VoiceInteractor.Prompt;
+import android.content.pm.ComponentInfo;
import android.os.AsyncTask;
import android.provider.Settings;
import android.text.TextUtils;
@@ -336,12 +337,12 @@
/**
* Perform any initialization needed for voice interaction.
*/
- void onSetupVoiceInteraction() {
+ public void onSetupVoiceInteraction() {
// Do it right now. Subclasses may delay this and send it later.
sendVoiceChoicesIfNeeded();
}
- void sendVoiceChoicesIfNeeded() {
+ public void sendVoiceChoicesIfNeeded() {
if (!isVoiceInteraction()) {
// Clearly not needed.
return;
@@ -382,7 +383,7 @@
return null;
}
- int getLayoutResource() {
+ public int getLayoutResource() {
return R.layout.resolver_list;
}
@@ -591,7 +592,7 @@
mAlwaysUseOption);
}
- void startSelected(int which, boolean always, boolean filtered) {
+ public void startSelected(int which, boolean always, boolean filtered) {
if (isFinishing()) {
return;
}
@@ -761,7 +762,7 @@
return true;
}
- void safelyStartActivity(TargetInfo cti) {
+ public void safelyStartActivity(TargetInfo cti) {
// If needed, show that intent is forwarded
// from managed profile to owner or other way around.
if (mProfileSwitchMessageId != -1) {
@@ -791,26 +792,26 @@
}
}
- void onActivityStarted(TargetInfo cti) {
+ public void onActivityStarted(TargetInfo cti) {
// Do nothing
}
- boolean shouldGetActivityMetadata() {
+ public boolean shouldGetActivityMetadata() {
return false;
}
- boolean shouldAutoLaunchSingleChoice(TargetInfo target) {
+ public boolean shouldAutoLaunchSingleChoice(TargetInfo target) {
return true;
}
- void showAppDetails(ResolveInfo ri) {
+ public void showTargetDetails(ResolveInfo ri) {
Intent in = new Intent().setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
.setData(Uri.fromParts("package", ri.activityInfo.packageName, null))
.addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT);
startActivity(in);
}
- ResolveListAdapter createAdapter(Context context, List<Intent> payloadIntents,
+ public ResolveListAdapter createAdapter(Context context, List<Intent> payloadIntents,
Intent[] initialIntents, List<ResolveInfo> rList, int launchedFromUid,
boolean filterLastUsed) {
return new ResolveListAdapter(context, payloadIntents, initialIntents, rList,
@@ -820,7 +821,7 @@
/**
* Returns true if the activity is finishing and creation should halt
*/
- boolean configureContentView(List<Intent> payloadIntents, Intent[] initialIntents,
+ public boolean configureContentView(List<Intent> payloadIntents, Intent[] initialIntents,
List<ResolveInfo> rList, boolean alwaysUseOption) {
// The last argument of createAdapter is whether to do special handling
// of the last used choice to highlight it in the list. We need to always
@@ -867,7 +868,7 @@
return false;
}
- void onPrepareAdapterView(AbsListView adapterView, ResolveListAdapter adapter,
+ public void onPrepareAdapterView(AbsListView adapterView, ResolveListAdapter adapter,
boolean alwaysUseOption) {
final boolean useHeader = adapter.hasFilteredItem();
final ListView listView = adapterView instanceof ListView ? (ListView) adapterView : null;
@@ -898,7 +899,7 @@
&& Objects.equals(lhs.activityInfo.packageName, rhs.activityInfo.packageName);
}
- final class DisplayResolveInfo implements TargetInfo {
+ public final class DisplayResolveInfo implements TargetInfo {
private final ResolveInfo mResolveInfo;
private final CharSequence mDisplayLabel;
private Drawable mDisplayIcon;
@@ -906,8 +907,9 @@
private final CharSequence mExtendedInfo;
private final Intent mResolvedIntent;
private final List<Intent> mSourceIntents = new ArrayList<>();
+ private boolean mPinned;
- DisplayResolveInfo(Intent originalIntent, ResolveInfo pri, CharSequence pLabel,
+ public DisplayResolveInfo(Intent originalIntent, ResolveInfo pri, CharSequence pLabel,
CharSequence pInfo, Intent pOrigIntent) {
mSourceIntents.add(originalIntent);
mResolveInfo = pri;
@@ -932,6 +934,7 @@
mExtendedInfo = other.mExtendedInfo;
mResolvedIntent = new Intent(other.mResolvedIntent);
mResolvedIntent.fillIn(fillInIntent, flags);
+ mPinned = other.mPinned;
}
public ResolveInfo getResolveInfo() {
@@ -1026,6 +1029,15 @@
activity.startActivityAsUser(mResolvedIntent, options, user);
return false;
}
+
+ @Override
+ public boolean isPinned() {
+ return mPinned;
+ }
+
+ public void setPinned(boolean pinned) {
+ mPinned = pinned;
+ }
}
/**
@@ -1039,7 +1051,7 @@
*
* @return the resolved intent for this target
*/
- public Intent getResolvedIntent();
+ Intent getResolvedIntent();
/**
* Get the resolved component name that represents this target. Note that this may not
@@ -1048,7 +1060,7 @@
*
* @return the resolved ComponentName for this target
*/
- public ComponentName getResolvedComponentName();
+ ComponentName getResolvedComponentName();
/**
* Start the activity referenced by this target.
@@ -1057,7 +1069,7 @@
* @param options ActivityOptions bundle
* @return true if the start completed successfully
*/
- public boolean start(Activity activity, Bundle options);
+ boolean start(Activity activity, Bundle options);
/**
* Start the activity referenced by this target as if the ResolverActivity's caller
@@ -1068,7 +1080,7 @@
* @param userId userId to start as or {@link UserHandle#USER_NULL} for activity's caller
* @return true if the start completed successfully
*/
- public boolean startAsCaller(Activity activity, Bundle options, int userId);
+ boolean startAsCaller(Activity activity, Bundle options, int userId);
/**
* Start the activity referenced by this target as a given user.
@@ -1078,7 +1090,7 @@
* @param user handle for the user to start the activity as
* @return true if the start completed successfully
*/
- public boolean startAsUser(Activity activity, Bundle options, UserHandle user);
+ boolean startAsUser(Activity activity, Bundle options, UserHandle user);
/**
* Return the ResolveInfo about how and why this target matched the original query
@@ -1086,14 +1098,14 @@
*
* @return ResolveInfo representing this target's match
*/
- public ResolveInfo getResolveInfo();
+ ResolveInfo getResolveInfo();
/**
* Return the human-readable text label for this target.
*
* @return user-visible target label
*/
- public CharSequence getDisplayLabel();
+ CharSequence getDisplayLabel();
/**
* Return any extended info for this target. This may be used to disambiguate
@@ -1101,35 +1113,40 @@
*
* @return human-readable disambig string or null if none present
*/
- public CharSequence getExtendedInfo();
+ CharSequence getExtendedInfo();
/**
* @return The drawable that should be used to represent this target
*/
- public Drawable getDisplayIcon();
+ Drawable getDisplayIcon();
/**
* @return The (small) icon to badge the target with
*/
- public Drawable getBadgeIcon();
+ Drawable getBadgeIcon();
/**
* @return The content description for the badge icon
*/
- public CharSequence getBadgeContentDescription();
+ CharSequence getBadgeContentDescription();
/**
* Clone this target with the given fill-in information.
*/
- public TargetInfo cloneFilledIn(Intent fillInIntent, int flags);
+ TargetInfo cloneFilledIn(Intent fillInIntent, int flags);
/**
* @return the list of supported source intents deduped against this single target
*/
- public List<Intent> getAllSourceIntents();
+ List<Intent> getAllSourceIntents();
+
+ /**
+ * @return true if this target should be pinned to the front by the request of the user
+ */
+ boolean isPinned();
}
- class ResolveListAdapter extends BaseAdapter {
+ public class ResolveListAdapter extends BaseAdapter {
private final List<Intent> mIntents;
private final Intent[] mInitialIntents;
private final List<ResolveInfo> mBaseResolveList;
@@ -1376,9 +1393,12 @@
}
}
if (!found) {
- into.add(new ResolvedComponentInfo(new ComponentName(
- newInfo.activityInfo.packageName, newInfo.activityInfo.name),
- intent, newInfo));
+ final ComponentName name = new ComponentName(
+ newInfo.activityInfo.packageName, newInfo.activityInfo.name);
+ final ResolvedComponentInfo rci = new ResolvedComponentInfo(name,
+ intent, newInfo);
+ rci.setPinned(isComponentPinned(name));
+ into.add(rci);
}
}
}
@@ -1454,6 +1474,7 @@
final Intent replaceIntent = getReplacementIntent(add.activityInfo, intent);
final DisplayResolveInfo dri = new DisplayResolveInfo(intent, add, roLabel,
extraInfo, replaceIntent);
+ dri.setPinned(rci.isPinned());
addResolveInfo(dri);
if (replaceIntent == intent) {
// Only add alternates if we didn't get a specific replacement from
@@ -1537,11 +1558,11 @@
return false;
}
- protected int getDisplayResolveInfoCount() {
+ public int getDisplayResolveInfoCount() {
return mDisplayList.size();
}
- protected DisplayResolveInfo getDisplayResolveInfo(int index) {
+ public DisplayResolveInfo getDisplayResolveInfo(int index) {
// Used to query services. We only query services for primary targets, not alternates.
return mDisplayList.get(index);
}
@@ -1571,6 +1592,10 @@
return !TextUtils.isEmpty(info.getExtendedInfo());
}
+ public boolean isComponentPinned(ComponentName name) {
+ return false;
+ }
+
public final void bindView(int position, View view) {
onBindView(view, getItem(position));
}
@@ -1607,6 +1632,7 @@
static final class ResolvedComponentInfo {
public final ComponentName name;
+ private boolean mPinned;
private final List<Intent> mIntents = new ArrayList<>();
private final List<ResolveInfo> mResolveInfos = new ArrayList<>();
@@ -1649,6 +1675,14 @@
}
return -1;
}
+
+ public boolean isPinned() {
+ return mPinned;
+ }
+
+ public void setPinned(boolean pinned) {
+ mPinned = pinned;
+ }
}
static class ViewHolder {
@@ -1702,7 +1736,7 @@
return false;
}
ResolveInfo ri = mAdapter.resolveInfoForPosition(position, true);
- showAppDetails(ri);
+ showTargetDetails(ri);
return true;
}
diff --git a/core/java/com/android/internal/app/ResolverComparator.java b/core/java/com/android/internal/app/ResolverComparator.java
index 31556e2..0ae21c6 100644
--- a/core/java/com/android/internal/app/ResolverComparator.java
+++ b/core/java/com/android/internal/app/ResolverComparator.java
@@ -47,8 +47,8 @@
private static final boolean DEBUG = false;
- // Two weeks
- private static final long USAGE_STATS_PERIOD = 1000 * 60 * 60 * 24 * 14;
+ // One week
+ private static final long USAGE_STATS_PERIOD = 1000 * 60 * 60 * 24 * 7;
private static final long RECENCY_TIME_PERIOD = 1000 * 60 * 60 * 12;
@@ -171,15 +171,27 @@
}
}
- if (mStats != null) {
- final ScoredTarget lhsTarget = mScoredTargets.get(new ComponentName(
- lhs.activityInfo.packageName, lhs.activityInfo.name));
- final ScoredTarget rhsTarget = mScoredTargets.get(new ComponentName(
- rhs.activityInfo.packageName, rhs.activityInfo.name));
- final float diff = rhsTarget.score - lhsTarget.score;
+ final boolean lPinned = lhsp.isPinned();
+ final boolean rPinned = rhsp.isPinned();
- if (diff != 0) {
- return diff > 0 ? 1 : -1;
+ if (lPinned && !rPinned) {
+ return -1;
+ } else if (!lPinned && rPinned) {
+ return 1;
+ }
+
+ // Pinned items stay stable within a normal lexical sort and ignore scoring.
+ if (!lPinned && !rPinned) {
+ if (mStats != null) {
+ final ScoredTarget lhsTarget = mScoredTargets.get(new ComponentName(
+ lhs.activityInfo.packageName, lhs.activityInfo.name));
+ final ScoredTarget rhsTarget = mScoredTargets.get(new ComponentName(
+ rhs.activityInfo.packageName, rhs.activityInfo.name));
+ final float diff = rhsTarget.score - lhsTarget.score;
+
+ if (diff != 0) {
+ return diff > 0 ? 1 : -1;
+ }
}
}
diff --git a/core/java/com/android/internal/app/ResolverTargetActionsDialogFragment.java b/core/java/com/android/internal/app/ResolverTargetActionsDialogFragment.java
new file mode 100644
index 0000000..8156f79
--- /dev/null
+++ b/core/java/com/android/internal/app/ResolverTargetActionsDialogFragment.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package com.android.internal.app;
+
+import android.app.AlertDialog.Builder;
+import android.app.Dialog;
+import android.app.DialogFragment;
+import android.content.ComponentName;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.net.Uri;
+import android.os.Bundle;
+import android.provider.Settings;
+
+import com.android.internal.R;
+
+/**
+ * Shows a dialog with actions to take on a chooser target
+ */
+public class ResolverTargetActionsDialogFragment extends DialogFragment
+ implements DialogInterface.OnClickListener {
+ private static final String NAME_KEY = "componentName";
+ private static final String PINNED_KEY = "pinned";
+ private static final String TITLE_KEY = "title";
+
+ // Sync with R.array.resolver_target_actions_* resources
+ private static final int TOGGLE_PIN_INDEX = 0;
+ private static final int APP_INFO_INDEX = 1;
+
+ public ResolverTargetActionsDialogFragment() {
+ }
+
+ public ResolverTargetActionsDialogFragment(CharSequence title, ComponentName name,
+ boolean pinned) {
+ Bundle args = new Bundle();
+ args.putCharSequence(TITLE_KEY, title);
+ args.putParcelable(NAME_KEY, name);
+ args.putBoolean(PINNED_KEY, pinned);
+ setArguments(args);
+ }
+
+ @Override
+ public Dialog onCreateDialog(Bundle savedInstanceState) {
+ final Bundle args = getArguments();
+ final int itemRes = args.getBoolean(PINNED_KEY, false)
+ ? R.array.resolver_target_actions_unpin
+ : R.array.resolver_target_actions_pin;
+ return new Builder(getContext())
+ .setCancelable(true)
+ .setItems(itemRes, this)
+ .setTitle(args.getCharSequence(TITLE_KEY))
+ .create();
+ }
+
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ final Bundle args = getArguments();
+ ComponentName name = args.getParcelable(NAME_KEY);
+ switch (which) {
+ case TOGGLE_PIN_INDEX:
+ SharedPreferences sp = ChooserActivity.getPinnedSharedPrefs(getContext());
+ final String key = name.flattenToString();
+ boolean currentVal = sp.getBoolean(name.flattenToString(), false);
+ if (currentVal) {
+ sp.edit().remove(key).apply();
+ } else {
+ sp.edit().putBoolean(key, true).apply();
+ }
+
+ // Force the chooser to requery and resort things
+ getActivity().recreate();
+ break;
+ case APP_INFO_INDEX:
+ Intent in = new Intent().setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
+ .setData(Uri.fromParts("package", name.getPackageName(), null))
+ .addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT);
+ startActivity(in);
+ break;
+ }
+ dismiss();
+ }
+}
diff --git a/core/res/res/values/arrays.xml b/core/res/res/values/arrays.xml
index a6a4564..c083a5e 100644
--- a/core/res/res/values/arrays.xml
+++ b/core/res/res/values/arrays.xml
@@ -402,4 +402,15 @@
<item>@color/Red_700</item>
</array>
+ <!-- Used in ResolverTargetActionsDialogFragment -->
+ <string-array name="resolver_target_actions_pin">
+ <item>@string/pin_target</item>
+ <item>@string/app_info</item>
+ </string-array>
+
+ <string-array name="resolver_target_actions_unpin">
+ <item>@string/unpin_target</item>
+ <item>@string/app_info</item>
+ </string-array>
+
</resources>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 523826b..b2f5f8a 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -4206,4 +4206,15 @@
<string name="usb_mtp_launch_notification_title">Connected to <xliff:g id="product_name">%1$s</xliff:g></string>
<!-- Description of notification shown after a MTP device is connected to Android. -->
<string name="usb_mtp_launch_notification_description">Tap to view files</string>
+
+ <!-- Resolver target actions strings -->
+
+ <!-- Pin (as in to a bulletin board with a pushpin) a resolver
+ target to the front of the list. -->
+ <string name="pin_target">Pin</string>
+ <!-- Unpin a resolver target such that it sorts normally. -->
+ <string name="unpin_target">Unpin</string>
+ <!-- View application info for a target. -->
+ <string name="app_info">App info</string>
+
</resources>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 1f0e21e..bc609c6 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -2525,4 +2525,9 @@
<java-symbol type="string" name="usb_mtp_launch_notification_title" />
<java-symbol type="string" name="usb_mtp_launch_notification_description" />
+
+ <!-- Resolver target actions -->
+ <java-symbol type="array" name="resolver_target_actions_pin" />
+ <java-symbol type="array" name="resolver_target_actions_unpin" />
+
</resources>
diff --git a/media/java/android/media/tv/TvContract.java b/media/java/android/media/tv/TvContract.java
index c219418..62a01dc 100644
--- a/media/java/android/media/tv/TvContract.java
+++ b/media/java/android/media/tv/TvContract.java
@@ -30,7 +30,6 @@
import java.util.HashMap;
import java.util.List;
import java.util.Map;
-import java.util.StringTokenizer;
/**
* The contract between the TV provider and applications. Contains definitions for the supported
@@ -1331,13 +1330,7 @@
* @return canonical genre strings.
*/
public static String[] decode(String genres) {
- StringTokenizer st = new StringTokenizer(genres, "\\s*,\\s*");
- String[] result = new String[st.countTokens()];
- int i = 0;
- while (st.hasMoreTokens()){
- result[i++] = st.nextToken();
- }
- return result;
+ return genres.split("\\s*,\\s*");
}
/**
diff --git a/packages/DocumentsUI/res/drawable/ic_sd_storage.xml b/packages/DocumentsUI/res/drawable/ic_sd_storage.xml
new file mode 100644
index 0000000..b0f3cc3
--- /dev/null
+++ b/packages/DocumentsUI/res/drawable/ic_sd_storage.xml
@@ -0,0 +1,24 @@
+<!--
+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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="48dp"
+ android:height="48dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M18 2h-8L4.02 8 4 20c0 1.1.9 2 2 2h12c1.1 0 2,-.9 2,-2V4c0,-1.1,-.9,-2,-2,-2zm-6 6h-2V4h2v4zm3 0h-2V4h2v4zm3 0h-2V4h2v4z"/>
+</vector>
diff --git a/packages/DocumentsUI/res/menu/activity.xml b/packages/DocumentsUI/res/menu/activity.xml
index a3cfde8..ac668fd 100644
--- a/packages/DocumentsUI/res/menu/activity.xml
+++ b/packages/DocumentsUI/res/menu/activity.xml
@@ -88,5 +88,6 @@
<item
android:id="@+id/menu_settings"
android:title="@string/menu_settings"
- android:showAsAction="never" />
+ android:showAsAction="never"
+ android:visible="false" />
</menu>
diff --git a/packages/DocumentsUI/res/values/strings.xml b/packages/DocumentsUI/res/values/strings.xml
index afe9336..3c49f16 100644
--- a/packages/DocumentsUI/res/values/strings.xml
+++ b/packages/DocumentsUI/res/values/strings.xml
@@ -39,8 +39,8 @@
<string name="menu_sort">Sort by</string>
<!-- Menu item that enters a mode to search for documents [CHAR LIMIT=24] -->
<string name="menu_search">Search</string>
- <!-- Menu item that enters activity to change settings [CHAR LIMIT=24] -->
- <string name="menu_settings">Settings</string>
+ <!-- Menu item that enters activity to change settings for current root [CHAR LIMIT=24] -->
+ <string name="menu_settings">Storage settings</string>
<!-- Menu item title that opens the selected documents [CHAR LIMIT=24] -->
<string name="menu_open">Open</string>
diff --git a/packages/DocumentsUI/src/com/android/documentsui/BaseActivity.java b/packages/DocumentsUI/src/com/android/documentsui/BaseActivity.java
index 1474aa6..6fd29a4 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/BaseActivity.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/BaseActivity.java
@@ -150,7 +150,6 @@
final MenuItem list = menu.findItem(R.id.menu_list);
final MenuItem advanced = menu.findItem(R.id.menu_advanced);
final MenuItem fileSize = menu.findItem(R.id.menu_file_size);
- final MenuItem settings = menu.findItem(R.id.menu_settings);
final MenuItem search = menu.findItem(R.id.menu_search);
// I'm thinkin' this isn't necesary here. If it is...'cuz of a bug....
@@ -167,7 +166,6 @@
sortSize.setVisible(mState.showSize); // Only sort by size when visible
fileSize.setVisible(!mState.forceSize);
advanced.setVisible(!mState.forceAdvanced);
- settings.setVisible((root.flags & Root.FLAG_HAS_SETTINGS) != 0);
search.setVisible(canSearchRoot());
advanced.setTitle(LocalPreferences.getDisplayAdvancedDevices(this)
diff --git a/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java b/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java
index c3395ae..e7347a3 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java
@@ -297,7 +297,6 @@
final MenuItem grid = menu.findItem(R.id.menu_grid);
final MenuItem list = menu.findItem(R.id.menu_list);
final MenuItem fileSize = menu.findItem(R.id.menu_file_size);
- final MenuItem settings = menu.findItem(R.id.menu_settings);
boolean recents = cwd == null;
boolean picking = mState.action == ACTION_CREATE
@@ -314,7 +313,6 @@
}
fileSize.setVisible(fileSize.isVisible() && !picking);
- settings.setVisible(false);
if (mState.action == ACTION_CREATE) {
final FragmentManager fm = getFragmentManager();
diff --git a/packages/DocumentsUI/src/com/android/documentsui/FilesActivity.java b/packages/DocumentsUI/src/com/android/documentsui/FilesActivity.java
index b490c00..3968cae 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/FilesActivity.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/FilesActivity.java
@@ -245,16 +245,21 @@
@Override
public boolean onPrepareOptionsMenu(Menu menu) {
super.onPrepareOptionsMenu(menu);
+ final RootInfo root = getCurrentRoot();
final MenuItem createDir = menu.findItem(R.id.menu_create_dir);
- final MenuItem newWindow = menu.findItem(R.id.menu_new_window);
final MenuItem pasteFromCb = menu.findItem(R.id.menu_paste_from_clipboard);
+ final MenuItem settings = menu.findItem(R.id.menu_settings);
createDir.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
createDir.setVisible(true);
createDir.setEnabled(canCreateDirectory());
-
pasteFromCb.setEnabled(mClipper.hasItemsToPaste());
+ settings.setVisible(root.hasSettings());
+
+ // TODO: For some reason menu is ignoring this being set
+ // to never in activity.xml. File a bug.
+ settings.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
Menus.disableHiddenItems(menu, pasteFromCb);
return true;
diff --git a/packages/DocumentsUI/src/com/android/documentsui/model/RootInfo.java b/packages/DocumentsUI/src/com/android/documentsui/model/RootInfo.java
index 12c0b8f..3f14a55 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/model/RootInfo.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/model/RootInfo.java
@@ -176,6 +176,7 @@
} else if (isExternalStorage()) {
derivedIcon = R.drawable.ic_root_smartphone;
derivedType = TYPE_LOCAL;
+ // TODO: Apply SD card icon to SD devices.
} else if (isDownloads()) {
derivedIcon = R.drawable.ic_root_download;
derivedType = TYPE_DOWNLOADS;
@@ -244,6 +245,10 @@
|| derivedType == TYPE_RECENTS || derivedType == TYPE_DOWNLOADS;
}
+ public boolean hasSettings() {
+ return (flags & Root.FLAG_HAS_SETTINGS) != 0;
+ }
+
public Drawable loadIcon(Context context) {
if (derivedIcon != 0) {
return context.getDrawable(derivedIcon);
diff --git a/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java b/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java
index 56e5a9b..f89934d 100644
--- a/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java
+++ b/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java
@@ -134,9 +134,6 @@
final String rootId;
final String title;
if (volume.getType() == VolumeInfo.TYPE_EMULATED) {
- // save off the primary volume for subsequent "Home" dir initialization.
- primaryVolume = volume;
-
// We currently only support a single emulated volume mounted at
// a time, and it's always considered the primary
rootId = ROOT_ID_PRIMARY_EMULATED;
@@ -167,9 +164,14 @@
mRoots.put(rootId, root);
root.rootId = rootId;
- root.flags = Root.FLAG_LOCAL_ONLY | Root.FLAG_ADVANCED
+ root.flags = Root.FLAG_LOCAL_ONLY
| Root.FLAG_SUPPORTS_SEARCH | Root.FLAG_SUPPORTS_IS_CHILD;
+ if (volume.isPrimary()) {
+ // save off the primary volume for subsequent "Home" dir initialization.
+ primaryVolume = volume;
+ root.flags |= Root.FLAG_ADVANCED;
+ }
// Dunno when this would NOT be the case, but never hurts to be correct.
if (volume.isMountedWritable()) {
root.flags |= Root.FLAG_SUPPORTS_CREATE;
diff --git a/packages/PrintSpooler/res/values-ca/arrays.xml b/packages/PrintSpooler/res/values-ca/arrays.xml
deleted file mode 100644
index c1b149c..0000000
--- a/packages/PrintSpooler/res/values-ca/arrays.xml
+++ /dev/null
@@ -1,33 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2014 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.
--->
-<resources>
-
- <string-array name="pdf_printer_media_sizes" translatable="false">
- <item>NA_LETTER</item>
- <item>NA_GOVT_LETTER</item>
- <item>NA_LEGAL</item>
- <item>NA_JUNIOR_LEGAL</item>
- <item>NA_LEDGER</item>
- <item>NA_TABLOID</item>
- <item>NA_INDEX_3X5</item>
- <item>NA_INDEX_4X6</item>
- <item>NA_INDEX_5X8</item>
- <item>NA_MONARCH</item>
- <item>NA_QUARTO</item>
- <item>NA_FOOLSCAP</item>
- </string-array>
-
-</resources>
diff --git a/packages/PrintSpooler/res/values-en-rCA/arrays.xml b/packages/PrintSpooler/res/values-en-rCA/arrays.xml
deleted file mode 100644
index d40278c..0000000
--- a/packages/PrintSpooler/res/values-en-rCA/arrays.xml
+++ /dev/null
@@ -1,33 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2013 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.
--->
-<resources>
-
- <string-array name="pdf_printer_media_sizes" translatable="false">
- <item>NA_LETTER</item>
- <item>NA_GOVT_LETTER</item>
- <item>NA_LEGAL</item>
- <item>NA_JUNIOR_LEGAL</item>
- <item>NA_LEDGER</item>
- <item>NA_TABLOID</item>
- <item>NA_INDEX_3X5</item>
- <item>NA_INDEX_4X6</item>
- <item>NA_INDEX_5X8</item>
- <item>NA_MONARCH</item>
- <item>NA_QUARTO</item>
- <item>NA_FOOLSCAP</item>
- </string-array>
-
-</resources>
diff --git a/packages/PrintSpooler/res/values-en-rUS/arrays.xml b/packages/PrintSpooler/res/values-en-rUS/arrays.xml
deleted file mode 100644
index d40278c..0000000
--- a/packages/PrintSpooler/res/values-en-rUS/arrays.xml
+++ /dev/null
@@ -1,33 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2013 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.
--->
-<resources>
-
- <string-array name="pdf_printer_media_sizes" translatable="false">
- <item>NA_LETTER</item>
- <item>NA_GOVT_LETTER</item>
- <item>NA_LEGAL</item>
- <item>NA_JUNIOR_LEGAL</item>
- <item>NA_LEDGER</item>
- <item>NA_TABLOID</item>
- <item>NA_INDEX_3X5</item>
- <item>NA_INDEX_4X6</item>
- <item>NA_INDEX_5X8</item>
- <item>NA_MONARCH</item>
- <item>NA_QUARTO</item>
- <item>NA_FOOLSCAP</item>
- </string-array>
-
-</resources>
diff --git a/packages/PrintSpooler/res/values-es-rUS/arrays.xml b/packages/PrintSpooler/res/values-es-rUS/arrays.xml
deleted file mode 100644
index c1b149c..0000000
--- a/packages/PrintSpooler/res/values-es-rUS/arrays.xml
+++ /dev/null
@@ -1,33 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2014 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.
--->
-<resources>
-
- <string-array name="pdf_printer_media_sizes" translatable="false">
- <item>NA_LETTER</item>
- <item>NA_GOVT_LETTER</item>
- <item>NA_LEGAL</item>
- <item>NA_JUNIOR_LEGAL</item>
- <item>NA_LEDGER</item>
- <item>NA_TABLOID</item>
- <item>NA_INDEX_3X5</item>
- <item>NA_INDEX_4X6</item>
- <item>NA_INDEX_5X8</item>
- <item>NA_MONARCH</item>
- <item>NA_QUARTO</item>
- <item>NA_FOOLSCAP</item>
- </string-array>
-
-</resources>
diff --git a/packages/PrintSpooler/res/values-ja/arrays.xml b/packages/PrintSpooler/res/values-ja/arrays.xml
deleted file mode 100644
index 3187cbe..0000000
--- a/packages/PrintSpooler/res/values-ja/arrays.xml
+++ /dev/null
@@ -1,41 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2013 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.
--->
-<resources>
-
- <string-array name="pdf_printer_media_sizes" translatable="false">
- <item>JIS_B10</item>
- <item>JIS_B9</item>
- <item>JIS_B8</item>
- <item>JIS_B7</item>
- <item>JIS_B6</item>
- <item>JIS_B5</item>
- <item>JIS_B4</item>
- <item>JIS_B3</item>
- <item>JIS_B2</item>
- <item>JIS_B1</item>
- <item>JIS_B0</item>
- <item>JIS_EXEC</item>
- <item>JPN_CHOU4</item>
- <item>JPN_CHOU3</item>
- <item>JPN_CHOU2</item>
- <item>JPN_HAGAKI</item>
- <item>JPN_OUFUKU</item>
- <item>JPN_KAHU</item>
- <item>JPN_KAKU2</item>
- <item>JPN_YOU4</item>
- </string-array>
-
-</resources>
diff --git a/packages/PrintSpooler/res/values/arrays.xml b/packages/PrintSpooler/res/values/arrays.xml
index afe3c71..8658be4 100644
--- a/packages/PrintSpooler/res/values/arrays.xml
+++ b/packages/PrintSpooler/res/values/arrays.xml
@@ -121,38 +121,4 @@
<!-- Everything else is ISO -->
</string-array>
- <string-array name="pdf_printer_media_sizes" translatable="false">
- <item>ISO_A0</item>
- <item>ISO_A1</item>
- <item>ISO_A2</item>
- <item>ISO_A3</item>
- <item>ISO_A4</item>
- <item>ISO_A5</item>
- <item>ISO_A6</item>
- <item>ISO_A7</item>
- <item>ISO_A8</item>
- <item>ISO_A9</item>
- <item>ISO_A10</item>
- <item>ISO_B1</item>
- <item>ISO_B2</item>
- <item>ISO_B3</item>
- <item>ISO_B4</item>
- <item>ISO_B5</item>
- <item>ISO_B6</item>
- <item>ISO_B7</item>
- <item>ISO_B8</item>
- <item>ISO_B9</item>
- <item>ISO_B10</item>
- <item>ISO_C1</item>
- <item>ISO_C2</item>
- <item>ISO_C3</item>
- <item>ISO_C4</item>
- <item>ISO_C5</item>
- <item>ISO_C6</item>
- <item>ISO_C7</item>
- <item>ISO_C8</item>
- <item>ISO_C9</item>
- <item>ISO_C10</item>
- </string-array>
-
</resources>
diff --git a/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java b/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java
index 1e79121..a1ea658 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java
@@ -61,6 +61,7 @@
import android.text.TextUtils.SimpleStringSplitter;
import android.text.TextWatcher;
import android.util.ArrayMap;
+import android.util.ArraySet;
import android.util.Log;
import android.util.TypedValue;
import android.view.KeyEvent;
@@ -2361,6 +2362,7 @@
}
private PrinterInfo createFakePdfPrinter() {
+ ArraySet<MediaSize> allMediaSizes = MediaSize.getAllPredefinedSizes();
MediaSize defaultMediaSize = MediaSizeUtils.getDefault(PrintActivity.this);
PrinterId printerId = new PrinterId(getComponentName(), "PDF printer");
@@ -2368,11 +2370,9 @@
PrinterCapabilitiesInfo.Builder builder =
new PrinterCapabilitiesInfo.Builder(printerId);
- String[] mediaSizeIds = getResources().getStringArray(R.array.pdf_printer_media_sizes);
- final int mediaSizeIdCount = mediaSizeIds.length;
- for (int i = 0; i < mediaSizeIdCount; i++) {
- String id = mediaSizeIds[i];
- MediaSize mediaSize = MediaSize.getStandardMediaSizeById(id);
+ final int mediaSizeCount = allMediaSizes.size();
+ for (int i = 0; i < mediaSizeCount; i++) {
+ MediaSize mediaSize = allMediaSizes.valueAt(i);
builder.addMediaSize(mediaSize, mediaSize.equals(defaultMediaSize));
}
diff --git a/packages/PrintSpooler/src/com/android/printspooler/ui/SelectPrinterActivity.java b/packages/PrintSpooler/src/com/android/printspooler/ui/SelectPrinterActivity.java
index 4e3932f..4f7624a 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/ui/SelectPrinterActivity.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/ui/SelectPrinterActivity.java
@@ -546,7 +546,10 @@
final int printerCount = mPrinters.size();
for (int i = 0; i < printerCount; i++) {
PrinterInfo printer = mPrinters.get(i);
- if (printer.getName().toLowerCase().contains(constraintLowerCase)) {
+ String description = printer.getDescription();
+ if (printer.getName().toLowerCase().contains(constraintLowerCase)
+ || description != null && description.toLowerCase()
+ .contains(constraintLowerCase)) {
filteredPrinters.add(printer);
}
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/drawer/SettingsDrawerActivity.java b/packages/SettingsLib/src/com/android/settingslib/drawer/SettingsDrawerActivity.java
index 56c4edb..5ffa581 100644
--- a/packages/SettingsLib/src/com/android/settingslib/drawer/SettingsDrawerActivity.java
+++ b/packages/SettingsLib/src/com/android/settingslib/drawer/SettingsDrawerActivity.java
@@ -141,6 +141,16 @@
mCategoryListeners.remove(listener);
}
+ public void setIsDrawerPresent(boolean isPresent) {
+ if (isPresent) {
+ mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout);
+ updateDrawer();
+ } else {
+ mDrawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED);
+ mDrawerLayout = null;
+ }
+ }
+
public void openDrawer() {
if (mDrawerLayout != null) {
mDrawerLayout.openDrawer(Gravity.START);
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index ebe0d97..e8df01b 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -159,18 +159,6 @@
in from the bottom of the screen. -->
<integer name="recents_enter_from_home_transition_duration">100</integer>
- <!-- The duration for animating the task from the bottom of the screen when transitioning
- from home. -->
- <integer name="recents_task_enter_from_home_duration">225</integer>
-
- <!-- The stagger for each task when animating the task from the bottom of the screen when
- transitioning from home. -->
- <integer name="recents_task_enter_from_home_stagger_delay">12</integer>
-
- <!-- The duration of the animation of the tasks to the bottom of the screen when leaving
- Recents to go back to the Launcher. -->
- <integer name="recents_task_exit_to_home_duration">225</integer>
-
<!-- The min animation duration for animating the nav bar scrim in. -->
<integer name="recents_nav_bar_scrim_enter_duration">400</integer>
diff --git a/packages/SystemUI/src/com/android/systemui/recents/history/RecentsHistoryAdapter.java b/packages/SystemUI/src/com/android/systemui/recents/history/RecentsHistoryAdapter.java
index e288878..ee3eb02 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/history/RecentsHistoryAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/history/RecentsHistoryAdapter.java
@@ -39,7 +39,7 @@
import com.android.systemui.recents.model.RecentsTaskLoader;
import com.android.systemui.recents.model.Task;
import com.android.systemui.recents.model.TaskStack;
-import com.android.systemui.recents.views.TaskViewAnimation;
+import com.android.systemui.recents.views.AnimationProps;
import java.util.ArrayList;
import java.util.Calendar;
@@ -224,7 +224,7 @@
if (row.getViewType() == TASK_ROW_VIEW_TYPE) {
TaskRow taskRow = (TaskRow) row;
Task task = taskRow.task;
- mStack.removeTask(task, TaskViewAnimation.IMMEDIATE);
+ mStack.removeTask(task, AnimationProps.IMMEDIATE);
EventBus.getDefault().send(new DeleteTaskDataEvent(task));
i = removeTaskRow(i);
}
@@ -304,7 +304,7 @@
public void onTaskRemoved(Task task, int position) {
// Since this is removed from the history, we need to update the stack as well to ensure
// that the model is correct. Since the stack is hidden, we can update it immediately.
- mStack.removeTask(task, TaskViewAnimation.IMMEDIATE);
+ mStack.removeTask(task, AnimationProps.IMMEDIATE);
removeTaskRow(position);
if (mRows.isEmpty()) {
dismissHistory();
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java b/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java
index aa8efa7..1f91dce 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java
@@ -43,7 +43,7 @@
import com.android.systemui.recents.misc.SystemServicesProxy;
import com.android.systemui.recents.misc.Utilities;
import com.android.systemui.recents.views.DropTarget;
-import com.android.systemui.recents.views.TaskViewAnimation;
+import com.android.systemui.recents.views.AnimationProps;
import java.util.ArrayList;
import java.util.Collections;
@@ -228,12 +228,12 @@
* Notifies when a task has been removed from the stack.
*/
void onStackTaskRemoved(TaskStack stack, Task removedTask, boolean wasFrontMostTask,
- Task newFrontMostTask, TaskViewAnimation animation);
+ Task newFrontMostTask, AnimationProps animation);
/**
* Notifies when a task has been removed from the history.
*/
- void onHistoryTaskRemoved(TaskStack stack, Task removedTask, TaskViewAnimation animation);
+ void onHistoryTaskRemoved(TaskStack stack, Task removedTask, AnimationProps animation);
}
/**
@@ -531,7 +531,7 @@
* Removes a task from the stack, with an additional {@param animation} hint to the callbacks on
* how they should update themselves.
*/
- public void removeTask(Task t, TaskViewAnimation animation) {
+ public void removeTask(Task t, AnimationProps animation) {
if (mStackTaskList.contains(t)) {
boolean wasFrontMostTask = (getStackFrontMostTask(false /* includeFreeform */) == t);
removeTaskImpl(mStackTaskList, t);
@@ -575,7 +575,7 @@
if (!newTasksMap.containsKey(task.key)) {
if (notifyStackChanges) {
mCb.onStackTaskRemoved(this, task, i == (taskCount - 1), null,
- TaskViewAnimation.IMMEDIATE);
+ AnimationProps.IMMEDIATE);
}
}
task.setGroup(null);
diff --git a/packages/SystemUI/src/com/android/systemui/recents/tv/views/TaskStackHorizontalGridView.java b/packages/SystemUI/src/com/android/systemui/recents/tv/views/TaskStackHorizontalGridView.java
index 2d41742..58ec852 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/tv/views/TaskStackHorizontalGridView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/tv/views/TaskStackHorizontalGridView.java
@@ -28,7 +28,7 @@
import com.android.systemui.recents.model.Task;
import com.android.systemui.recents.model.TaskStack;
import com.android.systemui.recents.model.TaskStack.TaskStackCallbacks;
-import com.android.systemui.recents.views.TaskViewAnimation;
+import com.android.systemui.recents.views.AnimationProps;
import java.util.ArrayList;
import java.util.List;
@@ -137,7 +137,7 @@
@Override
public void onStackTaskRemoved(TaskStack stack, Task removedTask, boolean wasFrontMostTask,
- Task newFrontMostTask, TaskViewAnimation animation) {
+ Task newFrontMostTask, AnimationProps animation) {
getAdapter().notifyItemRemoved(stack.getStackTasks().indexOf(removedTask));
if (mFocusedTask == removedTask) {
resetFocusedTask(removedTask);
@@ -152,7 +152,7 @@
}
@Override
- public void onHistoryTaskRemoved(TaskStack stack, Task removedTask, TaskViewAnimation animation) {
+ public void onHistoryTaskRemoved(TaskStack stack, Task removedTask, AnimationProps animation) {
//No history task on tv
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/AnimationProps.java b/packages/SystemUI/src/com/android/systemui/recents/views/AnimationProps.java
new file mode 100644
index 0000000..93878c52
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/AnimationProps.java
@@ -0,0 +1,227 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.recents.views;
+
+import android.animation.Animator;
+import android.animation.AnimatorSet;
+import android.annotation.IntDef;
+import android.util.SparseArray;
+import android.util.SparseLongArray;
+import android.view.View;
+import android.view.animation.Interpolator;
+
+import com.android.systemui.Interpolators;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.List;
+
+/**
+ * The generic set of animation properties to animate a {@link View}. The animation can have
+ * different interpolators, start delays and durations for each of the different properties.
+ */
+public class AnimationProps {
+
+ public static final AnimationProps IMMEDIATE = new AnimationProps(0, Interpolators.LINEAR);
+
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({ALL, TRANSLATION_X, TRANSLATION_Y, TRANSLATION_Z, ALPHA, SCALE, BOUNDS})
+ public @interface PropType {}
+
+ public static final int ALL = 0;
+ public static final int TRANSLATION_X = 1;
+ public static final int TRANSLATION_Y = 2;
+ public static final int TRANSLATION_Z = 3;
+ public static final int ALPHA = 4;
+ public static final int SCALE = 5;
+ public static final int BOUNDS = 6;
+
+ private SparseLongArray mPropStartDelay;
+ private SparseLongArray mPropDuration;
+ private SparseArray<Interpolator> mPropInterpolators;
+ private Animator.AnimatorListener mListener;
+
+ /**
+ * The builder constructor.
+ */
+ public AnimationProps() {}
+
+ /**
+ * Creates an animation with a default {@param duration} and {@param interpolator} for all
+ * properties in this animation.
+ */
+ public AnimationProps(int duration, Interpolator interpolator) {
+ this(0, duration, interpolator, null);
+ }
+
+ /**
+ * Creates an animation with a default {@param duration} and {@param interpolator} for all
+ * properties in this animation.
+ */
+ public AnimationProps(int duration, Interpolator interpolator,
+ Animator.AnimatorListener listener) {
+ this(0, duration, interpolator, listener);
+ }
+
+ /**
+ * Creates an animation with a default {@param startDelay}, {@param duration} and
+ * {@param interpolator} for all properties in this animation.
+ */
+ public AnimationProps(int startDelay, int duration, Interpolator interpolator) {
+ this(startDelay, duration, interpolator, null);
+ }
+
+ /**
+ * Creates an animation with a default {@param startDelay}, {@param duration} and
+ * {@param interpolator} for all properties in this animation.
+ */
+ public AnimationProps(int startDelay, int duration, Interpolator interpolator,
+ Animator.AnimatorListener listener) {
+ setStartDelay(ALL, startDelay);
+ setDuration(ALL, duration);
+ setInterpolator(ALL, interpolator);
+ setListener(listener);
+ }
+
+ /**
+ * Creates a new {@link AnimatorSet} that will animate the given animators. Callers need to
+ * manually apply the individual animation properties for each of the animators respectively.
+ */
+ public AnimatorSet createAnimator(List<Animator> animators) {
+ AnimatorSet anim = new AnimatorSet();
+ if (mListener != null) {
+ anim.addListener(mListener);
+ }
+ anim.playTogether(animators);
+ return anim;
+ }
+
+ /**
+ * Applies the specific start delay, duration and interpolator to the given {@param animator}
+ * for the specified {@param propertyType}.
+ */
+ public <T extends Animator> T apply(@PropType int propertyType, T animator) {
+ animator.setStartDelay(getStartDelay(propertyType));
+ animator.setDuration(getDuration(propertyType));
+ animator.setInterpolator(getInterpolator(propertyType));
+ return animator;
+ }
+
+ /**
+ * Sets a start delay for a specific property.
+ */
+ public AnimationProps setStartDelay(@PropType int propertyType, int startDelay) {
+ if (mPropStartDelay == null) {
+ mPropStartDelay = new SparseLongArray();
+ }
+ mPropStartDelay.append(propertyType, startDelay);
+ return this;
+ }
+
+ /**
+ * Returns the start delay for a specific property.
+ */
+ public long getStartDelay(@PropType int propertyType) {
+ if (mPropStartDelay != null) {
+ long startDelay = mPropStartDelay.get(propertyType, -1);
+ if (startDelay != -1) {
+ return startDelay;
+ }
+ return mPropStartDelay.get(ALL, 0);
+ }
+ return 0;
+ }
+
+ /**
+ * Sets a duration for a specific property.
+ */
+ public AnimationProps setDuration(@PropType int propertyType, int duration) {
+ if (mPropDuration == null) {
+ mPropDuration = new SparseLongArray();
+ }
+ mPropDuration.append(propertyType, duration);
+ return this;
+ }
+
+ /**
+ * Returns the duration for a specific property.
+ */
+ public long getDuration(@PropType int propertyType) {
+ if (mPropDuration != null) {
+ long duration = mPropDuration.get(propertyType, -1);
+ if (duration != -1) {
+ return duration;
+ }
+ return mPropDuration.get(ALL, 0);
+ }
+ return 0;
+ }
+
+ /**
+ * Sets an interpolator for a specific property.
+ */
+ public AnimationProps setInterpolator(@PropType int propertyType, Interpolator interpolator) {
+ if (mPropInterpolators == null) {
+ mPropInterpolators = new SparseArray<>();
+ }
+ mPropInterpolators.append(propertyType, interpolator);
+ return this;
+ }
+
+ /**
+ * Returns the interpolator for a specific property, falling back to the general interpolator
+ * if there is no specific property interpolator.
+ */
+ public Interpolator getInterpolator(@PropType int propertyType) {
+ if (mPropInterpolators != null) {
+ Interpolator interp = mPropInterpolators.get(propertyType);
+ if (interp != null) {
+ return interp;
+ }
+ return mPropInterpolators.get(ALL, Interpolators.LINEAR);
+ }
+ return Interpolators.LINEAR;
+ }
+
+ /**
+ * Sets an animator listener for this animation.
+ */
+ public AnimationProps setListener(Animator.AnimatorListener listener) {
+ mListener = listener;
+ return this;
+ }
+
+ /**
+ * Returns the animator listener for this animation.
+ */
+ public Animator.AnimatorListener getListener() {
+ return mListener;
+ }
+
+ /**
+ * Returns whether this animation has any duration.
+ */
+ public boolean isImmediate() {
+ int count = mPropDuration.size();
+ for (int i = 0; i < count; i++) {
+ if (mPropDuration.valueAt(i) > 0) {
+ return false;
+ }
+ }
+ return true;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
index e2ff52c..5e113b9 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
@@ -497,8 +497,9 @@
}
@Override
- protected void dispatchDraw(Canvas canvas) {
- super.dispatchDraw(canvas);
+ public void onDrawForeground(Canvas canvas) {
+ super.onDrawForeground(canvas);
+
ArrayList<TaskStack.DockState> visDockStates = mTouchHandler.getVisibleDockStates();
for (int i = visDockStates.size() - 1; i >= 0; i--) {
Drawable d = visDockStates.get(i).viewState.dockAreaOverlay;
@@ -530,8 +531,7 @@
public final void onBusEvent(DismissRecentsToHomeAnimationStarted event) {
// Hide the history button
- int taskViewExitToHomeDuration = getResources().getInteger(
- R.integer.recents_task_exit_to_home_duration);
+ int taskViewExitToHomeDuration = TaskStackAnimationHelper.EXIT_TO_HOME_TRANSLATION_DURATION;
hideHistoryButton(taskViewExitToHomeDuration, false /* translate */);
animateBackgroundScrim(0f, taskViewExitToHomeDuration);
}
@@ -588,7 +588,7 @@
tmpTransform.scale = 1f;
tmpTransform.rect.set(taskViewRect);
mTaskStackView.updateTaskViewToTransform(event.taskView, tmpTransform,
- new TaskViewAnimation(125, Interpolators.ALPHA_OUT,
+ new AnimationProps(125, Interpolators.ALPHA_OUT,
new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
@@ -598,7 +598,7 @@
event.task.key.id, dockState.createMode);
// Animate the stack accordingly
- TaskViewAnimation stackAnim = new TaskViewAnimation(
+ AnimationProps stackAnim = new AnimationProps(
TaskStackView.DEFAULT_SYNC_STACK_DURATION,
Interpolators.FAST_OUT_SLOW_IN);
mTaskStackView.getStack().removeTask(event.task, stackAnim);
@@ -646,9 +646,8 @@
public final void onBusEvent(EnterRecentsWindowAnimationCompletedEvent event) {
RecentsActivityLaunchState launchState = Recents.getConfiguration().getLaunchState();
if (!launchState.launchedFromAppWithThumbnail && mStack.getTaskCount() > 0) {
- int taskViewEnterFromHomeDuration = getResources().getInteger(
- R.integer.recents_task_enter_from_home_duration);
- animateBackgroundScrim(DEFAULT_SCRIM_ALPHA, taskViewEnterFromHomeDuration);
+ animateBackgroundScrim(DEFAULT_SCRIM_ALPHA,
+ TaskStackAnimationHelper.ENTER_FROM_HOME_TRANSLATION_DURATION);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackAnimationHelper.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackAnimationHelper.java
index 682c298..0eae183 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackAnimationHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackAnimationHelper.java
@@ -20,8 +20,10 @@
import android.animation.AnimatorListenerAdapter;
import android.content.Context;
import android.content.res.Resources;
+import android.graphics.Path;
import android.graphics.RectF;
import android.view.View;
+import android.view.animation.PathInterpolator;
import com.android.systemui.Interpolators;
import com.android.systemui.R;
@@ -63,6 +65,22 @@
ReferenceCountedTrigger postAnimationTrigger);
}
+ private static final int FRAME_OFFSET_MS = 16;
+
+ public static final int ENTER_FROM_HOME_ALPHA_DURATION = 100;
+ public static final int ENTER_FROM_HOME_TRANSLATION_DURATION = 333;
+ private static final PathInterpolator ENTER_FROM_HOME_TRANSLATION_INTERPOLATOR =
+ new PathInterpolator(0, 0, 0, 1f);
+ private static final PathInterpolator ENTER_FROM_HOME_ALPHA_INTERPOLATOR =
+ new PathInterpolator(0, 0, 0.2f, 1f);
+
+ public static final int EXIT_TO_HOME_ALPHA_DURATION = 100;
+ public static final int EXIT_TO_HOME_TRANSLATION_DURATION = 150;
+ private static final PathInterpolator EXIT_TO_HOME_TRANSLATION_INTERPOLATOR =
+ new PathInterpolator(0.8f, 0, 0.6f, 1f);
+ private static final PathInterpolator EXIT_TO_HOME_ALPHA_INTERPOLATOR =
+ new PathInterpolator(0.4f, 0, 1f, 1f);
+
private TaskStackView mStackView;
private TaskViewTransform mTmpTransform = new TaskViewTransform();
@@ -157,15 +175,12 @@
R.integer.recents_task_enter_from_app_duration);
int taskViewEnterFromAffiliatedAppDuration = res.getInteger(
R.integer.recents_task_enter_from_affiliated_app_duration);
- int taskViewEnterFromHomeDuration = res.getInteger(
- R.integer.recents_task_enter_from_home_duration);
- int taskViewEnterFromHomeStaggerDelay = res.getInteger(
- R.integer.recents_task_enter_from_home_stagger_delay);
// Create enter animations for each of the views from front to back
List<TaskView> taskViews = mStackView.getTaskViews();
int taskViewCount = taskViews.size();
for (int i = taskViewCount - 1; i >= 0; i--) {
+ int taskIndexFromFront = taskViewCount - i - 1;
final TaskView tv = taskViews.get(i);
Task task = tv.getTask();
boolean currentTaskOccludesLaunchTarget = false;
@@ -186,7 +201,7 @@
} else {
// Animate the task up if it was occluding the launch target
if (currentTaskOccludesLaunchTarget) {
- TaskViewAnimation taskAnimation = new TaskViewAnimation(
+ AnimationProps taskAnimation = new AnimationProps(
taskViewEnterFromAffiliatedAppDuration, Interpolators.ALPHA_IN,
new AnimatorListenerAdapter() {
@Override
@@ -202,14 +217,16 @@
} else if (launchState.launchedFromHome) {
// Animate the tasks up
- int frontIndex = (taskViewCount - i - 1);
- int delay = frontIndex * taskViewEnterFromHomeStaggerDelay;
- int duration = taskViewEnterFromHomeDuration +
- frontIndex * taskViewEnterFromHomeStaggerDelay;
-
- TaskViewAnimation taskAnimation = new TaskViewAnimation(delay,
- duration, Interpolators.DECELERATE_QUINT,
- postAnimationTrigger.decrementOnAnimationEnd());
+ AnimationProps taskAnimation = new AnimationProps()
+ .setStartDelay(AnimationProps.ALPHA, taskIndexFromFront * FRAME_OFFSET_MS)
+ .setDuration(AnimationProps.ALPHA, ENTER_FROM_HOME_ALPHA_DURATION)
+ .setDuration(AnimationProps.BOUNDS, ENTER_FROM_HOME_TRANSLATION_DURATION -
+ (taskIndexFromFront * FRAME_OFFSET_MS))
+ .setInterpolator(AnimationProps.BOUNDS,
+ ENTER_FROM_HOME_TRANSLATION_INTERPOLATOR)
+ .setInterpolator(AnimationProps.ALPHA,
+ ENTER_FROM_HOME_ALPHA_INTERPOLATOR)
+ .setListener(postAnimationTrigger.decrementOnAnimationEnd());
postAnimationTrigger.increment();
mStackView.updateTaskViewToTransform(tv, mTmpTransform, taskAnimation);
}
@@ -221,7 +238,6 @@
*/
public void startExitToHomeAnimation(boolean animated,
ReferenceCountedTrigger postAnimationTrigger) {
- Resources res = mStackView.getResources();
TaskStackLayoutAlgorithm stackLayout = mStackView.getStackAlgorithm();
TaskStackViewScroller stackScroller = mStackView.getScroller();
TaskStack stack = mStackView.getStack();
@@ -232,19 +248,32 @@
}
int offscreenY = stackLayout.mStackRect.bottom;
- int taskViewExitToHomeDuration = res.getInteger(
- R.integer.recents_task_exit_to_home_duration);
// Create the animations for each of the tasks
List<TaskView> taskViews = mStackView.getTaskViews();
int taskViewCount = taskViews.size();
for (int i = 0; i < taskViewCount; i++) {
+ int taskIndexFromFront = taskViewCount - i - 1;
TaskView tv = taskViews.get(i);
Task task = tv.getTask();
- TaskViewAnimation taskAnimation = new TaskViewAnimation(
- animated ? taskViewExitToHomeDuration : 0, Interpolators.FAST_OUT_LINEAR_IN,
- postAnimationTrigger.decrementOnAnimationEnd());
- postAnimationTrigger.increment();
+
+ // Animate the tasks down
+ AnimationProps taskAnimation;
+ if (animated) {
+ taskAnimation = new AnimationProps()
+ .setStartDelay(AnimationProps.ALPHA, i * FRAME_OFFSET_MS)
+ .setDuration(AnimationProps.ALPHA, EXIT_TO_HOME_ALPHA_DURATION)
+ .setDuration(AnimationProps.BOUNDS, EXIT_TO_HOME_TRANSLATION_DURATION +
+ (taskIndexFromFront * FRAME_OFFSET_MS))
+ .setInterpolator(AnimationProps.BOUNDS,
+ EXIT_TO_HOME_TRANSLATION_INTERPOLATOR)
+ .setInterpolator(AnimationProps.ALPHA,
+ EXIT_TO_HOME_ALPHA_INTERPOLATOR)
+ .setListener(postAnimationTrigger.decrementOnAnimationEnd());
+ postAnimationTrigger.increment();
+ } else {
+ taskAnimation = AnimationProps.IMMEDIATE;
+ }
stackLayout.getStackTransform(task, stackScroller.getStackScroll(), mTmpTransform,
null);
@@ -283,7 +312,7 @@
screenPinningRequested, postAnimationTrigger);
} else if (currentTaskOccludesLaunchTarget) {
// Animate this task out of view
- TaskViewAnimation taskAnimation = new TaskViewAnimation(
+ AnimationProps taskAnimation = new AnimationProps(
taskViewExitToAppDuration, Interpolators.ALPHA_OUT,
postAnimationTrigger.decrementOnAnimationEnd());
postAnimationTrigger.increment();
@@ -315,7 +344,7 @@
deleteTaskView.setClipViewInStack(false);
// Compose the new animation and transform and star the animation
- TaskViewAnimation taskAnimation = new TaskViewAnimation(taskViewRemoveAnimDuration,
+ AnimationProps taskAnimation = new AnimationProps(taskViewRemoveAnimDuration,
Interpolators.ALPHA_OUT, new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
@@ -352,7 +381,7 @@
for (int i = taskViewCount - 1; i >= 0; i--) {
TaskView tv = taskViews.get(i);
Task task = tv.getTask();
- TaskViewAnimation taskAnimation = new TaskViewAnimation(startDelayIncr * i,
+ AnimationProps taskAnimation = new AnimationProps(startDelayIncr * i,
historyTransitionDuration, Interpolators.FAST_OUT_SLOW_IN,
postAnimationTrigger.decrementOnAnimationEnd());
postAnimationTrigger.increment();
@@ -381,7 +410,7 @@
int taskViewCount = taskViews.size();
for (int i = taskViewCount - 1; i >= 0; i--) {
TaskView tv = taskViews.get(i);
- TaskViewAnimation taskAnimation = new TaskViewAnimation(startDelayIncr * i,
+ AnimationProps taskAnimation = new AnimationProps(startDelayIncr * i,
historyTransitionDuration, Interpolators.FAST_OUT_SLOW_IN);
stackLayout.getStackTransform(tv.getTask(), stackScroller.getStackScroll(),
mTmpTransform, null);
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
index 232b416..1c97b5a 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
@@ -16,6 +16,10 @@
package com.android.systemui.recents.views;
+import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID;
+import static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID;
+import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
+
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.content.ComponentName;
@@ -34,9 +38,9 @@
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
+import android.view.ViewGroup;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo;
-import android.view.animation.Interpolator;
import android.widget.FrameLayout;
import com.android.systemui.Interpolators;
@@ -85,10 +89,6 @@
import java.util.ArrayList;
import java.util.List;
-import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID;
-import static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID;
-import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
-
/* The visual representation of a task stack view */
public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCallbacks,
@@ -124,7 +124,7 @@
ArrayList<TaskView> mTaskViews = new ArrayList<>();
ArrayList<TaskViewTransform> mCurrentTaskTransforms = new ArrayList<>();
ArraySet<Task.TaskKey> mIgnoreTasks = new ArraySet<>();
- TaskViewAnimation mDeferredTaskViewLayoutAnimation = null;
+ AnimationProps mDeferredTaskViewLayoutAnimation = null;
DozeTrigger mUIDozeTrigger;
Task mFocusedTask;
@@ -135,6 +135,7 @@
boolean mTaskViewsClipDirty = true;
boolean mAwaitingFirstLayout = true;
+ boolean mInMeasureLayout = false;
boolean mEnterAnimationComplete = false;
boolean mTouchExplorationEnabled;
boolean mScreenPinningEnabled;
@@ -537,17 +538,17 @@
if (tv == null) {
tv = mViewPool.pickUpViewFromPool(task, task);
if (task.isFreeformTask()) {
- tv.updateViewPropertiesToTaskTransform(transform, TaskViewAnimation.IMMEDIATE,
+ tv.updateViewPropertiesToTaskTransform(transform, AnimationProps.IMMEDIATE,
mRequestUpdateClippingListener);
} else {
if (Float.compare(transform.p, 0f) <= 0) {
tv.updateViewPropertiesToTaskTransform(
mLayoutAlgorithm.getBackOfStackTransform(),
- TaskViewAnimation.IMMEDIATE, mRequestUpdateClippingListener);
+ AnimationProps.IMMEDIATE, mRequestUpdateClippingListener);
} else {
tv.updateViewPropertiesToTaskTransform(
mLayoutAlgorithm.getFrontOfStackTransform(),
- TaskViewAnimation.IMMEDIATE, mRequestUpdateClippingListener);
+ AnimationProps.IMMEDIATE, mRequestUpdateClippingListener);
}
}
} else {
@@ -580,9 +581,9 @@
* animations that are current running on those task views, and will ensure that the children
* {@link TaskView}s will match the set of visible tasks in the stack.
*
- * @see #relayoutTaskViews(TaskViewAnimation, ArraySet<Task.TaskKey>)
+ * @see #relayoutTaskViews(AnimationProps, ArraySet<Task.TaskKey>)
*/
- void relayoutTaskViews(TaskViewAnimation animation) {
+ void relayoutTaskViews(AnimationProps animation) {
relayoutTaskViews(animation, mIgnoreTasks);
}
@@ -594,7 +595,7 @@
*
* @param ignoreTasksSet the set of tasks to ignore in the relayout
*/
- void relayoutTaskViews(TaskViewAnimation animation, ArraySet<Task.TaskKey> ignoreTasksSet) {
+ void relayoutTaskViews(AnimationProps animation, ArraySet<Task.TaskKey> ignoreTasksSet) {
// If we had a deferred animation, cancel that
mDeferredTaskViewLayoutAnimation = null;
@@ -623,17 +624,17 @@
/**
* Posts an update to synchronize the {@link TaskView}s with the stack on the next frame.
*/
- void relayoutTaskViewsOnNextFrame(TaskViewAnimation animation) {
+ void relayoutTaskViewsOnNextFrame(AnimationProps animation) {
mDeferredTaskViewLayoutAnimation = animation;
invalidate();
}
/**
* Called to update a specific {@link TaskView} to a given {@link TaskViewTransform} with a
- * given set of {@link TaskViewAnimation} properties.
+ * given set of {@link AnimationProps} properties.
*/
public void updateTaskViewToTransform(TaskView taskView, TaskViewTransform transform,
- TaskViewAnimation animation) {
+ AnimationProps animation) {
taskView.updateViewPropertiesToTaskTransform(transform, animation,
mRequestUpdateClippingListener);
}
@@ -1137,6 +1138,7 @@
*/
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ mInMeasureLayout = true;
int width = MeasureSpec.getSize(widthMeasureSpec);
int height = MeasureSpec.getSize(heightMeasureSpec);
@@ -1159,22 +1161,29 @@
mTmpTaskViews.addAll(mViewPool.getViews());
int taskViewCount = mTmpTaskViews.size();
for (int i = 0; i < taskViewCount; i++) {
- TaskView tv = mTmpTaskViews.get(i);
- if (tv.getBackground() != null) {
- tv.getBackground().getPadding(mTmpRect);
- } else {
- mTmpRect.setEmpty();
- }
- tv.measure(
- MeasureSpec.makeMeasureSpec(
- mLayoutAlgorithm.mTaskRect.width() + mTmpRect.left + mTmpRect.right,
- MeasureSpec.EXACTLY),
- MeasureSpec.makeMeasureSpec(
- mLayoutAlgorithm.mTaskRect.height() + mTmpRect.top + mTmpRect.bottom,
- MeasureSpec.EXACTLY));
+ measureTaskView(mTmpTaskViews.get(i));
}
setMeasuredDimension(width, height);
+ mInMeasureLayout = false;
+ }
+
+ /**
+ * Measures a TaskView.
+ */
+ private void measureTaskView(TaskView tv) {
+ if (tv.getBackground() != null) {
+ tv.getBackground().getPadding(mTmpRect);
+ } else {
+ mTmpRect.setEmpty();
+ }
+ tv.measure(
+ MeasureSpec.makeMeasureSpec(
+ mLayoutAlgorithm.mTaskRect.width() + mTmpRect.left + mTmpRect.right,
+ MeasureSpec.EXACTLY),
+ MeasureSpec.makeMeasureSpec(
+ mLayoutAlgorithm.mTaskRect.height() + mTmpRect.top + mTmpRect.bottom,
+ MeasureSpec.EXACTLY));
}
/**
@@ -1190,15 +1199,7 @@
mTmpTaskViews.addAll(mViewPool.getViews());
int taskViewCount = mTmpTaskViews.size();
for (int i = 0; i < taskViewCount; i++) {
- TaskView tv = mTmpTaskViews.get(i);
- if (tv.getBackground() != null) {
- tv.getBackground().getPadding(mTmpRect);
- } else {
- mTmpRect.setEmpty();
- }
- Rect taskRect = mLayoutAlgorithm.mTaskRect;
- tv.layout(taskRect.left - mTmpRect.left, taskRect.top - mTmpRect.top,
- taskRect.right + mTmpRect.right, taskRect.bottom + mTmpRect.bottom);
+ layoutTaskView(mTmpTaskViews.get(i));
}
if (changed) {
@@ -1207,29 +1208,41 @@
}
}
// Relayout all of the task views including the ignored ones
- relayoutTaskViews(TaskViewAnimation.IMMEDIATE, EMPTY_TASK_SET);
+ relayoutTaskViews(AnimationProps.IMMEDIATE, EMPTY_TASK_SET);
clipTaskViews();
if (mAwaitingFirstLayout || !mEnterAnimationComplete) {
mAwaitingFirstLayout = false;
onFirstLayout();
- return;
}
}
+ /**
+ * Lays out a TaskView.
+ */
+ private void layoutTaskView(TaskView tv) {
+ if (tv.getBackground() != null) {
+ tv.getBackground().getPadding(mTmpRect);
+ } else {
+ mTmpRect.setEmpty();
+ }
+ Rect taskRect = mLayoutAlgorithm.mTaskRect;
+ tv.layout(taskRect.left - mTmpRect.left, taskRect.top - mTmpRect.top,
+ taskRect.right + mTmpRect.right, taskRect.bottom + mTmpRect.bottom);
+ }
+
/** Handler for the first layout. */
void onFirstLayout() {
// Setup the view for the enter animation
mAnimationHelper.prepareForEnterAnimation();
// Animate in the freeform workspace
- animateFreeformWorkspaceBackgroundAlpha(
- mLayoutAlgorithm.getStackState().freeformBackgroundAlpha, 150,
- Interpolators.FAST_OUT_SLOW_IN);
+ int ffBgAlpha = mLayoutAlgorithm.getStackState().freeformBackgroundAlpha;
+ animateFreeformWorkspaceBackgroundAlpha(ffBgAlpha, new AnimationProps(150,
+ Interpolators.FAST_OUT_SLOW_IN));
// Set the task focused state without requesting view focus, and leave the focus animations
// until after the enter-animation
- Task launchTask = mStack.getLaunchTarget();
RecentsConfiguration config = Recents.getConfiguration();
RecentsActivityLaunchState launchState = config.getLaunchState();
int focusedTaskIndex = launchState.getInitialFocusTaskIndex(mStack.getTaskCount());
@@ -1274,7 +1287,9 @@
}
@Override
- protected void dispatchDraw(Canvas canvas) {
+ protected void onDraw(Canvas canvas) {
+ super.onDraw(canvas);
+
// Draw the freeform workspace background
SystemServicesProxy ssp = Recents.getSystemServices();
if (ssp.hasFreeformWorkspaceSupport()) {
@@ -1282,8 +1297,6 @@
mFreeformWorkspaceBackground.draw(canvas);
}
}
-
- super.dispatchDraw(canvas);
}
@Override
@@ -1318,7 +1331,7 @@
updateLayoutAlgorithm(true /* boundScroll */);
// Animate all the tasks into place
- relayoutTaskViews(new TaskViewAnimation(DEFAULT_SYNC_STACK_DURATION,
+ relayoutTaskViews(new AnimationProps(DEFAULT_SYNC_STACK_DURATION,
Interpolators.FAST_OUT_SLOW_IN));
}
@@ -1327,7 +1340,7 @@
*/
@Override
public void onStackTaskRemoved(TaskStack stack, Task removedTask, boolean wasFrontMostTask,
- Task newFrontMostTask, TaskViewAnimation animation) {
+ Task newFrontMostTask, AnimationProps animation) {
if (mFocusedTask == removedTask) {
resetFocusedTask(removedTask);
}
@@ -1364,7 +1377,7 @@
@Override
public void onHistoryTaskRemoved(TaskStack stack, Task removedTask,
- TaskViewAnimation animation) {
+ AnimationProps animation) {
// To be implemented
}
@@ -1404,7 +1417,21 @@
// Add/attach the view to the hierarchy
if (isNewView) {
- addView(tv, insertIndex);
+ if (mInMeasureLayout) {
+ // If we are measuring the layout, then just add the view normally as it will be
+ // laid out during the layout pass
+ addView(tv, insertIndex);
+ } else {
+ // Otherwise, this is from a bindVisibleTaskViews() call outside the measure/layout
+ // pass, and we should layout the new child ourselves
+ ViewGroup.LayoutParams params = tv.getLayoutParams();
+ if (params == null) {
+ params = generateDefaultLayoutParams();
+ }
+ addViewInLayout(tv, insertIndex, params, true /* preventRequestLayout */);
+ measureTaskView(tv);
+ layoutTaskView(tv);
+ }
} else {
attachViewToParent(tv, insertIndex, tv.getLayoutParams());
}
@@ -1463,14 +1490,14 @@
public void onFocusStateChanged(float prevFocusState, float curFocusState) {
if (mDeferredTaskViewLayoutAnimation == null) {
mUIDozeTrigger.poke();
- relayoutTaskViewsOnNextFrame(TaskViewAnimation.IMMEDIATE);
+ relayoutTaskViewsOnNextFrame(AnimationProps.IMMEDIATE);
}
}
/**** TaskStackViewScroller.TaskStackViewScrollerCallbacks ****/
@Override
- public void onScrollChanged(float prevScroll, float curScroll, TaskViewAnimation animation) {
+ public void onScrollChanged(float prevScroll, float curScroll, AnimationProps animation) {
mUIDozeTrigger.poke();
if (animation != null) {
relayoutTaskViewsOnNextFrame(animation);
@@ -1506,7 +1533,7 @@
tv.dismissTask();
} else {
// Otherwise, remove the task from the stack immediately
- mStack.removeTask(t, TaskViewAnimation.IMMEDIATE);
+ mStack.removeTask(t, AnimationProps.IMMEDIATE);
}
}
}
@@ -1531,10 +1558,9 @@
mAnimationHelper.startExitToHomeAnimation(event.animated, event.getAnimationTrigger());
// Dismiss the freeform workspace background
- int taskViewExitToHomeDuration = getResources().getInteger(
- R.integer.recents_task_exit_to_home_duration);
- animateFreeformWorkspaceBackgroundAlpha(0, taskViewExitToHomeDuration,
- Interpolators.FAST_OUT_SLOW_IN);
+ int taskViewExitToHomeDuration = TaskStackAnimationHelper.EXIT_TO_HOME_TRANSLATION_DURATION;
+ animateFreeformWorkspaceBackgroundAlpha(0, new AnimationProps(taskViewExitToHomeDuration,
+ Interpolators.FAST_OUT_SLOW_IN));
}
public final void onBusEvent(DismissFocusedTaskViewEvent event) {
@@ -1587,6 +1613,9 @@
}
public final void onBusEvent(DragStartEvent event) {
+ // Ensure that the drag task is not animated
+ addIgnoreTask(event.task);
+
if (event.task.isFreeformTask()) {
// Animate to the front of the stack
mStackScroller.animateScroll(mLayoutAlgorithm.mInitialScrollP, null);
@@ -1599,7 +1628,7 @@
mTmpTransform.scale = finalScale;
mTmpTransform.translationZ = mLayoutAlgorithm.mMaxTranslationZ + 1;
updateTaskViewToTransform(event.taskView, mTmpTransform,
- new TaskViewAnimation(DRAG_SCALE_DURATION, Interpolators.FAST_OUT_SLOW_IN));
+ new AnimationProps(DRAG_SCALE_DURATION, Interpolators.FAST_OUT_SLOW_IN));
}
public final void onBusEvent(DragStartInitializeDropTargetsEvent event) {
@@ -1611,7 +1640,7 @@
}
public final void onBusEvent(DragDropTargetChangedEvent event) {
- TaskViewAnimation animation = new TaskViewAnimation(250, Interpolators.FAST_OUT_SLOW_IN);
+ AnimationProps animation = new AnimationProps(250, Interpolators.FAST_OUT_SLOW_IN);
if (event.dropTarget instanceof TaskStack.DockState) {
// Calculate the new task stack bounds that matches the window size that Recents will
// have after the drop
@@ -1683,10 +1712,10 @@
mLayoutAlgorithm.getStackTransform(event.task, getScroller().getStackScroll(),
mTmpTransform, null);
event.getAnimationTrigger().increment();
- relayoutTaskViews(new TaskViewAnimation(DEFAULT_SYNC_STACK_DURATION,
+ relayoutTaskViews(new AnimationProps(DEFAULT_SYNC_STACK_DURATION,
Interpolators.FAST_OUT_SLOW_IN));
updateTaskViewToTransform(event.taskView, mTmpTransform,
- new TaskViewAnimation(DEFAULT_SYNC_STACK_DURATION, Interpolators.FAST_OUT_SLOW_IN,
+ new AnimationProps(DEFAULT_SYNC_STACK_DURATION, Interpolators.FAST_OUT_SLOW_IN,
event.getAnimationTrigger().decrementOnAnimationEnd()));
removeIgnoreTask(event.task);
}
@@ -1797,15 +1826,15 @@
R.string.accessibility_recents_item_dismissed, task.title));
// Remove the task from the stack
- mStack.removeTask(task, new TaskViewAnimation(DEFAULT_SYNC_STACK_DURATION,
+ mStack.removeTask(task, new AnimationProps(DEFAULT_SYNC_STACK_DURATION,
Interpolators.FAST_OUT_SLOW_IN));
}
/**
* Starts an alpha animation on the freeform workspace background.
*/
- private void animateFreeformWorkspaceBackgroundAlpha(int targetAlpha, int duration,
- Interpolator interpolator) {
+ private void animateFreeformWorkspaceBackgroundAlpha(int targetAlpha,
+ AnimationProps animation) {
if (mFreeformWorkspaceBackground.getAlpha() == targetAlpha) {
return;
}
@@ -1813,8 +1842,12 @@
Utilities.cancelAnimationWithoutCallbacks(mFreeformWorkspaceBackgroundAnimator);
mFreeformWorkspaceBackgroundAnimator = ObjectAnimator.ofInt(mFreeformWorkspaceBackground,
Utilities.DRAWABLE_ALPHA, mFreeformWorkspaceBackground.getAlpha(), targetAlpha);
- mFreeformWorkspaceBackgroundAnimator.setDuration(duration);
- mFreeformWorkspaceBackgroundAnimator.setInterpolator(interpolator);
+ mFreeformWorkspaceBackgroundAnimator.setStartDelay(
+ animation.getDuration(AnimationProps.ALPHA));
+ mFreeformWorkspaceBackgroundAnimator.setDuration(
+ animation.getDuration(AnimationProps.ALPHA));
+ mFreeformWorkspaceBackgroundAnimator.setInterpolator(
+ animation.getInterpolator(AnimationProps.ALPHA));
mFreeformWorkspaceBackgroundAnimator.start();
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewScroller.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewScroller.java
index ced5d4b..c641d75 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewScroller.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewScroller.java
@@ -38,7 +38,7 @@
private static final boolean DEBUG = false;
public interface TaskStackViewScrollerCallbacks {
- void onScrollChanged(float prevScroll, float curScroll, TaskViewAnimation animation);
+ void onScrollChanged(float prevScroll, float curScroll, AnimationProps animation);
}
/**
@@ -93,14 +93,14 @@
* Sets the current stack scroll immediately.
*/
public void setStackScroll(float s) {
- setStackScroll(s, TaskViewAnimation.IMMEDIATE);
+ setStackScroll(s, AnimationProps.IMMEDIATE);
}
/**
* Sets the current stack scroll, but indicates to the callback the preferred animation to
* update to this new scroll.
*/
- public void setStackScroll(float s, TaskViewAnimation animation) {
+ public void setStackScroll(float s, AnimationProps animation) {
float prevStackScroll = mStackScrollP;
mStackScrollP = s;
if (mCb != null) {
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java
index da99956..b8b5068 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java
@@ -546,7 +546,7 @@
mTmpTransform.translationZ = fromTransform.translationZ +
(toTransform.translationZ - fromTransform.translationZ) * dismissFraction;
- mSv.updateTaskViewToTransform(tv, mTmpTransform, TaskViewAnimation.IMMEDIATE);
+ mSv.updateTaskViewToTransform(tv, mTmpTransform, AnimationProps.IMMEDIATE);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
index 853f868..703005f 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
@@ -29,7 +29,6 @@
import android.graphics.PorterDuff;
import android.graphics.PorterDuffColorFilter;
import android.graphics.Rect;
-import android.provider.Settings;
import android.util.AttributeSet;
import android.util.FloatProperty;
import android.util.IntProperty;
@@ -243,9 +242,9 @@
}
void updateViewPropertiesToTaskTransform(TaskViewTransform toTransform,
- TaskViewAnimation toAnimation, ValueAnimator.AnimatorUpdateListener updateCallback) {
+ AnimationProps toAnimation, ValueAnimator.AnimatorUpdateListener updateCallback) {
RecentsConfiguration config = Recents.getConfiguration();
- Utilities.cancelAnimationWithoutCallbacks(mTransformAnimation);
+ cancelTransformAnimation();
// Compose the animations for the transform
mTmpAnimators.clear();
@@ -255,8 +254,8 @@
setTaskProgress(toTransform.p);
}
// Manually call back to the animator listener and update callback
- if (toAnimation.listener != null) {
- toAnimation.listener.onAnimationEnd(null);
+ if (toAnimation.getListener() != null) {
+ toAnimation.getListener().onAnimationEnd(null);
}
if (updateCallback != null) {
updateCallback.onAnimationUpdate(null);
@@ -280,7 +279,7 @@
/** Resets this view's properties */
void resetViewProperties() {
- Utilities.cancelAnimationWithoutCallbacks(mTransformAnimation);
+ cancelTransformAnimation();
setDim(0);
setVisibility(View.VISIBLE);
getViewBounds().reset();
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewAnimation.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewAnimation.java
deleted file mode 100644
index 5455042..0000000
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewAnimation.java
+++ /dev/null
@@ -1,83 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.recents.views;
-
-import android.animation.Animator;
-import android.animation.AnimatorSet;
-import android.view.animation.Interpolator;
-
-import com.android.systemui.Interpolators;
-
-import java.util.List;
-
-/**
- * The animation properties to animate a {@link TaskView} to a given {@link TaskViewTransform}.
- */
-public class TaskViewAnimation {
-
- public static final TaskViewAnimation IMMEDIATE = new TaskViewAnimation(0,
- Interpolators.LINEAR);
-
- public final int startDelay;
- public final int duration;
- public final Interpolator interpolator;
- public final Animator.AnimatorListener listener;
-
- public TaskViewAnimation(int duration, Interpolator interpolator) {
- this(0 /* startDelay */, duration, interpolator, null);
- }
-
- public TaskViewAnimation(int duration, Interpolator interpolator,
- Animator.AnimatorListener listener) {
- this(0 /* startDelay */, duration, interpolator, listener);
- }
-
- public TaskViewAnimation(int startDelay, int duration, Interpolator interpolator) {
- this(startDelay, duration, interpolator, null);
- }
-
- public TaskViewAnimation(int startDelay, int duration, Interpolator interpolator,
- Animator.AnimatorListener listener) {
- this.startDelay = startDelay;
- this.duration = duration;
- this.interpolator = interpolator;
- this.listener = listener;
- }
-
- /**
- * Creates a new {@link AnimatorSet} that will animate the given animators with the current
- * animation properties.
- */
- public AnimatorSet createAnimator(List<Animator> animators) {
- AnimatorSet anim = new AnimatorSet();
- anim.setStartDelay(startDelay);
- anim.setDuration(duration);
- anim.setInterpolator(interpolator);
- if (listener != null) {
- anim.addListener(listener);
- }
- anim.playTogether(animators);
- return anim;
- }
-
- /**
- * Returns whether this animation has any duration.
- */
- public boolean isImmediate() {
- return duration <= 0;
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java
index 7cde463..c91a833 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java
@@ -274,8 +274,8 @@
}
@Override
- protected void dispatchDraw(Canvas canvas) {
- super.dispatchDraw(canvas);
+ public void onDrawForeground(Canvas canvas) {
+ super.onDrawForeground(canvas);
// Draw the dim layer with the rounded corners
canvas.drawRoundRect(0, 0, mTaskViewRect.width(), getHeight() + mCornerRadius,
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewTransform.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewTransform.java
index 85b7c82..32878b0 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewTransform.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewTransform.java
@@ -154,13 +154,13 @@
* Applies this transform to a view.
*/
public void applyToTaskView(TaskView v, ArrayList<Animator> animators,
- TaskViewAnimation taskAnimation, boolean allowShadows) {
+ AnimationProps animation, boolean allowShadows) {
// Return early if not visible
if (!visible) {
return;
}
- if (taskAnimation.isImmediate()) {
+ if (animation.isImmediate()) {
if (allowShadows && hasTranslationZChangedFrom(v.getTranslationZ())) {
v.setTranslationZ(translationZ);
}
@@ -177,23 +177,27 @@
}
} else {
if (allowShadows && hasTranslationZChangedFrom(v.getTranslationZ())) {
- animators.add(ObjectAnimator.ofFloat(v, View.TRANSLATION_Z, v.getTranslationZ(),
- translationZ));
+ ObjectAnimator anim = ObjectAnimator.ofFloat(v, View.TRANSLATION_Z,
+ v.getTranslationZ(), translationZ);
+ animators.add(animation.apply(AnimationProps.TRANSLATION_Z, anim));
}
if (hasScaleChangedFrom(v.getScaleX())) {
- animators.add(ObjectAnimator.ofPropertyValuesHolder(v,
+ ObjectAnimator anim = ObjectAnimator.ofPropertyValuesHolder(v,
PropertyValuesHolder.ofFloat(View.SCALE_X, v.getScaleX(), scale),
- PropertyValuesHolder.ofFloat(View.SCALE_Y, v.getScaleX(), scale)));
+ PropertyValuesHolder.ofFloat(View.SCALE_Y, v.getScaleX(), scale));
+ animators.add(animation.apply(AnimationProps.SCALE, anim));
}
if (hasAlphaChangedFrom(v.getAlpha())) {
- animators.add(ObjectAnimator.ofFloat(v, View.ALPHA, v.getAlpha(), alpha));
+ ObjectAnimator anim = ObjectAnimator.ofFloat(v, View.ALPHA, v.getAlpha(), alpha);
+ animators.add(animation.apply(AnimationProps.ALPHA, anim));
}
if (hasRectChangedFrom(v)) {
- animators.add(ObjectAnimator.ofPropertyValuesHolder(v,
+ ObjectAnimator anim = ObjectAnimator.ofPropertyValuesHolder(v,
PropertyValuesHolder.ofInt(LEFT, v.getLeft(), (int) rect.left),
PropertyValuesHolder.ofInt(TOP, v.getTop(), (int) rect.top),
PropertyValuesHolder.ofInt(RIGHT, v.getRight(), (int) rect.right),
- PropertyValuesHolder.ofInt(BOTTOM, v.getBottom(), (int) rect.bottom)));
+ PropertyValuesHolder.ofInt(BOTTOM, v.getBottom(), (int) rect.bottom));
+ animators.add(animation.apply(AnimationProps.BOUNDS, anim));
}
}
}
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityGestureDetector.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityGestureDetector.java
index e3108015..912e8e2 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityGestureDetector.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityGestureDetector.java
@@ -27,6 +27,8 @@
import android.util.Slog;
import android.view.GestureDetector;
import android.view.MotionEvent;
+import android.view.VelocityTracker;
+import android.view.ViewConfiguration;
import com.android.internal.R;
@@ -46,7 +48,9 @@
public interface Listener {
public void onDoubleTapAndHold(MotionEvent event, int policyFlags);
public boolean onDoubleTap(MotionEvent event, int policyFlags);
- public boolean onGesture(int gestureId);
+ public boolean onGestureCompleted(int gestureId);
+ public void onGestureStarted();
+ public void onGestureCancelled(MotionEvent event, int policyFlags);
}
private final Listener mListener;
@@ -64,6 +68,10 @@
// Indicates that motion events are being collected to match a gesture.
private boolean mRecognizingGesture;
+ // Indicates that we've collected enough data to be sure it could be a
+ // gesture.
+ private boolean mGestureConfirmed;
+
// Indicates that motion events from the second pointer are being checked
// for a double tap.
private boolean mSecondFingerDoubleTap;
@@ -81,18 +89,41 @@
// The Y of the previous event.
private float mPreviousY;
+ // The X of the down event.
+ private float mBaseX;
+
+ // The Y of the down event.
+ private float mBaseY;
+
+ // Slop between the first and second tap to be a double tap.
+ private final int mDoubleTapSlop;
+
+ // The scaled velocity above which we detect gestures.
+ private final int mScaledGestureDetectionVelocity;
+
// Buffer for storing points for gesture detection.
private final ArrayList<GesturePoint> mStrokeBuffer = new ArrayList<GesturePoint>(100);
+ // Helper to track gesture velocity.
+ private final VelocityTracker mVelocityTracker = VelocityTracker.obtain();
+
// The minimal delta between moves to add a gesture point.
private static final int TOUCH_TOLERANCE = 3;
// The minimal score for accepting a predicted gesture.
private static final float MIN_PREDICTION_SCORE = 2.0f;
+ // The velocity above which we detect gestures. Expressed in DIPs/Second.
+ private static final int GESTURE_DETECTION_VELOCITY_DIP = 1000;
+
+ // Constant used to calculate velocity in seconds.
+ private static final int VELOCITY_UNITS_SECONDS = 1000;
+
AccessibilityGestureDetector(Context context, Listener listener) {
mListener = listener;
+ mDoubleTapSlop = ViewConfiguration.get(context).getScaledDoubleTapSlop();
+
mGestureDetector = new GestureDetector(context, this);
mGestureDetector.setOnDoubleTapListener(this);
@@ -100,9 +131,14 @@
mGestureLibrary.setOrientationStyle(8 /* GestureStore.ORIENTATION_SENSITIVE_8 */);
mGestureLibrary.setSequenceType(GestureStore.SEQUENCE_SENSITIVE);
mGestureLibrary.load();
+
+ final float density = context.getResources().getDisplayMetrics().density;
+ mScaledGestureDetectionVelocity = (int) (GESTURE_DETECTION_VELOCITY_DIP * density);
}
public boolean onMotionEvent(MotionEvent event, int policyFlags) {
+ mVelocityTracker.addMovement(event);
+
final float x = event.getX();
final float y = event.getY();
@@ -112,14 +148,48 @@
mDoubleTapDetected = false;
mSecondFingerDoubleTap = false;
mRecognizingGesture = true;
+ mGestureConfirmed = false;
+ mBaseX = x;
+ mBaseY = y;
mPreviousX = x;
mPreviousY = y;
mStrokeBuffer.clear();
mStrokeBuffer.add(new GesturePoint(x, y, event.getEventTime()));
+ mVelocityTracker.clear();
+ mVelocityTracker.addMovement(event);
break;
case MotionEvent.ACTION_MOVE:
if (mRecognizingGesture) {
+ if (!mGestureConfirmed) {
+ mVelocityTracker.addMovement(event);
+ // It is *important* to use the distance traveled by the pointers
+ // on the screen which may or may not be magnified.
+ final float deltaX = mBaseX - event.getX(0);
+ final float deltaY = mBaseY - event.getY(0);
+ final double moveDelta = Math.hypot(deltaX, deltaY);
+ // The user has moved enough for us to decide.
+ if (moveDelta > mDoubleTapSlop) {
+ // Check whether the user is performing a gesture. We
+ // detect gestures if the pointer is moving above a
+ // given velocity.
+ mVelocityTracker.computeCurrentVelocity(VELOCITY_UNITS_SECONDS);
+ final float maxAbsVelocity = Math.max(
+ Math.abs(mVelocityTracker.getXVelocity(0)),
+ Math.abs(mVelocityTracker.getYVelocity(0)));
+ if (maxAbsVelocity > mScaledGestureDetectionVelocity) {
+ // We have to perform gesture detection, so
+ // notify the listener.
+ mGestureConfirmed = true;
+ mListener.onGestureStarted();
+ } else {
+ // This won't match any gesture, so notify the
+ // listener.
+ cancelGesture();
+ mListener.onGestureCancelled(event, policyFlags);
+ }
+ }
+ }
final float dX = Math.abs(x - mPreviousX);
final float dY = Math.abs(y - mPreviousY);
if (dX >= TOUCH_TOLERANCE || dY >= TOUCH_TOLERANCE) {
@@ -134,12 +204,13 @@
if (maybeFinishDoubleTap(event, policyFlags)) {
return true;
}
- if (mRecognizingGesture) {
+ if (mGestureConfirmed) {
mStrokeBuffer.add(new GesturePoint(x, y, event.getEventTime()));
- if (recognizeGesture()) {
- return true;
+ if (!recognizeGesture()) {
+ mListener.onGestureCancelled(event, policyFlags);
}
+ return true;
}
break;
@@ -167,6 +238,10 @@
return true;
}
break;
+
+ case MotionEvent.ACTION_CANCEL:
+ clear();
+ break;
}
// If we're detecting taps on the second finger, map events from the
@@ -194,18 +269,13 @@
mDoubleTapDetected = false;
mSecondFingerDoubleTap = false;
cancelGesture();
- mStrokeBuffer.clear();
+ mVelocityTracker.clear();
}
public boolean firstTapDetected() {
return mFirstTapDetected;
}
- public void cancelGesture() {
- mRecognizingGesture = false;
- mStrokeBuffer.clear();
- }
-
@Override
public void onLongPress(MotionEvent e) {
maybeSendLongPress(e, mPolicyFlags);
@@ -251,6 +321,12 @@
return mListener.onDoubleTap(event, policyFlags);
}
+ private void cancelGesture() {
+ mRecognizingGesture = false;
+ mGestureConfirmed = false;
+ mStrokeBuffer.clear();
+ }
+
private boolean recognizeGesture() {
Gesture gesture = new Gesture();
gesture.addStroke(new GestureStroke(mStrokeBuffer));
@@ -265,7 +341,7 @@
}
try {
final int gestureId = Integer.parseInt(bestPrediction.name);
- if (mListener.onGesture(gestureId)) {
+ if (mListener.onGestureCompleted(gestureId)) {
return true;
}
} catch (NumberFormatException nfe) {
diff --git a/services/accessibility/java/com/android/server/accessibility/TouchExplorer.java b/services/accessibility/java/com/android/server/accessibility/TouchExplorer.java
index ca30349..9e6cd00 100644
--- a/services/accessibility/java/com/android/server/accessibility/TouchExplorer.java
+++ b/services/accessibility/java/com/android/server/accessibility/TouchExplorer.java
@@ -25,7 +25,6 @@
import android.view.MotionEvent;
import android.view.MotionEvent.PointerCoords;
import android.view.MotionEvent.PointerProperties;
-import android.view.VelocityTracker;
import android.view.ViewConfiguration;
import android.view.WindowManagerPolicy;
import android.view.accessibility.AccessibilityEvent;
@@ -87,9 +86,6 @@
// Invalid pointer ID.
private static final int INVALID_POINTER_ID = -1;
- // The velocity above which we detect gestures.
- private static final int GESTURE_DETECTION_VELOCITY_DIP = 1000;
-
// The minimal distance before we take the middle of the distance between
// the two dragging pointers as opposed to use the location of the primary one.
private static final int MIN_POINTER_DISTANCE_TO_USE_MIDDLE_LOCATION_DIP = 200;
@@ -134,15 +130,9 @@
// the two dragging pointers as opposed to use the location of the primary one.
private final int mScaledMinPointerDistanceToUseMiddleLocation;
- // The scaled velocity above which we detect gestures.
- private final int mScaledGestureDetectionVelocity;
-
// The handler to which to delegate events.
private EventStreamTransformation mNext;
- // Helper to track gesture velocity.
- private final VelocityTracker mVelocityTracker = VelocityTracker.obtain();
-
// Helper class to track received pointers.
private final ReceivedPointerTracker mReceivedPointerTracker;
@@ -200,7 +190,6 @@
final float density = context.getResources().getDisplayMetrics().density;
mScaledMinPointerDistanceToUseMiddleLocation =
(int) (MIN_POINTER_DISTANCE_TO_USE_MIDDLE_LOCATION_DIP * density);
- mScaledGestureDetectionVelocity = (int) (GESTURE_DETECTION_VELOCITY_DIP * density);
}
@Override
@@ -289,11 +278,19 @@
mReceivedPointerTracker.onMotionEvent(rawEvent);
- if (mGestureDetector.onMotionEvent(event, policyFlags)) {
+ // The motion detector is interested in the movements in physical space,
+ // so it uses the rawEvent to ignore magnification and other
+ // transformations.
+ if (mGestureDetector.onMotionEvent(rawEvent, policyFlags)) {
// Event was handled by the gesture detector.
return;
}
+ if (event.getActionMasked() == MotionEvent.ACTION_CANCEL) {
+ clear(event, policyFlags);
+ return;
+ }
+
switch(mCurrentState) {
case STATE_TOUCH_EXPLORING: {
handleMotionEventStateTouchExploring(event, rawEvent, policyFlags);
@@ -305,7 +302,7 @@
handleMotionEventStateDelegating(event, policyFlags);
} break;
case STATE_GESTURE_DETECTING: {
- handleMotionEventGestureDetecting(rawEvent, policyFlags);
+ // Already handled.
} break;
default:
throw new IllegalStateException("Illegal state: " + mCurrentState);
@@ -440,26 +437,50 @@
}
@Override
- public boolean onGesture(int gestureId) {
+ public boolean onGestureCompleted(int gestureId) {
if (mCurrentState != STATE_GESTURE_DETECTING) {
return false;
}
- mAms.onTouchInteractionEnd();
-
- // Announce the end of the gesture recognition.
- sendAccessibilityEvent(AccessibilityEvent.TYPE_GESTURE_DETECTION_END);
- // Announce the end of a the touch interaction.
- sendAccessibilityEvent(AccessibilityEvent.TYPE_TOUCH_INTERACTION_END);
+ endGestureDetection();
mAms.onGesture(gestureId);
- mExitGestureDetectionModeDelayed.cancel();
- mCurrentState = STATE_TOUCH_EXPLORING;
-
return true;
}
+ @Override
+ public void onGestureStarted() {
+ // We have to perform gesture detection, so
+ // clear the current state and try to detect.
+ mCurrentState = STATE_GESTURE_DETECTING;
+ mSendHoverEnterAndMoveDelayed.cancel();
+ mSendHoverExitDelayed.cancel();
+ mExitGestureDetectionModeDelayed.post();
+ // Send accessibility event to announce the start
+ // of gesture recognition.
+ sendAccessibilityEvent(AccessibilityEvent.TYPE_GESTURE_DETECTION_START);
+ }
+
+ @Override
+ public void onGestureCancelled(MotionEvent event, int policyFlags) {
+ if (mCurrentState == STATE_GESTURE_DETECTING) {
+ endGestureDetection();
+ } else if (mCurrentState == STATE_TOUCH_EXPLORING) {
+ final int pointerId = mReceivedPointerTracker.getPrimaryPointerId();
+ final int pointerIdBits = (1 << pointerId);
+
+ // Cache the event until we discern exploration from gesturing.
+ mSendHoverEnterAndMoveDelayed.addEvent(event);
+
+ // We have just decided that the user is touch,
+ // exploring so start sending events.
+ mSendHoverEnterAndMoveDelayed.forceSendAndRemove();
+ mSendHoverExitDelayed.cancel();
+ sendMotionEvent(event, MotionEvent.ACTION_HOVER_MOVE, pointerIdBits, policyFlags);
+ }
+ }
+
/**
* Handles a motion event in touch exploring state.
*
@@ -471,8 +492,6 @@
int policyFlags) {
ReceivedPointerTracker receivedTracker = mReceivedPointerTracker;
- mVelocityTracker.addMovement(rawEvent);
-
switch (event.getActionMasked()) {
case MotionEvent.ACTION_DOWN: {
mAms.onTouchInteractionStart();
@@ -525,46 +544,6 @@
if (mSendHoverEnterAndMoveDelayed.isPending()) {
// Cache the event until we discern exploration from gesturing.
mSendHoverEnterAndMoveDelayed.addEvent(event);
-
- // It is *important* to use the distance traveled by the pointers
- // on the screen which may or may not be magnified.
- final float deltaX = receivedTracker.getReceivedPointerDownX(pointerId)
- - rawEvent.getX(pointerIndex);
- final float deltaY = receivedTracker.getReceivedPointerDownY(pointerId)
- - rawEvent.getY(pointerIndex);
- final double moveDelta = Math.hypot(deltaX, deltaY);
- // The user has moved enough for us to decide.
- if (moveDelta > mDoubleTapSlop) {
- // Check whether the user is performing a gesture. We
- // detect gestures if the pointer is moving above a
- // given velocity.
- mVelocityTracker.computeCurrentVelocity(1000);
- final float maxAbsVelocity = Math.max(
- Math.abs(mVelocityTracker.getXVelocity(pointerId)),
- Math.abs(mVelocityTracker.getYVelocity(pointerId)));
- if (maxAbsVelocity > mScaledGestureDetectionVelocity) {
- // We have to perform gesture detection, so
- // clear the current state and try to detect.
- mCurrentState = STATE_GESTURE_DETECTING;
- mVelocityTracker.clear();
- mSendHoverEnterAndMoveDelayed.cancel();
- mSendHoverExitDelayed.cancel();
- mExitGestureDetectionModeDelayed.post();
- // Send accessibility event to announce the start
- // of gesture recognition.
- sendAccessibilityEvent(
- AccessibilityEvent.TYPE_GESTURE_DETECTION_START);
- } else {
- // We have just decided that the user is touch,
- // exploring so start sending events.
- mGestureDetector.cancelGesture();
- mSendHoverEnterAndMoveDelayed.forceSendAndRemove();
- mSendHoverExitDelayed.cancel();
- sendMotionEvent(event, MotionEvent.ACTION_HOVER_MOVE,
- pointerIdBits, policyFlags);
- }
- break;
- }
} else {
if (mTouchExplorationInProgress) {
sendTouchExplorationGestureStartAndHoverEnterIfNeeded(policyFlags);
@@ -602,11 +581,6 @@
}
}
- // We know that a new state transition is to happen and the
- // new state will not be gesture recognition, so cancel
- // the gesture.
- mGestureDetector.cancelGesture();
-
if (isDraggingGesture(event)) {
// Two pointers moving in the same direction within
// a given distance perform a drag.
@@ -620,7 +594,6 @@
mCurrentState = STATE_DELEGATING;
sendDownForAllNotInjectedPointers(event, policyFlags);
}
- mVelocityTracker.clear();
} break;
default: {
// More than one pointer so the user is not touch exploring
@@ -639,7 +612,6 @@
// More than two pointers are delegated to the view hierarchy.
mCurrentState = STATE_DELEGATING;
sendDownForAllNotInjectedPointers(event, policyFlags);
- mVelocityTracker.clear();
}
}
} break;
@@ -648,8 +620,6 @@
final int pointerId = event.getPointerId(event.getActionIndex());
final int pointerIdBits = (1 << pointerId);
- mVelocityTracker.clear();
-
if (mSendHoverEnterAndMoveDelayed.isPending()) {
// If we have not delivered the enter schedule an exit.
mSendHoverExitDelayed.post(event, pointerIdBits, policyFlags);
@@ -663,9 +633,6 @@
}
} break;
- case MotionEvent.ACTION_CANCEL: {
- clear(event, policyFlags);
- } break;
}
}
@@ -756,9 +723,6 @@
}
mCurrentState = STATE_TOUCH_EXPLORING;
} break;
- case MotionEvent.ACTION_CANCEL: {
- clear(event, policyFlags);
- } break;
}
}
@@ -795,9 +759,6 @@
mCurrentState = STATE_TOUCH_EXPLORING;
} break;
- case MotionEvent.ACTION_CANCEL: {
- clear(event, policyFlags);
- } break;
default: {
// Deliver the event.
sendMotionEvent(event, event.getAction(), ALL_POINTER_ID_BITS, policyFlags);
@@ -805,22 +766,16 @@
}
}
- private void handleMotionEventGestureDetecting(MotionEvent event, int policyFlags) {
- switch (event.getActionMasked()) {
- case MotionEvent.ACTION_UP: {
- mAms.onTouchInteractionEnd();
- // Announce the end of the gesture recognition.
- sendAccessibilityEvent(AccessibilityEvent.TYPE_GESTURE_DETECTION_END);
- // Announce the end of a the touch interaction.
- sendAccessibilityEvent(AccessibilityEvent.TYPE_TOUCH_INTERACTION_END);
+ private void endGestureDetection() {
+ mAms.onTouchInteractionEnd();
- mExitGestureDetectionModeDelayed.cancel();
- mCurrentState = STATE_TOUCH_EXPLORING;
- } break;
- case MotionEvent.ACTION_CANCEL: {
- clear(event, policyFlags);
- } break;
- }
+ // Announce the end of the gesture recognition.
+ sendAccessibilityEvent(AccessibilityEvent.TYPE_GESTURE_DETECTION_END);
+ // Announce the end of a the touch interaction.
+ sendAccessibilityEvent(AccessibilityEvent.TYPE_TOUCH_INTERACTION_END);
+
+ mExitGestureDetectionModeDelayed.cancel();
+ mCurrentState = STATE_TOUCH_EXPLORING;
}
/**
diff --git a/services/core/java/com/android/server/pm/Installer.java b/services/core/java/com/android/server/pm/Installer.java
index 5d97afa..dac89ec 100644
--- a/services/core/java/com/android/server/pm/Installer.java
+++ b/services/core/java/com/android/server/pm/Installer.java
@@ -49,6 +49,8 @@
public static final int DEXOPT_BOOTCOMPLETE = 1 << 4;
/** Do not compile, only extract bytecode into an OAT file */
public static final int DEXOPT_EXTRACTONLY = 1 << 5;
+ /** This is an OTA update dexopt */
+ public static final int DEXOPT_OTA = 1 << 6;
/** @hide */
@IntDef(flag = true, value = {
diff --git a/services/core/java/com/android/server/pm/OtaDexoptService.java b/services/core/java/com/android/server/pm/OtaDexoptService.java
new file mode 100644
index 0000000..da62a2d
--- /dev/null
+++ b/services/core/java/com/android/server/pm/OtaDexoptService.java
@@ -0,0 +1,274 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pm;
+
+import android.app.AppGlobals;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.IOtaDexopt;
+import android.content.pm.PackageParser;
+import android.content.pm.PackageParser.Package;
+import android.content.pm.ResolveInfo;
+import android.os.Environment;
+import android.os.RemoteException;
+import android.os.ResultReceiver;
+import android.os.ServiceManager;
+import android.os.UserHandle;
+import android.os.storage.StorageManager;
+import android.util.ArraySet;
+import android.util.Log;
+
+import dalvik.system.DexFile;
+
+import java.io.File;
+import java.io.FileDescriptor;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Date;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Set;
+
+import static com.android.server.pm.Installer.DEXOPT_OTA;
+
+/**
+ * A service for A/B OTA dexopting.
+ *
+ * {@hide}
+ */
+public class OtaDexoptService extends IOtaDexopt.Stub {
+ private final static String TAG = "OTADexopt";
+ private final static boolean DEBUG_DEXOPT = true;
+ // Apps used in the last 7 days.
+ private final static long DEXOPT_LRU_THRESHOLD_IN_MINUTES = 7 * 24 * 60;
+
+ private final Context mContext;
+ private final PackageDexOptimizer mPackageDexOptimizer;
+ private final PackageManagerService mPackageManagerService;
+
+ // TODO: Evaluate the need for WeakReferences here.
+ private List<PackageParser.Package> mDexoptPackages;
+
+ public OtaDexoptService(Context context, PackageManagerService packageManagerService) {
+ this.mContext = context;
+ this.mPackageManagerService = packageManagerService;
+
+ // Use the package manager install and install lock here for the OTA dex optimizer.
+ mPackageDexOptimizer = new OTADexoptPackageDexOptimizer(packageManagerService.mInstaller,
+ packageManagerService.mInstallLock, context);
+ }
+
+ public static OtaDexoptService main(Context context,
+ PackageManagerService packageManagerService) {
+ OtaDexoptService ota = new OtaDexoptService(context, packageManagerService);
+ ServiceManager.addService("otadexopt", ota);
+
+ return ota;
+ }
+
+ @Override
+ public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
+ String[] args, ResultReceiver resultReceiver) throws RemoteException {
+ (new OtaDexoptShellCommand(this)).exec(
+ this, in, out, err, args, resultReceiver);
+ }
+
+ @Override
+ public synchronized void prepare() throws RemoteException {
+ if (mDexoptPackages != null) {
+ throw new IllegalStateException("already called prepare()");
+ }
+
+ mDexoptPackages = new LinkedList<>();
+
+ ArrayList<PackageParser.Package> pkgs;
+ synchronized (mPackageManagerService.mPackages) {
+ pkgs = new ArrayList<PackageParser.Package>(mPackageManagerService.mPackages.values());
+ }
+
+ // Sort apps by importance for dexopt ordering. Important apps are given more priority
+ // in case the device runs out of space.
+
+ // Give priority to core apps.
+ for (PackageParser.Package pkg : pkgs) {
+ if (pkg.coreApp) {
+ if (DEBUG_DEXOPT) {
+ Log.i(TAG, "Adding core app " + mDexoptPackages.size() + ": " + pkg.packageName);
+ }
+ mDexoptPackages.add(pkg);
+ }
+ }
+ pkgs.removeAll(mDexoptPackages);
+
+ // Give priority to system apps that listen for pre boot complete.
+ Intent intent = new Intent(Intent.ACTION_PRE_BOOT_COMPLETED);
+ ArraySet<String> pkgNames = getPackageNamesForIntent(intent, UserHandle.USER_SYSTEM);
+ for (PackageParser.Package pkg : pkgs) {
+ if (pkgNames.contains(pkg.packageName)) {
+ if (DEBUG_DEXOPT) {
+ Log.i(TAG, "Adding pre boot system app " + mDexoptPackages.size() + ": " +
+ pkg.packageName);
+ }
+ mDexoptPackages.add(pkg);
+ }
+ }
+ pkgs.removeAll(mDexoptPackages);
+
+ // Filter out packages that aren't recently used, add all remaining apps.
+ // TODO: add a property to control this?
+ if (mPackageManagerService.isHistoricalPackageUsageAvailable()) {
+ filterRecentlyUsedApps(pkgs, DEXOPT_LRU_THRESHOLD_IN_MINUTES * 60 * 1000);
+ }
+ mDexoptPackages.addAll(pkgs);
+
+ // Now go ahead and also add the libraries required for these packages.
+ // TODO: Think about interleaving things.
+ Set<PackageParser.Package> dependencies = new HashSet<>();
+ for (PackageParser.Package p : mDexoptPackages) {
+ dependencies.addAll(mPackageManagerService.findSharedNonSystemLibraries(p));
+ }
+ if (!dependencies.isEmpty()) {
+ dependencies.removeAll(mDexoptPackages);
+ }
+ mDexoptPackages.addAll(dependencies);
+
+ if (DEBUG_DEXOPT) {
+ StringBuilder sb = new StringBuilder();
+ for (PackageParser.Package pkg : mDexoptPackages) {
+ if (sb.length() > 0) {
+ sb.append(", ");
+ }
+ sb.append(pkg.packageName);
+ }
+ Log.i(TAG, "Packages to be optimized: " + sb.toString());
+ }
+ }
+
+ @Override
+ public synchronized void cleanup() throws RemoteException {
+ if (DEBUG_DEXOPT) {
+ Log.i(TAG, "Cleaning up OTA Dexopt state.");
+ }
+ mDexoptPackages = null;
+ }
+
+ @Override
+ public synchronized boolean isDone() throws RemoteException {
+ if (mDexoptPackages == null) {
+ throw new IllegalStateException("done() called before prepare()");
+ }
+
+ return mDexoptPackages.isEmpty();
+ }
+
+ @Override
+ public synchronized void dexoptNextPackage() throws RemoteException {
+ if (mDexoptPackages == null) {
+ throw new IllegalStateException("dexoptNextPackage() called before prepare()");
+ }
+ if (mDexoptPackages.isEmpty()) {
+ // Tolerate repeated calls.
+ return;
+ }
+
+ PackageParser.Package nextPackage = mDexoptPackages.remove(0);
+
+ if (DEBUG_DEXOPT) {
+ Log.i(TAG, "Processing " + nextPackage.packageName + " for OTA dexopt.");
+ }
+
+ // Check for low space.
+ // TODO: If apps are not installed in the internal /data partition, we should compare
+ // against that storage's free capacity.
+ File dataDir = Environment.getDataDirectory();
+ long lowThreshold = StorageManager.from(mContext).getStorageLowBytes(dataDir);
+ if (lowThreshold == 0) {
+ throw new IllegalStateException("Invalid low memory threshold");
+ }
+ long usableSpace = dataDir.getUsableSpace();
+ if (usableSpace < lowThreshold) {
+ Log.w(TAG, "Not running dexopt on " + nextPackage.packageName + " due to low memory: " +
+ usableSpace);
+ return;
+ }
+
+ mPackageDexOptimizer.performDexOpt(nextPackage, null /* ISAs */, false /* useProfiles */,
+ false /* extractOnly */);
+ }
+
+ private ArraySet<String> getPackageNamesForIntent(Intent intent, int userId) {
+ List<ResolveInfo> ris = null;
+ try {
+ ris = AppGlobals.getPackageManager().queryIntentReceivers(
+ intent, null, 0, userId);
+ } catch (RemoteException e) {
+ }
+ ArraySet<String> pkgNames = new ArraySet<String>(ris == null ? 0 : ris.size());
+ if (ris != null) {
+ for (ResolveInfo ri : ris) {
+ pkgNames.add(ri.activityInfo.packageName);
+ }
+ }
+ return pkgNames;
+ }
+
+ private void filterRecentlyUsedApps(Collection<PackageParser.Package> pkgs,
+ long dexOptLRUThresholdInMills) {
+ // Filter out packages that aren't recently used.
+ int total = pkgs.size();
+ int skipped = 0;
+ long now = System.currentTimeMillis();
+ for (Iterator<PackageParser.Package> i = pkgs.iterator(); i.hasNext();) {
+ PackageParser.Package pkg = i.next();
+ long then = pkg.mLastPackageUsageTimeInMills;
+ if (then + dexOptLRUThresholdInMills < now) {
+ if (DEBUG_DEXOPT) {
+ Log.i(TAG, "Skipping dexopt of " + pkg.packageName + " last resumed: " +
+ ((then == 0) ? "never" : new Date(then)));
+ }
+ i.remove();
+ skipped++;
+ }
+ }
+ if (DEBUG_DEXOPT) {
+ Log.i(TAG, "Skipped optimizing " + skipped + " of " + total);
+ }
+ }
+
+ private static class OTADexoptPackageDexOptimizer extends
+ PackageDexOptimizer.ForcedUpdatePackageDexOptimizer {
+
+ public OTADexoptPackageDexOptimizer(Installer installer, Object installLock,
+ Context context) {
+ super(installer, installLock, context, "*otadexopt*");
+ }
+
+ @Override
+ protected int adjustDexoptFlags(int dexoptFlags) {
+ // Add the OTA flag.
+ return dexoptFlags | DEXOPT_OTA;
+ }
+
+ @Override
+ protected void recordSuccessfulDexopt(Package pkg, String instructionSet) {
+ // Never record the dexopt, as it's in the B partition.
+ }
+
+ }
+}
diff --git a/services/core/java/com/android/server/pm/OtaDexoptShellCommand.java b/services/core/java/com/android/server/pm/OtaDexoptShellCommand.java
new file mode 100644
index 0000000..ea9cf17
--- /dev/null
+++ b/services/core/java/com/android/server/pm/OtaDexoptShellCommand.java
@@ -0,0 +1,100 @@
+/*
+ * 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 com.android.server.pm;
+
+import android.content.pm.IOtaDexopt;
+import android.os.RemoteException;
+import android.os.ShellCommand;
+
+import java.io.PrintWriter;
+
+class OtaDexoptShellCommand extends ShellCommand {
+ final IOtaDexopt mInterface;
+
+ OtaDexoptShellCommand(OtaDexoptService service) {
+ mInterface = service;
+ }
+
+ @Override
+ public int onCommand(String cmd) {
+ if (cmd == null) {
+ return handleDefaultCommands(null);
+ }
+
+ final PrintWriter pw = getOutPrintWriter();
+ try {
+ switch(cmd) {
+ case "prepare":
+ return runOtaPrepare();
+ case "cleanup":
+ return runOtaCleanup();
+ case "done":
+ return runOtaDone();
+ case "step":
+ return runOtaStep();
+ default:
+ return handleDefaultCommands(cmd);
+ }
+ } catch (RemoteException e) {
+ pw.println("Remote exception: " + e);
+ }
+ return -1;
+ }
+
+ private int runOtaPrepare() throws RemoteException {
+ mInterface.prepare();
+ getOutPrintWriter().println("Success");
+ return 0;
+ }
+
+ private int runOtaCleanup() throws RemoteException {
+ mInterface.cleanup();
+ return 0;
+ }
+
+ private int runOtaDone() throws RemoteException {
+ final PrintWriter pw = getOutPrintWriter();
+ if (mInterface.isDone()) {
+ pw.println("OTA complete.");
+ } else {
+ pw.println("OTA incomplete.");
+ }
+ return 0;
+ }
+
+ private int runOtaStep() throws RemoteException {
+ mInterface.dexoptNextPackage();
+ return 0;
+ }
+
+ @Override
+ public void onHelp() {
+ final PrintWriter pw = getOutPrintWriter();
+ pw.println("OTA Dexopt (ota) commands:");
+ pw.println(" help");
+ pw.println(" Print this help text.");
+ pw.println("");
+ pw.println(" prepare");
+ pw.println(" Prepare an OTA dexopt pass, collecting all packages.");
+ pw.println(" done");
+ pw.println(" Replies whether the OTA is complete or not.");
+ pw.println(" step");
+ pw.println(" OTA dexopt the next package.");
+ pw.println(" cleanup");
+ pw.println(" Clean up internal states. Ends an OTA session.");
+ }
+}
diff --git a/services/core/java/com/android/server/pm/PackageDexOptimizer.java b/services/core/java/com/android/server/pm/PackageDexOptimizer.java
index fe0f141..64af213 100644
--- a/services/core/java/com/android/server/pm/PackageDexOptimizer.java
+++ b/services/core/java/com/android/server/pm/PackageDexOptimizer.java
@@ -20,6 +20,7 @@
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageParser;
+import android.content.pm.PackageParser.Package;
import android.os.PowerManager;
import android.os.UserHandle;
import android.os.WorkSource;
@@ -48,7 +49,7 @@
/**
* Helper class for running dexopt command on packages.
*/
-final class PackageDexOptimizer {
+class PackageDexOptimizer {
private static final String TAG = "PackageManager.DexOptimizer";
static final String OAT_DIR_NAME = "oat";
// TODO b/19550105 Remove error codes and use exceptions
@@ -57,16 +58,28 @@
static final int DEX_OPT_DEFERRED = 2;
static final int DEX_OPT_FAILED = -1;
- private final PackageManagerService mPackageManagerService;
+ private static final boolean DEBUG_DEXOPT = PackageManagerService.DEBUG_DEXOPT;
+
+ private final Installer mInstaller;
+ private final Object mInstallLock;
private final PowerManager.WakeLock mDexoptWakeLock;
private volatile boolean mSystemReady;
- PackageDexOptimizer(PackageManagerService packageManagerService) {
- this.mPackageManagerService = packageManagerService;
- PowerManager powerManager = (PowerManager)packageManagerService.mContext.getSystemService(
- Context.POWER_SERVICE);
- mDexoptWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "*dexopt*");
+ PackageDexOptimizer(Installer installer, Object installLock, Context context,
+ String wakeLockTag) {
+ this.mInstaller = installer;
+ this.mInstallLock = installLock;
+
+ PowerManager powerManager = (PowerManager)context.getSystemService(Context.POWER_SERVICE);
+ mDexoptWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, wakeLockTag);
+ }
+
+ protected PackageDexOptimizer(PackageDexOptimizer from) {
+ this.mInstaller = from.mInstaller;
+ this.mInstallLock = from.mInstallLock;
+ this.mDexoptWakeLock = from.mDexoptWakeLock;
+ this.mSystemReady = from.mSystemReady;
}
static boolean canOptimizePackage(PackageParser.Package pkg) {
@@ -77,27 +90,19 @@
* Performs dexopt on all code paths and libraries of the specified package for specified
* instruction sets.
*
- * <p>Calls to {@link com.android.server.pm.Installer#dexopt} are synchronized on
- * {@link PackageManagerService#mInstallLock}.
+ * <p>Calls to {@link com.android.server.pm.Installer#dexopt} on {@link #mInstaller} are
+ * synchronized on {@link #mInstallLock}.
*/
- int performDexOpt(PackageParser.Package pkg, String[] instructionSets,
- boolean inclDependencies, boolean useProfiles, boolean extractOnly, boolean force) {
- ArraySet<String> done;
- if (inclDependencies && (pkg.usesLibraries != null || pkg.usesOptionalLibraries != null)) {
- done = new ArraySet<String>();
- done.add(pkg.packageName);
- } else {
- done = null;
- }
- synchronized (mPackageManagerService.mInstallLock) {
+ int performDexOpt(PackageParser.Package pkg, String[] instructionSets, boolean useProfiles,
+ boolean extractOnly) {
+ synchronized (mInstallLock) {
final boolean useLock = mSystemReady;
if (useLock) {
mDexoptWakeLock.setWorkSource(new WorkSource(pkg.applicationInfo.uid));
mDexoptWakeLock.acquire();
}
try {
- return performDexOptLI(pkg, instructionSets, done, useProfiles,
- extractOnly, force);
+ return performDexOptLI(pkg, instructionSets, useProfiles, extractOnly);
} finally {
if (useLock) {
mDexoptWakeLock.release();
@@ -106,21 +111,42 @@
}
}
+ /**
+ * Determine whether the package should be skipped for the given instruction set. A return
+ * value of true means the package will be skipped. A return value of false means that the
+ * package will be further investigated, and potentially compiled.
+ */
+ protected boolean shouldSkipBasedOnISA(PackageParser.Package pkg, String instructionSet) {
+ return pkg.mDexOptPerformed.contains(instructionSet);
+ }
+
+ /**
+ * Adjust the given dexopt-needed value. Can be overridden to influence the decision to
+ * optimize or not (and in what way).
+ */
+ protected int adjustDexoptNeeded(int dexoptNeeded) {
+ return dexoptNeeded;
+ }
+
+ /**
+ * Adjust the given dexopt flags that will be passed to the installer.
+ */
+ protected int adjustDexoptFlags(int dexoptFlags) {
+ return dexoptFlags;
+ }
+
+ /**
+ * Update the package status after a successful compilation.
+ */
+ protected void recordSuccessfulDexopt(PackageParser.Package pkg, String instructionSet) {
+ pkg.mDexOptPerformed.add(instructionSet);
+ }
+
private int performDexOptLI(PackageParser.Package pkg, String[] targetInstructionSets,
- ArraySet<String> done, boolean useProfiles, boolean extractOnly, boolean force) {
+ boolean useProfiles, boolean extractOnly) {
final String[] instructionSets = targetInstructionSets != null ?
targetInstructionSets : getAppDexInstructionSets(pkg.applicationInfo);
- if (done != null) {
- done.add(pkg.packageName);
- if (pkg.usesLibraries != null) {
- performDexOptLibsLI(pkg.usesLibraries, instructionSets, done);
- }
- if (pkg.usesOptionalLibraries != null) {
- performDexOptLibsLI(pkg.usesOptionalLibraries, instructionSets, done);
- }
- }
-
if (!canOptimizePackage(pkg)) {
return DEX_OPT_SKIPPED;
}
@@ -128,34 +154,26 @@
final boolean vmSafeMode = (pkg.applicationInfo.flags & ApplicationInfo.FLAG_VM_SAFE_MODE) != 0;
final boolean debuggable = (pkg.applicationInfo.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0;
- if (useProfiles) {
- // If we do a profile guided compilation then we might recompile
- // the same package if more profile information is available.
- force = true;
- }
-
final List<String> paths = pkg.getAllCodePathsExcludingResourceOnly();
boolean performedDexOpt = false;
final String[] dexCodeInstructionSets = getDexCodeInstructionSets(instructionSets);
for (String dexCodeInstructionSet : dexCodeInstructionSets) {
- if (!force && pkg.mDexOptPerformed.contains(dexCodeInstructionSet)) {
+ if (!useProfiles && shouldSkipBasedOnISA(pkg, dexCodeInstructionSet)) {
+ // Skip only if we do not use profiles since they might trigger a recompilation.
continue;
}
for (String path : paths) {
int dexoptNeeded;
- if (force) {
- dexoptNeeded = DexFile.DEX2OAT_NEEDED;
- } else {
- try {
- dexoptNeeded = DexFile.getDexOptNeeded(path, pkg.packageName,
- dexCodeInstructionSet, /* defer */false);
- } catch (IOException ioe) {
- Slog.w(TAG, "IOException reading apk: " + path, ioe);
- return DEX_OPT_FAILED;
- }
+ try {
+ dexoptNeeded = DexFile.getDexOptNeeded(path, pkg.packageName,
+ dexCodeInstructionSet, /* defer */false);
+ } catch (IOException ioe) {
+ Slog.w(TAG, "IOException reading apk: " + path, ioe);
+ return DEX_OPT_FAILED;
}
+ dexoptNeeded = adjustDexoptNeeded(dexoptNeeded);
if (dexoptNeeded == DexFile.NO_DEXOPT_NEEDED) {
// No dexopt needed and we don't use profiles. Nothing to do.
@@ -174,21 +192,22 @@
throw new IllegalStateException("Invalid dexopt needed: " + dexoptNeeded);
}
+
Log.i(TAG, "Running dexopt (" + dexoptType + ") on: " + path + " pkg="
+ pkg.applicationInfo.packageName + " isa=" + dexCodeInstructionSet
+ " vmSafeMode=" + vmSafeMode + " debuggable=" + debuggable
+ " extractOnly=" + extractOnly + " oatDir = " + oatDir);
final int sharedGid = UserHandle.getSharedAppGid(pkg.applicationInfo.uid);
- final int dexFlags =
+ final int dexFlags = adjustDexoptFlags(
(!pkg.isForwardLocked() ? DEXOPT_PUBLIC : 0)
| (vmSafeMode ? DEXOPT_SAFEMODE : 0)
| (debuggable ? DEXOPT_DEBUGGABLE : 0)
| (extractOnly ? DEXOPT_EXTRACTONLY : 0)
- | DEXOPT_BOOTCOMPLETE;
+ | DEXOPT_BOOTCOMPLETE);
+
try {
- mPackageManagerService.mInstaller.dexopt(path, sharedGid,
- pkg.packageName, dexCodeInstructionSet, dexoptNeeded, oatDir,
- dexFlags, pkg.volumeUuid, useProfiles);
+ mInstaller.dexopt(path, sharedGid, pkg.packageName, dexCodeInstructionSet,
+ dexoptNeeded, oatDir, dexFlags, pkg.volumeUuid, useProfiles);
performedDexOpt = true;
} catch (InstallerException e) {
Slog.w(TAG, "Failed to dexopt", e);
@@ -201,7 +220,7 @@
// it isn't required. We therefore mark that this package doesn't need dexopt unless
// it's forced. performedDexOpt will tell us whether we performed dex-opt or skipped
// it.
- pkg.mDexOptPerformed.add(dexCodeInstructionSet);
+ recordSuccessfulDexopt(pkg, dexCodeInstructionSet);
}
}
@@ -232,8 +251,7 @@
if (codePath.isDirectory()) {
File oatDir = getOatDir(codePath);
try {
- mPackageManagerService.mInstaller.createOatDir(oatDir.getAbsolutePath(),
- dexInstructionSet);
+ mInstaller.createOatDir(oatDir.getAbsolutePath(), dexInstructionSet);
} catch (InstallerException e) {
Slog.w(TAG, "Failed to create oat dir", e);
return null;
@@ -247,21 +265,36 @@
return new File(codePath, OAT_DIR_NAME);
}
- private void performDexOptLibsLI(ArrayList<String> libs, String[] instructionSets,
- ArraySet<String> done) {
- for (String libName : libs) {
- PackageParser.Package libPkg = mPackageManagerService.findSharedNonSystemLibrary(
- libName);
- if (libPkg != null && !done.contains(libName)) {
- // TODO: Analyze and investigate if we (should) profile libraries.
- // Currently this will do a full compilation of the library.
- performDexOptLI(libPkg, instructionSets, done, /*useProfiles*/ false,
- /* extractOnly */ false, /* force */ false);
- }
- }
- }
-
void systemReady() {
mSystemReady = true;
}
+
+ /**
+ * A specialized PackageDexOptimizer that overrides already-installed checks, forcing a
+ * dexopt path.
+ */
+ public static class ForcedUpdatePackageDexOptimizer extends PackageDexOptimizer {
+
+ public ForcedUpdatePackageDexOptimizer(Installer installer, Object installLock,
+ Context context, String wakeLockTag) {
+ super(installer, installLock, context, wakeLockTag);
+ }
+
+ public ForcedUpdatePackageDexOptimizer(PackageDexOptimizer from) {
+ super(from);
+ }
+
+ @Override
+ protected boolean shouldSkipBasedOnISA(Package pkg, String instructionSet) {
+ // Forced compilation, never skip.
+ return false;
+ }
+
+ @Override
+ protected int adjustDexoptNeeded(int dexoptNeeded) {
+ // Ensure compilation, no matter the current state.
+ // TODO: The return value is wrong when patchoat is needed.
+ return DexFile.DEX2OAT_NEEDED;
+ }
+ }
}
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index d3b9187..553a9a2 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -139,6 +139,7 @@
import android.content.pm.PackageManagerInternal;
import android.content.pm.PackageParser;
import android.content.pm.PackageParser.ActivityIntentInfo;
+import android.content.pm.PackageParser.Package;
import android.content.pm.PackageParser.PackageLite;
import android.content.pm.PackageParser.PackageParserException;
import android.content.pm.PackageStats;
@@ -263,6 +264,7 @@
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStream;
+import java.io.PrintStream;
import java.io.PrintWriter;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
@@ -277,6 +279,7 @@
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
+import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
@@ -313,7 +316,12 @@
private static final boolean DEBUG_INTENT_MATCHING = false;
private static final boolean DEBUG_PACKAGE_SCANNING = false;
private static final boolean DEBUG_VERIFY = false;
- private static final boolean DEBUG_DEXOPT = false;
+
+ // Debug output for dexopting. This is shared between PackageManagerService, OtaDexoptService
+ // and PackageDexOptimizer. All these classes have their own flag to allow switching a single
+ // user, but by default initialize to this.
+ static final boolean DEBUG_DEXOPT = false;
+
private static final boolean DEBUG_ABI_SELECTION = false;
private static final boolean DEBUG_EPHEMERAL = false;
private static final boolean DEBUG_TRIAGED_MISSING = false;
@@ -1990,7 +1998,8 @@
}
mInstaller = installer;
- mPackageDexOptimizer = new PackageDexOptimizer(this);
+ mPackageDexOptimizer = new PackageDexOptimizer(installer, mInstallLock, context,
+ "*dexopt*");
mMoveCallbacks = new MoveCallbacks(FgThread.get().getLooper());
mOnPermissionChangeListeners = new OnPermissionChangeListeners(
@@ -3377,19 +3386,6 @@
return null;
}
- /**
- * @hide
- */
- PackageParser.Package findSharedNonSystemLibrary(String libName) {
- synchronized (mPackages) {
- PackageManagerService.SharedLibraryEntry lib = mSharedLibraries.get(libName);
- if (lib != null && lib.apk != null) {
- return mPackages.get(lib.apk);
- }
- }
- return null;
- }
-
@Override
public FeatureInfo[] getSystemAvailableFeatures() {
Collection<FeatureInfo> featSet;
@@ -6718,8 +6714,8 @@
try {
synchronized (mInstallLock) {
final String[] instructionSets = new String[] { targetInstructionSet };
- int result = mPackageDexOptimizer.performDexOpt(p, instructionSets,
- true /* inclDependencies */, useProfiles, extractOnly, force);
+ int result = performDexOptInternalWithDependenciesLI(p, instructionSets,
+ useProfiles, extractOnly, force);
return result == PackageDexOptimizer.DEX_OPT_PERFORMED;
}
} finally {
@@ -6739,6 +6735,80 @@
return pkgs;
}
+ private int performDexOptInternalWithDependenciesLI(PackageParser.Package p,
+ String instructionSets[], boolean useProfiles, boolean extractOnly, boolean force) {
+ // Select the dex optimizer based on the force parameter.
+ // Note: The force option is rarely used (cmdline input for testing, mostly), so it's OK to
+ // allocate an object here.
+ PackageDexOptimizer pdo = force
+ ? new PackageDexOptimizer.ForcedUpdatePackageDexOptimizer(mPackageDexOptimizer)
+ : mPackageDexOptimizer;
+
+ // Optimize all dependencies first. Note: we ignore the return value and march on
+ // on errors.
+ Collection<PackageParser.Package> deps = findSharedNonSystemLibraries(p);
+ if (!deps.isEmpty()) {
+ for (PackageParser.Package depPackage : deps) {
+ // TODO: Analyze and investigate if we (should) profile libraries.
+ // Currently this will do a full compilation of the library.
+ pdo.performDexOpt(depPackage, instructionSets, false /* useProfiles */,
+ false /* extractOnly */);
+ }
+ }
+
+ return pdo.performDexOpt(p, instructionSets, useProfiles, extractOnly);
+ }
+
+ Collection<PackageParser.Package> findSharedNonSystemLibraries(PackageParser.Package p) {
+ if (p.usesLibraries != null || p.usesOptionalLibraries != null) {
+ ArrayList<PackageParser.Package> retValue = new ArrayList<>();
+ Set<String> collectedNames = new HashSet<>();
+ findSharedNonSystemLibrariesRecursive(p, retValue, collectedNames);
+
+ retValue.remove(p);
+
+ return retValue;
+ } else {
+ return Collections.emptyList();
+ }
+ }
+
+ private void findSharedNonSystemLibrariesRecursive(PackageParser.Package p,
+ Collection<PackageParser.Package> collected, Set<String> collectedNames) {
+ if (!collectedNames.contains(p.packageName)) {
+ collectedNames.add(p.packageName);
+ collected.add(p);
+
+ if (p.usesLibraries != null) {
+ findSharedNonSystemLibrariesRecursive(p.usesLibraries, collected, collectedNames);
+ }
+ if (p.usesOptionalLibraries != null) {
+ findSharedNonSystemLibrariesRecursive(p.usesOptionalLibraries, collected,
+ collectedNames);
+ }
+ }
+ }
+
+ private void findSharedNonSystemLibrariesRecursive(Collection<String> libs,
+ Collection<PackageParser.Package> collected, Set<String> collectedNames) {
+ for (String libName : libs) {
+ PackageParser.Package libPkg = findSharedNonSystemLibrary(libName);
+ if (libPkg != null) {
+ findSharedNonSystemLibrariesRecursive(libPkg, collected, collectedNames);
+ }
+ }
+ }
+
+ private PackageParser.Package findSharedNonSystemLibrary(String libName) {
+ synchronized (mPackages) {
+ PackageManagerService.SharedLibraryEntry lib = mSharedLibraries.get(libName);
+ if (lib != null && lib.apk != null) {
+ return mPackages.get(lib.apk);
+ }
+ }
+ return null;
+ }
+
public void shutdown() {
mPackageUsage.write(true);
}
@@ -6763,9 +6833,8 @@
// Whoever is calling forceDexOpt wants a fully compiled package.
// Don't use profiles since that may cause compilation to be skipped.
- final int res = mPackageDexOptimizer.performDexOpt(pkg, instructionSets,
- true /* inclDependencies */, false /* useProfiles */,
- false /* extractOnly */, true /* force */);
+ final int res = performDexOptInternalWithDependenciesLI(pkg, instructionSets,
+ false /* useProfiles */, false /* extractOnly */, true /* force */);
Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
if (res != PackageDexOptimizer.DEX_OPT_PERFORMED) {
@@ -13042,8 +13111,7 @@
// Do not run PackageDexOptimizer through the local performDexOpt
// method because `pkg` is not in `mPackages` yet.
int result = mPackageDexOptimizer.performDexOpt(pkg, null /* instructionSets */,
- false /* inclDependencies */, false /* useProfiles */,
- true /* extractOnly */, false /* force */);
+ false /* useProfiles */, true /* extractOnly */);
Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
if (result == PackageDexOptimizer.DEX_OPT_FAILED) {
String msg = "Extracking package failed for " + pkgName;
@@ -18043,4 +18111,8 @@
"Cannot call " + tag + " from UID " + callingUid);
}
}
+
+ boolean isHistoricalPackageUsageAvailable() {
+ return mPackageUsage.isHistoricalPackageUsageAvailable();
+ }
}
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 47bdb5c..14e4fd5 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -7260,8 +7260,18 @@
mTaskPositioner = new TaskPositioner(this);
mTaskPositioner.register(display);
mInputMonitor.updateInputWindowsLw(true /*force*/);
+
+ // We need to grab the touch focus so that the touch events during the
+ // resizing/scrolling are not sent to the app. 'win' is the main window
+ // of the app, it may not have focus since there might be other windows
+ // on top (eg. a dialog window).
+ WindowState transferFocusFromWin = win;
+ if (mCurrentFocus != null && mCurrentFocus != win
+ && mCurrentFocus.mAppToken == win.mAppToken) {
+ transferFocusFromWin = mCurrentFocus;
+ }
if (!mInputManager.transferTouchFocus(
- win.mInputChannel, mTaskPositioner.mServerChannel)) {
+ transferFocusFromWin.mInputChannel, mTaskPositioner.mServerChannel)) {
Slog.e(TAG_WM, "startPositioningLocked: Unable to transfer touch focus");
mTaskPositioner.unregister();
mTaskPositioner = null;
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index e8b9b1f..572843e 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -1463,6 +1463,14 @@
void securityLogSetLoggingEnabledProperty(boolean enabled) {
SecurityLog.setLoggingEnabledProperty(enabled);
}
+
+ boolean securityLogGetLoggingEnabledProperty() {
+ return SecurityLog.getLoggingEnabledProperty();
+ }
+
+ boolean securityLogIsLoggingEnabled() {
+ return SecurityLog.isLoggingEnabled();
+ }
}
/**
@@ -1607,7 +1615,7 @@
if (mOwners.hasDeviceOwner()) {
mInjector.systemPropertiesSet(PROPERTY_DEVICE_OWNER_PRESENT, "true");
disableDeviceLoggingIfNotCompliant();
- if (SecurityLog.getLoggingEnabledProperty()) {
+ if (mInjector.securityLogGetLoggingEnabledProperty()) {
mSecurityLogMonitor.start();
}
} else {
@@ -4472,7 +4480,7 @@
mInjector.binderRestoreCallingIdentity(ident);
}
- if (SecurityLog.isLoggingEnabled()) {
+ if (mInjector.securityLogIsLoggingEnabled()) {
SecurityLog.writeEvent(SecurityLog.TAG_KEYGUARD_DISMISS_AUTH_ATTEMPT, /*result*/ 0);
}
}
@@ -4502,7 +4510,7 @@
}
}
- if (SecurityLog.isLoggingEnabled()) {
+ if (mInjector.securityLogIsLoggingEnabled()) {
SecurityLog.writeEvent(SecurityLog.TAG_KEYGUARD_DISMISS_AUTH_ATTEMPT, /*result*/ 1);
}
}
@@ -4511,7 +4519,7 @@
public void reportKeyguardDismissed() {
mContext.enforceCallingOrSelfPermission(
android.Manifest.permission.BIND_DEVICE_ADMIN, null);
- if (SecurityLog.isLoggingEnabled()) {
+ if (mInjector.securityLogIsLoggingEnabled()) {
SecurityLog.writeEvent(SecurityLog.TAG_KEYGUARD_DISMISSED);
}
}
@@ -4520,7 +4528,7 @@
public void reportKeyguardSecured() {
mContext.enforceCallingOrSelfPermission(
android.Manifest.permission.BIND_DEVICE_ADMIN, null);
- if (SecurityLog.isLoggingEnabled()) {
+ if (mInjector.securityLogIsLoggingEnabled()) {
SecurityLog.writeEvent(SecurityLog.TAG_KEYGUARD_SECURED);
}
}
@@ -8352,7 +8360,7 @@
Preconditions.checkNotNull(admin);
synchronized (this) {
getActiveAdminForCallerLocked(admin, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER);
- return SecurityLog.getLoggingEnabledProperty();
+ return mInjector.securityLogGetLoggingEnabledProperty();
}
}
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 254a37a..b64db57 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -80,6 +80,7 @@
import com.android.server.pm.BackgroundDexOptService;
import com.android.server.pm.Installer;
import com.android.server.pm.LauncherAppsService;
+import com.android.server.pm.OtaDexoptService;
import com.android.server.pm.PackageManagerService;
import com.android.server.pm.UserManagerService;
import com.android.server.power.PowerManagerService;
@@ -1097,6 +1098,18 @@
}
Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER);
+ // Manages A/B OTA dexopting.
+ boolean disableOtaDexopt = SystemProperties.getBoolean("config.disable_otadexopt",
+ false);
+ if (!disableOtaDexopt) {
+ traceBeginAndSlog("StartOtaDexOptService");
+ try {
+ OtaDexoptService.main(mSystemContext, mPackageManagerService);
+ } catch (Throwable e) {
+ reportWtf("starting BackgroundDexOptService", e);
+ }
+ Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER);
+ }
}
mSystemServiceManager.startService(LauncherAppsService.class);
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java
index bd37f4a..db2a9ad 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java
@@ -291,5 +291,15 @@
void securityLogSetLoggingEnabledProperty(boolean enabled) {
context.settings.securityLogSetLoggingEnabledProperty(enabled);
}
+
+ @Override
+ boolean securityLogGetLoggingEnabledProperty() {
+ return context.settings.securityLogGetLoggingEnabledProperty();
+ }
+
+ @Override
+ boolean securityLogIsLoggingEnabled() {
+ return context.settings.securityLogIsLoggingEnabled();
+ }
}
}
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java
index 6518732..7a00098 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java
@@ -198,6 +198,14 @@
void securityLogSetLoggingEnabledProperty(boolean enabled) {
}
+
+ public boolean securityLogGetLoggingEnabledProperty() {
+ return false;
+ }
+
+ public boolean securityLogIsLoggingEnabled() {
+ return false;
+ }
}
public final Context realTestContext;
diff --git a/telecomm/java/com/android/internal/telecom/ITelecomService.aidl b/telecomm/java/com/android/internal/telecom/ITelecomService.aidl
index af36b3e..95c8db5 100644
--- a/telecomm/java/com/android/internal/telecom/ITelecomService.aidl
+++ b/telecomm/java/com/android/internal/telecom/ITelecomService.aidl
@@ -243,4 +243,9 @@
* @see TelecomServiceImpl#setDefaultDialer
*/
boolean setDefaultDialer(in String packageName);
+
+ /**
+ * @see TelecomServiceImpl#launchManageBlockedNumbersActivity
+ **/
+ void launchManageBlockedNumbersActivity(in String callingPackageName);
}
diff --git a/telephony/java/android/telephony/DisconnectCause.java b/telephony/java/android/telephony/DisconnectCause.java
index 8443490..9eb1304 100644
--- a/telephony/java/android/telephony/DisconnectCause.java
+++ b/telephony/java/android/telephony/DisconnectCause.java
@@ -187,6 +187,13 @@
*/
public static final int CDMA_ALREADY_ACTIVATED = 49;
+ /**
+ * The call was terminated because it is not possible to place a video call while TTY is
+ * enabled.
+ * {@hide}
+ */
+ public static final int VIDEO_CALL_NOT_ALLOWED_WHILE_TTY_ENABLED = 50;
+
//*********************************************************************************************
// When adding a disconnect type:
// 1) Please assign the new type the next id value below.
@@ -202,7 +209,7 @@
public static final int MINIMUM_VALID_VALUE = NOT_DISCONNECTED;
/** Largest valid value for call disconnect codes. */
- public static final int MAXIMUM_VALID_VALUE = CDMA_ALREADY_ACTIVATED;
+ public static final int MAXIMUM_VALID_VALUE = VIDEO_CALL_NOT_ALLOWED_WHILE_TTY_ENABLED;
/** Private constructor to avoid class instantiation. */
private DisconnectCause() {
@@ -310,6 +317,8 @@
return "IMS_MERGED_SUCCESSFULLY";
case CDMA_ALREADY_ACTIVATED:
return "CDMA_ALREADY_ACTIVATED";
+ case VIDEO_CALL_NOT_ALLOWED_WHILE_TTY_ENABLED:
+ return "VIDEO_CALL_NOT_ALLOWED_WHILE_TTY_ENABLED";
default:
return "INVALID: " + cause;
}