diff --git a/Android.mk b/Android.mk index 151621c36c8..d63a70ad1df 100644 --- a/Android.mk +++ b/Android.mk @@ -67,9 +67,11 @@ LOCAL_SRC_FILES += \ core/java/android/app/IActivityController.aidl \ core/java/android/app/IActivityPendingResult.aidl \ core/java/android/app/IAlarmManager.aidl \ + core/java/android/hardware/IIrdaManager.aidl \ core/java/android/app/IBackupAgent.aidl \ core/java/android/app/IInstrumentationWatcher.aidl \ core/java/android/app/INotificationManager.aidl \ + core/java/android/app/IProfileManager.aidl \ core/java/android/app/IProcessObserver.aidl \ core/java/android/app/ISearchManager.aidl \ core/java/android/app/ISearchManagerCallback.aidl \ @@ -101,9 +103,12 @@ LOCAL_SRC_FILES += \ core/java/android/bluetooth/IBluetoothManagerCallback.aidl \ core/java/android/bluetooth/IBluetoothPbap.aidl \ core/java/android/bluetooth/IBluetoothStateChangeCallback.aidl \ + core/java/android/bluetooth/IBluetoothHandsfreeClient.aidl \ core/java/android/bluetooth/IBluetoothGatt.aidl \ core/java/android/bluetooth/IBluetoothGattCallback.aidl \ core/java/android/bluetooth/IBluetoothGattServerCallback.aidl \ + core/java/android/bluetooth/IBluetoothSap.aidl \ + core/java/android/bluetooth/IBluetoothDun.aidl \ core/java/android/content/IClipboard.aidl \ core/java/android/content/IContentService.aidl \ core/java/android/content/IIntentReceiver.aidl \ @@ -153,6 +158,7 @@ LOCAL_SRC_FILES += \ core/java/android/service/notification/INotificationListener.aidl \ core/java/android/service/dreams/IDreamManager.aidl \ core/java/android/service/dreams/IDreamService.aidl \ + core/java/android/service/gesture/IGestureService.aidl \ core/java/android/service/wallpaper/IWallpaperConnection.aidl \ core/java/android/service/wallpaper/IWallpaperEngine.aidl \ core/java/android/service/wallpaper/IWallpaperService.aidl \ @@ -180,6 +186,7 @@ LOCAL_SRC_FILES += \ core/java/com/android/internal/app/IBatteryStats.aidl \ core/java/com/android/internal/app/IUsageStats.aidl \ core/java/com/android/internal/app/IMediaContainerService.aidl \ + core/java/com/android/internal/app/IAssetRedirectionManager.aidl \ core/java/com/android/internal/appwidget/IAppWidgetService.aidl \ core/java/com/android/internal/appwidget/IAppWidgetHost.aidl \ core/java/com/android/internal/backup/IBackupTransport.aidl \ @@ -211,6 +218,8 @@ LOCAL_SRC_FILES += \ location/java/android/location/ICountryListener.aidl \ location/java/android/location/IGeocodeProvider.aidl \ location/java/android/location/IGeofenceProvider.aidl \ + location/java/android/location/IGeoFencer.aidl \ + location/java/android/location/IGeoFenceListener.aidl \ location/java/android/location/IGpsStatusListener.aidl \ location/java/android/location/IGpsStatusProvider.aidl \ location/java/android/location/ILocationListener.aidl \ @@ -229,9 +238,12 @@ LOCAL_SRC_FILES += \ media/java/android/media/IRingtonePlayer.aidl \ telephony/java/com/android/internal/telephony/IPhoneStateListener.aidl \ telephony/java/com/android/internal/telephony/IPhoneSubInfo.aidl \ + telephony/java/com/android/internal/telephony/msim/IPhoneSubInfoMSim.aidl \ telephony/java/com/android/internal/telephony/ITelephony.aidl \ + telephony/java/com/android/internal/telephony/msim/ITelephonyMSim.aidl \ telephony/java/com/android/internal/telephony/ISms.aidl \ telephony/java/com/android/internal/telephony/ITelephonyRegistry.aidl \ + telephony/java/com/android/internal/telephony/ITelephonyRegistryMSim.aidl \ telephony/java/com/android/internal/telephony/IWapPushManager.aidl \ wifi/java/android/net/wifi/IWifiManager.aidl \ wifi/java/android/net/wifi/p2p/IWifiP2pManager.aidl @@ -281,6 +293,8 @@ aidl_files := \ frameworks/base/core/java/android/accounts/IAccountAuthenticator.aidl \ frameworks/base/core/java/android/accounts/IAccountAuthenticatorResponse.aidl \ frameworks/base/core/java/android/app/Notification.aidl \ + frameworks/base/core/java/android/app/NotificationGroup.aidl \ + frameworks/base/core/java/android/app/Profile.aidl \ frameworks/base/core/java/android/app/PendingIntent.aidl \ frameworks/base/core/java/android/appwidget/AppWidgetProviderInfo.aidl \ frameworks/base/core/java/android/bluetooth/BluetoothDevice.aidl \ @@ -327,7 +341,9 @@ aidl_files := \ frameworks/base/location/java/com/android/internal/location/ProviderRequest.aidl \ frameworks/base/telephony/java/android/telephony/ServiceState.aidl \ frameworks/base/telephony/java/com/android/internal/telephony/IPhoneSubInfo.aidl \ + frameworks/base/telephony/java/com/android/internal/telephony/msim/IPhoneSubInfoMSim.aidl \ frameworks/base/telephony/java/com/android/internal/telephony/ITelephony.aidl \ + frameworks/base/telephony/java/com/android/internal/telephony/msim/ITelephonyMSim.aidl \ gen := $(TARGET_OUT_COMMON_INTERMEDIATES)/framework.aidl $(gen): PRIVATE_SRC_FILES := $(aidl_files) diff --git a/api/current.txt b/api/current.txt index 02b95a0fc10..0662f7bb1c2 100644 --- a/api/current.txt +++ b/api/current.txt @@ -6631,6 +6631,7 @@ package android.content.pm { field public static final int CONFIG_SCREEN_SIZE = 1024; // 0x400 field public static final int CONFIG_SMALLEST_SCREEN_SIZE = 2048; // 0x800 field public static final int CONFIG_TOUCHSCREEN = 8; // 0x8 + field public static final int CONFIG_UI_INVERTED_MODE = 768; // 0x300 field public static final int CONFIG_UI_MODE = 512; // 0x200 field public static final android.os.Parcelable.Creator CREATOR; field public static final int FLAG_ALLOW_TASK_REPARENTING = 64; // 0x40 @@ -7290,6 +7291,10 @@ package android.content.res { field public static final int TOUCHSCREEN_NOTOUCH = 1; // 0x1 field public static final deprecated int TOUCHSCREEN_STYLUS = 2; // 0x2 field public static final int TOUCHSCREEN_UNDEFINED = 0; // 0x0 + field public static final int UI_INVERTED_MODE_UNDEFINED = 0; // 0x0 + field public static final int UI_INVERTED_MODE_NORMAL = 1; // 0x1 + field public static final int UI_INVERTED_MODE_YES = 2; // 0x2 + field public static final int UI_INVERTED_MODE_NO = 3; // 0x3 field public static final int UI_MODE_NIGHT_MASK = 48; // 0x30 field public static final int UI_MODE_NIGHT_NO = 16; // 0x10 field public static final int UI_MODE_NIGHT_UNDEFINED = 0; // 0x0 @@ -7317,6 +7322,7 @@ package android.content.res { field public int screenWidthDp; field public int smallestScreenWidthDp; field public int touchscreen; + field public int uiInvertedMode; field public int uiMode; } @@ -10129,6 +10135,7 @@ package android.hardware { method public int getJpegQuality(); method public int getJpegThumbnailQuality(); method public android.hardware.Camera.Size getJpegThumbnailSize(); + method public java.lang.String getPowerMode(); method public int getMaxExposureCompensation(); method public int getMaxNumDetectedFaces(); method public int getMaxNumFocusAreas(); @@ -10165,6 +10172,7 @@ package android.hardware { method public java.util.List getZoomRatios(); method public boolean isAutoExposureLockSupported(); method public boolean isAutoWhiteBalanceLockSupported(); + method public boolean isPowerModeSupported(); method public boolean isSmoothZoomSupported(); method public boolean isVideoSnapshotSupported(); method public boolean isVideoStabilizationSupported(); @@ -10192,6 +10200,7 @@ package android.hardware { method public void setMeteringAreas(java.util.List); method public void setPictureFormat(int); method public void setPictureSize(int, int); + method public void setPowerMode(java.lang.String); method public void setPreviewFormat(int); method public void setPreviewFpsRange(int, int); method public deprecated void setPreviewFrameRate(int); @@ -10231,6 +10240,8 @@ package android.hardware { field public static final java.lang.String FOCUS_MODE_FIXED = "fixed"; field public static final java.lang.String FOCUS_MODE_INFINITY = "infinity"; field public static final java.lang.String FOCUS_MODE_MACRO = "macro"; + field public static final java.lang.String LOW_POWER = "Low_Power"; + field public static final java.lang.String NORMAL_POWER = "Normal_Power"; field public static final int PREVIEW_FPS_MAX_INDEX = 1; // 0x1 field public static final int PREVIEW_FPS_MIN_INDEX = 0; // 0x0 field public static final java.lang.String SCENE_MODE_ACTION = "action"; @@ -28320,6 +28331,7 @@ package android.webkit { method public synchronized int getTextZoom(); method public synchronized boolean getUseWideViewPort(); method public synchronized java.lang.String getUserAgentString(); + method public synchronized boolean getWebSocketsEnabled(); method public void setAllowContentAccess(boolean); method public void setAllowFileAccess(boolean); method public abstract void setAllowFileAccessFromFileURLs(boolean); @@ -28368,6 +28380,7 @@ package android.webkit { method public synchronized void setTextZoom(int); method public synchronized void setUseWideViewPort(boolean); method public synchronized void setUserAgentString(java.lang.String); + method public synchronized void setWebSocketsEnabled(boolean); method public synchronized boolean supportMultipleWindows(); method public boolean supportZoom(); field public static final int LOAD_CACHE_ELSE_NETWORK = 1; // 0x1 diff --git a/cmds/bootanimation/Android.mk b/cmds/bootanimation/Android.mk index d5ff84e7dbb..6841caec044 100644 --- a/cmds/bootanimation/Android.mk +++ b/cmds/bootanimation/Android.mk @@ -22,6 +22,22 @@ LOCAL_SHARED_LIBRARIES := \ LOCAL_C_INCLUDES := \ $(call include-path-for, corecg graphics) +ifeq ($(TARGET_BOOTANIMATION_PRELOAD),true) + LOCAL_CFLAGS += -DPRELOAD_BOOTANIMATION +endif + +ifeq ($(TARGET_BOOTANIMATION_TEXTURE_CACHE),true) + LOCAL_CFLAGS += -DNO_TEXTURE_CACHE=0 +endif + +ifeq ($(TARGET_BOOTANIMATION_TEXTURE_CACHE),false) + LOCAL_CFLAGS += -DNO_TEXTURE_CACHE=1 +endif + +ifeq ($(TARGET_BOOTANIMATION_USE_RGB565),true) + LOCAL_CFLAGS += -DUSE_565 +endif + LOCAL_MODULE:= bootanimation diff --git a/cmds/bootanimation/BootAnimation.cpp b/cmds/bootanimation/BootAnimation.cpp index 8511735a15a..7c78cfb65af 100644 --- a/cmds/bootanimation/BootAnimation.cpp +++ b/cmds/bootanimation/BootAnimation.cpp @@ -162,7 +162,11 @@ status_t BootAnimation::initTexture(void* buffer, size_t len) codec->setDitherImage(false); if (codec) { codec->decode(&stream, &bitmap, + #ifdef USE_565 + SkBitmap::kRGB_565_Config, + #else SkBitmap::kARGB_8888_Config, + #endif SkImageDecoder::kDecodePixels_Mode); delete codec; } @@ -289,6 +293,38 @@ status_t BootAnimation::readyToRun() { mAndroidAnimation = false; } + +#ifdef PRELOAD_BOOTANIMATION + // Preload the bootanimation zip on memory, so we don't stutter + // when showing the animation + FILE* fd; + if (encryptedAnimation && access(SYSTEM_ENCRYPTED_BOOTANIMATION_FILE, R_OK) == 0) + fd = fopen(SYSTEM_ENCRYPTED_BOOTANIMATION_FILE, "r"); + else if (access(USER_BOOTANIMATION_FILE, R_OK) == 0) + fd = fopen(USER_BOOTANIMATION_FILE, "r"); + else if (access(SYSTEM_BOOTANIMATION_FILE, R_OK) == 0) + fd = fopen(SYSTEM_BOOTANIMATION_FILE, "r"); + else + return NO_ERROR; + + if (fd != NULL) { + // We could use readahead.. + // ... if bionic supported it :( + //readahead(fd, 0, INT_MAX); + void *crappyBuffer = malloc(2*1024*1024); + if (crappyBuffer != NULL) { + // Read all the zip + while (!feof(fd)) + fread(crappyBuffer, 1024, 2*1024, fd); + + free(crappyBuffer); + } else { + ALOGW("Unable to allocate memory to preload the animation"); + } + fclose(fd); + } +#endif + return NO_ERROR; } @@ -446,7 +482,7 @@ bool BootAnimation::movie() const String8 path(entryName.getPathDir()); const String8 leaf(entryName.getPathLeaf()); if (leaf.size() > 0) { - for (int j=0 ; j + 48 * 1024 * 1024) ? 1 : 0; + #endif + glBindTexture(GL_TEXTURE_2D, 0); for (int r=0 ; !part.count || r 0) { + if (r > 0 && !noTextureCache) { glBindTexture(GL_TEXTURE_2D, frame.tid); } else { if (part.count != 1) { @@ -553,6 +598,9 @@ bool BootAnimation::movie() } checkExit(); + + if (noTextureCache) + glDeleteTextures(1, &frame.tid); } usleep(part.pause * ns2us(frameDuration)); @@ -563,8 +611,8 @@ bool BootAnimation::movie() } // free the textures for this part - if (part.count != 1) { - for (int j=0 ; j 1) { diff --git a/core/java/android/accounts/ChooseAccountTypeActivity.java b/core/java/android/accounts/ChooseAccountTypeActivity.java index acc85496ba2..5b3b553fa78 100644 --- a/core/java/android/accounts/ChooseAccountTypeActivity.java +++ b/core/java/android/accounts/ChooseAccountTypeActivity.java @@ -134,7 +134,6 @@ private void buildTypeToAuthDescriptionMap() { if (sequence != null) { name = sequence.toString(); } - name = sequence.toString(); } catch (PackageManager.NameNotFoundException e) { // Nothing we can do much here, just log if (Log.isLoggable(TAG, Log.WARN)) { diff --git a/core/java/android/app/ActionBarDrawerToggle.java b/core/java/android/app/ActionBarDrawerToggle.java new file mode 100644 index 00000000000..6faa334a5c3 --- /dev/null +++ b/core/java/android/app/ActionBarDrawerToggle.java @@ -0,0 +1,591 @@ +/* + * 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.app; + +import java.lang.reflect.Method; + +import android.R; +import android.app.Activity; +import android.content.res.Configuration; +import android.content.res.TypedArray; +import android.graphics.Canvas; +import android.graphics.ColorFilter; +import android.graphics.PorterDuff; +import android.graphics.Rect; +import android.graphics.Region; +import android.graphics.drawable.Drawable; +import android.os.Build; +import android.util.Log; +import android.view.Gravity; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; +import android.widget.DrawerLayout; +import android.widget.ImageView; + +/** + * This class provides a handy way to tie together the functionality of + * {@link DrawerLayout} and the framework ActionBar to implement the recommended + * design for navigation drawers. + * + *

To use ActionBarDrawerToggle, create one in your Activity and call through + * to the following methods corresponding to your Activity callbacks:

+ * + *
    + *
  • {@link Activity#onConfigurationChanged(android.content.res.Configuration) onConfigurationChanged}
  • + *
  • {@link Activity#onOptionsItemSelected(android.view.MenuItem) onOptionsItemSelected}
  • + *
+ * + *

Call {@link #syncState()} from your Activity's + * {@link Activity#onPostCreate(android.os.Bundle) onPostCreate} to synchronize the indicator + * with the state of the linked DrawerLayout after onRestoreInstanceState + * has occurred.

+ * + *

ActionBarDrawerToggle can be used directly as a + * {@link DrawerLayout.DrawerListener}, or if you are already providing your own listener, + * call through to each of the listener methods from your own.

+ */ +public class ActionBarDrawerToggle implements DrawerLayout.DrawerListener { + + private interface ActionBarDrawerToggleImpl { + Drawable getThemeUpIndicator(Activity activity); + Object setActionBarUpIndicator(Object info, Activity activity, + Drawable themeImage, int contentDescRes); + Object setActionBarDescription(Object info, Activity activity, int contentDescRes); + } + + private static class ActionBarDrawerToggleImplBase implements ActionBarDrawerToggleImpl { + @Override + public Drawable getThemeUpIndicator(Activity activity) { + return null; + } + + @Override + public Object setActionBarUpIndicator(Object info, Activity activity, + Drawable themeImage, int contentDescRes) { + // No action bar to set. + return info; + } + + @Override + public Object setActionBarDescription(Object info, Activity activity, int contentDescRes) { + // No action bar to set + return info; + } + } + + private static class ActionBarDrawerToggleImplHC implements ActionBarDrawerToggleImpl { + @Override + public Drawable getThemeUpIndicator(Activity activity) { + return ActionBarDrawerToggleHoneycomb.getThemeUpIndicator(activity); + } + + @Override + public Object setActionBarUpIndicator(Object info, Activity activity, + Drawable themeImage, int contentDescRes) { + return ActionBarDrawerToggleHoneycomb.setActionBarUpIndicator(info, activity, + themeImage, contentDescRes); + } + + @Override + public Object setActionBarDescription(Object info, Activity activity, int contentDescRes) { + return ActionBarDrawerToggleHoneycomb.setActionBarDescription(info, activity, + contentDescRes); + } + } + + private static final ActionBarDrawerToggleImpl IMPL; + + static { + final int version = Build.VERSION.SDK_INT; + if (version >= 11) { + IMPL = new ActionBarDrawerToggleImplHC(); + } else { + IMPL = new ActionBarDrawerToggleImplBase(); + } + } + + // android.R.id.home as defined by public API in v11 + private static final int ID_HOME = 0x0102002c; + + private final Activity mActivity; + private final DrawerLayout mDrawerLayout; + private boolean mDrawerIndicatorEnabled = true; + + private Drawable mThemeImage; + private Drawable mDrawerImage; + private SlideDrawable mSlider; + private final int mDrawerImageResource; + private final int mOpenDrawerContentDescRes; + private final int mCloseDrawerContentDescRes; + + private Object mSetIndicatorInfo; + + /** + * Construct a new ActionBarDrawerToggle. + * + *

The given {@link Activity} will be linked to the specified {@link DrawerLayout}. + * The provided drawer indicator drawable will animate slightly off-screen as the drawer + * is opened, indicating that in the open state the drawer will move off-screen when pressed + * and in the closed state the drawer will move on-screen when pressed.

+ * + *

String resources must be provided to describe the open/close drawer actions for + * accessibility services.

+ * + * @param activity The Activity hosting the drawer + * @param drawerLayout The DrawerLayout to link to the given Activity's ActionBar + * @param drawerImageRes A Drawable resource to use as the drawer indicator + * @param openDrawerContentDescRes A String resource to describe the "open drawer" action + * for accessibility + * @param closeDrawerContentDescRes A String resource to describe the "close drawer" action + * for accessibility + */ + public ActionBarDrawerToggle(Activity activity, DrawerLayout drawerLayout, + int drawerImageRes, int openDrawerContentDescRes, int closeDrawerContentDescRes) { + mActivity = activity; + mDrawerLayout = drawerLayout; + mDrawerImageResource = drawerImageRes; + mOpenDrawerContentDescRes = openDrawerContentDescRes; + mCloseDrawerContentDescRes = closeDrawerContentDescRes; + + mThemeImage = IMPL.getThemeUpIndicator(activity); + mDrawerImage = activity.getResources().getDrawable(drawerImageRes); + mSlider = new SlideDrawable(mDrawerImage); + mSlider.setOffsetBy(1.f / 3); + } + + /** + * Synchronize the state of the drawer indicator/affordance with the linked DrawerLayout. + * + *

This should be called from your Activity's + * {@link Activity#onPostCreate(android.os.Bundle) onPostCreate} method to synchronize after + * the DrawerLayout's instance state has been restored, and any other time when the state + * may have diverged in such a way that the ActionBarDrawerToggle was not notified. + * (For example, if you stop forwarding appropriate drawer events for a period of time.)

+ */ + public void syncState() { + if (mDrawerLayout.isDrawerOpen(Gravity.START)) { + mSlider.setOffset(1.f); + } else { + mSlider.setOffset(0.f); + } + + if (mDrawerIndicatorEnabled) { + mSetIndicatorInfo = IMPL.setActionBarUpIndicator(mSetIndicatorInfo, mActivity, + mSlider, mDrawerLayout.isDrawerOpen(Gravity.START) ? + mOpenDrawerContentDescRes : mCloseDrawerContentDescRes); + } + } + + /** + * Enable or disable the drawer indicator. The indicator defaults to enabled. + * + *

When the indicator is disabled, the ActionBar will revert to displaying + * the home-as-up indicator provided by the Activity's theme in the + * android.R.attr.homeAsUpIndicator attribute instead of the animated + * drawer glyph.

+ * + * @param enable true to enable, false to disable + */ + public void setDrawerIndicatorEnabled(boolean enable) { + if (enable != mDrawerIndicatorEnabled) { + if (enable) { + mSetIndicatorInfo = IMPL.setActionBarUpIndicator(mSetIndicatorInfo, + mActivity, mSlider, mDrawerLayout.isDrawerOpen(Gravity.START) ? + mOpenDrawerContentDescRes : mCloseDrawerContentDescRes); + } else { + mSetIndicatorInfo = IMPL.setActionBarUpIndicator(mSetIndicatorInfo, + mActivity, mThemeImage, 0); + } + mDrawerIndicatorEnabled = enable; + } + } + + /** + * @return true if the enhanced drawer indicator is enabled, false otherwise + * @see #setDrawerIndicatorEnabled(boolean) + */ + public boolean isDrawerIndicatorEnabled() { + return mDrawerIndicatorEnabled; + } + + /** + * This method should always be called by your Activity's + * {@link Activity#onConfigurationChanged(android.content.res.Configuration) onConfigurationChanged} + * method. + * + * @param newConfig The new configuration + */ + public void onConfigurationChanged(Configuration newConfig) { + // Reload drawables that can change with configuration + mThemeImage = IMPL.getThemeUpIndicator(mActivity); + mDrawerImage = mActivity.getResources().getDrawable(mDrawerImageResource); + syncState(); + } + + /** + * This method should be called by your Activity's + * {@link Activity#onOptionsItemSelected(android.view.MenuItem) onOptionsItemSelected} method. + * If it returns true, your onOptionsItemSelected method should return true and + * skip further processing. + * + * @param item the MenuItem instance representing the selected menu item + * @return true if the event was handled and further processing should not occur + */ + public boolean onOptionsItemSelected(MenuItem item) { + if (item != null && item.getItemId() == ID_HOME && mDrawerIndicatorEnabled) { + if (mDrawerLayout.isDrawerVisible(Gravity.START)) { + mDrawerLayout.closeDrawer(Gravity.START); + } else { + mDrawerLayout.openDrawer(Gravity.START); + } + } + return false; + } + + /** + * {@link DrawerLayout.DrawerListener} callback method. If you do not use your + * ActionBarDrawerToggle instance directly as your DrawerLayout's listener, you should call + * through to this method from your own listener object. + * + * @param drawerView The child view that was moved + * @param slideOffset The new offset of this drawer within its range, from 0-1 + */ + @Override + public void onDrawerSlide(View drawerView, float slideOffset) { + float glyphOffset = mSlider.getOffset(); + if (slideOffset > 0.5f) { + glyphOffset = Math.max(glyphOffset, Math.max(0.f, slideOffset - 0.5f) * 2); + } else { + glyphOffset = Math.min(glyphOffset, slideOffset * 2); + } + mSlider.setOffset(glyphOffset); + } + + /** + * {@link DrawerLayout.DrawerListener} callback method. If you do not use your + * ActionBarDrawerToggle instance directly as your DrawerLayout's listener, you should call + * through to this method from your own listener object. + * + * @param drawerView Drawer view that is now open + */ + @Override + public void onDrawerOpened(View drawerView) { + mSlider.setOffset(1.f); + if (mDrawerIndicatorEnabled) { + mSetIndicatorInfo = IMPL.setActionBarDescription(mSetIndicatorInfo, mActivity, + mOpenDrawerContentDescRes); + } + } + + /** + * {@link DrawerLayout.DrawerListener} callback method. If you do not use your + * ActionBarDrawerToggle instance directly as your DrawerLayout's listener, you should call + * through to this method from your own listener object. + * + * @param drawerView Drawer view that is now closed + */ + @Override + public void onDrawerClosed(View drawerView) { + mSlider.setOffset(0.f); + if (mDrawerIndicatorEnabled) { + mSetIndicatorInfo = IMPL.setActionBarDescription(mSetIndicatorInfo, mActivity, + mCloseDrawerContentDescRes); + } + } + + /** + * {@link DrawerLayout.DrawerListener} callback method. If you do not use your + * ActionBarDrawerToggle instance directly as your DrawerLayout's listener, you should call + * through to this method from your own listener object. + * + * @param newState The new drawer motion state + */ + @Override + public void onDrawerStateChanged(int newState) { + } + + private static class SlideDrawable extends Drawable implements Drawable.Callback { + private Drawable mWrapped; + private float mOffset; + private float mOffsetBy; + + private final Rect mTmpRect = new Rect(); + + public SlideDrawable(Drawable wrapped) { + mWrapped = wrapped; + } + + public void setOffset(float offset) { + mOffset = offset; + invalidateSelf(); + } + + public float getOffset() { + return mOffset; + } + + public void setOffsetBy(float offsetBy) { + mOffsetBy = offsetBy; + invalidateSelf(); + } + + @Override + public void draw(Canvas canvas) { + mWrapped.copyBounds(mTmpRect); + canvas.save(); + canvas.translate(mOffsetBy * mTmpRect.width() * -mOffset, 0); + mWrapped.draw(canvas); + canvas.restore(); + } + + @Override + public void setChangingConfigurations(int configs) { + mWrapped.setChangingConfigurations(configs); + } + + @Override + public int getChangingConfigurations() { + return mWrapped.getChangingConfigurations(); + } + + @Override + public void setDither(boolean dither) { + mWrapped.setDither(dither); + } + + @Override + public void setFilterBitmap(boolean filter) { + mWrapped.setFilterBitmap(filter); + } + + @Override + public void setAlpha(int alpha) { + mWrapped.setAlpha(alpha); + } + + @Override + public void setColorFilter(ColorFilter cf) { + mWrapped.setColorFilter(cf); + } + + @Override + public void setColorFilter(int color, PorterDuff.Mode mode) { + mWrapped.setColorFilter(color, mode); + } + + @Override + public void clearColorFilter() { + mWrapped.clearColorFilter(); + } + + @Override + public boolean isStateful() { + return mWrapped.isStateful(); + } + + @Override + public boolean setState(int[] stateSet) { + return mWrapped.setState(stateSet); + } + + @Override + public int[] getState() { + return mWrapped.getState(); + } + + @Override + public Drawable getCurrent() { + return mWrapped.getCurrent(); + } + + @Override + public boolean setVisible(boolean visible, boolean restart) { + return super.setVisible(visible, restart); + } + + @Override + public int getOpacity() { + return mWrapped.getOpacity(); + } + + @Override + public Region getTransparentRegion() { + return mWrapped.getTransparentRegion(); + } + + @Override + protected boolean onStateChange(int[] state) { + mWrapped.setState(state); + return super.onStateChange(state); + } + + @Override + protected void onBoundsChange(Rect bounds) { + super.onBoundsChange(bounds); + mWrapped.setBounds(bounds); + } + + @Override + public int getIntrinsicWidth() { + return mWrapped.getIntrinsicWidth(); + } + + @Override + public int getIntrinsicHeight() { + return mWrapped.getIntrinsicHeight(); + } + + @Override + public int getMinimumWidth() { + return mWrapped.getMinimumWidth(); + } + + @Override + public int getMinimumHeight() { + return mWrapped.getMinimumHeight(); + } + + @Override + public boolean getPadding(Rect padding) { + return mWrapped.getPadding(padding); + } + + @Override + public ConstantState getConstantState() { + return super.getConstantState(); + } + + @Override + public void invalidateDrawable(Drawable who) { + if (who == mWrapped) { + invalidateSelf(); + } + } + + @Override + public void scheduleDrawable(Drawable who, Runnable what, long when) { + if (who == mWrapped) { + scheduleSelf(what, when); + } + } + + @Override + public void unscheduleDrawable(Drawable who, Runnable what) { + if (who == mWrapped) { + unscheduleSelf(what); + } + } + } + + static class ActionBarDrawerToggleHoneycomb { + private static final String TAG = "ActionBarDrawerToggleHoneycomb"; + + private static final int[] THEME_ATTRS = new int[] { + R.attr.homeAsUpIndicator + }; + + public static Object setActionBarUpIndicator(Object info, Activity activity, + Drawable drawable, int contentDescRes) { + if (info == null) { + info = new SetIndicatorInfo(activity); + } + final SetIndicatorInfo sii = (SetIndicatorInfo) info; + if (sii.setHomeAsUpIndicator != null) { + try { + final ActionBar actionBar = activity.getActionBar(); + sii.setHomeAsUpIndicator.invoke(actionBar, drawable); + sii.setHomeActionContentDescription.invoke(actionBar, contentDescRes); + } catch (Exception e) { + Log.w(TAG, "Couldn't set home-as-up indicator via JB-MR2 API", e); + } + } else if (sii.upIndicatorView != null) { + sii.upIndicatorView.setImageDrawable(drawable); + } else { + Log.w(TAG, "Couldn't set home-as-up indicator"); + } + return info; + } + + public static Object setActionBarDescription(Object info, Activity activity, + int contentDescRes) { + if (info == null) { + info = new SetIndicatorInfo(activity); + } + final SetIndicatorInfo sii = (SetIndicatorInfo) info; + if (sii.setHomeAsUpIndicator != null) { + try { + final ActionBar actionBar = activity.getActionBar(); + sii.setHomeActionContentDescription.invoke(actionBar, contentDescRes); + } catch (Exception e) { + Log.w(TAG, "Couldn't set content description via JB-MR2 API", e); + } + } + return info; + } + + public static Drawable getThemeUpIndicator(Activity activity) { + final TypedArray a = activity.obtainStyledAttributes(THEME_ATTRS); + final Drawable result = a.getDrawable(0); + a.recycle(); + return result; + } + + private static class SetIndicatorInfo { + public Method setHomeAsUpIndicator; + public Method setHomeActionContentDescription; + public ImageView upIndicatorView; + + SetIndicatorInfo(Activity activity) { + try { + setHomeAsUpIndicator = ActionBar.class.getDeclaredMethod("setHomeAsUpIndicator", + Drawable.class); + setHomeActionContentDescription = ActionBar.class.getDeclaredMethod( + "setHomeActionContentDescription", Integer.TYPE); + + // If we got the method we won't need the stuff below. + return; + } catch (NoSuchMethodException e) { + // Oh well. We'll use the other mechanism below instead. + } + + final View home = activity.findViewById(android.R.id.home); + if (home == null) { + // Action bar doesn't have a known configuration, an OEM messed with things. + return; + } + + final ViewGroup parent = (ViewGroup) home.getParent(); + final int childCount = parent.getChildCount(); + if (childCount != 2) { + // No idea which one will be the right one, an OEM messed with things. + return; + } + + final View first = parent.getChildAt(0); + final View second = parent.getChildAt(1); + final View up = first.getId() == android.R.id.home ? second : first; + + if (up instanceof ImageView) { + // Jackpot! (Probably...) + upIndicatorView = (ImageView) up; + } + } + } + } +} diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java index 6b5df7f74c7..3af93467b6f 100644 --- a/core/java/android/app/Activity.java +++ b/core/java/android/app/Activity.java @@ -47,21 +47,27 @@ import android.os.Looper; import android.os.Parcelable; import android.os.RemoteException; +import android.os.ServiceManager; import android.os.StrictMode; import android.os.UserHandle; +import android.provider.Settings; import android.text.Selection; import android.text.SpannableStringBuilder; import android.text.TextUtils; import android.text.method.TextKeyListener; import android.util.AttributeSet; import android.util.EventLog; +import android.util.DisplayMetrics; import android.util.Log; +import android.util.TypedValue; import android.util.Slog; import android.util.SparseArray; import android.view.ActionMode; import android.view.ContextMenu; import android.view.ContextMenu.ContextMenuInfo; import android.view.ContextThemeWrapper; +import android.view.Display; +import android.view.Gravity; import android.view.KeyEvent; import android.view.LayoutInflater; import android.view.Menu; @@ -79,6 +85,8 @@ import android.view.accessibility.AccessibilityEvent; import android.widget.AdapterView; +import com.android.internal.statusbar.IStatusBarService; + import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.ArrayList; @@ -695,6 +703,9 @@ private static class ManagedDialog { private SearchManager mSearchManager; private MenuInflater mMenuInflater; + private IStatusBarService mStatusBarService; + private Object mServiceAquireLock = new Object(); + static final class NonConfigurationInstances { Object activity; HashMap children; @@ -716,6 +727,10 @@ static final class NonConfigurationInstances { private CharSequence mTitle; private int mTitleColor = 0; + private boolean mQuickPeekAction = false; + private boolean mNtQsShadeActive = false; + private float mQuickPeekInitialY; + final FragmentManagerImpl mFragments = new FragmentManagerImpl(); final FragmentContainer mContainer = new FragmentContainer() { @Override @@ -1473,6 +1488,9 @@ public void onConfigurationChanged(Configuration newConfig) { if (mWindow != null) { // Pass the configuration changed event to the window mWindow.onConfigurationChanged(newConfig); + if (mWindow.mIsFloatingWindow) { + scaleFloatingWindow(null); + } } if (mActionBar != null) { @@ -2418,15 +2436,72 @@ public boolean dispatchKeyShortcutEvent(KeyEvent event) { * @return boolean Return true if this event was consumed. */ public boolean dispatchTouchEvent(MotionEvent ev) { - if (ev.getAction() == MotionEvent.ACTION_DOWN) { - onUserInteraction(); + final int action = ev.getAction(); + switch (action) { + case MotionEvent.ACTION_DOWN: + if (Settings.System.getInt(getContentResolver(), + Settings.System.STATUSBAR_PEEK, 0) == 1) { + if (ev.getY() < getStatusBarHeight()) { + mQuickPeekInitialY = ev.getY(); + mQuickPeekAction = true; + } else if (mNtQsShadeActive) { + try { + IStatusBarService statusbar = getStatusBarService(); + if (statusbar != null) { + statusbar.toggleStatusBar(false); + } + mNtQsShadeActive = false; + } catch (RemoteException e) { + Slog.e(TAG, "RemoteException during toggle statusbar", e); + mStatusBarService = null; + } + } + } + onUserInteraction(); + break; + case MotionEvent.ACTION_MOVE: + if (!mQuickPeekAction) { + break; + } + if (Math.abs(ev.getY() - mQuickPeekInitialY) > getStatusBarHeight()) { + try { + IStatusBarService statusbar = getStatusBarService(); + if (statusbar != null) { + statusbar.toggleStatusBar(true); + } + mNtQsShadeActive = true; + mQuickPeekAction = false; + } catch (RemoteException e) { + Slog.e(TAG, "RemoteException during toggle statusbar", e); + mStatusBarService = null; + } + } + break; + default: + mQuickPeekAction = false; + break; } + if (getWindow().superDispatchTouchEvent(ev)) { return true; } return onTouchEvent(ev); } - + + private int getStatusBarHeight() { + return getResources().getDimensionPixelSize(com.android.internal.R.dimen.status_bar_height); + } + + IStatusBarService getStatusBarService() { + synchronized (mServiceAquireLock) { + if (mStatusBarService == null) { + mStatusBarService = IStatusBarService.Stub.asInterface( + ServiceManager.getService("statusbar")); + } + return mStatusBarService; + } + } + /** * Called to process trackball events. You can override this to * intercept all trackball events before they are dispatched to the @@ -4185,6 +4260,10 @@ public void finish() { } } + public void finishFloating() { + mMainThread.performFinishFloating(); + } + /** * Finish this activity as well as all activities immediately below it * in the current task that have the same affinity. This is typically @@ -5089,7 +5168,46 @@ final void attach(Context context, ActivityThread aThread, mFragments.attachActivity(this, mContainer, null); - mWindow = PolicyManager.makeNewWindow(this); + boolean floating = (intent.getFlags()&Intent.FLAG_FLOATING_WINDOW) == Intent.FLAG_FLOATING_WINDOW; + boolean mWeWantPopups = (Settings.System.getInt(getContentResolver(), Settings.System.WE_WANT_POPUPS, 1) == 1); + boolean history = (intent.getFlags()&Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY) == Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY; + if ((intent != null) && floating && mWeWantPopups) { + TypedArray styleArray = context.obtainStyledAttributes(info.theme, com.android.internal.R.styleable.Window); + TypedValue backgroundValue = styleArray.peekValue(com.android.internal.R.styleable.Window_windowBackground); + + // Apps that have no title don't need no title bar + TypedValue outValue = new TypedValue(); + boolean result = styleArray.getValue(com.android.internal.R.styleable.Window_windowNoTitle, outValue); + + if (backgroundValue != null && backgroundValue.toString().contains("light")) { + context.getTheme().applyStyle(com.android.internal.R.style.Theme_DeviceDefault_FloatingWindowLight, true); + } else { + context.getTheme().applyStyle(com.android.internal.R.style.Theme_DeviceDefault_FloatingWindow, true); + } + + parent = null; + + // Create our new window + mWindow = PolicyManager.makeNewWindow(this); + mWindow.mIsFloatingWindow = true; + mWindow.setCloseOnTouchOutsideIfNotSet(true); + mWindow.setGravity(Gravity.CENTER); + + if (this instanceof LayerActivity || android.os.Process.myUid() == android.os.Process.SYSTEM_UID) { + mWindow.setFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND, + WindowManager.LayoutParams.FLAG_DIM_BEHIND); + WindowManager.LayoutParams params = mWindow.getAttributes(); + params.alpha = 1f; + params.dimAmount = 0.25f; + mWindow.setAttributes((android.view.WindowManager.LayoutParams) params); + } + + // Scale it + scaleFloatingWindow(context); + } else { + mWindow = PolicyManager.makeNewWindow(this); + } + mWindow.setCallback(this); mWindow.getLayoutInflater().setPrivateFactory(this); if (info.softInputMode != WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED) { @@ -5124,6 +5242,26 @@ final void attach(Context context, ActivityThread aThread, mCurrentConfig = config; } + private void scaleFloatingWindow(Context context) { + if (!mWindow.mIsFloatingWindow) { + return; + } + WindowManager wm = null; + if (context != null) { + wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); + } else { + wm = (WindowManager) getSystemService(Context.WINDOW_SERVICE); + } + Display display = wm.getDefaultDisplay(); + DisplayMetrics metrics = new DisplayMetrics(); + display.getMetrics(metrics); + if (metrics.heightPixels > metrics.widthPixels) { + mWindow.setLayout((int)(metrics.widthPixels * 0.9f), (int)(metrics.heightPixels * 0.7f)); + } else { + mWindow.setLayout((int)(metrics.widthPixels * 0.7f), (int)(metrics.heightPixels * 0.8f)); + } + } + /** @hide */ public final IBinder getActivityToken() { return mParent != null ? mParent.getActivityToken() : mToken; @@ -5293,6 +5431,14 @@ final void performStop() { mStopped = true; } mResumed = false; + + // Floatingwindows activities should be kept volatile to prevent new activities taking + // up front in a minimized space. Every stop call, for instance when pressing home, + // will terminate the activity. If the activity is already finishing we might just + // as well let it go. + if (!mChangingConfigurations && mWindow != null && mWindow.mIsFloatingWindow && !isFinishing()) { + finish(); + } } final void performDestroy() { diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java index a25e311f882..238f8dc6baa 100644 --- a/core/java/android/app/ActivityManager.java +++ b/core/java/android/app/ActivityManager.java @@ -1,5 +1,6 @@ /* * Copyright (C) 2007 The Android Open Source Project + * This code has been modified. Portions copyright (C) 2010, T-Mobile USA, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -29,6 +30,7 @@ import android.content.pm.IPackageDataObserver; import android.content.pm.PackageManager; import android.content.pm.UserInfo; +import android.content.res.Configuration; import android.content.res.Resources; import android.graphics.Bitmap; import android.graphics.Point; @@ -64,6 +66,9 @@ public class ActivityManager { private final Context mContext; private final Handler mHandler; + private static long memSize; + private static long totalSize; + private static int pixels; /** * Result for IActivityManager.startActivity: an error where the @@ -379,20 +384,27 @@ static public int staticGetLargeMemoryClass() { * (which tends to consume a lot more RAM). * @hide */ + static public boolean isHighEndGfx() { - MemInfoReader reader = new MemInfoReader(); - reader.readMemInfo(); - if (reader.getTotalSize() >= (512*1024*1024)) { - // If the device has at least 512MB RAM available to the kernel, - // we can afford the overhead of graphics acceleration. + + if (totalSize == 0) { + MemInfoReader reader = new MemInfoReader(); + reader.readMemInfo(); + totalSize = reader.getTotalSize(); + reader = null; + } + + if (totalSize >= (512*1024*1024)) { return true; } - Display display = DisplayManagerGlobal.getInstance().getRealDisplay( + if (pixels == 0) { + Display display = DisplayManagerGlobal.getInstance().getRealDisplay( Display.DEFAULT_DISPLAY); - Point p = new Point(); - display.getRealSize(p); - int pixels = p.x * p.y; + Point p = new Point(); + display.getRealSize(p); + pixels = p.x * p.y; + } if (pixels >= (1024*600)) { // If this is a sufficiently large screen, then there are enough // pixels on it that we'd really like to use hw drawing. @@ -409,9 +421,16 @@ static public boolean isHighEndGfx() { * @hide */ static public boolean isLargeRAM() { - MemInfoReader reader = new MemInfoReader(); - reader.readMemInfo(); - if (reader.getTotalSize() >= (640*1024*1024)) { + + if (memSize == 0) { + MemInfoReader reader = new MemInfoReader(); + reader.readMemInfo(); + memSize = reader.getTotalSize(); + reader = null; + } + + + if (memSize >= (640*1024*1024)) { // Currently 640MB RAM available to the kernel is the point at // which we have plenty of RAM to spare. return true; @@ -516,6 +535,11 @@ private RecentTaskInfo(Parcel source) { */ public static final int RECENT_IGNORE_UNAVAILABLE = 0x0002; + /** + * @hide + */ + public static final int RECENT_DO_NOT_COUNT_EXCLUDED = 0x0004; + /** * Return a list of the tasks that the user has recently launched, with * the most recent being first and older ones after in order. @@ -1621,6 +1645,16 @@ public List getRunningExternalApplications() { return null; } } + /** + * @hide + */ + public Configuration getConfiguration() { + try { + return ActivityManagerNative.getDefault().getConfiguration(); + } catch (RemoteException e) { + return null; + } + } /** * Returns a list of application processes that are running on the device. @@ -2010,4 +2044,17 @@ public boolean isUserRunning(int userid) { return false; } } + + /** + * @throws SecurityException Throws SecurityException if the caller does + * not hold the {@link android.Manifest.permission#CHANGE_CONFIGURATION} permission. + * + * @hide + */ + public void updateConfiguration(Configuration values) throws SecurityException { + try { + ActivityManagerNative.getDefault().updateConfiguration(values); + } catch (RemoteException e) { + } + } } diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java index d4478bf046d..ae353385c8c 100644 --- a/core/java/android/app/ActivityManagerNative.java +++ b/core/java/android/app/ActivityManagerNative.java @@ -461,6 +461,15 @@ public boolean onTransact(int code, Parcel data, Parcel reply, int flags) return true; } + case GET_CALLING_PACKAGE_FOR_BROADCAST_TRANSACTION: { + data.enforceInterface(IActivityManager.descriptor); + boolean foreground = data.readInt() == 1 ? true : false; + String res = getCallingPackageForBroadcast(foreground); + reply.writeNoException(); + reply.writeString(res); + return true; + } + case GET_CALLING_ACTIVITY_TRANSACTION: { data.enforceInterface(IActivityManager.descriptor); IBinder token = data.readStrongBinder(); @@ -2363,6 +2372,19 @@ public String getCallingPackage(IBinder token) throws RemoteException reply.recycle(); return res; } + public String getCallingPackageForBroadcast(boolean foreground) throws RemoteException + { + Parcel data = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + data.writeInterfaceToken(IActivityManager.descriptor); + data.writeInt(foreground ? 1 : 0); + mRemote.transact(GET_CALLING_PACKAGE_FOR_BROADCAST_TRANSACTION, data, reply, 0); + reply.readException(); + String res = reply.readString(); + data.recycle(); + reply.recycle(); + return res; + } public ComponentName getCallingActivity(IBinder token) throws RemoteException { Parcel data = Parcel.obtain(); diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index d4056c98bee..fe4421f915f 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -1,5 +1,6 @@ /* * Copyright (C) 2006 The Android Open Source Project + * This code has been modified. Portions copyright (C) 2010, T-Mobile USA, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,15 +17,23 @@ package android.app; +import com.android.internal.app.IAssetRedirectionManager; +import com.android.internal.os.BinderInternal; +import com.android.internal.os.RuntimeInit; +import com.android.internal.os.SamplingProfilerIntegration; + +import org.apache.harmony.xnet.provider.jsse.OpenSSLSocketImpl; + import android.app.backup.BackupAgent; import android.content.BroadcastReceiver; import android.content.ComponentCallbacks2; import android.content.ComponentName; import android.content.ContentProvider; import android.content.Context; +import android.content.ContextWrapper; import android.content.IContentProvider; -import android.content.Intent; import android.content.IIntentReceiver; +import android.content.Intent; import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; import android.content.pm.IPackageManager; @@ -37,6 +46,8 @@ import android.content.res.AssetManager; import android.content.res.CompatibilityInfo; import android.content.res.Configuration; +import android.content.res.CustomTheme; +import android.content.res.PackageRedirectionMap; import android.content.res.Resources; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteDebug; @@ -49,6 +60,7 @@ import android.net.ProxyProperties; import android.opengl.GLUtils; import android.os.AsyncTask; +import android.net.Uri; import android.os.Binder; import android.os.Bundle; import android.os.Debug; @@ -68,6 +80,7 @@ import android.os.SystemProperties; import android.os.Trace; import android.os.UserHandle; +import android.text.TextUtils; import android.util.AndroidRuntimeException; import android.util.DisplayMetrics; import android.util.EventLog; @@ -78,6 +91,7 @@ import android.view.CompatibilityInfoHolder; import android.view.Display; import android.view.HardwareRenderer; +import android.view.InflateException; import android.view.View; import android.view.ViewDebug; import android.view.ViewManager; @@ -88,13 +102,8 @@ import android.renderscript.RenderScript; import android.security.AndroidKeyStoreProvider; -import com.android.internal.os.BinderInternal; -import com.android.internal.os.RuntimeInit; -import com.android.internal.os.SamplingProfilerIntegration; import com.android.internal.util.Objects; -import org.apache.harmony.xnet.provider.jsse.OpenSSLSocketImpl; - import java.io.File; import java.io.FileDescriptor; import java.io.FileOutputStream; @@ -117,6 +126,9 @@ import libcore.io.IoUtils; import dalvik.system.CloseGuard; +import dalvik.system.VMRuntime; +import android.os.SystemProperties; +import java.lang.*; final class SuperNotCalledException extends AndroidRuntimeException { public SuperNotCalledException(String msg) { @@ -161,6 +173,7 @@ public final class ActivityThread { static ContextImpl mSystemContext = null; static IPackageManager sPackageManager; + static IAssetRedirectionManager sAssetRedirectionManager; final ApplicationThread mAppThread = new ApplicationThread(); final Looper mLooper = Looper.myLooper(); @@ -1535,9 +1548,10 @@ private static class ResourcesKey { final private int mDisplayId; final private Configuration mOverrideConfiguration; final private float mScale; + final private boolean mIsThemeable; final private int mHash; - ResourcesKey(String resDir, int displayId, Configuration overrideConfiguration, float scale) { + ResourcesKey(String resDir, int displayId, Configuration overrideConfiguration, float scale, boolean isThemeable) { mResDir = resDir; mDisplayId = displayId; if (overrideConfiguration != null) { @@ -1547,12 +1561,14 @@ private static class ResourcesKey { } mOverrideConfiguration = overrideConfiguration; mScale = scale; + mIsThemeable = isThemeable; int hash = 17; hash = 31 * hash + mResDir.hashCode(); hash = 31 * hash + mDisplayId; hash = 31 * hash + (mOverrideConfiguration != null ? mOverrideConfiguration.hashCode() : 0); hash = 31 * hash + Float.floatToIntBits(mScale); + hash = 31 * hash + (mIsThemeable ? 1 : 0); mHash = hash; } @@ -1584,7 +1600,7 @@ public boolean equals(Object obj) { if (mScale != peer.mScale) { return false; } - return true; + return mIsThemeable == peer.mIsThemeable; } } @@ -1625,6 +1641,18 @@ private void flushDisplayMetricsLocked() { mDefaultDisplayMetrics.clear(); } + // NOTE: this method can return null if the SystemServer is still + // initializing (for example, of another SystemServer component is accessing + // a resources object) + public static IAssetRedirectionManager getAssetRedirectionManager() { + if (sAssetRedirectionManager != null) { + return sAssetRedirectionManager; + } + IBinder b = ServiceManager.getService("assetredirection"); + sAssetRedirectionManager = IAssetRedirectionManager.Stub.asInterface(b); + return sAssetRedirectionManager; + } + DisplayMetrics getDisplayMetricsLocked(int displayId, CompatibilityInfo ci) { boolean isDefaultDisplay = (displayId == Display.DEFAULT_DISPLAY); DisplayMetrics dm = isDefaultDisplay ? mDefaultDisplayMetrics.get(ci) : null; @@ -1688,7 +1716,8 @@ Resources getTopLevelResources(String resDir, CompatibilityInfo compInfo) { ResourcesKey key = new ResourcesKey(resDir, displayId, overrideConfiguration, - compInfo.applicationScale); + compInfo.applicationScale, + compInfo.isThemeable); Resources r; synchronized (mPackages) { // Resources is app scale dependent. @@ -1714,6 +1743,7 @@ Resources getTopLevelResources(String resDir, //} AssetManager assets = new AssetManager(); + assets.setThemeSupport(compInfo.isThemeable); if (assets.addAssetPath(resDir) == 0) { return null; } @@ -1733,6 +1763,18 @@ Resources getTopLevelResources(String resDir, } else { config = getConfiguration(); } + + /* Attach theme information to the resulting AssetManager when appropriate. */ + if (compInfo.isThemeable && config != null) { + if (config.customTheme == null) { + config.customTheme = CustomTheme.getBootTheme(); + } + + if (!TextUtils.isEmpty(config.customTheme.getThemePackageName())) { + attachThemeAssets(assets, config.customTheme); + } + } + r = new Resources(assets, dm, config, compInfo); if (false) { Slog.i(TAG, "Created app resources " + resDir + " " + r + ": " @@ -1756,6 +1798,82 @@ Resources getTopLevelResources(String resDir, } } + private void detachThemeAssets(AssetManager assets) { + String themePackageName = assets.getThemePackageName(); + int themeCookie = assets.getThemeCookie(); + if (!TextUtils.isEmpty(themePackageName) && themeCookie != 0) { + assets.detachThemePath(themePackageName, themeCookie); + assets.setThemePackageName(null); + assets.setThemeCookie(0); + assets.clearRedirections(); + } + } + + /** + * Attach the necessary theme asset paths and meta information to convert an + * AssetManager to being globally "theme-aware". + * + * @param assets + * @param theme + * @return true if the AssetManager is now theme-aware; false otherwise. + * This can fail, for example, if the theme package has been been + * removed and the theme manager has yet to revert formally back to + * the framework default. + */ + private boolean attachThemeAssets(AssetManager assets, CustomTheme theme) { + IAssetRedirectionManager rm = getAssetRedirectionManager(); + if (rm == null) { + return false; + } + PackageInfo pi = null; + try { + pi = getPackageManager().getPackageInfo(theme.getThemePackageName(), + 0, UserHandle.myUserId()); + } catch (RemoteException e) { + } + if (pi != null && pi.applicationInfo != null && pi.themeInfos != null) { + String themeResDir = pi.applicationInfo.publicSourceDir; + int cookie = assets.attachThemePath(themeResDir); + if (cookie != 0) { + String themePackageName = theme.getThemePackageName(); + String themeId = theme.getThemeId(); + int N = assets.getBasePackageCount(); + for (int i = 0; i < N; i++) { + String packageName = assets.getBasePackageName(i); + int packageId = assets.getBasePackageId(i); + + /* + * For now, we only consider redirections coming from the + * framework or regular android packages. This excludes + * themes and other specialty APKs we are not aware of. + */ + if (packageId != 0x01 && packageId != 0x7f) { + continue; + } + + try { + PackageRedirectionMap map = rm.getPackageRedirectionMap(themePackageName, themeId, + packageName); + if (map != null) { + assets.addRedirections(map); + } + } catch (RemoteException e) { + Log.e(TAG, "Failure accessing package redirection map, removing theme support."); + assets.detachThemePath(themePackageName, cookie); + return false; + } + } + + assets.setThemePackageName(theme.getThemePackageName()); + assets.setThemeCookie(cookie); + return true; + } else { + Log.e(TAG, "Unable to attach theme assets at " + themeResDir); + } + } + return false; + } + /** * Creates the top level resources for the given package. */ @@ -2058,6 +2176,17 @@ public final Activity getActivity(IBinder token) { return mActivities.get(token).activity; } + protected void performFinishFloating() { + synchronized (mPackages) { + for (ActivityClientRecord ar : mActivities.values()) { + Activity a = ar.activity; + if (a != null && !a.mFinished && a.getWindow() != null && a.getWindow().mIsFloatingWindow) { + a.finish(); + } + } + } + } + public final void sendActivityResult( IBinder token, String id, int requestCode, int resultCode, Intent data) { @@ -2208,6 +2337,16 @@ private Activity performLaunchActivity(ActivityClientRecord r, Intent customInte } catch (Exception e) { if (!mInstrumentation.onException(activity, e)) { + if (e instanceof InflateException) { + Log.e(TAG, "Failed to inflate", e); + String pkg = null; + if (r.packageInfo != null && !TextUtils.isEmpty(r.packageInfo.getPackageName())) { + pkg = r.packageInfo.getPackageName(); + } + Intent intent = new Intent(Intent.ACTION_APP_LAUNCH_FAILURE, + (pkg != null)? Uri.fromParts("package", pkg, null) : null); + getSystemContext().sendBroadcast(intent); + } throw new RuntimeException( "Unable to start activity " + component + ": " + e.toString(), e); @@ -2299,12 +2438,7 @@ private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent) { throw e; } catch (Exception e) { - if (!mInstrumentation.onException(r.activity, e)) { - throw new RuntimeException( - "Unable to pause activity " - + r.intent.getComponent().toShortString() - + ": " + e.toString(), e); - } + // Unable to resume activity } r.paused = true; } @@ -3865,7 +3999,7 @@ public final void applyConfigurationToResources(Configuration config) { } } - final boolean applyConfigurationToResourcesLocked(Configuration config, + final int applyConfigurationToResourcesLocked(Configuration config, CompatibilityInfo compat) { if (mResConfiguration == null) { mResConfiguration = new Configuration(); @@ -3873,7 +4007,7 @@ final boolean applyConfigurationToResourcesLocked(Configuration config, if (!mResConfiguration.isOtherSeqNewer(config) && compat == null) { if (DEBUG_CONFIGURATION) Slog.v(TAG, "Skipping new config: curSeq=" + mResConfiguration.seq + ", newSeq=" + config.seq); - return false; + return 0; } int changes = mResConfiguration.updateFrom(config); flushDisplayMetricsLocked(); @@ -3912,6 +4046,16 @@ final boolean applyConfigurationToResourcesLocked(Configuration config, boolean isDefaultDisplay = (displayId == Display.DEFAULT_DISPLAY); DisplayMetrics dm = defaultDisplayMetrics; Configuration overrideConfig = entry.getKey().mOverrideConfiguration; + boolean themeChanged = (changes & ActivityInfo.CONFIG_THEME_RESOURCE) != 0; + if (themeChanged) { + AssetManager am = r.getAssets(); + if (am.hasThemeSupport()) { + detachThemeAssets(am); + if (!TextUtils.isEmpty(config.customTheme.getThemePackageName())) { + attachThemeAssets(am, config.customTheme); + } + } + } if (!isDefaultDisplay || overrideConfig != null) { if (tmpConfig == null) { tmpConfig = new Configuration(); @@ -3928,6 +4072,9 @@ final boolean applyConfigurationToResourcesLocked(Configuration config, } else { r.updateConfiguration(config, dm, compat); } + if (themeChanged) { + r.updateStringCache(); + } //Slog.i(TAG, "Updated app resources " + v.getKey() // + " " + r + ": " + r.getConfiguration()); } else { @@ -3936,7 +4083,7 @@ final boolean applyConfigurationToResourcesLocked(Configuration config, } } - return changes != 0; + return changes; } final void applyNonDefaultDisplayMetricsToConfigurationLocked( @@ -3978,6 +4125,8 @@ final void handleConfigurationChanged(Configuration config, CompatibilityInfo co int configDiff = 0; + int diff = 0; + synchronized (mPackages) { if (mPendingConfiguration != null) { if (!mPendingConfiguration.isOtherSeqNewer(config)) { @@ -3995,7 +4144,7 @@ final void handleConfigurationChanged(Configuration config, CompatibilityInfo co if (DEBUG_CONFIGURATION) Slog.v(TAG, "Handle configuration changed: " + config); - applyConfigurationToResourcesLocked(config, compat); + diff = applyConfigurationToResourcesLocked(config, compat); if (mConfiguration == null) { mConfiguration = new Configuration(); @@ -4018,7 +4167,20 @@ final void handleConfigurationChanged(Configuration config, CompatibilityInfo co if (callbacks != null) { final int N = callbacks.size(); for (int i=0; i 0 ){ + VMRuntime.getRuntime().setTargetHeapMinFree(heapMinFree); + Log.d(TAG, "setTargetHeapMinFree:" + heapMinFree ); + } + int heapConcurrentStart = SystemProperties.getInt("dalvik.vm.heapconcurrentstart", 0 ); + if( heapConcurrentStart > 0 ){ + VMRuntime.getRuntime().setTargetHeapConcurrentStart(heapConcurrentStart); + Log.d(TAG, "setTargetHeapConcurrentStart:" + heapConcurrentStart ); + } + + //// + ////If want to set application specific GC paramters, can use + ////the following check + //// + //if( data.processName.equals("com.android.gallery3d")) { + // VMRuntime.getRuntime().setTargetHeapUtilization(0.25f); + // VMRuntime.getRuntime().setTargetHeapMinFree(12*1024*1024); + // VMRuntime.getRuntime().setTargetHeapConcurrentStart(4*1024*1024); + //} + + + android.ddm.DdmHandleAppName.setAppName(data.processName, UserHandle.myUserId()); @@ -4999,7 +5191,7 @@ public void onConfigurationChanged(Configuration newConfig) { // We need to apply this change to the resources // immediately, because upon returning the view // hierarchy will be informed about it. - if (applyConfigurationToResourcesLocked(newConfig, null)) { + if (applyConfigurationToResourcesLocked(newConfig, null) != 0) { // This actually changed the resources! Tell // everyone about it. if (mPendingConfiguration == null || diff --git a/core/java/android/app/AirplaneModeSettings.java b/core/java/android/app/AirplaneModeSettings.java new file mode 100644 index 00000000000..7b4022ac942 --- /dev/null +++ b/core/java/android/app/AirplaneModeSettings.java @@ -0,0 +1,133 @@ +package android.app; + +import android.content.Context; +import android.content.Intent; +import android.provider.Settings; +import android.os.Parcel; +import android.os.Parcelable; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + + +import java.io.IOException; + +/** @hide */ +public final class AirplaneModeSettings implements Parcelable { + + private int mValue; + private boolean mOverride; + private boolean mDirty; + + /** @hide */ + public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { + public AirplaneModeSettings createFromParcel(Parcel in) { + return new AirplaneModeSettings(in); + } + + @Override + public AirplaneModeSettings[] newArray(int size) { + return new AirplaneModeSettings[size]; + } + }; + + + public AirplaneModeSettings(Parcel parcel) { + readFromParcel(parcel); + } + + public AirplaneModeSettings() { + this(0, false); + } + + public AirplaneModeSettings(int value, boolean override) { + mValue = value; + mOverride = override; + mDirty = false; + } + + public int getValue() { + return mValue; + } + + public void setValue(int value) { + mValue = value; + mDirty = true; + } + + public void setOverride(boolean override) { + mOverride = override; + mDirty = true; + } + + public boolean isOverride() { + return mOverride; + } + + /** @hide */ + public boolean isDirty() { + return mDirty; + } + + public void processOverride(Context context) { + if (isOverride()) { + int current = Settings.Global.getInt(context.getContentResolver(), Settings.Global.AIRPLANE_MODE_ON, 0); + if (current != mValue) { + Settings.Global.putInt(context.getContentResolver(), Settings.Global.AIRPLANE_MODE_ON, mValue); + Intent intent = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED); + intent.putExtra("state", mValue == 1); + context.sendBroadcast(intent); + } + } + } + + /** @hide */ + public static AirplaneModeSettings fromXml(XmlPullParser xpp, Context context) + throws XmlPullParserException, IOException { + int event = xpp.next(); + AirplaneModeSettings connectionDescriptor = new AirplaneModeSettings(); + while (event != XmlPullParser.END_TAG) { + if (event == XmlPullParser.START_TAG) { + String name = xpp.getName(); + if (name.equals("value")) { + connectionDescriptor.mValue = Integer.parseInt(xpp.nextText()); + } else if (name.equals("override")) { + connectionDescriptor.mOverride = Boolean.parseBoolean(xpp.nextText()); + } + } + event = xpp.next(); + } + return connectionDescriptor; + } + + /** @hide */ + public void getXmlString(StringBuilder builder, Context context) { + builder.append("\n"); + builder.append(mValue); + builder.append("\n"); + builder.append(mOverride); + builder.append("\n\n"); + } + + @Override + public int describeContents() { + return 0; + } + + /** @hide */ + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(mOverride ? 1 : 0); + dest.writeInt(mValue); + dest.writeInt(mDirty ? 1 : 0); + } + + /** @hide */ + public void readFromParcel(Parcel in) { + mOverride = in.readInt() != 0; + mValue = in.readInt(); + mDirty = in.readInt() != 0; + } + + +} diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java index 4fcb18a84b4..3a3b2a5da1f 100644 --- a/core/java/android/app/AppOpsManager.java +++ b/core/java/android/app/AppOpsManager.java @@ -17,6 +17,7 @@ package android.app; import android.Manifest; + import com.android.internal.app.IAppOpsService; import com.android.internal.app.IAppOpsCallback; @@ -61,7 +62,7 @@ public class AppOpsManager { // when adding one of these: // - increment _NUM_OP - // - add rows to sOpToSwitch, sOpNames, sOpPerms + // - add rows to sOpToSwitch, sOpNames, sOpPerms, sPrivacyGuardOp // - add descriptive strings to Settings/res/values/arrays.xml public static final int OP_NONE = -1; public static final int OP_COARSE_LOCATION = 0; @@ -103,7 +104,7 @@ public class AppOpsManager { * switch to determine whether it is allowed. Generally this is * a 1:1 mapping, but for some things (like location) that have * multiple low-level operations being tracked that should be - * presented to hte user as one switch then this can be used to + * presented to the user as one switch then this can be used to * make them all controlled by the same single operation. */ private static int[] sOpToSwitch = new int[] { @@ -216,6 +217,54 @@ public class AppOpsManager { null, // no permission for writing clipboard }; + /** + * Privacy Guard Ops and states need to + * match general Ops map. Unused Ops are flagged as OP_NONE + */ + private static final int[] sPrivacyGuardOp = new int[] { + OP_COARSE_LOCATION, + OP_COARSE_LOCATION, + OP_COARSE_LOCATION, + OP_NONE, + OP_READ_CONTACTS, + OP_WRITE_CONTACTS, + OP_READ_CALL_LOG, + OP_WRITE_CALL_LOG, + OP_READ_CALENDAR, + OP_WRITE_CALENDAR, + OP_COARSE_LOCATION, + OP_NONE, + OP_COARSE_LOCATION, + OP_NONE, + OP_READ_SMS, + OP_WRITE_SMS, + OP_READ_SMS, + OP_READ_SMS, + OP_READ_SMS, + OP_READ_SMS, + OP_WRITE_SMS, + OP_READ_SMS, + OP_WRITE_SMS, + OP_NONE, + OP_NONE, + OP_NONE, + OP_NONE, + OP_NONE, + OP_NONE, + OP_NONE, + OP_NONE, + }; + + /** + * Privacy Guard states + */ + public static final int PRIVACY_GUARD_DISABLED = 0; + public static final int PRIVACY_GUARD_DISABLED_PLUS = 1; + public static final int PRIVACY_GUARD_ENABLED = 2; + public static final int PRIVACY_GUARD_ENABLED_PLUS = 3; + public static final int PRIVACY_GUARD_CUSTOM = 4; + public static final int PRIVACY_GUARD_CUSTOM_PLUS = 5; + /** * Retrieve the op switch that controls the given operation. */ @@ -238,6 +287,19 @@ public static String opToPermission(int op) { return sOpPerms[op]; } + /** + * Retrieve the permission associated privacy guard operation, + * or OP_NONE if there is not one. + */ + public static int getPrivacyGuardOp(String permission) { + for (int i=0; i getPrivacyGuardOpsForPackage(String packageName) { + try { + return mService.getPrivacyGuardOpsForPackage(packageName); + } catch (RemoteException e) { + } + return null; + } + + public int getPrivacyGuardSettingForPackage(int uid, String packageName) { + try { + return mService.getPrivacyGuardSettingForPackage(uid, packageName); + } catch (RemoteException e) { + } + return PRIVACY_GUARD_DISABLED; + } + + public void setPrivacyGuardSettingForPackage(int uid, String packageName, + boolean state, boolean forceAll) { + try { + mService.setPrivacyGuardSettingForPackage(uid, packageName, state, forceAll); + } catch (RemoteException e) { + } + } + + /** + * Retrieve the privacy guard state associated icons for notification and settings + */ + public static int getPrivacyGuardIconResId(int pgState) { + switch (pgState) { + case PRIVACY_GUARD_DISABLED: + return com.android.internal.R.drawable.stat_notify_privacy_guard_off; + case PRIVACY_GUARD_ENABLED: + return com.android.internal.R.drawable.stat_notify_privacy_guard; + case PRIVACY_GUARD_CUSTOM: + return com.android.internal.R.drawable.stat_notify_privacy_guard_custom; + case PRIVACY_GUARD_DISABLED_PLUS: + return com.android.internal.R.drawable.stat_notify_privacy_guard_off_plus; + case PRIVACY_GUARD_ENABLED_PLUS: + return com.android.internal.R.drawable.stat_notify_privacy_guard_plus; + case PRIVACY_GUARD_CUSTOM_PLUS: + return com.android.internal.R.drawable.stat_notify_privacy_guard_custom_plus; + } + return com.android.internal.R.drawable.stat_notify_privacy_guard_off; + } } diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java index 271494f1d21..69f349aa8e3 100644 --- a/core/java/android/app/ApplicationPackageManager.java +++ b/core/java/android/app/ApplicationPackageManager.java @@ -448,6 +448,19 @@ public List getPackagesHoldingPermissions( } } + @Override + public List getInstalledThemePackages() { + // Returns a list of theme APKs. + ArrayList finalList = new ArrayList(); + List installedPackagesList = getInstalledPackages(0); + for (PackageInfo pi : installedPackagesList) { + if (pi != null && pi.isThemeApk) { + finalList.add(pi); + } + } + return finalList; + } + @SuppressWarnings("unchecked") @Override public List getInstalledApplications(int flags) { diff --git a/core/java/android/app/ConnectionSettings.java b/core/java/android/app/ConnectionSettings.java new file mode 100644 index 00000000000..09eb0b9984b --- /dev/null +++ b/core/java/android/app/ConnectionSettings.java @@ -0,0 +1,269 @@ +package android.app; + +import android.bluetooth.BluetoothAdapter; +import android.content.ContentResolver; +import android.content.Context; +import android.content.Intent; +import android.location.LocationManager; +import android.net.ConnectivityManager; +import android.net.wifi.WifiManager; +import android.net.wimax.WimaxHelper; +import android.nfc.NfcAdapter; +import android.os.Parcel; +import android.os.Parcelable; +import android.provider.Settings; +import com.android.internal.telephony.RILConstants; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + +import java.io.IOException; + +/** @hide */ +public final class ConnectionSettings implements Parcelable { + + private int mConnectionId; + private int mValue; + private boolean mOverride; + private boolean mDirty; + + public static final int PROFILE_CONNECTION_MOBILEDATA = 0; + public static final int PROFILE_CONNECTION_WIFI = 1; + public static final int PROFILE_CONNECTION_WIFIAP = 2; + public static final int PROFILE_CONNECTION_WIMAX = 3; + public static final int PROFILE_CONNECTION_GPS = 4; + public static final int PROFILE_CONNECTION_SYNC = 5; + public static final int PROFILE_CONNECTION_BLUETOOTH = 7; + public static final int PROFILE_CONNECTION_NFC = 8; + public static final int PROFILE_CONNECTION_2G3G = 9; + + // retrieved from Phone.apk + private static final String ACTION_MODIFY_NETWORK_MODE = "com.android.internal.telephony.MODIFY_NETWORK_MODE"; + private static final String EXTRA_NETWORK_MODE = "networkMode"; + + private static final int CM_MODE_2G = 0; + private static final int CM_MODE_3G = 1; + private static final int CM_MODE_BOTH = 2; + + /** @hide */ + public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { + public ConnectionSettings createFromParcel(Parcel in) { + return new ConnectionSettings(in); + } + + @Override + public ConnectionSettings[] newArray(int size) { + return new ConnectionSettings[size]; + } + }; + + + public ConnectionSettings(Parcel parcel) { + readFromParcel(parcel); + } + + public ConnectionSettings(int connectionId) { + this(connectionId, 0, false); + } + + public ConnectionSettings(int connectionId, int value, boolean override) { + mConnectionId = connectionId; + mValue = value; + mOverride = override; + mDirty = false; + } + + public int getConnectionId() { + return mConnectionId; + } + + public int getValue() { + return mValue; + } + + public void setValue(int value) { + mValue = value; + mDirty = true; + } + + public void setOverride(boolean override) { + mOverride = override; + mDirty = true; + } + + public boolean isOverride() { + return mOverride; + } + + /** @hide */ + public boolean isDirty() { + return mDirty; + } + + public void processOverride(Context context) { + BluetoothAdapter bta = BluetoothAdapter.getDefaultAdapter(); + LocationManager lm = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE); + WifiManager wm = (WifiManager) context.getSystemService(Context.WIFI_SERVICE); + ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); + NfcAdapter nfcAdapter = null; + try { + nfcAdapter = NfcAdapter.getNfcAdapter(context); + } catch (UnsupportedOperationException e) { + //Nfc not available + } + + boolean forcedState = getValue() == 1; + boolean currentState; + + switch (getConnectionId()) { + case PROFILE_CONNECTION_MOBILEDATA: + currentState = cm.getMobileDataEnabled(); + if (forcedState != currentState) { + cm.setMobileDataEnabled(forcedState); + } + break; + case PROFILE_CONNECTION_2G3G: + Intent intent = new Intent(ACTION_MODIFY_NETWORK_MODE); + switch(getValue()) { + case CM_MODE_2G: + intent.putExtra(EXTRA_NETWORK_MODE, RILConstants.NETWORK_MODE_GSM_ONLY); + break; + case CM_MODE_3G: + intent.putExtra(EXTRA_NETWORK_MODE, RILConstants.NETWORK_MODE_WCDMA_ONLY); + break; + case CM_MODE_BOTH: + intent.putExtra(EXTRA_NETWORK_MODE, RILConstants.NETWORK_MODE_WCDMA_PREF); + break; + default: + return; + } + context.sendBroadcast(intent); + break; + case PROFILE_CONNECTION_BLUETOOTH: + int btstate = bta.getState(); + if (forcedState && (btstate == BluetoothAdapter.STATE_OFF + || btstate == BluetoothAdapter.STATE_TURNING_OFF)) { + bta.enable(); + } else if (!forcedState && (btstate == BluetoothAdapter.STATE_ON + || btstate == BluetoothAdapter.STATE_TURNING_ON)) { + bta.disable(); + } + break; + case PROFILE_CONNECTION_GPS: + currentState = lm.isProviderEnabled(LocationManager.GPS_PROVIDER); + if (currentState != forcedState) { + Settings.Secure.setLocationProviderEnabled(context.getContentResolver(), + LocationManager.GPS_PROVIDER, forcedState); + } + break; + case PROFILE_CONNECTION_SYNC: + currentState = ContentResolver.getMasterSyncAutomatically(); + if (forcedState != currentState) { + ContentResolver.setMasterSyncAutomatically(forcedState); + } + break; + case PROFILE_CONNECTION_WIFI: + int wifiApState = wm.getWifiApState(); + currentState = wm.isWifiEnabled(); + if (currentState != forcedState) { + // Disable wifi tether + if (forcedState && (wifiApState == WifiManager.WIFI_AP_STATE_ENABLING) || + (wifiApState == WifiManager.WIFI_AP_STATE_ENABLED)) { + wm.setWifiApEnabled(null, false); + } + wm.setWifiEnabled(forcedState); + } + break; + case PROFILE_CONNECTION_WIFIAP: + int wifiState = wm.getWifiState(); + currentState = wm.isWifiApEnabled(); + if (currentState != forcedState) { + // Disable wifi + if (forcedState && (wifiState == WifiManager.WIFI_STATE_ENABLING) || + (wifiState == WifiManager.WIFI_STATE_ENABLED)) { + wm.setWifiEnabled(false); + } + wm.setWifiApEnabled(null, forcedState); + } + break; + case PROFILE_CONNECTION_WIMAX: + if (WimaxHelper.isWimaxSupported(context)) { + currentState = WimaxHelper.isWimaxEnabled(context); + if (currentState != forcedState) { + WimaxHelper.setWimaxEnabled(context, forcedState); + } + } + break; + case PROFILE_CONNECTION_NFC: + if (nfcAdapter != null) { + int adapterState = nfcAdapter.getAdapterState(); + currentState = (adapterState == NfcAdapter.STATE_ON || + adapterState == NfcAdapter.STATE_TURNING_ON); + if (currentState != forcedState) { + if (forcedState) { + nfcAdapter.enable(); + } else if (!forcedState && adapterState != NfcAdapter.STATE_TURNING_OFF) { + nfcAdapter.disable(); + } + } + } + break; + } + } + + /** @hide */ + public static ConnectionSettings fromXml(XmlPullParser xpp, Context context) + throws XmlPullParserException, IOException { + int event = xpp.next(); + ConnectionSettings connectionDescriptor = new ConnectionSettings(0); + while (event != XmlPullParser.END_TAG || !xpp.getName().equals("connectionDescriptor")) { + if (event == XmlPullParser.START_TAG) { + String name = xpp.getName(); + if (name.equals("connectionId")) { + connectionDescriptor.mConnectionId = Integer.parseInt(xpp.nextText()); + } else if (name.equals("value")) { + connectionDescriptor.mValue = Integer.parseInt(xpp.nextText()); + } else if (name.equals("override")) { + connectionDescriptor.mOverride = Boolean.parseBoolean(xpp.nextText()); + } + } + event = xpp.next(); + } + return connectionDescriptor; + } + + /** @hide */ + public void getXmlString(StringBuilder builder, Context context) { + builder.append("\n"); + builder.append(mConnectionId); + builder.append("\n"); + builder.append(mValue); + builder.append("\n"); + builder.append(mOverride); + builder.append("\n\n"); + } + + @Override + public int describeContents() { + return 0; + } + + /** @hide */ + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(mConnectionId); + dest.writeInt(mOverride ? 1 : 0); + dest.writeInt(mValue); + dest.writeInt(mDirty ? 1 : 0); + } + + /** @hide */ + public void readFromParcel(Parcel in) { + mConnectionId = in.readInt(); + mOverride = in.readInt() != 0; + mValue = in.readInt(); + mDirty = in.readInt() != 0; + } + + +} diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java index 3fc82faa90c..001f22fa1ca 100644 --- a/core/java/android/app/ContextImpl.java +++ b/core/java/android/app/ContextImpl.java @@ -1,5 +1,8 @@ /* * Copyright (C) 2006 The Android Open Source Project + * This code has been modified. Portions copyright (C) 2010, T-Mobile USA, Inc. + * Copyright (c) 2012-2013 The Linux Foundation. All rights reserved. + * * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,15 +23,17 @@ import com.android.internal.util.Preconditions; import android.bluetooth.BluetoothManager; +import android.accounts.AccountManager; +import android.accounts.IAccountManager; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.ContentResolver; import android.content.Context; import android.content.ContextWrapper; import android.content.IContentProvider; +import android.content.IIntentReceiver; import android.content.Intent; import android.content.IntentFilter; -import android.content.IIntentReceiver; import android.content.IntentSender; import android.content.ReceiverCallNotAllowedException; import android.content.ServiceConnection; @@ -40,6 +45,7 @@ import android.content.res.AssetManager; import android.content.res.CompatibilityInfo; import android.content.res.Configuration; +import android.content.res.CustomTheme; import android.content.res.Resources; import android.database.DatabaseErrorHandler; import android.database.sqlite.SQLiteDatabase; @@ -70,6 +76,8 @@ import android.net.wifi.WifiManager; import android.net.wifi.p2p.IWifiP2pManager; import android.net.wifi.p2p.WifiP2pManager; +import android.net.wimax.WimaxHelper; +import android.net.wimax.WimaxManagerConstants; import android.nfc.NfcManager; import android.os.Binder; import android.os.Bundle; @@ -81,6 +89,8 @@ import android.os.IBinder; import android.os.IPowerManager; import android.os.IUserManager; +import android.hardware.IIrdaManager; +import android.hardware.IrdaManager; import android.os.Looper; import android.os.PowerManager; import android.os.Process; @@ -90,6 +100,7 @@ import android.os.SystemVibrator; import android.os.UserManager; import android.os.storage.StorageManager; +import android.telephony.MSimTelephonyManager; import android.telephony.TelephonyManager; import android.content.ClipboardManager; import android.util.AndroidRuntimeException; @@ -474,6 +485,13 @@ public Object createService(ContextImpl ctx) { return new TelephonyManager(ctx.getOuterContext()); }}); + if (MSimTelephonyManager.getDefault().isMultiSimEnabled()) { + registerService(MSIM_TELEPHONY_SERVICE, new ServiceFetcher() { + public Object createService(ContextImpl ctx) { + return new MSimTelephonyManager(ctx.getOuterContext()); + }}); + } + registerService(UI_MODE_SERVICE, new ServiceFetcher() { public Object createService(ContextImpl ctx) { return new UiModeManager(); @@ -536,6 +554,24 @@ public Object createService(ContextImpl ctx) { IAppOpsService service = IAppOpsService.Stub.asInterface(b); return new AppOpsManager(ctx, service); }}); + + registerService(PROFILE_SERVICE, new ServiceFetcher() { + public Object createService(ContextImpl ctx) { + final Context outerContext = ctx.getOuterContext(); + return new ProfileManager (outerContext, ctx.mMainThread.getHandler()); + }}); + + registerService(WimaxManagerConstants.WIMAX_SERVICE, new ServiceFetcher() { + public Object createService(ContextImpl ctx) { + return WimaxHelper.createWimaxService(ctx, ctx.mMainThread.getHandler()); + }}); + + registerService(IRDA_SERVICE, new StaticServiceFetcher() { + public Object createStaticService() { + IBinder b = ServiceManager.getService(IRDA_SERVICE); + IIrdaManager service = IIrdaManager.Stub.asInterface(b); + return new IrdaManager(service); + }}); } static ContextImpl getImpl(Context context) { @@ -562,6 +598,20 @@ public Resources getResources() { return mResources; } + /** + * Refresh resources object which may have been changed by a theme + * configuration change. + */ + /* package */ void refreshResourcesIfNecessary() { + if (mResources == Resources.getSystem()) { + return; + } + + if (mPackageInfo.mCompatibilityInfo.get().isThemeable) { + mTheme = null; + } + } + @Override public PackageManager getPackageManager() { if (mPackageManager != null) { diff --git a/core/java/android/app/FragmentManager.java b/core/java/android/app/FragmentManager.java index 10ea109edc0..72c91562d64 100644 --- a/core/java/android/app/FragmentManager.java +++ b/core/java/android/app/FragmentManager.java @@ -1169,7 +1169,7 @@ public void hideFragment(Fragment fragment, int transition, int transitionStyle) if (!fragment.mHidden) { fragment.mHidden = true; if (fragment.mView != null) { - Animator anim = loadAnimator(fragment, transition, true, + Animator anim = loadAnimator(fragment, transition, false, transitionStyle); if (anim != null) { anim.setTarget(fragment.mView); diff --git a/core/java/android/app/IActivityManager.java b/core/java/android/app/IActivityManager.java index a21caee7f09..31a678448e0 100644 --- a/core/java/android/app/IActivityManager.java +++ b/core/java/android/app/IActivityManager.java @@ -98,6 +98,7 @@ public void activityStopped(IBinder token, Bundle state, public void activitySlept(IBinder token) throws RemoteException; public void activityDestroyed(IBinder token) throws RemoteException; public String getCallingPackage(IBinder token) throws RemoteException; + public String getCallingPackageForBroadcast(boolean foreground) throws RemoteException; public ComponentName getCallingActivity(IBinder token) throws RemoteException; public List getTasks(int maxNum, int flags, IThumbnailReceiver receiver) throws RemoteException; @@ -641,4 +642,5 @@ private WaitResult(Parcel source) { int KILL_UID_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+164; int SET_USER_IS_MONKEY_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+165; int HANG_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+166; + int GET_CALLING_PACKAGE_FOR_BROADCAST_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+168; } diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl index 9f933ca66c2..957bfe201b7 100644 --- a/core/java/android/app/INotificationManager.aidl +++ b/core/java/android/app/INotificationManager.aidl @@ -48,4 +48,11 @@ interface INotificationManager void cancelAllNotificationsFromListener(in INotificationListener token); StatusBarNotification[] getActiveNotificationsFromListener(in INotificationListener token); -} \ No newline at end of file + + void setHaloPolicyBlack(boolean state); + void setHaloStatus(String pkg, boolean status); + void setHaloBlacklistStatus(String pkg, boolean status); + void setHaloWhitelistStatus(String pkg, boolean status); + boolean isHaloPolicyBlack(); + boolean isPackageAllowedForHalo(String pkg); +} diff --git a/core/java/android/app/IProfileManager.aidl b/core/java/android/app/IProfileManager.aidl new file mode 100644 index 00000000000..c7c6744f17a --- /dev/null +++ b/core/java/android/app/IProfileManager.aidl @@ -0,0 +1,50 @@ +/* //device/java/android/android/app/IProfileManager.aidl +** +** Copyright 2007, The Android Open Source Project +** +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at +** +** http://www.apache.org/licenses/LICENSE-2.0 +** +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and +** limitations under the License. +*/ + +package android.app; + +import android.app.Profile; +import android.app.NotificationGroup; +import android.os.ParcelUuid; + +/** {@hide} */ +interface IProfileManager +{ + boolean setActiveProfile(in ParcelUuid profileParcelUuid); + boolean setActiveProfileByName(String profileName); + Profile getActiveProfile(); + + boolean addProfile(in Profile profile); + boolean removeProfile(in Profile profile); + void updateProfile(in Profile profile); + + Profile getProfile(in ParcelUuid profileParcelUuid); + Profile getProfileByName(String profileName); + Profile[] getProfiles(); + boolean profileExists(in ParcelUuid profileUuid); + boolean profileExistsByName(String profileName); + boolean notificationGroupExistsByName(String notificationGroupName); + + NotificationGroup[] getNotificationGroups(); + void addNotificationGroup(in NotificationGroup group); + void removeNotificationGroup(in NotificationGroup group); + void updateNotificationGroup(in NotificationGroup group); + NotificationGroup getNotificationGroupForPackage(in String pkg); + NotificationGroup getNotificationGroup(in ParcelUuid groupParcelUuid); + + void resetAll(); +} diff --git a/core/java/android/app/LayerActivity.java b/core/java/android/app/LayerActivity.java new file mode 100644 index 00000000000..45004720189 --- /dev/null +++ b/core/java/android/app/LayerActivity.java @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2013 ParanoidAndroid Project. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.app; + +import android.app.Activity; +import android.os.Bundle; + +public class LayerActivity extends Activity { + + private Bundle mSavedInstanceState; + private boolean mRestarted = false; + private boolean mShouldFinish = false; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + mSavedInstanceState = savedInstanceState; + } + + @Override + public void onRestart() { + super.onRestart(); + mRestarted = true; + } + + @Override + public void onStart() { + super.onStart(); + mShouldFinish = mRestarted; + } + + @Override + public void onResume() { + super.onResume(); + if (!mShouldFinish) { + mShouldFinish = true; + return; + } + mShouldFinish = false; + finish(); + } + + @Override + public void onStop() { + super.onStop(); + if (mSavedInstanceState != null) { + finish(); + } + } +} diff --git a/core/java/android/app/NotificationGroup.aidl b/core/java/android/app/NotificationGroup.aidl new file mode 100644 index 00000000000..44b62907fb4 --- /dev/null +++ b/core/java/android/app/NotificationGroup.aidl @@ -0,0 +1,19 @@ +/** + * Copyright (c) 2012, The CyanogenMod Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.app; + +parcelable NotificationGroup; diff --git a/core/java/android/app/NotificationGroup.java b/core/java/android/app/NotificationGroup.java new file mode 100644 index 00000000000..bcb70d31749 --- /dev/null +++ b/core/java/android/app/NotificationGroup.java @@ -0,0 +1,201 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.app; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + +import android.content.Context; +import android.os.Parcel; +import android.os.Parcelable; +import android.os.ParcelUuid; +import android.os.RemoteException; +import android.text.TextUtils; +import android.util.Log; + +import java.io.IOException; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; +import java.util.UUID; + +/** @hide */ +public class NotificationGroup implements Parcelable { + private static final String TAG = "NotificationGroup"; + + private String mName; + private int mNameResId; + + private UUID mUuid; + + private Set mPackages = new HashSet(); + + private boolean mDirty; + + public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { + public NotificationGroup createFromParcel(Parcel in) { + return new NotificationGroup(in); + } + + @Override + public NotificationGroup[] newArray(int size) { + return new NotificationGroup[size]; + } + }; + + public NotificationGroup(String name) { + this(name, -1, null); + } + + public NotificationGroup(String name, int nameResId, UUID uuid) { + mName = name; + mNameResId = nameResId; + mUuid = (uuid != null) ? uuid : UUID.randomUUID(); + mDirty = uuid == null; + } + + private NotificationGroup(Parcel in) { + readFromParcel(in); + } + + @Override + public String toString() { + return getName(); + } + + public String getName() { + return mName; + } + + public void setName(String name) { + mName = name; + mNameResId = -1; + mDirty = true; + } + + public UUID getUuid() { + return mUuid; + } + + public void addPackage(String pkg) { + mPackages.add(pkg); + mDirty = true; + } + + public String[] getPackages() { + return mPackages.toArray(new String[mPackages.size()]); + } + + public void removePackage(String pkg) { + mPackages.remove(pkg); + mDirty = true; + } + + public boolean hasPackage(String pkg) { + return mPackages.contains(pkg); + } + + public boolean isDirty() { + return mDirty; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(mName); + dest.writeInt(mNameResId); + dest.writeInt(mDirty ? 1 : 0); + new ParcelUuid(mUuid).writeToParcel(dest, 0); + dest.writeStringArray(getPackages()); + } + + public void readFromParcel(Parcel in) { + mName = in.readString(); + mNameResId = in.readInt(); + mDirty = in.readInt() != 0; + mUuid = ParcelUuid.CREATOR.createFromParcel(in).getUuid(); + mPackages.addAll(Arrays.asList(in.readStringArray())); + } + + public void getXmlString(StringBuilder builder, Context context) { + builder.append(" 0) { + builder.append("nameres=\""); + builder.append(context.getResources().getResourceEntryName(mNameResId)); + } else { + builder.append("name=\""); + builder.append(TextUtils.htmlEncode(getName())); + } + builder.append("\" uuid=\""); + builder.append(TextUtils.htmlEncode(getUuid().toString())); + builder.append("\">\n"); + for (String pkg : mPackages) { + builder.append("" + TextUtils.htmlEncode(pkg) + "\n"); + } + builder.append("\n"); + mDirty = false; + } + + public static NotificationGroup fromXml(XmlPullParser xpp, Context context) + throws XmlPullParserException, IOException { + String value = xpp.getAttributeValue(null, "nameres"); + int nameResId = -1; + String name = null; + UUID uuid = null; + + if (value != null) { + nameResId = context.getResources().getIdentifier(value, "string", "android"); + if (nameResId > 0) { + name = context.getResources().getString(nameResId); + } + } + + if (name == null) { + name = xpp.getAttributeValue(null, "name"); + } + + value = xpp.getAttributeValue(null, "uuid"); + if (value != null) { + try { + uuid = UUID.fromString(value); + } catch (IllegalArgumentException e) { + Log.w(TAG, "UUID not recognized for " + name + ", using new one."); + } + } + + NotificationGroup notificationGroup = new NotificationGroup(name, nameResId, uuid); + int event = xpp.next(); + while (event != XmlPullParser.END_TAG || !xpp.getName().equals("notificationGroup")) { + if (event == XmlPullParser.START_TAG) { + if (xpp.getName().equals("package")) { + String pkg = xpp.nextText(); + notificationGroup.addPackage(pkg); + } + } + event = xpp.next(); + } + + /* we just loaded from XML, no need to save */ + notificationGroup.mDirty = false; + + return notificationGroup; + } +} diff --git a/core/java/android/app/Profile.aidl b/core/java/android/app/Profile.aidl new file mode 100644 index 00000000000..d75bd763998 --- /dev/null +++ b/core/java/android/app/Profile.aidl @@ -0,0 +1,19 @@ +/** + * Copyright (c) 2007, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.app; + +parcelable Profile; diff --git a/core/java/android/app/Profile.java b/core/java/android/app/Profile.java new file mode 100644 index 00000000000..7dd62c268c3 --- /dev/null +++ b/core/java/android/app/Profile.java @@ -0,0 +1,762 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.app; + +import android.app.admin.DevicePolicyManager; +import android.content.Context; +import android.media.AudioManager; +import android.os.Parcel; +import android.os.ParcelUuid; +import android.os.Parcelable; +import android.text.TextUtils; +import android.util.Log; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.UUID; + +/** + * @hide + */ +public final class Profile implements Parcelable, Comparable { + + private String mName; + + private int mNameResId; + + private UUID mUuid; + + private ArrayList mSecondaryUuids = new ArrayList(); + + private Map profileGroups = new HashMap(); + + private ProfileGroup mDefaultGroup; + + private boolean mStatusBarIndicator = false; + + private boolean mDirty; + + private static final String TAG = "Profile"; + + private int mProfileType; + + private static final int CONDITIONAL_TYPE = 1; + + private static final int TOGGLE_TYPE = 0; + + private Map streams = new HashMap(); + + private Map mTriggers = new HashMap(); + + private Map connections = new HashMap(); + + private RingModeSettings mRingMode = new RingModeSettings(); + + private AirplaneModeSettings mAirplaneMode = new AirplaneModeSettings(); + + private int mScreenLockMode = LockMode.DEFAULT; + + /** @hide */ + public static class LockMode { + public static final int DEFAULT = 0; + public static final int INSECURE = 1; + public static final int DISABLE = 2; + } + + /** @hide */ + public static class TriggerType { + public static final int WIFI = 0; + public static final int BLUETOOTH = 1; + } + + /** @hide */ + public static class TriggerState { + public static final int ON_CONNECT = 0; + public static final int ON_DISCONNECT = 1; + public static final int DISABLED = 2; + } + + public static class ProfileTrigger implements Parcelable { + private int mType; + private String mId; + private String mName; + private int mState; + + public ProfileTrigger(int type, String id, int state, String name) { + mType = type; + mId = id; + mState = state; + mName = name; + } + + private ProfileTrigger(Parcel in) { + mType = in.readInt(); + mId = in.readString(); + mState = in.readInt(); + mName = in.readString(); + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(mType); + dest.writeString(mId); + dest.writeInt(mState); + dest.writeString(mName); + } + + @Override + public int describeContents() { + return 0; + } + + public int getType() { + return mType; + } + + public String getName() { + return mName; + } + + public String getId() { + return mId; + } + + public int getState() { + return mState; + } + + public void getXmlString(StringBuilder builder, Context context) { + final String itemType = mType == TriggerType.WIFI ? "wifiAP" : "btDevice"; + + builder.append("<"); + builder.append(itemType); + builder.append(" "); + builder.append(getIdType(mType)); + builder.append("=\""); + builder.append(mId); + builder.append("\" state=\""); + builder.append(mState); + builder.append("\" name=\""); + builder.append(mName); + builder.append("\">\n"); + } + + public static ProfileTrigger fromXml(XmlPullParser xpp, Context context) { + final String name = xpp.getName(); + final int type; + + if (name.equals("wifiAP")) { + type = TriggerType.WIFI; + } else if (name.equals("btDevice")) { + type = TriggerType.BLUETOOTH; + } else { + return null; + } + + String id = xpp.getAttributeValue(null, getIdType(type)); + int state = Integer.valueOf(xpp.getAttributeValue(null, "state")); + String triggerName = xpp.getAttributeValue(null, "name"); + if (triggerName == null) { + triggerName = id; + } + + return new ProfileTrigger(type, id, state, triggerName); + } + + private static String getIdType(int type) { + return type == TriggerType.WIFI ? "ssid" : "address"; + } + + public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { + public ProfileTrigger createFromParcel(Parcel in) { + return new ProfileTrigger(in); + } + + @Override + public ProfileTrigger[] newArray(int size) { + return new ProfileTrigger[size]; + } + }; + } + + /** @hide */ + public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { + public Profile createFromParcel(Parcel in) { + return new Profile(in); + } + + @Override + public Profile[] newArray(int size) { + return new Profile[size]; + } + }; + + /** @hide */ + public Profile(String name) { + this(name, -1, UUID.randomUUID()); + } + + private Profile(String name, int nameResId, UUID uuid) { + mName = name; + mNameResId = nameResId; + mUuid = uuid; + mProfileType = TOGGLE_TYPE; //Default to toggle type + mDirty = false; + } + + private Profile(Parcel in) { + readFromParcel(in); + } + + public int getTrigger(int type, String id) { + ProfileTrigger trigger = id != null ? mTriggers.get(id) : null; + if (trigger != null) { + return trigger.mState; + } + return TriggerState.DISABLED; + } + + public ArrayList getTriggersFromType(int type) { + ArrayList result = new ArrayList(); + for (Entry profileTrigger: mTriggers.entrySet()) { + ProfileTrigger trigger = profileTrigger.getValue(); + if (trigger.getType() == type) { + result.add(trigger); + } + } + return result; + } + + public void setTrigger(int type, String id, int state, String name) { + if (id == null + || type < TriggerType.WIFI || type > TriggerType.BLUETOOTH + || state < TriggerState.ON_CONNECT || state > TriggerState.DISABLED) { + return; + } + + ProfileTrigger trigger = mTriggers.get(id); + + if (state == TriggerState.DISABLED) { + if (trigger != null) { + mTriggers.remove(id); + } + } else if (trigger != null) { + trigger.mState = state; + } else { + mTriggers.put(id, new ProfileTrigger(type, id, state, name)); + } + + mDirty = true; + } + + public int compareTo(Object obj) { + Profile tmp = (Profile) obj; + if (mName.compareTo(tmp.mName) < 0) { + return -1; + } else if (mName.compareTo(tmp.mName) > 0) { + return 1; + } + return 0; + } + + /** @hide */ + public void addProfileGroup(ProfileGroup value) { + if (value.isDefaultGroup()) { + /* we must not have more than one default group */ + if (mDefaultGroup != null) { + return; + } + mDefaultGroup = value; + } + profileGroups.put(value.getUuid(), value); + mDirty = true; + } + + /** @hide */ + public void removeProfileGroup(UUID uuid) { + if (!profileGroups.get(uuid).isDefaultGroup()) { + profileGroups.remove(uuid); + } else { + Log.e(TAG, "Cannot remove default group: " + uuid); + } + } + + public ProfileGroup[] getProfileGroups() { + return profileGroups.values().toArray(new ProfileGroup[profileGroups.size()]); + } + + public ProfileGroup getProfileGroup(UUID uuid) { + return profileGroups.get(uuid); + } + + public ProfileGroup getDefaultGroup() { + return mDefaultGroup; + } + + /** @hide */ + @Override + public int describeContents() { + return 0; + } + + /** @hide */ + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(mName); + dest.writeInt(mNameResId); + new ParcelUuid(mUuid).writeToParcel(dest, 0); + ArrayList uuids = new ArrayList(mSecondaryUuids.size()); + for (UUID u : mSecondaryUuids) { + uuids.add(new ParcelUuid(u)); + } + dest.writeParcelableArray(uuids.toArray(new Parcelable[uuids.size()]), flags); + dest.writeInt(mStatusBarIndicator ? 1 : 0); + dest.writeInt(mProfileType); + dest.writeInt(mDirty ? 1 : 0); + dest.writeParcelableArray( + profileGroups.values().toArray(new Parcelable[profileGroups.size()]), flags); + dest.writeParcelableArray( + streams.values().toArray(new Parcelable[streams.size()]), flags); + dest.writeParcelableArray( + connections.values().toArray(new Parcelable[connections.size()]), flags); + dest.writeParcelable(mRingMode, flags); + dest.writeParcelable(mAirplaneMode, flags); + dest.writeInt(mScreenLockMode); + dest.writeMap(mTriggers); + } + + /** @hide */ + public void readFromParcel(Parcel in) { + mName = in.readString(); + mNameResId = in.readInt(); + mUuid = ParcelUuid.CREATOR.createFromParcel(in).getUuid(); + for (Parcelable parcel : in.readParcelableArray(null)) { + ParcelUuid u = (ParcelUuid) parcel; + mSecondaryUuids.add(u.getUuid()); + } + mStatusBarIndicator = (in.readInt() == 1); + mProfileType = in.readInt(); + mDirty = (in.readInt() == 1); + for (Parcelable group : in.readParcelableArray(null)) { + ProfileGroup grp = (ProfileGroup) group; + profileGroups.put(grp.getUuid(), grp); + if (grp.isDefaultGroup()) { + mDefaultGroup = grp; + } + } + for (Parcelable parcel : in.readParcelableArray(null)) { + StreamSettings stream = (StreamSettings) parcel; + streams.put(stream.getStreamId(), stream); + } + for (Parcelable parcel : in.readParcelableArray(null)) { + ConnectionSettings connection = (ConnectionSettings) parcel; + connections.put(connection.getConnectionId(), connection); + } + mRingMode = (RingModeSettings) in.readParcelable(null); + mAirplaneMode = (AirplaneModeSettings) in.readParcelable(null); + mScreenLockMode = in.readInt(); + in.readMap(mTriggers, null); + } + + public String getName() { + return mName; + } + + /** @hide */ + public void setName(String name) { + mName = name; + mNameResId = -1; + mDirty = true; + } + + public int getProfileType() { + return mProfileType; + } + + /** @hide */ + public void setProfileType(int type) { + mProfileType = type; + mDirty = true; + } + + public UUID getUuid() { + if (this.mUuid == null) this.mUuid = UUID.randomUUID(); + return this.mUuid; + } + + public UUID[] getSecondaryUuids() { + return mSecondaryUuids.toArray(new UUID[mSecondaryUuids.size()]); + } + + public void setSecondaryUuids(List uuids) { + mSecondaryUuids.clear(); + if (uuids != null) { + mSecondaryUuids.addAll(uuids); + mDirty = true; + } + } + + public void addSecondaryUuid(UUID uuid) { + if (uuid != null) { + mSecondaryUuids.add(uuid); + mDirty = true; + } + } + + public boolean getStatusBarIndicator() { + return mStatusBarIndicator; + } + + public void setStatusBarIndicator(boolean newStatusBarIndicator) { + mStatusBarIndicator = newStatusBarIndicator; + mDirty = true; + } + + public boolean isConditionalType() { + return(mProfileType == CONDITIONAL_TYPE ? true : false); + } + + public void setConditionalType() { + mProfileType = CONDITIONAL_TYPE; + mDirty = true; + } + + public RingModeSettings getRingMode() { + return mRingMode; + } + + public void setRingMode(RingModeSettings descriptor) { + mRingMode = descriptor; + mDirty = true; + } + + public int getScreenLockModeWithDPM(Context context) { + // Check device policy + DevicePolicyManager dpm = (DevicePolicyManager) context.getSystemService(Context.DEVICE_POLICY_SERVICE); + + if (dpm.requireSecureKeyguard()) { + // Always enforce lock screen + return LockMode.DEFAULT; + } + + return mScreenLockMode; + } + + public int getScreenLockMode() { + return mScreenLockMode; + } + + public void setScreenLockMode(int screenLockMode) { + if (screenLockMode < LockMode.DEFAULT || screenLockMode > LockMode.DISABLE) { + mScreenLockMode = LockMode.DEFAULT; + } else { + mScreenLockMode = screenLockMode; + } + mDirty = true; + } + + public AirplaneModeSettings getAirplaneMode() { + return mAirplaneMode; + } + + public void setAirplaneMode(AirplaneModeSettings descriptor) { + mAirplaneMode = descriptor; + mDirty = true; + } + + /** @hide */ + public boolean isDirty() { + if (mDirty) { + return true; + } + for (ProfileGroup group : profileGroups.values()) { + if (group.isDirty()) { + return true; + } + } + for (StreamSettings stream : streams.values()) { + if (stream.isDirty()) { + return true; + } + } + for (ConnectionSettings conn : connections.values()) { + if (conn.isDirty()) { + return true; + } + } + if (mRingMode.isDirty()) { + return true; + } + if (mAirplaneMode.isDirty()) { + return true; + } + return false; + } + + /** @hide */ + public void getXmlString(StringBuilder builder, Context context) { + builder.append(" 0) { + builder.append("nameres=\""); + builder.append(context.getResources().getResourceEntryName(mNameResId)); + } else { + builder.append("name=\""); + builder.append(TextUtils.htmlEncode(getName())); + } + builder.append("\" uuid=\""); + builder.append(TextUtils.htmlEncode(getUuid().toString())); + builder.append("\">\n"); + + builder.append(""); + for (UUID u : mSecondaryUuids) { + builder.append(""); + builder.append(TextUtils.htmlEncode(u.toString())); + builder.append(""); + } + builder.append("\n"); + + builder.append(""); + builder.append(getProfileType() == TOGGLE_TYPE ? "toggle" : "conditional"); + builder.append("\n"); + + builder.append(""); + builder.append(getStatusBarIndicator() ? "yes" : "no"); + builder.append("\n"); + + builder.append(""); + builder.append(mScreenLockMode); + builder.append("\n"); + + mAirplaneMode.getXmlString(builder, context); + + mRingMode.getXmlString(builder, context); + + for (ProfileGroup pGroup : profileGroups.values()) { + pGroup.getXmlString(builder, context); + } + for (StreamSettings sd : streams.values()) { + sd.getXmlString(builder, context); + } + for (ConnectionSettings cs : connections.values()) { + cs.getXmlString(builder, context); + } + if (!mTriggers.isEmpty()) { + builder.append("\n"); + for (ProfileTrigger trigger : mTriggers.values()) { + trigger.getXmlString(builder, context); + } + builder.append("\n"); + } + + builder.append("\n"); + mDirty = false; + } + + private static List readSecondaryUuidsFromXml(XmlPullParser xpp, Context context) + throws XmlPullParserException, + IOException { + ArrayList uuids = new ArrayList(); + int event = xpp.next(); + while (event != XmlPullParser.END_TAG || !xpp.getName().equals("uuids")) { + if (event == XmlPullParser.START_TAG) { + String name = xpp.getName(); + if (name.equals("uuid")) { + try { + uuids.add(UUID.fromString(xpp.nextText())); + } catch (NullPointerException e) { + Log.w(TAG, "Null Pointer - invalid UUID"); + } catch (IllegalArgumentException e) { + Log.w(TAG, "UUID not recognized"); + } + } + } + event = xpp.next(); + } + return uuids; + } + + private static void readTriggersFromXml(XmlPullParser xpp, Context context, Profile profile) + throws XmlPullParserException, IOException { + int event = xpp.next(); + while (event != XmlPullParser.END_TAG || !xpp.getName().equals("triggers")) { + if (event == XmlPullParser.START_TAG) { + ProfileTrigger trigger = ProfileTrigger.fromXml(xpp, context); + if (trigger != null) { + profile.mTriggers.put(trigger.mId, trigger); + } + } + event = xpp.next(); + } + } + + /** @hide */ + public void validateRingtones(Context context) { + for (ProfileGroup pg : profileGroups.values()) { + pg.validateOverrideUris(context); + } + } + + /** @hide */ + public static Profile fromXml(XmlPullParser xpp, Context context) + throws XmlPullParserException, IOException { + String value = xpp.getAttributeValue(null, "nameres"); + int profileNameResId = -1; + String profileName = null; + + if (value != null) { + profileNameResId = context.getResources().getIdentifier(value, "string", "android"); + if (profileNameResId > 0) { + profileName = context.getResources().getString(profileNameResId); + } + } + + if (profileName == null) { + profileName = xpp.getAttributeValue(null, "name"); + } + + UUID profileUuid = UUID.randomUUID(); + try { + profileUuid = UUID.fromString(xpp.getAttributeValue(null, "uuid")); + } catch (NullPointerException e) { + Log.w(TAG, + "Null Pointer - UUID not found for " + + profileName + + ". New UUID generated: " + + profileUuid.toString() + ); + } catch (IllegalArgumentException e) { + Log.w(TAG, + "UUID not recognized for " + + profileName + + ". New UUID generated: " + + profileUuid.toString() + ); + } + + Profile profile = new Profile(profileName, profileNameResId, profileUuid); + int event = xpp.next(); + while (event != XmlPullParser.END_TAG) { + if (event == XmlPullParser.START_TAG) { + String name = xpp.getName(); + if (name.equals("uuids")) { + profile.setSecondaryUuids(readSecondaryUuidsFromXml(xpp, context)); + } + if (name.equals("statusbar")) { + profile.setStatusBarIndicator(xpp.nextText().equals("yes")); + } + if (name.equals("profiletype")) { + profile.setProfileType(xpp.nextText().equals("toggle") ? TOGGLE_TYPE : CONDITIONAL_TYPE); + } + if (name.equals("ringModeDescriptor")) { + RingModeSettings smd = RingModeSettings.fromXml(xpp, context); + profile.setRingMode(smd); + } + if (name.equals("airplaneModeDescriptor")) { + AirplaneModeSettings amd = AirplaneModeSettings.fromXml(xpp, context); + profile.setAirplaneMode(amd); + } + if (name.equals("screen-lock-mode")) { + profile.setScreenLockMode(Integer.valueOf(xpp.nextText())); + } + if (name.equals("profileGroup")) { + ProfileGroup pg = ProfileGroup.fromXml(xpp, context); + profile.addProfileGroup(pg); + } + if (name.equals("streamDescriptor")) { + StreamSettings sd = StreamSettings.fromXml(xpp, context); + profile.setStreamSettings(sd); + } + if (name.equals("connectionDescriptor")) { + ConnectionSettings cs = ConnectionSettings.fromXml(xpp, context); + profile.connections.put(cs.getConnectionId(), cs); + } + if (name.equals("triggers")) { + readTriggersFromXml(xpp, context, profile); + } + } + event = xpp.next(); + } + + /* we just loaded from XML, so nothing needs saving */ + profile.mDirty = false; + + return profile; + } + + /** @hide */ + public void doSelect(Context context) { + // Set stream volumes + AudioManager am = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); + for (StreamSettings sd : streams.values()) { + if (sd.isOverride()) { + am.setStreamVolume(sd.getStreamId(), sd.getValue(), 0); + } + } + // Set connections + for (ConnectionSettings cs : connections.values()) { + if (cs.isOverride()) { + cs.processOverride(context); + } + } + // Set ring mode + mRingMode.processOverride(context); + // Set airplane mode + mAirplaneMode.processOverride(context); + } + + /** @hide */ + public StreamSettings getSettingsForStream(int streamId){ + return streams.get(streamId); + } + + /** @hide */ + public void setStreamSettings(StreamSettings descriptor){ + streams.put(descriptor.getStreamId(), descriptor); + mDirty = true; + } + + /** @hide */ + public Collection getStreamSettings(){ + return streams.values(); + } + + /** @hide */ + public ConnectionSettings getSettingsForConnection(int connectionId){ + return connections.get(connectionId); + } + + /** @hide */ + public void setConnectionSettings(ConnectionSettings descriptor){ + connections.put(descriptor.getConnectionId(), descriptor); + } + + /** @hide */ + public Collection getConnectionSettings(){ + return connections.values(); + } + +} diff --git a/core/java/android/app/ProfileGroup.java b/core/java/android/app/ProfileGroup.java new file mode 100644 index 00000000000..93857c49b39 --- /dev/null +++ b/core/java/android/app/ProfileGroup.java @@ -0,0 +1,376 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.app; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + +import android.content.Context; +import android.database.Cursor; +import android.media.RingtoneManager; +import android.net.Uri; +import android.os.Parcel; +import android.os.Parcelable; +import android.os.ParcelUuid; +import android.text.TextUtils; +import android.util.Log; + +import java.io.IOException; +import java.util.UUID; + +/** + * @hide + */ +public final class ProfileGroup implements Parcelable { + private static final String TAG = "ProfileGroup"; + + private String mName; + private int mNameResId; + + private UUID mUuid; + + private Uri mSoundOverride = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION); + private Uri mRingerOverride = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_RINGTONE); + + private Mode mSoundMode = Mode.DEFAULT; + private Mode mRingerMode = Mode.DEFAULT; + private Mode mVibrateMode = Mode.DEFAULT; + private Mode mLightsMode = Mode.DEFAULT; + + private boolean mDefaultGroup = false; + private boolean mDirty; + + /** @hide */ + public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { + public ProfileGroup createFromParcel(Parcel in) { + return new ProfileGroup(in); + } + + @Override + public ProfileGroup[] newArray(int size) { + return new ProfileGroup[size]; + } + }; + + /** @hide */ + public ProfileGroup(UUID uuid, boolean defaultGroup) { + this(null, uuid, defaultGroup); + } + + private ProfileGroup(String name, UUID uuid, boolean defaultGroup) { + mName = name; + mUuid = (uuid != null) ? uuid : UUID.randomUUID(); + mDefaultGroup = defaultGroup; + mDirty = uuid == null; + } + + /** @hide */ + private ProfileGroup(Parcel in) { + readFromParcel(in); + } + + /** @hide */ + public boolean matches(NotificationGroup group, boolean defaultGroup) { + if (mUuid.equals(group.getUuid())) { + return true; + } + + /* fallback matches for backwards compatibility */ + boolean matches = false; + + /* fallback attempt 1: match name */ + if (mName != null && mName.equals(group.getName())) { + matches = true; + /* fallback attempt 2: match for the 'defaultGroup' flag to match the wildcard group */ + } else if (mDefaultGroup && defaultGroup) { + matches = true; + } + + if (!matches) { + return false; + } + + mName = null; + mUuid = group.getUuid(); + mDirty = true; + + return true; + } + + public UUID getUuid() { + return mUuid; + } + + public boolean isDefaultGroup() { + return mDefaultGroup; + } + + /** @hide */ + public boolean isDirty() { + return mDirty; + } + + /** @hide */ + public void setSoundOverride(Uri sound) { + mSoundOverride = sound; + mDirty = true; + } + + public Uri getSoundOverride() { + return mSoundOverride; + } + + /** @hide */ + public void setRingerOverride(Uri ringer) { + mRingerOverride = ringer; + mDirty = true; + } + + public Uri getRingerOverride() { + return mRingerOverride; + } + + /** @hide */ + public void setSoundMode(Mode soundMode) { + mSoundMode = soundMode; + mDirty = true; + } + + public Mode getSoundMode() { + return mSoundMode; + } + + /** @hide */ + public void setRingerMode(Mode ringerMode) { + mRingerMode = ringerMode; + mDirty = true; + } + + public Mode getRingerMode() { + return mRingerMode; + } + + /** @hide */ + public void setVibrateMode(Mode vibrateMode) { + mVibrateMode = vibrateMode; + mDirty = true; + } + + public Mode getVibrateMode() { + return mVibrateMode; + } + + /** @hide */ + public void setLightsMode(Mode lightsMode) { + mLightsMode = lightsMode; + mDirty = true; + } + + public Mode getLightsMode() { + return mLightsMode; + } + + // TODO : add support for LEDs / screen etc. + + /** @hide */ + public Notification processNotification(Notification notification) { + + switch (mSoundMode) { + case OVERRIDE: + notification.sound = mSoundOverride; + break; + case SUPPRESS: + silenceNotification(notification); + break; + case DEFAULT: + } + switch (mVibrateMode) { + case OVERRIDE: + notification.defaults |= Notification.DEFAULT_VIBRATE; + break; + case SUPPRESS: + suppressVibrate(notification); + break; + case DEFAULT: + } + switch (mLightsMode) { + case OVERRIDE: + notification.defaults |= Notification.DEFAULT_LIGHTS; + break; + case SUPPRESS: + suppressLights(notification); + break; + case DEFAULT: + } + return notification; + } + + private boolean validateOverrideUri(Context context, Uri uri) { + if (RingtoneManager.isDefault(uri)) { + return true; + } + Cursor cursor = context.getContentResolver().query(uri, null, null, null, null); + boolean valid = false; + + if (cursor != null) { + valid = cursor.moveToFirst(); + cursor.close(); + } + return valid; + } + + void validateOverrideUris(Context context) { + if (!validateOverrideUri(context, mSoundOverride)) { + mSoundOverride = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION); + mSoundMode = Mode.DEFAULT; + mDirty = true; + } + if (!validateOverrideUri(context, mRingerOverride)) { + mRingerOverride = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_RINGTONE); + mRingerMode = Mode.DEFAULT; + mDirty = true; + } + } + + private void silenceNotification(Notification notification) { + notification.defaults &= (~Notification.DEFAULT_SOUND); + notification.sound = null; + } + + private void suppressVibrate(Notification notification) { + notification.defaults &= (~Notification.DEFAULT_VIBRATE); + notification.vibrate = null; + } + + private void suppressLights(Notification notification) { + notification.defaults &= (~Notification.DEFAULT_LIGHTS); + notification.flags &= (~Notification.FLAG_SHOW_LIGHTS); + } + + /** @hide */ + @Override + public int describeContents() { + return 0; + } + + /** @hide */ + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(mName); + new ParcelUuid(mUuid).writeToParcel(dest, 0); + dest.writeInt(mDefaultGroup ? 1 : 0); + dest.writeInt(mDirty ? 1 : 0); + dest.writeParcelable(mSoundOverride, flags); + dest.writeParcelable(mRingerOverride, flags); + + dest.writeString(mSoundMode.name()); + dest.writeString(mRingerMode.name()); + dest.writeString(mVibrateMode.name()); + dest.writeString(mLightsMode.name()); + } + + /** @hide */ + public void readFromParcel(Parcel in) { + mName = in.readString(); + mUuid = ParcelUuid.CREATOR.createFromParcel(in).getUuid(); + mDefaultGroup = in.readInt() != 0; + mDirty = in.readInt() != 0; + mSoundOverride = in.readParcelable(null); + mRingerOverride = in.readParcelable(null); + + mSoundMode = Mode.valueOf(Mode.class, in.readString()); + mRingerMode = Mode.valueOf(Mode.class, in.readString()); + mVibrateMode = Mode.valueOf(Mode.class, in.readString()); + mLightsMode = Mode.valueOf(Mode.class, in.readString()); + } + + public enum Mode { + SUPPRESS, DEFAULT, OVERRIDE; + } + + /** @hide */ + public void getXmlString(StringBuilder builder, Context context) { + builder.append("\n"); + builder.append(TextUtils.htmlEncode(mSoundOverride.toString())); + builder.append("\n"); + builder.append(TextUtils.htmlEncode(mRingerOverride.toString())); + builder.append("\n"); + builder.append(mSoundMode); + builder.append("\n"); + builder.append(mRingerMode); + builder.append("\n"); + builder.append(mVibrateMode); + builder.append("\n"); + builder.append(mLightsMode); + builder.append("\n\n"); + mDirty = false; + } + + /** @hide */ + public static ProfileGroup fromXml(XmlPullParser xpp, Context context) + throws XmlPullParserException, IOException { + String name = xpp.getAttributeValue(null, "name"); + UUID uuid = null; + String value = xpp.getAttributeValue(null, "uuid"); + + if (value != null) { + try { + uuid = UUID.fromString(value); + } catch (IllegalArgumentException e) { + Log.w(TAG, "UUID not recognized for " + name + ", using new one."); + } + } + + value = xpp.getAttributeValue(null, "default"); + boolean defaultGroup = TextUtils.equals(value, "true"); + + ProfileGroup profileGroup = new ProfileGroup(name, uuid, defaultGroup); + int event = xpp.next(); + while (event != XmlPullParser.END_TAG || !xpp.getName().equals("profileGroup")) { + if (event == XmlPullParser.START_TAG) { + name = xpp.getName(); + if (name.equals("sound")) { + profileGroup.setSoundOverride(Uri.parse(xpp.nextText())); + } else if (name.equals("ringer")) { + profileGroup.setRingerOverride(Uri.parse(xpp.nextText())); + } else if (name.equals("soundMode")) { + profileGroup.setSoundMode(Mode.valueOf(xpp.nextText())); + } else if (name.equals("ringerMode")) { + profileGroup.setRingerMode(Mode.valueOf(xpp.nextText())); + } else if (name.equals("vibrateMode")) { + profileGroup.setVibrateMode(Mode.valueOf(xpp.nextText())); + } else if (name.equals("lightsMode")) { + profileGroup.setLightsMode(Mode.valueOf(xpp.nextText())); + } + } + event = xpp.next(); + } + + /* we just loaded from XML, no need to save */ + profileGroup.mDirty = false; + + return profileGroup; + } +} diff --git a/core/java/android/app/ProfileManager.java b/core/java/android/app/ProfileManager.java new file mode 100644 index 00000000000..8e221b25fe6 --- /dev/null +++ b/core/java/android/app/ProfileManager.java @@ -0,0 +1,436 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.app; + +import java.util.UUID; + +import android.annotation.SdkConstant; +import android.annotation.SdkConstant.SdkConstantType; +import android.content.Context; +import android.os.Handler; +import android.os.IBinder; +import android.os.ParcelUuid; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.provider.Settings; +import android.util.Log; + +import com.android.internal.R; + + +/** + * @hide + */ +public class ProfileManager { + + private static IProfileManager sService; + + private Context mContext; + + private static final String TAG = "ProfileManager"; + + private static final String SYSTEM_PROFILES_ENABLED = "system_profiles_enabled"; + + /** + *

Broadcast Action: A new profile has been selected. This can be triggered by the user + * or by calls to the ProfileManagerService / Profile.

+ * @hide + */ + public static final String INTENT_ACTION_PROFILE_SELECTED = + "android.intent.action.PROFILE_SELECTED"; + + /** + * Extra for {@link INTENT_ACTION_PROFILE_SELECTED} and {@link INTENT_ACTION_PROFILE_UPDATED}: + * The name of the newly activated or updated profile + * @hide + */ + public static final String EXTRA_PROFILE_NAME = "name"; + + /** + * Extra for {@link INTENT_ACTION_PROFILE_SELECTED} and {@link INTENT_ACTION_PROFILE_UPDATED}: + * The string representation of the UUID of the newly activated or updated profile + * @hide + */ + public static final String EXTRA_PROFILE_UUID = "uuid"; + + /** + * Extra for {@link INTENT_ACTION_PROFILE_SELECTED}: + * The name of the previously active profile + * @hide + */ + public static final String EXTRA_LAST_PROFILE_NAME = "lastName"; + + /** + * Extra for {@link INTENT_ACTION_PROFILE_SELECTED}: + * The string representation of the UUID of the previously active profile + * @hide + */ + public static final String EXTRA_LAST_PROFILE_UUID = "uuid"; + + /** + *

Broadcast Action: Current profile has been updated. This is triggered every time the + * currently active profile is updated, instead of selected.

+ *

For instance, this includes profile updates caused by a locale change, which doesn't + * trigger a profile selection, but causes its name to change.

+ * @hide + */ + public static final String INTENT_ACTION_PROFILE_UPDATED = + "android.intent.action.PROFILE_UPDATED"; + + /** + * Activity Action: Shows a profile picker. + *

+ * Input: {@link #EXTRA_PROFILE_EXISTING_UUID}, {@link #EXTRA_PROFILE_SHOW_NONE}, + * {@link #EXTRA_PROFILE_TITLE}. + *

+ * Output: {@link #EXTRA_PROFILE_PICKED_UUID}. + * @hide + */ + @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) + public static final String ACTION_PROFILE_PICKER = "android.intent.action.PROFILE_PICKER"; + + /** + * @hide + */ + public static final UUID NO_PROFILE = + UUID.fromString("00000000-0000-0000-0000-000000000000"); + + /** + * Given to the profile picker as a boolean. Whether to show an item for + * deselect the profile. If the "None" item is picked, + * {@link #EXTRA_PROFILE_PICKED_UUID} will be {@link #NO_PROFILE}. + * + * @see #ACTION_PROFILE_PICKER + * @hide + */ + public static final String EXTRA_PROFILE_SHOW_NONE = + "android.intent.extra.profile.SHOW_NONE"; + + /** + * Given to the profile picker as a {@link UUID} string representation. The {@link UUID} + * representation of the current profile, which will be used to show a checkmark next to + * the item for this {@link UUID}. If the item is {@link #NO_PROFILE} then "None" item + * is selected if {@link EXTRA_PROFILE_SHOW_NONE} is enabled. Otherwise, the current + * profile is selected. + * + * @see #ACTION_PROFILE_PICKER + * @hide + */ + public static final String EXTRA_PROFILE_EXISTING_UUID = + "android.intent.extra.profile.EXISTING_UUID"; + + /** + * Given to the profile picker as a {@link CharSequence}. The title to + * show for the profile picker. This has a default value that is suitable + * in most cases. + * + * @see #ACTION_PROFILE_PICKER + * @hide + */ + public static final String EXTRA_PROFILE_TITLE = "android.intent.extra.profile.TITLE"; + + /** + * Returned from the profile picker as a {@link UUID} string representation. + *

+ * It will be one of: + *

  • the picked profile, + *
  • null if the "None" item was picked. + * + * @see #ACTION_PROFILE_PICKER + * @hide + */ + public static final String EXTRA_PROFILE_PICKED_UUID = + "android.intent.extra.profile.PICKED_UUID"; + + + /** + * Broadcast intent action indicating that Profiles has been enabled or disabled. + * One extra provides this state as an int. + * + * @see #EXTRA_PROFILES_STATE + * @hide + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String PROFILES_STATE_CHANGED_ACTION = + "android.app.profiles.PROFILES_STATE_CHANGED"; + /** + * The lookup key for an int that indicates whether Profiles are enabled or + * disabled. Retrieve it with {@link android.content.Intent#getIntExtra(String,int)}. + * + * @see #PROFILES_STATE_DISABLED + * @see #PROFILES_STATE_ENABLED + * @hide + */ + public static final String EXTRA_PROFILES_STATE = "profile_state"; + + /** + * Profiles are disabled. + * + * @see #PROFILES_STATE_CHANGED_ACTION + * @hide + */ + public static final int PROFILES_STATE_DISABLED = 0; + /** + * Profiles are enabled. + * + * @see #PROFILES_STATE_CHANGED_ACTION + * @hide + */ + public static final int PROFILES_STATE_ENABLED = 1; + + // A blank profile that is created to be returned if profiles disabled + private static Profile mEmptyProfile; + + /** @hide */ + static public IProfileManager getService() { + if (sService != null) { + return sService; + } + IBinder b = ServiceManager.getService(Context.PROFILE_SERVICE); + sService = IProfileManager.Stub.asInterface(b); + return sService; + } + + /** @hide */ + ProfileManager(Context context, Handler handler) { + mContext = context; + mEmptyProfile = new Profile("EmptyProfile"); + } + + @Deprecated + public void setActiveProfile(String profileName) { + if (Settings.System.getInt(mContext.getContentResolver(), + SYSTEM_PROFILES_ENABLED, 1) == 1) { + // Profiles are enabled, return active profile + try { + getService().setActiveProfileByName(profileName); + } catch (RemoteException e) { + Log.e(TAG, e.getLocalizedMessage(), e); + } + } + } + + public void setActiveProfile(UUID profileUuid) { + if (Settings.System.getInt(mContext.getContentResolver(), + SYSTEM_PROFILES_ENABLED, 1) == 1) { + // Profiles are enabled, return active profile + try { + getService().setActiveProfile(new ParcelUuid(profileUuid)); + } catch (RemoteException e) { + Log.e(TAG, e.getLocalizedMessage(), e); + } + } + } + + public Profile getActiveProfile() { + if (Settings.System.getInt(mContext.getContentResolver(), + SYSTEM_PROFILES_ENABLED, 1) == 1) { + // Profiles are enabled, return active profile + try { + return getService().getActiveProfile(); + } catch (RemoteException e) { + Log.e(TAG, e.getLocalizedMessage(), e); + } + return null; + + } else { + // Profiles are not enabled, return the empty profile + return mEmptyProfile; + } + + } + + /** @hide */ + public void addProfile(Profile profile) { + try { + getService().addProfile(profile); + } catch (RemoteException e) { + Log.e(TAG, e.getLocalizedMessage(), e); + } + } + + /** @hide */ + public void removeProfile(Profile profile) { + try { + getService().removeProfile(profile); + } catch (RemoteException e) { + Log.e(TAG, e.getLocalizedMessage(), e); + } + } + + /** @hide */ + public void updateProfile(Profile profile) { + try { + getService().updateProfile(profile); + } catch (RemoteException e) { + Log.e(TAG, e.getLocalizedMessage(), e); + } + } + + @Deprecated + public Profile getProfile(String profileName) { + try { + return getService().getProfileByName(profileName); + } catch (RemoteException e) { + Log.e(TAG, e.getLocalizedMessage(), e); + } + return null; + } + + public Profile getProfile(UUID profileUuid) { + try { + return getService().getProfile(new ParcelUuid(profileUuid)); + } catch (RemoteException e) { + Log.e(TAG, e.getLocalizedMessage(), e); + } + return null; + } + + public String[] getProfileNames() { + try { + Profile[] profiles = getService().getProfiles(); + String[] names = new String[profiles.length]; + for (int i = 0; i < profiles.length; i++) { + names[i] = profiles[i].getName(); + } + return names; + } catch (RemoteException e) { + Log.e(TAG, e.getLocalizedMessage(), e); + } + return null; + } + + public Profile[] getProfiles() { + try { + return getService().getProfiles(); + } catch (RemoteException e) { + Log.e(TAG, e.getLocalizedMessage(), e); + } + return null; + } + + public boolean profileExists(String profileName) { + try { + return getService().profileExistsByName(profileName); + } catch (RemoteException e) { + Log.e(TAG, e.getLocalizedMessage(), e); + // To be on the safe side, we'll return "true", to prevent duplicate profiles + // from being created. + return true; + } + } + + public boolean profileExists(UUID profileUuid) { + try { + return getService().profileExists(new ParcelUuid(profileUuid)); + } catch (RemoteException e) { + Log.e(TAG, e.getLocalizedMessage(), e); + // To be on the safe side, we'll return "true", to prevent duplicate profiles + // from being created. + return true; + } + } + + public boolean notificationGroupExists(String notificationGroupName) { + try { + return getService().notificationGroupExistsByName(notificationGroupName); + } catch (RemoteException e) { + Log.e(TAG, e.getLocalizedMessage(), e); + // To be on the safe side, we'll return "true", to prevent duplicate notification + // groups from being created. + return true; + } + } + + /** @hide */ + public NotificationGroup[] getNotificationGroups() { + try { + return getService().getNotificationGroups(); + } catch (RemoteException e) { + Log.e(TAG, e.getLocalizedMessage(), e); + } + return null; + } + + /** @hide */ + public void addNotificationGroup(NotificationGroup group) { + try { + getService().addNotificationGroup(group); + } catch (RemoteException e) { + Log.e(TAG, e.getLocalizedMessage(), e); + } + } + + /** @hide */ + public void removeNotificationGroup(NotificationGroup group) { + try { + getService().removeNotificationGroup(group); + } catch (RemoteException e) { + Log.e(TAG, e.getLocalizedMessage(), e); + } + } + + /** @hide */ + public void updateNotificationGroup(NotificationGroup group) { + try { + getService().updateNotificationGroup(group); + } catch (RemoteException e) { + Log.e(TAG, e.getLocalizedMessage(), e); + } + } + + /** @hide */ + public NotificationGroup getNotificationGroupForPackage(String pkg) { + try { + return getService().getNotificationGroupForPackage(pkg); + } catch (RemoteException e) { + Log.e(TAG, e.getLocalizedMessage(), e); + } + return null; + } + + /** @hide */ + public NotificationGroup getNotificationGroup(UUID uuid) { + try { + return getService().getNotificationGroup(new ParcelUuid(uuid)); + } catch (RemoteException e) { + Log.e(TAG, e.getLocalizedMessage(), e); + } + return null; + } + + /** @hide */ + public ProfileGroup getActiveProfileGroup(String packageName) { + NotificationGroup notificationGroup = getNotificationGroupForPackage(packageName); + if(notificationGroup == null){ + ProfileGroup defaultGroup = getActiveProfile().getDefaultGroup(); + return defaultGroup; + } + return getActiveProfile().getProfileGroup(notificationGroup.getUuid()); + } + + /** @hide */ + public void resetAll() { + try { + getService().resetAll(); + } catch (RemoteException e) { + Log.e(TAG, e.getLocalizedMessage(), e); + } catch (SecurityException e) { + Log.e(TAG, e.getLocalizedMessage(), e); + } + } +} diff --git a/core/java/android/app/RingModeSettings.java b/core/java/android/app/RingModeSettings.java new file mode 100644 index 00000000000..72714f24a50 --- /dev/null +++ b/core/java/android/app/RingModeSettings.java @@ -0,0 +1,135 @@ +package android.app; + +import android.content.Context; +import android.media.AudioManager; +import android.os.Parcel; +import android.os.Parcelable; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + +import java.io.IOException; + +/** @hide */ +public final class RingModeSettings implements Parcelable { + private static final String RING_MODE_NORMAL = "normal"; + private static final String RING_MODE_VIBRATE = "vibrate"; + private static final String RING_MODE_MUTE = "mute"; + + private String mValue; + private boolean mOverride; + private boolean mDirty; + + /** @hide */ + public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { + public RingModeSettings createFromParcel(Parcel in) { + return new RingModeSettings(in); + } + + @Override + public RingModeSettings[] newArray(int size) { + return new RingModeSettings[size]; + } + }; + + + public RingModeSettings(Parcel parcel) { + readFromParcel(parcel); + } + + public RingModeSettings() { + this(RING_MODE_NORMAL, false); + } + + public RingModeSettings(String value, boolean override) { + mValue = value; + mOverride = override; + mDirty = false; + } + + public String getValue() { + return mValue; + } + + public void setValue(String value) { + mValue = value; + mDirty = true; + } + + public void setOverride(boolean override) { + mOverride = override; + mDirty = true; + } + + public boolean isOverride() { + return mOverride; + } + + /** @hide */ + public boolean isDirty() { + return mDirty; + } + + public void processOverride(Context context) { + if (isOverride()) { + int ringerMode = AudioManager.RINGER_MODE_NORMAL; + if (mValue.equals(RING_MODE_MUTE)) { + ringerMode = AudioManager.RINGER_MODE_SILENT; + } else if (mValue.equals(RING_MODE_VIBRATE)) { + ringerMode = AudioManager.RINGER_MODE_VIBRATE; + } + AudioManager amgr = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); + amgr.setRingerMode(ringerMode); + } + } + + /** @hide */ + public static RingModeSettings fromXml(XmlPullParser xpp, Context context) + throws XmlPullParserException, IOException { + int event = xpp.next(); + RingModeSettings connectionDescriptor = new RingModeSettings(); + while (event != XmlPullParser.END_TAG) { + if (event == XmlPullParser.START_TAG) { + String name = xpp.getName(); + if (name.equals("value")) { + connectionDescriptor.mValue = xpp.nextText(); + } else if (name.equals("override")) { + connectionDescriptor.mOverride = Boolean.parseBoolean(xpp.nextText()); + } + } + event = xpp.next(); + } + return connectionDescriptor; + } + + /** @hide */ + public void getXmlString(StringBuilder builder, Context context) { + builder.append("\n"); + builder.append(mValue); + builder.append("\n"); + builder.append(mOverride); + builder.append("\n\n"); + } + + @Override + public int describeContents() { + return 0; + } + + /** @hide */ + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(mOverride ? 1 : 0); + dest.writeString(mValue); + dest.writeInt(mDirty ? 1 : 0); + } + + /** @hide */ + public void readFromParcel(Parcel in) { + mOverride = in.readInt() != 0; + mValue = in.readString(); + mDirty = in.readInt() != 0; + } + + +} diff --git a/core/java/android/app/StreamSettings.java b/core/java/android/app/StreamSettings.java new file mode 100644 index 00000000000..2f3bf27ee8f --- /dev/null +++ b/core/java/android/app/StreamSettings.java @@ -0,0 +1,130 @@ + +package android.app; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + +import android.content.Context; +import android.os.Parcel; +import android.os.Parcelable; + +import java.io.IOException; + +/** @hide */ +public final class StreamSettings implements Parcelable{ + + private int mStreamId; + private int mValue; + private boolean mOverride; + private boolean mDirty; + + /** @hide */ + public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { + public StreamSettings createFromParcel(Parcel in) { + return new StreamSettings(in); + } + + @Override + public StreamSettings[] newArray(int size) { + return new StreamSettings[size]; + } + }; + + + public StreamSettings(Parcel parcel) { + readFromParcel(parcel); + } + + public StreamSettings(int streamId) { + this(streamId, 0, false); + } + + public StreamSettings(int streamId, int value, boolean override) { + mStreamId = streamId; + mValue = value; + mOverride = override; + mDirty = false; + } + + public int getStreamId() { + return mStreamId; + } + + public int getValue() { + return mValue; + } + + public void setValue(int value) { + mValue = value; + mDirty = true; + } + + public void setOverride(boolean override) { + mOverride = override; + mDirty = true; + } + + public boolean isOverride() { + return mOverride; + } + + /** @hide */ + public boolean isDirty() { + return mDirty; + } + + /** @hide */ + public static StreamSettings fromXml(XmlPullParser xpp, Context context) + throws XmlPullParserException, IOException { + int event = xpp.next(); + StreamSettings streamDescriptor = new StreamSettings(0); + while (event != XmlPullParser.END_TAG || !xpp.getName().equals("streamDescriptor")) { + if (event == XmlPullParser.START_TAG) { + String name = xpp.getName(); + if (name.equals("streamId")) { + streamDescriptor.mStreamId = Integer.parseInt(xpp.nextText()); + } else if (name.equals("value")) { + streamDescriptor.mValue = Integer.parseInt(xpp.nextText()); + } else if (name.equals("override")) { + streamDescriptor.mOverride = Boolean.parseBoolean(xpp.nextText()); + } + } + event = xpp.next(); + } + return streamDescriptor; + } + + /** @hide */ + public void getXmlString(StringBuilder builder, Context context) { + builder.append("\n"); + builder.append(mStreamId); + builder.append("\n"); + builder.append(mValue); + builder.append("\n"); + builder.append(mOverride); + builder.append("\n\n"); + mDirty = false; + } + + @Override + public int describeContents() { + return 0; + } + + /** @hide */ + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(mStreamId); + dest.writeInt(mOverride ? 1 : 0); + dest.writeInt(mValue); + dest.writeInt(mDirty ? 1 : 0); + } + + /** @hide */ + public void readFromParcel(Parcel in) { + mStreamId = in.readInt(); + mOverride = in.readInt() != 0; + mValue = in.readInt(); + mDirty = in.readInt() != 0; + } +} diff --git a/core/java/android/app/TaskStackBuilder.java b/core/java/android/app/TaskStackBuilder.java index 3e0ac7ec476..93babb72505 100644 --- a/core/java/android/app/TaskStackBuilder.java +++ b/core/java/android/app/TaskStackBuilder.java @@ -62,6 +62,7 @@ public class TaskStackBuilder { private final ArrayList mIntents = new ArrayList(); private final Context mSourceContext; + private boolean mFirstTaskOnHome = true; private TaskStackBuilder(Context a) { mSourceContext = a; @@ -78,6 +79,10 @@ public static TaskStackBuilder create(Context context) { return new TaskStackBuilder(context); } + public void setTaskOnHome(boolean firstTaskOnHome) { + mFirstTaskOnHome = firstTaskOnHome; + } + /** * Add a new Intent to the task stack. The most recently added Intent will invoke * the Activity at the top of the final task stack. @@ -298,9 +303,16 @@ public Intent[] getIntents() { Intent[] intents = new Intent[mIntents.size()]; if (intents.length == 0) return intents; - intents[0] = new Intent(mIntents.get(0)).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | - Intent.FLAG_ACTIVITY_CLEAR_TASK | - Intent.FLAG_ACTIVITY_TASK_ON_HOME); + Intent newIntent = new Intent(mIntents.get(0)); + newIntent.addFlags( + Intent.FLAG_ACTIVITY_NEW_TASK | + Intent.FLAG_ACTIVITY_CLEAR_TASK); + + if (mFirstTaskOnHome) { + newIntent.addFlags(Intent.FLAG_ACTIVITY_TASK_ON_HOME); + } + + intents[0] = newIntent; for (int i = 1; i < intents.length; i++) { intents[i] = new Intent(mIntents.get(i)); } diff --git a/core/java/android/app/UiModeManager.java b/core/java/android/app/UiModeManager.java index 0c22740b6e0..199a0648d93 100644 --- a/core/java/android/app/UiModeManager.java +++ b/core/java/android/app/UiModeManager.java @@ -168,7 +168,7 @@ public void disableCarMode(int flags) { * {@link Configuration#UI_MODE_TYPE_NORMAL Configuration.UI_MODE_TYPE_NORMAL}, * {@link Configuration#UI_MODE_TYPE_DESK Configuration.UI_MODE_TYPE_DESK}, or * {@link Configuration#UI_MODE_TYPE_CAR Configuration.UI_MODE_TYPE_CAR}, or - * {@link Configuration#UI_MODE_TYPE_TELEVISION Configuration.UI_MODE_TYPE_APPLIANCE}. + * {@link Configuration#UI_MODE_TYPE_TELEVISION Configuration.UI_MODE_TYPE_APPLIANCE}, or */ public int getCurrentModeType() { if (mService != null) { diff --git a/core/java/android/app/admin/DeviceAdminInfo.java b/core/java/android/app/admin/DeviceAdminInfo.java index 66fc816201e..3ea4c7f2d04 100644 --- a/core/java/android/app/admin/DeviceAdminInfo.java +++ b/core/java/android/app/admin/DeviceAdminInfo.java @@ -146,6 +146,25 @@ public final class DeviceAdminInfo implements Parcelable { */ public static final int USES_POLICY_DISABLE_KEYGUARD_FEATURES = 9; + /** + * @hide + * A type of policy that this device admin can use: enforce SELinux policy. + * + *

    To control this policy, the device admin must have a "enforce-selinux" + * tag in the "uses-policies" section of its meta-data. + */ + public static final int USES_POLICY_ENFORCE_SELINUX = 10; + + /** + * @hide + * A type of policy that this device admin can use: enforce SE Android MMAC policy. + * + *

    To control this policy, the device admin must have a "enforce-mmac" tag in the + * "uses-policies" section of its meta-data. + */ + public static final int USES_POLICY_ENFORCE_MMAC = 11; + + /** @hide */ public static class PolicyInfo { public final int ident; @@ -197,6 +216,12 @@ public PolicyInfo(int identIn, String tagIn, int labelIn, int descriptionIn) { USES_POLICY_DISABLE_KEYGUARD_FEATURES, "disable-keyguard-features", com.android.internal.R.string.policylab_disableKeyguardFeatures, com.android.internal.R.string.policydesc_disableKeyguardFeatures)); + sPoliciesDisplayOrder.add(new PolicyInfo(USES_POLICY_ENFORCE_SELINUX, "enforce-selinux", + com.android.internal.R.string.policylab_enforceSelinux, + com.android.internal.R.string.policydesc_enforceSelinux)); + sPoliciesDisplayOrder.add(new PolicyInfo(USES_POLICY_ENFORCE_MMAC, "enforce-mmac", + com.android.internal.R.string.policylab_enforceMmac, + com.android.internal.R.string.policydesc_enforceMmac)); for (int i=0; iThe calling device admin must have requested + * {@link DeviceAdminInfo#USES_POLICY_ENFORCE_SELINUX} to be able to call + * this method; if it has not, a security exception will be thrown. + * + *

    When an application gains control of SELinux settings, it is called an + * SELinux administrator. Admistration applications will call this with true and + * ensure this method returned true before attempting to toggle SELinux settings. + * When apps intend to stop controlling SELinux settings, apps should call this + * with false. + * + * @param admin Which {@link DeviceAdminReceiver} this request is associated, + * must be self + * @param control true if the admin wishes to control SELinux, false if the admin + * wishes to give back control of SELinux + * @return true if the operation succeeded, false if the operation failed or + * SELinux was not enabled on the device. + * @hide + */ + public boolean setSELinuxAdmin(ComponentName admin, boolean control) { + return setSELinuxAdmin(admin, control, UserHandle.myUserId()); + } + + /** @hide per-user version */ + public boolean setSELinuxAdmin(ComponentName admin, boolean control, int userHandle) { + if (mService != null) { + try { + return mService.setSELinuxAdmin(admin, control, userHandle); + } catch (RemoteException e) { + Log.w(TAG, "Failed talking with device policy server", e); + } + } + return false; + } + + /** + * Checks whether an admin app has control over SELinux policy. + * + *

    The calling device admin must have requested + * {@link DeviceAdminInfo#USES_POLICY_ENFORCE_SELINUX} to be able to call + * this method; if it has not, a security exception will be thrown. + * + * @param admin Which {@link DeviceAdminReceiver} this request is associated, + * must be self + * @return true if admin app can control SELinux policy, false otherwise + * @hide + */ + public boolean isSELinuxAdmin(ComponentName admin) { + return isSELinuxAdmin(admin, UserHandle.myUserId()); + } + + /** @hide per-user version */ + public boolean isSELinuxAdmin(ComponentName admin, int userHandle) { + if (mService != null) { + try { + return mService.isSELinuxAdmin(admin, userHandle); + } catch (RemoteException e) { + Log.w(TAG, "Failed talking with device policy server", e); + } + } + return false; + } + + /** + * Called by a SELinux admin to set SELinux into enforcing or permissive mode. + * + *

    The calling device admin must have requested + * {@link DeviceAdminInfo#USES_POLICY_ENFORCE_SELINUX} to be able to call + * this method; if it has not, a security exception will be thrown. + * + * @param admin Which {@link DeviceAdminReceiver} this request is associated with. + * @param enforcing true for enforcing mode, false for permissive mode. + * @return false if Android was unable to set the desired mode + * @hide + */ + public boolean setSELinuxEnforcing(ComponentName admin, boolean enforcing) { + return setSELinuxEnforcing(admin, enforcing, UserHandle.myUserId()); + } + + /** @hide per-user version */ + public boolean setSELinuxEnforcing(ComponentName admin, boolean enforcing, int userHandle) { + if (mService != null) { + try { + return mService.setSELinuxEnforcing(admin, enforcing, userHandle); + } catch (RemoteException e) { + Log.w(TAG, "Failed talking with device policy server", e); + } + } + return false; // I guess this doesn't fit the spec, but it never happens... + } + + /** + * Determine whether or not SELinux policies are currently being enforced + * by the current admin. + * + *

    The calling device admin must have requested + * {@link DeviceAdminInfo#USES_POLICY_ENFORCE_SELINUX} to be able to call + * this method; if it has not, a security exception will be thrown. + * + *

    The returned value is only meaningful if the current admin is a + * SELinux admin. + * + * @param admin Which {@link DeviceAdminReceiver} this request is associated with. + * @hide + */ + public boolean getSELinuxEnforcing(ComponentName admin) { + return getSELinuxEnforcing(admin, UserHandle.myUserId()); + } + + /** @hide per-user version */ + public boolean getSELinuxEnforcing(ComponentName admin, int userHandle) { + if (mService != null) { + try { + return mService.getSELinuxEnforcing(admin, userHandle); + } catch (RemoteException e) { + Log.w(TAG, "Failed talking with device policy server", e); + } + } + return false; + } + + /** + * Get a list of the SELinux booleans available on the system. + * + *

    The calling device admin must have requested + * {@link DeviceAdminInfo#USES_POLICY_ENFORCE_SELINUX} to be able to call + * this method; if it has not, a security exception will be thrown. + * + *

    The returned value is only meaningful if the current admin is a + * SELinux admin. + * + * @param admin Which {@link DeviceAdminReceiver} this request is associated with. + * @hide + */ + public List getSELinuxBooleanNames(ComponentName admin) { + return getSELinuxBooleanNames(admin, UserHandle.myUserId()); + } + + /** @hide per-user version */ + public List getSELinuxBooleanNames(ComponentName admin, int userHandle) { + if (mService != null) { + try { + return mService.getSELinuxBooleanNames(admin, userHandle); + } catch (RemoteException e) { + Log.w(TAG, "Failed talking with device policy server", e); + } + } + return null; + } + + /** + * Get the value of a SELinux boolean. + * + *

    The calling device admin must have requested + * {@link DeviceAdminInfo#USES_POLICY_ENFORCE_SELINUX} to be able to call + * this method; if it has not, a security exception will be thrown. + * + *

    The returned value is only meaningful if the current admin is a + * SELinux admin. + * + * @param admin Which {@link DeviceAdminReceiver} this request is associated with. + * @param name the name of the SELinux boolean + * @return the value of the SELinux boolean + * @hide + */ + public boolean getSELinuxBooleanValue(ComponentName admin, String name) { + return getSELinuxBooleanValue(admin, name, UserHandle.myUserId()); + } + + /** @hide per-user version */ + public boolean getSELinuxBooleanValue(ComponentName admin, String name, int userHandle) { + if (mService != null) { + try { + return mService.getSELinuxBooleanValue(admin, name, userHandle); + } catch (RemoteException e) { + Log.w(TAG, "Failed talking with device policy server", e); + } + } + return false; + } + + /** + * Set the value of a SELinux boolean. + * + *

    The calling device admin must have requested + * {@link DeviceAdminInfo#USES_POLICY_ENFORCE_SELINUX} to be able to call + * this method; if it has not, a security exception will be thrown. + * + *

    The returned value is only meaningful if the current admin is a + * SELinux admin. + * + * @param admin Which {@link DeviceAdminReceiver} this request is associated with. + * @param name the name of the SELinux boolean + * @param value the desired value for the boolean + * @return false if Android was unable to set the desired mode + * @hide + */ + public boolean setSELinuxBooleanValue(ComponentName admin, String name, + boolean value) { + return setSELinuxBooleanValue(admin, name, value, UserHandle.myUserId()); + } + + /** @hide per-user version */ + public boolean setSELinuxBooleanValue(ComponentName admin, String name, + boolean value, int userHandle) { + if (mService != null) { + try { + return mService.setSELinuxBooleanValue(admin, name, value, userHandle); + } catch (RemoteException e) { + Log.w(TAG, "Failed talking with device policy server", e); + } + } + return false; + } + + /** + * Checks whether an admin app has control over SE Android MMAC policy. + * + *

    The calling device admin must have requested + * {@link DeviceAdminInfo#USES_POLICY_ENFORCE_MMAC} to be able to call + * this method; if it has not, a security exception will be thrown. + * + * @param admin Which {@link DeviceAdminReceiver} this request is associated, + * must be self + * @return true if admin app can control MMAC policy, false otherwise + * @hide + */ + public boolean isMMACadmin(ComponentName admin) { + return isMMACadmin(admin, UserHandle.myUserId()); + } + + /** @hide per-user version */ + public boolean isMMACadmin(ComponentName admin, int userHandle) { + if (mService != null) { + try { + return mService.isMMACadmin(admin, userHandle); + } catch (RemoteException e) { + Log.w(TAG, "Failed talking with device policy server", e); + } + } + return false; + } + + /** + * Called by an application that is administering the device to start or stop + * controlling SE Android MMAC policies, enforcement, etc. When an admin + * app gives up control of MMAC policies, the policy in place prior to the app + * taking control will be applied. + * + *

    The calling device admin must have requested + * {@link DeviceAdminInfo#USES_POLICY_ENFORCE_MMAC} to be able to call + * this method; if it has not, a security exception will be thrown. + * + *

    When an application gains control of MMAC settings, it is called an + * MMAC administrator. Admistration applications will call this with true and + * ensure this method returned true before attempting to toggle MMAC settings. + * When apps intend to stop controlling MMAC settings, apps should call this + * with false. + * + * @param admin Which {@link DeviceAdminReceiver} this request is associated, + * must be self + * @param control true if the admin wishes to control MMAC, false if the admin + * wishes to give back control of MMAC + * @return true if the operation succeeded, false if the operation failed or + * MMAC was not enabled on the device. + * @hide + */ + public boolean setMMACadmin(ComponentName admin, boolean control) { + return setMMACadmin(admin, control, UserHandle.myUserId()); + } + + /** @hide per-user version */ + public boolean setMMACadmin(ComponentName admin, boolean control, int userHandle) { + if (mService != null) { + try { + return mService.setMMACadmin(admin, control, userHandle); + } catch (RemoteException e) { + Log.w(TAG, "Failed talking with device policy server", e); + } + } + return false; + } + + /** + * Called by an application that is a SEAndroid MMAC admin to set MMAC + * protections into enforcing or permissive mode. The system requires a + * reboot for the protections to take effect. + * + *

    The calling device admin must have requested + * {@link DeviceAdminInfo#USES_POLICY_ENFORCE_MMAC} to be able to call + * this method; if it has not, a security exception will be thrown. + * + * @param admin Which {@link DeviceAdminReceiver} this request is associated with. + * @param enforcing true for enforcing mode, false for permissive mode. + * @return false if Android was unable to set the desired mode + * @hide + */ + public boolean setMMACenforcing(ComponentName admin, boolean enforcing) { + return setMMACenforcing(admin, enforcing, UserHandle.myUserId()); + } + + /** @hide per-user version */ + public boolean setMMACenforcing(ComponentName admin, boolean enforcing, int userHandle) { + if (mService != null) { + try { + return mService.setMMACenforcing(admin, enforcing, userHandle); + } catch (RemoteException e) { + Log.w(TAG, "Failed talking with device policy server", e); + } + } + return false; + } + + /** + * Determine whether SE Android MMAC policies are being enforced by the + * current admin. + * + *

    The calling device admin must have requested + * {@link DeviceAdminInfo#USES_POLICY_ENFORCE_MMAC} to be able to call + * this method; if it has not, a security exception will be thrown. + * + *

    The returned value is only meaningful if the current admin is a + * MMAC admin. + * + * @param admin Which {@link DeviceAdminReceiver} this request is associated with. + * @hide + */ + public boolean getMMACenforcing(ComponentName admin) { + return getMMACenforcing(admin, UserHandle.myUserId()); + } + + /** @hide per-user version */ + public boolean getMMACenforcing(ComponentName admin, int userHandle) { + if (mService != null) { + try { + return mService.getMMACenforcing(admin, userHandle); + } catch (RemoteException e) { + Log.w(TAG, "Failed talking with device policy server", e); + } + } + return false; + } + + // Before changing these values, be sure to update + // DevicePolicyManagerService.java's POLICY_DESCRIPTIONS array. + /** @hide */ + public static final int SEPOLICY_FILE_SEPOLICY = 0; + /** @hide */ + public static final int SEPOLICY_FILE_PROPCTXS = 1; + /** @hide */ + public static final int SEPOLICY_FILE_FILECTXS = 2; + /** @hide */ + public static final int SEPOLICY_FILE_SEAPPCTXS = 3; + /** @hide */ + public static final int MMAC_POLICY_FILE = 4; + /** @hide */ + public static final int SEPOLICY_FILE_COUNT = MMAC_POLICY_FILE+1; + + /** + * Sets a new policy file and reloads it at the proper time. + * + *

    For {@link #SEPOLICY_FILE_SEPOLICY}, {@link #SEPOLICY_FILE_PROPCTXS}, + * {@link #SEPOLICY_FILE_FILECTXS}, and {@link #SEPOLICY_FILE_SEAPPCTXS}, the admin + * must have requested {@link DeviceAdminInfo#USES_POLICY_ENFORCE_SELINUX} + * before calling this method. If it has not, a security exception will be + * thrown. + * + *

    For {@link #SEPOLICY_FILE_SEPOLICY}, {@link #SEPOLICY_FILE_PROPCTXS}, + * {@link #SEPOLICY_FILE_FILECTXS}, and {@link #SEPOLICY_FILE_SEAPPCTXS}, these + * files are reloaded before returning from the DevicePolicyManager. + * + *

    For {@link #SEPOLICY_FILE_SEPOLICY}, {@link #SEPOLICY_FILE_PROPCTXS}, + * {@link #SEPOLICY_FILE_FILECTXS}, and {@link #SEPOLICY_FILE_SEAPPCTXS}, the + * returned value is only meaingful if the current admin is a SELinux + * admin. + * + *

    For {@link #MMAC_POLICY_FILE}, the admin must have requested + * {@link DeviceAdminInfo#USES_POLICY_ENFORCE_MMAC} before calling this + * method. If it has not, a security exception will be thrown. + * + *

    For {@link #MMAC_POLICY_FILE}, the MMAC policy file is reloaded on + * reboot. + * + *

    For {@link #MMAC_POLICY_FILE}, the returned value is only meaingful + * if the current admin is a MMAC admin. + * + * @param admin which {@link DeviceAdminReceiver} this request is associated with + * @param policyType one of {@link #SEPOLICY_FILE_SEPOLICY}, {@link #SEPOLICY_FILE_PROPCTXS}, + * {@link #SEPOLICY_FILE_FILECTXS}, {@link #SEPOLICY_FILE_SEAPPCTXS}, + * or {@link #MMAC_POLICY_FILE}. + * @param policy the new policy file in bytes, or null if you wish to revert to + * the default policy + * @return false if Android was unable to set the new policy + * @hide + */ + public boolean setCustomPolicyFile(ComponentName admin, int policyType, byte[] policy) { + return setCustomPolicyFile(admin, policyType, policy, UserHandle.myUserId()); + } + + /** @hide per-user version */ + public boolean setCustomPolicyFile(ComponentName admin, int policyType, byte[] policy, int userHandle) { + if (mService != null) { + try { + return mService.setCustomPolicyFile(admin, policyType, policy, userHandle); + } catch (RemoteException e) { + Log.w(TAG, "Failed talking with device policy server", e); + } + } + return false; + } + + /** + * Determine whether this admin set a custom policy file. + * + *

    For {@link #SEPOLICY_FILE_SEPOLICY}, {@link #SEPOLICY_FILE_PROPCTXS}, + * {@link #SEPOLICY_FILE_FILECTXS}, and {@link #SEPOLICY_FILE_SEAPPCTXS}, the admin + * must have requested {@link DeviceAdminInfo#USES_POLICY_ENFORCE_SELINUX} + * before calling this method. If it has not, a security exception will be + * thrown. + * + *

    For {@link #SEPOLICY_FILE_SEPOLICY}, {@link #SEPOLICY_FILE_PROPCTXS}, + * {@link #SEPOLICY_FILE_FILECTXS}, and {@link #SEPOLICY_FILE_SEAPPCTXS}, the + * returned value is only meaingful if the current admin is a SELinux + * admin. + * + *

    For {@link #MMAC_POLICY_FILE}, the admin must have requested + * {@link DeviceAdminInfo#USES_POLICY_ENFORCE_MMAC} before calling this + * method. If it has not, a security exception will be thrown. + * + *

    For {@link #MMAC_POLICY_FILE}, the returned value is only meaingful + * if the current admin is a MMAC admin. + * + * @param admin which {@link DeviceAdminReceiver} this request is associated with + * @param policyType one of {@link #SEPOLICY_FILE_SEPOLICY}, {@link #SEPOLICY_FILE_PROPCTXS}, + * {@link #SEPOLICY_FILE_FILECTXS}, {@link #SEPOLICY_FILE_SEAPPCTXS}, or + * {@link #MMAC_POLICY_FILE} + * @return true if the admin set a custom policy file + * @hide + */ + public boolean isCustomPolicyFile(ComponentName admin, int policyType) { + return isCustomPolicyFile(admin, policyType, UserHandle.myUserId()); + } + + /** @hide per-user version */ + public boolean isCustomPolicyFile(ComponentName admin, int policyType, int userHandle) { + if (mService != null) { + try { + return mService.isCustomPolicyFile(admin, policyType, userHandle); + } catch (RemoteException e) { + Log.w(TAG, "Failed talking with device policy server", e); + } + } + return false; + } + /** * @hide */ @@ -1581,4 +2044,20 @@ public String getDeviceOwner() { } return null; } + + /** + * CM: check if secure keyguard is required + * @hide + */ + public boolean requireSecureKeyguard() { + int encryptionStatus = getStorageEncryptionStatus(); + if (getPasswordQuality(null) > PASSWORD_QUALITY_UNSPECIFIED || + !KeyStore.getInstance().isEmpty() || + encryptionStatus == DevicePolicyManager.ENCRYPTION_STATUS_ACTIVE || + encryptionStatus == DevicePolicyManager.ENCRYPTION_STATUS_ACTIVATING) { + // Require secure keyguard + return true; + } + return false; + } } diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl index b2a65bf3750..50959c640b1 100644 --- a/core/java/android/app/admin/IDevicePolicyManager.aidl +++ b/core/java/android/app/admin/IDevicePolicyManager.aidl @@ -93,6 +93,25 @@ interface IDevicePolicyManager { void removeActiveAdmin(in ComponentName policyReceiver, int userHandle); boolean hasGrantedPolicy(in ComponentName policyReceiver, int usesPolicy, int userHandle); + boolean setSELinuxAdmin(in ComponentName who, boolean control, int userHandle); + boolean isSELinuxAdmin(in ComponentName who, int userHandle); + + boolean setSELinuxEnforcing(in ComponentName who, boolean enforcing, int userHandle); + boolean getSELinuxEnforcing(in ComponentName who, int userHandle); + + List getSELinuxBooleanNames(in ComponentName who, int userHandle); + boolean getSELinuxBooleanValue(in ComponentName who, String name, int userHandle); + boolean setSELinuxBooleanValue(in ComponentName who, String name, boolean value, int userHandle); + + boolean isMMACadmin(in ComponentName who, int userHandle); + boolean setMMACadmin(in ComponentName who, boolean control, int userHandle); + + boolean setMMACenforcing(in ComponentName who, boolean enforcing, int userHandle); + boolean getMMACenforcing(in ComponentName who, int userHandle); + + boolean setCustomPolicyFile(in ComponentName who, int policyType, in byte[] policy, int userHandle); + boolean isCustomPolicyFile(in ComponentName who, int policyType, int userHandle); + void setActivePasswordState(int quality, int length, int letters, int uppercase, int lowercase, int numbers, int symbols, int nonletter, int userHandle); void reportFailedPasswordAttempt(int userHandle); diff --git a/core/java/android/bluetooth/BluetoothAdapter.java b/core/java/android/bluetooth/BluetoothAdapter.java index 79bb476cb21..5a329e0334f 100644 --- a/core/java/android/bluetooth/BluetoothAdapter.java +++ b/core/java/android/bluetooth/BluetoothAdapter.java @@ -1,4 +1,6 @@ /* + * Copyright (C) 2013 The Linux Foundation. All rights reserved + * Not a Contribution. * Copyright (C) 2009 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -954,7 +956,15 @@ public BluetoothServerSocket listenUsingRfcommOn(int channel) throws IOException */ public BluetoothServerSocket listenUsingRfcommWithServiceRecord(String name, UUID uuid) throws IOException { - return createNewRfcommSocketAndRecord(name, uuid, true, true); + return createNewRfcommSocketAndRecord(name, -1, uuid, true, true); + } + + /** + * @hide + */ + public BluetoothServerSocket listenUsingRfcommWithServiceRecordOn(String name, int port, UUID uuid) + throws IOException { + return createNewRfcommSocketAndRecord(name, port, uuid, true, true); } /** @@ -985,7 +995,7 @@ public BluetoothServerSocket listenUsingRfcommWithServiceRecord(String name, UUI */ public BluetoothServerSocket listenUsingInsecureRfcommWithServiceRecord(String name, UUID uuid) throws IOException { - return createNewRfcommSocketAndRecord(name, uuid, false, false); + return createNewRfcommSocketAndRecord(name, -1, uuid, false, false); } /** @@ -1023,15 +1033,15 @@ public BluetoothServerSocket listenUsingInsecureRfcommWithServiceRecord(String n */ public BluetoothServerSocket listenUsingEncryptedRfcommWithServiceRecord( String name, UUID uuid) throws IOException { - return createNewRfcommSocketAndRecord(name, uuid, false, true); + return createNewRfcommSocketAndRecord(name, -1, uuid, false, true); } - private BluetoothServerSocket createNewRfcommSocketAndRecord(String name, UUID uuid, + private BluetoothServerSocket createNewRfcommSocketAndRecord(String name, int port, UUID uuid, boolean auth, boolean encrypt) throws IOException { BluetoothServerSocket socket; socket = new BluetoothServerSocket(BluetoothSocket.TYPE_RFCOMM, auth, - encrypt, new ParcelUuid(uuid)); + encrypt, port, new ParcelUuid(uuid)); socket.setServiceName(name); int errno = socket.mSocket.bindListen(); if (errno != 0) { @@ -1170,9 +1180,18 @@ public boolean getProfileProxy(Context context, BluetoothProfile.ServiceListener } else if (profile == BluetoothProfile.PAN) { BluetoothPan pan = new BluetoothPan(context, listener); return true; + } else if (profile == BluetoothProfile.SAP) { + BluetoothSap sap = new BluetoothSap(context, listener); + return true; + } else if (profile == BluetoothProfile.DUN) { + BluetoothDun dun = new BluetoothDun(context, listener); + return true; } else if (profile == BluetoothProfile.HEALTH) { BluetoothHealth health = new BluetoothHealth(context, listener); return true; + } else if (profile == BluetoothProfile.HANDSFREE_CLIENT) { + BluetoothHandsfreeClient hfpclient = new BluetoothHandsfreeClient(context, listener); + return true; } else { return false; } @@ -1209,6 +1228,14 @@ public void closeProfileProxy(int profile, BluetoothProfile proxy) { BluetoothPan pan = (BluetoothPan)proxy; pan.close(); break; + case BluetoothProfile.SAP: + BluetoothSap sap = (BluetoothSap)proxy; + sap.close(); + break; + case BluetoothProfile.DUN: + BluetoothDun dun = (BluetoothDun)proxy; + dun.close(); + break; case BluetoothProfile.HEALTH: BluetoothHealth health = (BluetoothHealth)proxy; health.close(); @@ -1221,6 +1248,10 @@ public void closeProfileProxy(int profile, BluetoothProfile proxy) { BluetoothGattServer gattServer = (BluetoothGattServer)proxy; gattServer.close(); break; + case BluetoothProfile.HANDSFREE_CLIENT: + BluetoothHandsfreeClient hfpclient = (BluetoothHandsfreeClient)proxy; + hfpclient.close(); + break; } } diff --git a/core/java/android/bluetooth/BluetoothDevice.java b/core/java/android/bluetooth/BluetoothDevice.java index 3ee7142d8d6..a9b627a18f0 100644 --- a/core/java/android/bluetooth/BluetoothDevice.java +++ b/core/java/android/bluetooth/BluetoothDevice.java @@ -1,4 +1,6 @@ /* + * Copyright (C) 2013 The Linux Foundation. All rights reserved + * Not a Contribution. * Copyright (C) 2009 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -259,6 +261,8 @@ public final class BluetoothDevice implements Parcelable { public static final String EXTRA_PAIRING_VARIANT = "android.bluetooth.device.extra.PAIRING_VARIANT"; /** @hide */ + public static final String EXTRA_SECURE_PAIRING = "codeaurora.bluetooth.device.extra.SECURE"; + /** @hide */ public static final String EXTRA_PAIRING_KEY = "android.bluetooth.device.extra.PAIRING_KEY"; /** @@ -294,6 +298,11 @@ public final class BluetoothDevice implements Parcelable { public static final String ACTION_UUID = "android.bluetooth.device.action.UUID"; + /** @hide */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_MAS_INSTANCE = + "org.codeaurora.bluetooth.device.action.MAS_INSTANCE"; + /** * Broadcast Action: Indicates a failure to retrieve the name of a remote * device. @@ -343,6 +352,9 @@ public final class BluetoothDevice implements Parcelable { /**@hide*/ public static final int REQUEST_TYPE_PHONEBOOK_ACCESS = 2; + /**@hide*/ + public static final int REQUEST_TYPE_MESSAGE_ACCESS = 3; + /** * Used as an extra field in {@link #ACTION_CONNECTION_ACCESS_REQUEST} intents, * Contains package name to return reply intent to. @@ -493,6 +505,10 @@ public final class BluetoothDevice implements Parcelable { */ public static final String EXTRA_UUID = "android.bluetooth.device.extra.UUID"; + /** @hide */ + public static final String EXTRA_MAS_INSTANCE = + "org.codeaurora.bluetooth.device.extra.MAS_INSTANCE"; + /** * Lazy initialization. Guaranteed final after first object constructed, or * getService() called. @@ -858,16 +874,20 @@ public BluetoothClass getBluetoothClass() { /** * Get trust state of a remote device. *

    Requires {@link android.Manifest.permission#BLUETOOTH}. + * @return true/false * @hide */ public boolean getTrustState() { - //TODO(BT) - /* + if (sService == null) { + Log.e(TAG, "BT not enabled. Cannot get Remote Device Alias"); + return false; + } + try { - return sService.getTrustState(this); + return sService.getRemoteTrust(this); } catch (RemoteException e) { Log.e(TAG, "", e); - }*/ + } return false; } @@ -875,16 +895,19 @@ public boolean getTrustState() { * Set trust state for a remote device. *

    Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}. * @param value the trust state value (true or false) + * @return true/false * @hide */ - public boolean setTrust(boolean value) { - //TODO(BT) - /* + public boolean setTrust(boolean trustValue) { + if (sService == null) { + Log.e(TAG, "BT not enabled. Cannot set Remote Device name"); + return false; + } try { - return sService.setTrust(this, value); + return sService.setRemoteTrust(this, trustValue); } catch (RemoteException e) { Log.e(TAG, "", e); - }*/ + } return false; } @@ -933,6 +956,18 @@ public boolean fetchUuidsWithSdp() { return false; } + /** @hide */ + public boolean fetchMasInstances() { + if (sService == null) { + Log.e(TAG, "BT not enabled. Cannot query remote device for MAS instances"); + return false; + } + try { + return sService.fetchRemoteMasInstances(this); + } catch (RemoteException e) {Log.e(TAG, "", e);} + return false; + } + /** @hide */ public int getServiceChannel(ParcelUuid uuid) { //TODO(BT) diff --git a/core/java/android/bluetooth/BluetoothDun.java b/core/java/android/bluetooth/BluetoothDun.java new file mode 100644 index 00000000000..37fc34a4299 --- /dev/null +++ b/core/java/android/bluetooth/BluetoothDun.java @@ -0,0 +1,280 @@ +/* + * Copyright (c) 2013, The Linux Foundation. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of The Linux Foundation nor + * the names of its contributors may be used to endorse or promote + * products derived from this software without specific prior written + * permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NON-INFRINGEMENT ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package android.bluetooth; + +import android.annotation.SdkConstant; +import android.annotation.SdkConstant.SdkConstantType; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.ServiceConnection; +import android.os.IBinder; +import android.os.RemoteException; +import android.util.Log; + +import java.util.ArrayList; +import java.util.List; + +/** + * This class provides the APIs to control the Bluetooth Dun + * Profile. + * + *

    BluetoothDun is a proxy object for controlling the Bluetooth DUN + * Service via IPC. Use {@link BluetoothAdapter#getProfileProxy} to get + * the BluetoothDun proxy object. + * + *

    Each method is protected with its appropriate permission. + *@hide + */ +public final class BluetoothDun implements BluetoothProfile { + private static final String TAG = "BluetoothDun"; + private static final boolean DBG = false; + private static final boolean VDBG = false; + + /** + * Intent used to broadcast the change in connection state of the Dun + * profile. + * + *

    This intent will have 3 extras: + *

      + *
    • {@link #EXTRA_STATE} - The current state of the profile.
    • + *
    • {@link #EXTRA_PREVIOUS_STATE}- The previous state of the profile.
    • + *
    • {@link BluetoothDevice#EXTRA_DEVICE} - The remote device.
    • + *
    + * + *

    {@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} can be any of + * {@link #STATE_DISCONNECTED}, {@link #STATE_CONNECTED}. + * + *

    Requires {@link android.Manifest.permission#BLUETOOTH} permission to + * receive. + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_CONNECTION_STATE_CHANGED = + "codeaurora.bluetooth.dun.profile.action.CONNECTION_STATE_CHANGED"; + + private Context mContext; + private ServiceListener mServiceListener; + private BluetoothAdapter mAdapter; + private IBluetoothDun mDunService; + + /** + * Create a BluetoothDun proxy object for interacting with the local + * Bluetooth Service which handles the Dun profile + * + */ + /*package*/ BluetoothDun(Context context, ServiceListener l) { + mContext = context; + mServiceListener = l; + mAdapter = BluetoothAdapter.getDefaultAdapter(); + try { + mAdapter.getBluetoothManager().registerStateChangeCallback(mStateChangeCallback); + } catch (RemoteException re) { + Log.w(TAG,"Unable to register BluetoothStateChangeCallback",re); + } + Log.d(TAG, "BluetoothDun() call bindService"); + if (!context.bindService(new Intent(IBluetoothDun.class.getName()), + mConnection, 0)) { + Log.e(TAG, "Could not bind to Bluetooth DUN Service"); + } + Log.d(TAG, "BluetoothDun(), bindService called"); + } + + /*package*/ void close() { + if (VDBG) log("close()"); + mServiceListener = null; + IBluetoothManager mgr = mAdapter.getBluetoothManager(); + if (mgr != null) { + try { + mgr.unregisterStateChangeCallback(mStateChangeCallback); + } catch (RemoteException re) { + Log.w(TAG,"Unable to unregister BluetoothStateChangeCallback",re); + } + } + + synchronized (mConnection) { + if ( mDunService != null) { + try { + mDunService = null; + mContext.unbindService(mConnection); + } catch (Exception re) { + Log.e(TAG,"",re); + } + } + } + } + + protected void finalize() { + close(); + } + + private IBluetoothStateChangeCallback mStateChangeCallback = + new IBluetoothStateChangeCallback.Stub() { + + @Override + public void onBluetoothStateChange(boolean on) throws RemoteException { + //Handle enable request to bind again. + if (on) { + Log.d(TAG, "onBluetoothStateChange(on) call bindService"); + if (!mContext.bindService(new Intent(IBluetoothDun.class.getName()), + mConnection, 0)) { + Log.e(TAG, "Could not bind to Bluetooth DUN Service"); + } + Log.d(TAG, "BluetoothDun(), bindService called"); + } else { + if (VDBG) Log.d(TAG,"Unbinding service..."); + synchronized (mConnection) { + if ( mDunService != null) { + try { + mDunService = null; + mContext.unbindService(mConnection); + } catch (Exception re) { + Log.e(TAG,"",re); + } + } + } + } + } + }; + + /** + * Initiate disconnection from DUN server. + * + *

    Once the disconnection is initiated by any device either local host + * or remote device, the state will transition from {@link #STATE_CONNECTED} + * to {@link #STATE_DISCONNECTED}. + * + *

    Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} + * permission. + * + * @param device Remote Bluetooth Device + * @return false on immediate error, + * true otherwise + * @hide + */ + public boolean disconnect(BluetoothDevice device) { + if (DBG) log("disconnect(" + device + ")"); + if (mDunService != null && isEnabled() && + isValidDevice(device)) { + try { + return mDunService.disconnect(device); + } catch (RemoteException e) { + Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); + return false; + } + } + if (mDunService == null) Log.w(TAG, "Proxy not attached to service"); + return false; + } + /** + * {@inheritDoc} + */ + public List getConnectedDevices() { + if (VDBG) log("getConnectedDevices()"); + if (mDunService != null && isEnabled()) { + try { + return mDunService.getConnectedDevices(); + } catch (RemoteException e) { + Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); + return new ArrayList(); + } + } + if (mDunService == null) Log.w(TAG, "Proxy not attached to service"); + return new ArrayList(); + } + + /** + * {@inheritDoc} + */ + public List getDevicesMatchingConnectionStates(int[] states) { + if (VDBG) log("getDevicesMatchingStates()"); + if (mDunService != null && isEnabled()) { + try { + return mDunService.getDevicesMatchingConnectionStates(states); + } catch (RemoteException e) { + Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); + return new ArrayList(); + } + } + if (mDunService == null) Log.w(TAG, "Proxy not attached to service"); + return new ArrayList(); + } + + /** + * {@inheritDoc} + */ + public int getConnectionState(BluetoothDevice device) { + if (VDBG) log("getState(" + device + ")"); + if (mDunService != null && isEnabled() + && isValidDevice(device)) { + try { + return mDunService.getConnectionState(device); + } catch (RemoteException e) { + Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); + return BluetoothProfile.STATE_DISCONNECTED; + } + } + if (mDunService == null) Log.w(TAG, "Proxy not attached to service"); + return BluetoothProfile.STATE_DISCONNECTED; + } + + private ServiceConnection mConnection = new ServiceConnection() { + public void onServiceConnected(ComponentName className, IBinder service) { + if (DBG) Log.d(TAG, "BluetoothDUN Proxy object connected"); + mDunService = IBluetoothDun.Stub.asInterface(service); + + if (mServiceListener != null) { + mServiceListener.onServiceConnected(BluetoothProfile.DUN, + BluetoothDun.this); + } + } + public void onServiceDisconnected(ComponentName className) { + if (DBG) Log.d(TAG, "BluetoothDUN Proxy object disconnected"); + mDunService = null; + if (mServiceListener != null) { + mServiceListener.onServiceDisconnected(BluetoothProfile.DUN); + } + } + }; + + private boolean isEnabled() { + if (mAdapter.getState() == BluetoothAdapter.STATE_ON) return true; + return false; + } + + private boolean isValidDevice(BluetoothDevice device) { + if (device == null) return false; + + if (BluetoothAdapter.checkBluetoothAddress(device.getAddress())) return true; + return false; + } + + private static void log(String msg) { + Log.d(TAG, msg); + } +} diff --git a/core/java/android/bluetooth/BluetoothHandsfreeClient.java b/core/java/android/bluetooth/BluetoothHandsfreeClient.java new file mode 100644 index 00000000000..dd4212a6825 --- /dev/null +++ b/core/java/android/bluetooth/BluetoothHandsfreeClient.java @@ -0,0 +1,1158 @@ +/* + * Copyright (C) 2013 The Linux Foundation. All rights reserved + * Not a Contribution. + * Copyright (C) 2008 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.bluetooth; + +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.ServiceConnection; +import android.os.Bundle; +import android.os.IBinder; +import android.os.RemoteException; +import android.util.Log; + +import java.util.ArrayList; +import java.util.List; + +/** + * Public API to control Hands Free Profile (HFP role only). + *

    + * This class defines methods that shall be used by application to manage profile + * connection, calls states and calls actions. + *

    + * + * @hide + * */ +public final class BluetoothHandsfreeClient implements BluetoothProfile { + private static final String TAG = "BluetoothHandsfreeClient"; + private static final boolean DBG = true; + private static final boolean VDBG = false; + + /** + * Intent sent whenever connection to remote changes. + * + *

    It includes two extras: + * BluetoothProfile.EXTRA_PREVIOUS_STATE + * and BluetoothProfile.EXTRA_STATE, which + * are mandatory. + *

    There are also non mandatory feature extras: + * {@link #EXTRA_AG_FEATURE_3WAY_CALLING}, + * {@link #EXTRA_AG_FEATURE_VOICE_RECOGNITION}, + * {@link #EXTRA_AG_FEATURE_ATTACH_NUMBER_TO_VT}, + * {@link #EXTRA_AG_FEATURE_REJECT_CALL}, + * {@link #EXTRA_AG_FEATURE_ECC}, + * {@link #EXTRA_AG_FEATURE_RESPONSE_AND_HOLD}, + * {@link #EXTRA_AG_FEATURE_ACCEPT_HELD_OR_WAITING_CALL}, + * {@link #EXTRA_AG_FEATURE_RELEASE_HELD_OR_WAITING_CALL}, + * {@link #EXTRA_AG_FEATURE_RELEASE_AND_ACCEPT}, + * {@link #EXTRA_AG_FEATURE_MERGE}, + * {@link #EXTRA_AG_FEATURE_MERGE_AND_DETACH}, + * sent as boolean values only when EXTRA_STATE + * is set to STATE_CONNECTED.

    + * + *

    Note that features supported by AG are being sent as + * booleans with value true, + * and not supported ones are not being sent at all.

    + */ + public static final String ACTION_CONNECTION_STATE_CHANGED = + "org.codeaurora.handsfreeclient.profile.action.CONNECTION_STATE_CHANGED"; + + /** + * Intent sent whenever audio state changes. + * + *

    It includes two mandatory extras: + * {@link BluetoothProfile.EXTRA_STATE}, + * {@link BluetoothProfile.EXTRA_PREVIOUS_STATE}, + * with possible values: + * {@link #STATE_AUDIO_CONNECTING}, + * {@link #STATE_AUDIO_CONNECTED}, + * {@link #STATE_AUDIO_DISCONNECTED}

    + *

    When EXTRA_STATE is set + * to STATE_AUDIO_CONNECTED, + * it also includes {@link #EXTRA_AUDIO_WBS} + * indicating wide band speech support.

    + */ + public static final String ACTION_AUDIO_STATE_CHANGED = + "org.codeaurora.handsfreeclient.profile.action.AUDIO_STATE_CHANGED"; + + /** + * Intent sending updates of the Audio Gateway state. + * Each extra is being sent only when value it + * represents has been changed recently on AG. + *

    It can contain one or more of the following extras: + * {@link #EXTRA_NETWORK_STATUS}, + * {@link #EXTRA_NETWORK_SIGNAL_STRENGTH}, + * {@link #EXTRA_NETWORK_ROAMING}, + * {@link #EXTRA_BATTERY_LEVEL}, + * {@link #EXTRA_OPERATOR_NAME}, + * {@link #EXTRA_VOICE_RECOGNITION}, + * {@link #EXTRA_IN_BAND_RING}

    + */ + public static final String ACTION_AG_EVENT = + "org.codeaurora.handsfreeclient.profile.action.AG_EVENT"; + + /** + * Intent sent whenever state of a call changes. + * + *

    It includes: + * {@link #EXTRA_CALL}, + * with value of {@link BluetoothHandsfreeClientCall} instance, + * representing actual call state.

    + */ + public static final String ACTION_CALL_CHANGED = + "org.codeaurora.handsfreeclient.profile.action.AG_CALL_CHANGED"; + + /** + * Intent that notifies about the result of the last issued action. + * Please note that not every action results in explicit action result code being sent. + * Instead other notifications about new Audio Gateway state might be sent, + * like ACTION_AG_EVENT with EXTRA_VOICE_RECOGNITION value + * when for example user started voice recognition from HF unit. + */ + public static final String ACTION_RESULT = + "org.codeaurora.handsfreeclient.profile.action.RESULT"; + + /** + * Intent that notifies about the number attached to the last voice tag + * recorded on AG. + * + *

    It contains: + * {@link #EXTRA_NUMBER}, + * with a String value representing phone number.

    + */ + public static final String ACTION_LAST_VTAG = + "org.codeaurora.handsfreeclient.profile.action.LAST_VTAG"; + + public static final int STATE_AUDIO_DISCONNECTED = 0; + public static final int STATE_AUDIO_CONNECTING = 1; + public static final int STATE_AUDIO_CONNECTED = 2; + + /** + * Extra with information if connected audio is WBS. + *

    Possible values: true, + * false.

    + */ + public static final String EXTRA_AUDIO_WBS = + "android.bluetooth.handsfreeclient.extra.AUDIO_WBS"; + + /** + * Extra for AG_EVENT indicates network status. + *

    Value: 0 - network unavailable, + * 1 - network available

    + */ + public static final String EXTRA_NETWORK_STATUS = + "android.bluetooth.handsfreeclient.extra.NETWORK_STATUS"; + /** + * Extra for AG_EVENT intent indicates network signal strength. + *

    Value: Integer representing signal strength.

    + */ + public static final String EXTRA_NETWORK_SIGNAL_STRENGTH = + "android.bluetooth.handsfreeclient.extra.NETWORK_SIGNAL_STRENGTH"; + /** + * Extra for AG_EVENT intent indicates roaming state. + *

    Value: 0 - no roaming + * 1 - active roaming

    + */ + public static final String EXTRA_NETWORK_ROAMING = + "android.bluetooth.handsfreeclient.extra.NETWORK_ROAMING"; + /** + * Extra for AG_EVENT intent indicates the battery level. + *

    Value: Integer representing signal strength.

    + */ + public static final String EXTRA_BATTERY_LEVEL = + "android.bluetooth.handsfreeclient.extra.BATTERY_LEVEL"; + /** + * Extra for AG_EVENT intent indicates operator name. + *

    Value: String representing operator name.

    + */ + public static final String EXTRA_OPERATOR_NAME = + "android.bluetooth.handsfreeclient.extra.OPERATOR_NAME"; + /** + * Extra for AG_EVENT intent indicates voice recognition state. + *

    Value: + * 0 - voice recognition stopped, + * 1 - voice recognition started.

    + */ + public static final String EXTRA_VOICE_RECOGNITION = + "android.bluetooth.handsfreeclient.extra.VOICE_RECOGNITION"; + /** + * Extra for AG_EVENT intent indicates in band ring state. + *

    Value: + * 0 - in band ring tone not supported, or + * 1 - in band ring tone supported.

    + */ + public static final String EXTRA_IN_BAND_RING = + "android.bluetooth.handsfreeclient.extra.IN_BAND_RING"; + + /** + * Extra for AG_EVENT intent indicates subscriber info. + *

    Value: String containing subscriber information.

    + */ + public static final String EXTRA_SUBSCRIBER_INFO = + "android.bluetooth.handsfreeclient.extra.SUBSCRIBER_INFO"; + + /** + * Extra for AG_CALL_CHANGED intent indicates the + * {@link BluetoothHandsfreeClientCall} object that has changed. + */ + public static final String EXTRA_CALL = + "android.bluetooth.handsfreeclient.extra.CALL"; + + /** + * Extra for ACTION_LAST_VTAG intent. + *

    Value: String representing phone number + * corresponding to last voice tag recorded on AG

    + */ + public static final String EXTRA_NUMBER = + "android.bluetooth.handsfreeclient.extra.NUMBER"; + + /** + * Extra for ACTION_RESULT intent that shows the result code of + * last issued action. + *

    Possible results: + * {@link #ACTION_RESULT_OK}, + * {@link #ACTION_RESULT_ERROR}, + * {@link #ACTION_RESULT_ERROR_NO_CARRIER}, + * {@link #ACTION_RESULT_ERROR_BUSY}, + * {@link #ACTION_RESULT_ERROR_NO_ANSWER}, + * {@link #ACTION_RESULT_ERROR_DELAYED}, + * {@link #ACTION_RESULT_ERROR_BLACKLISTED}, + * {@link #ACTION_RESULT_ERROR_CME}

    + */ + public static final String EXTRA_RESULT_CODE = + "android.bluetooth.handsfreeclient.extra.RESULT_CODE"; + + /** + * Extra for ACTION_RESULT intent that shows the extended result code of + * last issued action. + *

    Value: Integer - error code.

    + */ + public static final String EXTRA_CME_CODE = + "android.bluetooth.handsfreeclient.extra.CME_CODE"; + + /* Extras for AG_FEATURES, extras type is boolean */ + // TODO verify if all of those are actually useful + /** + * AG feature: three way calling. + */ + public final static String EXTRA_AG_FEATURE_3WAY_CALLING = + "android.bluetooth.handsfreeclient.extra.EXTRA_AG_FEATURE_3WAY_CALLING"; + /** + * AG feature: voice recognition. + */ + public final static String EXTRA_AG_FEATURE_VOICE_RECOGNITION = + "android.bluetooth.handsfreeclient.extra.EXTRA_AG_FEATURE_VOICE_RECOGNITION"; + /** + * AG feature: fetching phone number for voice tagging procedure. + */ + public final static String EXTRA_AG_FEATURE_ATTACH_NUMBER_TO_VT = + "android.bluetooth.handsfreeclient.extra.EXTRA_AG_FEATURE_ATTACH_NUMBER_TO_VT"; + /** + * AG feature: ability to reject incoming call. + */ + public final static String EXTRA_AG_FEATURE_REJECT_CALL = + "android.bluetooth.handsfreeclient.extra.EXTRA_AG_FEATURE_REJECT_CALL"; + /** + * AG feature: enhanced call handling (terminate specific call, private consultation). + */ + public final static String EXTRA_AG_FEATURE_ECC = + "android.bluetooth.handsfreeclient.extra.EXTRA_AG_FEATURE_ECC"; + /** + * AG feature: response and hold. + */ + public final static String EXTRA_AG_FEATURE_RESPONSE_AND_HOLD = + "android.bluetooth.handsfreeclient.extra.EXTRA_AG_FEATURE_RESPONSE_AND_HOLD"; + /** + * AG call handling feature: accept held or waiting call in three way calling scenarios. + */ + public final static String EXTRA_AG_FEATURE_ACCEPT_HELD_OR_WAITING_CALL = + "android.bluetooth.handsfreeclient.extra.EXTRA_AG_FEATURE_ACCEPT_HELD_OR_WAITING_CALL"; + /** + * AG call handling feature: release held or waiting call in three way calling scenarios. + */ + public final static String EXTRA_AG_FEATURE_RELEASE_HELD_OR_WAITING_CALL = + "android.bluetooth.handsfreeclient.extra.EXTRA_AG_FEATURE_RELEASE_HELD_OR_WAITING_CALL"; + /** + * AG call handling feature: release active call and accept held or waiting call in three way calling scenarios. + */ + public final static String EXTRA_AG_FEATURE_RELEASE_AND_ACCEPT = + "android.bluetooth.handsfreeclient.extra.EXTRA_AG_FEATURE_RELEASE_AND_ACCEPT"; + /** + * AG call handling feature: merge two calls, held and active - multi party conference mode. + */ + public final static String EXTRA_AG_FEATURE_MERGE = + "android.bluetooth.handsfreeclient.extra.EXTRA_AG_FEATURE_MERGE"; + /** + * AG call handling feature: merge calls and disconnect from multi party + * conversation leaving peers connected to each other. + * Note that this feature needs to be supported by mobile network operator + * as it requires connection and billing transfer. + */ + public final static String EXTRA_AG_FEATURE_MERGE_AND_DETACH = + "android.bluetooth.handsfreeclient.extra.EXTRA_AG_FEATURE_MERGE_AND_DETACH"; + + /* Action result codes */ + public final static int ACTION_RESULT_OK = 0; + public final static int ACTION_RESULT_ERROR = 1; + public final static int ACTION_RESULT_ERROR_NO_CARRIER = 2; + public final static int ACTION_RESULT_ERROR_BUSY = 3; + public final static int ACTION_RESULT_ERROR_NO_ANSWER = 4; + public final static int ACTION_RESULT_ERROR_DELAYED = 5; + public final static int ACTION_RESULT_ERROR_BLACKLISTED = 6; + public final static int ACTION_RESULT_ERROR_CME = 7; + + /* Detailed CME error codes */ + public final static int CME_PHONE_FAILURE = 0; + public final static int CME_NO_CONNECTION_TO_PHONE = 1; + public final static int CME_OPERATION_NOT_ALLOWED = 3; + public final static int CME_OPERATION_NOT_SUPPORTED = 4; + public final static int CME_PHSIM_PIN_REQUIRED = 5; + public final static int CME_PHFSIM_PIN_REQUIRED = 6; + public final static int CME_PHFSIM_PUK_REQUIRED = 7; + public final static int CME_SIM_NOT_INSERTED = 10; + public final static int CME_SIM_PIN_REQUIRED = 11; + public final static int CME_SIM_PUK_REQUIRED = 12; + public final static int CME_SIM_FAILURE = 13; + public final static int CME_SIM_BUSY = 14; + public final static int CME_SIM_WRONG = 15; + public final static int CME_INCORRECT_PASSWORD = 16; + public final static int CME_SIM_PIN2_REQUIRED = 17; + public final static int CME_SIM_PUK2_REQUIRED = 18; + public final static int CME_MEMORY_FULL = 20; + public final static int CME_INVALID_INDEX = 21; + public final static int CME_NOT_FOUND = 22; + public final static int CME_MEMORY_FAILURE = 23; + public final static int CME_TEXT_STRING_TOO_LONG = 24; + public final static int CME_INVALID_CHARACTER_IN_TEXT_STRING = 25; + public final static int CME_DIAL_STRING_TOO_LONG = 26; + public final static int CME_INVALID_CHARACTER_IN_DIAL_STRING = 27; + public final static int CME_NO_NETWORK_SERVICE = 30; + public final static int CME_NETWORK_TIMEOUT = 31; + public final static int CME_EMERGENCY_SERVICE_ONLY = 32; + public final static int CME_NO_SIMULTANOUS_VOIP_CS_CALLS = 33; + public final static int CME_NOT_SUPPORTED_FOR_VOIP = 34; + public final static int CME_SIP_RESPONSE_CODE = 35; + public final static int CME_NETWORK_PERSONALIZATION_PIN_REQUIRED = 40; + public final static int CME_NETWORK_PERSONALIZATION_PUK_REQUIRED = 41; + public final static int CME_NETWORK_SUBSET_PERSONALIZATION_PIN_REQUIRED = 42; + public final static int CME_NETWORK_SUBSET_PERSONALIZATION_PUK_REQUIRED = 43; + public final static int CME_SERVICE_PROVIDER_PERSONALIZATION_PIN_REQUIRED = 44; + public final static int CME_SERVICE_PROVIDER_PERSONALIZATION_PUK_REQUIRED = 45; + public final static int CME_CORPORATE_PERSONALIZATION_PIN_REQUIRED = 46; + public final static int CME_CORPORATE_PERSONALIZATION_PUK_REQUIRED = 47; + public final static int CME_HIDDEN_KEY_REQUIRED = 48; + public final static int CME_EAP_NOT_SUPPORTED = 49; + public final static int CME_INCORRECT_PARAMETERS = 50; + + /* Action policy for other calls when accepting call */ + public static final int CALL_ACCEPT_NONE = 0; + public static final int CALL_ACCEPT_HOLD = 1; + public static final int CALL_ACCEPT_TERMINATE = 2; + + private Context mContext; + private ServiceListener mServiceListener; + private IBluetoothHandsfreeClient mService; + private BluetoothAdapter mAdapter; + + final private IBluetoothStateChangeCallback mBluetoothStateChangeCallback = + new IBluetoothStateChangeCallback.Stub() { + @Override + public void onBluetoothStateChange(boolean up) { + if (DBG) Log.d(TAG, "onBluetoothStateChange: up=" + up); + if (!up) { + if (VDBG) Log.d(TAG,"Unbinding service..."); + synchronized (mConnection) { + try { + mService = null; + mContext.unbindService(mConnection); + } catch (Exception re) { + Log.e(TAG,"",re); + } + } + } else { + synchronized (mConnection) { + try { + if (mService == null) { + if (VDBG) Log.d(TAG,"Binding service..."); + if (!mContext.bindService(new Intent(IBluetoothHandsfreeClient.class.getName()), mConnection, 0)) { + Log.e(TAG, "Could not bind to Bluetooth Handsfree Client Service"); + } + } + } catch (Exception re) { + Log.e(TAG,"",re); + } + } + } + } + }; + + /** + * Create a BluetoothHandsfreeClient proxy object. + */ + /*package*/ BluetoothHandsfreeClient(Context context, ServiceListener l) { + mContext = context; + mServiceListener = l; + mAdapter = BluetoothAdapter.getDefaultAdapter(); + + IBluetoothManager mgr = mAdapter.getBluetoothManager(); + if (mgr != null) { + try { + mgr.registerStateChangeCallback(mBluetoothStateChangeCallback); + } catch (RemoteException e) { + Log.e(TAG,"",e); + } + } + + if (!context.bindService(new Intent(IBluetoothHandsfreeClient.class.getName()), mConnection, 0)) { + Log.e(TAG, "Could not bind to Bluetooth Handsfree Client Service"); + } + } + + /** + * Close the connection to the backing service. + * Other public functions of BluetoothHandsfreeClient will return default error + * results once close() has been called. Multiple invocations of close() + * are ok. + */ + /*package*/ void close() { + if (VDBG) log("close()"); + + IBluetoothManager mgr = mAdapter.getBluetoothManager(); + if (mgr != null) { + try { + mgr.unregisterStateChangeCallback(mBluetoothStateChangeCallback); + } catch (Exception e) { + Log.e(TAG,"",e); + } + } + + synchronized (mConnection) { + if (mService != null) { + try { + mService = null; + mContext.unbindService(mConnection); + } catch (Exception re) { + Log.e(TAG,"",re); + } + } + } + mServiceListener = null; + } + + /** + * Connects to remote device. + * + * Currently, the system supports only 1 connection. So, in case of the + * second connection, this implementation will disconnect already connected + * device automatically and will process the new one. + * + * @param device a remote device we want connect to + * @return true if command has been issued successfully; + * false otherwise; + * upon completion HFP sends {@link #ACTION_CONNECTION_STATE_CHANGED} + * intent. + */ + public boolean connect(BluetoothDevice device) { + if (DBG) log("connect(" + device + ")"); + if (mService != null && isEnabled() && + isValidDevice(device)) { + try { + return mService.connect(device); + } catch (RemoteException e) { + Log.e(TAG, Log.getStackTraceString(new Throwable())); + return false; + } + } + if (mService == null) Log.w(TAG, "Proxy not attached to service"); + return false; + } + + /** + * Disconnects remote device + * + * @param device a remote device we want disconnect + * @return true if command has been issued successfully; + * false otherwise; + * upon completion HFP sends {@link #ACTION_CONNECTION_STATE_CHANGED} + * intent. + */ + public boolean disconnect(BluetoothDevice device) { + if (DBG) log("disconnect(" + device + ")"); + if (mService != null && isEnabled() && + isValidDevice(device)) { + try { + return mService.disconnect(device); + } catch (RemoteException e) { + Log.e(TAG, Log.getStackTraceString(new Throwable())); + return false; + } + } + if (mService == null) Log.w(TAG, "Proxy not attached to service"); + return false; + } + + /** + * Return the list of connected remote devices + * + * @return list of connected devices; empty list if nothing is connected. + */ + @Override + public List getConnectedDevices() { + if (VDBG) log("getConnectedDevices()"); + if (mService != null && isEnabled()) { + try { + return mService.getConnectedDevices(); + } catch (RemoteException e) { + Log.e(TAG, Log.getStackTraceString(new Throwable())); + return new ArrayList(); + } + } + if (mService == null) Log.w(TAG, "Proxy not attached to service"); + return new ArrayList(); + } + + /** + * Returns list of remote devices in a particular state + * + * @param states collection of states + * @return list of devices that state matches the states listed in + * states; empty list if nothing matches the + * states + */ + @Override + public List getDevicesMatchingConnectionStates(int[] states) { + if (VDBG) log("getDevicesMatchingStates()"); + if (mService != null && isEnabled()) { + try { + return mService.getDevicesMatchingConnectionStates(states); + } catch (RemoteException e) { + Log.e(TAG, Log.getStackTraceString(new Throwable())); + return new ArrayList(); + } + } + if (mService == null) Log.w(TAG, "Proxy not attached to service"); + return new ArrayList(); + } + + /** + * Returns state of the device + * + * @param device a remote device + * @return the state of connection of the device + */ + @Override + public int getConnectionState(BluetoothDevice device) { + if (VDBG) log("getConnectionState(" + device + ")"); + if (mService != null && isEnabled() && + isValidDevice(device)) { + try { + return mService.getConnectionState(device); + } catch (RemoteException e) { + Log.e(TAG, Log.getStackTraceString(new Throwable())); + return BluetoothProfile.STATE_DISCONNECTED; + } + } + if (mService == null) Log.w(TAG, "Proxy not attached to service"); + return BluetoothProfile.STATE_DISCONNECTED; + } + + /** + * Set priority of the profile + * + * The device should already be paired. + */ + public boolean setPriority(BluetoothDevice device, int priority) { + if (DBG) log("setPriority(" + device + ", " + priority + ")"); + if (mService != null && isEnabled() && + isValidDevice(device)) { + if (priority != BluetoothProfile.PRIORITY_OFF && + priority != BluetoothProfile.PRIORITY_ON) { + return false; + } + try { + return mService.setPriority(device, priority); + } catch (RemoteException e) { + Log.e(TAG, Log.getStackTraceString(new Throwable())); + return false; + } + } + if (mService == null) Log.w(TAG, "Proxy not attached to service"); + return false; + } + + /** + * Get the priority of the profile. + */ + public int getPriority(BluetoothDevice device) { + if (VDBG) log("getPriority(" + device + ")"); + if (mService != null && isEnabled() && + isValidDevice(device)) { + try { + return mService.getPriority(device); + } catch (RemoteException e) { + Log.e(TAG, Log.getStackTraceString(new Throwable())); + return PRIORITY_OFF; + } + } + if (mService == null) Log.w(TAG, "Proxy not attached to service"); + return PRIORITY_OFF; + } + + /** + * Starts voice recognition. + * + * @param device remote device + * @return true if command has been issued successfully; + * false otherwise; + * upon completion HFP sends {@link #ACTION_AG_EVENT} + * intent. + * + *

    Feature required for successful execution is being reported by: + * {@link #EXTRA_AG_FEATURE_VOICE_RECOGNITION}. + * This method invocation will fail silently when feature is not supported.

    + */ + public boolean startVoiceRecognition(BluetoothDevice device) { + if (DBG) log("startVoiceRecognition()"); + if (mService != null && isEnabled() && + isValidDevice(device)) { + try { + return mService.startVoiceRecognition(device); + } catch (RemoteException e) { + Log.e(TAG, Log.getStackTraceString(new Throwable())); + } + } + if (mService == null) Log.w(TAG, "Proxy not attached to service"); + return false; + } + + /** + * Stops voice recognition. + * + * @param device remote device + * @return true if command has been issued successfully; + * false otherwise; + * upon completion HFP sends {@link #ACTION_AG_EVENT} + * intent. + * + *

    Feature required for successful execution is being reported by: + * {@link #EXTRA_AG_FEATURE_VOICE_RECOGNITION}. + * This method invocation will fail silently when feature is not supported.

    + */ + public boolean stopVoiceRecognition(BluetoothDevice device) { + if (DBG) log("stopVoiceRecognition()"); + if (mService != null && isEnabled() && + isValidDevice(device)) { + try { + return mService.stopVoiceRecognition(device); + } catch (RemoteException e) { + Log.e(TAG, Log.getStackTraceString(new Throwable())); + } + } + if (mService == null) Log.w(TAG, "Proxy not attached to service"); + return false; + } + + /** + * Returns list of all calls in any state. + * + * @param device remote device + * @return list of calls; empty list if none call exists + */ + public List getCurrentCalls(BluetoothDevice device) { + if (DBG) log("getCurrentCalls()"); + if (mService != null && isEnabled() && + isValidDevice(device)) { + try { + return mService.getCurrentCalls(device); + } catch (RemoteException e) { + Log.e(TAG, Log.getStackTraceString(new Throwable())); + } + } + if (mService == null) Log.w(TAG, "Proxy not attached to service"); + return null; + } + + /** + * Returns list of current values of AG indicators. + * + * @param device remote device + * @return bundle of AG indicators; null if device is not in + * CONNECTED state + */ + public Bundle getCurrentAgEvents(BluetoothDevice device) { + if (DBG) log("getCurrentCalls()"); + if (mService != null && isEnabled() && + isValidDevice(device)) { + try { + return mService.getCurrentAgEvents(device); + } catch (RemoteException e) { + Log.e(TAG, Log.getStackTraceString(new Throwable())); + } + } + if (mService == null) Log.w(TAG, "Proxy not attached to service"); + return null; + } + + /** + * Accepts a call + * + * @param device remote device + * @param flag action policy while accepting a call. Possible values + * {@link #CALL_ACCEPT_NONE}, {@link #CALL_ACCEPT_HOLD}, + * {@link #CALL_ACCEPT_TERMINATE} + * @return true if command has been issued successfully; + * false otherwise; + * upon completion HFP sends {@link #ACTION_CALL_CHANGED} + * intent. + */ + public boolean acceptCall(BluetoothDevice device, int flag) { + if (DBG) log("acceptCall()"); + if (mService != null && isEnabled() && + isValidDevice(device)) { + try { + return mService.acceptCall(device, flag); + } catch (RemoteException e) { + Log.e(TAG, Log.getStackTraceString(new Throwable())); + } + } + if (mService == null) Log.w(TAG, "Proxy not attached to service"); + return false; + } + + /** + * Holds a call. + * + * @param device remote device + * @return true if command has been issued successfully; + * false otherwise; + * upon completion HFP sends {@link #ACTION_CALL_CHANGED} + * intent. + */ + public boolean holdCall(BluetoothDevice device) { + if (DBG) log("holdCall()"); + if (mService != null && isEnabled() && + isValidDevice(device)) { + try { + return mService.holdCall(device); + } catch (RemoteException e) { + Log.e(TAG, Log.getStackTraceString(new Throwable())); + } + } + if (mService == null) Log.w(TAG, "Proxy not attached to service"); + return false; + } + + /** + * Rejects a call. + * + * @param device remote device + * @return true if command has been issued successfully; + * false otherwise; + * upon completion HFP sends {@link #ACTION_CALL_CHANGED} + * intent. + * + *

    Feature required for successful execution is being reported by: + * {@link #EXTRA_AG_FEATURE_REJECT_CALL}. + * This method invocation will fail silently when feature is not supported.

    + */ + public boolean rejectCall(BluetoothDevice device) { + if (DBG) log("rejectCall()"); + if (mService != null && isEnabled() && + isValidDevice(device)) { + try { + return mService.rejectCall(device); + } catch (RemoteException e) { + Log.e(TAG, Log.getStackTraceString(new Throwable())); + } + } + if (mService == null) Log.w(TAG, "Proxy not attached to service"); + return false; + } + + /** + * Terminates a specified call. + * + * Works only when Extended Call Control is supported by Audio Gateway. + * + * @param device remote device + * @param index index of the call to be terminated + * @return true if command has been issued successfully; + * false otherwise; + * upon completion HFP sends {@link #ACTION_CALL_CHANGED} + * intent. + * + *

    Feature required for successful execution is being reported by: + * {@link #EXTRA_AG_FEATURE_ECC}. + * This method invocation will fail silently when feature is not supported.

    + */ + public boolean terminateCall(BluetoothDevice device, int index) { + if (DBG) log("terminateCall()"); + if (mService != null && isEnabled() && + isValidDevice(device)) { + try { + return mService.terminateCall(device, index); + } catch (RemoteException e) { + Log.e(TAG, Log.getStackTraceString(new Throwable())); + } + } + if (mService == null) Log.w(TAG, "Proxy not attached to service"); + return false; + } + + /** + * Enters private mode with a specified call. + * + * Works only when Extended Call Control is supported by Audio Gateway. + * + * @param device remote device + * @param index index of the call to connect in private mode + * @return true if command has been issued successfully; + * false otherwise; + * upon completion HFP sends {@link #ACTION_CALL_CHANGED} + * intent. + * + *

    Feature required for successful execution is being reported by: + * {@link #EXTRA_AG_FEATURE_ECC}. + * This method invocation will fail silently when feature is not supported.

    + */ + public boolean enterPrivateMode(BluetoothDevice device, int index) { + if (DBG) log("enterPrivateMode()"); + if (mService != null && isEnabled() && + isValidDevice(device)) { + try { + return mService.enterPrivateMode(device, index); + } catch (RemoteException e) { + Log.e(TAG, Log.getStackTraceString(new Throwable())); + } + } + if (mService == null) Log.w(TAG, "Proxy not attached to service"); + return false; + } + + /** + * Performs explicit call transfer. + * + * That means connect other calls and disconnect. + * + * @param device remote device + * @return true if command has been issued successfully; + * false otherwise; + * upon completion HFP sends {@link #ACTION_CALL_CHANGED} + * intent. + * + *

    Feature required for successful execution is being reported by: + * {@link #EXTRA_AG_FEATURE_MERGE_AND_DETACH}. + * This method invocation will fail silently when feature is not supported.

    + */ + public boolean explicitCallTransfer(BluetoothDevice device) { + if (DBG) log("explicitCallTransfer()"); + if (mService != null && isEnabled() && + isValidDevice(device)) { + try { + return mService.explicitCallTransfer(device); + } catch (RemoteException e) { + Log.e(TAG, Log.getStackTraceString(new Throwable())); + } + } + if (mService == null) Log.w(TAG, "Proxy not attached to service"); + return false; + } + + /** + * Redials last number from Audio Gateway. + * + * @param device remote device + * @return true if command has been issued successfully; + * false otherwise; + * upon completion HFP sends {@link #ACTION_CALL_CHANGED} + * intent in case of success; {@link #ACTION_RESULT} is sent + * otherwise; + */ + public boolean redial(BluetoothDevice device) { + if (DBG) log("redial()"); + if (mService != null && isEnabled() && + isValidDevice(device)) { + try { + return mService.redial(device); + } catch (RemoteException e) { + Log.e(TAG, Log.getStackTraceString(new Throwable())); + } + } + if (mService == null) Log.w(TAG, "Proxy not attached to service"); + return false; + } + + /** + * Places a call with specified number. + * + * @param device remote device + * @param number valid phone number + * @return true if command has been issued successfully; + * false otherwise; + * upon completion HFP sends {@link #ACTION_CALL_CHANGED} + * intent in case of success; {@link #ACTION_RESULT} is sent + * otherwise; + */ + public boolean dial(BluetoothDevice device, String number) { + if (DBG) log("dial()"); + if (mService != null && isEnabled() && + isValidDevice(device)) { + try { + return mService.dial(device, number); + } catch (RemoteException e) { + Log.e(TAG, Log.getStackTraceString(new Throwable())); + } + } + if (mService == null) Log.w(TAG, "Proxy not attached to service"); + return false; + } + + /** + * Places a call to the number under specified memory location. + * + * @param device remote device + * @param location valid memory location + * @return true if command has been issued successfully; + * false otherwise; + * upon completion HFP sends {@link #ACTION_CALL_CHANGED} + * intent in case of success; {@link #ACTION_RESULT} is sent + * otherwise; + */ + public boolean dialMemory(BluetoothDevice device, int location) { + if (DBG) log("dialMemory()"); + if (mService != null && isEnabled() && + isValidDevice(device)) { + try { + return mService.dialMemory(device, location); + } catch (RemoteException e) { + Log.e(TAG, Log.getStackTraceString(new Throwable())); + } + } + if (mService == null) Log.w(TAG, "Proxy not attached to service"); + return false; + } + + /** + * Sends DTMF code. + * + * Possible code values : 0,1,2,3,4,5,6,7,8,9,A,B,C,D,*,# + * + * @param device remote device + * @param code ASCII code + * @return true if command has been issued successfully; + * false otherwise; + * upon completion HFP sends {@link #ACTION_RESULT} intent; + */ + public boolean sendDTMF(BluetoothDevice device, byte code) { + if (DBG) log("sendDTMF()"); + if (mService != null && isEnabled() && + isValidDevice(device)) { + try { + return mService.sendDTMF(device, code); + } catch (RemoteException e) { + Log.e(TAG, Log.getStackTraceString(new Throwable())); + } + } + if (mService == null) Log.w(TAG, "Proxy not attached to service"); + return false; + } + + /** + * Get a number corresponding to last voice tag recorded on AG. + * + * @param device remote device + * @return true if command has been issued successfully; + * false otherwise; + * upon completion HFP sends {@link #ACTION_LAST_VTAG} + * or {@link #ACTION_RESULT} intent; + * + *

    Feature required for successful execution is being reported by: + * {@link #EXTRA_AG_FEATURE_ATTACH_NUMBER_TO_VT}. + * This method invocation will fail silently when feature is not supported.

    + */ + public boolean getLastVoiceTagNumber(BluetoothDevice device) { + if (DBG) log("getLastVoiceTagNumber()"); + if (mService != null && isEnabled() && + isValidDevice(device)) { + try { + return mService.getLastVoiceTagNumber(device); + } catch (RemoteException e) { + Log.e(TAG, Log.getStackTraceString(new Throwable())); + } + } + if (mService == null) Log.w(TAG, "Proxy not attached to service"); + return false; + } + + /** + * Accept the incoming connection. + */ + public boolean acceptIncomingConnect(BluetoothDevice device) { + if (DBG) log("acceptIncomingConnect"); + if (mService != null && isEnabled()) { + try { + return mService.acceptIncomingConnect(device); + } catch (RemoteException e) {Log.e(TAG, e.toString());} + } else { + Log.w(TAG, "Proxy not attached to service"); + if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable())); + } + return false; + } + + /** + * Reject the incoming connection. + */ + public boolean rejectIncomingConnect(BluetoothDevice device) { + if (DBG) log("rejectIncomingConnect"); + if (mService != null) { + try { + return mService.rejectIncomingConnect(device); + } catch (RemoteException e) {Log.e(TAG, e.toString());} + } else { + Log.w(TAG, "Proxy not attached to service"); + if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable())); + } + return false; + } + + /** + * Returns current audio state of Audio Gateway. + * + * Note: This is an internal function and shouldn't be exposed + */ + public int getAudioState(BluetoothDevice device) { + if (VDBG) log("getAudioState"); + if (mService != null && isEnabled()) { + try { + return mService.getAudioState(device); + } catch (RemoteException e) {Log.e(TAG, e.toString());} + } else { + Log.w(TAG, "Proxy not attached to service"); + if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable())); + } + return BluetoothHandsfreeClient.STATE_AUDIO_DISCONNECTED; + } + + /** + * Initiates a connection of audio channel. + * + * It setup SCO channel with remote connected Handsfree AG device. + * + * @return true if command has been issued successfully; + * false otherwise; + * upon completion HFP sends {@link #ACTION_AUDIO_STATE_CHANGED} + * intent; + */ + public boolean connectAudio() { + if (mService != null && isEnabled()) { + try { + return mService.connectAudio(); + } catch (RemoteException e) { + Log.e(TAG, e.toString()); + } + } else { + Log.w(TAG, "Proxy not attached to service"); + if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable())); + } + return false; + } + + /** + * Disconnects audio channel. + * + * It tears down the SCO channel from remote AG device. + * + * @return true if command has been issued successfully; + * false otherwise; + * upon completion HFP sends {@link #ACTION_AUDIO_STATE_CHANGED} + * intent; + */ + public boolean disconnectAudio() { + if (mService != null && isEnabled()) { + try { + return mService.disconnectAudio(); + } catch (RemoteException e) { + Log.e(TAG, e.toString()); + } + } else { + Log.w(TAG, "Proxy not attached to service"); + if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable())); + } + return false; + } + + /** + * Get Audio Gateway features + * + * @param device remote device + * @return bundle of AG features; null if no service or + * AG not connected + */ + public Bundle getCurrentAgFeatures(BluetoothDevice device) { + if (mService != null && isEnabled()) { + try { + return mService.getCurrentAgFeatures(device); + } catch (RemoteException e) { + Log.e(TAG, e.toString()); + } + } else { + Log.w(TAG, "Proxy not attached to service"); + if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable())); + } + return null; + } + + + private ServiceConnection mConnection = new ServiceConnection() { + @Override + public void onServiceConnected(ComponentName className, IBinder service) { + if (DBG) Log.d(TAG, "Proxy object connected"); + mService = IBluetoothHandsfreeClient.Stub.asInterface(service); + + if (mServiceListener != null) { + mServiceListener.onServiceConnected(BluetoothProfile.HANDSFREE_CLIENT, BluetoothHandsfreeClient.this); + } + } + @Override + public void onServiceDisconnected(ComponentName className) { + if (DBG) Log.d(TAG, "Proxy object disconnected"); + mService = null; + if (mServiceListener != null) { + mServiceListener.onServiceDisconnected(BluetoothProfile.HANDSFREE_CLIENT); + } + } + }; + + private boolean isEnabled() { + if (mAdapter.getState() == BluetoothAdapter.STATE_ON) return true; + return false; + } + + private boolean isValidDevice(BluetoothDevice device) { + if (device == null) return false; + + if (BluetoothAdapter.checkBluetoothAddress(device.getAddress())) return true; + return false; + } + + private static void log(String msg) { + Log.d(TAG, msg); + } +} diff --git a/core/java/android/bluetooth/BluetoothHandsfreeClientCall.aidl b/core/java/android/bluetooth/BluetoothHandsfreeClientCall.aidl new file mode 100644 index 00000000000..ca90385a8dc --- /dev/null +++ b/core/java/android/bluetooth/BluetoothHandsfreeClientCall.aidl @@ -0,0 +1,20 @@ +/* + * Copyright (C) 2013 The Linux Foundation. All rights reserved + * Not a Contribution. + * Copyright (C) 2008 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.bluetooth; + +parcelable BluetoothHandsfreeClientCall; diff --git a/core/java/android/bluetooth/BluetoothHandsfreeClientCall.java b/core/java/android/bluetooth/BluetoothHandsfreeClientCall.java new file mode 100644 index 00000000000..872ab6be0c9 --- /dev/null +++ b/core/java/android/bluetooth/BluetoothHandsfreeClientCall.java @@ -0,0 +1,205 @@ +/* + * Copyright (c) 2013, The Linux Foundation. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of The Linux Foundation nor + * the names of its contributors may be used to endorse or promote + * products derived from this software without specific prior written + * permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NON-INFRINGEMENT ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package android.bluetooth; + +import android.os.Parcel; +import android.os.Parcelable; + +/** + * This class represents a single call, its state and properties. + * It implements {@link Parcelable} for inter-process message passing. + * @hide + */ +public final class BluetoothHandsfreeClientCall implements Parcelable { + + /* Call state */ + /** + * Call is active. + */ + public static final int CALL_STATE_ACTIVE = 0; + /** + * Call is in held state. + */ + public static final int CALL_STATE_HELD = 1; + /** + * Outgoing call that is being dialed right now. + */ + public static final int CALL_STATE_DIALING = 2; + /** + * Outgoing call that remote party has already been alerted about. + */ + public static final int CALL_STATE_ALERTING = 3; + /** + * Incoming call that can be accepted or rejected. + */ + public static final int CALL_STATE_INCOMING = 4; + /** + * Waiting call state when there is already an active call. + */ + public static final int CALL_STATE_WAITING = 5; + /** + * Call that has been held by response and hold + * (see Bluetooth specification for further references). + */ + public static final int CALL_STATE_HELD_BY_RESPONSE_AND_HOLD = 6; + /** + * Call that has been already terminated and should not be referenced as a valid call. + */ + public static final int CALL_STATE_TERMINATED = 7; + + private final int mId; + private int mState; + private String mNumber; + private boolean mMultiParty; + private final boolean mOutgoing; + + /** + * Creates BluetoothHandsfreeClientCall instance. + */ + public BluetoothHandsfreeClientCall(int id, int state, String number, boolean multiParty, + boolean outgoing) { + mId = id; + mState = state; + mNumber = number != null ? number : ""; + mMultiParty = multiParty; + mOutgoing = outgoing; + } + + /** + * Sets call's state. + * + *

    Note: This is an internal function and shouldn't be exposed

    + * + * @param state new call state. + */ + public void setState(int state) { + mState = state; + } + + /** + * Sets call's number. + * + *

    Note: This is an internal function and shouldn't be exposed

    + * + * @param number String representing phone number. + */ + public void setNumber(String number) { + mNumber = number; + } + + /** + * Sets this call as multi party call. + * + *

    Note: This is an internal function and shouldn't be exposed

    + * + * @param multiParty if true sets this call as a part + * of multi party conference. + */ + public void setMultiParty(boolean multiParty) { + mMultiParty = multiParty; + } + + /** + * Gets call's Id. + * + * @return call id. + */ + public int getId() { + return mId; + } + + /** + * Gets call's current state. + * + * @return state of this particular phone call. + */ + public int getState() { + return mState; + } + + /** + * Gets call's number. + * + * @return string representing phone number. + */ + public String getNumber() { + return mNumber; + } + + /** + * Checks if call is an active call in a conference mode (aka multi party). + * + * @return true if call is a multi party call, + * false otherwise. + */ + public boolean isMultiParty() { + return mMultiParty; + } + + /** + * Checks if this call is an outgoing call. + * + * @return true if its outgoing call, + * false otherwise. + */ + public boolean isOutgoing() { + return mOutgoing; + } + + /** + * {@link Parcelable.Creator} interface implementation. + */ + public static final Parcelable.Creator CREATOR = + new Parcelable.Creator() { + @Override + public BluetoothHandsfreeClientCall createFromParcel(Parcel in) { + return new BluetoothHandsfreeClientCall(in.readInt(), in.readInt(), + in.readString(), in.readInt() == 1, in.readInt() == 1); + } + + @Override + public BluetoothHandsfreeClientCall[] newArray(int size) { + return new BluetoothHandsfreeClientCall[size]; + } + }; + + @Override + public void writeToParcel(Parcel out, int flags) { + out.writeInt(mId); + out.writeInt(mState); + out.writeString(mNumber); + out.writeInt(mMultiParty ? 1 : 0); + out.writeInt(mOutgoing ? 1 : 0); + } + + @Override + public int describeContents() { + return 0; + } +} diff --git a/core/java/android/bluetooth/BluetoothInputDevice.java b/core/java/android/bluetooth/BluetoothInputDevice.java index db7e424d40e..ea6c67fe5e9 100644 --- a/core/java/android/bluetooth/BluetoothInputDevice.java +++ b/core/java/android/bluetooth/BluetoothInputDevice.java @@ -84,6 +84,12 @@ public final class BluetoothInputDevice implements BluetoothProfile { public static final String ACTION_VIRTUAL_UNPLUG_STATUS = "android.bluetooth.input.profile.action.VIRTUAL_UNPLUG_STATUS"; + /** + * @hide + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_IDLE_TIME_CHANGED = + "codeaurora.bluetooth.input.profile.action.IDLE_TIME_CHANGED"; /** * Return codes for the connect and disconnect Bluez / Dbus calls. @@ -182,6 +188,11 @@ public final class BluetoothInputDevice implements BluetoothProfile { */ public static final String EXTRA_VIRTUAL_UNPLUG_STATUS = "android.bluetooth.BluetoothInputDevice.extra.VIRTUAL_UNPLUG_STATUS"; + /** + * @hide + */ + public static final String EXTRA_IDLE_TIME = "codeaurora.bluetooth.BluetoothInputDevice.extra.IDLE_TIME"; + private Context mContext; private ServiceListener mServiceListener; private BluetoothAdapter mAdapter; @@ -634,6 +645,56 @@ public boolean sendData(BluetoothDevice device, String report) { if (mService == null) Log.w(TAG, "Proxy not attached to service"); return false; } + + /** + * Send Get_Idle_Time command to the connected HID input device. + * + *

    Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} permission. + * + * @param device Remote Bluetooth Device + * @return false on immediate error, + * true otherwise + * @hide + */ + public boolean getIdleTime(BluetoothDevice device) { + if (DBG) log("getIdletime(" + device + ")"); + if (mService != null && isEnabled() && isValidDevice(device)) { + try { + return mService.getIdleTime(device); + } catch (RemoteException e) { + Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); + return false; + } + } + if (mService == null) Log.w(TAG, "Proxy not attached to service"); + return false; + } + + /** + * Send Set_Idle_Time command to the connected HID input device. + * + *

    Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} permission. + * + * @param device Remote Bluetooth Device + * @param idleTime Idle time to be set on HID Device + * @return false on immediate error, + * true otherwise + * @hide + */ + public boolean setIdleTime(BluetoothDevice device, byte idleTime) { + if (DBG) log("setIdletime(" + device + "), idleTime=" + idleTime); + if (mService != null && isEnabled() && isValidDevice(device)) { + try { + return mService.setIdleTime(device, idleTime); + } catch (RemoteException e) { + Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); + return false; + } + } + if (mService == null) Log.w(TAG, "Proxy not attached to service"); + return false; + } + private static void log(String msg) { Log.d(TAG, msg); } diff --git a/core/java/android/bluetooth/BluetoothMasInstance.java b/core/java/android/bluetooth/BluetoothMasInstance.java new file mode 100644 index 00000000000..72cfcf53c5f --- /dev/null +++ b/core/java/android/bluetooth/BluetoothMasInstance.java @@ -0,0 +1,116 @@ +/* + * Copyright (c) 2013, The Linux Foundation. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of The Linux Foundation nor + * the names of its contributors may be used to endorse or promote + * products derived from this software without specific prior written + * permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NON-INFRINGEMENT ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + + +package android.bluetooth; + +import android.os.Parcel; +import android.os.Parcelable; + +/** @hide */ +public final class BluetoothMasInstance implements Parcelable { + private final int mId; + private final String mName; + private final int mChannel; + private final int mMsgTypes; + + public BluetoothMasInstance(int id, String name, int channel, int msgTypes) { + mId = id; + mName = name; + mChannel = channel; + mMsgTypes = msgTypes; + } + + @Override + public boolean equals(Object o) { + if (o instanceof BluetoothMasInstance) { + return mId == ((BluetoothMasInstance)o).mId; + } + return false; + } + + @Override + public int hashCode() { + return mId + (mChannel << 8) + (mMsgTypes << 16); + } + + @Override + public String toString() { + return Integer.toString(mId) + ":" + mName + ":" + mChannel + ":" + + Integer.toHexString(mMsgTypes); + } + + public int describeContents() { + return 0; + } + + public static final Parcelable.Creator CREATOR = + new Parcelable.Creator() { + public BluetoothMasInstance createFromParcel(Parcel in) { + return new BluetoothMasInstance(in.readInt(), in.readString(), + in.readInt(), in.readInt()); + } + public BluetoothMasInstance[] newArray(int size) { + return new BluetoothMasInstance[size]; + } + }; + + public void writeToParcel(Parcel out, int flags) { + out.writeInt(mId); + out.writeString(mName); + out.writeInt(mChannel); + out.writeInt(mMsgTypes); + } + + public static final class MessageType { + public static final int EMAIL = 0x01; + public static final int SMS_GSM = 0x02; + public static final int SMS_CDMA = 0x04; + public static final int MMS = 0x08; + } + + public int getId() { + return mId; + } + + public String getName() { + return mName; + } + + public int getChannel() { + return mChannel; + } + + public int getMsgTypes() { + return mMsgTypes; + } + + public boolean msgSupported(int msg) { + return (mMsgTypes & msg) != 0; + } +} diff --git a/core/java/android/bluetooth/BluetoothProfile.java b/core/java/android/bluetooth/BluetoothProfile.java index 43079f44f5b..f92f83dc78e 100644 --- a/core/java/android/bluetooth/BluetoothProfile.java +++ b/core/java/android/bluetooth/BluetoothProfile.java @@ -1,4 +1,6 @@ /* + * Copyright (C) 2013 The Linux Foundation. All rights reserved + * Not a Contribution. * Copyright (C) 2010 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -97,6 +99,24 @@ public interface BluetoothProfile { */ static public final int GATT_SERVER = 8; + /** + * SAP + * @hide + */ + public static final int SAP = 20; + + /** + * Handsfree Client - HFP HF Role + * @hide + */ + public static final int HANDSFREE_CLIENT = 9; + + /** + * DUN + * @hide + */ + public static final int DUN = 21; + /** * Default priority for devices that we try to auto-connect to and * and allow incoming connections for the profile diff --git a/core/java/android/bluetooth/BluetoothSap.java b/core/java/android/bluetooth/BluetoothSap.java new file mode 100644 index 00000000000..5a4ed7d2d07 --- /dev/null +++ b/core/java/android/bluetooth/BluetoothSap.java @@ -0,0 +1,279 @@ +/* + * Copyright (c) 2013, The Linux Foundation. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of The Linux Foundation nor + * the names of its contributors may be used to endorse or promote + * products derived from this software without specific prior written + * permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NON-INFRINGEMENT ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package android.bluetooth; + +import android.annotation.SdkConstant; +import android.annotation.SdkConstant.SdkConstantType; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.ServiceConnection; +import android.os.IBinder; +import android.os.RemoteException; +import android.util.Log; + +import java.util.ArrayList; +import java.util.List; + +/** + * This class provides the APIs to control the Bluetooth Sap + * Profile. + * + *

    BluetoothSap is a proxy object for controlling the Bluetooth SAP + * Service via IPC. Use {@link BluetoothAdapter#getProfileProxy} to get + * the BluetoothSap proxy object. + * + *

    Each method is protected with its appropriate permission. + *@hide + */ +public final class BluetoothSap implements BluetoothProfile { + private static final String TAG = "BluetoothSap"; + private static final boolean DBG = false; + private static final boolean VDBG = false; + + /** + * Intent used to broadcast the change in connection state of the Sap + * profile. + * + *

    This intent will have 3 extras: + *

      + *
    • {@link #EXTRA_STATE} - The current state of the profile.
    • + *
    • {@link #EXTRA_PREVIOUS_STATE}- The previous state of the profile.
    • + *
    • {@link BluetoothDevice#EXTRA_DEVICE} - The remote device.
    • + *
    + * + *

    {@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} can be any of + * {@link #STATE_DISCONNECTED}, {@link #STATE_CONNECTED}. + * + *

    Requires {@link android.Manifest.permission#BLUETOOTH} permission to + * receive. + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_CONNECTION_STATE_CHANGED = + "codeaurora.bluetooth.sap.profile.action.CONNECTION_STATE_CHANGED"; + + private Context mContext; + private ServiceListener mServiceListener; + private BluetoothAdapter mAdapter; + private IBluetoothSap mSapService; + + /** + * Create a BluetoothSap proxy object for interacting with the local + * Bluetooth Service which handles the Sap profile + * + */ + /*package*/ BluetoothSap(Context context, ServiceListener l) { + mContext = context; + mServiceListener = l; + mAdapter = BluetoothAdapter.getDefaultAdapter(); + try { + mAdapter.getBluetoothManager().registerStateChangeCallback(mStateChangeCallback); + } catch (RemoteException re) { + Log.w(TAG,"Unable to register BluetoothStateChangeCallback",re); + } + Log.d(TAG, "BluetoothSap() call bindService"); + if (!context.bindService(new Intent(IBluetoothSap.class.getName()), + mConnection, 0)) { + Log.e(TAG, "Could not bind to Bluetooth SAP Service"); + } + Log.d(TAG, "BluetoothSap(), bindService called"); + } + + /*package*/ void close() { + if (VDBG) log("close()"); + mServiceListener = null; + IBluetoothManager mgr = mAdapter.getBluetoothManager(); + if (mgr != null) { + try { + mgr.unregisterStateChangeCallback(mStateChangeCallback); + } catch (RemoteException re) { + Log.w(TAG,"Unable to unregister BluetoothStateChangeCallback",re); + } + } + + synchronized (mConnection) { + if ( mSapService != null) { + try { + mSapService = null; + mContext.unbindService(mConnection); + } catch (Exception re) { + Log.e(TAG,"",re); + } + } + } + } + + protected void finalize() { + close(); + } + + private IBluetoothStateChangeCallback mStateChangeCallback = new IBluetoothStateChangeCallback.Stub() { + + @Override + public void onBluetoothStateChange(boolean on) throws RemoteException { + //Handle enable request to bind again. + if (on) { + Log.d(TAG, "onBluetoothStateChange(on) call bindService"); + if (!mContext.bindService(new Intent(IBluetoothSap.class.getName()), + mConnection, 0)) { + Log.e(TAG, "Could not bind to Bluetooth SAP Service"); + } + Log.d(TAG, "BluetoothSap(), bindService called"); + } else { + if (VDBG) Log.d(TAG,"Unbinding service..."); + synchronized (mConnection) { + if ( mSapService != null) { + try { + mSapService = null; + mContext.unbindService(mConnection); + } catch (Exception re) { + Log.e(TAG,"",re); + } + } + } + } + } + }; + + /** + * Initiate disconnection from SAP server. + * + *

    Once the disconnection is initiated by any device either local host + * or remote device, the state will transition from {@link #STATE_CONNECTED} + * to {@link #STATE_DISCONNECTED}. + * + *

    Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} + * permission. + * + * @param device Remote Bluetooth Device + * @return false on immediate error, + * true otherwise + * @hide + */ + public boolean disconnect(BluetoothDevice device) { + if (DBG) log("disconnect(" + device + ")"); + if (mSapService != null && isEnabled() && + isValidDevice(device)) { + try { + return mSapService.disconnect(device); + } catch (RemoteException e) { + Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); + return false; + } + } + if (mSapService == null) Log.w(TAG, "Proxy not attached to service"); + return false; + } + /** + * {@inheritDoc} + */ + public List getConnectedDevices() { + if (VDBG) log("getConnectedDevices()"); + if (mSapService != null && isEnabled()) { + try { + return mSapService.getConnectedDevices(); + } catch (RemoteException e) { + Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); + return new ArrayList(); + } + } + if (mSapService == null) Log.w(TAG, "Proxy not attached to service"); + return new ArrayList(); + } + + /** + * {@inheritDoc} + */ + public List getDevicesMatchingConnectionStates(int[] states) { + if (VDBG) log("getDevicesMatchingStates()"); + if (mSapService != null && isEnabled()) { + try { + return mSapService.getDevicesMatchingConnectionStates(states); + } catch (RemoteException e) { + Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); + return new ArrayList(); + } + } + if (mSapService == null) Log.w(TAG, "Proxy not attached to service"); + return new ArrayList(); + } + + /** + * {@inheritDoc} + */ + public int getConnectionState(BluetoothDevice device) { + if (VDBG) log("getState(" + device + ")"); + if (mSapService != null && isEnabled() + && isValidDevice(device)) { + try { + return mSapService.getConnectionState(device); + } catch (RemoteException e) { + Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); + return BluetoothProfile.STATE_DISCONNECTED; + } + } + if (mSapService == null) Log.w(TAG, "Proxy not attached to service"); + return BluetoothProfile.STATE_DISCONNECTED; + } + + private ServiceConnection mConnection = new ServiceConnection() { + public void onServiceConnected(ComponentName className, IBinder service) { + if (DBG) Log.d(TAG, "BluetoothSAP Proxy object connected"); + mSapService = IBluetoothSap.Stub.asInterface(service); + + if (mServiceListener != null) { + mServiceListener.onServiceConnected(BluetoothProfile.SAP, + BluetoothSap.this); + } + } + public void onServiceDisconnected(ComponentName className) { + if (DBG) Log.d(TAG, "BluetoothSAP Proxy object disconnected"); + mSapService = null; + if (mServiceListener != null) { + mServiceListener.onServiceDisconnected(BluetoothProfile.SAP); + } + } + }; + + private boolean isEnabled() { + if (mAdapter.getState() == BluetoothAdapter.STATE_ON) return true; + return false; + } + + private boolean isValidDevice(BluetoothDevice device) { + if (device == null) return false; + + if (BluetoothAdapter.checkBluetoothAddress(device.getAddress())) return true; + return false; + } + + private static void log(String msg) { + Log.d(TAG, msg); + } +} diff --git a/core/java/android/bluetooth/BluetoothServerSocket.java b/core/java/android/bluetooth/BluetoothServerSocket.java index 96be8a2fb67..49601c5b55b 100644 --- a/core/java/android/bluetooth/BluetoothServerSocket.java +++ b/core/java/android/bluetooth/BluetoothServerSocket.java @@ -92,13 +92,14 @@ public final class BluetoothServerSocket implements Closeable { * @param type type of socket * @param auth require the remote device to be authenticated * @param encrypt require the connection to be encrypted + * @param port remote port * @param uuid uuid * @throws IOException On error, for example Bluetooth not available, or * insufficient privileges */ - /*package*/ BluetoothServerSocket(int type, boolean auth, boolean encrypt, ParcelUuid uuid) + /*package*/ BluetoothServerSocket(int type, boolean auth, boolean encrypt, int port, ParcelUuid uuid) throws IOException { - mSocket = new BluetoothSocket(type, -1, auth, encrypt, null, -1, uuid); + mSocket = new BluetoothSocket(type, -1, auth, encrypt, null, port, uuid); mChannel = mSocket.getPort(); } diff --git a/core/java/android/bluetooth/BluetoothSocket.java b/core/java/android/bluetooth/BluetoothSocket.java index a19341c07da..375b24af751 100644 --- a/core/java/android/bluetooth/BluetoothSocket.java +++ b/core/java/android/bluetooth/BluetoothSocket.java @@ -200,6 +200,7 @@ private BluetoothSocket acceptSocket(String RemoteAddr) throws IOException { as.mSocketOS = as.mSocket.getOutputStream(); as.mAddress = RemoteAddr; as.mDevice = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(RemoteAddr); + as.mPort = mPort; return as; } /** @@ -404,6 +405,61 @@ public void connect() throws IOException { return acceptedSocket; } + /** + * setSocketOpt for the Buetooth Socket. + * + * @param optionName socket option name + * @param optionVal socket option value + * @param optionLen socket option length + * @return -1 on immediate error, + * 0 otherwise + * @hide + */ + public int setSocketOpt(int optionName, byte [] optionVal, int optionLen) throws IOException { + int ret = 0; + if (mSocketState == SocketState.CLOSED) throw new IOException("socket closed"); + IBluetooth bluetoothProxy = BluetoothAdapter.getDefaultAdapter().getBluetoothService(null); + if (bluetoothProxy == null) { + Log.e(TAG, "setSocketOpt fail, reason: bluetooth is off"); + return -1; + } + try { + if(VDBG) Log.d(TAG, "setSocketOpt(), mType: " + mType + " mPort: " + mPort); + ret = bluetoothProxy.setSocketOpt(mType, mPort, optionName, optionVal, optionLen); + } catch (RemoteException e) { + Log.e(TAG, Log.getStackTraceString(new Throwable())); + return -1; + } + return ret; + } + + /** + * getSocketOpt for the Buetooth Socket. + * + * @param optionName socket option name + * @param optionVal socket option value + * @return -1 on immediate error, + * length of returned socket option otherwise + * @hide + */ + public int getSocketOpt(int optionName, byte [] optionVal) throws IOException { + int ret = 0; + if (mSocketState == SocketState.CLOSED) throw new IOException("socket closed"); + IBluetooth bluetoothProxy = BluetoothAdapter.getDefaultAdapter().getBluetoothService(null); + if (bluetoothProxy == null) { + Log.e(TAG, "getSocketOpt fail, reason: bluetooth is off"); + return -1; + } + try { + if(VDBG) Log.d(TAG, "getSocketOpt(), mType: " + mType + " mPort: " + mPort); + ret = bluetoothProxy.getSocketOpt(mType, mPort, optionName, optionVal); + } catch (RemoteException e) { + Log.e(TAG, Log.getStackTraceString(new Throwable())); + return -1; + } + return ret; + } + /*package*/ int available() throws IOException { if (VDBG) Log.d(TAG, "available: " + mSocketIS); return mSocketIS.available(); diff --git a/core/java/android/bluetooth/IBluetooth.aidl b/core/java/android/bluetooth/IBluetooth.aidl old mode 100644 new mode 100755 index 80806f97d48..043387feb99 --- a/core/java/android/bluetooth/IBluetooth.aidl +++ b/core/java/android/bluetooth/IBluetooth.aidl @@ -1,4 +1,6 @@ /* + * Copyright (C) 2013 The Linux Foundation. All rights reserved + * Not a Contribution. * Copyright (C) 2008, The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -63,9 +65,12 @@ interface IBluetooth int getRemoteType(in BluetoothDevice device); String getRemoteAlias(in BluetoothDevice device); boolean setRemoteAlias(in BluetoothDevice device, in String name); + boolean setRemoteTrust(in BluetoothDevice device, in boolean value); + boolean getRemoteTrust(in BluetoothDevice device); int getRemoteClass(in BluetoothDevice device); ParcelUuid[] getRemoteUuids(in BluetoothDevice device); boolean fetchRemoteUuids(in BluetoothDevice device); + boolean fetchRemoteMasInstances(in BluetoothDevice device); boolean setPin(in BluetoothDevice device, boolean accept, int len, in byte[] pinCode); boolean setPasskey(in BluetoothDevice device, boolean accept, int len, in byte[] @@ -80,4 +85,6 @@ interface IBluetooth // For Socket ParcelFileDescriptor connectSocket(in BluetoothDevice device, int type, in ParcelUuid uuid, int port, int flag); ParcelFileDescriptor createSocketChannel(int type, in String serviceName, in ParcelUuid uuid, int port, int flag); + int setSocketOpt(int type, int port, int optionName, in byte [] optionVal, int optionLen); + int getSocketOpt(int type, int port, int optionName, out byte [] optionVal); } diff --git a/core/java/android/bluetooth/IBluetoothDun.aidl b/core/java/android/bluetooth/IBluetoothDun.aidl new file mode 100755 index 00000000000..23fc90d026d --- /dev/null +++ b/core/java/android/bluetooth/IBluetoothDun.aidl @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2013, The Linux Foundation. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of The Linux Foundation nor + * the names of its contributors may be used to endorse or promote + * products derived from this software without specific prior written + * permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NON-INFRINGEMENT ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package android.bluetooth; + +import android.bluetooth.BluetoothDevice; + +/** + * API for Bluetooth Dun service + * + * {@hide} + */ +interface IBluetoothDun { + // Public API + boolean disconnect(in BluetoothDevice device); + int getConnectionState(in BluetoothDevice device); + List getConnectedDevices(); + List getDevicesMatchingConnectionStates(in int[] states); +} diff --git a/core/java/android/bluetooth/IBluetoothHandsfreeClient.aidl b/core/java/android/bluetooth/IBluetoothHandsfreeClient.aidl new file mode 100644 index 00000000000..1e660ae1b18 --- /dev/null +++ b/core/java/android/bluetooth/IBluetoothHandsfreeClient.aidl @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2013 The Linux Foundation. All rights reserved + * Not a Contribution. + * Copyright (C) 2008 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.bluetooth; + +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothHandsfreeClientCall; +import android.os.Bundle; + +/** + * API for Bluetooth Handsfree Client service (HFP HF Role) + * + * {@hide} + */ +interface IBluetoothHandsfreeClient { + boolean connect(in BluetoothDevice device); + boolean disconnect(in BluetoothDevice device); + + boolean acceptIncomingConnect(in BluetoothDevice device); + boolean rejectIncomingConnect(in BluetoothDevice device); + + List getConnectedDevices(); + List getDevicesMatchingConnectionStates(in int[] states); + int getConnectionState(in BluetoothDevice device); + boolean setPriority(in BluetoothDevice device, int priority); + int getPriority(in BluetoothDevice device); + + boolean startVoiceRecognition(in BluetoothDevice device); + boolean stopVoiceRecognition(in BluetoothDevice device); + + List getCurrentCalls(in BluetoothDevice device); + Bundle getCurrentAgEvents(in BluetoothDevice device); + + boolean acceptCall(in BluetoothDevice device, int flag); + boolean holdCall(in BluetoothDevice device); + boolean rejectCall(in BluetoothDevice device); + boolean terminateCall(in BluetoothDevice device, int index); + + boolean enterPrivateMode(in BluetoothDevice device, int index); + boolean explicitCallTransfer(in BluetoothDevice device); + + boolean redial(in BluetoothDevice device); + boolean dial(in BluetoothDevice device, String number); + boolean dialMemory(in BluetoothDevice device, int location); + + boolean sendDTMF(in BluetoothDevice device, byte code); + boolean getLastVoiceTagNumber(in BluetoothDevice device); + + int getAudioState(in BluetoothDevice device); + boolean connectAudio(); + boolean disconnectAudio(); + + Bundle getCurrentAgFeatures(in BluetoothDevice device); +} diff --git a/core/java/android/bluetooth/IBluetoothInputDevice.aidl b/core/java/android/bluetooth/IBluetoothInputDevice.aidl index 1ebb9ca6eb1..5bd3f781932 100644 --- a/core/java/android/bluetooth/IBluetoothInputDevice.aidl +++ b/core/java/android/bluetooth/IBluetoothInputDevice.aidl @@ -56,4 +56,12 @@ interface IBluetoothInputDevice { * @hide */ boolean sendData(in BluetoothDevice device, String report); + /** + * @hide + */ + boolean getIdleTime(in BluetoothDevice device); + /** + * @hide + */ + boolean setIdleTime(in BluetoothDevice device, byte idleTime); } diff --git a/core/java/android/bluetooth/IBluetoothSap.aidl b/core/java/android/bluetooth/IBluetoothSap.aidl new file mode 100755 index 00000000000..9299a27ab31 --- /dev/null +++ b/core/java/android/bluetooth/IBluetoothSap.aidl @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2013, The Linux Foundation. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of The Linux Foundation nor + * the names of its contributors may be used to endorse or promote + * products derived from this software without specific prior written + * permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NON-INFRINGEMENT ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package android.bluetooth; + +import android.bluetooth.BluetoothDevice; + +/** + * API for Bluetooth Sap service + * + * {@hide} + */ +interface IBluetoothSap { + // Public API + boolean disconnect(in BluetoothDevice device); + int getConnectionState(in BluetoothDevice device); + List getConnectedDevices(); + List getDevicesMatchingConnectionStates(in int[] states); +} diff --git a/core/java/android/content/AbstractThreadedSyncAdapter.java b/core/java/android/content/AbstractThreadedSyncAdapter.java index bafe67d619f..898cc4e7a1e 100644 --- a/core/java/android/content/AbstractThreadedSyncAdapter.java +++ b/core/java/android/content/AbstractThreadedSyncAdapter.java @@ -160,10 +160,13 @@ public void startSync(ISyncContext syncContext, String authority, Account accoun if (mAutoInitialize && extras != null && extras.getBoolean(ContentResolver.SYNC_EXTRAS_INITIALIZE, false)) { - if (ContentResolver.getIsSyncable(account, authority) < 0) { - ContentResolver.setIsSyncable(account, authority, 1); + try { + if (ContentResolver.getIsSyncable(account, authority) < 0) { + ContentResolver.setIsSyncable(account, authority, 1); + } + } finally { + syncContextClient.onFinished(new SyncResult()); } - syncContextClient.onFinished(new SyncResult()); return; } SyncThread syncThread = new SyncThread( diff --git a/core/java/android/content/BroadcastReceiver.java b/core/java/android/content/BroadcastReceiver.java index 9a32fdffbb9..d95843338b7 100644 --- a/core/java/android/content/BroadcastReceiver.java +++ b/core/java/android/content/BroadcastReceiver.java @@ -16,6 +16,7 @@ package android.content; +import android.app.ActivityManager; import android.app.ActivityManagerNative; import android.app.ActivityThread; import android.app.IActivityManager; @@ -745,6 +746,17 @@ public int getSendingUserId() { return mPendingResult.mSendingUser; } + /** @hide */ + public String getSendingPackage(Intent intent) { + final IActivityManager mgr = ActivityManagerNative.getDefault(); + try { + boolean fg = (intent.getFlags() & Intent.FLAG_RECEIVER_FOREGROUND) != 0; + return mgr.getCallingPackageForBroadcast(fg); + } catch (RemoteException ex) { + return null; + } + } + /** * Control inclusion of debugging help for mismatched * calls to {@link Context#registerReceiver(BroadcastReceiver, IntentFilter) diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index 5bd28b983ab..0cd4c75e90b 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -1,5 +1,8 @@ /* * Copyright (C) 2006 The Android Open Source Project + * Copyright (c) 2012-2013 The Linux Foundation. All rights reserved. + * + * Not a Contribution. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -1650,7 +1653,7 @@ public abstract Intent registerReceiverAsUser(BroadcastReceiver receiver, * @hide like {@link #stopService(Intent)} but for a specific user. */ public abstract boolean stopServiceAsUser(Intent service, UserHandle user); - + /** * Connect to an application service, creating it if needed. This defines * a dependency between your application and the service. The given @@ -1769,6 +1772,9 @@ public abstract boolean startInstrumentation(ComponentName className, *

    {@link #ALARM_SERVICE} ("alarm") *
    A {@link android.app.AlarmManager} for receiving intents at the * time of your choosing. + *
    {@link #IRDA_SERVICE} ("irda") + *
    A {@link android.hardware.IrdaManager} for sending IR codes + * with the IR emitter *
    {@link #NOTIFICATION_SERVICE} ("notification") *
    A {@link android.app.NotificationManager} for informing the user * of background events. @@ -1816,6 +1822,8 @@ public abstract boolean startInstrumentation(ComponentName className, * @see android.os.PowerManager * @see #ALARM_SERVICE * @see android.app.AlarmManager + * @see #IRDA_SERVICE + * @see android.hardware.IrdaManager * @see #NOTIFICATION_SERVICE * @see android.app.NotificationManager * @see #KEYGUARD_SERVICE @@ -1907,6 +1915,17 @@ public abstract boolean startInstrumentation(ComponentName className, */ public static final String ALARM_SERVICE = "alarm"; + /** + * Use with {@link #getSystemService} to retrieve a + * {@link android.hardware.IrdaManager} for sending IR codes + * with the IR emitter + * + * @see #getSystemService + * @see android.hardware.IrdaManager + * @hide + */ + public static final String IRDA_SERVICE = "irda"; + /** * Use with {@link #getSystemService} to retrieve a * {@link android.app.NotificationManager} for informing the user of @@ -1917,6 +1936,18 @@ public abstract boolean startInstrumentation(ComponentName className, */ public static final String NOTIFICATION_SERVICE = "notification"; + /** + * Use with {@link #getSystemService} to retrieve a + * {@link android.app.ProfileManager} for setting + * notification profiles. + * + * @see #getSystemService + * @see android.app.ProfileManager + * + * @hide + */ + public static final String PROFILE_SERVICE = "profile"; + /** * Use with {@link #getSystemService} to retrieve a * {@link android.view.accessibility.AccessibilityManager} for giving the user @@ -2107,6 +2138,17 @@ public abstract boolean startInstrumentation(ComponentName className, */ public static final String TELEPHONY_SERVICE = "phone"; + /** + * Use with {@link #getSystemService} to retrieve a + * {android.telephony.MSimTelephonyManager} for handling the management + * of the telephony features of the multi sim device. + * + * @see #getSystemService + * @see android.telephony.MSimTelephonyManager + * @hide + */ + public static final String MSIM_TELEPHONY_SERVICE = "phone_msim"; + /** * Use with {@link #getSystemService} to retrieve a * {@link android.text.ClipboardManager} for accessing and modifying diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java index fc9572824f4..6dd1b3ac041 100644 --- a/core/java/android/content/Intent.java +++ b/core/java/android/content/Intent.java @@ -1,5 +1,6 @@ /* * Copyright (C) 2006 The Android Open Source Project + * This code has been modified. Portions copyright (C) 2010, T-Mobile USA, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -684,6 +685,38 @@ public class Intent implements Parcelable, Cloneable { @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) public static final String ACTION_CREATE_SHORTCUT = "android.intent.action.CREATE_SHORTCUT"; + /** + * Global Action: Shows power menu dialog + *

    Input: nothing + *

    Output: nothing + * @hide + */ + public static final String ACTION_POWERMENU = "android.intent.action.POWERMENU"; + + /** + * Global Action: Shows power menu reboot dialog + *

    Input: nothing + *

    Output: nothing + * @hide + */ + public static final String ACTION_POWERMENU_REBOOT = "android.intent.action.POWERMENU_REBOOT"; + + /** + * Global Action: Shows power menu profile toggle dialog + *

    Input: nothing + *

    Output: nothing + * @hide + */ + public static final String ACTION_POWERMENU_PROFILE = "android.intent.action.POWERMENU_PROFILE"; + + /** + * PhoneWindowManager: Take Screenshot via takeScreenshot() + *

    Input: nothing + *

    Output: nothing + * @hide + */ + public static final String ACTION_SCREENSHOT = "android.intent.action.SCREENSHOT"; + /** * The name of the extra used to define the Intent of a shortcut. * @@ -2175,6 +2208,31 @@ public static Intent createChooser(Intent target, CharSequence title) { public static final String ACTION_HEADSET_PLUG = "android.intent.action.HEADSET_PLUG"; + /** + * Broadcast Action: WiFi Display audio is enabled or disabled + * + *

    The intent will have the following extra values: + *

      + *
    • state - 0 for disabled, 1 for enabled.
    • + *
    + * @hide + */ + public static final String ACTION_WIFI_DISPLAY_AUDIO = + "qualcomm.intent.action.WIFI_DISPLAY_AUDIO"; + + /** + * Broadcast Action: WiFi Display video is enabled or disabled + * + *

    The intent will have the following extra values: + *

      + *
    • state - 0 for disabled, 1 for enabled.
    • + *
    + * @hide + */ + + public static final String ACTION_WIFI_DISPLAY_VIDEO = + "qualcomm.intent.action.WIFI_DISPLAY_VIDEO"; + /** * Broadcast Action: An analog audio speaker/headset plugged in or unplugged. * @@ -2309,6 +2367,25 @@ public static Intent createChooser(Intent target, CharSequence title) { public static final String ACTION_NEW_OUTGOING_CALL = "android.intent.action.NEW_OUTGOING_CALL"; + /** + * Broadcast Action: An outgoing sms is about to be sent. + * + * The Intent will have the following extras: + * destAddr - the phone number originally intended to be dialled + * scAddr - the service center address + * multipart - indicate whether this is a multipart or single message + * parts - ArrayList of text parts (one item if multipart=false) + * sentIntents - ArrayList to send on send + * deliveryIntents - ArrayList to send on delivery + * + * Once the broadcast is finished, resultData is used as the actual + * number to text. + * + * @hide + */ + public static final String ACTION_NEW_OUTGOING_SMS = + "android.intent.action.NEW_OUTGOING_SMS"; + /** * Broadcast Action: Have the device reboot. This is only for use by * system code. @@ -2599,6 +2676,19 @@ public static Intent createChooser(Intent target, CharSequence title) { */ public static final String ACTION_GLOBAL_BUTTON = "android.intent.action.GLOBAL_BUTTON"; + /** + * Broadcast Action: Indicate that unrecoverable error happened during app launch. + * Could indicate that curently applied theme is malicious. + * @hide + */ + public static final String ACTION_APP_LAUNCH_FAILURE = "com.tmobile.intent.action.APP_LAUNCH_FAILURE"; + + /** + * Broadcast Action: Request to reset the unrecoverable errors count to 0. + * @hide + */ + public static final String ACTION_APP_LAUNCH_FAILURE_RESET = "com.tmobile.intent.action.APP_LAUNCH_FAILURE_RESET"; + // --------------------------------------------------------------------- // --------------------------------------------------------------------- // Standard intent categories (see addCategory()). @@ -2732,6 +2822,7 @@ public static Intent createChooser(Intent target, CharSequence title) { */ public static final String CATEGORY_FRAMEWORK_INSTRUMENTATION_TEST = "android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"; + /** * An activity to run when device is inserted into a car dock. * Used with {@link #ACTION_MAIN} to launch an activity. For more @@ -2768,6 +2859,14 @@ public static Intent createChooser(Intent target, CharSequence title) { @SdkConstant(SdkConstantType.INTENT_CATEGORY) public static final String CATEGORY_CAR_MODE = "android.intent.category.CAR_MODE"; + /** + * Used to indicate that a theme package has been installed or un-installed. + * + * @hide + */ + public static final String CATEGORY_THEME_PACKAGE_INSTALLED_STATE_CHANGE = + "com.tmobile.intent.category.THEME_PACKAGE_INSTALL_STATE_CHANGE"; + // --------------------------------------------------------------------- // --------------------------------------------------------------------- // Application launch intent categories (see addCategory()). @@ -3465,6 +3564,11 @@ public static Intent createChooser(Intent target, CharSequence title) { * saw. This can only be used in conjunction with {@link #FLAG_ACTIVITY_NEW_TASK}. */ public static final int FLAG_ACTIVITY_TASK_ON_HOME = 0X00004000; + /** + * If set, this intent will always match start up as a floating window + * in mutil window scenarios. + */ + public static final int FLAG_FLOATING_WINDOW = 0x00002000; /** * If set, when sending a broadcast only registered receivers will be * called -- no BroadcastReceiver components will be launched. @@ -6979,7 +7083,7 @@ public static String normalizeMimeType(String type) { return null; } - type = type.trim().toLowerCase(Locale.US); + type = type.trim().toLowerCase(Locale.ROOT); final int semicolonIndex = type.indexOf(';'); if (semicolonIndex != -1) { diff --git a/core/java/android/content/IntentFilter.java b/core/java/android/content/IntentFilter.java index 5e65b59d01d..7a42440ffc5 100644 --- a/core/java/android/content/IntentFilter.java +++ b/core/java/android/content/IntentFilter.java @@ -1385,6 +1385,15 @@ public final void writeToParcel(Parcel dest, int flags) { dest.writeInt(mHasPartialTypes ? 1 : 0); } + /** + * {@hide} + * @param other + * @return + */ + public int onCompareTie(IntentFilter other) { + return 0; + } + /** * For debugging -- perform a check on the filter, return true if it passed * or false if it failed. diff --git a/core/java/android/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java index 8154bca2cb8..fa9ee716db5 100644 --- a/core/java/android/content/pm/ActivityInfo.java +++ b/core/java/android/content/pm/ActivityInfo.java @@ -1,5 +1,6 @@ /* * Copyright (C) 2007 The Android Open Source Project + * This code has been modified. Portions copyright (C) 2010, T-Mobile USA, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -374,6 +375,10 @@ public class ActivityInfo extends ComponentInfo * {@link android.R.attr#configChanges} attribute. */ public static final int CONFIG_ORIENTATION = 0x0080; + /** + * @hide + */ + public static final int CONFIG_THEME_RESOURCE = 0x008000; /** * Bit in {@link #configChanges} that indicates that the activity * can itself handle changes to the screen layout. Set from the @@ -386,6 +391,12 @@ public class ActivityInfo extends ComponentInfo * {@link android.R.attr#configChanges} attribute. */ public static final int CONFIG_UI_MODE = 0x0200; + /** + * Bit in {@link #configChanges} that indicates that the activity + * can itself handle the inverted ui mode. Set from the + * {@link android.R.attr#configChanges} attribute. + */ + public static final int CONFIG_UI_INVERTED_MODE = 0x0300; /** * Bit in {@link #configChanges} that indicates that the activity * can itself handle the screen size. Set from the @@ -446,6 +457,7 @@ public class ActivityInfo extends ComponentInfo 0x0040, // NAVIGATION 0x0080, // ORIENTATION 0x0800, // SCREEN LAYOUT + 0x8000, // UI INVERTED MODE 0x1000, // UI MODE 0x0200, // SCREEN SIZE 0x2000, // SMALLEST SCREEN SIZE diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java index 02401dc5416..ab32e48d22d 100644 --- a/core/java/android/content/pm/ApplicationInfo.java +++ b/core/java/android/content/pm/ApplicationInfo.java @@ -1,5 +1,6 @@ /* * Copyright (C) 2007 The Android Open Source Project + * This code has been modified. Portions copyright (C) 2010, T-Mobile USA, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -453,6 +454,30 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { * @hide */ public int enabledSetting = PackageManager.COMPONENT_ENABLED_STATE_DEFAULT; + /** + * Is given application theme agnostic, i.e. behaves properly when default theme is changed. + * {@hide} + */ + public boolean isThemeable = false; + + private static final String PLUTO_SCHEMA = "http://www.w3.org/2001/pluto.html"; + + /** + * @hide + */ + public static final String PLUTO_ISTHEMEABLE_ATTRIBUTE_NAME = "isThemeable"; + + /** + * @hide + */ + public static final String PLUTO_HANDLE_THEME_CONFIG_CHANGES_ATTRIBUTE_NAME = "handleThemeConfigChanges"; + + /** + * @hide + */ + public static boolean isPlutoNamespace(String namespace) { + return namespace != null && namespace.equalsIgnoreCase(PLUTO_SCHEMA); + } /** * For convenient access to package's install location. @@ -568,6 +593,7 @@ public ApplicationInfo(ApplicationInfo orig) { descriptionRes = orig.descriptionRes; uiOptions = orig.uiOptions; backupAgentName = orig.backupAgentName; + isThemeable = orig.isThemeable; } @@ -608,6 +634,7 @@ public void writeToParcel(Parcel dest, int parcelableFlags) { dest.writeString(backupAgentName); dest.writeInt(descriptionRes); dest.writeInt(uiOptions); + dest.writeInt(isThemeable? 1 : 0); } public static final Parcelable.Creator CREATOR @@ -647,6 +674,7 @@ private ApplicationInfo(Parcel source) { backupAgentName = source.readString(); descriptionRes = source.readInt(); uiOptions = source.readInt(); + isThemeable = source.readInt() != 0; } /** diff --git a/core/java/android/content/pm/BaseThemeInfo.java b/core/java/android/content/pm/BaseThemeInfo.java new file mode 100644 index 00000000000..0171137bac1 --- /dev/null +++ b/core/java/android/content/pm/BaseThemeInfo.java @@ -0,0 +1,244 @@ +/* + * Copyright (C) 2010, T-Mobile USA, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.content.pm; + +import android.os.Parcelable; +import android.os.Parcel; +import android.util.Log; +import android.util.AttributeSet; +import android.content.res.Resources; + +/** + * @hide + */ +public class BaseThemeInfo implements Parcelable { + + /** + * Wallpaper drawable. + * + * @see wallpaperImage attribute + */ + public int wallpaperResourceId; + + /** + * The resource id of theme thumbnail. + * Specifies a theme thumbnail image resource as @drawable/foo. + * + * @see thumbnail attribute + * + */ + public int thumbnailResourceId; + + /** + * The theme id, which does not change when the theme is modified. + * Specifies an Android UI Style using style name. + * + * @see themeId attribute + * + */ + public String themeId; + + /** + * The style resource id of Android UI Style, supplied by the resource commpiler. + * Specifies an Android UI Style id. + * + * @see styleId attribute + * + */ + public int styleResourceId = 0; + + /** + * The name of the theme (as displayed by UI). + * + * @see name attribute + * + */ + public String name; + + /** + * The name of the call ringtone audio file. + * Specifies a relative path in assets subfolder. + * If the parent's name is "locked" - DRM protected. + * + * @see ringtoneFileName attribute + * + */ + public String ringtoneFileName; + + /** + * The name of the call ringtone as shown to user. + * + * @see ringtoneName attribute + * + */ + public String ringtoneName; + + /** + * The name of the notification ringtone audio file. + * Specifies a relative path in assets subfolder. + * If the parent's name is "locked" - DRM protected. + * + * @see notificationRingtoneFileName attribute + * + */ + public String notificationRingtoneFileName; + + /** + * The name of the notification ringtone as shown to user. + * + * @see notificationRingtoneName attribute + * + */ + public String notificationRingtoneName; + + /** + * The author name of the theme package. + * + * @see author attribute + * + */ + public String author; + + /** + * The copyright text. + * + * @see copyright attribute + * + */ + public String copyright; + + /** + * {@hide} + */ + // There is no corresposponding flag in manifest file + // This flag is set to true iff any media resource is DRM protected + public boolean isDrmProtected = false; + + /** + * The name of the "main" theme style (as displayed by UI). + * + * @see themeStyleName attribute + * + */ + public String themeStyleName; + + /** + * Preview image drawable. + * + * @see preview attribute + */ + public int previewResourceId; + + /** + * The name of a sound pack. + * + * @see soundpack attribute + * + */ + public String soundPackName; + + + private static final String LOCKED_NAME = "locked/"; + + /* + * Describe the kinds of special objects contained in this Parcelable's + * marshalled representation. + * + * @return a bitmask indicating the set of special object types marshalled + * by the Parcelable. + * + * @see android.os.Parcelable#describeContents() + */ + public int describeContents() { + return 0; + } + + /* + * Flatten this object in to a Parcel. + * + * @param dest The Parcel in which the object should be written. + * @param flags Additional flags about how the object should be written. + * May be 0 or {@link #PARCELABLE_WRITE_RETURN_VALUE}. + * + * @see android.os.Parcelable#writeToParcel(android.os.Parcel, int) + */ + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(wallpaperResourceId); + dest.writeInt(thumbnailResourceId); + dest.writeString(themeId); + dest.writeInt(styleResourceId); + dest.writeString(name); + dest.writeString(ringtoneFileName); + dest.writeString(notificationRingtoneFileName); + dest.writeString(ringtoneName); + dest.writeString(notificationRingtoneName); + dest.writeString(author); + dest.writeString(copyright); + dest.writeInt(isDrmProtected? 1 : 0); + dest.writeString(soundPackName); + dest.writeString(themeStyleName); + dest.writeInt(previewResourceId); + } + + /** @hide */ + public static final Parcelable.Creator CREATOR + = new Parcelable.Creator() { + public BaseThemeInfo createFromParcel(Parcel source) { + return new BaseThemeInfo(source); + } + + public BaseThemeInfo[] newArray(int size) { + return new BaseThemeInfo[size]; + } + }; + + /** @hide */ + public final String getResolvedString(Resources res, AttributeSet attrs, int index) { + int resId = attrs.getAttributeResourceValue(index, 0); + if (resId !=0 ) { + return res.getString(resId); + } + return attrs.getAttributeValue(index); + } + + protected BaseThemeInfo() { + } + + protected BaseThemeInfo(Parcel source) { + wallpaperResourceId = source.readInt(); + thumbnailResourceId = source.readInt(); + themeId = source.readString(); + styleResourceId = source.readInt(); + name = source.readString(); + ringtoneFileName = source.readString(); + notificationRingtoneFileName = source.readString(); + ringtoneName = source.readString(); + notificationRingtoneName = source.readString(); + author = source.readString(); + copyright = source.readString(); + isDrmProtected = (source.readInt() != 0); + soundPackName = source.readString(); + themeStyleName = source.readString(); + previewResourceId = source.readInt(); + } + + protected void changeDrmFlagIfNeeded(String resourcePath) { + if (resourcePath != null && resourcePath.contains(LOCKED_NAME)) { + isDrmProtected = true; + } + } +} diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl index a0e15551a78..01bc76e2a1f 100644 --- a/core/java/android/content/pm/IPackageManager.aidl +++ b/core/java/android/content/pm/IPackageManager.aidl @@ -42,6 +42,7 @@ import android.content.pm.ServiceInfo; import android.content.pm.UserInfo; import android.content.pm.VerificationParams; import android.content.pm.VerifierDeviceIdentity; +import android.content.pm.ThemeInfo; import android.net.Uri; import android.os.ParcelFileDescriptor; import android.content.IntentSender; diff --git a/core/java/android/content/pm/PackageInfo.java b/core/java/android/content/pm/PackageInfo.java index af1a6d52fc9..b89ec115fb1 100644 --- a/core/java/android/content/pm/PackageInfo.java +++ b/core/java/android/content/pm/PackageInfo.java @@ -1,5 +1,6 @@ /* * Copyright (C) 2007 The Android Open Source Project + * This code has been modified. Portions copyright (C) 2010, T-Mobile USA, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -226,10 +227,70 @@ public class PackageInfo implements Parcelable { /** @hide */ public String requiredAccountType; + + // Is Theme Apk + /** + * {@hide} + */ + public boolean isThemeApk = false; + + // ThemeInfo + /** + * {@hide} + */ + public ThemeInfo [] themeInfos; public PackageInfo() { } + /* + * Is Theme Apk is DRM protected (contains DRM-protected resources) + * + */ + private boolean drmProtectedThemeApk = false; + + /** + * @hide + * + * @return Is Theme Apk is DRM protected (contains DRM-protected resources) + */ + public boolean isDrmProtectedThemeApk() { + return drmProtectedThemeApk; + } + + /** + * @hide + * + * @param value if Theme Apk is DRM protected (contains DRM-protected resources) + */ + public void setDrmProtectedThemeApk(boolean value) { + drmProtectedThemeApk = value; + } + + /* + * If isThemeApk and isDrmProtectedThemeApk are true - path to hidden locked zip file + * + */ + private String lockedZipFilePath; + + /** + * @hide + * + * @return path for hidden locked zip file + */ + public String getLockedZipFilePath() { + return lockedZipFilePath; + } + + /** + * @hide + * + * @param value path for hidden locked zip file + */ + public void setLockedZipFilePath(String value) { + lockedZipFilePath = value; + } + public String toString() { return "PackageInfo{" + Integer.toHexString(System.identityHashCode(this)) @@ -270,6 +331,12 @@ public void writeToParcel(Parcel dest, int parcelableFlags) { dest.writeInt(requiredForAllUsers ? 1 : 0); dest.writeString(restrictedAccountType); dest.writeString(requiredAccountType); + + /* Theme-specific. */ + dest.writeInt((isThemeApk)? 1 : 0); + dest.writeInt((drmProtectedThemeApk)? 1 : 0); + dest.writeTypedArray(themeInfos, parcelableFlags); + dest.writeString(lockedZipFilePath); } public static final Parcelable.Creator CREATOR @@ -311,5 +378,11 @@ private PackageInfo(Parcel source) { requiredForAllUsers = source.readInt() != 0; restrictedAccountType = source.readString(); requiredAccountType = source.readString(); + + /* Theme-specific. */ + isThemeApk = (source.readInt() != 0); + drmProtectedThemeApk = (source.readInt() != 0); + themeInfos = source.createTypedArray(ThemeInfo.CREATOR); + lockedZipFilePath = source.readString(); } } diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java index 4266d85f08e..869c041c420 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -1,5 +1,6 @@ /* * Copyright (C) 2006 The Android Open Source Project + * This code has been modified. Portions copyright (C) 2010, T-Mobile USA, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -674,6 +675,14 @@ public NameNotFoundException(String name) { */ public static final int INSTALL_FAILED_USER_RESTRICTED = -111; + /** + * Installation failed return code: this is passed to the {@link IPackageInstallObserver} by + * {@link #installPackage(android.net.Uri, IPackageInstallObserver, int)} + * if the system failed to install the package because of a policy denial. + * @hide + */ + public static final int INSTALL_FAILED_POLICY_REJECTED_PERMISSION = -112; + /** * Flag parameter for {@link #deletePackage} to indicate that you don't want to delete the * package's data directory. @@ -1685,6 +1694,17 @@ public abstract List getPackagesHoldingPermissions( */ public abstract List getInstalledPackages(int flags, int userId); + /** + * Return a List of all theme packages that are installed + * on the device. + * + * @return A List of PackageInfo objects, one for each theme package + * that is installed on the device. + * + * @hide + */ + public abstract List getInstalledThemePackages(); + /** * Check whether a particular package has been granted a particular * permission. diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java index 34e0c124bb9..da3242b8580 100644 --- a/core/java/android/content/pm/PackageParser.java +++ b/core/java/android/content/pm/PackageParser.java @@ -1,5 +1,6 @@ /* * Copyright (C) 2007 The Android Open Source Project + * This code has been modified. Portions copyright (C) 2010, T-Mobile USA, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -254,6 +255,17 @@ public static PackageInfo generatePackageInfo(PackageParser.Package p, } */ + public static String getLockedZipFilePath(String path) { + if (path == null) { + return null; + } + if (isPackageFilename(path)) { + return path.substring(0, path.length() - 4) + ".locked.zip"; + } else { + return path + ".locked.zip"; + } + } + /** * Generate and return the {@link PackageInfo} for a parsed package. * @@ -285,6 +297,21 @@ public static PackageInfo generatePackageInfo(PackageParser.Package p, pi.versionName = p.mVersionName; pi.sharedUserId = p.mSharedUserId; pi.sharedUserLabel = p.mSharedUserLabel; + pi.isThemeApk = p.mIsThemeApk; + pi.setDrmProtectedThemeApk(false); + if (pi.isThemeApk) { + int N = p.mThemeInfos.size(); + if (N > 0) { + pi.themeInfos = new ThemeInfo[N]; + for (int i = 0; i < N; i++) { + pi.themeInfos[i] = p.mThemeInfos.get(i); + pi.setDrmProtectedThemeApk(pi.isDrmProtectedThemeApk() || pi.themeInfos[i].isDrmProtected); + } + if (pi.isDrmProtectedThemeApk()) { + pi.setLockedZipFilePath(PackageParser.getLockedZipFilePath(p.mPath)); + } + } + } pi.applicationInfo = generateApplicationInfo(p, flags, state, userId); pi.installLocation = p.installLocation; if ((pi.applicationInfo.flags&ApplicationInfo.FLAG_SYSTEM) != 0 @@ -508,7 +535,7 @@ public Package parsePackage(File sourceFile, String destCodePath, int cookie = assmgr.addAssetPath(mArchiveSourcePath); if (cookie != 0) { res = new Resources(assmgr, metrics, null); - assmgr.setConfiguration(0, 0, null, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + assmgr.setConfiguration(0, 0, null, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, Build.VERSION.RESOURCES_SDK_INT); parser = assmgr.openXmlResourceParser(cookie, ANDROID_MANIFEST_FILENAME); assetError = false; @@ -744,7 +771,7 @@ public static PackageLite parsePackageLite(String packageFilePath, int flags) { final Resources res; try { assmgr = new AssetManager(); - assmgr.setConfiguration(0, 0, null, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + assmgr.setConfiguration(0, 0, null, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, Build.VERSION.RESOURCES_SDK_INT); int cookie = assmgr.addAssetPath(packageFilePath); @@ -1295,7 +1322,10 @@ private Package parsePackage( // Just skip this tag XmlUtils.skipCurrentTag(parser); continue; - + } else if (tagName.equals("theme")) { + // this is a theme apk. + pkg.mIsThemeApk = true; + pkg.mThemeInfos.add(new ThemeInfo(parser, res, attrs)); } else if (RIGID_PARSER) { outError[0] = "Bad element under : " + parser.getName(); @@ -1386,6 +1416,9 @@ private Package parsePackage( >= android.os.Build.VERSION_CODES.DONUT)) { pkg.applicationInfo.flags |= ApplicationInfo.FLAG_SUPPORTS_SCREEN_DENSITIES; } + if (pkg.mIsThemeApk) { + pkg.applicationInfo.isThemeable = false; + } /* * b/8528162: Ignore the attribute if @@ -1738,12 +1771,43 @@ private Instrumentation parseInstrumentation(Package owner, Resources res, return a; } + private void parseApplicationThemeAttributes(XmlPullParser parser, AttributeSet attrs, + ApplicationInfo appInfo) { + for (int i = 0; i < attrs.getAttributeCount(); i++) { + if (!ApplicationInfo.isPlutoNamespace(parser.getAttributeNamespace(i))) { + continue; + } + String attrName = attrs.getAttributeName(i); + if (attrName.equalsIgnoreCase(ApplicationInfo.PLUTO_ISTHEMEABLE_ATTRIBUTE_NAME)) { + appInfo.isThemeable = attrs.getAttributeBooleanValue(i, false); + return; + } + } + } + + private void parseActivityThemeAttributes(XmlPullParser parser, AttributeSet attrs, + ActivityInfo ai) { + for (int i = 0; i < attrs.getAttributeCount(); i++) { + if (!ApplicationInfo.isPlutoNamespace(parser.getAttributeNamespace(i))) { + continue; + } + String attrName = attrs.getAttributeName(i); + if (attrName.equalsIgnoreCase(ApplicationInfo.PLUTO_HANDLE_THEME_CONFIG_CHANGES_ATTRIBUTE_NAME)) { + ai.configChanges |= ActivityInfo.CONFIG_THEME_RESOURCE; + } + } + } + private boolean parseApplication(Package owner, Resources res, XmlPullParser parser, AttributeSet attrs, int flags, String[] outError) throws XmlPullParserException, IOException { final ApplicationInfo ai = owner.applicationInfo; final String pkgName = owner.applicationInfo.packageName; + // assume that this package is themeable unless explicitly set to false. + ai.isThemeable = true; + parseApplicationThemeAttributes(parser, attrs, ai); + TypedArray sa = res.obtainAttributes(attrs, com.android.internal.R.styleable.AndroidManifestApplication); @@ -2324,6 +2388,8 @@ private Activity parseActivity(Package owner, Resources res, return null; } + parseActivityThemeAttributes(parser, attrs, a.info); + int outerDepth = parser.getDepth(); int type; while ((type=parser.next()) != XmlPullParser.END_DOCUMENT @@ -3319,6 +3385,12 @@ public final static class Package { // For use by package manager to keep track of where it has done dexopt. public boolean mDidDexOpt; + + // Is Theme Apk + public boolean mIsThemeApk = false; + + // Theme info + public final ArrayList mThemeInfos = new ArrayList(0); // // User set enabled state. // public int mSetEnabled = PackageManager.COMPONENT_ENABLED_STATE_DEFAULT; diff --git a/core/java/android/content/pm/PackageUserState.java b/core/java/android/content/pm/PackageUserState.java index dcd54fcb585..a885a956322 100644 --- a/core/java/android/content/pm/PackageUserState.java +++ b/core/java/android/content/pm/PackageUserState.java @@ -51,4 +51,4 @@ public PackageUserState(PackageUserState o) { enabledComponents = o.enabledComponents != null ? new HashSet(o.enabledComponents) : null; } -} \ No newline at end of file +} diff --git a/core/java/android/content/pm/ThemeInfo.aidl b/core/java/android/content/pm/ThemeInfo.aidl new file mode 100644 index 00000000000..acbc85e9c8b --- /dev/null +++ b/core/java/android/content/pm/ThemeInfo.aidl @@ -0,0 +1,3 @@ +package android.content.pm; + +parcelable ThemeInfo; diff --git a/core/java/android/content/pm/ThemeInfo.java b/core/java/android/content/pm/ThemeInfo.java new file mode 100644 index 00000000000..e51dbb6ae8c --- /dev/null +++ b/core/java/android/content/pm/ThemeInfo.java @@ -0,0 +1,205 @@ +/* + * Copyright (C) 2010, T-Mobile USA, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.content.pm; + +import java.util.HashMap; +import java.util.Map; + +import org.xmlpull.v1.XmlPullParserException; +import org.xmlpull.v1.XmlPullParser; + +import android.os.Parcel; +import android.os.Parcelable; +import android.util.AttributeSet; +import android.content.res.Resources; + +/** + * Overall information about "theme" package. This corresponds + * to the information collected from AndroidManifest.xml (theme tag). + * + * Below is an example of theme tag + * + * + * @hide + */ +public final class ThemeInfo extends BaseThemeInfo { + private enum AttributeIndex { + THEME_PACKAGE_INDEX, + PREVIEW_INDEX, + AUTHOR_INDEX, + THEME_INDEX, + THEME_STYLE_NAME_INDEX, + THUMBNAIL_INDEX, + RINGTONE_FILE_NAME_INDEX, + NOTIFICATION_RINGTONE_FILE_NAME_INDEX, + WALLPAPER_IMAGE_INDEX, + COPYRIGHT_INDEX, + RINGTONE_NAME_INDEX, + NOTIFICATION_RINGTONE_NAME_INDEX, + STYLE_INDEX; + + public static AttributeIndex get(int ordinal) { + return values()[ordinal]; + } + }; + + private static final String [] compulsoryAttributes = new String [] { + "name", + "preview", + "author", + "themeId", + "styleName", + }; + + private static final String [] optionalAttributes = new String [] { + "thumbnail", + "ringtoneFileName", + "notificationRingtoneFileName", + "wallpaperImage", + "copyright", + "ringtoneName", + "notificationRingtoneName", + "styleId", + }; + + private static final Map sAttributesLookupTable; + + static { + sAttributesLookupTable = new HashMap(); + for (int i = 0; i < compulsoryAttributes.length; i++) { + sAttributesLookupTable.put(compulsoryAttributes[i], AttributeIndex.get(i)); + } + + for (int i = 0; i < optionalAttributes.length; i++) { + sAttributesLookupTable.put(optionalAttributes[i], + AttributeIndex.get(compulsoryAttributes.length + i)); + } + } + + public ThemeInfo(XmlPullParser parser, Resources res, AttributeSet attrs) throws XmlPullParserException { + super(); + + Map tempMap = + new HashMap(sAttributesLookupTable); + int numberOfCompulsoryAttributes = 0; + for (int i = 0; i < attrs.getAttributeCount(); i++) { + if (!ApplicationInfo.isPlutoNamespace(parser.getAttributeNamespace(i))) { + continue; + } + String key = attrs.getAttributeName(i); + if (tempMap.containsKey(key)) { + AttributeIndex index = tempMap.get(key); + tempMap.remove(key); + + if (index.ordinal() < compulsoryAttributes.length) { + numberOfCompulsoryAttributes++; + } + switch (index) { + case THEME_PACKAGE_INDEX: + // theme name + name = getResolvedString(res, attrs, i); + break; + + case THUMBNAIL_INDEX: + // theme thumbprint + thumbnailResourceId = attrs.getAttributeResourceValue(i, 0); + break; + + case AUTHOR_INDEX: + // theme author + author = getResolvedString(res, attrs, i); + break; + + case THEME_INDEX: + // androidUiStyle attribute + themeId = attrs.getAttributeValue(i); + break; + + case THEME_STYLE_NAME_INDEX: + themeStyleName = getResolvedString(res, attrs, i); + break; + + case RINGTONE_FILE_NAME_INDEX: + // ringtone + ringtoneFileName = attrs.getAttributeValue(i); + changeDrmFlagIfNeeded(ringtoneFileName); + break; + + case NOTIFICATION_RINGTONE_FILE_NAME_INDEX: + // notification ringtone + notificationRingtoneFileName = attrs.getAttributeValue(i); + changeDrmFlagIfNeeded(notificationRingtoneFileName); + break; + + case WALLPAPER_IMAGE_INDEX: + // wallpaperImage attribute + wallpaperResourceId = attrs.getAttributeResourceValue(i, 0); + break; + + case COPYRIGHT_INDEX: + // themeCopyright attribute + copyright = getResolvedString(res, attrs, i); + break; + + case RINGTONE_NAME_INDEX: + // ringtone UI name + ringtoneName = attrs.getAttributeValue(i); + break; + + case NOTIFICATION_RINGTONE_NAME_INDEX: + // notification ringtone UI name + notificationRingtoneName = attrs.getAttributeValue(i); + break; + + case STYLE_INDEX: + styleResourceId = attrs.getAttributeResourceValue(i, 0); + break; + + case PREVIEW_INDEX: + // theme thumbprint + previewResourceId = attrs.getAttributeResourceValue(i, 0); + break; + } + } + } + if (numberOfCompulsoryAttributes < compulsoryAttributes.length) { + throw new XmlPullParserException("Not all compulsory attributes are specified in "); + } + } + + public static final Parcelable.Creator CREATOR + = new Parcelable.Creator() { + public ThemeInfo createFromParcel(Parcel source) { + return new ThemeInfo(source); + } + + public ThemeInfo[] newArray(int size) { + return new ThemeInfo[size]; + } + }; + + private ThemeInfo(Parcel source) { + super(source); + } +} diff --git a/core/java/android/content/res/AssetManager.java b/core/java/android/content/res/AssetManager.java index fc9e4866181..d1b4f6157f6 100644 --- a/core/java/android/content/res/AssetManager.java +++ b/core/java/android/content/res/AssetManager.java @@ -1,5 +1,6 @@ /* * Copyright (C) 2006 The Android Open Source Project + * This code has been modified. Portions copyright (C) 2010, T-Mobile USA, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,6 +20,7 @@ import android.os.ParcelFileDescriptor; import android.os.Trace; import android.util.Log; +import android.util.SparseArray; import android.util.TypedValue; import java.io.FileNotFoundException; @@ -78,6 +80,20 @@ public final class AssetManager { private boolean mOpen = true; private HashMap mRefStacks; + private String mAssetDir; + private String mAppName; + + private boolean mThemeSupport; + private String mThemePackageName; + private int mThemeCookie; + + /** + * Organize all added redirection maps using Java strong references to keep + * the native layer cleanup simple (that is, finalize() in Java will be + * responsible for delete in C++). + */ + private SparseArray mRedirections; + /** * Create a new AssetManager containing only the basic system assets. * Applications will not generally use this method, instead retrieving the @@ -253,6 +269,12 @@ public void close() { } } + /*package*/ final void recreateStringBlocks() { + synchronized (this) { + makeStringBlocks(true); + } + } + /*package*/ final void makeStringBlocks(boolean copyFromSystem) { final int sysNum = copyFromSystem ? sSystem.mStringBlocks.length : 0; final int num = getStringBlockCount(); @@ -459,6 +481,18 @@ public final XmlResourceParser openXmlResourceParser(int cookie, return rp; } + /** + * {@hide} + * Split a theme package with DRM-protected resources into two files. + * + * @param packageFileName Original theme package file name. + * @param lockedFileName Name of the new "locked" file with DRM resources. + * @param drmProtectedresources Array of names of DRM-protected assets. + */ + public final int splitDrmProtectedThemePackage(String packageFileName, String lockedFileName, String [] drmProtectedresources) { + return splitThemePackage(packageFileName, lockedFileName, drmProtectedresources); + } + /** * {@hide} * Retrieve a non-asset as a compiled XML file. Not for use by @@ -630,6 +664,110 @@ public final int[] addAssetPaths(String[] paths) { return cookies; } + /** + * Delete a set of theme assets from the asset manager. Not for use by + * applications. Returns true if succeeded or false on failure. + * + * @hide + */ + public native final boolean detachThemePath(String packageName, int cookie); + + /** + * Attach a set of theme assets to the asset manager. If necessary, this + * method will forcefully update the internal ResTable data structure. + * + * @return Cookie of the added asset or 0 on failure. + * @hide + */ + public native final int attachThemePath(String path); + + /** + * Sets a flag indicating that this AssetManager should have themes + * attached, according to the initial request to create it by the + * ApplicationContext. + * + * {@hide} + */ + public final void setThemeSupport(boolean themeSupport) { + mThemeSupport = themeSupport; + } + + /** + * Should this AssetManager have themes attached, according to the initial + * request to create it by the ApplicationContext? + * + * {@hide} + */ + public final boolean hasThemeSupport() { + return mThemeSupport; + } + + /** + * Apply a heuristic to match-up all attributes from the source style with + * attributes in the destination style. For each match, an entry in the + * package redirection map will be inserted. + * + * {@hide} + */ + public native final boolean generateStyleRedirections(int resMapNative, int sourceStyle, + int destStyle); + + /** + * Get package name of current theme (may return null). + * {@hide} + */ + public String getThemePackageName() { + return mThemePackageName; + } + + /** + * Sets package name and highest level style id for current theme (null, 0 is allowed). + * {@hide} + */ + public void setThemePackageName(String packageName) { + mThemePackageName = packageName; + } + + /** + * Get asset cookie for current theme (may return 0). + * {@hide} + */ + public int getThemeCookie() { + return mThemeCookie; + } + + /** + * Sets asset cookie for current theme (0 if not a themed asset manager). + * {@hide} + */ + public void setThemeCookie(int cookie) { + mThemeCookie = cookie; + } + + /** + * Add a redirection map to the asset manager. All future resource lookups + * will consult this map. + * {@hide} + */ + public void addRedirections(PackageRedirectionMap map) { + if (mRedirections == null) { + mRedirections = new SparseArray(2); + } + mRedirections.append(map.getPackageId(), map); + addRedirectionsNative(map.getNativePointer()); + } + + /** + * Clear redirection map for the asset manager. + * {@hide} + */ + public void clearRedirections() { + if (mRedirections != null) { + mRedirections.clear(); + } + clearRedirectionsNative(); + } + /** * Determine whether the state in this asset manager is up-to-date with * the files on the filesystem. If false is returned, you need to @@ -659,7 +797,7 @@ public native final void setConfiguration(int mcc, int mnc, String locale, int orientation, int touchscreen, int density, int keyboard, int keyboardHidden, int navigation, int screenWidth, int screenHeight, int smallestScreenWidthDp, int screenWidthDp, int screenHeightDp, - int screenLayout, int uiMode, int majorVersion); + int screenLayout, int uiInvertedMode, int uiMode, int majorVersion); /** * Retrieve the resource identifier for the given resource name. @@ -747,6 +885,26 @@ private native final int loadResourceBagValue(int ident, int bagEntryId, TypedVa private native final int[] getArrayStringInfo(int arrayRes); /*package*/ native final int[] getArrayIntResource(int arrayRes); + private native final int splitThemePackage(String srcFileName, String dstFileName, String [] drmProtectedAssetNames); + + /** + * {@hide} + */ + public native final int getBasePackageCount(); + + /** + * {@hide} + */ + public native final String getBasePackageName(int index); + + /** + * {@hide} + */ + public native final int getBasePackageId(int index); + + private native final void addRedirectionsNative(int redirectionMapNativePointer); + private native final void clearRedirectionsNative(); + private native final void init(); private native final void destroy(); diff --git a/core/java/android/content/res/CompatibilityInfo.java b/core/java/android/content/res/CompatibilityInfo.java index 28c751c373f..789d25e914c 100644 --- a/core/java/android/content/res/CompatibilityInfo.java +++ b/core/java/android/content/res/CompatibilityInfo.java @@ -92,9 +92,15 @@ public class CompatibilityInfo implements Parcelable { */ public final float applicationInvertedScale; + /** + * Whether the application supports third-party theming. + */ + public final boolean isThemeable; + public CompatibilityInfo(ApplicationInfo appInfo, int screenLayout, int sw, boolean forceCompat) { int compatFlags = 0; + isThemeable = appInfo.isThemeable; if (appInfo.requiresSmallestWidthDp != 0 || appInfo.compatibleWidthLimitDp != 0 || appInfo.largestWidthLimitDp != 0) { @@ -242,17 +248,19 @@ public CompatibilityInfo(ApplicationInfo appInfo, int screenLayout, int sw, } private CompatibilityInfo(int compFlags, - int dens, float scale, float invertedScale) { + int dens, float scale, float invertedScale, boolean isThemeable) { mCompatibilityFlags = compFlags; applicationDensity = dens; applicationScale = scale; applicationInvertedScale = invertedScale; + this.isThemeable = isThemeable; } private CompatibilityInfo() { this(NEVER_NEEDS_COMPAT, DisplayMetrics.DENSITY_DEVICE, 1.0f, - 1.0f); + 1.0f, + true); } /** @@ -524,6 +532,7 @@ public boolean equals(Object o) { if (applicationDensity != oc.applicationDensity) return false; if (applicationScale != oc.applicationScale) return false; if (applicationInvertedScale != oc.applicationInvertedScale) return false; + if (isThemeable != oc.isThemeable) return false; return true; } catch (ClassCastException e) { return false; @@ -561,6 +570,7 @@ public int hashCode() { result = 31 * result + applicationDensity; result = 31 * result + Float.floatToIntBits(applicationScale); result = 31 * result + Float.floatToIntBits(applicationInvertedScale); + result = 31 * result + (isThemeable ? 1 : 0); return result; } @@ -575,6 +585,7 @@ public void writeToParcel(Parcel dest, int flags) { dest.writeInt(applicationDensity); dest.writeFloat(applicationScale); dest.writeFloat(applicationInvertedScale); + dest.writeInt(isThemeable ? 1 : 0); } public static final Parcelable.Creator CREATOR @@ -593,5 +604,6 @@ private CompatibilityInfo(Parcel source) { applicationDensity = source.readInt(); applicationScale = source.readFloat(); applicationInvertedScale = source.readFloat(); + isThemeable = source.readInt() == 1 ? true : false; } } diff --git a/core/java/android/content/res/Configuration.java b/core/java/android/content/res/Configuration.java index 905ae0ddec8..2ffc1a28602 100644 --- a/core/java/android/content/res/Configuration.java +++ b/core/java/android/content/res/Configuration.java @@ -1,5 +1,6 @@ /* * Copyright (C) 2008 The Android Open Source Project + * This code has been modified. Portions copyright (C) 2010, T-Mobile USA, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,6 +22,9 @@ import android.os.Parcelable; import android.text.TextUtils; import android.view.View; +import android.util.Log; +import android.os.SystemProperties; +import android.text.TextUtils; import java.util.Locale; @@ -65,6 +69,11 @@ public final class Configuration implements Parcelable, Comparable