Merge "Fix bar visibility after restarting immersive activities" into lmp-dev
diff --git a/cmds/am/src/com/android/commands/am/Am.java b/cmds/am/src/com/android/commands/am/Am.java
index 57c1505..5ba7d50 100644
--- a/cmds/am/src/com/android/commands/am/Am.java
+++ b/cmds/am/src/com/android/commands/am/Am.java
@@ -48,6 +48,7 @@
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
import android.os.ServiceManager;
+import android.os.SystemClock;
import android.os.SystemProperties;
import android.os.UserHandle;
import android.text.TextUtils;
@@ -726,6 +727,7 @@
IActivityManager.WaitResult result = null;
int res;
+ final long startTime = SystemClock.uptimeMillis();
if (mWaitOption) {
result = mAm.startActivityAndWait(null, null, intent, mimeType,
null, null, 0, mStartFlags, profilerInfo, null, mUserId);
@@ -734,6 +736,7 @@
res = mAm.startActivityAsUser(null, null, intent, mimeType,
null, null, 0, mStartFlags, profilerInfo, null, mUserId);
}
+ final long endTime = SystemClock.uptimeMillis();
PrintStream out = mWaitOption ? System.out : System.err;
boolean launched = false;
switch (res) {
@@ -811,6 +814,7 @@
if (result.totalTime >= 0) {
System.out.println("TotalTime: " + result.totalTime);
}
+ System.out.println("WaitTime: " + (endTime-startTime));
System.out.println("Complete");
}
mRepeat--;
diff --git a/cmds/dpm/src/com/android/commands/dpm/Dpm.java b/cmds/dpm/src/com/android/commands/dpm/Dpm.java
index 6a5ecee..b8b2087 100644
--- a/cmds/dpm/src/com/android/commands/dpm/Dpm.java
+++ b/cmds/dpm/src/com/android/commands/dpm/Dpm.java
@@ -39,6 +39,7 @@
}
private static final String COMMAND_SET_DEVICE_OWNER = "set-device-owner";
+ private static final String COMMAND_SET_PROFILE_OWNER = "set-profile-owner";
private IDevicePolicyManager mDevicePolicyManager;
@@ -47,9 +48,13 @@
out.println(
"usage: dpm [subcommand] [options]\n" +
"usage: dpm set-device-owner <COMPONENT>\n" +
+ "usage: dpm set-profile-owner <COMPONENT> <USER_ID>\n" +
"\n" +
"dpm set-device-owner: Sets the given component as active admin, and its\n" +
- " package as device owner.\n");
+ " package as device owner.\n" +
+ "\n" +
+ "dpm set-profile-owner: Sets the given component as active admin and profile" +
+ " owner for an existing user.\n");
}
@Override
@@ -64,24 +69,25 @@
String command = nextArgRequired();
switch (command) {
case COMMAND_SET_DEVICE_OWNER:
- runSetDeviceOwner(nextArgRequired());
+ runSetDeviceOwner();
+ break;
+ case COMMAND_SET_PROFILE_OWNER:
+ runSetProfileOwner();
break;
default:
throw new IllegalArgumentException ("unknown command '" + command + "'");
}
}
- private void runSetDeviceOwner(String argument) throws Exception {
- ComponentName component = ComponentName.unflattenFromString(argument);
- if (component == null) {
- throw new IllegalArgumentException ("Invalid component " + argument);
- }
- mDevicePolicyManager.setActiveAdmin(component, true, UserHandle.USER_OWNER);
+ private void runSetDeviceOwner() throws RemoteException {
+ ComponentName component = parseComponentName(nextArgRequired());
+ mDevicePolicyManager.setActiveAdmin(component, true /*refreshing*/, UserHandle.USER_OWNER);
String packageName = component.getPackageName();
try {
- if (!mDevicePolicyManager.setDeviceOwner(packageName, null)) {
- throw new Exception("Can't set package " + packageName + " as device owner.");
+ if (!mDevicePolicyManager.setDeviceOwner(packageName, null /*ownerName*/)) {
+ throw new RuntimeException(
+ "Can't set package " + packageName + " as device owner.");
}
} catch (Exception e) {
// Need to remove the admin that we just added.
@@ -91,4 +97,39 @@
System.out.println("Device owner set to package " + packageName);
System.out.println("Active admin set to component " + component.toShortString());
}
+
+ private void runSetProfileOwner() throws RemoteException {
+ ComponentName component = parseComponentName(nextArgRequired());
+ int userId = parseInt(nextArgRequired());
+ mDevicePolicyManager.setActiveAdmin(component, true /*refreshing*/, userId);
+
+ try {
+ if (!mDevicePolicyManager.setProfileOwner(component, "" /*ownerName*/, userId)) {
+ throw new RuntimeException("Can't set component " + component.toShortString() +
+ " as profile owner for user " + userId);
+ }
+ } catch (Exception e) {
+ // Need to remove the admin that we just added.
+ mDevicePolicyManager.removeActiveAdmin(component, userId);
+ throw e;
+ }
+ System.out.println("Active admin and profile owner set to " + component.toShortString() +
+ " for user " + userId);
+ }
+
+ private ComponentName parseComponentName(String component) {
+ ComponentName cn = ComponentName.unflattenFromString(component);
+ if (cn == null) {
+ throw new IllegalArgumentException ("Invalid component " + component);
+ }
+ return cn;
+ }
+
+ private int parseInt(String argument) {
+ try {
+ return Integer.parseInt(argument);
+ } catch (NumberFormatException e) {
+ throw new IllegalArgumentException ("Invalid integer argument '" + argument + "'", e);
+ }
+ }
}
\ No newline at end of file
diff --git a/cmds/pm/src/com/android/commands/pm/Pm.java b/cmds/pm/src/com/android/commands/pm/Pm.java
index 6d12133..5e9d8f7 100644
--- a/cmds/pm/src/com/android/commands/pm/Pm.java
+++ b/cmds/pm/src/com/android/commands/pm/Pm.java
@@ -1190,10 +1190,6 @@
if (userId < 0) {
info = mUm.createUser(name, flags);
} else {
- if (Process.myUid() != 0) {
- System.err.println("Error: not running as root.");
- return;
- }
info = mUm.createProfileForUser(name, flags, userId);
}
if (info != null) {
diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java
index 7c69a7d..e3cbef5 100644
--- a/core/java/android/net/ConnectivityManager.java
+++ b/core/java/android/net/ConnectivityManager.java
@@ -1402,6 +1402,20 @@
return (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
}
+ /** {@hide */
+ public static final void enforceTetherChangePermission(Context context) {
+ if (context.getResources().getStringArray(
+ com.android.internal.R.array.config_mobile_hotspot_provision_app).length == 2) {
+ // Have a provisioning app - must only let system apps (which check this app)
+ // turn on tethering
+ context.enforceCallingOrSelfPermission(
+ android.Manifest.permission.CONNECTIVITY_INTERNAL, "ConnectivityService");
+ } else {
+ context.enforceCallingOrSelfPermission(
+ android.Manifest.permission.CHANGE_NETWORK_STATE, "ConnectivityService");
+ }
+ }
+
/**
* Get the set of tetherable, available interfaces. This list is limited by
* device configuration and current interface existence.
diff --git a/core/java/android/net/NetworkStats.java b/core/java/android/net/NetworkStats.java
index 54d43d3..ea5dfd1 100644
--- a/core/java/android/net/NetworkStats.java
+++ b/core/java/android/net/NetworkStats.java
@@ -24,6 +24,8 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.ArrayUtils;
+import libcore.util.EmptyArray;
+
import java.io.CharArrayWriter;
import java.io.PrintWriter;
import java.util.Arrays;
@@ -169,6 +171,15 @@
} else {
// Special case for use by NetworkStatsFactory to start out *really* empty.
this.capacity = 0;
+ this.iface = EmptyArray.STRING;
+ this.uid = EmptyArray.INT;
+ this.set = EmptyArray.INT;
+ this.tag = EmptyArray.INT;
+ this.rxBytes = EmptyArray.LONG;
+ this.rxPackets = EmptyArray.LONG;
+ this.txBytes = EmptyArray.LONG;
+ this.txPackets = EmptyArray.LONG;
+ this.operations = EmptyArray.LONG;
}
}
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index c25278f..82016c3 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -19,6 +19,7 @@
import android.app.ActivityManager;
import android.app.ActivityManagerNative;
import android.content.Context;
+import android.content.pm.PackageManager;
import android.content.pm.UserInfo;
import android.content.res.Resources;
import android.graphics.Bitmap;
@@ -430,7 +431,8 @@
* @return whether the user making this call is a goat
*/
public boolean isUserAGoat() {
- return false;
+ return mContext.getPackageManager()
+ .isPackageAvailable("com.coffeestainstudios.goatsimulator");
}
/**
diff --git a/core/java/android/service/voice/AlwaysOnHotwordDetector.java b/core/java/android/service/voice/AlwaysOnHotwordDetector.java
index 8aa2689..ac7d539 100644
--- a/core/java/android/service/voice/AlwaysOnHotwordDetector.java
+++ b/core/java/android/service/voice/AlwaysOnHotwordDetector.java
@@ -170,8 +170,7 @@
= SoundTrigger.RECOGNITION_MODE_USER_IDENTIFICATION;
static final String TAG = "AlwaysOnHotwordDetector";
- // TODO: Set to false.
- static final boolean DBG = true;
+ static final boolean DBG = false;
private static final int STATUS_ERROR = SoundTrigger.STATUS_ERROR;
private static final int STATUS_OK = SoundTrigger.STATUS_OK;
@@ -575,7 +574,7 @@
int code = STATUS_ERROR;
try {
code = mModelManagementService.startRecognition(mVoiceInteractionService,
- mKeyphraseMetadata.id, mInternalCallback,
+ mKeyphraseMetadata.id, mLocale.toLanguageTag(), mInternalCallback,
new RecognitionConfig(captureTriggerAudio, allowMultipleTriggers,
recognitionExtra, null /* additional data */));
} catch (RemoteException e) {
@@ -690,7 +689,7 @@
if (availability == STATE_NOT_READY
|| availability == STATE_KEYPHRASE_UNENROLLED
|| availability == STATE_KEYPHRASE_ENROLLED) {
- enrolled = internalGetIsEnrolled(mKeyphraseMetadata.id);
+ enrolled = internalGetIsEnrolled(mKeyphraseMetadata.id, mLocale);
if (!enrolled) {
availability = STATE_KEYPHRASE_UNENROLLED;
} else {
@@ -741,10 +740,10 @@
/**
* @return The corresponding {@link KeyphraseSoundModel} or null if none is found.
*/
- private boolean internalGetIsEnrolled(int keyphraseId) {
+ private boolean internalGetIsEnrolled(int keyphraseId, Locale locale) {
try {
return mModelManagementService.isEnrolledForKeyphrase(
- mVoiceInteractionService, keyphraseId);
+ mVoiceInteractionService, keyphraseId, locale.toLanguageTag());
} catch (RemoteException e) {
Slog.w(TAG, "RemoteException in listRegisteredKeyphraseSoundModels!", e);
}
diff --git a/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl b/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl
index 22ec4be..5a10524 100644
--- a/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl
+++ b/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl
@@ -33,32 +33,44 @@
void finish(IBinder token);
/**
- * Lists the registered Sound model for keyphrase detection.
- * May be null if no matching sound models exist.
+ * Gets the registered Sound model for keyphrase detection for the current user.
+ * May be null if no matching sound model exists.
+ *
+ * @param keyphraseId The unique identifier for the keyphrase.
+ * @param bcp47Locale The BCP47 language tag for the keyphrase's locale.
*/
- SoundTrigger.KeyphraseSoundModel getKeyphraseSoundModel(int keyphraseId);
+ SoundTrigger.KeyphraseSoundModel getKeyphraseSoundModel(int keyphraseId, in String bcp47Locale);
/**
- * Updates the given keyphrase sound model. Adds the model if it doesn't exist currently.
+ * Add/Update the given keyphrase sound model.
*/
int updateKeyphraseSoundModel(in SoundTrigger.KeyphraseSoundModel model);
/**
- * Deletes the given keyphrase sound model.
+ * Deletes the given keyphrase sound model for the current user.
+ *
+ * @param keyphraseId The unique identifier for the keyphrase.
+ * @param bcp47Locale The BCP47 language tag for the keyphrase's locale.
*/
- int deleteKeyphraseSoundModel(int keyphraseId);
+ int deleteKeyphraseSoundModel(int keyphraseId, in String bcp47Locale);
/**
- * Indicates if there's a keyphrase sound model available for the given keyphrase ID.
- */
- boolean isEnrolledForKeyphrase(IVoiceInteractionService service, int keyphraseId);
- /**
* Gets the properties of the DSP hardware on this device, null if not present.
*/
SoundTrigger.ModuleProperties getDspModuleProperties(in IVoiceInteractionService service);
/**
+ * Indicates if there's a keyphrase sound model available for the given keyphrase ID.
+ * This performs the check for the current user.
+ *
+ * @param service The current VoiceInteractionService.
+ * @param keyphraseId The unique identifier for the keyphrase.
+ * @param bcp47Locale The BCP47 language tag for the keyphrase's locale.
+ */
+ boolean isEnrolledForKeyphrase(IVoiceInteractionService service, int keyphraseId,
+ String bcp47Locale);
+ /**
* Starts a recognition for the given keyphrase.
*/
int startRecognition(in IVoiceInteractionService service, int keyphraseId,
- in IRecognitionStatusCallback callback,
+ in String bcp47Locale, in IRecognitionStatusCallback callback,
in SoundTrigger.RecognitionConfig recognitionConfig);
/**
* Stops a recognition for the given keyphrase.
diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java
index 107e8c6..22600de 100644
--- a/core/java/com/android/internal/app/ResolverActivity.java
+++ b/core/java/com/android/internal/app/ResolverActivity.java
@@ -491,8 +491,7 @@
}
private boolean versionNumberAtLeastL(int versionNumber) {
- // TODO: remove "|| true" once the build code for L is fixed.
- return versionNumber >= Build.VERSION_CODES.L || true;
+ return versionNumber >= Build.VERSION_CODES.L;
}
private void setAlwaysButtonEnabled(boolean hasValidSelection, int checkedPos,
diff --git a/core/java/com/android/internal/app/WindowDecorActionBar.java b/core/java/com/android/internal/app/WindowDecorActionBar.java
index a8f7bb3..2377c22 100644
--- a/core/java/com/android/internal/app/WindowDecorActionBar.java
+++ b/core/java/com/android/internal/app/WindowDecorActionBar.java
@@ -866,14 +866,7 @@
mDecorToolbar.animateToVisibility(toActionMode ? View.GONE : View.VISIBLE);
mContextView.animateToVisibility(toActionMode ? View.VISIBLE : View.GONE);
- if (mTabScrollView != null && !mDecorToolbar.hasEmbeddedTabs() &&
- isCollapsed(mDecorToolbar.getViewGroup())) {
- mTabScrollView.animateToVisibility(toActionMode ? View.GONE : View.VISIBLE);
- }
- }
-
- private boolean isCollapsed(View view) {
- return view == null || view.getVisibility() == View.GONE || view.getMeasuredHeight() == 0;
+ // mTabScrollView's visibility is not affected by action mode.
}
public Context getThemedContext() {
diff --git a/core/java/com/android/internal/widget/ActionBarContainer.java b/core/java/com/android/internal/widget/ActionBarContainer.java
index d24f32f..847a47d 100644
--- a/core/java/com/android/internal/widget/ActionBarContainer.java
+++ b/core/java/com/android/internal/widget/ActionBarContainer.java
@@ -260,6 +260,11 @@
return view == null || view.getVisibility() == GONE || view.getMeasuredHeight() == 0;
}
+ private int getMeasuredHeightWithMargins(View view) {
+ final LayoutParams lp = (LayoutParams) view.getLayoutParams();
+ return view.getMeasuredHeight() + lp.topMargin + lp.bottomMargin;
+ }
+
@Override
public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (mActionBarView == null &&
@@ -271,26 +276,23 @@
if (mActionBarView == null) return;
- int nonTabMaxHeight = 0;
- final int childCount = getChildCount();
- for (int i = 0; i < childCount; i++) {
- final View child = getChildAt(i);
- if (child == mTabContainer) {
- continue;
- }
- final LayoutParams lp = (LayoutParams) child.getLayoutParams();
- nonTabMaxHeight = Math.max(nonTabMaxHeight, isCollapsed(child) ? 0 :
- child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);
- }
-
if (mTabContainer != null && mTabContainer.getVisibility() != GONE) {
- final int mode = MeasureSpec.getMode(heightMeasureSpec);
- if (mode == MeasureSpec.AT_MOST) {
- final int maxHeight = MeasureSpec.getSize(heightMeasureSpec);
- setMeasuredDimension(getMeasuredWidth(),
- Math.min(nonTabMaxHeight + mTabContainer.getMeasuredHeight(),
- maxHeight));
+ int nonTabMaxHeight = 0;
+ final int childCount = getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ final View child = getChildAt(i);
+ if (child == mTabContainer) {
+ continue;
+ }
+ nonTabMaxHeight = Math.max(nonTabMaxHeight, isCollapsed(child) ? 0 :
+ getMeasuredHeightWithMargins(child));
}
+ final int mode = MeasureSpec.getMode(heightMeasureSpec);
+ final int maxHeight = mode == MeasureSpec.AT_MOST ?
+ MeasureSpec.getSize(heightMeasureSpec) : Integer.MAX_VALUE;
+ setMeasuredDimension(getMeasuredWidth(),
+ Math.min(nonTabMaxHeight + getMeasuredHeightWithMargins(mTabContainer),
+ maxHeight));
}
}
@@ -303,8 +305,10 @@
if (tabContainer != null && tabContainer.getVisibility() != GONE) {
final int containerHeight = getMeasuredHeight();
+ final LayoutParams lp = (LayoutParams) tabContainer.getLayoutParams();
final int tabHeight = tabContainer.getMeasuredHeight();
- tabContainer.layout(l, containerHeight - tabHeight, r, containerHeight);
+ tabContainer.layout(l, containerHeight - tabHeight - lp.bottomMargin, r,
+ containerHeight - lp.bottomMargin);
}
boolean needsInvalidate = false;
diff --git a/core/java/com/android/internal/widget/ToolbarWidgetWrapper.java b/core/java/com/android/internal/widget/ToolbarWidgetWrapper.java
index 63a4843..478c8f2 100644
--- a/core/java/com/android/internal/widget/ToolbarWidgetWrapper.java
+++ b/core/java/com/android/internal/widget/ToolbarWidgetWrapper.java
@@ -578,7 +578,7 @@
@Override
public void animateToVisibility(int visibility) {
if (visibility == View.GONE) {
- mToolbar.animate().translationY(mToolbar.getHeight()).alpha(0)
+ mToolbar.animate().alpha(0)
.setListener(new AnimatorListenerAdapter() {
private boolean mCanceled = false;
@Override
@@ -594,7 +594,7 @@
}
});
} else if (visibility == View.VISIBLE) {
- mToolbar.animate().translationY(0).alpha(1)
+ mToolbar.animate().alpha(1)
.setListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animation) {
diff --git a/core/res/res/drawable/ic_corp_icon_badge.xml b/core/res/res/drawable/ic_corp_icon_badge.xml
index 0b05bf5..0e1c63d 100644
--- a/core/res/res/drawable/ic_corp_icon_badge.xml
+++ b/core/res/res/drawable/ic_corp_icon_badge.xml
@@ -19,27 +19,29 @@
android:viewportWidth="64.0"
android:viewportHeight="64.0">
<path
- android:pathData="M49.062000,50.000000m-14.000000,0.000000a14.000000,14.000000 0.000000,1.000000 1.000000,28.000000 0.000000a14.000000,14.000000 0.000000,1.000000 1.000000,-28.000000 0.000000"
- android:fillColor="#FF000000"/>
+ android:fillColor="#FF000000"
+ android:pathData="M49.062,50.0m-14.0,0.0a14.0,14.0 0.0,1.0 1.0,28.0 0.0a14.0,14.0 0.0,1.0 1.0,-28.0 0.0"
+ android:fillAlpha="0.2"/>
<path
- android:pathData="M49.000000,49.500000m-14.000000,0.000000a14.000000,14.000000 0.000000,1.000000 1.000000,28.000000 0.000000a14.000000,14.000000 0.000000,1.000000 1.000000,-28.000000 0.000000"
- android:fillColor="#FF000000"/>
+ android:fillColor="#FF000000"
+ android:pathData="M49.0,49.5m-14.0,0.0a14.0,14.0 0.0,1.0 1.0,28.0 0.0a14.0,14.0 0.0,1.0 1.0,-28.0 0.0"
+ android:fillAlpha="0.2"/>
<path
- android:pathData="M49.000000,49.000000m-14.000000,0.000000a14.000000,14.000000 0.000000,1.000000 1.000000,28.000000 0.000000a14.000000,14.000000 0.000000,1.000000 1.000000,-28.000000 0.000000"
+ android:pathData="M49.0,49.0m-14.0,0.0a14.0,14.0 0.0,1.0 1.0,28.0 0.0a14.0,14.0 0.0,1.0 1.0,-28.0 0.0"
android:fillColor="#FF5722"/>
<path
- android:pathData="M55.250000,44.264000l-2.254000,0.000000l0.000000,-1.531000l-1.531000,-1.531000l-4.638000,0.000000l-1.531000,1.531000l0.000000,1.531000l-2.294000,0.000000c-0.846000,0.000000 -1.523000,0.685000 -1.523000,1.531000l-0.008000,8.421000c0.000000,0.846000 0.685000,1.531000 1.531000,1.531000L55.250000,55.746994c0.846000,0.000000 1.531000,-0.685000 1.531000,-1.531000l0.000000,-8.421000C56.782001,44.948002 56.097000,44.264000 55.250000,44.264000zM51.465000,44.264000l-4.638000,0.000000l0.000000,-1.531000l4.638000,0.000000L51.465000,44.264000z"
+ android:pathData="M55.25,44.264l-2.254,0.0l0.0,-1.531l-1.531,-1.531l-4.638,0.0l-1.531,1.531l0.0,1.531l-2.294,0.0c-0.846,0.0 -1.523,0.685 -1.523,1.531l-0.008,8.421c0.0,0.846 0.685,1.531 1.531,1.531L55.25,55.746994c0.846,0.0 1.531,-0.685 1.531,-1.531l0.0,-8.421C56.782,44.948 56.097,44.264 55.25,44.264zM51.465,44.264l-4.638,0.0l0.0,-1.531l4.638,0.0L51.465,44.264z"
android:fillColor="#FFFFFF"/>
<path
- android:pathData="M57.359001,45.373001c0.000000,-0.855000 -0.738000,-1.547000 -1.651000,-1.547000L42.535000,43.826000c-0.913000,0.000000 -1.643000,0.692000 -1.643000,1.547000l0.004000,3.232000c0.000000,0.911000 0.737000,1.648000 1.648000,1.648000l13.162000,0.000000c0.911000,0.000000 1.648000,-0.737000 1.648000,-1.648000L57.359001,45.373001z"
+ android:pathData="M57.359,45.373c0.0,-0.855 -0.738,-1.547 -1.651,-1.547L42.535,43.826c-0.913,0.0 -1.643,0.692 -1.643,1.547l0.004,3.232c0.0,0.911 0.737,1.648 1.648,1.648l13.162,0.0c0.911,0.0 1.648,-0.737 1.648,-1.648L57.359,45.373z"
android:fillColor="#FFFFFF"/>
<path
android:pathData="M40.726,40.726 h16.13 v16.13 h-16.13z"
android:fillColor="#00000000"/>
<path
- android:pathData="M40.000000,49.000000l17.000000,0.000000l0.000000,2.000000l-17.000000,0.000000z"
+ android:pathData="M40.0,49.0l17.0,0.0l0.0,2.0l-17.0,0.0z"
android:fillColor="#FF5722"/>
<path
- android:pathData="M47.625000,48.951000l3.003000,0.000000l0.000000,3.002000l-3.003000,0.000000z"
+ android:pathData="M47.625,48.951l3.003,0.0l0.0,3.002l-3.003,0.0z"
android:fillColor="#FF5722"/>
-</vector>
+</vector>
\ No newline at end of file
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index e95d735..ef3f47e 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -355,9 +355,9 @@
<!-- Integer parameters of the wifi to cellular handover feature
wifi should not stick to bad networks -->
- <integer translatable="false" name="config_wifi_framework_wifi_score_bad_rssi_threshold_5GHz">-60</integer>
+ <integer translatable="false" name="config_wifi_framework_wifi_score_bad_rssi_threshold_5GHz">-82</integer>
<integer translatable="false" name="config_wifi_framework_wifi_score_low_rssi_threshold_5GHz">-72</integer>
- <integer translatable="false" name="config_wifi_framework_wifi_score_good_rssi_threshold_5GHz">-82</integer>
+ <integer translatable="false" name="config_wifi_framework_wifi_score_good_rssi_threshold_5GHz">-60</integer>
<integer translatable="false" name="config_wifi_framework_wifi_score_bad_rssi_threshold_24GHz">-87</integer>
<integer translatable="false" name="config_wifi_framework_wifi_score_low_rssi_threshold_24GHz">-77</integer>
<integer translatable="false" name="config_wifi_framework_wifi_score_good_rssi_threshold_24GHz">-65</integer>
@@ -1791,6 +1791,7 @@
<item>SUPL_PORT=7275</item>
<item>NTP_SERVER=north-america.pool.ntp.org</item>
<item>SUPL_VER=0x20000</item>
+ <item>SUPL_MODE=0x01</item>
</string-array>
<!-- If there is no preload VM number in the sim card, carriers such as
diff --git a/core/res/res/values/dimens_material.xml b/core/res/res/values/dimens_material.xml
index 2b49ba2..ac5890a 100644
--- a/core/res/res/values/dimens_material.xml
+++ b/core/res/res/values/dimens_material.xml
@@ -45,8 +45,8 @@
<dimen name="text_size_title_material_toolbar">@dimen/text_size_title_material</dimen>
<dimen name="text_size_subtitle_material_toolbar">@dimen/text_size_subhead_material</dimen>
<dimen name="text_size_menu_material">16sp</dimen>
- <dimen name="text_size_body_2_material">16sp</dimen>
- <dimen name="text_size_body_1_material">16sp</dimen>
+ <dimen name="text_size_body_2_material">14sp</dimen>
+ <dimen name="text_size_body_1_material">14sp</dimen>
<dimen name="text_size_caption_material">12sp</dimen>
<dimen name="text_size_button_material">14sp</dimen>
diff --git a/core/res/res/values/themes_material.xml b/core/res/res/values/themes_material.xml
index a984cfa..ebff965 100644
--- a/core/res/res/values/themes_material.xml
+++ b/core/res/res/values/themes_material.xml
@@ -316,6 +316,8 @@
<item name="actionBarStyle">@style/Widget.Material.ActionBar.Solid</item>
<item name="actionBarSize">@dimen/action_bar_default_height_material</item>
<item name="actionModePopupWindowStyle">@style/Widget.Material.PopupWindow.ActionMode</item>
+ <item name="actionMenuTextAppearance">@style/TextAppearance.Material.Widget.ActionBar.Menu</item>
+ <item name="actionMenuTextColor">?attr/textColorPrimary</item>
<item name="actionBarWidgetTheme">@null</item>
<item name="actionBarPopupTheme">@null</item>
<item name="actionBarTheme">@style/ThemeOverlay.Material.ActionBar</item>
diff --git a/core/tests/coretests/src/android/net/NetworkStatsTest.java b/core/tests/coretests/src/android/net/NetworkStatsTest.java
index 6331964..9ee4e20 100644
--- a/core/tests/coretests/src/android/net/NetworkStatsTest.java
+++ b/core/tests/coretests/src/android/net/NetworkStatsTest.java
@@ -310,6 +310,16 @@
assertEquals(128L + 512L, clone.getTotalBytes());
}
+ public void testAddWhenEmpty() throws Exception {
+ final NetworkStats red = new NetworkStats(TEST_START, -1);
+ final NetworkStats blue = new NetworkStats(TEST_START, 5)
+ .addValues(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, 128L, 8L, 0L, 2L, 20L)
+ .addValues(TEST_IFACE2, 100, SET_DEFAULT, TAG_NONE, 512L, 32L, 0L, 0L, 0L);
+
+ // We're mostly checking that we don't crash
+ red.combineAllValues(blue);
+ }
+
private static void assertValues(NetworkStats stats, int index, String iface, int uid, int set,
int tag, long rxBytes, long rxPackets, long txBytes, long txPackets, long operations) {
final NetworkStats.Entry entry = stats.getValues(index, null);
diff --git a/data/keyboards/Vendor_0a5c_Product_8502.kl b/data/keyboards/Vendor_0a5c_Product_8502.kl
deleted file mode 100644
index 2f07328..0000000
--- a/data/keyboards/Vendor_0a5c_Product_8502.kl
+++ /dev/null
@@ -1,42 +0,0 @@
-# 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.
-
-# Rhodi
-
-key 304 BUTTON_A
-key 305 BUTTON_B
-key 307 BUTTON_X
-key 308 BUTTON_Y
-key 310 BUTTON_L1
-key 311 BUTTON_R1
-key 316 BUTTON_MODE
-key 317 BUTTON_THUMBL
-key 318 BUTTON_THUMBR
-
-key 158 BACK
-key 172 HOME
-
-axis 0x00 X
-axis 0x01 Y
-axis 0x02 Z
-axis 0x05 RZ
-axis 0x09 RTRIGGER
-axis 0x0a LTRIGGER
-axis 0x10 HAT_X
-axis 0x11 HAT_Y
-
-led 0x00 CONTROLLER_1
-led 0x01 CONTROLLER_2
-led 0x02 CONTROLLER_3
-led 0x03 CONTROLLER_4
diff --git a/docs/html/distribute/googleplay/edu/start.jd b/docs/html/distribute/googleplay/edu/start.jd
index 067a15f..3c3a175 100644
--- a/docs/html/distribute/googleplay/edu/start.jd
+++ b/docs/html/distribute/googleplay/edu/start.jd
@@ -9,27 +9,23 @@
<div id="qv-wrapper"><div id="qv">
<h2>Steps to Join</h2>
<ol>
-<li><a href="#register">Register for a Publisher Account</li>
+<li><a href="#register">Register for a Publisher Account</a></li>
<li><a href="#prepare">Prepare Your Apps</a></li>
<li><a href="#publish">Publish Your Apps</a></li>
<li><a href="#related-resources">Related Resources</a></li>
</ol>
</div></div>
<p>
- If you've got great Android apps for education and want to reach even more teachers
+ If you've got great Android and Chrome apps for education and want to reach even more teachers
and students, you can join the <strong>Google Play for Education</strong>
program in a few simple steps. You do everything using the familiar tools and
- processes in Google Play.
+ processes you currently use to manage your Android or Chrome apps.
</p>
<p>
Note that Google Play for Education is currently available to <strong>K-12 schools in the United
States</strong> only.</p>
-<p>If you have an educational Chrome app instead of an Android app, you can learn more about
-Chrome Apps in Google Play for Education at <a href=
-"https://developers.google.com/edu">developers.google.com/edu</a>.
-</p>
<img src="{@docRoot}images/gpfe-start-0.jpg" style=
"border:1px solid #ddd;padding:0px" width="760" height="403">
@@ -43,23 +39,20 @@
</div>
<p>
- If you’re new to Google Play, review the information on <a href=
+ To publish an Android app in Google Play for Education, review the information on <a href=
"{@docRoot}distribute/googleplay/start.html">getting started</a> with
publishing on Google Play. You’ll gain access to the <a href=
"{@docRoot}distribute/googleplay/developer-console.html">Developer
Console</a>, where you’ll manage your details, apps, and payments.
</p>
+<p>To publish a Chrome app in Google Play for Education, you'll need a
+<a href="https://developer.chrome.com/webstore/publish">Chrome Web Store account</a>.</p>
+
<div class="headerLine">
<h2 id="prepare">
Prepare Your Apps
</h2>
-
-
-</div>
-
-<div class="figure-right">
- <img src="{@docRoot}images/gp-edu-process.png">
</div>
<p>
@@ -77,13 +70,13 @@
To prepare for a launch on Google Play for Education, start by reviewing the
guidelines for educational apps in Google Play and the policies that apply to
your apps. See the <a href=
- "{@docRoot}distribute/essentials/gpfe-guidelines.html">Education
+ "https://developers.google.com/edu/guidelines">Education
Guidelines</a> for details.
</p>
<p>
Also, make sure that you're familiar with the policies that your app must
- comply with, including <a href=
+ comply with. For Android, they include <a href=
"http://play.google.com/about/developer-content-policy.html">content
policies</a>, the <a href=
"http://play.google.com/about/developer-distribution-agreement.html">Developer
@@ -91,6 +84,14 @@
"https://play.google.com/about/developer-distribution-agreement-addendum.html">
Google Play for Education Addendum</a>.
</p>
+<p>For Chrome, they include <a href=
+ "https://developer.chrome.com/webstore/program_policies">content
+ policies</a>, the <a href=
+ "https://developer.chrome.com/webstore/terms">Developer
+ Distribution Agreement</a>, and <a href=
+ "https://developers.google.com/edu/chrome-d-d-a-addendum">
+ Google Play for Education Addendum</a>.
+</p>
<h3>
Design and develop a great app for education
@@ -105,7 +106,7 @@
<p>
Assess your app against the criteria listed in the <a href=
- "{@docRoot}distribute/essentials/gpfe-guidelines.html">Education
+ "https://developers.google.com/edu/guidelines">Education
Guidelines</a> and plan on supporting them to the greatest extent possible.
In some cases you might need to modify the app’s features or UI to support
classroom requirements. It's a good idea to identify any changes early in
@@ -113,7 +114,7 @@
</p>
<p>
- With Google Play for Education, optimizing your apps for tablets is crucial.
+ With Google Play for Education, optimizing your Android apps for tablets is crucial.
A variety of resources are available to help you understand what you need to
do — a good starting point is the <a href=
"{@docRoot}distribute/essentials/quality/tablets.html">Tablet App Quality</a>
@@ -132,6 +133,11 @@
</p>
<p>
+For Chrome apps, optimizing your apps for mouse, keyboard, and touch is just as important.
+Some students will use touch as their primary input method, and some will have Chromebooks
+without touch. Make sure your app works for all students.
+</p>
+<p>
Comprehensive testing and quality assurance are key aspects of delivering
great apps for teachers and students. Make sure you set up a <a href=
"{@docRoot}distribute/essentials/gpfe-guidelines.html#test-environment">proper
@@ -160,15 +166,13 @@
apps.
</li>
- <li>Publish your apps in the Developer Console as normal, but opt-in to
+ <li>Publish your apps in the Developer Console (for Android apps) or the Chrome Web Store
+ (for Chrome apps) as normal, but opt-in to
Google Play for Education.
</li>
</ul>
-<h3 id="opt-in">
- Opt-in to Google Play for Education and publish
-</h3>
-
+<h3>Opt-in to Google Play for Education and publish Android apps</h3>
<p>
Once you've built your release-ready APK upload it to the Developer Console,
create your store listing, and set distribution options. If you aren't
@@ -231,7 +235,7 @@
will be shown to educators when they are browsing your app.
</li>
- <li>Click <strong>Save</strong>f to save your Pricing and Distribution
+ <li>Click <strong>Save</strong> to save your Pricing and Distribution
changes.
</li>
</ol>
@@ -247,58 +251,55 @@
</div>
<p>
- Once you save changes and publish your app, the app will be submitted to our
- third-party educator network for review and approval. If the app is already
- published, it will be submitted for review as soon as you opt-in and save
- your changes.
+ Once you save changes and publish your app, the app will be available on Google Play for
+ Education. We may submit your app to our
+ third-party educator network for additional review and to be featured and badged as
+ Educator-approved.
</p>
<p class="note">
<strong>Note</strong>: Google Play for Education is part of Google Play. When
- you publish an app that's opted-in to Google Play for Education, the app
- becomes available to users in Google Play right away. After the app is
- <a href="{@docRoot}distribute/essentials/gpfe-guidelines.html#e-value">review
- and approval</a>, it then becomes available to educators in Google Play for
- Education.
+ you publish an Android app that's opted-in to Google Play for Education, the app
+ becomes available to users in Google Play.
</p>
-<h3>
- Track your review and approval
-</h3>
-
+<h3>Opt-in to Google Play for Education and publish Chrome apps</h3>
<p>
- As soon as you opt-in to Google Play for Education and publish, your apps are
- queued for review by our third-party educator network. The review and
- approval process can take four weeks or more. You'll receive notification by
- email (to your developer account address) when the review is complete, with a
- summary of the review results.
+Once you've uploaded your app or extension to the Developer Dashboard, create your store listing
+and set distribution options.
</p>
-
<p>
- At any time, you can check the review and approval status of your app in the
- Developer Console, under "Google Play for Education" in the app's Pricing and
- Distribution page. There are three approval states:
+When your apps are ready to publish, you opt-in to Google Play for Education directly from the
+<a href="https://chrome.google.com/webstore/developer/dashboard">Developer Dashboard</a>.
+Opt-in means that you want your apps to be made available to educators
+through Google Play for Education, including review, classification, and approval by our
+third-party educator network.
+</p>
+<p>
+Opt-in also confirms that your app complies with
+<a href="https://developer.chrome.com/webstore/program_policies">Chrome Web Store Program Policies</a>
+and the <a href="https://developer.chrome.com/webstore/terms">Terms of Service</a>, including a
+<a href="https://developers.google.com/edu/chrome-d-d-a-addendum">Google Play for Education Addendum</a>.
+If you are not familiar with these
+policy documents or the Addendum, make sure to read them before opting-in.
</p>
-<ul>
- <li>
- <em>Pending</em> — Your app was sent for review and the review isn't
- yet complete.
- </li>
+<p>Here's how to opt-in to Google Play for Education for a specific app or extension:</p>
+<ol>
+<li>In the Developer Dashboard, click <b>Edit</b> on the app you want to opt-in.</li>
+<li>Scroll down to <b>Google Play for Education</b> and review the additional
+Developer Terms and Conditions.</li>
+<li>Select the checkbox to opt-in to include this item in Google Play for Education.</li>
+<li>Click <b>Publish Changes</b> to save your changes and submit your application.
+Once you save changes and publish your app, the app will be available on Google Play for Education.
+We may submit your app to our third-party educator network for additional review, and to be
+featured and badged as Educator-approved.</li>
+<p class="note"><b>Note:</b>
+When you publish an app that's opted-in to Google Play for Education,
+the app becomes available to users in the Chrome Web Store.
+</p>
+</ol>
- <li>
- <em>Approved</em> — Your app was reviewed and approved. The app will
- be made available directly to educators through Google Play for Education.
- Once your app is approved, you can update it at your convenience without
- needing another full review.
- </li>
-
- <li>
- <em>Not approved</em> — Your app was reviewed and not approved. Check
- the notification email send for information about why the app wasn’t
- approved. You can address any issues and opt-in again for another review.
- </li>
-</ul>
<div class="headerLine">
<h2 id="related-resources">Related Resources</h2>
</div>
diff --git a/docs/html/google/play/billing/billing_subscriptions.jd b/docs/html/google/play/billing/billing_subscriptions.jd
index 2b78ab3..b9b77df 100644
--- a/docs/html/google/play/billing/billing_subscriptions.jd
+++ b/docs/html/google/play/billing/billing_subscriptions.jd
@@ -11,25 +11,32 @@
<div id="qv">
<h2>Quickview</h2>
<ul>
- <li>Users purchase your subscriptions from inside your apps, rather than
-directly from Google Play.</li>
- <li>Subscriptions let you sell products with automated, recurring billing
-(monthly or annual).</li>
- <li>You can offer a configurable trial period for any subscription.</li>
-
+ <li>Subscriptions let you sell products with automated, recurring billing
+ at a variety of intervals.</li>
+ <li>You can offer a configurable trial period for monthly and
+ annual subscriptions.</li>
+ <li>You can manage subscriptions through the Developer Console, or by using
+ the
+ <a href="https://developers.google.com/android-publisher/">Google Play
+ Developer API</a>.</li>
+ <li>Users purchase your subscriptions from inside your apps, rather than
+ directly from Google Play.</li>
+ <li>You can defer billing for a particular user's subscription, to manage
+ accounts or offer rewards.</li>
</ul>
<h2>In this document</h2>
<ol>
- <li><a href="#overview">Overview</a></li>
+ <li><a href="#overview">Overview of Subscriptions</a></li>
<li><a href="#administering">Configuring Subscriptions Items</a></li>
- <li><a href="#cancellation">Cancellation</a></li>
- <li><a href="#payment">Payment Processing</a></li>
+ <li><a href="#cancellation">Subscription Cancellation</a></li>
+ <li><a href="#payment">Payment Processing and Policies</a></li>
<li><a href="#strategies">Purchase Verification Strategies</a></li>
+ <li><a href="#play-dev-api">Google Play Developer API</a></li>
</ol>
<h2>See also</h2>
<ol>
<li><a href="{@docRoot}google/play/billing/billing_integrate.html#Subs">Implementing Subscriptions (V3)</a></li>
- <li><a href="https://developers.google.com/android-publisher/v1_1/">Google Play Android Developer API</a></li>
+ <li><a href="https://developers.google.com/android-publisher/">Google Play Developer API</a></li>
</ol>
</div>
</div>
@@ -44,14 +51,15 @@
<h2 id="overview">Overview of Subscriptions</h2>
<p>A <em>subscription</em> is a product type offered in In-app Billing that
lets you sell content, services, or features to users from inside your app with
-recurring monthly or annual billing. You can sell subscriptions to almost any
+recurring, automated billing at the interval you specify. You can sell subscriptions to almost
+any
type of digital content, from any type of app or game.</p>
<p>As with other in-app products, you configure and publish subscriptions using
the Developer Console and then sell them from inside apps installed on
Android devices. In the Developer console, you create subscription
products and add them to a product list, then set a price and optional trial
-period for each, choose a billing interval (monthly or annual), and then
+period for each, choose a billing interval, and then
publish. For more information about using the Developer Console, see
<a href="#administering">Configuring Subscription Items</a>.</p>
@@ -63,17 +71,17 @@
<img src="{@docRoot}images/in-app-billing/v3/billing_subscription_v3.png" style="float:right; border:4px solid ddd;">
-<p>After users have purchase subscriptions, they can view the subscriptions and
+<p>After users have purchased subscriptions, they can view the subscriptions and
cancel them from the <strong>My Apps</strong> screen in the Play Store app or
from the app's product details page in the Play Store app. For more information
about handling user cancellations, see <a href="#cancellation">Subscription Cancellation</a>.</p>
-<p>In adddition to client-side API calls, you can use the server-side API for
+<p>In addition to client-side API calls, you can use the server-side API for
In-app Billing to provide subscription purchasers with extended access to
content (for example, from your web site or another service).
The server-side API lets you validate the status of a subscription when users
sign into your other services. For more information about the API, see <a
-href="{@docRoot}google/play/billing/gp-purchase-status-api.html">Purchase Status API</a>. </p>
+href="{@docRoot}google/play/billing/gp-purchase-status-api.html">Google Play Developer API</a>. </p>
<p>You can also build on your existing external subscriber base from inside your
Android apps.</p>
@@ -102,8 +110,10 @@
subscriptions, see the <a href="{@docRoot}google/play/billing/versions.html#Subs">Version Notes</a>.</p>
<h2 id="administering">Configuring Subscription Items</h2>
-<p>To create and manage subscriptions, use the Developer Console to set up a
-product list for the app then configure these attributes for each subscription
+
+<p>To create and manage subscriptions, you can use the Developer Console to set
+up a
+product list for the app, then configure these attributes for each subscription
product:</p>
<ul>
@@ -113,8 +123,8 @@
<li>Language: The default language for displaying the subscription</li>
<li>Title: The title of the subscription product</li>
<li>Description: Details that tell the user about the subscription</li>
-<li>Price: USD price of subscription per recurrence</li>
-<li>Recurrence: monthly or yearly</li>
+<li>Price: Default price of subscription per recurrence</li>
+<li>Recurrence: Interval of billing recurrence</li>
<li>Additional currency pricing (can be auto-filled)</li>
</ul>
@@ -122,6 +132,10 @@
see <a href="{@docRoot}google/play/billing/billing_admin.html">Administering
In-app Billing</a>.</p>
+<p>You can also create and manage subscriptions using the
+<a href="{@docRoot}google/play/billing/gp-purchase-status-api.html">
+Google Play Developer API</a>.</p>
+
<h3 id="pricing">Subscription pricing</h3>
<p>When you create a subscription in the Developer Console, you can set a price
@@ -139,20 +153,30 @@
<h3 id="user-billing">User billing</h3>
<p>In the Developer Console, you can configure subscription products with
-automated recurring billing at either of two intervals:</p>
+automated recurring billing at your choice of intervals:</p>
<ul>
<li>Monthly — Google Play bills the customer’s Google Wallet account at
the time of purchase and monthly subsequent to the purchase date (exact billing
- intervals can vary slightly over time)</li>
+ intervals can vary slightly over time).</li>
<li>Annually — Google Play bills the customer's Google Wallet account at
the time of purchase and again on the same date in subsequent years.</li>
+
+ <li>Seasonal — Google Play bills the customer's Google Wallet account at
+ the beginning of each "season" (you specify the season beginning and end
+ dates). This
+ is intended for annual purchases of seasonal content (such as sports-related
+ content). The subscription runs through the end of the season, and restarts
+ the next year at the start of the season.</li>
+
</ul>
<p>Billing continues indefinitely at the interval and price specified for the
subscription. At each subscription renewal, Google Play charges the user account
-automatically, then notifies the user of the charges afterward by email. Billing
-cycles will always match subscription cycles, based on the purchase date.</p>
+automatically, then notifies the user of the charges afterward by email. For
+monthly and annual subscriptions, billing cycles will always match subscription
+cycles, based on the purchase date. (Seasonal subscriptions are charged
+annually, on the first day of the season.)</p>
<p>Over the life of a subscription, the form of payment billed remains the same
— Google Play always bills the same form of payment (such as credit card
@@ -164,7 +188,7 @@
API. Your apps can store the token locally or pass it to your backend servers,
which can then use it to validate or cancel the subscription remotely using the
<a
-href="{@docRoot}google/play/billing/gp-purchase-status-api.html">Purchase Status API</a>.</p>
+href="{@docRoot}google/play/billing/gp-purchase-status-api.html">Google Play Developer API</a>.</p>
<p>If a recurring payment fails (for example, because the customer’s credit
card has become invalid), the subscription does not renew. How your app is
@@ -182,18 +206,57 @@
billing errors that may occur. Your backend servers can use the server-side API
to query and update your records and follow up with customers directly, if needed.</p>
+<h3 id="deferred-billing">Deferred Billing</h3>
+
+<p>Using the
+<a href="{@docRoot}google/play/billing/gp-purchase-status-api.html">Google
+Play Developer API</a>, you can defer the next billing date for a
+subscriber. The user continues to be subscribed to the content, and has full
+access to it, but is not charged during the deferral period. This allows you
+to do things like:</p>
+
+<ul>
+ <li>Give users free access as part of a bundle or a special offer (for
+ example, giving free access to web content to users who subscribe to a
+ print magazine)</li>
+ <li>Give free access to customers as a goodwill gesture</li>
+</ul>
+
+<p>The longest you can defer billing is for one year per call. Of course, you
+can call the API again before the year is up to defer billing further.</p>
+
+<p>For example, Darcy has a monthly subscription to online content for the
+<em>Fishing Gentleman</em> app. He is normally
+billed £1.25 on the first of each month.
+On March 10, he participates in an online survey for the app publisher. The
+publisher rewards him by deferring his next payment until June 1. Darcy is not
+charged on April 1 or May 1, but still has access to the content as normal. On
+June 1, he is charged his normal £1.25 subscription fee.</p>
+
+<p class="note"><strong>Note:</strong> The API always defers the billing date
+by a whole number of days. If you request a deferral period that includes a
+fractional number of days, the API rounds the period up to the next full day.
+For example, if a user's subscription is set to renew on 15 June 2015 at
+14:00:00 UTC, and you use the API to defer the renewal date to 15 August 2015 at
+02:00:00 UTC, the API will round up to the next full day and set the renewal
+date to 15 August 2015 14:00:00 UTC.</p>
+
+<p>You can also offer free trials to new subscribers, as described in
+<a href="#trials">Free trials</a>.</p>
+
<h3 id="trials">Free trials</h3>
<p>In the Developer Console, you can set up a free trial period that lets users
try your subscription content before buying it. The trial period runs for the
period of time that you set and then automatically converts to a full
subscription managed according to the subscription's billing interval and
-price.</p>
+price. Free trials are supported for monthly and annual subscriptions only, and are not supported for seasonal subscriptions.</p>
<p>To take advantage of a free trial, a user must "purchase" the full
subscription through the standard In-app Billing flow, providing a valid form of
payment to use for billing and completing the normal purchase transaction.
-However, the user is not charged any money, since the initial period corresponds
+However, the user is not charged any money, because the initial period
+corresponds
to the free trial. Instead, Google Play records a transaction of $0.00 and the
subscription is marked as purchased for the duration of the trial period or
until cancellation. When the transaction is complete, Google Play notifies users
@@ -220,8 +283,10 @@
period per subscription product.</p>
<h3 id="publishing">Subscription publishing</h3>
+
<p>When you have finished configuring your subscription product details in the
-Developer Console, you can publish the subscription in the app product list.</p>
+Developer Console or via the API,
+you can publish the subscription in the app product list.</p>
<p>In the product list, you can add subscriptions, in-app products, or both. You
can add multiple subscriptions that give access to different content or
@@ -263,10 +328,13 @@
<p class="caution"><strong>Important:</strong> In all cases, you must continue
to offer the content that your subscribers have purchased through their
-subscriptions, for as long any users are able to access it. That is, you must
-not remove any subscriber’s content while any user still has an active
+subscriptions, as long any user is able to access it. That is, you must
+not remove any content while any user still has an active
subscription to it, even if that subscription will terminate at the end of the
-current billing cycle. Removing content that a subscriber is entitled to access
+current billing cycle. Alternatively, you can use the <a href="#refunds">refund
+and revoke</a> API to revoke each subscriber's subscription (one by one) and
+refund their subscription payments.
+Removing content that any subscriber is entitled to access
will result in penalties. Please see the <a
href="http://support.google.com/googleplay/android-developer/bin/answer.py?hl=en&answer=140504">policies document</a> for more information. </p>
@@ -280,19 +348,26 @@
screen of the Play Store app. If the user chooses to cancel the uninstallation,
the app and subscriptions remain as they were.</p>
-<h3 id="refunds">Refunds</h3>
+<h3 id="refunds">Refunding and revoking subscriptions</h3>
-<p>With subscriptions, Google Play does not provide a refund window, so users
-will need to contact you directly to request a refund.
+<p>With subscriptions, Google Play does not provide a refund window, so users
+will need to request a refund. They can request a refund from the <strong>My
+Orders</strong> page in the Play Store, or by contacting you directly.</p>
-<p>If you receive requests for refunds, you can use the server-side API to
-cancel the subscription or verify that it is already cancelled. However, keep in
-mind that Google Play considers cancelled subscriptions valid until the end of
-their current billing cycles, so even if you grant a refund and cancel the
-subscription, the user will still have access to the content.
+<p>If you receive requests for refunds, you can use the
+<a href="{@docRoot}google/play/billing/gp-purchase-status-api.html">Google Play
+Developer API</a> or the Merchant Center to cancel the subscription, verify that it
+is already cancelled, or refund the user's payment without cancelling it. You
+can also use the
+<a href="{@docRoot}google/play/billing/gp-purchase-status-api.html">Google
+Play Developer API</a> to <em>refund and revoke</em> a
+user's subscription. If you refund and revoke a subscription, the user's
+subscription is immediately cancelled, and the user's most recent subscription
+payment is refunded. (If you want to refund more than the most recent payment,
+you can process additional refunds through the Merchant Center.)</p>
-<p class="caution"><strong>Important:</strong> Partial refunds for canceled
-subscriptions are not available at this time.</p>
+<p class="caution"><strong>Important:</strong> Partial refunds are not available
+at this time.</p>
<h2 id="payment">Payment Processing and Policies</h2>
@@ -317,9 +392,9 @@
each recurring transaction by appending an integer as follows: </p>
<p><span style="color:#777"><code style="color:#777">12999556515565155651.5565135565155651</code> (base order number)</span><br />
-<code>12999556515565155651.5565135565155651..0</code> (initial purchase orderID)<br />
-<code>12999556515565155651.5565135565155651..1</code> (first recurrence orderID)<br />
-<code>12999556515565155651.5565135565155651..2</code> (second recurrence orderID)<br />
+<code>12999556515565155651.5565135565155651..0</code> (first recurrence orderID)<br />
+<code>12999556515565155651.5565135565155651..1</code> (second recurrence orderID)<br />
+<code>12999556515565155651.5565135565155651..2</code> (third recurrence orderID)<br />
...<br /></p>
<p>Google Play provides the order number as the value of the
@@ -334,19 +409,28 @@
<p>To verify a purchase, the app passes the purchase token and other details up
to your backend servers, which verifies them directly with Google Play using the
-Purchase Status API. If the backend server determines that the purchase is
+Google Play Developer API. If the backend server determines that the purchase is
valid, it notifies the app and grants access to the content.</p>
<p>Keep in mind that users will want the ability to use your app at any time,
including when there may be no network connection available. Make sure that your
approach to purchase verification accounts for the offline use-case.</p>
-<h2 id="play-dev-api">Google Play Android Developer API</h2>
+<h2 id="play-dev-api">Google Play Developer API</h2>
-<p>Google Play offers an HTTP-based API that lets you remotely query the
-validity of a specific subscription at any time or cancel a subscription. The
-API is designed to be used from your backend servers as a way of securely
+<p>Google Play offers an HTTP-based API that lets you perform such tasks as:</p>
+ <ul>
+ <li>Remotely query the validity of a specific subscription at any time</li>
+ <li>Cancel a subscription</li>
+ <li>Defer a subscription's next billing date</li>
+ <li>Refund a subscription payment without cancelling the subscription</li>
+ <li>Refund and revoke a subscription</li>
+ </ul>
+
+<p>The API is designed to be used from your backend servers as a way of securely
managing subscriptions, as well as extending and integrating subscriptions with
other services.</p>
-<p>For complete information, see <a href="{@docRoot}google/play/billing/gp-purchase-status-api.html">Purchase Status API</a>.</p>
+<p>For complete information, see
+<a href="{@docRoot}google/play/billing/gp-purchase-status-api.html">Google Play
+Developer API</a>.</p>
diff --git a/docs/html/google/play/billing/index.jd b/docs/html/google/play/billing/index.jd
index 875271fe..bdbf5c7 100644
--- a/docs/html/google/play/billing/index.jd
+++ b/docs/html/google/play/billing/index.jd
@@ -14,21 +14,35 @@
<div class="sidebox">
<h2><strong>New in In-App Billing</strong></h2>
<ul>
+ <li><strong>IAB v2 shutdown</strong>—In-app Billing v2 API is deprecated and will be shut down in January 2015. If your app is still using In-app Billing v2, please migrate to the v3 API as soon as possible.</li>
+ <li><strong>Seasonal subscriptions</strong>—You can now set up a
+ recurring <a href="billing_subscriptions.html#user-billing">seasonal
+ subscription</a> that starts and ends on the same date each year (for
+ example, a sports subscription that starts every September 1 and ends every
+ April 10).</li>
+ <li><strong>Deferred subscription billing</strong>—You can
+ <a href="billing_subscriptions.html#deferred-billing">defer</a> a
+ subscriber's next billing date until the date you choose. The user still has
+ access to the content but is not charged during the deferral period.</li>
<li><strong>Google Play Developer API</strong>—The
<a href="{@docRoot}google/play/billing/gp-purchase-status-api.html">Google
Play Developer API</a> allows you to perform a number of publishing and
app-management tasks. It includes the functionality previously known as the
<em>Purchase Status API.</em> </li>
+ <li><strong>Refund/Revoke subscription</strong>—You can use the
+ Google Play Developer API to <a href="billing_subscriptions.html#refunds">refund
+ and revoke</a> a user's subscription. If you do this, the user's
+ subscription ends
+ immediately, and his or her most recent subscription payment is
+ refunded.</li>
<li><strong>In-app Billing Version 3</strong>—The <a href="{@docRoot}google/play/billing/api.html">latest version</a> of In-app Billing features a synchronous API that is easier to implement and lets you manage in-app products and subscriptions more effectively.</li>
- <li><strong>Subscriptions now supported in Version 3</strong>—You can query and launch purchase flows for subscription items using the V3 API.</li>
- <li><strong>Free trials</strong>—You can now offer users a configurable <a href="/google/play/billing/v2/billing_subscriptions.html#trials">free trial period</a> for your in-app subscriptions. You can set up trials with a simple change in the Developer Console—no change to your app code is needed.</li>
</ul>
</div>
</div>
<ul>
<li>Standard in-app products (one-time billing), or</li>
-<li>Subscriptions, (recurring, automated billing)</li>
+<li>Subscriptions (recurring, automated billing)</li>
</ul>
<p>When you use the in-app billing service to sell an item,
diff --git a/docs/html/google/play/billing/v2/api.jd b/docs/html/google/play/billing/v2/api.jd
index 9501555..36a9017 100644
--- a/docs/html/google/play/billing/v2/api.jd
+++ b/docs/html/google/play/billing/v2/api.jd
@@ -2,7 +2,28 @@
excludeFromSuggestions=true
@jd:body
-<div style="background-color:#fffdeb;width:100%;margin-bottom:1em;padding:.5em;">In-app Billing Version 2 is superseded. Please <a href="{@docRoot}google/play/billing/billing_overview.html#migration">migrate to Version 3</a> at your earliest convenience.</div>
+<p class="caution" style=
+"background-color:#fffdeb;width:100%;margin-bottom:1em;padding:.5em;">
+ The <strong>In-app Billing Version 2 API</strong> has been deprecated in
+ favor of the Version 3 API. If your app is using In-app Billing, please
+ <strong>make sure that it is using the Version 3 API</strong>. If your app is
+ still using the Version 2 API, you must <strong>migrate to the Version 3 API
+ as soon as possible</strong>.<br>
+ <br>
+ We plan to turn off the In-app Billing Version 2 service on <strong>January
+ 27, 2015</strong>, after which time users will <strong>no longer be able to
+ purchase in-app items and subscriptions through the Version 2 API</strong>.
+ We strongly encourage and recommend you migrate your apps to use Version 3
+ API by November 2014, to provide ample time for users to update their apps to
+ the new version.<br>
+ <br>
+ For more information, please see the <a href=
+ "http://support.google.com/googleplay/android-developer/answer/6090268">Help Center
+ article</a>. For common questions about transitioning your implementation to
+ In-app Billing Version 3, please see <a href=
+ "{@docRoot}google/play/billing/billing_overview.html#migration">Migration
+ Considerations</a>.
+</p>
<div id="qv-wrapper" style="margin-top:0;">
<div id="qv">
diff --git a/docs/html/google/play/billing/v2/billing_integrate.jd b/docs/html/google/play/billing/v2/billing_integrate.jd
index 5eb17d5..c264271 100644
--- a/docs/html/google/play/billing/v2/billing_integrate.jd
+++ b/docs/html/google/play/billing/v2/billing_integrate.jd
@@ -2,7 +2,28 @@
excludeFromSuggestions=true
@jd:body
-<div style="background-color:#fffdeb;width:100%;margin-bottom:1em;padding:.5em;">In-app Billing Version 2 is superseded. Please <a href="{@docRoot}google/play/billing/billing_overview.html#migration">migrate to Version 3</a> at your earliest convenience.</div>
+<p class="caution" style=
+"background-color:#fffdeb;width:100%;margin-bottom:1em;padding:.5em;">
+ The <strong>In-app Billing Version 2 API</strong> has been deprecated in
+ favor of the Version 3 API. If your app is using In-app Billing, please
+ <strong>make sure that it is using the Version 3 API</strong>. If your app is
+ still using the Version 2 API, you must <strong>migrate to the Version 3 API
+ as soon as possible</strong>.<br>
+ <br>
+ We plan to turn off the In-app Billing Version 2 service on <strong>January
+ 27, 2015</strong>, after which time users will <strong>no longer be able to
+ purchase in-app items and subscriptions through the Version 2 API</strong>.
+ We strongly encourage and recommend you migrate your apps to use Version 3
+ API by November 2014, to provide ample time for users to update their apps to
+ the new version.<br>
+ <br>
+ For more information, please see the <a href=
+ "http://support.google.com/googleplay/android-developer/answer/6090268">Help Center
+ article</a>. For common questions about transitioning your implementation to
+ In-app Billing Version 3, please see <a href=
+ "{@docRoot}google/play/billing/billing_overview.html#migration">Migration
+ Considerations</a>.
+</p>
<div id="qv-wrapper" style="margin-top:0;">
<div id="qv">
<h2>In this document</h2>
diff --git a/docs/html/google/play/billing/v2/billing_reference.jd b/docs/html/google/play/billing/v2/billing_reference.jd
index 4587dee..32e00cf 100644
--- a/docs/html/google/play/billing/v2/billing_reference.jd
+++ b/docs/html/google/play/billing/v2/billing_reference.jd
@@ -2,7 +2,28 @@
excludeFromSuggestions=true
@jd:body
-<div style="background-color:#fffdeb;width:100%;margin-bottom:1em;padding:.5em;">In-app Billing Version 2 is superseded. Please <a href="{@docRoot}google/play/billing/billing_overview.html#migration">migrate to Version 3</a> at your earliest convenience.</div>
+<p class="caution" style=
+"background-color:#fffdeb;width:100%;margin-bottom:1em;padding:.5em;">
+ The <strong>In-app Billing Version 2 API</strong> has been deprecated in
+ favor of the Version 3 API. If your app is using In-app Billing, please
+ <strong>make sure that it is using the Version 3 API</strong>. If your app is
+ still using the Version 2 API, you must <strong>migrate to the Version 3 API
+ as soon as possible</strong>.<br>
+ <br>
+ We plan to turn off the In-app Billing Version 2 service on <strong>January
+ 27, 2015</strong>, after which time users will <strong>no longer be able to
+ purchase in-app items and subscriptions through the Version 2 API</strong>.
+ We strongly encourage and recommend you migrate your apps to use Version 3
+ API by November 2014, to provide ample time for users to update their apps to
+ the new version.<br>
+ <br>
+ For more information, please see the <a href=
+ "http://support.google.com/googleplay/android-developer/answer/6090268">Help Center
+ article</a>. For common questions about transitioning your implementation to
+ In-app Billing Version 3, please see <a href=
+ "{@docRoot}google/play/billing/billing_overview.html#migration">Migration
+ Considerations</a>.
+</p>
<div id="qv-wrapper" style="margin-top:0;">
<div id="qv">
<h2>In this document</h2>
diff --git a/docs/html/google/play/billing/v2/billing_subscriptions.jd b/docs/html/google/play/billing/v2/billing_subscriptions.jd
index f8051a9..01e39ac 100644
--- a/docs/html/google/play/billing/v2/billing_subscriptions.jd
+++ b/docs/html/google/play/billing/v2/billing_subscriptions.jd
@@ -2,7 +2,28 @@
excludeFromSuggestions=true
@jd:body
-<div style="background-color:#fffdeb;width:100%;margin-bottom:1em;padding:.5em;">In-app Billing Version 2 is superseded. Please <a href="{@docRoot}google/play/billing/billing_overview.html#migration">migrate to Version 3</a> at your earliest convenience.</div>
+<p class="caution" style=
+"background-color:#fffdeb;width:100%;margin-bottom:1em;padding:.5em;">
+ The <strong>In-app Billing Version 2 API</strong> has been deprecated in
+ favor of the Version 3 API. If your app is using In-app Billing, please
+ <strong>make sure that it is using the Version 3 API</strong>. If your app is
+ still using the Version 2 API, you must <strong>migrate to the Version 3 API
+ as soon as possible</strong>.<br>
+ <br>
+ We plan to turn off the In-app Billing Version 2 service on <strong>January
+ 27, 2015</strong>, after which time users will <strong>no longer be able to
+ purchase in-app items and subscriptions through the Version 2 API</strong>.
+ We strongly encourage and recommend you migrate your apps to use Version 3
+ API by November 2014, to provide ample time for users to update their apps to
+ the new version.<br>
+ <br>
+ For more information, please see the <a href=
+ "http://support.google.com/googleplay/android-developer/answer/6090268">Help Center
+ article</a>. For common questions about transitioning your implementation to
+ In-app Billing Version 3, please see <a href=
+ "{@docRoot}google/play/billing/billing_overview.html#migration">Migration
+ Considerations</a>.
+</p>
<div id="qv-wrapper" style="margin-top:0;">
<div id="qv">
<h2>In this document</h2>
diff --git a/libs/hwui/ProgramCache.cpp b/libs/hwui/ProgramCache.cpp
index a8b8b16..06353c0 100644
--- a/libs/hwui/ProgramCache.cpp
+++ b/libs/hwui/ProgramCache.cpp
@@ -327,8 +327,10 @@
// None
"",
// Matrix
+ " fragColor.rgb /= (fragColor.a + 0.0019);\n" // un-premultiply
" fragColor *= colorMatrix;\n"
- " fragColor += colorMatrixVector;\n",
+ " fragColor += colorMatrixVector;\n"
+ " fragColor.rgb *= (fragColor.a + 0.0019);\n", // re-premultiply
// PorterDuff
" fragColor = blendColors(colorBlend, fragColor);\n"
};
diff --git a/libs/hwui/Properties.h b/libs/hwui/Properties.h
index feaee8e..7eb9a32 100644
--- a/libs/hwui/Properties.h
+++ b/libs/hwui/Properties.h
@@ -227,7 +227,7 @@
#define DEFAULT_PATCH_CACHE_SIZE 128 // in kB
#define DEFAULT_GRADIENT_CACHE_SIZE 0.5f
#define DEFAULT_DROP_SHADOW_CACHE_SIZE 2.0f
-#define DEFAULT_FBO_CACHE_SIZE 16
+#define DEFAULT_FBO_CACHE_SIZE 0
#define DEFAULT_TEXTURE_CACHE_FLUSH_RATE 0.6f
diff --git a/libs/hwui/Renderer.h b/libs/hwui/Renderer.h
index 6d4bb4a..3681637 100644
--- a/libs/hwui/Renderer.h
+++ b/libs/hwui/Renderer.h
@@ -70,6 +70,7 @@
// TODO: move to a method on android:Paint
static inline bool paintWillNotDraw(const SkPaint& paint) {
return paint.getAlpha() == 0
+ && !paint.getColorFilter()
&& getXfermode(paint.getXfermode()) != SkXfermode::kClear_Mode;
}
@@ -77,6 +78,7 @@
static inline bool paintWillNotDrawText(const SkPaint& paint) {
return paint.getAlpha() == 0
&& paint.getLooper() == NULL
+ && !paint.getColorFilter()
&& getXfermode(paint.getXfermode()) == SkXfermode::kSrcOver_Mode;
}
// ----------------------------------------------------------------------------
diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp
index 5e6796c..832d170 100644
--- a/libs/hwui/renderthread/CanvasContext.cpp
+++ b/libs/hwui/renderthread/CanvasContext.cpp
@@ -91,7 +91,9 @@
}
void CanvasContext::swapBuffers() {
- mEglManager.swapBuffers(mEglSurface);
+ if (CC_UNLIKELY(!mEglManager.swapBuffers(mEglSurface))) {
+ setSurface(NULL);
+ }
mHaveNewSurface = false;
}
@@ -102,8 +104,8 @@
}
bool CanvasContext::initialize(ANativeWindow* window) {
- if (mCanvas) return false;
setSurface(window);
+ if (mCanvas) return false;
mCanvas = new OpenGLRenderer(mRenderThread.renderState());
mCanvas->initProperties();
return true;
diff --git a/libs/hwui/renderthread/EglManager.cpp b/libs/hwui/renderthread/EglManager.cpp
index e030cdb..a87834e 100644
--- a/libs/hwui/renderthread/EglManager.cpp
+++ b/libs/hwui/renderthread/EglManager.cpp
@@ -254,11 +254,23 @@
eglBeginFrame(mEglDisplay, surface);
}
-void EglManager::swapBuffers(EGLSurface surface) {
+bool EglManager::swapBuffers(EGLSurface surface) {
eglSwapBuffers(mEglDisplay, surface);
EGLint err = eglGetError();
- LOG_ALWAYS_FATAL_IF(err != EGL_SUCCESS,
- "Encountered EGL error %d %s during rendering", err, egl_error_str(err));
+ if (CC_LIKELY(err == EGL_SUCCESS)) {
+ return true;
+ }
+ if (err == EGL_BAD_SURFACE) {
+ // For some reason our surface was destroyed out from under us
+ // This really shouldn't happen, but if it does we can recover easily
+ // by just not trying to use the surface anymore
+ ALOGW("swapBuffers encountered EGL_BAD_SURFACE on %p, halting rendering...", surface);
+ return false;
+ }
+ LOG_ALWAYS_FATAL("Encountered EGL error %d %s during rendering",
+ err, egl_error_str(err));
+ // Impossible to hit this, but the compiler doesn't know that
+ return false;
}
bool EglManager::enableDirtyRegions(EGLSurface surface) {
diff --git a/libs/hwui/renderthread/EglManager.h b/libs/hwui/renderthread/EglManager.h
index a844cfc..71213fb 100644
--- a/libs/hwui/renderthread/EglManager.h
+++ b/libs/hwui/renderthread/EglManager.h
@@ -47,7 +47,7 @@
// Returns true if the current surface changed, false if it was already current
bool makeCurrent(EGLSurface surface);
void beginFrame(EGLSurface surface, EGLint* width, EGLint* height);
- void swapBuffers(EGLSurface surface);
+ bool swapBuffers(EGLSurface surface);
bool enableDirtyRegions(EGLSurface surface);
diff --git a/location/java/android/location/LocationManager.java b/location/java/android/location/LocationManager.java
index 2c805bb..0445869 100644
--- a/location/java/android/location/LocationManager.java
+++ b/location/java/android/location/LocationManager.java
@@ -18,6 +18,7 @@
import com.android.internal.location.ProviderProperties;
+import android.annotation.SystemApi;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
@@ -808,6 +809,7 @@
*
* @hide
*/
+ @SystemApi
public void requestLocationUpdates(LocationRequest request, LocationListener listener,
Looper looper) {
checkListener(listener);
@@ -835,6 +837,7 @@
*
* @hide
*/
+ @SystemApi
public void requestLocationUpdates(LocationRequest request, PendingIntent intent) {
checkPendingIntent(intent);
requestLocationUpdates(request, null, null, intent);
diff --git a/packages/PrintSpooler/src/com/android/printspooler/model/PageContentRepository.java b/packages/PrintSpooler/src/com/android/printspooler/model/PageContentRepository.java
index 14c41da..a4555f1 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/model/PageContentRepository.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/model/PageContentRepository.java
@@ -64,9 +64,6 @@
private final CloseGuard mCloseGuard = CloseGuard.get();
- private final ArrayMap<Integer, PageContentProvider> mPageContentProviders =
- new ArrayMap<>();
-
private final AsyncRenderer mRenderer;
private RenderSpec mLastRenderSpec;
@@ -141,10 +138,6 @@
return mRenderer.getPageCount();
}
- public PageContentProvider peekPageContentProvider(int pageIndex) {
- return mPageContentProviders.get(pageIndex);
- }
-
public PageContentProvider acquirePageContentProvider(int pageIndex, View owner) {
throwIfDestroyed();
@@ -152,15 +145,7 @@
Log.i(LOG_TAG, "Acquiring provider for page: " + pageIndex);
}
- if (mPageContentProviders.get(pageIndex)!= null) {
- throw new IllegalStateException("Already acquired for page: " + pageIndex);
- }
-
- PageContentProvider provider = new PageContentProvider(pageIndex, owner);
-
- mPageContentProviders.put(pageIndex, provider);
-
- return provider;
+ return new PageContentProvider(pageIndex, owner);
}
public void releasePageContentProvider(PageContentProvider provider) {
@@ -170,10 +155,6 @@
Log.i(LOG_TAG, "Releasing provider for page: " + provider.mPageIndex);
}
- if (mPageContentProviders.remove(provider.mPageIndex) == null) {
- throw new IllegalStateException("Not acquired");
- }
-
provider.cancelLoad();
}
@@ -526,7 +507,7 @@
callback.run();
}
}
- }.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR, (Void[]) null);
+ }.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR);
}
public void close(final Runnable callback) {
@@ -552,7 +533,7 @@
callback.run();
}
}
- }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void[]) null);
+ }.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR);
}
public void destroy() {
@@ -571,7 +552,7 @@
mPageContentCache.invalidate();
mPageContentCache.clear();
}
- }.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR, (Void[]) null);
+ }.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR);
}
public void startPreload(int firstShownPage, int lastShownPage, RenderSpec renderSpec) {
@@ -687,7 +668,7 @@
// Oh well, we will have work to do...
renderTask = new RenderPageTask(pageIndex, renderSpec, callback);
mPageToRenderTaskMap.put(pageIndex, renderTask);
- renderTask.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR, (Void[]) null);
+ renderTask.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR);
}
public void cancelRendering(int pageIndex) {
diff --git a/packages/PrintSpooler/src/com/android/printspooler/ui/PageAdapter.java b/packages/PrintSpooler/src/com/android/printspooler/ui/PageAdapter.java
index 369c453..da8160a 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/ui/PageAdapter.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/ui/PageAdapter.java
@@ -274,9 +274,7 @@
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View page = mLayoutInflater.inflate(R.layout.preview_page, parent, false);
- ViewHolder holder = new MyViewHolder(page);
- holder.setIsRecyclable(true);
- return holder;
+ return new MyViewHolder(page);
}
@Override
@@ -314,14 +312,8 @@
+ ", pageIndexInFile: " + pageIndexInFile);
}
- // OK, there are bugs in recycler view which tries to bind views
- // without recycling them which would give us a chance to clean up.
- PageContentProvider boundProvider = mPageContentRepository
- .peekPageContentProvider(pageIndexInFile);
- if (boundProvider != null) {
- PageContentView owner = (PageContentView) boundProvider.getOwner();
- owner.init(null, mEmptyState, mMediaSize, mMinMargins);
- mPageContentRepository.releasePageContentProvider(boundProvider);
+ if (provider != null && provider.getPageIndex() != pageIndexInFile) {
+ mPageContentRepository.releasePageContentProvider(provider);
}
provider = mPageContentRepository.acquirePageContentProvider(
@@ -732,7 +724,7 @@
private void recyclePageView(PageContentView page, int pageIndexInAdapter) {
PageContentProvider provider = page.getPageContentProvider();
if (provider != null) {
- page.init(null, null, null, null);
+ page.init(null, mEmptyState, mMediaSize, mMinMargins);
mPageContentRepository.releasePageContentProvider(provider);
}
mBoundPagesInAdapter.remove(pageIndexInAdapter);
diff --git a/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java b/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java
index 389988a..d169319 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java
@@ -953,7 +953,7 @@
// When the update is done we update the print preview.
mProgressMessageController.post();
return true;
- } else {
+ } else if (!willUpdate) {
// Update preview.
updatePrintPreviewController(false);
}
diff --git a/packages/PrintSpooler/src/com/android/printspooler/ui/PrintPreviewController.java b/packages/PrintSpooler/src/com/android/printspooler/ui/PrintPreviewController.java
index b999866..0d45352 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/ui/PrintPreviewController.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/ui/PrintPreviewController.java
@@ -77,6 +77,7 @@
mRecyclerView = (RecyclerView) activity.findViewById(R.id.preview_content);
mRecyclerView.setLayoutManager(mLayoutManger);
mRecyclerView.setAdapter(mPageAdapter);
+ mRecyclerView.setItemViewCacheSize(0);
mPreloadController = new PreloadController(mRecyclerView);
mRecyclerView.setOnScrollListener(mPreloadController);
@@ -348,8 +349,7 @@
public void startPreloadContent() {
PageAdapter pageAdapter = (PageAdapter) mRecyclerView.getAdapter();
-
- if (pageAdapter.isOpened()) {
+ if (pageAdapter != null && pageAdapter.isOpened()) {
PageRange shownPages = computeShownPages();
if (shownPages != null) {
pageAdapter.startPreloadContent(shownPages);
@@ -359,8 +359,7 @@
public void stopPreloadContent() {
PageAdapter pageAdapter = (PageAdapter) mRecyclerView.getAdapter();
-
- if (pageAdapter.isOpened()) {
+ if (pageAdapter != null && pageAdapter.isOpened()) {
pageAdapter.stopPreloadContent();
}
}
diff --git a/packages/PrintSpooler/src/com/android/printspooler/widget/PageContentView.java b/packages/PrintSpooler/src/com/android/printspooler/widget/PageContentView.java
index 1000117..b792789 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/widget/PageContentView.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/widget/PageContentView.java
@@ -52,12 +52,12 @@
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
+ mContentRequested = false;
requestPageContentIfNeeded();
}
@Override
public void onPageContentAvailable(BitmapDrawable content) {
- assert (getBackground() != content);
setBackground(content);
}
@@ -70,7 +70,7 @@
final boolean providerChanged = (mProvider == null)
? provider != null : !mProvider.equals(provider);
final boolean loadingDrawableChanged = (mEmptyState == null)
- ? mEmptyState != null : !mEmptyState.equals(emptyState);
+ ? emptyState != null : !mEmptyState.equals(emptyState);
final boolean mediaSizeChanged = (mMediaSize == null)
? mediaSize != null : !mMediaSize.equals(mediaSize);
final boolean marginsChanged = (mMinMargins == null)
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index b94a258..bddd691 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -199,7 +199,7 @@
android:excludeFromRecents="true"
android:stateNotNeeded="true"
android:resumeWhilePausing="true"
- android:theme="@style/RecentsTheme">
+ android:theme="@style/config_recents_activity_theme">
<intent-filter>
<action android:name="com.android.systemui.recents.TOGGLE_RECENTS" />
</intent-filter>
@@ -283,6 +283,21 @@
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
+ </activity>
+
+ <activity android:name=".egg.LLandActivity"
+ android:theme="@android:style/Theme.Material.Light.NoActionBar.TranslucentDecor"
+ android:exported="true"
+ android:label="@string/lland"
+ android:hardwareAccelerated="true"
+ android:launchMode="singleInstance"
+ android:screenOrientation="locked"
+ android:process=":sweetsweetdesserts"
+ android:excludeFromRecents="true">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN"/>
+ <category android:name="android.intent.category.DEFAULT" />
<category android:name="com.android.internal.category.PLATLOGO" />
</intent-filter>
</activity>
diff --git a/packages/SystemUI/res/drawable/android.xml b/packages/SystemUI/res/drawable/android.xml
new file mode 100644
index 0000000..750de05
--- /dev/null
+++ b/packages/SystemUI/res/drawable/android.xml
@@ -0,0 +1,37 @@
+<!--
+Copyright (C) 2014 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2 (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="48"
+ android:viewportHeight="48">
+ <path
+ android:name="torso"
+ android:pathData="M12,36c0,1.1 0.9,2 2,2l2,0l0,7c0,1.7 1.3,3 3,3c1.7,0 3,-1.3 3,-3l0,-7l4,0l0,7c0,1.7 1.3,3 3,3c1.7,0 3,-1.3 3,-3l0,-7l2,0c1.1,0 2,-0.9 2,-2L36,16L12,16L12,36z"
+ android:fillColor="#FFFFFF"/>
+ <path
+ android:name="leftArm"
+ android:pathData="M7,16c-1.7,0 -3,1.3 -3,3l0,14c0,1.7 1.3,3 3,3c1.7,0 3,-1.3 3,-3L10,19C10,17.3 8.7,16 7,16z"
+ android:fillColor="#FFFFFF"/>
+ <path
+ android:name="rightArm"
+ android:pathData="M41,16c-1.7,0 -3,1.3 -3,3l0,14c0,1.7 1.3,3 3,3c1.7,0 3,-1.3 3,-3L44,19C44,17.3 42.7,16 41,16z"
+ android:fillColor="#FFFFFF"/>
+ <path
+ android:name="illFormTheHead"
+ android:pathData="M31.1,4.3l2.6,-2.6c0.4,-0.4 0.4,-1 0,-1.4c-0.4,-0.4 -1,-0.4 -1.4,0l-3,3C27.7,2.5 25.9,2 24,2c-1.9,0 -3.7,0.5 -5.3,1.3l-3,-3c-0.4,-0.4 -1,-0.4 -1.4,0c-0.4,0.4 -0.4,1 0,1.4l2.6,2.6C13.9,6.5 12,10 12,14l24,0C36,10 34.1,6.5 31.1,4.3zM20.31,9c0,0.72 -0.59,1.31 -1.31,1.31c-0.72,0 -1.31,-0.59 -1.31,-1.31c0,-0.72 0.59,-1.31 1.31,-1.31C19.72,7.69 20.31,8.28 20.31,9zM30.31,9c0,0.72 -0.59,1.31 -1.31,1.31c-0.73,0 -1.31,-0.59 -1.31,-1.31c0,-0.72 0.59,-1.31 1.31,-1.31C29.72,7.69 30.31,8.28 30.31,9z"
+ android:fillColor="#FFFFFF"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/cloud.xml b/packages/SystemUI/res/drawable/cloud.xml
new file mode 100644
index 0000000..17e4ad2
--- /dev/null
+++ b/packages/SystemUI/res/drawable/cloud.xml
@@ -0,0 +1,24 @@
+<!--
+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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="48.0dp"
+ android:height="48.0dp"
+ android:viewportWidth="48.0"
+ android:viewportHeight="48.0">
+ <path
+ android:pathData="M38.700001,20.100000C37.299999,13.200000 31.299999,8.000000 24.000000,8.000000c-5.800000,0.000000 -10.800000,3.300000 -13.300000,8.100000C4.700000,16.700001 0.000000,21.799999 0.000000,28.000000c0.000000,6.600000 5.400000,12.000000 12.000000,12.000000l26.000000,0.000000c5.500000,0.000000 10.000000,-4.500000 10.000000,-10.000000C48.000000,24.700001 43.900002,20.400000 38.700001,20.100000z"
+ android:fillColor="#FFFFFF"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/cloud_off.xml b/packages/SystemUI/res/drawable/cloud_off.xml
new file mode 100644
index 0000000..b15ea5f
--- /dev/null
+++ b/packages/SystemUI/res/drawable/cloud_off.xml
@@ -0,0 +1,24 @@
+<!--
+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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24.0dp"
+ android:height="24.0dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:pathData="M19.400000,10.000000c-0.700000,-3.400000 -3.700000,-6.000000 -7.400000,-6.000000c-1.500000,0.000000 -2.900000,0.400000 -4.000000,1.200000l1.500000,1.500000C10.200000,6.200000 11.100000,6.000000 12.000000,6.000000c3.000000,0.000000 5.500000,2.500000 5.500000,5.500000L17.500000,12.000000L19.000000,12.000000c1.700000,0.000000 3.000000,1.300000 3.000000,3.000000c0.000000,1.100000 -0.600000,2.100000 -1.600000,2.600000l1.500000,1.500000c1.300000,-0.900000 2.100000,-2.400000 2.100000,-4.100000C24.000000,12.400000 21.900000,10.200000 19.400000,10.000000zM3.000000,5.300000L5.800000,8.000000C2.600000,8.200000 0.000000,10.800000 0.000000,14.000000c0.000000,3.300000 2.700000,6.000000 6.000000,6.000000l11.700000,0.000000l2.000000,2.000000l1.300000,-1.300000L4.300000,4.000000L3.000000,5.300000zM7.700000,10.000000l8.000000,8.000000L6.000000,18.000000c-2.200000,0.000000 -4.000000,-1.800000 -4.000000,-4.000000c0.000000,-2.200000 1.800000,-4.000000 4.000000,-4.000000L7.700000,10.000000z"
+ android:fillColor="#FFFFFF"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/moon.xml b/packages/SystemUI/res/drawable/moon.xml
new file mode 100644
index 0000000..4ee6286
--- /dev/null
+++ b/packages/SystemUI/res/drawable/moon.xml
@@ -0,0 +1,24 @@
+<!--
+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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="48.0dp"
+ android:height="48.0dp"
+ android:viewportWidth="48.0"
+ android:viewportHeight="48.0">
+ <path
+ android:pathData="M18.000000,4.000000c-2.100000,0.000000 -4.100000,0.300000 -6.000000,0.900000C20.100000,7.500000 26.000000,15.000000 26.000000,24.000000s-5.900000,16.500000 -14.000000,19.100000c1.900000,0.600000 3.900000,0.900000 6.000000,0.900000c11.000000,0.000000 20.000000,-9.000000 20.000000,-20.000000S29.000000,4.000000 18.000000,4.000000z"
+ android:fillColor="#FFF2F2FF"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/placeholder.xml b/packages/SystemUI/res/drawable/placeholder.xml
new file mode 100644
index 0000000..1933145
--- /dev/null
+++ b/packages/SystemUI/res/drawable/placeholder.xml
@@ -0,0 +1,51 @@
+<!--
+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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="100dp"
+ android:height="400dp"
+ android:viewportWidth="100"
+ android:viewportHeight="400">
+
+ <!-- future site of real artwork -->
+
+ <path android:fillColor="#FFFFFF00"
+ android:pathData="M 0,0 L 100,0 L 100,400 L 0,400 z" />
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M 0,0 L 100,25 L 100,50 L 0,25 z" />
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M 0,50 L 100,75 L 100,100 L 0,75 z" />
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M 0,100 L 100,125 L 100,150 L 0,125 z" />
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M 0,150 L 100,175 L 100,200 L 0,175 z" />
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M 0,200 L 100,225 L 100,250 L 0,225 z" />
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M 0,250 L 100,275 L 100,300 L 0,275 z" />
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M 0,300 L 100,325 L 100,350 L 0,325 z" />
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M 0,350 L 100,375 L 100,400 L 0,375 z" />
+</vector>
+
diff --git a/packages/SystemUI/res/drawable/scorecard.xml b/packages/SystemUI/res/drawable/scorecard.xml
new file mode 100644
index 0000000..707449a
--- /dev/null
+++ b/packages/SystemUI/res/drawable/scorecard.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<shape
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="rectangle"
+ >
+ <corners
+ android:radius="8dp" />
+ <solid
+ android:color="#ffffffff" />
+</shape>
diff --git a/packages/SystemUI/res/drawable/scorecard_gameover.xml b/packages/SystemUI/res/drawable/scorecard_gameover.xml
new file mode 100644
index 0000000..f663a66
--- /dev/null
+++ b/packages/SystemUI/res/drawable/scorecard_gameover.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<shape
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="rectangle"
+ >
+ <corners
+ android:radius="8dp" />
+ <solid
+ android:color="#ffff0000" />
+</shape>
diff --git a/packages/SystemUI/res/drawable/star.xml b/packages/SystemUI/res/drawable/star.xml
new file mode 100644
index 0000000..73ca04a
--- /dev/null
+++ b/packages/SystemUI/res/drawable/star.xml
@@ -0,0 +1,24 @@
+<!--
+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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="48.0dp"
+ android:height="48.0dp"
+ android:viewportWidth="48.0"
+ android:viewportHeight="48.0">
+ <path
+ android:pathData="M30.250000,17.750000L24.000000,4.000000l-6.250000,13.750000L4.000000,24.000000l13.750000,6.250000L24.000000,44.000000l6.250000,-13.750000L44.000000,24.000000L30.250000,17.750000z"
+ android:fillColor="#FFFFFF"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/sun.xml b/packages/SystemUI/res/drawable/sun.xml
new file mode 100644
index 0000000..3e4a233
--- /dev/null
+++ b/packages/SystemUI/res/drawable/sun.xml
@@ -0,0 +1,29 @@
+<!--
+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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="48.0dp"
+ android:height="48.0dp"
+ android:viewportWidth="48.0"
+ android:viewportHeight="48.0">
+ <group>
+ <path
+ android:pathData="M 24,8 A 16,16 0 1,0 24.0001,8 z"
+ android:fillColor="#FFFFFFCC" />
+ <path
+ android:pathData="M40.0,30.6l6.6,-6.6L40.0,17.4L40.0,8.0l-9.4,0.0L24.0,1.4L17.4,8.0L8.0,8.0l0.0,9.4L1.4,24.0L8.0,30.6L8.0,40.0l9.4,0.0l6.6,6.6l6.6,-6.6L40.0,40.0L40.0,30.6zM24.0,36.0c-6.6,0.0 -12.0,-5.4 -12.0,-12.0s5.4,-12.0 12.0,-12.0c6.6,0.0 12.0,5.4 12.0,12.0S30.6,36.0 24.0,36.0z"
+ android:fillColor="#FFFFFF40"/>
+ </group>
+</vector>
diff --git a/packages/SystemUI/res/drawable/sun2.xml b/packages/SystemUI/res/drawable/sun2.xml
new file mode 100644
index 0000000..6d2d504
--- /dev/null
+++ b/packages/SystemUI/res/drawable/sun2.xml
@@ -0,0 +1,24 @@
+<!--
+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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="48.0dp"
+ android:height="48.0dp"
+ android:viewportWidth="48.0"
+ android:viewportHeight="48.0">
+ <path
+ android:pathData="M40.000000,17.400000L40.000000,8.000000l-9.400000,0.000000L24.000000,1.400000L17.400000,8.000000L8.000000,8.000000l0.000000,9.400000L1.400000,24.000000L8.000000,30.600000L8.000000,40.000000l9.400000,0.000000l6.600000,6.600000l6.600000,-6.600000L40.000000,40.000000l0.000000,-9.400000l6.600000,-6.600000L40.000000,17.400000zM24.000000,36.000000c-6.600000,0.000000 -12.000000,-5.400000 -12.000000,-12.000000s5.400000,-12.000000 12.000000,-12.000000c6.600000,0.000000 12.000000,5.400000 12.000000,12.000000S30.600000,36.000000 24.000000,36.000000zM24.000000,16.000000c-4.400000,0.000000 -8.000000,3.600000 -8.000000,8.000000c0.000000,4.400000 3.600000,8.000000 8.000000,8.000000s8.000000,-3.600000 8.000000,-8.000000C32.000000,19.600000 28.400000,16.000000 24.000000,16.000000z"
+ android:fillColor="#FF000000"/>
+</vector>
diff --git a/packages/SystemUI/res/layout/lland.xml b/packages/SystemUI/res/layout/lland.xml
new file mode 100644
index 0000000..053225d
--- /dev/null
+++ b/packages/SystemUI/res/layout/lland.xml
@@ -0,0 +1,53 @@
+<?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.
+-->
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ >
+ <com.android.systemui.egg.LLand
+ android:id="@+id/world"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+ </com.android.systemui.egg.LLand>
+ <TextView
+ android:id="@+id/score"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textSize="32sp"
+ android:textColor="#FFAAAAAA"
+ android:layout_marginTop="32dp"
+ android:layout_marginLeft="16dp"
+ android:layout_gravity="top|left"
+ android:paddingLeft="16dp"
+ android:paddingRight="16dp"
+ android:paddingTop="8dp"
+ android:paddingBottom="8dp"
+ android:background="@drawable/scorecard"
+ />
+ <TextView
+ android:id="@+id/welcome"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textSize="30sp"
+ android:textColor="#FFFFFFFF"
+ android:layout_gravity="center"
+ android:layout_marginTop="70dp"
+ android:visibility="gone"
+ />
+</FrameLayout>
+
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index a8c95c1..9654da9 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -35,6 +35,10 @@
<!-- The number of app icons we keep in memory -->
<integer name="config_recents_max_icon_count">20</integer>
+
+ <!-- The theme to use for RecentsActivity. -->
+ <item type="style" name="config_recents_activity_theme">@style/RecentsTheme.Wallpaper</item>
+
<!-- Control whether status bar should distinguish HSPA data icon form UMTS
data icon on devices -->
<bool name="config_hspa_data_distinguishable">false</bool>
diff --git a/packages/SystemUI/res/values/lland_config.xml b/packages/SystemUI/res/values/lland_config.xml
new file mode 100644
index 0000000..56125a5
--- /dev/null
+++ b/packages/SystemUI/res/values/lland_config.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 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>
+ <dimen name="obstacle_spacing">380dp</dimen>
+ <dimen name="translation_per_sec">100dp</dimen>
+ <dimen name="boost_dv">600dp</dimen>
+ <dimen name="player_hit_size">40dp</dimen>
+ <dimen name="player_size">40dp</dimen>
+ <dimen name="obstacle_width">80dp</dimen>
+ <dimen name="obstacle_gap">170dp</dimen>
+ <dimen name="obstacle_height_min">40dp</dimen>
+ <dimen name="building_width_min">20dp</dimen>
+ <dimen name="building_width_max">250dp</dimen>
+ <dimen name="building_height_min">20dp</dimen>
+ <dimen name="cloud_size_min">10dp</dimen>
+ <dimen name="cloud_size_max">100dp</dimen>
+ <dimen name="sun_size">45dp</dimen>
+ <dimen name="moon_size">30dp</dimen>
+ <dimen name="star_size_min">3dp</dimen>
+ <dimen name="star_size_max">5dp</dimen>
+ <dimen name="G">30dp</dimen>
+ <dimen name="max_v">1000dp</dimen>
+ <dimen name="scenery_z">6dp</dimen>
+ <dimen name="obstacle_z">15dp</dimen>
+ <dimen name="player_z">15dp</dimen>
+ <dimen name="player_z_boost">18dp</dimen>
+ <dimen name="hud_z">35dp</dimen>
+</resources>
diff --git a/packages/SystemUI/res/values/lland_strings.xml b/packages/SystemUI/res/values/lland_strings.xml
new file mode 100644
index 0000000..ce88157
--- /dev/null
+++ b/packages/SystemUI/res/values/lland_strings.xml
@@ -0,0 +1,22 @@
+<?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 xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!-- Name of the L Land easter egg. DO NOT TRANSLATE -->
+ <string name="lland">L Land</string>
+</resources>
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index 0456c82..46e7587 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -20,14 +20,9 @@
<item name="android:windowAnimationStyle">@style/Animation.RecentsActivity</item>
</style>
- <!-- Alternate Recents theme -->
<style name="RecentsTheme" parent="@android:style/Theme">
<!-- NoTitle -->
<item name="android:windowNoTitle">true</item>
- <!-- Wallpaper -->
- <item name="android:windowBackground">@color/transparent</item>
- <item name="android:colorBackgroundCacheHint">@null</item>
- <item name="android:windowShowWallpaper">true</item>
<!-- Misc -->
<item name="android:statusBarColor">@android:color/transparent</item>
<item name="android:navigationBarColor">@android:color/transparent</item>
@@ -36,6 +31,20 @@
<item name="android:ambientShadowAlpha">0.35</item>
</style>
+
+ <!-- Alternate Recents theme -->
+ <style name="RecentsTheme.Wallpaper">
+ <!-- Wallpaper -->
+ <item name="android:windowBackground">@color/transparent</item>
+ <item name="android:colorBackgroundCacheHint">@null</item>
+ <item name="android:windowShowWallpaper">true</item>
+ </style>
+
+ <!-- Performance optimized alternate Recents theme (no wallpaper) -->
+ <style name="RecentsTheme.NoWallpaper">
+ <item name="android:windowBackground">@android:color/black</item>
+ </style>
+
<!-- Animations for a non-full-screen window or activity. -->
<style name="Animation.RecentsActivity" parent="@android:style/Animation.Activity">
<item name="android:activityOpenEnterAnimation">@anim/recents_launch_from_launcher_enter</item>
diff --git a/packages/SystemUI/src/com/android/systemui/egg/LLand.java b/packages/SystemUI/src/com/android/systemui/egg/LLand.java
new file mode 100644
index 0000000..d1c02dd
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/egg/LLand.java
@@ -0,0 +1,748 @@
+/*
+ * 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.
+ */
+
+package com.android.systemui.egg;
+
+import android.animation.TimeAnimator;
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.*;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.GradientDrawable;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.*;
+import android.view.animation.DecelerateInterpolator;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import java.util.ArrayList;
+
+import com.android.systemui.R;
+
+public class LLand extends FrameLayout {
+ public static final String TAG = "LLand";
+
+ public static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+ public static final boolean DEBUG_DRAW = false && DEBUG;
+
+ public static final void L(String s, Object ... objects) {
+ if (DEBUG) {
+ Log.d(TAG, String.format(s, objects));
+ }
+ }
+
+ public static final boolean AUTOSTART = true;
+ public static final boolean HAVE_STARS = true;
+
+ public static final float DEBUG_SPEED_MULTIPLIER = 1f; // 0.1f;
+ public static final boolean DEBUG_IDDQD = false;
+
+ private static class Params {
+ public float TRANSLATION_PER_SEC;
+ public int OBSTACLE_SPACING, OBSTACLE_PERIOD;
+ public int BOOST_DV;
+ public int PLAYER_HIT_SIZE;
+ public int PLAYER_SIZE;
+ public int OBSTACLE_WIDTH;
+ public int OBSTACLE_GAP;
+ public int OBSTACLE_MIN;
+ public int BUILDING_WIDTH_MIN, BUILDING_WIDTH_MAX;
+ public int BUILDING_HEIGHT_MIN;
+ public int CLOUD_SIZE_MIN, CLOUD_SIZE_MAX;
+ public int STAR_SIZE_MIN, STAR_SIZE_MAX;
+ public int G;
+ public int MAX_V;
+ public float SCENERY_Z, OBSTACLE_Z, PLAYER_Z, PLAYER_Z_BOOST, HUD_Z;
+ public Params(Resources res) {
+ TRANSLATION_PER_SEC = res.getDimension(R.dimen.translation_per_sec);
+ OBSTACLE_SPACING = res.getDimensionPixelSize(R.dimen.obstacle_spacing);
+ OBSTACLE_PERIOD = (int) (OBSTACLE_SPACING / TRANSLATION_PER_SEC);
+ BOOST_DV = res.getDimensionPixelSize(R.dimen.boost_dv);
+ PLAYER_HIT_SIZE = res.getDimensionPixelSize(R.dimen.player_hit_size);
+ PLAYER_SIZE = res.getDimensionPixelSize(R.dimen.player_size);
+ OBSTACLE_WIDTH = res.getDimensionPixelSize(R.dimen.obstacle_width);
+ OBSTACLE_GAP = res.getDimensionPixelSize(R.dimen.obstacle_gap);
+ OBSTACLE_MIN = res.getDimensionPixelSize(R.dimen.obstacle_height_min);
+ BUILDING_HEIGHT_MIN = res.getDimensionPixelSize(R.dimen.building_height_min);
+ BUILDING_WIDTH_MIN = res.getDimensionPixelSize(R.dimen.building_width_min);
+ BUILDING_WIDTH_MAX = res.getDimensionPixelSize(R.dimen.building_width_max);
+ CLOUD_SIZE_MIN = res.getDimensionPixelSize(R.dimen.cloud_size_min);
+ CLOUD_SIZE_MAX = res.getDimensionPixelSize(R.dimen.cloud_size_max);
+ STAR_SIZE_MIN = res.getDimensionPixelSize(R.dimen.star_size_min);
+ STAR_SIZE_MAX = res.getDimensionPixelSize(R.dimen.star_size_max);
+
+ G = res.getDimensionPixelSize(R.dimen.G);
+ MAX_V = res.getDimensionPixelSize(R.dimen.max_v);
+
+ SCENERY_Z = res.getDimensionPixelSize(R.dimen.scenery_z);
+ OBSTACLE_Z = res.getDimensionPixelSize(R.dimen.obstacle_z);
+ PLAYER_Z = res.getDimensionPixelSize(R.dimen.player_z);
+ PLAYER_Z_BOOST = res.getDimensionPixelSize(R.dimen.player_z_boost);
+ HUD_Z = res.getDimensionPixelSize(R.dimen.hud_z);
+ }
+ }
+
+ private TimeAnimator mAnim;
+
+ private TextView mScoreField;
+ private View mSplash;
+
+ private Player mDroid;
+ private ArrayList<Obstacle> mObstaclesInPlay = new ArrayList<Obstacle>();
+
+ private float t, dt;
+
+ private int mScore;
+ private float mLastPipeTime; // in sec
+ private int mWidth, mHeight;
+ private boolean mAnimating, mPlaying;
+ private boolean mFrozen; // after death, a short backoff
+
+ private int mTimeOfDay;
+ private static final int DAY = 0, NIGHT = 1, TWILIGHT = 2, SUNSET = 3;
+ private static final int[][] SKIES = {
+ { 0xFFc0c0FF, 0xFFa0a0FF }, // DAY
+ { 0xFF000010, 0xFF000000 }, // NIGHT
+ { 0xFF000040, 0xFF000010 }, // TWILIGHT
+ { 0xFF805010, 0xFF202080 }, // SUNSET
+ };
+
+ private static Params PARAMS;
+
+ public LLand(Context context) {
+ this(context, null);
+ }
+
+ public LLand(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public LLand(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ setFocusable(true);
+ PARAMS = new Params(getResources());
+ mTimeOfDay = irand(0, SKIES.length);
+ }
+
+ @Override
+ public boolean willNotDraw() {
+ return !DEBUG;
+ }
+
+ public int getGameWidth() { return mWidth; }
+ public int getGameHeight() { return mHeight; }
+ public float getGameTime() { return t; }
+ public float getLastTimeStep() { return dt; }
+
+ public void setScoreField(TextView tv) {
+ mScoreField = tv;
+ if (tv != null) {
+ tv.setTranslationZ(PARAMS.HUD_Z);
+ if (!(mAnimating && mPlaying)) {
+ tv.setTranslationY(-500);
+ }
+ }
+ }
+
+ public void setSplash(View v) {
+ mSplash = v;
+ }
+
+ @Override
+ protected void onSizeChanged(int w, int h, int oldw, int oldh) {
+ stop();
+ reset();
+ if (AUTOSTART) {
+ start(false);
+ }
+ }
+
+ final float hsv[] = {0, 0, 0};
+
+ private void reset() {
+ L("reset");
+ final Drawable sky = new GradientDrawable(
+ GradientDrawable.Orientation.BOTTOM_TOP,
+ SKIES[mTimeOfDay]
+ );
+ sky.setDither(true);
+ setBackground(sky);
+
+ setScaleX(frand() > 0.5f ? 1 : -1);
+
+ setScore(0);
+
+ int i = getChildCount();
+ while (i-->0) {
+ final View v = getChildAt(i);
+ if (v instanceof GameView) {
+ removeViewAt(i);
+ }
+ }
+
+ mObstaclesInPlay.clear();
+
+ mWidth = getWidth();
+ mHeight = getHeight();
+
+ boolean showingSun = (mTimeOfDay == DAY || mTimeOfDay == SUNSET) && frand() > 0.25;
+ if (showingSun) {
+ final Star sun = new Star(getContext());
+ sun.setBackgroundResource(R.drawable.sun);
+ final int w = getResources().getDimensionPixelSize(R.dimen.sun_size);
+ sun.setTranslationX(frand(w, mWidth-w));
+ if (mTimeOfDay == DAY) {
+ sun.setTranslationY(frand(w, (mHeight * 0.66f)));
+ sun.getBackground().setTint(0);
+ } else {
+ sun.setTranslationY(frand(mHeight * 0.66f, mHeight - w));
+ sun.getBackground().setTintMode(PorterDuff.Mode.SRC_ATOP);
+ sun.getBackground().setTint(0xC0FF8000);
+
+ }
+ addView(sun, new LayoutParams(w, w));
+ }
+ if (!showingSun) {
+ final boolean dark = mTimeOfDay == NIGHT || mTimeOfDay == TWILIGHT;
+ final float ff = frand();
+ if ((dark && ff < 0.75f) || ff < 0.5f) {
+ final Star moon = new Star(getContext());
+ moon.setBackgroundResource(R.drawable.moon);
+ moon.getBackground().setAlpha(dark ? 255 : 128);
+ moon.setScaleX(frand() > 0.5 ? -1 : 1);
+ moon.setRotation(moon.getScaleX() * frand(5, 30));
+ final int w = getResources().getDimensionPixelSize(R.dimen.sun_size);
+ moon.setTranslationX(frand(w, mWidth - w));
+ moon.setTranslationY(frand(w, mHeight - w));
+ addView(moon, new LayoutParams(w, w));
+ }
+ }
+
+ final int mh = mHeight / 6;
+ final boolean cloudless = frand() < 0.25;
+ final int N = 20;
+ for (i=0; i<N; i++) {
+ final float r1 = frand();
+ final Scenery s;
+ if (HAVE_STARS && r1 < 0.3 && mTimeOfDay != DAY) {
+ s = new Star(getContext());
+ } else if (r1 < 0.6 && !cloudless) {
+ s = new Cloud(getContext());
+ } else {
+ s = new Building(getContext());
+
+ s.z = (float)i/N;
+ s.setTranslationZ(PARAMS.SCENERY_Z * (1+s.z));
+ s.v = 0.85f * s.z; // buildings move proportional to their distance
+ hsv[0] = 175;
+ hsv[1] = 0.25f;
+ hsv[2] = 1 * s.z;
+ s.setBackgroundColor(Color.HSVToColor(hsv));
+ s.h = irand(PARAMS.BUILDING_HEIGHT_MIN, mh);
+ }
+ final LayoutParams lp = new LayoutParams(s.w, s.h);
+ if (s instanceof Building) {
+ lp.gravity = Gravity.BOTTOM;
+ } else {
+ lp.gravity = Gravity.TOP;
+ final float r = frand();
+ if (s instanceof Star) {
+ lp.topMargin = (int) (r * r * mHeight);
+ } else {
+ lp.topMargin = (int) (1 - r*r * mHeight/2) + mHeight/2;
+ }
+ }
+
+ addView(s, lp);
+ s.setTranslationX(frand(-lp.width, mWidth + lp.width));
+ }
+
+ mDroid = new Player(getContext());
+ mDroid.setX(mWidth / 2);
+ mDroid.setY(mHeight / 2);
+ addView(mDroid, new LayoutParams(PARAMS.PLAYER_SIZE, PARAMS.PLAYER_SIZE));
+ if (mAnim != null) {
+ Log.wtf(TAG, "reseting while animating??!?");
+ }
+ mAnim = new TimeAnimator();
+ mAnim.setTimeListener(new TimeAnimator.TimeListener() {
+ @Override
+ public void onTimeUpdate(TimeAnimator timeAnimator, long t, long dt) {
+ step(t, dt);
+ }
+ });
+ }
+
+ private void setScore(int score) {
+ mScore = score;
+ if (mScoreField != null) mScoreField.setText(String.valueOf(score));
+ }
+
+ private void addScore(int incr) {
+ setScore(mScore + incr);
+ }
+
+ private void start(boolean startPlaying) {
+ L("start(startPlaying=%s)", startPlaying?"true":"false");
+ if (startPlaying) {
+ mPlaying = true;
+
+ t = 0;
+ mLastPipeTime = getGameTime() - PARAMS.OBSTACLE_PERIOD; // queue up a obstacle
+
+ if (mSplash != null && mSplash.getAlpha() > 0f) {
+ mSplash.setTranslationZ(PARAMS.HUD_Z);
+ mSplash.animate().alpha(0).translationZ(0).setDuration(400);
+
+ mScoreField.animate().translationY(0)
+ .setInterpolator(new DecelerateInterpolator())
+ .setDuration(1500);
+ }
+
+ mScoreField.setTextColor(0xFFAAAAAA);
+ mScoreField.setBackgroundResource(R.drawable.scorecard);
+ mDroid.setVisibility(View.VISIBLE);
+ mDroid.setX(mWidth / 2);
+ mDroid.setY(mHeight / 2);
+ } else {
+ mDroid.setVisibility(View.GONE);
+ }
+ if (!mAnimating) {
+ mAnim.start();
+ mAnimating = true;
+ }
+ }
+
+ private void stop() {
+ if (mAnimating) {
+ mAnim.cancel();
+ mAnim = null;
+ mAnimating = false;
+ mScoreField.setTextColor(0xFFFFFFFF);
+ mScoreField.setBackgroundResource(R.drawable.scorecard_gameover);
+ mTimeOfDay = irand(0, SKIES.length); // for next reset
+ mFrozen = true;
+ postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ mFrozen = false;
+ }
+ }, 250);
+ }
+ }
+
+ public static final float lerp(float x, float a, float b) {
+ return (b - a) * x + a;
+ }
+
+ public static final float rlerp(float v, float a, float b) {
+ return (v - a) / (b - a);
+ }
+
+ public static final float clamp(float f) {
+ return f < 0f ? 0f : f > 1f ? 1f : f;
+ }
+
+ public static final float frand() {
+ return (float) Math.random();
+ }
+
+ public static final float frand(float a, float b) {
+ return lerp(frand(), a, b);
+ }
+
+ public static final int irand(int a, int b) {
+ return (int) lerp(frand(), (float) a, (float) b);
+ }
+
+ private void step(long t_ms, long dt_ms) {
+ t = t_ms / 1000f; // seconds
+ dt = dt_ms / 1000f;
+
+ if (DEBUG) {
+ t *= DEBUG_SPEED_MULTIPLIER;
+ dt *= DEBUG_SPEED_MULTIPLIER;
+ }
+
+ // 1. Move all objects and update bounds
+ final int N = getChildCount();
+ int i = 0;
+ for (; i<N; i++) {
+ final View v = getChildAt(i);
+ if (v instanceof GameView) {
+ ((GameView) v).step(t_ms, dt_ms, t, dt);
+ }
+ }
+
+ // 2. Check for altitude
+ if (mPlaying && mDroid.below(mHeight)) {
+ if (DEBUG_IDDQD) {
+ poke();
+ } else {
+ L("player hit the floor");
+ stop();
+ }
+ }
+
+ // 3. Check for obstacles
+ boolean passedBarrier = false;
+ for (int j = mObstaclesInPlay.size(); j-->0;) {
+ final Obstacle ob = mObstaclesInPlay.get(j);
+ if (mPlaying && ob.intersects(mDroid) && !DEBUG_IDDQD) {
+ L("player hit an obstacle");
+ stop();
+ } else if (ob.cleared(mDroid)) {
+ passedBarrier = true;
+ mObstaclesInPlay.remove(j);
+ }
+ }
+
+ if (mPlaying && passedBarrier) {
+ addScore(1);
+ }
+
+ // 4. Handle edge of screen
+ // Walk backwards to make sure removal is safe
+ while (i-->0) {
+ final View v = getChildAt(i);
+ if (v instanceof Obstacle) {
+ if (v.getTranslationX() + v.getWidth() < 0) {
+ removeViewAt(i);
+ }
+ } else if (v instanceof Scenery) {
+ final Scenery s = (Scenery) v;
+ if (v.getTranslationX() + s.w < 0) {
+ v.setTranslationX(getWidth());
+ }
+ }
+ }
+
+ // 3. Time for more obstacles!
+ if (mPlaying && (t - mLastPipeTime) > PARAMS.OBSTACLE_PERIOD) {
+ mLastPipeTime = t;
+ final int obstacley = (int) (Math.random()
+ * (mHeight - 2*PARAMS.OBSTACLE_MIN - PARAMS.OBSTACLE_GAP)) + PARAMS.OBSTACLE_MIN;
+
+ final Obstacle p1 = new Obstacle(getContext(), obstacley);
+ addView(p1, new LayoutParams(
+ PARAMS.OBSTACLE_WIDTH,
+ mHeight,
+ Gravity.TOP|Gravity.LEFT));
+ p1.setTranslationX(mWidth);
+ p1.setTranslationY(-mHeight);
+ p1.setTranslationZ(0);
+ p1.animate()
+ .translationY(-mHeight+p1.h)
+ .translationZ(PARAMS.OBSTACLE_Z)
+ .setStartDelay(irand(0,250))
+ .setDuration(250);
+ mObstaclesInPlay.add(p1);
+
+ final Obstacle p2 = new Obstacle(getContext(),
+ mHeight - obstacley - PARAMS.OBSTACLE_GAP);
+ addView(p2, new LayoutParams(
+ PARAMS.OBSTACLE_WIDTH,
+ mHeight,
+ Gravity.TOP|Gravity.LEFT));
+ p2.setTranslationX(mWidth);
+ p2.setTranslationY(mHeight);
+ p2.setTranslationZ(0);
+ p2.animate()
+ .translationY(mHeight-p2.h)
+ .translationZ(PARAMS.OBSTACLE_Z)
+ .setStartDelay(irand(0,100))
+ .setDuration(400);
+ mObstaclesInPlay.add(p2);
+ }
+
+ if (DEBUG) {
+ final Rect r = new Rect();
+ mDroid.getHitRect(r);
+ r.inset(-4, -4);
+ invalidate(r);
+ }
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent ev) {
+ if (DEBUG) L("touch: %s", ev);
+ if (ev.getAction() == MotionEvent.ACTION_DOWN) {
+ poke();
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public boolean onTrackballEvent(MotionEvent ev) {
+ if (DEBUG) L("trackball: %s", ev);
+ if (ev.getAction() == MotionEvent.ACTION_DOWN) {
+ poke();
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public boolean onKeyDown(int keyCode, KeyEvent ev) {
+ if (DEBUG) L("keyDown: %d", keyCode);
+ switch (keyCode) {
+ case KeyEvent.KEYCODE_DPAD_CENTER:
+ case KeyEvent.KEYCODE_DPAD_UP:
+ case KeyEvent.KEYCODE_SPACE:
+ case KeyEvent.KEYCODE_ENTER:
+ case KeyEvent.KEYCODE_BUTTON_A:
+ poke();
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public boolean onGenericMotionEvent (MotionEvent ev) {
+ if (DEBUG) L("generic: %s", ev);
+ return false;
+ }
+
+ private void poke() {
+ L("poke");
+ if (mFrozen) return;
+ if (!mAnimating) {
+ reset();
+ start(true);
+ } else if (!mPlaying) {
+ start(true);
+ }
+ mDroid.boost();
+ if (DEBUG) {
+ mDroid.dv *= DEBUG_SPEED_MULTIPLIER;
+ mDroid.animate().setDuration((long) (200/DEBUG_SPEED_MULTIPLIER));
+ }
+ }
+
+ @Override
+ public void onDraw(Canvas c) {
+ super.onDraw(c);
+
+ if (!DEBUG_DRAW) return;
+
+ final Paint pt = new Paint();
+ pt.setColor(0xFFFFFFFF);
+ final int L = mDroid.corners.length;
+ final int N = L/2;
+ for (int i=0; i<N; i++) {
+ final int x = (int) mDroid.corners[i*2];
+ final int y = (int) mDroid.corners[i*2+1];
+ c.drawCircle(x, y, 4, pt);
+ c.drawLine(x, y,
+ mDroid.corners[(i*2+2)%L],
+ mDroid.corners[(i*2+3)%L],
+ pt);
+ }
+
+ final int M = getChildCount();
+ pt.setColor(0x6000FF00);
+ for (int i=0; i<M; i++) {
+ final View v = getChildAt(i);
+ if (v == mDroid) continue;
+ if (!(v instanceof GameView)) continue;
+ final Rect r = new Rect();
+ v.getHitRect(r);
+ c.drawRect(r, pt);
+ }
+
+ pt.setColor(Color.BLACK);
+ final StringBuilder sb = new StringBuilder("obstacles: ");
+ for (Obstacle ob : mObstaclesInPlay) {
+ sb.append(ob.hitRect.toShortString());
+ sb.append(" ");
+ }
+ pt.setTextSize(20f);
+ c.drawText(sb.toString(), 20, 100, pt);
+ }
+
+ static final Rect sTmpRect = new Rect();
+
+ private interface GameView {
+ public void step(long t_ms, long dt_ms, float t, float dt);
+ }
+
+ private class Player extends ImageView implements GameView {
+ public float dv;
+
+ private final float[] sHull = new float[] {
+ 0.3f, 0f, // left antenna
+ 0.7f, 0f, // right antenna
+ 0.92f, 0.33f, // off the right shoulder of Orion
+ 0.92f, 0.75f, // right hand (our right, not his right)
+ 0.6f, 1f, // right foot
+ 0.4f, 1f, // left foot BLUE!
+ 0.08f, 0.75f, // sinistram
+ 0.08f, 0.33f, // cold shoulder
+ };
+ public final float[] corners = new float[sHull.length];
+
+ public Player(Context context) {
+ super(context);
+
+ setBackgroundResource(R.drawable.android);
+ getBackground().setTintMode(PorterDuff.Mode.SRC_ATOP);
+ getBackground().setTint(0xFF00FF00);
+ setOutlineProvider(new ViewOutlineProvider() {
+ @Override
+ public void getOutline(View view, Outline outline) {
+ final int w = view.getWidth();
+ final int h = view.getHeight();
+ final int ix = (int) (w * 0.3f);
+ final int iy = (int) (h * 0.2f);
+ outline.setRect(ix, iy, w - ix, h - iy);
+ }
+ });
+ }
+
+ public void prepareCheckIntersections() {
+ final int inset = (PARAMS.PLAYER_SIZE - PARAMS.PLAYER_HIT_SIZE)/2;
+ final int scale = PARAMS.PLAYER_HIT_SIZE;
+ final int N = sHull.length/2;
+ for (int i=0; i<N; i++) {
+ corners[i*2] = scale * sHull[i*2] + inset;
+ corners[i*2+1] = scale * sHull[i*2+1] + inset;
+ }
+ final Matrix m = getMatrix();
+ m.mapPoints(corners);
+ }
+
+ public boolean below(int h) {
+ final int N = corners.length/2;
+ for (int i=0; i<N; i++) {
+ final int y = (int) corners[i*2+1];
+ if (y >= h) return true;
+ }
+ return false;
+ }
+
+ public void step(long t_ms, long dt_ms, float t, float dt) {
+ if (getVisibility() != View.VISIBLE) return; // not playing yet
+
+ dv += PARAMS.G;
+ if (dv < -PARAMS.MAX_V) dv = -PARAMS.MAX_V;
+ else if (dv > PARAMS.MAX_V) dv = PARAMS.MAX_V;
+
+ final float y = getTranslationY() + dv * dt;
+ setTranslationY(y < 0 ? 0 : y);
+ setRotation(
+ 90 + lerp(clamp(rlerp(dv, PARAMS.MAX_V, -1 * PARAMS.MAX_V)), 90, -90));
+
+ prepareCheckIntersections();
+ }
+
+ public void boost() {
+ dv = -PARAMS.BOOST_DV;
+ setTranslationZ(PARAMS.PLAYER_Z_BOOST);
+ setScaleX(1.25f);
+ setScaleY(1.25f);
+ animate()
+ .scaleX(1f)
+ .scaleY(1f)
+ .translationZ(PARAMS.PLAYER_Z)
+ .setDuration(200);
+ }
+ }
+
+ private class Obstacle extends View implements GameView {
+ public float h;
+
+ public final Rect hitRect = new Rect();
+
+ public Obstacle(Context context, float h) {
+ super(context);
+ setBackgroundResource(R.drawable.placeholder);
+ this.h = h;
+ }
+
+ public boolean intersects(Player p) {
+ final int N = p.corners.length/2;
+ for (int i=0; i<N; i++) {
+ final int x = (int) p.corners[i*2];
+ final int y = (int) p.corners[i*2+1];
+ if (hitRect.contains(x, y)) return true;
+ }
+ return false;
+ }
+
+ public boolean cleared(Player p) {
+ final int N = p.corners.length/2;
+ for (int i=0; i<N; i++) {
+ final int x = (int) p.corners[i*2];
+ if (hitRect.right >= x) return false;
+ }
+ return true;
+ }
+
+ @Override
+ public void step(long t_ms, long dt_ms, float t, float dt) {
+ setTranslationX(getTranslationX()-PARAMS.TRANSLATION_PER_SEC*dt);
+ getHitRect(hitRect);
+ }
+ }
+
+ private class Scenery extends FrameLayout implements GameView {
+ public float z;
+ public float v;
+ public int h, w;
+ public Scenery(Context context) {
+ super(context);
+ }
+
+ @Override
+ public void step(long t_ms, long dt_ms, float t, float dt) {
+ setTranslationX(getTranslationX() - PARAMS.TRANSLATION_PER_SEC * dt * v);
+ }
+ }
+
+ private class Building extends Scenery {
+ public Building(Context context) {
+ super(context);
+
+ w = irand(PARAMS.BUILDING_WIDTH_MIN, PARAMS.BUILDING_WIDTH_MAX);
+ h = 0; // will be setup later, along with z
+
+ setTranslationZ(PARAMS.SCENERY_Z);
+ }
+ }
+
+ private class Cloud extends Scenery {
+ public Cloud(Context context) {
+ super(context);
+ setBackgroundResource(frand() < 0.01f ? R.drawable.cloud_off : R.drawable.cloud);
+ getBackground().setAlpha(0x40);
+ w = h = irand(PARAMS.CLOUD_SIZE_MIN, PARAMS.CLOUD_SIZE_MAX);
+ z = 0;
+ v = frand(0.15f,0.5f);
+ }
+ }
+
+ private class Star extends Scenery {
+ public Star(Context context) {
+ super(context);
+ setBackgroundResource(R.drawable.star);
+ w = h = irand(PARAMS.STAR_SIZE_MIN, PARAMS.STAR_SIZE_MAX);
+ v = z = 0;
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/egg/LLandActivity.java b/packages/SystemUI/src/com/android/systemui/egg/LLandActivity.java
new file mode 100644
index 0000000..88fd952
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/egg/LLandActivity.java
@@ -0,0 +1,36 @@
+/*
+ * 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.
+ */
+
+package com.android.systemui.egg;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.util.Log;
+import android.widget.TextView;
+
+import com.android.systemui.R;
+
+public class LLandActivity extends Activity {
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.lland);
+ LLand world = (LLand) findViewById(R.id.world);
+ world.setScoreField((TextView) findViewById(R.id.score));
+ world.setSplash(findViewById(R.id.welcome));
+ Log.v(LLand.TAG, "focus: " + world.requestFocus());
+ }
+}
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index ebe21ff..24bfba6 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -3277,7 +3277,7 @@
// But we still have not gotten the window state from the
// window manager, so delay the notification until then.
AccessibilityWindowInfo window = findWindowById(event.getWindowId());
- if (window == null || !window.isFocused()) {
+ if (window == null) {
mShowingFocusedWindowEvent = AccessibilityEvent.obtain(event);
return false;
}
@@ -3377,7 +3377,7 @@
if (mShowingFocusedWindowEvent != null) {
final int windowId = mShowingFocusedWindowEvent.getWindowId();
AccessibilityWindowInfo window = findWindowById(windowId);
- if (window != null && window.isFocused()) {
+ if (window != null) {
// Sending does the recycle.
sendAccessibilityEvent(mShowingFocusedWindowEvent, mCurrentUserId);
}
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index dfec307..967681b 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -1357,13 +1357,6 @@
"ConnectivityService");
}
- // TODO Make this a special check when it goes public
- private void enforceTetherChangePermission() {
- mContext.enforceCallingOrSelfPermission(
- android.Manifest.permission.CHANGE_NETWORK_STATE,
- "ConnectivityService");
- }
-
private void enforceTetherAccessPermission() {
mContext.enforceCallingOrSelfPermission(
android.Manifest.permission.ACCESS_NETWORK_STATE,
@@ -2389,8 +2382,7 @@
// javadoc from interface
public int tether(String iface) {
- enforceTetherChangePermission();
-
+ ConnectivityManager.enforceTetherChangePermission(mContext);
if (isTetheringSupported()) {
return mTethering.tether(iface);
} else {
@@ -2400,7 +2392,7 @@
// javadoc from interface
public int untether(String iface) {
- enforceTetherChangePermission();
+ ConnectivityManager.enforceTetherChangePermission(mContext);
if (isTetheringSupported()) {
return mTethering.untether(iface);
@@ -2449,7 +2441,7 @@
}
public int setUsbTethering(boolean enable) {
- enforceTetherChangePermission();
+ ConnectivityManager.enforceTetherChangePermission(mContext);
if (isTetheringSupported()) {
return mTethering.setUsbTethering(enable);
} else {
diff --git a/services/core/java/com/android/server/SystemConfig.java b/services/core/java/com/android/server/SystemConfig.java
index fdcb3b9..cf2a49f 100644
--- a/services/core/java/com/android/server/SystemConfig.java
+++ b/services/core/java/com/android/server/SystemConfig.java
@@ -50,19 +50,16 @@
// These are the built-in uid -> permission mappings that were read from the
// system configuration files.
- final SparseArray<HashSet<String>> mSystemPermissions =
- new SparseArray<HashSet<String>>();
+ final SparseArray<HashSet<String>> mSystemPermissions = new SparseArray<>();
// These are the built-in shared libraries that were read from the
// system configuration files. Keys are the library names; strings are the
// paths to the libraries.
- final ArrayMap<String, String> mSharedLibraries
- = new ArrayMap<String, String>();
+ final ArrayMap<String, String> mSharedLibraries = new ArrayMap<>();
// These are the features this devices supports that were read from the
// system configuration files.
- final HashMap<String, FeatureInfo> mAvailableFeatures =
- new HashMap<String, FeatureInfo>();
+ final HashMap<String, FeatureInfo> mAvailableFeatures = new HashMap<>();
public static final class PermissionEntry {
public final String name;
@@ -75,12 +72,14 @@
// These are the permission -> gid mappings that were read from the
// system configuration files.
- final ArrayMap<String, PermissionEntry> mPermissions =
- new ArrayMap<String, PermissionEntry>();
+ final ArrayMap<String, PermissionEntry> mPermissions = new ArrayMap<>();
// These are the packages that are white-listed to be able to run in the
// background while in power save mode, as read from the configuration files.
- final ArraySet<String> mAllowInPowerSave = new ArraySet<String>();
+ final ArraySet<String> mAllowInPowerSave = new ArraySet<>();
+
+ // These are the app package names that should not allow IME switching.
+ final ArraySet<String> mFixedImeApps = new ArraySet<>();
public static SystemConfig getInstance() {
synchronized (SystemConfig.class) {
@@ -115,6 +114,10 @@
return mAllowInPowerSave;
}
+ public ArraySet<String> getFixedImeApps() {
+ return mFixedImeApps;
+ }
+
SystemConfig() {
// Read configuration from system
readPermissions(Environment.buildPath(
@@ -298,6 +301,17 @@
XmlUtils.skipCurrentTag(parser);
continue;
+ } else if ("fixed-ime-app".equals(name)) {
+ String pkgname = parser.getAttributeValue(null, "package");
+ if (pkgname == null) {
+ Slog.w(TAG, "<fixed-ime-app> without package at "
+ + parser.getPositionDescription());
+ } else {
+ mFixedImeApps.add(pkgname);
+ }
+ XmlUtils.skipCurrentTag(parser);
+ continue;
+
} else {
XmlUtils.skipCurrentTag(parser);
continue;
diff --git a/services/core/java/com/android/server/am/ActivityRecord.java b/services/core/java/com/android/server/am/ActivityRecord.java
index e043f03..adea271 100755
--- a/services/core/java/com/android/server/am/ActivityRecord.java
+++ b/services/core/java/com/android/server/am/ActivityRecord.java
@@ -917,6 +917,7 @@
if (displayStartTime != 0) {
reportLaunchTimeLocked(SystemClock.uptimeMillis());
}
+ mStackSupervisor.sendWaitingVisibleReportLocked(this);
startTime = 0;
finishLaunchTickingLocked();
if (task != null) {
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
index 4940493..ebd0d4e9 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -650,31 +650,47 @@
}
void reportActivityVisibleLocked(ActivityRecord r) {
+ sendWaitingVisibleReportLocked(r);
+ notifyActivityDrawnForKeyguard();
+ }
+
+ void sendWaitingVisibleReportLocked(ActivityRecord r) {
+ boolean changed = false;
for (int i = mWaitingActivityVisible.size()-1; i >= 0; i--) {
WaitResult w = mWaitingActivityVisible.get(i);
- w.timeout = false;
- if (r != null) {
- w.who = new ComponentName(r.info.packageName, r.info.name);
+ if (w.who == null) {
+ changed = true;
+ w.timeout = false;
+ if (r != null) {
+ w.who = new ComponentName(r.info.packageName, r.info.name);
+ }
+ w.totalTime = SystemClock.uptimeMillis() - w.thisTime;
+ w.thisTime = w.totalTime;
}
- w.totalTime = SystemClock.uptimeMillis() - w.thisTime;
- w.thisTime = w.totalTime;
}
- mService.notifyAll();
- notifyActivityDrawnForKeyguard();
+ if (changed) {
+ mService.notifyAll();
+ }
}
void reportActivityLaunchedLocked(boolean timeout, ActivityRecord r,
long thisTime, long totalTime) {
+ boolean changed = false;
for (int i = mWaitingActivityLaunched.size() - 1; i >= 0; i--) {
WaitResult w = mWaitingActivityLaunched.remove(i);
- w.timeout = timeout;
- if (r != null) {
- w.who = new ComponentName(r.info.packageName, r.info.name);
+ if (w.who == null) {
+ changed = true;
+ w.timeout = timeout;
+ if (r != null) {
+ w.who = new ComponentName(r.info.packageName, r.info.name);
+ }
+ w.thisTime = thisTime;
+ w.totalTime = totalTime;
}
- w.thisTime = thisTime;
- w.totalTime = totalTime;
}
- mService.notifyAll();
+ if (changed) {
+ mService.notifyAll();
+ }
}
ActivityRecord topRunningActivityLocked() {
@@ -936,7 +952,7 @@
} while (!outResult.timeout && outResult.who == null);
} else if (res == ActivityManager.START_TASK_TO_FRONT) {
ActivityRecord r = stack.topRunningActivityLocked(null);
- if (r.nowVisible) {
+ if (r.nowVisible && r.state == ActivityState.RESUMED) {
outResult.timeout = false;
outResult.who = new ComponentName(r.info.packageName, r.info.name);
outResult.totalTime = 0;
diff --git a/services/core/java/com/android/server/am/TaskPersister.java b/services/core/java/com/android/server/am/TaskPersister.java
index b21af48..df1772a 100644
--- a/services/core/java/com/android/server/am/TaskPersister.java
+++ b/services/core/java/com/android/server/am/TaskPersister.java
@@ -51,6 +51,12 @@
* task being launched a chance to load its resources without this occupying IO bandwidth. */
private static final long PRE_TASK_DELAY_MS = 3000;
+ /** The maximum number of entries to keep in the queue before draining it automatically. */
+ private static final int MAX_WRITE_QUEUE_LENGTH = 6;
+
+ /** Special value for mWriteTime to mean don't wait, just write */
+ private static final long FLUSH_QUEUE = -1;
+
private static final String RECENTS_FILENAME = "_task";
private static final String TASKS_DIRNAME = "recent_tasks";
private static final String TASK_EXTENSION = ".xml";
@@ -120,6 +126,31 @@
mLazyTaskWriterThread.start();
}
+ private void removeThumbnails(TaskRecord task) {
+ final String taskString = Integer.toString(task.taskId);
+ for (int queueNdx = mWriteQueue.size() - 1; queueNdx >= 0; --queueNdx) {
+ final WriteQueueItem item = mWriteQueue.get(queueNdx);
+ if (item instanceof ImageWriteQueueItem &&
+ ((ImageWriteQueueItem) item).mFilename.startsWith(taskString)) {
+ if (DEBUG) Slog.d(TAG, "Removing " + ((ImageWriteQueueItem) item).mFilename +
+ " from write queue");
+ mWriteQueue.remove(queueNdx);
+ }
+ }
+ }
+
+ private void yieldIfQueueTooDeep() {
+ boolean stall = false;
+ synchronized (this) {
+ if (mNextWriteTime == FLUSH_QUEUE) {
+ stall = true;
+ }
+ }
+ if (stall) {
+ Thread.yield();
+ }
+ }
+
void wakeup(TaskRecord task, boolean flush) {
synchronized (this) {
if (task != null) {
@@ -128,6 +159,10 @@
final WriteQueueItem item = mWriteQueue.get(queueNdx);
if (item instanceof TaskWriteQueueItem &&
((TaskWriteQueueItem) item).mTask == task) {
+ if (!task.inRecents) {
+ // This task is being removed.
+ removeThumbnails(task);
+ }
break;
}
}
@@ -138,15 +173,18 @@
// Dummy.
mWriteQueue.add(new WriteQueueItem());
}
- if (flush) {
- mNextWriteTime = -1;
+ if (flush || mWriteQueue.size() > MAX_WRITE_QUEUE_LENGTH) {
+ mNextWriteTime = FLUSH_QUEUE;
} else if (mNextWriteTime == 0) {
mNextWriteTime = SystemClock.uptimeMillis() + PRE_TASK_DELAY_MS;
}
if (DEBUG) Slog.d(TAG, "wakeup: task=" + task + " flush=" + flush + " mNextWriteTime="
- + mNextWriteTime + " Callers=" + Debug.getCallers(4));
+ + mNextWriteTime + " mWriteQueue.size=" + mWriteQueue.size()
+ + " Callers=" + Debug.getCallers(4));
notifyAll();
}
+
+ yieldIfQueueTooDeep();
}
void saveImage(Bitmap image, String filename) {
@@ -166,7 +204,9 @@
if (queueNdx < 0) {
mWriteQueue.add(new ImageWriteQueueItem(filename, image));
}
- if (mNextWriteTime == 0) {
+ if (mWriteQueue.size() > MAX_WRITE_QUEUE_LENGTH) {
+ mNextWriteTime = FLUSH_QUEUE;
+ } else if (mNextWriteTime == 0) {
mNextWriteTime = SystemClock.uptimeMillis() + PRE_TASK_DELAY_MS;
}
if (DEBUG) Slog.d(TAG, "saveImage: filename=" + filename + " now=" +
@@ -174,6 +214,8 @@
mNextWriteTime + " Callers=" + Debug.getCallers(4));
notifyAll();
}
+
+ yieldIfQueueTooDeep();
}
Bitmap getThumbnail(String filename) {
@@ -425,7 +467,7 @@
// If mNextWriteTime, then don't delay between each call to saveToXml().
final WriteQueueItem item;
synchronized (TaskPersister.this) {
- if (mNextWriteTime >= 0) {
+ if (mNextWriteTime != FLUSH_QUEUE) {
// The next write we don't have to wait so long.
mNextWriteTime = SystemClock.uptimeMillis() + INTER_WRITE_DELAY_MS;
if (DEBUG) Slog.d(TAG, "Next write time may be in " +
@@ -439,13 +481,14 @@
TaskPersister.this.wait();
} catch (InterruptedException e) {
}
- // Invariant: mNextWriteTime is either -1 or PRE_WRITE_DELAY_MS from now.
+ // Invariant: mNextWriteTime is either FLUSH_QUEUE or PRE_WRITE_DELAY_MS
+ // from now.
}
item = mWriteQueue.remove(0);
long now = SystemClock.uptimeMillis();
if (DEBUG) Slog.d(TAG, "LazyTaskWriter: now=" + now + " mNextWriteTime=" +
- mNextWriteTime);
+ mNextWriteTime + " mWriteQueue.size=" + mWriteQueue.size());
while (now < mNextWriteTime) {
try {
if (DEBUG) Slog.d(TAG, "LazyTaskWriter: waiting " +
@@ -484,7 +527,7 @@
TaskRecord task = ((TaskWriteQueueItem) item).mTask;
if (DEBUG) Slog.d(TAG, "Writing task=" + task);
synchronized (mService) {
- if (mService.mRecentTasks.contains(task)) {
+ if (task.inRecents) {
// Still there.
try {
if (DEBUG) Slog.d(TAG, "Saving task=" + task);
diff --git a/services/core/java/com/android/server/am/TaskRecord.java b/services/core/java/com/android/server/am/TaskRecord.java
index 4de7367..f74b795 100644
--- a/services/core/java/com/android/server/am/TaskRecord.java
+++ b/services/core/java/com/android/server/am/TaskRecord.java
@@ -327,8 +327,8 @@
}
}
- if (intent != null &&
- (intent.getFlags()&Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED) != 0) {
+ final int intentFlags = intent == null ? 0 : intent.getFlags();
+ if ((intentFlags & Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED) != 0) {
// Once we are set to an Intent with this flag, we count this
// task as having a true root activity.
rootWasReset = true;
@@ -338,8 +338,8 @@
if ((info.flags & ActivityInfo.FLAG_AUTO_REMOVE_FROM_RECENTS) != 0) {
// If the activity itself has requested auto-remove, then just always do it.
autoRemoveRecents = true;
- } else if ((intent.getFlags()&(Intent.FLAG_ACTIVITY_NEW_DOCUMENT
- |Intent.FLAG_ACTIVITY_RETAIN_IN_RECENTS)) == Intent.FLAG_ACTIVITY_NEW_DOCUMENT) {
+ } else if ((intentFlags & (Intent.FLAG_ACTIVITY_NEW_DOCUMENT
+ | Intent.FLAG_ACTIVITY_RETAIN_IN_RECENTS)) == Intent.FLAG_ACTIVITY_NEW_DOCUMENT) {
// If the caller has not asked for the document to be retained, then we may
// want to turn on auto-remove, depending on whether the target has set its
// own document launch mode.
diff --git a/services/core/java/com/android/server/location/GpsLocationProvider.java b/services/core/java/com/android/server/location/GpsLocationProvider.java
index df846a8..f1c5a6c 100644
--- a/services/core/java/com/android/server/location/GpsLocationProvider.java
+++ b/services/core/java/com/android/server/location/GpsLocationProvider.java
@@ -162,6 +162,10 @@
private static final int GPS_CAPABILITY_SINGLE_SHOT = 0x0000008;
private static final int GPS_CAPABILITY_ON_DEMAND_TIME = 0x0000010;
+ // The AGPS SUPL mode
+ private static final int AGPS_SUPL_MODE_MSA = 0x02;
+ private static final int AGPS_SUPL_MODE_MSB = 0x01;
+
// these need to match AGpsType enum in gps.h
private static final int AGPS_TYPE_SUPL = 1;
private static final int AGPS_TYPE_C2K = 2;
@@ -486,12 +490,9 @@
} else if (action.equals(SIM_STATE_CHANGED)) {
TelephonyManager phone = (TelephonyManager)
mContext.getSystemService(Context.TELEPHONY_SERVICE);
- int simState = phone.getSimState();
- Log.d(TAG, "SIM STATE CHANGED to " + simState);
String mccMnc = phone.getSimOperator();
- if (simState == TelephonyManager.SIM_STATE_READY &&
- !TextUtils.isEmpty(mccMnc)) {
- Log.d(TAG, "SIM STATE is ready, SIM MCC/MNC is " + mccMnc);
+ if (!TextUtils.isEmpty(mccMnc)) {
+ Log.d(TAG, "SIM MCC/MNC is available: " + mccMnc);
synchronized (mLock) {
reloadGpsProperties(context, mProperties);
mNIHandler.setSuplEsEnabled(mSuplEsEnabled);
@@ -922,6 +923,39 @@
}
}
+ /**
+ * Checks what SUPL mode to use, according to the AGPS mode as well as the
+ * allowed mode from properties.
+ *
+ * @param properties GPS properties
+ * @param agpsEnabled whether AGPS is enabled by settings value
+ * @param singleShot whether "singleshot" is needed
+ * @return SUPL mode (MSA vs MSB vs STANDALONE)
+ */
+ private int getSuplMode(Properties properties, boolean agpsEnabled, boolean singleShot) {
+ if (agpsEnabled) {
+ String modeString = properties.getProperty("SUPL_MODE");
+ int suplMode = 0;
+ if (!TextUtils.isEmpty(modeString)) {
+ try {
+ suplMode = Integer.parseInt(modeString);
+ } catch (NumberFormatException e) {
+ Log.e(TAG, "unable to parse SUPL_MODE: " + modeString);
+ return GPS_POSITION_MODE_STANDALONE;
+ }
+ }
+ if (singleShot
+ && hasCapability(GPS_CAPABILITY_MSA)
+ && (suplMode & AGPS_SUPL_MODE_MSA) != 0) {
+ return GPS_POSITION_MODE_MS_ASSISTED;
+ } else if (hasCapability(GPS_CAPABILITY_MSB)
+ && (suplMode & AGPS_SUPL_MODE_MSB) != 0) {
+ return GPS_POSITION_MODE_MS_BASED;
+ }
+ }
+ return GPS_POSITION_MODE_STANDALONE;
+ }
+
private void handleEnable() {
if (DEBUG) Log.d(TAG, "handleEnable");
@@ -1199,14 +1233,10 @@
mSingleShot = singleShot;
mPositionMode = GPS_POSITION_MODE_STANDALONE;
- if (Settings.Global.getInt(mContext.getContentResolver(),
- Settings.Global.ASSISTED_GPS_ENABLED, 1) != 0) {
- if (singleShot && hasCapability(GPS_CAPABILITY_MSA)) {
- mPositionMode = GPS_POSITION_MODE_MS_ASSISTED;
- } else if (hasCapability(GPS_CAPABILITY_MSB)) {
- mPositionMode = GPS_POSITION_MODE_MS_BASED;
- }
- }
+ boolean agpsEnabled =
+ (Settings.Global.getInt(mContext.getContentResolver(),
+ Settings.Global.ASSISTED_GPS_ENABLED, 1) != 0);
+ mPositionMode = getSuplMode(mProperties, agpsEnabled, singleShot);
if (DEBUG) {
String mode;
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index d4bcd5c..0c51160 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -328,6 +328,7 @@
for (int tokenNdx = tokens.size() - 1; tokenNdx >= 0; --tokenNdx) {
AppWindowToken wtoken = tokens.get(tokenNdx);
if (wtoken.mDeferRemoval) {
+ stack.mExitingAppTokens.remove(wtoken);
wtoken.mDeferRemoval = false;
mService.removeAppFromTaskLocked(wtoken);
}
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/DatabaseHelper.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/DatabaseHelper.java
index cc0d8df..9c15f2b 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/DatabaseHelper.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/DatabaseHelper.java
@@ -24,10 +24,10 @@
import android.hardware.soundtrigger.SoundTrigger;
import android.hardware.soundtrigger.SoundTrigger.Keyphrase;
import android.hardware.soundtrigger.SoundTrigger.KeyphraseSoundModel;
-import android.os.UserManager;
import android.text.TextUtils;
import android.util.Slog;
+import java.util.Locale;
import java.util.UUID;
/**
@@ -37,8 +37,7 @@
*/
public class DatabaseHelper extends SQLiteOpenHelper {
static final String TAG = "SoundModelDBHelper";
- // TODO: Set to false.
- static final boolean DBG = true;
+ static final boolean DBG = false;
private static final String NAME = "sound_model.db";
private static final int VERSION = 4;
@@ -67,11 +66,8 @@
+ SoundModelContract.KEY_HINT_TEXT + " TEXT,"
+ SoundModelContract.KEY_USERS + " TEXT" + ")";
- private final UserManager mUserManager;
-
public DatabaseHelper(Context context) {
super(context, NAME, null, VERSION);
- mUserManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
}
@Override
@@ -122,17 +118,20 @@
/**
* Deletes the sound model and associated keyphrases.
*/
- public boolean deleteKeyphraseSoundModel(UUID modelUuid) {
- if (modelUuid == null) {
- Slog.w(TAG, "Model UUID must be specified for deletion");
- return false;
- }
-
+ public boolean deleteKeyphraseSoundModel(int keyphraseId, int userHandle, String bcp47Locale) {
+ // Sanitize the locale to guard against SQL injection.
+ bcp47Locale = Locale.forLanguageTag(bcp47Locale).toLanguageTag();
synchronized(this) {
- SQLiteDatabase db = getWritableDatabase();
- String soundModelClause = SoundModelContract.KEY_MODEL_UUID + "='"
- + modelUuid.toString() + "'";
+ KeyphraseSoundModel soundModel = getKeyphraseSoundModel(keyphraseId, userHandle,
+ bcp47Locale);
+ if (soundModel == null) {
+ return false;
+ }
+ // Delete all sound models for the given keyphrase and specified user.
+ SQLiteDatabase db = getWritableDatabase();
+ String soundModelClause = SoundModelContract.KEY_MODEL_UUID
+ + "='" + soundModel.uuid.toString() + "'";
try {
return db.delete(SoundModelContract.TABLE, soundModelClause, null) != 0;
} finally {
@@ -147,11 +146,15 @@
*
* TODO: We only support one keyphrase currently.
*/
- public KeyphraseSoundModel getKeyphraseSoundModel(int keyphraseId) {
+ public KeyphraseSoundModel getKeyphraseSoundModel(int keyphraseId, int userHandle,
+ String bcp47Locale) {
+ // Sanitize the locale to guard against SQL injection.
+ bcp47Locale = Locale.forLanguageTag(bcp47Locale).toLanguageTag();
synchronized(this) {
// Find the corresponding sound model ID for the keyphrase.
String selectQuery = "SELECT * FROM " + SoundModelContract.TABLE
- + " WHERE " + SoundModelContract.KEY_KEYPHRASE_ID + " = '" + keyphraseId + "'";
+ + " WHERE " + SoundModelContract.KEY_KEYPHRASE_ID + "= '" + keyphraseId
+ + "' AND " + SoundModelContract.KEY_LOCALE + "='" + bcp47Locale + "'";
SQLiteDatabase db = getReadableDatabase();
Cursor c = db.rawQuery(selectQuery, null);
@@ -160,14 +163,16 @@
do {
int type = c.getInt(c.getColumnIndex(SoundModelContract.KEY_TYPE));
if (type != SoundTrigger.SoundModel.TYPE_KEYPHRASE) {
- Slog.w(TAG, "Ignoring sound model since it's type is incorrect");
+ if (DBG) {
+ Slog.w(TAG, "Ignoring SoundModel since it's type is incorrect");
+ }
continue;
}
String modelUuid = c.getString(
c.getColumnIndex(SoundModelContract.KEY_MODEL_UUID));
if (modelUuid == null) {
- Slog.w(TAG, "Ignoring sound model since it doesn't specify an ID");
+ Slog.w(TAG, "Ignoring SoundModel since it doesn't specify an ID");
continue;
}
@@ -176,7 +181,7 @@
c.getColumnIndex(SoundModelContract.KEY_RECOGNITION_MODES));
int[] users = getArrayForCommaSeparatedString(
c.getString(c.getColumnIndex(SoundModelContract.KEY_USERS)));
- String locale = c.getString(
+ String modelLocale = c.getString(
c.getColumnIndex(SoundModelContract.KEY_LOCALE));
String text = c.getString(
c.getColumnIndex(SoundModelContract.KEY_HINT_TEXT));
@@ -184,28 +189,37 @@
// Only add keyphrases meant for the current user.
if (users == null) {
// No users present in the keyphrase.
- Slog.w(TAG, "Ignoring keyphrase since it doesn't specify users");
+ Slog.w(TAG, "Ignoring SoundModel since it doesn't specify users");
continue;
}
boolean isAvailableForCurrentUser = false;
- int currentUser = mUserManager.getUserHandle();
for (int user : users) {
- if (currentUser == user) {
+ if (userHandle == user) {
isAvailableForCurrentUser = true;
break;
}
}
if (!isAvailableForCurrentUser) {
- Slog.w(TAG, "Ignoring keyphrase since it's not for the current user");
+ if (DBG) {
+ Slog.w(TAG, "Ignoring SoundModel since user handles don't match");
+ }
continue;
+ } else {
+ if (DBG) Slog.d(TAG, "Found a SoundModel for user: " + userHandle);
}
Keyphrase[] keyphrases = new Keyphrase[1];
keyphrases[0] = new Keyphrase(
- keyphraseId, recognitionModes, locale, text, users);
- return new KeyphraseSoundModel(UUID.fromString(modelUuid),
+ keyphraseId, recognitionModes, modelLocale, text, users);
+ KeyphraseSoundModel model = new KeyphraseSoundModel(
+ UUID.fromString(modelUuid),
null /* FIXME use vendor UUID */, data, keyphrases);
+ if (DBG) {
+ Slog.d(TAG, "Found SoundModel for the given keyphrase/locale/user: "
+ + model);
+ }
+ return model;
} while (c.moveToNext());
}
Slog.w(TAG, "No SoundModel available for the given keyphrase");
@@ -218,15 +232,17 @@
}
private static String getCommaSeparatedString(int[] users) {
- if (users == null || users.length == 0) {
+ if (users == null) {
return "";
}
- String csv = "";
- for (int user : users) {
- csv += String.valueOf(user);
- csv += ",";
+ StringBuilder sb = new StringBuilder();
+ for (int i = 0; i < users.length; i++) {
+ if (i != 0) {
+ sb.append(',');
+ }
+ sb.append(users[i]);
}
- return csv.substring(0, csv.length() - 1);
+ return sb.toString();
}
private static int[] getArrayForCommaSeparatedString(String text) {
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/SoundTriggerHelper.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/SoundTriggerHelper.java
index ad38b22..8ce7f74 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/SoundTriggerHelper.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/SoundTriggerHelper.java
@@ -50,8 +50,7 @@
*/
public class SoundTriggerHelper implements SoundTrigger.StatusListener {
static final String TAG = "SoundTriggerHelper";
- // TODO: Set to false.
- static final boolean DBG = true;
+ static final boolean DBG = false;
/**
* Return codes for {@link #startRecognition(int, KeyphraseSoundModel,
@@ -166,8 +165,14 @@
}
}
+ // Unload the previous model if the current one isn't invalid
+ // and, it's not the same as the new one, or we are already started
+ // if we are already started, we can get multiple calls to start
+ // if the underlying sound model changes, in which case we should unload and reload.
+ // The model reuse helps only in cases when we trigger and stop internally
+ // without a start recognition call.
if (mCurrentSoundModelHandle != INVALID_VALUE
- && !soundModel.uuid.equals(mCurrentSoundModelUuid)) {
+ && (!soundModel.uuid.equals(mCurrentSoundModelUuid) || mStarted)) {
Slog.w(TAG, "Unloading previous sound model");
int status = mModule.unloadSoundModel(mCurrentSoundModelHandle);
if (status != SoundTrigger.STATUS_OK) {
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
index 7c7b732..82b7f8b 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
@@ -446,7 +446,7 @@
//----------------- Model management APIs --------------------------------//
@Override
- public KeyphraseSoundModel getKeyphraseSoundModel(int keyphraseId) {
+ public KeyphraseSoundModel getKeyphraseSoundModel(int keyphraseId, String bcp47Locale) {
synchronized (this) {
if (mContext.checkCallingPermission(Manifest.permission.MANAGE_VOICE_KEYPHRASES)
!= PackageManager.PERMISSION_GRANTED) {
@@ -455,9 +455,14 @@
}
}
+ if (bcp47Locale == null) {
+ throw new IllegalArgumentException("Illegal argument(s) in getKeyphraseSoundModel");
+ }
+
+ final int callingUid = UserHandle.getCallingUserId();
final long caller = Binder.clearCallingIdentity();
try {
- return mDbHelper.getKeyphraseSoundModel(keyphraseId);
+ return mDbHelper.getKeyphraseSoundModel(keyphraseId, callingUid, bcp47Locale);
} finally {
Binder.restoreCallingIdentity(caller);
}
@@ -495,7 +500,7 @@
}
@Override
- public int deleteKeyphraseSoundModel(int keyphraseId) {
+ public int deleteKeyphraseSoundModel(int keyphraseId, String bcp47Locale) {
synchronized (this) {
if (mContext.checkCallingPermission(Manifest.permission.MANAGE_VOICE_KEYPHRASES)
!= PackageManager.PERMISSION_GRANTED) {
@@ -504,13 +509,16 @@
}
}
+ if (bcp47Locale == null) {
+ throw new IllegalArgumentException(
+ "Illegal argument(s) in deleteKeyphraseSoundModel");
+ }
+
+ final int callingUid = UserHandle.getCallingUserId();
final long caller = Binder.clearCallingIdentity();
boolean deleted = false;
try {
- KeyphraseSoundModel soundModel = mDbHelper.getKeyphraseSoundModel(keyphraseId);
- if (soundModel != null) {
- deleted = mDbHelper.deleteKeyphraseSoundModel(soundModel.uuid);
- }
+ deleted = mDbHelper.deleteKeyphraseSoundModel(keyphraseId, callingUid, bcp47Locale);
return deleted ? SoundTriggerHelper.STATUS_OK : SoundTriggerHelper.STATUS_ERROR;
} finally {
if (deleted) {
@@ -527,7 +535,8 @@
//----------------- SoundTrigger APIs --------------------------------//
@Override
- public boolean isEnrolledForKeyphrase(IVoiceInteractionService service, int keyphraseId) {
+ public boolean isEnrolledForKeyphrase(IVoiceInteractionService service, int keyphraseId,
+ String bcp47Locale) {
synchronized (this) {
if (mImpl == null || mImpl.mService == null
|| service.asBinder() != mImpl.mService.asBinder()) {
@@ -536,9 +545,15 @@
}
}
+ if (bcp47Locale == null) {
+ throw new IllegalArgumentException("Illegal argument(s) in isEnrolledForKeyphrase");
+ }
+
+ final int callingUid = UserHandle.getCallingUserId();
final long caller = Binder.clearCallingIdentity();
try {
- KeyphraseSoundModel model = mDbHelper.getKeyphraseSoundModel(keyphraseId);
+ KeyphraseSoundModel model =
+ mDbHelper.getKeyphraseSoundModel(keyphraseId, callingUid, bcp47Locale);
return model != null;
} finally {
Binder.restoreCallingIdentity(caller);
@@ -566,7 +581,8 @@
@Override
public int startRecognition(IVoiceInteractionService service, int keyphraseId,
- IRecognitionStatusCallback callback, RecognitionConfig recognitionConfig) {
+ String bcp47Locale, IRecognitionStatusCallback callback,
+ RecognitionConfig recognitionConfig) {
// Allow the call if this is the current voice interaction service.
synchronized (this) {
if (mImpl == null || mImpl.mService == null
@@ -575,14 +591,16 @@
"Caller is not the current voice interaction service");
}
- if (callback == null || recognitionConfig == null) {
+ if (callback == null || recognitionConfig == null || bcp47Locale == null) {
throw new IllegalArgumentException("Illegal argument(s) in startRecognition");
}
}
+ int callingUid = UserHandle.getCallingUserId();
final long caller = Binder.clearCallingIdentity();
try {
- KeyphraseSoundModel soundModel = mDbHelper.getKeyphraseSoundModel(keyphraseId);
+ KeyphraseSoundModel soundModel =
+ mDbHelper.getKeyphraseSoundModel(keyphraseId, callingUid, bcp47Locale);
if (soundModel == null
|| soundModel.uuid == null
|| soundModel.keyphrases == null) {
diff --git a/telephony/java/android/telephony/DisconnectCause.java b/telephony/java/android/telephony/DisconnectCause.java
index 3fb6be7..6fe10dc 100644
--- a/telephony/java/android/telephony/DisconnectCause.java
+++ b/telephony/java/android/telephony/DisconnectCause.java
@@ -263,7 +263,7 @@
case OUTGOING_CANCELED:
return "OUTGOING_CANCELED";
default:
- return "INVALID";
+ return "INVALID: " + cause;
}
}
}