diff options
39 files changed, 2015 insertions, 516 deletions
diff --git a/api/current.txt b/api/current.txt index 29b1fc60e128..39d2eb79b2d6 100644 --- a/api/current.txt +++ b/api/current.txt @@ -1368,6 +1368,7 @@ package android { field public static final int windowContentTransitions = 16843794; // 0x1010412 field public static final int windowDisablePreview = 16843298; // 0x1010222 field public static final int windowDrawsSystemBarBackgrounds = 16843858; // 0x1010452 + field public static final int windowElevation = 16843922; // 0x1010492 field public static final int windowEnableSplitTouch = 16843543; // 0x1010317 field public static final int windowEnterAnimation = 16842932; // 0x10100b4 field public static final int windowEnterTransition = 16843833; // 0x1010439 @@ -7061,6 +7062,7 @@ package android.content { method public abstract java.io.File getFileStreamPath(java.lang.String); method public abstract java.io.File getFilesDir(); method public abstract android.os.Looper getMainLooper(); + method public abstract java.io.File getNoBackupFilesDir(); method public abstract java.io.File getObbDir(); method public abstract java.io.File[] getObbDirs(); method public abstract java.lang.String getPackageCodePath(); @@ -7230,6 +7232,7 @@ package android.content { method public java.io.File getFileStreamPath(java.lang.String); method public java.io.File getFilesDir(); method public android.os.Looper getMainLooper(); + method public java.io.File getNoBackupFilesDir(); method public java.io.File getObbDir(); method public java.io.File[] getObbDirs(); method public java.lang.String getPackageCodePath(); @@ -27851,6 +27854,62 @@ package android.system { package android.telecomm { + public final class Call { + method public void addListener(android.telecomm.Call.Listener); + method public void answer(); + method public void conference(); + method public void disconnect(); + method public android.telecomm.RemoteCallVideoProvider getCallVideoProvider(); + method public java.util.List<java.lang.String> getCannedTextResponses(); + method public java.util.List<android.telecomm.Call> getChildren(); + method public android.telecomm.Call.Details getDetails(); + method public android.telecomm.Call getParent(); + method public java.lang.String getRemainingPostDialSequence(); + method public int getState(); + method public void hold(); + method public void phoneAccountClicked(); + method public void playDtmfTone(char); + method public void postDialContinue(boolean); + method public void reject(boolean, java.lang.String); + method public void removeListener(android.telecomm.Call.Listener); + method public void splitFromConference(); + method public void stopDtmfTone(); + method public void swapWithBackgroundCall(); + method public void unhold(); + field public static final int STATE_ACTIVE = 4; // 0x4 + field public static final int STATE_DIALING = 1; // 0x1 + field public static final int STATE_DISCONNECTED = 7; // 0x7 + field public static final int STATE_HOLDING = 3; // 0x3 + field public static final int STATE_NEW = 0; // 0x0 + field public static final int STATE_RINGING = 2; // 0x2 + } + + public static class Call.Details { + method public android.telecomm.PhoneAccount getAccount(); + method public java.lang.String getCallerDisplayName(); + method public int getCallerDisplayNamePresentation(); + method public int getCapabilities(); + method public long getConnectTimeMillis(); + method public int getDisconnectCauseCode(); + method public java.lang.String getDisconnectCauseMsg(); + method public android.telecomm.GatewayInfo getGatewayInfo(); + method public android.net.Uri getHandle(); + method public int getHandlePresentation(); + } + + public static abstract class Call.Listener { + ctor public Call.Listener(); + method public void onCallDestroyed(android.telecomm.Call); + method public void onCallVideoProviderChanged(android.telecomm.Call, android.telecomm.RemoteCallVideoProvider); + method public void onCannedTextResponsesLoaded(android.telecomm.Call, java.util.List<java.lang.String>); + method public void onChildrenChanged(android.telecomm.Call, java.util.List<android.telecomm.Call>); + method public void onDetailsChanged(android.telecomm.Call, android.telecomm.Call.Details); + method public void onParentChanged(android.telecomm.Call, android.telecomm.Call); + method public void onPostDial(android.telecomm.Call, java.lang.String); + method public void onPostDialWait(android.telecomm.Call, java.lang.String); + method public void onStateChanged(android.telecomm.Call, int); + } + public final class CallAudioState implements android.os.Parcelable { method public int describeContents(); method public void writeToParcel(android.os.Parcel, int); @@ -27908,8 +27967,6 @@ package android.telecomm { enum_constant public static final android.telecomm.CallState DISCONNECTED; enum_constant public static final android.telecomm.CallState NEW; enum_constant public static final android.telecomm.CallState ON_HOLD; - enum_constant public static final android.telecomm.CallState POST_DIAL; - enum_constant public static final android.telecomm.CallState POST_DIAL_WAIT; enum_constant public static final android.telecomm.CallState RINGING; } @@ -28083,31 +28140,52 @@ package android.telecomm { field public static final android.os.Parcelable.Creator CREATOR; } - public abstract class InCallService extends android.app.Service { + public abstract class InCallService { ctor protected InCallService(); - method protected abstract void addCall(android.telecomm.InCallCall); - method protected abstract void bringToForeground(boolean); - method protected final android.telecomm.InCallAdapter getAdapter(); - method protected void onAdapterAttached(android.telecomm.InCallAdapter); - method protected abstract void onAudioStateChanged(android.telecomm.CallAudioState); - method public final android.os.IBinder onBind(android.content.Intent); - method protected abstract void setPostDial(java.lang.String, java.lang.String); - method protected abstract void setPostDialWait(java.lang.String, java.lang.String); - method protected abstract void updateCall(android.telecomm.InCallCall); + method public final android.os.IBinder getBinder(); + method public android.telecomm.Phone getPhone(); + method public void onPhoneCreated(android.telecomm.Phone); + method public void onPhoneDestroyed(android.telecomm.Phone); + } + + public final class Phone { + method public final void addListener(android.telecomm.Phone.Listener); + method public final android.telecomm.CallAudioState getAudioState(); + method public final java.util.List<android.telecomm.Call> getCalls(); + method public final void removeListener(android.telecomm.Phone.Listener); + method public final void setAudioRoute(int); + method public final void setMuted(boolean); + } + + public static abstract class Phone.Listener { + ctor public Phone.Listener(); + method public void onAudioStateChanged(android.telecomm.Phone, android.telecomm.CallAudioState); + method public void onBringToForeground(android.telecomm.Phone, boolean); + method public void onCallAdded(android.telecomm.Phone, android.telecomm.Call); + method public void onCallRemoved(android.telecomm.Phone, android.telecomm.Call); } - public final class PhoneAccount implements android.os.Parcelable { - ctor public PhoneAccount(android.content.ComponentName, java.lang.String, android.net.Uri, java.lang.String, java.lang.String, boolean, boolean); + public class PhoneAccount implements android.os.Parcelable { + ctor public PhoneAccount(android.content.ComponentName, java.lang.String, android.net.Uri, int); method public int describeContents(); + method public int getCapabilities(); method public android.content.ComponentName getComponentName(); method public android.net.Uri getHandle(); - method public android.graphics.drawable.Drawable getIcon(android.content.Context); - method public android.graphics.drawable.Drawable getIcon(android.content.Context, int); method public java.lang.String getId(); - method public java.lang.String getLabel(android.content.Context); - method public java.lang.String getShortDescription(android.content.Context); - method public boolean isEnabled(); - method public boolean isSystemDefault(); + method public void writeToParcel(android.os.Parcel, int); + field public static final int CAPABILITY_CALL_PROVIDER = 2; // 0x2 + field public static final int CAPABILITY_SIM_CALL_MANAGER = 1; // 0x1 + field public static final int CAPABILITY_SIM_SUBSCRIPTION = 4; // 0x4 + field public static final android.os.Parcelable.Creator CREATOR; + } + + public class PhoneAccountMetadata implements android.os.Parcelable { + ctor public PhoneAccountMetadata(android.telecomm.PhoneAccount, int, java.lang.String, java.lang.String); + method public int describeContents(); + method public android.telecomm.PhoneAccount getAccount(); + method public android.graphics.drawable.Drawable getIcon(android.content.Context); + method public java.lang.String getLabel(); + method public java.lang.String getShortDescription(); method public void writeToParcel(android.os.Parcel, int); field public static final android.os.Parcelable.Creator CREATOR; } @@ -28122,18 +28200,17 @@ package android.telecomm { method public void updatePeerDimensions(int, int) throws android.os.RemoteException; } - public class RemoteCallVideoProvider implements android.os.IBinder.DeathRecipient { - method public void binderDied(); - method public void requestCallDataUsage() throws android.os.RemoteException; - method public void requestCameraCapabilities() throws android.os.RemoteException; - method public void sendSessionModifyRequest(android.telecomm.VideoCallProfile) throws android.os.RemoteException; - method public void sendSessionModifyResponse(android.telecomm.VideoCallProfile) throws android.os.RemoteException; - method public void setCallVideoClient(android.telecomm.CallVideoClient) throws android.os.RemoteException; + public class RemoteCallVideoProvider { + method public void requestCallDataUsage(); + method public void requestCameraCapabilities(); + method public void sendSessionModifyRequest(android.telecomm.VideoCallProfile); + method public void sendSessionModifyResponse(android.telecomm.VideoCallProfile); + method public void setCallVideoClient(android.telecomm.CallVideoClient); method public void setCamera(java.lang.String) throws android.os.RemoteException; - method public void setDeviceOrientation(int) throws android.os.RemoteException; - method public void setDisplaySurface(android.view.Surface) throws android.os.RemoteException; - method public void setPauseImage(java.lang.String) throws android.os.RemoteException; - method public void setPreviewSurface(android.view.Surface) throws android.os.RemoteException; + method public void setDeviceOrientation(int); + method public void setDisplaySurface(android.view.Surface); + method public void setPauseImage(java.lang.String); + method public void setPreviewSurface(android.view.Surface); method public void setZoom(float) throws android.os.RemoteException; } @@ -28648,7 +28725,6 @@ package android.telephony { } public class TelephonyManager { - method public java.util.List<android.telecomm.PhoneAccount> getAccounts(); method public java.util.List<android.telephony.CellInfo> getAllCellInfo(); method public int getCallState(); method public android.telephony.CellLocation getCellLocation(); @@ -28697,7 +28773,6 @@ package android.telephony { field public static final int DATA_CONNECTING = 1; // 0x1 field public static final int DATA_DISCONNECTED = 0; // 0x0 field public static final int DATA_SUSPENDED = 3; // 0x3 - field public static final java.lang.String EXTRA_ACCOUNT = "account"; field public static final java.lang.String EXTRA_INCOMING_NUMBER = "incoming_number"; field public static final java.lang.String EXTRA_STATE = "state"; field public static final java.lang.String EXTRA_STATE_IDLE; @@ -29213,6 +29288,7 @@ package android.test.mock { method public java.io.File getFileStreamPath(java.lang.String); method public java.io.File getFilesDir(); method public android.os.Looper getMainLooper(); + method public java.io.File getNoBackupFilesDir(); method public java.io.File getObbDir(); method public java.io.File[] getObbDirs(); method public java.lang.String getPackageCodePath(); diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java index bbfb05e12ecc..b9de22028627 100644 --- a/core/java/android/app/ContextImpl.java +++ b/core/java/android/app/ContextImpl.java @@ -247,6 +247,8 @@ class ContextImpl extends Context { @GuardedBy("mSync") private File mFilesDir; @GuardedBy("mSync") + private File mNoBackupFilesDir; + @GuardedBy("mSync") private File mCacheDir; @GuardedBy("mSync") @@ -558,9 +560,7 @@ class ContextImpl extends Context { registerService(TELECOMM_SERVICE, new ServiceFetcher() { public Object createService(ContextImpl ctx) { - IBinder b = ServiceManager.getService(TELECOMM_SERVICE); - return new TelecommManager(ctx.getOuterContext(), - ITelecommService.Stub.asInterface(b)); + return new TelecommManager(ctx.getOuterContext()); }}); registerService(PHONE_SERVICE, new ServiceFetcher() { @@ -963,27 +963,42 @@ class ContextImpl extends Context { return f.delete(); } + // Common-path handling of app data dir creation + private static File createFilesDirLocked(File file) { + if (!file.exists()) { + if (!file.mkdirs()) { + if (file.exists()) { + // spurious failure; probably racing with another process for this app + return file; + } + Log.w(TAG, "Unable to create files subdir " + file.getPath()); + return null; + } + FileUtils.setPermissions( + file.getPath(), + FileUtils.S_IRWXU|FileUtils.S_IRWXG|FileUtils.S_IXOTH, + -1, -1); + } + return file; + } + @Override public File getFilesDir() { synchronized (mSync) { if (mFilesDir == null) { mFilesDir = new File(getDataDirFile(), "files"); } - if (!mFilesDir.exists()) { - if(!mFilesDir.mkdirs()) { - if (mFilesDir.exists()) { - // spurious failure; probably racing with another process for this app - return mFilesDir; - } - Log.w(TAG, "Unable to create files directory " + mFilesDir.getPath()); - return null; - } - FileUtils.setPermissions( - mFilesDir.getPath(), - FileUtils.S_IRWXU|FileUtils.S_IRWXG|FileUtils.S_IXOTH, - -1, -1); + return createFilesDirLocked(mFilesDir); + } + } + + @Override + public File getNoBackupFilesDir() { + synchronized (mSync) { + if (mNoBackupFilesDir == null) { + mNoBackupFilesDir = new File(getDataDirFile(), "no_backup"); } - return mFilesDir; + return createFilesDirLocked(mNoBackupFilesDir); } } @@ -1035,22 +1050,8 @@ class ContextImpl extends Context { if (mCacheDir == null) { mCacheDir = new File(getDataDirFile(), "cache"); } - if (!mCacheDir.exists()) { - if(!mCacheDir.mkdirs()) { - if (mCacheDir.exists()) { - // spurious failure; probably racing with another process for this app - return mCacheDir; - } - Log.w(TAG, "Unable to create cache directory " + mCacheDir.getAbsolutePath()); - return null; - } - FileUtils.setPermissions( - mCacheDir.getPath(), - FileUtils.S_IRWXU|FileUtils.S_IRWXG|FileUtils.S_IXOTH, - -1, -1); - } + return createFilesDirLocked(mCacheDir); } - return mCacheDir; } @Override diff --git a/core/java/android/app/backup/BackupAgent.java b/core/java/android/app/backup/BackupAgent.java index 886f1a64143f..e2a86e85cd53 100644 --- a/core/java/android/app/backup/BackupAgent.java +++ b/core/java/android/app/backup/BackupAgent.java @@ -247,12 +247,40 @@ public abstract class BackupAgent extends ContextWrapper { throws IOException; /** - * The default implementation backs up the entirety of the application's "owned" - * file system trees to the output. + * The application is having its entire file system contents backed up. {@code data} + * points to the backup destination, and the app has the opportunity to choose which + * files are to be stored. To commit a file as part of the backup, call the + * {@link #fullBackupFile(File, FullBackupDataOutput)} helper method. After all file + * data is written to the output, the agent returns from this method and the backup + * operation concludes. + * + * <p>Certain parts of the app's data are never backed up even if the app explicitly + * sends them to the output: + * + * <ul> + * <li>The contents of the {@link #getCacheDir()} directory</li> + * <li>The contents of the {@link #getNoBackupFilesDir()} directory</li> + * <li>The contents of the app's shared library directory</li> + * </ul> + * + * <p>The default implementation of this method backs up the entirety of the + * application's "owned" file system trees to the output other than the few exceptions + * listed above. Apps only need to override this method if they need to impose special + * limitations on which files are being stored beyond the control that + * {@link #getNoBackupFilesDir()} offers. + * + * @param data A structured wrapper pointing to the backup destination. + * @throws IOException + * + * @see Context#getNoBackupFilesDir() + * @see #fullBackupFile(File, FullBackupDataOutput) + * @see #onRestoreFile(ParcelFileDescriptor, long, File, int, long, long) */ public void onFullBackup(FullBackupDataOutput data) throws IOException { ApplicationInfo appInfo = getApplicationInfo(); + // Note that we don't need to think about the no_backup dir because it's outside + // all of the ones we will be traversing String rootDir = new File(appInfo.dataDir).getCanonicalPath(); String filesDir = getFilesDir().getCanonicalPath(); String databaseDir = getDatabasePath("foo").getParentFile().getCanonicalPath(); @@ -311,6 +339,10 @@ public abstract class BackupAgent extends ContextWrapper { * to place it with the proper location and permissions on the device where the * data is restored. * + * <p class="note">It is safe to explicitly back up files underneath your application's + * {@link #getNoBackupFilesDir()} directory, and they will be restored to that + * location correctly. + * * @param file The file to be backed up. The file must exist and be readable by * the caller. * @param output The destination to which the backed-up file data will be sent. @@ -319,6 +351,7 @@ public abstract class BackupAgent extends ContextWrapper { // Look up where all of our various well-defined dir trees live on this device String mainDir; String filesDir; + String nbFilesDir; String dbDir; String spDir; String cacheDir; @@ -331,6 +364,7 @@ public abstract class BackupAgent extends ContextWrapper { try { mainDir = new File(appInfo.dataDir).getCanonicalPath(); filesDir = getFilesDir().getCanonicalPath(); + nbFilesDir = getNoBackupFilesDir().getCanonicalPath(); dbDir = getDatabasePath("foo").getParentFile().getCanonicalPath(); spDir = getSharedPrefsFile("foo").getParentFile().getCanonicalPath(); cacheDir = getCacheDir().getCanonicalPath(); @@ -354,8 +388,10 @@ public abstract class BackupAgent extends ContextWrapper { return; } - if (filePath.startsWith(cacheDir) || filePath.startsWith(libDir)) { - Log.w(TAG, "lib and cache files are not backed up"); + if (filePath.startsWith(cacheDir) + || filePath.startsWith(libDir) + || filePath.startsWith(nbFilesDir)) { + Log.w(TAG, "lib, cache, and no_backup files are not backed up"); return; } @@ -508,6 +544,8 @@ public abstract class BackupAgent extends ContextWrapper { mode = -1; // < 0 is a token to skip attempting a chmod() } } + } else if (domain.equals(FullBackup.NO_BACKUP_TREE_TOKEN)) { + basePath = getNoBackupFilesDir().getCanonicalPath(); } else { // Not a supported location Log.i(TAG, "Unrecognized domain " + domain); diff --git a/core/java/android/app/backup/FullBackup.java b/core/java/android/app/backup/FullBackup.java index 6ebb6c45de64..e5b47c652ccb 100644 --- a/core/java/android/app/backup/FullBackup.java +++ b/core/java/android/app/backup/FullBackup.java @@ -40,6 +40,7 @@ public class FullBackup { public static final String OBB_TREE_TOKEN = "obb"; public static final String ROOT_TREE_TOKEN = "r"; public static final String DATA_TREE_TOKEN = "f"; + public static final String NO_BACKUP_TREE_TOKEN = "nb"; public static final String DATABASE_TREE_TOKEN = "db"; public static final String SHAREDPREFS_TREE_TOKEN = "sp"; public static final String MANAGED_EXTERNAL_TREE_TOKEN = "ef"; diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index 535aaa1a936b..a52cbdd85a6e 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -647,6 +647,26 @@ public abstract class Context { public abstract File getFilesDir(); /** + * Returns the absolute path to the directory on the filesystem similar to + * {@link #getFilesDir()}. The difference is that files placed under this + * directory will be excluded from automatic backup to remote storage. See + * {@link android.app.backup.BackupAgent BackupAgent} for a full discussion + * of the automatic backup mechanism in Android. + * + * <p>No permissions are required to read or write to the returned path, since this + * path is internal storage. + * + * @return The path of the directory holding application files that will not be + * automatically backed up to remote storage. + * + * @see #openFileOutput + * @see #getFileStreamPath + * @see #getDir + * @see android.app.backup.BackupAgent + */ + public abstract File getNoBackupFilesDir(); + + /** * Returns the absolute path to the directory on the primary external filesystem * (that is somewhere on {@link android.os.Environment#getExternalStorageDirectory() * Environment.getExternalStorageDirectory()}) where the application can diff --git a/core/java/android/content/ContextWrapper.java b/core/java/android/content/ContextWrapper.java index dbf9122e454d..13eed07dbef0 100644 --- a/core/java/android/content/ContextWrapper.java +++ b/core/java/android/content/ContextWrapper.java @@ -200,7 +200,12 @@ public class ContextWrapper extends Context { public File getFilesDir() { return mBase.getFilesDir(); } - + + @Override + public File getNoBackupFilesDir() { + return mBase.getNoBackupFilesDir(); + } + @Override public File getExternalFilesDir(String type) { return mBase.getExternalFilesDir(type); diff --git a/core/java/android/hardware/camera2/legacy/LegacyCameraDevice.java b/core/java/android/hardware/camera2/legacy/LegacyCameraDevice.java index e9793c48fab7..5f29e5c50472 100644 --- a/core/java/android/hardware/camera2/legacy/LegacyCameraDevice.java +++ b/core/java/android/hardware/camera2/legacy/LegacyCameraDevice.java @@ -17,6 +17,7 @@ package android.hardware.camera2.legacy; import android.graphics.ImageFormat; +import android.graphics.SurfaceTexture; import android.hardware.Camera; import android.hardware.camera2.CameraCharacteristics; import android.hardware.camera2.CaptureRequest; @@ -463,6 +464,24 @@ public class LegacyCameraDevice implements AutoCloseable { return ids.contains(id); } + static void setSurfaceOrientation(Surface surface, int facing, int sensorOrientation) + throws BufferQueueAbandonedException { + checkNotNull(surface); + LegacyExceptionUtils.throwOnError(nativeSetSurfaceOrientation(surface, facing, + sensorOrientation)); + } + + static Size getTextureSize(SurfaceTexture surfaceTexture) + throws BufferQueueAbandonedException { + checkNotNull(surfaceTexture); + + int[] dimens = new int[2]; + LegacyExceptionUtils.throwOnError(nativeDetectTextureDimens(surfaceTexture, + /*out*/dimens)); + + return new Size(dimens[0], dimens[1]); + } + private static native int nativeDetectSurfaceType(Surface surface); private static native int nativeDetectSurfaceDimens(Surface surface, @@ -479,4 +498,11 @@ public class LegacyCameraDevice implements AutoCloseable { private static native int nativeSetSurfaceDimens(Surface surface, int width, int height); private static native long nativeGetSurfaceId(Surface surface); + + private static native int nativeSetSurfaceOrientation(Surface surface, int facing, + int sensorOrientation); + + private static native int nativeDetectTextureDimens(SurfaceTexture surfaceTexture, + /*out*/int[/*2*/] dimens); + } diff --git a/core/java/android/hardware/camera2/legacy/RequestThreadManager.java b/core/java/android/hardware/camera2/legacy/RequestThreadManager.java index e6d84c564826..7d1be3b9c67a 100644 --- a/core/java/android/hardware/camera2/legacy/RequestThreadManager.java +++ b/core/java/android/hardware/camera2/legacy/RequestThreadManager.java @@ -292,10 +292,13 @@ public class RequestThreadManager { mInFlightPreview = null; mInFlightJpeg = null; + int facing = mCharacteristics.get(CameraCharacteristics.LENS_FACING); + int orientation = mCharacteristics.get(CameraCharacteristics.SENSOR_ORIENTATION); if (outputs != null) { for (Surface s : outputs) { try { int format = LegacyCameraDevice.detectSurfaceType(s); + LegacyCameraDevice.setSurfaceOrientation(s, facing, orientation); switch (format) { case CameraMetadataNative.NATIVE_JPEG_FORMAT: mCallbackOutputs.add(s); diff --git a/core/java/android/hardware/camera2/legacy/SurfaceTextureRenderer.java b/core/java/android/hardware/camera2/legacy/SurfaceTextureRenderer.java index daa64c05e8b3..fdf9ba06d41d 100644 --- a/core/java/android/hardware/camera2/legacy/SurfaceTextureRenderer.java +++ b/core/java/android/hardware/camera2/legacy/SurfaceTextureRenderer.java @@ -1,18 +1,18 @@ /* -* Copyright (C) 2014 The Android Open Source Project -* -* Licensed under the Apache License, Version 2.0 (the "License"); -* you may not use this file except in compliance with the License. -* You may obtain a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, software -* distributed under the License is distributed on an "AS IS" BASIS, -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -* See the License for the specific language governing permissions and -* limitations under the License. -*/ + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package android.hardware.camera2.legacy; import android.graphics.ImageFormat; @@ -98,14 +98,14 @@ public class SurfaceTextureRenderer { */ private static final String VERTEX_SHADER = "uniform mat4 uMVPMatrix;\n" + - "uniform mat4 uSTMatrix;\n" + - "attribute vec4 aPosition;\n" + - "attribute vec4 aTextureCoord;\n" + - "varying vec2 vTextureCoord;\n" + - "void main() {\n" + - " gl_Position = uMVPMatrix * aPosition;\n" + - " vTextureCoord = (uSTMatrix * aTextureCoord).xy;\n" + - "}\n"; + "uniform mat4 uSTMatrix;\n" + + "attribute vec4 aPosition;\n" + + "attribute vec4 aTextureCoord;\n" + + "varying vec2 vTextureCoord;\n" + + "void main() {\n" + + " gl_Position = uMVPMatrix * aPosition;\n" + + " vTextureCoord = (uSTMatrix * aTextureCoord).xy;\n" + + "}\n"; /** * This fragment shader simply draws the color in the 2D texture at @@ -113,12 +113,12 @@ public class SurfaceTextureRenderer { */ private static final String FRAGMENT_SHADER = "#extension GL_OES_EGL_image_external : require\n" + - "precision mediump float;\n" + - "varying vec2 vTextureCoord;\n" + - "uniform samplerExternalOES sTexture;\n" + - "void main() {\n" + - " gl_FragColor = texture2D(sTexture, vTextureCoord);\n" + - "}\n"; + "precision mediump float;\n" + + "varying vec2 vTextureCoord;\n" + + "uniform samplerExternalOES sTexture;\n" + + "void main() {\n" + + " gl_FragColor = texture2D(sTexture, vTextureCoord);\n" + + "}\n"; private float[] mMVPMatrix = new float[GL_MATRIX_SIZE]; private float[] mSTMatrix = new float[GL_MATRIX_SIZE]; @@ -189,12 +189,56 @@ public class SurfaceTextureRenderer { return program; } - private void drawFrame(SurfaceTexture st) { + private void drawFrame(SurfaceTexture st, int width, int height) { checkGlError("onDrawFrame start"); st.getTransformMatrix(mSTMatrix); + Size dimens; + try { + dimens = LegacyCameraDevice.getTextureSize(st); + } catch (LegacyExceptionUtils.BufferQueueAbandonedException e) { + // Should never hit this. + throw new IllegalStateException("Surface abandoned, skipping drawFrame...", e); + } + + Matrix.setIdentityM(mMVPMatrix, /*smOffset*/0); + + float texWidth = dimens.getWidth(); + float texHeight = dimens.getHeight(); + + if (texWidth <= 0 || texHeight <= 0) { + throw new IllegalStateException("Illegal intermediate texture with dimension of 0"); + } + + // Find largest scaling factor from the intermediate texture dimension to the + // output surface dimension. Scaling the intermediate texture by this allows + // us to letterbox/pillerbox the output surface into the intermediate texture. + float widthRatio = width / texWidth; + float heightRatio = height / texHeight; + float actual = (widthRatio < heightRatio) ? heightRatio : widthRatio; + + if (DEBUG) { + Log.d(TAG, "Scaling factor " + actual + " used for " + width + "x" + height + + " surface, intermediate buffer size is " + texWidth + "x" + texHeight); + } + + // Set the viewport height and width to be the scaled intermediate texture dimensions. + int viewportW = (int) (actual * texWidth); + int viewportH = (int) (actual * texHeight); + + // Set the offset of the viewport so that the output surface is centered in the viewport. + float dx = (width - viewportW) / 2f; + float dy = (height - viewportH) / 2f; + if (DEBUG) { - GLES20.glClearColor(0.0f, 1.0f, 0.0f, 1.0f); + Log.d(TAG, "Translation " + dx + "," + dy + " used for " + width + "x" + height + + " surface"); + } + + GLES20.glViewport((int) dx, (int) dy, viewportW, viewportH); + + if (DEBUG) { + GLES20.glClearColor(1.0f, 0.0f, 0.0f, 1.0f); GLES20.glClear(GLES20.GL_DEPTH_BUFFER_BIT | GLES20.GL_COLOR_BUFFER_BIT); } @@ -218,7 +262,6 @@ public class SurfaceTextureRenderer { GLES20.glEnableVertexAttribArray(maTextureHandle); checkGlError("glEnableVertexAttribArray maTextureHandle"); - Matrix.setIdentityM(mMVPMatrix, 0); GLES20.glUniformMatrix4fv(muMVPMatrixHandle, /*count*/ 1, /*transpose*/ false, mMVPMatrix, /*offset*/ 0); GLES20.glUniformMatrix4fv(muSTMatrixHandle, /*count*/ 1, /*transpose*/ false, mSTMatrix, @@ -589,18 +632,19 @@ public class SurfaceTextureRenderer { for (EGLSurfaceHolder holder : mSurfaces) { if (LegacyCameraDevice.containsSurfaceId(holder.surface, targetSurfaceIds)) { makeCurrent(holder.eglSurface); - drawFrame(mSurfaceTexture); + drawFrame(mSurfaceTexture, holder.width, holder.height); swapBuffers(holder.eglSurface); } } for (EGLSurfaceHolder holder : mConversionSurfaces) { if (LegacyCameraDevice.containsSurfaceId(holder.surface, targetSurfaceIds)) { makeCurrent(holder.eglSurface); - drawFrame(mSurfaceTexture); + drawFrame(mSurfaceTexture, holder.width, holder.height); mPBufferPixels.clear(); - GLES20.glReadPixels(/*x*/ 0, /*y*/ 0, holder.width, holder.height, GLES20.GL_RGBA, - GLES20.GL_UNSIGNED_BYTE, mPBufferPixels); + GLES20.glReadPixels(/*x*/ 0, /*y*/ 0, holder.width, holder.height, + GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, mPBufferPixels); checkGlError("glReadPixels"); + try { int format = LegacyCameraDevice.detectSurfaceType(holder.surface); LegacyCameraDevice.produceFrame(holder.surface, mPBufferPixels.array(), diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 7fab80801448..b554548e0232 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -461,6 +461,11 @@ public final class ViewRootImpl implements ViewParent, } } + // Compute surface insets required to draw at specified Z value. + // TODO: Use real shadow insets for a constant max Z. + final int surfaceInset = (int) Math.ceil(view.getZ() * 2); + attrs.surfaceInsets.set(surfaceInset, surfaceInset, surfaceInset, surfaceInset); + CompatibilityInfo compatibilityInfo = mDisplayAdjustments.getCompatibilityInfo(); mTranslator = compatibilityInfo.getTranslator(); mDisplayAdjustments.setActivityToken(attrs.token); @@ -1713,8 +1718,8 @@ public final class ViewRootImpl implements ViewParent, if (hwInitialized || mWidth != mAttachInfo.mHardwareRenderer.getWidth() || mHeight != mAttachInfo.mHardwareRenderer.getHeight()) { - final Rect shadowInsets = params != null ? params.shadowInsets : null; - mAttachInfo.mHardwareRenderer.setup(mWidth, mHeight, shadowInsets); + final Rect surfaceInsets = params != null ? params.surfaceInsets : null; + mAttachInfo.mHardwareRenderer.setup(mWidth, mHeight, surfaceInsets); if (!hwInitialized) { mAttachInfo.mHardwareRenderer.invalidate(mSurface); mFullRedrawNeeded = true; @@ -2371,7 +2376,7 @@ public final class ViewRootImpl implements ViewParent, } final WindowManager.LayoutParams params = mWindowAttributes; - final Rect surfaceInsets = params != null ? params.shadowInsets : null; + final Rect surfaceInsets = params != null ? params.surfaceInsets : null; boolean animating = mScroller != null && mScroller.computeScrollOffset(); final int curScrollY; if (animating) { @@ -3155,7 +3160,7 @@ public final class ViewRootImpl implements ViewParent, mFullRedrawNeeded = true; try { final WindowManager.LayoutParams lp = mWindowAttributes; - final Rect surfaceInsets = lp != null ? lp.shadowInsets : null; + final Rect surfaceInsets = lp != null ? lp.surfaceInsets : null; mAttachInfo.mHardwareRenderer.initializeIfNeeded( mWidth, mHeight, mSurface, surfaceInsets); } catch (OutOfResourcesException e) { diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java index c06b5d8f50a9..034778ff397a 100644 --- a/core/java/android/view/WindowManager.java +++ b/core/java/android/view/WindowManager.java @@ -1298,7 +1298,7 @@ public interface WindowManager extends ViewManager { * * @hide */ - public Rect shadowInsets = new Rect(); + public Rect surfaceInsets = new Rect(); /** * The desired bitmap format. May be one of the constants in @@ -1580,10 +1580,10 @@ public interface WindowManager extends ViewManager { out.writeInt(hasSystemUiListeners ? 1 : 0); out.writeInt(inputFeatures); out.writeLong(userActivityTimeout); - out.writeInt(shadowInsets.left); - out.writeInt(shadowInsets.top); - out.writeInt(shadowInsets.right); - out.writeInt(shadowInsets.bottom); + out.writeInt(surfaceInsets.left); + out.writeInt(surfaceInsets.top); + out.writeInt(surfaceInsets.right); + out.writeInt(surfaceInsets.bottom); } public static final Parcelable.Creator<LayoutParams> CREATOR @@ -1626,7 +1626,10 @@ public interface WindowManager extends ViewManager { hasSystemUiListeners = in.readInt() != 0; inputFeatures = in.readInt(); userActivityTimeout = in.readLong(); - shadowInsets.set(in.readInt(), in.readInt(), in.readInt(), in.readInt()); + surfaceInsets.left = in.readInt(); + surfaceInsets.top = in.readInt(); + surfaceInsets.right = in.readInt(); + surfaceInsets.bottom = in.readInt(); } @SuppressWarnings({"PointlessBitwiseExpression"}) @@ -1658,7 +1661,7 @@ public interface WindowManager extends ViewManager { /** {@hide} */ public static final int TRANSLUCENT_FLAGS_CHANGED = 1<<19; /** {@hide} */ - public static final int SHADOW_INSETS_CHANGED = 1<<20; + public static final int SURFACE_INSETS_CHANGED = 1<<20; /** {@hide} */ public static final int EVERYTHING_CHANGED = 0xffffffff; @@ -1794,9 +1797,9 @@ public interface WindowManager extends ViewManager { changes |= USER_ACTIVITY_TIMEOUT_CHANGED; } - if (!shadowInsets.equals(o.shadowInsets)) { - shadowInsets.set(o.shadowInsets); - changes |= SHADOW_INSETS_CHANGED; + if (!surfaceInsets.equals(o.surfaceInsets)) { + surfaceInsets.set(o.surfaceInsets); + changes |= SURFACE_INSETS_CHANGED; } return changes; @@ -1898,8 +1901,8 @@ public interface WindowManager extends ViewManager { if (userActivityTimeout >= 0) { sb.append(" userActivityTimeout=").append(userActivityTimeout); } - if (!shadowInsets.equals(Insets.NONE)) { - sb.append(" shadowInsets=").append(shadowInsets); + if (!surfaceInsets.equals(Insets.NONE)) { + sb.append(" surfaceInsets=").append(surfaceInsets); } sb.append('}'); return sb.toString(); diff --git a/core/java/android/widget/PopupWindow.java b/core/java/android/widget/PopupWindow.java index a35d4470aad4..41d3e3208ee2 100644 --- a/core/java/android/widget/PopupWindow.java +++ b/core/java/android/widget/PopupWindow.java @@ -1096,10 +1096,6 @@ public class PopupWindow { p.softInputMode = mSoftInputMode; p.setTitle("PopupWindow:" + Integer.toHexString(hashCode())); - // TODO: Use real shadow insets once that algorithm is finalized. - final int shadowInset = (int) Math.ceil(mElevation * 2); - p.shadowInsets.set(shadowInset, shadowInset, shadowInset, shadowInset); - return p; } diff --git a/core/jni/android_hardware_camera2_legacy_LegacyCameraDevice.cpp b/core/jni/android_hardware_camera2_legacy_LegacyCameraDevice.cpp index 9621bb25d772..d2f5b5d6d633 100644 --- a/core/jni/android_hardware_camera2_legacy_LegacyCameraDevice.cpp +++ b/core/jni/android_hardware_camera2_legacy_LegacyCameraDevice.cpp @@ -19,17 +19,20 @@ #include <utils/Log.h> #include <utils/Errors.h> #include <utils/Trace.h> +#include <camera/CameraUtils.h> #include "jni.h" #include "JNIHelp.h" #include "android_runtime/AndroidRuntime.h" #include "android_runtime/android_view_Surface.h" +#include "android_runtime/android_graphics_SurfaceTexture.h" #include <gui/Surface.h> #include <gui/IGraphicBufferProducer.h> #include <ui/GraphicBuffer.h> #include <system/window.h> #include <hardware/camera3.h> +#include <system/camera_metadata.h> #include <stdint.h> #include <inttypes.h> @@ -335,6 +338,25 @@ static sp<ANativeWindow> getNativeWindow(JNIEnv* env, jobject surface) { return anw; } +static sp<ANativeWindow> getNativeWindowFromTexture(JNIEnv* env, jobject surfaceTexture) { + sp<ANativeWindow> anw; + if (surfaceTexture) { + anw = android_SurfaceTexture_getNativeWindow(env, surfaceTexture); + if (env->ExceptionCheck()) { + return NULL; + } + } else { + jniThrowNullPointerException(env, "surfaceTexture"); + return NULL; + } + if (anw == NULL) { + jniThrowExceptionFmt(env, "java/lang/IllegalArgumentException", + "SurfaceTexture had no valid native window."); + return NULL; + } + return anw; +} + static sp<Surface> getSurface(JNIEnv* env, jobject surface) { sp<Surface> s; if (surface) { @@ -376,6 +398,17 @@ static jint LegacyCameraDevice_nativeDetectSurfaceType(JNIEnv* env, jobject thiz static jint LegacyCameraDevice_nativeDetectSurfaceDimens(JNIEnv* env, jobject thiz, jobject surface, jintArray dimens) { ALOGV("nativeGetSurfaceDimens"); + + if (dimens == NULL) { + ALOGE("%s: Null dimens argument passed to nativeDetectSurfaceDimens", __FUNCTION__); + return BAD_VALUE; + } + + if (env->GetArrayLength(dimens) < 2) { + ALOGE("%s: Invalid length of dimens argument in nativeDetectSurfaceDimens", __FUNCTION__); + return BAD_VALUE; + } + sp<ANativeWindow> anw; if ((anw = getNativeWindow(env, surface)) == NULL) { ALOGE("%s: Could not retrieve native window from surface.", __FUNCTION__); @@ -398,6 +431,37 @@ static jint LegacyCameraDevice_nativeDetectSurfaceDimens(JNIEnv* env, jobject th return NO_ERROR; } +static jint LegacyCameraDevice_nativeDetectTextureDimens(JNIEnv* env, jobject thiz, + jobject surfaceTexture, jintArray dimens) { + ALOGV("nativeDetectTextureDimens"); + sp<ANativeWindow> anw; + if ((anw = getNativeWindowFromTexture(env, surfaceTexture)) == NULL) { + ALOGE("%s: Could not retrieve native window from SurfaceTexture.", __FUNCTION__); + return BAD_VALUE; + } + + int32_t dimenBuf[2]; + status_t err = anw->query(anw.get(), NATIVE_WINDOW_WIDTH, dimenBuf); + if(err != NO_ERROR) { + ALOGE("%s: Error while querying SurfaceTexture width %s (%d)", __FUNCTION__, + strerror(-err), err); + return err; + } + + err = anw->query(anw.get(), NATIVE_WINDOW_HEIGHT, dimenBuf + 1); + if(err != NO_ERROR) { + ALOGE("%s: Error while querying SurfaceTexture height %s (%d)", __FUNCTION__, + strerror(-err), err); + return err; + } + + env->SetIntArrayRegion(dimens, /*start*/0, /*length*/ARRAY_SIZE(dimenBuf), dimenBuf); + if (env->ExceptionCheck()) { + return BAD_VALUE; + } + return NO_ERROR; +} + static jint LegacyCameraDevice_nativeConfigureSurface(JNIEnv* env, jobject thiz, jobject surface, jint width, jint height, jint pixelFormat) { ALOGV("nativeConfigureSurface"); @@ -504,6 +568,42 @@ static jlong LegacyCameraDevice_nativeGetSurfaceId(JNIEnv* env, jobject thiz, jo return reinterpret_cast<jlong>(b.get()); } +static jint LegacyCameraDevice_nativeSetSurfaceOrientation(JNIEnv* env, jobject thiz, + jobject surface, jint facing, jint orientation) { + ALOGV("nativeSetSurfaceOrientation"); + sp<ANativeWindow> anw; + if ((anw = getNativeWindow(env, surface)) == NULL) { + ALOGE("%s: Could not retrieve native window from surface.", __FUNCTION__); + return BAD_VALUE; + } + + status_t err = NO_ERROR; + CameraMetadata staticMetadata; + + int32_t orientVal = static_cast<int32_t>(orientation); + uint8_t facingVal = static_cast<uint8_t>(facing); + staticMetadata.update(ANDROID_SENSOR_ORIENTATION, &orientVal, 1); + staticMetadata.update(ANDROID_LENS_FACING, &facingVal, 1); + + int32_t transform = 0; + + if ((err = CameraUtils::getRotationTransform(staticMetadata, /*out*/&transform)) != OK) { + ALOGE("%s: Invalid rotation transform %s (%d)", __FUNCTION__, strerror(-err), + err); + return err; + } + + ALOGV("%s: Setting buffer sticky transform to %d", __FUNCTION__, transform); + + if ((err = native_window_set_buffers_sticky_transform(anw.get(), transform)) != OK) { + ALOGE("%s: Unable to configure surface transform, error %s (%d)", __FUNCTION__, + strerror(-err), err); + return err; + } + + return NO_ERROR; +} + } // extern "C" static JNINativeMethod gCameraDeviceMethods[] = { @@ -528,6 +628,12 @@ static JNINativeMethod gCameraDeviceMethods[] = { { "nativeGetSurfaceId", "(Landroid/view/Surface;)J", (void *)LegacyCameraDevice_nativeGetSurfaceId }, + { "nativeDetectTextureDimens", + "(Landroid/graphics/SurfaceTexture;[I)I", + (void *)LegacyCameraDevice_nativeDetectTextureDimens }, + { "nativeSetSurfaceOrientation", + "(Landroid/view/Surface;II)I", + (void *)LegacyCameraDevice_nativeSetSurfaceOrientation }, }; // Get all the required offsets in java class and register native functions diff --git a/core/res/res/layout/alert_dialog_material.xml b/core/res/res/layout/alert_dialog_material.xml index 93acc3f5cd8d..6a435b25d927 100644 --- a/core/res/res/layout/alert_dialog_material.xml +++ b/core/res/res/layout/alert_dialog_material.xml @@ -20,13 +20,7 @@ android:id="@+id/parentPanel" android:layout_width="match_parent" android:layout_height="wrap_content" - android:orientation="vertical" - android:background="@drawable/dialog_background_material" - android:translationZ="@dimen/floating_window_z" - android:layout_marginLeft="@dimen/floating_window_margin_left" - android:layout_marginTop="@dimen/floating_window_margin_top" - android:layout_marginRight="@dimen/floating_window_margin_right" - android:layout_marginBottom="@dimen/floating_window_margin_bottom"> + android:orientation="vertical"> <LinearLayout android:id="@+id/topPanel" android:layout_width="match_parent" diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml index 5fdadb7fdd0e..ed228e4ac563 100644 --- a/core/res/res/values/attrs.xml +++ b/core/res/res/values/attrs.xml @@ -1826,6 +1826,9 @@ Activity Transition. Corresponds to {@link android.view.Window#setTransitionBackgroundFadeDuration(long)}. --> <attr name="windowTransitionBackgroundFadeDuration" /> + + <!-- Elevation to use for the window. --> + <attr name="windowElevation" format="dimension" /> </declare-styleable> <!-- The set of attributes that describe a AlertDialog's theme. --> diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml index a0ca06ef9621..fee5c4335446 100644 --- a/core/res/res/values/public.xml +++ b/core/res/res/values/public.xml @@ -2233,6 +2233,9 @@ <public type="attr" name="buttonBarNegativeButtonStyle" /> <public type="attr" name="popupElevation" /> <public type="attr" name="actionBarPopupTheme" /> + <public type="attr" name="multiArch" /> + <public type="attr" name="touchscreenBlocksFocus" /> + <public type="attr" name="windowElevation" /> <public-padding type="dimen" name="l_resource_pad" end="0x01050010" /> @@ -2509,6 +2512,4 @@ <!-- A transition that moves views in or out of the scene to or from the left edge when a view visibility changes. --> <public type="transition" name="slide_left"/> - <public type="attr" name="multiArch" /> - <public type="attr" name="touchscreenBlocksFocus" /> </resources> diff --git a/core/res/res/values/styles.xml b/core/res/res/values/styles.xml index 0eceae607926..f5d7f5f8483e 100644 --- a/core/res/res/values/styles.xml +++ b/core/res/res/values/styles.xml @@ -824,15 +824,9 @@ please see styles_device_defaults.xml. <item name="textColorLink">?textColorLinkInverse</item> </style> - <style name="TextAppearance.Theme.Dialog" parent="TextAppearance.Theme"> - </style> - - <style name="TextAppearance.Theme.Dialog.AppError"> - <item name="textColor">#ffffc0c0</item> - </style> + <style name="TextAppearance.Theme.Dialog" parent="TextAppearance.Theme" /> - <style name="TextAppearance.Widget"> - </style> + <style name="TextAppearance.Widget" /> <style name="TextAppearance.Widget.Button" parent="TextAppearance.Small.Inverse"> <item name="textColor">@color/primary_text_light_nodisable</item> diff --git a/core/res/res/values/themes.xml b/core/res/res/values/themes.xml index 6ada97586ae9..a519c37acce8 100644 --- a/core/res/res/values/themes.xml +++ b/core/res/res/values/themes.xml @@ -820,21 +820,14 @@ please see themes_device_defaults.xml. <eat-comment /> <!-- Theme for the dialog shown when an app crashes or ANRs. --> - <style name="Theme.Dialog.AppError" parent="Theme.DeviceDefault.Light.Dialog"> - <item name="windowFrame">@null</item> - <item name="windowTitleStyle">@style/DialogWindowTitle</item> - <item name="windowBackground">@color/transparent</item> - <item name="windowIsFloating">true</item> - <item name="windowContentOverlay">@null</item> + <style name="Theme.Dialog.AppError" parent="Theme.DeviceDefault.Light.Dialog.Alert"> <item name="windowContentTransitions">false</item> - <item name="textAppearance">@style/TextAppearance.Theme.Dialog.AppError</item> <item name="windowCloseOnTouchOutside">false</item> </style> <!-- Special theme for the recent apps dialog, to allow customization with overlays. --> <style name="Theme.Dialog.RecentApplications" parent="Theme.DeviceDefault.Light.Dialog"> - <item name="windowFrame">@null</item> <item name="windowBackground">@color/transparent</item> <item name="windowAnimationStyle">@style/Animation.RecentApplications</item> <item name="textColor">@color/secondary_text_nofocus</item> diff --git a/core/res/res/values/themes_material.xml b/core/res/res/values/themes_material.xml index 47f3778b02d8..efc92d95d017 100644 --- a/core/res/res/values/themes_material.xml +++ b/core/res/res/values/themes_material.xml @@ -1011,7 +1011,8 @@ please see themes_device_defaults.xml. <style name="Theme.Material.Dialog"> <item name="windowFrame">@null</item> <item name="windowTitleStyle">@style/DialogWindowTitle.Material</item> - <item name="windowBackground">@drawable/dialog_background_shadow_material</item> + <item name="windowBackground">@drawable/dialog_background_material</item> + <item name="windowElevation">@dimen/floating_window_z</item> <item name="windowIsFloating">true</item> <item name="windowContentOverlay">@null</item> <item name="windowAnimationStyle">@style/Animation.Material.Dialog</item> @@ -1077,6 +1078,7 @@ please see themes_device_defaults.xml. its pixels. --> <style name="Theme.Material.Dialog.NoFrame"> <item name="windowBackground">@color/transparent</item> + <item name="windowElevation">0dp</item> <item name="windowAnimationStyle">@null</item> <item name="backgroundDimEnabled">false</item> <item name="windowIsTranslucent">true</item> @@ -1085,7 +1087,6 @@ please see themes_device_defaults.xml. </style> <style name="Theme.Material.Dialog.BaseAlert"> - <item name="windowBackground">@color/transparent</item> <item name="windowTitleStyle">@style/DialogWindowTitle.Material</item> <item name="windowMinWidthMajor">@dimen/dialog_min_width_major</item> <item name="windowMinWidthMinor">@dimen/dialog_min_width_minor</item> @@ -1100,6 +1101,7 @@ please see themes_device_defaults.xml. <style name="Theme.Material.Dialog.BaseTimePicker"> <item name="windowBackground">@color/transparent</item> + <item name="windowElevation">0dp</item> <item name="windowTitleStyle">@style/DialogWindowTitle.Material</item> <item name="windowContentOverlay">@null</item> </style> @@ -1131,7 +1133,8 @@ please see themes_device_defaults.xml. <style name="Theme.Material.Light.Dialog"> <item name="windowFrame">@null</item> <item name="windowTitleStyle">@style/DialogWindowTitle.Material.Light</item> - <item name="windowBackground">@drawable/dialog_background_shadow_material</item> + <item name="windowBackground">@drawable/dialog_background_material</item> + <item name="windowElevation">@dimen/floating_window_z</item> <item name="windowIsFloating">true</item> <item name="windowContentOverlay">@null</item> <item name="windowAnimationStyle">@style/Animation.Material.Dialog</item> @@ -1203,7 +1206,6 @@ please see themes_device_defaults.xml. <style name="Theme.Material.Light.DialogWhenLarge.NoActionBar" parent="@style/Theme.Material.Light.NoActionBar" /> <style name="Theme.Material.Light.Dialog.BaseAlert"> - <item name="windowBackground">@color/transparent</item> <item name="windowTitleStyle">@style/DialogWindowTitle.Material.Light</item> <item name="windowMinWidthMajor">@dimen/dialog_min_width_major</item> <item name="windowMinWidthMinor">@dimen/dialog_min_width_minor</item> @@ -1218,6 +1220,7 @@ please see themes_device_defaults.xml. <style name="Theme.Material.Light.Dialog.BaseTimePicker"> <item name="windowBackground">@color/transparent</item> + <item name="windowElevation">0dp</item> <item name="windowTitleStyle">@style/DialogWindowTitle.Material.Light</item> </style> diff --git a/graphics/java/android/graphics/drawable/AnimatedVectorDrawable.java b/graphics/java/android/graphics/drawable/AnimatedVectorDrawable.java index 1cecef329867..b5fc62895946 100644 --- a/graphics/java/android/graphics/drawable/AnimatedVectorDrawable.java +++ b/graphics/java/android/graphics/drawable/AnimatedVectorDrawable.java @@ -209,6 +209,7 @@ public class AnimatedVectorDrawable extends Drawable implements Animatable { if (drawableRes != 0) { mAnimatedVectorState.mVectorDrawable = (VectorDrawable) res.getDrawable( drawableRes, theme).mutate(); + mAnimatedVectorState.mVectorDrawable.setAllowCaching(false); } a.recycle(); } else if (TARGET.equals(tagName)) { @@ -258,6 +259,7 @@ public class AnimatedVectorDrawable extends Drawable implements Animatable { mChangingConfigurations = copy.mChangingConfigurations; // TODO: Make sure the constant state are handled correctly. mVectorDrawable = new VectorDrawable(); + mVectorDrawable.setAllowCaching(false); mAnimators = new ArrayList<Animator>(); } } diff --git a/graphics/java/android/graphics/drawable/VectorDrawable.java b/graphics/java/android/graphics/drawable/VectorDrawable.java index 8783994cf7ed..8c907b2b4c84 100644 --- a/graphics/java/android/graphics/drawable/VectorDrawable.java +++ b/graphics/java/android/graphics/drawable/VectorDrawable.java @@ -18,6 +18,7 @@ import android.content.res.ColorStateList; import android.content.res.Resources; import android.content.res.Resources.Theme; import android.content.res.TypedArray; +import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.ColorFilter; @@ -142,6 +143,10 @@ public class VectorDrawable extends Drawable { private boolean mMutated; + // AnimatedVectorDrawable needs to turn off the cache all the time, otherwise, + // caching the bitmap by default is allowed. + private boolean mAllowCaching = true; + public VectorDrawable() { mVectorState = new VectorDrawableState(); } @@ -183,7 +188,23 @@ public class VectorDrawable extends Drawable { final int saveCount = canvas.save(); final Rect bounds = getBounds(); canvas.translate(bounds.left, bounds.top); - mVectorState.mVPathRenderer.draw(canvas, bounds.width(), bounds.height()); + + if (!mAllowCaching) { + mVectorState.mVPathRenderer.draw(canvas, bounds.width(), bounds.height()); + } else { + Bitmap bitmap = mVectorState.mCachedBitmap; + if (bitmap == null || !mVectorState.canReuseCache(bounds.width(), + bounds.height())) { + bitmap = Bitmap.createBitmap(bounds.width(), bounds.height(), + Bitmap.Config.ARGB_8888); + Canvas tmpCanvas = new Canvas(bitmap); + mVectorState.mVPathRenderer.draw(tmpCanvas, bounds.width(), bounds.height()); + mVectorState.mCachedBitmap = bitmap; + + mVectorState.updateCacheStates(); + } + canvas.drawBitmap(bitmap, null, bounds, null); + } canvas.restoreToCount(saveCount); } @@ -444,6 +465,10 @@ public class VectorDrawable extends Drawable { return super.getChangingConfigurations() | mVectorState.mChangingConfigurations; } + void setAllowCaching(boolean allowCaching) { + mAllowCaching = allowCaching; + } + private static class VectorDrawableState extends ConstantState { int[] mThemeAttrs; int mChangingConfigurations; @@ -451,6 +476,12 @@ public class VectorDrawable extends Drawable { ColorStateList mTint; Mode mTintMode; + Bitmap mCachedBitmap; + int[] mCachedThemeAttrs; + ColorStateList mCachedTint; + Mode mCachedTintMode; + int mCachedRootAlpha; + // Deep copy for mutate() or implicitly mutate. public VectorDrawableState(VectorDrawableState copy) { if (copy != null) { @@ -462,6 +493,27 @@ public class VectorDrawable extends Drawable { } } + public boolean canReuseCache(int width, int height) { + if (mCachedThemeAttrs == mThemeAttrs + && mCachedTint == mTint + && mCachedTintMode == mTintMode + && width == mCachedBitmap.getWidth() + && height == mCachedBitmap.getHeight() + && mCachedRootAlpha == mVPathRenderer.getRootAlpha()) { + return true; + } + return false; + } + + public void updateCacheStates() { + // Use shallow copy here and shallow comparison in canReuseCache(), + // likely hit cache miss more, but practically not much difference. + mCachedThemeAttrs = mThemeAttrs; + mCachedTint = mTint; + mCachedTintMode = mTintMode; + mCachedRootAlpha = mVPathRenderer.getRootAlpha(); + } + public VectorDrawableState() { mVPathRenderer = new VPathRenderer(); } diff --git a/policy/src/com/android/internal/policy/impl/PhoneWindow.java b/policy/src/com/android/internal/policy/impl/PhoneWindow.java index 2e7d33195b3a..f431fdb2b9ab 100644 --- a/policy/src/com/android/internal/policy/impl/PhoneWindow.java +++ b/policy/src/com/android/internal/policy/impl/PhoneWindow.java @@ -208,6 +208,8 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { private Drawable mBackgroundDrawable; + private float mElevation; + private int mFrameResource = 0; private int mTextColor = 0; @@ -3251,6 +3253,7 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { + Integer.toHexString(mFrameResource)); } } + mElevation = a.getDimension(com.android.internal.R.styleable.Window_windowElevation, 0); mTextColor = a.getColor(com.android.internal.R.styleable.Window_textColor, 0xFF000000); } @@ -3340,28 +3343,31 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { // Remaining setup -- of background and title -- that only applies // to top-level windows. if (getContainer() == null) { - Drawable drawable = mBackgroundDrawable; + final Drawable background; if (mBackgroundResource != 0) { - drawable = getContext().getDrawable(mBackgroundResource); + background = getContext().getDrawable(mBackgroundResource); + } else { + background = mBackgroundDrawable; } - mDecor.setWindowBackground(drawable); - drawable = null; + mDecor.setWindowBackground(background); + + final Drawable frame; if (mFrameResource != 0) { - drawable = getContext().getDrawable(mFrameResource); + frame = getContext().getDrawable(mFrameResource); + } else { + frame = null; } - mDecor.setWindowFrame(drawable); - - // System.out.println("Text=" + Integer.toHexString(mTextColor) + - // " Sel=" + Integer.toHexString(mTextSelectedColor) + - // " Title=" + Integer.toHexString(mTitleColor)); + mDecor.setWindowFrame(frame); - if (mTitleColor == 0) { - mTitleColor = mTextColor; - } + mDecor.setElevation(mElevation); if (mTitle != null) { setTitle(mTitle); } + + if (mTitleColor == 0) { + mTitleColor = mTextColor; + } setTitleColor(mTitleColor); } diff --git a/services/backup/java/com/android/server/backup/BackupManagerService.java b/services/backup/java/com/android/server/backup/BackupManagerService.java index 5bfde4dbaadb..c3a9dbe94331 100644 --- a/services/backup/java/com/android/server/backup/BackupManagerService.java +++ b/services/backup/java/com/android/server/backup/BackupManagerService.java @@ -3922,6 +3922,11 @@ public class BackupManagerService extends IBackupManager.Stub { break; } + // Is it a *file* we need to drop? + if (!isRestorableFile(info)) { + okay = false; + } + // If the policy is satisfied, go ahead and set up to pipe the // data to the agent. if (DEBUG && okay && mAgent != null) { @@ -4082,9 +4087,9 @@ public class BackupManagerService extends IBackupManager.Stub { } } - // Problems setting up the agent communication, or an already- - // ignored package: skip to the next tar stream entry by - // reading and discarding this file. + // Problems setting up the agent communication, an explicitly + // dropped file, or an already-ignored package: skip to the + // next stream entry by reading and discarding this file. if (!okay) { if (DEBUG) Slog.d(TAG, "[discarding file content]"); long bytesToConsume = (info.size + 511) & ~511; @@ -4691,6 +4696,31 @@ public class BackupManagerService extends IBackupManager.Stub { return info; } + private boolean isRestorableFile(FileMetadata info) { + if (FullBackup.CACHE_TREE_TOKEN.equals(info.domain)) { + if (MORE_DEBUG) { + Slog.i(TAG, "Dropping cache file path " + info.path); + } + return false; + } + + if (FullBackup.ROOT_TREE_TOKEN.equals(info.domain)) { + // It's possible this is "no-backup" dir contents in an archive stream + // produced on a device running a version of the OS that predates that + // API. Respect the no-backup intention and don't let the data get to + // the app. + if (info.path.startsWith("no_backup/")) { + if (MORE_DEBUG) { + Slog.i(TAG, "Dropping no_backup file path " + info.path); + } + return false; + } + } + + // Otherwise we think this file is good to go + return true; + } + private void HEXLOG(byte[] block) { int offset = 0; int todo = block.length; diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java index 8387b6522cee..b24072f54f35 100644 --- a/services/core/java/com/android/server/wm/WindowStateAnimator.java +++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java @@ -717,10 +717,10 @@ class WindowStateAnimator { float top = w.mFrame.top + w.mYOffset; // Adjust for surface insets. - width += attrs.shadowInsets.left + attrs.shadowInsets.right; - height += attrs.shadowInsets.top + attrs.shadowInsets.bottom; - left -= attrs.shadowInsets.left; - top -= attrs.shadowInsets.top; + width += attrs.surfaceInsets.left + attrs.surfaceInsets.right; + height += attrs.surfaceInsets.top + attrs.surfaceInsets.bottom; + left -= attrs.surfaceInsets.left; + top -= attrs.surfaceInsets.top; if (DEBUG_VISIBILITY) { Slog.v(TAG, "Creating surface in session " @@ -1140,19 +1140,12 @@ class WindowStateAnimator { void applyDecorRect(final Rect decorRect) { final WindowState w = mWin; - int width = w.mFrame.width(); - int height = w.mFrame.height(); + final int width = w.mFrame.width(); + final int height = w.mFrame.height(); // Compute the offset of the window in relation to the decor rect. - int left = w.mXOffset + w.mFrame.left; - int top = w.mYOffset + w.mFrame.top; - - // Adjust for surface insets. - final WindowManager.LayoutParams attrs = w.mAttrs; - width += attrs.shadowInsets.left + attrs.shadowInsets.right; - height += attrs.shadowInsets.top + attrs.shadowInsets.bottom; - left -= attrs.shadowInsets.left; - top -= attrs.shadowInsets.top; + final int left = w.mXOffset + w.mFrame.left; + final int top = w.mYOffset + w.mFrame.top; // Initialize the decor rect to the entire frame. w.mSystemDecorRect.set(0, 0, width, height); @@ -1182,7 +1175,6 @@ class WindowStateAnimator { if (displayContent == null) { return; } - DisplayInfo displayInfo = displayContent.getDisplayInfo(); // Need to recompute a new system decor rect each time. if ((w.mAttrs.flags & LayoutParams.FLAG_SCALED) != 0) { @@ -1192,6 +1184,7 @@ class WindowStateAnimator { } else if (!w.isDefaultDisplay()) { // On a different display there is no system decor. Crop the window // by the screen boundaries. + final DisplayInfo displayInfo = displayContent.getDisplayInfo(); w.mSystemDecorRect.set(0, 0, w.mCompatFrame.width(), w.mCompatFrame.height()); w.mSystemDecorRect.intersect(-w.mCompatFrame.left, -w.mCompatFrame.top, displayInfo.logicalWidth - w.mCompatFrame.left, @@ -1202,44 +1195,52 @@ class WindowStateAnimator { // windows need to be cropped by the screen, so they don't cover // the universe background. if (mAnimator.mUniverseBackground == null) { - w.mSystemDecorRect.set(0, 0, w.mCompatFrame.width(), - w.mCompatFrame.height()); + w.mSystemDecorRect.set(0, 0, w.mCompatFrame.width(), w.mCompatFrame.height()); } else { applyDecorRect(mService.mScreenRect); } } else if (w.mAttrs.type == WindowManager.LayoutParams.TYPE_UNIVERSE_BACKGROUND || w.mDecorFrame.isEmpty()) { // The universe background isn't cropped, nor windows without policy decor. - w.mSystemDecorRect.set(0, 0, w.mCompatFrame.width(), - w.mCompatFrame.height()); + w.mSystemDecorRect.set(0, 0, w.mCompatFrame.width(), w.mCompatFrame.height()); } else { // Crop to the system decor specified by policy. applyDecorRect(w.mDecorFrame); } - // By default, the clip rect is the system decor rect - Rect clipRect = w.mSystemDecorRect; - if (mHasClipRect) { + // By default, the clip rect is the system decor. + final Rect clipRect = mTmpClipRect; + clipRect.set(w.mSystemDecorRect); - // If we have an animated clip rect, intersect it with the system decor rect - // NOTE: We are adding a temporary workaround due to the status bar not always reporting - // the correct system decor rect. In such cases, we take into account the specified - // content insets as well. - int offsetTop = Math.max(w.mSystemDecorRect.top, w.mContentInsets.top); - mTmpClipRect.set(w.mSystemDecorRect); - // Don't apply the workaround to apps explicitly requesting fullscreen layout. + // Expand the clip rect for surface insets. + final WindowManager.LayoutParams attrs = w.mAttrs; + clipRect.left -= attrs.surfaceInsets.left; + clipRect.top -= attrs.surfaceInsets.top; + clipRect.right += attrs.surfaceInsets.right; + clipRect.bottom += attrs.surfaceInsets.bottom; + + // If we have an animated clip rect, intersect it with the clip rect. + if (mHasClipRect) { + // NOTE: We are adding a temporary workaround due to the status bar + // not always reporting the correct system decor rect. In such + // cases, we take into account the specified content insets as well. if ((w.mSystemUiVisibility & SYSTEM_UI_FLAGS_LAYOUT_STABLE_FULLSCREEN) == SYSTEM_UI_FLAGS_LAYOUT_STABLE_FULLSCREEN) { - mTmpClipRect.intersect(mClipRect); + // Don't apply the workaround to apps explicitly requesting + // fullscreen layout. + clipRect.intersect(mClipRect); } else { - mTmpClipRect.offset(0, -offsetTop); - mTmpClipRect.intersect(mClipRect); - mTmpClipRect.offset(0, offsetTop); + final int offsetTop = Math.max(clipRect.top, w.mContentInsets.top); + clipRect.offset(0, -offsetTop); + clipRect.intersect(mClipRect); + clipRect.offset(0, offsetTop); } - clipRect = mTmpClipRect; - } + // The clip rect was generated assuming (0,0) as the window origin, + // so we need to translate to match the actual surface coordinates. + clipRect.offset(attrs.surfaceInsets.left, attrs.surfaceInsets.top); + if (!clipRect.equals(mLastClipRect)) { mLastClipRect.set(clipRect); try { @@ -1285,10 +1286,10 @@ class WindowStateAnimator { // Adjust for surface insets. final LayoutParams attrs = w.getAttrs(); - width += attrs.shadowInsets.left + attrs.shadowInsets.right; - height += attrs.shadowInsets.top + attrs.shadowInsets.bottom; - left -= attrs.shadowInsets.left; - top -= attrs.shadowInsets.top; + width += attrs.surfaceInsets.left + attrs.surfaceInsets.right; + height += attrs.surfaceInsets.top + attrs.surfaceInsets.bottom; + left -= attrs.surfaceInsets.left; + top -= attrs.surfaceInsets.top; final boolean surfaceMoved = mSurfaceX != left || mSurfaceY != top; if (surfaceMoved) { diff --git a/telecomm/java/android/telecomm/Call.java b/telecomm/java/android/telecomm/Call.java new file mode 100644 index 000000000000..ba7e253fc824 --- /dev/null +++ b/telecomm/java/android/telecomm/Call.java @@ -0,0 +1,710 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.telecomm; + +import android.net.Uri; +import android.os.RemoteException; +import android.telephony.DisconnectCause; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Objects; + +/** + * Represents an ongoing phone call that the in-call app should present to the user. + */ +public final class Call { + /** + * The state of a {@code Call} when newly created. + */ + public static final int STATE_NEW = 0; + + /** + * The state of an outgoing {@code Call} when dialing the remote number, but not yet connected. + */ + public static final int STATE_DIALING = 1; + + /** + * The state of an incoming {@code Call} when ringing locally, but not yet connected. + */ + public static final int STATE_RINGING = 2; + + /** + * The state of a {@code Call} when in a holding state. + */ + public static final int STATE_HOLDING = 3; + + /** + * The state of a {@code Call} when actively supporting conversation. + */ + public static final int STATE_ACTIVE = 4; + + /** + * The state of a {@code Call} when no further voice or other communication is being + * transmitted, the remote side has been or will inevitably be informed that the {@code Call} + * is no longer active, and the local data transport has or inevitably will release resources + * associated with this {@code Call}. + */ + public static final int STATE_DISCONNECTED = 7; + + public static class Details { + private final Uri mHandle; + private final int mHandlePresentation; + private final String mCallerDisplayName; + private final int mCallerDisplayNamePresentation; + private final PhoneAccount mAccount; + private final int mCapabilities; + private final int mDisconnectCauseCode; + private final String mDisconnectCauseMsg; + private final long mConnectTimeMillis; + private final GatewayInfo mGatewayInfo; + + /** + * @return The handle (e.g., phone number) to which the {@code Call} is currently + * connected. + */ + public Uri getHandle() { + return mHandle; + } + + /** + * @return The presentation requirements for the handle. See + * {@link android.telecomm.CallPropertyPresentation} for valid values. + */ + public int getHandlePresentation() { + return mHandlePresentation; + } + + /** + * @return The display name for the caller. + */ + public String getCallerDisplayName() { + return mCallerDisplayName; + } + + /** + * @return The presentation requirements for the caller display name. See + * {@link android.telecomm.CallPropertyPresentation} for valid values. + */ + public int getCallerDisplayNamePresentation() { + return mCallerDisplayNamePresentation; + } + + /** + * @return The {@code PhoneAccount} whereby the {@code Call} is currently being routed. + */ + public PhoneAccount getAccount() { + return mAccount; + } + + /** + * @return A bitmask of the capabilities of the {@code Call}, as defined in + * {@link CallCapabilities}. + */ + public int getCapabilities() { + return mCapabilities; + } + + /** + * @return For a {@link #STATE_DISCONNECTED} {@code Call}, the disconnect cause expressed + * as a code chosen from among those declared in {@link DisconnectCause}. + */ + public int getDisconnectCauseCode() { + return mDisconnectCauseCode; + } + + /** + * @return For a {@link #STATE_DISCONNECTED} {@code Call}, an optional reason for + * disconnection expressed as a free text message. + */ + public String getDisconnectCauseMsg() { + return mDisconnectCauseMsg; + } + + /** + * @return The time the {@code Call} has been connected. This information is updated + * periodically, but user interfaces should not rely on this to display any "call time + * clock". + */ + public long getConnectTimeMillis() { + return mConnectTimeMillis; + } + + /** + * @return Information about any calling gateway the {@code Call} may be using. + */ + public GatewayInfo getGatewayInfo() { + return mGatewayInfo; + } + + @Override + public boolean equals(Object o) { + if (o instanceof Details) { + Details d = (Details) o; + return + Objects.equals(mHandle, d.mHandle) && + Objects.equals(mHandlePresentation, d.mHandlePresentation) && + Objects.equals(mCallerDisplayName, d.mCallerDisplayName) && + Objects.equals(mCallerDisplayNamePresentation, + d.mCallerDisplayNamePresentation) && + Objects.equals(mAccount, d.mAccount) && + Objects.equals(mCapabilities, d.mCapabilities) && + Objects.equals(mDisconnectCauseCode, d.mDisconnectCauseCode) && + Objects.equals(mDisconnectCauseMsg, d.mDisconnectCauseMsg) && + Objects.equals(mConnectTimeMillis, d.mConnectTimeMillis) && + Objects.equals(mGatewayInfo, d.mGatewayInfo); + } + return false; + } + + @Override + public int hashCode() { + return + Objects.hashCode(mHandle) + + Objects.hashCode(mHandlePresentation) + + Objects.hashCode(mCallerDisplayName) + + Objects.hashCode(mCallerDisplayNamePresentation) + + Objects.hashCode(mAccount) + + Objects.hashCode(mCapabilities) + + Objects.hashCode(mDisconnectCauseCode) + + Objects.hashCode(mDisconnectCauseMsg) + + Objects.hashCode(mConnectTimeMillis) + + Objects.hashCode(mGatewayInfo); + } + + /** {@hide} */ + public Details( + Uri handle, + int handlePresentation, + String callerDisplayName, + int callerDisplayNamePresentation, + PhoneAccount account, + int capabilities, + int disconnectCauseCode, + String disconnectCauseMsg, + long connectTimeMillis, + GatewayInfo gatewayInfo) { + mHandle = handle; + mHandlePresentation = handlePresentation; + mCallerDisplayName = callerDisplayName; + mCallerDisplayNamePresentation = callerDisplayNamePresentation; + mAccount = account; + mCapabilities = capabilities; + mDisconnectCauseCode = disconnectCauseCode; + mDisconnectCauseMsg = disconnectCauseMsg; + mConnectTimeMillis = connectTimeMillis; + mGatewayInfo = gatewayInfo; + } + } + + public static abstract class Listener { + /** + * Invoked when the state of this {@code Call} has changed. See {@link #getState()}. + * + * TODO(ihab): Provide previous state also? + * + * @param call The {@code Call} invoking this method. + * @param state The new state of the {@code Call}. + */ + public void onStateChanged(Call call, int state) {} + + /** + * Invoked when the parent of this {@code Call} has changed. See {@link #getParent()}. + * + * @param call The {@code Call} invoking this method. + * @param parent The new parent of the {@code Call}. + */ + public void onParentChanged(Call call, Call parent) {} + + /** + * Invoked when the children of this {@code Call} have changed. See {@link #getChildren()}. + * + * @param call The {@code Call} invoking this method. + * @param children The new children of the {@code Call}. + */ + public void onChildrenChanged(Call call, List<Call> children) {} + + /** + * Invoked when the details of this {@code Call} have changed. See {@link #getDetails()}. + * + * @param call The {@code Call} invoking this method. + * @param details A {@code Details} object describing the {@code Call}. + */ + public void onDetailsChanged(Call call, Details details) {} + + /** + * Invoked when the text messages that can be used as responses to the incoming + * {@code Call} are loaded from the relevant database. + * See {@link #getCannedTextResponses()}. + * + * @param call The {@code Call} invoking this method. + * @param cannedTextResponses The text messages useable as responses. + */ + public void onCannedTextResponsesLoaded(Call call, List<String> cannedTextResponses) {} + + /** + * Invoked when the outgoing {@code Call} has finished dialing but is sending DTMF signals + * that were embedded into the outgoing number. + * + * @param call The {@code Call} invoking this method. + * @param remainingPostDialSequence The post-dial characters that remain to be sent. + */ + public void onPostDial(Call call, String remainingPostDialSequence) {} + + /** + * Invoked when the post-dial sequence in the outgoing {@code Call} has reached a pause + * character. This causes the post-dial signals to stop pending user confirmation. An + * implementation should present this choice to the user and invoke + * {@link #postDialContinue(boolean)} when the user makes the choice. + * + * @param call The {@code Call} invoking this method. + * @param remainingPostDialSequence The post-dial characters that remain to be sent. + */ + public void onPostDialWait(Call call, String remainingPostDialSequence) {} + + /** + * Invoked when the {@code RemoteCallVideoProvider} of the {@code Call} has changed. + * + * @param call The {@code Call} invoking this method. + * @param callVideoProvider The {@code RemoteCallVideoProvider} associated with the + * {@code Call}. + */ + + public void onCallVideoProviderChanged(Call call, + RemoteCallVideoProvider callVideoProvider) {} + + /** + * Invoked when the {@code Call} is destroyed. Clients should refrain from cleaning + * up their UI for the {@code Call} in response to state transitions. Specifically, + * clients should not assume that a {@link #onStateChanged(Call, int)} with a state of + * {@link #STATE_DISCONNECTED} is the final notification the {@code Call} will send. Rather, + * clients should wait for this method to be invoked. + * + * @param call The {@code Call} being destroyed. + */ + public void onCallDestroyed(Call call) {} + } + + private final Phone mPhone; + private final String mTelecommCallId; + private final InCallAdapter mInCallAdapter; + private Call mParent = null; + private int mState; + private final List<Call> mChildren = new ArrayList<>(); + private final List<Call> mUnmodifiableChildren = Collections.unmodifiableList(mChildren); + private List<String> mCannedTextResponses = null; + private String mRemainingPostDialSequence; + private RemoteCallVideoProvider mCallVideoProvider; + private Details mDetails; + private final List<Listener> mListeners = new ArrayList<>(); + + /** + * Obtains the post-dial sequence remaining to be emitted by this {@code Call}, if any. + * + * @return The remaining post-dial sequence, or {@code null} if there is no post-dial sequence + * remaining or this {@code Call} is not in a post-dial state. + */ + public String getRemainingPostDialSequence() { + return mRemainingPostDialSequence; + } + + /** + * Instructs this {@link #STATE_RINGING} {@code Call} to answer. + */ + public void answer() { + mInCallAdapter.answerCall(mTelecommCallId); + } + + /** + * Instructs this {@link #STATE_RINGING} {@code Call} to reject. + * + * @param rejectWithMessage Whether to reject with a text message. + * @param textMessage An optional text message with which to respond. + */ + public void reject(boolean rejectWithMessage, String textMessage) { + mInCallAdapter.rejectCall(mTelecommCallId, rejectWithMessage, textMessage); + } + + /** + * Instructs this {@code Call} to disconnect. + */ + public void disconnect() { + mInCallAdapter.disconnectCall(mTelecommCallId); + } + + /** + * Instructs this {@code Call} to go on hold. + */ + public void hold() { + mInCallAdapter.holdCall(mTelecommCallId); + } + + /** + * Instructs this {@link #STATE_HOLDING} call to release from hold. + */ + public void unhold() { + mInCallAdapter.unholdCall(mTelecommCallId); + } + + /** + * Instructs this {@code Call} to play a dual-tone multi-frequency signaling (DTMF) tone. + * + * Any other currently playing DTMF tone in the specified call is immediately stopped. + * + * @param digit A character representing the DTMF digit for which to play the tone. This + * value must be one of {@code '0'} through {@code '9'}, {@code '*'} or {@code '#'}. + */ + public void playDtmfTone(char digit) { + mInCallAdapter.playDtmfTone(mTelecommCallId, digit); + } + + /** + * Instructs this {@code Call} to stop any dual-tone multi-frequency signaling (DTMF) tone + * currently playing. + * + * DTMF tones are played by calling {@link #playDtmfTone(char)}. If no DTMF tone is + * currently playing, this method will do nothing. + */ + public void stopDtmfTone() { + mInCallAdapter.stopDtmfTone(mTelecommCallId); + } + + /** + * Instructs this {@code Call} to continue playing a post-dial DTMF string. + * + * A post-dial DTMF string is a string of digits entered after a phone number, when dialed, + * that are immediately sent as DTMF tones to the recipient as soon as the connection is made. + * While these tones are playing, this {@code Call} will notify listeners via + * {@link Listener#onPostDial(Call, String)}. + * + * If the DTMF string contains a {@link TelecommConstants#DTMF_CHARACTER_PAUSE} symbol, this + * {@code Call} will temporarily pause playing the tones for a pre-defined period of time. + * + * If the DTMF string contains a {@link TelecommConstants#DTMF_CHARACTER_WAIT} symbol, this + * {@code Call} will pause playing the tones and notify listeners via + * {@link Listener#onPostDialWait(Call, String)}. At this point, the in-call app + * should display to the user an indication of this state and an affordance to continue + * the postdial sequence. When the user decides to continue the postdial sequence, the in-call + * app should invoke the {@link #postDialContinue(boolean)} method. + * + * @param proceed Whether or not to continue with the post-dial sequence. + */ + public void postDialContinue(boolean proceed) { + mInCallAdapter.postDialContinue(mTelecommCallId, proceed); + } + + /** + * Notifies this {@code Call} that the phone account user interface element was touched. + * + * TODO(ihab): Figure out if and how we can generalize this + */ + public void phoneAccountClicked() { + mInCallAdapter.phoneAccountClicked(mTelecommCallId); + } + + /** + * Instructs this {@code Call} to enter a conference. + */ + public void conference() { + mInCallAdapter.conference(mTelecommCallId); + } + + /** + * Instructs this {@code Call} to split from any conference call with which it may be + * connected. + */ + public void splitFromConference() { + mInCallAdapter.splitFromConference(mTelecommCallId); + } + + /** + * Instructs this {@code Call} to swap itself with an existing background call, if one + * such call exists. + */ + public void swapWithBackgroundCall() { + mInCallAdapter.swapWithBackgroundCall(mTelecommCallId); + } + + /** + * Obtains the parent of this {@code Call} in a conference, if any. + * + * @return The parent {@code Call}, or {@code null} if this {@code Call} is not a + * child of any conference {@code Call}s. + */ + public Call getParent() { + return mParent; + } + + /** + * Obtains the children of this conference {@code Call}, if any. + * + * @return The children of this {@code Call} if this {@code Call} is a conference, or an empty + * {@code List} otherwise. + */ + public List<Call> getChildren() { + return mUnmodifiableChildren; + } + + /** + * Obtains the state of this {@code Call}. + * + * @return A state value, chosen from the {@code STATE_*} constants. + */ + public int getState() { + return mState; + } + + /** + * Obtains a list of canned, pre-configured message responses to present to the user as + * ways of rejecting this {@code Call} using via a text message. + * + * @see #reject(boolean, String) + * + * @return A list of canned text message responses. + */ + public List<String> getCannedTextResponses() { + return mCannedTextResponses; + } + + /** + * Obtains an object that can be used to display video from this {@code Call}. + * + * @return An {@code ICallVideoProvider}. + */ + public RemoteCallVideoProvider getCallVideoProvider() { + return mCallVideoProvider; + } + + /** + * Obtains an object containing call details. + * + * @return A {@link Details} object. Depending on the state of the {@code Call}, the + * result may be {@code null}. + */ + public Details getDetails() { + return mDetails; + } + + /** + * Adds a listener to this {@code Call}. + * + * @param listener A {@code Listener}. + */ + public void addListener(Listener listener) { + mListeners.add(listener); + } + + /** + * Removes a listener from this {@code Call}. + * + * @param listener A {@code Listener}. + */ + public void removeListener(Listener listener) { + mListeners.remove(listener); + } + + /** {@hide} */ + Call(Phone phone, String telecommCallId, InCallAdapter inCallAdapter) { + mPhone = phone; + mTelecommCallId = telecommCallId; + mInCallAdapter = inCallAdapter; + mState = STATE_NEW; + } + + /** {@hide} */ + final String internalGetCallId() { + return mTelecommCallId; + } + + /** {@hide} */ + final void internalUpdate(InCallCall inCallCall) { + // First, we update the internal state as far as possible before firing any updates. + + Details details = new Details( + inCallCall.getHandle(), + inCallCall.getHandlePresentation(), + inCallCall.getCallerDisplayName(), + inCallCall.getCallerDisplayNamePresentation(), + inCallCall.getAccount(), + inCallCall.getCapabilities(), + inCallCall.getDisconnectCauseCode(), + inCallCall.getDisconnectCauseMsg(), + inCallCall.getConnectTimeMillis(), + inCallCall.getGatewayInfo()); + boolean detailsChanged = !Objects.equals(mDetails, details); + if (detailsChanged) { + mDetails = details; + } + + boolean cannedTextResponsesChanged = false; + if (mCannedTextResponses == null && inCallCall.getCannedSmsResponses() != null + && !inCallCall.getCannedSmsResponses().isEmpty()) { + mCannedTextResponses = Collections.unmodifiableList(inCallCall.getCannedSmsResponses()); + } + + boolean callVideoProviderChanged = false; + try { + callVideoProviderChanged = + !Objects.equals(mCallVideoProvider, inCallCall.getCallVideoProvider()); + if (callVideoProviderChanged) { + mCallVideoProvider = inCallCall.getCallVideoProvider(); + } + } catch (RemoteException e) { + } + + int state = stateFromInCallCallState(inCallCall.getState()); + boolean stateChanged = mState != state; + if (stateChanged) { + mState = state; + } + + if (inCallCall.getParentCallId() != null) { + mParent = mPhone.internalGetCallByTelecommId(inCallCall.getParentCallId()); + } + + mChildren.clear(); + if (inCallCall.getChildCallIds() != null) { + for (int i = 0; i < inCallCall.getChildCallIds().size(); i++) { + mChildren.add(mPhone.internalGetCallByTelecommId( + inCallCall.getChildCallIds().get(i))); + } + } + + // Now we fire updates, ensuring that any client who listens to any of these notifications + // gets the most up-to-date state. + + if (stateChanged) { + fireStateChanged(mState); + } + if (detailsChanged) { + fireDetailsChanged(mDetails); + } + if (cannedTextResponsesChanged) { + fireCannedTextResponsesLoaded(mCannedTextResponses); + } + if (callVideoProviderChanged) { + fireCallVideoProviderChanged(mCallVideoProvider); + } + + // If we have transitioned to DISCONNECTED, that means we need to notify clients and + // remove ourselves from the Phone. Note that we do this after completing all state updates + // so a client can cleanly transition all their UI to the state appropriate for a + // DISCONNECTED Call while still relying on the existence of that Call in the Phone's list. + if (mState == STATE_DISCONNECTED) { + fireCallDestroyed(); + mPhone.internalRemoveCall(this); + } + } + + /** {@hide} */ + final void internalSetPostDial(String remaining) { + mRemainingPostDialSequence = remaining; + firePostDial(mRemainingPostDialSequence); + } + + /** {@hide} */ + final void internalSetPostDialWait(String remaining) { + mRemainingPostDialSequence = remaining; + firePostDialWait(mRemainingPostDialSequence); + } + + private void fireStateChanged(int newState) { + Listener[] listeners = mListeners.toArray(new Listener[mListeners.size()]); + for (int i = 0; i < listeners.length; i++) { + listeners[i].onStateChanged(this, newState); + } + } + + private void fireParentChanged(Call newParent) { + Listener[] listeners = mListeners.toArray(new Listener[mListeners.size()]); + for (int i = 0; i < listeners.length; i++) { + listeners[i].onParentChanged(this, newParent); + } + } + + private void fireChildrenChanged(List<Call> children) { + Listener[] listeners = mListeners.toArray(new Listener[mListeners.size()]); + for (int i = 0; i < listeners.length; i++) { + listeners[i].onChildrenChanged(this, children); + } + } + + private void fireDetailsChanged(Details details) { + Listener[] listeners = mListeners.toArray(new Listener[mListeners.size()]); + for (int i = 0; i < listeners.length; i++) { + listeners[i].onDetailsChanged(this, details); + } + } + + private void fireCannedTextResponsesLoaded(List<String> cannedTextResponses) { + Listener[] listeners = mListeners.toArray(new Listener[mListeners.size()]); + for (int i = 0; i < listeners.length; i++) { + listeners[i].onCannedTextResponsesLoaded(this, cannedTextResponses); + } + } + + private void fireCallVideoProviderChanged(RemoteCallVideoProvider callVideoProvider) { + Listener[] listeners = mListeners.toArray(new Listener[mListeners.size()]); + for (int i = 0; i < listeners.length; i++) { + listeners[i].onCallVideoProviderChanged(this, callVideoProvider); + } + } + + private void firePostDial(String remainingPostDialSequence) { + Listener[] listeners = mListeners.toArray(new Listener[mListeners.size()]); + for (int i = 0; i < listeners.length; i++) { + listeners[i].onPostDial(this, remainingPostDialSequence); + } + } + + private void firePostDialWait(String remainingPostDialSequence) { + Listener[] listeners = mListeners.toArray(new Listener[mListeners.size()]); + for (int i = 0; i < listeners.length; i++) { + listeners[i].onPostDialWait(this, remainingPostDialSequence); + } + } + + private void fireCallDestroyed() { + Listener[] listeners = mListeners.toArray(new Listener[mListeners.size()]); + for (int i = 0; i < listeners.length; i++) { + listeners[i].onCallDestroyed(this); + } + } + + private int stateFromInCallCallState(CallState inCallCallState) { + switch (inCallCallState) { + case NEW: + return STATE_NEW; + case DIALING: + return STATE_DIALING; + case RINGING: + return STATE_RINGING; + case ACTIVE: + return STATE_ACTIVE; + case ON_HOLD: + return STATE_HOLDING; + case DISCONNECTED: + return STATE_DISCONNECTED; + case ABORTED: + return STATE_DISCONNECTED; + default: + Log.wtf(this, "Unrecognized CallState %s", inCallCallState); + return STATE_NEW; + } + } +} diff --git a/telecomm/java/android/telecomm/CallState.java b/telecomm/java/android/telecomm/CallState.java index 152c2023e7fd..a464da563105 100644 --- a/telecomm/java/android/telecomm/CallState.java +++ b/telecomm/java/android/telecomm/CallState.java @@ -48,22 +48,6 @@ public enum CallState { RINGING, /** - * Indicates that the call is active but in a "post-dial" state where Telecomm is now sending - * some dual-tone multi-frequency signaling (DTMF) tones appended to the dialed number. Normal - * transitions are to {@link #POST_DIAL_WAIT} when the post-dial string requires user - * confirmation to proceed, {@link #ACTIVE} when the post-dial tones are completed, or - * {@link #DISCONNECTED}. - */ - POST_DIAL, - - /** - * Indicates that the call was in the {@link #POST_DIAL} state but is now waiting for user - * confirmation before the remaining digits can be sent. Normal transitions are to - * {@link #POST_DIAL} when the user asks Telecomm to proceed with the post-dial sequence. - */ - POST_DIAL_WAIT, - - /** * Indicates that a call is currently connected to another party and a communication channel is * open between them. The normal transition to this state is by the user answering a * {@link #DIALING} call or a {@link #RINGING} call being answered by the other party. diff --git a/telecomm/java/android/telecomm/CallVideoClient.java b/telecomm/java/android/telecomm/CallVideoClient.java index 76b28fad2d07..fb970dc14a35 100644 --- a/telecomm/java/android/telecomm/CallVideoClient.java +++ b/telecomm/java/android/telecomm/CallVideoClient.java @@ -241,6 +241,7 @@ public abstract class CallVideoClient { * * @param callCameraCapabilities The changed camera capabilities. */ - public abstract void onHandleCameraCapabilitiesChange(CallCameraCapabilities callCameraCapabilities); + public abstract void onHandleCameraCapabilitiesChange( + CallCameraCapabilities callCameraCapabilities); } diff --git a/telecomm/java/android/telecomm/InCallAdapter.java b/telecomm/java/android/telecomm/InCallAdapter.java index d8293a5cdbe4..66cf1df98b1c 100644 --- a/telecomm/java/android/telecomm/InCallAdapter.java +++ b/telecomm/java/android/telecomm/InCallAdapter.java @@ -24,7 +24,7 @@ import com.android.internal.telecomm.IInCallAdapter; * Receives commands from {@link InCallService} implementations which should be executed by * Telecomm. When Telecomm binds to a {@link InCallService}, an instance of this class is given to * the in-call service through which it can manipulate live (active, dialing, ringing) calls. When - * the in-call service is notified of new calls ({@link InCallService#addCall}), it can use the + * the in-call service is notified of new calls, it can use the * given call IDs to execute commands such as {@link #answerCall} for incoming calls or * {@link #disconnectCall} for active calls the user would like to end. Some commands are only * appropriate for calls in certain states; please consult each method for such limitations. @@ -167,16 +167,15 @@ public final class InCallAdapter { * A post-dial DTMF string is a string of digits entered after a phone number, when dialed, * that are immediately sent as DTMF tones to the recipient as soon as the connection is made. * While these tones are playing, Telecomm will notify the {@link InCallService} that the call - * is in the {@link InCallService#setPostDial(String,String)} state. + * is in the post dial state. * * If the DTMF string contains a {@link TelecommConstants#DTMF_CHARACTER_PAUSE} symbol, Telecomm * will temporarily pause playing the tones for a pre-defined period of time. * * If the DTMF string contains a {@link TelecommConstants#DTMF_CHARACTER_WAIT} symbol, Telecomm * will pause playing the tones and notify the {@link InCallService} that the call is in the - * {@link InCallService#setPostDialWait(String,String)} state. When the user decides to continue - * the postdial sequence, the {@link InCallService} should invoke the - * {@link #postDialContinue(String,boolean)} method. + * post dial wait state. When the user decides to continue the postdial sequence, the + * {@link InCallService} should invoke the {@link #postDialContinue(String,boolean)} method. * * @param callId The unique ID of the call for which postdial string playing should continue. * @param proceed Whether or not to continue with the post-dial sequence. diff --git a/telecomm/java/android/telecomm/InCallService.java b/telecomm/java/android/telecomm/InCallService.java index 31291fba7ec7..028b6e49e34e 100644 --- a/telecomm/java/android/telecomm/InCallService.java +++ b/telecomm/java/android/telecomm/InCallService.java @@ -16,8 +16,6 @@ package android.telecomm; -import android.app.Service; -import android.content.Intent; import android.os.Handler; import android.os.IBinder; import android.os.Looper; @@ -31,11 +29,10 @@ import com.android.internal.telecomm.IInCallService; * This service is implemented by any app that wishes to provide the user-interface for managing * phone calls. Telecomm binds to this service while there exists a live (active or incoming) * call, and uses it to notify the in-call app of any live and and recently disconnected calls. - * TODO(santoscordon): Needs more/better description of lifecycle once the interface is better - * defined. + * * TODO(santoscordon): What happens if two or more apps on a given device implement this interface? */ -public abstract class InCallService extends Service { +public abstract class InCallService { private static final int MSG_SET_IN_CALL_ADAPTER = 1; private static final int MSG_ADD_CALL = 2; private static final int MSG_UPDATE_CALL = 3; @@ -50,21 +47,21 @@ public abstract class InCallService extends Service { public void handleMessage(Message msg) { switch (msg.what) { case MSG_SET_IN_CALL_ADAPTER: - mAdapter = new InCallAdapter((IInCallAdapter) msg.obj); - onAdapterAttached(mAdapter); + mPhone = new Phone(new InCallAdapter((IInCallAdapter) msg.obj)); + onPhoneCreated(mPhone); break; case MSG_ADD_CALL: - addCall((InCallCall) msg.obj); + mPhone.internalAddCall((InCallCall) msg.obj); break; case MSG_UPDATE_CALL: - updateCall((InCallCall) msg.obj); + mPhone.internalUpdateCall((InCallCall) msg.obj); break; - case MSG_SET_POST_DIAL: { + case MSG_SET_POST_DIAL: { SomeArgs args = (SomeArgs) msg.obj; try { String callId = (String) args.arg1; String remaining = (String) args.arg2; - setPostDial(callId, remaining); + mPhone.internalSetPostDial(callId, remaining); } finally { args.recycle(); } @@ -75,17 +72,17 @@ public abstract class InCallService extends Service { try { String callId = (String) args.arg1; String remaining = (String) args.arg2; - setPostDialWait(callId, remaining); + mPhone.internalSetPostDialWait(callId, remaining); } finally { args.recycle(); } break; } case MSG_ON_AUDIO_STATE_CHANGED: - onAudioStateChanged((CallAudioState) msg.obj); + mPhone.internalAudioStateChanged((CallAudioState) msg.obj); break; case MSG_BRING_TO_FOREGROUND: - bringToForeground(msg.arg1 == 1); + mPhone.internalBringToForeground(msg.arg1 == 1); break; default: break; @@ -142,85 +139,41 @@ public abstract class InCallService extends Service { } } - private final InCallServiceBinder mBinder; - - private InCallAdapter mAdapter; - - protected InCallService() { - mBinder = new InCallServiceBinder(); - } + private Phone mPhone; - @Override - public final IBinder onBind(Intent intent) { - return mBinder; - } + protected InCallService() {} - /** - * @return The attached {@link InCallAdapter} if attached, or null otherwise. - */ - protected final InCallAdapter getAdapter() { - return mAdapter; + public final IBinder getBinder() { + return new InCallServiceBinder(); } /** - * Lifecycle callback which is called when this {@link InCallService} has been attached - * to a {@link InCallAdapter}, indicating {@link #getAdapter()} is now safe to use. + * Obtain the {@code Phone} associated with this {@code InCallService}. * - * @param adapter The adapter now attached to this in-call service. + * @return The {@code Phone} object associated with this {@code InCallService}, or {@code null} + * if the {@code InCallService} is not in a state where it has an associated {@code Phone}. */ - protected void onAdapterAttached(InCallAdapter adapter) { + public Phone getPhone() { + return mPhone; } /** - * Indicates to the in-call app that a new call has been created and an appropriate - * user-interface should be built and shown to notify the user. - * - * @param call Information about the new call. - */ - protected abstract void addCall(InCallCall call); - - /** - * Call when information about a call has changed. - * - * @param call Information about the new call. - */ - protected abstract void updateCall(InCallCall call); - - /** - * Indicates to the in-call app that the specified call is active but in a "post-dial" state - * where Telecomm is now sending some dual-tone multi-frequency signaling (DTMF) tones appended - * to the dialed number. Normal transitions are to {@link #setPostDialWait(String,String)} when - * the post-dial string requires user confirmation to proceed, and {@link CallState#ACTIVE} when - * the post-dial tones are completed. - * - * @param callId The identifier of the call changing state. - * @param remaining The remaining postdial string to be dialed. - */ - protected abstract void setPostDial(String callId, String remaining); - - /** - * Indicates to the in-call app that the specified call was in the - * {@link #setPostDial(String,String)} state but is now waiting for user confirmation before the - * remaining digits can be sent. Normal transitions are to {@link #setPostDial(String,String)} - * when the user asks Telecomm to proceed with the post-dial sequence and the in-call app - * informs Telecomm of this by invoking {@link InCallAdapter#postDialContinue(String,boolean)}. - * - * @param callId The identifier of the call changing state. - * @param remaining The remaining postdial string to be dialed. - */ - protected abstract void setPostDialWait(String callId, String remaining); - - /** - * Called when the audio state changes. + * Invoked when the {@code Phone} has been created. This is a signal to the in-call experience + * to start displaying in-call information to the user. Each instance of {@code InCallService} + * will have only one {@code Phone}, and this method will be called exactly once in the + * lifetime of the {@code InCallService}. * - * @param audioState The new {@link CallAudioState}. + * @param phone The {@code Phone} object associated with this {@code InCallService}. */ - protected abstract void onAudioStateChanged(CallAudioState audioState); + public void onPhoneCreated(Phone phone) { } /** - * Brings the in-call screen to the foreground. + * Invoked when a {@code Phone} has been destroyed. This is a signal to the in-call experience + * to stop displaying in-call information to the user. This method will be called exactly once + * in the lifetime of the {@code InCallService}, and it will always be called after a previous + * call to {@link #onPhoneCreated(Phone)}. * - * @param showDialpad If true, put up the dialpad when the screen is shown. + * @param phone The {@code Phone} object associated with this {@code InCallService}. */ - protected abstract void bringToForeground(boolean showDialpad); + public void onPhoneDestroyed(Phone phone) { } } diff --git a/telecomm/java/android/telecomm/Phone.java b/telecomm/java/android/telecomm/Phone.java new file mode 100644 index 000000000000..9c97b45891b4 --- /dev/null +++ b/telecomm/java/android/telecomm/Phone.java @@ -0,0 +1,255 @@ +/* + * 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. + */ + +package android.telecomm; + +import android.util.ArrayMap; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +/** + * A unified virtual device providing a means of voice (and other) communication on a device. + */ +public final class Phone { + + public abstract static class Listener { + /** + * Called when the audio state changes. + * + * @param phone The {@code Phone} calling this method. + * @param audioState The new {@link CallAudioState}. + */ + public void onAudioStateChanged(Phone phone, CallAudioState audioState) { } + + /** + * Called to bring the in-call screen to the foreground. The in-call experience should + * respond immediately by coming to the foreground to inform the user of the state of + * ongoing {@code Call}s. + * + * @param phone The {@code Phone} calling this method. + * @param showDialpad If true, put up the dialpad when the screen is shown. + */ + public void onBringToForeground(Phone phone, boolean showDialpad) { } + + /** + * Called when a {@code Call} has been added to this in-call session. The in-call user + * experience should add necessary state listeners to the specified {@code Call} and + * immediately start to show the user information about the existence + * and nature of this {@code Call}. Subsequent invocations of {@link #getCalls()} will + * include this {@code Call}. + * + * @param phone The {@code Phone} calling this method. + * @param call A newly added {@code Call}. + */ + public void onCallAdded(Phone phone, Call call) { } + + /** + * Called when a {@code Call} has been removed from this in-call session. The in-call user + * experience should remove any state listeners from the specified {@code Call} and + * immediately stop displaying any information about this {@code Call}. + * Subsequent invocations of {@link #getCalls()} will no longer include this {@code Call}. + * + * @param phone The {@code Phone} calling this method. + * @param call A newly removed {@code Call}. + */ + public void onCallRemoved(Phone phone, Call call) { } + } + + // A Map allows us to track each Call by its Telecomm-specified call ID + private final Map<String, Call> mCallByTelecommCallId = new ArrayMap<>(); + + // A List allows us to keep the Calls in a stable iteration order so that casually developed + // user interface components do not incur any spurious jank + private final List<Call> mCalls = new ArrayList<>(); + + // An unmodifiable view of the above List can be safely shared with subclass implementations + private final List<Call> mUnmodifiableCalls = Collections.unmodifiableList(mCalls); + + private final InCallAdapter mInCallAdapter; + + private CallAudioState mAudioState; + + private final List<Listener> mListeners = new ArrayList<>(); + + /** {@hide} */ + Phone(InCallAdapter adapter) { + mInCallAdapter = adapter; + } + + /** {@hide} */ + final void internalAddCall(InCallCall inCallCall) { + Call call = new Call(this, inCallCall.getId(), mInCallAdapter); + mCallByTelecommCallId.put(inCallCall.getId(), call); + mCalls.add(call); + checkCallTree(inCallCall); + call.internalUpdate(inCallCall); + fireCallAdded(call); + } + + /** {@hide} */ + final void internalRemoveCall(Call call) { + mCallByTelecommCallId.remove(call.internalGetCallId()); + mCalls.remove(call); + fireCallRemoved(call); + } + + /** {@hide} */ + final void internalUpdateCall(InCallCall inCallCall) { + Call call = mCallByTelecommCallId.get(inCallCall.getId()); + if (call != null) { + checkCallTree(inCallCall); + call.internalUpdate(inCallCall); + } + } + + /** {@hide} */ + final void internalSetPostDial(String callId, String remaining) { + Call call = mCallByTelecommCallId.get(callId); + if (call != null) { + call.internalSetPostDial(remaining); + } + } + + /** {@hide} */ + final void internalSetPostDialWait(String callId, String remaining) { + Call call = mCallByTelecommCallId.get(callId); + if (call != null) { + call.internalSetPostDialWait(remaining); + } + } + + /** {@hide} */ + final void internalAudioStateChanged(CallAudioState callAudioState) { + if (!Objects.equals(mAudioState, callAudioState)) { + mAudioState = callAudioState; + fireAudioStateChanged(callAudioState); + } + } + + /** {@hide} */ + final Call internalGetCallByTelecommId(String telecommId) { + return mCallByTelecommCallId.get(telecommId); + } + + /** {@hide} */ + final void internalBringToForeground(boolean showDialpad) { + fireBringToForeground(showDialpad); + } + + /** + * Adds a listener to this {@code Phone}. + * + * @param listener A {@code Listener} object. + */ + public final void addListener(Listener listener) { + mListeners.add(listener); + } + + /** + * Removes a listener from this {@code Phone}. + * + * @param listener A {@code Listener} object. + */ + public final void removeListener(Listener listener) { + mListeners.remove(listener); + } + + /** + * Obtains the current list of {@code Call}s to be displayed by this in-call experience. + * + * @return A list of the relevant {@code Call}s. + */ + public final List<Call> getCalls() { + return mUnmodifiableCalls; + } + + /** + * Sets the microphone mute state. When this request is honored, there will be change to + * the {@link #getAudioState()}. + * + * @param state {@code true} if the microphone should be muted; {@code false} otherwise. + */ + public final void setMuted(boolean state) { + mInCallAdapter.mute(state); + } + + /** + * Sets the audio route (speaker, bluetooth, etc...). When this request is honored, there will + * be change to the {@link #getAudioState()}. + * + * @param route The audio route to use. + */ + public final void setAudioRoute(int route) { + mInCallAdapter.setAudioRoute(route); + } + + /** + * Obtains the current phone call audio state of the {@code Phone}. + * + * @return An object encapsulating the audio state. + */ + public final CallAudioState getAudioState() { + return mAudioState; + } + + private void fireCallAdded(Call call) { + Listener[] listeners = mListeners.toArray(new Listener[mListeners.size()]); + for (int i = 0; i < listeners.length; i++) { + listeners[i].onCallAdded(this, call); + } + } + + private void fireCallRemoved(Call call) { + Listener[] listeners = mListeners.toArray(new Listener[mListeners.size()]); + for (int i = 0; i < listeners.length; i++) { + listeners[i].onCallRemoved(this, call); + } + } + + private void fireAudioStateChanged(CallAudioState audioState) { + Listener[] listeners = mListeners.toArray(new Listener[mListeners.size()]); + for (int i = 0; i < listeners.length; i++) { + listeners[i].onAudioStateChanged(this, audioState); + } + } + + private void fireBringToForeground(boolean showDialpad) { + Listener[] listeners = mListeners.toArray(new Listener[mListeners.size()]); + for (int i = 0; i < listeners.length; i++) { + listeners[i].onBringToForeground(this, showDialpad); + } + } + + private void checkCallTree(InCallCall inCallCall) { + if (inCallCall.getParentCallId() != null && + !mCallByTelecommCallId.containsKey(inCallCall.getParentCallId())) { + Log.wtf(this, "InCallCall %s has nonexistent parent %s", + inCallCall.getId(), inCallCall.getParentCallId()); + } + if (inCallCall.getChildCallIds() != null) { + for (int i = 0; i < inCallCall.getChildCallIds().size(); i++) { + if (!mCallByTelecommCallId.containsKey(inCallCall.getChildCallIds().get(i))) { + Log.wtf(this, "InCallCall %s has nonexistent child %s", + inCallCall.getId(), inCallCall.getChildCallIds().get(i)); + } + } + } + } +} diff --git a/telecomm/java/android/telecomm/PhoneAccount.java b/telecomm/java/android/telecomm/PhoneAccount.java index 4e440d8d18c5..c1eec83e2a9a 100644 --- a/telecomm/java/android/telecomm/PhoneAccount.java +++ b/telecomm/java/android/telecomm/PhoneAccount.java @@ -17,52 +17,64 @@ package android.telecomm; import android.content.ComponentName; -import android.content.Context; -import android.content.pm.PackageManager; -import android.graphics.drawable.Drawable; import android.net.Uri; import android.os.Parcel; import android.os.Parcelable; -import android.telephony.Rlog; -import android.util.DisplayMetrics; -import android.util.Log; -import java.util.MissingResourceException; import java.util.Objects; /** * Represents a distinct account, line of service or call placement method that * the system can use to place phone calls. */ -public final class PhoneAccount implements Parcelable { +public class PhoneAccount implements Parcelable { - private static final int NO_DENSITY = -1; - private static final String LOG_TAG = "Account"; + /** + * Flag indicating that this {@code PhoneAccount} can act as a call manager for traditional + * SIM-based telephony calls. The {@link ConnectionService} associated with this phone-account + * will be allowed to manage SIM-based phone calls including using its own proprietary + * phone-call implementation (like VoIP calling) to make calls instead of the telephony stack. + * When a user opts to place a call using the SIM-based telephony stack, the connection-service + * associated with this phone-account will be attempted first if the user has explicitly + * selected it to be used as the default call-manager. + * <p> + * See {@link #getCapabilities} + */ + public static final int CAPABILITY_SIM_CALL_MANAGER = 0x1; + + /** + * Flag indicating that this {@code PhoneAccount} can make phone calls in place of traditional + * SIM-based telephony calls. This account will be treated as a distinct method for placing + * calls alongside the traditional SIM-based telephony stack. This flag is distinct from + * {@link #CAPABILITY_SIM_CALL_MANAGER} in that it is not allowed to manage calls from or use + * the built-in telephony stack to place its calls. + * <p> + * See {@link #getCapabilities} + */ + public static final int CAPABILITY_CALL_PROVIDER = 0x2; + + /** + * Flag indicating that this {@code PhoneAccount} represents a built-in PSTN SIM subscription. + * <p> + * Only the android framework can set this capability on a phone-account. + */ + public static final int CAPABILITY_SIM_SUBSCRIPTION = 0x4; - private final ComponentName mComponentName; - private final String mId; - private final Uri mHandle; - private final String mLabel; - private final String mShortDescription; - private final boolean mIsEnabled; - private final boolean mIsSystemDefault; + private ComponentName mComponentName; + private String mId; + private Uri mHandle; + private int mCapabilities; public PhoneAccount( ComponentName componentName, String id, Uri handle, - String label, - String shortDescription, - boolean isEnabled, - boolean isSystemDefault) { + int capabilities) { mComponentName = componentName; mId = id; mHandle = handle; - mLabel = label; - mShortDescription = shortDescription; - mIsSystemDefault = isSystemDefault; - mIsEnabled = isEnabled; + mCapabilities = capabilities; } /** @@ -87,8 +99,8 @@ public final class PhoneAccount implements Parcelable { /** * The handle (e.g., a phone number) associated with this {@code PhoneAccount}. This represents - * the destination from which outgoing calls using this {@code PhoneAccount} will appear to come - * from, if applicable, and the destination to which incoming calls using this + * the destination from which outgoing calls using this {@code PhoneAccount} will appear to + * come, if applicable, and the destination to which incoming calls using this * {@code PhoneAccount} may be addressed. * * @return A handle expressed as a {@code Uri}, for example, a phone number. @@ -98,76 +110,23 @@ public final class PhoneAccount implements Parcelable { } /** - * A short string label describing this {@code PhoneAccount}. - * - * @param context The invoking {@code Context}, used for retrieving resources. + * The capabilities of this {@code PhoneAccount}. * - * TODO(ihab): If don't need context, remove param - * - * @return A label for this {@code PhoneAccount}. + * @return A bit field of flags describing this {@code PhoneAccount}'s capabilities. */ - public String getLabel(Context context) { - return mLabel; + public int getCapabilities() { + return mCapabilities; } - /** - * A short paragraph describing this {@code PhoneAccount}. - * - * @param context The invoking {@code Context}, used for retrieving resources. - * - * TODO(ihab): If don't need context, remove param - * - * @return A description for this {@code PhoneAccount}. - */ - public String getShortDescription(Context context) { - return mShortDescription; + @Override + public int hashCode() { + return Objects.hashCode(mComponentName) + Objects.hashCode(mId) + + Objects.hashCode(mHandle) + mCapabilities; } - // TODO(ihab): Representation of the icons // - // Refactor to pass a Bitmap (scale it at runtime), but if they don't pass one, fall - // back to the android:icon attr in the manifest (<service /> first, <application /> second) - - /** - * An icon to represent this {@code PhoneAccount} in a user interface. - * - * @param context The invoking {@code Context}, used for retrieving resources. - * - * @return An icon for this {@code PhoneAccount}. - */ - public Drawable getIcon(Context context) { - return null; // TODO(ihab): See above - } - - /** - * An icon to represent this {@code PhoneAccount} in a user interface. - * - * @param context The invoking {@code Context}, used for retrieving resources. - * @param density A display density from {@link DisplayMetrics}. - * - * @return An icon for this {@code PhoneAccount}. - */ - public Drawable getIcon(Context context, int density) { - return null; // TODO(ihab): See above - } - - /** - * Whether this {@code PhoneAccount} is enabled for use. - * - * @return {@code true} if this {@code PhoneAccount} is enabled. - */ - public boolean isEnabled() { - return mIsEnabled; - } - - /** - * Whether this {@code PhoneAccount} is the system default. - * - * @return {@code true} if this {@code PhoneAccount} is the system default. - */ - public boolean isSystemDefault() { - return mIsSystemDefault; - } + // Parcelable implementation. + // @Override public int describeContents() { @@ -179,18 +138,16 @@ public final class PhoneAccount implements Parcelable { out.writeParcelable(mComponentName, flags); out.writeString(mId); out.writeString(mHandle != null ? mHandle.toString() : ""); - out.writeString(mLabel); - out.writeString(mShortDescription); - out.writeInt(mIsEnabled ? 1 : 0); - out.writeInt(mIsSystemDefault ? 1 : 0); + out.writeInt(mCapabilities); } - public static final Creator<PhoneAccount> CREATOR - = new Creator<PhoneAccount>() { + public static final Creator<PhoneAccount> CREATOR = new Creator<PhoneAccount>() { + @Override public PhoneAccount createFromParcel(Parcel in) { return new PhoneAccount(in); } + @Override public PhoneAccount[] newArray(int size) { return new PhoneAccount[size]; } @@ -201,22 +158,6 @@ public final class PhoneAccount implements Parcelable { mId = in.readString(); String uriString = in.readString(); mHandle = uriString.length() > 0 ? Uri.parse(uriString) : null; - mLabel = in.readString(); - mShortDescription = in.readString(); - mIsEnabled = in.readInt() == 1; - mIsSystemDefault = in.readInt() == 1; - } - - @Override - public boolean equals(Object other) { - return - other instanceof PhoneAccount && - Objects.equals(mComponentName, ((PhoneAccount) other).mComponentName) && - Objects.equals(mId, ((PhoneAccount) other).mId); - } - - @Override - public int hashCode() { - return Objects.hashCode(mComponentName) + Objects.hashCode(mId); + mCapabilities = in.readInt(); } } diff --git a/telecomm/java/android/telecomm/PhoneAccountMetadata.aidl b/telecomm/java/android/telecomm/PhoneAccountMetadata.aidl new file mode 100644 index 000000000000..55b890007eb7 --- /dev/null +++ b/telecomm/java/android/telecomm/PhoneAccountMetadata.aidl @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.telecomm; + +/** + * {@hide} + */ +parcelable PhoneAccountMetadata; diff --git a/telecomm/java/android/telecomm/PhoneAccountMetadata.java b/telecomm/java/android/telecomm/PhoneAccountMetadata.java new file mode 100644 index 000000000000..20a4d47a714b --- /dev/null +++ b/telecomm/java/android/telecomm/PhoneAccountMetadata.java @@ -0,0 +1,140 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.telecomm; + +import android.content.Context; +import android.content.pm.PackageManager; +import android.graphics.drawable.Drawable; +import android.os.Parcel; +import android.os.Parcelable; + +import java.io.IOException; +import java.io.ObjectStreamException; +import java.io.Serializable; +import java.util.MissingResourceException; + +/** + * Provides user interface description information for a {@code PhoneAccount}. + */ +public class PhoneAccountMetadata implements Parcelable { + private PhoneAccount mAccount; + private int mIconResId; + private String mLabel; + private String mShortDescription; + + public PhoneAccountMetadata( + PhoneAccount account, + int iconResId, + String label, + String shortDescription) { + mAccount = account; + mIconResId = iconResId; + mLabel = label; + mShortDescription = shortDescription; + } + + /** + * The {@code PhoneAccount} to which this metadata pertains. + * + * @return A {@code PhoneAccount}. + */ + public PhoneAccount getAccount() { + return mAccount; + } + + /** + * A short string label describing a {@code PhoneAccount}. + * + * @return A label for this {@code PhoneAccount}. + */ + public String getLabel() { + return mLabel; + } + + /** + * A short paragraph describing a {@code PhoneAccount}. + * + * @return A description for this {@code PhoneAccount}. + */ + public String getShortDescription() { + return mShortDescription; + } + + /** + * An icon to represent this {@code PhoneAccount} in a user interface. + * + * @return An icon for this {@code PhoneAccount}. + */ + public Drawable getIcon(Context context) { + return getIcon(context, mIconResId); + } + + private Drawable getIcon(Context context, int resId) { + Context packageContext; + try { + packageContext = context.createPackageContext( + mAccount.getComponentName().getPackageName(), 0); + } catch (PackageManager.NameNotFoundException e) { + Log.w(this, "Cannot find package %s", mAccount.getComponentName().getPackageName()); + return null; + } + try { + return packageContext.getResources().getDrawable(resId); + } catch (MissingResourceException e) { + Log.e(this, e, "Cannot find icon %d in package %s", + resId, mAccount.getComponentName().getPackageName()); + return null; + } + } + + // + // Parcelable implementation + // + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel out, int flags) { + out.writeParcelable(mAccount, 0); + out.writeInt(mIconResId); + out.writeString(mLabel); + out.writeString(mShortDescription); + } + + public static final Creator<PhoneAccountMetadata> CREATOR + = new Creator<PhoneAccountMetadata>() { + @Override + public PhoneAccountMetadata createFromParcel(Parcel in) { + return new PhoneAccountMetadata(in); + } + + @Override + public PhoneAccountMetadata[] newArray(int size) { + return new PhoneAccountMetadata[size]; + } + }; + + private PhoneAccountMetadata(Parcel in) { + mAccount = in.readParcelable(getClass().getClassLoader()); + mIconResId = in.readInt(); + mLabel = in.readString(); + mShortDescription = in.readString(); + } +} diff --git a/telecomm/java/android/telecomm/RemoteCallVideoProvider.java b/telecomm/java/android/telecomm/RemoteCallVideoProvider.java index 856d321dc408..a49076a2fd0c 100644 --- a/telecomm/java/android/telecomm/RemoteCallVideoProvider.java +++ b/telecomm/java/android/telecomm/RemoteCallVideoProvider.java @@ -20,63 +20,92 @@ import android.os.IBinder; import android.os.RemoteException; import android.view.Surface; -import com.android.internal.telecomm.ICallVideoClient; import com.android.internal.telecomm.ICallVideoProvider; -public class RemoteCallVideoProvider implements IBinder.DeathRecipient { +public class RemoteCallVideoProvider { private final ICallVideoProvider mCallVideoProvider; + private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() { + @Override + public void binderDied() { + mCallVideoProvider.asBinder().unlinkToDeath(this, 0); + } + }; + + /** {@hide} */ RemoteCallVideoProvider(ICallVideoProvider callVideoProvider) throws RemoteException { mCallVideoProvider = callVideoProvider; - mCallVideoProvider.asBinder().linkToDeath(this, 0); - } - - @Override - public void binderDied() { - mCallVideoProvider.asBinder().unlinkToDeath(this, 0); + mCallVideoProvider.asBinder().linkToDeath(mDeathRecipient, 0); } - public void setCallVideoClient(CallVideoClient callVideoClient) throws RemoteException { - mCallVideoProvider.setCallVideoClient(callVideoClient.getBinder()); + public void setCallVideoClient(CallVideoClient callVideoClient) { + try { + mCallVideoProvider.setCallVideoClient(callVideoClient.getBinder()); + } catch (RemoteException e) { + } } public void setCamera(String cameraId) throws RemoteException { mCallVideoProvider.setCamera(cameraId); } - public void setPreviewSurface(Surface surface) throws RemoteException { - mCallVideoProvider.setPreviewSurface(surface); + public void setPreviewSurface(Surface surface) { + try { + mCallVideoProvider.setPreviewSurface(surface); + } catch (RemoteException e) { + } } - public void setDisplaySurface(Surface surface) throws RemoteException { - mCallVideoProvider.setDisplaySurface(surface); + public void setDisplaySurface(Surface surface) { + try { + mCallVideoProvider.setDisplaySurface(surface); + } catch (RemoteException e) { + } } - public void setDeviceOrientation(int rotation) throws RemoteException { - mCallVideoProvider.setDeviceOrientation(rotation); + public void setDeviceOrientation(int rotation) { + try { + mCallVideoProvider.setDeviceOrientation(rotation); + } catch (RemoteException e) { + } } public void setZoom(float value) throws RemoteException { mCallVideoProvider.setZoom(value); } - public void sendSessionModifyRequest(VideoCallProfile requestProfile) throws RemoteException { - mCallVideoProvider.sendSessionModifyRequest(requestProfile); + public void sendSessionModifyRequest(VideoCallProfile requestProfile) { + try { + mCallVideoProvider.sendSessionModifyRequest(requestProfile); + } catch (RemoteException e) { + } } - public void sendSessionModifyResponse(VideoCallProfile responseProfile) throws RemoteException { - mCallVideoProvider.sendSessionModifyResponse(responseProfile); + public void sendSessionModifyResponse(VideoCallProfile responseProfile) { + try { + mCallVideoProvider.sendSessionModifyResponse(responseProfile); + } catch (RemoteException e) { + } } - public void requestCameraCapabilities() throws RemoteException { - mCallVideoProvider.requestCameraCapabilities(); + public void requestCameraCapabilities() { + try { + mCallVideoProvider.requestCameraCapabilities(); + } catch (RemoteException e) { + } } - public void requestCallDataUsage() throws RemoteException { - mCallVideoProvider.requestCallDataUsage(); + public void requestCallDataUsage() { + try { + mCallVideoProvider.requestCallDataUsage(); + } catch (RemoteException e) { + } } - public void setPauseImage(String uri) throws RemoteException { - mCallVideoProvider.setPauseImage(uri); + public void setPauseImage(String uri) { + try { + mCallVideoProvider.setPauseImage(uri); + } catch (RemoteException e) { + } } }
\ No newline at end of file diff --git a/telecomm/java/android/telecomm/RemoteConnectionService.java b/telecomm/java/android/telecomm/RemoteConnectionService.java index a436af27a314..430133cebfc6 100644 --- a/telecomm/java/android/telecomm/RemoteConnectionService.java +++ b/telecomm/java/android/telecomm/RemoteConnectionService.java @@ -266,10 +266,7 @@ final class RemoteConnectionService implements DeathRecipient { mComponentName, null /* id */, null /* handle */, - "" /* label */, - "" /* shortDescription */, - true /* isEnabled */, - false /* isSystemDefault */)); + 0 /* capabilities */)); return accounts; } diff --git a/telecomm/java/android/telecomm/TelecommManager.java b/telecomm/java/android/telecomm/TelecommManager.java index 1bb18f234147..fcd2eba7116f 100644 --- a/telecomm/java/android/telecomm/TelecommManager.java +++ b/telecomm/java/android/telecomm/TelecommManager.java @@ -23,37 +23,133 @@ import android.util.Log; import com.android.internal.telecomm.ITelecommService; +import java.util.List; + /** * Provides access to Telecomm-related functionality. * TODO(santoscordon): Move this all into PhoneManager. * @hide */ public class TelecommManager { + + /** + * The extra used with an {@link android.content.Intent#ACTION_CALL} or + * {@link android.content.Intent#ACTION_DIAL} {@code Intent} to specify a {@link PhoneAccount} + * to use when making the call. + * + * <p class="note"> + * Retrieve with + * {@link android.content.Intent#getParcelableExtra(String)}. + */ + public static final String EXTRA_PHONE_ACCOUNT = "account"; + private static final String TAG = "TelecommManager"; private static final String TELECOMM_SERVICE_NAME = "telecomm"; private final Context mContext; - private final ITelecommService mService; /** * @hide */ - public TelecommManager(Context context, ITelecommService service) { + public static TelecommManager from(Context context) { + return (TelecommManager) context.getSystemService(Context.TELECOMM_SERVICE); + } + + /** + * @hide + */ + public TelecommManager(Context context) { Context appContext = context.getApplicationContext(); if (appContext != null) { mContext = appContext; } else { mContext = context; } + } - mService = service; + /** + * Return a list of {@link PhoneAccount}s which can be used to make and receive phone calls. + * + * @see #EXTRA_PHONE_ACCOUNT + * @return A list of {@code PhoneAccount} objects. + */ + public List<PhoneAccount> getEnabledPhoneAccounts() { + try { + if (isServiceConnected()) { + return getTelecommService().getEnabledPhoneAccounts(); + } + } catch (RemoteException e) { + Log.e(TAG, "Error calling ITelecommService#getEnabledPhoneAccounts", e); + } + return null; } /** + * Return the metadata for a specified {@link PhoneAccount}. Metadata includes resources which + * can be used in a user interface. + * + * @param account The {@link PhoneAccount}. + * + * @return The metadata for the account. + */ + public PhoneAccountMetadata getPhoneAccountMetadata(PhoneAccount account) { + try { + if (isServiceConnected()) { + return getTelecommService().getPhoneAccountMetadata(account); + } + } catch (RemoteException e) { + Log.e(TAG, "Error calling ITelecommService#getPhoneAccountMetadata", e); + } + return null; + } + + /** + * Register a {@link PhoneAccount} for use by the system. + * + * @param account The {@link PhoneAccount}. + * @param metadata The metadata for the account. + */ + public void registerPhoneAccount(PhoneAccount account, PhoneAccountMetadata metadata) { + try { + if (isServiceConnected()) { + getTelecommService().registerPhoneAccount(account, metadata); + } + } catch (RemoteException e) { + Log.e(TAG, "Error calling ITelecommService#registerPhoneAccount", e); + } + } + + /** + * Remove a {@link PhoneAccount} registration from the system. + * + * @param account An Account. + */ + public void unregisterPhoneAccount(PhoneAccount account) { + try { + if (isServiceConnected()) { + getTelecommService().unregisterPhoneAccount(account); + } + } catch (RemoteException e) { + Log.e(TAG, "Error calling ITelecommService#unregisterPhoneAccount", e); + } + } + + /** + * Remove all Accounts for a given package from the system. + * + * @param packageName A package name that may have registered Accounts. + * * @hide */ - public static TelecommManager from(Context context) { - return (TelecommManager) context.getSystemService(Context.TELECOMM_SERVICE); + @SystemApi + public void clearAccounts(String packageName) { + try { + if (isServiceConnected()) { + getTelecommService().clearAccounts(packageName); + } + } catch (RemoteException e) { + Log.e(TAG, "Error calling ITelecommService#clearAccounts", e); + } } /** @@ -108,7 +204,7 @@ public class TelecommManager { /** * Ends an ongoing call. TODO(santoscordon): L-release - need to convert all invocations of - * ITelephony#endCall to use this method (clockwork & gearhead). + * ITelecommService#endCall to use this method (clockwork & gearhead). * * @hide */ @@ -127,7 +223,7 @@ public class TelecommManager { /** * If there is a ringing incoming call, this method accepts the call on behalf of the user. * TODO(santoscordon): L-release - need to convert all invocation of - * ITelephony#answerRingingCall to use this method (clockwork & gearhead). + * ITelecommService#answerRingingCall to use this method (clockwork & gearhead). * * @hide */ diff --git a/telecomm/java/com/android/internal/telecomm/ITelecommService.aidl b/telecomm/java/com/android/internal/telecomm/ITelecommService.aidl index 30e4bdc234b0..3334385b3f57 100644 --- a/telecomm/java/com/android/internal/telecomm/ITelecommService.aidl +++ b/telecomm/java/com/android/internal/telecomm/ITelecommService.aidl @@ -18,6 +18,7 @@ package com.android.internal.telecomm; import android.content.ComponentName; import android.telecomm.PhoneAccount; +import android.telecomm.PhoneAccountMetadata; /** * Interface used to interact with Telecomm. Mostly this is used by TelephonyManager for passing @@ -33,22 +34,32 @@ interface ITelecommService { void showCallScreen(boolean showDialpad); /** - * Gets a list of accounts. + * @see TelecommManager#getEnabledPhoneAccounts */ - List<PhoneAccount> getAccounts(); + List<PhoneAccount> getEnabledPhoneAccounts(); /** - * Sets the enabled state of a given account. + * @see TelecommManager#getPhoneAccountMetadata */ - void setEnabled(in PhoneAccount account, boolean enabled); + PhoneAccountMetadata getPhoneAccountMetadata(in PhoneAccount account); /** - * Sets a given account as the system default. + * @see TelecommManager#registerPhoneAccount */ - void setSystemDefault(in PhoneAccount account); + void registerPhoneAccount(in PhoneAccount account, in PhoneAccountMetadata metadata); /** - * Returns the component name of the default phone application. + * @see TelecommManager#unregisterPhoneAccount + */ + void unregisterPhoneAccount(in PhoneAccount account); + + /** + * @see TelecommManager#clearAccounts + */ + void clearAccounts(String packageName); + + /** + * @see TelecommManager#getDefaultPhoneApp */ ComponentName getDefaultPhoneApp(); diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java index 91ce73a326ca..c1eb843eb635 100644 --- a/telephony/java/android/telephony/TelephonyManager.java +++ b/telephony/java/android/telephony/TelephonyManager.java @@ -298,17 +298,6 @@ public class TelephonyManager { public static final String EXTRA_INCOMING_NUMBER = "incoming_number"; /** - * The lookup key used with an {@link android.content.Intent#ACTION_CALL} or - * {@link android.content.Intent#ACTION_DIAL} {@code Intent} for a {@link PhoneAccount} - * object indicating a preference when making a phone connection. - * - * <p class="note"> - * Retrieve with - * {@link android.content.Intent#getParcelableExtra(String)}. - */ - public static final String EXTRA_ACCOUNT = "account"; - - /** * Broadcast intent action indicating that a precise call state * (cellular) on the device has changed. * @@ -3207,42 +3196,6 @@ public class TelephonyManager { } /** - * Return a list of Accounts that can be used to indicate a preference when making - * a phone call. - * - * @see #EXTRA_ACCOUNT - * @return A list of {@code Accouint} objects. - */ - public List<PhoneAccount> getAccounts() { - try { - return getTelecommService().getAccounts(); - } catch (RemoteException e) { - Log.e(TAG, "Error calling ITelephony#getAccounts", e); - } - return null; - } - - /** @hide */ - @SystemApi - public void setEnabled(PhoneAccount account, boolean enabled) { - try { - getTelecommService().setEnabled(account, enabled); - } catch (RemoteException e) { - Log.e(TAG, "Error calling ITelephony#setEnabled", e); - } - } - - /** @hide */ - @SystemApi - public void setSystemDefault(PhoneAccount account) { - try { - getTelecommService().setSystemDefault(account); - } catch (RemoteException e) { - Log.e(TAG, "Error calling ITelephony#setSystemDefault", e); - } - } - - /** * Set whether Android should display a simplified Mobile Network Settings UI. * The setting won't be persisted during power cycle. * <p> diff --git a/test-runner/src/android/test/mock/MockContext.java b/test-runner/src/android/test/mock/MockContext.java index a54936bfb1c5..2ebce9bcaf21 100644 --- a/test-runner/src/android/test/mock/MockContext.java +++ b/test-runner/src/android/test/mock/MockContext.java @@ -175,6 +175,11 @@ public class MockContext extends Context { } @Override + public File getNoBackupFilesDir() { + throw new UnsupportedOperationException(); + } + + @Override public File getExternalFilesDir(String type) { throw new UnsupportedOperationException(); } |