diff options
54 files changed, 794 insertions, 509 deletions
diff --git a/api/current.txt b/api/current.txt index f9913fa32079..cbf4d67319b0 100644 --- a/api/current.txt +++ b/api/current.txt @@ -28073,14 +28073,18 @@ package android.telecom { ctor public Conference(android.telecom.PhoneAccountHandle); method public final boolean addConnection(android.telecom.Connection); method public final void destroy(); + method public final android.telecom.AudioState getAudioState(); method public final int getCapabilities(); method public final java.util.List<android.telecom.Connection> getConnections(); method public final android.telecom.PhoneAccountHandle getPhoneAccountHandle(); method public final int getState(); + method public void onAudioStateChanged(android.telecom.AudioState); method public void onDisconnect(); method public void onHold(); method public void onMerge(); + method public void onPlayDtmfTone(char); method public void onSeparate(android.telecom.Connection); + method public void onStopDtmfTone(); method public void onSwap(); method public void onUnhold(); method public final void removeConnection(android.telecom.Connection); @@ -28219,7 +28223,6 @@ package android.telecom { method public android.net.Uri getSubscriptionAddress(); method public java.util.List<java.lang.String> getSupportedUriSchemes(); method public boolean hasCapabilities(int); - method public boolean isEnabled(); method public boolean supportsUriScheme(java.lang.String); method public void writeToParcel(android.os.Parcel, int); field public static final int CAPABILITY_CONNECTION_MANAGER = 1; // 0x1 @@ -28274,8 +28277,11 @@ package android.telecom { method public android.telecom.DisconnectCause getDisconnectCause(); method public final int getState(); method public void hold(); + method public void playDtmfTone(char); method public final void registerCallback(android.telecom.RemoteConference.Callback); method public void separate(android.telecom.RemoteConnection); + method public void setAudioState(android.telecom.AudioState); + method public void stopDtmfTone(); method public void unhold(); method public final void unregisterCallback(android.telecom.RemoteConference.Callback); } @@ -28349,21 +28355,19 @@ package android.telecom { method public void addNewIncomingCall(android.telecom.PhoneAccountHandle, android.os.Bundle); method public void cancelMissedCallsNotification(); method public void clearAccounts(); + method public java.util.List<android.telecom.PhoneAccountHandle> getCallCapablePhoneAccounts(); method public android.telecom.PhoneAccountHandle getConnectionManager(); method public android.telecom.PhoneAccountHandle getDefaultOutgoingPhoneAccount(java.lang.String); - method public java.util.List<android.telecom.PhoneAccountHandle> getEnabledPhoneAccounts(); method public android.telecom.PhoneAccount getPhoneAccount(android.telecom.PhoneAccountHandle); method public java.util.List<android.telecom.PhoneAccountHandle> getPhoneAccountsSupportingScheme(java.lang.String); method public boolean handleMmi(java.lang.String); - method public boolean hasMultipleEnabledAccounts(); + method public boolean hasMultipleCallCapableAccounts(); method public boolean isInCall(); method public void registerPhoneAccount(android.telecom.PhoneAccount); method public void showInCallScreen(boolean); method public void unregisterPhoneAccount(android.telecom.PhoneAccountHandle); field public static final java.lang.String ACTION_CHANGE_PHONE_ACCOUNTS = "android.telecom.action.CHANGE_PHONE_ACCOUNTS"; field public static final java.lang.String ACTION_CONNECTION_SERVICE_CONFIGURE = "android.telecom.action.CONNECTION_SERVICE_CONFIGURE"; - field public static final java.lang.String ACTION_PHONE_ACCOUNT_DISABLED = "android.telecom.action.PHONE_ACCOUNT_DISABLED"; - field public static final java.lang.String ACTION_PHONE_ACCOUNT_ENABLED = "android.telecom.action.PHONE_ACCOUNT_ENABLED"; field public static final java.lang.String ACTION_SHOW_CALL_SETTINGS = "android.telecom.action.SHOW_CALL_SETTINGS"; field public static final char DTMF_CHARACTER_PAUSE = 44; // 0x002c ',' field public static final char DTMF_CHARACTER_WAIT = 59; // 0x003b ';' diff --git a/cmds/am/src/com/android/commands/am/Am.java b/cmds/am/src/com/android/commands/am/Am.java index 57c1505d2ac3..5ba7d507f8f1 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.IBinder; 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 @@ public class Am extends BaseCommand { 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 @@ public class Am extends BaseCommand { 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 @@ public class Am extends BaseCommand { 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 6a5ecee1713b..b8b20873066d 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 @@ public final class Dpm extends BaseCommand { } 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 @@ public final class Dpm extends BaseCommand { 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 @@ public final class Dpm extends BaseCommand { 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 @@ public final class Dpm extends BaseCommand { 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 6d1213336eb7..5e9d8f74f342 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 @@ public final class Pm { 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 7c69a7da145f..e3cbef5a1182 100644 --- a/core/java/android/net/ConnectivityManager.java +++ b/core/java/android/net/ConnectivityManager.java @@ -1402,6 +1402,20 @@ public class ConnectivityManager { 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 54d43d380a26..ea5dfd184f29 100644 --- a/core/java/android/net/NetworkStats.java +++ b/core/java/android/net/NetworkStats.java @@ -24,6 +24,8 @@ import android.util.SparseBooleanArray; 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 @@ public class NetworkStats implements Parcelable { } 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 c25278f19d9b..82016c305f90 100644 --- a/core/java/android/os/UserManager.java +++ b/core/java/android/os/UserManager.java @@ -19,6 +19,7 @@ import android.annotation.SystemApi; 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 @@ public class UserManager { * @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 8aa26895fe1d..ac7d539bb628 100644 --- a/core/java/android/service/voice/AlwaysOnHotwordDetector.java +++ b/core/java/android/service/voice/AlwaysOnHotwordDetector.java @@ -170,8 +170,7 @@ public class AlwaysOnHotwordDetector { = 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 @@ public class AlwaysOnHotwordDetector { 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 @@ public class AlwaysOnHotwordDetector { 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 @@ public class AlwaysOnHotwordDetector { /** * @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 22ec4bead91e..5a1052411ddb 100644 --- a/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl +++ b/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl @@ -33,32 +33,44 @@ interface IVoiceInteractionManagerService { 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/WindowDecorActionBar.java b/core/java/com/android/internal/app/WindowDecorActionBar.java index a8f7bb391412..2377c22b488d 100644 --- a/core/java/com/android/internal/app/WindowDecorActionBar.java +++ b/core/java/com/android/internal/app/WindowDecorActionBar.java @@ -866,14 +866,7 @@ public class WindowDecorActionBar extends ActionBar implements 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 d24f32fafe59..847a47d72040 100644 --- a/core/java/com/android/internal/widget/ActionBarContainer.java +++ b/core/java/com/android/internal/widget/ActionBarContainer.java @@ -260,6 +260,11 @@ public class ActionBarContainer extends FrameLayout { 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 @@ public class ActionBarContainer extends FrameLayout { 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 @@ public class ActionBarContainer extends FrameLayout { 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 63a4843fb0a4..478c8f27fecd 100644 --- a/core/java/com/android/internal/widget/ToolbarWidgetWrapper.java +++ b/core/java/com/android/internal/widget/ToolbarWidgetWrapper.java @@ -578,7 +578,7 @@ public class ToolbarWidgetWrapper implements DecorToolbar { @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 @@ public class ToolbarWidgetWrapper implements DecorToolbar { } }); } 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 0b05bf5577a1..0e1c63dc5293 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 @@ Copyright (C) 2014 The Android Open Source Project 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/colors.xml b/core/res/res/values/colors.xml index 5a7e168c536e..cbaf54fd3efa 100644 --- a/core/res/res/values/colors.xml +++ b/core/res/res/values/colors.xml @@ -148,10 +148,10 @@ <color name="battery_saver_mode_color">#fff4511e</color><!-- deep orange 600 --> <!-- Default user icon colors --> - <color name="user_icon_1">#ffe91e63</color><!-- pink 500 --> + <color name="user_icon_1">#ff00bcd4</color><!-- teal 500 --> <color name="user_icon_2">#ff3f51b5</color><!-- indigo 500 --> <color name="user_icon_3">#ff4285f4</color><!-- blue 500 --> - <color name="user_icon_4">#ff00bcd4</color><!-- teal 500 --> + <color name="user_icon_4">#ffe91e63</color><!-- pink 500 --> <color name="user_icon_5">#ff0f9d58</color><!-- green 500 --> <color name="user_icon_6">#ff8bc34a</color><!-- light green 500 --> <color name="user_icon_7">#ffff9800</color><!-- orange 500 --> diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index e95d7357a759..ef3f47e97e17 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 2b49ba25208c..ac5890acaff2 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 a984cfa41957..ebff9655b1c9 100644 --- a/core/res/res/values/themes_material.xml +++ b/core/res/res/values/themes_material.xml @@ -316,6 +316,8 @@ please see themes_device_defaults.xml. <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 633196433d49..9ee4e2073dc7 100644 --- a/core/tests/coretests/src/android/net/NetworkStatsTest.java +++ b/core/tests/coretests/src/android/net/NetworkStatsTest.java @@ -310,6 +310,16 @@ public class NetworkStatsTest extends TestCase { 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 2f07328e3d2d..000000000000 --- 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 067a15fb0f49..3c3a175fb520 100644 --- a/docs/html/distribute/googleplay/edu/start.jd +++ b/docs/html/distribute/googleplay/edu/start.jd @@ -9,27 +9,23 @@ page.metaDescription=Join Google Play for Education in just a few simple steps. <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 @@ Chrome Apps in Google Play for Education at <a href= </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 @@ Chrome Apps in Google Play for Education at <a href= 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 @@ Chrome Apps in Google Play for Education at <a href= "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 @@ Chrome Apps in Google Play for Education at <a href= <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 @@ Chrome Apps in Google Play for Education at <a href= </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 @@ Chrome Apps in Google Play for Education at <a href= </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 @@ Chrome Apps in Google Play for Education at <a href= 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 @@ Chrome Apps in Google Play for Education at <a href= 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 @@ Chrome Apps in Google Play for Education at <a href= </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> - - <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> +<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>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 2b78ab36c162..b9b77df493dd 100644 --- a/docs/html/google/play/billing/billing_subscriptions.jd +++ b/docs/html/google/play/billing/billing_subscriptions.jd @@ -11,25 +11,32 @@ meta.tags="monetization, inappbilling, subscriptions" <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 @@ and business models.</p> <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 @@ This ensures a consistent and familiar purchase flow for your users.</p> <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 @@ href="http://support.google.com/googleplay/android-developer/bin/answer.py?hl=en 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 @@ product:</p> <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 @@ product:</p> 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 @@ original price, but new users will be charged at the new price.</p> <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 @@ provides a purchase token back to the purchasing app through the In-app Billing 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 @@ app to notify your backend servers of subscription purchases, tokens, and any 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 @@ purchases will use the updated trial period. You can create one free trial 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 @@ query and directly cancel the user’s subscription from your servers. <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 @@ to cancel the associated subscriptions at any time in the <strong>My Apps</stron 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 @@ subscription and denotes 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 @@ content.</p> <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 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>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>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 875271fe8fde..e1326d75e41a 100644 --- a/docs/html/google/play/billing/index.jd +++ b/docs/html/google/play/billing/index.jd @@ -14,14 +14,27 @@ and features, and more. You can use In-app Billing to sell products as</p> <div class="sidebox"> <h2><strong>New in In-App Billing</strong></h2> <ul> + <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> diff --git a/libs/hwui/ProgramCache.cpp b/libs/hwui/ProgramCache.cpp index a8b8b16826a3..06353c096adc 100644 --- a/libs/hwui/ProgramCache.cpp +++ b/libs/hwui/ProgramCache.cpp @@ -327,8 +327,10 @@ const char* gFS_Main_ApplyColorOp[3] = { // 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 feaee8ed5009..7eb9a32843e1 100644 --- a/libs/hwui/Properties.h +++ b/libs/hwui/Properties.h @@ -227,7 +227,7 @@ enum DebugLevel { #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 6d4bb4ab50f2..3681637a962e 100644 --- a/libs/hwui/Renderer.h +++ b/libs/hwui/Renderer.h @@ -70,6 +70,7 @@ public: // 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 @@ public: 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 5e6796c2b406..832d17013b02 100644 --- a/libs/hwui/renderthread/CanvasContext.cpp +++ b/libs/hwui/renderthread/CanvasContext.cpp @@ -91,7 +91,9 @@ void CanvasContext::setSurface(ANativeWindow* window) { } void CanvasContext::swapBuffers() { - mEglManager.swapBuffers(mEglSurface); + if (CC_UNLIKELY(!mEglManager.swapBuffers(mEglSurface))) { + setSurface(NULL); + } mHaveNewSurface = false; } @@ -102,8 +104,8 @@ void CanvasContext::requireSurface() { } 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 e030cdb29ff6..a87834ee0c63 100644 --- a/libs/hwui/renderthread/EglManager.cpp +++ b/libs/hwui/renderthread/EglManager.cpp @@ -254,11 +254,23 @@ void EglManager::beginFrame(EGLSurface surface, EGLint* width, EGLint* height) { 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 a844cfcbe61c..71213fbf9343 100644 --- a/libs/hwui/renderthread/EglManager.h +++ b/libs/hwui/renderthread/EglManager.h @@ -47,7 +47,7 @@ public: // 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 2c805bbe7835..04458697afdd 100644 --- a/location/java/android/location/LocationManager.java +++ b/location/java/android/location/LocationManager.java @@ -18,6 +18,7 @@ package android.location; 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 @@ public class LocationManager { * * @hide */ + @SystemApi public void requestLocationUpdates(LocationRequest request, LocationListener listener, Looper looper) { checkListener(listener); @@ -835,6 +837,7 @@ public class LocationManager { * * @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 14c41dadb0ba..a4555f18fb59 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 @@ public final class PageContentRepository { 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 @@ public final class PageContentRepository { return mRenderer.getPageCount(); } - public PageContentProvider peekPageContentProvider(int pageIndex) { - return mPageContentProviders.get(pageIndex); - } - public PageContentProvider acquirePageContentProvider(int pageIndex, View owner) { throwIfDestroyed(); @@ -152,15 +145,7 @@ public final class PageContentRepository { 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 @@ public final class PageContentRepository { 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 @@ public final class PageContentRepository { callback.run(); } } - }.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR, (Void[]) null); + }.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR); } public void close(final Runnable callback) { @@ -552,7 +533,7 @@ public final class PageContentRepository { callback.run(); } } - }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void[]) null); + }.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR); } public void destroy() { @@ -571,7 +552,7 @@ public final class PageContentRepository { 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 @@ public final class PageContentRepository { // 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 369c453937d0..da8160ac8141 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 @@ public final class PageAdapter extends Adapter implements @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 @@ public final class PageAdapter extends Adapter implements + ", 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 @@ public final class PageAdapter extends Adapter implements 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 389988a760bc..d169319e8092 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 @@ public class PrintActivity extends Activity implements RemotePrintDocument.Updat // 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 b999866dba69..0d453523542a 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 @@ class PrintPreviewController implements MutexFileProvider.OnReleaseRequestCallba 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 @@ class PrintPreviewController implements MutexFileProvider.OnReleaseRequestCallba 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 @@ class PrintPreviewController implements MutexFileProvider.OnReleaseRequestCallba 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 100011738c47..b79278950eb4 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 @@ public class PageContentView extends View @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 @@ public class PageContentView extends View 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 b94a258c8fb9..3c2a776cd537 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> diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml index a8c95c12b42b..9654da99ed03 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/styles.xml b/packages/SystemUI/res/values/styles.xml index 0456c823e70c..46e7587cb14e 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/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java index dfec307ab075..967681b20d8a 100644 --- a/services/core/java/com/android/server/ConnectivityService.java +++ b/services/core/java/com/android/server/ConnectivityService.java @@ -1357,13 +1357,6 @@ public class ConnectivityService extends IConnectivityManager.Stub { "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 @@ public class ConnectivityService extends IConnectivityManager.Stub { // javadoc from interface public int tether(String iface) { - enforceTetherChangePermission(); - + ConnectivityManager.enforceTetherChangePermission(mContext); if (isTetheringSupported()) { return mTethering.tether(iface); } else { @@ -2400,7 +2392,7 @@ public class ConnectivityService extends IConnectivityManager.Stub { // javadoc from interface public int untether(String iface) { - enforceTetherChangePermission(); + ConnectivityManager.enforceTetherChangePermission(mContext); if (isTetheringSupported()) { return mTethering.untether(iface); @@ -2449,7 +2441,7 @@ public class ConnectivityService extends IConnectivityManager.Stub { } 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 fdcb3b9029e5..cf2a49f922e2 100644 --- a/services/core/java/com/android/server/SystemConfig.java +++ b/services/core/java/com/android/server/SystemConfig.java @@ -50,19 +50,16 @@ public class SystemConfig { // 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 @@ public class SystemConfig { // 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 @@ public class SystemConfig { return mAllowInPowerSave; } + public ArraySet<String> getFixedImeApps() { + return mFixedImeApps; + } + SystemConfig() { // Read configuration from system readPermissions(Environment.buildPath( @@ -298,6 +301,17 @@ public class SystemConfig { 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 e043f03fc51d..adea27108f67 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 @@ final class ActivityRecord { 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 49404937344c..ebd0d4e9c931 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 @@ public final class ActivityStackSupervisor implements DisplayListener { } 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 @@ public final class ActivityStackSupervisor implements DisplayListener { } 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 b21af48c4d3b..df1772a56550 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 @@ public class TaskPersister { * 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 @@ public class TaskPersister { 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 @@ public class TaskPersister { 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 @@ public class TaskPersister { // 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 @@ public class TaskPersister { 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 @@ public class TaskPersister { mNextWriteTime + " Callers=" + Debug.getCallers(4)); notifyAll(); } + + yieldIfQueueTooDeep(); } Bitmap getThumbnail(String filename) { @@ -425,7 +467,7 @@ public class TaskPersister { // 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 @@ public class TaskPersister { 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 @@ public class TaskPersister { 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/location/GpsLocationProvider.java b/services/core/java/com/android/server/location/GpsLocationProvider.java index df846a848d91..f1c5a6c934e4 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 @@ public class GpsLocationProvider implements LocationProviderInterface { 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 @@ public class GpsLocationProvider implements LocationProviderInterface { } 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 @@ public class GpsLocationProvider implements LocationProviderInterface { } } + /** + * 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 @@ public class GpsLocationProvider implements LocationProviderInterface { 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 d4bcd5c48083..0c51160098ff 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 @@ class DisplayContent { 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 cc0d8df2adda..9c15f2b3b92f 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.database.sqlite.SQLiteOpenHelper; 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 @@ import java.util.UUID; */ 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 @@ public class DatabaseHelper extends SQLiteOpenHelper { + 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 @@ public class DatabaseHelper extends SQLiteOpenHelper { /** * 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 @@ public class DatabaseHelper extends SQLiteOpenHelper { * * 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 @@ public class DatabaseHelper extends SQLiteOpenHelper { 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 @@ public class DatabaseHelper extends SQLiteOpenHelper { 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 @@ public class DatabaseHelper extends SQLiteOpenHelper { // 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 @@ public class DatabaseHelper extends SQLiteOpenHelper { } 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 ad38b22e7a97..8ce7f74cef57 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 @@ import java.util.UUID; */ 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 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener { } } + // 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 7c7b73236369..82b7f8b6eb52 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 @@ public class VoiceInteractionManagerService extends SystemService { //----------------- 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 @@ public class VoiceInteractionManagerService extends SystemService { } } + 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 @@ public class VoiceInteractionManagerService extends SystemService { } @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 @@ public class VoiceInteractionManagerService extends SystemService { } } + 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 @@ public class VoiceInteractionManagerService extends SystemService { //----------------- 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 @@ public class VoiceInteractionManagerService extends SystemService { } } + 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 @@ public class VoiceInteractionManagerService extends SystemService { @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 @@ public class VoiceInteractionManagerService extends SystemService { "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/telecomm/java/android/telecom/Conference.java b/telecomm/java/android/telecom/Conference.java index d1e150f628ad..9b350c1978a2 100644 --- a/telecomm/java/android/telecom/Conference.java +++ b/telecomm/java/android/telecom/Conference.java @@ -43,6 +43,7 @@ public abstract class Conference { Collections.unmodifiableList(mChildConnections); private PhoneAccountHandle mPhoneAccount; + private AudioState mAudioState; private int mState = Connection.STATE_NEW; private DisconnectCause mDisconnectCause; private int mCapabilities; @@ -94,6 +95,15 @@ public abstract class Conference { } /** + * @return The audio state of the conference, describing how its audio is currently + * being routed by the system. This is {@code null} if this Conference + * does not directly know about its audio state. + */ + public final AudioState getAudioState() { + return mAudioState; + } + + /** * Invoked when the Conference and all it's {@link Connection}s should be disconnected. */ public void onDisconnect() {} @@ -128,6 +138,25 @@ public abstract class Conference { public void onSwap() {} /** + * Notifies this conference of a request to play a DTMF tone. + * + * @param c A DTMF character. + */ + public void onPlayDtmfTone(char c) {} + + /** + * Notifies this conference of a request to stop any currently playing DTMF tones. + */ + public void onStopDtmfTone() {} + + /** + * Notifies this conference that the {@link #getAudioState()} property has a new value. + * + * @param state The new call audio state. + */ + public void onAudioStateChanged(AudioState state) {} + + /** * Sets state to be on hold. */ public final void setOnHold() { @@ -251,6 +280,18 @@ public abstract class Conference { return this; } + /** + * Inform this Conference that the state of its audio output has been changed externally. + * + * @param state The new audio state. + * @hide + */ + final void setAudioState(AudioState state) { + Log.d(this, "setAudioState %s", state); + mAudioState = state; + onAudioStateChanged(state); + } + private void setState(int newState) { if (newState != Connection.STATE_ACTIVE && newState != Connection.STATE_HOLDING && diff --git a/telecomm/java/android/telecom/ConnectionService.java b/telecomm/java/android/telecom/ConnectionService.java index 0da0adb3963c..3e18bac43260 100644 --- a/telecomm/java/android/telecom/ConnectionService.java +++ b/telecomm/java/android/telecom/ConnectionService.java @@ -400,7 +400,7 @@ public abstract class ConnectionService extends Service { @Override public void onDisconnected(Connection c, DisconnectCause disconnectCause) { String id = mIdByConnection.get(c); - Log.d(this, "Adapter set disconnected %d %s", disconnectCause); + Log.d(this, "Adapter set disconnected %s", disconnectCause); mAdapter.setDisconnected(id, disconnectCause); } @@ -607,17 +607,29 @@ public abstract class ConnectionService extends Service { private void onAudioStateChanged(String callId, AudioState audioState) { Log.d(this, "onAudioStateChanged %s %s", callId, audioState); - findConnectionForAction(callId, "onAudioStateChanged").setAudioState(audioState); + if (mConnectionById.containsKey(callId)) { + findConnectionForAction(callId, "onAudioStateChanged").setAudioState(audioState); + } else { + findConferenceForAction(callId, "onAudioStateChanged").setAudioState(audioState); + } } private void playDtmfTone(String callId, char digit) { Log.d(this, "playDtmfTone %s %c", callId, digit); - findConnectionForAction(callId, "playDtmfTone").onPlayDtmfTone(digit); + if (mConnectionById.containsKey(callId)) { + findConnectionForAction(callId, "playDtmfTone").onPlayDtmfTone(digit); + } else { + findConferenceForAction(callId, "playDtmfTone").onPlayDtmfTone(digit); + } } private void stopDtmfTone(String callId) { Log.d(this, "stopDtmfTone %s", callId); - findConnectionForAction(callId, "stopDtmfTone").onStopDtmfTone(); + if (mConnectionById.containsKey(callId)) { + findConnectionForAction(callId, "stopDtmfTone").onStopDtmfTone(); + } else { + findConferenceForAction(callId, "stopDtmfTone").onStopDtmfTone(); + } } private void conference(String callId1, String callId2) { diff --git a/telecomm/java/android/telecom/PhoneAccount.java b/telecomm/java/android/telecom/PhoneAccount.java index 0c233ebb1ebd..4b059b240906 100644 --- a/telecomm/java/android/telecom/PhoneAccount.java +++ b/telecomm/java/android/telecom/PhoneAccount.java @@ -91,17 +91,6 @@ public class PhoneAccount implements Parcelable { public static final int CAPABILITY_PLACE_EMERGENCY_CALLS = 0x10; /** - * Flag indicating that this {@code PhoneAccount} is always enabled and cannot be disabled by - * the user. - * This capability is reserved for important {@code PhoneAccount}s such as the emergency calling - * only {@code PhoneAccount}. - * <p> - * See {@link #getCapabilities} - * @hide - */ - public static final int CAPABILITY_ALWAYS_ENABLED = 0x20; - - /** * URI scheme for telephone number URIs. */ public static final String SCHEME_TEL = "tel"; @@ -124,7 +113,6 @@ public class PhoneAccount implements Parcelable { private final CharSequence mLabel; private final CharSequence mShortDescription; private final List<String> mSupportedUriSchemes; - private final boolean mIsEnabled; public static class Builder { private PhoneAccountHandle mAccountHandle; @@ -135,7 +123,6 @@ public class PhoneAccount implements Parcelable { private CharSequence mLabel; private CharSequence mShortDescription; private List<String> mSupportedUriSchemes = new ArrayList<String>(); - private boolean mIsEnabled = false; public Builder(PhoneAccountHandle accountHandle, CharSequence label) { this.mAccountHandle = accountHandle; @@ -157,7 +144,6 @@ public class PhoneAccount implements Parcelable { mLabel = phoneAccount.getLabel(); mShortDescription = phoneAccount.getShortDescription(); mSupportedUriSchemes.addAll(phoneAccount.getSupportedUriSchemes()); - mIsEnabled = phoneAccount.isEnabled(); } public Builder setAddress(Uri value) { @@ -217,19 +203,6 @@ public class PhoneAccount implements Parcelable { } /** - * Specifies whether the {@link PhoneAccount} is enabled or not. {@link PhoneAccount}s are - * by default not enabled. - * - * @param value {@code True} if the {@link PhoneAccount} is enabled. - * @return The Builder. - * @hide - */ - public Builder setEnabled(boolean value) { - this.mIsEnabled = value; - return this; - } - - /** * Creates an instance of a {@link PhoneAccount} based on the current builder settings. * * @return The {@link PhoneAccount}. @@ -248,8 +221,7 @@ public class PhoneAccount implements Parcelable { mIconResId, mLabel, mShortDescription, - mSupportedUriSchemes, - mIsEnabled); + mSupportedUriSchemes); } } @@ -261,8 +233,7 @@ public class PhoneAccount implements Parcelable { int iconResId, CharSequence label, CharSequence shortDescription, - List<String> supportedUriSchemes, - boolean enabled) { + List<String> supportedUriSchemes) { mAccountHandle = account; mAddress = address; mSubscriptionAddress = subscriptionAddress; @@ -271,7 +242,6 @@ public class PhoneAccount implements Parcelable { mLabel = label; mShortDescription = shortDescription; mSupportedUriSchemes = Collections.unmodifiableList(supportedUriSchemes); - mIsEnabled = enabled; } public static Builder builder( @@ -392,15 +362,6 @@ public class PhoneAccount implements Parcelable { } /** - * Determines whether this {@code PhoneAccount} is enabled. - * - * @return {@code True} if this {@code PhoneAccount} is enabled.. - */ - public boolean isEnabled() { - return mIsEnabled; - } - - /** * The icon resource ID for the icon of this {@code PhoneAccount}. * * @return A resource ID. @@ -455,7 +416,6 @@ public class PhoneAccount implements Parcelable { out.writeCharSequence(mLabel); out.writeCharSequence(mShortDescription); out.writeList(mSupportedUriSchemes); - out.writeInt(mIsEnabled ? 1 : 0); } public static final Creator<PhoneAccount> CREATOR @@ -485,6 +445,5 @@ public class PhoneAccount implements Parcelable { List<String> supportedUriSchemes = new ArrayList<>(); in.readList(supportedUriSchemes, classLoader); mSupportedUriSchemes = Collections.unmodifiableList(supportedUriSchemes); - mIsEnabled = in.readInt() == 1; } } diff --git a/telecomm/java/android/telecom/RemoteConference.java b/telecomm/java/android/telecom/RemoteConference.java index 0cf84d0b7a64..f931bc555330 100644 --- a/telecomm/java/android/telecom/RemoteConference.java +++ b/telecomm/java/android/telecom/RemoteConference.java @@ -181,6 +181,27 @@ public final class RemoteConference { return mDisconnectCause; } + public void playDtmfTone(char digit) { + try { + mConnectionService.playDtmfTone(mId, digit); + } catch (RemoteException e) { + } + } + + public void stopDtmfTone() { + try { + mConnectionService.stopDtmfTone(mId); + } catch (RemoteException e) { + } + } + + public void setAudioState(AudioState state) { + try { + mConnectionService.onAudioStateChanged(mId, state); + } catch (RemoteException e) { + } + } + public final void registerCallback(Callback callback) { mCallbacks.add(callback); } diff --git a/telecomm/java/android/telecom/TelecomManager.java b/telecomm/java/android/telecom/TelecomManager.java index 4d438ed7fb51..a91d92f01253 100644 --- a/telecomm/java/android/telecom/TelecomManager.java +++ b/telecomm/java/android/telecom/TelecomManager.java @@ -71,24 +71,6 @@ public class TelecomManager { "android.telecom.action.CHANGE_PHONE_ACCOUNTS"; /** - * The {@link android.content.Intent} action used to inform a - * {@link android.telecom.ConnectionService} that one of its {@link PhoneAccount}s has been - * enabled. The {@link TelecomManager#EXTRA_PHONE_ACCOUNT_HANDLE} extra is used to indicate - * which {@link PhoneAccount} has been enabled. - */ - public static final String ACTION_PHONE_ACCOUNT_ENABLED = - "android.telecom.action.PHONE_ACCOUNT_ENABLED"; - - /** - * The {@link android.content.Intent} action used to inform a - * {@link android.telecom.ConnectionService} that one of its {@link PhoneAccount}s has been - * disabled. The {@link TelecomManager#EXTRA_PHONE_ACCOUNT_HANDLE} extra is used to indicate - * which {@link PhoneAccount} has been disabled. - */ - public static final String ACTION_PHONE_ACCOUNT_DISABLED = - "android.telecom.action.PHONE_ACCOUNT_DISABLED"; - - /** * Optional extra for {@link android.content.Intent#ACTION_CALL} containing a boolean that * determines whether the speakerphone should be automatically turned on for an outgoing call. */ @@ -325,14 +307,14 @@ public class TelecomManager { /** * Return the {@link PhoneAccount} which is the user-chosen default for making outgoing phone * calls with a specified URI scheme. This {@code PhoneAccount} will always be a member of the - * list which is returned from calling {@link #getEnabledPhoneAccounts()}. + * list which is returned from calling {@link #getCallCapablePhoneAccounts()}. * <p> * Apps must be prepared for this method to return {@code null}, indicating that there currently * exists no user-chosen default {@code PhoneAccount}. In this case, apps wishing to initiate a * phone call must either create their {@link android.content.Intent#ACTION_CALL} or * {@link android.content.Intent#ACTION_DIAL} {@code Intent} with no * {@link TelecomManager#EXTRA_PHONE_ACCOUNT_HANDLE}, or present the user with an affordance to - * select one of the elements of {@link #getEnabledPhoneAccounts()}. + * select one of the elements of {@link #getCallCapablePhoneAccounts()}. * <p> * An {@link android.content.Intent#ACTION_CALL} or {@link android.content.Intent#ACTION_DIAL} * {@code Intent} with no {@link TelecomManager#EXTRA_PHONE_ACCOUNT_HANDLE} is valid, and @@ -355,7 +337,7 @@ public class TelecomManager { /** * Return the {@link PhoneAccount} which is the user-chosen default for making outgoing phone * calls. This {@code PhoneAccount} will always be a member of the list which is returned from - * calling {@link #getEnabledPhoneAccounts()} + * calling {@link #getCallCapablePhoneAccounts()} * * Apps must be prepared for this method to return {@code null}, indicating that there currently * exists no user-chosen default {@code PhoneAccount}. @@ -389,19 +371,19 @@ public class TelecomManager { } /** - * Return a list of enabled {@link PhoneAccountHandle}s which can be used to make and receive - * phone calls. + * Return a list of {@link PhoneAccountHandle}s which can be used to make and receive phone + * calls. * * @see #EXTRA_PHONE_ACCOUNT_HANDLE * @return A list of {@code PhoneAccountHandle} objects. */ - public List<PhoneAccountHandle> getEnabledPhoneAccounts() { + public List<PhoneAccountHandle> getCallCapablePhoneAccounts() { try { if (isServiceConnected()) { - return getTelecomService().getEnabledPhoneAccounts(); + return getTelecomService().getCallCapablePhoneAccounts(); } } catch (RemoteException e) { - Log.e(TAG, "Error calling ITelecomService#getEnabledPhoneAccounts", e); + Log.e(TAG, "Error calling ITelecomService#getCallCapablePhoneAccounts", e); } return new ArrayList<>(); } @@ -467,8 +449,8 @@ public class TelecomManager { } /** - * Returns a list of the enabled {@link PhoneAccountHandle}s which can be used to make and - * receive phone calls which support the specified URI scheme. + * Returns a list of the {@link PhoneAccountHandle}s which can be used to make and receive phone + * calls which support the specified URI scheme. * <P> * For example, invoking with {@code "tel"} will find all {@link PhoneAccountHandle}s which * support telephone calls (e.g. URIs such as {@code tel:555-555-1212}). Invoking with @@ -490,13 +472,14 @@ public class TelecomManager { } /** - * Determine whether the device has more than one account registered and enabled. + * Determine whether the device has more than one account registered that can make and receive + * phone calls. * - * @return {@code true} if the device has more than one account registered and enabled and - * {@code false} otherwise. + * @return {@code true} if the device has more than one account registered and {@code false} + * otherwise. */ - public boolean hasMultipleEnabledAccounts() { - return getEnabledPhoneAccounts().size() > 1; + public boolean hasMultipleCallCapableAccounts() { + return getCallCapablePhoneAccounts().size() > 1; } /** @@ -518,9 +501,9 @@ public class TelecomManager { } /** - * Returns a count of enabled and disabled {@link PhoneAccount}s. + * Returns a count of all {@link PhoneAccount}s. * - * @return The count of enabled and disabled {@link PhoneAccount}s. + * @return The count of {@link PhoneAccount}s. * @hide */ @SystemApi @@ -572,24 +555,6 @@ public class TelecomManager { } /** - * Enables or disables a {@link PhoneAccount}. - * - * @param account The {@link PhoneAccountHandle} to enable or disable. - * @param isEnabled {@code True} if the phone account should be enabled. - * @hide - */ - @SystemApi - public void setPhoneAccountEnabled(PhoneAccountHandle account, boolean isEnabled) { - try { - if (isServiceConnected()) { - getTelecomService().setPhoneAccountEnabled(account, isEnabled); - } - } catch (RemoteException e) { - Log.e(TAG, "Error calling ITelecomService#setPhoneAccountEnabled", e); - } - } - - /** * Register a {@link PhoneAccount} for use by the system. * * @param account The complete {@link PhoneAccount}. @@ -797,9 +762,8 @@ public class TelecomManager { /** * Registers a new incoming call. A {@link ConnectionService} should invoke this method when it * has an incoming call. The specified {@link PhoneAccountHandle} must have been registered - * with {@link #registerPhoneAccount} and subsequently enabled by the user within the phone's - * settings. Once invoked, this method will cause the system to bind to the - * {@link ConnectionService} associated with the {@link PhoneAccountHandle} and request + * with {@link #registerPhoneAccount}. Once invoked, this method will cause the system to bind + * to the {@link ConnectionService} associated with the {@link PhoneAccountHandle} and request * additional information about the call (See * {@link ConnectionService#onCreateIncomingConnection}) before starting the incoming call UI. * diff --git a/telecomm/java/com/android/internal/telecom/ITelecomService.aidl b/telecomm/java/com/android/internal/telecom/ITelecomService.aidl index 4875cc3fd660..77a80fe5b70f 100644 --- a/telecomm/java/com/android/internal/telecom/ITelecomService.aidl +++ b/telecomm/java/com/android/internal/telecom/ITelecomService.aidl @@ -50,9 +50,9 @@ interface ITelecomService { void setUserSelectedOutgoingPhoneAccount(in PhoneAccountHandle account); /** - * @see TelecomServiceImpl#getEnabledPhoneAccounts + * @see TelecomServiceImpl#getCallCapablePhoneAccounts */ - List<PhoneAccountHandle> getEnabledPhoneAccounts(); + List<PhoneAccountHandle> getCallCapablePhoneAccounts(); /** * @see TelecomManager#getPhoneAccountsSupportingScheme @@ -95,11 +95,6 @@ interface ITelecomService { List<PhoneAccountHandle> getSimCallManagers(); /** - * @see TelecomServiceImpl#setPhoneAccountEnabled - */ - void setPhoneAccountEnabled(in PhoneAccountHandle account, in boolean isEnabled); - - /** * @see TelecomServiceImpl#registerPhoneAccount */ void registerPhoneAccount(in PhoneAccount metadata); diff --git a/telephony/java/android/telephony/DisconnectCause.java b/telephony/java/android/telephony/DisconnectCause.java index 3fb6be7afa8b..6fe10dc9d256 100644 --- a/telephony/java/android/telephony/DisconnectCause.java +++ b/telephony/java/android/telephony/DisconnectCause.java @@ -263,7 +263,7 @@ public class DisconnectCause { case OUTGOING_CANCELED: return "OUTGOING_CANCELED"; default: - return "INVALID"; + return "INVALID: " + cause; } } } |