summaryrefslogtreecommitdiffstats
path: root/core/java/android
diff options
context:
space:
mode:
Diffstat (limited to 'core/java/android')
-rw-r--r--core/java/android/alsa/AlsaCardsParser.java198
-rw-r--r--core/java/android/alsa/AlsaDevicesParser.java173
-rw-r--r--core/java/android/animation/AnimatorInflater.java399
-rw-r--r--core/java/android/animation/AnimatorSet.java12
-rw-r--r--core/java/android/animation/ObjectAnimator.java21
-rw-r--r--core/java/android/animation/ValueAnimator.java16
-rw-r--r--core/java/android/app/Activity.java31
-rw-r--r--core/java/android/app/ActivityManager.java3
-rw-r--r--core/java/android/app/ActivityManagerNative.java119
-rw-r--r--core/java/android/app/ActivityThread.java100
-rw-r--r--core/java/android/app/ActivityView.java2
-rw-r--r--core/java/android/app/ApplicationThreadNative.java59
-rw-r--r--core/java/android/app/AssistData.java519
-rw-r--r--core/java/android/app/ContextImpl.java43
-rw-r--r--core/java/android/app/DatePickerDialog.java3
-rw-r--r--core/java/android/app/Dialog.java18
-rw-r--r--core/java/android/app/Fragment.java1
-rw-r--r--core/java/android/app/FragmentManager.java2
-rw-r--r--core/java/android/app/IActivityContainer.aidl1
-rw-r--r--core/java/android/app/IActivityManager.java14
-rw-r--r--core/java/android/app/IApplicationThread.java18
-rw-r--r--core/java/android/app/Notification.java397
-rw-r--r--core/java/android/app/ResourcesManager.java48
-rw-r--r--core/java/android/app/SharedPreferencesImpl.java11
-rw-r--r--core/java/android/app/VoiceInteractor.java17
-rw-r--r--core/java/android/app/admin/DevicePolicyManager.java95
-rw-r--r--core/java/android/app/admin/IDevicePolicyManager.aidl40
-rw-r--r--core/java/android/appwidget/AppWidgetHost.java50
-rw-r--r--core/java/android/appwidget/AppWidgetHostView.java5
-rw-r--r--core/java/android/bluetooth/BluetoothA2dp.java12
-rw-r--r--core/java/android/bluetooth/BluetoothHeadsetClientCall.java25
-rw-r--r--core/java/android/content/ContentProviderOperation.java16
-rw-r--r--core/java/android/content/Context.java57
-rw-r--r--core/java/android/content/Intent.java4
-rw-r--r--core/java/android/content/SharedPreferences.java12
-rw-r--r--core/java/android/content/pm/ActivityInfo.java10
-rw-r--r--core/java/android/content/pm/LauncherActivityInfo.java40
-rw-r--r--core/java/android/content/pm/PackageParser.java154
-rw-r--r--core/java/android/content/pm/RegisteredServicesCache.java46
-rw-r--r--core/java/android/content/res/ColorStateList.java400
-rw-r--r--core/java/android/content/res/Resources.java329
-rw-r--r--core/java/android/content/res/ResourcesKey.java29
-rw-r--r--core/java/android/content/res/TypedArray.java351
-rw-r--r--core/java/android/hardware/camera2/CameraAccessException.java10
-rw-r--r--core/java/android/hardware/camera2/CameraCharacteristics.java153
-rw-r--r--core/java/android/hardware/camera2/CameraManager.java146
-rw-r--r--core/java/android/hardware/camera2/CameraMetadata.java154
-rw-r--r--core/java/android/hardware/camera2/CaptureRequest.java160
-rw-r--r--core/java/android/hardware/camera2/CaptureResult.java214
-rw-r--r--core/java/android/hardware/camera2/legacy/LegacyMetadataMapper.java58
-rw-r--r--core/java/android/hardware/camera2/legacy/RequestThreadManager.java2
-rw-r--r--core/java/android/hardware/camera2/params/LensShadingMap.java45
-rw-r--r--core/java/android/hardware/usb/UsbDevice.java1
-rw-r--r--core/java/android/hardware/usb/UsbManager.java10
-rw-r--r--core/java/android/midi/IMidiDeviceServer.aidl26
-rw-r--r--core/java/android/midi/IMidiListener.aidl26
-rw-r--r--core/java/android/midi/IMidiManager.aidl41
-rw-r--r--core/java/android/midi/MidiDevice.java102
-rw-r--r--core/java/android/midi/MidiDeviceInfo.aidl19
-rw-r--r--core/java/android/midi/MidiDeviceInfo.java204
-rw-r--r--core/java/android/midi/MidiDeviceServer.java268
-rw-r--r--core/java/android/midi/MidiInputPort.java80
-rw-r--r--core/java/android/midi/MidiManager.java196
-rw-r--r--core/java/android/midi/MidiOutputPort.java135
-rw-r--r--core/java/android/midi/MidiPort.java131
-rw-r--r--core/java/android/midi/MidiReceiver.java44
-rw-r--r--core/java/android/midi/MidiSender.java40
-rw-r--r--core/java/android/net/ConnectivityManager.java2
-rw-r--r--core/java/android/net/PacProxySelector.java5
-rw-r--r--core/java/android/net/SSLCertificateSocketFactory.java2
-rw-r--r--core/java/android/net/Uri.java9
-rw-r--r--core/java/android/os/Build.java77
-rw-r--r--core/java/android/os/Debug.java3
-rw-r--r--core/java/android/os/IProcessInfoService.aidl29
-rw-r--r--core/java/android/os/ParcelFileDescriptor.java18
-rw-r--r--core/java/android/os/StrictMode.java74
-rw-r--r--core/java/android/print/PrintAttributes.java109
-rw-r--r--core/java/android/print/PrinterCapabilitiesInfo.java91
-rw-r--r--core/java/android/printservice/PrintService.java4
-rw-r--r--core/java/android/provider/Contacts.java49
-rw-r--r--core/java/android/provider/ContactsContract.java272
-rw-r--r--core/java/android/provider/Settings.java612
-rw-r--r--core/java/android/service/dreams/DreamService.java6
-rw-r--r--core/java/android/service/voice/VoiceInteractionService.java9
-rw-r--r--core/java/android/service/voice/VoiceInteractionSession.java77
-rw-r--r--core/java/android/speech/tts/ITextToSpeechCallback.aidl2
-rw-r--r--core/java/android/speech/tts/TextToSpeech.java4
-rw-r--r--core/java/android/speech/tts/TextToSpeechService.java79
-rw-r--r--core/java/android/speech/tts/UtteranceProgressListener.java19
-rw-r--r--core/java/android/text/StaticLayout.java366
-rw-r--r--core/java/android/text/util/Rfc822Token.java14
-rw-r--r--core/java/android/transition/ChangeScroll.java2
-rw-r--r--core/java/android/transition/TransitionManager.java27
-rw-r--r--core/java/android/util/AtomicFile.java2
-rw-r--r--core/java/android/util/AttributeSet.java2
-rw-r--r--core/java/android/util/Spline.java2
-rw-r--r--core/java/android/util/StateSet.java83
-rw-r--r--core/java/android/view/ActionMode.java32
-rw-r--r--core/java/android/view/ContextThemeWrapper.java14
-rw-r--r--core/java/android/view/GLES20Canvas.java791
-rw-r--r--core/java/android/view/GLES20RecordingCanvas.java2
-rw-r--r--core/java/android/view/HardwareCanvas.java25
-rw-r--r--core/java/android/view/HardwareLayer.java2
-rw-r--r--core/java/android/view/IWindowManager.aidl2
-rw-r--r--core/java/android/view/IWindowSession.aidl3
-rw-r--r--core/java/android/view/LayoutInflater.java100
-rw-r--r--core/java/android/view/PhoneFallbackEventHandler.java288
-rw-r--r--core/java/android/view/PhoneLayoutInflater.java73
-rw-r--r--core/java/android/view/PhoneWindow.java4778
-rw-r--r--core/java/android/view/RenderNode.java2
-rw-r--r--core/java/android/view/Surface.java1
-rw-r--r--core/java/android/view/View.java503
-rw-r--r--core/java/android/view/ViewAssistData.java32
-rw-r--r--core/java/android/view/ViewGroup.java21
-rw-r--r--core/java/android/view/ViewRootImpl.java20
-rw-r--r--core/java/android/view/ViewStub.java25
-rw-r--r--core/java/android/view/Window.java7
-rw-r--r--core/java/android/view/WindowManager.java29
-rw-r--r--core/java/android/view/WindowManagerGlobal.java41
-rw-r--r--core/java/android/view/WindowManagerPolicy.java7
-rw-r--r--core/java/android/view/accessibility/AccessibilityNodeInfo.java2
-rw-r--r--core/java/android/view/inputmethod/InputMethodSubtypeArray.java59
-rw-r--r--core/java/android/webkit/CookieManager.java5
-rw-r--r--core/java/android/webkit/WebChromeClient.java24
-rw-r--r--core/java/android/webkit/WebResourceRequest.java2
-rw-r--r--core/java/android/webkit/WebResourceResponse.java18
-rw-r--r--core/java/android/webkit/WebView.java67
-rw-r--r--core/java/android/webkit/WebViewClient.java4
-rw-r--r--core/java/android/webkit/WebViewFactory.java8
-rw-r--r--core/java/android/widget/AbsListView.java44
-rw-r--r--core/java/android/widget/AbsSeekBar.java20
-rw-r--r--core/java/android/widget/AbsSpinner.java20
-rw-r--r--core/java/android/widget/ActionMenuPresenter.java5
-rw-r--r--core/java/android/widget/ActionMenuView.java14
-rw-r--r--core/java/android/widget/AdapterView.java25
-rw-r--r--core/java/android/widget/AdapterViewAnimator.java11
-rw-r--r--core/java/android/widget/AdapterViewFlipper.java11
-rw-r--r--core/java/android/widget/ArrayAdapter.java71
-rw-r--r--core/java/android/widget/Button.java11
-rw-r--r--core/java/android/widget/CalendarView.java9
-rw-r--r--core/java/android/widget/CheckBox.java11
-rw-r--r--core/java/android/widget/CheckedTextView.java17
-rw-r--r--core/java/android/widget/Chronometer.java11
-rw-r--r--core/java/android/widget/CompoundButton.java17
-rw-r--r--core/java/android/widget/CursorAdapter.java46
-rw-r--r--core/java/android/widget/DatePicker.java31
-rw-r--r--core/java/android/widget/DatePickerCalendarDelegate.java51
-rw-r--r--core/java/android/widget/DayPickerView.java21
-rw-r--r--core/java/android/widget/DigitalClock.java12
-rw-r--r--core/java/android/widget/EditText.java16
-rw-r--r--core/java/android/widget/Editor.java2
-rw-r--r--core/java/android/widget/ExpandableListView.java11
-rw-r--r--core/java/android/widget/FrameLayout.java27
-rw-r--r--core/java/android/widget/Gallery.java16
-rw-r--r--core/java/android/widget/GridLayout.java11
-rw-r--r--core/java/android/widget/GridView.java11
-rw-r--r--core/java/android/widget/HorizontalScrollView.java22
-rw-r--r--core/java/android/widget/ImageButton.java11
-rw-r--r--core/java/android/widget/ImageSwitcher.java11
-rw-r--r--core/java/android/widget/ImageView.java23
-rw-r--r--core/java/android/widget/LinearLayout.java11
-rw-r--r--core/java/android/widget/ListView.java11
-rw-r--r--core/java/android/widget/MediaController.java16
-rw-r--r--core/java/android/widget/MultiAutoCompleteTextView.java11
-rw-r--r--core/java/android/widget/NumberPicker.java5
-rw-r--r--core/java/android/widget/PopupMenu.java23
-rw-r--r--core/java/android/widget/PopupWindow.java721
-rw-r--r--core/java/android/widget/ProgressBar.java118
-rw-r--r--core/java/android/widget/QuickContactBadge.java11
-rw-r--r--core/java/android/widget/RadialTimePickerView.java618
-rw-r--r--core/java/android/widget/RadioButton.java11
-rw-r--r--core/java/android/widget/RadioGroup.java11
-rw-r--r--core/java/android/widget/RatingBar.java81
-rw-r--r--core/java/android/widget/RelativeLayout.java117
-rw-r--r--core/java/android/widget/ResourceCursorAdapter.java41
-rw-r--r--core/java/android/widget/ScrollBarDrawable.java229
-rw-r--r--core/java/android/widget/ScrollView.java22
-rw-r--r--core/java/android/widget/SearchView.java11
-rw-r--r--core/java/android/widget/SeekBar.java43
-rw-r--r--core/java/android/widget/SimpleAdapter.java55
-rw-r--r--core/java/android/widget/SimpleMonthAdapter.java13
-rw-r--r--core/java/android/widget/SimpleMonthView.java247
-rw-r--r--core/java/android/widget/SlidingDrawer.java11
-rw-r--r--core/java/android/widget/Spinner.java229
-rw-r--r--core/java/android/widget/SpinnerAdapter.java13
-rw-r--r--core/java/android/widget/StackView.java16
-rw-r--r--core/java/android/widget/SuggestionsAdapter.java39
-rw-r--r--core/java/android/widget/Switch.java206
-rw-r--r--core/java/android/widget/TabHost.java14
-rw-r--r--core/java/android/widget/TabWidget.java25
-rw-r--r--core/java/android/widget/TableLayout.java11
-rw-r--r--core/java/android/widget/TableRow.java11
-rw-r--r--core/java/android/widget/TextSwitcher.java11
-rw-r--r--core/java/android/widget/TextView.java464
-rw-r--r--core/java/android/widget/TextViewWithCircularIndicator.java43
-rw-r--r--core/java/android/widget/TimePicker.java24
-rw-r--r--core/java/android/widget/TimePickerClockDelegate.java10
-rw-r--r--core/java/android/widget/TimePickerSpinnerDelegate.java10
-rw-r--r--core/java/android/widget/ToggleButton.java11
-rw-r--r--core/java/android/widget/TwoLineListItem.java11
-rw-r--r--core/java/android/widget/VideoView.java11
-rw-r--r--core/java/android/widget/ViewAnimator.java11
-rw-r--r--core/java/android/widget/ViewFlipper.java11
-rw-r--r--core/java/android/widget/ViewSwitcher.java11
-rw-r--r--core/java/android/widget/YearPickerView.java45
-rw-r--r--core/java/android/widget/ZoomButton.java11
-rw-r--r--core/java/android/widget/ZoomControls.java11
207 files changed, 14842 insertions, 4675 deletions
diff --git a/core/java/android/alsa/AlsaCardsParser.java b/core/java/android/alsa/AlsaCardsParser.java
index 8b44881..26a61ae 100644
--- a/core/java/android/alsa/AlsaCardsParser.java
+++ b/core/java/android/alsa/AlsaCardsParser.java
@@ -22,46 +22,69 @@ import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
-import java.util.Vector;
+import java.util.ArrayList;
/**
* @hide Retrieves information from an ALSA "cards" file.
*/
public class AlsaCardsParser {
private static final String TAG = "AlsaCardsParser";
+ protected static final boolean DEBUG = true;
- private static LineTokenizer tokenizer_ = new LineTokenizer(" :[]");
+ private static final String kCardsFilePath = "/proc/asound/cards";
+
+ private static LineTokenizer mTokenizer = new LineTokenizer(" :[]");
+
+ private ArrayList<AlsaCardRecord> mCardRecords = new ArrayList<AlsaCardRecord>();
public class AlsaCardRecord {
+ private static final String TAG = "AlsaCardRecord";
+ private static final String kUsbCardKeyStr = "at usb-";
+
public int mCardNum = -1;
public String mField1 = "";
public String mCardName = "";
public String mCardDescription = "";
+ public boolean mIsUsb = false;
public AlsaCardRecord() {}
public boolean parse(String line, int lineIndex) {
int tokenIndex = 0;
int delimIndex = 0;
+
if (lineIndex == 0) {
// line # (skip)
- tokenIndex = tokenizer_.nextToken(line, tokenIndex);
- delimIndex = tokenizer_.nextDelimiter(line, tokenIndex);
+ tokenIndex = mTokenizer.nextToken(line, tokenIndex);
+ delimIndex = mTokenizer.nextDelimiter(line, tokenIndex);
+
+ try {
+ // mCardNum
+ mCardNum = Integer.parseInt(line.substring(tokenIndex, delimIndex));
+ } catch (NumberFormatException e) {
+ Slog.e(TAG, "Failed to parse line " + lineIndex + " of " + kCardsFilePath
+ + ": " + line.substring(tokenIndex, delimIndex));
+ return false;
+ }
// mField1
- tokenIndex = tokenizer_.nextToken(line, delimIndex);
- delimIndex = tokenizer_.nextDelimiter(line, tokenIndex);
+ tokenIndex = mTokenizer.nextToken(line, delimIndex);
+ delimIndex = mTokenizer.nextDelimiter(line, tokenIndex);
mField1 = line.substring(tokenIndex, delimIndex);
// mCardName
- tokenIndex = tokenizer_.nextToken(line, delimIndex);
- // delimIndex = tokenizer_.nextDelimiter(line, tokenIndex);
+ tokenIndex = mTokenizer.nextToken(line, delimIndex);
mCardName = line.substring(tokenIndex);
+
// done
} else if (lineIndex == 1) {
- tokenIndex = tokenizer_.nextToken(line, 0);
+ tokenIndex = mTokenizer.nextToken(line, 0);
if (tokenIndex != -1) {
- mCardDescription = line.substring(tokenIndex);
+ int keyIndex = line.indexOf(kUsbCardKeyStr);
+ mIsUsb = keyIndex != -1;
+ if (mIsUsb) {
+ mCardDescription = line.substring(tokenIndex, keyIndex - 1);
+ }
}
}
@@ -73,44 +96,127 @@ public class AlsaCardsParser {
}
}
- private Vector<AlsaCardRecord> cardRecords_ = new Vector<AlsaCardRecord>();
+ public AlsaCardsParser() {}
public void scan() {
- cardRecords_.clear();
- final String cardsFilePath = "/proc/asound/cards";
- File cardsFile = new File(cardsFilePath);
- try {
- FileReader reader = new FileReader(cardsFile);
- BufferedReader bufferedReader = new BufferedReader(reader);
- String line = "";
- while ((line = bufferedReader.readLine()) != null) {
- AlsaCardRecord cardRecord = new AlsaCardRecord();
- cardRecord.parse(line, 0);
- cardRecord.parse(line = bufferedReader.readLine(), 1);
- cardRecords_.add(cardRecord);
- }
- reader.close();
- } catch (FileNotFoundException e) {
- e.printStackTrace();
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
-
- public AlsaCardRecord getCardRecordAt(int index) {
- return cardRecords_.get(index);
- }
-
- public int getNumCardRecords() {
- return cardRecords_.size();
- }
-
- public void Log() {
- int numCardRecs = getNumCardRecords();
- for (int index = 0; index < numCardRecs; ++index) {
- Slog.w(TAG, "usb:" + getCardRecordAt(index).textFormat());
- }
+ if (DEBUG) {
+ Slog.i(TAG, "AlsaCardsParser.scan()");
+ }
+ mCardRecords = new ArrayList<AlsaCardRecord>();
+
+ File cardsFile = new File(kCardsFilePath);
+ try {
+ FileReader reader = new FileReader(cardsFile);
+ BufferedReader bufferedReader = new BufferedReader(reader);
+ String line = "";
+ while ((line = bufferedReader.readLine()) != null) {
+ AlsaCardRecord cardRecord = new AlsaCardRecord();
+ if (DEBUG) {
+ Slog.i(TAG, " " + line);
+ }
+ cardRecord.parse(line, 0);
+
+ line = bufferedReader.readLine();
+ if (DEBUG) {
+ Slog.i(TAG, " " + line);
+ }
+ cardRecord.parse(line, 1);
+
+ mCardRecords.add(cardRecord);
+ }
+ reader.close();
+ } catch (FileNotFoundException e) {
+ e.printStackTrace();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
}
- public AlsaCardsParser() {}
+ public ArrayList<AlsaCardRecord> getScanRecords() {
+ return mCardRecords;
+ }
+
+ public AlsaCardRecord getCardRecordAt(int index) {
+ return mCardRecords.get(index);
+ }
+
+ public AlsaCardRecord getCardRecordFor(int cardNum) {
+ for (AlsaCardRecord rec : mCardRecords) {
+ if (rec.mCardNum == cardNum) {
+ return rec;
+ }
+ }
+
+ return null;
+ }
+
+ public int getNumCardRecords() {
+ return mCardRecords.size();
+ }
+
+ public boolean isCardUsb(int cardNum) {
+ for (AlsaCardRecord rec : mCardRecords) {
+ if (rec.mCardNum == cardNum) {
+ return rec.mIsUsb;
+ }
+ }
+
+ return false;
+ }
+
+ // return -1 if none found
+ public int getDefaultUsbCard() {
+ // Choose the most-recently added EXTERNAL card
+ // or return the first added EXTERNAL card?
+ for (AlsaCardRecord rec : mCardRecords) {
+ if (rec.mIsUsb) {
+ return rec.mCardNum;
+ }
+ }
+
+ return -1;
+ }
+
+ public int getDefaultCard() {
+ // return an external card if possible
+ int card = getDefaultUsbCard();
+
+ if (card < 0 && getNumCardRecords() > 0) {
+ // otherwise return the (internal) card with the highest number
+ card = getCardRecordAt(getNumCardRecords() - 1).mCardNum;
+ }
+ return card;
+ }
+
+ static public boolean hasCardNumber(ArrayList<AlsaCardRecord> recs, int cardNum) {
+ for (AlsaCardRecord cardRec : recs) {
+ if (cardRec.mCardNum == cardNum) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public ArrayList<AlsaCardRecord> getNewCardRecords(ArrayList<AlsaCardRecord> prevScanRecs) {
+ ArrayList<AlsaCardRecord> newRecs = new ArrayList<AlsaCardRecord>();
+ for (AlsaCardRecord rec : mCardRecords) {
+ // now scan to see if this card number is in the previous scan list
+ if (!hasCardNumber(prevScanRecs, rec.mCardNum)) {
+ newRecs.add(rec);
+ }
+ }
+ return newRecs;
+ }
+
+ //
+ // Logging
+ //
+ public void Log(String heading) {
+ if (DEBUG) {
+ Slog.i(TAG, heading);
+ for (AlsaCardRecord cardRec : mCardRecords) {
+ Slog.i(TAG, cardRec.textFormat());
+ }
+ }
+ }
}
diff --git a/core/java/android/alsa/AlsaDevicesParser.java b/core/java/android/alsa/AlsaDevicesParser.java
index 82cc1ae..b140d3d 100644
--- a/core/java/android/alsa/AlsaDevicesParser.java
+++ b/core/java/android/alsa/AlsaDevicesParser.java
@@ -21,7 +21,7 @@ import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
-import java.util.Vector;
+import java.util.ArrayList;
/**
* @hide
@@ -29,6 +29,9 @@ import java.util.Vector;
*/
public class AlsaDevicesParser {
private static final String TAG = "AlsaDevicesParser";
+ protected static final boolean DEBUG = false;
+
+ private static final String kDevicesFilePath = "/proc/asound/devices";
private static final int kIndex_CardDeviceField = 5;
private static final int kStartIndex_CardNum = 6;
@@ -58,8 +61,7 @@ public class AlsaDevicesParser {
int mDeviceType = kDeviceType_Unknown;
int mDeviceDir = kDeviceDir_Unknown;
- public AlsaDeviceRecord() {
- }
+ public AlsaDeviceRecord() {}
public boolean parse(String line) {
// "0123456789012345678901234567890"
@@ -89,51 +91,57 @@ public class AlsaDevicesParser {
}
String token = line.substring(tokenOffset, delimOffset);
- switch (tokenIndex) {
- case kToken_LineNum:
- // ignore
- break;
-
- case kToken_CardNum:
- mCardNum = Integer.parseInt(token);
- if (line.charAt(delimOffset) != '-') {
- tokenIndex++; // no device # in the token stream
- }
- break;
-
- case kToken_DeviceNum:
- mDeviceNum = Integer.parseInt(token);
- break;
-
- case kToken_Type0:
- if (token.equals("digital")) {
- // NOP
- } else if (token.equals("control")) {
- mDeviceType = kDeviceType_Control;
- } else if (token.equals("raw")) {
- // NOP
- }
- break;
-
- case kToken_Type1:
- if (token.equals("audio")) {
- mDeviceType = kDeviceType_Audio;
- } else if (token.equals("midi")) {
- mDeviceType = kDeviceType_MIDI;
- mHasMIDIDevices = true;
- }
- break;
-
- case kToken_Type2:
- if (token.equals("capture")) {
- mDeviceDir = kDeviceDir_Capture;
- mHasCaptureDevices = true;
- } else if (token.equals("playback")) {
- mDeviceDir = kDeviceDir_Playback;
- mHasPlaybackDevices = true;
- }
- break;
- } // switch (tokenIndex)
+ try {
+ switch (tokenIndex) {
+ case kToken_LineNum:
+ // ignore
+ break;
+
+ case kToken_CardNum:
+ mCardNum = Integer.parseInt(token);
+ if (line.charAt(delimOffset) != '-') {
+ tokenIndex++; // no device # in the token stream
+ }
+ break;
+
+ case kToken_DeviceNum:
+ mDeviceNum = Integer.parseInt(token);
+ break;
+
+ case kToken_Type0:
+ if (token.equals("digital")) {
+ // NOP
+ } else if (token.equals("control")) {
+ mDeviceType = kDeviceType_Control;
+ } else if (token.equals("raw")) {
+ // NOP
+ }
+ break;
+
+ case kToken_Type1:
+ if (token.equals("audio")) {
+ mDeviceType = kDeviceType_Audio;
+ } else if (token.equals("midi")) {
+ mDeviceType = kDeviceType_MIDI;
+ mHasMIDIDevices = true;
+ }
+ break;
+
+ case kToken_Type2:
+ if (token.equals("capture")) {
+ mDeviceDir = kDeviceDir_Capture;
+ mHasCaptureDevices = true;
+ } else if (token.equals("playback")) {
+ mDeviceDir = kDeviceDir_Playback;
+ mHasPlaybackDevices = true;
+ }
+ break;
+ } // switch (tokenIndex)
+ } catch (NumberFormatException e) {
+ Slog.e(TAG, "Failed to parse token " + tokenIndex + " of " + kDevicesFilePath
+ + " token: " + token);
+ return false;
+ }
tokenIndex++;
} // while (true)
@@ -176,38 +184,27 @@ public class AlsaDevicesParser {
}
}
- private Vector<AlsaDeviceRecord>
- deviceRecords_ = new Vector<AlsaDeviceRecord>();
+ private ArrayList<AlsaDeviceRecord> mDeviceRecords = new ArrayList<AlsaDeviceRecord>();
- private boolean isLineDeviceRecord(String line) {
- return line.charAt(kIndex_CardDeviceField) == '[';
- }
+ public AlsaDevicesParser() {}
- public AlsaDevicesParser() {
+ //
+ // Access
+ //
+ public int getDefaultDeviceNum(int card) {
+ // TODO - This (obviously) isn't sufficient. Revisit.
+ return 0;
}
- public int getNumDeviceRecords() {
- return deviceRecords_.size();
- }
-
- public AlsaDeviceRecord getDeviceRecordAt(int index) {
- return deviceRecords_.get(index);
- }
-
- public void Log() {
- int numDevRecs = getNumDeviceRecords();
- for (int index = 0; index < numDevRecs; ++index) {
- Slog.w(TAG, "usb:" + getDeviceRecordAt(index).textFormat());
- }
- }
-
- public boolean hasPlaybackDevices() {
+ //
+ // Predicates
+ //
+ public boolean hasPlaybackDevices() {
return mHasPlaybackDevices;
}
public boolean hasPlaybackDevices(int card) {
- for (int index = 0; index < deviceRecords_.size(); index++) {
- AlsaDeviceRecord deviceRecord = deviceRecords_.get(index);
+ for (AlsaDeviceRecord deviceRecord : mDeviceRecords) {
if (deviceRecord.mCardNum == card &&
deviceRecord.mDeviceType == AlsaDeviceRecord.kDeviceType_Audio &&
deviceRecord.mDeviceDir == AlsaDeviceRecord.kDeviceDir_Playback) {
@@ -222,8 +219,7 @@ public class AlsaDevicesParser {
}
public boolean hasCaptureDevices(int card) {
- for (int index = 0; index < deviceRecords_.size(); index++) {
- AlsaDeviceRecord deviceRecord = deviceRecords_.get(index);
+ for (AlsaDeviceRecord deviceRecord : mDeviceRecords) {
if (deviceRecord.mCardNum == card &&
deviceRecord.mDeviceType == AlsaDeviceRecord.kDeviceType_Audio &&
deviceRecord.mDeviceDir == AlsaDeviceRecord.kDeviceDir_Capture) {
@@ -238,8 +234,7 @@ public class AlsaDevicesParser {
}
public boolean hasMIDIDevices(int card) {
- for (int index = 0; index < deviceRecords_.size(); index++) {
- AlsaDeviceRecord deviceRecord = deviceRecords_.get(index);
+ for (AlsaDeviceRecord deviceRecord : mDeviceRecords) {
if (deviceRecord.mCardNum == card &&
deviceRecord.mDeviceType == AlsaDeviceRecord.kDeviceType_MIDI) {
return true;
@@ -248,11 +243,17 @@ public class AlsaDevicesParser {
return false;
}
+ //
+ // Process
+ //
+ private boolean isLineDeviceRecord(String line) {
+ return line.charAt(kIndex_CardDeviceField) == '[';
+ }
+
public void scan() {
- deviceRecords_.clear();
+ mDeviceRecords.clear();
- final String devicesFilePath = "/proc/asound/devices";
- File devicesFile = new File(devicesFilePath);
+ File devicesFile = new File(kDevicesFilePath);
try {
FileReader reader = new FileReader(devicesFile);
BufferedReader bufferedReader = new BufferedReader(reader);
@@ -261,7 +262,7 @@ public class AlsaDevicesParser {
if (isLineDeviceRecord(line)) {
AlsaDeviceRecord deviceRecord = new AlsaDeviceRecord();
deviceRecord.parse(line);
- deviceRecords_.add(deviceRecord);
+ mDeviceRecords.add(deviceRecord);
}
}
reader.close();
@@ -271,5 +272,17 @@ public class AlsaDevicesParser {
e.printStackTrace();
}
}
+
+ //
+ // Loging
+ //
+ public void Log(String heading) {
+ if (DEBUG) {
+ Slog.i(TAG, heading);
+ for (AlsaDeviceRecord deviceRecord : mDeviceRecords) {
+ Slog.i(TAG, deviceRecord.textFormat());
+ }
+ }
+ }
} // class AlsaDevicesParser
diff --git a/core/java/android/animation/AnimatorInflater.java b/core/java/android/animation/AnimatorInflater.java
index 688d7e4..6ef3da8 100644
--- a/core/java/android/animation/AnimatorInflater.java
+++ b/core/java/android/animation/AnimatorInflater.java
@@ -42,6 +42,7 @@ import org.xmlpull.v1.XmlPullParserException;
import java.io.IOException;
import java.util.ArrayList;
+import java.util.List;
/**
* This class is used to instantiate animator XML files into Animator objects.
@@ -66,8 +67,7 @@ public class AnimatorInflater {
private static final int VALUE_TYPE_FLOAT = 0;
private static final int VALUE_TYPE_INT = 1;
private static final int VALUE_TYPE_PATH = 2;
- private static final int VALUE_TYPE_COLOR = 4;
- private static final int VALUE_TYPE_CUSTOM = 5;
+ private static final int VALUE_TYPE_COLOR = 3;
private static final boolean DBG_ANIMATOR_INFLATER = false;
@@ -296,39 +296,50 @@ public class AnimatorInflater {
}
}
- /**
- * @param anim The animator, must not be null
- * @param arrayAnimator Incoming typed array for Animator's attributes.
- * @param arrayObjectAnimator Incoming typed array for Object Animator's
- * attributes.
- * @param pixelSize The relative pixel size, used to calculate the
- * maximum error for path animations.
- */
- private static void parseAnimatorFromTypeArray(ValueAnimator anim,
- TypedArray arrayAnimator, TypedArray arrayObjectAnimator, float pixelSize) {
- long duration = arrayAnimator.getInt(R.styleable.Animator_duration, 300);
-
- long startDelay = arrayAnimator.getInt(R.styleable.Animator_startOffset, 0);
-
- int valueType = arrayAnimator.getInt(R.styleable.Animator_valueType,
- VALUE_TYPE_FLOAT);
-
- TypeEvaluator evaluator = null;
+ private static PropertyValuesHolder getPVH(TypedArray styledAttributes, int valueType,
+ int valueFromId, int valueToId, String propertyName) {
boolean getFloats = (valueType == VALUE_TYPE_FLOAT);
- TypedValue tvFrom = arrayAnimator.peekValue(R.styleable.Animator_valueFrom);
+ TypedValue tvFrom = styledAttributes.peekValue(valueFromId);
boolean hasFrom = (tvFrom != null);
int fromType = hasFrom ? tvFrom.type : 0;
- TypedValue tvTo = arrayAnimator.peekValue(R.styleable.Animator_valueTo);
+ TypedValue tvTo = styledAttributes.peekValue(valueToId);
boolean hasTo = (tvTo != null);
int toType = hasTo ? tvTo.type : 0;
- // TODO: Further clean up this part of code into 4 types : path, color,
- // integer and float.
+ PropertyValuesHolder returnValue = null;
+
if (valueType == VALUE_TYPE_PATH) {
- evaluator = setupAnimatorForPath(anim, arrayAnimator);
+ String fromString = styledAttributes.getString(valueFromId);
+ String toString = styledAttributes.getString(valueToId);
+ PathParser.PathDataNode[] nodesFrom = PathParser.createNodesFromPathData(fromString);
+ PathParser.PathDataNode[] nodesTo = PathParser.createNodesFromPathData(toString);
+
+ if (nodesFrom != null || nodesTo != null) {
+ if (nodesFrom != null) {
+ TypeEvaluator evaluator =
+ new PathDataEvaluator(PathParser.deepCopyNodes(nodesFrom));
+ if (nodesTo != null) {
+ if (!PathParser.canMorph(nodesFrom, nodesTo)) {
+ throw new InflateException(" Can't morph from " + fromString + " to " +
+ toString);
+ }
+ returnValue = PropertyValuesHolder.ofObject(propertyName, evaluator,
+ nodesFrom, nodesTo);
+ } else {
+ returnValue = PropertyValuesHolder.ofObject(propertyName, evaluator,
+ (Object) nodesFrom);
+ }
+ } else if (nodesTo != null) {
+ TypeEvaluator evaluator =
+ new PathDataEvaluator(PathParser.deepCopyNodes(nodesTo));
+ returnValue = PropertyValuesHolder.ofObject(propertyName, evaluator,
+ (Object) nodesTo);
+ }
+ }
} else {
+ TypeEvaluator evaluator = null;
// Integer and float value types are handled here.
if ((hasFrom && (fromType >= TypedValue.TYPE_FIRST_COLOR_INT) &&
(fromType <= TypedValue.TYPE_LAST_COLOR_INT)) ||
@@ -338,7 +349,101 @@ public class AnimatorInflater {
getFloats = false;
evaluator = ArgbEvaluator.getInstance();
}
- setupValues(anim, arrayAnimator, getFloats, hasFrom, fromType, hasTo, toType);
+ if (getFloats) {
+ float valueFrom;
+ float valueTo;
+ if (hasFrom) {
+ if (fromType == TypedValue.TYPE_DIMENSION) {
+ valueFrom = styledAttributes.getDimension(valueFromId, 0f);
+ } else {
+ valueFrom = styledAttributes.getFloat(valueFromId, 0f);
+ }
+ if (hasTo) {
+ if (toType == TypedValue.TYPE_DIMENSION) {
+ valueTo = styledAttributes.getDimension(valueToId, 0f);
+ } else {
+ valueTo = styledAttributes.getFloat(valueToId, 0f);
+ }
+ returnValue = PropertyValuesHolder.ofFloat(propertyName,
+ valueFrom, valueTo);
+ } else {
+ returnValue = PropertyValuesHolder.ofFloat(propertyName, valueFrom);
+ }
+ } else {
+ if (toType == TypedValue.TYPE_DIMENSION) {
+ valueTo = styledAttributes.getDimension(valueToId, 0f);
+ } else {
+ valueTo = styledAttributes.getFloat(valueToId, 0f);
+ }
+ returnValue = PropertyValuesHolder.ofFloat(propertyName, valueTo);
+ }
+ } else {
+ int valueFrom;
+ int valueTo;
+ if (hasFrom) {
+ if (fromType == TypedValue.TYPE_DIMENSION) {
+ valueFrom = (int) styledAttributes.getDimension(valueFromId, 0f);
+ } else if ((fromType >= TypedValue.TYPE_FIRST_COLOR_INT) &&
+ (fromType <= TypedValue.TYPE_LAST_COLOR_INT)) {
+ valueFrom = styledAttributes.getColor(valueFromId, 0);
+ } else {
+ valueFrom = styledAttributes.getInt(valueFromId, 0);
+ }
+ if (hasTo) {
+ if (toType == TypedValue.TYPE_DIMENSION) {
+ valueTo = (int) styledAttributes.getDimension(valueToId, 0f);
+ } else if ((toType >= TypedValue.TYPE_FIRST_COLOR_INT) &&
+ (toType <= TypedValue.TYPE_LAST_COLOR_INT)) {
+ valueTo = styledAttributes.getColor(valueToId, 0);
+ } else {
+ valueTo = styledAttributes.getInt(valueToId, 0);
+ }
+ returnValue = PropertyValuesHolder.ofInt(propertyName, valueFrom, valueTo);
+ } else {
+ returnValue = PropertyValuesHolder.ofInt(propertyName, valueFrom);
+ }
+ } else {
+ if (hasTo) {
+ if (toType == TypedValue.TYPE_DIMENSION) {
+ valueTo = (int) styledAttributes.getDimension(valueToId, 0f);
+ } else if ((toType >= TypedValue.TYPE_FIRST_COLOR_INT) &&
+ (toType <= TypedValue.TYPE_LAST_COLOR_INT)) {
+ valueTo = styledAttributes.getColor(valueToId, 0);
+ } else {
+ valueTo = styledAttributes.getInt(valueToId, 0);
+ }
+ returnValue = PropertyValuesHolder.ofInt(propertyName, valueTo);
+ }
+ }
+ }
+ if (returnValue != null && evaluator != null) {
+ returnValue.setEvaluator(evaluator);
+ }
+ }
+
+ return returnValue;
+ }
+
+ /**
+ * @param anim The animator, must not be null
+ * @param arrayAnimator Incoming typed array for Animator's attributes.
+ * @param arrayObjectAnimator Incoming typed array for Object Animator's
+ * attributes.
+ * @param pixelSize The relative pixel size, used to calculate the
+ * maximum error for path animations.
+ */
+ private static void parseAnimatorFromTypeArray(ValueAnimator anim,
+ TypedArray arrayAnimator, TypedArray arrayObjectAnimator, float pixelSize) {
+ long duration = arrayAnimator.getInt(R.styleable.Animator_duration, 300);
+
+ long startDelay = arrayAnimator.getInt(R.styleable.Animator_startOffset, 0);
+
+ int valueType = arrayAnimator.getInt(R.styleable.Animator_valueType, VALUE_TYPE_FLOAT);
+
+ PropertyValuesHolder pvh = getPVH(arrayAnimator, valueType,
+ R.styleable.Animator_valueFrom, R.styleable.Animator_valueTo, "");
+ if (pvh != null) {
+ anim.setValues(pvh);
}
anim.setDuration(duration);
@@ -353,12 +458,10 @@ public class AnimatorInflater {
arrayAnimator.getInt(R.styleable.Animator_repeatMode,
ValueAnimator.RESTART));
}
- if (evaluator != null) {
- anim.setEvaluator(evaluator);
- }
if (arrayObjectAnimator != null) {
- setupObjectAnimator(anim, arrayObjectAnimator, getFloats, pixelSize);
+ setupObjectAnimator(anim, arrayObjectAnimator, valueType == VALUE_TYPE_FLOAT,
+ pixelSize);
}
}
@@ -570,6 +673,7 @@ public class AnimatorInflater {
}
String name = parser.getName();
+ boolean gotValues = false;
if (name.equals("objectAnimator")) {
anim = loadObjectAnimator(res, theme, attrs, pixelSize);
@@ -588,11 +692,18 @@ public class AnimatorInflater {
createAnimatorFromXml(res, theme, parser, attrs, (AnimatorSet) anim, ordering,
pixelSize);
a.recycle();
+ } else if (name.equals("propertyValuesHolder")) {
+ PropertyValuesHolder[] values = loadValues(res, theme, parser,
+ Xml.asAttributeSet(parser));
+ if (values != null && anim != null && (anim instanceof ValueAnimator)) {
+ ((ValueAnimator) anim).setValues(values);
+ }
+ gotValues = true;
} else {
throw new RuntimeException("Unknown animator name: " + parser.getName());
}
- if (parent != null) {
+ if (parent != null && !gotValues) {
if (childAnims == null) {
childAnims = new ArrayList<Animator>();
}
@@ -612,7 +723,233 @@ public class AnimatorInflater {
}
}
return anim;
+ }
+
+ private static PropertyValuesHolder[] loadValues(Resources res, Theme theme,
+ XmlPullParser parser, AttributeSet attrs) throws XmlPullParserException, IOException {
+ ArrayList<PropertyValuesHolder> values = null;
+
+ int type;
+ while ((type = parser.getEventType()) != XmlPullParser.END_TAG &&
+ type != XmlPullParser.END_DOCUMENT) {
+
+ if (type != XmlPullParser.START_TAG) {
+ parser.next();
+ continue;
+ }
+
+ String name = parser.getName();
+
+ if (name.equals("propertyValuesHolder")) {
+ TypedArray a;
+ if (theme != null) {
+ a = theme.obtainStyledAttributes(attrs, R.styleable.PropertyValuesHolder, 0, 0);
+ } else {
+ a = res.obtainAttributes(attrs, R.styleable.PropertyValuesHolder);
+ }
+ String propertyName = a.getString(R.styleable.PropertyValuesHolder_propertyName);
+ int valueType = a.getInt(R.styleable.PropertyValuesHolder_valueType,
+ VALUE_TYPE_FLOAT);
+ PropertyValuesHolder pvh = loadPvh(res, theme, parser, propertyName, valueType);
+ if (pvh == null) {
+ pvh = getPVH(a, valueType,
+ R.styleable.PropertyValuesHolder_valueFrom,
+ R.styleable.PropertyValuesHolder_valueTo, propertyName);
+ }
+ if (pvh != null) {
+ if (values == null) {
+ values = new ArrayList<PropertyValuesHolder>();
+ }
+ values.add(pvh);
+ }
+ a.recycle();
+ }
+
+ parser.next();
+ }
+
+ PropertyValuesHolder[] valuesArray = null;
+ if (values != null) {
+ int count = values.size();
+ valuesArray = new PropertyValuesHolder[count];
+ for (int i = 0; i < count; ++i) {
+ valuesArray[i] = values.get(i);
+ }
+ }
+ return valuesArray;
+ }
+
+ private static void dumpKeyframes(Object[] keyframes, String header) {
+ if (keyframes == null || keyframes.length == 0) {
+ return;
+ }
+ Log.d(TAG, header);
+ int count = keyframes.length;
+ for (int i = 0; i < count; ++i) {
+ Keyframe keyframe = (Keyframe) keyframes[i];
+ Log.d(TAG, "Keyframe " + i + ": fraction " +
+ (keyframe.getFraction() < 0 ? "null" : keyframe.getFraction()) + ", " +
+ ", value : " + ((keyframe.hasValue()) ? keyframe.getValue() : "null"));
+ }
+ }
+
+ private static PropertyValuesHolder loadPvh(Resources res, Theme theme, XmlPullParser parser,
+ String propertyName, int valueType)
+ throws XmlPullParserException, IOException {
+
+ PropertyValuesHolder value = null;
+ ArrayList<Keyframe> keyframes = null;
+
+ int type;
+ while ((type = parser.next()) != XmlPullParser.END_TAG &&
+ type != XmlPullParser.END_DOCUMENT) {
+ String name = parser.getName();
+ if (name.equals("keyframe")) {
+ Keyframe keyframe = loadKeyframe(res, theme, Xml.asAttributeSet(parser), valueType);
+ if (keyframe != null) {
+ if (keyframes == null) {
+ keyframes = new ArrayList<Keyframe>();
+ }
+ keyframes.add(keyframe);
+ }
+ parser.next();
+ }
+ }
+
+ int count;
+ if (keyframes != null && (count = keyframes.size()) > 0) {
+ // make sure we have keyframes at 0 and 1
+ // If we have keyframes with set fractions, add keyframes at start/end
+ // appropriately. If start/end have no set fractions:
+ // if there's only one keyframe, set its fraction to 1 and add one at 0
+ // if >1 keyframe, set the last fraction to 1, the first fraction to 0
+ Keyframe firstKeyframe = keyframes.get(0);
+ Keyframe lastKeyframe = keyframes.get(count - 1);
+ float endFraction = lastKeyframe.getFraction();
+ if (endFraction < 1) {
+ if (endFraction < 0) {
+ lastKeyframe.setFraction(1);
+ } else {
+ keyframes.add(keyframes.size(), createNewKeyframe(lastKeyframe, 1));
+ ++count;
+ }
+ }
+ float startFraction = firstKeyframe.getFraction();
+ if (startFraction != 0) {
+ if (startFraction < 0) {
+ firstKeyframe.setFraction(0);
+ } else {
+ keyframes.add(0, createNewKeyframe(firstKeyframe, 0));
+ ++count;
+ }
+ }
+ Keyframe[] keyframeArray = new Keyframe[count];
+ keyframes.toArray(keyframeArray);
+ for (int i = 0; i < count; ++i) {
+ Keyframe keyframe = keyframeArray[i];
+ if (keyframe.getFraction() < 0) {
+ if (i == 0) {
+ keyframe.setFraction(0);
+ } else if (i == count - 1) {
+ keyframe.setFraction(1);
+ } else {
+ // figure out the start/end parameters of the current gap
+ // in fractions and distribute the gap among those keyframes
+ int startIndex = i;
+ int endIndex = i;
+ for (int j = startIndex + 1; j < count - 1; ++j) {
+ if (keyframeArray[j].getFraction() >= 0) {
+ break;
+ }
+ endIndex = j;
+ }
+ float gap = keyframeArray[endIndex + 1].getFraction() -
+ keyframeArray[startIndex - 1].getFraction();
+ distributeKeyframes(keyframeArray, gap, startIndex, endIndex);
+ }
+ }
+ }
+ value = PropertyValuesHolder.ofKeyframe(propertyName, keyframeArray);
+ if (valueType == VALUE_TYPE_COLOR) {
+ value.setEvaluator(ArgbEvaluator.getInstance());
+ }
+ }
+
+ return value;
+ }
+
+ private static Keyframe createNewKeyframe(Keyframe sampleKeyframe, float fraction) {
+ return sampleKeyframe.getType() == float.class ?
+ Keyframe.ofFloat(fraction) :
+ (sampleKeyframe.getType() == int.class) ?
+ Keyframe.ofInt(fraction) :
+ Keyframe.ofObject(fraction);
+ }
+
+ /**
+ * Utility function to set fractions on keyframes to cover a gap in which the
+ * fractions are not currently set. Keyframe fractions will be distributed evenly
+ * in this gap. For example, a gap of 1 keyframe in the range 0-1 will be at .5, a gap
+ * of .6 spread between two keyframes will be at .2 and .4 beyond the fraction at the
+ * keyframe before startIndex.
+ * Assumptions:
+ * - First and last keyframe fractions (bounding this spread) are already set. So,
+ * for example, if no fractions are set, we will already set first and last keyframe
+ * fraction values to 0 and 1.
+ * - startIndex must be >0 (which follows from first assumption).
+ * - endIndex must be >= startIndex.
+ *
+ * @param keyframes the array of keyframes
+ * @param gap The total gap we need to distribute
+ * @param startIndex The index of the first keyframe whose fraction must be set
+ * @param endIndex The index of the last keyframe whose fraction must be set
+ */
+ private static void distributeKeyframes(Keyframe[] keyframes, float gap,
+ int startIndex, int endIndex) {
+ int count = endIndex - startIndex + 2;
+ float increment = gap / count;
+ for (int i = startIndex; i <= endIndex; ++i) {
+ keyframes[i].setFraction(keyframes[i-1].getFraction() + increment);
+ }
+ }
+
+ private static Keyframe loadKeyframe(Resources res, Theme theme, AttributeSet attrs,
+ int valueType)
+ throws XmlPullParserException, IOException {
+
+ TypedArray a;
+ if (theme != null) {
+ a = theme.obtainStyledAttributes(attrs, R.styleable.Keyframe, 0, 0);
+ } else {
+ a = res.obtainAttributes(attrs, R.styleable.Keyframe);
+ }
+
+ Keyframe keyframe = null;
+
+ float fraction = a.getFloat(R.styleable.Keyframe_fraction, -1);
+
+ boolean hasValue = a.peekValue(R.styleable.Keyframe_value) != null;
+
+ if (hasValue) {
+ switch (valueType) {
+ case VALUE_TYPE_FLOAT:
+ float value = a.getFloat(R.styleable.Keyframe_value, 0);
+ keyframe = Keyframe.ofFloat(fraction, value);
+ break;
+ case VALUE_TYPE_COLOR:
+ case VALUE_TYPE_INT:
+ int intValue = a.getInt(R.styleable.Keyframe_value, 0);
+ keyframe = Keyframe.ofInt(fraction, intValue);
+ break;
+ }
+ } else {
+ keyframe = (valueType == VALUE_TYPE_FLOAT) ? Keyframe.ofFloat(fraction) :
+ Keyframe.ofInt(fraction);
+ }
+
+ a.recycle();
+ return keyframe;
}
private static ObjectAnimator loadObjectAnimator(Resources res, Theme theme, AttributeSet attrs,
diff --git a/core/java/android/animation/AnimatorSet.java b/core/java/android/animation/AnimatorSet.java
index 92762c3..53d5237 100644
--- a/core/java/android/animation/AnimatorSet.java
+++ b/core/java/android/animation/AnimatorSet.java
@@ -972,6 +972,18 @@ public final class AnimatorSet extends Animator {
}
}
+ @Override
+ public String toString() {
+ String returnVal = "AnimatorSet@" + Integer.toHexString(hashCode()) + "{";
+ boolean prevNeedsSort = mNeedsSort;
+ sortNodes();
+ mNeedsSort = prevNeedsSort;
+ for (Node node : mSortedNodes) {
+ returnVal += "\n " + node.animation.toString();
+ }
+ return returnVal + "\n}";
+ }
+
/**
* Dependency holds information about the node that some other node is
* dependent upon and the nature of that dependency.
diff --git a/core/java/android/animation/ObjectAnimator.java b/core/java/android/animation/ObjectAnimator.java
index 59daaab..71855da 100644
--- a/core/java/android/animation/ObjectAnimator.java
+++ b/core/java/android/animation/ObjectAnimator.java
@@ -33,6 +33,27 @@ import java.util.ArrayList;
* are then determined internally and the animation will call these functions as necessary to
* animate the property.
*
+ * <p>Animators can be created from either code or resource files, as shown here:</p>
+ *
+ * {@sample development/samples/ApiDemos/res/anim/object_animator.xml ObjectAnimatorResources}
+ *
+ * <p>When using resource files, it is possible to use {@link PropertyValuesHolder} and
+ * {@link Keyframe} to create more complex animations. Using PropertyValuesHolders
+ * allows animators to animate several properties in parallel, as shown in this sample:</p>
+ *
+ * {@sample development/samples/ApiDemos/res/anim/object_animator_pvh.xml
+ * PropertyValuesHolderResources}
+ *
+ * <p>Using Keyframes allows animations to follow more complex paths from the start
+ * to the end values. Note that you can specify explicit fractional values (from 0 to 1) for
+ * each keyframe to determine when, in the overall duration, the animation should arrive at that
+ * value. Alternatively, you can leave the fractions off and the keyframes will be equally
+ * distributed within the total duration. Also, a keyframe with no value will derive its value
+ * from the target object when the animator starts, just like animators with only one
+ * value specified.</p>
+ *
+ * {@sample development/samples/ApiDemos/res/anim/object_animator_pvh_kf.xml KeyframeResources}
+ *
* <div class="special reference">
* <h3>Developer Guides</h3>
* <p>For more information about animating with {@code ObjectAnimator}, read the
diff --git a/core/java/android/animation/ValueAnimator.java b/core/java/android/animation/ValueAnimator.java
index 9709555..292bb1d 100644
--- a/core/java/android/animation/ValueAnimator.java
+++ b/core/java/android/animation/ValueAnimator.java
@@ -17,6 +17,7 @@
package android.animation;
import android.content.res.ConfigurationBoundResourceCache;
+import android.os.Debug;
import android.os.Looper;
import android.os.Trace;
import android.util.AndroidRuntimeException;
@@ -40,6 +41,21 @@ import java.util.HashMap;
* out of an animation. This behavior can be changed by calling
* {@link ValueAnimator#setInterpolator(TimeInterpolator)}.</p>
*
+ * <p>Animators can be created from either code or resource files. Here is an example
+ * of a ValueAnimator resource file:</p>
+ *
+ * {@sample development/samples/ApiDemos/res/anim/animator.xml ValueAnimatorResources}
+ *
+ * <p>It is also possible to use a combination of {@link PropertyValuesHolder} and
+ * {@link Keyframe} resource tags to create a multi-step animation.
+ * Note that you can specify explicit fractional values (from 0 to 1) for
+ * each keyframe to determine when, in the overall duration, the animation should arrive at that
+ * value. Alternatively, you can leave the fractions off and the keyframes will be equally
+ * distributed within the total duration:</p>
+ *
+ * {@sample development/samples/ApiDemos/res/anim/value_animator_pvh_kf.xml
+ * ValueAnimatorKeyframeResources}
+ *
* <div class="special reference">
* <h3>Developer Guides</h3>
* <p>For more information about animating with {@code ValueAnimator}, read the
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 9568897..aa1c70e 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -27,7 +27,6 @@ import android.widget.Toolbar;
import com.android.internal.app.IVoiceInteractor;
import com.android.internal.app.WindowDecorActionBar;
import com.android.internal.app.ToolbarActionBar;
-import com.android.internal.policy.PolicyManager;
import android.annotation.IntDef;
import android.annotation.Nullable;
@@ -84,6 +83,7 @@ import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.MotionEvent;
+import android.view.PhoneWindow;
import android.view.View;
import android.view.View.OnCreateContextMenuListener;
import android.view.ViewGroup;
@@ -362,7 +362,7 @@ import java.util.HashMap;
*
* <p>Note the "Killable" column in the above table -- for those methods that
* are marked as being killable, after that method returns the process hosting the
- * activity may killed by the system <em>at any time</em> without another line
+ * activity may be killed by the system <em>at any time</em> without another line
* of its code being executed. Because of this, you should use the
* {@link #onPause} method to write any persistent data (such as user edits)
* to storage. In addition, the method
@@ -743,6 +743,7 @@ public class Activity extends ContextThemeWrapper
final FragmentManagerImpl mFragments = new FragmentManagerImpl();
final FragmentContainer mContainer = new FragmentContainer() {
@Override
+ @Nullable
public View findViewById(int id) {
return Activity.this.findViewById(id);
}
@@ -1242,22 +1243,18 @@ public class Activity extends ContextThemeWrapper
}
/**
- * @hide
* Check whether this activity is running as part of a voice interaction with the user.
* If true, it should perform its interaction with the user through the
* {@link VoiceInteractor} returned by {@link #getVoiceInteractor}.
*/
- @SystemApi
public boolean isVoiceInteraction() {
return mVoiceInteractor != null;
}
/**
- * @hide
* Retrieve the active {@link VoiceInteractor} that the user is going through to
* interact with this activity.
*/
- @SystemApi
public VoiceInteractor getVoiceInteractor() {
return mVoiceInteractor;
}
@@ -1538,13 +1535,17 @@ public class Activity extends ContextThemeWrapper
* {@link Intent#ACTION_ASSIST} Intent with all of the context of the current
* application. You can override this method to place into the bundle anything
* you would like to appear in the {@link Intent#EXTRA_ASSIST_CONTEXT} part
- * of the assist Intent. The default implementation does nothing.
+ * of the assist Intent. The default implementation automatically generates a
+ * {@link AssistData} from your activity and places it in to the Bundle; if you
+ * don't want your UI reported to the assistant, don't call this default
+ * implementation.
*
* <p>This function will be called after any global assist callbacks that had
* been registered with {@link Application#registerOnProvideAssistDataListener
* Application.registerOnProvideAssistDataListener}.
*/
public void onProvideAssistData(Bundle data) {
+ data.putParcelable(AssistData.ASSIST_KEY, new AssistData(this));
}
/**
@@ -2068,6 +2069,7 @@ public class Activity extends ContextThemeWrapper
*
* @return The view if found or null otherwise.
*/
+ @Nullable
public View findViewById(int id) {
return getWindow().findViewById(id);
}
@@ -4619,7 +4621,7 @@ public class Activity extends ContextThemeWrapper
if (Looper.myLooper() != mMainThread.getLooper()) {
throw new IllegalStateException("Must be called from main thread");
}
- mMainThread.requestRelaunchActivity(mToken, null, null, 0, false, null, false);
+ mMainThread.requestRelaunchActivity(mToken, null, null, 0, false, null, null, false);
}
/**
@@ -5929,7 +5931,7 @@ public class Activity extends ContextThemeWrapper
mFragments.attachActivity(this, mContainer, null);
- mWindow = PolicyManager.makeNewWindow(this);
+ mWindow = new PhoneWindow(this);
mWindow.setCallback(this);
mWindow.setOnWindowDismissedCallback(this);
mWindow.getLayoutInflater().setPrivateFactory(this);
@@ -6080,6 +6082,17 @@ public class Activity extends ContextThemeWrapper
" did not call through to super.onResume()");
}
+ // invisible activities must be finished before onResume() completes
+ if (!mVisibleFromClient && !mFinished) {
+ Log.w(TAG, "An activity without a UI must call finish() before onResume() completes");
+ if (getApplicationInfo().targetSdkVersion
+ > android.os.Build.VERSION_CODES.LOLLIPOP_MR1) {
+ throw new IllegalStateException(
+ "Activity " + mComponent.toShortString() +
+ " did not call finish() prior to onResume() completing");
+ }
+ }
+
// Now really resume, and install the current status bar and menu.
mCalled = false;
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index 7a636db..c6ffef6 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -256,6 +256,9 @@ public class ActivityManager {
/** @hide User operation call: given user id is the current user, can't be stopped. */
public static final int USER_OP_IS_CURRENT = -2;
+ /** @hide Process does not exist. */
+ public static final int PROCESS_STATE_NONEXISTENT = -1;
+
/** @hide Process is a persistent system process. */
public static final int PROCESS_STATE_PERSISTENT = 0;
diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java
index a7099d4..47f57ea 100644
--- a/core/java/android/app/ActivityManagerNative.java
+++ b/core/java/android/app/ActivityManagerNative.java
@@ -690,14 +690,6 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM
return true;
}
- case MOVE_TASK_TO_BACK_TRANSACTION: {
- data.enforceInterface(IActivityManager.descriptor);
- int task = data.readInt();
- moveTaskToBack(task);
- reply.writeNoException();
- return true;
- }
-
case MOVE_ACTIVITY_TASK_TO_BACK_TRANSACTION: {
data.enforceInterface(IActivityManager.descriptor);
IBinder token = data.readStrongBinder();
@@ -729,7 +721,6 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM
case RESIZE_STACK_TRANSACTION: {
data.enforceInterface(IActivityManager.descriptor);
int stackId = data.readInt();
- float weight = data.readFloat();
Rect r = Rect.CREATOR.createFromParcel(data);
resizeStack(stackId, r);
reply.writeNoException();
@@ -775,6 +766,14 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM
return true;
}
+ case GET_FOCUSED_STACK_ID_TRANSACTION: {
+ data.enforceInterface(IActivityManager.descriptor);
+ int focusedStackId = getFocusedStackId();
+ reply.writeNoException();
+ reply.writeInt(focusedStackId);
+ return true;
+ }
+
case REGISTER_TASK_STACK_LISTENER_TRANSACTION: {
data.enforceInterface(IActivityManager.descriptor);
IBinder token = data.readStrongBinder();
@@ -2184,13 +2183,13 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM
return true;
}
- case CREATE_ACTIVITY_CONTAINER_TRANSACTION: {
+ case CREATE_VIRTUAL_ACTIVITY_CONTAINER_TRANSACTION: {
data.enforceInterface(IActivityManager.descriptor);
IBinder parentActivityToken = data.readStrongBinder();
IActivityContainerCallback callback =
IActivityContainerCallback.Stub.asInterface(data.readStrongBinder());
IActivityContainer activityContainer =
- createActivityContainer(parentActivityToken, callback);
+ createVirtualActivityContainer(parentActivityToken, callback);
reply.writeNoException();
if (activityContainer != null) {
reply.writeInt(1);
@@ -2210,6 +2209,20 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM
return true;
}
+ case CREATE_STACK_ON_DISPLAY: {
+ data.enforceInterface(IActivityManager.descriptor);
+ int displayId = data.readInt();
+ IActivityContainer activityContainer = createStackOnDisplay(displayId);
+ reply.writeNoException();
+ if (activityContainer != null) {
+ reply.writeInt(1);
+ reply.writeStrongBinder(activityContainer.asBinder());
+ } else {
+ reply.writeInt(0);
+ }
+ return true;
+ }
+
case GET_ACTIVITY_CONTAINER_TRANSACTION: {
data.enforceInterface(IActivityManager.descriptor);
IBinder activityToken = data.readStrongBinder();
@@ -2287,6 +2300,15 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM
return true;
}
+ case SET_TASK_RESIZEABLE_TRANSACTION: {
+ data.enforceInterface(IActivityManager.descriptor);
+ int taskId = data.readInt();
+ boolean resizeable = (data.readInt() == 1) ? true : false;
+ setTaskResizeable(taskId, resizeable);
+ reply.writeNoException();
+ return true;
+ }
+
case GET_TASK_DESCRIPTION_ICON_TRANSACTION: {
data.enforceInterface(IActivityManager.descriptor);
String filename = data.readString();
@@ -3000,7 +3022,7 @@ class ActivityManagerProxy implements IActivityManager
ArrayList<IAppTask> list = null;
int N = reply.readInt();
if (N >= 0) {
- list = new ArrayList<IAppTask>();
+ list = new ArrayList<>();
while (N > 0) {
IAppTask task = IAppTask.Stub.asInterface(reply.readStrongBinder());
list.add(task);
@@ -3038,7 +3060,8 @@ class ActivityManagerProxy implements IActivityManager
reply.recycle();
return size;
}
- public List getTasks(int maxNum, int flags) throws RemoteException {
+ public List<ActivityManager.RunningTaskInfo> getTasks(int maxNum, int flags)
+ throws RemoteException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
data.writeInterfaceToken(IActivityManager.descriptor);
@@ -3046,10 +3069,10 @@ class ActivityManagerProxy implements IActivityManager
data.writeInt(flags);
mRemote.transact(GET_TASKS_TRANSACTION, data, reply, 0);
reply.readException();
- ArrayList list = null;
+ ArrayList<ActivityManager.RunningTaskInfo> list = null;
int N = reply.readInt();
if (N >= 0) {
- list = new ArrayList();
+ list = new ArrayList<>();
while (N > 0) {
ActivityManager.RunningTaskInfo info =
ActivityManager.RunningTaskInfo.CREATOR
@@ -3093,7 +3116,8 @@ class ActivityManagerProxy implements IActivityManager
reply.recycle();
return taskThumbnail;
}
- public List getServices(int maxNum, int flags) throws RemoteException {
+ public List<ActivityManager.RunningServiceInfo> getServices(int maxNum, int flags)
+ throws RemoteException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
data.writeInterfaceToken(IActivityManager.descriptor);
@@ -3101,10 +3125,10 @@ class ActivityManagerProxy implements IActivityManager
data.writeInt(flags);
mRemote.transact(GET_SERVICES_TRANSACTION, data, reply, 0);
reply.readException();
- ArrayList list = null;
+ ArrayList<ActivityManager.RunningServiceInfo> list = null;
int N = reply.readInt();
if (N >= 0) {
- list = new ArrayList();
+ list = new ArrayList<>();
while (N > 0) {
ActivityManager.RunningServiceInfo info =
ActivityManager.RunningServiceInfo.CREATOR
@@ -3174,17 +3198,6 @@ class ActivityManagerProxy implements IActivityManager
data.recycle();
reply.recycle();
}
- public void moveTaskToBack(int task) throws RemoteException
- {
- Parcel data = Parcel.obtain();
- Parcel reply = Parcel.obtain();
- data.writeInterfaceToken(IActivityManager.descriptor);
- data.writeInt(task);
- mRemote.transact(MOVE_TASK_TO_BACK_TRANSACTION, data, reply, 0);
- reply.readException();
- data.recycle();
- reply.recycle();
- }
public boolean moveActivityTaskToBack(IBinder token, boolean nonRoot)
throws RemoteException {
Parcel data = Parcel.obtain();
@@ -3294,6 +3307,18 @@ class ActivityManagerProxy implements IActivityManager
reply.recycle();
}
@Override
+ public int getFocusedStackId() throws RemoteException {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ data.writeInterfaceToken(IActivityManager.descriptor);
+ mRemote.transact(GET_FOCUSED_STACK_ID_TRANSACTION, data, reply, 0);
+ reply.readException();
+ int focusedStackId = reply.readInt();
+ data.recycle();
+ reply.recycle();
+ return focusedStackId;
+ }
+ @Override
public void registerTaskStackListener(ITaskStackListener listener) throws RemoteException
{
Parcel data = Parcel.obtain();
@@ -5217,14 +5242,14 @@ class ActivityManagerProxy implements IActivityManager
reply.recycle();
}
- public IActivityContainer createActivityContainer(IBinder parentActivityToken,
+ public IActivityContainer createVirtualActivityContainer(IBinder parentActivityToken,
IActivityContainerCallback callback) throws RemoteException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
data.writeInterfaceToken(IActivityManager.descriptor);
data.writeStrongBinder(parentActivityToken);
data.writeStrongBinder(callback == null ? null : callback.asBinder());
- mRemote.transact(CREATE_ACTIVITY_CONTAINER_TRANSACTION, data, reply, 0);
+ mRemote.transact(CREATE_VIRTUAL_ACTIVITY_CONTAINER_TRANSACTION, data, reply, 0);
reply.readException();
final int result = reply.readInt();
final IActivityContainer res;
@@ -5250,6 +5275,25 @@ class ActivityManagerProxy implements IActivityManager
reply.recycle();
}
+ public IActivityContainer createStackOnDisplay(int displayId) throws RemoteException {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ data.writeInterfaceToken(IActivityManager.descriptor);
+ data.writeInt(displayId);
+ mRemote.transact(CREATE_STACK_ON_DISPLAY, data, reply, 0);
+ reply.readException();
+ final int result = reply.readInt();
+ final IActivityContainer res;
+ if (result == 1) {
+ res = IActivityContainer.Stub.asInterface(reply.readStrongBinder());
+ } else {
+ res = null;
+ }
+ data.recycle();
+ reply.recycle();
+ return res;
+ }
+
public IActivityContainer getEnclosingActivityContainer(IBinder activityToken)
throws RemoteException {
Parcel data = Parcel.obtain();
@@ -5367,6 +5411,19 @@ class ActivityManagerProxy implements IActivityManager
}
@Override
+ public void setTaskResizeable(int taskId, boolean resizeable) throws RemoteException {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ data.writeInterfaceToken(IActivityManager.descriptor);
+ data.writeInt(taskId);
+ data.writeInt(resizeable ? 1 : 0);
+ mRemote.transact(SET_TASK_RESIZEABLE_TRANSACTION, data, reply, IBinder.FLAG_ONEWAY);
+ reply.readException();
+ data.recycle();
+ reply.recycle();
+ }
+
+ @Override
public Bitmap getTaskDescriptionIcon(String filename) throws RemoteException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index ccbed92..d781863 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -46,7 +46,6 @@ import android.graphics.Canvas;
import android.hardware.display.DisplayManagerGlobal;
import android.net.ConnectivityManager;
import android.net.IConnectivityManager;
-import android.net.LinkProperties;
import android.net.Network;
import android.net.Proxy;
import android.net.ProxyInfo;
@@ -87,8 +86,6 @@ import android.util.Slog;
import android.util.SuperNotCalledException;
import android.view.Display;
import android.view.HardwareRenderer;
-import android.view.IWindowManager;
-import android.view.IWindowSessionCallback;
import android.view.View;
import android.view.ViewDebug;
import android.view.ViewManager;
@@ -165,8 +162,8 @@ public final class ActivityThread {
private static final long MIN_TIME_BETWEEN_GCS = 5*1000;
private static final Pattern PATTERN_SEMICOLON = Pattern.compile(";");
private static final int SQLITE_MEM_RELEASED_EVENT_LOG_TAG = 75003;
- private static final int LOG_ON_PAUSE_CALLED = 30021;
- private static final int LOG_ON_RESUME_CALLED = 30022;
+ private static final int LOG_AM_ON_PAUSE_CALLED = 30021;
+ private static final int LOG_AM_ON_RESUME_CALLED = 30022;
/** Type for IActivityManager.serviceDoneExecuting: anonymous operation */
public static final int SERVICE_DONE_EXECUTING_ANON = 0;
@@ -296,6 +293,9 @@ public final class ActivityThread {
boolean hideForNow;
Configuration newConfig;
Configuration createdConfig;
+ Configuration overrideConfig;
+ // Used for consolidating configs before sending on to Activity.
+ private Configuration tmpConfig = new Configuration();
ActivityClientRecord nextIdle;
ProfilerInfo profilerInfo;
@@ -559,6 +559,15 @@ public final class ActivityThread {
int requestType;
}
+ static final class ActivityConfigChangeData {
+ final IBinder activityToken;
+ final Configuration overrideConfig;
+ public ActivityConfigChangeData(IBinder token, Configuration config) {
+ activityToken = token;
+ overrideConfig = config;
+ }
+ }
+
private native void dumpGraphicsInfo(FileDescriptor fd);
private class ApplicationThread extends ApplicationThreadNative {
@@ -618,12 +627,13 @@ public final class ActivityThread {
// we use token to identify this activity without having to send the
// activity itself back to the activity manager. (matters more with ipc)
+ @Override
public final void scheduleLaunchActivity(Intent intent, IBinder token, int ident,
- ActivityInfo info, Configuration curConfig, CompatibilityInfo compatInfo,
- String referrer, IVoiceInteractor voiceInteractor, int procState, Bundle state,
- PersistableBundle persistentState, List<ResultInfo> pendingResults,
- List<ReferrerIntent> pendingNewIntents, boolean notResumed, boolean isForward,
- ProfilerInfo profilerInfo) {
+ ActivityInfo info, Configuration curConfig, Configuration overrideConfig,
+ CompatibilityInfo compatInfo, String referrer, IVoiceInteractor voiceInteractor,
+ int procState, Bundle state, PersistableBundle persistentState,
+ List<ResultInfo> pendingResults, List<ReferrerIntent> pendingNewIntents,
+ boolean notResumed, boolean isForward, ProfilerInfo profilerInfo) {
updateProcessState(procState, false);
@@ -647,16 +657,19 @@ public final class ActivityThread {
r.profilerInfo = profilerInfo;
+ r.overrideConfig = overrideConfig;
updatePendingConfiguration(curConfig);
sendMessage(H.LAUNCH_ACTIVITY, r);
}
+ @Override
public final void scheduleRelaunchActivity(IBinder token,
List<ResultInfo> pendingResults, List<ReferrerIntent> pendingNewIntents,
- int configChanges, boolean notResumed, Configuration config) {
+ int configChanges, boolean notResumed, Configuration config,
+ Configuration overrideConfig) {
requestRelaunchActivity(token, pendingResults, pendingNewIntents,
- configChanges, notResumed, config, true);
+ configChanges, notResumed, config, overrideConfig, true);
}
public final void scheduleNewIntent(List<ReferrerIntent> intents, IBinder token) {
@@ -886,14 +899,19 @@ public final class ActivityThread {
sticky, sendingUser);
}
+ @Override
public void scheduleLowMemory() {
sendMessage(H.LOW_MEMORY, null);
}
- public void scheduleActivityConfigurationChanged(IBinder token) {
- sendMessage(H.ACTIVITY_CONFIGURATION_CHANGED, token);
+ @Override
+ public void scheduleActivityConfigurationChanged(
+ IBinder token, Configuration overrideConfig) {
+ sendMessage(H.ACTIVITY_CONFIGURATION_CHANGED,
+ new ActivityConfigChangeData(token, overrideConfig));
}
+ @Override
public void profilerControl(boolean start, ProfilerInfo profilerInfo, int profileType) {
sendMessage(H.PROFILER_CONTROL, profilerInfo, start ? 1 : 0, profileType);
}
@@ -1453,7 +1471,7 @@ public final class ActivityThread {
break;
case ACTIVITY_CONFIGURATION_CHANGED:
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityConfigChanged");
- handleActivityConfigurationChanged((IBinder)msg.obj);
+ handleActivityConfigurationChanged((ActivityConfigChangeData)msg.obj);
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
break;
case PROFILER_CONTROL:
@@ -1671,7 +1689,7 @@ public final class ActivityThread {
String[] libDirs, int displayId, Configuration overrideConfiguration,
LoadedApk pkgInfo) {
return mResourcesManager.getTopLevelResources(resDir, splitResDirs, overlayDirs, libDirs,
- displayId, overrideConfiguration, pkgInfo.getCompatibilityInfo(), null);
+ displayId, overrideConfiguration, pkgInfo.getCompatibilityInfo());
}
final Handler getHandler() {
@@ -2356,7 +2374,8 @@ public final class ActivityThread {
private Context createBaseContextForActivity(ActivityClientRecord r,
final Activity activity) {
- ContextImpl appContext = ContextImpl.createActivityContext(this, r.packageInfo, r.token);
+ ContextImpl appContext =
+ ContextImpl.createActivityContext(this, r.packageInfo, r.overrideConfig);
appContext.setOuterContext(activity);
Context baseContext = appContext;
@@ -3000,7 +3019,7 @@ public final class ActivityThread {
}
r.activity.performResume();
- EventLog.writeEvent(LOG_ON_RESUME_CALLED,
+ EventLog.writeEvent(LOG_AM_ON_RESUME_CALLED,
UserHandle.myUserId(), r.activity.getComponentName().getClassName());
r.paused = false;
@@ -3095,10 +3114,14 @@ public final class ActivityThread {
if (!r.activity.mFinished && willBeVisible
&& r.activity.mDecor != null && !r.hideForNow) {
if (r.newConfig != null) {
+ r.tmpConfig.setTo(r.newConfig);
+ if (r.overrideConfig != null) {
+ r.tmpConfig.updateFrom(r.overrideConfig);
+ }
if (DEBUG_CONFIGURATION) Slog.v(TAG, "Resuming activity "
- + r.activityInfo.name + " with newConfig " + r.newConfig);
- performConfigurationChanged(r.activity, r.newConfig);
- freeTextLayoutCachesIfNeeded(r.activity.mCurrentConfig.diff(r.newConfig));
+ + r.activityInfo.name + " with newConfig " + r.tmpConfig);
+ performConfigurationChanged(r.activity, r.tmpConfig);
+ freeTextLayoutCachesIfNeeded(r.activity.mCurrentConfig.diff(r.tmpConfig));
r.newConfig = null;
}
if (localLOGV) Slog.v(TAG, "Resuming " + r + " with isForward="
@@ -3270,7 +3293,7 @@ public final class ActivityThread {
// Now we are idle.
r.activity.mCalled = false;
mInstrumentation.callActivityOnPause(r.activity);
- EventLog.writeEvent(LOG_ON_PAUSE_CALLED, UserHandle.myUserId(),
+ EventLog.writeEvent(LOG_AM_ON_PAUSE_CALLED, UserHandle.myUserId(),
r.activity.getComponentName().getClassName());
if (!r.activity.mCalled) {
throw new SuperNotCalledException(
@@ -3427,10 +3450,14 @@ public final class ActivityThread {
}
}
if (r.newConfig != null) {
+ r.tmpConfig.setTo(r.newConfig);
+ if (r.overrideConfig != null) {
+ r.tmpConfig.updateFrom(r.overrideConfig);
+ }
if (DEBUG_CONFIGURATION) Slog.v(TAG, "Updating activity vis "
- + r.activityInfo.name + " with new config " + r.newConfig);
- performConfigurationChanged(r.activity, r.newConfig);
- freeTextLayoutCachesIfNeeded(r.activity.mCurrentConfig.diff(r.newConfig));
+ + r.activityInfo.name + " with new config " + r.tmpConfig);
+ performConfigurationChanged(r.activity, r.tmpConfig);
+ freeTextLayoutCachesIfNeeded(r.activity.mCurrentConfig.diff(r.tmpConfig));
r.newConfig = null;
}
} else {
@@ -3564,7 +3591,7 @@ public final class ActivityThread {
// request all activities to relaunch for the changes to take place
for (Map.Entry<IBinder, ActivityClientRecord> entry : mActivities.entrySet()) {
- requestRelaunchActivity(entry.getKey(), null, null, 0, false, null, false);
+ requestRelaunchActivity(entry.getKey(), null, null, 0, false, null, null, false);
}
}
}
@@ -3667,7 +3694,7 @@ public final class ActivityThread {
try {
r.activity.mCalled = false;
mInstrumentation.callActivityOnPause(r.activity);
- EventLog.writeEvent(LOG_ON_PAUSE_CALLED, UserHandle.myUserId(),
+ EventLog.writeEvent(LOG_AM_ON_PAUSE_CALLED, UserHandle.myUserId(),
r.activity.getComponentName().getClassName());
if (!r.activity.mCalled) {
throw new SuperNotCalledException(
@@ -3808,7 +3835,7 @@ public final class ActivityThread {
public final void requestRelaunchActivity(IBinder token,
List<ResultInfo> pendingResults, List<ReferrerIntent> pendingNewIntents,
int configChanges, boolean notResumed, Configuration config,
- boolean fromServer) {
+ Configuration overrideConfig, boolean fromServer) {
ActivityClientRecord target = null;
synchronized (mResourcesManager) {
@@ -3857,6 +3884,9 @@ public final class ActivityThread {
if (config != null) {
target.createdConfig = config;
}
+ if (overrideConfig != null) {
+ target.overrideConfig = overrideConfig;
+ }
target.pendingConfigChanges |= configChanges;
}
}
@@ -3969,6 +3999,7 @@ public final class ActivityThread {
}
}
r.startsNotResumed = tmp.startsNotResumed;
+ r.overrideConfig = tmp.overrideConfig;
handleLaunchActivity(r, currentIntent);
}
@@ -4156,16 +4187,21 @@ public final class ActivityThread {
}
}
- final void handleActivityConfigurationChanged(IBinder token) {
- ActivityClientRecord r = mActivities.get(token);
+ final void handleActivityConfigurationChanged(ActivityConfigChangeData data) {
+ ActivityClientRecord r = mActivities.get(data.activityToken);
if (r == null || r.activity == null) {
return;
}
if (DEBUG_CONFIGURATION) Slog.v(TAG, "Handle activity config changed: "
+ r.activityInfo.name);
-
- performConfigurationChanged(r.activity, mCompatConfiguration);
+
+ r.tmpConfig.setTo(mCompatConfiguration);
+ if (data.overrideConfig != null) {
+ r.overrideConfig = data.overrideConfig;
+ r.tmpConfig.updateFrom(data.overrideConfig);
+ }
+ performConfigurationChanged(r.activity, r.tmpConfig);
freeTextLayoutCachesIfNeeded(r.activity.mCurrentConfig.diff(mCompatConfiguration));
diff --git a/core/java/android/app/ActivityView.java b/core/java/android/app/ActivityView.java
index 94ea2c5..6d455b1 100644
--- a/core/java/android/app/ActivityView.java
+++ b/core/java/android/app/ActivityView.java
@@ -83,7 +83,7 @@ public class ActivityView extends ViewGroup {
try {
mActivityContainer = new ActivityContainerWrapper(
- ActivityManagerNative.getDefault().createActivityContainer(
+ ActivityManagerNative.getDefault().createVirtualActivityContainer(
mActivity.getActivityToken(), new ActivityContainerCallback(this)));
} catch (RemoteException e) {
throw new RuntimeException("ActivityView: Unable to create ActivityContainer. "
diff --git a/core/java/android/app/ApplicationThreadNative.java b/core/java/android/app/ApplicationThreadNative.java
index eb3ddb2..b6989ab 100644
--- a/core/java/android/app/ApplicationThreadNative.java
+++ b/core/java/android/app/ApplicationThreadNative.java
@@ -140,6 +140,10 @@ public abstract class ApplicationThreadNative extends Binder
int ident = data.readInt();
ActivityInfo info = ActivityInfo.CREATOR.createFromParcel(data);
Configuration curConfig = Configuration.CREATOR.createFromParcel(data);
+ Configuration overrideConfig = null;
+ if (data.readInt() != 0) {
+ overrideConfig = Configuration.CREATOR.createFromParcel(data);
+ }
CompatibilityInfo compatInfo = CompatibilityInfo.CREATOR.createFromParcel(data);
String referrer = data.readString();
IVoiceInteractor voiceInteractor = IVoiceInteractor.Stub.asInterface(
@@ -153,8 +157,8 @@ public abstract class ApplicationThreadNative extends Binder
boolean isForward = data.readInt() != 0;
ProfilerInfo profilerInfo = data.readInt() != 0
? ProfilerInfo.CREATOR.createFromParcel(data) : null;
- scheduleLaunchActivity(intent, b, ident, info, curConfig, compatInfo, referrer,
- voiceInteractor, procState, state, persistentState, ri, pi,
+ scheduleLaunchActivity(intent, b, ident, info, curConfig, overrideConfig, compatInfo,
+ referrer, voiceInteractor, procState, state, persistentState, ri, pi,
notResumed, isForward, profilerInfo);
return true;
}
@@ -167,14 +171,15 @@ public abstract class ApplicationThreadNative extends Binder
List<ReferrerIntent> pi = data.createTypedArrayList(ReferrerIntent.CREATOR);
int configChanges = data.readInt();
boolean notResumed = data.readInt() != 0;
- Configuration config = null;
+ Configuration config = Configuration.CREATOR.createFromParcel(data);
+ Configuration overrideConfig = null;
if (data.readInt() != 0) {
- config = Configuration.CREATOR.createFromParcel(data);
+ overrideConfig = Configuration.CREATOR.createFromParcel(data);
}
- scheduleRelaunchActivity(b, ri, pi, configChanges, notResumed, config);
+ scheduleRelaunchActivity(b, ri, pi, configChanges, notResumed, config, overrideConfig);
return true;
}
-
+
case SCHEDULE_NEW_INTENT_TRANSACTION:
{
data.enforceInterface(IApplicationThread.descriptor);
@@ -404,7 +409,11 @@ public abstract class ApplicationThreadNative extends Binder
{
data.enforceInterface(IApplicationThread.descriptor);
IBinder b = data.readStrongBinder();
- scheduleActivityConfigurationChanged(b);
+ Configuration overrideConfig = null;
+ if (data.readInt() != 0) {
+ overrideConfig = Configuration.CREATOR.createFromParcel(data);
+ }
+ scheduleActivityConfigurationChanged(b, overrideConfig);
return true;
}
@@ -775,11 +784,11 @@ class ApplicationThreadProxy implements IApplicationThread {
}
public final void scheduleLaunchActivity(Intent intent, IBinder token, int ident,
- ActivityInfo info, Configuration curConfig, CompatibilityInfo compatInfo,
- String referrer, IVoiceInteractor voiceInteractor, int procState, Bundle state,
- PersistableBundle persistentState, List<ResultInfo> pendingResults,
- List<ReferrerIntent> pendingNewIntents, boolean notResumed, boolean isForward,
- ProfilerInfo profilerInfo) throws RemoteException {
+ ActivityInfo info, Configuration curConfig, Configuration overrideConfig,
+ CompatibilityInfo compatInfo, String referrer, IVoiceInteractor voiceInteractor,
+ int procState, Bundle state, PersistableBundle persistentState,
+ List<ResultInfo> pendingResults, List<ReferrerIntent> pendingNewIntents,
+ boolean notResumed, boolean isForward, ProfilerInfo profilerInfo) throws RemoteException {
Parcel data = Parcel.obtain();
data.writeInterfaceToken(IApplicationThread.descriptor);
intent.writeToParcel(data, 0);
@@ -787,6 +796,12 @@ class ApplicationThreadProxy implements IApplicationThread {
data.writeInt(ident);
info.writeToParcel(data, 0);
curConfig.writeToParcel(data, 0);
+ if (overrideConfig != null) {
+ data.writeInt(1);
+ overrideConfig.writeToParcel(data, 0);
+ } else {
+ data.writeInt(0);
+ }
compatInfo.writeToParcel(data, 0);
data.writeString(referrer);
data.writeStrongBinder(voiceInteractor != null ? voiceInteractor.asBinder() : null);
@@ -810,8 +825,8 @@ class ApplicationThreadProxy implements IApplicationThread {
public final void scheduleRelaunchActivity(IBinder token,
List<ResultInfo> pendingResults, List<ReferrerIntent> pendingNewIntents,
- int configChanges, boolean notResumed, Configuration config)
- throws RemoteException {
+ int configChanges, boolean notResumed, Configuration config,
+ Configuration overrideConfig) throws RemoteException {
Parcel data = Parcel.obtain();
data.writeInterfaceToken(IApplicationThread.descriptor);
data.writeStrongBinder(token);
@@ -819,9 +834,10 @@ class ApplicationThreadProxy implements IApplicationThread {
data.writeTypedList(pendingNewIntents);
data.writeInt(configChanges);
data.writeInt(notResumed ? 1 : 0);
- if (config != null) {
+ config.writeToParcel(data, 0);
+ if (overrideConfig != null) {
data.writeInt(1);
- config.writeToParcel(data, 0);
+ overrideConfig.writeToParcel(data, 0);
} else {
data.writeInt(0);
}
@@ -1104,6 +1120,7 @@ class ApplicationThreadProxy implements IApplicationThread {
data.recycle();
}
+ @Override
public final void scheduleLowMemory() throws RemoteException {
Parcel data = Parcel.obtain();
data.writeInterfaceToken(IApplicationThread.descriptor);
@@ -1112,16 +1129,24 @@ class ApplicationThreadProxy implements IApplicationThread {
data.recycle();
}
+ @Override
public final void scheduleActivityConfigurationChanged(
- IBinder token) throws RemoteException {
+ IBinder token, Configuration overrideConfig) throws RemoteException {
Parcel data = Parcel.obtain();
data.writeInterfaceToken(IApplicationThread.descriptor);
data.writeStrongBinder(token);
+ if (overrideConfig != null) {
+ data.writeInt(1);
+ overrideConfig.writeToParcel(data, 0);
+ } else {
+ data.writeInt(0);
+ }
mRemote.transact(SCHEDULE_ACTIVITY_CONFIGURATION_CHANGED_TRANSACTION, data, null,
IBinder.FLAG_ONEWAY);
data.recycle();
}
+ @Override
public void profilerControl(boolean start, ProfilerInfo profilerInfo, int profileType)
throws RemoteException {
Parcel data = Parcel.obtain();
diff --git a/core/java/android/app/AssistData.java b/core/java/android/app/AssistData.java
new file mode 100644
index 0000000..8d3d348
--- /dev/null
+++ b/core/java/android/app/AssistData.java
@@ -0,0 +1,519 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app;
+
+import android.graphics.Rect;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.Log;
+import android.view.View;
+import android.view.ViewAssistData;
+import android.view.ViewGroup;
+import android.view.ViewRootImpl;
+import android.view.WindowManagerGlobal;
+import android.widget.Checkable;
+
+import java.util.ArrayList;
+
+/**
+ * Assist data automatically created by the platform's implementation
+ * of {@link Activity#onProvideAssistData}. Retrieve it from the assist
+ * data with {@link #getAssistData(android.os.Bundle)}.
+ */
+final public class AssistData implements Parcelable {
+ static final String TAG = "AssistData";
+
+ /**
+ * Key name this data structure is stored in the Bundle generated by
+ * {@link Activity#onProvideAssistData}.
+ */
+ public static final String ASSIST_KEY = "android:assist";
+
+ final ArrayList<ViewNodeImpl> mRootViews = new ArrayList<>();
+
+ ViewAssistDataImpl mTmpViewAssistDataImpl = new ViewAssistDataImpl();
+ Bundle mTmpExtras = new Bundle();
+
+ final static class ViewAssistDataImpl extends ViewAssistData {
+ CharSequence mText;
+ int mTextSelectionStart = -1;
+ int mTextSelectionEnd = -1;
+ CharSequence mHint;
+
+ @Override
+ public void setText(CharSequence text) {
+ mText = text;
+ mTextSelectionStart = mTextSelectionEnd = -1;
+ }
+
+ @Override
+ public void setText(CharSequence text, int selectionStart, int selectionEnd) {
+ mText = text;
+ mTextSelectionStart = selectionStart;
+ mTextSelectionEnd = selectionEnd;
+ }
+
+ @Override
+ public void setHint(CharSequence hint) {
+ mHint = hint;
+ }
+
+ @Override
+ public CharSequence getText() {
+ return mText;
+ }
+
+ @Override
+ public int getTextSelectionStart() {
+ return mTextSelectionStart;
+ }
+
+ @Override
+ public int getTextSelectionEnd() {
+ return mTextSelectionEnd;
+ }
+
+ @Override
+ public CharSequence getHint() {
+ return mHint;
+ }
+ }
+
+ final static class ViewNodeTextImpl {
+ final String mText;
+ final int mTextSelectionStart;
+ final int mTextSelectionEnd;
+ final String mHint;
+
+ ViewNodeTextImpl(ViewAssistDataImpl data) {
+ mText = data.mText != null ? data.mText.toString() : null;
+ mTextSelectionStart = data.mTextSelectionStart;
+ mTextSelectionEnd = data.mTextSelectionEnd;
+ mHint = data.mHint != null ? data.mHint.toString() : null;
+ }
+
+ ViewNodeTextImpl(Parcel in) {
+ mText = in.readString();
+ mTextSelectionStart = in.readInt();
+ mTextSelectionEnd = in.readInt();
+ mHint = in.readString();
+ }
+
+ void writeToParcel(Parcel out) {
+ out.writeString(mText);
+ out.writeInt(mTextSelectionStart);
+ out.writeInt(mTextSelectionEnd);
+ out.writeString(mHint);
+ }
+ }
+
+ final static class ViewNodeImpl {
+ final int mX;
+ final int mY;
+ final int mScrollX;
+ final int mScrollY;
+ final int mWidth;
+ final int mHeight;
+
+ static final int FLAGS_DISABLED = 0x00000001;
+ static final int FLAGS_VISIBILITY_MASK = View.VISIBLE|View.INVISIBLE|View.GONE;
+ static final int FLAGS_FOCUSABLE = 0x00000010;
+ static final int FLAGS_FOCUSED = 0x00000020;
+ static final int FLAGS_ACCESSIBILITY_FOCUSED = 0x04000000;
+ static final int FLAGS_SELECTED = 0x00000040;
+ static final int FLAGS_ACTIVATED = 0x40000000;
+ static final int FLAGS_CHECKABLE = 0x00000100;
+ static final int FLAGS_CHECKED = 0x00000200;
+ static final int FLAGS_CLICKABLE = 0x00004000;
+ static final int FLAGS_LONG_CLICKABLE = 0x00200000;
+
+ final int mFlags;
+
+ final String mClassName;
+ final String mContentDescription;
+
+ final ViewNodeTextImpl mText;
+ final Bundle mExtras;
+
+ final ViewNodeImpl[] mChildren;
+
+ ViewNodeImpl(AssistData assistData, View view, int left, int top,
+ CharSequence contentDescription) {
+ mX = left;
+ mY = top;
+ mScrollX = view.getScrollX();
+ mScrollY = view.getScrollY();
+ mWidth = view.getWidth();
+ mHeight = view.getHeight();
+ int flags = view.getVisibility();
+ if (!view.isEnabled()) {
+ flags |= FLAGS_DISABLED;
+ }
+ if (!view.isClickable()) {
+ flags |= FLAGS_CLICKABLE;
+ }
+ if (!view.isFocusable()) {
+ flags |= FLAGS_FOCUSABLE;
+ }
+ if (!view.isFocused()) {
+ flags |= FLAGS_FOCUSED;
+ }
+ if (!view.isAccessibilityFocused()) {
+ flags |= FLAGS_ACCESSIBILITY_FOCUSED;
+ }
+ if (!view.isSelected()) {
+ flags |= FLAGS_SELECTED;
+ }
+ if (!view.isActivated()) {
+ flags |= FLAGS_ACTIVATED;
+ }
+ if (!view.isLongClickable()) {
+ flags |= FLAGS_LONG_CLICKABLE;
+ }
+ if (view instanceof Checkable) {
+ flags |= FLAGS_CHECKABLE;
+ if (((Checkable)view).isChecked()) {
+ flags |= FLAGS_CHECKED;
+ }
+ }
+ mFlags = flags;
+ mClassName = view.getAccessibilityClassName().toString();
+ mContentDescription = contentDescription != null ? contentDescription.toString() : null;
+ final ViewAssistDataImpl viewData = assistData.mTmpViewAssistDataImpl;
+ final Bundle extras = assistData.mTmpExtras;
+ view.onProvideAssistData(viewData, extras);
+ if (viewData.mText != null || viewData.mHint != null) {
+ mText = new ViewNodeTextImpl(viewData);
+ assistData.mTmpViewAssistDataImpl = new ViewAssistDataImpl();
+ } else {
+ mText = null;
+ }
+ if (!extras.isEmpty()) {
+ mExtras = extras;
+ assistData.mTmpExtras = new Bundle();
+ } else {
+ mExtras = null;
+ }
+ if (view instanceof ViewGroup) {
+ ViewGroup vg = (ViewGroup)view;
+ final int NCHILDREN = vg.getChildCount();
+ if (NCHILDREN > 0) {
+ mChildren = new ViewNodeImpl[NCHILDREN];
+ for (int i=0; i<NCHILDREN; i++) {
+ mChildren[i] = new ViewNodeImpl(assistData, vg.getChildAt(i));
+ }
+ } else {
+ mChildren = null;
+ }
+ } else {
+ mChildren = null;
+ }
+ }
+
+ ViewNodeImpl(AssistData assistData, View view) {
+ this(assistData, view, view.getLeft(), view.getTop(), view.getContentDescription());
+ }
+
+ ViewNodeImpl(Parcel in) {
+ mX = in.readInt();
+ mY = in.readInt();
+ mScrollX = in.readInt();
+ mScrollY = in.readInt();
+ mWidth = in.readInt();
+ mHeight = in.readInt();
+ mFlags = in.readInt();
+ mClassName = in.readString();
+ mContentDescription = in.readString();
+ if (in.readInt() != 0) {
+ mText = new ViewNodeTextImpl(in);
+ } else {
+ mText = null;
+ }
+ mExtras = in.readBundle();
+ final int NCHILDREN = in.readInt();
+ if (NCHILDREN > 0) {
+ mChildren = new ViewNodeImpl[NCHILDREN];
+ for (int i=0; i<NCHILDREN; i++) {
+ mChildren[i] = new ViewNodeImpl(in);
+ }
+ } else {
+ mChildren = null;
+ }
+ }
+
+ void writeToParcel(Parcel out) {
+ out.writeInt(mX);
+ out.writeInt(mY);
+ out.writeInt(mScrollX);
+ out.writeInt(mScrollY);
+ out.writeInt(mWidth);
+ out.writeInt(mHeight);
+ out.writeInt(mFlags);
+ out.writeString(mClassName);
+ out.writeString(mContentDescription);
+ if (mText != null) {
+ out.writeInt(1);
+ mText.writeToParcel(out);
+ } else {
+ out.writeInt(0);
+ }
+ out.writeBundle(mExtras);
+ if (mChildren != null) {
+ final int NCHILDREN = mChildren.length;
+ out.writeInt(NCHILDREN);
+ for (int i=0; i<NCHILDREN; i++) {
+ mChildren[i].writeToParcel(out);
+ }
+ } else {
+ out.writeInt(0);
+ }
+ }
+ }
+
+ /**
+ * Provides access to information about a single view in the assist data.
+ */
+ static public class ViewNode {
+ ViewNodeImpl mImpl;
+
+ public ViewNode() {
+ }
+
+ public int getLeft() {
+ return mImpl.mX;
+ }
+
+ public int getTop() {
+ return mImpl.mY;
+ }
+
+ public int getScrollX() {
+ return mImpl.mScrollX;
+ }
+
+ public int getScrollY() {
+ return mImpl.mScrollY;
+ }
+
+ public int getWidth() {
+ return mImpl.mWidth;
+ }
+
+ public int getHeight() {
+ return mImpl.mHeight;
+ }
+
+ public int getVisibility() {
+ return mImpl.mFlags&ViewNodeImpl.FLAGS_VISIBILITY_MASK;
+ }
+
+ public boolean isEnabled() {
+ return (mImpl.mFlags&ViewNodeImpl.FLAGS_DISABLED) == 0;
+ }
+
+ public boolean isClickable() {
+ return (mImpl.mFlags&ViewNodeImpl.FLAGS_CLICKABLE) != 0;
+ }
+
+ public boolean isFocusable() {
+ return (mImpl.mFlags&ViewNodeImpl.FLAGS_FOCUSABLE) != 0;
+ }
+
+ public boolean isFocused() {
+ return (mImpl.mFlags&ViewNodeImpl.FLAGS_FOCUSED) != 0;
+ }
+
+ public boolean isAccessibilityFocused() {
+ return (mImpl.mFlags&ViewNodeImpl.FLAGS_ACCESSIBILITY_FOCUSED) != 0;
+ }
+
+ public boolean isCheckable() {
+ return (mImpl.mFlags&ViewNodeImpl.FLAGS_CHECKABLE) != 0;
+ }
+
+ public boolean isChecked() {
+ return (mImpl.mFlags&ViewNodeImpl.FLAGS_CHECKED) != 0;
+ }
+
+ public boolean isSelected() {
+ return (mImpl.mFlags&ViewNodeImpl.FLAGS_SELECTED) != 0;
+ }
+
+ public boolean isActivated() {
+ return (mImpl.mFlags&ViewNodeImpl.FLAGS_ACTIVATED) != 0;
+ }
+
+ public boolean isLongClickable() {
+ return (mImpl.mFlags&ViewNodeImpl.FLAGS_LONG_CLICKABLE) != 0;
+ }
+
+ public String getClassName() {
+ return mImpl.mClassName;
+ }
+
+ public String getContentDescription() {
+ return mImpl.mContentDescription;
+ }
+
+ public String getText() {
+ return mImpl.mText != null ? mImpl.mText.mText : null;
+ }
+
+ public int getTextSelectionStart() {
+ return mImpl.mText != null ? mImpl.mText.mTextSelectionStart : -1;
+ }
+
+ public int getTextSelectionEnd() {
+ return mImpl.mText != null ? mImpl.mText.mTextSelectionEnd : -1;
+ }
+
+ public String getHint() {
+ return mImpl.mText != null ? mImpl.mText.mHint : null;
+ }
+
+ public Bundle getExtras() {
+ return mImpl.mExtras;
+ }
+
+ public int getChildCount() {
+ return mImpl.mChildren != null ? mImpl.mChildren.length : 0;
+ }
+
+ public void getChildAt(int index, ViewNode outNode) {
+ outNode.mImpl = mImpl.mChildren[index];
+ }
+ }
+
+ AssistData(Activity activity) {
+ ArrayList<ViewRootImpl> views = WindowManagerGlobal.getInstance().getRootViews(
+ activity.getActivityToken());
+ for (int i=0; i<views.size(); i++) {
+ ViewRootImpl root = views.get(i);
+ View view = root.getView();
+ Rect rect = new Rect();
+ view.getBoundsOnScreen(rect);
+ CharSequence title = root.getTitle();
+ mRootViews.add(new ViewNodeImpl(this, view, rect.left, rect.top,
+ title != null ? title : view.getContentDescription()));
+ }
+ }
+
+ AssistData(Parcel in) {
+ final int N = in.readInt();
+ for (int i=0; i<N; i++) {
+ mRootViews.add(new ViewNodeImpl(in));
+ }
+ //dump();
+ }
+
+ void dump() {
+ ViewNode node = new ViewNode();
+ final int N = getWindowCount();
+ for (int i=0; i<N; i++) {
+ Log.i(TAG, "Window #" + i + ":");
+ getWindowAt(i, node);
+ dump(" ", node);
+ }
+ }
+
+ void dump(String prefix, ViewNode node) {
+ Log.i(TAG, prefix + "View [" + node.getLeft() + "," + node.getTop()
+ + " " + node.getWidth() + "x" + node.getHeight() + "]" + " " + node.getClassName());
+ int scrollX = node.getScrollX();
+ int scrollY = node.getScrollY();
+ if (scrollX != 0 || scrollY != 0) {
+ Log.i(TAG, prefix + " Scroll: " + scrollX + "," + scrollY);
+ }
+ String contentDescription = node.getContentDescription();
+ if (contentDescription != null) {
+ Log.i(TAG, prefix + " Content description: " + contentDescription);
+ }
+ String text = node.getText();
+ if (text != null) {
+ Log.i(TAG, prefix + " Text (sel " + node.getTextSelectionStart() + "-"
+ + node.getTextSelectionEnd() + "): " + text);
+ }
+ String hint = node.getHint();
+ if (hint != null) {
+ Log.i(TAG, prefix + " Hint: " + hint);
+ }
+ Bundle extras = node.getExtras();
+ if (extras != null) {
+ Log.i(TAG, prefix + " Extras: " + extras);
+ }
+ final int NCHILDREN = node.getChildCount();
+ if (NCHILDREN > 0) {
+ Log.i(TAG, prefix + " Children:");
+ String cprefix = prefix + " ";
+ ViewNode cnode = new ViewNode();
+ for (int i=0; i<NCHILDREN; i++) {
+ node.getChildAt(i, cnode);
+ dump(cprefix, cnode);
+ }
+ }
+ }
+
+ /**
+ * Retrieve the framework-generated AssistData that is stored within
+ * the Bundle filled in by {@link Activity#onProvideAssistData}.
+ */
+ public static AssistData getAssistData(Bundle assistBundle) {
+ return assistBundle.getParcelable(ASSIST_KEY);
+ }
+
+ /**
+ * Return the number of window contents that have been collected in this assist data.
+ */
+ public int getWindowCount() {
+ return mRootViews.size();
+ }
+
+ /**
+ * Return the root view for one of the windows in the assist data.
+ * @param index Which window to retrieve, may be 0 to {@link #getWindowCount()}-1.
+ * @param outNode Node in which to place the window's root view.
+ */
+ public void getWindowAt(int index, ViewNode outNode) {
+ outNode.mImpl = mRootViews.get(index);
+ }
+
+ public int describeContents() {
+ return 0;
+ }
+
+ public void writeToParcel(Parcel out, int flags) {
+ int start = out.dataPosition();
+ final int N = mRootViews.size();
+ out.writeInt(N);
+ for (int i=0; i<N; i++) {
+ mRootViews.get(i).writeToParcel(out);
+ }
+ Log.i(TAG, "Flattened assist data: " + (out.dataPosition() - start) + " bytes");
+ }
+
+ public static final Parcelable.Creator<AssistData> CREATOR
+ = new Parcelable.Creator<AssistData>() {
+ public AssistData createFromParcel(Parcel in) {
+ return new AssistData(in);
+ }
+
+ public AssistData[] newArray(int size) {
+ return new AssistData[size];
+ }
+ };
+}
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index 2ef046d..6c78cab 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -24,7 +24,6 @@ import android.service.persistentdata.IPersistentDataBlockService;
import android.service.persistentdata.PersistentDataBlockManager;
import com.android.internal.appwidget.IAppWidgetService;
-import com.android.internal.policy.PolicyManager;
import com.android.internal.util.Preconditions;
import android.bluetooth.BluetoothManager;
@@ -80,6 +79,8 @@ import android.media.projection.MediaProjectionManager;
import android.media.session.MediaSessionManager;
import android.media.tv.ITvInputManager;
import android.media.tv.TvInputManager;
+import android.midi.IMidiManager;
+import android.midi.MidiManager;
import android.net.ConnectivityManager;
import android.net.IConnectivityManager;
import android.net.EthernetManager;
@@ -92,6 +93,8 @@ import android.net.nsd.INsdManager;
import android.net.nsd.NsdManager;
import android.net.wifi.IWifiManager;
import android.net.wifi.WifiManager;
+import android.net.wifi.passpoint.IWifiPasspointManager;
+import android.net.wifi.passpoint.WifiPasspointManager;
import android.net.wifi.p2p.IWifiP2pManager;
import android.net.wifi.p2p.WifiP2pManager;
import android.net.wifi.IWifiScanner;
@@ -135,6 +138,7 @@ import android.util.Slog;
import android.view.DisplayAdjustments;
import android.view.ContextThemeWrapper;
import android.view.Display;
+import android.view.PhoneLayoutInflater;
import android.view.WindowManagerImpl;
import android.view.accessibility.AccessibilityManager;
import android.view.accessibility.CaptioningManager;
@@ -231,7 +235,6 @@ class ContextImpl extends Context {
private final Resources mResources;
private final Display mDisplay; // may be null if default display
private final DisplayAdjustments mDisplayAdjustments = new DisplayAdjustments();
- private final Configuration mOverrideConfiguration;
private final boolean mRestricted;
@@ -476,7 +479,7 @@ class ContextImpl extends Context {
registerService(LAYOUT_INFLATER_SERVICE, new ServiceFetcher() {
public Object createService(ContextImpl ctx) {
- return PolicyManager.makeNewLayoutInflater(ctx.getOuterContext());
+ return new PhoneLayoutInflater(ctx.getOuterContext());
}});
registerService(LOCATION_SERVICE, new ServiceFetcher() {
@@ -603,6 +606,13 @@ class ContextImpl extends Context {
return new WifiManager(ctx.getOuterContext(), service);
}});
+ registerService(WIFI_PASSPOINT_SERVICE, new ServiceFetcher() {
+ public Object createService(ContextImpl ctx) {
+ IBinder b = ServiceManager.getService(WIFI_PASSPOINT_SERVICE);
+ IWifiPasspointManager service = IWifiPasspointManager.Stub.asInterface(b);
+ return new WifiPasspointManager(ctx.getOuterContext(), service);
+ }});
+
registerService(WIFI_P2P_SERVICE, new ServiceFetcher() {
public Object createService(ContextImpl ctx) {
IBinder b = ServiceManager.getService(WIFI_P2P_SERVICE);
@@ -768,6 +778,12 @@ class ContextImpl extends Context {
IBinder b = ServiceManager.getService(APPWIDGET_SERVICE);
return new AppWidgetManager(ctx, IAppWidgetService.Stub.asInterface(b));
}});
+
+ registerService(MIDI_SERVICE, new ServiceFetcher() {
+ public Object createService(ContextImpl ctx) {
+ IBinder b = ServiceManager.getService(MIDI_SERVICE);
+ return new MidiManager(ctx, IMidiManager.Stub.asInterface(b));
+ }});
}
static ContextImpl getImpl(Context context) {
@@ -2132,7 +2148,7 @@ class ContextImpl extends Context {
final boolean restricted = (flags & CONTEXT_RESTRICTED) == CONTEXT_RESTRICTED;
ContextImpl c = new ContextImpl(this, mMainThread, pi, mActivityToken,
new UserHandle(UserHandle.getUserId(application.uid)), restricted,
- mDisplay, mOverrideConfiguration);
+ mDisplay, null);
if (c.mResources != null) {
return c;
}
@@ -2155,14 +2171,14 @@ class ContextImpl extends Context {
final boolean restricted = (flags & CONTEXT_RESTRICTED) == CONTEXT_RESTRICTED;
if (packageName.equals("system") || packageName.equals("android")) {
return new ContextImpl(this, mMainThread, mPackageInfo, mActivityToken,
- user, restricted, mDisplay, mOverrideConfiguration);
+ user, restricted, mDisplay, null);
}
LoadedApk pi = mMainThread.getPackageInfo(packageName, mResources.getCompatibilityInfo(),
flags | CONTEXT_REGISTER_PACKAGE, user.getIdentifier());
if (pi != null) {
ContextImpl c = new ContextImpl(this, mMainThread, pi, mActivityToken,
- user, restricted, mDisplay, mOverrideConfiguration);
+ user, restricted, mDisplay, null);
if (c.mResources != null) {
return c;
}
@@ -2190,7 +2206,7 @@ class ContextImpl extends Context {
}
return new ContextImpl(this, mMainThread, mPackageInfo, mActivityToken,
- mUser, mRestricted, display, mOverrideConfiguration);
+ mUser, mRestricted, display, null);
}
private int getDisplayId() {
@@ -2247,11 +2263,10 @@ class ContextImpl extends Context {
}
static ContextImpl createActivityContext(ActivityThread mainThread,
- LoadedApk packageInfo, IBinder activityToken) {
+ LoadedApk packageInfo, Configuration overrideConfiguration) {
if (packageInfo == null) throw new IllegalArgumentException("packageInfo");
- if (activityToken == null) throw new IllegalArgumentException("activityInfo");
- return new ContextImpl(null, mainThread,
- packageInfo, activityToken, null, false, null, null);
+ return new ContextImpl(null, mainThread, packageInfo, null, null, false, null,
+ overrideConfiguration);
}
private ContextImpl(ContextImpl container, ActivityThread mainThread,
@@ -2271,7 +2286,6 @@ class ContextImpl extends Context {
mPackageInfo = packageInfo;
mResourcesManager = ResourcesManager.getInstance();
mDisplay = display;
- mOverrideConfiguration = overrideConfiguration;
final int displayId = getDisplayId();
CompatibilityInfo compatInfo = null;
@@ -2286,15 +2300,14 @@ class ContextImpl extends Context {
Resources resources = packageInfo.getResources(mainThread);
if (resources != null) {
- if (activityToken != null
- || displayId != Display.DEFAULT_DISPLAY
+ if (displayId != Display.DEFAULT_DISPLAY
|| overrideConfiguration != null
|| (compatInfo != null && compatInfo.applicationScale
!= resources.getCompatibilityInfo().applicationScale)) {
resources = mResourcesManager.getTopLevelResources(packageInfo.getResDir(),
packageInfo.getSplitResDirs(), packageInfo.getOverlayDirs(),
packageInfo.getApplicationInfo().sharedLibraryFiles, displayId,
- overrideConfiguration, compatInfo, activityToken);
+ overrideConfiguration, compatInfo);
}
}
mResources = resources;
diff --git a/core/java/android/app/DatePickerDialog.java b/core/java/android/app/DatePickerDialog.java
index f79d32b..3fbbdff 100644
--- a/core/java/android/app/DatePickerDialog.java
+++ b/core/java/android/app/DatePickerDialog.java
@@ -131,6 +131,9 @@ public class DatePickerDialog extends AlertDialog implements OnClickListener,
switch (which) {
case BUTTON_POSITIVE:
if (mDateSetListener != null) {
+ // Clearing focus forces the dialog to commit any pending
+ // changes, e.g. typed text in a NumberPicker.
+ mDatePicker.clearFocus();
mDateSetListener.onDateSet(mDatePicker, mDatePicker.getYear(),
mDatePicker.getMonth(), mDatePicker.getDayOfMonth());
}
diff --git a/core/java/android/app/Dialog.java b/core/java/android/app/Dialog.java
index 067073a..a3662b2 100644
--- a/core/java/android/app/Dialog.java
+++ b/core/java/android/app/Dialog.java
@@ -16,14 +16,14 @@
package android.app;
-import android.content.pm.ApplicationInfo;
import com.android.internal.app.WindowDecorActionBar;
-import com.android.internal.policy.PolicyManager;
+import android.annotation.Nullable;
import android.content.ComponentName;
import android.content.Context;
import android.content.ContextWrapper;
import android.content.DialogInterface;
+import android.content.pm.ApplicationInfo;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Bundle;
@@ -42,6 +42,7 @@ import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.MotionEvent;
+import android.view.PhoneWindow;
import android.view.View;
import android.view.View.OnCreateContextMenuListener;
import android.view.ViewGroup;
@@ -134,14 +135,14 @@ public class Dialog implements DialogInterface, Window.Callback,
/**
* Create a Dialog window that uses a custom dialog style.
- *
+ *
* @param context The Context in which the Dialog should run. In particular, it
* uses the window manager and theme from this context to
* present its UI.
- * @param theme A style resource describing the theme to use for the
- * window. See <a href="{@docRoot}guide/topics/resources/available-resources.html#stylesandthemes">Style
- * and Theme Resources</a> for more information about defining and using
- * styles. This theme is applied on top of the current theme in
+ * @param theme A style resource describing the theme to use for the
+ * window. See <a href="{@docRoot}guide/topics/resources/available-resources.html#stylesandthemes">Style
+ * and Theme Resources</a> for more information about defining and using
+ * styles. This theme is applied on top of the current theme in
* <var>context</var>. If 0, the default dialog theme will be used.
*/
public Dialog(Context context, int theme) {
@@ -162,7 +163,7 @@ public class Dialog implements DialogInterface, Window.Callback,
}
mWindowManager = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);
- Window w = PolicyManager.makeNewWindow(mContext);
+ Window w = new PhoneWindow(mContext);
mWindow = w;
w.setCallback(this);
w.setOnWindowDismissedCallback(this);
@@ -476,6 +477,7 @@ public class Dialog implements DialogInterface, Window.Callback,
* @param id the identifier of the view to find
* @return The view with the given id or null.
*/
+ @Nullable
public View findViewById(int id) {
return mWindow.findViewById(id);
}
diff --git a/core/java/android/app/Fragment.java b/core/java/android/app/Fragment.java
index ab28d95..f319309 100644
--- a/core/java/android/app/Fragment.java
+++ b/core/java/android/app/Fragment.java
@@ -2008,6 +2008,7 @@ public class Fragment implements ComponentCallbacks2, OnCreateContextMenuListene
mChildFragmentManager = new FragmentManagerImpl();
mChildFragmentManager.attachActivity(mActivity, new FragmentContainer() {
@Override
+ @Nullable
public View findViewById(int id) {
if (mView == null) {
throw new IllegalStateException("Fragment does not have a view");
diff --git a/core/java/android/app/FragmentManager.java b/core/java/android/app/FragmentManager.java
index ccceef4..afdc917 100644
--- a/core/java/android/app/FragmentManager.java
+++ b/core/java/android/app/FragmentManager.java
@@ -19,6 +19,7 @@ package android.app;
import android.animation.Animator;
import android.animation.AnimatorInflater;
import android.animation.AnimatorListenerAdapter;
+import android.annotation.Nullable;
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.TypedArray;
@@ -394,6 +395,7 @@ final class FragmentManagerState implements Parcelable {
* Callbacks from FragmentManagerImpl to its container.
*/
interface FragmentContainer {
+ @Nullable
public View findViewById(int id);
public boolean hasView();
}
diff --git a/core/java/android/app/IActivityContainer.aidl b/core/java/android/app/IActivityContainer.aidl
index 52884f7..ff1175f 100644
--- a/core/java/android/app/IActivityContainer.aidl
+++ b/core/java/android/app/IActivityContainer.aidl
@@ -32,6 +32,7 @@ interface IActivityContainer {
void checkEmbeddedAllowed(in Intent intent);
void checkEmbeddedAllowedIntentSender(in IIntentSender intentSender);
int getDisplayId();
+ int getStackId();
boolean injectEvent(in InputEvent event);
void release();
}
diff --git a/core/java/android/app/IActivityManager.java b/core/java/android/app/IActivityManager.java
index 70c14c6..467c99a 100644
--- a/core/java/android/app/IActivityManager.java
+++ b/core/java/android/app/IActivityManager.java
@@ -131,7 +131,6 @@ public interface IActivityManager extends IInterface {
public List<ActivityManager.ProcessErrorStateInfo> getProcessesInErrorState()
throws RemoteException;
public void moveTaskToFront(int task, int flags, Bundle options) throws RemoteException;
- public void moveTaskToBack(int task) throws RemoteException;
public boolean moveActivityTaskToBack(IBinder token, boolean nonRoot) throws RemoteException;
public void moveTaskBackwards(int task) throws RemoteException;
public void moveTaskToStack(int taskId, int stackId, boolean toTop) throws RemoteException;
@@ -140,6 +139,7 @@ public interface IActivityManager extends IInterface {
public StackInfo getStackInfo(int stackId) throws RemoteException;
public boolean isInHomeStack(int taskId) throws RemoteException;
public void setFocusedStack(int stackId) throws RemoteException;
+ public int getFocusedStackId() throws RemoteException;
public void registerTaskStackListener(ITaskStackListener listener) throws RemoteException;
public int getTaskForActivity(IBinder token, boolean onlyRoot) throws RemoteException;
public ContentProviderHolder getContentProvider(IApplicationThread caller,
@@ -434,9 +434,11 @@ public interface IActivityManager extends IInterface {
public void performIdleMaintenance() throws RemoteException;
- public IActivityContainer createActivityContainer(IBinder parentActivityToken,
+ public IActivityContainer createVirtualActivityContainer(IBinder parentActivityToken,
IActivityContainerCallback callback) throws RemoteException;
+ public IActivityContainer createStackOnDisplay(int displayId) throws RemoteException;
+
public void deleteActivityContainer(IActivityContainer container) throws RemoteException;
public IActivityContainer getEnclosingActivityContainer(IBinder activityToken)
@@ -458,6 +460,7 @@ public interface IActivityManager extends IInterface {
public void setTaskDescription(IBinder token, ActivityManager.TaskDescription values)
throws RemoteException;
+ public void setTaskResizeable(int taskId, boolean resizeable) throws RemoteException;
public Bitmap getTaskDescriptionIcon(String filename) throws RemoteException;
public void startInPlaceAnimationOnFrontMostApplication(ActivityOptions opts)
@@ -599,7 +602,7 @@ public interface IActivityManager extends IInterface {
int GET_CALLING_ACTIVITY_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+21;
int GET_TASKS_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+22;
int MOVE_TASK_TO_FRONT_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+23;
- int MOVE_TASK_TO_BACK_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+24;
+
int MOVE_TASK_BACKWARDS_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+25;
int GET_TASK_FOR_ACTIVITY_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+26;
@@ -739,7 +742,7 @@ public interface IActivityManager extends IInterface {
int KILL_UID_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+164;
int SET_USER_IS_MONKEY_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+165;
int HANG_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+166;
- int CREATE_ACTIVITY_CONTAINER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+167;
+ int CREATE_VIRTUAL_ACTIVITY_CONTAINER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+167;
int MOVE_TASK_TO_STACK_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+168;
int RESIZE_STACK_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+169;
int GET_ALL_STACK_INFOS_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+170;
@@ -798,4 +801,7 @@ public interface IActivityManager extends IInterface {
// Start of M transactions
int NOTIFY_CLEARTEXT_NETWORK_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+280;
+ int CREATE_STACK_ON_DISPLAY = IBinder.FIRST_CALL_TRANSACTION+281;
+ int GET_FOCUSED_STACK_ID_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+282;
+ int SET_TASK_RESIZEABLE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+283;
}
diff --git a/core/java/android/app/IApplicationThread.java b/core/java/android/app/IApplicationThread.java
index 8bf8cd7..3fb82f6 100644
--- a/core/java/android/app/IApplicationThread.java
+++ b/core/java/android/app/IApplicationThread.java
@@ -33,7 +33,6 @@ import android.os.PersistableBundle;
import android.os.RemoteException;
import android.os.IBinder;
import android.os.IInterface;
-import android.service.voice.IVoiceInteractionSession;
import com.android.internal.app.IVoiceInteractor;
import com.android.internal.content.ReferrerIntent;
@@ -59,14 +58,14 @@ public interface IApplicationThread extends IInterface {
throws RemoteException;
void scheduleSendResult(IBinder token, List<ResultInfo> results) throws RemoteException;
void scheduleLaunchActivity(Intent intent, IBinder token, int ident,
- ActivityInfo info, Configuration curConfig, CompatibilityInfo compatInfo,
- String referrer, IVoiceInteractor voiceInteractor, int procState, Bundle state,
- PersistableBundle persistentState, List<ResultInfo> pendingResults,
- List<ReferrerIntent> pendingNewIntents, boolean notResumed, boolean isForward,
- ProfilerInfo profilerInfo) throws RemoteException;
+ ActivityInfo info, Configuration curConfig, Configuration overrideConfig,
+ CompatibilityInfo compatInfo, String referrer, IVoiceInteractor voiceInteractor,
+ int procState, Bundle state, PersistableBundle persistentState,
+ List<ResultInfo> pendingResults, List<ReferrerIntent> pendingNewIntents,
+ boolean notResumed, boolean isForward, ProfilerInfo profilerInfo) throws RemoteException;
void scheduleRelaunchActivity(IBinder token, List<ResultInfo> pendingResults,
- List<ReferrerIntent> pendingNewIntents, int configChanges,
- boolean notResumed, Configuration config) throws RemoteException;
+ List<ReferrerIntent> pendingNewIntents, int configChanges, boolean notResumed,
+ Configuration config, Configuration overrideConfig) throws RemoteException;
void scheduleNewIntent(List<ReferrerIntent> intent, IBinder token) throws RemoteException;
void scheduleDestroyActivity(IBinder token, boolean finished,
int configChanges) throws RemoteException;
@@ -115,7 +114,8 @@ public interface IApplicationThread extends IInterface {
int resultCode, String data, Bundle extras, boolean ordered,
boolean sticky, int sendingUser, int processState) throws RemoteException;
void scheduleLowMemory() throws RemoteException;
- void scheduleActivityConfigurationChanged(IBinder token) throws RemoteException;
+ void scheduleActivityConfigurationChanged(IBinder token, Configuration overrideConfig)
+ throws RemoteException;
void profilerControl(boolean start, ProfilerInfo profilerInfo, int profileType)
throws RemoteException;
void dumpHeap(boolean managed, String path, ParcelFileDescriptor fd)
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 860b9cc..87e744c 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -5065,6 +5065,403 @@ public class Notification implements Parcelable
}
/**
+ * <p>Helper class to add Android Auto extensions to notifications. To create a notification
+ * with car extensions:
+ *
+ * <ol>
+ * <li>Create an {@link Notification.Builder}, setting any desired
+ * properties.
+ * <li>Create a {@link CarExtender}.
+ * <li>Set car-specific properties using the {@code add} and {@code set} methods of
+ * {@link CarExtender}.
+ * <li>Call {@link Notification.Builder#extend(Notification.Extender)}
+ * to apply the extensions to a notification.
+ * </ol>
+ *
+ * <pre class="prettyprint">
+ * Notification notification = new Notification.Builder(context)
+ * ...
+ * .extend(new CarExtender()
+ * .set*(...))
+ * .build();
+ * </pre>
+ *
+ * <p>Car extensions can be accessed on an existing notification by using the
+ * {@code CarExtender(Notification)} constructor, and then using the {@code get} methods
+ * to access values.
+ */
+ public static final class CarExtender implements Extender {
+ private static final String TAG = "CarExtender";
+
+ private static final String EXTRA_CAR_EXTENDER = "android.car.EXTENSIONS";
+ private static final String EXTRA_LARGE_ICON = "large_icon";
+ private static final String EXTRA_CONVERSATION = "car_conversation";
+ private static final String EXTRA_COLOR = "app_color";
+
+ private Bitmap mLargeIcon;
+ private UnreadConversation mUnreadConversation;
+ private int mColor = Notification.COLOR_DEFAULT;
+
+ /**
+ * Create a {@link CarExtender} with default options.
+ */
+ public CarExtender() {
+ }
+
+ /**
+ * Create a {@link CarExtender} from the CarExtender options of an existing Notification.
+ *
+ * @param notif The notification from which to copy options.
+ */
+ public CarExtender(Notification notif) {
+ Bundle carBundle = notif.extras == null ?
+ null : notif.extras.getBundle(EXTRA_CAR_EXTENDER);
+ if (carBundle != null) {
+ mLargeIcon = carBundle.getParcelable(EXTRA_LARGE_ICON);
+ mColor = carBundle.getInt(EXTRA_COLOR, Notification.COLOR_DEFAULT);
+
+ Bundle b = carBundle.getBundle(EXTRA_CONVERSATION);
+ mUnreadConversation = UnreadConversation.getUnreadConversationFromBundle(b);
+ }
+ }
+
+ /**
+ * Apply car extensions to a notification that is being built. This is typically called by
+ * the {@link Notification.Builder#extend(Notification.Extender)}
+ * method of {@link Notification.Builder}.
+ */
+ @Override
+ public Notification.Builder extend(Notification.Builder builder) {
+ Bundle carExtensions = new Bundle();
+
+ if (mLargeIcon != null) {
+ carExtensions.putParcelable(EXTRA_LARGE_ICON, mLargeIcon);
+ }
+ if (mColor != Notification.COLOR_DEFAULT) {
+ carExtensions.putInt(EXTRA_COLOR, mColor);
+ }
+
+ if (mUnreadConversation != null) {
+ Bundle b = mUnreadConversation.getBundleForUnreadConversation();
+ carExtensions.putBundle(EXTRA_CONVERSATION, b);
+ }
+
+ builder.getExtras().putBundle(EXTRA_CAR_EXTENDER, carExtensions);
+ return builder;
+ }
+
+ /**
+ * Sets the accent color to use when Android Auto presents the notification.
+ *
+ * Android Auto uses the color set with {@link Notification.Builder#setColor(int)}
+ * to accent the displayed notification. However, not all colors are acceptable in an
+ * automotive setting. This method can be used to override the color provided in the
+ * notification in such a situation.
+ */
+ public CarExtender setColor(int color) {
+ mColor = color;
+ return this;
+ }
+
+ /**
+ * Gets the accent color.
+ *
+ * @see setColor
+ */
+ public int getColor() {
+ return mColor;
+ }
+
+ /**
+ * Sets the large icon of the car notification.
+ *
+ * If no large icon is set in the extender, Android Auto will display the icon
+ * specified by {@link Notification.Builder#setLargeIcon(android.graphics.Bitmap)}
+ *
+ * @param largeIcon The large icon to use in the car notification.
+ * @return This object for method chaining.
+ */
+ public CarExtender setLargeIcon(Bitmap largeIcon) {
+ mLargeIcon = largeIcon;
+ return this;
+ }
+
+ /**
+ * Gets the large icon used in this car notification, or null if no icon has been set.
+ *
+ * @return The large icon for the car notification.
+ * @see CarExtender#setLargeIcon
+ */
+ public Bitmap getLargeIcon() {
+ return mLargeIcon;
+ }
+
+ /**
+ * Sets the unread conversation in a message notification.
+ *
+ * @param unreadConversation The unread part of the conversation this notification conveys.
+ * @return This object for method chaining.
+ */
+ public CarExtender setUnreadConversation(UnreadConversation unreadConversation) {
+ mUnreadConversation = unreadConversation;
+ return this;
+ }
+
+ /**
+ * Returns the unread conversation conveyed by this notification.
+ * @see #setUnreadConversation(UnreadConversation)
+ */
+ public UnreadConversation getUnreadConversation() {
+ return mUnreadConversation;
+ }
+
+ /**
+ * A class which holds the unread messages from a conversation.
+ */
+ public static class UnreadConversation {
+ private static final String KEY_AUTHOR = "author";
+ private static final String KEY_TEXT = "text";
+ private static final String KEY_MESSAGES = "messages";
+ private static final String KEY_REMOTE_INPUT = "remote_input";
+ private static final String KEY_ON_REPLY = "on_reply";
+ private static final String KEY_ON_READ = "on_read";
+ private static final String KEY_PARTICIPANTS = "participants";
+ private static final String KEY_TIMESTAMP = "timestamp";
+
+ private final String[] mMessages;
+ private final RemoteInput mRemoteInput;
+ private final PendingIntent mReplyPendingIntent;
+ private final PendingIntent mReadPendingIntent;
+ private final String[] mParticipants;
+ private final long mLatestTimestamp;
+
+ UnreadConversation(String[] messages, RemoteInput remoteInput,
+ PendingIntent replyPendingIntent, PendingIntent readPendingIntent,
+ String[] participants, long latestTimestamp) {
+ mMessages = messages;
+ mRemoteInput = remoteInput;
+ mReadPendingIntent = readPendingIntent;
+ mReplyPendingIntent = replyPendingIntent;
+ mParticipants = participants;
+ mLatestTimestamp = latestTimestamp;
+ }
+
+ /**
+ * Gets the list of messages conveyed by this notification.
+ */
+ public String[] getMessages() {
+ return mMessages;
+ }
+
+ /**
+ * Gets the remote input that will be used to convey the response to a message list, or
+ * null if no such remote input exists.
+ */
+ public RemoteInput getRemoteInput() {
+ return mRemoteInput;
+ }
+
+ /**
+ * Gets the pending intent that will be triggered when the user replies to this
+ * notification.
+ */
+ public PendingIntent getReplyPendingIntent() {
+ return mReplyPendingIntent;
+ }
+
+ /**
+ * Gets the pending intent that Android Auto will send after it reads aloud all messages
+ * in this object's message list.
+ */
+ public PendingIntent getReadPendingIntent() {
+ return mReadPendingIntent;
+ }
+
+ /**
+ * Gets the participants in the conversation.
+ */
+ public String[] getParticipants() {
+ return mParticipants;
+ }
+
+ /**
+ * Gets the firs participant in the conversation.
+ */
+ public String getParticipant() {
+ return mParticipants.length > 0 ? mParticipants[0] : null;
+ }
+
+ /**
+ * Gets the timestamp of the conversation.
+ */
+ public long getLatestTimestamp() {
+ return mLatestTimestamp;
+ }
+
+ Bundle getBundleForUnreadConversation() {
+ Bundle b = new Bundle();
+ String author = null;
+ if (mParticipants != null && mParticipants.length > 1) {
+ author = mParticipants[0];
+ }
+ Parcelable[] messages = new Parcelable[mMessages.length];
+ for (int i = 0; i < messages.length; i++) {
+ Bundle m = new Bundle();
+ m.putString(KEY_TEXT, mMessages[i]);
+ m.putString(KEY_AUTHOR, author);
+ messages[i] = m;
+ }
+ b.putParcelableArray(KEY_MESSAGES, messages);
+ if (mRemoteInput != null) {
+ b.putParcelable(KEY_REMOTE_INPUT, mRemoteInput);
+ }
+ b.putParcelable(KEY_ON_REPLY, mReplyPendingIntent);
+ b.putParcelable(KEY_ON_READ, mReadPendingIntent);
+ b.putStringArray(KEY_PARTICIPANTS, mParticipants);
+ b.putLong(KEY_TIMESTAMP, mLatestTimestamp);
+ return b;
+ }
+
+ static UnreadConversation getUnreadConversationFromBundle(Bundle b) {
+ if (b == null) {
+ return null;
+ }
+ Parcelable[] parcelableMessages = b.getParcelableArray(KEY_MESSAGES);
+ String[] messages = null;
+ if (parcelableMessages != null) {
+ String[] tmp = new String[parcelableMessages.length];
+ boolean success = true;
+ for (int i = 0; i < tmp.length; i++) {
+ if (!(parcelableMessages[i] instanceof Bundle)) {
+ success = false;
+ break;
+ }
+ tmp[i] = ((Bundle) parcelableMessages[i]).getString(KEY_TEXT);
+ if (tmp[i] == null) {
+ success = false;
+ break;
+ }
+ }
+ if (success) {
+ messages = tmp;
+ } else {
+ return null;
+ }
+ }
+
+ PendingIntent onRead = b.getParcelable(KEY_ON_READ);
+ PendingIntent onReply = b.getParcelable(KEY_ON_REPLY);
+
+ RemoteInput remoteInput = b.getParcelable(KEY_REMOTE_INPUT);
+
+ String[] participants = b.getStringArray(KEY_PARTICIPANTS);
+ if (participants == null || participants.length != 1) {
+ return null;
+ }
+
+ return new UnreadConversation(messages,
+ remoteInput,
+ onReply,
+ onRead,
+ participants, b.getLong(KEY_TIMESTAMP));
+ }
+ };
+
+ /**
+ * Builder class for {@link CarExtender.UnreadConversation} objects.
+ */
+ public static class Builder {
+ private final List<String> mMessages = new ArrayList<String>();
+ private final String mParticipant;
+ private RemoteInput mRemoteInput;
+ private PendingIntent mReadPendingIntent;
+ private PendingIntent mReplyPendingIntent;
+ private long mLatestTimestamp;
+
+ /**
+ * Constructs a new builder for {@link CarExtender.UnreadConversation}.
+ *
+ * @param name The name of the other participant in the conversation.
+ */
+ public Builder(String name) {
+ mParticipant = name;
+ }
+
+ /**
+ * Appends a new unread message to the list of messages for this conversation.
+ *
+ * The messages should be added from oldest to newest.
+ *
+ * @param message The text of the new unread message.
+ * @return This object for method chaining.
+ */
+ public Builder addMessage(String message) {
+ mMessages.add(message);
+ return this;
+ }
+
+ /**
+ * Sets the pending intent and remote input which will convey the reply to this
+ * notification.
+ *
+ * @param pendingIntent The pending intent which will be triggered on a reply.
+ * @param remoteInput The remote input parcelable which will carry the reply.
+ * @return This object for method chaining.
+ *
+ * @see CarExtender.UnreadConversation#getRemoteInput
+ * @see CarExtender.UnreadConversation#getReplyPendingIntent
+ */
+ public Builder setReplyAction(
+ PendingIntent pendingIntent, RemoteInput remoteInput) {
+ mRemoteInput = remoteInput;
+ mReplyPendingIntent = pendingIntent;
+
+ return this;
+ }
+
+ /**
+ * Sets the pending intent that will be sent once the messages in this notification
+ * are read.
+ *
+ * @param pendingIntent The pending intent to use.
+ * @return This object for method chaining.
+ */
+ public Builder setReadPendingIntent(PendingIntent pendingIntent) {
+ mReadPendingIntent = pendingIntent;
+ return this;
+ }
+
+ /**
+ * Sets the timestamp of the most recent message in an unread conversation.
+ *
+ * If a messaging notification has been posted by your application and has not
+ * yet been cancelled, posting a later notification with the same id and tag
+ * but without a newer timestamp may result in Android Auto not displaying a
+ * heads up notification for the later notification.
+ *
+ * @param timestamp The timestamp of the most recent message in the conversation.
+ * @return This object for method chaining.
+ */
+ public Builder setLatestTimestamp(long timestamp) {
+ mLatestTimestamp = timestamp;
+ return this;
+ }
+
+ /**
+ * Builds a new unread conversation object.
+ *
+ * @return The new unread conversation object.
+ */
+ public UnreadConversation build() {
+ String[] messages = mMessages.toArray(new String[mMessages.size()]);
+ String[] participants = { mParticipant };
+ return new UnreadConversation(messages, mRemoteInput, mReplyPendingIntent,
+ mReadPendingIntent, participants, mLatestTimestamp);
+ }
+ }
+ }
+
+ /**
* Get an array of Notification objects from a parcelable array bundle field.
* Update the bundle to have a typed array so fetches in the future don't need
* to do an array copy.
diff --git a/core/java/android/app/ResourcesManager.java b/core/java/android/app/ResourcesManager.java
index 1691d8e..fac40b2 100644
--- a/core/java/android/app/ResourcesManager.java
+++ b/core/java/android/app/ResourcesManager.java
@@ -25,7 +25,6 @@ import android.content.res.Configuration;
import android.content.res.Resources;
import android.content.res.ResourcesKey;
import android.hardware.display.DisplayManagerGlobal;
-import android.os.IBinder;
import android.util.ArrayMap;
import android.util.DisplayMetrics;
import android.util.Slog;
@@ -38,8 +37,7 @@ import java.util.Locale;
/** @hide */
public class ResourcesManager {
static final String TAG = "ResourcesManager";
- static final boolean DEBUG_CACHE = false;
- static final boolean DEBUG_STATS = true;
+ private static final boolean DEBUG = false;
private static ResourcesManager sResourcesManager;
final ArrayMap<ResourcesKey, WeakReference<Resources> > mActiveResources
@@ -51,7 +49,6 @@ public class ResourcesManager {
CompatibilityInfo mResCompatibilityInfo;
Configuration mResConfiguration;
- final Configuration mTmpConfig = new Configuration();
public static ResourcesManager getInstance() {
synchronized (ResourcesManager.class) {
@@ -144,30 +141,32 @@ public class ResourcesManager {
* Creates the top level Resources for applications with the given compatibility info.
*
* @param resDir the resource directory.
+ * @param splitResDirs split resource directories.
* @param overlayDirs the resource overlay directories.
* @param libDirs the shared library resource dirs this app references.
- * @param compatInfo the compability info. Must not be null.
- * @param token the application token for determining stack bounds.
+ * @param displayId display Id.
+ * @param overrideConfiguration override configurations.
+ * @param compatInfo the compatibility info. Must not be null.
*/
public Resources getTopLevelResources(String resDir, String[] splitResDirs,
String[] overlayDirs, String[] libDirs, int displayId,
- Configuration overrideConfiguration, CompatibilityInfo compatInfo, IBinder token) {
+ Configuration overrideConfiguration, CompatibilityInfo compatInfo) {
final float scale = compatInfo.applicationScale;
- ResourcesKey key = new ResourcesKey(resDir, displayId, overrideConfiguration, scale, token);
+ Configuration overrideConfigCopy = (overrideConfiguration != null)
+ ? new Configuration(overrideConfiguration) : null;
+ ResourcesKey key = new ResourcesKey(resDir, displayId, overrideConfigCopy, scale);
Resources r;
synchronized (this) {
// Resources is app scale dependent.
- if (false) {
- Slog.w(TAG, "getTopLevelResources: " + resDir + " / " + scale);
- }
+ if (DEBUG) Slog.w(TAG, "getTopLevelResources: " + resDir + " / " + scale);
+
WeakReference<Resources> wr = mActiveResources.get(key);
r = wr != null ? wr.get() : null;
//if (r != null) Slog.i(TAG, "isUpToDate " + resDir + ": " + r.getAssets().isUpToDate());
if (r != null && r.getAssets().isUpToDate()) {
- if (false) {
- Slog.w(TAG, "Returning cached resources " + r + " " + resDir
- + ": appScale=" + r.getCompatibilityInfo().applicationScale);
- }
+ if (DEBUG) Slog.w(TAG, "Returning cached resources " + r + " " + resDir
+ + ": appScale=" + r.getCompatibilityInfo().applicationScale
+ + " key=" + key + " overrideConfig=" + overrideConfiguration);
return r;
}
}
@@ -213,7 +212,7 @@ public class ResourcesManager {
//Slog.i(TAG, "Resource: key=" + key + ", display metrics=" + metrics);
DisplayMetrics dm = getDisplayMetricsLocked(displayId);
Configuration config;
- boolean isDefaultDisplay = (displayId == Display.DEFAULT_DISPLAY);
+ final boolean isDefaultDisplay = (displayId == Display.DEFAULT_DISPLAY);
final boolean hasOverrideConfig = key.hasOverrideConfiguration();
if (!isDefaultDisplay || hasOverrideConfig) {
config = new Configuration(getConfiguration());
@@ -222,16 +221,14 @@ public class ResourcesManager {
}
if (hasOverrideConfig) {
config.updateFrom(key.mOverrideConfiguration);
+ if (DEBUG) Slog.v(TAG, "Applied overrideConfig=" + key.mOverrideConfiguration);
}
} else {
config = getConfiguration();
}
- r = new Resources(assets, dm, config, compatInfo, token);
- if (false) {
- Slog.i(TAG, "Created app resources " + resDir + " " + r + ": "
- + r.getConfiguration() + " appScale="
- + r.getCompatibilityInfo().applicationScale);
- }
+ r = new Resources(assets, dm, config, compatInfo);
+ if (DEBUG) Slog.i(TAG, "Created app resources " + resDir + " " + r + ": "
+ + r.getConfiguration() + " appScale=" + r.getCompatibilityInfo().applicationScale);
synchronized (this) {
WeakReference<Resources> wr = mActiveResources.get(key);
@@ -244,7 +241,8 @@ public class ResourcesManager {
}
// XXX need to remove entries when weak references go away
- mActiveResources.put(key, new WeakReference<Resources>(r));
+ mActiveResources.put(key, new WeakReference<>(r));
+ if (DEBUG) Slog.v(TAG, "mActiveResources.size()=" + mActiveResources.size());
return r;
}
}
@@ -255,7 +253,7 @@ public class ResourcesManager {
mResConfiguration = new Configuration();
}
if (!mResConfiguration.isOtherSeqNewer(config) && compat == null) {
- if (DEBUG_CONFIGURATION) Slog.v(TAG, "Skipping new config: curSeq="
+ if (DEBUG || DEBUG_CONFIGURATION) Slog.v(TAG, "Skipping new config: curSeq="
+ mResConfiguration.seq + ", newSeq=" + config.seq);
return false;
}
@@ -287,7 +285,7 @@ public class ResourcesManager {
ResourcesKey key = mActiveResources.keyAt(i);
Resources r = mActiveResources.valueAt(i).get();
if (r != null) {
- if (DEBUG_CONFIGURATION) Slog.v(TAG, "Changing resources "
+ if (DEBUG || DEBUG_CONFIGURATION) Slog.v(TAG, "Changing resources "
+ r + " config to: " + config);
int displayId = key.mDisplayId;
boolean isDefaultDisplay = (displayId == Display.DEFAULT_DISPLAY);
diff --git a/core/java/android/app/SharedPreferencesImpl.java b/core/java/android/app/SharedPreferencesImpl.java
index 4427ce1..e617553 100644
--- a/core/java/android/app/SharedPreferencesImpl.java
+++ b/core/java/android/app/SharedPreferencesImpl.java
@@ -16,6 +16,7 @@
package android.app;
+import android.annotation.Nullable;
import android.content.SharedPreferences;
import android.os.FileUtils;
import android.os.Looper;
@@ -217,7 +218,8 @@ final class SharedPreferencesImpl implements SharedPreferences {
}
}
- public String getString(String key, String defValue) {
+ @Nullable
+ public String getString(String key, @Nullable String defValue) {
synchronized (this) {
awaitLoadedLocked();
String v = (String)mMap.get(key);
@@ -225,7 +227,8 @@ final class SharedPreferencesImpl implements SharedPreferences {
}
}
- public Set<String> getStringSet(String key, Set<String> defValues) {
+ @Nullable
+ public Set<String> getStringSet(String key, @Nullable Set<String> defValues) {
synchronized (this) {
awaitLoadedLocked();
Set<String> v = (Set<String>) mMap.get(key);
@@ -303,13 +306,13 @@ final class SharedPreferencesImpl implements SharedPreferences {
private final Map<String, Object> mModified = Maps.newHashMap();
private boolean mClear = false;
- public Editor putString(String key, String value) {
+ public Editor putString(String key, @Nullable String value) {
synchronized (this) {
mModified.put(key, value);
return this;
}
}
- public Editor putStringSet(String key, Set<String> values) {
+ public Editor putStringSet(String key, @Nullable Set<String> values) {
synchronized (this) {
mModified.put(key,
(values == null) ? null : new HashSet<String>(values));
diff --git a/core/java/android/app/VoiceInteractor.java b/core/java/android/app/VoiceInteractor.java
index 723cb9b..7f9693f 100644
--- a/core/java/android/app/VoiceInteractor.java
+++ b/core/java/android/app/VoiceInteractor.java
@@ -16,7 +16,6 @@
package android.app;
-import android.annotation.SystemApi;
import android.content.Context;
import android.os.Bundle;
import android.os.IBinder;
@@ -34,7 +33,6 @@ import com.android.internal.os.SomeArgs;
import java.util.ArrayList;
/**
- * @hide
* Interface for an {@link Activity} to interact with the user through voice. Use
* {@link android.app.Activity#getVoiceInteractor() Activity.getVoiceInteractor}
* to retrieve the interface, if the activity is currently involved in a voice interaction.
@@ -56,7 +54,6 @@ import java.util.ArrayList;
* request, rather than holding on to the activity instance yourself, either explicitly
* or implicitly through a non-static inner class.
*/
-@SystemApi
public class VoiceInteractor {
static final String TAG = "VoiceInteractor";
static final boolean DEBUG = true;
@@ -108,9 +105,10 @@ public class VoiceInteractor {
request = pullRequest((IVoiceInteractorRequest)args.arg1, msg.arg1 != 0);
if (DEBUG) Log.d(TAG, "onCommandResult: req="
+ ((IVoiceInteractorRequest)args.arg1).asBinder() + "/" + request
- + " result=" + args.arg2);
+ + " completed=" + msg.arg1 + " result=" + args.arg2);
if (request != null) {
- ((CommandRequest)request).onCommandResult((Bundle) args.arg2);
+ ((CommandRequest)request).onCommandResult(msg.arg1 != 0,
+ (Bundle) args.arg2);
if (msg.arg1 != 0) {
request.clear();
}
@@ -321,7 +319,7 @@ public class VoiceInteractor {
* complete an action (e.g. booking a table might have several possible times that the
* user could select from or an app might need the user to agree to a terms of service).
* The result of the confirmation will be returned through an asynchronous call to
- * either {@link #onCommandResult(android.os.Bundle)} or
+ * either {@link #onCommandResult(boolean, android.os.Bundle)} or
* {@link #onCancel()}.
*
* <p>The command is a string that describes the generic operation to be performed.
@@ -338,7 +336,12 @@ public class VoiceInteractor {
mArgs = args;
}
- public void onCommandResult(Bundle result) {
+ /**
+ * Results for CommandRequest can be returned in partial chunks.
+ * The isCompleted is set to true iff all results have been returned, indicating the
+ * CommandRequest has completed.
+ */
+ public void onCommandResult(boolean isCompleted, Bundle result) {
}
IVoiceInteractorRequest submit(IVoiceInteractor interactor, String packageName,
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index cf06cb7..318a314 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -52,11 +52,15 @@ import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.Proxy;
+import java.security.KeyFactory;
import java.security.PrivateKey;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
+import java.security.spec.PKCS8EncodedKeySpec;
+import java.security.spec.InvalidKeySpecException;
+import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@@ -328,6 +332,16 @@ public class DevicePolicyManager {
= "android.app.extra.PROVISIONING_DEVICE_ADMIN_PACKAGE_CHECKSUM";
/**
+ * A boolean extra indicating whether device encryption is required as part of Device Owner
+ * provisioning.
+ *
+ * <p>Use in an NFC record with {@link #MIME_TYPE_PROVISIONING_NFC} that starts device owner
+ * provisioning via an NFC bump.
+ */
+ public static final String EXTRA_PROVISIONING_SKIP_ENCRYPTION =
+ "android.app.extra.PROVISIONING_SKIP_ENCRYPTION";
+
+ /**
* This MIME type is used for starting the Device Owner provisioning.
*
* <p>During device owner provisioning a device admin app is set as the owner of the device.
@@ -357,7 +371,8 @@ public class DevicePolicyManager {
* <li>{@link #EXTRA_PROVISIONING_WIFI_PROXY_HOST}, optional</li>
* <li>{@link #EXTRA_PROVISIONING_WIFI_PROXY_PORT} (convert to String), optional</li>
* <li>{@link #EXTRA_PROVISIONING_WIFI_PROXY_BYPASS}, optional</li>
- * <li>{@link #EXTRA_PROVISIONING_WIFI_PAC_URL}, optional</li></ul>
+ * <li>{@link #EXTRA_PROVISIONING_WIFI_PAC_URL}, optional</li>
+ * <li>{@link #EXTRA_PROVISIONING_SKIP_ENCRYPTION}, optional</li></ul>
*
* <p> When device owner provisioning has completed, an intent of the type
* {@link DeviceAdminReceiver#ACTION_PROFILE_PROVISIONING_COMPLETE} is broadcasted to the
@@ -690,7 +705,7 @@ public class DevicePolicyManager {
public void setPasswordQuality(ComponentName admin, int quality) {
if (mService != null) {
try {
- mService.setPasswordQuality(admin, quality, UserHandle.myUserId());
+ mService.setPasswordQuality(admin, quality);
} catch (RemoteException e) {
Log.w(TAG, "Failed talking with device policy service", e);
}
@@ -743,7 +758,7 @@ public class DevicePolicyManager {
public void setPasswordMinimumLength(ComponentName admin, int length) {
if (mService != null) {
try {
- mService.setPasswordMinimumLength(admin, length, UserHandle.myUserId());
+ mService.setPasswordMinimumLength(admin, length);
} catch (RemoteException e) {
Log.w(TAG, "Failed talking with device policy service", e);
}
@@ -797,7 +812,7 @@ public class DevicePolicyManager {
public void setPasswordMinimumUpperCase(ComponentName admin, int length) {
if (mService != null) {
try {
- mService.setPasswordMinimumUpperCase(admin, length, UserHandle.myUserId());
+ mService.setPasswordMinimumUpperCase(admin, length);
} catch (RemoteException e) {
Log.w(TAG, "Failed talking with device policy service", e);
}
@@ -858,7 +873,7 @@ public class DevicePolicyManager {
public void setPasswordMinimumLowerCase(ComponentName admin, int length) {
if (mService != null) {
try {
- mService.setPasswordMinimumLowerCase(admin, length, UserHandle.myUserId());
+ mService.setPasswordMinimumLowerCase(admin, length);
} catch (RemoteException e) {
Log.w(TAG, "Failed talking with device policy service", e);
}
@@ -918,7 +933,7 @@ public class DevicePolicyManager {
public void setPasswordMinimumLetters(ComponentName admin, int length) {
if (mService != null) {
try {
- mService.setPasswordMinimumLetters(admin, length, UserHandle.myUserId());
+ mService.setPasswordMinimumLetters(admin, length);
} catch (RemoteException e) {
Log.w(TAG, "Failed talking with device policy service", e);
}
@@ -976,7 +991,7 @@ public class DevicePolicyManager {
public void setPasswordMinimumNumeric(ComponentName admin, int length) {
if (mService != null) {
try {
- mService.setPasswordMinimumNumeric(admin, length, UserHandle.myUserId());
+ mService.setPasswordMinimumNumeric(admin, length);
} catch (RemoteException e) {
Log.w(TAG, "Failed talking with device policy service", e);
}
@@ -1035,7 +1050,7 @@ public class DevicePolicyManager {
public void setPasswordMinimumSymbols(ComponentName admin, int length) {
if (mService != null) {
try {
- mService.setPasswordMinimumSymbols(admin, length, UserHandle.myUserId());
+ mService.setPasswordMinimumSymbols(admin, length);
} catch (RemoteException e) {
Log.w(TAG, "Failed talking with device policy service", e);
}
@@ -1093,7 +1108,7 @@ public class DevicePolicyManager {
public void setPasswordMinimumNonLetter(ComponentName admin, int length) {
if (mService != null) {
try {
- mService.setPasswordMinimumNonLetter(admin, length, UserHandle.myUserId());
+ mService.setPasswordMinimumNonLetter(admin, length);
} catch (RemoteException e) {
Log.w(TAG, "Failed talking with device policy service", e);
}
@@ -1153,7 +1168,7 @@ public class DevicePolicyManager {
public void setPasswordHistoryLength(ComponentName admin, int length) {
if (mService != null) {
try {
- mService.setPasswordHistoryLength(admin, length, UserHandle.myUserId());
+ mService.setPasswordHistoryLength(admin, length);
} catch (RemoteException e) {
Log.w(TAG, "Failed talking with device policy service", e);
}
@@ -1185,7 +1200,7 @@ public class DevicePolicyManager {
public void setPasswordExpirationTimeout(ComponentName admin, long timeout) {
if (mService != null) {
try {
- mService.setPasswordExpirationTimeout(admin, timeout, UserHandle.myUserId());
+ mService.setPasswordExpirationTimeout(admin, timeout);
} catch (RemoteException e) {
Log.w(TAG, "Failed talking with device policy service", e);
}
@@ -1330,7 +1345,7 @@ public class DevicePolicyManager {
public void setMaximumFailedPasswordsForWipe(ComponentName admin, int num) {
if (mService != null) {
try {
- mService.setMaximumFailedPasswordsForWipe(admin, num, UserHandle.myUserId());
+ mService.setMaximumFailedPasswordsForWipe(admin, num);
} catch (RemoteException e) {
Log.w(TAG, "Failed talking with device policy service", e);
}
@@ -1414,7 +1429,7 @@ public class DevicePolicyManager {
public boolean resetPassword(String password, int flags) {
if (mService != null) {
try {
- return mService.resetPassword(password, flags, UserHandle.myUserId());
+ return mService.resetPassword(password, flags);
} catch (RemoteException e) {
Log.w(TAG, "Failed talking with device policy service", e);
}
@@ -1438,7 +1453,7 @@ public class DevicePolicyManager {
public void setMaximumTimeToLock(ComponentName admin, long timeMs) {
if (mService != null) {
try {
- mService.setMaximumTimeToLock(admin, timeMs, UserHandle.myUserId());
+ mService.setMaximumTimeToLock(admin, timeMs);
} catch (RemoteException e) {
Log.w(TAG, "Failed talking with device policy service", e);
}
@@ -1503,8 +1518,8 @@ public class DevicePolicyManager {
public static final int WIPE_RESET_PROTECTION_DATA = 0x0002;
/**
- * Ask the user data be wiped. This will cause the device to reboot,
- * erasing all user data while next booting up.
+ * Ask the user data be wiped. Wiping the primary user will cause the
+ * device to reboot, erasing all user data while next booting up.
*
* <p>The calling device admin must have requested
* {@link DeviceAdminInfo#USES_POLICY_WIPE_DATA} to be able to call
@@ -1588,7 +1603,7 @@ public class DevicePolicyManager {
!= android.net.Proxy.PROXY_VALID)
throw new IllegalArgumentException();
}
- return mService.setGlobalProxy(admin, hostSpec, exclSpec, UserHandle.myUserId());
+ return mService.setGlobalProxy(admin, hostSpec, exclSpec);
} catch (RemoteException e) {
Log.w(TAG, "Failed talking with device policy service", e);
}
@@ -1754,7 +1769,7 @@ public class DevicePolicyManager {
public int setStorageEncryption(ComponentName admin, boolean encrypt) {
if (mService != null) {
try {
- return mService.setStorageEncryption(admin, encrypt, UserHandle.myUserId());
+ return mService.setStorageEncryption(admin, encrypt);
} catch (RemoteException e) {
Log.w(TAG, "Failed talking with device policy service", e);
}
@@ -1934,13 +1949,15 @@ public class DevicePolicyManager {
String alias) {
try {
final byte[] pemCert = Credentials.convertToPem(cert);
- return mService.installKeyPair(who, privKey.getEncoded(), pemCert, alias);
- } catch (CertificateException e) {
- Log.w(TAG, "Error encoding certificate", e);
- } catch (IOException e) {
- Log.w(TAG, "Error writing certificate", e);
+ final byte[] pkcs8Key = KeyFactory.getInstance(privKey.getAlgorithm())
+ .getKeySpec(privKey, PKCS8EncodedKeySpec.class).getEncoded();
+ return mService.installKeyPair(who, pkcs8Key, pemCert, alias);
} catch (RemoteException e) {
Log.w(TAG, "Failed talking with device policy service", e);
+ } catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
+ Log.w(TAG, "Failed to obtain private key material", e);
+ } catch (CertificateException | IOException e) {
+ Log.w(TAG, "Could not pem-encode certificate", e);
}
return false;
}
@@ -1971,7 +1988,7 @@ public class DevicePolicyManager {
public void setCameraDisabled(ComponentName admin, boolean disabled) {
if (mService != null) {
try {
- mService.setCameraDisabled(admin, disabled, UserHandle.myUserId());
+ mService.setCameraDisabled(admin, disabled);
} catch (RemoteException e) {
Log.w(TAG, "Failed talking with device policy service", e);
}
@@ -2015,7 +2032,7 @@ public class DevicePolicyManager {
public void setScreenCaptureDisabled(ComponentName admin, boolean disabled) {
if (mService != null) {
try {
- mService.setScreenCaptureDisabled(admin, UserHandle.myUserId(), disabled);
+ mService.setScreenCaptureDisabled(admin, disabled);
} catch (RemoteException e) {
Log.w(TAG, "Failed talking with device policy service", e);
}
@@ -2059,7 +2076,7 @@ public class DevicePolicyManager {
public void setAutoTimeRequired(ComponentName admin, boolean required) {
if (mService != null) {
try {
- mService.setAutoTimeRequired(admin, UserHandle.myUserId(), required);
+ mService.setAutoTimeRequired(admin, required);
} catch (RemoteException e) {
Log.w(TAG, "Failed talking with device policy service", e);
}
@@ -2100,7 +2117,7 @@ public class DevicePolicyManager {
public void setKeyguardDisabledFeatures(ComponentName admin, int which) {
if (mService != null) {
try {
- mService.setKeyguardDisabledFeatures(admin, which, UserHandle.myUserId());
+ mService.setKeyguardDisabledFeatures(admin, which);
} catch (RemoteException e) {
Log.w(TAG, "Failed talking with device policy service", e);
}
@@ -2417,27 +2434,6 @@ public class DevicePolicyManager {
}
/**
- * @deprecated Use setProfileOwner(ComponentName ...)
- * @hide
- * Sets the given package as the profile owner of the given user profile. The package must
- * already be installed and there shouldn't be an existing profile owner registered for this
- * user. Also, this method must be called before the user has been used for the first time.
- * @param packageName the package name of the application to be registered as profile owner.
- * @param ownerName the human readable name of the organisation associated with this DPM.
- * @param userHandle the userId to set the profile owner for.
- * @return whether the package was successfully registered as the profile owner.
- * @throws IllegalArgumentException if packageName is null, the package isn't installed, or
- * the user has already been set up.
- */
- public boolean setProfileOwner(String packageName, String ownerName, int userHandle)
- throws IllegalArgumentException {
- if (packageName == null) {
- throw new NullPointerException("packageName cannot be null");
- }
- return setProfileOwner(new ComponentName(packageName, ""), ownerName, userHandle);
- }
-
- /**
* @hide
* Sets the given component as the profile owner of the given user profile. The package must
* already be installed and there shouldn't be an existing profile owner registered for this
@@ -2703,8 +2699,7 @@ public class DevicePolicyManager {
PersistableBundle configuration) {
if (mService != null) {
try {
- mService.setTrustAgentConfiguration(admin, target, configuration,
- UserHandle.myUserId());
+ mService.setTrustAgentConfiguration(admin, target, configuration);
} catch (RemoteException e) {
Log.w(TAG, "Failed talking with device policy service", e);
}
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index 0ca60c0..67bca4e 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -32,34 +32,34 @@ import java.util.List;
* {@hide}
*/
interface IDevicePolicyManager {
- void setPasswordQuality(in ComponentName who, int quality, int userHandle);
+ void setPasswordQuality(in ComponentName who, int quality);
int getPasswordQuality(in ComponentName who, int userHandle);
- void setPasswordMinimumLength(in ComponentName who, int length, int userHandle);
+ void setPasswordMinimumLength(in ComponentName who, int length);
int getPasswordMinimumLength(in ComponentName who, int userHandle);
- void setPasswordMinimumUpperCase(in ComponentName who, int length, int userHandle);
+ void setPasswordMinimumUpperCase(in ComponentName who, int length);
int getPasswordMinimumUpperCase(in ComponentName who, int userHandle);
- void setPasswordMinimumLowerCase(in ComponentName who, int length, int userHandle);
+ void setPasswordMinimumLowerCase(in ComponentName who, int length);
int getPasswordMinimumLowerCase(in ComponentName who, int userHandle);
- void setPasswordMinimumLetters(in ComponentName who, int length, int userHandle);
+ void setPasswordMinimumLetters(in ComponentName who, int length);
int getPasswordMinimumLetters(in ComponentName who, int userHandle);
- void setPasswordMinimumNumeric(in ComponentName who, int length, int userHandle);
+ void setPasswordMinimumNumeric(in ComponentName who, int length);
int getPasswordMinimumNumeric(in ComponentName who, int userHandle);
- void setPasswordMinimumSymbols(in ComponentName who, int length, int userHandle);
+ void setPasswordMinimumSymbols(in ComponentName who, int length);
int getPasswordMinimumSymbols(in ComponentName who, int userHandle);
- void setPasswordMinimumNonLetter(in ComponentName who, int length, int userHandle);
+ void setPasswordMinimumNonLetter(in ComponentName who, int length);
int getPasswordMinimumNonLetter(in ComponentName who, int userHandle);
- void setPasswordHistoryLength(in ComponentName who, int length, int userHandle);
+ void setPasswordHistoryLength(in ComponentName who, int length);
int getPasswordHistoryLength(in ComponentName who, int userHandle);
- void setPasswordExpirationTimeout(in ComponentName who, long expiration, int userHandle);
+ void setPasswordExpirationTimeout(in ComponentName who, long expiration);
long getPasswordExpirationTimeout(in ComponentName who, int userHandle);
long getPasswordExpiration(in ComponentName who, int userHandle);
@@ -68,33 +68,33 @@ interface IDevicePolicyManager {
int getCurrentFailedPasswordAttempts(int userHandle);
int getProfileWithMinimumFailedPasswordsForWipe(int userHandle);
- void setMaximumFailedPasswordsForWipe(in ComponentName admin, int num, int userHandle);
+ void setMaximumFailedPasswordsForWipe(in ComponentName admin, int num);
int getMaximumFailedPasswordsForWipe(in ComponentName admin, int userHandle);
- boolean resetPassword(String password, int flags, int userHandle);
+ boolean resetPassword(String password, int flags);
- void setMaximumTimeToLock(in ComponentName who, long timeMs, int userHandle);
+ void setMaximumTimeToLock(in ComponentName who, long timeMs);
long getMaximumTimeToLock(in ComponentName who, int userHandle);
void lockNow();
void wipeData(int flags, int userHandle);
- ComponentName setGlobalProxy(in ComponentName admin, String proxySpec, String exclusionList, int userHandle);
+ ComponentName setGlobalProxy(in ComponentName admin, String proxySpec, String exclusionList);
ComponentName getGlobalProxyAdmin(int userHandle);
void setRecommendedGlobalProxy(in ComponentName admin, in ProxyInfo proxyInfo);
- int setStorageEncryption(in ComponentName who, boolean encrypt, int userHandle);
+ int setStorageEncryption(in ComponentName who, boolean encrypt);
boolean getStorageEncryption(in ComponentName who, int userHandle);
int getStorageEncryptionStatus(int userHandle);
- void setCameraDisabled(in ComponentName who, boolean disabled, int userHandle);
+ void setCameraDisabled(in ComponentName who, boolean disabled);
boolean getCameraDisabled(in ComponentName who, int userHandle);
- void setScreenCaptureDisabled(in ComponentName who, int userHandle, boolean disabled);
+ void setScreenCaptureDisabled(in ComponentName who, boolean disabled);
boolean getScreenCaptureDisabled(in ComponentName who, int userHandle);
- void setKeyguardDisabledFeatures(in ComponentName who, int which, int userHandle);
+ void setKeyguardDisabledFeatures(in ComponentName who, int which);
int getKeyguardDisabledFeatures(in ComponentName who, int userHandle);
void setActiveAdmin(in ComponentName policyReceiver, boolean refreshing, int userHandle);
@@ -186,7 +186,7 @@ interface IDevicePolicyManager {
boolean getCrossProfileCallerIdDisabledForUser(int userId);
void setTrustAgentConfiguration(in ComponentName admin, in ComponentName agent,
- in PersistableBundle args, int userId);
+ in PersistableBundle args);
List<PersistableBundle> getTrustAgentConfiguration(in ComponentName admin,
in ComponentName agent, int userId);
@@ -194,7 +194,7 @@ interface IDevicePolicyManager {
boolean removeCrossProfileWidgetProvider(in ComponentName admin, String packageName);
List<String> getCrossProfileWidgetProviders(in ComponentName admin);
- void setAutoTimeRequired(in ComponentName who, int userHandle, boolean required);
+ void setAutoTimeRequired(in ComponentName who, boolean required);
boolean getAutoTimeRequired();
boolean isRemovingAdmin(in ComponentName adminReceiver, int userHandle);
diff --git a/core/java/android/appwidget/AppWidgetHost.java b/core/java/android/appwidget/AppWidgetHost.java
index 8e86824..1af4953 100644
--- a/core/java/android/appwidget/AppWidgetHost.java
+++ b/core/java/android/appwidget/AppWidgetHost.java
@@ -16,6 +16,7 @@
package android.appwidget;
+import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.HashMap;
@@ -58,18 +59,28 @@ public class AppWidgetHost {
private DisplayMetrics mDisplayMetrics;
private String mContextOpPackageName;
- Handler mHandler;
- int mHostId;
- Callbacks mCallbacks = new Callbacks();
- final HashMap<Integer,AppWidgetHostView> mViews = new HashMap<Integer, AppWidgetHostView>();
+ private final Handler mHandler;
+ private final int mHostId;
+ private final Callbacks mCallbacks;
+ private final HashMap<Integer,AppWidgetHostView> mViews = new HashMap<>();
private OnClickHandler mOnClickHandler;
- class Callbacks extends IAppWidgetHost.Stub {
+ static class Callbacks extends IAppWidgetHost.Stub {
+ private final WeakReference<Handler> mWeakHandler;
+
+ public Callbacks(Handler handler) {
+ mWeakHandler = new WeakReference<>(handler);
+ }
+
public void updateAppWidget(int appWidgetId, RemoteViews views) {
if (isLocalBinder() && views != null) {
views = views.clone();
}
- Message msg = mHandler.obtainMessage(HANDLE_UPDATE, appWidgetId, 0, views);
+ Handler handler = mWeakHandler.get();
+ if (handler == null) {
+ return;
+ }
+ Message msg = handler.obtainMessage(HANDLE_UPDATE, appWidgetId, 0, views);
msg.sendToTarget();
}
@@ -77,20 +88,36 @@ public class AppWidgetHost {
if (isLocalBinder() && info != null) {
info = info.clone();
}
- Message msg = mHandler.obtainMessage(HANDLE_PROVIDER_CHANGED,
+ Handler handler = mWeakHandler.get();
+ if (handler == null) {
+ return;
+ }
+ Message msg = handler.obtainMessage(HANDLE_PROVIDER_CHANGED,
appWidgetId, 0, info);
msg.sendToTarget();
}
public void providersChanged() {
- mHandler.obtainMessage(HANDLE_PROVIDERS_CHANGED).sendToTarget();
+ Handler handler = mWeakHandler.get();
+ if (handler == null) {
+ return;
+ }
+ handler.obtainMessage(HANDLE_PROVIDERS_CHANGED).sendToTarget();
}
public void viewDataChanged(int appWidgetId, int viewId) {
- Message msg = mHandler.obtainMessage(HANDLE_VIEW_DATA_CHANGED,
+ Handler handler = mWeakHandler.get();
+ if (handler == null) {
+ return;
+ }
+ Message msg = handler.obtainMessage(HANDLE_VIEW_DATA_CHANGED,
appWidgetId, viewId);
msg.sendToTarget();
}
+
+ private static boolean isLocalBinder() {
+ return Process.myPid() == Binder.getCallingPid();
+ }
}
class UpdateHandler extends Handler {
@@ -132,6 +159,7 @@ public class AppWidgetHost {
mHostId = hostId;
mOnClickHandler = handler;
mHandler = new UpdateHandler(looper);
+ mCallbacks = new Callbacks(mHandler);
mDisplayMetrics = context.getResources().getDisplayMetrics();
bindService();
}
@@ -251,10 +279,6 @@ public class AppWidgetHost {
}
}
- private boolean isLocalBinder() {
- return Process.myPid() == Binder.getCallingPid();
- }
-
/**
* Stop listening to changes for this AppWidget.
*/
diff --git a/core/java/android/appwidget/AppWidgetHostView.java b/core/java/android/appwidget/AppWidgetHostView.java
index 1ff476e..022e225 100644
--- a/core/java/android/appwidget/AppWidgetHostView.java
+++ b/core/java/android/appwidget/AppWidgetHostView.java
@@ -588,9 +588,10 @@ public class AppWidgetHostView extends FrameLayout {
return tv;
}
+ /** @hide */
@Override
- public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(info);
+ public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
+ super.onInitializeAccessibilityNodeInfoInternal(info);
info.setClassName(AppWidgetHostView.class.getName());
}
diff --git a/core/java/android/bluetooth/BluetoothA2dp.java b/core/java/android/bluetooth/BluetoothA2dp.java
index 0450150..767f59e 100644
--- a/core/java/android/bluetooth/BluetoothA2dp.java
+++ b/core/java/android/bluetooth/BluetoothA2dp.java
@@ -22,6 +22,7 @@ import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
+import android.media.AudioManager;
import android.os.IBinder;
import android.os.ParcelUuid;
import android.os.RemoteException;
@@ -415,9 +416,16 @@ public final class BluetoothA2dp implements BluetoothProfile {
}
/**
- * Tells remote device to adjust volume. Only if absolute volume is supported.
+ * Tells remote device to adjust volume. Only if absolute volume is
+ * supported. Uses the following values:
+ * <ul>
+ * <li>{@link AudioManager#ADJUST_LOWER}</li>
+ * <li>{@link AudioManager#ADJUST_RAISE}</li>
+ * <li>{@link AudioManager#ADJUST_MUTE}</li>
+ * <li>{@link AudioManager#ADJUST_UNMUTE}</li>
+ * </ul>
*
- * @param direction 1 to increase volume, or -1 to decrease volume
+ * @param direction One of the supported adjust values.
* @hide
*/
public void adjustAvrcpAbsoluteVolume(int direction) {
diff --git a/core/java/android/bluetooth/BluetoothHeadsetClientCall.java b/core/java/android/bluetooth/BluetoothHeadsetClientCall.java
index a15bd97..7b5a045 100644
--- a/core/java/android/bluetooth/BluetoothHeadsetClientCall.java
+++ b/core/java/android/bluetooth/BluetoothHeadsetClientCall.java
@@ -61,6 +61,7 @@ public final class BluetoothHeadsetClientCall implements Parcelable {
*/
public static final int CALL_STATE_TERMINATED = 7;
+ private final BluetoothDevice mDevice;
private final int mId;
private int mState;
private String mNumber;
@@ -70,8 +71,9 @@ public final class BluetoothHeadsetClientCall implements Parcelable {
/**
* Creates BluetoothHeadsetClientCall instance.
*/
- public BluetoothHeadsetClientCall(int id, int state, String number, boolean multiParty,
- boolean outgoing) {
+ public BluetoothHeadsetClientCall(BluetoothDevice device, int id, int state, String number,
+ boolean multiParty, boolean outgoing) {
+ mDevice = device;
mId = id;
mState = state;
mNumber = number != null ? number : "";
@@ -114,6 +116,15 @@ public final class BluetoothHeadsetClientCall implements Parcelable {
}
/**
+ * Gets call's device.
+ *
+ * @return call device.
+ */
+ public BluetoothDevice getDevice() {
+ return mDevice;
+ }
+
+ /**
* Gets call's Id.
*
* @return call id.
@@ -161,7 +172,9 @@ public final class BluetoothHeadsetClientCall implements Parcelable {
}
public String toString() {
- StringBuilder builder = new StringBuilder("BluetoothHeadsetClientCall{mId: ");
+ StringBuilder builder = new StringBuilder("BluetoothHeadsetClientCall{mDevice: ");
+ builder.append(mDevice);
+ builder.append(", mId: ");
builder.append(mId);
builder.append(", mState: ");
switch (mState) {
@@ -192,8 +205,9 @@ public final class BluetoothHeadsetClientCall implements Parcelable {
new Parcelable.Creator<BluetoothHeadsetClientCall>() {
@Override
public BluetoothHeadsetClientCall createFromParcel(Parcel in) {
- return new BluetoothHeadsetClientCall(in.readInt(), in.readInt(),
- in.readString(), in.readInt() == 1, in.readInt() == 1);
+ return new BluetoothHeadsetClientCall((BluetoothDevice)in.readParcelable(null),
+ in.readInt(), in.readInt(), in.readString(),
+ in.readInt() == 1, in.readInt() == 1);
}
@Override
@@ -204,6 +218,7 @@ public final class BluetoothHeadsetClientCall implements Parcelable {
@Override
public void writeToParcel(Parcel out, int flags) {
+ out.writeParcelable(mDevice, 0);
out.writeInt(mId);
out.writeInt(mState);
out.writeString(mNumber);
diff --git a/core/java/android/content/ContentProviderOperation.java b/core/java/android/content/ContentProviderOperation.java
index 136e54d..49ac062 100644
--- a/core/java/android/content/ContentProviderOperation.java
+++ b/core/java/android/content/ContentProviderOperation.java
@@ -208,6 +208,22 @@ public class ContentProviderOperation implements Parcelable {
return mType;
}
+ public boolean isInsert() {
+ return mType == TYPE_INSERT;
+ }
+
+ public boolean isDelete() {
+ return mType == TYPE_DELETE;
+ }
+
+ public boolean isUpdate() {
+ return mType == TYPE_UPDATE;
+ }
+
+ public boolean isAssertQuery() {
+ return mType == TYPE_ASSERT;
+ }
+
public boolean isWriteOperation() {
return mType == TYPE_DELETE || mType == TYPE_INSERT || mType == TYPE_UPDATE;
}
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 26735a6..dec2524 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -24,6 +24,7 @@ import android.annotation.SystemApi;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.res.AssetManager;
+import android.content.res.ColorStateList;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.content.res.TypedArray;
@@ -146,12 +147,13 @@ public abstract class Context {
@IntDef(flag = true,
value = {
BIND_AUTO_CREATE,
- BIND_AUTO_CREATE,
BIND_DEBUG_UNBIND,
BIND_NOT_FOREGROUND,
BIND_ABOVE_CLIENT,
BIND_ALLOW_OOM_MANAGEMENT,
- BIND_WAIVE_PRIORITY
+ BIND_WAIVE_PRIORITY,
+ BIND_IMPORTANT,
+ BIND_ADJUST_WITH_ACTIVITY
})
@Retention(RetentionPolicy.SOURCE)
public @interface BindServiceFlags {}
@@ -391,18 +393,55 @@ public abstract class Context {
}
/**
- * Return a drawable object associated with a particular resource ID and
+ * Returns a color associated with a particular resource ID and styled for
+ * the current theme.
+ *
+ * @param id The desired resource identifier, as generated by the aapt
+ * tool. This integer encodes the package, type, and resource
+ * entry. The value 0 is an invalid identifier.
+ * @return A single color value in the form 0xAARRGGBB.
+ * @throws android.content.res.Resources.NotFoundException if the given ID
+ * does not exist.
+ */
+ @Nullable
+ public final int getColor(int id) {
+ return getResources().getColor(id, getTheme());
+ }
+
+ /**
+ * Returns a drawable object associated with a particular resource ID and
* styled for the current theme.
*
* @param id The desired resource identifier, as generated by the aapt
* tool. This integer encodes the package, type, and resource
* entry. The value 0 is an invalid identifier.
- * @return Drawable An object that can be used to draw this resource.
+ * @return An object that can be used to draw this resource, or
+ * {@code null} if the resource could not be resolved.
+ * @throws android.content.res.Resources.NotFoundException if the given ID
+ * does not exist.
*/
+ @Nullable
public final Drawable getDrawable(int id) {
return getResources().getDrawable(id, getTheme());
}
+ /**
+ * Returns a color state list associated with a particular resource ID and
+ * styled for the current theme.
+ *
+ * @param id The desired resource identifier, as generated by the aapt
+ * tool. This integer encodes the package, type, and resource
+ * entry. The value 0 is an invalid identifier.
+ * @return A color state list, or {@code null} if the resource could not be
+ * resolved.
+ * @throws android.content.res.Resources.NotFoundException if the given ID
+ * does not exist.
+ */
+ @Nullable
+ public final ColorStateList getColorStateList(int id) {
+ return getResources().getColorStateList(id, getTheme());
+ }
+
/**
* Set the base theme for this context. Note that this should be called
* before any views are instantiated in the Context (for example before
@@ -2143,6 +2182,7 @@ public abstract class Context {
BATTERY_SERVICE,
JOB_SCHEDULER_SERVICE,
MEDIA_PROJECTION_SERVICE,
+ MIDI_SERVICE,
})
@Retention(RetentionPolicy.SOURCE)
public @interface ServiceName {}
@@ -2916,6 +2956,15 @@ public abstract class Context {
public static final String MEDIA_PROJECTION_SERVICE = "media_projection";
/**
+ * Use with {@link #getSystemService} to retrieve a
+ * {@link android.midi.MidiManager} for accessing the MIDI service.
+ *
+ * @see #getSystemService
+ * @hide
+ */
+ public static final String MIDI_SERVICE = "midi";
+
+ /**
* Determine whether the given permission is allowed for a particular
* process and user ID running in the system.
*
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 2fe727c..582802b 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -1760,6 +1760,7 @@ public class Intent implements Parcelable, Cloneable {
* <p class="note">This is a protected intent that can only be sent
* by the system.
*/
+ @SystemApi
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
public static final String ACTION_QUERY_PACKAGE_RESTART = "android.intent.action.QUERY_PACKAGE_RESTART";
/**
@@ -2808,14 +2809,12 @@ public class Intent implements Parcelable, Cloneable {
@SdkConstant(SdkConstantType.INTENT_CATEGORY)
public static final String CATEGORY_BROWSABLE = "android.intent.category.BROWSABLE";
/**
- * @hide
* Categories for activities that can participate in voice interaction.
* An activity that supports this category must be prepared to run with
* no UI shown at all (though in some case it may have a UI shown), and
* rely on {@link android.app.VoiceInteractor} to interact with the user.
*/
@SdkConstant(SdkConstantType.INTENT_CATEGORY)
- @SystemApi
public static final String CATEGORY_VOICE = "android.intent.category.VOICE";
/**
* Set if the activity should be considered as an alternative action to
@@ -3254,6 +3253,7 @@ public class Intent implements Parcelable, Cloneable {
/**
* @hide String array of package names.
*/
+ @SystemApi
public static final String EXTRA_PACKAGES = "android.intent.extra.PACKAGES";
/**
diff --git a/core/java/android/content/SharedPreferences.java b/core/java/android/content/SharedPreferences.java
index 46c9234..cd32dae 100644
--- a/core/java/android/content/SharedPreferences.java
+++ b/core/java/android/content/SharedPreferences.java
@@ -16,6 +16,8 @@
package android.content;
+import android.annotation.Nullable;
+
import java.util.Map;
import java.util.Set;
@@ -78,7 +80,7 @@ public interface SharedPreferences {
* @return Returns a reference to the same Editor object, so you can
* chain put calls together.
*/
- Editor putString(String key, String value);
+ Editor putString(String key, @Nullable String value);
/**
* Set a set of String values in the preferences editor, to be written
@@ -91,7 +93,7 @@ public interface SharedPreferences {
* @return Returns a reference to the same Editor object, so you can
* chain put calls together.
*/
- Editor putStringSet(String key, Set<String> values);
+ Editor putStringSet(String key, @Nullable Set<String> values);
/**
* Set an int value in the preferences editor, to be written back once
@@ -254,7 +256,8 @@ public interface SharedPreferences {
*
* @throws ClassCastException
*/
- String getString(String key, String defValue);
+ @Nullable
+ String getString(String key, @Nullable String defValue);
/**
* Retrieve a set of String values from the preferences.
@@ -272,7 +275,8 @@ public interface SharedPreferences {
*
* @throws ClassCastException
*/
- Set<String> getStringSet(String key, Set<String> defValues);
+ @Nullable
+ Set<String> getStringSet(String key, @Nullable Set<String> defValues);
/**
* Retrieve an int value from the preferences.
diff --git a/core/java/android/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java
index 641f843..4723c0d 100644
--- a/core/java/android/content/pm/ActivityInfo.java
+++ b/core/java/android/content/pm/ActivityInfo.java
@@ -641,6 +641,13 @@ public class ActivityInfo extends ComponentInfo
*/
public String parentActivityName;
+ /**
+ * Value indicating if the activity is resizeable to any dimension.
+ * See {@link android.R.attr#resizeableActivity}.
+ * @hide
+ */
+ public boolean resizeable;
+
public ActivityInfo() {
}
@@ -702,6 +709,7 @@ public class ActivityInfo extends ComponentInfo
if (uiOptions != 0) {
pw.println(prefix + " uiOptions=0x" + Integer.toHexString(uiOptions));
}
+ pw.println(prefix + "resizeable=" + resizeable);
super.dumpBack(pw, prefix);
}
@@ -730,6 +738,7 @@ public class ActivityInfo extends ComponentInfo
dest.writeString(parentActivityName);
dest.writeInt(persistableMode);
dest.writeInt(maxRecents);
+ dest.writeInt(resizeable ? 1 : 0);
}
public static final Parcelable.Creator<ActivityInfo> CREATOR
@@ -757,5 +766,6 @@ public class ActivityInfo extends ComponentInfo
parentActivityName = source.readString();
persistableMode = source.readInt();
maxRecents = source.readInt();
+ resizeable = (source.readInt() == 1);
}
}
diff --git a/core/java/android/content/pm/LauncherActivityInfo.java b/core/java/android/content/pm/LauncherActivityInfo.java
index ee23fcd..87b97aa 100644
--- a/core/java/android/content/pm/LauncherActivityInfo.java
+++ b/core/java/android/content/pm/LauncherActivityInfo.java
@@ -39,6 +39,7 @@ public class LauncherActivityInfo {
private ActivityInfo mActivityInfo;
private ComponentName mComponentName;
+ private ResolveInfo mResolveInfo;
private UserHandle mUser;
private long mFirstInstallTime;
@@ -52,6 +53,7 @@ public class LauncherActivityInfo {
LauncherActivityInfo(Context context, ResolveInfo info, UserHandle user,
long firstInstallTime) {
this(context);
+ mResolveInfo = info;
mActivityInfo = info.activityInfo;
mComponentName = LauncherApps.getComponentName(info);
mUser = user;
@@ -92,7 +94,7 @@ public class LauncherActivityInfo {
* @return The label for the activity.
*/
public CharSequence getLabel() {
- return mActivityInfo.loadLabel(mPm);
+ return mResolveInfo.loadLabel(mPm);
}
/**
@@ -104,8 +106,22 @@ public class LauncherActivityInfo {
* @return The drawable associated with the activity
*/
public Drawable getIcon(int density) {
- // TODO: Use density
- return mActivityInfo.loadIcon(mPm);
+ int iconRes = mResolveInfo.getIconResource();
+ Resources resources = null;
+ Drawable icon = null;
+ // Get the preferred density icon from the app's resources
+ if (density != 0 && iconRes != 0) {
+ try {
+ resources = mPm.getResourcesForApplication(mActivityInfo.applicationInfo);
+ icon = resources.getDrawableForDensity(iconRes, density);
+ } catch (NameNotFoundException | Resources.NotFoundException exc) {
+ }
+ }
+ // Get the default density icon
+ if (icon == null) {
+ icon = mResolveInfo.loadIcon(mPm);
+ }
+ return icon;
}
/**
@@ -151,23 +167,7 @@ public class LauncherActivityInfo {
* @return A badged icon for the activity.
*/
public Drawable getBadgedIcon(int density) {
- int iconRes = mActivityInfo.getIconResource();
- Resources resources = null;
- Drawable originalIcon = null;
- try {
- resources = mPm.getResourcesForApplication(mActivityInfo.applicationInfo);
- try {
- if (density != 0) {
- originalIcon = resources.getDrawableForDensity(iconRes, density);
- }
- } catch (Resources.NotFoundException e) {
- }
- } catch (NameNotFoundException nnfe) {
- }
-
- if (originalIcon == null) {
- originalIcon = mActivityInfo.loadIcon(mPm);
- }
+ Drawable originalIcon = getIcon(density);
if (originalIcon instanceof BitmapDrawable) {
return mPm.getUserBadgedIcon(originalIcon, mUser);
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index 77dc27a..b0e0300 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -49,6 +49,7 @@ import android.util.Pair;
import android.util.Slog;
import android.util.TypedValue;
+import com.android.internal.R;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.XmlUtils;
@@ -2760,9 +2761,17 @@ public class PackageParser {
}
}
+ addSharedLibrariesForBackwardCompatibility(owner);
+
return true;
}
+ private static void addSharedLibrariesForBackwardCompatibility(Package owner) {
+ if (owner.applicationInfo.targetSdkVersion <= Build.VERSION_CODES.LOLLIPOP_MR1) {
+ owner.usesLibraries = ArrayUtils.add(owner.usesLibraries, "org.apache.http.legacy");
+ }
+ }
+
/**
* Parse the {@code application} XML tree at the current parse location in a
* <em>split APK</em> manifest.
@@ -2947,20 +2956,19 @@ public class PackageParser {
XmlPullParser parser, AttributeSet attrs, int flags, String[] outError,
boolean receiver, boolean hardwareAccelerated)
throws XmlPullParserException, IOException {
- TypedArray sa = res.obtainAttributes(attrs,
- com.android.internal.R.styleable.AndroidManifestActivity);
+ TypedArray sa = res.obtainAttributes(attrs, R.styleable.AndroidManifestActivity);
if (mParseActivityArgs == null) {
mParseActivityArgs = new ParseComponentArgs(owner, outError,
- com.android.internal.R.styleable.AndroidManifestActivity_name,
- com.android.internal.R.styleable.AndroidManifestActivity_label,
- com.android.internal.R.styleable.AndroidManifestActivity_icon,
- com.android.internal.R.styleable.AndroidManifestActivity_logo,
- com.android.internal.R.styleable.AndroidManifestActivity_banner,
+ R.styleable.AndroidManifestActivity_name,
+ R.styleable.AndroidManifestActivity_label,
+ R.styleable.AndroidManifestActivity_icon,
+ R.styleable.AndroidManifestActivity_logo,
+ R.styleable.AndroidManifestActivity_banner,
mSeparateProcesses,
- com.android.internal.R.styleable.AndroidManifestActivity_process,
- com.android.internal.R.styleable.AndroidManifestActivity_description,
- com.android.internal.R.styleable.AndroidManifestActivity_enabled);
+ R.styleable.AndroidManifestActivity_process,
+ R.styleable.AndroidManifestActivity_description,
+ R.styleable.AndroidManifestActivity_enabled);
}
mParseActivityArgs.tag = receiver ? "<receiver>" : "<activity>";
@@ -2973,22 +2981,18 @@ public class PackageParser {
return null;
}
- boolean setExported = sa.hasValue(
- com.android.internal.R.styleable.AndroidManifestActivity_exported);
+ boolean setExported = sa.hasValue(R.styleable.AndroidManifestActivity_exported);
if (setExported) {
- a.info.exported = sa.getBoolean(
- com.android.internal.R.styleable.AndroidManifestActivity_exported, false);
+ a.info.exported = sa.getBoolean(R.styleable.AndroidManifestActivity_exported, false);
}
- a.info.theme = sa.getResourceId(
- com.android.internal.R.styleable.AndroidManifestActivity_theme, 0);
+ a.info.theme = sa.getResourceId(R.styleable.AndroidManifestActivity_theme, 0);
- a.info.uiOptions = sa.getInt(
- com.android.internal.R.styleable.AndroidManifestActivity_uiOptions,
+ a.info.uiOptions = sa.getInt(R.styleable.AndroidManifestActivity_uiOptions,
a.info.applicationInfo.uiOptions);
String parentName = sa.getNonConfigurationString(
- com.android.internal.R.styleable.AndroidManifestActivity_parentActivityName,
+ R.styleable.AndroidManifestActivity_parentActivityName,
Configuration.NATIVE_CONFIG_VERSION);
if (parentName != null) {
String parentClassName = buildClassName(a.info.packageName, parentName, outError);
@@ -3002,8 +3006,7 @@ public class PackageParser {
}
String str;
- str = sa.getNonConfigurationString(
- com.android.internal.R.styleable.AndroidManifestActivity_permission, 0);
+ str = sa.getNonConfigurationString(R.styleable.AndroidManifestActivity_permission, 0);
if (str == null) {
a.info.permission = owner.applicationInfo.permission;
} else {
@@ -3011,140 +3014,116 @@ public class PackageParser {
}
str = sa.getNonConfigurationString(
- com.android.internal.R.styleable.AndroidManifestActivity_taskAffinity,
+ R.styleable.AndroidManifestActivity_taskAffinity,
Configuration.NATIVE_CONFIG_VERSION);
a.info.taskAffinity = buildTaskAffinityName(owner.applicationInfo.packageName,
owner.applicationInfo.taskAffinity, str, outError);
a.info.flags = 0;
if (sa.getBoolean(
- com.android.internal.R.styleable.AndroidManifestActivity_multiprocess,
- false)) {
+ R.styleable.AndroidManifestActivity_multiprocess, false)) {
a.info.flags |= ActivityInfo.FLAG_MULTIPROCESS;
}
- if (sa.getBoolean(
- com.android.internal.R.styleable.AndroidManifestActivity_finishOnTaskLaunch,
- false)) {
+ if (sa.getBoolean(R.styleable.AndroidManifestActivity_finishOnTaskLaunch, false)) {
a.info.flags |= ActivityInfo.FLAG_FINISH_ON_TASK_LAUNCH;
}
- if (sa.getBoolean(
- com.android.internal.R.styleable.AndroidManifestActivity_clearTaskOnLaunch,
- false)) {
+ if (sa.getBoolean(R.styleable.AndroidManifestActivity_clearTaskOnLaunch, false)) {
a.info.flags |= ActivityInfo.FLAG_CLEAR_TASK_ON_LAUNCH;
}
- if (sa.getBoolean(
- com.android.internal.R.styleable.AndroidManifestActivity_noHistory,
- false)) {
+ if (sa.getBoolean(R.styleable.AndroidManifestActivity_noHistory, false)) {
a.info.flags |= ActivityInfo.FLAG_NO_HISTORY;
}
- if (sa.getBoolean(
- com.android.internal.R.styleable.AndroidManifestActivity_alwaysRetainTaskState,
- false)) {
+ if (sa.getBoolean(R.styleable.AndroidManifestActivity_alwaysRetainTaskState, false)) {
a.info.flags |= ActivityInfo.FLAG_ALWAYS_RETAIN_TASK_STATE;
}
- if (sa.getBoolean(
- com.android.internal.R.styleable.AndroidManifestActivity_stateNotNeeded,
- false)) {
+ if (sa.getBoolean(R.styleable.AndroidManifestActivity_stateNotNeeded, false)) {
a.info.flags |= ActivityInfo.FLAG_STATE_NOT_NEEDED;
}
- if (sa.getBoolean(
- com.android.internal.R.styleable.AndroidManifestActivity_excludeFromRecents,
- false)) {
+ if (sa.getBoolean(R.styleable.AndroidManifestActivity_excludeFromRecents, false)) {
a.info.flags |= ActivityInfo.FLAG_EXCLUDE_FROM_RECENTS;
}
- if (sa.getBoolean(
- com.android.internal.R.styleable.AndroidManifestActivity_allowTaskReparenting,
+ if (sa.getBoolean(R.styleable.AndroidManifestActivity_allowTaskReparenting,
(owner.applicationInfo.flags&ApplicationInfo.FLAG_ALLOW_TASK_REPARENTING) != 0)) {
a.info.flags |= ActivityInfo.FLAG_ALLOW_TASK_REPARENTING;
}
- if (sa.getBoolean(
- com.android.internal.R.styleable.AndroidManifestActivity_finishOnCloseSystemDialogs,
- false)) {
+ if (sa.getBoolean(R.styleable.AndroidManifestActivity_finishOnCloseSystemDialogs, false)) {
a.info.flags |= ActivityInfo.FLAG_FINISH_ON_CLOSE_SYSTEM_DIALOGS;
}
- if (sa.getBoolean(
- com.android.internal.R.styleable.AndroidManifestActivity_showOnLockScreen,
- false)) {
+ if (sa.getBoolean(R.styleable.AndroidManifestActivity_showOnLockScreen, false)) {
a.info.flags |= ActivityInfo.FLAG_SHOW_ON_LOCK_SCREEN;
}
- if (sa.getBoolean(
- com.android.internal.R.styleable.AndroidManifestActivity_immersive,
- false)) {
+ if (sa.getBoolean(R.styleable.AndroidManifestActivity_immersive, false)) {
a.info.flags |= ActivityInfo.FLAG_IMMERSIVE;
}
+ if (sa.getBoolean(R.styleable.AndroidManifestActivity_primaryUserOnly, false)) {
+ a.info.flags |= ActivityInfo.FLAG_PRIMARY_USER_ONLY;
+ }
+
if (!receiver) {
- if (sa.getBoolean(
- com.android.internal.R.styleable.AndroidManifestActivity_hardwareAccelerated,
+ if (sa.getBoolean(R.styleable.AndroidManifestActivity_hardwareAccelerated,
hardwareAccelerated)) {
a.info.flags |= ActivityInfo.FLAG_HARDWARE_ACCELERATED;
}
a.info.launchMode = sa.getInt(
- com.android.internal.R.styleable.AndroidManifestActivity_launchMode,
- ActivityInfo.LAUNCH_MULTIPLE);
+ R.styleable.AndroidManifestActivity_launchMode, ActivityInfo.LAUNCH_MULTIPLE);
a.info.documentLaunchMode = sa.getInt(
- com.android.internal.R.styleable.AndroidManifestActivity_documentLaunchMode,
+ R.styleable.AndroidManifestActivity_documentLaunchMode,
ActivityInfo.DOCUMENT_LAUNCH_NONE);
a.info.maxRecents = sa.getInt(
- com.android.internal.R.styleable.AndroidManifestActivity_maxRecents,
+ R.styleable.AndroidManifestActivity_maxRecents,
ActivityManager.getDefaultAppRecentsLimitStatic());
- a.info.screenOrientation = sa.getInt(
- com.android.internal.R.styleable.AndroidManifestActivity_screenOrientation,
- ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
- a.info.configChanges = sa.getInt(
- com.android.internal.R.styleable.AndroidManifestActivity_configChanges,
- 0);
+ a.info.configChanges = sa.getInt(R.styleable.AndroidManifestActivity_configChanges, 0);
a.info.softInputMode = sa.getInt(
- com.android.internal.R.styleable.AndroidManifestActivity_windowSoftInputMode,
- 0);
+ R.styleable.AndroidManifestActivity_windowSoftInputMode, 0);
a.info.persistableMode = sa.getInteger(
- com.android.internal.R.styleable.AndroidManifestActivity_persistableMode,
+ R.styleable.AndroidManifestActivity_persistableMode,
ActivityInfo.PERSIST_ROOT_ONLY);
- if (sa.getBoolean(
- com.android.internal.R.styleable.AndroidManifestActivity_allowEmbedded,
- false)) {
+ if (sa.getBoolean(R.styleable.AndroidManifestActivity_allowEmbedded, false)) {
a.info.flags |= ActivityInfo.FLAG_ALLOW_EMBEDDED;
}
- if (sa.getBoolean(
- com.android.internal.R.styleable.AndroidManifestActivity_autoRemoveFromRecents,
- false)) {
+ if (sa.getBoolean(R.styleable.AndroidManifestActivity_autoRemoveFromRecents, false)) {
a.info.flags |= ActivityInfo.FLAG_AUTO_REMOVE_FROM_RECENTS;
}
- if (sa.getBoolean(
- com.android.internal.R.styleable.AndroidManifestActivity_relinquishTaskIdentity,
- false)) {
+ if (sa.getBoolean(R.styleable.AndroidManifestActivity_relinquishTaskIdentity, false)) {
a.info.flags |= ActivityInfo.FLAG_RELINQUISH_TASK_IDENTITY;
}
- if (sa.getBoolean(
- com.android.internal.R.styleable.AndroidManifestActivity_resumeWhilePausing,
- false)) {
+ if (sa.getBoolean(R.styleable.AndroidManifestActivity_resumeWhilePausing, false)) {
a.info.flags |= ActivityInfo.FLAG_RESUME_WHILE_PAUSING;
}
+
+ a.info.resizeable = sa.getBoolean(
+ R.styleable.AndroidManifestActivity_resizeableActivity,
+ owner.applicationInfo.targetSdkVersion >= Build.VERSION_CODES.MNC);
+ if (a.info.resizeable) {
+ // Fixed screen orientation isn't supported with resizeable activities.
+ a.info.screenOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
+ } else {
+ a.info.screenOrientation = sa.getInt(
+ R.styleable.AndroidManifestActivity_screenOrientation,
+ ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
+ }
} else {
a.info.launchMode = ActivityInfo.LAUNCH_MULTIPLE;
a.info.configChanges = 0;
- }
- if (receiver) {
- if (sa.getBoolean(
- com.android.internal.R.styleable.AndroidManifestActivity_singleUser,
- false)) {
+ if (sa.getBoolean(R.styleable.AndroidManifestActivity_singleUser, false)) {
a.info.flags |= ActivityInfo.FLAG_SINGLE_USER;
if (a.info.exported && (flags & PARSE_IS_PRIVILEGED) == 0) {
Slog.w(TAG, "Activity exported request ignored due to singleUser: "
@@ -3154,11 +3133,6 @@ public class PackageParser {
setExported = true;
}
}
- if (sa.getBoolean(
- com.android.internal.R.styleable.AndroidManifestActivity_primaryUserOnly,
- false)) {
- a.info.flags |= ActivityInfo.FLAG_PRIMARY_USER_ONLY;
- }
}
sa.recycle();
diff --git a/core/java/android/content/pm/RegisteredServicesCache.java b/core/java/android/content/pm/RegisteredServicesCache.java
index 391ef22..a386097 100644
--- a/core/java/android/content/pm/RegisteredServicesCache.java
+++ b/core/java/android/content/pm/RegisteredServicesCache.java
@@ -35,6 +35,7 @@ import android.util.SparseArray;
import android.util.Xml;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.FastXmlSerializer;
import com.google.android.collect.Lists;
@@ -56,6 +57,8 @@ import java.util.Collections;
import java.util.List;
import java.util.Map;
+import libcore.io.IoUtils;
+
/**
* Cache of registered services. This cache is lazily built by interrogating
* {@link PackageManager} on a per-user basis. It's updated as packages are
@@ -113,13 +116,19 @@ public abstract class RegisteredServicesCache<V> {
public RegisteredServicesCache(Context context, String interfaceName, String metaDataName,
String attributeName, XmlSerializerAndParser<V> serializerAndParser) {
+ this(context, interfaceName, metaDataName, attributeName, serializerAndParser,
+ Environment.getDataDirectory());
+ }
+
+ @VisibleForTesting
+ protected RegisteredServicesCache(Context context, String interfaceName, String metaDataName,
+ String attributeName, XmlSerializerAndParser<V> serializerAndParser, File dataDir) {
mContext = context;
mInterfaceName = interfaceName;
mMetaDataName = metaDataName;
mAttributesName = attributeName;
mSerializerAndParser = serializerAndParser;
- File dataDir = Environment.getDataDirectory();
File systemDir = new File(dataDir, "system");
File syncDir = new File(systemDir, "registered_services");
mPersistentServicesFile = new AtomicFile(new File(syncDir, interfaceName + ".xml"));
@@ -303,7 +312,8 @@ public abstract class RegisteredServicesCache<V> {
}
}
- private boolean inSystemImage(int callerUid) {
+ @VisibleForTesting
+ protected boolean inSystemImage(int callerUid) {
String[] packages = mContext.getPackageManager().getPackagesForUid(callerUid);
for (String name : packages) {
try {
@@ -319,6 +329,13 @@ public abstract class RegisteredServicesCache<V> {
return false;
}
+ @VisibleForTesting
+ protected List<ResolveInfo> queryIntentServices(int userId) {
+ final PackageManager pm = mContext.getPackageManager();
+ return pm.queryIntentServicesAsUser(
+ new Intent(mInterfaceName), PackageManager.GET_META_DATA, userId);
+ }
+
/**
* Populate {@link UserServices#services} by scanning installed packages for
* given {@link UserHandle}.
@@ -331,10 +348,8 @@ public abstract class RegisteredServicesCache<V> {
Slog.d(TAG, "generateServicesMap() for " + userId + ", changed UIDs = " + changedUids);
}
- final PackageManager pm = mContext.getPackageManager();
final ArrayList<ServiceInfo<V>> serviceInfos = new ArrayList<ServiceInfo<V>>();
- final List<ResolveInfo> resolveInfos = pm.queryIntentServicesAsUser(
- new Intent(mInterfaceName), PackageManager.GET_META_DATA, userId);
+ final List<ResolveInfo> resolveInfos = queryIntentServices(userId);
for (ResolveInfo resolveInfo : resolveInfos) {
try {
ServiceInfo<V> info = parseServiceInfo(resolveInfo);
@@ -343,9 +358,7 @@ public abstract class RegisteredServicesCache<V> {
continue;
}
serviceInfos.add(info);
- } catch (XmlPullParserException e) {
- Log.w(TAG, "Unable to load service info " + resolveInfo.toString(), e);
- } catch (IOException e) {
+ } catch (XmlPullParserException|IOException e) {
Log.w(TAG, "Unable to load service info " + resolveInfo.toString(), e);
}
}
@@ -481,7 +494,8 @@ public abstract class RegisteredServicesCache<V> {
return false;
}
- private ServiceInfo<V> parseServiceInfo(ResolveInfo service)
+ @VisibleForTesting
+ protected ServiceInfo<V> parseServiceInfo(ResolveInfo service)
throws XmlPullParserException, IOException {
android.content.pm.ServiceInfo si = service.serviceInfo;
ComponentName componentName = new ComponentName(si.packageName, si.name);
@@ -571,12 +585,7 @@ public abstract class RegisteredServicesCache<V> {
} catch (Exception e) {
Log.w(TAG, "Error reading persistent services, starting from scratch", e);
} finally {
- if (fis != null) {
- try {
- fis.close();
- } catch (java.io.IOException e1) {
- }
- }
+ IoUtils.closeQuietly(fis);
}
}
@@ -607,7 +616,7 @@ public abstract class RegisteredServicesCache<V> {
out.endTag(null, "services");
out.endDocument();
mPersistentServicesFile.finishWrite(fos);
- } catch (java.io.IOException e1) {
+ } catch (IOException e1) {
Log.w(TAG, "Error writing accounts", e1);
if (fos != null) {
mPersistentServicesFile.failWrite(fos);
@@ -615,6 +624,11 @@ public abstract class RegisteredServicesCache<V> {
}
}
+ @VisibleForTesting
+ protected Map<V, Integer> getPersistentServices(int userId) {
+ return findOrCreateUserLocked(userId).persistentServices;
+ }
+
public abstract V parseServiceAttributes(Resources res,
String packageName, AttributeSet attrs);
}
diff --git a/core/java/android/content/res/ColorStateList.java b/core/java/android/content/res/ColorStateList.java
index 68a39d3..b42d8bc 100644
--- a/core/java/android/content/res/ColorStateList.java
+++ b/core/java/android/content/res/ColorStateList.java
@@ -16,8 +16,12 @@
package android.content.res;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.res.Resources.Theme;
import android.graphics.Color;
+import com.android.internal.R;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.GrowingArrayUtils;
@@ -25,6 +29,7 @@ import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import android.util.AttributeSet;
+import android.util.Log;
import android.util.MathUtils;
import android.util.SparseArray;
import android.util.StateSet;
@@ -64,15 +69,23 @@ import java.util.Arrays;
* List Resource</a>.</p>
*/
public class ColorStateList implements Parcelable {
- private int[][] mStateSpecs; // must be parallel to mColors
- private int[] mColors; // must be parallel to mStateSpecs
- private int mDefaultColor = 0xffff0000;
-
+ private static final String TAG = "ColorStateList";
+ private static final int DEFAULT_COLOR = Color.RED;
private static final int[][] EMPTY = new int[][] { new int[0] };
private static final SparseArray<WeakReference<ColorStateList>> sCache =
new SparseArray<WeakReference<ColorStateList>>();
- private ColorStateList() { }
+ private int[][] mThemeAttrs;
+ private int mChangingConfigurations;
+
+ private int[][] mStateSpecs;
+ private int[] mColors;
+ private int mDefaultColor;
+ private boolean mIsOpaque;
+
+ private ColorStateList() {
+ // Not publicly instantiable.
+ }
/**
* Creates a ColorStateList that returns the specified mapping from
@@ -82,53 +95,102 @@ public class ColorStateList implements Parcelable {
mStateSpecs = states;
mColors = colors;
- if (states.length > 0) {
- mDefaultColor = colors[0];
-
- for (int i = 0; i < states.length; i++) {
- if (states[i].length == 0) {
- mDefaultColor = colors[i];
- }
- }
- }
+ onColorsChanged();
}
/**
- * Creates or retrieves a ColorStateList that always returns a single color.
+ * @return A ColorStateList containing a single color.
*/
+ @NonNull
public static ColorStateList valueOf(int color) {
- // TODO: should we collect these eventually?
synchronized (sCache) {
- final WeakReference<ColorStateList> ref = sCache.get(color);
+ final int index = sCache.indexOfKey(color);
+ if (index >= 0) {
+ final ColorStateList cached = sCache.valueAt(index).get();
+ if (cached != null) {
+ return cached;
+ }
- ColorStateList csl = ref != null ? ref.get() : null;
- if (csl != null) {
- return csl;
+ // Prune missing entry.
+ sCache.removeAt(index);
}
- csl = new ColorStateList(EMPTY, new int[] { color });
+ // Prune the cache before adding new items.
+ final int N = sCache.size();
+ for (int i = N - 1; i >= 0; i--) {
+ if (sCache.valueAt(i).get() == null) {
+ sCache.removeAt(i);
+ }
+ }
+
+ final ColorStateList csl = new ColorStateList(EMPTY, new int[] { color });
sCache.put(color, new WeakReference<ColorStateList>(csl));
return csl;
}
}
/**
- * Create a ColorStateList from an XML document, given a set of {@link Resources}.
+ * Creates a ColorStateList with the same properties as another
+ * ColorStateList.
+ * <p>
+ * The properties of the new ColorStateList can be modified without
+ * affecting the source ColorStateList.
+ *
+ * @param orig the source color state list
*/
+ private ColorStateList(ColorStateList orig) {
+ if (orig != null) {
+ mStateSpecs = orig.mStateSpecs;
+ mDefaultColor = orig.mDefaultColor;
+ mIsOpaque = orig.mIsOpaque;
+
+ // Deep copy, this may change due to theming.
+ mColors = orig.mColors.clone();
+ }
+ }
+
+ /**
+ * Creates a ColorStateList from an XML document.
+ *
+ * @param r Resources against which the ColorStateList should be inflated.
+ * @param parser Parser for the XML document defining the ColorStateList.
+ * @return A new color state list.
+ *
+ * @deprecated Use #createFromXml(Resources, XmlPullParser parser, Theme)
+ */
+ @NonNull
+ @Deprecated
public static ColorStateList createFromXml(Resources r, XmlPullParser parser)
throws XmlPullParserException, IOException {
+ return createFromXml(r, parser, null);
+ }
+
+ /**
+ * Creates a ColorStateList from an XML document using given a set of
+ * {@link Resources} and a {@link Theme}.
+ *
+ * @param r Resources against which the ColorStateList should be inflated.
+ * @param parser Parser for the XML document defining the ColorStateList.
+ * @param theme Optional theme to apply to the color state list, may be
+ * {@code null}.
+ * @return A new color state list.
+ */
+ @NonNull
+ public static ColorStateList createFromXml(@NonNull Resources r, @NonNull XmlPullParser parser,
+ @Nullable Theme theme) throws XmlPullParserException, IOException {
final AttributeSet attrs = Xml.asAttributeSet(parser);
int type;
- while ((type=parser.next()) != XmlPullParser.START_TAG
+ while ((type = parser.next()) != XmlPullParser.START_TAG
&& type != XmlPullParser.END_DOCUMENT) {
+ // Seek parser to start tag.
}
if (type != XmlPullParser.START_TAG) {
throw new XmlPullParserException("No start tag found");
}
- return createFromXmlInner(r, parser, attrs);
+ return createFromXmlInner(r, parser, attrs, theme);
}
/**
@@ -136,28 +198,31 @@ public class ColorStateList implements Parcelable {
* tag in an XML document, tries to create a ColorStateList from that tag.
*
* @throws XmlPullParserException if the current tag is not &lt;selector>
- * @return A color state list for the current tag.
+ * @return A new color state list for the current tag.
*/
- private static ColorStateList createFromXmlInner(Resources r, XmlPullParser parser,
- AttributeSet attrs) throws XmlPullParserException, IOException {
- final ColorStateList colorStateList;
+ @NonNull
+ private static ColorStateList createFromXmlInner(@NonNull Resources r,
+ @NonNull XmlPullParser parser, @NonNull AttributeSet attrs, @Nullable Theme theme)
+ throws XmlPullParserException, IOException {
final String name = parser.getName();
- if (name.equals("selector")) {
- colorStateList = new ColorStateList();
- } else {
+ if (!name.equals("selector")) {
throw new XmlPullParserException(
- parser.getPositionDescription() + ": invalid drawable tag " + name);
+ parser.getPositionDescription() + ": invalid color state list tag " + name);
}
- colorStateList.inflate(r, parser, attrs);
+ final ColorStateList colorStateList = new ColorStateList();
+ colorStateList.inflate(r, parser, attrs, theme);
return colorStateList;
}
/**
- * Creates a new ColorStateList that has the same states and
- * colors as this one but where each color has the specified alpha value
- * (0-255).
+ * Creates a new ColorStateList that has the same states and colors as this
+ * one but where each color has the specified alpha value (0-255).
+ *
+ * @param alpha The new alpha channel value (0-255).
+ * @return A new color state list.
*/
+ @NonNull
public ColorStateList withAlpha(int alpha) {
final int[] colors = new int[mColors.length];
final int len = colors.length;
@@ -171,88 +236,154 @@ public class ColorStateList implements Parcelable {
/**
* Fill in this object based on the contents of an XML "selector" element.
*/
- private void inflate(Resources r, XmlPullParser parser, AttributeSet attrs)
+ private void inflate(@NonNull Resources r, @NonNull XmlPullParser parser,
+ @NonNull AttributeSet attrs, @Nullable Theme theme)
throws XmlPullParserException, IOException {
- int type;
-
final int innerDepth = parser.getDepth()+1;
int depth;
+ int type;
+
+ int changingConfigurations = 0;
+ int defaultColor = DEFAULT_COLOR;
+
+ boolean hasUnresolvedAttrs = false;
int[][] stateSpecList = ArrayUtils.newUnpaddedArray(int[].class, 20);
+ int[][] themeAttrsList = new int[stateSpecList.length][];
int[] colorList = new int[stateSpecList.length];
int listSize = 0;
- while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
- && ((depth=parser.getDepth()) >= innerDepth
- || type != XmlPullParser.END_TAG)) {
- if (type != XmlPullParser.START_TAG) {
+ while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+ && ((depth = parser.getDepth()) >= innerDepth || type != XmlPullParser.END_TAG)) {
+ if (type != XmlPullParser.START_TAG || depth > innerDepth
+ || !parser.getName().equals("item")) {
continue;
}
- if (depth > innerDepth || !parser.getName().equals("item")) {
- continue;
- }
+ final TypedArray a = Resources.obtainAttributes(r, theme, attrs,
+ R.styleable.ColorStateListItem);
+ final int[] themeAttrs = a.extractThemeAttrs();
+ final int baseColor = a.getColor(R.styleable.ColorStateListItem_color, 0);
+ final float alphaMod = a.getFloat(R.styleable.ColorStateListItem_alpha, 1.0f);
+
+ changingConfigurations |= a.getChangingConfigurations();
- int alphaRes = 0;
- float alpha = 1.0f;
- int colorRes = 0;
- int color = 0xffff0000;
- boolean haveColor = false;
+ a.recycle();
- int i;
+ // Parse all unrecognized attributes as state specifiers.
int j = 0;
final int numAttrs = attrs.getAttributeCount();
int[] stateSpec = new int[numAttrs];
- for (i = 0; i < numAttrs; i++) {
+ for (int i = 0; i < numAttrs; i++) {
final int stateResId = attrs.getAttributeNameResource(i);
- if (stateResId == 0) break;
- if (stateResId == com.android.internal.R.attr.alpha) {
- alphaRes = attrs.getAttributeResourceValue(i, 0);
- if (alphaRes == 0) {
- alpha = attrs.getAttributeFloatValue(i, 1.0f);
- }
- } else if (stateResId == com.android.internal.R.attr.color) {
- colorRes = attrs.getAttributeResourceValue(i, 0);
- if (colorRes == 0) {
- color = attrs.getAttributeIntValue(i, color);
- haveColor = true;
- }
- } else {
- stateSpec[j++] = attrs.getAttributeBooleanValue(i, false)
- ? stateResId : -stateResId;
+ switch (stateResId) {
+ case R.attr.color:
+ case R.attr.alpha:
+ // Recognized attribute, ignore.
+ break;
+ default:
+ stateSpec[j++] = attrs.getAttributeBooleanValue(i, false)
+ ? stateResId : -stateResId;
}
}
stateSpec = StateSet.trimStateSet(stateSpec, j);
- if (colorRes != 0) {
- color = r.getColor(colorRes);
- } else if (!haveColor) {
- throw new XmlPullParserException(
- parser.getPositionDescription()
- + ": <item> tag requires a 'android:color' attribute.");
- }
-
- if (alphaRes != 0) {
- alpha = r.getFloat(alphaRes);
- }
-
// Apply alpha modulation.
- final int alphaMod = MathUtils.constrain((int) (Color.alpha(color) * alpha), 0, 255);
- color = (color & 0xFFFFFF) | (alphaMod << 24);
-
+ final int color = modulateColorAlpha(baseColor, alphaMod);
if (listSize == 0 || stateSpec.length == 0) {
- mDefaultColor = color;
+ defaultColor = color;
+ }
+
+ if (themeAttrs != null) {
+ hasUnresolvedAttrs = true;
}
colorList = GrowingArrayUtils.append(colorList, listSize, color);
+ themeAttrsList = GrowingArrayUtils.append(themeAttrsList, listSize, themeAttrs);
stateSpecList = GrowingArrayUtils.append(stateSpecList, listSize, stateSpec);
listSize++;
}
+ mChangingConfigurations = changingConfigurations;
+ mDefaultColor = defaultColor;
+
+ if (hasUnresolvedAttrs) {
+ mThemeAttrs = new int[listSize][];
+ System.arraycopy(themeAttrsList, 0, mThemeAttrs, 0, listSize);
+ } else {
+ mThemeAttrs = null;
+ }
+
mColors = new int[listSize];
mStateSpecs = new int[listSize][];
System.arraycopy(colorList, 0, mColors, 0, listSize);
System.arraycopy(stateSpecList, 0, mStateSpecs, 0, listSize);
+
+ onColorsChanged();
+ }
+
+ /**
+ * Returns whether a theme can be applied to this color state list, which
+ * usually indicates that the color state list has unresolved theme
+ * attributes.
+ *
+ * @return whether a theme can be applied to this color state list
+ */
+ public boolean canApplyTheme() {
+ return mThemeAttrs != null;
+ }
+
+ /**
+ * Applies a theme to this color state list.
+ *
+ * @param t the theme to apply
+ */
+ public void applyTheme(Theme t) {
+ if (mThemeAttrs == null) {
+ return;
+ }
+
+ boolean hasUnresolvedAttrs = false;
+
+ final int[][] themeAttrsList = mThemeAttrs;
+ final int N = themeAttrsList.length;
+ for (int i = 0; i < N; i++) {
+ if (themeAttrsList[i] != null) {
+ final TypedArray a = t.resolveAttributes(themeAttrsList[i],
+ R.styleable.ColorStateListItem);
+ final int baseColor = a.getColor(
+ R.styleable.ColorStateListItem_color, mColors[i]);
+ final float alphaMod = a.getFloat(
+ R.styleable.ColorStateListItem_alpha, 1.0f);
+
+ mColors[i] = modulateColorAlpha(baseColor, alphaMod);
+ mChangingConfigurations |= a.getChangingConfigurations();
+
+ themeAttrsList[i] = a.extractThemeAttrs(themeAttrsList[i]);
+ if (themeAttrsList[i] != null) {
+ hasUnresolvedAttrs = true;
+ }
+
+ a.recycle();
+ }
+ }
+
+ if (!hasUnresolvedAttrs) {
+ mThemeAttrs = null;
+ }
+
+ onColorsChanged();
+ }
+
+ private int modulateColorAlpha(int baseColor, float alphaMod) {
+ if (alphaMod == 1.0f) {
+ return baseColor;
+ }
+
+ final int baseAlpha = Color.alpha(baseColor);
+ final int alpha = MathUtils.constrain((int) (baseAlpha * alphaMod + 0.5f), 0, 255);
+ final int color = (baseColor & 0xFFFFFF) | (alpha << 24);
+ return color;
}
/**
@@ -275,28 +406,24 @@ public class ColorStateList implements Parcelable {
* @return True if this color state list is opaque.
*/
public boolean isOpaque() {
- final int n = mColors.length;
- for (int i = 0; i < n; i++) {
- if (Color.alpha(mColors[i]) != 0xFF) {
- return false;
- }
- }
- return true;
+ return mIsOpaque;
}
/**
- * Return the color associated with the given set of {@link android.view.View} states.
+ * Return the color associated with the given set of
+ * {@link android.view.View} states.
*
* @param stateSet an array of {@link android.view.View} states
- * @param defaultColor the color to return if there's not state spec in this
- * {@link ColorStateList} that matches the stateSet.
+ * @param defaultColor the color to return if there's no matching state
+ * spec in this {@link ColorStateList} that matches the
+ * stateSet.
*
* @return the color associated with that set of states in this {@link ColorStateList}.
*/
- public int getColorForState(int[] stateSet, int defaultColor) {
+ public int getColorForState(@Nullable int[] stateSet, int defaultColor) {
final int setLength = mStateSpecs.length;
for (int i = 0; i < setLength; i++) {
- int[] stateSpec = mStateSpecs[i];
+ final int[] stateSpec = mStateSpecs[i];
if (StateSet.stateSetMatches(stateSpec, stateSet)) {
return mColors[i];
}
@@ -314,7 +441,9 @@ public class ColorStateList implements Parcelable {
}
/**
- * Return the states in this {@link ColorStateList}.
+ * Return the states in this {@link ColorStateList}. The returned array
+ * should not be modified.
+ *
* @return the states in this {@link ColorStateList}
* @hide
*/
@@ -323,7 +452,9 @@ public class ColorStateList implements Parcelable {
}
/**
- * Return the colors in this {@link ColorStateList}.
+ * Return the colors in this {@link ColorStateList}. The returned array
+ * should not be modified.
+ *
* @return the colors in this {@link ColorStateList}
* @hide
*/
@@ -374,11 +505,81 @@ public class ColorStateList implements Parcelable {
@Override
public String toString() {
return "ColorStateList{" +
+ "mThemeAttrs=" + Arrays.deepToString(mThemeAttrs) +
+ "mChangingConfigurations=" + mChangingConfigurations +
"mStateSpecs=" + Arrays.deepToString(mStateSpecs) +
"mColors=" + Arrays.toString(mColors) +
"mDefaultColor=" + mDefaultColor + '}';
}
+ /**
+ * Updates the default color and opacity.
+ */
+ private void onColorsChanged() {
+ int defaultColor = DEFAULT_COLOR;
+ boolean isOpaque = true;
+
+ final int[][] states = mStateSpecs;
+ final int[] colors = mColors;
+ final int N = states.length;
+ if (N > 0) {
+ defaultColor = colors[0];
+
+ for (int i = N - 1; i > 0; i--) {
+ if (states[i].length == 0) {
+ defaultColor = colors[i];
+ break;
+ }
+ }
+
+ for (int i = 0; i < N; i++) {
+ if (Color.alpha(colors[i]) != 0xFF) {
+ isOpaque = false;
+ break;
+ }
+ }
+ }
+
+ mDefaultColor = defaultColor;
+ mIsOpaque = isOpaque;
+ }
+
+ /**
+ * @return A factory that can create new instances of this ColorStateList.
+ */
+ ColorStateListFactory getFactory() {
+ return new ColorStateListFactory(this);
+ }
+
+ static class ColorStateListFactory extends ConstantState<ColorStateList> {
+ final ColorStateList mSrc;
+
+ public ColorStateListFactory(ColorStateList src) {
+ mSrc = src;
+ }
+
+ @Override
+ public int getChangingConfigurations() {
+ return mSrc.mChangingConfigurations;
+ }
+
+ @Override
+ public ColorStateList newInstance() {
+ return mSrc;
+ }
+
+ @Override
+ public ColorStateList newInstance(Resources res, Theme theme) {
+ if (theme == null || !mSrc.canApplyTheme()) {
+ return mSrc;
+ }
+
+ final ColorStateList clone = new ColorStateList(mSrc);
+ clone.applyTheme(theme);
+ return clone;
+ }
+ }
+
@Override
public int describeContents() {
return 0;
@@ -386,6 +587,9 @@ public class ColorStateList implements Parcelable {
@Override
public void writeToParcel(Parcel dest, int flags) {
+ if (canApplyTheme()) {
+ Log.w(TAG, "Wrote partially-resolved ColorStateList to parcel!");
+ }
final int N = mStateSpecs.length;
dest.writeInt(N);
for (int i = 0; i < N; i++) {
diff --git a/core/java/android/content/res/Resources.java b/core/java/android/content/res/Resources.java
index 73913b6..6fb7299 100644
--- a/core/java/android/content/res/Resources.java
+++ b/core/java/android/content/res/Resources.java
@@ -16,21 +16,20 @@
package android.content.res;
-import android.animation.Animator;
-import android.animation.StateListAnimator;
-import android.annotation.NonNull;
-import android.util.Pools.SynchronizedPool;
-import android.view.ViewDebug;
import com.android.internal.util.XmlUtils;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
+import android.animation.Animator;
+import android.animation.StateListAnimator;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.pm.ActivityInfo;
+import android.content.res.ColorStateList.ColorStateListFactory;
import android.graphics.Movie;
-import android.graphics.drawable.Drawable;
import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.Drawable;
import android.graphics.drawable.Drawable.ConstantState;
import android.os.Build;
import android.os.Bundle;
@@ -40,9 +39,11 @@ import android.util.ArrayMap;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.Log;
+import android.util.LongSparseArray;
+import android.util.Pools.SynchronizedPool;
import android.util.Slog;
import android.util.TypedValue;
-import android.util.LongSparseArray;
+import android.view.ViewDebug;
import java.io.IOException;
import java.io.InputStream;
@@ -97,8 +98,8 @@ public class Resources {
private static final LongSparseArray<ConstantState>[] sPreloadedDrawables;
private static final LongSparseArray<ConstantState> sPreloadedColorDrawables
= new LongSparseArray<ConstantState>();
- private static final LongSparseArray<ColorStateList> sPreloadedColorStateLists
- = new LongSparseArray<ColorStateList>();
+ private static final LongSparseArray<ColorStateListFactory> sPreloadedColorStateLists
+ = new LongSparseArray<ColorStateListFactory>();
// Pool of TypedArrays targeted to this Resources object.
final SynchronizedPool<TypedArray> mTypedArrayPool = new SynchronizedPool<TypedArray>(5);
@@ -116,8 +117,8 @@ public class Resources {
new ArrayMap<String, LongSparseArray<WeakReference<ConstantState>>>();
private final ArrayMap<String, LongSparseArray<WeakReference<ConstantState>>> mColorDrawableCache =
new ArrayMap<String, LongSparseArray<WeakReference<ConstantState>>>();
- private final LongSparseArray<WeakReference<ColorStateList>> mColorStateListCache =
- new LongSparseArray<WeakReference<ColorStateList>>();
+ private final ConfigurationBoundResourceCache<ColorStateList> mColorStateListCache =
+ new ConfigurationBoundResourceCache<ColorStateList>(this);
private final ConfigurationBoundResourceCache<Animator> mAnimatorCache =
new ConfigurationBoundResourceCache<Animator>(this);
private final ConfigurationBoundResourceCache<StateListAnimator> mStateListAnimatorCache =
@@ -140,9 +141,6 @@ public class Resources {
private CompatibilityInfo mCompatibilityInfo = CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO;
- @SuppressWarnings("unused")
- private WeakReference<IBinder> mToken;
-
static {
sPreloadedDrawables = new LongSparseArray[2];
sPreloadedDrawables[0] = new LongSparseArray<ConstantState>();
@@ -223,46 +221,44 @@ public class Resources {
/**
* Create a new Resources object on top of an existing set of assets in an
* AssetManager.
- *
- * @param assets Previously created AssetManager.
- * @param metrics Current display metrics to consider when
+ *
+ * @param assets Previously created AssetManager.
+ * @param metrics Current display metrics to consider when
* selecting/computing resource values.
- * @param config Desired device configuration to consider when
+ * @param config Desired device configuration to consider when
* selecting/computing resource values (optional).
*/
public Resources(AssetManager assets, DisplayMetrics metrics, Configuration config) {
- this(assets, metrics, config, CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null);
+ this(assets, metrics, config, CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO);
}
/**
* Creates a new Resources object with CompatibilityInfo.
- *
- * @param assets Previously created AssetManager.
- * @param metrics Current display metrics to consider when
+ *
+ * @param assets Previously created AssetManager.
+ * @param metrics Current display metrics to consider when
* selecting/computing resource values.
- * @param config Desired device configuration to consider when
+ * @param config Desired device configuration to consider when
* selecting/computing resource values (optional).
* @param compatInfo this resource's compatibility info. Must not be null.
- * @param token The Activity token for determining stack affiliation. Usually null.
* @hide
*/
public Resources(AssetManager assets, DisplayMetrics metrics, Configuration config,
- CompatibilityInfo compatInfo, IBinder token) {
+ CompatibilityInfo compatInfo) {
mAssets = assets;
mMetrics.setToDefaults();
if (compatInfo != null) {
mCompatibilityInfo = compatInfo;
}
- mToken = new WeakReference<IBinder>(token);
updateConfiguration(config, metrics);
assets.ensureStringBlocks();
}
/**
* Return a global shared Resources object that provides access to only
- * system resources (no application resources), and is not configured for
- * the current screen (can not use dimension units, does not change based
- * on orientation, etc).
+ * system resources (no application resources), and is not configured for
+ * the current screen (can not use dimension units, does not change based
+ * on orientation, etc).
*/
public static Resources getSystem() {
synchronized (sSync) {
@@ -897,20 +893,41 @@ public class Resources {
}
/**
- * Return a color integer associated with a particular resource ID.
- * If the resource holds a complex
- * {@link android.content.res.ColorStateList}, then the default color from
- * the set is returned.
+ * Returns a color integer associated with a particular resource ID. If the
+ * resource holds a complex {@link ColorStateList}, then the default color
+ * from the set is returned.
*
* @param id The desired resource identifier, as generated by the aapt
* tool. This integer encodes the package, type, and resource
* entry. The value 0 is an invalid identifier.
*
- * @throws NotFoundException Throws NotFoundException if the given ID does not exist.
+ * @throws NotFoundException Throws NotFoundException if the given ID does
+ * not exist.
*
- * @return Returns a single color value in the form 0xAARRGGBB.
+ * @return A single color value in the form 0xAARRGGBB.
+ * @deprecated Use {@link #getColor(int, Theme)} instead.
*/
public int getColor(int id) throws NotFoundException {
+ return getColor(id, null);
+ }
+
+ /**
+ * Returns a themed color integer associated with a particular resource ID.
+ * If the resource holds a complex {@link ColorStateList}, then the default
+ * color from the set is returned.
+ *
+ * @param id The desired resource identifier, as generated by the aapt
+ * tool. This integer encodes the package, type, and resource
+ * entry. The value 0 is an invalid identifier.
+ * @param theme The theme used to style the color attributes, may be
+ * {@code null}.
+ *
+ * @throws NotFoundException Throws NotFoundException if the given ID does
+ * not exist.
+ *
+ * @return A single color value in the form 0xAARRGGBB.
+ */
+ public int getColor(int id, @Nullable Theme theme) throws NotFoundException {
TypedValue value;
synchronized (mAccessLock) {
value = mTmpValue;
@@ -919,40 +936,77 @@ public class Resources {
}
getValue(id, value, true);
if (value.type >= TypedValue.TYPE_FIRST_INT
- && value.type <= TypedValue.TYPE_LAST_INT) {
+ && value.type <= TypedValue.TYPE_LAST_INT) {
mTmpValue = value;
return value.data;
} else if (value.type != TypedValue.TYPE_STRING) {
throw new NotFoundException(
- "Resource ID #0x" + Integer.toHexString(id) + " type #0x"
- + Integer.toHexString(value.type) + " is not valid");
+ "Resource ID #0x" + Integer.toHexString(id) + " type #0x"
+ + Integer.toHexString(value.type) + " is not valid");
}
mTmpValue = null;
}
- ColorStateList csl = loadColorStateList(value, id);
+
+ final ColorStateList csl = loadColorStateList(value, id, theme);
synchronized (mAccessLock) {
if (mTmpValue == null) {
mTmpValue = value;
}
}
+
return csl.getDefaultColor();
}
/**
- * Return a color state list associated with a particular resource ID. The
- * resource may contain either a single raw color value, or a complex
- * {@link android.content.res.ColorStateList} holding multiple possible colors.
+ * Returns a color state list associated with a particular resource ID. The
+ * resource may contain either a single raw color value or a complex
+ * {@link ColorStateList} holding multiple possible colors.
*
* @param id The desired resource identifier of a {@link ColorStateList},
- * as generated by the aapt tool. This integer encodes the package, type, and resource
- * entry. The value 0 is an invalid identifier.
+ * as generated by the aapt tool. This integer encodes the
+ * package, type, and resource entry. The value 0 is an invalid
+ * identifier.
*
- * @throws NotFoundException Throws NotFoundException if the given ID does not exist.
+ * @throws NotFoundException Throws NotFoundException if the given ID does
+ * not exist.
*
- * @return Returns a ColorStateList object containing either a single
- * solid color or multiple colors that can be selected based on a state.
+ * @return A ColorStateList object containing either a single solid color
+ * or multiple colors that can be selected based on a state.
+ * @deprecated Use {@link #getColorStateList(int, Theme)} instead.
*/
+ @Nullable
public ColorStateList getColorStateList(int id) throws NotFoundException {
+ final ColorStateList csl = getColorStateList(id, null);
+ if (csl != null && csl.canApplyTheme()) {
+ Log.w(TAG, "ColorStateList " + getResourceName(id) + " has "
+ + "unresolved theme attributes! Consider using "
+ + "Resources.getColorStateList(int, Theme) or "
+ + "Context.getColorStateList(int).", new RuntimeException());
+ }
+ return csl;
+ }
+
+ /**
+ * Returns a themed color state list associated with a particular resource
+ * ID. The resource may contain either a single raw color value or a
+ * complex {@link ColorStateList} holding multiple possible colors.
+ *
+ * @param id The desired resource identifier of a {@link ColorStateList},
+ * as generated by the aapt tool. This integer encodes the
+ * package, type, and resource entry. The value 0 is an invalid
+ * identifier.
+ * @param theme The theme used to style the color attributes, may be
+ * {@code null}.
+ *
+ * @throws NotFoundException Throws NotFoundException if the given ID does
+ * not exist.
+ *
+ * @return A themed ColorStateList object containing either a single solid
+ * color or multiple colors that can be selected based on a state.
+ */
+ @Nullable
+ public ColorStateList getColorStateList(int id, @Nullable Theme theme)
+ throws NotFoundException {
TypedValue value;
synchronized (mAccessLock) {
value = mTmpValue;
@@ -963,15 +1017,19 @@ public class Resources {
}
getValue(id, value, true);
}
- ColorStateList res = loadColorStateList(value, id);
+
+ final ColorStateList res = loadColorStateList(value, id, theme);
synchronized (mAccessLock) {
if (mTmpValue == null) {
mTmpValue = value;
}
}
+
return res;
}
+
+
/**
* Return a boolean associated with a particular resource ID. This can be
* used with any integral resource value, and will return true if it is
@@ -1843,11 +1901,10 @@ public class Resources {
clearDrawableCachesLocked(mDrawableCache, configChanges);
clearDrawableCachesLocked(mColorDrawableCache, configChanges);
+ mColorStateListCache.onConfigurationChange(configChanges);
mAnimatorCache.onConfigurationChange(configChanges);
mStateListAnimatorCache.onConfigurationChange(configChanges);
- mColorStateListCache.clear();
-
flushLayoutCache();
}
synchronized (sSync) {
@@ -2425,6 +2482,9 @@ public class Resources {
final String themeKey = theme == null ? "" : theme.mKey;
LongSparseArray<WeakReference<ConstantState>> themedCache = caches.get(themeKey);
if (themedCache == null) {
+ // Clean out the caches before we add more. This shouldn't
+ // happen very often.
+ pruneCaches(caches);
themedCache = new LongSparseArray<WeakReference<ConstantState>>(1);
caches.put(themeKey, themedCache);
}
@@ -2434,12 +2494,46 @@ public class Resources {
}
/**
+ * Prunes empty caches from the cache map.
+ *
+ * @param caches The map of caches to prune.
+ */
+ private void pruneCaches(ArrayMap<String,
+ LongSparseArray<WeakReference<ConstantState>>> caches) {
+ final int N = caches.size();
+ for (int i = N - 1; i >= 0; i--) {
+ final LongSparseArray<WeakReference<ConstantState>> cache = caches.valueAt(i);
+ if (pruneCache(cache)) {
+ caches.removeAt(i);
+ }
+ }
+ }
+
+ /**
+ * Prunes obsolete weak references from a cache, returning {@code true} if
+ * the cache is empty and should be removed.
+ *
+ * @param cache The cache of weak references to prune.
+ * @return {@code true} if the cache is empty and should be removed.
+ */
+ private boolean pruneCache(LongSparseArray<WeakReference<ConstantState>> cache) {
+ final int N = cache.size();
+ for (int i = N - 1; i >= 0; i--) {
+ final WeakReference entry = cache.valueAt(i);
+ if (entry == null || entry.get() == null) {
+ cache.removeAt(i);
+ }
+ }
+ return cache.size() == 0;
+ }
+
+ /**
* Loads a drawable from XML or resources stream.
*/
private Drawable loadDrawableForCookie(TypedValue value, int id, Theme theme) {
if (value.string == null) {
throw new NotFoundException("Resource \"" + getResourceName(id) + "\" ("
- + Integer.toHexString(id) + ") is not a Drawable (color or path): " + value);
+ + Integer.toHexString(id) + ") is not a Drawable (color or path): " + value);
}
final String file = value.string.toString();
@@ -2530,7 +2624,8 @@ public class Resources {
return null;
}
- /*package*/ ColorStateList loadColorStateList(TypedValue value, int id)
+ @Nullable
+ ColorStateList loadColorStateList(TypedValue value, int id, Theme theme)
throws NotFoundException {
if (TRACE_FOR_PRELOAD) {
// Log only framework resources
@@ -2544,101 +2639,107 @@ public class Resources {
ColorStateList csl;
- if (value.type >= TypedValue.TYPE_FIRST_COLOR_INT &&
- value.type <= TypedValue.TYPE_LAST_COLOR_INT) {
-
- csl = sPreloadedColorStateLists.get(key);
- if (csl != null) {
- return csl;
+ // Handle inline color definitions.
+ if (value.type >= TypedValue.TYPE_FIRST_COLOR_INT
+ && value.type <= TypedValue.TYPE_LAST_COLOR_INT) {
+ final ColorStateListFactory factory = sPreloadedColorStateLists.get(key);
+ if (factory != null) {
+ return factory.newInstance();
}
csl = ColorStateList.valueOf(value.data);
+
if (mPreloading) {
if (verifyPreloadConfig(value.changingConfigurations, 0, value.resourceId,
"color")) {
- sPreloadedColorStateLists.put(key, csl);
+ sPreloadedColorStateLists.put(key, csl.getFactory());
}
}
return csl;
}
- csl = getCachedColorStateList(key);
+ final ConfigurationBoundResourceCache<ColorStateList> cache = mColorStateListCache;
+
+ csl = cache.get(key, theme);
if (csl != null) {
return csl;
}
- csl = sPreloadedColorStateLists.get(key);
+ final ColorStateListFactory factory = sPreloadedColorStateLists.get(key);
+ if (factory != null) {
+ csl = factory.newInstance(this, theme);
+ }
+
+ if (csl == null) {
+ csl = loadColorStateListForCookie(value, id, theme);
+ }
+
if (csl != null) {
- return csl;
+ if (mPreloading) {
+ if (verifyPreloadConfig(value.changingConfigurations, 0, value.resourceId,
+ "color")) {
+ sPreloadedColorStateLists.put(key, csl.getFactory());
+ }
+ } else {
+ cache.put(key, theme, csl.getFactory());
+ }
}
+ return csl;
+ }
+
+ private ColorStateList loadColorStateListForCookie(TypedValue value, int id, Theme theme) {
if (value.string == null) {
- throw new NotFoundException(
- "Resource is not a ColorStateList (color or path): " + value);
+ throw new UnsupportedOperationException(
+ "Can't convert to color state list: type=0x" + value.type);
}
-
+
final String file = value.string.toString();
+ if (TRACE_FOR_MISS_PRELOAD) {
+ // Log only framework resources
+ if ((id >>> 24) == 0x1) {
+ final String name = getResourceName(id);
+ if (name != null) {
+ Log.d(TAG, "Loading framework color state list #" + Integer.toHexString(id)
+ + ": " + name + " at " + file);
+ }
+ }
+ }
+
+ if (DEBUG_LOAD) {
+ Log.v(TAG, "Loading color state list for cookie " + value.assetCookie + ": " + file);
+ }
+
+ final ColorStateList csl;
+
+ Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, file);
if (file.endsWith(".xml")) {
- Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, file);
try {
final XmlResourceParser rp = loadXmlResourceParser(
- file, id, value.assetCookie, "colorstatelist");
- csl = ColorStateList.createFromXml(this, rp);
+ file, id, value.assetCookie, "colorstatelist");
+ csl = ColorStateList.createFromXml(this, rp, theme);
rp.close();
} catch (Exception e) {
Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
- NotFoundException rnf = new NotFoundException(
- "File " + file + " from color state list resource ID #0x"
- + Integer.toHexString(id));
+ final NotFoundException rnf = new NotFoundException(
+ "File " + file + " from color state list resource ID #0x"
+ + Integer.toHexString(id));
rnf.initCause(e);
throw rnf;
}
- Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
} else {
+ Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
throw new NotFoundException(
"File " + file + " from drawable resource ID #0x"
- + Integer.toHexString(id) + ": .xml extension required");
- }
-
- if (csl != null) {
- if (mPreloading) {
- if (verifyPreloadConfig(value.changingConfigurations, 0, value.resourceId,
- "color")) {
- sPreloadedColorStateLists.put(key, csl);
- }
- } else {
- synchronized (mAccessLock) {
- //Log.i(TAG, "Saving cached color state list @ #" +
- // Integer.toHexString(key.intValue())
- // + " in " + this + ": " + csl);
- mColorStateListCache.put(key, new WeakReference<ColorStateList>(csl));
- }
- }
+ + Integer.toHexString(id) + ": .xml extension required");
}
+ Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
return csl;
}
- private ColorStateList getCachedColorStateList(long key) {
- synchronized (mAccessLock) {
- WeakReference<ColorStateList> wr = mColorStateListCache.get(key);
- if (wr != null) { // we have the key
- ColorStateList entry = wr.get();
- if (entry != null) {
- //Log.i(TAG, "Returning cached color state list @ #" +
- // Integer.toHexString(((Integer)key).intValue())
- // + " in " + this + ": " + entry);
- return entry;
- } else { // our entry has been purged
- mColorStateListCache.delete(key);
- }
- }
- }
- return null;
- }
-
/*package*/ XmlResourceParser loadXmlResourceParser(int id, String type)
throws NotFoundException {
synchronized (mAccessLock) {
@@ -2715,6 +2816,20 @@ public class Resources {
}
}
+ /**
+ * Obtains styled attributes from the theme, if available, or unstyled
+ * resources if the theme is null.
+ *
+ * @hide
+ */
+ public static TypedArray obtainAttributes(
+ Resources res, Theme theme, AttributeSet set, int[] attrs) {
+ if (theme == null) {
+ return res.obtainAttributes(set, attrs);
+ }
+ return theme.obtainStyledAttributes(set, attrs, 0, 0);
+ }
+
private Resources() {
mAssets = AssetManager.getSystem();
// NOTE: Intentionally leaving this uninitialized (all values set
diff --git a/core/java/android/content/res/ResourcesKey.java b/core/java/android/content/res/ResourcesKey.java
index 4ae3825..9548d49 100644
--- a/core/java/android/content/res/ResourcesKey.java
+++ b/core/java/android/content/res/ResourcesKey.java
@@ -16,27 +16,23 @@
package android.content.res;
-import android.os.IBinder;
+import java.util.Objects;
/** @hide */
public final class ResourcesKey {
- final String mResDir;
- final float mScale;
+ private final String mResDir;
+ private final float mScale;
private final int mHash;
- private final IBinder mToken;
public final int mDisplayId;
- public final Configuration mOverrideConfiguration = new Configuration();
+ public final Configuration mOverrideConfiguration;
public ResourcesKey(String resDir, int displayId, Configuration overrideConfiguration,
- float scale, IBinder token) {
+ float scale) {
mResDir = resDir;
mDisplayId = displayId;
- if (overrideConfiguration != null) {
- mOverrideConfiguration.setTo(overrideConfiguration);
- }
+ mOverrideConfiguration = overrideConfiguration;
mScale = scale;
- mToken = token;
int hash = 17;
hash = 31 * hash + (mResDir == null ? 0 : mResDir.hashCode());
@@ -48,7 +44,8 @@ public final class ResourcesKey {
}
public boolean hasOverrideConfiguration() {
- return !Configuration.EMPTY.equals(mOverrideConfiguration);
+ return mOverrideConfiguration != null
+ && !Configuration.EMPTY.equals(mOverrideConfiguration);
}
@Override
@@ -63,17 +60,9 @@ public final class ResourcesKey {
}
ResourcesKey peer = (ResourcesKey) obj;
- if ((mResDir == null) && (peer.mResDir != null)) {
- return false;
- }
- if ((mResDir != null) && (peer.mResDir == null)) {
+ if (!Objects.equals(mResDir, peer.mResDir)) {
return false;
}
- if ((mResDir != null) && (peer.mResDir != null)) {
- if (!mResDir.equals(peer.mResDir)) {
- return false;
- }
- }
if (mDisplayId != peer.mDisplayId) {
return false;
}
diff --git a/core/java/android/content/res/TypedArray.java b/core/java/android/content/res/TypedArray.java
index 02602fb..f15b6b9 100644
--- a/core/java/android/content/res/TypedArray.java
+++ b/core/java/android/content/res/TypedArray.java
@@ -18,6 +18,7 @@ package android.content.res;
import android.annotation.Nullable;
import android.graphics.drawable.Drawable;
+import android.os.StrictMode;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.Log;
@@ -73,7 +74,9 @@ public class TypedArray {
/*package*/ TypedValue mValue = new TypedValue();
/**
- * Return the number of values in this array.
+ * Returns the number of values in this array.
+ *
+ * @throws RuntimeException if the TypedArray has already been recycled.
*/
public int length() {
if (mRecycled) {
@@ -85,6 +88,8 @@ public class TypedArray {
/**
* Return the number of indices in the array that actually have data.
+ *
+ * @throws RuntimeException if the TypedArray has already been recycled.
*/
public int getIndexCount() {
if (mRecycled) {
@@ -95,13 +100,14 @@ public class TypedArray {
}
/**
- * Return an index in the array that has data.
+ * Returns an index in the array that has data.
*
* @param at The index you would like to returned, ranging from 0 to
- * {@link #getIndexCount()}.
+ * {@link #getIndexCount()}.
*
* @return The index at the given offset, which can be used with
- * {@link #getValue} and related APIs.
+ * {@link #getValue} and related APIs.
+ * @throws RuntimeException if the TypedArray has already been recycled.
*/
public int getIndex(int at) {
if (mRecycled) {
@@ -112,7 +118,9 @@ public class TypedArray {
}
/**
- * Return the Resources object this array was loaded from.
+ * Returns the Resources object this array was loaded from.
+ *
+ * @throws RuntimeException if the TypedArray has already been recycled.
*/
public Resources getResources() {
if (mRecycled) {
@@ -123,12 +131,17 @@ public class TypedArray {
}
/**
- * Retrieve the styled string value for the attribute at <var>index</var>.
+ * Retrieves the styled string value for the attribute at <var>index</var>.
+ * <p>
+ * If the attribute is not a string, this method will attempt to coerce
+ * it to a string.
*
* @param index Index of attribute to retrieve.
*
- * @return CharSequence holding string data. May be styled. Returns
- * null if the attribute is not defined.
+ * @return CharSequence holding string data. May be styled. Returns
+ * {@code null} if the attribute is not defined or could not be
+ * coerced to a string.
+ * @throws RuntimeException if the TypedArray has already been recycled.
*/
public CharSequence getText(int index) {
if (mRecycled) {
@@ -144,23 +157,28 @@ public class TypedArray {
return loadStringValueAt(index);
}
- TypedValue v = mValue;
+ final TypedValue v = mValue;
if (getValueAt(index, v)) {
- Log.w(Resources.TAG, "Converting to string: " + v);
+ StrictMode.noteResourceMismatch(v);
return v.coerceToString();
}
- Log.w(Resources.TAG, "getString of bad type: 0x"
- + Integer.toHexString(type));
- return null;
+
+ // We already checked for TYPE_NULL. This should never happen.
+ throw new RuntimeException("getText of bad type: 0x" + Integer.toHexString(type));
}
/**
- * Retrieve the string value for the attribute at <var>index</var>.
+ * Retrieves the string value for the attribute at <var>index</var>.
+ * <p>
+ * If the attribute is not a string, this method will attempt to coerce
+ * it to a string.
*
* @param index Index of attribute to retrieve.
*
- * @return String holding string data. Any styling information is
- * removed. Returns null if the attribute is not defined.
+ * @return String holding string data. Any styling information is removed.
+ * Returns {@code null} if the attribute is not defined or could
+ * not be coerced to a string.
+ * @throws RuntimeException if the TypedArray has already been recycled.
*/
public String getString(int index) {
if (mRecycled) {
@@ -176,19 +194,19 @@ public class TypedArray {
return loadStringValueAt(index).toString();
}
- TypedValue v = mValue;
+ final TypedValue v = mValue;
if (getValueAt(index, v)) {
- Log.w(Resources.TAG, "Converting to string: " + v);
- CharSequence cs = v.coerceToString();
+ StrictMode.noteResourceMismatch(v);
+ final CharSequence cs = v.coerceToString();
return cs != null ? cs.toString() : null;
}
- Log.w(Resources.TAG, "getString of bad type: 0x"
- + Integer.toHexString(type));
- return null;
+
+ // We already checked for TYPE_NULL. This should never happen.
+ throw new RuntimeException("getString of bad type: 0x" + Integer.toHexString(type));
}
/**
- * Retrieve the string value for the attribute at <var>index</var>, but
+ * Retrieves the string value for the attribute at <var>index</var>, but
* only if that string comes from an immediate value in an XML file. That
* is, this does not allow references to string resources, string
* attributes, or conversions from other types. As such, this method
@@ -197,9 +215,10 @@ public class TypedArray {
*
* @param index Index of attribute to retrieve.
*
- * @return String holding string data. Any styling information is
- * removed. Returns null if the attribute is not defined or is not
- * an immediate string value.
+ * @return String holding string data. Any styling information is removed.
+ * Returns {@code null} if the attribute is not defined or is not
+ * an immediate string value.
+ * @throws RuntimeException if the TypedArray has already been recycled.
*/
public String getNonResourceString(int index) {
if (mRecycled) {
@@ -220,16 +239,17 @@ public class TypedArray {
}
/**
- * @hide
- * Retrieve the string value for the attribute at <var>index</var> that is
+ * Retrieves the string value for the attribute at <var>index</var> that is
* not allowed to change with the given configurations.
*
* @param index Index of attribute to retrieve.
* @param allowedChangingConfigs Bit mask of configurations from
- * {@link Configuration}.NATIVE_CONFIG_* that are allowed to change.
+ * {@link Configuration}.NATIVE_CONFIG_* that are allowed to change.
*
- * @return String holding string data. Any styling information is
- * removed. Returns null if the attribute is not defined.
+ * @return String holding string data. Any styling information is removed.
+ * Returns {@code null} if the attribute is not defined.
+ * @throws RuntimeException if the TypedArray has already been recycled.
+ * @hide
*/
public String getNonConfigurationString(int index, int allowedChangingConfigs) {
if (mRecycled) {
@@ -248,24 +268,33 @@ public class TypedArray {
return loadStringValueAt(index).toString();
}
- TypedValue v = mValue;
+ final TypedValue v = mValue;
if (getValueAt(index, v)) {
- Log.w(Resources.TAG, "Converting to string: " + v);
- CharSequence cs = v.coerceToString();
+ StrictMode.noteResourceMismatch(v);
+ final CharSequence cs = v.coerceToString();
return cs != null ? cs.toString() : null;
}
- Log.w(Resources.TAG, "getString of bad type: 0x"
- + Integer.toHexString(type));
- return null;
+
+ // We already checked for TYPE_NULL. This should never happen.
+ throw new RuntimeException("getNonConfigurationString of bad type: 0x"
+ + Integer.toHexString(type));
}
/**
* Retrieve the boolean value for the attribute at <var>index</var>.
+ * <p>
+ * If the attribute is an integer value, this method will return whether
+ * it is equal to zero. If the attribute is not a boolean or integer value,
+ * this method will attempt to coerce it to an integer using
+ * {@link Integer#decode(String)} and return whether it is equal to zero.
*
* @param index Index of attribute to retrieve.
- * @param defValue Value to return if the attribute is not defined.
+ * @param defValue Value to return if the attribute is not defined or
+ * cannot be coerced to an integer.
*
- * @return Attribute boolean value, or defValue if not defined.
+ * @return Boolean value of the attribute, or defValue if the attribute was
+ * not defined or could not be coerced to an integer.
+ * @throws RuntimeException if the TypedArray has already been recycled.
*/
public boolean getBoolean(int index, boolean defValue) {
if (mRecycled) {
@@ -278,28 +307,33 @@ public class TypedArray {
if (type == TypedValue.TYPE_NULL) {
return defValue;
} else if (type >= TypedValue.TYPE_FIRST_INT
- && type <= TypedValue.TYPE_LAST_INT) {
+ && type <= TypedValue.TYPE_LAST_INT) {
return data[index+AssetManager.STYLE_DATA] != 0;
}
- TypedValue v = mValue;
+ final TypedValue v = mValue;
if (getValueAt(index, v)) {
- Log.w(Resources.TAG, "Converting to boolean: " + v);
- return XmlUtils.convertValueToBoolean(
- v.coerceToString(), defValue);
+ StrictMode.noteResourceMismatch(v);
+ return XmlUtils.convertValueToBoolean(v.coerceToString(), defValue);
}
- Log.w(Resources.TAG, "getBoolean of bad type: 0x"
- + Integer.toHexString(type));
- return defValue;
+
+ // We already checked for TYPE_NULL. This should never happen.
+ throw new RuntimeException("getBoolean of bad type: 0x" + Integer.toHexString(type));
}
/**
* Retrieve the integer value for the attribute at <var>index</var>.
+ * <p>
+ * If the attribute is not an integer, this method will attempt to coerce
+ * it to an integer using {@link Integer#decode(String)}.
*
* @param index Index of attribute to retrieve.
- * @param defValue Value to return if the attribute is not defined.
+ * @param defValue Value to return if the attribute is not defined or
+ * cannot be coerced to an integer.
*
- * @return Attribute int value, or defValue if not defined.
+ * @return Integer value of the attribute, or defValue if the attribute was
+ * not defined or could not be coerced to an integer.
+ * @throws RuntimeException if the TypedArray has already been recycled.
*/
public int getInt(int index, int defValue) {
if (mRecycled) {
@@ -312,27 +346,31 @@ public class TypedArray {
if (type == TypedValue.TYPE_NULL) {
return defValue;
} else if (type >= TypedValue.TYPE_FIRST_INT
- && type <= TypedValue.TYPE_LAST_INT) {
+ && type <= TypedValue.TYPE_LAST_INT) {
return data[index+AssetManager.STYLE_DATA];
}
- TypedValue v = mValue;
+ final TypedValue v = mValue;
if (getValueAt(index, v)) {
- Log.w(Resources.TAG, "Converting to int: " + v);
- return XmlUtils.convertValueToInt(
- v.coerceToString(), defValue);
+ StrictMode.noteResourceMismatch(v);
+ return XmlUtils.convertValueToInt(v.coerceToString(), defValue);
}
- Log.w(Resources.TAG, "getInt of bad type: 0x"
- + Integer.toHexString(type));
- return defValue;
+
+ // We already checked for TYPE_NULL. This should never happen.
+ throw new RuntimeException("getInt of bad type: 0x" + Integer.toHexString(type));
}
/**
* Retrieve the float value for the attribute at <var>index</var>.
+ * <p>
+ * If the attribute is not a float or an integer, this method will attempt
+ * to coerce it to a float using {@link Float#parseFloat(String)}.
*
* @param index Index of attribute to retrieve.
*
- * @return Attribute float value, or defValue if not defined..
+ * @return Attribute float value, or defValue if the attribute was
+ * not defined or could not be coerced to a float.
+ * @throws RuntimeException if the TypedArray has already been recycled.
*/
public float getFloat(int index, float defValue) {
if (mRecycled) {
@@ -347,21 +385,21 @@ public class TypedArray {
} else if (type == TypedValue.TYPE_FLOAT) {
return Float.intBitsToFloat(data[index+AssetManager.STYLE_DATA]);
} else if (type >= TypedValue.TYPE_FIRST_INT
- && type <= TypedValue.TYPE_LAST_INT) {
+ && type <= TypedValue.TYPE_LAST_INT) {
return data[index+AssetManager.STYLE_DATA];
}
- TypedValue v = mValue;
+ final TypedValue v = mValue;
if (getValueAt(index, v)) {
- Log.w(Resources.TAG, "Converting to float: " + v);
- CharSequence str = v.coerceToString();
+ final CharSequence str = v.coerceToString();
if (str != null) {
+ StrictMode.noteResourceMismatch(v);
return Float.parseFloat(str.toString());
}
}
- Log.w(Resources.TAG, "getFloat of bad type: 0x"
- + Integer.toHexString(type));
- return defValue;
+
+ // We already checked for TYPE_NULL. This should never happen.
+ throw new RuntimeException("getFloat of bad type: 0x" + Integer.toHexString(type));
}
/**
@@ -369,12 +407,18 @@ public class TypedArray {
* the attribute references a color resource holding a complex
* {@link android.content.res.ColorStateList}, then the default color from
* the set is returned.
+ * <p>
+ * This method will throw an exception if the attribute is defined but is
+ * not an integer color or color state list.
*
* @param index Index of attribute to retrieve.
* @param defValue Value to return if the attribute is not defined or
* not a resource.
*
* @return Attribute color value, or defValue if not defined.
+ * @throws RuntimeException if the TypedArray has already been recycled.
+ * @throws UnsupportedOperationException if the attribute is defined but is
+ * not an integer color or color state list.
*/
public int getColor(int index, int defValue) {
if (mRecycled) {
@@ -387,18 +431,19 @@ public class TypedArray {
if (type == TypedValue.TYPE_NULL) {
return defValue;
} else if (type >= TypedValue.TYPE_FIRST_INT
- && type <= TypedValue.TYPE_LAST_INT) {
+ && type <= TypedValue.TYPE_LAST_INT) {
return data[index+AssetManager.STYLE_DATA];
} else if (type == TypedValue.TYPE_STRING) {
final TypedValue value = mValue;
if (getValueAt(index, value)) {
- ColorStateList csl = mResources.loadColorStateList(
- value, value.resourceId);
+ final ColorStateList csl = mResources.loadColorStateList(
+ value, value.resourceId, mTheme);
return csl.getDefaultColor();
}
return defValue;
} else if (type == TypedValue.TYPE_ATTRIBUTE) {
- throw new RuntimeException("Failed to resolve attribute at index " + index);
+ throw new UnsupportedOperationException(
+ "Failed to resolve attribute at index " + index);
}
throw new UnsupportedOperationException("Can't convert to color: type=0x"
@@ -408,12 +453,22 @@ public class TypedArray {
/**
* Retrieve the ColorStateList for the attribute at <var>index</var>.
* The value may be either a single solid color or a reference to
- * a color or complex {@link android.content.res.ColorStateList} description.
+ * a color or complex {@link android.content.res.ColorStateList}
+ * description.
+ * <p>
+ * This method will return {@code null} if the attribute is not defined or
+ * is not an integer color or color state list.
*
* @param index Index of attribute to retrieve.
*
- * @return ColorStateList for the attribute, or null if not defined.
+ * @return ColorStateList for the attribute, or {@code null} if not
+ * defined.
+ * @throws RuntimeException if the attribute if the TypedArray has already
+ * been recycled.
+ * @throws UnsupportedOperationException if the attribute is defined but is
+ * not an integer color or color state list.
*/
+ @Nullable
public ColorStateList getColorStateList(int index) {
if (mRecycled) {
throw new RuntimeException("Cannot make calls to a recycled instance!");
@@ -422,21 +477,28 @@ public class TypedArray {
final TypedValue value = mValue;
if (getValueAt(index*AssetManager.STYLE_NUM_ENTRIES, value)) {
if (value.type == TypedValue.TYPE_ATTRIBUTE) {
- throw new RuntimeException("Failed to resolve attribute at index " + index);
+ throw new UnsupportedOperationException(
+ "Failed to resolve attribute at index " + index);
}
- return mResources.loadColorStateList(value, value.resourceId);
+ return mResources.loadColorStateList(value, value.resourceId, mTheme);
}
return null;
}
/**
* Retrieve the integer value for the attribute at <var>index</var>.
+ * <p>
+ * Unlike {@link #getInt(int, int)}, this method will throw an exception if
+ * the attribute is defined but is not an integer.
*
* @param index Index of attribute to retrieve.
* @param defValue Value to return if the attribute is not defined or
* not a resource.
*
* @return Attribute integer value, or defValue if not defined.
+ * @throws RuntimeException if the TypedArray has already been recycled.
+ * @throws UnsupportedOperationException if the attribute is defined but is
+ * not an integer.
*/
public int getInteger(int index, int defValue) {
if (mRecycled) {
@@ -449,10 +511,11 @@ public class TypedArray {
if (type == TypedValue.TYPE_NULL) {
return defValue;
} else if (type >= TypedValue.TYPE_FIRST_INT
- && type <= TypedValue.TYPE_LAST_INT) {
+ && type <= TypedValue.TYPE_LAST_INT) {
return data[index+AssetManager.STYLE_DATA];
} else if (type == TypedValue.TYPE_ATTRIBUTE) {
- throw new RuntimeException("Failed to resolve attribute at index " + index);
+ throw new UnsupportedOperationException(
+ "Failed to resolve attribute at index " + index);
}
throw new UnsupportedOperationException("Can't convert to integer: type=0x"
@@ -460,17 +523,23 @@ public class TypedArray {
}
/**
- * Retrieve a dimensional unit attribute at <var>index</var>. Unit
+ * Retrieve a dimensional unit attribute at <var>index</var>. Unit
* conversions are based on the current {@link DisplayMetrics}
* associated with the resources this {@link TypedArray} object
* came from.
+ * <p>
+ * This method will throw an exception if the attribute is defined but is
+ * not a dimension.
*
* @param index Index of attribute to retrieve.
* @param defValue Value to return if the attribute is not defined or
* not a resource.
*
* @return Attribute dimension value multiplied by the appropriate
- * metric, or defValue if not defined.
+ * metric, or defValue if not defined.
+ * @throws RuntimeException if the TypedArray has already been recycled.
+ * @throws UnsupportedOperationException if the attribute is defined but is
+ * not an integer.
*
* @see #getDimensionPixelOffset
* @see #getDimensionPixelSize
@@ -487,9 +556,10 @@ public class TypedArray {
return defValue;
} else if (type == TypedValue.TYPE_DIMENSION) {
return TypedValue.complexToDimension(
- data[index+AssetManager.STYLE_DATA], mMetrics);
+ data[index + AssetManager.STYLE_DATA], mMetrics);
} else if (type == TypedValue.TYPE_ATTRIBUTE) {
- throw new RuntimeException("Failed to resolve attribute at index " + index);
+ throw new UnsupportedOperationException(
+ "Failed to resolve attribute at index " + index);
}
throw new UnsupportedOperationException("Can't convert to dimension: type=0x"
@@ -502,13 +572,19 @@ public class TypedArray {
* {@link #getDimension}, except the returned value is converted to
* integer pixels for you. An offset conversion involves simply
* truncating the base value to an integer.
+ * <p>
+ * This method will throw an exception if the attribute is defined but is
+ * not a dimension.
*
* @param index Index of attribute to retrieve.
* @param defValue Value to return if the attribute is not defined or
* not a resource.
*
* @return Attribute dimension value multiplied by the appropriate
- * metric and truncated to integer pixels, or defValue if not defined.
+ * metric and truncated to integer pixels, or defValue if not defined.
+ * @throws RuntimeException if the TypedArray has already been recycled.
+ * @throws UnsupportedOperationException if the attribute is defined but is
+ * not an integer.
*
* @see #getDimension
* @see #getDimensionPixelSize
@@ -525,9 +601,10 @@ public class TypedArray {
return defValue;
} else if (type == TypedValue.TYPE_DIMENSION) {
return TypedValue.complexToDimensionPixelOffset(
- data[index+AssetManager.STYLE_DATA], mMetrics);
+ data[index + AssetManager.STYLE_DATA], mMetrics);
} else if (type == TypedValue.TYPE_ATTRIBUTE) {
- throw new RuntimeException("Failed to resolve attribute at index " + index);
+ throw new UnsupportedOperationException(
+ "Failed to resolve attribute at index " + index);
}
throw new UnsupportedOperationException("Can't convert to dimension: type=0x"
@@ -541,13 +618,19 @@ public class TypedArray {
* integer pixels for use as a size. A size conversion involves
* rounding the base value, and ensuring that a non-zero base value
* is at least one pixel in size.
+ * <p>
+ * This method will throw an exception if the attribute is defined but is
+ * not a dimension.
*
* @param index Index of attribute to retrieve.
* @param defValue Value to return if the attribute is not defined or
* not a resource.
*
* @return Attribute dimension value multiplied by the appropriate
- * metric and truncated to integer pixels, or defValue if not defined.
+ * metric and truncated to integer pixels, or defValue if not defined.
+ * @throws RuntimeException if the TypedArray has already been recycled.
+ * @throws UnsupportedOperationException if the attribute is defined but is
+ * not a dimension.
*
* @see #getDimension
* @see #getDimensionPixelOffset
@@ -566,7 +649,8 @@ public class TypedArray {
return TypedValue.complexToDimensionPixelSize(
data[index+AssetManager.STYLE_DATA], mMetrics);
} else if (type == TypedValue.TYPE_ATTRIBUTE) {
- throw new RuntimeException("Failed to resolve attribute at index " + index);
+ throw new UnsupportedOperationException(
+ "Failed to resolve attribute at index " + index);
}
throw new UnsupportedOperationException("Can't convert to dimension: type=0x"
@@ -578,12 +662,18 @@ public class TypedArray {
* {@link android.view.ViewGroup}'s layout_width and layout_height
* attributes. This is only here for performance reasons; applications
* should use {@link #getDimensionPixelSize}.
+ * <p>
+ * This method will throw an exception if the attribute is defined but is
+ * not a dimension or integer (enum).
*
* @param index Index of the attribute to retrieve.
* @param name Textual name of attribute for error reporting.
*
* @return Attribute dimension value multiplied by the appropriate
- * metric and truncated to integer pixels.
+ * metric and truncated to integer pixels.
+ * @throws RuntimeException if the TypedArray has already been recycled.
+ * @throws UnsupportedOperationException if the attribute is defined but is
+ * not a dimension or integer (enum).
*/
public int getLayoutDimension(int index, String name) {
if (mRecycled) {
@@ -600,10 +690,11 @@ public class TypedArray {
return TypedValue.complexToDimensionPixelSize(
data[index+AssetManager.STYLE_DATA], mMetrics);
} else if (type == TypedValue.TYPE_ATTRIBUTE) {
- throw new RuntimeException("Failed to resolve attribute at index " + index);
+ throw new UnsupportedOperationException(
+ "Failed to resolve attribute at index " + index);
}
- throw new RuntimeException(getPositionDescription()
+ throw new UnsupportedOperationException(getPositionDescription()
+ ": You must supply a " + name + " attribute.");
}
@@ -615,10 +706,11 @@ public class TypedArray {
*
* @param index Index of the attribute to retrieve.
* @param defValue The default value to return if this attribute is not
- * default or contains the wrong type of data.
+ * default or contains the wrong type of data.
*
* @return Attribute dimension value multiplied by the appropriate
- * metric and truncated to integer pixels.
+ * metric and truncated to integer pixels.
+ * @throws RuntimeException if the TypedArray has already been recycled.
*/
public int getLayoutDimension(int index, int defValue) {
if (mRecycled) {
@@ -633,14 +725,14 @@ public class TypedArray {
return data[index+AssetManager.STYLE_DATA];
} else if (type == TypedValue.TYPE_DIMENSION) {
return TypedValue.complexToDimensionPixelSize(
- data[index+AssetManager.STYLE_DATA], mMetrics);
+ data[index + AssetManager.STYLE_DATA], mMetrics);
}
return defValue;
}
/**
- * Retrieve a fractional unit attribute at <var>index</var>.
+ * Retrieves a fractional unit attribute at <var>index</var>.
*
* @param index Index of attribute to retrieve.
* @param base The base value of this fraction. In other words, a
@@ -652,7 +744,10 @@ public class TypedArray {
* not a resource.
*
* @return Attribute fractional value multiplied by the appropriate
- * base value, or defValue if not defined.
+ * base value, or defValue if not defined.
+ * @throws RuntimeException if the TypedArray has already been recycled.
+ * @throws UnsupportedOperationException if the attribute is defined but is
+ * not a fraction.
*/
public float getFraction(int index, int base, int pbase, float defValue) {
if (mRecycled) {
@@ -668,7 +763,8 @@ public class TypedArray {
return TypedValue.complexToFraction(
data[index+AssetManager.STYLE_DATA], base, pbase);
} else if (type == TypedValue.TYPE_ATTRIBUTE) {
- throw new RuntimeException("Failed to resolve attribute at index " + index);
+ throw new UnsupportedOperationException(
+ "Failed to resolve attribute at index " + index);
}
throw new UnsupportedOperationException("Can't convert to fraction: type=0x"
@@ -676,7 +772,7 @@ public class TypedArray {
}
/**
- * Retrieve the resource identifier for the attribute at
+ * Retrieves the resource identifier for the attribute at
* <var>index</var>. Note that attribute resource as resolved when
* the overall {@link TypedArray} object is retrieved. As a
* result, this function will return the resource identifier of the
@@ -688,6 +784,7 @@ public class TypedArray {
* not a resource.
*
* @return Attribute resource identifier, or defValue if not defined.
+ * @throws RuntimeException if the TypedArray has already been recycled.
*/
public int getResourceId(int index, int defValue) {
if (mRecycled) {
@@ -706,13 +803,15 @@ public class TypedArray {
}
/**
- * Retrieve the theme attribute resource identifier for the attribute at
+ * Retrieves the theme attribute resource identifier for the attribute at
* <var>index</var>.
*
* @param index Index of attribute to retrieve.
* @param defValue Value to return if the attribute is not defined or not a
- * resource.
+ * resource.
+ *
* @return Theme attribute resource identifier, or defValue if not defined.
+ * @throws RuntimeException if the TypedArray has already been recycled.
* @hide
*/
public int getThemeAttributeId(int index, int defValue) {
@@ -730,10 +829,16 @@ public class TypedArray {
/**
* Retrieve the Drawable for the attribute at <var>index</var>.
+ * <p>
+ * This method will throw an exception if the attribute is defined but is
+ * not a color or drawable resource.
*
* @param index Index of attribute to retrieve.
*
- * @return Drawable for the attribute, or null if not defined.
+ * @return Drawable for the attribute, or {@code null} if not defined.
+ * @throws RuntimeException if the TypedArray has already been recycled.
+ * @throws UnsupportedOperationException if the attribute is defined but is
+ * not a color or drawable resource.
*/
@Nullable
public Drawable getDrawable(int index) {
@@ -744,7 +849,8 @@ public class TypedArray {
final TypedValue value = mValue;
if (getValueAt(index*AssetManager.STYLE_NUM_ENTRIES, value)) {
if (value.type == TypedValue.TYPE_ATTRIBUTE) {
- throw new RuntimeException("Failed to resolve attribute at index " + index);
+ throw new UnsupportedOperationException(
+ "Failed to resolve attribute at index " + index);
}
return mResources.loadDrawable(value, value.resourceId, mTheme);
}
@@ -756,10 +862,15 @@ public class TypedArray {
* This gets the resource ID of the selected attribute, and uses
* {@link Resources#getTextArray Resources.getTextArray} of the owning
* Resources object to retrieve its String[].
+ * <p>
+ * This method will throw an exception if the attribute is defined but is
+ * not a text array resource.
*
* @param index Index of attribute to retrieve.
*
- * @return CharSequence[] for the attribute, or null if not defined.
+ * @return CharSequence[] for the attribute, or {@code null} if not
+ * defined.
+ * @throws RuntimeException if the TypedArray has already been recycled.
*/
public CharSequence[] getTextArray(int index) {
if (mRecycled) {
@@ -780,7 +891,8 @@ public class TypedArray {
* @param outValue TypedValue object in which to place the attribute's
* data.
*
- * @return Returns true if the value was retrieved, else false.
+ * @return {@code true} if the value was retrieved, false otherwise.
+ * @throws RuntimeException if the TypedArray has already been recycled.
*/
public boolean getValue(int index, TypedValue outValue) {
if (mRecycled) {
@@ -794,7 +906,9 @@ public class TypedArray {
* Returns the type of attribute at the specified index.
*
* @param index Index of attribute whose type to retrieve.
+ *
* @return Attribute type.
+ * @throws RuntimeException if the TypedArray has already been recycled.
*/
public int getType(int index) {
if (mRecycled) {
@@ -814,6 +928,7 @@ public class TypedArray {
* @param index Index of attribute to retrieve.
*
* @return True if the attribute has a value, false otherwise.
+ * @throws RuntimeException if the TypedArray has already been recycled.
*/
public boolean hasValue(int index) {
if (mRecycled) {
@@ -834,6 +949,7 @@ public class TypedArray {
* @param index Index of attribute to retrieve.
*
* @return True if the attribute has a value or is empty, false otherwise.
+ * @throws RuntimeException if the TypedArray has already been recycled.
*/
public boolean hasValueOrEmpty(int index) {
if (mRecycled) {
@@ -857,6 +973,7 @@ public class TypedArray {
* @return Returns a TypedValue object if the attribute is defined,
* containing its data; otherwise returns null. (You will not
* receive a TypedValue whose type is TYPE_NULL.)
+ * @throws RuntimeException if the TypedArray has already been recycled.
*/
public TypedValue peekValue(int index) {
if (mRecycled) {
@@ -872,6 +989,9 @@ public class TypedArray {
/**
* Returns a message about the parser state suitable for printing error messages.
+ *
+ * @return Human-readable description of current parser state.
+ * @throws RuntimeException if the TypedArray has already been recycled.
*/
public String getPositionDescription() {
if (mRecycled) {
@@ -882,8 +1002,10 @@ public class TypedArray {
}
/**
- * Recycle the TypedArray, to be re-used by a later caller. After calling
+ * Recycles the TypedArray, to be re-used by a later caller. After calling
* this function you must not ever touch the typed array again.
+ *
+ * @throws RuntimeException if the TypedArray has already been recycled.
*/
public void recycle() {
if (mRecycled) {
@@ -908,9 +1030,19 @@ public class TypedArray {
* @return an array of length {@link #getIndexCount()} populated with theme
* attributes, or null if there are no theme attributes in the typed
* array
+ * @throws RuntimeException if the TypedArray has already been recycled.
* @hide
*/
+ @Nullable
public int[] extractThemeAttrs() {
+ return extractThemeAttrs(null);
+ }
+
+ /**
+ * @hide
+ */
+ @Nullable
+ public int[] extractThemeAttrs(@Nullable int[] scrap) {
if (mRecycled) {
throw new RuntimeException("Cannot make calls to a recycled instance!");
}
@@ -922,6 +1054,7 @@ public class TypedArray {
for (int i = 0; i < N; i++) {
final int index = i * AssetManager.STYLE_NUM_ENTRIES;
if (data[index + AssetManager.STYLE_TYPE] != TypedValue.TYPE_ATTRIBUTE) {
+ // Not an attribute, ignore.
continue;
}
@@ -930,13 +1063,20 @@ public class TypedArray {
final int attr = data[index + AssetManager.STYLE_DATA];
if (attr == 0) {
- // This attribute is useless!
+ // Useless data, ignore.
continue;
}
+ // Ensure we have a usable attribute array.
if (attrs == null) {
- attrs = new int[N];
+ if (scrap != null && scrap.length == N) {
+ attrs = scrap;
+ Arrays.fill(attrs, 0);
+ } else {
+ attrs = new int[N];
+ }
}
+
attrs[i] = attr;
}
@@ -949,9 +1089,14 @@ public class TypedArray {
*
* @return Returns a mask of the changing configuration parameters, as
* defined by {@link android.content.pm.ActivityInfo}.
+ * @throws RuntimeException if the TypedArray has already been recycled.
* @see android.content.pm.ActivityInfo
*/
public int getChangingConfigurations() {
+ if (mRecycled) {
+ throw new RuntimeException("Cannot make calls to a recycled instance!");
+ }
+
int changingConfig = 0;
final int[] data = mData;
diff --git a/core/java/android/hardware/camera2/CameraAccessException.java b/core/java/android/hardware/camera2/CameraAccessException.java
index 91ef6ca..84e9912 100644
--- a/core/java/android/hardware/camera2/CameraAccessException.java
+++ b/core/java/android/hardware/camera2/CameraAccessException.java
@@ -28,16 +28,14 @@ import android.util.AndroidException;
*/
public class CameraAccessException extends AndroidException {
/**
- * The camera device is in use already
- * @hide
+ * The camera device is in use already.
*/
public static final int CAMERA_IN_USE = 4;
/**
- * The system-wide limit for number of open cameras has been reached,
- * and more camera devices cannot be opened until previous instances are
- * closed.
- * @hide
+ * The system-wide limit for number of open cameras or camera resources has
+ * been reached, and more camera devices cannot be opened or torch mode
+ * cannot be turned on until previous instances are closed.
*/
public static final int MAX_CAMERAS_IN_USE = 5;
diff --git a/core/java/android/hardware/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java
index 7cf8fb0..60ff32c 100644
--- a/core/java/android/hardware/camera2/CameraCharacteristics.java
+++ b/core/java/android/hardware/camera2/CameraCharacteristics.java
@@ -424,6 +424,17 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri
new Key<Rational>("android.control.aeCompensationStep", Rational.class);
/**
+ * <p>Whether the camera device supports {@link CaptureRequest#CONTROL_AE_LOCK android.control.aeLock}</p>
+ * <p>LIMITED or FULL devices will always list <code>true</code></p>
+ * <p>This key is available on all devices.</p>
+ *
+ * @see CaptureRequest#CONTROL_AE_LOCK
+ */
+ @PublicKey
+ public static final Key<Boolean> CONTROL_AE_LOCK_AVAILABLE =
+ new Key<Boolean>("android.control.aeLockAvailable", boolean.class);
+
+ /**
* <p>List of auto-focus (AF) modes for {@link CaptureRequest#CONTROL_AF_MODE android.control.afMode} that are
* supported by this camera device.</p>
* <p>Not all the auto-focus modes may be supported by a
@@ -471,6 +482,22 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri
new Key<int[]>("android.control.availableEffects", int[].class);
/**
+ * <p>List of control modes for {@link CaptureRequest#CONTROL_MODE android.control.mode} that are supported by this camera
+ * device.</p>
+ * <p>This list contains control modes that can be set for the camera device.
+ * LEGACY mode devices will always support AUTO mode. LIMITED and FULL
+ * devices will always support OFF, AUTO modes.</p>
+ * <p><b>Range of valid values:</b><br>
+ * Any value listed in {@link CaptureRequest#CONTROL_MODE android.control.mode}</p>
+ * <p>This key is available on all devices.</p>
+ *
+ * @see CaptureRequest#CONTROL_MODE
+ */
+ @PublicKey
+ public static final Key<int[]> CONTROL_AVAILABLE_MODES =
+ new Key<int[]>("android.control.availableModes", int[].class);
+
+ /**
* <p>List of scene modes for {@link CaptureRequest#CONTROL_SCENE_MODE android.control.sceneMode} that are supported by this camera
* device.</p>
* <p>This list contains scene modes that can be set for the camera device.
@@ -532,6 +559,17 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri
new Key<int[]>("android.control.awbAvailableModes", int[].class);
/**
+ * <p>Whether the camera device supports {@link CaptureRequest#CONTROL_AWB_LOCK android.control.awbLock}</p>
+ * <p>LIMITED or FULL devices will always list <code>true</code></p>
+ * <p>This key is available on all devices.</p>
+ *
+ * @see CaptureRequest#CONTROL_AWB_LOCK
+ */
+ @PublicKey
+ public static final Key<Boolean> CONTROL_AWB_LOCK_AVAILABLE =
+ new Key<Boolean>("android.control.awbLockAvailable", boolean.class);
+
+ /**
* <p>List of the maximum number of regions that can be used for metering in
* auto-exposure (AE), auto-white balance (AWB), and auto-focus (AF);
* this corresponds to the the maximum number of elements in
@@ -889,10 +927,12 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri
* <ul>
* <li>{@link #LENS_FACING_FRONT FRONT}</li>
* <li>{@link #LENS_FACING_BACK BACK}</li>
+ * <li>{@link #LENS_FACING_EXTERNAL EXTERNAL}</li>
* </ul></p>
* <p>This key is available on all devices.</p>
* @see #LENS_FACING_FRONT
* @see #LENS_FACING_BACK
+ * @see #LENS_FACING_EXTERNAL
*/
@PublicKey
public static final Key<Integer> LENS_FACING =
@@ -1069,8 +1109,11 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri
* android.scaler.availableInputOutputFormatsMap. When using an
* input stream, there must be at least one output stream
* configured to to receive the reprocessed images.</p>
+ * <p>When an input stream and some output streams are used in a reprocessing request,
+ * only the input buffer will be used to produce these output stream buffers, and a
+ * new sensor image will not be captured.</p>
* <p>For example, for Zero Shutter Lag (ZSL) still capture use case, the input
- * stream image format will be RAW_OPAQUE, the associated output stream image format
+ * stream image format will be OPAQUE, the associated output stream image format
* should be JPEG.</p>
* <p><b>Range of valid values:</b><br></p>
* <p>0 or 1.</p>
@@ -1080,8 +1123,8 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri
* {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL android.info.supportedHardwareLevel} key</p>
*
* @see CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL
- * @hide
*/
+ @PublicKey
public static final Key<Integer> REQUEST_MAX_NUM_INPUT_STREAMS =
new Key<Integer>("android.request.maxNumInputStreams", int.class);
@@ -1157,8 +1200,10 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri
* <li>{@link #REQUEST_AVAILABLE_CAPABILITIES_MANUAL_SENSOR MANUAL_SENSOR}</li>
* <li>{@link #REQUEST_AVAILABLE_CAPABILITIES_MANUAL_POST_PROCESSING MANUAL_POST_PROCESSING}</li>
* <li>{@link #REQUEST_AVAILABLE_CAPABILITIES_RAW RAW}</li>
+ * <li>{@link #REQUEST_AVAILABLE_CAPABILITIES_OPAQUE_REPROCESSING OPAQUE_REPROCESSING}</li>
* <li>{@link #REQUEST_AVAILABLE_CAPABILITIES_READ_SENSOR_SETTINGS READ_SENSOR_SETTINGS}</li>
* <li>{@link #REQUEST_AVAILABLE_CAPABILITIES_BURST_CAPTURE BURST_CAPTURE}</li>
+ * <li>{@link #REQUEST_AVAILABLE_CAPABILITIES_YUV_REPROCESSING YUV_REPROCESSING}</li>
* </ul></p>
* <p>This key is available on all devices.</p>
*
@@ -1167,8 +1212,10 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri
* @see #REQUEST_AVAILABLE_CAPABILITIES_MANUAL_SENSOR
* @see #REQUEST_AVAILABLE_CAPABILITIES_MANUAL_POST_PROCESSING
* @see #REQUEST_AVAILABLE_CAPABILITIES_RAW
+ * @see #REQUEST_AVAILABLE_CAPABILITIES_OPAQUE_REPROCESSING
* @see #REQUEST_AVAILABLE_CAPABILITIES_READ_SENSOR_SETTINGS
* @see #REQUEST_AVAILABLE_CAPABILITIES_BURST_CAPTURE
+ * @see #REQUEST_AVAILABLE_CAPABILITIES_YUV_REPROCESSING
*/
@PublicKey
public static final Key<int[]> REQUEST_AVAILABLE_CAPABILITIES =
@@ -1345,10 +1392,10 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri
* <p>The mapping of image formats that are supported by this
* camera device for input streams, to their corresponding output formats.</p>
* <p>All camera devices with at least 1
- * android.request.maxNumInputStreams will have at least one
+ * {@link CameraCharacteristics#REQUEST_MAX_NUM_INPUT_STREAMS android.request.maxNumInputStreams} will have at least one
* available input format.</p>
* <p>The camera device will support the following map of formats,
- * if its dependent capability is supported:</p>
+ * if its dependent capability ({@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES android.request.availableCapabilities}) is supported:</p>
* <table>
* <thead>
* <tr>
@@ -1359,45 +1406,42 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri
* </thead>
* <tbody>
* <tr>
- * <td align="left">RAW_OPAQUE</td>
+ * <td align="left">OPAQUE</td>
* <td align="left">JPEG</td>
- * <td align="left">ZSL</td>
+ * <td align="left">OPAQUE_REPROCESSING</td>
* </tr>
* <tr>
- * <td align="left">RAW_OPAQUE</td>
+ * <td align="left">OPAQUE</td>
* <td align="left">YUV_420_888</td>
- * <td align="left">ZSL</td>
+ * <td align="left">OPAQUE_REPROCESSING</td>
* </tr>
* <tr>
- * <td align="left">RAW_OPAQUE</td>
- * <td align="left">RAW16</td>
- * <td align="left">RAW</td>
- * </tr>
- * <tr>
- * <td align="left">RAW16</td>
* <td align="left">YUV_420_888</td>
- * <td align="left">RAW</td>
+ * <td align="left">JPEG</td>
+ * <td align="left">YUV_REPROCESSING</td>
* </tr>
* <tr>
- * <td align="left">RAW16</td>
- * <td align="left">JPEG</td>
- * <td align="left">RAW</td>
+ * <td align="left">YUV_420_888</td>
+ * <td align="left">YUV_420_888</td>
+ * <td align="left">YUV_REPROCESSING</td>
* </tr>
* </tbody>
* </table>
- * <p>For ZSL-capable camera devices, using the RAW_OPAQUE format
+ * <p>OPAQUE refers to a device-internal format that is not directly application-visible.
+ * An OPAQUE input or output surface can be acquired by
+ * OpaqueImageRingBufferQueue#getInputSurface() or
+ * OpaqueImageRingBufferQueue#getOutputSurface().
+ * For a OPAQUE_REPROCESSING-capable camera device, using the OPAQUE format
* as either input or output will never hurt maximum frame rate (i.e.
- * StreamConfigurationMap#getOutputStallDuration(int,Size)
- * for a <code>format =</code> RAW_OPAQUE is always 0).</p>
+ * StreamConfigurationMap#getOutputStallDuration(klass,Size) is always 0),
+ * where klass is android.media.OpaqueImageRingBufferQueue.class.</p>
* <p>Attempting to configure an input stream with output streams not
* listed as available in this map is not valid.</p>
* <p>TODO: typedef to ReprocessFormatMap</p>
* <p><b>Optional</b> - This value may be {@code null} on some devices.</p>
- * <p><b>Full capability</b> -
- * Present on all camera devices that report being {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL_FULL HARDWARE_LEVEL_FULL} devices in the
- * {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL android.info.supportedHardwareLevel} key</p>
*
- * @see CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL
+ * @see CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES
+ * @see CameraCharacteristics#REQUEST_MAX_NUM_INPUT_STREAMS
* @hide
*/
public static final Key<int[]> SCALER_AVAILABLE_INPUT_OUTPUT_FORMATS_MAP =
@@ -1899,6 +1943,23 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri
new Key<Integer>("android.sensor.info.timestampSource", int.class);
/**
+ * <p>Whether the RAW images output from this camera device are subject to
+ * lens shading correction.</p>
+ * <p>If TRUE, all images produced by the camera device in the RAW image formats will
+ * have lens shading correction already applied to it. If FALSE, the images will
+ * not be adjusted for lens shading correction.
+ * See {@link CameraCharacteristics#REQUEST_MAX_NUM_OUTPUT_RAW android.request.maxNumOutputRaw} for a list of RAW image formats.</p>
+ * <p>This key will be <code>null</code> for all devices do not report this information.
+ * Devices with RAW capability will always report this information in this key.</p>
+ * <p><b>Optional</b> - This value may be {@code null} on some devices.</p>
+ *
+ * @see CameraCharacteristics#REQUEST_MAX_NUM_OUTPUT_RAW
+ */
+ @PublicKey
+ public static final Key<Boolean> SENSOR_INFO_LENS_SHADING_APPLIED =
+ new Key<Boolean>("android.sensor.info.lensShadingApplied", boolean.class);
+
+ /**
* <p>The standard reference illuminant used as the scene light source when
* calculating the {@link CameraCharacteristics#SENSOR_COLOR_TRANSFORM1 android.sensor.colorTransform1},
* {@link CameraCharacteristics#SENSOR_CALIBRATION_TRANSFORM1 android.sensor.calibrationTransform1}, and
@@ -2189,6 +2250,22 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri
new Key<int[]>("android.sensor.availableTestPatternModes", int[].class);
/**
+ * <p>List of lens shading modes for {@link CaptureRequest#SHADING_MODE android.shading.mode} that are supported by this camera device.</p>
+ * <p>This list contains lens shading modes that can be set for the camera device.
+ * Camera devices that support the MANUAL_POST_PROCESSING capability will always
+ * list OFF and FAST mode. This includes all FULL level devices.
+ * LEGACY devices will always only support FAST mode.</p>
+ * <p><b>Range of valid values:</b><br>
+ * Any value listed in {@link CaptureRequest#SHADING_MODE android.shading.mode}</p>
+ * <p>This key is available on all devices.</p>
+ *
+ * @see CaptureRequest#SHADING_MODE
+ */
+ @PublicKey
+ public static final Key<int[]> SHADING_AVAILABLE_MODES =
+ new Key<int[]>("android.shading.availableModes", int[].class);
+
+ /**
* <p>List of face detection modes for {@link CaptureRequest#STATISTICS_FACE_DETECT_MODE android.statistics.faceDetectMode} that are
* supported by this camera device.</p>
* <p>OFF is always supported.</p>
@@ -2232,6 +2309,23 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri
new Key<boolean[]>("android.statistics.info.availableHotPixelMapModes", boolean[].class);
/**
+ * <p>List of lens shading map output modes for {@link CaptureRequest#STATISTICS_LENS_SHADING_MAP_MODE android.statistics.lensShadingMapMode} that
+ * are supported by this camera device.</p>
+ * <p>If no lens shading map output is available for this camera device, this key will
+ * contain only OFF.</p>
+ * <p>ON is always supported on devices with the RAW capability.
+ * LEGACY mode devices will always only support OFF.</p>
+ * <p><b>Range of valid values:</b><br>
+ * Any value listed in {@link CaptureRequest#STATISTICS_LENS_SHADING_MAP_MODE android.statistics.lensShadingMapMode}</p>
+ * <p><b>Optional</b> - This value may be {@code null} on some devices.</p>
+ *
+ * @see CaptureRequest#STATISTICS_LENS_SHADING_MAP_MODE
+ */
+ @PublicKey
+ public static final Key<byte[]> STATISTICS_INFO_AVAILABLE_LENS_SHADING_MAP_MODES =
+ new Key<byte[]>("android.statistics.info.availableLensShadingMapModes", byte[].class);
+
+ /**
* <p>Maximum number of supported points in the
* tonemap curve that can be used for {@link CaptureRequest#TONEMAP_CURVE android.tonemap.curve}.</p>
* <p>If the actual number of points provided by the application (in {@link CaptureRequest#TONEMAP_CURVE android.tonemap.curve}*) is
@@ -2255,8 +2349,13 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri
/**
* <p>List of tonemapping modes for {@link CaptureRequest#TONEMAP_MODE android.tonemap.mode} that are supported by this camera
* device.</p>
- * <p>Camera devices that support the MANUAL_POST_PROCESSING capability will always list
- * CONTRAST_CURVE and FAST. This includes all FULL level devices.</p>
+ * <p>Camera devices that support the MANUAL_POST_PROCESSING capability will always contain
+ * at least one of below mode combinations:</p>
+ * <ul>
+ * <li>CONTRAST_CURVE and FAST</li>
+ * <li>GAMMA_VALUE, PRESET_CURVE, and FAST</li>
+ * </ul>
+ * <p>This includes all FULL level devices.</p>
* <p><b>Range of valid values:</b><br>
* Any value listed in {@link CaptureRequest#TONEMAP_MODE android.tonemap.mode}</p>
* <p><b>Optional</b> - This value may be {@code null} on some devices.</p>
diff --git a/core/java/android/hardware/camera2/CameraManager.java b/core/java/android/hardware/camera2/CameraManager.java
index b6bb33b..8af3c15 100644
--- a/core/java/android/hardware/camera2/CameraManager.java
+++ b/core/java/android/hardware/camera2/CameraManager.java
@@ -109,8 +109,11 @@ public final class CameraManager {
* of the state of individual CameraManager instances.</p>
*
* @param callback the new callback to send camera availability notices to
- * @param handler The handler on which the callback should be invoked, or
- * {@code null} to use the current thread's {@link android.os.Looper looper}.
+ * @param handler The handler on which the callback should be invoked, or {@code null} to use
+ * the current thread's {@link android.os.Looper looper}.
+ *
+ * @throws IllegalArgumentException if the handler is {@code null} but the current thread has
+ * no looper.
*/
public void registerAvailabilityCallback(AvailabilityCallback callback, Handler handler) {
if (handler == null) {
@@ -138,6 +141,42 @@ public final class CameraManager {
}
/**
+ * Register a callback to be notified about torch mode status.
+ *
+ * <p>Registering the same callback again will replace the handler with the
+ * new one provided.</p>
+ *
+ * <p>The first time a callback is registered, it is immediately called
+ * with the torch mode status of all currently known camera devices.</p>
+ *
+ * <p>Since this callback will be registered with the camera service, remember to unregister it
+ * once it is no longer needed; otherwise the callback will continue to receive events
+ * indefinitely and it may prevent other resources from being released. Specifically, the
+ * callbacks will be invoked independently of the general activity lifecycle and independently
+ * of the state of individual CameraManager instances.</p>
+ *
+ * @param callback The new callback to send torch mode status to
+ * @param handler The handler on which the callback should be invoked, or {@code null} to use
+ * the current thread's {@link android.os.Looper looper}.
+ *
+ * @throws IllegalArgumentException if the handler is {@code null} but the current thread has
+ * no looper.
+ */
+ public void registerTorchCallback(TorchCallback callback, Handler handler) {
+ }
+
+ /**
+ * Remove a previously-added callback; the callback will no longer receive torch mode status
+ * callbacks.
+ *
+ * <p>Removing a callback that isn't registered has no effect.</p>
+ *
+ * @param callback The callback to remove from the notification list
+ */
+ public void unregisterTorchCallback(TorchCallback callback) {
+ }
+
+ /**
* <p>Query the capabilities of a camera device. These capabilities are
* immutable for a given camera.</p>
*
@@ -384,6 +423,47 @@ public final class CameraManager {
}
/**
+ * Set the flash unit's torch mode of the camera of the given ID without opening the camera
+ * device.
+ *
+ * <p>Use {@link #getCameraIdList} to get the list of available camera devices and use
+ * {@link #getCameraCharacteristics} to check whether the camera device has a flash unit.
+ * Note that even if a camera device has a flash unit, turning on the torch mode may fail
+ * if the camera device or other camera resources needed to turn on the torch mode are in use.
+ * </p>
+ *
+ * <p> If {@link #setTorchMode} is called to turn on or off the torch mode successfully,
+ * {@link CameraManager.TorchCallback#onTorchModeChanged} will be invoked.
+ * However, even if turning on the torch mode is successful, the application does not have the
+ * exclusive ownership of the flash unit or the camera device. The torch mode will be turned
+ * off and becomes unavailable when the camera device that the flash unit belongs to becomes
+ * unavailable ({@link CameraManager.TorchCallback#onTorchModeAvailable} will be
+ * invoked) or when other camera resources to keep the torch on become unavailable (
+ * {@link CameraManager.TorchCallback#onTorchModeUnavailable} will be invoked). Also,
+ * other applications are free to call {@link #setTorchMode} to turn off the torch mode (
+ * {@link CameraManager.TorchCallback#onTorchModeChanged} will be invoked).
+ *
+ * @param cameraId
+ * The unique identifier of the camera device that the flash unit belongs to.
+ * @param enabled
+ * The desired state of the torch mode for the target camera device. Set to
+ * {@code true} to turn on the torch mode. Set to {@code false} to turn off the
+ * torch mode.
+ *
+ * @throws CameraAccessException if it failed to access the flash unit.
+ * {@link CameraAccessException#CAMERA_IN_USE} will be thrown if the camera device
+ * is in use. {@link CameraAccessException#MAX_CAMERAS_IN_USE} will be thrown if
+ * other camera resources needed to turn on the torch mode are in use.
+ *
+ * @throws IllegalArgumentException if cameraId was null, cameraId doesn't match any currently
+ * or previously available camera device, or the camera device doesn't have a
+ * flash unit.
+ */
+ public void setTorchMode(String cameraId, boolean enabled) throws CameraAccessException {
+
+ }
+
+ /**
* A callback for camera devices becoming available or
* unavailable to open.
*
@@ -428,6 +508,68 @@ public final class CameraManager {
}
/**
+ * A callback for camera flash torch modes becoming available, unavailable, enabled, or
+ * disabled.
+ *
+ * <p>The torch mode becomes available when the camera device it belongs to is no longer in use
+ * and other camera resources it needs are no longer busy. It becomes unavailable when the
+ * camera device it belongs to becomes unavailable or other camera resouces it needs become
+ * busy due to other higher priority camera activities. The torch mode changes when an
+ * application calls {@link #setTorchMode} successfully.
+ *
+ * <p>Extend this callback and pass an instance of the subclass to
+ * {@link CameraManager#registerTorchCallback} to be notified of such status changes.
+ * </p>
+ *
+ * @see registerTorchCallback
+ */
+ public static abstract class TorchCallback {
+ /**
+ * The torch mode of a camera has become available to use.
+ *
+ * <p>The default implementation of this method does nothing.</p>
+ *
+ * @param cameraId The unique identifier of the camera whose torch mode has become
+ * available.
+ */
+ public void onTorchModeAvailable(String cameraId) {
+ // default empty implementation
+ }
+
+ /**
+ * A previously-available torch mode of a camera has become unavailable.
+ *
+ * <p>If torch mode was previously turned on by calling {@link #setTorchMode}, it will be
+ * turned off before {@link CameraManager.TorchCallback#onTorchModeUnavailable} is
+ * invoked. {@link #setTorchMode} will fail until the flash unit becomes available again.
+ * </p>
+ *
+ * <p>The default implementation of this method does nothing.</p>
+ *
+ * @param cameraId The unique identifier of the camera whose torch mode has become
+ * unavailable.
+ */
+ public void onTorchModeUnavailable(String cameraId) {
+ // default empty implementation
+ }
+
+ /**
+ * Torch mode of a camera has been turned on or off through {@link #setTorchMode}.
+ *
+ * <p>The default implementation of this method does nothing.</p>
+ *
+ * @param cameraId The unique identifier of the camera whose torch mode has been changed.
+ *
+ * @param enabled The state that the torch mode of the camera has been changed to.
+ * {@code true} when the torch mode has been turned on. {@code false} when
+ * the torch mode has been turned off.
+ */
+ public void onTorchModeChanged(String cameraId, boolean enabled) {
+ // default empty implementation
+ }
+ }
+
+ /**
* Return or create the list of currently connected camera devices.
*
* <p>In case of errors connecting to the camera service, will return an empty list.</p>
diff --git a/core/java/android/hardware/camera2/CameraMetadata.java b/core/java/android/hardware/camera2/CameraMetadata.java
index 1b10858..ffe30d4 100644
--- a/core/java/android/hardware/camera2/CameraMetadata.java
+++ b/core/java/android/hardware/camera2/CameraMetadata.java
@@ -288,6 +288,13 @@ public abstract class CameraMetadata<TKey> {
*/
public static final int LENS_FACING_BACK = 1;
+ /**
+ * <p>The camera device is an external camera, and has no fixed facing relative to the
+ * device's screen.</p>
+ * @see CameraCharacteristics#LENS_FACING
+ */
+ public static final int LENS_FACING_EXTERNAL = 2;
+
//
// Enumeration values for CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES
//
@@ -367,13 +374,19 @@ public abstract class CameraMetadata<TKey> {
* The camera device supports basic manual control of the image post-processing
* stages. This means the following controls are guaranteed to be supported:</p>
* <ul>
- * <li>Manual tonemap control<ul>
+ * <li>
+ * <p>Manual tonemap control</p>
+ * <ul>
* <li>{@link CaptureRequest#TONEMAP_CURVE android.tonemap.curve}</li>
* <li>{@link CaptureRequest#TONEMAP_MODE android.tonemap.mode}</li>
* <li>{@link CameraCharacteristics#TONEMAP_MAX_CURVE_POINTS android.tonemap.maxCurvePoints}</li>
+ * <li>{@link CaptureRequest#TONEMAP_GAMMA android.tonemap.gamma}</li>
+ * <li>{@link CaptureRequest#TONEMAP_PRESET_CURVE android.tonemap.presetCurve}</li>
* </ul>
* </li>
- * <li>Manual white balance control<ul>
+ * <li>
+ * <p>Manual white balance control</p>
+ * <ul>
* <li>{@link CaptureRequest#COLOR_CORRECTION_TRANSFORM android.colorCorrection.transform}</li>
* <li>{@link CaptureRequest#COLOR_CORRECTION_GAINS android.colorCorrection.gains}</li>
* </ul>
@@ -403,8 +416,10 @@ public abstract class CameraMetadata<TKey> {
* @see CaptureRequest#SHADING_MODE
* @see CaptureRequest#STATISTICS_LENS_SHADING_MAP_MODE
* @see CaptureRequest#TONEMAP_CURVE
+ * @see CaptureRequest#TONEMAP_GAMMA
* @see CameraCharacteristics#TONEMAP_MAX_CURVE_POINTS
* @see CaptureRequest#TONEMAP_MODE
+ * @see CaptureRequest#TONEMAP_PRESET_CURVE
* @see CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES
*/
public static final int REQUEST_AVAILABLE_CAPABILITIES_MANUAL_POST_PROCESSING = 2;
@@ -432,23 +447,40 @@ public abstract class CameraMetadata<TKey> {
public static final int REQUEST_AVAILABLE_CAPABILITIES_RAW = 3;
/**
- * <p>The camera device supports the Zero Shutter Lag use case.</p>
+ * <p>The camera device supports the Zero Shutter Lag reprocessing use case.</p>
* <ul>
- * <li>At least one input stream can be used.</li>
- * <li>RAW_OPAQUE is supported as an output/input format</li>
- * <li>Using RAW_OPAQUE does not cause a frame rate drop
+ * <li>One input stream is supported, that is, <code>{@link CameraCharacteristics#REQUEST_MAX_NUM_INPUT_STREAMS android.request.maxNumInputStreams} == 1</code>.</li>
+ * <li>OPAQUE is supported as an output/input format, that is,
+ * StreamConfigurationMap#getOutputSizes(klass) and
+ * StreamConfigurationMap#getInputSizes(klass) return non empty Size[] and have common
+ * sizes, where klass is android.media.OpaqueImageRingBufferQueue.class. See
+ * android.scaler.availableInputOutputFormatsMap for detailed information about
+ * OPAQUE format.</li>
+ * <li>android.scaler.availableInputOutputFormatsMap has the required map entries.</li>
+ * <li>Using OPAQUE does not cause a frame rate drop
* relative to the sensor's maximum capture rate (at that
- * resolution).</li>
- * <li>RAW_OPAQUE will be reprocessable into both YUV_420_888
+ * resolution), see android.scaler.availableInputOutputFormatsMap for more details.</li>
+ * <li>OPAQUE will be reprocessable into both YUV_420_888
* and JPEG formats.</li>
- * <li>The maximum available resolution for RAW_OPAQUE streams
+ * <li>The maximum available resolution for OPAQUE streams
* (both input/output) will match the maximum available
* resolution of JPEG streams.</li>
+ * <li>Only below controls are effective for reprocessing requests and
+ * will be present in capture results, other controls in reprocess
+ * requests will be ignored by the camera device.<ul>
+ * <li>android.jpeg.*</li>
+ * <li>{@link CaptureRequest#NOISE_REDUCTION_MODE android.noiseReduction.mode}</li>
+ * <li>{@link CaptureRequest#EDGE_MODE android.edge.mode}</li>
+ * </ul>
+ * </li>
* </ul>
+ *
+ * @see CaptureRequest#EDGE_MODE
+ * @see CaptureRequest#NOISE_REDUCTION_MODE
+ * @see CameraCharacteristics#REQUEST_MAX_NUM_INPUT_STREAMS
* @see CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES
- * @hide
*/
- public static final int REQUEST_AVAILABLE_CAPABILITIES_ZSL = 4;
+ public static final int REQUEST_AVAILABLE_CAPABILITIES_OPAQUE_REPROCESSING = 4;
/**
* <p>The camera device supports accurately reporting the sensor settings for many of
@@ -508,6 +540,45 @@ public abstract class CameraMetadata<TKey> {
*/
public static final int REQUEST_AVAILABLE_CAPABILITIES_BURST_CAPTURE = 6;
+ /**
+ * <p>The camera device supports the YUV420_888 reprocessing use case, similar as
+ * OPAQUE_REPROCESSING, This capability requires the camera device to support the
+ * following:</p>
+ * <ul>
+ * <li>One input stream is supported, that is, <code>{@link CameraCharacteristics#REQUEST_MAX_NUM_INPUT_STREAMS android.request.maxNumInputStreams} == 1</code>.</li>
+ * <li>YUV420_888 is supported as a common format for both input and output, that is,
+ * StreamConfigurationMap#getOutputSizes(YUV420_888) and
+ * StreamConfigurationMap#getInputSizes(YUV420_888) return non empty Size[] and have
+ * common sizes.</li>
+ * <li>android.scaler.availableInputOutputFormatsMap has the required map entries.</li>
+ * <li>Using YUV420_888 does not cause a frame rate drop
+ * relative to the sensor's maximum capture rate (at that
+ * resolution), see android.scaler.availableInputOutputFormatsMap for more details.</li>
+ * <li>YUV420_888 will be reprocessable into both YUV_420_888
+ * and JPEG formats.</li>
+ * <li>The maximum available resolution for YUV420_888 streams
+ * (both input/output) will match the maximum available
+ * resolution of JPEG streams.</li>
+ * <li>Only the below controls are effective for reprocessing requests and will be
+ * present in capture results. The reprocess requests are from the original capture
+ * results that are assocaited with the intermidate YUV420_888 output buffers.
+ * All other controls in the reprocess requests will be ignored by the camera device.<ul>
+ * <li>android.jpeg.*</li>
+ * <li>{@link CaptureRequest#NOISE_REDUCTION_MODE android.noiseReduction.mode}</li>
+ * <li>{@link CaptureRequest#EDGE_MODE android.edge.mode}</li>
+ * <li>{@link CaptureRequest#REPROCESS_EFFECTIVE_EXPOSURE_FACTOR android.reprocess.effectiveExposureFactor}</li>
+ * </ul>
+ * </li>
+ * </ul>
+ *
+ * @see CaptureRequest#EDGE_MODE
+ * @see CaptureRequest#NOISE_REDUCTION_MODE
+ * @see CaptureRequest#REPROCESS_EFFECTIVE_EXPOSURE_FACTOR
+ * @see CameraCharacteristics#REQUEST_MAX_NUM_INPUT_STREAMS
+ * @see CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES
+ */
+ public static final int REQUEST_AVAILABLE_CAPABILITIES_YUV_REPROCESSING = 7;
+
//
// Enumeration values for CameraCharacteristics#SCALER_CROPPING_TYPE
//
@@ -966,6 +1037,14 @@ public abstract class CameraMetadata<TKey> {
*/
public static final int CONTROL_AE_PRECAPTURE_TRIGGER_START = 1;
+ /**
+ * <p>The camera device will cancel any currently active or completed
+ * precapture metering sequence, the auto-exposure routine will return to its
+ * initial state.</p>
+ * @see CaptureRequest#CONTROL_AE_PRECAPTURE_TRIGGER
+ */
+ public static final int CONTROL_AE_PRECAPTURE_TRIGGER_CANCEL = 2;
+
//
// Enumeration values for CaptureRequest#CONTROL_AF_MODE
//
@@ -1823,6 +1902,13 @@ public abstract class CameraMetadata<TKey> {
*/
public static final int NOISE_REDUCTION_MODE_HIGH_QUALITY = 2;
+ /**
+ * <p>MINIMAL noise reduction is applied without reducing frame rate relative to
+ * sensor output. </p>
+ * @see CaptureRequest#NOISE_REDUCTION_MODE
+ */
+ public static final int NOISE_REDUCTION_MODE_MINIMAL = 3;
+
//
// Enumeration values for CaptureRequest#SENSOR_TEST_PATTERN_MODE
//
@@ -2026,6 +2112,47 @@ public abstract class CameraMetadata<TKey> {
*/
public static final int TONEMAP_MODE_HIGH_QUALITY = 2;
+ /**
+ * <p>Use the gamma value specified in {@link CaptureRequest#TONEMAP_GAMMA android.tonemap.gamma} to peform
+ * tonemapping.</p>
+ * <p>All color enhancement and tonemapping must be disabled, except
+ * for applying the tonemapping curve specified by {@link CaptureRequest#TONEMAP_GAMMA android.tonemap.gamma}.</p>
+ * <p>Must not slow down frame rate relative to raw sensor output.</p>
+ *
+ * @see CaptureRequest#TONEMAP_GAMMA
+ * @see CaptureRequest#TONEMAP_MODE
+ */
+ public static final int TONEMAP_MODE_GAMMA_VALUE = 3;
+
+ /**
+ * <p>Use the preset tonemapping curve specified in
+ * {@link CaptureRequest#TONEMAP_PRESET_CURVE android.tonemap.presetCurve} to peform tonemapping.</p>
+ * <p>All color enhancement and tonemapping must be disabled, except
+ * for applying the tonemapping curve specified by
+ * {@link CaptureRequest#TONEMAP_PRESET_CURVE android.tonemap.presetCurve}.</p>
+ * <p>Must not slow down frame rate relative to raw sensor output.</p>
+ *
+ * @see CaptureRequest#TONEMAP_PRESET_CURVE
+ * @see CaptureRequest#TONEMAP_MODE
+ */
+ public static final int TONEMAP_MODE_PRESET_CURVE = 4;
+
+ //
+ // Enumeration values for CaptureRequest#TONEMAP_PRESET_CURVE
+ //
+
+ /**
+ * <p>Tonemapping curve is defined by sRGB</p>
+ * @see CaptureRequest#TONEMAP_PRESET_CURVE
+ */
+ public static final int TONEMAP_PRESET_CURVE_SRGB = 0;
+
+ /**
+ * <p>Tonemapping curve is defined by ITU-R BT.709</p>
+ * @see CaptureRequest#TONEMAP_PRESET_CURVE
+ */
+ public static final int TONEMAP_PRESET_CURVE_REC709 = 1;
+
//
// Enumeration values for CaptureResult#CONTROL_AE_STATE
//
@@ -2073,7 +2200,10 @@ public abstract class CameraMetadata<TKey> {
* <p>AE has been asked to do a precapture sequence
* and is currently executing it.</p>
* <p>Precapture can be triggered through setting
- * {@link CaptureRequest#CONTROL_AE_PRECAPTURE_TRIGGER android.control.aePrecaptureTrigger} to START.</p>
+ * {@link CaptureRequest#CONTROL_AE_PRECAPTURE_TRIGGER android.control.aePrecaptureTrigger} to START. Currently
+ * active and completed (if it causes camera device internal AE lock) precapture
+ * metering sequence can be canceled through setting
+ * {@link CaptureRequest#CONTROL_AE_PRECAPTURE_TRIGGER android.control.aePrecaptureTrigger} to CANCEL.</p>
* <p>Once PRECAPTURE completes, AE will transition to CONVERGED
* or FLASH_REQUIRED as appropriate. This is a transient
* state, the camera device may skip reporting this state in
diff --git a/core/java/android/hardware/camera2/CaptureRequest.java b/core/java/android/hardware/camera2/CaptureRequest.java
index b417496..7569ea5 100644
--- a/core/java/android/hardware/camera2/CaptureRequest.java
+++ b/core/java/android/hardware/camera2/CaptureRequest.java
@@ -552,6 +552,8 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>>
* in this matrix result metadata. The transform should keep the magnitude
* of the output color values within <code>[0, 1.0]</code> (assuming input color
* values is within the normalized range <code>[0, 1.0]</code>), or clipping may occur.</p>
+ * <p>The valid range of each matrix element varies on different devices, but
+ * values within [-1.5, 3.0] are guaranteed not to be clipped.</p>
* <p><b>Units</b>: Unitless scale factors</p>
* <p><b>Optional</b> - This value may be {@code null} on some devices.</p>
* <p><b>Full capability</b> -
@@ -575,6 +577,10 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>>
* TRANSFORM_MATRIX.</p>
* <p>The gains in the result metadata are the gains actually
* applied by the camera device to the current frame.</p>
+ * <p>The valid range of gains varies on different devices, but gains
+ * between [1.0, 3.0] are guaranteed not to be clipped. Even if a given
+ * device allows gains below 1.0, this is usually not recommended because
+ * this can create color artifacts.</p>
* <p><b>Units</b>: Unitless gain factors</p>
* <p><b>Optional</b> - This value may be {@code null} on some devices.</p>
* <p><b>Full capability</b> -
@@ -724,7 +730,14 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>>
* ({@link CaptureRequest#SENSOR_EXPOSURE_TIME android.sensor.exposureTime}) and sensitivity ({@link CaptureRequest#SENSOR_SENSITIVITY android.sensor.sensitivity})
* parameters. The flash may be fired if the {@link CaptureRequest#CONTROL_AE_MODE android.control.aeMode}
* is ON_AUTO_FLASH/ON_AUTO_FLASH_REDEYE and the scene is too dark. If the
- * {@link CaptureRequest#CONTROL_AE_MODE android.control.aeMode} is ON_ALWAYS_FLASH, the scene may become overexposed.</p>
+ * {@link CaptureRequest#CONTROL_AE_MODE android.control.aeMode} is ON_ALWAYS_FLASH, the scene may become overexposed.
+ * Similarly, AE precapture trigger CANCEL has no effect when AE is already locked.</p>
+ * <p>When an AE precapture sequence is triggered, AE unlock will not be able to unlock
+ * the AE if AE is locked by the camera device internally during precapture metering
+ * sequence In other words, submitting requests with AE unlock has no effect for an
+ * ongoing precapture metering sequence. Otherwise, the precapture metering sequence
+ * will never succeed in a sequence of preview requests where AE lock is always set
+ * to <code>false</code>.</p>
* <p>Since the camera device has a pipeline of in-flight requests, the settings that
* get locked do not necessarily correspond to the settings that were present in the
* latest capture result received from the camera device, since additional captures
@@ -869,6 +882,11 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>>
* included at all in the request settings. When included and
* set to START, the camera device will trigger the auto-exposure (AE)
* precapture metering sequence.</p>
+ * <p>When set to CANCEL, the camera device will cancel any active
+ * precapture metering trigger, and return to its initial AE state.
+ * If a precapture metering sequence is already completed, and the camera
+ * device has implicitly locked the AE for subsequent still capture, the
+ * CANCEL trigger will unlock the AE and return to its initial AE state.</p>
* <p>The precapture sequence should be triggered before starting a
* high-quality still capture for final metering decisions to
* be made, and for firing pre-capture flash pulses to estimate
@@ -884,7 +902,11 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>>
* submitted. To ensure that the AE routine restarts normal scan, the application should
* submit a request with <code>{@link CaptureRequest#CONTROL_AE_LOCK android.control.aeLock} == true</code>, followed by a request
* with <code>{@link CaptureRequest#CONTROL_AE_LOCK android.control.aeLock} == false</code>, if the application decides not to submit a
- * still capture request after the precapture sequence completes.</p>
+ * still capture request after the precapture sequence completes. Alternatively, for
+ * API level 23 or newer devices, the CANCEL can be used to unlock the camera device
+ * internally locked AE if the application doesn't submit a still capture request after
+ * the AE precapture trigger. Note that, the CANCEL was added in API level 23, and must not
+ * be used in devices that have earlier API levels.</p>
* <p>The exact effect of auto-exposure (AE) precapture trigger
* depends on the current AE mode and state; see
* {@link CaptureResult#CONTROL_AE_STATE android.control.aeState} for AE precapture state transition
@@ -897,6 +919,7 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>>
* <ul>
* <li>{@link #CONTROL_AE_PRECAPTURE_TRIGGER_IDLE IDLE}</li>
* <li>{@link #CONTROL_AE_PRECAPTURE_TRIGGER_START START}</li>
+ * <li>{@link #CONTROL_AE_PRECAPTURE_TRIGGER_CANCEL CANCEL}</li>
* </ul></p>
* <p><b>Optional</b> - This value may be {@code null} on some devices.</p>
* <p><b>Limited capability</b> -
@@ -909,6 +932,7 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>>
* @see CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL
* @see #CONTROL_AE_PRECAPTURE_TRIGGER_IDLE
* @see #CONTROL_AE_PRECAPTURE_TRIGGER_START
+ * @see #CONTROL_AE_PRECAPTURE_TRIGGER_CANCEL
*/
@PublicKey
public static final Key<Integer> CONTROL_AE_PRECAPTURE_TRIGGER =
@@ -1164,7 +1188,7 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>>
* <p>This control (except for MANUAL) is only effective if
* <code>{@link CaptureRequest#CONTROL_MODE android.control.mode} != OFF</code> and any 3A routine is active.</p>
* <p>ZERO_SHUTTER_LAG will be supported if {@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES android.request.availableCapabilities}
- * contains ZSL. MANUAL will be supported if {@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES android.request.availableCapabilities}
+ * contains OPAQUE_REPROCESSING. MANUAL will be supported if {@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES android.request.availableCapabilities}
* contains MANUAL_SENSOR. Other intent values are always supported.</p>
* <p><b>Possible values:</b>
* <ul>
@@ -1249,10 +1273,6 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>>
* update, as if this frame is never captured. This mode can be used in the scenario
* where the application doesn't want a 3A manual control capture to affect
* the subsequent auto 3A capture results.</p>
- * <p>LEGACY mode devices will only support AUTO and USE_SCENE_MODE modes.
- * LIMITED mode devices will only support OFF and OFF_KEEP_STATE if they
- * support the MANUAL_SENSOR and MANUAL_POST_PROCSESING capabilities.
- * FULL mode devices will always support OFF and OFF_KEEP_STATE.</p>
* <p><b>Possible values:</b>
* <ul>
* <li>{@link #CONTROL_MODE_OFF OFF}</li>
@@ -1260,9 +1280,12 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>>
* <li>{@link #CONTROL_MODE_USE_SCENE_MODE USE_SCENE_MODE}</li>
* <li>{@link #CONTROL_MODE_OFF_KEEP_STATE OFF_KEEP_STATE}</li>
* </ul></p>
+ * <p><b>Available values for this device:</b><br>
+ * {@link CameraCharacteristics#CONTROL_AVAILABLE_MODES android.control.availableModes}</p>
* <p>This key is available on all devices.</p>
*
* @see CaptureRequest#CONTROL_AF_MODE
+ * @see CameraCharacteristics#CONTROL_AVAILABLE_MODES
* @see #CONTROL_MODE_OFF
* @see #CONTROL_MODE_AUTO
* @see #CONTROL_MODE_USE_SCENE_MODE
@@ -1382,6 +1405,10 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>>
* camera device will use the highest-quality enhancement algorithms,
* even if it slows down capture rate. FAST means the camera device will
* not slow down capture rate when applying edge enhancement.</p>
+ * <p>For YUV_REPROCESSING, these FAST/HIGH_QUALITY modes both mean that the camera
+ * device will apply FAST/HIGH_QUALITY YUV-domain edge enhancement, respectively.
+ * The camera device may adjust its internal noise reduction parameters for best
+ * image quality based on the {@link CaptureRequest#REPROCESS_EFFECTIVE_EXPOSURE_FACTOR android.reprocess.effectiveExposureFactor}, if it is set.</p>
* <p><b>Possible values:</b>
* <ul>
* <li>{@link #EDGE_MODE_OFF OFF}</li>
@@ -1397,6 +1424,7 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>>
*
* @see CameraCharacteristics#EDGE_AVAILABLE_EDGE_MODES
* @see CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL
+ * @see CaptureRequest#REPROCESS_EFFECTIVE_EXPOSURE_FACTOR
* @see #EDGE_MODE_OFF
* @see #EDGE_MODE_FAST
* @see #EDGE_MODE_HIGH_QUALITY
@@ -1761,18 +1789,28 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>>
/**
* <p>Mode of operation for the noise reduction algorithm.</p>
* <p>The noise reduction algorithm attempts to improve image quality by removing
- * excessive noise added by the capture process, especially in dark conditions.
- * OFF means no noise reduction will be applied by the camera device.</p>
+ * excessive noise added by the capture process, especially in dark conditions.</p>
+ * <p>OFF means no noise reduction will be applied by the camera device, for both raw and
+ * YUV domain.</p>
+ * <p>MINIMAL means that only sensor raw domain basic noise reduction is enabled ,to remove
+ * demosaicing or other processing artifacts. For YUV_REPROCESSING, MINIMAL is same as OFF.
+ * This mode is optional, may not be support by all devices. The application should check
+ * {@link CameraCharacteristics#NOISE_REDUCTION_AVAILABLE_NOISE_REDUCTION_MODES android.noiseReduction.availableNoiseReductionModes} before using it.</p>
* <p>FAST/HIGH_QUALITY both mean camera device determined noise filtering
* will be applied. HIGH_QUALITY mode indicates that the camera device
* will use the highest-quality noise filtering algorithms,
* even if it slows down capture rate. FAST means the camera device will not
* slow down capture rate when applying noise filtering.</p>
+ * <p>For YUV_REPROCESSING, these FAST/HIGH_QUALITY modes both mean that the camera device
+ * will apply FAST/HIGH_QUALITY YUV domain noise reduction, respectively. The camera device
+ * may adjust the noise reduction parameters for best image quality based on the
+ * {@link CaptureRequest#REPROCESS_EFFECTIVE_EXPOSURE_FACTOR android.reprocess.effectiveExposureFactor} if it is set.</p>
* <p><b>Possible values:</b>
* <ul>
* <li>{@link #NOISE_REDUCTION_MODE_OFF OFF}</li>
* <li>{@link #NOISE_REDUCTION_MODE_FAST FAST}</li>
* <li>{@link #NOISE_REDUCTION_MODE_HIGH_QUALITY HIGH_QUALITY}</li>
+ * <li>{@link #NOISE_REDUCTION_MODE_MINIMAL MINIMAL}</li>
* </ul></p>
* <p><b>Available values for this device:</b><br>
* {@link CameraCharacteristics#NOISE_REDUCTION_AVAILABLE_NOISE_REDUCTION_MODES android.noiseReduction.availableNoiseReductionModes}</p>
@@ -1783,9 +1821,11 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>>
*
* @see CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL
* @see CameraCharacteristics#NOISE_REDUCTION_AVAILABLE_NOISE_REDUCTION_MODES
+ * @see CaptureRequest#REPROCESS_EFFECTIVE_EXPOSURE_FACTOR
* @see #NOISE_REDUCTION_MODE_OFF
* @see #NOISE_REDUCTION_MODE_FAST
* @see #NOISE_REDUCTION_MODE_HIGH_QUALITY
+ * @see #NOISE_REDUCTION_MODE_MINIMAL
*/
@PublicKey
public static final Key<Integer> NOISE_REDUCTION_MODE =
@@ -2078,6 +2118,8 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>>
* <li>{@link #SHADING_MODE_FAST FAST}</li>
* <li>{@link #SHADING_MODE_HIGH_QUALITY HIGH_QUALITY}</li>
* </ul></p>
+ * <p><b>Available values for this device:</b><br>
+ * {@link CameraCharacteristics#SHADING_AVAILABLE_MODES android.shading.availableModes}</p>
* <p><b>Optional</b> - This value may be {@code null} on some devices.</p>
* <p><b>Full capability</b> -
* Present on all camera devices that report being {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL_FULL HARDWARE_LEVEL_FULL} devices in the
@@ -2086,6 +2128,7 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>>
* @see CaptureRequest#CONTROL_AE_MODE
* @see CaptureRequest#CONTROL_AWB_MODE
* @see CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL
+ * @see CameraCharacteristics#SHADING_AVAILABLE_MODES
* @see CaptureResult#STATISTICS_LENS_SHADING_CORRECTION_MAP
* @see CaptureRequest#STATISTICS_LENS_SHADING_MAP_MODE
* @see #SHADING_MODE_OFF
@@ -2148,12 +2191,15 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>>
* <li>{@link #STATISTICS_LENS_SHADING_MAP_MODE_OFF OFF}</li>
* <li>{@link #STATISTICS_LENS_SHADING_MAP_MODE_ON ON}</li>
* </ul></p>
+ * <p><b>Available values for this device:</b><br>
+ * {@link CameraCharacteristics#STATISTICS_INFO_AVAILABLE_LENS_SHADING_MAP_MODES android.statistics.info.availableLensShadingMapModes}</p>
* <p><b>Optional</b> - This value may be {@code null} on some devices.</p>
* <p><b>Full capability</b> -
* Present on all camera devices that report being {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL_FULL HARDWARE_LEVEL_FULL} devices in the
* {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL android.info.supportedHardwareLevel} key</p>
*
* @see CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL
+ * @see CameraCharacteristics#STATISTICS_INFO_AVAILABLE_LENS_SHADING_MAP_MODES
* @see #STATISTICS_LENS_SHADING_MAP_MODE_OFF
* @see #STATISTICS_LENS_SHADING_MAP_MODE_ON
*/
@@ -2340,6 +2386,8 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>>
* <li>{@link #TONEMAP_MODE_CONTRAST_CURVE CONTRAST_CURVE}</li>
* <li>{@link #TONEMAP_MODE_FAST FAST}</li>
* <li>{@link #TONEMAP_MODE_HIGH_QUALITY HIGH_QUALITY}</li>
+ * <li>{@link #TONEMAP_MODE_GAMMA_VALUE GAMMA_VALUE}</li>
+ * <li>{@link #TONEMAP_MODE_PRESET_CURVE PRESET_CURVE}</li>
* </ul></p>
* <p><b>Available values for this device:</b><br>
* {@link CameraCharacteristics#TONEMAP_AVAILABLE_TONE_MAP_MODES android.tonemap.availableToneMapModes}</p>
@@ -2355,12 +2403,60 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>>
* @see #TONEMAP_MODE_CONTRAST_CURVE
* @see #TONEMAP_MODE_FAST
* @see #TONEMAP_MODE_HIGH_QUALITY
+ * @see #TONEMAP_MODE_GAMMA_VALUE
+ * @see #TONEMAP_MODE_PRESET_CURVE
*/
@PublicKey
public static final Key<Integer> TONEMAP_MODE =
new Key<Integer>("android.tonemap.mode", int.class);
/**
+ * <p>Tonemapping curve to use when {@link CaptureRequest#TONEMAP_MODE android.tonemap.mode} is
+ * GAMMA_VALUE</p>
+ * <p>The tonemap curve will be defined the following formula:
+ * * OUT = pow(IN, 1.0 / gamma)
+ * where IN and OUT is the input pixel value scaled to range [0.0, 1.0],
+ * pow is the power function and gamma is the gamma value specified by this
+ * key.</p>
+ * <p>The same curve will be applied to all color channels. The camera device
+ * may clip the input gamma value to its supported range. The actual applied
+ * value will be returned in capture result.</p>
+ * <p>The valid range of gamma value varies on different devices, but values
+ * within [1.0, 5.0] are guaranteed not to be clipped.</p>
+ * <p><b>Optional</b> - This value may be {@code null} on some devices.</p>
+ *
+ * @see CaptureRequest#TONEMAP_MODE
+ */
+ @PublicKey
+ public static final Key<Float> TONEMAP_GAMMA =
+ new Key<Float>("android.tonemap.gamma", float.class);
+
+ /**
+ * <p>Tonemapping curve to use when {@link CaptureRequest#TONEMAP_MODE android.tonemap.mode} is
+ * PRESET_CURVE</p>
+ * <p>The tonemap curve will be defined by specified standard.</p>
+ * <p>sRGB (approximated by 16 control points):</p>
+ * <p><img alt="sRGB tonemapping curve" src="../../../../images/camera2/metadata/android.tonemap.curveRed/srgb_tonemap.png" /></p>
+ * <p>Rec. 709 (approximated by 16 control points):</p>
+ * <p><img alt="Rec. 709 tonemapping curve" src="../../../../images/camera2/metadata/android.tonemap.curveRed/rec709_tonemap.png" /></p>
+ * <p>Note that above figures show a 16 control points approximation of preset
+ * curves. Camera devices may apply a different approximation to the curve.</p>
+ * <p><b>Possible values:</b>
+ * <ul>
+ * <li>{@link #TONEMAP_PRESET_CURVE_SRGB SRGB}</li>
+ * <li>{@link #TONEMAP_PRESET_CURVE_REC709 REC709}</li>
+ * </ul></p>
+ * <p><b>Optional</b> - This value may be {@code null} on some devices.</p>
+ *
+ * @see CaptureRequest#TONEMAP_MODE
+ * @see #TONEMAP_PRESET_CURVE_SRGB
+ * @see #TONEMAP_PRESET_CURVE_REC709
+ */
+ @PublicKey
+ public static final Key<Integer> TONEMAP_PRESET_CURVE =
+ new Key<Integer>("android.tonemap.presetCurve", int.class);
+
+ /**
* <p>This LED is nominally used to indicate to the user
* that the camera is powered on and may be streaming images back to the
* Application Processor. In certain rare circumstances, the OS may
@@ -2426,6 +2522,52 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>>
public static final Key<Boolean> BLACK_LEVEL_LOCK =
new Key<Boolean>("android.blackLevel.lock", boolean.class);
+ /**
+ * <p>The amount of exposure time increase factor applied to the original output
+ * frame by the application processing before sending for reprocessing.</p>
+ * <p>This is optional, and will be supported if the camera device supports YUV_REPROCESSING
+ * capability ({@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES android.request.availableCapabilities} contains YUV_REPROCESSING).</p>
+ * <p>For some YUV reprocessing use cases, the application may choose to filter the original
+ * output frames to effectively reduce the noise to the same level as a frame that was
+ * captured with longer exposure time. To be more specific, assuming the original captured
+ * images were captured with a sensitivity of S and an exposure time of T, the model in
+ * the camera device is that the amount of noise in the image would be approximately what
+ * would be expected if the original capture parameters had been a sensitivity of
+ * S/effectiveExposureFactor and an exposure time of T*effectiveExposureFactor, rather
+ * than S and T respectively. If the captured images were processed by the application
+ * before being sent for reprocessing, then the application may have used image processing
+ * algorithms and/or multi-frame image fusion to reduce the noise in the
+ * application-processed images (input images). By using the effectiveExposureFactor
+ * control, the application can communicate to the camera device the actual noise level
+ * improvement in the application-processed image. With this information, the camera
+ * device can select appropriate noise reduction and edge enhancement parameters to avoid
+ * excessive noise reduction ({@link CaptureRequest#NOISE_REDUCTION_MODE android.noiseReduction.mode}) and insufficient edge
+ * enhancement ({@link CaptureRequest#EDGE_MODE android.edge.mode}) being applied to the reprocessed frames.</p>
+ * <p>For example, for multi-frame image fusion use case, the application may fuse
+ * multiple output frames together to a final frame for reprocessing. When N image are
+ * fused into 1 image for reprocessing, the exposure time increase factor could be up to
+ * square root of N (based on a simple photon shot noise model). The camera device will
+ * adjust the reprocessing noise reduction and edge enhancement parameters accordingly to
+ * produce the best quality images.</p>
+ * <p>This is relative factor, 1.0 indicates the application hasn't processed the input
+ * buffer in a way that affects its effective exposure time.</p>
+ * <p>This control is only effective for YUV reprocessing capture request. For noise
+ * reduction reprocessing, it is only effective when <code>{@link CaptureRequest#NOISE_REDUCTION_MODE android.noiseReduction.mode} != OFF</code>.
+ * Similarly, for edge enhancement reprocessing, it is only effective when
+ * <code>{@link CaptureRequest#EDGE_MODE android.edge.mode} != OFF</code>.</p>
+ * <p><b>Units</b>: Relative exposure time increase factor.</p>
+ * <p><b>Range of valid values:</b><br>
+ * &gt;= 1.0</p>
+ * <p><b>Optional</b> - This value may be {@code null} on some devices.</p>
+ *
+ * @see CaptureRequest#EDGE_MODE
+ * @see CaptureRequest#NOISE_REDUCTION_MODE
+ * @see CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES
+ */
+ @PublicKey
+ public static final Key<Float> REPROCESS_EFFECTIVE_EXPOSURE_FACTOR =
+ new Key<Float>("android.reprocess.effectiveExposureFactor", float.class);
+
/*~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~
* End generated code
*~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~O@*/
diff --git a/core/java/android/hardware/camera2/CaptureResult.java b/core/java/android/hardware/camera2/CaptureResult.java
index f17100d..b84dc2e 100644
--- a/core/java/android/hardware/camera2/CaptureResult.java
+++ b/core/java/android/hardware/camera2/CaptureResult.java
@@ -403,6 +403,8 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> {
* in this matrix result metadata. The transform should keep the magnitude
* of the output color values within <code>[0, 1.0]</code> (assuming input color
* values is within the normalized range <code>[0, 1.0]</code>), or clipping may occur.</p>
+ * <p>The valid range of each matrix element varies on different devices, but
+ * values within [-1.5, 3.0] are guaranteed not to be clipped.</p>
* <p><b>Units</b>: Unitless scale factors</p>
* <p><b>Optional</b> - This value may be {@code null} on some devices.</p>
* <p><b>Full capability</b> -
@@ -426,6 +428,10 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> {
* TRANSFORM_MATRIX.</p>
* <p>The gains in the result metadata are the gains actually
* applied by the camera device to the current frame.</p>
+ * <p>The valid range of gains varies on different devices, but gains
+ * between [1.0, 3.0] are guaranteed not to be clipped. Even if a given
+ * device allows gains below 1.0, this is usually not recommended because
+ * this can create color artifacts.</p>
* <p><b>Units</b>: Unitless gain factors</p>
* <p><b>Optional</b> - This value may be {@code null} on some devices.</p>
* <p><b>Full capability</b> -
@@ -575,7 +581,14 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> {
* ({@link CaptureRequest#SENSOR_EXPOSURE_TIME android.sensor.exposureTime}) and sensitivity ({@link CaptureRequest#SENSOR_SENSITIVITY android.sensor.sensitivity})
* parameters. The flash may be fired if the {@link CaptureRequest#CONTROL_AE_MODE android.control.aeMode}
* is ON_AUTO_FLASH/ON_AUTO_FLASH_REDEYE and the scene is too dark. If the
- * {@link CaptureRequest#CONTROL_AE_MODE android.control.aeMode} is ON_ALWAYS_FLASH, the scene may become overexposed.</p>
+ * {@link CaptureRequest#CONTROL_AE_MODE android.control.aeMode} is ON_ALWAYS_FLASH, the scene may become overexposed.
+ * Similarly, AE precapture trigger CANCEL has no effect when AE is already locked.</p>
+ * <p>When an AE precapture sequence is triggered, AE unlock will not be able to unlock
+ * the AE if AE is locked by the camera device internally during precapture metering
+ * sequence In other words, submitting requests with AE unlock has no effect for an
+ * ongoing precapture metering sequence. Otherwise, the precapture metering sequence
+ * will never succeed in a sequence of preview requests where AE lock is always set
+ * to <code>false</code>.</p>
* <p>Since the camera device has a pipeline of in-flight requests, the settings that
* get locked do not necessarily correspond to the settings that were present in the
* latest capture result received from the camera device, since additional captures
@@ -720,6 +733,11 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> {
* included at all in the request settings. When included and
* set to START, the camera device will trigger the auto-exposure (AE)
* precapture metering sequence.</p>
+ * <p>When set to CANCEL, the camera device will cancel any active
+ * precapture metering trigger, and return to its initial AE state.
+ * If a precapture metering sequence is already completed, and the camera
+ * device has implicitly locked the AE for subsequent still capture, the
+ * CANCEL trigger will unlock the AE and return to its initial AE state.</p>
* <p>The precapture sequence should be triggered before starting a
* high-quality still capture for final metering decisions to
* be made, and for firing pre-capture flash pulses to estimate
@@ -735,7 +753,11 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> {
* submitted. To ensure that the AE routine restarts normal scan, the application should
* submit a request with <code>{@link CaptureRequest#CONTROL_AE_LOCK android.control.aeLock} == true</code>, followed by a request
* with <code>{@link CaptureRequest#CONTROL_AE_LOCK android.control.aeLock} == false</code>, if the application decides not to submit a
- * still capture request after the precapture sequence completes.</p>
+ * still capture request after the precapture sequence completes. Alternatively, for
+ * API level 23 or newer devices, the CANCEL can be used to unlock the camera device
+ * internally locked AE if the application doesn't submit a still capture request after
+ * the AE precapture trigger. Note that, the CANCEL was added in API level 23, and must not
+ * be used in devices that have earlier API levels.</p>
* <p>The exact effect of auto-exposure (AE) precapture trigger
* depends on the current AE mode and state; see
* {@link CaptureResult#CONTROL_AE_STATE android.control.aeState} for AE precapture state transition
@@ -748,6 +770,7 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> {
* <ul>
* <li>{@link #CONTROL_AE_PRECAPTURE_TRIGGER_IDLE IDLE}</li>
* <li>{@link #CONTROL_AE_PRECAPTURE_TRIGGER_START START}</li>
+ * <li>{@link #CONTROL_AE_PRECAPTURE_TRIGGER_CANCEL CANCEL}</li>
* </ul></p>
* <p><b>Optional</b> - This value may be {@code null} on some devices.</p>
* <p><b>Limited capability</b> -
@@ -760,6 +783,7 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> {
* @see CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL
* @see #CONTROL_AE_PRECAPTURE_TRIGGER_IDLE
* @see #CONTROL_AE_PRECAPTURE_TRIGGER_START
+ * @see #CONTROL_AE_PRECAPTURE_TRIGGER_CANCEL
*/
@PublicKey
public static final Key<Integer> CONTROL_AE_PRECAPTURE_TRIGGER =
@@ -892,11 +916,29 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> {
* <td align="center">Ready for high-quality capture</td>
* </tr>
* <tr>
- * <td align="center">Any state</td>
+ * <td align="center">LOCKED</td>
+ * <td align="center">aeLock is ON and aePrecaptureTrigger is START</td>
+ * <td align="center">LOCKED</td>
+ * <td align="center">Precapture trigger is ignored when AE is already locked</td>
+ * </tr>
+ * <tr>
+ * <td align="center">LOCKED</td>
+ * <td align="center">aeLock is ON and aePrecaptureTrigger is CANCEL</td>
+ * <td align="center">LOCKED</td>
+ * <td align="center">Precapture trigger is ignored when AE is already locked</td>
+ * </tr>
+ * <tr>
+ * <td align="center">Any state (excluding LOCKED)</td>
* <td align="center">{@link CaptureRequest#CONTROL_AE_PRECAPTURE_TRIGGER android.control.aePrecaptureTrigger} is START</td>
* <td align="center">PRECAPTURE</td>
* <td align="center">Start AE precapture metering sequence</td>
* </tr>
+ * <tr>
+ * <td align="center">Any state (excluding LOCKED)</td>
+ * <td align="center">{@link CaptureRequest#CONTROL_AE_PRECAPTURE_TRIGGER android.control.aePrecaptureTrigger} is CANCEL</td>
+ * <td align="center">INACTIVE</td>
+ * <td align="center">Currently active precapture metering sequence is canceled</td>
+ * </tr>
* </tbody>
* </table>
* <p>For the above table, the camera device may skip reporting any state changes that happen
@@ -922,18 +964,30 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> {
* <td align="center">Values are already good, transient states are skipped by camera device.</td>
* </tr>
* <tr>
- * <td align="center">Any state</td>
+ * <td align="center">Any state (excluding LOCKED)</td>
* <td align="center">{@link CaptureRequest#CONTROL_AE_PRECAPTURE_TRIGGER android.control.aePrecaptureTrigger} is START, sequence done</td>
* <td align="center">FLASH_REQUIRED</td>
* <td align="center">Converged but too dark w/o flash after a precapture sequence, transient states are skipped by camera device.</td>
* </tr>
* <tr>
- * <td align="center">Any state</td>
+ * <td align="center">Any state (excluding LOCKED)</td>
* <td align="center">{@link CaptureRequest#CONTROL_AE_PRECAPTURE_TRIGGER android.control.aePrecaptureTrigger} is START, sequence done</td>
* <td align="center">CONVERGED</td>
* <td align="center">Converged after a precapture sequence, transient states are skipped by camera device.</td>
* </tr>
* <tr>
+ * <td align="center">Any state (excluding LOCKED)</td>
+ * <td align="center">{@link CaptureRequest#CONTROL_AE_PRECAPTURE_TRIGGER android.control.aePrecaptureTrigger} is CANCEL, converged</td>
+ * <td align="center">FLASH_REQUIRED</td>
+ * <td align="center">Converged but too dark w/o flash after a precapture sequence is canceled, transient states are skipped by camera device.</td>
+ * </tr>
+ * <tr>
+ * <td align="center">Any state (excluding LOCKED)</td>
+ * <td align="center">{@link CaptureRequest#CONTROL_AE_PRECAPTURE_TRIGGER android.control.aePrecaptureTrigger} is CANCEL, converged</td>
+ * <td align="center">CONVERGED</td>
+ * <td align="center">Converged after a precapture sequenceis canceled, transient states are skipped by camera device.</td>
+ * </tr>
+ * <tr>
* <td align="center">CONVERGED</td>
* <td align="center">Camera device finished AE scan</td>
* <td align="center">FLASH_REQUIRED</td>
@@ -1637,7 +1691,7 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> {
* <p>This control (except for MANUAL) is only effective if
* <code>{@link CaptureRequest#CONTROL_MODE android.control.mode} != OFF</code> and any 3A routine is active.</p>
* <p>ZERO_SHUTTER_LAG will be supported if {@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES android.request.availableCapabilities}
- * contains ZSL. MANUAL will be supported if {@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES android.request.availableCapabilities}
+ * contains OPAQUE_REPROCESSING. MANUAL will be supported if {@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES android.request.availableCapabilities}
* contains MANUAL_SENSOR. Other intent values are always supported.</p>
* <p><b>Possible values:</b>
* <ul>
@@ -1865,10 +1919,6 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> {
* update, as if this frame is never captured. This mode can be used in the scenario
* where the application doesn't want a 3A manual control capture to affect
* the subsequent auto 3A capture results.</p>
- * <p>LEGACY mode devices will only support AUTO and USE_SCENE_MODE modes.
- * LIMITED mode devices will only support OFF and OFF_KEEP_STATE if they
- * support the MANUAL_SENSOR and MANUAL_POST_PROCSESING capabilities.
- * FULL mode devices will always support OFF and OFF_KEEP_STATE.</p>
* <p><b>Possible values:</b>
* <ul>
* <li>{@link #CONTROL_MODE_OFF OFF}</li>
@@ -1876,9 +1926,12 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> {
* <li>{@link #CONTROL_MODE_USE_SCENE_MODE USE_SCENE_MODE}</li>
* <li>{@link #CONTROL_MODE_OFF_KEEP_STATE OFF_KEEP_STATE}</li>
* </ul></p>
+ * <p><b>Available values for this device:</b><br>
+ * {@link CameraCharacteristics#CONTROL_AVAILABLE_MODES android.control.availableModes}</p>
* <p>This key is available on all devices.</p>
*
* @see CaptureRequest#CONTROL_AF_MODE
+ * @see CameraCharacteristics#CONTROL_AVAILABLE_MODES
* @see #CONTROL_MODE_OFF
* @see #CONTROL_MODE_AUTO
* @see #CONTROL_MODE_USE_SCENE_MODE
@@ -1998,6 +2051,10 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> {
* camera device will use the highest-quality enhancement algorithms,
* even if it slows down capture rate. FAST means the camera device will
* not slow down capture rate when applying edge enhancement.</p>
+ * <p>For YUV_REPROCESSING, these FAST/HIGH_QUALITY modes both mean that the camera
+ * device will apply FAST/HIGH_QUALITY YUV-domain edge enhancement, respectively.
+ * The camera device may adjust its internal noise reduction parameters for best
+ * image quality based on the {@link CaptureRequest#REPROCESS_EFFECTIVE_EXPOSURE_FACTOR android.reprocess.effectiveExposureFactor}, if it is set.</p>
* <p><b>Possible values:</b>
* <ul>
* <li>{@link #EDGE_MODE_OFF OFF}</li>
@@ -2013,6 +2070,7 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> {
*
* @see CameraCharacteristics#EDGE_AVAILABLE_EDGE_MODES
* @see CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL
+ * @see CaptureRequest#REPROCESS_EFFECTIVE_EXPOSURE_FACTOR
* @see #EDGE_MODE_OFF
* @see #EDGE_MODE_FAST
* @see #EDGE_MODE_HIGH_QUALITY
@@ -2475,18 +2533,28 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> {
/**
* <p>Mode of operation for the noise reduction algorithm.</p>
* <p>The noise reduction algorithm attempts to improve image quality by removing
- * excessive noise added by the capture process, especially in dark conditions.
- * OFF means no noise reduction will be applied by the camera device.</p>
+ * excessive noise added by the capture process, especially in dark conditions.</p>
+ * <p>OFF means no noise reduction will be applied by the camera device, for both raw and
+ * YUV domain.</p>
+ * <p>MINIMAL means that only sensor raw domain basic noise reduction is enabled ,to remove
+ * demosaicing or other processing artifacts. For YUV_REPROCESSING, MINIMAL is same as OFF.
+ * This mode is optional, may not be support by all devices. The application should check
+ * {@link CameraCharacteristics#NOISE_REDUCTION_AVAILABLE_NOISE_REDUCTION_MODES android.noiseReduction.availableNoiseReductionModes} before using it.</p>
* <p>FAST/HIGH_QUALITY both mean camera device determined noise filtering
* will be applied. HIGH_QUALITY mode indicates that the camera device
* will use the highest-quality noise filtering algorithms,
* even if it slows down capture rate. FAST means the camera device will not
* slow down capture rate when applying noise filtering.</p>
+ * <p>For YUV_REPROCESSING, these FAST/HIGH_QUALITY modes both mean that the camera device
+ * will apply FAST/HIGH_QUALITY YUV domain noise reduction, respectively. The camera device
+ * may adjust the noise reduction parameters for best image quality based on the
+ * {@link CaptureRequest#REPROCESS_EFFECTIVE_EXPOSURE_FACTOR android.reprocess.effectiveExposureFactor} if it is set.</p>
* <p><b>Possible values:</b>
* <ul>
* <li>{@link #NOISE_REDUCTION_MODE_OFF OFF}</li>
* <li>{@link #NOISE_REDUCTION_MODE_FAST FAST}</li>
* <li>{@link #NOISE_REDUCTION_MODE_HIGH_QUALITY HIGH_QUALITY}</li>
+ * <li>{@link #NOISE_REDUCTION_MODE_MINIMAL MINIMAL}</li>
* </ul></p>
* <p><b>Available values for this device:</b><br>
* {@link CameraCharacteristics#NOISE_REDUCTION_AVAILABLE_NOISE_REDUCTION_MODES android.noiseReduction.availableNoiseReductionModes}</p>
@@ -2497,9 +2565,11 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> {
*
* @see CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL
* @see CameraCharacteristics#NOISE_REDUCTION_AVAILABLE_NOISE_REDUCTION_MODES
+ * @see CaptureRequest#REPROCESS_EFFECTIVE_EXPOSURE_FACTOR
* @see #NOISE_REDUCTION_MODE_OFF
* @see #NOISE_REDUCTION_MODE_FAST
* @see #NOISE_REDUCTION_MODE_HIGH_QUALITY
+ * @see #NOISE_REDUCTION_MODE_MINIMAL
*/
@PublicKey
public static final Key<Integer> NOISE_REDUCTION_MODE =
@@ -2984,6 +3054,8 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> {
* <li>{@link #SHADING_MODE_FAST FAST}</li>
* <li>{@link #SHADING_MODE_HIGH_QUALITY HIGH_QUALITY}</li>
* </ul></p>
+ * <p><b>Available values for this device:</b><br>
+ * {@link CameraCharacteristics#SHADING_AVAILABLE_MODES android.shading.availableModes}</p>
* <p><b>Optional</b> - This value may be {@code null} on some devices.</p>
* <p><b>Full capability</b> -
* Present on all camera devices that report being {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL_FULL HARDWARE_LEVEL_FULL} devices in the
@@ -2992,6 +3064,7 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> {
* @see CaptureRequest#CONTROL_AE_MODE
* @see CaptureRequest#CONTROL_AWB_MODE
* @see CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL
+ * @see CameraCharacteristics#SHADING_AVAILABLE_MODES
* @see CaptureResult#STATISTICS_LENS_SHADING_CORRECTION_MAP
* @see CaptureRequest#STATISTICS_LENS_SHADING_MAP_MODE
* @see #SHADING_MODE_OFF
@@ -3155,7 +3228,7 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> {
/**
* <p>The shading map is a low-resolution floating-point map
* that lists the coefficients used to correct for vignetting, for each
- * Bayer color channel.</p>
+ * Bayer color channel of RAW image data.</p>
* <p>The least shaded section of the image should have a gain factor
* of 1; all other sections should have gains above 1.</p>
* <p>When {@link CaptureRequest#COLOR_CORRECTION_MODE android.colorCorrection.mode} = TRANSFORM_MATRIX, the map
@@ -3191,8 +3264,20 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> {
* <img alt="Green (odd rows) lens shading map" src="../../../../images/camera2/metadata/android.statistics.lensShadingMap/green_o_shading.png" />
* <img alt="Blue lens shading map" src="../../../../images/camera2/metadata/android.statistics.lensShadingMap/blue_shading.png" /></p>
* <p>As a visualization only, inverting the full-color map to recover an
- * image of a gray wall (using bicubic interpolation for visual quality) as captured by the sensor gives:</p>
+ * image of a gray wall (using bicubic interpolation for visual quality)
+ * as captured by the sensor gives:</p>
* <p><img alt="Image of a uniform white wall (inverse shading map)" src="../../../../images/camera2/metadata/android.statistics.lensShadingMap/inv_shading.png" /></p>
+ * <p>Note that the RAW image data might be subject to lens shading
+ * correction not reported on this map. Query
+ * {@link CameraCharacteristics#SENSOR_INFO_LENS_SHADING_APPLIED android.sensor.info.lensShadingApplied} to see if RAW image data has subject
+ * to lens shading correction. If {@link CameraCharacteristics#SENSOR_INFO_LENS_SHADING_APPLIED android.sensor.info.lensShadingApplied}
+ * is TRUE, the RAW image data is subject to partial or full lens shading
+ * correction. In the case full lens shading correction is applied to RAW
+ * images, the gain factor map reported in this key will contain all 1.0 gains.
+ * In other words, the map reported in this key is the remaining lens shading
+ * that needs to be applied on the RAW image to get images without lens shading
+ * artifacts. See {@link CameraCharacteristics#REQUEST_MAX_NUM_OUTPUT_RAW android.request.maxNumOutputRaw} for a list of RAW image
+ * formats.</p>
* <p><b>Range of valid values:</b><br>
* Each gain factor is &gt;= 1</p>
* <p><b>Optional</b> - This value may be {@code null} on some devices.</p>
@@ -3202,6 +3287,8 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> {
*
* @see CaptureRequest#COLOR_CORRECTION_MODE
* @see CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL
+ * @see CameraCharacteristics#REQUEST_MAX_NUM_OUTPUT_RAW
+ * @see CameraCharacteristics#SENSOR_INFO_LENS_SHADING_APPLIED
* @hide
*/
public static final Key<float[]> STATISTICS_LENS_SHADING_MAP =
@@ -3339,12 +3426,15 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> {
* <li>{@link #STATISTICS_LENS_SHADING_MAP_MODE_OFF OFF}</li>
* <li>{@link #STATISTICS_LENS_SHADING_MAP_MODE_ON ON}</li>
* </ul></p>
+ * <p><b>Available values for this device:</b><br>
+ * {@link CameraCharacteristics#STATISTICS_INFO_AVAILABLE_LENS_SHADING_MAP_MODES android.statistics.info.availableLensShadingMapModes}</p>
* <p><b>Optional</b> - This value may be {@code null} on some devices.</p>
* <p><b>Full capability</b> -
* Present on all camera devices that report being {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL_FULL HARDWARE_LEVEL_FULL} devices in the
* {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL android.info.supportedHardwareLevel} key</p>
*
* @see CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL
+ * @see CameraCharacteristics#STATISTICS_INFO_AVAILABLE_LENS_SHADING_MAP_MODES
* @see #STATISTICS_LENS_SHADING_MAP_MODE_OFF
* @see #STATISTICS_LENS_SHADING_MAP_MODE_ON
*/
@@ -3531,6 +3621,8 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> {
* <li>{@link #TONEMAP_MODE_CONTRAST_CURVE CONTRAST_CURVE}</li>
* <li>{@link #TONEMAP_MODE_FAST FAST}</li>
* <li>{@link #TONEMAP_MODE_HIGH_QUALITY HIGH_QUALITY}</li>
+ * <li>{@link #TONEMAP_MODE_GAMMA_VALUE GAMMA_VALUE}</li>
+ * <li>{@link #TONEMAP_MODE_PRESET_CURVE PRESET_CURVE}</li>
* </ul></p>
* <p><b>Available values for this device:</b><br>
* {@link CameraCharacteristics#TONEMAP_AVAILABLE_TONE_MAP_MODES android.tonemap.availableToneMapModes}</p>
@@ -3546,12 +3638,60 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> {
* @see #TONEMAP_MODE_CONTRAST_CURVE
* @see #TONEMAP_MODE_FAST
* @see #TONEMAP_MODE_HIGH_QUALITY
+ * @see #TONEMAP_MODE_GAMMA_VALUE
+ * @see #TONEMAP_MODE_PRESET_CURVE
*/
@PublicKey
public static final Key<Integer> TONEMAP_MODE =
new Key<Integer>("android.tonemap.mode", int.class);
/**
+ * <p>Tonemapping curve to use when {@link CaptureRequest#TONEMAP_MODE android.tonemap.mode} is
+ * GAMMA_VALUE</p>
+ * <p>The tonemap curve will be defined the following formula:
+ * * OUT = pow(IN, 1.0 / gamma)
+ * where IN and OUT is the input pixel value scaled to range [0.0, 1.0],
+ * pow is the power function and gamma is the gamma value specified by this
+ * key.</p>
+ * <p>The same curve will be applied to all color channels. The camera device
+ * may clip the input gamma value to its supported range. The actual applied
+ * value will be returned in capture result.</p>
+ * <p>The valid range of gamma value varies on different devices, but values
+ * within [1.0, 5.0] are guaranteed not to be clipped.</p>
+ * <p><b>Optional</b> - This value may be {@code null} on some devices.</p>
+ *
+ * @see CaptureRequest#TONEMAP_MODE
+ */
+ @PublicKey
+ public static final Key<Float> TONEMAP_GAMMA =
+ new Key<Float>("android.tonemap.gamma", float.class);
+
+ /**
+ * <p>Tonemapping curve to use when {@link CaptureRequest#TONEMAP_MODE android.tonemap.mode} is
+ * PRESET_CURVE</p>
+ * <p>The tonemap curve will be defined by specified standard.</p>
+ * <p>sRGB (approximated by 16 control points):</p>
+ * <p><img alt="sRGB tonemapping curve" src="../../../../images/camera2/metadata/android.tonemap.curveRed/srgb_tonemap.png" /></p>
+ * <p>Rec. 709 (approximated by 16 control points):</p>
+ * <p><img alt="Rec. 709 tonemapping curve" src="../../../../images/camera2/metadata/android.tonemap.curveRed/rec709_tonemap.png" /></p>
+ * <p>Note that above figures show a 16 control points approximation of preset
+ * curves. Camera devices may apply a different approximation to the curve.</p>
+ * <p><b>Possible values:</b>
+ * <ul>
+ * <li>{@link #TONEMAP_PRESET_CURVE_SRGB SRGB}</li>
+ * <li>{@link #TONEMAP_PRESET_CURVE_REC709 REC709}</li>
+ * </ul></p>
+ * <p><b>Optional</b> - This value may be {@code null} on some devices.</p>
+ *
+ * @see CaptureRequest#TONEMAP_MODE
+ * @see #TONEMAP_PRESET_CURVE_SRGB
+ * @see #TONEMAP_PRESET_CURVE_REC709
+ */
+ @PublicKey
+ public static final Key<Integer> TONEMAP_PRESET_CURVE =
+ new Key<Integer>("android.tonemap.presetCurve", int.class);
+
+ /**
* <p>This LED is nominally used to indicate to the user
* that the camera is powered on and may be streaming images back to the
* Application Processor. In certain rare circumstances, the OS may
@@ -3657,6 +3797,52 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> {
public static final Key<Long> SYNC_FRAME_NUMBER =
new Key<Long>("android.sync.frameNumber", long.class);
+ /**
+ * <p>The amount of exposure time increase factor applied to the original output
+ * frame by the application processing before sending for reprocessing.</p>
+ * <p>This is optional, and will be supported if the camera device supports YUV_REPROCESSING
+ * capability ({@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES android.request.availableCapabilities} contains YUV_REPROCESSING).</p>
+ * <p>For some YUV reprocessing use cases, the application may choose to filter the original
+ * output frames to effectively reduce the noise to the same level as a frame that was
+ * captured with longer exposure time. To be more specific, assuming the original captured
+ * images were captured with a sensitivity of S and an exposure time of T, the model in
+ * the camera device is that the amount of noise in the image would be approximately what
+ * would be expected if the original capture parameters had been a sensitivity of
+ * S/effectiveExposureFactor and an exposure time of T*effectiveExposureFactor, rather
+ * than S and T respectively. If the captured images were processed by the application
+ * before being sent for reprocessing, then the application may have used image processing
+ * algorithms and/or multi-frame image fusion to reduce the noise in the
+ * application-processed images (input images). By using the effectiveExposureFactor
+ * control, the application can communicate to the camera device the actual noise level
+ * improvement in the application-processed image. With this information, the camera
+ * device can select appropriate noise reduction and edge enhancement parameters to avoid
+ * excessive noise reduction ({@link CaptureRequest#NOISE_REDUCTION_MODE android.noiseReduction.mode}) and insufficient edge
+ * enhancement ({@link CaptureRequest#EDGE_MODE android.edge.mode}) being applied to the reprocessed frames.</p>
+ * <p>For example, for multi-frame image fusion use case, the application may fuse
+ * multiple output frames together to a final frame for reprocessing. When N image are
+ * fused into 1 image for reprocessing, the exposure time increase factor could be up to
+ * square root of N (based on a simple photon shot noise model). The camera device will
+ * adjust the reprocessing noise reduction and edge enhancement parameters accordingly to
+ * produce the best quality images.</p>
+ * <p>This is relative factor, 1.0 indicates the application hasn't processed the input
+ * buffer in a way that affects its effective exposure time.</p>
+ * <p>This control is only effective for YUV reprocessing capture request. For noise
+ * reduction reprocessing, it is only effective when <code>{@link CaptureRequest#NOISE_REDUCTION_MODE android.noiseReduction.mode} != OFF</code>.
+ * Similarly, for edge enhancement reprocessing, it is only effective when
+ * <code>{@link CaptureRequest#EDGE_MODE android.edge.mode} != OFF</code>.</p>
+ * <p><b>Units</b>: Relative exposure time increase factor.</p>
+ * <p><b>Range of valid values:</b><br>
+ * &gt;= 1.0</p>
+ * <p><b>Optional</b> - This value may be {@code null} on some devices.</p>
+ *
+ * @see CaptureRequest#EDGE_MODE
+ * @see CaptureRequest#NOISE_REDUCTION_MODE
+ * @see CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES
+ */
+ @PublicKey
+ public static final Key<Float> REPROCESS_EFFECTIVE_EXPOSURE_FACTOR =
+ new Key<Float>("android.reprocess.effectiveExposureFactor", float.class);
+
/*~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~
* End generated code
*~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~O@*/
diff --git a/core/java/android/hardware/camera2/legacy/LegacyMetadataMapper.java b/core/java/android/hardware/camera2/legacy/LegacyMetadataMapper.java
index 347db05..802b938 100644
--- a/core/java/android/hardware/camera2/legacy/LegacyMetadataMapper.java
+++ b/core/java/android/hardware/camera2/legacy/LegacyMetadataMapper.java
@@ -474,6 +474,15 @@ public class LegacyMetadataMapper {
m.set(CONTROL_AE_COMPENSATION_STEP, ParamsUtils.createRational(step));
}
+
+ /*
+ * control.aeLockAvailable
+ */
+ {
+ boolean aeLockAvailable = p.isAutoExposureLockSupported();
+
+ m.set(CONTROL_AE_LOCK_AVAILABLE, aeLockAvailable);
+ }
}
@@ -571,6 +580,16 @@ public class LegacyMetadataMapper {
Log.v(TAG, "mapControlAwb - control.awbAvailableModes set to " +
ListUtils.listToString(awbAvail));
}
+
+
+ /*
+ * control.awbLockAvailable
+ */
+ {
+ boolean awbLockAvailable = p.isAutoWhiteBalanceLockSupported();
+
+ m.set(CONTROL_AWB_LOCK_AVAILABLE, awbLockAvailable);
+ }
}
}
@@ -618,17 +637,44 @@ public class LegacyMetadataMapper {
/*
* android.control.availableSceneModes
*/
+ int maxNumDetectedFaces = p.getMaxNumDetectedFaces();
List<String> sceneModes = p.getSupportedSceneModes();
List<Integer> supportedSceneModes =
ArrayUtils.convertStringListToIntList(sceneModes, sLegacySceneModes, sSceneModes);
- if (supportedSceneModes == null) { // camera1 doesn't support scene mode settings
- supportedSceneModes = new ArrayList<Integer>();
- supportedSceneModes.add(CONTROL_SCENE_MODE_DISABLED); // disabled is always available
+
+ // Special case where the only scene mode listed is AUTO => no scene mode
+ if (sceneModes != null && sceneModes.size() == 1 &&
+ sceneModes.get(0) == Parameters.SCENE_MODE_AUTO) {
+ supportedSceneModes = null;
}
- if (p.getMaxNumDetectedFaces() > 0) { // always supports FACE_PRIORITY when face detecting
- supportedSceneModes.add(CONTROL_SCENE_MODE_FACE_PRIORITY);
+
+ boolean sceneModeSupported = true;
+ if (supportedSceneModes == null && maxNumDetectedFaces == 0) {
+ sceneModeSupported = false;
}
- m.set(CONTROL_AVAILABLE_SCENE_MODES, ArrayUtils.toIntArray(supportedSceneModes));
+
+ if (sceneModeSupported) {
+ if (supportedSceneModes == null) {
+ supportedSceneModes = new ArrayList<Integer>();
+ }
+ if (maxNumDetectedFaces > 0) { // always supports FACE_PRIORITY when face detecting
+ supportedSceneModes.add(CONTROL_SCENE_MODE_FACE_PRIORITY);
+ }
+ // Remove all DISABLED occurrences
+ if (supportedSceneModes.contains(CONTROL_SCENE_MODE_DISABLED)) {
+ while(supportedSceneModes.remove(new Integer(CONTROL_SCENE_MODE_DISABLED))) {}
+ }
+ m.set(CONTROL_AVAILABLE_SCENE_MODES, ArrayUtils.toIntArray(supportedSceneModes));
+ } else {
+ m.set(CONTROL_AVAILABLE_SCENE_MODES, new int[] {CONTROL_SCENE_MODE_DISABLED});
+ }
+
+ /*
+ * android.control.availableModes
+ */
+ m.set(CONTROL_AVAILABLE_MODES, sceneModeSupported ?
+ new int[] { CONTROL_MODE_AUTO, CONTROL_MODE_USE_SCENE_MODE } :
+ new int[] { CONTROL_MODE_AUTO });
}
private static void mapLens(CameraMetadataNative m, Camera.Parameters p) {
diff --git a/core/java/android/hardware/camera2/legacy/RequestThreadManager.java b/core/java/android/hardware/camera2/legacy/RequestThreadManager.java
index f1f2f0c..cf3510d 100644
--- a/core/java/android/hardware/camera2/legacy/RequestThreadManager.java
+++ b/core/java/android/hardware/camera2/legacy/RequestThreadManager.java
@@ -85,7 +85,7 @@ public class RequestThreadManager {
private static final int PREVIEW_FRAME_TIMEOUT = 1000; // ms
private static final int JPEG_FRAME_TIMEOUT = 4000; // ms (same as CTS for API2)
- private static final int REQUEST_COMPLETE_TIMEOUT = JPEG_FRAME_TIMEOUT; // ms (same as JPEG timeout)
+ private static final int REQUEST_COMPLETE_TIMEOUT = JPEG_FRAME_TIMEOUT;
private static final float ASPECT_RATIO_TOLERANCE = 0.01f;
private boolean mPreviewRunning = false;
diff --git a/core/java/android/hardware/camera2/params/LensShadingMap.java b/core/java/android/hardware/camera2/params/LensShadingMap.java
index 9bbc33a..13929b1 100644
--- a/core/java/android/hardware/camera2/params/LensShadingMap.java
+++ b/core/java/android/hardware/camera2/params/LensShadingMap.java
@@ -238,6 +238,51 @@ public final class LensShadingMap {
return HashCodeHelpers.hashCode(mRows, mColumns, elemsHash);
}
+ /**
+ * Return the LensShadingMap as a string representation.
+ *
+ * <p> {@code "LensShadingMap{R:([%f, %f, ... %f], ... [%f, %f, ... %f]), G_even:([%f, %f, ...
+ * %f], ... [%f, %f, ... %f]), G_odd:([%f, %f, ... %f], ... [%f, %f, ... %f]), B:([%f, %f, ...
+ * %f], ... [%f, %f, ... %f])}"},
+ * where each {@code %f} represents one gain factor and each {@code [%f, %f, ... %f]} represents
+ * a row of the lens shading map</p>
+ *
+ * @return string representation of {@link LensShadingMap}
+ */
+ @Override
+ public String toString() {
+ StringBuilder str = new StringBuilder();
+ str.append("LensShadingMap{");
+
+ final String channelPrefix[] = {"R:(", "G_even:(", "G_odd:(", "B:("};
+
+ for (int ch = 0; ch < COUNT; ch++) {
+ str.append(channelPrefix[ch]);
+
+ for (int r = 0; r < mRows; r++) {
+ str.append("[");
+ for (int c = 0; c < mColumns; c++) {
+ float gain = getGainFactor(ch, c, r);
+ str.append(gain);
+ if (c < mColumns - 1) {
+ str.append(", ");
+ }
+ }
+ str.append("]");
+ if (r < mRows - 1) {
+ str.append(", ");
+ }
+ }
+
+ str.append(")");
+ if (ch < COUNT - 1) {
+ str.append(", ");
+ }
+ }
+
+ str.append("}");
+ return str.toString();
+ }
private final int mRows;
private final int mColumns;
diff --git a/core/java/android/hardware/usb/UsbDevice.java b/core/java/android/hardware/usb/UsbDevice.java
index d90e06e..1a42319 100644
--- a/core/java/android/hardware/usb/UsbDevice.java
+++ b/core/java/android/hardware/usb/UsbDevice.java
@@ -40,6 +40,7 @@ import android.os.Parcelable;
public class UsbDevice implements Parcelable {
private static final String TAG = "UsbDevice";
+ private static final boolean DEBUG = false;
private final String mName;
private final String mManufacturerName;
diff --git a/core/java/android/hardware/usb/UsbManager.java b/core/java/android/hardware/usb/UsbManager.java
index f64ef87..f283051 100644
--- a/core/java/android/hardware/usb/UsbManager.java
+++ b/core/java/android/hardware/usb/UsbManager.java
@@ -68,6 +68,8 @@ public class UsbManager {
* accessory function is enabled
* <li> {@link #USB_FUNCTION_AUDIO_SOURCE} boolean extra indicating whether the
* audio source function is enabled
+ * <li> {@link #USB_FUNCTION_MIDI} boolean extra indicating whether the
+ * MIDI function is enabled
* </ul>
*
* {@hide}
@@ -188,6 +190,14 @@ public class UsbManager {
public static final String USB_FUNCTION_AUDIO_SOURCE = "audio_source";
/**
+ * Name of the MIDI USB function.
+ * Used in extras for the {@link #ACTION_USB_STATE} broadcast
+ *
+ * {@hide}
+ */
+ public static final String USB_FUNCTION_MIDI = "midi";
+
+ /**
* Name of the Accessory USB function.
* Used in extras for the {@link #ACTION_USB_STATE} broadcast
*
diff --git a/core/java/android/midi/IMidiDeviceServer.aidl b/core/java/android/midi/IMidiDeviceServer.aidl
new file mode 100644
index 0000000..31fdbbb
--- /dev/null
+++ b/core/java/android/midi/IMidiDeviceServer.aidl
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.midi;
+
+import android.os.ParcelFileDescriptor;
+
+/** @hide */
+interface IMidiDeviceServer
+{
+ ParcelFileDescriptor openInputPort(int portNumber);
+ ParcelFileDescriptor openOutputPort(int portNumber);
+}
diff --git a/core/java/android/midi/IMidiListener.aidl b/core/java/android/midi/IMidiListener.aidl
new file mode 100644
index 0000000..b650593
--- /dev/null
+++ b/core/java/android/midi/IMidiListener.aidl
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.midi;
+
+import android.midi.MidiDeviceInfo;
+
+/** @hide */
+oneway interface IMidiListener
+{
+ void onDeviceAdded(in MidiDeviceInfo device);
+ void onDeviceRemoved(in MidiDeviceInfo device);
+}
diff --git a/core/java/android/midi/IMidiManager.aidl b/core/java/android/midi/IMidiManager.aidl
new file mode 100644
index 0000000..575b525
--- /dev/null
+++ b/core/java/android/midi/IMidiManager.aidl
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.midi;
+
+import android.midi.IMidiDeviceServer;
+import android.midi.IMidiListener;
+import android.midi.MidiDeviceInfo;
+import android.os.Bundle;
+import android.os.IBinder;
+
+/** @hide */
+interface IMidiManager
+{
+ MidiDeviceInfo[] getDeviceList();
+
+ // for device creation & removal notifications
+ void registerListener(IBinder token, in IMidiListener listener);
+ void unregisterListener(IBinder token, in IMidiListener listener);
+
+ // for communicating with MIDI devices
+ IMidiDeviceServer openDevice(IBinder token, in MidiDeviceInfo device);
+
+ // for implementing virtual MIDI devices
+ MidiDeviceInfo registerDeviceServer(in IMidiDeviceServer server, int numInputPorts,
+ int numOutputPorts, in Bundle properties, boolean isPrivate, int type);
+ void unregisterDeviceServer(in IMidiDeviceServer server);
+}
diff --git a/core/java/android/midi/MidiDevice.java b/core/java/android/midi/MidiDevice.java
new file mode 100644
index 0000000..b91aedf
--- /dev/null
+++ b/core/java/android/midi/MidiDevice.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.midi;
+
+import android.os.ParcelFileDescriptor;
+import android.os.RemoteException;
+import android.util.Log;
+
+import java.io.FileDescriptor;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+
+/**
+ * This class is used for sending and receiving data to and from an MIDI device
+ * Instances of this class are created by {@link MidiManager#openDevice}.
+ *
+ * CANDIDATE FOR PUBLIC API
+ * @hide
+ */
+public final class MidiDevice {
+ private static final String TAG = "MidiDevice";
+
+ private final MidiDeviceInfo mDeviceInfo;
+ private final IMidiDeviceServer mServer;
+
+ /**
+ * MidiDevice should only be instantiated by MidiManager
+ * @hide
+ */
+ public MidiDevice(MidiDeviceInfo deviceInfo, IMidiDeviceServer server) {
+ mDeviceInfo = deviceInfo;
+ mServer = server;
+ }
+
+ /**
+ * Returns a {@link MidiDeviceInfo} object, which describes this device.
+ *
+ * @return the {@link MidiDeviceInfo} object
+ */
+ public MidiDeviceInfo getInfo() {
+ return mDeviceInfo;
+ }
+
+ /**
+ * Called to open a {@link MidiInputPort} for the specified port number.
+ *
+ * @param portNumber the number of the input port to open
+ * @return the {@link MidiInputPort}
+ */
+ public MidiInputPort openInputPort(int portNumber) {
+ try {
+ ParcelFileDescriptor pfd = mServer.openInputPort(portNumber);
+ if (pfd == null) {
+ return null;
+ }
+ return new MidiInputPort(pfd, portNumber);
+ } catch (RemoteException e) {
+ Log.e(TAG, "RemoteException in openInputPort");
+ return null;
+ }
+ }
+
+ /**
+ * Called to open a {@link MidiOutputPort} for the specified port number.
+ *
+ * @param portNumber the number of the output port to open
+ * @return the {@link MidiOutputPort}
+ */
+ public MidiOutputPort openOutputPort(int portNumber) {
+ try {
+ ParcelFileDescriptor pfd = mServer.openOutputPort(portNumber);
+ if (pfd == null) {
+ return null;
+ }
+ return new MidiOutputPort(pfd, portNumber);
+ } catch (RemoteException e) {
+ Log.e(TAG, "RemoteException in openOutputPort");
+ return null;
+ }
+ }
+
+ @Override
+ public String toString() {
+ return ("MidiDevice: " + mDeviceInfo.toString());
+ }
+}
diff --git a/core/java/android/midi/MidiDeviceInfo.aidl b/core/java/android/midi/MidiDeviceInfo.aidl
new file mode 100644
index 0000000..59be059
--- /dev/null
+++ b/core/java/android/midi/MidiDeviceInfo.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2014, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.midi;
+
+parcelable MidiDeviceInfo;
diff --git a/core/java/android/midi/MidiDeviceInfo.java b/core/java/android/midi/MidiDeviceInfo.java
new file mode 100644
index 0000000..dde2669
--- /dev/null
+++ b/core/java/android/midi/MidiDeviceInfo.java
@@ -0,0 +1,204 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.midi;
+
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * This class contains information to describe a MIDI device.
+ * For now we only have information that can be retrieved easily for USB devices,
+ * but we will probably expand this in the future.
+ *
+ * This class is just an immutable object to encapsulate the MIDI device description.
+ * Use the MidiDevice class to actually communicate with devices.
+ *
+ * CANDIDATE FOR PUBLIC API
+ * @hide
+ */
+public class MidiDeviceInfo implements Parcelable {
+
+ private static final String TAG = "MidiDeviceInfo";
+
+ /**
+ * Constant representing USB MIDI devices for {@link #getType}
+ */
+ public static final int TYPE_USB = 1;
+
+ /**
+ * Constant representing virtual (software based) MIDI devices for {@link #getType}
+ */
+ public static final int TYPE_VIRTUAL = 2;
+
+ private final int mType; // USB or virtual
+ private final int mId; // unique ID generated by MidiService
+ private final int mInputPortCount;
+ private final int mOutputPortCount;
+ private final Bundle mProperties;
+
+ /**
+ * Bundle key for the device's manufacturer name property.
+ * Used with the {@link android.os.Bundle} returned by {@link #getProperties}.
+ * Matches the USB device manufacturer name string for USB MIDI devices.
+ */
+ public static final String PROPERTY_MANUFACTURER = "manufacturer";
+
+ /**
+ * Bundle key for the device's model name property.
+ * Used with the {@link android.os.Bundle} returned by {@link #getProperties}
+ * Matches the USB device product name string for USB MIDI devices.
+ */
+ public static final String PROPERTY_MODEL = "model";
+
+ /**
+ * Bundle key for the device's serial number property.
+ * Used with the {@link android.os.Bundle} returned by {@link #getProperties}
+ * Matches the USB device serial number for USB MIDI devices.
+ */
+ public static final String PROPERTY_SERIAL_NUMBER = "serial_number";
+
+ /**
+ * Bundle key for the device's {@link android.hardware.usb.UsbDevice}.
+ * Only set for USB MIDI devices.
+ * Used with the {@link android.os.Bundle} returned by {@link #getProperties}
+ */
+ public static final String PROPERTY_USB_DEVICE = "usb_device";
+
+ /**
+ * Bundle key for the device's ALSA card number.
+ * Only set for USB MIDI devices.
+ * Used with the {@link android.os.Bundle} returned by {@link #getProperties}
+ */
+ public static final String PROPERTY_ALSA_CARD = "alsa_card";
+
+ /**
+ * Bundle key for the device's ALSA device number.
+ * Only set for USB MIDI devices.
+ * Used with the {@link android.os.Bundle} returned by {@link #getProperties}
+ */
+ public static final String PROPERTY_ALSA_DEVICE = "alsa_device";
+
+ /**
+ * MidiDeviceInfo should only be instantiated by MidiService implementation
+ * @hide
+ */
+ public MidiDeviceInfo(int type, int id, int numInputPorts, int numOutputPorts,
+ Bundle properties) {
+ mType = type;
+ mId = id;
+ mInputPortCount = numInputPorts;
+ mOutputPortCount = numOutputPorts;
+ mProperties = properties;
+ }
+
+ /**
+ * Returns the type of the device.
+ *
+ * @return the device's type
+ */
+ public int getType() {
+ return mType;
+ }
+
+ /**
+ * Returns the ID of the device.
+ * This ID is generated by the MIDI service and is not persistent across device unplugs.
+ *
+ * @return the device's ID
+ */
+ public int getId() {
+ return mId;
+ }
+
+ /**
+ * Returns the device's number of input ports.
+ *
+ * @return the number of input ports
+ */
+ public int getInputPortCount() {
+ return mInputPortCount;
+ }
+
+ /**
+ * Returns the device's number of output ports.
+ *
+ * @return the number of output ports
+ */
+ public int getOutputPortCount() {
+ return mOutputPortCount;
+ }
+
+ /**
+ * Returns the {@link android.os.Bundle} containing the device's properties.
+ *
+ * @return the device's properties
+ */
+ public Bundle getProperties() {
+ return mProperties;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (o instanceof MidiDeviceInfo) {
+ return (((MidiDeviceInfo)o).mId == mId);
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public int hashCode() {
+ return mId;
+ }
+
+ @Override
+ public String toString() {
+ return ("MidiDeviceInfo[mType=" + mType +
+ ",mInputPortCount=" + mInputPortCount +
+ ",mOutputPortCount=" + mOutputPortCount +
+ ",mProperties=" + mProperties);
+ }
+
+ public static final Parcelable.Creator<MidiDeviceInfo> CREATOR =
+ new Parcelable.Creator<MidiDeviceInfo>() {
+ public MidiDeviceInfo createFromParcel(Parcel in) {
+ int type = in.readInt();
+ int id = in.readInt();
+ int inputPorts = in.readInt();
+ int outputPorts = in.readInt();
+ Bundle properties = in.readBundle();
+ return new MidiDeviceInfo(type, id, inputPorts, outputPorts, properties);
+ }
+
+ public MidiDeviceInfo[] newArray(int size) {
+ return new MidiDeviceInfo[size];
+ }
+ };
+
+ public int describeContents() {
+ return 0;
+ }
+
+ public void writeToParcel(Parcel parcel, int flags) {
+ parcel.writeInt(mType);
+ parcel.writeInt(mId);
+ parcel.writeInt(mInputPortCount);
+ parcel.writeInt(mOutputPortCount);
+ parcel.writeBundle(mProperties);
+ }
+}
diff --git a/core/java/android/midi/MidiDeviceServer.java b/core/java/android/midi/MidiDeviceServer.java
new file mode 100644
index 0000000..7499934
--- /dev/null
+++ b/core/java/android/midi/MidiDeviceServer.java
@@ -0,0 +1,268 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.midi;
+
+import android.os.ParcelFileDescriptor;
+import android.os.RemoteException;
+import android.system.OsConstants;
+import android.util.Log;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.util.ArrayList;
+
+/**
+ * This class is used to provide the implemention of MIDI device.
+ * Applications may call {@link MidiManager#createDeviceServer}
+ * to create an instance of this class to implement a virtual MIDI device.
+ *
+ * CANDIDATE FOR PUBLIC API
+ * @hide
+ */
+public final class MidiDeviceServer implements Closeable {
+ private static final String TAG = "MidiDeviceServer";
+
+ private final IMidiManager mMidiManager;
+
+ // MidiDeviceInfo for the device implemented by this server
+ private MidiDeviceInfo mDeviceInfo;
+ private int mInputPortCount;
+ private int mOutputPortCount;
+
+ // output ports for receiving messages from our clients
+ // we can have only one per port number
+ private MidiOutputPort[] mInputPortSenders;
+
+ // receivers attached to our input ports
+ private ArrayList<MidiReceiver>[] mInputPortReceivers;
+
+ // input ports for sending messages to our clients
+ // we can have multiple outputs per port number
+ private ArrayList<MidiInputPort>[] mOutputPortReceivers;
+
+ // subclass of MidiInputPort for passing to clients
+ // that notifies us when the connection has failed
+ private class ServerInputPort extends MidiInputPort {
+ ServerInputPort(ParcelFileDescriptor pfd, int portNumber) {
+ super(pfd, portNumber);
+ }
+
+ @Override
+ public void onIOException() {
+ synchronized (mOutputPortReceivers) {
+ mOutputPortReceivers[getPortNumber()].clear();
+ }
+ }
+ }
+
+ // subclass of MidiOutputPort for passing to clients
+ // that notifies us when the connection has failed
+ private class ServerOutputPort extends MidiOutputPort {
+ ServerOutputPort(ParcelFileDescriptor pfd, int portNumber) {
+ super(pfd, portNumber);
+ }
+
+ @Override
+ public void onIOException() {
+ synchronized (mInputPortSenders) {
+ mInputPortSenders[getPortNumber()] = null;
+ }
+ }
+ }
+
+ // Binder interface stub for receiving connection requests from clients
+ private final IMidiDeviceServer mServer = new IMidiDeviceServer.Stub() {
+
+ @Override
+ public ParcelFileDescriptor openInputPort(int portNumber) {
+ if (portNumber < 0 || portNumber >= mInputPortCount) {
+ Log.e(TAG, "portNumber out of range in openInputPort: " + portNumber);
+ return null;
+ }
+
+ ParcelFileDescriptor result = null;
+
+ synchronized (mInputPortSenders) {
+ if (mInputPortSenders[portNumber] != null) {
+ Log.d(TAG, "port " + portNumber + " already open");
+ return null;
+ }
+
+ try {
+ ParcelFileDescriptor[] pair = ParcelFileDescriptor.createSocketPair(
+ OsConstants.SOCK_SEQPACKET);
+ MidiOutputPort newOutputPort = new ServerOutputPort(pair[0], portNumber);
+ mInputPortSenders[portNumber] = newOutputPort;
+ result = pair[1];
+
+ ArrayList<MidiReceiver> receivers = mInputPortReceivers[portNumber];
+ synchronized (receivers) {
+ for (int i = 0; i < receivers.size(); i++) {
+ newOutputPort.connect(receivers.get(i));
+ }
+ }
+ } catch (IOException e) {
+ Log.e(TAG, "unable to create ParcelFileDescriptors in openInputPort");
+ return null;
+ }
+ }
+
+ return result;
+ }
+
+ @Override
+ public ParcelFileDescriptor openOutputPort(int portNumber) {
+ if (portNumber < 0 || portNumber >= mOutputPortCount) {
+ Log.e(TAG, "portNumber out of range in openOutputPort: " + portNumber);
+ return null;
+ }
+ synchronized (mOutputPortReceivers) {
+ try {
+ ParcelFileDescriptor[] pair = ParcelFileDescriptor.createSocketPair(
+ OsConstants.SOCK_SEQPACKET);
+ mOutputPortReceivers[portNumber].add(new ServerInputPort(pair[0], portNumber));
+ return pair[1];
+ } catch (IOException e) {
+ Log.e(TAG, "unable to create ParcelFileDescriptors in openOutputPort");
+ return null;
+ }
+ }
+ }
+ };
+
+ /* package */ MidiDeviceServer(IMidiManager midiManager) {
+ mMidiManager = midiManager;
+ }
+
+ /* package */ IMidiDeviceServer getBinderInterface() {
+ return mServer;
+ }
+
+ /* package */ void setDeviceInfo(MidiDeviceInfo deviceInfo) {
+ if (mDeviceInfo != null) {
+ throw new IllegalStateException("setDeviceInfo should only be called once");
+ }
+ mDeviceInfo = deviceInfo;
+ mInputPortCount = deviceInfo.getInputPortCount();
+ mOutputPortCount = deviceInfo.getOutputPortCount();
+ mInputPortSenders = new MidiOutputPort[mInputPortCount];
+
+ mInputPortReceivers = new ArrayList[mInputPortCount];
+ for (int i = 0; i < mInputPortCount; i++) {
+ mInputPortReceivers[i] = new ArrayList<MidiReceiver>();
+ }
+
+ mOutputPortReceivers = new ArrayList[mOutputPortCount];
+ for (int i = 0; i < mOutputPortCount; i++) {
+ mOutputPortReceivers[i] = new ArrayList<MidiInputPort>();
+ }
+ }
+
+ @Override
+ public void close() throws IOException {
+ try {
+ // FIXME - close input and output ports too?
+ mMidiManager.unregisterDeviceServer(mServer);
+ } catch (RemoteException e) {
+ Log.e(TAG, "RemoteException in unregisterDeviceServer");
+ }
+ }
+
+ /**
+ * Returns a {@link MidiDeviceInfo} object, which describes this device.
+ *
+ * @return the {@link MidiDeviceInfo} object
+ */
+ public MidiDeviceInfo getInfo() {
+ return mDeviceInfo;
+ }
+
+ /**
+ * Called to open a {@link MidiSender} to allow receiving MIDI messages
+ * on the device's input port for the specified port number.
+ *
+ * @param portNumber the number of the input port
+ * @return the {@link MidiSender}
+ */
+ public MidiSender openInputPortSender(int portNumber) {
+ if (portNumber < 0 || portNumber >= mDeviceInfo.getInputPortCount()) {
+ throw new IllegalArgumentException("portNumber " + portNumber + " out of range");
+ }
+ final int portNumberF = portNumber;
+ return new MidiSender() {
+
+ @Override
+ public void connect(MidiReceiver receiver) {
+ // We always synchronize on mInputPortSenders before receivers if we need to
+ // synchronize on both.
+ synchronized (mInputPortSenders) {
+ ArrayList<MidiReceiver> receivers = mInputPortReceivers[portNumberF];
+ synchronized (receivers) {
+ receivers.add(receiver);
+ MidiOutputPort outputPort = mInputPortSenders[portNumberF];
+ if (outputPort != null) {
+ outputPort.connect(receiver);
+ }
+ }
+ }
+ }
+
+ @Override
+ public void disconnect(MidiReceiver receiver) {
+ // We always synchronize on mInputPortSenders before receivers if we need to
+ // synchronize on both.
+ synchronized (mInputPortSenders) {
+ ArrayList<MidiReceiver> receivers = mInputPortReceivers[portNumberF];
+ synchronized (receivers) {
+ receivers.remove(receiver);
+ MidiOutputPort outputPort = mInputPortSenders[portNumberF];
+ if (outputPort != null) {
+ outputPort.disconnect(receiver);
+ }
+ }
+ }
+ }
+ };
+ }
+
+ /**
+ * Called to open a {@link MidiReceiver} to allow sending MIDI messages
+ * on the virtual device's output port for the specified port number.
+ *
+ * @param portNumber the number of the output port
+ * @return the {@link MidiReceiver}
+ */
+ public MidiReceiver openOutputPortReceiver(int portNumber) {
+ if (portNumber < 0 || portNumber >= mDeviceInfo.getOutputPortCount()) {
+ throw new IllegalArgumentException("portNumber " + portNumber + " out of range");
+ }
+ final int portNumberF = portNumber;
+ return new MidiReceiver() {
+
+ @Override
+ public void onPost(byte[] msg, int offset, int count, long timestamp) throws IOException {
+ ArrayList<MidiInputPort> receivers = mOutputPortReceivers[portNumberF];
+ synchronized (receivers) {
+ for (int i = 0; i < receivers.size(); i++) {
+ // FIXME catch errors and remove dead ones
+ receivers.get(i).onPost(msg, offset, count, timestamp);
+ }
+ }
+ }
+ };
+ }
+}
diff --git a/core/java/android/midi/MidiInputPort.java b/core/java/android/midi/MidiInputPort.java
new file mode 100644
index 0000000..51c47dd
--- /dev/null
+++ b/core/java/android/midi/MidiInputPort.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.midi;
+
+import android.os.ParcelFileDescriptor;
+
+import libcore.io.IoUtils;
+
+import java.io.FileOutputStream;
+import java.io.IOException;
+
+/**
+ * This class is used for sending data to a port on a MIDI device
+ *
+ * CANDIDATE FOR PUBLIC API
+ * @hide
+ */
+public class MidiInputPort extends MidiPort implements MidiReceiver {
+
+ private final FileOutputStream mOutputStream;
+
+ // buffer to use for sending messages out our output stream
+ private final byte[] mBuffer = new byte[MAX_PACKET_SIZE];
+
+ /* package */ MidiInputPort(ParcelFileDescriptor pfd, int portNumber) {
+ super(portNumber);
+ mOutputStream = new ParcelFileDescriptor.AutoCloseOutputStream(pfd);
+ }
+
+ /**
+ * Writes a MIDI message to the input port
+ *
+ * @param msg byte array containing the message
+ * @param offset offset of first byte of the message in msg byte array
+ * @param count size of the message in bytes
+ * @param timestamp future time to post the message (based on
+ * {@link java.lang.System#nanoTime}
+ */
+ public void onPost(byte[] msg, int offset, int count, long timestamp) throws IOException {
+ assert(offset >= 0 && count >= 0 && offset + count <= msg.length);
+
+ synchronized (mBuffer) {
+ try {
+ while (count > 0) {
+ int length = packMessage(msg, offset, count, timestamp, mBuffer);
+ mOutputStream.write(mBuffer, 0, length);
+ int sent = getMessageSize(mBuffer, length);
+ assert(sent >= 0 && sent <= length);
+
+ offset += sent;
+ count -= sent;
+ }
+ } catch (IOException e) {
+ IoUtils.closeQuietly(mOutputStream);
+ // report I/O failure
+ onIOException();
+ throw e;
+ }
+ }
+ }
+
+ @Override
+ public void close() throws IOException {
+ mOutputStream.close();
+ }
+}
diff --git a/core/java/android/midi/MidiManager.java b/core/java/android/midi/MidiManager.java
new file mode 100644
index 0000000..8aa8395
--- /dev/null
+++ b/core/java/android/midi/MidiManager.java
@@ -0,0 +1,196 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.midi;
+
+import android.content.Context;
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.util.Log;
+
+import java.io.IOException;
+import java.util.HashMap;
+
+/**
+ * This class is the public application interface to the MIDI service.
+ *
+ * <p>You can obtain an instance of this class by calling
+ * {@link android.content.Context#getSystemService(java.lang.String) Context.getSystemService()}.
+ *
+ * {@samplecode
+ * MidiManager manager = (MidiManager) getSystemService(Context.MIDI_SERVICE);}
+ *
+ * CANDIDATE FOR PUBLIC API
+ * @hide
+ */
+public class MidiManager {
+ private static final String TAG = "MidiManager";
+
+ private final Context mContext;
+ private final IMidiManager mService;
+ private final IBinder mToken = new Binder();
+
+ private HashMap<DeviceCallback,DeviceListener> mDeviceListeners =
+ new HashMap<DeviceCallback,DeviceListener>();
+
+ // Binder stub for receiving device notifications from MidiService
+ private class DeviceListener extends IMidiListener.Stub {
+ private DeviceCallback mCallback;
+
+ public DeviceListener(DeviceCallback callback) {
+ mCallback = callback;
+ }
+
+ public void onDeviceAdded(MidiDeviceInfo device) {
+ mCallback.onDeviceAdded(device);
+ }
+
+ public void onDeviceRemoved(MidiDeviceInfo device) {
+ mCallback.onDeviceRemoved(device);
+ }
+ }
+
+ /**
+ * Callback interface used for clients to receive MIDI device added and removed notifications
+ */
+ public interface DeviceCallback {
+ /**
+ * Called to notify when a new MIDI device has been added
+ *
+ * @param device a {@link MidiDeviceInfo} for the newly added device
+ */
+ void onDeviceAdded(MidiDeviceInfo device);
+
+ /**
+ * Called to notify when a MIDI device has been removed
+ *
+ * @param device a {@link MidiDeviceInfo} for the removed device
+ */
+ void onDeviceRemoved(MidiDeviceInfo device);
+ }
+
+ /**
+ * @hide
+ */
+ public MidiManager(Context context, IMidiManager service) {
+ mContext = context;
+ mService = service;
+ }
+
+ /**
+ * Registers a callback to receive notifications when MIDI devices are added and removed.
+ *
+ * @param callback a {@link DeviceCallback} for MIDI device notifications
+ */
+ public void registerDeviceCallback(DeviceCallback callback) {
+ DeviceListener deviceListener = new DeviceListener(callback);
+ try {
+ mService.registerListener(mToken, deviceListener);
+ } catch (RemoteException e) {
+ Log.e(TAG, "RemoteException in registerDeviceListener");
+ return;
+ }
+ mDeviceListeners.put(callback, deviceListener);
+ }
+
+ /**
+ * Unregisters a {@link DeviceCallback}.
+ *
+ * @param callback a {@link DeviceCallback} to unregister
+ */
+ public void unregisterDeviceCallback(DeviceCallback callback) {
+ DeviceListener deviceListener = mDeviceListeners.remove(callback);
+ if (deviceListener != null) {
+ try {
+ mService.unregisterListener(mToken, deviceListener);
+ } catch (RemoteException e) {
+ Log.e(TAG, "RemoteException in unregisterDeviceListener");
+ }
+ }
+ }
+
+ /**
+ * Gets the list of all connected MIDI devices.
+ *
+ * @return an array of all MIDI devices
+ */
+ public MidiDeviceInfo[] getDeviceList() {
+ try {
+ return mService.getDeviceList();
+ } catch (RemoteException e) {
+ Log.e(TAG, "RemoteException in getDeviceList");
+ return new MidiDeviceInfo[0];
+ }
+ }
+
+ /**
+ * Opens a MIDI device for reading and writing.
+ *
+ * @param deviceInfo a {@link android.midi.MidiDeviceInfo} to open
+ * @return a {@link MidiDevice} object for the device
+ */
+ public MidiDevice openDevice(MidiDeviceInfo deviceInfo) {
+ try {
+ IMidiDeviceServer server = mService.openDevice(mToken, deviceInfo);
+ if (server == null) {
+ Log.e(TAG, "could not open device " + deviceInfo);
+ return null;
+ }
+ return new MidiDevice(deviceInfo, server);
+ } catch (RemoteException e) {
+ Log.e(TAG, "RemoteException in openDevice");
+ }
+ return null;
+ }
+
+ /** @hide */
+ public MidiDeviceServer createDeviceServer(int numInputPorts, int numOutputPorts,
+ Bundle properties, boolean isPrivate, int type) {
+ try {
+ MidiDeviceServer server = new MidiDeviceServer(mService);
+ MidiDeviceInfo deviceInfo = mService.registerDeviceServer(server.getBinderInterface(),
+ numInputPorts, numOutputPorts, properties, isPrivate, type);
+ if (deviceInfo == null) {
+ Log.e(TAG, "registerVirtualDevice failed");
+ return null;
+ }
+ server.setDeviceInfo(deviceInfo);
+ return server;
+ } catch (RemoteException e) {
+ Log.e(TAG, "RemoteException in createVirtualDevice");
+ return null;
+ }
+ }
+
+ /**
+ * Creates a new MIDI virtual device.
+ *
+ * @param numInputPorts number of input ports for the virtual device
+ * @param numOutputPorts number of output ports for the virtual device
+ * @param properties a {@link android.os.Bundle} containing properties describing the device
+ * @param isPrivate true if this device should only be visible and accessible to apps
+ * with the same UID as the caller
+ * @return a {@link MidiDeviceServer} object to locally represent the device
+ */
+ public MidiDeviceServer createDeviceServer(int numInputPorts, int numOutputPorts,
+ Bundle properties, boolean isPrivate) {
+ return createDeviceServer(numInputPorts, numOutputPorts, properties,
+ isPrivate, MidiDeviceInfo.TYPE_VIRTUAL);
+ }
+
+}
diff --git a/core/java/android/midi/MidiOutputPort.java b/core/java/android/midi/MidiOutputPort.java
new file mode 100644
index 0000000..332b431
--- /dev/null
+++ b/core/java/android/midi/MidiOutputPort.java
@@ -0,0 +1,135 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.midi;
+
+import android.os.ParcelFileDescriptor;
+import android.util.Log;
+
+import libcore.io.IoUtils;
+
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+
+/**
+ * This class is used for receiving data from a port on a MIDI device
+ *
+ * CANDIDATE FOR PUBLIC API
+ * @hide
+ */
+public class MidiOutputPort extends MidiPort implements MidiSender {
+ private static final String TAG = "MidiOutputPort";
+
+ private final FileInputStream mInputStream;
+
+ // array of receiver lists, indexed by port number
+ private final ArrayList<MidiReceiver> mReceivers = new ArrayList<MidiReceiver>();
+
+ private int mReceiverCount; // total number of receivers for all ports
+
+ // This thread reads MIDI events from a socket and distributes them to the list of
+ // MidiReceivers attached to this device.
+ private final Thread mThread = new Thread() {
+ @Override
+ public void run() {
+ byte[] buffer = new byte[MAX_PACKET_SIZE];
+ ArrayList<MidiReceiver> deadReceivers = new ArrayList<MidiReceiver>();
+
+ try {
+ while (true) {
+ // read next event
+ int count = mInputStream.read(buffer);
+ if (count < 0) {
+ break;
+ }
+
+ int offset = getMessageOffset(buffer, count);
+ int size = getMessageSize(buffer, count);
+ long timestamp = getMessageTimeStamp(buffer, count);
+
+ synchronized (mReceivers) {
+ for (int i = 0; i < mReceivers.size(); i++) {
+ MidiReceiver receiver = mReceivers.get(i);
+ try {
+ receiver.onPost(buffer, offset, size, timestamp);
+ } catch (IOException e) {
+ Log.e(TAG, "post failed");
+ deadReceivers.add(receiver);
+ }
+ }
+ // remove any receivers that failed
+ if (deadReceivers.size() > 0) {
+ for (MidiReceiver receiver: deadReceivers) {
+ mReceivers.remove(receiver);
+ mReceiverCount--;
+ }
+ deadReceivers.clear();
+ }
+ // exit if we have no receivers left
+ if (mReceiverCount == 0) {
+ break;
+ }
+ }
+ }
+ } catch (IOException e) {
+ // report I/O failure
+ Log.e(TAG, "read failed");
+ } finally {
+ IoUtils.closeQuietly(mInputStream);
+ onIOException();
+ }
+ }
+ };
+
+ /* package */ MidiOutputPort(ParcelFileDescriptor pfd, int portNumber) {
+ super(portNumber);
+ mInputStream = new ParcelFileDescriptor.AutoCloseInputStream(pfd);
+ }
+
+ /**
+ * Connects a {@link MidiReceiver} to the output port to allow receiving
+ * MIDI messages from the port.
+ *
+ * @param receiver the receiver to connect
+ */
+ public void connect(MidiReceiver receiver) {
+ synchronized (mReceivers) {
+ mReceivers.add(receiver);
+ if (mReceiverCount++ == 0) {
+ mThread.start();
+ }
+ }
+ }
+
+ /**
+ * Disconnects a {@link MidiReceiver} from the output port.
+ *
+ * @param receiver the receiver to connect
+ */
+ public void disconnect(MidiReceiver receiver) {
+ synchronized (mReceivers) {
+ if (mReceivers.remove(receiver)) {
+ mReceiverCount--;
+ }
+ }
+ }
+
+ @Override
+ public void close() throws IOException {
+ mInputStream.close();
+ }
+}
diff --git a/core/java/android/midi/MidiPort.java b/core/java/android/midi/MidiPort.java
new file mode 100644
index 0000000..7512a90
--- /dev/null
+++ b/core/java/android/midi/MidiPort.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.midi;
+
+import android.util.Log;
+
+import java.io.Closeable;
+
+/**
+ * This class represents a MIDI input or output port.
+ * Base class for {@link MidiInputPort} and {@link MidiOutputPort}
+ *
+ * CANDIDATE FOR PUBLIC API
+ * @hide
+ */
+abstract public class MidiPort implements Closeable {
+ private static final String TAG = "MidiPort";
+
+ private final int mPortNumber;
+
+ /**
+ * Maximum size of a packet that can pass through our ParcelFileDescriptor
+ */
+ protected static final int MAX_PACKET_SIZE = 1024;
+
+ /**
+ * size of message timestamp in bytes
+ */
+ private static final int TIMESTAMP_SIZE = 8;
+
+ /**
+ * Maximum amount of MIDI data that can be included in a packet
+ */
+ public static final int MAX_PACKET_DATA_SIZE = MAX_PACKET_SIZE - TIMESTAMP_SIZE;
+
+
+ /* package */ MidiPort(int portNumber) {
+ mPortNumber = portNumber;
+ }
+
+ /**
+ * Returns the port number of this port
+ *
+ * @return the port's port number
+ */
+ public final int getPortNumber() {
+ return mPortNumber;
+ }
+
+ /**
+ * Called when an IOExeption occurs while sending or receiving data.
+ * Subclasses can override to be notified of such errors
+ *
+ */
+ public void onIOException() {
+ }
+
+ /**
+ * Utility function for packing a MIDI message to be sent through our ParcelFileDescriptor
+ *
+ * message byte array contains variable length MIDI message.
+ * messageSize is size of variable length MIDI message
+ * timestamp is message timestamp to pack
+ * dest is buffer to pack into
+ * returns size of packed message
+ */
+ protected static int packMessage(byte[] message, int offset, int size, long timestamp,
+ byte[] dest) {
+ if (size + TIMESTAMP_SIZE > MAX_PACKET_SIZE) {
+ size = MAX_PACKET_SIZE - TIMESTAMP_SIZE;
+ }
+ // message data goes first
+ System.arraycopy(message, offset, dest, 0, size);
+
+ // followed by timestamp
+ for (int i = 0; i < TIMESTAMP_SIZE; i++) {
+ dest[size++] = (byte)timestamp;
+ timestamp >>= 8;
+ }
+
+ return size;
+ }
+
+ /**
+ * Utility function for unpacking a MIDI message received from our ParcelFileDescriptor
+ * returns the offset of the MIDI message in packed buffer
+ */
+ protected static int getMessageOffset(byte[] buffer, int bufferLength) {
+ // message is at the beginning
+ return 0;
+ }
+
+ /**
+ * Utility function for unpacking a MIDI message received from our ParcelFileDescriptor
+ * returns size of MIDI data in packed buffer
+ */
+ protected static int getMessageSize(byte[] buffer, int bufferLength) {
+ // message length is total buffer length minus size of the timestamp
+ return bufferLength - TIMESTAMP_SIZE;
+ }
+
+ /**
+ * Utility function for unpacking a MIDI message received from our ParcelFileDescriptor
+ * unpacks timestamp from packed buffer
+ */
+ protected static long getMessageTimeStamp(byte[] buffer, int bufferLength) {
+ // timestamp is at end of the packet
+ int offset = bufferLength;
+ long timestamp = 0;
+
+ for (int i = 0; i < TIMESTAMP_SIZE; i++) {
+ int b = (int)buffer[--offset] & 0xFF;
+ timestamp = (timestamp << 8) | b;
+ }
+ return timestamp;
+ }
+}
diff --git a/core/java/android/midi/MidiReceiver.java b/core/java/android/midi/MidiReceiver.java
new file mode 100644
index 0000000..fdfe51a
--- /dev/null
+++ b/core/java/android/midi/MidiReceiver.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.midi;
+
+import java.io.IOException;
+
+/**
+ * Interface for receiving data from a MIDI device.
+ *
+ * CANDIDATE FOR PUBLIC API
+ * @hide
+ */
+public interface MidiReceiver {
+ /**
+ * Called to pass MIDI data to the receiver.
+ *
+ * NOTE: the msg array parameter is only valid within the context of this call.
+ * The msg bytes should be copied by the receiver rather than retaining a reference
+ * to this parameter.
+ * Also, modifying the contents of the msg array parameter may result in other receivers
+ * in the same application receiving incorrect values in their onPost() method.
+ *
+ * @param msg a byte array containing the MIDI data
+ * @param offset the offset of the first byte of the data in the byte array
+ * @param count the number of bytes of MIDI data in the array
+ * @param timestamp the timestamp of the message (based on {@link java.lang.System#nanoTime}
+ * @throws IOException
+ */
+ public void onPost(byte[] msg, int offset, int count, long timestamp) throws IOException;
+}
diff --git a/core/java/android/midi/MidiSender.java b/core/java/android/midi/MidiSender.java
new file mode 100644
index 0000000..2b7afad
--- /dev/null
+++ b/core/java/android/midi/MidiSender.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.midi;
+
+/**
+ * Interface provided by a device to allow attaching
+ * MidiReceivers to a MIDI device.
+ *
+ * CANDIDATE FOR PUBLIC API
+ * @hide
+ */
+public interface MidiSender {
+ /**
+ * Called to connect a {@link MidiReceiver} to the sender
+ *
+ * @param receiver the receiver to connect
+ */
+ public void connect(MidiReceiver receiver);
+
+ /**
+ * Called to disconnect a {@link MidiReceiver} from the sender
+ *
+ * @param receiver the receiver to disconnect
+ */
+ public void disconnect(MidiReceiver receiver);
+}
diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java
index cdc8661..1a51808 100644
--- a/core/java/android/net/ConnectivityManager.java
+++ b/core/java/android/net/ConnectivityManager.java
@@ -504,6 +504,8 @@ public class ConnectivityManager {
return "MOBILE_EMERGENCY";
case TYPE_PROXY:
return "PROXY";
+ case TYPE_VPN:
+ return "VPN";
default:
return Integer.toString(type);
}
diff --git a/core/java/android/net/PacProxySelector.java b/core/java/android/net/PacProxySelector.java
index 8626d08..9bdf4f6 100644
--- a/core/java/android/net/PacProxySelector.java
+++ b/core/java/android/net/PacProxySelector.java
@@ -16,7 +16,6 @@
package android.net;
-import android.os.RemoteException;
import android.os.ServiceManager;
import android.util.Log;
@@ -74,8 +73,8 @@ public class PacProxySelector extends ProxySelector {
}
try {
response = mProxyService.resolvePacFile(uri.getHost(), urlString);
- } catch (RemoteException e) {
- e.printStackTrace();
+ } catch (Exception e) {
+ Log.e(TAG, "Error resolving PAC File", e);
}
if (response == null) {
return mDefaultList;
diff --git a/core/java/android/net/SSLCertificateSocketFactory.java b/core/java/android/net/SSLCertificateSocketFactory.java
index 6654577..27096b1 100644
--- a/core/java/android/net/SSLCertificateSocketFactory.java
+++ b/core/java/android/net/SSLCertificateSocketFactory.java
@@ -159,6 +159,8 @@ public class SSLCertificateSocketFactory extends SSLSocketFactory {
* instead. The Apache HTTP client is no longer maintained and may be removed in a future
* release. Please visit <a href="http://android-developers.blogspot.com/2011/09/androids-http-clients.html">this webpage</a>
* for further details.
+ *
+ * @removed
*/
@Deprecated
public static org.apache.http.conn.ssl.SSLSocketFactory getHttpSocketFactory(
diff --git a/core/java/android/net/Uri.java b/core/java/android/net/Uri.java
index 2099c3f..bf3d9aa 100644
--- a/core/java/android/net/Uri.java
+++ b/core/java/android/net/Uri.java
@@ -366,6 +366,7 @@ public abstract class Uri implements Parcelable, Comparable<Uri> {
public String toSafeString() {
String scheme = getScheme();
String ssp = getSchemeSpecificPart();
+ String authority = null;
if (scheme != null) {
if (scheme.equalsIgnoreCase("tel") || scheme.equalsIgnoreCase("sip")
|| scheme.equalsIgnoreCase("sms") || scheme.equalsIgnoreCase("smsto")
@@ -384,6 +385,9 @@ public abstract class Uri implements Parcelable, Comparable<Uri> {
}
}
return builder.toString();
+ } else if (scheme.equalsIgnoreCase("http") || scheme.equalsIgnoreCase("https")) {
+ ssp = null;
+ authority = "//" + getAuthority() + "/...";
}
}
// Not a sensitive scheme, but let's still be conservative about
@@ -397,6 +401,9 @@ public abstract class Uri implements Parcelable, Comparable<Uri> {
if (ssp != null) {
builder.append(ssp);
}
+ if (authority != null) {
+ builder.append(authority);
+ }
return builder.toString();
}
@@ -1742,7 +1749,7 @@ public abstract class Uri implements Parcelable, Comparable<Uri> {
*
* @return normalized Uri (never null)
* @see {@link android.content.Intent#setData}
- * @see {@link #setNormalizedData}
+ * @see {@link android.content.Intent#setDataAndNormalize}
*/
public Uri normalizeScheme() {
String scheme = getScheme();
diff --git a/core/java/android/os/Build.java b/core/java/android/os/Build.java
index b209690..eb86e7e 100644
--- a/core/java/android/os/Build.java
+++ b/core/java/android/os/Build.java
@@ -91,7 +91,7 @@ public class Build {
/** The name of the hardware (from the kernel command line or /proc). */
public static final String HARDWARE = getString("ro.hardware");
- /** A hardware serial number, if available. Alphanumeric only, case-insensitive. */
+ /** A hardware serial number, if available. Alphanumeric only, case-insensitive. */
public static final String SERIAL = getString("ro.serialno");
/**
@@ -159,7 +159,7 @@ public class Build {
/**
* The user-visible SDK version of the framework in its raw String
* representation; use {@link #SDK_INT} instead.
- *
+ *
* @deprecated Use {@link #SDK_INT} to easily get this as an integer.
*/
@Deprecated
@@ -207,25 +207,25 @@ public class Build {
* not yet turned into an official release.
*/
public static final int CUR_DEVELOPMENT = 10000;
-
+
/**
* October 2008: The original, first, version of Android. Yay!
*/
public static final int BASE = 1;
-
+
/**
* February 2009: First Android update, officially called 1.1.
*/
public static final int BASE_1_1 = 2;
-
+
/**
* May 2009: Android 1.5.
*/
public static final int CUPCAKE = 3;
-
+
/**
* September 2009: Android 1.6.
- *
+ *
* <p>Applications targeting this or a later release will get these
* new changes in behavior:</p>
* <ul>
@@ -247,10 +247,10 @@ public class Build {
* </ul>
*/
public static final int DONUT = 4;
-
+
/**
* November 2009: Android 2.0
- *
+ *
* <p>Applications targeting this or a later release will get these
* new changes in behavior:</p>
* <ul>
@@ -267,22 +267,22 @@ public class Build {
* </ul>
*/
public static final int ECLAIR = 5;
-
+
/**
* December 2009: Android 2.0.1
*/
public static final int ECLAIR_0_1 = 6;
-
+
/**
* January 2010: Android 2.1
*/
public static final int ECLAIR_MR1 = 7;
-
+
/**
* June 2010: Android 2.2
*/
public static final int FROYO = 8;
-
+
/**
* November 2010: Android 2.3
*
@@ -294,7 +294,7 @@ public class Build {
* </ul>
*/
public static final int GINGERBREAD = 9;
-
+
/**
* February 2011: Android 2.3.3.
*/
@@ -339,12 +339,12 @@ public class Build {
* </ul>
*/
public static final int HONEYCOMB = 11;
-
+
/**
* May 2011: Android 3.1.
*/
public static final int HONEYCOMB_MR1 = 12;
-
+
/**
* June 2011: Android 3.2.
*
@@ -597,8 +597,13 @@ public class Build {
* Lollipop with an extra sugar coating on the outside!
*/
public static final int LOLLIPOP_MR1 = 22;
+
+ /**
+ * M comes after L.
+ */
+ public static final int MNC = CUR_DEVELOPMENT;
}
-
+
/** The type of build, like "user" or "eng". */
public static final String TYPE = getString("ro.build.type");
@@ -645,14 +650,22 @@ public class Build {
}
/**
- * Check that device fingerprint is defined and that it matches across
- * various partitions.
+ * Verifies the the current flash of the device is consistent with what
+ * was expected at build time.
+ * 1) Checks that device fingerprint is defined and that it matches across
+ * various partitions.
+ * 2) Verifies radio and bootloader partitions are those expected in the build.
*
* @hide
*/
- public static boolean isFingerprintConsistent() {
+ public static boolean isBuildConsistent() {
final String system = SystemProperties.get("ro.build.fingerprint");
final String vendor = SystemProperties.get("ro.vendor.build.fingerprint");
+ final String bootimage = SystemProperties.get("ro.bootimage.build.fingerprint");
+ final String requiredBootloader = SystemProperties.get("ro.build.expect.bootloader");
+ final String currentBootloader = SystemProperties.get("ro.bootloader");
+ final String requiredRadio = SystemProperties.get("ro.build.expect.baseband");
+ final String currentRadio = SystemProperties.get("gsm.version.baseband");
if (TextUtils.isEmpty(system)) {
Slog.e(TAG, "Required ro.build.fingerprint is empty!");
@@ -667,6 +680,30 @@ public class Build {
}
}
+ if (!TextUtils.isEmpty(bootimage)) {
+ if (!Objects.equals(system, bootimage)) {
+ Slog.e(TAG, "Mismatched fingerprints; system reported " + system
+ + " but bootimage reported " + bootimage);
+ return false;
+ }
+ }
+
+ if (!TextUtils.isEmpty(requiredBootloader)) {
+ if (!Objects.equals(currentBootloader, requiredBootloader)) {
+ Slog.e(TAG, "Mismatched bootloader version: build requires " + requiredBootloader
+ + " but runtime reports " + currentBootloader);
+ return false;
+ }
+ }
+
+ if (!TextUtils.isEmpty(requiredRadio)) {
+ if (!Objects.equals(currentRadio, requiredRadio)) {
+ Slog.e(TAG, "Mismatched radio version: build requires " + requiredRadio
+ + " but runtime reports " + currentRadio);
+ return false;
+ }
+ }
+
return true;
}
diff --git a/core/java/android/os/Debug.java b/core/java/android/os/Debug.java
index 2d92c7b..d03365b 100644
--- a/core/java/android/os/Debug.java
+++ b/core/java/android/os/Debug.java
@@ -1286,7 +1286,10 @@ href="{@docRoot}guide/developing/tools/traceview.html">Traceview: A Graphical Lo
* + icount.globalMethodInvocations());
* }
* </pre>
+ *
+ * @deprecated Instruction counting is no longer supported.
*/
+ @Deprecated
public static class InstructionCount {
private static final int NUM_INSTR =
OpcodeInfo.MAXIMUM_PACKED_VALUE + 1;
diff --git a/core/java/android/os/IProcessInfoService.aidl b/core/java/android/os/IProcessInfoService.aidl
new file mode 100644
index 0000000..c98daa2
--- /dev/null
+++ b/core/java/android/os/IProcessInfoService.aidl
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2015 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.os;
+
+/** {@hide} */
+interface IProcessInfoService
+{
+ /**
+ * For each PID in the given input array, write the current process state
+ * for that process into the output array, or ActivityManager.PROCESS_STATE_NONEXISTENT
+ * to indicate that no process with the given PID exists.
+ */
+ void getProcessStatesFromPids(in int[] pids, out int[] states);
+}
+
diff --git a/core/java/android/os/ParcelFileDescriptor.java b/core/java/android/os/ParcelFileDescriptor.java
index c6b2151..4e8ec89 100644
--- a/core/java/android/os/ParcelFileDescriptor.java
+++ b/core/java/android/os/ParcelFileDescriptor.java
@@ -395,10 +395,17 @@ public class ParcelFileDescriptor implements Parcelable, Closeable {
* connected to each other. The two sockets are indistinguishable.
*/
public static ParcelFileDescriptor[] createSocketPair() throws IOException {
+ return createSocketPair(SOCK_STREAM);
+ }
+
+ /**
+ * @hide
+ */
+ public static ParcelFileDescriptor[] createSocketPair(int type) throws IOException {
try {
final FileDescriptor fd0 = new FileDescriptor();
final FileDescriptor fd1 = new FileDescriptor();
- Os.socketpair(AF_UNIX, SOCK_STREAM, 0, fd0, fd1);
+ Os.socketpair(AF_UNIX, type, 0, fd0, fd1);
return new ParcelFileDescriptor[] {
new ParcelFileDescriptor(fd0),
new ParcelFileDescriptor(fd1) };
@@ -417,11 +424,18 @@ public class ParcelFileDescriptor implements Parcelable, Closeable {
* This can also be used to detect remote crashes.
*/
public static ParcelFileDescriptor[] createReliableSocketPair() throws IOException {
+ return createReliableSocketPair(SOCK_STREAM);
+ }
+
+ /**
+ * @hide
+ */
+ public static ParcelFileDescriptor[] createReliableSocketPair(int type) throws IOException {
try {
final FileDescriptor[] comm = createCommSocketPair();
final FileDescriptor fd0 = new FileDescriptor();
final FileDescriptor fd1 = new FileDescriptor();
- Os.socketpair(AF_UNIX, SOCK_STREAM, 0, fd0, fd1);
+ Os.socketpair(AF_UNIX, type, 0, fd0, fd1);
return new ParcelFileDescriptor[] {
new ParcelFileDescriptor(fd0, comm[0]),
new ParcelFileDescriptor(fd1, comm[1]) };
diff --git a/core/java/android/os/StrictMode.java b/core/java/android/os/StrictMode.java
index 5018711..0b55998 100644
--- a/core/java/android/os/StrictMode.java
+++ b/core/java/android/os/StrictMode.java
@@ -145,7 +145,7 @@ public final class StrictMode {
* in {@link VmPolicy.Builder#detectAll()}. Apps can still always opt-into
* detection using {@link VmPolicy.Builder#detectCleartextNetwork()}.
*/
- private static final String CLEARTEXT_PROPERTY = "persist.sys.strictmode.nonssl";
+ private static final String CLEARTEXT_PROPERTY = "persist.sys.strictmode.clear";
// Only log a duplicate stack trace to the logs every second.
private static final long MIN_LOG_INTERVAL_MS = 1000;
@@ -184,8 +184,16 @@ public final class StrictMode {
*/
public static final int DETECT_CUSTOM = 0x08; // for ThreadPolicy
+ /**
+ * For StrictMode.noteResourceMismatch()
+ *
+ * @hide
+ */
+ public static final int DETECT_RESOURCE_MISMATCH = 0x10; // for ThreadPolicy
+
private static final int ALL_THREAD_DETECT_BITS =
- DETECT_DISK_WRITE | DETECT_DISK_READ | DETECT_NETWORK | DETECT_CUSTOM;
+ DETECT_DISK_WRITE | DETECT_DISK_READ | DETECT_NETWORK | DETECT_CUSTOM |
+ DETECT_RESOURCE_MISMATCH;
// Byte 2: Process-policy
@@ -460,6 +468,22 @@ public final class StrictMode {
}
/**
+ * Disable detection of mismatches between defined resource types
+ * and getter calls.
+ */
+ public Builder permitResourceMismatches() {
+ return disable(DETECT_RESOURCE_MISMATCH);
+ }
+
+ /**
+ * Enable detection of mismatches between defined resource types
+ * and getter calls.
+ */
+ public Builder detectResourceMismatches() {
+ return enable(DETECT_RESOURCE_MISMATCH);
+ }
+
+ /**
* Enable detection of disk writes.
*/
public Builder detectDiskWrites() {
@@ -739,8 +763,6 @@ public final class StrictMode {
* This inspects both IPv4/IPv6 and TCP/UDP network traffic, but it
* may be subject to false positives, such as when STARTTLS
* protocols or HTTP proxies are used.
- *
- * @hide
*/
public Builder detectCleartextNetwork() {
return enable(DETECT_VM_CLEARTEXT_NETWORK);
@@ -760,7 +782,6 @@ public final class StrictMode {
* detected.
*
* @see #detectCleartextNetwork()
- * @hide
*/
public Builder penaltyDeathOnCleartextNetwork() {
return enable(PENALTY_DEATH_ON_CLEARTEXT_NETWORK);
@@ -923,6 +944,15 @@ public final class StrictMode {
}
/**
+ * @hide
+ */
+ private static class StrictModeResourceMismatchViolation extends StrictModeViolation {
+ public StrictModeResourceMismatchViolation(int policyMask, Object tag) {
+ super(policyMask, DETECT_RESOURCE_MISMATCH, tag != null ? tag.toString() : null);
+ }
+ }
+
+ /**
* Returns the bitmask of the current thread's policy.
*
* @return the bitmask of all the DETECT_* and PENALTY_* bits currently enabled
@@ -1197,6 +1227,20 @@ public final class StrictMode {
startHandlingViolationException(e);
}
+ // Not part of BlockGuard.Policy; just part of StrictMode:
+ void onResourceMismatch(Object tag) {
+ if ((mPolicyMask & DETECT_RESOURCE_MISMATCH) == 0) {
+ return;
+ }
+ if (tooManyViolationsThisLoop()) {
+ return;
+ }
+ BlockGuard.BlockGuardPolicyException e =
+ new StrictModeResourceMismatchViolation(mPolicyMask, tag);
+ e.fillInStackTrace();
+ startHandlingViolationException(e);
+ }
+
// Part of BlockGuard.Policy interface:
public void onReadFromDisk() {
if ((mPolicyMask & DETECT_DISK_READ) == 0) {
@@ -2083,6 +2127,26 @@ public final class StrictMode {
}
/**
+ * For code to note that a resource was obtained using a type other than
+ * its defined type. This is a no-op unless the current thread's
+ * {@link android.os.StrictMode.ThreadPolicy} has
+ * {@link android.os.StrictMode.ThreadPolicy.Builder#detectResourceMismatches()}
+ * enabled.
+ *
+ * @param tag an object for the exception stack trace that's
+ * built if when this fires.
+ * @hide
+ */
+ public static void noteResourceMismatch(Object tag) {
+ BlockGuard.Policy policy = BlockGuard.getThreadPolicy();
+ if (!(policy instanceof AndroidBlockGuardPolicy)) {
+ // StrictMode not enabled.
+ return;
+ }
+ ((AndroidBlockGuardPolicy) policy).onResourceMismatch(tag);
+ }
+
+ /**
* @hide
*/
public static void noteDiskRead() {
diff --git a/core/java/android/print/PrintAttributes.java b/core/java/android/print/PrintAttributes.java
index 30f0c6a..90d30d6 100644
--- a/core/java/android/print/PrintAttributes.java
+++ b/core/java/android/print/PrintAttributes.java
@@ -45,11 +45,22 @@ public final class PrintAttributes implements Parcelable {
private static final int VALID_COLOR_MODES =
COLOR_MODE_MONOCHROME | COLOR_MODE_COLOR;
+ /** Duplex mode: No duplexing. */
+ public static final int DUPLEX_MODE_NONE = 1 << 0;
+ /** Duplex mode: Pages are turned sideways along the long edge - like a book. */
+ public static final int DUPLEX_MODE_LONG_EDGE = 1 << 1;
+ /** Duplex mode: Pages are turned upwards along the short edge - like a notpad. */
+ public static final int DUPLEX_MODE_SHORT_EDGE = 1 << 2;
+
+ private static final int VALID_DUPLEX_MODES =
+ DUPLEX_MODE_NONE | DUPLEX_MODE_LONG_EDGE | DUPLEX_MODE_SHORT_EDGE;
+
private MediaSize mMediaSize;
private Resolution mResolution;
private Margins mMinMargins;
private int mColorMode;
+ private int mDuplexMode = DUPLEX_MODE_NONE;
PrintAttributes() {
/* hide constructor */
@@ -60,6 +71,7 @@ public final class PrintAttributes implements Parcelable {
mResolution = (parcel.readInt() == 1) ? Resolution.createFromParcel(parcel) : null;
mMinMargins = (parcel.readInt() == 1) ? Margins.createFromParcel(parcel) : null;
mColorMode = parcel.readInt();
+ mDuplexMode = parcel.readInt();
}
/**
@@ -74,7 +86,7 @@ public final class PrintAttributes implements Parcelable {
/**
* Sets the media size.
*
- * @param The media size.
+ * @param mediaSize The media size.
*
* @hide
*/
@@ -94,7 +106,7 @@ public final class PrintAttributes implements Parcelable {
/**
* Sets the resolution.
*
- * @param The resolution.
+ * @param resolution The resolution.
*
* @hide
*/
@@ -130,7 +142,7 @@ public final class PrintAttributes implements Parcelable {
* </strong>
* </p>
*
- * @param The margins.
+ * @param margins The margins.
*
* @hide
*/
@@ -153,7 +165,7 @@ public final class PrintAttributes implements Parcelable {
/**
* Sets the color mode.
*
- * @param The color mode.
+ * @param colorMode The color mode.
*
* @see #COLOR_MODE_MONOCHROME
* @see #COLOR_MODE_COLOR
@@ -179,6 +191,35 @@ public final class PrintAttributes implements Parcelable {
}
/**
+ * Gets the duplex mode.
+ *
+ * @return The duplex mode.
+ *
+ * @see #DUPLEX_MODE_NONE
+ * @see #DUPLEX_MODE_LONG_EDGE
+ * @see #DUPLEX_MODE_SHORT_EDGE
+ */
+ public int getDuplexMode() {
+ return mDuplexMode;
+ }
+
+ /**
+ * Sets the duplex mode.
+ *
+ * @param duplexMode The duplex mode.
+ *
+ * @see #DUPLEX_MODE_NONE
+ * @see #DUPLEX_MODE_LONG_EDGE
+ * @see #DUPLEX_MODE_SHORT_EDGE
+ *
+ * @hide
+ */
+ public void setDuplexMode(int duplexMode) {
+ enforceValidDuplexMode(duplexMode);
+ mDuplexMode = duplexMode;
+ }
+
+ /**
* Gets a new print attributes instance which is in portrait orientation,
* which is the media size is in portrait and all orientation dependent
* attributes such as resolution and margins are properly adjusted.
@@ -211,6 +252,7 @@ public final class PrintAttributes implements Parcelable {
attributes.setMinMargins(getMinMargins());
attributes.setColorMode(getColorMode());
+ attributes.setDuplexMode(getDuplexMode());
return attributes;
}
@@ -248,6 +290,7 @@ public final class PrintAttributes implements Parcelable {
attributes.setMinMargins(getMinMargins());
attributes.setColorMode(getColorMode());
+ attributes.setDuplexMode(getDuplexMode());
return attributes;
}
@@ -273,6 +316,7 @@ public final class PrintAttributes implements Parcelable {
parcel.writeInt(0);
}
parcel.writeInt(mColorMode);
+ parcel.writeInt(mDuplexMode);
}
@Override
@@ -285,6 +329,7 @@ public final class PrintAttributes implements Parcelable {
final int prime = 31;
int result = 1;
result = prime * result + mColorMode;
+ result = prime * result + mDuplexMode;
result = prime * result + ((mMinMargins == null) ? 0 : mMinMargins.hashCode());
result = prime * result + ((mMediaSize == null) ? 0 : mMediaSize.hashCode());
result = prime * result + ((mResolution == null) ? 0 : mResolution.hashCode());
@@ -306,6 +351,9 @@ public final class PrintAttributes implements Parcelable {
if (mColorMode != other.mColorMode) {
return false;
}
+ if (mDuplexMode != other.mDuplexMode) {
+ return false;
+ }
if (mMinMargins == null) {
if (other.mMinMargins != null) {
return false;
@@ -344,6 +392,7 @@ public final class PrintAttributes implements Parcelable {
builder.append(", resolution: ").append(mResolution);
builder.append(", minMargins: ").append(mMinMargins);
builder.append(", colorMode: ").append(colorModeToString(mColorMode));
+ builder.append(", duplexMode: ").append(duplexModeToString(mDuplexMode));
builder.append("}");
return builder.toString();
}
@@ -354,6 +403,7 @@ public final class PrintAttributes implements Parcelable {
mResolution = null;
mMinMargins = null;
mColorMode = 0;
+ mDuplexMode = DUPLEX_MODE_NONE;
}
/**
@@ -364,6 +414,7 @@ public final class PrintAttributes implements Parcelable {
mResolution = other.mResolution;
mMinMargins = other.mMinMargins;
mColorMode = other.mColorMode;
+ mDuplexMode = other.mDuplexMode;
}
/**
@@ -1270,17 +1321,41 @@ public final class PrintAttributes implements Parcelable {
case COLOR_MODE_COLOR: {
return "COLOR_MODE_COLOR";
}
- default:
+ default: {
return "COLOR_MODE_UNKNOWN";
+ }
+ }
+ }
+
+ static String duplexModeToString(int duplexMode) {
+ switch (duplexMode) {
+ case DUPLEX_MODE_NONE: {
+ return "DUPLEX_MODE_NONE";
+ }
+ case DUPLEX_MODE_LONG_EDGE: {
+ return "DUPLEX_MODE_LONG_EDGE";
+ }
+ case DUPLEX_MODE_SHORT_EDGE: {
+ return "DUPLEX_MODE_SHORT_EDGE";
+ }
+ default: {
+ return "DUPLEX_MODE_UNKNOWN";
+ }
}
}
static void enforceValidColorMode(int colorMode) {
- if ((colorMode & VALID_COLOR_MODES) == 0 && Integer.bitCount(colorMode) == 1) {
+ if ((colorMode & VALID_COLOR_MODES) == 0 || Integer.bitCount(colorMode) != 1) {
throw new IllegalArgumentException("invalid color mode: " + colorMode);
}
}
+ static void enforceValidDuplexMode(int duplexMode) {
+ if ((duplexMode & VALID_DUPLEX_MODES) == 0 || Integer.bitCount(duplexMode) != 1) {
+ throw new IllegalArgumentException("invalid duplex mode: " + duplexMode);
+ }
+ }
+
/**
* Builder for creating {@link PrintAttributes}.
*/
@@ -1331,15 +1406,31 @@ public final class PrintAttributes implements Parcelable {
* @see PrintAttributes#COLOR_MODE_COLOR
*/
public Builder setColorMode(int colorMode) {
- if (Integer.bitCount(colorMode) > 1) {
- throw new IllegalArgumentException("can specify at most one colorMode bit.");
- }
mAttributes.setColorMode(colorMode);
return this;
}
/**
+ * Sets the duplex mode.
+ *
+ * @param duplexMode A valid duplex mode or zero.
+ * @return This builder.
+ *
+ * @see PrintAttributes#DUPLEX_MODE_NONE
+ * @see PrintAttributes#DUPLEX_MODE_LONG_EDGE
+ * @see PrintAttributes#DUPLEX_MODE_SHORT_EDGE
+ */
+ public Builder setDuplexMode(int duplexMode) {
+ mAttributes.setDuplexMode(duplexMode);
+ return this;
+ }
+
+ /**
* Creates a new {@link PrintAttributes} instance.
+ * <p>
+ * If you do not specify a duplex mode, the default
+ * {@link #DUPLEX_MODE_NONE} will be used.
+ * </p>
*
* @return The new instance.
*/
diff --git a/core/java/android/print/PrinterCapabilitiesInfo.java b/core/java/android/print/PrinterCapabilitiesInfo.java
index 806a89d..96f3185 100644
--- a/core/java/android/print/PrinterCapabilitiesInfo.java
+++ b/core/java/android/print/PrinterCapabilitiesInfo.java
@@ -47,7 +47,8 @@ public final class PrinterCapabilitiesInfo implements Parcelable {
private static final int PROPERTY_MEDIA_SIZE = 0;
private static final int PROPERTY_RESOLUTION = 1;
private static final int PROPERTY_COLOR_MODE = 2;
- private static final int PROPERTY_COUNT = 3;
+ private static final int PROPERTY_DUPLEX_MODE = 3;
+ private static final int PROPERTY_COUNT = 4;
private static final Margins DEFAULT_MARGINS = new Margins(0, 0, 0, 0);
@@ -56,6 +57,7 @@ public final class PrinterCapabilitiesInfo implements Parcelable {
private List<Resolution> mResolutions;
private int mColorModes;
+ private int mDuplexModes;
private final int[] mDefaults = new int[PROPERTY_COUNT];
@@ -106,6 +108,7 @@ public final class PrinterCapabilitiesInfo implements Parcelable {
}
mColorModes = other.mColorModes;
+ mDuplexModes = other.mDuplexModes;
final int defaultCount = other.mDefaults.length;
for (int i = 0; i < defaultCount; i++) {
@@ -154,6 +157,19 @@ public final class PrinterCapabilitiesInfo implements Parcelable {
}
/**
+ * Gets the bit mask of supported duplex modes.
+ *
+ * @return The bit mask of supported duplex modes.
+ *
+ * @see PrintAttributes#DUPLEX_MODE_NONE
+ * @see PrintAttributes#DUPLEX_MODE_LONG_EDGE
+ * @see PrintAttributes#DUPLEX_MODE_SHORT_EDGE
+ */
+ public int getDuplexModes() {
+ return mDuplexModes;
+ }
+
+ /**
* Gets the default print attributes.
*
* @return The default attributes.
@@ -178,6 +194,11 @@ public final class PrinterCapabilitiesInfo implements Parcelable {
builder.setColorMode(colorMode);
}
+ final int duplexMode = mDefaults[PROPERTY_DUPLEX_MODE];
+ if (duplexMode > 0) {
+ builder.setDuplexMode(duplexMode);
+ }
+
return builder.build();
}
@@ -187,6 +208,7 @@ public final class PrinterCapabilitiesInfo implements Parcelable {
readResolutions(parcel);
mColorModes = parcel.readInt();
+ mDuplexModes = parcel.readInt();
readDefaults(parcel);
}
@@ -203,6 +225,7 @@ public final class PrinterCapabilitiesInfo implements Parcelable {
writeResolutions(parcel);
parcel.writeInt(mColorModes);
+ parcel.writeInt(mDuplexModes);
writeDefaults(parcel);
}
@@ -215,6 +238,7 @@ public final class PrinterCapabilitiesInfo implements Parcelable {
result = prime * result + ((mMediaSizes == null) ? 0 : mMediaSizes.hashCode());
result = prime * result + ((mResolutions == null) ? 0 : mResolutions.hashCode());
result = prime * result + mColorModes;
+ result = prime * result + mDuplexModes;
result = prime * result + Arrays.hashCode(mDefaults);
return result;
}
@@ -255,6 +279,9 @@ public final class PrinterCapabilitiesInfo implements Parcelable {
if (mColorModes != other.mColorModes) {
return false;
}
+ if (mDuplexModes != other.mDuplexModes) {
+ return false;
+ }
if (!Arrays.equals(mDefaults, other.mDefaults)) {
return false;
}
@@ -269,6 +296,7 @@ public final class PrinterCapabilitiesInfo implements Parcelable {
builder.append(", mediaSizes=").append(mMediaSizes);
builder.append(", resolutions=").append(mResolutions);
builder.append(", colorModes=").append(colorModesToString());
+ builder.append(", duplexModes=").append(duplexModesToString());
builder.append("\"}");
return builder.toString();
}
@@ -289,6 +317,22 @@ public final class PrinterCapabilitiesInfo implements Parcelable {
return builder.toString();
}
+ private String duplexModesToString() {
+ StringBuilder builder = new StringBuilder();
+ builder.append('[');
+ int duplexModes = mDuplexModes;
+ while (duplexModes != 0) {
+ final int duplexMode = 1 << Integer.numberOfTrailingZeros(duplexModes);
+ duplexModes &= ~duplexMode;
+ if (builder.length() > 1) {
+ builder.append(", ");
+ }
+ builder.append(PrintAttributes.duplexModeToString(duplexMode));
+ }
+ builder.append(']');
+ return builder.toString();
+ }
+
private void writeMediaSizes(Parcel parcel) {
if (mMediaSizes == null) {
parcel.writeInt(0);
@@ -495,19 +539,51 @@ public final class PrinterCapabilitiesInfo implements Parcelable {
currentModes &= ~currentMode;
PrintAttributes.enforceValidColorMode(currentMode);
}
- if ((colorModes & defaultColorMode) == 0) {
- throw new IllegalArgumentException("Default color mode not in color modes.");
- }
- PrintAttributes.enforceValidColorMode(colorModes);
+ PrintAttributes.enforceValidColorMode(defaultColorMode);
mPrototype.mColorModes = colorModes;
mPrototype.mDefaults[PROPERTY_COLOR_MODE] = defaultColorMode;
return this;
}
/**
+ * Sets the duplex modes.
+ * <p>
+ * <strong>Required:</strong> No
+ * </p>
+ *
+ * @param duplexModes The duplex mode bit mask.
+ * @param defaultDuplexMode The default duplex mode.
+ * @return This builder.
+ *
+ * @throws IllegalArgumentException If duplex modes contains an invalid
+ * mode bit or if the default duplex mode is invalid.
+ *
+ * @see PrintAttributes#DUPLEX_MODE_NONE
+ * @see PrintAttributes#DUPLEX_MODE_LONG_EDGE
+ * @see PrintAttributes#DUPLEX_MODE_SHORT_EDGE
+ */
+ public Builder setDuplexModes(int duplexModes, int defaultDuplexMode) {
+ int currentModes = duplexModes;
+ while (currentModes > 0) {
+ final int currentMode = (1 << Integer.numberOfTrailingZeros(currentModes));
+ currentModes &= ~currentMode;
+ PrintAttributes.enforceValidDuplexMode(currentMode);
+ }
+ PrintAttributes.enforceValidDuplexMode(defaultDuplexMode);
+ mPrototype.mDuplexModes = duplexModes;
+ mPrototype.mDefaults[PROPERTY_DUPLEX_MODE] = defaultDuplexMode;
+ return this;
+ }
+
+ /**
* Crates a new {@link PrinterCapabilitiesInfo} enforcing that all
* required properties have been specified. See individual methods
* in this class for reference about required attributes.
+ * <p>
+ * <strong>Note:</strong> If you do not add supported duplex modes,
+ * {@link android.print.PrintAttributes#DUPLEX_MODE_NONE} will set
+ * as the only supported mode and also as the default duplex mode.
+ * </p>
*
* @return A new {@link PrinterCapabilitiesInfo}.
*
@@ -532,6 +608,10 @@ public final class PrinterCapabilitiesInfo implements Parcelable {
if (mPrototype.mDefaults[PROPERTY_COLOR_MODE] == DEFAULT_UNDEFINED) {
throw new IllegalStateException("No default color mode specified.");
}
+ if (mPrototype.mDuplexModes == 0) {
+ setDuplexModes(PrintAttributes.DUPLEX_MODE_NONE,
+ PrintAttributes.DUPLEX_MODE_NONE);
+ }
if (mPrototype.mMinMargins == null) {
throw new IllegalArgumentException("margins cannot be null");
}
@@ -558,4 +638,3 @@ public final class PrinterCapabilitiesInfo implements Parcelable {
}
};
}
-
diff --git a/core/java/android/printservice/PrintService.java b/core/java/android/printservice/PrintService.java
index ae95854..7ed1649 100644
--- a/core/java/android/printservice/PrintService.java
+++ b/core/java/android/printservice/PrintService.java
@@ -192,7 +192,7 @@ public abstract class PrintService extends Service {
/**
* If you declared an optional activity with advanced print options via the
- * {@link R.attr#advancedPrintOptionsActivity advancedPrintOptionsActivity}
+ * {@link android.R.attr#advancedPrintOptionsActivity advancedPrintOptionsActivity}
* attribute, this extra is used to pass in the currently constructed {@link
* PrintJobInfo} to your activity allowing you to modify it. After you are
* done, you must return the modified {@link PrintJobInfo} via the same extra.
@@ -224,7 +224,7 @@ public abstract class PrintService extends Service {
/**
* If you declared an optional activity with advanced print options via the
- * {@link R.attr#advancedPrintOptionsActivity advancedPrintOptionsActivity}
+ * {@link android.R.attr#advancedPrintOptionsActivity advancedPrintOptionsActivity}
* attribute, this extra is used to pass in the currently selected printer's
* {@link android.print.PrinterInfo} to your activity allowing you to inspect it.
*
diff --git a/core/java/android/provider/Contacts.java b/core/java/android/provider/Contacts.java
index d4c5cfb..3aa526d 100644
--- a/core/java/android/provider/Contacts.java
+++ b/core/java/android/provider/Contacts.java
@@ -2077,12 +2077,12 @@ public class Contacts {
/**
* Intents related to the Contacts app UI.
- * @deprecated see {@link android.provider.ContactsContract}
+ * @deprecated Do not use. This is not supported.
*/
@Deprecated
public static final class UI {
/**
- * @deprecated see {@link android.provider.ContactsContract}
+ * @deprecated Do not use. This is not supported.
*/
@Deprecated
public UI() {
@@ -2090,76 +2090,77 @@ public class Contacts {
/**
* The action for the default contacts list tab.
- * @deprecated see {@link android.provider.ContactsContract}
+ * @deprecated Do not use. This is not supported.
*/
@Deprecated
- public static final String LIST_DEFAULT = ContactsContract.Intents.UI.LIST_DEFAULT;
+ public static final String LIST_DEFAULT
+ = "com.android.contacts.action.LIST_DEFAULT";
/**
* The action for the contacts list tab.
- * @deprecated see {@link android.provider.ContactsContract}
+ * @deprecated Do not use. This is not supported.
*/
@Deprecated
public static final String LIST_GROUP_ACTION =
- ContactsContract.Intents.UI.LIST_GROUP_ACTION;
+ "com.android.contacts.action.LIST_GROUP";
/**
* When in LIST_GROUP_ACTION mode, this is the group to display.
- * @deprecated see {@link android.provider.ContactsContract}
+ * @deprecated Do not use. This is not supported.
*/
@Deprecated
public static final String GROUP_NAME_EXTRA_KEY =
- ContactsContract.Intents.UI.GROUP_NAME_EXTRA_KEY;
+ "com.android.contacts.extra.GROUP";
/**
* The action for the all contacts list tab.
- * @deprecated see {@link android.provider.ContactsContract}
+ * @deprecated Do not use. This is not supported.
*/
@Deprecated
public static final String LIST_ALL_CONTACTS_ACTION =
- ContactsContract.Intents.UI.LIST_ALL_CONTACTS_ACTION;
+ "com.android.contacts.action.LIST_ALL_CONTACTS";
/**
* The action for the contacts with phone numbers list tab.
- * @deprecated see {@link android.provider.ContactsContract}
+ * @deprecated Do not use. This is not supported.
*/
@Deprecated
public static final String LIST_CONTACTS_WITH_PHONES_ACTION =
- ContactsContract.Intents.UI.LIST_CONTACTS_WITH_PHONES_ACTION;
+ "com.android.contacts.action.LIST_CONTACTS_WITH_PHONES";
/**
* The action for the starred contacts list tab.
- * @deprecated see {@link android.provider.ContactsContract}
+ * @deprecated Do not use. This is not supported.
*/
@Deprecated
public static final String LIST_STARRED_ACTION =
- ContactsContract.Intents.UI.LIST_STARRED_ACTION;
+ "com.android.contacts.action.LIST_STARRED";
/**
* The action for the frequent contacts list tab.
- * @deprecated see {@link android.provider.ContactsContract}
+ * @deprecated Do not use. This is not supported.
*/
@Deprecated
public static final String LIST_FREQUENT_ACTION =
- ContactsContract.Intents.UI.LIST_FREQUENT_ACTION;
+ "com.android.contacts.action.LIST_FREQUENT";
/**
* The action for the "strequent" contacts list tab. It first lists the starred
* contacts in alphabetical order and then the frequent contacts in descending
* order of the number of times they have been contacted.
- * @deprecated see {@link android.provider.ContactsContract}
+ * @deprecated Do not use. This is not supported.
*/
@Deprecated
public static final String LIST_STREQUENT_ACTION =
- ContactsContract.Intents.UI.LIST_STREQUENT_ACTION;
+ "com.android.contacts.action.LIST_STREQUENT";
/**
* A key for to be used as an intent extra to set the activity
* title to a custom String value.
- * @deprecated see {@link android.provider.ContactsContract}
+ * @deprecated Do not use. This is not supported.
*/
@Deprecated
public static final String TITLE_EXTRA_KEY =
- ContactsContract.Intents.UI.TITLE_EXTRA_KEY;
+ "com.android.contacts.extra.TITLE_EXTRA";
/**
* Activity Action: Display a filtered list of contacts
@@ -2168,20 +2169,20 @@ public class Contacts {
* filtering
* <p>
* Output: Nothing.
- * @deprecated see {@link android.provider.ContactsContract}
+ * @deprecated Do not use. This is not supported.
*/
@Deprecated
public static final String FILTER_CONTACTS_ACTION =
- ContactsContract.Intents.UI.FILTER_CONTACTS_ACTION;
+ "com.android.contacts.action.FILTER_CONTACTS";
/**
* Used as an int extra field in {@link #FILTER_CONTACTS_ACTION}
* intents to supply the text on which to filter.
- * @deprecated see {@link android.provider.ContactsContract}
+ * @deprecated Do not use. This is not supported.
*/
@Deprecated
public static final String FILTER_TEXT_EXTRA_KEY =
- ContactsContract.Intents.UI.FILTER_TEXT_EXTRA_KEY;
+ "com.android.contacts.extra.FILTER_TEXT";
}
/**
diff --git a/core/java/android/provider/ContactsContract.java b/core/java/android/provider/ContactsContract.java
index 18a9eb1..67ac043 100644
--- a/core/java/android/provider/ContactsContract.java
+++ b/core/java/android/provider/ContactsContract.java
@@ -197,12 +197,15 @@ public final class ContactsContract {
* API for obtaining a pre-authorized version of a URI that normally requires special
* permission (beyond READ_CONTACTS) to read. The caller obtaining the pre-authorized URI
* must already have the necessary permissions to access the URI; otherwise a
- * {@link SecurityException} will be thrown.
+ * {@link SecurityException} will be thrown. Unlike {@link Context#grantUriPermission},
+ * this can be used to grant permissions that aren't explicitly required for the URI inside
+ * AndroidManifest.xml. For example, permissions that are only required when reading URIs
+ * that refer to the user's profile.
* </p>
* <p>
* The authorized URI returned in the bundle contains an expiring token that allows the
* caller to execute the query without having the special permissions that would normally
- * be required.
+ * be required. The token expires in five minutes.
* </p>
* <p>
* This API does not access disk, and should be safe to invoke from the UI thread.
@@ -226,7 +229,6 @@ public final class ContactsContract {
* }
* </pre>
* </p>
- * @hide
*/
public static final class Authorization {
/**
@@ -1432,9 +1434,9 @@ public final class ContactsContract {
* and {@link #CONTENT_MULTI_VCARD_URI} to indicate that the returned
* vcard should not contain a photo.
*
- * @hide
+ * This is useful for obtaining a space efficient vcard.
*/
- public static final String QUERY_PARAMETER_VCARD_NO_PHOTO = "nophoto";
+ public static final String QUERY_PARAMETER_VCARD_NO_PHOTO = "no_photo";
/**
* Base {@link Uri} for referencing multiple {@link Contacts} entry,
@@ -1790,52 +1792,26 @@ public final class ContactsContract {
public static final String CONTENT_DIRECTORY = "suggestions";
/**
- * Used with {@link Builder#addParameter} to specify what kind of data is
- * supplied for the suggestion query.
+ * Used to specify what kind of data is supplied for the suggestion query.
*
* @hide
*/
public static final String PARAMETER_MATCH_NAME = "name";
/**
- * Used with {@link Builder#addParameter} to specify what kind of data is
- * supplied for the suggestion query.
- *
- * @hide
- */
- public static final String PARAMETER_MATCH_EMAIL = "email";
-
- /**
- * Used with {@link Builder#addParameter} to specify what kind of data is
- * supplied for the suggestion query.
- *
- * @hide
- */
- public static final String PARAMETER_MATCH_PHONE = "phone";
-
- /**
- * Used with {@link Builder#addParameter} to specify what kind of data is
- * supplied for the suggestion query.
- *
- * @hide
- */
- public static final String PARAMETER_MATCH_NICKNAME = "nickname";
-
- /**
* A convenience builder for aggregation suggestion content URIs.
- *
- * TODO: change documentation for this class to use the builder.
- * @hide
*/
public static final class Builder {
private long mContactId;
- private ArrayList<String> mKinds = new ArrayList<String>();
- private ArrayList<String> mValues = new ArrayList<String>();
+ private final ArrayList<String> mValues = new ArrayList<String>();
private int mLimit;
/**
* Optional existing contact ID. If it is not provided, the search
- * will be based exclusively on the values supplied with {@link #addParameter}.
+ * will be based exclusively on the values supplied with {@link #addNameParameter}.
+ *
+ * @param contactId contact to find aggregation suggestions for
+ * @return This Builder object to allow for chaining of calls to builder methods
*/
public Builder setContactId(long contactId) {
this.mContactId = contactId;
@@ -1843,28 +1819,31 @@ public final class ContactsContract {
}
/**
- * A value that can be used when searching for an aggregation
- * suggestion.
+ * Add a name to be used when searching for aggregation suggestions.
*
- * @param kind can be one of
- * {@link AggregationSuggestions#PARAMETER_MATCH_NAME},
- * {@link AggregationSuggestions#PARAMETER_MATCH_EMAIL},
- * {@link AggregationSuggestions#PARAMETER_MATCH_NICKNAME},
- * {@link AggregationSuggestions#PARAMETER_MATCH_PHONE}
+ * @param name name to find aggregation suggestions for
+ * @return This Builder object to allow for chaining of calls to builder methods
*/
- public Builder addParameter(String kind, String value) {
- if (!TextUtils.isEmpty(value)) {
- mKinds.add(kind);
- mValues.add(value);
- }
+ public Builder addNameParameter(String name) {
+ mValues.add(name);
return this;
}
+ /**
+ * Sets the Maximum number of suggested aggregations that should be returned.
+ * @param limit The maximum number of suggested aggregations
+ *
+ * @return This Builder object to allow for chaining of calls to builder methods
+ */
public Builder setLimit(int limit) {
mLimit = limit;
return this;
}
+ /**
+ * Combine all of the options that have been set and return a new {@link Uri}
+ * object for fetching aggregation suggestions.
+ */
public Uri build() {
android.net.Uri.Builder builder = Contacts.CONTENT_URI.buildUpon();
builder.appendEncodedPath(String.valueOf(mContactId));
@@ -1873,9 +1852,10 @@ public final class ContactsContract {
builder.appendQueryParameter("limit", String.valueOf(mLimit));
}
- int count = mKinds.size();
+ int count = mValues.size();
for (int i = 0; i < count; i++) {
- builder.appendQueryParameter("query", mKinds.get(i) + ":" + mValues.get(i));
+ builder.appendQueryParameter("query", PARAMETER_MATCH_NAME
+ + ":" + mValues.get(i));
}
return builder.build();
@@ -2214,6 +2194,16 @@ public final class ContactsContract {
public static final String CONTACT_ID = "contact_id";
/**
+ * Persistent unique id for each raw_contact within its account.
+ * This id is provided by its own data source, and can be used to backup metadata
+ * to the server.
+ * This should be unique within each set of account_name/account_type/data_set
+ *
+ * @hide
+ */
+ public static final String BACKUP_ID = "backup_id";
+
+ /**
* The data set within the account that this row belongs to. This allows
* multiple sync adapters for the same account type to distinguish between
* each others' data.
@@ -2258,33 +2248,6 @@ public final class ContactsContract {
public static final String DELETED = "deleted";
/**
- * The "name_verified" flag: "1" means that the name fields on this raw
- * contact can be trusted and therefore should be used for the entire
- * aggregated contact.
- * <p>
- * If an aggregated contact contains more than one raw contact with a
- * verified name, one of those verified names is chosen at random.
- * If an aggregated contact contains no verified names, the
- * name is chosen randomly from the constituent raw contacts.
- * </p>
- * <p>
- * Updating this flag from "0" to "1" automatically resets it to "0" on
- * all other raw contacts in the same aggregated contact.
- * </p>
- * <p>
- * Sync adapters should only specify a value for this column when
- * inserting a raw contact and leave it out when doing an update.
- * </p>
- * <p>
- * The default value is "0"
- * </p>
- * <p>Type: INTEGER</p>
- *
- * @hide
- */
- public static final String NAME_VERIFIED = "name_verified";
-
- /**
* The "read-only" flag: "0" by default, "1" if the row cannot be modified or
* deleted except by a sync adapter. See {@link ContactsContract#CALLER_IS_SYNCADAPTER}.
* <P>Type: INTEGER</P>
@@ -2980,7 +2943,6 @@ public final class ContactsContract {
DatabaseUtils.cursorLongToContentValuesIfPresent(cursor, cv, DELETED);
DatabaseUtils.cursorLongToContentValuesIfPresent(cursor, cv, CONTACT_ID);
DatabaseUtils.cursorLongToContentValuesIfPresent(cursor, cv, STARRED);
- DatabaseUtils.cursorIntToContentValuesIfPresent(cursor, cv, NAME_VERIFIED);
android.content.Entity contact = new android.content.Entity(cv);
// read data rows until the contact id changes
@@ -4006,6 +3968,13 @@ public final class ContactsContract {
public static final String MIMETYPE = "mimetype";
/**
+ * Hash id on the data fields, used for backup and restore.
+ *
+ * @hide
+ */
+ public static final String HASH_ID = "hash_id";
+
+ /**
* A reference to the {@link RawContacts#_ID}
* that this data belongs to.
*/
@@ -7839,9 +7808,7 @@ public final class ContactsContract {
}
/**
- * Private API for inquiring about the general status of the provider.
- *
- * @hide
+ * API for inquiring about the general status of the provider.
*/
public static final class ProviderStatus {
@@ -7854,8 +7821,6 @@ public final class ContactsContract {
/**
* The content:// style URI for this table. Requests to this URI can be
* performed on the UI thread because they are always unblocking.
- *
- * @hide
*/
public static final Uri CONTENT_URI =
Uri.withAppendedPath(AUTHORITY_URI, "provider_status");
@@ -7863,64 +7828,35 @@ public final class ContactsContract {
/**
* The MIME-type of {@link #CONTENT_URI} providing a directory of
* settings.
- *
- * @hide
*/
public static final String CONTENT_TYPE = "vnd.android.cursor.dir/provider_status";
/**
* An integer representing the current status of the provider.
- *
- * @hide
*/
public static final String STATUS = "status";
/**
* Default status of the provider.
- *
- * @hide
*/
public static final int STATUS_NORMAL = 0;
/**
* The status used when the provider is in the process of upgrading. Contacts
* are temporarily unaccessible.
- *
- * @hide
*/
public static final int STATUS_UPGRADING = 1;
/**
- * The status used if the provider was in the process of upgrading but ran
- * out of storage. The DATA1 column will contain the estimated amount of
- * storage required (in bytes). Update status to STATUS_NORMAL to force
- * the provider to retry the upgrade.
- *
- * @hide
- */
- public static final int STATUS_UPGRADE_OUT_OF_MEMORY = 2;
-
- /**
* The status used during a locale change.
- *
- * @hide
*/
public static final int STATUS_CHANGING_LOCALE = 3;
/**
* The status that indicates that there are no accounts and no contacts
* on the device.
- *
- * @hide
*/
public static final int STATUS_NO_ACCOUNTS_NO_CONTACTS = 4;
-
- /**
- * Additional data associated with the status.
- *
- * @hide
- */
- public static final String DATA1 = "data1";
}
/**
@@ -8125,8 +8061,10 @@ public final class ContactsContract {
public static final String EXTRA_TARGET_RECT = "android.provider.extra.TARGET_RECT";
/**
- * Extra used to specify size of pivot dialog.
- * @hide
+ * Extra used to specify size of QuickContacts. Not all implementations of QuickContacts
+ * will respect this extra's value.
+ *
+ * One of {@link #MODE_SMALL}, {@link #MODE_MEDIUM}, or {@link #MODE_LARGE}.
*/
public static final String EXTRA_MODE = "android.provider.extra.MODE";
@@ -8538,102 +8476,6 @@ public final class ContactsContract {
public static final String EXTRA_EXCLUDE_MIMES = "exclude_mimes";
/**
- * Intents related to the Contacts app UI.
- *
- * @hide
- */
- public static final class UI {
- /**
- * The action for the default contacts list tab.
- */
- public static final String LIST_DEFAULT =
- "com.android.contacts.action.LIST_DEFAULT";
-
- /**
- * The action for the contacts list tab.
- */
- public static final String LIST_GROUP_ACTION =
- "com.android.contacts.action.LIST_GROUP";
-
- /**
- * When in LIST_GROUP_ACTION mode, this is the group to display.
- */
- public static final String GROUP_NAME_EXTRA_KEY = "com.android.contacts.extra.GROUP";
-
- /**
- * The action for the all contacts list tab.
- */
- public static final String LIST_ALL_CONTACTS_ACTION =
- "com.android.contacts.action.LIST_ALL_CONTACTS";
-
- /**
- * The action for the contacts with phone numbers list tab.
- */
- public static final String LIST_CONTACTS_WITH_PHONES_ACTION =
- "com.android.contacts.action.LIST_CONTACTS_WITH_PHONES";
-
- /**
- * The action for the starred contacts list tab.
- */
- public static final String LIST_STARRED_ACTION =
- "com.android.contacts.action.LIST_STARRED";
-
- /**
- * The action for the frequent contacts list tab.
- */
- public static final String LIST_FREQUENT_ACTION =
- "com.android.contacts.action.LIST_FREQUENT";
-
- /**
- * The action for the "Join Contact" picker.
- */
- public static final String PICK_JOIN_CONTACT_ACTION =
- "com.android.contacts.action.JOIN_CONTACT";
-
- /**
- * The action for the "strequent" contacts list tab. It first lists the starred
- * contacts in alphabetical order and then the frequent contacts in descending
- * order of the number of times they have been contacted.
- */
- public static final String LIST_STREQUENT_ACTION =
- "com.android.contacts.action.LIST_STREQUENT";
-
- /**
- * A key for to be used as an intent extra to set the activity
- * title to a custom String value.
- */
- public static final String TITLE_EXTRA_KEY =
- "com.android.contacts.extra.TITLE_EXTRA";
-
- /**
- * Activity Action: Display a filtered list of contacts
- * <p>
- * Input: Extra field {@link #FILTER_TEXT_EXTRA_KEY} is the text to use for
- * filtering
- * <p>
- * Output: Nothing.
- */
- public static final String FILTER_CONTACTS_ACTION =
- "com.android.contacts.action.FILTER_CONTACTS";
-
- /**
- * Used as an int extra field in {@link #FILTER_CONTACTS_ACTION}
- * intents to supply the text on which to filter.
- */
- public static final String FILTER_TEXT_EXTRA_KEY =
- "com.android.contacts.extra.FILTER_TEXT";
-
- /**
- * Used with JOIN_CONTACT action to set the target for aggregation. This action type
- * uses contact ids instead of contact uris for the sake of backwards compatibility.
- * <p>
- * Type: LONG
- */
- public static final String TARGET_CONTACT_ID_EXTRA_KEY
- = "com.android.contacts.action.CONTACT_ID";
- }
-
- /**
* Convenience class that contains string constants used
* to create contact {@link android.content.Intent Intents}.
*/
@@ -8858,10 +8700,8 @@ public final class ContactsContract {
* dialog to chose an account
* <p>
* Type: {@link Account}
- *
- * @hide
*/
- public static final String ACCOUNT = "com.android.contacts.extra.ACCOUNT";
+ public static final String EXTRA_ACCOUNT = "android.provider.extra.ACCOUNT";
/**
* Used to specify the data set within the account in which to create the
@@ -8871,10 +8711,8 @@ public final class ContactsContract {
* created in the base account, with no data set.
* <p>
* Type: String
- *
- * @hide
*/
- public static final String DATA_SET = "com.android.contacts.extra.DATA_SET";
+ public static final String EXTRA_DATA_SET = "android.provider.extra.DATA_SET";
}
}
}
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index ef0f72f..7b3eceb 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -51,14 +51,21 @@ import android.os.Build.VERSION_CODES;
import android.speech.tts.TextToSpeech;
import android.text.TextUtils;
import android.util.AndroidException;
+import android.util.ArrayMap;
+import android.util.ArraySet;
import android.util.Log;
+import com.android.internal.util.ArrayUtils;
import com.android.internal.widget.ILockSettings;
import java.net.URISyntaxException;
+import java.text.SimpleDateFormat;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+import java.util.regex.Pattern;
/**
* The Settings provider contains global system-level device preferences.
@@ -132,7 +139,6 @@ public final class Settings {
"android.settings.AIRPLANE_MODE_SETTINGS";
/**
- * @hide
* Activity Action: Modify Airplane mode settings using the users voice.
* <p>
* In some cases, a matching Activity may not exist, so ensure you safeguard against this.
@@ -154,7 +160,6 @@ public final class Settings {
* Output: Nothing.
*/
@SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
- @SystemApi
public static final String ACTION_VOICE_CONTROL_AIRPLANE_MODE =
"android.settings.VOICE_CONTROL_AIRPLANE_MODE";
@@ -308,7 +313,7 @@ public final class Settings {
/**
* Activity Action: Show settings to allow configuration of
- * cast endpoints.
+ * {@link android.media.routing.MediaRouteService media route providers}.
* <p>
* In some cases, a matching Activity may not exist, so ensure you
* safeguard against this.
@@ -991,13 +996,11 @@ public final class Settings {
public static final String EXTRA_INPUT_DEVICE_IDENTIFIER = "input_device_identifier";
/**
- * @hide
* Activity Extra: Enable or disable Airplane Mode.
* <p>
* This can be passed as an extra field to the {@link #ACTION_VOICE_CONTROL_AIRPLANE_MODE}
* intent as a boolean.
*/
- @SystemApi
public static final String EXTRA_AIRPLANE_MODE_ENABLED = "airplane_mode_enabled";
private static final String JID_RESOURCE_PREFIX = "android";
@@ -1196,6 +1199,11 @@ public final class Settings {
public static final class System extends NameValueTable {
public static final String SYS_PROP_SETTING_VERSION = "sys.settings_system_version";
+ /** @hide */
+ public static interface Validator {
+ public boolean validate(String value);
+ }
+
/**
* The content:// style URL for this table
*/
@@ -1298,13 +1306,56 @@ public final class Settings {
MOVED_TO_GLOBAL.add(Settings.Global.CERT_PIN_UPDATE_METADATA_URL);
}
+ private static final Validator sBooleanValidator =
+ new DiscreteValueValidator(new String[] {"0", "1"});
+
+ private static final Validator sNonNegativeIntegerValidator = new Validator() {
+ @Override
+ public boolean validate(String value) {
+ try {
+ return Integer.parseInt(value) >= 0;
+ } catch (NumberFormatException e) {
+ return false;
+ }
+ }
+ };
+
+ private static final Validator sVolumeValidator =
+ new InclusiveFloatRangeValidator(0, 1);
+
+ private static final Validator sUriValidator = new Validator() {
+ @Override
+ public boolean validate(String value) {
+ try {
+ Uri.decode(value);
+ return true;
+ } catch (IllegalArgumentException e) {
+ return false;
+ }
+ }
+ };
+
+ private static final Validator sLenientIpAddressValidator = new Validator() {
+ private static final int MAX_IPV6_LENGTH = 45;
+
+ @Override
+ public boolean validate(String value) {
+ return value.length() <= MAX_IPV6_LENGTH;
+ }
+ };
+
/** @hide */
- public static void getMovedKeys(HashSet<String> outKeySet) {
+ public static void getMovedToGlobalSettings(Set<String> outKeySet) {
outKeySet.addAll(MOVED_TO_GLOBAL);
outKeySet.addAll(MOVED_TO_SECURE_THEN_GLOBAL);
}
/** @hide */
+ public static void getMovedToSecureSettings(Set<String> outKeySet) {
+ outKeySet.addAll(MOVED_TO_SECURE);
+ }
+
+ /** @hide */
public static void getNonLegacyMovedKeys(HashSet<String> outKeySet) {
outKeySet.addAll(MOVED_TO_GLOBAL);
}
@@ -1727,6 +1778,56 @@ public final class Settings {
putIntForUser(cr, SHOW_GTALK_SERVICE_STATUS, flag ? 1 : 0, userHandle);
}
+ private static final class DiscreteValueValidator implements Validator {
+ private final String[] mValues;
+
+ public DiscreteValueValidator(String[] values) {
+ mValues = values;
+ }
+
+ public boolean validate(String value) {
+ return ArrayUtils.contains(mValues, value);
+ }
+ }
+
+ private static final class InclusiveIntegerRangeValidator implements Validator {
+ private final int mMin;
+ private final int mMax;
+
+ public InclusiveIntegerRangeValidator(int min, int max) {
+ mMin = min;
+ mMax = max;
+ }
+
+ public boolean validate(String value) {
+ try {
+ final int intValue = Integer.parseInt(value);
+ return intValue >= mMin && intValue <= mMax;
+ } catch (NumberFormatException e) {
+ return false;
+ }
+ }
+ }
+
+ private static final class InclusiveFloatRangeValidator implements Validator {
+ private final float mMin;
+ private final float mMax;
+
+ public InclusiveFloatRangeValidator(float min, float max) {
+ mMin = min;
+ mMax = max;
+ }
+
+ public boolean validate(String value) {
+ try {
+ final float floatValue = Float.parseFloat(value);
+ return floatValue >= mMin && floatValue <= mMax;
+ } catch (NumberFormatException e) {
+ return false;
+ }
+ }
+ }
+
/**
* @deprecated Use {@link android.provider.Settings.Global#STAY_ON_WHILE_PLUGGED_IN} instead
*/
@@ -1745,6 +1846,9 @@ public final class Settings {
*/
public static final String END_BUTTON_BEHAVIOR = "end_button_behavior";
+ private static final Validator END_BUTTON_BEHAVIOR_VALIDATOR =
+ new InclusiveIntegerRangeValidator(0, 3);
+
/**
* END_BUTTON_BEHAVIOR value for "go home".
* @hide
@@ -1769,6 +1873,8 @@ public final class Settings {
*/
public static final String ADVANCED_SETTINGS = "advanced_settings";
+ private static final Validator ADVANCED_SETTINGS_VALIDATOR = sBooleanValidator;
+
/**
* ADVANCED_SETTINGS default value.
* @hide
@@ -1868,6 +1974,8 @@ public final class Settings {
@Deprecated
public static final String WIFI_USE_STATIC_IP = "wifi_use_static_ip";
+ private static final Validator WIFI_USE_STATIC_IP_VALIDATOR = sBooleanValidator;
+
/**
* The static IP address.
* <p>
@@ -1878,6 +1986,8 @@ public final class Settings {
@Deprecated
public static final String WIFI_STATIC_IP = "wifi_static_ip";
+ private static final Validator WIFI_STATIC_IP_VALIDATOR = sLenientIpAddressValidator;
+
/**
* If using static IP, the gateway's IP address.
* <p>
@@ -1888,6 +1998,8 @@ public final class Settings {
@Deprecated
public static final String WIFI_STATIC_GATEWAY = "wifi_static_gateway";
+ private static final Validator WIFI_STATIC_GATEWAY_VALIDATOR = sLenientIpAddressValidator;
+
/**
* If using static IP, the net mask.
* <p>
@@ -1898,6 +2010,8 @@ public final class Settings {
@Deprecated
public static final String WIFI_STATIC_NETMASK = "wifi_static_netmask";
+ private static final Validator WIFI_STATIC_NETMASK_VALIDATOR = sLenientIpAddressValidator;
+
/**
* If using static IP, the primary DNS's IP address.
* <p>
@@ -1908,6 +2022,8 @@ public final class Settings {
@Deprecated
public static final String WIFI_STATIC_DNS1 = "wifi_static_dns1";
+ private static final Validator WIFI_STATIC_DNS1_VALIDATOR = sLenientIpAddressValidator;
+
/**
* If using static IP, the secondary DNS's IP address.
* <p>
@@ -1918,6 +2034,7 @@ public final class Settings {
@Deprecated
public static final String WIFI_STATIC_DNS2 = "wifi_static_dns2";
+ private static final Validator WIFI_STATIC_DNS2_VALIDATOR = sLenientIpAddressValidator;
/**
* Determines whether remote devices may discover and/or connect to
@@ -1930,6 +2047,9 @@ public final class Settings {
public static final String BLUETOOTH_DISCOVERABILITY =
"bluetooth_discoverability";
+ private static final Validator BLUETOOTH_DISCOVERABILITY_VALIDATOR =
+ new InclusiveIntegerRangeValidator(0, 2);
+
/**
* Bluetooth discoverability timeout. If this value is nonzero, then
* Bluetooth becomes discoverable for a certain number of seconds,
@@ -1938,6 +2058,9 @@ public final class Settings {
public static final String BLUETOOTH_DISCOVERABILITY_TIMEOUT =
"bluetooth_discoverability_timeout";
+ private static final Validator BLUETOOTH_DISCOVERABILITY_TIMEOUT_VALIDATOR =
+ sNonNegativeIntegerValidator;
+
/**
* @deprecated Use {@link android.provider.Settings.Secure#LOCK_PATTERN_ENABLED}
* instead
@@ -1961,7 +2084,6 @@ public final class Settings {
public static final String LOCK_PATTERN_TACTILE_FEEDBACK_ENABLED =
"lock_pattern_tactile_feedback_enabled";
-
/**
* A formatted string of the next alarm that is set, or the empty string
* if there is no alarm set.
@@ -1971,11 +2093,31 @@ public final class Settings {
@Deprecated
public static final String NEXT_ALARM_FORMATTED = "next_alarm_formatted";
+ private static final Validator NEXT_ALARM_FORMATTED_VALIDATOR = new Validator() {
+ private static final int MAX_LENGTH = 1000;
+ @Override
+ public boolean validate(String value) {
+ // TODO: No idea what the correct format is.
+ return value == null || value.length() > MAX_LENGTH;
+ }
+ };
+
/**
* Scaling factor for fonts, float.
*/
public static final String FONT_SCALE = "font_scale";
+ private static final Validator FONT_SCALE_VALIDATOR = new Validator() {
+ @Override
+ public boolean validate(String value) {
+ try {
+ return Float.parseFloat(value) >= 0;
+ } catch (NumberFormatException e) {
+ return false;
+ }
+ }
+ };
+
/**
* Name of an application package to be debugged.
*
@@ -2000,6 +2142,8 @@ public final class Settings {
@Deprecated
public static final String DIM_SCREEN = "dim_screen";
+ private static final Validator DIM_SCREEN_VALIDATOR = sBooleanValidator;
+
/**
* The amount of time in milliseconds before the device goes to sleep or begins
* to dream after a period of inactivity. This value is also known as the
@@ -2008,16 +2152,23 @@ public final class Settings {
*/
public static final String SCREEN_OFF_TIMEOUT = "screen_off_timeout";
+ private static final Validator SCREEN_OFF_TIMEOUT_VALIDATOR = sNonNegativeIntegerValidator;
+
/**
* The screen backlight brightness between 0 and 255.
*/
public static final String SCREEN_BRIGHTNESS = "screen_brightness";
+ private static final Validator SCREEN_BRIGHTNESS_VALIDATOR =
+ new InclusiveIntegerRangeValidator(0, 255);
+
/**
* Control whether to enable automatic brightness mode.
*/
public static final String SCREEN_BRIGHTNESS_MODE = "screen_brightness_mode";
+ private static final Validator SCREEN_BRIGHTNESS_MODE_VALIDATOR = sBooleanValidator;
+
/**
* Adjustment to auto-brightness to make it generally more (>0.0 <1.0)
* or less (<0.0 >-1.0) bright.
@@ -2025,6 +2176,9 @@ public final class Settings {
*/
public static final String SCREEN_AUTO_BRIGHTNESS_ADJ = "screen_auto_brightness_adj";
+ private static final Validator SCREEN_AUTO_BRIGHTNESS_ADJ_VALIDATOR =
+ new InclusiveFloatRangeValidator(-1, 1);
+
/**
* SCREEN_BRIGHTNESS_MODE value for manual mode.
*/
@@ -2060,12 +2214,18 @@ public final class Settings {
*/
public static final String MODE_RINGER_STREAMS_AFFECTED = "mode_ringer_streams_affected";
- /**
+ private static final Validator MODE_RINGER_STREAMS_AFFECTED_VALIDATOR =
+ sNonNegativeIntegerValidator;
+
+ /**
* Determines which streams are affected by mute. The
* stream type's bit should be set to 1 if it should be muted when a mute request
* is received.
*/
- public static final String MUTE_STREAMS_AFFECTED = "mute_streams_affected";
+ public static final String MUTE_STREAMS_AFFECTED = "mute_streams_affected";
+
+ private static final Validator MUTE_STREAMS_AFFECTED_VALIDATOR =
+ sNonNegativeIntegerValidator;
/**
* Whether vibrate is on for different events. This is used internally,
@@ -2073,6 +2233,8 @@ public final class Settings {
*/
public static final String VIBRATE_ON = "vibrate_on";
+ private static final Validator VIBRATE_ON_VALIDATOR = sBooleanValidator;
+
/**
* If 1, redirects the system vibrator to all currently attached input devices
* that support vibration. If there are no such input devices, then the system
@@ -2087,54 +2249,72 @@ public final class Settings {
*/
public static final String VIBRATE_INPUT_DEVICES = "vibrate_input_devices";
+ private static final Validator VIBRATE_INPUT_DEVICES_VALIDATOR = sBooleanValidator;
+
/**
* Ringer volume. This is used internally, changing this value will not
* change the volume. See AudioManager.
*/
public static final String VOLUME_RING = "volume_ring";
+ private static final Validator VOLUME_RING_VALIDATOR = sVolumeValidator;
+
/**
* System/notifications volume. This is used internally, changing this
* value will not change the volume. See AudioManager.
*/
public static final String VOLUME_SYSTEM = "volume_system";
+ private static final Validator VOLUME_SYSTEM_VALIDATOR = sVolumeValidator;
+
/**
* Voice call volume. This is used internally, changing this value will
* not change the volume. See AudioManager.
*/
public static final String VOLUME_VOICE = "volume_voice";
+ private static final Validator VOLUME_VOICE_VALIDATOR = sVolumeValidator;
+
/**
* Music/media/gaming volume. This is used internally, changing this
* value will not change the volume. See AudioManager.
*/
public static final String VOLUME_MUSIC = "volume_music";
+ private static final Validator VOLUME_MUSIC_VALIDATOR = sVolumeValidator;
+
/**
* Alarm volume. This is used internally, changing this
* value will not change the volume. See AudioManager.
*/
public static final String VOLUME_ALARM = "volume_alarm";
+ private static final Validator VOLUME_ALARM_VALIDATOR = sVolumeValidator;
+
/**
* Notification volume. This is used internally, changing this
* value will not change the volume. See AudioManager.
*/
public static final String VOLUME_NOTIFICATION = "volume_notification";
+ private static final Validator VOLUME_NOTIFICATION_VALIDATOR = sVolumeValidator;
+
/**
* Bluetooth Headset volume. This is used internally, changing this value will
* not change the volume. See AudioManager.
*/
public static final String VOLUME_BLUETOOTH_SCO = "volume_bluetooth_sco";
+ private static final Validator VOLUME_BLUETOOTH_SCO_VALIDATOR = sVolumeValidator;
+
/**
* Master volume (float in the range 0.0f to 1.0f).
* @hide
*/
public static final String VOLUME_MASTER = "volume_master";
+ private static final Validator VOLUME_MASTER_VALIDATOR = sVolumeValidator;
+
/**
* Master volume mute (int 1 = mute, 0 = not muted).
*
@@ -2142,6 +2322,8 @@ public final class Settings {
*/
public static final String VOLUME_MASTER_MUTE = "volume_master_mute";
+ private static final Validator VOLUME_MASTER_MUTE_VALIDATOR = sBooleanValidator;
+
/**
* Microphone mute (int 1 = mute, 0 = not muted).
*
@@ -2149,6 +2331,8 @@ public final class Settings {
*/
public static final String MICROPHONE_MUTE = "microphone_mute";
+ private static final Validator MICROPHONE_MUTE_VALIDATOR = sBooleanValidator;
+
/**
* Whether the notifications should use the ring volume (value of 1) or
* a separate notification volume (value of 0). In most cases, users
@@ -2167,6 +2351,8 @@ public final class Settings {
public static final String NOTIFICATIONS_USE_RING_VOLUME =
"notifications_use_ring_volume";
+ private static final Validator NOTIFICATIONS_USE_RING_VOLUME_VALIDATOR = sBooleanValidator;
+
/**
* Whether silent mode should allow vibration feedback. This is used
* internally in AudioService and the Sound settings activity to
@@ -2181,6 +2367,8 @@ public final class Settings {
*/
public static final String VIBRATE_IN_SILENT = "vibrate_in_silent";
+ private static final Validator VIBRATE_IN_SILENT_VALIDATOR = sBooleanValidator;
+
/**
* The mapping of stream type (integer) to its setting.
*/
@@ -2207,6 +2395,8 @@ public final class Settings {
*/
public static final String RINGTONE = "ringtone";
+ private static final Validator RINGTONE_VALIDATOR = sUriValidator;
+
/**
* A {@link Uri} that will point to the current default ringtone at any
* given time.
@@ -2225,6 +2415,8 @@ public final class Settings {
*/
public static final String NOTIFICATION_SOUND = "notification_sound";
+ private static final Validator NOTIFICATION_SOUND_VALIDATOR = sUriValidator;
+
/**
* A {@link Uri} that will point to the current default notification
* sound at any given time.
@@ -2241,6 +2433,8 @@ public final class Settings {
*/
public static final String ALARM_ALERT = "alarm_alert";
+ private static final Validator ALARM_ALERT_VALIDATOR = sUriValidator;
+
/**
* A {@link Uri} that will point to the current default alarm alert at
* any given time.
@@ -2256,30 +2450,52 @@ public final class Settings {
*/
public static final String MEDIA_BUTTON_RECEIVER = "media_button_receiver";
+ private static final Validator MEDIA_BUTTON_RECEIVER_VALIDATOR = new Validator() {
+ @Override
+ public boolean validate(String value) {
+ try {
+ ComponentName.unflattenFromString(value);
+ return true;
+ } catch (NullPointerException e) {
+ return false;
+ }
+ }
+ };
+
/**
* Setting to enable Auto Replace (AutoText) in text editors. 1 = On, 0 = Off
*/
public static final String TEXT_AUTO_REPLACE = "auto_replace";
+ private static final Validator TEXT_AUTO_REPLACE_VALIDATOR = sBooleanValidator;
+
/**
* Setting to enable Auto Caps in text editors. 1 = On, 0 = Off
*/
public static final String TEXT_AUTO_CAPS = "auto_caps";
+ private static final Validator TEXT_AUTO_CAPS_VALIDATOR = sBooleanValidator;
+
/**
* Setting to enable Auto Punctuate in text editors. 1 = On, 0 = Off. This
* feature converts two spaces to a "." and space.
*/
public static final String TEXT_AUTO_PUNCTUATE = "auto_punctuate";
+ private static final Validator TEXT_AUTO_PUNCTUATE_VALIDATOR = sBooleanValidator;
+
/**
* Setting to showing password characters in text editors. 1 = On, 0 = Off
*/
public static final String TEXT_SHOW_PASSWORD = "show_password";
+ private static final Validator TEXT_SHOW_PASSWORD_VALIDATOR = sBooleanValidator;
+
public static final String SHOW_GTALK_SERVICE_STATUS =
"SHOW_GTALK_SERVICE_STATUS";
+ private static final Validator SHOW_GTALK_SERVICE_STATUS_VALIDATOR = sBooleanValidator;
+
/**
* Name of activity to use for wallpaper on the home screen.
*
@@ -2288,6 +2504,18 @@ public final class Settings {
@Deprecated
public static final String WALLPAPER_ACTIVITY = "wallpaper_activity";
+ private static final Validator WALLPAPER_ACTIVITY_VALIDATOR = new Validator() {
+ private static final int MAX_LENGTH = 1000;
+
+ @Override
+ public boolean validate(String value) {
+ if (value != null && value.length() > MAX_LENGTH) {
+ return false;
+ }
+ return ComponentName.unflattenFromString(value) != null;
+ }
+ };
+
/**
* @deprecated Use {@link android.provider.Settings.Global#AUTO_TIME}
* instead
@@ -2309,6 +2537,10 @@ public final class Settings {
*/
public static final String TIME_12_24 = "time_12_24";
+ /** @hide */
+ public static final Validator TIME_12_24_VALIDATOR =
+ new DiscreteValueValidator(new String[] {"12", "24"});
+
/**
* Date format string
* mm/dd/yyyy
@@ -2317,6 +2549,19 @@ public final class Settings {
*/
public static final String DATE_FORMAT = "date_format";
+ /** @hide */
+ public static final Validator DATE_FORMAT_VALIDATOR = new Validator() {
+ @Override
+ public boolean validate(String value) {
+ try {
+ new SimpleDateFormat(value);
+ return true;
+ } catch (IllegalArgumentException e) {
+ return false;
+ }
+ }
+ };
+
/**
* Whether the setup wizard has been run before (on first boot), or if
* it still needs to be run.
@@ -2326,6 +2571,9 @@ public final class Settings {
*/
public static final String SETUP_WIZARD_HAS_RUN = "setup_wizard_has_run";
+ /** @hide */
+ public static final Validator SETUP_WIZARD_HAS_RUN_VALIDATOR = sBooleanValidator;
+
/**
* Scaling factor for normal window animations. Setting to 0 will disable window
* animations.
@@ -2362,6 +2610,9 @@ public final class Settings {
*/
public static final String ACCELEROMETER_ROTATION = "accelerometer_rotation";
+ /** @hide */
+ public static final Validator ACCELEROMETER_ROTATION_VALIDATOR = sBooleanValidator;
+
/**
* Default screen rotation when no other policy applies.
* When {@link #ACCELEROMETER_ROTATION} is zero and no on-screen Activity expresses a
@@ -2372,6 +2623,10 @@ public final class Settings {
*/
public static final String USER_ROTATION = "user_rotation";
+ /** @hide */
+ public static final Validator USER_ROTATION_VALIDATOR =
+ new InclusiveIntegerRangeValidator(0, 3);
+
/**
* Control whether the rotation lock toggle in the System UI should be hidden.
* Typically this is done for accessibility purposes to make it harder for
@@ -2386,6 +2641,10 @@ public final class Settings {
public static final String HIDE_ROTATION_LOCK_TOGGLE_FOR_ACCESSIBILITY =
"hide_rotation_lock_toggle_for_accessibility";
+ /** @hide */
+ public static final Validator HIDE_ROTATION_LOCK_TOGGLE_FOR_ACCESSIBILITY_VALIDATOR =
+ sBooleanValidator;
+
/**
* Whether the phone vibrates when it is ringing due to an incoming call. This will
* be used by Phone and Setting apps; it shouldn't affect other apps.
@@ -2400,12 +2659,18 @@ public final class Settings {
*/
public static final String VIBRATE_WHEN_RINGING = "vibrate_when_ringing";
+ /** @hide */
+ public static final Validator VIBRATE_WHEN_RINGING_VALIDATOR = sBooleanValidator;
+
/**
* Whether the audible DTMF tones are played by the dialer when dialing. The value is
* boolean (1 or 0).
*/
public static final String DTMF_TONE_WHEN_DIALING = "dtmf_tone";
+ /** @hide */
+ public static final Validator DTMF_TONE_WHEN_DIALING_VALIDATOR = sBooleanValidator;
+
/**
* CDMA only settings
* DTMF tone type played by the dialer when dialing.
@@ -2415,6 +2680,9 @@ public final class Settings {
*/
public static final String DTMF_TONE_TYPE_WHEN_DIALING = "dtmf_tone_type";
+ /** @hide */
+ public static final Validator DTMF_TONE_TYPE_WHEN_DIALING_VALIDATOR = sBooleanValidator;
+
/**
* Whether the hearing aid is enabled. The value is
* boolean (1 or 0).
@@ -2422,6 +2690,9 @@ public final class Settings {
*/
public static final String HEARING_AID = "hearing_aid";
+ /** @hide */
+ public static final Validator HEARING_AID_VALIDATOR = sBooleanValidator;
+
/**
* CDMA only settings
* TTY Mode
@@ -2433,18 +2704,27 @@ public final class Settings {
*/
public static final String TTY_MODE = "tty_mode";
+ /** @hide */
+ public static final Validator TTY_MODE_VALIDATOR = new InclusiveIntegerRangeValidator(0, 3);
+
/**
* Whether the sounds effects (key clicks, lid open ...) are enabled. The value is
* boolean (1 or 0).
*/
public static final String SOUND_EFFECTS_ENABLED = "sound_effects_enabled";
+ /** @hide */
+ public static final Validator SOUND_EFFECTS_ENABLED_VALIDATOR = sBooleanValidator;
+
/**
* Whether the haptic feedback (long presses, ...) are enabled. The value is
* boolean (1 or 0).
*/
public static final String HAPTIC_FEEDBACK_ENABLED = "haptic_feedback_enabled";
+ /** @hide */
+ public static final Validator HAPTIC_FEEDBACK_ENABLED_VALIDATOR = sBooleanValidator;
+
/**
* @deprecated Each application that shows web suggestions should have its own
* setting for this.
@@ -2452,6 +2732,9 @@ public final class Settings {
@Deprecated
public static final String SHOW_WEB_SUGGESTIONS = "show_web_suggestions";
+ /** @hide */
+ public static final Validator SHOW_WEB_SUGGESTIONS_VALIDATOR = sBooleanValidator;
+
/**
* Whether the notification LED should repeatedly flash when a notification is
* pending. The value is boolean (1 or 0).
@@ -2459,6 +2742,9 @@ public final class Settings {
*/
public static final String NOTIFICATION_LIGHT_PULSE = "notification_light_pulse";
+ /** @hide */
+ public static final Validator NOTIFICATION_LIGHT_PULSE_VALIDATOR = sBooleanValidator;
+
/**
* Show pointer location on screen?
* 0 = no
@@ -2467,6 +2753,9 @@ public final class Settings {
*/
public static final String POINTER_LOCATION = "pointer_location";
+ /** @hide */
+ public static final Validator POINTER_LOCATION_VALIDATOR = sBooleanValidator;
+
/**
* Show touch positions on screen?
* 0 = no
@@ -2475,9 +2764,12 @@ public final class Settings {
*/
public static final String SHOW_TOUCHES = "show_touches";
+ /** @hide */
+ public static final Validator SHOW_TOUCHES_VALIDATOR = sBooleanValidator;
+
/**
* Log raw orientation data from
- * {@link com.android.internal.policy.impl.WindowOrientationListener} for use with the
+ * {@link com.android.server.policy.WindowOrientationListener} for use with the
* orientationplot.py tool.
* 0 = no
* 1 = yes
@@ -2486,6 +2778,9 @@ public final class Settings {
public static final String WINDOW_ORIENTATION_LISTENER_LOG =
"window_orientation_listener_log";
+ /** @hide */
+ public static final Validator WINDOW_ORIENTATION_LISTENER_LOG_VALIDATOR = sBooleanValidator;
+
/**
* @deprecated Use {@link android.provider.Settings.Global#POWER_SOUNDS_ENABLED}
* instead
@@ -2508,12 +2803,18 @@ public final class Settings {
*/
public static final String LOCKSCREEN_SOUNDS_ENABLED = "lockscreen_sounds_enabled";
+ /** @hide */
+ public static final Validator LOCKSCREEN_SOUNDS_ENABLED_VALIDATOR = sBooleanValidator;
+
/**
* Whether the lockscreen should be completely disabled.
* @hide
*/
public static final String LOCKSCREEN_DISABLED = "lockscreen.disabled";
+ /** @hide */
+ public static final Validator LOCKSCREEN_DISABLED_VALIDATOR = sBooleanValidator;
+
/**
* @deprecated Use {@link android.provider.Settings.Global#LOW_BATTERY_SOUND}
* instead
@@ -2578,6 +2879,9 @@ public final class Settings {
*/
public static final String SIP_RECEIVE_CALLS = "sip_receive_calls";
+ /** @hide */
+ public static final Validator SIP_RECEIVE_CALLS_VALIDATOR = sBooleanValidator;
+
/**
* Call Preference String.
* "SIP_ALWAYS" : Always use SIP with network access
@@ -2586,18 +2890,28 @@ public final class Settings {
*/
public static final String SIP_CALL_OPTIONS = "sip_call_options";
+ /** @hide */
+ public static final Validator SIP_CALL_OPTIONS_VALIDATOR = new DiscreteValueValidator(
+ new String[] {"SIP_ALWAYS", "SIP_ADDRESS_ONLY"});
+
/**
* One of the sip call options: Always use SIP with network access.
* @hide
*/
public static final String SIP_ALWAYS = "SIP_ALWAYS";
+ /** @hide */
+ public static final Validator SIP_ALWAYS_VALIDATOR = sBooleanValidator;
+
/**
* One of the sip call options: Only if destination is a SIP address.
* @hide
*/
public static final String SIP_ADDRESS_ONLY = "SIP_ADDRESS_ONLY";
+ /** @hide */
+ public static final Validator SIP_ADDRESS_ONLY_VALIDATOR = sBooleanValidator;
+
/**
* @deprecated Use SIP_ALWAYS or SIP_ADDRESS_ONLY instead. Formerly used to indicate that
* the user should be prompted each time a call is made whether it should be placed using
@@ -2608,6 +2922,9 @@ public final class Settings {
@Deprecated
public static final String SIP_ASK_ME_EACH_TIME = "SIP_ASK_ME_EACH_TIME";
+ /** @hide */
+ public static final Validator SIP_ASK_ME_EACH_TIME_VALIDATOR = sBooleanValidator;
+
/**
* Pointer speed setting.
* This is an integer value in a range between -7 and +7, so there are 15 possible values.
@@ -2618,12 +2935,19 @@ public final class Settings {
*/
public static final String POINTER_SPEED = "pointer_speed";
+ /** @hide */
+ public static final Validator POINTER_SPEED_VALIDATOR =
+ new InclusiveFloatRangeValidator(-7, 7);
+
/**
* Whether lock-to-app will be triggered by long-press on recents.
* @hide
*/
public static final String LOCK_TO_APP_ENABLED = "lock_to_app_enabled";
+ /** @hide */
+ public static final Validator LOCK_TO_APP_ENABLED_VALIDATOR = sBooleanValidator;
+
/**
* I am the lolrus.
* <p>
@@ -2633,6 +2957,16 @@ public final class Settings {
*/
public static final String EGG_MODE = "egg_mode";
+ /** @hide */
+ public static final Validator EGG_MODE_VALIDATOR = sBooleanValidator;
+
+ /**
+ * IMPORTANT: If you add a new public settings you also have to add it to
+ * PUBLIC_SETTINGS below. If the new setting is hidden you have to add
+ * it to PRIVATE_SETTINGS below. Also add a validator that can validate
+ * the setting value. See an example above.
+ */
+
/**
* Settings to backup. This is here so that it's in the same place as the settings
* keys and easy to update.
@@ -2703,17 +3037,207 @@ public final class Settings {
};
/**
+ * These are all pulbic system settings
+ *
+ * @hide
+ */
+ public static final Set<String> PUBLIC_SETTINGS = new ArraySet<>();
+ static {
+ PUBLIC_SETTINGS.add(END_BUTTON_BEHAVIOR);
+ PUBLIC_SETTINGS.add(WIFI_USE_STATIC_IP);
+ PUBLIC_SETTINGS.add(WIFI_STATIC_IP);
+ PUBLIC_SETTINGS.add(WIFI_STATIC_GATEWAY);
+ PUBLIC_SETTINGS.add(WIFI_STATIC_NETMASK);
+ PUBLIC_SETTINGS.add(WIFI_STATIC_DNS1);
+ PUBLIC_SETTINGS.add(WIFI_STATIC_DNS2);
+ PUBLIC_SETTINGS.add(BLUETOOTH_DISCOVERABILITY);
+ PUBLIC_SETTINGS.add(BLUETOOTH_DISCOVERABILITY_TIMEOUT);
+ PUBLIC_SETTINGS.add(NEXT_ALARM_FORMATTED);
+ PUBLIC_SETTINGS.add(FONT_SCALE);
+ PUBLIC_SETTINGS.add(DIM_SCREEN);
+ PUBLIC_SETTINGS.add(SCREEN_OFF_TIMEOUT);
+ PUBLIC_SETTINGS.add(SCREEN_BRIGHTNESS);
+ PUBLIC_SETTINGS.add(SCREEN_BRIGHTNESS_MODE);
+ PUBLIC_SETTINGS.add(MODE_RINGER_STREAMS_AFFECTED);
+ PUBLIC_SETTINGS.add(MUTE_STREAMS_AFFECTED);
+ PUBLIC_SETTINGS.add(VIBRATE_ON);
+ PUBLIC_SETTINGS.add(VOLUME_RING);
+ PUBLIC_SETTINGS.add(VOLUME_SYSTEM);
+ PUBLIC_SETTINGS.add(VOLUME_VOICE);
+ PUBLIC_SETTINGS.add(VOLUME_MUSIC);
+ PUBLIC_SETTINGS.add(VOLUME_ALARM);
+ PUBLIC_SETTINGS.add(VOLUME_NOTIFICATION);
+ PUBLIC_SETTINGS.add(VOLUME_BLUETOOTH_SCO);
+ PUBLIC_SETTINGS.add(RINGTONE);
+ PUBLIC_SETTINGS.add(NOTIFICATION_SOUND);
+ PUBLIC_SETTINGS.add(ALARM_ALERT);
+ PUBLIC_SETTINGS.add(TEXT_AUTO_REPLACE);
+ PUBLIC_SETTINGS.add(TEXT_AUTO_CAPS);
+ PUBLIC_SETTINGS.add(TEXT_AUTO_PUNCTUATE);
+ PUBLIC_SETTINGS.add(TEXT_SHOW_PASSWORD);
+ PUBLIC_SETTINGS.add(SHOW_GTALK_SERVICE_STATUS);
+ PUBLIC_SETTINGS.add(WALLPAPER_ACTIVITY);
+ PUBLIC_SETTINGS.add(TIME_12_24);
+ PUBLIC_SETTINGS.add(DATE_FORMAT);
+ PUBLIC_SETTINGS.add(SETUP_WIZARD_HAS_RUN);
+ PUBLIC_SETTINGS.add(ACCELEROMETER_ROTATION);
+ PUBLIC_SETTINGS.add(USER_ROTATION);
+ PUBLIC_SETTINGS.add(DTMF_TONE_WHEN_DIALING);
+ PUBLIC_SETTINGS.add(SOUND_EFFECTS_ENABLED);
+ PUBLIC_SETTINGS.add(HAPTIC_FEEDBACK_ENABLED);
+ PUBLIC_SETTINGS.add(SHOW_WEB_SUGGESTIONS);
+ }
+
+ /**
+ * These are all hidden system settings.
+ *
+ * @hide
+ */
+ public static final Set<String> PRIVATE_SETTINGS = new ArraySet<>();
+ static {
+ PRIVATE_SETTINGS.add(WIFI_USE_STATIC_IP);
+ PRIVATE_SETTINGS.add(END_BUTTON_BEHAVIOR);
+ PRIVATE_SETTINGS.add(ADVANCED_SETTINGS);
+ PRIVATE_SETTINGS.add(SCREEN_AUTO_BRIGHTNESS_ADJ);
+ PRIVATE_SETTINGS.add(VIBRATE_INPUT_DEVICES);
+ PRIVATE_SETTINGS.add(VOLUME_MASTER);
+ PRIVATE_SETTINGS.add(VOLUME_MASTER_MUTE);
+ PRIVATE_SETTINGS.add(MICROPHONE_MUTE);
+ PRIVATE_SETTINGS.add(NOTIFICATIONS_USE_RING_VOLUME);
+ PRIVATE_SETTINGS.add(VIBRATE_IN_SILENT);
+ PRIVATE_SETTINGS.add(MEDIA_BUTTON_RECEIVER);
+ PRIVATE_SETTINGS.add(HIDE_ROTATION_LOCK_TOGGLE_FOR_ACCESSIBILITY);
+ PRIVATE_SETTINGS.add(VIBRATE_WHEN_RINGING);
+ PRIVATE_SETTINGS.add(DTMF_TONE_TYPE_WHEN_DIALING);
+ PRIVATE_SETTINGS.add(HEARING_AID);
+ PRIVATE_SETTINGS.add(TTY_MODE);
+ PRIVATE_SETTINGS.add(NOTIFICATION_LIGHT_PULSE);
+ PRIVATE_SETTINGS.add(POINTER_LOCATION);
+ PRIVATE_SETTINGS.add(SHOW_TOUCHES);
+ PRIVATE_SETTINGS.add(WINDOW_ORIENTATION_LISTENER_LOG);
+ PRIVATE_SETTINGS.add(POWER_SOUNDS_ENABLED);
+ PRIVATE_SETTINGS.add(DOCK_SOUNDS_ENABLED);
+ PRIVATE_SETTINGS.add(LOCKSCREEN_SOUNDS_ENABLED);
+ PRIVATE_SETTINGS.add(LOCKSCREEN_DISABLED);
+ PRIVATE_SETTINGS.add(LOW_BATTERY_SOUND);
+ PRIVATE_SETTINGS.add(DESK_DOCK_SOUND);
+ PRIVATE_SETTINGS.add(DESK_UNDOCK_SOUND);
+ PRIVATE_SETTINGS.add(CAR_DOCK_SOUND);
+ PRIVATE_SETTINGS.add(CAR_UNDOCK_SOUND);
+ PRIVATE_SETTINGS.add(LOCK_SOUND);
+ PRIVATE_SETTINGS.add(UNLOCK_SOUND);
+ PRIVATE_SETTINGS.add(SIP_RECEIVE_CALLS);
+ PRIVATE_SETTINGS.add(SIP_CALL_OPTIONS);
+ PRIVATE_SETTINGS.add(SIP_ALWAYS);
+ PRIVATE_SETTINGS.add(SIP_ADDRESS_ONLY);
+ PRIVATE_SETTINGS.add(SIP_ASK_ME_EACH_TIME);
+ PRIVATE_SETTINGS.add(POINTER_SPEED);
+ PRIVATE_SETTINGS.add(LOCK_TO_APP_ENABLED);
+ PRIVATE_SETTINGS.add(EGG_MODE);
+ }
+
+ /**
+ * These are all pulbic system settings
+ *
+ * @hide
+ */
+ public static final Map<String, Validator> VALIDATORS = new ArrayMap<>();
+ static {
+ VALIDATORS.put(END_BUTTON_BEHAVIOR,END_BUTTON_BEHAVIOR_VALIDATOR);
+ VALIDATORS.put(WIFI_USE_STATIC_IP, WIFI_USE_STATIC_IP_VALIDATOR);
+ VALIDATORS.put(BLUETOOTH_DISCOVERABILITY, BLUETOOTH_DISCOVERABILITY_VALIDATOR);
+ VALIDATORS.put(BLUETOOTH_DISCOVERABILITY_TIMEOUT,
+ BLUETOOTH_DISCOVERABILITY_TIMEOUT_VALIDATOR);
+ VALIDATORS.put(NEXT_ALARM_FORMATTED, NEXT_ALARM_FORMATTED_VALIDATOR);
+ VALIDATORS.put(FONT_SCALE, FONT_SCALE_VALIDATOR);
+ VALIDATORS.put(DIM_SCREEN, DIM_SCREEN_VALIDATOR);
+ VALIDATORS.put(SCREEN_OFF_TIMEOUT, SCREEN_OFF_TIMEOUT_VALIDATOR);
+ VALIDATORS.put(SCREEN_BRIGHTNESS, SCREEN_BRIGHTNESS_VALIDATOR);
+ VALIDATORS.put(SCREEN_BRIGHTNESS_MODE, SCREEN_BRIGHTNESS_MODE_VALIDATOR);
+ VALIDATORS.put(MODE_RINGER_STREAMS_AFFECTED, MODE_RINGER_STREAMS_AFFECTED_VALIDATOR);
+ VALIDATORS.put(MUTE_STREAMS_AFFECTED, MUTE_STREAMS_AFFECTED_VALIDATOR);
+ VALIDATORS.put(VIBRATE_ON, VIBRATE_ON_VALIDATOR);
+ VALIDATORS.put(VOLUME_RING, VOLUME_RING_VALIDATOR);
+ VALIDATORS.put(VOLUME_SYSTEM, VOLUME_SYSTEM_VALIDATOR);
+ VALIDATORS.put(VOLUME_VOICE, VOLUME_VOICE_VALIDATOR);
+ VALIDATORS.put(VOLUME_MUSIC, VOLUME_MUSIC_VALIDATOR);
+ VALIDATORS.put(VOLUME_ALARM, VOLUME_ALARM_VALIDATOR);
+ VALIDATORS.put(VOLUME_NOTIFICATION, VOLUME_NOTIFICATION_VALIDATOR);
+ VALIDATORS.put(VOLUME_BLUETOOTH_SCO, VOLUME_BLUETOOTH_SCO_VALIDATOR);
+ VALIDATORS.put(RINGTONE, RINGTONE_VALIDATOR);
+ VALIDATORS.put(NOTIFICATION_SOUND, NOTIFICATION_SOUND_VALIDATOR);
+ VALIDATORS.put(ALARM_ALERT, ALARM_ALERT_VALIDATOR);
+ VALIDATORS.put(TEXT_AUTO_REPLACE, TEXT_AUTO_REPLACE_VALIDATOR);
+ VALIDATORS.put(TEXT_AUTO_CAPS, TEXT_AUTO_CAPS_VALIDATOR);
+ VALIDATORS.put(TEXT_AUTO_PUNCTUATE, TEXT_AUTO_PUNCTUATE_VALIDATOR);
+ VALIDATORS.put(TEXT_SHOW_PASSWORD, TEXT_SHOW_PASSWORD_VALIDATOR);
+ VALIDATORS.put(SHOW_GTALK_SERVICE_STATUS, SHOW_GTALK_SERVICE_STATUS_VALIDATOR);
+ VALIDATORS.put(WALLPAPER_ACTIVITY, WALLPAPER_ACTIVITY_VALIDATOR);
+ VALIDATORS.put(TIME_12_24, TIME_12_24_VALIDATOR);
+ VALIDATORS.put(DATE_FORMAT, DATE_FORMAT_VALIDATOR);
+ VALIDATORS.put(SETUP_WIZARD_HAS_RUN, SETUP_WIZARD_HAS_RUN_VALIDATOR);
+ VALIDATORS.put(ACCELEROMETER_ROTATION, ACCELEROMETER_ROTATION_VALIDATOR);
+ VALIDATORS.put(USER_ROTATION, USER_ROTATION_VALIDATOR);
+ VALIDATORS.put(DTMF_TONE_WHEN_DIALING, DTMF_TONE_WHEN_DIALING_VALIDATOR);
+ VALIDATORS.put(SOUND_EFFECTS_ENABLED, SOUND_EFFECTS_ENABLED_VALIDATOR);
+ VALIDATORS.put(HAPTIC_FEEDBACK_ENABLED, HAPTIC_FEEDBACK_ENABLED_VALIDATOR);
+ VALIDATORS.put(SHOW_WEB_SUGGESTIONS, SHOW_WEB_SUGGESTIONS_VALIDATOR);
+ VALIDATORS.put(WIFI_USE_STATIC_IP, WIFI_USE_STATIC_IP_VALIDATOR);
+ VALIDATORS.put(END_BUTTON_BEHAVIOR, END_BUTTON_BEHAVIOR_VALIDATOR);
+ VALIDATORS.put(ADVANCED_SETTINGS, ADVANCED_SETTINGS_VALIDATOR);
+ VALIDATORS.put(SCREEN_AUTO_BRIGHTNESS_ADJ, SCREEN_AUTO_BRIGHTNESS_ADJ_VALIDATOR);
+ VALIDATORS.put(VIBRATE_INPUT_DEVICES, VIBRATE_INPUT_DEVICES_VALIDATOR);
+ VALIDATORS.put(VOLUME_MASTER, VOLUME_MASTER_VALIDATOR);
+ VALIDATORS.put(VOLUME_MASTER_MUTE, VOLUME_MASTER_MUTE_VALIDATOR);
+ VALIDATORS.put(MICROPHONE_MUTE, MICROPHONE_MUTE_VALIDATOR);
+ VALIDATORS.put(NOTIFICATIONS_USE_RING_VOLUME, NOTIFICATIONS_USE_RING_VOLUME_VALIDATOR);
+ VALIDATORS.put(VIBRATE_IN_SILENT, VIBRATE_IN_SILENT_VALIDATOR);
+ VALIDATORS.put(MEDIA_BUTTON_RECEIVER, MEDIA_BUTTON_RECEIVER_VALIDATOR);
+ VALIDATORS.put(HIDE_ROTATION_LOCK_TOGGLE_FOR_ACCESSIBILITY,
+ HIDE_ROTATION_LOCK_TOGGLE_FOR_ACCESSIBILITY_VALIDATOR);
+ VALIDATORS.put(VIBRATE_WHEN_RINGING, VIBRATE_WHEN_RINGING_VALIDATOR);
+ VALIDATORS.put(DTMF_TONE_TYPE_WHEN_DIALING, DTMF_TONE_TYPE_WHEN_DIALING_VALIDATOR);
+ VALIDATORS.put(HEARING_AID, HEARING_AID_VALIDATOR);
+ VALIDATORS.put(TTY_MODE, TTY_MODE_VALIDATOR);
+ VALIDATORS.put(NOTIFICATION_LIGHT_PULSE, NOTIFICATION_LIGHT_PULSE_VALIDATOR);
+ VALIDATORS.put(POINTER_LOCATION, POINTER_LOCATION_VALIDATOR);
+ VALIDATORS.put(SHOW_TOUCHES, SHOW_TOUCHES_VALIDATOR);
+ VALIDATORS.put(WINDOW_ORIENTATION_LISTENER_LOG,
+ WINDOW_ORIENTATION_LISTENER_LOG_VALIDATOR);
+ VALIDATORS.put(LOCKSCREEN_SOUNDS_ENABLED, LOCKSCREEN_SOUNDS_ENABLED_VALIDATOR);
+ VALIDATORS.put(LOCKSCREEN_DISABLED, LOCKSCREEN_DISABLED_VALIDATOR);
+ VALIDATORS.put(SIP_RECEIVE_CALLS, SIP_RECEIVE_CALLS_VALIDATOR);
+ VALIDATORS.put(SIP_CALL_OPTIONS, SIP_CALL_OPTIONS_VALIDATOR);
+ VALIDATORS.put(SIP_ALWAYS, SIP_ALWAYS_VALIDATOR);
+ VALIDATORS.put(SIP_ADDRESS_ONLY, SIP_ADDRESS_ONLY_VALIDATOR);
+ VALIDATORS.put(SIP_ASK_ME_EACH_TIME, SIP_ASK_ME_EACH_TIME_VALIDATOR);
+ VALIDATORS.put(POINTER_SPEED, POINTER_SPEED_VALIDATOR);
+ VALIDATORS.put(LOCK_TO_APP_ENABLED, LOCK_TO_APP_ENABLED_VALIDATOR);
+ VALIDATORS.put(EGG_MODE, EGG_MODE_VALIDATOR);
+ VALIDATORS.put(WIFI_STATIC_IP, WIFI_STATIC_IP_VALIDATOR);
+ VALIDATORS.put(WIFI_STATIC_GATEWAY, WIFI_STATIC_GATEWAY_VALIDATOR);
+ VALIDATORS.put(WIFI_STATIC_NETMASK, WIFI_STATIC_NETMASK_VALIDATOR);
+ VALIDATORS.put(WIFI_STATIC_DNS1, WIFI_STATIC_DNS1_VALIDATOR);
+ VALIDATORS.put(WIFI_STATIC_DNS2, WIFI_STATIC_DNS2_VALIDATOR);
+ }
+
+ /**
* These entries are considered common between the personal and the managed profile,
* since the managed profile doesn't get to change them.
- * @hide
*/
- public static final String[] CLONE_TO_MANAGED_PROFILE = {
- DATE_FORMAT,
- HAPTIC_FEEDBACK_ENABLED,
- SOUND_EFFECTS_ENABLED,
- TEXT_SHOW_PASSWORD,
- TIME_12_24
- };
+ private static final Set<String> CLONE_TO_MANAGED_PROFILE = new ArraySet<>();
+ static {
+ CLONE_TO_MANAGED_PROFILE.add(DATE_FORMAT);
+ CLONE_TO_MANAGED_PROFILE.add(HAPTIC_FEEDBACK_ENABLED);
+ CLONE_TO_MANAGED_PROFILE.add(SOUND_EFFECTS_ENABLED);
+ CLONE_TO_MANAGED_PROFILE.add(TEXT_SHOW_PASSWORD);
+ CLONE_TO_MANAGED_PROFILE.add(TIME_12_24);
+ }
+
+ /** @hide */
+ public static void getCloneToManagedProfileSettings(Set<String> outKeySet) {
+ outKeySet.addAll(CLONE_TO_MANAGED_PROFILE);
+ }
/**
* When to use Wi-Fi calling
@@ -3103,7 +3627,7 @@ public final class Settings {
}
/** @hide */
- public static void getMovedKeys(HashSet<String> outKeySet) {
+ public static void getMovedToGlobalSettings(Set<String> outKeySet) {
outKeySet.addAll(MOVED_TO_GLOBAL);
}
@@ -3657,6 +4181,7 @@ public final class Settings {
* A flag containing settings used for biometric weak
* @hide
*/
+ @Deprecated
public static final String LOCK_BIOMETRIC_WEAK_FLAGS =
"lock_biometric_weak_flags";
@@ -3668,7 +4193,11 @@ public final class Settings {
/**
* Whether autolock is enabled (0 = false, 1 = true)
+ *
+ * @deprecated Use {@link android.app.KeyguardManager} to determine the state and security
+ * level of the keyguard.
*/
+ @Deprecated
public static final String LOCK_PATTERN_ENABLED = "lock_pattern_autolock";
/**
@@ -3707,6 +4236,7 @@ public final class Settings {
* Ids of the user-selected appwidgets on the lockscreen (comma-delimited).
* @hide
*/
+ @Deprecated
public static final String LOCK_SCREEN_APPWIDGET_IDS =
"lock_screen_appwidget_ids";
@@ -3720,6 +4250,7 @@ public final class Settings {
* Id of the appwidget shown on the lock screen when appwidgets are disabled.
* @hide
*/
+ @Deprecated
public static final String LOCK_SCREEN_FALLBACK_APPWIDGET_ID =
"lock_screen_fallback_appwidget_id";
@@ -3727,6 +4258,7 @@ public final class Settings {
* Index of the lockscreen appwidget to restore, -1 if none.
* @hide
*/
+ @Deprecated
public static final String LOCK_SCREEN_STICKY_APPWIDGET =
"lock_screen_sticky_appwidget";
@@ -4892,22 +5424,27 @@ public final class Settings {
/**
* These entries are considered common between the personal and the managed profile,
* since the managed profile doesn't get to change them.
- * @hide
*/
- public static final String[] CLONE_TO_MANAGED_PROFILE = {
- ACCESSIBILITY_ENABLED,
- ALLOW_MOCK_LOCATION,
- ALLOWED_GEOLOCATION_ORIGINS,
- DEFAULT_INPUT_METHOD,
- ENABLED_ACCESSIBILITY_SERVICES,
- ENABLED_INPUT_METHODS,
- LOCATION_MODE,
- LOCATION_PROVIDERS_ALLOWED,
- LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS,
- SELECTED_INPUT_METHOD_SUBTYPE,
- SELECTED_SPELL_CHECKER,
- SELECTED_SPELL_CHECKER_SUBTYPE
- };
+ private static final Set<String> CLONE_TO_MANAGED_PROFILE = new ArraySet<>();
+ static {
+ CLONE_TO_MANAGED_PROFILE.add(ACCESSIBILITY_ENABLED);
+ CLONE_TO_MANAGED_PROFILE.add(ALLOW_MOCK_LOCATION);
+ CLONE_TO_MANAGED_PROFILE.add(ALLOWED_GEOLOCATION_ORIGINS);
+ CLONE_TO_MANAGED_PROFILE.add(DEFAULT_INPUT_METHOD);
+ CLONE_TO_MANAGED_PROFILE.add(ENABLED_ACCESSIBILITY_SERVICES);
+ CLONE_TO_MANAGED_PROFILE.add(ENABLED_INPUT_METHODS);
+ CLONE_TO_MANAGED_PROFILE.add(LOCATION_MODE);
+ CLONE_TO_MANAGED_PROFILE.add(LOCATION_PROVIDERS_ALLOWED);
+ CLONE_TO_MANAGED_PROFILE.add(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
+ CLONE_TO_MANAGED_PROFILE.add(SELECTED_INPUT_METHOD_SUBTYPE);
+ CLONE_TO_MANAGED_PROFILE.add(SELECTED_SPELL_CHECKER);
+ CLONE_TO_MANAGED_PROFILE.add(SELECTED_SPELL_CHECKER_SUBTYPE);
+ }
+
+ /** @hide */
+ public static void getCloneToManagedProfileSettings(Set<String> outKeySet) {
+ outKeySet.addAll(CLONE_TO_MANAGED_PROFILE);
+ }
/**
* Helper method for determining if a location provider is enabled.
@@ -6541,7 +7078,7 @@ public final class Settings {
/**
* Defines global runtime overrides to window policy.
*
- * See {@link com.android.internal.policy.impl.PolicyControl} for value format.
+ * See {@link com.android.server.policy.PolicyControl} for value format.
*
* @hide
*/
@@ -6689,6 +7226,11 @@ public final class Settings {
MOVED_TO_SECURE.add(Settings.Global.INSTALL_NON_MARKET_APPS);
}
+ /** @hide */
+ public static void getMovedToSecureSettings(Set<String> outKeySet) {
+ outKeySet.addAll(MOVED_TO_SECURE);
+ }
+
/**
* Look up a name in the database.
* @param resolver to access the database with
diff --git a/core/java/android/service/dreams/DreamService.java b/core/java/android/service/dreams/DreamService.java
index 38b0439..fa5ac42 100644
--- a/core/java/android/service/dreams/DreamService.java
+++ b/core/java/android/service/dreams/DreamService.java
@@ -18,6 +18,7 @@ package android.service.dreams;
import java.io.FileDescriptor;
import java.io.PrintWriter;
+import android.annotation.Nullable;
import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
import android.app.AlarmManager;
@@ -37,6 +38,7 @@ import android.view.KeyEvent;
import android.view.Menu;
import android.view.MenuItem;
import android.view.MotionEvent;
+import android.view.PhoneWindow;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
@@ -46,7 +48,6 @@ import android.view.WindowManager.LayoutParams;
import android.view.accessibility.AccessibilityEvent;
import android.util.MathUtils;
-import com.android.internal.policy.PolicyManager;
import com.android.internal.util.DumpUtils;
import com.android.internal.util.DumpUtils.Dump;
@@ -442,6 +443,7 @@ public class DreamService extends Service implements Window.Callback {
*
* @return The view if found or null otherwise.
*/
+ @Nullable
public View findViewById(int id) {
return getWindow().findViewById(id);
}
@@ -945,7 +947,7 @@ public class DreamService extends Service implements Window.Callback {
throw new IllegalStateException("Only doze dreams can be windowless");
}
if (!mWindowless) {
- mWindow = PolicyManager.makeNewWindow(this);
+ mWindow = new PhoneWindow(this);
mWindow.setCallback(this);
mWindow.requestFeature(Window.FEATURE_NO_TITLE);
mWindow.setBackgroundDrawable(new ColorDrawable(0xFF000000));
diff --git a/core/java/android/service/voice/VoiceInteractionService.java b/core/java/android/service/voice/VoiceInteractionService.java
index 0cde4f2..65e6988 100644
--- a/core/java/android/service/voice/VoiceInteractionService.java
+++ b/core/java/android/service/voice/VoiceInteractionService.java
@@ -40,15 +40,16 @@ import java.util.Locale;
/**
* Top-level service of the current global voice interactor, which is providing
- * support for hotwording etc.
+ * support for hotwording, the back-end of a {@link android.app.VoiceInteractor}, etc.
* The current VoiceInteractionService that has been selected by the user is kept
* always running by the system, to allow it to do things like listen for hotwords
- * in the background.
+ * in the background to instigate voice interactions.
*
* <p>Because this service is always running, it should be kept as lightweight as
* possible. Heavy-weight operations (including showing UI) should be implemented
- * in the associated {@link android.service.voice.VoiceInteractionSessionService}
- * that only runs while the operation is active.
+ * in the associated {@link android.service.voice.VoiceInteractionSessionService} when
+ * an actual voice interaction is taking place, and that service should run in a
+ * separate process from this one.
*/
public class VoiceInteractionService extends Service {
/**
diff --git a/core/java/android/service/voice/VoiceInteractionSession.java b/core/java/android/service/voice/VoiceInteractionSession.java
index 749f813..19d14bf 100644
--- a/core/java/android/service/voice/VoiceInteractionSession.java
+++ b/core/java/android/service/voice/VoiceInteractionSession.java
@@ -16,7 +16,6 @@
package android.service.voice;
-import android.annotation.SystemApi;
import android.app.Dialog;
import android.app.Instrumentation;
import android.content.Context;
@@ -54,7 +53,15 @@ import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
/**
- * An active interaction session, started by a {@link VoiceInteractionService}.
+ * An active voice interaction session, providing a facility for the implementation
+ * to interact with the user in the voice interaction layer. This interface is no shown
+ * by default, but you can request that it be shown with {@link #showWindow()}, which
+ * will result in a later call to {@link #onCreateContentView()} in which the UI can be
+ * built
+ *
+ * <p>A voice interaction session can be self-contained, ultimately calling {@link #finish}
+ * when done. It can also initiate voice interactions with applications by calling
+ * {@link #startVoiceActivity}</p>.
*/
public abstract class VoiceInteractionSession implements KeyEvent.Callback {
static final String TAG = "VoiceInteractionSession";
@@ -168,10 +175,6 @@ public abstract class VoiceInteractionSession implements KeyEvent.Callback {
}
};
- /**
- * @hide
- */
- @SystemApi
public static class Request {
final IVoiceInteractorRequest mInterface = new IVoiceInteractorRequest.Stub() {
@Override
@@ -255,10 +258,6 @@ public abstract class VoiceInteractionSession implements KeyEvent.Callback {
}
}
- /**
- * @hide
- */
- @SystemApi
public static class Caller {
final String packageName;
final int uid;
@@ -354,10 +353,8 @@ public abstract class VoiceInteractionSession implements KeyEvent.Callback {
final MyCallbacks mCallbacks = new MyCallbacks();
/**
- * @hide
* Information about where interesting parts of the input method UI appear.
*/
- @SystemApi
public static final class Insets {
/**
* This is the part of the UI that is the main content. It is
@@ -477,10 +474,6 @@ public abstract class VoiceInteractionSession implements KeyEvent.Callback {
mContentFrame = (FrameLayout)mRootView.findViewById(android.R.id.content);
}
- /**
- * @hide
- */
- @SystemApi
public void showWindow() {
if (DEBUG) Log.v(TAG, "Showing window: mWindowAdded=" + mWindowAdded
+ " mWindowVisible=" + mWindowVisible);
@@ -509,10 +502,6 @@ public abstract class VoiceInteractionSession implements KeyEvent.Callback {
}
}
- /**
- * @hide
- */
- @SystemApi
public void hideWindow() {
if (mWindowVisible) {
mWindow.hide();
@@ -521,13 +510,11 @@ public abstract class VoiceInteractionSession implements KeyEvent.Callback {
}
/**
- * @hide
* You can call this to customize the theme used by your IME's window.
* This must be set before {@link #onCreate}, so you
* will typically call it in your constructor with the resource ID
* of your custom theme.
*/
- @SystemApi
public void setTheme(int theme) {
if (mWindow != null) {
throw new IllegalStateException("Must be called before onCreate()");
@@ -536,7 +523,6 @@ public abstract class VoiceInteractionSession implements KeyEvent.Callback {
}
/**
- * @hide
* Ask that a new activity be started for voice interaction. This will create a
* new dedicated task in the activity manager for this voice interaction session;
* this means that {@link Intent#FLAG_ACTIVITY_NEW_TASK Intent.FLAG_ACTIVITY_NEW_TASK}
@@ -557,7 +543,6 @@ public abstract class VoiceInteractionSession implements KeyEvent.Callback {
* always have {@link Intent#CATEGORY_VOICE Intent.CATEGORY_VOICE} added to it, since
* this is part of a voice interaction.
*/
- @SystemApi
public void startVoiceActivity(Intent intent) {
if (mToken == null) {
throw new IllegalStateException("Can't call before onCreate()");
@@ -573,19 +558,15 @@ public abstract class VoiceInteractionSession implements KeyEvent.Callback {
}
/**
- * @hide
* Convenience for inflating views.
*/
- @SystemApi
public LayoutInflater getLayoutInflater() {
return mInflater;
}
/**
- * @hide
* Retrieve the window being used to show the session's UI.
*/
- @SystemApi
public Dialog getWindow() {
return mWindow;
}
@@ -631,10 +612,8 @@ public abstract class VoiceInteractionSession implements KeyEvent.Callback {
}
/**
- * @hide
* Hook in which to create the session's UI.
*/
- @SystemApi
public View onCreateContentView() {
return null;
}
@@ -647,42 +626,22 @@ public abstract class VoiceInteractionSession implements KeyEvent.Callback {
}
- /**
- * @hide
- */
- @SystemApi
public boolean onKeyDown(int keyCode, KeyEvent event) {
return false;
}
- /**
- * @hide
- */
- @SystemApi
public boolean onKeyLongPress(int keyCode, KeyEvent event) {
return false;
}
- /**
- * @hide
- */
- @SystemApi
public boolean onKeyUp(int keyCode, KeyEvent event) {
return false;
}
- /**
- * @hide
- */
- @SystemApi
public boolean onKeyMultiple(int keyCode, int count, KeyEvent event) {
return false;
}
- /**
- * @hide
- */
- @SystemApi
public void onBackPressed() {
finish();
}
@@ -697,14 +656,12 @@ public abstract class VoiceInteractionSession implements KeyEvent.Callback {
}
/**
- * @hide
* Compute the interesting insets into your UI. The default implementation
* uses the entire window frame as the insets. The default touchable
* insets are {@link Insets#TOUCHABLE_INSETS_FRAME}.
*
* @param outInsets Fill in with the current UI insets.
*/
- @SystemApi
public void onComputeInsets(Insets outInsets) {
int[] loc = mTmpLocation;
View decor = getWindow().getWindow().getDecorView();
@@ -718,8 +675,6 @@ public abstract class VoiceInteractionSession implements KeyEvent.Callback {
}
/**
- * @hide
- * @SystemApi
* Called when a task initiated by {@link #startVoiceActivity(android.content.Intent)}
* has actually started.
*
@@ -731,8 +686,6 @@ public abstract class VoiceInteractionSession implements KeyEvent.Callback {
}
/**
- * @hide
- * @SystemApi
* Called when the last activity of a task initiated by
* {@link #startVoiceActivity(android.content.Intent)} has finished. The default
* implementation calls {@link #finish()} on the assumption that this represents
@@ -748,8 +701,6 @@ public abstract class VoiceInteractionSession implements KeyEvent.Callback {
}
/**
- * @hide
- * @SystemApi
* Request to query for what extended commands the session supports.
*
* @param caller Who is making the request.
@@ -764,8 +715,6 @@ public abstract class VoiceInteractionSession implements KeyEvent.Callback {
}
/**
- * @hide
- * @SystemApi
* Request to confirm with the user before proceeding with an unrecoverable operation,
* corresponding to a {@link android.app.VoiceInteractor.ConfirmationRequest
* VoiceInteractor.ConfirmationRequest}.
@@ -781,8 +730,6 @@ public abstract class VoiceInteractionSession implements KeyEvent.Callback {
Bundle extras);
/**
- * @hide
- * @SystemApi
* Request to complete the voice interaction session because the voice activity successfully
* completed its interaction using voice. Corresponds to
* {@link android.app.VoiceInteractor.CompleteVoiceRequest
@@ -804,8 +751,6 @@ public abstract class VoiceInteractionSession implements KeyEvent.Callback {
}
/**
- * @hide
- * @SystemApi
* Request to abort the voice interaction session because the voice activity can not
* complete its interaction using voice. Corresponds to
* {@link android.app.VoiceInteractor.AbortVoiceRequest
@@ -824,8 +769,6 @@ public abstract class VoiceInteractionSession implements KeyEvent.Callback {
}
/**
- * @hide
- * @SystemApi
* Process an arbitrary extended command from the caller,
* corresponding to a {@link android.app.VoiceInteractor.CommandRequest
* VoiceInteractor.CommandRequest}.
@@ -840,8 +783,6 @@ public abstract class VoiceInteractionSession implements KeyEvent.Callback {
public abstract void onCommand(Caller caller, Request request, String command, Bundle extras);
/**
- * @hide
- * @SystemApi
* Called when the {@link android.app.VoiceInteractor} has asked to cancel a {@link Request}
* that was previously delivered to {@link #onConfirm} or {@link #onCommand}.
*
diff --git a/core/java/android/speech/tts/ITextToSpeechCallback.aidl b/core/java/android/speech/tts/ITextToSpeechCallback.aidl
index 899515f..d785c3f 100644
--- a/core/java/android/speech/tts/ITextToSpeechCallback.aidl
+++ b/core/java/android/speech/tts/ITextToSpeechCallback.aidl
@@ -40,7 +40,7 @@ oneway interface ITextToSpeechCallback {
*
* @param utteranceId Unique id identifying synthesis request.
*/
- void onStop(String utteranceId);
+ void onStop(String utteranceId, boolean isStarted);
/**
* Tells the client that the synthesis has failed.
diff --git a/core/java/android/speech/tts/TextToSpeech.java b/core/java/android/speech/tts/TextToSpeech.java
index c59ca8a..06e9ce0 100644
--- a/core/java/android/speech/tts/TextToSpeech.java
+++ b/core/java/android/speech/tts/TextToSpeech.java
@@ -2066,10 +2066,10 @@ public class TextToSpeech {
private boolean mEstablished;
private final ITextToSpeechCallback.Stub mCallback = new ITextToSpeechCallback.Stub() {
- public void onStop(String utteranceId) throws RemoteException {
+ public void onStop(String utteranceId, boolean isStarted) throws RemoteException {
UtteranceProgressListener listener = mUtteranceProgressListener;
if (listener != null) {
- listener.onDone(utteranceId);
+ listener.onStop(utteranceId, isStarted);
}
};
diff --git a/core/java/android/speech/tts/TextToSpeechService.java b/core/java/android/speech/tts/TextToSpeechService.java
index 9bb7f02..02c9a36 100644
--- a/core/java/android/speech/tts/TextToSpeechService.java
+++ b/core/java/android/speech/tts/TextToSpeechService.java
@@ -455,10 +455,37 @@ public abstract class TextToSpeechService extends Service {
private class SynthHandler extends Handler {
private SpeechItem mCurrentSpeechItem = null;
+ private ArrayList<Object> mFlushedObjects = new ArrayList<Object>();
+ private boolean mFlushAll;
+
public SynthHandler(Looper looper) {
super(looper);
}
+ private void startFlushingSpeechItems(Object callerIdentity) {
+ synchronized (mFlushedObjects) {
+ if (callerIdentity == null) {
+ mFlushAll = true;
+ } else {
+ mFlushedObjects.add(callerIdentity);
+ }
+ }
+ }
+ private void endFlushingSpeechItems(Object callerIdentity) {
+ synchronized (mFlushedObjects) {
+ if (callerIdentity == null) {
+ mFlushAll = false;
+ } else {
+ mFlushedObjects.remove(callerIdentity);
+ }
+ }
+ }
+ private boolean isFlushed(SpeechItem speechItem) {
+ synchronized (mFlushedObjects) {
+ return mFlushAll || mFlushedObjects.contains(speechItem.getCallerIdentity());
+ }
+ }
+
private synchronized SpeechItem getCurrentSpeechItem() {
return mCurrentSpeechItem;
}
@@ -522,9 +549,13 @@ public abstract class TextToSpeechService extends Service {
Runnable runnable = new Runnable() {
@Override
public void run() {
- setCurrentSpeechItem(speechItem);
- speechItem.play();
- setCurrentSpeechItem(null);
+ if (isFlushed(speechItem)) {
+ speechItem.stop();
+ } else {
+ setCurrentSpeechItem(speechItem);
+ speechItem.play();
+ setCurrentSpeechItem(null);
+ }
}
};
Message msg = Message.obtain(this, runnable);
@@ -552,12 +583,14 @@ public abstract class TextToSpeechService extends Service {
*
* Called on a service binder thread.
*/
- public int stopForApp(Object callerIdentity) {
+ public int stopForApp(final Object callerIdentity) {
if (callerIdentity == null) {
return TextToSpeech.ERROR;
}
- removeCallbacksAndMessages(callerIdentity);
+ // Flush pending messages from callerIdentity
+ startFlushingSpeechItems(callerIdentity);
+
// This stops writing data to the file / or publishing
// items to the audio playback handler.
//
@@ -573,20 +606,39 @@ public abstract class TextToSpeechService extends Service {
// Remove any enqueued audio too.
mAudioPlaybackHandler.stopForApp(callerIdentity);
+ // Stop flushing pending messages
+ Runnable runnable = new Runnable() {
+ @Override
+ public void run() {
+ endFlushingSpeechItems(callerIdentity);
+ }
+ };
+ sendMessage(Message.obtain(this, runnable));
return TextToSpeech.SUCCESS;
}
public int stopAll() {
+ // Order to flush pending messages
+ startFlushingSpeechItems(null);
+
// Stop the current speech item unconditionally .
SpeechItem current = setCurrentSpeechItem(null);
if (current != null) {
current.stop();
}
- // Remove all other items from the queue.
- removeCallbacksAndMessages(null);
// Remove all pending playback as well.
mAudioPlaybackHandler.stop();
+ // Message to stop flushing pending messages
+ Runnable runnable = new Runnable() {
+ @Override
+ public void run() {
+ endFlushingSpeechItems(null);
+ }
+ };
+ sendMessage(Message.obtain(this, runnable));
+
+
return TextToSpeech.SUCCESS;
}
}
@@ -698,7 +750,6 @@ public abstract class TextToSpeechService extends Service {
return mCallerIdentity;
}
-
public int getCallerUid() {
return mCallerUid;
}
@@ -752,6 +803,10 @@ public abstract class TextToSpeechService extends Service {
protected synchronized boolean isStopped() {
return mStopped;
}
+
+ protected synchronized boolean isStarted() {
+ return mStarted;
+ }
}
/**
@@ -777,7 +832,7 @@ public abstract class TextToSpeechService extends Service {
public void dispatchOnStop() {
final String utteranceId = getUtteranceId();
if (utteranceId != null) {
- mCallbacks.dispatchOnStop(getCallerIdentity(), utteranceId);
+ mCallbacks.dispatchOnStop(getCallerIdentity(), utteranceId, isStarted());
}
}
@@ -940,6 +995,8 @@ public abstract class TextToSpeechService extends Service {
// turn implies that synthesis would not have started.
synthesisCallback.stop();
TextToSpeechService.this.onStop();
+ } else {
+ dispatchOnStop();
}
}
@@ -1345,11 +1402,11 @@ public abstract class TextToSpeechService extends Service {
}
}
- public void dispatchOnStop(Object callerIdentity, String utteranceId) {
+ public void dispatchOnStop(Object callerIdentity, String utteranceId, boolean started) {
ITextToSpeechCallback cb = getCallbackFor(callerIdentity);
if (cb == null) return;
try {
- cb.onStop(utteranceId);
+ cb.onStop(utteranceId, started);
} catch (RemoteException e) {
Log.e(TAG, "Callback onStop failed: " + e);
}
diff --git a/core/java/android/speech/tts/UtteranceProgressListener.java b/core/java/android/speech/tts/UtteranceProgressListener.java
index 6769794..9eb22ef 100644
--- a/core/java/android/speech/tts/UtteranceProgressListener.java
+++ b/core/java/android/speech/tts/UtteranceProgressListener.java
@@ -60,6 +60,20 @@ public abstract class UtteranceProgressListener {
}
/**
+ * Called when an utterance has been stopped while in progress or flushed from the
+ * synthesis queue. This can happen if client calls {@link TextToSpeech#stop()}
+ * or use {@link TextToSpeech#QUEUE_FLUSH} as an argument in
+ * {@link TextToSpeech#speak} or {@link TextToSpeech#synthesizeToFile} methods.
+ *
+ * @param utteranceId the utterance ID of the utterance.
+ * @param isStarted If true, then utterance was interrupted while being synthesized
+ * and it's output is incomplete. If it's false, then utterance was flushed
+ * before the synthesis started.
+ */
+ public void onStop(String utteranceId, boolean isStarted) {
+ }
+
+ /**
* Wraps an old deprecated OnUtteranceCompletedListener with a shiny new
* progress listener.
*
@@ -83,6 +97,11 @@ public abstract class UtteranceProgressListener {
// Left unimplemented, has no equivalent in the old
// API.
}
+
+ @Override
+ public void onStop(String utteranceId, boolean isStarted) {
+ listener.onUtteranceCompleted(utteranceId);
+ }
};
}
}
diff --git a/core/java/android/text/StaticLayout.java b/core/java/android/text/StaticLayout.java
index 07505a9..2d4b4dc 100644
--- a/core/java/android/text/StaticLayout.java
+++ b/core/java/android/text/StaticLayout.java
@@ -28,6 +28,8 @@ import android.util.Log;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.GrowingArrayUtils;
+import java.util.Arrays;
+
/**
* StaticLayout is a Layout for text that will not be edited after it
* is laid out. Use {@link DynamicLayout} for text that may change.
@@ -161,7 +163,12 @@ public class StaticLayout extends Layout {
float spacingadd, boolean includepad,
boolean trackpad, float ellipsizedWidth,
TextUtils.TruncateAt ellipsize) {
- int[] breakOpp = null;
+ LineBreaks lineBreaks = new LineBreaks();
+ // store span end locations
+ int[] spanEndCache = new int[4];
+ // store fontMetrics per span range
+ // must be a multiple of 4 (and > 0) (store top, bottom, ascent, and descent per range)
+ int[] fmCache = new int[4 * 4];
final String localeLanguageTag = paint.getTextLocale().toLanguageTag();
mLineCount = 0;
@@ -186,7 +193,7 @@ public class StaticLayout extends Layout {
else
paraEnd++;
- int firstWidthLineLimit = mLineCount + 1;
+ int firstWidthLineCount = 1;
int firstWidth = outerWidth;
int restWidth = outerWidth;
@@ -204,9 +211,8 @@ public class StaticLayout extends Layout {
// leading margin spans, not just this particular one
if (lms instanceof LeadingMarginSpan2) {
LeadingMarginSpan2 lms2 = (LeadingMarginSpan2) lms;
- int lmsFirstLine = getLineForOffset(spanned.getSpanStart(lms2));
- firstWidthLineLimit = Math.max(firstWidthLineLimit,
- lmsFirstLine + lms2.getLeadingMarginLineCount());
+ firstWidthLineCount = Math.max(firstWidthLineCount,
+ lms2.getLeadingMarginLineCount());
}
}
@@ -242,34 +248,23 @@ public class StaticLayout extends Layout {
int dir = measured.mDir;
boolean easy = measured.mEasy;
- breakOpp = nLineBreakOpportunities(localeLanguageTag, chs, paraEnd - paraStart, breakOpp);
- int breakOppIndex = 0;
-
- int width = firstWidth;
-
- float w = 0;
- // here is the offset of the starting character of the line we are currently measuring
- int here = paraStart;
-
- // ok is a character offset located after a word separator (space, tab, number...) where
- // we would prefer to cut the current line. Equals to here when no such break was found.
- int ok = paraStart;
- float okWidth = w;
- int okAscent = 0, okDescent = 0, okTop = 0, okBottom = 0;
-
- // fit is a character offset such that the [here, fit[ range fits in the allowed width.
- // We will cut the line there if no ok position is found.
- int fit = paraStart;
- float fitWidth = w;
- int fitAscent = 0, fitDescent = 0, fitTop = 0, fitBottom = 0;
- // same as fitWidth but not including any trailing whitespace
- float fitWidthGraphing = w;
-
- boolean hasTabOrEmoji = false;
- boolean hasTab = false;
- TabStops tabStops = null;
-
+ // measurement has to be done before performing line breaking
+ // but we don't want to recompute fontmetrics or span ranges the
+ // second time, so we cache those and then use those stored values
+ int fmCacheCount = 0;
+ int spanEndCacheCount = 0;
for (int spanStart = paraStart, spanEnd; spanStart < paraEnd; spanStart = spanEnd) {
+ if (fmCacheCount * 4 >= fmCache.length) {
+ int[] grow = new int[fmCacheCount * 4 * 2];
+ System.arraycopy(fmCache, 0, grow, 0, fmCacheCount * 4);
+ fmCache = grow;
+ }
+
+ if (spanEndCacheCount >= spanEndCache.length) {
+ int[] grow = new int[spanEndCacheCount * 2];
+ System.arraycopy(spanEndCache, 0, grow, 0, spanEndCacheCount);
+ spanEndCache = grow;
+ }
if (spanned == null) {
spanEnd = paraEnd;
@@ -285,200 +280,107 @@ public class StaticLayout extends Layout {
measured.addStyleRun(paint, spans, spanLen, fm);
}
- int fmTop = fm.top;
- int fmBottom = fm.bottom;
- int fmAscent = fm.ascent;
- int fmDescent = fm.descent;
-
- for (int j = spanStart; j < spanEnd; j++) {
- char c = chs[j - paraStart];
-
- if (c == CHAR_NEW_LINE) {
- // intentionally left empty
- } else if (c == CHAR_TAB) {
- if (hasTab == false) {
- hasTab = true;
- hasTabOrEmoji = true;
- if (spanned != null) {
- // First tab this para, check for tabstops
- TabStopSpan[] spans = getParagraphSpans(spanned, paraStart,
- paraEnd, TabStopSpan.class);
- if (spans.length > 0) {
- tabStops = new TabStops(TAB_INCREMENT, spans);
- }
- }
- }
- if (tabStops != null) {
- w = tabStops.nextTab(w);
- } else {
- w = TabStops.nextDefaultStop(w, TAB_INCREMENT);
- }
- } else if (c >= CHAR_FIRST_HIGH_SURROGATE && c <= CHAR_LAST_LOW_SURROGATE
- && j + 1 < spanEnd) {
- int emoji = Character.codePointAt(chs, j - paraStart);
-
- if (emoji >= MIN_EMOJI && emoji <= MAX_EMOJI) {
- Bitmap bm = EMOJI_FACTORY.getBitmapFromAndroidPua(emoji);
-
- if (bm != null) {
- Paint whichPaint;
-
- if (spanned == null) {
- whichPaint = paint;
- } else {
- whichPaint = mWorkPaint;
- }
-
- float wid = bm.getWidth() * -whichPaint.ascent() / bm.getHeight();
-
- w += wid;
- hasTabOrEmoji = true;
- j++;
- } else {
- w += widths[j - paraStart];
- }
- } else {
- w += widths[j - paraStart];
- }
- } else {
- w += widths[j - paraStart];
+ // the order of storage here (top, bottom, ascent, descent) has to match the code below
+ // where these values are retrieved
+ fmCache[fmCacheCount * 4 + 0] = fm.top;
+ fmCache[fmCacheCount * 4 + 1] = fm.bottom;
+ fmCache[fmCacheCount * 4 + 2] = fm.ascent;
+ fmCache[fmCacheCount * 4 + 3] = fm.descent;
+ fmCacheCount++;
+
+ spanEndCache[spanEndCacheCount] = spanEnd;
+ spanEndCacheCount++;
+ }
+
+ // tab stop locations
+ int[] variableTabStops = null;
+ if (spanned != null) {
+ TabStopSpan[] spans = getParagraphSpans(spanned, paraStart,
+ paraEnd, TabStopSpan.class);
+ if (spans.length > 0) {
+ int[] stops = new int[spans.length];
+ for (int i = 0; i < spans.length; i++) {
+ stops[i] = spans[i].getTabStop();
}
+ Arrays.sort(stops, 0, stops.length);
+ variableTabStops = stops;
+ }
+ }
- boolean isSpaceOrTab = c == CHAR_SPACE || c == CHAR_TAB || c == CHAR_ZWSP;
+ int breakCount = nComputeLineBreaks(localeLanguageTag, chs, widths, paraEnd - paraStart, firstWidth,
+ firstWidthLineCount, restWidth, variableTabStops, TAB_INCREMENT, false, lineBreaks,
+ lineBreaks.breaks, lineBreaks.widths, lineBreaks.flags, lineBreaks.breaks.length);
- if (w <= width || isSpaceOrTab) {
- fitWidth = w;
- if (!isSpaceOrTab) {
- fitWidthGraphing = w;
- }
- fit = j + 1;
-
- if (fmTop < fitTop)
- fitTop = fmTop;
- if (fmAscent < fitAscent)
- fitAscent = fmAscent;
- if (fmDescent > fitDescent)
- fitDescent = fmDescent;
- if (fmBottom > fitBottom)
- fitBottom = fmBottom;
-
- while (breakOpp[breakOppIndex] != -1
- && breakOpp[breakOppIndex] < j - paraStart + 1) {
- breakOppIndex++;
- }
- boolean isLineBreak = breakOppIndex < breakOpp.length &&
- breakOpp[breakOppIndex] == j - paraStart + 1;
-
- if (isLineBreak) {
- okWidth = fitWidthGraphing;
- ok = j + 1;
-
- if (fitTop < okTop)
- okTop = fitTop;
- if (fitAscent < okAscent)
- okAscent = fitAscent;
- if (fitDescent > okDescent)
- okDescent = fitDescent;
- if (fitBottom > okBottom)
- okBottom = fitBottom;
- }
- } else {
- int endPos;
- int above, below, top, bottom;
- float currentTextWidth;
-
- if (ok != here) {
- endPos = ok;
- above = okAscent;
- below = okDescent;
- top = okTop;
- bottom = okBottom;
- currentTextWidth = okWidth;
- } else if (fit != here) {
- endPos = fit;
- above = fitAscent;
- below = fitDescent;
- top = fitTop;
- bottom = fitBottom;
- currentTextWidth = fitWidth;
- } else {
- // must make progress, so take next character
- endPos = here + 1;
- // but to deal properly with clusters
- // take all zero width characters following that
- while (endPos < spanEnd && widths[endPos - paraStart] == 0) {
- endPos++;
- }
- above = fmAscent;
- below = fmDescent;
- top = fmTop;
- bottom = fmBottom;
- currentTextWidth = widths[here - paraStart];
- }
+ int[] breaks = lineBreaks.breaks;
+ float[] lineWidths = lineBreaks.widths;
+ boolean[] flags = lineBreaks.flags;
- int ellipseEnd = endPos;
- if (mMaximumVisibleLineCount == 1 && ellipsize == TextUtils.TruncateAt.MIDDLE) {
- ellipseEnd = paraEnd;
- }
- v = out(source, here, ellipseEnd,
- above, below, top, bottom,
- v, spacingmult, spacingadd, chooseHt,chooseHtv, fm, hasTabOrEmoji,
- needMultiply, chdirs, dir, easy, bufEnd, includepad, trackpad,
- chs, widths, paraStart, ellipsize, ellipsizedWidth,
- currentTextWidth, paint, true);
-
- here = endPos;
- j = here - 1; // restart j-span loop from here, compensating for the j++
- ok = fit = here;
- w = 0;
- fitWidthGraphing = w;
- fitAscent = fitDescent = fitTop = fitBottom = 0;
- okAscent = okDescent = okTop = okBottom = 0;
-
- if (--firstWidthLineLimit <= 0) {
- width = restWidth;
- }
+ // here is the offset of the starting character of the line we are currently measuring
+ int here = paraStart;
- if (here < spanStart) {
- // The text was cut before the beginning of the current span range.
- // Exit the span loop, and get spanStart to start over from here.
- measured.setPos(here);
- spanEnd = here;
- break;
- }
+ int fmTop = 0, fmBottom = 0, fmAscent = 0, fmDescent = 0;
+ int fmCacheIndex = 0;
+ int spanEndCacheIndex = 0;
+ int breakIndex = 0;
+ for (int spanStart = paraStart, spanEnd; spanStart < paraEnd; spanStart = spanEnd) {
+ // retrieve end of span
+ spanEnd = spanEndCache[spanEndCacheIndex++];
+
+ // retrieve cached metrics, order matches above
+ fm.top = fmCache[fmCacheIndex * 4 + 0];
+ fm.bottom = fmCache[fmCacheIndex * 4 + 1];
+ fm.ascent = fmCache[fmCacheIndex * 4 + 2];
+ fm.descent = fmCache[fmCacheIndex * 4 + 3];
+ fmCacheIndex++;
+
+ if (fm.top < fmTop) {
+ fmTop = fm.top;
+ }
+ if (fm.ascent < fmAscent) {
+ fmAscent = fm.ascent;
+ }
+ if (fm.descent > fmDescent) {
+ fmDescent = fm.descent;
+ }
+ if (fm.bottom > fmBottom) {
+ fmBottom = fm.bottom;
+ }
- if (mLineCount >= mMaximumVisibleLineCount) {
- return;
- }
- }
+ // skip breaks ending before current span range
+ while (breakIndex < breakCount && paraStart + breaks[breakIndex] < spanStart) {
+ breakIndex++;
}
- }
- if (paraEnd != here && mLineCount < mMaximumVisibleLineCount) {
- if ((fitTop | fitBottom | fitDescent | fitAscent) == 0) {
- paint.getFontMetricsInt(fm);
+ while (breakIndex < breakCount && paraStart + breaks[breakIndex] <= spanEnd) {
+ int endPos = paraStart + breaks[breakIndex];
- fitTop = fm.top;
- fitBottom = fm.bottom;
- fitAscent = fm.ascent;
- fitDescent = fm.descent;
- }
+ boolean moreChars = (endPos < paraEnd); // XXX is this the right way to calculate this?
- // Log.e("text", "output rest " + here + " to " + end);
-
- v = out(source,
- here, paraEnd, fitAscent, fitDescent,
- fitTop, fitBottom,
- v,
- spacingmult, spacingadd, chooseHt,
- chooseHtv, fm, hasTabOrEmoji,
- needMultiply, chdirs, dir, easy, bufEnd,
- includepad, trackpad, chs,
- widths, paraStart, ellipsize,
- ellipsizedWidth, w, paint, paraEnd != bufEnd);
- }
+ v = out(source, here, endPos,
+ fmAscent, fmDescent, fmTop, fmBottom,
+ v, spacingmult, spacingadd, chooseHt,chooseHtv, fm, flags[breakIndex],
+ needMultiply, chdirs, dir, easy, bufEnd, includepad, trackpad,
+ chs, widths, paraStart, ellipsize, ellipsizedWidth,
+ lineWidths[breakIndex], paint, moreChars);
- paraStart = paraEnd;
+ if (endPos < spanEnd) {
+ // preserve metrics for current span
+ fmTop = fm.top;
+ fmBottom = fm.bottom;
+ fmAscent = fm.ascent;
+ fmDescent = fm.descent;
+ } else {
+ fmTop = fmBottom = fmAscent = fmDescent = 0;
+ }
+
+ here = endPos;
+ breakIndex++;
+
+ if (mLineCount >= mMaximumVisibleLineCount) {
+ return;
+ }
+ }
+ }
if (paraEnd == bufEnd)
break;
@@ -488,7 +390,7 @@ public class StaticLayout extends Layout {
mLineCount < mMaximumVisibleLineCount) {
// Log.e("text", "output last " + bufEnd);
- measured.setPara(source, bufStart, bufEnd, textDir);
+ measured.setPara(source, bufEnd, bufEnd, textDir);
paint.getFontMetricsInt(fm);
@@ -848,15 +750,20 @@ public class StaticLayout extends Layout {
void prepare() {
mMeasured = MeasuredText.obtain();
}
-
+
void finish() {
mMeasured = MeasuredText.recycle(mMeasured);
}
- // returns an array with terminal sentinel value -1 to indicate end
- // this is so that arrays can be recycled instead of allocating new arrays
- // every time
- private static native int[] nLineBreakOpportunities(String locale, char[] text, int length, int[] recycle);
+ // populates LineBreaks and returns the number of breaks found
+ //
+ // the arrays inside the LineBreaks objects are passed in as well
+ // to reduce the number of JNI calls in the common case where the
+ // arrays do not have to be resized
+ private static native int nComputeLineBreaks(String locale, char[] text, float[] widths,
+ int length, float firstWidth, int firstWidthLineCount, float restWidth,
+ int[] variableTabStops, int defaultTabStop, boolean optimize, LineBreaks recycle,
+ int[] recycleBreaks, float[] recycleWidths, boolean[] recycleFlags, int recycleLength);
private int mLineCount;
private int mTopPadding, mBottomPadding;
@@ -884,18 +791,23 @@ public class StaticLayout extends Layout {
private static final int TAB_INCREMENT = 20; // same as Layout, but that's private
private static final char CHAR_NEW_LINE = '\n';
- private static final char CHAR_TAB = '\t';
- private static final char CHAR_SPACE = ' ';
- private static final char CHAR_ZWSP = '\u200B';
private static final double EXTRA_ROUNDING = 0.5;
- private static final int CHAR_FIRST_HIGH_SURROGATE = 0xD800;
- private static final int CHAR_LAST_LOW_SURROGATE = 0xDFFF;
-
/*
* This is reused across calls to generate()
*/
private MeasuredText mMeasured;
private Paint.FontMetricsInt mFontMetricsInt = new Paint.FontMetricsInt();
+
+ // This is used to return three arrays from a single JNI call when
+ // performing line breaking
+ /*package*/ static class LineBreaks {
+ private static final int INITIAL_SIZE = 16;
+ public int[] breaks = new int[INITIAL_SIZE];
+ public float[] widths = new float[INITIAL_SIZE];
+ public boolean[] flags = new boolean[INITIAL_SIZE]; // hasTabOrEmoji
+ // breaks, widths, and flags should all have the same length
+ }
+
}
diff --git a/core/java/android/text/util/Rfc822Token.java b/core/java/android/text/util/Rfc822Token.java
index 0edeeb5..058757a 100644
--- a/core/java/android/text/util/Rfc822Token.java
+++ b/core/java/android/text/util/Rfc822Token.java
@@ -16,18 +16,21 @@
package android.text.util;
+import android.annotation.Nullable;
+
/**
* This class stores an RFC 822-like name, address, and comment,
* and provides methods to convert them to quoted strings.
*/
public class Rfc822Token {
+ @Nullable
private String mName, mAddress, mComment;
/**
* Creates a new Rfc822Token with the specified name, address,
* and comment.
*/
- public Rfc822Token(String name, String address, String comment) {
+ public Rfc822Token(@Nullable String name, @Nullable String address, @Nullable String comment) {
mName = name;
mAddress = address;
mComment = comment;
@@ -36,6 +39,7 @@ public class Rfc822Token {
/**
* Returns the name part.
*/
+ @Nullable
public String getName() {
return mName;
}
@@ -43,6 +47,7 @@ public class Rfc822Token {
/**
* Returns the address part.
*/
+ @Nullable
public String getAddress() {
return mAddress;
}
@@ -50,6 +55,7 @@ public class Rfc822Token {
/**
* Returns the comment part.
*/
+ @Nullable
public String getComment() {
return mComment;
}
@@ -57,21 +63,21 @@ public class Rfc822Token {
/**
* Changes the name to the specified name.
*/
- public void setName(String name) {
+ public void setName(@Nullable String name) {
mName = name;
}
/**
* Changes the address to the specified address.
*/
- public void setAddress(String address) {
+ public void setAddress(@Nullable String address) {
mAddress = address;
}
/**
* Changes the comment to the specified comment.
*/
- public void setComment(String comment) {
+ public void setComment(@Nullable String comment) {
mComment = comment;
}
diff --git a/core/java/android/transition/ChangeScroll.java b/core/java/android/transition/ChangeScroll.java
index 39291bf..5a78b94 100644
--- a/core/java/android/transition/ChangeScroll.java
+++ b/core/java/android/transition/ChangeScroll.java
@@ -28,8 +28,6 @@ import android.view.ViewGroup;
/**
* This transition captures the scroll properties of targets before and after
* the scene change and animates any changes.
- *
- * @hide
*/
public class ChangeScroll extends Transition {
diff --git a/core/java/android/transition/TransitionManager.java b/core/java/android/transition/TransitionManager.java
index 7bd6287..80245ef 100644
--- a/core/java/android/transition/TransitionManager.java
+++ b/core/java/android/transition/TransitionManager.java
@@ -268,7 +268,12 @@ public class TransitionManager {
@Override
public boolean onPreDraw() {
removeListeners();
- sPendingTransitions.remove(mSceneRoot);
+
+ // Don't start the transition if it's no longer pending.
+ if (!sPendingTransitions.remove(mSceneRoot)) {
+ return true;
+ }
+
// Add to running list, handle end to remove it
final ArrayMap<ViewGroup, ArrayList<Transition>> runningTransitions =
getRunningTransitions();
@@ -417,4 +422,24 @@ public class TransitionManager {
sceneChangeRunTransition(sceneRoot, transitionClone);
}
}
+
+ /**
+ * Ends all pending and ongoing transitions on the specified scene root.
+ *
+ * @param sceneRoot The root of the View hierarchy to end transitions on.
+ * @hide
+ */
+ public static void endTransitions(final ViewGroup sceneRoot) {
+ sPendingTransitions.remove(sceneRoot);
+
+ final ArrayList<Transition> runningTransitions = getRunningTransitions().get(sceneRoot);
+ if (runningTransitions != null) {
+ final int count = runningTransitions.size();
+ for (int i = 0; i < count; i++) {
+ final Transition transition = runningTransitions.get(i);
+ transition.end();
+ }
+ }
+
+ }
}
diff --git a/core/java/android/util/AtomicFile.java b/core/java/android/util/AtomicFile.java
index a6466fc..3aa3447 100644
--- a/core/java/android/util/AtomicFile.java
+++ b/core/java/android/util/AtomicFile.java
@@ -102,7 +102,7 @@ public class AtomicFile {
str = new FileOutputStream(mBaseName);
} catch (FileNotFoundException e) {
File parent = mBaseName.getParentFile();
- if (!parent.mkdir()) {
+ if (!parent.mkdirs()) {
throw new IOException("Couldn't create directory " + mBaseName);
}
FileUtils.setPermissions(
diff --git a/core/java/android/util/AttributeSet.java b/core/java/android/util/AttributeSet.java
index 74942ba..eb8c168 100644
--- a/core/java/android/util/AttributeSet.java
+++ b/core/java/android/util/AttributeSet.java
@@ -39,7 +39,7 @@ package android.util;
* is more useful in conjunction with compiled XML resources:
*
* <pre>
- * XmlPullParser parser = resources.getXml(myResouce);
+ * XmlPullParser parser = resources.getXml(myResource);
* AttributeSet attributes = Xml.asAttributeSet(parser);</pre>
*
* <p>The implementation returned here, unlike using
diff --git a/core/java/android/util/Spline.java b/core/java/android/util/Spline.java
index 41a2e5d..bed3a60 100644
--- a/core/java/android/util/Spline.java
+++ b/core/java/android/util/Spline.java
@@ -165,7 +165,7 @@ public abstract class Spline {
throw new IllegalArgumentException("The control points must have "
+ "monotonic Y values.");
}
- float h = FloatMath.hypot(a, b);
+ float h = (float) Math.hypot(a, b);
if (h > 9f) {
float t = 3f / h;
m[i] = t * a * d[i];
diff --git a/core/java/android/util/StateSet.java b/core/java/android/util/StateSet.java
index 2623638..83dfc47 100644
--- a/core/java/android/util/StateSet.java
+++ b/core/java/android/util/StateSet.java
@@ -36,7 +36,88 @@ import com.android.internal.R;
*/
public class StateSet {
- /** @hide */ public StateSet() {}
+ /**
+ * The order here is very important to
+ * {@link android.view.View#getDrawableState()}
+ */
+ private static final int[][] VIEW_STATE_SETS;
+
+ /** @hide */
+ public static final int VIEW_STATE_WINDOW_FOCUSED = 1;
+ /** @hide */
+ public static final int VIEW_STATE_SELECTED = 1 << 1;
+ /** @hide */
+ public static final int VIEW_STATE_FOCUSED = 1 << 2;
+ /** @hide */
+ public static final int VIEW_STATE_ENABLED = 1 << 3;
+ /** @hide */
+ public static final int VIEW_STATE_PRESSED = 1 << 4;
+ /** @hide */
+ public static final int VIEW_STATE_ACTIVATED = 1 << 5;
+ /** @hide */
+ public static final int VIEW_STATE_ACCELERATED = 1 << 6;
+ /** @hide */
+ public static final int VIEW_STATE_HOVERED = 1 << 7;
+ /** @hide */
+ public static final int VIEW_STATE_DRAG_CAN_ACCEPT = 1 << 8;
+ /** @hide */
+ public static final int VIEW_STATE_DRAG_HOVERED = 1 << 9;
+
+ static final int[] VIEW_STATE_IDS = new int[] {
+ R.attr.state_window_focused, VIEW_STATE_WINDOW_FOCUSED,
+ R.attr.state_selected, VIEW_STATE_SELECTED,
+ R.attr.state_focused, VIEW_STATE_FOCUSED,
+ R.attr.state_enabled, VIEW_STATE_ENABLED,
+ R.attr.state_pressed, VIEW_STATE_PRESSED,
+ R.attr.state_activated, VIEW_STATE_ACTIVATED,
+ R.attr.state_accelerated, VIEW_STATE_ACCELERATED,
+ R.attr.state_hovered, VIEW_STATE_HOVERED,
+ R.attr.state_drag_can_accept, VIEW_STATE_DRAG_CAN_ACCEPT,
+ R.attr.state_drag_hovered, VIEW_STATE_DRAG_HOVERED
+ };
+
+ static {
+ if ((VIEW_STATE_IDS.length / 2) != R.styleable.ViewDrawableStates.length) {
+ throw new IllegalStateException(
+ "VIEW_STATE_IDs array length does not match ViewDrawableStates style array");
+ }
+
+ final int[] orderedIds = new int[VIEW_STATE_IDS.length];
+ for (int i = 0; i < R.styleable.ViewDrawableStates.length; i++) {
+ final int viewState = R.styleable.ViewDrawableStates[i];
+ for (int j = 0; j < VIEW_STATE_IDS.length; j += 2) {
+ if (VIEW_STATE_IDS[j] == viewState) {
+ orderedIds[i * 2] = viewState;
+ orderedIds[i * 2 + 1] = VIEW_STATE_IDS[j + 1];
+ }
+ }
+ }
+
+ final int NUM_BITS = VIEW_STATE_IDS.length / 2;
+ VIEW_STATE_SETS = new int[1 << NUM_BITS][];
+ for (int i = 0; i < VIEW_STATE_SETS.length; i++) {
+ final int numBits = Integer.bitCount(i);
+ final int[] set = new int[numBits];
+ int pos = 0;
+ for (int j = 0; j < orderedIds.length; j += 2) {
+ if ((i & orderedIds[j + 1]) != 0) {
+ set[pos++] = orderedIds[j];
+ }
+ }
+ VIEW_STATE_SETS[i] = set;
+ }
+ }
+
+ /** @hide */
+ public static int[] get(int mask) {
+ if (mask >= VIEW_STATE_SETS.length) {
+ throw new IllegalArgumentException("Invalid state set mask");
+ }
+ return VIEW_STATE_SETS[mask];
+ }
+
+ /** @hide */
+ public StateSet() {}
public static final int[] WILD_CARD = new int[0];
public static final int[] NOTHING = new int[] { 0 };
diff --git a/core/java/android/view/ActionMode.java b/core/java/android/view/ActionMode.java
index a359952..ae4b60f 100644
--- a/core/java/android/view/ActionMode.java
+++ b/core/java/android/view/ActionMode.java
@@ -29,8 +29,21 @@ package android.view;
* </div>
*/
public abstract class ActionMode {
+
+ /**
+ * The action mode is treated as a Primary mode. This is the default.
+ * Use with {@link #setType}.
+ */
+ public static final int TYPE_PRIMARY = 0;
+ /**
+ * The action mode is treated as a Floating Toolbar.
+ * Use with {@link #setType}.
+ */
+ public static final int TYPE_FLOATING = 1;
+
private Object mTag;
private boolean mTitleOptionalHint;
+ private int mType = TYPE_PRIMARY;
/**
* Set a tag object associated with this ActionMode.
@@ -154,6 +167,25 @@ public abstract class ActionMode {
public abstract void setCustomView(View view);
/**
+ * Set a type for this action mode. This will affect how the system renders the action mode if
+ * it has to.
+ *
+ * @param type One of {@link #TYPE_PRIMARY} or {@link #TYPE_FLOATING}.
+ */
+ public void setType(int type) {
+ mType = type;
+ }
+
+ /**
+ * Returns the type for this action mode.
+ *
+ * @return One of {@link #TYPE_PRIMARY} or {@link #TYPE_FLOATING}.
+ */
+ public int getType() {
+ return mType;
+ }
+
+ /**
* Invalidate the action mode and refresh menu content. The mode's
* {@link ActionMode.Callback} will have its
* {@link Callback#onPrepareActionMode(ActionMode, Menu)} method called.
diff --git a/core/java/android/view/ContextThemeWrapper.java b/core/java/android/view/ContextThemeWrapper.java
index 0afbde9..d9f6054 100644
--- a/core/java/android/view/ContextThemeWrapper.java
+++ b/core/java/android/view/ContextThemeWrapper.java
@@ -35,13 +35,19 @@ public class ContextThemeWrapper extends ContextWrapper {
public ContextThemeWrapper() {
super(null);
}
-
- public ContextThemeWrapper(Context base, int themeres) {
+
+ public ContextThemeWrapper(Context base, int themeResId) {
+ super(base);
+ mThemeResource = themeResId;
+ }
+
+ public ContextThemeWrapper(Context base, Resources.Theme theme) {
super(base);
- mThemeResource = themeres;
+ mTheme = theme;
}
- @Override protected void attachBaseContext(Context newBase) {
+ @Override
+ protected void attachBaseContext(Context newBase) {
super.attachBaseContext(newBase);
}
diff --git a/core/java/android/view/GLES20Canvas.java b/core/java/android/view/GLES20Canvas.java
index 60a489b..3cb4666 100644
--- a/core/java/android/view/GLES20Canvas.java
+++ b/core/java/android/view/GLES20Canvas.java
@@ -19,19 +19,13 @@ package android.view;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.CanvasProperty;
-import android.graphics.DrawFilter;
import android.graphics.Matrix;
import android.graphics.NinePatch;
import android.graphics.Paint;
-import android.graphics.PaintFlagsDrawFilter;
import android.graphics.Path;
import android.graphics.Picture;
-import android.graphics.PorterDuff;
import android.graphics.Rect;
import android.graphics.RectF;
-import android.graphics.Region;
-import android.graphics.Shader;
-import android.graphics.TemporaryBuffer;
import android.text.GraphicsOperations;
import android.text.SpannableString;
import android.text.SpannedString;
@@ -41,14 +35,6 @@ import android.text.TextUtils;
* An implementation of Canvas on top of OpenGL ES 2.0.
*/
class GLES20Canvas extends HardwareCanvas {
- private final boolean mOpaque;
- protected long mRenderer;
-
- // The native renderer will be destroyed when this object dies.
- // DO NOT overwrite this reference once it is set.
- @SuppressWarnings({"unused", "FieldCanBeLocal"})
- private CanvasFinalizer mFinalizer;
-
private int mWidth;
private int mHeight;
@@ -58,8 +44,6 @@ class GLES20Canvas extends HardwareCanvas {
private Rect mClipBounds;
private RectF mPathBounds;
- private DrawFilter mFilter;
-
///////////////////////////////////////////////////////////////////////////
// JNI
///////////////////////////////////////////////////////////////////////////
@@ -77,39 +61,10 @@ class GLES20Canvas extends HardwareCanvas {
// TODO: Merge with GLES20RecordingCanvas
protected GLES20Canvas() {
- mOpaque = false;
- mRenderer = nCreateDisplayListRenderer();
- setupFinalizer();
- }
-
- private void setupFinalizer() {
- if (mRenderer == 0) {
- throw new IllegalStateException("Could not create GLES20Canvas renderer");
- } else {
- mFinalizer = new CanvasFinalizer(mRenderer);
- }
+ super(nCreateDisplayListRenderer());
}
private static native long nCreateDisplayListRenderer();
- private static native void nResetDisplayListRenderer(long renderer);
- private static native void nDestroyRenderer(long renderer);
-
- private static final class CanvasFinalizer {
- private final long mRenderer;
-
- public CanvasFinalizer(long renderer) {
- mRenderer = renderer;
- }
-
- @Override
- protected void finalize() throws Throwable {
- try {
- nDestroyRenderer(mRenderer);
- } finally {
- super.finalize();
- }
- }
- }
public static void setProperty(String name, String value) {
nSetProperty(name, value);
@@ -123,7 +78,7 @@ class GLES20Canvas extends HardwareCanvas {
@Override
public boolean isOpaque() {
- return mOpaque;
+ return false;
}
@Override
@@ -153,7 +108,7 @@ class GLES20Canvas extends HardwareCanvas {
* Returns the native OpenGLRenderer object.
*/
long getRenderer() {
- return mRenderer;
+ return mNativeCanvasWrapper;
}
///////////////////////////////////////////////////////////////////////////
@@ -165,7 +120,7 @@ class GLES20Canvas extends HardwareCanvas {
mWidth = width;
mHeight = height;
- nSetViewport(mRenderer, width, height);
+ nSetViewport(mNativeCanvasWrapper, width, height);
}
private static native void nSetViewport(long renderer,
@@ -173,40 +128,38 @@ class GLES20Canvas extends HardwareCanvas {
@Override
public void setHighContrastText(boolean highContrastText) {
- nSetHighContrastText(mRenderer, highContrastText);
+ nSetHighContrastText(mNativeCanvasWrapper, highContrastText);
}
private static native void nSetHighContrastText(long renderer, boolean highContrastText);
@Override
public void insertReorderBarrier() {
- nInsertReorderBarrier(mRenderer, true);
+ nInsertReorderBarrier(mNativeCanvasWrapper, true);
}
@Override
public void insertInorderBarrier() {
- nInsertReorderBarrier(mRenderer, false);
+ nInsertReorderBarrier(mNativeCanvasWrapper, false);
}
private static native void nInsertReorderBarrier(long renderer, boolean enableReorder);
@Override
- public int onPreDraw(Rect dirty) {
+ public void onPreDraw(Rect dirty) {
if (dirty != null) {
- return nPrepareDirty(mRenderer, dirty.left, dirty.top, dirty.right, dirty.bottom,
- mOpaque);
+ nPrepareDirty(mNativeCanvasWrapper, dirty.left, dirty.top, dirty.right, dirty.bottom);
} else {
- return nPrepare(mRenderer, mOpaque);
+ nPrepare(mNativeCanvasWrapper);
}
}
- private static native int nPrepare(long renderer, boolean opaque);
- private static native int nPrepareDirty(long renderer, int left, int top, int right, int bottom,
- boolean opaque);
+ private static native void nPrepare(long renderer);
+ private static native void nPrepareDirty(long renderer, int left, int top, int right, int bottom);
@Override
public void onPostDraw() {
- nFinish(mRenderer);
+ nFinish(mNativeCanvasWrapper);
}
private static native void nFinish(long renderer);
@@ -216,11 +169,11 @@ class GLES20Canvas extends HardwareCanvas {
///////////////////////////////////////////////////////////////////////////
@Override
- public int callDrawGLFunction2(long drawGLFunction) {
- return nCallDrawGLFunction(mRenderer, drawGLFunction);
+ public void callDrawGLFunction2(long drawGLFunction) {
+ nCallDrawGLFunction(mNativeCanvasWrapper, drawGLFunction);
}
- private static native int nCallDrawGLFunction(long renderer, long drawGLFunction);
+ private static native void nCallDrawGLFunction(long renderer, long drawGLFunction);
///////////////////////////////////////////////////////////////////////////
// Display list
@@ -229,12 +182,12 @@ class GLES20Canvas extends HardwareCanvas {
protected static native long nFinishRecording(long renderer);
@Override
- public int drawRenderNode(RenderNode renderNode, Rect dirty, int flags) {
- return nDrawRenderNode(mRenderer, renderNode.getNativeDisplayList(), dirty, flags);
+ public void drawRenderNode(RenderNode renderNode, int flags) {
+ nDrawRenderNode(mNativeCanvasWrapper, renderNode.getNativeDisplayList(), flags);
}
- private static native int nDrawRenderNode(long renderer, long renderNode,
- Rect dirty, int flags);
+ private static native void nDrawRenderNode(long renderer, long renderNode,
+ int flags);
///////////////////////////////////////////////////////////////////////////
// Hardware layer
@@ -242,332 +195,32 @@ class GLES20Canvas extends HardwareCanvas {
void drawHardwareLayer(HardwareLayer layer, float x, float y, Paint paint) {
layer.setLayerPaint(paint);
- nDrawLayer(mRenderer, layer.getLayerHandle(), x, y);
+ nDrawLayer(mNativeCanvasWrapper, layer.getLayerHandle(), x, y);
}
private static native void nDrawLayer(long renderer, long layer, float x, float y);
///////////////////////////////////////////////////////////////////////////
- // Support
- ///////////////////////////////////////////////////////////////////////////
-
- private Rect getInternalClipBounds() {
- if (mClipBounds == null) mClipBounds = new Rect();
- return mClipBounds;
- }
-
-
- private RectF getPathBounds() {
- if (mPathBounds == null) mPathBounds = new RectF();
- return mPathBounds;
- }
-
- private float[] getPointStorage() {
- if (mPoint == null) mPoint = new float[2];
- return mPoint;
- }
-
- private float[] getLineStorage() {
- if (mLine == null) mLine = new float[4];
- return mLine;
- }
-
- ///////////////////////////////////////////////////////////////////////////
- // Clipping
- ///////////////////////////////////////////////////////////////////////////
-
- @Override
- public boolean clipPath(Path path) {
- return nClipPath(mRenderer, path.mNativePath, Region.Op.INTERSECT.nativeInt);
- }
-
- @Override
- public boolean clipPath(Path path, Region.Op op) {
- return nClipPath(mRenderer, path.mNativePath, op.nativeInt);
- }
-
- private static native boolean nClipPath(long renderer, long path, int op);
-
- @Override
- public boolean clipRect(float left, float top, float right, float bottom) {
- return nClipRect(mRenderer, left, top, right, bottom, Region.Op.INTERSECT.nativeInt);
- }
-
- private static native boolean nClipRect(long renderer, float left, float top,
- float right, float bottom, int op);
-
- @Override
- public boolean clipRect(float left, float top, float right, float bottom, Region.Op op) {
- return nClipRect(mRenderer, left, top, right, bottom, op.nativeInt);
- }
-
- @Override
- public boolean clipRect(int left, int top, int right, int bottom) {
- return nClipRect(mRenderer, left, top, right, bottom, Region.Op.INTERSECT.nativeInt);
- }
-
- private static native boolean nClipRect(long renderer, int left, int top,
- int right, int bottom, int op);
-
- @Override
- public boolean clipRect(Rect rect) {
- return nClipRect(mRenderer, rect.left, rect.top, rect.right, rect.bottom,
- Region.Op.INTERSECT.nativeInt);
- }
-
- @Override
- public boolean clipRect(Rect rect, Region.Op op) {
- return nClipRect(mRenderer, rect.left, rect.top, rect.right, rect.bottom, op.nativeInt);
- }
-
- @Override
- public boolean clipRect(RectF rect) {
- return nClipRect(mRenderer, rect.left, rect.top, rect.right, rect.bottom,
- Region.Op.INTERSECT.nativeInt);
- }
-
- @Override
- public boolean clipRect(RectF rect, Region.Op op) {
- return nClipRect(mRenderer, rect.left, rect.top, rect.right, rect.bottom, op.nativeInt);
- }
-
- @Override
- public boolean clipRegion(Region region) {
- return nClipRegion(mRenderer, region.mNativeRegion, Region.Op.INTERSECT.nativeInt);
- }
-
- @Override
- public boolean clipRegion(Region region, Region.Op op) {
- return nClipRegion(mRenderer, region.mNativeRegion, op.nativeInt);
- }
-
- private static native boolean nClipRegion(long renderer, long region, int op);
-
- @Override
- public boolean getClipBounds(Rect bounds) {
- return nGetClipBounds(mRenderer, bounds);
- }
-
- private static native boolean nGetClipBounds(long renderer, Rect bounds);
-
- @Override
- public boolean quickReject(float left, float top, float right, float bottom, EdgeType type) {
- return nQuickReject(mRenderer, left, top, right, bottom);
- }
-
- private static native boolean nQuickReject(long renderer, float left, float top,
- float right, float bottom);
-
- @Override
- public boolean quickReject(Path path, EdgeType type) {
- RectF pathBounds = getPathBounds();
- path.computeBounds(pathBounds, true);
- return nQuickReject(mRenderer, pathBounds.left, pathBounds.top,
- pathBounds.right, pathBounds.bottom);
- }
-
- @Override
- public boolean quickReject(RectF rect, EdgeType type) {
- return nQuickReject(mRenderer, rect.left, rect.top, rect.right, rect.bottom);
- }
-
- ///////////////////////////////////////////////////////////////////////////
- // Transformations
- ///////////////////////////////////////////////////////////////////////////
-
- @Override
- public void translate(float dx, float dy) {
- if (dx != 0.0f || dy != 0.0f) nTranslate(mRenderer, dx, dy);
- }
-
- private static native void nTranslate(long renderer, float dx, float dy);
-
- @Override
- public void skew(float sx, float sy) {
- nSkew(mRenderer, sx, sy);
- }
-
- private static native void nSkew(long renderer, float sx, float sy);
-
- @Override
- public void rotate(float degrees) {
- nRotate(mRenderer, degrees);
- }
-
- private static native void nRotate(long renderer, float degrees);
-
- @Override
- public void scale(float sx, float sy) {
- nScale(mRenderer, sx, sy);
- }
-
- private static native void nScale(long renderer, float sx, float sy);
-
- @Override
- public void setMatrix(Matrix matrix) {
- nSetMatrix(mRenderer, matrix == null ? 0 : matrix.native_instance);
- }
-
- private static native void nSetMatrix(long renderer, long matrix);
-
- @SuppressWarnings("deprecation")
- @Override
- public void getMatrix(Matrix matrix) {
- nGetMatrix(mRenderer, matrix.native_instance);
- }
-
- private static native void nGetMatrix(long renderer, long matrix);
-
- @Override
- public void concat(Matrix matrix) {
- if (matrix != null) nConcatMatrix(mRenderer, matrix.native_instance);
- }
-
- private static native void nConcatMatrix(long renderer, long matrix);
-
- ///////////////////////////////////////////////////////////////////////////
- // State management
- ///////////////////////////////////////////////////////////////////////////
-
- @Override
- public int save() {
- return nSave(mRenderer, Canvas.CLIP_SAVE_FLAG | Canvas.MATRIX_SAVE_FLAG);
- }
-
- @Override
- public int save(int saveFlags) {
- return nSave(mRenderer, saveFlags);
- }
-
- private static native int nSave(long renderer, int flags);
-
- @Override
- public int saveLayer(RectF bounds, Paint paint, int saveFlags) {
- if (bounds != null) {
- return saveLayer(bounds.left, bounds.top, bounds.right, bounds.bottom, paint, saveFlags);
- }
-
- final long nativePaint = paint == null ? 0 : paint.mNativePaint;
- return nSaveLayer(mRenderer, nativePaint, saveFlags);
- }
-
- private static native int nSaveLayer(long renderer, long paint, int saveFlags);
-
- @Override
- public int saveLayer(float left, float top, float right, float bottom, Paint paint,
- int saveFlags) {
- if (left < right && top < bottom) {
- final long nativePaint = paint == null ? 0 : paint.mNativePaint;
- return nSaveLayer(mRenderer, left, top, right, bottom, nativePaint, saveFlags);
- }
- return save(saveFlags);
- }
-
- private static native int nSaveLayer(long renderer, float left, float top,
- float right, float bottom, long paint, int saveFlags);
-
- @Override
- public int saveLayerAlpha(RectF bounds, int alpha, int saveFlags) {
- if (bounds != null) {
- return saveLayerAlpha(bounds.left, bounds.top, bounds.right, bounds.bottom,
- alpha, saveFlags);
- }
- return nSaveLayerAlpha(mRenderer, alpha, saveFlags);
- }
-
- private static native int nSaveLayerAlpha(long renderer, int alpha, int saveFlags);
-
- @Override
- public int saveLayerAlpha(float left, float top, float right, float bottom, int alpha,
- int saveFlags) {
- if (left < right && top < bottom) {
- return nSaveLayerAlpha(mRenderer, left, top, right, bottom, alpha, saveFlags);
- }
- return save(saveFlags);
- }
-
- private static native int nSaveLayerAlpha(long renderer, float left, float top, float right,
- float bottom, int alpha, int saveFlags);
-
- @Override
- public void restore() {
- nRestore(mRenderer);
- }
-
- private static native void nRestore(long renderer);
-
- @Override
- public void restoreToCount(int saveCount) {
- nRestoreToCount(mRenderer, saveCount);
- }
-
- private static native void nRestoreToCount(long renderer, int saveCount);
-
- @Override
- public int getSaveCount() {
- return nGetSaveCount(mRenderer);
- }
-
- private static native int nGetSaveCount(long renderer);
-
- ///////////////////////////////////////////////////////////////////////////
- // Filtering
- ///////////////////////////////////////////////////////////////////////////
-
- @Override
- public void setDrawFilter(DrawFilter filter) {
- mFilter = filter;
- if (filter == null) {
- nResetPaintFilter(mRenderer);
- } else if (filter instanceof PaintFlagsDrawFilter) {
- PaintFlagsDrawFilter flagsFilter = (PaintFlagsDrawFilter) filter;
- nSetupPaintFilter(mRenderer, flagsFilter.clearBits, flagsFilter.setBits);
- }
- }
-
- private static native void nResetPaintFilter(long renderer);
- private static native void nSetupPaintFilter(long renderer, int clearBits, int setBits);
-
- @Override
- public DrawFilter getDrawFilter() {
- return mFilter;
- }
-
- ///////////////////////////////////////////////////////////////////////////
// Drawing
///////////////////////////////////////////////////////////////////////////
- @Override
- public void drawArc(float left, float top, float right, float bottom,
- float startAngle, float sweepAngle, boolean useCenter, Paint paint) {
- nDrawArc(mRenderer, left, top, right, bottom,
- startAngle, sweepAngle, useCenter, paint.mNativePaint);
- }
-
- private static native void nDrawArc(long renderer, float left, float top,
- float right, float bottom, float startAngle, float sweepAngle,
- boolean useCenter, long paint);
-
- @Override
- public void drawARGB(int a, int r, int g, int b) {
- drawColor((a & 0xFF) << 24 | (r & 0xFF) << 16 | (g & 0xFF) << 8 | (b & 0xFF));
- }
-
+ // TODO: move to Canvas.java
@Override
public void drawPatch(NinePatch patch, Rect dst, Paint paint) {
Bitmap bitmap = patch.getBitmap();
throwIfCannotDraw(bitmap);
- final long nativePaint = paint == null ? 0 : paint.mNativePaint;
- nDrawPatch(mRenderer, bitmap.mNativeBitmap, patch.mNativeChunk,
+ final long nativePaint = paint == null ? 0 : paint.getNativeInstance();
+ nDrawPatch(mNativeCanvasWrapper, bitmap.mNativeBitmap, patch.mNativeChunk,
dst.left, dst.top, dst.right, dst.bottom, nativePaint);
}
+ // TODO: move to Canvas.java
@Override
public void drawPatch(NinePatch patch, RectF dst, Paint paint) {
Bitmap bitmap = patch.getBitmap();
throwIfCannotDraw(bitmap);
- final long nativePaint = paint == null ? 0 : paint.mNativePaint;
- nDrawPatch(mRenderer, bitmap.mNativeBitmap, patch.mNativeChunk,
+ final long nativePaint = paint == null ? 0 : paint.getNativeInstance();
+ nDrawPatch(mNativeCanvasWrapper, bitmap.mNativeBitmap, patch.mNativeChunk,
dst.left, dst.top, dst.right, dst.bottom, nativePaint);
}
@@ -575,148 +228,9 @@ class GLES20Canvas extends HardwareCanvas {
float left, float top, float right, float bottom, long paint);
@Override
- public void drawBitmap(Bitmap bitmap, float left, float top, Paint paint) {
- throwIfCannotDraw(bitmap);
- final long nativePaint = paint == null ? 0 : paint.mNativePaint;
- nDrawBitmap(mRenderer, bitmap.mNativeBitmap, left, top, nativePaint);
- }
-
- private static native void nDrawBitmap(long renderer, long bitmap, float left,
- float top, long paint);
-
- @Override
- public void drawBitmap(Bitmap bitmap, Matrix matrix, Paint paint) {
- throwIfCannotDraw(bitmap);
- final long nativePaint = paint == null ? 0 : paint.mNativePaint;
- nDrawBitmap(mRenderer, bitmap.mNativeBitmap, matrix.native_instance, nativePaint);
- }
-
- private static native void nDrawBitmap(long renderer, long bitmap,
- long matrix, long paint);
-
- @Override
- public void drawBitmap(Bitmap bitmap, Rect src, Rect dst, Paint paint) {
- throwIfCannotDraw(bitmap);
- final long nativePaint = paint == null ? 0 : paint.mNativePaint;
-
- int left, top, right, bottom;
- if (src == null) {
- left = top = 0;
- right = bitmap.getWidth();
- bottom = bitmap.getHeight();
- } else {
- left = src.left;
- right = src.right;
- top = src.top;
- bottom = src.bottom;
- }
-
- nDrawBitmap(mRenderer, bitmap.mNativeBitmap, left, top, right, bottom,
- dst.left, dst.top, dst.right, dst.bottom, nativePaint);
- }
-
- @Override
- public void drawBitmap(Bitmap bitmap, Rect src, RectF dst, Paint paint) {
- throwIfCannotDraw(bitmap);
- final long nativePaint = paint == null ? 0 : paint.mNativePaint;
-
- float left, top, right, bottom;
- if (src == null) {
- left = top = 0;
- right = bitmap.getWidth();
- bottom = bitmap.getHeight();
- } else {
- left = src.left;
- right = src.right;
- top = src.top;
- bottom = src.bottom;
- }
-
- nDrawBitmap(mRenderer, bitmap.mNativeBitmap, left, top, right, bottom,
- dst.left, dst.top, dst.right, dst.bottom, nativePaint);
- }
-
- private static native void nDrawBitmap(long renderer, long bitmap,
- float srcLeft, float srcTop, float srcRight, float srcBottom,
- float left, float top, float right, float bottom, long paint);
-
- @Override
- public void drawBitmap(int[] colors, int offset, int stride, float x, float y,
- int width, int height, boolean hasAlpha, Paint paint) {
- if (width < 0) {
- throw new IllegalArgumentException("width must be >= 0");
- }
-
- if (height < 0) {
- throw new IllegalArgumentException("height must be >= 0");
- }
-
- if (Math.abs(stride) < width) {
- throw new IllegalArgumentException("abs(stride) must be >= width");
- }
-
- int lastScanline = offset + (height - 1) * stride;
- int length = colors.length;
-
- if (offset < 0 || (offset + width > length) || lastScanline < 0 ||
- (lastScanline + width > length)) {
- throw new ArrayIndexOutOfBoundsException();
- }
-
- final long nativePaint = paint == null ? 0 : paint.mNativePaint;
- nDrawBitmap(mRenderer, colors, offset, stride, x, y,
- width, height, hasAlpha, nativePaint);
- }
-
- private static native void nDrawBitmap(long renderer, int[] colors, int offset, int stride,
- float x, float y, int width, int height, boolean hasAlpha, long nativePaint);
-
- @Override
- public void drawBitmap(int[] colors, int offset, int stride, int x, int y,
- int width, int height, boolean hasAlpha, Paint paint) {
- drawBitmap(colors, offset, stride, (float) x, (float) y, width, height, hasAlpha, paint);
- }
-
- @Override
- public void drawBitmapMesh(Bitmap bitmap, int meshWidth, int meshHeight, float[] verts,
- int vertOffset, int[] colors, int colorOffset, Paint paint) {
- throwIfCannotDraw(bitmap);
- if (meshWidth < 0 || meshHeight < 0 || vertOffset < 0 || colorOffset < 0) {
- throw new ArrayIndexOutOfBoundsException();
- }
-
- if (meshWidth == 0 || meshHeight == 0) {
- return;
- }
-
- final int count = (meshWidth + 1) * (meshHeight + 1);
- checkRange(verts.length, vertOffset, count * 2);
-
- if (colors != null) {
- checkRange(colors.length, colorOffset, count);
- }
-
- final long nativePaint = paint == null ? 0 : paint.mNativePaint;
- nDrawBitmapMesh(mRenderer, bitmap.mNativeBitmap, meshWidth, meshHeight,
- verts, vertOffset, colors, colorOffset, nativePaint);
- }
-
- private static native void nDrawBitmapMesh(long renderer, long bitmap,
- int meshWidth, int meshHeight, float[] verts, int vertOffset,
- int[] colors, int colorOffset, long paint);
-
- @Override
- public void drawCircle(float cx, float cy, float radius, Paint paint) {
- nDrawCircle(mRenderer, cx, cy, radius, paint.mNativePaint);
- }
-
- private static native void nDrawCircle(long renderer, float cx, float cy,
- float radius, long paint);
-
- @Override
public void drawCircle(CanvasProperty<Float> cx, CanvasProperty<Float> cy,
CanvasProperty<Float> radius, CanvasProperty<Paint> paint) {
- nDrawCircle(mRenderer, cx.getNativeContainer(), cy.getNativeContainer(),
+ nDrawCircle(mNativeCanvasWrapper, cx.getNativeContainer(), cy.getNativeContainer(),
radius.getNativeContainer(), paint.getNativeContainer());
}
@@ -727,7 +241,7 @@ class GLES20Canvas extends HardwareCanvas {
public void drawRoundRect(CanvasProperty<Float> left, CanvasProperty<Float> top,
CanvasProperty<Float> right, CanvasProperty<Float> bottom, CanvasProperty<Float> rx,
CanvasProperty<Float> ry, CanvasProperty<Paint> paint) {
- nDrawRoundRect(mRenderer, left.getNativeContainer(), top.getNativeContainer(),
+ nDrawRoundRect(mNativeCanvasWrapper, left.getNativeContainer(), top.getNativeContainer(),
right.getNativeContainer(), bottom.getNativeContainer(),
rx.getNativeContainer(), ry.getNativeContainer(),
paint.getNativeContainer());
@@ -736,73 +250,18 @@ class GLES20Canvas extends HardwareCanvas {
private static native void nDrawRoundRect(long renderer, long propLeft, long propTop,
long propRight, long propBottom, long propRx, long propRy, long propPaint);
- @Override
- public void drawColor(int color) {
- drawColor(color, PorterDuff.Mode.SRC_OVER);
- }
-
- @Override
- public void drawColor(int color, PorterDuff.Mode mode) {
- nDrawColor(mRenderer, color, mode.nativeInt);
- }
-
- private static native void nDrawColor(long renderer, int color, int mode);
-
- @Override
- public void drawLine(float startX, float startY, float stopX, float stopY, Paint paint) {
- float[] line = getLineStorage();
- line[0] = startX;
- line[1] = startY;
- line[2] = stopX;
- line[3] = stopY;
- drawLines(line, 0, 4, paint);
- }
-
- @Override
- public void drawLines(float[] pts, int offset, int count, Paint paint) {
- if (count < 4) return;
-
- if ((offset | count) < 0 || offset + count > pts.length) {
- throw new IllegalArgumentException("The lines array must contain 4 elements per line.");
- }
- nDrawLines(mRenderer, pts, offset, count, paint.mNativePaint);
- }
-
- private static native void nDrawLines(long renderer, float[] points,
- int offset, int count, long paint);
-
- @Override
- public void drawLines(float[] pts, Paint paint) {
- drawLines(pts, 0, pts.length, paint);
- }
-
- @Override
- public void drawOval(float left, float top, float right, float bottom, Paint paint) {
- nDrawOval(mRenderer, left, top, right, bottom, paint.mNativePaint);
- }
-
- private static native void nDrawOval(long renderer, float left, float top,
- float right, float bottom, long paint);
-
- @Override
- public void drawPaint(Paint paint) {
- final Rect r = getInternalClipBounds();
- nGetClipBounds(mRenderer, r);
- drawRect(r.left, r.top, r.right, r.bottom, paint);
- }
-
+ // TODO: move this optimization to Canvas.java
@Override
public void drawPath(Path path, Paint paint) {
if (path.isSimplePath) {
if (path.rects != null) {
- nDrawRects(mRenderer, path.rects.mNativeRegion, paint.mNativePaint);
+ nDrawRects(mNativeCanvasWrapper, path.rects.mNativeRegion, paint.getNativeInstance());
}
} else {
- nDrawPath(mRenderer, path.mNativePath, paint.mNativePaint);
+ super.drawPath(path, paint);
}
}
- private static native void nDrawPath(long renderer, long path, long paint);
private static native void nDrawRects(long renderer, long region, long paint);
@Override
@@ -810,190 +269,4 @@ class GLES20Canvas extends HardwareCanvas {
picture.endRecording();
// TODO: Implement rendering
}
-
- @Override
- public void drawPoint(float x, float y, Paint paint) {
- float[] point = getPointStorage();
- point[0] = x;
- point[1] = y;
- drawPoints(point, 0, 2, paint);
- }
-
- @Override
- public void drawPoints(float[] pts, Paint paint) {
- drawPoints(pts, 0, pts.length, paint);
- }
-
- @Override
- public void drawPoints(float[] pts, int offset, int count, Paint paint) {
- if (count < 2) return;
-
- nDrawPoints(mRenderer, pts, offset, count, paint.mNativePaint);
- }
-
- private static native void nDrawPoints(long renderer, float[] points,
- int offset, int count, long paint);
-
- // Note: drawPosText just uses implementation in Canvas
-
- @Override
- public void drawRect(float left, float top, float right, float bottom, Paint paint) {
- if (left == right || top == bottom) return;
- nDrawRect(mRenderer, left, top, right, bottom, paint.mNativePaint);
- }
-
- private static native void nDrawRect(long renderer, float left, float top,
- float right, float bottom, long paint);
-
- @Override
- public void drawRect(Rect r, Paint paint) {
- drawRect(r.left, r.top, r.right, r.bottom, paint);
- }
-
- @Override
- public void drawRect(RectF r, Paint paint) {
- drawRect(r.left, r.top, r.right, r.bottom, paint);
- }
-
- @Override
- public void drawRGB(int r, int g, int b) {
- drawColor(0xFF000000 | (r & 0xFF) << 16 | (g & 0xFF) << 8 | (b & 0xFF));
- }
-
- @Override
- public void drawRoundRect(float left, float top, float right, float bottom, float rx, float ry,
- Paint paint) {
- nDrawRoundRect(mRenderer, left, top, right, bottom, rx, ry, paint.mNativePaint);
- }
-
- private static native void nDrawRoundRect(long renderer, float left, float top,
- float right, float bottom, float rx, float y, long paint);
-
- @Override
- public void drawText(char[] text, int index, int count, float x, float y, Paint paint) {
- if ((index | count | (index + count) | (text.length - index - count)) < 0) {
- throw new IndexOutOfBoundsException();
- }
-
- nDrawText(mRenderer, text, index, count, x, y,
- paint.mBidiFlags, paint.mNativePaint, paint.mNativeTypeface);
- }
-
- private static native void nDrawText(long renderer, char[] text, int index, int count,
- float x, float y, int bidiFlags, long paint, long typeface);
-
- @Override
- public void drawText(CharSequence text, int start, int end, float x, float y, Paint paint) {
- if ((start | end | (end - start) | (text.length() - end)) < 0) {
- throw new IndexOutOfBoundsException();
- }
- if (text instanceof String || text instanceof SpannedString ||
- text instanceof SpannableString) {
- nDrawText(mRenderer, text.toString(), start, end, x, y, paint.mBidiFlags,
- paint.mNativePaint, paint.mNativeTypeface);
- } else if (text instanceof GraphicsOperations) {
- ((GraphicsOperations) text).drawText(this, start, end, x, y, paint);
- } else {
- char[] buf = TemporaryBuffer.obtain(end - start);
- TextUtils.getChars(text, start, end, buf, 0);
- nDrawText(mRenderer, buf, 0, end - start, x, y,
- paint.mBidiFlags, paint.mNativePaint, paint.mNativeTypeface);
- TemporaryBuffer.recycle(buf);
- }
- }
-
- @Override
- public void drawText(String text, int start, int end, float x, float y, Paint paint) {
- if ((start | end | (end - start) | (text.length() - end)) < 0) {
- throw new IndexOutOfBoundsException();
- }
-
- nDrawText(mRenderer, text, start, end, x, y,
- paint.mBidiFlags, paint.mNativePaint, paint.mNativeTypeface);
- }
-
- private static native void nDrawText(long renderer, String text, int start, int end,
- float x, float y, int bidiFlags, long paint, long typeface);
-
- @Override
- public void drawText(String text, float x, float y, Paint paint) {
- nDrawText(mRenderer, text, 0, text.length(), x, y,
- paint.mBidiFlags, paint.mNativePaint, paint.mNativeTypeface);
- }
-
- @Override
- public void drawTextOnPath(char[] text, int index, int count, Path path, float hOffset,
- float vOffset, Paint paint) {
- if (index < 0 || index + count > text.length) {
- throw new ArrayIndexOutOfBoundsException();
- }
-
- nDrawTextOnPath(mRenderer, text, index, count, path.mNativePath, hOffset, vOffset,
- paint.mBidiFlags, paint.mNativePaint, paint.mNativeTypeface);
- }
-
- private static native void nDrawTextOnPath(long renderer, char[] text, int index, int count,
- long path, float hOffset, float vOffset, int bidiFlags, long nativePaint,
- long typeface);
-
- @Override
- public void drawTextOnPath(String text, Path path, float hOffset, float vOffset, Paint paint) {
- if (text.length() == 0) return;
-
- nDrawTextOnPath(mRenderer, text, 0, text.length(), path.mNativePath, hOffset, vOffset,
- paint.mBidiFlags, paint.mNativePaint, paint.mNativeTypeface);
- }
-
- private static native void nDrawTextOnPath(long renderer, String text, int start, int end,
- long path, float hOffset, float vOffset, int bidiFlags, long nativePaint,
- long typeface);
-
- @Override
- public void drawTextRun(char[] text, int index, int count, int contextIndex, int contextCount,
- float x, float y, boolean isRtl, Paint paint) {
- if ((index | count | text.length - index - count) < 0) {
- throw new IndexOutOfBoundsException();
- }
-
- nDrawTextRun(mRenderer, text, index, count, contextIndex, contextCount, x, y, isRtl,
- paint.mNativePaint, paint.mNativeTypeface);
- }
-
- private static native void nDrawTextRun(long renderer, char[] text, int index, int count,
- int contextIndex, int contextCount, float x, float y, boolean isRtl, long nativePaint, long nativeTypeface);
-
- @Override
- public void drawTextRun(CharSequence text, int start, int end, int contextStart, int contextEnd,
- float x, float y, boolean isRtl, Paint paint) {
- if ((start | end | end - start | text.length() - end) < 0) {
- throw new IndexOutOfBoundsException();
- }
-
- if (text instanceof String || text instanceof SpannedString ||
- text instanceof SpannableString) {
- nDrawTextRun(mRenderer, text.toString(), start, end, contextStart,
- contextEnd, x, y, isRtl, paint.mNativePaint, paint.mNativeTypeface);
- } else if (text instanceof GraphicsOperations) {
- ((GraphicsOperations) text).drawTextRun(this, start, end,
- contextStart, contextEnd, x, y, isRtl, paint);
- } else {
- int contextLen = contextEnd - contextStart;
- int len = end - start;
- char[] buf = TemporaryBuffer.obtain(contextLen);
- TextUtils.getChars(text, contextStart, contextEnd, buf, 0);
- nDrawTextRun(mRenderer, buf, start - contextStart, len, 0, contextLen,
- x, y, isRtl, paint.mNativePaint, paint.mNativeTypeface);
- TemporaryBuffer.recycle(buf);
- }
- }
-
- private static native void nDrawTextRun(long renderer, String text, int start, int end,
- int contextStart, int contextEnd, float x, float y, boolean isRtl, long nativePaint, long nativeTypeface);
-
- @Override
- public void drawVertices(VertexMode mode, int vertexCount, float[] verts, int vertOffset,
- float[] texs, int texOffset, int[] colors, int colorOffset, short[] indices,
- int indexOffset, int indexCount, Paint paint) {
- // TODO: Implement
- }
}
diff --git a/core/java/android/view/GLES20RecordingCanvas.java b/core/java/android/view/GLES20RecordingCanvas.java
index 5e49d8e..5ca5626 100644
--- a/core/java/android/view/GLES20RecordingCanvas.java
+++ b/core/java/android/view/GLES20RecordingCanvas.java
@@ -56,7 +56,7 @@ class GLES20RecordingCanvas extends GLES20Canvas {
}
long finishRecording() {
- return nFinishRecording(mRenderer);
+ return nFinishRecording(mNativeCanvasWrapper);
}
@Override
diff --git a/core/java/android/view/HardwareCanvas.java b/core/java/android/view/HardwareCanvas.java
index 18accb8..cdb350f 100644
--- a/core/java/android/view/HardwareCanvas.java
+++ b/core/java/android/view/HardwareCanvas.java
@@ -29,6 +29,14 @@ import android.graphics.Rect;
*/
public abstract class HardwareCanvas extends Canvas {
+ /**
+ * Pass a reference to the native renderer to our superclass's
+ * constructor.
+ */
+ protected HardwareCanvas(long renderer) {
+ super(renderer);
+ }
+
@Override
public boolean isHardwareAccelerated() {
return true;
@@ -43,12 +51,10 @@ public abstract class HardwareCanvas extends Canvas {
* Invoked before any drawing operation is performed in this canvas.
*
* @param dirty The dirty rectangle to update, can be null.
- * @return {@link RenderNode#STATUS_DREW} if anything was drawn (such as a call to clear
- * the canvas).
*
* @hide
*/
- public abstract int onPreDraw(Rect dirty);
+ public abstract void onPreDraw(Rect dirty);
/**
* Invoked after all drawing operation have been performed.
@@ -64,7 +70,7 @@ public abstract class HardwareCanvas extends Canvas {
* @param renderNode The RenderNode to replay.
*/
public void drawRenderNode(RenderNode renderNode) {
- drawRenderNode(renderNode, null, RenderNode.FLAG_CLIP_CHILDREN);
+ drawRenderNode(renderNode, RenderNode.FLAG_CLIP_CHILDREN);
}
/**
@@ -75,12 +81,9 @@ public abstract class HardwareCanvas extends Canvas {
* @param flags Optional flags about drawing, see {@link RenderNode} for
* the possible flags.
*
- * @return One of {@link RenderNode#STATUS_DONE} or {@link RenderNode#STATUS_DREW}
- * if anything was drawn.
- *
* @hide
*/
- public abstract int drawRenderNode(RenderNode renderNode, Rect dirty, int flags);
+ public abstract void drawRenderNode(RenderNode renderNode, int flags);
/**
* Draws the specified layer onto this canvas.
@@ -101,11 +104,11 @@ public abstract class HardwareCanvas extends Canvas {
*
* @param drawGLFunction A native function pointer
*
- * @return {@link RenderNode#STATUS_DONE}
- *
* @hide
*/
- public abstract int callDrawGLFunction2(long drawGLFunction);
+ public void callDrawGLFunction2(long drawGLFunction) {
+ // Noop - this is done in the display list recorder subclass
+ }
public abstract void drawCircle(CanvasProperty<Float> cx, CanvasProperty<Float> cy,
CanvasProperty<Float> radius, CanvasProperty<Paint> paint);
diff --git a/core/java/android/view/HardwareLayer.java b/core/java/android/view/HardwareLayer.java
index a130bda..65ae8a6 100644
--- a/core/java/android/view/HardwareLayer.java
+++ b/core/java/android/view/HardwareLayer.java
@@ -52,7 +52,7 @@ final class HardwareLayer {
* @see View#setLayerPaint(android.graphics.Paint)
*/
public void setLayerPaint(Paint paint) {
- nSetLayerPaint(mFinalizer.get(), paint.mNativePaint);
+ nSetLayerPaint(mFinalizer.get(), paint.getNativeInstance());
mRenderer.pushLayerUpdate(this);
}
diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index 7b20e72..743f6b7 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -81,7 +81,7 @@ interface IWindowManager
void addAppToken(int addPos, IApplicationToken token, int groupId, int stackId,
int requestedOrientation, boolean fullscreen, boolean showWhenLocked, int userId,
int configChanges, boolean voiceInteraction, boolean launchTaskBehind);
- void setAppGroupId(IBinder token, int groupId);
+ void setAppTask(IBinder token, int taskId);
void setAppOrientation(IApplicationToken token, int requestedOrientation);
int getAppOrientation(IApplicationToken token);
void setFocusedApp(IBinder token, boolean moveFocusNow);
diff --git a/core/java/android/view/IWindowSession.aidl b/core/java/android/view/IWindowSession.aidl
index 7b13e84..d08ab46 100644
--- a/core/java/android/view/IWindowSession.aidl
+++ b/core/java/android/view/IWindowSession.aidl
@@ -188,9 +188,6 @@ interface IWindowSession {
void wallpaperCommandComplete(IBinder window, in Bundle result);
- void setUniverseTransform(IBinder window, float alpha, float offx, float offy,
- float dsdx, float dtdx, float dsdy, float dtdy);
-
/**
* Notifies that a rectangle on the screen has been requested.
*/
diff --git a/core/java/android/view/LayoutInflater.java b/core/java/android/view/LayoutInflater.java
index 1546877..a5225cb 100644
--- a/core/java/android/view/LayoutInflater.java
+++ b/core/java/android/view/LayoutInflater.java
@@ -16,22 +16,25 @@
package android.view;
-import android.graphics.Canvas;
-import android.os.Handler;
-import android.os.Message;
-import android.os.Trace;
-import android.widget.FrameLayout;
+import com.android.internal.R;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
+import android.annotation.Nullable;
import android.content.Context;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.content.res.XmlResourceParser;
+import android.graphics.Canvas;
+import android.os.Handler;
+import android.os.Message;
+import android.os.Trace;
import android.util.AttributeSet;
import android.util.Log;
+import android.util.TypedValue;
import android.util.Xml;
+import android.widget.FrameLayout;
import java.io.IOException;
import java.lang.reflect.Constructor;
@@ -64,6 +67,7 @@ import java.util.HashMap;
* @see Context#getSystemService
*/
public abstract class LayoutInflater {
+
private static final String TAG = LayoutInflater.class.getSimpleName();
private static final boolean DEBUG = false;
@@ -90,12 +94,16 @@ public abstract class LayoutInflater {
private HashMap<String, Boolean> mFilterMap;
+ private TypedValue mTempValue;
+
private static final String TAG_MERGE = "merge";
private static final String TAG_INCLUDE = "include";
private static final String TAG_1995 = "blink";
private static final String TAG_REQUEST_FOCUS = "requestFocus";
private static final String TAG_TAG = "tag";
+ private static final String ATTR_LAYOUT = "layout";
+
private static final int[] ATTRS_THEME = new int[] {
com.android.internal.R.attr.theme };
@@ -361,7 +369,7 @@ public abstract class LayoutInflater {
* this is the root View; otherwise it is the root of the inflated
* XML file.
*/
- public View inflate(int resource, ViewGroup root) {
+ public View inflate(int resource, @Nullable ViewGroup root) {
return inflate(resource, root, root != null);
}
@@ -381,7 +389,7 @@ public abstract class LayoutInflater {
* this is the root View; otherwise it is the root of the inflated
* XML file.
*/
- public View inflate(XmlPullParser parser, ViewGroup root) {
+ public View inflate(XmlPullParser parser, @Nullable ViewGroup root) {
return inflate(parser, root, root != null);
}
@@ -402,7 +410,7 @@ public abstract class LayoutInflater {
* attachToRoot is true, this is root; otherwise it is the root of
* the inflated XML file.
*/
- public View inflate(int resource, ViewGroup root, boolean attachToRoot) {
+ public View inflate(int resource, @Nullable ViewGroup root, boolean attachToRoot) {
final Resources res = getContext().getResources();
if (DEBUG) {
Log.d(TAG, "INFLATING from resource: \"" + res.getResourceName(resource) + "\" ("
@@ -439,7 +447,7 @@ public abstract class LayoutInflater {
* attachToRoot is true, this is root; otherwise it is the root of
* the inflated XML file.
*/
- public View inflate(XmlPullParser parser, ViewGroup root, boolean attachToRoot) {
+ public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
synchronized (mConstructorArgs) {
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "inflate");
@@ -837,7 +845,7 @@ public abstract class LayoutInflater {
throws XmlPullParserException, IOException {
int type;
- final TypedArray ta = mContext.obtainStyledAttributes(
+ final TypedArray ta = view.getContext().obtainStyledAttributes(
attrs, com.android.internal.R.styleable.ViewTag);
final int key = ta.getResourceId(com.android.internal.R.styleable.ViewTag_id, 0);
final CharSequence value = ta.getText(com.android.internal.R.styleable.ViewTag_value);
@@ -856,16 +864,41 @@ public abstract class LayoutInflater {
int type;
if (parent instanceof ViewGroup) {
- final int layout = attrs.getAttributeResourceValue(null, "layout", 0);
+ Context context = inheritContext ? parent.getContext() : mContext;
+
+ // Apply a theme wrapper, if requested.
+ final TypedArray ta = context.obtainStyledAttributes(attrs, ATTRS_THEME);
+ final int themeResId = ta.getResourceId(0, 0);
+ if (themeResId != 0) {
+ context = new ContextThemeWrapper(context, themeResId);
+ }
+ ta.recycle();
+
+ // If the layout is pointing to a theme attribute, we have to
+ // massage the value to get a resource identifier out of it.
+ int layout = attrs.getAttributeResourceValue(null, ATTR_LAYOUT, 0);
if (layout == 0) {
- final String value = attrs.getAttributeValue(null, "layout");
- if (value == null) {
- throw new InflateException("You must specifiy a layout in the"
+ final String value = attrs.getAttributeValue(null, ATTR_LAYOUT);
+ if (value == null || value.length() < 1) {
+ throw new InflateException("You must specify a layout in the"
+ " include tag: <include layout=\"@layout/layoutID\" />");
- } else {
- throw new InflateException("You must specifiy a valid layout "
- + "reference. The layout ID " + value + " is not valid.");
}
+
+ layout = context.getResources().getIdentifier(value.substring(1), null, null);
+ }
+
+ // The layout might be referencing a theme attribute.
+ if (mTempValue == null) {
+ mTempValue = new TypedValue();
+ }
+ if (layout != 0 && context.getTheme().resolveAttribute(layout, mTempValue, true)) {
+ layout = mTempValue.resourceId;
+ }
+
+ if (layout == 0) {
+ final String value = attrs.getAttributeValue(null, ATTR_LAYOUT);
+ throw new InflateException("You must specify a valid layout "
+ + "reference. The layout ID " + value + " is not valid.");
} else {
final XmlResourceParser childParser =
getContext().getResources().getLayout(layout);
@@ -893,6 +926,14 @@ public abstract class LayoutInflater {
inheritContext);
final ViewGroup group = (ViewGroup) parent;
+ final TypedArray a = context.obtainStyledAttributes(
+ attrs, R.styleable.Include);
+ final int id = a.getResourceId(R.styleable.Include_id, View.NO_ID);
+ final int visibility = a.getInt(R.styleable.Include_visibility, -1);
+ final boolean hasWidth = a.hasValue(R.styleable.Include_layout_width);
+ final boolean hasHeight = a.hasValue(R.styleable.Include_layout_height);
+ a.recycle();
+
// We try to load the layout params set in the <include /> tag. If
// they don't exist, we will rely on the layout params set in the
// included XML file.
@@ -902,28 +943,21 @@ public abstract class LayoutInflater {
// successfully loaded layout params from the <include /> tag,
// false means we need to rely on the included layout params.
ViewGroup.LayoutParams params = null;
- try {
- params = group.generateLayoutParams(attrs);
- } catch (RuntimeException e) {
- params = group.generateLayoutParams(childAttrs);
- } finally {
- if (params != null) {
- view.setLayoutParams(params);
+ if (hasWidth && hasHeight) {
+ try {
+ params = group.generateLayoutParams(attrs);
+ } catch (RuntimeException e) {
+ // Ignore, just fail over to child attrs.
}
}
+ if (params == null) {
+ params = group.generateLayoutParams(childAttrs);
+ }
+ view.setLayoutParams(params);
// Inflate all children.
rInflate(childParser, view, childAttrs, true, true);
- // Attempt to override the included layout's android:id with the
- // one set on the <include /> tag itself.
- TypedArray a = mContext.obtainStyledAttributes(attrs,
- com.android.internal.R.styleable.View, 0, 0);
- int id = a.getResourceId(com.android.internal.R.styleable.View_id, View.NO_ID);
- // While we're at it, let's try to override android:visibility.
- int visibility = a.getInt(com.android.internal.R.styleable.View_visibility, -1);
- a.recycle();
-
if (id != View.NO_ID) {
view.setId(id);
}
diff --git a/core/java/android/view/PhoneFallbackEventHandler.java b/core/java/android/view/PhoneFallbackEventHandler.java
new file mode 100644
index 0000000..fbf5732
--- /dev/null
+++ b/core/java/android/view/PhoneFallbackEventHandler.java
@@ -0,0 +1,288 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import android.app.KeyguardManager;
+import android.app.SearchManager;
+import android.content.ActivityNotFoundException;
+import android.content.Context;
+import android.content.Intent;
+import android.content.res.Configuration;
+import android.media.AudioManager;
+import android.media.session.MediaSessionLegacyHelper;
+import android.os.UserHandle;
+import android.telephony.TelephonyManager;
+import android.util.Slog;
+
+/**
+ * @hide
+ */
+public class PhoneFallbackEventHandler implements FallbackEventHandler {
+ private static String TAG = "PhoneFallbackEventHandler";
+ private static final boolean DEBUG = false;
+
+ Context mContext;
+ View mView;
+
+ AudioManager mAudioManager;
+ KeyguardManager mKeyguardManager;
+ SearchManager mSearchManager;
+ TelephonyManager mTelephonyManager;
+
+ public PhoneFallbackEventHandler(Context context) {
+ mContext = context;
+ }
+
+ public void setView(View v) {
+ mView = v;
+ }
+
+ public void preDispatchKeyEvent(KeyEvent event) {
+ getAudioManager().preDispatchKeyEvent(event, AudioManager.USE_DEFAULT_STREAM_TYPE);
+ }
+
+ public boolean dispatchKeyEvent(KeyEvent event) {
+
+ final int action = event.getAction();
+ final int keyCode = event.getKeyCode();
+
+ if (action == KeyEvent.ACTION_DOWN) {
+ return onKeyDown(keyCode, event);
+ } else {
+ return onKeyUp(keyCode, event);
+ }
+ }
+
+ boolean onKeyDown(int keyCode, KeyEvent event) {
+ /* ****************************************************************************
+ * HOW TO DECIDE WHERE YOUR KEY HANDLING GOES.
+ * See the comment in PhoneWindow.onKeyDown
+ * ****************************************************************************/
+ final KeyEvent.DispatcherState dispatcher = mView.getKeyDispatcherState();
+
+ switch (keyCode) {
+ case KeyEvent.KEYCODE_VOLUME_UP:
+ case KeyEvent.KEYCODE_VOLUME_DOWN:
+ case KeyEvent.KEYCODE_VOLUME_MUTE: {
+ MediaSessionLegacyHelper.getHelper(mContext).sendVolumeKeyEvent(event, false);
+ return true;
+ }
+
+
+ case KeyEvent.KEYCODE_MEDIA_PLAY:
+ case KeyEvent.KEYCODE_MEDIA_PAUSE:
+ case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE:
+ /* Suppress PLAY/PAUSE toggle when phone is ringing or in-call
+ * to avoid music playback */
+ if (getTelephonyManager().getCallState() != TelephonyManager.CALL_STATE_IDLE) {
+ return true; // suppress key event
+ }
+ case KeyEvent.KEYCODE_MUTE:
+ case KeyEvent.KEYCODE_HEADSETHOOK:
+ case KeyEvent.KEYCODE_MEDIA_STOP:
+ case KeyEvent.KEYCODE_MEDIA_NEXT:
+ case KeyEvent.KEYCODE_MEDIA_PREVIOUS:
+ case KeyEvent.KEYCODE_MEDIA_REWIND:
+ case KeyEvent.KEYCODE_MEDIA_RECORD:
+ case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD:
+ case KeyEvent.KEYCODE_MEDIA_AUDIO_TRACK: {
+ handleMediaKeyEvent(event);
+ return true;
+ }
+
+ case KeyEvent.KEYCODE_CALL: {
+ if (getKeyguardManager().inKeyguardRestrictedInputMode() || dispatcher == null) {
+ break;
+ }
+ if (event.getRepeatCount() == 0) {
+ dispatcher.startTracking(event, this);
+ } else if (event.isLongPress() && dispatcher.isTracking(event)) {
+ dispatcher.performedLongPress(event);
+ mView.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
+ // launch the VoiceDialer
+ Intent intent = new Intent(Intent.ACTION_VOICE_COMMAND);
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ try {
+ sendCloseSystemWindows();
+ mContext.startActivity(intent);
+ } catch (ActivityNotFoundException e) {
+ startCallActivity();
+ }
+ }
+ return true;
+ }
+
+ case KeyEvent.KEYCODE_CAMERA: {
+ if (getKeyguardManager().inKeyguardRestrictedInputMode() || dispatcher == null) {
+ break;
+ }
+ if (event.getRepeatCount() == 0) {
+ dispatcher.startTracking(event, this);
+ } else if (event.isLongPress() && dispatcher.isTracking(event)) {
+ dispatcher.performedLongPress(event);
+ mView.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
+ sendCloseSystemWindows();
+ // Broadcast an intent that the Camera button was longpressed
+ Intent intent = new Intent(Intent.ACTION_CAMERA_BUTTON, null);
+ intent.putExtra(Intent.EXTRA_KEY_EVENT, event);
+ mContext.sendOrderedBroadcastAsUser(intent, UserHandle.CURRENT_OR_SELF,
+ null, null, null, 0, null, null);
+ }
+ return true;
+ }
+
+ case KeyEvent.KEYCODE_SEARCH: {
+ if (getKeyguardManager().inKeyguardRestrictedInputMode() || dispatcher == null) {
+ break;
+ }
+ if (event.getRepeatCount() == 0) {
+ dispatcher.startTracking(event, this);
+ } else if (event.isLongPress() && dispatcher.isTracking(event)) {
+ Configuration config = mContext.getResources().getConfiguration();
+ if (config.keyboard == Configuration.KEYBOARD_NOKEYS
+ || config.hardKeyboardHidden == Configuration.HARDKEYBOARDHIDDEN_YES) {
+ // launch the search activity
+ Intent intent = new Intent(Intent.ACTION_SEARCH_LONG_PRESS);
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ try {
+ mView.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
+ sendCloseSystemWindows();
+ getSearchManager().stopSearch();
+ mContext.startActivity(intent);
+ // Only clear this if we successfully start the
+ // activity; otherwise we will allow the normal short
+ // press action to be performed.
+ dispatcher.performedLongPress(event);
+ return true;
+ } catch (ActivityNotFoundException e) {
+ // Ignore
+ }
+ }
+ }
+ break;
+ }
+ }
+ return false;
+ }
+
+ boolean onKeyUp(int keyCode, KeyEvent event) {
+ if (DEBUG) {
+ Slog.d(TAG, "up " + keyCode);
+ }
+ final KeyEvent.DispatcherState dispatcher = mView.getKeyDispatcherState();
+ if (dispatcher != null) {
+ dispatcher.handleUpEvent(event);
+ }
+
+ switch (keyCode) {
+ case KeyEvent.KEYCODE_VOLUME_UP:
+ case KeyEvent.KEYCODE_VOLUME_DOWN:
+ case KeyEvent.KEYCODE_VOLUME_MUTE: {
+ if (!event.isCanceled()) {
+ MediaSessionLegacyHelper.getHelper(mContext).sendVolumeKeyEvent(event, false);
+ }
+ return true;
+ }
+
+ case KeyEvent.KEYCODE_HEADSETHOOK:
+ case KeyEvent.KEYCODE_MUTE:
+ case KeyEvent.KEYCODE_MEDIA_PLAY:
+ case KeyEvent.KEYCODE_MEDIA_PAUSE:
+ case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE:
+ case KeyEvent.KEYCODE_MEDIA_STOP:
+ case KeyEvent.KEYCODE_MEDIA_NEXT:
+ case KeyEvent.KEYCODE_MEDIA_PREVIOUS:
+ case KeyEvent.KEYCODE_MEDIA_REWIND:
+ case KeyEvent.KEYCODE_MEDIA_RECORD:
+ case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD:
+ case KeyEvent.KEYCODE_MEDIA_AUDIO_TRACK: {
+ handleMediaKeyEvent(event);
+ return true;
+ }
+
+ case KeyEvent.KEYCODE_CAMERA: {
+ if (getKeyguardManager().inKeyguardRestrictedInputMode()) {
+ break;
+ }
+ if (event.isTracking() && !event.isCanceled()) {
+ // Add short press behavior here if desired
+ }
+ return true;
+ }
+
+ case KeyEvent.KEYCODE_CALL: {
+ if (getKeyguardManager().inKeyguardRestrictedInputMode()) {
+ break;
+ }
+ if (event.isTracking() && !event.isCanceled()) {
+ startCallActivity();
+ }
+ return true;
+ }
+ }
+ return false;
+ }
+
+ void startCallActivity() {
+ sendCloseSystemWindows();
+ Intent intent = new Intent(Intent.ACTION_CALL_BUTTON);
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ try {
+ mContext.startActivity(intent);
+ } catch (ActivityNotFoundException e) {
+ Slog.w(TAG, "No activity found for android.intent.action.CALL_BUTTON.");
+ }
+ }
+
+ SearchManager getSearchManager() {
+ if (mSearchManager == null) {
+ mSearchManager = (SearchManager) mContext.getSystemService(Context.SEARCH_SERVICE);
+ }
+ return mSearchManager;
+ }
+
+ TelephonyManager getTelephonyManager() {
+ if (mTelephonyManager == null) {
+ mTelephonyManager = (TelephonyManager)mContext.getSystemService(
+ Context.TELEPHONY_SERVICE);
+ }
+ return mTelephonyManager;
+ }
+
+ KeyguardManager getKeyguardManager() {
+ if (mKeyguardManager == null) {
+ mKeyguardManager = (KeyguardManager)mContext.getSystemService(Context.KEYGUARD_SERVICE);
+ }
+ return mKeyguardManager;
+ }
+
+ AudioManager getAudioManager() {
+ if (mAudioManager == null) {
+ mAudioManager = (AudioManager)mContext.getSystemService(Context.AUDIO_SERVICE);
+ }
+ return mAudioManager;
+ }
+
+ void sendCloseSystemWindows() {
+ PhoneWindow.sendCloseSystemWindows(mContext, null);
+ }
+
+ private void handleMediaKeyEvent(KeyEvent keyEvent) {
+ MediaSessionLegacyHelper.getHelper(mContext).sendMediaButtonEvent(keyEvent, false);
+ }
+}
+
diff --git a/core/java/android/view/PhoneLayoutInflater.java b/core/java/android/view/PhoneLayoutInflater.java
new file mode 100644
index 0000000..7d89a0b
--- /dev/null
+++ b/core/java/android/view/PhoneLayoutInflater.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2006 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.view;
+
+import android.content.Context;
+import android.util.AttributeSet;
+
+/**
+ * @hide
+ */
+public class PhoneLayoutInflater extends LayoutInflater {
+ private static final String[] sClassPrefixList = {
+ "android.widget.",
+ "android.webkit.",
+ "android.app."
+ };
+
+ /**
+ * Instead of instantiating directly, you should retrieve an instance
+ * through {@link Context#getSystemService}
+ *
+ * @param context The Context in which in which to find resources and other
+ * application-specific things.
+ *
+ * @see Context#getSystemService
+ */
+ public PhoneLayoutInflater(Context context) {
+ super(context);
+ }
+
+ protected PhoneLayoutInflater(LayoutInflater original, Context newContext) {
+ super(original, newContext);
+ }
+
+ /** Override onCreateView to instantiate names that correspond to the
+ widgets known to the Widget factory. If we don't find a match,
+ call through to our super class.
+ */
+ @Override protected View onCreateView(String name, AttributeSet attrs) throws ClassNotFoundException {
+ for (String prefix : sClassPrefixList) {
+ try {
+ View view = createView(name, prefix, attrs);
+ if (view != null) {
+ return view;
+ }
+ } catch (ClassNotFoundException e) {
+ // In this case we want to let the base class take a crack
+ // at it.
+ }
+ }
+
+ return super.onCreateView(name, attrs);
+ }
+
+ public LayoutInflater cloneInContext(Context newContext) {
+ return new PhoneLayoutInflater(this, newContext);
+ }
+}
+
diff --git a/core/java/android/view/PhoneWindow.java b/core/java/android/view/PhoneWindow.java
new file mode 100644
index 0000000..5f4d201
--- /dev/null
+++ b/core/java/android/view/PhoneWindow.java
@@ -0,0 +1,4778 @@
+/*
+ *
+ * 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.view;
+
+import static android.view.View.MeasureSpec.AT_MOST;
+import static android.view.View.MeasureSpec.EXACTLY;
+import static android.view.View.MeasureSpec.getMode;
+import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
+import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
+import static android.view.WindowManager.LayoutParams.*;
+
+import android.app.ActivityManagerNative;
+import android.app.SearchManager;
+import android.os.UserHandle;
+import com.android.internal.R;
+import com.android.internal.view.RootViewSurfaceTaker;
+import com.android.internal.view.StandaloneActionMode;
+import com.android.internal.view.menu.ContextMenuBuilder;
+import com.android.internal.view.menu.IconMenuPresenter;
+import com.android.internal.view.menu.ListMenuPresenter;
+import com.android.internal.view.menu.MenuBuilder;
+import com.android.internal.view.menu.MenuDialogHelper;
+import com.android.internal.view.menu.MenuPresenter;
+import com.android.internal.view.menu.MenuView;
+import com.android.internal.widget.ActionBarContextView;
+import com.android.internal.widget.BackgroundFallback;
+import com.android.internal.widget.DecorContentParent;
+import com.android.internal.widget.SwipeDismissLayout;
+
+import android.app.ActivityManager;
+import android.app.KeyguardManager;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.res.Configuration;
+import android.content.res.Resources.Theme;
+import android.content.res.TypedArray;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.PixelFormat;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.media.AudioManager;
+import android.media.session.MediaController;
+import android.media.session.MediaSessionLegacyHelper;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.transition.Scene;
+import android.transition.Transition;
+import android.transition.TransitionInflater;
+import android.transition.TransitionManager;
+import android.transition.TransitionSet;
+import android.util.AndroidRuntimeException;
+import android.util.DisplayMetrics;
+import android.util.EventLog;
+import android.util.Log;
+import android.util.SparseArray;
+import android.util.TypedValue;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityManager;
+import android.view.animation.Animation;
+import android.view.animation.AnimationUtils;
+import android.view.animation.Interpolator;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
+import android.widget.PopupWindow;
+import android.widget.ProgressBar;
+import android.widget.TextView;
+
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+
+/**
+ * Android-specific Window.
+ * <p>
+ * todo: need to pull the generic functionality out into a base class
+ * in android.widget.
+ *
+ * @hide
+ */
+public class PhoneWindow extends Window implements MenuBuilder.Callback {
+
+ private final static String TAG = "PhoneWindow";
+
+ private final static boolean SWEEP_OPEN_MENU = false;
+
+ private final static int DEFAULT_BACKGROUND_FADE_DURATION_MS = 300;
+
+ private static final int CUSTOM_TITLE_COMPATIBLE_FEATURES = DEFAULT_FEATURES |
+ (1 << FEATURE_CUSTOM_TITLE) |
+ (1 << FEATURE_CONTENT_TRANSITIONS) |
+ (1 << FEATURE_ACTIVITY_TRANSITIONS) |
+ (1 << FEATURE_ACTION_MODE_OVERLAY);
+
+ private static final Transition USE_DEFAULT_TRANSITION = new TransitionSet();
+
+ /**
+ * Simple callback used by the context menu and its submenus. The options
+ * menu submenus do not use this (their behavior is more complex).
+ */
+ final DialogMenuCallback mContextMenuCallback = new DialogMenuCallback(FEATURE_CONTEXT_MENU);
+
+ final TypedValue mMinWidthMajor = new TypedValue();
+ final TypedValue mMinWidthMinor = new TypedValue();
+ TypedValue mFixedWidthMajor;
+ TypedValue mFixedWidthMinor;
+ TypedValue mFixedHeightMajor;
+ TypedValue mFixedHeightMinor;
+ TypedValue mOutsetBottom;
+
+ // This is the top-level view of the window, containing the window decor.
+ private DecorView mDecor;
+
+ // This is the view in which the window contents are placed. It is either
+ // mDecor itself, or a child of mDecor where the contents go.
+ private ViewGroup mContentParent;
+
+ private ViewGroup mContentRoot;
+
+ SurfaceHolder.Callback2 mTakeSurfaceCallback;
+
+ InputQueue.Callback mTakeInputQueueCallback;
+
+ private boolean mIsFloating;
+
+ private LayoutInflater mLayoutInflater;
+
+ private TextView mTitleView;
+
+ private DecorContentParent mDecorContentParent;
+ private ActionMenuPresenterCallback mActionMenuPresenterCallback;
+ private PanelMenuPresenterCallback mPanelMenuPresenterCallback;
+
+ private TransitionManager mTransitionManager;
+ private Scene mContentScene;
+
+ // The icon resource has been explicitly set elsewhere
+ // and should not be overwritten with a default.
+ static final int FLAG_RESOURCE_SET_ICON = 1 << 0;
+
+ // The logo resource has been explicitly set elsewhere
+ // and should not be overwritten with a default.
+ static final int FLAG_RESOURCE_SET_LOGO = 1 << 1;
+
+ // The icon resource is currently configured to use the system fallback
+ // as no default was previously specified. Anything can override this.
+ static final int FLAG_RESOURCE_SET_ICON_FALLBACK = 1 << 2;
+
+ int mResourcesSetFlags;
+ int mIconRes;
+ int mLogoRes;
+
+ private DrawableFeatureState[] mDrawables;
+
+ private PanelFeatureState[] mPanels;
+
+ /**
+ * The panel that is prepared or opened (the most recent one if there are
+ * multiple panels). Shortcuts will go to this panel. It gets set in
+ * {@link #preparePanel} and cleared in {@link #closePanel}.
+ */
+ private PanelFeatureState mPreparedPanel;
+
+ /**
+ * The keycode that is currently held down (as a modifier) for chording. If
+ * this is 0, there is no key held down.
+ */
+ private int mPanelChordingKey;
+
+ private ImageView mLeftIconView;
+
+ private ImageView mRightIconView;
+
+ private ProgressBar mCircularProgressBar;
+
+ private ProgressBar mHorizontalProgressBar;
+
+ private int mBackgroundResource = 0;
+ private int mBackgroundFallbackResource = 0;
+
+ private Drawable mBackgroundDrawable;
+
+ private float mElevation;
+
+ /** Whether window content should be clipped to the background outline. */
+ private boolean mClipToOutline;
+
+ private int mFrameResource = 0;
+
+ private int mTextColor = 0;
+ private int mStatusBarColor = 0;
+ private int mNavigationBarColor = 0;
+ private boolean mForcedStatusBarColor = false;
+ private boolean mForcedNavigationBarColor = false;
+
+ private CharSequence mTitle = null;
+
+ private int mTitleColor = 0;
+
+ private boolean mAlwaysReadCloseOnTouchAttr = false;
+
+ private ContextMenuBuilder mContextMenu;
+ private MenuDialogHelper mContextMenuHelper;
+ private boolean mClosingActionMenu;
+
+ private int mVolumeControlStreamType = AudioManager.USE_DEFAULT_STREAM_TYPE;
+ private MediaController mMediaController;
+
+ private AudioManager mAudioManager;
+ private KeyguardManager mKeyguardManager;
+
+ private int mUiOptions = 0;
+
+ private boolean mInvalidatePanelMenuPosted;
+ private int mInvalidatePanelMenuFeatures;
+ private final Runnable mInvalidatePanelMenuRunnable = new Runnable() {
+ @Override public void run() {
+ for (int i = 0; i <= FEATURE_MAX; i++) {
+ if ((mInvalidatePanelMenuFeatures & 1 << i) != 0) {
+ doInvalidatePanelMenu(i);
+ }
+ }
+ mInvalidatePanelMenuPosted = false;
+ mInvalidatePanelMenuFeatures = 0;
+ }
+ };
+
+ private Transition mEnterTransition = null;
+ private Transition mReturnTransition = USE_DEFAULT_TRANSITION;
+ private Transition mExitTransition = null;
+ private Transition mReenterTransition = USE_DEFAULT_TRANSITION;
+ private Transition mSharedElementEnterTransition = null;
+ private Transition mSharedElementReturnTransition = USE_DEFAULT_TRANSITION;
+ private Transition mSharedElementExitTransition = null;
+ private Transition mSharedElementReenterTransition = USE_DEFAULT_TRANSITION;
+ private Boolean mAllowReturnTransitionOverlap;
+ private Boolean mAllowEnterTransitionOverlap;
+ private long mBackgroundFadeDurationMillis = -1;
+ private Boolean mSharedElementsUseOverlay;
+
+ private Rect mTempRect;
+
+ static class WindowManagerHolder {
+ static final IWindowManager sWindowManager = IWindowManager.Stub.asInterface(
+ ServiceManager.getService("window"));
+ }
+
+ static final RotationWatcher sRotationWatcher = new RotationWatcher();
+
+ public PhoneWindow(Context context) {
+ super(context);
+ mLayoutInflater = LayoutInflater.from(context);
+ }
+
+ @Override
+ public final void setContainer(Window container) {
+ super.setContainer(container);
+ }
+
+ @Override
+ public boolean requestFeature(int featureId) {
+ if (mContentParent != null) {
+ throw new AndroidRuntimeException("requestFeature() must be called before adding content");
+ }
+ final int features = getFeatures();
+ final int newFeatures = features | (1 << featureId);
+ if ((newFeatures & (1 << FEATURE_CUSTOM_TITLE)) != 0 &&
+ (newFeatures & ~CUSTOM_TITLE_COMPATIBLE_FEATURES) != 0) {
+ // Another feature is enabled and the user is trying to enable the custom title feature
+ // or custom title feature is enabled and the user is trying to enable another feature
+ throw new AndroidRuntimeException(
+ "You cannot combine custom titles with other title features");
+ }
+ if ((features & (1 << FEATURE_NO_TITLE)) != 0 && featureId == FEATURE_ACTION_BAR) {
+ return false; // Ignore. No title dominates.
+ }
+ if ((features & (1 << FEATURE_ACTION_BAR)) != 0 && featureId == FEATURE_NO_TITLE) {
+ // Remove the action bar feature if we have no title. No title dominates.
+ removeFeature(FEATURE_ACTION_BAR);
+ }
+
+ if ((features & (1 << FEATURE_ACTION_BAR)) != 0 && featureId == FEATURE_SWIPE_TO_DISMISS) {
+ throw new AndroidRuntimeException(
+ "You cannot combine swipe dismissal and the action bar.");
+ }
+ if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0 && featureId == FEATURE_ACTION_BAR) {
+ throw new AndroidRuntimeException(
+ "You cannot combine swipe dismissal and the action bar.");
+ }
+
+ if (featureId == FEATURE_INDETERMINATE_PROGRESS &&
+ getContext().getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH)) {
+ throw new AndroidRuntimeException("You cannot use indeterminate progress on a watch.");
+ }
+ return super.requestFeature(featureId);
+ }
+
+ @Override
+ public void setUiOptions(int uiOptions) {
+ mUiOptions = uiOptions;
+ }
+
+ @Override
+ public void setUiOptions(int uiOptions, int mask) {
+ mUiOptions = (mUiOptions & ~mask) | (uiOptions & mask);
+ }
+
+ @Override
+ public TransitionManager getTransitionManager() {
+ return mTransitionManager;
+ }
+
+ @Override
+ public void setTransitionManager(TransitionManager tm) {
+ mTransitionManager = tm;
+ }
+
+ @Override
+ public Scene getContentScene() {
+ return mContentScene;
+ }
+
+ @Override
+ public void setContentView(int layoutResID) {
+ // Note: FEATURE_CONTENT_TRANSITIONS may be set in the process of installing the window
+ // decor, when theme attributes and the like are crystalized. Do not check the feature
+ // before this happens.
+ if (mContentParent == null) {
+ installDecor();
+ } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
+ mContentParent.removeAllViews();
+ }
+
+ if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
+ final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
+ getContext());
+ transitionTo(newScene);
+ } else {
+ mLayoutInflater.inflate(layoutResID, mContentParent);
+ }
+ final Callback cb = getCallback();
+ if (cb != null && !isDestroyed()) {
+ cb.onContentChanged();
+ }
+ }
+
+ @Override
+ public void setContentView(View view) {
+ setContentView(view, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
+ }
+
+ @Override
+ public void setContentView(View view, ViewGroup.LayoutParams params) {
+ // Note: FEATURE_CONTENT_TRANSITIONS may be set in the process of installing the window
+ // decor, when theme attributes and the like are crystalized. Do not check the feature
+ // before this happens.
+ if (mContentParent == null) {
+ installDecor();
+ } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
+ mContentParent.removeAllViews();
+ }
+
+ if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
+ view.setLayoutParams(params);
+ final Scene newScene = new Scene(mContentParent, view);
+ transitionTo(newScene);
+ } else {
+ mContentParent.addView(view, params);
+ }
+ final Callback cb = getCallback();
+ if (cb != null && !isDestroyed()) {
+ cb.onContentChanged();
+ }
+ }
+
+ @Override
+ public void addContentView(View view, ViewGroup.LayoutParams params) {
+ if (mContentParent == null) {
+ installDecor();
+ }
+ if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
+ // TODO Augment the scenes/transitions API to support this.
+ Log.v(TAG, "addContentView does not support content transitions");
+ }
+ mContentParent.addView(view, params);
+ final Callback cb = getCallback();
+ if (cb != null && !isDestroyed()) {
+ cb.onContentChanged();
+ }
+ }
+
+ private void transitionTo(Scene scene) {
+ if (mContentScene == null) {
+ scene.enter();
+ } else {
+ mTransitionManager.transitionTo(scene);
+ }
+ mContentScene = scene;
+ }
+
+ @Override
+ public View getCurrentFocus() {
+ return mDecor != null ? mDecor.findFocus() : null;
+ }
+
+ @Override
+ public void takeSurface(SurfaceHolder.Callback2 callback) {
+ mTakeSurfaceCallback = callback;
+ }
+
+ public void takeInputQueue(InputQueue.Callback callback) {
+ mTakeInputQueueCallback = callback;
+ }
+
+ @Override
+ public boolean isFloating() {
+ return mIsFloating;
+ }
+
+ /**
+ * Return a LayoutInflater instance that can be used to inflate XML view layout
+ * resources for use in this Window.
+ *
+ * @return LayoutInflater The shared LayoutInflater.
+ */
+ @Override
+ public LayoutInflater getLayoutInflater() {
+ return mLayoutInflater;
+ }
+
+ @Override
+ public void setTitle(CharSequence title) {
+ if (mTitleView != null) {
+ mTitleView.setText(title);
+ } else if (mDecorContentParent != null) {
+ mDecorContentParent.setWindowTitle(title);
+ }
+ mTitle = title;
+ }
+
+ @Override
+ @Deprecated
+ public void setTitleColor(int textColor) {
+ if (mTitleView != null) {
+ mTitleView.setTextColor(textColor);
+ }
+ mTitleColor = textColor;
+ }
+
+ /**
+ * Prepares the panel to either be opened or chorded. This creates the Menu
+ * instance for the panel and populates it via the Activity callbacks.
+ *
+ * @param st The panel state to prepare.
+ * @param event The event that triggered the preparing of the panel.
+ * @return Whether the panel was prepared. If the panel should not be shown,
+ * returns false.
+ */
+ public final boolean preparePanel(PanelFeatureState st, KeyEvent event) {
+ if (isDestroyed()) {
+ return false;
+ }
+
+ // Already prepared (isPrepared will be reset to false later)
+ if (st.isPrepared) {
+ return true;
+ }
+
+ if ((mPreparedPanel != null) && (mPreparedPanel != st)) {
+ // Another Panel is prepared and possibly open, so close it
+ closePanel(mPreparedPanel, false);
+ }
+
+ final Callback cb = getCallback();
+
+ if (cb != null) {
+ st.createdPanelView = cb.onCreatePanelView(st.featureId);
+ }
+
+ final boolean isActionBarMenu =
+ (st.featureId == FEATURE_OPTIONS_PANEL || st.featureId == FEATURE_ACTION_BAR);
+
+ if (isActionBarMenu && mDecorContentParent != null) {
+ // Enforce ordering guarantees around events so that the action bar never
+ // dispatches menu-related events before the panel is prepared.
+ mDecorContentParent.setMenuPrepared();
+ }
+
+ if (st.createdPanelView == null) {
+ // Init the panel state's menu--return false if init failed
+ if (st.menu == null || st.refreshMenuContent) {
+ if (st.menu == null) {
+ if (!initializePanelMenu(st) || (st.menu == null)) {
+ return false;
+ }
+ }
+
+ if (isActionBarMenu && mDecorContentParent != null) {
+ if (mActionMenuPresenterCallback == null) {
+ mActionMenuPresenterCallback = new ActionMenuPresenterCallback();
+ }
+ mDecorContentParent.setMenu(st.menu, mActionMenuPresenterCallback);
+ }
+
+ // Call callback, and return if it doesn't want to display menu.
+
+ // Creating the panel menu will involve a lot of manipulation;
+ // don't dispatch change events to presenters until we're done.
+ st.menu.stopDispatchingItemsChanged();
+ if ((cb == null) || !cb.onCreatePanelMenu(st.featureId, st.menu)) {
+ // Ditch the menu created above
+ st.setMenu(null);
+
+ if (isActionBarMenu && mDecorContentParent != null) {
+ // Don't show it in the action bar either
+ mDecorContentParent.setMenu(null, mActionMenuPresenterCallback);
+ }
+
+ return false;
+ }
+
+ st.refreshMenuContent = false;
+ }
+
+ // Callback and return if the callback does not want to show the menu
+
+ // Preparing the panel menu can involve a lot of manipulation;
+ // don't dispatch change events to presenters until we're done.
+ st.menu.stopDispatchingItemsChanged();
+
+ // Restore action view state before we prepare. This gives apps
+ // an opportunity to override frozen/restored state in onPrepare.
+ if (st.frozenActionViewState != null) {
+ st.menu.restoreActionViewStates(st.frozenActionViewState);
+ st.frozenActionViewState = null;
+ }
+
+ if (!cb.onPreparePanel(st.featureId, st.createdPanelView, st.menu)) {
+ if (isActionBarMenu && mDecorContentParent != null) {
+ // The app didn't want to show the menu for now but it still exists.
+ // Clear it out of the action bar.
+ mDecorContentParent.setMenu(null, mActionMenuPresenterCallback);
+ }
+ st.menu.startDispatchingItemsChanged();
+ return false;
+ }
+
+ // Set the proper keymap
+ KeyCharacterMap kmap = KeyCharacterMap.load(
+ event != null ? event.getDeviceId() : KeyCharacterMap.VIRTUAL_KEYBOARD);
+ st.qwertyMode = kmap.getKeyboardType() != KeyCharacterMap.NUMERIC;
+ st.menu.setQwertyMode(st.qwertyMode);
+ st.menu.startDispatchingItemsChanged();
+ }
+
+ // Set other state
+ st.isPrepared = true;
+ st.isHandled = false;
+ mPreparedPanel = st;
+
+ return true;
+ }
+
+ @Override
+ public void onConfigurationChanged(Configuration newConfig) {
+ // Action bars handle their own menu state
+ if (mDecorContentParent == null) {
+ PanelFeatureState st = getPanelState(FEATURE_OPTIONS_PANEL, false);
+ if ((st != null) && (st.menu != null)) {
+ if (st.isOpen) {
+ // Freeze state
+ final Bundle state = new Bundle();
+ if (st.iconMenuPresenter != null) {
+ st.iconMenuPresenter.saveHierarchyState(state);
+ }
+ if (st.listMenuPresenter != null) {
+ st.listMenuPresenter.saveHierarchyState(state);
+ }
+
+ // Remove the menu views since they need to be recreated
+ // according to the new configuration
+ clearMenuViews(st);
+
+ // Re-open the same menu
+ reopenMenu(false);
+
+ // Restore state
+ if (st.iconMenuPresenter != null) {
+ st.iconMenuPresenter.restoreHierarchyState(state);
+ }
+ if (st.listMenuPresenter != null) {
+ st.listMenuPresenter.restoreHierarchyState(state);
+ }
+
+ } else {
+ // Clear menu views so on next menu opening, it will use
+ // the proper layout
+ clearMenuViews(st);
+ }
+ }
+ }
+ }
+
+ private static void clearMenuViews(PanelFeatureState st) {
+ // This can be called on config changes, so we should make sure
+ // the views will be reconstructed based on the new orientation, etc.
+
+ // Allow the callback to create a new panel view
+ st.createdPanelView = null;
+
+ // Causes the decor view to be recreated
+ st.refreshDecorView = true;
+
+ st.clearMenuPresenters();
+ }
+
+ @Override
+ public final void openPanel(int featureId, KeyEvent event) {
+ if (featureId == FEATURE_OPTIONS_PANEL && mDecorContentParent != null &&
+ mDecorContentParent.canShowOverflowMenu() &&
+ !ViewConfiguration.get(getContext()).hasPermanentMenuKey()) {
+ mDecorContentParent.showOverflowMenu();
+ } else {
+ openPanel(getPanelState(featureId, true), event);
+ }
+ }
+
+ private void openPanel(final PanelFeatureState st, KeyEvent event) {
+ // System.out.println("Open panel: isOpen=" + st.isOpen);
+
+ // Already open, return
+ if (st.isOpen || isDestroyed()) {
+ return;
+ }
+
+ // Don't open an options panel for honeycomb apps on xlarge devices.
+ // (The app should be using an action bar for menu items.)
+ if (st.featureId == FEATURE_OPTIONS_PANEL) {
+ Context context = getContext();
+ Configuration config = context.getResources().getConfiguration();
+ boolean isXLarge = (config.screenLayout & Configuration.SCREENLAYOUT_SIZE_MASK) ==
+ Configuration.SCREENLAYOUT_SIZE_XLARGE;
+ boolean isHoneycombApp = context.getApplicationInfo().targetSdkVersion >=
+ android.os.Build.VERSION_CODES.HONEYCOMB;
+
+ if (isXLarge && isHoneycombApp) {
+ return;
+ }
+ }
+
+ Callback cb = getCallback();
+ if ((cb != null) && (!cb.onMenuOpened(st.featureId, st.menu))) {
+ // Callback doesn't want the menu to open, reset any state
+ closePanel(st, true);
+ return;
+ }
+
+ final WindowManager wm = getWindowManager();
+ if (wm == null) {
+ return;
+ }
+
+ // Prepare panel (should have been done before, but just in case)
+ if (!preparePanel(st, event)) {
+ return;
+ }
+
+ int width = WRAP_CONTENT;
+ if (st.decorView == null || st.refreshDecorView) {
+ if (st.decorView == null) {
+ // Initialize the panel decor, this will populate st.decorView
+ if (!initializePanelDecor(st) || (st.decorView == null))
+ return;
+ } else if (st.refreshDecorView && (st.decorView.getChildCount() > 0)) {
+ // Decor needs refreshing, so remove its views
+ st.decorView.removeAllViews();
+ }
+
+ // This will populate st.shownPanelView
+ if (!initializePanelContent(st) || !st.hasPanelItems()) {
+ return;
+ }
+
+ ViewGroup.LayoutParams lp = st.shownPanelView.getLayoutParams();
+ if (lp == null) {
+ lp = new ViewGroup.LayoutParams(WRAP_CONTENT, WRAP_CONTENT);
+ }
+
+ int backgroundResId;
+ if (lp.width == ViewGroup.LayoutParams.MATCH_PARENT) {
+ // If the contents is fill parent for the width, set the
+ // corresponding background
+ backgroundResId = st.fullBackground;
+ width = MATCH_PARENT;
+ } else {
+ // Otherwise, set the normal panel background
+ backgroundResId = st.background;
+ }
+ st.decorView.setWindowBackground(getContext().getDrawable(
+ backgroundResId));
+
+ ViewParent shownPanelParent = st.shownPanelView.getParent();
+ if (shownPanelParent != null && shownPanelParent instanceof ViewGroup) {
+ ((ViewGroup) shownPanelParent).removeView(st.shownPanelView);
+ }
+ st.decorView.addView(st.shownPanelView, lp);
+
+ /*
+ * Give focus to the view, if it or one of its children does not
+ * already have it.
+ */
+ if (!st.shownPanelView.hasFocus()) {
+ st.shownPanelView.requestFocus();
+ }
+ } else if (!st.isInListMode()) {
+ width = MATCH_PARENT;
+ } else if (st.createdPanelView != null) {
+ // If we already had a panel view, carry width=MATCH_PARENT through
+ // as we did above when it was created.
+ ViewGroup.LayoutParams lp = st.createdPanelView.getLayoutParams();
+ if (lp != null && lp.width == ViewGroup.LayoutParams.MATCH_PARENT) {
+ width = MATCH_PARENT;
+ }
+ }
+
+ st.isHandled = false;
+
+ WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
+ width, WRAP_CONTENT,
+ st.x, st.y, WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG,
+ WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM
+ | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH,
+ st.decorView.mDefaultOpacity);
+
+ if (st.isCompact) {
+ lp.gravity = getOptionsPanelGravity();
+ sRotationWatcher.addWindow(this);
+ } else {
+ lp.gravity = st.gravity;
+ }
+
+ lp.windowAnimations = st.windowAnimations;
+
+ wm.addView(st.decorView, lp);
+ st.isOpen = true;
+ // Log.v(TAG, "Adding main menu to window manager.");
+ }
+
+ @Override
+ public final void closePanel(int featureId) {
+ if (featureId == FEATURE_OPTIONS_PANEL && mDecorContentParent != null &&
+ mDecorContentParent.canShowOverflowMenu() &&
+ !ViewConfiguration.get(getContext()).hasPermanentMenuKey()) {
+ mDecorContentParent.hideOverflowMenu();
+ } else if (featureId == FEATURE_CONTEXT_MENU) {
+ closeContextMenu();
+ } else {
+ closePanel(getPanelState(featureId, true), true);
+ }
+ }
+
+ /**
+ * Closes the given panel.
+ *
+ * @param st The panel to be closed.
+ * @param doCallback Whether to notify the callback that the panel was
+ * closed. If the panel is in the process of re-opening or
+ * opening another panel (e.g., menu opening a sub menu), the
+ * callback should not happen and this variable should be false.
+ * In addition, this method internally will only perform the
+ * callback if the panel is open.
+ */
+ public final void closePanel(PanelFeatureState st, boolean doCallback) {
+ // System.out.println("Close panel: isOpen=" + st.isOpen);
+ if (doCallback && st.featureId == FEATURE_OPTIONS_PANEL &&
+ mDecorContentParent != null && mDecorContentParent.isOverflowMenuShowing()) {
+ checkCloseActionMenu(st.menu);
+ return;
+ }
+
+ final ViewManager wm = getWindowManager();
+ if ((wm != null) && st.isOpen) {
+ if (st.decorView != null) {
+ wm.removeView(st.decorView);
+ // Log.v(TAG, "Removing main menu from window manager.");
+ if (st.isCompact) {
+ sRotationWatcher.removeWindow(this);
+ }
+ }
+
+ if (doCallback) {
+ callOnPanelClosed(st.featureId, st, null);
+ }
+ }
+
+ st.isPrepared = false;
+ st.isHandled = false;
+ st.isOpen = false;
+
+ // This view is no longer shown, so null it out
+ st.shownPanelView = null;
+
+ if (st.isInExpandedMode) {
+ // Next time the menu opens, it should not be in expanded mode, so
+ // force a refresh of the decor
+ st.refreshDecorView = true;
+ st.isInExpandedMode = false;
+ }
+
+ if (mPreparedPanel == st) {
+ mPreparedPanel = null;
+ mPanelChordingKey = 0;
+ }
+ }
+
+ void checkCloseActionMenu(Menu menu) {
+ if (mClosingActionMenu) {
+ return;
+ }
+
+ mClosingActionMenu = true;
+ mDecorContentParent.dismissPopups();
+ Callback cb = getCallback();
+ if (cb != null && !isDestroyed()) {
+ cb.onPanelClosed(FEATURE_ACTION_BAR, menu);
+ }
+ mClosingActionMenu = false;
+ }
+
+ @Override
+ public final void togglePanel(int featureId, KeyEvent event) {
+ PanelFeatureState st = getPanelState(featureId, true);
+ if (st.isOpen) {
+ closePanel(st, true);
+ } else {
+ openPanel(st, event);
+ }
+ }
+
+ @Override
+ public void invalidatePanelMenu(int featureId) {
+ mInvalidatePanelMenuFeatures |= 1 << featureId;
+
+ if (!mInvalidatePanelMenuPosted && mDecor != null) {
+ mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
+ mInvalidatePanelMenuPosted = true;
+ }
+ }
+
+ void doPendingInvalidatePanelMenu() {
+ if (mInvalidatePanelMenuPosted) {
+ mDecor.removeCallbacks(mInvalidatePanelMenuRunnable);
+ mInvalidatePanelMenuRunnable.run();
+ }
+ }
+
+ void doInvalidatePanelMenu(int featureId) {
+ PanelFeatureState st = getPanelState(featureId, false);
+ if (st == null) {
+ return;
+ }
+ Bundle savedActionViewStates = null;
+ if (st.menu != null) {
+ savedActionViewStates = new Bundle();
+ st.menu.saveActionViewStates(savedActionViewStates);
+ if (savedActionViewStates.size() > 0) {
+ st.frozenActionViewState = savedActionViewStates;
+ }
+ // This will be started again when the panel is prepared.
+ st.menu.stopDispatchingItemsChanged();
+ st.menu.clear();
+ }
+ st.refreshMenuContent = true;
+ st.refreshDecorView = true;
+
+ // Prepare the options panel if we have an action bar
+ if ((featureId == FEATURE_ACTION_BAR || featureId == FEATURE_OPTIONS_PANEL)
+ && mDecorContentParent != null) {
+ st = getPanelState(Window.FEATURE_OPTIONS_PANEL, false);
+ if (st != null) {
+ st.isPrepared = false;
+ preparePanel(st, null);
+ }
+ }
+ }
+
+ /**
+ * Called when the panel key is pushed down.
+ * @param featureId The feature ID of the relevant panel (defaults to FEATURE_OPTIONS_PANEL}.
+ * @param event The key event.
+ * @return Whether the key was handled.
+ */
+ public final boolean onKeyDownPanel(int featureId, KeyEvent event) {
+ final int keyCode = event.getKeyCode();
+
+ if (event.getRepeatCount() == 0) {
+ // The panel key was pushed, so set the chording key
+ mPanelChordingKey = keyCode;
+
+ PanelFeatureState st = getPanelState(featureId, false);
+ if (st != null && !st.isOpen) {
+ return preparePanel(st, event);
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Called when the panel key is released.
+ * @param featureId The feature ID of the relevant panel (defaults to FEATURE_OPTIONS_PANEL}.
+ * @param event The key event.
+ */
+ public final void onKeyUpPanel(int featureId, KeyEvent event) {
+ // The panel key was released, so clear the chording key
+ if (mPanelChordingKey != 0) {
+ mPanelChordingKey = 0;
+
+ final PanelFeatureState st = getPanelState(featureId, false);
+
+ if (event.isCanceled() || (mDecor != null && mDecor.mActionMode != null) ||
+ (st == null)) {
+ return;
+ }
+
+ boolean playSoundEffect = false;
+ if (featureId == FEATURE_OPTIONS_PANEL && mDecorContentParent != null &&
+ mDecorContentParent.canShowOverflowMenu() &&
+ !ViewConfiguration.get(getContext()).hasPermanentMenuKey()) {
+ if (!mDecorContentParent.isOverflowMenuShowing()) {
+ if (!isDestroyed() && preparePanel(st, event)) {
+ playSoundEffect = mDecorContentParent.showOverflowMenu();
+ }
+ } else {
+ playSoundEffect = mDecorContentParent.hideOverflowMenu();
+ }
+ } else {
+ if (st.isOpen || st.isHandled) {
+
+ // Play the sound effect if the user closed an open menu (and not if
+ // they just released a menu shortcut)
+ playSoundEffect = st.isOpen;
+
+ // Close menu
+ closePanel(st, true);
+
+ } else if (st.isPrepared) {
+ boolean show = true;
+ if (st.refreshMenuContent) {
+ // Something may have invalidated the menu since we prepared it.
+ // Re-prepare it to refresh.
+ st.isPrepared = false;
+ show = preparePanel(st, event);
+ }
+
+ if (show) {
+ // Write 'menu opened' to event log
+ EventLog.writeEvent(50001, 0);
+
+ // Show menu
+ openPanel(st, event);
+
+ playSoundEffect = true;
+ }
+ }
+ }
+
+ if (playSoundEffect) {
+ AudioManager audioManager = (AudioManager) getContext().getSystemService(
+ Context.AUDIO_SERVICE);
+ if (audioManager != null) {
+ audioManager.playSoundEffect(AudioManager.FX_KEY_CLICK);
+ } else {
+ Log.w(TAG, "Couldn't get audio manager");
+ }
+ }
+ }
+ }
+
+ @Override
+ public final void closeAllPanels() {
+ final ViewManager wm = getWindowManager();
+ if (wm == null) {
+ return;
+ }
+
+ final PanelFeatureState[] panels = mPanels;
+ final int N = panels != null ? panels.length : 0;
+ for (int i = 0; i < N; i++) {
+ final PanelFeatureState panel = panels[i];
+ if (panel != null) {
+ closePanel(panel, true);
+ }
+ }
+
+ closeContextMenu();
+ }
+
+ /**
+ * Closes the context menu. This notifies the menu logic of the close, along
+ * with dismissing it from the UI.
+ */
+ private synchronized void closeContextMenu() {
+ if (mContextMenu != null) {
+ mContextMenu.close();
+ dismissContextMenu();
+ }
+ }
+
+ /**
+ * Dismisses just the context menu UI. To close the context menu, use
+ * {@link #closeContextMenu()}.
+ */
+ private synchronized void dismissContextMenu() {
+ mContextMenu = null;
+
+ if (mContextMenuHelper != null) {
+ mContextMenuHelper.dismiss();
+ mContextMenuHelper = null;
+ }
+ }
+
+ @Override
+ public boolean performPanelShortcut(int featureId, int keyCode, KeyEvent event, int flags) {
+ return performPanelShortcut(getPanelState(featureId, false), keyCode, event, flags);
+ }
+
+ private boolean performPanelShortcut(PanelFeatureState st, int keyCode, KeyEvent event,
+ int flags) {
+ if (event.isSystem() || (st == null)) {
+ return false;
+ }
+
+ boolean handled = false;
+
+ // Only try to perform menu shortcuts if preparePanel returned true (possible false
+ // return value from application not wanting to show the menu).
+ if ((st.isPrepared || preparePanel(st, event)) && st.menu != null) {
+ // The menu is prepared now, perform the shortcut on it
+ handled = st.menu.performShortcut(keyCode, event, flags);
+ }
+
+ if (handled) {
+ // Mark as handled
+ st.isHandled = true;
+
+ // Only close down the menu if we don't have an action bar keeping it open.
+ if ((flags & Menu.FLAG_PERFORM_NO_CLOSE) == 0 && mDecorContentParent == null) {
+ closePanel(st, true);
+ }
+ }
+
+ return handled;
+ }
+
+ @Override
+ public boolean performPanelIdentifierAction(int featureId, int id, int flags) {
+
+ PanelFeatureState st = getPanelState(featureId, true);
+ if (!preparePanel(st, new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_MENU))) {
+ return false;
+ }
+ if (st.menu == null) {
+ return false;
+ }
+
+ boolean res = st.menu.performIdentifierAction(id, flags);
+
+ // Only close down the menu if we don't have an action bar keeping it open.
+ if (mDecorContentParent == null) {
+ closePanel(st, true);
+ }
+
+ return res;
+ }
+
+ public PanelFeatureState findMenuPanel(Menu menu) {
+ final PanelFeatureState[] panels = mPanels;
+ final int N = panels != null ? panels.length : 0;
+ for (int i = 0; i < N; i++) {
+ final PanelFeatureState panel = panels[i];
+ if (panel != null && panel.menu == menu) {
+ return panel;
+ }
+ }
+ return null;
+ }
+
+ public boolean onMenuItemSelected(MenuBuilder menu, MenuItem item) {
+ final Callback cb = getCallback();
+ if (cb != null && !isDestroyed()) {
+ final PanelFeatureState panel = findMenuPanel(menu.getRootMenu());
+ if (panel != null) {
+ return cb.onMenuItemSelected(panel.featureId, item);
+ }
+ }
+ return false;
+ }
+
+ public void onMenuModeChange(MenuBuilder menu) {
+ reopenMenu(true);
+ }
+
+ private void reopenMenu(boolean toggleMenuMode) {
+ if (mDecorContentParent != null && mDecorContentParent.canShowOverflowMenu() &&
+ (!ViewConfiguration.get(getContext()).hasPermanentMenuKey() ||
+ mDecorContentParent.isOverflowMenuShowPending())) {
+ final Callback cb = getCallback();
+ if (!mDecorContentParent.isOverflowMenuShowing() || !toggleMenuMode) {
+ if (cb != null && !isDestroyed()) {
+ // If we have a menu invalidation pending, do it now.
+ if (mInvalidatePanelMenuPosted &&
+ (mInvalidatePanelMenuFeatures & (1 << FEATURE_OPTIONS_PANEL)) != 0) {
+ mDecor.removeCallbacks(mInvalidatePanelMenuRunnable);
+ mInvalidatePanelMenuRunnable.run();
+ }
+
+ final PanelFeatureState st = getPanelState(FEATURE_OPTIONS_PANEL, false);
+
+ // If we don't have a menu or we're waiting for a full content refresh,
+ // forget it. This is a lingering event that no longer matters.
+ if (st != null && st.menu != null && !st.refreshMenuContent &&
+ cb.onPreparePanel(FEATURE_OPTIONS_PANEL, st.createdPanelView, st.menu)) {
+ cb.onMenuOpened(FEATURE_ACTION_BAR, st.menu);
+ mDecorContentParent.showOverflowMenu();
+ }
+ }
+ } else {
+ mDecorContentParent.hideOverflowMenu();
+ final PanelFeatureState st = getPanelState(FEATURE_OPTIONS_PANEL, false);
+ if (st != null && cb != null && !isDestroyed()) {
+ cb.onPanelClosed(FEATURE_ACTION_BAR, st.menu);
+ }
+ }
+ return;
+ }
+
+ PanelFeatureState st = getPanelState(FEATURE_OPTIONS_PANEL, false);
+
+ if (st == null) {
+ return;
+ }
+
+ // Save the future expanded mode state since closePanel will reset it
+ boolean newExpandedMode = toggleMenuMode ? !st.isInExpandedMode : st.isInExpandedMode;
+
+ st.refreshDecorView = true;
+ closePanel(st, false);
+
+ // Set the expanded mode state
+ st.isInExpandedMode = newExpandedMode;
+
+ openPanel(st, null);
+ }
+
+ /**
+ * Initializes the menu associated with the given panel feature state. You
+ * must at the very least set PanelFeatureState.menu to the Menu to be
+ * associated with the given panel state. The default implementation creates
+ * a new menu for the panel state.
+ *
+ * @param st The panel whose menu is being initialized.
+ * @return Whether the initialization was successful.
+ */
+ protected boolean initializePanelMenu(final PanelFeatureState st) {
+ Context context = getContext();
+
+ // If we have an action bar, initialize the menu with the right theme.
+ if ((st.featureId == FEATURE_OPTIONS_PANEL || st.featureId == FEATURE_ACTION_BAR) &&
+ mDecorContentParent != null) {
+ final TypedValue outValue = new TypedValue();
+ final Theme baseTheme = context.getTheme();
+ baseTheme.resolveAttribute(R.attr.actionBarTheme, outValue, true);
+
+ Theme widgetTheme = null;
+ if (outValue.resourceId != 0) {
+ widgetTheme = context.getResources().newTheme();
+ widgetTheme.setTo(baseTheme);
+ widgetTheme.applyStyle(outValue.resourceId, true);
+ widgetTheme.resolveAttribute(
+ R.attr.actionBarWidgetTheme, outValue, true);
+ } else {
+ baseTheme.resolveAttribute(
+ R.attr.actionBarWidgetTheme, outValue, true);
+ }
+
+ if (outValue.resourceId != 0) {
+ if (widgetTheme == null) {
+ widgetTheme = context.getResources().newTheme();
+ widgetTheme.setTo(baseTheme);
+ }
+ widgetTheme.applyStyle(outValue.resourceId, true);
+ }
+
+ if (widgetTheme != null) {
+ context = new ContextThemeWrapper(context, 0);
+ context.getTheme().setTo(widgetTheme);
+ }
+ }
+
+ final MenuBuilder menu = new MenuBuilder(context);
+ menu.setCallback(this);
+ st.setMenu(menu);
+
+ return true;
+ }
+
+ /**
+ * Perform initial setup of a panel. This should at the very least set the
+ * style information in the PanelFeatureState and must set
+ * PanelFeatureState.decor to the panel's window decor view.
+ *
+ * @param st The panel being initialized.
+ */
+ protected boolean initializePanelDecor(PanelFeatureState st) {
+ st.decorView = new DecorView(getContext(), st.featureId);
+ st.gravity = Gravity.CENTER | Gravity.BOTTOM;
+ st.setStyle(getContext());
+ TypedArray a = getContext().obtainStyledAttributes(null,
+ R.styleable.Window, 0, st.listPresenterTheme);
+ final float elevation = a.getDimension(R.styleable.Window_windowElevation, 0);
+ if (elevation != 0) {
+ st.decorView.setElevation(elevation);
+ }
+ a.recycle();
+
+ return true;
+ }
+
+ /**
+ * Determine the gravity value for the options panel. This can
+ * differ in compact mode.
+ *
+ * @return gravity value to use for the panel window
+ */
+ private int getOptionsPanelGravity() {
+ try {
+ return WindowManagerHolder.sWindowManager.getPreferredOptionsPanelGravity();
+ } catch (RemoteException ex) {
+ Log.e(TAG, "Couldn't getOptionsPanelGravity; using default", ex);
+ return Gravity.CENTER | Gravity.BOTTOM;
+ }
+ }
+
+ void onOptionsPanelRotationChanged() {
+ final PanelFeatureState st = getPanelState(FEATURE_OPTIONS_PANEL, false);
+ if (st == null) return;
+
+ final WindowManager.LayoutParams lp = st.decorView != null ?
+ (WindowManager.LayoutParams) st.decorView.getLayoutParams() : null;
+ if (lp != null) {
+ lp.gravity = getOptionsPanelGravity();
+ final ViewManager wm = getWindowManager();
+ if (wm != null) {
+ wm.updateViewLayout(st.decorView, lp);
+ }
+ }
+ }
+
+ /**
+ * Initializes the panel associated with the panel feature state. You must
+ * at the very least set PanelFeatureState.panel to the View implementing
+ * its contents. The default implementation gets the panel from the menu.
+ *
+ * @param st The panel state being initialized.
+ * @return Whether the initialization was successful.
+ */
+ protected boolean initializePanelContent(PanelFeatureState st) {
+ if (st.createdPanelView != null) {
+ st.shownPanelView = st.createdPanelView;
+ return true;
+ }
+
+ if (st.menu == null) {
+ return false;
+ }
+
+ if (mPanelMenuPresenterCallback == null) {
+ mPanelMenuPresenterCallback = new PanelMenuPresenterCallback();
+ }
+
+ MenuView menuView = st.isInListMode()
+ ? st.getListMenuView(getContext(), mPanelMenuPresenterCallback)
+ : st.getIconMenuView(getContext(), mPanelMenuPresenterCallback);
+
+ st.shownPanelView = (View) menuView;
+
+ if (st.shownPanelView != null) {
+ // Use the menu View's default animations if it has any
+ final int defaultAnimations = menuView.getWindowAnimations();
+ if (defaultAnimations != 0) {
+ st.windowAnimations = defaultAnimations;
+ }
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public boolean performContextMenuIdentifierAction(int id, int flags) {
+ return (mContextMenu != null) ? mContextMenu.performIdentifierAction(id, flags) : false;
+ }
+
+ @Override
+ public final void setElevation(float elevation) {
+ mElevation = elevation;
+ if (mDecor != null) {
+ mDecor.setElevation(elevation);
+ }
+ dispatchWindowAttributesChanged(getAttributes());
+ }
+
+ @Override
+ public final void setClipToOutline(boolean clipToOutline) {
+ mClipToOutline = clipToOutline;
+ if (mDecor != null) {
+ mDecor.setClipToOutline(clipToOutline);
+ }
+ }
+
+ @Override
+ public final void setBackgroundDrawable(Drawable drawable) {
+ if (drawable != mBackgroundDrawable || mBackgroundResource != 0) {
+ mBackgroundResource = 0;
+ mBackgroundDrawable = drawable;
+ if (mDecor != null) {
+ mDecor.setWindowBackground(drawable);
+ }
+ if (mBackgroundFallbackResource != 0) {
+ mDecor.setBackgroundFallback(drawable != null ? 0 : mBackgroundFallbackResource);
+ }
+ }
+ }
+
+ @Override
+ public final void setFeatureDrawableResource(int featureId, int resId) {
+ if (resId != 0) {
+ DrawableFeatureState st = getDrawableState(featureId, true);
+ if (st.resid != resId) {
+ st.resid = resId;
+ st.uri = null;
+ st.local = getContext().getDrawable(resId);
+ updateDrawable(featureId, st, false);
+ }
+ } else {
+ setFeatureDrawable(featureId, null);
+ }
+ }
+
+ @Override
+ public final void setFeatureDrawableUri(int featureId, Uri uri) {
+ if (uri != null) {
+ DrawableFeatureState st = getDrawableState(featureId, true);
+ if (st.uri == null || !st.uri.equals(uri)) {
+ st.resid = 0;
+ st.uri = uri;
+ st.local = loadImageURI(uri);
+ updateDrawable(featureId, st, false);
+ }
+ } else {
+ setFeatureDrawable(featureId, null);
+ }
+ }
+
+ @Override
+ public final void setFeatureDrawable(int featureId, Drawable drawable) {
+ DrawableFeatureState st = getDrawableState(featureId, true);
+ st.resid = 0;
+ st.uri = null;
+ if (st.local != drawable) {
+ st.local = drawable;
+ updateDrawable(featureId, st, false);
+ }
+ }
+
+ @Override
+ public void setFeatureDrawableAlpha(int featureId, int alpha) {
+ DrawableFeatureState st = getDrawableState(featureId, true);
+ if (st.alpha != alpha) {
+ st.alpha = alpha;
+ updateDrawable(featureId, st, false);
+ }
+ }
+
+ protected final void setFeatureDefaultDrawable(int featureId, Drawable drawable) {
+ DrawableFeatureState st = getDrawableState(featureId, true);
+ if (st.def != drawable) {
+ st.def = drawable;
+ updateDrawable(featureId, st, false);
+ }
+ }
+
+ @Override
+ public final void setFeatureInt(int featureId, int value) {
+ // XXX Should do more management (as with drawable features) to
+ // deal with interactions between multiple window policies.
+ updateInt(featureId, value, false);
+ }
+
+ /**
+ * Update the state of a drawable feature. This should be called, for every
+ * drawable feature supported, as part of onActive(), to make sure that the
+ * contents of a containing window is properly updated.
+ *
+ * @see #onActive
+ * @param featureId The desired drawable feature to change.
+ * @param fromActive Always true when called from onActive().
+ */
+ protected final void updateDrawable(int featureId, boolean fromActive) {
+ final DrawableFeatureState st = getDrawableState(featureId, false);
+ if (st != null) {
+ updateDrawable(featureId, st, fromActive);
+ }
+ }
+
+ /**
+ * Called when a Drawable feature changes, for the window to update its
+ * graphics.
+ *
+ * @param featureId The feature being changed.
+ * @param drawable The new Drawable to show, or null if none.
+ * @param alpha The new alpha blending of the Drawable.
+ */
+ protected void onDrawableChanged(int featureId, Drawable drawable, int alpha) {
+ ImageView view;
+ if (featureId == FEATURE_LEFT_ICON) {
+ view = getLeftIconView();
+ } else if (featureId == FEATURE_RIGHT_ICON) {
+ view = getRightIconView();
+ } else {
+ return;
+ }
+
+ if (drawable != null) {
+ drawable.setAlpha(alpha);
+ view.setImageDrawable(drawable);
+ view.setVisibility(View.VISIBLE);
+ } else {
+ view.setVisibility(View.GONE);
+ }
+ }
+
+ /**
+ * Called when an int feature changes, for the window to update its
+ * graphics.
+ *
+ * @param featureId The feature being changed.
+ * @param value The new integer value.
+ */
+ protected void onIntChanged(int featureId, int value) {
+ if (featureId == FEATURE_PROGRESS || featureId == FEATURE_INDETERMINATE_PROGRESS) {
+ updateProgressBars(value);
+ } else if (featureId == FEATURE_CUSTOM_TITLE) {
+ FrameLayout titleContainer = (FrameLayout) findViewById(R.id.title_container);
+ if (titleContainer != null) {
+ mLayoutInflater.inflate(value, titleContainer);
+ }
+ }
+ }
+
+ /**
+ * Updates the progress bars that are shown in the title bar.
+ *
+ * @param value Can be one of {@link Window#PROGRESS_VISIBILITY_ON},
+ * {@link Window#PROGRESS_VISIBILITY_OFF},
+ * {@link Window#PROGRESS_INDETERMINATE_ON},
+ * {@link Window#PROGRESS_INDETERMINATE_OFF}, or a value
+ * starting at {@link Window#PROGRESS_START} through
+ * {@link Window#PROGRESS_END} for setting the default
+ * progress (if {@link Window#PROGRESS_END} is given,
+ * the progress bar widgets in the title will be hidden after an
+ * animation), a value between
+ * {@link Window#PROGRESS_SECONDARY_START} -
+ * {@link Window#PROGRESS_SECONDARY_END} for the
+ * secondary progress (if
+ * {@link Window#PROGRESS_SECONDARY_END} is given, the
+ * progress bar widgets will still be shown with the secondary
+ * progress bar will be completely filled in.)
+ */
+ private void updateProgressBars(int value) {
+ ProgressBar circularProgressBar = getCircularProgressBar(true);
+ ProgressBar horizontalProgressBar = getHorizontalProgressBar(true);
+
+ final int features = getLocalFeatures();
+ if (value == PROGRESS_VISIBILITY_ON) {
+ if ((features & (1 << FEATURE_PROGRESS)) != 0) {
+ if (horizontalProgressBar != null) {
+ int level = horizontalProgressBar.getProgress();
+ int visibility = (horizontalProgressBar.isIndeterminate() || level < 10000) ?
+ View.VISIBLE : View.INVISIBLE;
+ horizontalProgressBar.setVisibility(visibility);
+ } else {
+ Log.e(TAG, "Horizontal progress bar not located in current window decor");
+ }
+ }
+ if ((features & (1 << FEATURE_INDETERMINATE_PROGRESS)) != 0) {
+ if (circularProgressBar != null) {
+ circularProgressBar.setVisibility(View.VISIBLE);
+ } else {
+ Log.e(TAG, "Circular progress bar not located in current window decor");
+ }
+ }
+ } else if (value == PROGRESS_VISIBILITY_OFF) {
+ if ((features & (1 << FEATURE_PROGRESS)) != 0) {
+ if (horizontalProgressBar != null) {
+ horizontalProgressBar.setVisibility(View.GONE);
+ } else {
+ Log.e(TAG, "Horizontal progress bar not located in current window decor");
+ }
+ }
+ if ((features & (1 << FEATURE_INDETERMINATE_PROGRESS)) != 0) {
+ if (circularProgressBar != null) {
+ circularProgressBar.setVisibility(View.GONE);
+ } else {
+ Log.e(TAG, "Circular progress bar not located in current window decor");
+ }
+ }
+ } else if (value == PROGRESS_INDETERMINATE_ON) {
+ if (horizontalProgressBar != null) {
+ horizontalProgressBar.setIndeterminate(true);
+ } else {
+ Log.e(TAG, "Horizontal progress bar not located in current window decor");
+ }
+ } else if (value == PROGRESS_INDETERMINATE_OFF) {
+ if (horizontalProgressBar != null) {
+ horizontalProgressBar.setIndeterminate(false);
+ } else {
+ Log.e(TAG, "Horizontal progress bar not located in current window decor");
+ }
+ } else if (PROGRESS_START <= value && value <= PROGRESS_END) {
+ // We want to set the progress value before testing for visibility
+ // so that when the progress bar becomes visible again, it has the
+ // correct level.
+ if (horizontalProgressBar != null) {
+ horizontalProgressBar.setProgress(value - PROGRESS_START);
+ } else {
+ Log.e(TAG, "Horizontal progress bar not located in current window decor");
+ }
+
+ if (value < PROGRESS_END) {
+ showProgressBars(horizontalProgressBar, circularProgressBar);
+ } else {
+ hideProgressBars(horizontalProgressBar, circularProgressBar);
+ }
+ } else if (PROGRESS_SECONDARY_START <= value && value <= PROGRESS_SECONDARY_END) {
+ if (horizontalProgressBar != null) {
+ horizontalProgressBar.setSecondaryProgress(value - PROGRESS_SECONDARY_START);
+ } else {
+ Log.e(TAG, "Horizontal progress bar not located in current window decor");
+ }
+
+ showProgressBars(horizontalProgressBar, circularProgressBar);
+ }
+
+ }
+
+ private void showProgressBars(ProgressBar horizontalProgressBar, ProgressBar spinnyProgressBar) {
+ final int features = getLocalFeatures();
+ if ((features & (1 << FEATURE_INDETERMINATE_PROGRESS)) != 0 &&
+ spinnyProgressBar != null && spinnyProgressBar.getVisibility() == View.INVISIBLE) {
+ spinnyProgressBar.setVisibility(View.VISIBLE);
+ }
+ // Only show the progress bars if the primary progress is not complete
+ if ((features & (1 << FEATURE_PROGRESS)) != 0 && horizontalProgressBar != null &&
+ horizontalProgressBar.getProgress() < 10000) {
+ horizontalProgressBar.setVisibility(View.VISIBLE);
+ }
+ }
+
+ private void hideProgressBars(ProgressBar horizontalProgressBar, ProgressBar spinnyProgressBar) {
+ final int features = getLocalFeatures();
+ Animation anim = AnimationUtils.loadAnimation(getContext(), R.anim.fade_out);
+ anim.setDuration(1000);
+ if ((features & (1 << FEATURE_INDETERMINATE_PROGRESS)) != 0 &&
+ spinnyProgressBar != null &&
+ spinnyProgressBar.getVisibility() == View.VISIBLE) {
+ spinnyProgressBar.startAnimation(anim);
+ spinnyProgressBar.setVisibility(View.INVISIBLE);
+ }
+ if ((features & (1 << FEATURE_PROGRESS)) != 0 && horizontalProgressBar != null &&
+ horizontalProgressBar.getVisibility() == View.VISIBLE) {
+ horizontalProgressBar.startAnimation(anim);
+ horizontalProgressBar.setVisibility(View.INVISIBLE);
+ }
+ }
+
+ @Override
+ public void setIcon(int resId) {
+ mIconRes = resId;
+ mResourcesSetFlags |= FLAG_RESOURCE_SET_ICON;
+ mResourcesSetFlags &= ~FLAG_RESOURCE_SET_ICON_FALLBACK;
+ if (mDecorContentParent != null) {
+ mDecorContentParent.setIcon(resId);
+ }
+ }
+
+ @Override
+ public void setDefaultIcon(int resId) {
+ if ((mResourcesSetFlags & FLAG_RESOURCE_SET_ICON) != 0) {
+ return;
+ }
+ mIconRes = resId;
+ if (mDecorContentParent != null && (!mDecorContentParent.hasIcon() ||
+ (mResourcesSetFlags & FLAG_RESOURCE_SET_ICON_FALLBACK) != 0)) {
+ if (resId != 0) {
+ mDecorContentParent.setIcon(resId);
+ mResourcesSetFlags &= ~FLAG_RESOURCE_SET_ICON_FALLBACK;
+ } else {
+ mDecorContentParent.setIcon(
+ getContext().getPackageManager().getDefaultActivityIcon());
+ mResourcesSetFlags |= FLAG_RESOURCE_SET_ICON_FALLBACK;
+ }
+ }
+ }
+
+ @Override
+ public void setLogo(int resId) {
+ mLogoRes = resId;
+ mResourcesSetFlags |= FLAG_RESOURCE_SET_LOGO;
+ if (mDecorContentParent != null) {
+ mDecorContentParent.setLogo(resId);
+ }
+ }
+
+ @Override
+ public void setDefaultLogo(int resId) {
+ if ((mResourcesSetFlags & FLAG_RESOURCE_SET_LOGO) != 0) {
+ return;
+ }
+ mLogoRes = resId;
+ if (mDecorContentParent != null && !mDecorContentParent.hasLogo()) {
+ mDecorContentParent.setLogo(resId);
+ }
+ }
+
+ @Override
+ public void setLocalFocus(boolean hasFocus, boolean inTouchMode) {
+ getViewRootImpl().windowFocusChanged(hasFocus, inTouchMode);
+
+ }
+
+ @Override
+ public void injectInputEvent(InputEvent event) {
+ getViewRootImpl().dispatchInputEvent(event);
+ }
+
+ private ViewRootImpl getViewRootImpl() {
+ if (mDecor != null) {
+ ViewRootImpl viewRootImpl = mDecor.getViewRootImpl();
+ if (viewRootImpl != null) {
+ return viewRootImpl;
+ }
+ }
+ throw new IllegalStateException("view not added");
+ }
+
+ /**
+ * Request that key events come to this activity. Use this if your activity
+ * has no views with focus, but the activity still wants a chance to process
+ * key events.
+ */
+ @Override
+ public void takeKeyEvents(boolean get) {
+ mDecor.setFocusable(get);
+ }
+
+ @Override
+ public boolean superDispatchKeyEvent(KeyEvent event) {
+ return mDecor.superDispatchKeyEvent(event);
+ }
+
+ @Override
+ public boolean superDispatchKeyShortcutEvent(KeyEvent event) {
+ return mDecor.superDispatchKeyShortcutEvent(event);
+ }
+
+ @Override
+ public boolean superDispatchTouchEvent(MotionEvent event) {
+ return mDecor.superDispatchTouchEvent(event);
+ }
+
+ @Override
+ public boolean superDispatchTrackballEvent(MotionEvent event) {
+ return mDecor.superDispatchTrackballEvent(event);
+ }
+
+ @Override
+ public boolean superDispatchGenericMotionEvent(MotionEvent event) {
+ return mDecor.superDispatchGenericMotionEvent(event);
+ }
+
+ /**
+ * A key was pressed down and not handled by anything else in the window.
+ *
+ * @see #onKeyUp
+ * @see android.view.KeyEvent
+ */
+ protected boolean onKeyDown(int featureId, int keyCode, KeyEvent event) {
+ /* ****************************************************************************
+ * HOW TO DECIDE WHERE YOUR KEY HANDLING GOES.
+ *
+ * If your key handling must happen before the app gets a crack at the event,
+ * it goes in PhoneWindowManager.
+ *
+ * If your key handling should happen in all windows, and does not depend on
+ * the state of the current application, other than that the current
+ * application can override the behavior by handling the event itself, it
+ * should go in PhoneFallbackEventHandler.
+ *
+ * Only if your handling depends on the window, and the fact that it has
+ * a DecorView, should it go here.
+ * ****************************************************************************/
+
+ final KeyEvent.DispatcherState dispatcher =
+ mDecor != null ? mDecor.getKeyDispatcherState() : null;
+ //Log.i(TAG, "Key down: repeat=" + event.getRepeatCount()
+ // + " flags=0x" + Integer.toHexString(event.getFlags()));
+
+ switch (keyCode) {
+ case KeyEvent.KEYCODE_VOLUME_UP:
+ case KeyEvent.KEYCODE_VOLUME_DOWN:
+ case KeyEvent.KEYCODE_VOLUME_MUTE: {
+ int direction = 0;
+ switch (keyCode) {
+ case KeyEvent.KEYCODE_VOLUME_UP:
+ direction = AudioManager.ADJUST_RAISE;
+ break;
+ case KeyEvent.KEYCODE_VOLUME_DOWN:
+ direction = AudioManager.ADJUST_LOWER;
+ break;
+ case KeyEvent.KEYCODE_VOLUME_MUTE:
+ direction = AudioManager.ADJUST_TOGGLE_MUTE;
+ break;
+ }
+ // If we have a session send it the volume command, otherwise
+ // use the suggested stream.
+ if (mMediaController != null) {
+ mMediaController.adjustVolume(direction, AudioManager.FLAG_SHOW_UI);
+ } else {
+ MediaSessionLegacyHelper.getHelper(getContext()).sendAdjustVolumeBy(
+ mVolumeControlStreamType, direction,
+ AudioManager.FLAG_SHOW_UI | AudioManager.FLAG_VIBRATE);
+ }
+ return true;
+ }
+ // These are all the recognized media key codes in
+ // KeyEvent.isMediaKey()
+ case KeyEvent.KEYCODE_MEDIA_PLAY:
+ case KeyEvent.KEYCODE_MEDIA_PAUSE:
+ case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE:
+ case KeyEvent.KEYCODE_MUTE:
+ case KeyEvent.KEYCODE_HEADSETHOOK:
+ case KeyEvent.KEYCODE_MEDIA_STOP:
+ case KeyEvent.KEYCODE_MEDIA_NEXT:
+ case KeyEvent.KEYCODE_MEDIA_PREVIOUS:
+ case KeyEvent.KEYCODE_MEDIA_REWIND:
+ case KeyEvent.KEYCODE_MEDIA_RECORD:
+ case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD: {
+ if (mMediaController != null) {
+ if (mMediaController.dispatchMediaButtonEvent(event)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ case KeyEvent.KEYCODE_MENU: {
+ onKeyDownPanel((featureId < 0) ? FEATURE_OPTIONS_PANEL : featureId, event);
+ return true;
+ }
+
+ case KeyEvent.KEYCODE_BACK: {
+ if (event.getRepeatCount() > 0) break;
+ if (featureId < 0) break;
+ // Currently don't do anything with long press.
+ if (dispatcher != null) {
+ dispatcher.startTracking(event, this);
+ }
+ return true;
+ }
+
+ }
+
+ return false;
+ }
+
+ private KeyguardManager getKeyguardManager() {
+ if (mKeyguardManager == null) {
+ mKeyguardManager = (KeyguardManager) getContext().getSystemService(
+ Context.KEYGUARD_SERVICE);
+ }
+ return mKeyguardManager;
+ }
+
+ AudioManager getAudioManager() {
+ if (mAudioManager == null) {
+ mAudioManager = (AudioManager)getContext().getSystemService(Context.AUDIO_SERVICE);
+ }
+ return mAudioManager;
+ }
+
+ /**
+ * A key was released and not handled by anything else in the window.
+ *
+ * @see #onKeyDown
+ * @see android.view.KeyEvent
+ */
+ protected boolean onKeyUp(int featureId, int keyCode, KeyEvent event) {
+ final KeyEvent.DispatcherState dispatcher =
+ mDecor != null ? mDecor.getKeyDispatcherState() : null;
+ if (dispatcher != null) {
+ dispatcher.handleUpEvent(event);
+ }
+ //Log.i(TAG, "Key up: repeat=" + event.getRepeatCount()
+ // + " flags=0x" + Integer.toHexString(event.getFlags()));
+
+ switch (keyCode) {
+ case KeyEvent.KEYCODE_VOLUME_UP:
+ case KeyEvent.KEYCODE_VOLUME_DOWN: {
+ // If we have a session send it the volume command, otherwise
+ // use the suggested stream.
+ if (mMediaController != null) {
+ mMediaController.adjustVolume(0, AudioManager.FLAG_PLAY_SOUND
+ | AudioManager.FLAG_VIBRATE);
+ } else {
+ MediaSessionLegacyHelper.getHelper(getContext()).sendAdjustVolumeBy(
+ mVolumeControlStreamType, 0,
+ AudioManager.FLAG_PLAY_SOUND | AudioManager.FLAG_VIBRATE);
+ }
+ return true;
+ }
+ case KeyEvent.KEYCODE_VOLUME_MUTE: {
+ // Similar code is in PhoneFallbackEventHandler in case the window
+ // doesn't have one of these. In this case, we execute it here and
+ // eat the event instead, because we have mVolumeControlStreamType
+ // and they don't.
+ getAudioManager().handleKeyUp(event, mVolumeControlStreamType);
+ return true;
+ }
+ // These are all the recognized media key codes in
+ // KeyEvent.isMediaKey()
+ case KeyEvent.KEYCODE_MEDIA_PLAY:
+ case KeyEvent.KEYCODE_MEDIA_PAUSE:
+ case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE:
+ case KeyEvent.KEYCODE_MUTE:
+ case KeyEvent.KEYCODE_HEADSETHOOK:
+ case KeyEvent.KEYCODE_MEDIA_STOP:
+ case KeyEvent.KEYCODE_MEDIA_NEXT:
+ case KeyEvent.KEYCODE_MEDIA_PREVIOUS:
+ case KeyEvent.KEYCODE_MEDIA_REWIND:
+ case KeyEvent.KEYCODE_MEDIA_RECORD:
+ case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD: {
+ if (mMediaController != null) {
+ if (mMediaController.dispatchMediaButtonEvent(event)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ case KeyEvent.KEYCODE_MENU: {
+ onKeyUpPanel(featureId < 0 ? FEATURE_OPTIONS_PANEL : featureId,
+ event);
+ return true;
+ }
+
+ case KeyEvent.KEYCODE_BACK: {
+ if (featureId < 0) break;
+ if (event.isTracking() && !event.isCanceled()) {
+ if (featureId == FEATURE_OPTIONS_PANEL) {
+ PanelFeatureState st = getPanelState(featureId, false);
+ if (st != null && st.isInExpandedMode) {
+ // If the user is in an expanded menu and hits back, it
+ // should go back to the icon menu
+ reopenMenu(true);
+ return true;
+ }
+ }
+ closePanel(featureId);
+ return true;
+ }
+ break;
+ }
+
+ case KeyEvent.KEYCODE_SEARCH: {
+ /*
+ * Do this in onKeyUp since the Search key is also used for
+ * chording quick launch shortcuts.
+ */
+ if (getKeyguardManager().inKeyguardRestrictedInputMode()) {
+ break;
+ }
+ if (event.isTracking() && !event.isCanceled()) {
+ launchDefaultSearch();
+ }
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ @Override
+ protected void onActive() {
+ }
+
+ @Override
+ public final View getDecorView() {
+ if (mDecor == null) {
+ installDecor();
+ }
+ return mDecor;
+ }
+
+ @Override
+ public final View peekDecorView() {
+ return mDecor;
+ }
+
+ static private final String FOCUSED_ID_TAG = "android:focusedViewId";
+ static private final String VIEWS_TAG = "android:views";
+ static private final String PANELS_TAG = "android:Panels";
+ static private final String ACTION_BAR_TAG = "android:ActionBar";
+
+ /** {@inheritDoc} */
+ @Override
+ public Bundle saveHierarchyState() {
+ Bundle outState = new Bundle();
+ if (mContentParent == null) {
+ return outState;
+ }
+
+ SparseArray<Parcelable> states = new SparseArray<Parcelable>();
+ mContentParent.saveHierarchyState(states);
+ outState.putSparseParcelableArray(VIEWS_TAG, states);
+
+ // save the focused view id
+ View focusedView = mContentParent.findFocus();
+ if (focusedView != null) {
+ if (focusedView.getId() != View.NO_ID) {
+ outState.putInt(FOCUSED_ID_TAG, focusedView.getId());
+ } else {
+ if (false) {
+ Log.d(TAG, "couldn't save which view has focus because the focused view "
+ + focusedView + " has no id.");
+ }
+ }
+ }
+
+ // save the panels
+ SparseArray<Parcelable> panelStates = new SparseArray<Parcelable>();
+ savePanelState(panelStates);
+ if (panelStates.size() > 0) {
+ outState.putSparseParcelableArray(PANELS_TAG, panelStates);
+ }
+
+ if (mDecorContentParent != null) {
+ SparseArray<Parcelable> actionBarStates = new SparseArray<Parcelable>();
+ mDecorContentParent.saveToolbarHierarchyState(actionBarStates);
+ outState.putSparseParcelableArray(ACTION_BAR_TAG, actionBarStates);
+ }
+
+ return outState;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void restoreHierarchyState(Bundle savedInstanceState) {
+ if (mContentParent == null) {
+ return;
+ }
+
+ SparseArray<Parcelable> savedStates
+ = savedInstanceState.getSparseParcelableArray(VIEWS_TAG);
+ if (savedStates != null) {
+ mContentParent.restoreHierarchyState(savedStates);
+ }
+
+ // restore the focused view
+ int focusedViewId = savedInstanceState.getInt(FOCUSED_ID_TAG, View.NO_ID);
+ if (focusedViewId != View.NO_ID) {
+ View needsFocus = mContentParent.findViewById(focusedViewId);
+ if (needsFocus != null) {
+ needsFocus.requestFocus();
+ } else {
+ Log.w(TAG,
+ "Previously focused view reported id " + focusedViewId
+ + " during save, but can't be found during restore.");
+ }
+ }
+
+ // restore the panels
+ SparseArray<Parcelable> panelStates = savedInstanceState.getSparseParcelableArray(PANELS_TAG);
+ if (panelStates != null) {
+ restorePanelState(panelStates);
+ }
+
+ if (mDecorContentParent != null) {
+ SparseArray<Parcelable> actionBarStates =
+ savedInstanceState.getSparseParcelableArray(ACTION_BAR_TAG);
+ if (actionBarStates != null) {
+ doPendingInvalidatePanelMenu();
+ mDecorContentParent.restoreToolbarHierarchyState(actionBarStates);
+ } else {
+ Log.w(TAG, "Missing saved instance states for action bar views! " +
+ "State will not be restored.");
+ }
+ }
+ }
+
+ /**
+ * Invoked when the panels should freeze their state.
+ *
+ * @param icicles Save state into this. This is usually indexed by the
+ * featureId. This will be given to {@link #restorePanelState} in the
+ * future.
+ */
+ private void savePanelState(SparseArray<Parcelable> icicles) {
+ PanelFeatureState[] panels = mPanels;
+ if (panels == null) {
+ return;
+ }
+
+ for (int curFeatureId = panels.length - 1; curFeatureId >= 0; curFeatureId--) {
+ if (panels[curFeatureId] != null) {
+ icicles.put(curFeatureId, panels[curFeatureId].onSaveInstanceState());
+ }
+ }
+ }
+
+ /**
+ * Invoked when the panels should thaw their state from a previously frozen state.
+ *
+ * @param icicles The state saved by {@link #savePanelState} that needs to be thawed.
+ */
+ private void restorePanelState(SparseArray<Parcelable> icicles) {
+ PanelFeatureState st;
+ int curFeatureId;
+ for (int i = icicles.size() - 1; i >= 0; i--) {
+ curFeatureId = icicles.keyAt(i);
+ st = getPanelState(curFeatureId, false /* required */);
+ if (st == null) {
+ // The panel must not have been required, and is currently not around, skip it
+ continue;
+ }
+
+ st.onRestoreInstanceState(icicles.get(curFeatureId));
+ invalidatePanelMenu(curFeatureId);
+ }
+
+ /*
+ * Implementation note: call openPanelsAfterRestore later to actually open the
+ * restored panels.
+ */
+ }
+
+ /**
+ * Opens the panels that have had their state restored. This should be
+ * called sometime after {@link #restorePanelState} when it is safe to add
+ * to the window manager.
+ */
+ private void openPanelsAfterRestore() {
+ PanelFeatureState[] panels = mPanels;
+
+ if (panels == null) {
+ return;
+ }
+
+ PanelFeatureState st;
+ for (int i = panels.length - 1; i >= 0; i--) {
+ st = panels[i];
+ // We restore the panel if it was last open; we skip it if it
+ // now is open, to avoid a race condition if the user immediately
+ // opens it when we are resuming.
+ if (st != null) {
+ st.applyFrozenState();
+ if (!st.isOpen && st.wasLastOpen) {
+ st.isInExpandedMode = st.wasLastExpanded;
+ openPanel(st, null);
+ }
+ }
+ }
+ }
+
+ private class PanelMenuPresenterCallback implements MenuPresenter.Callback {
+ @Override
+ public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) {
+ final Menu parentMenu = menu.getRootMenu();
+ final boolean isSubMenu = parentMenu != menu;
+ final PanelFeatureState panel = findMenuPanel(isSubMenu ? parentMenu : menu);
+ if (panel != null) {
+ if (isSubMenu) {
+ callOnPanelClosed(panel.featureId, panel, parentMenu);
+ closePanel(panel, true);
+ } else {
+ // Close the panel and only do the callback if the menu is being
+ // closed completely, not if opening a sub menu
+ closePanel(panel, allMenusAreClosing);
+ }
+ }
+ }
+
+ @Override
+ public boolean onOpenSubMenu(MenuBuilder subMenu) {
+ if (subMenu == null && hasFeature(FEATURE_ACTION_BAR)) {
+ Callback cb = getCallback();
+ if (cb != null && !isDestroyed()) {
+ cb.onMenuOpened(FEATURE_ACTION_BAR, subMenu);
+ }
+ }
+
+ return true;
+ }
+ }
+
+ private final class ActionMenuPresenterCallback implements MenuPresenter.Callback {
+ @Override
+ public boolean onOpenSubMenu(MenuBuilder subMenu) {
+ Callback cb = getCallback();
+ if (cb != null) {
+ cb.onMenuOpened(FEATURE_ACTION_BAR, subMenu);
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) {
+ checkCloseActionMenu(menu);
+ }
+ }
+
+ private final class DecorView extends FrameLayout implements RootViewSurfaceTaker {
+
+ /* package */int mDefaultOpacity = PixelFormat.OPAQUE;
+
+ /** The feature ID of the panel, or -1 if this is the application's DecorView */
+ private final int mFeatureId;
+
+ private final Rect mDrawingBounds = new Rect();
+
+ private final Rect mBackgroundPadding = new Rect();
+
+ private final Rect mFramePadding = new Rect();
+
+ private final Rect mFrameOffsets = new Rect();
+
+ private boolean mChanging;
+
+ private Drawable mMenuBackground;
+ private boolean mWatchingForMenu;
+ private int mDownY;
+
+ private ActionMode mActionMode;
+ private ActionBarContextView mActionModeView;
+ private PopupWindow mActionModePopup;
+ private Runnable mShowActionModePopup;
+
+ // View added at runtime to draw under the status bar area
+ private View mStatusGuard;
+ // View added at runtime to draw under the navigation bar area
+ private View mNavigationGuard;
+
+ private final ColorViewState mStatusColorViewState = new ColorViewState(
+ SYSTEM_UI_FLAG_FULLSCREEN, FLAG_TRANSLUCENT_STATUS,
+ Gravity.TOP,
+ STATUS_BAR_BACKGROUND_TRANSITION_NAME,
+ com.android.internal.R.id.statusBarBackground,
+ FLAG_FULLSCREEN);
+ private final ColorViewState mNavigationColorViewState = new ColorViewState(
+ SYSTEM_UI_FLAG_HIDE_NAVIGATION, FLAG_TRANSLUCENT_NAVIGATION,
+ Gravity.BOTTOM,
+ NAVIGATION_BAR_BACKGROUND_TRANSITION_NAME,
+ com.android.internal.R.id.navigationBarBackground,
+ 0 /* hideWindowFlag */);
+
+ private final Interpolator mShowInterpolator;
+ private final Interpolator mHideInterpolator;
+ private final int mBarEnterExitDuration;
+
+ private final BackgroundFallback mBackgroundFallback = new BackgroundFallback();
+
+ private int mLastTopInset = 0;
+ private int mLastBottomInset = 0;
+ private int mLastRightInset = 0;
+ private boolean mLastHasTopStableInset = false;
+ private boolean mLastHasBottomStableInset = false;
+ private int mLastWindowFlags = 0;
+
+ private int mRootScrollY = 0;
+
+ public DecorView(Context context, int featureId) {
+ super(context);
+ mFeatureId = featureId;
+
+ mShowInterpolator = AnimationUtils.loadInterpolator(context,
+ android.R.interpolator.linear_out_slow_in);
+ mHideInterpolator = AnimationUtils.loadInterpolator(context,
+ android.R.interpolator.fast_out_linear_in);
+
+ mBarEnterExitDuration = context.getResources().getInteger(
+ R.integer.dock_enter_exit_duration);
+ }
+
+ public void setBackgroundFallback(int resId) {
+ mBackgroundFallback.setDrawable(resId != 0 ? getContext().getDrawable(resId) : null);
+ setWillNotDraw(getBackground() == null && !mBackgroundFallback.hasFallback());
+ }
+
+ @Override
+ public void onDraw(Canvas c) {
+ super.onDraw(c);
+ mBackgroundFallback.draw(mContentRoot, c, mContentParent);
+ }
+
+ @Override
+ public boolean dispatchKeyEvent(KeyEvent event) {
+ final int keyCode = event.getKeyCode();
+ final int action = event.getAction();
+ final boolean isDown = action == KeyEvent.ACTION_DOWN;
+
+ if (isDown && (event.getRepeatCount() == 0)) {
+ // First handle chording of panel key: if a panel key is held
+ // but not released, try to execute a shortcut in it.
+ if ((mPanelChordingKey > 0) && (mPanelChordingKey != keyCode)) {
+ boolean handled = dispatchKeyShortcutEvent(event);
+ if (handled) {
+ return true;
+ }
+ }
+
+ // If a panel is open, perform a shortcut on it without the
+ // chorded panel key
+ if ((mPreparedPanel != null) && mPreparedPanel.isOpen) {
+ if (performPanelShortcut(mPreparedPanel, keyCode, event, 0)) {
+ return true;
+ }
+ }
+ }
+
+ if (!isDestroyed()) {
+ final Callback cb = getCallback();
+ final boolean handled = cb != null && mFeatureId < 0 ? cb.dispatchKeyEvent(event)
+ : super.dispatchKeyEvent(event);
+ if (handled) {
+ return true;
+ }
+ }
+
+ return isDown ? PhoneWindow.this.onKeyDown(mFeatureId, event.getKeyCode(), event)
+ : PhoneWindow.this.onKeyUp(mFeatureId, event.getKeyCode(), event);
+ }
+
+ @Override
+ public boolean dispatchKeyShortcutEvent(KeyEvent ev) {
+ // If the panel is already prepared, then perform the shortcut using it.
+ boolean handled;
+ if (mPreparedPanel != null) {
+ handled = performPanelShortcut(mPreparedPanel, ev.getKeyCode(), ev,
+ Menu.FLAG_PERFORM_NO_CLOSE);
+ if (handled) {
+ if (mPreparedPanel != null) {
+ mPreparedPanel.isHandled = true;
+ }
+ return true;
+ }
+ }
+
+ // Shortcut not handled by the panel. Dispatch to the view hierarchy.
+ final Callback cb = getCallback();
+ handled = cb != null && !isDestroyed() && mFeatureId < 0
+ ? cb.dispatchKeyShortcutEvent(ev) : super.dispatchKeyShortcutEvent(ev);
+ if (handled) {
+ return true;
+ }
+
+ // If the panel is not prepared, then we may be trying to handle a shortcut key
+ // combination such as Control+C. Temporarily prepare the panel then mark it
+ // unprepared again when finished to ensure that the panel will again be prepared
+ // the next time it is shown for real.
+ PanelFeatureState st = getPanelState(FEATURE_OPTIONS_PANEL, false);
+ if (st != null && mPreparedPanel == null) {
+ preparePanel(st, ev);
+ handled = performPanelShortcut(st, ev.getKeyCode(), ev,
+ Menu.FLAG_PERFORM_NO_CLOSE);
+ st.isPrepared = false;
+ if (handled) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean dispatchTouchEvent(MotionEvent ev) {
+ final Callback cb = getCallback();
+ return cb != null && !isDestroyed() && mFeatureId < 0 ? cb.dispatchTouchEvent(ev)
+ : super.dispatchTouchEvent(ev);
+ }
+
+ @Override
+ public boolean dispatchTrackballEvent(MotionEvent ev) {
+ final Callback cb = getCallback();
+ return cb != null && !isDestroyed() && mFeatureId < 0 ? cb.dispatchTrackballEvent(ev)
+ : super.dispatchTrackballEvent(ev);
+ }
+
+ @Override
+ public boolean dispatchGenericMotionEvent(MotionEvent ev) {
+ final Callback cb = getCallback();
+ return cb != null && !isDestroyed() && mFeatureId < 0 ? cb.dispatchGenericMotionEvent(ev)
+ : super.dispatchGenericMotionEvent(ev);
+ }
+
+ public boolean superDispatchKeyEvent(KeyEvent event) {
+ // Give priority to closing action modes if applicable.
+ if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) {
+ final int action = event.getAction();
+ // Back cancels action modes first.
+ if (mActionMode != null) {
+ if (action == KeyEvent.ACTION_UP) {
+ mActionMode.finish();
+ }
+ return true;
+ }
+ }
+
+ return super.dispatchKeyEvent(event);
+ }
+
+ public boolean superDispatchKeyShortcutEvent(KeyEvent event) {
+ return super.dispatchKeyShortcutEvent(event);
+ }
+
+ public boolean superDispatchTouchEvent(MotionEvent event) {
+ return super.dispatchTouchEvent(event);
+ }
+
+ public boolean superDispatchTrackballEvent(MotionEvent event) {
+ return super.dispatchTrackballEvent(event);
+ }
+
+ public boolean superDispatchGenericMotionEvent(MotionEvent event) {
+ return super.dispatchGenericMotionEvent(event);
+ }
+
+ @Override
+ public WindowInsets dispatchApplyWindowInsets(WindowInsets insets) {
+ if (mOutsetBottom != null) {
+ final DisplayMetrics metrics = getContext().getResources().getDisplayMetrics();
+ int bottom = (int) mOutsetBottom.getDimension(metrics);
+ WindowInsets newInsets = insets.replaceSystemWindowInsets(
+ insets.getSystemWindowInsetLeft(), insets.getSystemWindowInsetTop(),
+ insets.getSystemWindowInsetRight(), bottom);
+ return super.dispatchApplyWindowInsets(newInsets);
+ } else {
+ return super.dispatchApplyWindowInsets(insets);
+ }
+ }
+
+
+ @Override
+ public boolean onTouchEvent(MotionEvent event) {
+ return onInterceptTouchEvent(event);
+ }
+
+ private boolean isOutOfBounds(int x, int y) {
+ return x < -5 || y < -5 || x > (getWidth() + 5)
+ || y > (getHeight() + 5);
+ }
+
+ @Override
+ public boolean onInterceptTouchEvent(MotionEvent event) {
+ int action = event.getAction();
+ if (mFeatureId >= 0) {
+ if (action == MotionEvent.ACTION_DOWN) {
+ int x = (int)event.getX();
+ int y = (int)event.getY();
+ if (isOutOfBounds(x, y)) {
+ closePanel(mFeatureId);
+ return true;
+ }
+ }
+ }
+
+ if (!SWEEP_OPEN_MENU) {
+ return false;
+ }
+
+ if (mFeatureId >= 0) {
+ if (action == MotionEvent.ACTION_DOWN) {
+ Log.i(TAG, "Watchiing!");
+ mWatchingForMenu = true;
+ mDownY = (int) event.getY();
+ return false;
+ }
+
+ if (!mWatchingForMenu) {
+ return false;
+ }
+
+ int y = (int)event.getY();
+ if (action == MotionEvent.ACTION_MOVE) {
+ if (y > (mDownY+30)) {
+ Log.i(TAG, "Closing!");
+ closePanel(mFeatureId);
+ mWatchingForMenu = false;
+ return true;
+ }
+ } else if (action == MotionEvent.ACTION_UP) {
+ mWatchingForMenu = false;
+ }
+
+ return false;
+ }
+
+ //Log.i(TAG, "Intercept: action=" + action + " y=" + event.getY()
+ // + " (in " + getHeight() + ")");
+
+ if (action == MotionEvent.ACTION_DOWN) {
+ int y = (int)event.getY();
+ if (y >= (getHeight()-5) && !hasChildren()) {
+ Log.i(TAG, "Watchiing!");
+ mWatchingForMenu = true;
+ }
+ return false;
+ }
+
+ if (!mWatchingForMenu) {
+ return false;
+ }
+
+ int y = (int)event.getY();
+ if (action == MotionEvent.ACTION_MOVE) {
+ if (y < (getHeight()-30)) {
+ Log.i(TAG, "Opening!");
+ openPanel(FEATURE_OPTIONS_PANEL, new KeyEvent(
+ KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_MENU));
+ mWatchingForMenu = false;
+ return true;
+ }
+ } else if (action == MotionEvent.ACTION_UP) {
+ mWatchingForMenu = false;
+ }
+
+ return false;
+ }
+
+ @Override
+ public void sendAccessibilityEvent(int eventType) {
+ if (!AccessibilityManager.getInstance(mContext).isEnabled()) {
+ return;
+ }
+
+ // if we are showing a feature that should be announced and one child
+ // make this child the event source since this is the feature itself
+ // otherwise the callback will take over and announce its client
+ if ((mFeatureId == FEATURE_OPTIONS_PANEL ||
+ mFeatureId == FEATURE_CONTEXT_MENU ||
+ mFeatureId == FEATURE_PROGRESS ||
+ mFeatureId == FEATURE_INDETERMINATE_PROGRESS)
+ && getChildCount() == 1) {
+ getChildAt(0).sendAccessibilityEvent(eventType);
+ } else {
+ super.sendAccessibilityEvent(eventType);
+ }
+ }
+
+ @Override
+ public boolean dispatchPopulateAccessibilityEventInternal(AccessibilityEvent event) {
+ final Callback cb = getCallback();
+ if (cb != null && !isDestroyed()) {
+ if (cb.dispatchPopulateAccessibilityEvent(event)) {
+ return true;
+ }
+ }
+ return super.dispatchPopulateAccessibilityEventInternal(event);
+ }
+
+ @Override
+ protected boolean setFrame(int l, int t, int r, int b) {
+ boolean changed = super.setFrame(l, t, r, b);
+ if (changed) {
+ final Rect drawingBounds = mDrawingBounds;
+ getDrawingRect(drawingBounds);
+
+ Drawable fg = getForeground();
+ if (fg != null) {
+ final Rect frameOffsets = mFrameOffsets;
+ drawingBounds.left += frameOffsets.left;
+ drawingBounds.top += frameOffsets.top;
+ drawingBounds.right -= frameOffsets.right;
+ drawingBounds.bottom -= frameOffsets.bottom;
+ fg.setBounds(drawingBounds);
+ final Rect framePadding = mFramePadding;
+ drawingBounds.left += framePadding.left - frameOffsets.left;
+ drawingBounds.top += framePadding.top - frameOffsets.top;
+ drawingBounds.right -= framePadding.right - frameOffsets.right;
+ drawingBounds.bottom -= framePadding.bottom - frameOffsets.bottom;
+ }
+
+ Drawable bg = getBackground();
+ if (bg != null) {
+ bg.setBounds(drawingBounds);
+ }
+
+ if (SWEEP_OPEN_MENU) {
+ if (mMenuBackground == null && mFeatureId < 0
+ && getAttributes().height
+ == WindowManager.LayoutParams.MATCH_PARENT) {
+ mMenuBackground = getContext().getDrawable(
+ R.drawable.menu_background);
+ }
+ if (mMenuBackground != null) {
+ mMenuBackground.setBounds(drawingBounds.left,
+ drawingBounds.bottom-6, drawingBounds.right,
+ drawingBounds.bottom+20);
+ }
+ }
+ }
+ return changed;
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ final DisplayMetrics metrics = getContext().getResources().getDisplayMetrics();
+ final boolean isPortrait = metrics.widthPixels < metrics.heightPixels;
+
+ final int widthMode = getMode(widthMeasureSpec);
+ final int heightMode = getMode(heightMeasureSpec);
+
+ boolean fixedWidth = false;
+ if (widthMode == AT_MOST) {
+ final TypedValue tvw = isPortrait ? mFixedWidthMinor : mFixedWidthMajor;
+ if (tvw != null && tvw.type != TypedValue.TYPE_NULL) {
+ final int w;
+ if (tvw.type == TypedValue.TYPE_DIMENSION) {
+ w = (int) tvw.getDimension(metrics);
+ } else if (tvw.type == TypedValue.TYPE_FRACTION) {
+ w = (int) tvw.getFraction(metrics.widthPixels, metrics.widthPixels);
+ } else {
+ w = 0;
+ }
+
+ if (w > 0) {
+ final int widthSize = MeasureSpec.getSize(widthMeasureSpec);
+ widthMeasureSpec = MeasureSpec.makeMeasureSpec(
+ Math.min(w, widthSize), EXACTLY);
+ fixedWidth = true;
+ }
+ }
+ }
+
+ if (heightMode == AT_MOST) {
+ final TypedValue tvh = isPortrait ? mFixedHeightMajor : mFixedHeightMinor;
+ if (tvh != null && tvh.type != TypedValue.TYPE_NULL) {
+ final int h;
+ if (tvh.type == TypedValue.TYPE_DIMENSION) {
+ h = (int) tvh.getDimension(metrics);
+ } else if (tvh.type == TypedValue.TYPE_FRACTION) {
+ h = (int) tvh.getFraction(metrics.heightPixels, metrics.heightPixels);
+ } else {
+ h = 0;
+ }
+ if (h > 0) {
+ final int heightSize = MeasureSpec.getSize(heightMeasureSpec);
+ heightMeasureSpec = MeasureSpec.makeMeasureSpec(
+ Math.min(h, heightSize), EXACTLY);
+ }
+ }
+ }
+
+ if (mOutsetBottom != null) {
+ int mode = MeasureSpec.getMode(heightMeasureSpec);
+ if (mode != MeasureSpec.UNSPECIFIED) {
+ int outset = (int) mOutsetBottom.getDimension(metrics);
+ int height = MeasureSpec.getSize(heightMeasureSpec);
+ heightMeasureSpec = MeasureSpec.makeMeasureSpec(height + outset, mode);
+ }
+ }
+
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+
+ int width = getMeasuredWidth();
+ boolean measure = false;
+
+ widthMeasureSpec = MeasureSpec.makeMeasureSpec(width, EXACTLY);
+
+ if (!fixedWidth && widthMode == AT_MOST) {
+ final TypedValue tv = isPortrait ? mMinWidthMinor : mMinWidthMajor;
+ if (tv.type != TypedValue.TYPE_NULL) {
+ final int min;
+ if (tv.type == TypedValue.TYPE_DIMENSION) {
+ min = (int)tv.getDimension(metrics);
+ } else if (tv.type == TypedValue.TYPE_FRACTION) {
+ min = (int)tv.getFraction(metrics.widthPixels, metrics.widthPixels);
+ } else {
+ min = 0;
+ }
+
+ if (width < min) {
+ widthMeasureSpec = MeasureSpec.makeMeasureSpec(min, EXACTLY);
+ measure = true;
+ }
+ }
+ }
+
+ // TODO: Support height?
+
+ if (measure) {
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+ }
+ }
+
+ @Override
+ public void draw(Canvas canvas) {
+ super.draw(canvas);
+
+ if (mMenuBackground != null) {
+ mMenuBackground.draw(canvas);
+ }
+ }
+
+
+ @Override
+ public boolean showContextMenuForChild(View originalView) {
+ // Reuse the context menu builder
+ if (mContextMenu == null) {
+ mContextMenu = new ContextMenuBuilder(getContext());
+ mContextMenu.setCallback(mContextMenuCallback);
+ } else {
+ mContextMenu.clearAll();
+ }
+
+ final MenuDialogHelper helper = mContextMenu.show(originalView,
+ originalView.getWindowToken());
+ if (helper != null) {
+ helper.setPresenterCallback(mContextMenuCallback);
+ } else if (mContextMenuHelper != null) {
+ // No menu to show, but if we have a menu currently showing it just became blank.
+ // Close it.
+ mContextMenuHelper.dismiss();
+ }
+ mContextMenuHelper = helper;
+ return helper != null;
+ }
+
+ @Override
+ public ActionMode startActionModeForChild(View originalView,
+ ActionMode.Callback callback) {
+ // originalView can be used here to be sure that we don't obscure
+ // relevant content with the context mode UI.
+ return startActionMode(callback);
+ }
+
+ @Override
+ public ActionMode startActionMode(ActionMode.Callback callback) {
+ if (mActionMode != null) {
+ mActionMode.finish();
+ }
+
+ final ActionMode.Callback wrappedCallback = new ActionModeCallbackWrapper(callback);
+ ActionMode mode = null;
+ if (getCallback() != null && !isDestroyed()) {
+ try {
+ mode = getCallback().onWindowStartingActionMode(wrappedCallback);
+ } catch (AbstractMethodError ame) {
+ // Older apps might not implement this callback method.
+ }
+ }
+ if (mode != null) {
+ mActionMode = mode;
+ } else {
+ if (mActionModeView == null) {
+ if (isFloating()) {
+ // Use the action bar theme.
+ final TypedValue outValue = new TypedValue();
+ final Theme baseTheme = mContext.getTheme();
+ baseTheme.resolveAttribute(R.attr.actionBarTheme, outValue, true);
+
+ final Context actionBarContext;
+ if (outValue.resourceId != 0) {
+ final Theme actionBarTheme = mContext.getResources().newTheme();
+ actionBarTheme.setTo(baseTheme);
+ actionBarTheme.applyStyle(outValue.resourceId, true);
+
+ actionBarContext = new ContextThemeWrapper(mContext, 0);
+ actionBarContext.getTheme().setTo(actionBarTheme);
+ } else {
+ actionBarContext = mContext;
+ }
+
+ mActionModeView = new ActionBarContextView(actionBarContext);
+ mActionModePopup = new PopupWindow(actionBarContext, null,
+ R.attr.actionModePopupWindowStyle);
+ mActionModePopup.setWindowLayoutType(
+ WindowManager.LayoutParams.TYPE_APPLICATION);
+ mActionModePopup.setContentView(mActionModeView);
+ mActionModePopup.setWidth(MATCH_PARENT);
+
+ actionBarContext.getTheme().resolveAttribute(
+ R.attr.actionBarSize, outValue, true);
+ final int height = TypedValue.complexToDimensionPixelSize(outValue.data,
+ actionBarContext.getResources().getDisplayMetrics());
+ mActionModeView.setContentHeight(height);
+ mActionModePopup.setHeight(WRAP_CONTENT);
+ mShowActionModePopup = new Runnable() {
+ public void run() {
+ mActionModePopup.showAtLocation(
+ mActionModeView.getApplicationWindowToken(),
+ Gravity.TOP | Gravity.FILL_HORIZONTAL, 0, 0);
+ }
+ };
+ } else {
+ ViewStub stub = (ViewStub) findViewById(
+ R.id.action_mode_bar_stub);
+ if (stub != null) {
+ mActionModeView = (ActionBarContextView) stub.inflate();
+ }
+ }
+ }
+
+ if (mActionModeView != null) {
+ mActionModeView.killMode();
+ mode = new StandaloneActionMode(mActionModeView.getContext(), mActionModeView,
+ wrappedCallback, mActionModePopup == null);
+ if (callback.onCreateActionMode(mode, mode.getMenu())) {
+ mode.invalidate();
+ mActionModeView.initForMode(mode);
+ mActionModeView.setVisibility(View.VISIBLE);
+ mActionMode = mode;
+ if (mActionModePopup != null) {
+ post(mShowActionModePopup);
+ }
+ mActionModeView.sendAccessibilityEvent(
+ AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
+ } else {
+ mActionMode = null;
+ }
+ }
+ }
+ if (mActionMode != null && getCallback() != null && !isDestroyed()) {
+ try {
+ getCallback().onActionModeStarted(mActionMode);
+ } catch (AbstractMethodError ame) {
+ // Older apps might not implement this callback method.
+ }
+ }
+ return mActionMode;
+ }
+
+ public void startChanging() {
+ mChanging = true;
+ }
+
+ public void finishChanging() {
+ mChanging = false;
+ drawableChanged();
+ }
+
+ public void setWindowBackground(Drawable drawable) {
+ if (getBackground() != drawable) {
+ setBackgroundDrawable(drawable);
+ if (drawable != null) {
+ drawable.getPadding(mBackgroundPadding);
+ } else {
+ mBackgroundPadding.setEmpty();
+ }
+ drawableChanged();
+ }
+ }
+
+ @Override
+ public void setBackgroundDrawable(Drawable d) {
+ super.setBackgroundDrawable(d);
+ if (getWindowToken() != null) {
+ updateWindowResizeState();
+ }
+ }
+
+ public void setWindowFrame(Drawable drawable) {
+ if (getForeground() != drawable) {
+ setForeground(drawable);
+ if (drawable != null) {
+ drawable.getPadding(mFramePadding);
+ } else {
+ mFramePadding.setEmpty();
+ }
+ drawableChanged();
+ }
+ }
+
+ @Override
+ public void onWindowSystemUiVisibilityChanged(int visible) {
+ updateColorViews(null /* insets */, true /* animate */);
+ }
+
+ @Override
+ public WindowInsets onApplyWindowInsets(WindowInsets insets) {
+ mFrameOffsets.set(insets.getSystemWindowInsets());
+ insets = updateColorViews(insets, true /* animate */);
+ insets = updateStatusGuard(insets);
+ updateNavigationGuard(insets);
+ if (getForeground() != null) {
+ drawableChanged();
+ }
+ return insets;
+ }
+
+ @Override
+ public boolean isTransitionGroup() {
+ return false;
+ }
+
+ private WindowInsets updateColorViews(WindowInsets insets, boolean animate) {
+ WindowManager.LayoutParams attrs = getAttributes();
+ int sysUiVisibility = attrs.systemUiVisibility | getWindowSystemUiVisibility();
+
+ if (!mIsFloating && ActivityManager.isHighEndGfx()) {
+ boolean disallowAnimate = !isLaidOut();
+ disallowAnimate |= ((mLastWindowFlags ^ attrs.flags)
+ & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) != 0;
+ mLastWindowFlags = attrs.flags;
+
+ if (insets != null) {
+ mLastTopInset = Math.min(insets.getStableInsetTop(),
+ insets.getSystemWindowInsetTop());
+ mLastBottomInset = Math.min(insets.getStableInsetBottom(),
+ insets.getSystemWindowInsetBottom());
+ mLastRightInset = Math.min(insets.getStableInsetRight(),
+ insets.getSystemWindowInsetRight());
+
+ // Don't animate if the presence of stable insets has changed, because that
+ // indicates that the window was either just added and received them for the
+ // first time, or the window size or position has changed.
+ boolean hasTopStableInset = insets.getStableInsetTop() != 0;
+ disallowAnimate |= hasTopStableInset && !mLastHasTopStableInset;
+ mLastHasTopStableInset = hasTopStableInset;
+
+ boolean hasBottomStableInset = insets.getStableInsetBottom() != 0;
+ disallowAnimate |= hasBottomStableInset && !mLastHasBottomStableInset;
+ mLastHasBottomStableInset = hasBottomStableInset;
+ }
+
+ updateColorViewInt(mStatusColorViewState, sysUiVisibility, mStatusBarColor,
+ mLastTopInset, animate && !disallowAnimate);
+ updateColorViewInt(mNavigationColorViewState, sysUiVisibility, mNavigationBarColor,
+ mLastBottomInset, animate && !disallowAnimate);
+ }
+
+ // When we expand the window with FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS, we still need
+ // to ensure that the rest of the view hierarchy doesn't notice it, unless they've
+ // explicitly asked for it.
+
+ boolean consumingNavBar =
+ (attrs.flags & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) != 0
+ && (sysUiVisibility & SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION) == 0
+ && (sysUiVisibility & SYSTEM_UI_FLAG_HIDE_NAVIGATION) == 0;
+
+ int consumedRight = consumingNavBar ? mLastRightInset : 0;
+ int consumedBottom = consumingNavBar ? mLastBottomInset : 0;
+
+ if (mContentRoot != null
+ && mContentRoot.getLayoutParams() instanceof MarginLayoutParams) {
+ MarginLayoutParams lp = (MarginLayoutParams) mContentRoot.getLayoutParams();
+ if (lp.rightMargin != consumedRight || lp.bottomMargin != consumedBottom) {
+ lp.rightMargin = consumedRight;
+ lp.bottomMargin = consumedBottom;
+ mContentRoot.setLayoutParams(lp);
+
+ if (insets == null) {
+ // The insets have changed, but we're not currently in the process
+ // of dispatching them.
+ requestApplyInsets();
+ }
+ }
+ if (insets != null) {
+ insets = insets.replaceSystemWindowInsets(
+ insets.getSystemWindowInsetLeft(),
+ insets.getSystemWindowInsetTop(),
+ insets.getSystemWindowInsetRight() - consumedRight,
+ insets.getSystemWindowInsetBottom() - consumedBottom);
+ }
+ }
+
+ if (insets != null) {
+ insets = insets.consumeStableInsets();
+ }
+ return insets;
+ }
+
+ private void updateColorViewInt(final ColorViewState state, int sysUiVis, int color,
+ int height, boolean animate) {
+ boolean show = height > 0 && (sysUiVis & state.systemUiHideFlag) == 0
+ && (getAttributes().flags & state.hideWindowFlag) == 0
+ && (getAttributes().flags & state.translucentFlag) == 0
+ && (color & Color.BLACK) != 0
+ && (getAttributes().flags & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) != 0;
+
+ boolean visibilityChanged = false;
+ View view = state.view;
+
+ if (view == null) {
+ if (show) {
+ state.view = view = new View(mContext);
+ view.setBackgroundColor(color);
+ view.setTransitionName(state.transitionName);
+ view.setId(state.id);
+ visibilityChanged = true;
+ view.setVisibility(INVISIBLE);
+ state.targetVisibility = VISIBLE;
+
+ addView(view, new LayoutParams(LayoutParams.MATCH_PARENT, height,
+ Gravity.START | state.verticalGravity));
+ updateColorViewTranslations();
+ }
+ } else {
+ int vis = show ? VISIBLE : INVISIBLE;
+ visibilityChanged = state.targetVisibility != vis;
+ state.targetVisibility = vis;
+ if (show) {
+ LayoutParams lp = (LayoutParams) view.getLayoutParams();
+ if (lp.height != height) {
+ lp.height = height;
+ view.setLayoutParams(lp);
+ }
+ view.setBackgroundColor(color);
+ }
+ }
+ if (visibilityChanged) {
+ view.animate().cancel();
+ if (animate) {
+ if (show) {
+ if (view.getVisibility() != VISIBLE) {
+ view.setVisibility(VISIBLE);
+ view.setAlpha(0.0f);
+ }
+ view.animate().alpha(1.0f).setInterpolator(mShowInterpolator).
+ setDuration(mBarEnterExitDuration);
+ } else {
+ view.animate().alpha(0.0f).setInterpolator(mHideInterpolator)
+ .setDuration(mBarEnterExitDuration)
+ .withEndAction(new Runnable() {
+ @Override
+ public void run() {
+ state.view.setAlpha(1.0f);
+ state.view.setVisibility(INVISIBLE);
+ }
+ });
+ }
+ } else {
+ view.setAlpha(1.0f);
+ view.setVisibility(show ? VISIBLE : INVISIBLE);
+ }
+ }
+ }
+
+ private void updateColorViewTranslations() {
+ // Put the color views back in place when they get moved off the screen
+ // due to the the ViewRootImpl panning.
+ int rootScrollY = mRootScrollY;
+ if (mStatusColorViewState.view != null) {
+ mStatusColorViewState.view.setTranslationY(rootScrollY > 0 ? rootScrollY : 0);
+ }
+ if (mNavigationColorViewState.view != null) {
+ mNavigationColorViewState.view.setTranslationY(rootScrollY < 0 ? rootScrollY : 0);
+ }
+ }
+
+ private WindowInsets updateStatusGuard(WindowInsets insets) {
+ boolean showStatusGuard = false;
+ // Show the status guard when the non-overlay contextual action bar is showing
+ if (mActionModeView != null) {
+ if (mActionModeView.getLayoutParams() instanceof MarginLayoutParams) {
+ // Insets are magic!
+ final MarginLayoutParams mlp = (MarginLayoutParams)
+ mActionModeView.getLayoutParams();
+ boolean mlpChanged = false;
+ if (mActionModeView.isShown()) {
+ if (mTempRect == null) {
+ mTempRect = new Rect();
+ }
+ final Rect rect = mTempRect;
+
+ // If the parent doesn't consume the insets, manually
+ // apply the default system window insets.
+ mContentParent.computeSystemWindowInsets(insets, rect);
+ final int newMargin = rect.top == 0 ? insets.getSystemWindowInsetTop() : 0;
+ if (mlp.topMargin != newMargin) {
+ mlpChanged = true;
+ mlp.topMargin = insets.getSystemWindowInsetTop();
+
+ if (mStatusGuard == null) {
+ mStatusGuard = new View(mContext);
+ mStatusGuard.setBackgroundColor(mContext.getResources()
+ .getColor(R.color.input_method_navigation_guard));
+ addView(mStatusGuard, indexOfChild(mStatusColorViewState.view),
+ new LayoutParams(LayoutParams.MATCH_PARENT,
+ mlp.topMargin, Gravity.START | Gravity.TOP));
+ } else {
+ final LayoutParams lp = (LayoutParams)
+ mStatusGuard.getLayoutParams();
+ if (lp.height != mlp.topMargin) {
+ lp.height = mlp.topMargin;
+ mStatusGuard.setLayoutParams(lp);
+ }
+ }
+ }
+
+ // The action mode's theme may differ from the app, so
+ // always show the status guard above it if we have one.
+ showStatusGuard = mStatusGuard != null;
+
+ // We only need to consume the insets if the action
+ // mode is overlaid on the app content (e.g. it's
+ // sitting in a FrameLayout, see
+ // screen_simple_overlay_action_mode.xml).
+ final boolean nonOverlay = (getLocalFeatures()
+ & (1 << FEATURE_ACTION_MODE_OVERLAY)) == 0;
+ insets = insets.consumeSystemWindowInsets(
+ false, nonOverlay && showStatusGuard /* top */, false, false);
+ } else {
+ // reset top margin
+ if (mlp.topMargin != 0) {
+ mlpChanged = true;
+ mlp.topMargin = 0;
+ }
+ }
+ if (mlpChanged) {
+ mActionModeView.setLayoutParams(mlp);
+ }
+ }
+ }
+ if (mStatusGuard != null) {
+ mStatusGuard.setVisibility(showStatusGuard ? View.VISIBLE : View.GONE);
+ }
+ return insets;
+ }
+
+ private void updateNavigationGuard(WindowInsets insets) {
+ // IMEs lay out below the nav bar, but the content view must not (for back compat)
+ if (getAttributes().type == WindowManager.LayoutParams.TYPE_INPUT_METHOD) {
+ // prevent the content view from including the nav bar height
+ if (mContentParent != null) {
+ if (mContentParent.getLayoutParams() instanceof MarginLayoutParams) {
+ MarginLayoutParams mlp =
+ (MarginLayoutParams) mContentParent.getLayoutParams();
+ mlp.bottomMargin = insets.getSystemWindowInsetBottom();
+ mContentParent.setLayoutParams(mlp);
+ }
+ }
+ // position the navigation guard view, creating it if necessary
+ if (mNavigationGuard == null) {
+ mNavigationGuard = new View(mContext);
+ mNavigationGuard.setBackgroundColor(mContext.getResources()
+ .getColor(R.color.input_method_navigation_guard));
+ addView(mNavigationGuard, indexOfChild(mNavigationColorViewState.view),
+ new LayoutParams(LayoutParams.MATCH_PARENT,
+ insets.getSystemWindowInsetBottom(),
+ Gravity.START | Gravity.BOTTOM));
+ } else {
+ LayoutParams lp = (LayoutParams) mNavigationGuard.getLayoutParams();
+ lp.height = insets.getSystemWindowInsetBottom();
+ mNavigationGuard.setLayoutParams(lp);
+ }
+ }
+ }
+
+ private void drawableChanged() {
+ if (mChanging) {
+ return;
+ }
+
+ setPadding(mFramePadding.left + mBackgroundPadding.left, mFramePadding.top
+ + mBackgroundPadding.top, mFramePadding.right + mBackgroundPadding.right,
+ mFramePadding.bottom + mBackgroundPadding.bottom);
+ requestLayout();
+ invalidate();
+
+ int opacity = PixelFormat.OPAQUE;
+ // Note: if there is no background, we will assume opaque. The
+ // common case seems to be that an application sets there to be
+ // no background so it can draw everything itself. For that,
+ // we would like to assume OPAQUE and let the app force it to
+ // the slower TRANSLUCENT mode if that is really what it wants.
+ Drawable bg = getBackground();
+ Drawable fg = getForeground();
+ if (bg != null) {
+ if (fg == null) {
+ opacity = bg.getOpacity();
+ } else if (mFramePadding.left <= 0 && mFramePadding.top <= 0
+ && mFramePadding.right <= 0 && mFramePadding.bottom <= 0) {
+ // If the frame padding is zero, then we can be opaque
+ // if either the frame -or- the background is opaque.
+ int fop = fg.getOpacity();
+ int bop = bg.getOpacity();
+ if (false)
+ Log.v(TAG, "Background opacity: " + bop + ", Frame opacity: " + fop);
+ if (fop == PixelFormat.OPAQUE || bop == PixelFormat.OPAQUE) {
+ opacity = PixelFormat.OPAQUE;
+ } else if (fop == PixelFormat.UNKNOWN) {
+ opacity = bop;
+ } else if (bop == PixelFormat.UNKNOWN) {
+ opacity = fop;
+ } else {
+ opacity = Drawable.resolveOpacity(fop, bop);
+ }
+ } else {
+ // For now we have to assume translucent if there is a
+ // frame with padding... there is no way to tell if the
+ // frame and background together will draw all pixels.
+ if (false)
+ Log.v(TAG, "Padding: " + mFramePadding);
+ opacity = PixelFormat.TRANSLUCENT;
+ }
+ }
+
+ if (false)
+ Log.v(TAG, "Background: " + bg + ", Frame: " + fg);
+ if (false)
+ Log.v(TAG, "Selected default opacity: " + opacity);
+
+ mDefaultOpacity = opacity;
+ if (mFeatureId < 0) {
+ setDefaultWindowFormat(opacity);
+ }
+ }
+
+ @Override
+ public void onWindowFocusChanged(boolean hasWindowFocus) {
+ super.onWindowFocusChanged(hasWindowFocus);
+
+ // If the user is chording a menu shortcut, release the chord since
+ // this window lost focus
+ if (hasFeature(FEATURE_OPTIONS_PANEL) && !hasWindowFocus && mPanelChordingKey != 0) {
+ closePanel(FEATURE_OPTIONS_PANEL);
+ }
+
+ final Callback cb = getCallback();
+ if (cb != null && !isDestroyed() && mFeatureId < 0) {
+ cb.onWindowFocusChanged(hasWindowFocus);
+ }
+ }
+
+ void updateWindowResizeState() {
+ Drawable bg = getBackground();
+ hackTurnOffWindowResizeAnim(bg == null || bg.getOpacity()
+ != PixelFormat.OPAQUE);
+ }
+
+ @Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+
+ updateWindowResizeState();
+
+ final Callback cb = getCallback();
+ if (cb != null && !isDestroyed() && mFeatureId < 0) {
+ cb.onAttachedToWindow();
+ }
+
+ if (mFeatureId == -1) {
+ /*
+ * The main window has been attached, try to restore any panels
+ * that may have been open before. This is called in cases where
+ * an activity is being killed for configuration change and the
+ * menu was open. When the activity is recreated, the menu
+ * should be shown again.
+ */
+ openPanelsAfterRestore();
+ }
+ }
+
+ @Override
+ protected void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+
+ final Callback cb = getCallback();
+ if (cb != null && mFeatureId < 0) {
+ cb.onDetachedFromWindow();
+ }
+
+ if (mDecorContentParent != null) {
+ mDecorContentParent.dismissPopups();
+ }
+
+ if (mActionModePopup != null) {
+ removeCallbacks(mShowActionModePopup);
+ if (mActionModePopup.isShowing()) {
+ mActionModePopup.dismiss();
+ }
+ mActionModePopup = null;
+ }
+
+ PanelFeatureState st = getPanelState(FEATURE_OPTIONS_PANEL, false);
+ if (st != null && st.menu != null && mFeatureId < 0) {
+ st.menu.close();
+ }
+ }
+
+ @Override
+ public void onCloseSystemDialogs(String reason) {
+ if (mFeatureId >= 0) {
+ closeAllPanels();
+ }
+ }
+
+ public android.view.SurfaceHolder.Callback2 willYouTakeTheSurface() {
+ return mFeatureId < 0 ? mTakeSurfaceCallback : null;
+ }
+
+ public InputQueue.Callback willYouTakeTheInputQueue() {
+ return mFeatureId < 0 ? mTakeInputQueueCallback : null;
+ }
+
+ public void setSurfaceType(int type) {
+ PhoneWindow.this.setType(type);
+ }
+
+ public void setSurfaceFormat(int format) {
+ PhoneWindow.this.setFormat(format);
+ }
+
+ public void setSurfaceKeepScreenOn(boolean keepOn) {
+ if (keepOn) PhoneWindow.this.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+ else PhoneWindow.this.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+ }
+
+ @Override
+ public void onRootViewScrollYChanged(int rootScrollY) {
+ mRootScrollY = rootScrollY;
+ updateColorViewTranslations();
+ }
+
+ /**
+ * Clears out internal reference when the action mode is destroyed.
+ */
+ private class ActionModeCallbackWrapper implements ActionMode.Callback {
+ private ActionMode.Callback mWrapped;
+
+ public ActionModeCallbackWrapper(ActionMode.Callback wrapped) {
+ mWrapped = wrapped;
+ }
+
+ public boolean onCreateActionMode(ActionMode mode, Menu menu) {
+ return mWrapped.onCreateActionMode(mode, menu);
+ }
+
+ public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
+ requestFitSystemWindows();
+ return mWrapped.onPrepareActionMode(mode, menu);
+ }
+
+ public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
+ return mWrapped.onActionItemClicked(mode, item);
+ }
+
+ public void onDestroyActionMode(ActionMode mode) {
+ mWrapped.onDestroyActionMode(mode);
+ if (mActionModePopup != null) {
+ removeCallbacks(mShowActionModePopup);
+ mActionModePopup.dismiss();
+ } else if (mActionModeView != null) {
+ mActionModeView.setVisibility(GONE);
+ }
+ if (mActionModeView != null) {
+ mActionModeView.removeAllViews();
+ }
+ if (getCallback() != null && !isDestroyed()) {
+ try {
+ getCallback().onActionModeFinished(mActionMode);
+ } catch (AbstractMethodError ame) {
+ // Older apps might not implement this callback method.
+ }
+ }
+ mActionMode = null;
+ requestFitSystemWindows();
+ }
+ }
+ }
+
+ protected DecorView generateDecor() {
+ return new DecorView(getContext(), -1);
+ }
+
+ protected void setFeatureFromAttrs(int featureId, TypedArray attrs,
+ int drawableAttr, int alphaAttr) {
+ Drawable d = attrs.getDrawable(drawableAttr);
+ if (d != null) {
+ requestFeature(featureId);
+ setFeatureDefaultDrawable(featureId, d);
+ }
+ if ((getFeatures() & (1 << featureId)) != 0) {
+ int alpha = attrs.getInt(alphaAttr, -1);
+ if (alpha >= 0) {
+ setFeatureDrawableAlpha(featureId, alpha);
+ }
+ }
+ }
+
+ protected ViewGroup generateLayout(DecorView decor) {
+ // Apply data from current theme.
+
+ TypedArray a = getWindowStyle();
+
+ if (false) {
+ System.out.println("From style:");
+ String s = "Attrs:";
+ for (int i = 0; i < R.styleable.Window.length; i++) {
+ s = s + " " + Integer.toHexString(R.styleable.Window[i]) + "="
+ + a.getString(i);
+ }
+ System.out.println(s);
+ }
+
+ mIsFloating = a.getBoolean(R.styleable.Window_windowIsFloating, false);
+ int flagsToUpdate = (FLAG_LAYOUT_IN_SCREEN|FLAG_LAYOUT_INSET_DECOR)
+ & (~getForcedWindowFlags());
+ if (mIsFloating) {
+ setLayout(WRAP_CONTENT, WRAP_CONTENT);
+ setFlags(0, flagsToUpdate);
+ } else {
+ setFlags(FLAG_LAYOUT_IN_SCREEN|FLAG_LAYOUT_INSET_DECOR, flagsToUpdate);
+ }
+
+ if (a.getBoolean(R.styleable.Window_windowNoTitle, false)) {
+ requestFeature(FEATURE_NO_TITLE);
+ } else if (a.getBoolean(R.styleable.Window_windowActionBar, false)) {
+ // Don't allow an action bar if there is no title.
+ requestFeature(FEATURE_ACTION_BAR);
+ }
+
+ if (a.getBoolean(R.styleable.Window_windowActionBarOverlay, false)) {
+ requestFeature(FEATURE_ACTION_BAR_OVERLAY);
+ }
+
+ if (a.getBoolean(R.styleable.Window_windowActionModeOverlay, false)) {
+ requestFeature(FEATURE_ACTION_MODE_OVERLAY);
+ }
+
+ if (a.getBoolean(R.styleable.Window_windowSwipeToDismiss, false)) {
+ requestFeature(FEATURE_SWIPE_TO_DISMISS);
+ }
+
+ if (a.getBoolean(R.styleable.Window_windowFullscreen, false)) {
+ setFlags(FLAG_FULLSCREEN, FLAG_FULLSCREEN & (~getForcedWindowFlags()));
+ }
+
+ if (a.getBoolean(R.styleable.Window_windowTranslucentStatus,
+ false)) {
+ setFlags(FLAG_TRANSLUCENT_STATUS, FLAG_TRANSLUCENT_STATUS
+ & (~getForcedWindowFlags()));
+ }
+
+ if (a.getBoolean(R.styleable.Window_windowTranslucentNavigation,
+ false)) {
+ setFlags(FLAG_TRANSLUCENT_NAVIGATION, FLAG_TRANSLUCENT_NAVIGATION
+ & (~getForcedWindowFlags()));
+ }
+
+ if (a.getBoolean(R.styleable.Window_windowOverscan, false)) {
+ setFlags(FLAG_LAYOUT_IN_OVERSCAN, FLAG_LAYOUT_IN_OVERSCAN&(~getForcedWindowFlags()));
+ }
+
+ if (a.getBoolean(R.styleable.Window_windowShowWallpaper, false)) {
+ setFlags(FLAG_SHOW_WALLPAPER, FLAG_SHOW_WALLPAPER&(~getForcedWindowFlags()));
+ }
+
+ if (a.getBoolean(R.styleable.Window_windowEnableSplitTouch,
+ getContext().getApplicationInfo().targetSdkVersion
+ >= android.os.Build.VERSION_CODES.HONEYCOMB)) {
+ setFlags(FLAG_SPLIT_TOUCH, FLAG_SPLIT_TOUCH&(~getForcedWindowFlags()));
+ }
+
+ a.getValue(R.styleable.Window_windowMinWidthMajor, mMinWidthMajor);
+ a.getValue(R.styleable.Window_windowMinWidthMinor, mMinWidthMinor);
+ if (a.hasValue(R.styleable.Window_windowFixedWidthMajor)) {
+ if (mFixedWidthMajor == null) mFixedWidthMajor = new TypedValue();
+ a.getValue(R.styleable.Window_windowFixedWidthMajor,
+ mFixedWidthMajor);
+ }
+ if (a.hasValue(R.styleable.Window_windowFixedWidthMinor)) {
+ if (mFixedWidthMinor == null) mFixedWidthMinor = new TypedValue();
+ a.getValue(R.styleable.Window_windowFixedWidthMinor,
+ mFixedWidthMinor);
+ }
+ if (a.hasValue(R.styleable.Window_windowFixedHeightMajor)) {
+ if (mFixedHeightMajor == null) mFixedHeightMajor = new TypedValue();
+ a.getValue(R.styleable.Window_windowFixedHeightMajor,
+ mFixedHeightMajor);
+ }
+ if (a.hasValue(R.styleable.Window_windowFixedHeightMinor)) {
+ if (mFixedHeightMinor == null) mFixedHeightMinor = new TypedValue();
+ a.getValue(R.styleable.Window_windowFixedHeightMinor,
+ mFixedHeightMinor);
+ }
+ if (a.getBoolean(R.styleable.Window_windowContentTransitions, false)) {
+ requestFeature(FEATURE_CONTENT_TRANSITIONS);
+ }
+ if (a.getBoolean(R.styleable.Window_windowActivityTransitions, false)) {
+ requestFeature(FEATURE_ACTIVITY_TRANSITIONS);
+ }
+
+ final WindowManager windowService = (WindowManager) getContext().getSystemService(
+ Context.WINDOW_SERVICE);
+ if (windowService != null) {
+ final Display display = windowService.getDefaultDisplay();
+ final boolean shouldUseBottomOutset =
+ display.getDisplayId() == Display.DEFAULT_DISPLAY
+ || (getForcedWindowFlags() & FLAG_FULLSCREEN) != 0;
+ if (shouldUseBottomOutset && a.hasValue(R.styleable.Window_windowOutsetBottom)) {
+ if (mOutsetBottom == null) mOutsetBottom = new TypedValue();
+ a.getValue(R.styleable.Window_windowOutsetBottom,
+ mOutsetBottom);
+ }
+ }
+
+ final Context context = getContext();
+ final int targetSdk = context.getApplicationInfo().targetSdkVersion;
+ final boolean targetPreHoneycomb = targetSdk < android.os.Build.VERSION_CODES.HONEYCOMB;
+ final boolean targetPreIcs = targetSdk < android.os.Build.VERSION_CODES.ICE_CREAM_SANDWICH;
+ final boolean targetPreL = targetSdk < android.os.Build.VERSION_CODES.LOLLIPOP;
+ final boolean targetHcNeedsOptions = context.getResources().getBoolean(
+ R.bool.target_honeycomb_needs_options_menu);
+ final boolean noActionBar = !hasFeature(FEATURE_ACTION_BAR) || hasFeature(FEATURE_NO_TITLE);
+
+ if (targetPreHoneycomb || (targetPreIcs && targetHcNeedsOptions && noActionBar)) {
+ setNeedsMenuKey(WindowManager.LayoutParams.NEEDS_MENU_SET_TRUE);
+ } else {
+ setNeedsMenuKey(WindowManager.LayoutParams.NEEDS_MENU_SET_FALSE);
+ }
+
+ // Non-floating windows on high end devices must put up decor beneath the system bars and
+ // therefore must know about visibility changes of those.
+ if (!mIsFloating && ActivityManager.isHighEndGfx()) {
+ if (!targetPreL && a.getBoolean(
+ R.styleable.Window_windowDrawsSystemBarBackgrounds,
+ false)) {
+ setFlags(FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS,
+ FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS & ~getForcedWindowFlags());
+ }
+ }
+ if (!mForcedStatusBarColor) {
+ mStatusBarColor = a.getColor(R.styleable.Window_statusBarColor, 0xFF000000);
+ }
+ if (!mForcedNavigationBarColor) {
+ mNavigationBarColor = a.getColor(R.styleable.Window_navigationBarColor, 0xFF000000);
+ }
+ if (a.getBoolean(R.styleable.Window_windowHasLightStatusBar, false)) {
+ decor.setSystemUiVisibility(
+ decor.getSystemUiVisibility() | View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR);
+ }
+
+ if (mAlwaysReadCloseOnTouchAttr || getContext().getApplicationInfo().targetSdkVersion
+ >= android.os.Build.VERSION_CODES.HONEYCOMB) {
+ if (a.getBoolean(
+ R.styleable.Window_windowCloseOnTouchOutside,
+ false)) {
+ setCloseOnTouchOutsideIfNotSet(true);
+ }
+ }
+
+ WindowManager.LayoutParams params = getAttributes();
+
+ if (!hasSoftInputMode()) {
+ params.softInputMode = a.getInt(
+ R.styleable.Window_windowSoftInputMode,
+ params.softInputMode);
+ }
+
+ if (a.getBoolean(R.styleable.Window_backgroundDimEnabled,
+ mIsFloating)) {
+ /* All dialogs should have the window dimmed */
+ if ((getForcedWindowFlags()&WindowManager.LayoutParams.FLAG_DIM_BEHIND) == 0) {
+ params.flags |= WindowManager.LayoutParams.FLAG_DIM_BEHIND;
+ }
+ if (!haveDimAmount()) {
+ params.dimAmount = a.getFloat(
+ android.R.styleable.Window_backgroundDimAmount, 0.5f);
+ }
+ }
+
+ if (params.windowAnimations == 0) {
+ params.windowAnimations = a.getResourceId(
+ R.styleable.Window_windowAnimationStyle, 0);
+ }
+
+ // The rest are only done if this window is not embedded; otherwise,
+ // the values are inherited from our container.
+ if (getContainer() == null) {
+ if (mBackgroundDrawable == null) {
+ if (mBackgroundResource == 0) {
+ mBackgroundResource = a.getResourceId(
+ R.styleable.Window_windowBackground, 0);
+ }
+ if (mFrameResource == 0) {
+ mFrameResource = a.getResourceId(R.styleable.Window_windowFrame, 0);
+ }
+ mBackgroundFallbackResource = a.getResourceId(
+ R.styleable.Window_windowBackgroundFallback, 0);
+ if (false) {
+ System.out.println("Background: "
+ + Integer.toHexString(mBackgroundResource) + " Frame: "
+ + Integer.toHexString(mFrameResource));
+ }
+ }
+ mElevation = a.getDimension(R.styleable.Window_windowElevation, 0);
+ mClipToOutline = a.getBoolean(R.styleable.Window_windowClipToOutline, false);
+ mTextColor = a.getColor(R.styleable.Window_textColor, Color.TRANSPARENT);
+ }
+
+ // Inflate the window decor.
+
+ int layoutResource;
+ int features = getLocalFeatures();
+ // System.out.println("Features: 0x" + Integer.toHexString(features));
+ if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) {
+ layoutResource = R.layout.screen_swipe_dismiss;
+ } else if ((features & ((1 << FEATURE_LEFT_ICON) | (1 << FEATURE_RIGHT_ICON))) != 0) {
+ if (mIsFloating) {
+ TypedValue res = new TypedValue();
+ getContext().getTheme().resolveAttribute(
+ R.attr.dialogTitleIconsDecorLayout, res, true);
+ layoutResource = res.resourceId;
+ } else {
+ layoutResource = R.layout.screen_title_icons;
+ }
+ // XXX Remove this once action bar supports these features.
+ removeFeature(FEATURE_ACTION_BAR);
+ // System.out.println("Title Icons!");
+ } else if ((features & ((1 << FEATURE_PROGRESS) | (1 << FEATURE_INDETERMINATE_PROGRESS))) != 0
+ && (features & (1 << FEATURE_ACTION_BAR)) == 0) {
+ // Special case for a window with only a progress bar (and title).
+ // XXX Need to have a no-title version of embedded windows.
+ layoutResource = R.layout.screen_progress;
+ // System.out.println("Progress!");
+ } else if ((features & (1 << FEATURE_CUSTOM_TITLE)) != 0) {
+ // Special case for a window with a custom title.
+ // If the window is floating, we need a dialog layout
+ if (mIsFloating) {
+ TypedValue res = new TypedValue();
+ getContext().getTheme().resolveAttribute(
+ R.attr.dialogCustomTitleDecorLayout, res, true);
+ layoutResource = res.resourceId;
+ } else {
+ layoutResource = R.layout.screen_custom_title;
+ }
+ // XXX Remove this once action bar supports these features.
+ removeFeature(FEATURE_ACTION_BAR);
+ } else if ((features & (1 << FEATURE_NO_TITLE)) == 0) {
+ // If no other features and not embedded, only need a title.
+ // If the window is floating, we need a dialog layout
+ if (mIsFloating) {
+ TypedValue res = new TypedValue();
+ getContext().getTheme().resolveAttribute(
+ R.attr.dialogTitleDecorLayout, res, true);
+ layoutResource = res.resourceId;
+ } else if ((features & (1 << FEATURE_ACTION_BAR)) != 0) {
+ layoutResource = a.getResourceId(
+ R.styleable.Window_windowActionBarFullscreenDecorLayout,
+ R.layout.screen_action_bar);
+ } else {
+ layoutResource = R.layout.screen_title;
+ }
+ // System.out.println("Title!");
+ } else if ((features & (1 << FEATURE_ACTION_MODE_OVERLAY)) != 0) {
+ layoutResource = R.layout.screen_simple_overlay_action_mode;
+ } else {
+ // Embedded, so no decoration is needed.
+ layoutResource = R.layout.screen_simple;
+ // System.out.println("Simple!");
+ }
+
+ mDecor.startChanging();
+
+ View in = mLayoutInflater.inflate(layoutResource, null);
+ decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
+ mContentRoot = (ViewGroup) in;
+
+ ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
+ if (contentParent == null) {
+ throw new RuntimeException("Window couldn't find content container view");
+ }
+
+ if ((features & (1 << FEATURE_INDETERMINATE_PROGRESS)) != 0) {
+ ProgressBar progress = getCircularProgressBar(false);
+ if (progress != null) {
+ progress.setIndeterminate(true);
+ }
+ }
+
+ if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) {
+ registerSwipeCallbacks();
+ }
+
+ // Remaining setup -- of background and title -- that only applies
+ // to top-level windows.
+ if (getContainer() == null) {
+ final Drawable background;
+ if (mBackgroundResource != 0) {
+ background = getContext().getDrawable(mBackgroundResource);
+ } else {
+ background = mBackgroundDrawable;
+ }
+ mDecor.setWindowBackground(background);
+
+ final Drawable frame;
+ if (mFrameResource != 0) {
+ frame = getContext().getDrawable(mFrameResource);
+ } else {
+ frame = null;
+ }
+ mDecor.setWindowFrame(frame);
+
+ mDecor.setElevation(mElevation);
+ mDecor.setClipToOutline(mClipToOutline);
+
+ if (mTitle != null) {
+ setTitle(mTitle);
+ }
+
+ if (mTitleColor == 0) {
+ mTitleColor = mTextColor;
+ }
+ setTitleColor(mTitleColor);
+ }
+
+ mDecor.finishChanging();
+
+ return contentParent;
+ }
+
+ /** @hide */
+ public void alwaysReadCloseOnTouchAttr() {
+ mAlwaysReadCloseOnTouchAttr = true;
+ }
+
+ private void installDecor() {
+ if (mDecor == null) {
+ mDecor = generateDecor();
+ mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
+ mDecor.setIsRootNamespace(true);
+ if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
+ mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
+ }
+ }
+ if (mContentParent == null) {
+ mContentParent = generateLayout(mDecor);
+
+ // Set up decor part of UI to ignore fitsSystemWindows if appropriate.
+ mDecor.makeOptionalFitsSystemWindows();
+
+ final DecorContentParent decorContentParent = (DecorContentParent) mDecor.findViewById(
+ R.id.decor_content_parent);
+
+ if (decorContentParent != null) {
+ mDecorContentParent = decorContentParent;
+ mDecorContentParent.setWindowCallback(getCallback());
+ if (mDecorContentParent.getTitle() == null) {
+ mDecorContentParent.setWindowTitle(mTitle);
+ }
+
+ final int localFeatures = getLocalFeatures();
+ for (int i = 0; i < FEATURE_MAX; i++) {
+ if ((localFeatures & (1 << i)) != 0) {
+ mDecorContentParent.initFeature(i);
+ }
+ }
+
+ mDecorContentParent.setUiOptions(mUiOptions);
+
+ if ((mResourcesSetFlags & FLAG_RESOURCE_SET_ICON) != 0 ||
+ (mIconRes != 0 && !mDecorContentParent.hasIcon())) {
+ mDecorContentParent.setIcon(mIconRes);
+ } else if ((mResourcesSetFlags & FLAG_RESOURCE_SET_ICON) == 0 &&
+ mIconRes == 0 && !mDecorContentParent.hasIcon()) {
+ mDecorContentParent.setIcon(
+ getContext().getPackageManager().getDefaultActivityIcon());
+ mResourcesSetFlags |= FLAG_RESOURCE_SET_ICON_FALLBACK;
+ }
+ if ((mResourcesSetFlags & FLAG_RESOURCE_SET_LOGO) != 0 ||
+ (mLogoRes != 0 && !mDecorContentParent.hasLogo())) {
+ mDecorContentParent.setLogo(mLogoRes);
+ }
+
+ // Invalidate if the panel menu hasn't been created before this.
+ // Panel menu invalidation is deferred avoiding application onCreateOptionsMenu
+ // being called in the middle of onCreate or similar.
+ // A pending invalidation will typically be resolved before the posted message
+ // would run normally in order to satisfy instance state restoration.
+ PanelFeatureState st = getPanelState(FEATURE_OPTIONS_PANEL, false);
+ if (!isDestroyed() && (st == null || st.menu == null)) {
+ invalidatePanelMenu(FEATURE_ACTION_BAR);
+ }
+ } else {
+ mTitleView = (TextView)findViewById(R.id.title);
+ if (mTitleView != null) {
+ mTitleView.setLayoutDirection(mDecor.getLayoutDirection());
+ if ((getLocalFeatures() & (1 << FEATURE_NO_TITLE)) != 0) {
+ View titleContainer = findViewById(
+ R.id.title_container);
+ if (titleContainer != null) {
+ titleContainer.setVisibility(View.GONE);
+ } else {
+ mTitleView.setVisibility(View.GONE);
+ }
+ if (mContentParent instanceof FrameLayout) {
+ ((FrameLayout)mContentParent).setForeground(null);
+ }
+ } else {
+ mTitleView.setText(mTitle);
+ }
+ }
+ }
+
+ if (mDecor.getBackground() == null && mBackgroundFallbackResource != 0) {
+ mDecor.setBackgroundFallback(mBackgroundFallbackResource);
+ }
+
+ // Only inflate or create a new TransitionManager if the caller hasn't
+ // already set a custom one.
+ if (hasFeature(FEATURE_ACTIVITY_TRANSITIONS)) {
+ if (mTransitionManager == null) {
+ final int transitionRes = getWindowStyle().getResourceId(
+ R.styleable.Window_windowContentTransitionManager,
+ 0);
+ if (transitionRes != 0) {
+ final TransitionInflater inflater = TransitionInflater.from(getContext());
+ mTransitionManager = inflater.inflateTransitionManager(transitionRes,
+ mContentParent);
+ } else {
+ mTransitionManager = new TransitionManager();
+ }
+ }
+
+ mEnterTransition = getTransition(mEnterTransition, null,
+ R.styleable.Window_windowEnterTransition);
+ mReturnTransition = getTransition(mReturnTransition, USE_DEFAULT_TRANSITION,
+ R.styleable.Window_windowReturnTransition);
+ mExitTransition = getTransition(mExitTransition, null,
+ R.styleable.Window_windowExitTransition);
+ mReenterTransition = getTransition(mReenterTransition, USE_DEFAULT_TRANSITION,
+ R.styleable.Window_windowReenterTransition);
+ mSharedElementEnterTransition = getTransition(mSharedElementEnterTransition, null,
+ R.styleable.Window_windowSharedElementEnterTransition);
+ mSharedElementReturnTransition = getTransition(mSharedElementReturnTransition,
+ USE_DEFAULT_TRANSITION,
+ R.styleable.Window_windowSharedElementReturnTransition);
+ mSharedElementExitTransition = getTransition(mSharedElementExitTransition, null,
+ R.styleable.Window_windowSharedElementExitTransition);
+ mSharedElementReenterTransition = getTransition(mSharedElementReenterTransition,
+ USE_DEFAULT_TRANSITION,
+ R.styleable.Window_windowSharedElementReenterTransition);
+ if (mAllowEnterTransitionOverlap == null) {
+ mAllowEnterTransitionOverlap = getWindowStyle().getBoolean(
+ R.styleable.Window_windowAllowEnterTransitionOverlap, true);
+ }
+ if (mAllowReturnTransitionOverlap == null) {
+ mAllowReturnTransitionOverlap = getWindowStyle().getBoolean(
+ R.styleable.Window_windowAllowReturnTransitionOverlap, true);
+ }
+ if (mBackgroundFadeDurationMillis < 0) {
+ mBackgroundFadeDurationMillis = getWindowStyle().getInteger(
+ R.styleable.Window_windowTransitionBackgroundFadeDuration,
+ DEFAULT_BACKGROUND_FADE_DURATION_MS);
+ }
+ if (mSharedElementsUseOverlay == null) {
+ mSharedElementsUseOverlay = getWindowStyle().getBoolean(
+ R.styleable.Window_windowSharedElementsUseOverlay, true);
+ }
+ }
+ }
+ }
+
+ private Transition getTransition(Transition currentValue, Transition defaultValue, int id) {
+ if (currentValue != defaultValue) {
+ return currentValue;
+ }
+ int transitionId = getWindowStyle().getResourceId(id, -1);
+ Transition transition = defaultValue;
+ if (transitionId != -1 && transitionId != R.transition.no_transition) {
+ TransitionInflater inflater = TransitionInflater.from(getContext());
+ transition = inflater.inflateTransition(transitionId);
+ if (transition instanceof TransitionSet &&
+ ((TransitionSet)transition).getTransitionCount() == 0) {
+ transition = null;
+ }
+ }
+ return transition;
+ }
+
+ private Drawable loadImageURI(Uri uri) {
+ try {
+ return Drawable.createFromStream(
+ getContext().getContentResolver().openInputStream(uri), null);
+ } catch (Exception e) {
+ Log.w(TAG, "Unable to open content: " + uri);
+ }
+ return null;
+ }
+
+ private DrawableFeatureState getDrawableState(int featureId, boolean required) {
+ if ((getFeatures() & (1 << featureId)) == 0) {
+ if (!required) {
+ return null;
+ }
+ throw new RuntimeException("The feature has not been requested");
+ }
+
+ DrawableFeatureState[] ar;
+ if ((ar = mDrawables) == null || ar.length <= featureId) {
+ DrawableFeatureState[] nar = new DrawableFeatureState[featureId + 1];
+ if (ar != null) {
+ System.arraycopy(ar, 0, nar, 0, ar.length);
+ }
+ mDrawables = ar = nar;
+ }
+
+ DrawableFeatureState st = ar[featureId];
+ if (st == null) {
+ ar[featureId] = st = new DrawableFeatureState(featureId);
+ }
+ return st;
+ }
+
+ /**
+ * Gets a panel's state based on its feature ID.
+ *
+ * @param featureId The feature ID of the panel.
+ * @param required Whether the panel is required (if it is required and it
+ * isn't in our features, this throws an exception).
+ * @return The panel state.
+ */
+ private PanelFeatureState getPanelState(int featureId, boolean required) {
+ return getPanelState(featureId, required, null);
+ }
+
+ /**
+ * Gets a panel's state based on its feature ID.
+ *
+ * @param featureId The feature ID of the panel.
+ * @param required Whether the panel is required (if it is required and it
+ * isn't in our features, this throws an exception).
+ * @param convertPanelState Optional: If the panel state does not exist, use
+ * this as the panel state.
+ * @return The panel state.
+ */
+ private PanelFeatureState getPanelState(int featureId, boolean required,
+ PanelFeatureState convertPanelState) {
+ if ((getFeatures() & (1 << featureId)) == 0) {
+ if (!required) {
+ return null;
+ }
+ throw new RuntimeException("The feature has not been requested");
+ }
+
+ PanelFeatureState[] ar;
+ if ((ar = mPanels) == null || ar.length <= featureId) {
+ PanelFeatureState[] nar = new PanelFeatureState[featureId + 1];
+ if (ar != null) {
+ System.arraycopy(ar, 0, nar, 0, ar.length);
+ }
+ mPanels = ar = nar;
+ }
+
+ PanelFeatureState st = ar[featureId];
+ if (st == null) {
+ ar[featureId] = st = (convertPanelState != null)
+ ? convertPanelState
+ : new PanelFeatureState(featureId);
+ }
+ return st;
+ }
+
+ @Override
+ public final void setChildDrawable(int featureId, Drawable drawable) {
+ DrawableFeatureState st = getDrawableState(featureId, true);
+ st.child = drawable;
+ updateDrawable(featureId, st, false);
+ }
+
+ @Override
+ public final void setChildInt(int featureId, int value) {
+ updateInt(featureId, value, false);
+ }
+
+ @Override
+ public boolean isShortcutKey(int keyCode, KeyEvent event) {
+ PanelFeatureState st = getPanelState(FEATURE_OPTIONS_PANEL, false);
+ return st != null && st.menu != null && st.menu.isShortcutKey(keyCode, event);
+ }
+
+ private void updateDrawable(int featureId, DrawableFeatureState st, boolean fromResume) {
+ // Do nothing if the decor is not yet installed... an update will
+ // need to be forced when we eventually become active.
+ if (mContentParent == null) {
+ return;
+ }
+
+ final int featureMask = 1 << featureId;
+
+ if ((getFeatures() & featureMask) == 0 && !fromResume) {
+ return;
+ }
+
+ Drawable drawable = null;
+ if (st != null) {
+ drawable = st.child;
+ if (drawable == null)
+ drawable = st.local;
+ if (drawable == null)
+ drawable = st.def;
+ }
+ if ((getLocalFeatures() & featureMask) == 0) {
+ if (getContainer() != null) {
+ if (isActive() || fromResume) {
+ getContainer().setChildDrawable(featureId, drawable);
+ }
+ }
+ } else if (st != null && (st.cur != drawable || st.curAlpha != st.alpha)) {
+ // System.out.println("Drawable changed: old=" + st.cur
+ // + ", new=" + drawable);
+ st.cur = drawable;
+ st.curAlpha = st.alpha;
+ onDrawableChanged(featureId, drawable, st.alpha);
+ }
+ }
+
+ private void updateInt(int featureId, int value, boolean fromResume) {
+
+ // Do nothing if the decor is not yet installed... an update will
+ // need to be forced when we eventually become active.
+ if (mContentParent == null) {
+ return;
+ }
+
+ final int featureMask = 1 << featureId;
+
+ if ((getFeatures() & featureMask) == 0 && !fromResume) {
+ return;
+ }
+
+ if ((getLocalFeatures() & featureMask) == 0) {
+ if (getContainer() != null) {
+ getContainer().setChildInt(featureId, value);
+ }
+ } else {
+ onIntChanged(featureId, value);
+ }
+ }
+
+ private ImageView getLeftIconView() {
+ if (mLeftIconView != null) {
+ return mLeftIconView;
+ }
+ if (mContentParent == null) {
+ installDecor();
+ }
+ return (mLeftIconView = (ImageView)findViewById(R.id.left_icon));
+ }
+
+ @Override
+ protected void dispatchWindowAttributesChanged(WindowManager.LayoutParams attrs) {
+ super.dispatchWindowAttributesChanged(attrs);
+ if (mDecor != null) {
+ mDecor.updateColorViews(null /* insets */, true /* animate */);
+ }
+ }
+
+ private ProgressBar getCircularProgressBar(boolean shouldInstallDecor) {
+ if (mCircularProgressBar != null) {
+ return mCircularProgressBar;
+ }
+ if (mContentParent == null && shouldInstallDecor) {
+ installDecor();
+ }
+ mCircularProgressBar = (ProgressBar) findViewById(R.id.progress_circular);
+ if (mCircularProgressBar != null) {
+ mCircularProgressBar.setVisibility(View.INVISIBLE);
+ }
+ return mCircularProgressBar;
+ }
+
+ private ProgressBar getHorizontalProgressBar(boolean shouldInstallDecor) {
+ if (mHorizontalProgressBar != null) {
+ return mHorizontalProgressBar;
+ }
+ if (mContentParent == null && shouldInstallDecor) {
+ installDecor();
+ }
+ mHorizontalProgressBar = (ProgressBar) findViewById(R.id.progress_horizontal);
+ if (mHorizontalProgressBar != null) {
+ mHorizontalProgressBar.setVisibility(View.INVISIBLE);
+ }
+ return mHorizontalProgressBar;
+ }
+
+ private ImageView getRightIconView() {
+ if (mRightIconView != null) {
+ return mRightIconView;
+ }
+ if (mContentParent == null) {
+ installDecor();
+ }
+ return (mRightIconView = (ImageView)findViewById(R.id.right_icon));
+ }
+
+ private void registerSwipeCallbacks() {
+ SwipeDismissLayout swipeDismiss =
+ (SwipeDismissLayout) findViewById(R.id.content);
+ swipeDismiss.setOnDismissedListener(new SwipeDismissLayout.OnDismissedListener() {
+ @Override
+ public void onDismissed(SwipeDismissLayout layout) {
+ dispatchOnWindowDismissed();
+ }
+ });
+ swipeDismiss.setOnSwipeProgressChangedListener(
+ new SwipeDismissLayout.OnSwipeProgressChangedListener() {
+ private static final float ALPHA_DECREASE = 0.5f;
+ private boolean mIsTranslucent = false;
+ @Override
+ public void onSwipeProgressChanged(
+ SwipeDismissLayout layout, float progress, float translate) {
+ WindowManager.LayoutParams newParams = getAttributes();
+ newParams.x = (int) translate;
+ newParams.alpha = 1 - (progress * ALPHA_DECREASE);
+ setAttributes(newParams);
+
+ int flags = 0;
+ if (newParams.x == 0) {
+ flags = FLAG_FULLSCREEN;
+ } else {
+ flags = FLAG_LAYOUT_NO_LIMITS;
+ }
+ setFlags(flags, FLAG_FULLSCREEN | FLAG_LAYOUT_NO_LIMITS);
+ }
+
+ @Override
+ public void onSwipeCancelled(SwipeDismissLayout layout) {
+ WindowManager.LayoutParams newParams = getAttributes();
+ newParams.x = 0;
+ newParams.alpha = 1;
+ setAttributes(newParams);
+ setFlags(FLAG_FULLSCREEN, FLAG_FULLSCREEN | FLAG_LAYOUT_NO_LIMITS);
+ }
+ });
+ }
+
+ /**
+ * Helper method for calling the {@link Callback#onPanelClosed(int, Menu)}
+ * callback. This method will grab whatever extra state is needed for the
+ * callback that isn't given in the parameters. If the panel is not open,
+ * this will not perform the callback.
+ *
+ * @param featureId Feature ID of the panel that was closed. Must be given.
+ * @param panel Panel that was closed. Optional but useful if there is no
+ * menu given.
+ * @param menu The menu that was closed. Optional, but give if you have.
+ */
+ private void callOnPanelClosed(int featureId, PanelFeatureState panel, Menu menu) {
+ final Callback cb = getCallback();
+ if (cb == null)
+ return;
+
+ // Try to get a menu
+ if (menu == null) {
+ // Need a panel to grab the menu, so try to get that
+ if (panel == null) {
+ if ((featureId >= 0) && (featureId < mPanels.length)) {
+ panel = mPanels[featureId];
+ }
+ }
+
+ if (panel != null) {
+ // menu still may be null, which is okay--we tried our best
+ menu = panel.menu;
+ }
+ }
+
+ // If the panel is not open, do not callback
+ if ((panel != null) && (!panel.isOpen))
+ return;
+
+ if (!isDestroyed()) {
+ cb.onPanelClosed(featureId, menu);
+ }
+ }
+
+ /**
+ * Helper method for adding launch-search to most applications. Opens the
+ * search window using default settings.
+ *
+ * @return true if search window opened
+ */
+ private boolean launchDefaultSearch() {
+ boolean result;
+ final Callback cb = getCallback();
+ if (cb == null || isDestroyed()) {
+ result = false;
+ } else {
+ sendCloseSystemWindows("search");
+ result = cb.onSearchRequested();
+ }
+ if (!result && (getContext().getResources().getConfiguration().uiMode
+ & Configuration.UI_MODE_TYPE_MASK) == Configuration.UI_MODE_TYPE_TELEVISION) {
+ // On TVs, if the app doesn't implement search, we want to launch assist.
+ return ((SearchManager)getContext().getSystemService(Context.SEARCH_SERVICE))
+ .launchAssistAction(0, null, UserHandle.myUserId());
+ }
+ return result;
+ }
+
+ @Override
+ public void setVolumeControlStream(int streamType) {
+ mVolumeControlStreamType = streamType;
+ }
+
+ @Override
+ public int getVolumeControlStream() {
+ return mVolumeControlStreamType;
+ }
+
+ @Override
+ public void setMediaController(MediaController controller) {
+ mMediaController = controller;
+ }
+
+ @Override
+ public MediaController getMediaController() {
+ return mMediaController;
+ }
+
+ private boolean isTranslucent() {
+ TypedArray a = getWindowStyle();
+ return a.getBoolean(a.getResourceId(
+ R.styleable.Window_windowIsTranslucent, 0), false);
+ }
+
+ @Override
+ public void setEnterTransition(Transition enterTransition) {
+ mEnterTransition = enterTransition;
+ }
+
+ @Override
+ public void setReturnTransition(Transition transition) {
+ mReturnTransition = transition;
+ }
+
+ @Override
+ public void setExitTransition(Transition exitTransition) {
+ mExitTransition = exitTransition;
+ }
+
+ @Override
+ public void setReenterTransition(Transition transition) {
+ mReenterTransition = transition;
+ }
+
+ @Override
+ public void setSharedElementEnterTransition(Transition sharedElementEnterTransition) {
+ mSharedElementEnterTransition = sharedElementEnterTransition;
+ }
+
+ @Override
+ public void setSharedElementReturnTransition(Transition transition) {
+ mSharedElementReturnTransition = transition;
+ }
+
+ @Override
+ public void setSharedElementExitTransition(Transition sharedElementExitTransition) {
+ mSharedElementExitTransition = sharedElementExitTransition;
+ }
+
+ @Override
+ public void setSharedElementReenterTransition(Transition transition) {
+ mSharedElementReenterTransition = transition;
+ }
+
+ @Override
+ public Transition getEnterTransition() {
+ return mEnterTransition;
+ }
+
+ @Override
+ public Transition getReturnTransition() {
+ return mReturnTransition == USE_DEFAULT_TRANSITION ? getEnterTransition()
+ : mReturnTransition;
+ }
+
+ @Override
+ public Transition getExitTransition() {
+ return mExitTransition;
+ }
+
+ @Override
+ public Transition getReenterTransition() {
+ return mReenterTransition == USE_DEFAULT_TRANSITION ? getExitTransition()
+ : mReenterTransition;
+ }
+
+ @Override
+ public Transition getSharedElementEnterTransition() {
+ return mSharedElementEnterTransition;
+ }
+
+ @Override
+ public Transition getSharedElementReturnTransition() {
+ return mSharedElementReturnTransition == USE_DEFAULT_TRANSITION
+ ? getSharedElementEnterTransition() : mSharedElementReturnTransition;
+ }
+
+ @Override
+ public Transition getSharedElementExitTransition() {
+ return mSharedElementExitTransition;
+ }
+
+ @Override
+ public Transition getSharedElementReenterTransition() {
+ return mSharedElementReenterTransition == USE_DEFAULT_TRANSITION
+ ? getSharedElementExitTransition() : mSharedElementReenterTransition;
+ }
+
+ @Override
+ public void setAllowEnterTransitionOverlap(boolean allow) {
+ mAllowEnterTransitionOverlap = allow;
+ }
+
+ @Override
+ public boolean getAllowEnterTransitionOverlap() {
+ return (mAllowEnterTransitionOverlap == null) ? true : mAllowEnterTransitionOverlap;
+ }
+
+ @Override
+ public void setAllowReturnTransitionOverlap(boolean allowExitTransitionOverlap) {
+ mAllowReturnTransitionOverlap = allowExitTransitionOverlap;
+ }
+
+ @Override
+ public boolean getAllowReturnTransitionOverlap() {
+ return (mAllowReturnTransitionOverlap == null) ? true : mAllowReturnTransitionOverlap;
+ }
+
+ @Override
+ public long getTransitionBackgroundFadeDuration() {
+ return (mBackgroundFadeDurationMillis < 0) ? DEFAULT_BACKGROUND_FADE_DURATION_MS
+ : mBackgroundFadeDurationMillis;
+ }
+
+ @Override
+ public void setTransitionBackgroundFadeDuration(long fadeDurationMillis) {
+ if (fadeDurationMillis < 0) {
+ throw new IllegalArgumentException("negative durations are not allowed");
+ }
+ mBackgroundFadeDurationMillis = fadeDurationMillis;
+ }
+
+ @Override
+ public void setSharedElementsUseOverlay(boolean sharedElementsUseOverlay) {
+ mSharedElementsUseOverlay = sharedElementsUseOverlay;
+ }
+
+ @Override
+ public boolean getSharedElementsUseOverlay() {
+ return (mSharedElementsUseOverlay == null) ? true : mSharedElementsUseOverlay;
+ }
+
+ private static final class DrawableFeatureState {
+ DrawableFeatureState(int _featureId) {
+ featureId = _featureId;
+ }
+
+ final int featureId;
+
+ int resid;
+
+ Uri uri;
+
+ Drawable local;
+
+ Drawable child;
+
+ Drawable def;
+
+ Drawable cur;
+
+ int alpha = 255;
+
+ int curAlpha = 255;
+ }
+
+ private static final class PanelFeatureState {
+
+ /** Feature ID for this panel. */
+ int featureId;
+
+ // Information pulled from the style for this panel.
+
+ int background;
+
+ /** The background when the panel spans the entire available width. */
+ int fullBackground;
+
+ int gravity;
+
+ int x;
+
+ int y;
+
+ int windowAnimations;
+
+ /** Dynamic state of the panel. */
+ DecorView decorView;
+
+ /** The panel that was returned by onCreatePanelView(). */
+ View createdPanelView;
+
+ /** The panel that we are actually showing. */
+ View shownPanelView;
+
+ /** Use {@link #setMenu} to set this. */
+ MenuBuilder menu;
+
+ IconMenuPresenter iconMenuPresenter;
+ ListMenuPresenter listMenuPresenter;
+
+ /** true if this menu will show in single-list compact mode */
+ boolean isCompact;
+
+ /** Theme resource ID for list elements of the panel menu */
+ int listPresenterTheme;
+
+ /**
+ * Whether the panel has been prepared (see
+ * {@link PhoneWindow#preparePanel}).
+ */
+ boolean isPrepared;
+
+ /**
+ * Whether an item's action has been performed. This happens in obvious
+ * scenarios (user clicks on menu item), but can also happen with
+ * chording menu+(shortcut key).
+ */
+ boolean isHandled;
+
+ boolean isOpen;
+
+ /**
+ * True if the menu is in expanded mode, false if the menu is in icon
+ * mode
+ */
+ boolean isInExpandedMode;
+
+ public boolean qwertyMode;
+
+ boolean refreshDecorView;
+
+ boolean refreshMenuContent;
+
+ boolean wasLastOpen;
+
+ boolean wasLastExpanded;
+
+ /**
+ * Contains the state of the menu when told to freeze.
+ */
+ Bundle frozenMenuState;
+
+ /**
+ * Contains the state of associated action views when told to freeze.
+ * These are saved across invalidations.
+ */
+ Bundle frozenActionViewState;
+
+ PanelFeatureState(int featureId) {
+ this.featureId = featureId;
+
+ refreshDecorView = false;
+ }
+
+ public boolean isInListMode() {
+ return isInExpandedMode || isCompact;
+ }
+
+ public boolean hasPanelItems() {
+ if (shownPanelView == null) return false;
+ if (createdPanelView != null) return true;
+
+ if (isCompact || isInExpandedMode) {
+ return listMenuPresenter.getAdapter().getCount() > 0;
+ } else {
+ return ((ViewGroup) shownPanelView).getChildCount() > 0;
+ }
+ }
+
+ /**
+ * Unregister and free attached MenuPresenters. They will be recreated as needed.
+ */
+ public void clearMenuPresenters() {
+ if (menu != null) {
+ menu.removeMenuPresenter(iconMenuPresenter);
+ menu.removeMenuPresenter(listMenuPresenter);
+ }
+ iconMenuPresenter = null;
+ listMenuPresenter = null;
+ }
+
+ void setStyle(Context context) {
+ TypedArray a = context.obtainStyledAttributes(R.styleable.Theme);
+ background = a.getResourceId(
+ R.styleable.Theme_panelBackground, 0);
+ fullBackground = a.getResourceId(
+ R.styleable.Theme_panelFullBackground, 0);
+ windowAnimations = a.getResourceId(
+ R.styleable.Theme_windowAnimationStyle, 0);
+ isCompact = a.getBoolean(
+ R.styleable.Theme_panelMenuIsCompact, false);
+ listPresenterTheme = a.getResourceId(
+ R.styleable.Theme_panelMenuListTheme,
+ R.style.Theme_ExpandedMenu);
+ a.recycle();
+ }
+
+ void setMenu(MenuBuilder menu) {
+ if (menu == this.menu) return;
+
+ if (this.menu != null) {
+ this.menu.removeMenuPresenter(iconMenuPresenter);
+ this.menu.removeMenuPresenter(listMenuPresenter);
+ }
+ this.menu = menu;
+ if (menu != null) {
+ if (iconMenuPresenter != null) menu.addMenuPresenter(iconMenuPresenter);
+ if (listMenuPresenter != null) menu.addMenuPresenter(listMenuPresenter);
+ }
+ }
+
+ MenuView getListMenuView(Context context, MenuPresenter.Callback cb) {
+ if (menu == null) return null;
+
+ if (!isCompact) {
+ getIconMenuView(context, cb); // Need this initialized to know where our offset goes
+ }
+
+ if (listMenuPresenter == null) {
+ listMenuPresenter = new ListMenuPresenter(
+ R.layout.list_menu_item_layout, listPresenterTheme);
+ listMenuPresenter.setCallback(cb);
+ listMenuPresenter.setId(R.id.list_menu_presenter);
+ menu.addMenuPresenter(listMenuPresenter);
+ }
+
+ if (iconMenuPresenter != null) {
+ listMenuPresenter.setItemIndexOffset(
+ iconMenuPresenter.getNumActualItemsShown());
+ }
+ MenuView result = listMenuPresenter.getMenuView(decorView);
+
+ return result;
+ }
+
+ MenuView getIconMenuView(Context context, MenuPresenter.Callback cb) {
+ if (menu == null) return null;
+
+ if (iconMenuPresenter == null) {
+ iconMenuPresenter = new IconMenuPresenter(context);
+ iconMenuPresenter.setCallback(cb);
+ iconMenuPresenter.setId(R.id.icon_menu_presenter);
+ menu.addMenuPresenter(iconMenuPresenter);
+ }
+
+ MenuView result = iconMenuPresenter.getMenuView(decorView);
+
+ return result;
+ }
+
+ Parcelable onSaveInstanceState() {
+ SavedState savedState = new SavedState();
+ savedState.featureId = featureId;
+ savedState.isOpen = isOpen;
+ savedState.isInExpandedMode = isInExpandedMode;
+
+ if (menu != null) {
+ savedState.menuState = new Bundle();
+ menu.savePresenterStates(savedState.menuState);
+ }
+
+ return savedState;
+ }
+
+ void onRestoreInstanceState(Parcelable state) {
+ SavedState savedState = (SavedState) state;
+ featureId = savedState.featureId;
+ wasLastOpen = savedState.isOpen;
+ wasLastExpanded = savedState.isInExpandedMode;
+ frozenMenuState = savedState.menuState;
+
+ /*
+ * A LocalActivityManager keeps the same instance of this class around.
+ * The first time the menu is being shown after restoring, the
+ * Activity.onCreateOptionsMenu should be called. But, if it is the
+ * same instance then menu != null and we won't call that method.
+ * We clear any cached views here. The caller should invalidatePanelMenu.
+ */
+ createdPanelView = null;
+ shownPanelView = null;
+ decorView = null;
+ }
+
+ void applyFrozenState() {
+ if (menu != null && frozenMenuState != null) {
+ menu.restorePresenterStates(frozenMenuState);
+ frozenMenuState = null;
+ }
+ }
+
+ private static class SavedState implements Parcelable {
+ int featureId;
+ boolean isOpen;
+ boolean isInExpandedMode;
+ Bundle menuState;
+
+ public int describeContents() {
+ return 0;
+ }
+
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(featureId);
+ dest.writeInt(isOpen ? 1 : 0);
+ dest.writeInt(isInExpandedMode ? 1 : 0);
+
+ if (isOpen) {
+ dest.writeBundle(menuState);
+ }
+ }
+
+ private static SavedState readFromParcel(Parcel source) {
+ SavedState savedState = new SavedState();
+ savedState.featureId = source.readInt();
+ savedState.isOpen = source.readInt() == 1;
+ savedState.isInExpandedMode = source.readInt() == 1;
+
+ if (savedState.isOpen) {
+ savedState.menuState = source.readBundle();
+ }
+
+ return savedState;
+ }
+
+ public static final Parcelable.Creator<SavedState> CREATOR
+ = new Parcelable.Creator<SavedState>() {
+ public SavedState createFromParcel(Parcel in) {
+ return readFromParcel(in);
+ }
+
+ public SavedState[] newArray(int size) {
+ return new SavedState[size];
+ }
+ };
+ }
+
+ }
+
+ static class RotationWatcher extends IRotationWatcher.Stub {
+ private Handler mHandler;
+ private final Runnable mRotationChanged = new Runnable() {
+ public void run() {
+ dispatchRotationChanged();
+ }
+ };
+ private final ArrayList<WeakReference<PhoneWindow>> mWindows =
+ new ArrayList<WeakReference<PhoneWindow>>();
+ private boolean mIsWatching;
+
+ @Override
+ public void onRotationChanged(int rotation) throws RemoteException {
+ mHandler.post(mRotationChanged);
+ }
+
+ public void addWindow(PhoneWindow phoneWindow) {
+ synchronized (mWindows) {
+ if (!mIsWatching) {
+ try {
+ WindowManagerHolder.sWindowManager.watchRotation(this);
+ mHandler = new Handler();
+ mIsWatching = true;
+ } catch (RemoteException ex) {
+ Log.e(TAG, "Couldn't start watching for device rotation", ex);
+ }
+ }
+ mWindows.add(new WeakReference<PhoneWindow>(phoneWindow));
+ }
+ }
+
+ public void removeWindow(PhoneWindow phoneWindow) {
+ synchronized (mWindows) {
+ int i = 0;
+ while (i < mWindows.size()) {
+ final WeakReference<PhoneWindow> ref = mWindows.get(i);
+ final PhoneWindow win = ref.get();
+ if (win == null || win == phoneWindow) {
+ mWindows.remove(i);
+ } else {
+ i++;
+ }
+ }
+ }
+ }
+
+ void dispatchRotationChanged() {
+ synchronized (mWindows) {
+ int i = 0;
+ while (i < mWindows.size()) {
+ final WeakReference<PhoneWindow> ref = mWindows.get(i);
+ final PhoneWindow win = ref.get();
+ if (win != null) {
+ win.onOptionsPanelRotationChanged();
+ i++;
+ } else {
+ mWindows.remove(i);
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Simple implementation of MenuBuilder.Callback that:
+ * <li> Opens a submenu when selected.
+ * <li> Calls back to the callback's onMenuItemSelected when an item is
+ * selected.
+ */
+ private final class DialogMenuCallback implements MenuBuilder.Callback, MenuPresenter.Callback {
+ private int mFeatureId;
+ private MenuDialogHelper mSubMenuHelper;
+
+ public DialogMenuCallback(int featureId) {
+ mFeatureId = featureId;
+ }
+
+ public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) {
+ if (menu.getRootMenu() != menu) {
+ onCloseSubMenu(menu);
+ }
+
+ if (allMenusAreClosing) {
+ Callback callback = getCallback();
+ if (callback != null && !isDestroyed()) {
+ callback.onPanelClosed(mFeatureId, menu);
+ }
+
+ if (menu == mContextMenu) {
+ dismissContextMenu();
+ }
+
+ // Dismiss the submenu, if it is showing
+ if (mSubMenuHelper != null) {
+ mSubMenuHelper.dismiss();
+ mSubMenuHelper = null;
+ }
+ }
+ }
+
+ public void onCloseSubMenu(MenuBuilder menu) {
+ Callback callback = getCallback();
+ if (callback != null && !isDestroyed()) {
+ callback.onPanelClosed(mFeatureId, menu.getRootMenu());
+ }
+ }
+
+ public boolean onMenuItemSelected(MenuBuilder menu, MenuItem item) {
+ Callback callback = getCallback();
+ return (callback != null && !isDestroyed())
+ && callback.onMenuItemSelected(mFeatureId, item);
+ }
+
+ public void onMenuModeChange(MenuBuilder menu) {
+ }
+
+ public boolean onOpenSubMenu(MenuBuilder subMenu) {
+ if (subMenu == null) return false;
+
+ // Set a simple callback for the submenu
+ subMenu.setCallback(this);
+
+ // The window manager will give us a valid window token
+ mSubMenuHelper = new MenuDialogHelper(subMenu);
+ mSubMenuHelper.show(null);
+
+ return true;
+ }
+ }
+
+ private static class ColorViewState {
+ View view = null;
+ int targetVisibility = View.INVISIBLE;
+
+ final int id;
+ final int systemUiHideFlag;
+ final int translucentFlag;
+ final int verticalGravity;
+ final String transitionName;
+ final int hideWindowFlag;
+
+ ColorViewState(int systemUiHideFlag,
+ int translucentFlag, int verticalGravity,
+ String transitionName, int id, int hideWindowFlag) {
+ this.id = id;
+ this.systemUiHideFlag = systemUiHideFlag;
+ this.translucentFlag = translucentFlag;
+ this.verticalGravity = verticalGravity;
+ this.transitionName = transitionName;
+ this.hideWindowFlag = hideWindowFlag;
+ }
+ }
+
+ void sendCloseSystemWindows() {
+ sendCloseSystemWindows(getContext(), null);
+ }
+
+ void sendCloseSystemWindows(String reason) {
+ sendCloseSystemWindows(getContext(), reason);
+ }
+
+ public static void sendCloseSystemWindows(Context context, String reason) {
+ if (ActivityManagerNative.isSystemReady()) {
+ try {
+ ActivityManagerNative.getDefault().closeSystemDialogs(reason);
+ } catch (RemoteException e) {
+ }
+ }
+ }
+
+ @Override
+ public int getStatusBarColor() {
+ return mStatusBarColor;
+ }
+
+ @Override
+ public void setStatusBarColor(int color) {
+ mStatusBarColor = color;
+ mForcedStatusBarColor = true;
+ if (mDecor != null) {
+ mDecor.updateColorViews(null, false /* animate */);
+ }
+ }
+
+ @Override
+ public int getNavigationBarColor() {
+ return mNavigationBarColor;
+ }
+
+ @Override
+ public void setNavigationBarColor(int color) {
+ mNavigationBarColor = color;
+ mForcedNavigationBarColor = true;
+ if (mDecor != null) {
+ mDecor.updateColorViews(null, false /* animate */);
+ }
+ }
+}
diff --git a/core/java/android/view/RenderNode.java b/core/java/android/view/RenderNode.java
index 47f72a8..09eb486 100644
--- a/core/java/android/view/RenderNode.java
+++ b/core/java/android/view/RenderNode.java
@@ -305,7 +305,7 @@ public class RenderNode {
}
public boolean setLayerPaint(Paint paint) {
- return nSetLayerPaint(mNativeRenderNode, paint != null ? paint.mNativePaint : 0);
+ return nSetLayerPaint(mNativeRenderNode, paint != null ? paint.getNativeInstance() : 0);
}
public boolean setClipBounds(@Nullable Rect rect) {
diff --git a/core/java/android/view/Surface.java b/core/java/android/view/Surface.java
index 33ce517..83b8100 100644
--- a/core/java/android/view/Surface.java
+++ b/core/java/android/view/Surface.java
@@ -322,7 +322,6 @@ public class Surface implements Parcelable {
* @return A canvas for drawing into the surface.
*
* @throws IllegalStateException If the canvas cannot be locked.
- * @hide
*/
public Canvas lockHardwareCanvas() {
synchronized (mLock) {
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 3c05872..ad5d651 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -66,6 +66,7 @@ import android.util.LongSparseLongArray;
import android.util.Pools.SynchronizedPool;
import android.util.Property;
import android.util.SparseArray;
+import android.util.StateSet;
import android.util.SuperNotCalledException;
import android.util.TypedValue;
import android.view.ContextMenu.ContextMenuInfo;
@@ -439,7 +440,7 @@ import java.util.concurrent.atomic.AtomicInteger;
* </p>
*
* <p>
- * To intiate a layout, call {@link #requestLayout}. This method is typically
+ * To initiate a layout, call {@link #requestLayout}. This method is typically
* called by a view on itself when it believes that is can no longer fit within
* its current bounds.
* </p>
@@ -1438,140 +1439,87 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
*/
protected static final int[] PRESSED_ENABLED_FOCUSED_SELECTED_WINDOW_FOCUSED_STATE_SET;
- /**
- * The order here is very important to {@link #getDrawableState()}
- */
- private static final int[][] VIEW_STATE_SETS;
-
- static final int VIEW_STATE_WINDOW_FOCUSED = 1;
- static final int VIEW_STATE_SELECTED = 1 << 1;
- static final int VIEW_STATE_FOCUSED = 1 << 2;
- static final int VIEW_STATE_ENABLED = 1 << 3;
- static final int VIEW_STATE_PRESSED = 1 << 4;
- static final int VIEW_STATE_ACTIVATED = 1 << 5;
- static final int VIEW_STATE_ACCELERATED = 1 << 6;
- static final int VIEW_STATE_HOVERED = 1 << 7;
- static final int VIEW_STATE_DRAG_CAN_ACCEPT = 1 << 8;
- static final int VIEW_STATE_DRAG_HOVERED = 1 << 9;
-
- static final int[] VIEW_STATE_IDS = new int[] {
- R.attr.state_window_focused, VIEW_STATE_WINDOW_FOCUSED,
- R.attr.state_selected, VIEW_STATE_SELECTED,
- R.attr.state_focused, VIEW_STATE_FOCUSED,
- R.attr.state_enabled, VIEW_STATE_ENABLED,
- R.attr.state_pressed, VIEW_STATE_PRESSED,
- R.attr.state_activated, VIEW_STATE_ACTIVATED,
- R.attr.state_accelerated, VIEW_STATE_ACCELERATED,
- R.attr.state_hovered, VIEW_STATE_HOVERED,
- R.attr.state_drag_can_accept, VIEW_STATE_DRAG_CAN_ACCEPT,
- R.attr.state_drag_hovered, VIEW_STATE_DRAG_HOVERED
- };
-
static {
- if ((VIEW_STATE_IDS.length/2) != R.styleable.ViewDrawableStates.length) {
- throw new IllegalStateException(
- "VIEW_STATE_IDs array length does not match ViewDrawableStates style array");
- }
- int[] orderedIds = new int[VIEW_STATE_IDS.length];
- for (int i = 0; i < R.styleable.ViewDrawableStates.length; i++) {
- int viewState = R.styleable.ViewDrawableStates[i];
- for (int j = 0; j<VIEW_STATE_IDS.length; j += 2) {
- if (VIEW_STATE_IDS[j] == viewState) {
- orderedIds[i * 2] = viewState;
- orderedIds[i * 2 + 1] = VIEW_STATE_IDS[j + 1];
- }
- }
- }
- final int NUM_BITS = VIEW_STATE_IDS.length / 2;
- VIEW_STATE_SETS = new int[1 << NUM_BITS][];
- for (int i = 0; i < VIEW_STATE_SETS.length; i++) {
- int numBits = Integer.bitCount(i);
- int[] set = new int[numBits];
- int pos = 0;
- for (int j = 0; j < orderedIds.length; j += 2) {
- if ((i & orderedIds[j+1]) != 0) {
- set[pos++] = orderedIds[j];
- }
- }
- VIEW_STATE_SETS[i] = set;
- }
-
- EMPTY_STATE_SET = VIEW_STATE_SETS[0];
- WINDOW_FOCUSED_STATE_SET = VIEW_STATE_SETS[VIEW_STATE_WINDOW_FOCUSED];
- SELECTED_STATE_SET = VIEW_STATE_SETS[VIEW_STATE_SELECTED];
- SELECTED_WINDOW_FOCUSED_STATE_SET = VIEW_STATE_SETS[
- VIEW_STATE_WINDOW_FOCUSED | VIEW_STATE_SELECTED];
- FOCUSED_STATE_SET = VIEW_STATE_SETS[VIEW_STATE_FOCUSED];
- FOCUSED_WINDOW_FOCUSED_STATE_SET = VIEW_STATE_SETS[
- VIEW_STATE_WINDOW_FOCUSED | VIEW_STATE_FOCUSED];
- FOCUSED_SELECTED_STATE_SET = VIEW_STATE_SETS[
- VIEW_STATE_SELECTED | VIEW_STATE_FOCUSED];
- FOCUSED_SELECTED_WINDOW_FOCUSED_STATE_SET = VIEW_STATE_SETS[
- VIEW_STATE_WINDOW_FOCUSED | VIEW_STATE_SELECTED
- | VIEW_STATE_FOCUSED];
- ENABLED_STATE_SET = VIEW_STATE_SETS[VIEW_STATE_ENABLED];
- ENABLED_WINDOW_FOCUSED_STATE_SET = VIEW_STATE_SETS[
- VIEW_STATE_WINDOW_FOCUSED | VIEW_STATE_ENABLED];
- ENABLED_SELECTED_STATE_SET = VIEW_STATE_SETS[
- VIEW_STATE_SELECTED | VIEW_STATE_ENABLED];
- ENABLED_SELECTED_WINDOW_FOCUSED_STATE_SET = VIEW_STATE_SETS[
- VIEW_STATE_WINDOW_FOCUSED | VIEW_STATE_SELECTED
- | VIEW_STATE_ENABLED];
- ENABLED_FOCUSED_STATE_SET = VIEW_STATE_SETS[
- VIEW_STATE_FOCUSED | VIEW_STATE_ENABLED];
- ENABLED_FOCUSED_WINDOW_FOCUSED_STATE_SET = VIEW_STATE_SETS[
- VIEW_STATE_WINDOW_FOCUSED | VIEW_STATE_FOCUSED
- | VIEW_STATE_ENABLED];
- ENABLED_FOCUSED_SELECTED_STATE_SET = VIEW_STATE_SETS[
- VIEW_STATE_SELECTED | VIEW_STATE_FOCUSED
- | VIEW_STATE_ENABLED];
- ENABLED_FOCUSED_SELECTED_WINDOW_FOCUSED_STATE_SET = VIEW_STATE_SETS[
- VIEW_STATE_WINDOW_FOCUSED | VIEW_STATE_SELECTED
- | VIEW_STATE_FOCUSED| VIEW_STATE_ENABLED];
-
- PRESSED_STATE_SET = VIEW_STATE_SETS[VIEW_STATE_PRESSED];
- PRESSED_WINDOW_FOCUSED_STATE_SET = VIEW_STATE_SETS[
- VIEW_STATE_WINDOW_FOCUSED | VIEW_STATE_PRESSED];
- PRESSED_SELECTED_STATE_SET = VIEW_STATE_SETS[
- VIEW_STATE_SELECTED | VIEW_STATE_PRESSED];
- PRESSED_SELECTED_WINDOW_FOCUSED_STATE_SET = VIEW_STATE_SETS[
- VIEW_STATE_WINDOW_FOCUSED | VIEW_STATE_SELECTED
- | VIEW_STATE_PRESSED];
- PRESSED_FOCUSED_STATE_SET = VIEW_STATE_SETS[
- VIEW_STATE_FOCUSED | VIEW_STATE_PRESSED];
- PRESSED_FOCUSED_WINDOW_FOCUSED_STATE_SET = VIEW_STATE_SETS[
- VIEW_STATE_WINDOW_FOCUSED | VIEW_STATE_FOCUSED
- | VIEW_STATE_PRESSED];
- PRESSED_FOCUSED_SELECTED_STATE_SET = VIEW_STATE_SETS[
- VIEW_STATE_SELECTED | VIEW_STATE_FOCUSED
- | VIEW_STATE_PRESSED];
- PRESSED_FOCUSED_SELECTED_WINDOW_FOCUSED_STATE_SET = VIEW_STATE_SETS[
- VIEW_STATE_WINDOW_FOCUSED | VIEW_STATE_SELECTED
- | VIEW_STATE_FOCUSED | VIEW_STATE_PRESSED];
- PRESSED_ENABLED_STATE_SET = VIEW_STATE_SETS[
- VIEW_STATE_ENABLED | VIEW_STATE_PRESSED];
- PRESSED_ENABLED_WINDOW_FOCUSED_STATE_SET = VIEW_STATE_SETS[
- VIEW_STATE_WINDOW_FOCUSED | VIEW_STATE_ENABLED
- | VIEW_STATE_PRESSED];
- PRESSED_ENABLED_SELECTED_STATE_SET = VIEW_STATE_SETS[
- VIEW_STATE_SELECTED | VIEW_STATE_ENABLED
- | VIEW_STATE_PRESSED];
- PRESSED_ENABLED_SELECTED_WINDOW_FOCUSED_STATE_SET = VIEW_STATE_SETS[
- VIEW_STATE_WINDOW_FOCUSED | VIEW_STATE_SELECTED
- | VIEW_STATE_ENABLED | VIEW_STATE_PRESSED];
- PRESSED_ENABLED_FOCUSED_STATE_SET = VIEW_STATE_SETS[
- VIEW_STATE_FOCUSED | VIEW_STATE_ENABLED
- | VIEW_STATE_PRESSED];
- PRESSED_ENABLED_FOCUSED_WINDOW_FOCUSED_STATE_SET = VIEW_STATE_SETS[
- VIEW_STATE_WINDOW_FOCUSED | VIEW_STATE_FOCUSED
- | VIEW_STATE_ENABLED | VIEW_STATE_PRESSED];
- PRESSED_ENABLED_FOCUSED_SELECTED_STATE_SET = VIEW_STATE_SETS[
- VIEW_STATE_SELECTED | VIEW_STATE_FOCUSED
- | VIEW_STATE_ENABLED | VIEW_STATE_PRESSED];
- PRESSED_ENABLED_FOCUSED_SELECTED_WINDOW_FOCUSED_STATE_SET = VIEW_STATE_SETS[
- VIEW_STATE_WINDOW_FOCUSED | VIEW_STATE_SELECTED
- | VIEW_STATE_FOCUSED| VIEW_STATE_ENABLED
- | VIEW_STATE_PRESSED];
+ EMPTY_STATE_SET = StateSet.get(0);
+
+ WINDOW_FOCUSED_STATE_SET = StateSet.get(StateSet.VIEW_STATE_WINDOW_FOCUSED);
+
+ SELECTED_STATE_SET = StateSet.get(StateSet.VIEW_STATE_SELECTED);
+ SELECTED_WINDOW_FOCUSED_STATE_SET = StateSet.get(
+ StateSet.VIEW_STATE_WINDOW_FOCUSED | StateSet.VIEW_STATE_SELECTED);
+
+ FOCUSED_STATE_SET = StateSet.get(StateSet.VIEW_STATE_FOCUSED);
+ FOCUSED_WINDOW_FOCUSED_STATE_SET = StateSet.get(
+ StateSet.VIEW_STATE_WINDOW_FOCUSED | StateSet.VIEW_STATE_FOCUSED);
+ FOCUSED_SELECTED_STATE_SET = StateSet.get(
+ StateSet.VIEW_STATE_SELECTED | StateSet.VIEW_STATE_FOCUSED);
+ FOCUSED_SELECTED_WINDOW_FOCUSED_STATE_SET = StateSet.get(
+ StateSet.VIEW_STATE_WINDOW_FOCUSED | StateSet.VIEW_STATE_SELECTED
+ | StateSet.VIEW_STATE_FOCUSED);
+
+ ENABLED_STATE_SET = StateSet.get(StateSet.VIEW_STATE_ENABLED);
+ ENABLED_WINDOW_FOCUSED_STATE_SET = StateSet.get(
+ StateSet.VIEW_STATE_WINDOW_FOCUSED | StateSet.VIEW_STATE_ENABLED);
+ ENABLED_SELECTED_STATE_SET = StateSet.get(
+ StateSet.VIEW_STATE_SELECTED | StateSet.VIEW_STATE_ENABLED);
+ ENABLED_SELECTED_WINDOW_FOCUSED_STATE_SET = StateSet.get(
+ StateSet.VIEW_STATE_WINDOW_FOCUSED | StateSet.VIEW_STATE_SELECTED
+ | StateSet.VIEW_STATE_ENABLED);
+ ENABLED_FOCUSED_STATE_SET = StateSet.get(
+ StateSet.VIEW_STATE_FOCUSED | StateSet.VIEW_STATE_ENABLED);
+ ENABLED_FOCUSED_WINDOW_FOCUSED_STATE_SET = StateSet.get(
+ StateSet.VIEW_STATE_WINDOW_FOCUSED | StateSet.VIEW_STATE_FOCUSED
+ | StateSet.VIEW_STATE_ENABLED);
+ ENABLED_FOCUSED_SELECTED_STATE_SET = StateSet.get(
+ StateSet.VIEW_STATE_SELECTED | StateSet.VIEW_STATE_FOCUSED
+ | StateSet.VIEW_STATE_ENABLED);
+ ENABLED_FOCUSED_SELECTED_WINDOW_FOCUSED_STATE_SET = StateSet.get(
+ StateSet.VIEW_STATE_WINDOW_FOCUSED | StateSet.VIEW_STATE_SELECTED
+ | StateSet.VIEW_STATE_FOCUSED| StateSet.VIEW_STATE_ENABLED);
+
+ PRESSED_STATE_SET = StateSet.get(StateSet.VIEW_STATE_PRESSED);
+ PRESSED_WINDOW_FOCUSED_STATE_SET = StateSet.get(
+ StateSet.VIEW_STATE_WINDOW_FOCUSED | StateSet.VIEW_STATE_PRESSED);
+ PRESSED_SELECTED_STATE_SET = StateSet.get(
+ StateSet.VIEW_STATE_SELECTED | StateSet.VIEW_STATE_PRESSED);
+ PRESSED_SELECTED_WINDOW_FOCUSED_STATE_SET = StateSet.get(
+ StateSet.VIEW_STATE_WINDOW_FOCUSED | StateSet.VIEW_STATE_SELECTED
+ | StateSet.VIEW_STATE_PRESSED);
+ PRESSED_FOCUSED_STATE_SET = StateSet.get(
+ StateSet.VIEW_STATE_FOCUSED | StateSet.VIEW_STATE_PRESSED);
+ PRESSED_FOCUSED_WINDOW_FOCUSED_STATE_SET = StateSet.get(
+ StateSet.VIEW_STATE_WINDOW_FOCUSED | StateSet.VIEW_STATE_FOCUSED
+ | StateSet.VIEW_STATE_PRESSED);
+ PRESSED_FOCUSED_SELECTED_STATE_SET = StateSet.get(
+ StateSet.VIEW_STATE_SELECTED | StateSet.VIEW_STATE_FOCUSED
+ | StateSet.VIEW_STATE_PRESSED);
+ PRESSED_FOCUSED_SELECTED_WINDOW_FOCUSED_STATE_SET = StateSet.get(
+ StateSet.VIEW_STATE_WINDOW_FOCUSED | StateSet.VIEW_STATE_SELECTED
+ | StateSet.VIEW_STATE_FOCUSED | StateSet.VIEW_STATE_PRESSED);
+ PRESSED_ENABLED_STATE_SET = StateSet.get(
+ StateSet.VIEW_STATE_ENABLED | StateSet.VIEW_STATE_PRESSED);
+ PRESSED_ENABLED_WINDOW_FOCUSED_STATE_SET = StateSet.get(
+ StateSet.VIEW_STATE_WINDOW_FOCUSED | StateSet.VIEW_STATE_ENABLED
+ | StateSet.VIEW_STATE_PRESSED);
+ PRESSED_ENABLED_SELECTED_STATE_SET = StateSet.get(
+ StateSet.VIEW_STATE_SELECTED | StateSet.VIEW_STATE_ENABLED
+ | StateSet.VIEW_STATE_PRESSED);
+ PRESSED_ENABLED_SELECTED_WINDOW_FOCUSED_STATE_SET = StateSet.get(
+ StateSet.VIEW_STATE_WINDOW_FOCUSED | StateSet.VIEW_STATE_SELECTED
+ | StateSet.VIEW_STATE_ENABLED | StateSet.VIEW_STATE_PRESSED);
+ PRESSED_ENABLED_FOCUSED_STATE_SET = StateSet.get(
+ StateSet.VIEW_STATE_FOCUSED | StateSet.VIEW_STATE_ENABLED
+ | StateSet.VIEW_STATE_PRESSED);
+ PRESSED_ENABLED_FOCUSED_WINDOW_FOCUSED_STATE_SET = StateSet.get(
+ StateSet.VIEW_STATE_WINDOW_FOCUSED | StateSet.VIEW_STATE_FOCUSED
+ | StateSet.VIEW_STATE_ENABLED | StateSet.VIEW_STATE_PRESSED);
+ PRESSED_ENABLED_FOCUSED_SELECTED_STATE_SET = StateSet.get(
+ StateSet.VIEW_STATE_SELECTED | StateSet.VIEW_STATE_FOCUSED
+ | StateSet.VIEW_STATE_ENABLED | StateSet.VIEW_STATE_PRESSED);
+ PRESSED_ENABLED_FOCUSED_SELECTED_WINDOW_FOCUSED_STATE_SET = StateSet.get(
+ StateSet.VIEW_STATE_WINDOW_FOCUSED | StateSet.VIEW_STATE_SELECTED
+ | StateSet.VIEW_STATE_FOCUSED| StateSet.VIEW_STATE_ENABLED
+ | StateSet.VIEW_STATE_PRESSED);
}
/**
@@ -1977,7 +1925,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
static final int LAYOUT_DIRECTION_RESOLVED_DEFAULT = LAYOUT_DIRECTION_LTR;
/**
- * Text direction is inherited thru {@link ViewGroup}
+ * Text direction is inherited through {@link ViewGroup}
*/
public static final int TEXT_DIRECTION_INHERIT = 0;
@@ -2545,7 +2493,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
/**
* Flag for {@link #setSystemUiVisibility(int)}: View would like its window
- * to be layed out as if it has requested
+ * to be laid out as if it has requested
* {@link #SYSTEM_UI_FLAG_HIDE_NAVIGATION}, even if it currently hasn't. This
* allows it to avoid artifacts when switching in and out of that mode, at
* the expense that some of its user interface may be covered by screen
@@ -2557,7 +2505,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
/**
* Flag for {@link #setSystemUiVisibility(int)}: View would like its window
- * to be layed out as if it has requested
+ * to be laid out as if it has requested
* {@link #SYSTEM_UI_FLAG_FULLSCREEN}, even if it currently hasn't. This
* allows it to avoid artifacts when switching in and out of that mode, at
* the expense that some of its user interface may be covered by screen
@@ -2596,6 +2544,20 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
public static final int SYSTEM_UI_FLAG_IMMERSIVE_STICKY = 0x00001000;
/**
+ * Flag for {@link #setSystemUiVisibility(int)}: Requests the status bar to draw in a mode that
+ * is compatible with light status bar backgrounds.
+ *
+ * <p>For this to take effect, the window must request
+ * {@link android.view.WindowManager.LayoutParams#FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS
+ * FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS} but not
+ * {@link android.view.WindowManager.LayoutParams#FLAG_TRANSLUCENT_STATUS
+ * FLAG_TRANSLUCENT_STATUS}.
+ *
+ * @see android.R.attr#windowHasLightStatusBar
+ */
+ public static final int SYSTEM_UI_FLAG_LIGHT_STATUS_BAR = 0x00002000;
+
+ /**
* @deprecated Use {@link #SYSTEM_UI_FLAG_LOW_PROFILE} instead.
*/
public static final int STATUS_BAR_HIDDEN = SYSTEM_UI_FLAG_LOW_PROFILE;
@@ -3620,7 +3582,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* @param attrs The attributes of the XML tag that is inflating the view.
* @see #View(Context, AttributeSet, int)
*/
- public View(Context context, AttributeSet attrs) {
+ public View(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
@@ -3641,7 +3603,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* the view. Can be 0 to not look for defaults.
* @see #View(Context, AttributeSet)
*/
- public View(Context context, AttributeSet attrs, int defStyleAttr) {
+ public View(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
this(context, attrs, defStyleAttr, 0);
}
@@ -3678,7 +3640,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* to not look for defaults.
* @see #View(Context, AttributeSet, int)
*/
- public View(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ public View(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
this(context);
final TypedArray a = context.obtainStyledAttributes(
@@ -4508,6 +4470,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
if (scrollabilityCache.scrollBar == null) {
scrollabilityCache.scrollBar = new ScrollBarDrawable();
+ scrollabilityCache.scrollBar.setCallback(this);
+ scrollabilityCache.scrollBar.setState(getDrawableState());
}
final boolean fadeScrollbars = a.getBoolean(R.styleable.View_fadeScrollbars, true);
@@ -4619,11 +4583,18 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
}
/**
- * Register a callback to be invoked when the scroll position of this view
- * changed.
+ * Register a callback to be invoked when the scroll X or Y positions of
+ * this view change.
+ * <p>
+ * <b>Note:</b> Some views handle scrolling independently from View and may
+ * have their own separate listeners for scroll-type events. For example,
+ * {@link android.widget.ListView ListView} allows clients to register an
+ * {@link android.widget.ListView#setOnScrollListener(android.widget.AbsListView.OnScrollListener) AbsListView.OnScrollListener}
+ * to listen for changes in list scroll position.
*
- * @param l The callback that will run.
- * @hide Only used internally.
+ * @param l The listener to notify when the scroll X or Y position changes.
+ * @see android.view.View#getScrollX()
+ * @see android.view.View#getScrollY()
*/
public void setOnScrollChangeListener(OnScrollChangeListener l) {
getListenerInfo().mOnScrollChangeListener = l;
@@ -4719,7 +4690,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
*
* @see #setClickable(boolean)
*/
- public void setOnClickListener(OnClickListener l) {
+ public void setOnClickListener(@Nullable OnClickListener l) {
if (!isClickable()) {
setClickable(true);
}
@@ -4743,7 +4714,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
*
* @see #setLongClickable(boolean)
*/
- public void setOnLongClickListener(OnLongClickListener l) {
+ public void setOnLongClickListener(@Nullable OnLongClickListener l) {
if (!isLongClickable()) {
setLongClickable(true);
}
@@ -5123,7 +5094,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
}
/**
- * Returns true if this view has focus iteself, or is the ancestor of the
+ * Returns true if this view has focus itself, or is the ancestor of the
* view that has focus.
*
* @return True if this view has or contains focus, false otherwise.
@@ -5220,7 +5191,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* populate the text content of the event source including its descendants,
* and last calls
* {@link ViewParent#requestSendAccessibilityEvent(View, AccessibilityEvent)}
- * on its parent to resuest sending of the event to interested parties.
+ * on its parent to request sending of the event to interested parties.
* <p>
* If an {@link AccessibilityDelegate} has been specified via calling
* {@link #setAccessibilityDelegate(AccessibilityDelegate)} its
@@ -5270,8 +5241,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* @see #sendAccessibilityEvent(int)
*
* Note: Called from the default {@link AccessibilityDelegate}.
+ *
+ * @hide
*/
- void sendAccessibilityEventInternal(int eventType) {
+ public void sendAccessibilityEventInternal(int eventType) {
if (AccessibilityManager.getInstance(mContext).isEnabled()) {
sendAccessibilityEventUnchecked(AccessibilityEvent.obtain(eventType));
}
@@ -5304,8 +5277,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* @see #sendAccessibilityEventUnchecked(AccessibilityEvent)
*
* Note: Called from the default {@link AccessibilityDelegate}.
+ *
+ * @hide
*/
- void sendAccessibilityEventUncheckedInternal(AccessibilityEvent event) {
+ public void sendAccessibilityEventUncheckedInternal(AccessibilityEvent event) {
if (!isShown()) {
return;
}
@@ -5355,8 +5330,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* @see #dispatchPopulateAccessibilityEvent(AccessibilityEvent)
*
* Note: Called from the default {@link AccessibilityDelegate}.
+ *
+ * @hide
*/
- boolean dispatchPopulateAccessibilityEventInternal(AccessibilityEvent event) {
+ public boolean dispatchPopulateAccessibilityEventInternal(AccessibilityEvent event) {
onPopulateAccessibilityEvent(event);
return false;
}
@@ -5404,8 +5381,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* @see #onPopulateAccessibilityEvent(AccessibilityEvent)
*
* Note: Called from the default {@link AccessibilityDelegate}.
+ *
+ * @hide
*/
- void onPopulateAccessibilityEventInternal(AccessibilityEvent event) {
+ public void onPopulateAccessibilityEventInternal(AccessibilityEvent event) {
}
/**
@@ -5446,10 +5425,12 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* @see #onInitializeAccessibilityEvent(AccessibilityEvent)
*
* Note: Called from the default {@link AccessibilityDelegate}.
+ *
+ * @hide
*/
- void onInitializeAccessibilityEventInternal(AccessibilityEvent event) {
+ public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) {
event.setSource(this);
- event.setClassName(View.class.getName());
+ event.setClassName(getAccessibilityClassName());
event.setPackageName(getContext().getPackageName());
event.setEnabled(isEnabled());
event.setContentDescription(mContentDescription);
@@ -5502,8 +5483,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
/**
* @see #createAccessibilityNodeInfo()
+ *
+ * @hide
*/
- AccessibilityNodeInfo createAccessibilityNodeInfoInternal() {
+ public AccessibilityNodeInfo createAccessibilityNodeInfoInternal() {
AccessibilityNodeProvider provider = getAccessibilityNodeProvider();
if (provider != null) {
return provider.createAccessibilityNodeInfo(View.NO_ID);
@@ -5617,11 +5600,33 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
}
/**
+ * Return the class name of this object to be used for accessibility purposes.
+ * Subclasses should only override this if they are implementing something that
+ * should be seen as a completely new class of view when used by accessibility,
+ * unrelated to the class it is deriving from. This is used to fill in
+ * {@link AccessibilityNodeInfo#setClassName AccessibilityNodeInfo.setClassName}.
+ */
+ public CharSequence getAccessibilityClassName() {
+ return View.class.getName();
+ }
+
+ /**
+ * Called when assist data is being retrieved from a view as part of
+ * {@link android.app.Activity#onProvideAssistData Activity.onProvideAssistData}.
+ * @param data
+ * @param extras
+ */
+ public void onProvideAssistData(ViewAssistData data, Bundle extras) {
+ }
+
+ /**
* @see #onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo)
*
* Note: Called from the default {@link AccessibilityDelegate}.
+ *
+ * @hide
*/
- void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
+ public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
Rect bounds = mAttachInfo.mTmpInvalRect;
getDrawingRect(bounds);
@@ -5696,7 +5701,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
info.setVisibleToUser(isVisibleToUser());
info.setPackageName(mContext.getPackageName());
- info.setClassName(View.class.getName());
+ info.setClassName(getAccessibilityClassName());
info.setContentDescription(getContentDescription());
info.setEnabled(isEnabled());
@@ -6691,7 +6696,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
@RemotableViewMethod
public void setVisibility(@Visibility int visibility) {
setFlags(visibility, VISIBILITY_MASK);
- if (mBackground != null) mBackground.setVisible(visibility == VISIBLE, false);
}
/**
@@ -7394,8 +7398,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* including this view if it is focusable itself) to views. This method
* adds all focusable views regardless if we are in touch mode or
* only views focusable in touch mode if we are in touch mode or
- * only views that can take accessibility focus if accessibility is enabeld
- * depending on the focusable mode paramater.
+ * only views that can take accessibility focus if accessibility is enabled
+ * depending on the focusable mode parameter.
*
* @param views Focusable views found so far or null if all we are interested is
* the number of focusables.
@@ -7681,7 +7685,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
/**
* Call this to try to give focus to a specific view or to one of its descendants. This is a
- * special variant of {@link #requestFocus() } that will allow views that are not focuable in
+ * special variant of {@link #requestFocus() } that will allow views that are not focusable in
* touch mode to request focus when they are touched.
*
* @return Whether this view or one of its descendants actually took focus.
@@ -8083,7 +8087,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
*
* Note: Called from the default {@link AccessibilityDelegate}.
*
- * @hide Until we've refactored all accessibility delegation methods.
+ * @hide
*/
public boolean performAccessibilityActionInternal(int action, Bundle arguments) {
if (isNestedScrollingEnabled()
@@ -8732,20 +8736,28 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
}
/**
- * Called when the visibility of the view or an ancestor of the view is changed.
- * @param changedView The view whose visibility changed. Could be 'this' or
- * an ancestor view.
- * @param visibility The new visibility of changedView: {@link #VISIBLE},
- * {@link #INVISIBLE} or {@link #GONE}.
+ * Called when the visibility of the view or an ancestor of the view has
+ * changed.
+ *
+ * @param changedView The view whose visibility changed. May be
+ * {@code this} or an ancestor view.
+ * @param visibility The new visibility, one of {@link #VISIBLE},
+ * {@link #INVISIBLE} or {@link #GONE}.
*/
protected void onVisibilityChanged(@NonNull View changedView, @Visibility int visibility) {
- if (visibility == VISIBLE) {
+ final boolean visible = visibility == VISIBLE && getVisibility() == VISIBLE;
+ if (visible) {
if (mAttachInfo != null) {
initialAwakenScrollBars();
} else {
mPrivateFlags |= PFLAG_AWAKEN_SCROLL_BARS_ON_ATTACH;
}
}
+
+ final Drawable dr = mBackground;
+ if (dr != null && visible != dr.isVisible()) {
+ dr.setVisible(visible, false);
+ }
}
/**
@@ -8868,7 +8880,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* Called when the current configuration of the resources being used
* by the application have changed. You can use this to decide when
* to reload resources that can changed based on orientation and other
- * configuration characterstics. You only need to use this if you are
+ * configuration characteristics. You only need to use this if you are
* not relying on the normal {@link android.app.Activity} mechanism of
* recreating the activity instance upon a configuration change.
*
@@ -9875,9 +9887,15 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
/**
* Interface definition for a callback to be invoked when the scroll
- * position of a view changes.
+ * X or Y positions of a view change.
+ * <p>
+ * <b>Note:</b> Some views handle scrolling independently from View and may
+ * have their own separate listeners for scroll-type events. For example,
+ * {@link android.widget.ListView ListView} allows clients to register an
+ * {@link android.widget.ListView#setOnScrollListener(android.widget.AbsListView.OnScrollListener) AbsListView.OnScrollListener}
+ * to listen for changes in list scroll position.
*
- * @hide Only used internally.
+ * @see #setOnScrollChangeListener(View.OnScrollChangeListener)
*/
public interface OnScrollChangeListener {
/**
@@ -10537,7 +10555,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* <p>Note that if the view is backed by a
* {@link #setLayerType(int, android.graphics.Paint) layer} and is associated with a
* {@link #setLayerPaint(android.graphics.Paint) layer paint}, setting an alpha value less than
- * 1.0 will supercede the alpha of the layer paint.</p>
+ * 1.0 will supersede the alpha of the layer paint.</p>
*
* @param alpha The opacity of the view.
*
@@ -11585,7 +11603,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* </p>
*
* <p>
- * This method should be invoked everytime a subclass directly updates the
+ * This method should be invoked every time a subclass directly updates the
* scroll parameters.
* </p>
*
@@ -11624,7 +11642,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* </p>
*
* <p>
- * This method should be invoked everytime a subclass directly updates the
+ * This method should be invoked every time a subclass directly updates the
* scroll parameters.
* </p>
*
@@ -11632,7 +11650,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* should start; when the delay is 0, the animation starts
* immediately
*
- * @param invalidate Wheter this method should call invalidate
+ * @param invalidate Whether this method should call invalidate
*
* @return true if the animation is played, false otherwise
*
@@ -11652,6 +11670,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
if (scrollCache.scrollBar == null) {
scrollCache.scrollBar = new ScrollBarDrawable();
+ scrollCache.scrollBar.setCallback(this);
+ scrollCache.scrollBar.setState(getDrawableState());
}
if (isHorizontalScrollBarEnabled() || isVerticalScrollBarEnabled()) {
@@ -12537,7 +12557,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
/**
* Define whether scrollbars will fade when the view is not scrolling.
*
- * @param fadeScrollbars wheter to enable fading
+ * @param fadeScrollbars whether to enable fading
*
* @attr ref android.R.styleable#View_fadeScrollbars
*/
@@ -13389,7 +13409,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* after onDetachedFromWindow().
*
* If you override this you *MUST* call super.onDetachedFromWindowInternal()!
- * The super method should be called at the end of the overriden method to ensure
+ * The super method should be called at the end of the overridden method to ensure
* subclasses are destroyed first
*
* @hide
@@ -13790,7 +13810,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* <p>Note: if this view's parent addStateFromChildren property is enabled and this
* property is enabled, an exception will be thrown.</p>
*
- * <p>Note: if the child view uses and updates additionnal states which are unknown to the
+ * <p>Note: if the child view uses and updates additional states which are unknown to the
* parent, these states should not be affected by this method.</p>
*
* @param enabled True to enable duplication of the parent's drawable state, false
@@ -13831,7 +13851,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* </ul>
*
* <p>If this view has an alpha value set to < 1.0 by calling
- * {@link #setAlpha(float)}, the alpha value of the layer's paint is superceded
+ * {@link #setAlpha(float)}, the alpha value of the layer's paint is superseded
* by this view's alpha value.</p>
*
* <p>Refer to the documentation of {@link #LAYER_TYPE_NONE},
@@ -13899,7 +13919,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* </ul>
*
* <p>If this view has an alpha value set to < 1.0 by calling {@link #setAlpha(float)}, the
- * alpha value of the layer's paint is superceded by this view's alpha value.</p>
+ * alpha value of the layer's paint is superseded by this view's alpha value.</p>
*
* @param paint The paint used to compose the layer. This argument is optional
* and can be null. It is ignored when the layer type is
@@ -14803,10 +14823,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
void setDisplayListProperties(RenderNode renderNode) {
if (renderNode != null) {
renderNode.setHasOverlappingRendering(hasOverlappingRendering());
- if (mParent instanceof ViewGroup) {
- renderNode.setClipToBounds(
- (((ViewGroup) mParent).mGroupFlags & ViewGroup.FLAG_CLIP_CHILDREN) != 0);
- }
+ renderNode.setClipToBounds(mParent instanceof ViewGroup
+ && ((ViewGroup) mParent).getClipChildren());
+
float alpha = 1;
if (mParent instanceof ViewGroup && (((ViewGroup) mParent).mGroupFlags &
ViewGroup.FLAG_SUPPORT_STATIC_TRANSFORMATIONS) != 0) {
@@ -15141,7 +15160,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
}
} else {
mPrivateFlags &= ~PFLAG_DIRTY_MASK;
- ((HardwareCanvas) canvas).drawRenderNode(renderNode, null, flags);
+ ((HardwareCanvas) canvas).drawRenderNode(renderNode, flags);
}
}
} else if (cache != null) {
@@ -15964,7 +15983,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* @see #drawableStateChanged()
*/
protected boolean verifyDrawable(Drawable who) {
- return who == mBackground;
+ return who == mBackground || (mScrollCache != null && mScrollCache.scrollBar == who);
}
/**
@@ -15979,13 +15998,22 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* @see Drawable#setState(int[])
*/
protected void drawableStateChanged() {
+ final int[] state = getDrawableState();
+
final Drawable d = mBackground;
if (d != null && d.isStateful()) {
- d.setState(getDrawableState());
+ d.setState(state);
+ }
+
+ if (mScrollCache != null) {
+ final Drawable scrollBar = mScrollCache.scrollBar;
+ if (scrollBar != null && scrollBar.isStateful()) {
+ scrollBar.setState(state);
+ }
}
if (mStateListAnimator != null) {
- mStateListAnimator.setState(getDrawableState());
+ mStateListAnimator.setState(state);
}
}
@@ -16083,26 +16111,30 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
int privateFlags = mPrivateFlags;
int viewStateIndex = 0;
- if ((privateFlags & PFLAG_PRESSED) != 0) viewStateIndex |= VIEW_STATE_PRESSED;
- if ((mViewFlags & ENABLED_MASK) == ENABLED) viewStateIndex |= VIEW_STATE_ENABLED;
- if (isFocused()) viewStateIndex |= VIEW_STATE_FOCUSED;
- if ((privateFlags & PFLAG_SELECTED) != 0) viewStateIndex |= VIEW_STATE_SELECTED;
- if (hasWindowFocus()) viewStateIndex |= VIEW_STATE_WINDOW_FOCUSED;
- if ((privateFlags & PFLAG_ACTIVATED) != 0) viewStateIndex |= VIEW_STATE_ACTIVATED;
+ if ((privateFlags & PFLAG_PRESSED) != 0) viewStateIndex |= StateSet.VIEW_STATE_PRESSED;
+ if ((mViewFlags & ENABLED_MASK) == ENABLED) viewStateIndex |= StateSet.VIEW_STATE_ENABLED;
+ if (isFocused()) viewStateIndex |= StateSet.VIEW_STATE_FOCUSED;
+ if ((privateFlags & PFLAG_SELECTED) != 0) viewStateIndex |= StateSet.VIEW_STATE_SELECTED;
+ if (hasWindowFocus()) viewStateIndex |= StateSet.VIEW_STATE_WINDOW_FOCUSED;
+ if ((privateFlags & PFLAG_ACTIVATED) != 0) viewStateIndex |= StateSet.VIEW_STATE_ACTIVATED;
if (mAttachInfo != null && mAttachInfo.mHardwareAccelerationRequested &&
HardwareRenderer.isAvailable()) {
// This is set if HW acceleration is requested, even if the current
// process doesn't allow it. This is just to allow app preview
// windows to better match their app.
- viewStateIndex |= VIEW_STATE_ACCELERATED;
+ viewStateIndex |= StateSet.VIEW_STATE_ACCELERATED;
}
- if ((privateFlags & PFLAG_HOVERED) != 0) viewStateIndex |= VIEW_STATE_HOVERED;
+ if ((privateFlags & PFLAG_HOVERED) != 0) viewStateIndex |= StateSet.VIEW_STATE_HOVERED;
final int privateFlags2 = mPrivateFlags2;
- if ((privateFlags2 & PFLAG2_DRAG_CAN_ACCEPT) != 0) viewStateIndex |= VIEW_STATE_DRAG_CAN_ACCEPT;
- if ((privateFlags2 & PFLAG2_DRAG_HOVERED) != 0) viewStateIndex |= VIEW_STATE_DRAG_HOVERED;
+ if ((privateFlags2 & PFLAG2_DRAG_CAN_ACCEPT) != 0) {
+ viewStateIndex |= StateSet.VIEW_STATE_DRAG_CAN_ACCEPT;
+ }
+ if ((privateFlags2 & PFLAG2_DRAG_HOVERED) != 0) {
+ viewStateIndex |= StateSet.VIEW_STATE_DRAG_HOVERED;
+ }
- drawableState = VIEW_STATE_SETS[viewStateIndex];
+ drawableState = StateSet.get(viewStateIndex);
//noinspection ConstantIfStatement
if (false) {
@@ -16190,6 +16222,19 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
}
/**
+ * If the view has a ColorDrawable background, returns the color of that
+ * drawable.
+ *
+ * @return The color of the ColorDrawable background, if set, otherwise 0.
+ */
+ public int getBackgroundColor() {
+ if (mBackground instanceof ColorDrawable) {
+ return ((ColorDrawable) mBackground).getColor();
+ }
+ return 0;
+ }
+
+ /**
* Set the background to a given resource. The resource should refer to
* a Drawable object or 0 to remove the background.
* @param resid The identifier of the resource.
@@ -16652,8 +16697,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
}
/**
- * Return if the padding as been set thru relative values
- * {@link #setPaddingRelative(int, int, int, int)} or thru
+ * Return if the padding has been set through relative values
+ * {@link #setPaddingRelative(int, int, int, int)} or through
* @attr ref android.R.styleable#View_paddingStart or
* @attr ref android.R.styleable#View_paddingEnd
*
@@ -17064,6 +17109,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* @param id The id to search for.
* @return The view that has the given id in the hierarchy or null
*/
+ @Nullable
public final View findViewById(int id) {
if (id < 0) {
return null;
@@ -17277,7 +17323,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
*
* The specified key should be an id declared in the resources of the
* application to ensure it is unique (see the <a
- * href={@docRoot}guide/topics/resources/more-resources.html#Id">ID resource type</a>).
+ * href="{@docRoot}guide/topics/resources/more-resources.html#Id">ID resource type</a>).
* Keys identified as belonging to
* the Android framework or not associated with any package will cause
* an {@link IllegalArgumentException} to be thrown.
@@ -17575,7 +17621,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* <p>
* Measure the view and its content to determine the measured width and the
* measured height. This method is invoked by {@link #measure(int, int)} and
- * should be overriden by subclasses to provide accurate and efficient
+ * should be overridden by subclasses to provide accurate and efficient
* measurement of their contents.
* </p>
*
@@ -17688,37 +17734,40 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
/**
* Utility to reconcile a desired size and state, with constraints imposed
- * by a MeasureSpec. Will take the desired size, unless a different size
- * is imposed by the constraints. The returned value is a compound integer,
+ * by a MeasureSpec. Will take the desired size, unless a different size
+ * is imposed by the constraints. The returned value is a compound integer,
* with the resolved size in the {@link #MEASURED_SIZE_MASK} bits and
- * optionally the bit {@link #MEASURED_STATE_TOO_SMALL} set if the resulting
- * size is smaller than the size the view wants to be.
+ * optionally the bit {@link #MEASURED_STATE_TOO_SMALL} set if the
+ * resulting size is smaller than the size the view wants to be.
*
- * @param size How big the view wants to be
- * @param measureSpec Constraints imposed by the parent
+ * @param size How big the view wants to be.
+ * @param measureSpec Constraints imposed by the parent.
+ * @param childMeasuredState Size information bit mask for the view's
+ * children.
* @return Size information bit mask as defined by
- * {@link #MEASURED_SIZE_MASK} and {@link #MEASURED_STATE_TOO_SMALL}.
+ * {@link #MEASURED_SIZE_MASK} and
+ * {@link #MEASURED_STATE_TOO_SMALL}.
*/
public static int resolveSizeAndState(int size, int measureSpec, int childMeasuredState) {
- int result = size;
- int specMode = MeasureSpec.getMode(measureSpec);
- int specSize = MeasureSpec.getSize(measureSpec);
+ final int specMode = MeasureSpec.getMode(measureSpec);
+ final int specSize = MeasureSpec.getSize(measureSpec);
+ final int result;
switch (specMode) {
- case MeasureSpec.UNSPECIFIED:
- result = size;
- break;
- case MeasureSpec.AT_MOST:
- if (specSize < size) {
- result = specSize | MEASURED_STATE_TOO_SMALL;
- } else {
+ case MeasureSpec.AT_MOST:
+ if (specSize < size) {
+ result = specSize | MEASURED_STATE_TOO_SMALL;
+ } else {
+ result = size;
+ }
+ break;
+ case MeasureSpec.EXACTLY:
+ result = specSize;
+ break;
+ case MeasureSpec.UNSPECIFIED:
+ default:
result = size;
- }
- break;
- case MeasureSpec.EXACTLY:
- result = specSize;
- break;
}
- return result | (childMeasuredState&MEASURED_STATE_MASK);
+ return result | (childMeasuredState & MEASURED_STATE_MASK);
}
/**
@@ -18181,7 +18230,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* appearance as the given View. The default also positions the center of the drag shadow
* directly under the touch point. If no View is provided (the constructor with no parameters
* is used), and {@link #onProvideShadowMetrics(Point,Point) onProvideShadowMetrics()} and
- * {@link #onDrawShadow(Canvas) onDrawShadow()} are not overriden, then the
+ * {@link #onDrawShadow(Canvas) onDrawShadow()} are not overridden, then the
* default is an invisible drag shadow.
* <p>
* You are not required to use the View you provide to the constructor as the basis of the
diff --git a/core/java/android/view/ViewAssistData.java b/core/java/android/view/ViewAssistData.java
new file mode 100644
index 0000000..74436ea
--- /dev/null
+++ b/core/java/android/view/ViewAssistData.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2015 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.view;
+
+/**
+ * Container for storing data generated by {@link View#onProvideAssistData
+ * View.onProvideAssistData}.
+ */
+public abstract class ViewAssistData {
+ public abstract void setText(CharSequence text);
+ public abstract void setText(CharSequence text, int selectionStart, int selectionEnd);
+ public abstract void setHint(CharSequence hint);
+
+ public abstract CharSequence getText();
+ public abstract int getTextSelectionStart();
+ public abstract int getTextSelectionEnd();
+ public abstract CharSequence getHint();
+}
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index f8026d1..504a758 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -771,8 +771,10 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
* @see #onRequestSendAccessibilityEvent(View, AccessibilityEvent)
*
* Note: Called from the default {@link View.AccessibilityDelegate}.
+ *
+ * @hide
*/
- boolean onRequestSendAccessibilityEventInternal(View child, AccessibilityEvent event) {
+ public boolean onRequestSendAccessibilityEventInternal(View child, AccessibilityEvent event) {
return true;
}
@@ -1206,7 +1208,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
* {@inheritDoc}
*/
public void bringChildToFront(View child) {
- int index = indexOfChild(child);
+ final int index = indexOfChild(child);
if (index >= 0) {
removeFromArray(index);
addInArray(child, mChildrenCount);
@@ -2708,8 +2710,9 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
}
}
+ /** @hide */
@Override
- boolean dispatchPopulateAccessibilityEventInternal(AccessibilityEvent event) {
+ public boolean dispatchPopulateAccessibilityEventInternal(AccessibilityEvent event) {
boolean handled = false;
if (includeForAccessibility()) {
handled = super.dispatchPopulateAccessibilityEventInternal(event);
@@ -2736,8 +2739,9 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
return false;
}
+ /** @hide */
@Override
- void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
+ public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
super.onInitializeAccessibilityNodeInfoInternal(info);
if (getAccessibilityNodeProvider() != null) {
return;
@@ -2755,8 +2759,9 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
}
}
+ /** @hide */
@Override
- void onInitializeAccessibilityEventInternal(AccessibilityEvent event) {
+ public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) {
super.onInitializeAccessibilityEventInternal(event);
event.setClassName(ViewGroup.class.getName());
}
@@ -3766,7 +3771,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
* {@link #dispatchDraw(android.graphics.Canvas)} or any related method.</p>
*
* @param child the child view to add
- * @param index the position at which to add the child
+ * @param index the position at which to add the child or -1 to add last
* @param params the layout parameters to set on the child
*/
public void addView(View child, int index, LayoutParams params) {
@@ -3882,7 +3887,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
* If index is negative, it means put it at the end of the list.
*
* @param child the view to add to the group
- * @param index the index at which the child must be added
+ * @param index the index at which the child must be added or -1 to add last
* @param params the layout parameters to associate with the child
* @return true if the child was added, false otherwise
*/
@@ -3897,7 +3902,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
* If index is negative, it means put it at the end of the list.
*
* @param child the view to add to the group
- * @param index the index at which the child must be added
+ * @param index the index at which the child must be added or -1 to add last
* @param params the layout parameters to associate with the child
* @param preventRequestLayout if true, calling this method will not trigger a
* layout request on child
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index e4d82b1..fb2a8d8 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -76,7 +76,6 @@ import android.widget.Scroller;
import com.android.internal.R;
import com.android.internal.os.SomeArgs;
-import com.android.internal.policy.PolicyManager;
import com.android.internal.view.BaseSurfaceHolder;
import com.android.internal.view.RootViewSurfaceTaker;
@@ -386,7 +385,7 @@ public final class ViewRootImpl implements ViewParent,
mViewConfiguration = ViewConfiguration.get(context);
mDensity = context.getResources().getDisplayMetrics().densityDpi;
mNoncompatDensity = context.getResources().getDisplayMetrics().noncompatDensityDpi;
- mFallbackEventHandler = PolicyManager.makeNewFallbackEventHandler(context);
+ mFallbackEventHandler = new PhoneFallbackEventHandler(context);
mChoreographer = Choreographer.getInstance();
mDisplayManager = (DisplayManager)context.getSystemService(Context.DISPLAY_SERVICE);
loadSystemProperties();
@@ -472,8 +471,10 @@ public final class ViewRootImpl implements ViewParent,
// Compute surface insets required to draw at specified Z value.
// TODO: Use real shadow insets for a constant max Z.
- final int surfaceInset = (int) Math.ceil(view.getZ() * 2);
- attrs.surfaceInsets.set(surfaceInset, surfaceInset, surfaceInset, surfaceInset);
+ if (!attrs.hasManualSurfaceInsets) {
+ final int surfaceInset = (int) Math.ceil(view.getZ() * 2);
+ attrs.surfaceInsets.set(surfaceInset, surfaceInset, surfaceInset, surfaceInset);
+ }
CompatibilityInfo compatibilityInfo = mDisplayAdjustments.getCompatibilityInfo();
mTranslator = compatibilityInfo.getTranslator();
@@ -649,6 +650,10 @@ public final class ViewRootImpl implements ViewParent,
return (mWindowAttributes.flags & WindowManager.LayoutParams.FLAG_LOCAL_FOCUS_MODE) != 0;
}
+ public CharSequence getTitle() {
+ return mWindowAttributes.getTitle();
+ }
+
void destroyHardwareResources() {
if (mAttachInfo.mHardwareRenderer != null) {
mAttachInfo.mHardwareRenderer.destroyHardwareResources(mView);
@@ -760,6 +765,7 @@ public final class ViewRootImpl implements ViewParent,
final int oldInsetRight = mWindowAttributes.surfaceInsets.right;
final int oldInsetBottom = mWindowAttributes.surfaceInsets.bottom;
final int oldSoftInputMode = mWindowAttributes.softInputMode;
+ final boolean oldHasManualSurfaceInsets = mWindowAttributes.hasManualSurfaceInsets;
// Keep track of the actual window flags supplied by the client.
mClientWindowLayoutFlags = attrs.flags;
@@ -786,6 +792,7 @@ public final class ViewRootImpl implements ViewParent,
// Restore old surface insets.
mWindowAttributes.surfaceInsets.set(
oldInsetLeft, oldInsetTop, oldInsetRight, oldInsetBottom);
+ mWindowAttributes.hasManualSurfaceInsets = oldHasManualSurfaceInsets;
applyKeepScreenOnFlag(mWindowAttributes);
@@ -1326,6 +1333,11 @@ public final class ViewRootImpl implements ViewParent,
}
}
+ // Non-visible windows can't hold accessibility focus.
+ if (mAttachInfo.mWindowVisibility != View.VISIBLE) {
+ host.clearAccessibilityFocus();
+ }
+
// Execute enqueued actions on every traversal in case a detached view enqueued an action
getRunQueue().executeActions(mAttachInfo.mHandler);
diff --git a/core/java/android/view/ViewStub.java b/core/java/android/view/ViewStub.java
index d68a860..9f9ed5b 100644
--- a/core/java/android/view/ViewStub.java
+++ b/core/java/android/view/ViewStub.java
@@ -69,8 +69,8 @@ import java.lang.ref.WeakReference;
*/
@RemoteView
public final class ViewStub extends View {
- private int mLayoutResource = 0;
private int mInflatedId;
+ private int mLayoutResource;
private WeakReference<View> mInflatedViewRef;
@@ -78,7 +78,7 @@ public final class ViewStub extends View {
private OnInflateListener mInflateListener;
public ViewStub(Context context) {
- initialize(context);
+ this(context, 0);
}
/**
@@ -88,38 +88,29 @@ public final class ViewStub extends View {
* @param layoutResource The reference to a layout resource that will be inflated.
*/
public ViewStub(Context context, int layoutResource) {
+ this(context, null);
+
mLayoutResource = layoutResource;
- initialize(context);
}
public ViewStub(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
- @SuppressWarnings({"UnusedDeclaration"})
public ViewStub(Context context, AttributeSet attrs, int defStyleAttr) {
this(context, attrs, defStyleAttr, 0);
}
public ViewStub(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
- TypedArray a = context.obtainStyledAttributes(
- attrs, com.android.internal.R.styleable.ViewStub, defStyleAttr, defStyleRes);
+ super(context);
+ final TypedArray a = context.obtainStyledAttributes(attrs,
+ R.styleable.ViewStub, defStyleAttr, defStyleRes);
mInflatedId = a.getResourceId(R.styleable.ViewStub_inflatedId, NO_ID);
mLayoutResource = a.getResourceId(R.styleable.ViewStub_layout, 0);
-
- a.recycle();
-
- a = context.obtainStyledAttributes(
- attrs, com.android.internal.R.styleable.View, defStyleAttr, defStyleRes);
- mID = a.getResourceId(R.styleable.View_id, NO_ID);
+ mID = a.getResourceId(R.styleable.ViewStub_id, NO_ID);
a.recycle();
- initialize(context);
- }
-
- private void initialize(Context context) {
- mContext = context;
setVisibility(GONE);
setWillNotDraw(true);
}
diff --git a/core/java/android/view/Window.java b/core/java/android/view/Window.java
index 8704356..8964862 100644
--- a/core/java/android/view/Window.java
+++ b/core/java/android/view/Window.java
@@ -42,10 +42,8 @@ import android.view.accessibility.AccessibilityEvent;
* area, default key processing, etc.
*
* <p>The only existing implementation of this abstract class is
- * android.policy.PhoneWindow, which you should instantiate when needing a
- * Window. Eventually that class will be refactored and a factory method
- * added for creating Window instances without knowing about a particular
- * implementation.
+ * android.view.PhoneWindow, which you should instantiate when needing a
+ * Window.
*/
public abstract class Window {
/** Flag for the "options panel" feature. This is enabled by default. */
@@ -986,6 +984,7 @@ public abstract class Window {
*
* @return The view if found or null otherwise.
*/
+ @Nullable
public View findViewById(int id) {
return getDecorView().findViewById(id);
}
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index 84434f7..905d6d7 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -502,13 +502,6 @@ public interface WindowManager extends ViewManager {
public static final int TYPE_NAVIGATION_BAR_PANEL = FIRST_SYSTEM_WINDOW+24;
/**
- * Window type: Behind the universe of the real windows.
- * In multiuser systems shows on all users' windows.
- * @hide
- */
- public static final int TYPE_UNIVERSE_BACKGROUND = FIRST_SYSTEM_WINDOW+25;
-
- /**
* Window type: Display overlay window. Used to simulate secondary display devices.
* In multiuser systems shows on all users' windows.
* @hide
@@ -1333,6 +1326,16 @@ public interface WindowManager extends ViewManager {
* @hide
*/
public final Rect surfaceInsets = new Rect();
+
+ /**
+ * Whether the surface insets have been manually set. When set to
+ * {@code false}, the view root will automatically determine the
+ * appropriate surface insets.
+ *
+ * @see #surfaceInsets
+ * @hide
+ */
+ public boolean hasManualSurfaceInsets;
/**
* The desired bitmap format. May be one of the constants in
@@ -1641,6 +1644,7 @@ public interface WindowManager extends ViewManager {
out.writeInt(surfaceInsets.top);
out.writeInt(surfaceInsets.right);
out.writeInt(surfaceInsets.bottom);
+ out.writeInt(hasManualSurfaceInsets ? 1 : 0);
out.writeInt(needsMenuKey);
}
@@ -1689,6 +1693,7 @@ public interface WindowManager extends ViewManager {
surfaceInsets.top = in.readInt();
surfaceInsets.right = in.readInt();
surfaceInsets.bottom = in.readInt();
+ hasManualSurfaceInsets = in.readInt() != 0;
needsMenuKey = in.readInt();
}
@@ -1871,6 +1876,11 @@ public interface WindowManager extends ViewManager {
changes |= SURFACE_INSETS_CHANGED;
}
+ if (hasManualSurfaceInsets != o.hasManualSurfaceInsets) {
+ hasManualSurfaceInsets = o.hasManualSurfaceInsets;
+ changes |= SURFACE_INSETS_CHANGED;
+ }
+
if (needsMenuKey != o.needsMenuKey) {
needsMenuKey = o.needsMenuKey;
changes |= NEEDS_MENU_KEY_CHANGED;
@@ -1979,8 +1989,11 @@ public interface WindowManager extends ViewManager {
if (userActivityTimeout >= 0) {
sb.append(" userActivityTimeout=").append(userActivityTimeout);
}
- if (!surfaceInsets.equals(Insets.NONE)) {
+ if (!surfaceInsets.equals(Insets.NONE) || hasManualSurfaceInsets) {
sb.append(" surfaceInsets=").append(surfaceInsets);
+ if (hasManualSurfaceInsets) {
+ sb.append(" (manual)");
+ }
}
if (needsMenuKey != NEEDS_MENU_UNSET) {
sb.append(" needsMenuKey=");
diff --git a/core/java/android/view/WindowManagerGlobal.java b/core/java/android/view/WindowManagerGlobal.java
index a14c766..279627a 100644
--- a/core/java/android/view/WindowManagerGlobal.java
+++ b/core/java/android/view/WindowManagerGlobal.java
@@ -190,6 +190,39 @@ public final class WindowManagerGlobal {
}
}
+ public ArrayList<ViewRootImpl> getRootViews(IBinder token) {
+ ArrayList<ViewRootImpl> views = new ArrayList<>();
+ synchronized (mLock) {
+ final int numRoots = mRoots.size();
+ for (int i = 0; i < numRoots; ++i) {
+ WindowManager.LayoutParams params = mParams.get(i);
+ if (params.token == null) {
+ continue;
+ }
+ if (params.token != token) {
+ boolean isChild = false;
+ if (params.type >= WindowManager.LayoutParams.FIRST_SUB_WINDOW
+ && params.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW) {
+ for (int j = 0 ; j < numRoots; ++j) {
+ View viewj = mViews.get(j);
+ WindowManager.LayoutParams paramsj = mParams.get(j);
+ if (params.token == viewj.getWindowToken()
+ && paramsj.token == token) {
+ isChild = true;
+ break;
+ }
+ }
+ }
+ if (!isChild) {
+ continue;
+ }
+ }
+ views.add(mRoots.get(i));
+ }
+ }
+ return views;
+ }
+
public View getRootView(String name) {
synchronized (mLock) {
for (int i = mRoots.size() - 1; i >= 0; --i) {
@@ -213,15 +246,15 @@ public final class WindowManagerGlobal {
throw new IllegalArgumentException("Params must be WindowManager.LayoutParams");
}
- final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams)params;
+ final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
if (parentWindow != null) {
parentWindow.adjustLayoutParamsForSubWindow(wparams);
- } else {
+ } else if (ActivityManager.isHighEndGfx()) {
// If there's no parent and we're running on L or above (or in the
// system context), assume we want hardware acceleration.
final Context context = view.getContext();
- if (context != null
- && context.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.LOLLIPOP) {
+ if (context != null && context.getApplicationInfo().targetSdkVersion
+ >= Build.VERSION_CODES.LOLLIPOP) {
wparams.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
}
}
diff --git a/core/java/android/view/WindowManagerPolicy.java b/core/java/android/view/WindowManagerPolicy.java
index 780ca99..3f35612 100644
--- a/core/java/android/view/WindowManagerPolicy.java
+++ b/core/java/android/view/WindowManagerPolicy.java
@@ -587,13 +587,6 @@ public interface WindowManagerPolicy {
public int getMaxWallpaperLayer();
/**
- * Return the window layer at which windows appear above the normal
- * universe (that is no longer impacted by the universe background
- * transform).
- */
- public int getAboveUniverseLayer();
-
- /**
* Return the display width available after excluding any screen
* decorations that can never be removed. That is, system bar or
* button bar.
diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfo.java b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
index b5afdf7..6096d7d 100644
--- a/core/java/android/view/accessibility/AccessibilityNodeInfo.java
+++ b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
@@ -721,7 +721,7 @@ public class AccessibilityNodeInfo implements Parcelable {
* @return Whether the refresh succeeded.
*/
public boolean refresh() {
- return refresh(false);
+ return refresh(true);
}
/**
diff --git a/core/java/android/view/inputmethod/InputMethodSubtypeArray.java b/core/java/android/view/inputmethod/InputMethodSubtypeArray.java
index 5bef71f..3ff099a 100644
--- a/core/java/android/view/inputmethod/InputMethodSubtypeArray.java
+++ b/core/java/android/view/inputmethod/InputMethodSubtypeArray.java
@@ -203,43 +203,20 @@ public class InputMethodSubtypeArray {
}
private static byte[] compress(final byte[] data) {
- ByteArrayOutputStream resultStream = null;
- GZIPOutputStream zipper = null;
- try {
- resultStream = new ByteArrayOutputStream();
- zipper = new GZIPOutputStream(resultStream);
+ try (final ByteArrayOutputStream resultStream = new ByteArrayOutputStream();
+ final GZIPOutputStream zipper = new GZIPOutputStream(resultStream)) {
zipper.write(data);
- } catch(IOException e) {
+ zipper.finish();
+ return resultStream.toByteArray();
+ } catch(Exception e) {
+ Slog.e(TAG, "Failed to compress the data.", e);
return null;
- } finally {
- try {
- if (zipper != null) {
- zipper.close();
- }
- } catch (IOException e) {
- zipper = null;
- Slog.e(TAG, "Failed to close the stream.", e);
- // swallowed, not propagated back to the caller
- }
- try {
- if (resultStream != null) {
- resultStream.close();
- }
- } catch (IOException e) {
- resultStream = null;
- Slog.e(TAG, "Failed to close the stream.", e);
- // swallowed, not propagated back to the caller
- }
}
- return resultStream != null ? resultStream.toByteArray() : null;
}
private static byte[] decompress(final byte[] data, final int expectedSize) {
- ByteArrayInputStream inputStream = null;
- GZIPInputStream unzipper = null;
- try {
- inputStream = new ByteArrayInputStream(data);
- unzipper = new GZIPInputStream(inputStream);
+ try (final ByteArrayInputStream inputStream = new ByteArrayInputStream(data);
+ final GZIPInputStream unzipper = new GZIPInputStream(inputStream)) {
final byte [] result = new byte[expectedSize];
int totalReadBytes = 0;
while (totalReadBytes < result.length) {
@@ -254,25 +231,9 @@ public class InputMethodSubtypeArray {
return null;
}
return result;
- } catch(IOException e) {
+ } catch(Exception e) {
+ Slog.e(TAG, "Failed to decompress the data.", e);
return null;
- } finally {
- try {
- if (unzipper != null) {
- unzipper.close();
- }
- } catch (IOException e) {
- Slog.e(TAG, "Failed to close the stream.", e);
- // swallowed, not propagated back to the caller
- }
- try {
- if (inputStream != null) {
- inputStream.close();
- }
- } catch (IOException e) {
- Slog.e(TAG, "Failed to close the stream.", e);
- // swallowed, not propagated back to the caller
- }
}
}
}
diff --git a/core/java/android/webkit/CookieManager.java b/core/java/android/webkit/CookieManager.java
index eca96f9..6d5fac1 100644
--- a/core/java/android/webkit/CookieManager.java
+++ b/core/java/android/webkit/CookieManager.java
@@ -31,10 +31,7 @@ public abstract class CookieManager {
}
/**
- * Gets the singleton CookieManager instance. If this method is used
- * before the application instantiates a {@link WebView} instance,
- * {@link CookieSyncManager#createInstance CookieSyncManager.createInstance(Context)}
- * must be called first.
+ * Gets the singleton CookieManager instance.
*
* @return the singleton CookieManager instance
*/
diff --git a/core/java/android/webkit/WebChromeClient.java b/core/java/android/webkit/WebChromeClient.java
index 768dc9f..4737e9b 100644
--- a/core/java/android/webkit/WebChromeClient.java
+++ b/core/java/android/webkit/WebChromeClient.java
@@ -70,13 +70,14 @@ public class WebChromeClient {
}
/**
- * Notify the host application that the current page would
- * like to show a custom View. This is used for Fullscreen
- * video playback; see "HTML5 Video support" documentation on
+ * Notify the host application that the current page has entered full
+ * screen mode. The host application must show the custom View which
+ * contains the web contents &mdash; video or other HTML content &mdash;
+ * in full screen mode. Also see "Full screen support" documentation on
* {@link WebView}.
* @param view is the View object to be shown.
- * @param callback is the callback to be invoked if and when the view
- * is dismissed.
+ * @param callback invoke this callback to request the page to exit
+ * full screen mode.
*/
public void onShowCustomView(View view, CustomViewCallback callback) {};
@@ -96,8 +97,10 @@ public class WebChromeClient {
CustomViewCallback callback) {};
/**
- * Notify the host application that the current page would
- * like to hide its custom view.
+ * Notify the host application that the current page has exited full
+ * screen mode. The host application must hide the custom View, ie. the
+ * View passed to {@link #onShowCustomView} when the content entered fullscreen.
+ * Also see "Full screen support" documentation on {@link WebView}.
*/
public void onHideCustomView() {}
@@ -377,10 +380,9 @@ public class WebChromeClient {
}
/**
- * When the user starts to playback a video element, it may take time for enough
- * data to be buffered before the first frames can be rendered. While this buffering
- * is taking place, the ChromeClient can use this function to provide a View to be
- * displayed. For example, the ChromeClient could show a spinner animation.
+ * Obtains a View to be displayed while buffering of full screen video is taking
+ * place. The host application can override this method to provide a View
+ * containing a spinner or similar.
*
* @return View The View to be displayed whilst the video is loading.
*/
diff --git a/core/java/android/webkit/WebResourceRequest.java b/core/java/android/webkit/WebResourceRequest.java
index 2185658..07402b3 100644
--- a/core/java/android/webkit/WebResourceRequest.java
+++ b/core/java/android/webkit/WebResourceRequest.java
@@ -42,6 +42,8 @@ public interface WebResourceRequest {
/**
* Gets whether a gesture (such as a click) was associated with the request.
+ * For security reasons in certain situations this method may return false even though the
+ * sequence of events which caused the request to be created was initiated by a user gesture.
*
* @return whether a gesture was associated with the request.
*/
diff --git a/core/java/android/webkit/WebResourceResponse.java b/core/java/android/webkit/WebResourceResponse.java
index ad6e9aa..f487a4e 100644
--- a/core/java/android/webkit/WebResourceResponse.java
+++ b/core/java/android/webkit/WebResourceResponse.java
@@ -17,6 +17,7 @@
package android.webkit;
import java.io.InputStream;
+import java.io.StringBufferInputStream;
import java.util.Map;
/**
@@ -40,13 +41,14 @@ public class WebResourceResponse {
*
* @param mimeType the resource response's MIME type, for example text/html
* @param encoding the resource response's encoding
- * @param data the input stream that provides the resource response's data
+ * @param data the input stream that provides the resource response's data. Must not be a
+ * StringBufferInputStream.
*/
public WebResourceResponse(String mimeType, String encoding,
InputStream data) {
mMimeType = mimeType;
mEncoding = encoding;
- mInputStream = data;
+ setData(data);
}
/**
@@ -62,7 +64,8 @@ public class WebResourceResponse {
* and not empty.
* @param responseHeaders the resource response's headers represented as a mapping of header
* name -> header value.
- * @param data the input stream that provides the resource response's data
+ * @param data the input stream that provides the resource response's data. Must not be a
+ * StringBufferInputStream.
*/
public WebResourceResponse(String mimeType, String encoding, int statusCode,
String reasonPhrase, Map<String, String> responseHeaders, InputStream data) {
@@ -178,9 +181,16 @@ public class WebResourceResponse {
* Sets the input stream that provides the resource response's data. Callers
* must implement {@link InputStream#read(byte[]) InputStream.read(byte[])}.
*
- * @param data the input stream that provides the resource response's data
+ * @param data the input stream that provides the resource response's data. Must not be a
+ * StringBufferInputStream.
*/
public void setData(InputStream data) {
+ // If data is (or is a subclass of) StringBufferInputStream
+ if (data != null && StringBufferInputStream.class.isAssignableFrom(data.getClass())) {
+ throw new IllegalArgumentException("StringBufferInputStream is deprecated and must " +
+ "not be passed to a WebResourceResponse");
+ }
+
mInputStream = data;
}
diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java
index bab1f3b..01a506c 100644
--- a/core/java/android/webkit/WebView.java
+++ b/core/java/android/webkit/WebView.java
@@ -233,12 +233,48 @@ import java.util.Map;
*
* <h3>HTML5 Video support</h3>
*
- * <p>In order to support inline HTML5 video in your application, you need to have hardware
- * acceleration turned on, and set a {@link android.webkit.WebChromeClient}. For full screen support,
- * implementations of {@link WebChromeClient#onShowCustomView(View, WebChromeClient.CustomViewCallback)}
- * and {@link WebChromeClient#onHideCustomView()} are required,
- * {@link WebChromeClient#getVideoLoadingProgressView()} is optional.
+ * <p>In order to support inline HTML5 video in your application you need to have hardware
+ * acceleration turned on.
* </p>
+ *
+ * <h3>Full screen support</h3>
+ *
+ * <p>In order to support full screen &mdash; for video or other HTML content &mdash; you need to set a
+ * {@link android.webkit.WebChromeClient} and implement both
+ * {@link WebChromeClient#onShowCustomView(View, WebChromeClient.CustomViewCallback)}
+ * and {@link WebChromeClient#onHideCustomView()}. If the implementation of either of these two methods is
+ * missing then the web contents will not be allowed to enter full screen. Optionally you can implement
+ * {@link WebChromeClient#getVideoLoadingProgressView()} to customize the View displayed whilst a video
+ * is loading.
+ * </p>
+ *
+ * <h3>Layout size</h3>
+ * <p>
+ * It is recommended to set the WebView layout height to a fixed value or to
+ * {@link android.view.ViewGroup.LayoutParams#MATCH_PARENT} instead of using
+ * {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT}.
+ * When using {@link android.view.ViewGroup.LayoutParams#MATCH_PARENT}
+ * for the height none of the WebView's parents should use a
+ * {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT} layout height since that could result in
+ * incorrect sizing of the views.
+ * </p>
+ *
+ * <p>Setting the WebView's height to {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT}
+ * enables the following behaviors:
+ * <ul>
+ * <li>The HTML body layout height is set to a fixed value. This means that elements with a height
+ * relative to the HTML body may not be sized correctly. </li>
+ * <li>For applications targetting {@link android.os.Build.VERSION_CODES#KITKAT} and earlier SDKs the
+ * HTML viewport meta tag will be ignored in order to preserve backwards compatibility. </li>
+ * </ul>
+ * </p>
+ *
+ * <p>
+ * Using a layout width of {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT} is not
+ * supported. If such a width is used the WebView will attempt to use the width of the parent
+ * instead.
+ * </p>
+ *
*/
// Implementation notes.
// The WebView is a thin API class that delegates its public API to a backend WebViewProvider
@@ -2043,7 +2079,7 @@ public class WebView extends AbsoluteLayout
}
public boolean super_performAccessibilityAction(int action, Bundle arguments) {
- return WebView.super.performAccessibilityAction(action, arguments);
+ return WebView.super.performAccessibilityActionInternal(action, arguments);
}
public boolean super_performLongClick() {
@@ -2351,22 +2387,27 @@ public class WebView extends AbsoluteLayout
return mProvider.getViewDelegate().shouldDelayChildPressedState();
}
+ public CharSequence getAccessibilityClassName() {
+ return WebView.class.getName();
+ }
+
+ /** @hide */
@Override
- public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(info);
- info.setClassName(WebView.class.getName());
+ public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
+ super.onInitializeAccessibilityNodeInfoInternal(info);
mProvider.getViewDelegate().onInitializeAccessibilityNodeInfo(info);
}
+ /** @hide */
@Override
- public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
- super.onInitializeAccessibilityEvent(event);
- event.setClassName(WebView.class.getName());
+ public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) {
+ super.onInitializeAccessibilityEventInternal(event);
mProvider.getViewDelegate().onInitializeAccessibilityEvent(event);
}
+ /** @hide */
@Override
- public boolean performAccessibilityAction(int action, Bundle arguments) {
+ public boolean performAccessibilityActionInternal(int action, Bundle arguments) {
return mProvider.getViewDelegate().performAccessibilityAction(action, arguments);
}
diff --git a/core/java/android/webkit/WebViewClient.java b/core/java/android/webkit/WebViewClient.java
index d52dd60..1cc899d 100644
--- a/core/java/android/webkit/WebViewClient.java
+++ b/core/java/android/webkit/WebViewClient.java
@@ -50,7 +50,9 @@ public class WebViewClient {
* is called once for each main frame load so a page with iframes or
* framesets will call onPageStarted one time for the main frame. This also
* means that onPageStarted will not be called when the contents of an
- * embedded frame changes, i.e. clicking a link whose target is an iframe.
+ * embedded frame changes, i.e. clicking a link whose target is an iframe,
+ * it will also not be called for fragment navigations (navigations to
+ * #fragment_id).
*
* @param view The WebView that is initiating the callback.
* @param url The url to be loaded.
diff --git a/core/java/android/webkit/WebViewFactory.java b/core/java/android/webkit/WebViewFactory.java
index 474ef42..cafe053 100644
--- a/core/java/android/webkit/WebViewFactory.java
+++ b/core/java/android/webkit/WebViewFactory.java
@@ -117,12 +117,8 @@ public final class WebViewFactory {
StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskReads();
Trace.traceBegin(Trace.TRACE_TAG_WEBVIEW, "providerClass.newInstance()");
try {
- try {
- sProviderInstance = providerClass.getConstructor(WebViewDelegate.class)
- .newInstance(new WebViewDelegate());
- } catch (Exception e) {
- sProviderInstance = providerClass.newInstance();
- }
+ sProviderInstance = providerClass.getConstructor(WebViewDelegate.class)
+ .newInstance(new WebViewDelegate());
if (DEBUG) Log.v(LOGTAG, "Loaded provider: " + sProviderInstance);
return sProviderInstance;
} catch (Exception e) {
diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java
index 1e269a3..e87a117 100644
--- a/core/java/android/widget/AbsListView.java
+++ b/core/java/android/widget/AbsListView.java
@@ -578,6 +578,12 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
private boolean mIsChildViewEnabled;
/**
+ * The cached drawable state for the selector. Accounts for child enabled
+ * state, but otherwise identical to the view's own drawable state.
+ */
+ private int[] mSelectorState;
+
+ /**
* The last scroll state reported to clients through {@link OnScrollListener}.
*/
private int mLastScrollState = OnScrollListener.SCROLL_STATE_IDLE;
@@ -1466,8 +1472,9 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
onScrollChanged(0, 0, 0, 0); // dummy values, View's implementation does not use these.
}
+ /** @hide */
@Override
- public void sendAccessibilityEvent(int eventType) {
+ public void sendAccessibilityEventInternal(int eventType) {
// Since this class calls onScrollChanged even if the mFirstPosition and the
// child count have not changed we will avoid sending duplicate accessibility
// events.
@@ -1482,19 +1489,18 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
mLastAccessibilityScrollEventToIndex = lastVisiblePosition;
}
}
- super.sendAccessibilityEvent(eventType);
+ super.sendAccessibilityEventInternal(eventType);
}
@Override
- public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
- super.onInitializeAccessibilityEvent(event);
- event.setClassName(AbsListView.class.getName());
+ public CharSequence getAccessibilityClassName() {
+ return AbsListView.class.getName();
}
+ /** @hide */
@Override
- public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(info);
- info.setClassName(AbsListView.class.getName());
+ public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
+ super.onInitializeAccessibilityNodeInfoInternal(info);
if (isEnabled()) {
if (canScrollUp()) {
info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD);
@@ -1522,9 +1528,10 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
}
}
+ /** @hide */
@Override
- public boolean performAccessibilityAction(int action, Bundle arguments) {
- if (super.performAccessibilityAction(action, arguments)) {
+ public boolean performAccessibilityActionInternal(int action, Bundle arguments) {
+ if (super.performAccessibilityActionInternal(action, arguments)) {
return true;
}
switch (action) {
@@ -2785,7 +2792,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
void updateSelectorState() {
if (mSelector != null) {
if (shouldShowSelector()) {
- mSelector.setState(getDrawableState());
+ mSelector.setState(getDrawableStateForSelector());
} else {
mSelector.setState(StateSet.NOTHING);
}
@@ -2798,12 +2805,11 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
updateSelectorState();
}
- @Override
- protected int[] onCreateDrawableState(int extraSpace) {
+ private int[] getDrawableStateForSelector() {
// If the child view is enabled then do the default behavior.
if (mIsChildViewEnabled) {
// Common case
- return super.onCreateDrawableState(extraSpace);
+ return super.getDrawableState();
}
// The selector uses this View's drawable state. The selected child view
@@ -2811,10 +2817,12 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
// states.
final int enabledState = ENABLED_STATE_SET[0];
- // If we don't have any extra space, it will return one of the static state arrays,
- // and clearing the enabled state on those arrays is a bad thing! If we specify
- // we need extra space, it will create+copy into a new array that safely mutable.
- int[] state = super.onCreateDrawableState(extraSpace + 1);
+ // If we don't have any extra space, it will return one of the static
+ // state arrays, and clearing the enabled state on those arrays is a
+ // bad thing! If we specify we need extra space, it will create+copy
+ // into a new array that is safely mutable.
+ final int[] state = onCreateDrawableState(1);
+
int enabledPos = -1;
for (int i = state.length - 1; i >= 0; i--) {
if (state[i] == enabledState) {
diff --git a/core/java/android/widget/AbsSeekBar.java b/core/java/android/widget/AbsSeekBar.java
index a3ce808..78344ac 100644
--- a/core/java/android/widget/AbsSeekBar.java
+++ b/core/java/android/widget/AbsSeekBar.java
@@ -381,8 +381,8 @@ public abstract class AbsSeekBar extends ProgressBar {
}
@Override
- void onProgressRefresh(float scale, boolean fromUser) {
- super.onProgressRefresh(scale, fromUser);
+ void onProgressRefresh(float scale, boolean fromUser, int progress) {
+ super.onProgressRefresh(scale, fromUser, progress);
final Drawable thumb = mThumb;
if (thumb != null) {
@@ -716,15 +716,14 @@ public abstract class AbsSeekBar extends ProgressBar {
}
@Override
- public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
- super.onInitializeAccessibilityEvent(event);
- event.setClassName(AbsSeekBar.class.getName());
+ public CharSequence getAccessibilityClassName() {
+ return AbsSeekBar.class.getName();
}
+ /** @hide */
@Override
- public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(info);
- info.setClassName(AbsSeekBar.class.getName());
+ public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
+ super.onInitializeAccessibilityNodeInfoInternal(info);
if (isEnabled()) {
final int progress = getProgress();
@@ -737,9 +736,10 @@ public abstract class AbsSeekBar extends ProgressBar {
}
}
+ /** @hide */
@Override
- public boolean performAccessibilityAction(int action, Bundle arguments) {
- if (super.performAccessibilityAction(action, arguments)) {
+ public boolean performAccessibilityActionInternal(int action, Bundle arguments) {
+ if (super.performAccessibilityActionInternal(action, arguments)) {
return true;
}
if (!isEnabled()) {
diff --git a/core/java/android/widget/AbsSpinner.java b/core/java/android/widget/AbsSpinner.java
index 6a4ad75..e432747 100644
--- a/core/java/android/widget/AbsSpinner.java
+++ b/core/java/android/widget/AbsSpinner.java
@@ -73,13 +73,12 @@ public abstract class AbsSpinner extends AdapterView<SpinnerAdapter> {
initAbsSpinner();
final TypedArray a = context.obtainStyledAttributes(
- attrs, com.android.internal.R.styleable.AbsSpinner, defStyleAttr, defStyleRes);
+ attrs, R.styleable.AbsSpinner, defStyleAttr, defStyleRes);
- CharSequence[] entries = a.getTextArray(R.styleable.AbsSpinner_entries);
+ final CharSequence[] entries = a.getTextArray(R.styleable.AbsSpinner_entries);
if (entries != null) {
- ArrayAdapter<CharSequence> adapter =
- new ArrayAdapter<CharSequence>(context,
- R.layout.simple_spinner_item, entries);
+ final ArrayAdapter<CharSequence> adapter = new ArrayAdapter<CharSequence>(
+ context, R.layout.simple_spinner_item, entries);
adapter.setDropDownViewResource(R.layout.simple_spinner_dropdown_item);
setAdapter(adapter);
}
@@ -472,14 +471,7 @@ public abstract class AbsSpinner extends AdapterView<SpinnerAdapter> {
}
@Override
- public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
- super.onInitializeAccessibilityEvent(event);
- event.setClassName(AbsSpinner.class.getName());
- }
-
- @Override
- public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(info);
- info.setClassName(AbsSpinner.class.getName());
+ public CharSequence getAccessibilityClassName() {
+ return AbsSpinner.class.getName();
}
}
diff --git a/core/java/android/widget/ActionMenuPresenter.java b/core/java/android/widget/ActionMenuPresenter.java
index 18f15a0..94827dd 100644
--- a/core/java/android/widget/ActionMenuPresenter.java
+++ b/core/java/android/widget/ActionMenuPresenter.java
@@ -645,9 +645,10 @@ public class ActionMenuPresenter extends BaseMenuPresenter
return false;
}
+ /** @hide */
@Override
- public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(info);
+ public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
+ super.onInitializeAccessibilityNodeInfoInternal(info);
info.setCanOpenPopup(true);
}
diff --git a/core/java/android/widget/ActionMenuView.java b/core/java/android/widget/ActionMenuView.java
index 0a8a01f..403e4ac 100644
--- a/core/java/android/widget/ActionMenuView.java
+++ b/core/java/android/widget/ActionMenuView.java
@@ -116,11 +116,14 @@ public class ActionMenuView extends LinearLayout implements MenuBuilder.ItemInvo
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
- mPresenter.updateMenuView(false);
- if (mPresenter != null && mPresenter.isOverflowMenuShowing()) {
- mPresenter.hideOverflowMenu();
- mPresenter.showOverflowMenu();
+ if (mPresenter != null) {
+ mPresenter.updateMenuView(false);
+
+ if (mPresenter.isOverflowMenuShowing()) {
+ mPresenter.hideOverflowMenu();
+ mPresenter.showOverflowMenu();
+ }
}
}
@@ -700,7 +703,8 @@ public class ActionMenuView extends LinearLayout implements MenuBuilder.ItemInvo
return result;
}
- public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
+ /** @hide */
+ public boolean dispatchPopulateAccessibilityEventInternal(AccessibilityEvent event) {
return false;
}
diff --git a/core/java/android/widget/AdapterView.java b/core/java/android/widget/AdapterView.java
index 5e2394c..9b977fa 100644
--- a/core/java/android/widget/AdapterView.java
+++ b/core/java/android/widget/AdapterView.java
@@ -929,8 +929,9 @@ public abstract class AdapterView<T extends Adapter> extends ViewGroup {
}
}
+ /** @hide */
@Override
- public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
+ public boolean dispatchPopulateAccessibilityEventInternal(AccessibilityEvent event) {
View selectedView = getSelectedView();
if (selectedView != null && selectedView.getVisibility() == VISIBLE
&& selectedView.dispatchPopulateAccessibilityEvent(event)) {
@@ -939,9 +940,10 @@ public abstract class AdapterView<T extends Adapter> extends ViewGroup {
return false;
}
+ /** @hide */
@Override
- public boolean onRequestSendAccessibilityEvent(View child, AccessibilityEvent event) {
- if (super.onRequestSendAccessibilityEvent(child, event)) {
+ public boolean onRequestSendAccessibilityEventInternal(View child, AccessibilityEvent event) {
+ if (super.onRequestSendAccessibilityEventInternal(child, event)) {
// Add a record for ourselves as well.
AccessibilityEvent record = AccessibilityEvent.obtain();
onInitializeAccessibilityEvent(record);
@@ -954,9 +956,14 @@ public abstract class AdapterView<T extends Adapter> extends ViewGroup {
}
@Override
- public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(info);
- info.setClassName(AdapterView.class.getName());
+ public CharSequence getAccessibilityClassName() {
+ return AdapterView.class.getName();
+ }
+
+ /** @hide */
+ @Override
+ public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
+ super.onInitializeAccessibilityNodeInfoInternal(info);
info.setScrollable(isScrollableForAccessibility());
View selectedView = getSelectedView();
if (selectedView != null) {
@@ -964,10 +971,10 @@ public abstract class AdapterView<T extends Adapter> extends ViewGroup {
}
}
+ /** @hide */
@Override
- public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
- super.onInitializeAccessibilityEvent(event);
- event.setClassName(AdapterView.class.getName());
+ public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) {
+ super.onInitializeAccessibilityEventInternal(event);
event.setScrollable(isScrollableForAccessibility());
View selectedView = getSelectedView();
if (selectedView != null) {
diff --git a/core/java/android/widget/AdapterViewAnimator.java b/core/java/android/widget/AdapterViewAnimator.java
index 1bc2f4b..a242175 100644
--- a/core/java/android/widget/AdapterViewAnimator.java
+++ b/core/java/android/widget/AdapterViewAnimator.java
@@ -1085,14 +1085,7 @@ public abstract class AdapterViewAnimator extends AdapterView<Adapter>
}
@Override
- public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
- super.onInitializeAccessibilityEvent(event);
- event.setClassName(AdapterViewAnimator.class.getName());
- }
-
- @Override
- public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(info);
- info.setClassName(AdapterViewAnimator.class.getName());
+ public CharSequence getAccessibilityClassName() {
+ return AdapterViewAnimator.class.getName();
}
}
diff --git a/core/java/android/widget/AdapterViewFlipper.java b/core/java/android/widget/AdapterViewFlipper.java
index 285dee8..01b6530 100644
--- a/core/java/android/widget/AdapterViewFlipper.java
+++ b/core/java/android/widget/AdapterViewFlipper.java
@@ -305,14 +305,7 @@ public class AdapterViewFlipper extends AdapterViewAnimator {
}
@Override
- public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
- super.onInitializeAccessibilityEvent(event);
- event.setClassName(AdapterViewFlipper.class.getName());
- }
-
- @Override
- public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(info);
- info.setClassName(AdapterViewFlipper.class.getName());
+ public CharSequence getAccessibilityClassName() {
+ return AdapterViewFlipper.class.getName();
}
}
diff --git a/core/java/android/widget/ArrayAdapter.java b/core/java/android/widget/ArrayAdapter.java
index 97926a7..aff5e29 100644
--- a/core/java/android/widget/ArrayAdapter.java
+++ b/core/java/android/widget/ArrayAdapter.java
@@ -17,7 +17,9 @@
package android.widget;
import android.content.Context;
+import android.content.res.Resources;
import android.util.Log;
+import android.view.ContextThemeWrapper;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@@ -44,7 +46,8 @@ import java.util.List;
* or to have some of data besides toString() results fill the views,
* override {@link #getView(int, View, ViewGroup)} to return the type of view you want.
*/
-public class ArrayAdapter<T> extends BaseAdapter implements Filterable {
+public class ArrayAdapter<T> extends BaseAdapter implements Filterable,
+ Spinner.ThemedSpinnerAdapter {
/**
* Contains the list of objects that represent the data of this ArrayAdapter.
* The content of this list is referred to as "the array" in the documentation.
@@ -93,6 +96,9 @@ public class ArrayAdapter<T> extends BaseAdapter implements Filterable {
private LayoutInflater mInflater;
+ /** Layout inflater used for {@link #getDropDownView(int, View, ViewGroup)}. */
+ private LayoutInflater mDropDownInflater;
+
/**
* Constructor
*
@@ -101,7 +107,7 @@ public class ArrayAdapter<T> extends BaseAdapter implements Filterable {
* instantiating views.
*/
public ArrayAdapter(Context context, int resource) {
- init(context, resource, 0, new ArrayList<T>());
+ this(context, resource, 0, new ArrayList<T>());
}
/**
@@ -113,7 +119,7 @@ public class ArrayAdapter<T> extends BaseAdapter implements Filterable {
* @param textViewResourceId The id of the TextView within the layout resource to be populated
*/
public ArrayAdapter(Context context, int resource, int textViewResourceId) {
- init(context, resource, textViewResourceId, new ArrayList<T>());
+ this(context, resource, textViewResourceId, new ArrayList<T>());
}
/**
@@ -125,7 +131,7 @@ public class ArrayAdapter<T> extends BaseAdapter implements Filterable {
* @param objects The objects to represent in the ListView.
*/
public ArrayAdapter(Context context, int resource, T[] objects) {
- init(context, resource, 0, Arrays.asList(objects));
+ this(context, resource, 0, Arrays.asList(objects));
}
/**
@@ -138,7 +144,7 @@ public class ArrayAdapter<T> extends BaseAdapter implements Filterable {
* @param objects The objects to represent in the ListView.
*/
public ArrayAdapter(Context context, int resource, int textViewResourceId, T[] objects) {
- init(context, resource, textViewResourceId, Arrays.asList(objects));
+ this(context, resource, textViewResourceId, Arrays.asList(objects));
}
/**
@@ -150,7 +156,7 @@ public class ArrayAdapter<T> extends BaseAdapter implements Filterable {
* @param objects The objects to represent in the ListView.
*/
public ArrayAdapter(Context context, int resource, List<T> objects) {
- init(context, resource, 0, objects);
+ this(context, resource, 0, objects);
}
/**
@@ -163,7 +169,11 @@ public class ArrayAdapter<T> extends BaseAdapter implements Filterable {
* @param objects The objects to represent in the ListView.
*/
public ArrayAdapter(Context context, int resource, int textViewResourceId, List<T> objects) {
- init(context, resource, textViewResourceId, objects);
+ mContext = context;
+ mInflater = LayoutInflater.from(context);
+ mResource = mDropDownResource = resource;
+ mObjects = objects;
+ mFieldId = textViewResourceId;
}
/**
@@ -305,14 +315,6 @@ public class ArrayAdapter<T> extends BaseAdapter implements Filterable {
mNotifyOnChange = notifyOnChange;
}
- private void init(Context context, int resource, int textViewResourceId, List<T> objects) {
- mContext = context;
- mInflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
- mResource = mDropDownResource = resource;
- mObjects = objects;
- mFieldId = textViewResourceId;
- }
-
/**
* Returns the context associated with this array adapter. The context is used
* to create views from the resource passed to the constructor.
@@ -359,16 +361,16 @@ public class ArrayAdapter<T> extends BaseAdapter implements Filterable {
* {@inheritDoc}
*/
public View getView(int position, View convertView, ViewGroup parent) {
- return createViewFromResource(position, convertView, parent, mResource);
+ return createViewFromResource(mInflater, position, convertView, parent, mResource);
}
- private View createViewFromResource(int position, View convertView, ViewGroup parent,
- int resource) {
+ private View createViewFromResource(LayoutInflater inflater, int position, View convertView,
+ ViewGroup parent, int resource) {
View view;
TextView text;
if (convertView == null) {
- view = mInflater.inflate(resource, parent, false);
+ view = inflater.inflate(resource, parent, false);
} else {
view = convertView;
}
@@ -408,11 +410,40 @@ public class ArrayAdapter<T> extends BaseAdapter implements Filterable {
}
/**
+ * Sets the {@link Resources.Theme} against which drop-down views are
+ * inflated.
+ * <p>
+ * By default, drop-down views are inflated against the theme of the
+ * {@link Context} passed to the adapter's constructor.
+ *
+ * @param theme the theme against which to inflate drop-down views or
+ * {@code null} to use the theme from the adapter's context
+ * @see #getDropDownView(int, View, ViewGroup)
+ */
+ @Override
+ public void setDropDownViewTheme(Resources.Theme theme) {
+ if (theme == null) {
+ mDropDownInflater = null;
+ } else if (theme == mInflater.getContext().getTheme()) {
+ mDropDownInflater = mInflater;
+ } else {
+ final Context context = new ContextThemeWrapper(mContext, theme);
+ mDropDownInflater = LayoutInflater.from(context);
+ }
+ }
+
+ @Override
+ public Resources.Theme getDropDownViewTheme() {
+ return mDropDownInflater == null ? null : mDropDownInflater.getContext().getTheme();
+ }
+
+ /**
* {@inheritDoc}
*/
@Override
public View getDropDownView(int position, View convertView, ViewGroup parent) {
- return createViewFromResource(position, convertView, parent, mDropDownResource);
+ final LayoutInflater inflater = mDropDownInflater == null ? mInflater : mDropDownInflater;
+ return createViewFromResource(inflater, position, convertView, parent, mDropDownResource);
}
/**
diff --git a/core/java/android/widget/Button.java b/core/java/android/widget/Button.java
index 1663620..9b0d0dd 100644
--- a/core/java/android/widget/Button.java
+++ b/core/java/android/widget/Button.java
@@ -112,14 +112,7 @@ public class Button extends TextView {
}
@Override
- public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
- super.onInitializeAccessibilityEvent(event);
- event.setClassName(Button.class.getName());
- }
-
- @Override
- public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(info);
- info.setClassName(Button.class.getName());
+ public CharSequence getAccessibilityClassName() {
+ return Button.class.getName();
}
}
diff --git a/core/java/android/widget/CalendarView.java b/core/java/android/widget/CalendarView.java
index ed59ea6..5e43916 100644
--- a/core/java/android/widget/CalendarView.java
+++ b/core/java/android/widget/CalendarView.java
@@ -502,13 +502,8 @@ public class CalendarView extends FrameLayout {
}
@Override
- public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
- event.setClassName(CalendarView.class.getName());
- }
-
- @Override
- public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
- info.setClassName(CalendarView.class.getName());
+ public CharSequence getAccessibilityClassName() {
+ return CalendarView.class.getName();
}
/**
diff --git a/core/java/android/widget/CheckBox.java b/core/java/android/widget/CheckBox.java
index 71438c9..5a7d585 100644
--- a/core/java/android/widget/CheckBox.java
+++ b/core/java/android/widget/CheckBox.java
@@ -73,14 +73,7 @@ public class CheckBox extends CompoundButton {
}
@Override
- public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
- super.onInitializeAccessibilityEvent(event);
- event.setClassName(CheckBox.class.getName());
- }
-
- @Override
- public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(info);
- info.setClassName(CheckBox.class.getName());
+ public CharSequence getAccessibilityClassName() {
+ return CheckBox.class.getName();
}
}
diff --git a/core/java/android/widget/CheckedTextView.java b/core/java/android/widget/CheckedTextView.java
index 69969a9..344d00a 100644
--- a/core/java/android/widget/CheckedTextView.java
+++ b/core/java/android/widget/CheckedTextView.java
@@ -436,16 +436,21 @@ public class CheckedTextView extends TextView implements Checkable {
}
@Override
- public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
- super.onInitializeAccessibilityEvent(event);
- event.setClassName(CheckedTextView.class.getName());
+ public CharSequence getAccessibilityClassName() {
+ return CheckedTextView.class.getName();
+ }
+
+ /** @hide */
+ @Override
+ public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) {
+ super.onInitializeAccessibilityEventInternal(event);
event.setChecked(mChecked);
}
+ /** @hide */
@Override
- public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(info);
- info.setClassName(CheckedTextView.class.getName());
+ public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
+ super.onInitializeAccessibilityNodeInfoInternal(info);
info.setCheckable(true);
info.setChecked(mChecked);
}
diff --git a/core/java/android/widget/Chronometer.java b/core/java/android/widget/Chronometer.java
index f94789d..019d475 100644
--- a/core/java/android/widget/Chronometer.java
+++ b/core/java/android/widget/Chronometer.java
@@ -282,14 +282,7 @@ public class Chronometer extends TextView {
}
@Override
- public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
- super.onInitializeAccessibilityEvent(event);
- event.setClassName(Chronometer.class.getName());
- }
-
- @Override
- public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(info);
- info.setClassName(Chronometer.class.getName());
+ public CharSequence getAccessibilityClassName() {
+ return Chronometer.class.getName();
}
}
diff --git a/core/java/android/widget/CompoundButton.java b/core/java/android/widget/CompoundButton.java
index 447ccc2..fede493 100644
--- a/core/java/android/widget/CompoundButton.java
+++ b/core/java/android/widget/CompoundButton.java
@@ -325,16 +325,21 @@ public abstract class CompoundButton extends Button implements Checkable {
}
@Override
- public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
- super.onInitializeAccessibilityEvent(event);
- event.setClassName(CompoundButton.class.getName());
+ public CharSequence getAccessibilityClassName() {
+ return CompoundButton.class.getName();
+ }
+
+ /** @hide */
+ @Override
+ public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) {
+ super.onInitializeAccessibilityEventInternal(event);
event.setChecked(mChecked);
}
+ /** @hide */
@Override
- public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(info);
- info.setClassName(CompoundButton.class.getName());
+ public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
+ super.onInitializeAccessibilityNodeInfoInternal(info);
info.setCheckable(true);
info.setChecked(mChecked);
}
diff --git a/core/java/android/widget/CursorAdapter.java b/core/java/android/widget/CursorAdapter.java
index d4c5be0..8e318fd 100644
--- a/core/java/android/widget/CursorAdapter.java
+++ b/core/java/android/widget/CursorAdapter.java
@@ -17,11 +17,14 @@
package android.widget;
import android.content.Context;
+import android.content.res.Resources;
import android.database.ContentObserver;
import android.database.Cursor;
import android.database.DataSetObserver;
import android.os.Handler;
import android.util.Log;
+import android.view.ContextThemeWrapper;
+import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@@ -35,7 +38,7 @@ import android.view.ViewGroup;
* columns.
*/
public abstract class CursorAdapter extends BaseAdapter implements Filterable,
- CursorFilter.CursorFilterClient {
+ CursorFilter.CursorFilterClient, Spinner.ThemedSpinnerAdapter {
/**
* This field should be made private, so it is hidden from the SDK.
* {@hide}
@@ -57,6 +60,11 @@ public abstract class CursorAdapter extends BaseAdapter implements Filterable,
*/
protected Context mContext;
/**
+ * Context used for {@link #getDropDownView(int, View, ViewGroup)}.
+ * {@hide}
+ */
+ protected Context mDropDownContext;
+ /**
* This field should be made private, so it is hidden from the SDK.
* {@hide}
*/
@@ -185,6 +193,33 @@ public abstract class CursorAdapter extends BaseAdapter implements Filterable,
}
/**
+ * Sets the {@link Resources.Theme} against which drop-down views are
+ * inflated.
+ * <p>
+ * By default, drop-down views are inflated against the theme of the
+ * {@link Context} passed to the adapter's constructor.
+ *
+ * @param theme the theme against which to inflate drop-down views or
+ * {@code null} to use the theme from the adapter's context
+ * @see #newDropDownView(Context, Cursor, ViewGroup)
+ */
+ @Override
+ public void setDropDownViewTheme(Resources.Theme theme) {
+ if (theme == null) {
+ mDropDownContext = null;
+ } else if (theme == mContext.getTheme()) {
+ mDropDownContext = mContext;
+ } else {
+ mDropDownContext = new ContextThemeWrapper(mContext, theme);
+ }
+ }
+
+ @Override
+ public Resources.Theme getDropDownViewTheme() {
+ return mDropDownContext == null ? null : mDropDownContext.getTheme();
+ }
+
+ /**
* Returns the cursor.
* @return the cursor.
*/
@@ -258,20 +293,21 @@ public abstract class CursorAdapter extends BaseAdapter implements Filterable,
@Override
public View getDropDownView(int position, View convertView, ViewGroup parent) {
if (mDataValid) {
+ final Context context = mDropDownContext == null ? mContext : mDropDownContext;
mCursor.moveToPosition(position);
- View v;
+ final View v;
if (convertView == null) {
- v = newDropDownView(mContext, mCursor, parent);
+ v = newDropDownView(context, mCursor, parent);
} else {
v = convertView;
}
- bindView(v, mContext, mCursor);
+ bindView(v, context, mCursor);
return v;
} else {
return null;
}
}
-
+
/**
* Makes a new view to hold the data pointed to by cursor.
* @param context Interface to application's global information
diff --git a/core/java/android/widget/DatePicker.java b/core/java/android/widget/DatePicker.java
index 0ca08e1..7c6055a 100644
--- a/core/java/android/widget/DatePicker.java
+++ b/core/java/android/widget/DatePicker.java
@@ -283,27 +283,22 @@ public class DatePicker extends FrameLayout {
return mDelegate.isEnabled();
}
+ /** @hide */
@Override
- public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
+ public boolean dispatchPopulateAccessibilityEventInternal(AccessibilityEvent event) {
return mDelegate.dispatchPopulateAccessibilityEvent(event);
}
+ /** @hide */
@Override
- public void onPopulateAccessibilityEvent(AccessibilityEvent event) {
- super.onPopulateAccessibilityEvent(event);
+ public void onPopulateAccessibilityEventInternal(AccessibilityEvent event) {
+ super.onPopulateAccessibilityEventInternal(event);
mDelegate.onPopulateAccessibilityEvent(event);
}
@Override
- public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
- super.onInitializeAccessibilityEvent(event);
- mDelegate.onInitializeAccessibilityEvent(event);
- }
-
- @Override
- public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(info);
- mDelegate.onInitializeAccessibilityNodeInfo(info);
+ public CharSequence getAccessibilityClassName() {
+ return DatePicker.class.getName();
}
@Override
@@ -472,8 +467,6 @@ public class DatePicker extends FrameLayout {
boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event);
void onPopulateAccessibilityEvent(AccessibilityEvent event);
- void onInitializeAccessibilityEvent(AccessibilityEvent event);
- void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info);
}
/**
@@ -888,16 +881,6 @@ public class DatePicker extends FrameLayout {
event.getText().add(selectedDateUtterance);
}
- @Override
- public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
- event.setClassName(DatePicker.class.getName());
- }
-
- @Override
- public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
- info.setClassName(DatePicker.class.getName());
- }
-
/**
* Sets the current locale.
*
diff --git a/core/java/android/widget/DatePickerCalendarDelegate.java b/core/java/android/widget/DatePickerCalendarDelegate.java
index e75643ab..85b4d30 100644
--- a/core/java/android/widget/DatePickerCalendarDelegate.java
+++ b/core/java/android/widget/DatePickerCalendarDelegate.java
@@ -145,8 +145,8 @@ class DatePickerCalendarDelegate extends DatePicker.AbstractDatePickerDelegate i
// Use Theme attributes if possible
final int dayOfWeekTextAppearanceResId = a.getResourceId(
- R.styleable.DatePicker_dayOfWeekTextAppearance, -1);
- if (dayOfWeekTextAppearanceResId != -1) {
+ R.styleable.DatePicker_dayOfWeekTextAppearance, 0);
+ if (dayOfWeekTextAppearanceResId != 0) {
mDayOfWeekView.setTextAppearance(context, dayOfWeekTextAppearanceResId);
}
@@ -157,8 +157,8 @@ class DatePickerCalendarDelegate extends DatePicker.AbstractDatePickerDelegate i
final int headerSelectedTextColor = a.getColor(
R.styleable.DatePicker_headerSelectedTextColor, defaultHighlightColor);
final int monthTextAppearanceResId = a.getResourceId(
- R.styleable.DatePicker_headerMonthTextAppearance, -1);
- if (monthTextAppearanceResId != -1) {
+ R.styleable.DatePicker_headerMonthTextAppearance, 0);
+ if (monthTextAppearanceResId != 0) {
mHeaderMonthTextView.setTextAppearance(context, monthTextAppearanceResId);
}
mHeaderMonthTextView.setTextColor(ColorStateList.addFirstIfMissing(
@@ -166,18 +166,18 @@ class DatePickerCalendarDelegate extends DatePicker.AbstractDatePickerDelegate i
headerSelectedTextColor));
final int dayOfMonthTextAppearanceResId = a.getResourceId(
- R.styleable.DatePicker_headerDayOfMonthTextAppearance, -1);
- if (dayOfMonthTextAppearanceResId != -1) {
+ R.styleable.DatePicker_headerDayOfMonthTextAppearance, 0);
+ if (dayOfMonthTextAppearanceResId != 0) {
mHeaderDayOfMonthTextView.setTextAppearance(context, dayOfMonthTextAppearanceResId);
}
mHeaderDayOfMonthTextView.setTextColor(ColorStateList.addFirstIfMissing(
mHeaderDayOfMonthTextView.getTextColors(), R.attr.state_selected,
headerSelectedTextColor));
- final int yearTextAppearanceResId = a.getResourceId(
- R.styleable.DatePicker_headerYearTextAppearance, -1);
- if (yearTextAppearanceResId != -1) {
- mHeaderYearTextView.setTextAppearance(context, yearTextAppearanceResId);
+ final int headerYearTextAppearanceResId = a.getResourceId(
+ R.styleable.DatePicker_headerYearTextAppearance, 0);
+ if (headerYearTextAppearanceResId != 0) {
+ mHeaderYearTextView.setTextAppearance(context, headerYearTextAppearanceResId);
}
mHeaderYearTextView.setTextColor(ColorStateList.addFirstIfMissing(
mHeaderYearTextView.getTextColors(), R.attr.state_selected,
@@ -193,16 +193,23 @@ class DatePickerCalendarDelegate extends DatePicker.AbstractDatePickerDelegate i
mYearPickerView = new YearPickerView(mContext);
mYearPickerView.init(this);
- final int yearSelectedCircleColor = a.getColor(R.styleable.DatePicker_yearListSelectorColor,
- defaultHighlightColor);
- mYearPickerView.setYearSelectedCircleColor(yearSelectedCircleColor);
+ final ColorStateList yearBackgroundColor = a.getColorStateList(
+ R.styleable.DatePicker_yearListSelectorColor);
+ mYearPickerView.setYearBackgroundColor(yearBackgroundColor);
+
+ final int yearTextAppearanceResId = a.getResourceId(
+ R.styleable.DatePicker_yearListItemTextAppearance, 0);
+ if (yearTextAppearanceResId != 0) {
+ mYearPickerView.setYearTextAppearance(yearTextAppearanceResId);
+ }
final ColorStateList calendarTextColor = a.getColorStateList(
R.styleable.DatePicker_calendarTextColor);
- final int calendarSelectedTextColor = a.getColor(
- R.styleable.DatePicker_calendarSelectedTextColor, defaultHighlightColor);
- mDayPickerView.setCalendarTextColor(ColorStateList.addFirstIfMissing(
- calendarTextColor, R.attr.state_selected, calendarSelectedTextColor));
+ mDayPickerView.setCalendarTextColor(calendarTextColor);
+
+ final ColorStateList calendarDayBackgroundColor = a.getColorStateList(
+ R.styleable.DatePicker_calendarDayBackgroundColor);
+ mDayPickerView.setCalendarDayBackgroundColor(calendarDayBackgroundColor);
mDayPickerDescription = res.getString(R.string.day_picker_description);
mSelectDay = res.getString(R.string.select_day);
@@ -577,14 +584,8 @@ class DatePickerCalendarDelegate extends DatePicker.AbstractDatePickerDelegate i
event.getText().add(mCurrentDate.getTime().toString());
}
- @Override
- public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
- event.setClassName(DatePicker.class.getName());
- }
-
- @Override
- public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
- info.setClassName(DatePicker.class.getName());
+ public CharSequence getAccessibilityClassName() {
+ return DatePicker.class.getName();
}
@Override
diff --git a/core/java/android/widget/DayPickerView.java b/core/java/android/widget/DayPickerView.java
index 7db3fb9..65af45d 100644
--- a/core/java/android/widget/DayPickerView.java
+++ b/core/java/android/widget/DayPickerView.java
@@ -305,6 +305,10 @@ class DayPickerView extends ListView implements AbsListView.OnScrollListener {
mAdapter.setCalendarTextColor(colors);
}
+ void setCalendarDayBackgroundColor(ColorStateList dayBackgroundColor) {
+ mAdapter.setCalendarDayBackgroundColor(dayBackgroundColor);
+ }
+
void setCalendarTextAppearance(int resId) {
mAdapter.setCalendarTextAppearance(resId);
}
@@ -459,9 +463,10 @@ class DayPickerView extends ListView implements AbsListView.OnScrollListener {
mYearFormat = new SimpleDateFormat("yyyy", Locale.getDefault());
}
+ /** @hide */
@Override
- public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
- super.onInitializeAccessibilityEvent(event);
+ public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) {
+ super.onInitializeAccessibilityEventInternal(event);
event.setItemCount(-1);
}
@@ -476,22 +481,26 @@ class DayPickerView extends ListView implements AbsListView.OnScrollListener {
/**
* Necessary for accessibility, to ensure we support "scrolling" forward and backward
* in the month list.
+ *
+ * @hide
*/
@Override
- public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(info);
+ public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
+ super.onInitializeAccessibilityNodeInfoInternal(info);
info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_FORWARD);
info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_BACKWARD);
}
/**
* When scroll forward/backward events are received, announce the newly scrolled-to month.
+ *
+ * @hide
*/
@Override
- public boolean performAccessibilityAction(int action, Bundle arguments) {
+ public boolean performAccessibilityActionInternal(int action, Bundle arguments) {
if (action != AccessibilityNodeInfo.ACTION_SCROLL_FORWARD &&
action != AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD) {
- return super.performAccessibilityAction(action, arguments);
+ return super.performAccessibilityActionInternal(action, arguments);
}
// Figure out what month is showing.
diff --git a/core/java/android/widget/DigitalClock.java b/core/java/android/widget/DigitalClock.java
index b6c1e5b..b936a5b 100644
--- a/core/java/android/widget/DigitalClock.java
+++ b/core/java/android/widget/DigitalClock.java
@@ -116,16 +116,8 @@ public class DigitalClock extends TextView {
}
@Override
- public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
- super.onInitializeAccessibilityEvent(event);
+ public CharSequence getAccessibilityClassName() {
//noinspection deprecation
- event.setClassName(DigitalClock.class.getName());
- }
-
- @Override
- public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(info);
- //noinspection deprecation
- info.setClassName(DigitalClock.class.getName());
+ return DigitalClock.class.getName();
}
}
diff --git a/core/java/android/widget/EditText.java b/core/java/android/widget/EditText.java
index a8ff562..24cc2d8 100644
--- a/core/java/android/widget/EditText.java
+++ b/core/java/android/widget/EditText.java
@@ -123,19 +123,13 @@ public class EditText extends TextView {
}
@Override
- public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
- super.onInitializeAccessibilityEvent(event);
- event.setClassName(EditText.class.getName());
+ public CharSequence getAccessibilityClassName() {
+ return EditText.class.getName();
}
+ /** @hide */
@Override
- public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(info);
- info.setClassName(EditText.class.getName());
- }
-
- @Override
- public boolean performAccessibilityAction(int action, Bundle arguments) {
+ public boolean performAccessibilityActionInternal(int action, Bundle arguments) {
switch (action) {
case AccessibilityNodeInfo.ACTION_SET_TEXT: {
CharSequence text = (arguments != null) ? arguments.getCharSequence(
@@ -147,7 +141,7 @@ public class EditText extends TextView {
return true;
}
default: {
- return super.performAccessibilityAction(action, arguments);
+ return super.performAccessibilityActionInternal(action, arguments);
}
}
}
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java
index 936da32..d5166f3 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -1402,7 +1402,7 @@ public class Editor {
blockDisplayList.setLeftTopRightBottom(left, top, right, bottom);
}
- ((HardwareCanvas) canvas).drawRenderNode(blockDisplayList, null,
+ ((HardwareCanvas) canvas).drawRenderNode(blockDisplayList,
0 /* no child clipping, our TextView parent enforces it */);
endOfPreviousBlock = blockEndLine;
diff --git a/core/java/android/widget/ExpandableListView.java b/core/java/android/widget/ExpandableListView.java
index 70089e0..323ddb6 100644
--- a/core/java/android/widget/ExpandableListView.java
+++ b/core/java/android/widget/ExpandableListView.java
@@ -1342,14 +1342,7 @@ public class ExpandableListView extends ListView {
}
@Override
- public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
- super.onInitializeAccessibilityEvent(event);
- event.setClassName(ExpandableListView.class.getName());
- }
-
- @Override
- public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(info);
- info.setClassName(ExpandableListView.class.getName());
+ public CharSequence getAccessibilityClassName() {
+ return ExpandableListView.class.getName();
}
}
diff --git a/core/java/android/widget/FrameLayout.java b/core/java/android/widget/FrameLayout.java
index d974c29..57bbc42 100644
--- a/core/java/android/widget/FrameLayout.java
+++ b/core/java/android/widget/FrameLayout.java
@@ -18,6 +18,7 @@ package android.widget;
import java.util.ArrayList;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
import android.content.res.ColorStateList;
@@ -203,11 +204,15 @@ public class FrameLayout extends ViewGroup {
}
@Override
- @RemotableViewMethod
- public void setVisibility(@Visibility int visibility) {
- super.setVisibility(visibility);
- if (mForeground != null) {
- mForeground.setVisible(visibility == VISIBLE, false);
+ protected void onVisibilityChanged(@NonNull View changedView, @Visibility int visibility) {
+ super.onVisibilityChanged(changedView, visibility);
+
+ final Drawable dr = mForeground;
+ if (dr != null) {
+ final boolean visible = visibility == VISIBLE && getVisibility() == VISIBLE;
+ if (visible != dr.isVisible()) {
+ dr.setVisible(visible, false);
+ }
}
}
@@ -703,17 +708,9 @@ public class FrameLayout extends ViewGroup {
return new LayoutParams(p);
}
-
- @Override
- public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
- super.onInitializeAccessibilityEvent(event);
- event.setClassName(FrameLayout.class.getName());
- }
-
@Override
- public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(info);
- info.setClassName(FrameLayout.class.getName());
+ public CharSequence getAccessibilityClassName() {
+ return FrameLayout.class.getName();
}
/**
diff --git a/core/java/android/widget/Gallery.java b/core/java/android/widget/Gallery.java
index f7c839f..ac19e6d 100644
--- a/core/java/android/widget/Gallery.java
+++ b/core/java/android/widget/Gallery.java
@@ -1374,15 +1374,14 @@ public class Gallery extends AbsSpinner implements GestureDetector.OnGestureList
}
@Override
- public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
- super.onInitializeAccessibilityEvent(event);
- event.setClassName(Gallery.class.getName());
+ public CharSequence getAccessibilityClassName() {
+ return Gallery.class.getName();
}
+ /** @hide */
@Override
- public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(info);
- info.setClassName(Gallery.class.getName());
+ public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
+ super.onInitializeAccessibilityNodeInfoInternal(info);
info.setScrollable(mItemCount > 1);
if (isEnabled()) {
if (mItemCount > 0 && mSelectedPosition < mItemCount - 1) {
@@ -1394,9 +1393,10 @@ public class Gallery extends AbsSpinner implements GestureDetector.OnGestureList
}
}
+ /** @hide */
@Override
- public boolean performAccessibilityAction(int action, Bundle arguments) {
- if (super.performAccessibilityAction(action, arguments)) {
+ public boolean performAccessibilityActionInternal(int action, Bundle arguments) {
+ if (super.performAccessibilityActionInternal(action, arguments)) {
return true;
}
switch (action) {
diff --git a/core/java/android/widget/GridLayout.java b/core/java/android/widget/GridLayout.java
index 161ae7e..41ddc98 100644
--- a/core/java/android/widget/GridLayout.java
+++ b/core/java/android/widget/GridLayout.java
@@ -1191,15 +1191,8 @@ public class GridLayout extends ViewGroup {
}
@Override
- public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
- super.onInitializeAccessibilityEvent(event);
- event.setClassName(GridLayout.class.getName());
- }
-
- @Override
- public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(info);
- info.setClassName(GridLayout.class.getName());
+ public CharSequence getAccessibilityClassName() {
+ return GridLayout.class.getName();
}
// Inner classes
diff --git a/core/java/android/widget/GridView.java b/core/java/android/widget/GridView.java
index efd6fc0..8b2217c 100644
--- a/core/java/android/widget/GridView.java
+++ b/core/java/android/widget/GridView.java
@@ -2342,15 +2342,14 @@ public class GridView extends AbsListView {
}
@Override
- public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
- super.onInitializeAccessibilityEvent(event);
- event.setClassName(GridView.class.getName());
+ public CharSequence getAccessibilityClassName() {
+ return GridView.class.getName();
}
+ /** @hide */
@Override
- public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(info);
- info.setClassName(GridView.class.getName());
+ public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
+ super.onInitializeAccessibilityNodeInfoInternal(info);
final int columnsCount = getNumColumns();
final int rowsCount = getCount() / columnsCount;
diff --git a/core/java/android/widget/HorizontalScrollView.java b/core/java/android/widget/HorizontalScrollView.java
index 1b93b97..b37495f 100644
--- a/core/java/android/widget/HorizontalScrollView.java
+++ b/core/java/android/widget/HorizontalScrollView.java
@@ -762,9 +762,10 @@ public class HorizontalScrollView extends FrameLayout {
awakenScrollBars();
}
+ /** @hide */
@Override
- public boolean performAccessibilityAction(int action, Bundle arguments) {
- if (super.performAccessibilityAction(action, arguments)) {
+ public boolean performAccessibilityActionInternal(int action, Bundle arguments) {
+ if (super.performAccessibilityActionInternal(action, arguments)) {
return true;
}
switch (action) {
@@ -795,9 +796,14 @@ public class HorizontalScrollView extends FrameLayout {
}
@Override
- public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(info);
- info.setClassName(HorizontalScrollView.class.getName());
+ public CharSequence getAccessibilityClassName() {
+ return HorizontalScrollView.class.getName();
+ }
+
+ /** @hide */
+ @Override
+ public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
+ super.onInitializeAccessibilityNodeInfoInternal(info);
final int scrollRange = getScrollRange();
if (scrollRange > 0) {
info.setScrollable(true);
@@ -810,10 +816,10 @@ public class HorizontalScrollView extends FrameLayout {
}
}
+ /** @hide */
@Override
- public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
- super.onInitializeAccessibilityEvent(event);
- event.setClassName(HorizontalScrollView.class.getName());
+ public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) {
+ super.onInitializeAccessibilityEventInternal(event);
event.setScrollable(getScrollRange() > 0);
event.setScrollX(mScrollX);
event.setScrollY(mScrollY);
diff --git a/core/java/android/widget/ImageButton.java b/core/java/android/widget/ImageButton.java
index 3a20628..22f9c10 100644
--- a/core/java/android/widget/ImageButton.java
+++ b/core/java/android/widget/ImageButton.java
@@ -93,14 +93,7 @@ public class ImageButton extends ImageView {
}
@Override
- public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
- super.onInitializeAccessibilityEvent(event);
- event.setClassName(ImageButton.class.getName());
- }
-
- @Override
- public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(info);
- info.setClassName(ImageButton.class.getName());
+ public CharSequence getAccessibilityClassName() {
+ return ImageButton.class.getName();
}
}
diff --git a/core/java/android/widget/ImageSwitcher.java b/core/java/android/widget/ImageSwitcher.java
index c048970..80e908a 100644
--- a/core/java/android/widget/ImageSwitcher.java
+++ b/core/java/android/widget/ImageSwitcher.java
@@ -57,14 +57,7 @@ public class ImageSwitcher extends ViewSwitcher
}
@Override
- public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
- super.onInitializeAccessibilityEvent(event);
- event.setClassName(ImageSwitcher.class.getName());
- }
-
- @Override
- public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(info);
- info.setClassName(ImageSwitcher.class.getName());
+ public CharSequence getAccessibilityClassName() {
+ return ImageSwitcher.class.getName();
}
}
diff --git a/core/java/android/widget/ImageView.java b/core/java/android/widget/ImageView.java
index c68bfca..dd9bdb6 100644
--- a/core/java/android/widget/ImageView.java
+++ b/core/java/android/widget/ImageView.java
@@ -127,15 +127,16 @@ public class ImageView extends View {
initImageView();
}
- public ImageView(Context context, AttributeSet attrs) {
+ public ImageView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
- public ImageView(Context context, AttributeSet attrs, int defStyleAttr) {
+ public ImageView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
this(context, attrs, defStyleAttr, 0);
}
- public ImageView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ public ImageView(Context context, @Nullable AttributeSet attrs, int defStyleAttr,
+ int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
initImageView();
@@ -239,9 +240,10 @@ public class ImageView extends View {
return (getBackground() != null && getBackground().getCurrent() != null);
}
+ /** @hide */
@Override
- public void onPopulateAccessibilityEvent(AccessibilityEvent event) {
- super.onPopulateAccessibilityEvent(event);
+ public void onPopulateAccessibilityEventInternal(AccessibilityEvent event) {
+ super.onPopulateAccessibilityEventInternal(event);
CharSequence contentDescription = getContentDescription();
if (!TextUtils.isEmpty(contentDescription)) {
event.getText().add(contentDescription);
@@ -1419,14 +1421,7 @@ public class ImageView extends View {
}
@Override
- public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
- super.onInitializeAccessibilityEvent(event);
- event.setClassName(ImageView.class.getName());
- }
-
- @Override
- public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(info);
- info.setClassName(ImageView.class.getName());
+ public CharSequence getAccessibilityClassName() {
+ return ImageView.class.getName();
}
}
diff --git a/core/java/android/widget/LinearLayout.java b/core/java/android/widget/LinearLayout.java
index 6476cdc..c0f63d2 100644
--- a/core/java/android/widget/LinearLayout.java
+++ b/core/java/android/widget/LinearLayout.java
@@ -1807,15 +1807,8 @@ public class LinearLayout extends ViewGroup {
}
@Override
- public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
- super.onInitializeAccessibilityEvent(event);
- event.setClassName(LinearLayout.class.getName());
- }
-
- @Override
- public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(info);
- info.setClassName(LinearLayout.class.getName());
+ public CharSequence getAccessibilityClassName() {
+ return LinearLayout.class.getName();
}
/**
diff --git a/core/java/android/widget/ListView.java b/core/java/android/widget/ListView.java
index ba6f061..0aaef6d 100644
--- a/core/java/android/widget/ListView.java
+++ b/core/java/android/widget/ListView.java
@@ -3879,15 +3879,14 @@ public class ListView extends AbsListView {
}
@Override
- public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
- super.onInitializeAccessibilityEvent(event);
- event.setClassName(ListView.class.getName());
+ public CharSequence getAccessibilityClassName() {
+ return ListView.class.getName();
}
+ /** @hide */
@Override
- public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(info);
- info.setClassName(ListView.class.getName());
+ public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
+ super.onInitializeAccessibilityNodeInfoInternal(info);
final int rowsCount = getCount();
final int selectionMode = getSelectionModeForAccessibility();
diff --git a/core/java/android/widget/MediaController.java b/core/java/android/widget/MediaController.java
index f1aaa4d..0b63843 100644
--- a/core/java/android/widget/MediaController.java
+++ b/core/java/android/widget/MediaController.java
@@ -28,6 +28,7 @@ import android.view.Gravity;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.MotionEvent;
+import android.view.PhoneWindow;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
@@ -36,8 +37,6 @@ import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.SeekBar.OnSeekBarChangeListener;
-import com.android.internal.policy.PolicyManager;
-
import java.util.Formatter;
import java.util.Locale;
@@ -128,7 +127,7 @@ public class MediaController extends FrameLayout {
private void initFloatingWindow() {
mWindowManager = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);
- mWindow = PolicyManager.makeNewWindow(mContext);
+ mWindow = new PhoneWindow(mContext);
mWindow.setWindowManager(mWindowManager, null, null);
mWindow.requestFeature(Window.FEATURE_NO_TITLE);
mDecor = mWindow.getDecorView();
@@ -626,15 +625,8 @@ public class MediaController extends FrameLayout {
}
@Override
- public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
- super.onInitializeAccessibilityEvent(event);
- event.setClassName(MediaController.class.getName());
- }
-
- @Override
- public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(info);
- info.setClassName(MediaController.class.getName());
+ public CharSequence getAccessibilityClassName() {
+ return MediaController.class.getName();
}
private View.OnClickListener mRewListener = new View.OnClickListener() {
diff --git a/core/java/android/widget/MultiAutoCompleteTextView.java b/core/java/android/widget/MultiAutoCompleteTextView.java
index cbd01b0..c10e581 100644
--- a/core/java/android/widget/MultiAutoCompleteTextView.java
+++ b/core/java/android/widget/MultiAutoCompleteTextView.java
@@ -203,15 +203,8 @@ public class MultiAutoCompleteTextView extends AutoCompleteTextView {
}
@Override
- public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
- super.onInitializeAccessibilityEvent(event);
- event.setClassName(MultiAutoCompleteTextView.class.getName());
- }
-
- @Override
- public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(info);
- info.setClassName(MultiAutoCompleteTextView.class.getName());
+ public CharSequence getAccessibilityClassName() {
+ return MultiAutoCompleteTextView.class.getName();
}
public static interface Tokenizer {
diff --git a/core/java/android/widget/NumberPicker.java b/core/java/android/widget/NumberPicker.java
index ee17b78..16dc26d 100644
--- a/core/java/android/widget/NumberPicker.java
+++ b/core/java/android/widget/NumberPicker.java
@@ -1558,9 +1558,10 @@ public class NumberPicker extends LinearLayout {
}
}
+ /** @hide */
@Override
- public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
- super.onInitializeAccessibilityEvent(event);
+ public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) {
+ super.onInitializeAccessibilityEventInternal(event);
event.setClassName(NumberPicker.class.getName());
event.setScrollable(true);
event.setScrollY((mMinValue + mValue) * mSelectorElementHeight);
diff --git a/core/java/android/widget/PopupMenu.java b/core/java/android/widget/PopupMenu.java
index 2708398..06ac1c3 100644
--- a/core/java/android/widget/PopupMenu.java
+++ b/core/java/android/widget/PopupMenu.java
@@ -115,6 +115,29 @@ public class PopupMenu implements MenuBuilder.Callback, MenuPresenter.Callback {
}
/**
+ * Sets the gravity used to align the popup window to its anchor view.
+ * <p>
+ * If the popup is showing, calling this method will take effect only
+ * the next time the popup is shown.
+ *
+ * @param gravity the gravity used to align the popup window
+ *
+ * @see #getGravity()
+ */
+ public void setGravity(int gravity) {
+ mPopup.setGravity(gravity);
+ }
+
+ /**
+ * @return the gravity used to align the popup window to its anchor view
+ *
+ * @see #setGravity(int)
+ */
+ public int getGravity() {
+ return mPopup.getGravity();
+ }
+
+ /**
* Returns an {@link OnTouchListener} that can be added to the anchor view
* to implement drag-to-open behavior.
* <p>
diff --git a/core/java/android/widget/PopupWindow.java b/core/java/android/widget/PopupWindow.java
index 5419ab6..a929f3d 100644
--- a/core/java/android/widget/PopupWindow.java
+++ b/core/java/android/widget/PopupWindow.java
@@ -18,17 +18,20 @@ package android.widget;
import com.android.internal.R;
-import android.animation.ValueAnimator;
import android.content.Context;
import android.content.res.Resources;
import android.content.res.TypedArray;
-import android.graphics.Insets;
import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.StateListDrawable;
import android.os.Build;
import android.os.IBinder;
+import android.transition.Transition;
+import android.transition.Transition.EpicenterCallback;
+import android.transition.TransitionInflater;
+import android.transition.TransitionManager;
+import android.transition.TransitionSet;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.KeyEvent;
@@ -41,12 +44,13 @@ import android.view.ViewTreeObserver.OnScrollChangedListener;
import android.view.WindowManager;
import java.lang.ref.WeakReference;
+import java.util.List;
/**
* <p>A popup window that can be used to display an arbitrary view. The popup
* window is a floating container that appears on top of the current
* activity.</p>
- *
+ *
* @see android.widget.AutoCompleteTextView
* @see android.widget.Spinner
*/
@@ -58,7 +62,7 @@ public class PopupWindow {
* it doesn't.
*/
public static final int INPUT_METHOD_FROM_FOCUSABLE = 0;
-
+
/**
* Mode for {@link #setInputMethodMode(int)}: this popup always needs to
* work with an input method, regardless of whether it is focusable. This
@@ -66,7 +70,7 @@ public class PopupWindow {
* the input method while it is shown.
*/
public static final int INPUT_METHOD_NEEDED = 1;
-
+
/**
* Mode for {@link #setInputMethodMode(int)}: this popup never needs to
* work with an input method, regardless of whether it is focusable. This
@@ -77,14 +81,32 @@ public class PopupWindow {
private static final int DEFAULT_ANCHORED_GRAVITY = Gravity.TOP | Gravity.START;
+ /**
+ * Default animation style indicating that separate animations should be
+ * used for top/bottom anchoring states.
+ */
+ private static final int ANIMATION_STYLE_DEFAULT = -1;
+
+ private final int[] mDrawingLocation = new int[2];
+ private final int[] mScreenLocation = new int[2];
+ private final Rect mTempRect = new Rect();
+ private final Rect mAnchorBounds = new Rect();
+
private Context mContext;
private WindowManager mWindowManager;
-
+
private boolean mIsShowing;
private boolean mIsDropdown;
+ /** View that handles event dispatch and content transitions. */
+ private PopupDecorView mDecorView;
+
+ /** View that holds the popup background. May be the content view. */
+ private View mBackgroundView;
+
+ /** The contents of the popup. */
private View mContentView;
- private View mPopupView;
+
private boolean mFocusable;
private int mInputMethodMode = INPUT_METHOD_FROM_FOCUSABLE;
private int mSoftInputMode = WindowManager.LayoutParams.SOFT_INPUT_STATE_UNCHANGED;
@@ -114,49 +136,67 @@ public class PopupWindow {
private float mElevation;
- private int[] mDrawingLocation = new int[2];
- private int[] mScreenLocation = new int[2];
- private Rect mTempRect = new Rect();
-
private Drawable mBackground;
private Drawable mAboveAnchorBackgroundDrawable;
private Drawable mBelowAnchorBackgroundDrawable;
- // Temporary animation centers. Should be moved into window params?
- private int mAnchorRelativeX;
- private int mAnchorRelativeY;
+ private Transition mEnterTransition;
+ private Transition mExitTransition;
private boolean mAboveAnchor;
private int mWindowLayoutType = WindowManager.LayoutParams.TYPE_APPLICATION_PANEL;
-
+
private OnDismissListener mOnDismissListener;
private boolean mIgnoreCheekPress = false;
- private int mAnimationStyle = -1;
-
+ private int mAnimationStyle = ANIMATION_STYLE_DEFAULT;
+
private static final int[] ABOVE_ANCHOR_STATE_SET = new int[] {
com.android.internal.R.attr.state_above_anchor
};
private WeakReference<View> mAnchor;
- private final OnScrollChangedListener mOnScrollChangedListener =
- new OnScrollChangedListener() {
- @Override
- public void onScrollChanged() {
- final View anchor = mAnchor != null ? mAnchor.get() : null;
- if (anchor != null && mPopupView != null) {
- final WindowManager.LayoutParams p = (WindowManager.LayoutParams)
- mPopupView.getLayoutParams();
-
- updateAboveAnchor(findDropDownPosition(anchor, p, mAnchorXoff, mAnchorYoff,
- mAnchoredGravity));
- update(p.x, p.y, -1, -1, true);
- }
+ private final EpicenterCallback mEpicenterCallback = new EpicenterCallback() {
+ @Override
+ public Rect onGetEpicenter(Transition transition) {
+ final View anchor = mAnchor.get();
+ final View decor = mDecorView;
+ if (anchor == null || decor == null) {
+ return null;
+ }
+
+ final Rect anchorBounds = mAnchorBounds;
+ final int[] anchorLocation = mAnchor.get().getLocationOnScreen();
+ final int[] popupLocation = mDecorView.getLocationOnScreen();
+
+ // Compute the position of the anchor relative to the popup.
+ anchorBounds.set(0, 0, anchor.getWidth(), anchor.getHeight());
+ anchorBounds.offset(anchorLocation[0] - popupLocation[0],
+ anchorLocation[1] - popupLocation[1]);
+
+ return anchorBounds;
+ }
+ };
+
+ private final OnScrollChangedListener mOnScrollChangedListener = new OnScrollChangedListener() {
+ @Override
+ public void onScrollChanged() {
+ final View anchor = mAnchor != null ? mAnchor.get() : null;
+ if (anchor != null && mDecorView != null) {
+ final WindowManager.LayoutParams p = (WindowManager.LayoutParams)
+ mDecorView.getLayoutParams();
+
+ updateAboveAnchor(findDropDownPosition(anchor, p, mAnchorXoff, mAnchorYoff,
+ mAnchoredGravity));
+ update(p.x, p.y, -1, -1, true);
}
- };
+ }
+ };
- private int mAnchorXoff, mAnchorYoff, mAnchoredGravity;
+ private int mAnchorXoff;
+ private int mAnchorYoff;
+ private int mAnchoredGravity;
private boolean mOverlapAnchor;
private boolean mPopupViewInitialLayoutDirectionInherited;
@@ -187,15 +227,15 @@ public class PopupWindow {
public PopupWindow(Context context, AttributeSet attrs, int defStyleAttr) {
this(context, attrs, defStyleAttr, 0);
}
-
+
/**
* <p>Create a new, empty, non focusable popup window of dimension (0,0).</p>
- *
+ *
* <p>The popup does not provide a background.</p>
*/
public PopupWindow(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
mContext = context;
- mWindowManager = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);
+ mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
final TypedArray a = context.obtainStyledAttributes(
attrs, R.styleable.PopupWindow, defStyleAttr, defStyleRes);
@@ -203,11 +243,34 @@ public class PopupWindow {
mElevation = a.getDimension(R.styleable.PopupWindow_popupElevation, 0);
mOverlapAnchor = a.getBoolean(R.styleable.PopupWindow_overlapAnchor, false);
- final int animStyle = a.getResourceId(R.styleable.PopupWindow_popupAnimationStyle, -1);
- mAnimationStyle = animStyle == R.style.Animation_PopupWindow ? -1 : animStyle;
+ // Preserve default behavior from Gingerbread. If the animation is
+ // undefined or explicitly specifies the Gingerbread animation style,
+ // use a sentinel value.
+ if (a.hasValueOrEmpty(R.styleable.PopupWindow_popupAnimationStyle)) {
+ final int animStyle = a.getResourceId(R.styleable.PopupWindow_popupAnimationStyle, 0);
+ if (animStyle == R.style.Animation_PopupWindow) {
+ mAnimationStyle = ANIMATION_STYLE_DEFAULT;
+ } else {
+ mAnimationStyle = animStyle;
+ }
+ } else {
+ mAnimationStyle = ANIMATION_STYLE_DEFAULT;
+ }
+
+ final Transition enterTransition = getTransition(a.getResourceId(
+ R.styleable.PopupWindow_popupEnterTransition, 0));
+ final Transition exitTransition;
+ if (a.hasValueOrEmpty(R.styleable.PopupWindow_popupExitTransition)) {
+ exitTransition = getTransition(a.getResourceId(
+ R.styleable.PopupWindow_popupExitTransition, 0));
+ } else {
+ exitTransition = enterTransition == null ? null : enterTransition.clone();
+ }
a.recycle();
+ setEnterTransition(enterTransition);
+ setExitTransition(exitTransition);
setBackgroundDrawable(bg);
}
@@ -288,6 +351,37 @@ public class PopupWindow {
setFocusable(focusable);
}
+ public void setEnterTransition(Transition enterTransition) {
+ mEnterTransition = enterTransition;
+
+ if (mEnterTransition != null) {
+ mEnterTransition.setEpicenterCallback(mEpicenterCallback);
+ }
+ }
+
+ public void setExitTransition(Transition exitTransition) {
+ mExitTransition = exitTransition;
+
+ if (mExitTransition != null) {
+ mExitTransition.setEpicenterCallback(mEpicenterCallback);
+ }
+ }
+
+ private Transition getTransition(int resId) {
+ if (resId != 0 && resId != R.transition.no_transition) {
+ final TransitionInflater inflater = TransitionInflater.from(mContext);
+ final Transition transition = inflater.inflateTransition(resId);
+ if (transition != null) {
+ final boolean isEmpty = transition instanceof TransitionSet
+ && ((TransitionSet) transition).getTransitionCount() == 0;
+ if (!isEmpty) {
+ return transition;
+ }
+ }
+ }
+ return null;
+ }
+
/**
* Return the drawable used as the popup window's background.
*
@@ -381,7 +475,7 @@ public class PopupWindow {
* Set the flag on popup to ignore cheek press events; by default this flag
* is set to false
* which means the popup will not ignore cheek press dispatch events.
- *
+ *
* <p>If the popup is showing, calling this method will take effect only
* the next time the popup is shown or through a manual call to one of
* the {@link #update()} methods.</p>
@@ -391,7 +485,7 @@ public class PopupWindow {
public void setIgnoreCheekPress() {
mIgnoreCheekPress = true;
}
-
+
/**
* <p>Change the animation style resource for this popup.</p>
@@ -403,13 +497,13 @@ public class PopupWindow {
* @param animationStyle animation style to use when the popup appears
* and disappears. Set to -1 for the default animation, 0 for no
* animation, or a resource identifier for an explicit animation.
- *
+ *
* @see #update()
*/
public void setAnimationStyle(int animationStyle) {
mAnimationStyle = animationStyle;
}
-
+
/**
* <p>Return the view used as the content of the popup window.</p>
*
@@ -493,7 +587,7 @@ public class PopupWindow {
* @param focusable true if the popup should grab focus, false otherwise.
*
* @see #isFocusable()
- * @see #isShowing()
+ * @see #isShowing()
* @see #update()
*/
public void setFocusable(boolean focusable) {
@@ -502,23 +596,23 @@ public class PopupWindow {
/**
* Return the current value in {@link #setInputMethodMode(int)}.
- *
+ *
* @see #setInputMethodMode(int)
*/
public int getInputMethodMode() {
return mInputMethodMode;
-
+
}
-
+
/**
* Control how the popup operates with an input method: one of
* {@link #INPUT_METHOD_FROM_FOCUSABLE}, {@link #INPUT_METHOD_NEEDED},
* or {@link #INPUT_METHOD_NOT_NEEDED}.
- *
+ *
* <p>If the popup is showing, calling this method will take effect only
* the next time the popup is shown or through a manual call to one of
* the {@link #update()} methods.</p>
- *
+ *
* @see #getInputMethodMode()
* @see #update()
*/
@@ -549,12 +643,12 @@ public class PopupWindow {
public int getSoftInputMode() {
return mSoftInputMode;
}
-
+
/**
* <p>Indicates whether the popup window receives touch events.</p>
- *
+ *
* @return true if the popup is touchable, false otherwise
- *
+ *
* @see #setTouchable(boolean)
*/
public boolean isTouchable() {
@@ -573,7 +667,7 @@ public class PopupWindow {
* @param touchable true if the popup should receive touch events, false otherwise
*
* @see #isTouchable()
- * @see #isShowing()
+ * @see #isShowing()
* @see #update()
*/
public void setTouchable(boolean touchable) {
@@ -583,9 +677,9 @@ public class PopupWindow {
/**
* <p>Indicates whether the popup window will be informed of touch events
* outside of its window.</p>
- *
+ *
* @return true if the popup is outside touchable, false otherwise
- *
+ *
* @see #setOutsideTouchable(boolean)
*/
public boolean isOutsideTouchable() {
@@ -606,7 +700,7 @@ public class PopupWindow {
* touch events, false otherwise
*
* @see #isOutsideTouchable()
- * @see #isShowing()
+ * @see #isShowing()
* @see #update()
*/
public void setOutsideTouchable(boolean touchable) {
@@ -615,9 +709,9 @@ public class PopupWindow {
/**
* <p>Indicates whether clipping of the popup window is enabled.</p>
- *
+ *
* @return true if the clipping is enabled, false otherwise
- *
+ *
* @see #setClippingEnabled(boolean)
*/
public boolean isClippingEnabled() {
@@ -628,13 +722,13 @@ public class PopupWindow {
* <p>Allows the popup window to extend beyond the bounds of the screen. By default the
* window is clipped to the screen boundaries. Setting this to false will allow windows to be
* accurately positioned.</p>
- *
+ *
* <p>If the popup is showing, calling this method will take effect only
* the next time the popup is shown or through a manual call to one of
* the {@link #update()} methods.</p>
*
* @param enabled false if the window should be allowed to extend outside of the screen
- * @see #isShowing()
+ * @see #isShowing()
* @see #isClippingEnabled()
* @see #update()
*/
@@ -662,12 +756,12 @@ public class PopupWindow {
void setAllowScrollingAnchorParent(boolean enabled) {
mAllowScrollingAnchorParent = enabled;
}
-
+
/**
* <p>Indicates whether the popup window supports splitting touches.</p>
- *
+ *
* @return true if the touch splitting is enabled, false otherwise
- *
+ *
* @see #setSplitTouchEnabled(boolean)
*/
public boolean isSplitTouchEnabled() {
@@ -796,7 +890,7 @@ public class PopupWindow {
* window manager by the popup. By default these are 0, meaning that
* the current width or height is requested as an explicit size from
* the window manager. You can supply
- * {@link ViewGroup.LayoutParams#WRAP_CONTENT} or
+ * {@link ViewGroup.LayoutParams#WRAP_CONTENT} or
* {@link ViewGroup.LayoutParams#MATCH_PARENT} to have that measure
* spec supplied instead, replacing the absolute width and height that
* has been set in the popup.</p>
@@ -817,7 +911,7 @@ public class PopupWindow {
mWidthMode = widthSpec;
mHeightMode = heightSpec;
}
-
+
/**
* <p>Return this popup's height MeasureSpec</p>
*
@@ -838,7 +932,7 @@ public class PopupWindow {
* @param height the height MeasureSpec of the popup
*
* @see #getHeight()
- * @see #isShowing()
+ * @see #isShowing()
*/
public void setHeight(int height) {
mHeight = height;
@@ -849,7 +943,7 @@ public class PopupWindow {
*
* @return the width MeasureSpec of the popup
*
- * @see #setWidth(int)
+ * @see #setWidth(int)
*/
public int getWidth() {
return mWidth;
@@ -871,6 +965,34 @@ public class PopupWindow {
}
/**
+ * Sets whether the popup window should overlap its anchor view when
+ * displayed as a drop-down.
+ * <p>
+ * If the popup is showing, calling this method will take effect only
+ * the next time the popup is shown.
+ *
+ * @param overlapAnchor Whether the popup should overlap its anchor.
+ *
+ * @see #getOverlapAnchor()
+ * @see #isShowing()
+ */
+ public void setOverlapAnchor(boolean overlapAnchor) {
+ mOverlapAnchor = overlapAnchor;
+ }
+
+ /**
+ * Returns whether the popup window should overlap its anchor view when
+ * displayed as a drop-down.
+ *
+ * @return Whether the popup should overlap its anchor.
+ *
+ * @see #setOverlapAnchor(boolean)
+ */
+ public boolean getOverlapAnchor() {
+ return mOverlapAnchor;
+ }
+
+ /**
* <p>Indicate whether this popup window is showing on screen.</p>
*
* @return true if the popup is showing, false otherwise
@@ -887,7 +1009,7 @@ public class PopupWindow {
* a gravity of {@link android.view.Gravity#NO_GRAVITY} is similar to specifying
* <code>Gravity.LEFT | Gravity.TOP</code>.
* </p>
- *
+ *
* @param parent a parent view to get the {@link android.view.View#getWindowToken()} token from
* @param gravity the gravity which controls the placement of the popup window
* @param x the popup's x location offset
@@ -913,32 +1035,34 @@ public class PopupWindow {
return;
}
+ TransitionManager.endTransitions(mDecorView);
+
unregisterForScrollChanged();
mIsShowing = true;
mIsDropdown = false;
- WindowManager.LayoutParams p = createPopupLayout(token);
- p.windowAnimations = computeAnimationResource();
-
+ final WindowManager.LayoutParams p = createPopupLayoutParams(token);
preparePopup(p);
- if (gravity == Gravity.NO_GRAVITY) {
- gravity = Gravity.TOP | Gravity.START;
+
+ // Only override the default if some gravity was specified.
+ if (gravity != Gravity.NO_GRAVITY) {
+ p.gravity = gravity;
}
- p.gravity = gravity;
+
p.x = x;
p.y = y;
- if (mHeightMode < 0) p.height = mLastHeight = mHeightMode;
- if (mWidthMode < 0) p.width = mLastWidth = mWidthMode;
+
invokePopup(p);
}
/**
- * <p>Display the content view in a popup window anchored to the bottom-left
+ * Display the content view in a popup window anchored to the bottom-left
* corner of the anchor view. If there is not enough room on screen to show
* the popup in its entirety, this method tries to find a parent scroll
- * view to scroll. If no parent scroll view can be scrolled, the bottom-left
- * corner of the popup is pinned at the top left corner of the anchor view.</p>
+ * view to scroll. If no parent scroll view can be scrolled, the
+ * bottom-left corner of the popup is pinned at the top left corner of the
+ * anchor view.
*
* @param anchor the view on which to pin the popup window
*
@@ -949,14 +1073,15 @@ public class PopupWindow {
}
/**
- * <p>Display the content view in a popup window anchored to the bottom-left
+ * Display the content view in a popup window anchored to the bottom-left
* corner of the anchor view offset by the specified x and y coordinates.
- * If there is not enough room on screen to show
- * the popup in its entirety, this method tries to find a parent scroll
- * view to scroll. If no parent scroll view can be scrolled, the bottom-left
- * corner of the popup is pinned at the top left corner of the anchor view.</p>
- * <p>If the view later scrolls to move <code>anchor</code> to a different
- * location, the popup will be moved correspondingly.</p>
+ * If there is not enough room on screen to show the popup in its entirety,
+ * this method tries to find a parent scroll view to scroll. If no parent
+ * scroll view can be scrolled, the bottom-left corner of the popup is
+ * pinned at the top left corner of the anchor view.
+ * <p>
+ * If the view later scrolls to move <code>anchor</code> to a different
+ * location, the popup will be moved correspondingly.
*
* @param anchor the view on which to pin the popup window
* @param xoff A horizontal offset from the anchor in pixels
@@ -969,14 +1094,17 @@ public class PopupWindow {
}
/**
- * <p>Display the content view in a popup window anchored to the bottom-left
- * corner of the anchor view offset by the specified x and y coordinates.
- * If there is not enough room on screen to show
- * the popup in its entirety, this method tries to find a parent scroll
- * view to scroll. If no parent scroll view can be scrolled, the bottom-left
- * corner of the popup is pinned at the top left corner of the anchor view.</p>
- * <p>If the view later scrolls to move <code>anchor</code> to a different
- * location, the popup will be moved correspondingly.</p>
+ * Displays the content view in a popup window anchored to the corner of
+ * another view. The window is positioned according to the specified
+ * gravity and offset by the specified x and y coordinates.
+ * <p>
+ * If there is not enough room on screen to show the popup in its entirety,
+ * this method tries to find a parent scroll view to scroll. If no parent
+ * view can be scrolled, the specified vertical gravity will be ignored and
+ * the popup will anchor itself such that it is visible.
+ * <p>
+ * If the view later scrolls to move <code>anchor</code> to a different
+ * location, the popup will be moved correspondingly.
*
* @param anchor the view on which to pin the popup window
* @param xoff A horizontal offset from the anchor in pixels
@@ -990,20 +1118,18 @@ public class PopupWindow {
return;
}
+ TransitionManager.endTransitions(mDecorView);
+
registerForScrollChanged(anchor, xoff, yoff, gravity);
mIsShowing = true;
mIsDropdown = true;
- WindowManager.LayoutParams p = createPopupLayout(anchor.getWindowToken());
+ final WindowManager.LayoutParams p = createPopupLayoutParams(anchor.getWindowToken());
preparePopup(p);
- updateAboveAnchor(findDropDownPosition(anchor, p, xoff, yoff, gravity));
-
- if (mHeightMode < 0) p.height = mLastHeight = mHeightMode;
- if (mWidthMode < 0) p.width = mLastWidth = mWidthMode;
-
- p.windowAnimations = computeAnimationResource();
+ final boolean aboveAnchor = findDropDownPosition(anchor, p, xoff, yoff, gravity);
+ updateAboveAnchor(aboveAnchor);
invokePopup(p);
}
@@ -1018,12 +1144,12 @@ public class PopupWindow {
// do the job.
if (mAboveAnchorBackgroundDrawable != null) {
if (mAboveAnchor) {
- mPopupView.setBackground(mAboveAnchorBackgroundDrawable);
+ mDecorView.setBackground(mAboveAnchorBackgroundDrawable);
} else {
- mPopupView.setBackground(mBelowAnchorBackgroundDrawable);
+ mDecorView.setBackground(mBelowAnchorBackgroundDrawable);
}
} else {
- mPopupView.refreshDrawableState();
+ mDecorView.refreshDrawableState();
}
}
}
@@ -1045,10 +1171,9 @@ public class PopupWindow {
}
/**
- * <p>Prepare the popup by embedding in into a new ViewGroup if the
- * background drawable is not null. If embedding is required, the layout
- * parameters' height is modified to take into account the background's
- * padding.</p>
+ * Prepare the popup by embedding it into a new ViewGroup if the background
+ * drawable is not null. If embedding is required, the layout parameters'
+ * height is modified to take into account the background's padding.
*
* @param p the layout parameters of the popup's content view
*/
@@ -1058,36 +1183,79 @@ public class PopupWindow {
+ "calling setContentView() before attempting to show the popup.");
}
+ // When a background is available, we embed the content view within
+ // another view that owns the background drawable.
if (mBackground != null) {
- final ViewGroup.LayoutParams layoutParams = mContentView.getLayoutParams();
- int height = ViewGroup.LayoutParams.MATCH_PARENT;
- if (layoutParams != null &&
- layoutParams.height == ViewGroup.LayoutParams.WRAP_CONTENT) {
- height = ViewGroup.LayoutParams.WRAP_CONTENT;
- }
-
- // when a background is available, we embed the content view
- // within another view that owns the background drawable
- PopupViewContainer popupViewContainer = new PopupViewContainer(mContext);
- PopupViewContainer.LayoutParams listParams = new PopupViewContainer.LayoutParams(
- ViewGroup.LayoutParams.MATCH_PARENT, height
- );
- popupViewContainer.setBackground(mBackground);
- popupViewContainer.addView(mContentView, listParams);
-
- mPopupView = popupViewContainer;
+ mBackgroundView = createBackgroundView(mContentView);
+ mBackgroundView.setBackground(mBackground);
} else {
- mPopupView = mContentView;
+ mBackgroundView = mContentView;
}
- mPopupView.setElevation(mElevation);
+ mDecorView = createDecorView(mBackgroundView);
+
+ // The background owner should be elevated so that it casts a shadow.
+ mBackgroundView.setElevation(mElevation);
+
+ // We may wrap that in another view, so we'll need to manually specify
+ // the surface insets.
+ final int surfaceInset = (int) Math.ceil(mBackgroundView.getZ() * 2);
+ p.surfaceInsets.set(surfaceInset, surfaceInset, surfaceInset, surfaceInset);
+ p.hasManualSurfaceInsets = true;
+
mPopupViewInitialLayoutDirectionInherited =
- (mPopupView.getRawLayoutDirection() == View.LAYOUT_DIRECTION_INHERIT);
+ (mContentView.getRawLayoutDirection() == View.LAYOUT_DIRECTION_INHERIT);
mPopupWidth = p.width;
mPopupHeight = p.height;
}
/**
+ * Wraps a content view in a PopupViewContainer.
+ *
+ * @param contentView the content view to wrap
+ * @return a PopupViewContainer that wraps the content view
+ */
+ private PopupBackgroundView createBackgroundView(View contentView) {
+ final ViewGroup.LayoutParams layoutParams = mContentView.getLayoutParams();
+ final int height;
+ if (layoutParams != null && layoutParams.height == ViewGroup.LayoutParams.WRAP_CONTENT) {
+ height = ViewGroup.LayoutParams.WRAP_CONTENT;
+ } else {
+ height = ViewGroup.LayoutParams.MATCH_PARENT;
+ }
+
+ final PopupBackgroundView backgroundView = new PopupBackgroundView(mContext);
+ final PopupBackgroundView.LayoutParams listParams = new PopupBackgroundView.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT, height);
+ backgroundView.addView(contentView, listParams);
+
+ return backgroundView;
+ }
+
+ /**
+ * Wraps a content view in a FrameLayout.
+ *
+ * @param contentView the content view to wrap
+ * @return a FrameLayout that wraps the content view
+ */
+ private PopupDecorView createDecorView(View contentView) {
+ final ViewGroup.LayoutParams layoutParams = mContentView.getLayoutParams();
+ final int height;
+ if (layoutParams != null && layoutParams.height == ViewGroup.LayoutParams.WRAP_CONTENT) {
+ height = ViewGroup.LayoutParams.WRAP_CONTENT;
+ } else {
+ height = ViewGroup.LayoutParams.MATCH_PARENT;
+ }
+
+ final PopupDecorView decorView = new PopupDecorView(mContext);
+ decorView.addView(contentView, ViewGroup.LayoutParams.MATCH_PARENT, height);
+ decorView.setClipChildren(false);
+ decorView.setClipToPadding(false);
+
+ return decorView;
+ }
+
+ /**
* <p>Invoke the popup window by adding the content view to the window
* manager.</p>
*
@@ -1099,16 +1267,34 @@ public class PopupWindow {
if (mContext != null) {
p.packageName = mContext.getPackageName();
}
- mPopupView.setFitsSystemWindows(mLayoutInsetDecor);
+
+ final View rootView = mContentView.getRootView();
+ rootView.setFitsSystemWindows(mLayoutInsetDecor);
setLayoutDirectionFromAnchor();
- mWindowManager.addView(mPopupView, p);
+
+ mWindowManager.addView(rootView, p);
+
+ // Postpone enter transition until the scene root has been laid out.
+ if (mEnterTransition != null) {
+ mEnterTransition.addTarget(mBackgroundView);
+ mEnterTransition.addListener(new Transition.TransitionListenerAdapter() {
+ @Override
+ public void onTransitionEnd(Transition transition) {
+ transition.removeListener(this);
+ transition.removeTarget(mBackgroundView);
+ }
+ });
+
+ mDecorView.getViewTreeObserver().addOnGlobalLayoutListener(
+ new PostLayoutTransitionListener(mDecorView, mEnterTransition));
+ }
}
private void setLayoutDirectionFromAnchor() {
if (mAnchor != null) {
View anchor = mAnchor.get();
if (anchor != null && mPopupViewInitialLayoutDirectionInherited) {
- mPopupView.setLayoutDirection(anchor.getLayoutDirection());
+ mDecorView.setLayoutDirection(anchor.getLayoutDirection());
}
}
}
@@ -1120,26 +1306,39 @@ public class PopupWindow {
*
* @return the layout parameters to pass to the window manager
*/
- private WindowManager.LayoutParams createPopupLayout(IBinder token) {
- // generates the layout parameters for the drop down
- // we want a fixed size view located at the bottom left of the anchor
- WindowManager.LayoutParams p = new WindowManager.LayoutParams();
- // these gravity settings put the view at the top left corner of the
- // screen. The view is then positioned to the appropriate location
- // by setting the x and y offsets to match the anchor's bottom
- // left corner
+ private WindowManager.LayoutParams createPopupLayoutParams(IBinder token) {
+ final WindowManager.LayoutParams p = new WindowManager.LayoutParams();
+
+ // These gravity settings put the view at the top left corner of the
+ // screen. The view is then positioned to the appropriate location by
+ // setting the x and y offsets to match the anchor's bottom-left
+ // corner.
p.gravity = Gravity.START | Gravity.TOP;
- p.width = mLastWidth = mWidth;
- p.height = mLastHeight = mHeight;
+ p.flags = computeFlags(p.flags);
+ p.type = mWindowLayoutType;
+ p.token = token;
+ p.softInputMode = mSoftInputMode;
+ p.windowAnimations = computeAnimationResource();
+
if (mBackground != null) {
p.format = mBackground.getOpacity();
} else {
p.format = PixelFormat.TRANSLUCENT;
}
- p.flags = computeFlags(p.flags);
- p.type = mWindowLayoutType;
- p.token = token;
- p.softInputMode = mSoftInputMode;
+
+ if (mHeightMode < 0) {
+ p.height = mLastHeight = mHeightMode;
+ } else {
+ p.height = mLastHeight = mHeight;
+ }
+
+ if (mWidthMode < 0) {
+ p.width = mLastWidth = mWidthMode;
+ } else {
+ p.width = mLastWidth = mWidth;
+ }
+
+ // Used for debugging.
p.setTitle("PopupWindow:" + Integer.toHexString(hashCode()));
return p;
@@ -1193,7 +1392,7 @@ public class PopupWindow {
}
private int computeAnimationResource() {
- if (mAnimationStyle == -1) {
+ if (mAnimationStyle == ANIMATION_STYLE_DEFAULT) {
if (mIsDropdown) {
return mAboveAnchor
? com.android.internal.R.style.Animation_DropDownUp
@@ -1212,7 +1411,7 @@ public class PopupWindow {
* <p>
* The height must have been set on the layout parameters prior to calling
* this method.
- *
+ *
* @param anchor the view on which the popup window must be anchored
* @param p the layout parameters used to display the drop down
* @param xoff horizontal offset used to adjust for background padding
@@ -1310,19 +1509,15 @@ public class PopupWindow {
p.gravity |= Gravity.DISPLAY_CLIP_VERTICAL;
- // Compute the position of the anchor relative to the popup.
- mAnchorRelativeX = mDrawingLocation[0] - p.x + anchorHeight / 2;
- mAnchorRelativeY = mDrawingLocation[1] - p.y + anchorWidth / 2;
-
return onTop;
}
-
+
/**
* Returns the maximum height that is available for the popup to be
* completely shown. It is recommended that this height be the maximum for
* the popup's height, otherwise it is possible that the popup will be
* clipped.
- *
+ *
* @param anchor The view on which the popup window must be anchored.
* @return The maximum available height for the popup to be completely
* shown.
@@ -1345,14 +1540,14 @@ public class PopupWindow {
public int getMaxAvailableHeight(View anchor, int yOffset) {
return getMaxAvailableHeight(anchor, yOffset, false);
}
-
+
/**
* Returns the maximum height that is available for the popup to be
* completely shown, optionally ignoring any bottom decorations such as
* the input method. It is recommended that this height be the maximum for
* the popup's height, otherwise it is possible that the popup will be
* clipped.
- *
+ *
* @param anchor The view on which the popup window must be anchored.
* @param yOffset y offset from the view's bottom edge
* @param ignoreBottomDecorations if true, the height returned will be
@@ -1360,7 +1555,7 @@ public class PopupWindow {
* bottom decorations
* @return The maximum available height for the popup to be completely
* shown.
- *
+ *
* @hide Pending API council approval.
*/
public int getMaxAvailableHeight(View anchor, int yOffset, boolean ignoreBottomDecorations) {
@@ -1369,7 +1564,7 @@ public class PopupWindow {
final int[] anchorPos = mDrawingLocation;
anchor.getLocationOnScreen(anchorPos);
-
+
int bottomEdge = displayFrame.bottom;
if (ignoreBottomDecorations) {
Resources res = anchor.getContext().getResources();
@@ -1382,49 +1577,83 @@ public class PopupWindow {
int returnedHeight = Math.max(distanceToBottom, distanceToTop);
if (mBackground != null) {
mBackground.getPadding(mTempRect);
- returnedHeight -= mTempRect.top + mTempRect.bottom;
+ returnedHeight -= mTempRect.top + mTempRect.bottom;
}
-
+
return returnedHeight;
}
-
+
/**
- * <p>Dispose of the popup window. This method can be invoked only after
- * {@link #showAsDropDown(android.view.View)} has been executed. Failing that, calling
- * this method will have no effect.</p>
+ * Disposes of the popup window. This method can be invoked only after
+ * {@link #showAsDropDown(android.view.View)} has been executed. Failing
+ * that, calling this method will have no effect.
*
- * @see #showAsDropDown(android.view.View)
+ * @see #showAsDropDown(android.view.View)
*/
public void dismiss() {
- if (isShowing() && mPopupView != null) {
- mIsShowing = false;
+ if (!isShowing()) {
+ return;
+ }
+
+ unregisterForScrollChanged();
- unregisterForScrollChanged();
+ mIsShowing = false;
- try {
- mWindowManager.removeViewImmediate(mPopupView);
- } finally {
- if (mPopupView != mContentView && mPopupView instanceof ViewGroup) {
- ((ViewGroup) mPopupView).removeView(mContentView);
- }
- mPopupView = null;
+ if (mExitTransition != null) {
+ // Cache the content view, since it may change without notice.
+ final View contentView = mContentView;
- if (mOnDismissListener != null) {
- mOnDismissListener.onDismiss();
+ mExitTransition.addTarget(mBackgroundView);
+ mExitTransition.addListener(new Transition.TransitionListenerAdapter() {
+ @Override
+ public void onTransitionEnd(Transition transition) {
+ transition.removeListener(this);
+ transition.removeTarget(mBackgroundView);
+
+ dismissImmediate(contentView);
}
+ });
+
+ TransitionManager.beginDelayedTransition(mDecorView, mExitTransition);
+
+ // Transition to invisible.
+ mBackgroundView.setVisibility(View.INVISIBLE);
+ } else {
+ dismissImmediate(mContentView);
+ }
+
+ if (mOnDismissListener != null) {
+ mOnDismissListener.onDismiss();
+ }
+ }
+
+ /**
+ * Removes the popup from the window manager and tears down the supporting
+ * view hierarchy, if necessary.
+ */
+ private void dismissImmediate(View contentView) {
+ try {
+ mWindowManager.removeViewImmediate(mDecorView);
+ } finally {
+ mDecorView.removeView(mBackgroundView);
+ mDecorView = null;
+
+ if (mBackgroundView != contentView) {
+ ((ViewGroup) mBackgroundView).removeView(contentView);
}
+ mBackgroundView = null;
}
}
/**
* Sets the listener to be called when the window is dismissed.
- *
+ *
* @param onDismissListener The listener.
*/
public void setOnDismissListener(OnDismissListener onDismissListener) {
mOnDismissListener = onDismissListener;
}
-
+
/**
* Updates the state of the popup window, if it is currently being displayed,
* from the currently set state. This includes:
@@ -1436,12 +1665,12 @@ public class PopupWindow {
if (!isShowing() || mContentView == null) {
return;
}
-
- WindowManager.LayoutParams p = (WindowManager.LayoutParams)
- mPopupView.getLayoutParams();
-
+
+ final WindowManager.LayoutParams p =
+ (WindowManager.LayoutParams) mDecorView.getLayoutParams();
+
boolean update = false;
-
+
final int newAnim = computeAnimationResource();
if (newAnim != p.windowAnimations) {
p.windowAnimations = newAnim;
@@ -1456,7 +1685,7 @@ public class PopupWindow {
if (update) {
setLayoutDirectionFromAnchor();
- mWindowManager.updateViewLayout(mPopupView, p);
+ mWindowManager.updateViewLayout(mDecorView, p);
}
}
@@ -1469,11 +1698,11 @@ public class PopupWindow {
* @param height the new height
*/
public void update(int width, int height) {
- WindowManager.LayoutParams p = (WindowManager.LayoutParams)
- mPopupView.getLayoutParams();
+ final WindowManager.LayoutParams p =
+ (WindowManager.LayoutParams) mDecorView.getLayoutParams();
update(p.x, p.y, width, height, false);
}
-
+
/**
* <p>Updates the position and the dimension of the popup window. Width and
* height can be set to -1 to update location only. Calling this function
@@ -1517,7 +1746,8 @@ public class PopupWindow {
return;
}
- WindowManager.LayoutParams p = (WindowManager.LayoutParams) mPopupView.getLayoutParams();
+ final WindowManager.LayoutParams p =
+ (WindowManager.LayoutParams) mDecorView.getLayoutParams();
boolean update = force;
@@ -1557,7 +1787,7 @@ public class PopupWindow {
if (update) {
setLayoutDirectionFromAnchor();
- mWindowManager.updateViewLayout(mPopupView, p);
+ mWindowManager.updateViewLayout(mDecorView, p);
}
}
@@ -1571,7 +1801,7 @@ public class PopupWindow {
* @param height the new height, can be -1 to ignore
*/
public void update(View anchor, int width, int height) {
- update(anchor, false, 0, 0, true, width, height, mAnchoredGravity);
+ update(anchor, false, 0, 0, true, width, height);
}
/**
@@ -1590,30 +1820,26 @@ public class PopupWindow {
* @param height the new height, can be -1 to ignore
*/
public void update(View anchor, int xoff, int yoff, int width, int height) {
- update(anchor, true, xoff, yoff, true, width, height, mAnchoredGravity);
+ update(anchor, true, xoff, yoff, true, width, height);
}
private void update(View anchor, boolean updateLocation, int xoff, int yoff,
- boolean updateDimension, int width, int height, int gravity) {
+ boolean updateDimension, int width, int height) {
if (!isShowing() || mContentView == null) {
return;
}
- WeakReference<View> oldAnchor = mAnchor;
- final boolean needsUpdate = updateLocation
- && (mAnchorXoff != xoff || mAnchorYoff != yoff);
+ final WeakReference<View> oldAnchor = mAnchor;
+ final boolean needsUpdate = updateLocation && (mAnchorXoff != xoff || mAnchorYoff != yoff);
if (oldAnchor == null || oldAnchor.get() != anchor || (needsUpdate && !mIsDropdown)) {
- registerForScrollChanged(anchor, xoff, yoff, gravity);
+ registerForScrollChanged(anchor, xoff, yoff, mAnchoredGravity);
} else if (needsUpdate) {
// No need to register again if this is a DropDown, showAsDropDown already did.
mAnchorXoff = xoff;
mAnchorYoff = yoff;
- mAnchoredGravity = gravity;
}
- WindowManager.LayoutParams p = (WindowManager.LayoutParams) mPopupView.getLayoutParams();
-
if (updateDimension) {
if (width == -1) {
width = mPopupWidth;
@@ -1627,11 +1853,12 @@ public class PopupWindow {
}
}
- int x = p.x;
- int y = p.y;
-
+ final WindowManager.LayoutParams p =
+ (WindowManager.LayoutParams) mDecorView.getLayoutParams();
+ final int x = p.x;
+ final int y = p.y;
if (updateLocation) {
- updateAboveAnchor(findDropDownPosition(anchor, p, xoff, yoff, gravity));
+ updateAboveAnchor(findDropDownPosition(anchor, p, xoff, yoff, mAnchoredGravity));
} else {
updateAboveAnchor(findDropDownPosition(anchor, p, mAnchorXoff, mAnchorYoff,
mAnchoredGravity));
@@ -1651,23 +1878,22 @@ public class PopupWindow {
}
private void unregisterForScrollChanged() {
- WeakReference<View> anchorRef = mAnchor;
- View anchor = null;
- if (anchorRef != null) {
- anchor = anchorRef.get();
- }
+ final WeakReference<View> anchorRef = mAnchor;
+ final View anchor = anchorRef == null ? null : anchorRef.get();
if (anchor != null) {
- ViewTreeObserver vto = anchor.getViewTreeObserver();
+ final ViewTreeObserver vto = anchor.getViewTreeObserver();
vto.removeOnScrollChangedListener(mOnScrollChangedListener);
}
+
mAnchor = null;
}
private void registerForScrollChanged(View anchor, int xoff, int yoff, int gravity) {
unregisterForScrollChanged();
- mAnchor = new WeakReference<View>(anchor);
- ViewTreeObserver vto = anchor.getViewTreeObserver();
+ mAnchor = new WeakReference<>(anchor);
+
+ final ViewTreeObserver vto = anchor.getViewTreeObserver();
if (vto != null) {
vto.addOnScrollChangedListener(mOnScrollChangedListener);
}
@@ -1677,23 +1903,49 @@ public class PopupWindow {
mAnchoredGravity = gravity;
}
- private class PopupViewContainer extends FrameLayout {
- private static final String TAG = "PopupWindow.PopupViewContainer";
+ /**
+ * Layout listener used to run a transition immediately after a view is
+ * laid out. Forces the view to transition from invisible to visible.
+ */
+ private static class PostLayoutTransitionListener implements
+ ViewTreeObserver.OnGlobalLayoutListener {
+ private final ViewGroup mSceneRoot;
+ private final Transition mTransition;
- public PopupViewContainer(Context context) {
- super(context);
+ public PostLayoutTransitionListener(ViewGroup sceneRoot, Transition transition) {
+ mSceneRoot = sceneRoot;
+ mTransition = transition;
}
@Override
- protected int[] onCreateDrawableState(int extraSpace) {
- if (mAboveAnchor) {
- // 1 more needed for the above anchor state
- final int[] drawableState = super.onCreateDrawableState(extraSpace + 1);
- View.mergeDrawableStates(drawableState, ABOVE_ANCHOR_STATE_SET);
- return drawableState;
- } else {
- return super.onCreateDrawableState(extraSpace);
+ public void onGlobalLayout() {
+ final ViewTreeObserver observer = mSceneRoot.getViewTreeObserver();
+ if (observer == null) {
+ // View has been detached.
+ return;
}
+
+ observer.removeOnGlobalLayoutListener(this);
+
+ // Set all targets to be initially invisible.
+ final List<View> targets = mTransition.getTargets();
+ final int N = targets.size();
+ for (int i = 0; i < N; i++) {
+ targets.get(i).setVisibility(View.INVISIBLE);
+ }
+
+ TransitionManager.beginDelayedTransition(mSceneRoot, mTransition);
+
+ // Transition targets to visible.
+ for (int i = 0; i < N; i++) {
+ targets.get(i).setVisibility(View.VISIBLE);
+ }
+ }
+ }
+
+ private class PopupDecorView extends FrameLayout {
+ public PopupDecorView(Context context) {
+ super(context);
}
@Override
@@ -1703,15 +1955,14 @@ public class PopupWindow {
return super.dispatchKeyEvent(event);
}
- if (event.getAction() == KeyEvent.ACTION_DOWN
- && event.getRepeatCount() == 0) {
- KeyEvent.DispatcherState state = getKeyDispatcherState();
+ if (event.getAction() == KeyEvent.ACTION_DOWN && event.getRepeatCount() == 0) {
+ final KeyEvent.DispatcherState state = getKeyDispatcherState();
if (state != null) {
state.startTracking(event, this);
}
return true;
} else if (event.getAction() == KeyEvent.ACTION_UP) {
- KeyEvent.DispatcherState state = getKeyDispatcherState();
+ final KeyEvent.DispatcherState state = getKeyDispatcherState();
if (state != null && state.isTracking(event) && !event.isCanceled()) {
dismiss();
return true;
@@ -1735,7 +1986,7 @@ public class PopupWindow {
public boolean onTouchEvent(MotionEvent event) {
final int x = (int) event.getX();
final int y = (int) event.getY();
-
+
if ((event.getAction() == MotionEvent.ACTION_DOWN)
&& ((x < 0) || (x >= getWidth()) || (y < 0) || (y >= getHeight()))) {
dismiss();
@@ -1747,16 +1998,22 @@ public class PopupWindow {
return super.onTouchEvent(event);
}
}
+ }
+
+ private class PopupBackgroundView extends FrameLayout {
+ public PopupBackgroundView(Context context) {
+ super(context);
+ }
@Override
- public void sendAccessibilityEvent(int eventType) {
- // clinets are interested in the content not the container, make it event source
- if (mContentView != null) {
- mContentView.sendAccessibilityEvent(eventType);
+ protected int[] onCreateDrawableState(int extraSpace) {
+ if (mAboveAnchor) {
+ final int[] drawableState = super.onCreateDrawableState(extraSpace + 1);
+ View.mergeDrawableStates(drawableState, ABOVE_ANCHOR_STATE_SET);
+ return drawableState;
} else {
- super.sendAccessibilityEvent(eventType);
+ return super.onCreateDrawableState(extraSpace);
}
}
}
-
}
diff --git a/core/java/android/widget/ProgressBar.java b/core/java/android/widget/ProgressBar.java
index de1bbc7..5b0745e 100644
--- a/core/java/android/widget/ProgressBar.java
+++ b/core/java/android/widget/ProgressBar.java
@@ -18,6 +18,7 @@ package android.widget;
import android.annotation.Nullable;
import android.graphics.PorterDuff;
+
import com.android.internal.R;
import android.content.Context;
@@ -64,8 +65,8 @@ import java.util.ArrayList;
/**
* <p>
* Visual indicator of progress in some operation. Displays a bar to the user
- * representing how far the operation has progressed; the application can
- * change the amount of progress (modifying the length of the bar) as it moves
+ * representing how far the operation has progressed; the application can
+ * change the amount of progress (modifying the length of the bar) as it moves
* forward. There is also a secondary progress displayable on a progress bar
* which is useful for displaying intermediate progress, such as the buffer
* level during a streaming playback progress bar.
@@ -81,7 +82,7 @@ import java.util.ArrayList;
* <p>The following code example shows how a progress bar can be used from
* a worker thread to update the user interface to notify the user of progress:
* </p>
- *
+ *
* <pre>
* public class MyActivity extends Activity {
* private static final int PROGRESS = 0x1;
@@ -169,13 +170,13 @@ import java.util.ArrayList;
* </ul>
* <p>The "inverse" styles provide an inverse color scheme for the spinner, which may be necessary
* if your application uses a light colored theme (a white background).</p>
- *
- * <p><strong>XML attributes</b></strong>
- * <p>
- * See {@link android.R.styleable#ProgressBar ProgressBar Attributes},
+ *
+ * <p><strong>XML attributes</b></strong>
+ * <p>
+ * See {@link android.R.styleable#ProgressBar ProgressBar Attributes},
* {@link android.R.styleable#View View Attributes}
* </p>
- *
+ *
* @attr ref android.R.styleable#ProgressBar_animationResolution
* @attr ref android.R.styleable#ProgressBar_indeterminate
* @attr ref android.R.styleable#ProgressBar_indeterminateBehavior
@@ -244,7 +245,7 @@ public class ProgressBar extends View {
public ProgressBar(Context context) {
this(context, null);
}
-
+
public ProgressBar(Context context, AttributeSet attrs) {
this(context, attrs, com.android.internal.R.attr.progressBarStyle);
}
@@ -261,9 +262,9 @@ public class ProgressBar extends View {
final TypedArray a = context.obtainStyledAttributes(
attrs, R.styleable.ProgressBar, defStyleAttr, defStyleRes);
-
+
mNoInvalidate = true;
-
+
final Drawable progressDrawable = a.getDrawable(R.styleable.ProgressBar_progressDrawable);
if (progressDrawable != null) {
// Calling this method can set mMaxHeight, make sure the corresponding
@@ -282,11 +283,11 @@ public class ProgressBar extends View {
mBehavior = a.getInt(R.styleable.ProgressBar_indeterminateBehavior, mBehavior);
final int resID = a.getResourceId(
- com.android.internal.R.styleable.ProgressBar_interpolator,
+ com.android.internal.R.styleable.ProgressBar_interpolator,
android.R.anim.linear_interpolator); // default to linear interpolator
if (resID > 0) {
setInterpolator(context, resID);
- }
+ }
setMax(a.getInt(R.styleable.ProgressBar_max, mMax));
@@ -399,12 +400,12 @@ public class ProgressBar extends View {
* traverse layer and state list drawables.
*/
private Drawable tileify(Drawable drawable, boolean clip) {
-
+
if (drawable instanceof LayerDrawable) {
LayerDrawable background = (LayerDrawable) drawable;
final int N = background.getNumberOfLayers();
Drawable[] outDrawables = new Drawable[N];
-
+
for (int i = 0; i < N; i++) {
int id = background.getId(i);
outDrawables[i] = tileify(background.getDrawable(i),
@@ -412,13 +413,13 @@ public class ProgressBar extends View {
}
LayerDrawable newBg = new LayerDrawable(outDrawables);
-
+
for (int i = 0; i < N; i++) {
newBg.setId(i, background.getId(i));
}
-
+
return newBg;
-
+
} else if (drawable instanceof StateListDrawable) {
StateListDrawable in = (StateListDrawable) drawable;
StateListDrawable out = new StateListDrawable();
@@ -427,7 +428,7 @@ public class ProgressBar extends View {
out.addState(in.getStateSet(i), tileify(in.getStateDrawable(i), clip));
}
return out;
-
+
} else if (drawable instanceof BitmapDrawable) {
final BitmapDrawable bitmap = (BitmapDrawable) drawable;
final Bitmap tileBitmap = bitmap.getBitmap();
@@ -448,7 +449,7 @@ public class ProgressBar extends View {
return clip ? new ClipDrawable(
shapeDrawable, Gravity.LEFT, ClipDrawable.HORIZONTAL) : shapeDrawable;
}
-
+
return drawable;
}
@@ -456,7 +457,7 @@ public class ProgressBar extends View {
final float[] roundedCorners = new float[] { 5, 5, 5, 5, 5, 5, 5, 5 };
return new RoundRectShape(roundedCorners, null, null);
}
-
+
/**
* Convert a AnimationDrawable for use as a barberpole animation.
* Each frame of the animation is wrapped in a ClipDrawable and
@@ -468,7 +469,7 @@ public class ProgressBar extends View {
final int N = background.getNumberOfFrames();
AnimationDrawable newBg = new AnimationDrawable();
newBg.setOneShot(background.isOneShot());
-
+
for (int i = 0; i < N; i++) {
Drawable frame = tileify(background.getFrame(i), true);
frame.setLevel(10000);
@@ -479,7 +480,7 @@ public class ProgressBar extends View {
}
return drawable;
}
-
+
/**
* <p>
* Initialize the progress bar's default values:
@@ -520,7 +521,7 @@ public class ProgressBar extends View {
* <p>Change the indeterminate mode for this progress bar. In indeterminate
* mode, the progress is ignored and the progress bar shows an infinite
* animation instead.</p>
- *
+ *
* If this progress bar's style only supports indeterminate mode (such as the circular
* progress bars), then this will be ignored.
*
@@ -699,7 +700,7 @@ public class ProgressBar extends View {
setIndeterminateDrawable(d);
}
-
+
/**
* <p>Get the drawable used to draw the progress bar in
* progress mode.</p>
@@ -1135,7 +1136,7 @@ public class ProgressBar extends View {
setProgressDrawable(d);
}
-
+
/**
* @return The drawable currently used to draw the progress bar
*/
@@ -1214,7 +1215,7 @@ public class ProgressBar extends View {
rd.fromUser = fromUser;
return rd;
}
-
+
public void recycle() {
sPool.release(this);
}
@@ -1257,13 +1258,13 @@ public class ProgressBar extends View {
} else {
invalidate();
}
-
+
if (callBackToApp && id == R.id.progress) {
- onProgressRefresh(scale, fromUser);
+ onProgressRefresh(scale, fromUser, progress);
}
}
- void onProgressRefresh(float scale, boolean fromUser) {
+ void onProgressRefresh(float scale, boolean fromUser, int progress) {
if (AccessibilityManager.getInstance(mContext).isEnabled()) {
scheduleAccessibilityEventSender();
}
@@ -1285,7 +1286,7 @@ public class ProgressBar extends View {
}
}
}
-
+
/**
* <p>Set the current progress to the specified value. Does not do anything
* if the progress bar is in indeterminate mode.</p>
@@ -1295,13 +1296,13 @@ public class ProgressBar extends View {
* @see #setIndeterminate(boolean)
* @see #isIndeterminate()
* @see #getProgress()
- * @see #incrementProgressBy(int)
+ * @see #incrementProgressBy(int)
*/
@android.view.RemotableViewMethod
public synchronized void setProgress(int progress) {
setProgress(progress, false);
}
-
+
@android.view.RemotableViewMethod
synchronized void setProgress(int progress, boolean fromUser) {
if (mIndeterminate) {
@@ -1327,7 +1328,7 @@ public class ProgressBar extends View {
* Set the current secondary progress to the specified value. Does not do
* anything if the progress bar is in indeterminate mode.
* </p>
- *
+ *
* @param secondaryProgress the new secondary progress, between 0 and {@link #getMax()}
* @see #setIndeterminate(boolean)
* @see #isIndeterminate()
@@ -1408,8 +1409,8 @@ public class ProgressBar extends View {
* @param max the upper range of this progress bar
*
* @see #getMax()
- * @see #setProgress(int)
- * @see #setSecondaryProgress(int)
+ * @see #setProgress(int)
+ * @see #setSecondaryProgress(int)
*/
@android.view.RemotableViewMethod
public synchronized void setMax(int max) {
@@ -1426,13 +1427,13 @@ public class ProgressBar extends View {
refreshProgress(R.id.progress, mProgress, false);
}
}
-
+
/**
* <p>Increase the progress bar's progress by the specified amount.</p>
*
* @param diff the amount by which the progress must be increased
*
- * @see #setProgress(int)
+ * @see #setProgress(int)
*/
public synchronized final void incrementProgressBy(int diff) {
setProgress(mProgress + diff);
@@ -1443,7 +1444,7 @@ public class ProgressBar extends View {
*
* @param diff the amount by which the secondary progress must be increased
*
- * @see #setSecondaryProgress(int)
+ * @see #setSecondaryProgress(int)
*/
public synchronized final void incrementSecondaryProgressBy(int diff) {
setSecondaryProgress(mSecondaryProgress + diff);
@@ -1466,13 +1467,13 @@ public class ProgressBar extends View {
if (mInterpolator == null) {
mInterpolator = new LinearInterpolator();
}
-
+
if (mTransformation == null) {
mTransformation = new Transformation();
} else {
mTransformation.clear();
}
-
+
if (mAnimation == null) {
mAnimation = new AlphaAnimation(0.0f, 1.0f);
} else {
@@ -1623,7 +1624,7 @@ public class ProgressBar extends View {
}
mIndeterminateDrawable.setBounds(left, top, right, bottom);
}
-
+
if (mProgressDrawable != null) {
mProgressDrawable.setBounds(0, 0, right, bottom);
}
@@ -1693,20 +1694,20 @@ public class ProgressBar extends View {
setMeasuredDimension(resolveSizeAndState(dw, widthMeasureSpec, 0),
resolveSizeAndState(dh, heightMeasureSpec, 0));
}
-
+
@Override
protected void drawableStateChanged() {
super.drawableStateChanged();
updateDrawableState();
}
-
+
private void updateDrawableState() {
int[] state = getDrawableState();
-
+
if (mProgressDrawable != null && mProgressDrawable.isStateful()) {
mProgressDrawable.setState(state);
}
-
+
if (mIndeterminateDrawable != null && mIndeterminateDrawable.isStateful()) {
mIndeterminateDrawable.setState(state);
}
@@ -1728,14 +1729,14 @@ public class ProgressBar extends View {
static class SavedState extends BaseSavedState {
int progress;
int secondaryProgress;
-
+
/**
* Constructor called from {@link ProgressBar#onSaveInstanceState()}
*/
SavedState(Parcelable superState) {
super(superState);
}
-
+
/**
* Constructor called from {@link #CREATOR}
*/
@@ -1769,10 +1770,10 @@ public class ProgressBar extends View {
// Force our ancestor class to save its state
Parcelable superState = super.onSaveInstanceState();
SavedState ss = new SavedState(superState);
-
+
ss.progress = mProgress;
ss.secondaryProgress = mSecondaryProgress;
-
+
return ss;
}
@@ -1780,7 +1781,7 @@ public class ProgressBar extends View {
public void onRestoreInstanceState(Parcelable state) {
SavedState ss = (SavedState) state;
super.onRestoreInstanceState(ss.getSuperState());
-
+
setProgress(ss.progress);
setSecondaryProgress(ss.secondaryProgress);
}
@@ -1826,17 +1827,16 @@ public class ProgressBar extends View {
}
@Override
- public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
- super.onInitializeAccessibilityEvent(event);
- event.setClassName(ProgressBar.class.getName());
- event.setItemCount(mMax);
- event.setCurrentItemIndex(mProgress);
+ public CharSequence getAccessibilityClassName() {
+ return ProgressBar.class.getName();
}
+ /** @hide */
@Override
- public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(info);
- info.setClassName(ProgressBar.class.getName());
+ public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) {
+ super.onInitializeAccessibilityEventInternal(event);
+ event.setItemCount(mMax);
+ event.setCurrentItemIndex(mProgress);
}
/**
diff --git a/core/java/android/widget/QuickContactBadge.java b/core/java/android/widget/QuickContactBadge.java
index 23fa402..3068de9 100644
--- a/core/java/android/widget/QuickContactBadge.java
+++ b/core/java/android/widget/QuickContactBadge.java
@@ -305,15 +305,8 @@ public class QuickContactBadge extends ImageView implements OnClickListener {
}
@Override
- public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
- super.onInitializeAccessibilityEvent(event);
- event.setClassName(QuickContactBadge.class.getName());
- }
-
- @Override
- public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(info);
- info.setClassName(QuickContactBadge.class.getName());
+ public CharSequence getAccessibilityClassName() {
+ return QuickContactBadge.class.getName();
}
/**
diff --git a/core/java/android/widget/RadialTimePickerView.java b/core/java/android/widget/RadialTimePickerView.java
index 11fda2c..4b061d3 100644
--- a/core/java/android/widget/RadialTimePickerView.java
+++ b/core/java/android/widget/RadialTimePickerView.java
@@ -23,18 +23,22 @@ import android.animation.ObjectAnimator;
import android.animation.PropertyValuesHolder;
import android.animation.ValueAnimator;
import android.content.Context;
+import android.content.res.ColorStateList;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
+import android.graphics.Path;
import android.graphics.Rect;
+import android.graphics.Region;
import android.graphics.Typeface;
import android.os.Bundle;
import android.util.AttributeSet;
import android.util.IntArray;
import android.util.Log;
import android.util.MathUtils;
+import android.util.StateSet;
import android.util.TypedValue;
import android.view.HapticFeedbackConstants;
import android.view.MotionEvent;
@@ -56,8 +60,8 @@ import java.util.Locale;
*
* @hide
*/
-public class RadialTimePickerView extends View implements View.OnTouchListener {
- private static final String TAG = "ClockView";
+public class RadialTimePickerView extends View {
+ private static final String TAG = "RadialTimePickerView";
private static final boolean DEBUG = false;
@@ -82,12 +86,6 @@ public class RadialTimePickerView extends View implements View.OnTouchListener {
// Transparent alpha level
private static final int ALPHA_TRANSPARENT = 0;
- // Alpha level of color for selector.
- private static final int ALPHA_SELECTOR = 60; // was 51
-
- private static final float COSINE_30_DEGREES = ((float) Math.sqrt(3)) * 0.5f;
- private static final float SINE_30_DEGREES = 0.5f;
-
private static final int DEGREES_FOR_ONE_HOUR = 30;
private static final int DEGREES_FOR_ONE_MINUTE = 6;
@@ -97,7 +95,27 @@ public class RadialTimePickerView extends View implements View.OnTouchListener {
private static final int CENTER_RADIUS = 2;
- private static int[] sSnapPrefer30sMap = new int[361];
+ private static final int FADE_OUT_DURATION = 500;
+ private static final int FADE_IN_DURATION = 500;
+
+ private static final int[] SNAP_PREFER_30S_MAP = new int[361];
+
+ private static final int NUM_POSITIONS = 12;
+ private static final float[] COS_30 = new float[NUM_POSITIONS];
+ private static final float[] SIN_30 = new float[NUM_POSITIONS];
+
+ static {
+ // Prepare mapping to snap touchable degrees to selectable degrees.
+ preparePrefer30sMap();
+
+ final double increment = 2.0 * Math.PI / NUM_POSITIONS;
+ double angle = Math.PI / 2.0;
+ for (int i = 0; i < NUM_POSITIONS; i++) {
+ COS_30[i] = (float) Math.cos(angle);
+ SIN_30[i] = (float) Math.sin(angle);
+ angle += increment;
+ }
+ }
private final InvalidateUpdateListener mInvalidateUpdateListener =
new InvalidateUpdateListener();
@@ -108,7 +126,6 @@ public class RadialTimePickerView extends View implements View.OnTouchListener {
private final String[] mMinutesTexts = new String[12];
private final Paint[] mPaint = new Paint[2];
- private final int[] mColor = new int[2];
private final IntHolder[] mAlpha = new IntHolder[2];
private final Paint mPaintCenter = new Paint();
@@ -122,26 +139,18 @@ public class RadialTimePickerView extends View implements View.OnTouchListener {
private final Typeface mTypeface;
- private final float[] mCircleRadius = new float[3];
-
private final float[] mTextSize = new float[2];
- private final float[][] mTextGridHeights = new float[2][7];
- private final float[][] mTextGridWidths = new float[2][7];
+ private final float[][] mOuterTextX = new float[2][12];
+ private final float[][] mOuterTextY = new float[2][12];
- private final float[] mInnerTextGridHeights = new float[7];
- private final float[] mInnerTextGridWidths = new float[7];
+ private final float[] mInnerTextX = new float[12];
+ private final float[] mInnerTextY = new float[12];
- private final float[] mCircleRadiusMultiplier = new float[2];
private final float[] mNumbersRadiusMultiplier = new float[3];
private final float[] mTextSizeMultiplier = new float[3];
- private final float[] mAnimationRadiusMultiplier = new float[3];
-
- private final float mTransitionMidRadiusMultiplier;
- private final float mTransitionEndRadiusMultiplier;
-
private final int[] mLineLength = new int[3];
private final int[] mSelectionRadius = new int[3];
private final float mSelectionRadiusMultiplier;
@@ -152,7 +161,7 @@ public class RadialTimePickerView extends View implements View.OnTouchListener {
private final RadialPickerTouchHelper mTouchHelper;
- private float mInnerTextSize;
+ private ColorStateList mNumbersTextColor;
private boolean mIs24HourMode;
private boolean mShowHours;
@@ -163,8 +172,9 @@ public class RadialTimePickerView extends View implements View.OnTouchListener {
*/
private boolean mIsOnInnerCircle;
- private int mXCenter;
- private int mYCenter;
+ private float mXCenter;
+ private float mYCenter;
+ private float mCircleRadius;
private int mMinHypotenuseForInnerNumber;
private int mMaxHypotenuseForOuterNumber;
@@ -186,11 +196,6 @@ public class RadialTimePickerView extends View implements View.OnTouchListener {
void onValueSelected(int pickerIndex, int newValue, boolean autoAdvance);
}
- static {
- // Prepare mapping to snap touchable degrees to selectable degrees.
- preparePrefer30sMap();
- }
-
/**
* Split up the 360 degrees of the circle among the 60 selectable values. Assigns a larger
* selectable area to each of the 12 visible values, such that the ratio of space apportioned
@@ -225,7 +230,7 @@ public class RadialTimePickerView extends View implements View.OnTouchListener {
// Iterate through the input.
for (int degrees = 0; degrees < 361; degrees++) {
// Save the input-output mapping.
- sSnapPrefer30sMap[degrees] = snappedOutputDegrees;
+ SNAP_PREFER_30S_MAP[degrees] = snappedOutputDegrees;
// If this is the last input for the specified output, calculate the next output and
// the next expected count.
if (count == expectedCount) {
@@ -252,10 +257,10 @@ public class RadialTimePickerView extends View implements View.OnTouchListener {
* mapping.
*/
private static int snapPrefer30s(int degrees) {
- if (sSnapPrefer30sMap == null) {
+ if (SNAP_PREFER_30S_MAP == null) {
return -1;
}
- return sSnapPrefer30sMap[degrees];
+ return SNAP_PREFER_30S_MAP[degrees];
}
/**
@@ -327,63 +332,56 @@ public class RadialTimePickerView extends View implements View.OnTouchListener {
}
}
- final int numbersTextColor = a.getColor(R.styleable.TimePicker_numbersTextColor,
- res.getColor(R.color.timepicker_default_text_color_material));
+ mNumbersTextColor = a.getColorStateList(
+ R.styleable.TimePicker_numbersTextColor);
mPaint[HOURS] = new Paint();
mPaint[HOURS].setAntiAlias(true);
mPaint[HOURS].setTextAlign(Paint.Align.CENTER);
- mColor[HOURS] = numbersTextColor;
mPaint[MINUTES] = new Paint();
mPaint[MINUTES].setAntiAlias(true);
mPaint[MINUTES].setTextAlign(Paint.Align.CENTER);
- mColor[MINUTES] = numbersTextColor;
- mPaintCenter.setColor(numbersTextColor);
+ final ColorStateList selectorColors = a.getColorStateList(
+ R.styleable.TimePicker_numbersSelectorColor);
+ final int selectorActivatedColor = selectorColors.getColorForState(
+ StateSet.get(StateSet.VIEW_STATE_ENABLED | StateSet.VIEW_STATE_ACTIVATED), 0);
+
+ mPaintCenter.setColor(selectorActivatedColor);
mPaintCenter.setAntiAlias(true);
- mPaintCenter.setTextAlign(Paint.Align.CENTER);
+
+ final int textActivatedColor = mNumbersTextColor.getColorForState(
+ StateSet.get(StateSet.VIEW_STATE_ENABLED | StateSet.VIEW_STATE_ACTIVATED), 0);
mPaintSelector[HOURS][SELECTOR_CIRCLE] = new Paint();
mPaintSelector[HOURS][SELECTOR_CIRCLE].setAntiAlias(true);
- mColorSelector[HOURS][SELECTOR_CIRCLE] = a.getColor(
- R.styleable.TimePicker_numbersSelectorColor,
- R.color.timepicker_default_selector_color_material);
+ mColorSelector[HOURS][SELECTOR_CIRCLE] = selectorActivatedColor;
mPaintSelector[HOURS][SELECTOR_DOT] = new Paint();
mPaintSelector[HOURS][SELECTOR_DOT].setAntiAlias(true);
- mColorSelector[HOURS][SELECTOR_DOT] = a.getColor(
- R.styleable.TimePicker_numbersSelectorColor,
- R.color.timepicker_default_selector_color_material);
+ mColorSelector[HOURS][SELECTOR_DOT] = textActivatedColor;
mPaintSelector[HOURS][SELECTOR_LINE] = new Paint();
mPaintSelector[HOURS][SELECTOR_LINE].setAntiAlias(true);
mPaintSelector[HOURS][SELECTOR_LINE].setStrokeWidth(2);
- mColorSelector[HOURS][SELECTOR_LINE] = a.getColor(
- R.styleable.TimePicker_numbersSelectorColor,
- R.color.timepicker_default_selector_color_material);
+ mColorSelector[HOURS][SELECTOR_LINE] = selectorActivatedColor;
mPaintSelector[MINUTES][SELECTOR_CIRCLE] = new Paint();
mPaintSelector[MINUTES][SELECTOR_CIRCLE].setAntiAlias(true);
- mColorSelector[MINUTES][SELECTOR_CIRCLE] = a.getColor(
- R.styleable.TimePicker_numbersSelectorColor,
- R.color.timepicker_default_selector_color_material);
+ mColorSelector[MINUTES][SELECTOR_CIRCLE] = selectorActivatedColor;
mPaintSelector[MINUTES][SELECTOR_DOT] = new Paint();
mPaintSelector[MINUTES][SELECTOR_DOT].setAntiAlias(true);
- mColorSelector[MINUTES][SELECTOR_DOT] = a.getColor(
- R.styleable.TimePicker_numbersSelectorColor,
- R.color.timepicker_default_selector_color_material);
+ mColorSelector[MINUTES][SELECTOR_DOT] = textActivatedColor;
mPaintSelector[MINUTES][SELECTOR_LINE] = new Paint();
mPaintSelector[MINUTES][SELECTOR_LINE].setAntiAlias(true);
mPaintSelector[MINUTES][SELECTOR_LINE].setStrokeWidth(2);
- mColorSelector[MINUTES][SELECTOR_LINE] = a.getColor(
- R.styleable.TimePicker_numbersSelectorColor,
- R.color.timepicker_default_selector_color_material);
+ mColorSelector[MINUTES][SELECTOR_LINE] = selectorActivatedColor;
mPaintBackground.setColor(a.getColor(R.styleable.TimePicker_numbersBackgroundColor,
- res.getColor(R.color.timepicker_default_numbers_background_color_material)));
+ context.getColor(R.color.timepicker_default_numbers_background_color_material)));
mPaintBackground.setAntiAlias(true);
if (DEBUG) {
@@ -409,22 +407,10 @@ public class RadialTimePickerView extends View implements View.OnTouchListener {
initHoursAndMinutesText();
initData();
- mTransitionMidRadiusMultiplier = Float.parseFloat(
- res.getString(R.string.timepicker_transition_mid_radius_multiplier));
- mTransitionEndRadiusMultiplier = Float.parseFloat(
- res.getString(R.string.timepicker_transition_end_radius_multiplier));
-
- mTextGridHeights[HOURS] = new float[7];
- mTextGridHeights[MINUTES] = new float[7];
-
- mSelectionRadiusMultiplier = Float.parseFloat(
- res.getString(R.string.timepicker_selection_radius_multiplier));
+ mSelectionRadiusMultiplier = res.getFloat(R.dimen.timepicker_selection_radius_multiplier);
a.recycle();
- setOnTouchListener(this);
- setClickable(true);
-
// Initial values
final Calendar calendar = Calendar.getInstance(Locale.getDefault());
final int currentHour = calendar.get(Calendar.HOUR_OF_DAY);
@@ -436,21 +422,6 @@ public class RadialTimePickerView extends View implements View.OnTouchListener {
setHapticFeedbackEnabled(true);
}
- /**
- * Measure the view to end up as a square, based on the minimum of the height and width.
- */
- @Override
- public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- int measuredWidth = MeasureSpec.getSize(widthMeasureSpec);
- int widthMode = MeasureSpec.getMode(widthMeasureSpec);
- int measuredHeight = MeasureSpec.getSize(heightMeasureSpec);
- int heightMode = MeasureSpec.getMode(heightMeasureSpec);
- int minDimension = Math.min(measuredWidth, measuredHeight);
-
- super.onMeasure(MeasureSpec.makeMeasureSpec(minDimension, widthMode),
- MeasureSpec.makeMeasureSpec(minDimension, heightMode));
- }
-
public void initialize(int hour, int minute, boolean is24HourMode) {
if (mIs24HourMode != is24HourMode) {
mIs24HourMode = is24HourMode;
@@ -512,7 +483,6 @@ public class RadialTimePickerView extends View implements View.OnTouchListener {
mIsOnInnerCircle = isOnInnerCircle;
initData();
- updateLayoutData();
mTouchHelper.invalidateRoot();
}
@@ -601,24 +571,32 @@ public class RadialTimePickerView extends View implements View.OnTouchListener {
}
public void showHours(boolean animate) {
- if (mShowHours) return;
+ if (mShowHours) {
+ return;
+ }
+
mShowHours = true;
+
if (animate) {
startMinutesToHoursAnimation();
}
+
initData();
- updateLayoutData();
invalidate();
}
public void showMinutes(boolean animate) {
- if (!mShowHours) return;
+ if (!mShowHours) {
+ return;
+ }
+
mShowHours = false;
+
if (animate) {
startHoursToMinutesAnimation();
}
+
initData();
- updateLayoutData();
invalidate();
}
@@ -645,93 +623,71 @@ public class RadialTimePickerView extends View implements View.OnTouchListener {
final Resources res = getResources();
- if (mShowHours) {
- if (mIs24HourMode) {
- mCircleRadiusMultiplier[HOURS] = Float.parseFloat(
- res.getString(R.string.timepicker_circle_radius_multiplier_24HourMode));
- mNumbersRadiusMultiplier[HOURS] = Float.parseFloat(
- res.getString(R.string.timepicker_numbers_radius_multiplier_outer));
- mTextSizeMultiplier[HOURS] = Float.parseFloat(
- res.getString(R.string.timepicker_text_size_multiplier_outer));
-
- mNumbersRadiusMultiplier[HOURS_INNER] = Float.parseFloat(
- res.getString(R.string.timepicker_numbers_radius_multiplier_inner));
- mTextSizeMultiplier[HOURS_INNER] = Float.parseFloat(
- res.getString(R.string.timepicker_text_size_multiplier_inner));
- } else {
- mCircleRadiusMultiplier[HOURS] = Float.parseFloat(
- res.getString(R.string.timepicker_circle_radius_multiplier));
- mNumbersRadiusMultiplier[HOURS] = Float.parseFloat(
- res.getString(R.string.timepicker_numbers_radius_multiplier_normal));
- mTextSizeMultiplier[HOURS] = Float.parseFloat(
- res.getString(R.string.timepicker_text_size_multiplier_normal));
- }
+ if (mIs24HourMode) {
+ mNumbersRadiusMultiplier[HOURS] = res.getFloat(
+ R.dimen.timepicker_numbers_radius_multiplier_outer);
+ mTextSizeMultiplier[HOURS] = res.getFloat(
+ R.dimen.timepicker_text_size_multiplier_outer);
+
+ mNumbersRadiusMultiplier[HOURS_INNER] = res.getFloat(
+ R.dimen.timepicker_numbers_radius_multiplier_inner);
+ mTextSizeMultiplier[HOURS_INNER] = res.getFloat(
+ R.dimen.timepicker_text_size_multiplier_inner);
} else {
- mCircleRadiusMultiplier[MINUTES] = Float.parseFloat(
- res.getString(R.string.timepicker_circle_radius_multiplier));
- mNumbersRadiusMultiplier[MINUTES] = Float.parseFloat(
- res.getString(R.string.timepicker_numbers_radius_multiplier_normal));
- mTextSizeMultiplier[MINUTES] = Float.parseFloat(
- res.getString(R.string.timepicker_text_size_multiplier_normal));
- }
-
- mAnimationRadiusMultiplier[HOURS] = 1;
- mAnimationRadiusMultiplier[HOURS_INNER] = 1;
- mAnimationRadiusMultiplier[MINUTES] = 1;
-
- mAlpha[HOURS].setValue(mShowHours ? ALPHA_OPAQUE : ALPHA_TRANSPARENT);
- mAlpha[MINUTES].setValue(mShowHours ? ALPHA_TRANSPARENT : ALPHA_OPAQUE);
-
- mAlphaSelector[HOURS][SELECTOR_CIRCLE].setValue(
- mShowHours ? ALPHA_SELECTOR : ALPHA_TRANSPARENT);
- mAlphaSelector[HOURS][SELECTOR_DOT].setValue(
- mShowHours ? ALPHA_OPAQUE : ALPHA_TRANSPARENT);
- mAlphaSelector[HOURS][SELECTOR_LINE].setValue(
- mShowHours ? ALPHA_SELECTOR : ALPHA_TRANSPARENT);
-
- mAlphaSelector[MINUTES][SELECTOR_CIRCLE].setValue(
- mShowHours ? ALPHA_TRANSPARENT : ALPHA_SELECTOR);
- mAlphaSelector[MINUTES][SELECTOR_DOT].setValue(
- mShowHours ? ALPHA_TRANSPARENT : ALPHA_OPAQUE);
- mAlphaSelector[MINUTES][SELECTOR_LINE].setValue(
- mShowHours ? ALPHA_TRANSPARENT : ALPHA_SELECTOR);
+ mNumbersRadiusMultiplier[HOURS] = res.getFloat(
+ R.dimen.timepicker_numbers_radius_multiplier_normal);
+ mTextSizeMultiplier[HOURS] = res.getFloat(
+ R.dimen.timepicker_text_size_multiplier_normal);
+ }
+
+ mNumbersRadiusMultiplier[MINUTES] = res.getFloat(
+ R.dimen.timepicker_numbers_radius_multiplier_normal);
+ mTextSizeMultiplier[MINUTES] = res.getFloat(
+ R.dimen.timepicker_text_size_multiplier_normal);
+
+ final int hoursAlpha = mShowHours ? ALPHA_OPAQUE : ALPHA_TRANSPARENT;
+ mAlpha[HOURS].setValue(hoursAlpha);
+ mAlphaSelector[HOURS][SELECTOR_CIRCLE].setValue(hoursAlpha);
+ mAlphaSelector[HOURS][SELECTOR_DOT].setValue(hoursAlpha);
+ mAlphaSelector[HOURS][SELECTOR_LINE].setValue(hoursAlpha);
+
+ final int minutesAlpha = mShowHours ? ALPHA_TRANSPARENT : ALPHA_OPAQUE;
+ mAlpha[MINUTES].setValue(minutesAlpha);
+ mAlphaSelector[MINUTES][SELECTOR_CIRCLE].setValue(minutesAlpha);
+ mAlphaSelector[MINUTES][SELECTOR_DOT].setValue(minutesAlpha);
+ mAlphaSelector[MINUTES][SELECTOR_LINE].setValue(minutesAlpha);
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
- updateLayoutData();
- }
+ if (!changed) {
+ return;
+ }
- private void updateLayoutData() {
mXCenter = getWidth() / 2;
mYCenter = getHeight() / 2;
+ mCircleRadius = Math.min(mXCenter, mYCenter);
- final int min = Math.min(mXCenter, mYCenter);
-
- mCircleRadius[HOURS] = min * mCircleRadiusMultiplier[HOURS];
- mCircleRadius[HOURS_INNER] = min * mCircleRadiusMultiplier[HOURS];
- mCircleRadius[MINUTES] = min * mCircleRadiusMultiplier[MINUTES];
-
- mMinHypotenuseForInnerNumber = (int) (mCircleRadius[HOURS]
+ mMinHypotenuseForInnerNumber = (int) (mCircleRadius
* mNumbersRadiusMultiplier[HOURS_INNER]) - mSelectionRadius[HOURS];
- mMaxHypotenuseForOuterNumber = (int) (mCircleRadius[HOURS]
+ mMaxHypotenuseForOuterNumber = (int) (mCircleRadius
* mNumbersRadiusMultiplier[HOURS]) + mSelectionRadius[HOURS];
- mHalfwayHypotenusePoint = (int) (mCircleRadius[HOURS]
+ mHalfwayHypotenusePoint = (int) (mCircleRadius
* ((mNumbersRadiusMultiplier[HOURS] + mNumbersRadiusMultiplier[HOURS_INNER]) / 2));
- mTextSize[HOURS] = mCircleRadius[HOURS] * mTextSizeMultiplier[HOURS];
- mTextSize[MINUTES] = mCircleRadius[MINUTES] * mTextSizeMultiplier[MINUTES];
+ mTextSize[HOURS] = mCircleRadius * mTextSizeMultiplier[HOURS];
+ mTextSize[MINUTES] = mCircleRadius * mTextSizeMultiplier[MINUTES];
if (mIs24HourMode) {
- mInnerTextSize = mCircleRadius[HOURS] * mTextSizeMultiplier[HOURS_INNER];
+ mTextSize[HOURS_INNER] = mCircleRadius * mTextSizeMultiplier[HOURS_INNER];
}
- calculateGridSizesHours();
- calculateGridSizesMinutes();
+ calculatePositionsHours();
+ calculatePositionsMinutes();
- mSelectionRadius[HOURS] = (int) (mCircleRadius[HOURS] * mSelectionRadiusMultiplier);
+ mSelectionRadius[HOURS] = (int) (mCircleRadius * mSelectionRadiusMultiplier);
mSelectionRadius[HOURS_INNER] = mSelectionRadius[HOURS];
- mSelectionRadius[MINUTES] = (int) (mCircleRadius[MINUTES] * mSelectionRadiusMultiplier);
+ mSelectionRadius[MINUTES] = (int) (mCircleRadius * mSelectionRadiusMultiplier);
mTouchHelper.invalidateRoot();
}
@@ -744,25 +700,48 @@ public class RadialTimePickerView extends View implements View.OnTouchListener {
canvas.save();
}
- calculateGridSizesHours();
- calculateGridSizesMinutes();
-
drawCircleBackground(canvas);
- drawSelector(canvas);
-
- drawTextElements(canvas, mTextSize[HOURS], mTypeface, mOuterTextHours,
- mTextGridWidths[HOURS], mTextGridHeights[HOURS], mPaint[HOURS],
- mColor[HOURS], mAlpha[HOURS].getValue());
- if (mIs24HourMode && mInnerTextHours != null) {
- drawTextElements(canvas, mInnerTextSize, mTypeface, mInnerTextHours,
- mInnerTextGridWidths, mInnerTextGridHeights, mPaint[HOURS],
- mColor[HOURS], mAlpha[HOURS].getValue());
+ final int hoursAlpha = mAlpha[HOURS].getValue();
+ if (hoursAlpha > 0) {
+ // Draw the hour selector under the elements.
+ drawSelector(canvas, mIsOnInnerCircle ? HOURS_INNER : HOURS, null);
+
+ // Draw outer hours.
+ drawTextElements(canvas, mTextSize[HOURS], mTypeface, mOuterTextHours,
+ mOuterTextX[HOURS], mOuterTextY[HOURS], mPaint[HOURS], hoursAlpha,
+ !mIsOnInnerCircle, mSelectionDegrees[HOURS], false);
+
+ // Draw inner hours (12-23) for 24-hour time.
+ if (mIs24HourMode && mInnerTextHours != null) {
+ drawTextElements(canvas, mTextSize[HOURS_INNER], mTypeface, mInnerTextHours,
+ mInnerTextX, mInnerTextY, mPaint[HOURS], hoursAlpha,
+ mIsOnInnerCircle, mSelectionDegrees[HOURS], false);
+ }
}
- drawTextElements(canvas, mTextSize[MINUTES], mTypeface, mOuterTextMinutes,
- mTextGridWidths[MINUTES], mTextGridHeights[MINUTES], mPaint[MINUTES],
- mColor[MINUTES], mAlpha[MINUTES].getValue());
+ final int minutesAlpha = mAlpha[MINUTES].getValue();
+ if (minutesAlpha > 0) {
+ drawSelector(canvas, MINUTES, mSelectorPath);
+
+ // Exclude the selector region, then draw minutes with no
+ // activated states.
+ canvas.save(Canvas.CLIP_SAVE_FLAG);
+ canvas.clipPath(mSelectorPath, Region.Op.DIFFERENCE);
+ drawTextElements(canvas, mTextSize[MINUTES], mTypeface, mOuterTextMinutes,
+ mOuterTextX[MINUTES], mOuterTextY[MINUTES], mPaint[MINUTES], minutesAlpha,
+ false, 0, false);
+ canvas.restore();
+
+ // Intersect the selector region, then draw minutes with only
+ // activated states.
+ canvas.save(Canvas.CLIP_SAVE_FLAG);
+ canvas.clipPath(mSelectorPath, Region.Op.INTERSECT);
+ drawTextElements(canvas, mTextSize[MINUTES], mTypeface, mOuterTextMinutes,
+ mOuterTextX[MINUTES], mOuterTextY[MINUTES], mPaint[MINUTES], minutesAlpha,
+ true, mSelectionDegrees[MINUTES], true);
+ canvas.restore();
+ }
drawCenter(canvas);
@@ -774,31 +753,27 @@ public class RadialTimePickerView extends View implements View.OnTouchListener {
}
private void drawCircleBackground(Canvas canvas) {
- canvas.drawCircle(mXCenter, mYCenter, mCircleRadius[HOURS], mPaintBackground);
+ canvas.drawCircle(mXCenter, mYCenter, mCircleRadius, mPaintBackground);
}
private void drawCenter(Canvas canvas) {
canvas.drawCircle(mXCenter, mYCenter, CENTER_RADIUS, mPaintCenter);
}
- private void drawSelector(Canvas canvas) {
- drawSelector(canvas, mIsOnInnerCircle ? HOURS_INNER : HOURS);
- drawSelector(canvas, MINUTES);
- }
-
private int getMultipliedAlpha(int argb, int alpha) {
return (int) (Color.alpha(argb) * (alpha / 255.0) + 0.5);
}
- private void drawSelector(Canvas canvas, int index) {
+ private final Path mSelectorPath = new Path();
+
+ private void drawSelector(Canvas canvas, int index, Path selectorPath) {
// Calculate the current radius at which to place the selection circle.
- mLineLength[index] = (int) (mCircleRadius[index]
- * mNumbersRadiusMultiplier[index] * mAnimationRadiusMultiplier[index]);
+ mLineLength[index] = (int) (mCircleRadius * mNumbersRadiusMultiplier[index]);
- double selectionRadians = Math.toRadians(mSelectionDegrees[index]);
+ final double selectionRadians = Math.toRadians(mSelectionDegrees[index]);
- int pointX = mXCenter + (int) (mLineLength[index] * Math.sin(selectionRadians));
- int pointY = mYCenter - (int) (mLineLength[index] * Math.cos(selectionRadians));
+ float pointX = mXCenter + (int) (mLineLength[index] * Math.sin(selectionRadians));
+ float pointY = mYCenter - (int) (mLineLength[index] * Math.cos(selectionRadians));
int color;
int alpha;
@@ -812,23 +787,29 @@ public class RadialTimePickerView extends View implements View.OnTouchListener {
paint.setAlpha(getMultipliedAlpha(color, alpha));
canvas.drawCircle(pointX, pointY, mSelectionRadius[index], paint);
- // Draw the dot if needed
- if (mSelectionDegrees[index] % 30 != 0) {
+ // If needed, set up the clip path for later.
+ if (selectorPath != null) {
+ mSelectorPath.reset();
+ mSelectorPath.addCircle(pointX, pointY, mSelectionRadius[index], Path.Direction.CCW);
+ }
+
+ // Draw the dot if needed.
+ final boolean shouldDrawDot = mSelectionDegrees[index] % 30 != 0;
+ if (shouldDrawDot) {
// We're not on a direct tick
color = mColorSelector[index % 2][SELECTOR_DOT];
alpha = mAlphaSelector[index % 2][SELECTOR_DOT].getValue();
paint = mPaintSelector[index % 2][SELECTOR_DOT];
paint.setColor(color);
paint.setAlpha(getMultipliedAlpha(color, alpha));
- canvas.drawCircle(pointX, pointY, (mSelectionRadius[index] * 2 / 7), paint);
- } else {
- // We're not drawing the dot, so shorten the line to only go as far as the edge of the
- // selection circle
- int lineLength = mLineLength[index] - mSelectionRadius[index];
- pointX = mXCenter + (int) (lineLength * Math.sin(selectionRadians));
- pointY = mYCenter - (int) (lineLength * Math.cos(selectionRadians));
+ canvas.drawCircle(pointX, pointY, (mSelectionRadius[index] * 0.125f), paint);
}
+ // Shorten the line to only go to the edge of the selection circle.
+ final int lineLength = mLineLength[index] - mSelectionRadius[index];
+ pointX = mXCenter + (int) (lineLength * Math.sin(selectionRadians));
+ pointY = mYCenter - (int) (lineLength * Math.cos(selectionRadians));
+
// Draw the line
color = mColorSelector[index % 2][SELECTOR_LINE];
alpha = mAlphaSelector[index % 2][SELECTOR_LINE].getValue();
@@ -840,15 +821,15 @@ public class RadialTimePickerView extends View implements View.OnTouchListener {
private void drawDebug(Canvas canvas) {
// Draw outer numbers circle
- final float outerRadius = mCircleRadius[HOURS] * mNumbersRadiusMultiplier[HOURS];
+ final float outerRadius = mCircleRadius * mNumbersRadiusMultiplier[HOURS];
canvas.drawCircle(mXCenter, mYCenter, outerRadius, mPaintDebug);
// Draw inner numbers circle
- final float innerRadius = mCircleRadius[HOURS] * mNumbersRadiusMultiplier[HOURS_INNER];
+ final float innerRadius = mCircleRadius * mNumbersRadiusMultiplier[HOURS_INNER];
canvas.drawCircle(mXCenter, mYCenter, innerRadius, mPaintDebug);
// Draw outer background circle
- canvas.drawCircle(mXCenter, mYCenter, mCircleRadius[HOURS], mPaintDebug);
+ canvas.drawCircle(mXCenter, mYCenter, mCircleRadius, mPaintDebug);
// Draw outer rectangle for circles
float left = mXCenter - outerRadius;
@@ -858,10 +839,10 @@ public class RadialTimePickerView extends View implements View.OnTouchListener {
canvas.drawRect(left, top, right, bottom, mPaintDebug);
// Draw outer rectangle for background
- left = mXCenter - mCircleRadius[HOURS];
- top = mYCenter - mCircleRadius[HOURS];
- right = mXCenter + mCircleRadius[HOURS];
- bottom = mYCenter + mCircleRadius[HOURS];
+ left = mXCenter - mCircleRadius;
+ top = mYCenter - mCircleRadius;
+ right = mXCenter + mCircleRadius;
+ bottom = mYCenter + mCircleRadius;
canvas.drawRect(left, top, right, bottom, mPaintDebug);
// Draw outer view rectangle
@@ -888,185 +869,104 @@ public class RadialTimePickerView extends View implements View.OnTouchListener {
canvas.drawText(selected, x, y, paint);
}
- private void calculateGridSizesHours() {
+ private void calculatePositionsHours() {
// Calculate the text positions
- float numbersRadius = mCircleRadius[HOURS]
- * mNumbersRadiusMultiplier[HOURS] * mAnimationRadiusMultiplier[HOURS];
+ final float numbersRadius = mCircleRadius * mNumbersRadiusMultiplier[HOURS];
// Calculate the positions for the 12 numbers in the main circle.
- calculateGridSizes(mPaint[HOURS], numbersRadius, mXCenter, mYCenter,
- mTextSize[HOURS], mTextGridHeights[HOURS], mTextGridWidths[HOURS]);
+ calculatePositions(mPaint[HOURS], numbersRadius, mXCenter, mYCenter,
+ mTextSize[HOURS], mOuterTextX[HOURS], mOuterTextY[HOURS]);
// If we have an inner circle, calculate those positions too.
if (mIs24HourMode) {
- float innerNumbersRadius = mCircleRadius[HOURS_INNER]
- * mNumbersRadiusMultiplier[HOURS_INNER]
- * mAnimationRadiusMultiplier[HOURS_INNER];
+ final float innerNumbersRadius = mCircleRadius
+ * mNumbersRadiusMultiplier[HOURS_INNER];
- calculateGridSizes(mPaint[HOURS], innerNumbersRadius, mXCenter, mYCenter,
- mInnerTextSize, mInnerTextGridHeights, mInnerTextGridWidths);
+ calculatePositions(mPaint[HOURS], innerNumbersRadius, mXCenter, mYCenter,
+ mTextSize[HOURS_INNER], mInnerTextX, mInnerTextY);
}
}
- private void calculateGridSizesMinutes() {
+ private void calculatePositionsMinutes() {
// Calculate the text positions
- float numbersRadius = mCircleRadius[MINUTES]
- * mNumbersRadiusMultiplier[MINUTES] * mAnimationRadiusMultiplier[MINUTES];
+ final float numbersRadius = mCircleRadius * mNumbersRadiusMultiplier[MINUTES];
// Calculate the positions for the 12 numbers in the main circle.
- calculateGridSizes(mPaint[MINUTES], numbersRadius, mXCenter, mYCenter,
- mTextSize[MINUTES], mTextGridHeights[MINUTES], mTextGridWidths[MINUTES]);
+ calculatePositions(mPaint[MINUTES], numbersRadius, mXCenter, mYCenter,
+ mTextSize[MINUTES], mOuterTextX[MINUTES], mOuterTextY[MINUTES]);
}
-
/**
* Using the trigonometric Unit Circle, calculate the positions that the text will need to be
* drawn at based on the specified circle radius. Place the values in the textGridHeights and
* textGridWidths parameters.
*/
- private static void calculateGridSizes(Paint paint, float numbersRadius, float xCenter,
- float yCenter, float textSize, float[] textGridHeights, float[] textGridWidths) {
- /*
- * The numbers need to be drawn in a 7x7 grid, representing the points on the Unit Circle.
- */
- final float offset1 = numbersRadius;
- // cos(30) = a / r => r * cos(30)
- final float offset2 = numbersRadius * COSINE_30_DEGREES;
- // sin(30) = o / r => r * sin(30)
- final float offset3 = numbersRadius * SINE_30_DEGREES;
-
+ private static void calculatePositions(Paint paint, float radius, float xCenter, float yCenter,
+ float textSize, float[] x, float[] y) {
+ // Adjust yCenter to account for the text's baseline.
paint.setTextSize(textSize);
- // We'll need yTextBase to be slightly lower to account for the text's baseline.
yCenter -= (paint.descent() + paint.ascent()) / 2;
- textGridHeights[0] = yCenter - offset1;
- textGridWidths[0] = xCenter - offset1;
- textGridHeights[1] = yCenter - offset2;
- textGridWidths[1] = xCenter - offset2;
- textGridHeights[2] = yCenter - offset3;
- textGridWidths[2] = xCenter - offset3;
- textGridHeights[3] = yCenter;
- textGridWidths[3] = xCenter;
- textGridHeights[4] = yCenter + offset3;
- textGridWidths[4] = xCenter + offset3;
- textGridHeights[5] = yCenter + offset2;
- textGridWidths[5] = xCenter + offset2;
- textGridHeights[6] = yCenter + offset1;
- textGridWidths[6] = xCenter + offset1;
+ for (int i = 0; i < NUM_POSITIONS; i++) {
+ x[i] = xCenter - radius * COS_30[i];
+ y[i] = yCenter - radius * SIN_30[i];
+ }
}
/**
* Draw the 12 text values at the positions specified by the textGrid parameters.
*/
private void drawTextElements(Canvas canvas, float textSize, Typeface typeface, String[] texts,
- float[] textGridWidths, float[] textGridHeights, Paint paint, int color, int alpha) {
+ float[] textX, float[] textY, Paint paint, int alpha, boolean showActivated,
+ int activatedDegrees, boolean activatedOnly) {
paint.setTextSize(textSize);
paint.setTypeface(typeface);
- paint.setColor(color);
- paint.setAlpha(getMultipliedAlpha(color, alpha));
- canvas.drawText(texts[0], textGridWidths[3], textGridHeights[0], paint);
- canvas.drawText(texts[1], textGridWidths[4], textGridHeights[1], paint);
- canvas.drawText(texts[2], textGridWidths[5], textGridHeights[2], paint);
- canvas.drawText(texts[3], textGridWidths[6], textGridHeights[3], paint);
- canvas.drawText(texts[4], textGridWidths[5], textGridHeights[4], paint);
- canvas.drawText(texts[5], textGridWidths[4], textGridHeights[5], paint);
- canvas.drawText(texts[6], textGridWidths[3], textGridHeights[6], paint);
- canvas.drawText(texts[7], textGridWidths[2], textGridHeights[5], paint);
- canvas.drawText(texts[8], textGridWidths[1], textGridHeights[4], paint);
- canvas.drawText(texts[9], textGridWidths[0], textGridHeights[3], paint);
- canvas.drawText(texts[10], textGridWidths[1], textGridHeights[2], paint);
- canvas.drawText(texts[11], textGridWidths[2], textGridHeights[1], paint);
- }
- // Used for animating the hours by changing their radius
- @SuppressWarnings("unused")
- private void setAnimationRadiusMultiplierHours(float animationRadiusMultiplier) {
- mAnimationRadiusMultiplier[HOURS] = animationRadiusMultiplier;
- mAnimationRadiusMultiplier[HOURS_INNER] = animationRadiusMultiplier;
- }
+ // The activated index can touch a range of elements.
+ final float activatedIndex = activatedDegrees / (360.0f / NUM_POSITIONS);
+ final int activatedFloor = (int) activatedIndex;
+ final int activatedCeil = ((int) Math.ceil(activatedIndex)) % NUM_POSITIONS;
- // Used for animating the minutes by changing their radius
- @SuppressWarnings("unused")
- private void setAnimationRadiusMultiplierMinutes(float animationRadiusMultiplier) {
- mAnimationRadiusMultiplier[MINUTES] = animationRadiusMultiplier;
- }
+ for (int i = 0; i < 12; i++) {
+ final boolean activated = (activatedFloor == i || activatedCeil == i);
+ if (activatedOnly && !activated) {
+ continue;
+ }
- private static ObjectAnimator getRadiusDisappearAnimator(Object target,
- String radiusPropertyName, InvalidateUpdateListener updateListener,
- float midRadiusMultiplier, float endRadiusMultiplier) {
- Keyframe kf0, kf1, kf2;
- float midwayPoint = 0.2f;
- int duration = 500;
-
- kf0 = Keyframe.ofFloat(0f, 1);
- kf1 = Keyframe.ofFloat(midwayPoint, midRadiusMultiplier);
- kf2 = Keyframe.ofFloat(1f, endRadiusMultiplier);
- PropertyValuesHolder radiusDisappear = PropertyValuesHolder.ofKeyframe(
- radiusPropertyName, kf0, kf1, kf2);
-
- ObjectAnimator animator = ObjectAnimator.ofPropertyValuesHolder(
- target, radiusDisappear).setDuration(duration);
- animator.addUpdateListener(updateListener);
- return animator;
- }
+ final int stateMask = StateSet.VIEW_STATE_ENABLED
+ | (showActivated && activated ? StateSet.VIEW_STATE_ACTIVATED : 0);
+ final int color = mNumbersTextColor.getColorForState(StateSet.get(stateMask), 0);
+ paint.setColor(color);
+ paint.setAlpha(getMultipliedAlpha(color, alpha));
- private static ObjectAnimator getRadiusReappearAnimator(Object target,
- String radiusPropertyName, InvalidateUpdateListener updateListener,
- float midRadiusMultiplier, float endRadiusMultiplier) {
- Keyframe kf0, kf1, kf2, kf3;
- float midwayPoint = 0.2f;
- int duration = 500;
-
- // Set up animator for reappearing.
- float delayMultiplier = 0.25f;
- float transitionDurationMultiplier = 1f;
- float totalDurationMultiplier = transitionDurationMultiplier + delayMultiplier;
- int totalDuration = (int) (duration * totalDurationMultiplier);
- float delayPoint = (delayMultiplier * duration) / totalDuration;
- midwayPoint = 1 - (midwayPoint * (1 - delayPoint));
-
- kf0 = Keyframe.ofFloat(0f, endRadiusMultiplier);
- kf1 = Keyframe.ofFloat(delayPoint, endRadiusMultiplier);
- kf2 = Keyframe.ofFloat(midwayPoint, midRadiusMultiplier);
- kf3 = Keyframe.ofFloat(1f, 1);
- PropertyValuesHolder radiusReappear = PropertyValuesHolder.ofKeyframe(
- radiusPropertyName, kf0, kf1, kf2, kf3);
-
- ObjectAnimator animator = ObjectAnimator.ofPropertyValuesHolder(
- target, radiusReappear).setDuration(totalDuration);
- animator.addUpdateListener(updateListener);
- return animator;
+ canvas.drawText(texts[i], textX[i], textY[i], paint);
+ }
}
private static ObjectAnimator getFadeOutAnimator(IntHolder target, int startAlpha, int endAlpha,
InvalidateUpdateListener updateListener) {
- int duration = 500;
- ObjectAnimator animator = ObjectAnimator.ofInt(target, "value", startAlpha, endAlpha);
- animator.setDuration(duration);
+ final ObjectAnimator animator = ObjectAnimator.ofInt(target, "value", startAlpha, endAlpha);
+ animator.setDuration(FADE_OUT_DURATION);
animator.addUpdateListener(updateListener);
-
return animator;
}
private static ObjectAnimator getFadeInAnimator(IntHolder target, int startAlpha, int endAlpha,
InvalidateUpdateListener updateListener) {
- Keyframe kf0, kf1, kf2;
- int duration = 500;
-
- // Set up animator for reappearing.
- float delayMultiplier = 0.25f;
- float transitionDurationMultiplier = 1f;
- float totalDurationMultiplier = transitionDurationMultiplier + delayMultiplier;
- int totalDuration = (int) (duration * totalDurationMultiplier);
- float delayPoint = (delayMultiplier * duration) / totalDuration;
+ final float delayMultiplier = 0.25f;
+ final float transitionDurationMultiplier = 1f;
+ final float totalDurationMultiplier = transitionDurationMultiplier + delayMultiplier;
+ final int totalDuration = (int) (FADE_IN_DURATION * totalDurationMultiplier);
+ final float delayPoint = (delayMultiplier * FADE_IN_DURATION) / totalDuration;
+ final Keyframe kf0, kf1, kf2;
kf0 = Keyframe.ofInt(0f, startAlpha);
kf1 = Keyframe.ofInt(delayPoint, startAlpha);
kf2 = Keyframe.ofInt(1f, endAlpha);
- PropertyValuesHolder fadeIn = PropertyValuesHolder.ofKeyframe("value", kf0, kf1, kf2);
+ final PropertyValuesHolder fadeIn = PropertyValuesHolder.ofKeyframe("value", kf0, kf1, kf2);
- ObjectAnimator animator = ObjectAnimator.ofPropertyValuesHolder(
- target, fadeIn).setDuration(totalDuration);
+ final ObjectAnimator animator = ObjectAnimator.ofPropertyValuesHolder(target, fadeIn);
+ animator.setDuration(totalDuration);
animator.addUpdateListener(updateListener);
return animator;
}
@@ -1080,29 +980,23 @@ public class RadialTimePickerView extends View implements View.OnTouchListener {
private void startHoursToMinutesAnimation() {
if (mHoursToMinutesAnims.size() == 0) {
- mHoursToMinutesAnims.add(getRadiusDisappearAnimator(this,
- "animationRadiusMultiplierHours", mInvalidateUpdateListener,
- mTransitionMidRadiusMultiplier, mTransitionEndRadiusMultiplier));
mHoursToMinutesAnims.add(getFadeOutAnimator(mAlpha[HOURS],
ALPHA_OPAQUE, ALPHA_TRANSPARENT, mInvalidateUpdateListener));
mHoursToMinutesAnims.add(getFadeOutAnimator(mAlphaSelector[HOURS][SELECTOR_CIRCLE],
- ALPHA_SELECTOR, ALPHA_TRANSPARENT, mInvalidateUpdateListener));
+ ALPHA_OPAQUE, ALPHA_TRANSPARENT, mInvalidateUpdateListener));
mHoursToMinutesAnims.add(getFadeOutAnimator(mAlphaSelector[HOURS][SELECTOR_DOT],
ALPHA_OPAQUE, ALPHA_TRANSPARENT, mInvalidateUpdateListener));
mHoursToMinutesAnims.add(getFadeOutAnimator(mAlphaSelector[HOURS][SELECTOR_LINE],
- ALPHA_SELECTOR, ALPHA_TRANSPARENT, mInvalidateUpdateListener));
+ ALPHA_OPAQUE, ALPHA_TRANSPARENT, mInvalidateUpdateListener));
- mHoursToMinutesAnims.add(getRadiusReappearAnimator(this,
- "animationRadiusMultiplierMinutes", mInvalidateUpdateListener,
- mTransitionMidRadiusMultiplier, mTransitionEndRadiusMultiplier));
mHoursToMinutesAnims.add(getFadeInAnimator(mAlpha[MINUTES],
ALPHA_TRANSPARENT, ALPHA_OPAQUE, mInvalidateUpdateListener));
mHoursToMinutesAnims.add(getFadeInAnimator(mAlphaSelector[MINUTES][SELECTOR_CIRCLE],
- ALPHA_TRANSPARENT, ALPHA_SELECTOR, mInvalidateUpdateListener));
+ ALPHA_TRANSPARENT, ALPHA_OPAQUE, mInvalidateUpdateListener));
mHoursToMinutesAnims.add(getFadeInAnimator(mAlphaSelector[MINUTES][SELECTOR_DOT],
ALPHA_TRANSPARENT, ALPHA_OPAQUE, mInvalidateUpdateListener));
mHoursToMinutesAnims.add(getFadeInAnimator(mAlphaSelector[MINUTES][SELECTOR_LINE],
- ALPHA_TRANSPARENT, ALPHA_SELECTOR, mInvalidateUpdateListener));
+ ALPHA_TRANSPARENT, ALPHA_OPAQUE, mInvalidateUpdateListener));
}
if (mTransition != null && mTransition.isRunning()) {
@@ -1115,29 +1009,23 @@ public class RadialTimePickerView extends View implements View.OnTouchListener {
private void startMinutesToHoursAnimation() {
if (mMinuteToHoursAnims.size() == 0) {
- mMinuteToHoursAnims.add(getRadiusDisappearAnimator(this,
- "animationRadiusMultiplierMinutes", mInvalidateUpdateListener,
- mTransitionMidRadiusMultiplier, mTransitionEndRadiusMultiplier));
mMinuteToHoursAnims.add(getFadeOutAnimator(mAlpha[MINUTES],
ALPHA_OPAQUE, ALPHA_TRANSPARENT, mInvalidateUpdateListener));
mMinuteToHoursAnims.add(getFadeOutAnimator(mAlphaSelector[MINUTES][SELECTOR_CIRCLE],
- ALPHA_SELECTOR, ALPHA_TRANSPARENT, mInvalidateUpdateListener));
+ ALPHA_OPAQUE, ALPHA_TRANSPARENT, mInvalidateUpdateListener));
mMinuteToHoursAnims.add(getFadeOutAnimator(mAlphaSelector[MINUTES][SELECTOR_DOT],
ALPHA_OPAQUE, ALPHA_TRANSPARENT, mInvalidateUpdateListener));
mMinuteToHoursAnims.add(getFadeOutAnimator(mAlphaSelector[MINUTES][SELECTOR_LINE],
- ALPHA_SELECTOR, ALPHA_TRANSPARENT, mInvalidateUpdateListener));
+ ALPHA_OPAQUE, ALPHA_TRANSPARENT, mInvalidateUpdateListener));
- mMinuteToHoursAnims.add(getRadiusReappearAnimator(this,
- "animationRadiusMultiplierHours", mInvalidateUpdateListener,
- mTransitionMidRadiusMultiplier, mTransitionEndRadiusMultiplier));
mMinuteToHoursAnims.add(getFadeInAnimator(mAlpha[HOURS],
ALPHA_TRANSPARENT, ALPHA_OPAQUE, mInvalidateUpdateListener));
mMinuteToHoursAnims.add(getFadeInAnimator(mAlphaSelector[HOURS][SELECTOR_CIRCLE],
- ALPHA_TRANSPARENT, ALPHA_SELECTOR, mInvalidateUpdateListener));
+ ALPHA_TRANSPARENT, ALPHA_OPAQUE, mInvalidateUpdateListener));
mMinuteToHoursAnims.add(getFadeInAnimator(mAlphaSelector[HOURS][SELECTOR_DOT],
ALPHA_TRANSPARENT, ALPHA_OPAQUE, mInvalidateUpdateListener));
mMinuteToHoursAnims.add(getFadeInAnimator(mAlphaSelector[HOURS][SELECTOR_LINE],
- ALPHA_TRANSPARENT, ALPHA_SELECTOR, mInvalidateUpdateListener));
+ ALPHA_TRANSPARENT, ALPHA_OPAQUE, mInvalidateUpdateListener));
}
if (mTransition != null && mTransition.isRunning()) {
@@ -1148,20 +1036,21 @@ public class RadialTimePickerView extends View implements View.OnTouchListener {
mTransition.start();
}
- private int getDegreesFromXY(float x, float y) {
+ private int getDegreesFromXY(float x, float y, boolean constrainOutside) {
final double hypotenuse = Math.sqrt(
(y - mYCenter) * (y - mYCenter) + (x - mXCenter) * (x - mXCenter));
// Basic check if we're outside the range of the disk
- if (hypotenuse > mCircleRadius[HOURS]) {
+ if (constrainOutside && hypotenuse > mCircleRadius) {
return -1;
}
+
// Check
if (mIs24HourMode && mShowHours) {
if (hypotenuse >= mMinHypotenuseForInnerNumber
&& hypotenuse <= mHalfwayHypotenusePoint) {
mIsOnInnerCircle = true;
- } else if (hypotenuse <= mMaxHypotenuseForOuterNumber
+ } else if ((hypotenuse <= mMaxHypotenuseForOuterNumber || !constrainOutside)
&& hypotenuse >= mHalfwayHypotenusePoint) {
mIsOnInnerCircle = false;
} else {
@@ -1169,11 +1058,12 @@ public class RadialTimePickerView extends View implements View.OnTouchListener {
}
} else {
final int index = (mShowHours) ? HOURS : MINUTES;
- final float length = (mCircleRadius[index] * mNumbersRadiusMultiplier[index]);
- final int distanceToNumber = (int) Math.abs(hypotenuse - length);
+ final float length = (mCircleRadius * mNumbersRadiusMultiplier[index]);
+ final int distanceToNumber = (int) (hypotenuse - length);
final int maxAllowedDistance =
- (int) (mCircleRadius[index] * (1 - mNumbersRadiusMultiplier[index]));
- if (distanceToNumber > maxAllowedDistance) {
+ (int) (mCircleRadius * (1 - mNumbersRadiusMultiplier[index]));
+ if (distanceToNumber < -maxAllowedDistance
+ || (constrainOutside && distanceToNumber > maxAllowedDistance)) {
return -1;
}
}
@@ -1203,7 +1093,7 @@ public class RadialTimePickerView extends View implements View.OnTouchListener {
boolean mChangedDuringTouch = false;
@Override
- public boolean onTouch(View v, MotionEvent event) {
+ public boolean onTouchEvent(MotionEvent event) {
if (!mInputEnabled) {
return true;
}
@@ -1240,7 +1130,7 @@ public class RadialTimePickerView extends View implements View.OnTouchListener {
// Calling getDegreesFromXY has side effects, so cache
// whether we used to be on the inner circle.
final boolean wasOnInnerCircle = mIsOnInnerCircle;
- final int degrees = getDegreesFromXY(x, y);
+ final int degrees = getDegreesFromXY(x, y, false);
if (degrees == -1) {
return false;
}
@@ -1385,7 +1275,7 @@ public class RadialTimePickerView extends View implements View.OnTouchListener {
// Calling getDegreesXY() has side-effects, so we need to cache the
// current inner circle value and restore after the call.
final boolean wasOnInnerCircle = mIsOnInnerCircle;
- final int degrees = getDegreesFromXY(x, y);
+ final int degrees = getDegreesFromXY(x, y, true);
final boolean isOnInnerCircle = mIsOnInnerCircle;
mIsOnInnerCircle = wasOnInnerCircle;
@@ -1541,16 +1431,16 @@ public class RadialTimePickerView extends View implements View.OnTouchListener {
if (type == TYPE_HOUR) {
final boolean innerCircle = mIs24HourMode && value > 0 && value <= 12;
if (innerCircle) {
- centerRadius = mCircleRadius[HOURS_INNER] * mNumbersRadiusMultiplier[HOURS_INNER];
+ centerRadius = mCircleRadius * mNumbersRadiusMultiplier[HOURS_INNER];
radius = mSelectionRadius[HOURS_INNER];
} else {
- centerRadius = mCircleRadius[HOURS] * mNumbersRadiusMultiplier[HOURS];
+ centerRadius = mCircleRadius * mNumbersRadiusMultiplier[HOURS];
radius = mSelectionRadius[HOURS];
}
degrees = getDegreesForHour(value);
} else if (type == TYPE_MINUTE) {
- centerRadius = mCircleRadius[MINUTES] * mNumbersRadiusMultiplier[MINUTES];
+ centerRadius = mCircleRadius * mNumbersRadiusMultiplier[MINUTES];
degrees = getDegreesForMinute(value);
radius = mSelectionRadius[MINUTES];
} else {
diff --git a/core/java/android/widget/RadioButton.java b/core/java/android/widget/RadioButton.java
index afc4830..aebc1b6 100644
--- a/core/java/android/widget/RadioButton.java
+++ b/core/java/android/widget/RadioButton.java
@@ -80,14 +80,7 @@ public class RadioButton extends CompoundButton {
}
@Override
- public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
- super.onInitializeAccessibilityEvent(event);
- event.setClassName(RadioButton.class.getName());
- }
-
- @Override
- public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(info);
- info.setClassName(RadioButton.class.getName());
+ public CharSequence getAccessibilityClassName() {
+ return RadioButton.class.getName();
}
}
diff --git a/core/java/android/widget/RadioGroup.java b/core/java/android/widget/RadioGroup.java
index 78d05b0..f04bb3d 100644
--- a/core/java/android/widget/RadioGroup.java
+++ b/core/java/android/widget/RadioGroup.java
@@ -241,15 +241,8 @@ public class RadioGroup extends LinearLayout {
}
@Override
- public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
- super.onInitializeAccessibilityEvent(event);
- event.setClassName(RadioGroup.class.getName());
- }
-
- @Override
- public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(info);
- info.setClassName(RadioGroup.class.getName());
+ public CharSequence getAccessibilityClassName() {
+ return RadioGroup.class.getName();
}
/**
diff --git a/core/java/android/widget/RatingBar.java b/core/java/android/widget/RatingBar.java
index 82b490e..f18e372 100644
--- a/core/java/android/widget/RatingBar.java
+++ b/core/java/android/widget/RatingBar.java
@@ -43,7 +43,7 @@ import com.android.internal.R;
* <p>
* The secondary progress should not be modified by the client as it is used
* internally as the background for a fractionally filled star.
- *
+ *
* @attr ref android.R.styleable#RatingBar_numStars
* @attr ref android.R.styleable#RatingBar_rating
* @attr ref android.R.styleable#RatingBar_stepSize
@@ -58,14 +58,14 @@ public class RatingBar extends AbsSeekBar {
* programmatically.
*/
public interface OnRatingBarChangeListener {
-
+
/**
* Notification that the rating has changed. Clients can use the
* fromUser parameter to distinguish user-initiated changes from those
* that occurred programmatically. This will not be called continuously
* while the user is dragging, only when the user finalizes a rating by
* lifting the touch.
- *
+ *
* @param ratingBar The RatingBar whose rating has changed.
* @param rating The current rating. This will be in the range
* 0..numStars.
@@ -79,9 +79,9 @@ public class RatingBar extends AbsSeekBar {
private int mNumStars = 5;
private int mProgressOnStartTracking;
-
+
private OnRatingBarChangeListener mOnRatingBarChangeListener;
-
+
public RatingBar(Context context, AttributeSet attrs, int defStyleAttr) {
this(context, attrs, defStyleAttr, 0);
}
@@ -98,19 +98,19 @@ public class RatingBar extends AbsSeekBar {
a.recycle();
if (numStars > 0 && numStars != mNumStars) {
- setNumStars(numStars);
+ setNumStars(numStars);
}
-
+
if (stepSize >= 0) {
setStepSize(stepSize);
} else {
setStepSize(0.5f);
}
-
+
if (rating >= 0) {
setRating(rating);
}
-
+
// A touch inside a star fill up to that fractional area (slightly more
// than 1 so boundaries round up).
mTouchProgressOffset = 1.1f;
@@ -123,16 +123,16 @@ public class RatingBar extends AbsSeekBar {
public RatingBar(Context context) {
this(context, null);
}
-
+
/**
* Sets the listener to be called when the rating changes.
- *
+ *
* @param listener The listener.
*/
public void setOnRatingBarChangeListener(OnRatingBarChangeListener listener) {
mOnRatingBarChangeListener = listener;
}
-
+
/**
* @return The listener (may be null) that is listening for rating change
* events.
@@ -144,7 +144,7 @@ public class RatingBar extends AbsSeekBar {
/**
* Whether this rating bar should only be an indicator (thus non-changeable
* by the user).
- *
+ *
* @param isIndicator Whether it should be an indicator.
*
* @attr ref android.R.styleable#RatingBar_isIndicator
@@ -153,7 +153,7 @@ public class RatingBar extends AbsSeekBar {
mIsUserSeekable = !isIndicator;
setFocusable(!isIndicator);
}
-
+
/**
* @return Whether this rating bar is only an indicator.
*
@@ -162,21 +162,21 @@ public class RatingBar extends AbsSeekBar {
public boolean isIndicator() {
return !mIsUserSeekable;
}
-
+
/**
* Sets the number of stars to show. In order for these to be shown
* properly, it is recommended the layout width of this widget be wrap
* content.
- *
+ *
* @param numStars The number of stars.
*/
public void setNumStars(final int numStars) {
if (numStars <= 0) {
return;
}
-
+
mNumStars = numStars;
-
+
// This causes the width to change, so re-layout
requestLayout();
}
@@ -188,10 +188,10 @@ public class RatingBar extends AbsSeekBar {
public int getNumStars() {
return mNumStars;
}
-
+
/**
* Sets the rating (the number of stars filled).
- *
+ *
* @param rating The rating to set.
*/
public void setRating(float rating) {
@@ -200,16 +200,16 @@ public class RatingBar extends AbsSeekBar {
/**
* Gets the current rating (number of stars filled).
- *
+ *
* @return The current rating.
*/
public float getRating() {
- return getProgress() / getProgressPerStar();
+ return getProgress() / getProgressPerStar();
}
/**
* Sets the step size (granularity) of this rating bar.
- *
+ *
* @param stepSize The step size of this rating bar. For example, if
* half-star granularity is wanted, this would be 0.5.
*/
@@ -217,7 +217,7 @@ public class RatingBar extends AbsSeekBar {
if (stepSize <= 0) {
return;
}
-
+
final float newMax = mNumStars / stepSize;
final int newProgress = (int) (newMax / getMax() * getProgress());
setMax((int) newMax);
@@ -226,13 +226,13 @@ public class RatingBar extends AbsSeekBar {
/**
* Gets the step size of this rating bar.
- *
+ *
* @return The step size.
*/
public float getStepSize() {
return (float) getNumStars() / getMax();
}
-
+
/**
* @return The amount of progress that fits into a star
*/
@@ -251,12 +251,12 @@ public class RatingBar extends AbsSeekBar {
}
@Override
- void onProgressRefresh(float scale, boolean fromUser) {
- super.onProgressRefresh(scale, fromUser);
+ void onProgressRefresh(float scale, boolean fromUser, int progress) {
+ super.onProgressRefresh(scale, fromUser, progress);
// Keep secondary progress in sync with primary
- updateSecondaryProgress(getProgress());
-
+ updateSecondaryProgress(progress);
+
if (!fromUser) {
// Callback for non-user rating changes
dispatchRatingChange(false);
@@ -267,7 +267,7 @@ public class RatingBar extends AbsSeekBar {
* The secondary progress is used to differentiate the background of a
* partially filled star. This method keeps the secondary progress in sync
* with the progress.
- *
+ *
* @param progress The primary progress level.
*/
private void updateSecondaryProgress(int progress) {
@@ -278,11 +278,11 @@ public class RatingBar extends AbsSeekBar {
setSecondaryProgress(secondaryProgress);
}
}
-
+
@Override
protected synchronized void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
-
+
if (mSampleTile != null) {
// TODO: Once ProgressBar's TODOs are gone, this can be done more
// cleanly than mSampleTile
@@ -295,7 +295,7 @@ public class RatingBar extends AbsSeekBar {
@Override
void onStartTrackingTouch() {
mProgressOnStartTracking = getProgress();
-
+
super.onStartTrackingTouch();
}
@@ -327,19 +327,12 @@ public class RatingBar extends AbsSeekBar {
if (max <= 0) {
return;
}
-
+
super.setMax(max);
}
-
- @Override
- public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
- super.onInitializeAccessibilityEvent(event);
- event.setClassName(RatingBar.class.getName());
- }
@Override
- public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(info);
- info.setClassName(RatingBar.class.getName());
+ public CharSequence getAccessibilityClassName() {
+ return RatingBar.class.getName();
}
}
diff --git a/core/java/android/widget/RelativeLayout.java b/core/java/android/widget/RelativeLayout.java
index 5b604cd..89b1d54 100644
--- a/core/java/android/widget/RelativeLayout.java
+++ b/core/java/android/widget/RelativeLayout.java
@@ -201,7 +201,6 @@ public class RelativeLayout extends ViewGroup {
private static final int VALUE_NOT_SET = Integer.MIN_VALUE;
private View mBaselineView = null;
- private boolean mHasBaselineAlignedChild;
private int mGravity = Gravity.START | Gravity.TOP;
private final Rect mContentBounds = new Rect();
@@ -417,8 +416,6 @@ public class RelativeLayout extends ViewGroup {
height = myHeight;
}
- mHasBaselineAlignedChild = false;
-
View ignore = null;
int gravity = mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK;
final boolean horizontalGravity = gravity != Gravity.START && gravity != 0;
@@ -473,11 +470,11 @@ public class RelativeLayout extends ViewGroup {
final int targetSdkVersion = getContext().getApplicationInfo().targetSdkVersion;
for (int i = 0; i < count; i++) {
- View child = views[i];
+ final View child = views[i];
if (child.getVisibility() != GONE) {
- LayoutParams params = (LayoutParams) child.getLayoutParams();
-
- applyVerticalSizeRules(params, myHeight);
+ final LayoutParams params = (LayoutParams) child.getLayoutParams();
+
+ applyVerticalSizeRules(params, myHeight, child.getBaseline());
measureChild(child, params, myWidth, myHeight);
if (positionChildVertical(child, params, myHeight, isWrapContentHeight)) {
offsetVerticalAxis = true;
@@ -519,26 +516,6 @@ public class RelativeLayout extends ViewGroup {
}
}
- if (mHasBaselineAlignedChild) {
- for (int i = 0; i < count; i++) {
- View child = getChildAt(i);
- if (child.getVisibility() != GONE) {
- LayoutParams params = (LayoutParams) child.getLayoutParams();
- alignBaseline(child, params);
-
- if (child != ignore || verticalGravity) {
- left = Math.min(left, params.mLeft - params.leftMargin);
- top = Math.min(top, params.mTop - params.topMargin);
- }
-
- if (child != ignore || horizontalGravity) {
- right = Math.max(right, params.mRight + params.rightMargin);
- bottom = Math.max(bottom, params.mBottom + params.bottomMargin);
- }
- }
- }
- }
-
if (isWrapContentWidth) {
// Width already has left padding in it since it was calculated by looking at
// the right of each child view
@@ -638,39 +615,22 @@ public class RelativeLayout extends ViewGroup {
params.mRight -= offsetWidth;
}
}
-
}
- setMeasuredDimension(width, height);
- }
-
- private void alignBaseline(View child, LayoutParams params) {
- final int layoutDirection = getLayoutDirection();
- int[] rules = params.getRules(layoutDirection);
- int anchorBaseline = getRelatedViewBaseline(rules, ALIGN_BASELINE);
-
- if (anchorBaseline != -1) {
- LayoutParams anchorParams = getRelatedViewParams(rules, ALIGN_BASELINE);
- if (anchorParams != null) {
- int offset = anchorParams.mTop + anchorBaseline;
- int baseline = child.getBaseline();
- if (baseline != -1) {
- offset -= baseline;
- }
- int height = params.mBottom - params.mTop;
- params.mTop = offset;
- params.mBottom = params.mTop + height;
+ // Use the bottom-most view as the baseline.
+ View baselineView = null;
+ int baseline = 0;
+ for (int i = 0; i < count; i++) {
+ final View child = getChildAt(i);
+ final int childBaseline = child.getBaseline();
+ if (childBaseline >= baseline) {
+ baselineView = child;
+ baseline = childBaseline;
}
}
+ mBaselineView = baselineView;
- if (mBaselineView == null) {
- mBaselineView = child;
- } else {
- LayoutParams lp = (LayoutParams) mBaselineView.getLayoutParams();
- if (params.mTop < lp.mTop || (params.mTop == lp.mTop && params.mLeft < lp.mLeft)) {
- mBaselineView = child;
- }
- }
+ setMeasuredDimension(width, height);
}
/**
@@ -950,8 +910,20 @@ public class RelativeLayout extends ViewGroup {
}
}
- private void applyVerticalSizeRules(LayoutParams childParams, int myHeight) {
- int[] rules = childParams.getRules();
+ private void applyVerticalSizeRules(LayoutParams childParams, int myHeight, int myBaseline) {
+ final int[] rules = childParams.getRules();
+
+ // Baseline alignment overrides any explicitly specified top or bottom.
+ int baselineOffset = getRelatedViewBaselineOffset(rules);
+ if (baselineOffset != -1) {
+ if (myBaseline != -1) {
+ baselineOffset -= myBaseline;
+ }
+ childParams.mTop = baselineOffset;
+ childParams.mBottom = VALUE_NOT_SET;
+ return;
+ }
+
RelativeLayout.LayoutParams anchorParams;
childParams.mTop = VALUE_NOT_SET;
@@ -1000,10 +972,6 @@ public class RelativeLayout extends ViewGroup {
childParams.mBottom = myHeight - mPaddingBottom - childParams.bottomMargin;
}
}
-
- if (rules[ALIGN_BASELINE] != 0) {
- mHasBaselineAlignedChild = true;
- }
}
private View getRelatedView(int[] rules, int relation) {
@@ -1038,10 +1006,17 @@ public class RelativeLayout extends ViewGroup {
return null;
}
- private int getRelatedViewBaseline(int[] rules, int relation) {
- View v = getRelatedView(rules, relation);
+ private int getRelatedViewBaselineOffset(int[] rules) {
+ final View v = getRelatedView(rules, ALIGN_BASELINE);
if (v != null) {
- return v.getBaseline();
+ final int baseline = v.getBaseline();
+ if (baseline != -1) {
+ final ViewGroup.LayoutParams params = v.getLayoutParams();
+ if (params instanceof LayoutParams) {
+ final LayoutParams anchorParams = (LayoutParams) v.getLayoutParams();
+ return anchorParams.mTop + baseline;
+ }
+ }
}
return -1;
}
@@ -1104,8 +1079,9 @@ public class RelativeLayout extends ViewGroup {
return new LayoutParams(p);
}
+ /** @hide */
@Override
- public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
+ public boolean dispatchPopulateAccessibilityEventInternal(AccessibilityEvent event) {
if (mTopToBottomLeftToRightSet == null) {
mTopToBottomLeftToRightSet = new TreeSet<View>(new TopToBottomLeftToRightComparator());
}
@@ -1128,15 +1104,8 @@ public class RelativeLayout extends ViewGroup {
}
@Override
- public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
- super.onInitializeAccessibilityEvent(event);
- event.setClassName(RelativeLayout.class.getName());
- }
-
- @Override
- public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(info);
- info.setClassName(RelativeLayout.class.getName());
+ public CharSequence getAccessibilityClassName() {
+ return RelativeLayout.class.getName();
}
/**
diff --git a/core/java/android/widget/ResourceCursorAdapter.java b/core/java/android/widget/ResourceCursorAdapter.java
index 7341c2c..100f919 100644
--- a/core/java/android/widget/ResourceCursorAdapter.java
+++ b/core/java/android/widget/ResourceCursorAdapter.java
@@ -17,7 +17,9 @@
package android.widget;
import android.content.Context;
+import android.content.res.Resources;
import android.database.Cursor;
+import android.view.ContextThemeWrapper;
import android.view.View;
import android.view.ViewGroup;
import android.view.LayoutInflater;
@@ -31,9 +33,10 @@ public abstract class ResourceCursorAdapter extends CursorAdapter {
private int mLayout;
private int mDropDownLayout;
-
+
private LayoutInflater mInflater;
-
+ private LayoutInflater mDropDownInflater;
+
/**
* Constructor the enables auto-requery.
*
@@ -52,8 +55,9 @@ public abstract class ResourceCursorAdapter extends CursorAdapter {
super(context, c);
mLayout = mDropDownLayout = layout;
mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+ mDropDownInflater = mInflater;
}
-
+
/**
* Constructor with default behavior as per
* {@link CursorAdapter#CursorAdapter(Context, Cursor, boolean)}; it is recommended
@@ -74,6 +78,7 @@ public abstract class ResourceCursorAdapter extends CursorAdapter {
super(context, c, autoRequery);
mLayout = mDropDownLayout = layout;
mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+ mDropDownInflater = mInflater;
}
/**
@@ -91,11 +96,37 @@ public abstract class ResourceCursorAdapter extends CursorAdapter {
super(context, c, flags);
mLayout = mDropDownLayout = layout;
mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+ mDropDownInflater = mInflater;
+ }
+
+ /**
+ * Sets the {@link android.content.res.Resources.Theme} against which drop-down views are
+ * inflated.
+ * <p>
+ * By default, drop-down views are inflated against the theme of the
+ * {@link Context} passed to the adapter's constructor.
+ *
+ * @param theme the theme against which to inflate drop-down views or
+ * {@code null} to use the theme from the adapter's context
+ * @see #newDropDownView(Context, Cursor, ViewGroup)
+ */
+ @Override
+ public void setDropDownViewTheme(Resources.Theme theme) {
+ super.setDropDownViewTheme(theme);
+
+ if (theme == null) {
+ mDropDownInflater = null;
+ } else if (theme == mInflater.getContext().getTheme()) {
+ mDropDownInflater = mInflater;
+ } else {
+ final Context context = new ContextThemeWrapper(mContext, theme);
+ mDropDownInflater = LayoutInflater.from(context);
+ }
}
/**
* Inflates view(s) from the specified XML file.
- *
+ *
* @see android.widget.CursorAdapter#newView(android.content.Context,
* android.database.Cursor, ViewGroup)
*/
@@ -106,7 +137,7 @@ public abstract class ResourceCursorAdapter extends CursorAdapter {
@Override
public View newDropDownView(Context context, Cursor cursor, ViewGroup parent) {
- return mInflater.inflate(mDropDownLayout, parent, false);
+ return mDropDownInflater.inflate(mDropDownLayout, parent, false);
}
/**
diff --git a/core/java/android/widget/ScrollBarDrawable.java b/core/java/android/widget/ScrollBarDrawable.java
index 8eff1aa..6fd90c3 100644
--- a/core/java/android/widget/ScrollBarDrawable.java
+++ b/core/java/android/widget/ScrollBarDrawable.java
@@ -23,63 +23,74 @@ import android.graphics.Rect;
import android.graphics.drawable.Drawable;
/**
- * This is only used by View for displaying its scroll bars. It should probably
+ * This is only used by View for displaying its scroll bars. It should probably
* be moved in to the view package since it is used in that lower-level layer.
* For now, we'll hide it so it can be cleaned up later.
+ *
* {@hide}
*/
-public class ScrollBarDrawable extends Drawable {
- private static final int[] STATE_ENABLED = new int[] { android.R.attr.state_enabled };
-
+public class ScrollBarDrawable extends Drawable implements Drawable.Callback {
private Drawable mVerticalTrack;
private Drawable mHorizontalTrack;
private Drawable mVerticalThumb;
private Drawable mHorizontalThumb;
+
private int mRange;
private int mOffset;
private int mExtent;
+
private boolean mVertical;
- private boolean mChanged;
+ private boolean mBoundsChanged;
private boolean mRangeChanged;
- private final Rect mTempBounds = new Rect();
private boolean mAlwaysDrawHorizontalTrack;
private boolean mAlwaysDrawVerticalTrack;
private boolean mMutated;
- public ScrollBarDrawable() {
- }
+ private int mAlpha = 255;
+ private boolean mHasSetAlpha;
+
+ private ColorFilter mColorFilter;
+ private boolean mHasSetColorFilter;
/**
- * Indicate whether the horizontal scrollbar track should always be drawn regardless of the
- * extent. Defaults to false.
+ * Indicate whether the horizontal scrollbar track should always be drawn
+ * regardless of the extent. Defaults to false.
+ *
+ * @param alwaysDrawTrack Whether the track should always be drawn
*
- * @param alwaysDrawTrack Set to true if the track should always be drawn
+ * @see #getAlwaysDrawHorizontalTrack()
*/
public void setAlwaysDrawHorizontalTrack(boolean alwaysDrawTrack) {
mAlwaysDrawHorizontalTrack = alwaysDrawTrack;
}
/**
- * Indicate whether the vertical scrollbar track should always be drawn regardless of the
- * extent. Defaults to false.
+ * Indicate whether the vertical scrollbar track should always be drawn
+ * regardless of the extent. Defaults to false.
+ *
+ * @param alwaysDrawTrack Whether the track should always be drawn
*
- * @param alwaysDrawTrack Set to true if the track should always be drawn
+ * @see #getAlwaysDrawVerticalTrack()
*/
public void setAlwaysDrawVerticalTrack(boolean alwaysDrawTrack) {
mAlwaysDrawVerticalTrack = alwaysDrawTrack;
}
/**
- * Indicates whether the vertical scrollbar track should always be drawn regardless of the
- * extent.
+ * @return whether the vertical scrollbar track should always be drawn
+ * regardless of the extent.
+ *
+ * @see #setAlwaysDrawVerticalTrack(boolean)
*/
public boolean getAlwaysDrawVerticalTrack() {
return mAlwaysDrawVerticalTrack;
}
/**
- * Indicates whether the horizontal scrollbar track should always be drawn regardless of the
- * extent.
+ * @return whether the horizontal scrollbar track should always be drawn
+ * regardless of the extent.
+ *
+ * @see #setAlwaysDrawHorizontalTrack(boolean)
*/
public boolean getAlwaysDrawHorizontalTrack() {
return mAlwaysDrawHorizontalTrack;
@@ -87,17 +98,18 @@ public class ScrollBarDrawable extends Drawable {
public void setParameters(int range, int offset, int extent, boolean vertical) {
if (mVertical != vertical) {
- mChanged = true;
+ mVertical = vertical;
+
+ mBoundsChanged = true;
}
if (mRange != range || mOffset != offset || mExtent != extent) {
+ mRange = range;
+ mOffset = offset;
+ mExtent = extent;
+
mRangeChanged = true;
}
-
- mRange = range;
- mOffset = offset;
- mExtent = extent;
- mVertical = vertical;
}
@Override
@@ -113,27 +125,29 @@ public class ScrollBarDrawable extends Drawable {
drawThumb = false;
}
- Rect r = getBounds();
+ final Rect r = getBounds();
if (canvas.quickReject(r.left, r.top, r.right, r.bottom, Canvas.EdgeType.AA)) {
return;
}
+
if (drawTrack) {
drawTrack(canvas, r, vertical);
}
if (drawThumb) {
- int size = vertical ? r.height() : r.width();
- int thickness = vertical ? r.width() : r.height();
- int length = Math.round((float) size * extent / range);
- int offset = Math.round((float) (size - length) * mOffset / (range - extent));
+ final int size = vertical ? r.height() : r.width();
+ final int thickness = vertical ? r.width() : r.height();
+ final int minLength = thickness * 2;
- // avoid the tiny thumb
- int minLength = thickness * 2;
+ // Avoid the tiny thumb.
+ int length = Math.round((float) size * extent / range);
if (length < minLength) {
length = minLength;
}
- // avoid the too-big thumb
- if (offset + length > size) {
+
+ // Avoid the too-big thumb.
+ int offset = Math.round((float) (size - length) * mOffset / (range - extent));
+ if (offset > size - length) {
offset = size - length;
}
@@ -144,90 +158,130 @@ public class ScrollBarDrawable extends Drawable {
@Override
protected void onBoundsChange(Rect bounds) {
super.onBoundsChange(bounds);
- mChanged = true;
+ mBoundsChanged = true;
+ }
+
+ @Override
+ public boolean isStateful() {
+ return (mVerticalTrack != null && mVerticalTrack.isStateful())
+ || (mVerticalThumb != null && mVerticalThumb.isStateful())
+ || (mHorizontalTrack != null && mHorizontalTrack.isStateful())
+ || (mHorizontalThumb != null && mHorizontalThumb.isStateful())
+ || super.isStateful();
+ }
+
+ @Override
+ protected boolean onStateChange(int[] state) {
+ boolean changed = super.onStateChange(state);
+ if (mVerticalTrack != null) {
+ changed |= mVerticalTrack.setState(state);
+ }
+ if (mVerticalThumb != null) {
+ changed |= mVerticalThumb.setState(state);
+ }
+ if (mHorizontalTrack != null) {
+ changed |= mHorizontalTrack.setState(state);
+ }
+ if (mHorizontalThumb != null) {
+ changed |= mHorizontalThumb.setState(state);
+ }
+ return changed;
}
- protected void drawTrack(Canvas canvas, Rect bounds, boolean vertical) {
- Drawable track;
+ private void drawTrack(Canvas canvas, Rect bounds, boolean vertical) {
+ final Drawable track;
if (vertical) {
track = mVerticalTrack;
} else {
track = mHorizontalTrack;
}
+
if (track != null) {
- if (mChanged) {
+ if (mBoundsChanged) {
track.setBounds(bounds);
}
track.draw(canvas);
}
}
- protected void drawThumb(Canvas canvas, Rect bounds, int offset, int length, boolean vertical) {
- final Rect thumbRect = mTempBounds;
- final boolean changed = mRangeChanged || mChanged;
- if (changed) {
- if (vertical) {
- thumbRect.set(bounds.left, bounds.top + offset,
- bounds.right, bounds.top + offset + length);
- } else {
- thumbRect.set(bounds.left + offset, bounds.top,
- bounds.left + offset + length, bounds.bottom);
- }
- }
-
+ private void drawThumb(Canvas canvas, Rect bounds, int offset, int length, boolean vertical) {
+ final boolean changed = mRangeChanged || mBoundsChanged;
if (vertical) {
if (mVerticalThumb != null) {
final Drawable thumb = mVerticalThumb;
- if (changed) thumb.setBounds(thumbRect);
+ if (changed) {
+ thumb.setBounds(bounds.left, bounds.top + offset,
+ bounds.right, bounds.top + offset + length);
+ }
+
thumb.draw(canvas);
}
} else {
if (mHorizontalThumb != null) {
final Drawable thumb = mHorizontalThumb;
- if (changed) thumb.setBounds(thumbRect);
+ if (changed) {
+ thumb.setBounds(bounds.left + offset, bounds.top,
+ bounds.left + offset + length, bounds.bottom);
+ }
+
thumb.draw(canvas);
}
}
}
public void setVerticalThumbDrawable(Drawable thumb) {
- if (thumb != null) {
- if (mMutated) {
- thumb.mutate();
- }
- thumb.setState(STATE_ENABLED);
- mVerticalThumb = thumb;
+ if (mVerticalThumb != null) {
+ mVerticalThumb.setCallback(null);
}
+
+ propagateCurrentState(thumb);
+ mVerticalThumb = thumb;
}
public void setVerticalTrackDrawable(Drawable track) {
- if (track != null) {
- if (mMutated) {
- track.mutate();
- }
- track.setState(STATE_ENABLED);
+ if (mVerticalTrack != null) {
+ mVerticalTrack.setCallback(null);
}
+
+ propagateCurrentState(track);
mVerticalTrack = track;
}
public void setHorizontalThumbDrawable(Drawable thumb) {
- if (thumb != null) {
- if (mMutated) {
- thumb.mutate();
- }
- thumb.setState(STATE_ENABLED);
- mHorizontalThumb = thumb;
+ if (mHorizontalThumb != null) {
+ mHorizontalThumb.setCallback(null);
}
+
+ propagateCurrentState(thumb);
+ mHorizontalThumb = thumb;
}
public void setHorizontalTrackDrawable(Drawable track) {
- if (track != null) {
+ if (mHorizontalTrack != null) {
+ mHorizontalTrack.setCallback(null);
+ }
+
+ propagateCurrentState(track);
+ mHorizontalTrack = track;
+ }
+
+ private void propagateCurrentState(Drawable d) {
+ if (d != null) {
if (mMutated) {
- track.mutate();
+ d.mutate();
+ }
+
+ d.setState(getState());
+ d.setCallback(this);
+
+ if (mHasSetAlpha) {
+ d.setAlpha(mAlpha);
+ }
+
+ if (mHasSetColorFilter) {
+ d.setColorFilter(mColorFilter);
}
- track.setState(STATE_ENABLED);
}
- mHorizontalTrack = track;
}
public int getSize(boolean vertical) {
@@ -262,6 +316,9 @@ public class ScrollBarDrawable extends Drawable {
@Override
public void setAlpha(int alpha) {
+ mAlpha = alpha;
+ mHasSetAlpha = true;
+
if (mVerticalTrack != null) {
mVerticalTrack.setAlpha(alpha);
}
@@ -278,12 +335,14 @@ public class ScrollBarDrawable extends Drawable {
@Override
public int getAlpha() {
- // All elements should have same alpha, just return one of them
- return mVerticalThumb.getAlpha();
+ return mAlpha;
}
@Override
public void setColorFilter(ColorFilter cf) {
+ mColorFilter = cf;
+ mHasSetColorFilter = true;
+
if (mVerticalTrack != null) {
mVerticalTrack.setColorFilter(cf);
}
@@ -299,11 +358,31 @@ public class ScrollBarDrawable extends Drawable {
}
@Override
+ public ColorFilter getColorFilter() {
+ return mColorFilter;
+ }
+
+ @Override
public int getOpacity() {
return PixelFormat.TRANSLUCENT;
}
@Override
+ public void invalidateDrawable(Drawable who) {
+ invalidateSelf();
+ }
+
+ @Override
+ public void scheduleDrawable(Drawable who, Runnable what, long when) {
+ scheduleSelf(what, when);
+ }
+
+ @Override
+ public void unscheduleDrawable(Drawable who, Runnable what) {
+ unscheduleSelf(what);
+ }
+
+ @Override
public String toString() {
return "ScrollBarDrawable: range=" + mRange + " offset=" + mOffset +
" extent=" + mExtent + (mVertical ? " V" : " H");
diff --git a/core/java/android/widget/ScrollView.java b/core/java/android/widget/ScrollView.java
index a90b392..b95c27d 100644
--- a/core/java/android/widget/ScrollView.java
+++ b/core/java/android/widget/ScrollView.java
@@ -809,9 +809,10 @@ public class ScrollView extends FrameLayout {
awakenScrollBars();
}
+ /** @hide */
@Override
- public boolean performAccessibilityAction(int action, Bundle arguments) {
- if (super.performAccessibilityAction(action, arguments)) {
+ public boolean performAccessibilityActionInternal(int action, Bundle arguments) {
+ if (super.performAccessibilityActionInternal(action, arguments)) {
return true;
}
if (!isEnabled()) {
@@ -839,9 +840,14 @@ public class ScrollView extends FrameLayout {
}
@Override
- public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(info);
- info.setClassName(ScrollView.class.getName());
+ public CharSequence getAccessibilityClassName() {
+ return ScrollView.class.getName();
+ }
+
+ /** @hide */
+ @Override
+ public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
+ super.onInitializeAccessibilityNodeInfoInternal(info);
if (isEnabled()) {
final int scrollRange = getScrollRange();
if (scrollRange > 0) {
@@ -856,10 +862,10 @@ public class ScrollView extends FrameLayout {
}
}
+ /** @hide */
@Override
- public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
- super.onInitializeAccessibilityEvent(event);
- event.setClassName(ScrollView.class.getName());
+ public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) {
+ super.onInitializeAccessibilityEventInternal(event);
final boolean scrollable = getScrollRange() > 0;
event.setScrollable(scrollable);
event.setScrollX(mScrollX);
diff --git a/core/java/android/widget/SearchView.java b/core/java/android/widget/SearchView.java
index 4ee6418..8846421 100644
--- a/core/java/android/widget/SearchView.java
+++ b/core/java/android/widget/SearchView.java
@@ -1326,15 +1326,8 @@ public class SearchView extends LinearLayout implements CollapsibleActionView {
}
@Override
- public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
- super.onInitializeAccessibilityEvent(event);
- event.setClassName(SearchView.class.getName());
- }
-
- @Override
- public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(info);
- info.setClassName(SearchView.class.getName());
+ public CharSequence getAccessibilityClassName() {
+ return SearchView.class.getName();
}
private void adjustDropDownSizeAndPosition() {
diff --git a/core/java/android/widget/SeekBar.java b/core/java/android/widget/SeekBar.java
index dc7c04c..fc070e7 100644
--- a/core/java/android/widget/SeekBar.java
+++ b/core/java/android/widget/SeekBar.java
@@ -26,7 +26,7 @@ import android.view.accessibility.AccessibilityNodeInfo;
/**
* A SeekBar is an extension of ProgressBar that adds a draggable thumb. The user can touch
* the thumb and drag left or right to set the current progress level or use the arrow keys.
- * Placing focusable widgets to the left or right of a SeekBar is discouraged.
+ * Placing focusable widgets to the left or right of a SeekBar is discouraged.
* <p>
* Clients of the SeekBar can attach a {@link SeekBar.OnSeekBarChangeListener} to
* be notified of the user's actions.
@@ -42,39 +42,39 @@ public class SeekBar extends AbsSeekBar {
* programmatically.
*/
public interface OnSeekBarChangeListener {
-
+
/**
* Notification that the progress level has changed. Clients can use the fromUser parameter
* to distinguish user-initiated changes from those that occurred programmatically.
- *
+ *
* @param seekBar The SeekBar whose progress has changed
* @param progress The current progress level. This will be in the range 0..max where max
* was set by {@link ProgressBar#setMax(int)}. (The default value for max is 100.)
* @param fromUser True if the progress change was initiated by the user.
*/
void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser);
-
+
/**
* Notification that the user has started a touch gesture. Clients may want to use this
- * to disable advancing the seekbar.
+ * to disable advancing the seekbar.
* @param seekBar The SeekBar in which the touch gesture began
*/
void onStartTrackingTouch(SeekBar seekBar);
-
+
/**
* Notification that the user has finished a touch gesture. Clients may want to use this
- * to re-enable advancing the seekbar.
+ * to re-enable advancing the seekbar.
* @param seekBar The SeekBar in which the touch gesture began
*/
void onStopTrackingTouch(SeekBar seekBar);
}
private OnSeekBarChangeListener mOnSeekBarChangeListener;
-
+
public SeekBar(Context context) {
this(context, null);
}
-
+
public SeekBar(Context context, AttributeSet attrs) {
this(context, attrs, com.android.internal.R.attr.seekBarStyle);
}
@@ -88,26 +88,26 @@ public class SeekBar extends AbsSeekBar {
}
@Override
- void onProgressRefresh(float scale, boolean fromUser) {
- super.onProgressRefresh(scale, fromUser);
+ void onProgressRefresh(float scale, boolean fromUser, int progress) {
+ super.onProgressRefresh(scale, fromUser, progress);
if (mOnSeekBarChangeListener != null) {
- mOnSeekBarChangeListener.onProgressChanged(this, getProgress(), fromUser);
+ mOnSeekBarChangeListener.onProgressChanged(this, progress, fromUser);
}
}
/**
* Sets a listener to receive notifications of changes to the SeekBar's progress level. Also
* provides notifications of when the user starts and stops a touch gesture within the SeekBar.
- *
+ *
* @param l The seek bar notification listener
- *
+ *
* @see SeekBar.OnSeekBarChangeListener
*/
public void setOnSeekBarChangeListener(OnSeekBarChangeListener l) {
mOnSeekBarChangeListener = l;
}
-
+
@Override
void onStartTrackingTouch() {
super.onStartTrackingTouch();
@@ -115,7 +115,7 @@ public class SeekBar extends AbsSeekBar {
mOnSeekBarChangeListener.onStartTrackingTouch(this);
}
}
-
+
@Override
void onStopTrackingTouch() {
super.onStopTrackingTouch();
@@ -125,14 +125,7 @@ public class SeekBar extends AbsSeekBar {
}
@Override
- public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
- super.onInitializeAccessibilityEvent(event);
- event.setClassName(SeekBar.class.getName());
- }
-
- @Override
- public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(info);
- info.setClassName(SeekBar.class.getName());
+ public CharSequence getAccessibilityClassName() {
+ return SeekBar.class.getName();
}
}
diff --git a/core/java/android/widget/SimpleAdapter.java b/core/java/android/widget/SimpleAdapter.java
index 98bcfff..a656712 100644
--- a/core/java/android/widget/SimpleAdapter.java
+++ b/core/java/android/widget/SimpleAdapter.java
@@ -17,6 +17,8 @@
package android.widget;
import android.content.Context;
+import android.content.res.Resources;
+import android.view.ContextThemeWrapper;
import android.view.View;
import android.view.ViewGroup;
import android.view.LayoutInflater;
@@ -40,14 +42,14 @@ import java.util.Map;
* If the returned value is false, the following views are then tried in order:
* <ul>
* <li> A view that implements Checkable (e.g. CheckBox). The expected bind value is a boolean.
- * <li> TextView. The expected bind value is a string and {@link #setViewText(TextView, String)}
+ * <li> TextView. The expected bind value is a string and {@link #setViewText(TextView, String)}
* is invoked.
- * <li> ImageView. The expected bind value is a resource id or a string and
- * {@link #setViewImage(ImageView, int)} or {@link #setViewImage(ImageView, String)} is invoked.
+ * <li> ImageView. The expected bind value is a resource id or a string and
+ * {@link #setViewImage(ImageView, int)} or {@link #setViewImage(ImageView, String)} is invoked.
* </ul>
* If no appropriate binding can be found, an {@link IllegalStateException} is thrown.
*/
-public class SimpleAdapter extends BaseAdapter implements Filterable {
+public class SimpleAdapter extends BaseAdapter implements Filterable, Spinner.ThemedSpinnerAdapter {
private int[] mTo;
private String[] mFrom;
private ViewBinder mViewBinder;
@@ -58,12 +60,15 @@ public class SimpleAdapter extends BaseAdapter implements Filterable {
private int mDropDownResource;
private LayoutInflater mInflater;
+ /** Layout inflater used for {@link #getDropDownView(int, View, ViewGroup)}. */
+ private LayoutInflater mDropDownInflater;
+
private SimpleFilter mFilter;
private ArrayList<Map<String, ?>> mUnfilteredData;
/**
* Constructor
- *
+ *
* @param context The context where the View associated with this SimpleAdapter is running
* @param data A List of Maps. Each entry in the List corresponds to one row in the list. The
* Maps contain the data for each row, and should include all the entries specified in
@@ -85,7 +90,6 @@ public class SimpleAdapter extends BaseAdapter implements Filterable {
mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}
-
/**
* @see android.widget.Adapter#getCount()
*/
@@ -111,14 +115,14 @@ public class SimpleAdapter extends BaseAdapter implements Filterable {
* @see android.widget.Adapter#getView(int, View, ViewGroup)
*/
public View getView(int position, View convertView, ViewGroup parent) {
- return createViewFromResource(position, convertView, parent, mResource);
+ return createViewFromResource(mInflater, position, convertView, parent, mResource);
}
- private View createViewFromResource(int position, View convertView,
+ private View createViewFromResource(LayoutInflater inflater, int position, View convertView,
ViewGroup parent, int resource) {
View v;
if (convertView == null) {
- v = mInflater.inflate(resource, parent, false);
+ v = inflater.inflate(resource, parent, false);
} else {
v = convertView;
}
@@ -135,12 +139,41 @@ public class SimpleAdapter extends BaseAdapter implements Filterable {
* @see #getDropDownView(int, android.view.View, android.view.ViewGroup)
*/
public void setDropDownViewResource(int resource) {
- this.mDropDownResource = resource;
+ mDropDownResource = resource;
+ }
+
+ /**
+ * Sets the {@link android.content.res.Resources.Theme} against which drop-down views are
+ * inflated.
+ * <p>
+ * By default, drop-down views are inflated against the theme of the
+ * {@link Context} passed to the adapter's constructor.
+ *
+ * @param theme the theme against which to inflate drop-down views or
+ * {@code null} to use the theme from the adapter's context
+ * @see #getDropDownView(int, View, ViewGroup)
+ */
+ @Override
+ public void setDropDownViewTheme(Resources.Theme theme) {
+ if (theme == null) {
+ mDropDownInflater = null;
+ } else if (theme == mInflater.getContext().getTheme()) {
+ mDropDownInflater = mInflater;
+ } else {
+ final Context context = new ContextThemeWrapper(mInflater.getContext(), theme);
+ mDropDownInflater = LayoutInflater.from(context);
+ }
+ }
+
+ @Override
+ public Resources.Theme getDropDownViewTheme() {
+ return mDropDownInflater == null ? null : mDropDownInflater.getContext().getTheme();
}
@Override
public View getDropDownView(int position, View convertView, ViewGroup parent) {
- return createViewFromResource(position, convertView, parent, mDropDownResource);
+ return createViewFromResource(
+ mDropDownInflater, position, convertView, parent, mDropDownResource);
}
private void bindView(int position, View view) {
diff --git a/core/java/android/widget/SimpleMonthAdapter.java b/core/java/android/widget/SimpleMonthAdapter.java
index 24ebb2c..c807d56 100644
--- a/core/java/android/widget/SimpleMonthAdapter.java
+++ b/core/java/android/widget/SimpleMonthAdapter.java
@@ -39,6 +39,7 @@ class SimpleMonthAdapter extends BaseAdapter {
private Calendar mSelectedDay = Calendar.getInstance();
private ColorStateList mCalendarTextColors = ColorStateList.valueOf(Color.BLACK);
+ private ColorStateList mCalendarDayBackgroundColor = ColorStateList.valueOf(Color.MAGENTA);
private OnDaySelectedListener mOnDaySelectedListener;
private int mFirstDayOfWeek;
@@ -88,6 +89,10 @@ class SimpleMonthAdapter extends BaseAdapter {
mCalendarTextColors = colors;
}
+ void setCalendarDayBackgroundColor(ColorStateList dayBackgroundColor) {
+ mCalendarDayBackgroundColor = dayBackgroundColor;
+ }
+
/**
* Sets the text color, size, style, hint color, and highlight color from
* the specified TextAppearance resource. This is mostly copied from
@@ -144,9 +149,11 @@ class SimpleMonthAdapter extends BaseAdapter {
v.setClickable(true);
v.setOnDayClickListener(mOnDayClickListener);
- if (mCalendarTextColors != null) {
- v.setTextColor(mCalendarTextColors);
- }
+ v.setMonthTextColor(mCalendarTextColors);
+ v.setDayOfWeekTextColor(mCalendarTextColors);
+ v.setDayTextColor(mCalendarTextColors);
+
+ v.setDayBackgroundColor(mCalendarDayBackgroundColor);
}
final int minMonth = mMinDate.get(Calendar.MONTH);
diff --git a/core/java/android/widget/SimpleMonthView.java b/core/java/android/widget/SimpleMonthView.java
index d2a37ac..58ad515 100644
--- a/core/java/android/widget/SimpleMonthView.java
+++ b/core/java/android/widget/SimpleMonthView.java
@@ -27,12 +27,13 @@ import android.graphics.Paint.Style;
import android.graphics.Rect;
import android.graphics.Typeface;
import android.os.Bundle;
+import android.text.TextPaint;
import android.text.format.DateFormat;
import android.text.format.DateUtils;
import android.text.format.Time;
import android.util.AttributeSet;
import android.util.IntArray;
-import android.util.MathUtils;
+import android.util.StateSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.accessibility.AccessibilityEvent;
@@ -44,7 +45,6 @@ import com.android.internal.widget.ExploreByTouchHelper;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Formatter;
-import java.util.List;
import java.util.Locale;
/**
@@ -52,8 +52,7 @@ import java.util.Locale;
* within the specified month.
*/
class SimpleMonthView extends View {
- private static final int DEFAULT_HEIGHT = 32;
- private static final int MIN_HEIGHT = 10;
+ private static final int MIN_ROW_HEIGHT = 10;
private static final int DEFAULT_SELECTED_DAY = -1;
private static final int DEFAULT_WEEK_START = Calendar.SUNDAY;
@@ -61,18 +60,21 @@ class SimpleMonthView extends View {
private static final int DEFAULT_NUM_ROWS = 6;
private static final int MAX_NUM_ROWS = 6;
- private static final int SELECTED_CIRCLE_ALPHA = 60;
-
- private static final int DAY_SEPARATOR_WIDTH = 1;
-
private final Formatter mFormatter;
private final StringBuilder mStringBuilder;
- private final int mMiniDayNumberTextSize;
- private final int mMonthLabelTextSize;
- private final int mMonthDayLabelTextSize;
- private final int mMonthHeaderSize;
- private final int mDaySelectedCircleSize;
+ private final int mMonthTextSize;
+ private final int mDayOfWeekTextSize;
+ private final int mDayTextSize;
+
+ /** Height of the header containing the month and day of week labels. */
+ private final int mMonthHeaderHeight;
+
+ private final TextPaint mMonthPaint = new TextPaint();
+ private final TextPaint mDayOfWeekPaint = new TextPaint();
+ private final TextPaint mDayPaint = new TextPaint();
+
+ private final Paint mDayBackgroundPaint = new Paint();
/** Single-letter (when available) formatter for the day of week label. */
private SimpleDateFormat mDayFormatter = new SimpleDateFormat("EEEEE", Locale.getDefault());
@@ -81,14 +83,7 @@ class SimpleMonthView extends View {
private int mPadding = 0;
private String mDayOfWeekTypeface;
- private String mMonthTitleTypeface;
-
- private Paint mDayNumberPaint;
- private Paint mDayNumberDisabledPaint;
- private Paint mDayNumberSelectedPaint;
-
- private Paint mMonthTitlePaint;
- private Paint mMonthDayLabelPaint;
+ private String mMonthTypeface;
private int mMonth;
private int mYear;
@@ -97,13 +92,13 @@ class SimpleMonthView extends View {
private int mWidth;
// The height this view should draw at in pixels, set by height param
- private int mRowHeight = DEFAULT_HEIGHT;
+ private final int mRowHeight;
// If this view contains the today
private boolean mHasToday = false;
// Which day is selected [0-6] or -1 if no day is selected
- private int mSelectedDay = -1;
+ private int mActivatedDay = -1;
// Which day is today [0-6] or -1 if no day is today
private int mToday = DEFAULT_SELECTED_DAY;
@@ -142,6 +137,8 @@ class SimpleMonthView extends View {
private int mDisabledTextColor;
private int mSelectedDayColor;
+ private ColorStateList mDayTextColor;
+
public SimpleMonthView(Context context) {
this(context, null);
}
@@ -159,22 +156,21 @@ class SimpleMonthView extends View {
final Resources res = context.getResources();
mDayOfWeekTypeface = res.getString(R.string.day_of_week_label_typeface);
- mMonthTitleTypeface = res.getString(R.string.sans_serif);
+ mMonthTypeface = res.getString(R.string.sans_serif);
mStringBuilder = new StringBuilder(50);
mFormatter = new Formatter(mStringBuilder, Locale.getDefault());
- mMiniDayNumberTextSize = res.getDimensionPixelSize(R.dimen.datepicker_day_number_size);
- mMonthLabelTextSize = res.getDimensionPixelSize(R.dimen.datepicker_month_label_size);
- mMonthDayLabelTextSize = res.getDimensionPixelSize(
+ mDayTextSize = res.getDimensionPixelSize(R.dimen.datepicker_day_number_size);
+ mMonthTextSize = res.getDimensionPixelSize(R.dimen.datepicker_month_label_size);
+ mDayOfWeekTextSize = res.getDimensionPixelSize(
R.dimen.datepicker_month_day_label_text_size);
- mMonthHeaderSize = res.getDimensionPixelOffset(
+ mMonthHeaderHeight = res.getDimensionPixelOffset(
R.dimen.datepicker_month_list_item_header_height);
- mDaySelectedCircleSize = res.getDimensionPixelSize(
- R.dimen.datepicker_day_number_select_circle_radius);
- mRowHeight = (res.getDimensionPixelOffset(R.dimen.datepicker_view_animator_height)
- - mMonthHeaderSize) / MAX_NUM_ROWS;
+ mRowHeight = Math.max(MIN_ROW_HEIGHT,
+ (res.getDimensionPixelOffset(R.dimen.datepicker_view_animator_height)
+ - mMonthHeaderHeight) / MAX_NUM_ROWS);
// Set up accessibility components.
mTouchHelper = new MonthViewTouchHelper(this);
@@ -182,8 +178,32 @@ class SimpleMonthView extends View {
setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
mLockAccessibilityDelegate = true;
- // Sets up any standard paints that will be used
- initView();
+ initPaints();
+ }
+
+ /**
+ * Sets up the text and style properties for painting.
+ */
+ private void initPaints() {
+ mMonthPaint.setAntiAlias(true);
+ mMonthPaint.setTextSize(mMonthTextSize);
+ mMonthPaint.setTypeface(Typeface.create(mMonthTypeface, Typeface.BOLD));
+ mMonthPaint.setTextAlign(Align.CENTER);
+ mMonthPaint.setStyle(Style.FILL);
+
+ mDayOfWeekPaint.setAntiAlias(true);
+ mDayOfWeekPaint.setTextSize(mDayOfWeekTextSize);
+ mDayOfWeekPaint.setTypeface(Typeface.create(mDayOfWeekTypeface, Typeface.BOLD));
+ mDayOfWeekPaint.setTextAlign(Align.CENTER);
+ mDayOfWeekPaint.setStyle(Style.FILL);
+
+ mDayBackgroundPaint.setAntiAlias(true);
+ mDayBackgroundPaint.setStyle(Style.FILL);
+
+ mDayPaint.setAntiAlias(true);
+ mDayPaint.setTextSize(mDayTextSize);
+ mDayPaint.setTextAlign(Align.CENTER);
+ mDayPaint.setStyle(Style.FILL);
}
@Override
@@ -193,22 +213,28 @@ class SimpleMonthView extends View {
mDayFormatter = new SimpleDateFormat("EEEEE", newConfig.locale);
}
- void setTextColor(ColorStateList colors) {
- final Resources res = getContext().getResources();
+ void setMonthTextColor(ColorStateList monthTextColor) {
+ final int enabledColor = monthTextColor.getColorForState(ENABLED_STATE_SET, 0);
+ mMonthPaint.setColor(enabledColor);
+ invalidate();
+ }
- mNormalTextColor = colors.getColorForState(ENABLED_STATE_SET,
- res.getColor(R.color.datepicker_default_normal_text_color_holo_light));
- mMonthTitlePaint.setColor(mNormalTextColor);
- mMonthDayLabelPaint.setColor(mNormalTextColor);
+ void setDayOfWeekTextColor(ColorStateList dayOfWeekTextColor) {
+ final int enabledColor = dayOfWeekTextColor.getColorForState(ENABLED_STATE_SET, 0);
+ mDayOfWeekPaint.setColor(enabledColor);
+ invalidate();
+ }
- mDisabledTextColor = colors.getColorForState(EMPTY_STATE_SET,
- res.getColor(R.color.datepicker_default_disabled_text_color_holo_light));
- mDayNumberDisabledPaint.setColor(mDisabledTextColor);
+ void setDayTextColor(ColorStateList dayTextColor) {
+ mDayTextColor = dayTextColor;
+ invalidate();
+ }
- mSelectedDayColor = colors.getColorForState(ENABLED_SELECTED_STATE_SET,
- res.getColor(R.color.holo_blue_light));
- mDayNumberSelectedPaint.setColor(mSelectedDayColor);
- mDayNumberSelectedPaint.setAlpha(SELECTED_CIRCLE_ALPHA);
+ void setDayBackgroundColor(ColorStateList dayBackgroundColor) {
+ final int activatedColor = dayBackgroundColor.getColorForState(
+ StateSet.get(StateSet.VIEW_STATE_ENABLED | StateSet.VIEW_STATE_ACTIVATED), 0);
+ mDayBackgroundPaint.setColor(activatedColor);
+ invalidate();
}
@Override
@@ -246,52 +272,6 @@ class SimpleMonthView extends View {
return true;
}
- /**
- * Sets up the text and style properties for painting.
- */
- private void initView() {
- mMonthTitlePaint = new Paint();
- mMonthTitlePaint.setAntiAlias(true);
- mMonthTitlePaint.setColor(mNormalTextColor);
- mMonthTitlePaint.setTextSize(mMonthLabelTextSize);
- mMonthTitlePaint.setTypeface(Typeface.create(mMonthTitleTypeface, Typeface.BOLD));
- mMonthTitlePaint.setTextAlign(Align.CENTER);
- mMonthTitlePaint.setStyle(Style.FILL);
- mMonthTitlePaint.setFakeBoldText(true);
-
- mMonthDayLabelPaint = new Paint();
- mMonthDayLabelPaint.setAntiAlias(true);
- mMonthDayLabelPaint.setColor(mNormalTextColor);
- mMonthDayLabelPaint.setTextSize(mMonthDayLabelTextSize);
- mMonthDayLabelPaint.setTypeface(Typeface.create(mDayOfWeekTypeface, Typeface.NORMAL));
- mMonthDayLabelPaint.setTextAlign(Align.CENTER);
- mMonthDayLabelPaint.setStyle(Style.FILL);
- mMonthDayLabelPaint.setFakeBoldText(true);
-
- mDayNumberSelectedPaint = new Paint();
- mDayNumberSelectedPaint.setAntiAlias(true);
- mDayNumberSelectedPaint.setColor(mSelectedDayColor);
- mDayNumberSelectedPaint.setAlpha(SELECTED_CIRCLE_ALPHA);
- mDayNumberSelectedPaint.setTextAlign(Align.CENTER);
- mDayNumberSelectedPaint.setStyle(Style.FILL);
- mDayNumberSelectedPaint.setFakeBoldText(true);
-
- mDayNumberPaint = new Paint();
- mDayNumberPaint.setAntiAlias(true);
- mDayNumberPaint.setTextSize(mMiniDayNumberTextSize);
- mDayNumberPaint.setTextAlign(Align.CENTER);
- mDayNumberPaint.setStyle(Style.FILL);
- mDayNumberPaint.setFakeBoldText(false);
-
- mDayNumberDisabledPaint = new Paint();
- mDayNumberDisabledPaint.setAntiAlias(true);
- mDayNumberDisabledPaint.setColor(mDisabledTextColor);
- mDayNumberDisabledPaint.setTextSize(mMiniDayNumberTextSize);
- mDayNumberDisabledPaint.setTextAlign(Align.CENTER);
- mDayNumberDisabledPaint.setStyle(Style.FILL);
- mDayNumberDisabledPaint.setFakeBoldText(false);
- }
-
@Override
protected void onDraw(Canvas canvas) {
drawMonthTitle(canvas);
@@ -323,11 +303,7 @@ class SimpleMonthView extends View {
*/
void setMonthParams(int selectedDay, int month, int year, int weekStart, int enabledDayStart,
int enabledDayEnd) {
- if (mRowHeight < MIN_HEIGHT) {
- mRowHeight = MIN_HEIGHT;
- }
-
- mSelectedDay = selectedDay;
+ mActivatedDay = selectedDay;
if (isValidMonth(month)) {
mMonth = month;
@@ -415,7 +391,7 @@ class SimpleMonthView extends View {
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec), mRowHeight * mNumRows
- + mMonthHeaderSize);
+ + mMonthHeaderHeight);
}
@Override
@@ -437,21 +413,28 @@ class SimpleMonthView extends View {
private void drawMonthTitle(Canvas canvas) {
final float x = (mWidth + 2 * mPadding) / 2f;
- final float y = (mMonthHeaderSize - mMonthDayLabelTextSize) / 2f;
- canvas.drawText(getMonthAndYearString(), x, y, mMonthTitlePaint);
+
+ // Centered on the upper half of the month header.
+ final float lineHeight = mMonthPaint.ascent() + mMonthPaint.descent();
+ final float y = mMonthHeaderHeight * 0.25f - lineHeight / 2f;
+
+ canvas.drawText(getMonthAndYearString(), x, y, mMonthPaint);
}
private void drawWeekDayLabels(Canvas canvas) {
- final int y = mMonthHeaderSize - (mMonthDayLabelTextSize / 2);
- final int dayWidthHalf = (mWidth - mPadding * 2) / (mNumDays * 2);
+ final float dayWidthHalf = (mWidth - mPadding * 2) / (mNumDays * 2);
+
+ // Centered on the lower half of the month header.
+ final float lineHeight = mDayOfWeekPaint.ascent() + mDayOfWeekPaint.descent();
+ final float y = mMonthHeaderHeight * 0.75f - lineHeight / 2f;
for (int i = 0; i < mNumDays; i++) {
final int calendarDay = (i + mWeekStart) % mNumDays;
mDayLabelCalendar.set(Calendar.DAY_OF_WEEK, calendarDay);
final String dayLabel = mDayFormatter.format(mDayLabelCalendar.getTime());
- final int x = (2 * i + 1) * dayWidthHalf + mPadding;
- canvas.drawText(dayLabel, x, y, mMonthDayLabelPaint);
+ final float x = (2 * i + 1) * dayWidthHalf + mPadding;
+ canvas.drawText(dayLabel, x, y, mDayOfWeekPaint);
}
}
@@ -459,26 +442,40 @@ class SimpleMonthView extends View {
* Draws the month days.
*/
private void drawDays(Canvas canvas) {
- int y = (((mRowHeight + mMiniDayNumberTextSize) / 2) - DAY_SEPARATOR_WIDTH)
- + mMonthHeaderSize;
- int dayWidthHalf = (mWidth - mPadding * 2) / (mNumDays * 2);
- int j = findDayOffset();
- for (int day = 1; day <= mNumCells; day++) {
- int x = (2 * j + 1) * dayWidthHalf + mPadding;
- if (mSelectedDay == day) {
- canvas.drawCircle(x, y - (mMiniDayNumberTextSize / 3), mDaySelectedCircleSize,
- mDayNumberSelectedPaint);
+ final int dayWidthHalf = (mWidth - mPadding * 2) / (mNumDays * 2);
+
+ // Centered within the row.
+ final float lineHeight = mDayOfWeekPaint.ascent() + mDayOfWeekPaint.descent();
+ float y = mMonthHeaderHeight + (mRowHeight - lineHeight) / 2f;
+
+ for (int day = 1, j = findDayOffset(); day <= mNumCells; day++) {
+ final int x = (2 * j + 1) * dayWidthHalf + mPadding;
+ int stateMask = 0;
+
+ if (day >= mEnabledDayStart && day <= mEnabledDayEnd) {
+ stateMask |= StateSet.VIEW_STATE_ENABLED;
}
- if (mHasToday && mToday == day) {
- mDayNumberPaint.setColor(mSelectedDayColor);
- } else {
- mDayNumberPaint.setColor(mNormalTextColor);
+ if (mActivatedDay == day) {
+ stateMask |= StateSet.VIEW_STATE_ACTIVATED;
+
+ // Adjust the circle to be centered the row.
+ final float rowCenterY = y + lineHeight / 2;
+ canvas.drawCircle(x, rowCenterY, mRowHeight / 2,
+ mDayBackgroundPaint);
}
- final Paint paint = (day < mEnabledDayStart || day > mEnabledDayEnd) ?
- mDayNumberDisabledPaint : mDayNumberPaint;
- canvas.drawText(String.format("%d", day), x, y, paint);
+
+ final int[] stateSet = StateSet.get(stateMask);
+ final int dayTextColor = mDayTextColor.getColorForState(stateSet, 0);
+ mDayPaint.setColor(dayTextColor);
+
+ final boolean isDayToday = mHasToday && mToday == day;
+ mDayPaint.setFakeBoldText(isDayToday);
+
+ canvas.drawText(String.format("%d", day), x, y, mDayPaint);
+
j++;
+
if (j == mNumDays) {
j = 0;
y += mRowHeight;
@@ -504,7 +501,7 @@ class SimpleMonthView extends View {
return -1;
}
// Selection is (x - start) / (pixels/day) == (x -s) * day / pixels
- int row = (int) (y - mMonthHeaderSize) / mRowHeight;
+ int row = (int) (y - mMonthHeaderHeight) / mRowHeight;
int column = (int) ((x - dayStart) * mNumDays / (mWidth - dayStart - mPadding));
int day = column - findDayOffset() + 1;
@@ -628,7 +625,7 @@ class SimpleMonthView extends View {
node.setBoundsInParent(mTempRect);
node.addAction(AccessibilityNodeInfo.ACTION_CLICK);
- if (virtualViewId == mSelectedDay) {
+ if (virtualViewId == mActivatedDay) {
node.setSelected(true);
}
@@ -654,7 +651,7 @@ class SimpleMonthView extends View {
*/
private void getItemBounds(int day, Rect rect) {
final int offsetX = mPadding;
- final int offsetY = mMonthHeaderSize;
+ final int offsetY = mMonthHeaderHeight;
final int cellHeight = mRowHeight;
final int cellWidth = ((mWidth - (2 * mPadding)) / mNumDays);
final int index = ((day - 1) + findDayOffset());
@@ -679,7 +676,7 @@ class SimpleMonthView extends View {
final CharSequence date = DateFormat.format(DATE_FORMAT,
mTempCalendar.getTimeInMillis());
- if (day == mSelectedDay) {
+ if (day == mActivatedDay) {
return getContext().getString(R.string.item_is_selected, date);
}
diff --git a/core/java/android/widget/SlidingDrawer.java b/core/java/android/widget/SlidingDrawer.java
index ec06c02..272f4b0 100644
--- a/core/java/android/widget/SlidingDrawer.java
+++ b/core/java/android/widget/SlidingDrawer.java
@@ -838,15 +838,8 @@ public class SlidingDrawer extends ViewGroup {
}
@Override
- public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
- super.onInitializeAccessibilityEvent(event);
- event.setClassName(SlidingDrawer.class.getName());
- }
-
- @Override
- public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(info);
- info.setClassName(SlidingDrawer.class.getName());
+ public CharSequence getAccessibilityClassName() {
+ return SlidingDrawer.class.getName();
}
private void closeDrawer() {
diff --git a/core/java/android/widget/Spinner.java b/core/java/android/widget/Spinner.java
index 0d76239..6ee2b4c 100644
--- a/core/java/android/widget/Spinner.java
+++ b/core/java/android/widget/Spinner.java
@@ -16,11 +16,15 @@
package android.widget;
+import com.android.internal.R;
+
+import android.annotation.Nullable;
import android.annotation.Widget;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.DialogInterface.OnClickListener;
+import android.content.res.Resources;
import android.content.res.TypedArray;
import android.database.DataSetObserver;
import android.graphics.Rect;
@@ -30,6 +34,7 @@ import android.os.Parcel;
import android.os.Parcelable;
import android.util.AttributeSet;
import android.util.Log;
+import android.view.ContextThemeWrapper;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.View;
@@ -74,17 +79,22 @@ public class Spinner extends AbsSpinner implements OnClickListener {
* Use a dropdown anchored to the Spinner for selecting spinner options.
*/
public static final int MODE_DROPDOWN = 1;
-
+
/**
* Use the theme-supplied value to select the dropdown mode.
*/
private static final int MODE_THEME = -1;
+ /** Context used to inflate the popup window or dialog. */
+ private Context mPopupContext;
+
/** Forwarding listener used to implement drag-to-open. */
private ForwardingListener mForwardingListener;
+ /** Temporary holder for setAdapter() calls from the super constructor. */
+ private SpinnerAdapter mTempAdapter;
+
private SpinnerPopup mPopup;
- private DropDownAdapter mTempAdapter;
int mDropDownWidth;
private int mGravity;
@@ -184,69 +194,120 @@ public class Spinner extends AbsSpinner implements OnClickListener {
* @see #MODE_DIALOG
* @see #MODE_DROPDOWN
*/
- public Spinner(
- Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes, int mode) {
+ public Spinner(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes,
+ int mode) {
+ this(context, attrs, defStyleAttr, defStyleRes, mode, null);
+ }
+
+ /**
+ * Constructs a new spinner with the given context's theme, the supplied
+ * attribute set, default styles, popup mode (one of {@link #MODE_DIALOG}
+ * or {@link #MODE_DROPDOWN}), and the context against which the popup
+ * should be inflated.
+ *
+ * @param context The context against which the view is inflated, which
+ * provides access to the current theme, resources, etc.
+ * @param attrs The attributes of the XML tag that is inflating the view.
+ * @param defStyleAttr An attribute in the current theme that contains a
+ * reference to a style resource that supplies default
+ * values for the view. Can be 0 to not look for
+ * defaults.
+ * @param defStyleRes A resource identifier of a style resource that
+ * supplies default values for the view, used only if
+ * defStyleAttr is 0 or can not be found in the theme.
+ * Can be 0 to not look for defaults.
+ * @param mode Constant describing how the user will select choices from
+ * the spinner.
+ * @param popupContext The context against which the dialog or dropdown
+ * popup will be inflated. Can be null to use the view
+ * context. If set, this will override any value
+ * specified by
+ * {@link android.R.styleable#Spinner_popupTheme}.
+ *
+ * @see #MODE_DIALOG
+ * @see #MODE_DROPDOWN
+ */
+ public Spinner(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes, int mode,
+ Context popupContext) {
super(context, attrs, defStyleAttr, defStyleRes);
final TypedArray a = context.obtainStyledAttributes(
- attrs, com.android.internal.R.styleable.Spinner, defStyleAttr, defStyleRes);
+ attrs, R.styleable.Spinner, defStyleAttr, defStyleRes);
- if (mode == MODE_THEME) {
- mode = a.getInt(com.android.internal.R.styleable.Spinner_spinnerMode, MODE_DIALOG);
- }
-
- switch (mode) {
- case MODE_DIALOG: {
- mPopup = new DialogPopup();
- break;
+ if (popupContext != null) {
+ mPopupContext = popupContext;
+ } else {
+ final int popupThemeResId = a.getResourceId(R.styleable.Spinner_popupTheme, 0);
+ if (popupThemeResId != 0) {
+ mPopupContext = new ContextThemeWrapper(context, popupThemeResId);
+ } else {
+ mPopupContext = context;
+ }
}
- case MODE_DROPDOWN: {
- final DropdownPopup popup = new DropdownPopup(context, attrs, defStyleAttr, defStyleRes);
+ if (mode == MODE_THEME) {
+ mode = a.getInt(R.styleable.Spinner_spinnerMode, MODE_DIALOG);
+ }
- mDropDownWidth = a.getLayoutDimension(
- com.android.internal.R.styleable.Spinner_dropDownWidth,
- ViewGroup.LayoutParams.WRAP_CONTENT);
- popup.setBackgroundDrawable(a.getDrawable(
- com.android.internal.R.styleable.Spinner_popupBackground));
+ switch (mode) {
+ case MODE_DIALOG: {
+ mPopup = new DialogPopup();
+ mPopup.setPromptText(a.getString(R.styleable.Spinner_prompt));
+ break;
+ }
- mPopup = popup;
- mForwardingListener = new ForwardingListener(this) {
- @Override
- public ListPopupWindow getPopup() {
- return popup;
- }
+ case MODE_DROPDOWN: {
+ final DropdownPopup popup = new DropdownPopup(
+ mPopupContext, attrs, defStyleAttr, defStyleRes);
+ final TypedArray pa = mPopupContext.obtainStyledAttributes(
+ attrs, R.styleable.Spinner, defStyleAttr, defStyleRes);
+ mDropDownWidth = pa.getLayoutDimension(R.styleable.Spinner_dropDownWidth,
+ ViewGroup.LayoutParams.WRAP_CONTENT);
+ popup.setBackgroundDrawable(pa.getDrawable(R.styleable.Spinner_popupBackground));
+ popup.setPromptText(a.getString(R.styleable.Spinner_prompt));
+ pa.recycle();
+
+ mPopup = popup;
+ mForwardingListener = new ForwardingListener(this) {
+ @Override
+ public ListPopupWindow getPopup() {
+ return popup;
+ }
- @Override
- public boolean onForwardingStarted() {
- if (!mPopup.isShowing()) {
- mPopup.show(getTextDirection(), getTextAlignment());
+ @Override
+ public boolean onForwardingStarted() {
+ if (!mPopup.isShowing()) {
+ mPopup.show(getTextDirection(), getTextAlignment());
+ }
+ return true;
}
- return true;
- }
- };
- break;
- }
+ };
+ break;
+ }
}
- mGravity = a.getInt(com.android.internal.R.styleable.Spinner_gravity, Gravity.CENTER);
-
- mPopup.setPromptText(a.getString(com.android.internal.R.styleable.Spinner_prompt));
-
+ mGravity = a.getInt(R.styleable.Spinner_gravity, Gravity.CENTER);
mDisableChildrenWhenDisabled = a.getBoolean(
- com.android.internal.R.styleable.Spinner_disableChildrenWhenDisabled, false);
+ R.styleable.Spinner_disableChildrenWhenDisabled, false);
a.recycle();
// Base constructor can call setAdapter before we initialize mPopup.
// Finish setting things up if this happened.
if (mTempAdapter != null) {
- mPopup.setAdapter(mTempAdapter);
+ setAdapter(mTempAdapter);
mTempAdapter = null;
}
}
/**
+ * @return the context used to inflate the Spinner's popup or dialog window
+ */
+ public Context getPopupContext() {
+ return mPopupContext;
+ }
+
+ /**
* Set the background drawable for the spinner's popup window of choices.
* Only valid in {@link #MODE_DROPDOWN}; this method is a no-op in other modes.
*
@@ -259,7 +320,7 @@ public class Spinner extends AbsSpinner implements OnClickListener {
Log.e(TAG, "setPopupBackgroundDrawable: incompatible spinner mode; ignoring...");
return;
}
- ((DropdownPopup) mPopup).setBackgroundDrawable(background);
+ mPopup.setBackgroundDrawable(background);
}
/**
@@ -271,7 +332,7 @@ public class Spinner extends AbsSpinner implements OnClickListener {
* @attr ref android.R.styleable#Spinner_popupBackground
*/
public void setPopupBackgroundResource(int resId) {
- setPopupBackgroundDrawable(getContext().getDrawable(resId));
+ setPopupBackgroundDrawable(getPopupContext().getDrawable(resId));
}
/**
@@ -410,9 +471,17 @@ public class Spinner extends AbsSpinner implements OnClickListener {
}
/**
- * Sets the Adapter used to provide the data which backs this Spinner.
+ * Sets the {@link SpinnerAdapter} used to provide the data which backs
+ * this Spinner.
+ * <p>
+ * If this Spinner has a popup theme set in XML via the
+ * {@link android.R.styleable#Spinner_popupTheme popupTheme} attribute, the
+ * adapter should inflate drop-down views using the same theme. The easiest
+ * way to achieve this is by using {@link #getPopupContext()} to obtain a
+ * layout inflater for use in
+ * {@link SpinnerAdapter#getDropDownView(int, View, ViewGroup)}.
* <p>
- * Note that Spinner overrides {@link Adapter#getViewTypeCount()} on the
+ * Spinner overrides {@link Adapter#getViewTypeCount()} on the
* Adapter associated with this view. Calling
* {@link Adapter#getItemViewType(int) getItemViewType(int)} on the object
* returned from {@link #getAdapter()} will always return 0. Calling
@@ -429,6 +498,13 @@ public class Spinner extends AbsSpinner implements OnClickListener {
*/
@Override
public void setAdapter(SpinnerAdapter adapter) {
+ // The super constructor may call setAdapter before we're prepared.
+ // Postpone doing anything until we've finished construction.
+ if (mPopup == null) {
+ mTempAdapter = adapter;
+ return;
+ }
+
super.setAdapter(adapter);
mRecycler.clear();
@@ -439,11 +515,8 @@ public class Spinner extends AbsSpinner implements OnClickListener {
throw new IllegalArgumentException("Spinner adapter view type count must be 1");
}
- if (mPopup != null) {
- mPopup.setAdapter(new DropDownAdapter(adapter));
- } else {
- mTempAdapter = new DropDownAdapter(adapter);
- }
+ final Context popupContext = mPopupContext == null ? mContext : mPopupContext;
+ mPopup.setAdapter(new DropDownAdapter(adapter, popupContext.getTheme()));
}
@Override
@@ -693,15 +766,14 @@ public class Spinner extends AbsSpinner implements OnClickListener {
}
@Override
- public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
- super.onInitializeAccessibilityEvent(event);
- event.setClassName(Spinner.class.getName());
+ public CharSequence getAccessibilityClassName() {
+ return Spinner.class.getName();
}
+ /** @hide */
@Override
- public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(info);
- info.setClassName(Spinner.class.getName());
+ public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
+ super.onInitializeAccessibilityNodeInfoInternal(info);
if (mAdapter != null) {
info.setCanOpenPopup(true);
@@ -847,14 +919,26 @@ public class Spinner extends AbsSpinner implements OnClickListener {
private ListAdapter mListAdapter;
/**
- * <p>Creates a new ListAdapter wrapper for the specified adapter.</p>
+ * Creates a new ListAdapter wrapper for the specified adapter.
*
- * @param adapter the Adapter to transform into a ListAdapter
+ * @param adapter the SpinnerAdapter to transform into a ListAdapter
+ * @param dropDownTheme the theme against which to inflate drop-down
+ * views, may be {@null} to use default theme
*/
- public DropDownAdapter(SpinnerAdapter adapter) {
- this.mAdapter = adapter;
+ public DropDownAdapter(@Nullable SpinnerAdapter adapter,
+ @Nullable Resources.Theme dropDownTheme) {
+ mAdapter = adapter;
+
if (adapter instanceof ListAdapter) {
- this.mListAdapter = (ListAdapter) adapter;
+ mListAdapter = (ListAdapter) adapter;
+ }
+
+ if (dropDownTheme != null && adapter instanceof Spinner.ThemedSpinnerAdapter) {
+ final Spinner.ThemedSpinnerAdapter themedAdapter =
+ (Spinner.ThemedSpinnerAdapter) adapter;
+ if (themedAdapter.getDropDownViewTheme() == null) {
+ themedAdapter.setDropDownViewTheme(dropDownTheme);
+ }
}
}
@@ -970,7 +1054,7 @@ public class Spinner extends AbsSpinner implements OnClickListener {
public int getVerticalOffset();
public int getHorizontalOffset();
}
-
+
private class DialogPopup implements SpinnerPopup, DialogInterface.OnClickListener {
private AlertDialog mPopup;
private ListAdapter mListAdapter;
@@ -994,7 +1078,7 @@ public class Spinner extends AbsSpinner implements OnClickListener {
public void setPromptText(CharSequence hintText) {
mPrompt = hintText;
}
-
+
public CharSequence getHintText() {
return mPrompt;
}
@@ -1003,7 +1087,7 @@ public class Spinner extends AbsSpinner implements OnClickListener {
if (mListAdapter == null) {
return;
}
- AlertDialog.Builder builder = new AlertDialog.Builder(getContext());
+ AlertDialog.Builder builder = new AlertDialog.Builder(getPopupContext());
if (mPrompt != null) {
builder.setTitle(mPrompt);
}
@@ -1179,4 +1263,21 @@ public class Spinner extends AbsSpinner implements OnClickListener {
}
}
}
+
+ public interface ThemedSpinnerAdapter {
+ /**
+ * Sets the {@link Resources.Theme} against which drop-down views are
+ * inflated.
+ *
+ * @param theme the context against which to inflate drop-down views
+ * @see SpinnerAdapter#getDropDownView(int, View, ViewGroup)
+ */
+ public void setDropDownViewTheme(Resources.Theme theme);
+
+ /**
+ * @return The {@link Resources.Theme} against which drop-down views are
+ * inflated.
+ */
+ public Resources.Theme getDropDownViewTheme();
+ }
}
diff --git a/core/java/android/widget/SpinnerAdapter.java b/core/java/android/widget/SpinnerAdapter.java
index 91504cf..f99f45b 100644
--- a/core/java/android/widget/SpinnerAdapter.java
+++ b/core/java/android/widget/SpinnerAdapter.java
@@ -22,16 +22,17 @@ import android.view.ViewGroup;
/**
* Extended {@link Adapter} that is the bridge between a
* {@link android.widget.Spinner} and its data. A spinner adapter allows to
- * define two different views: one that shows the data in the spinner itself and
- * one that shows the data in the drop down list when the spinner is pressed.</p>
+ * define two different views: one that shows the data in the spinner itself
+ * and one that shows the data in the drop down list when the spinner is
+ * pressed.
*/
public interface SpinnerAdapter extends Adapter {
/**
- * <p>Get a {@link android.view.View} that displays in the drop down popup
- * the data at the specified position in the data set.</p>
+ * Gets a {@link android.view.View} that displays in the drop down popup
+ * the data at the specified position in the data set.
*
- * @param position index of the item whose view we want.
- * @param convertView the old view to reuse, if possible. Note: You should
+ * @param position index of the item whose view we want.
+ * @param convertView the old view to reuse, if possible. Note: You should
* check that this view is non-null and of an appropriate type before
* using. If it is not possible to convert this view to display the
* correct data, this method can create a new view.
diff --git a/core/java/android/widget/StackView.java b/core/java/android/widget/StackView.java
index 9e168b8..803ba4b 100644
--- a/core/java/android/widget/StackView.java
+++ b/core/java/android/widget/StackView.java
@@ -1225,15 +1225,14 @@ public class StackView extends AdapterViewAnimator {
}
@Override
- public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
- super.onInitializeAccessibilityEvent(event);
- event.setClassName(StackView.class.getName());
+ public CharSequence getAccessibilityClassName() {
+ return StackView.class.getName();
}
+ /** @hide */
@Override
- public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(info);
- info.setClassName(StackView.class.getName());
+ public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
+ super.onInitializeAccessibilityNodeInfoInternal(info);
info.setScrollable(getChildCount() > 1);
if (isEnabled()) {
if (getDisplayedChild() < getChildCount() - 1) {
@@ -1245,9 +1244,10 @@ public class StackView extends AdapterViewAnimator {
}
}
+ /** @hide */
@Override
- public boolean performAccessibilityAction(int action, Bundle arguments) {
- if (super.performAccessibilityAction(action, arguments)) {
+ public boolean performAccessibilityActionInternal(int action, Bundle arguments) {
+ if (super.performAccessibilityActionInternal(action, arguments)) {
return true;
}
if (!isEnabled()) {
diff --git a/core/java/android/widget/SuggestionsAdapter.java b/core/java/android/widget/SuggestionsAdapter.java
index 6349347..4323851 100644
--- a/core/java/android/widget/SuggestionsAdapter.java
+++ b/core/java/android/widget/SuggestionsAdapter.java
@@ -145,8 +145,9 @@ class SuggestionsAdapter extends ResourceCursorAdapter implements OnClickListene
* copied to the query text field.
* <p>
*
- * @param refine which queries to refine. Possible values are {@link #REFINE_NONE},
- * {@link #REFINE_BY_ENTRY}, and {@link #REFINE_ALL}.
+ * @param refineWhat which queries to refine. Possible values are
+ * {@link #REFINE_NONE}, {@link #REFINE_BY_ENTRY}, and
+ * {@link #REFINE_ALL}.
*/
public void setQueryRefinement(int refineWhat) {
mQueryRefinement = refineWhat;
@@ -327,7 +328,7 @@ class SuggestionsAdapter extends ResourceCursorAdapter implements OnClickListene
// First check TEXT_2_URL
CharSequence text2 = getStringOrNull(cursor, mText2UrlCol);
if (text2 != null) {
- text2 = formatUrl(text2);
+ text2 = formatUrl(context, text2);
} else {
text2 = getStringOrNull(cursor, mText2Col);
}
@@ -372,12 +373,12 @@ class SuggestionsAdapter extends ResourceCursorAdapter implements OnClickListene
}
}
- private CharSequence formatUrl(CharSequence url) {
+ private CharSequence formatUrl(Context context, CharSequence url) {
if (mUrlColor == null) {
// Lazily get the URL color from the current theme.
TypedValue colorValue = new TypedValue();
- mContext.getTheme().resolveAttribute(R.attr.textColorSearchUrl, colorValue, true);
- mUrlColor = mContext.getResources().getColorStateList(colorValue.resourceId);
+ context.getTheme().resolveAttribute(R.attr.textColorSearchUrl, colorValue, true);
+ mUrlColor = context.getResources().getColorStateList(colorValue.resourceId);
}
SpannableString text = new SpannableString(url);
@@ -502,6 +503,30 @@ class SuggestionsAdapter extends ResourceCursorAdapter implements OnClickListene
}
/**
+ * This method is overridden purely to provide a bit of protection against
+ * flaky content providers.
+ *
+ * @see android.widget.CursorAdapter#getDropDownView(int, View, ViewGroup)
+ */
+ @Override
+ public View getDropDownView(int position, View convertView, ViewGroup parent) {
+ try {
+ return super.getDropDownView(position, convertView, parent);
+ } catch (RuntimeException e) {
+ Log.w(LOG_TAG, "Search suggestions cursor threw exception.", e);
+ // Put exception string in item title
+ final Context context = mDropDownContext == null ? mContext : mDropDownContext;
+ final View v = newDropDownView(context, mCursor, parent);
+ if (v != null) {
+ final ChildViewCache views = (ChildViewCache) v.getTag();
+ final TextView tv = views.mText1;
+ tv.setText(e.toString());
+ }
+ return v;
+ }
+ }
+
+ /**
* Gets a drawable given a value provided by a suggestion provider.
*
* This value could be just the string value of a resource id
@@ -570,7 +595,7 @@ class SuggestionsAdapter extends ResourceCursorAdapter implements OnClickListene
OpenResourceIdResult r =
mProviderContext.getContentResolver().getResourceId(uri);
try {
- return r.r.getDrawable(r.id, mContext.getTheme());
+ return r.r.getDrawable(r.id, mProviderContext.getTheme());
} catch (Resources.NotFoundException ex) {
throw new FileNotFoundException("Resource does not exist: " + uri);
}
diff --git a/core/java/android/widget/Switch.java b/core/java/android/widget/Switch.java
index 7a22224..b959ddc 100644
--- a/core/java/android/widget/Switch.java
+++ b/core/java/android/widget/Switch.java
@@ -17,6 +17,7 @@
package android.widget;
import android.animation.ObjectAnimator;
+import android.annotation.Nullable;
import android.content.Context;
import android.content.res.ColorStateList;
import android.content.res.Resources;
@@ -24,10 +25,12 @@ import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Insets;
import android.graphics.Paint;
+import android.graphics.PorterDuff;
import android.graphics.Rect;
import android.graphics.Typeface;
import android.graphics.Region.Op;
import android.graphics.drawable.Drawable;
+import android.os.Bundle;
import android.text.Layout;
import android.text.StaticLayout;
import android.text.TextPaint;
@@ -41,6 +44,7 @@ import android.view.Gravity;
import android.view.MotionEvent;
import android.view.SoundEffectConstants;
import android.view.VelocityTracker;
+import android.view.ViewAssistData;
import android.view.ViewConfiguration;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo;
@@ -84,7 +88,17 @@ public class Switch extends CompoundButton {
private static final int MONOSPACE = 3;
private Drawable mThumbDrawable;
+ private ColorStateList mThumbTintList = null;
+ private PorterDuff.Mode mThumbTintMode = null;
+ private boolean mHasThumbTint = false;
+ private boolean mHasThumbTintMode = false;
+
private Drawable mTrackDrawable;
+ private ColorStateList mTrackTintList = null;
+ private PorterDuff.Mode mTrackTintMode = null;
+ private boolean mHasTrackTint = false;
+ private boolean mHasTrackTintMode = false;
+
private int mThumbTextPadding;
private int mSwitchMinWidth;
private int mSwitchPadding;
@@ -473,6 +487,86 @@ public class Switch extends CompoundButton {
}
/**
+ * Applies a tint to the track drawable. Does not modify the current
+ * tint mode, which is {@link PorterDuff.Mode#SRC_IN} by default.
+ * <p>
+ * Subsequent calls to {@link #setTrackDrawable(Drawable)} will
+ * automatically mutate the drawable and apply the specified tint and tint
+ * mode using {@link Drawable#setTintList(ColorStateList)}.
+ *
+ * @param tint the tint to apply, may be {@code null} to clear tint
+ *
+ * @attr ref android.R.styleable#Switch_trackTint
+ * @see #getTrackTintList()
+ * @see Drawable#setTintList(ColorStateList)
+ */
+ public void setTrackTintList(@Nullable ColorStateList tint) {
+ mTrackTintList = tint;
+ mHasTrackTint = true;
+
+ applyTrackTint();
+ }
+
+ /**
+ * @return the tint applied to the track drawable
+ * @attr ref android.R.styleable#Switch_trackTint
+ * @see #setTrackTintList(ColorStateList)
+ */
+ @Nullable
+ public ColorStateList getTrackTintList() {
+ return mTrackTintList;
+ }
+
+ /**
+ * Specifies the blending mode used to apply the tint specified by
+ * {@link #setTrackTintList(ColorStateList)}} to the track drawable.
+ * The default mode is {@link PorterDuff.Mode#SRC_IN}.
+ *
+ * @param tintMode the blending mode used to apply the tint, may be
+ * {@code null} to clear tint
+ * @attr ref android.R.styleable#Switch_trackTintMode
+ * @see #getTrackTintMode()
+ * @see Drawable#setTintMode(PorterDuff.Mode)
+ */
+ public void setTrackTintMode(@Nullable PorterDuff.Mode tintMode) {
+ mTrackTintMode = tintMode;
+ mHasTrackTintMode = true;
+
+ applyTrackTint();
+ }
+
+ /**
+ * @return the blending mode used to apply the tint to the track
+ * drawable
+ * @attr ref android.R.styleable#Switch_trackTintMode
+ * @see #setTrackTintMode(PorterDuff.Mode)
+ */
+ @Nullable
+ public PorterDuff.Mode getTrackTintMode() {
+ return mTrackTintMode;
+ }
+
+ private void applyTrackTint() {
+ if (mTrackDrawable != null && (mHasTrackTint || mHasTrackTintMode)) {
+ mTrackDrawable = mTrackDrawable.mutate();
+
+ if (mHasTrackTint) {
+ mTrackDrawable.setTintList(mTrackTintList);
+ }
+
+ if (mHasTrackTintMode) {
+ mTrackDrawable.setTintMode(mTrackTintMode);
+ }
+
+ // The drawable (or one of its children) may not have been
+ // stateful before applying the tint, so let's try again.
+ if (mTrackDrawable.isStateful()) {
+ mTrackDrawable.setState(getDrawableState());
+ }
+ }
+ }
+
+ /**
* Set the drawable used for the switch "thumb" - the piece that the user
* can physically touch and drag along the track.
*
@@ -516,6 +610,86 @@ public class Switch extends CompoundButton {
}
/**
+ * Applies a tint to the thumb drawable. Does not modify the current
+ * tint mode, which is {@link PorterDuff.Mode#SRC_IN} by default.
+ * <p>
+ * Subsequent calls to {@link #setThumbDrawable(Drawable)} will
+ * automatically mutate the drawable and apply the specified tint and tint
+ * mode using {@link Drawable#setTintList(ColorStateList)}.
+ *
+ * @param tint the tint to apply, may be {@code null} to clear tint
+ *
+ * @attr ref android.R.styleable#Switch_thumbTint
+ * @see #getThumbTintList()
+ * @see Drawable#setTintList(ColorStateList)
+ */
+ public void setThumbTintList(@Nullable ColorStateList tint) {
+ mThumbTintList = tint;
+ mHasThumbTint = true;
+
+ applyThumbTint();
+ }
+
+ /**
+ * @return the tint applied to the thumb drawable
+ * @attr ref android.R.styleable#Switch_thumbTint
+ * @see #setThumbTintList(ColorStateList)
+ */
+ @Nullable
+ public ColorStateList getThumbTintList() {
+ return mThumbTintList;
+ }
+
+ /**
+ * Specifies the blending mode used to apply the tint specified by
+ * {@link #setThumbTintList(ColorStateList)}} to the thumb drawable.
+ * The default mode is {@link PorterDuff.Mode#SRC_IN}.
+ *
+ * @param tintMode the blending mode used to apply the tint, may be
+ * {@code null} to clear tint
+ * @attr ref android.R.styleable#Switch_thumbTintMode
+ * @see #getThumbTintMode()
+ * @see Drawable#setTintMode(PorterDuff.Mode)
+ */
+ public void setThumbTintMode(@Nullable PorterDuff.Mode tintMode) {
+ mThumbTintMode = tintMode;
+ mHasThumbTintMode = true;
+
+ applyThumbTint();
+ }
+
+ /**
+ * @return the blending mode used to apply the tint to the thumb
+ * drawable
+ * @attr ref android.R.styleable#Switch_thumbTintMode
+ * @see #setThumbTintMode(PorterDuff.Mode)
+ */
+ @Nullable
+ public PorterDuff.Mode getThumbTintMode() {
+ return mThumbTintMode;
+ }
+
+ private void applyThumbTint() {
+ if (mThumbDrawable != null && (mHasThumbTint || mHasThumbTintMode)) {
+ mThumbDrawable = mThumbDrawable.mutate();
+
+ if (mHasThumbTint) {
+ mThumbDrawable.setTintList(mThumbTintList);
+ }
+
+ if (mHasThumbTintMode) {
+ mThumbDrawable.setTintMode(mThumbTintMode);
+ }
+
+ // The drawable (or one of its children) may not have been
+ // stateful before applying the tint, so let's try again.
+ if (mThumbDrawable.isStateful()) {
+ mThumbDrawable.setState(getDrawableState());
+ }
+ }
+ }
+
+ /**
* Specifies whether the track should be split by the thumb. When true,
* the thumb's optical bounds will be clipped out of the track drawable,
* then the thumb will be drawn into the resulting gap.
@@ -665,9 +839,10 @@ public class Switch extends CompoundButton {
}
}
+ /** @hide */
@Override
- public void onPopulateAccessibilityEvent(AccessibilityEvent event) {
- super.onPopulateAccessibilityEvent(event);
+ public void onPopulateAccessibilityEventInternal(AccessibilityEvent event) {
+ super.onPopulateAccessibilityEventInternal(event);
final CharSequence text = isChecked() ? mTextOn : mTextOff;
if (text != null) {
@@ -1181,15 +1356,30 @@ public class Switch extends CompoundButton {
}
@Override
- public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
- super.onInitializeAccessibilityEvent(event);
- event.setClassName(Switch.class.getName());
+ public CharSequence getAccessibilityClassName() {
+ return Switch.class.getName();
+ }
+
+ @Override
+ public void onProvideAssistData(ViewAssistData data, Bundle extras) {
+ super.onProvideAssistData(data, extras);
+ CharSequence switchText = isChecked() ? mTextOn : mTextOff;
+ if (!TextUtils.isEmpty(switchText)) {
+ CharSequence oldText = data.getText();
+ if (TextUtils.isEmpty(oldText)) {
+ data.setText(switchText);
+ } else {
+ StringBuilder newText = new StringBuilder();
+ newText.append(oldText).append(' ').append(switchText);
+ data.setText(newText);
+ }
+ }
}
+ /** @hide */
@Override
- public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(info);
- info.setClassName(Switch.class.getName());
+ public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
+ super.onInitializeAccessibilityNodeInfoInternal(info);
CharSequence switchText = isChecked() ? mTextOn : mTextOff;
if (!TextUtils.isEmpty(switchText)) {
CharSequence oldText = info.getText();
diff --git a/core/java/android/widget/TabHost.java b/core/java/android/widget/TabHost.java
index 89df51a..bf35cf9 100644
--- a/core/java/android/widget/TabHost.java
+++ b/core/java/android/widget/TabHost.java
@@ -173,8 +173,9 @@ mTabHost.addTab(TAB_TAG_1, "Hello, world!", "Tab 1");
}
}
+ /** @hide */
@Override
- public void sendAccessibilityEvent(int eventType) {
+ public void sendAccessibilityEventInternal(int eventType) {
/* avoid super class behavior - TabWidget sends the right events */
}
@@ -384,15 +385,8 @@ mTabHost.addTab(TAB_TAG_1, "Hello, world!", "Tab 1");
}
@Override
- public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
- super.onInitializeAccessibilityEvent(event);
- event.setClassName(TabHost.class.getName());
- }
-
- @Override
- public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(info);
- info.setClassName(TabHost.class.getName());
+ public CharSequence getAccessibilityClassName() {
+ return TabHost.class.getName();
}
public void setCurrentTab(int index) {
diff --git a/core/java/android/widget/TabWidget.java b/core/java/android/widget/TabWidget.java
index 47a5449..88ecb13 100644
--- a/core/java/android/widget/TabWidget.java
+++ b/core/java/android/widget/TabWidget.java
@@ -401,8 +401,9 @@ public class TabWidget extends LinearLayout implements OnFocusChangeListener {
}
}
+ /** @hide */
@Override
- public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
+ public boolean dispatchPopulateAccessibilityEventInternal(AccessibilityEvent event) {
onPopulateAccessibilityEvent(event);
// Dispatch only to the selected tab.
if (mSelectedTab != -1) {
@@ -415,28 +416,28 @@ public class TabWidget extends LinearLayout implements OnFocusChangeListener {
}
@Override
- public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
- super.onInitializeAccessibilityEvent(event);
- event.setClassName(TabWidget.class.getName());
+ public CharSequence getAccessibilityClassName() {
+ return TabWidget.class.getName();
+ }
+
+ /** @hide */
+ @Override
+ public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) {
+ super.onInitializeAccessibilityEventInternal(event);
event.setItemCount(getTabCount());
event.setCurrentItemIndex(mSelectedTab);
}
+ /** @hide */
@Override
- public void sendAccessibilityEventUnchecked(AccessibilityEvent event) {
+ public void sendAccessibilityEventUncheckedInternal(AccessibilityEvent event) {
// this class fires events only when tabs are focused or selected
if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_FOCUSED && isFocused()) {
event.recycle();
return;
}
- super.sendAccessibilityEventUnchecked(event);
- }
-
- @Override
- public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(info);
- info.setClassName(TabWidget.class.getName());
+ super.sendAccessibilityEventUncheckedInternal(event);
}
/**
diff --git a/core/java/android/widget/TableLayout.java b/core/java/android/widget/TableLayout.java
index f4b2ce0..c825d17 100644
--- a/core/java/android/widget/TableLayout.java
+++ b/core/java/android/widget/TableLayout.java
@@ -667,15 +667,8 @@ public class TableLayout extends LinearLayout {
}
@Override
- public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
- super.onInitializeAccessibilityEvent(event);
- event.setClassName(TableLayout.class.getName());
- }
-
- @Override
- public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(info);
- info.setClassName(TableLayout.class.getName());
+ public CharSequence getAccessibilityClassName() {
+ return TableLayout.class.getName();
}
/**
diff --git a/core/java/android/widget/TableRow.java b/core/java/android/widget/TableRow.java
index fe3631a..72fce3f 100644
--- a/core/java/android/widget/TableRow.java
+++ b/core/java/android/widget/TableRow.java
@@ -380,15 +380,8 @@ public class TableRow extends LinearLayout {
}
@Override
- public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
- super.onInitializeAccessibilityEvent(event);
- event.setClassName(TableRow.class.getName());
- }
-
- @Override
- public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(info);
- info.setClassName(TableRow.class.getName());
+ public CharSequence getAccessibilityClassName() {
+ return TableRow.class.getName();
}
/**
diff --git a/core/java/android/widget/TextSwitcher.java b/core/java/android/widget/TextSwitcher.java
index 1aefd2b..22822b1 100644
--- a/core/java/android/widget/TextSwitcher.java
+++ b/core/java/android/widget/TextSwitcher.java
@@ -92,14 +92,7 @@ public class TextSwitcher extends ViewSwitcher {
}
@Override
- public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
- super.onInitializeAccessibilityEvent(event);
- event.setClassName(TextSwitcher.class.getName());
- }
-
- @Override
- public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(info);
- info.setClassName(TextSwitcher.class.getName());
+ public CharSequence getAccessibilityClassName() {
+ return TextSwitcher.class.getName();
}
}
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index dd8280b..848c1c0 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -25,6 +25,7 @@ import android.content.Context;
import android.content.UndoManager;
import android.content.res.ColorStateList;
import android.content.res.CompatibilityInfo;
+import android.content.res.Configuration;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.content.res.XmlResourceParser;
@@ -32,6 +33,7 @@ import android.graphics.Canvas;
import android.graphics.Insets;
import android.graphics.Paint;
import android.graphics.Path;
+import android.graphics.PorterDuff;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.Typeface;
@@ -107,6 +109,7 @@ import android.view.Menu;
import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.View;
+import android.view.ViewAssistData;
import android.view.ViewConfiguration;
import android.view.ViewDebug;
import android.view.ViewGroup.LayoutParams;
@@ -214,6 +217,8 @@ import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1;
* @attr ref android.R.styleable#TextView_drawableStart
* @attr ref android.R.styleable#TextView_drawableEnd
* @attr ref android.R.styleable#TextView_drawablePadding
+ * @attr ref android.R.styleable#TextView_drawableTint
+ * @attr ref android.R.styleable#TextView_drawableTintMode
* @attr ref android.R.styleable#TextView_lineSpacingExtra
* @attr ref android.R.styleable#TextView_lineSpacingMultiplier
* @attr ref android.R.styleable#TextView_marqueeRepeatLimit
@@ -298,7 +303,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
private float mShadowRadius, mShadowDx, mShadowDy;
private int mShadowColor;
-
private boolean mPreDrawRegistered;
private boolean mPreDrawListenerDetached;
@@ -312,16 +316,27 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
private TextUtils.TruncateAt mEllipsize;
static class Drawables {
- final static int DRAWABLE_NONE = -1;
- final static int DRAWABLE_RIGHT = 0;
- final static int DRAWABLE_LEFT = 1;
+ static final int LEFT = 0;
+ static final int TOP = 1;
+ static final int RIGHT = 2;
+ static final int BOTTOM = 3;
+
+ static final int DRAWABLE_NONE = -1;
+ static final int DRAWABLE_RIGHT = 0;
+ static final int DRAWABLE_LEFT = 1;
final Rect mCompoundRect = new Rect();
- Drawable mDrawableTop, mDrawableBottom, mDrawableLeft, mDrawableRight,
- mDrawableStart, mDrawableEnd, mDrawableError, mDrawableTemp;
+ final Drawable[] mShowing = new Drawable[4];
+ ColorStateList mTintList;
+ PorterDuff.Mode mTintMode;
+ boolean mHasTint;
+ boolean mHasTintMode;
+
+ Drawable mDrawableStart, mDrawableEnd, mDrawableError, mDrawableTemp;
Drawable mDrawableLeftInitial, mDrawableRightInitial;
+
boolean mIsRtlCompatibilityMode;
boolean mOverride;
@@ -344,19 +359,19 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
public void resolveWithLayoutDirection(int layoutDirection) {
// First reset "left" and "right" drawables to their initial values
- mDrawableLeft = mDrawableLeftInitial;
- mDrawableRight = mDrawableRightInitial;
+ mShowing[Drawables.LEFT] = mDrawableLeftInitial;
+ mShowing[Drawables.RIGHT] = mDrawableRightInitial;
if (mIsRtlCompatibilityMode) {
// Use "start" drawable as "left" drawable if the "left" drawable was not defined
- if (mDrawableStart != null && mDrawableLeft == null) {
- mDrawableLeft = mDrawableStart;
+ if (mDrawableStart != null && mShowing[Drawables.LEFT] == null) {
+ mShowing[Drawables.LEFT] = mDrawableStart;
mDrawableSizeLeft = mDrawableSizeStart;
mDrawableHeightLeft = mDrawableHeightStart;
}
// Use "end" drawable as "right" drawable if the "right" drawable was not defined
- if (mDrawableEnd != null && mDrawableRight == null) {
- mDrawableRight = mDrawableEnd;
+ if (mDrawableEnd != null && mShowing[Drawables.RIGHT] == null) {
+ mShowing[Drawables.RIGHT] = mDrawableEnd;
mDrawableSizeRight = mDrawableSizeEnd;
mDrawableHeightRight = mDrawableHeightEnd;
}
@@ -366,11 +381,11 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
switch(layoutDirection) {
case LAYOUT_DIRECTION_RTL:
if (mOverride) {
- mDrawableRight = mDrawableStart;
+ mShowing[Drawables.RIGHT] = mDrawableStart;
mDrawableSizeRight = mDrawableSizeStart;
mDrawableHeightRight = mDrawableHeightStart;
- mDrawableLeft = mDrawableEnd;
+ mShowing[Drawables.LEFT] = mDrawableEnd;
mDrawableSizeLeft = mDrawableSizeEnd;
mDrawableHeightLeft = mDrawableHeightEnd;
}
@@ -379,11 +394,11 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
case LAYOUT_DIRECTION_LTR:
default:
if (mOverride) {
- mDrawableLeft = mDrawableStart;
+ mShowing[Drawables.LEFT] = mDrawableStart;
mDrawableSizeLeft = mDrawableSizeStart;
mDrawableHeightLeft = mDrawableHeightStart;
- mDrawableRight = mDrawableEnd;
+ mShowing[Drawables.RIGHT] = mDrawableEnd;
mDrawableSizeRight = mDrawableSizeEnd;
mDrawableHeightRight = mDrawableHeightEnd;
}
@@ -395,17 +410,10 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
private void updateDrawablesLayoutDirection(int layoutDirection) {
- if (mDrawableLeft != null) {
- mDrawableLeft.setLayoutDirection(layoutDirection);
- }
- if (mDrawableRight != null) {
- mDrawableRight.setLayoutDirection(layoutDirection);
- }
- if (mDrawableTop != null) {
- mDrawableTop.setLayoutDirection(layoutDirection);
- }
- if (mDrawableBottom != null) {
- mDrawableBottom.setLayoutDirection(layoutDirection);
+ for (Drawable dr : mShowing) {
+ if (dr != null) {
+ dr.setLayoutDirection(layoutDirection);
+ }
}
}
@@ -415,10 +423,10 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
mDrawableError = dr;
- final Rect compoundRect = mCompoundRect;
- int[] state = tv.getDrawableState();
-
if (mDrawableError != null) {
+ final Rect compoundRect = mCompoundRect;
+ final int[] state = tv.getDrawableState();
+
mDrawableError.setState(state);
mDrawableError.copyBounds(compoundRect);
mDrawableError.setCallback(tv);
@@ -433,12 +441,12 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
// first restore the initial state if needed
switch (mDrawableSaved) {
case DRAWABLE_LEFT:
- mDrawableLeft = mDrawableTemp;
+ mShowing[Drawables.LEFT] = mDrawableTemp;
mDrawableSizeLeft = mDrawableSizeTemp;
mDrawableHeightLeft = mDrawableHeightTemp;
break;
case DRAWABLE_RIGHT:
- mDrawableRight = mDrawableTemp;
+ mShowing[Drawables.RIGHT] = mDrawableTemp;
mDrawableSizeRight = mDrawableSizeTemp;
mDrawableHeightRight = mDrawableHeightTemp;
break;
@@ -451,11 +459,11 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
case LAYOUT_DIRECTION_RTL:
mDrawableSaved = DRAWABLE_LEFT;
- mDrawableTemp = mDrawableLeft;
+ mDrawableTemp = mShowing[Drawables.LEFT];
mDrawableSizeTemp = mDrawableSizeLeft;
mDrawableHeightTemp = mDrawableHeightLeft;
- mDrawableLeft = mDrawableError;
+ mShowing[Drawables.LEFT] = mDrawableError;
mDrawableSizeLeft = mDrawableSizeError;
mDrawableHeightLeft = mDrawableHeightError;
break;
@@ -463,11 +471,11 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
default:
mDrawableSaved = DRAWABLE_RIGHT;
- mDrawableTemp = mDrawableRight;
+ mDrawableTemp = mShowing[Drawables.RIGHT];
mDrawableSizeTemp = mDrawableSizeRight;
mDrawableHeightTemp = mDrawableHeightRight;
- mDrawableRight = mDrawableError;
+ mShowing[Drawables.RIGHT] = mDrawableError;
mDrawableSizeRight = mDrawableSizeError;
mDrawableHeightRight = mDrawableHeightError;
break;
@@ -520,6 +528,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
private final TextPaint mTextPaint;
private boolean mUserSetTextScaleX;
private Layout mLayout;
+ private boolean mLocaleChanged = false;
private int mGravity = Gravity.TOP | Gravity.START;
private boolean mHorizontallyScrolling;
@@ -771,6 +780,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
boolean selectallonfocus = false;
Drawable drawableLeft = null, drawableTop = null, drawableRight = null,
drawableBottom = null, drawableStart = null, drawableEnd = null;
+ ColorStateList drawableTint = null;
+ PorterDuff.Mode drawableTintMode = null;
int drawablePadding = 0;
int ellipsize = -1;
boolean singleLine = false;
@@ -856,6 +867,14 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
drawableEnd = a.getDrawable(attr);
break;
+ case com.android.internal.R.styleable.TextView_drawableTint:
+ drawableTint = a.getColorStateList(attr);
+ break;
+
+ case com.android.internal.R.styleable.TextView_drawableTintMode:
+ drawableTintMode = Drawable.parseTintMode(a.getInt(attr, -1), drawableTintMode);
+ break;
+
case com.android.internal.R.styleable.TextView_drawablePadding:
drawablePadding = a.getDimensionPixelSize(attr, drawablePadding);
break;
@@ -1240,6 +1259,22 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
bufferType = BufferType.SPANNABLE;
}
+ // Set up the tint (if needed) before setting the drawables so that it
+ // gets applied correctly.
+ if (drawableTint != null || drawableTintMode != null) {
+ if (mDrawables == null) {
+ mDrawables = new Drawables(context);
+ }
+ if (drawableTint != null) {
+ mDrawables.mTintList = drawableTint;
+ mDrawables.mHasTint = true;
+ }
+ if (drawableTintMode != null) {
+ mDrawables.mTintMode = drawableTintMode;
+ mDrawables.mHasTintMode = true;
+ }
+ }
+
// This call will save the initial left/right drawables
setCompoundDrawablesWithIntrinsicBounds(
drawableLeft, drawableTop, drawableRight, drawableBottom);
@@ -1425,6 +1460,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
resetResolvedDrawables();
resolveDrawables();
+ applyCompoundDrawableTint();
}
}
@@ -1783,7 +1819,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
*/
public int getCompoundPaddingTop() {
final Drawables dr = mDrawables;
- if (dr == null || dr.mDrawableTop == null) {
+ if (dr == null || dr.mShowing[Drawables.TOP] == null) {
return mPaddingTop;
} else {
return mPaddingTop + dr.mDrawablePadding + dr.mDrawableSizeTop;
@@ -1796,7 +1832,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
*/
public int getCompoundPaddingBottom() {
final Drawables dr = mDrawables;
- if (dr == null || dr.mDrawableBottom == null) {
+ if (dr == null || dr.mShowing[Drawables.BOTTOM] == null) {
return mPaddingBottom;
} else {
return mPaddingBottom + dr.mDrawablePadding + dr.mDrawableSizeBottom;
@@ -1809,7 +1845,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
*/
public int getCompoundPaddingLeft() {
final Drawables dr = mDrawables;
- if (dr == null || dr.mDrawableLeft == null) {
+ if (dr == null || dr.mShowing[Drawables.LEFT] == null) {
return mPaddingLeft;
} else {
return mPaddingLeft + dr.mDrawablePadding + dr.mDrawableSizeLeft;
@@ -1822,7 +1858,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
*/
public int getCompoundPaddingRight() {
final Drawables dr = mDrawables;
- if (dr == null || dr.mDrawableRight == null) {
+ if (dr == null || dr.mShowing[Drawables.RIGHT] == null) {
return mPaddingRight;
} else {
return mPaddingRight + dr.mDrawablePadding + dr.mDrawableSizeRight;
@@ -2020,14 +2056,12 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
} else {
// We need to retain the last set padding, so just clear
// out all of the fields in the existing structure.
- if (dr.mDrawableLeft != null) dr.mDrawableLeft.setCallback(null);
- dr.mDrawableLeft = null;
- if (dr.mDrawableTop != null) dr.mDrawableTop.setCallback(null);
- dr.mDrawableTop = null;
- if (dr.mDrawableRight != null) dr.mDrawableRight.setCallback(null);
- dr.mDrawableRight = null;
- if (dr.mDrawableBottom != null) dr.mDrawableBottom.setCallback(null);
- dr.mDrawableBottom = null;
+ for (int i = dr.mShowing.length - 1; i >= 0; i--) {
+ if (dr.mShowing[i] != null) {
+ dr.mShowing[i].setCallback(null);
+ }
+ dr.mShowing[i] = null;
+ }
dr.mDrawableSizeLeft = dr.mDrawableHeightLeft = 0;
dr.mDrawableSizeRight = dr.mDrawableHeightRight = 0;
dr.mDrawableSizeTop = dr.mDrawableWidthTop = 0;
@@ -2041,25 +2075,25 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
mDrawables.mOverride = false;
- if (dr.mDrawableLeft != left && dr.mDrawableLeft != null) {
- dr.mDrawableLeft.setCallback(null);
+ if (dr.mShowing[Drawables.LEFT] != left && dr.mShowing[Drawables.LEFT] != null) {
+ dr.mShowing[Drawables.LEFT].setCallback(null);
}
- dr.mDrawableLeft = left;
+ dr.mShowing[Drawables.LEFT] = left;
- if (dr.mDrawableTop != top && dr.mDrawableTop != null) {
- dr.mDrawableTop.setCallback(null);
+ if (dr.mShowing[Drawables.TOP] != top && dr.mShowing[Drawables.TOP] != null) {
+ dr.mShowing[Drawables.TOP].setCallback(null);
}
- dr.mDrawableTop = top;
+ dr.mShowing[Drawables.TOP] = top;
- if (dr.mDrawableRight != right && dr.mDrawableRight != null) {
- dr.mDrawableRight.setCallback(null);
+ if (dr.mShowing[Drawables.RIGHT] != right && dr.mShowing[Drawables.RIGHT] != null) {
+ dr.mShowing[Drawables.RIGHT].setCallback(null);
}
- dr.mDrawableRight = right;
+ dr.mShowing[Drawables.RIGHT] = right;
- if (dr.mDrawableBottom != bottom && dr.mDrawableBottom != null) {
- dr.mDrawableBottom.setCallback(null);
+ if (dr.mShowing[Drawables.BOTTOM] != bottom && dr.mShowing[Drawables.BOTTOM] != null) {
+ dr.mShowing[Drawables.BOTTOM].setCallback(null);
}
- dr.mDrawableBottom = bottom;
+ dr.mShowing[Drawables.BOTTOM] = bottom;
final Rect compoundRect = dr.mCompoundRect;
int[] state;
@@ -2115,6 +2149,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
resetResolvedDrawables();
resolveDrawables();
+ applyCompoundDrawableTint();
invalidate();
requestLayout();
}
@@ -2198,10 +2233,14 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
// We're switching to relative, discard absolute.
if (dr != null) {
- if (dr.mDrawableLeft != null) dr.mDrawableLeft.setCallback(null);
- dr.mDrawableLeft = dr.mDrawableLeftInitial = null;
- if (dr.mDrawableRight != null) dr.mDrawableRight.setCallback(null);
- dr.mDrawableRight = dr.mDrawableRightInitial = null;
+ if (dr.mShowing[Drawables.LEFT] != null) {
+ dr.mShowing[Drawables.LEFT].setCallback(null);
+ }
+ dr.mShowing[Drawables.LEFT] = dr.mDrawableLeftInitial = null;
+ if (dr.mShowing[Drawables.RIGHT] != null) {
+ dr.mShowing[Drawables.RIGHT].setCallback(null);
+ }
+ dr.mShowing[Drawables.RIGHT] = dr.mDrawableRightInitial = null;
dr.mDrawableSizeLeft = dr.mDrawableHeightLeft = 0;
dr.mDrawableSizeRight = dr.mDrawableHeightRight = 0;
}
@@ -2219,12 +2258,18 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
// out all of the fields in the existing structure.
if (dr.mDrawableStart != null) dr.mDrawableStart.setCallback(null);
dr.mDrawableStart = null;
- if (dr.mDrawableTop != null) dr.mDrawableTop.setCallback(null);
- dr.mDrawableTop = null;
- if (dr.mDrawableEnd != null) dr.mDrawableEnd.setCallback(null);
+ if (dr.mShowing[Drawables.TOP] != null) {
+ dr.mShowing[Drawables.TOP].setCallback(null);
+ }
+ dr.mShowing[Drawables.TOP] = null;
+ if (dr.mDrawableEnd != null) {
+ dr.mDrawableEnd.setCallback(null);
+ }
dr.mDrawableEnd = null;
- if (dr.mDrawableBottom != null) dr.mDrawableBottom.setCallback(null);
- dr.mDrawableBottom = null;
+ if (dr.mShowing[Drawables.BOTTOM] != null) {
+ dr.mShowing[Drawables.BOTTOM].setCallback(null);
+ }
+ dr.mShowing[Drawables.BOTTOM] = null;
dr.mDrawableSizeStart = dr.mDrawableHeightStart = 0;
dr.mDrawableSizeEnd = dr.mDrawableHeightEnd = 0;
dr.mDrawableSizeTop = dr.mDrawableWidthTop = 0;
@@ -2243,20 +2288,20 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
dr.mDrawableStart = start;
- if (dr.mDrawableTop != top && dr.mDrawableTop != null) {
- dr.mDrawableTop.setCallback(null);
+ if (dr.mShowing[Drawables.TOP] != top && dr.mShowing[Drawables.TOP] != null) {
+ dr.mShowing[Drawables.TOP].setCallback(null);
}
- dr.mDrawableTop = top;
+ dr.mShowing[Drawables.TOP] = top;
if (dr.mDrawableEnd != end && dr.mDrawableEnd != null) {
dr.mDrawableEnd.setCallback(null);
}
dr.mDrawableEnd = end;
- if (dr.mDrawableBottom != bottom && dr.mDrawableBottom != null) {
- dr.mDrawableBottom.setCallback(null);
+ if (dr.mShowing[Drawables.BOTTOM] != bottom && dr.mShowing[Drawables.BOTTOM] != null) {
+ dr.mShowing[Drawables.BOTTOM].setCallback(null);
}
- dr.mDrawableBottom = bottom;
+ dr.mShowing[Drawables.BOTTOM] = bottom;
final Rect compoundRect = dr.mCompoundRect;
int[] state;
@@ -2382,9 +2427,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
public Drawable[] getCompoundDrawables() {
final Drawables dr = mDrawables;
if (dr != null) {
- return new Drawable[] {
- dr.mDrawableLeft, dr.mDrawableTop, dr.mDrawableRight, dr.mDrawableBottom
- };
+ return dr.mShowing.clone();
} else {
return new Drawable[] { null, null, null, null };
}
@@ -2403,7 +2446,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
final Drawables dr = mDrawables;
if (dr != null) {
return new Drawable[] {
- dr.mDrawableStart, dr.mDrawableTop, dr.mDrawableEnd, dr.mDrawableBottom
+ dr.mDrawableStart, dr.mShowing[Drawables.TOP],
+ dr.mDrawableEnd, dr.mShowing[Drawables.BOTTOM]
};
} else {
return new Drawable[] { null, null, null, null };
@@ -2444,6 +2488,118 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
return dr != null ? dr.mDrawablePadding : 0;
}
+ /**
+ * Applies a tint to the compound drawables. Does not modify the
+ * current tint mode, which is {@link PorterDuff.Mode#SRC_IN} by default.
+ * <p>
+ * Subsequent calls to
+ * {@link #setCompoundDrawables(Drawable, Drawable, Drawable, Drawable)}
+ * and related methods will automatically mutate the drawables and apply
+ * the specified tint and tint mode using
+ * {@link Drawable#setTintList(ColorStateList)}.
+ *
+ * @param tint the tint to apply, may be {@code null} to clear tint
+ *
+ * @attr ref android.R.styleable#TextView_drawableTint
+ * @see #getCompoundDrawableTintList()
+ * @see Drawable#setTintList(ColorStateList)
+ */
+ public void setCompoundDrawableTintList(@Nullable ColorStateList tint) {
+ if (mDrawables == null) {
+ mDrawables = new Drawables(getContext());
+ }
+ mDrawables.mTintList = tint;
+ mDrawables.mHasTint = true;
+
+ applyCompoundDrawableTint();
+ }
+
+ /**
+ * @return the tint applied to the compound drawables
+ * @attr ref android.R.styleable#TextView_drawableTint
+ * @see #setCompoundDrawableTintList(ColorStateList)
+ */
+ public ColorStateList getCompoundDrawableTintList() {
+ return mDrawables != null ? mDrawables.mTintList : null;
+ }
+
+ /**
+ * Specifies the blending mode used to apply the tint specified by
+ * {@link #setCompoundDrawableTintList(ColorStateList)} to the compound
+ * drawables. The default mode is {@link PorterDuff.Mode#SRC_IN}.
+ *
+ * @param tintMode the blending mode used to apply the tint, may be
+ * {@code null} to clear tint
+ * @attr ref android.R.styleable#TextView_drawableTintMode
+ * @see #setCompoundDrawableTintList(ColorStateList)
+ * @see Drawable#setTintMode(PorterDuff.Mode)
+ */
+ public void setCompoundDrawableTintMode(@Nullable PorterDuff.Mode tintMode) {
+ if (mDrawables == null) {
+ mDrawables = new Drawables(getContext());
+ }
+ mDrawables.mTintMode = tintMode;
+ mDrawables.mHasTintMode = true;
+
+ applyCompoundDrawableTint();
+ }
+
+ /**
+ * Returns the blending mode used to apply the tint to the compound
+ * drawables, if specified.
+ *
+ * @return the blending mode used to apply the tint to the compound
+ * drawables
+ * @attr ref android.R.styleable#TextView_drawableTintMode
+ * @see #setCompoundDrawableTintMode(PorterDuff.Mode)
+ */
+ public PorterDuff.Mode getCompoundDrawableTintMode() {
+ return mDrawables != null ? mDrawables.mTintMode : null;
+ }
+
+ private void applyCompoundDrawableTint() {
+ if (mDrawables == null) {
+ return;
+ }
+
+ if (mDrawables.mHasTint || mDrawables.mHasTintMode) {
+ final ColorStateList tintList = mDrawables.mTintList;
+ final PorterDuff.Mode tintMode = mDrawables.mTintMode;
+ final boolean hasTint = mDrawables.mHasTint;
+ final boolean hasTintMode = mDrawables.mHasTintMode;
+ final int[] state = getDrawableState();
+
+ for (Drawable dr : mDrawables.mShowing) {
+ if (dr == null) {
+ continue;
+ }
+
+ if (dr == mDrawables.mDrawableError) {
+ // From a developer's perspective, the error drawable isn't
+ // a compound drawable. Don't apply the generic compound
+ // drawable tint to it.
+ continue;
+ }
+
+ dr.mutate();
+
+ if (hasTint) {
+ dr.setTintList(tintList);
+ }
+
+ if (hasTintMode) {
+ dr.setTintMode(tintMode);
+ }
+
+ // The drawable (or one of its children) may not have been
+ // stateful before applying the tint, so let's try again.
+ if (dr.isStateful()) {
+ dr.setState(state);
+ }
+ }
+ }
+ }
+
@Override
public void setPadding(int left, int top, int right, int bottom) {
if (left != mPaddingLeft ||
@@ -2592,9 +2748,18 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
* @see Paint#setTextLocale
*/
public void setTextLocale(Locale locale) {
+ mLocaleChanged = true;
mTextPaint.setTextLocale(locale);
}
+ @Override
+ protected void onConfigurationChanged(Configuration newConfig) {
+ super.onConfigurationChanged(newConfig);
+ if (!mLocaleChanged) {
+ mTextPaint.setTextLocale(Locale.getDefault());
+ }
+ }
+
/**
* @return the size (in pixels) of the default text size in this TextView.
*/
@@ -3662,26 +3827,12 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
updateTextColors();
}
- final Drawables dr = mDrawables;
- if (dr != null) {
- int[] state = getDrawableState();
- if (dr.mDrawableTop != null && dr.mDrawableTop.isStateful()) {
- dr.mDrawableTop.setState(state);
- }
- if (dr.mDrawableBottom != null && dr.mDrawableBottom.isStateful()) {
- dr.mDrawableBottom.setState(state);
- }
- if (dr.mDrawableLeft != null && dr.mDrawableLeft.isStateful()) {
- dr.mDrawableLeft.setState(state);
- }
- if (dr.mDrawableRight != null && dr.mDrawableRight.isStateful()) {
- dr.mDrawableRight.setState(state);
- }
- if (dr.mDrawableStart != null && dr.mDrawableStart.isStateful()) {
- dr.mDrawableStart.setState(state);
- }
- if (dr.mDrawableEnd != null && dr.mDrawableEnd.isStateful()) {
- dr.mDrawableEnd.setState(state);
+ if (mDrawables != null) {
+ final int[] state = getDrawableState();
+ for (Drawable dr : mDrawables.mShowing) {
+ if (dr != null && dr.isStateful()) {
+ dr.setState(state);
+ }
}
}
}
@@ -3690,25 +3841,12 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
public void drawableHotspotChanged(float x, float y) {
super.drawableHotspotChanged(x, y);
- final Drawables dr = mDrawables;
- if (dr != null) {
- if (dr.mDrawableTop != null) {
- dr.mDrawableTop.setHotspot(x, y);
- }
- if (dr.mDrawableBottom != null) {
- dr.mDrawableBottom.setHotspot(x, y);
- }
- if (dr.mDrawableLeft != null) {
- dr.mDrawableLeft.setHotspot(x, y);
- }
- if (dr.mDrawableRight != null) {
- dr.mDrawableRight.setHotspot(x, y);
- }
- if (dr.mDrawableStart != null) {
- dr.mDrawableStart.setHotspot(x, y);
- }
- if (dr.mDrawableEnd != null) {
- dr.mDrawableEnd.setHotspot(x, y);
+ if (mDrawables != null) {
+ final int[] state = getDrawableState();
+ for (Drawable dr : mDrawables.mShowing) {
+ if (dr != null && dr.isStateful()) {
+ dr.setHotspot(x, y);
+ }
}
}
}
@@ -5039,9 +5177,11 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
protected boolean verifyDrawable(Drawable who) {
final boolean verified = super.verifyDrawable(who);
if (!verified && mDrawables != null) {
- return who == mDrawables.mDrawableLeft || who == mDrawables.mDrawableTop ||
- who == mDrawables.mDrawableRight || who == mDrawables.mDrawableBottom ||
- who == mDrawables.mDrawableStart || who == mDrawables.mDrawableEnd;
+ for (Drawable dr : mDrawables.mShowing) {
+ if (who == dr) {
+ return true;
+ }
+ }
}
return verified;
}
@@ -5050,23 +5190,10 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
public void jumpDrawablesToCurrentState() {
super.jumpDrawablesToCurrentState();
if (mDrawables != null) {
- if (mDrawables.mDrawableLeft != null) {
- mDrawables.mDrawableLeft.jumpToCurrentState();
- }
- if (mDrawables.mDrawableTop != null) {
- mDrawables.mDrawableTop.jumpToCurrentState();
- }
- if (mDrawables.mDrawableRight != null) {
- mDrawables.mDrawableRight.jumpToCurrentState();
- }
- if (mDrawables.mDrawableBottom != null) {
- mDrawables.mDrawableBottom.jumpToCurrentState();
- }
- if (mDrawables.mDrawableStart != null) {
- mDrawables.mDrawableStart.jumpToCurrentState();
- }
- if (mDrawables.mDrawableEnd != null) {
- mDrawables.mDrawableEnd.jumpToCurrentState();
+ for (Drawable dr : mDrawables.mShowing) {
+ if (dr != null) {
+ dr.jumpToCurrentState();
+ }
}
}
}
@@ -5085,7 +5212,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
// accordingly.
final TextView.Drawables drawables = mDrawables;
if (drawables != null) {
- if (drawable == drawables.mDrawableLeft) {
+ if (drawable == drawables.mShowing[Drawables.LEFT]) {
final int compoundPaddingTop = getCompoundPaddingTop();
final int compoundPaddingBottom = getCompoundPaddingBottom();
final int vspace = mBottom - mTop - compoundPaddingBottom - compoundPaddingTop;
@@ -5093,7 +5220,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
scrollX += mPaddingLeft;
scrollY += compoundPaddingTop + (vspace - drawables.mDrawableHeightLeft) / 2;
handled = true;
- } else if (drawable == drawables.mDrawableRight) {
+ } else if (drawable == drawables.mShowing[Drawables.RIGHT]) {
final int compoundPaddingTop = getCompoundPaddingTop();
final int compoundPaddingBottom = getCompoundPaddingBottom();
final int vspace = mBottom - mTop - compoundPaddingBottom - compoundPaddingTop;
@@ -5101,7 +5228,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
scrollX += (mRight - mLeft - mPaddingRight - drawables.mDrawableSizeRight);
scrollY += compoundPaddingTop + (vspace - drawables.mDrawableHeightRight) / 2;
handled = true;
- } else if (drawable == drawables.mDrawableTop) {
+ } else if (drawable == drawables.mShowing[Drawables.TOP]) {
final int compoundPaddingLeft = getCompoundPaddingLeft();
final int compoundPaddingRight = getCompoundPaddingRight();
final int hspace = mRight - mLeft - compoundPaddingRight - compoundPaddingLeft;
@@ -5109,7 +5236,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
scrollX += compoundPaddingLeft + (hspace - drawables.mDrawableWidthTop) / 2;
scrollY += mPaddingTop;
handled = true;
- } else if (drawable == drawables.mDrawableBottom) {
+ } else if (drawable == drawables.mShowing[Drawables.BOTTOM]) {
final int compoundPaddingLeft = getCompoundPaddingLeft();
final int compoundPaddingRight = getCompoundPaddingRight();
final int hspace = mRight - mLeft - compoundPaddingRight - compoundPaddingLeft;
@@ -5313,44 +5440,44 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
// IMPORTANT: The coordinates computed are also used in invalidateDrawable()
// Make sure to update invalidateDrawable() when changing this code.
- if (dr.mDrawableLeft != null) {
+ if (dr.mShowing[Drawables.LEFT] != null) {
canvas.save();
canvas.translate(scrollX + mPaddingLeft + leftOffset,
scrollY + compoundPaddingTop +
(vspace - dr.mDrawableHeightLeft) / 2);
- dr.mDrawableLeft.draw(canvas);
+ dr.mShowing[Drawables.LEFT].draw(canvas);
canvas.restore();
}
// IMPORTANT: The coordinates computed are also used in invalidateDrawable()
// Make sure to update invalidateDrawable() when changing this code.
- if (dr.mDrawableRight != null) {
+ if (dr.mShowing[Drawables.RIGHT] != null) {
canvas.save();
canvas.translate(scrollX + right - left - mPaddingRight
- dr.mDrawableSizeRight - rightOffset,
scrollY + compoundPaddingTop + (vspace - dr.mDrawableHeightRight) / 2);
- dr.mDrawableRight.draw(canvas);
+ dr.mShowing[Drawables.RIGHT].draw(canvas);
canvas.restore();
}
// IMPORTANT: The coordinates computed are also used in invalidateDrawable()
// Make sure to update invalidateDrawable() when changing this code.
- if (dr.mDrawableTop != null) {
+ if (dr.mShowing[Drawables.TOP] != null) {
canvas.save();
canvas.translate(scrollX + compoundPaddingLeft +
(hspace - dr.mDrawableWidthTop) / 2, scrollY + mPaddingTop);
- dr.mDrawableTop.draw(canvas);
+ dr.mShowing[Drawables.TOP].draw(canvas);
canvas.restore();
}
// IMPORTANT: The coordinates computed are also used in invalidateDrawable()
// Make sure to update invalidateDrawable() when changing this code.
- if (dr.mDrawableBottom != null) {
+ if (dr.mShowing[Drawables.BOTTOM] != null) {
canvas.save();
canvas.translate(scrollX + compoundPaddingLeft +
(hspace - dr.mDrawableWidthBottom) / 2,
scrollY + bottom - top - mPaddingBottom - dr.mDrawableSizeBottom);
- dr.mDrawableBottom.draw(canvas);
+ dr.mShowing[Drawables.BOTTOM].draw(canvas);
canvas.restore();
}
}
@@ -8381,9 +8508,10 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
}
+ /** @hide */
@Override
- public void onPopulateAccessibilityEvent(AccessibilityEvent event) {
- super.onPopulateAccessibilityEvent(event);
+ public void onPopulateAccessibilityEventInternal(AccessibilityEvent event) {
+ super.onPopulateAccessibilityEventInternal(event);
final boolean isPassword = hasPasswordTransformationMethod();
if (!isPassword || shouldSpeakPasswordsForAccessibility()) {
@@ -8405,10 +8533,25 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
@Override
- public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
- super.onInitializeAccessibilityEvent(event);
+ public CharSequence getAccessibilityClassName() {
+ return TextView.class.getName();
+ }
+
+ @Override
+ public void onProvideAssistData(ViewAssistData data, Bundle extras) {
+ super.onProvideAssistData(data, extras);
+ final boolean isPassword = hasPasswordTransformationMethod();
+ if (!isPassword) {
+ data.setText(getText(), getSelectionStart(), getSelectionEnd());
+ }
+ data.setHint(getHint());
+ }
+
+ /** @hide */
+ @Override
+ public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) {
+ super.onInitializeAccessibilityEventInternal(event);
- event.setClassName(TextView.class.getName());
final boolean isPassword = hasPasswordTransformationMethod();
event.setPassword(isPassword);
@@ -8419,11 +8562,11 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
}
+ /** @hide */
@Override
- public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(info);
+ public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
+ super.onInitializeAccessibilityNodeInfoInternal(info);
- info.setClassName(TextView.class.getName());
final boolean isPassword = hasPasswordTransformationMethod();
info.setPassword(isPassword);
@@ -8579,15 +8722,16 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
}
+ /** @hide */
@Override
- public void sendAccessibilityEvent(int eventType) {
+ public void sendAccessibilityEventInternal(int eventType) {
// Do not send scroll events since first they are not interesting for
// accessibility and second such events a generated too frequently.
// For details see the implementation of bringTextIntoView().
if (eventType == AccessibilityEvent.TYPE_VIEW_SCROLLED) {
return;
}
- super.sendAccessibilityEvent(eventType);
+ super.sendAccessibilityEventInternal(eventType);
}
/**
diff --git a/core/java/android/widget/TextViewWithCircularIndicator.java b/core/java/android/widget/TextViewWithCircularIndicator.java
index 43c0843..d3c786c 100644
--- a/core/java/android/widget/TextViewWithCircularIndicator.java
+++ b/core/java/android/widget/TextViewWithCircularIndicator.java
@@ -18,7 +18,6 @@ package android.widget;
import android.content.Context;
import android.content.res.Resources;
-import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Typeface;
@@ -27,14 +26,8 @@ import android.util.AttributeSet;
import com.android.internal.R;
class TextViewWithCircularIndicator extends TextView {
-
- private static final int SELECTED_CIRCLE_ALPHA = 60;
-
private final Paint mCirclePaint = new Paint();
-
private final String mItemIsSelectedText;
- private int mCircleColor;
- private boolean mDrawIndicator;
public TextViewWithCircularIndicator(Context context) {
this(context, null);
@@ -50,22 +43,11 @@ class TextViewWithCircularIndicator extends TextView {
public TextViewWithCircularIndicator(Context context, AttributeSet attrs,
int defStyleAttr, int defStyleRes) {
- super(context, attrs);
-
-
- // Use Theme attributes if possible
- final TypedArray a = mContext.obtainStyledAttributes(attrs,
- R.styleable.DatePicker, defStyleAttr, defStyleRes);
- final int resId = a.getResourceId(R.styleable.DatePicker_yearListItemTextAppearance, -1);
- if (resId != -1) {
- setTextAppearance(context, resId);
- }
+ super(context, attrs, defStyleAttr, defStyleRes);
final Resources res = context.getResources();
mItemIsSelectedText = res.getString(R.string.item_is_selected);
- a.recycle();
-
init();
}
@@ -77,33 +59,26 @@ class TextViewWithCircularIndicator extends TextView {
}
public void setCircleColor(int color) {
- if (color != mCircleColor) {
- mCircleColor = color;
- mCirclePaint.setColor(mCircleColor);
- mCirclePaint.setAlpha(SELECTED_CIRCLE_ALPHA);
- requestLayout();
- }
- }
-
- public void setDrawIndicator(boolean drawIndicator) {
- mDrawIndicator = drawIndicator;
+ mCirclePaint.setColor(color);
+ invalidate();
}
@Override
public void onDraw(Canvas canvas) {
- super.onDraw(canvas);
- if (mDrawIndicator) {
+ if (isActivated()) {
final int width = getWidth();
final int height = getHeight();
- int radius = Math.min(width, height) / 2;
+ final int radius = Math.min(width, height) / 2;
canvas.drawCircle(width / 2, height / 2, radius, mCirclePaint);
}
+
+ super.onDraw(canvas);
}
@Override
public CharSequence getContentDescription() {
- CharSequence itemText = getText();
- if (mDrawIndicator) {
+ final CharSequence itemText = getText();
+ if (isActivated()) {
return String.format(mItemIsSelectedText, itemText);
} else {
return itemText;
diff --git a/core/java/android/widget/TimePicker.java b/core/java/android/widget/TimePicker.java
index 26e02f8..9df8a21 100644
--- a/core/java/android/widget/TimePicker.java
+++ b/core/java/android/widget/TimePicker.java
@@ -196,26 +196,14 @@ public class TimePicker extends FrameLayout {
}
@Override
- public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
- return mDelegate.dispatchPopulateAccessibilityEvent(event);
- }
-
- @Override
- public void onPopulateAccessibilityEvent(AccessibilityEvent event) {
- super.onPopulateAccessibilityEvent(event);
- mDelegate.onPopulateAccessibilityEvent(event);
+ public CharSequence getAccessibilityClassName() {
+ return TimePicker.class.getName();
}
+ /** @hide */
@Override
- public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
- super.onInitializeAccessibilityEvent(event);
- mDelegate.onInitializeAccessibilityEvent(event);
- }
-
- @Override
- public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(info);
- mDelegate.onInitializeAccessibilityNodeInfo(info);
+ public boolean dispatchPopulateAccessibilityEventInternal(AccessibilityEvent event) {
+ return mDelegate.dispatchPopulateAccessibilityEvent(event);
}
/**
@@ -248,8 +236,6 @@ public class TimePicker extends FrameLayout {
boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event);
void onPopulateAccessibilityEvent(AccessibilityEvent event);
- void onInitializeAccessibilityEvent(AccessibilityEvent event);
- void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info);
}
/**
diff --git a/core/java/android/widget/TimePickerClockDelegate.java b/core/java/android/widget/TimePickerClockDelegate.java
index 1534429..05c7a5f 100644
--- a/core/java/android/widget/TimePickerClockDelegate.java
+++ b/core/java/android/widget/TimePickerClockDelegate.java
@@ -466,16 +466,6 @@ class TimePickerClockDelegate extends TimePicker.AbstractTimePickerDelegate impl
event.getText().add(selectedDate);
}
- @Override
- public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
- event.setClassName(TimePicker.class.getName());
- }
-
- @Override
- public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
- info.setClassName(TimePicker.class.getName());
- }
-
/**
* Set whether in keyboard mode or not.
*
diff --git a/core/java/android/widget/TimePickerSpinnerDelegate.java b/core/java/android/widget/TimePickerSpinnerDelegate.java
index e162f4a..af69110 100644
--- a/core/java/android/widget/TimePickerSpinnerDelegate.java
+++ b/core/java/android/widget/TimePickerSpinnerDelegate.java
@@ -427,16 +427,6 @@ class TimePickerSpinnerDelegate extends TimePicker.AbstractTimePickerDelegate {
event.getText().add(selectedDateUtterance);
}
- @Override
- public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
- event.setClassName(TimePicker.class.getName());
- }
-
- @Override
- public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
- info.setClassName(TimePicker.class.getName());
- }
-
private void updateInputState() {
// Make sure that if the user changes the value and the IME is active
// for one of the inputs if this widget, the IME is closed. If the user
diff --git a/core/java/android/widget/ToggleButton.java b/core/java/android/widget/ToggleButton.java
index 28519d1..4f8342b 100644
--- a/core/java/android/widget/ToggleButton.java
+++ b/core/java/android/widget/ToggleButton.java
@@ -154,14 +154,7 @@ public class ToggleButton extends CompoundButton {
}
@Override
- public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
- super.onInitializeAccessibilityEvent(event);
- event.setClassName(ToggleButton.class.getName());
- }
-
- @Override
- public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(info);
- info.setClassName(ToggleButton.class.getName());
+ public CharSequence getAccessibilityClassName() {
+ return ToggleButton.class.getName();
}
}
diff --git a/core/java/android/widget/TwoLineListItem.java b/core/java/android/widget/TwoLineListItem.java
index 5606c60..0cd7eb9 100644
--- a/core/java/android/widget/TwoLineListItem.java
+++ b/core/java/android/widget/TwoLineListItem.java
@@ -94,14 +94,7 @@ public class TwoLineListItem extends RelativeLayout {
}
@Override
- public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
- super.onInitializeAccessibilityEvent(event);
- event.setClassName(TwoLineListItem.class.getName());
- }
-
- @Override
- public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(info);
- info.setClassName(TwoLineListItem.class.getName());
+ public CharSequence getAccessibilityClassName() {
+ return TwoLineListItem.class.getName();
}
}
diff --git a/core/java/android/widget/VideoView.java b/core/java/android/widget/VideoView.java
index 572cca2..48283d4 100644
--- a/core/java/android/widget/VideoView.java
+++ b/core/java/android/widget/VideoView.java
@@ -202,15 +202,8 @@ public class VideoView extends SurfaceView
}
@Override
- public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
- super.onInitializeAccessibilityEvent(event);
- event.setClassName(VideoView.class.getName());
- }
-
- @Override
- public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(info);
- info.setClassName(VideoView.class.getName());
+ public CharSequence getAccessibilityClassName() {
+ return VideoView.class.getName();
}
public int resolveAdjustedSize(int desiredSize, int measureSpec) {
diff --git a/core/java/android/widget/ViewAnimator.java b/core/java/android/widget/ViewAnimator.java
index eee914e..5ef5222 100644
--- a/core/java/android/widget/ViewAnimator.java
+++ b/core/java/android/widget/ViewAnimator.java
@@ -358,14 +358,7 @@ public class ViewAnimator extends FrameLayout {
}
@Override
- public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
- super.onInitializeAccessibilityEvent(event);
- event.setClassName(ViewAnimator.class.getName());
- }
-
- @Override
- public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(info);
- info.setClassName(ViewAnimator.class.getName());
+ public CharSequence getAccessibilityClassName() {
+ return ViewAnimator.class.getName();
}
}
diff --git a/core/java/android/widget/ViewFlipper.java b/core/java/android/widget/ViewFlipper.java
index cf1f554..a43a185 100644
--- a/core/java/android/widget/ViewFlipper.java
+++ b/core/java/android/widget/ViewFlipper.java
@@ -150,15 +150,8 @@ public class ViewFlipper extends ViewAnimator {
}
@Override
- public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
- super.onInitializeAccessibilityEvent(event);
- event.setClassName(ViewFlipper.class.getName());
- }
-
- @Override
- public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(info);
- info.setClassName(ViewFlipper.class.getName());
+ public CharSequence getAccessibilityClassName() {
+ return ViewFlipper.class.getName();
}
/**
diff --git a/core/java/android/widget/ViewSwitcher.java b/core/java/android/widget/ViewSwitcher.java
index 0376918..2f544cc 100644
--- a/core/java/android/widget/ViewSwitcher.java
+++ b/core/java/android/widget/ViewSwitcher.java
@@ -69,15 +69,8 @@ public class ViewSwitcher extends ViewAnimator {
}
@Override
- public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
- super.onInitializeAccessibilityEvent(event);
- event.setClassName(ViewSwitcher.class.getName());
- }
-
- @Override
- public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(info);
- info.setClassName(ViewSwitcher.class.getName());
+ public CharSequence getAccessibilityClassName() {
+ return ViewSwitcher.class.getName();
}
/**
diff --git a/core/java/android/widget/YearPickerView.java b/core/java/android/widget/YearPickerView.java
index 24ed7ce..6f0465f 100644
--- a/core/java/android/widget/YearPickerView.java
+++ b/core/java/android/widget/YearPickerView.java
@@ -17,8 +17,10 @@
package android.widget;
import android.content.Context;
+import android.content.res.ColorStateList;
import android.content.res.Resources;
import android.util.AttributeSet;
+import android.util.StateSet;
import android.view.View;
import android.view.ViewGroup;
import android.view.accessibility.AccessibilityEvent;
@@ -42,7 +44,7 @@ class YearPickerView extends ListView implements AdapterView.OnItemClickListener
private DatePickerController mController;
private int mSelectedPosition = -1;
- private int mYearSelectedCircleColor;
+ private int mYearActivatedColor;
public YearPickerView(Context context) {
this(context, null);
@@ -97,15 +99,14 @@ class YearPickerView extends ListView implements AdapterView.OnItemClickListener
onDateChanged();
}
- public void setYearSelectedCircleColor(int color) {
- if (color != mYearSelectedCircleColor) {
- mYearSelectedCircleColor = color;
- }
- requestLayout();
+ public void setYearBackgroundColor(ColorStateList yearBackgroundColor) {
+ mYearActivatedColor = yearBackgroundColor.getColorForState(
+ StateSet.get(StateSet.VIEW_STATE_ENABLED | StateSet.VIEW_STATE_ACTIVATED), 0);
+ invalidate();
}
- public int getYearSelectedCircleColor() {
- return mYearSelectedCircleColor;
+ public void setYearTextAppearance(int resId) {
+ mAdapter.setItemTextAppearance(resId);
}
private void updateAdapterData() {
@@ -127,12 +128,8 @@ class YearPickerView extends ListView implements AdapterView.OnItemClickListener
mController.onYearSelected(mAdapter.getItem(position));
}
- void setItemTextAppearance(int resId) {
- mAdapter.setItemTextAppearance(resId);
- }
-
private class YearAdapter extends ArrayAdapter<Integer> {
- int mItemTextAppearanceResId;
+ private int mItemTextAppearanceResId;
public YearAdapter(Context context, int resource) {
super(context, resource);
@@ -140,16 +137,15 @@ class YearPickerView extends ListView implements AdapterView.OnItemClickListener
@Override
public View getView(int position, View convertView, ViewGroup parent) {
- TextViewWithCircularIndicator v = (TextViewWithCircularIndicator)
+ final TextViewWithCircularIndicator v = (TextViewWithCircularIndicator)
super.getView(position, convertView, parent);
- v.setTextAppearance(getContext(), mItemTextAppearanceResId);
- v.requestLayout();
- int year = getItem(position);
- boolean selected = mController.getSelectedDay().get(Calendar.YEAR) == year;
- v.setDrawIndicator(selected);
- if (selected) {
- v.setCircleColor(mYearSelectedCircleColor);
- }
+ v.setTextAppearance(v.getContext(), mItemTextAppearanceResId);
+ v.setCircleColor(mYearActivatedColor);
+
+ final int year = getItem(position);
+ final boolean selected = mController.getSelectedDay().get(Calendar.YEAR) == year;
+ v.setActivated(selected);
+
return v;
}
@@ -189,9 +185,10 @@ class YearPickerView extends ListView implements AdapterView.OnItemClickListener
mController.getSelectedDay().get(Calendar.YEAR) - mMinDate.get(Calendar.YEAR));
}
+ /** @hide */
@Override
- public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
- super.onInitializeAccessibilityEvent(event);
+ public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) {
+ super.onInitializeAccessibilityEventInternal(event);
if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_SCROLLED) {
event.setFromIndex(0);
diff --git a/core/java/android/widget/ZoomButton.java b/core/java/android/widget/ZoomButton.java
index 715e868..6b3faed 100644
--- a/core/java/android/widget/ZoomButton.java
+++ b/core/java/android/widget/ZoomButton.java
@@ -104,14 +104,7 @@ public class ZoomButton extends ImageButton implements OnLongClickListener {
}
@Override
- public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
- super.onInitializeAccessibilityEvent(event);
- event.setClassName(ZoomButton.class.getName());
- }
-
- @Override
- public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(info);
- info.setClassName(ZoomButton.class.getName());
+ public CharSequence getAccessibilityClassName() {
+ return ZoomButton.class.getName();
}
}
diff --git a/core/java/android/widget/ZoomControls.java b/core/java/android/widget/ZoomControls.java
index 8897875..bef1ace 100644
--- a/core/java/android/widget/ZoomControls.java
+++ b/core/java/android/widget/ZoomControls.java
@@ -110,14 +110,7 @@ public class ZoomControls extends LinearLayout {
}
@Override
- public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
- super.onInitializeAccessibilityEvent(event);
- event.setClassName(ZoomControls.class.getName());
- }
-
- @Override
- public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(info);
- info.setClassName(ZoomControls.class.getName());
+ public CharSequence getAccessibilityClassName() {
+ return ZoomControls.class.getName();
}
}