diff options
71 files changed, 806 insertions, 667 deletions
diff --git a/api/current.txt b/api/current.txt index d9bf802..9d3dd0f 100644 --- a/api/current.txt +++ b/api/current.txt @@ -12166,7 +12166,7 @@ package android.media { method public int getSampleRate(); method public int getState(); method public int getStreamType(); - method public android.media.AudioTimestamp getTimestamp(android.media.AudioTimestamp); + method public boolean getTimestamp(android.media.AudioTimestamp); method public void pause() throws java.lang.IllegalStateException; method public void play() throws java.lang.IllegalStateException; method public void release(); @@ -12784,6 +12784,7 @@ package android.media { ctor public MediaMuxer(java.lang.String, int) throws java.io.IOException; method public int addTrack(android.media.MediaFormat); method public void release(); + method public void setLocation(float, float); method public void setOrientationHint(int); method public void start(); method public void stop(); @@ -15260,7 +15261,7 @@ package android.nfc.cardemulation { method public final void notifyUnhandled(); method public final android.os.IBinder onBind(android.content.Intent); method public abstract void onDeactivated(int); - method public byte[] processCommandApdu(byte[], android.os.Bundle); + method public abstract byte[] processCommandApdu(byte[], android.os.Bundle); method public final void sendResponseApdu(byte[]); field public static final int DEACTIVATION_DESELECTED = 1; // 0x1 field public static final int DEACTIVATION_LINK_LOSS = 0; // 0x0 diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java index eacd78d..370db31 100644 --- a/core/java/android/app/ActivityManagerNative.java +++ b/core/java/android/app/ActivityManagerNative.java @@ -1917,7 +1917,8 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM data.enforceInterface(IActivityManager.descriptor); int pid = data.readInt(); boolean aboveSystem = data.readInt() != 0; - long res = inputDispatchingTimedOut(pid, aboveSystem); + String reason = data.readString(); + long res = inputDispatchingTimedOut(pid, aboveSystem, reason); reply.writeNoException(); reply.writeLong(res); return true; @@ -4461,12 +4462,14 @@ class ActivityManagerProxy implements IActivityManager reply.recycle(); } - public long inputDispatchingTimedOut(int pid, boolean aboveSystem) throws RemoteException { + public long inputDispatchingTimedOut(int pid, boolean aboveSystem, String reason) + throws RemoteException { Parcel data = Parcel.obtain(); Parcel reply = Parcel.obtain(); data.writeInterfaceToken(IActivityManager.descriptor); data.writeInt(pid); data.writeInt(aboveSystem ? 1 : 0); + data.writeString(reason); mRemote.transact(INPUT_DISPATCHING_TIMED_OUT_TRANSACTION, data, reply, 0); reply.readException(); long res = reply.readInt(); diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java index 19a028d..64054c5 100644 --- a/core/java/android/app/AppOpsManager.java +++ b/core/java/android/app/AppOpsManager.java @@ -185,11 +185,11 @@ public class AppOpsManager { OP_CALL_PHONE, OP_READ_SMS, OP_WRITE_SMS, - OP_READ_SMS, - OP_READ_SMS, - OP_READ_SMS, - OP_READ_SMS, - OP_WRITE_SMS, + OP_RECEIVE_SMS, + OP_RECEIVE_SMS, + OP_RECEIVE_SMS, + OP_RECEIVE_SMS, + OP_SEND_SMS, OP_READ_SMS, OP_WRITE_SMS, OP_WRITE_SETTINGS, diff --git a/core/java/android/app/IActivityManager.java b/core/java/android/app/IActivityManager.java index af9254d..b2ae298 100644 --- a/core/java/android/app/IActivityManager.java +++ b/core/java/android/app/IActivityManager.java @@ -384,7 +384,8 @@ public interface IActivityManager extends IInterface { public void requestBugReport() throws RemoteException; - public long inputDispatchingTimedOut(int pid, boolean aboveSystem) throws RemoteException; + public long inputDispatchingTimedOut(int pid, boolean aboveSystem, String reason) + throws RemoteException; public Bundle getAssistContextExtras(int requestType) throws RemoteException; diff --git a/core/java/android/hardware/camera2/CameraManager.java b/core/java/android/hardware/camera2/CameraManager.java index 8903b4a..ff9282e 100644 --- a/core/java/android/hardware/camera2/CameraManager.java +++ b/core/java/android/hardware/camera2/CameraManager.java @@ -55,7 +55,7 @@ public final class CameraManager { private final ICameraService mCameraService; private ArrayList<String> mDeviceIdList; - private HashSet<CameraListener> mListenerSet = new HashSet<CameraListener>(); + private final HashSet<CameraListener> mListenerSet = new HashSet<CameraListener>(); private final Context mContext; private final Object mLock = new Object(); @@ -332,7 +332,7 @@ public final class CameraManager { Integer oldStatus = mDeviceStatus.put(id, status); - if (oldStatus == status) { + if (oldStatus != null && oldStatus == status) { Log.v(TAG, String.format( "Device status changed to 0x%x, which is what it already was", status)); diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java index 4cf38b6..c78a973 100644 --- a/core/java/android/net/ConnectivityManager.java +++ b/core/java/android/net/ConnectivityManager.java @@ -1476,4 +1476,20 @@ public class ConnectivityManager { } catch (RemoteException e) { } } + + /** + * Set the value for enabling/disabling airplane mode + * + * @param enable whether to enable airplane mode or not + * + * <p>This method requires the call to hold the permission + * {@link android.Manifest.permission#CONNECTIVITY_INTERNAL}. + * @hide + */ + public void setAirplaneMode(boolean enable) { + try { + mService.setAirplaneMode(enable); + } catch (RemoteException e) { + } + } } diff --git a/core/java/android/net/IConnectivityManager.aidl b/core/java/android/net/IConnectivityManager.aidl index a6f10ec..b3fa79f 100644 --- a/core/java/android/net/IConnectivityManager.aidl +++ b/core/java/android/net/IConnectivityManager.aidl @@ -156,4 +156,6 @@ interface IConnectivityManager LinkQualityInfo[] getAllLinkQualityInfo(); void setProvisioningNotificationVisible(boolean visible, int networkType, in String extraInfo, in String url); + + void setAirplaneMode(boolean enable); } diff --git a/core/java/android/nfc/cardemulation/ApduServiceInfo.java b/core/java/android/nfc/cardemulation/ApduServiceInfo.java index 41c6603..d3e5752 100644 --- a/core/java/android/nfc/cardemulation/ApduServiceInfo.java +++ b/core/java/android/nfc/cardemulation/ApduServiceInfo.java @@ -105,12 +105,8 @@ public final class ApduServiceInfo implements Parcelable { if (onHost) { parser = si.loadXmlMetaData(pm, HostApduService.SERVICE_META_DATA); if (parser == null) { - Log.d(TAG, "Didn't find service meta-data, trying legacy."); - parser = si.loadXmlMetaData(pm, HostApduService.OLD_SERVICE_META_DATA); - if (parser == null) { - throw new XmlPullParserException("No " + HostApduService.SERVICE_META_DATA + - " meta-data"); - } + throw new XmlPullParserException("No " + HostApduService.SERVICE_META_DATA + + " meta-data"); } } else { parser = si.loadXmlMetaData(pm, OffHostApduService.SERVICE_META_DATA); diff --git a/core/java/android/nfc/cardemulation/CardEmulationManager.java b/core/java/android/nfc/cardemulation/CardEmulationManager.java deleted file mode 100644 index 124ea1c..0000000 --- a/core/java/android/nfc/cardemulation/CardEmulationManager.java +++ /dev/null @@ -1,347 +0,0 @@ -/* - * Copyright (C) 2013 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.nfc.cardemulation; - -import android.annotation.SdkConstant; -import android.annotation.SdkConstant.SdkConstantType; -import android.app.ActivityThread; -import android.content.ComponentName; -import android.content.Context; -import android.content.pm.IPackageManager; -import android.content.pm.PackageManager; -import android.nfc.INfcCardEmulation; -import android.nfc.NfcAdapter; -import android.os.RemoteException; -import android.os.UserHandle; -import android.provider.Settings; -import android.util.Log; - -import java.util.HashMap; -import java.util.List; - -/** - * TODO Remove when calling .apks are upgraded - * @hide - */ -public final class CardEmulationManager { - static final String TAG = "CardEmulationManager"; - - /** - * Activity action: ask the user to change the default - * card emulation service for a certain category. This will - * show a dialog that asks the user whether he wants to - * replace the current default service with the service - * identified with the ComponentName specified in - * {@link #EXTRA_SERVICE_COMPONENT}, for the category - * specified in {@link #EXTRA_CATEGORY} - */ - @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) - public static final String ACTION_CHANGE_DEFAULT = - "android.nfc.cardemulation.ACTION_CHANGE_DEFAULT"; - - /** - * The category extra for {@link #ACTION_CHANGE_DEFAULT} - * - * @see #ACTION_CHANGE_DEFAULT - */ - public static final String EXTRA_CATEGORY = "category"; - - /** - * The ComponentName object passed in as a parcelable - * extra for {@link #ACTION_CHANGE_DEFAULT} - * - * @see #ACTION_CHANGE_DEFAULT - */ - public static final String EXTRA_SERVICE_COMPONENT = "component"; - - /** - * The payment category can be used to indicate that an AID - * represents a payment application. - */ - public static final String CATEGORY_PAYMENT = "payment"; - - /** - * If an AID group does not contain a category, or the - * specified category is not defined by the platform version - * that is parsing the AID group, all AIDs in the group will - * automatically be categorized under the {@link #CATEGORY_OTHER} - * category. - */ - public static final String CATEGORY_OTHER = "other"; - - /** - * Return value for {@link #getSelectionModeForCategory(String)}. - * - * <p>In this mode, the user has set a default service for this - * AID category. If a remote reader selects any of the AIDs - * that the default service has registered in this category, - * that service will automatically be bound to to handle - * the transaction. - * - * <p>There are still cases where a service that is - * not the default for a category can selected: - * <p> - * If a remote reader selects an AID in this category - * that is not handled by the default service, and there is a set - * of other services {S} that do handle this AID, the - * user is asked if he wants to use any of the services in - * {S} instead. - * <p> - * As a special case, if the size of {S} is one, containing a single service X, - * and all AIDs X has registered in this category are not - * registered by any other service, then X will be - * selected automatically without asking the user. - * <p>Example: - * <ul> - * <li>Service A registers AIDs "1", "2" and "3" in the category - * <li>Service B registers AIDs "3" and "4" in the category - * <li>Service C registers AIDs "5" and "6" in the category - * </ul> - * In this case, the following will happen when service A - * is the default: - * <ul> - * <li>Reader selects AID "1", "2" or "3": service A is invoked automatically - * <li>Reader selects AID "4": the user is asked to confirm he - * wants to use service B, because its AIDs overlap with service A. - * <li>Reader selects AID "5" or "6": service C is invoked automatically, - * because all AIDs it has asked for are only registered by C, - * and there is no overlap. - * </ul> - * - */ - public static final int SELECTION_MODE_PREFER_DEFAULT = 0; - - /** - * Return value for {@link #getSelectionModeForCategory(String)}. - * - * <p>In this mode, whenever an AID of this category is selected, - * the user is asked which service he wants to use to handle - * the transaction, even if there is only one matching service. - */ - public static final int SELECTION_MODE_ALWAYS_ASK = 1; - - /** - * Return value for {@link #getSelectionModeForCategory(String)}. - * - * <p>In this mode, the user will only be asked to select a service - * if the selected AID has been registered by multiple applications. - */ - public static final int SELECTION_MODE_ASK_IF_CONFLICT = 2; - - static boolean sIsInitialized = false; - static HashMap<Context, CardEmulationManager> sCardEmuManagers = new HashMap(); - static INfcCardEmulation sService; - - final Context mContext; - - private CardEmulationManager(Context context, INfcCardEmulation service) { - mContext = context.getApplicationContext(); - sService = service; - } - - public static synchronized CardEmulationManager getInstance(NfcAdapter adapter) { - if (adapter == null) throw new NullPointerException("NfcAdapter is null"); - Context context = adapter.getContext(); - if (context == null) { - Log.e(TAG, "NfcAdapter context is null."); - throw new UnsupportedOperationException(); - } - if (!sIsInitialized) { - IPackageManager pm = ActivityThread.getPackageManager(); - if (pm == null) { - Log.e(TAG, "Cannot get PackageManager"); - throw new UnsupportedOperationException(); - } - try { - if (!pm.hasSystemFeature(PackageManager.FEATURE_NFC_HOST_CARD_EMULATION)) { - Log.e(TAG, "This device does not support card emulation"); - throw new UnsupportedOperationException(); - } - } catch (RemoteException e) { - Log.e(TAG, "PackageManager query failed."); - throw new UnsupportedOperationException(); - } - sIsInitialized = true; - } - CardEmulationManager manager = sCardEmuManagers.get(context); - if (manager == null) { - // Get card emu service - INfcCardEmulation service = adapter.getCardEmulationService(); - manager = new CardEmulationManager(context, service); - sCardEmuManagers.put(context, manager); - } - return manager; - } - - /** - * Allows an application to query whether a service is currently - * the default service to handle a card emulation category. - * - * <p>Note that if {@link #getSelectionModeForCategory(String)} - * returns {@link #SELECTION_MODE_ALWAYS_ASK}, this method will always - * return false. - * - * @param service The ComponentName of the service - * @param category The category - * @return whether service is currently the default service for the category. - */ - public boolean isDefaultServiceForCategory(ComponentName service, String category) { - try { - return sService.isDefaultServiceForCategory(UserHandle.myUserId(), service, category); - } catch (RemoteException e) { - // Try one more time - recoverService(); - if (sService == null) { - Log.e(TAG, "Failed to recover CardEmulationService."); - return false; - } - try { - return sService.isDefaultServiceForCategory(UserHandle.myUserId(), service, - category); - } catch (RemoteException ee) { - Log.e(TAG, "Failed to recover CardEmulationService."); - return false; - } - } - } - - /** - * - * Allows an application to query whether a service is currently - * the default handler for a specified ISO7816-4 Application ID. - * - * @param service The ComponentName of the service - * @param aid The ISO7816-4 Application ID - * @return - */ - public boolean isDefaultServiceForAid(ComponentName service, String aid) { - try { - return sService.isDefaultServiceForAid(UserHandle.myUserId(), service, aid); - } catch (RemoteException e) { - // Try one more time - recoverService(); - if (sService == null) { - Log.e(TAG, "Failed to recover CardEmulationService."); - return false; - } - try { - return sService.isDefaultServiceForAid(UserHandle.myUserId(), service, aid); - } catch (RemoteException ee) { - Log.e(TAG, "Failed to reach CardEmulationService."); - return false; - } - } - } - - /** - * Returns the application selection mode for the passed in category. - * Valid return values are: - * <p>{@link #SELECTION_MODE_PREFER_DEFAULT} the user has requested a default - * application for this category, which will be preferred. - * <p>{@link #SELECTION_MODE_ALWAYS_ASK} the user has requested to be asked - * every time what app he would like to use in this category. - * <p>{@link #SELECTION_MODE_ASK_IF_CONFLICT} the user will only be asked - * to pick a service if there is a conflict. - * @param category The category, for example {@link #CATEGORY_PAYMENT} - * @return - */ - public int getSelectionModeForCategory(String category) { - if (CATEGORY_PAYMENT.equals(category)) { - String defaultComponent = Settings.Secure.getString(mContext.getContentResolver(), - Settings.Secure.NFC_PAYMENT_DEFAULT_COMPONENT); - if (defaultComponent != null) { - return SELECTION_MODE_PREFER_DEFAULT; - } else { - return SELECTION_MODE_ALWAYS_ASK; - } - } else { - // All other categories are in "only ask if conflict" mode - return SELECTION_MODE_ASK_IF_CONFLICT; - } - } - - /** - * @hide - */ - public boolean setDefaultServiceForCategory(ComponentName service, String category) { - try { - return sService.setDefaultServiceForCategory(UserHandle.myUserId(), service, category); - } catch (RemoteException e) { - // Try one more time - recoverService(); - if (sService == null) { - Log.e(TAG, "Failed to recover CardEmulationService."); - return false; - } - try { - return sService.setDefaultServiceForCategory(UserHandle.myUserId(), service, - category); - } catch (RemoteException ee) { - Log.e(TAG, "Failed to reach CardEmulationService."); - return false; - } - } - } - - /** - * @hide - */ - public boolean setDefaultForNextTap(ComponentName service) { - try { - return sService.setDefaultForNextTap(UserHandle.myUserId(), service); - } catch (RemoteException e) { - // Try one more time - recoverService(); - if (sService == null) { - Log.e(TAG, "Failed to recover CardEmulationService."); - return false; - } - try { - return sService.setDefaultForNextTap(UserHandle.myUserId(), service); - } catch (RemoteException ee) { - Log.e(TAG, "Failed to reach CardEmulationService."); - return false; - } - } - } - /** - * @hide - */ - public List<ApduServiceInfo> getServices(String category) { - try { - return sService.getServices(UserHandle.myUserId(), category); - } catch (RemoteException e) { - // Try one more time - recoverService(); - if (sService == null) { - Log.e(TAG, "Failed to recover CardEmulationService."); - return null; - } - try { - return sService.getServices(UserHandle.myUserId(), category); - } catch (RemoteException ee) { - Log.e(TAG, "Failed to reach CardEmulationService."); - return null; - } - } - } - - void recoverService() { - NfcAdapter adapter = NfcAdapter.getDefaultAdapter(mContext); - sService = adapter.getCardEmulationService(); - } -} diff --git a/core/java/android/nfc/cardemulation/HostApduService.java b/core/java/android/nfc/cardemulation/HostApduService.java index 174acc0..e2c3ca6 100644 --- a/core/java/android/nfc/cardemulation/HostApduService.java +++ b/core/java/android/nfc/cardemulation/HostApduService.java @@ -50,23 +50,6 @@ public abstract class HostApduService extends Service { "android.nfc.cardemulation.host_apdu_service"; /** - * The {@link Intent} that must be declared as handled by the service. - * TODO Remove - * @hide - */ - public static final String OLD_SERVICE_INTERFACE = - "android.nfc.HostApduService"; - - /** - * The name of the meta-data element that contains - * more information about this service. - * - * TODO Remove - * @hide - */ - public static final String OLD_SERVICE_META_DATA = "android.nfc.HostApduService"; - - /** * Reason for {@link #onDeactivated(int)}. * Indicates deactivation was due to the NFC link * being lost. @@ -282,37 +265,7 @@ public abstract class HostApduService extends Service { * @return a byte-array containing the response APDU, or null if no * response APDU can be sent at this point. */ - public byte[] processCommandApdu(byte[] commandApdu, Bundle extras) { - // TODO make this abstract - return processCommandApdu(commandApdu, 0); - } - - /** - * <p>This method will be called when a command APDU has been received - * from a remote device. A response APDU can be provided directly - * by returning a byte-array in this method. Note that in general - * response APDUs must be sent as quickly as possible, given the fact - * that the user is likely holding his device over an NFC reader - * when this method is called. - * - * <p class="note">If there are multiple services that have registered for the same - * AIDs in their meta-data entry, you will only get called if the user has - * explicitly selected your service, either as a default or just for the next tap. - * - * <p class="note">This method is running on the main thread of your application. - * If you cannot return a response APDU immediately, return null - * and use the {@link #sendResponseApdu(byte[])} method later. - * - * @deprecated use {@link #processCommandApdu(byte[], Bundle)} - * @param commandApdu - * @param flags - * @return a byte-array containing the response APDU, or null if no - * response APDU can be sent at this point. - * @hide - */ - public byte[] processCommandApdu(byte[] commandApdu, int flags) { - return null; - } + public abstract byte[] processCommandApdu(byte[] commandApdu, Bundle extras); /** * This method will be called in two possible scenarios: diff --git a/core/java/android/preference/TwoStatePreference.java b/core/java/android/preference/TwoStatePreference.java index c649879..af83953 100644 --- a/core/java/android/preference/TwoStatePreference.java +++ b/core/java/android/preference/TwoStatePreference.java @@ -21,6 +21,7 @@ import android.content.SharedPreferences; import android.content.res.TypedArray; import android.os.Parcel; import android.os.Parcelable; +import android.text.TextUtils; import android.util.AttributeSet; import android.view.View; import android.view.accessibility.AccessibilityEvent; @@ -215,17 +216,17 @@ public abstract class TwoStatePreference extends Preference { TextView summaryView = (TextView) view.findViewById(com.android.internal.R.id.summary); if (summaryView != null) { boolean useDefaultSummary = true; - if (mChecked && mSummaryOn != null) { + if (mChecked && !TextUtils.isEmpty(mSummaryOn)) { summaryView.setText(mSummaryOn); useDefaultSummary = false; - } else if (!mChecked && mSummaryOff != null) { + } else if (!mChecked && !TextUtils.isEmpty(mSummaryOff)) { summaryView.setText(mSummaryOff); useDefaultSummary = false; } if (useDefaultSummary) { final CharSequence summary = getSummary(); - if (summary != null) { + if (!TextUtils.isEmpty(summary)) { summaryView.setText(summary); useDefaultSummary = false; } diff --git a/core/java/android/provider/CallLog.java b/core/java/android/provider/CallLog.java index 9d52c83..a6f23a8 100644 --- a/core/java/android/provider/CallLog.java +++ b/core/java/android/provider/CallLog.java @@ -137,8 +137,18 @@ public class CallLog { public static final String NUMBER = "number"; /** - * The number presenting rules set by the network for "allowed", - * "payphone", "restricted" or "unknown". + * The number presenting rules set by the network. + * + * <p> + * Allowed values: + * <ul> + * <li>{@link #PRESENTATION_ALLOWED}</li> + * <li>{@link #PRESENTATION_RESTRICTED}</li> + * <li>{@link #PRESENTATION_UNKNOWN}</li> + * <li>{@link #PRESENTATION_PAYPHONE}</li> + * </ul> + * </p> + * * <P>Type: INTEGER</P> */ public static final String NUMBER_PRESENTATION = "presentation"; diff --git a/core/java/android/provider/DocumentsContract.java b/core/java/android/provider/DocumentsContract.java index 91b3b48..43120c4 100644 --- a/core/java/android/provider/DocumentsContract.java +++ b/core/java/android/provider/DocumentsContract.java @@ -459,6 +459,7 @@ public final class DocumentsContract { private static final String PATH_SEARCH = "search"; private static final String PARAM_QUERY = "query"; + private static final String PARAM_MANAGE = "manage"; /** * Build Uri representing the roots of a document provider. When queried, a @@ -583,6 +584,16 @@ public final class DocumentsContract { return searchDocumentsUri.getQueryParameter(PARAM_QUERY); } + /** {@hide} */ + public static Uri setManageMode(Uri uri) { + return uri.buildUpon().appendQueryParameter(PARAM_MANAGE, "true").build(); + } + + /** {@hide} */ + public static boolean isManageMode(Uri uri) { + return uri.getBooleanQueryParameter(PARAM_MANAGE, false); + } + /** * Return list of all documents that the calling package has "open." These * are Uris matching {@link DocumentsContract} to which persistent diff --git a/core/java/android/provider/DocumentsProvider.java b/core/java/android/provider/DocumentsProvider.java index 1b0fc4d..d801827 100644 --- a/core/java/android/provider/DocumentsProvider.java +++ b/core/java/android/provider/DocumentsProvider.java @@ -167,6 +167,14 @@ public abstract class DocumentsProvider extends ContentProvider { String parentDocumentId, String[] projection, String sortOrder) throws FileNotFoundException; + /** {@hide} */ + @SuppressWarnings("unused") + public Cursor queryChildDocumentsForManage( + String parentDocumentId, String[] projection, String sortOrder) + throws FileNotFoundException { + throw new UnsupportedOperationException("Manage not supported"); + } + /** * Return documents that that match the given query, starting the search at * the given directory. @@ -262,7 +270,12 @@ public abstract class DocumentsProvider extends ContentProvider { case MATCH_DOCUMENT: return queryDocument(getDocumentId(uri), projection); case MATCH_CHILDREN: - return queryChildDocuments(getDocumentId(uri), projection, sortOrder); + if (DocumentsContract.isManageMode(uri)) { + return queryChildDocumentsForManage( + getDocumentId(uri), projection, sortOrder); + } else { + return queryChildDocuments(getDocumentId(uri), projection, sortOrder); + } case MATCH_SEARCH: return querySearchDocuments( getDocumentId(uri), getSearchDocumentsQuery(uri), projection); diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 1a80818..b70d74a 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -2439,7 +2439,9 @@ public final class Settings { SIP_CALL_OPTIONS, SIP_RECEIVE_CALLS, POINTER_SPEED, - VIBRATE_WHEN_RINGING + VIBRATE_WHEN_RINGING, + RINGTONE, + NOTIFICATION_SOUND }; // Settings moved to Settings.Secure diff --git a/core/java/android/speech/RecognizerIntent.java b/core/java/android/speech/RecognizerIntent.java index 457e66c..e991d84 100644 --- a/core/java/android/speech/RecognizerIntent.java +++ b/core/java/android/speech/RecognizerIntent.java @@ -55,7 +55,10 @@ public class RecognizerIntent { * <p>Starting this intent with just {@link Activity#startActivity(Intent)} is not supported. * You must either use {@link Activity#startActivityForResult(Intent, int)}, or provide a * PendingIntent, to receive recognition results. - * + * + * <p>The implementation of this API is likely to stream audio to remote servers to perform + * speech recognition which can use a substantial amount of bandwidth. + * * <p>Required extras: * <ul> * <li>{@link #EXTRA_LANGUAGE_MODEL} diff --git a/core/java/android/speech/SpeechRecognizer.java b/core/java/android/speech/SpeechRecognizer.java index 8fee41d..94aedbd 100644 --- a/core/java/android/speech/SpeechRecognizer.java +++ b/core/java/android/speech/SpeechRecognizer.java @@ -39,8 +39,14 @@ import java.util.Queue; * This class provides access to the speech recognition service. This service allows access to the * speech recognizer. Do not instantiate this class directly, instead, call * {@link SpeechRecognizer#createSpeechRecognizer(Context)}. This class's methods must be - * invoked only from the main application thread. Please note that the application must have - * {@link android.Manifest.permission#RECORD_AUDIO} permission to use this class. + * invoked only from the main application thread. + * + * <p>The implementation of this API is likely to stream audio to remote servers to perform speech + * recognition. As such this API is not intended to be used for continuous recognition, which would + * consume a significant amount of battery and bandwidth. + * + * <p>Please note that the application must have {@link android.Manifest.permission#RECORD_AUDIO} + * permission to use this class. */ public class SpeechRecognizer { /** DEBUG value to enable verbose debug prints */ diff --git a/core/java/android/transition/Transition.java b/core/java/android/transition/Transition.java index 59df183..8ea9d48 100644 --- a/core/java/android/transition/Transition.java +++ b/core/java/android/transition/Transition.java @@ -122,6 +122,10 @@ public abstract class Transition implements Cloneable { // Whether this transition is currently paused, due to a call to pause() boolean mPaused = false; + // Whether this transition has ended. Used to avoid pause/resume on transitions + // that have completed + private boolean mEnded = false; + // The set of listeners to be sent transition lifecycle events. ArrayList<TransitionListener> mListeners = null; @@ -914,21 +918,23 @@ public abstract class Transition implements Cloneable { * @hide */ public void pause() { - ArrayMap<Animator, AnimationInfo> runningAnimators = getRunningAnimators(); - int numOldAnims = runningAnimators.size(); - for (int i = numOldAnims - 1; i >= 0; i--) { - Animator anim = runningAnimators.keyAt(i); - anim.pause(); - } - if (mListeners != null && mListeners.size() > 0) { - ArrayList<TransitionListener> tmpListeners = - (ArrayList<TransitionListener>) mListeners.clone(); - int numListeners = tmpListeners.size(); - for (int i = 0; i < numListeners; ++i) { - tmpListeners.get(i).onTransitionPause(this); + if (!mEnded) { + ArrayMap<Animator, AnimationInfo> runningAnimators = getRunningAnimators(); + int numOldAnims = runningAnimators.size(); + for (int i = numOldAnims - 1; i >= 0; i--) { + Animator anim = runningAnimators.keyAt(i); + anim.pause(); + } + if (mListeners != null && mListeners.size() > 0) { + ArrayList<TransitionListener> tmpListeners = + (ArrayList<TransitionListener>) mListeners.clone(); + int numListeners = tmpListeners.size(); + for (int i = 0; i < numListeners; ++i) { + tmpListeners.get(i).onTransitionPause(this); + } } + mPaused = true; } - mPaused = true; } /** @@ -940,18 +946,20 @@ public abstract class Transition implements Cloneable { */ public void resume() { if (mPaused) { - ArrayMap<Animator, AnimationInfo> runningAnimators = getRunningAnimators(); - int numOldAnims = runningAnimators.size(); - for (int i = numOldAnims - 1; i >= 0; i--) { - Animator anim = runningAnimators.keyAt(i); - anim.resume(); - } - if (mListeners != null && mListeners.size() > 0) { - ArrayList<TransitionListener> tmpListeners = - (ArrayList<TransitionListener>) mListeners.clone(); - int numListeners = tmpListeners.size(); - for (int i = 0; i < numListeners; ++i) { - tmpListeners.get(i).onTransitionResume(this); + if (!mEnded) { + ArrayMap<Animator, AnimationInfo> runningAnimators = getRunningAnimators(); + int numOldAnims = runningAnimators.size(); + for (int i = numOldAnims - 1; i >= 0; i--) { + Animator anim = runningAnimators.keyAt(i); + anim.resume(); + } + if (mListeners != null && mListeners.size() > 0) { + ArrayList<TransitionListener> tmpListeners = + (ArrayList<TransitionListener>) mListeners.clone(); + int numListeners = tmpListeners.size(); + for (int i = 0; i < numListeners; ++i) { + tmpListeners.get(i).onTransitionResume(this); + } } } mPaused = false; @@ -1071,6 +1079,7 @@ public abstract class Transition implements Cloneable { tmpListeners.get(i).onTransitionStart(this); } } + mEnded = false; } mNumInstances++; } @@ -1111,6 +1120,7 @@ public abstract class Transition implements Cloneable { v.setHasTransientState(false); } } + mEnded = true; } } diff --git a/core/java/android/view/IApplicationToken.aidl b/core/java/android/view/IApplicationToken.aidl index 5f0600f..633b40f 100644 --- a/core/java/android/view/IApplicationToken.aidl +++ b/core/java/android/view/IApplicationToken.aidl @@ -23,7 +23,7 @@ interface IApplicationToken void windowsDrawn(); void windowsVisible(); void windowsGone(); - boolean keyDispatchingTimedOut(); + boolean keyDispatchingTimedOut(String reason); long getKeyDispatchingTimeout(); } diff --git a/core/java/android/widget/ListView.java b/core/java/android/widget/ListView.java index 389d9d6..b239fbd 100644 --- a/core/java/android/widget/ListView.java +++ b/core/java/android/widget/ListView.java @@ -1555,8 +1555,9 @@ public class ListView extends AbsListView { } else if (mItemCount != mAdapter.getCount()) { throw new IllegalStateException("The content of the adapter has changed but " + "ListView did not receive a notification. Make sure the content of " - + "your adapter is not modified from a background thread, but only " - + "from the UI thread. [in ListView(" + getId() + ", " + getClass() + + "your adapter is not modified from a background thread, but only from " + + "the UI thread. Make sure your adapter calls notifyDataSetChanged() " + + "when its content changes. [in ListView(" + getId() + ", " + getClass() + ") with Adapter(" + mAdapter.getClass() + ")]"); } diff --git a/core/java/android/widget/Spinner.java b/core/java/android/widget/Spinner.java index b87ed7a..b75d36f 100644 --- a/core/java/android/widget/Spinner.java +++ b/core/java/android/widget/Spinner.java @@ -412,6 +412,8 @@ public class Spinner extends AbsSpinner implements OnClickListener { public void setAdapter(SpinnerAdapter adapter) { super.setAdapter(adapter); + mRecycler.clear(); + if (mPopup != null) { mPopup.setAdapter(new DropDownAdapter(adapter)); } else { @@ -426,9 +428,8 @@ public class Spinner extends AbsSpinner implements OnClickListener { if (getChildCount() > 0) { child = getChildAt(0); } else if (mAdapter != null && mAdapter.getCount() > 0) { - child = makeAndAddView(0); + child = makeView(0, false); mRecycler.put(0, child); - removeAllViewsInLayout(); } if (child != null) { @@ -536,7 +537,7 @@ public class Spinner extends AbsSpinner implements OnClickListener { mFirstPosition = mSelectedPosition; if (mAdapter != null) { - View sel = makeAndAddView(mSelectedPosition); + View sel = makeView(mSelectedPosition, true); int width = sel.getMeasuredWidth(); int selectedOffset = childrenLeft; final int layoutDirection = getLayoutDirection(); @@ -571,17 +572,17 @@ public class Spinner extends AbsSpinner implements OnClickListener { * from the old to new positions. * * @param position Position in the spinner for the view to obtain - * @return A view that has been added to the spinner + * @param addChild true to add the child to the spinner, false to obtain and configure only. + * @return A view for the given position */ - private View makeAndAddView(int position) { - + private View makeView(int position, boolean addChild) { View child; if (!mDataChanged) { child = mRecycler.get(position); if (child != null) { // Position the view - setUpChild(child); + setUpChild(child, addChild); return child; } @@ -591,7 +592,7 @@ public class Spinner extends AbsSpinner implements OnClickListener { child = mAdapter.getView(position, null, this); // Position the view - setUpChild(child); + setUpChild(child, addChild); return child; } @@ -601,8 +602,9 @@ public class Spinner extends AbsSpinner implements OnClickListener { * and fill out its layout paramters. * * @param child The view to position + * @param addChild true if the child should be added to the Spinner during setup */ - private void setUpChild(View child) { + private void setUpChild(View child, boolean addChild) { // Respect layout params that are already in the view. Otherwise // make some up... @@ -611,7 +613,9 @@ public class Spinner extends AbsSpinner implements OnClickListener { lp = generateDefaultLayoutParams(); } - addViewInLayout(child, 0, lp); + if (addChild) { + addViewInLayout(child, 0, lp); + } child.setSelected(hasFocus()); if (mDisableChildrenWhenDisabled) { diff --git a/graphics/java/android/graphics/BitmapFactory.java b/graphics/java/android/graphics/BitmapFactory.java index e3adc59..23606a1 100644 --- a/graphics/java/android/graphics/BitmapFactory.java +++ b/graphics/java/android/graphics/BitmapFactory.java @@ -565,24 +565,10 @@ public class BitmapFactory { Trace.traceBegin(Trace.TRACE_TAG_GRAPHICS, "decodeBitmap"); try { - boolean decodeGenericStream = true; if (is instanceof AssetManager.AssetInputStream) { final int asset = ((AssetManager.AssetInputStream) is).getAssetInt(); bm = nativeDecodeAsset(asset, outPadding, opts); - // Do not follow the normal case. - decodeGenericStream = false; - } else if (is instanceof FileInputStream) { - try { - FileDescriptor fd = ((FileInputStream) is).getFD(); - // decodeFileDescriptor will take care of throwing the IAE and - // calling setDensityFromOptions. - return decodeFileDescriptor(fd, outPadding, opts); - } catch (IOException e) { - // Fall through to nativeDecodeStream. - } - } - - if (decodeGenericStream) { + } else { byte [] tempStorage = null; if (opts != null) tempStorage = opts.inTempStorage; if (tempStorage == null) tempStorage = new byte[DECODE_BUFFER_SIZE]; diff --git a/libs/hwui/FontRenderer.cpp b/libs/hwui/FontRenderer.cpp index e8456af..7e99a5f 100644 --- a/libs/hwui/FontRenderer.cpp +++ b/libs/hwui/FontRenderer.cpp @@ -732,7 +732,7 @@ void FontRenderer::blurImage(uint8_t** image, int32_t width, int32_t height, int if (mRs == 0) { mRs = new RSC::RS(); - if (!mRs->init(RSC::RS_INIT_LOW_LATENCY & RSC::RS_INIT_SYNCHRONOUS)) { + if (!mRs->init(RSC::RS_INIT_LOW_LATENCY | RSC::RS_INIT_SYNCHRONOUS)) { ALOGE("blur RS failed to init"); } diff --git a/libs/hwui/OpenGLRenderer.cpp b/libs/hwui/OpenGLRenderer.cpp index 238d9a4..2066f69 100644 --- a/libs/hwui/OpenGLRenderer.cpp +++ b/libs/hwui/OpenGLRenderer.cpp @@ -471,12 +471,14 @@ status_t OpenGLRenderer::callDrawGLFunction(Functor* functor, Rect& dirty) { info.height = getSnapshot()->height; getSnapshot()->transform->copyTo(&info.transform[0]); + bool dirtyClip = mDirtyClip; // setup GL state for functor if (mDirtyClip) { - setScissorFromClip(); setStencilFromClip(); // can issue draws, so must precede enableScissor()/interrupt() } - mCaches.enableScissor(); + if (mCaches.enableScissor() || dirtyClip) { + setScissorFromClip(); + } interrupt(); // call functor immediately after GL state setup diff --git a/media/java/android/media/AudioTrack.java b/media/java/android/media/AudioTrack.java index 788257d..78a37c5 100644 --- a/media/java/android/media/AudioTrack.java +++ b/media/java/android/media/AudioTrack.java @@ -746,31 +746,28 @@ public class AudioTrack * If you need such features, consider implementing them at application level. * * @param timestamp a reference to a non-null AudioTimestamp instance allocated - * and owned by caller, or null. - * @return that same instance if timestamp parameter is non-null and a timestamp is available, - * or a reference to a new AudioTimestamp instance which is now owned by caller - * if timestamp parameter is null and a timestamp is available, - * or null if no timestamp is available. In either successful case, + * and owned by caller. + * @return true if a timestamp is available, or false if no timestamp is available. + * If a timestamp if available, * the AudioTimestamp instance is filled in with a position in frame units, together * with the estimated time when that frame was presented or is committed to * be presented. * In the case that no timestamp is available, any supplied instance is left unaltered. */ - public AudioTimestamp getTimestamp(AudioTimestamp timestamp) + public boolean getTimestamp(AudioTimestamp timestamp) { + if (timestamp == null) { + throw new IllegalArgumentException(); + } // It's unfortunate, but we have to either create garbage every time or use synchronized long[] longArray = new long[2]; int ret = native_get_timestamp(longArray); - if (ret == SUCCESS) { - if (timestamp == null) { - timestamp = new AudioTimestamp(); - } - timestamp.framePosition = longArray[0]; - timestamp.nanoTime = longArray[1]; - } else { - timestamp = null; + if (ret != SUCCESS) { + return false; } - return timestamp; + timestamp.framePosition = longArray[0]; + timestamp.nanoTime = longArray[1]; + return true; } diff --git a/media/java/android/media/MediaMuxer.java b/media/java/android/media/MediaMuxer.java index 774964e..65a9308 100644 --- a/media/java/android/media/MediaMuxer.java +++ b/media/java/android/media/MediaMuxer.java @@ -92,6 +92,7 @@ final public class MediaMuxer { Object[] values); private static native void nativeSetOrientationHint(int nativeObject, int degrees); + private static native void nativeSetLocation(int nativeObject, int latitude, int longitude); private static native void nativeWriteSampleData(int nativeObject, int trackIndex, ByteBuffer byteBuf, int offset, int size, long presentationTimeUs, int flags); @@ -165,6 +166,41 @@ final public class MediaMuxer { } /** + * Set and store the geodata (latitude and longitude) in the output file. + * This method should be called before {@link #start}. The geodata is stored + * in udta box if the output format is + * {@link OutputFormat#MUXER_OUTPUT_MPEG_4}, and is ignored for other output + * formats. The geodata is stored according to ISO-6709 standard. + * + * @param latitude Latitude in degrees. Its value must be in the range [-90, + * 90]. + * @param longitude Longitude in degrees. Its value must be in the range + * [-180, 180]. + * @throws IllegalArgumentException If the given latitude or longitude is out + * of range. + * @throws IllegalStateException If this method is called after {@link #start}. + */ + public void setLocation(float latitude, float longitude) { + int latitudex10000 = (int) (latitude * 10000 + 0.5); + int longitudex10000 = (int) (longitude * 10000 + 0.5); + + if (latitudex10000 > 900000 || latitudex10000 < -900000) { + String msg = "Latitude: " + latitude + " out of range."; + throw new IllegalArgumentException(msg); + } + if (longitudex10000 > 1800000 || longitudex10000 < -1800000) { + String msg = "Longitude: " + longitude + " out of range"; + throw new IllegalArgumentException(msg); + } + + if (mState == MUXER_STATE_INITIALIZED && mNativeObject != 0) { + nativeSetLocation(mNativeObject, latitudex10000, longitudex10000); + } else { + throw new IllegalStateException("Can't set location due to wrong state."); + } + } + + /** * Starts the muxer. * <p>Make sure this is called after {@link #addTrack} and before * {@link #writeSampleData}.</p> diff --git a/media/jni/android_media_MediaMuxer.cpp b/media/jni/android_media_MediaMuxer.cpp index 7517e85..457b956 100644 --- a/media/jni/android_media_MediaMuxer.cpp +++ b/media/jni/android_media_MediaMuxer.cpp @@ -164,6 +164,18 @@ static void android_media_MediaMuxer_setOrientationHint( } +static void android_media_MediaMuxer_setLocation( + JNIEnv *env, jclass clazz, jint nativeObject, jint latitude, jint longitude) { + MediaMuxer* muxer = reinterpret_cast<MediaMuxer *>(nativeObject); + + status_t res = muxer->setLocation(latitude, longitude); + if (res != OK) { + jniThrowException(env, "java/lang/IllegalStateException", + "Failed to set location"); + return; + } +} + static void android_media_MediaMuxer_start(JNIEnv *env, jclass clazz, jint nativeObject) { sp<MediaMuxer> muxer(reinterpret_cast<MediaMuxer *>(nativeObject)); @@ -216,6 +228,9 @@ static JNINativeMethod gMethods[] = { { "nativeSetOrientationHint", "(II)V", (void *)android_media_MediaMuxer_setOrientationHint}, + { "nativeSetLocation", "(III)V", + (void *)android_media_MediaMuxer_setLocation}, + { "nativeStart", "(I)V", (void *)android_media_MediaMuxer_start}, { "nativeWriteSampleData", "(IILjava/nio/ByteBuffer;IIJI)V", diff --git a/packages/DocumentsUI/res/layout/fragment_directory.xml b/packages/DocumentsUI/res/layout/fragment_directory.xml index 881349b..f0ab0d1 100644 --- a/packages/DocumentsUI/res/layout/fragment_directory.xml +++ b/packages/DocumentsUI/res/layout/fragment_directory.xml @@ -24,8 +24,8 @@ android:layout_height="match_parent" android:gravity="center" android:text="@string/empty" - android:textAppearance="?android:attr/textAppearanceMedium" - android:visibility="gone" /> + android:visibility="gone" + style="@style/TextAppearance.Medium" /> <ListView android:id="@+id/list" diff --git a/packages/DocumentsUI/res/layout/item_doc_grid.xml b/packages/DocumentsUI/res/layout/item_doc_grid.xml index 8d1fc9a..eea90b5 100644 --- a/packages/DocumentsUI/res/layout/item_doc_grid.xml +++ b/packages/DocumentsUI/res/layout/item_doc_grid.xml @@ -32,12 +32,26 @@ android:layout_weight="1" android:background="#fff"> - <ImageView + <FrameLayout android:id="@android:id/icon" android:layout_width="match_parent" - android:layout_height="match_parent" - android:scaleType="centerInside" - android:contentDescription="@null" /> + android:layout_height="match_parent"> + + <ImageView + android:id="@+id/icon_mime" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:scaleType="centerInside" + android:contentDescription="@null" /> + + <ImageView + android:id="@+id/icon_thumb" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:scaleType="centerCrop" + android:contentDescription="@null" /> + + </FrameLayout> <ImageView android:layout_width="match_parent" @@ -63,8 +77,8 @@ android:layout_weight="1" android:singleLine="true" android:ellipsize="marquee" - android:textAppearance="?android:attr/textAppearanceMedium" - android:textAlignment="viewStart" /> + android:textAlignment="viewStart" + style="@style/TextAppearance.Medium" /> <ImageView android:id="@android:id/icon1" @@ -93,7 +107,7 @@ android:singleLine="true" android:ellipsize="marquee" android:textAlignment="viewStart" - android:textAppearance="?android:attr/textAppearanceSmall" /> + style="@style/TextAppearance.Small" /> <TextView android:id="@+id/size" @@ -105,7 +119,7 @@ android:singleLine="true" android:ellipsize="marquee" android:textAlignment="viewStart" - android:textAppearance="?android:attr/textAppearanceSmall" /> + style="@style/TextAppearance.Small" /> <TextView android:id="@android:id/summary" @@ -117,7 +131,7 @@ android:singleLine="true" android:ellipsize="marquee" android:textAlignment="viewStart" - android:textAppearance="?android:attr/textAppearanceSmall" /> + style="@style/TextAppearance.Small" /> </LinearLayout> diff --git a/packages/DocumentsUI/res/layout/item_doc_list.xml b/packages/DocumentsUI/res/layout/item_doc_list.xml index 8372eed..84fda9d 100644 --- a/packages/DocumentsUI/res/layout/item_doc_list.xml +++ b/packages/DocumentsUI/res/layout/item_doc_list.xml @@ -25,15 +25,29 @@ android:paddingBottom="8dip" android:orientation="horizontal"> - <ImageView + <FrameLayout android:id="@android:id/icon" android:layout_width="@dimen/icon_size" android:layout_height="@dimen/icon_size" android:layout_marginStart="12dp" android:layout_marginEnd="20dp" - android:layout_gravity="center_vertical" - android:scaleType="centerInside" - android:contentDescription="@null" /> + android:layout_gravity="center_vertical"> + + <ImageView + android:id="@+id/icon_mime" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:scaleType="centerInside" + android:contentDescription="@null" /> + + <ImageView + android:id="@+id/icon_thumb" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:scaleType="centerCrop" + android:contentDescription="@null" /> + + </FrameLayout> <LinearLayout android:layout_width="0dip" @@ -54,8 +68,8 @@ android:layout_weight="1" android:singleLine="true" android:ellipsize="marquee" - android:textAppearance="?android:attr/textAppearanceMedium" - android:textAlignment="viewStart" /> + android:textAlignment="viewStart" + style="@style/TextAppearance.Medium" /> <ImageView android:id="@android:id/icon1" @@ -82,7 +96,7 @@ android:singleLine="true" android:ellipsize="marquee" android:textAlignment="viewStart" - android:textAppearance="?android:attr/textAppearanceSmall" /> + style="@style/TextAppearance.Small" /> <TextView android:id="@+id/size" @@ -94,7 +108,7 @@ android:singleLine="true" android:ellipsize="marquee" android:textAlignment="viewStart" - android:textAppearance="?android:attr/textAppearanceSmall" /> + style="@style/TextAppearance.Small" /> <TextView android:id="@android:id/summary" @@ -106,7 +120,7 @@ android:singleLine="true" android:ellipsize="marquee" android:textAlignment="viewStart" - android:textAppearance="?android:attr/textAppearanceSmall" /> + style="@style/TextAppearance.Small" /> </LinearLayout> diff --git a/packages/DocumentsUI/res/layout/item_loading_grid.xml b/packages/DocumentsUI/res/layout/item_loading_grid.xml new file mode 100644 index 0000000..21be137 --- /dev/null +++ b/packages/DocumentsUI/res/layout/item_loading_grid.xml @@ -0,0 +1,34 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2013 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="@dimen/grid_height" + android:minHeight="?android:attr/listPreferredItemHeight" + android:paddingStart="?android:attr/listPreferredItemPaddingStart" + android:paddingEnd="?android:attr/listPreferredItemPaddingEnd" + android:paddingTop="8dip" + android:paddingBottom="8dip" + android:orientation="horizontal"> + + <ProgressBar + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center" + android:indeterminate="true" + style="?android:attr/progressBarStyle" /> + +</FrameLayout> diff --git a/packages/DocumentsUI/res/layout/item_loading.xml b/packages/DocumentsUI/res/layout/item_loading_list.xml index 7da71e3..7da71e3 100644 --- a/packages/DocumentsUI/res/layout/item_loading.xml +++ b/packages/DocumentsUI/res/layout/item_loading_list.xml diff --git a/packages/DocumentsUI/res/layout/item_message_grid.xml b/packages/DocumentsUI/res/layout/item_message_grid.xml index 941340e..b3bdd28 100644 --- a/packages/DocumentsUI/res/layout/item_message_grid.xml +++ b/packages/DocumentsUI/res/layout/item_message_grid.xml @@ -16,44 +16,36 @@ <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" - android:layout_height="180dip" + android:layout_height="@dimen/grid_height" + android:paddingTop="?android:attr/listPreferredItemPaddingStart" + android:paddingStart="?android:attr/listPreferredItemPaddingStart" + android:paddingEnd="?android:attr/listPreferredItemPaddingEnd" android:paddingBottom="?android:attr/listPreferredItemPaddingEnd" - android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"> + android:foreground="@drawable/item_background"> - <FrameLayout + <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" - android:background="@color/chip" - android:foreground="@drawable/item_background" - android:duplicateParentState="true"> - - <LinearLayout - android:layout_width="match_parent" - android:layout_height="match_parent" - android:paddingBottom="6dp" - android:orientation="vertical" - android:gravity="center"> - - <ImageView - android:id="@android:id/icon" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:contentDescription="@null" /> - - <TextView - android:id="@android:id/title" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:singleLine="true" - android:ellipsize="marquee" - android:paddingTop="6dp" - android:paddingStart="?android:attr/listPreferredItemPaddingStart" - android:paddingEnd="?android:attr/listPreferredItemPaddingEnd" - android:textAppearance="?android:attr/textAppearanceSmall" - android:textAlignment="viewStart" /> - - </LinearLayout> - - </FrameLayout> + android:orientation="vertical" + android:gravity="center"> + + <ImageView + android:id="@android:id/icon" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:contentDescription="@null" /> + + <TextView + android:id="@android:id/title" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:gravity="center" + android:maxLines="4" + android:ellipsize="end" + android:paddingTop="6dp" + android:textAppearance="?android:attr/textAppearanceSmall" + android:textAlignment="viewStart" /> + + </LinearLayout> </FrameLayout> diff --git a/packages/DocumentsUI/res/layout/item_message_list.xml b/packages/DocumentsUI/res/layout/item_message_list.xml index dda3c80..ffda98c 100644 --- a/packages/DocumentsUI/res/layout/item_message_list.xml +++ b/packages/DocumentsUI/res/layout/item_message_list.xml @@ -39,9 +39,9 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_gravity="center_vertical" - android:singleLine="true" - android:ellipsize="marquee" - android:textAppearance="?android:attr/textAppearanceMedium" - android:textAlignment="viewStart" /> + android:maxLines="2" + android:ellipsize="end" + android:textAlignment="viewStart" + android:textAppearance="?android:attr/textAppearanceSmall" /> </LinearLayout> diff --git a/packages/DocumentsUI/res/layout/item_root.xml b/packages/DocumentsUI/res/layout/item_root.xml index ce97b57..98d78da 100644 --- a/packages/DocumentsUI/res/layout/item_root.xml +++ b/packages/DocumentsUI/res/layout/item_root.xml @@ -43,8 +43,8 @@ android:layout_height="wrap_content" android:singleLine="true" android:ellipsize="marquee" - android:textAppearance="?android:attr/textAppearanceMedium" - android:textAlignment="viewStart" /> + android:textAlignment="viewStart" + style="@style/TextAppearance.Medium" /> <TextView android:id="@android:id/summary" @@ -52,8 +52,8 @@ android:layout_height="wrap_content" android:singleLine="true" android:ellipsize="marquee" - android:textAppearance="?android:attr/textAppearanceSmall" - android:textAlignment="viewStart" /> + android:textAlignment="viewStart" + style="@style/TextAppearance.Small" /> </LinearLayout> diff --git a/packages/DocumentsUI/res/layout/item_root_header.xml b/packages/DocumentsUI/res/layout/item_root_header.xml index 127b254..6b9857d 100644 --- a/packages/DocumentsUI/res/layout/item_root_header.xml +++ b/packages/DocumentsUI/res/layout/item_root_header.xml @@ -14,6 +14,14 @@ limitations under the License. --> -<TextView xmlns:android="http://schemas.android.com/apk/res/android" - android:id="@android:id/title" - style="?android:attr/listSeparatorTextViewStyle" /> +<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:paddingTop="12dp"> + + <TextView + android:id="@android:id/title" + android:textColor="?android:attr/textColorTertiary" + style="?android:attr/listSeparatorTextViewStyle" /> + +</FrameLayout> diff --git a/packages/DocumentsUI/res/layout/item_title.xml b/packages/DocumentsUI/res/layout/item_title.xml index 9594e4e..7eb100a 100644 --- a/packages/DocumentsUI/res/layout/item_title.xml +++ b/packages/DocumentsUI/res/layout/item_title.xml @@ -38,7 +38,7 @@ android:layout_height="wrap_content" android:singleLine="true" android:ellipsize="marquee" - android:textAppearance="?android:attr/textAppearanceMedium" - android:textAlignment="viewStart" /> + android:textAlignment="viewStart" + style="@style/TextAppearance.Medium" /> </LinearLayout> diff --git a/packages/DocumentsUI/res/menu/mode_directory.xml b/packages/DocumentsUI/res/menu/mode_directory.xml index 624e024..0a3645f 100644 --- a/packages/DocumentsUI/res/menu/mode_directory.xml +++ b/packages/DocumentsUI/res/menu/mode_directory.xml @@ -21,12 +21,12 @@ android:showAsAction="always" /> <item android:id="@+id/menu_share" - android:icon="@android:drawable/ic_menu_share" + android:icon="@drawable/ic_menu_share" android:title="@string/menu_share" android:showAsAction="always" /> <item android:id="@+id/menu_delete" - android:icon="@android:drawable/ic_menu_delete" + android:icon="@drawable/ic_menu_delete" android:title="@string/menu_delete" android:showAsAction="always" /> </menu> diff --git a/packages/DocumentsUI/res/values/strings.xml b/packages/DocumentsUI/res/values/strings.xml index f4a822d..682ae4a 100644 --- a/packages/DocumentsUI/res/values/strings.xml +++ b/packages/DocumentsUI/res/values/strings.xml @@ -15,54 +15,83 @@ --> <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <!-- Title of the documents application [CHAR LIMIT=32] --> <string name="app_label">Documents</string> + <!-- Action bar title prompting user to choose a location to open a document from [CHAR LIMIT=32] --> <string name="title_open">Open from</string> + <!-- Action bar title prompting user to choose a location to save a document to [CHAR LIMIT=32] --> <string name="title_save">Save to</string> + <!-- Menu item that creates a new directory/folder at the current location [CHAR LIMIT=24] --> <string name="menu_create_dir">Create folder</string> + <!-- Menu item that switches view to show documents as a large-format grid of thumbnails [CHAR LIMIT=24] --> <string name="menu_grid">Grid view</string> + <!-- Menu item that switches view to show documents as a list [CHAR LIMIT=24] --> <string name="menu_list">List view</string> + <!-- Menu item that switches the criteria with which documents are sorted [CHAR LIMIT=24] --> <string name="menu_sort">Sort by</string> + <!-- Menu item that enters a mode to search for documents [CHAR LIMIT=24] --> <string name="menu_search">Search</string> + <!-- Menu item that enters activity to change settings [CHAR LIMIT=24] --> <string name="menu_settings">Settings</string> + <!-- Menu item title that opens the selected documents [CHAR LIMIT=24] --> <string name="menu_open">Open</string> + <!-- Menu item title that saves the current document [CHAR LIMIT=24] --> <string name="menu_save">Save</string> + <!-- Menu item title that shares the selected documents [CHAR LIMIT=24] --> <string name="menu_share">Share</string> + <!-- Menu item title that deletes the selected documents [CHAR LIMIT=24] --> <string name="menu_delete">Delete</string> + <!-- Action mode title summarizing the number of documents selected [CHAR LIMIT=32] --> <string name="mode_selected_count"><xliff:g id="count" example="3">%1$d</xliff:g> selected</string> + <!-- Mode that sorts documents by their display name alphabetically [CHAR LIMIT=24] --> <string name="sort_name">By name</string> + <!-- Mode that sorts documents by their last modified time in descending order; most recent first [CHAR LIMIT=24] --> <string name="sort_date">By date modified</string> + <!-- Mode that sorts documents by their file size in descending order; largest first [CHAR LIMIT=24] --> <string name="sort_size">By size</string> + <!-- Accessibility title to open the drawer showing all roots where documents can be stored [CHAR LIMIT=32] --> <string name="drawer_open">Show roots</string> + <!-- Accessibility title to close the drawer showing all roots where documents can be stored [CHAR LIMIT=32] --> <string name="drawer_close">Hide roots</string> + <!-- Toast shown when saving a document failed with an error [CHAR LIMIT=48] --> <string name="save_error">Failed to save document</string> + <!-- Title of storage root location that contains recently modified or used documents [CHAR LIMIT=24] --> <string name="root_recent">Recent</string> + <!-- Subtitle of storage root indicating the total free space available, in bytes [CHAR LIMIT=24] --> <string name="root_available_bytes"><xliff:g id="size" example="3GB">%1$s</xliff:g> free</string> - <string name="root_type_service">Services</string> + <!-- Header title for list of storage roots that contains cloud services [CHAR LIMIT=24] --> + <string name="root_type_service">Storage services</string> + <!-- Header title for list of storage roots that contains shortcuts to documents that may be available elsewhere [CHAR LIMIT=24] --> <string name="root_type_shortcut">Shortcuts</string> + <!-- Header title for list of storage roots that contains physical devices [CHAR LIMIT=24] --> <string name="root_type_device">Devices</string> + <!-- Header title for list of additional apps that can provide documents [CHAR LIMIT=24] --> <string name="root_type_apps">More apps</string> + <!-- Title for setting that will show all advanced storage devices [CHAR LIMIT=32] --> <string name="pref_advanced_devices">Display advanced devices</string> + <!-- Title for setting that will show file sizes for all documents [CHAR LIMIT=32] --> <string name="pref_file_size">Display file size</string> <string name="pref_device_size">Display device size</string> + <!-- Text shown when a directory of documents is empty [CHAR LIMIT=24] --> <string name="empty">No items</string> + <!-- Toast shown when no app can be found to open the selected document [CHAR LIMIT=48] --> <string name="toast_no_application">Can\'t open file</string> + <!-- Toast shown when some of the selected documents failed to be deleted [CHAR LIMIT=48] --> <string name="toast_failed_delete">Unable to delete some documents</string> - <string name="more">More</string> - <string name="loading">Loading\u2026</string> - + <!-- Title of dialog when prompting user to select an app to share documents with [CHAR LIMIT=32] --> <string name="share_via">Share via</string> </resources> diff --git a/packages/DocumentsUI/res/values/styles.xml b/packages/DocumentsUI/res/values/styles.xml new file mode 100644 index 0000000..59fbd6f --- /dev/null +++ b/packages/DocumentsUI/res/values/styles.xml @@ -0,0 +1,29 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2013 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<resources> + <style name="TextAppearance" /> + + <style name="TextAppearance.Medium"> + <item name="android:textAppearance">?android:attr/textAppearanceMedium</item> + <item name="android:textColor">?android:attr/textColorSecondary</item> + </style> + + <style name="TextAppearance.Small"> + <item name="android:textAppearance">?android:attr/textAppearanceSmall</item> + <item name="android:textColor">?android:attr/textColorTertiary</item> + </style> +</resources> diff --git a/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java b/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java index 45f028d..ba5a511 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java +++ b/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java @@ -38,9 +38,11 @@ import android.content.Loader; import android.database.Cursor; import android.graphics.Bitmap; import android.graphics.Point; +import android.graphics.drawable.Drawable; import android.net.Uri; import android.os.AsyncTask; import android.os.Bundle; +import android.os.CancellationSignal; import android.provider.DocumentsContract; import android.provider.DocumentsContract.Document; import android.text.format.DateUtils; @@ -56,6 +58,7 @@ import android.view.View; import android.view.ViewGroup; import android.widget.AbsListView; import android.widget.AbsListView.MultiChoiceModeListener; +import android.widget.AbsListView.RecyclerListener; import android.widget.AdapterView; import android.widget.AdapterView.OnItemClickListener; import android.widget.BaseAdapter; @@ -97,6 +100,7 @@ public class DirectoryFragment extends Fragment { private int mLastMode = MODE_UNKNOWN; private int mLastSortOrder = SORT_ORDER_UNKNOWN; + private boolean mLastShowSize = false; private Point mThumbSize; @@ -162,10 +166,12 @@ public class DirectoryFragment extends Fragment { mListView = (ListView) view.findViewById(R.id.list); mListView.setOnItemClickListener(mItemListener); mListView.setMultiChoiceModeListener(mMultiListener); + mListView.setRecyclerListener(mRecycleListener); mGridView = (GridView) view.findViewById(R.id.grid); mGridView.setOnItemClickListener(mItemListener); mGridView.setMultiChoiceModeListener(mMultiListener); + mGridView.setRecyclerListener(mRecycleListener); return view; } @@ -192,13 +198,19 @@ public class DirectoryFragment extends Fragment { case TYPE_NORMAL: contentsUri = DocumentsContract.buildChildDocumentsUri( doc.authority, doc.documentId); + if (state.action == ACTION_MANAGE) { + contentsUri = DocumentsContract.setManageMode(contentsUri); + } return new DirectoryLoader( - context, root, doc, contentsUri, state.userSortOrder); + context, mType, root, doc, contentsUri, state.userSortOrder); case TYPE_SEARCH: contentsUri = DocumentsContract.buildSearchDocumentsUri( doc.authority, doc.documentId, query); + if (state.action == ACTION_MANAGE) { + contentsUri = DocumentsContract.setManageMode(contentsUri); + } return new DirectoryLoader( - context, root, doc, contentsUri, state.userSortOrder); + context, mType, root, doc, contentsUri, state.userSortOrder); case TYPE_RECENT_OPEN: final RootsCache roots = DocumentsApplication.getRootsCache(context); final List<RootInfo> matchingRoots = roots.getMatchingRoots(state); @@ -286,8 +298,9 @@ public class DirectoryFragment extends Fragment { mFilter = new MimePredicate(state.acceptMimes); - if (mLastMode == state.derivedMode) return; + if (mLastMode == state.derivedMode && mLastShowSize == state.showSize) return; mLastMode = state.derivedMode; + mLastShowSize = state.showSize; mListView.setVisibility(state.derivedMode == MODE_LIST ? View.VISIBLE : View.GONE); mGridView.setVisibility(state.derivedMode == MODE_GRID ? View.VISIBLE : View.GONE); @@ -425,6 +438,20 @@ public class DirectoryFragment extends Fragment { } }; + private RecyclerListener mRecycleListener = new RecyclerListener() { + @Override + public void onMovedToScrapHeap(View view) { + final ImageView iconThumb = (ImageView) view.findViewById(R.id.icon_thumb); + if (iconThumb != null) { + final ThumbnailAsyncTask oldTask = (ThumbnailAsyncTask) iconThumb.getTag(); + if (oldTask != null) { + oldTask.reallyCancel(); + iconThumb.setTag(null); + } + } + } + }; + private void onShareDocuments(List<DocumentInfo> docs) { Intent intent; if (docs.size() == 1) { @@ -500,7 +527,7 @@ public class DirectoryFragment extends Fragment { } } - private static class LoadingFooter extends Footer { + private class LoadingFooter extends Footer { public LoadingFooter() { super(1); } @@ -508,10 +535,19 @@ public class DirectoryFragment extends Fragment { @Override public View getView(View convertView, ViewGroup parent) { final Context context = parent.getContext(); + final State state = getDisplayState(DirectoryFragment.this); + if (convertView == null) { final LayoutInflater inflater = LayoutInflater.from(context); - convertView = inflater.inflate(R.layout.item_loading, parent, false); + if (state.derivedMode == MODE_LIST) { + convertView = inflater.inflate(R.layout.item_loading_list, parent, false); + } else if (state.derivedMode == MODE_GRID) { + convertView = inflater.inflate(R.layout.item_loading_grid, parent, false); + } else { + throw new IllegalStateException(); + } } + return convertView; } } @@ -632,7 +668,9 @@ public class DirectoryFragment extends Fragment { final String docSummary = getCursorString(cursor, Document.COLUMN_SUMMARY); final long docSize = getCursorLong(cursor, Document.COLUMN_SIZE); - final ImageView icon = (ImageView) convertView.findViewById(android.R.id.icon); + final View icon = convertView.findViewById(android.R.id.icon); + final ImageView iconMime = (ImageView) convertView.findViewById(R.id.icon_mime); + final ImageView iconThumb = (ImageView) convertView.findViewById(R.id.icon_thumb); final TextView title = (TextView) convertView.findViewById(android.R.id.title); final View line2 = convertView.findViewById(R.id.line2); final ImageView icon1 = (ImageView) convertView.findViewById(android.R.id.icon1); @@ -640,30 +678,49 @@ public class DirectoryFragment extends Fragment { final TextView date = (TextView) convertView.findViewById(R.id.date); final TextView size = (TextView) convertView.findViewById(R.id.size); - final ThumbnailAsyncTask oldTask = (ThumbnailAsyncTask) icon.getTag(); + final ThumbnailAsyncTask oldTask = (ThumbnailAsyncTask) iconThumb.getTag(); if (oldTask != null) { - oldTask.cancel(false); + oldTask.reallyCancel(); + iconThumb.setTag(null); } + iconMime.animate().cancel(); + iconThumb.animate().cancel(); + final boolean supportsThumbnail = (docFlags & Document.FLAG_SUPPORTS_THUMBNAIL) != 0; final boolean allowThumbnail = (state.derivedMode == MODE_GRID) || MimePredicate.mimeMatches(LIST_THUMBNAIL_MIMES, docMimeType); + boolean cacheHit = false; if (supportsThumbnail && allowThumbnail) { final Uri uri = DocumentsContract.buildDocumentUri(docAuthority, docId); final Bitmap cachedResult = thumbs.get(uri); if (cachedResult != null) { - icon.setImageBitmap(cachedResult); + iconThumb.setImageBitmap(cachedResult); + cacheHit = true; } else { - final ThumbnailAsyncTask task = new ThumbnailAsyncTask(icon, mThumbSize); - icon.setImageBitmap(null); - icon.setTag(task); - task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, uri); + iconThumb.setImageDrawable(null); + final ThumbnailAsyncTask task = new ThumbnailAsyncTask( + uri, iconMime, iconThumb, mThumbSize); + iconThumb.setTag(task); + task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); } - } else if (docIcon != 0) { - icon.setImageDrawable(IconUtils.loadPackageIcon(context, docAuthority, docIcon)); + } + + // Always throw MIME icon into place, even when a thumbnail is being + // loaded in background. + if (cacheHit) { + iconMime.setAlpha(0f); + iconThumb.setAlpha(1f); } else { - icon.setImageDrawable(IconUtils.loadMimeIcon(context, docMimeType)); + iconMime.setAlpha(1f); + iconThumb.setAlpha(0f); + if (docIcon != 0) { + iconMime.setImageDrawable( + IconUtils.loadPackageIcon(context, docAuthority, docIcon)); + } else { + iconMime.setImageDrawable(IconUtils.loadMimeIcon(context, docMimeType)); + } } title.setText(docDisplayName); @@ -672,12 +729,19 @@ public class DirectoryFragment extends Fragment { if (mType == TYPE_RECENT_OPEN) { final RootInfo root = roots.getRoot(docAuthority, docRootId); + final Drawable iconDrawable = root.loadIcon(context); icon1.setVisibility(View.VISIBLE); - icon1.setImageDrawable(root.loadIcon(context)); - summary.setText(root.getDirectoryString()); - summary.setVisibility(View.VISIBLE); - summary.setTextAlignment(TextView.TEXT_ALIGNMENT_TEXT_END); - hasLine2 = true; + icon1.setImageDrawable(iconDrawable); + + if (iconDrawable != null && roots.isIconUnique(root)) { + // No summary needed if icon speaks for itself + summary.setVisibility(View.INVISIBLE); + } else { + summary.setText(root.getDirectoryString()); + summary.setVisibility(View.VISIBLE); + summary.setTextAlignment(TextView.TEXT_ALIGNMENT_TEXT_END); + hasLine2 = true; + } } else { icon1.setVisibility(View.GONE); if (docSummary != null) { @@ -762,32 +826,39 @@ public class DirectoryFragment extends Fragment { } private static class ThumbnailAsyncTask extends AsyncTask<Uri, Void, Bitmap> { - private final ImageView mTarget; + private final Uri mUri; + private final ImageView mIconMime; + private final ImageView mIconThumb; private final Point mThumbSize; + private final CancellationSignal mSignal; - public ThumbnailAsyncTask(ImageView target, Point thumbSize) { - mTarget = target; + public ThumbnailAsyncTask( + Uri uri, ImageView iconMime, ImageView iconThumb, Point thumbSize) { + mUri = uri; + mIconMime = iconMime; + mIconThumb = iconThumb; mThumbSize = thumbSize; + mSignal = new CancellationSignal(); } - @Override - protected void onPreExecute() { - mTarget.setTag(this); + public void reallyCancel() { + cancel(false); + mSignal.cancel(); } @Override protected Bitmap doInBackground(Uri... params) { - final Context context = mTarget.getContext(); - final Uri uri = params[0]; + final Context context = mIconThumb.getContext(); Bitmap result = null; try { + // TODO: switch to using unstable provider result = DocumentsContract.getDocumentThumbnail( - context.getContentResolver(), uri, mThumbSize, null); + context.getContentResolver(), mUri, mThumbSize, mSignal); if (result != null) { final ThumbnailCache thumbs = DocumentsApplication.getThumbnailsCache( context, mThumbSize); - thumbs.put(uri, result); + thumbs.put(mUri, result); } } catch (Exception e) { Log.w(TAG, "Failed to load thumbnail: " + e); @@ -797,9 +868,14 @@ public class DirectoryFragment extends Fragment { @Override protected void onPostExecute(Bitmap result) { - if (mTarget.getTag() == this) { - mTarget.setImageBitmap(result); - mTarget.setTag(null); + if (mIconThumb.getTag() == this && result != null) { + mIconThumb.setTag(null); + mIconThumb.setImageBitmap(result); + + mIconMime.setAlpha(1f); + mIconMime.animate().alpha(0f).start(); + mIconThumb.setAlpha(0f); + mIconThumb.animate().alpha(1f).start(); } } } diff --git a/packages/DocumentsUI/src/com/android/documentsui/DirectoryLoader.java b/packages/DocumentsUI/src/com/android/documentsui/DirectoryLoader.java index 1471836..334e262 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/DirectoryLoader.java +++ b/packages/DocumentsUI/src/com/android/documentsui/DirectoryLoader.java @@ -62,6 +62,7 @@ class DirectoryResult implements AutoCloseable { public class DirectoryLoader extends AsyncTaskLoader<DirectoryResult> { private final ForceLoadContentObserver mObserver = new ForceLoadContentObserver(); + private final int mType; private final RootInfo mRoot; private final DocumentInfo mDoc; private final Uri mUri; @@ -70,9 +71,10 @@ public class DirectoryLoader extends AsyncTaskLoader<DirectoryResult> { private CancellationSignal mSignal; private DirectoryResult mResult; - public DirectoryLoader( - Context context, RootInfo root, DocumentInfo doc, Uri uri, int userSortOrder) { + public DirectoryLoader(Context context, int type, RootInfo root, DocumentInfo doc, Uri uri, + int userSortOrder) { super(context); + mType = type; mRoot = root; mDoc = doc; mUri = uri; @@ -128,6 +130,11 @@ public class DirectoryLoader extends AsyncTaskLoader<DirectoryResult> { } } + // Search always uses ranking from provider + if (mType == DirectoryFragment.TYPE_SEARCH) { + result.sortOrder = State.SORT_ORDER_UNKNOWN; + } + Log.d(TAG, "userMode=" + userMode + ", userSortOrder=" + mUserSortOrder + " --> mode=" + result.mode + ", sortOrder=" + result.sortOrder); @@ -137,11 +144,18 @@ public class DirectoryLoader extends AsyncTaskLoader<DirectoryResult> { mUri, null, null, null, getQuerySortOrder(result.sortOrder), mSignal); cursor.registerContentObserver(mObserver); - final Cursor withRoot = new RootCursorWrapper( - mUri.getAuthority(), mRoot.rootId, cursor, -1); - final Cursor sorted = new SortingCursorWrapper(withRoot, result.sortOrder); + cursor = new RootCursorWrapper(mUri.getAuthority(), mRoot.rootId, cursor, -1); + + if (mType == DirectoryFragment.TYPE_SEARCH) { + // Filter directories out of search results, for now + cursor = new FilteringCursorWrapper(cursor, null, new String[] { + Document.MIME_TYPE_DIR }); + } else { + // Normal directories should have sorting applied + cursor = new SortingCursorWrapper(cursor, result.sortOrder); + } - result.cursor = sorted; + result.cursor = cursor; } catch (Exception e) { Log.d(TAG, "Failed to query", e); result.exception = e; diff --git a/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java b/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java index 79d2443..e89d388 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java +++ b/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java @@ -50,6 +50,7 @@ import android.util.Log; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuItem; +import android.view.MenuItem.OnActionExpandListener; import android.view.View; import android.view.ViewGroup; import android.widget.BaseAdapter; @@ -87,6 +88,7 @@ public class DocumentsActivity extends Activity { private static final String EXTRA_STATE = "state"; private boolean mIgnoreNextNavigation; + private boolean mIgnoreNextCollapse; private RootsCache mRoots; private State mState; @@ -234,12 +236,14 @@ public class DocumentsActivity extends Activity { public void onDrawerOpened(View drawerView) { mDrawerToggle.onDrawerOpened(drawerView); updateActionBar(); + invalidateOptionsMenu(); } @Override public void onDrawerClosed(View drawerView) { mDrawerToggle.onDrawerClosed(drawerView); updateActionBar(); + invalidateOptionsMenu(); } @Override @@ -305,7 +309,6 @@ public class DocumentsActivity extends Activity { public boolean onQueryTextSubmit(String query) { mState.currentSearch = query; onCurrentDirectoryChanged(); - mSearchView.setIconified(true); return true; } @@ -315,12 +318,22 @@ public class DocumentsActivity extends Activity { } }); - mSearchView.setOnCloseListener(new OnCloseListener() { + searchMenu.setOnActionExpandListener(new OnActionExpandListener() { @Override - public boolean onClose() { + public boolean onMenuItemActionExpand(MenuItem item) { + return true; + } + + @Override + public boolean onMenuItemActionCollapse(MenuItem item) { + if (mIgnoreNextCollapse) { + mIgnoreNextCollapse = false; + return true; + } + mState.currentSearch = null; onCurrentDirectoryChanged(); - return false; + return true; } }); @@ -342,6 +355,18 @@ public class DocumentsActivity extends Activity { final MenuItem list = menu.findItem(R.id.menu_list); final MenuItem settings = menu.findItem(R.id.menu_settings); + // Open drawer means we hide most actions + if (mDrawerLayout.isDrawerOpen(mRootsContainer)) { + createDir.setVisible(false); + search.setVisible(false); + sort.setVisible(false); + grid.setVisible(false); + list.setVisible(false); + mIgnoreNextCollapse = true; + search.collapseActionView(); + return true; + } + if (cwd != null) { sort.setVisible(true); grid.setVisible(mState.derivedMode != MODE_GRID); @@ -352,6 +377,17 @@ public class DocumentsActivity extends Activity { list.setVisible(false); } + if (mState.currentSearch != null) { + // Search uses backend ranking; no sorting + sort.setVisible(false); + + search.expandActionView(); + mSearchView.setQuery(mState.currentSearch, false); + } else { + mIgnoreNextCollapse = true; + search.collapseActionView(); + } + // Only sort by size when visible sortSize.setVisible(mState.showSize); diff --git a/packages/DocumentsUI/src/com/android/documentsui/DocumentsApplication.java b/packages/DocumentsUI/src/com/android/documentsui/DocumentsApplication.java index 0a6cbc0..180ddef 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/DocumentsApplication.java +++ b/packages/DocumentsUI/src/com/android/documentsui/DocumentsApplication.java @@ -56,7 +56,11 @@ public class DocumentsApplication extends Application { packageFilter.addAction(Intent.ACTION_PACKAGE_CHANGED); packageFilter.addAction(Intent.ACTION_PACKAGE_REMOVED); packageFilter.addDataScheme("package"); - registerReceiver(mPackageReceiver, packageFilter); + registerReceiver(mCacheReceiver, packageFilter); + + final IntentFilter localeFilter = new IntentFilter(); + localeFilter.addAction(Intent.ACTION_LOCALE_CHANGED); + registerReceiver(mCacheReceiver, localeFilter); } @Override @@ -70,7 +74,7 @@ public class DocumentsApplication extends Application { } } - private BroadcastReceiver mPackageReceiver = new BroadcastReceiver() { + private BroadcastReceiver mCacheReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { // TODO: narrow changed/removed to only packages that have backends diff --git a/packages/DocumentsUI/src/com/android/documentsui/FilteringCursorWrapper.java b/packages/DocumentsUI/src/com/android/documentsui/FilteringCursorWrapper.java index 60f0103..5f56963 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/FilteringCursorWrapper.java +++ b/packages/DocumentsUI/src/com/android/documentsui/FilteringCursorWrapper.java @@ -34,6 +34,10 @@ public class FilteringCursorWrapper extends AbstractCursor { private int mCount; public FilteringCursorWrapper(Cursor cursor, String[] acceptMimes) { + this(cursor, acceptMimes, null); + } + + public FilteringCursorWrapper(Cursor cursor, String[] acceptMimes, String[] rejectMimes) { mCursor = cursor; final int count = cursor.getCount(); @@ -43,6 +47,9 @@ public class FilteringCursorWrapper extends AbstractCursor { while (cursor.moveToNext()) { final String mimeType = cursor.getString( cursor.getColumnIndex(Document.COLUMN_MIME_TYPE)); + if (rejectMimes != null && MimePredicate.mimeMatches(rejectMimes, mimeType)) { + continue; + } if (MimePredicate.mimeMatches(acceptMimes, mimeType)) { mPosition[mCount++] = cursor.getPosition(); } diff --git a/packages/DocumentsUI/src/com/android/documentsui/RecentsCreateFragment.java b/packages/DocumentsUI/src/com/android/documentsui/RecentsCreateFragment.java index 3642478..140373b 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/RecentsCreateFragment.java +++ b/packages/DocumentsUI/src/com/android/documentsui/RecentsCreateFragment.java @@ -183,12 +183,12 @@ public class RecentsCreateFragment extends Fragment { convertView = inflater.inflate(R.layout.item_doc_list, parent, false); } - final ImageView icon = (ImageView) convertView.findViewById(android.R.id.icon); + final ImageView iconMime = (ImageView) convertView.findViewById(R.id.icon_mime); final TextView title = (TextView) convertView.findViewById(android.R.id.title); final View line2 = convertView.findViewById(R.id.line2); final DocumentStack stack = getItem(position); - icon.setImageDrawable(stack.root.loadIcon(context)); + iconMime.setImageDrawable(stack.root.loadIcon(context)); final Drawable crumb = context.getResources() .getDrawable(R.drawable.ic_breadcrumb_arrow); diff --git a/packages/DocumentsUI/src/com/android/documentsui/RecentsProvider.java b/packages/DocumentsUI/src/com/android/documentsui/RecentsProvider.java index 1fe5d54..af79c93 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/RecentsProvider.java +++ b/packages/DocumentsUI/src/com/android/documentsui/RecentsProvider.java @@ -151,18 +151,18 @@ public class RecentsProvider extends ContentProvider { case URI_RECENT: final long cutoff = System.currentTimeMillis() - MAX_HISTORY_IN_MILLIS; return db.query(TABLE_RECENT, projection, RecentColumns.TIMESTAMP + ">" + cutoff, - null, null, null, null); + null, null, null, sortOrder); case URI_STATE: final String authority = uri.getPathSegments().get(1); final String rootId = uri.getPathSegments().get(2); final String documentId = uri.getPathSegments().get(3); return db.query(TABLE_STATE, projection, StateColumns.AUTHORITY + "=? AND " + StateColumns.ROOT_ID + "=? AND " + StateColumns.DOCUMENT_ID + "=?", - new String[] { authority, rootId, documentId }, null, null, null); + new String[] { authority, rootId, documentId }, null, null, sortOrder); case URI_RESUME: final String packageName = uri.getPathSegments().get(1); return db.query(TABLE_RESUME, projection, ResumeColumns.PACKAGE_NAME + "=?", - new String[] { packageName }, null, null, null); + new String[] { packageName }, null, null, sortOrder); default: throw new UnsupportedOperationException("Unsupported Uri " + uri); } diff --git a/packages/DocumentsUI/src/com/android/documentsui/RootsCache.java b/packages/DocumentsUI/src/com/android/documentsui/RootsCache.java index adf4701..b48674cf 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/RootsCache.java +++ b/packages/DocumentsUI/src/com/android/documentsui/RootsCache.java @@ -109,6 +109,8 @@ public class RootsCache { } } } + + Log.d(TAG, "Update found " + mRoots.size() + " roots"); } @Deprecated @@ -134,6 +136,21 @@ public class RootsCache { } @GuardedBy("ActivityThread") + public boolean isIconUnique(RootInfo root) { + for (RootInfo test : mRoots) { + if (Objects.equal(test.authority, root.authority)) { + if (Objects.equal(test.rootId, root.rootId)) { + continue; + } + if (test.icon == root.icon) { + return false; + } + } + } + return true; + } + + @GuardedBy("ActivityThread") public RootInfo getRecentsRoot() { return mRecentsRoot; } diff --git a/packages/DocumentsUI/src/com/android/documentsui/RootsFragment.java b/packages/DocumentsUI/src/com/android/documentsui/RootsFragment.java index efb972d..f3a21c6 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/RootsFragment.java +++ b/packages/DocumentsUI/src/com/android/documentsui/RootsFragment.java @@ -25,6 +25,7 @@ import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.os.Bundle; import android.provider.DocumentsContract.Root; +import android.text.TextUtils; import android.text.format.Formatter; import android.view.LayoutInflater; import android.view.View; @@ -168,7 +169,7 @@ public class RootsFragment extends Fragment { } summary.setText(summaryText); - summary.setVisibility(summaryText != null ? View.VISIBLE : View.GONE); + summary.setVisibility(TextUtils.isEmpty(summaryText) ? View.GONE : View.VISIBLE); return convertView; } diff --git a/packages/DocumentsUI/src/com/android/documentsui/model/RootInfo.java b/packages/DocumentsUI/src/com/android/documentsui/model/RootInfo.java index e0e8acf..b5a198c 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/model/RootInfo.java +++ b/packages/DocumentsUI/src/com/android/documentsui/model/RootInfo.java @@ -26,6 +26,7 @@ import android.graphics.drawable.Drawable; import android.os.Parcel; import android.os.Parcelable; import android.provider.DocumentsContract.Root; +import android.text.TextUtils; import com.android.documentsui.IconUtils; import com.android.documentsui.R; @@ -203,6 +204,6 @@ public class RootInfo implements Durable, Parcelable { } public String getDirectoryString() { - return (summary != null) ? summary : title; + return !TextUtils.isEmpty(summary) ? summary : title; } } diff --git a/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java b/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java index 2326ec2..ada3ad7 100644 --- a/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java +++ b/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java @@ -267,16 +267,15 @@ public class ExternalStorageProvider extends DocumentsProvider { final LinkedList<File> pending = new LinkedList<File>(); pending.add(parent); - while (!pending.isEmpty() && result.getCount() < 20) { + while (!pending.isEmpty() && result.getCount() < 24) { final File file = pending.removeFirst(); if (file.isDirectory()) { for (File child : file.listFiles()) { pending.add(child); } - } else { - if (file.getName().toLowerCase().contains(query)) { - includeFile(result, null, file); - } + } + if (file.getName().toLowerCase().contains(query)) { + includeFile(result, null, file); } } return result; diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java index 3f04470..344446f 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java @@ -738,10 +738,12 @@ public class SettingsBackupAgent extends BackupAgentHelper { } } + // Intercept the keys and see if they need special handling + value = mSettingsHelper.onBackupValue(key, value); + if (value == null) { continue; } - // Write the key and value in the intermediary array. byte[] keyBytes = key.getBytes(); totalSize += INTEGER_BYTE_COUNT + keyBytes.length; diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java index a446e40..080290c 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java @@ -23,6 +23,8 @@ import android.content.Context; import android.content.res.Configuration; import android.location.LocationManager; import android.media.AudioManager; +import android.media.RingtoneManager; +import android.net.Uri; import android.os.IPowerManager; import android.os.RemoteException; import android.os.ServiceManager; @@ -33,6 +35,7 @@ import android.text.TextUtils; import java.util.Locale; public class SettingsHelper { + private static final String SILENT_RINGTONE = "_silent"; private Context mContext; private AudioManager mAudioManager; @@ -63,10 +66,56 @@ public class SettingsHelper { setAutoRestore(Integer.parseInt(value) == 1); } else if (isAlreadyConfiguredCriticalAccessibilitySetting(name)) { return false; + } else if (Settings.System.RINGTONE.equals(name) + || Settings.System.NOTIFICATION_SOUND.equals(name)) { + setRingtone(name, value); + return false; } return true; } + public String onBackupValue(String name, String value) { + // Special processing for backing up ringtones + if (Settings.System.RINGTONE.equals(name) + || Settings.System.NOTIFICATION_SOUND.equals(name)) { + if (value == null) { + // Silent ringtone + return SILENT_RINGTONE; + } else { + return getCanonicalRingtoneValue(value); + } + } + // Return the original value + return value; + } + + /** + * Sets the ringtone of type specified by the name. + * + * @param name should be Settings.System.RINGTONE or Settings.System.NOTIFICATION_SOUND. + * @param value can be a canonicalized uri or "_silent" to indicate a silent (null) ringtone. + */ + private void setRingtone(String name, String value) { + // If it's null, don't change the default + if (value == null) return; + Uri ringtoneUri = null; + if (SILENT_RINGTONE.equals(value)) { + ringtoneUri = null; + } else { + Uri canonicalUri = Uri.parse(value); + ringtoneUri = mContext.getContentResolver().uncanonicalize(canonicalUri); + } + final int ringtoneType = Settings.System.RINGTONE.equals(name) + ? RingtoneManager.TYPE_RINGTONE : RingtoneManager.TYPE_NOTIFICATION; + RingtoneManager.setActualDefaultRingtoneUri(mContext, ringtoneType, ringtoneUri); + } + + private String getCanonicalRingtoneValue(String value) { + final Uri ringtoneUri = Uri.parse(value); + final Uri canonicalUri = mContext.getContentResolver().canonicalize(ringtoneUri); + return canonicalUri == null ? null : canonicalUri.toString(); + } + private boolean isAlreadyConfiguredCriticalAccessibilitySetting(String name) { // These are the critical accessibility settings that are required for a // blind user to be able to interact with the device. If these settings are diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java index 119299f..2063563 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java @@ -196,7 +196,8 @@ class SaveImageInBackgroundTask extends AsyncTask<SaveImageInBackgroundData, Voi // Create screenshot directory if it doesn't exist mScreenshotDir.mkdirs(); - // media provider uses seconds, not milliseconds + // media provider uses seconds for DATE_MODIFIED and DATE_ADDED, but milliseconds + // for DATE_TAKEN long dateSeconds = mImageTime / 1000; // Save the screenshot to the MediaStore @@ -205,7 +206,7 @@ class SaveImageInBackgroundTask extends AsyncTask<SaveImageInBackgroundData, Voi values.put(MediaStore.Images.ImageColumns.DATA, mImageFilePath); values.put(MediaStore.Images.ImageColumns.TITLE, mImageFileName); values.put(MediaStore.Images.ImageColumns.DISPLAY_NAME, mImageFileName); - values.put(MediaStore.Images.ImageColumns.DATE_TAKEN, dateSeconds); + values.put(MediaStore.Images.ImageColumns.DATE_TAKEN, mImageTime); values.put(MediaStore.Images.ImageColumns.DATE_ADDED, dateSeconds); values.put(MediaStore.Images.ImageColumns.DATE_MODIFIED, dateSeconds); values.put(MediaStore.Images.ImageColumns.MIME_TYPE, "image/png"); diff --git a/packages/services/PacProcessor/src/com/android/pacprocessor/PacService.java b/packages/services/PacProcessor/src/com/android/pacprocessor/PacService.java index 7e76025..c6b76f1 100644 --- a/packages/services/PacProcessor/src/com/android/pacprocessor/PacService.java +++ b/packages/services/PacProcessor/src/com/android/pacprocessor/PacService.java @@ -25,6 +25,9 @@ import android.util.Log; import com.android.net.IProxyService; +import java.net.MalformedURLException; +import java.net.URL; + public class PacService extends Service { private static final String TAG = "PacService"; @@ -68,7 +71,18 @@ public class PacService extends Service { @Override public String resolvePacFile(String host, String url) throws RemoteException { - return mPacNative.makeProxyRequest(url, host); + try { + // Check for characters that could be used for an injection attack. + new URL(url); + for (char c : host.toCharArray()) { + if (!Character.isLetterOrDigit(c) && (c != '.') && (c != '-')) { + throw new RemoteException("Invalid host was passed"); + } + } + return mPacNative.makeProxyRequest(url, host); + } catch (MalformedURLException e) { + throw new RemoteException("Invalid URL was passed"); + } } @Override diff --git a/services/input/InputDispatcher.cpp b/services/input/InputDispatcher.cpp index 795ab47..9e7a15d 100644 --- a/services/input/InputDispatcher.cpp +++ b/services/input/InputDispatcher.cpp @@ -3405,6 +3405,7 @@ void InputDispatcher::onANRLocked( & InputDispatcher::doNotifyANRLockedInterruptible); commandEntry->inputApplicationHandle = applicationHandle; commandEntry->inputWindowHandle = windowHandle; + commandEntry->reason = reason; } void InputDispatcher::doNotifyConfigurationChangedInterruptible( @@ -3434,7 +3435,8 @@ void InputDispatcher::doNotifyANRLockedInterruptible( mLock.unlock(); nsecs_t newTimeout = mPolicy->notifyANR( - commandEntry->inputApplicationHandle, commandEntry->inputWindowHandle); + commandEntry->inputApplicationHandle, commandEntry->inputWindowHandle, + commandEntry->reason); mLock.lock(); diff --git a/services/input/InputDispatcher.h b/services/input/InputDispatcher.h index 0273dc4..190e7b2 100644 --- a/services/input/InputDispatcher.h +++ b/services/input/InputDispatcher.h @@ -202,7 +202,8 @@ public: /* Notifies the system that an application is not responding. * Returns a new timeout to continue waiting, or 0 to abort dispatch. */ virtual nsecs_t notifyANR(const sp<InputApplicationHandle>& inputApplicationHandle, - const sp<InputWindowHandle>& inputWindowHandle) = 0; + const sp<InputWindowHandle>& inputWindowHandle, + const String8& reason) = 0; /* Notifies the system that an input channel is unrecoverably broken. */ virtual void notifyInputChannelBroken(const sp<InputWindowHandle>& inputWindowHandle) = 0; @@ -596,6 +597,7 @@ private: KeyEntry* keyEntry; sp<InputApplicationHandle> inputApplicationHandle; sp<InputWindowHandle> inputWindowHandle; + String8 reason; int32_t userActivityEventType; uint32_t seq; bool handled; diff --git a/services/input/tests/InputDispatcher_test.cpp b/services/input/tests/InputDispatcher_test.cpp index ed2b4a5..26b4fab 100644 --- a/services/input/tests/InputDispatcher_test.cpp +++ b/services/input/tests/InputDispatcher_test.cpp @@ -50,7 +50,8 @@ private: } virtual nsecs_t notifyANR(const sp<InputApplicationHandle>& inputApplicationHandle, - const sp<InputWindowHandle>& inputWindowHandle) { + const sp<InputWindowHandle>& inputWindowHandle, + const String8& reason) { return 0; } diff --git a/services/java/com/android/server/ConnectivityService.java b/services/java/com/android/server/ConnectivityService.java index 02a78de..4e3faca 100644 --- a/services/java/com/android/server/ConnectivityService.java +++ b/services/java/com/android/server/ConnectivityService.java @@ -4681,6 +4681,21 @@ public class ConnectivityService extends IConnectivityManager.Stub { setProvNotificationVisible(visible, networkType, extraInfo, url); } + @Override + public void setAirplaneMode(boolean enable) { + enforceConnectivityInternalPermission(); + final ContentResolver cr = mContext.getContentResolver(); + Settings.Global.putInt(cr, Settings.Global.AIRPLANE_MODE_ON, enable ? 1 : 0); + Intent intent = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED); + intent.putExtra("state", enable); + final long ident = Binder.clearCallingIdentity(); + try { + mContext.sendBroadcast(intent); + } finally { + Binder.restoreCallingIdentity(ident); + } + } + private void onUserStart(int userId) { synchronized(mVpns) { Vpn userVpn = mVpns.get(userId); diff --git a/services/java/com/android/server/am/ActivityManagerService.java b/services/java/com/android/server/am/ActivityManagerService.java index c48b03c..96b7030 100644 --- a/services/java/com/android/server/am/ActivityManagerService.java +++ b/services/java/com/android/server/am/ActivityManagerService.java @@ -7951,8 +7951,8 @@ public final class ActivityManagerService extends ActivityManagerNative return KEY_DISPATCHING_TIMEOUT; } - - public long inputDispatchingTimedOut(int pid, final boolean aboveSystem) { + @Override + public long inputDispatchingTimedOut(int pid, final boolean aboveSystem, String reason) { if (checkCallingPermission(android.Manifest.permission.FILTER_EVENTS) != PackageManager.PERMISSION_GRANTED) { throw new SecurityException("Requires permission " @@ -7967,7 +7967,7 @@ public final class ActivityManagerService extends ActivityManagerNative timeout = getInputDispatchingTimeoutLocked(proc); } - if (!inputDispatchingTimedOut(proc, null, null, aboveSystem)) { + if (!inputDispatchingTimedOut(proc, null, null, aboveSystem, reason)) { return -1; } @@ -7980,13 +7980,20 @@ public final class ActivityManagerService extends ActivityManagerNative */ public boolean inputDispatchingTimedOut(final ProcessRecord proc, final ActivityRecord activity, final ActivityRecord parent, - final boolean aboveSystem) { + final boolean aboveSystem, String reason) { if (checkCallingPermission(android.Manifest.permission.FILTER_EVENTS) != PackageManager.PERMISSION_GRANTED) { throw new SecurityException("Requires permission " + android.Manifest.permission.FILTER_EVENTS); } + final String annotation; + if (reason == null) { + annotation = "Input dispatching timed out"; + } else { + annotation = "Input dispatching timed out (" + reason + ")"; + } + if (proc != null) { synchronized (this) { if (proc.debugging) { @@ -8002,7 +8009,7 @@ public final class ActivityManagerService extends ActivityManagerNative if (proc.instrumentationClass != null) { Bundle info = new Bundle(); info.putString("shortMsg", "keyDispatchingTimedOut"); - info.putString("longMsg", "Timed out while dispatching key event"); + info.putString("longMsg", annotation); finishInstrumentationLocked(proc, Activity.RESULT_CANCELED, info); return true; } @@ -8010,7 +8017,7 @@ public final class ActivityManagerService extends ActivityManagerNative mHandler.post(new Runnable() { @Override public void run() { - appNotResponding(proc, activity, parent, aboveSystem, "keyDispatchingTimedOut"); + appNotResponding(proc, activity, parent, aboveSystem, annotation); } }); } diff --git a/services/java/com/android/server/am/ActivityRecord.java b/services/java/com/android/server/am/ActivityRecord.java index bf3713b..6e50808 100644 --- a/services/java/com/android/server/am/ActivityRecord.java +++ b/services/java/com/android/server/am/ActivityRecord.java @@ -305,9 +305,9 @@ final class ActivityRecord { } } - @Override public boolean keyDispatchingTimedOut() { + @Override public boolean keyDispatchingTimedOut(String reason) { ActivityRecord activity = weakActivity.get(); - return activity != null && activity.keyDispatchingTimedOut(); + return activity != null && activity.keyDispatchingTimedOut(reason); } @Override public long getKeyDispatchingTimeout() { @@ -960,14 +960,14 @@ final class ActivityRecord { return r; } - public boolean keyDispatchingTimedOut() { + public boolean keyDispatchingTimedOut(String reason) { ActivityRecord r; ProcessRecord anrApp; synchronized(service) { r = getWaitingHistoryRecordLocked(); anrApp = r != null ? r.app : null; } - return service.inputDispatchingTimedOut(anrApp, r, this, false); + return service.inputDispatchingTimedOut(anrApp, r, this, false, reason); } /** Returns the key dispatching timeout for this application token. */ diff --git a/services/java/com/android/server/am/ActivityStack.java b/services/java/com/android/server/am/ActivityStack.java index 2b76e71..e994c23 100644 --- a/services/java/com/android/server/am/ActivityStack.java +++ b/services/java/com/android/server/am/ActivityStack.java @@ -620,7 +620,13 @@ final class ActivityStack { } void clearLaunchTime(ActivityRecord r) { - r.displayStartTime = r.fullyDrawnStartTime = 0; + // Make sure that there is no activity waiting for this to launch. + if (mStackSupervisor.mWaitingActivityLaunched.isEmpty()) { + r.displayStartTime = r.fullyDrawnStartTime = 0; + } else { + mStackSupervisor.removeTimeoutsForActivityLocked(r); + mStackSupervisor.scheduleIdleTimeoutLocked(r); + } } void awakeFromSleepingLocked() { diff --git a/services/java/com/android/server/input/InputManagerService.java b/services/java/com/android/server/input/InputManagerService.java index 7b4c077..d749e6c 100644 --- a/services/java/com/android/server/input/InputManagerService.java +++ b/services/java/com/android/server/input/InputManagerService.java @@ -1292,8 +1292,9 @@ public class InputManagerService extends IInputManager.Stub // Native callback. private long notifyANR(InputApplicationHandle inputApplicationHandle, - InputWindowHandle inputWindowHandle) { - return mWindowManagerCallbacks.notifyANR(inputApplicationHandle, inputWindowHandle); + InputWindowHandle inputWindowHandle, String reason) { + return mWindowManagerCallbacks.notifyANR( + inputApplicationHandle, inputWindowHandle, reason); } // Native callback. @@ -1477,7 +1478,7 @@ public class InputManagerService extends IInputManager.Stub public void notifyInputChannelBroken(InputWindowHandle inputWindowHandle); public long notifyANR(InputApplicationHandle inputApplicationHandle, - InputWindowHandle inputWindowHandle); + InputWindowHandle inputWindowHandle, String reason); public int interceptKeyBeforeQueueing(KeyEvent event, int policyFlags, boolean isScreenOn); diff --git a/services/java/com/android/server/pm/Settings.java b/services/java/com/android/server/pm/Settings.java index 415cda1..92026b2 100644 --- a/services/java/com/android/server/pm/Settings.java +++ b/services/java/com/android/server/pm/Settings.java @@ -1395,9 +1395,8 @@ final class Settings { final boolean isDebug = (ai.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0; final int[] gids = pkg.getGids(); - // Avoid any application that has a space in its path - // or that is handled by the system. - if (dataPath.indexOf(" ") >= 0 || ai.uid < Process.FIRST_APPLICATION_UID) + // Avoid any application that has a space in its path. + if (dataPath.indexOf(" ") >= 0) continue; // we store on each line the following information for now: diff --git a/services/java/com/android/server/power/WirelessChargerDetector.java b/services/java/com/android/server/power/WirelessChargerDetector.java index ac6dc3e..35920f7 100644 --- a/services/java/com/android/server/power/WirelessChargerDetector.java +++ b/services/java/com/android/server/power/WirelessChargerDetector.java @@ -21,6 +21,7 @@ import android.hardware.SensorEvent; import android.hardware.SensorEventListener; import android.hardware.SensorManager; import android.os.BatteryManager; +import android.os.SystemClock; import android.util.Slog; import java.io.PrintWriter; @@ -130,6 +131,10 @@ final class WirelessChargerDetector { private long mFirstSampleTime; private float mFirstSampleX, mFirstSampleY, mFirstSampleZ; + // The time and value of the last sample that was collected (for debugging only). + private long mLastSampleTime; + private float mLastSampleX, mLastSampleY, mLastSampleZ; + public WirelessChargerDetector(SensorManager sensorManager, SuspendBlocker suspendBlocker) { mSensorManager = sensorManager; @@ -153,6 +158,9 @@ final class WirelessChargerDetector { pw.println(" mFirstSampleTime=" + mFirstSampleTime); pw.println(" mFirstSampleX=" + mFirstSampleX + ", mFirstSampleY=" + mFirstSampleY + ", mFirstSampleZ=" + mFirstSampleZ); + pw.println(" mLastSampleTime=" + mLastSampleTime); + pw.println(" mLastSampleX=" + mLastSampleX + + ", mLastSampleY=" + mLastSampleY + ", mLastSampleZ=" + mLastSampleZ); } } @@ -224,6 +232,11 @@ final class WirelessChargerDetector { return; } + mLastSampleTime = timeNanos; + mLastSampleX = x; + mLastSampleY = y; + mLastSampleZ = z; + mTotalSamples += 1; if (mTotalSamples == 1) { // Save information about the first sample collected. @@ -310,7 +323,10 @@ final class WirelessChargerDetector { private final SensorEventListener mListener = new SensorEventListener() { @Override public void onSensorChanged(SensorEvent event) { - processSample(event.timestamp, event.values[0], event.values[1], event.values[2]); + // We use SystemClock.elapsedRealtimeNanos() instead of event.timestamp because + // on some devices the sensor HAL may produce timestamps that are not monotonic. + processSample(SystemClock.elapsedRealtimeNanos(), + event.values[0], event.values[1], event.values[2]); } @Override diff --git a/services/java/com/android/server/wm/InputMonitor.java b/services/java/com/android/server/wm/InputMonitor.java index 9620612..ea3af263 100644 --- a/services/java/com/android/server/wm/InputMonitor.java +++ b/services/java/com/android/server/wm/InputMonitor.java @@ -88,7 +88,7 @@ final class InputMonitor implements InputManagerService.WindowManagerCallbacks { */ @Override public long notifyANR(InputApplicationHandle inputApplicationHandle, - InputWindowHandle inputWindowHandle) { + InputWindowHandle inputWindowHandle, String reason) { AppWindowToken appWindowToken = null; WindowState windowState = null; boolean aboveSystem = false; @@ -105,7 +105,8 @@ final class InputMonitor implements InputManagerService.WindowManagerCallbacks { if (windowState != null) { Slog.i(WindowManagerService.TAG, "Input event dispatching timed out " - + "sending to " + windowState.mAttrs.getTitle()); + + "sending to " + windowState.mAttrs.getTitle() + + ". Reason: " + reason); // Figure out whether this window is layered above system windows. // We need to do this here to help the activity manager know how to // layer its ANR dialog. @@ -114,19 +115,21 @@ final class InputMonitor implements InputManagerService.WindowManagerCallbacks { aboveSystem = windowState.mBaseLayer > systemAlertLayer; } else if (appWindowToken != null) { Slog.i(WindowManagerService.TAG, "Input event dispatching timed out " - + "sending to application " + appWindowToken.stringName); + + "sending to application " + appWindowToken.stringName + + ". Reason: " + reason); } else { - Slog.i(WindowManagerService.TAG, "Input event dispatching timed out."); + Slog.i(WindowManagerService.TAG, "Input event dispatching timed out " + + ". Reason: " + reason); } - mService.saveANRStateLocked(appWindowToken, windowState); + mService.saveANRStateLocked(appWindowToken, windowState, reason); } if (appWindowToken != null && appWindowToken.appToken != null) { try { // Notify the activity manager about the timeout and let it decide whether // to abort dispatching or keep waiting. - boolean abort = appWindowToken.appToken.keyDispatchingTimedOut(); + boolean abort = appWindowToken.appToken.keyDispatchingTimedOut(reason); if (! abort) { // The activity manager declined to abort dispatching. // Wait a bit longer and timeout again later. @@ -139,7 +142,7 @@ final class InputMonitor implements InputManagerService.WindowManagerCallbacks { // Notify the activity manager about the timeout and let it decide whether // to abort dispatching or keep waiting. long timeout = ActivityManagerNative.getDefault().inputDispatchingTimedOut( - windowState.mSession.mPid, aboveSystem); + windowState.mSession.mPid, aboveSystem, reason); if (timeout >= 0) { // The activity manager declined to abort dispatching. // Wait a bit longer and timeout again later. diff --git a/services/java/com/android/server/wm/WindowManagerService.java b/services/java/com/android/server/wm/WindowManagerService.java index 34d8973..b8d2050 100644 --- a/services/java/com/android/server/wm/WindowManagerService.java +++ b/services/java/com/android/server/wm/WindowManagerService.java @@ -10541,8 +10541,10 @@ public class WindowManagerService extends IWindowManager.Stub * * @param appWindowToken The application that ANR'd, may be null. * @param windowState The window that ANR'd, may be null. + * @param reason The reason for the ANR, may be null. */ - public void saveANRStateLocked(AppWindowToken appWindowToken, WindowState windowState) { + public void saveANRStateLocked(AppWindowToken appWindowToken, WindowState windowState, + String reason) { StringWriter sw = new StringWriter(); PrintWriter pw = new FastPrintWriter(sw, false, 1024); pw.println(" ANR time: " + DateFormat.getInstance().format(new Date())); @@ -10552,6 +10554,9 @@ public class WindowManagerService extends IWindowManager.Stub if (windowState != null) { pw.println(" Window at fault: " + windowState.mAttrs.getTitle()); } + if (reason != null) { + pw.println(" Reason: " + reason); + } pw.println(); dumpWindowsNoHeaderLocked(pw, true, null); pw.close(); diff --git a/services/jni/com_android_server_input_InputManagerService.cpp b/services/jni/com_android_server_input_InputManagerService.cpp index 09e5be4..d8b8b94 100644 --- a/services/jni/com_android_server_input_InputManagerService.cpp +++ b/services/jni/com_android_server_input_InputManagerService.cpp @@ -191,7 +191,8 @@ public: uint32_t policyFlags); virtual void notifyConfigurationChanged(nsecs_t when); virtual nsecs_t notifyANR(const sp<InputApplicationHandle>& inputApplicationHandle, - const sp<InputWindowHandle>& inputWindowHandle); + const sp<InputWindowHandle>& inputWindowHandle, + const String8& reason); virtual void notifyInputChannelBroken(const sp<InputWindowHandle>& inputWindowHandle); virtual bool filterInputEvent(const InputEvent* inputEvent, uint32_t policyFlags); virtual void getDispatcherConfiguration(InputDispatcherConfiguration* outConfig); @@ -553,7 +554,7 @@ void NativeInputManager::notifyConfigurationChanged(nsecs_t when) { } nsecs_t NativeInputManager::notifyANR(const sp<InputApplicationHandle>& inputApplicationHandle, - const sp<InputWindowHandle>& inputWindowHandle) { + const sp<InputWindowHandle>& inputWindowHandle, const String8& reason) { #if DEBUG_INPUT_DISPATCHER_POLICY ALOGD("notifyANR"); #endif @@ -564,15 +565,18 @@ nsecs_t NativeInputManager::notifyANR(const sp<InputApplicationHandle>& inputApp getInputApplicationHandleObjLocalRef(env, inputApplicationHandle); jobject inputWindowHandleObj = getInputWindowHandleObjLocalRef(env, inputWindowHandle); + jstring reasonObj = env->NewStringUTF(reason.string()); jlong newTimeout = env->CallLongMethod(mServiceObj, - gServiceClassInfo.notifyANR, inputApplicationHandleObj, inputWindowHandleObj); + gServiceClassInfo.notifyANR, inputApplicationHandleObj, inputWindowHandleObj, + reasonObj); if (checkAndClearExceptionFromCallback(env, "notifyANR")) { newTimeout = 0; // abort dispatch } else { assert(newTimeout >= 0); } + env->DeleteLocalRef(reasonObj); env->DeleteLocalRef(inputWindowHandleObj); env->DeleteLocalRef(inputApplicationHandleObj); return newTimeout; @@ -1379,7 +1383,7 @@ int register_android_server_InputManager(JNIEnv* env) { GET_METHOD_ID(gServiceClassInfo.notifyANR, clazz, "notifyANR", - "(Lcom/android/server/input/InputApplicationHandle;Lcom/android/server/input/InputWindowHandle;)J"); + "(Lcom/android/server/input/InputApplicationHandle;Lcom/android/server/input/InputWindowHandle;Ljava/lang/String;)J"); GET_METHOD_ID(gServiceClassInfo.filterInputEvent, clazz, "filterInputEvent", "(Landroid/view/InputEvent;I)Z"); diff --git a/wifi/java/android/net/wifi/WifiNative.java b/wifi/java/android/net/wifi/WifiNative.java index 83789e2..5626192 100644 --- a/wifi/java/android/net/wifi/WifiNative.java +++ b/wifi/java/android/net/wifi/WifiNative.java @@ -25,6 +25,7 @@ import android.util.Log; import java.util.ArrayList; import java.util.List; +import java.util.Locale; /** * Native calls for bring up/shut down of the supplicant daemon and for @@ -457,7 +458,7 @@ public class WifiNative { } public boolean setCountryCode(String countryCode) { - return doBooleanCommand("DRIVER COUNTRY " + countryCode); + return doBooleanCommand("DRIVER COUNTRY " + countryCode.toUpperCase(Locale.ROOT)); } public void enableBackgroundScan(boolean enable) { diff --git a/wifi/java/android/net/wifi/WifiStateMachine.java b/wifi/java/android/net/wifi/WifiStateMachine.java index 3ccdbea..2bc22f2 100644 --- a/wifi/java/android/net/wifi/WifiStateMachine.java +++ b/wifi/java/android/net/wifi/WifiStateMachine.java @@ -88,7 +88,6 @@ import java.net.InetAddress; import java.net.Inet6Address; import java.util.ArrayList; import java.util.List; -import java.util.Locale; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicBoolean; import java.util.Iterator; @@ -1431,6 +1430,7 @@ public class WifiStateMachine extends StateMachine { countryCode); } sendMessage(CMD_SET_COUNTRY_CODE, countryCode); + mWifiP2pChannel.sendMessage(WifiP2pService.SET_COUNTRY_CODE, countryCode); } /** @@ -2952,7 +2952,7 @@ public class WifiStateMachine extends StateMachine { case CMD_SET_COUNTRY_CODE: String country = (String) message.obj; if (DBG) log("set country code " + country); - if (!mWifiNative.setCountryCode(country.toUpperCase(Locale.ROOT))) { + if (!mWifiNative.setCountryCode(country)) { loge("Failed to set country code " + country); } break; @@ -4256,7 +4256,7 @@ public class WifiStateMachine extends StateMachine { /** * arg2 on the source message has a unique id that needs to be retained in replies * to match the request - * + * see WifiManager for details */ private Message obtainMessageWithArg2(Message srcMsg) { diff --git a/wifi/java/android/net/wifi/p2p/WifiP2pService.java b/wifi/java/android/net/wifi/p2p/WifiP2pService.java index 05196b8..625ffb8 100644 --- a/wifi/java/android/net/wifi/p2p/WifiP2pService.java +++ b/wifi/java/android/net/wifi/p2p/WifiP2pService.java @@ -174,6 +174,9 @@ public class WifiP2pService extends IWifiP2pManager.Stub { // msg.obj = StateMachine to send to when blocked public static final int BLOCK_DISCOVERY = BASE + 15; + // set country code + public static final int SET_COUNTRY_CODE = BASE + 16; + public static final int ENABLED = 1; public static final int DISABLED = 0; @@ -632,6 +635,7 @@ public class WifiP2pService extends IWifiP2pManager.Stub { case WifiP2pManager.START_LISTEN: case WifiP2pManager.STOP_LISTEN: case WifiP2pManager.SET_CHANNEL: + case SET_COUNTRY_CODE: break; case WifiStateMachine.CMD_ENABLE_P2P: // Enable is lazy and has no response @@ -1064,6 +1068,10 @@ public class WifiP2pService extends IWifiP2pManager.Stub { replyToMessage(message, WifiP2pManager.SET_CHANNEL_FAILED); } break; + case SET_COUNTRY_CODE: + String countryCode = (String) message.obj; + mWifiNative.setCountryCode(countryCode); + break; default: return NOT_HANDLED; } @@ -2537,6 +2545,12 @@ public class WifiP2pService extends IWifiP2pManager.Stub { mServiceTransactionId = 0; mServiceDiscReqId = null; + String countryCode = Settings.Global.getString(mContext.getContentResolver(), + Settings.Global.WIFI_COUNTRY_CODE); + if (countryCode != null && !countryCode.isEmpty()) { + mP2pStateMachine.sendMessage(SET_COUNTRY_CODE, countryCode); + } + updatePersistentNetworks(RELOAD); } |